Browse code

Add capability to specify mount propagation per volume

Allow passing mount propagation option shared, slave, or private as volume
property.

For example.
docker run -ti -v /root/mnt-source:/root/mnt-dest:slave fedora bash

Signed-off-by: Vivek Goyal <vgoyal@redhat.com>

Vivek Goyal authored on 2015/10/24 05:57:57
Showing 18 changed files
... ...
@@ -338,6 +338,7 @@ type MountPoint struct {
338 338
 	Driver      string `json:",omitempty"`
339 339
 	Mode        string
340 340
 	RW          bool
341
+	Propagation string
341 342
 }
342 343
 
343 344
 // Volume represents the configuration of a volume for the remote API
... ...
@@ -403,7 +403,7 @@ func (container *Container) NetworkMounts() []execdriver.Mount {
403 403
 				Source:      container.ResolvConfPath,
404 404
 				Destination: "/etc/resolv.conf",
405 405
 				Writable:    writable,
406
-				Private:     true,
406
+				Propagation: volume.DefaultPropagationMode,
407 407
 			})
408 408
 		}
409 409
 	}
... ...
@@ -420,7 +420,7 @@ func (container *Container) NetworkMounts() []execdriver.Mount {
420 420
 				Source:      container.HostnamePath,
421 421
 				Destination: "/etc/hostname",
422 422
 				Writable:    writable,
423
-				Private:     true,
423
+				Propagation: volume.DefaultPropagationMode,
424 424
 			})
425 425
 		}
426 426
 	}
... ...
@@ -437,7 +437,7 @@ func (container *Container) NetworkMounts() []execdriver.Mount {
437 437
 				Source:      container.HostsPath,
438 438
 				Destination: "/etc/hosts",
439 439
 				Writable:    writable,
440
-				Private:     true,
440
+				Propagation: volume.DefaultPropagationMode,
441 441
 			})
442 442
 		}
443 443
 	}
... ...
@@ -534,7 +534,7 @@ func (container *Container) IpcMounts() []execdriver.Mount {
534 534
 			Source:      container.ShmPath,
535 535
 			Destination: "/dev/shm",
536 536
 			Writable:    true,
537
-			Private:     true,
537
+			Propagation: volume.DefaultPropagationMode,
538 538
 		})
539 539
 	}
540 540
 
... ...
@@ -544,7 +544,7 @@ func (container *Container) IpcMounts() []execdriver.Mount {
544 544
 			Source:      container.MqueuePath,
545 545
 			Destination: "/dev/mqueue",
546 546
 			Writable:    true,
547
-			Private:     true,
547
+			Propagation: volume.DefaultPropagationMode,
548 548
 		})
549 549
 	}
550 550
 	return mounts
... ...
@@ -26,9 +26,8 @@ type Mount struct {
26 26
 	Source      string `json:"source"`
27 27
 	Destination string `json:"destination"`
28 28
 	Writable    bool   `json:"writable"`
29
-	Private     bool   `json:"private"`
30
-	Slave       bool   `json:"slave"`
31 29
 	Data        string `json:"data"`
30
+	Propagation string `json:"mountpropagation"`
32 31
 }
33 32
 
34 33
 // Resources contains all resource configs for a driver.
... ...
@@ -125,6 +124,11 @@ type Command struct {
125 125
 	UTS                *UTS              `json:"uts"`
126 126
 }
127 127
 
128
+// SetRootPropagation sets the root mount propagation mode.
129
+func SetRootPropagation(config *configs.Config, propagation int) {
130
+	config.RootPropagation = propagation
131
+}
132
+
128 133
 // InitContainer is the initialization of a container config.
129 134
 // It returns the initial configs for a container. It's mostly
130 135
 // defined by the default template.
... ...
@@ -137,7 +141,9 @@ func InitContainer(c *Command) *configs.Config {
137 137
 	container.Devices = c.AutoCreatedDevices
138 138
 	container.Rootfs = c.Rootfs
139 139
 	container.Readonlyfs = c.ReadonlyRootfs
140
-	container.RootPropagation = mount.RPRIVATE
140
+	// This can be overridden later by driver during mount setup based
141
+	// on volume options
142
+	SetRootPropagation(container, mount.RPRIVATE)
141 143
 
142 144
 	// check to see if we are running in ramdisk to disable pivot root
143 145
 	container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != ""
... ...
@@ -12,6 +12,7 @@ import (
12 12
 	derr "github.com/docker/docker/errors"
13 13
 	"github.com/docker/docker/pkg/mount"
14 14
 
15
+	"github.com/docker/docker/volume"
15 16
 	"github.com/opencontainers/runc/libcontainer/apparmor"
16 17
 	"github.com/opencontainers/runc/libcontainer/configs"
17 18
 	"github.com/opencontainers/runc/libcontainer/devices"
... ...
@@ -278,6 +279,20 @@ func (d *Driver) setupRlimits(container *configs.Config, c *execdriver.Command)
278 278
 	}
279 279
 }
280 280
 
281
+// If rootfs mount propagation is RPRIVATE, that means all the volumes are
282
+// going to be private anyway. There is no need to apply per volume
283
+// propagation on top. This is just an optimzation so that cost of per volume
284
+// propagation is paid only if user decides to make some volume non-private
285
+// which will force rootfs mount propagation to be non RPRIVATE.
286
+func checkResetVolumePropagation(container *configs.Config) {
287
+	if container.RootPropagation != mount.RPRIVATE {
288
+		return
289
+	}
290
+	for _, m := range container.Mounts {
291
+		m.PropagationFlags = nil
292
+	}
293
+}
294
+
281 295
 func (d *Driver) setupMounts(container *configs.Config, c *execdriver.Command) error {
282 296
 	userMounts := make(map[string]struct{})
283 297
 	for _, m := range c.Mounts {
... ...
@@ -298,6 +313,15 @@ func (d *Driver) setupMounts(container *configs.Config, c *execdriver.Command) e
298 298
 	}
299 299
 	container.Mounts = defaultMounts
300 300
 
301
+	mountPropagationMap := map[string]int{
302
+		"private":  mount.PRIVATE,
303
+		"rprivate": mount.RPRIVATE,
304
+		"shared":   mount.SHARED,
305
+		"rshared":  mount.RSHARED,
306
+		"slave":    mount.SLAVE,
307
+		"rslave":   mount.RSLAVE,
308
+	}
309
+
301 310
 	for _, m := range c.Mounts {
302 311
 		for _, cm := range container.Mounts {
303 312
 			if cm.Destination == m.Destination {
... ...
@@ -319,31 +343,59 @@ func (d *Driver) setupMounts(container *configs.Config, c *execdriver.Command) e
319 319
 				}
320 320
 			}
321 321
 			container.Mounts = append(container.Mounts, &configs.Mount{
322
-				Source:        m.Source,
323
-				Destination:   m.Destination,
324
-				Data:          data,
325
-				Device:        "tmpfs",
326
-				Flags:         flags,
327
-				PremountCmds:  genTmpfsPremountCmd(c.TmpDir, fulldest, m.Destination),
328
-				PostmountCmds: genTmpfsPostmountCmd(c.TmpDir, fulldest, m.Destination),
322
+				Source:           m.Source,
323
+				Destination:      m.Destination,
324
+				Data:             data,
325
+				Device:           "tmpfs",
326
+				Flags:            flags,
327
+				PremountCmds:     genTmpfsPremountCmd(c.TmpDir, fulldest, m.Destination),
328
+				PostmountCmds:    genTmpfsPostmountCmd(c.TmpDir, fulldest, m.Destination),
329
+				PropagationFlags: []int{mountPropagationMap[volume.DefaultPropagationMode]},
329 330
 			})
330 331
 			continue
331 332
 		}
332 333
 		flags := syscall.MS_BIND | syscall.MS_REC
334
+		var pFlag int
333 335
 		if !m.Writable {
334 336
 			flags |= syscall.MS_RDONLY
335 337
 		}
336
-		if m.Slave {
337
-			flags |= syscall.MS_SLAVE
338
+
339
+		// Determine property of RootPropagation based on volume
340
+		// properties. If a volume is shared, then keep root propagtion
341
+		// shared. This should work for slave and private volumes too.
342
+		//
343
+		// For slave volumes, it can be either [r]shared/[r]slave.
344
+		//
345
+		// For private volumes any root propagation value should work.
346
+
347
+		pFlag = mountPropagationMap[m.Propagation]
348
+		if pFlag == mount.SHARED || pFlag == mount.RSHARED {
349
+			rootpg := container.RootPropagation
350
+			if rootpg != mount.SHARED && rootpg != mount.RSHARED {
351
+				execdriver.SetRootPropagation(container, mount.SHARED)
352
+			}
353
+		} else if pFlag == mount.SLAVE || pFlag == mount.RSLAVE {
354
+			rootpg := container.RootPropagation
355
+			if rootpg != mount.SHARED && rootpg != mount.RSHARED && rootpg != mount.SLAVE && rootpg != mount.RSLAVE {
356
+				execdriver.SetRootPropagation(container, mount.RSLAVE)
357
+			}
338 358
 		}
339 359
 
340
-		container.Mounts = append(container.Mounts, &configs.Mount{
360
+		mount := &configs.Mount{
341 361
 			Source:      m.Source,
342 362
 			Destination: m.Destination,
343 363
 			Device:      "bind",
344 364
 			Flags:       flags,
345
-		})
365
+		}
366
+
367
+		if pFlag != 0 {
368
+			mount.PropagationFlags = []int{pFlag}
369
+		}
370
+
371
+		container.Mounts = append(container.Mounts, mount)
346 372
 	}
373
+
374
+	checkResetVolumePropagation(container)
347 375
 	return nil
348 376
 }
349 377
 
... ...
@@ -72,6 +72,7 @@ func addMountPoints(container *container.Container) []types.MountPoint {
72 72
 			Driver:      m.Driver,
73 73
 			Mode:        m.Mode,
74 74
 			RW:          m.RW,
75
+			Propagation: m.Propagation,
75 76
 		})
76 77
 	}
77 78
 	return mountPoints
... ...
@@ -99,6 +99,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
99 99
 				RW:          m.RW && volume.ReadWrite(mode),
100 100
 				Driver:      m.Driver,
101 101
 				Destination: m.Destination,
102
+				Propagation: m.Propagation,
102 103
 			}
103 104
 
104 105
 			if len(cp.Source) == 0 {
... ...
@@ -24,11 +24,13 @@ func (daemon *Daemon) setupMounts(container *container.Container) ([]execdriver.
24 24
 			return nil, err
25 25
 		}
26 26
 		if !container.TrySetNetworkMount(m.Destination, path) {
27
-			mounts = append(mounts, execdriver.Mount{
27
+			mnt := execdriver.Mount{
28 28
 				Source:      path,
29 29
 				Destination: m.Destination,
30 30
 				Writable:    m.RW,
31
-			})
31
+				Propagation: m.Propagation,
32
+			}
33
+			mounts = append(mounts, mnt)
32 34
 		}
33 35
 	}
34 36
 
... ...
@@ -79,12 +79,12 @@ Creates a new container.
79 79
       -u, --user=""                 Username or UID
80 80
       --ulimit=[]                   Ulimit options
81 81
       --uts=""                      UTS namespace to use
82
-      -v, --volume=[]               Bind mount a volume with: [host-src:]container-dest[:<options>], where
83
-                                    options are comma delimited and selected from [rw|ro] and [z|Z].
84
-                                    The 'host-src' can either be an absolute path or a name value.
85
-                                    If 'host-src' is missing, then docker creates a new volume.
86
-                                    If neither 'rw' or 'ro' is specified then the volume is mounted
87
-                                    in read-write mode.
82
+      -v, --volume=[host-src:]container-dest[:<options>]
83
+                                    Bind mount a volume. The comma-delimited
84
+                                    `options` are [rw|ro], [z|Z], or
85
+                                    [[r]shared|[r]slave|[r]private]. The
86
+                                    'host-src' is an absolute path or a name
87
+                                    value.
88 88
       --volume-driver=""            Container's volume driver
89 89
       --volumes-from=[]             Mount volumes from the specified container(s)
90 90
       -w, --workdir=""              Working directory inside the container
... ...
@@ -80,12 +80,12 @@ parent = "smn_cli"
80 80
       -u, --user=""                 Username or UID (format: <name|uid>[:<group|gid>])
81 81
       --ulimit=[]                   Ulimit options
82 82
       --uts=""                      UTS namespace to use
83
-      -v, --volume=[]               Bind mount a volume with: [host-src:]container-dest[:<options>], where
84
-                                    options are comma delimited and selected from [rw|ro] and [z|Z].
85
-                                    The 'host-src' can either be an absolute path or a name value.
86
-                                    If 'host-src' is missing, then docker creates a new volume.
87
-                                    If neither 'rw' or 'ro' is specified then the volume is mounted
88
-                                    in read-write mode.
83
+      -v, --volume=[host-src:]container-dest[:<options>]
84
+                                    Bind mount a volume. The comma-delimited
85
+                                    `options` are [rw|ro], [z|Z], or
86
+                                    [[r]shared|[r]slave|[r]private]. The
87
+                                    'host-src' is an absolute path or a name
88
+                                    value.
89 89
       --volume-driver=""            Container's volume driver
90 90
       --volumes-from=[]             Mount volumes from the specified container(s)
91 91
       -w, --workdir=""              Working directory inside the container
... ...
@@ -1330,11 +1330,14 @@ Similarly the operator can set the **hostname** with `-h`.
1330 1330
 
1331 1331
 ### VOLUME (shared filesystems)
1332 1332
 
1333
-    -v=[]: Create a bind mount with: [host-src:]container-dest[:<options>], where
1334
-    options are comma delimited and selected from [rw|ro] and [z|Z].
1335
-           If 'host-src' is missing, then docker creates a new volume.
1336
-		   If neither 'rw' or 'ro' is specified then the volume is mounted
1337
-		   in read-write mode.
1333
+    -v, --volume=[host-src:]container-dest[:<options>]: Bind mount a volume.
1334
+    The comma-delimited `options` are [rw|ro], [z|Z], or
1335
+    [[r]shared|[r]slave|[r]private]. The 'host-src' is an absolute path or a
1336
+    name value.
1337
+
1338
+    If neither 'rw' or 'ro' is specified then the volume is mounted in
1339
+    read-write mode.
1340
+
1338 1341
     --volumes-from="": Mount all volumes from the given container(s)
1339 1342
 
1340 1343
 > **Note**:
... ...
@@ -64,7 +64,7 @@ docker-create - Create a new container
64 64
 [**-u**|**--user**[=*USER*]]
65 65
 [**--ulimit**[=*[]*]]
66 66
 [**--uts**[=*[]*]]
67
-[**-v**|**--volume**[=*[]*]]
67
+[**-v**|**--volume**[=*[[HOST-DIR:]CONTAINER-DIR[:OPTIONS]]*]]
68 68
 [**--volume-driver**[=*DRIVER*]]
69 69
 [**--volumes-from**[=*[]*]]
70 70
 [**-w**|**--workdir**[=*WORKDIR*]]
... ...
@@ -311,8 +311,78 @@ any options, the systems uses the following options:
311 311
      **host**: use the host's UTS namespace inside the container.
312 312
      Note: the host mode gives the container access to changing the host's hostname and is therefore considered insecure.
313 313
 
314
-**-v**, **--volume**=[]
315
-   Bind mount a volume (e.g., from the host: -v /host:/container, from Docker: -v /container)
314
+**-v**|**--volume**[=*[[HOST-DIR:]CONTAINER-DIR[:OPTIONS]]*]
315
+   Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, Docker
316
+   bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the Docker
317
+   container. If 'HOST-DIR' is omitted,  Docker automatically creates the new
318
+   volume on the host.  The `OPTIONS` are a comma delimited list and can be:
319
+
320
+   * [rw|ro]
321
+   * [z|Z]
322
+   * [`[r]shared`|`[r]slave`|`[r]private`]
323
+
324
+The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The `HOST-DIR`
325
+can be an absolute path or a `name` value. A `name` value must start with an
326
+alphanumeric character, followed by `a-z0-9`, `_` (underscore), `.` (period) or
327
+`-` (hyphen). An absolute path starts with a `/` (forward slash).
328
+
329
+If you supply a `HOST-DIR` that is an absolute path,  Docker bind-mounts to the
330
+path you specify. If you supply a `name`, Docker creates a named volume by that
331
+`name`. For example, you can specify either `/foo` or `foo` for a `HOST-DIR`
332
+value. If you supply the `/foo` value, Docker creates a bind-mount. If you
333
+supply the `foo` specification, Docker creates a named volume.
334
+
335
+You can specify multiple  **-v** options to mount one or more mounts to a
336
+container. To use these same mounts in other containers, specify the
337
+**--volumes-from** option also.
338
+
339
+You can add `:ro` or `:rw` suffix to a volume to mount it  read-only or
340
+read-write mode, respectively. By default, the volumes are mounted read-write.
341
+See examples.
342
+
343
+Labeling systems like SELinux require that proper labels are placed on volume
344
+content mounted into a container. Without a label, the security system might
345
+prevent the processes running inside the container from using the content. By
346
+default, Docker does not change the labels set by the OS.
347
+
348
+To change a label in the container context, you can add either of two suffixes
349
+`:z` or `:Z` to the volume mount. These suffixes tell Docker to relabel file
350
+objects on the shared volumes. The `z` option tells Docker that two containers
351
+share the volume content. As a result, Docker labels the content with a shared
352
+content label. Shared volume labels allow all containers to read/write content.
353
+The `Z` option tells Docker to label the content with a private unshared label.
354
+Only the current container can use a private volume.
355
+
356
+By default bind mounted volumes are `private`. That means any mounts done
357
+inside container will not be visible on host and vice-a-versa. One can change
358
+this behavior by specifying a volume mount propagation property. Making a
359
+volume `shared` mounts done under that volume inside container will be
360
+visible on host and vice-a-versa. Making a volume `slave` enables only one
361
+way mount propagation and that is mounts done on host under that volume
362
+will be visible inside container but not the other way around.
363
+
364
+To control mount propagation property of volume one can use `:[r]shared`,
365
+`:[r]slave` or `:[r]private` propagation flag. Propagation property can
366
+be specified only for bind mounted volumes and not for internal volumes or
367
+named volumes. For mount propagation to work source mount point (mount point
368
+where source dir is mounted on) has to have right propagation properties. For
369
+shared volumes, source mount point has to be shared. And for slave volumes,
370
+source mount has to be either shared or slave.
371
+
372
+Use `df <source-dir>` to figure out the source mount and then use
373
+`findmnt -o TARGET,PROPAGATION <source-mount-dir>` to figure out propagation
374
+properties of source mount. If `findmnt` utility is not available, then one
375
+can look at mount entry for source mount point in `/proc/self/mountinfo`. Look
376
+at `optional fields` and see if any propagaion properties are specified.
377
+`shared:X` means mount is `shared`, `master:X` means mount is `slave` and if
378
+nothing is there that means mount is `private`.
379
+
380
+To change propagation properties of a mount point use `mount` command. For
381
+example, if one wants to bind mount source directory `/foo` one can do
382
+`mount --bind /foo /foo` and `mount --make-private --make-shared /foo`. This
383
+will convert /foo into a `shared` mount point. Alternatively one can directly
384
+change propagation properties of source mount. Say `/` is source mount for
385
+`/foo`, then use `mount --make-shared /` to convert `/` into a `shared` mount.
316 386
 
317 387
 **--volume-driver**=""
318 388
    Container's volume driver. This driver creates volumes specified either from
... ...
@@ -104,6 +104,7 @@ To get information on a container use its ID or instance name:
104 104
         "Destination": "/data",
105 105
         "Mode": "ro,Z",
106 106
         "RW": false
107
+	"Propagation": ""
107 108
       }
108 109
     ],
109 110
     "AppArmorProfile": "",
... ...
@@ -67,7 +67,7 @@ docker-run - Run a command in a new container
67 67
 [**-u**|**--user**[=*USER*]]
68 68
 [**--ulimit**[=*[]*]]
69 69
 [**--uts**[=*[]*]]
70
-[**-v**|**--volume**[=*[]*]]
70
+[**-v**|**--volume**[=*[[HOST-DIR:]CONTAINER-DIR[:OPTIONS]]*]]
71 71
 [**--volume-driver**[=*DRIVER*]]
72 72
 [**--volumes-from**[=*[]*]]
73 73
 [**-w**|**--workdir**[=*WORKDIR*]]
... ...
@@ -476,24 +476,34 @@ any options, the systems uses the following options:
476 476
 **--ulimit**=[]
477 477
     Ulimit options
478 478
 
479
-**-v**, **--volume**=[] Create a bind mount
480
-   (format: `[host-dir:]container-dir[:<suffix options>]`, where suffix options
481
-are comma delimited and selected from [rw|ro] and [z|Z].)
479
+**-v**|**--volume**[=*[[HOST-DIR:]CONTAINER-DIR[:OPTIONS]]*]
480
+   Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, Docker
481
+   bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the Docker
482
+   container. If 'HOST-DIR' is omitted,  Docker automatically creates the new
483
+   volume on the host.  The `OPTIONS` are a comma delimited list and can be:
482 484
 
483
-   (e.g., using -v /host-dir:/container-dir, bind mounts /host-dir in the
484
-host to /container-dir in the Docker container)
485
+   * [rw|ro]
486
+   * [z|Z]
487
+   * [`[r]shared`|`[r]slave`|`[r]private`]
485 488
 
486
-   If 'host-dir' is missing, then docker automatically creates the new volume
487
-on the host. **This auto-creation of the host path has been deprecated in
488
-Release: v1.9.**
489
+The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The `HOST-DIR`
490
+can be an absolute path or a `name` value. A `name` value must start with an
491
+alphanumeric character, followed by `a-z0-9`, `_` (underscore), `.` (period) or
492
+`-` (hyphen). An absolute path starts with a `/` (forward slash).
489 493
 
490
-   The **-v** option can be used one or
491
-more times to add one or more mounts to a container. These mounts can then be
492
-used in other containers using the **--volumes-from** option.
494
+If you supply a `HOST-DIR` that is an absolute path,  Docker bind-mounts to the
495
+path you specify. If you supply a `name`, Docker creates a named volume by that
496
+`name`. For example, you can specify either `/foo` or `foo` for a `HOST-DIR`
497
+value. If you supply the `/foo` value, Docker creates a bind-mount. If you
498
+supply the `foo` specification, Docker creates a named volume.
493 499
 
494
-   The volume may be optionally suffixed with :ro or :rw to mount the volumes in
495
-read-only or read-write mode, respectively. By default, the volumes are mounted
496
-read-write. See examples.
500
+You can specify multiple  **-v** options to mount one or more mounts to a
501
+container. To use these same mounts in other containers, specify the
502
+**--volumes-from** option also.
503
+
504
+You can add `:ro` or `:rw` suffix to a volume to mount it  read-only or
505
+read-write mode, respectively. By default, the volumes are mounted read-write.
506
+See examples.
497 507
 
498 508
 Labeling systems like SELinux require that proper labels are placed on volume
499 509
 content mounted into a container. Without a label, the security system might
... ...
@@ -508,18 +518,36 @@ content label. Shared volume labels allow all containers to read/write content.
508 508
 The `Z` option tells Docker to label the content with a private unshared label.
509 509
 Only the current container can use a private volume.
510 510
 
511
-The `container-dir` must always be an absolute path such as `/src/docs`.
512
-The `host-dir` can either be an absolute path or a `name` value. If you
513
-supply an absolute path for the `host-dir`, Docker bind-mounts to the path
514
-you specify. If you supply a `name`, Docker creates a named volume by that `name`.
515
-
516
-A `name` value must start with start with an alphanumeric character,
517
-followed by `a-z0-9`, `_` (underscore), `.` (period) or `-` (hyphen).
518
-An absolute path starts with a `/` (forward slash).
519
-
520
-For example, you can specify either `/foo` or `foo` for a `host-dir` value.
521
-If you supply the `/foo` value, Docker creates a bind-mount. If you supply
522
-the `foo` specification, Docker creates a named volume.
511
+By default bind mounted volumes are `private`. That means any mounts done
512
+inside container will not be visible on host and vice-a-versa. One can change
513
+this behavior by specifying a volume mount propagation property. Making a
514
+volume `shared` mounts done under that volume inside container will be
515
+visible on host and vice-a-versa. Making a volume `slave` enables only one
516
+way mount propagation and that is mounts done on host under that volume
517
+will be visible inside container but not the other way around.
518
+
519
+To control mount propagation property of volume one can use `:[r]shared`,
520
+`:[r]slave` or `:[r]private` propagation flag. Propagation property can
521
+be specified only for bind mounted volumes and not for internal volumes or
522
+named volumes. For mount propagation to work source mount point (mount point
523
+where source dir is mounted on) has to have right propagation properties. For
524
+shared volumes, source mount point has to be shared. And for slave volumes,
525
+source mount has to be either shared or slave.
526
+
527
+Use `df <source-dir>` to figure out the source mount and then use
528
+`findmnt -o TARGET,PROPAGATION <source-mount-dir>` to figure out propagation
529
+properties of source mount. If `findmnt` utility is not available, then one
530
+can look at mount entry for source mount point in `/proc/self/mountinfo`. Look
531
+at `optional fields` and see if any propagaion properties are specified.
532
+`shared:X` means mount is `shared`, `master:X` means mount is `slave` and if
533
+nothing is there that means mount is `private`.
534
+
535
+To change propagation properties of a mount point use `mount` command. For
536
+example, if one wants to bind mount source directory `/foo` one can do
537
+`mount --bind /foo /foo` and `mount --make-private --make-shared /foo`. This
538
+will convert /foo into a `shared` mount point. Alternatively one can directly
539
+change propagation properties of source mount. Say `/` is source mount for
540
+`/foo`, then use `mount --make-shared /` to convert `/` into a `shared` mount.
523 541
 
524 542
 **--volume-driver**=""
525 543
    Container's volume driver. This driver creates volumes specified either from
... ...
@@ -52,6 +52,9 @@ type MountPoint struct {
52 52
 
53 53
 	// Note Mode is not used on Windows
54 54
 	Mode string `json:"Relabel"` // Originally field was `Relabel`"
55
+
56
+	// Note Propagation is not used on Windows
57
+	Propagation string // Mount propagation string
55 58
 }
56 59
 
57 60
 // Setup sets up a mount point by either mounting the volume if it is
... ...
@@ -85,17 +88,6 @@ func (m *MountPoint) Path() string {
85 85
 	return m.Source
86 86
 }
87 87
 
88
-// ValidMountMode will make sure the mount mode is valid.
89
-// returns if it's a valid mount mode or not.
90
-func ValidMountMode(mode string) bool {
91
-	return roModes[strings.ToLower(mode)] || rwModes[strings.ToLower(mode)]
92
-}
93
-
94
-// ReadWrite tells you if a mode string is a valid read-write mode or not.
95
-func ReadWrite(mode string) bool {
96
-	return rwModes[strings.ToLower(mode)]
97
-}
98
-
99 88
 // ParseVolumesFrom ensure that the supplied volumes-from is valid.
100 89
 func ParseVolumesFrom(spec string) (string, string, error) {
101 90
 	if len(spec) == 0 {
... ...
@@ -111,6 +103,13 @@ func ParseVolumesFrom(spec string) (string, string, error) {
111 111
 		if !ValidMountMode(mode) {
112 112
 			return "", "", derr.ErrorCodeVolumeInvalidMode.WithArgs(mode)
113 113
 		}
114
+		// For now don't allow propagation properties while importing
115
+		// volumes from data container. These volumes will inherit
116
+		// the same propagation property as of the original volume
117
+		// in data container. This probably can be relaxed in future.
118
+		if HasPropagation(mode) {
119
+			return "", "", derr.ErrorCodeVolumeInvalidMode.WithArgs(mode)
120
+		}
114 121
 	}
115 122
 	return id, mode, nil
116 123
 }
117 124
new file mode 100644
... ...
@@ -0,0 +1,44 @@
0
+// +build linux
1
+
2
+package volume
3
+
4
+import (
5
+	"strings"
6
+)
7
+
8
+// DefaultPropagationMode defines what propagation mode should be used by
9
+// default if user has not specified one explicitly.
10
+const DefaultPropagationMode string = "rprivate"
11
+
12
+// propagation modes
13
+var propagationModes = map[string]bool{
14
+	"private":  true,
15
+	"rprivate": true,
16
+	"slave":    true,
17
+	"rslave":   true,
18
+	"shared":   true,
19
+	"rshared":  true,
20
+}
21
+
22
+// GetPropagation extracts and returns the mount propagation mode. If there
23
+// are no specifications, then by default it is "private".
24
+func GetPropagation(mode string) string {
25
+	for _, o := range strings.Split(mode, ",") {
26
+		if propagationModes[o] {
27
+			return o
28
+		}
29
+	}
30
+	return DefaultPropagationMode
31
+}
32
+
33
+// HasPropagation checks if there is a valid propagation mode present in
34
+// passed string. Returns true if a valid propagatio mode specifier is
35
+// present, false otherwise.
36
+func HasPropagation(mode string) bool {
37
+	for _, o := range strings.Split(mode, ",") {
38
+		if propagationModes[o] {
39
+			return true
40
+		}
41
+	}
42
+	return false
43
+}
0 44
new file mode 100644
... ...
@@ -0,0 +1,22 @@
0
+// +build !linux
1
+
2
+package volume
3
+
4
+// DefaultPropagationMode is used only in linux. In other cases it returns
5
+// empty string.
6
+const DefaultPropagationMode string = ""
7
+
8
+// propagation modes not supported on this platform.
9
+var propagationModes = map[string]bool{}
10
+
11
+// GetPropagation is not supported. Return empty string.
12
+func GetPropagation(mode string) string {
13
+	return DefaultPropagationMode
14
+}
15
+
16
+// HasPropagation checks if there is a valid propagation mode present in
17
+// passed string. Returns true if a valid propagatio mode specifier is
18
+// present, false otherwise.
19
+func HasPropagation(mode string) bool {
20
+	return false
21
+}
... ...
@@ -12,22 +12,14 @@ import (
12 12
 
13 13
 // read-write modes
14 14
 var rwModes = map[string]bool{
15
-	"rw":   true,
16
-	"rw,Z": true,
17
-	"rw,z": true,
18
-	"z,rw": true,
19
-	"Z,rw": true,
20
-	"Z":    true,
21
-	"z":    true,
15
+	"rw": true,
16
+	"ro": true,
22 17
 }
23 18
 
24
-// read-only modes
25
-var roModes = map[string]bool{
26
-	"ro":   true,
27
-	"ro,Z": true,
28
-	"ro,z": true,
29
-	"z,ro": true,
30
-	"Z,ro": true,
19
+// label modes
20
+var labelModes = map[string]bool{
21
+	"Z": true,
22
+	"z": true,
31 23
 }
32 24
 
33 25
 // BackwardsCompatible decides whether this mount point can be
... ...
@@ -51,7 +43,8 @@ func ParseMountSpec(spec, volumeDriver string) (*MountPoint, error) {
51 51
 	spec = filepath.ToSlash(spec)
52 52
 
53 53
 	mp := &MountPoint{
54
-		RW: true,
54
+		RW:          true,
55
+		Propagation: DefaultPropagationMode,
55 56
 	}
56 57
 	if strings.Count(spec, ":") > 2 {
57 58
 		return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec)
... ...
@@ -84,6 +77,7 @@ func ParseMountSpec(spec, volumeDriver string) (*MountPoint, error) {
84 84
 			return nil, derr.ErrorCodeVolumeInvalidMode.WithArgs(mp.Mode)
85 85
 		}
86 86
 		mp.RW = ReadWrite(mp.Mode)
87
+		mp.Propagation = GetPropagation(mp.Mode)
87 88
 	default:
88 89
 		return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec)
89 90
 	}
... ...
@@ -106,6 +100,17 @@ func ParseMountSpec(spec, volumeDriver string) (*MountPoint, error) {
106 106
 		if len(mp.Driver) == 0 {
107 107
 			mp.Driver = DefaultDriverName
108 108
 		}
109
+		// Named volumes can't have propagation properties specified.
110
+		// Their defaults will be decided by docker. This is just a
111
+		// safeguard. Don't want to get into situations where named
112
+		// volumes were mounted as '[r]shared' inside container and
113
+		// container does further mounts under that volume and these
114
+		// mounts become visible on  host and later original volume
115
+		// cleanup becomes an issue if container does not unmount
116
+		// submounts explicitly.
117
+		if HasPropagation(mp.Mode) {
118
+			return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec)
119
+		}
109 120
 	} else {
110 121
 		mp.Source = filepath.Clean(source)
111 122
 	}
... ...
@@ -130,3 +135,48 @@ func ParseVolumeSource(spec string) (string, string) {
130 130
 func IsVolumeNameValid(name string) (bool, error) {
131 131
 	return true, nil
132 132
 }
133
+
134
+// ValidMountMode will make sure the mount mode is valid.
135
+// returns if it's a valid mount mode or not.
136
+func ValidMountMode(mode string) bool {
137
+	rwModeCount := 0
138
+	labelModeCount := 0
139
+	propagationModeCount := 0
140
+
141
+	for _, o := range strings.Split(mode, ",") {
142
+		if rwModes[o] {
143
+			rwModeCount++
144
+			continue
145
+		} else if labelModes[o] {
146
+			labelModeCount++
147
+			continue
148
+		} else if propagationModes[o] {
149
+			propagationModeCount++
150
+			continue
151
+		}
152
+		return false
153
+	}
154
+
155
+	// Only one string for each mode is allowed.
156
+	if rwModeCount > 1 || labelModeCount > 1 || propagationModeCount > 1 {
157
+		return false
158
+	}
159
+	return true
160
+}
161
+
162
+// ReadWrite tells you if a mode string is a valid read-write mode or not.
163
+// If there are no specifications w.r.t read write mode, then by default
164
+// it returs true.
165
+func ReadWrite(mode string) bool {
166
+	if !ValidMountMode(mode) {
167
+		return false
168
+	}
169
+
170
+	for _, o := range strings.Split(mode, ",") {
171
+		if o == "ro" {
172
+			return false
173
+		}
174
+	}
175
+
176
+	return true
177
+}
... ...
@@ -186,3 +186,14 @@ func IsVolumeNameValid(name string) (bool, error) {
186 186
 	}
187 187
 	return true, nil
188 188
 }
189
+
190
+// ValidMountMode will make sure the mount mode is valid.
191
+// returns if it's a valid mount mode or not.
192
+func ValidMountMode(mode string) bool {
193
+	return roModes[strings.ToLower(mode)] || rwModes[strings.ToLower(mode)]
194
+}
195
+
196
+// ReadWrite tells you if a mode string is a valid read-write mode or not.
197
+func ReadWrite(mode string) bool {
198
+	return rwModes[strings.ToLower(mode)]
199
+}