Merge pull request #18106 from runcom/bump-runc-libcontainer

vendor: bump libcontainer and its deps
This commit is contained in:
Alexander Morozov 2015-11-26 12:15:48 -08:00
commit 0b5a28590f
65 changed files with 1344 additions and 828 deletions

View File

@ -22,7 +22,7 @@ clone git github.com/vdemeester/shakers 3c10293ce22b900c27acad7b28656196fcc2f73b
clone git golang.org/x/net 47990a1ba55743e6ef1affd3a14e5bac8553615d https://github.com/golang/net.git
#get libnetwork packages
clone git github.com/docker/libnetwork b4ddf18317b19d6e4bcc821145589749206a7d00
clone git github.com/docker/libnetwork 04cc1fa0a89f8c407b7be8cab883d4b17531ea7d
clone git github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec
clone git github.com/hashicorp/go-msgpack 71c2886f5a673a35f909803f38ece5810165097b
clone git github.com/hashicorp/memberlist 9a1e242e454d2443df330bdd51a436d5a9058fc4
@ -49,14 +49,14 @@ clone git github.com/miekg/pkcs11 80f102b5cac759de406949c47f0928b99bd64cdf
clone git github.com/jfrazelle/go v1.5.1-1
clone git github.com/agl/ed25519 d2b94fd789ea21d12fac1a4443dd3a3f79cda72c
# this runc commit from branch relabel_fix_docker_1.9.1, pls remove it when you
# update next time
clone git github.com/opencontainers/runc 1349b37bd56f4f5ce2690b5b2c0f53f88a261c67 # libcontainer
clone git github.com/opencontainers/runc v0.0.5 # libcontainer
# libcontainer deps (see src/github.com/opencontainers/runc/Godeps/Godeps.json)
clone git github.com/coreos/go-systemd v4
clone git github.com/godbus/dbus v2
clone git github.com/syndtr/gocapability 66ef2aa7a23ba682594e2b6f74cf40c0692b49fb
clone git github.com/golang/protobuf 655cdfa588ea
clone git github.com/godbus/dbus v3
clone git github.com/syndtr/gocapability 2c00daeb6c3b45114c80ac44119e7b8801fdd852
clone git github.com/golang/protobuf f7137ae6b19afbfd61a94b746fda3b3fe0491874
# gelf logging driver deps
clone git github.com/Graylog2/go-gelf 6c62a85f1d47a67f2a5144c0e745b325889a8120
clone git github.com/fluent/fluent-logger-golang v1.0.0

View File

@ -64,11 +64,11 @@ func PathBusEscape(path string) string {
type Conn struct {
// sysconn/sysobj are only used to call dbus methods
sysconn *dbus.Conn
sysobj *dbus.Object
sysobj dbus.BusObject
// sigconn/sigobj are only used to receive dbus signals
sigconn *dbus.Conn
sigobj *dbus.Object
sigobj dbus.BusObject
jobListener struct {
jobs map[dbus.ObjectPath]chan<- string
@ -86,14 +86,30 @@ type Conn struct {
// New establishes a connection to the system bus and authenticates.
// Callers should call Close() when done with the connection.
func New() (*Conn, error) {
return newConnection(dbus.SystemBusPrivate)
return newConnection(func() (*dbus.Conn, error) {
return dbusAuthHelloConnection(dbus.SystemBusPrivate)
})
}
// NewUserConnection establishes a connection to the session bus and
// authenticates. This can be used to connect to systemd user instances.
// Callers should call Close() when done with the connection.
func NewUserConnection() (*Conn, error) {
return newConnection(dbus.SessionBusPrivate)
return newConnection(func() (*dbus.Conn, error) {
return dbusAuthHelloConnection(dbus.SessionBusPrivate)
})
}
// NewSystemdConnection establishes a private, direct connection to systemd.
// This can be used for communicating with systemd without a dbus daemon.
// Callers should call Close() when done with the connection.
func NewSystemdConnection() (*Conn, error) {
return newConnection(func() (*dbus.Conn, error) {
// We skip Hello when talking directly to systemd.
return dbusAuthConnection(func() (*dbus.Conn, error) {
return dbus.Dial("unix:path=/run/systemd/private")
})
})
}
// Close closes an established connection
@ -103,12 +119,12 @@ func (c *Conn) Close() {
}
func newConnection(createBus func() (*dbus.Conn, error)) (*Conn, error) {
sysconn, err := dbusConnection(createBus)
sysconn, err := createBus()
if err != nil {
return nil, err
}
sigconn, err := dbusConnection(createBus)
sigconn, err := createBus()
if err != nil {
sysconn.Close()
return nil, err
@ -132,7 +148,7 @@ func newConnection(createBus func() (*dbus.Conn, error)) (*Conn, error) {
return c, nil
}
func dbusConnection(createBus func() (*dbus.Conn, error)) (*dbus.Conn, error) {
func dbusAuthConnection(createBus func() (*dbus.Conn, error)) (*dbus.Conn, error) {
conn, err := createBus()
if err != nil {
return nil, err
@ -149,8 +165,16 @@ func dbusConnection(createBus func() (*dbus.Conn, error)) (*dbus.Conn, error) {
return nil, err
}
err = conn.Hello()
return conn, nil
}
func dbusAuthHelloConnection(createBus func() (*dbus.Conn, error)) (*dbus.Conn, error) {
conn, err := dbusAuthConnection(createBus)
if err != nil {
return nil, err
}
if err = conn.Hello(); err != nil {
conn.Close()
return nil, err
}
@ -158,6 +182,6 @@ func dbusConnection(createBus func() (*dbus.Conn, error)) (*dbus.Conn, error) {
return conn, nil
}
func systemdObject(conn *dbus.Conn) *dbus.Object {
func systemdObject(conn *dbus.Conn) dbus.BusObject {
return conn.Object("org.freedesktop.systemd1", dbus.ObjectPath("/org/freedesktop/systemd1"))
}

View File

@ -12,7 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Package journal provides write bindings to the systemd journal
// Package journal provides write bindings to the local systemd journal.
// It is implemented in pure Go and connects to the journal directly over its
// unix socket.
//
// To read from the journal, see the "sdjournal" package, which wraps the
// sd-journal a C API.
//
// http://www.freedesktop.org/software/systemd/man/systemd-journald.service.html
package journal
import (
@ -53,14 +60,14 @@ func init() {
}
}
// Enabled returns true iff the systemd journal is available for logging
// Enabled returns true if the local systemd journal is available for logging
func Enabled() bool {
return conn != nil
}
// Send a message to the systemd journal. vars is a map of journald fields to
// values. Fields must be composed of uppercase letters, numbers, and
// underscores, but must not start with an underscore. Within these
// Send a message to the local systemd journal. vars is a map of journald
// fields to values. Fields must be composed of uppercase letters, numbers,
// and underscores, but must not start with an underscore. Within these
// restrictions, any arbitrary field name may be used. Some names have special
// significance: see the journalctl documentation
// (http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html)
@ -102,6 +109,11 @@ func Send(message string, priority Priority, vars map[string]string) error {
return nil
}
// Print prints a message to the local systemd journal using Send().
func Print(priority Priority, format string, a ...interface{}) error {
return Send(fmt.Sprintf(format, a...), priority, nil)
}
func appendVariable(w io.Writer, name, value string) {
if !validVarName(name) {
journalError("variable name contains invalid character, ignoring")

View File

@ -0,0 +1 @@
*

View File

@ -2,6 +2,7 @@
*.o
*.a
*.so
bin/
# Folders
integration-tmp/
@ -33,4 +34,4 @@ cmd/dnet/dnet
.project
.settings/
libnetwork-build.created
libnetworkbuild.created

View File

@ -0,0 +1,8 @@
FROM golang:1.4-cross
RUN apt-get update && apt-get -y install iptables
RUN go get github.com/tools/godep \
github.com/golang/lint/golint \
golang.org/x/tools/cmd/vet \
golang.org/x/tools/cmd/goimports \
golang.org/x/tools/cmd/cover\
github.com/mattn/goveralls

View File

@ -1,33 +1,20 @@
.PHONY: all all-local build build-local check check-code check-format run-tests check-local integration-tests install-deps coveralls circle-ci start-services clean
.PHONY: all all-local build build-local clean cross cross-local check check-code check-format run-tests integration-tests check-local coveralls circle-ci-cross circle-ci-build circle-ci-check circle-ci
SHELL=/bin/bash
build_image=libnetworkbuild
dockerargs = --privileged -v $(shell pwd):/go/src/github.com/docker/libnetwork -w /go/src/github.com/docker/libnetwork
container_env = -e "INSIDECONTAINER=-incontainer=true"
docker = docker run --rm -it ${dockerargs} ${container_env} ${build_image}
docker = docker run --rm -it ${dockerargs} $$EXTRA_ARGS ${container_env} ${build_image}
ciargs = -e "COVERALLS_TOKEN=$$COVERALLS_TOKEN" -e "INSIDECONTAINER=-incontainer=true"
cidocker = docker run ${ciargs} ${dockerargs} golang:1.4
cidocker = docker run ${dockerargs} ${ciargs} ${container_env} ${build_image}
CROSS_PLATFORMS = linux/amd64 linux/386 linux/arm windows/amd64 windows/386
${build_image}.created:
docker build -f Dockerfile.build -t ${build_image} .
touch ${build_image}.created
all: ${build_image}.created build check integration-tests clean
integration-tests: ./cmd/dnet/dnet
@./test/integration/dnet/run-integration-tests.sh
./cmd/dnet/dnet:
make build
clean:
@if [ -e ./cmd/dnet/dnet ]; then \
echo "Removing dnet binary"; \
rm -rf ./cmd/dnet/dnet; \
fi
all-local: check-local build-local
${build_image}.created:
docker run --name=libnetworkbuild -v $(shell pwd):/go/src/github.com/docker/libnetwork -w /go/src/github.com/docker/libnetwork golang:1.4 make install-deps
docker commit libnetworkbuild ${build_image}
docker rm libnetworkbuild
touch ${build_image}.created
all-local: build-local check-local integration-tests-local clean
build: ${build_image}.created
@echo "Building code... "
@ -35,8 +22,25 @@ build: ${build_image}.created
@echo "Done building code"
build-local:
@$(shell which godep) go build ./...
@$(shell which godep) go build -o ./cmd/dnet/dnet ./cmd/dnet
@mkdir -p "bin"
$(shell which godep) go build -o "bin/dnet" ./cmd/dnet
clean:
@if [ -d bin ]; then \
echo "Removing dnet binaries"; \
rm -rf bin; \
fi
cross: ${build_image}.created
@mkdir -p "bin"
@for platform in ${CROSS_PLATFORMS}; do \
EXTRA_ARGS="-e GOOS=$${platform%/*} -e GOARCH=$${platform##*/}" ; \
echo "$${platform}..." ; \
${docker} make cross-local ; \
done
cross-local:
$(shell which godep) go build -o "bin/dnet-$$GOOS-$$GOARCH" ./cmd/dnet
check: ${build_image}.created
@${docker} ./wrapmake.sh check-local
@ -71,27 +75,31 @@ run-tests:
done
@echo "Done running tests"
check-local: check-format check-code start-services run-tests
check-local: check-format check-code run-tests
install-deps:
apt-get update && apt-get -y install iptables zookeeperd
git clone https://github.com/golang/tools /go/src/golang.org/x/tools
go install golang.org/x/tools/cmd/vet
go install golang.org/x/tools/cmd/goimports
go install golang.org/x/tools/cmd/cover
go get github.com/tools/godep
go get github.com/golang/lint/golint
go get github.com/mattn/goveralls
integration-tests: ./bin/dnet
@./test/integration/dnet/run-integration-tests.sh
./bin/dnet:
make build
coveralls:
-@goveralls -service circleci -coverprofile=coverage.coverprofile -repotoken $$COVERALLS_TOKEN
# CircleCI's Docker fails when cleaning up using the --rm flag
# The following target is a workaround for this
# The following targets are a workaround for this
circle-ci-cross: ${build_image}.created
@mkdir -p "bin"
@for platform in ${CROSS_PLATFORMS}; do \
EXTRA_ARGS="-e GOOS=$${platform%/*} -e GOARCH=$${platform##*/}" ; \
echo "$${platform}..." ; \
${cidocker} make cross-local ; \
done
circle-ci:
@${cidocker} make install-deps build-local check-local coveralls
make integration-tests
circle-ci-check: ${build_image}.created
@${cidocker} make check-local coveralls
start-services:
service zookeeper start
circle-ci-build: ${build_image}.created
@${cidocker} make build-local
circle-ci: circle-ci-check circle-ci-build integration-tests

View File

@ -552,7 +552,7 @@ func pushReservation(bytePos, bitPos uint64, head *sequence, release bool) *sequ
}
removeCurrentIfEmpty(&newHead, newSequence, current)
mergeSequences(previous)
} else if precBlocks == current.count-2 { // Last in sequence (B)
} else if precBlocks == current.count { // Last in sequence (B)
newSequence.next = current.next
current.next = newSequence
mergeSequences(current)

View File

@ -1,12 +1,18 @@
machine:
services:
- docker
services:
- docker
dependencies:
override:
- echo "Nothing to install"
override:
- sudo apt-get update; sudo apt-get install -y iptables zookeeperd
- go get golang.org/x/tools/cmd/vet
- go get golang.org/x/tools/cmd/goimports
- go get golang.org/x/tools/cmd/cover
- go get github.com/tools/godep
- go get github.com/golang/lint/golint
- go get github.com/mattn/goveralls
test:
override:
- make circle-ci
override:
- make circle-ci

View File

@ -41,6 +41,9 @@ const (
DefaultGatewayV6AuxKey = "DefaultGatewayIPv6"
)
type iptableCleanFunc func() error
type iptablesCleanFuncs []iptableCleanFunc
// configuration info for the "bridge" driver.
type configuration struct {
EnableIPForwarding bool
@ -92,12 +95,13 @@ type bridgeEndpoint struct {
}
type bridgeNetwork struct {
id string
bridge *bridgeInterface // The bridge's L3 interface
config *networkConfiguration
endpoints map[string]*bridgeEndpoint // key: endpoint id
portMapper *portmapper.PortMapper
driver *driver // The network's driver
id string
bridge *bridgeInterface // The bridge's L3 interface
config *networkConfiguration
endpoints map[string]*bridgeEndpoint // key: endpoint id
portMapper *portmapper.PortMapper
driver *driver // The network's driver
iptCleanFuncs iptablesCleanFuncs
sync.Mutex
}
@ -236,6 +240,10 @@ func parseErr(label, value, errString string) error {
return types.BadRequestErrorf("failed to parse %s value: %v (%s)", label, value, errString)
}
func (n *bridgeNetwork) registerIptCleanFunc(clean iptableCleanFunc) {
n.iptCleanFuncs = append(n.iptCleanFuncs, clean)
}
func (n *bridgeNetwork) getDriverChains() (*iptables.ChainInfo, *iptables.ChainInfo, error) {
n.Lock()
defer n.Unlock()
@ -604,6 +612,10 @@ func (d *driver) createNetwork(config *networkConfiguration) error {
}
return err
}
network.registerIptCleanFunc(func() error {
nwList := d.getNetworks()
return network.isolateNetwork(nwList, false)
})
return nil
}
@ -722,22 +734,6 @@ func (d *driver) DeleteNetwork(nid string) error {
return err
}
// In case of failures after this point, restore the network isolation rules
nwList := d.getNetworks()
defer func() {
if err != nil {
if err := n.isolateNetwork(nwList, true); err != nil {
logrus.Warnf("Failed on restoring the inter-network iptables rules on cleanup: %v", err)
}
}
}()
// Remove inter-network communication rules.
err = n.isolateNetwork(nwList, false)
if err != nil {
return err
}
// We only delete the bridge when it's not the default bridge. This is keep the backward compatible behavior.
if !config.DefaultBridge {
if err := netlink.LinkDel(n.bridge.Link); err != nil {
@ -745,6 +741,12 @@ func (d *driver) DeleteNetwork(nid string) error {
}
}
// clean all relevant iptables rules
for _, cleanFunc := range n.iptCleanFuncs {
if errClean := cleanFunc(); errClean != nil {
logrus.Warnf("Failed to clean iptables rules for bridge network: %v", errClean)
}
}
return d.storeDelete(config)
}

View File

@ -68,21 +68,27 @@ func (n *bridgeNetwork) setupIPTables(config *networkConfiguration, i *bridgeInt
if err = setupIPTablesInternal(config.BridgeName, maskedAddrv4, config.EnableICC, config.EnableIPMasquerade, hairpinMode, true); err != nil {
return fmt.Errorf("Failed to Setup IP tables: %s", err.Error())
}
n.registerIptCleanFunc(func() error {
return setupIPTablesInternal(config.BridgeName, maskedAddrv4, config.EnableICC, config.EnableIPMasquerade, hairpinMode, false)
})
natChain, filterChain, err := n.getDriverChains()
if err != nil {
return fmt.Errorf("Failed to setup IP tables, cannot acquire chain info %s", err.Error())
}
err = iptables.ProgramChain(natChain, config.BridgeName, hairpinMode)
err = iptables.ProgramChain(natChain, config.BridgeName, hairpinMode, true)
if err != nil {
return fmt.Errorf("Failed to program NAT chain: %s", err.Error())
}
err = iptables.ProgramChain(filterChain, config.BridgeName, hairpinMode)
err = iptables.ProgramChain(filterChain, config.BridgeName, hairpinMode, true)
if err != nil {
return fmt.Errorf("Failed to program FILTER chain: %s", err.Error())
}
n.registerIptCleanFunc(func() error {
return iptables.ProgramChain(filterChain, config.BridgeName, hairpinMode, false)
})
n.portMapper.SetIptablesChain(filterChain, n.getNetworkBridgeName())

View File

@ -159,7 +159,11 @@ func (ep *endpoint) Info() EndpointInfo {
return ep
}
return sb.getEndpoint(ep.ID())
if epi := sb.getEndpoint(ep.ID()); epi != nil {
return epi
}
return nil
}
func (ep *endpoint) DriverInfo() (map[string]interface{}, error) {

View File

@ -27,7 +27,7 @@ const (
// Conn is a connection to firewalld dbus endpoint.
type Conn struct {
sysconn *dbus.Conn
sysobj *dbus.Object
sysobj dbus.BusObject
signal chan *dbus.Signal
}

View File

@ -95,7 +95,7 @@ func NewChain(name string, table Table, hairpinMode bool) (*ChainInfo, error) {
}
// ProgramChain is used to add rules to a chain
func ProgramChain(c *ChainInfo, bridgeName string, hairpinMode bool) error {
func ProgramChain(c *ChainInfo, bridgeName string, hairpinMode, enable bool) error {
if c.Name == "" {
return fmt.Errorf("Could not program chain, missing chain name.")
}
@ -106,10 +106,14 @@ func ProgramChain(c *ChainInfo, bridgeName string, hairpinMode bool) error {
"-m", "addrtype",
"--dst-type", "LOCAL",
"-j", c.Name}
if !Exists(Nat, "PREROUTING", preroute...) {
if !Exists(Nat, "PREROUTING", preroute...) && enable {
if err := c.Prerouting(Append, preroute...); err != nil {
return fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err)
}
} else if Exists(Nat, "PREROUTING", preroute...) && !enable {
if err := c.Prerouting(Delete, preroute...); err != nil {
return fmt.Errorf("Failed to remove docker in PREROUTING chain: %s", err)
}
}
output := []string{
"-m", "addrtype",
@ -118,10 +122,14 @@ func ProgramChain(c *ChainInfo, bridgeName string, hairpinMode bool) error {
if !hairpinMode {
output = append(output, "!", "--dst", "127.0.0.0/8")
}
if !Exists(Nat, "OUTPUT", output...) {
if !Exists(Nat, "OUTPUT", output...) && enable {
if err := c.Output(Append, output...); err != nil {
return fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
}
} else if Exists(Nat, "OUTPUT", output...) && !enable {
if err := c.Output(Delete, output...); err != nil {
return fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
}
}
case Filter:
if bridgeName == "" {
@ -131,13 +139,21 @@ func ProgramChain(c *ChainInfo, bridgeName string, hairpinMode bool) error {
link := []string{
"-o", bridgeName,
"-j", c.Name}
if !Exists(Filter, "FORWARD", link...) {
if !Exists(Filter, "FORWARD", link...) && enable {
insert := append([]string{string(Insert), "FORWARD"}, link...)
if output, err := Raw(insert...); err != nil {
return err
} else if len(output) != 0 {
return fmt.Errorf("Could not create linking rule to %s/%s: %s", c.Table, c.Name, output)
}
} else if Exists(Filter, "FORWARD", link...) && !enable {
del := append([]string{string(Delete), "FORWARD"}, link...)
if output, err := Raw(del...); err != nil {
return err
} else if len(output) != 0 {
return fmt.Errorf("Could not delete linking rule from %s/%s: %s", c.Table, c.Name, output)
}
}
}
return nil

View File

@ -9,10 +9,8 @@ import (
"fmt"
"io"
"net"
"strings"
"github.com/docker/libnetwork/types"
"github.com/vishvananda/netlink"
)
var (
@ -22,8 +20,6 @@ var (
ErrNetworkOverlaps = errors.New("requested network overlaps with existing network")
// ErrNoDefaultRoute preformatted error
ErrNoDefaultRoute = errors.New("no default route")
networkGetRoutesFct = netlink.RouteList
)
// CheckNameserverOverlaps checks whether the passed network overlaps with any of the nameservers
@ -42,21 +38,6 @@ func CheckNameserverOverlaps(nameservers []string, toCheck *net.IPNet) error {
return nil
}
// CheckRouteOverlaps checks whether the passed network overlaps with any existing routes
func CheckRouteOverlaps(toCheck *net.IPNet) error {
networks, err := networkGetRoutesFct(nil, netlink.FAMILY_V4)
if err != nil {
return err
}
for _, network := range networks {
if network.Dst != nil && NetworkOverlaps(toCheck, network.Dst) {
return ErrNetworkOverlaps
}
}
return nil
}
// NetworkOverlaps detects overlap between one IPNet and another
func NetworkOverlaps(netX *net.IPNet, netY *net.IPNet) bool {
return netX.Contains(netY.IP) || netY.Contains(netX.IP)
@ -151,22 +132,3 @@ func GenerateRandomName(prefix string, size int) (string, error) {
}
return prefix + hex.EncodeToString(id)[:size], nil
}
// GenerateIfaceName returns an interface name using the passed in
// prefix and the length of random bytes. The api ensures that the
// there are is no interface which exists with that name.
func GenerateIfaceName(prefix string, len int) (string, error) {
for i := 0; i < 3; i++ {
name, err := GenerateRandomName(prefix, len)
if err != nil {
continue
}
if _, err := netlink.LinkByName(name); err != nil {
if strings.Contains(err.Error(), "not found") {
return name, nil
}
return "", err
}
}
return "", types.InternalErrorf("could not generate interface name")
}

View File

@ -0,0 +1,50 @@
// +build linux
// Network utility functions.
package netutils
import (
"net"
"strings"
"github.com/docker/libnetwork/types"
"github.com/vishvananda/netlink"
)
var (
networkGetRoutesFct = netlink.RouteList
)
// CheckRouteOverlaps checks whether the passed network overlaps with any existing routes
func CheckRouteOverlaps(toCheck *net.IPNet) error {
networks, err := networkGetRoutesFct(nil, netlink.FAMILY_V4)
if err != nil {
return err
}
for _, network := range networks {
if network.Dst != nil && NetworkOverlaps(toCheck, network.Dst) {
return ErrNetworkOverlaps
}
}
return nil
}
// GenerateIfaceName returns an interface name using the passed in
// prefix and the length of random bytes. The api ensures that the
// there are is no interface which exists with that name.
func GenerateIfaceName(prefix string, len int) (string, error) {
for i := 0; i < 3; i++ {
name, err := GenerateRandomName(prefix, len)
if err != nil {
continue
}
if _, err := netlink.LinkByName(name); err != nil {
if strings.Contains(err.Error(), "not found") {
return name, nil
}
return "", err
}
}
return "", types.InternalErrorf("could not generate interface name")
}

View File

@ -671,6 +671,12 @@ func (n *network) CreateEndpoint(name string, options ...EndpointOption) (Endpoi
ep.processOptions(options...)
if opt, ok := ep.generic[netlabel.MacAddress]; ok {
if mac, ok := opt.(net.HardwareAddr); ok {
ep.iface.mac = mac
}
}
if err = ep.assignAddress(true, !n.postIPv6); err != nil {
return nil, err
}

View File

@ -1,19 +1,6 @@
package libnetwork
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/reexec"
"github.com/docker/libnetwork/types"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/configs"
)
import "github.com/docker/docker/pkg/reexec"
type setKeyData struct {
ContainerID string
@ -23,163 +10,3 @@ type setKeyData struct {
func init() {
reexec.Register("libnetwork-setkey", processSetKeyReexec)
}
const udsBase = "/var/lib/docker/network/files/"
const success = "success"
// processSetKeyReexec is a private function that must be called only on an reexec path
// It expects 3 args { [0] = "libnetwork-setkey", [1] = <container-id>, [2] = <controller-id> }
// It also expects libcontainer.State as a json string in <stdin>
// Refer to https://github.com/opencontainers/runc/pull/160/ for more information
func processSetKeyReexec() {
var err error
// Return a failure to the calling process via ExitCode
defer func() {
if err != nil {
logrus.Fatalf("%v", err)
}
}()
// expecting 3 args {[0]="libnetwork-setkey", [1]=<container-id>, [2]=<controller-id> }
if len(os.Args) < 3 {
err = fmt.Errorf("Re-exec expects 3 args, received : %d", len(os.Args))
return
}
containerID := os.Args[1]
// We expect libcontainer.State as a json string in <stdin>
stateBuf, err := ioutil.ReadAll(os.Stdin)
if err != nil {
return
}
var state libcontainer.State
if err = json.Unmarshal(stateBuf, &state); err != nil {
return
}
controllerID := os.Args[2]
key := state.NamespacePaths[configs.NamespaceType("NEWNET")]
err = SetExternalKey(controllerID, containerID, key)
return
}
// SetExternalKey provides a convenient way to set an External key to a sandbox
func SetExternalKey(controllerID string, containerID string, key string) error {
keyData := setKeyData{
ContainerID: containerID,
Key: key}
c, err := net.Dial("unix", udsBase+controllerID+".sock")
if err != nil {
return err
}
defer c.Close()
if err = sendKey(c, keyData); err != nil {
return fmt.Errorf("sendKey failed with : %v", err)
}
return processReturn(c)
}
func sendKey(c net.Conn, data setKeyData) error {
var err error
defer func() {
if err != nil {
c.Close()
}
}()
var b []byte
if b, err = json.Marshal(data); err != nil {
return err
}
_, err = c.Write(b)
return err
}
func processReturn(r io.Reader) error {
buf := make([]byte, 1024)
n, err := r.Read(buf[:])
if err != nil {
return fmt.Errorf("failed to read buf in processReturn : %v", err)
}
if string(buf[0:n]) != success {
return fmt.Errorf(string(buf[0:n]))
}
return nil
}
func (c *controller) startExternalKeyListener() error {
if err := os.MkdirAll(udsBase, 0600); err != nil {
return err
}
uds := udsBase + c.id + ".sock"
l, err := net.Listen("unix", uds)
if err != nil {
return err
}
if err := os.Chmod(uds, 0600); err != nil {
l.Close()
return err
}
c.Lock()
c.extKeyListener = l
c.Unlock()
go c.acceptClientConnections(uds, l)
return nil
}
func (c *controller) acceptClientConnections(sock string, l net.Listener) {
for {
conn, err := l.Accept()
if err != nil {
if _, err1 := os.Stat(sock); os.IsNotExist(err1) {
logrus.Debugf("Unix socket %s doesnt exist. cannot accept client connections", sock)
return
}
logrus.Errorf("Error accepting connection %v", err)
continue
}
go func() {
err := c.processExternalKey(conn)
ret := success
if err != nil {
ret = err.Error()
}
_, err = conn.Write([]byte(ret))
if err != nil {
logrus.Errorf("Error returning to the client %v", err)
}
}()
}
}
func (c *controller) processExternalKey(conn net.Conn) error {
buf := make([]byte, 1280)
nr, err := conn.Read(buf)
if err != nil {
return err
}
var s setKeyData
if err = json.Unmarshal(buf[0:nr], &s); err != nil {
return err
}
var sandbox Sandbox
search := SandboxContainerWalker(&sandbox, s.ContainerID)
c.WalkSandboxes(search)
if sandbox == nil {
return types.BadRequestErrorf("no sandbox present for %s", s.ContainerID)
}
return sandbox.SetKey(s.Key)
}
func (c *controller) stopExternalKeyListener() {
c.extKeyListener.Close()
}

View File

@ -0,0 +1,177 @@
// +build !windows
package libnetwork
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"github.com/Sirupsen/logrus"
"github.com/docker/libnetwork/types"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/configs"
)
const udsBase = "/var/lib/docker/network/files/"
const success = "success"
// processSetKeyReexec is a private function that must be called only on an reexec path
// It expects 3 args { [0] = "libnetwork-setkey", [1] = <container-id>, [2] = <controller-id> }
// It also expects libcontainer.State as a json string in <stdin>
// Refer to https://github.com/opencontainers/runc/pull/160/ for more information
func processSetKeyReexec() {
var err error
// Return a failure to the calling process via ExitCode
defer func() {
if err != nil {
logrus.Fatalf("%v", err)
}
}()
// expecting 3 args {[0]="libnetwork-setkey", [1]=<container-id>, [2]=<controller-id> }
if len(os.Args) < 3 {
err = fmt.Errorf("Re-exec expects 3 args, received : %d", len(os.Args))
return
}
containerID := os.Args[1]
// We expect libcontainer.State as a json string in <stdin>
stateBuf, err := ioutil.ReadAll(os.Stdin)
if err != nil {
return
}
var state libcontainer.State
if err = json.Unmarshal(stateBuf, &state); err != nil {
return
}
controllerID := os.Args[2]
key := state.NamespacePaths[configs.NamespaceType("NEWNET")]
err = SetExternalKey(controllerID, containerID, key)
return
}
// SetExternalKey provides a convenient way to set an External key to a sandbox
func SetExternalKey(controllerID string, containerID string, key string) error {
keyData := setKeyData{
ContainerID: containerID,
Key: key}
c, err := net.Dial("unix", udsBase+controllerID+".sock")
if err != nil {
return err
}
defer c.Close()
if err = sendKey(c, keyData); err != nil {
return fmt.Errorf("sendKey failed with : %v", err)
}
return processReturn(c)
}
func sendKey(c net.Conn, data setKeyData) error {
var err error
defer func() {
if err != nil {
c.Close()
}
}()
var b []byte
if b, err = json.Marshal(data); err != nil {
return err
}
_, err = c.Write(b)
return err
}
func processReturn(r io.Reader) error {
buf := make([]byte, 1024)
n, err := r.Read(buf[:])
if err != nil {
return fmt.Errorf("failed to read buf in processReturn : %v", err)
}
if string(buf[0:n]) != success {
return fmt.Errorf(string(buf[0:n]))
}
return nil
}
func (c *controller) startExternalKeyListener() error {
if err := os.MkdirAll(udsBase, 0600); err != nil {
return err
}
uds := udsBase + c.id + ".sock"
l, err := net.Listen("unix", uds)
if err != nil {
return err
}
if err := os.Chmod(uds, 0600); err != nil {
l.Close()
return err
}
c.Lock()
c.extKeyListener = l
c.Unlock()
go c.acceptClientConnections(uds, l)
return nil
}
func (c *controller) acceptClientConnections(sock string, l net.Listener) {
for {
conn, err := l.Accept()
if err != nil {
if _, err1 := os.Stat(sock); os.IsNotExist(err1) {
logrus.Debugf("Unix socket %s doesnt exist. cannot accept client connections", sock)
return
}
logrus.Errorf("Error accepting connection %v", err)
continue
}
go func() {
err := c.processExternalKey(conn)
ret := success
if err != nil {
ret = err.Error()
}
_, err = conn.Write([]byte(ret))
if err != nil {
logrus.Errorf("Error returning to the client %v", err)
}
}()
}
}
func (c *controller) processExternalKey(conn net.Conn) error {
buf := make([]byte, 1280)
nr, err := conn.Read(buf)
if err != nil {
return err
}
var s setKeyData
if err = json.Unmarshal(buf[0:nr], &s); err != nil {
return err
}
var sandbox Sandbox
search := SandboxContainerWalker(&sandbox, s.ContainerID)
c.WalkSandboxes(search)
if sandbox == nil {
return types.BadRequestErrorf("no sandbox present for %s", s.ContainerID)
}
return sandbox.SetKey(s.Key)
}
func (c *controller) stopExternalKeyListener() {
c.extKeyListener.Close()
}

View File

@ -0,0 +1,45 @@
// +build windows
package libnetwork
import (
"io"
"net"
"github.com/docker/libnetwork/types"
)
// processSetKeyReexec is a private function that must be called only on an reexec path
// It expects 3 args { [0] = "libnetwork-setkey", [1] = <container-id>, [2] = <controller-id> }
// It also expects libcontainer.State as a json string in <stdin>
// Refer to https://github.com/opencontainers/runc/pull/160/ for more information
func processSetKeyReexec() {
}
// SetExternalKey provides a convenient way to set an External key to a sandbox
func SetExternalKey(controllerID string, containerID string, key string) error {
return types.NotImplementedErrorf("SetExternalKey isn't supported on non linux systems")
}
func sendKey(c net.Conn, data setKeyData) error {
return types.NotImplementedErrorf("sendKey isn't supported on non linux systems")
}
func processReturn(r io.Reader) error {
return types.NotImplementedErrorf("processReturn isn't supported on non linux systems")
}
// no-op on non linux systems
func (c *controller) startExternalKeyListener() error {
return nil
}
func (c *controller) acceptClientConnections(sock string, l net.Listener) {
}
func (c *controller) processExternalKey(conn net.Conn) error {
return types.NotImplementedErrorf("processExternalKey isn't supported on non linux systems")
}
func (c *controller) stopExternalKeyListener() {
}

View File

@ -1,4 +1,4 @@
Copyright (c) 2013, Georg Reinke (<guelfey at gmail dot com>)
Copyright (c) 2013, Georg Reinke (<guelfey at gmail dot com>), Google
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@ -27,6 +27,9 @@ The complete package documentation and some simple examples are available at
[_examples](https://github.com/godbus/dbus/tree/master/_examples) directory
gives a short overview over the basic usage.
#### Projects using godbus
- [notify](https://github.com/esiqveland/notify) provides desktop notifications over dbus into a library.
Please note that the API is considered unstable for now and may change without
further notice.

View File

@ -2,7 +2,6 @@ package dbus
import (
"errors"
"strings"
)
// Call represents a pending or completed method call.
@ -35,113 +34,3 @@ func (c *Call) Store(retvalues ...interface{}) error {
return Store(c.Body, retvalues...)
}
// Object represents a remote object on which methods can be invoked.
type Object struct {
conn *Conn
dest string
path ObjectPath
}
// Call calls a method with (*Object).Go and waits for its reply.
func (o *Object) Call(method string, flags Flags, args ...interface{}) *Call {
return <-o.Go(method, flags, make(chan *Call, 1), args...).Done
}
// GetProperty calls org.freedesktop.DBus.Properties.GetProperty on the given
// object. The property name must be given in interface.member notation.
func (o *Object) GetProperty(p string) (Variant, error) {
idx := strings.LastIndex(p, ".")
if idx == -1 || idx+1 == len(p) {
return Variant{}, errors.New("dbus: invalid property " + p)
}
iface := p[:idx]
prop := p[idx+1:]
result := Variant{}
err := o.Call("org.freedesktop.DBus.Properties.Get", 0, iface, prop).Store(&result)
if err != nil {
return Variant{}, err
}
return result, nil
}
// Go calls a method with the given arguments asynchronously. It returns a
// Call structure representing this method call. The passed channel will
// return the same value once the call is done. If ch is nil, a new channel
// will be allocated. Otherwise, ch has to be buffered or Go will panic.
//
// If the flags include FlagNoReplyExpected, ch is ignored and a Call structure
// is returned of which only the Err member is valid.
//
// If the method parameter contains a dot ('.'), the part before the last dot
// specifies the interface on which the method is called.
func (o *Object) Go(method string, flags Flags, ch chan *Call, args ...interface{}) *Call {
iface := ""
i := strings.LastIndex(method, ".")
if i != -1 {
iface = method[:i]
}
method = method[i+1:]
msg := new(Message)
msg.Type = TypeMethodCall
msg.serial = o.conn.getSerial()
msg.Flags = flags & (FlagNoAutoStart | FlagNoReplyExpected)
msg.Headers = make(map[HeaderField]Variant)
msg.Headers[FieldPath] = MakeVariant(o.path)
msg.Headers[FieldDestination] = MakeVariant(o.dest)
msg.Headers[FieldMember] = MakeVariant(method)
if iface != "" {
msg.Headers[FieldInterface] = MakeVariant(iface)
}
msg.Body = args
if len(args) > 0 {
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(args...))
}
if msg.Flags&FlagNoReplyExpected == 0 {
if ch == nil {
ch = make(chan *Call, 10)
} else if cap(ch) == 0 {
panic("dbus: unbuffered channel passed to (*Object).Go")
}
call := &Call{
Destination: o.dest,
Path: o.path,
Method: method,
Args: args,
Done: ch,
}
o.conn.callsLck.Lock()
o.conn.calls[msg.serial] = call
o.conn.callsLck.Unlock()
o.conn.outLck.RLock()
if o.conn.closed {
call.Err = ErrClosed
call.Done <- call
} else {
o.conn.out <- msg
}
o.conn.outLck.RUnlock()
return call
}
o.conn.outLck.RLock()
defer o.conn.outLck.RUnlock()
if o.conn.closed {
return &Call{Err: ErrClosed}
}
o.conn.out <- msg
return &Call{Err: nil}
}
// Destination returns the destination that calls on o are sent to.
func (o *Object) Destination() string {
return o.dest
}
// Path returns the path that calls on o are sent to.
func (o *Object) Path() ObjectPath {
return o.path
}

View File

@ -32,7 +32,7 @@ var ErrClosed = errors.New("dbus: connection closed by user")
type Conn struct {
transport
busObj *Object
busObj BusObject
unixFD bool
uuid string
@ -46,7 +46,7 @@ type Conn struct {
calls map[uint32]*Call
callsLck sync.RWMutex
handlers map[ObjectPath]map[string]interface{}
handlers map[ObjectPath]map[string]exportWithMapping
handlersLck sync.RWMutex
out chan *Message
@ -157,7 +157,7 @@ func newConn(tr transport) (*Conn, error) {
conn.transport = tr
conn.calls = make(map[uint32]*Call)
conn.out = make(chan *Message, 10)
conn.handlers = make(map[ObjectPath]map[string]interface{})
conn.handlers = make(map[ObjectPath]map[string]exportWithMapping)
conn.nextSerial = 1
conn.serialUsed = map[uint32]bool{0: true}
conn.busObj = conn.Object("org.freedesktop.DBus", "/org/freedesktop/DBus")
@ -166,7 +166,7 @@ func newConn(tr transport) (*Conn, error) {
// BusObject returns the object owned by the bus daemon which handles
// administrative requests.
func (conn *Conn) BusObject() *Object {
func (conn *Conn) BusObject() BusObject {
return conn.busObj
}
@ -303,18 +303,33 @@ func (conn *Conn) inWorker() {
// as per http://dbus.freedesktop.org/doc/dbus-specification.html ,
// sender is optional for signals.
sender, _ := msg.Headers[FieldSender].value.(string)
if iface == "org.freedesktop.DBus" && member == "NameLost" &&
sender == "org.freedesktop.DBus" {
name, _ := msg.Body[0].(string)
conn.namesLck.Lock()
for i, v := range conn.names {
if v == name {
copy(conn.names[i:], conn.names[i+1:])
conn.names = conn.names[:len(conn.names)-1]
if iface == "org.freedesktop.DBus" && sender == "org.freedesktop.DBus" {
if member == "NameLost" {
// If we lost the name on the bus, remove it from our
// tracking list.
name, ok := msg.Body[0].(string)
if !ok {
panic("Unable to read the lost name")
}
conn.namesLck.Lock()
for i, v := range conn.names {
if v == name {
conn.names = append(conn.names[:i],
conn.names[i+1:]...)
}
}
conn.namesLck.Unlock()
} else if member == "NameAcquired" {
// If we acquired the name on the bus, add it to our
// tracking list.
name, ok := msg.Body[0].(string)
if !ok {
panic("Unable to read the acquired name")
}
conn.namesLck.Lock()
conn.names = append(conn.names, name)
conn.namesLck.Unlock()
}
conn.namesLck.Unlock()
}
signal := &Signal{
Sender: sender,
@ -360,7 +375,7 @@ func (conn *Conn) Names() []string {
}
// Object returns the object identified by the given destination name and path.
func (conn *Conn) Object(dest string, path ObjectPath) *Object {
func (conn *Conn) Object(dest string, path ObjectPath) BusObject {
return &Object{conn, dest, path}
}
@ -554,7 +569,7 @@ type transport interface {
}
var (
transports map[string]func(string) (transport, error) = make(map[string]func(string) (transport, error))
transports = make(map[string]func(string) (transport, error))
)
func getTransport(address string) (transport, error) {
@ -571,6 +586,7 @@ func getTransport(address string) (transport, error) {
f := transports[v[:i]]
if f == nil {
err = errors.New("dbus: invalid bus address (invalid or unsupported transport)")
continue
}
t, err = f(v[i+1:])
if err == nil {

View File

@ -16,24 +16,43 @@ type encoder struct {
// NewEncoder returns a new encoder that writes to out in the given byte order.
func newEncoder(out io.Writer, order binary.ByteOrder) *encoder {
return newEncoderAtOffset(out, 0, order)
}
// newEncoderAtOffset returns a new encoder that writes to out in the given
// byte order. Specify the offset to initialize pos for proper alignment
// computation.
func newEncoderAtOffset(out io.Writer, offset int, order binary.ByteOrder) *encoder {
enc := new(encoder)
enc.out = out
enc.order = order
enc.pos = offset
return enc
}
// Aligns the next output to be on a multiple of n. Panics on write errors.
func (enc *encoder) align(n int) {
if enc.pos%n != 0 {
newpos := (enc.pos + n - 1) & ^(n - 1)
empty := make([]byte, newpos-enc.pos)
pad := enc.padding(0, n)
if pad > 0 {
empty := make([]byte, pad)
if _, err := enc.out.Write(empty); err != nil {
panic(err)
}
enc.pos = newpos
enc.pos += pad
}
}
// pad returns the number of bytes of padding, based on current position and additional offset.
// and alignment.
func (enc *encoder) padding(offset, algn int) int {
abs := enc.pos + offset
if abs%algn != 0 {
newabs := (abs + algn - 1) & ^(algn - 1)
return newabs - abs
}
return 0
}
// Calls binary.Write(enc.out, enc.order, v) and panics on write errors.
func (enc *encoder) binwrite(v interface{}) {
if err := binary.Write(enc.out, enc.order, v); err != nil {
@ -108,8 +127,13 @@ func (enc *encoder) encode(v reflect.Value, depth int) {
if depth >= 64 {
panic(FormatError("input exceeds container depth limit"))
}
// Lookahead offset: 4 bytes for uint32 length (with alignment),
// plus alignment for elements.
n := enc.padding(0, 4) + 4
offset := enc.pos + n + enc.padding(n, alignment(v.Type().Elem()))
var buf bytes.Buffer
bufenc := newEncoder(&buf, enc.order)
bufenc := newEncoderAtOffset(&buf, offset, enc.order)
for i := 0; i < v.Len(); i++ {
bufenc.encode(v.Index(i), depth+1)
@ -159,8 +183,13 @@ func (enc *encoder) encode(v reflect.Value, depth int) {
panic(InvalidTypeError{v.Type()})
}
keys := v.MapKeys()
// Lookahead offset: 4 bytes for uint32 length (with alignment),
// plus 8-byte alignment
n := enc.padding(0, 4) + 4
offset := enc.pos + n + enc.padding(n, 8)
var buf bytes.Buffer
bufenc := newEncoder(&buf, enc.order)
bufenc := newEncoderAtOffset(&buf, offset, enc.order)
for _, k := range keys {
bufenc.align(8)
bufenc.encode(k, depth+2)

View File

@ -2,9 +2,9 @@ package dbus
import (
"errors"
"fmt"
"reflect"
"strings"
"unicode"
)
var (
@ -22,16 +22,52 @@ var (
}
)
// exportWithMapping represents an exported struct along with a method name
// mapping to allow for exporting lower-case methods, etc.
type exportWithMapping struct {
export interface{}
// Method name mapping; key -> struct method, value -> dbus method.
mapping map[string]string
// Whether or not this export is for the entire subtree
includeSubtree bool
}
// Sender is a type which can be used in exported methods to receive the message
// sender.
type Sender string
func exportedMethod(v interface{}, name string) reflect.Value {
if v == nil {
func exportedMethod(export exportWithMapping, name string) reflect.Value {
if export.export == nil {
return reflect.Value{}
}
m := reflect.ValueOf(v).MethodByName(name)
if !m.IsValid() {
// If a mapping was included in the export, check the map to see if we
// should be looking for a different method in the export.
if export.mapping != nil {
for key, value := range export.mapping {
if value == name {
name = key
break
}
// Catch the case where a method is aliased but the client is calling
// the original, e.g. the "Foo" method was exported mapped to
// "foo," and dbus client called the original "Foo."
if key == name {
return reflect.Value{}
}
}
}
value := reflect.ValueOf(export.export)
m := value.MethodByName(name)
// Catch the case of attempting to call an unexported method
method, ok := value.Type().MethodByName(name)
if !m.IsValid() || !ok || method.PkgPath != "" {
return reflect.Value{}
}
t := m.Type()
@ -43,6 +79,42 @@ func exportedMethod(v interface{}, name string) reflect.Value {
return m
}
// searchHandlers will look through all registered handlers looking for one
// to handle the given path. If a verbatim one isn't found, it will check for
// a subtree registration for the path as well.
func (conn *Conn) searchHandlers(path ObjectPath) (map[string]exportWithMapping, bool) {
conn.handlersLck.RLock()
defer conn.handlersLck.RUnlock()
handlers, ok := conn.handlers[path]
if ok {
return handlers, ok
}
// If handlers weren't found for this exact path, look for a matching subtree
// registration
handlers = make(map[string]exportWithMapping)
path = path[:strings.LastIndex(string(path), "/")]
for len(path) > 0 {
var subtreeHandlers map[string]exportWithMapping
subtreeHandlers, ok = conn.handlers[path]
if ok {
for iface, handler := range subtreeHandlers {
// Only include this handler if it registered for the subtree
if handler.includeSubtree {
handlers[iface] = handler
}
}
break
}
path = path[:strings.LastIndex(string(path), "/")]
}
return handlers, ok
}
// handleCall handles the given method call (i.e. looks if it's one of the
// pre-implemented ones and searches for a corresponding handler if not).
func (conn *Conn) handleCall(msg *Message) {
@ -62,40 +134,35 @@ func (conn *Conn) handleCall(msg *Message) {
}
return
}
if len(name) == 0 || unicode.IsLower([]rune(name)[0]) {
if len(name) == 0 {
conn.sendError(errmsgUnknownMethod, sender, serial)
}
// Find the exported handler (if any) for this path
handlers, ok := conn.searchHandlers(path)
if !ok {
conn.sendError(errmsgNoObject, sender, serial)
return
}
var m reflect.Value
if hasIface {
conn.handlersLck.RLock()
obj, ok := conn.handlers[path]
if !ok {
conn.sendError(errmsgNoObject, sender, serial)
conn.handlersLck.RUnlock()
return
}
iface := obj[ifaceName]
conn.handlersLck.RUnlock()
iface := handlers[ifaceName]
m = exportedMethod(iface, name)
} else {
conn.handlersLck.RLock()
if _, ok := conn.handlers[path]; !ok {
conn.sendError(errmsgNoObject, sender, serial)
conn.handlersLck.RUnlock()
return
}
for _, v := range conn.handlers[path] {
for _, v := range handlers {
m = exportedMethod(v, name)
if m.IsValid() {
break
}
}
conn.handlersLck.RUnlock()
}
if !m.IsValid() {
conn.sendError(errmsgUnknownMethod, sender, serial)
return
}
t := m.Type()
vs := msg.Body
pointers := make([]interface{}, t.NumIn())
@ -106,27 +173,36 @@ func (conn *Conn) handleCall(msg *Message) {
pointers[i] = val.Interface()
if tp == reflect.TypeOf((*Sender)(nil)).Elem() {
val.Elem().SetString(sender)
} else if tp == reflect.TypeOf((*Message)(nil)).Elem() {
val.Elem().Set(reflect.ValueOf(*msg))
} else {
decode = append(decode, pointers[i])
}
}
if len(decode) != len(vs) {
conn.sendError(errmsgInvalidArg, sender, serial)
return
}
if err := Store(vs, decode...); err != nil {
conn.sendError(errmsgInvalidArg, sender, serial)
return
}
// Extract parameters
params := make([]reflect.Value, len(pointers))
for i := 0; i < len(pointers); i++ {
params[i] = reflect.ValueOf(pointers[i]).Elem()
}
// Call method
ret := m.Call(params)
if em := ret[t.NumOut()-1].Interface().(*Error); em != nil {
conn.sendError(*em, sender, serial)
return
}
if msg.Flags&FlagNoReplyExpected == 0 {
reply := new(Message)
reply.Type = TypeMethodReply
@ -203,6 +279,10 @@ func (conn *Conn) Emit(path ObjectPath, name string, values ...interface{}) erro
// contribute to the dbus signature of the method (i.e. the method is exposed
// as if the parameters of type Sender were not there).
//
// Similarly, any parameters with the type Message are set to the raw message
// received on the bus. Again, parameters of this type do not contribute to the
// dbus signature of the method.
//
// Every method call is executed in a new goroutine, so the method may be called
// in multiple goroutines at once.
//
@ -214,10 +294,51 @@ func (conn *Conn) Emit(path ObjectPath, name string, values ...interface{}) erro
//
// Export returns an error if path is not a valid path name.
func (conn *Conn) Export(v interface{}, path ObjectPath, iface string) error {
return conn.ExportWithMap(v, nil, path, iface)
}
// ExportWithMap works exactly like Export but provides the ability to remap
// method names (e.g. export a lower-case method).
//
// The keys in the map are the real method names (exported on the struct), and
// the values are the method names to be exported on DBus.
func (conn *Conn) ExportWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string) error {
return conn.exportWithMap(v, mapping, path, iface, false)
}
// ExportSubtree works exactly like Export but registers the given value for
// an entire subtree rather under the root path provided.
//
// In order to make this useful, one parameter in each of the value's exported
// methods should be a Message, in which case it will contain the raw message
// (allowing one to get access to the path that caused the method to be called).
//
// Note that more specific export paths take precedence over less specific. For
// example, a method call using the ObjectPath /foo/bar/baz will call a method
// exported on /foo/bar before a method exported on /foo.
func (conn *Conn) ExportSubtree(v interface{}, path ObjectPath, iface string) error {
return conn.ExportSubtreeWithMap(v, nil, path, iface)
}
// ExportSubtreeWithMap works exactly like ExportSubtree but provides the
// ability to remap method names (e.g. export a lower-case method).
//
// The keys in the map are the real method names (exported on the struct), and
// the values are the method names to be exported on DBus.
func (conn *Conn) ExportSubtreeWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string) error {
return conn.exportWithMap(v, mapping, path, iface, true)
}
// exportWithMap is the worker function for all exports/registrations.
func (conn *Conn) exportWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string, includeSubtree bool) error {
if !path.IsValid() {
return errors.New("dbus: invalid path name")
return fmt.Errorf(`dbus: Invalid path name: "%s"`, path)
}
conn.handlersLck.Lock()
defer conn.handlersLck.Unlock()
// Remove a previous export if the interface is nil
if v == nil {
if _, ok := conn.handlers[path]; ok {
delete(conn.handlers[path], iface)
@ -225,51 +346,39 @@ func (conn *Conn) Export(v interface{}, path ObjectPath, iface string) error {
delete(conn.handlers, path)
}
}
return nil
}
// If this is the first handler for this path, make a new map to hold all
// handlers for this path.
if _, ok := conn.handlers[path]; !ok {
conn.handlers[path] = make(map[string]interface{})
conn.handlers[path] = make(map[string]exportWithMapping)
}
conn.handlers[path][iface] = v
conn.handlersLck.Unlock()
// Finally, save this handler
conn.handlers[path][iface] = exportWithMapping{export: v, mapping: mapping, includeSubtree: includeSubtree}
return nil
}
// ReleaseName calls org.freedesktop.DBus.ReleaseName. You should use only this
// method to release a name (see below).
// ReleaseName calls org.freedesktop.DBus.ReleaseName and awaits a response.
func (conn *Conn) ReleaseName(name string) (ReleaseNameReply, error) {
var r uint32
err := conn.busObj.Call("org.freedesktop.DBus.ReleaseName", 0, name).Store(&r)
if err != nil {
return 0, err
}
if r == uint32(ReleaseNameReplyReleased) {
conn.namesLck.Lock()
for i, v := range conn.names {
if v == name {
copy(conn.names[i:], conn.names[i+1:])
conn.names = conn.names[:len(conn.names)-1]
}
}
conn.namesLck.Unlock()
}
return ReleaseNameReply(r), nil
}
// RequestName calls org.freedesktop.DBus.RequestName. You should use only this
// method to request a name because package dbus needs to keep track of all
// names that the connection has.
// RequestName calls org.freedesktop.DBus.RequestName and awaits a response.
func (conn *Conn) RequestName(name string, flags RequestNameFlags) (RequestNameReply, error) {
var r uint32
err := conn.busObj.Call("org.freedesktop.DBus.RequestName", 0, name, flags).Store(&r)
if err != nil {
return 0, err
}
if r == uint32(RequestNameReplyPrimaryOwner) {
conn.namesLck.Lock()
conn.names = append(conn.names, name)
conn.namesLck.Unlock()
}
return RequestNameReply(r), nil
}

View File

@ -0,0 +1,126 @@
package dbus
import (
"errors"
"strings"
)
// BusObject is the interface of a remote object on which methods can be
// invoked.
type BusObject interface {
Call(method string, flags Flags, args ...interface{}) *Call
Go(method string, flags Flags, ch chan *Call, args ...interface{}) *Call
GetProperty(p string) (Variant, error)
Destination() string
Path() ObjectPath
}
// Object represents a remote object on which methods can be invoked.
type Object struct {
conn *Conn
dest string
path ObjectPath
}
// Call calls a method with (*Object).Go and waits for its reply.
func (o *Object) Call(method string, flags Flags, args ...interface{}) *Call {
return <-o.Go(method, flags, make(chan *Call, 1), args...).Done
}
// Go calls a method with the given arguments asynchronously. It returns a
// Call structure representing this method call. The passed channel will
// return the same value once the call is done. If ch is nil, a new channel
// will be allocated. Otherwise, ch has to be buffered or Go will panic.
//
// If the flags include FlagNoReplyExpected, ch is ignored and a Call structure
// is returned of which only the Err member is valid.
//
// If the method parameter contains a dot ('.'), the part before the last dot
// specifies the interface on which the method is called.
func (o *Object) Go(method string, flags Flags, ch chan *Call, args ...interface{}) *Call {
iface := ""
i := strings.LastIndex(method, ".")
if i != -1 {
iface = method[:i]
}
method = method[i+1:]
msg := new(Message)
msg.Type = TypeMethodCall
msg.serial = o.conn.getSerial()
msg.Flags = flags & (FlagNoAutoStart | FlagNoReplyExpected)
msg.Headers = make(map[HeaderField]Variant)
msg.Headers[FieldPath] = MakeVariant(o.path)
msg.Headers[FieldDestination] = MakeVariant(o.dest)
msg.Headers[FieldMember] = MakeVariant(method)
if iface != "" {
msg.Headers[FieldInterface] = MakeVariant(iface)
}
msg.Body = args
if len(args) > 0 {
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(args...))
}
if msg.Flags&FlagNoReplyExpected == 0 {
if ch == nil {
ch = make(chan *Call, 10)
} else if cap(ch) == 0 {
panic("dbus: unbuffered channel passed to (*Object).Go")
}
call := &Call{
Destination: o.dest,
Path: o.path,
Method: method,
Args: args,
Done: ch,
}
o.conn.callsLck.Lock()
o.conn.calls[msg.serial] = call
o.conn.callsLck.Unlock()
o.conn.outLck.RLock()
if o.conn.closed {
call.Err = ErrClosed
call.Done <- call
} else {
o.conn.out <- msg
}
o.conn.outLck.RUnlock()
return call
}
o.conn.outLck.RLock()
defer o.conn.outLck.RUnlock()
if o.conn.closed {
return &Call{Err: ErrClosed}
}
o.conn.out <- msg
return &Call{Err: nil}
}
// GetProperty calls org.freedesktop.DBus.Properties.GetProperty on the given
// object. The property name must be given in interface.member notation.
func (o *Object) GetProperty(p string) (Variant, error) {
idx := strings.LastIndex(p, ".")
if idx == -1 || idx+1 == len(p) {
return Variant{}, errors.New("dbus: invalid property " + p)
}
iface := p[:idx]
prop := p[idx+1:]
result := Variant{}
err := o.Call("org.freedesktop.DBus.Properties.Get", 0, iface, prop).Store(&result)
if err != nil {
return Variant{}, err
}
return result, nil
}
// Destination returns the destination that calls on o are sent to.
func (o *Object) Destination() string {
return o.dest
}
// Path returns the path that calls on o are sent to.
func (o *Object) Path() ObjectPath {
return o.path
}

View File

@ -39,5 +39,5 @@ test: install generate-test-pbs
generate-test-pbs:
make install
make -C testdata
protoc --go_out=Mtestdata/test.proto=github.com/golang/protobuf/proto/testdata:. proto3_proto/proto3.proto
make -C proto3_proto
make

View File

@ -1128,12 +1128,10 @@ func size_new_map(p *Properties, base structPointer) int {
keycopy.Set(key)
valcopy.Set(val)
// Tag codes for key and val are the responsibility of the sub-sizer.
keysize := p.mkeyprop.size(p.mkeyprop, keybase)
valsize := p.mvalprop.size(p.mvalprop, valbase)
entry := keysize + valsize
// Add on tag code and length of map entry itself.
n += len(p.tagcode) + sizeVarint(uint64(entry)) + entry
// Tag codes are two bytes per map entry.
n += 2
n += p.mkeyprop.size(p.mkeyprop, keybase)
n += p.mvalprop.size(p.mvalprop, valbase)
}
return n
}

View File

@ -607,15 +607,13 @@ func setDefaults(v reflect.Value, recur, zeros bool) {
for _, ni := range dm.nested {
f := v.Field(ni)
// f is *T or []*T or map[T]*T
switch f.Kind() {
case reflect.Ptr:
if f.IsNil() {
continue
}
if f.IsNil() {
continue
}
// f is *T or []*T
if f.Kind() == reflect.Ptr {
setDefaults(f, recur, zeros)
case reflect.Slice:
} else {
for i := 0; i < f.Len(); i++ {
e := f.Index(i)
if e.IsNil() {
@ -623,15 +621,6 @@ func setDefaults(v reflect.Value, recur, zeros bool) {
}
setDefaults(e, recur, zeros)
}
case reflect.Map:
for _, k := range f.MapKeys() {
e := f.MapIndex(k)
if e.IsNil() {
continue
}
setDefaults(e, recur, zeros)
}
}
}
}
@ -657,6 +646,10 @@ type scalarField struct {
value interface{} // the proto-declared default value, or nil
}
func ptrToStruct(t reflect.Type) bool {
return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
}
// t is a struct type.
func buildDefaultMessage(t reflect.Type) (dm defaultMessage) {
sprop := GetProperties(t)
@ -668,33 +661,9 @@ func buildDefaultMessage(t reflect.Type) (dm defaultMessage) {
}
ft := t.Field(fi).Type
var canHaveDefault, nestedMessage bool
switch ft.Kind() {
case reflect.Ptr:
if ft.Elem().Kind() == reflect.Struct {
nestedMessage = true
} else {
canHaveDefault = true // proto2 scalar field
}
case reflect.Slice:
switch ft.Elem().Kind() {
case reflect.Ptr:
nestedMessage = true // repeated message
case reflect.Uint8:
canHaveDefault = true // bytes field
}
case reflect.Map:
if ft.Elem().Kind() == reflect.Ptr {
nestedMessage = true // map with message values
}
}
if !canHaveDefault {
if nestedMessage {
dm.nested = append(dm.nested, fi)
}
// nested messages
if ptrToStruct(ft) || (ft.Kind() == reflect.Slice && ptrToStruct(ft.Elem())) {
dm.nested = append(dm.nested, fi)
continue
}

View File

@ -440,12 +440,7 @@ func (p *Properties) setEncAndDec(typ reflect.Type, f *reflect.StructField, lock
p.enc = (*Buffer).enc_slice_byte
p.dec = (*Buffer).dec_slice_byte
p.size = size_slice_byte
// This is a []byte, which is either a bytes field,
// or the value of a map field. In the latter case,
// we always encode an empty []byte, so we should not
// use the proto3 enc/size funcs.
// f == nil iff this is the key/value of a map field.
if p.proto3 && f != nil {
if p.proto3 {
p.enc = (*Buffer).enc_proto3_slice_byte
p.size = size_proto3_slice_byte
}

View File

@ -97,12 +97,12 @@ that is local to the container's rootfs.
After the container has `/proc` mounted a few standard symlinks are setup
within `/dev/` for the io.
| Source | Destination |
| ------------ | ----------- |
| /proc/1/fd | /dev/fd |
| /proc/1/fd/0 | /dev/stdin |
| /proc/1/fd/1 | /dev/stdout |
| /proc/1/fd/2 | /dev/stderr |
| Source | Destination |
| --------------- | ----------- |
| /proc/self/fd | /dev/fd |
| /proc/self/fd/0 | /dev/stdin |
| /proc/self/fd/1 | /dev/stdout |
| /proc/self/fd/2 | /dev/stderr |
A `pivot_root` is used to change the root for the process, effectively
jailing the process inside the rootfs.

View File

@ -3,6 +3,7 @@
package fs
import (
"errors"
"fmt"
"io"
"io/ioutil"
@ -16,30 +17,45 @@ import (
)
var (
subsystems = map[string]subsystem{
"devices": &DevicesGroup{},
"memory": &MemoryGroup{},
"cpu": &CpuGroup{},
"cpuset": &CpusetGroup{},
"cpuacct": &CpuacctGroup{},
"blkio": &BlkioGroup{},
"hugetlb": &HugetlbGroup{},
"net_cls": &NetClsGroup{},
"net_prio": &NetPrioGroup{},
"perf_event": &PerfEventGroup{},
"freezer": &FreezerGroup{},
subsystems = subsystemSet{
&CpusetGroup{},
&DevicesGroup{},
&MemoryGroup{},
&CpuGroup{},
&CpuacctGroup{},
&BlkioGroup{},
&HugetlbGroup{},
&NetClsGroup{},
&NetPrioGroup{},
&PerfEventGroup{},
&FreezerGroup{},
}
CgroupProcesses = "cgroup.procs"
HugePageSizes, _ = cgroups.GetHugePageSize()
)
var errSubsystemDoesNotExist = errors.New("cgroup: subsystem does not exist")
type subsystemSet []subsystem
func (s subsystemSet) Get(name string) (subsystem, error) {
for _, ss := range s {
if ss.Name() == name {
return ss, nil
}
}
return nil, errSubsystemDoesNotExist
}
type subsystem interface {
// Name returns the name of the subsystem.
Name() string
// Returns the stats, as 'stats', corresponding to the cgroup under 'path'.
GetStats(path string, stats *cgroups.Stats) error
// Removes the cgroup represented by 'data'.
Remove(*data) error
// Creates and joins the cgroup represented by data.
Apply(*data) error
// Removes the cgroup represented by 'cgroupData'.
Remove(*cgroupData) error
// Creates and joins the cgroup represented by 'cgroupData'.
Apply(*cgroupData) error
// Set the cgroup represented by cgroup.
Set(path string, cgroup *configs.Cgroup) error
}
@ -76,10 +92,11 @@ func getCgroupRoot() (string, error) {
return cgroupRoot, nil
}
type data struct {
type cgroupData struct {
root string
cgroup string
c *configs.Cgroup
parent string
name string
config *configs.Cgroup
pid int
}
@ -101,21 +118,21 @@ func (m *Manager) Apply(pid int) (err error) {
cgroups.RemovePaths(paths)
}
}()
for name, sys := range subsystems {
for _, sys := range subsystems {
if err := sys.Apply(d); err != nil {
return err
}
// TODO: Apply should, ideally, be reentrant or be broken up into a separate
// create and join phase so that the cgroup hierarchy for a container can be
// created then join consists of writing the process pids to cgroup.procs
p, err := d.path(name)
p, err := d.path(sys.Name())
if err != nil {
if cgroups.IsNotFound(err) {
continue
}
return err
}
paths[name] = p
paths[sys.Name()] = p
}
m.Paths = paths
@ -150,29 +167,27 @@ func (m *Manager) GetStats() (*cgroups.Stats, error) {
defer m.mu.Unlock()
stats := cgroups.NewStats()
for name, path := range m.Paths {
sys, ok := subsystems[name]
if !ok || !cgroups.PathExists(path) {
sys, err := subsystems.Get(name)
if err == errSubsystemDoesNotExist || !cgroups.PathExists(path) {
continue
}
if err := sys.GetStats(path, stats); err != nil {
return nil, err
}
}
return stats, nil
}
func (m *Manager) Set(container *configs.Config) error {
for name, path := range m.Paths {
sys, ok := subsystems[name]
if !ok || !cgroups.PathExists(path) {
sys, err := subsystems.Get(name)
if err == errSubsystemDoesNotExist || !cgroups.PathExists(path) {
continue
}
if err := sys.Set(path, container.Cgroups); err != nil {
return err
}
}
return nil
}
@ -183,22 +198,21 @@ func (m *Manager) Freeze(state configs.FreezerState) error {
if err != nil {
return err
}
dir, err := d.path("freezer")
if err != nil {
return err
}
prevState := m.Cgroups.Freezer
m.Cgroups.Freezer = state
freezer := subsystems["freezer"]
freezer, err := subsystems.Get("freezer")
if err != nil {
return err
}
err = freezer.Set(dir, m.Cgroups)
if err != nil {
m.Cgroups.Freezer = prevState
return err
}
return nil
}
@ -216,30 +230,31 @@ func (m *Manager) GetPids() ([]int, error) {
return cgroups.GetPids(dir)
}
func getCgroupData(c *configs.Cgroup, pid int) (*data, error) {
func getCgroupData(c *configs.Cgroup, pid int) (*cgroupData, error) {
root, err := getCgroupRoot()
if err != nil {
return nil, err
}
cgroup := c.Name
if c.Parent != "" {
cgroup = filepath.Join(c.Parent, cgroup)
}
return &data{
return &cgroupData{
root: root,
cgroup: cgroup,
c: c,
parent: c.Parent,
name: c.Name,
config: c,
pid: pid,
}, nil
}
func (raw *data) parent(subsystem, mountpoint, root string) (string, error) {
func (raw *cgroupData) parentPath(subsystem, mountpoint, root string) (string, error) {
// Use GetThisCgroupDir instead of GetInitCgroupDir, because the creating
// process could in container and shared pid namespace with host, and
// /proc/1/cgroup could point to whole other world of cgroups.
initPath, err := cgroups.GetThisCgroupDir(subsystem)
if err != nil {
return "", err
}
// This is needed for nested containers, because in /proc/self/cgroup we
// see pathes from host, which don't exist in container.
relDir, err := filepath.Rel(root, initPath)
if err != nil {
return "", err
@ -247,27 +262,29 @@ func (raw *data) parent(subsystem, mountpoint, root string) (string, error) {
return filepath.Join(mountpoint, relDir), nil
}
func (raw *data) path(subsystem string) (string, error) {
func (raw *cgroupData) path(subsystem string) (string, error) {
mnt, root, err := cgroups.FindCgroupMountpointAndRoot(subsystem)
// If we didn't mount the subsystem, there is no point we make the path.
if err != nil {
return "", err
}
cgPath := filepath.Join(raw.parent, raw.name)
// If the cgroup name/path is absolute do not look relative to the cgroup of the init process.
if filepath.IsAbs(raw.cgroup) {
return filepath.Join(raw.root, filepath.Base(mnt), raw.cgroup), nil
if filepath.IsAbs(cgPath) {
// Sometimes subsystems can be mounted togethger as 'cpu,cpuacct'.
return filepath.Join(raw.root, filepath.Base(mnt), cgPath), nil
}
parent, err := raw.parent(subsystem, mnt, root)
parentPath, err := raw.parentPath(subsystem, mnt, root)
if err != nil {
return "", err
}
return filepath.Join(parent, raw.cgroup), nil
return filepath.Join(parentPath, cgPath), nil
}
func (raw *data) join(subsystem string) (string, error) {
func (raw *cgroupData) join(subsystem string) (string, error) {
path, err := raw.path(subsystem)
if err != nil {
return "", err

View File

@ -17,13 +17,17 @@ import (
type BlkioGroup struct {
}
func (s *BlkioGroup) Apply(d *data) error {
func (s *BlkioGroup) Name() string {
return "blkio"
}
func (s *BlkioGroup) Apply(d *cgroupData) error {
dir, err := d.join("blkio")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
if err := s.Set(dir, d.c); err != nil {
if err := s.Set(dir, d.config); err != nil {
return err
}
@ -74,7 +78,7 @@ func (s *BlkioGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *BlkioGroup) Remove(d *data) error {
func (s *BlkioGroup) Remove(d *cgroupData) error {
return removePath(d.path("blkio"))
}

View File

@ -15,7 +15,11 @@ import (
type CpuGroup struct {
}
func (s *CpuGroup) Apply(d *data) error {
func (s *CpuGroup) Name() string {
return "cpu"
}
func (s *CpuGroup) Apply(d *cgroupData) error {
// We always want to join the cpu group, to allow fair cpu scheduling
// on a container basis
dir, err := d.join("cpu")
@ -23,7 +27,7 @@ func (s *CpuGroup) Apply(d *data) error {
return err
}
if err := s.Set(dir, d.c); err != nil {
if err := s.Set(dir, d.config); err != nil {
return err
}
@ -60,7 +64,7 @@ func (s *CpuGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *CpuGroup) Remove(d *data) error {
func (s *CpuGroup) Remove(d *cgroupData) error {
return removePath(d.path("cpu"))
}

View File

@ -24,7 +24,11 @@ var clockTicks = uint64(system.GetClockTicks())
type CpuacctGroup struct {
}
func (s *CpuacctGroup) Apply(d *data) error {
func (s *CpuacctGroup) Name() string {
return "cpuacct"
}
func (s *CpuacctGroup) Apply(d *cgroupData) error {
// we just want to join this group even though we don't set anything
if _, err := d.join("cpuacct"); err != nil && !cgroups.IsNotFound(err) {
return err
@ -37,7 +41,7 @@ func (s *CpuacctGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *CpuacctGroup) Remove(d *data) error {
func (s *CpuacctGroup) Remove(d *cgroupData) error {
return removePath(d.path("cpuacct"))
}

View File

@ -16,12 +16,16 @@ import (
type CpusetGroup struct {
}
func (s *CpusetGroup) Apply(d *data) error {
func (s *CpusetGroup) Name() string {
return "cpuset"
}
func (s *CpusetGroup) Apply(d *cgroupData) error {
dir, err := d.path("cpuset")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
return s.ApplyDir(dir, d.c, d.pid)
return s.ApplyDir(dir, d.config, d.pid)
}
func (s *CpusetGroup) Set(path string, cgroup *configs.Cgroup) error {
@ -38,7 +42,7 @@ func (s *CpusetGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *CpusetGroup) Remove(d *data) error {
func (s *CpusetGroup) Remove(d *cgroupData) error {
return removePath(d.path("cpuset"))
}
@ -59,17 +63,16 @@ func (s *CpusetGroup) ApplyDir(dir string, cgroup *configs.Cgroup, pid int) erro
if err := s.ensureParent(dir, root); err != nil {
return err
}
// because we are not using d.join we need to place the pid into the procs file
// unlike the other subsystems
if err := writeFile(dir, "cgroup.procs", strconv.Itoa(pid)); err != nil {
return err
}
// the default values inherit from parent cgroup are already set in
// s.ensureParent, cover these if we have our own
if err := s.Set(dir, cgroup); err != nil {
return err
}
// because we are not using d.join we need to place the pid into the procs file
// unlike the other subsystems
if err := writeFile(dir, "cgroup.procs", strconv.Itoa(pid)); err != nil {
return err
}
return nil
}

View File

@ -10,7 +10,11 @@ import (
type DevicesGroup struct {
}
func (s *DevicesGroup) Apply(d *data) error {
func (s *DevicesGroup) Name() string {
return "devices"
}
func (s *DevicesGroup) Apply(d *cgroupData) error {
dir, err := d.join("devices")
if err != nil {
// We will return error even it's `not found` error, devices
@ -18,7 +22,7 @@ func (s *DevicesGroup) Apply(d *data) error {
return err
}
if err := s.Set(dir, d.c); err != nil {
if err := s.Set(dir, d.config); err != nil {
return err
}
@ -52,7 +56,7 @@ func (s *DevicesGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *DevicesGroup) Remove(d *data) error {
func (s *DevicesGroup) Remove(d *cgroupData) error {
return removePath(d.path("devices"))
}

View File

@ -14,13 +14,17 @@ import (
type FreezerGroup struct {
}
func (s *FreezerGroup) Apply(d *data) error {
func (s *FreezerGroup) Name() string {
return "freezer"
}
func (s *FreezerGroup) Apply(d *cgroupData) error {
dir, err := d.join("freezer")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
if err := s.Set(dir, d.c); err != nil {
if err := s.Set(dir, d.config); err != nil {
return err
}
@ -53,7 +57,7 @@ func (s *FreezerGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *FreezerGroup) Remove(d *data) error {
func (s *FreezerGroup) Remove(d *cgroupData) error {
return removePath(d.path("freezer"))
}

View File

@ -14,13 +14,17 @@ import (
type HugetlbGroup struct {
}
func (s *HugetlbGroup) Apply(d *data) error {
func (s *HugetlbGroup) Name() string {
return "hugetlb"
}
func (s *HugetlbGroup) Apply(d *cgroupData) error {
dir, err := d.join("hugetlb")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
if err := s.Set(dir, d.c); err != nil {
if err := s.Set(dir, d.config); err != nil {
return err
}
@ -37,7 +41,7 @@ func (s *HugetlbGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *HugetlbGroup) Remove(d *data) error {
func (s *HugetlbGroup) Remove(d *cgroupData) error {
return removePath(d.path("hugetlb"))
}

View File

@ -17,16 +17,25 @@ import (
type MemoryGroup struct {
}
func (s *MemoryGroup) Apply(d *data) (err error) {
func (s *MemoryGroup) Name() string {
return "memory"
}
func (s *MemoryGroup) Apply(d *cgroupData) (err error) {
path, err := d.path("memory")
if err != nil {
if cgroups.IsNotFound(err) {
return nil
}
if err != nil && !cgroups.IsNotFound(err) {
return err
}
if err := os.MkdirAll(path, 0755); err != nil {
return err
if memoryAssigned(d.config) {
if path != "" {
if err := os.MkdirAll(path, 0755); err != nil {
return err
}
}
if err := s.Set(path, d.config); err != nil {
return err
}
}
defer func() {
@ -35,13 +44,10 @@ func (s *MemoryGroup) Apply(d *data) (err error) {
}
}()
if err := s.Set(path, d.c); err != nil {
return err
}
// We need to join memory cgroup after set memory limits, because
// kmem.limit_in_bytes can only be set when the cgroup is empty.
if _, err = d.join("memory"); err != nil {
_, err = d.join("memory")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
@ -88,7 +94,7 @@ func (s *MemoryGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *MemoryGroup) Remove(d *data) error {
func (s *MemoryGroup) Remove(d *cgroupData) error {
return removePath(d.path("memory"))
}
@ -132,6 +138,15 @@ func (s *MemoryGroup) GetStats(path string, stats *cgroups.Stats) error {
return nil
}
func memoryAssigned(cgroup *configs.Cgroup) bool {
return cgroup.Memory != 0 ||
cgroup.MemoryReservation != 0 ||
cgroup.MemorySwap > 0 ||
cgroup.KernelMemory > 0 ||
cgroup.OomKillDisable ||
cgroup.MemorySwappiness != -1
}
func getMemoryData(path, name string) (cgroups.MemoryData, error) {
memoryData := cgroups.MemoryData{}

View File

@ -1,3 +1,5 @@
// +build linux
package fs
import (
@ -6,9 +8,14 @@ import (
)
type NameGroup struct {
GroupName string
}
func (s *NameGroup) Apply(d *data) error {
func (s *NameGroup) Name() string {
return s.GroupName
}
func (s *NameGroup) Apply(d *cgroupData) error {
return nil
}
@ -16,7 +23,7 @@ func (s *NameGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *NameGroup) Remove(d *data) error {
func (s *NameGroup) Remove(d *cgroupData) error {
return nil
}

View File

@ -1,3 +1,5 @@
// +build linux
package fs
import (
@ -8,13 +10,17 @@ import (
type NetClsGroup struct {
}
func (s *NetClsGroup) Apply(d *data) error {
func (s *NetClsGroup) Name() string {
return "net_cls"
}
func (s *NetClsGroup) Apply(d *cgroupData) error {
dir, err := d.join("net_cls")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
if err := s.Set(dir, d.c); err != nil {
if err := s.Set(dir, d.config); err != nil {
return err
}
@ -31,7 +37,7 @@ func (s *NetClsGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *NetClsGroup) Remove(d *data) error {
func (s *NetClsGroup) Remove(d *cgroupData) error {
return removePath(d.path("net_cls"))
}

View File

@ -1,3 +1,5 @@
// +build linux
package fs
import (
@ -8,13 +10,17 @@ import (
type NetPrioGroup struct {
}
func (s *NetPrioGroup) Apply(d *data) error {
func (s *NetPrioGroup) Name() string {
return "net_prio"
}
func (s *NetPrioGroup) Apply(d *cgroupData) error {
dir, err := d.join("net_prio")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
if err := s.Set(dir, d.c); err != nil {
if err := s.Set(dir, d.config); err != nil {
return err
}
@ -31,7 +37,7 @@ func (s *NetPrioGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *NetPrioGroup) Remove(d *data) error {
func (s *NetPrioGroup) Remove(d *cgroupData) error {
return removePath(d.path("net_prio"))
}

View File

@ -10,7 +10,11 @@ import (
type PerfEventGroup struct {
}
func (s *PerfEventGroup) Apply(d *data) error {
func (s *PerfEventGroup) Name() string {
return "perf_event"
}
func (s *PerfEventGroup) Apply(d *cgroupData) error {
// we just want to join this group even though we don't set anything
if _, err := d.join("perf_event"); err != nil && !cgroups.IsNotFound(err) {
return err
@ -22,7 +26,7 @@ func (s *PerfEventGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *PerfEventGroup) Remove(d *data) error {
func (s *PerfEventGroup) Remove(d *cgroupData) error {
return removePath(d.path("perf_event"))
}

View File

@ -44,7 +44,7 @@ func getCgroupParamKeyValue(t string) (string, uint64, error) {
case 2:
value, err := parseUint(parts[1], 10, 64)
if err != nil {
return "", 0, fmt.Errorf("Unable to convert param value (%q) to uint64: %v", parts[1], err)
return "", 0, fmt.Errorf("unable to convert param value (%q) to uint64: %v", parts[1], err)
}
return parts[0], value, nil
@ -55,12 +55,17 @@ func getCgroupParamKeyValue(t string) (string, uint64, error) {
// Gets a single uint64 value from the specified cgroup file.
func getCgroupParamUint(cgroupPath, cgroupFile string) (uint64, error) {
contents, err := ioutil.ReadFile(filepath.Join(cgroupPath, cgroupFile))
fileName := filepath.Join(cgroupPath, cgroupFile)
contents, err := ioutil.ReadFile(fileName)
if err != nil {
return 0, err
}
return parseUint(strings.TrimSpace(string(contents)), 10, 64)
res, err := parseUint(strings.TrimSpace(string(contents)), 10, 64)
if err != nil {
return res, fmt.Errorf("unable to parse %q as a uint from Cgroup file %q", string(contents), fileName)
}
return res, nil
}
// Gets a string value from the specified cgroup file

View File

@ -3,6 +3,7 @@
package systemd
import (
"errors"
"fmt"
"io/ioutil"
"os"
@ -27,25 +28,40 @@ type Manager struct {
}
type subsystem interface {
// Name returns the name of the subsystem.
Name() string
// Returns the stats, as 'stats', corresponding to the cgroup under 'path'.
GetStats(path string, stats *cgroups.Stats) error
// Set the cgroup represented by cgroup.
Set(path string, cgroup *configs.Cgroup) error
}
var subsystems = map[string]subsystem{
"devices": &fs.DevicesGroup{},
"memory": &fs.MemoryGroup{},
"cpu": &fs.CpuGroup{},
"cpuset": &fs.CpusetGroup{},
"cpuacct": &fs.CpuacctGroup{},
"blkio": &fs.BlkioGroup{},
"hugetlb": &fs.HugetlbGroup{},
"perf_event": &fs.PerfEventGroup{},
"freezer": &fs.FreezerGroup{},
"net_prio": &fs.NetPrioGroup{},
"net_cls": &fs.NetClsGroup{},
"name=systemd": &fs.NameGroup{},
var errSubsystemDoesNotExist = errors.New("cgroup: subsystem does not exist")
type subsystemSet []subsystem
func (s subsystemSet) Get(name string) (subsystem, error) {
for _, ss := range s {
if ss.Name() == name {
return ss, nil
}
}
return nil, errSubsystemDoesNotExist
}
var subsystems = subsystemSet{
&fs.CpusetGroup{},
&fs.DevicesGroup{},
&fs.MemoryGroup{},
&fs.CpuGroup{},
&fs.CpuacctGroup{},
&fs.BlkioGroup{},
&fs.HugetlbGroup{},
&fs.PerfEventGroup{},
&fs.FreezerGroup{},
&fs.NetPrioGroup{},
&fs.NetClsGroup{},
&fs.NameGroup{GroupName: "name=systemd"},
}
const (
@ -249,8 +265,8 @@ func (m *Manager) Apply(pid int) error {
}
paths := make(map[string]string)
for sysname := range subsystems {
subsystemPath, err := getSubsystemPath(m.Cgroups, sysname)
for _, s := range subsystems {
subsystemPath, err := getSubsystemPath(m.Cgroups, s.Name())
if err != nil {
// Don't fail if a cgroup hierarchy was not found, just skip this subsystem
if cgroups.IsNotFound(err) {
@ -258,7 +274,7 @@ func (m *Manager) Apply(pid int) error {
}
return err
}
paths[sysname] = subsystemPath
paths[s.Name()] = subsystemPath
}
m.Paths = paths
@ -347,8 +363,10 @@ func joinFreezer(c *configs.Cgroup, pid int) error {
if err != nil && !cgroups.IsNotFound(err) {
return err
}
freezer := subsystems["freezer"]
freezer, err := subsystems.Get("freezer")
if err != nil {
return err
}
return freezer.Set(path, c)
}
@ -357,8 +375,10 @@ func joinNetPrio(c *configs.Cgroup, pid int) error {
if err != nil && !cgroups.IsNotFound(err) {
return err
}
netPrio := subsystems["net_prio"]
netPrio, err := subsystems.Get("net_prio")
if err != nil {
return err
}
return netPrio.Set(path, c)
}
@ -367,8 +387,10 @@ func joinNetCls(c *configs.Cgroup, pid int) error {
if err != nil && !cgroups.IsNotFound(err) {
return err
}
netcls := subsystems["net_cls"]
netcls, err := subsystems.Get("net_cls")
if err != nil {
return err
}
return netcls.Set(path, c)
}
@ -396,17 +418,17 @@ func (m *Manager) Freeze(state configs.FreezerState) error {
if err != nil {
return err
}
prevState := m.Cgroups.Freezer
m.Cgroups.Freezer = state
freezer := subsystems["freezer"]
freezer, err := subsystems.Get("freezer")
if err != nil {
return err
}
err = freezer.Set(path, m.Cgroups)
if err != nil {
m.Cgroups.Freezer = prevState
return err
}
return nil
}
@ -423,8 +445,8 @@ func (m *Manager) GetStats() (*cgroups.Stats, error) {
defer m.mu.Unlock()
stats := cgroups.NewStats()
for name, path := range m.Paths {
sys, ok := subsystems[name]
if !ok || !cgroups.PathExists(path) {
sys, err := subsystems.Get(name)
if err == errSubsystemDoesNotExist || !cgroups.PathExists(path) {
continue
}
if err := sys.GetStats(path, stats); err != nil {
@ -437,8 +459,8 @@ func (m *Manager) GetStats() (*cgroups.Stats, error) {
func (m *Manager) Set(container *configs.Config) error {
for name, path := range m.Paths {
sys, ok := subsystems[name]
if !ok || !cgroups.PathExists(path) {
sys, err := subsystems.Get(name)
if err == errSubsystemDoesNotExist || !cgroups.PathExists(path) {
continue
}
if err := sys.Set(path, container.Cgroups); err != nil {
@ -471,8 +493,10 @@ func joinDevices(c *configs.Cgroup, pid int) error {
if err != nil {
return err
}
devices := subsystems["devices"]
devices, err := subsystems.Get("devices")
if err != nil {
return err
}
return devices.Set(path, c)
}
@ -600,8 +624,10 @@ func joinHugetlb(c *configs.Cgroup, pid int) error {
if err != nil && !cgroups.IsNotFound(err) {
return err
}
hugetlb := subsystems["hugetlb"]
hugetlb, err := subsystems.Get("hugetlb")
if err != nil {
return err
}
return hugetlb.Set(path, c)
}
@ -610,7 +636,9 @@ func joinPerfEvent(c *configs.Cgroup, pid int) error {
if err != nil && !cgroups.IsNotFound(err) {
return err
}
perfEvent := subsystems["perf_event"]
perfEvent, err := subsystems.Get("perf_event")
if err != nil {
return err
}
return perfEvent.Set(path, c)
}

View File

@ -1,3 +1,5 @@
// +build linux freebsd
package configs
type FreezerState string
@ -8,9 +10,6 @@ const (
Thawed FreezerState = "THAWED"
)
// TODO Windows: This can be factored out in the future as Cgroups are not
// supported on the Windows platform.
type Cgroup struct {
Name string `json:"name"`

View File

@ -0,0 +1,6 @@
package configs
// TODO Windows: This can ultimately be entirely factored out on Windows as
// cgroups are a Unix-specific construct.
type Cgroup struct {
}

View File

@ -33,17 +33,18 @@ type Seccomp struct {
type Action int
const (
Kill Action = iota - 4
Kill Action = iota + 1
Errno
Trap
Allow
Trace
)
// A comparison operator to be used when matching syscall arguments in Seccomp
type Operator int
const (
EqualTo Operator = iota
EqualTo Operator = iota + 1
NotEqualTo
GreaterThan
GreaterThanOrEqualTo
@ -183,6 +184,9 @@ type Hooks struct {
// but before the user supplied command is executed from init.
Prestart []Hook
// Poststart commands are executed after the container init process starts.
Poststart []Hook
// Poststop commands are executed after the container init process exits.
Poststop []Hook
}

View File

@ -1,3 +1,5 @@
// +build !linux,!freebsd
package configs
// Namespace defines configuration for each namespace. It specifies an

View File

@ -30,8 +30,9 @@ const (
Destroyed
)
// State represents a running container's state
type State struct {
// BaseState represents the platform agnostic pieces relating to a
// running container's state
type BaseState struct {
// ID is the container ID.
ID string `json:"id"`
@ -41,27 +42,16 @@ type State struct {
// InitProcessStartTime is the init process start time.
InitProcessStartTime string `json:"init_process_start"`
// Path to all the cgroups setup for a container. Key is cgroup subsystem name
// with the value as the path.
CgroupPaths map[string]string `json:"cgroup_paths"`
// NamespacePaths are filepaths to the container's namespaces. Key is the namespace type
// with the value as the path.
NamespacePaths map[configs.NamespaceType]string `json:"namespace_paths"`
// Config is the container's configuration.
Config configs.Config `json:"config"`
// Container's standard descriptors (std{in,out,err}), needed for checkpoint and restore
ExternalDescriptors []string `json:"external_descriptors,omitempty"`
}
// A libcontainer container object.
//
// Each container is thread-safe within the same process. Since a container can
// be destroyed by a separate process, any function may return that the container
// was not found.
type Container interface {
// was not found. BaseContainer includes methods that are platform agnostic.
type BaseContainer interface {
// Returns the ID of the container
ID() string
@ -98,7 +88,7 @@ type Container interface {
// Systemerror - System error.
Stats() (*Stats, error)
// Set cgroup resources of container as configured
// Set resources of container as configured
//
// We can use this to change resources when containers are running.
//
@ -116,18 +106,6 @@ type Container interface {
// Systemerror - System error.
Start(process *Process) (err error)
// Checkpoint checkpoints the running container's state to disk using the criu(8) utility.
//
// errors:
// Systemerror - System error.
Checkpoint(criuOpts *CriuOpts) error
// Restore restores the checkpointed container to a running state using the criu(8) utiity.
//
// errors:
// Systemerror - System error.
Restore(process *Process, criuOpts *CriuOpts) error
// Destroys the container after killing all running processes.
//
// Any event registrations are removed before the container is destroyed.
@ -137,31 +115,6 @@ type Container interface {
// Systemerror - System error.
Destroy() error
// If the Container state is RUNNING or PAUSING, sets the Container state to PAUSING and pauses
// the execution of any user processes. Asynchronously, when the container finished being paused the
// state is changed to PAUSED.
// If the Container state is PAUSED, do nothing.
//
// errors:
// ContainerDestroyed - Container no longer exists,
// Systemerror - System error.
Pause() error
// If the Container state is PAUSED, resumes the execution of any user processes in the
// Container before setting the Container state to RUNNING.
// If the Container state is RUNNING, do nothing.
//
// errors:
// ContainerDestroyed - Container no longer exists,
// Systemerror - System error.
Resume() error
// NotifyOOM returns a read-only channel signaling when the container receives an OOM notification.
//
// errors:
// Systemerror - System error.
NotifyOOM() (<-chan struct{}, error)
// Signal sends the provided signal code to the container's initial process.
//
// errors:

View File

@ -9,6 +9,7 @@ import (
"os"
"os/exec"
"path/filepath"
"reflect"
"strings"
"sync"
"syscall"
@ -32,6 +33,73 @@ type linuxContainer struct {
initProcess parentProcess
criuPath string
m sync.Mutex
criuVersion int
}
// State represents a running container's state
type State struct {
BaseState
// Platform specific fields below here
// Path to all the cgroups setup for a container. Key is cgroup subsystem name
// with the value as the path.
CgroupPaths map[string]string `json:"cgroup_paths"`
// NamespacePaths are filepaths to the container's namespaces. Key is the namespace type
// with the value as the path.
NamespacePaths map[configs.NamespaceType]string `json:"namespace_paths"`
// Container's standard descriptors (std{in,out,err}), needed for checkpoint and restore
ExternalDescriptors []string `json:"external_descriptors,omitempty"`
}
// A libcontainer container object.
//
// Each container is thread-safe within the same process. Since a container can
// be destroyed by a separate process, any function may return that the container
// was not found.
type Container interface {
BaseContainer
// Methods below here are platform specific
// Checkpoint checkpoints the running container's state to disk using the criu(8) utility.
//
// errors:
// Systemerror - System error.
Checkpoint(criuOpts *CriuOpts) error
// Restore restores the checkpointed container to a running state using the criu(8) utiity.
//
// errors:
// Systemerror - System error.
Restore(process *Process, criuOpts *CriuOpts) error
// If the Container state is RUNNING or PAUSING, sets the Container state to PAUSING and pauses
// the execution of any user processes. Asynchronously, when the container finished being paused the
// state is changed to PAUSED.
// If the Container state is PAUSED, do nothing.
//
// errors:
// ContainerDestroyed - Container no longer exists,
// Systemerror - System error.
Pause() error
// If the Container state is PAUSED, resumes the execution of any user processes in the
// Container before setting the Container state to RUNNING.
// If the Container state is RUNNING, do nothing.
//
// errors:
// ContainerDestroyed - Container no longer exists,
// Systemerror - System error.
Resume() error
// NotifyOOM returns a read-only channel signaling when the container receives an OOM notification.
//
// errors:
// Systemerror - System error.
NotifyOOM() (<-chan struct{}, error)
}
// ID returns the container's unique ID
@ -111,10 +179,25 @@ func (c *linuxContainer) Start(process *Process) error {
}
return newSystemError(err)
}
process.ops = parent
if doInit {
c.updateState(parent)
}
if c.config.Hooks != nil {
s := configs.HookState{
Version: c.config.Version,
ID: c.id,
Pid: parent.pid(),
Root: c.config.Rootfs,
}
for _, hook := range c.config.Hooks.Poststart {
if err := hook.Run(s); err != nil {
if err := parent.terminate(); err != nil {
logrus.Warn(err)
}
return newSystemError(err)
}
}
}
return nil
}
@ -186,6 +269,7 @@ func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, c
manager: c.cgroupManager,
config: c.newInitConfig(p),
container: c,
process: p,
}, nil
}
@ -204,6 +288,7 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe,
childPipe: childPipe,
parentPipe: parentPipe,
config: c.newInitConfig(p),
process: p,
}
}
@ -337,7 +422,9 @@ func (c *linuxContainer) checkCriuVersion(min_version string) error {
}
}
if x*10000+y*100+z < versionReq {
c.criuVersion = x*10000 + y*100 + z
if c.criuVersion < versionReq {
return fmt.Errorf("CRIU version must be %s or higher", min_version)
}
@ -665,6 +752,8 @@ func (c *linuxContainer) criuSwrk(process *Process, req *criurpc.CriuReq, opts *
defer criuServer.Close()
args := []string{"swrk", "3"}
logrus.Debugf("Using CRIU %d at: %s", c.criuVersion, c.criuPath)
logrus.Debugf("Using CRIU with following args: %s", args)
cmd := exec.Command(c.criuPath, args...)
if process != nil {
cmd.Stdin = process.Stdin
@ -701,6 +790,18 @@ func (c *linuxContainer) criuSwrk(process *Process, req *criurpc.CriuReq, opts *
}
}
logrus.Debugf("Using CRIU in %s mode", req.GetType().String())
val := reflect.ValueOf(req.GetOpts())
v := reflect.Indirect(val)
for i := 0; i < v.NumField(); i++ {
st := v.Type()
name := st.Field(i).Name
if strings.HasPrefix(name, "XXX_") {
continue
}
value := val.MethodByName("Get" + name).Call([]reflect.Value{})
logrus.Debugf("CRIU option %s with value %v", name, value[0])
}
data, err := proto.Marshal(req)
if err != nil {
return err
@ -899,13 +1000,15 @@ func (c *linuxContainer) currentState() (*State, error) {
return nil, newSystemError(err)
}
state := &State{
ID: c.ID(),
Config: *c.config,
InitProcessPid: c.initProcess.pid(),
InitProcessStartTime: startTime,
CgroupPaths: c.cgroupManager.GetPaths(),
NamespacePaths: make(map[configs.NamespaceType]string),
ExternalDescriptors: c.initProcess.externalDescriptors(),
BaseState: BaseState{
ID: c.ID(),
Config: *c.config,
InitProcessPid: c.initProcess.pid(),
InitProcessStartTime: startTime,
},
CgroupPaths: c.cgroupManager.GetPaths(),
NamespacePaths: make(map[configs.NamespaceType]string),
ExternalDescriptors: c.initProcess.externalDescriptors(),
}
for _, ns := range c.config.Namespaces {
state.NamespacePaths[ns.Type] = ns.GetPath(c.initProcess.pid())

View File

@ -0,0 +1,20 @@
package libcontainer
// State represents a running container's state
type State struct {
BaseState
// Platform specific fields below here
}
// A libcontainer container object.
//
// Each container is thread-safe within the same process. Since a container can
// be destroyed by a separate process, any function may return that the container
// was not found.
type Container interface {
BaseContainer
// Methods below here are platform specific
}

View File

@ -1,3 +1,5 @@
// +build linux freebsd
package libcontainer
// cgroup restoring strategy provided by criu

View File

@ -0,0 +1,6 @@
package libcontainer
// TODO Windows: This can ultimately be entirely factored out as criu is
// a Unix concept not relevant on Windows.
type CriuOpts struct {
}

View File

@ -0,0 +1,3 @@
// +build windows
package devices

View File

@ -1,16 +0,0 @@
package devices
import (
"github.com/opencontainers/runc/libcontainer/configs"
)
// TODO Windows. This can be factored out further - Devices are not supported
// by Windows Containers.
func DeviceFromPath(path, permissions string) (*configs.Device, error) {
return nil, nil
}
func HostDevices() ([]*configs.Device, error) {
return nil, nil
}

View File

@ -159,7 +159,7 @@ func (l *LinuxFactory) Create(id string, config *configs.Config) (Container, err
}
containerRoot := filepath.Join(l.Root, id)
if _, err := os.Stat(containerRoot); err == nil {
return nil, newGenericError(fmt.Errorf("Container with id exists: %v", id), IdInUse)
return nil, newGenericError(fmt.Errorf("container with id exists: %v", id), IdInUse)
} else if !os.IsNotExist(err) {
return nil, newGenericError(err, SystemError)
}
@ -210,9 +210,10 @@ func (l *LinuxFactory) Type() string {
// StartInitialization loads a container by opening the pipe fd from the parent to read the configuration and state
// This is a low level implementation detail of the reexec and should not be consumed externally
func (l *LinuxFactory) StartInitialization() (err error) {
pipefd, err := strconv.Atoi(os.Getenv("_LIBCONTAINER_INITPIPE"))
fdStr := os.Getenv("_LIBCONTAINER_INITPIPE")
pipefd, err := strconv.Atoi(fdStr)
if err != nil {
return err
return fmt.Errorf("error converting env var _LIBCONTAINER_INITPIPE(%q) to an int: %s", fdStr, err)
}
var (
pipe = os.NewFile(uintptr(pipefd), "pipe")
@ -260,10 +261,10 @@ func (l *LinuxFactory) loadState(root string) (*State, error) {
func (l *LinuxFactory) validateID(id string) error {
if !idRegex.MatchString(id) {
return newGenericError(fmt.Errorf("Invalid id format: %v", id), InvalidIdFormat)
return newGenericError(fmt.Errorf("invalid id format: %v", id), InvalidIdFormat)
}
if len(id) > maxIdLen {
return newGenericError(fmt.Errorf("Invalid id format: %v", id), InvalidIdFormat)
return newGenericError(fmt.Errorf("invalid id format: %v", id), InvalidIdFormat)
}
return nil
}

View File

@ -47,6 +47,7 @@ type setnsProcess struct {
cgroupPaths map[string]string
config *initConfig
fds []string
process *Process
}
func (p *setnsProcess) startTime() (string, error) {
@ -87,7 +88,6 @@ func (p *setnsProcess) start() (err error) {
p.wait()
return newSystemError(ierr)
}
return nil
}
@ -115,13 +115,12 @@ func (p *setnsProcess) execSetns() error {
p.cmd.Wait()
return newSystemError(err)
}
process, err := os.FindProcess(pid.Pid)
if err != nil {
return err
}
p.cmd.Process = process
p.process.ops = p
return nil
}
@ -165,6 +164,7 @@ type initProcess struct {
manager cgroups.Manager
container *linuxContainer
fds []string
process *Process
}
func (p *initProcess) pid() int {
@ -178,8 +178,10 @@ func (p *initProcess) externalDescriptors() []string {
func (p *initProcess) start() (err error) {
defer p.parentPipe.Close()
err = p.cmd.Start()
p.process.ops = p
p.childPipe.Close()
if err != nil {
p.process.ops = nil
return newSystemError(err)
}
// Save the standard descriptor names before the container process

View File

@ -29,7 +29,7 @@ func setupRootfs(config *configs.Config, console *linuxConsole) (err error) {
return newSystemError(err)
}
setupDev := len(config.Devices) == 0
setupDev := len(config.Devices) != 0
for _, m := range config.Mounts {
for _, precmd := range m.PremountCmds {
if err := mountCmd(precmd); err != nil {
@ -46,7 +46,7 @@ func setupRootfs(config *configs.Config, console *linuxConsole) (err error) {
}
}
}
if !setupDev {
if setupDev {
if err := createDevices(config); err != nil {
return newSystemError(err)
}
@ -68,7 +68,7 @@ func setupRootfs(config *configs.Config, console *linuxConsole) (err error) {
if err != nil {
return newSystemError(err)
}
if !setupDev {
if setupDev {
if err := reOpenDevNull(); err != nil {
return newSystemError(err)
}
@ -290,8 +290,7 @@ func getCgroupMounts(m *configs.Mount) ([]*configs.Mount, error) {
return binds, nil
}
// checkMountDestination checks to ensure that the mount destination is not over the
// top of /proc or /sys.
// checkMountDestination checks to ensure that the mount destination is not over the top of /proc.
// dest is required to be an abs path and have any symlinks resolved before calling this function.
func checkMountDestination(rootfs, dest string) error {
if filepath.Clean(rootfs) == filepath.Clean(dest) {
@ -379,6 +378,17 @@ func createDevices(config *configs.Config) error {
return nil
}
func bindMountDeviceNode(dest string, node *configs.Device) error {
f, err := os.Create(dest)
if err != nil && !os.IsExist(err) {
return err
}
if f != nil {
f.Close()
}
return syscall.Mount(node.Path, dest, "bind", syscall.MS_BIND, "")
}
// Creates the device node in the rootfs of the container.
func createDeviceNode(rootfs string, node *configs.Device, bind bool) error {
dest := filepath.Join(rootfs, node.Path)
@ -387,18 +397,13 @@ func createDeviceNode(rootfs string, node *configs.Device, bind bool) error {
}
if bind {
f, err := os.Create(dest)
if err != nil && !os.IsExist(err) {
return err
}
if f != nil {
f.Close()
}
return syscall.Mount(node.Path, dest, "bind", syscall.MS_BIND, "")
return bindMountDeviceNode(dest, node)
}
if err := mknodDevice(dest, node); err != nil {
if os.IsExist(err) {
return nil
} else if os.IsPermission(err) {
return bindMountDeviceNode(dest, node)
}
return err
}
@ -634,7 +639,6 @@ func remount(m *configs.Mount, rootfs string) error {
if !strings.HasPrefix(dest, rootfs) {
dest = filepath.Join(rootfs, dest)
}
if err := syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags|syscall.MS_REMOUNT), ""); err != nil {
return err
}

View File

@ -6,29 +6,47 @@ import (
"github.com/opencontainers/runc/libcontainer/configs"
)
var operators = map[string]configs.Operator{
"SCMP_CMP_NE": configs.NotEqualTo,
"SCMP_CMP_LT": configs.LessThan,
"SCMP_CMP_LE": configs.LessThanOrEqualTo,
"SCMP_CMP_EQ": configs.EqualTo,
"SCMP_CMP_GE": configs.GreaterThanOrEqualTo,
"SCMP_CMP_GT": configs.GreaterThan,
"SCMP_CMP_MASKED_EQ": configs.MaskEqualTo,
}
var actions = map[string]configs.Action{
"SCMP_ACT_KILL": configs.Kill,
"SCMP_ACT_ERRNO": configs.Errno,
"SCMP_ACT_TRAP": configs.Trap,
"SCMP_ACT_ALLOW": configs.Allow,
"SCMP_ACT_TRACE": configs.Trace,
}
var archs = map[string]string{
"SCMP_ARCH_X86": "x86",
"SCMP_ARCH_X86_64": "amd64",
"SCMP_ARCH_X32": "x32",
"SCMP_ARCH_ARM": "arm",
"SCMP_ARCH_AARCH64": "arm64",
"SCMP_ARCH_MIPS": "mips",
"SCMP_ARCH_MIPS64": "mips64",
"SCMP_ARCH_MIPS64N32": "mips64n32",
"SCMP_ARCH_MIPSEL": "mipsel",
"SCMP_ARCH_MIPSEL64": "mipsel64",
"SCMP_ARCH_MIPSEL64N32": "mipsel64n32",
}
// ConvertStringToOperator converts a string into a Seccomp comparison operator.
// Comparison operators use the names they are assigned by Libseccomp's header.
// Attempting to convert a string that is not a valid operator results in an
// error.
func ConvertStringToOperator(in string) (configs.Operator, error) {
switch in {
case "SCMP_CMP_NE":
return configs.NotEqualTo, nil
case "SCMP_CMP_LT":
return configs.LessThan, nil
case "SCMP_CMP_LE":
return configs.LessThanOrEqualTo, nil
case "SCMP_CMP_EQ":
return configs.EqualTo, nil
case "SCMP_CMP_GE":
return configs.GreaterThan, nil
case "SCMP_CMP_GT":
return configs.GreaterThanOrEqualTo, nil
case "SCMP_CMP_MASKED_EQ":
return configs.MaskEqualTo, nil
default:
return 0, fmt.Errorf("string %s is not a valid operator for seccomp", in)
if op, ok := operators[in]; ok == true {
return op, nil
}
return 0, fmt.Errorf("string %s is not a valid operator for seccomp", in)
}
// ConvertStringToAction converts a string into a Seccomp rule match action.
@ -38,16 +56,16 @@ func ConvertStringToOperator(in string) (configs.Operator, error) {
// Attempting to convert a string that is not a valid action results in an
// error.
func ConvertStringToAction(in string) (configs.Action, error) {
switch in {
case "SCMP_ACT_KILL":
return configs.Kill, nil
case "SCMP_ACT_ERRNO":
return configs.Errno, nil
case "SCMP_ACT_TRAP":
return configs.Trap, nil
case "SCMP_ACT_ALLOW":
return configs.Allow, nil
default:
return 0, fmt.Errorf("string %s is not a valid action for seccomp", in)
if act, ok := actions[in]; ok == true {
return act, nil
}
return 0, fmt.Errorf("string %s is not a valid action for seccomp", in)
}
// ConvertStringToArch converts a string into a Seccomp comparison arch.
func ConvertStringToArch(in string) (string, error) {
if arch, ok := archs[in]; ok == true {
return arch, nil
}
return "", fmt.Errorf("string %s is not a valid arch for seccomp", in)
}

View File

@ -15,6 +15,7 @@ var (
actAllow = libseccomp.ActAllow
actTrap = libseccomp.ActTrap
actKill = libseccomp.ActKill
actTrace = libseccomp.ActTrace.SetReturnCode(int16(syscall.EPERM))
actErrno = libseccomp.ActErrno.SetReturnCode(int16(syscall.EPERM))
)
@ -83,6 +84,8 @@ func getAction(act configs.Action) (libseccomp.ScmpAction, error) {
return actTrap, nil
case configs.Allow:
return actAllow, nil
case configs.Trace:
return actTrace, nil
default:
return libseccomp.ActInvalid, fmt.Errorf("invalid action, cannot use in rule")
}

View File

@ -36,7 +36,7 @@ func ResolveRootfs(uncleanRootfs string) (string, error) {
}
// ExitStatus returns the correct exit status for a process based on if it
// was signaled or existed cleanly.
// was signaled or exited cleanly.
func ExitStatus(status syscall.WaitStatus) int {
if status.Signaled() {
return exitSignalOffset + int(status.Signal())

View File

@ -86,6 +86,10 @@ func getVfsCap(path string, dest *vfscapData) (err error) {
}
r0, _, e1 := syscall.Syscall6(syscall.SYS_GETXATTR, uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(_vfsXattrName)), uintptr(unsafe.Pointer(dest)), vfscapDataSizeV2, 0, 0)
if e1 != 0 {
if e1 == syscall.ENODATA {
dest.version = 2
return
}
err = e1
}
switch dest.magic & vfsCapVerMask {
@ -128,8 +132,6 @@ func setVfsCap(path string, data *vfscapData) (err error) {
data.magic = vfsCapVer2
if data.effective[0] != 0 || data.effective[1] != 0 {
data.magic |= vfsCapFlageffective
data.data[0].permitted |= data.effective[0]
data.data[1].permitted |= data.effective[1]
}
size = vfscapDataSizeV2
} else {