Browse Source

Handle external mounts outside of lxc

Michael Crosby 11 years ago
parent
commit
45d7dcfea2
4 changed files with 209 additions and 33 deletions
  1. 42 4
      container.go
  2. 97 0
      graphdriver/driver.go
  3. 7 25
      lxc_template.go
  4. 63 4
      runtime.go

+ 42 - 4
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 {

+ 97 - 0
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
+}

+ 7 - 25
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

+ 63 - 4
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 {