From 45d7dcfea276841cce782feced3a2eb3eab01208 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 17 Dec 2013 14:04:37 -0800 Subject: [PATCH] Handle external mounts outside of lxc --- container.go | 46 ++++++++++++++++++-- graphdriver/driver.go | 97 +++++++++++++++++++++++++++++++++++++++++++ lxc_template.go | 32 ++++---------- runtime.go | 67 ++++++++++++++++++++++++++++-- 4 files changed, 209 insertions(+), 33 deletions(-) diff --git a/container.go b/container.go index 206f2dfe63..8cc3ecf972 100644 --- a/container.go +++ b/container.go @@ -48,7 +48,6 @@ type Container struct { network *NetworkInterface NetworkSettings *NetworkSettings - SysInitPath string ResolvConfPath string HostnamePath string HostsPath string @@ -297,7 +296,11 @@ func (container *Container) generateEnvConfig(env []string) error { if err != nil { return err } - ioutil.WriteFile(container.EnvConfigPath(), data, 0600) + p, err := container.EnvConfigPath() + if err != nil { + return err + } + ioutil.WriteFile(p, data, 0600) return nil } @@ -681,6 +684,17 @@ func (container *Container) Start() (err error) { } } + mounts, err := runtime.getMounts(container) + if err != nil { + return err + } + + for _, m := range mounts { + if err := m.Mount(container.RootfsPath()); err != nil { + return err + } + } + container.cmd = exec.Command(params[0], params[1:]...) // Setup logging of stdout and stderr to disk @@ -1358,6 +1372,18 @@ func (container *Container) GetImage() (*Image, error) { } func (container *Container) Unmount() error { + mounts, err := container.runtime.getMounts(container) + if err != nil { + return err + } + for _, m := range mounts { + if lastError := m.Unmount(container.RootfsPath()); lastError != nil { + err = lastError + } + } + if err != nil { + return err + } return container.runtime.Unmount(container) } @@ -1377,8 +1403,20 @@ func (container *Container) jsonPath() string { return path.Join(container.root, "config.json") } -func (container *Container) EnvConfigPath() string { - return path.Join(container.root, "config.env") +func (container *Container) EnvConfigPath() (string, error) { + p := path.Join(container.root, "config.env") + if _, err := os.Stat(p); err != nil { + if os.IsNotExist(err) { + f, err := os.Create(p) + if err != nil { + return "", err + } + f.Close() + } else { + return "", err + } + } + return p, nil } func (container *Container) lxcConfigPath() string { diff --git a/graphdriver/driver.go b/graphdriver/driver.go index 1d5995dffc..86160c9023 100644 --- a/graphdriver/driver.go +++ b/graphdriver/driver.go @@ -6,6 +6,8 @@ import ( "github.com/dotcloud/docker/utils" "os" "path" + "strings" + "syscall" ) type InitFunc func(root string) (Driver, error) @@ -31,6 +33,13 @@ type Differ interface { DiffSize(id string) (bytes int64, err error) } +type Mount struct { + Device string + Target string + Type string + Options string +} + var ( DefaultDriver string // All registred drivers @@ -88,3 +97,91 @@ func New(root string) (driver Driver, err error) { } return nil, err } + +func (m *Mount) Mount(root string) error { + var ( + flag int + data []string + target = path.Join(root, m.Target) + ) + + if mounted, err := Mounted(target); err != nil || mounted { + return err + } + + flags := map[string]struct { + clear bool + flag int + }{ + "defaults": {false, 0}, + "ro": {false, syscall.MS_RDONLY}, + "rw": {true, syscall.MS_RDONLY}, + "suid": {true, syscall.MS_NOSUID}, + "nosuid": {false, syscall.MS_NOSUID}, + "dev": {true, syscall.MS_NODEV}, + "nodev": {false, syscall.MS_NODEV}, + "exec": {true, syscall.MS_NOEXEC}, + "noexec": {false, syscall.MS_NOEXEC}, + "sync": {false, syscall.MS_SYNCHRONOUS}, + "async": {true, syscall.MS_SYNCHRONOUS}, + "dirsync": {false, syscall.MS_DIRSYNC}, + "remount": {false, syscall.MS_REMOUNT}, + "mand": {false, syscall.MS_MANDLOCK}, + "nomand": {true, syscall.MS_MANDLOCK}, + "atime": {true, syscall.MS_NOATIME}, + "noatime": {false, syscall.MS_NOATIME}, + "diratime": {true, syscall.MS_NODIRATIME}, + "nodiratime": {false, syscall.MS_NODIRATIME}, + "bind": {false, syscall.MS_BIND}, + "rbind": {false, syscall.MS_BIND | syscall.MS_REC}, + "relatime": {false, syscall.MS_RELATIME}, + "norelatime": {true, syscall.MS_RELATIME}, + "strictatime": {false, syscall.MS_STRICTATIME}, + "nostrictatime": {true, syscall.MS_STRICTATIME}, + } + + for _, o := range strings.Split(m.Options, ",") { + // If the option does not exist in the flags table then it is a + // data value for a specific fs type + if f, exists := flags[o]; exists { + if f.clear { + flag &= ^f.flag + } else { + flag |= f.flag + } + } else { + data = append(data, o) + } + } + + if err := syscall.Mount(m.Device, target, m.Type, uintptr(flag), strings.Join(data, ",")); err != nil { + panic(err) + } + return nil +} + +func (m *Mount) Unmount(root string) error { + target := path.Join(root, m.Target) + if mounted, err := Mounted(target); err != nil || !mounted { + return err + } + return syscall.Unmount(target, 0) +} + +func Mounted(mountpoint string) (bool, error) { + mntpoint, err := os.Stat(mountpoint) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err + } + parent, err := os.Stat(path.Join(mountpoint, "..")) + if err != nil { + return false, err + } + mntpointSt := mntpoint.Sys().(*syscall.Stat_t) + parentSt := parent.Sys().(*syscall.Stat_t) + + return mntpointSt.Dev != parentSt.Dev, nil +} diff --git a/lxc_template.go b/lxc_template.go index ba5a8d5b82..c0dc4f39a9 100644 --- a/lxc_template.go +++ b/lxc_template.go @@ -21,12 +21,6 @@ lxc.network.mtu = 1500 {{$ROOTFS := .RootfsPath}} lxc.rootfs = {{$ROOTFS}} -{{if and .HostnamePath .HostsPath}} -# enable domain name support -lxc.mount.entry = {{escapeFstabSpaces .HostnamePath}} {{escapeFstabSpaces $ROOTFS}}/etc/hostname none bind,ro 0 0 -lxc.mount.entry = {{escapeFstabSpaces .HostsPath}} {{escapeFstabSpaces $ROOTFS}}/etc/hosts none bind,ro 0 0 -{{end}} - # use a dedicated pts for the container (and limit the number of pseudo terminal # available) lxc.pts = 1024 @@ -74,32 +68,20 @@ lxc.cgroup.devices.allow = c 10:200 rwm # standard mount point # Use mnt.putold as per https://bugs.launchpad.net/ubuntu/+source/lxc/+bug/986385 lxc.pivotdir = lxc_putold + +# NOTICE: These mounts must be applied within the namespace + # WARNING: procfs is a known attack vector and should probably be disabled # if your userspace allows it. eg. see http://blog.zx2c4.com/749 lxc.mount.entry = proc {{escapeFstabSpaces $ROOTFS}}/proc proc nosuid,nodev,noexec 0 0 -# WARNING: sysfs is a known attack vector and should probably be disabled -# if your userspace allows it. eg. see http://bit.ly/T9CkqJ + +# WARNING: sysfs is a known attack vector and should probably be disabled +# if your userspace allows it. eg. see http://bit.ly/T9CkqJ lxc.mount.entry = sysfs {{escapeFstabSpaces $ROOTFS}}/sys sysfs nosuid,nodev,noexec 0 0 + lxc.mount.entry = devpts {{escapeFstabSpaces $ROOTFS}}/dev/pts devpts newinstance,ptmxmode=0666,nosuid,noexec 0 0 -#lxc.mount.entry = varrun {{escapeFstabSpaces $ROOTFS}}/var/run tmpfs mode=755,size=4096k,nosuid,nodev,noexec 0 0 -#lxc.mount.entry = varlock {{escapeFstabSpaces $ROOTFS}}/var/lock tmpfs size=1024k,nosuid,nodev,noexec 0 0 lxc.mount.entry = shm {{escapeFstabSpaces $ROOTFS}}/dev/shm tmpfs size=65536k,nosuid,nodev,noexec 0 0 -# Inject dockerinit -lxc.mount.entry = {{escapeFstabSpaces .SysInitPath}} {{escapeFstabSpaces $ROOTFS}}/.dockerinit none bind,ro 0 0 - -# Inject env -lxc.mount.entry = {{escapeFstabSpaces .EnvConfigPath}} {{escapeFstabSpaces $ROOTFS}}/.dockerenv none bind,ro 0 0 - -# In order to get a working DNS environment, mount bind (ro) the host's /etc/resolv.conf into the container -lxc.mount.entry = {{escapeFstabSpaces .ResolvConfPath}} {{escapeFstabSpaces $ROOTFS}}/etc/resolv.conf none bind,ro 0 0 -{{if .Volumes}} -{{ $rw := .VolumesRW }} -{{range $virtualPath, $realPath := .Volumes}} -lxc.mount.entry = {{escapeFstabSpaces $realPath}} {{escapeFstabSpaces $ROOTFS}}/{{escapeFstabSpaces $virtualPath}} none bind,{{ if index $rw $virtualPath }}rw{{else}}ro{{end}} 0 0 -{{end}} -{{end}} - {{if (getHostConfig .).Privileged}} {{if (getCapabilities .).AppArmor}} lxc.aa_profile = unconfined diff --git a/runtime.go b/runtime.go index f942d0b740..a9e1e2a6fd 100644 --- a/runtime.go +++ b/runtime.go @@ -486,10 +486,8 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin hostConfig: &HostConfig{}, Image: img.ID, // Always use the resolved image id NetworkSettings: &NetworkSettings{}, - // FIXME: do we need to store this in the container? - SysInitPath: runtime.sysInitPath, - Name: name, - Driver: runtime.driver.String(), + Name: name, + Driver: runtime.driver.String(), } container.root = runtime.containerRoot(container.ID) // Step 1: create the container directory. @@ -790,6 +788,67 @@ func (runtime *Runtime) Close() error { return nil } +func (runtime *Runtime) getMounts(container *Container) ([]*graphdriver.Mount, error) { + // Generate additional bind mounts + envPath, err := container.EnvConfigPath() + if err != nil { + return nil, err + } + mounts := []*graphdriver.Mount{ + { + Device: runtime.sysInitPath, + Target: "/.dockerinit", + Type: "none", + Options: "bind,ro", + }, + { + Device: envPath, + Target: "/.dockerenv", + Type: "none", + Options: "bind,ro", + }, + // In order to get a working DNS environment, mount bind (ro) the host's /etc/resolv.conf into the container + { + Device: container.ResolvConfPath, + Target: "/etc/resolv.conf", + Type: "none", + Options: "bind,ro", + }, + } + + if container.HostnamePath != "" && container.HostsPath != "" { + mounts = append(mounts, + &graphdriver.Mount{ + Device: container.HostnamePath, + Target: "/etc/hostname", + Type: "none", + Options: "bind,ro", + }, + &graphdriver.Mount{ + Device: container.HostsPath, + Target: "/etc/hosts", + Type: "none", + Options: "bind,ro", + }) + } + + for r, v := range container.Volumes { + mountAs := "ro" + if container.VolumesRW[v] { + mountAs = "rw" + } + + mounts = append(mounts, + &graphdriver.Mount{ + Device: v, + Target: r, + Type: "none", + Options: fmt.Sprintf("bind,%s", mountAs), + }) + } + return mounts, nil +} + func (runtime *Runtime) Mount(container *Container) error { dir, err := runtime.driver.Get(container.ID) if err != nil {