Merge pull request #186 from vishh/fix_godeps

Updating libcontainer dependency via 'godep update'.
This commit is contained in:
Victor Marmol 2014-08-19 15:51:24 -04:00
commit a80c3b81a8
64 changed files with 1498 additions and 710 deletions

4
Godeps/Godeps.json generated
View File

@ -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",

View File

@ -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

View File

@ -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``

View File

@ -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"]

View File

@ -1,4 +1,6 @@
Michael Crosby <michael@docker.com> (@crosbymichael)
Rohit Jnagal <jnagal@google.com> (@rjnagal)
Victor Marmol <vmarmol@google.com> (@vmarmol)
Mrunal Patel <mpatel@redhat.com> (@mrunalp)
.travis.yml: Tianon Gravi <admwiggin@gmail.com> (@tianon)
update-vendor.sh: Tianon Gravi <admwiggin@gmail.com> (@tianon)

View File

@ -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)

View File

@ -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:

View File

@ -37,4 +37,5 @@ type Cgroup struct {
type ActiveCgroup interface {
Cleanup() error
Paths() (map[string]string, error)
}

View File

@ -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,
}

View File

@ -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

View File

@ -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

View File

@ -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

Binary file not shown.

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -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)
}
}
}

View File

@ -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...)

View File

@ -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)
}

View File

@ -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
}

View File

@ -1,223 +0,0 @@
// +build linux
package namespaces
/*
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/limits.h>
#include <linux/sched.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <getopt.h>
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 <sched.h>
#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, "<binary> nsenter --nspid <pid> --containerjson <container_json> -- 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: <binary> nsenter <PID> <process label> <container JSON> <argv>...
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"

View File

@ -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`.

View File

@ -0,0 +1,218 @@
// +build cgo
//
// formated with indent -linux nsenter.c
#include <errno.h>
#include <fcntl.h>
#include <linux/limits.h>
#include <linux/sched.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <getopt.h>
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 <sched.h>
#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 <pid> --console <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;
}

View File

@ -0,0 +1,10 @@
// +build linux
package nsenter
/*
__attribute__((constructor)) init() {
nsenter();
}
*/
import "C"

View File

@ -0,0 +1,3 @@
// +build !linux !cgo
package nsenter

View File

@ -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
}

Binary file not shown.

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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 {

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}
}

Binary file not shown.

Binary file not shown.

View File

@ -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 <path> <type> <major> <minor>
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()
}

Binary file not shown.

View File

@ -1,7 +0,0 @@
package main
import "github.com/docker/libcontainer/nsinit"
func main() {
nsinit.NsInit()
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

Binary file not shown.

View File

@ -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.

View File

@ -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) {

View File

@ -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)
}
}

View File

@ -1,4 +1,4 @@
// +build linux,cgo
// +build cgo
package system

View File

@ -1,4 +1,4 @@
// +build linux,!cgo
// +build !cgo
package system

View File

@ -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)
}

View File

@ -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)
}

View File

@ -0,0 +1 @@
Tianon Gravi <admwiggin@gmail.com> (@tianon)

View File

@ -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
}

View File

@ -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))
}
}

View File

@ -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