Browse code

Volume refactoring for LCOW

Signed-off-by: Simon Ferquel <simon.ferquel@docker.com>

Simon Ferquel authored on 2017/08/02 02:32:44
Showing 33 changed files
... ...
@@ -435,6 +435,11 @@ func (container *Container) ShouldRestart() bool {
435 435
 
436 436
 // AddMountPointWithVolume adds a new mount point configured with a volume to the container.
437 437
 func (container *Container) AddMountPointWithVolume(destination string, vol volume.Volume, rw bool) {
438
+	operatingSystem := container.Platform
439
+	if operatingSystem == "" {
440
+		operatingSystem = runtime.GOOS
441
+	}
442
+	volumeParser := volume.NewParser(operatingSystem)
438 443
 	container.MountPoints[destination] = &volume.MountPoint{
439 444
 		Type:        mounttypes.TypeVolume,
440 445
 		Name:        vol.Name(),
... ...
@@ -442,7 +447,7 @@ func (container *Container) AddMountPointWithVolume(destination string, vol volu
442 442
 		Destination: destination,
443 443
 		RW:          rw,
444 444
 		Volume:      vol,
445
-		CopyData:    volume.DefaultCopyMode,
445
+		CopyData:    volumeParser.DefaultCopyMode(),
446 446
 	}
447 447
 }
448 448
 
... ...
@@ -68,6 +68,7 @@ func (container *Container) BuildHostnameFile() error {
68 68
 func (container *Container) NetworkMounts() []Mount {
69 69
 	var mounts []Mount
70 70
 	shared := container.HostConfig.NetworkMode.IsContainer()
71
+	parser := volume.NewParser(container.Platform)
71 72
 	if container.ResolvConfPath != "" {
72 73
 		if _, err := os.Stat(container.ResolvConfPath); err != nil {
73 74
 			logrus.Warnf("ResolvConfPath set to %q, but can't stat this filename (err = %v); skipping", container.ResolvConfPath, err)
... ...
@@ -83,7 +84,7 @@ func (container *Container) NetworkMounts() []Mount {
83 83
 				Source:      container.ResolvConfPath,
84 84
 				Destination: "/etc/resolv.conf",
85 85
 				Writable:    writable,
86
-				Propagation: string(volume.DefaultPropagationMode),
86
+				Propagation: string(parser.DefaultPropagationMode()),
87 87
 			})
88 88
 		}
89 89
 	}
... ...
@@ -102,7 +103,7 @@ func (container *Container) NetworkMounts() []Mount {
102 102
 				Source:      container.HostnamePath,
103 103
 				Destination: "/etc/hostname",
104 104
 				Writable:    writable,
105
-				Propagation: string(volume.DefaultPropagationMode),
105
+				Propagation: string(parser.DefaultPropagationMode()),
106 106
 			})
107 107
 		}
108 108
 	}
... ...
@@ -121,7 +122,7 @@ func (container *Container) NetworkMounts() []Mount {
121 121
 				Source:      container.HostsPath,
122 122
 				Destination: "/etc/hosts",
123 123
 				Writable:    writable,
124
-				Propagation: string(volume.DefaultPropagationMode),
124
+				Propagation: string(parser.DefaultPropagationMode()),
125 125
 			})
126 126
 		}
127 127
 	}
... ...
@@ -196,6 +197,7 @@ func (container *Container) UnmountIpcMount(unmount func(pth string) error) erro
196 196
 // IpcMounts returns the list of IPC mounts
197 197
 func (container *Container) IpcMounts() []Mount {
198 198
 	var mounts []Mount
199
+	parser := volume.NewParser(container.Platform)
199 200
 
200 201
 	if container.HasMountFor("/dev/shm") {
201 202
 		return mounts
... ...
@@ -209,7 +211,7 @@ func (container *Container) IpcMounts() []Mount {
209 209
 		Source:      container.ShmPath,
210 210
 		Destination: "/dev/shm",
211 211
 		Writable:    true,
212
-		Propagation: string(volume.DefaultPropagationMode),
212
+		Propagation: string(parser.DefaultPropagationMode()),
213 213
 	})
214 214
 
215 215
 	return mounts
... ...
@@ -429,6 +431,7 @@ func copyOwnership(source, destination string) error {
429 429
 
430 430
 // TmpfsMounts returns the list of tmpfs mounts
431 431
 func (container *Container) TmpfsMounts() ([]Mount, error) {
432
+	parser := volume.NewParser(container.Platform)
432 433
 	var mounts []Mount
433 434
 	for dest, data := range container.HostConfig.Tmpfs {
434 435
 		mounts = append(mounts, Mount{
... ...
@@ -439,7 +442,7 @@ func (container *Container) TmpfsMounts() ([]Mount, error) {
439 439
 	}
440 440
 	for dest, mnt := range container.MountPoints {
441 441
 		if mnt.Type == mounttypes.TypeTmpfs {
442
-			data, err := volume.ConvertTmpfsOptions(mnt.Spec.TmpfsOptions, mnt.Spec.ReadOnly)
442
+			data, err := parser.ConvertTmpfsOptions(mnt.Spec.TmpfsOptions, mnt.Spec.ReadOnly)
443 443
 			if err != nil {
444 444
 				return nil, err
445 445
 			}
... ...
@@ -4,6 +4,7 @@ package daemon
4 4
 
5 5
 import (
6 6
 	"github.com/docker/docker/container"
7
+	"github.com/docker/docker/volume"
7 8
 )
8 9
 
9 10
 // checkIfPathIsInAVolume checks if the path is in a volume. If it is, it
... ...
@@ -11,8 +12,9 @@ import (
11 11
 // cannot be configured with a read-only rootfs.
12 12
 func checkIfPathIsInAVolume(container *container.Container, absPath string) (bool, error) {
13 13
 	var toVolume bool
14
+	parser := volume.NewParser(container.Platform)
14 15
 	for _, mnt := range container.MountPoints {
15
-		if toVolume = mnt.HasResource(absPath); toVolume {
16
+		if toVolume = parser.HasResource(mnt, absPath); toVolume {
16 17
 			if mnt.RW {
17 18
 				break
18 19
 			}
... ...
@@ -26,10 +26,10 @@ func (daemon *Daemon) createContainerPlatformSpecificSettings(container *contain
26 26
 		}
27 27
 		hostConfig.Isolation = "hyperv"
28 28
 	}
29
-
29
+	parser := volume.NewParser(container.Platform)
30 30
 	for spec := range config.Volumes {
31 31
 
32
-		mp, err := volume.ParseMountRaw(spec, hostConfig.VolumeDriver)
32
+		mp, err := parser.ParseMountRaw(spec, hostConfig.VolumeDriver)
33 33
 		if err != nil {
34 34
 			return fmt.Errorf("Unrecognised volume spec: %v", err)
35 35
 		}
... ...
@@ -612,8 +612,9 @@ func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes.
612 612
 		return warnings, fmt.Errorf("Unknown runtime specified %s", hostConfig.Runtime)
613 613
 	}
614 614
 
615
+	parser := volume.NewParser(runtime.GOOS)
615 616
 	for dest := range hostConfig.Tmpfs {
616
-		if err := volume.ValidateTmpfsMountDestination(dest); err != nil {
617
+		if err := parser.ValidateTmpfsMountDestination(dest); err != nil {
617 618
 			return warnings, err
618 619
 		}
619 620
 	}
... ...
@@ -498,6 +498,7 @@ func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []c
498 498
 
499 499
 	// Filter out mounts from spec
500 500
 	noIpc := c.HostConfig.IpcMode.IsNone()
501
+	// Filter out mounts that are overridden by user supplied mounts
501 502
 	var defaultMounts []specs.Mount
502 503
 	_, mountDev := userMounts["/dev"]
503 504
 	for _, m := range s.Mounts {
... ...
@@ -524,7 +525,8 @@ func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []c
524 524
 
525 525
 		if m.Source == "tmpfs" {
526 526
 			data := m.Data
527
-			options := []string{"noexec", "nosuid", "nodev", string(volume.DefaultPropagationMode)}
527
+			parser := volume.NewParser("linux")
528
+			options := []string{"noexec", "nosuid", "nodev", string(parser.DefaultPropagationMode())}
528 529
 			if data != "" {
529 530
 				options = append(options, strings.Split(data, ",")...)
530 531
 			}
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"fmt"
5 5
 	"io/ioutil"
6 6
 	"path/filepath"
7
+	"runtime"
7 8
 	"strings"
8 9
 
9 10
 	containertypes "github.com/docker/docker/api/types/container"
... ...
@@ -108,6 +109,11 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
108 108
 		if !mount.Writable {
109 109
 			m.Options = append(m.Options, "ro")
110 110
 		}
111
+		if img.OS != runtime.GOOS {
112
+			m.Type = "bind"
113
+			m.Options = append(m.Options, "rbind")
114
+			m.Options = append(m.Options, fmt.Sprintf("uvmpath=/tmp/gcs/%s/binds", c.ID))
115
+		}
111 116
 		s.Mounts = append(s.Mounts, m)
112 117
 	}
113 118
 
... ...
@@ -75,6 +75,7 @@ func (m mounts) parts(i int) int {
75 75
 func (daemon *Daemon) registerMountPoints(container *container.Container, hostConfig *containertypes.HostConfig) (retErr error) {
76 76
 	binds := map[string]bool{}
77 77
 	mountPoints := map[string]*volume.MountPoint{}
78
+	parser := volume.NewParser(container.Platform)
78 79
 	defer func() {
79 80
 		// clean up the container mountpoints once return with error
80 81
 		if retErr != nil {
... ...
@@ -103,7 +104,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
103 103
 
104 104
 	// 2. Read volumes from other containers.
105 105
 	for _, v := range hostConfig.VolumesFrom {
106
-		containerID, mode, err := volume.ParseVolumesFrom(v)
106
+		containerID, mode, err := parser.ParseVolumesFrom(v)
107 107
 		if err != nil {
108 108
 			return err
109 109
 		}
... ...
@@ -118,7 +119,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
118 118
 				Type:        m.Type,
119 119
 				Name:        m.Name,
120 120
 				Source:      m.Source,
121
-				RW:          m.RW && volume.ReadWrite(mode),
121
+				RW:          m.RW && parser.ReadWrite(mode),
122 122
 				Driver:      m.Driver,
123 123
 				Destination: m.Destination,
124 124
 				Propagation: m.Propagation,
... ...
@@ -140,7 +141,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
140 140
 
141 141
 	// 3. Read bind mounts
142 142
 	for _, b := range hostConfig.Binds {
143
-		bind, err := volume.ParseMountRaw(b, hostConfig.VolumeDriver)
143
+		bind, err := parser.ParseMountRaw(b, hostConfig.VolumeDriver)
144 144
 		if err != nil {
145 145
 			return err
146 146
 		}
... ...
@@ -172,7 +173,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
172 172
 	}
173 173
 
174 174
 	for _, cfg := range hostConfig.Mounts {
175
-		mp, err := volume.ParseMountSpec(cfg)
175
+		mp, err := parser.ParseMountSpec(cfg)
176 176
 		if err != nil {
177 177
 			return validationError{err}
178 178
 		}
... ...
@@ -217,7 +218,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
217 217
 
218 218
 	// 4. Cleanup old volumes that are about to be reassigned.
219 219
 	for _, m := range mountPoints {
220
-		if m.BackwardsCompatible() {
220
+		if parser.IsBackwardCompatible(m) {
221 221
 			if mp, exists := container.MountPoints[m.Destination]; exists && mp.Volume != nil {
222 222
 				daemon.volumes.Dereference(mp.Volume, container.ID)
223 223
 			}
... ...
@@ -252,6 +253,8 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) {
252 252
 	container.Lock()
253 253
 	defer container.Unlock()
254 254
 
255
+	parser := volume.NewParser(container.Platform)
256
+
255 257
 	maybeUpdate := make(map[string]bool)
256 258
 	for _, mp := range container.MountPoints {
257 259
 		if mp.Spec.Source != "" && mp.Type != "" {
... ...
@@ -270,7 +273,7 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) {
270 270
 
271 271
 	binds := make(map[string]*volume.MountPoint, len(container.HostConfig.Binds))
272 272
 	for _, rawSpec := range container.HostConfig.Binds {
273
-		mp, err := volume.ParseMountRaw(rawSpec, container.HostConfig.VolumeDriver)
273
+		mp, err := parser.ParseMountRaw(rawSpec, container.HostConfig.VolumeDriver)
274 274
 		if err != nil {
275 275
 			logrus.WithError(err).Error("Got unexpected error while re-parsing raw volume spec during spec backport")
276 276
 			continue
... ...
@@ -280,7 +283,7 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) {
280 280
 
281 281
 	volumesFrom := make(map[string]volume.MountPoint)
282 282
 	for _, fromSpec := range container.HostConfig.VolumesFrom {
283
-		from, _, err := volume.ParseVolumesFrom(fromSpec)
283
+		from, _, err := parser.ParseVolumesFrom(fromSpec)
284 284
 		if err != nil {
285 285
 			logrus.WithError(err).WithField("id", container.ID).Error("Error reading volumes-from spec during mount spec backport")
286 286
 			continue
... ...
@@ -1,6 +1,7 @@
1 1
 package daemon
2 2
 
3 3
 import (
4
+	"runtime"
4 5
 	"testing"
5 6
 
6 7
 	"github.com/docker/docker/volume"
... ...
@@ -20,8 +21,10 @@ func TestParseVolumesFrom(t *testing.T) {
20 20
 		{"foobar:baz", "", "", true},
21 21
 	}
22 22
 
23
+	parser := volume.NewParser(runtime.GOOS)
24
+
23 25
 	for _, c := range cases {
24
-		id, mode, err := volume.ParseVolumesFrom(c.spec)
26
+		id, mode, err := parser.ParseVolumesFrom(c.spec)
25 27
 		if c.fail {
26 28
 			if err == nil {
27 29
 				t.Fatalf("Expected error, was nil, for spec %s\n", c.spec)
... ...
@@ -7,6 +7,7 @@ import (
7 7
 	"io"
8 8
 	"io/ioutil"
9 9
 	"os"
10
+	"path"
10 11
 	"path/filepath"
11 12
 	"regexp"
12 13
 	"strings"
... ...
@@ -388,11 +389,101 @@ func (clnt *client) createLinux(containerID string, checkpoint string, checkpoin
388 388
 		configuration.NetworkSharedContainerName = spec.Windows.Network.NetworkSharedContainerName
389 389
 	}
390 390
 
391
+	// Add the mounts (volumes, bind mounts etc) to the structure. We have to do
392
+	// some translation for both the mapped directories passed into HCS and in
393
+	// the spec.
394
+	//
395
+	// For HCS, we only pass in the mounts from the spec which are type "bind".
396
+	// Further, the "ContainerPath" field (which is a little mis-leadingly
397
+	// named when it applies to the utility VM rather than the container in the
398
+	// utility VM) is moved to under /tmp/gcs/<ID>/binds, where this is passed
399
+	// by the caller through a 'uvmpath' option.
400
+	//
401
+	// We do similar translation for the mounts in the spec by stripping out
402
+	// the uvmpath option, and translating the Source path to the location in the
403
+	// utility VM calculated above.
404
+	//
405
+	// From inside the utility VM, you would see a 9p mount such as in the following
406
+	// where a host folder has been mapped to /target. The line with /tmp/gcs/<ID>/binds
407
+	// specifically:
408
+	//
409
+	//	/ # mount
410
+	//	rootfs on / type rootfs (rw,size=463736k,nr_inodes=115934)
411
+	//	proc on /proc type proc (rw,relatime)
412
+	//	sysfs on /sys type sysfs (rw,relatime)
413
+	//	udev on /dev type devtmpfs (rw,relatime,size=498100k,nr_inodes=124525,mode=755)
414
+	//	tmpfs on /run type tmpfs (rw,relatime)
415
+	//	cgroup on /sys/fs/cgroup type cgroup (rw,relatime,cpuset,cpu,cpuacct,blkio,memory,devices,freezer,net_cls,perf_event,net_prio,hugetlb,pids,rdma)
416
+	//	mqueue on /dev/mqueue type mqueue (rw,relatime)
417
+	//	devpts on /dev/pts type devpts (rw,relatime,mode=600,ptmxmode=000)
418
+	//	/binds/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/target on /binds/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/target type 9p (rw,sync,dirsync,relatime,trans=fd,rfdno=6,wfdno=6)
419
+	//	/dev/pmem0 on /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/layer0 type ext4 (ro,relatime,block_validity,delalloc,norecovery,barrier,dax,user_xattr,acl)
420
+	//	/dev/sda on /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/scratch type ext4 (rw,relatime,block_validity,delalloc,barrier,user_xattr,acl)
421
+	//	overlay on /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/rootfs type overlay (rw,relatime,lowerdir=/tmp/base/:/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/layer0,upperdir=/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/scratch/upper,workdir=/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/scratch/work)
422
+	//
423
+	//  /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc # ls -l
424
+	//	total 16
425
+	//	drwx------    3 0        0               60 Sep  7 18:54 binds
426
+	//	-rw-r--r--    1 0        0             3345 Sep  7 18:54 config.json
427
+	//	drwxr-xr-x   10 0        0             4096 Sep  6 17:26 layer0
428
+	//	drwxr-xr-x    1 0        0             4096 Sep  7 18:54 rootfs
429
+	//	drwxr-xr-x    5 0        0             4096 Sep  7 18:54 scratch
430
+	//
431
+	//	/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc # ls -l binds
432
+	//	total 0
433
+	//	drwxrwxrwt    2 0        0             4096 Sep  7 16:51 target
434
+
435
+	mds := []hcsshim.MappedDir{}
436
+	specMounts := []specs.Mount{}
437
+	for _, mount := range spec.Mounts {
438
+		specMount := mount
439
+		if mount.Type == "bind" {
440
+			// Strip out the uvmpath from the options
441
+			updatedOptions := []string{}
442
+			uvmPath := ""
443
+			readonly := false
444
+			for _, opt := range mount.Options {
445
+				dropOption := false
446
+				elements := strings.SplitN(opt, "=", 2)
447
+				switch elements[0] {
448
+				case "uvmpath":
449
+					uvmPath = elements[1]
450
+					dropOption = true
451
+				case "rw":
452
+				case "ro":
453
+					readonly = true
454
+				case "rbind":
455
+				default:
456
+					return fmt.Errorf("unsupported option %q", opt)
457
+				}
458
+				if !dropOption {
459
+					updatedOptions = append(updatedOptions, opt)
460
+				}
461
+			}
462
+			mount.Options = updatedOptions
463
+			if uvmPath == "" {
464
+				return fmt.Errorf("no uvmpath for bind mount %+v", mount)
465
+			}
466
+			md := hcsshim.MappedDir{
467
+				HostPath:          mount.Source,
468
+				ContainerPath:     path.Join(uvmPath, mount.Destination),
469
+				CreateInUtilityVM: true,
470
+				ReadOnly:          readonly,
471
+			}
472
+			mds = append(mds, md)
473
+			specMount.Source = path.Join(uvmPath, mount.Destination)
474
+		}
475
+		specMounts = append(specMounts, specMount)
476
+	}
477
+	configuration.MappedDirectories = mds
478
+
391 479
 	hcsContainer, err := hcsshim.CreateContainer(containerID, configuration)
392 480
 	if err != nil {
393 481
 		return err
394 482
 	}
395 483
 
484
+	spec.Mounts = specMounts
485
+
396 486
 	// Construct a container object for calling start on it.
397 487
 	container := &container{
398 488
 		containerCommon: containerCommon{
... ...
@@ -7,8 +7,6 @@ import (
7 7
 	"github.com/docker/docker/api/types/container"
8 8
 	networktypes "github.com/docker/docker/api/types/network"
9 9
 	"github.com/docker/docker/pkg/sysinfo"
10
-	"github.com/docker/docker/volume"
11
-	"github.com/pkg/errors"
12 10
 )
13 11
 
14 12
 // ContainerDecoder implements httputils.ContainerDecoder
... ...
@@ -46,11 +44,6 @@ func decodeContainerConfig(src io.Reader) (*container.Config, *container.HostCon
46 46
 		if w.Config.Volumes == nil {
47 47
 			w.Config.Volumes = make(map[string]struct{})
48 48
 		}
49
-
50
-		// Now validate all the volumes and binds
51
-		if err := validateMountSettings(w.Config, hc); err != nil {
52
-			return nil, nil, nil, err
53
-		}
54 49
 	}
55 50
 
56 51
 	// Certain parameters need daemon-side validation that cannot be done
... ...
@@ -86,23 +79,3 @@ func decodeContainerConfig(src io.Reader) (*container.Config, *container.HostCon
86 86
 
87 87
 	return w.Config, hc, w.NetworkingConfig, nil
88 88
 }
89
-
90
-// validateMountSettings validates each of the volumes and bind settings
91
-// passed by the caller to ensure they are valid.
92
-func validateMountSettings(c *container.Config, hc *container.HostConfig) error {
93
-	// it is ok to have len(hc.Mounts) > 0 && (len(hc.Binds) > 0 || len (c.Volumes) > 0 || len (hc.Tmpfs) > 0 )
94
-
95
-	// Ensure all volumes and binds are valid.
96
-	for spec := range c.Volumes {
97
-		if _, err := volume.ParseMountRaw(spec, hc.VolumeDriver); err != nil {
98
-			return errors.Wrapf(err, "invalid volume spec %q", spec)
99
-		}
100
-	}
101
-	for _, spec := range hc.Binds {
102
-		if _, err := volume.ParseMountRaw(spec, hc.VolumeDriver); err != nil {
103
-			return errors.Wrapf(err, "invalid bind mount spec %q", spec)
104
-		}
105
-	}
106
-
107
-	return nil
108
-}
... ...
@@ -9,12 +9,9 @@ import (
9 9
 	"strings"
10 10
 	"testing"
11 11
 
12
-	"os"
13
-
14 12
 	"github.com/docker/docker/api/types/container"
15 13
 	networktypes "github.com/docker/docker/api/types/network"
16 14
 	"github.com/docker/docker/api/types/strslice"
17
-	"github.com/gotestyourself/gotestyourself/skip"
18 15
 	"github.com/stretchr/testify/assert"
19 16
 	"github.com/stretchr/testify/require"
20 17
 )
... ...
@@ -143,103 +140,6 @@ func callDecodeContainerConfigIsolation(isolation string) (*container.Config, *c
143 143
 	return decodeContainerConfig(bytes.NewReader(b))
144 144
 }
145 145
 
146
-func TestDecodeContainerConfigWithVolumes(t *testing.T) {
147
-	var testcases = []decodeConfigTestcase{
148
-		{
149
-			doc:         "no paths volume",
150
-			wrapper:     containerWrapperWithVolume(":"),
151
-			expectedErr: `invalid volume specification: ':'`,
152
-		},
153
-		{
154
-			doc:         "no paths bind",
155
-			wrapper:     containerWrapperWithBind(":"),
156
-			expectedErr: `invalid volume specification: ':'`,
157
-		},
158
-		{
159
-			doc:         "no paths or mode volume",
160
-			wrapper:     containerWrapperWithVolume("::"),
161
-			expectedErr: `invalid volume specification: '::'`,
162
-		},
163
-		{
164
-			doc:         "no paths or mode bind",
165
-			wrapper:     containerWrapperWithBind("::"),
166
-			expectedErr: `invalid volume specification: '::'`,
167
-		},
168
-	}
169
-	for _, testcase := range testcases {
170
-		t.Run(testcase.doc, runDecodeContainerConfigTestCase(testcase))
171
-	}
172
-}
173
-
174
-func TestDecodeContainerConfigWithVolumesUnix(t *testing.T) {
175
-	skip.IfCondition(t, runtime.GOOS == "windows")
176
-
177
-	baseErr := `invalid mount config for type "volume": invalid specification: `
178
-	var testcases = []decodeConfigTestcase{
179
-		{
180
-			doc:         "root to root volume",
181
-			wrapper:     containerWrapperWithVolume("/:/"),
182
-			expectedErr: `invalid volume specification: '/:/'`,
183
-		},
184
-		{
185
-			doc:         "root to root bind",
186
-			wrapper:     containerWrapperWithBind("/:/"),
187
-			expectedErr: `invalid volume specification: '/:/'`,
188
-		},
189
-		{
190
-			doc:         "no destination path volume",
191
-			wrapper:     containerWrapperWithVolume(`/tmp:`),
192
-			expectedErr: ` invalid volume specification: '/tmp:'`,
193
-		},
194
-		{
195
-			doc:         "no destination path bind",
196
-			wrapper:     containerWrapperWithBind(`/tmp:`),
197
-			expectedErr: ` invalid volume specification: '/tmp:'`,
198
-		},
199
-		{
200
-			doc:         "no destination path or mode volume",
201
-			wrapper:     containerWrapperWithVolume(`/tmp::`),
202
-			expectedErr: `invalid mount config for type "bind": field Target must not be empty`,
203
-		},
204
-		{
205
-			doc:         "no destination path or mode bind",
206
-			wrapper:     containerWrapperWithBind(`/tmp::`),
207
-			expectedErr: `invalid mount config for type "bind": field Target must not be empty`,
208
-		},
209
-		{
210
-			doc:         "too many sections volume",
211
-			wrapper:     containerWrapperWithVolume(`/tmp:/tmp:/tmp:/tmp`),
212
-			expectedErr: `invalid volume specification: '/tmp:/tmp:/tmp:/tmp'`,
213
-		},
214
-		{
215
-			doc:         "too many sections bind",
216
-			wrapper:     containerWrapperWithBind(`/tmp:/tmp:/tmp:/tmp`),
217
-			expectedErr: `invalid volume specification: '/tmp:/tmp:/tmp:/tmp'`,
218
-		},
219
-		{
220
-			doc:         "just root volume",
221
-			wrapper:     containerWrapperWithVolume("/"),
222
-			expectedErr: baseErr + `destination can't be '/'`,
223
-		},
224
-		{
225
-			doc:         "just root bind",
226
-			wrapper:     containerWrapperWithBind("/"),
227
-			expectedErr: baseErr + `destination can't be '/'`,
228
-		},
229
-		{
230
-			doc:     "bind mount passed as a volume",
231
-			wrapper: containerWrapperWithVolume(`/foo:/bar`),
232
-			expectedConfig: &container.Config{
233
-				Volumes: map[string]struct{}{`/foo:/bar`: {}},
234
-			},
235
-			expectedHostConfig: &container.HostConfig{NetworkMode: "default"},
236
-		},
237
-	}
238
-	for _, testcase := range testcases {
239
-		t.Run(testcase.doc, runDecodeContainerConfigTestCase(testcase))
240
-	}
241
-}
242
-
243 146
 type decodeConfigTestcase struct {
244 147
 	doc                string
245 148
 	wrapper            ContainerConfigWrapper
... ...
@@ -266,89 +166,6 @@ func runDecodeContainerConfigTestCase(testcase decodeConfigTestcase) func(t *tes
266 266
 	}
267 267
 }
268 268
 
269
-func TestDecodeContainerConfigWithVolumesWindows(t *testing.T) {
270
-	skip.IfCondition(t, runtime.GOOS != "windows")
271
-
272
-	tmpDir := os.Getenv("TEMP")
273
-	systemDrive := os.Getenv("SystemDrive")
274
-	var testcases = []decodeConfigTestcase{
275
-		{
276
-			doc:         "root to root volume",
277
-			wrapper:     containerWrapperWithVolume(systemDrive + `\:c:\`),
278
-			expectedErr: `invalid volume specification: `,
279
-		},
280
-		{
281
-			doc:         "root to root bind",
282
-			wrapper:     containerWrapperWithBind(systemDrive + `\:c:\`),
283
-			expectedErr: `invalid volume specification: `,
284
-		},
285
-		{
286
-			doc:         "no destination path volume",
287
-			wrapper:     containerWrapperWithVolume(tmpDir + `\:`),
288
-			expectedErr: `invalid volume specification: `,
289
-		},
290
-		{
291
-			doc:         "no destination path bind",
292
-			wrapper:     containerWrapperWithBind(tmpDir + `\:`),
293
-			expectedErr: `invalid volume specification: `,
294
-		},
295
-		{
296
-			doc:         "no destination path or mode volume",
297
-			wrapper:     containerWrapperWithVolume(tmpDir + `\::`),
298
-			expectedErr: `invalid volume specification: `,
299
-		},
300
-		{
301
-			doc:         "no destination path or mode bind",
302
-			wrapper:     containerWrapperWithBind(tmpDir + `\::`),
303
-			expectedErr: `invalid volume specification: `,
304
-		},
305
-		{
306
-			doc:         "too many sections volume",
307
-			wrapper:     containerWrapperWithVolume(tmpDir + ":" + tmpDir + ":" + tmpDir + ":" + tmpDir),
308
-			expectedErr: `invalid volume specification: `,
309
-		},
310
-		{
311
-			doc:         "too many sections bind",
312
-			wrapper:     containerWrapperWithBind(tmpDir + ":" + tmpDir + ":" + tmpDir + ":" + tmpDir),
313
-			expectedErr: `invalid volume specification: `,
314
-		},
315
-		{
316
-			doc:         "no drive letter volume",
317
-			wrapper:     containerWrapperWithVolume(`\tmp`),
318
-			expectedErr: `invalid volume specification: `,
319
-		},
320
-		{
321
-			doc:         "no drive letter bind",
322
-			wrapper:     containerWrapperWithBind(`\tmp`),
323
-			expectedErr: `invalid volume specification: `,
324
-		},
325
-		{
326
-			doc:         "root to c-drive volume",
327
-			wrapper:     containerWrapperWithVolume(systemDrive + `\:c:`),
328
-			expectedErr: `invalid volume specification: `,
329
-		},
330
-		{
331
-			doc:         "root to c-drive bind",
332
-			wrapper:     containerWrapperWithBind(systemDrive + `\:c:`),
333
-			expectedErr: `invalid volume specification: `,
334
-		},
335
-		{
336
-			doc:         "container path without driver letter volume",
337
-			wrapper:     containerWrapperWithVolume(`c:\windows:\somewhere`),
338
-			expectedErr: `invalid volume specification: `,
339
-		},
340
-		{
341
-			doc:         "container path without driver letter bind",
342
-			wrapper:     containerWrapperWithBind(`c:\windows:\somewhere`),
343
-			expectedErr: `invalid volume specification: `,
344
-		},
345
-	}
346
-
347
-	for _, testcase := range testcases {
348
-		t.Run(testcase.doc, runDecodeContainerConfigTestCase(testcase))
349
-	}
350
-}
351
-
352 269
 func marshal(t *testing.T, w ContainerConfigWrapper, doc string) []byte {
353 270
 	b, err := json.Marshal(w)
354 271
 	require.NoError(t, err, "%s: failed to encode config wrapper", doc)
355 272
new file mode 100644
... ...
@@ -0,0 +1,35 @@
0
+package volume
1
+
2
+import (
3
+	"errors"
4
+	"fmt"
5
+	"path"
6
+
7
+	"github.com/docker/docker/api/types/mount"
8
+)
9
+
10
+var lcowSpecificValidators mountValidator = func(m *mount.Mount) error {
11
+	if path.Clean(m.Target) == "/" {
12
+		return fmt.Errorf("invalid specification: destination can't be '/'")
13
+	}
14
+	if m.Type == mount.TypeNamedPipe {
15
+		return errors.New("Linux containers on Windows do not support named pipe mounts")
16
+	}
17
+	return nil
18
+}
19
+
20
+type lcowParser struct {
21
+	windowsParser
22
+}
23
+
24
+func (p *lcowParser) validateMountConfig(mnt *mount.Mount) error {
25
+	return p.validateMountConfigReg(mnt, rxLCOWDestination, lcowSpecificValidators)
26
+}
27
+
28
+func (p *lcowParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
29
+	return p.parseMountRaw(raw, volumeDriver, rxLCOWDestination, false, lcowSpecificValidators)
30
+}
31
+
32
+func (p *lcowParser) ParseMountSpec(cfg mount.Mount) (*MountPoint, error) {
33
+	return p.parseMountSpec(cfg, rxLCOWDestination, false, lcowSpecificValidators)
34
+}
0 35
new file mode 100644
... ...
@@ -0,0 +1,416 @@
0
+package volume
1
+
2
+import (
3
+	"errors"
4
+	"fmt"
5
+	"path"
6
+	"path/filepath"
7
+	"strings"
8
+
9
+	"github.com/docker/docker/api/types/mount"
10
+	"github.com/docker/docker/pkg/stringid"
11
+)
12
+
13
+type linuxParser struct {
14
+}
15
+
16
+func linuxSplitRawSpec(raw string) ([]string, error) {
17
+	if strings.Count(raw, ":") > 2 {
18
+		return nil, errInvalidSpec(raw)
19
+	}
20
+
21
+	arr := strings.SplitN(raw, ":", 3)
22
+	if arr[0] == "" {
23
+		return nil, errInvalidSpec(raw)
24
+	}
25
+	return arr, nil
26
+}
27
+
28
+func linuxValidateNotRoot(p string) error {
29
+	p = path.Clean(strings.Replace(p, `\`, `/`, -1))
30
+	if p == "/" {
31
+		return fmt.Errorf("invalid specification: destination can't be '/'")
32
+	}
33
+	return nil
34
+}
35
+func linuxValidateAbsolute(p string) error {
36
+	p = strings.Replace(p, `\`, `/`, -1)
37
+	if path.IsAbs(p) {
38
+		return nil
39
+	}
40
+	return fmt.Errorf("invalid mount path: '%s' mount path must be absolute", p)
41
+}
42
+func (p *linuxParser) validateMountConfig(mnt *mount.Mount) error {
43
+	// there was something looking like a bug in existing codebase:
44
+	// - validateMountConfig on linux was called with options skipping bind source existance when calling ParseMountRaw
45
+	// - but not when calling ParseMountSpec directly... nor when the unit test called it directly
46
+	return p.validateMountConfigImpl(mnt, true)
47
+}
48
+func (p *linuxParser) validateMountConfigImpl(mnt *mount.Mount, validateBindSourceExists bool) error {
49
+	if len(mnt.Target) == 0 {
50
+		return &errMountConfig{mnt, errMissingField("Target")}
51
+	}
52
+
53
+	if err := linuxValidateNotRoot(mnt.Target); err != nil {
54
+		return &errMountConfig{mnt, err}
55
+	}
56
+
57
+	if err := linuxValidateAbsolute(mnt.Target); err != nil {
58
+		return &errMountConfig{mnt, err}
59
+	}
60
+
61
+	switch mnt.Type {
62
+	case mount.TypeBind:
63
+		if len(mnt.Source) == 0 {
64
+			return &errMountConfig{mnt, errMissingField("Source")}
65
+		}
66
+		// Don't error out just because the propagation mode is not supported on the platform
67
+		if opts := mnt.BindOptions; opts != nil {
68
+			if len(opts.Propagation) > 0 && len(linuxPropagationModes) > 0 {
69
+				if _, ok := linuxPropagationModes[opts.Propagation]; !ok {
70
+					return &errMountConfig{mnt, fmt.Errorf("invalid propagation mode: %s", opts.Propagation)}
71
+				}
72
+			}
73
+		}
74
+		if mnt.VolumeOptions != nil {
75
+			return &errMountConfig{mnt, errExtraField("VolumeOptions")}
76
+		}
77
+
78
+		if err := linuxValidateAbsolute(mnt.Source); err != nil {
79
+			return &errMountConfig{mnt, err}
80
+		}
81
+
82
+		if validateBindSourceExists {
83
+			exists, _, _ := currentFileInfoProvider.fileInfo(mnt.Source)
84
+			if !exists {
85
+				return &errMountConfig{mnt, errBindNotExist}
86
+			}
87
+		}
88
+
89
+	case mount.TypeVolume:
90
+		if mnt.BindOptions != nil {
91
+			return &errMountConfig{mnt, errExtraField("BindOptions")}
92
+		}
93
+
94
+		if len(mnt.Source) == 0 && mnt.ReadOnly {
95
+			return &errMountConfig{mnt, fmt.Errorf("must not set ReadOnly mode when using anonymous volumes")}
96
+		}
97
+	case mount.TypeTmpfs:
98
+		if len(mnt.Source) != 0 {
99
+			return &errMountConfig{mnt, errExtraField("Source")}
100
+		}
101
+		if _, err := p.ConvertTmpfsOptions(mnt.TmpfsOptions, mnt.ReadOnly); err != nil {
102
+			return &errMountConfig{mnt, err}
103
+		}
104
+	default:
105
+		return &errMountConfig{mnt, errors.New("mount type unknown")}
106
+	}
107
+	return nil
108
+}
109
+
110
+// read-write modes
111
+var rwModes = map[string]bool{
112
+	"rw": true,
113
+	"ro": true,
114
+}
115
+
116
+// label modes
117
+var linuxLabelModes = map[string]bool{
118
+	"Z": true,
119
+	"z": true,
120
+}
121
+
122
+// consistency modes
123
+var linuxConsistencyModes = map[mount.Consistency]bool{
124
+	mount.ConsistencyFull:      true,
125
+	mount.ConsistencyCached:    true,
126
+	mount.ConsistencyDelegated: true,
127
+}
128
+var linuxPropagationModes = map[mount.Propagation]bool{
129
+	mount.PropagationPrivate:  true,
130
+	mount.PropagationRPrivate: true,
131
+	mount.PropagationSlave:    true,
132
+	mount.PropagationRSlave:   true,
133
+	mount.PropagationShared:   true,
134
+	mount.PropagationRShared:  true,
135
+}
136
+
137
+const linuxDefaultPropagationMode = mount.PropagationRPrivate
138
+
139
+func linuxGetPropagation(mode string) mount.Propagation {
140
+	for _, o := range strings.Split(mode, ",") {
141
+		prop := mount.Propagation(o)
142
+		if linuxPropagationModes[prop] {
143
+			return prop
144
+		}
145
+	}
146
+	return linuxDefaultPropagationMode
147
+}
148
+
149
+func linuxHasPropagation(mode string) bool {
150
+	for _, o := range strings.Split(mode, ",") {
151
+		if linuxPropagationModes[mount.Propagation(o)] {
152
+			return true
153
+		}
154
+	}
155
+	return false
156
+}
157
+
158
+func linuxValidMountMode(mode string) bool {
159
+	if mode == "" {
160
+		return true
161
+	}
162
+
163
+	rwModeCount := 0
164
+	labelModeCount := 0
165
+	propagationModeCount := 0
166
+	copyModeCount := 0
167
+	consistencyModeCount := 0
168
+
169
+	for _, o := range strings.Split(mode, ",") {
170
+		switch {
171
+		case rwModes[o]:
172
+			rwModeCount++
173
+		case linuxLabelModes[o]:
174
+			labelModeCount++
175
+		case linuxPropagationModes[mount.Propagation(o)]:
176
+			propagationModeCount++
177
+		case copyModeExists(o):
178
+			copyModeCount++
179
+		case linuxConsistencyModes[mount.Consistency(o)]:
180
+			consistencyModeCount++
181
+		default:
182
+			return false
183
+		}
184
+	}
185
+
186
+	// Only one string for each mode is allowed.
187
+	if rwModeCount > 1 || labelModeCount > 1 || propagationModeCount > 1 || copyModeCount > 1 || consistencyModeCount > 1 {
188
+		return false
189
+	}
190
+	return true
191
+}
192
+
193
+func (p *linuxParser) ReadWrite(mode string) bool {
194
+	if !linuxValidMountMode(mode) {
195
+		return false
196
+	}
197
+
198
+	for _, o := range strings.Split(mode, ",") {
199
+		if o == "ro" {
200
+			return false
201
+		}
202
+	}
203
+	return true
204
+}
205
+
206
+func (p *linuxParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
207
+	arr, err := linuxSplitRawSpec(raw)
208
+	if err != nil {
209
+		return nil, err
210
+	}
211
+
212
+	var spec mount.Mount
213
+	var mode string
214
+	switch len(arr) {
215
+	case 1:
216
+		// Just a destination path in the container
217
+		spec.Target = arr[0]
218
+	case 2:
219
+		if linuxValidMountMode(arr[1]) {
220
+			// Destination + Mode is not a valid volume - volumes
221
+			// cannot include a mode. e.g. /foo:rw
222
+			return nil, errInvalidSpec(raw)
223
+		}
224
+		// Host Source Path or Name + Destination
225
+		spec.Source = arr[0]
226
+		spec.Target = arr[1]
227
+	case 3:
228
+		// HostSourcePath+DestinationPath+Mode
229
+		spec.Source = arr[0]
230
+		spec.Target = arr[1]
231
+		mode = arr[2]
232
+	default:
233
+		return nil, errInvalidSpec(raw)
234
+	}
235
+
236
+	if !linuxValidMountMode(mode) {
237
+		return nil, errInvalidMode(mode)
238
+	}
239
+
240
+	if path.IsAbs(spec.Source) {
241
+		spec.Type = mount.TypeBind
242
+	} else {
243
+		spec.Type = mount.TypeVolume
244
+	}
245
+
246
+	spec.ReadOnly = !p.ReadWrite(mode)
247
+
248
+	// cannot assume that if a volume driver is passed in that we should set it
249
+	if volumeDriver != "" && spec.Type == mount.TypeVolume {
250
+		spec.VolumeOptions = &mount.VolumeOptions{
251
+			DriverConfig: &mount.Driver{Name: volumeDriver},
252
+		}
253
+	}
254
+
255
+	if copyData, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
256
+		if spec.VolumeOptions == nil {
257
+			spec.VolumeOptions = &mount.VolumeOptions{}
258
+		}
259
+		spec.VolumeOptions.NoCopy = !copyData
260
+	}
261
+	if linuxHasPropagation(mode) {
262
+		spec.BindOptions = &mount.BindOptions{
263
+			Propagation: linuxGetPropagation(mode),
264
+		}
265
+	}
266
+
267
+	mp, err := p.parseMountSpec(spec, false)
268
+	if mp != nil {
269
+		mp.Mode = mode
270
+	}
271
+	if err != nil {
272
+		err = fmt.Errorf("%v: %v", errInvalidSpec(raw), err)
273
+	}
274
+	return mp, err
275
+}
276
+func (p *linuxParser) ParseMountSpec(cfg mount.Mount) (*MountPoint, error) {
277
+	return p.parseMountSpec(cfg, true)
278
+}
279
+func (p *linuxParser) parseMountSpec(cfg mount.Mount, validateBindSourceExists bool) (*MountPoint, error) {
280
+	if err := p.validateMountConfigImpl(&cfg, validateBindSourceExists); err != nil {
281
+		return nil, err
282
+	}
283
+	mp := &MountPoint{
284
+		RW:          !cfg.ReadOnly,
285
+		Destination: path.Clean(filepath.ToSlash(cfg.Target)),
286
+		Type:        cfg.Type,
287
+		Spec:        cfg,
288
+	}
289
+
290
+	switch cfg.Type {
291
+	case mount.TypeVolume:
292
+		if cfg.Source == "" {
293
+			mp.Name = stringid.GenerateNonCryptoID()
294
+		} else {
295
+			mp.Name = cfg.Source
296
+		}
297
+		mp.CopyData = p.DefaultCopyMode()
298
+
299
+		if cfg.VolumeOptions != nil {
300
+			if cfg.VolumeOptions.DriverConfig != nil {
301
+				mp.Driver = cfg.VolumeOptions.DriverConfig.Name
302
+			}
303
+			if cfg.VolumeOptions.NoCopy {
304
+				mp.CopyData = false
305
+			}
306
+		}
307
+	case mount.TypeBind:
308
+		mp.Source = path.Clean(filepath.ToSlash(cfg.Source))
309
+		if cfg.BindOptions != nil && len(cfg.BindOptions.Propagation) > 0 {
310
+			mp.Propagation = cfg.BindOptions.Propagation
311
+		} else {
312
+			// If user did not specify a propagation mode, get
313
+			// default propagation mode.
314
+			mp.Propagation = linuxDefaultPropagationMode
315
+		}
316
+	case mount.TypeTmpfs:
317
+		// NOP
318
+	}
319
+	return mp, nil
320
+}
321
+
322
+func (p *linuxParser) ParseVolumesFrom(spec string) (string, string, error) {
323
+	if len(spec) == 0 {
324
+		return "", "", fmt.Errorf("volumes-from specification cannot be an empty string")
325
+	}
326
+
327
+	specParts := strings.SplitN(spec, ":", 2)
328
+	id := specParts[0]
329
+	mode := "rw"
330
+
331
+	if len(specParts) == 2 {
332
+		mode = specParts[1]
333
+		if !linuxValidMountMode(mode) {
334
+			return "", "", errInvalidMode(mode)
335
+		}
336
+		// For now don't allow propagation properties while importing
337
+		// volumes from data container. These volumes will inherit
338
+		// the same propagation property as of the original volume
339
+		// in data container. This probably can be relaxed in future.
340
+		if linuxHasPropagation(mode) {
341
+			return "", "", errInvalidMode(mode)
342
+		}
343
+		// Do not allow copy modes on volumes-from
344
+		if _, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
345
+			return "", "", errInvalidMode(mode)
346
+		}
347
+	}
348
+	return id, mode, nil
349
+}
350
+
351
+func (p *linuxParser) DefaultPropagationMode() mount.Propagation {
352
+	return linuxDefaultPropagationMode
353
+}
354
+
355
+func (p *linuxParser) ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error) {
356
+	var rawOpts []string
357
+	if readOnly {
358
+		rawOpts = append(rawOpts, "ro")
359
+	}
360
+
361
+	if opt != nil && opt.Mode != 0 {
362
+		rawOpts = append(rawOpts, fmt.Sprintf("mode=%o", opt.Mode))
363
+	}
364
+
365
+	if opt != nil && opt.SizeBytes != 0 {
366
+		// calculate suffix here, making this linux specific, but that is
367
+		// okay, since API is that way anyways.
368
+
369
+		// we do this by finding the suffix that divides evenly into the
370
+		// value, returning the value itself, with no suffix, if it fails.
371
+		//
372
+		// For the most part, we don't enforce any semantic to this values.
373
+		// The operating system will usually align this and enforce minimum
374
+		// and maximums.
375
+		var (
376
+			size   = opt.SizeBytes
377
+			suffix string
378
+		)
379
+		for _, r := range []struct {
380
+			suffix  string
381
+			divisor int64
382
+		}{
383
+			{"g", 1 << 30},
384
+			{"m", 1 << 20},
385
+			{"k", 1 << 10},
386
+		} {
387
+			if size%r.divisor == 0 {
388
+				size = size / r.divisor
389
+				suffix = r.suffix
390
+				break
391
+			}
392
+		}
393
+
394
+		rawOpts = append(rawOpts, fmt.Sprintf("size=%d%s", size, suffix))
395
+	}
396
+	return strings.Join(rawOpts, ","), nil
397
+}
398
+
399
+func (p *linuxParser) DefaultCopyMode() bool {
400
+	return true
401
+}
402
+func (p *linuxParser) ValidateVolumeName(name string) error {
403
+	return nil
404
+}
405
+
406
+func (p *linuxParser) IsBackwardCompatible(m *MountPoint) bool {
407
+	return len(m.Source) > 0 || m.Driver == DefaultDriverName
408
+}
409
+
410
+func (p *linuxParser) ValidateTmpfsMountDestination(dest string) error {
411
+	if err := linuxValidateNotRoot(dest); err != nil {
412
+		return err
413
+	}
414
+	return linuxValidateAbsolute(dest)
415
+}
0 416
new file mode 100644
... ...
@@ -0,0 +1,43 @@
0
+package volume
1
+
2
+import (
3
+	"runtime"
4
+
5
+	"github.com/docker/docker/api/types/mount"
6
+)
7
+
8
+const (
9
+	// OSLinux is the same as runtime.GOOS on linux
10
+	OSLinux = "linux"
11
+	// OSWindows is the same as runtime.GOOS on windows
12
+	OSWindows = "windows"
13
+)
14
+
15
+// Parser represents a platform specific parser for mount expressions
16
+type Parser interface {
17
+	ParseMountRaw(raw, volumeDriver string) (*MountPoint, error)
18
+	ParseMountSpec(cfg mount.Mount) (*MountPoint, error)
19
+	ParseVolumesFrom(spec string) (string, string, error)
20
+	DefaultPropagationMode() mount.Propagation
21
+	ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error)
22
+	DefaultCopyMode() bool
23
+	ValidateVolumeName(name string) error
24
+	ReadWrite(mode string) bool
25
+	IsBackwardCompatible(m *MountPoint) bool
26
+	HasResource(m *MountPoint, absPath string) bool
27
+	ValidateTmpfsMountDestination(dest string) error
28
+
29
+	validateMountConfig(mt *mount.Mount) error
30
+}
31
+
32
+// NewParser creates a parser for a given container OS, depending on the current host OS (linux on a windows host will resolve to an lcowParser)
33
+func NewParser(containerOS string) Parser {
34
+	switch containerOS {
35
+	case OSWindows:
36
+		return &windowsParser{}
37
+	}
38
+	if runtime.GOOS == OSWindows {
39
+		return &lcowParser{}
40
+	}
41
+	return &linuxParser{}
42
+}
... ...
@@ -9,8 +9,6 @@ const (
9 9
 	errVolumeInUse conflictError = "volume is in use"
10 10
 	// errNoSuchVolume is a typed error returned if the requested volume doesn't exist in the volume store
11 11
 	errNoSuchVolume notFoundError = "no such volume"
12
-	// errInvalidName is a typed error returned when creating a volume with a name that is not valid on the platform
13
-	errInvalidName invalidName = "volume name is not valid on this platform"
14 12
 	// errNameConflict is a typed error returned on create when a volume exists with the given name, but for a different driver
15 13
 	errNameConflict conflictError = "volume name must be unique"
16 14
 )
... ...
@@ -30,13 +28,6 @@ func (e notFoundError) Error() string {
30 30
 
31 31
 func (notFoundError) NotFound() {}
32 32
 
33
-type invalidName string
34
-
35
-func (e invalidName) Error() string {
36
-	return string(e)
37
-}
38
-func (invalidName) InvalidParameter() {}
39
-
40 33
 // OpErr is the error type returned by functions in the store package. It describes
41 34
 // the operation, volume name, and error.
42 35
 type OpErr struct {
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"net"
5 5
 	"os"
6 6
 	"path/filepath"
7
+	"runtime"
7 8
 	"sync"
8 9
 	"time"
9 10
 
... ...
@@ -369,13 +370,14 @@ func volumeExists(v volume.Volume) (bool, error) {
369 369
 // It is expected that callers of this function hold any necessary locks.
370 370
 func (s *VolumeStore) create(name, driverName string, opts, labels map[string]string) (volume.Volume, error) {
371 371
 	// Validate the name in a platform-specific manner
372
-	valid, err := volume.IsVolumeNameValid(name)
372
+
373
+	// volume name validation is specific to the host os and not on container image
374
+	// windows/lcow should have an equivalent volumename validation logic so we create a parser for current host OS
375
+	parser := volume.NewParser(runtime.GOOS)
376
+	err := parser.ValidateVolumeName(name)
373 377
 	if err != nil {
374 378
 		return nil, err
375 379
 	}
376
-	if !valid {
377
-		return nil, &OpErr{Err: errInvalidName, Name: name, Op: "create"}
378
-	}
379 380
 
380 381
 	v, err := s.checkConflict(name, driverName)
381 382
 	if err != nil {
... ...
@@ -2,8 +2,6 @@ package volume
2 2
 
3 3
 import (
4 4
 	"fmt"
5
-	"os"
6
-	"runtime"
7 5
 
8 6
 	"github.com/docker/docker/api/types/mount"
9 7
 	"github.com/pkg/errors"
... ...
@@ -11,120 +9,6 @@ import (
11 11
 
12 12
 var errBindNotExist = errors.New("bind source path does not exist")
13 13
 
14
-type validateOpts struct {
15
-	skipBindSourceCheck bool
16
-}
17
-
18
-func validateMountConfig(mnt *mount.Mount, options ...func(*validateOpts)) error {
19
-	opts := validateOpts{}
20
-	for _, o := range options {
21
-		o(&opts)
22
-	}
23
-
24
-	if len(mnt.Target) == 0 {
25
-		return &errMountConfig{mnt, errMissingField("Target")}
26
-	}
27
-
28
-	if err := validateNotRoot(mnt.Target); err != nil {
29
-		return &errMountConfig{mnt, err}
30
-	}
31
-
32
-	if err := validateAbsolute(mnt.Target); err != nil {
33
-		return &errMountConfig{mnt, err}
34
-	}
35
-
36
-	switch mnt.Type {
37
-	case mount.TypeBind:
38
-		if len(mnt.Source) == 0 {
39
-			return &errMountConfig{mnt, errMissingField("Source")}
40
-		}
41
-		// Don't error out just because the propagation mode is not supported on the platform
42
-		if opts := mnt.BindOptions; opts != nil {
43
-			if len(opts.Propagation) > 0 && len(propagationModes) > 0 {
44
-				if _, ok := propagationModes[opts.Propagation]; !ok {
45
-					return &errMountConfig{mnt, fmt.Errorf("invalid propagation mode: %s", opts.Propagation)}
46
-				}
47
-			}
48
-		}
49
-		if mnt.VolumeOptions != nil {
50
-			return &errMountConfig{mnt, errExtraField("VolumeOptions")}
51
-		}
52
-
53
-		if err := validateAbsolute(mnt.Source); err != nil {
54
-			return &errMountConfig{mnt, err}
55
-		}
56
-
57
-		// Do not allow binding to non-existent path
58
-		if !opts.skipBindSourceCheck {
59
-			fi, err := os.Stat(mnt.Source)
60
-			if err != nil {
61
-				if !os.IsNotExist(err) {
62
-					return &errMountConfig{mnt, err}
63
-				}
64
-				return &errMountConfig{mnt, errBindNotExist}
65
-			}
66
-			if err := validateStat(fi); err != nil {
67
-				return &errMountConfig{mnt, err}
68
-			}
69
-		}
70
-	case mount.TypeVolume:
71
-		if mnt.BindOptions != nil {
72
-			return &errMountConfig{mnt, errExtraField("BindOptions")}
73
-		}
74
-
75
-		if len(mnt.Source) == 0 && mnt.ReadOnly {
76
-			return &errMountConfig{mnt, fmt.Errorf("must not set ReadOnly mode when using anonymous volumes")}
77
-		}
78
-
79
-		if len(mnt.Source) != 0 {
80
-			if valid, err := IsVolumeNameValid(mnt.Source); !valid {
81
-				if err == nil {
82
-					err = errors.New("invalid volume name")
83
-				}
84
-				return &errMountConfig{mnt, err}
85
-			}
86
-		}
87
-	case mount.TypeTmpfs:
88
-		if len(mnt.Source) != 0 {
89
-			return &errMountConfig{mnt, errExtraField("Source")}
90
-		}
91
-		if err := ValidateTmpfsMountDestination(mnt.Target); err != nil {
92
-			return &errMountConfig{mnt, err}
93
-		}
94
-		if _, err := ConvertTmpfsOptions(mnt.TmpfsOptions, mnt.ReadOnly); err != nil {
95
-			return &errMountConfig{mnt, err}
96
-		}
97
-	case mount.TypeNamedPipe:
98
-		if runtime.GOOS != "windows" {
99
-			return &errMountConfig{mnt, errors.New("named pipe bind mounts are not supported on this OS")}
100
-		}
101
-
102
-		if len(mnt.Source) == 0 {
103
-			return &errMountConfig{mnt, errMissingField("Source")}
104
-		}
105
-
106
-		if mnt.BindOptions != nil {
107
-			return &errMountConfig{mnt, errExtraField("BindOptions")}
108
-		}
109
-
110
-		if mnt.ReadOnly {
111
-			return &errMountConfig{mnt, errExtraField("ReadOnly")}
112
-		}
113
-
114
-		if detectMountType(mnt.Source) != mount.TypeNamedPipe {
115
-			return &errMountConfig{mnt, fmt.Errorf("'%s' is not a valid pipe path", mnt.Source)}
116
-		}
117
-
118
-		if detectMountType(mnt.Target) != mount.TypeNamedPipe {
119
-			return &errMountConfig{mnt, fmt.Errorf("'%s' is not a valid pipe path", mnt.Target)}
120
-		}
121
-
122
-	default:
123
-		return &errMountConfig{mnt, errors.New("mount type unknown")}
124
-	}
125
-	return nil
126
-}
127
-
128 14
 type errMountConfig struct {
129 15
 	mount *mount.Mount
130 16
 	err   error
... ...
@@ -140,37 +24,3 @@ func errExtraField(name string) error {
140 140
 func errMissingField(name string) error {
141 141
 	return errors.Errorf("field %s must not be empty", name)
142 142
 }
143
-
144
-func validateAbsolute(p string) error {
145
-	p = convertSlash(p)
146
-	if isAbsPath(p) {
147
-		return nil
148
-	}
149
-	return errors.Errorf("invalid mount path: '%s' mount path must be absolute", p)
150
-}
151
-
152
-// ValidateTmpfsMountDestination validates the destination of tmpfs mount.
153
-// Currently, we have only two obvious rule for validation:
154
-//  - path must not be "/"
155
-//  - path must be absolute
156
-// We should add more rules carefully (#30166)
157
-func ValidateTmpfsMountDestination(dest string) error {
158
-	if err := validateNotRoot(dest); err != nil {
159
-		return err
160
-	}
161
-	return validateAbsolute(dest)
162
-}
163
-
164
-type validationError struct {
165
-	err error
166
-}
167
-
168
-func (e validationError) Error() string {
169
-	return e.err.Error()
170
-}
171
-
172
-func (e validationError) InvalidParameter() {}
173
-
174
-func (e validationError) Cause() error {
175
-	return e.err
176
-}
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"errors"
5 5
 	"io/ioutil"
6 6
 	"os"
7
+	"runtime"
7 8
 	"strings"
8 9
 	"testing"
9 10
 
... ...
@@ -27,17 +28,50 @@ func TestValidateMount(t *testing.T) {
27 27
 		{mount.Mount{Type: mount.TypeBind}, errMissingField("Target")},
28 28
 		{mount.Mount{Type: mount.TypeBind, Target: testDestinationPath}, errMissingField("Source")},
29 29
 		{mount.Mount{Type: mount.TypeBind, Target: testDestinationPath, Source: testSourcePath, VolumeOptions: &mount.VolumeOptions{}}, errExtraField("VolumeOptions")},
30
-		{mount.Mount{Type: mount.TypeBind, Source: testSourcePath, Target: testDestinationPath}, errBindNotExist},
30
+
31 31
 		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath}, nil},
32 32
 		{mount.Mount{Type: "invalid", Target: testDestinationPath}, errors.New("mount type unknown")},
33 33
 	}
34
+	if runtime.GOOS == "windows" {
35
+		cases = append(cases, struct {
36
+			input    mount.Mount
37
+			expected error
38
+		}{mount.Mount{Type: mount.TypeBind, Source: testSourcePath, Target: testDestinationPath}, errBindNotExist}) // bind source existance is not checked on linux
39
+	}
40
+	lcowCases := []struct {
41
+		input    mount.Mount
42
+		expected error
43
+	}{
44
+		{mount.Mount{Type: mount.TypeVolume}, errMissingField("Target")},
45
+		{mount.Mount{Type: mount.TypeVolume, Target: "/foo", Source: "hello"}, nil},
46
+		{mount.Mount{Type: mount.TypeVolume, Target: "/foo"}, nil},
47
+		{mount.Mount{Type: mount.TypeBind}, errMissingField("Target")},
48
+		{mount.Mount{Type: mount.TypeBind, Target: "/foo"}, errMissingField("Source")},
49
+		{mount.Mount{Type: mount.TypeBind, Target: "/foo", Source: "c:\\foo", VolumeOptions: &mount.VolumeOptions{}}, errExtraField("VolumeOptions")},
50
+		{mount.Mount{Type: mount.TypeBind, Source: "c:\\foo", Target: "/foo"}, errBindNotExist},
51
+		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: "/foo"}, nil},
52
+		{mount.Mount{Type: "invalid", Target: "/foo"}, errors.New("mount type unknown")},
53
+	}
54
+	parser := NewParser(runtime.GOOS)
34 55
 	for i, x := range cases {
35
-		err := validateMountConfig(&x.input)
56
+		err := parser.validateMountConfig(&x.input)
36 57
 		if err == nil && x.expected == nil {
37 58
 			continue
38 59
 		}
39 60
 		if (err == nil && x.expected != nil) || (x.expected == nil && err != nil) || !strings.Contains(err.Error(), x.expected.Error()) {
40
-			t.Fatalf("expected %q, got %q, case: %d", x.expected, err, i)
61
+			t.Errorf("expected %q, got %q, case: %d", x.expected, err, i)
62
+		}
63
+	}
64
+	if runtime.GOOS == "windows" {
65
+		parser = &lcowParser{}
66
+		for i, x := range lcowCases {
67
+			err := parser.validateMountConfig(&x.input)
68
+			if err == nil && x.expected == nil {
69
+				continue
70
+			}
71
+			if (err == nil && x.expected != nil) || (x.expected == nil && err != nil) || !strings.Contains(err.Error(), x.expected.Error()) {
72
+				t.Errorf("expected %q, got %q, case: %d", x.expected, err, i)
73
+			}
41 74
 		}
42 75
 	}
43 76
 }
... ...
@@ -3,7 +3,6 @@ package volume
3 3
 import (
4 4
 	"fmt"
5 5
 	"os"
6
-	"strings"
7 6
 	"syscall"
8 7
 	"time"
9 8
 
... ...
@@ -216,153 +215,10 @@ func (m *MountPoint) Path() string {
216 216
 	return m.Source
217 217
 }
218 218
 
219
-// ParseVolumesFrom ensures that the supplied volumes-from is valid.
220
-func ParseVolumesFrom(spec string) (string, string, error) {
221
-	if len(spec) == 0 {
222
-		return "", "", fmt.Errorf("volumes-from specification cannot be an empty string")
223
-	}
224
-
225
-	specParts := strings.SplitN(spec, ":", 2)
226
-	id := specParts[0]
227
-	mode := "rw"
228
-
229
-	if len(specParts) == 2 {
230
-		mode = specParts[1]
231
-		if !ValidMountMode(mode) {
232
-			return "", "", errInvalidMode(mode)
233
-		}
234
-		// For now don't allow propagation properties while importing
235
-		// volumes from data container. These volumes will inherit
236
-		// the same propagation property as of the original volume
237
-		// in data container. This probably can be relaxed in future.
238
-		if HasPropagation(mode) {
239
-			return "", "", errInvalidMode(mode)
240
-		}
241
-		// Do not allow copy modes on volumes-from
242
-		if _, isSet := getCopyMode(mode); isSet {
243
-			return "", "", errInvalidMode(mode)
244
-		}
245
-	}
246
-	return id, mode, nil
247
-}
248
-
249
-// ParseMountRaw parses a raw volume spec (e.g. `-v /foo:/bar:shared`) into a
250
-// structured spec. Once the raw spec is parsed it relies on `ParseMountSpec` to
251
-// validate the spec and create a MountPoint
252
-func ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
253
-	arr, err := splitRawSpec(convertSlash(raw))
254
-	if err != nil {
255
-		return nil, err
256
-	}
257
-
258
-	var spec mounttypes.Mount
259
-	var mode string
260
-	switch len(arr) {
261
-	case 1:
262
-		// Just a destination path in the container
263
-		spec.Target = arr[0]
264
-	case 2:
265
-		if ValidMountMode(arr[1]) {
266
-			// Destination + Mode is not a valid volume - volumes
267
-			// cannot include a mode. e.g. /foo:rw
268
-			return nil, errInvalidSpec(raw)
269
-		}
270
-		// Host Source Path or Name + Destination
271
-		spec.Source = arr[0]
272
-		spec.Target = arr[1]
273
-	case 3:
274
-		// HostSourcePath+DestinationPath+Mode
275
-		spec.Source = arr[0]
276
-		spec.Target = arr[1]
277
-		mode = arr[2]
278
-	default:
279
-		return nil, errInvalidSpec(raw)
280
-	}
281
-
282
-	if !ValidMountMode(mode) {
283
-		return nil, errInvalidMode(mode)
284
-	}
285
-
286
-	spec.Type = detectMountType(spec.Source)
287
-	spec.ReadOnly = !ReadWrite(mode)
288
-
289
-	// cannot assume that if a volume driver is passed in that we should set it
290
-	if volumeDriver != "" && spec.Type == mounttypes.TypeVolume {
291
-		spec.VolumeOptions = &mounttypes.VolumeOptions{
292
-			DriverConfig: &mounttypes.Driver{Name: volumeDriver},
293
-		}
294
-	}
295
-
296
-	if copyData, isSet := getCopyMode(mode); isSet {
297
-		if spec.VolumeOptions == nil {
298
-			spec.VolumeOptions = &mounttypes.VolumeOptions{}
299
-		}
300
-		spec.VolumeOptions.NoCopy = !copyData
301
-	}
302
-	if HasPropagation(mode) {
303
-		spec.BindOptions = &mounttypes.BindOptions{
304
-			Propagation: GetPropagation(mode),
305
-		}
306
-	}
307
-
308
-	mp, err := ParseMountSpec(spec, platformRawValidationOpts...)
309
-	if mp != nil {
310
-		mp.Mode = mode
311
-	}
312
-	if err != nil {
313
-		err = errors.Wrap(err, errInvalidSpec(raw).Error())
314
-	}
315
-	return mp, err
316
-}
317
-
318
-// ParseMountSpec reads a mount config, validates it, and configures a mountpoint from it.
319
-func ParseMountSpec(cfg mounttypes.Mount, options ...func(*validateOpts)) (*MountPoint, error) {
320
-	if err := validateMountConfig(&cfg, options...); err != nil {
321
-		return nil, validationError{err}
322
-	}
323
-	mp := &MountPoint{
324
-		RW:          !cfg.ReadOnly,
325
-		Destination: clean(convertSlash(cfg.Target)),
326
-		Type:        cfg.Type,
327
-		Spec:        cfg,
328
-	}
329
-
330
-	switch cfg.Type {
331
-	case mounttypes.TypeVolume:
332
-		if cfg.Source == "" {
333
-			mp.Name = stringid.GenerateNonCryptoID()
334
-		} else {
335
-			mp.Name = cfg.Source
336
-		}
337
-		mp.CopyData = DefaultCopyMode
338
-
339
-		if cfg.VolumeOptions != nil {
340
-			if cfg.VolumeOptions.DriverConfig != nil {
341
-				mp.Driver = cfg.VolumeOptions.DriverConfig.Name
342
-			}
343
-			if cfg.VolumeOptions.NoCopy {
344
-				mp.CopyData = false
345
-			}
346
-		}
347
-	case mounttypes.TypeBind, mounttypes.TypeNamedPipe:
348
-		mp.Source = clean(convertSlash(cfg.Source))
349
-		if cfg.BindOptions != nil && len(cfg.BindOptions.Propagation) > 0 {
350
-			mp.Propagation = cfg.BindOptions.Propagation
351
-		} else {
352
-			// If user did not specify a propagation mode, get
353
-			// default propagation mode.
354
-			mp.Propagation = DefaultPropagationMode
355
-		}
356
-	case mounttypes.TypeTmpfs:
357
-		// NOP
358
-	}
359
-	return mp, nil
360
-}
361
-
362 219
 func errInvalidMode(mode string) error {
363
-	return validationError{errors.Errorf("invalid mode: %v", mode)}
220
+	return errors.Errorf("invalid mode: %v", mode)
364 221
 }
365 222
 
366 223
 func errInvalidSpec(spec string) error {
367
-	return validationError{errors.Errorf("invalid volume specification: '%s'", spec)}
224
+	return errors.Errorf("invalid volume specification: '%s'", spec)
368 225
 }
... ...
@@ -13,11 +13,11 @@ func copyModeExists(mode string) bool {
13 13
 }
14 14
 
15 15
 // GetCopyMode gets the copy mode from the mode string for mounts
16
-func getCopyMode(mode string) (bool, bool) {
16
+func getCopyMode(mode string, def bool) (bool, bool) {
17 17
 	for _, o := range strings.Split(mode, ",") {
18 18
 		if isEnabled, exists := copyModes[o]; exists {
19 19
 			return isEnabled, true
20 20
 		}
21 21
 	}
22
-	return DefaultCopyMode, false
22
+	return def, false
23 23
 }
24 24
deleted file mode 100644
... ...
@@ -1,8 +0,0 @@
1
-// +build !windows
2
-
3
-package volume
4
-
5
-const (
6
-	// DefaultCopyMode is the copy mode used by default for normal/named volumes
7
-	DefaultCopyMode = true
8
-)
9 1
deleted file mode 100644
... ...
@@ -1,6 +0,0 @@
1
-package volume
2
-
3
-const (
4
-	// DefaultCopyMode is the copy mode used by default for normal/named volumes
5
-	DefaultCopyMode = false
6
-)
7 1
deleted file mode 100644
... ...
@@ -1,56 +0,0 @@
1
-// +build linux
2
-
3
-package volume
4
-
5
-import (
6
-	"fmt"
7
-	"strings"
8
-
9
-	mounttypes "github.com/docker/docker/api/types/mount"
10
-)
11
-
12
-// ConvertTmpfsOptions converts *mounttypes.TmpfsOptions to the raw option string
13
-// for mount(2).
14
-func ConvertTmpfsOptions(opt *mounttypes.TmpfsOptions, readOnly bool) (string, error) {
15
-	var rawOpts []string
16
-	if readOnly {
17
-		rawOpts = append(rawOpts, "ro")
18
-	}
19
-
20
-	if opt != nil && opt.Mode != 0 {
21
-		rawOpts = append(rawOpts, fmt.Sprintf("mode=%o", opt.Mode))
22
-	}
23
-
24
-	if opt != nil && opt.SizeBytes != 0 {
25
-		// calculate suffix here, making this linux specific, but that is
26
-		// okay, since API is that way anyways.
27
-
28
-		// we do this by finding the suffix that divides evenly into the
29
-		// value, returning the value itself, with no suffix, if it fails.
30
-		//
31
-		// For the most part, we don't enforce any semantic to this values.
32
-		// The operating system will usually align this and enforce minimum
33
-		// and maximums.
34
-		var (
35
-			size   = opt.SizeBytes
36
-			suffix string
37
-		)
38
-		for _, r := range []struct {
39
-			suffix  string
40
-			divisor int64
41
-		}{
42
-			{"g", 1 << 30},
43
-			{"m", 1 << 20},
44
-			{"k", 1 << 10},
45
-		} {
46
-			if size%r.divisor == 0 {
47
-				size = size / r.divisor
48
-				suffix = r.suffix
49
-				break
50
-			}
51
-		}
52
-
53
-		rawOpts = append(rawOpts, fmt.Sprintf("size=%d%s", size, suffix))
54
-	}
55
-	return strings.Join(rawOpts, ","), nil
56
-}
57 1
deleted file mode 100644
... ...
@@ -1,51 +0,0 @@
1
-// +build linux
2
-
3
-package volume
4
-
5
-import (
6
-	"strings"
7
-	"testing"
8
-
9
-	mounttypes "github.com/docker/docker/api/types/mount"
10
-)
11
-
12
-func TestConvertTmpfsOptions(t *testing.T) {
13
-	type testCase struct {
14
-		opt                  mounttypes.TmpfsOptions
15
-		readOnly             bool
16
-		expectedSubstrings   []string
17
-		unexpectedSubstrings []string
18
-	}
19
-	cases := []testCase{
20
-		{
21
-			opt:                  mounttypes.TmpfsOptions{SizeBytes: 1024 * 1024, Mode: 0700},
22
-			readOnly:             false,
23
-			expectedSubstrings:   []string{"size=1m", "mode=700"},
24
-			unexpectedSubstrings: []string{"ro"},
25
-		},
26
-		{
27
-			opt:                  mounttypes.TmpfsOptions{},
28
-			readOnly:             true,
29
-			expectedSubstrings:   []string{"ro"},
30
-			unexpectedSubstrings: []string{},
31
-		},
32
-	}
33
-	for _, c := range cases {
34
-		data, err := ConvertTmpfsOptions(&c.opt, c.readOnly)
35
-		if err != nil {
36
-			t.Fatalf("could not convert %+v (readOnly: %v) to string: %v",
37
-				c.opt, c.readOnly, err)
38
-		}
39
-		t.Logf("data=%q", data)
40
-		for _, s := range c.expectedSubstrings {
41
-			if !strings.Contains(data, s) {
42
-				t.Fatalf("expected substring: %s, got %v (case=%+v)", s, data, c)
43
-			}
44
-		}
45
-		for _, s := range c.unexpectedSubstrings {
46
-			if strings.Contains(data, s) {
47
-				t.Fatalf("unexpected substring: %s, got %v (case=%+v)", s, data, c)
48
-			}
49
-		}
50
-	}
51
-}
52 1
deleted file mode 100644
... ...
@@ -1,47 +0,0 @@
1
-// +build linux
2
-
3
-package volume
4
-
5
-import (
6
-	"strings"
7
-
8
-	mounttypes "github.com/docker/docker/api/types/mount"
9
-)
10
-
11
-// DefaultPropagationMode defines what propagation mode should be used by
12
-// default if user has not specified one explicitly.
13
-// propagation modes
14
-const DefaultPropagationMode = mounttypes.PropagationRPrivate
15
-
16
-var propagationModes = map[mounttypes.Propagation]bool{
17
-	mounttypes.PropagationPrivate:  true,
18
-	mounttypes.PropagationRPrivate: true,
19
-	mounttypes.PropagationSlave:    true,
20
-	mounttypes.PropagationRSlave:   true,
21
-	mounttypes.PropagationShared:   true,
22
-	mounttypes.PropagationRShared:  true,
23
-}
24
-
25
-// GetPropagation extracts and returns the mount propagation mode. If there
26
-// are no specifications, then by default it is "private".
27
-func GetPropagation(mode string) mounttypes.Propagation {
28
-	for _, o := range strings.Split(mode, ",") {
29
-		prop := mounttypes.Propagation(o)
30
-		if propagationModes[prop] {
31
-			return prop
32
-		}
33
-	}
34
-	return DefaultPropagationMode
35
-}
36
-
37
-// HasPropagation checks if there is a valid propagation mode present in
38
-// passed string. Returns true if a valid propagation mode specifier is
39
-// present, false otherwise.
40
-func HasPropagation(mode string) bool {
41
-	for _, o := range strings.Split(mode, ",") {
42
-		if propagationModes[mounttypes.Propagation(o)] {
43
-			return true
44
-		}
45
-	}
46
-	return false
47
-}
48 1
deleted file mode 100644
... ...
@@ -1,65 +0,0 @@
1
-// +build linux
2
-
3
-package volume
4
-
5
-import (
6
-	"strings"
7
-	"testing"
8
-)
9
-
10
-func TestParseMountRawPropagation(t *testing.T) {
11
-	var (
12
-		valid   []string
13
-		invalid map[string]string
14
-	)
15
-
16
-	valid = []string{
17
-		"/hostPath:/containerPath:shared",
18
-		"/hostPath:/containerPath:rshared",
19
-		"/hostPath:/containerPath:slave",
20
-		"/hostPath:/containerPath:rslave",
21
-		"/hostPath:/containerPath:private",
22
-		"/hostPath:/containerPath:rprivate",
23
-		"/hostPath:/containerPath:ro,shared",
24
-		"/hostPath:/containerPath:ro,slave",
25
-		"/hostPath:/containerPath:ro,private",
26
-		"/hostPath:/containerPath:ro,z,shared",
27
-		"/hostPath:/containerPath:ro,Z,slave",
28
-		"/hostPath:/containerPath:Z,ro,slave",
29
-		"/hostPath:/containerPath:slave,Z,ro",
30
-		"/hostPath:/containerPath:Z,slave,ro",
31
-		"/hostPath:/containerPath:slave,ro,Z",
32
-		"/hostPath:/containerPath:rslave,ro,Z",
33
-		"/hostPath:/containerPath:ro,rshared,Z",
34
-		"/hostPath:/containerPath:ro,Z,rprivate",
35
-	}
36
-	invalid = map[string]string{
37
-		"/path:/path:ro,rshared,rslave":   `invalid mode`,
38
-		"/path:/path:ro,z,rshared,rslave": `invalid mode`,
39
-		"/path:shared":                    "invalid volume specification",
40
-		"/path:slave":                     "invalid volume specification",
41
-		"/path:private":                   "invalid volume specification",
42
-		"name:/absolute-path:shared":      "invalid volume specification",
43
-		"name:/absolute-path:rshared":     "invalid volume specification",
44
-		"name:/absolute-path:slave":       "invalid volume specification",
45
-		"name:/absolute-path:rslave":      "invalid volume specification",
46
-		"name:/absolute-path:private":     "invalid volume specification",
47
-		"name:/absolute-path:rprivate":    "invalid volume specification",
48
-	}
49
-
50
-	for _, path := range valid {
51
-		if _, err := ParseMountRaw(path, "local"); err != nil {
52
-			t.Fatalf("ParseMountRaw(`%q`) should succeed: error %q", path, err)
53
-		}
54
-	}
55
-
56
-	for path, expectedError := range invalid {
57
-		if _, err := ParseMountRaw(path, "local"); err == nil {
58
-			t.Fatalf("ParseMountRaw(`%q`) should have failed validation. Err %v", path, err)
59
-		} else {
60
-			if !strings.Contains(err.Error(), expectedError) {
61
-				t.Fatalf("ParseMountRaw(`%q`) error should contain %q, got %v", path, expectedError, err.Error())
62
-			}
63
-		}
64
-	}
65
-}
66 1
deleted file mode 100644
... ...
@@ -1,24 +0,0 @@
1
-// +build !linux
2
-
3
-package volume
4
-
5
-import mounttypes "github.com/docker/docker/api/types/mount"
6
-
7
-// DefaultPropagationMode is used only in linux. In other cases it returns
8
-// empty string.
9
-const DefaultPropagationMode mounttypes.Propagation = ""
10
-
11
-// propagation modes not supported on this platform.
12
-var propagationModes = map[mounttypes.Propagation]bool{}
13
-
14
-// GetPropagation is not supported. Return empty string.
15
-func GetPropagation(mode string) mounttypes.Propagation {
16
-	return DefaultPropagationMode
17
-}
18
-
19
-// HasPropagation checks if there is a valid propagation mode present in
20
-// passed string. Returns true if a valid propagation mode specifier is
21
-// present, false otherwise.
22
-func HasPropagation(mode string) bool {
23
-	return false
24
-}
... ...
@@ -10,14 +10,84 @@ import (
10 10
 	"github.com/docker/docker/api/types/mount"
11 11
 )
12 12
 
13
+type parseMountRawTestSet struct {
14
+	valid   []string
15
+	invalid map[string]string
16
+}
17
+
18
+func TestConvertTmpfsOptions(t *testing.T) {
19
+	type testCase struct {
20
+		opt                  mount.TmpfsOptions
21
+		readOnly             bool
22
+		expectedSubstrings   []string
23
+		unexpectedSubstrings []string
24
+	}
25
+	cases := []testCase{
26
+		{
27
+			opt:                  mount.TmpfsOptions{SizeBytes: 1024 * 1024, Mode: 0700},
28
+			readOnly:             false,
29
+			expectedSubstrings:   []string{"size=1m", "mode=700"},
30
+			unexpectedSubstrings: []string{"ro"},
31
+		},
32
+		{
33
+			opt:                  mount.TmpfsOptions{},
34
+			readOnly:             true,
35
+			expectedSubstrings:   []string{"ro"},
36
+			unexpectedSubstrings: []string{},
37
+		},
38
+	}
39
+	p := &linuxParser{}
40
+	for _, c := range cases {
41
+		data, err := p.ConvertTmpfsOptions(&c.opt, c.readOnly)
42
+		if err != nil {
43
+			t.Fatalf("could not convert %+v (readOnly: %v) to string: %v",
44
+				c.opt, c.readOnly, err)
45
+		}
46
+		t.Logf("data=%q", data)
47
+		for _, s := range c.expectedSubstrings {
48
+			if !strings.Contains(data, s) {
49
+				t.Fatalf("expected substring: %s, got %v (case=%+v)", s, data, c)
50
+			}
51
+		}
52
+		for _, s := range c.unexpectedSubstrings {
53
+			if strings.Contains(data, s) {
54
+				t.Fatalf("unexpected substring: %s, got %v (case=%+v)", s, data, c)
55
+			}
56
+		}
57
+	}
58
+}
59
+
60
+type mockFiProvider struct{}
61
+
62
+func (mockFiProvider) fileInfo(path string) (exists, isDir bool, err error) {
63
+	dirs := map[string]struct{}{
64
+		`c:\`:                    {},
65
+		`c:\windows\`:            {},
66
+		`c:\windows`:             {},
67
+		`c:\program files`:       {},
68
+		`c:\Windows`:             {},
69
+		`c:\Program Files (x86)`: {},
70
+		`\\?\c:\windows\`:        {},
71
+	}
72
+	files := map[string]struct{}{
73
+		`c:\windows\system32\ntdll.dll`: {},
74
+	}
75
+	if _, ok := dirs[path]; ok {
76
+		return true, true, nil
77
+	}
78
+	if _, ok := files[path]; ok {
79
+		return true, false, nil
80
+	}
81
+	return false, false, nil
82
+}
83
+
13 84
 func TestParseMountRaw(t *testing.T) {
14
-	var (
15
-		valid   []string
16
-		invalid map[string]string
17
-	)
18 85
 
19
-	if runtime.GOOS == "windows" {
20
-		valid = []string{
86
+	previousProvider := currentFileInfoProvider
87
+	defer func() { currentFileInfoProvider = previousProvider }()
88
+	currentFileInfoProvider = mockFiProvider{}
89
+	windowsSet := parseMountRawTestSet{
90
+		valid: []string{
21 91
 			`d:\`,
22 92
 			`d:`,
23 93
 			`d:\path`,
... ...
@@ -35,10 +105,14 @@ func TestParseMountRaw(t *testing.T) {
35 35
 			`name:D::RO`,
36 36
 			`c:/:d:/forward/slashes/are/good/too`,
37 37
 			`c:/:d:/including with/spaces:ro`,
38
-			`c:\Windows`,             // With capital
39
-			`c:\Program Files (x86)`, // With capitals and brackets
40
-		}
41
-		invalid = map[string]string{
38
+			`c:\Windows`,                // With capital
39
+			`c:\Program Files (x86)`,    // With capitals and brackets
40
+			`\\?\c:\windows\:d:`,        // Long path handling (source)
41
+			`c:\windows\:\\?\d:\`,       // Long path handling (target)
42
+			`\\.\pipe\foo:\\.\pipe\foo`, // named pipe
43
+			`//./pipe/foo://./pipe/foo`, // named pipe forward slashes
44
+		},
45
+		invalid: map[string]string{
42 46
 			``:                                 "invalid volume specification: ",
43 47
 			`.`:                                "invalid volume specification: ",
44 48
 			`..\`:                              "invalid volume specification: ",
... ...
@@ -82,10 +156,79 @@ func TestParseMountRaw(t *testing.T) {
82 82
 			`lpt8:d:`:                          `cannot be a reserved word for Windows filenames`,
83 83
 			`lpt9:d:`:                          `cannot be a reserved word for Windows filenames`,
84 84
 			`c:\windows\system32\ntdll.dll`:    `Only directories can be mapped on this platform`,
85
-		}
86
-
87
-	} else {
88
-		valid = []string{
85
+			`\\.\pipe\foo:c:\pipe`:             `'c:\pipe' is not a valid pipe path`,
86
+		},
87
+	}
88
+	lcowSet := parseMountRawTestSet{
89
+		valid: []string{
90
+			`/foo`,
91
+			`/foo/`,
92
+			`/foo bar`,
93
+			`c:\:/foo`,
94
+			`c:\windows\:/foo`,
95
+			`c:\windows:/s p a c e`,
96
+			`c:\windows:/s p a c e:RW`,
97
+			`c:\program files:/s p a c e i n h o s t d i r`,
98
+			`0123456789name:/foo`,
99
+			`MiXeDcAsEnAmE:/foo`,
100
+			`name:/foo`,
101
+			`name:/foo:rW`,
102
+			`name:/foo:RW`,
103
+			`name:/foo:RO`,
104
+			`c:/:/forward/slashes/are/good/too`,
105
+			`c:/:/including with/spaces:ro`,
106
+			`/Program Files (x86)`, // With capitals and brackets
107
+		},
108
+		invalid: map[string]string{
109
+			``:                                   "invalid volume specification: ",
110
+			`.`:                                  "invalid volume specification: ",
111
+			`c:`:                                 "invalid volume specification: ",
112
+			`c:\`:                                "invalid volume specification: ",
113
+			`../`:                                "invalid volume specification: ",
114
+			`c:\:../`:                            "invalid volume specification: ",
115
+			`c:\:/foo:xyzzy`:                     "invalid volume specification: ",
116
+			`/`:                                  "destination can't be '/'",
117
+			`/..`:                                "destination can't be '/'",
118
+			`c:\notexist:/foo`:                   `source path does not exist`,
119
+			`c:\windows\system32\ntdll.dll:/foo`: `source path must be a directory`,
120
+			`name<:/foo`:                         `invalid volume specification`,
121
+			`name>:/foo`:                         `invalid volume specification`,
122
+			`name::/foo`:                         `invalid volume specification`,
123
+			`name":/foo`:                         `invalid volume specification`,
124
+			`name\:/foo`:                         `invalid volume specification`,
125
+			`name*:/foo`:                         `invalid volume specification`,
126
+			`name|:/foo`:                         `invalid volume specification`,
127
+			`name?:/foo`:                         `invalid volume specification`,
128
+			`name/:/foo`:                         `invalid volume specification`,
129
+			`/foo:rw`:                            `invalid volume specification`,
130
+			`/foo:ro`:                            `invalid volume specification`,
131
+			`con:/foo`:                           `cannot be a reserved word for Windows filenames`,
132
+			`PRN:/foo`:                           `cannot be a reserved word for Windows filenames`,
133
+			`aUx:/foo`:                           `cannot be a reserved word for Windows filenames`,
134
+			`nul:/foo`:                           `cannot be a reserved word for Windows filenames`,
135
+			`com1:/foo`:                          `cannot be a reserved word for Windows filenames`,
136
+			`com2:/foo`:                          `cannot be a reserved word for Windows filenames`,
137
+			`com3:/foo`:                          `cannot be a reserved word for Windows filenames`,
138
+			`com4:/foo`:                          `cannot be a reserved word for Windows filenames`,
139
+			`com5:/foo`:                          `cannot be a reserved word for Windows filenames`,
140
+			`com6:/foo`:                          `cannot be a reserved word for Windows filenames`,
141
+			`com7:/foo`:                          `cannot be a reserved word for Windows filenames`,
142
+			`com8:/foo`:                          `cannot be a reserved word for Windows filenames`,
143
+			`com9:/foo`:                          `cannot be a reserved word for Windows filenames`,
144
+			`lpt1:/foo`:                          `cannot be a reserved word for Windows filenames`,
145
+			`lpt2:/foo`:                          `cannot be a reserved word for Windows filenames`,
146
+			`lpt3:/foo`:                          `cannot be a reserved word for Windows filenames`,
147
+			`lpt4:/foo`:                          `cannot be a reserved word for Windows filenames`,
148
+			`lpt5:/foo`:                          `cannot be a reserved word for Windows filenames`,
149
+			`lpt6:/foo`:                          `cannot be a reserved word for Windows filenames`,
150
+			`lpt7:/foo`:                          `cannot be a reserved word for Windows filenames`,
151
+			`lpt8:/foo`:                          `cannot be a reserved word for Windows filenames`,
152
+			`lpt9:/foo`:                          `cannot be a reserved word for Windows filenames`,
153
+			`\\.\pipe\foo:/foo`:                  `Linux containers on Windows do not support named pipe mounts`,
154
+		},
155
+	}
156
+	linuxSet := parseMountRawTestSet{
157
+		valid: []string{
89 158
 			"/home",
90 159
 			"/home:/home",
91 160
 			"/home:/something/else",
... ...
@@ -95,47 +238,87 @@ func TestParseMountRaw(t *testing.T) {
95 95
 			"hostPath:/containerPath:ro",
96 96
 			"/hostPath:/containerPath:rw",
97 97
 			"/rw:/ro",
98
-		}
99
-		invalid = map[string]string{
100
-			"":                "invalid volume specification",
101
-			"./":              "mount path must be absolute",
102
-			"../":             "mount path must be absolute",
103
-			"/:../":           "mount path must be absolute",
104
-			"/:path":          "mount path must be absolute",
105
-			":":               "invalid volume specification",
106
-			"/tmp:":           "invalid volume specification",
107
-			":test":           "invalid volume specification",
108
-			":/test":          "invalid volume specification",
109
-			"tmp:":            "invalid volume specification",
110
-			":test:":          "invalid volume specification",
111
-			"::":              "invalid volume specification",
112
-			":::":             "invalid volume specification",
113
-			"/tmp:::":         "invalid volume specification",
114
-			":/tmp::":         "invalid volume specification",
115
-			"/path:rw":        "invalid volume specification",
116
-			"/path:ro":        "invalid volume specification",
117
-			"/rw:rw":          "invalid volume specification",
118
-			"path:ro":         "invalid volume specification",
119
-			"/path:/path:sw":  `invalid mode`,
120
-			"/path:/path:rwz": `invalid mode`,
121
-		}
98
+			"/hostPath:/containerPath:shared",
99
+			"/hostPath:/containerPath:rshared",
100
+			"/hostPath:/containerPath:slave",
101
+			"/hostPath:/containerPath:rslave",
102
+			"/hostPath:/containerPath:private",
103
+			"/hostPath:/containerPath:rprivate",
104
+			"/hostPath:/containerPath:ro,shared",
105
+			"/hostPath:/containerPath:ro,slave",
106
+			"/hostPath:/containerPath:ro,private",
107
+			"/hostPath:/containerPath:ro,z,shared",
108
+			"/hostPath:/containerPath:ro,Z,slave",
109
+			"/hostPath:/containerPath:Z,ro,slave",
110
+			"/hostPath:/containerPath:slave,Z,ro",
111
+			"/hostPath:/containerPath:Z,slave,ro",
112
+			"/hostPath:/containerPath:slave,ro,Z",
113
+			"/hostPath:/containerPath:rslave,ro,Z",
114
+			"/hostPath:/containerPath:ro,rshared,Z",
115
+			"/hostPath:/containerPath:ro,Z,rprivate",
116
+		},
117
+		invalid: map[string]string{
118
+			"":                                "invalid volume specification",
119
+			"./":                              "mount path must be absolute",
120
+			"../":                             "mount path must be absolute",
121
+			"/:../":                           "mount path must be absolute",
122
+			"/:path":                          "mount path must be absolute",
123
+			":":                               "invalid volume specification",
124
+			"/tmp:":                           "invalid volume specification",
125
+			":test":                           "invalid volume specification",
126
+			":/test":                          "invalid volume specification",
127
+			"tmp:":                            "invalid volume specification",
128
+			":test:":                          "invalid volume specification",
129
+			"::":                              "invalid volume specification",
130
+			":::":                             "invalid volume specification",
131
+			"/tmp:::":                         "invalid volume specification",
132
+			":/tmp::":                         "invalid volume specification",
133
+			"/path:rw":                        "invalid volume specification",
134
+			"/path:ro":                        "invalid volume specification",
135
+			"/rw:rw":                          "invalid volume specification",
136
+			"path:ro":                         "invalid volume specification",
137
+			"/path:/path:sw":                  `invalid mode`,
138
+			"/path:/path:rwz":                 `invalid mode`,
139
+			"/path:/path:ro,rshared,rslave":   `invalid mode`,
140
+			"/path:/path:ro,z,rshared,rslave": `invalid mode`,
141
+			"/path:shared":                    "invalid volume specification",
142
+			"/path:slave":                     "invalid volume specification",
143
+			"/path:private":                   "invalid volume specification",
144
+			"name:/absolute-path:shared":      "invalid volume specification",
145
+			"name:/absolute-path:rshared":     "invalid volume specification",
146
+			"name:/absolute-path:slave":       "invalid volume specification",
147
+			"name:/absolute-path:rslave":      "invalid volume specification",
148
+			"name:/absolute-path:private":     "invalid volume specification",
149
+			"name:/absolute-path:rprivate":    "invalid volume specification",
150
+		},
122 151
 	}
123 152
 
124
-	for _, path := range valid {
125
-		if _, err := ParseMountRaw(path, "local"); err != nil {
126
-			t.Fatalf("ParseMountRaw(`%q`) should succeed: error %q", path, err)
153
+	linParser := &linuxParser{}
154
+	winParser := &windowsParser{}
155
+	lcowParser := &lcowParser{}
156
+	tester := func(parser Parser, set parseMountRawTestSet) {
157
+
158
+		for _, path := range set.valid {
159
+
160
+			if _, err := parser.ParseMountRaw(path, "local"); err != nil {
161
+				t.Errorf("ParseMountRaw(`%q`) should succeed: error %q", path, err)
162
+			}
127 163
 		}
128
-	}
129 164
 
130
-	for path, expectedError := range invalid {
131
-		if mp, err := ParseMountRaw(path, "local"); err == nil {
132
-			t.Fatalf("ParseMountRaw(`%q`) should have failed validation. Err '%v' - MP: %v", path, err, mp)
133
-		} else {
134
-			if !strings.Contains(err.Error(), expectedError) {
135
-				t.Fatalf("ParseMountRaw(`%q`) error should contain %q, got %v", path, expectedError, err.Error())
165
+		for path, expectedError := range set.invalid {
166
+			if mp, err := parser.ParseMountRaw(path, "local"); err == nil {
167
+				t.Errorf("ParseMountRaw(`%q`) should have failed validation. Err '%v' - MP: %v", path, err, mp)
168
+			} else {
169
+				if !strings.Contains(err.Error(), expectedError) {
170
+					t.Errorf("ParseMountRaw(`%q`) error should contain %q, got %v", path, expectedError, err.Error())
171
+				}
136 172
 			}
137 173
 		}
138 174
 	}
175
+	tester(linParser, linuxSet)
176
+	tester(winParser, windowsSet)
177
+	tester(lcowParser, lcowSet)
178
+
139 179
 }
140 180
 
141 181
 // testParseMountRaw is a structure used by TestParseMountRawSplit for
... ...
@@ -153,76 +336,96 @@ type testParseMountRaw struct {
153 153
 }
154 154
 
155 155
 func TestParseMountRawSplit(t *testing.T) {
156
-	var cases []testParseMountRaw
157
-	if runtime.GOOS == "windows" {
158
-		cases = []testParseMountRaw{
159
-			{`c:\:d:`, "local", mount.TypeBind, `d:`, `c:\`, ``, "", true, false},
160
-			{`c:\:d:\`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false},
161
-			{`c:\:d:\:ro`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, false},
162
-			{`c:\:d:\:rw`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false},
163
-			{`c:\:d:\:foo`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, true},
164
-			{`\\.\pipe\foo:\\.\pipe\bar`, "local", mount.TypeNamedPipe, `\\.\pipe\bar`, `\\.\pipe\foo`, "", "", true, false},
165
-			{`\\.\pipe\foo:c:\foo\bar`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
166
-			{`c:\foo\bar:\\.\pipe\foo`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
167
-			{`name:d::rw`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false},
168
-			{`name:d:`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false},
169
-			{`name:d::ro`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", false, false},
170
-			{`name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
171
-			{`driver/name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
172
-		}
173
-	} else {
174
-		cases = []testParseMountRaw{
175
-			{"/tmp:/tmp1", "", mount.TypeBind, "/tmp1", "/tmp", "", "", true, false},
176
-			{"/tmp:/tmp2:ro", "", mount.TypeBind, "/tmp2", "/tmp", "", "", false, false},
177
-			{"/tmp:/tmp3:rw", "", mount.TypeBind, "/tmp3", "/tmp", "", "", true, false},
178
-			{"/tmp:/tmp4:foo", "", mount.TypeBind, "", "", "", "", false, true},
179
-			{"name:/named1", "", mount.TypeVolume, "/named1", "", "name", "", true, false},
180
-			{"name:/named2", "external", mount.TypeVolume, "/named2", "", "name", "external", true, false},
181
-			{"name:/named3:ro", "local", mount.TypeVolume, "/named3", "", "name", "local", false, false},
182
-			{"local/name:/tmp:rw", "", mount.TypeVolume, "/tmp", "", "local/name", "", true, false},
183
-			{"/tmp:tmp", "", mount.TypeBind, "", "", "", "", true, true},
184
-		}
156
+	previousProvider := currentFileInfoProvider
157
+	defer func() { currentFileInfoProvider = previousProvider }()
158
+	currentFileInfoProvider = mockFiProvider{}
159
+	windowsCases := []testParseMountRaw{
160
+		{`c:\:d:`, "local", mount.TypeBind, `d:`, `c:\`, ``, "", true, false},
161
+		{`c:\:d:\`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false},
162
+		{`c:\:d:\:ro`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, false},
163
+		{`c:\:d:\:rw`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false},
164
+		{`c:\:d:\:foo`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, true},
165
+		{`name:d::rw`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false},
166
+		{`name:d:`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false},
167
+		{`name:d::ro`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", false, false},
168
+		{`name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
169
+		{`driver/name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
170
+		{`\\.\pipe\foo:\\.\pipe\bar`, "local", mount.TypeNamedPipe, `\\.\pipe\bar`, `\\.\pipe\foo`, "", "", true, false},
171
+		{`\\.\pipe\foo:c:\foo\bar`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
172
+		{`c:\foo\bar:\\.\pipe\foo`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
185 173
 	}
186
-
187
-	for i, c := range cases {
188
-		t.Logf("case %d", i)
189
-		m, err := ParseMountRaw(c.bind, c.driver)
190
-		if c.fail {
191
-			if err == nil {
192
-				t.Fatalf("Expected error, was nil, for spec %s\n", c.bind)
174
+	lcowCases := []testParseMountRaw{
175
+		{`c:\:/foo`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", true, false},
176
+		{`c:\:/foo:ro`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", false, false},
177
+		{`c:\:/foo:rw`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", true, false},
178
+		{`c:\:/foo:foo`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", false, true},
179
+		{`name:/foo:rw`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", true, false},
180
+		{`name:/foo`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", true, false},
181
+		{`name:/foo:ro`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", false, false},
182
+		{`name:/`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
183
+		{`driver/name:/`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
184
+		{`\\.\pipe\foo:\\.\pipe\bar`, "local", mount.TypeNamedPipe, `\\.\pipe\bar`, `\\.\pipe\foo`, "", "", true, true},
185
+		{`\\.\pipe\foo:/data`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
186
+		{`c:\foo\bar:\\.\pipe\foo`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
187
+	}
188
+	linuxCases := []testParseMountRaw{
189
+		{"/tmp:/tmp1", "", mount.TypeBind, "/tmp1", "/tmp", "", "", true, false},
190
+		{"/tmp:/tmp2:ro", "", mount.TypeBind, "/tmp2", "/tmp", "", "", false, false},
191
+		{"/tmp:/tmp3:rw", "", mount.TypeBind, "/tmp3", "/tmp", "", "", true, false},
192
+		{"/tmp:/tmp4:foo", "", mount.TypeBind, "", "", "", "", false, true},
193
+		{"name:/named1", "", mount.TypeVolume, "/named1", "", "name", "", true, false},
194
+		{"name:/named2", "external", mount.TypeVolume, "/named2", "", "name", "external", true, false},
195
+		{"name:/named3:ro", "local", mount.TypeVolume, "/named3", "", "name", "local", false, false},
196
+		{"local/name:/tmp:rw", "", mount.TypeVolume, "/tmp", "", "local/name", "", true, false},
197
+		{"/tmp:tmp", "", mount.TypeBind, "", "", "", "", true, true},
198
+	}
199
+	linParser := &linuxParser{}
200
+	winParser := &windowsParser{}
201
+	lcowParser := &lcowParser{}
202
+	tester := func(parser Parser, cases []testParseMountRaw) {
203
+		for i, c := range cases {
204
+			t.Logf("case %d", i)
205
+			m, err := parser.ParseMountRaw(c.bind, c.driver)
206
+			if c.fail {
207
+				if err == nil {
208
+					t.Errorf("Expected error, was nil, for spec %s\n", c.bind)
209
+				}
210
+				continue
193 211
 			}
194
-			continue
195
-		}
196
-
197
-		if m == nil || err != nil {
198
-			t.Fatalf("ParseMountRaw failed for spec '%s', driver '%s', error '%v'", c.bind, c.driver, err.Error())
199
-			continue
200
-		}
201 212
 
202
-		if m.Type != c.expType {
203
-			t.Fatalf("Expected type '%s', was '%s', for spec '%s'", c.expType, m.Type, c.bind)
204
-		}
213
+			if m == nil || err != nil {
214
+				t.Errorf("ParseMountRaw failed for spec '%s', driver '%s', error '%v'", c.bind, c.driver, err.Error())
215
+				continue
216
+			}
205 217
 
206
-		if m.Destination != c.expDest {
207
-			t.Fatalf("Expected destination '%s', was '%s', for spec '%s'", c.expDest, m.Destination, c.bind)
208
-		}
218
+			if m.Destination != c.expDest {
219
+				t.Errorf("Expected destination '%s, was %s', for spec '%s'", c.expDest, m.Destination, c.bind)
220
+			}
209 221
 
210
-		if m.Source != c.expSource {
211
-			t.Fatalf("Expected source '%s', was '%s', for spec '%s'", c.expSource, m.Source, c.bind)
212
-		}
222
+			if m.Source != c.expSource {
223
+				t.Errorf("Expected source '%s', was '%s', for spec '%s'", c.expSource, m.Source, c.bind)
224
+			}
213 225
 
214
-		if m.Name != c.expName {
215
-			t.Fatalf("Expected name '%s', was '%s' for spec '%s'", c.expName, m.Name, c.bind)
216
-		}
226
+			if m.Name != c.expName {
227
+				t.Errorf("Expected name '%s', was '%s' for spec '%s'", c.expName, m.Name, c.bind)
228
+			}
217 229
 
218
-		if m.Driver != c.expDriver {
219
-			t.Fatalf("Expected driver '%s', was '%s', for spec '%s'", c.expDriver, m.Driver, c.bind)
220
-		}
230
+			if m.Driver != c.expDriver {
231
+				t.Errorf("Expected driver '%s', was '%s', for spec '%s'", c.expDriver, m.Driver, c.bind)
232
+			}
221 233
 
222
-		if m.RW != c.expRW {
223
-			t.Fatalf("Expected RW '%v', was '%v' for spec '%s'", c.expRW, m.RW, c.bind)
234
+			if m.RW != c.expRW {
235
+				t.Errorf("Expected RW '%v', was '%v' for spec '%s'", c.expRW, m.RW, c.bind)
236
+			}
237
+			if m.Type != c.expType {
238
+				t.Fatalf("Expected type '%s', was '%s', for spec '%s'", c.expType, m.Type, c.bind)
239
+			}
224 240
 		}
225 241
 	}
242
+
243
+	tester(linParser, linuxCases)
244
+	tester(winParser, windowsCases)
245
+	tester(lcowParser, lcowCases)
226 246
 }
227 247
 
228 248
 func TestParseMountSpec(t *testing.T) {
... ...
@@ -235,43 +438,43 @@ func TestParseMountSpec(t *testing.T) {
235 235
 		t.Fatal(err)
236 236
 	}
237 237
 	defer os.RemoveAll(testDir)
238
-
238
+	parser := NewParser(runtime.GOOS)
239 239
 	cases := []c{
240
-		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: DefaultPropagationMode}},
241
-		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, RW: true, Propagation: DefaultPropagationMode}},
242
-		{mount.Mount{Type: mount.TypeBind, Source: testDir + string(os.PathSeparator), Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: DefaultPropagationMode}},
243
-		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath + string(os.PathSeparator), ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: DefaultPropagationMode}},
244
-		{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: DefaultCopyMode}},
245
-		{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath + string(os.PathSeparator)}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: DefaultCopyMode}},
240
+		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: parser.DefaultPropagationMode()}},
241
+		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, RW: true, Propagation: parser.DefaultPropagationMode()}},
242
+		{mount.Mount{Type: mount.TypeBind, Source: testDir + string(os.PathSeparator), Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: parser.DefaultPropagationMode()}},
243
+		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath + string(os.PathSeparator), ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: parser.DefaultPropagationMode()}},
244
+		{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: parser.DefaultCopyMode()}},
245
+		{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath + string(os.PathSeparator)}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: parser.DefaultCopyMode()}},
246 246
 	}
247 247
 
248 248
 	for i, c := range cases {
249 249
 		t.Logf("case %d", i)
250
-		mp, err := ParseMountSpec(c.input)
250
+		mp, err := parser.ParseMountSpec(c.input)
251 251
 		if err != nil {
252
-			t.Fatal(err)
252
+			t.Error(err)
253 253
 		}
254 254
 
255 255
 		if c.expected.Type != mp.Type {
256
-			t.Fatalf("Expected mount types to match. Expected: '%s', Actual: '%s'", c.expected.Type, mp.Type)
256
+			t.Errorf("Expected mount types to match. Expected: '%s', Actual: '%s'", c.expected.Type, mp.Type)
257 257
 		}
258 258
 		if c.expected.Destination != mp.Destination {
259
-			t.Fatalf("Expected mount destination to match. Expected: '%s', Actual: '%s'", c.expected.Destination, mp.Destination)
259
+			t.Errorf("Expected mount destination to match. Expected: '%s', Actual: '%s'", c.expected.Destination, mp.Destination)
260 260
 		}
261 261
 		if c.expected.Source != mp.Source {
262
-			t.Fatalf("Expected mount source to match. Expected: '%s', Actual: '%s'", c.expected.Source, mp.Source)
262
+			t.Errorf("Expected mount source to match. Expected: '%s', Actual: '%s'", c.expected.Source, mp.Source)
263 263
 		}
264 264
 		if c.expected.RW != mp.RW {
265
-			t.Fatalf("Expected mount writable to match. Expected: '%v', Actual: '%v'", c.expected.RW, mp.RW)
265
+			t.Errorf("Expected mount writable to match. Expected: '%v', Actual: '%v'", c.expected.RW, mp.RW)
266 266
 		}
267 267
 		if c.expected.Propagation != mp.Propagation {
268
-			t.Fatalf("Expected mount propagation to match. Expected: '%v', Actual: '%s'", c.expected.Propagation, mp.Propagation)
268
+			t.Errorf("Expected mount propagation to match. Expected: '%v', Actual: '%s'", c.expected.Propagation, mp.Propagation)
269 269
 		}
270 270
 		if c.expected.Driver != mp.Driver {
271
-			t.Fatalf("Expected mount driver to match. Expected: '%v', Actual: '%s'", c.expected.Driver, mp.Driver)
271
+			t.Errorf("Expected mount driver to match. Expected: '%v', Actual: '%s'", c.expected.Driver, mp.Driver)
272 272
 		}
273 273
 		if c.expected.CopyData != mp.CopyData {
274
-			t.Fatalf("Expected mount copy data to match. Expected: '%v', Actual: '%v'", c.expected.CopyData, mp.CopyData)
274
+			t.Errorf("Expected mount copy data to match. Expected: '%v', Actual: '%v'", c.expected.CopyData, mp.CopyData)
275 275
 		}
276 276
 	}
277 277
 }
... ...
@@ -4,153 +4,15 @@ package volume
4 4
 
5 5
 import (
6 6
 	"fmt"
7
-	"os"
8 7
 	"path/filepath"
9 8
 	"strings"
10
-
11
-	mounttypes "github.com/docker/docker/api/types/mount"
12 9
 )
13 10
 
14
-var platformRawValidationOpts = []func(o *validateOpts){
15
-	// need to make sure to not error out if the bind source does not exist on unix
16
-	// this is supported for historical reasons, the path will be automatically
17
-	// created later.
18
-	func(o *validateOpts) { o.skipBindSourceCheck = true },
19
-}
20
-
21
-// read-write modes
22
-var rwModes = map[string]bool{
23
-	"rw": true,
24
-	"ro": true,
25
-}
26
-
27
-// label modes
28
-var labelModes = map[string]bool{
29
-	"Z": true,
30
-	"z": true,
31
-}
32
-
33
-// consistency modes
34
-var consistencyModes = map[mounttypes.Consistency]bool{
35
-	mounttypes.ConsistencyFull:      true,
36
-	mounttypes.ConsistencyCached:    true,
37
-	mounttypes.ConsistencyDelegated: true,
38
-}
39
-
40
-// BackwardsCompatible decides whether this mount point can be
41
-// used in old versions of Docker or not.
42
-// Only bind mounts and local volumes can be used in old versions of Docker.
43
-func (m *MountPoint) BackwardsCompatible() bool {
44
-	return len(m.Source) > 0 || m.Driver == DefaultDriverName
45
-}
46
-
47
-// HasResource checks whether the given absolute path for a container is in
48
-// this mount point. If the relative path starts with `../` then the resource
49
-// is outside of this mount point, but we can't simply check for this prefix
50
-// because it misses `..` which is also outside of the mount, so check both.
51
-func (m *MountPoint) HasResource(absolutePath string) bool {
11
+func (p *linuxParser) HasResource(m *MountPoint, absolutePath string) bool {
52 12
 	relPath, err := filepath.Rel(m.Destination, absolutePath)
53 13
 	return err == nil && relPath != ".." && !strings.HasPrefix(relPath, fmt.Sprintf("..%c", filepath.Separator))
54 14
 }
55 15
 
56
-// IsVolumeNameValid checks a volume name in a platform specific manner.
57
-func IsVolumeNameValid(name string) (bool, error) {
58
-	return true, nil
59
-}
60
-
61
-// ValidMountMode will make sure the mount mode is valid.
62
-// returns if it's a valid mount mode or not.
63
-func ValidMountMode(mode string) bool {
64
-	if mode == "" {
65
-		return true
66
-	}
67
-
68
-	rwModeCount := 0
69
-	labelModeCount := 0
70
-	propagationModeCount := 0
71
-	copyModeCount := 0
72
-	consistencyModeCount := 0
73
-
74
-	for _, o := range strings.Split(mode, ",") {
75
-		switch {
76
-		case rwModes[o]:
77
-			rwModeCount++
78
-		case labelModes[o]:
79
-			labelModeCount++
80
-		case propagationModes[mounttypes.Propagation(o)]:
81
-			propagationModeCount++
82
-		case copyModeExists(o):
83
-			copyModeCount++
84
-		case consistencyModes[mounttypes.Consistency(o)]:
85
-			consistencyModeCount++
86
-		default:
87
-			return false
88
-		}
89
-	}
90
-
91
-	// Only one string for each mode is allowed.
92
-	if rwModeCount > 1 || labelModeCount > 1 || propagationModeCount > 1 || copyModeCount > 1 || consistencyModeCount > 1 {
93
-		return false
94
-	}
95
-	return true
96
-}
97
-
98
-// ReadWrite tells you if a mode string is a valid read-write mode or not.
99
-// If there are no specifications w.r.t read write mode, then by default
100
-// it returns true.
101
-func ReadWrite(mode string) bool {
102
-	if !ValidMountMode(mode) {
103
-		return false
104
-	}
105
-
106
-	for _, o := range strings.Split(mode, ",") {
107
-		if o == "ro" {
108
-			return false
109
-		}
110
-	}
111
-	return true
112
-}
113
-
114
-func validateNotRoot(p string) error {
115
-	p = filepath.Clean(convertSlash(p))
116
-	if p == "/" {
117
-		return fmt.Errorf("invalid specification: destination can't be '/'")
118
-	}
119
-	return nil
120
-}
121
-
122
-func convertSlash(p string) string {
123
-	return p
124
-}
125
-
126
-// isAbsPath reports whether the path is absolute.
127
-func isAbsPath(p string) bool {
128
-	return filepath.IsAbs(p)
129
-}
130
-
131
-func splitRawSpec(raw string) ([]string, error) {
132
-	if strings.Count(raw, ":") > 2 {
133
-		return nil, errInvalidSpec(raw)
134
-	}
135
-
136
-	arr := strings.SplitN(raw, ":", 3)
137
-	if arr[0] == "" {
138
-		return nil, errInvalidSpec(raw)
139
-	}
140
-	return arr, nil
141
-}
142
-
143
-func detectMountType(p string) mounttypes.Type {
144
-	if filepath.IsAbs(p) {
145
-		return mounttypes.TypeBind
146
-	}
147
-	return mounttypes.TypeVolume
148
-}
149
-
150
-func clean(p string) string {
151
-	return filepath.Clean(p)
152
-}
153
-
154
-func validateStat(fi os.FileInfo) error {
155
-	return nil
16
+func (p *windowsParser) HasResource(m *MountPoint, absolutePath string) bool {
17
+	return false
156 18
 }
157 19
deleted file mode 100644
... ...
@@ -1,16 +0,0 @@
1
-// +build !linux
2
-
3
-package volume
4
-
5
-import (
6
-	"fmt"
7
-	"runtime"
8
-
9
-	mounttypes "github.com/docker/docker/api/types/mount"
10
-)
11
-
12
-// ConvertTmpfsOptions converts *mounttypes.TmpfsOptions to the raw option string
13
-// for mount(2).
14
-func ConvertTmpfsOptions(opt *mounttypes.TmpfsOptions, readOnly bool) (string, error) {
15
-	return "", fmt.Errorf("%s does not support tmpfs", runtime.GOOS)
16
-}
... ...
@@ -1,213 +1,8 @@
1 1
 package volume
2 2
 
3
-import (
4
-	"fmt"
5
-	"os"
6
-	"path/filepath"
7
-	"regexp"
8
-	"strings"
9
-
10
-	mounttypes "github.com/docker/docker/api/types/mount"
11
-)
12
-
13
-// read-write modes
14
-var rwModes = map[string]bool{
15
-	"rw": true,
16
-}
17
-
18
-// read-only modes
19
-var roModes = map[string]bool{
20
-	"ro": true,
21
-}
22
-
23
-var platformRawValidationOpts = []func(*validateOpts){}
24
-
25
-const (
26
-	// Spec should be in the format [source:]destination[:mode]
27
-	//
28
-	// Examples: c:\foo bar:d:rw
29
-	//           c:\foo:d:\bar
30
-	//           myname:d:
31
-	//           d:\
32
-	//
33
-	// Explanation of this regex! Thanks @thaJeztah on IRC and gist for help. See
34
-	// https://gist.github.com/thaJeztah/6185659e4978789fb2b2. A good place to
35
-	// test is https://regex-golang.appspot.com/assets/html/index.html
36
-	//
37
-	// Useful link for referencing named capturing groups:
38
-	// http://stackoverflow.com/questions/20750843/using-named-matches-from-go-regex
39
-	//
40
-	// There are three match groups: source, destination and mode.
41
-	//
42
-
43
-	// RXHostDir is the first option of a source
44
-	RXHostDir = `[a-z]:\\(?:[^\\/:*?"<>|\r\n]+\\?)*`
45
-	// RXName is the second option of a source
46
-	RXName = `[^\\/:*?"<>|\r\n]+`
47
-	// RXPipe is a named path pipe (starts with `\\.\pipe\`, possibly with / instead of \)
48
-	RXPipe = `[/\\]{2}.[/\\]pipe[/\\][^:*?"<>|\r\n]+`
49
-	// RXReservedNames are reserved names not possible on Windows
50
-	RXReservedNames = `(con)|(prn)|(nul)|(aux)|(com[1-9])|(lpt[1-9])`
51
-
52
-	// RXSource is the combined possibilities for a source
53
-	RXSource = `((?P<source>((` + RXHostDir + `)|(` + RXName + `)|(` + RXPipe + `))):)?`
54
-
55
-	// Source. Can be either a host directory, a name, or omitted:
56
-	//  HostDir:
57
-	//    -  Essentially using the folder solution from
58
-	//       https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch08s18.html
59
-	//       but adding case insensitivity.
60
-	//    -  Must be an absolute path such as c:\path
61
-	//    -  Can include spaces such as `c:\program files`
62
-	//    -  And then followed by a colon which is not in the capture group
63
-	//    -  And can be optional
64
-	//  Name:
65
-	//    -  Must not contain invalid NTFS filename characters (https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx)
66
-	//    -  And then followed by a colon which is not in the capture group
67
-	//    -  And can be optional
68
-
69
-	// RXDestinationDir is the file path option for the mount destination
70
-	RXDestinationDir = `([a-z]):((?:\\[^\\/:*?"<>\r\n]+)*\\?)`
71
-	// RXDestination is the regex expression for the mount destination
72
-	RXDestination = `(?P<destination>(` + RXDestinationDir + `)|(` + RXPipe + `))`
73
-	// Destination (aka container path):
74
-	//    -  Variation on hostdir but can be a drive followed by colon as well
75
-	//    -  If a path, must be absolute. Can include spaces
76
-	//    -  Drive cannot be c: (explicitly checked in code, not RegEx)
77
-
78
-	// RXMode is the regex expression for the mode of the mount
79
-	// Mode (optional):
80
-	//    -  Hopefully self explanatory in comparison to above regex's.
81
-	//    -  Colon is not in the capture group
82
-	RXMode = `(:(?P<mode>(?i)ro|rw))?`
83
-)
84
-
85
-// BackwardsCompatible decides whether this mount point can be
86
-// used in old versions of Docker or not.
87
-// Windows volumes are never backwards compatible.
88
-func (m *MountPoint) BackwardsCompatible() bool {
3
+func (p *windowsParser) HasResource(m *MountPoint, absolutePath string) bool {
89 4
 	return false
90 5
 }
91
-
92
-func splitRawSpec(raw string) ([]string, error) {
93
-	specExp := regexp.MustCompile(`^` + RXSource + RXDestination + RXMode + `$`)
94
-	match := specExp.FindStringSubmatch(strings.ToLower(raw))
95
-
96
-	// Must have something back
97
-	if len(match) == 0 {
98
-		return nil, errInvalidSpec(raw)
99
-	}
100
-
101
-	var split []string
102
-	matchgroups := make(map[string]string)
103
-	// Pull out the sub expressions from the named capture groups
104
-	for i, name := range specExp.SubexpNames() {
105
-		matchgroups[name] = strings.ToLower(match[i])
106
-	}
107
-	if source, exists := matchgroups["source"]; exists {
108
-		if source != "" {
109
-			split = append(split, source)
110
-		}
111
-	}
112
-	if destination, exists := matchgroups["destination"]; exists {
113
-		if destination != "" {
114
-			split = append(split, destination)
115
-		}
116
-	}
117
-	if mode, exists := matchgroups["mode"]; exists {
118
-		if mode != "" {
119
-			split = append(split, mode)
120
-		}
121
-	}
122
-	// Fix #26329. If the destination appears to be a file, and the source is null,
123
-	// it may be because we've fallen through the possible naming regex and hit a
124
-	// situation where the user intention was to map a file into a container through
125
-	// a local volume, but this is not supported by the platform.
126
-	if matchgroups["source"] == "" && matchgroups["destination"] != "" {
127
-		validName, err := IsVolumeNameValid(matchgroups["destination"])
128
-		if err != nil {
129
-			return nil, err
130
-		}
131
-		if !validName {
132
-			if fi, err := os.Stat(matchgroups["destination"]); err == nil {
133
-				if !fi.IsDir() {
134
-					return nil, fmt.Errorf("file '%s' cannot be mapped. Only directories can be mapped on this platform", matchgroups["destination"])
135
-				}
136
-			}
137
-		}
138
-	}
139
-	return split, nil
140
-}
141
-
142
-func detectMountType(p string) mounttypes.Type {
143
-	if strings.HasPrefix(filepath.FromSlash(p), `\\.\pipe\`) {
144
-		return mounttypes.TypeNamedPipe
145
-	} else if filepath.IsAbs(p) {
146
-		return mounttypes.TypeBind
147
-	}
148
-	return mounttypes.TypeVolume
149
-}
150
-
151
-// IsVolumeNameValid checks a volume name in a platform specific manner.
152
-func IsVolumeNameValid(name string) (bool, error) {
153
-	nameExp := regexp.MustCompile(`^` + RXName + `$`)
154
-	if !nameExp.MatchString(name) {
155
-		return false, nil
156
-	}
157
-	nameExp = regexp.MustCompile(`^` + RXReservedNames + `$`)
158
-	if nameExp.MatchString(name) {
159
-		return false, fmt.Errorf("volume name %q cannot be a reserved word for Windows filenames", name)
160
-	}
161
-	return true, nil
162
-}
163
-
164
-// ValidMountMode will make sure the mount mode is valid.
165
-// returns if it's a valid mount mode or not.
166
-func ValidMountMode(mode string) bool {
167
-	if mode == "" {
168
-		return true
169
-	}
170
-	return roModes[strings.ToLower(mode)] || rwModes[strings.ToLower(mode)]
171
-}
172
-
173
-// ReadWrite tells you if a mode string is a valid read-write mode or not.
174
-func ReadWrite(mode string) bool {
175
-	return rwModes[strings.ToLower(mode)] || mode == ""
176
-}
177
-
178
-func validateNotRoot(p string) error {
179
-	p = strings.ToLower(convertSlash(p))
180
-	if p == "c:" || p == `c:\` {
181
-		return fmt.Errorf("destination path cannot be `c:` or `c:\\`: %v", p)
182
-	}
183
-	return nil
184
-}
185
-
186
-func convertSlash(p string) string {
187
-	return filepath.FromSlash(p)
188
-}
189
-
190
-// isAbsPath returns whether a path is absolute for the purposes of mounting into a container
191
-// (absolute paths, drive letter paths such as X:, and paths starting with `\\.\` to support named pipes).
192
-func isAbsPath(p string) bool {
193
-	return filepath.IsAbs(p) ||
194
-		strings.HasPrefix(p, `\\.\`) ||
195
-		(len(p) == 2 && p[1] == ':' && ((p[0] >= 'a' && p[0] <= 'z') || (p[0] >= 'A' && p[0] <= 'Z')))
196
-}
197
-
198
-// Do not clean plain drive letters or paths starting with `\\.\`.
199
-var cleanRegexp = regexp.MustCompile(`^([a-z]:|[/\\]{2}\.[/\\].*)$`)
200
-
201
-func clean(p string) string {
202
-	if match := cleanRegexp.MatchString(p); match {
203
-		return p
204
-	}
205
-	return filepath.Clean(p)
206
-}
207
-
208
-func validateStat(fi os.FileInfo) error {
209
-	if !fi.IsDir() {
210
-		return fmt.Errorf("source path must be a directory")
211
-	}
212
-	return nil
6
+func (p *linuxParser) HasResource(m *MountPoint, absolutePath string) bool {
7
+	return false
213 8
 }
214 9
new file mode 100644
... ...
@@ -0,0 +1,456 @@
0
+package volume
1
+
2
+import (
3
+	"errors"
4
+	"fmt"
5
+	"os"
6
+	"regexp"
7
+	"runtime"
8
+	"strings"
9
+
10
+	"github.com/docker/docker/api/types/mount"
11
+	"github.com/docker/docker/pkg/stringid"
12
+)
13
+
14
+type windowsParser struct {
15
+}
16
+
17
+const (
18
+	// Spec should be in the format [source:]destination[:mode]
19
+	//
20
+	// Examples: c:\foo bar:d:rw
21
+	//           c:\foo:d:\bar
22
+	//           myname:d:
23
+	//           d:\
24
+	//
25
+	// Explanation of this regex! Thanks @thaJeztah on IRC and gist for help. See
26
+	// https://gist.github.com/thaJeztah/6185659e4978789fb2b2. A good place to
27
+	// test is https://regex-golang.appspot.com/assets/html/index.html
28
+	//
29
+	// Useful link for referencing named capturing groups:
30
+	// http://stackoverflow.com/questions/20750843/using-named-matches-from-go-regex
31
+	//
32
+	// There are three match groups: source, destination and mode.
33
+	//
34
+
35
+	// rxHostDir is the first option of a source
36
+	rxHostDir = `(?:\\\\\?\\)?[a-z]:[\\/](?:[^\\/:*?"<>|\r\n]+[\\/]?)*`
37
+	// rxName is the second option of a source
38
+	rxName = `[^\\/:*?"<>|\r\n]+`
39
+
40
+	// RXReservedNames are reserved names not possible on Windows
41
+	rxReservedNames = `(con)|(prn)|(nul)|(aux)|(com[1-9])|(lpt[1-9])`
42
+
43
+	// rxPipe is a named path pipe (starts with `\\.\pipe\`, possibly with / instead of \)
44
+	rxPipe = `[/\\]{2}.[/\\]pipe[/\\][^:*?"<>|\r\n]+`
45
+	// rxSource is the combined possibilities for a source
46
+	rxSource = `((?P<source>((` + rxHostDir + `)|(` + rxName + `)|(` + rxPipe + `))):)?`
47
+
48
+	// Source. Can be either a host directory, a name, or omitted:
49
+	//  HostDir:
50
+	//    -  Essentially using the folder solution from
51
+	//       https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch08s18.html
52
+	//       but adding case insensitivity.
53
+	//    -  Must be an absolute path such as c:\path
54
+	//    -  Can include spaces such as `c:\program files`
55
+	//    -  And then followed by a colon which is not in the capture group
56
+	//    -  And can be optional
57
+	//  Name:
58
+	//    -  Must not contain invalid NTFS filename characters (https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx)
59
+	//    -  And then followed by a colon which is not in the capture group
60
+	//    -  And can be optional
61
+
62
+	// rxDestination is the regex expression for the mount destination
63
+	rxDestination = `(?P<destination>((?:\\\\\?\\)?([a-z]):((?:[\\/][^\\/:*?"<>\r\n]+)*[\\/]?))|(` + rxPipe + `))`
64
+
65
+	rxLCOWDestination = `(?P<destination>/(?:[^\\/:*?"<>\r\n]+[/]?)*)`
66
+	// Destination (aka container path):
67
+	//    -  Variation on hostdir but can be a drive followed by colon as well
68
+	//    -  If a path, must be absolute. Can include spaces
69
+	//    -  Drive cannot be c: (explicitly checked in code, not RegEx)
70
+
71
+	// rxMode is the regex expression for the mode of the mount
72
+	// Mode (optional):
73
+	//    -  Hopefully self explanatory in comparison to above regex's.
74
+	//    -  Colon is not in the capture group
75
+	rxMode = `(:(?P<mode>(?i)ro|rw))?`
76
+)
77
+
78
+type mountValidator func(mnt *mount.Mount) error
79
+
80
+func windowsSplitRawSpec(raw, destRegex string) ([]string, error) {
81
+	specExp := regexp.MustCompile(`^` + rxSource + destRegex + rxMode + `$`)
82
+	match := specExp.FindStringSubmatch(strings.ToLower(raw))
83
+
84
+	// Must have something back
85
+	if len(match) == 0 {
86
+		return nil, errInvalidSpec(raw)
87
+	}
88
+
89
+	var split []string
90
+	matchgroups := make(map[string]string)
91
+	// Pull out the sub expressions from the named capture groups
92
+	for i, name := range specExp.SubexpNames() {
93
+		matchgroups[name] = strings.ToLower(match[i])
94
+	}
95
+	if source, exists := matchgroups["source"]; exists {
96
+		if source != "" {
97
+			split = append(split, source)
98
+		}
99
+	}
100
+	if destination, exists := matchgroups["destination"]; exists {
101
+		if destination != "" {
102
+			split = append(split, destination)
103
+		}
104
+	}
105
+	if mode, exists := matchgroups["mode"]; exists {
106
+		if mode != "" {
107
+			split = append(split, mode)
108
+		}
109
+	}
110
+	// Fix #26329. If the destination appears to be a file, and the source is null,
111
+	// it may be because we've fallen through the possible naming regex and hit a
112
+	// situation where the user intention was to map a file into a container through
113
+	// a local volume, but this is not supported by the platform.
114
+	if matchgroups["source"] == "" && matchgroups["destination"] != "" {
115
+		volExp := regexp.MustCompile(`^` + rxName + `$`)
116
+		reservedNameExp := regexp.MustCompile(`^` + rxReservedNames + `$`)
117
+
118
+		if volExp.MatchString(matchgroups["destination"]) {
119
+			if reservedNameExp.MatchString(matchgroups["destination"]) {
120
+				return nil, fmt.Errorf("volume name %q cannot be a reserved word for Windows filenames", matchgroups["destination"])
121
+			}
122
+		} else {
123
+
124
+			exists, isDir, _ := currentFileInfoProvider.fileInfo(matchgroups["destination"])
125
+			if exists && !isDir {
126
+				return nil, fmt.Errorf("file '%s' cannot be mapped. Only directories can be mapped on this platform", matchgroups["destination"])
127
+
128
+			}
129
+		}
130
+	}
131
+	return split, nil
132
+}
133
+
134
+func windowsValidMountMode(mode string) bool {
135
+	if mode == "" {
136
+		return true
137
+	}
138
+	return rwModes[strings.ToLower(mode)]
139
+}
140
+func windowsValidateNotRoot(p string) error {
141
+	p = strings.ToLower(strings.Replace(p, `/`, `\`, -1))
142
+	if p == "c:" || p == `c:\` {
143
+		return fmt.Errorf("destination path cannot be `c:` or `c:\\`: %v", p)
144
+	}
145
+	return nil
146
+}
147
+
148
+var windowsSpecificValidators mountValidator = func(mnt *mount.Mount) error {
149
+	return windowsValidateNotRoot(mnt.Target)
150
+}
151
+
152
+func windowsValidateRegex(p, r string) error {
153
+	if regexp.MustCompile(`^` + r + `$`).MatchString(strings.ToLower(p)) {
154
+		return nil
155
+	}
156
+	return fmt.Errorf("invalid mount path: '%s'", p)
157
+}
158
+func windowsValidateAbsolute(p string) error {
159
+	if err := windowsValidateRegex(p, rxDestination); err != nil {
160
+		return fmt.Errorf("invalid mount path: '%s' mount path must be absolute", p)
161
+	}
162
+	return nil
163
+}
164
+
165
+func windowsDetectMountType(p string) mount.Type {
166
+	if strings.HasPrefix(p, `\\.\pipe\`) {
167
+		return mount.TypeNamedPipe
168
+	} else if regexp.MustCompile(`^` + rxHostDir + `$`).MatchString(p) {
169
+		return mount.TypeBind
170
+	} else {
171
+		return mount.TypeVolume
172
+	}
173
+}
174
+
175
+func (p *windowsParser) ReadWrite(mode string) bool {
176
+	return strings.ToLower(mode) != "ro"
177
+}
178
+
179
+// IsVolumeNameValid checks a volume name in a platform specific manner.
180
+func (p *windowsParser) ValidateVolumeName(name string) error {
181
+	nameExp := regexp.MustCompile(`^` + rxName + `$`)
182
+	if !nameExp.MatchString(name) {
183
+		return errors.New("invalid volume name")
184
+	}
185
+	nameExp = regexp.MustCompile(`^` + rxReservedNames + `$`)
186
+	if nameExp.MatchString(name) {
187
+		return fmt.Errorf("volume name %q cannot be a reserved word for Windows filenames", name)
188
+	}
189
+	return nil
190
+}
191
+func (p *windowsParser) validateMountConfig(mnt *mount.Mount) error {
192
+	return p.validateMountConfigReg(mnt, rxDestination, windowsSpecificValidators)
193
+}
194
+
195
+type fileInfoProvider interface {
196
+	fileInfo(path string) (exist, isDir bool, err error)
197
+}
198
+
199
+type defaultFileInfoProvider struct {
200
+}
201
+
202
+func (defaultFileInfoProvider) fileInfo(path string) (exist, isDir bool, err error) {
203
+	fi, err := os.Stat(path)
204
+	if err != nil {
205
+		if !os.IsNotExist(err) {
206
+			return false, false, err
207
+		}
208
+		return false, false, nil
209
+	}
210
+	return true, fi.IsDir(), nil
211
+}
212
+
213
+var currentFileInfoProvider fileInfoProvider = defaultFileInfoProvider{}
214
+
215
+func (p *windowsParser) validateMountConfigReg(mnt *mount.Mount, destRegex string, additionalValidators ...mountValidator) error {
216
+
217
+	for _, v := range additionalValidators {
218
+		if err := v(mnt); err != nil {
219
+			return &errMountConfig{mnt, err}
220
+		}
221
+	}
222
+	if len(mnt.Target) == 0 {
223
+		return &errMountConfig{mnt, errMissingField("Target")}
224
+	}
225
+
226
+	if err := windowsValidateRegex(mnt.Target, destRegex); err != nil {
227
+		return &errMountConfig{mnt, err}
228
+	}
229
+
230
+	switch mnt.Type {
231
+	case mount.TypeBind:
232
+		if len(mnt.Source) == 0 {
233
+			return &errMountConfig{mnt, errMissingField("Source")}
234
+		}
235
+		// Don't error out just because the propagation mode is not supported on the platform
236
+		if opts := mnt.BindOptions; opts != nil {
237
+			if len(opts.Propagation) > 0 {
238
+				return &errMountConfig{mnt, fmt.Errorf("invalid propagation mode: %s", opts.Propagation)}
239
+			}
240
+		}
241
+		if mnt.VolumeOptions != nil {
242
+			return &errMountConfig{mnt, errExtraField("VolumeOptions")}
243
+		}
244
+
245
+		if err := windowsValidateAbsolute(mnt.Source); err != nil {
246
+			return &errMountConfig{mnt, err}
247
+		}
248
+
249
+		exists, isdir, err := currentFileInfoProvider.fileInfo(mnt.Source)
250
+		if err != nil {
251
+			return &errMountConfig{mnt, err}
252
+		}
253
+		if !exists {
254
+			return &errMountConfig{mnt, errBindNotExist}
255
+		}
256
+		if !isdir {
257
+			return &errMountConfig{mnt, fmt.Errorf("source path must be a directory")}
258
+		}
259
+
260
+	case mount.TypeVolume:
261
+		if mnt.BindOptions != nil {
262
+			return &errMountConfig{mnt, errExtraField("BindOptions")}
263
+		}
264
+
265
+		if len(mnt.Source) == 0 && mnt.ReadOnly {
266
+			return &errMountConfig{mnt, fmt.Errorf("must not set ReadOnly mode when using anonymous volumes")}
267
+		}
268
+
269
+		if len(mnt.Source) != 0 {
270
+			if err := p.ValidateVolumeName(mnt.Source); err != nil {
271
+				return &errMountConfig{mnt, err}
272
+			}
273
+		}
274
+	case mount.TypeNamedPipe:
275
+		if len(mnt.Source) == 0 {
276
+			return &errMountConfig{mnt, errMissingField("Source")}
277
+		}
278
+
279
+		if mnt.BindOptions != nil {
280
+			return &errMountConfig{mnt, errExtraField("BindOptions")}
281
+		}
282
+
283
+		if mnt.ReadOnly {
284
+			return &errMountConfig{mnt, errExtraField("ReadOnly")}
285
+		}
286
+
287
+		if windowsDetectMountType(mnt.Source) != mount.TypeNamedPipe {
288
+			return &errMountConfig{mnt, fmt.Errorf("'%s' is not a valid pipe path", mnt.Source)}
289
+		}
290
+
291
+		if windowsDetectMountType(mnt.Target) != mount.TypeNamedPipe {
292
+			return &errMountConfig{mnt, fmt.Errorf("'%s' is not a valid pipe path", mnt.Target)}
293
+		}
294
+	default:
295
+		return &errMountConfig{mnt, errors.New("mount type unknown")}
296
+	}
297
+	return nil
298
+}
299
+func (p *windowsParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
300
+	return p.parseMountRaw(raw, volumeDriver, rxDestination, true, windowsSpecificValidators)
301
+}
302
+
303
+func (p *windowsParser) parseMountRaw(raw, volumeDriver, destRegex string, convertTargetToBackslash bool, additionalValidators ...mountValidator) (*MountPoint, error) {
304
+	arr, err := windowsSplitRawSpec(raw, destRegex)
305
+	if err != nil {
306
+		return nil, err
307
+	}
308
+
309
+	var spec mount.Mount
310
+	var mode string
311
+	switch len(arr) {
312
+	case 1:
313
+		// Just a destination path in the container
314
+		spec.Target = arr[0]
315
+	case 2:
316
+		if windowsValidMountMode(arr[1]) {
317
+			// Destination + Mode is not a valid volume - volumes
318
+			// cannot include a mode. e.g. /foo:rw
319
+			return nil, errInvalidSpec(raw)
320
+		}
321
+		// Host Source Path or Name + Destination
322
+		spec.Source = strings.Replace(arr[0], `/`, `\`, -1)
323
+		spec.Target = arr[1]
324
+	case 3:
325
+		// HostSourcePath+DestinationPath+Mode
326
+		spec.Source = strings.Replace(arr[0], `/`, `\`, -1)
327
+		spec.Target = arr[1]
328
+		mode = arr[2]
329
+	default:
330
+		return nil, errInvalidSpec(raw)
331
+	}
332
+	if convertTargetToBackslash {
333
+		spec.Target = strings.Replace(spec.Target, `/`, `\`, -1)
334
+	}
335
+
336
+	if !windowsValidMountMode(mode) {
337
+		return nil, errInvalidMode(mode)
338
+	}
339
+
340
+	spec.Type = windowsDetectMountType(spec.Source)
341
+	spec.ReadOnly = !p.ReadWrite(mode)
342
+
343
+	// cannot assume that if a volume driver is passed in that we should set it
344
+	if volumeDriver != "" && spec.Type == mount.TypeVolume {
345
+		spec.VolumeOptions = &mount.VolumeOptions{
346
+			DriverConfig: &mount.Driver{Name: volumeDriver},
347
+		}
348
+	}
349
+
350
+	if copyData, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
351
+		if spec.VolumeOptions == nil {
352
+			spec.VolumeOptions = &mount.VolumeOptions{}
353
+		}
354
+		spec.VolumeOptions.NoCopy = !copyData
355
+	}
356
+
357
+	mp, err := p.parseMountSpec(spec, destRegex, convertTargetToBackslash, additionalValidators...)
358
+	if mp != nil {
359
+		mp.Mode = mode
360
+	}
361
+	if err != nil {
362
+		err = fmt.Errorf("%v: %v", errInvalidSpec(raw), err)
363
+	}
364
+	return mp, err
365
+}
366
+
367
+func (p *windowsParser) ParseMountSpec(cfg mount.Mount) (*MountPoint, error) {
368
+	return p.parseMountSpec(cfg, rxDestination, true, windowsSpecificValidators)
369
+}
370
+func (p *windowsParser) parseMountSpec(cfg mount.Mount, destRegex string, convertTargetToBackslash bool, additionalValidators ...mountValidator) (*MountPoint, error) {
371
+	if err := p.validateMountConfigReg(&cfg, destRegex, additionalValidators...); err != nil {
372
+		return nil, err
373
+	}
374
+	mp := &MountPoint{
375
+		RW:          !cfg.ReadOnly,
376
+		Destination: cfg.Target,
377
+		Type:        cfg.Type,
378
+		Spec:        cfg,
379
+	}
380
+	if convertTargetToBackslash {
381
+		mp.Destination = strings.Replace(cfg.Target, `/`, `\`, -1)
382
+	}
383
+
384
+	switch cfg.Type {
385
+	case mount.TypeVolume:
386
+		if cfg.Source == "" {
387
+			mp.Name = stringid.GenerateNonCryptoID()
388
+		} else {
389
+			mp.Name = cfg.Source
390
+		}
391
+		mp.CopyData = p.DefaultCopyMode()
392
+
393
+		if cfg.VolumeOptions != nil {
394
+			if cfg.VolumeOptions.DriverConfig != nil {
395
+				mp.Driver = cfg.VolumeOptions.DriverConfig.Name
396
+			}
397
+			if cfg.VolumeOptions.NoCopy {
398
+				mp.CopyData = false
399
+			}
400
+		}
401
+	case mount.TypeBind:
402
+		mp.Source = strings.Replace(cfg.Source, `/`, `\`, -1)
403
+	case mount.TypeNamedPipe:
404
+		mp.Source = strings.Replace(cfg.Source, `/`, `\`, -1)
405
+	}
406
+	// cleanup trailing `\` except for paths like `c:\`
407
+	if len(mp.Source) > 3 && mp.Source[len(mp.Source)-1] == '\\' {
408
+		mp.Source = mp.Source[:len(mp.Source)-1]
409
+	}
410
+	if len(mp.Destination) > 3 && mp.Destination[len(mp.Destination)-1] == '\\' {
411
+		mp.Destination = mp.Destination[:len(mp.Destination)-1]
412
+	}
413
+	return mp, nil
414
+}
415
+
416
+func (p *windowsParser) ParseVolumesFrom(spec string) (string, string, error) {
417
+	if len(spec) == 0 {
418
+		return "", "", fmt.Errorf("volumes-from specification cannot be an empty string")
419
+	}
420
+
421
+	specParts := strings.SplitN(spec, ":", 2)
422
+	id := specParts[0]
423
+	mode := "rw"
424
+
425
+	if len(specParts) == 2 {
426
+		mode = specParts[1]
427
+		if !windowsValidMountMode(mode) {
428
+			return "", "", errInvalidMode(mode)
429
+		}
430
+
431
+		// Do not allow copy modes on volumes-from
432
+		if _, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
433
+			return "", "", errInvalidMode(mode)
434
+		}
435
+	}
436
+	return id, mode, nil
437
+}
438
+
439
+func (p *windowsParser) DefaultPropagationMode() mount.Propagation {
440
+	return mount.Propagation("")
441
+}
442
+
443
+func (p *windowsParser) ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error) {
444
+	return "", fmt.Errorf("%s does not support tmpfs", runtime.GOOS)
445
+}
446
+func (p *windowsParser) DefaultCopyMode() bool {
447
+	return false
448
+}
449
+func (p *windowsParser) IsBackwardCompatible(m *MountPoint) bool {
450
+	return false
451
+}
452
+
453
+func (p *windowsParser) ValidateTmpfsMountDestination(dest string) error {
454
+	return errors.New("Platform does not support tmpfs")
455
+}