Browse code

Handle external mounts outside of lxc

Michael Crosby authored on 2013/12/18 07:04:37
Showing 4 changed files
... ...
@@ -48,7 +48,6 @@ type Container struct {
48 48
 	network         *NetworkInterface
49 49
 	NetworkSettings *NetworkSettings
50 50
 
51
-	SysInitPath    string
52 51
 	ResolvConfPath string
53 52
 	HostnamePath   string
54 53
 	HostsPath      string
... ...
@@ -297,7 +296,11 @@ func (container *Container) generateEnvConfig(env []string) error {
297 297
 	if err != nil {
298 298
 		return err
299 299
 	}
300
-	ioutil.WriteFile(container.EnvConfigPath(), data, 0600)
300
+	p, err := container.EnvConfigPath()
301
+	if err != nil {
302
+		return err
303
+	}
304
+	ioutil.WriteFile(p, data, 0600)
301 305
 	return nil
302 306
 }
303 307
 
... ...
@@ -681,6 +684,17 @@ func (container *Container) Start() (err error) {
681 681
 		}
682 682
 	}
683 683
 
684
+	mounts, err := runtime.getMounts(container)
685
+	if err != nil {
686
+		return err
687
+	}
688
+
689
+	for _, m := range mounts {
690
+		if err := m.Mount(container.RootfsPath()); err != nil {
691
+			return err
692
+		}
693
+	}
694
+
684 695
 	container.cmd = exec.Command(params[0], params[1:]...)
685 696
 
686 697
 	// Setup logging of stdout and stderr to disk
... ...
@@ -1358,6 +1372,18 @@ func (container *Container) GetImage() (*Image, error) {
1358 1358
 }
1359 1359
 
1360 1360
 func (container *Container) Unmount() error {
1361
+	mounts, err := container.runtime.getMounts(container)
1362
+	if err != nil {
1363
+		return err
1364
+	}
1365
+	for _, m := range mounts {
1366
+		if lastError := m.Unmount(container.RootfsPath()); lastError != nil {
1367
+			err = lastError
1368
+		}
1369
+	}
1370
+	if err != nil {
1371
+		return err
1372
+	}
1361 1373
 	return container.runtime.Unmount(container)
1362 1374
 }
1363 1375
 
... ...
@@ -1377,8 +1403,20 @@ func (container *Container) jsonPath() string {
1377 1377
 	return path.Join(container.root, "config.json")
1378 1378
 }
1379 1379
 
1380
-func (container *Container) EnvConfigPath() string {
1381
-	return path.Join(container.root, "config.env")
1380
+func (container *Container) EnvConfigPath() (string, error) {
1381
+	p := path.Join(container.root, "config.env")
1382
+	if _, err := os.Stat(p); err != nil {
1383
+		if os.IsNotExist(err) {
1384
+			f, err := os.Create(p)
1385
+			if err != nil {
1386
+				return "", err
1387
+			}
1388
+			f.Close()
1389
+		} else {
1390
+			return "", err
1391
+		}
1392
+	}
1393
+	return p, nil
1382 1394
 }
1383 1395
 
1384 1396
 func (container *Container) lxcConfigPath() string {
... ...
@@ -6,6 +6,8 @@ import (
6 6
 	"github.com/dotcloud/docker/utils"
7 7
 	"os"
8 8
 	"path"
9
+	"strings"
10
+	"syscall"
9 11
 )
10 12
 
11 13
 type InitFunc func(root string) (Driver, error)
... ...
@@ -31,6 +33,13 @@ type Differ interface {
31 31
 	DiffSize(id string) (bytes int64, err error)
32 32
 }
33 33
 
34
+type Mount struct {
35
+	Device  string
36
+	Target  string
37
+	Type    string
38
+	Options string
39
+}
40
+
34 41
 var (
35 42
 	DefaultDriver string
36 43
 	// All registred drivers
... ...
@@ -88,3 +97,91 @@ func New(root string) (driver Driver, err error) {
88 88
 	}
89 89
 	return nil, err
90 90
 }
91
+
92
+func (m *Mount) Mount(root string) error {
93
+	var (
94
+		flag   int
95
+		data   []string
96
+		target = path.Join(root, m.Target)
97
+	)
98
+
99
+	if mounted, err := Mounted(target); err != nil || mounted {
100
+		return err
101
+	}
102
+
103
+	flags := map[string]struct {
104
+		clear bool
105
+		flag  int
106
+	}{
107
+		"defaults":      {false, 0},
108
+		"ro":            {false, syscall.MS_RDONLY},
109
+		"rw":            {true, syscall.MS_RDONLY},
110
+		"suid":          {true, syscall.MS_NOSUID},
111
+		"nosuid":        {false, syscall.MS_NOSUID},
112
+		"dev":           {true, syscall.MS_NODEV},
113
+		"nodev":         {false, syscall.MS_NODEV},
114
+		"exec":          {true, syscall.MS_NOEXEC},
115
+		"noexec":        {false, syscall.MS_NOEXEC},
116
+		"sync":          {false, syscall.MS_SYNCHRONOUS},
117
+		"async":         {true, syscall.MS_SYNCHRONOUS},
118
+		"dirsync":       {false, syscall.MS_DIRSYNC},
119
+		"remount":       {false, syscall.MS_REMOUNT},
120
+		"mand":          {false, syscall.MS_MANDLOCK},
121
+		"nomand":        {true, syscall.MS_MANDLOCK},
122
+		"atime":         {true, syscall.MS_NOATIME},
123
+		"noatime":       {false, syscall.MS_NOATIME},
124
+		"diratime":      {true, syscall.MS_NODIRATIME},
125
+		"nodiratime":    {false, syscall.MS_NODIRATIME},
126
+		"bind":          {false, syscall.MS_BIND},
127
+		"rbind":         {false, syscall.MS_BIND | syscall.MS_REC},
128
+		"relatime":      {false, syscall.MS_RELATIME},
129
+		"norelatime":    {true, syscall.MS_RELATIME},
130
+		"strictatime":   {false, syscall.MS_STRICTATIME},
131
+		"nostrictatime": {true, syscall.MS_STRICTATIME},
132
+	}
133
+
134
+	for _, o := range strings.Split(m.Options, ",") {
135
+		// If the option does not exist in the flags table then it is a
136
+		// data value for a specific fs type
137
+		if f, exists := flags[o]; exists {
138
+			if f.clear {
139
+				flag &= ^f.flag
140
+			} else {
141
+				flag |= f.flag
142
+			}
143
+		} else {
144
+			data = append(data, o)
145
+		}
146
+	}
147
+
148
+	if err := syscall.Mount(m.Device, target, m.Type, uintptr(flag), strings.Join(data, ",")); err != nil {
149
+		panic(err)
150
+	}
151
+	return nil
152
+}
153
+
154
+func (m *Mount) Unmount(root string) error {
155
+	target := path.Join(root, m.Target)
156
+	if mounted, err := Mounted(target); err != nil || !mounted {
157
+		return err
158
+	}
159
+	return syscall.Unmount(target, 0)
160
+}
161
+
162
+func Mounted(mountpoint string) (bool, error) {
163
+	mntpoint, err := os.Stat(mountpoint)
164
+	if err != nil {
165
+		if os.IsNotExist(err) {
166
+			return false, nil
167
+		}
168
+		return false, err
169
+	}
170
+	parent, err := os.Stat(path.Join(mountpoint, ".."))
171
+	if err != nil {
172
+		return false, err
173
+	}
174
+	mntpointSt := mntpoint.Sys().(*syscall.Stat_t)
175
+	parentSt := parent.Sys().(*syscall.Stat_t)
176
+
177
+	return mntpointSt.Dev != parentSt.Dev, nil
178
+}
... ...
@@ -21,12 +21,6 @@ lxc.network.mtu = 1500
21 21
 {{$ROOTFS := .RootfsPath}}
22 22
 lxc.rootfs = {{$ROOTFS}}
23 23
 
24
-{{if and .HostnamePath .HostsPath}}
25
-# enable domain name support
26
-lxc.mount.entry = {{escapeFstabSpaces .HostnamePath}} {{escapeFstabSpaces $ROOTFS}}/etc/hostname none bind,ro 0 0
27
-lxc.mount.entry = {{escapeFstabSpaces .HostsPath}} {{escapeFstabSpaces $ROOTFS}}/etc/hosts none bind,ro 0 0
28
-{{end}}
29
-
30 24
 # use a dedicated pts for the container (and limit the number of pseudo terminal
31 25
 # available)
32 26
 lxc.pts = 1024
... ...
@@ -74,32 +68,20 @@ lxc.cgroup.devices.allow = c 10:200 rwm
74 74
 # standard mount point
75 75
 # Use mnt.putold as per https://bugs.launchpad.net/ubuntu/+source/lxc/+bug/986385
76 76
 lxc.pivotdir = lxc_putold
77
+
78
+# NOTICE: These mounts must be applied within the namespace
79
+
77 80
 #  WARNING: procfs is a known attack vector and should probably be disabled
78 81
 #           if your userspace allows it. eg. see http://blog.zx2c4.com/749
79 82
 lxc.mount.entry = proc {{escapeFstabSpaces $ROOTFS}}/proc proc nosuid,nodev,noexec 0 0
80
-#  WARNING: sysfs is a known attack vector and should probably be disabled
81
-#           if your userspace allows it. eg. see http://bit.ly/T9CkqJ
83
+
84
+# WARNING: sysfs is a known attack vector and should probably be disabled
85
+# if your userspace allows it. eg. see http://bit.ly/T9CkqJ
82 86
 lxc.mount.entry = sysfs {{escapeFstabSpaces $ROOTFS}}/sys sysfs nosuid,nodev,noexec 0 0
87
+
83 88
 lxc.mount.entry = devpts {{escapeFstabSpaces $ROOTFS}}/dev/pts devpts newinstance,ptmxmode=0666,nosuid,noexec 0 0
84
-#lxc.mount.entry = varrun {{escapeFstabSpaces $ROOTFS}}/var/run tmpfs mode=755,size=4096k,nosuid,nodev,noexec 0 0
85
-#lxc.mount.entry = varlock {{escapeFstabSpaces $ROOTFS}}/var/lock tmpfs size=1024k,nosuid,nodev,noexec 0 0
86 89
 lxc.mount.entry = shm {{escapeFstabSpaces $ROOTFS}}/dev/shm tmpfs size=65536k,nosuid,nodev,noexec 0 0
87 90
 
88
-# Inject dockerinit
89
-lxc.mount.entry = {{escapeFstabSpaces .SysInitPath}} {{escapeFstabSpaces $ROOTFS}}/.dockerinit none bind,ro 0 0
90
-
91
-# Inject env
92
-lxc.mount.entry = {{escapeFstabSpaces .EnvConfigPath}} {{escapeFstabSpaces $ROOTFS}}/.dockerenv none bind,ro 0 0
93
-
94
-# In order to get a working DNS environment, mount bind (ro) the host's /etc/resolv.conf into the container
95
-lxc.mount.entry = {{escapeFstabSpaces .ResolvConfPath}} {{escapeFstabSpaces $ROOTFS}}/etc/resolv.conf none bind,ro 0 0
96
-{{if .Volumes}}
97
-{{ $rw := .VolumesRW }}
98
-{{range $virtualPath, $realPath := .Volumes}}
99
-lxc.mount.entry = {{escapeFstabSpaces $realPath}} {{escapeFstabSpaces $ROOTFS}}/{{escapeFstabSpaces $virtualPath}} none bind,{{ if index $rw $virtualPath }}rw{{else}}ro{{end}} 0 0
100
-{{end}}
101
-{{end}}
102
-
103 91
 {{if (getHostConfig .).Privileged}}
104 92
 {{if (getCapabilities .).AppArmor}}
105 93
 lxc.aa_profile = unconfined
... ...
@@ -486,10 +486,8 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin
486 486
 		hostConfig:      &HostConfig{},
487 487
 		Image:           img.ID, // Always use the resolved image id
488 488
 		NetworkSettings: &NetworkSettings{},
489
-		// FIXME: do we need to store this in the container?
490
-		SysInitPath: runtime.sysInitPath,
491
-		Name:        name,
492
-		Driver:      runtime.driver.String(),
489
+		Name:            name,
490
+		Driver:          runtime.driver.String(),
493 491
 	}
494 492
 	container.root = runtime.containerRoot(container.ID)
495 493
 	// Step 1: create the container directory.
... ...
@@ -790,6 +788,67 @@ func (runtime *Runtime) Close() error {
790 790
 	return nil
791 791
 }
792 792
 
793
+func (runtime *Runtime) getMounts(container *Container) ([]*graphdriver.Mount, error) {
794
+	// Generate additional bind mounts
795
+	envPath, err := container.EnvConfigPath()
796
+	if err != nil {
797
+		return nil, err
798
+	}
799
+	mounts := []*graphdriver.Mount{
800
+		{
801
+			Device:  runtime.sysInitPath,
802
+			Target:  "/.dockerinit",
803
+			Type:    "none",
804
+			Options: "bind,ro",
805
+		},
806
+		{
807
+			Device:  envPath,
808
+			Target:  "/.dockerenv",
809
+			Type:    "none",
810
+			Options: "bind,ro",
811
+		},
812
+		// In order to get a working DNS environment, mount bind (ro) the host's /etc/resolv.conf into the container
813
+		{
814
+			Device:  container.ResolvConfPath,
815
+			Target:  "/etc/resolv.conf",
816
+			Type:    "none",
817
+			Options: "bind,ro",
818
+		},
819
+	}
820
+
821
+	if container.HostnamePath != "" && container.HostsPath != "" {
822
+		mounts = append(mounts,
823
+			&graphdriver.Mount{
824
+				Device:  container.HostnamePath,
825
+				Target:  "/etc/hostname",
826
+				Type:    "none",
827
+				Options: "bind,ro",
828
+			},
829
+			&graphdriver.Mount{
830
+				Device:  container.HostsPath,
831
+				Target:  "/etc/hosts",
832
+				Type:    "none",
833
+				Options: "bind,ro",
834
+			})
835
+	}
836
+
837
+	for r, v := range container.Volumes {
838
+		mountAs := "ro"
839
+		if container.VolumesRW[v] {
840
+			mountAs = "rw"
841
+		}
842
+
843
+		mounts = append(mounts,
844
+			&graphdriver.Mount{
845
+				Device:  v,
846
+				Target:  r,
847
+				Type:    "none",
848
+				Options: fmt.Sprintf("bind,%s", mountAs),
849
+			})
850
+	}
851
+	return mounts, nil
852
+}
853
+
793 854
 func (runtime *Runtime) Mount(container *Container) error {
794 855
 	dir, err := runtime.driver.Get(container.ID)
795 856
 	if err != nil {