From b5434b5d7fb636f8c641d266011f9aebb794e45b Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 10 Apr 2014 21:41:31 +0000 Subject: [PATCH 01/14] Move apparmor into security sub dir Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- libcontainer/nsinit/init.go | 1 + 1 file changed, 1 insertion(+) diff --git a/libcontainer/nsinit/init.go b/libcontainer/nsinit/init.go index 0e85c0e..ce51db3 100644 --- a/libcontainer/nsinit/init.go +++ b/libcontainer/nsinit/init.go @@ -13,6 +13,7 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/capabilities" "github.com/dotcloud/docker/pkg/libcontainer/network" + "github.com/dotcloud/docker/pkg/libcontainer/security/apparmor" "github.com/dotcloud/docker/pkg/libcontainer/utils" "github.com/dotcloud/docker/pkg/system" "github.com/dotcloud/docker/pkg/user" From 3d546f20db59a4be1bbed581bc309bb08368c6c0 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 10 Apr 2014 23:03:52 +0000 Subject: [PATCH 02/14] Add restrictions to proc in libcontainer Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- libcontainer/nsinit/init.go | 2 +- libcontainer/nsinit/mount.go | 67 +++++++++++++--------- libcontainer/security/restrict/restrict.go | 46 +++++++++++++++ 3 files changed, 86 insertions(+), 29 deletions(-) create mode 100644 libcontainer/security/restrict/restrict.go diff --git a/libcontainer/nsinit/init.go b/libcontainer/nsinit/init.go index ce51db3..fb3a895 100644 --- a/libcontainer/nsinit/init.go +++ b/libcontainer/nsinit/init.go @@ -61,7 +61,7 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol label.Init() ns.logger.Println("setup mount namespace") - if err := setupNewMountNamespace(rootfs, container.Mounts, console, container.ReadonlyFs, container.NoPivotRoot, container.Context["mount_label"]); err != nil { + if err := setupNewMountNamespace(rootfs, console, container); err != nil { return fmt.Errorf("setup mount namespace %s", err) } if err := system.Sethostname(container.Hostname); err != nil { diff --git a/libcontainer/nsinit/mount.go b/libcontainer/nsinit/mount.go index dd6b1c8..e4869a0 100644 --- a/libcontainer/nsinit/mount.go +++ b/libcontainer/nsinit/mount.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/security/restrict" "github.com/dotcloud/docker/pkg/system" "io/ioutil" "os" @@ -21,9 +22,9 @@ const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NOD // // There is no need to unmount the new mounts because as soon as the mount namespace // is no longer in use, the mounts will be removed automatically -func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, console string, readonly, noPivotRoot bool, mountLabel string) error { +func setupNewMountNamespace(rootfs, console string, container *libcontainer.Container) error { flag := syscall.MS_PRIVATE - if noPivotRoot { + if container.NoPivotRoot { flag = syscall.MS_SLAVE } if err := system.Mount("", "/", "", uintptr(flag|syscall.MS_REC), ""); err != nil { @@ -32,44 +33,28 @@ func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, cons if err := system.Mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REC, ""); err != nil { return fmt.Errorf("mouting %s as bind %s", rootfs, err) } - if err := mountSystem(rootfs, mountLabel); err != nil { + if err := mountSystem(rootfs, container.Context["mount_label"]); err != nil { return fmt.Errorf("mount system %s", err) } - - for _, m := range bindMounts { - var ( - flags = syscall.MS_BIND | syscall.MS_REC - dest = filepath.Join(rootfs, m.Destination) - ) - if !m.Writable { - flags = flags | syscall.MS_RDONLY - } - if err := system.Mount(m.Source, dest, "bind", uintptr(flags), ""); err != nil { - return fmt.Errorf("mounting %s into %s %s", m.Source, dest, err) - } - if !m.Writable { - if err := system.Mount(m.Source, dest, "bind", uintptr(flags|syscall.MS_REMOUNT), ""); err != nil { - return fmt.Errorf("remounting %s into %s %s", m.Source, dest, err) - } - } - if m.Private { - if err := system.Mount("", dest, "none", uintptr(syscall.MS_PRIVATE), ""); err != nil { - return fmt.Errorf("mounting %s private %s", dest, err) - } + if err := setupBindmounts(rootfs, container.Mounts); err != nil { + return fmt.Errorf("bind mounts %s", err) + } + if restrictionPath := container.Context["restriction_path"]; restrictionPath != "" { + if err := restrict.Restrict(rootfs, restrictionPath); err != nil { + return fmt.Errorf("restrict %s", err) } } - if err := copyDevNodes(rootfs); err != nil { return fmt.Errorf("copy dev nodes %s", err) } - if err := setupPtmx(rootfs, console, mountLabel); err != nil { + if err := setupPtmx(rootfs, console, container.Context["mount_label"]); err != nil { return err } if err := system.Chdir(rootfs); err != nil { return fmt.Errorf("chdir into %s %s", rootfs, err) } - if noPivotRoot { + if container.NoPivotRoot { if err := rootMsMove(rootfs); err != nil { return err } @@ -79,7 +64,7 @@ func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, cons } } - if readonly { + if container.ReadonlyFs { if err := system.Mount("/", "/", "bind", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC, ""); err != nil { return fmt.Errorf("mounting %s as readonly %s", rootfs, err) } @@ -263,3 +248,29 @@ func remountSys() error { } return nil } + +func setupBindmounts(rootfs string, bindMounts []libcontainer.Mount) error { + for _, m := range bindMounts { + var ( + flags = syscall.MS_BIND | syscall.MS_REC + dest = filepath.Join(rootfs, m.Destination) + ) + if !m.Writable { + flags = flags | syscall.MS_RDONLY + } + if err := system.Mount(m.Source, dest, "bind", uintptr(flags), ""); err != nil { + return fmt.Errorf("mounting %s into %s %s", m.Source, dest, err) + } + if !m.Writable { + if err := system.Mount(m.Source, dest, "bind", uintptr(flags|syscall.MS_REMOUNT), ""); err != nil { + return fmt.Errorf("remounting %s into %s %s", m.Source, dest, err) + } + } + if m.Private { + if err := system.Mount("", dest, "none", uintptr(syscall.MS_PRIVATE), ""); err != nil { + return fmt.Errorf("mounting %s private %s", dest, err) + } + } + } + return nil +} diff --git a/libcontainer/security/restrict/restrict.go b/libcontainer/security/restrict/restrict.go new file mode 100644 index 0000000..d5c1dbb --- /dev/null +++ b/libcontainer/security/restrict/restrict.go @@ -0,0 +1,46 @@ +package restrict + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/system" + "path/filepath" + "syscall" +) + +const flags = syscall.MS_BIND | syscall.MS_REC | syscall.MS_RDONLY + +var restrictions = map[string]string{ + // dirs + "/proc/sys": "", + "/proc/irq": "", + "/proc/acpi": "", + + // files + "/proc/sysrq-trigger": "/dev/null", + "/proc/kcore": "/dev/null", +} + +// Restrict locks down access to many areas of proc +// by using the asumption that the user does not have mount caps to +// revert the changes made here +func Restrict(rootfs, empty string) error { + for dest, source := range restrictions { + dest = filepath.Join(rootfs, dest) + + // we don't have a "/dev/null" for dirs so have the requester pass a dir + // for us to bind mount + switch source { + case "": + source = empty + default: + source = filepath.Join(rootfs, source) + } + if err := system.Mount(source, dest, "bind", flags, ""); err != nil { + return fmt.Errorf("unable to mount %s over %s %s", source, dest, err) + } + if err := system.Mount("", dest, "bind", flags|syscall.MS_REMOUNT, ""); err != nil { + return fmt.Errorf("unable to mount %s over %s %s", source, dest, err) + } + } + return nil +} From ef923907df2ee86e7683c030d04cfc8b1759c4f9 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 11:45:39 +0000 Subject: [PATCH 03/14] No not mount sysfs by default for non privilged containers Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- libcontainer/container.go | 27 ++++++++++++++++------- libcontainer/nsinit/mount.go | 42 ++++++++++++++++++++++-------------- 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/libcontainer/container.go b/libcontainer/container.go index c7cac35..1e032c0 100644 --- a/libcontainer/container.go +++ b/libcontainer/container.go @@ -23,7 +23,7 @@ type Container struct { Networks []*Network `json:"networks,omitempty"` // nil for host's network stack Cgroups *cgroups.Cgroup `json:"cgroups,omitempty"` // cgroups Context Context `json:"context,omitempty"` // generic context for specific options (apparmor, selinux) - Mounts []Mount `json:"mounts,omitempty"` + Mounts Mounts `json:"mounts,omitempty"` } // Network defines configuration for a container's networking stack @@ -38,11 +38,22 @@ type Network struct { Mtu int `json:"mtu,omitempty"` } -// Bind mounts from the host system to the container -// -type Mount struct { - Source string `json:"source"` // Source path, in the host namespace - Destination string `json:"destination"` // Destination path, in the container - Writable bool `json:"writable"` - Private bool `json:"private"` +type Mounts []Mount + +func (s Mounts) OfType(t string) Mounts { + out := Mounts{} + for _, m := range s { + if m.Type == t { + out = append(out, m) + } + } + return out +} + +type Mount struct { + Type string `json:"type,omitempty"` + Source string `json:"source,omitempty"` // Source path, in the host namespace + Destination string `json:"destination,omitempty"` // Destination path, in the container + Writable bool `json:"writable,omitempty"` + Private bool `json:"private,omitempty"` } diff --git a/libcontainer/nsinit/mount.go b/libcontainer/nsinit/mount.go index e4869a0..ee48032 100644 --- a/libcontainer/nsinit/mount.go +++ b/libcontainer/nsinit/mount.go @@ -17,6 +17,14 @@ import ( // default mount point flags const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV +type mount struct { + source string + path string + device string + flags int + data string +} + // setupNewMountNamespace is used to initialize a new mount namespace for an new // container in the rootfs that is specified. // @@ -33,7 +41,7 @@ func setupNewMountNamespace(rootfs, console string, container *libcontainer.Cont if err := system.Mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REC, ""); err != nil { return fmt.Errorf("mouting %s as bind %s", rootfs, err) } - if err := mountSystem(rootfs, container.Context["mount_label"]); err != nil { + if err := mountSystem(rootfs, container); err != nil { return fmt.Errorf("mount system %s", err) } if err := setupBindmounts(rootfs, container.Mounts); err != nil { @@ -183,19 +191,8 @@ func setupConsole(rootfs, console string, mountLabel string) error { // mountSystem sets up linux specific system mounts like sys, proc, shm, and devpts // inside the mount namespace -func mountSystem(rootfs string, mountLabel string) error { - for _, m := range []struct { - source string - path string - device string - flags int - data string - }{ - {source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags}, - {source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags}, - {source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)}, - {source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)}, - } { +func mountSystem(rootfs string, container *libcontainer.Container) error { + for _, m := range newSystemMounts(rootfs, container.Context["mount_label"], container.Mounts) { if err := os.MkdirAll(m.path, 0755); err != nil && !os.IsExist(err) { return fmt.Errorf("mkdirall %s %s", m.path, err) } @@ -249,8 +246,8 @@ func remountSys() error { return nil } -func setupBindmounts(rootfs string, bindMounts []libcontainer.Mount) error { - for _, m := range bindMounts { +func setupBindmounts(rootfs string, bindMounts libcontainer.Mounts) error { + for _, m := range bindMounts.OfType("bind") { var ( flags = syscall.MS_BIND | syscall.MS_REC dest = filepath.Join(rootfs, m.Destination) @@ -274,3 +271,16 @@ func setupBindmounts(rootfs string, bindMounts []libcontainer.Mount) error { } return nil } + +func newSystemMounts(rootfs, mountLabel string, mounts libcontainer.Mounts) []mount { + systemMounts := []mount{ + {source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags}, + {source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)}, + {source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)}, + } + + if len(mounts.OfType("sysfs")) == 1 { + systemMounts = append(systemMounts, mount{source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags}) + } + return systemMounts +} From cc900b9db8d34438012ea60edd27efdbf9707d0b Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 14:30:09 +0000 Subject: [PATCH 04/14] Mount over dev and only copy allowed nodes in Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- libcontainer/nsinit/mount.go | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/libcontainer/nsinit/mount.go b/libcontainer/nsinit/mount.go index ee48032..81229ef 100644 --- a/libcontainer/nsinit/mount.go +++ b/libcontainer/nsinit/mount.go @@ -47,14 +47,14 @@ func setupNewMountNamespace(rootfs, console string, container *libcontainer.Cont if err := setupBindmounts(rootfs, container.Mounts); err != nil { return fmt.Errorf("bind mounts %s", err) } + if err := copyDevNodes(rootfs); err != nil { + return fmt.Errorf("copy dev nodes %s", err) + } if restrictionPath := container.Context["restriction_path"]; restrictionPath != "" { if err := restrict.Restrict(rootfs, restrictionPath); err != nil { return fmt.Errorf("restrict %s", err) } } - if err := copyDevNodes(rootfs); err != nil { - return fmt.Errorf("copy dev nodes %s", err) - } if err := setupPtmx(rootfs, console, container.Context["mount_label"]); err != nil { return err } @@ -273,12 +273,20 @@ func setupBindmounts(rootfs string, bindMounts libcontainer.Mounts) error { } func newSystemMounts(rootfs, mountLabel string, mounts libcontainer.Mounts) []mount { - systemMounts := []mount{ - {source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags}, + devMounts := []mount{ {source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)}, {source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)}, } + systemMounts := []mount{ + {source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags}, + } + + if len(mounts.OfType("devtmpfs")) == 1 { + systemMounts = append(systemMounts, mount{source: "tmpfs", path: filepath.Join(rootfs, "dev"), device: "tmpfs", flags: syscall.MS_NOSUID | syscall.MS_STRICTATIME, data: "mode=755"}) + } + systemMounts = append(systemMounts, devMounts...) + if len(mounts.OfType("sysfs")) == 1 { systemMounts = append(systemMounts, mount{source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags}) } From 323ea01c18cda883c27d625577fb71084d2c585e Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 14:42:53 +0000 Subject: [PATCH 05/14] Move console into its own package Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- libcontainer/console/console.go | 46 +++++++++++++++++++++++++++++++++ libcontainer/nsinit/mount.go | 41 +++-------------------------- 2 files changed, 50 insertions(+), 37 deletions(-) create mode 100644 libcontainer/console/console.go diff --git a/libcontainer/console/console.go b/libcontainer/console/console.go new file mode 100644 index 0000000..deee544 --- /dev/null +++ b/libcontainer/console/console.go @@ -0,0 +1,46 @@ +// +build linux + +package console + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/label" + "github.com/dotcloud/docker/pkg/system" + "os" + "path/filepath" + "syscall" +) + +// Setup initializes the proper /dev/console inside the rootfs path +func Setup(rootfs, consolePath, mountLabel string) error { + oldMask := system.Umask(0000) + defer system.Umask(oldMask) + + stat, err := os.Stat(consolePath) + if err != nil { + return fmt.Errorf("stat console %s %s", consolePath, err) + } + var ( + st = stat.Sys().(*syscall.Stat_t) + dest = filepath.Join(rootfs, "dev/console") + ) + if err := os.Remove(dest); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("remove %s %s", dest, err) + } + if err := os.Chmod(consolePath, 0600); err != nil { + return err + } + if err := os.Chown(consolePath, 0, 0); err != nil { + return err + } + if err := system.Mknod(dest, (st.Mode&^07777)|0600, int(st.Rdev)); err != nil { + return fmt.Errorf("mknod %s %s", dest, err) + } + if err := label.SetFileLabel(consolePath, mountLabel); err != nil { + return fmt.Errorf("set file label %s %s", dest, err) + } + if err := system.Mount(consolePath, dest, "bind", syscall.MS_BIND, ""); err != nil { + return fmt.Errorf("bind %s to %s %s", consolePath, dest, err) + } + return nil +} diff --git a/libcontainer/nsinit/mount.go b/libcontainer/nsinit/mount.go index 81229ef..c85058a 100644 --- a/libcontainer/nsinit/mount.go +++ b/libcontainer/nsinit/mount.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/console" "github.com/dotcloud/docker/pkg/libcontainer/security/restrict" "github.com/dotcloud/docker/pkg/system" "io/ioutil" @@ -155,40 +156,6 @@ func copyDevNode(rootfs, node string) error { return nil } -// setupConsole ensures that the container has a proper /dev/console setup -func setupConsole(rootfs, console string, mountLabel string) error { - oldMask := system.Umask(0000) - defer system.Umask(oldMask) - - stat, err := os.Stat(console) - if err != nil { - return fmt.Errorf("stat console %s %s", console, err) - } - var ( - st = stat.Sys().(*syscall.Stat_t) - dest = filepath.Join(rootfs, "dev/console") - ) - if err := os.Remove(dest); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("remove %s %s", dest, err) - } - if err := os.Chmod(console, 0600); err != nil { - return err - } - if err := os.Chown(console, 0, 0); err != nil { - return err - } - if err := system.Mknod(dest, (st.Mode&^07777)|0600, int(st.Rdev)); err != nil { - return fmt.Errorf("mknod %s %s", dest, err) - } - if err := label.SetFileLabel(console, mountLabel); err != nil { - return fmt.Errorf("SetFileLabel Failed %s %s", dest, err) - } - if err := system.Mount(console, dest, "bind", syscall.MS_BIND, ""); err != nil { - return fmt.Errorf("bind %s to %s %s", console, dest, err) - } - return nil -} - // mountSystem sets up linux specific system mounts like sys, proc, shm, and devpts // inside the mount namespace func mountSystem(rootfs string, container *libcontainer.Container) error { @@ -205,7 +172,7 @@ func mountSystem(rootfs string, container *libcontainer.Container) error { // setupPtmx adds a symlink to pts/ptmx for /dev/ptmx and // finishes setting up /dev/console -func setupPtmx(rootfs, console string, mountLabel string) error { +func setupPtmx(rootfs, consolePath, mountLabel string) error { ptmx := filepath.Join(rootfs, "dev/ptmx") if err := os.Remove(ptmx); err != nil && !os.IsNotExist(err) { return err @@ -213,8 +180,8 @@ func setupPtmx(rootfs, console string, mountLabel string) error { if err := os.Symlink("pts/ptmx", ptmx); err != nil { return fmt.Errorf("symlink dev ptmx %s", err) } - if console != "" { - if err := setupConsole(rootfs, console, mountLabel); err != nil { + if consolePath != "" { + if err := console.Setup(rootfs, consolePath, mountLabel); err != nil { return err } } From a77846506bb6fdf0a678f92f03ebb95dcc8735b5 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 15:06:56 +0000 Subject: [PATCH 06/14] Refactor mounts into pkg to make changes easier Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- .../{nsinit/mount.go => mount/init.go} | 153 +++--------------- libcontainer/mount/msmoveroot.go | 19 +++ libcontainer/mount/nodes/nodes.go | 49 ++++++ libcontainer/mount/pivotroot.go | 31 ++++ libcontainer/mount/ptmx.go | 26 +++ libcontainer/mount/readonly.go | 12 ++ libcontainer/mount/remount.go | 31 ++++ libcontainer/nsinit/execin.go | 5 +- libcontainer/nsinit/init.go | 3 +- 9 files changed, 191 insertions(+), 138 deletions(-) rename libcontainer/{nsinit/mount.go => mount/init.go} (51%) create mode 100644 libcontainer/mount/msmoveroot.go create mode 100644 libcontainer/mount/nodes/nodes.go create mode 100644 libcontainer/mount/pivotroot.go create mode 100644 libcontainer/mount/ptmx.go create mode 100644 libcontainer/mount/readonly.go create mode 100644 libcontainer/mount/remount.go diff --git a/libcontainer/nsinit/mount.go b/libcontainer/mount/init.go similarity index 51% rename from libcontainer/nsinit/mount.go rename to libcontainer/mount/init.go index c85058a..2a5e47a 100644 --- a/libcontainer/nsinit/mount.go +++ b/libcontainer/mount/init.go @@ -1,15 +1,14 @@ // +build linux -package nsinit +package mount import ( "fmt" "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/libcontainer/console" + "github.com/dotcloud/docker/pkg/libcontainer/mount/nodes" "github.com/dotcloud/docker/pkg/libcontainer/security/restrict" "github.com/dotcloud/docker/pkg/system" - "io/ioutil" "os" "path/filepath" "syscall" @@ -26,13 +25,13 @@ type mount struct { data string } -// setupNewMountNamespace is used to initialize a new mount namespace for an new -// container in the rootfs that is specified. -// -// There is no need to unmount the new mounts because as soon as the mount namespace -// is no longer in use, the mounts will be removed automatically -func setupNewMountNamespace(rootfs, console string, container *libcontainer.Container) error { - flag := syscall.MS_PRIVATE +// InitializeMountNamespace setups up the devices, mount points, and filesystems for use inside a +// new mount namepsace +func InitializeMountNamespace(rootfs, console string, container *libcontainer.Container) error { + var ( + err error + flag = syscall.MS_PRIVATE + ) if container.NoPivotRoot { flag = syscall.MS_SLAVE } @@ -48,7 +47,7 @@ func setupNewMountNamespace(rootfs, console string, container *libcontainer.Cont if err := setupBindmounts(rootfs, container.Mounts); err != nil { return fmt.Errorf("bind mounts %s", err) } - if err := copyDevNodes(rootfs); err != nil { + if err := nodes.CopyN(rootfs, nodes.DefaultNodes); err != nil { return fmt.Errorf("copy dev nodes %s", err) } if restrictionPath := container.Context["restriction_path"]; restrictionPath != "" { @@ -56,7 +55,7 @@ func setupNewMountNamespace(rootfs, console string, container *libcontainer.Cont return fmt.Errorf("restrict %s", err) } } - if err := setupPtmx(rootfs, console, container.Context["mount_label"]); err != nil { + if err := SetupPtmx(rootfs, console, container.Context["mount_label"]); err != nil { return err } if err := system.Chdir(rootfs); err != nil { @@ -64,18 +63,17 @@ func setupNewMountNamespace(rootfs, console string, container *libcontainer.Cont } if container.NoPivotRoot { - if err := rootMsMove(rootfs); err != nil { - return err - } + err = MsMoveRoot(rootfs) } else { - if err := rootPivot(rootfs); err != nil { - return err - } + err = PivotRoot(rootfs) + } + if err != nil { + return err } if container.ReadonlyFs { - if err := system.Mount("/", "/", "bind", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC, ""); err != nil { - return fmt.Errorf("mounting %s as readonly %s", rootfs, err) + if err := SetReadonly(); err != nil { + return fmt.Errorf("set readonly %s", err) } } @@ -84,78 +82,6 @@ func setupNewMountNamespace(rootfs, console string, container *libcontainer.Cont return nil } -// use a pivot root to setup the rootfs -func rootPivot(rootfs string) error { - pivotDir, err := ioutil.TempDir(rootfs, ".pivot_root") - if err != nil { - return fmt.Errorf("can't create pivot_root dir %s", pivotDir, err) - } - if err := system.Pivotroot(rootfs, pivotDir); err != nil { - return fmt.Errorf("pivot_root %s", err) - } - if err := system.Chdir("/"); err != nil { - return fmt.Errorf("chdir / %s", err) - } - // path to pivot dir now changed, update - pivotDir = filepath.Join("/", filepath.Base(pivotDir)) - if err := system.Unmount(pivotDir, syscall.MNT_DETACH); err != nil { - return fmt.Errorf("unmount pivot_root dir %s", err) - } - if err := os.Remove(pivotDir); err != nil { - return fmt.Errorf("remove pivot_root dir %s", err) - } - return nil -} - -// use MS_MOVE and chroot to setup the rootfs -func rootMsMove(rootfs string) error { - if err := system.Mount(rootfs, "/", "", syscall.MS_MOVE, ""); err != nil { - return fmt.Errorf("mount move %s into / %s", rootfs, err) - } - if err := system.Chroot("."); err != nil { - return fmt.Errorf("chroot . %s", err) - } - if err := system.Chdir("/"); err != nil { - return fmt.Errorf("chdir / %s", err) - } - return nil -} - -// copyDevNodes mknods the hosts devices so the new container has access to them -func copyDevNodes(rootfs string) error { - oldMask := system.Umask(0000) - defer system.Umask(oldMask) - - for _, node := range []string{ - "null", - "zero", - "full", - "random", - "urandom", - "tty", - } { - if err := copyDevNode(rootfs, node); err != nil { - return err - } - } - return nil -} - -func copyDevNode(rootfs, node string) error { - stat, err := os.Stat(filepath.Join("/dev", node)) - if err != nil { - return err - } - var ( - dest = filepath.Join(rootfs, "dev", node) - st = stat.Sys().(*syscall.Stat_t) - ) - if err := system.Mknod(dest, st.Mode, int(st.Rdev)); err != nil && !os.IsExist(err) { - return fmt.Errorf("copy %s %s", node, err) - } - return nil -} - // mountSystem sets up linux specific system mounts like sys, proc, shm, and devpts // inside the mount namespace func mountSystem(rootfs string, container *libcontainer.Container) error { @@ -170,49 +96,6 @@ func mountSystem(rootfs string, container *libcontainer.Container) error { return nil } -// setupPtmx adds a symlink to pts/ptmx for /dev/ptmx and -// finishes setting up /dev/console -func setupPtmx(rootfs, consolePath, mountLabel string) error { - ptmx := filepath.Join(rootfs, "dev/ptmx") - if err := os.Remove(ptmx); err != nil && !os.IsNotExist(err) { - return err - } - if err := os.Symlink("pts/ptmx", ptmx); err != nil { - return fmt.Errorf("symlink dev ptmx %s", err) - } - if consolePath != "" { - if err := console.Setup(rootfs, consolePath, mountLabel); err != nil { - return err - } - } - return nil -} - -// remountProc is used to detach and remount the proc filesystem -// commonly needed with running a new process inside an existing container -func remountProc() error { - if err := system.Unmount("/proc", syscall.MNT_DETACH); err != nil { - return err - } - if err := system.Mount("proc", "/proc", "proc", uintptr(defaultMountFlags), ""); err != nil { - return err - } - return nil -} - -func remountSys() error { - if err := system.Unmount("/sys", syscall.MNT_DETACH); err != nil { - if err != syscall.EINVAL { - return err - } - } else { - if err := system.Mount("sysfs", "/sys", "sysfs", uintptr(defaultMountFlags), ""); err != nil { - return err - } - } - return nil -} - func setupBindmounts(rootfs string, bindMounts libcontainer.Mounts) error { for _, m := range bindMounts.OfType("bind") { var ( diff --git a/libcontainer/mount/msmoveroot.go b/libcontainer/mount/msmoveroot.go new file mode 100644 index 0000000..b336c86 --- /dev/null +++ b/libcontainer/mount/msmoveroot.go @@ -0,0 +1,19 @@ +// +build linux + +package mount + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/system" + "syscall" +) + +func MsMoveRoot(rootfs string) error { + if err := system.Mount(rootfs, "/", "", syscall.MS_MOVE, ""); err != nil { + return fmt.Errorf("mount move %s into / %s", rootfs, err) + } + if err := system.Chroot("."); err != nil { + return fmt.Errorf("chroot . %s", err) + } + return system.Chdir("/") +} diff --git a/libcontainer/mount/nodes/nodes.go b/libcontainer/mount/nodes/nodes.go new file mode 100644 index 0000000..5022f85 --- /dev/null +++ b/libcontainer/mount/nodes/nodes.go @@ -0,0 +1,49 @@ +// +build linux + +package nodes + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/system" + "os" + "path/filepath" + "syscall" +) + +// Default list of device nodes to copy +var DefaultNodes = []string{ + "null", + "zero", + "full", + "random", + "urandom", + "tty", +} + +// CopyN copies the device node from the host into the rootfs +func CopyN(rootfs string, nodesToCopy []string) error { + oldMask := system.Umask(0000) + defer system.Umask(oldMask) + + for _, node := range nodesToCopy { + if err := Copy(rootfs, node); err != nil { + return err + } + } + return nil +} + +func Copy(rootfs, node string) error { + stat, err := os.Stat(filepath.Join("/dev", node)) + if err != nil { + return err + } + var ( + dest = filepath.Join(rootfs, "dev", node) + st = stat.Sys().(*syscall.Stat_t) + ) + if err := system.Mknod(dest, st.Mode, int(st.Rdev)); err != nil && !os.IsExist(err) { + return fmt.Errorf("copy %s %s", node, err) + } + return nil +} diff --git a/libcontainer/mount/pivotroot.go b/libcontainer/mount/pivotroot.go new file mode 100644 index 0000000..447f590 --- /dev/null +++ b/libcontainer/mount/pivotroot.go @@ -0,0 +1,31 @@ +// +build linux + +package mount + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/system" + "io/ioutil" + "os" + "path/filepath" + "syscall" +) + +func PivotRoot(rootfs string) error { + pivotDir, err := ioutil.TempDir(rootfs, ".pivot_root") + if err != nil { + return fmt.Errorf("can't create pivot_root dir %s", pivotDir, err) + } + if err := system.Pivotroot(rootfs, pivotDir); err != nil { + return fmt.Errorf("pivot_root %s", err) + } + if err := system.Chdir("/"); err != nil { + return fmt.Errorf("chdir / %s", err) + } + // path to pivot dir now changed, update + pivotDir = filepath.Join("/", filepath.Base(pivotDir)) + if err := system.Unmount(pivotDir, syscall.MNT_DETACH); err != nil { + return fmt.Errorf("unmount pivot_root dir %s", err) + } + return os.Remove(pivotDir) +} diff --git a/libcontainer/mount/ptmx.go b/libcontainer/mount/ptmx.go new file mode 100644 index 0000000..f6ca534 --- /dev/null +++ b/libcontainer/mount/ptmx.go @@ -0,0 +1,26 @@ +// +build linux + +package mount + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/libcontainer/console" + "os" + "path/filepath" +) + +func SetupPtmx(rootfs, consolePath, mountLabel string) error { + ptmx := filepath.Join(rootfs, "dev/ptmx") + if err := os.Remove(ptmx); err != nil && !os.IsNotExist(err) { + return err + } + if err := os.Symlink("pts/ptmx", ptmx); err != nil { + return fmt.Errorf("symlink dev ptmx %s", err) + } + if consolePath != "" { + if err := console.Setup(rootfs, consolePath, mountLabel); err != nil { + return err + } + } + return nil +} diff --git a/libcontainer/mount/readonly.go b/libcontainer/mount/readonly.go new file mode 100644 index 0000000..0658358 --- /dev/null +++ b/libcontainer/mount/readonly.go @@ -0,0 +1,12 @@ +// +build linux + +package mount + +import ( + "github.com/dotcloud/docker/pkg/system" + "syscall" +) + +func SetReadonly() error { + return system.Mount("/", "/", "bind", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC, "") +} diff --git a/libcontainer/mount/remount.go b/libcontainer/mount/remount.go new file mode 100644 index 0000000..3e00509 --- /dev/null +++ b/libcontainer/mount/remount.go @@ -0,0 +1,31 @@ +// +build linux + +package mount + +import ( + "github.com/dotcloud/docker/pkg/system" + "syscall" +) + +func RemountProc() error { + if err := system.Unmount("/proc", syscall.MNT_DETACH); err != nil { + return err + } + if err := system.Mount("proc", "/proc", "proc", uintptr(defaultMountFlags), ""); err != nil { + return err + } + return nil +} + +func RemountSys() error { + if err := system.Unmount("/sys", syscall.MNT_DETACH); err != nil { + if err != syscall.EINVAL { + return err + } + } else { + if err := system.Mount("sysfs", "/sys", "sysfs", uintptr(defaultMountFlags), ""); err != nil { + return err + } + } + return nil +} diff --git a/libcontainer/nsinit/execin.go b/libcontainer/nsinit/execin.go index 9017af0..b798810 100644 --- a/libcontainer/nsinit/execin.go +++ b/libcontainer/nsinit/execin.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/mount" "github.com/dotcloud/docker/pkg/system" "os" "path/filepath" @@ -63,10 +64,10 @@ func (ns *linuxNs) ExecIn(container *libcontainer.Container, nspid int, args []s if err := system.Unshare(syscall.CLONE_NEWNS); err != nil { return -1, err } - if err := remountProc(); err != nil { + if err := mount.RemountProc(); err != nil { return -1, fmt.Errorf("remount proc %s", err) } - if err := remountSys(); err != nil { + if err := mount.RemountSys(); err != nil { return -1, fmt.Errorf("remount sys %s", err) } goto dropAndExec diff --git a/libcontainer/nsinit/init.go b/libcontainer/nsinit/init.go index fb3a895..6e6b0e5 100644 --- a/libcontainer/nsinit/init.go +++ b/libcontainer/nsinit/init.go @@ -12,6 +12,7 @@ import ( "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/capabilities" + "github.com/dotcloud/docker/pkg/libcontainer/mount" "github.com/dotcloud/docker/pkg/libcontainer/network" "github.com/dotcloud/docker/pkg/libcontainer/security/apparmor" "github.com/dotcloud/docker/pkg/libcontainer/utils" @@ -61,7 +62,7 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol label.Init() ns.logger.Println("setup mount namespace") - if err := setupNewMountNamespace(rootfs, console, container); err != nil { + if err := mount.InitializeMountNamespace(rootfs, console, container); err != nil { return fmt.Errorf("setup mount namespace %s", err) } if err := system.Sethostname(container.Hostname); err != nil { From 824ee83816e75e735e2ebca5c8677a22f50360f6 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 15:15:28 +0000 Subject: [PATCH 07/14] Move rest of console functions to pkg Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- libcontainer/console/console.go | 14 ++++++++++++++ libcontainer/nsinit/init.go | 34 ++++++++------------------------- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/libcontainer/console/console.go b/libcontainer/console/console.go index deee544..05cd08a 100644 --- a/libcontainer/console/console.go +++ b/libcontainer/console/console.go @@ -44,3 +44,17 @@ func Setup(rootfs, consolePath, mountLabel string) error { } return nil } + +func OpenAndDup(consolePath string) error { + slave, err := system.OpenTerminal(consolePath, syscall.O_RDWR) + if err != nil { + return fmt.Errorf("open terminal %s", err) + } + if err := system.Dup2(slave.Fd(), 0); err != nil { + return err + } + if err := system.Dup2(slave.Fd(), 1); err != nil { + return err + } + return system.Dup2(slave.Fd(), 2) +} diff --git a/libcontainer/nsinit/init.go b/libcontainer/nsinit/init.go index 6e6b0e5..9aac9a4 100644 --- a/libcontainer/nsinit/init.go +++ b/libcontainer/nsinit/init.go @@ -12,6 +12,7 @@ import ( "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/capabilities" + "github.com/dotcloud/docker/pkg/libcontainer/console" "github.com/dotcloud/docker/pkg/libcontainer/mount" "github.com/dotcloud/docker/pkg/libcontainer/network" "github.com/dotcloud/docker/pkg/libcontainer/security/apparmor" @@ -22,7 +23,7 @@ import ( // 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. -func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, console string, syncPipe *SyncPipe, args []string) error { +func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consolePath string, syncPipe *SyncPipe, args []string) error { rootfs, err := utils.ResolveRootfs(uncleanRootfs) if err != nil { return err @@ -38,20 +39,16 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol ns.logger.Println("received context from parent") syncPipe.Close() - if console != "" { - ns.logger.Printf("setting up %s as console\n", console) - slave, err := system.OpenTerminal(console, syscall.O_RDWR) - if err != nil { - return fmt.Errorf("open terminal %s", err) - } - if err := dupSlave(slave); err != nil { - return fmt.Errorf("dup2 slave %s", err) + if consolePath != "" { + ns.logger.Printf("setting up %s as console\n", consolePath) + if err := console.OpenAndDup(consolePath); err != nil { + return err } } if _, err := system.Setsid(); err != nil { return fmt.Errorf("setsid %s", err) } - if console != "" { + if consolePath != "" { if err := system.Setctty(); err != nil { return fmt.Errorf("setctty %s", err) } @@ -62,7 +59,7 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol label.Init() ns.logger.Println("setup mount namespace") - if err := mount.InitializeMountNamespace(rootfs, console, container); err != nil { + if err := mount.InitializeMountNamespace(rootfs, consolePath, container); err != nil { return fmt.Errorf("setup mount namespace %s", err) } if err := system.Sethostname(container.Hostname); err != nil { @@ -116,21 +113,6 @@ func setupUser(container *libcontainer.Container) error { return nil } -// dupSlave dup2 the pty slave's fd into stdout and stdin and ensures that -// the slave's fd is 0, or stdin -func dupSlave(slave *os.File) error { - if err := system.Dup2(slave.Fd(), 0); err != nil { - return err - } - if err := system.Dup2(slave.Fd(), 1); err != nil { - return err - } - if err := system.Dup2(slave.Fd(), 2); err != nil { - return err - } - return nil -} - // setupVethNetwork uses the Network config if it is not nil to initialize // the new veth interface inside the container for use by changing the name to eth0 // setting the MTU and IP address along with the default gateway From 454751e768c1171629d6ef9346f3e39ac095d917 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 15:26:23 +0000 Subject: [PATCH 08/14] Move mounts into types.go Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- libcontainer/container.go | 20 -------------------- libcontainer/mount/init.go | 11 +++++------ libcontainer/types.go | 20 ++++++++++++++++++++ 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/libcontainer/container.go b/libcontainer/container.go index 1e032c0..ddcc6ca 100644 --- a/libcontainer/container.go +++ b/libcontainer/container.go @@ -37,23 +37,3 @@ type Network struct { Gateway string `json:"gateway,omitempty"` Mtu int `json:"mtu,omitempty"` } - -type Mounts []Mount - -func (s Mounts) OfType(t string) Mounts { - out := Mounts{} - for _, m := range s { - if m.Type == t { - out = append(out, m) - } - } - return out -} - -type Mount struct { - Type string `json:"type,omitempty"` - Source string `json:"source,omitempty"` // Source path, in the host namespace - Destination string `json:"destination,omitempty"` // Destination path, in the container - Writable bool `json:"writable,omitempty"` - Private bool `json:"private,omitempty"` -} diff --git a/libcontainer/mount/init.go b/libcontainer/mount/init.go index 2a5e47a..06b2c82 100644 --- a/libcontainer/mount/init.go +++ b/libcontainer/mount/init.go @@ -122,12 +122,9 @@ func setupBindmounts(rootfs string, bindMounts libcontainer.Mounts) error { return nil } +// TODO: this is crappy right now and should be cleaned up with a better way of handling system and +// standard bind mounts allowing them to be more dymanic func newSystemMounts(rootfs, mountLabel string, mounts libcontainer.Mounts) []mount { - devMounts := []mount{ - {source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)}, - {source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)}, - } - systemMounts := []mount{ {source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags}, } @@ -135,7 +132,9 @@ func newSystemMounts(rootfs, mountLabel string, mounts libcontainer.Mounts) []mo if len(mounts.OfType("devtmpfs")) == 1 { systemMounts = append(systemMounts, mount{source: "tmpfs", path: filepath.Join(rootfs, "dev"), device: "tmpfs", flags: syscall.MS_NOSUID | syscall.MS_STRICTATIME, data: "mode=755"}) } - systemMounts = append(systemMounts, devMounts...) + systemMounts = append(systemMounts, + mount{source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)}, + mount{source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)}) if len(mounts.OfType("sysfs")) == 1 { systemMounts = append(systemMounts, mount{source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags}) diff --git a/libcontainer/types.go b/libcontainer/types.go index d4818c3..ade3c32 100644 --- a/libcontainer/types.go +++ b/libcontainer/types.go @@ -11,6 +11,26 @@ var ( ErrUnsupported = errors.New("Unsupported method") ) +type Mounts []Mount + +func (s Mounts) OfType(t string) Mounts { + out := Mounts{} + for _, m := range s { + if m.Type == t { + out = append(out, m) + } + } + return out +} + +type Mount struct { + Type string `json:"type,omitempty"` + Source string `json:"source,omitempty"` // Source path, in the host namespace + Destination string `json:"destination,omitempty"` // Destination path, in the container + Writable bool `json:"writable,omitempty"` + Private bool `json:"private,omitempty"` +} + // namespaceList is used to convert the libcontainer types // into the names of the files located in /proc//ns/* for // each namespace From e40bde54a5f7856825379007a44531207bc4300b Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 15:28:56 +0000 Subject: [PATCH 09/14] Move capabilities into security pkg Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- libcontainer/nsinit/init.go | 2 +- libcontainer/{ => security}/capabilities/capabilities.go | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename libcontainer/{ => security}/capabilities/capabilities.go (100%) diff --git a/libcontainer/nsinit/init.go b/libcontainer/nsinit/init.go index 9aac9a4..22fdfbe 100644 --- a/libcontainer/nsinit/init.go +++ b/libcontainer/nsinit/init.go @@ -11,11 +11,11 @@ import ( "github.com/dotcloud/docker/pkg/apparmor" "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/libcontainer/capabilities" "github.com/dotcloud/docker/pkg/libcontainer/console" "github.com/dotcloud/docker/pkg/libcontainer/mount" "github.com/dotcloud/docker/pkg/libcontainer/network" "github.com/dotcloud/docker/pkg/libcontainer/security/apparmor" + "github.com/dotcloud/docker/pkg/libcontainer/security/capabilities" "github.com/dotcloud/docker/pkg/libcontainer/utils" "github.com/dotcloud/docker/pkg/system" "github.com/dotcloud/docker/pkg/user" diff --git a/libcontainer/capabilities/capabilities.go b/libcontainer/security/capabilities/capabilities.go similarity index 100% rename from libcontainer/capabilities/capabilities.go rename to libcontainer/security/capabilities/capabilities.go From bd7c140c019e126b7f8005a9359c02e48e4be17e Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 15:44:11 +0000 Subject: [PATCH 10/14] Update container.json and readme Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- libcontainer/README.md | 193 +++++++++++++++++++++++++----------- libcontainer/container.json | 192 ++++++++++++++++++++++++++--------- 2 files changed, 277 insertions(+), 108 deletions(-) diff --git a/libcontainer/README.md b/libcontainer/README.md index d6d0fba..31031b2 100644 --- a/libcontainer/README.md +++ b/libcontainer/README.md @@ -16,76 +16,149 @@ process are specified in this file. The configuration is used for each process Sample `container.json` file: ```json { + "mounts" : [ + { + "type" : "devtmpfs" + } + ], + "tty" : true, + "environment" : [ + "HOME=/", + "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin", + "container=docker", + "TERM=xterm-256color" + ], "hostname" : "koye", + "cgroups" : { + "parent" : "docker", + "name" : "docker-koye" + }, + "capabilities_mask" : [ + { + "value" : 8, + "key" : "SETPCAP", + "enabled" : false + }, + { + "enabled" : false, + "value" : 16, + "key" : "SYS_MODULE" + }, + { + "value" : 17, + "key" : "SYS_RAWIO", + "enabled" : false + }, + { + "key" : "SYS_PACCT", + "value" : 20, + "enabled" : false + }, + { + "value" : 21, + "key" : "SYS_ADMIN", + "enabled" : false + }, + { + "value" : 23, + "key" : "SYS_NICE", + "enabled" : false + }, + { + "value" : 24, + "key" : "SYS_RESOURCE", + "enabled" : false + }, + { + "key" : "SYS_TIME", + "value" : 25, + "enabled" : false + }, + { + "enabled" : false, + "value" : 26, + "key" : "SYS_TTY_CONFIG" + }, + { + "key" : "AUDIT_WRITE", + "value" : 29, + "enabled" : false + }, + { + "value" : 30, + "key" : "AUDIT_CONTROL", + "enabled" : false + }, + { + "enabled" : false, + "key" : "MAC_OVERRIDE", + "value" : 32 + }, + { + "enabled" : false, + "key" : "MAC_ADMIN", + "value" : 33 + }, + { + "key" : "NET_ADMIN", + "value" : 12, + "enabled" : false + }, + { + "value" : 27, + "key" : "MKNOD", + "enabled" : true + } + ], "networks" : [ { - "gateway" : "172.17.42.1", + "mtu" : 1500, + "address" : "127.0.0.1/0", + "type" : "loopback", + "gateway" : "localhost" + }, + { + "mtu" : 1500, + "address" : "172.17.42.2/16", + "type" : "veth", "context" : { "bridge" : "docker0", "prefix" : "veth" }, - "address" : "172.17.0.2/16", - "type" : "veth", - "mtu" : 1500 - } - ], - "cgroups" : { - "parent" : "docker", - "name" : "11bb30683fb0bdd57fab4d3a8238877f1e4395a2cfc7320ea359f7a02c1a5620" - }, - "tty" : true, - "environment" : [ - "HOME=/", - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "HOSTNAME=11bb30683fb0", - "TERM=xterm" - ], - "capabilities_mask" : [ - "SETPCAP", - "SYS_MODULE", - "SYS_RAWIO", - "SYS_PACCT", - "SYS_ADMIN", - "SYS_NICE", - "SYS_RESOURCE", - "SYS_TIME", - "SYS_TTY_CONFIG", - "MKNOD", - "AUDIT_WRITE", - "AUDIT_CONTROL", - "MAC_OVERRIDE", - "MAC_ADMIN", - "NET_ADMIN" - ], - "context" : { - "apparmor_profile" : "docker-default" - }, - "mounts" : [ - { - "source" : "/var/lib/docker/containers/11bb30683fb0bdd57fab4d3a8238877f1e4395a2cfc7320ea359f7a02c1a5620/resolv.conf", - "writable" : false, - "destination" : "/etc/resolv.conf", - "private" : true - }, - { - "source" : "/var/lib/docker/containers/11bb30683fb0bdd57fab4d3a8238877f1e4395a2cfc7320ea359f7a02c1a5620/hostname", - "writable" : false, - "destination" : "/etc/hostname", - "private" : true - }, - { - "source" : "/var/lib/docker/containers/11bb30683fb0bdd57fab4d3a8238877f1e4395a2cfc7320ea359f7a02c1a5620/hosts", - "writable" : false, - "destination" : "/etc/hosts", - "private" : true + "gateway" : "172.17.42.1" } ], "namespaces" : [ - "NEWNS", - "NEWUTS", - "NEWIPC", - "NEWPID", - "NEWNET" + { + "key" : "NEWNS", + "value" : 131072, + "enabled" : true, + "file" : "mnt" + }, + { + "key" : "NEWUTS", + "value" : 67108864, + "enabled" : true, + "file" : "uts" + }, + { + "enabled" : true, + "file" : "ipc", + "key" : "NEWIPC", + "value" : 134217728 + }, + { + "file" : "pid", + "enabled" : true, + "value" : 536870912, + "key" : "NEWPID" + }, + { + "enabled" : true, + "file" : "net", + "key" : "NEWNET", + "value" : 1073741824 + } ] } ``` diff --git a/libcontainer/container.json b/libcontainer/container.json index f045315..f15a49a 100644 --- a/libcontainer/container.json +++ b/libcontainer/container.json @@ -1,50 +1,146 @@ { - "hostname": "koye", - "tty": true, - "environment": [ - "HOME=/", - "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin", - "container=docker", - "TERM=xterm-256color" - ], - "namespaces": [ - "NEWIPC", - "NEWNS", - "NEWPID", - "NEWUTS", - "NEWNET" - ], - "capabilities_mask": [ - "SETPCAP", - "SYS_MODULE", - "SYS_RAWIO", - "SYS_PACCT", - "SYS_ADMIN", - "SYS_NICE", - "SYS_RESOURCE", - "SYS_TIME", - "SYS_TTY_CONFIG", - "MKNOD", - "AUDIT_WRITE", - "AUDIT_CONTROL", - "MAC_OVERRIDE", - "MAC_ADMIN", - "NET_ADMIN" - ], - "networks": [{ - "type": "veth", - "context": { - "bridge": "docker0", - "prefix": "dock" - }, - "address": "172.17.0.100/16", - "gateway": "172.17.42.1", - "mtu": 1500 - } - ], - "cgroups": { - "name": "docker-koye", - "parent": "docker", - "memory": 5248000 - } + "mounts" : [ + { + "type" : "devtmpfs" + } + ], + "tty" : true, + "environment" : [ + "HOME=/", + "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin", + "container=docker", + "TERM=xterm-256color" + ], + "hostname" : "koye", + "cgroups" : { + "parent" : "docker", + "name" : "docker-koye" + }, + "capabilities_mask" : [ + { + "value" : 8, + "key" : "SETPCAP", + "enabled" : false + }, + { + "enabled" : false, + "value" : 16, + "key" : "SYS_MODULE" + }, + { + "value" : 17, + "key" : "SYS_RAWIO", + "enabled" : false + }, + { + "key" : "SYS_PACCT", + "value" : 20, + "enabled" : false + }, + { + "value" : 21, + "key" : "SYS_ADMIN", + "enabled" : false + }, + { + "value" : 23, + "key" : "SYS_NICE", + "enabled" : false + }, + { + "value" : 24, + "key" : "SYS_RESOURCE", + "enabled" : false + }, + { + "key" : "SYS_TIME", + "value" : 25, + "enabled" : false + }, + { + "enabled" : false, + "value" : 26, + "key" : "SYS_TTY_CONFIG" + }, + { + "key" : "AUDIT_WRITE", + "value" : 29, + "enabled" : false + }, + { + "value" : 30, + "key" : "AUDIT_CONTROL", + "enabled" : false + }, + { + "enabled" : false, + "key" : "MAC_OVERRIDE", + "value" : 32 + }, + { + "enabled" : false, + "key" : "MAC_ADMIN", + "value" : 33 + }, + { + "key" : "NET_ADMIN", + "value" : 12, + "enabled" : false + }, + { + "value" : 27, + "key" : "MKNOD", + "enabled" : true + } + ], + "networks" : [ + { + "mtu" : 1500, + "address" : "127.0.0.1/0", + "type" : "loopback", + "gateway" : "localhost" + }, + { + "mtu" : 1500, + "address" : "172.17.42.2/16", + "type" : "veth", + "context" : { + "bridge" : "docker0", + "prefix" : "veth" + }, + "gateway" : "172.17.42.1" + } + ], + "namespaces" : [ + { + "key" : "NEWNS", + "value" : 131072, + "enabled" : true, + "file" : "mnt" + }, + { + "key" : "NEWUTS", + "value" : 67108864, + "enabled" : true, + "file" : "uts" + }, + { + "enabled" : true, + "file" : "ipc", + "key" : "NEWIPC", + "value" : 134217728 + }, + { + "file" : "pid", + "enabled" : true, + "value" : 536870912, + "key" : "NEWPID" + }, + { + "enabled" : true, + "file" : "net", + "key" : "NEWNET", + "value" : 1073741824 + } + ] } From 2ecea22c8c35faacbb9700fa345031ab35bd0fb7 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 21 Apr 2014 12:07:07 -0700 Subject: [PATCH 11/14] Update init for new apparmor import path Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- libcontainer/nsinit/init.go | 1 - 1 file changed, 1 deletion(-) diff --git a/libcontainer/nsinit/init.go b/libcontainer/nsinit/init.go index 22fdfbe..67095fd 100644 --- a/libcontainer/nsinit/init.go +++ b/libcontainer/nsinit/init.go @@ -14,7 +14,6 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer/console" "github.com/dotcloud/docker/pkg/libcontainer/mount" "github.com/dotcloud/docker/pkg/libcontainer/network" - "github.com/dotcloud/docker/pkg/libcontainer/security/apparmor" "github.com/dotcloud/docker/pkg/libcontainer/security/capabilities" "github.com/dotcloud/docker/pkg/libcontainer/utils" "github.com/dotcloud/docker/pkg/system" From 76a06effefe12cc086e1b95cb5c7b2340bbf1c44 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 23 Apr 2014 18:12:07 -0700 Subject: [PATCH 12/14] Ignore isnot exists errors for proc paths Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- libcontainer/security/restrict/restrict.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libcontainer/security/restrict/restrict.go b/libcontainer/security/restrict/restrict.go index d5c1dbb..291d6ca 100644 --- a/libcontainer/security/restrict/restrict.go +++ b/libcontainer/security/restrict/restrict.go @@ -2,9 +2,11 @@ package restrict import ( "fmt" - "github.com/dotcloud/docker/pkg/system" + "os" "path/filepath" "syscall" + + "github.com/dotcloud/docker/pkg/system" ) const flags = syscall.MS_BIND | syscall.MS_REC | syscall.MS_RDONLY @@ -36,6 +38,9 @@ func Restrict(rootfs, empty string) error { source = filepath.Join(rootfs, source) } if err := system.Mount(source, dest, "bind", flags, ""); err != nil { + if os.IsNotExist(err) { + continue + } return fmt.Errorf("unable to mount %s over %s %s", source, dest, err) } if err := system.Mount("", dest, "bind", flags|syscall.MS_REMOUNT, ""); err != nil { From 38d7599ca3a40f1d10d1a29acee3d1c7e94974c9 Mon Sep 17 00:00:00 2001 From: Victor Marmol Date: Thu, 24 Apr 2014 22:59:37 +0000 Subject: [PATCH 13/14] Add memory usage and max usage stats. Docker-DCO-1.1-Signed-off-by: Victor Marmol (github: vmarmol) --- cgroups/fs/memory.go | 22 +++++++++++-- cgroups/fs/utils.go | 11 +++++++ cgroups/fs/utils_test.go | 68 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 cgroups/fs/utils_test.go diff --git a/cgroups/fs/memory.go b/cgroups/fs/memory.go index 5315291..837640c 100644 --- a/cgroups/fs/memory.go +++ b/cgroups/fs/memory.go @@ -2,6 +2,7 @@ package fs import ( "bufio" + "fmt" "os" "path/filepath" "strconv" @@ -56,13 +57,14 @@ func (s *memoryGroup) Stats(d *data) (map[string]float64, error) { return nil, err } - f, err := os.Open(filepath.Join(path, "memory.stat")) + // Set stats from memory.stat. + statsFile, err := os.Open(filepath.Join(path, "memory.stat")) if err != nil { return nil, err } - defer f.Close() + defer statsFile.Close() - sc := bufio.NewScanner(f) + sc := bufio.NewScanner(statsFile) for sc.Scan() { t, v, err := getCgroupParamKeyValue(sc.Text()) if err != nil { @@ -70,5 +72,19 @@ func (s *memoryGroup) Stats(d *data) (map[string]float64, error) { } paramData[t] = v } + + // Set memory usage and max historical usage. + params := []string{ + "usage_in_bytes", + "max_usage_in_bytes", + } + for _, param := range params { + value, err := getCgroupParamFloat64(path, fmt.Sprintf("memory.%s", param)) + if err != nil { + return nil, err + } + paramData[param] = value + } + return paramData, nil } diff --git a/cgroups/fs/utils.go b/cgroups/fs/utils.go index f4c4846..8be65c9 100644 --- a/cgroups/fs/utils.go +++ b/cgroups/fs/utils.go @@ -3,6 +3,8 @@ package fs import ( "errors" "fmt" + "io/ioutil" + "path/filepath" "strconv" "strings" ) @@ -27,3 +29,12 @@ func getCgroupParamKeyValue(t string) (string, float64, error) { return "", 0.0, ErrNotValidFormat } } + +// Gets a single float64 value from the specified cgroup file. +func getCgroupParamFloat64(cgroupPath, cgroupFile string) (float64, error) { + contents, err := ioutil.ReadFile(filepath.Join(cgroupPath, cgroupFile)) + if err != nil { + return -1.0, err + } + return strconv.ParseFloat(strings.TrimSpace(string(contents)), 64) +} diff --git a/cgroups/fs/utils_test.go b/cgroups/fs/utils_test.go new file mode 100644 index 0000000..c8f1b01 --- /dev/null +++ b/cgroups/fs/utils_test.go @@ -0,0 +1,68 @@ +package fs + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" +) + +const ( + cgroupFile = "cgroup.file" + floatValue = 2048.0 + floatString = "2048" +) + +func TestGetCgroupParamsFloat64(t *testing.T) { + // Setup tempdir. + tempDir, err := ioutil.TempDir("", "cgroup_utils_test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tempDir) + tempFile := filepath.Join(tempDir, cgroupFile) + + // Success. + err = ioutil.WriteFile(tempFile, []byte(floatString), 0755) + if err != nil { + t.Fatal(err) + } + value, err := getCgroupParamFloat64(tempDir, cgroupFile) + if err != nil { + t.Fatal(err) + } else if value != floatValue { + t.Fatalf("Expected %f to equal %f", value, floatValue) + } + + // Success with new line. + err = ioutil.WriteFile(tempFile, []byte(floatString+"\n"), 0755) + if err != nil { + t.Fatal(err) + } + value, err = getCgroupParamFloat64(tempDir, cgroupFile) + if err != nil { + t.Fatal(err) + } else if value != floatValue { + t.Fatalf("Expected %f to equal %f", value, floatValue) + } + + // Not a float. + err = ioutil.WriteFile(tempFile, []byte("not-a-float"), 0755) + if err != nil { + t.Fatal(err) + } + _, err = getCgroupParamFloat64(tempDir, cgroupFile) + if err == nil { + t.Fatal("Expecting error, got none") + } + + // Unknown file. + err = os.Remove(tempFile) + if err != nil { + t.Fatal(err) + } + _, err = getCgroupParamFloat64(tempDir, cgroupFile) + if err == nil { + t.Fatal("Expecting error, got none") + } +} From a49cc7f252eb3751abd1ea46dc2be819667cbe9e Mon Sep 17 00:00:00 2001 From: Victor Marmol Date: Fri, 25 Apr 2014 17:44:40 +0000 Subject: [PATCH 14/14] Adding a test for blkio stats. Also adds a test utility we can use for other cgroup tests. Docker-DCO-1.1-Signed-off-by: Victor Marmol (github: vmarmol) --- cgroups/fs/blkio_test.go | 169 +++++++++++++++++++++++++++++++++++++++ cgroups/fs/test_util.go | 75 +++++++++++++++++ 2 files changed, 244 insertions(+) create mode 100644 cgroups/fs/blkio_test.go create mode 100644 cgroups/fs/test_util.go diff --git a/cgroups/fs/blkio_test.go b/cgroups/fs/blkio_test.go new file mode 100644 index 0000000..5279ac4 --- /dev/null +++ b/cgroups/fs/blkio_test.go @@ -0,0 +1,169 @@ +package fs + +import ( + "testing" +) + +const ( + sectorsRecursiveContents = `8:0 1024` + serviceBytesRecursiveContents = `8:0 Read 100 +8:0 Write 400 +8:0 Sync 200 +8:0 Async 300 +8:0 Total 500 +Total 500` + servicedRecursiveContents = `8:0 Read 10 +8:0 Write 40 +8:0 Sync 20 +8:0 Async 30 +8:0 Total 50 +Total 50` + queuedRecursiveContents = `8:0 Read 1 +8:0 Write 4 +8:0 Sync 2 +8:0 Async 3 +8:0 Total 5 +Total 5` +) + +func TestBlkioStats(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + }) + + blkio := &blkioGroup{} + stats, err := blkio.Stats(helper.CgroupData) + if err != nil { + t.Fatal(err) + } + + // Verify expected stats. + expectedStats := map[string]float64{ + "blkio.sectors_recursive:8:0": 1024.0, + + // Serviced bytes. + "io_service_bytes_recursive:8:0:Read": 100.0, + "io_service_bytes_recursive:8:0:Write": 400.0, + "io_service_bytes_recursive:8:0:Sync": 200.0, + "io_service_bytes_recursive:8:0:Async": 300.0, + "io_service_bytes_recursive:8:0:Total": 500.0, + + // Serviced requests. + "io_serviced_recursive:8:0:Read": 10.0, + "io_serviced_recursive:8:0:Write": 40.0, + "io_serviced_recursive:8:0:Sync": 20.0, + "io_serviced_recursive:8:0:Async": 30.0, + "io_serviced_recursive:8:0:Total": 50.0, + + // Queued requests. + "io_queued_recursive:8:0:Read": 1.0, + "io_queued_recursive:8:0:Write": 4.0, + "io_queued_recursive:8:0:Sync": 2.0, + "io_queued_recursive:8:0:Async": 3.0, + "io_queued_recursive:8:0:Total": 5.0, + } + expectStats(t, expectedStats, stats) +} + +func TestBlkioStatsNoSectorsFile(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + }) + + blkio := &blkioGroup{} + _, err := blkio.Stats(helper.CgroupData) + if err == nil { + t.Fatal("Expected to fail, but did not") + } +} + +func TestBlkioStatsNoServiceBytesFile(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + }) + + blkio := &blkioGroup{} + _, err := blkio.Stats(helper.CgroupData) + if err == nil { + t.Fatal("Expected to fail, but did not") + } +} + +func TestBlkioStatsNoServicedFile(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + }) + + blkio := &blkioGroup{} + _, err := blkio.Stats(helper.CgroupData) + if err == nil { + t.Fatal("Expected to fail, but did not") + } +} + +func TestBlkioStatsNoQueuedFile(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + }) + + blkio := &blkioGroup{} + _, err := blkio.Stats(helper.CgroupData) + if err == nil { + t.Fatal("Expected to fail, but did not") + } +} + +func TestBlkioStatsUnexpectedNumberOfFields(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": "8:0 Read 100 100", + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + }) + + blkio := &blkioGroup{} + _, err := blkio.Stats(helper.CgroupData) + if err == nil { + t.Fatal("Expected to fail, but did not") + } +} + +func TestBlkioStatsUnexpectedFieldType(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": "8:0 Read Write", + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + }) + + blkio := &blkioGroup{} + _, err := blkio.Stats(helper.CgroupData) + if err == nil { + t.Fatal("Expected to fail, but did not") + } +} diff --git a/cgroups/fs/test_util.go b/cgroups/fs/test_util.go new file mode 100644 index 0000000..11b90b2 --- /dev/null +++ b/cgroups/fs/test_util.go @@ -0,0 +1,75 @@ +/* +Utility for testing cgroup operations. + +Creates a mock of the cgroup filesystem for the duration of the test. +*/ +package fs + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "testing" +) + +type cgroupTestUtil struct { + // data to use in tests. + CgroupData *data + + // Path to the mock cgroup directory. + CgroupPath string + + // Temporary directory to store mock cgroup filesystem. + tempDir string + t *testing.T +} + +// Creates a new test util for the specified subsystem +func NewCgroupTestUtil(subsystem string, t *testing.T) *cgroupTestUtil { + d := &data{} + tempDir, err := ioutil.TempDir("", fmt.Sprintf("%s_cgroup_test", subsystem)) + if err != nil { + t.Fatal(err) + } + d.root = tempDir + testCgroupPath, err := d.path(subsystem) + if err != nil { + t.Fatal(err) + } + + // Ensure the full mock cgroup path exists. + err = os.MkdirAll(testCgroupPath, 0755) + if err != nil { + t.Fatal(err) + } + return &cgroupTestUtil{CgroupData: d, CgroupPath: testCgroupPath, tempDir: tempDir, t: t} +} + +func (c *cgroupTestUtil) cleanup() { + os.RemoveAll(c.tempDir) +} + +// Write the specified contents on the mock of the specified cgroup files. +func (c *cgroupTestUtil) writeFileContents(fileContents map[string]string) { + for file, contents := range fileContents { + err := writeFile(c.CgroupPath, file, contents) + if err != nil { + c.t.Fatal(err) + } + } +} + +// Expect the specified stats. +func expectStats(t *testing.T, expected, actual map[string]float64) { + for stat, expectedValue := range expected { + actualValue, ok := actual[stat] + if !ok { + log.Printf("Expected stat %s to exist: %s", stat, actual) + t.Fail() + } else if actualValue != expectedValue { + log.Printf("Expected stats %s to have value %f but had %f instead", stat, expectedValue, actualValue) + t.Fail() + } + } +}