Browse code

Move mount parsing to separate package.

This moves the platform specific stuff in a separate package and keeps
the `volume` package and the defined interfaces light to import.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>

Brian Goff authored on 2018/04/18 05:50:28
Showing 39 changed files
... ...
@@ -37,6 +37,7 @@ import (
37 37
 	"github.com/docker/docker/restartmanager"
38 38
 	"github.com/docker/docker/runconfig"
39 39
 	"github.com/docker/docker/volume"
40
+	volumemounts "github.com/docker/docker/volume/mounts"
40 41
 	"github.com/docker/go-connections/nat"
41 42
 	units "github.com/docker/go-units"
42 43
 	"github.com/docker/libnetwork"
... ...
@@ -94,7 +95,7 @@ type Container struct {
94 94
 	RestartCount           int
95 95
 	HasBeenStartedBefore   bool
96 96
 	HasBeenManuallyStopped bool // used for unless-stopped restart policy
97
-	MountPoints            map[string]*volume.MountPoint
97
+	MountPoints            map[string]*volumemounts.MountPoint
98 98
 	HostConfig             *containertypes.HostConfig `json:"-"` // do not serialize the host config in the json, otherwise we'll make the container unportable
99 99
 	ExecCommands           *exec.Store                `json:"-"`
100 100
 	DependencyStore        agentexec.DependencyGetter `json:"-"`
... ...
@@ -128,7 +129,7 @@ func NewBaseContainer(id, root string) *Container {
128 128
 		State:         NewState(),
129 129
 		ExecCommands:  exec.NewStore(),
130 130
 		Root:          root,
131
-		MountPoints:   make(map[string]*volume.MountPoint),
131
+		MountPoints:   make(map[string]*volumemounts.MountPoint),
132 132
 		StreamConfig:  stream.NewConfig(),
133 133
 		attachContext: &attachContext{},
134 134
 	}
... ...
@@ -450,8 +451,8 @@ func (container *Container) AddMountPointWithVolume(destination string, vol volu
450 450
 	if operatingSystem == "" {
451 451
 		operatingSystem = runtime.GOOS
452 452
 	}
453
-	volumeParser := volume.NewParser(operatingSystem)
454
-	container.MountPoints[destination] = &volume.MountPoint{
453
+	volumeParser := volumemounts.NewParser(operatingSystem)
454
+	container.MountPoints[destination] = &volumemounts.MountPoint{
455 455
 		Type:        mounttypes.TypeVolume,
456 456
 		Name:        vol.Name(),
457 457
 		Driver:      vol.DriverName(),
... ...
@@ -15,6 +15,7 @@ import (
15 15
 	"github.com/docker/docker/pkg/mount"
16 16
 	"github.com/docker/docker/pkg/stringid"
17 17
 	"github.com/docker/docker/volume"
18
+	volumemounts "github.com/docker/docker/volume/mounts"
18 19
 	"github.com/opencontainers/selinux/go-selinux/label"
19 20
 	"github.com/pkg/errors"
20 21
 	"github.com/sirupsen/logrus"
... ...
@@ -61,7 +62,7 @@ func (container *Container) BuildHostnameFile() error {
61 61
 func (container *Container) NetworkMounts() []Mount {
62 62
 	var mounts []Mount
63 63
 	shared := container.HostConfig.NetworkMode.IsContainer()
64
-	parser := volume.NewParser(container.OS)
64
+	parser := volumemounts.NewParser(container.OS)
65 65
 	if container.ResolvConfPath != "" {
66 66
 		if _, err := os.Stat(container.ResolvConfPath); err != nil {
67 67
 			logrus.Warnf("ResolvConfPath set to %q, but can't stat this filename (err = %v); skipping", container.ResolvConfPath, err)
... ...
@@ -198,7 +199,7 @@ func (container *Container) UnmountIpcMount(unmount func(pth string) error) erro
198 198
 // IpcMounts returns the list of IPC mounts
199 199
 func (container *Container) IpcMounts() []Mount {
200 200
 	var mounts []Mount
201
-	parser := volume.NewParser(container.OS)
201
+	parser := volumemounts.NewParser(container.OS)
202 202
 
203 203
 	if container.HasMountFor("/dev/shm") {
204 204
 		return mounts
... ...
@@ -402,7 +403,7 @@ func copyExistingContents(source, destination string) error {
402 402
 
403 403
 // TmpfsMounts returns the list of tmpfs mounts
404 404
 func (container *Container) TmpfsMounts() ([]Mount, error) {
405
-	parser := volume.NewParser(container.OS)
405
+	parser := volumemounts.NewParser(container.OS)
406 406
 	var mounts []Mount
407 407
 	for dest, data := range container.HostConfig.Tmpfs {
408 408
 		mounts = append(mounts, Mount{
... ...
@@ -4,7 +4,7 @@ package daemon // import "github.com/docker/docker/daemon"
4 4
 
5 5
 import (
6 6
 	"github.com/docker/docker/container"
7
-	"github.com/docker/docker/volume"
7
+	volumemounts "github.com/docker/docker/volume/mounts"
8 8
 )
9 9
 
10 10
 // checkIfPathIsInAVolume checks if the path is in a volume. If it is, it
... ...
@@ -12,7 +12,7 @@ import (
12 12
 // cannot be configured with a read-only rootfs.
13 13
 func checkIfPathIsInAVolume(container *container.Container, absPath string) (bool, error) {
14 14
 	var toVolume bool
15
-	parser := volume.NewParser(container.OS)
15
+	parser := volumemounts.NewParser(container.OS)
16 16
 	for _, mnt := range container.MountPoints {
17 17
 		if toVolume = parser.HasResource(mnt, absPath); toVolume {
18 18
 			if mnt.RW {
... ...
@@ -20,7 +20,7 @@ import (
20 20
 	"github.com/docker/docker/pkg/system"
21 21
 	"github.com/docker/docker/pkg/truncindex"
22 22
 	"github.com/docker/docker/runconfig"
23
-	"github.com/docker/docker/volume"
23
+	volumemounts "github.com/docker/docker/volume/mounts"
24 24
 	"github.com/docker/go-connections/nat"
25 25
 	"github.com/opencontainers/selinux/go-selinux/label"
26 26
 	"github.com/pkg/errors"
... ...
@@ -296,7 +296,7 @@ func (daemon *Daemon) verifyContainerSettings(platform string, hostConfig *conta
296 296
 	}
297 297
 
298 298
 	// Validate mounts; check if host directories still exist
299
-	parser := volume.NewParser(platform)
299
+	parser := volumemounts.NewParser(platform)
300 300
 	for _, cfg := range hostConfig.Mounts {
301 301
 		if err := parser.ValidateMountConfig(&cfg); err != nil {
302 302
 			return nil, err
... ...
@@ -7,7 +7,7 @@ import (
7 7
 	containertypes "github.com/docker/docker/api/types/container"
8 8
 	"github.com/docker/docker/container"
9 9
 	"github.com/docker/docker/pkg/stringid"
10
-	"github.com/docker/docker/volume"
10
+	volumemounts "github.com/docker/docker/volume/mounts"
11 11
 )
12 12
 
13 13
 // createContainerOSSpecificSettings performs host-OS specific container create functionality
... ...
@@ -26,7 +26,7 @@ func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Con
26 26
 		}
27 27
 		hostConfig.Isolation = "hyperv"
28 28
 	}
29
-	parser := volume.NewParser(container.OS)
29
+	parser := volumemounts.NewParser(container.OS)
30 30
 	for spec := range config.Volumes {
31 31
 
32 32
 		mp, err := parser.ParseMountRaw(spec, hostConfig.VolumeDriver)
... ...
@@ -33,7 +33,7 @@ import (
33 33
 	"github.com/docker/docker/pkg/parsers/kernel"
34 34
 	"github.com/docker/docker/pkg/sysinfo"
35 35
 	"github.com/docker/docker/runconfig"
36
-	"github.com/docker/docker/volume"
36
+	volumemounts "github.com/docker/docker/volume/mounts"
37 37
 	"github.com/docker/libnetwork"
38 38
 	nwconfig "github.com/docker/libnetwork/config"
39 39
 	"github.com/docker/libnetwork/drivers/bridge"
... ...
@@ -626,7 +626,7 @@ func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes.
626 626
 		return warnings, fmt.Errorf("Unknown runtime specified %s", hostConfig.Runtime)
627 627
 	}
628 628
 
629
-	parser := volume.NewParser(runtime.GOOS)
629
+	parser := volumemounts.NewParser(runtime.GOOS)
630 630
 	for dest := range hostConfig.Tmpfs {
631 631
 		if err := parser.ValidateTmpfsMountDestination(dest); err != nil {
632 632
 			return warnings, err
... ...
@@ -18,7 +18,7 @@ import (
18 18
 	"github.com/docker/docker/oci"
19 19
 	"github.com/docker/docker/pkg/idtools"
20 20
 	"github.com/docker/docker/pkg/mount"
21
-	"github.com/docker/docker/volume"
21
+	volumemounts "github.com/docker/docker/volume/mounts"
22 22
 	"github.com/opencontainers/runc/libcontainer/apparmor"
23 23
 	"github.com/opencontainers/runc/libcontainer/cgroups"
24 24
 	"github.com/opencontainers/runc/libcontainer/devices"
... ...
@@ -580,7 +580,7 @@ func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []c
580 580
 
581 581
 		if m.Source == "tmpfs" {
582 582
 			data := m.Data
583
-			parser := volume.NewParser("linux")
583
+			parser := volumemounts.NewParser("linux")
584 584
 			options := []string{"noexec", "nosuid", "nodev", string(parser.DefaultPropagationMode())}
585 585
 			if data != "" {
586 586
 				options = append(options, strings.Split(data, ",")...)
... ...
@@ -14,6 +14,7 @@ import (
14 14
 	"github.com/docker/docker/container"
15 15
 	"github.com/docker/docker/errdefs"
16 16
 	"github.com/docker/docker/volume"
17
+	volumemounts "github.com/docker/docker/volume/mounts"
17 18
 	"github.com/pkg/errors"
18 19
 	"github.com/sirupsen/logrus"
19 20
 )
... ...
@@ -74,8 +75,9 @@ func (m mounts) parts(i int) int {
74 74
 // 4. Cleanup old volumes that are about to be reassigned.
75 75
 func (daemon *Daemon) registerMountPoints(container *container.Container, hostConfig *containertypes.HostConfig) (retErr error) {
76 76
 	binds := map[string]bool{}
77
-	mountPoints := map[string]*volume.MountPoint{}
78
-	parser := volume.NewParser(container.OS)
77
+	mountPoints := map[string]*volumemounts.MountPoint{}
78
+	parser := volumemounts.NewParser(container.OS)
79
+
79 80
 	defer func() {
80 81
 		// clean up the container mountpoints once return with error
81 82
 		if retErr != nil {
... ...
@@ -115,7 +117,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
115 115
 		}
116 116
 
117 117
 		for _, m := range c.MountPoints {
118
-			cp := &volume.MountPoint{
118
+			cp := &volumemounts.MountPoint{
119 119
 				Type:        m.Type,
120 120
 				Name:        m.Name,
121 121
 				Source:      m.Source,
... ...
@@ -250,7 +252,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
250 250
 
251 251
 // lazyInitializeVolume initializes a mountpoint's volume if needed.
252 252
 // This happens after a daemon restart.
253
-func (daemon *Daemon) lazyInitializeVolume(containerID string, m *volume.MountPoint) error {
253
+func (daemon *Daemon) lazyInitializeVolume(containerID string, m *volumemounts.MountPoint) error {
254 254
 	if len(m.Driver) > 0 && m.Volume == nil {
255 255
 		v, err := daemon.volumes.GetWithRef(m.Name, m.Driver, containerID)
256 256
 		if err != nil {
... ...
@@ -270,7 +272,7 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) {
270 270
 	container.Lock()
271 271
 	defer container.Unlock()
272 272
 
273
-	parser := volume.NewParser(container.OS)
273
+	parser := volumemounts.NewParser(container.OS)
274 274
 
275 275
 	maybeUpdate := make(map[string]bool)
276 276
 	for _, mp := range container.MountPoints {
... ...
@@ -288,7 +290,7 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) {
288 288
 		mountSpecs[m.Target] = true
289 289
 	}
290 290
 
291
-	binds := make(map[string]*volume.MountPoint, len(container.HostConfig.Binds))
291
+	binds := make(map[string]*volumemounts.MountPoint, len(container.HostConfig.Binds))
292 292
 	for _, rawSpec := range container.HostConfig.Binds {
293 293
 		mp, err := parser.ParseMountRaw(rawSpec, container.HostConfig.VolumeDriver)
294 294
 		if err != nil {
... ...
@@ -298,7 +300,7 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) {
298 298
 		binds[mp.Destination] = mp
299 299
 	}
300 300
 
301
-	volumesFrom := make(map[string]volume.MountPoint)
301
+	volumesFrom := make(map[string]volumemounts.MountPoint)
302 302
 	for _, fromSpec := range container.HostConfig.VolumesFrom {
303 303
 		from, _, err := parser.ParseVolumesFrom(fromSpec)
304 304
 		if err != nil {
... ...
@@ -321,7 +323,7 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) {
321 321
 		fromC.Unlock()
322 322
 	}
323 323
 
324
-	needsUpdate := func(containerMount, other *volume.MountPoint) bool {
324
+	needsUpdate := func(containerMount, other *volumemounts.MountPoint) bool {
325 325
 		if containerMount.Type != other.Type || !reflect.DeepEqual(containerMount.Spec, other.Spec) {
326 326
 			return true
327 327
 		}
... ...
@@ -4,7 +4,7 @@ import (
4 4
 	"runtime"
5 5
 	"testing"
6 6
 
7
-	"github.com/docker/docker/volume"
7
+	volumemounts "github.com/docker/docker/volume/mounts"
8 8
 )
9 9
 
10 10
 func TestParseVolumesFrom(t *testing.T) {
... ...
@@ -21,7 +21,7 @@ func TestParseVolumesFrom(t *testing.T) {
21 21
 		{"foobar:baz", "", "", true},
22 22
 	}
23 23
 
24
-	parser := volume.NewParser(runtime.GOOS)
24
+	parser := volumemounts.NewParser(runtime.GOOS)
25 25
 
26 26
 	for _, c := range cases {
27 27
 		id, mode, err := parser.ParseVolumesFrom(c.spec)
... ...
@@ -12,7 +12,7 @@ import (
12 12
 	"github.com/docker/docker/container"
13 13
 	"github.com/docker/docker/pkg/fileutils"
14 14
 	"github.com/docker/docker/pkg/mount"
15
-	"github.com/docker/docker/volume"
15
+	volumemounts "github.com/docker/docker/volume/mounts"
16 16
 )
17 17
 
18 18
 // setupMounts iterates through each of the mount points for a container and
... ...
@@ -40,7 +40,7 @@ func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, er
40 40
 		// mount the socket the daemon is listening on. During daemon shutdown, the socket
41 41
 		// (/var/run/docker.sock by default) doesn't exist anymore causing the call to m.Setup to
42 42
 		// create at directory instead. This in turn will prevent the daemon to restart.
43
-		checkfunc := func(m *volume.MountPoint) error {
43
+		checkfunc := func(m *volumemounts.MountPoint) error {
44 44
 			if _, exist := daemon.hosts[m.Source]; exist && daemon.IsShuttingDown() {
45 45
 				return fmt.Errorf("Could not mount %q to container while the daemon is shutting down", m.Source)
46 46
 			}
... ...
@@ -102,7 +102,7 @@ func sortMounts(m []container.Mount) []container.Mount {
102 102
 // setBindModeIfNull is platform specific processing to ensure the
103 103
 // shared mode is set to 'z' if it is null. This is called in the case
104 104
 // of processing a named volume and not a typical bind.
105
-func setBindModeIfNull(bind *volume.MountPoint) {
105
+func setBindModeIfNull(bind *volumemounts.MountPoint) {
106 106
 	if bind.Mode == "" {
107 107
 		bind.Mode = "z"
108 108
 	}
... ...
@@ -11,7 +11,7 @@ import (
11 11
 	containertypes "github.com/docker/docker/api/types/container"
12 12
 	mounttypes "github.com/docker/docker/api/types/mount"
13 13
 	"github.com/docker/docker/container"
14
-	"github.com/docker/docker/volume"
14
+	volumemounts "github.com/docker/docker/volume/mounts"
15 15
 )
16 16
 
17 17
 func TestBackportMountSpec(t *testing.T) {
... ...
@@ -19,7 +19,7 @@ func TestBackportMountSpec(t *testing.T) {
19 19
 
20 20
 	c := &container.Container{
21 21
 		State: &container.State{},
22
-		MountPoints: map[string]*volume.MountPoint{
22
+		MountPoints: map[string]*volumemounts.MountPoint{
23 23
 			"/apple":      {Destination: "/apple", Source: "/var/lib/docker/volumes/12345678", Name: "12345678", RW: true, CopyData: true}, // anonymous volume
24 24
 			"/banana":     {Destination: "/banana", Source: "/var/lib/docker/volumes/data", Name: "data", RW: true, CopyData: true},        // named volume
25 25
 			"/cherry":     {Destination: "/cherry", Source: "/var/lib/docker/volumes/data", Name: "data", CopyData: true},                  // RO named volume
... ...
@@ -73,7 +73,7 @@ func TestBackportMountSpec(t *testing.T) {
73 73
 	d.containers.Add("1", &container.Container{
74 74
 		State: &container.State{},
75 75
 		ID:    "1",
76
-		MountPoints: map[string]*volume.MountPoint{
76
+		MountPoints: map[string]*volumemounts.MountPoint{
77 77
 			"/kumquat": {Destination: "/kumquat", Name: "data", RW: false, CopyData: true},
78 78
 		},
79 79
 		HostConfig: &containertypes.HostConfig{
... ...
@@ -84,11 +84,11 @@ func TestBackportMountSpec(t *testing.T) {
84 84
 	})
85 85
 
86 86
 	type expected struct {
87
-		mp      *volume.MountPoint
87
+		mp      *volumemounts.MountPoint
88 88
 		comment string
89 89
 	}
90 90
 
91
-	pretty := func(mp *volume.MountPoint) string {
91
+	pretty := func(mp *volumemounts.MountPoint) string {
92 92
 		b, err := json.MarshalIndent(mp, "\t", "    ")
93 93
 		if err != nil {
94 94
 			return fmt.Sprintf("%#v", mp)
... ...
@@ -98,7 +98,7 @@ func TestBackportMountSpec(t *testing.T) {
98 98
 
99 99
 	for _, x := range []expected{
100 100
 		{
101
-			mp: &volume.MountPoint{
101
+			mp: &volumemounts.MountPoint{
102 102
 				Type:        mounttypes.TypeVolume,
103 103
 				Destination: "/apple",
104 104
 				RW:          true,
... ...
@@ -114,7 +114,7 @@ func TestBackportMountSpec(t *testing.T) {
114 114
 			comment: "anonymous volume",
115 115
 		},
116 116
 		{
117
-			mp: &volume.MountPoint{
117
+			mp: &volumemounts.MountPoint{
118 118
 				Type:        mounttypes.TypeVolume,
119 119
 				Destination: "/banana",
120 120
 				RW:          true,
... ...
@@ -130,7 +130,7 @@ func TestBackportMountSpec(t *testing.T) {
130 130
 			comment: "named volume",
131 131
 		},
132 132
 		{
133
-			mp: &volume.MountPoint{
133
+			mp: &volumemounts.MountPoint{
134 134
 				Type:        mounttypes.TypeVolume,
135 135
 				Destination: "/cherry",
136 136
 				Name:        "data",
... ...
@@ -146,7 +146,7 @@ func TestBackportMountSpec(t *testing.T) {
146 146
 			comment: "read-only named volume",
147 147
 		},
148 148
 		{
149
-			mp: &volume.MountPoint{
149
+			mp: &volumemounts.MountPoint{
150 150
 				Type:        mounttypes.TypeVolume,
151 151
 				Destination: "/dates",
152 152
 				Name:        "data",
... ...
@@ -162,7 +162,7 @@ func TestBackportMountSpec(t *testing.T) {
162 162
 			comment: "named volume with nocopy",
163 163
 		},
164 164
 		{
165
-			mp: &volume.MountPoint{
165
+			mp: &volumemounts.MountPoint{
166 166
 				Type:        mounttypes.TypeVolume,
167 167
 				Destination: "/elderberry",
168 168
 				Name:        "data",
... ...
@@ -178,7 +178,7 @@ func TestBackportMountSpec(t *testing.T) {
178 178
 			comment: "masks an anonymous volume",
179 179
 		},
180 180
 		{
181
-			mp: &volume.MountPoint{
181
+			mp: &volumemounts.MountPoint{
182 182
 				Type:        mounttypes.TypeBind,
183 183
 				Destination: "/fig",
184 184
 				Source:      "/data",
... ...
@@ -192,7 +192,7 @@ func TestBackportMountSpec(t *testing.T) {
192 192
 			comment: "bind mount with read/write",
193 193
 		},
194 194
 		{
195
-			mp: &volume.MountPoint{
195
+			mp: &volumemounts.MountPoint{
196 196
 				Type:        mounttypes.TypeBind,
197 197
 				Destination: "/guava",
198 198
 				Source:      "/data",
... ...
@@ -209,7 +209,7 @@ func TestBackportMountSpec(t *testing.T) {
209 209
 			comment: "bind mount with read/write + shared propagation",
210 210
 		},
211 211
 		{
212
-			mp: &volume.MountPoint{
212
+			mp: &volumemounts.MountPoint{
213 213
 				Type:        mounttypes.TypeVolume,
214 214
 				Destination: "/honeydew",
215 215
 				Source:      "/var/lib/docker/volumes/data",
... ...
@@ -229,7 +229,7 @@ func TestBackportMountSpec(t *testing.T) {
229 229
 			comment: "volume defined in mounts API",
230 230
 		},
231 231
 		{
232
-			mp: &volume.MountPoint{
232
+			mp: &volumemounts.MountPoint{
233 233
 				Type:        mounttypes.TypeVolume,
234 234
 				Destination: "/kumquat",
235 235
 				Source:      "/var/lib/docker/volumes/data",
... ...
@@ -6,7 +6,7 @@ import (
6 6
 	"github.com/docker/docker/api/types/mount"
7 7
 	"github.com/docker/docker/container"
8 8
 	"github.com/docker/docker/pkg/idtools"
9
-	"github.com/docker/docker/volume"
9
+	volumemounts "github.com/docker/docker/volume/mounts"
10 10
 )
11 11
 
12 12
 // setupMounts configures the mount points for a container by appending each
... ...
@@ -20,7 +20,7 @@ import (
20 20
 
21 21
 func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, error) {
22 22
 	var mnts []container.Mount
23
-	for _, mount := range c.MountPoints { // type is volume.MountPoint
23
+	for _, mount := range c.MountPoints { // type is volumemounts.MountPoint
24 24
 		if err := daemon.lazyInitializeVolume(c.ID, mount); err != nil {
25 25
 			return nil, err
26 26
 		}
... ...
@@ -42,7 +42,7 @@ func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, er
42 42
 
43 43
 // setBindModeIfNull is platform specific processing which is a no-op on
44 44
 // Windows.
45
-func setBindModeIfNull(bind *volume.MountPoint) {
45
+func setBindModeIfNull(bind *volumemounts.MountPoint) {
46 46
 	return
47 47
 }
48 48
 
49 49
deleted file mode 100644
... ...
@@ -1,34 +0,0 @@
1
-package volume // import "github.com/docker/docker/volume"
2
-
3
-import (
4
-	"errors"
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 ErrVolumeTargetIsRoot
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
-}
35 1
deleted file mode 100644
... ...
@@ -1,416 +0,0 @@
1
-package volume // import "github.com/docker/docker/volume"
2
-
3
-import (
4
-	"errors"
5
-	"fmt"
6
-	"path"
7
-	"path/filepath"
8
-	"strings"
9
-
10
-	"github.com/docker/docker/api/types/mount"
11
-	"github.com/docker/docker/pkg/stringid"
12
-)
13
-
14
-type linuxParser struct {
15
-}
16
-
17
-func linuxSplitRawSpec(raw string) ([]string, error) {
18
-	if strings.Count(raw, ":") > 2 {
19
-		return nil, errInvalidSpec(raw)
20
-	}
21
-
22
-	arr := strings.SplitN(raw, ":", 3)
23
-	if arr[0] == "" {
24
-		return nil, errInvalidSpec(raw)
25
-	}
26
-	return arr, nil
27
-}
28
-
29
-func linuxValidateNotRoot(p string) error {
30
-	p = path.Clean(strings.Replace(p, `\`, `/`, -1))
31
-	if p == "/" {
32
-		return ErrVolumeTargetIsRoot
33
-	}
34
-	return nil
35
-}
36
-func linuxValidateAbsolute(p string) error {
37
-	p = strings.Replace(p, `\`, `/`, -1)
38
-	if path.IsAbs(p) {
39
-		return nil
40
-	}
41
-	return fmt.Errorf("invalid mount path: '%s' mount path must be absolute", p)
42
-}
43
-func (p *linuxParser) ValidateMountConfig(mnt *mount.Mount) error {
44
-	// there was something looking like a bug in existing codebase:
45
-	// - validateMountConfig on linux was called with options skipping bind source existence when calling ParseMountRaw
46
-	// - but not when calling ParseMountSpec directly... nor when the unit test called it directly
47
-	return p.validateMountConfigImpl(mnt, true)
48
-}
49
-func (p *linuxParser) validateMountConfigImpl(mnt *mount.Mount, validateBindSourceExists bool) error {
50
-	if len(mnt.Target) == 0 {
51
-		return &errMountConfig{mnt, errMissingField("Target")}
52
-	}
53
-
54
-	if err := linuxValidateNotRoot(mnt.Target); err != nil {
55
-		return &errMountConfig{mnt, err}
56
-	}
57
-
58
-	if err := linuxValidateAbsolute(mnt.Target); err != nil {
59
-		return &errMountConfig{mnt, err}
60
-	}
61
-
62
-	switch mnt.Type {
63
-	case mount.TypeBind:
64
-		if len(mnt.Source) == 0 {
65
-			return &errMountConfig{mnt, errMissingField("Source")}
66
-		}
67
-		// Don't error out just because the propagation mode is not supported on the platform
68
-		if opts := mnt.BindOptions; opts != nil {
69
-			if len(opts.Propagation) > 0 && len(linuxPropagationModes) > 0 {
70
-				if _, ok := linuxPropagationModes[opts.Propagation]; !ok {
71
-					return &errMountConfig{mnt, fmt.Errorf("invalid propagation mode: %s", opts.Propagation)}
72
-				}
73
-			}
74
-		}
75
-		if mnt.VolumeOptions != nil {
76
-			return &errMountConfig{mnt, errExtraField("VolumeOptions")}
77
-		}
78
-
79
-		if err := linuxValidateAbsolute(mnt.Source); err != nil {
80
-			return &errMountConfig{mnt, err}
81
-		}
82
-
83
-		if validateBindSourceExists {
84
-			exists, _, _ := currentFileInfoProvider.fileInfo(mnt.Source)
85
-			if !exists {
86
-				return &errMountConfig{mnt, errBindSourceDoesNotExist(mnt.Source)}
87
-			}
88
-		}
89
-
90
-	case mount.TypeVolume:
91
-		if mnt.BindOptions != nil {
92
-			return &errMountConfig{mnt, errExtraField("BindOptions")}
93
-		}
94
-
95
-		if len(mnt.Source) == 0 && mnt.ReadOnly {
96
-			return &errMountConfig{mnt, fmt.Errorf("must not set ReadOnly mode when using anonymous volumes")}
97
-		}
98
-	case mount.TypeTmpfs:
99
-		if len(mnt.Source) != 0 {
100
-			return &errMountConfig{mnt, errExtraField("Source")}
101
-		}
102
-		if _, err := p.ConvertTmpfsOptions(mnt.TmpfsOptions, mnt.ReadOnly); err != nil {
103
-			return &errMountConfig{mnt, err}
104
-		}
105
-	default:
106
-		return &errMountConfig{mnt, errors.New("mount type unknown")}
107
-	}
108
-	return nil
109
-}
110
-
111
-// read-write modes
112
-var rwModes = map[string]bool{
113
-	"rw": true,
114
-	"ro": true,
115
-}
116
-
117
-// label modes
118
-var linuxLabelModes = map[string]bool{
119
-	"Z": true,
120
-	"z": true,
121
-}
122
-
123
-// consistency modes
124
-var linuxConsistencyModes = map[mount.Consistency]bool{
125
-	mount.ConsistencyFull:      true,
126
-	mount.ConsistencyCached:    true,
127
-	mount.ConsistencyDelegated: true,
128
-}
129
-var linuxPropagationModes = map[mount.Propagation]bool{
130
-	mount.PropagationPrivate:  true,
131
-	mount.PropagationRPrivate: true,
132
-	mount.PropagationSlave:    true,
133
-	mount.PropagationRSlave:   true,
134
-	mount.PropagationShared:   true,
135
-	mount.PropagationRShared:  true,
136
-}
137
-
138
-const linuxDefaultPropagationMode = mount.PropagationRPrivate
139
-
140
-func linuxGetPropagation(mode string) mount.Propagation {
141
-	for _, o := range strings.Split(mode, ",") {
142
-		prop := mount.Propagation(o)
143
-		if linuxPropagationModes[prop] {
144
-			return prop
145
-		}
146
-	}
147
-	return linuxDefaultPropagationMode
148
-}
149
-
150
-func linuxHasPropagation(mode string) bool {
151
-	for _, o := range strings.Split(mode, ",") {
152
-		if linuxPropagationModes[mount.Propagation(o)] {
153
-			return true
154
-		}
155
-	}
156
-	return false
157
-}
158
-
159
-func linuxValidMountMode(mode string) bool {
160
-	if mode == "" {
161
-		return true
162
-	}
163
-
164
-	rwModeCount := 0
165
-	labelModeCount := 0
166
-	propagationModeCount := 0
167
-	copyModeCount := 0
168
-	consistencyModeCount := 0
169
-
170
-	for _, o := range strings.Split(mode, ",") {
171
-		switch {
172
-		case rwModes[o]:
173
-			rwModeCount++
174
-		case linuxLabelModes[o]:
175
-			labelModeCount++
176
-		case linuxPropagationModes[mount.Propagation(o)]:
177
-			propagationModeCount++
178
-		case copyModeExists(o):
179
-			copyModeCount++
180
-		case linuxConsistencyModes[mount.Consistency(o)]:
181
-			consistencyModeCount++
182
-		default:
183
-			return false
184
-		}
185
-	}
186
-
187
-	// Only one string for each mode is allowed.
188
-	if rwModeCount > 1 || labelModeCount > 1 || propagationModeCount > 1 || copyModeCount > 1 || consistencyModeCount > 1 {
189
-		return false
190
-	}
191
-	return true
192
-}
193
-
194
-func (p *linuxParser) ReadWrite(mode string) bool {
195
-	if !linuxValidMountMode(mode) {
196
-		return false
197
-	}
198
-
199
-	for _, o := range strings.Split(mode, ",") {
200
-		if o == "ro" {
201
-			return false
202
-		}
203
-	}
204
-	return true
205
-}
206
-
207
-func (p *linuxParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
208
-	arr, err := linuxSplitRawSpec(raw)
209
-	if err != nil {
210
-		return nil, err
211
-	}
212
-
213
-	var spec mount.Mount
214
-	var mode string
215
-	switch len(arr) {
216
-	case 1:
217
-		// Just a destination path in the container
218
-		spec.Target = arr[0]
219
-	case 2:
220
-		if linuxValidMountMode(arr[1]) {
221
-			// Destination + Mode is not a valid volume - volumes
222
-			// cannot include a mode. e.g. /foo:rw
223
-			return nil, errInvalidSpec(raw)
224
-		}
225
-		// Host Source Path or Name + Destination
226
-		spec.Source = arr[0]
227
-		spec.Target = arr[1]
228
-	case 3:
229
-		// HostSourcePath+DestinationPath+Mode
230
-		spec.Source = arr[0]
231
-		spec.Target = arr[1]
232
-		mode = arr[2]
233
-	default:
234
-		return nil, errInvalidSpec(raw)
235
-	}
236
-
237
-	if !linuxValidMountMode(mode) {
238
-		return nil, errInvalidMode(mode)
239
-	}
240
-
241
-	if path.IsAbs(spec.Source) {
242
-		spec.Type = mount.TypeBind
243
-	} else {
244
-		spec.Type = mount.TypeVolume
245
-	}
246
-
247
-	spec.ReadOnly = !p.ReadWrite(mode)
248
-
249
-	// cannot assume that if a volume driver is passed in that we should set it
250
-	if volumeDriver != "" && spec.Type == mount.TypeVolume {
251
-		spec.VolumeOptions = &mount.VolumeOptions{
252
-			DriverConfig: &mount.Driver{Name: volumeDriver},
253
-		}
254
-	}
255
-
256
-	if copyData, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
257
-		if spec.VolumeOptions == nil {
258
-			spec.VolumeOptions = &mount.VolumeOptions{}
259
-		}
260
-		spec.VolumeOptions.NoCopy = !copyData
261
-	}
262
-	if linuxHasPropagation(mode) {
263
-		spec.BindOptions = &mount.BindOptions{
264
-			Propagation: linuxGetPropagation(mode),
265
-		}
266
-	}
267
-
268
-	mp, err := p.parseMountSpec(spec, false)
269
-	if mp != nil {
270
-		mp.Mode = mode
271
-	}
272
-	if err != nil {
273
-		err = fmt.Errorf("%v: %v", errInvalidSpec(raw), err)
274
-	}
275
-	return mp, err
276
-}
277
-func (p *linuxParser) ParseMountSpec(cfg mount.Mount) (*MountPoint, error) {
278
-	return p.parseMountSpec(cfg, true)
279
-}
280
-func (p *linuxParser) parseMountSpec(cfg mount.Mount, validateBindSourceExists bool) (*MountPoint, error) {
281
-	if err := p.validateMountConfigImpl(&cfg, validateBindSourceExists); err != nil {
282
-		return nil, err
283
-	}
284
-	mp := &MountPoint{
285
-		RW:          !cfg.ReadOnly,
286
-		Destination: path.Clean(filepath.ToSlash(cfg.Target)),
287
-		Type:        cfg.Type,
288
-		Spec:        cfg,
289
-	}
290
-
291
-	switch cfg.Type {
292
-	case mount.TypeVolume:
293
-		if cfg.Source == "" {
294
-			mp.Name = stringid.GenerateNonCryptoID()
295
-		} else {
296
-			mp.Name = cfg.Source
297
-		}
298
-		mp.CopyData = p.DefaultCopyMode()
299
-
300
-		if cfg.VolumeOptions != nil {
301
-			if cfg.VolumeOptions.DriverConfig != nil {
302
-				mp.Driver = cfg.VolumeOptions.DriverConfig.Name
303
-			}
304
-			if cfg.VolumeOptions.NoCopy {
305
-				mp.CopyData = false
306
-			}
307
-		}
308
-	case mount.TypeBind:
309
-		mp.Source = path.Clean(filepath.ToSlash(cfg.Source))
310
-		if cfg.BindOptions != nil && len(cfg.BindOptions.Propagation) > 0 {
311
-			mp.Propagation = cfg.BindOptions.Propagation
312
-		} else {
313
-			// If user did not specify a propagation mode, get
314
-			// default propagation mode.
315
-			mp.Propagation = linuxDefaultPropagationMode
316
-		}
317
-	case mount.TypeTmpfs:
318
-		// NOP
319
-	}
320
-	return mp, nil
321
-}
322
-
323
-func (p *linuxParser) ParseVolumesFrom(spec string) (string, string, error) {
324
-	if len(spec) == 0 {
325
-		return "", "", fmt.Errorf("volumes-from specification cannot be an empty string")
326
-	}
327
-
328
-	specParts := strings.SplitN(spec, ":", 2)
329
-	id := specParts[0]
330
-	mode := "rw"
331
-
332
-	if len(specParts) == 2 {
333
-		mode = specParts[1]
334
-		if !linuxValidMountMode(mode) {
335
-			return "", "", errInvalidMode(mode)
336
-		}
337
-		// For now don't allow propagation properties while importing
338
-		// volumes from data container. These volumes will inherit
339
-		// the same propagation property as of the original volume
340
-		// in data container. This probably can be relaxed in future.
341
-		if linuxHasPropagation(mode) {
342
-			return "", "", errInvalidMode(mode)
343
-		}
344
-		// Do not allow copy modes on volumes-from
345
-		if _, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
346
-			return "", "", errInvalidMode(mode)
347
-		}
348
-	}
349
-	return id, mode, nil
350
-}
351
-
352
-func (p *linuxParser) DefaultPropagationMode() mount.Propagation {
353
-	return linuxDefaultPropagationMode
354
-}
355
-
356
-func (p *linuxParser) ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error) {
357
-	var rawOpts []string
358
-	if readOnly {
359
-		rawOpts = append(rawOpts, "ro")
360
-	}
361
-
362
-	if opt != nil && opt.Mode != 0 {
363
-		rawOpts = append(rawOpts, fmt.Sprintf("mode=%o", opt.Mode))
364
-	}
365
-
366
-	if opt != nil && opt.SizeBytes != 0 {
367
-		// calculate suffix here, making this linux specific, but that is
368
-		// okay, since API is that way anyways.
369
-
370
-		// we do this by finding the suffix that divides evenly into the
371
-		// value, returning the value itself, with no suffix, if it fails.
372
-		//
373
-		// For the most part, we don't enforce any semantic to this values.
374
-		// The operating system will usually align this and enforce minimum
375
-		// and maximums.
376
-		var (
377
-			size   = opt.SizeBytes
378
-			suffix string
379
-		)
380
-		for _, r := range []struct {
381
-			suffix  string
382
-			divisor int64
383
-		}{
384
-			{"g", 1 << 30},
385
-			{"m", 1 << 20},
386
-			{"k", 1 << 10},
387
-		} {
388
-			if size%r.divisor == 0 {
389
-				size = size / r.divisor
390
-				suffix = r.suffix
391
-				break
392
-			}
393
-		}
394
-
395
-		rawOpts = append(rawOpts, fmt.Sprintf("size=%d%s", size, suffix))
396
-	}
397
-	return strings.Join(rawOpts, ","), nil
398
-}
399
-
400
-func (p *linuxParser) DefaultCopyMode() bool {
401
-	return true
402
-}
403
-func (p *linuxParser) ValidateVolumeName(name string) error {
404
-	return nil
405
-}
406
-
407
-func (p *linuxParser) IsBackwardCompatible(m *MountPoint) bool {
408
-	return len(m.Source) > 0 || m.Driver == DefaultDriverName
409
-}
410
-
411
-func (p *linuxParser) ValidateTmpfsMountDestination(dest string) error {
412
-	if err := linuxValidateNotRoot(dest); err != nil {
413
-		return err
414
-	}
415
-	return linuxValidateAbsolute(dest)
416
-}
417 1
new file mode 100644
... ...
@@ -0,0 +1,34 @@
0
+package mounts // import "github.com/docker/docker/volume/mounts"
1
+
2
+import (
3
+	"errors"
4
+	"path"
5
+
6
+	"github.com/docker/docker/api/types/mount"
7
+)
8
+
9
+var lcowSpecificValidators mountValidator = func(m *mount.Mount) error {
10
+	if path.Clean(m.Target) == "/" {
11
+		return ErrVolumeTargetIsRoot
12
+	}
13
+	if m.Type == mount.TypeNamedPipe {
14
+		return errors.New("Linux containers on Windows do not support named pipe mounts")
15
+	}
16
+	return nil
17
+}
18
+
19
+type lcowParser struct {
20
+	windowsParser
21
+}
22
+
23
+func (p *lcowParser) ValidateMountConfig(mnt *mount.Mount) error {
24
+	return p.validateMountConfigReg(mnt, rxLCOWDestination, lcowSpecificValidators)
25
+}
26
+
27
+func (p *lcowParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
28
+	return p.parseMountRaw(raw, volumeDriver, rxLCOWDestination, false, lcowSpecificValidators)
29
+}
30
+
31
+func (p *lcowParser) ParseMountSpec(cfg mount.Mount) (*MountPoint, error) {
32
+	return p.parseMountSpec(cfg, rxLCOWDestination, false, lcowSpecificValidators)
33
+}
0 34
new file mode 100644
... ...
@@ -0,0 +1,417 @@
0
+package mounts // import "github.com/docker/docker/volume/mounts"
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
+	"github.com/docker/docker/volume"
12
+)
13
+
14
+type linuxParser struct {
15
+}
16
+
17
+func linuxSplitRawSpec(raw string) ([]string, error) {
18
+	if strings.Count(raw, ":") > 2 {
19
+		return nil, errInvalidSpec(raw)
20
+	}
21
+
22
+	arr := strings.SplitN(raw, ":", 3)
23
+	if arr[0] == "" {
24
+		return nil, errInvalidSpec(raw)
25
+	}
26
+	return arr, nil
27
+}
28
+
29
+func linuxValidateNotRoot(p string) error {
30
+	p = path.Clean(strings.Replace(p, `\`, `/`, -1))
31
+	if p == "/" {
32
+		return ErrVolumeTargetIsRoot
33
+	}
34
+	return nil
35
+}
36
+func linuxValidateAbsolute(p string) error {
37
+	p = strings.Replace(p, `\`, `/`, -1)
38
+	if path.IsAbs(p) {
39
+		return nil
40
+	}
41
+	return fmt.Errorf("invalid mount path: '%s' mount path must be absolute", p)
42
+}
43
+func (p *linuxParser) ValidateMountConfig(mnt *mount.Mount) error {
44
+	// there was something looking like a bug in existing codebase:
45
+	// - validateMountConfig on linux was called with options skipping bind source existence when calling ParseMountRaw
46
+	// - but not when calling ParseMountSpec directly... nor when the unit test called it directly
47
+	return p.validateMountConfigImpl(mnt, true)
48
+}
49
+func (p *linuxParser) validateMountConfigImpl(mnt *mount.Mount, validateBindSourceExists bool) error {
50
+	if len(mnt.Target) == 0 {
51
+		return &errMountConfig{mnt, errMissingField("Target")}
52
+	}
53
+
54
+	if err := linuxValidateNotRoot(mnt.Target); err != nil {
55
+		return &errMountConfig{mnt, err}
56
+	}
57
+
58
+	if err := linuxValidateAbsolute(mnt.Target); err != nil {
59
+		return &errMountConfig{mnt, err}
60
+	}
61
+
62
+	switch mnt.Type {
63
+	case mount.TypeBind:
64
+		if len(mnt.Source) == 0 {
65
+			return &errMountConfig{mnt, errMissingField("Source")}
66
+		}
67
+		// Don't error out just because the propagation mode is not supported on the platform
68
+		if opts := mnt.BindOptions; opts != nil {
69
+			if len(opts.Propagation) > 0 && len(linuxPropagationModes) > 0 {
70
+				if _, ok := linuxPropagationModes[opts.Propagation]; !ok {
71
+					return &errMountConfig{mnt, fmt.Errorf("invalid propagation mode: %s", opts.Propagation)}
72
+				}
73
+			}
74
+		}
75
+		if mnt.VolumeOptions != nil {
76
+			return &errMountConfig{mnt, errExtraField("VolumeOptions")}
77
+		}
78
+
79
+		if err := linuxValidateAbsolute(mnt.Source); err != nil {
80
+			return &errMountConfig{mnt, err}
81
+		}
82
+
83
+		if validateBindSourceExists {
84
+			exists, _, _ := currentFileInfoProvider.fileInfo(mnt.Source)
85
+			if !exists {
86
+				return &errMountConfig{mnt, errBindSourceDoesNotExist(mnt.Source)}
87
+			}
88
+		}
89
+
90
+	case mount.TypeVolume:
91
+		if mnt.BindOptions != nil {
92
+			return &errMountConfig{mnt, errExtraField("BindOptions")}
93
+		}
94
+
95
+		if len(mnt.Source) == 0 && mnt.ReadOnly {
96
+			return &errMountConfig{mnt, fmt.Errorf("must not set ReadOnly mode when using anonymous volumes")}
97
+		}
98
+	case mount.TypeTmpfs:
99
+		if len(mnt.Source) != 0 {
100
+			return &errMountConfig{mnt, errExtraField("Source")}
101
+		}
102
+		if _, err := p.ConvertTmpfsOptions(mnt.TmpfsOptions, mnt.ReadOnly); err != nil {
103
+			return &errMountConfig{mnt, err}
104
+		}
105
+	default:
106
+		return &errMountConfig{mnt, errors.New("mount type unknown")}
107
+	}
108
+	return nil
109
+}
110
+
111
+// read-write modes
112
+var rwModes = map[string]bool{
113
+	"rw": true,
114
+	"ro": true,
115
+}
116
+
117
+// label modes
118
+var linuxLabelModes = map[string]bool{
119
+	"Z": true,
120
+	"z": true,
121
+}
122
+
123
+// consistency modes
124
+var linuxConsistencyModes = map[mount.Consistency]bool{
125
+	mount.ConsistencyFull:      true,
126
+	mount.ConsistencyCached:    true,
127
+	mount.ConsistencyDelegated: true,
128
+}
129
+var linuxPropagationModes = map[mount.Propagation]bool{
130
+	mount.PropagationPrivate:  true,
131
+	mount.PropagationRPrivate: true,
132
+	mount.PropagationSlave:    true,
133
+	mount.PropagationRSlave:   true,
134
+	mount.PropagationShared:   true,
135
+	mount.PropagationRShared:  true,
136
+}
137
+
138
+const linuxDefaultPropagationMode = mount.PropagationRPrivate
139
+
140
+func linuxGetPropagation(mode string) mount.Propagation {
141
+	for _, o := range strings.Split(mode, ",") {
142
+		prop := mount.Propagation(o)
143
+		if linuxPropagationModes[prop] {
144
+			return prop
145
+		}
146
+	}
147
+	return linuxDefaultPropagationMode
148
+}
149
+
150
+func linuxHasPropagation(mode string) bool {
151
+	for _, o := range strings.Split(mode, ",") {
152
+		if linuxPropagationModes[mount.Propagation(o)] {
153
+			return true
154
+		}
155
+	}
156
+	return false
157
+}
158
+
159
+func linuxValidMountMode(mode string) bool {
160
+	if mode == "" {
161
+		return true
162
+	}
163
+
164
+	rwModeCount := 0
165
+	labelModeCount := 0
166
+	propagationModeCount := 0
167
+	copyModeCount := 0
168
+	consistencyModeCount := 0
169
+
170
+	for _, o := range strings.Split(mode, ",") {
171
+		switch {
172
+		case rwModes[o]:
173
+			rwModeCount++
174
+		case linuxLabelModes[o]:
175
+			labelModeCount++
176
+		case linuxPropagationModes[mount.Propagation(o)]:
177
+			propagationModeCount++
178
+		case copyModeExists(o):
179
+			copyModeCount++
180
+		case linuxConsistencyModes[mount.Consistency(o)]:
181
+			consistencyModeCount++
182
+		default:
183
+			return false
184
+		}
185
+	}
186
+
187
+	// Only one string for each mode is allowed.
188
+	if rwModeCount > 1 || labelModeCount > 1 || propagationModeCount > 1 || copyModeCount > 1 || consistencyModeCount > 1 {
189
+		return false
190
+	}
191
+	return true
192
+}
193
+
194
+func (p *linuxParser) ReadWrite(mode string) bool {
195
+	if !linuxValidMountMode(mode) {
196
+		return false
197
+	}
198
+
199
+	for _, o := range strings.Split(mode, ",") {
200
+		if o == "ro" {
201
+			return false
202
+		}
203
+	}
204
+	return true
205
+}
206
+
207
+func (p *linuxParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
208
+	arr, err := linuxSplitRawSpec(raw)
209
+	if err != nil {
210
+		return nil, err
211
+	}
212
+
213
+	var spec mount.Mount
214
+	var mode string
215
+	switch len(arr) {
216
+	case 1:
217
+		// Just a destination path in the container
218
+		spec.Target = arr[0]
219
+	case 2:
220
+		if linuxValidMountMode(arr[1]) {
221
+			// Destination + Mode is not a valid volume - volumes
222
+			// cannot include a mode. e.g. /foo:rw
223
+			return nil, errInvalidSpec(raw)
224
+		}
225
+		// Host Source Path or Name + Destination
226
+		spec.Source = arr[0]
227
+		spec.Target = arr[1]
228
+	case 3:
229
+		// HostSourcePath+DestinationPath+Mode
230
+		spec.Source = arr[0]
231
+		spec.Target = arr[1]
232
+		mode = arr[2]
233
+	default:
234
+		return nil, errInvalidSpec(raw)
235
+	}
236
+
237
+	if !linuxValidMountMode(mode) {
238
+		return nil, errInvalidMode(mode)
239
+	}
240
+
241
+	if path.IsAbs(spec.Source) {
242
+		spec.Type = mount.TypeBind
243
+	} else {
244
+		spec.Type = mount.TypeVolume
245
+	}
246
+
247
+	spec.ReadOnly = !p.ReadWrite(mode)
248
+
249
+	// cannot assume that if a volume driver is passed in that we should set it
250
+	if volumeDriver != "" && spec.Type == mount.TypeVolume {
251
+		spec.VolumeOptions = &mount.VolumeOptions{
252
+			DriverConfig: &mount.Driver{Name: volumeDriver},
253
+		}
254
+	}
255
+
256
+	if copyData, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
257
+		if spec.VolumeOptions == nil {
258
+			spec.VolumeOptions = &mount.VolumeOptions{}
259
+		}
260
+		spec.VolumeOptions.NoCopy = !copyData
261
+	}
262
+	if linuxHasPropagation(mode) {
263
+		spec.BindOptions = &mount.BindOptions{
264
+			Propagation: linuxGetPropagation(mode),
265
+		}
266
+	}
267
+
268
+	mp, err := p.parseMountSpec(spec, false)
269
+	if mp != nil {
270
+		mp.Mode = mode
271
+	}
272
+	if err != nil {
273
+		err = fmt.Errorf("%v: %v", errInvalidSpec(raw), err)
274
+	}
275
+	return mp, err
276
+}
277
+func (p *linuxParser) ParseMountSpec(cfg mount.Mount) (*MountPoint, error) {
278
+	return p.parseMountSpec(cfg, true)
279
+}
280
+func (p *linuxParser) parseMountSpec(cfg mount.Mount, validateBindSourceExists bool) (*MountPoint, error) {
281
+	if err := p.validateMountConfigImpl(&cfg, validateBindSourceExists); err != nil {
282
+		return nil, err
283
+	}
284
+	mp := &MountPoint{
285
+		RW:          !cfg.ReadOnly,
286
+		Destination: path.Clean(filepath.ToSlash(cfg.Target)),
287
+		Type:        cfg.Type,
288
+		Spec:        cfg,
289
+	}
290
+
291
+	switch cfg.Type {
292
+	case mount.TypeVolume:
293
+		if cfg.Source == "" {
294
+			mp.Name = stringid.GenerateNonCryptoID()
295
+		} else {
296
+			mp.Name = cfg.Source
297
+		}
298
+		mp.CopyData = p.DefaultCopyMode()
299
+
300
+		if cfg.VolumeOptions != nil {
301
+			if cfg.VolumeOptions.DriverConfig != nil {
302
+				mp.Driver = cfg.VolumeOptions.DriverConfig.Name
303
+			}
304
+			if cfg.VolumeOptions.NoCopy {
305
+				mp.CopyData = false
306
+			}
307
+		}
308
+	case mount.TypeBind:
309
+		mp.Source = path.Clean(filepath.ToSlash(cfg.Source))
310
+		if cfg.BindOptions != nil && len(cfg.BindOptions.Propagation) > 0 {
311
+			mp.Propagation = cfg.BindOptions.Propagation
312
+		} else {
313
+			// If user did not specify a propagation mode, get
314
+			// default propagation mode.
315
+			mp.Propagation = linuxDefaultPropagationMode
316
+		}
317
+	case mount.TypeTmpfs:
318
+		// NOP
319
+	}
320
+	return mp, nil
321
+}
322
+
323
+func (p *linuxParser) ParseVolumesFrom(spec string) (string, string, error) {
324
+	if len(spec) == 0 {
325
+		return "", "", fmt.Errorf("volumes-from specification cannot be an empty string")
326
+	}
327
+
328
+	specParts := strings.SplitN(spec, ":", 2)
329
+	id := specParts[0]
330
+	mode := "rw"
331
+
332
+	if len(specParts) == 2 {
333
+		mode = specParts[1]
334
+		if !linuxValidMountMode(mode) {
335
+			return "", "", errInvalidMode(mode)
336
+		}
337
+		// For now don't allow propagation properties while importing
338
+		// volumes from data container. These volumes will inherit
339
+		// the same propagation property as of the original volume
340
+		// in data container. This probably can be relaxed in future.
341
+		if linuxHasPropagation(mode) {
342
+			return "", "", errInvalidMode(mode)
343
+		}
344
+		// Do not allow copy modes on volumes-from
345
+		if _, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
346
+			return "", "", errInvalidMode(mode)
347
+		}
348
+	}
349
+	return id, mode, nil
350
+}
351
+
352
+func (p *linuxParser) DefaultPropagationMode() mount.Propagation {
353
+	return linuxDefaultPropagationMode
354
+}
355
+
356
+func (p *linuxParser) ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error) {
357
+	var rawOpts []string
358
+	if readOnly {
359
+		rawOpts = append(rawOpts, "ro")
360
+	}
361
+
362
+	if opt != nil && opt.Mode != 0 {
363
+		rawOpts = append(rawOpts, fmt.Sprintf("mode=%o", opt.Mode))
364
+	}
365
+
366
+	if opt != nil && opt.SizeBytes != 0 {
367
+		// calculate suffix here, making this linux specific, but that is
368
+		// okay, since API is that way anyways.
369
+
370
+		// we do this by finding the suffix that divides evenly into the
371
+		// value, returning the value itself, with no suffix, if it fails.
372
+		//
373
+		// For the most part, we don't enforce any semantic to this values.
374
+		// The operating system will usually align this and enforce minimum
375
+		// and maximums.
376
+		var (
377
+			size   = opt.SizeBytes
378
+			suffix string
379
+		)
380
+		for _, r := range []struct {
381
+			suffix  string
382
+			divisor int64
383
+		}{
384
+			{"g", 1 << 30},
385
+			{"m", 1 << 20},
386
+			{"k", 1 << 10},
387
+		} {
388
+			if size%r.divisor == 0 {
389
+				size = size / r.divisor
390
+				suffix = r.suffix
391
+				break
392
+			}
393
+		}
394
+
395
+		rawOpts = append(rawOpts, fmt.Sprintf("size=%d%s", size, suffix))
396
+	}
397
+	return strings.Join(rawOpts, ","), nil
398
+}
399
+
400
+func (p *linuxParser) DefaultCopyMode() bool {
401
+	return true
402
+}
403
+func (p *linuxParser) ValidateVolumeName(name string) error {
404
+	return nil
405
+}
406
+
407
+func (p *linuxParser) IsBackwardCompatible(m *MountPoint) bool {
408
+	return len(m.Source) > 0 || m.Driver == volume.DefaultDriverName
409
+}
410
+
411
+func (p *linuxParser) ValidateTmpfsMountDestination(dest string) error {
412
+	if err := linuxValidateNotRoot(dest); err != nil {
413
+		return err
414
+	}
415
+	return linuxValidateAbsolute(dest)
416
+}
0 417
new file mode 100644
... ...
@@ -0,0 +1,170 @@
0
+package mounts // import "github.com/docker/docker/volume/mounts"
1
+
2
+import (
3
+	"fmt"
4
+	"os"
5
+	"path/filepath"
6
+	"syscall"
7
+
8
+	mounttypes "github.com/docker/docker/api/types/mount"
9
+	"github.com/docker/docker/pkg/idtools"
10
+	"github.com/docker/docker/pkg/stringid"
11
+	"github.com/docker/docker/volume"
12
+	"github.com/opencontainers/selinux/go-selinux/label"
13
+	"github.com/pkg/errors"
14
+)
15
+
16
+// MountPoint is the intersection point between a volume and a container. It
17
+// specifies which volume is to be used and where inside a container it should
18
+// be mounted.
19
+//
20
+// Note that this type is embedded in `container.Container` object and persisted to disk.
21
+// Changes to this struct need to by synced with on disk state.
22
+type MountPoint struct {
23
+	// Source is the source path of the mount.
24
+	// E.g. `mount --bind /foo /bar`, `/foo` is the `Source`.
25
+	Source string
26
+	// Destination is the path relative to the container root (`/`) to the mount point
27
+	// It is where the `Source` is mounted to
28
+	Destination string
29
+	// RW is set to true when the mountpoint should be mounted as read-write
30
+	RW bool
31
+	// Name is the name reference to the underlying data defined by `Source`
32
+	// e.g., the volume name
33
+	Name string
34
+	// Driver is the volume driver used to create the volume (if it is a volume)
35
+	Driver string
36
+	// Type of mount to use, see `Type<foo>` definitions in github.com/docker/docker/api/types/mount
37
+	Type mounttypes.Type `json:",omitempty"`
38
+	// Volume is the volume providing data to this mountpoint.
39
+	// This is nil unless `Type` is set to `TypeVolume`
40
+	Volume volume.Volume `json:"-"`
41
+
42
+	// Mode is the comma separated list of options supplied by the user when creating
43
+	// the bind/volume mount.
44
+	// Note Mode is not used on Windows
45
+	Mode string `json:"Relabel,omitempty"` // Originally field was `Relabel`"
46
+
47
+	// Propagation describes how the mounts are propagated from the host into the
48
+	// mount point, and vice-versa.
49
+	// See https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt
50
+	// Note Propagation is not used on Windows
51
+	Propagation mounttypes.Propagation `json:",omitempty"` // Mount propagation string
52
+
53
+	// Specifies if data should be copied from the container before the first mount
54
+	// Use a pointer here so we can tell if the user set this value explicitly
55
+	// This allows us to error out when the user explicitly enabled copy but we can't copy due to the volume being populated
56
+	CopyData bool `json:"-"`
57
+	// ID is the opaque ID used to pass to the volume driver.
58
+	// This should be set by calls to `Mount` and unset by calls to `Unmount`
59
+	ID string `json:",omitempty"`
60
+
61
+	// Sepc is a copy of the API request that created this mount.
62
+	Spec mounttypes.Mount
63
+
64
+	// Track usage of this mountpoint
65
+	// Specifically needed for containers which are running and calls to `docker cp`
66
+	// because both these actions require mounting the volumes.
67
+	active int
68
+}
69
+
70
+// Cleanup frees resources used by the mountpoint
71
+func (m *MountPoint) Cleanup() error {
72
+	if m.Volume == nil || m.ID == "" {
73
+		return nil
74
+	}
75
+
76
+	if err := m.Volume.Unmount(m.ID); err != nil {
77
+		return errors.Wrapf(err, "error unmounting volume %s", m.Volume.Name())
78
+	}
79
+
80
+	m.active--
81
+	if m.active == 0 {
82
+		m.ID = ""
83
+	}
84
+	return nil
85
+}
86
+
87
+// Setup sets up a mount point by either mounting the volume if it is
88
+// configured, or creating the source directory if supplied.
89
+// The, optional, checkFun parameter allows doing additional checking
90
+// before creating the source directory on the host.
91
+func (m *MountPoint) Setup(mountLabel string, rootIDs idtools.IDPair, checkFun func(m *MountPoint) error) (path string, err error) {
92
+	defer func() {
93
+		if err != nil || !label.RelabelNeeded(m.Mode) {
94
+			return
95
+		}
96
+
97
+		var sourcePath string
98
+		sourcePath, err = filepath.EvalSymlinks(m.Source)
99
+		if err != nil {
100
+			path = ""
101
+			err = errors.Wrapf(err, "error evaluating symlinks from mount source %q", m.Source)
102
+			return
103
+		}
104
+		err = label.Relabel(sourcePath, mountLabel, label.IsShared(m.Mode))
105
+		if err == syscall.ENOTSUP {
106
+			err = nil
107
+		}
108
+		if err != nil {
109
+			path = ""
110
+			err = errors.Wrapf(err, "error setting label on mount source '%s'", sourcePath)
111
+		}
112
+	}()
113
+
114
+	if m.Volume != nil {
115
+		id := m.ID
116
+		if id == "" {
117
+			id = stringid.GenerateNonCryptoID()
118
+		}
119
+		path, err := m.Volume.Mount(id)
120
+		if err != nil {
121
+			return "", errors.Wrapf(err, "error while mounting volume '%s'", m.Source)
122
+		}
123
+
124
+		m.ID = id
125
+		m.active++
126
+		return path, nil
127
+	}
128
+
129
+	if len(m.Source) == 0 {
130
+		return "", fmt.Errorf("Unable to setup mount point, neither source nor volume defined")
131
+	}
132
+
133
+	if m.Type == mounttypes.TypeBind {
134
+		// Before creating the source directory on the host, invoke checkFun if it's not nil. One of
135
+		// the use case is to forbid creating the daemon socket as a directory if the daemon is in
136
+		// the process of shutting down.
137
+		if checkFun != nil {
138
+			if err := checkFun(m); err != nil {
139
+				return "", err
140
+			}
141
+		}
142
+		// idtools.MkdirAllNewAs() produces an error if m.Source exists and is a file (not a directory)
143
+		// also, makes sure that if the directory is created, the correct remapped rootUID/rootGID will own it
144
+		if err := idtools.MkdirAllAndChownNew(m.Source, 0755, rootIDs); err != nil {
145
+			if perr, ok := err.(*os.PathError); ok {
146
+				if perr.Err != syscall.ENOTDIR {
147
+					return "", errors.Wrapf(err, "error while creating mount source path '%s'", m.Source)
148
+				}
149
+			}
150
+		}
151
+	}
152
+	return m.Source, nil
153
+}
154
+
155
+// Path returns the path of a volume in a mount point.
156
+func (m *MountPoint) Path() string {
157
+	if m.Volume != nil {
158
+		return m.Volume.Path()
159
+	}
160
+	return m.Source
161
+}
162
+
163
+func errInvalidMode(mode string) error {
164
+	return errors.Errorf("invalid mode: %v", mode)
165
+}
166
+
167
+func errInvalidSpec(spec string) error {
168
+	return errors.Errorf("invalid volume specification: '%s'", spec)
169
+}
0 170
new file mode 100644
... ...
@@ -0,0 +1,47 @@
0
+package mounts // import "github.com/docker/docker/volume/mounts"
1
+
2
+import (
3
+	"errors"
4
+	"runtime"
5
+
6
+	"github.com/docker/docker/api/types/mount"
7
+)
8
+
9
+const (
10
+	// OSLinux is the same as runtime.GOOS on linux
11
+	OSLinux = "linux"
12
+	// OSWindows is the same as runtime.GOOS on windows
13
+	OSWindows = "windows"
14
+)
15
+
16
+// ErrVolumeTargetIsRoot is returned when the target destination is root.
17
+// It's used by both LCOW and Linux parsers.
18
+var ErrVolumeTargetIsRoot = errors.New("invalid specification: destination can't be '/'")
19
+
20
+// Parser represents a platform specific parser for mount expressions
21
+type Parser interface {
22
+	ParseMountRaw(raw, volumeDriver string) (*MountPoint, error)
23
+	ParseMountSpec(cfg mount.Mount) (*MountPoint, error)
24
+	ParseVolumesFrom(spec string) (string, string, error)
25
+	DefaultPropagationMode() mount.Propagation
26
+	ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error)
27
+	DefaultCopyMode() bool
28
+	ValidateVolumeName(name string) error
29
+	ReadWrite(mode string) bool
30
+	IsBackwardCompatible(m *MountPoint) bool
31
+	HasResource(m *MountPoint, absPath string) bool
32
+	ValidateTmpfsMountDestination(dest string) error
33
+	ValidateMountConfig(mt *mount.Mount) error
34
+}
35
+
36
+// 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)
37
+func NewParser(containerOS string) Parser {
38
+	switch containerOS {
39
+	case OSWindows:
40
+		return &windowsParser{}
41
+	}
42
+	if runtime.GOOS == OSWindows {
43
+		return &lcowParser{}
44
+	}
45
+	return &linuxParser{}
46
+}
0 47
new file mode 100644
... ...
@@ -0,0 +1,480 @@
0
+package mounts // import "github.com/docker/docker/volume/mounts"
1
+
2
+import (
3
+	"io/ioutil"
4
+	"os"
5
+	"runtime"
6
+	"strings"
7
+	"testing"
8
+
9
+	"github.com/docker/docker/api/types/mount"
10
+)
11
+
12
+type parseMountRawTestSet struct {
13
+	valid   []string
14
+	invalid map[string]string
15
+}
16
+
17
+func TestConvertTmpfsOptions(t *testing.T) {
18
+	type testCase struct {
19
+		opt                  mount.TmpfsOptions
20
+		readOnly             bool
21
+		expectedSubstrings   []string
22
+		unexpectedSubstrings []string
23
+	}
24
+	cases := []testCase{
25
+		{
26
+			opt:                  mount.TmpfsOptions{SizeBytes: 1024 * 1024, Mode: 0700},
27
+			readOnly:             false,
28
+			expectedSubstrings:   []string{"size=1m", "mode=700"},
29
+			unexpectedSubstrings: []string{"ro"},
30
+		},
31
+		{
32
+			opt:                  mount.TmpfsOptions{},
33
+			readOnly:             true,
34
+			expectedSubstrings:   []string{"ro"},
35
+			unexpectedSubstrings: []string{},
36
+		},
37
+	}
38
+	p := &linuxParser{}
39
+	for _, c := range cases {
40
+		data, err := p.ConvertTmpfsOptions(&c.opt, c.readOnly)
41
+		if err != nil {
42
+			t.Fatalf("could not convert %+v (readOnly: %v) to string: %v",
43
+				c.opt, c.readOnly, err)
44
+		}
45
+		t.Logf("data=%q", data)
46
+		for _, s := range c.expectedSubstrings {
47
+			if !strings.Contains(data, s) {
48
+				t.Fatalf("expected substring: %s, got %v (case=%+v)", s, data, c)
49
+			}
50
+		}
51
+		for _, s := range c.unexpectedSubstrings {
52
+			if strings.Contains(data, s) {
53
+				t.Fatalf("unexpected substring: %s, got %v (case=%+v)", s, data, c)
54
+			}
55
+		}
56
+	}
57
+}
58
+
59
+type mockFiProvider struct{}
60
+
61
+func (mockFiProvider) fileInfo(path string) (exists, isDir bool, err error) {
62
+	dirs := map[string]struct{}{
63
+		`c:\`:                    {},
64
+		`c:\windows\`:            {},
65
+		`c:\windows`:             {},
66
+		`c:\program files`:       {},
67
+		`c:\Windows`:             {},
68
+		`c:\Program Files (x86)`: {},
69
+		`\\?\c:\windows\`:        {},
70
+	}
71
+	files := map[string]struct{}{
72
+		`c:\windows\system32\ntdll.dll`: {},
73
+	}
74
+	if _, ok := dirs[path]; ok {
75
+		return true, true, nil
76
+	}
77
+	if _, ok := files[path]; ok {
78
+		return true, false, nil
79
+	}
80
+	return false, false, nil
81
+}
82
+
83
+func TestParseMountRaw(t *testing.T) {
84
+
85
+	previousProvider := currentFileInfoProvider
86
+	defer func() { currentFileInfoProvider = previousProvider }()
87
+	currentFileInfoProvider = mockFiProvider{}
88
+	windowsSet := parseMountRawTestSet{
89
+		valid: []string{
90
+			`d:\`,
91
+			`d:`,
92
+			`d:\path`,
93
+			`d:\path with space`,
94
+			`c:\:d:\`,
95
+			`c:\windows\:d:`,
96
+			`c:\windows:d:\s p a c e`,
97
+			`c:\windows:d:\s p a c e:RW`,
98
+			`c:\program files:d:\s p a c e i n h o s t d i r`,
99
+			`0123456789name:d:`,
100
+			`MiXeDcAsEnAmE:d:`,
101
+			`name:D:`,
102
+			`name:D::rW`,
103
+			`name:D::RW`,
104
+			`name:D::RO`,
105
+			`c:/:d:/forward/slashes/are/good/too`,
106
+			`c:/:d:/including with/spaces:ro`,
107
+			`c:\Windows`,                // With capital
108
+			`c:\Program Files (x86)`,    // With capitals and brackets
109
+			`\\?\c:\windows\:d:`,        // Long path handling (source)
110
+			`c:\windows\:\\?\d:\`,       // Long path handling (target)
111
+			`\\.\pipe\foo:\\.\pipe\foo`, // named pipe
112
+			`//./pipe/foo://./pipe/foo`, // named pipe forward slashes
113
+		},
114
+		invalid: map[string]string{
115
+			``:                                 "invalid volume specification: ",
116
+			`.`:                                "invalid volume specification: ",
117
+			`..\`:                              "invalid volume specification: ",
118
+			`c:\:..\`:                          "invalid volume specification: ",
119
+			`c:\:d:\:xyzzy`:                    "invalid volume specification: ",
120
+			`c:`:                               "cannot be `c:`",
121
+			`c:\`:                              "cannot be `c:`",
122
+			`c:\notexist:d:`:                   `bind mount source path does not exist: c:\notexist`,
123
+			`c:\windows\system32\ntdll.dll:d:`: `source path must be a directory`,
124
+			`name<:d:`:                         `invalid volume specification`,
125
+			`name>:d:`:                         `invalid volume specification`,
126
+			`name::d:`:                         `invalid volume specification`,
127
+			`name":d:`:                         `invalid volume specification`,
128
+			`name\:d:`:                         `invalid volume specification`,
129
+			`name*:d:`:                         `invalid volume specification`,
130
+			`name|:d:`:                         `invalid volume specification`,
131
+			`name?:d:`:                         `invalid volume specification`,
132
+			`name/:d:`:                         `invalid volume specification`,
133
+			`d:\pathandmode:rw`:                `invalid volume specification`,
134
+			`d:\pathandmode:ro`:                `invalid volume specification`,
135
+			`con:d:`:                           `cannot be a reserved word for Windows filenames`,
136
+			`PRN:d:`:                           `cannot be a reserved word for Windows filenames`,
137
+			`aUx:d:`:                           `cannot be a reserved word for Windows filenames`,
138
+			`nul:d:`:                           `cannot be a reserved word for Windows filenames`,
139
+			`com1:d:`:                          `cannot be a reserved word for Windows filenames`,
140
+			`com2:d:`:                          `cannot be a reserved word for Windows filenames`,
141
+			`com3:d:`:                          `cannot be a reserved word for Windows filenames`,
142
+			`com4:d:`:                          `cannot be a reserved word for Windows filenames`,
143
+			`com5:d:`:                          `cannot be a reserved word for Windows filenames`,
144
+			`com6:d:`:                          `cannot be a reserved word for Windows filenames`,
145
+			`com7:d:`:                          `cannot be a reserved word for Windows filenames`,
146
+			`com8:d:`:                          `cannot be a reserved word for Windows filenames`,
147
+			`com9:d:`:                          `cannot be a reserved word for Windows filenames`,
148
+			`lpt1:d:`:                          `cannot be a reserved word for Windows filenames`,
149
+			`lpt2:d:`:                          `cannot be a reserved word for Windows filenames`,
150
+			`lpt3:d:`:                          `cannot be a reserved word for Windows filenames`,
151
+			`lpt4:d:`:                          `cannot be a reserved word for Windows filenames`,
152
+			`lpt5:d:`:                          `cannot be a reserved word for Windows filenames`,
153
+			`lpt6:d:`:                          `cannot be a reserved word for Windows filenames`,
154
+			`lpt7:d:`:                          `cannot be a reserved word for Windows filenames`,
155
+			`lpt8:d:`:                          `cannot be a reserved word for Windows filenames`,
156
+			`lpt9:d:`:                          `cannot be a reserved word for Windows filenames`,
157
+			`c:\windows\system32\ntdll.dll`:    `Only directories can be mapped on this platform`,
158
+			`\\.\pipe\foo:c:\pipe`:             `'c:\pipe' is not a valid pipe path`,
159
+		},
160
+	}
161
+	lcowSet := parseMountRawTestSet{
162
+		valid: []string{
163
+			`/foo`,
164
+			`/foo/`,
165
+			`/foo bar`,
166
+			`c:\:/foo`,
167
+			`c:\windows\:/foo`,
168
+			`c:\windows:/s p a c e`,
169
+			`c:\windows:/s p a c e:RW`,
170
+			`c:\program files:/s p a c e i n h o s t d i r`,
171
+			`0123456789name:/foo`,
172
+			`MiXeDcAsEnAmE:/foo`,
173
+			`name:/foo`,
174
+			`name:/foo:rW`,
175
+			`name:/foo:RW`,
176
+			`name:/foo:RO`,
177
+			`c:/:/forward/slashes/are/good/too`,
178
+			`c:/:/including with/spaces:ro`,
179
+			`/Program Files (x86)`, // With capitals and brackets
180
+		},
181
+		invalid: map[string]string{
182
+			``:                                   "invalid volume specification: ",
183
+			`.`:                                  "invalid volume specification: ",
184
+			`c:`:                                 "invalid volume specification: ",
185
+			`c:\`:                                "invalid volume specification: ",
186
+			`../`:                                "invalid volume specification: ",
187
+			`c:\:../`:                            "invalid volume specification: ",
188
+			`c:\:/foo:xyzzy`:                     "invalid volume specification: ",
189
+			`/`:                                  "destination can't be '/'",
190
+			`/..`:                                "destination can't be '/'",
191
+			`c:\notexist:/foo`:                   `bind mount source path does not exist: c:\notexist`,
192
+			`c:\windows\system32\ntdll.dll:/foo`: `source path must be a directory`,
193
+			`name<:/foo`:                         `invalid volume specification`,
194
+			`name>:/foo`:                         `invalid volume specification`,
195
+			`name::/foo`:                         `invalid volume specification`,
196
+			`name":/foo`:                         `invalid volume specification`,
197
+			`name\:/foo`:                         `invalid volume specification`,
198
+			`name*:/foo`:                         `invalid volume specification`,
199
+			`name|:/foo`:                         `invalid volume specification`,
200
+			`name?:/foo`:                         `invalid volume specification`,
201
+			`name/:/foo`:                         `invalid volume specification`,
202
+			`/foo:rw`:                            `invalid volume specification`,
203
+			`/foo:ro`:                            `invalid volume specification`,
204
+			`con:/foo`:                           `cannot be a reserved word for Windows filenames`,
205
+			`PRN:/foo`:                           `cannot be a reserved word for Windows filenames`,
206
+			`aUx:/foo`:                           `cannot be a reserved word for Windows filenames`,
207
+			`nul:/foo`:                           `cannot be a reserved word for Windows filenames`,
208
+			`com1:/foo`:                          `cannot be a reserved word for Windows filenames`,
209
+			`com2:/foo`:                          `cannot be a reserved word for Windows filenames`,
210
+			`com3:/foo`:                          `cannot be a reserved word for Windows filenames`,
211
+			`com4:/foo`:                          `cannot be a reserved word for Windows filenames`,
212
+			`com5:/foo`:                          `cannot be a reserved word for Windows filenames`,
213
+			`com6:/foo`:                          `cannot be a reserved word for Windows filenames`,
214
+			`com7:/foo`:                          `cannot be a reserved word for Windows filenames`,
215
+			`com8:/foo`:                          `cannot be a reserved word for Windows filenames`,
216
+			`com9:/foo`:                          `cannot be a reserved word for Windows filenames`,
217
+			`lpt1:/foo`:                          `cannot be a reserved word for Windows filenames`,
218
+			`lpt2:/foo`:                          `cannot be a reserved word for Windows filenames`,
219
+			`lpt3:/foo`:                          `cannot be a reserved word for Windows filenames`,
220
+			`lpt4:/foo`:                          `cannot be a reserved word for Windows filenames`,
221
+			`lpt5:/foo`:                          `cannot be a reserved word for Windows filenames`,
222
+			`lpt6:/foo`:                          `cannot be a reserved word for Windows filenames`,
223
+			`lpt7:/foo`:                          `cannot be a reserved word for Windows filenames`,
224
+			`lpt8:/foo`:                          `cannot be a reserved word for Windows filenames`,
225
+			`lpt9:/foo`:                          `cannot be a reserved word for Windows filenames`,
226
+			`\\.\pipe\foo:/foo`:                  `Linux containers on Windows do not support named pipe mounts`,
227
+		},
228
+	}
229
+	linuxSet := parseMountRawTestSet{
230
+		valid: []string{
231
+			"/home",
232
+			"/home:/home",
233
+			"/home:/something/else",
234
+			"/with space",
235
+			"/home:/with space",
236
+			"relative:/absolute-path",
237
+			"hostPath:/containerPath:ro",
238
+			"/hostPath:/containerPath:rw",
239
+			"/rw:/ro",
240
+			"/hostPath:/containerPath:shared",
241
+			"/hostPath:/containerPath:rshared",
242
+			"/hostPath:/containerPath:slave",
243
+			"/hostPath:/containerPath:rslave",
244
+			"/hostPath:/containerPath:private",
245
+			"/hostPath:/containerPath:rprivate",
246
+			"/hostPath:/containerPath:ro,shared",
247
+			"/hostPath:/containerPath:ro,slave",
248
+			"/hostPath:/containerPath:ro,private",
249
+			"/hostPath:/containerPath:ro,z,shared",
250
+			"/hostPath:/containerPath:ro,Z,slave",
251
+			"/hostPath:/containerPath:Z,ro,slave",
252
+			"/hostPath:/containerPath:slave,Z,ro",
253
+			"/hostPath:/containerPath:Z,slave,ro",
254
+			"/hostPath:/containerPath:slave,ro,Z",
255
+			"/hostPath:/containerPath:rslave,ro,Z",
256
+			"/hostPath:/containerPath:ro,rshared,Z",
257
+			"/hostPath:/containerPath:ro,Z,rprivate",
258
+		},
259
+		invalid: map[string]string{
260
+			"":                                "invalid volume specification",
261
+			"./":                              "mount path must be absolute",
262
+			"../":                             "mount path must be absolute",
263
+			"/:../":                           "mount path must be absolute",
264
+			"/:path":                          "mount path must be absolute",
265
+			":":                               "invalid volume specification",
266
+			"/tmp:":                           "invalid volume specification",
267
+			":test":                           "invalid volume specification",
268
+			":/test":                          "invalid volume specification",
269
+			"tmp:":                            "invalid volume specification",
270
+			":test:":                          "invalid volume specification",
271
+			"::":                              "invalid volume specification",
272
+			":::":                             "invalid volume specification",
273
+			"/tmp:::":                         "invalid volume specification",
274
+			":/tmp::":                         "invalid volume specification",
275
+			"/path:rw":                        "invalid volume specification",
276
+			"/path:ro":                        "invalid volume specification",
277
+			"/rw:rw":                          "invalid volume specification",
278
+			"path:ro":                         "invalid volume specification",
279
+			"/path:/path:sw":                  `invalid mode`,
280
+			"/path:/path:rwz":                 `invalid mode`,
281
+			"/path:/path:ro,rshared,rslave":   `invalid mode`,
282
+			"/path:/path:ro,z,rshared,rslave": `invalid mode`,
283
+			"/path:shared":                    "invalid volume specification",
284
+			"/path:slave":                     "invalid volume specification",
285
+			"/path:private":                   "invalid volume specification",
286
+			"name:/absolute-path:shared":      "invalid volume specification",
287
+			"name:/absolute-path:rshared":     "invalid volume specification",
288
+			"name:/absolute-path:slave":       "invalid volume specification",
289
+			"name:/absolute-path:rslave":      "invalid volume specification",
290
+			"name:/absolute-path:private":     "invalid volume specification",
291
+			"name:/absolute-path:rprivate":    "invalid volume specification",
292
+		},
293
+	}
294
+
295
+	linParser := &linuxParser{}
296
+	winParser := &windowsParser{}
297
+	lcowParser := &lcowParser{}
298
+	tester := func(parser Parser, set parseMountRawTestSet) {
299
+
300
+		for _, path := range set.valid {
301
+
302
+			if _, err := parser.ParseMountRaw(path, "local"); err != nil {
303
+				t.Errorf("ParseMountRaw(`%q`) should succeed: error %q", path, err)
304
+			}
305
+		}
306
+
307
+		for path, expectedError := range set.invalid {
308
+			if mp, err := parser.ParseMountRaw(path, "local"); err == nil {
309
+				t.Errorf("ParseMountRaw(`%q`) should have failed validation. Err '%v' - MP: %v", path, err, mp)
310
+			} else {
311
+				if !strings.Contains(err.Error(), expectedError) {
312
+					t.Errorf("ParseMountRaw(`%q`) error should contain %q, got %v", path, expectedError, err.Error())
313
+				}
314
+			}
315
+		}
316
+	}
317
+	tester(linParser, linuxSet)
318
+	tester(winParser, windowsSet)
319
+	tester(lcowParser, lcowSet)
320
+
321
+}
322
+
323
+// testParseMountRaw is a structure used by TestParseMountRawSplit for
324
+// specifying test cases for the ParseMountRaw() function.
325
+type testParseMountRaw struct {
326
+	bind      string
327
+	driver    string
328
+	expType   mount.Type
329
+	expDest   string
330
+	expSource string
331
+	expName   string
332
+	expDriver string
333
+	expRW     bool
334
+	fail      bool
335
+}
336
+
337
+func TestParseMountRawSplit(t *testing.T) {
338
+	previousProvider := currentFileInfoProvider
339
+	defer func() { currentFileInfoProvider = previousProvider }()
340
+	currentFileInfoProvider = mockFiProvider{}
341
+	windowsCases := []testParseMountRaw{
342
+		{`c:\:d:`, "local", mount.TypeBind, `d:`, `c:\`, ``, "", true, false},
343
+		{`c:\:d:\`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false},
344
+		{`c:\:d:\:ro`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, false},
345
+		{`c:\:d:\:rw`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false},
346
+		{`c:\:d:\:foo`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, true},
347
+		{`name:d::rw`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false},
348
+		{`name:d:`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false},
349
+		{`name:d::ro`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", false, false},
350
+		{`name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
351
+		{`driver/name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
352
+		{`\\.\pipe\foo:\\.\pipe\bar`, "local", mount.TypeNamedPipe, `\\.\pipe\bar`, `\\.\pipe\foo`, "", "", true, false},
353
+		{`\\.\pipe\foo:c:\foo\bar`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
354
+		{`c:\foo\bar:\\.\pipe\foo`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
355
+	}
356
+	lcowCases := []testParseMountRaw{
357
+		{`c:\:/foo`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", true, false},
358
+		{`c:\:/foo:ro`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", false, false},
359
+		{`c:\:/foo:rw`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", true, false},
360
+		{`c:\:/foo:foo`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", false, true},
361
+		{`name:/foo:rw`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", true, false},
362
+		{`name:/foo`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", true, false},
363
+		{`name:/foo:ro`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", false, false},
364
+		{`name:/`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
365
+		{`driver/name:/`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
366
+		{`\\.\pipe\foo:\\.\pipe\bar`, "local", mount.TypeNamedPipe, `\\.\pipe\bar`, `\\.\pipe\foo`, "", "", true, true},
367
+		{`\\.\pipe\foo:/data`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
368
+		{`c:\foo\bar:\\.\pipe\foo`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
369
+	}
370
+	linuxCases := []testParseMountRaw{
371
+		{"/tmp:/tmp1", "", mount.TypeBind, "/tmp1", "/tmp", "", "", true, false},
372
+		{"/tmp:/tmp2:ro", "", mount.TypeBind, "/tmp2", "/tmp", "", "", false, false},
373
+		{"/tmp:/tmp3:rw", "", mount.TypeBind, "/tmp3", "/tmp", "", "", true, false},
374
+		{"/tmp:/tmp4:foo", "", mount.TypeBind, "", "", "", "", false, true},
375
+		{"name:/named1", "", mount.TypeVolume, "/named1", "", "name", "", true, false},
376
+		{"name:/named2", "external", mount.TypeVolume, "/named2", "", "name", "external", true, false},
377
+		{"name:/named3:ro", "local", mount.TypeVolume, "/named3", "", "name", "local", false, false},
378
+		{"local/name:/tmp:rw", "", mount.TypeVolume, "/tmp", "", "local/name", "", true, false},
379
+		{"/tmp:tmp", "", mount.TypeBind, "", "", "", "", true, true},
380
+	}
381
+	linParser := &linuxParser{}
382
+	winParser := &windowsParser{}
383
+	lcowParser := &lcowParser{}
384
+	tester := func(parser Parser, cases []testParseMountRaw) {
385
+		for i, c := range cases {
386
+			t.Logf("case %d", i)
387
+			m, err := parser.ParseMountRaw(c.bind, c.driver)
388
+			if c.fail {
389
+				if err == nil {
390
+					t.Errorf("Expected error, was nil, for spec %s\n", c.bind)
391
+				}
392
+				continue
393
+			}
394
+
395
+			if m == nil || err != nil {
396
+				t.Errorf("ParseMountRaw failed for spec '%s', driver '%s', error '%v'", c.bind, c.driver, err.Error())
397
+				continue
398
+			}
399
+
400
+			if m.Destination != c.expDest {
401
+				t.Errorf("Expected destination '%s, was %s', for spec '%s'", c.expDest, m.Destination, c.bind)
402
+			}
403
+
404
+			if m.Source != c.expSource {
405
+				t.Errorf("Expected source '%s', was '%s', for spec '%s'", c.expSource, m.Source, c.bind)
406
+			}
407
+
408
+			if m.Name != c.expName {
409
+				t.Errorf("Expected name '%s', was '%s' for spec '%s'", c.expName, m.Name, c.bind)
410
+			}
411
+
412
+			if m.Driver != c.expDriver {
413
+				t.Errorf("Expected driver '%s', was '%s', for spec '%s'", c.expDriver, m.Driver, c.bind)
414
+			}
415
+
416
+			if m.RW != c.expRW {
417
+				t.Errorf("Expected RW '%v', was '%v' for spec '%s'", c.expRW, m.RW, c.bind)
418
+			}
419
+			if m.Type != c.expType {
420
+				t.Fatalf("Expected type '%s', was '%s', for spec '%s'", c.expType, m.Type, c.bind)
421
+			}
422
+		}
423
+	}
424
+
425
+	tester(linParser, linuxCases)
426
+	tester(winParser, windowsCases)
427
+	tester(lcowParser, lcowCases)
428
+}
429
+
430
+func TestParseMountSpec(t *testing.T) {
431
+	type c struct {
432
+		input    mount.Mount
433
+		expected MountPoint
434
+	}
435
+	testDir, err := ioutil.TempDir("", "test-mount-config")
436
+	if err != nil {
437
+		t.Fatal(err)
438
+	}
439
+	defer os.RemoveAll(testDir)
440
+	parser := NewParser(runtime.GOOS)
441
+	cases := []c{
442
+		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: parser.DefaultPropagationMode()}},
443
+		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, RW: true, Propagation: parser.DefaultPropagationMode()}},
444
+		{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()}},
445
+		{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()}},
446
+		{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: parser.DefaultCopyMode()}},
447
+		{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath + string(os.PathSeparator)}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: parser.DefaultCopyMode()}},
448
+	}
449
+
450
+	for i, c := range cases {
451
+		t.Logf("case %d", i)
452
+		mp, err := parser.ParseMountSpec(c.input)
453
+		if err != nil {
454
+			t.Error(err)
455
+		}
456
+
457
+		if c.expected.Type != mp.Type {
458
+			t.Errorf("Expected mount types to match. Expected: '%s', Actual: '%s'", c.expected.Type, mp.Type)
459
+		}
460
+		if c.expected.Destination != mp.Destination {
461
+			t.Errorf("Expected mount destination to match. Expected: '%s', Actual: '%s'", c.expected.Destination, mp.Destination)
462
+		}
463
+		if c.expected.Source != mp.Source {
464
+			t.Errorf("Expected mount source to match. Expected: '%s', Actual: '%s'", c.expected.Source, mp.Source)
465
+		}
466
+		if c.expected.RW != mp.RW {
467
+			t.Errorf("Expected mount writable to match. Expected: '%v', Actual: '%v'", c.expected.RW, mp.RW)
468
+		}
469
+		if c.expected.Propagation != mp.Propagation {
470
+			t.Errorf("Expected mount propagation to match. Expected: '%v', Actual: '%s'", c.expected.Propagation, mp.Propagation)
471
+		}
472
+		if c.expected.Driver != mp.Driver {
473
+			t.Errorf("Expected mount driver to match. Expected: '%v', Actual: '%s'", c.expected.Driver, mp.Driver)
474
+		}
475
+		if c.expected.CopyData != mp.CopyData {
476
+			t.Errorf("Expected mount copy data to match. Expected: '%v', Actual: '%v'", c.expected.CopyData, mp.CopyData)
477
+		}
478
+	}
479
+}
0 480
new file mode 100644
... ...
@@ -0,0 +1,28 @@
0
+package mounts // import "github.com/docker/docker/volume/mounts"
1
+
2
+import (
3
+	"fmt"
4
+
5
+	"github.com/docker/docker/api/types/mount"
6
+	"github.com/pkg/errors"
7
+)
8
+
9
+type errMountConfig struct {
10
+	mount *mount.Mount
11
+	err   error
12
+}
13
+
14
+func (e *errMountConfig) Error() string {
15
+	return fmt.Sprintf("invalid mount config for type %q: %v", e.mount.Type, e.err.Error())
16
+}
17
+
18
+func errBindSourceDoesNotExist(path string) error {
19
+	return errors.Errorf("bind mount source path does not exist: %s", path)
20
+}
21
+
22
+func errExtraField(name string) error {
23
+	return errors.Errorf("field %s must not be specified", name)
24
+}
25
+func errMissingField(name string) error {
26
+	return errors.Errorf("field %s must not be empty", name)
27
+}
0 28
new file mode 100644
... ...
@@ -0,0 +1,73 @@
0
+package mounts // import "github.com/docker/docker/volume/mounts"
1
+
2
+import (
3
+	"errors"
4
+	"io/ioutil"
5
+	"os"
6
+	"runtime"
7
+	"strings"
8
+	"testing"
9
+
10
+	"github.com/docker/docker/api/types/mount"
11
+)
12
+
13
+func TestValidateMount(t *testing.T) {
14
+	testDir, err := ioutil.TempDir("", "test-validate-mount")
15
+	if err != nil {
16
+		t.Fatal(err)
17
+	}
18
+	defer os.RemoveAll(testDir)
19
+
20
+	cases := []struct {
21
+		input    mount.Mount
22
+		expected error
23
+	}{
24
+		{mount.Mount{Type: mount.TypeVolume}, errMissingField("Target")},
25
+		{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath, Source: "hello"}, nil},
26
+		{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath}, nil},
27
+		{mount.Mount{Type: mount.TypeBind}, errMissingField("Target")},
28
+		{mount.Mount{Type: mount.TypeBind, Target: testDestinationPath}, errMissingField("Source")},
29
+		{mount.Mount{Type: mount.TypeBind, Target: testDestinationPath, Source: testSourcePath, VolumeOptions: &mount.VolumeOptions{}}, errExtraField("VolumeOptions")},
30
+
31
+		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath}, nil},
32
+		{mount.Mount{Type: "invalid", Target: testDestinationPath}, errors.New("mount type unknown")},
33
+		{mount.Mount{Type: mount.TypeBind, Source: testSourcePath, Target: testDestinationPath}, errBindSourceDoesNotExist(testSourcePath)},
34
+	}
35
+
36
+	lcowCases := []struct {
37
+		input    mount.Mount
38
+		expected error
39
+	}{
40
+		{mount.Mount{Type: mount.TypeVolume}, errMissingField("Target")},
41
+		{mount.Mount{Type: mount.TypeVolume, Target: "/foo", Source: "hello"}, nil},
42
+		{mount.Mount{Type: mount.TypeVolume, Target: "/foo"}, nil},
43
+		{mount.Mount{Type: mount.TypeBind}, errMissingField("Target")},
44
+		{mount.Mount{Type: mount.TypeBind, Target: "/foo"}, errMissingField("Source")},
45
+		{mount.Mount{Type: mount.TypeBind, Target: "/foo", Source: "c:\\foo", VolumeOptions: &mount.VolumeOptions{}}, errExtraField("VolumeOptions")},
46
+		{mount.Mount{Type: mount.TypeBind, Source: "c:\\foo", Target: "/foo"}, errBindSourceDoesNotExist("c:\\foo")},
47
+		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: "/foo"}, nil},
48
+		{mount.Mount{Type: "invalid", Target: "/foo"}, errors.New("mount type unknown")},
49
+	}
50
+	parser := NewParser(runtime.GOOS)
51
+	for i, x := range cases {
52
+		err := parser.ValidateMountConfig(&x.input)
53
+		if err == nil && x.expected == nil {
54
+			continue
55
+		}
56
+		if (err == nil && x.expected != nil) || (x.expected == nil && err != nil) || !strings.Contains(err.Error(), x.expected.Error()) {
57
+			t.Errorf("expected %q, got %q, case: %d", x.expected, err, i)
58
+		}
59
+	}
60
+	if runtime.GOOS == "windows" {
61
+		parser = &lcowParser{}
62
+		for i, x := range lcowCases {
63
+			err := parser.ValidateMountConfig(&x.input)
64
+			if err == nil && x.expected == nil {
65
+				continue
66
+			}
67
+			if (err == nil && x.expected != nil) || (x.expected == nil && err != nil) || !strings.Contains(err.Error(), x.expected.Error()) {
68
+				t.Errorf("expected %q, got %q, case: %d", x.expected, err, i)
69
+			}
70
+		}
71
+	}
72
+}
0 73
new file mode 100644
... ...
@@ -0,0 +1,8 @@
0
+// +build !windows
1
+
2
+package mounts // import "github.com/docker/docker/volume/mounts"
3
+
4
+var (
5
+	testDestinationPath = "/foo"
6
+	testSourcePath      = "/foo"
7
+)
0 8
new file mode 100644
... ...
@@ -0,0 +1,6 @@
0
+package mounts // import "github.com/docker/docker/volume/mounts"
1
+
2
+var (
3
+	testDestinationPath = `c:\foo`
4
+	testSourcePath      = `c:\foo`
5
+)
0 6
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+package mounts // import "github.com/docker/docker/volume/mounts"
1
+
2
+import "strings"
3
+
4
+// {<copy mode>=isEnabled}
5
+var copyModes = map[string]bool{
6
+	"nocopy": false,
7
+}
8
+
9
+func copyModeExists(mode string) bool {
10
+	_, exists := copyModes[mode]
11
+	return exists
12
+}
13
+
14
+// GetCopyMode gets the copy mode from the mode string for mounts
15
+func getCopyMode(mode string, def bool) (bool, bool) {
16
+	for _, o := range strings.Split(mode, ",") {
17
+		if isEnabled, exists := copyModes[o]; exists {
18
+			return isEnabled, true
19
+		}
20
+	}
21
+	return def, false
22
+}
0 23
new file mode 100644
... ...
@@ -0,0 +1,18 @@
0
+// +build linux freebsd darwin
1
+
2
+package mounts // import "github.com/docker/docker/volume/mounts"
3
+
4
+import (
5
+	"fmt"
6
+	"path/filepath"
7
+	"strings"
8
+)
9
+
10
+func (p *linuxParser) HasResource(m *MountPoint, absolutePath string) bool {
11
+	relPath, err := filepath.Rel(m.Destination, absolutePath)
12
+	return err == nil && relPath != ".." && !strings.HasPrefix(relPath, fmt.Sprintf("..%c", filepath.Separator))
13
+}
14
+
15
+func (p *windowsParser) HasResource(m *MountPoint, absolutePath string) bool {
16
+	return false
17
+}
0 18
new file mode 100644
... ...
@@ -0,0 +1,8 @@
0
+package mounts // import "github.com/docker/docker/volume/mounts"
1
+
2
+func (p *windowsParser) HasResource(m *MountPoint, absolutePath string) bool {
3
+	return false
4
+}
5
+func (p *linuxParser) HasResource(m *MountPoint, absolutePath string) bool {
6
+	return false
7
+}
0 8
new file mode 100644
... ...
@@ -0,0 +1,456 @@
0
+package mounts // import "github.com/docker/docker/volume/mounts"
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, errBindSourceDoesNotExist(mnt.Source)}
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
+}
0 456
deleted file mode 100644
... ...
@@ -1,47 +0,0 @@
1
-package volume // import "github.com/docker/docker/volume"
2
-
3
-import (
4
-	"errors"
5
-	"runtime"
6
-
7
-	"github.com/docker/docker/api/types/mount"
8
-)
9
-
10
-const (
11
-	// OSLinux is the same as runtime.GOOS on linux
12
-	OSLinux = "linux"
13
-	// OSWindows is the same as runtime.GOOS on windows
14
-	OSWindows = "windows"
15
-)
16
-
17
-// ErrVolumeTargetIsRoot is returned when the target destination is root.
18
-// It's used by both LCOW and Linux parsers.
19
-var ErrVolumeTargetIsRoot = errors.New("invalid specification: destination can't be '/'")
20
-
21
-// Parser represents a platform specific parser for mount expressions
22
-type Parser interface {
23
-	ParseMountRaw(raw, volumeDriver string) (*MountPoint, error)
24
-	ParseMountSpec(cfg mount.Mount) (*MountPoint, error)
25
-	ParseVolumesFrom(spec string) (string, string, error)
26
-	DefaultPropagationMode() mount.Propagation
27
-	ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error)
28
-	DefaultCopyMode() bool
29
-	ValidateVolumeName(name string) error
30
-	ReadWrite(mode string) bool
31
-	IsBackwardCompatible(m *MountPoint) bool
32
-	HasResource(m *MountPoint, absPath string) bool
33
-	ValidateTmpfsMountDestination(dest string) error
34
-	ValidateMountConfig(mt *mount.Mount) error
35
-}
36
-
37
-// 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)
38
-func NewParser(containerOS string) Parser {
39
-	switch containerOS {
40
-	case OSWindows:
41
-		return &windowsParser{}
42
-	}
43
-	if runtime.GOOS == OSWindows {
44
-		return &lcowParser{}
45
-	}
46
-	return &linuxParser{}
47
-}
... ...
@@ -14,6 +14,7 @@ import (
14 14
 	"github.com/docker/docker/pkg/locker"
15 15
 	"github.com/docker/docker/volume"
16 16
 	"github.com/docker/docker/volume/drivers"
17
+	volumemounts "github.com/docker/docker/volume/mounts"
17 18
 	"github.com/sirupsen/logrus"
18 19
 )
19 20
 
... ...
@@ -387,7 +388,7 @@ func (s *VolumeStore) create(name, driverName string, opts, labels map[string]st
387 387
 
388 388
 	// volume name validation is specific to the host os and not on container image
389 389
 	// windows/lcow should have an equivalent volumename validation logic so we create a parser for current host OS
390
-	parser := volume.NewParser(runtime.GOOS)
390
+	parser := volumemounts.NewParser(runtime.GOOS)
391 391
 	err := parser.ValidateVolumeName(name)
392 392
 	if err != nil {
393 393
 		return nil, err
394 394
deleted file mode 100644
... ...
@@ -1,28 +0,0 @@
1
-package volume // import "github.com/docker/docker/volume"
2
-
3
-import (
4
-	"fmt"
5
-
6
-	"github.com/docker/docker/api/types/mount"
7
-	"github.com/pkg/errors"
8
-)
9
-
10
-type errMountConfig struct {
11
-	mount *mount.Mount
12
-	err   error
13
-}
14
-
15
-func (e *errMountConfig) Error() string {
16
-	return fmt.Sprintf("invalid mount config for type %q: %v", e.mount.Type, e.err.Error())
17
-}
18
-
19
-func errBindSourceDoesNotExist(path string) error {
20
-	return errors.Errorf("bind mount source path does not exist: %s", path)
21
-}
22
-
23
-func errExtraField(name string) error {
24
-	return errors.Errorf("field %s must not be specified", name)
25
-}
26
-func errMissingField(name string) error {
27
-	return errors.Errorf("field %s must not be empty", name)
28
-}
29 1
deleted file mode 100644
... ...
@@ -1,73 +0,0 @@
1
-package volume // import "github.com/docker/docker/volume"
2
-
3
-import (
4
-	"errors"
5
-	"io/ioutil"
6
-	"os"
7
-	"runtime"
8
-	"strings"
9
-	"testing"
10
-
11
-	"github.com/docker/docker/api/types/mount"
12
-)
13
-
14
-func TestValidateMount(t *testing.T) {
15
-	testDir, err := ioutil.TempDir("", "test-validate-mount")
16
-	if err != nil {
17
-		t.Fatal(err)
18
-	}
19
-	defer os.RemoveAll(testDir)
20
-
21
-	cases := []struct {
22
-		input    mount.Mount
23
-		expected error
24
-	}{
25
-		{mount.Mount{Type: mount.TypeVolume}, errMissingField("Target")},
26
-		{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath, Source: "hello"}, nil},
27
-		{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath}, nil},
28
-		{mount.Mount{Type: mount.TypeBind}, errMissingField("Target")},
29
-		{mount.Mount{Type: mount.TypeBind, Target: testDestinationPath}, errMissingField("Source")},
30
-		{mount.Mount{Type: mount.TypeBind, Target: testDestinationPath, Source: testSourcePath, VolumeOptions: &mount.VolumeOptions{}}, errExtraField("VolumeOptions")},
31
-
32
-		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath}, nil},
33
-		{mount.Mount{Type: "invalid", Target: testDestinationPath}, errors.New("mount type unknown")},
34
-		{mount.Mount{Type: mount.TypeBind, Source: testSourcePath, Target: testDestinationPath}, errBindSourceDoesNotExist(testSourcePath)},
35
-	}
36
-
37
-	lcowCases := []struct {
38
-		input    mount.Mount
39
-		expected error
40
-	}{
41
-		{mount.Mount{Type: mount.TypeVolume}, errMissingField("Target")},
42
-		{mount.Mount{Type: mount.TypeVolume, Target: "/foo", Source: "hello"}, nil},
43
-		{mount.Mount{Type: mount.TypeVolume, Target: "/foo"}, nil},
44
-		{mount.Mount{Type: mount.TypeBind}, errMissingField("Target")},
45
-		{mount.Mount{Type: mount.TypeBind, Target: "/foo"}, errMissingField("Source")},
46
-		{mount.Mount{Type: mount.TypeBind, Target: "/foo", Source: "c:\\foo", VolumeOptions: &mount.VolumeOptions{}}, errExtraField("VolumeOptions")},
47
-		{mount.Mount{Type: mount.TypeBind, Source: "c:\\foo", Target: "/foo"}, errBindSourceDoesNotExist("c:\\foo")},
48
-		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: "/foo"}, nil},
49
-		{mount.Mount{Type: "invalid", Target: "/foo"}, errors.New("mount type unknown")},
50
-	}
51
-	parser := NewParser(runtime.GOOS)
52
-	for i, x := range cases {
53
-		err := parser.ValidateMountConfig(&x.input)
54
-		if err == nil && x.expected == nil {
55
-			continue
56
-		}
57
-		if (err == nil && x.expected != nil) || (x.expected == nil && err != nil) || !strings.Contains(err.Error(), x.expected.Error()) {
58
-			t.Errorf("expected %q, got %q, case: %d", x.expected, err, i)
59
-		}
60
-	}
61
-	if runtime.GOOS == "windows" {
62
-		parser = &lcowParser{}
63
-		for i, x := range lcowCases {
64
-			err := parser.ValidateMountConfig(&x.input)
65
-			if err == nil && x.expected == nil {
66
-				continue
67
-			}
68
-			if (err == nil && x.expected != nil) || (x.expected == nil && err != nil) || !strings.Contains(err.Error(), x.expected.Error()) {
69
-				t.Errorf("expected %q, got %q, case: %d", x.expected, err, i)
70
-			}
71
-		}
72
-	}
73
-}
74 1
deleted file mode 100644
... ...
@@ -1,8 +0,0 @@
1
-// +build !windows
2
-
3
-package volume // import "github.com/docker/docker/volume"
4
-
5
-var (
6
-	testDestinationPath = "/foo"
7
-	testSourcePath      = "/foo"
8
-)
9 1
deleted file mode 100644
... ...
@@ -1,6 +0,0 @@
1
-package volume // import "github.com/docker/docker/volume"
2
-
3
-var (
4
-	testDestinationPath = `c:\foo`
5
-	testSourcePath      = `c:\foo`
6
-)
... ...
@@ -1,17 +1,7 @@
1 1
 package volume // import "github.com/docker/docker/volume"
2 2
 
3 3
 import (
4
-	"fmt"
5
-	"os"
6
-	"path/filepath"
7
-	"syscall"
8 4
 	"time"
9
-
10
-	mounttypes "github.com/docker/docker/api/types/mount"
11
-	"github.com/docker/docker/pkg/idtools"
12
-	"github.com/docker/docker/pkg/stringid"
13
-	"github.com/opencontainers/selinux/go-selinux/label"
14
-	"github.com/pkg/errors"
15 5
 )
16 6
 
17 7
 // DefaultDriverName is the driver name used for the driver
... ...
@@ -77,155 +67,3 @@ type DetailedVolume interface {
77 77
 	Scope() string
78 78
 	Volume
79 79
 }
80
-
81
-// MountPoint is the intersection point between a volume and a container. It
82
-// specifies which volume is to be used and where inside a container it should
83
-// be mounted.
84
-type MountPoint struct {
85
-	// Source is the source path of the mount.
86
-	// E.g. `mount --bind /foo /bar`, `/foo` is the `Source`.
87
-	Source string
88
-	// Destination is the path relative to the container root (`/`) to the mount point
89
-	// It is where the `Source` is mounted to
90
-	Destination string
91
-	// RW is set to true when the mountpoint should be mounted as read-write
92
-	RW bool
93
-	// Name is the name reference to the underlying data defined by `Source`
94
-	// e.g., the volume name
95
-	Name string
96
-	// Driver is the volume driver used to create the volume (if it is a volume)
97
-	Driver string
98
-	// Type of mount to use, see `Type<foo>` definitions in github.com/docker/docker/api/types/mount
99
-	Type mounttypes.Type `json:",omitempty"`
100
-	// Volume is the volume providing data to this mountpoint.
101
-	// This is nil unless `Type` is set to `TypeVolume`
102
-	Volume Volume `json:"-"`
103
-
104
-	// Mode is the comma separated list of options supplied by the user when creating
105
-	// the bind/volume mount.
106
-	// Note Mode is not used on Windows
107
-	Mode string `json:"Relabel,omitempty"` // Originally field was `Relabel`"
108
-
109
-	// Propagation describes how the mounts are propagated from the host into the
110
-	// mount point, and vice-versa.
111
-	// See https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt
112
-	// Note Propagation is not used on Windows
113
-	Propagation mounttypes.Propagation `json:",omitempty"` // Mount propagation string
114
-
115
-	// Specifies if data should be copied from the container before the first mount
116
-	// Use a pointer here so we can tell if the user set this value explicitly
117
-	// This allows us to error out when the user explicitly enabled copy but we can't copy due to the volume being populated
118
-	CopyData bool `json:"-"`
119
-	// ID is the opaque ID used to pass to the volume driver.
120
-	// This should be set by calls to `Mount` and unset by calls to `Unmount`
121
-	ID string `json:",omitempty"`
122
-
123
-	// Sepc is a copy of the API request that created this mount.
124
-	Spec mounttypes.Mount
125
-
126
-	// Track usage of this mountpoint
127
-	// Specifically needed for containers which are running and calls to `docker cp`
128
-	// because both these actions require mounting the volumes.
129
-	active int
130
-}
131
-
132
-// Cleanup frees resources used by the mountpoint
133
-func (m *MountPoint) Cleanup() error {
134
-	if m.Volume == nil || m.ID == "" {
135
-		return nil
136
-	}
137
-
138
-	if err := m.Volume.Unmount(m.ID); err != nil {
139
-		return errors.Wrapf(err, "error unmounting volume %s", m.Volume.Name())
140
-	}
141
-
142
-	m.active--
143
-	if m.active == 0 {
144
-		m.ID = ""
145
-	}
146
-	return nil
147
-}
148
-
149
-// Setup sets up a mount point by either mounting the volume if it is
150
-// configured, or creating the source directory if supplied.
151
-// The, optional, checkFun parameter allows doing additional checking
152
-// before creating the source directory on the host.
153
-func (m *MountPoint) Setup(mountLabel string, rootIDs idtools.IDPair, checkFun func(m *MountPoint) error) (path string, err error) {
154
-	defer func() {
155
-		if err != nil || !label.RelabelNeeded(m.Mode) {
156
-			return
157
-		}
158
-
159
-		var sourcePath string
160
-		sourcePath, err = filepath.EvalSymlinks(m.Source)
161
-		if err != nil {
162
-			path = ""
163
-			err = errors.Wrapf(err, "error evaluating symlinks from mount source %q", m.Source)
164
-			return
165
-		}
166
-		err = label.Relabel(sourcePath, mountLabel, label.IsShared(m.Mode))
167
-		if err == syscall.ENOTSUP {
168
-			err = nil
169
-		}
170
-		if err != nil {
171
-			path = ""
172
-			err = errors.Wrapf(err, "error setting label on mount source '%s'", sourcePath)
173
-		}
174
-	}()
175
-
176
-	if m.Volume != nil {
177
-		id := m.ID
178
-		if id == "" {
179
-			id = stringid.GenerateNonCryptoID()
180
-		}
181
-		path, err := m.Volume.Mount(id)
182
-		if err != nil {
183
-			return "", errors.Wrapf(err, "error while mounting volume '%s'", m.Source)
184
-		}
185
-
186
-		m.ID = id
187
-		m.active++
188
-		return path, nil
189
-	}
190
-
191
-	if len(m.Source) == 0 {
192
-		return "", fmt.Errorf("Unable to setup mount point, neither source nor volume defined")
193
-	}
194
-
195
-	if m.Type == mounttypes.TypeBind {
196
-		// Before creating the source directory on the host, invoke checkFun if it's not nil. One of
197
-		// the use case is to forbid creating the daemon socket as a directory if the daemon is in
198
-		// the process of shutting down.
199
-		if checkFun != nil {
200
-			if err := checkFun(m); err != nil {
201
-				return "", err
202
-			}
203
-		}
204
-		// idtools.MkdirAllNewAs() produces an error if m.Source exists and is a file (not a directory)
205
-		// also, makes sure that if the directory is created, the correct remapped rootUID/rootGID will own it
206
-		if err := idtools.MkdirAllAndChownNew(m.Source, 0755, rootIDs); err != nil {
207
-			if perr, ok := err.(*os.PathError); ok {
208
-				if perr.Err != syscall.ENOTDIR {
209
-					return "", errors.Wrapf(err, "error while creating mount source path '%s'", m.Source)
210
-				}
211
-			}
212
-		}
213
-	}
214
-	return m.Source, nil
215
-}
216
-
217
-// Path returns the path of a volume in a mount point.
218
-func (m *MountPoint) Path() string {
219
-	if m.Volume != nil {
220
-		return m.Volume.Path()
221
-	}
222
-	return m.Source
223
-}
224
-
225
-func errInvalidMode(mode string) error {
226
-	return errors.Errorf("invalid mode: %v", mode)
227
-}
228
-
229
-func errInvalidSpec(spec string) error {
230
-	return errors.Errorf("invalid volume specification: '%s'", spec)
231
-}
232 80
deleted file mode 100644
... ...
@@ -1,23 +0,0 @@
1
-package volume // import "github.com/docker/docker/volume"
2
-
3
-import "strings"
4
-
5
-// {<copy mode>=isEnabled}
6
-var copyModes = map[string]bool{
7
-	"nocopy": false,
8
-}
9
-
10
-func copyModeExists(mode string) bool {
11
-	_, exists := copyModes[mode]
12
-	return exists
13
-}
14
-
15
-// GetCopyMode gets the copy mode from the mode string for mounts
16
-func getCopyMode(mode string, def bool) (bool, bool) {
17
-	for _, o := range strings.Split(mode, ",") {
18
-		if isEnabled, exists := copyModes[o]; exists {
19
-			return isEnabled, true
20
-		}
21
-	}
22
-	return def, false
23
-}
24 1
deleted file mode 100644
... ...
@@ -1,480 +0,0 @@
1
-package volume // import "github.com/docker/docker/volume"
2
-
3
-import (
4
-	"io/ioutil"
5
-	"os"
6
-	"runtime"
7
-	"strings"
8
-	"testing"
9
-
10
-	"github.com/docker/docker/api/types/mount"
11
-)
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
-
84
-func TestParseMountRaw(t *testing.T) {
85
-
86
-	previousProvider := currentFileInfoProvider
87
-	defer func() { currentFileInfoProvider = previousProvider }()
88
-	currentFileInfoProvider = mockFiProvider{}
89
-	windowsSet := parseMountRawTestSet{
90
-		valid: []string{
91
-			`d:\`,
92
-			`d:`,
93
-			`d:\path`,
94
-			`d:\path with space`,
95
-			`c:\:d:\`,
96
-			`c:\windows\:d:`,
97
-			`c:\windows:d:\s p a c e`,
98
-			`c:\windows:d:\s p a c e:RW`,
99
-			`c:\program files:d:\s p a c e i n h o s t d i r`,
100
-			`0123456789name:d:`,
101
-			`MiXeDcAsEnAmE:d:`,
102
-			`name:D:`,
103
-			`name:D::rW`,
104
-			`name:D::RW`,
105
-			`name:D::RO`,
106
-			`c:/:d:/forward/slashes/are/good/too`,
107
-			`c:/:d:/including with/spaces:ro`,
108
-			`c:\Windows`,                // With capital
109
-			`c:\Program Files (x86)`,    // With capitals and brackets
110
-			`\\?\c:\windows\:d:`,        // Long path handling (source)
111
-			`c:\windows\:\\?\d:\`,       // Long path handling (target)
112
-			`\\.\pipe\foo:\\.\pipe\foo`, // named pipe
113
-			`//./pipe/foo://./pipe/foo`, // named pipe forward slashes
114
-		},
115
-		invalid: map[string]string{
116
-			``:                                 "invalid volume specification: ",
117
-			`.`:                                "invalid volume specification: ",
118
-			`..\`:                              "invalid volume specification: ",
119
-			`c:\:..\`:                          "invalid volume specification: ",
120
-			`c:\:d:\:xyzzy`:                    "invalid volume specification: ",
121
-			`c:`:                               "cannot be `c:`",
122
-			`c:\`:                              "cannot be `c:`",
123
-			`c:\notexist:d:`:                   `bind mount source path does not exist: c:\notexist`,
124
-			`c:\windows\system32\ntdll.dll:d:`: `source path must be a directory`,
125
-			`name<:d:`:                         `invalid volume specification`,
126
-			`name>:d:`:                         `invalid volume specification`,
127
-			`name::d:`:                         `invalid volume specification`,
128
-			`name":d:`:                         `invalid volume specification`,
129
-			`name\:d:`:                         `invalid volume specification`,
130
-			`name*:d:`:                         `invalid volume specification`,
131
-			`name|:d:`:                         `invalid volume specification`,
132
-			`name?:d:`:                         `invalid volume specification`,
133
-			`name/:d:`:                         `invalid volume specification`,
134
-			`d:\pathandmode:rw`:                `invalid volume specification`,
135
-			`d:\pathandmode:ro`:                `invalid volume specification`,
136
-			`con:d:`:                           `cannot be a reserved word for Windows filenames`,
137
-			`PRN:d:`:                           `cannot be a reserved word for Windows filenames`,
138
-			`aUx:d:`:                           `cannot be a reserved word for Windows filenames`,
139
-			`nul:d:`:                           `cannot be a reserved word for Windows filenames`,
140
-			`com1:d:`:                          `cannot be a reserved word for Windows filenames`,
141
-			`com2:d:`:                          `cannot be a reserved word for Windows filenames`,
142
-			`com3:d:`:                          `cannot be a reserved word for Windows filenames`,
143
-			`com4:d:`:                          `cannot be a reserved word for Windows filenames`,
144
-			`com5:d:`:                          `cannot be a reserved word for Windows filenames`,
145
-			`com6:d:`:                          `cannot be a reserved word for Windows filenames`,
146
-			`com7:d:`:                          `cannot be a reserved word for Windows filenames`,
147
-			`com8:d:`:                          `cannot be a reserved word for Windows filenames`,
148
-			`com9:d:`:                          `cannot be a reserved word for Windows filenames`,
149
-			`lpt1:d:`:                          `cannot be a reserved word for Windows filenames`,
150
-			`lpt2:d:`:                          `cannot be a reserved word for Windows filenames`,
151
-			`lpt3:d:`:                          `cannot be a reserved word for Windows filenames`,
152
-			`lpt4:d:`:                          `cannot be a reserved word for Windows filenames`,
153
-			`lpt5:d:`:                          `cannot be a reserved word for Windows filenames`,
154
-			`lpt6:d:`:                          `cannot be a reserved word for Windows filenames`,
155
-			`lpt7:d:`:                          `cannot be a reserved word for Windows filenames`,
156
-			`lpt8:d:`:                          `cannot be a reserved word for Windows filenames`,
157
-			`lpt9:d:`:                          `cannot be a reserved word for Windows filenames`,
158
-			`c:\windows\system32\ntdll.dll`:    `Only directories can be mapped on this platform`,
159
-			`\\.\pipe\foo:c:\pipe`:             `'c:\pipe' is not a valid pipe path`,
160
-		},
161
-	}
162
-	lcowSet := parseMountRawTestSet{
163
-		valid: []string{
164
-			`/foo`,
165
-			`/foo/`,
166
-			`/foo bar`,
167
-			`c:\:/foo`,
168
-			`c:\windows\:/foo`,
169
-			`c:\windows:/s p a c e`,
170
-			`c:\windows:/s p a c e:RW`,
171
-			`c:\program files:/s p a c e i n h o s t d i r`,
172
-			`0123456789name:/foo`,
173
-			`MiXeDcAsEnAmE:/foo`,
174
-			`name:/foo`,
175
-			`name:/foo:rW`,
176
-			`name:/foo:RW`,
177
-			`name:/foo:RO`,
178
-			`c:/:/forward/slashes/are/good/too`,
179
-			`c:/:/including with/spaces:ro`,
180
-			`/Program Files (x86)`, // With capitals and brackets
181
-		},
182
-		invalid: map[string]string{
183
-			``:                                   "invalid volume specification: ",
184
-			`.`:                                  "invalid volume specification: ",
185
-			`c:`:                                 "invalid volume specification: ",
186
-			`c:\`:                                "invalid volume specification: ",
187
-			`../`:                                "invalid volume specification: ",
188
-			`c:\:../`:                            "invalid volume specification: ",
189
-			`c:\:/foo:xyzzy`:                     "invalid volume specification: ",
190
-			`/`:                                  "destination can't be '/'",
191
-			`/..`:                                "destination can't be '/'",
192
-			`c:\notexist:/foo`:                   `bind mount source path does not exist: c:\notexist`,
193
-			`c:\windows\system32\ntdll.dll:/foo`: `source path must be a directory`,
194
-			`name<:/foo`:                         `invalid volume specification`,
195
-			`name>:/foo`:                         `invalid volume specification`,
196
-			`name::/foo`:                         `invalid volume specification`,
197
-			`name":/foo`:                         `invalid volume specification`,
198
-			`name\:/foo`:                         `invalid volume specification`,
199
-			`name*:/foo`:                         `invalid volume specification`,
200
-			`name|:/foo`:                         `invalid volume specification`,
201
-			`name?:/foo`:                         `invalid volume specification`,
202
-			`name/:/foo`:                         `invalid volume specification`,
203
-			`/foo:rw`:                            `invalid volume specification`,
204
-			`/foo:ro`:                            `invalid volume specification`,
205
-			`con:/foo`:                           `cannot be a reserved word for Windows filenames`,
206
-			`PRN:/foo`:                           `cannot be a reserved word for Windows filenames`,
207
-			`aUx:/foo`:                           `cannot be a reserved word for Windows filenames`,
208
-			`nul:/foo`:                           `cannot be a reserved word for Windows filenames`,
209
-			`com1:/foo`:                          `cannot be a reserved word for Windows filenames`,
210
-			`com2:/foo`:                          `cannot be a reserved word for Windows filenames`,
211
-			`com3:/foo`:                          `cannot be a reserved word for Windows filenames`,
212
-			`com4:/foo`:                          `cannot be a reserved word for Windows filenames`,
213
-			`com5:/foo`:                          `cannot be a reserved word for Windows filenames`,
214
-			`com6:/foo`:                          `cannot be a reserved word for Windows filenames`,
215
-			`com7:/foo`:                          `cannot be a reserved word for Windows filenames`,
216
-			`com8:/foo`:                          `cannot be a reserved word for Windows filenames`,
217
-			`com9:/foo`:                          `cannot be a reserved word for Windows filenames`,
218
-			`lpt1:/foo`:                          `cannot be a reserved word for Windows filenames`,
219
-			`lpt2:/foo`:                          `cannot be a reserved word for Windows filenames`,
220
-			`lpt3:/foo`:                          `cannot be a reserved word for Windows filenames`,
221
-			`lpt4:/foo`:                          `cannot be a reserved word for Windows filenames`,
222
-			`lpt5:/foo`:                          `cannot be a reserved word for Windows filenames`,
223
-			`lpt6:/foo`:                          `cannot be a reserved word for Windows filenames`,
224
-			`lpt7:/foo`:                          `cannot be a reserved word for Windows filenames`,
225
-			`lpt8:/foo`:                          `cannot be a reserved word for Windows filenames`,
226
-			`lpt9:/foo`:                          `cannot be a reserved word for Windows filenames`,
227
-			`\\.\pipe\foo:/foo`:                  `Linux containers on Windows do not support named pipe mounts`,
228
-		},
229
-	}
230
-	linuxSet := parseMountRawTestSet{
231
-		valid: []string{
232
-			"/home",
233
-			"/home:/home",
234
-			"/home:/something/else",
235
-			"/with space",
236
-			"/home:/with space",
237
-			"relative:/absolute-path",
238
-			"hostPath:/containerPath:ro",
239
-			"/hostPath:/containerPath:rw",
240
-			"/rw:/ro",
241
-			"/hostPath:/containerPath:shared",
242
-			"/hostPath:/containerPath:rshared",
243
-			"/hostPath:/containerPath:slave",
244
-			"/hostPath:/containerPath:rslave",
245
-			"/hostPath:/containerPath:private",
246
-			"/hostPath:/containerPath:rprivate",
247
-			"/hostPath:/containerPath:ro,shared",
248
-			"/hostPath:/containerPath:ro,slave",
249
-			"/hostPath:/containerPath:ro,private",
250
-			"/hostPath:/containerPath:ro,z,shared",
251
-			"/hostPath:/containerPath:ro,Z,slave",
252
-			"/hostPath:/containerPath:Z,ro,slave",
253
-			"/hostPath:/containerPath:slave,Z,ro",
254
-			"/hostPath:/containerPath:Z,slave,ro",
255
-			"/hostPath:/containerPath:slave,ro,Z",
256
-			"/hostPath:/containerPath:rslave,ro,Z",
257
-			"/hostPath:/containerPath:ro,rshared,Z",
258
-			"/hostPath:/containerPath:ro,Z,rprivate",
259
-		},
260
-		invalid: map[string]string{
261
-			"":                                "invalid volume specification",
262
-			"./":                              "mount path must be absolute",
263
-			"../":                             "mount path must be absolute",
264
-			"/:../":                           "mount path must be absolute",
265
-			"/:path":                          "mount path must be absolute",
266
-			":":                               "invalid volume specification",
267
-			"/tmp:":                           "invalid volume specification",
268
-			":test":                           "invalid volume specification",
269
-			":/test":                          "invalid volume specification",
270
-			"tmp:":                            "invalid volume specification",
271
-			":test:":                          "invalid volume specification",
272
-			"::":                              "invalid volume specification",
273
-			":::":                             "invalid volume specification",
274
-			"/tmp:::":                         "invalid volume specification",
275
-			":/tmp::":                         "invalid volume specification",
276
-			"/path:rw":                        "invalid volume specification",
277
-			"/path:ro":                        "invalid volume specification",
278
-			"/rw:rw":                          "invalid volume specification",
279
-			"path:ro":                         "invalid volume specification",
280
-			"/path:/path:sw":                  `invalid mode`,
281
-			"/path:/path:rwz":                 `invalid mode`,
282
-			"/path:/path:ro,rshared,rslave":   `invalid mode`,
283
-			"/path:/path:ro,z,rshared,rslave": `invalid mode`,
284
-			"/path:shared":                    "invalid volume specification",
285
-			"/path:slave":                     "invalid volume specification",
286
-			"/path:private":                   "invalid volume specification",
287
-			"name:/absolute-path:shared":      "invalid volume specification",
288
-			"name:/absolute-path:rshared":     "invalid volume specification",
289
-			"name:/absolute-path:slave":       "invalid volume specification",
290
-			"name:/absolute-path:rslave":      "invalid volume specification",
291
-			"name:/absolute-path:private":     "invalid volume specification",
292
-			"name:/absolute-path:rprivate":    "invalid volume specification",
293
-		},
294
-	}
295
-
296
-	linParser := &linuxParser{}
297
-	winParser := &windowsParser{}
298
-	lcowParser := &lcowParser{}
299
-	tester := func(parser Parser, set parseMountRawTestSet) {
300
-
301
-		for _, path := range set.valid {
302
-
303
-			if _, err := parser.ParseMountRaw(path, "local"); err != nil {
304
-				t.Errorf("ParseMountRaw(`%q`) should succeed: error %q", path, err)
305
-			}
306
-		}
307
-
308
-		for path, expectedError := range set.invalid {
309
-			if mp, err := parser.ParseMountRaw(path, "local"); err == nil {
310
-				t.Errorf("ParseMountRaw(`%q`) should have failed validation. Err '%v' - MP: %v", path, err, mp)
311
-			} else {
312
-				if !strings.Contains(err.Error(), expectedError) {
313
-					t.Errorf("ParseMountRaw(`%q`) error should contain %q, got %v", path, expectedError, err.Error())
314
-				}
315
-			}
316
-		}
317
-	}
318
-	tester(linParser, linuxSet)
319
-	tester(winParser, windowsSet)
320
-	tester(lcowParser, lcowSet)
321
-
322
-}
323
-
324
-// testParseMountRaw is a structure used by TestParseMountRawSplit for
325
-// specifying test cases for the ParseMountRaw() function.
326
-type testParseMountRaw struct {
327
-	bind      string
328
-	driver    string
329
-	expType   mount.Type
330
-	expDest   string
331
-	expSource string
332
-	expName   string
333
-	expDriver string
334
-	expRW     bool
335
-	fail      bool
336
-}
337
-
338
-func TestParseMountRawSplit(t *testing.T) {
339
-	previousProvider := currentFileInfoProvider
340
-	defer func() { currentFileInfoProvider = previousProvider }()
341
-	currentFileInfoProvider = mockFiProvider{}
342
-	windowsCases := []testParseMountRaw{
343
-		{`c:\:d:`, "local", mount.TypeBind, `d:`, `c:\`, ``, "", true, false},
344
-		{`c:\:d:\`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false},
345
-		{`c:\:d:\:ro`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, false},
346
-		{`c:\:d:\:rw`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false},
347
-		{`c:\:d:\:foo`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, true},
348
-		{`name:d::rw`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false},
349
-		{`name:d:`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false},
350
-		{`name:d::ro`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", false, false},
351
-		{`name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
352
-		{`driver/name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
353
-		{`\\.\pipe\foo:\\.\pipe\bar`, "local", mount.TypeNamedPipe, `\\.\pipe\bar`, `\\.\pipe\foo`, "", "", true, false},
354
-		{`\\.\pipe\foo:c:\foo\bar`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
355
-		{`c:\foo\bar:\\.\pipe\foo`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
356
-	}
357
-	lcowCases := []testParseMountRaw{
358
-		{`c:\:/foo`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", true, false},
359
-		{`c:\:/foo:ro`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", false, false},
360
-		{`c:\:/foo:rw`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", true, false},
361
-		{`c:\:/foo:foo`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", false, true},
362
-		{`name:/foo:rw`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", true, false},
363
-		{`name:/foo`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", true, false},
364
-		{`name:/foo:ro`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", false, false},
365
-		{`name:/`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
366
-		{`driver/name:/`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
367
-		{`\\.\pipe\foo:\\.\pipe\bar`, "local", mount.TypeNamedPipe, `\\.\pipe\bar`, `\\.\pipe\foo`, "", "", true, true},
368
-		{`\\.\pipe\foo:/data`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
369
-		{`c:\foo\bar:\\.\pipe\foo`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
370
-	}
371
-	linuxCases := []testParseMountRaw{
372
-		{"/tmp:/tmp1", "", mount.TypeBind, "/tmp1", "/tmp", "", "", true, false},
373
-		{"/tmp:/tmp2:ro", "", mount.TypeBind, "/tmp2", "/tmp", "", "", false, false},
374
-		{"/tmp:/tmp3:rw", "", mount.TypeBind, "/tmp3", "/tmp", "", "", true, false},
375
-		{"/tmp:/tmp4:foo", "", mount.TypeBind, "", "", "", "", false, true},
376
-		{"name:/named1", "", mount.TypeVolume, "/named1", "", "name", "", true, false},
377
-		{"name:/named2", "external", mount.TypeVolume, "/named2", "", "name", "external", true, false},
378
-		{"name:/named3:ro", "local", mount.TypeVolume, "/named3", "", "name", "local", false, false},
379
-		{"local/name:/tmp:rw", "", mount.TypeVolume, "/tmp", "", "local/name", "", true, false},
380
-		{"/tmp:tmp", "", mount.TypeBind, "", "", "", "", true, true},
381
-	}
382
-	linParser := &linuxParser{}
383
-	winParser := &windowsParser{}
384
-	lcowParser := &lcowParser{}
385
-	tester := func(parser Parser, cases []testParseMountRaw) {
386
-		for i, c := range cases {
387
-			t.Logf("case %d", i)
388
-			m, err := parser.ParseMountRaw(c.bind, c.driver)
389
-			if c.fail {
390
-				if err == nil {
391
-					t.Errorf("Expected error, was nil, for spec %s\n", c.bind)
392
-				}
393
-				continue
394
-			}
395
-
396
-			if m == nil || err != nil {
397
-				t.Errorf("ParseMountRaw failed for spec '%s', driver '%s', error '%v'", c.bind, c.driver, err.Error())
398
-				continue
399
-			}
400
-
401
-			if m.Destination != c.expDest {
402
-				t.Errorf("Expected destination '%s, was %s', for spec '%s'", c.expDest, m.Destination, c.bind)
403
-			}
404
-
405
-			if m.Source != c.expSource {
406
-				t.Errorf("Expected source '%s', was '%s', for spec '%s'", c.expSource, m.Source, c.bind)
407
-			}
408
-
409
-			if m.Name != c.expName {
410
-				t.Errorf("Expected name '%s', was '%s' for spec '%s'", c.expName, m.Name, c.bind)
411
-			}
412
-
413
-			if m.Driver != c.expDriver {
414
-				t.Errorf("Expected driver '%s', was '%s', for spec '%s'", c.expDriver, m.Driver, c.bind)
415
-			}
416
-
417
-			if m.RW != c.expRW {
418
-				t.Errorf("Expected RW '%v', was '%v' for spec '%s'", c.expRW, m.RW, c.bind)
419
-			}
420
-			if m.Type != c.expType {
421
-				t.Fatalf("Expected type '%s', was '%s', for spec '%s'", c.expType, m.Type, c.bind)
422
-			}
423
-		}
424
-	}
425
-
426
-	tester(linParser, linuxCases)
427
-	tester(winParser, windowsCases)
428
-	tester(lcowParser, lcowCases)
429
-}
430
-
431
-func TestParseMountSpec(t *testing.T) {
432
-	type c struct {
433
-		input    mount.Mount
434
-		expected MountPoint
435
-	}
436
-	testDir, err := ioutil.TempDir("", "test-mount-config")
437
-	if err != nil {
438
-		t.Fatal(err)
439
-	}
440
-	defer os.RemoveAll(testDir)
441
-	parser := NewParser(runtime.GOOS)
442
-	cases := []c{
443
-		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: parser.DefaultPropagationMode()}},
444
-		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, RW: true, Propagation: parser.DefaultPropagationMode()}},
445
-		{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()}},
446
-		{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()}},
447
-		{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: parser.DefaultCopyMode()}},
448
-		{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath + string(os.PathSeparator)}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: parser.DefaultCopyMode()}},
449
-	}
450
-
451
-	for i, c := range cases {
452
-		t.Logf("case %d", i)
453
-		mp, err := parser.ParseMountSpec(c.input)
454
-		if err != nil {
455
-			t.Error(err)
456
-		}
457
-
458
-		if c.expected.Type != mp.Type {
459
-			t.Errorf("Expected mount types to match. Expected: '%s', Actual: '%s'", c.expected.Type, mp.Type)
460
-		}
461
-		if c.expected.Destination != mp.Destination {
462
-			t.Errorf("Expected mount destination to match. Expected: '%s', Actual: '%s'", c.expected.Destination, mp.Destination)
463
-		}
464
-		if c.expected.Source != mp.Source {
465
-			t.Errorf("Expected mount source to match. Expected: '%s', Actual: '%s'", c.expected.Source, mp.Source)
466
-		}
467
-		if c.expected.RW != mp.RW {
468
-			t.Errorf("Expected mount writable to match. Expected: '%v', Actual: '%v'", c.expected.RW, mp.RW)
469
-		}
470
-		if c.expected.Propagation != mp.Propagation {
471
-			t.Errorf("Expected mount propagation to match. Expected: '%v', Actual: '%s'", c.expected.Propagation, mp.Propagation)
472
-		}
473
-		if c.expected.Driver != mp.Driver {
474
-			t.Errorf("Expected mount driver to match. Expected: '%v', Actual: '%s'", c.expected.Driver, mp.Driver)
475
-		}
476
-		if c.expected.CopyData != mp.CopyData {
477
-			t.Errorf("Expected mount copy data to match. Expected: '%v', Actual: '%v'", c.expected.CopyData, mp.CopyData)
478
-		}
479
-	}
480
-}
481 1
deleted file mode 100644
... ...
@@ -1,18 +0,0 @@
1
-// +build linux freebsd darwin
2
-
3
-package volume // import "github.com/docker/docker/volume"
4
-
5
-import (
6
-	"fmt"
7
-	"path/filepath"
8
-	"strings"
9
-)
10
-
11
-func (p *linuxParser) HasResource(m *MountPoint, absolutePath string) bool {
12
-	relPath, err := filepath.Rel(m.Destination, absolutePath)
13
-	return err == nil && relPath != ".." && !strings.HasPrefix(relPath, fmt.Sprintf("..%c", filepath.Separator))
14
-}
15
-
16
-func (p *windowsParser) HasResource(m *MountPoint, absolutePath string) bool {
17
-	return false
18
-}
19 1
deleted file mode 100644
... ...
@@ -1,8 +0,0 @@
1
-package volume // import "github.com/docker/docker/volume"
2
-
3
-func (p *windowsParser) HasResource(m *MountPoint, absolutePath string) bool {
4
-	return false
5
-}
6
-func (p *linuxParser) HasResource(m *MountPoint, absolutePath string) bool {
7
-	return false
8
-}
9 1
deleted file mode 100644
... ...
@@ -1,456 +0,0 @@
1
-package volume // import "github.com/docker/docker/volume"
2
-
3
-import (
4
-	"errors"
5
-	"fmt"
6
-	"os"
7
-	"regexp"
8
-	"runtime"
9
-	"strings"
10
-
11
-	"github.com/docker/docker/api/types/mount"
12
-	"github.com/docker/docker/pkg/stringid"
13
-)
14
-
15
-type windowsParser struct {
16
-}
17
-
18
-const (
19
-	// Spec should be in the format [source:]destination[:mode]
20
-	//
21
-	// Examples: c:\foo bar:d:rw
22
-	//           c:\foo:d:\bar
23
-	//           myname:d:
24
-	//           d:\
25
-	//
26
-	// Explanation of this regex! Thanks @thaJeztah on IRC and gist for help. See
27
-	// https://gist.github.com/thaJeztah/6185659e4978789fb2b2. A good place to
28
-	// test is https://regex-golang.appspot.com/assets/html/index.html
29
-	//
30
-	// Useful link for referencing named capturing groups:
31
-	// http://stackoverflow.com/questions/20750843/using-named-matches-from-go-regex
32
-	//
33
-	// There are three match groups: source, destination and mode.
34
-	//
35
-
36
-	// rxHostDir is the first option of a source
37
-	rxHostDir = `(?:\\\\\?\\)?[a-z]:[\\/](?:[^\\/:*?"<>|\r\n]+[\\/]?)*`
38
-	// rxName is the second option of a source
39
-	rxName = `[^\\/:*?"<>|\r\n]+`
40
-
41
-	// RXReservedNames are reserved names not possible on Windows
42
-	rxReservedNames = `(con)|(prn)|(nul)|(aux)|(com[1-9])|(lpt[1-9])`
43
-
44
-	// rxPipe is a named path pipe (starts with `\\.\pipe\`, possibly with / instead of \)
45
-	rxPipe = `[/\\]{2}.[/\\]pipe[/\\][^:*?"<>|\r\n]+`
46
-	// rxSource is the combined possibilities for a source
47
-	rxSource = `((?P<source>((` + rxHostDir + `)|(` + rxName + `)|(` + rxPipe + `))):)?`
48
-
49
-	// Source. Can be either a host directory, a name, or omitted:
50
-	//  HostDir:
51
-	//    -  Essentially using the folder solution from
52
-	//       https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch08s18.html
53
-	//       but adding case insensitivity.
54
-	//    -  Must be an absolute path such as c:\path
55
-	//    -  Can include spaces such as `c:\program files`
56
-	//    -  And then followed by a colon which is not in the capture group
57
-	//    -  And can be optional
58
-	//  Name:
59
-	//    -  Must not contain invalid NTFS filename characters (https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx)
60
-	//    -  And then followed by a colon which is not in the capture group
61
-	//    -  And can be optional
62
-
63
-	// rxDestination is the regex expression for the mount destination
64
-	rxDestination = `(?P<destination>((?:\\\\\?\\)?([a-z]):((?:[\\/][^\\/:*?"<>\r\n]+)*[\\/]?))|(` + rxPipe + `))`
65
-
66
-	rxLCOWDestination = `(?P<destination>/(?:[^\\/:*?"<>\r\n]+[/]?)*)`
67
-	// Destination (aka container path):
68
-	//    -  Variation on hostdir but can be a drive followed by colon as well
69
-	//    -  If a path, must be absolute. Can include spaces
70
-	//    -  Drive cannot be c: (explicitly checked in code, not RegEx)
71
-
72
-	// rxMode is the regex expression for the mode of the mount
73
-	// Mode (optional):
74
-	//    -  Hopefully self explanatory in comparison to above regex's.
75
-	//    -  Colon is not in the capture group
76
-	rxMode = `(:(?P<mode>(?i)ro|rw))?`
77
-)
78
-
79
-type mountValidator func(mnt *mount.Mount) error
80
-
81
-func windowsSplitRawSpec(raw, destRegex string) ([]string, error) {
82
-	specExp := regexp.MustCompile(`^` + rxSource + destRegex + rxMode + `$`)
83
-	match := specExp.FindStringSubmatch(strings.ToLower(raw))
84
-
85
-	// Must have something back
86
-	if len(match) == 0 {
87
-		return nil, errInvalidSpec(raw)
88
-	}
89
-
90
-	var split []string
91
-	matchgroups := make(map[string]string)
92
-	// Pull out the sub expressions from the named capture groups
93
-	for i, name := range specExp.SubexpNames() {
94
-		matchgroups[name] = strings.ToLower(match[i])
95
-	}
96
-	if source, exists := matchgroups["source"]; exists {
97
-		if source != "" {
98
-			split = append(split, source)
99
-		}
100
-	}
101
-	if destination, exists := matchgroups["destination"]; exists {
102
-		if destination != "" {
103
-			split = append(split, destination)
104
-		}
105
-	}
106
-	if mode, exists := matchgroups["mode"]; exists {
107
-		if mode != "" {
108
-			split = append(split, mode)
109
-		}
110
-	}
111
-	// Fix #26329. If the destination appears to be a file, and the source is null,
112
-	// it may be because we've fallen through the possible naming regex and hit a
113
-	// situation where the user intention was to map a file into a container through
114
-	// a local volume, but this is not supported by the platform.
115
-	if matchgroups["source"] == "" && matchgroups["destination"] != "" {
116
-		volExp := regexp.MustCompile(`^` + rxName + `$`)
117
-		reservedNameExp := regexp.MustCompile(`^` + rxReservedNames + `$`)
118
-
119
-		if volExp.MatchString(matchgroups["destination"]) {
120
-			if reservedNameExp.MatchString(matchgroups["destination"]) {
121
-				return nil, fmt.Errorf("volume name %q cannot be a reserved word for Windows filenames", matchgroups["destination"])
122
-			}
123
-		} else {
124
-
125
-			exists, isDir, _ := currentFileInfoProvider.fileInfo(matchgroups["destination"])
126
-			if exists && !isDir {
127
-				return nil, fmt.Errorf("file '%s' cannot be mapped. Only directories can be mapped on this platform", matchgroups["destination"])
128
-
129
-			}
130
-		}
131
-	}
132
-	return split, nil
133
-}
134
-
135
-func windowsValidMountMode(mode string) bool {
136
-	if mode == "" {
137
-		return true
138
-	}
139
-	return rwModes[strings.ToLower(mode)]
140
-}
141
-func windowsValidateNotRoot(p string) error {
142
-	p = strings.ToLower(strings.Replace(p, `/`, `\`, -1))
143
-	if p == "c:" || p == `c:\` {
144
-		return fmt.Errorf("destination path cannot be `c:` or `c:\\`: %v", p)
145
-	}
146
-	return nil
147
-}
148
-
149
-var windowsSpecificValidators mountValidator = func(mnt *mount.Mount) error {
150
-	return windowsValidateNotRoot(mnt.Target)
151
-}
152
-
153
-func windowsValidateRegex(p, r string) error {
154
-	if regexp.MustCompile(`^` + r + `$`).MatchString(strings.ToLower(p)) {
155
-		return nil
156
-	}
157
-	return fmt.Errorf("invalid mount path: '%s'", p)
158
-}
159
-func windowsValidateAbsolute(p string) error {
160
-	if err := windowsValidateRegex(p, rxDestination); err != nil {
161
-		return fmt.Errorf("invalid mount path: '%s' mount path must be absolute", p)
162
-	}
163
-	return nil
164
-}
165
-
166
-func windowsDetectMountType(p string) mount.Type {
167
-	if strings.HasPrefix(p, `\\.\pipe\`) {
168
-		return mount.TypeNamedPipe
169
-	} else if regexp.MustCompile(`^` + rxHostDir + `$`).MatchString(p) {
170
-		return mount.TypeBind
171
-	} else {
172
-		return mount.TypeVolume
173
-	}
174
-}
175
-
176
-func (p *windowsParser) ReadWrite(mode string) bool {
177
-	return strings.ToLower(mode) != "ro"
178
-}
179
-
180
-// IsVolumeNameValid checks a volume name in a platform specific manner.
181
-func (p *windowsParser) ValidateVolumeName(name string) error {
182
-	nameExp := regexp.MustCompile(`^` + rxName + `$`)
183
-	if !nameExp.MatchString(name) {
184
-		return errors.New("invalid volume name")
185
-	}
186
-	nameExp = regexp.MustCompile(`^` + rxReservedNames + `$`)
187
-	if nameExp.MatchString(name) {
188
-		return fmt.Errorf("volume name %q cannot be a reserved word for Windows filenames", name)
189
-	}
190
-	return nil
191
-}
192
-func (p *windowsParser) ValidateMountConfig(mnt *mount.Mount) error {
193
-	return p.validateMountConfigReg(mnt, rxDestination, windowsSpecificValidators)
194
-}
195
-
196
-type fileInfoProvider interface {
197
-	fileInfo(path string) (exist, isDir bool, err error)
198
-}
199
-
200
-type defaultFileInfoProvider struct {
201
-}
202
-
203
-func (defaultFileInfoProvider) fileInfo(path string) (exist, isDir bool, err error) {
204
-	fi, err := os.Stat(path)
205
-	if err != nil {
206
-		if !os.IsNotExist(err) {
207
-			return false, false, err
208
-		}
209
-		return false, false, nil
210
-	}
211
-	return true, fi.IsDir(), nil
212
-}
213
-
214
-var currentFileInfoProvider fileInfoProvider = defaultFileInfoProvider{}
215
-
216
-func (p *windowsParser) validateMountConfigReg(mnt *mount.Mount, destRegex string, additionalValidators ...mountValidator) error {
217
-
218
-	for _, v := range additionalValidators {
219
-		if err := v(mnt); err != nil {
220
-			return &errMountConfig{mnt, err}
221
-		}
222
-	}
223
-	if len(mnt.Target) == 0 {
224
-		return &errMountConfig{mnt, errMissingField("Target")}
225
-	}
226
-
227
-	if err := windowsValidateRegex(mnt.Target, destRegex); err != nil {
228
-		return &errMountConfig{mnt, err}
229
-	}
230
-
231
-	switch mnt.Type {
232
-	case mount.TypeBind:
233
-		if len(mnt.Source) == 0 {
234
-			return &errMountConfig{mnt, errMissingField("Source")}
235
-		}
236
-		// Don't error out just because the propagation mode is not supported on the platform
237
-		if opts := mnt.BindOptions; opts != nil {
238
-			if len(opts.Propagation) > 0 {
239
-				return &errMountConfig{mnt, fmt.Errorf("invalid propagation mode: %s", opts.Propagation)}
240
-			}
241
-		}
242
-		if mnt.VolumeOptions != nil {
243
-			return &errMountConfig{mnt, errExtraField("VolumeOptions")}
244
-		}
245
-
246
-		if err := windowsValidateAbsolute(mnt.Source); err != nil {
247
-			return &errMountConfig{mnt, err}
248
-		}
249
-
250
-		exists, isdir, err := currentFileInfoProvider.fileInfo(mnt.Source)
251
-		if err != nil {
252
-			return &errMountConfig{mnt, err}
253
-		}
254
-		if !exists {
255
-			return &errMountConfig{mnt, errBindSourceDoesNotExist(mnt.Source)}
256
-		}
257
-		if !isdir {
258
-			return &errMountConfig{mnt, fmt.Errorf("source path must be a directory")}
259
-		}
260
-
261
-	case mount.TypeVolume:
262
-		if mnt.BindOptions != nil {
263
-			return &errMountConfig{mnt, errExtraField("BindOptions")}
264
-		}
265
-
266
-		if len(mnt.Source) == 0 && mnt.ReadOnly {
267
-			return &errMountConfig{mnt, fmt.Errorf("must not set ReadOnly mode when using anonymous volumes")}
268
-		}
269
-
270
-		if len(mnt.Source) != 0 {
271
-			if err := p.ValidateVolumeName(mnt.Source); err != nil {
272
-				return &errMountConfig{mnt, err}
273
-			}
274
-		}
275
-	case mount.TypeNamedPipe:
276
-		if len(mnt.Source) == 0 {
277
-			return &errMountConfig{mnt, errMissingField("Source")}
278
-		}
279
-
280
-		if mnt.BindOptions != nil {
281
-			return &errMountConfig{mnt, errExtraField("BindOptions")}
282
-		}
283
-
284
-		if mnt.ReadOnly {
285
-			return &errMountConfig{mnt, errExtraField("ReadOnly")}
286
-		}
287
-
288
-		if windowsDetectMountType(mnt.Source) != mount.TypeNamedPipe {
289
-			return &errMountConfig{mnt, fmt.Errorf("'%s' is not a valid pipe path", mnt.Source)}
290
-		}
291
-
292
-		if windowsDetectMountType(mnt.Target) != mount.TypeNamedPipe {
293
-			return &errMountConfig{mnt, fmt.Errorf("'%s' is not a valid pipe path", mnt.Target)}
294
-		}
295
-	default:
296
-		return &errMountConfig{mnt, errors.New("mount type unknown")}
297
-	}
298
-	return nil
299
-}
300
-func (p *windowsParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
301
-	return p.parseMountRaw(raw, volumeDriver, rxDestination, true, windowsSpecificValidators)
302
-}
303
-
304
-func (p *windowsParser) parseMountRaw(raw, volumeDriver, destRegex string, convertTargetToBackslash bool, additionalValidators ...mountValidator) (*MountPoint, error) {
305
-	arr, err := windowsSplitRawSpec(raw, destRegex)
306
-	if err != nil {
307
-		return nil, err
308
-	}
309
-
310
-	var spec mount.Mount
311
-	var mode string
312
-	switch len(arr) {
313
-	case 1:
314
-		// Just a destination path in the container
315
-		spec.Target = arr[0]
316
-	case 2:
317
-		if windowsValidMountMode(arr[1]) {
318
-			// Destination + Mode is not a valid volume - volumes
319
-			// cannot include a mode. e.g. /foo:rw
320
-			return nil, errInvalidSpec(raw)
321
-		}
322
-		// Host Source Path or Name + Destination
323
-		spec.Source = strings.Replace(arr[0], `/`, `\`, -1)
324
-		spec.Target = arr[1]
325
-	case 3:
326
-		// HostSourcePath+DestinationPath+Mode
327
-		spec.Source = strings.Replace(arr[0], `/`, `\`, -1)
328
-		spec.Target = arr[1]
329
-		mode = arr[2]
330
-	default:
331
-		return nil, errInvalidSpec(raw)
332
-	}
333
-	if convertTargetToBackslash {
334
-		spec.Target = strings.Replace(spec.Target, `/`, `\`, -1)
335
-	}
336
-
337
-	if !windowsValidMountMode(mode) {
338
-		return nil, errInvalidMode(mode)
339
-	}
340
-
341
-	spec.Type = windowsDetectMountType(spec.Source)
342
-	spec.ReadOnly = !p.ReadWrite(mode)
343
-
344
-	// cannot assume that if a volume driver is passed in that we should set it
345
-	if volumeDriver != "" && spec.Type == mount.TypeVolume {
346
-		spec.VolumeOptions = &mount.VolumeOptions{
347
-			DriverConfig: &mount.Driver{Name: volumeDriver},
348
-		}
349
-	}
350
-
351
-	if copyData, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
352
-		if spec.VolumeOptions == nil {
353
-			spec.VolumeOptions = &mount.VolumeOptions{}
354
-		}
355
-		spec.VolumeOptions.NoCopy = !copyData
356
-	}
357
-
358
-	mp, err := p.parseMountSpec(spec, destRegex, convertTargetToBackslash, additionalValidators...)
359
-	if mp != nil {
360
-		mp.Mode = mode
361
-	}
362
-	if err != nil {
363
-		err = fmt.Errorf("%v: %v", errInvalidSpec(raw), err)
364
-	}
365
-	return mp, err
366
-}
367
-
368
-func (p *windowsParser) ParseMountSpec(cfg mount.Mount) (*MountPoint, error) {
369
-	return p.parseMountSpec(cfg, rxDestination, true, windowsSpecificValidators)
370
-}
371
-func (p *windowsParser) parseMountSpec(cfg mount.Mount, destRegex string, convertTargetToBackslash bool, additionalValidators ...mountValidator) (*MountPoint, error) {
372
-	if err := p.validateMountConfigReg(&cfg, destRegex, additionalValidators...); err != nil {
373
-		return nil, err
374
-	}
375
-	mp := &MountPoint{
376
-		RW:          !cfg.ReadOnly,
377
-		Destination: cfg.Target,
378
-		Type:        cfg.Type,
379
-		Spec:        cfg,
380
-	}
381
-	if convertTargetToBackslash {
382
-		mp.Destination = strings.Replace(cfg.Target, `/`, `\`, -1)
383
-	}
384
-
385
-	switch cfg.Type {
386
-	case mount.TypeVolume:
387
-		if cfg.Source == "" {
388
-			mp.Name = stringid.GenerateNonCryptoID()
389
-		} else {
390
-			mp.Name = cfg.Source
391
-		}
392
-		mp.CopyData = p.DefaultCopyMode()
393
-
394
-		if cfg.VolumeOptions != nil {
395
-			if cfg.VolumeOptions.DriverConfig != nil {
396
-				mp.Driver = cfg.VolumeOptions.DriverConfig.Name
397
-			}
398
-			if cfg.VolumeOptions.NoCopy {
399
-				mp.CopyData = false
400
-			}
401
-		}
402
-	case mount.TypeBind:
403
-		mp.Source = strings.Replace(cfg.Source, `/`, `\`, -1)
404
-	case mount.TypeNamedPipe:
405
-		mp.Source = strings.Replace(cfg.Source, `/`, `\`, -1)
406
-	}
407
-	// cleanup trailing `\` except for paths like `c:\`
408
-	if len(mp.Source) > 3 && mp.Source[len(mp.Source)-1] == '\\' {
409
-		mp.Source = mp.Source[:len(mp.Source)-1]
410
-	}
411
-	if len(mp.Destination) > 3 && mp.Destination[len(mp.Destination)-1] == '\\' {
412
-		mp.Destination = mp.Destination[:len(mp.Destination)-1]
413
-	}
414
-	return mp, nil
415
-}
416
-
417
-func (p *windowsParser) ParseVolumesFrom(spec string) (string, string, error) {
418
-	if len(spec) == 0 {
419
-		return "", "", fmt.Errorf("volumes-from specification cannot be an empty string")
420
-	}
421
-
422
-	specParts := strings.SplitN(spec, ":", 2)
423
-	id := specParts[0]
424
-	mode := "rw"
425
-
426
-	if len(specParts) == 2 {
427
-		mode = specParts[1]
428
-		if !windowsValidMountMode(mode) {
429
-			return "", "", errInvalidMode(mode)
430
-		}
431
-
432
-		// Do not allow copy modes on volumes-from
433
-		if _, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
434
-			return "", "", errInvalidMode(mode)
435
-		}
436
-	}
437
-	return id, mode, nil
438
-}
439
-
440
-func (p *windowsParser) DefaultPropagationMode() mount.Propagation {
441
-	return mount.Propagation("")
442
-}
443
-
444
-func (p *windowsParser) ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error) {
445
-	return "", fmt.Errorf("%s does not support tmpfs", runtime.GOOS)
446
-}
447
-func (p *windowsParser) DefaultCopyMode() bool {
448
-	return false
449
-}
450
-func (p *windowsParser) IsBackwardCompatible(m *MountPoint) bool {
451
-	return false
452
-}
453
-
454
-func (p *windowsParser) ValidateTmpfsMountDestination(dest string) error {
455
-	return errors.New("Platform does not support tmpfs")
456
-}