diff --git a/container.go b/container.go index 8cc3ecf972..d440b6f6c4 100644 --- a/container.go +++ b/container.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/graphdriver" + "github.com/dotcloud/docker/mount" "github.com/dotcloud/docker/term" "github.com/dotcloud/docker/utils" "github.com/kr/pty" @@ -684,13 +685,41 @@ func (container *Container) Start() (err error) { } } - mounts, err := runtime.getMounts(container) + root := container.RootfsPath() + envPath, err := container.EnvConfigPath() if err != nil { return err } - for _, m := range mounts { - if err := m.Mount(container.RootfsPath()); err != nil { + // Mount docker specific files into the containers root fs + if err := mount.Mount(runtime.sysInitPath, path.Join(root, "/.dockerinit"), "none", "bind,ro"); err != nil { + return err + } + if err := mount.Mount(envPath, path.Join(root, "/.dockerenv"), "none", "bind,ro"); err != nil { + return err + } + if err := mount.Mount(container.ResolvConfPath, path.Join(root, "/etc/resolv.conf"), "none", "bind,ro"); err != nil { + return err + } + + if container.HostnamePath != "" && container.HostsPath != "" { + if err := mount.Mount(container.HostnamePath, path.Join(root, "/etc/hostname"), "none", "bind,ro"); err != nil { + return err + } + if err := mount.Mount(container.HostsPath, path.Join(root, "/etc/hosts"), "none", "bind,ro"); err != nil { + return err + } + } + + // Mount user specified volumes + + for r, v := range container.Volumes { + mountAs := "ro" + if container.VolumesRW[v] { + mountAs = "rw" + } + + if err := mount.Mount(v, path.Join(root, r), "none", fmt.Sprintf("bind,%s", mountAs)); err != nil { return err } } @@ -1372,12 +1401,26 @@ func (container *Container) GetImage() (*Image, error) { } func (container *Container) Unmount() error { - mounts, err := container.runtime.getMounts(container) - if err != nil { - return err + var ( + err error + root = container.RootfsPath() + mounts = []string{ + path.Join(root, "/.dockerinit"), + path.Join(root, "/.dockerenv"), + path.Join(root, "/etc/resolv.conf"), + } + ) + + if container.HostnamePath != "" && container.HostsPath != "" { + mounts = append(mounts, path.Join(root, "/etc/hostname"), path.Join(root, "/etc/hosts")) } + + for r := range container.Volumes { + mounts = append(mounts, path.Join(root, r)) + } + for _, m := range mounts { - if lastError := m.Unmount(container.RootfsPath()); lastError != nil { + if lastError := mount.Unmount(m); lastError != nil { err = lastError } } diff --git a/graphdriver/aufs/aufs.go b/graphdriver/aufs/aufs.go index 6b4b9024a9..8875cac6d9 100644 --- a/graphdriver/aufs/aufs.go +++ b/graphdriver/aufs/aufs.go @@ -25,6 +25,7 @@ import ( "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/graphdriver" + mountpk "github.com/dotcloud/docker/mount" "github.com/dotcloud/docker/utils" "os" "os/exec" @@ -295,7 +296,7 @@ func (a *Driver) unmount(id string) error { func (a *Driver) mounted(id string) (bool, error) { target := path.Join(a.rootPath(), "mnt", id) - return graphdriver.Mounted(target) + return mountpk.Mounted(target) } // During cleanup aufs needs to unmount all mountpoints diff --git a/graphdriver/driver.go b/graphdriver/driver.go index 7ca303cf12..1d5995dffc 100644 --- a/graphdriver/driver.go +++ b/graphdriver/driver.go @@ -1,19 +1,13 @@ package graphdriver import ( - "bufio" "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/utils" "os" "path" - "strings" - "syscall" - "time" ) -const mountinfoFormat = "%d %d %d:%d %s %s %s - %s %s %s" - type InitFunc func(root string) (Driver, error) type Driver interface { @@ -37,13 +31,6 @@ 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 @@ -101,137 +88,3 @@ func New(root string) (driver Driver, err error) { } return nil, err } - -func (m *Mount) Mount(root string) error { - target := path.Join(root, m.Target) - if mounted, err := Mounted(target); err != nil || mounted { - return err - } - - flag, data := parseOptions(m.Options) - if err := syscall.Mount(m.Device, target, m.Type, uintptr(flag), data); err != nil { - return err - } - return nil -} - -func parseOptions(options string) (int, string) { - var ( - flag int - data []string - ) - - 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(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) - } - } - return flag, strings.Join(data, ",") -} - -func (m *Mount) Unmount(root string) (err error) { - target := path.Join(root, m.Target) - if mounted, err := Mounted(target); err != nil || !mounted { - return err - } - - // Simple retry logic for unmount - for i := 0; i < 10; i++ { - if err = syscall.Unmount(target, 0); err == nil { - return nil - } - utils.Debugf("[Unmount] %s", err) - time.Sleep(100 * time.Millisecond) - } - return -} - -func Mounted(mountpoint string) (bool, error) { - entries, err := parseMountTable() - if err != nil { - return false, err - } - - // Search the table for the mountpoint - for _, e := range entries { - if e.mountpoint == mountpoint { - return true, nil - } - } - return false, nil -} - -// Represents one line from /proc/self/mountinfo -type procEntry struct { - id, parent, major, minor int - source, mountpoint, fstype, device string - vfsopts, opts string -} - -// Parse /proc/self/mountinfo because comparing Dev and ino does not work from bind mounts -// -// 180 20 0:2851 / /var/lib/docker/aufs/mnt/a22632d4ed3cb2438246064408f9f07734cbd331f50c81f1ca3dcbd78541ce83 rw,relatime - aufs none rw,si=e9663ac1adbdb4f8 -func parseMountTable() ([]*procEntry, error) { - f, err := os.Open("/proc/self/mountinfo") - if err != nil { - return nil, err - } - defer f.Close() - - s := bufio.NewScanner(f) - out := []*procEntry{} - for s.Scan() { - if err := s.Err(); err != nil { - return nil, err - } - - p := &procEntry{} - if _, err := fmt.Sscanf(s.Text(), mountinfoFormat, - &p.id, &p.parent, &p.major, &p.minor, - &p.source, &p.mountpoint, &p.vfsopts, &p.fstype, - &p.device, &p.opts); err != nil { - return nil, err - } - out = append(out, p) - } - return out, nil -} diff --git a/mount/MAINTAINERS b/mount/MAINTAINERS new file mode 100644 index 0000000000..1e998f8ac1 --- /dev/null +++ b/mount/MAINTAINERS @@ -0,0 +1 @@ +Michael Crosby (@crosbymichael) diff --git a/mount/flags_darwin.go b/mount/flags_darwin.go new file mode 100644 index 0000000000..e89d5e703a --- /dev/null +++ b/mount/flags_darwin.go @@ -0,0 +1,5 @@ +package mount + +func parseOptions(options string) (int, string) { + panic("Not implemented") +} diff --git a/mount/flags_linux.go b/mount/flags_linux.go new file mode 100644 index 0000000000..6f4c7acffa --- /dev/null +++ b/mount/flags_linux.go @@ -0,0 +1,61 @@ +package mount + +import ( + "strings" + "syscall" +) + +// Parse fstab type mount options into mount() flags +// and device specific data +func parseOptions(options string) (int, string) { + var ( + flag int + data []string + ) + + 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(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) + } + } + return flag, strings.Join(data, ",") +} diff --git a/mount/mount.go b/mount/mount.go new file mode 100644 index 0000000000..253a4681ef --- /dev/null +++ b/mount/mount.go @@ -0,0 +1,53 @@ +package mount + +import ( + "time" +) + +// Looks at /proc/self/mountinfo to determine of the specified +// mountpoint has been mounted +func Mounted(mountpoint string) (bool, error) { + entries, err := parseMountTable() + if err != nil { + return false, err + } + + // Search the table for the mountpoint + for _, e := range entries { + if e.mountpoint == mountpoint { + return true, nil + } + } + return false, nil +} + +// Mount the specified options at the target path +// Options must be specified as fstab style +func Mount(device, target, mType, options string) error { + if mounted, err := Mounted(target); err != nil || mounted { + return err + } + + flag, data := parseOptions(options) + if err := mount(device, target, mType, uintptr(flag), data); err != nil { + return err + } + return nil + +} + +// Unmount the target only if it is mounted +func Unmount(target string) (err error) { + if mounted, err := Mounted(target); err != nil || !mounted { + return err + } + + // Simple retry logic for unmount + for i := 0; i < 10; i++ { + if err = unmount(target, 0); err == nil { + return nil + } + time.Sleep(100 * time.Millisecond) + } + return +} diff --git a/graphdriver/driver_test.go b/mount/mount_test.go similarity index 78% rename from graphdriver/driver_test.go rename to mount/mount_test.go index c16ce70369..5dc9dc256a 100644 --- a/graphdriver/driver_test.go +++ b/mount/mount_test.go @@ -1,4 +1,4 @@ -package graphdriver +package mount import ( "os" @@ -24,7 +24,7 @@ func TestMountOptionsParsing(t *testing.T) { } func TestMounted(t *testing.T) { - tmp := path.Join(os.TempDir(), "graphdriver-tests") + tmp := path.Join(os.TempDir(), "mount-tests") if err := os.MkdirAll(tmp, 0777); err != nil { t.Fatal(err) } @@ -48,18 +48,11 @@ func TestMounted(t *testing.T) { } f.Close() - mount := &Mount{ - Device: sourcePath, - Target: targetPath, - Type: "none", - Options: "bind,ro", - } - - if err := mount.Mount("/"); err != nil { + if err := Mount(sourcePath, targetPath, "none", "bind,ro"); err != nil { t.Fatal(err) } defer func() { - if err := mount.Unmount("/"); err != nil { + if err := Unmount(targetPath); err != nil { t.Fatal(err) } }() diff --git a/mount/mounter_darwin.go b/mount/mounter_darwin.go new file mode 100644 index 0000000000..7615f94f9e --- /dev/null +++ b/mount/mounter_darwin.go @@ -0,0 +1,9 @@ +package mount + +func mount(device, target, mType string, flag uintptr, data string) error { + panic("Not implemented") +} + +func unmount(target string, flag int) error { + panic("Not implemented") +} diff --git a/mount/mounter_linux.go b/mount/mounter_linux.go new file mode 100644 index 0000000000..1371f72bd9 --- /dev/null +++ b/mount/mounter_linux.go @@ -0,0 +1,13 @@ +package mount + +import ( + "syscall" +) + +func mount(device, target, mType string, flag uintptr, data string) error { + return syscall.Mount(device, target, mType, flag, data) +} + +func unmount(target string, flag int) error { + return syscall.Unmount(target, flag) +} diff --git a/mount/mountinfo.go b/mount/mountinfo.go new file mode 100644 index 0000000000..397e792259 --- /dev/null +++ b/mount/mountinfo.go @@ -0,0 +1,45 @@ +package mount + +import ( + "bufio" + "fmt" + "os" +) + +const ( + mountinfoFormat = "%d %d %d:%d %s %s %s - %s %s %s" +) + +// Represents one line from /proc/self/mountinfo +type procEntry struct { + id, parent, major, minor int + source, mountpoint, fstype, device string + vfsopts, opts string +} + +// Parse /proc/self/mountinfo because comparing Dev and ino does not work from bind mounts +func parseMountTable() ([]*procEntry, error) { + f, err := os.Open("/proc/self/mountinfo") + if err != nil { + return nil, err + } + defer f.Close() + + s := bufio.NewScanner(f) + out := []*procEntry{} + for s.Scan() { + if err := s.Err(); err != nil { + return nil, err + } + + p := &procEntry{} + if _, err := fmt.Sscanf(s.Text(), mountinfoFormat, + &p.id, &p.parent, &p.major, &p.minor, + &p.source, &p.mountpoint, &p.vfsopts, &p.fstype, + &p.device, &p.opts); err != nil { + return nil, err + } + out = append(out, p) + } + return out, nil +} diff --git a/runtime.go b/runtime.go index a9e1e2a6fd..63ac795d02 100644 --- a/runtime.go +++ b/runtime.go @@ -788,67 +788,6 @@ 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 {