diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index c9522544..6a0f3cb8 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -9,8 +9,8 @@ }, { "ImportPath": "github.com/docker/libcontainer", - "Comment": "v1.1.0-115-ge6a43c1", - "Rev": "e6a43c1c2b9f769deb96348a0a93417cd48a36d8" + "Comment": "v1.1.0-194-gedfe81a", + "Rev": "edfe81a08b2780ad75b63e60b6cb9eb3a17c671f" }, { "ImportPath": "github.com/fsouza/go-dockerclient", diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/.travis.yml b/Godeps/_workspace/src/github.com/docker/libcontainer/.travis.yml index 7040d0bd..3ce0e27e 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/.travis.yml +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/.travis.yml @@ -13,22 +13,24 @@ env: - _GOOS=linux _GOARCH=arm CGO_ENABLED=0 install: + - go get code.google.com/p/go.tools/cmd/cover - mkdir -pv "${GOPATH%%:*}/src/github.com/docker" && [ -d "${GOPATH%%:*}/src/github.com/docker/libcontainer" ] || ln -sv "$(readlink -f .)" "${GOPATH%%:*}/src/github.com/docker/libcontainer" - if [ -z "$TRAVIS_GLOBAL_WTF" ]; then gvm cross "$_GOOS" "$_GOARCH"; export GOOS="$_GOOS" GOARCH="$_GOARCH"; fi + - export GOPATH="$GOPATH:$(pwd)/vendor" - if [ -z "$TRAVIS_GLOBAL_WTF" ]; then go env; fi - - go get -d -v ./... + - go get -d -v ./... # TODO remove this if /docker/docker gets purged from our includes - if [ "$TRAVIS_GLOBAL_WTF" ]; then - export DOCKER_PATH="${GOPATH%%:*}/src/github.com/dotcloud/docker"; + export DOCKER_PATH="${GOPATH%%:*}/src/github.com/docker/docker"; mkdir -p "$DOCKER_PATH/hack/make"; - ( cd "$DOCKER_PATH/hack/make" && wget -c 'https://raw.githubusercontent.com/dotcloud/docker/master/hack/make/'{.validate,validate-dco,validate-gofmt} ); - sed -i 's!dotcloud/docker!docker/libcontainer!' "$DOCKER_PATH/hack/make/.validate"; + ( cd "$DOCKER_PATH/hack/make" && wget -c 'https://raw.githubusercontent.com/docker/docker/master/hack/make/'{.validate,validate-dco,validate-gofmt} ); + sed -i 's!docker/docker!docker/libcontainer!' "$DOCKER_PATH/hack/make/.validate"; fi script: - if [ "$TRAVIS_GLOBAL_WTF" ]; then bash "$DOCKER_PATH/hack/make/validate-dco"; fi - if [ "$TRAVIS_GLOBAL_WTF" ]; then bash "$DOCKER_PATH/hack/make/validate-gofmt"; fi - - if [ -z "$TRAVIS_GLOBAL_WTF" ]; then go build -v ./...; fi - - if [ -z "$TRAVIS_GLOBAL_WTF" -a "$GOARCH" != 'arm' ]; then go test -test.short -v ./...; fi + - if [ -z "$TRAVIS_GLOBAL_WTF" ]; then make direct-build; fi + - if [ -z "$TRAVIS_GLOBAL_WTF" -a "$GOARCH" != 'arm' ]; then make direct-test-short; fi diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/CONTRIBUTORS_GUIDE.md b/Godeps/_workspace/src/github.com/docker/libcontainer/CONTRIBUTORS_GUIDE.md index f0268962..07bf22a0 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/CONTRIBUTORS_GUIDE.md +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/CONTRIBUTORS_GUIDE.md @@ -176,7 +176,7 @@ One way to automate this, is customise your get ``commit.template`` by adding a ``prepare-commit-msg`` hook to your libcontainer checkout: ``` -curl -o .git/hooks/prepare-commit-msg https://raw.githubusercontent.com/dotcloud/docker/master/contrib/prepare-commit-msg.hook && chmod +x .git/hooks/prepare-commit-msg +curl -o .git/hooks/prepare-commit-msg https://raw.githubusercontent.com/docker/docker/master/contrib/prepare-commit-msg.hook && chmod +x .git/hooks/prepare-commit-msg ``` * Note: the above script expects to find your GitHub user name in ``git config --get github.user`` diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/Dockerfile b/Godeps/_workspace/src/github.com/docker/libcontainer/Dockerfile index 1fbba3e5..65bf5731 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/Dockerfile +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/Dockerfile @@ -1,6 +1,6 @@ FROM crosbymichael/golang -RUN apt-get update && apt-get install -y gcc +RUN apt-get update && apt-get install -y gcc make RUN go get code.google.com/p/go.tools/cmd/cover # setup a playground for us to spawn containers in @@ -14,8 +14,10 @@ COPY . /go/src/github.com/docker/libcontainer WORKDIR /go/src/github.com/docker/libcontainer RUN cp sample_configs/minimal.json /busybox/container.json +ENV GOPATH $GOPATH:/go/src/github.com/docker/libcontainer/vendor + RUN go get -d -v ./... -RUN go install -v ./... +RUN make direct-install ENTRYPOINT ["/dind"] -CMD ["go", "test", "-cover", "./..."] +CMD ["make", "direct-test"] diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/MAINTAINERS b/Godeps/_workspace/src/github.com/docker/libcontainer/MAINTAINERS index 8c36d096..24011b05 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/MAINTAINERS +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/MAINTAINERS @@ -1,4 +1,6 @@ Michael Crosby (@crosbymichael) Rohit Jnagal (@rjnagal) Victor Marmol (@vmarmol) +Mrunal Patel (@mrunalp) .travis.yml: Tianon Gravi (@tianon) +update-vendor.sh: Tianon Gravi (@tianon) diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/Makefile b/Godeps/_workspace/src/github.com/docker/libcontainer/Makefile index 843b761e..d6852b24 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/Makefile +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/Makefile @@ -2,9 +2,23 @@ all: docker build -t docker/libcontainer . -test: +test: # we need NET_ADMIN for the netlink tests and SYS_ADMIN for mounting - docker run --rm --cap-add NET_ADMIN --cap-add SYS_ADMIN docker/libcontainer + docker run --rm -it --cap-add NET_ADMIN --cap-add SYS_ADMIN docker/libcontainer sh: - docker run --rm -ti -w /busybox --rm --cap-add NET_ADMIN --cap-add SYS_ADMIN docker/libcontainer nsinit exec sh + docker run --rm -it --cap-add NET_ADMIN --cap-add SYS_ADMIN -w /busybox docker/libcontainer nsinit exec sh + +GO_PACKAGES = $(shell find . -not \( -wholename ./vendor -prune \) -name '*.go' -print0 | xargs -0n1 dirname | sort -u) + +direct-test: + go test -cover -v $(GO_PACKAGES) + +direct-test-short: + go test -cover -test.short -v $(GO_PACKAGES) + +direct-build: + go build -v $(GO_PACKAGES) + +direct-install: + go install -v $(GO_PACKAGES) diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/README.md b/Godeps/_workspace/src/github.com/docker/libcontainer/README.md index ee14a57c..b80d2841 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/README.md +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/README.md @@ -1,4 +1,4 @@ -## libcontainer - reference implementation for containers +## libcontainer - reference implementation for containers [![Build Status](https://travis-ci.org/docker/libcontainer.png?branch=master)](https://travis-ci.org/docker/libcontainer) ### Note on API changes: diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/cgroups.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/cgroups.go index 64ece568..59845486 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/cgroups.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/cgroups.go @@ -37,4 +37,5 @@ type Cgroup struct { type ActiveCgroup interface { Cleanup() error + Paths() (map[string]string, error) } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/cgutil/cgutil.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/cgutil/cgutil.go index f4a541ea..d1a66117 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/cgutil/cgutil.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/cgutil/cgutil.go @@ -18,8 +18,8 @@ var createCommand = cli.Command{ Name: "create", Usage: "Create a cgroup container using the supplied configuration and initial process.", Flags: []cli.Flag{ - cli.StringFlag{"config, c", "cgroup.json", "path to container configuration (cgroups.Cgroup object)"}, - cli.IntFlag{"pid, p", 0, "pid of the initial process in the container"}, + cli.StringFlag{Name: "config, c", Value: "cgroup.json", Usage: "path to container configuration (cgroups.Cgroup object)"}, + cli.IntFlag{Name: "pid, p", Value: 0, Usage: "pid of the initial process in the container"}, }, Action: createAction, } @@ -28,8 +28,8 @@ var destroyCommand = cli.Command{ Name: "destroy", Usage: "Destroy an existing cgroup container.", Flags: []cli.Flag{ - cli.StringFlag{"name, n", "", "container name"}, - cli.StringFlag{"parent, p", "", "container parent"}, + cli.StringFlag{Name: "name, n", Value: "", Usage: "container name"}, + cli.StringFlag{Name: "parent, p", Value: "", Usage: "container parent"}, }, Action: destroyAction, } @@ -38,8 +38,8 @@ var statsCommand = cli.Command{ Name: "stats", Usage: "Get stats for cgroup", Flags: []cli.Flag{ - cli.StringFlag{"name, n", "", "container name"}, - cli.StringFlag{"parent, p", "", "container parent"}, + cli.StringFlag{Name: "name, n", Value: "", Usage: "container name"}, + cli.StringFlag{Name: "parent, p", Value: "", Usage: "container parent"}, }, Action: statsAction, } @@ -48,8 +48,8 @@ var pauseCommand = cli.Command{ Name: "pause", Usage: "Pause cgroup", Flags: []cli.Flag{ - cli.StringFlag{"name, n", "", "container name"}, - cli.StringFlag{"parent, p", "", "container parent"}, + cli.StringFlag{Name: "name, n", Value: "", Usage: "container name"}, + cli.StringFlag{Name: "parent, p", Value: "", Usage: "container parent"}, }, Action: pauseAction, } @@ -58,8 +58,8 @@ var resumeCommand = cli.Command{ Name: "resume", Usage: "Resume a paused cgroup", Flags: []cli.Flag{ - cli.StringFlag{"name, n", "", "container name"}, - cli.StringFlag{"parent, p", "", "container parent"}, + cli.StringFlag{Name: "name, n", Value: "", Usage: "container name"}, + cli.StringFlag{Name: "parent, p", Value: "", Usage: "container parent"}, }, Action: resumeAction, } @@ -68,8 +68,8 @@ var psCommand = cli.Command{ Name: "ps", Usage: "Get list of pids for a cgroup", Flags: []cli.Flag{ - cli.StringFlag{"name, n", "", "container name"}, - cli.StringFlag{"parent, p", "", "container parent"}, + cli.StringFlag{Name: "name, n", Value: "", Usage: "container name"}, + cli.StringFlag{Name: "parent, p", Value: "", Usage: "container parent"}, }, Action: psAction, } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go index e9c06e1e..e20cdbb9 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go @@ -21,12 +21,16 @@ var ( "perf_event": &PerfEventGroup{}, "freezer": &FreezerGroup{}, } + CgroupProcesses = "cgroup.procs" ) type subsystem interface { - Set(*data) error + // 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 - GetStats(string, *cgroups.Stats) error + // Creates and joins the cgroup represented by data. + Set(*data) error } type data struct { @@ -149,7 +153,23 @@ func (raw *data) parent(subsystem string) (string, error) { return filepath.Join(raw.root, subsystem, initPath), nil } +func (raw *data) Paths() (map[string]string, error) { + paths := make(map[string]string) + for sysname := range subsystems { + path, err := raw.path(sysname) + if err != nil { + return nil, err + } + paths[sysname] = path + } + return paths, nil +} + func (raw *data) path(subsystem string) (string, error) { + // 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, subsystem, raw.cgroup), nil + } parent, err := raw.parent(subsystem) if err != nil { return "", err @@ -165,7 +185,7 @@ func (raw *data) join(subsystem string) (string, error) { if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) { return "", err } - if err := writeFile(path, "cgroup.procs", strconv.Itoa(raw.pid)); err != nil { + if err := writeFile(path, CgroupProcesses, strconv.Itoa(raw.pid)); err != nil { return "", err } return path, nil diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/cpuacct.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/cpuacct.go index 7979009c..7761d4c2 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/cpuacct.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/cpuacct.go @@ -54,7 +54,7 @@ func (s *CpuacctGroup) GetStats(path string, stats *cgroups.Stats) error { return err } // sample for 100ms - time.Sleep(100 * time.Millisecond) + time.Sleep(1000 * time.Millisecond) if kernelModeUsage, userModeUsage, err = getCpuUsage(path); err != nil { return err } @@ -73,7 +73,7 @@ func (s *CpuacctGroup) GetStats(path string, stats *cgroups.Stats) error { deltaUsage = lastUsage - startUsage ) if deltaSystem > 0.0 { - percentage = ((deltaProc / deltaSystem) * clockTicks) * cpuCount + percentage = uint64((float64(deltaProc) / float64(deltaSystem)) * float64(clockTicks*cpuCount)) } // NOTE: a percentage over 100% is valid for POSIX because that means the // processes is using multiple cores diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/cpuset.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/cpuset.go index 9570125f..88477394 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/cpuset.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/cpuset.go @@ -20,19 +20,10 @@ func (s *CpusetGroup) Set(d *data) error { if err != nil { return err } - if err := s.ensureParent(dir); 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(d.pid)); err != nil { - return err - } - if err := writeFile(dir, "cpuset.cpus", d.c.CpusetCpus); err != nil { - return err - } + return s.SetDir(dir, d.c.CpusetCpus, d.pid) } + return nil } @@ -44,6 +35,24 @@ func (s *CpusetGroup) GetStats(path string, stats *cgroups.Stats) error { return nil } +func (s *CpusetGroup) SetDir(dir, value string, pid int) error { + if err := s.ensureParent(dir); 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 + } + + if err := writeFile(dir, "cpuset.cpus", value); err != nil { + return err + } + + return nil +} + func (s *CpusetGroup) getSubsystemSettings(parent string) (cpus []byte, mems []byte, err error) { if cpus, err = ioutil.ReadFile(filepath.Join(parent, "cpuset.cpus")); err != nil { return diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/fs.test b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/fs.test new file mode 100644 index 00000000..7d61b991 Binary files /dev/null and b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/fs.test differ diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/memory.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/memory.go index c27150d2..ea92934a 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/memory.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/memory.go @@ -14,7 +14,7 @@ type MemoryGroup struct { func (s *MemoryGroup) Set(d *data) error { dir, err := d.join("memory") - // only return an error for memory if it was not specified + // only return an error for memory if it was specified if err != nil && (d.c.Memory != 0 || d.c.MemoryReservation != 0 || d.c.MemorySwap != 0) { return err } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/stats_test_util.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/stats_util_test.go similarity index 100% rename from Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/stats_test_util.go rename to Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/stats_util_test.go diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/test_util.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/util_test.go similarity index 100% rename from Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/test_util.go rename to Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/util_test.go diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/utils_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/utils_test.go index 63d743f0..6ea59bc5 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/utils_test.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/fs/utils_test.go @@ -5,6 +5,8 @@ import ( "os" "path/filepath" "testing" + + "github.com/docker/libcontainer/cgroups" ) const ( @@ -66,3 +68,20 @@ func TestGetCgroupParamsInt(t *testing.T) { t.Fatal("Expecting error, got none") } } + +func TestAbsolutePathHandling(t *testing.T) { + testCgroup := cgroups.Cgroup{ + Name: "bar", + Parent: "/foo", + } + cgroupData := data{ + root: "/sys/fs/cgroup", + cgroup: "/foo/bar", + c: &testCgroup, + pid: 1, + } + expectedPath := filepath.Join(cgroupData.root, "cpu", testCgroup.Parent, testCgroup.Name) + if path, err := cgroupData.path("cpu"); path != expectedPath || err != nil { + t.Fatalf("expected path %s but got %s %s", expectedPath, path, err) + } +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go index 01e5bf49..252fb60e 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go @@ -13,15 +13,14 @@ import ( "sync" "time" - systemd1 "github.com/coreos/go-systemd/dbus" - "github.com/docker/docker/pkg/systemd" + systemd "github.com/coreos/go-systemd/dbus" "github.com/docker/libcontainer/cgroups" "github.com/docker/libcontainer/cgroups/fs" "github.com/godbus/dbus" ) type systemdCgroup struct { - cleanupDirs []string + cgroup *cgroups.Cgroup } type subsystem interface { @@ -30,7 +29,7 @@ type subsystem interface { var ( connLock sync.Mutex - theConn *systemd1.Conn + theConn *systemd.Conn hasStartTransientUnit bool subsystems = map[string]subsystem{ "devices": &fs.DevicesGroup{}, @@ -45,7 +44,8 @@ var ( ) func UseSystemd() bool { - if !systemd.SdBooted() { + s, err := os.Stat("/run/systemd/system") + if err != nil || !s.IsDir() { return false } @@ -54,7 +54,7 @@ func UseSystemd() bool { if theConn == nil { var err error - theConn, err = systemd1.New() + theConn, err = systemd.New() if err != nil { return false } @@ -84,267 +84,126 @@ func getIfaceForUnit(unitName string) string { return "Unit" } -type cgroupArg struct { - File string - Value string -} - func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) { var ( unitName = getUnitName(c) slice = "system.slice" - properties []systemd1.Property - cpuArgs []cgroupArg - cpusetArgs []cgroupArg - memoryArgs []cgroupArg - res systemdCgroup + properties []systemd.Property + res = &systemdCgroup{} ) - // First set up things not supported by systemd - - // -1 disables memorySwap - if c.MemorySwap >= 0 && (c.Memory != 0 || c.MemorySwap > 0) { - memorySwap := c.MemorySwap - - if memorySwap == 0 { - // By default, MemorySwap is set to twice the size of RAM. - memorySwap = c.Memory * 2 - } - - memoryArgs = append(memoryArgs, cgroupArg{"memory.memsw.limit_in_bytes", strconv.FormatInt(memorySwap, 10)}) - } - - if c.CpusetCpus != "" { - cpusetArgs = append(cpusetArgs, cgroupArg{"cpuset.cpus", c.CpusetCpus}) - } + res.cgroup = c if c.Slice != "" { slice = c.Slice } properties = append(properties, - systemd1.Property{"Slice", dbus.MakeVariant(slice)}, - systemd1.Property{"Description", dbus.MakeVariant("docker container " + c.Name)}, - systemd1.Property{"PIDs", dbus.MakeVariant([]uint32{uint32(pid)})}, + systemd.Property{"Slice", dbus.MakeVariant(slice)}, + systemd.Property{"Description", dbus.MakeVariant("docker container " + c.Name)}, + systemd.Property{"PIDs", dbus.MakeVariant([]uint32{uint32(pid)})}, ) // Always enable accounting, this gets us the same behaviour as the fs implementation, // plus the kernel has some problems with joining the memory cgroup at a later time. properties = append(properties, - systemd1.Property{"MemoryAccounting", dbus.MakeVariant(true)}, - systemd1.Property{"CPUAccounting", dbus.MakeVariant(true)}, - systemd1.Property{"BlockIOAccounting", dbus.MakeVariant(true)}) + systemd.Property{"MemoryAccounting", dbus.MakeVariant(true)}, + systemd.Property{"CPUAccounting", dbus.MakeVariant(true)}, + systemd.Property{"BlockIOAccounting", dbus.MakeVariant(true)}) if c.Memory != 0 { properties = append(properties, - systemd1.Property{"MemoryLimit", dbus.MakeVariant(uint64(c.Memory))}) + systemd.Property{"MemoryLimit", dbus.MakeVariant(uint64(c.Memory))}) } // TODO: MemoryReservation and MemorySwap not available in systemd if c.CpuShares != 0 { properties = append(properties, - systemd1.Property{"CPUShares", dbus.MakeVariant(uint64(c.CpuShares))}) + systemd.Property{"CPUShares", dbus.MakeVariant(uint64(c.CpuShares))}) } if _, err := theConn.StartTransientUnit(unitName, "replace", properties...); err != nil { return nil, err } - // To work around the lack of /dev/pts/* support above we need to manually add these - // so, ask systemd for the cgroup used - props, err := theConn.GetUnitTypeProperties(unitName, getIfaceForUnit(unitName)) - if err != nil { - return nil, err - } - - cgroup := props["ControlGroup"].(string) - if !c.AllowAllDevices { - // Atm we can't use the systemd device support because of two missing things: - // * Support for wildcards to allow mknod on any device - // * Support for wildcards to allow /dev/pts support - // - // The second is available in more recent systemd as "char-pts", but not in e.g. v208 which is - // in wide use. When both these are availalable we will be able to switch, but need to keep the old - // implementation for backwards compat. - // - // Note: we can't use systemd to set up the initial limits, and then change the cgroup - // because systemd will re-write the device settings if it needs to re-apply the cgroup context. - // This happens at least for v208 when any sibling unit is started. - - mountpoint, err := cgroups.FindCgroupMountpoint("devices") - if err != nil { + if err := joinDevices(c, pid); err != nil { return nil, err } - - initPath, err := cgroups.GetInitCgroupDir("devices") - if err != nil { - return nil, err - } - - dir := filepath.Join(mountpoint, initPath, c.Parent, c.Name) - - res.cleanupDirs = append(res.cleanupDirs, dir) - - if err := os.MkdirAll(dir, 0755); err != nil && !os.IsExist(err) { - return nil, err - } - - if err := ioutil.WriteFile(filepath.Join(dir, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700); err != nil { - return nil, err - } - - if err := writeFile(dir, "devices.deny", "a"); err != nil { - return nil, err - } - - for _, dev := range c.AllowedDevices { - if err := writeFile(dir, "devices.allow", dev.GetCgroupAllowString()); err != nil { - return nil, err - } - } } - if len(cpuArgs) != 0 { - mountpoint, err := cgroups.FindCgroupMountpoint("cpu") - if err != nil { + // -1 disables memorySwap + if c.MemorySwap >= 0 && (c.Memory != 0 || c.MemorySwap > 0) { + if err := joinMemory(c, pid); err != nil { return nil, err } - path := filepath.Join(mountpoint, cgroup) - - for _, arg := range cpuArgs { - if err := ioutil.WriteFile(filepath.Join(path, arg.File), []byte(arg.Value), 0700); err != nil { - return nil, err - } - } - } - - if len(memoryArgs) != 0 { - mountpoint, err := cgroups.FindCgroupMountpoint("memory") - if err != nil { - return nil, err - } - - path := filepath.Join(mountpoint, cgroup) - - for _, arg := range memoryArgs { - if err := ioutil.WriteFile(filepath.Join(path, arg.File), []byte(arg.Value), 0700); err != nil { - return nil, err - } - } } // we need to manually join the freezer cgroup in systemd because it does not currently support it // via the dbus api - freezerPath, err := joinFreezer(c, pid) - if err != nil { + if err := joinFreezer(c, pid); err != nil { return nil, err } - res.cleanupDirs = append(res.cleanupDirs, freezerPath) - if len(cpusetArgs) != 0 { - // systemd does not atm set up the cpuset controller, so we must manually - // join it. Additionally that is a very finicky controller where each - // level must have a full setup as the default for a new directory is "no cpus", - // so we avoid using any hierarchies here, creating a toplevel directory. - mountpoint, err := cgroups.FindCgroupMountpoint("cpuset") - if err != nil { - return nil, err - } - - initPath, err := cgroups.GetInitCgroupDir("cpuset") - if err != nil { - return nil, err - } - - var ( - foundCpus bool - foundMems bool - - rootPath = filepath.Join(mountpoint, initPath) - path = filepath.Join(mountpoint, initPath, c.Parent+"-"+c.Name) - ) - - res.cleanupDirs = append(res.cleanupDirs, path) - - if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) { - return nil, err - } - - for _, arg := range cpusetArgs { - if arg.File == "cpuset.cpus" { - foundCpus = true - } - if arg.File == "cpuset.mems" { - foundMems = true - } - if err := ioutil.WriteFile(filepath.Join(path, arg.File), []byte(arg.Value), 0700); err != nil { - return nil, err - } - } - - // These are required, if not specified inherit from parent - if !foundCpus { - s, err := ioutil.ReadFile(filepath.Join(rootPath, "cpuset.cpus")) - if err != nil { - return nil, err - } - - if err := ioutil.WriteFile(filepath.Join(path, "cpuset.cpus"), s, 0700); err != nil { - return nil, err - } - } - - // These are required, if not specified inherit from parent - if !foundMems { - s, err := ioutil.ReadFile(filepath.Join(rootPath, "cpuset.mems")) - if err != nil { - return nil, err - } - - if err := ioutil.WriteFile(filepath.Join(path, "cpuset.mems"), s, 0700); err != nil { - return nil, err - } - } - - if err := ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700); err != nil { + if c.CpusetCpus != "" { + if err := joinCpuset(c, pid); err != nil { return nil, err } } - return &res, nil + return res, nil } func writeFile(dir, file, data string) error { return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700) } +func (c *systemdCgroup) Paths() (map[string]string, error) { + paths := make(map[string]string) + + for sysname := range subsystems { + subsystemPath, err := getSubsystemPath(c.cgroup, sysname) + if err != nil { + // Don't fail if a cgroup hierarchy was not found, just skip this subsystem + if err == cgroups.ErrNotFound { + continue + } + + return nil, err + } + + paths[sysname] = subsystemPath + } + + return paths, nil +} + func (c *systemdCgroup) Cleanup() error { // systemd cleans up, we don't need to do much + paths, err := c.Paths() + if err != nil { + return err + } - for _, path := range c.cleanupDirs { + for _, path := range paths { os.RemoveAll(path) } return nil } -func joinFreezer(c *cgroups.Cgroup, pid int) (string, error) { +func joinFreezer(c *cgroups.Cgroup, pid int) error { path, err := getSubsystemPath(c, "freezer") if err != nil { - return "", err + return err } if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) { - return "", err + return err } - if err := ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700); err != nil { - return "", err - } - - return path, nil + return ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700) } func getSubsystemPath(c *cgroups.Cgroup, subsystem string) (string, error) { @@ -389,20 +248,12 @@ func Freeze(c *cgroups.Cgroup, state cgroups.FreezerState) error { } func GetPids(c *cgroups.Cgroup) ([]int, error) { - unitName := getUnitName(c) - - mountpoint, err := cgroups.FindCgroupMountpoint("cpu") + path, err := getSubsystemPath(c, "cpu") if err != nil { return nil, err } - props, err := theConn.GetUnitTypeProperties(unitName, getIfaceForUnit(unitName)) - if err != nil { - return nil, err - } - cgroup := props["ControlGroup"].(string) - - return cgroups.ReadProcsFile(filepath.Join(mountpoint, cgroup)) + return cgroups.ReadProcsFile(path) } func getUnitName(c *cgroups.Cgroup) string { @@ -437,3 +288,71 @@ func GetStats(c *cgroups.Cgroup) (*cgroups.Stats, error) { return stats, nil } + +// Atm we can't use the systemd device support because of two missing things: +// * Support for wildcards to allow mknod on any device +// * Support for wildcards to allow /dev/pts support +// +// The second is available in more recent systemd as "char-pts", but not in e.g. v208 which is +// in wide use. When both these are availalable we will be able to switch, but need to keep the old +// implementation for backwards compat. +// +// Note: we can't use systemd to set up the initial limits, and then change the cgroup +// because systemd will re-write the device settings if it needs to re-apply the cgroup context. +// This happens at least for v208 when any sibling unit is started. +func joinDevices(c *cgroups.Cgroup, pid int) error { + path, err := getSubsystemPath(c, "devices") + if err != nil { + return err + } + + if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) { + return err + } + + if err := ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700); err != nil { + return err + } + + if err := writeFile(path, "devices.deny", "a"); err != nil { + return err + } + + for _, dev := range c.AllowedDevices { + if err := writeFile(path, "devices.allow", dev.GetCgroupAllowString()); err != nil { + return err + } + } + + return nil +} + +func joinMemory(c *cgroups.Cgroup, pid int) error { + memorySwap := c.MemorySwap + + if memorySwap == 0 { + // By default, MemorySwap is set to twice the size of RAM. + memorySwap = c.Memory * 2 + } + + path, err := getSubsystemPath(c, "memory") + if err != nil { + return err + } + + return ioutil.WriteFile(filepath.Join(path, "memory.memsw.limit_in_bytes"), []byte(strconv.FormatInt(memorySwap, 10)), 0700) +} + +// systemd does not atm set up the cpuset controller, so we must manually +// join it. Additionally that is a very finicky controller where each +// level must have a full setup as the default for a new directory is "no cpus" +func joinCpuset(c *cgroups.Cgroup, pid int) error { + path, err := getSubsystemPath(c, "cpuset") + if err != nil { + return err + } + + s := &fs.CpusetGroup{} + + return s.SetDir(path, c.CpusetCpus, pid) +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/utils.go b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/utils.go index ce5c4f33..6688ff71 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/utils.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/cgroups/utils.go @@ -4,6 +4,7 @@ import ( "bufio" "fmt" "io" + "io/ioutil" "os" "path/filepath" "strconv" @@ -166,3 +167,23 @@ func parseCgroupFile(subsystem string, r io.Reader) (string, error) { } return "", ErrNotFound } + +func pathExists(path string) bool { + if _, err := os.Stat(path); err != nil { + return false + } + return true +} + +func EnterPid(cgroupPaths map[string]string, pid int) error { + for _, path := range cgroupPaths { + if pathExists(path) { + if err := ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), + []byte(strconv.Itoa(pid)), 0700); err != nil { + return err + } + } + } + + return nil +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/console/console.go b/Godeps/_workspace/src/github.com/docker/libcontainer/console/console.go index c0d1fb04..346f537d 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/console/console.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/console/console.go @@ -114,7 +114,7 @@ func OpenPtmx() (*os.File, error) { func OpenTerminal(name string, flag int) (*os.File, error) { r, e := syscall.Open(name, flag, 0) if e != nil { - return nil, &os.PathError{"open", name, e} + return nil, &os.PathError{Op: "open", Path: name, Err: e} } return os.NewFile(uintptr(r), name), nil } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/label/label.go b/Godeps/_workspace/src/github.com/docker/libcontainer/label/label.go index 5c8228cd..73869b36 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/label/label.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/label/label.go @@ -2,6 +2,13 @@ package label +// InitLabels returns the process label and file labels to be used within +// the container. A list of options can be passed into this function to alter +// the labels. +func InitLabels(options []string) (string, string, error) { + return "", "", nil +} + func GenLabels(options string) (string, string, error) { return "", "", nil } @@ -22,7 +29,7 @@ func Relabel(path string, fileLabel string, relabel string) error { return nil } -func GetPidCon(pid int) (string, error) { +func GetPidLabel(pid int) (string, error) { return "", nil } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/label/label_selinux.go b/Godeps/_workspace/src/github.com/docker/libcontainer/label/label_selinux.go index aa502a3d..5b1380a2 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/label/label_selinux.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/label/label_selinux.go @@ -9,30 +9,49 @@ import ( "github.com/docker/libcontainer/selinux" ) -func GenLabels(options string) (string, string, error) { +// InitLabels returns the process label and file labels to be used within +// the container. A list of options can be passed into this function to alter +// the labels. The labels returned will include a random MCS String, that is +// guaranteed to be unique. +func InitLabels(options []string) (string, string, error) { if !selinux.SelinuxEnabled() { return "", "", nil } var err error processLabel, mountLabel := selinux.GetLxcContexts() if processLabel != "" { - var ( - s = strings.Fields(options) - l = len(s) - ) - if l > 0 { - pcon := selinux.NewContext(processLabel) - for i := 0; i < l; i++ { - o := strings.Split(s[i], "=") - pcon[o[0]] = o[1] + pcon := selinux.NewContext(processLabel) + mcon := selinux.NewContext(mountLabel) + for _, opt := range options { + if opt == "disable" { + return "", "", nil + } + if i := strings.Index(opt, ":"); i == -1 { + return "", "", fmt.Errorf("Bad SELinux Option") + } + con := strings.SplitN(opt, ":", 2) + pcon[con[0]] = con[1] + if con[0] == "level" || con[0] == "user" { + mcon[con[0]] = con[1] } - processLabel = pcon.Get() - mountLabel, err = selinux.CopyLevel(processLabel, mountLabel) } + processLabel = pcon.Get() + mountLabel = mcon.Get() } return processLabel, mountLabel, err } +// DEPRECATED: The GenLabels function is only to be used during the transition to the official API. +func GenLabels(options string) (string, string, error) { + return InitLabels(strings.Fields(options)) +} + +// FormatMountLabel returns a string to be used by the mount command. +// The format of this string will be used to alter the labeling of the mountpoint. +// The string returned is suitable to be used as the options field of the mount command. +// If you need to have additional mount point options, you can pass them in as +// the first parameter. Second parameter is the label that you wish to apply +// to all content in the mount point. func FormatMountLabel(src, mountLabel string) string { if mountLabel != "" { switch src { @@ -45,6 +64,8 @@ func FormatMountLabel(src, mountLabel string) string { return src } +// SetProcessLabel takes a process label and tells the kernel to assign the +// label to the next program executed by the current process. func SetProcessLabel(processLabel string) error { if selinux.SelinuxEnabled() { return selinux.Setexeccon(processLabel) @@ -52,6 +73,9 @@ func SetProcessLabel(processLabel string) error { return nil } +// GetProcessLabel returns the process label that the kernel will assign +// to the next program executed by the current process. If "" is returned +// this indicates that the default labeling will happen for the process. func GetProcessLabel() (string, error) { if selinux.SelinuxEnabled() { return selinux.Getexeccon() @@ -59,6 +83,7 @@ func GetProcessLabel() (string, error) { return "", nil } +// SetFileLabel modifies the "path" label to the specified file label func SetFileLabel(path string, fileLabel string) error { if selinux.SelinuxEnabled() && fileLabel != "" { return selinux.Setfilecon(path, fileLabel) @@ -83,17 +108,22 @@ func Relabel(path string, fileLabel string, relabel string) error { return selinux.Chcon(path, fileLabel, true) } -func GetPidCon(pid int) (string, error) { +// GetPidLabel will return the label of the process running with the specified pid +func GetPidLabel(pid int) (string, error) { if !selinux.SelinuxEnabled() { return "", nil } return selinux.Getpidcon(pid) } +// Init initialises the labeling system func Init() { selinux.SelinuxEnabled() } +// ReserveLabel will record the fact that the MCS label has already been used. +// This will prevent InitLabels from using the MCS label in a newly created +// container func ReserveLabel(label string) error { selinux.ReserveLabel(label) return nil diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/label/label_selinux_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/label/label_selinux_test.go new file mode 100644 index 00000000..c83654f6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/label/label_selinux_test.go @@ -0,0 +1,48 @@ +// +build selinux,linux + +package label + +import ( + "testing" + + "github.com/docker/libcontainer/selinux" +) + +func TestInit(t *testing.T) { + if selinux.SelinuxEnabled() { + var testNull []string + plabel, mlabel, err := InitLabels(testNull) + if err != nil { + t.Log("InitLabels Failed") + t.Fatal(err) + } + testDisabled := []string{"disable"} + plabel, mlabel, err = InitLabels(testDisabled) + if err != nil { + t.Log("InitLabels Disabled Failed") + t.Fatal(err) + } + if plabel != "" { + t.Log("InitLabels Disabled Failed") + t.Fatal() + } + testUser := []string{"user:user_u", "role:user_r", "type:user_t", "level:s0:c1,c15"} + plabel, mlabel, err = InitLabels(testUser) + if err != nil { + t.Log("InitLabels User Failed") + t.Fatal(err) + } + if plabel != "user_u:user_r:user_t:s0:c1,c15" || mlabel != "user_u:object_r:svirt_sandbox_file_t:s0:c1,c15" { + t.Log("InitLabels User Failed") + t.Log(plabel, mlabel) + t.Fatal(err) + } + + testBadData := []string{"user", "role:user_r", "type:user_t", "level:s0:c1,c15"} + plabel, mlabel, err = InitLabels(testBadData) + if err == nil { + t.Log("InitLabels Bad Failed") + t.Fatal(err) + } + } +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/mount/init.go b/Godeps/_workspace/src/github.com/docker/libcontainer/mount/init.go index 7edea499..05ab334c 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/mount/init.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/mount/init.go @@ -236,7 +236,7 @@ func reOpenDevNull(rootfs string) error { if stat.Rdev == devNullStat.Rdev { // Close and re-open the fd. if err = syscall.Dup2(int(file.Fd()), fd); err != nil { - return fmt.Errorf("Failed to dup fd %d to fd %d - %s", file.Fd(), fd) + return fmt.Errorf("Failed to dup fd %d to fd %d - %s", file.Fd(), fd, err) } } } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/exec.go b/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/exec.go index 6f3838fd..382abfbc 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/exec.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/exec.go @@ -56,14 +56,19 @@ func Exec(container *libcontainer.Config, stdin io.Reader, stdout, stderr io.Wri // Do this before syncing with child so that no children // can escape the cgroup - cleaner, err := SetupCgroups(container, command.Process.Pid) + cgroupRef, err := SetupCgroups(container, command.Process.Pid) if err != nil { command.Process.Kill() command.Wait() return -1, err } - if cleaner != nil { - defer cleaner.Cleanup() + defer cgroupRef.Cleanup() + + cgroupPaths, err := cgroupRef.Paths() + if err != nil { + command.Process.Kill() + command.Wait() + return -1, err } var networkState network.NetworkState @@ -77,6 +82,7 @@ func Exec(container *libcontainer.Config, stdin io.Reader, stdout, stderr io.Wri InitPid: command.Process.Pid, InitStartTime: started, NetworkState: networkState, + CgroupPaths: cgroupPaths, } if err := libcontainer.SaveState(dataPath, state); err != nil { @@ -133,7 +139,7 @@ func DefaultCreateCommand(container *libcontainer.Config, console, rootfs, dataP } */ - command := exec.Command(init, append([]string{"init"}, args...)...) + command := exec.Command(init, append([]string{"init", "--"}, args...)...) // make sure the process is executed inside the context of the rootfs command.Dir = rootfs command.Env = append(os.Environ(), env...) diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/execin.go b/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/execin.go index 3a385cb7..8b81edec 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/execin.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/execin.go @@ -3,26 +3,49 @@ package namespaces import ( - "encoding/json" - "github.com/docker/libcontainer" - "github.com/docker/libcontainer/label" - "github.com/docker/libcontainer/system" + "fmt" "io" "os" "os/exec" + "path/filepath" "strconv" "syscall" + + "github.com/docker/libcontainer" + "github.com/docker/libcontainer/cgroups" + "github.com/docker/libcontainer/label" + "github.com/docker/libcontainer/syncpipe" + "github.com/docker/libcontainer/system" ) -// Runs the command under 'args' inside an existing container referred to by 'container'. -// Returns the exitcode of the command upon success and appropriate error on failure. -func RunIn(container *libcontainer.Config, state *libcontainer.State, args []string, nsinitPath string, stdin io.Reader, stdout, stderr io.Writer, console string, startCallback func(*exec.Cmd)) (int, error) { - initArgs, err := getNsEnterCommand(strconv.Itoa(state.InitPid), container, console, args) +// ExecIn reexec's the initPath with the argv 0 rewrite to "nsenter" so that it is able to run the +// setns code in a single threaded environment joining the existing containers' namespaces. +func ExecIn(container *libcontainer.Config, state *libcontainer.State, userArgs []string, initPath, action string, + stdin io.Reader, stdout, stderr io.Writer, console string, startCallback func(*exec.Cmd)) (int, error) { + + args := []string{fmt.Sprintf("nsenter-%s", action), "--nspid", strconv.Itoa(state.InitPid)} + + if console != "" { + args = append(args, "--console", console) + } + + cmd := &exec.Cmd{ + Path: initPath, + Args: append(args, append([]string{"--"}, userArgs...)...), + } + + if filepath.Base(initPath) == initPath { + if lp, err := exec.LookPath(initPath); err == nil { + cmd.Path = lp + } + } + + pipe, err := syncpipe.NewSyncPipe() if err != nil { return -1, err } + defer pipe.Close() - cmd := exec.Command(nsinitPath, initArgs...) // Note: these are only used in non-tty mode // if there is a tty for the container it will be opened within the namespace and the // fds will be duped to stdin, stdiout, and stderr @@ -30,9 +53,24 @@ func RunIn(container *libcontainer.Config, state *libcontainer.State, args []str cmd.Stdout = stdout cmd.Stderr = stderr + cmd.ExtraFiles = []*os.File{pipe.Child()} + if err := cmd.Start(); err != nil { return -1, err } + pipe.CloseChild() + + // Enter cgroups. + if err := EnterCgroups(state, cmd.Process.Pid); err != nil { + return -1, err + } + + if err := pipe.SendToChild(container); err != nil { + cmd.Process.Kill() + cmd.Wait() + return -1, err + } + if startCallback != nil { startCallback(cmd) } @@ -46,61 +84,14 @@ func RunIn(container *libcontainer.Config, state *libcontainer.State, args []str return cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus(), nil } -// ExecIn uses an existing pid and joins the pid's namespaces with the new command. -func ExecIn(container *libcontainer.Config, state *libcontainer.State, args []string) error { - // Enter the namespace and then finish setup - args, err := getNsEnterCommand(strconv.Itoa(state.InitPid), container, "", args) - if err != nil { - return err - } - - finalArgs := append([]string{os.Args[0]}, args...) - - if err := system.Execv(finalArgs[0], finalArgs[0:], os.Environ()); err != nil { - return err - } - - panic("unreachable") -} - -func getContainerJson(container *libcontainer.Config) (string, error) { - // TODO(vmarmol): If this gets too long, send it over a pipe to the child. - // Marshall the container into JSON since it won't be available in the namespace. - containerJson, err := json.Marshal(container) - if err != nil { - return "", err - } - return string(containerJson), nil -} - -func getNsEnterCommand(initPid string, container *libcontainer.Config, console string, args []string) ([]string, error) { - containerJson, err := getContainerJson(container) - if err != nil { - return nil, err - } - - out := []string{ - "nsenter", - "--nspid", initPid, - "--containerjson", containerJson, - } - - if console != "" { - out = append(out, "--console", console) - } - out = append(out, "--") - out = append(out, args...) - - return out, nil -} - -// Run a command in a container after entering the namespace. -func NsEnter(container *libcontainer.Config, args []string) error { - // clear the current processes env and replace it with the environment - // defined on the container +// Finalize expects that the setns calls have been setup and that is has joined an +// existing namespace +func FinalizeSetns(container *libcontainer.Config, args []string) error { + // clear the current processes env and replace it with the environment defined on the container if err := LoadContainerEnvironment(container); err != nil { return err } + if err := FinalizeNamespace(container); err != nil { return err } @@ -114,5 +105,10 @@ func NsEnter(container *libcontainer.Config, args []string) error { if err := system.Execv(args[0], args[0:], container.Env); err != nil { return err } + panic("unreachable") } + +func EnterCgroups(state *libcontainer.State, pid int) error { + return cgroups.EnterPid(state.CgroupPaths, pid) +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/init.go b/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/init.go index e43db396..4c2b3327 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/init.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/init.go @@ -5,11 +5,9 @@ package namespaces import ( "fmt" "os" - "runtime" "strings" "syscall" - "github.com/docker/docker/pkg/user" "github.com/docker/libcontainer" "github.com/docker/libcontainer/apparmor" "github.com/docker/libcontainer/console" @@ -21,6 +19,7 @@ import ( "github.com/docker/libcontainer/security/restrict" "github.com/docker/libcontainer/syncpipe" "github.com/docker/libcontainer/system" + "github.com/docker/libcontainer/user" "github.com/docker/libcontainer/utils" ) @@ -28,6 +27,8 @@ import ( // Move this to libcontainer package. // Init is the init process that first runs inside a new namespace to setup mounts, users, networking, // and other options required for the new container. +// The caller of Init function has to ensure that the go runtime is locked to an OS thread +// (using runtime.LockOSThread) else system calls like setns called within Init may not work as intended. func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, syncPipe *syncpipe.SyncPipe, args []string) (err error) { defer func() { if err != nil { @@ -47,8 +48,8 @@ func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, syn } // We always read this as it is a way to sync with the parent as well - networkState, err := syncPipe.ReadFromParent() - if err != nil { + var networkState *network.NetworkState + if err := syncPipe.ReadFromParent(&networkState); err != nil { return err } @@ -87,8 +88,6 @@ func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, syn } } - runtime.LockOSThread() - if err := apparmor.ApplyProfile(container.AppArmorProfile); err != nil { return fmt.Errorf("set apparmor profile %s: %s", container.AppArmorProfile, err) } @@ -119,7 +118,7 @@ func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, syn return fmt.Errorf("restore parent death signal %s", err) } - return system.Execv(args[0], args[0:], container.Env) + return system.Execv(args[0], args[0:], os.Environ()) } // RestoreParentDeathSignal sets the parent death signal to old. @@ -152,7 +151,7 @@ func RestoreParentDeathSignal(old int) error { // SetupUser changes the groups, gid, and uid for the user inside the container func SetupUser(u string) error { - uid, gid, suppGids, err := user.GetUserGroupSupplementary(u, syscall.Getuid(), syscall.Getgid()) + uid, gid, suppGids, home, err := user.GetUserGroupSupplementaryHome(u, syscall.Getuid(), syscall.Getgid(), "/") if err != nil { return fmt.Errorf("get supplementary groups %s", err) } @@ -169,6 +168,13 @@ func SetupUser(u string) error { return fmt.Errorf("setuid %s", err) } + // if we didn't get HOME already, set it based on the user's HOME + if envHome := os.Getenv("HOME"); envHome == "" { + if err := os.Setenv("HOME", home); err != nil { + return fmt.Errorf("set HOME %s", err) + } + } + return nil } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/nsenter.go b/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/nsenter.go deleted file mode 100644 index cddfa442..00000000 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/nsenter.go +++ /dev/null @@ -1,223 +0,0 @@ -// +build linux - -package namespaces - -/* -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static const kBufSize = 256; - -void get_args(int *argc, char ***argv) { - // Read argv - int fd = open("/proc/self/cmdline", O_RDONLY); - - // Read the whole commandline. - ssize_t contents_size = 0; - ssize_t contents_offset = 0; - char *contents = NULL; - ssize_t bytes_read = 0; - do { - contents_size += kBufSize; - contents = (char *) realloc(contents, contents_size); - bytes_read = read(fd, contents + contents_offset, contents_size - contents_offset); - contents_offset += bytes_read; - } while (bytes_read > 0); - close(fd); - - // Parse the commandline into an argv. /proc/self/cmdline has \0 delimited args. - ssize_t i; - *argc = 0; - for (i = 0; i < contents_offset; i++) { - if (contents[i] == '\0') { - (*argc)++; - } - } - *argv = (char **) malloc(sizeof(char *) * ((*argc) + 1)); - int idx; - for (idx = 0; idx < (*argc); idx++) { - (*argv)[idx] = contents; - contents += strlen(contents) + 1; - } - (*argv)[*argc] = NULL; -} - -// Use raw setns syscall for versions of glibc that don't include it (namely glibc-2.12) -#if __GLIBC__ == 2 && __GLIBC_MINOR__ < 14 -#define _GNU_SOURCE -#include -#include "syscall.h" -#ifdef SYS_setns -int setns(int fd, int nstype) { - return syscall(SYS_setns, fd, nstype); -} -#endif -#endif - -void print_usage() { - fprintf(stderr, " nsenter --nspid --containerjson -- cmd1 arg1 arg2...\n"); -} - -void nsenter() { - int argc; - char **argv; - get_args(&argc, &argv); - - // Ignore if this is not for us. - if (argc < 2 || strcmp(argv[1], "nsenter") != 0) { - return; - } - - // USAGE: nsenter ... - if (argc < 6) { - fprintf(stderr, "nsenter: Incorrect usage, not enough arguments\n"); - exit(1); - } - - static const struct option longopts[] = { - { "nspid", required_argument, NULL, 'n' }, - { "containerjson", required_argument, NULL, 'c' }, - { "console", required_argument, NULL, 't' }, - { NULL, 0, NULL, 0 } - }; - - int c; - pid_t init_pid = -1; - char *init_pid_str = NULL; - char *container_json = NULL; - char *console = NULL; - while ((c = getopt_long_only(argc, argv, "n:s:c:", longopts, NULL)) != -1) { - switch (c) { - case 'n': - init_pid_str = optarg; - break; - case 'c': - container_json = optarg; - break; - case 't': - console = optarg; - break; - } - } - - if (container_json == NULL || init_pid_str == NULL) { - print_usage(); - exit(1); - } - - init_pid = strtol(init_pid_str, NULL, 10); - if (errno != 0 || init_pid <= 0) { - fprintf(stderr, "nsenter: Failed to parse PID from \"%s\" with error: \"%s\"\n", init_pid_str, strerror(errno)); - print_usage(); - exit(1); - } - - argc -= 3; - argv += 3; - - if (setsid() == -1) { - fprintf(stderr, "setsid failed. Error: %s\n", strerror(errno)); - exit(1); - } - - // before we setns we need to dup the console - int consolefd = -1; - if (console != NULL) { - consolefd = open(console, O_RDWR); - if (consolefd < 0) { - fprintf(stderr, "nsenter: failed to open console %s %s\n", console, strerror(errno)); - exit(1); - } - } - - // Setns on all supported namespaces. - char ns_dir[PATH_MAX]; - memset(ns_dir, 0, PATH_MAX); - snprintf(ns_dir, PATH_MAX - 1, "/proc/%d/ns/", init_pid); - struct dirent *dent; - DIR *dir = opendir(ns_dir); - if (dir == NULL) { - fprintf(stderr, "nsenter: Failed to open directory \"%s\" with error: \"%s\"\n", ns_dir, strerror(errno)); - exit(1); - } - - while((dent = readdir(dir)) != NULL) { - if(strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0 || strcmp(dent->d_name, "user") == 0) { - continue; - } - - // Get and open the namespace for the init we are joining.. - char buf[PATH_MAX]; - memset(buf, 0, PATH_MAX); - snprintf(buf, PATH_MAX - 1, "%s%s", ns_dir, dent->d_name); - int fd = open(buf, O_RDONLY); - if (fd == -1) { - fprintf(stderr, "nsenter: Failed to open ns file \"%s\" for ns \"%s\" with error: \"%s\"\n", buf, dent->d_name, strerror(errno)); - exit(1); - } - - // Set the namespace. - if (setns(fd, 0) == -1) { - fprintf(stderr, "nsenter: Failed to setns for \"%s\" with error: \"%s\"\n", dent->d_name, strerror(errno)); - exit(1); - } - close(fd); - } - closedir(dir); - - // We must fork to actually enter the PID namespace. - int child = fork(); - if (child == 0) { - if (consolefd != -1) { - if (dup2(consolefd, STDIN_FILENO) != 0) { - fprintf(stderr, "nsenter: failed to dup 0 %s\n", strerror(errno)); - exit(1); - } - if (dup2(consolefd, STDOUT_FILENO) != STDOUT_FILENO) { - fprintf(stderr, "nsenter: failed to dup 1 %s\n", strerror(errno)); - exit(1); - } - if (dup2(consolefd, STDERR_FILENO) != STDERR_FILENO) { - fprintf(stderr, "nsenter: failed to dup 2 %s\n", strerror(errno)); - exit(1); - } -} - - // Finish executing, let the Go runtime take over. - return; - } else { - // Parent, wait for the child. - int status = 0; - if (waitpid(child, &status, 0) == -1) { - fprintf(stderr, "nsenter: Failed to waitpid with error: \"%s\"\n", strerror(errno)); - exit(1); - } - - // Forward the child's exit code or re-send its death signal. - if (WIFEXITED(status)) { - exit(WEXITSTATUS(status)); - } else if (WIFSIGNALED(status)) { - kill(getpid(), WTERMSIG(status)); - } - exit(1); - } - - return; -} - -__attribute__((constructor)) init() { - nsenter(); -} -*/ -import "C" diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/nsenter/README.md b/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/nsenter/README.md new file mode 100644 index 00000000..ac94cba0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/nsenter/README.md @@ -0,0 +1,6 @@ +## nsenter + +The `nsenter` package registers a special init constructor that is called before the Go runtime has +a chance to boot. This provides us the ability to `setns` on existing namespaces and avoid the issues +that the Go runtime has with multiple threads. This constructor is only called if this package is +registered, imported, in your go application and the argv 0 is `nsenter`. diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/nsenter/nsenter.c b/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/nsenter/nsenter.c new file mode 100644 index 00000000..2869dd14 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/nsenter/nsenter.c @@ -0,0 +1,218 @@ +// +build cgo +// +// formated with indent -linux nsenter.c + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const kBufSize = 256; +static const char *kNsEnter = "nsenter"; + +void get_args(int *argc, char ***argv) +{ + // Read argv + int fd = open("/proc/self/cmdline", O_RDONLY); + + // Read the whole commandline. + ssize_t contents_size = 0; + ssize_t contents_offset = 0; + char *contents = NULL; + ssize_t bytes_read = 0; + do { + contents_size += kBufSize; + contents = (char *)realloc(contents, contents_size); + bytes_read = + read(fd, contents + contents_offset, + contents_size - contents_offset); + contents_offset += bytes_read; + } + while (bytes_read > 0); + close(fd); + + // Parse the commandline into an argv. /proc/self/cmdline has \0 delimited args. + ssize_t i; + *argc = 0; + for (i = 0; i < contents_offset; i++) { + if (contents[i] == '\0') { + (*argc)++; + } + } + *argv = (char **)malloc(sizeof(char *) * ((*argc) + 1)); + int idx; + for (idx = 0; idx < (*argc); idx++) { + (*argv)[idx] = contents; + contents += strlen(contents) + 1; + } + (*argv)[*argc] = NULL; +} + +// Use raw setns syscall for versions of glibc that don't include it (namely glibc-2.12) +#if __GLIBC__ == 2 && __GLIBC_MINOR__ < 14 +#define _GNU_SOURCE +#include +#include "syscall.h" +#ifdef SYS_setns +int setns(int fd, int nstype) +{ + return syscall(SYS_setns, fd, nstype); +} +#endif +#endif + +void print_usage() +{ + fprintf(stderr, + "nsenter --nspid --console -- cmd1 arg1 arg2...\n"); +} + +void nsenter() +{ + int argc, c; + char **argv; + get_args(&argc, &argv); + + // check argv 0 to ensure that we are supposed to setns + // we use strncmp to test for a value of "nsenter" but also allows alternate implmentations + // after the setns code path to continue to use the argv 0 to determine actions to be run + // resulting in the ability to specify "nsenter-mknod", "nsenter-exec", etc... + if (strncmp(argv[0], kNsEnter, strlen(kNsEnter)) != 0) { + return; + } + + static const struct option longopts[] = { + {"nspid", required_argument, NULL, 'n'}, + {"console", required_argument, NULL, 't'}, + {NULL, 0, NULL, 0} + }; + + pid_t init_pid = -1; + char *init_pid_str = NULL; + char *console = NULL; + while ((c = getopt_long_only(argc, argv, "n:c:", longopts, NULL)) != -1) { + switch (c) { + case 'n': + init_pid_str = optarg; + break; + case 't': + console = optarg; + break; + } + } + + if (init_pid_str == NULL) { + print_usage(); + exit(1); + } + + init_pid = strtol(init_pid_str, NULL, 10); + if ((init_pid == 0 && errno == EINVAL) || errno == ERANGE) { + fprintf(stderr, + "nsenter: Failed to parse PID from \"%s\" with output \"%d\" and error: \"%s\"\n", + init_pid_str, init_pid, strerror(errno)); + print_usage(); + exit(1); + } + + argc -= 3; + argv += 3; + + if (setsid() == -1) { + fprintf(stderr, "setsid failed. Error: %s\n", strerror(errno)); + exit(1); + } + // before we setns we need to dup the console + int consolefd = -1; + if (console != NULL) { + consolefd = open(console, O_RDWR); + if (consolefd < 0) { + fprintf(stderr, + "nsenter: failed to open console %s %s\n", + console, strerror(errno)); + exit(1); + } + } + // Setns on all supported namespaces. + char ns_dir[PATH_MAX]; + memset(ns_dir, 0, PATH_MAX); + snprintf(ns_dir, PATH_MAX - 1, "/proc/%d/ns/", init_pid); + + char *namespaces[] = { "ipc", "uts", "net", "pid", "mnt" }; + const int num = sizeof(namespaces) / sizeof(char *); + int i; + for (i = 0; i < num; i++) { + char buf[PATH_MAX]; + memset(buf, 0, PATH_MAX); + snprintf(buf, PATH_MAX - 1, "%s%s", ns_dir, namespaces[i]); + int fd = open(buf, O_RDONLY); + if (fd == -1) { + // Ignore nonexistent namespaces. + if (errno == ENOENT) + continue; + + fprintf(stderr, + "nsenter: Failed to open ns file \"%s\" for ns \"%s\" with error: \"%s\"\n", + buf, namespaces[i], strerror(errno)); + exit(1); + } + // Set the namespace. + if (setns(fd, 0) == -1) { + fprintf(stderr, + "nsenter: Failed to setns for \"%s\" with error: \"%s\"\n", + namespaces[i], strerror(errno)); + exit(1); + } + close(fd); + } + + // We must fork to actually enter the PID namespace. + int child = fork(); + if (child == 0) { + if (consolefd != -1) { + if (dup2(consolefd, STDIN_FILENO) != 0) { + fprintf(stderr, "nsenter: failed to dup 0 %s\n", + strerror(errno)); + exit(1); + } + if (dup2(consolefd, STDOUT_FILENO) != STDOUT_FILENO) { + fprintf(stderr, "nsenter: failed to dup 1 %s\n", + strerror(errno)); + exit(1); + } + if (dup2(consolefd, STDERR_FILENO) != STDERR_FILENO) { + fprintf(stderr, "nsenter: failed to dup 2 %s\n", + strerror(errno)); + exit(1); + } + } + // Finish executing, let the Go runtime take over. + return; + } else { + // Parent, wait for the child. + int status = 0; + if (waitpid(child, &status, 0) == -1) { + fprintf(stderr, + "nsenter: Failed to waitpid with error: \"%s\"\n", + strerror(errno)); + exit(1); + } + // Forward the child's exit code or re-send its death signal. + if (WIFEXITED(status)) { + exit(WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + kill(getpid(), WTERMSIG(status)); + } + + exit(1); + } + + return; +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/nsenter/nsenter.go b/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/nsenter/nsenter.go new file mode 100644 index 00000000..7d21e8e5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/nsenter/nsenter.go @@ -0,0 +1,10 @@ +// +build linux + +package nsenter + +/* +__attribute__((constructor)) init() { + nsenter(); +} +*/ +import "C" diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/nsenter/nsenter_unsupported.go b/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/nsenter/nsenter_unsupported.go new file mode 100644 index 00000000..2459c636 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/namespaces/nsenter/nsenter_unsupported.go @@ -0,0 +1,3 @@ +// +build !linux !cgo + +package nsenter diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/netlink/netlink.go b/Godeps/_workspace/src/github.com/docker/libcontainer/netlink/netlink.go index 5cc75625..dd9b1c16 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/netlink/netlink.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/netlink/netlink.go @@ -21,3 +21,10 @@ type Route struct { Iface *net.Interface Default bool } + +// An IfAddr defines IP network settings for a given network interface +type IfAddr struct { + Iface *net.Interface + IP net.IP + IPNet *net.IPNet +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/netlink/netlink.test b/Godeps/_workspace/src/github.com/docker/libcontainer/netlink/netlink.test new file mode 100644 index 00000000..3ba78ff0 Binary files /dev/null and b/Godeps/_workspace/src/github.com/docker/libcontainer/netlink/netlink.test differ diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/netlink/netlink_linux.go b/Godeps/_workspace/src/github.com/docker/libcontainer/netlink/netlink_linux.go index 6b43fdf0..215fb178 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/netlink/netlink_linux.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/netlink/netlink_linux.go @@ -189,13 +189,15 @@ func newRtAttrChild(parent *RtAttr, attrType int, data []byte) *RtAttr { } func (a *RtAttr) Len() int { + if len(a.children) == 0 { + return (syscall.SizeofRtAttr + len(a.Data)) + } + l := 0 for _, child := range a.children { - l += child.Len() + syscall.SizeofRtAttr - } - if l == 0 { - l++ + l += child.Len() } + l += syscall.SizeofRtAttr return rtaAlignOf(l + len(a.Data)) } @@ -203,7 +205,7 @@ func (a *RtAttr) ToWireFormat() []byte { native := nativeEndian() length := a.Len() - buf := make([]byte, rtaAlignOf(length+syscall.SizeofRtAttr)) + buf := make([]byte, rtaAlignOf(length)) if a.Data != nil { copy(buf[4:], a.Data) @@ -216,11 +218,10 @@ func (a *RtAttr) ToWireFormat() []byte { } } - if l := uint16(rtaAlignOf(length)); l != 0 { - native.PutUint16(buf[0:2], l+1) + if l := uint16(length); l != 0 { + native.PutUint16(buf[0:2], l) } native.PutUint16(buf[2:4], a.Type) - return buf } @@ -650,30 +651,28 @@ func NetworkSetNsFd(iface *net.Interface, fd int) error { return s.HandleAck(wb.Seq) } -// Add an Ip address to an interface. This is identical to: -// ip addr add $ip/$ipNet dev $iface -func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error { +func networkLinkIpAction(action, flags int, ifa IfAddr) error { s, err := getNetlinkSocket() if err != nil { return err } defer s.Close() - family := getIpFamily(ip) + family := getIpFamily(ifa.IP) - wb := newNetlinkRequest(syscall.RTM_NEWADDR, syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK) + wb := newNetlinkRequest(action, flags) msg := newIfAddrmsg(family) - msg.Index = uint32(iface.Index) - prefixLen, _ := ipNet.Mask.Size() + msg.Index = uint32(ifa.Iface.Index) + prefixLen, _ := ifa.IPNet.Mask.Size() msg.Prefixlen = uint8(prefixLen) wb.AddData(msg) var ipData []byte if family == syscall.AF_INET { - ipData = ip.To4() + ipData = ifa.IP.To4() } else { - ipData = ip.To16() + ipData = ifa.IP.To16() } localData := newRtAttr(syscall.IFA_LOCAL, ipData) @@ -689,6 +688,26 @@ func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error { return s.HandleAck(wb.Seq) } +// Delete an IP address from an interface. This is identical to: +// ip addr del $ip/$ipNet dev $iface +func NetworkLinkDelIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error { + return networkLinkIpAction( + syscall.RTM_DELADDR, + syscall.NLM_F_ACK, + IfAddr{iface, ip, ipNet}, + ) +} + +// Add an Ip address to an interface. This is identical to: +// ip addr add $ip/$ipNet dev $iface +func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error { + return networkLinkIpAction( + syscall.RTM_NEWADDR, + syscall.NLM_F_CREATE|syscall.NLM_F_EXCL|syscall.NLM_F_ACK, + IfAddr{iface, ip, ipNet}, + ) +} + func zeroTerminated(s string) []byte { return []byte(s + "\000") } @@ -700,6 +719,10 @@ func nonZeroTerminated(s string) []byte { // Add a new network link of a specified type. This is identical to // running: ip add link $name type $linkType func NetworkLinkAdd(name string, linkType string) error { + if name == "" || linkType == "" { + return fmt.Errorf("Neither link name nor link type can be empty!") + } + s, err := getNetlinkSocket() if err != nil { return err @@ -711,15 +734,43 @@ func NetworkLinkAdd(name string, linkType string) error { msg := newIfInfomsg(syscall.AF_UNSPEC) wb.AddData(msg) - if name != "" { - nameData := newRtAttr(syscall.IFLA_IFNAME, zeroTerminated(name)) - wb.AddData(nameData) + linkInfo := newRtAttr(syscall.IFLA_LINKINFO, nil) + newRtAttrChild(linkInfo, IFLA_INFO_KIND, nonZeroTerminated(linkType)) + wb.AddData(linkInfo) + + nameData := newRtAttr(syscall.IFLA_IFNAME, zeroTerminated(name)) + wb.AddData(nameData) + + if err := s.Send(wb); err != nil { + return err } - kindData := newRtAttr(IFLA_INFO_KIND, nonZeroTerminated(linkType)) + return s.HandleAck(wb.Seq) +} - infoData := newRtAttr(syscall.IFLA_LINKINFO, kindData.ToWireFormat()) - wb.AddData(infoData) +// Delete a network link. This is identical to +// running: ip link del $name +func NetworkLinkDel(name string) error { + if name == "" { + return fmt.Errorf("Network link name can not be empty!") + } + + s, err := getNetlinkSocket() + if err != nil { + return err + } + defer s.Close() + + iface, err := net.InterfaceByName(name) + if err != nil { + return err + } + + wb := newNetlinkRequest(syscall.RTM_DELLINK, syscall.NLM_F_ACK) + + msg := newIfInfomsg(syscall.AF_UNSPEC) + msg.Index = int32(iface.Index) + wb.AddData(msg) if err := s.Send(wb); err != nil { return err diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/netlink/netlink_linux_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/netlink/netlink_linux_test.go index ee61d5e2..086aee7f 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/netlink/netlink_linux_test.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/netlink/netlink_linux_test.go @@ -2,9 +2,55 @@ package netlink import ( "net" + "strings" "testing" ) +func ipAssigned(iface *net.Interface, ip net.IP) bool { + addrs, _ := iface.Addrs() + + for _, addr := range addrs { + args := strings.SplitN(addr.String(), "/", 2) + if args[0] == ip.String() { + return true + } + } + + return false +} + +func TestAddDelNetworkIp(t *testing.T) { + if testing.Short() { + return + } + + ifaceName := "lo" + ip := net.ParseIP("127.0.1.1") + mask := net.IPv4Mask(255, 255, 255, 255) + ipNet := &net.IPNet{IP: ip, Mask: mask} + + iface, err := net.InterfaceByName(ifaceName) + if err != nil { + t.Skip("No 'lo' interface; skipping tests") + } + + if err := NetworkLinkAddIp(iface, ip, ipNet); err != nil { + t.Fatal(err) + } + + if !ipAssigned(iface, ip) { + t.Fatalf("Could not locate address '%s' in lo address list.", ip.String()) + } + + if err := NetworkLinkDelIp(iface, ip, ipNet); err != nil { + t.Fatal(err) + } + + if ipAssigned(iface, ip) { + t.Fatalf("Located address '%s' in lo address list after removal.", ip.String()) + } +} + func TestCreateBridgeWithMac(t *testing.T) { if testing.Short() { return @@ -27,10 +73,35 @@ func TestCreateBridgeWithMac(t *testing.T) { } if _, err := net.InterfaceByName(name); err == nil { - t.Fatal("expected error getting interface because bridge was deleted") + t.Fatalf("expected error getting interface because %s bridge was deleted", name) } } +func TestCreateBridgeLink(t *testing.T) { + if testing.Short() { + return + } + + name := "mybrlink" + + if err := NetworkLinkAdd(name, "bridge"); err != nil { + t.Fatal(err) + } + + if _, err := net.InterfaceByName(name); err != nil { + t.Fatal(err) + } + + if err := NetworkLinkDel(name); err != nil { + t.Fatal(err) + } + + if _, err := net.InterfaceByName(name); err == nil { + t.Fatalf("expected error getting interface because %s bridge was deleted", name) + } + +} + func TestCreateVethPair(t *testing.T) { if testing.Short() { return diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/netlink/netlink_unsupported.go b/Godeps/_workspace/src/github.com/docker/libcontainer/netlink/netlink_unsupported.go index f428933c..f6e84adf 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/netlink/netlink_unsupported.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/netlink/netlink_unsupported.go @@ -19,6 +19,10 @@ func NetworkLinkAdd(name string, linkType string) error { return ErrNotImplemented } +func NetworkLinkDel(name string) error { + return ErrNotImplemented +} + func NetworkLinkUp(iface *net.Interface) error { return ErrNotImplemented } @@ -27,6 +31,10 @@ func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error { return ErrNotImplemented } +func NetworkLinkDelIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error { + return ErrNotImplemented +} + func AddRoute(destination, source, gateway, device string) error { return ErrNotImplemented } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/network/network.go b/Godeps/_workspace/src/github.com/docker/libcontainer/network/network.go index 48eeec60..c7560c04 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/network/network.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/network/network.go @@ -44,6 +44,14 @@ func SetInterfaceInNamespacePid(name string, nsPid int) error { return netlink.NetworkSetNsPid(iface, nsPid) } +func SetInterfaceInNamespaceFd(name string, fd uintptr) error { + iface, err := net.InterfaceByName(name) + if err != nil { + return err + } + return netlink.NetworkSetNsFd(iface, int(fd)) +} + func SetInterfaceMaster(name, master string) error { iface, err := net.InterfaceByName(name) if err != nil { diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/cli.go b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/cli.go deleted file mode 100644 index d4235aef..00000000 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/cli.go +++ /dev/null @@ -1,42 +0,0 @@ -package nsinit - -import ( - "log" - "os" - - "github.com/codegangsta/cli" -) - -var logPath = os.Getenv("log") - -func preload(context *cli.Context) error { - if logPath != "" { - if err := openLog(logPath); err != nil { - return err - } - } - - return nil -} - -func NsInit() { - app := cli.NewApp() - app.Name = "nsinit" - app.Version = "0.1" - app.Author = "libcontainer maintainers" - - app.Before = preload - app.Commands = []cli.Command{ - execCommand, - initCommand, - statsCommand, - configCommand, - nsenterCommand, - pauseCommand, - unpauseCommand, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/config.go b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/config.go index 5beb04ac..74c7b3c0 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/config.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/config.go @@ -1,4 +1,4 @@ -package nsinit +package main import ( "encoding/json" @@ -15,7 +15,7 @@ var configCommand = cli.Command{ } func configAction(context *cli.Context) { - container, err := loadContainer() + container, err := loadConfig() if err != nil { log.Fatal(err) } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/exec.go b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/exec.go index 5d410466..c46b1917 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/exec.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/exec.go @@ -1,4 +1,4 @@ -package nsinit +package main import ( "fmt" @@ -8,6 +8,7 @@ import ( "os/exec" "os/signal" "syscall" + "text/tabwriter" "github.com/codegangsta/cli" "github.com/docker/docker/pkg/term" @@ -20,12 +21,29 @@ var execCommand = cli.Command{ Name: "exec", Usage: "execute a new command inside a container", Action: execAction, + Flags: []cli.Flag{ + cli.BoolFlag{Name: "list", Usage: "list all registered exec functions"}, + cli.StringFlag{Name: "func", Value: "exec", Usage: "function name to exec inside a container"}, + }, } func execAction(context *cli.Context) { + if context.Bool("list") { + w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0) + fmt.Fprint(w, "NAME\tUSAGE\n") + + for k, f := range argvs { + fmt.Fprintf(w, "%s\t%s\n", k, f.Usage) + } + + w.Flush() + + return + } + var exitCode int - container, err := loadContainer() + container, err := loadConfig() if err != nil { log.Fatal(err) } @@ -36,7 +54,7 @@ func execAction(context *cli.Context) { } if state != nil { - exitCode, err = runIn(container, state, []string(context.Args())) + exitCode, err = startInExistingContainer(container, state, context.String("func"), context) } else { exitCode, err = startContainer(container, dataPath, []string(context.Args())) } @@ -48,28 +66,32 @@ func execAction(context *cli.Context) { os.Exit(exitCode) } -func runIn(container *libcontainer.Config, state *libcontainer.State, args []string) (int, error) { +// the process for execing a new process inside an existing container is that we have to exec ourself +// with the nsenter argument so that the C code can setns an the namespaces that we require. Then that +// code path will drop us into the path that we can do the final setup of the namespace and exec the users +// application. +func startInExistingContainer(config *libcontainer.Config, state *libcontainer.State, action string, context *cli.Context) (int, error) { var ( master *os.File console string err error + sigc = make(chan os.Signal, 10) + stdin = os.Stdin stdout = os.Stdout stderr = os.Stderr - sigc = make(chan os.Signal, 10) ) - signal.Notify(sigc) - if container.Tty { + if config.Tty { stdin = nil stdout = nil stderr = nil master, console, err = consolepkg.CreateMasterAndConsole() if err != nil { - log.Fatal(err) + return -1, err } go io.Copy(master, os.Stdin) @@ -77,7 +99,7 @@ func runIn(container *libcontainer.Config, state *libcontainer.State, args []str state, err := term.SetRawTerminal(os.Stdin.Fd()) if err != nil { - log.Fatal(err) + return -1, err } defer term.RestoreTerminal(os.Stdin.Fd(), state) @@ -98,7 +120,7 @@ func runIn(container *libcontainer.Config, state *libcontainer.State, args []str }() } - return namespaces.RunIn(container, state, args, os.Args[0], stdin, stdout, stderr, console, startCallback) + return namespaces.ExecIn(config, state, context.Args(), os.Args[0], action, stdin, stdout, stderr, console, startCallback) } // startContainer starts the container. Returns the exit status or -1 and an diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/init.go b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/init.go index 0dd96411..c091ee10 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/init.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/init.go @@ -1,8 +1,9 @@ -package nsinit +package main import ( "log" "os" + "runtime" "strconv" "github.com/codegangsta/cli" @@ -23,7 +24,9 @@ var ( ) func initAction(context *cli.Context) { - container, err := loadContainer() + runtime.LockOSThread() + + container, err := loadConfig() if err != nil { log.Fatal(err) } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/main.go b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/main.go new file mode 100644 index 00000000..d65c0140 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/main.go @@ -0,0 +1,67 @@ +package main + +import ( + "log" + "os" + "strings" + + "github.com/codegangsta/cli" +) + +var ( + logPath = os.Getenv("log") + argvs = make(map[string]*rFunc) +) + +func init() { + argvs["exec"] = &rFunc{ + Usage: "execute a process inside an existing container", + Action: nsenterExec, + } + + argvs["mknod"] = &rFunc{ + Usage: "mknod a device inside an existing container", + Action: nsenterMknod, + } + + argvs["ip"] = &rFunc{ + Usage: "display the container's network interfaces", + Action: nsenterIp, + } +} + +func main() { + // we need to check our argv 0 for any registred functions to run instead of the + // normal cli code path + f, exists := argvs[strings.TrimPrefix(os.Args[0], "nsenter-")] + if exists { + runFunc(f) + + return + } + + app := cli.NewApp() + + app.Name = "nsinit" + app.Version = "0.1" + app.Author = "libcontainer maintainers" + app.Flags = []cli.Flag{ + cli.StringFlag{Name: "nspid"}, + cli.StringFlag{Name: "console"}, + } + + app.Before = preload + + app.Commands = []cli.Command{ + execCommand, + initCommand, + statsCommand, + configCommand, + pauseCommand, + unpauseCommand, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/main/main b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/main/main new file mode 100644 index 00000000..fca84309 Binary files /dev/null and b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/main/main differ diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/main/nsinit b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/main/nsinit new file mode 100644 index 00000000..3c9209d7 Binary files /dev/null and b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/main/nsinit differ diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/nsenter.go b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/nsenter.go index 11d646eb..8dc149f4 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/nsenter.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/nsenter.go @@ -1,41 +1,84 @@ -package nsinit +package main import ( + "fmt" "log" + "net" + "os" + "strconv" + "strings" + "text/tabwriter" - "github.com/codegangsta/cli" + "github.com/docker/libcontainer" + "github.com/docker/libcontainer/devices" + "github.com/docker/libcontainer/mount/nodes" "github.com/docker/libcontainer/namespaces" + _ "github.com/docker/libcontainer/namespaces/nsenter" ) -var nsenterCommand = cli.Command{ - Name: "nsenter", - Usage: "init process for entering an existing namespace", - Action: nsenterAction, - Flags: []cli.Flag{ - cli.IntFlag{Name: "nspid"}, - cli.StringFlag{Name: "containerjson"}, - cli.StringFlag{Name: "console"}, - }, -} - -func nsenterAction(context *cli.Context) { - args := context.Args() - - if len(args) == 0 { - args = []string{"/bin/bash"} - } - - container, err := loadContainerFromJson(context.String("containerjson")) - if err != nil { - log.Fatalf("unable to load container: %s", err) - } - - nspid := context.Int("nspid") - if nspid <= 0 { - log.Fatalf("cannot enter into namespaces without valid pid: %q", nspid) - } - - if err := namespaces.NsEnter(container, args); err != nil { +// nsenterExec exec's a process inside an existing container +func nsenterExec(config *libcontainer.Config, args []string) { + if err := namespaces.FinalizeSetns(config, args); err != nil { log.Fatalf("failed to nsenter: %s", err) } } + +// nsenterMknod runs mknod inside an existing container +// +// mknod +func nsenterMknod(config *libcontainer.Config, args []string) { + if len(args) != 4 { + log.Fatalf("expected mknod to have 4 arguments not %d", len(args)) + } + + t := rune(args[1][0]) + + major, err := strconv.Atoi(args[2]) + if err != nil { + log.Fatal(err) + } + + minor, err := strconv.Atoi(args[3]) + if err != nil { + log.Fatal(err) + } + + n := &devices.Device{ + Path: args[0], + Type: t, + MajorNumber: int64(major), + MinorNumber: int64(minor), + } + + if err := nodes.CreateDeviceNode("/", n); err != nil { + log.Fatal(err) + } +} + +// nsenterIp displays the network interfaces inside a container's net namespace +func nsenterIp(config *libcontainer.Config, args []string) { + interfaces, err := net.Interfaces() + if err != nil { + log.Fatal(err) + } + + w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0) + fmt.Fprint(w, "NAME\tMTU\tMAC\tFLAG\tADDRS\n") + + for _, iface := range interfaces { + addrs, err := iface.Addrs() + if err != nil { + log.Fatal(err) + } + + o := []string{} + + for _, a := range addrs { + o = append(o, a.String()) + } + + fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\n", iface.Name, iface.MTU, iface.HardwareAddr, iface.Flags, strings.Join(o, ",")) + } + + w.Flush() +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/nsinit b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/nsinit new file mode 100644 index 00000000..0b51cf40 Binary files /dev/null and b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/nsinit differ diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/nsinit/nsinit.go b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/nsinit/nsinit.go deleted file mode 100644 index 816c4da5..00000000 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/nsinit/nsinit.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "github.com/docker/libcontainer/nsinit" - -func main() { - nsinit.NsInit() -} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/pause.go b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/pause.go index 492a0e85..ada24250 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/pause.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/pause.go @@ -1,4 +1,4 @@ -package nsinit +package main import ( "log" @@ -34,7 +34,7 @@ func unpauseAction(context *cli.Context) { } func toggle(state cgroups.FreezerState) error { - container, err := loadContainer() + container, err := loadConfig() if err != nil { return err } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/stats.go b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/stats.go index 3e59305b..612b4a4b 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/stats.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/stats.go @@ -1,4 +1,4 @@ -package nsinit +package main import ( "encoding/json" @@ -16,7 +16,7 @@ var statsCommand = cli.Command{ } func statsAction(context *cli.Context) { - container, err := loadContainer() + container, err := loadConfig() if err != nil { log.Fatal(err) } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/utils.go b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/utils.go index 8525ba9a..7f515594 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/utils.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinit/utils.go @@ -1,4 +1,4 @@ -package nsinit +package main import ( "encoding/json" @@ -6,10 +6,18 @@ import ( "os" "path/filepath" + "github.com/codegangsta/cli" "github.com/docker/libcontainer" + "github.com/docker/libcontainer/syncpipe" ) -func loadContainer() (*libcontainer.Config, error) { +// rFunc is a function registration for calling after an execin +type rFunc struct { + Usage string + Action func(*libcontainer.Config, []string) +} + +func loadConfig() (*libcontainer.Config, error) { f, err := os.Open(filepath.Join(dataPath, "container.json")) if err != nil { return nil, err @@ -35,12 +43,52 @@ func openLog(name string) error { return nil } -func loadContainerFromJson(rawData string) (*libcontainer.Config, error) { - var container *libcontainer.Config +func findUserArgs() []string { + i := 0 + for _, a := range os.Args { + i++ - if err := json.Unmarshal([]byte(rawData), &container); err != nil { + if a == "--" { + break + } + } + + return os.Args[i:] +} + +// loadConfigFromFd loads a container's config from the sync pipe that is provided by +// fd 3 when running a process +func loadConfigFromFd() (*libcontainer.Config, error) { + syncPipe, err := syncpipe.NewSyncPipeFromFd(0, 3) + if err != nil { return nil, err } - return container, nil + var config *libcontainer.Config + if err := syncPipe.ReadFromParent(&config); err != nil { + return nil, err + } + + return config, nil +} + +func preload(context *cli.Context) error { + if logPath != "" { + if err := openLog(logPath); err != nil { + return err + } + } + + return nil +} + +func runFunc(f *rFunc) { + userArgs := findUserArgs() + + config, err := loadConfigFromFd() + if err != nil { + log.Fatalf("unable to receive config from sync pipe: %s", err) + } + + f.Action(config, userArgs) } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/nsinitb b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinitb new file mode 100644 index 00000000..f9d2a6a3 Binary files /dev/null and b/Godeps/_workspace/src/github.com/docker/libcontainer/nsinitb differ diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/security/capabilities/capabilities.test b/Godeps/_workspace/src/github.com/docker/libcontainer/security/capabilities/capabilities.test new file mode 100644 index 00000000..debc8d5e Binary files /dev/null and b/Godeps/_workspace/src/github.com/docker/libcontainer/security/capabilities/capabilities.test differ diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/state.go b/Godeps/_workspace/src/github.com/docker/libcontainer/state.go index ee5d14d2..208b4c62 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/state.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/state.go @@ -18,6 +18,9 @@ type State struct { // Network runtime state. NetworkState network.NetworkState `json:"network_state,omitempty"` + + // Path to all the cgroups setup for a container. Key is cgroup subsystem name. + CgroupPaths map[string]string `json:"cgroup_paths,omitempty"` } // The running state of the container. diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/syncpipe/sync_pipe.go b/Godeps/_workspace/src/github.com/docker/libcontainer/syncpipe/sync_pipe.go index 10a21a9e..d2870f52 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/syncpipe/sync_pipe.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/syncpipe/sync_pipe.go @@ -6,8 +6,6 @@ import ( "io/ioutil" "os" "syscall" - - "github.com/docker/libcontainer/network" ) // SyncPipe allows communication to and from the child processes @@ -39,8 +37,8 @@ func (s *SyncPipe) Parent() *os.File { return s.parent } -func (s *SyncPipe) SendToChild(networkState *network.NetworkState) error { - data, err := json.Marshal(networkState) +func (s *SyncPipe) SendToChild(v interface{}) error { + data, err := json.Marshal(v) if err != nil { return err } @@ -63,18 +61,19 @@ func (s *SyncPipe) ReadFromChild() error { return nil } -func (s *SyncPipe) ReadFromParent() (*network.NetworkState, error) { +func (s *SyncPipe) ReadFromParent(v interface{}) error { data, err := ioutil.ReadAll(s.child) if err != nil { - return nil, fmt.Errorf("error reading from sync pipe %s", err) + return fmt.Errorf("error reading from sync pipe %s", err) } - var networkState *network.NetworkState + if len(data) > 0 { - if err := json.Unmarshal(data, &networkState); err != nil { - return nil, err + if err := json.Unmarshal(data, v); err != nil { + return err } } - return networkState, nil + + return nil } func (s *SyncPipe) ReportChildError(err error) { diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/syncpipe/sync_pipe_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/syncpipe/sync_pipe_test.go index 3f99a7d1..6833277a 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/syncpipe/sync_pipe_test.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/syncpipe/sync_pipe_test.go @@ -3,10 +3,12 @@ package syncpipe import ( "fmt" "testing" - - "github.com/docker/libcontainer/network" ) +type testStruct struct { + Name string +} + func TestSendErrorFromChild(t *testing.T) { pipe, err := NewSyncPipe() if err != nil { @@ -46,16 +48,16 @@ func TestSendPayloadToChild(t *testing.T) { expected := "libcontainer" - if err := pipe.SendToChild(&network.NetworkState{VethHost: expected}); err != nil { + if err := pipe.SendToChild(testStruct{Name: expected}); err != nil { t.Fatal(err) } - payload, err := pipe.ReadFromParent() - if err != nil { + var s *testStruct + if err := pipe.ReadFromParent(&s); err != nil { t.Fatal(err) } - if payload.VethHost != expected { - t.Fatalf("expected veth host %q but received %q", expected, payload.VethHost) + if s.Name != expected { + t.Fatalf("expected name %q but received %q", expected, s.Name) } } diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/system/sysconfig.go b/Godeps/_workspace/src/github.com/docker/libcontainer/system/sysconfig.go index 3e2f43b1..5efddefa 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/system/sysconfig.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/system/sysconfig.go @@ -1,4 +1,4 @@ -// +build linux,cgo +// +build cgo package system diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/system/sysconfig_notcgo.go b/Godeps/_workspace/src/github.com/docker/libcontainer/system/sysconfig_notcgo.go index 4bbb6989..663db82b 100644 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/system/sysconfig_notcgo.go +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/system/sysconfig_notcgo.go @@ -1,4 +1,4 @@ -// +build linux,!cgo +// +build !cgo package system diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/test/utils.go b/Godeps/_workspace/src/github.com/docker/libcontainer/test/utils.go new file mode 100644 index 00000000..6283d84b --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/test/utils.go @@ -0,0 +1,18 @@ +package libcontainer + +import ( + "github.com/docker/libcontainer" + "github.com/docker/libcontainer/security/capabilities" +) + +func GetAllCapabilities() []string { + return capabilities.GetAllCapabilities() +} + +func DropBoundingSet(container *libcontainer.Config) error { + return capabilities.DropBoundingSet(container.Capabilities) +} + +func DropCapabilities(container *libcontainer.Config) error { + return capabilities.DropCapabilities(container.Capabilities) +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/test/utils.go~ b/Godeps/_workspace/src/github.com/docker/libcontainer/test/utils.go~ new file mode 100644 index 00000000..55239a25 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/test/utils.go~ @@ -0,0 +1,18 @@ +package libcontainer + +import ( + "github.com/docker/libcontainer" + "github.com/docker/libcontainer/security/capabilities" +) + +func GetAllCapabilities() []string { + return capabilities.GetAllCapabilities() +} + +func DropBoundingSet(container *libcontainer.Container) error { + return capabilities.DropBoundingSet(container.Capabilities) +} + +func DropCapabilities(container *libcontainer.Container) error { + return capabilities.DropCapabilities(container.Capabilities) +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/MAINTAINERS b/Godeps/_workspace/src/github.com/docker/libcontainer/user/MAINTAINERS new file mode 100644 index 00000000..18e05a30 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/user/MAINTAINERS @@ -0,0 +1 @@ +Tianon Gravi (@tianon) diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/user.go b/Godeps/_workspace/src/github.com/docker/libcontainer/user/user.go new file mode 100644 index 00000000..493dd86f --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/user/user.go @@ -0,0 +1,258 @@ +package user + +import ( + "bufio" + "fmt" + "io" + "os" + "strconv" + "strings" +) + +const ( + minId = 0 + maxId = 1<<31 - 1 //for 32-bit systems compatibility +) + +var ( + ErrRange = fmt.Errorf("Uids and gids must be in range %d-%d", minId, maxId) +) + +type User struct { + Name string + Pass string + Uid int + Gid int + Gecos string + Home string + Shell string +} + +type Group struct { + Name string + Pass string + Gid int + List []string +} + +func parseLine(line string, v ...interface{}) { + if line == "" { + return + } + + parts := strings.Split(line, ":") + for i, p := range parts { + if len(v) <= i { + // if we have more "parts" than we have places to put them, bail for great "tolerance" of naughty configuration files + break + } + + switch e := v[i].(type) { + case *string: + // "root", "adm", "/bin/bash" + *e = p + case *int: + // "0", "4", "1000" + // ignore string to int conversion errors, for great "tolerance" of naughty configuration files + *e, _ = strconv.Atoi(p) + case *[]string: + // "", "root", "root,adm,daemon" + if p != "" { + *e = strings.Split(p, ",") + } else { + *e = []string{} + } + default: + // panic, because this is a programming/logic error, not a runtime one + panic("parseLine expects only pointers! argument " + strconv.Itoa(i) + " is not a pointer!") + } + } +} + +func ParsePasswd() ([]*User, error) { + return ParsePasswdFilter(nil) +} + +func ParsePasswdFilter(filter func(*User) bool) ([]*User, error) { + f, err := os.Open("/etc/passwd") + if err != nil { + return nil, err + } + defer f.Close() + return parsePasswdFile(f, filter) +} + +func parsePasswdFile(r io.Reader, filter func(*User) bool) ([]*User, error) { + var ( + s = bufio.NewScanner(r) + out = []*User{} + ) + + for s.Scan() { + if err := s.Err(); err != nil { + return nil, err + } + + text := strings.TrimSpace(s.Text()) + if text == "" { + continue + } + + // see: man 5 passwd + // name:password:UID:GID:GECOS:directory:shell + // Name:Pass:Uid:Gid:Gecos:Home:Shell + // root:x:0:0:root:/root:/bin/bash + // adm:x:3:4:adm:/var/adm:/bin/false + p := &User{} + parseLine( + text, + &p.Name, &p.Pass, &p.Uid, &p.Gid, &p.Gecos, &p.Home, &p.Shell, + ) + + if filter == nil || filter(p) { + out = append(out, p) + } + } + + return out, nil +} + +func ParseGroup() ([]*Group, error) { + return ParseGroupFilter(nil) +} + +func ParseGroupFilter(filter func(*Group) bool) ([]*Group, error) { + f, err := os.Open("/etc/group") + if err != nil { + return nil, err + } + defer f.Close() + return parseGroupFile(f, filter) +} + +func parseGroupFile(r io.Reader, filter func(*Group) bool) ([]*Group, error) { + var ( + s = bufio.NewScanner(r) + out = []*Group{} + ) + + for s.Scan() { + if err := s.Err(); err != nil { + return nil, err + } + + text := s.Text() + if text == "" { + continue + } + + // see: man 5 group + // group_name:password:GID:user_list + // Name:Pass:Gid:List + // root:x:0:root + // adm:x:4:root,adm,daemon + p := &Group{} + parseLine( + text, + &p.Name, &p.Pass, &p.Gid, &p.List, + ) + + if filter == nil || filter(p) { + out = append(out, p) + } + } + + return out, nil +} + +// Given a string like "user", "1000", "user:group", "1000:1000", returns the uid, gid, list of supplementary group IDs, and home directory, if available and/or applicable. +func GetUserGroupSupplementaryHome(userSpec string, defaultUid, defaultGid int, defaultHome string) (int, int, []int, string, error) { + var ( + uid = defaultUid + gid = defaultGid + suppGids = []int{} + home = defaultHome + + userArg, groupArg string + ) + + // allow for userArg to have either "user" syntax, or optionally "user:group" syntax + parseLine(userSpec, &userArg, &groupArg) + + users, err := ParsePasswdFilter(func(u *User) bool { + if userArg == "" { + return u.Uid == uid + } + return u.Name == userArg || strconv.Itoa(u.Uid) == userArg + }) + if err != nil && !os.IsNotExist(err) { + if userArg == "" { + userArg = strconv.Itoa(uid) + } + return 0, 0, nil, "", fmt.Errorf("Unable to find user %v: %v", userArg, err) + } + + haveUser := users != nil && len(users) > 0 + if haveUser { + // if we found any user entries that matched our filter, let's take the first one as "correct" + uid = users[0].Uid + gid = users[0].Gid + home = users[0].Home + } else if userArg != "" { + // we asked for a user but didn't find them... let's check to see if we wanted a numeric user + uid, err = strconv.Atoi(userArg) + if err != nil { + // not numeric - we have to bail + return 0, 0, nil, "", fmt.Errorf("Unable to find user %v", userArg) + } + if uid < minId || uid > maxId { + return 0, 0, nil, "", ErrRange + } + + // if userArg couldn't be found in /etc/passwd but is numeric, just roll with it - this is legit + } + + if groupArg != "" || (haveUser && users[0].Name != "") { + groups, err := ParseGroupFilter(func(g *Group) bool { + if groupArg != "" { + return g.Name == groupArg || strconv.Itoa(g.Gid) == groupArg + } + for _, u := range g.List { + if u == users[0].Name { + return true + } + } + return false + }) + if err != nil && !os.IsNotExist(err) { + return 0, 0, nil, "", fmt.Errorf("Unable to find groups for user %v: %v", users[0].Name, err) + } + + haveGroup := groups != nil && len(groups) > 0 + if groupArg != "" { + if haveGroup { + // if we found any group entries that matched our filter, let's take the first one as "correct" + gid = groups[0].Gid + } else { + // we asked for a group but didn't find id... let's check to see if we wanted a numeric group + gid, err = strconv.Atoi(groupArg) + if err != nil { + // not numeric - we have to bail + return 0, 0, nil, "", fmt.Errorf("Unable to find group %v", groupArg) + } + if gid < minId || gid > maxId { + return 0, 0, nil, "", ErrRange + } + + // if groupArg couldn't be found in /etc/group but is numeric, just roll with it - this is legit + } + } else if haveGroup { + suppGids = make([]int, len(groups)) + for i, group := range groups { + suppGids[i] = group.Gid + } + } + } + + return uid, gid, suppGids, home, nil +} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/user_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/user/user_test.go new file mode 100644 index 00000000..136632c2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/libcontainer/user/user_test.go @@ -0,0 +1,94 @@ +package user + +import ( + "strings" + "testing" +) + +func TestUserParseLine(t *testing.T) { + var ( + a, b string + c []string + d int + ) + + parseLine("", &a, &b) + if a != "" || b != "" { + t.Fatalf("a and b should be empty ('%v', '%v')", a, b) + } + + parseLine("a", &a, &b) + if a != "a" || b != "" { + t.Fatalf("a should be 'a' and b should be empty ('%v', '%v')", a, b) + } + + parseLine("bad boys:corny cows", &a, &b) + if a != "bad boys" || b != "corny cows" { + t.Fatalf("a should be 'bad boys' and b should be 'corny cows' ('%v', '%v')", a, b) + } + + parseLine("", &c) + if len(c) != 0 { + t.Fatalf("c should be empty (%#v)", c) + } + + parseLine("d,e,f:g:h:i,j,k", &c, &a, &b, &c) + if a != "g" || b != "h" || len(c) != 3 || c[0] != "i" || c[1] != "j" || c[2] != "k" { + t.Fatalf("a should be 'g', b should be 'h', and c should be ['i','j','k'] ('%v', '%v', '%#v')", a, b, c) + } + + parseLine("::::::::::", &a, &b, &c) + if a != "" || b != "" || len(c) != 0 { + t.Fatalf("a, b, and c should all be empty ('%v', '%v', '%#v')", a, b, c) + } + + parseLine("not a number", &d) + if d != 0 { + t.Fatalf("d should be 0 (%v)", d) + } + + parseLine("b:12:c", &a, &d, &b) + if a != "b" || b != "c" || d != 12 { + t.Fatalf("a should be 'b' and b should be 'c', and d should be 12 ('%v', '%v', %v)", a, b, d) + } +} + +func TestUserParsePasswd(t *testing.T) { + users, err := parsePasswdFile(strings.NewReader(` +root:x:0:0:root:/root:/bin/bash +adm:x:3:4:adm:/var/adm:/bin/false +this is just some garbage data +`), nil) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if len(users) != 3 { + t.Fatalf("Expected 3 users, got %v", len(users)) + } + if users[0].Uid != 0 || users[0].Name != "root" { + t.Fatalf("Expected users[0] to be 0 - root, got %v - %v", users[0].Uid, users[0].Name) + } + if users[1].Uid != 3 || users[1].Name != "adm" { + t.Fatalf("Expected users[1] to be 3 - adm, got %v - %v", users[1].Uid, users[1].Name) + } +} + +func TestUserParseGroup(t *testing.T) { + groups, err := parseGroupFile(strings.NewReader(` +root:x:0:root +adm:x:4:root,adm,daemon +this is just some garbage data +`), nil) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if len(groups) != 3 { + t.Fatalf("Expected 3 groups, got %v", len(groups)) + } + if groups[0].Gid != 0 || groups[0].Name != "root" || len(groups[0].List) != 1 { + t.Fatalf("Expected groups[0] to be 0 - root - 1 member, got %v - %v - %v", groups[0].Gid, groups[0].Name, len(groups[0].List)) + } + if groups[1].Gid != 4 || groups[1].Name != "adm" || len(groups[1].List) != 3 { + t.Fatalf("Expected groups[1] to be 4 - adm - 3 members, got %v - %v - %v", groups[1].Gid, groups[1].Name, len(groups[1].List)) + } +} diff --git a/deploy/prepare.sh b/deploy/prepare.sh index c2251064..55283a96 100755 --- a/deploy/prepare.sh +++ b/deploy/prepare.sh @@ -4,4 +4,4 @@ set -e set -x # Statically build cAdvisor from source and stage it. -godep go build --ldflags '-extldflags "-static"' github.com/google/cadvisor +godep go build -a --ldflags '-extldflags "-static"' github.com/google/cadvisor