Browse code

Windows: Add volume support

Signed-off-by: John Howard <jhoward@microsoft.com>

John Howard authored on 2015/09/10 11:23:06
Showing 52 changed files
... ...
@@ -331,7 +331,12 @@ func (s *router) postContainersCreate(ctx context.Context, w http.ResponseWriter
331 331
 	version := httputils.VersionFromContext(ctx)
332 332
 	adjustCPUShares := version.LessThan("1.19")
333 333
 
334
-	ccr, err := s.daemon.ContainerCreate(name, config, hostConfig, adjustCPUShares)
334
+	ccr, err := s.daemon.ContainerCreate(&daemon.ContainerCreateConfig{
335
+		Name:            name,
336
+		Config:          config,
337
+		HostConfig:      hostConfig,
338
+		AdjustCPUShares: adjustCPUShares,
339
+	})
335 340
 	if err != nil {
336 341
 		return err
337 342
 	}
... ...
@@ -186,7 +186,7 @@ func platformSupports(command string) error {
186 186
 		return nil
187 187
 	}
188 188
 	switch command {
189
-	case "expose", "volume", "user", "stopsignal", "arg":
189
+	case "expose", "user", "stopsignal", "arg":
190 190
 		return fmt.Errorf("The daemon on this platform does not support the command '%s'", command)
191 191
 	}
192 192
 	return nil
... ...
@@ -8,7 +8,7 @@ package daemon
8 8
 func checkIfPathIsInAVolume(container *Container, absPath string) (bool, error) {
9 9
 	var toVolume bool
10 10
 	for _, mnt := range container.MountPoints {
11
-		if toVolume = mnt.hasResource(absPath); toVolume {
11
+		if toVolume = mnt.HasResource(absPath); toVolume {
12 12
 			if mnt.RW {
13 13
 				break
14 14
 			}
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"io/ioutil"
9 9
 	"os"
10 10
 	"path/filepath"
11
+	"strings"
11 12
 	"sync"
12 13
 	"syscall"
13 14
 	"time"
... ...
@@ -30,8 +31,10 @@ import (
30 30
 	"github.com/docker/docker/pkg/promise"
31 31
 	"github.com/docker/docker/pkg/signal"
32 32
 	"github.com/docker/docker/pkg/symlink"
33
+	"github.com/docker/docker/pkg/system"
33 34
 	"github.com/docker/docker/runconfig"
34 35
 	"github.com/docker/docker/volume"
36
+	"github.com/docker/docker/volume/store"
35 37
 )
36 38
 
37 39
 var (
... ...
@@ -72,6 +75,7 @@ type CommonContainer struct {
72 72
 	RestartCount           int
73 73
 	HasBeenStartedBefore   bool
74 74
 	HasBeenManuallyStopped bool // used for unless-stopped restart policy
75
+	MountPoints            map[string]*volume.MountPoint
75 76
 	hostConfig             *runconfig.HostConfig
76 77
 	command                *execdriver.Command
77 78
 	monitor                *containerMonitor
... ...
@@ -1108,29 +1112,109 @@ func (container *Container) mountVolumes() error {
1108 1108
 	return nil
1109 1109
 }
1110 1110
 
1111
-func (container *Container) copyImagePathContent(v volume.Volume, destination string) error {
1112
-	rootfs, err := symlink.FollowSymlinkInScope(filepath.Join(container.basefs, destination), container.basefs)
1113
-	if err != nil {
1114
-		return err
1111
+func (container *Container) prepareMountPoints() error {
1112
+	for _, config := range container.MountPoints {
1113
+		if len(config.Driver) > 0 {
1114
+			v, err := container.daemon.createVolume(config.Name, config.Driver, nil)
1115
+			if err != nil {
1116
+				return err
1117
+			}
1118
+			config.Volume = v
1119
+		}
1115 1120
 	}
1121
+	return nil
1122
+}
1116 1123
 
1117
-	if _, err = ioutil.ReadDir(rootfs); err != nil {
1118
-		if os.IsNotExist(err) {
1119
-			return nil
1124
+func (container *Container) removeMountPoints(rm bool) error {
1125
+	var rmErrors []string
1126
+	for _, m := range container.MountPoints {
1127
+		if m.Volume == nil {
1128
+			continue
1129
+		}
1130
+		container.daemon.volumes.Decrement(m.Volume)
1131
+		if rm {
1132
+			err := container.daemon.volumes.Remove(m.Volume)
1133
+			// ErrVolumeInUse is ignored because having this
1134
+			// volume being referenced by other container is
1135
+			// not an error, but an implementation detail.
1136
+			// This prevents docker from logging "ERROR: Volume in use"
1137
+			// where there is another container using the volume.
1138
+			if err != nil && err != store.ErrVolumeInUse {
1139
+				rmErrors = append(rmErrors, err.Error())
1140
+			}
1120 1141
 		}
1121
-		return err
1122 1142
 	}
1143
+	if len(rmErrors) > 0 {
1144
+		return derr.ErrorCodeRemovingVolume.WithArgs(strings.Join(rmErrors, "\n"))
1145
+	}
1146
+	return nil
1147
+}
1123 1148
 
1124
-	path, err := v.Mount()
1125
-	if err != nil {
1126
-		return err
1149
+func (container *Container) unmountVolumes(forceSyscall bool) error {
1150
+	var (
1151
+		volumeMounts []volume.MountPoint
1152
+		err          error
1153
+	)
1154
+
1155
+	for _, mntPoint := range container.MountPoints {
1156
+		dest, err := container.GetResourcePath(mntPoint.Destination)
1157
+		if err != nil {
1158
+			return err
1159
+		}
1160
+
1161
+		volumeMounts = append(volumeMounts, volume.MountPoint{Destination: dest, Volume: mntPoint.Volume})
1127 1162
 	}
1128 1163
 
1129
-	if err := copyExistingContents(rootfs, path); err != nil {
1164
+	// Append any network mounts to the list (this is a no-op on Windows)
1165
+	if volumeMounts, err = appendNetworkMounts(container, volumeMounts); err != nil {
1130 1166
 		return err
1131 1167
 	}
1132 1168
 
1133
-	return v.Unmount()
1169
+	for _, volumeMount := range volumeMounts {
1170
+		if forceSyscall {
1171
+			system.UnmountWithSyscall(volumeMount.Destination)
1172
+		}
1173
+
1174
+		if volumeMount.Volume != nil {
1175
+			if err := volumeMount.Volume.Unmount(); err != nil {
1176
+				return err
1177
+			}
1178
+		}
1179
+	}
1180
+
1181
+	return nil
1182
+}
1183
+
1184
+func (container *Container) addBindMountPoint(name, source, destination string, rw bool) {
1185
+	container.MountPoints[destination] = &volume.MountPoint{
1186
+		Name:        name,
1187
+		Source:      source,
1188
+		Destination: destination,
1189
+		RW:          rw,
1190
+	}
1191
+}
1192
+
1193
+func (container *Container) addLocalMountPoint(name, destination string, rw bool) {
1194
+	container.MountPoints[destination] = &volume.MountPoint{
1195
+		Name:        name,
1196
+		Driver:      volume.DefaultDriverName,
1197
+		Destination: destination,
1198
+		RW:          rw,
1199
+	}
1200
+}
1201
+
1202
+func (container *Container) addMountPointWithVolume(destination string, vol volume.Volume, rw bool) {
1203
+	container.MountPoints[destination] = &volume.MountPoint{
1204
+		Name:        vol.Name(),
1205
+		Driver:      vol.DriverName(),
1206
+		Destination: destination,
1207
+		RW:          rw,
1208
+		Volume:      vol,
1209
+	}
1210
+}
1211
+
1212
+func (container *Container) isDestinationMounted(destination string) bool {
1213
+	return container.MountPoints[destination] != nil
1134 1214
 }
1135 1215
 
1136 1216
 func (container *Container) stopSignal() int {
... ...
@@ -23,12 +23,12 @@ import (
23 23
 	"github.com/docker/docker/pkg/idtools"
24 24
 	"github.com/docker/docker/pkg/nat"
25 25
 	"github.com/docker/docker/pkg/stringid"
26
+	"github.com/docker/docker/pkg/symlink"
26 27
 	"github.com/docker/docker/pkg/system"
27 28
 	"github.com/docker/docker/pkg/ulimit"
28 29
 	"github.com/docker/docker/runconfig"
29 30
 	"github.com/docker/docker/utils"
30 31
 	"github.com/docker/docker/volume"
31
-	"github.com/docker/docker/volume/store"
32 32
 	"github.com/docker/libnetwork"
33 33
 	"github.com/docker/libnetwork/drivers/bridge"
34 34
 	"github.com/docker/libnetwork/netlabel"
... ...
@@ -54,9 +54,8 @@ type Container struct {
54 54
 	AppArmorProfile string
55 55
 	HostnamePath    string
56 56
 	HostsPath       string
57
-	ShmPath         string
58
-	MqueuePath      string
59
-	MountPoints     map[string]*mountPoint
57
+	ShmPath         string // TODO Windows - Factor this out (GH15862)
58
+	MqueuePath      string // TODO Windows - Factor this out (GH15862)
60 59
 	ResolvConfPath  string
61 60
 
62 61
 	Volumes   map[string]string // Deprecated since 1.7, kept for backwards compatibility
... ...
@@ -1197,40 +1196,16 @@ func (container *Container) disconnectFromNetwork(n libnetwork.Network) error {
1197 1197
 	return nil
1198 1198
 }
1199 1199
 
1200
-func (container *Container) unmountVolumes(forceSyscall bool) error {
1201
-	var volumeMounts []mountPoint
1202
-
1203
-	for _, mntPoint := range container.MountPoints {
1204
-		dest, err := container.GetResourcePath(mntPoint.Destination)
1205
-		if err != nil {
1206
-			return err
1207
-		}
1208
-
1209
-		volumeMounts = append(volumeMounts, mountPoint{Destination: dest, Volume: mntPoint.Volume})
1210
-	}
1211
-
1200
+// appendNetworkMounts appends any network mounts to the array of mount points passed in
1201
+func appendNetworkMounts(container *Container, volumeMounts []volume.MountPoint) ([]volume.MountPoint, error) {
1212 1202
 	for _, mnt := range container.networkMounts() {
1213 1203
 		dest, err := container.GetResourcePath(mnt.Destination)
1214 1204
 		if err != nil {
1215
-			return err
1216
-		}
1217
-
1218
-		volumeMounts = append(volumeMounts, mountPoint{Destination: dest})
1219
-	}
1220
-
1221
-	for _, volumeMount := range volumeMounts {
1222
-		if forceSyscall {
1223
-			syscall.Unmount(volumeMount.Destination, 0)
1224
-		}
1225
-
1226
-		if volumeMount.Volume != nil {
1227
-			if err := volumeMount.Volume.Unmount(); err != nil {
1228
-				return err
1229
-			}
1205
+			return nil, err
1230 1206
 		}
1207
+		volumeMounts = append(volumeMounts, volume.MountPoint{Destination: dest})
1231 1208
 	}
1232
-
1233
-	return nil
1209
+	return volumeMounts, nil
1234 1210
 }
1235 1211
 
1236 1212
 func (container *Container) networkMounts() []execdriver.Mount {
... ...
@@ -1290,74 +1265,29 @@ func (container *Container) networkMounts() []execdriver.Mount {
1290 1290
 	return mounts
1291 1291
 }
1292 1292
 
1293
-func (container *Container) addBindMountPoint(name, source, destination string, rw bool) {
1294
-	container.MountPoints[destination] = &mountPoint{
1295
-		Name:        name,
1296
-		Source:      source,
1297
-		Destination: destination,
1298
-		RW:          rw,
1293
+func (container *Container) copyImagePathContent(v volume.Volume, destination string) error {
1294
+	rootfs, err := symlink.FollowSymlinkInScope(filepath.Join(container.basefs, destination), container.basefs)
1295
+	if err != nil {
1296
+		return err
1299 1297
 	}
1300
-}
1301 1298
 
1302
-func (container *Container) addLocalMountPoint(name, destination string, rw bool) {
1303
-	container.MountPoints[destination] = &mountPoint{
1304
-		Name:        name,
1305
-		Driver:      volume.DefaultDriverName,
1306
-		Destination: destination,
1307
-		RW:          rw,
1299
+	if _, err = ioutil.ReadDir(rootfs); err != nil {
1300
+		if os.IsNotExist(err) {
1301
+			return nil
1302
+		}
1303
+		return err
1308 1304
 	}
1309
-}
1310 1305
 
1311
-func (container *Container) addMountPointWithVolume(destination string, vol volume.Volume, rw bool) {
1312
-	container.MountPoints[destination] = &mountPoint{
1313
-		Name:        vol.Name(),
1314
-		Driver:      vol.DriverName(),
1315
-		Destination: destination,
1316
-		RW:          rw,
1317
-		Volume:      vol,
1306
+	path, err := v.Mount()
1307
+	if err != nil {
1308
+		return err
1318 1309
 	}
1319
-}
1320 1310
 
1321
-func (container *Container) isDestinationMounted(destination string) bool {
1322
-	return container.MountPoints[destination] != nil
1323
-}
1324
-
1325
-func (container *Container) prepareMountPoints() error {
1326
-	for _, config := range container.MountPoints {
1327
-		if len(config.Driver) > 0 {
1328
-			v, err := container.daemon.createVolume(config.Name, config.Driver, nil)
1329
-			if err != nil {
1330
-				return err
1331
-			}
1332
-			config.Volume = v
1333
-		}
1311
+	if err := copyExistingContents(rootfs, path); err != nil {
1312
+		return err
1334 1313
 	}
1335
-	return nil
1336
-}
1337 1314
 
1338
-func (container *Container) removeMountPoints(rm bool) error {
1339
-	var rmErrors []string
1340
-	for _, m := range container.MountPoints {
1341
-		if m.Volume == nil {
1342
-			continue
1343
-		}
1344
-		container.daemon.volumes.Decrement(m.Volume)
1345
-		if rm {
1346
-			err := container.daemon.volumes.Remove(m.Volume)
1347
-			// ErrVolumeInUse is ignored because having this
1348
-			// volume being referenced by othe container is
1349
-			// not an error, but an implementation detail.
1350
-			// This prevents docker from logging "ERROR: Volume in use"
1351
-			// where there is another container using the volume.
1352
-			if err != nil && err != store.ErrVolumeInUse {
1353
-				rmErrors = append(rmErrors, err.Error())
1354
-			}
1355
-		}
1356
-	}
1357
-	if len(rmErrors) > 0 {
1358
-		return derr.ErrorCodeRemovingVolume.WithArgs(strings.Join(rmErrors, "\n"))
1359
-	}
1360
-	return nil
1315
+	return v.Unmount()
1361 1316
 }
1362 1317
 
1363 1318
 func (container *Container) shmPath() (string, error) {
... ...
@@ -7,6 +7,7 @@ import (
7 7
 
8 8
 	"github.com/docker/docker/daemon/execdriver"
9 9
 	derr "github.com/docker/docker/errors"
10
+	"github.com/docker/docker/volume"
10 11
 	"github.com/docker/libnetwork"
11 12
 )
12 13
 
... ...
@@ -169,18 +170,11 @@ func (container *Container) updateNetwork() error {
169 169
 func (container *Container) releaseNetwork() {
170 170
 }
171 171
 
172
-func (container *Container) unmountVolumes(forceSyscall bool) error {
173
-	return nil
174
-}
175
-
176
-// prepareMountPoints is a no-op on Windows
177
-func (container *Container) prepareMountPoints() error {
178
-	return nil
179
-}
180
-
181
-// removeMountPoints is a no-op on Windows.
182
-func (container *Container) removeMountPoints(_ bool) error {
183
-	return nil
172
+// appendNetworkMounts appends any network mounts to the array of mount points passed in.
173
+// Windows does not support network mounts (not to be confused with SMB network mounts), so
174
+// this is a no-op.
175
+func appendNetworkMounts(container *Container, volumeMounts []volume.MountPoint) ([]volume.MountPoint, error) {
176
+	return volumeMounts, nil
184 177
 }
185 178
 
186 179
 func (container *Container) setupIpcDirs() error {
... ...
@@ -15,26 +15,34 @@ import (
15 15
 	"github.com/opencontainers/runc/libcontainer/label"
16 16
 )
17 17
 
18
+// ContainerCreateConfig is the parameter set to ContainerCreate()
19
+type ContainerCreateConfig struct {
20
+	Name            string
21
+	Config          *runconfig.Config
22
+	HostConfig      *runconfig.HostConfig
23
+	AdjustCPUShares bool
24
+}
25
+
18 26
 // ContainerCreate takes configs and creates a container.
19
-func (daemon *Daemon) ContainerCreate(name string, config *runconfig.Config, hostConfig *runconfig.HostConfig, adjustCPUShares bool) (types.ContainerCreateResponse, error) {
20
-	if config == nil {
27
+func (daemon *Daemon) ContainerCreate(params *ContainerCreateConfig) (types.ContainerCreateResponse, error) {
28
+	if params.Config == nil {
21 29
 		return types.ContainerCreateResponse{}, derr.ErrorCodeEmptyConfig
22 30
 	}
23 31
 
24
-	warnings, err := daemon.verifyContainerSettings(hostConfig, config)
32
+	warnings, err := daemon.verifyContainerSettings(params.HostConfig, params.Config)
25 33
 	if err != nil {
26 34
 		return types.ContainerCreateResponse{"", warnings}, err
27 35
 	}
28 36
 
29
-	daemon.adaptContainerSettings(hostConfig, adjustCPUShares)
37
+	daemon.adaptContainerSettings(params.HostConfig, params.AdjustCPUShares)
30 38
 
31
-	container, err := daemon.Create(config, hostConfig, name)
39
+	container, err := daemon.create(params)
32 40
 	if err != nil {
33
-		if daemon.Graph().IsNotExist(err, config.Image) {
34
-			if strings.Contains(config.Image, "@") {
35
-				return types.ContainerCreateResponse{"", warnings}, derr.ErrorCodeNoSuchImageHash.WithArgs(config.Image)
41
+		if daemon.Graph().IsNotExist(err, params.Config.Image) {
42
+			if strings.Contains(params.Config.Image, "@") {
43
+				return types.ContainerCreateResponse{"", warnings}, derr.ErrorCodeNoSuchImageHash.WithArgs(params.Config.Image)
36 44
 			}
37
-			img, tag := parsers.ParseRepositoryTag(config.Image)
45
+			img, tag := parsers.ParseRepositoryTag(params.Config.Image)
38 46
 			if tag == "" {
39 47
 				tag = tags.DefaultTag
40 48
 			}
... ...
@@ -47,7 +55,7 @@ func (daemon *Daemon) ContainerCreate(name string, config *runconfig.Config, hos
47 47
 }
48 48
 
49 49
 // Create creates a new container from the given configuration with a given name.
50
-func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.HostConfig, name string) (retC *Container, retErr error) {
50
+func (daemon *Daemon) create(params *ContainerCreateConfig) (retC *Container, retErr error) {
51 51
 	var (
52 52
 		container *Container
53 53
 		img       *image.Image
... ...
@@ -55,8 +63,8 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos
55 55
 		err       error
56 56
 	)
57 57
 
58
-	if config.Image != "" {
59
-		img, err = daemon.repositories.LookupImage(config.Image)
58
+	if params.Config.Image != "" {
59
+		img, err = daemon.repositories.LookupImage(params.Config.Image)
60 60
 		if err != nil {
61 61
 			return nil, err
62 62
 		}
... ...
@@ -66,20 +74,20 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos
66 66
 		imgID = img.ID
67 67
 	}
68 68
 
69
-	if err := daemon.mergeAndVerifyConfig(config, img); err != nil {
69
+	if err := daemon.mergeAndVerifyConfig(params.Config, img); err != nil {
70 70
 		return nil, err
71 71
 	}
72 72
 
73
-	if hostConfig == nil {
74
-		hostConfig = &runconfig.HostConfig{}
73
+	if params.HostConfig == nil {
74
+		params.HostConfig = &runconfig.HostConfig{}
75 75
 	}
76
-	if hostConfig.SecurityOpt == nil {
77
-		hostConfig.SecurityOpt, err = daemon.generateSecurityOpt(hostConfig.IpcMode, hostConfig.PidMode)
76
+	if params.HostConfig.SecurityOpt == nil {
77
+		params.HostConfig.SecurityOpt, err = daemon.generateSecurityOpt(params.HostConfig.IpcMode, params.HostConfig.PidMode)
78 78
 		if err != nil {
79 79
 			return nil, err
80 80
 		}
81 81
 	}
82
-	if container, err = daemon.newContainer(name, config, imgID); err != nil {
82
+	if container, err = daemon.newContainer(params.Name, params.Config, imgID); err != nil {
83 83
 		return nil, err
84 84
 	}
85 85
 	defer func() {
... ...
@@ -96,7 +104,7 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos
96 96
 	if err := daemon.createRootfs(container); err != nil {
97 97
 		return nil, err
98 98
 	}
99
-	if err := daemon.setHostConfig(container, hostConfig); err != nil {
99
+	if err := daemon.setHostConfig(container, params.HostConfig); err != nil {
100 100
 		return nil, err
101 101
 	}
102 102
 	defer func() {
... ...
@@ -111,7 +119,7 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos
111 111
 	}
112 112
 	defer container.Unmount()
113 113
 
114
-	if err := createContainerPlatformSpecificSettings(container, config, hostConfig, img); err != nil {
114
+	if err := createContainerPlatformSpecificSettings(container, params.Config, params.HostConfig, img); err != nil {
115 115
 		return nil, err
116 116
 	}
117 117
 
... ...
@@ -16,9 +16,11 @@ import (
16 16
 
17 17
 // createContainerPlatformSpecificSettings performs platform specific container create functionality
18 18
 func createContainerPlatformSpecificSettings(container *Container, config *runconfig.Config, hostConfig *runconfig.HostConfig, img *image.Image) error {
19
+	var name, destination string
20
+
19 21
 	for spec := range config.Volumes {
20
-		name := stringid.GenerateNonCryptoID()
21
-		destination := filepath.Clean(spec)
22
+		name = stringid.GenerateNonCryptoID()
23
+		destination = filepath.Clean(spec)
22 24
 
23 25
 		// Skip volumes for which we already have something mounted on that
24 26
 		// destination because of a --volume-from.
... ...
@@ -1,11 +1,83 @@
1 1
 package daemon
2 2
 
3 3
 import (
4
+	"fmt"
5
+
4 6
 	"github.com/docker/docker/image"
7
+	"github.com/docker/docker/pkg/stringid"
5 8
 	"github.com/docker/docker/runconfig"
9
+	"github.com/docker/docker/volume"
6 10
 )
7 11
 
8 12
 // createContainerPlatformSpecificSettings performs platform specific container create functionality
9 13
 func createContainerPlatformSpecificSettings(container *Container, config *runconfig.Config, hostConfig *runconfig.HostConfig, img *image.Image) error {
14
+	for spec := range config.Volumes {
15
+
16
+		mp, err := volume.ParseMountSpec(spec, hostConfig.VolumeDriver)
17
+		if err != nil {
18
+			return fmt.Errorf("Unrecognised volume spec: %v", err)
19
+		}
20
+
21
+		// If the mountpoint doesn't have a name, generate one.
22
+		if len(mp.Name) == 0 {
23
+			mp.Name = stringid.GenerateNonCryptoID()
24
+		}
25
+
26
+		// Skip volumes for which we already have something mounted on that
27
+		// destination because of a --volume-from.
28
+		if container.isDestinationMounted(mp.Destination) {
29
+			continue
30
+		}
31
+
32
+		volumeDriver := hostConfig.VolumeDriver
33
+		if mp.Destination != "" && img != nil {
34
+			if _, ok := img.ContainerConfig.Volumes[mp.Destination]; ok {
35
+				// check for whether bind is not specified and then set to local
36
+				if _, ok := container.MountPoints[mp.Destination]; !ok {
37
+					volumeDriver = volume.DefaultDriverName
38
+				}
39
+			}
40
+		}
41
+
42
+		// Create the volume in the volume driver. If it doesn't exist,
43
+		// a new one will be created.
44
+		v, err := container.daemon.createVolume(mp.Name, volumeDriver, nil)
45
+		if err != nil {
46
+			return err
47
+		}
48
+
49
+		// FIXME Windows: This code block is present in the Linux version and
50
+		// allows the contents to be copied to the container FS prior to it
51
+		// being started. However, the function utilises the FollowSymLinkInScope
52
+		// path which does not cope with Windows volume-style file paths. There
53
+		// is a seperate effort to resolve this (@swernli), so this processing
54
+		// is deferred for now. A case where this would be useful is when
55
+		// a dockerfile includes a VOLUME statement, but something is created
56
+		// in that directory during the dockerfile processing. What this means
57
+		// on Windows for TP4 is that in that scenario, the contents will not
58
+		// copied, but that's (somewhat) OK as HCS will bomb out soon after
59
+		// at it doesn't support mapped directories which have contents in the
60
+		// destination path anyway.
61
+		//
62
+		// Example for repro later:
63
+		//   FROM windowsservercore
64
+		//   RUN mkdir c:\myvol
65
+		//   RUN copy c:\windows\system32\ntdll.dll c:\myvol
66
+		//   VOLUME "c:\myvol"
67
+		//
68
+		// Then
69
+		//   docker build -t vol .
70
+		//   docker run -it --rm vol cmd  <-- This is where HCS will error out.
71
+		//
72
+		//	// never attempt to copy existing content in a container FS to a shared volume
73
+		//	if v.DriverName() == volume.DefaultDriverName {
74
+		//		if err := container.copyImagePathContent(v, mp.Destination); err != nil {
75
+		//			return err
76
+		//		}
77
+		//	}
78
+
79
+		// Add it to container.MountPoints
80
+		container.addMountPointWithVolume(mp.Destination, v, mp.RW)
81
+	}
10 82
 	return nil
11 83
 }
... ...
@@ -22,6 +22,7 @@ import (
22 22
 	"github.com/docker/docker/pkg/sysinfo"
23 23
 	"github.com/docker/docker/runconfig"
24 24
 	"github.com/docker/docker/utils"
25
+	"github.com/docker/docker/volume"
25 26
 	"github.com/docker/libnetwork"
26 27
 	nwconfig "github.com/docker/libnetwork/config"
27 28
 	"github.com/docker/libnetwork/drivers/bridge"
... ...
@@ -603,10 +604,10 @@ func (daemon *Daemon) newBaseContainer(id string) Container {
603 603
 			State:        NewState(),
604 604
 			execCommands: newExecStore(),
605 605
 			root:         daemon.containerRoot(id),
606
+			MountPoints:  make(map[string]*volume.MountPoint),
606 607
 		},
607
-		MountPoints: make(map[string]*mountPoint),
608
-		Volumes:     make(map[string]string),
609
-		VolumesRW:   make(map[string]bool),
608
+		Volumes:   make(map[string]string),
609
+		VolumesRW: make(map[string]bool),
610 610
 	}
611 611
 }
612 612
 
... ...
@@ -83,7 +83,12 @@ func (d Docker) Container(id string) (*daemon.Container, error) {
83 83
 
84 84
 // Create creates a new Docker container and returns potential warnings
85 85
 func (d Docker) Create(cfg *runconfig.Config, hostCfg *runconfig.HostConfig) (*daemon.Container, []string, error) {
86
-	ccr, err := d.Daemon.ContainerCreate("", cfg, hostCfg, true)
86
+	ccr, err := d.Daemon.ContainerCreate(&daemon.ContainerCreateConfig{
87
+		Name:            "",
88
+		Config:          cfg,
89
+		HostConfig:      hostCfg,
90
+		AdjustCPUShares: true,
91
+	})
87 92
 	if err != nil {
88 93
 		return nil, nil, err
89 94
 	}
... ...
@@ -165,16 +165,8 @@ type ResourceStats struct {
165 165
 	SystemUsage uint64    `json:"system_usage"`
166 166
 }
167 167
 
168
-// Mount contains information for a mount operation.
169
-type Mount struct {
170
-	Source      string `json:"source"`
171
-	Destination string `json:"destination"`
172
-	Writable    bool   `json:"writable"`
173
-	Private     bool   `json:"private"`
174
-	Slave       bool   `json:"slave"`
175
-}
176
-
177 168
 // User contains the uid and gid representing a Unix user
169
+// TODO Windows: Factor out User
178 170
 type User struct {
179 171
 	UID int `json:"root_uid"`
180 172
 	GID int `json:"root_gid"`
... ...
@@ -18,6 +18,15 @@ import (
18 18
 	"github.com/opencontainers/runc/libcontainer/configs"
19 19
 )
20 20
 
21
+// Mount contains information for a mount operation.
22
+type Mount struct {
23
+	Source      string `json:"source"`
24
+	Destination string `json:"destination"`
25
+	Writable    bool   `json:"writable"`
26
+	Private     bool   `json:"private"`
27
+	Slave       bool   `json:"slave"`
28
+}
29
+
21 30
 // Network settings of the container
22 31
 type Network struct {
23 32
 	Mtu            int    `json:"mtu"`
... ...
@@ -2,6 +2,13 @@ package execdriver
2 2
 
3 3
 import "github.com/docker/docker/pkg/nat"
4 4
 
5
+// Mount contains information for a mount operation.
6
+type Mount struct {
7
+	Source      string `json:"source"`
8
+	Destination string `json:"destination"`
9
+	Writable    bool   `json:"writable"`
10
+}
11
+
5 12
 // Network settings of the container
6 13
 type Network struct {
7 14
 	Interface   *NetworkInterface `json:"interface"`
... ...
@@ -2,8 +2,6 @@
2 2
 
3 3
 package windows
4 4
 
5
-// Note this is alpha code for the bring up of containers on Windows.
6
-
7 5
 import (
8 6
 	"encoding/json"
9 7
 	"errors"
... ...
@@ -60,18 +58,25 @@ type device struct {
60 60
 	Settings   interface{}
61 61
 }
62 62
 
63
+type mappedDir struct {
64
+	HostPath      string
65
+	ContainerPath string
66
+	ReadOnly      bool
67
+}
68
+
63 69
 type containerInit struct {
64
-	SystemType              string   // HCS requires this to be hard-coded to "Container"
65
-	Name                    string   // Name of the container. We use the docker ID.
66
-	Owner                   string   // The management platform that created this container
67
-	IsDummy                 bool     // Used for development purposes.
68
-	VolumePath              string   // Windows volume path for scratch space
69
-	Devices                 []device // Devices used by the container
70
-	IgnoreFlushesDuringBoot bool     // Optimisation hint for container startup in Windows
71
-	LayerFolderPath         string   // Where the layer folders are located
72
-	Layers                  []layer  // List of storage layers
73
-	ProcessorWeight         int64    // CPU Shares 1..9 on Windows; or 0 is platform default.
74
-	HostName                string   // Hostname
70
+	SystemType              string      // HCS requires this to be hard-coded to "Container"
71
+	Name                    string      // Name of the container. We use the docker ID.
72
+	Owner                   string      // The management platform that created this container
73
+	IsDummy                 bool        // Used for development purposes.
74
+	VolumePath              string      // Windows volume path for scratch space
75
+	Devices                 []device    // Devices used by the container
76
+	IgnoreFlushesDuringBoot bool        // Optimisation hint for container startup in Windows
77
+	LayerFolderPath         string      // Where the layer folders are located
78
+	Layers                  []layer     // List of storage layers
79
+	ProcessorWeight         int64       // CPU Shares 1..9 on Windows; or 0 is platform default.
80
+	HostName                string      // Hostname
81
+	MappedDirectories       []mappedDir // List of mapped directories (volumes/mounts)
75 82
 }
76 83
 
77 84
 // defaultOwner is a tag passed to HCS to allow it to differentiate between
... ...
@@ -105,18 +110,28 @@ func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execd
105 105
 		HostName:                c.Hostname,
106 106
 	}
107 107
 
108
-	for i := 0; i < len(c.LayerPaths); i++ {
109
-		_, filename := filepath.Split(c.LayerPaths[i])
108
+	for _, layerPath := range c.LayerPaths {
109
+		_, filename := filepath.Split(layerPath)
110 110
 		g, err := hcsshim.NameToGuid(filename)
111 111
 		if err != nil {
112 112
 			return execdriver.ExitStatus{ExitCode: -1}, err
113 113
 		}
114 114
 		cu.Layers = append(cu.Layers, layer{
115 115
 			ID:   g.ToString(),
116
-			Path: c.LayerPaths[i],
116
+			Path: layerPath,
117 117
 		})
118 118
 	}
119 119
 
120
+	// Add the mounts (volumes, bind mounts etc) to the structure
121
+	mds := make([]mappedDir, len(c.Mounts))
122
+	for i, mount := range c.Mounts {
123
+		mds[i] = mappedDir{
124
+			HostPath:      mount.Source,
125
+			ContainerPath: mount.Destination,
126
+			ReadOnly:      !mount.Writable}
127
+	}
128
+	cu.MappedDirectories = mds
129
+
120 130
 	// TODO Windows. At some point, when there is CLI on docker run to
121 131
 	// enable the IP Address of the container to be passed into docker run,
122 132
 	// the IP Address needs to be wired through to HCS in the JSON. It
... ...
@@ -8,7 +8,17 @@ func setPlatformSpecificContainerFields(container *Container, contJSONBase *type
8 8
 }
9 9
 
10 10
 func addMountPoints(container *Container) []types.MountPoint {
11
-	return nil
11
+	mountPoints := make([]types.MountPoint, 0, len(container.MountPoints))
12
+	for _, m := range container.MountPoints {
13
+		mountPoints = append(mountPoints, types.MountPoint{
14
+			Name:        m.Name,
15
+			Source:      m.Path(),
16
+			Destination: m.Destination,
17
+			Driver:      m.Driver,
18
+			RW:          m.RW,
19
+		})
20
+	}
21
+	return mountPoints
12 22
 }
13 23
 
14 24
 // ContainerInspectPre120 get containers for pre 1.20 APIs.
... ...
@@ -2,18 +2,16 @@ package daemon
2 2
 
3 3
 import (
4 4
 	"errors"
5
-	"fmt"
6
-	"io/ioutil"
7 5
 	"os"
8 6
 	"path/filepath"
9 7
 	"strings"
10 8
 
11
-	"github.com/Sirupsen/logrus"
12 9
 	"github.com/docker/docker/api/types"
10
+	"github.com/docker/docker/daemon/execdriver"
13 11
 	derr "github.com/docker/docker/errors"
14
-	"github.com/docker/docker/pkg/chrootarchive"
15
-	"github.com/docker/docker/pkg/system"
12
+	"github.com/docker/docker/runconfig"
16 13
 	"github.com/docker/docker/volume"
14
+	"github.com/opencontainers/runc/libcontainer/label"
17 15
 )
18 16
 
19 17
 var (
... ...
@@ -22,88 +20,136 @@ var (
22 22
 	ErrVolumeReadonly = errors.New("mounted volume is marked read-only")
23 23
 )
24 24
 
25
-// mountPoint is the intersection point between a volume and a container. It
26
-// specifies which volume is to be used and where inside a container it should
27
-// be mounted.
28
-type mountPoint struct {
29
-	Name        string
30
-	Destination string
31
-	Driver      string
32
-	RW          bool
33
-	Volume      volume.Volume `json:"-"`
34
-	Source      string
35
-	Mode        string `json:"Relabel"` // Originally field was `Relabel`"
36
-}
25
+type mounts []execdriver.Mount
37 26
 
38
-// Setup sets up a mount point by either mounting the volume if it is
39
-// configured, or creating the source directory if supplied.
40
-func (m *mountPoint) Setup() (string, error) {
41
-	if m.Volume != nil {
42
-		return m.Volume.Mount()
27
+// volumeToAPIType converts a volume.Volume to the type used by the remote API
28
+func volumeToAPIType(v volume.Volume) *types.Volume {
29
+	return &types.Volume{
30
+		Name:       v.Name(),
31
+		Driver:     v.DriverName(),
32
+		Mountpoint: v.Path(),
43 33
 	}
34
+}
44 35
 
45
-	if len(m.Source) > 0 {
46
-		if _, err := os.Stat(m.Source); err != nil {
47
-			if !os.IsNotExist(err) {
48
-				return "", err
49
-			}
50
-			logrus.Warnf("Auto-creating non-existant volume host path %s, this is deprecated and will be removed soon", m.Source)
51
-			if err := system.MkdirAll(m.Source, 0755); err != nil {
52
-				return "", err
53
-			}
54
-		}
55
-		return m.Source, nil
36
+// createVolume creates a volume.
37
+func (daemon *Daemon) createVolume(name, driverName string, opts map[string]string) (volume.Volume, error) {
38
+	v, err := daemon.volumes.Create(name, driverName, opts)
39
+	if err != nil {
40
+		return nil, err
56 41
 	}
42
+	daemon.volumes.Increment(v)
43
+	return v, nil
44
+}
45
+
46
+// Len returns the number of mounts. Used in sorting.
47
+func (m mounts) Len() int {
48
+	return len(m)
49
+}
57 50
 
58
-	return "", derr.ErrorCodeMountSetup
51
+// Less returns true if the number of parts (a/b/c would be 3 parts) in the
52
+// mount indexed by parameter 1 is less than that of the mount indexed by
53
+// parameter 2. Used in sorting.
54
+func (m mounts) Less(i, j int) bool {
55
+	return m.parts(i) < m.parts(j)
59 56
 }
60 57
 
61
-// hasResource checks whether the given absolute path for a container is in
62
-// this mount point. If the relative path starts with `../` then the resource
63
-// is outside of this mount point, but we can't simply check for this prefix
64
-// because it misses `..` which is also outside of the mount, so check both.
65
-func (m *mountPoint) hasResource(absolutePath string) bool {
66
-	relPath, err := filepath.Rel(m.Destination, absolutePath)
58
+// Swap swaps two items in an array of mounts. Used in sorting
59
+func (m mounts) Swap(i, j int) {
60
+	m[i], m[j] = m[j], m[i]
61
+}
67 62
 
68
-	return err == nil && relPath != ".." && !strings.HasPrefix(relPath, fmt.Sprintf("..%c", filepath.Separator))
63
+// parts returns the number of parts in the destination of a mount. Used in sorting.
64
+func (m mounts) parts(i int) int {
65
+	return strings.Count(filepath.Clean(m[i].Destination), string(os.PathSeparator))
69 66
 }
70 67
 
71
-// Path returns the path of a volume in a mount point.
72
-func (m *mountPoint) Path() string {
73
-	if m.Volume != nil {
74
-		return m.Volume.Path()
68
+// registerMountPoints initializes the container mount points with the configured volumes and bind mounts.
69
+// It follows the next sequence to decide what to mount in each final destination:
70
+//
71
+// 1. Select the previously configured mount points for the containers, if any.
72
+// 2. Select the volumes mounted from another containers. Overrides previously configured mount point destination.
73
+// 3. Select the bind mounts set by the client. Overrides previously configured mount point destinations.
74
+func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runconfig.HostConfig) error {
75
+	binds := map[string]bool{}
76
+	mountPoints := map[string]*volume.MountPoint{}
77
+
78
+	// 1. Read already configured mount points.
79
+	for name, point := range container.MountPoints {
80
+		mountPoints[name] = point
75 81
 	}
76 82
 
77
-	return m.Source
78
-}
83
+	// 2. Read volumes from other containers.
84
+	for _, v := range hostConfig.VolumesFrom {
85
+		containerID, mode, err := volume.ParseVolumesFrom(v)
86
+		if err != nil {
87
+			return err
88
+		}
79 89
 
80
-// copyExistingContents copies from the source to the destination and
81
-// ensures the ownership is appropriately set.
82
-func copyExistingContents(source, destination string) error {
83
-	volList, err := ioutil.ReadDir(source)
84
-	if err != nil {
85
-		return err
90
+		c, err := daemon.Get(containerID)
91
+		if err != nil {
92
+			return err
93
+		}
94
+
95
+		for _, m := range c.MountPoints {
96
+			cp := &volume.MountPoint{
97
+				Name:        m.Name,
98
+				Source:      m.Source,
99
+				RW:          m.RW && volume.ReadWrite(mode),
100
+				Driver:      m.Driver,
101
+				Destination: m.Destination,
102
+			}
103
+
104
+			if len(cp.Source) == 0 {
105
+				v, err := daemon.createVolume(cp.Name, cp.Driver, nil)
106
+				if err != nil {
107
+					return err
108
+				}
109
+				cp.Volume = v
110
+			}
111
+
112
+			mountPoints[cp.Destination] = cp
113
+		}
86 114
 	}
87
-	if len(volList) > 0 {
88
-		srcList, err := ioutil.ReadDir(destination)
115
+
116
+	// 3. Read bind mounts
117
+	for _, b := range hostConfig.Binds {
118
+		// #10618
119
+		bind, err := volume.ParseMountSpec(b, hostConfig.VolumeDriver)
89 120
 		if err != nil {
90 121
 			return err
91 122
 		}
92
-		if len(srcList) == 0 {
93
-			// If the source volume is empty copy files from the root into the volume
94
-			if err := chrootarchive.CopyWithTar(source, destination); err != nil {
123
+
124
+		if binds[bind.Destination] {
125
+			return derr.ErrorCodeVolumeDup.WithArgs(bind.Destination)
126
+		}
127
+
128
+		if len(bind.Name) > 0 && len(bind.Driver) > 0 {
129
+			// create the volume
130
+			v, err := daemon.createVolume(bind.Name, bind.Driver, nil)
131
+			if err != nil {
95 132
 				return err
96 133
 			}
134
+			bind.Volume = v
135
+			bind.Source = v.Path()
136
+			// bind.Name is an already existing volume, we need to use that here
137
+			bind.Driver = v.DriverName()
138
+			bind = setBindModeIfNull(bind)
139
+		}
140
+		shared := label.IsShared(bind.Mode)
141
+		if err := label.Relabel(bind.Source, container.MountLabel, shared); err != nil {
142
+			return err
97 143
 		}
144
+		binds[bind.Destination] = true
145
+		mountPoints[bind.Destination] = bind
98 146
 	}
99
-	return copyOwnership(source, destination)
100
-}
101 147
 
102
-// volumeToAPIType converts a volume.Volume to the type used by the remote API
103
-func volumeToAPIType(v volume.Volume) *types.Volume {
104
-	return &types.Volume{
105
-		Name:       v.Name(),
106
-		Driver:     v.DriverName(),
107
-		Mountpoint: v.Path(),
108
-	}
148
+	bcVolumes, bcVolumesRW := configureBackCompatStructures(daemon, container, mountPoints)
149
+
150
+	container.Lock()
151
+	container.MountPoints = mountPoints
152
+	setBackCompatStructures(container, bcVolumes, bcVolumesRW)
153
+
154
+	container.Unlock()
155
+
156
+	return nil
109 157
 }
110 158
deleted file mode 100644
... ...
@@ -1,58 +0,0 @@
1
-// +build experimental
2
-
3
-package daemon
4
-
5
-import "testing"
6
-
7
-func TestParseBindMount(t *testing.T) {
8
-	cases := []struct {
9
-		bind      string
10
-		driver    string
11
-		expDest   string
12
-		expSource string
13
-		expName   string
14
-		expDriver string
15
-		expRW     bool
16
-		fail      bool
17
-	}{
18
-		{"/tmp:/tmp", "", "/tmp", "/tmp", "", "", true, false},
19
-		{"/tmp:/tmp:ro", "", "/tmp", "/tmp", "", "", false, false},
20
-		{"/tmp:/tmp:rw", "", "/tmp", "/tmp", "", "", true, false},
21
-		{"/tmp:/tmp:foo", "", "/tmp", "/tmp", "", "", false, true},
22
-		{"name:/tmp", "", "/tmp", "", "name", "local", true, false},
23
-		{"name:/tmp", "external", "/tmp", "", "name", "external", true, false},
24
-		{"name:/tmp:ro", "local", "/tmp", "", "name", "local", false, false},
25
-		{"local/name:/tmp:rw", "", "/tmp", "", "local/name", "local", true, false},
26
-		{"/tmp:tmp", "", "", "", "", "", true, true},
27
-	}
28
-
29
-	for _, c := range cases {
30
-		m, err := parseBindMount(c.bind, c.driver)
31
-		if c.fail {
32
-			if err == nil {
33
-				t.Fatalf("Expected error, was nil, for spec %s\n", c.bind)
34
-			}
35
-			continue
36
-		}
37
-
38
-		if m.Destination != c.expDest {
39
-			t.Fatalf("Expected destination %s, was %s, for spec %s\n", c.expDest, m.Destination, c.bind)
40
-		}
41
-
42
-		if m.Source != c.expSource {
43
-			t.Fatalf("Expected source %s, was %s, for spec %s\n", c.expSource, m.Source, c.bind)
44
-		}
45
-
46
-		if m.Name != c.expName {
47
-			t.Fatalf("Expected name %s, was %s for spec %s\n", c.expName, m.Name, c.bind)
48
-		}
49
-
50
-		if m.Driver != c.expDriver {
51
-			t.Fatalf("Expected driver %s, was %s, for spec %s\n", c.expDriver, m.Driver, c.bind)
52
-		}
53
-
54
-		if m.RW != c.expRW {
55
-			t.Fatalf("Expected RW %v, was %v for spec %s\n", c.expRW, m.RW, c.bind)
56
-		}
57
-	}
58
-}
... ...
@@ -1,6 +1,9 @@
1 1
 package daemon
2 2
 
3
-import "testing"
3
+import (
4
+	"github.com/docker/docker/volume"
5
+	"testing"
6
+)
4 7
 
5 8
 func TestParseVolumesFrom(t *testing.T) {
6 9
 	cases := []struct {
... ...
@@ -17,7 +20,7 @@ func TestParseVolumesFrom(t *testing.T) {
17 17
 	}
18 18
 
19 19
 	for _, c := range cases {
20
-		id, mode, err := parseVolumesFrom(c.spec)
20
+		id, mode, err := volume.ParseVolumesFrom(c.spec)
21 21
 		if c.fail {
22 22
 			if err == nil {
23 23
 				t.Fatalf("Expected error, was nil, for spec %s\n", c.spec)
... ...
@@ -11,15 +11,35 @@ import (
11 11
 
12 12
 	"github.com/Sirupsen/logrus"
13 13
 	"github.com/docker/docker/daemon/execdriver"
14
-	derr "github.com/docker/docker/errors"
14
+	"github.com/docker/docker/pkg/chrootarchive"
15 15
 	"github.com/docker/docker/pkg/system"
16
-	"github.com/docker/docker/runconfig"
17 16
 	"github.com/docker/docker/volume"
18 17
 	volumedrivers "github.com/docker/docker/volume/drivers"
19 18
 	"github.com/docker/docker/volume/local"
20
-	"github.com/opencontainers/runc/libcontainer/label"
21 19
 )
22 20
 
21
+// copyExistingContents copies from the source to the destination and
22
+// ensures the ownership is appropriately set.
23
+func copyExistingContents(source, destination string) error {
24
+	volList, err := ioutil.ReadDir(source)
25
+	if err != nil {
26
+		return err
27
+	}
28
+	if len(volList) > 0 {
29
+		srcList, err := ioutil.ReadDir(destination)
30
+		if err != nil {
31
+			return err
32
+		}
33
+		if len(srcList) == 0 {
34
+			// If the source volume is empty copy files from the root into the volume
35
+			if err := chrootarchive.CopyWithTar(source, destination); err != nil {
36
+				return err
37
+			}
38
+		}
39
+	}
40
+	return copyOwnership(source, destination)
41
+}
42
+
23 43
 // copyOwnership copies the permissions and uid:gid of the source file
24 44
 // to the destination file
25 45
 func copyOwnership(source, destination string) error {
... ...
@@ -68,53 +88,6 @@ func (container *Container) setupMounts() ([]execdriver.Mount, error) {
68 68
 	return append(mounts, netMounts...), nil
69 69
 }
70 70
 
71
-// parseBindMount validates the configuration of mount information in runconfig is valid.
72
-func parseBindMount(spec, volumeDriver string) (*mountPoint, error) {
73
-	bind := &mountPoint{
74
-		RW: true,
75
-	}
76
-	arr := strings.Split(spec, ":")
77
-
78
-	switch len(arr) {
79
-	case 2:
80
-		bind.Destination = arr[1]
81
-	case 3:
82
-		bind.Destination = arr[1]
83
-		mode := arr[2]
84
-		if !volume.ValidMountMode(mode) {
85
-			return nil, derr.ErrorCodeVolumeInvalidMode.WithArgs(mode)
86
-		}
87
-		bind.RW = volume.ReadWrite(mode)
88
-		// Mode field is used by SELinux to decide whether to apply label
89
-		bind.Mode = mode
90
-	default:
91
-		return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec)
92
-	}
93
-
94
-	//validate the volumes destination path
95
-	if !filepath.IsAbs(bind.Destination) {
96
-		return nil, derr.ErrorCodeVolumeAbs.WithArgs(bind.Destination)
97
-	}
98
-
99
-	name, source, err := parseVolumeSource(arr[0])
100
-	if err != nil {
101
-		return nil, err
102
-	}
103
-
104
-	if len(source) == 0 {
105
-		bind.Driver = volumeDriver
106
-		if len(bind.Driver) == 0 {
107
-			bind.Driver = volume.DefaultDriverName
108
-		}
109
-	} else {
110
-		bind.Source = filepath.Clean(source)
111
-	}
112
-
113
-	bind.Name = name
114
-	bind.Destination = filepath.Clean(bind.Destination)
115
-	return bind, nil
116
-}
117
-
118 71
 // sortMounts sorts an array of mounts in lexicographic order. This ensure that
119 72
 // when mounting, the mounts don't shadow other mounts. For example, if mounting
120 73
 // /etc and /etc/resolv.conf, /etc/resolv.conf must not be mounted first.
... ...
@@ -123,30 +96,6 @@ func sortMounts(m []execdriver.Mount) []execdriver.Mount {
123 123
 	return m
124 124
 }
125 125
 
126
-type mounts []execdriver.Mount
127
-
128
-// Len returns the number of mounts
129
-func (m mounts) Len() int {
130
-	return len(m)
131
-}
132
-
133
-// Less returns true if the number of parts (a/b/c would be 3 parts) in the
134
-// mount indexed by parameter 1 is less than that of the mount indexed by
135
-// parameter 2.
136
-func (m mounts) Less(i, j int) bool {
137
-	return m.parts(i) < m.parts(j)
138
-}
139
-
140
-// Swap swaps two items in an array of mounts.
141
-func (m mounts) Swap(i, j int) {
142
-	m[i], m[j] = m[j], m[i]
143
-}
144
-
145
-// parts returns the number of parts in the destination of a mount.
146
-func (m mounts) parts(i int) int {
147
-	return len(strings.Split(filepath.Clean(m[i].Destination), string(os.PathSeparator)))
148
-}
149
-
150 126
 // migrateVolume links the contents of a volume created pre Docker 1.7
151 127
 // into the location expected by the local driver.
152 128
 // It creates a symlink from DOCKER_ROOT/vfs/dir/VOLUME_ID to DOCKER_ROOT/volumes/VOLUME_ID/_container_data.
... ...
@@ -211,12 +160,7 @@ func (daemon *Daemon) verifyVolumesInfo(container *Container) error {
211 211
 				}
212 212
 				container.addLocalMountPoint(id, destination, rw)
213 213
 			} else { // Bind mount
214
-				id, source, err := parseVolumeSource(hostPath)
215
-				// We should not find an error here coming
216
-				// from the old configuration, but who knows.
217
-				if err != nil {
218
-					return err
219
-				}
214
+				id, source := volume.ParseVolumeSource(hostPath)
220 215
 				container.addBindMountPoint(id, source, destination, rw)
221 216
 			}
222 217
 		}
... ...
@@ -270,109 +214,19 @@ func (daemon *Daemon) verifyVolumesInfo(container *Container) error {
270 270
 	return nil
271 271
 }
272 272
 
273
-// parseVolumesFrom ensure that the supplied volumes-from is valid.
274
-func parseVolumesFrom(spec string) (string, string, error) {
275
-	if len(spec) == 0 {
276
-		return "", "", derr.ErrorCodeVolumeFromBlank.WithArgs(spec)
273
+// setBindModeIfNull is platform specific processing to ensure the
274
+// shared mode is set to 'z' if it is null. This is called in the case
275
+// of processing a named volume and not a typical bind.
276
+func setBindModeIfNull(bind *volume.MountPoint) *volume.MountPoint {
277
+	if bind.Mode == "" {
278
+		bind.Mode = "z"
277 279
 	}
278
-
279
-	specParts := strings.SplitN(spec, ":", 2)
280
-	id := specParts[0]
281
-	mode := "rw"
282
-
283
-	if len(specParts) == 2 {
284
-		mode = specParts[1]
285
-		if !volume.ValidMountMode(mode) {
286
-			return "", "", derr.ErrorCodeVolumeMode.WithArgs(mode)
287
-		}
288
-	}
289
-	return id, mode, nil
280
+	return bind
290 281
 }
291 282
 
292
-// registerMountPoints initializes the container mount points with the configured volumes and bind mounts.
293
-// It follows the next sequence to decide what to mount in each final destination:
294
-//
295
-// 1. Select the previously configured mount points for the containers, if any.
296
-// 2. Select the volumes mounted from another containers. Overrides previously configured mount point destination.
297
-// 3. Select the bind mounts set by the client. Overrides previously configured mount point destinations.
298
-func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runconfig.HostConfig) error {
299
-	binds := map[string]bool{}
300
-	mountPoints := map[string]*mountPoint{}
301
-
302
-	// 1. Read already configured mount points.
303
-	for name, point := range container.MountPoints {
304
-		mountPoints[name] = point
305
-	}
306
-
307
-	// 2. Read volumes from other containers.
308
-	for _, v := range hostConfig.VolumesFrom {
309
-		containerID, mode, err := parseVolumesFrom(v)
310
-		if err != nil {
311
-			return err
312
-		}
313
-
314
-		c, err := daemon.Get(containerID)
315
-		if err != nil {
316
-			return err
317
-		}
318
-
319
-		for _, m := range c.MountPoints {
320
-			cp := &mountPoint{
321
-				Name:        m.Name,
322
-				Source:      m.Source,
323
-				RW:          m.RW && volume.ReadWrite(mode),
324
-				Driver:      m.Driver,
325
-				Destination: m.Destination,
326
-			}
327
-
328
-			if len(cp.Source) == 0 {
329
-				v, err := daemon.createVolume(cp.Name, cp.Driver, nil)
330
-				if err != nil {
331
-					return err
332
-				}
333
-				cp.Volume = v
334
-			}
335
-
336
-			mountPoints[cp.Destination] = cp
337
-		}
338
-	}
339
-
340
-	// 3. Read bind mounts
341
-	for _, b := range hostConfig.Binds {
342
-		// #10618
343
-		bind, err := parseBindMount(b, hostConfig.VolumeDriver)
344
-		if err != nil {
345
-			return err
346
-		}
347
-
348
-		if binds[bind.Destination] {
349
-			return derr.ErrorCodeVolumeDup.WithArgs(bind.Destination)
350
-		}
351
-
352
-		if len(bind.Name) > 0 && len(bind.Driver) > 0 {
353
-			// create the volume
354
-			v, err := daemon.createVolume(bind.Name, bind.Driver, nil)
355
-			if err != nil {
356
-				return err
357
-			}
358
-			bind.Volume = v
359
-			bind.Source = v.Path()
360
-			// bind.Name is an already existing volume, we need to use that here
361
-			bind.Driver = v.DriverName()
362
-			// Since this is just a named volume and not a typical bind, set to shared mode `z`
363
-			if bind.Mode == "" {
364
-				bind.Mode = "z"
365
-			}
366
-		}
367
-
368
-		shared := label.IsShared(bind.Mode)
369
-		if err := label.Relabel(bind.Source, container.MountLabel, shared); err != nil {
370
-			return err
371
-		}
372
-		binds[bind.Destination] = true
373
-		mountPoints[bind.Destination] = bind
374
-	}
375
-
283
+// configureBackCompatStructures is platform specific processing for
284
+// registering mount points to populate old structures.
285
+func configureBackCompatStructures(daemon *Daemon, container *Container, mountPoints map[string]*volume.MountPoint) (map[string]string, map[string]bool) {
376 286
 	// Keep backwards compatible structures
377 287
 	bcVolumes := map[string]string{}
378 288
 	bcVolumesRW := map[string]bool{}
... ...
@@ -387,38 +241,12 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc
387 387
 			}
388 388
 		}
389 389
 	}
390
+	return bcVolumes, bcVolumesRW
391
+}
390 392
 
391
-	container.Lock()
392
-	container.MountPoints = mountPoints
393
+// setBackCompatStructures is a platform specific helper function to set
394
+// backwards compatible structures in the container when registering volumes.
395
+func setBackCompatStructures(container *Container, bcVolumes map[string]string, bcVolumesRW map[string]bool) {
393 396
 	container.Volumes = bcVolumes
394 397
 	container.VolumesRW = bcVolumesRW
395
-	container.Unlock()
396
-
397
-	return nil
398
-}
399
-
400
-// createVolume creates a volume.
401
-func (daemon *Daemon) createVolume(name, driverName string, opts map[string]string) (volume.Volume, error) {
402
-	v, err := daemon.volumes.Create(name, driverName, opts)
403
-	if err != nil {
404
-		return nil, err
405
-	}
406
-	daemon.volumes.Increment(v)
407
-	return v, nil
408
-}
409
-
410
-// parseVolumeSource parses the origin sources that's mounted into the container.
411
-func parseVolumeSource(spec string) (string, string, error) {
412
-	if !filepath.IsAbs(spec) {
413
-		return spec, "", nil
414
-	}
415
-
416
-	return "", spec, nil
417
-}
418
-
419
-// BackwardsCompatible decides whether this mount point can be
420
-// used in old versions of Docker or not.
421
-// Only bind mounts and local volumes can be used in old versions of Docker.
422
-func (m *mountPoint) BackwardsCompatible() bool {
423
-	return len(m.Source) > 0 || m.Driver == volume.DefaultDriverName
424 398
 }
... ...
@@ -4,22 +4,35 @@ package daemon
4 4
 
5 5
 import (
6 6
 	"github.com/docker/docker/daemon/execdriver"
7
-	"github.com/docker/docker/runconfig"
7
+	derr "github.com/docker/docker/errors"
8
+	"github.com/docker/docker/volume"
9
+	"sort"
8 10
 )
9 11
 
10
-// copyOwnership copies the permissions and group of a source file to the
11
-// destination file. This is a no-op on Windows.
12
-func copyOwnership(source, destination string) error {
13
-	return nil
14
-}
15
-
16
-// setupMounts configures the mount points for a container.
17
-// setupMounts on Linux iterates through each of the mount points for a
18
-// container and calls Setup() on each. It also looks to see if is a network
19
-// mount such as /etc/resolv.conf, and if it is not, appends it to the array
20
-// of mounts. As Windows does not support mount points, this is a no-op.
12
+// setupMounts configures the mount points for a container by appending each
13
+// of the configured mounts on the container to the execdriver mount structure
14
+// which will ultimately be passed into the exec driver during container creation.
15
+// It also ensures each of the mounts are lexographically sorted.
21 16
 func (container *Container) setupMounts() ([]execdriver.Mount, error) {
22
-	return nil, nil
17
+	var mnts []execdriver.Mount
18
+	for _, mount := range container.MountPoints { // type is volume.MountPoint
19
+		// If there is no source, take it from the volume path
20
+		s := mount.Source
21
+		if s == "" && mount.Volume != nil {
22
+			s = mount.Volume.Path()
23
+		}
24
+		if s == "" {
25
+			return nil, derr.ErrorCodeVolumeNoSourceForMount.WithArgs(mount.Name, mount.Driver, mount.Destination)
26
+		}
27
+		mnts = append(mnts, execdriver.Mount{
28
+			Source:      s,
29
+			Destination: mount.Destination,
30
+			Writable:    mount.RW,
31
+		})
32
+	}
33
+
34
+	sort.Sort(mounts(mnts))
35
+	return mnts, nil
23 36
 }
24 37
 
25 38
 // verifyVolumesInfo ports volumes configured for the containers pre docker 1.7.
... ...
@@ -28,9 +41,20 @@ func (daemon *Daemon) verifyVolumesInfo(container *Container) error {
28 28
 	return nil
29 29
 }
30 30
 
31
-// registerMountPoints initializes the container mount points with the
32
-// configured volumes and bind mounts. Windows does not support volumes or
33
-// mount points.
34
-func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runconfig.HostConfig) error {
35
-	return nil
31
+// setBindModeIfNull is platform specific processing which is a no-op on
32
+// Windows.
33
+func setBindModeIfNull(bind *volume.MountPoint) *volume.MountPoint {
34
+	return bind
35
+}
36
+
37
+// configureBackCompatStructures is platform specific processing for
38
+// registering mount points to populate old structures. This is a no-op on Windows.
39
+func configureBackCompatStructures(*Daemon, *Container, map[string]*volume.MountPoint) (map[string]string, map[string]bool) {
40
+	return nil, nil
41
+}
42
+
43
+// setBackCompatStructures is a platform specific helper function to set
44
+// backwards compatible structures in the container when registering volumes.
45
+// This is a no-op on Windows.
46
+func setBackCompatStructures(*Container, map[string]string, map[string]bool) {
36 47
 }
... ...
@@ -359,12 +359,12 @@ var (
359 359
 		HTTPStatusCode: http.StatusInternalServerError,
360 360
 	})
361 361
 
362
-	// ErrorCodeVolumeInvalidMode is generated when we the mode of a volume
362
+	// ErrorCodeVolumeInvalidMode is generated when we the mode of a volume/bind
363 363
 	// mount is invalid.
364 364
 	ErrorCodeVolumeInvalidMode = errcode.Register(errGroup, errcode.ErrorDescriptor{
365 365
 		Value:          "VOLUMEINVALIDMODE",
366
-		Message:        "invalid mode for volumes-from: %s",
367
-		Description:    "An invalid 'mode' was specified in the mount request",
366
+		Message:        "invalid mode: %s",
367
+		Description:    "An invalid 'mode' was specified",
368 368
 		HTTPStatusCode: http.StatusInternalServerError,
369 369
 	})
370 370
 
... ...
@@ -393,6 +393,41 @@ var (
393 393
 		HTTPStatusCode: http.StatusBadRequest,
394 394
 	})
395 395
 
396
+	// ErrorCodeVolumeSlash is generated when destination path to a volume is /
397
+	ErrorCodeVolumeSlash = errcode.Register(errGroup, errcode.ErrorDescriptor{
398
+		Value:          "VOLUMESLASH",
399
+		Message:        "Invalid specification: destination can't be '/' in '%s'",
400
+		HTTPStatusCode: http.StatusInternalServerError,
401
+	})
402
+
403
+	// ErrorCodeVolumeDestIsC is generated the destination is c: (Windows specific)
404
+	ErrorCodeVolumeDestIsC = errcode.Register(errGroup, errcode.ErrorDescriptor{
405
+		Value:          "VOLUMEDESTISC",
406
+		Message:        "Destination drive letter in '%s' cannot be c:",
407
+		HTTPStatusCode: http.StatusInternalServerError,
408
+	})
409
+
410
+	// ErrorCodeVolumeDestIsCRoot is generated the destination path is c:\ (Windows specific)
411
+	ErrorCodeVolumeDestIsCRoot = errcode.Register(errGroup, errcode.ErrorDescriptor{
412
+		Value:          "VOLUMEDESTISCROOT",
413
+		Message:        `Destination path in '%s' cannot be c:\`,
414
+		HTTPStatusCode: http.StatusInternalServerError,
415
+	})
416
+
417
+	// ErrorCodeVolumeSourceNotFound is generated the source directory could not be found (Windows specific)
418
+	ErrorCodeVolumeSourceNotFound = errcode.Register(errGroup, errcode.ErrorDescriptor{
419
+		Value:          "VOLUMESOURCENOTFOUND",
420
+		Message:        "Source directory '%s' could not be found: %v",
421
+		HTTPStatusCode: http.StatusInternalServerError,
422
+	})
423
+
424
+	// ErrorCodeVolumeSourceNotDirectory is generated the source is not a directory (Windows specific)
425
+	ErrorCodeVolumeSourceNotDirectory = errcode.Register(errGroup, errcode.ErrorDescriptor{
426
+		Value:          "VOLUMESOURCENOTDIRECTORY",
427
+		Message:        "Source '%s' is not a directory",
428
+		HTTPStatusCode: http.StatusInternalServerError,
429
+	})
430
+
396 431
 	// ErrorCodeVolumeFromBlank is generated when path to a volume is blank.
397 432
 	ErrorCodeVolumeFromBlank = errcode.Register(errGroup, errcode.ErrorDescriptor{
398 433
 		Value:          "VOLUMEFROMBLANK",
... ...
@@ -401,15 +436,6 @@ var (
401 401
 		HTTPStatusCode: http.StatusInternalServerError,
402 402
 	})
403 403
 
404
-	// ErrorCodeVolumeMode is generated when 'mode' for a volume
405
-	// isn't a valid.
406
-	ErrorCodeVolumeMode = errcode.Register(errGroup, errcode.ErrorDescriptor{
407
-		Value:          "VOLUMEMODE",
408
-		Message:        "invalid mode for volumes-from: %s",
409
-		Description:    "An invalid 'mode' path was specified in the mount request",
410
-		HTTPStatusCode: http.StatusInternalServerError,
411
-	})
412
-
413 404
 	// ErrorCodeVolumeDup is generated when we try to mount two volumes
414 405
 	// to the same path.
415 406
 	ErrorCodeVolumeDup = errcode.Register(errGroup, errcode.ErrorDescriptor{
... ...
@@ -419,6 +445,22 @@ var (
419 419
 		HTTPStatusCode: http.StatusInternalServerError,
420 420
 	})
421 421
 
422
+	// ErrorCodeVolumeNoSourceForMount is generated when no source directory
423
+	// for a volume mount was found. (Windows specific)
424
+	ErrorCodeVolumeNoSourceForMount = errcode.Register(errGroup, errcode.ErrorDescriptor{
425
+		Value:          "VOLUMENOSOURCEFORMOUNT",
426
+		Message:        "No source for mount name %q driver %q destination %s",
427
+		HTTPStatusCode: http.StatusInternalServerError,
428
+	})
429
+
430
+	// ErrorCodeVolumeNameReservedWord is generated when the name in a volume
431
+	// uses a reserved word for filenames. (Windows specific)
432
+	ErrorCodeVolumeNameReservedWord = errcode.Register(errGroup, errcode.ErrorDescriptor{
433
+		Value:          "VOLUMENAMERESERVEDWORD",
434
+		Message:        "Volume name %q cannot be a reserved word for Windows filenames",
435
+		HTTPStatusCode: http.StatusInternalServerError,
436
+	})
437
+
422 438
 	// ErrorCodeCantUnpause is generated when there's an error while trying
423 439
 	// to unpause a container.
424 440
 	ErrorCodeCantUnpause = errcode.Register(errGroup, errcode.ErrorDescriptor{
... ...
@@ -1 +1,2 @@
1 1
 {"architecture":"amd64","config":{"Hostname":"03797203757d","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","GOLANG_VERSION=1.4.1","GOPATH=/go"],"Cmd":null,"Image":"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02","Volumes":null,"WorkingDir":"/go","Entrypoint":["/go/bin/dnsdock"],"OnBuild":[],"Labels":{}},"container":"d91be3479d5b1e84b0c00d18eea9dc777ca0ad166d51174b24283e2e6f104253","container_config":{"Hostname":"03797203757d","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","GOLANG_VERSION=1.4.1","GOPATH=/go"],"Cmd":["/bin/sh","-c","#(nop) ENTRYPOINT [\"/go/bin/dnsdock\"]"],"Image":"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02","Volumes":null,"WorkingDir":"/go","Entrypoint":["/go/bin/dnsdock"],"OnBuild":[],"Labels":{}},"created":"2015-08-19T16:49:11.368300679Z","docker_version":"1.6.2","layer_id":"sha256:31176893850e05d308cdbfef88877e460d50c8063883fb13eb5753097da6422a","os":"linux","parent_id":"sha256:ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02"}
2
+
... ...
@@ -293,8 +293,8 @@ func (s *DockerSuite) TestRunVolumesFromInReadWriteMode(c *check.C) {
293 293
 	dockerCmd(c, "run", "--name", "parent", "-v", "/test", "busybox", "true")
294 294
 	dockerCmd(c, "run", "--volumes-from", "parent:rw", "busybox", "touch", "/test/file")
295 295
 
296
-	if out, _, err := dockerCmdWithError("run", "--volumes-from", "parent:bar", "busybox", "touch", "/test/file"); err == nil || !strings.Contains(out, "invalid mode for volumes-from: bar") {
297
-		c.Fatalf("running --volumes-from foo:bar should have failed with invalid mount mode: %q", out)
296
+	if out, _, err := dockerCmdWithError("run", "--volumes-from", "parent:bar", "busybox", "touch", "/test/file"); err == nil || !strings.Contains(out, "invalid mode: bar") {
297
+		c.Fatalf("running --volumes-from foo:bar should have failed with invalid mode: %q", out)
298 298
 	}
299 299
 
300 300
 	dockerCmd(c, "run", "--volumes-from", "parent", "busybox", "touch", "/test/file")
... ...
@@ -9,7 +9,6 @@ import (
9 9
 	"strings"
10 10
 
11 11
 	"github.com/docker/docker/pkg/parsers"
12
-	"github.com/docker/docker/volume"
13 12
 )
14 13
 
15 14
 var (
... ...
@@ -214,14 +213,6 @@ func ValidateDevice(val string) (string, error) {
214 214
 	return validatePath(val, ValidDeviceMode)
215 215
 }
216 216
 
217
-// ValidatePath validates a path for volumes
218
-// It will make sure 'val' is in the form:
219
-//    [host-dir:]container-path[:rw|ro]
220
-// It also validates the mount mode.
221
-func ValidatePath(val string) (string, error) {
222
-	return validatePath(val, volume.ValidMountMode)
223
-}
224
-
225 217
 func validatePath(val string, validator func(string) bool) (string, error) {
226 218
 	var containerPath string
227 219
 	var mode string
... ...
@@ -274,58 +274,6 @@ func TestValidateLink(t *testing.T) {
274 274
 	}
275 275
 }
276 276
 
277
-func TestValidatePath(t *testing.T) {
278
-	valid := []string{
279
-		"/home",
280
-		"/home:/home",
281
-		"/home:/something/else",
282
-		"/with space",
283
-		"/home:/with space",
284
-		"relative:/absolute-path",
285
-		"hostPath:/containerPath:ro",
286
-		"/hostPath:/containerPath:rw",
287
-		"/rw:/ro",
288
-		"/path:rw",
289
-		"/path:ro",
290
-		"/rw:rw",
291
-	}
292
-	invalid := map[string]string{
293
-		"":                "bad format for path: ",
294
-		"./":              "./ is not an absolute path",
295
-		"../":             "../ is not an absolute path",
296
-		"/:../":           "../ is not an absolute path",
297
-		"/:path":          "path is not an absolute path",
298
-		":":               "bad format for path: :",
299
-		"/tmp:":           " is not an absolute path",
300
-		":test":           "bad format for path: :test",
301
-		":/test":          "bad format for path: :/test",
302
-		"tmp:":            " is not an absolute path",
303
-		":test:":          "bad format for path: :test:",
304
-		"::":              "bad format for path: ::",
305
-		":::":             "bad format for path: :::",
306
-		"/tmp:::":         "bad format for path: /tmp:::",
307
-		":/tmp::":         "bad format for path: :/tmp::",
308
-		"path:ro":         "path is not an absolute path",
309
-		"/path:/path:sw":  "bad mode specified: sw",
310
-		"/path:/path:rwz": "bad mode specified: rwz",
311
-	}
312
-
313
-	for _, path := range valid {
314
-		if _, err := ValidatePath(path); err != nil {
315
-			t.Fatalf("ValidatePath(`%q`) should succeed: error %q", path, err)
316
-		}
317
-	}
318
-
319
-	for path, expectedError := range invalid {
320
-		if _, err := ValidatePath(path); err == nil {
321
-			t.Fatalf("ValidatePath(`%q`) should have failed validation", path)
322
-		} else {
323
-			if err.Error() != expectedError {
324
-				t.Fatalf("ValidatePath(`%q`) error should contain %q, got %q", path, expectedError, err.Error())
325
-			}
326
-		}
327
-	}
328
-}
329 277
 func TestValidateDevice(t *testing.T) {
330 278
 	valid := []string{
331 279
 		"/home",
332 280
new file mode 100644
... ...
@@ -0,0 +1,11 @@
0
+// +build linux freebsd
1
+
2
+package system
3
+
4
+import "syscall"
5
+
6
+// UnmountWithSyscall is a platform-specific helper function to call
7
+// the unmount syscall.
8
+func UnmountWithSyscall(dest string) {
9
+	syscall.Unmount(dest, 0)
10
+}
0 11
new file mode 100644
... ...
@@ -0,0 +1,6 @@
0
+package system
1
+
2
+// UnmountWithSyscall is a platform-specific helper function to call
3
+// the unmount syscall. Not supported on Windows
4
+func UnmountWithSyscall(dest string) {
5
+}
... ...
@@ -2,10 +2,12 @@ package runconfig
2 2
 
3 3
 import (
4 4
 	"encoding/json"
5
+	"fmt"
5 6
 	"io"
6 7
 
7 8
 	"github.com/docker/docker/pkg/nat"
8 9
 	"github.com/docker/docker/pkg/stringutils"
10
+	"github.com/docker/docker/volume"
9 11
 )
10 12
 
11 13
 // Config contains the configuration data about a container.
... ...
@@ -44,15 +46,29 @@ type Config struct {
44 44
 // Be aware this function is not checking whether the resulted structs are nil,
45 45
 // it's your business to do so
46 46
 func DecodeContainerConfig(src io.Reader) (*Config, *HostConfig, error) {
47
-	decoder := json.NewDecoder(src)
48
-
49 47
 	var w ContainerConfigWrapper
48
+
49
+	decoder := json.NewDecoder(src)
50 50
 	if err := decoder.Decode(&w); err != nil {
51 51
 		return nil, nil, err
52 52
 	}
53 53
 
54 54
 	hc := w.getHostConfig()
55 55
 
56
+	// Perform platform-specific processing of Volumes and Binds.
57
+	if w.Config != nil && hc != nil {
58
+
59
+		// Initialise the volumes map if currently nil
60
+		if w.Config.Volumes == nil {
61
+			w.Config.Volumes = make(map[string]struct{})
62
+		}
63
+
64
+		// Now validate all the volumes and binds
65
+		if err := validateVolumesAndBindSettings(w.Config, hc); err != nil {
66
+			return nil, nil, err
67
+		}
68
+	}
69
+
56 70
 	// Certain parameters need daemon-side validation that cannot be done
57 71
 	// on the client, as only the daemon knows what is valid for the platform.
58 72
 	if err := ValidateNetMode(w.Config, hc); err != nil {
... ...
@@ -61,3 +77,22 @@ func DecodeContainerConfig(src io.Reader) (*Config, *HostConfig, error) {
61 61
 
62 62
 	return w.Config, hc, nil
63 63
 }
64
+
65
+// validateVolumesAndBindSettings validates each of the volumes and bind settings
66
+// passed by the caller to ensure they are valid.
67
+func validateVolumesAndBindSettings(c *Config, hc *HostConfig) error {
68
+
69
+	// Ensure all volumes and binds are valid.
70
+	for spec := range c.Volumes {
71
+		if _, err := volume.ParseMountSpec(spec, hc.VolumeDriver); err != nil {
72
+			return fmt.Errorf("Invalid volume spec %q: %v", spec, err)
73
+		}
74
+	}
75
+	for _, spec := range hc.Binds {
76
+		if _, err := volume.ParseMountSpec(spec, hc.VolumeDriver); err != nil {
77
+			return fmt.Errorf("Invalid bind mount spec %q: %v", spec, err)
78
+		}
79
+	}
80
+
81
+	return nil
82
+}
... ...
@@ -4,19 +4,36 @@ import (
4 4
 	"bytes"
5 5
 	"fmt"
6 6
 	"io/ioutil"
7
+	"runtime"
7 8
 	"testing"
8 9
 
9 10
 	"github.com/docker/docker/pkg/stringutils"
10 11
 )
11 12
 
13
+type f struct {
14
+	file       string
15
+	entrypoint *stringutils.StrSlice
16
+}
17
+
12 18
 func TestDecodeContainerConfig(t *testing.T) {
13
-	fixtures := []struct {
14
-		file       string
15
-		entrypoint *stringutils.StrSlice
16
-	}{
17
-		{"fixtures/container_config_1_14.json", stringutils.NewStrSlice()},
18
-		{"fixtures/container_config_1_17.json", stringutils.NewStrSlice("bash")},
19
-		{"fixtures/container_config_1_19.json", stringutils.NewStrSlice("bash")},
19
+
20
+	var (
21
+		fixtures []f
22
+		image    string
23
+	)
24
+
25
+	if runtime.GOOS != "windows" {
26
+		image = "ubuntu"
27
+		fixtures = []f{
28
+			{"fixtures/unix/container_config_1_14.json", stringutils.NewStrSlice()},
29
+			{"fixtures/unix/container_config_1_17.json", stringutils.NewStrSlice("bash")},
30
+			{"fixtures/unix/container_config_1_19.json", stringutils.NewStrSlice("bash")},
31
+		}
32
+	} else {
33
+		image = "windows"
34
+		fixtures = []f{
35
+			{"fixtures/windows/container_config_1_19.json", stringutils.NewStrSlice("cmd")},
36
+		}
20 37
 	}
21 38
 
22 39
 	for _, f := range fixtures {
... ...
@@ -30,15 +47,15 @@ func TestDecodeContainerConfig(t *testing.T) {
30 30
 			t.Fatal(fmt.Errorf("Error parsing %s: %v", f, err))
31 31
 		}
32 32
 
33
-		if c.Image != "ubuntu" {
34
-			t.Fatalf("Expected ubuntu image, found %s\n", c.Image)
33
+		if c.Image != image {
34
+			t.Fatalf("Expected %s image, found %s\n", image, c.Image)
35 35
 		}
36 36
 
37 37
 		if c.Entrypoint.Len() != f.entrypoint.Len() {
38 38
 			t.Fatalf("Expected %v, found %v\n", f.entrypoint, c.Entrypoint)
39 39
 		}
40 40
 
41
-		if h.Memory != 1000 {
41
+		if h != nil && h.Memory != 1000 {
42 42
 			t.Fatalf("Expected memory to be 1000, found %d\n", h.Memory)
43 43
 		}
44 44
 	}
45 45
deleted file mode 100644
... ...
@@ -1,30 +0,0 @@
1
-{
2
-     "Hostname":"",
3
-     "Domainname": "",
4
-     "User":"",
5
-     "Memory": 1000,
6
-     "MemorySwap":0,
7
-     "CpuShares": 512,
8
-     "Cpuset": "0,1",
9
-     "AttachStdin":false,
10
-     "AttachStdout":true,
11
-     "AttachStderr":true,
12
-     "PortSpecs":null,
13
-     "Tty":false,
14
-     "OpenStdin":false,
15
-     "StdinOnce":false,
16
-     "Env":null,
17
-     "Cmd":[
18
-             "bash"
19
-     ],
20
-     "Image":"ubuntu",
21
-     "Volumes":{
22
-             "/tmp": {}
23
-     },
24
-     "WorkingDir":"",
25
-     "NetworkDisabled": false,
26
-     "ExposedPorts":{
27
-             "22/tcp": {}
28
-     },
29
-     "RestartPolicy": { "Name": "always" }
30
-}
31 1
deleted file mode 100644
... ...
@@ -1,50 +0,0 @@
1
-{
2
-     "Hostname": "",
3
-     "Domainname": "",
4
-     "User": "",
5
-     "Memory": 1000,
6
-     "MemorySwap": 0,
7
-     "CpuShares": 512,
8
-     "Cpuset": "0,1",
9
-     "AttachStdin": false,
10
-     "AttachStdout": true,
11
-     "AttachStderr": true,
12
-     "Tty": false,
13
-     "OpenStdin": false,
14
-     "StdinOnce": false,
15
-     "Env": null,
16
-     "Cmd": [
17
-             "date"
18
-     ],
19
-     "Entrypoint": "bash",
20
-     "Image": "ubuntu",
21
-     "Volumes": {
22
-             "/tmp": {}
23
-     },
24
-     "WorkingDir": "",
25
-     "NetworkDisabled": false,
26
-     "MacAddress": "12:34:56:78:9a:bc",
27
-     "ExposedPorts": {
28
-             "22/tcp": {}
29
-     },
30
-     "SecurityOpt": [""],
31
-     "HostConfig": {
32
-       "Binds": ["/tmp:/tmp"],
33
-       "Links": ["redis3:redis"],
34
-       "LxcConf": {"lxc.utsname":"docker"},
35
-       "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] },
36
-       "PublishAllPorts": false,
37
-       "Privileged": false,
38
-       "ReadonlyRootfs": false,
39
-       "Dns": ["8.8.8.8"],
40
-       "DnsSearch": [""],
41
-       "DnsOptions": [""],
42
-       "ExtraHosts": null,
43
-       "VolumesFrom": ["parent", "other:ro"],
44
-       "CapAdd": ["NET_ADMIN"],
45
-       "CapDrop": ["MKNOD"],
46
-       "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
47
-       "NetworkMode": "bridge",
48
-       "Devices": []
49
-    }
50
-}
51 1
deleted file mode 100644
... ...
@@ -1,58 +0,0 @@
1
-{
2
-     "Hostname": "",
3
-     "Domainname": "",
4
-     "User": "",
5
-     "AttachStdin": false,
6
-     "AttachStdout": true,
7
-     "AttachStderr": true,
8
-     "Tty": false,
9
-     "OpenStdin": false,
10
-     "StdinOnce": false,
11
-     "Env": null,
12
-     "Cmd": [
13
-             "date"
14
-     ],
15
-     "Entrypoint": "bash",
16
-     "Image": "ubuntu",
17
-     "Labels": {
18
-             "com.example.vendor": "Acme",
19
-             "com.example.license": "GPL",
20
-             "com.example.version": "1.0"
21
-     },
22
-     "Volumes": {
23
-             "/tmp": {}
24
-     },
25
-     "WorkingDir": "",
26
-     "NetworkDisabled": false,
27
-     "MacAddress": "12:34:56:78:9a:bc",
28
-     "ExposedPorts": {
29
-             "22/tcp": {}
30
-     },
31
-     "HostConfig": {
32
-       "Binds": ["/tmp:/tmp"],
33
-       "Links": ["redis3:redis"],
34
-       "LxcConf": {"lxc.utsname":"docker"},
35
-       "Memory": 1000,
36
-       "MemorySwap": 0,
37
-       "CpuShares": 512,
38
-       "CpusetCpus": "0,1",
39
-       "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] },
40
-       "PublishAllPorts": false,
41
-       "Privileged": false,
42
-       "ReadonlyRootfs": false,
43
-       "Dns": ["8.8.8.8"],
44
-       "DnsSearch": [""],
45
-       "DnsOptions": [""],
46
-       "ExtraHosts": null,
47
-       "VolumesFrom": ["parent", "other:ro"],
48
-       "CapAdd": ["NET_ADMIN"],
49
-       "CapDrop": ["MKNOD"],
50
-       "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
51
-       "NetworkMode": "bridge",
52
-       "Devices": [],
53
-       "Ulimits": [{}],
54
-       "LogConfig": { "Type": "json-file", "Config": {} },
55
-       "SecurityOpt": [""],
56
-       "CgroupParent": ""
57
-    }
58
-}
59 1
deleted file mode 100644
... ...
@@ -1,18 +0,0 @@
1
-{
2
-    "Binds": ["/tmp:/tmp"],
3
-    "ContainerIDFile": "",
4
-    "LxcConf": [],
5
-    "Privileged": false,
6
-    "PortBindings": {
7
-        "80/tcp": [
8
-            {
9
-                "HostIp": "0.0.0.0",
10
-                "HostPort": "49153"
11
-            }
12
-        ]
13
-    },
14
-    "Links": ["/name:alias"],
15
-    "PublishAllPorts": false,
16
-    "CapAdd": ["NET_ADMIN"],
17
-    "CapDrop": ["MKNOD"]
18
-}
19 1
deleted file mode 100644
... ...
@@ -1,30 +0,0 @@
1
-{
2
-    "Binds": ["/tmp:/tmp"],
3
-    "Links": ["redis3:redis"],
4
-    "LxcConf": {"lxc.utsname":"docker"},
5
-    "Memory": 0,
6
-    "MemorySwap": 0,
7
-    "CpuShares": 512,
8
-    "CpuPeriod": 100000,
9
-    "CpusetCpus": "0,1",
10
-    "CpusetMems": "0,1",
11
-    "BlkioWeight": 300,
12
-    "OomKillDisable": false,
13
-    "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] },
14
-    "PublishAllPorts": false,
15
-    "Privileged": false,
16
-    "ReadonlyRootfs": false,
17
-    "Dns": ["8.8.8.8"],
18
-    "DnsSearch": [""],
19
-    "ExtraHosts": null,
20
-    "VolumesFrom": ["parent", "other:ro"],
21
-    "CapAdd": ["NET_ADMIN"],
22
-    "CapDrop": ["MKNOD"],
23
-    "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
24
-    "NetworkMode": "bridge",
25
-    "Devices": [],
26
-    "Ulimits": [{}],
27
-    "LogConfig": { "Type": "json-file", "Config": {} },
28
-    "SecurityOpt": [""],
29
-    "CgroupParent": ""
30
-}
31 1
new file mode 100644
... ...
@@ -0,0 +1,30 @@
0
+{
1
+     "Hostname":"",
2
+     "Domainname": "",
3
+     "User":"",
4
+     "Memory": 1000,
5
+     "MemorySwap":0,
6
+     "CpuShares": 512,
7
+     "Cpuset": "0,1",
8
+     "AttachStdin":false,
9
+     "AttachStdout":true,
10
+     "AttachStderr":true,
11
+     "PortSpecs":null,
12
+     "Tty":false,
13
+     "OpenStdin":false,
14
+     "StdinOnce":false,
15
+     "Env":null,
16
+     "Cmd":[
17
+             "bash"
18
+     ],
19
+     "Image":"ubuntu",
20
+     "Volumes":{
21
+             "/tmp": {}
22
+     },
23
+     "WorkingDir":"",
24
+     "NetworkDisabled": false,
25
+     "ExposedPorts":{
26
+             "22/tcp": {}
27
+     },
28
+     "RestartPolicy": { "Name": "always" }
29
+}
0 30
new file mode 100644
... ...
@@ -0,0 +1,50 @@
0
+{
1
+     "Hostname": "",
2
+     "Domainname": "",
3
+     "User": "",
4
+     "Memory": 1000,
5
+     "MemorySwap": 0,
6
+     "CpuShares": 512,
7
+     "Cpuset": "0,1",
8
+     "AttachStdin": false,
9
+     "AttachStdout": true,
10
+     "AttachStderr": true,
11
+     "Tty": false,
12
+     "OpenStdin": false,
13
+     "StdinOnce": false,
14
+     "Env": null,
15
+     "Cmd": [
16
+             "date"
17
+     ],
18
+     "Entrypoint": "bash",
19
+     "Image": "ubuntu",
20
+     "Volumes": {
21
+             "/tmp": {}
22
+     },
23
+     "WorkingDir": "",
24
+     "NetworkDisabled": false,
25
+     "MacAddress": "12:34:56:78:9a:bc",
26
+     "ExposedPorts": {
27
+             "22/tcp": {}
28
+     },
29
+     "SecurityOpt": [""],
30
+     "HostConfig": {
31
+       "Binds": ["/tmp:/tmp"],
32
+       "Links": ["redis3:redis"],
33
+       "LxcConf": {"lxc.utsname":"docker"},
34
+       "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] },
35
+       "PublishAllPorts": false,
36
+       "Privileged": false,
37
+       "ReadonlyRootfs": false,
38
+       "Dns": ["8.8.8.8"],
39
+       "DnsSearch": [""],
40
+       "DnsOptions": [""],
41
+       "ExtraHosts": null,
42
+       "VolumesFrom": ["parent", "other:ro"],
43
+       "CapAdd": ["NET_ADMIN"],
44
+       "CapDrop": ["MKNOD"],
45
+       "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
46
+       "NetworkMode": "bridge",
47
+       "Devices": []
48
+    }
49
+}
0 50
new file mode 100644
... ...
@@ -0,0 +1,58 @@
0
+{
1
+     "Hostname": "",
2
+     "Domainname": "",
3
+     "User": "",
4
+     "AttachStdin": false,
5
+     "AttachStdout": true,
6
+     "AttachStderr": true,
7
+     "Tty": false,
8
+     "OpenStdin": false,
9
+     "StdinOnce": false,
10
+     "Env": null,
11
+     "Cmd": [
12
+             "date"
13
+     ],
14
+     "Entrypoint": "bash",
15
+     "Image": "ubuntu",
16
+     "Labels": {
17
+             "com.example.vendor": "Acme",
18
+             "com.example.license": "GPL",
19
+             "com.example.version": "1.0"
20
+     },
21
+     "Volumes": {
22
+             "/tmp": {}
23
+     },
24
+     "WorkingDir": "",
25
+     "NetworkDisabled": false,
26
+     "MacAddress": "12:34:56:78:9a:bc",
27
+     "ExposedPorts": {
28
+             "22/tcp": {}
29
+     },
30
+     "HostConfig": {
31
+       "Binds": ["/tmp:/tmp"],
32
+       "Links": ["redis3:redis"],
33
+       "LxcConf": {"lxc.utsname":"docker"},
34
+       "Memory": 1000,
35
+       "MemorySwap": 0,
36
+       "CpuShares": 512,
37
+       "CpusetCpus": "0,1",
38
+       "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] },
39
+       "PublishAllPorts": false,
40
+       "Privileged": false,
41
+       "ReadonlyRootfs": false,
42
+       "Dns": ["8.8.8.8"],
43
+       "DnsSearch": [""],
44
+       "DnsOptions": [""],
45
+       "ExtraHosts": null,
46
+       "VolumesFrom": ["parent", "other:ro"],
47
+       "CapAdd": ["NET_ADMIN"],
48
+       "CapDrop": ["MKNOD"],
49
+       "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
50
+       "NetworkMode": "bridge",
51
+       "Devices": [],
52
+       "Ulimits": [{}],
53
+       "LogConfig": { "Type": "json-file", "Config": {} },
54
+       "SecurityOpt": [""],
55
+       "CgroupParent": ""
56
+    }
57
+}
0 58
new file mode 100644
... ...
@@ -0,0 +1,18 @@
0
+{
1
+    "Binds": ["/tmp:/tmp"],
2
+    "ContainerIDFile": "",
3
+    "LxcConf": [],
4
+    "Privileged": false,
5
+    "PortBindings": {
6
+        "80/tcp": [
7
+            {
8
+                "HostIp": "0.0.0.0",
9
+                "HostPort": "49153"
10
+            }
11
+        ]
12
+    },
13
+    "Links": ["/name:alias"],
14
+    "PublishAllPorts": false,
15
+    "CapAdd": ["NET_ADMIN"],
16
+    "CapDrop": ["MKNOD"]
17
+}
0 18
new file mode 100644
... ...
@@ -0,0 +1,30 @@
0
+{
1
+    "Binds": ["/tmp:/tmp"],
2
+    "Links": ["redis3:redis"],
3
+    "LxcConf": {"lxc.utsname":"docker"},
4
+    "Memory": 0,
5
+    "MemorySwap": 0,
6
+    "CpuShares": 512,
7
+    "CpuPeriod": 100000,
8
+    "CpusetCpus": "0,1",
9
+    "CpusetMems": "0,1",
10
+    "BlkioWeight": 300,
11
+    "OomKillDisable": false,
12
+    "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] },
13
+    "PublishAllPorts": false,
14
+    "Privileged": false,
15
+    "ReadonlyRootfs": false,
16
+    "Dns": ["8.8.8.8"],
17
+    "DnsSearch": [""],
18
+    "ExtraHosts": null,
19
+    "VolumesFrom": ["parent", "other:ro"],
20
+    "CapAdd": ["NET_ADMIN"],
21
+    "CapDrop": ["MKNOD"],
22
+    "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
23
+    "NetworkMode": "bridge",
24
+    "Devices": [],
25
+    "Ulimits": [{}],
26
+    "LogConfig": { "Type": "json-file", "Config": {} },
27
+    "SecurityOpt": [""],
28
+    "CgroupParent": ""
29
+}
0 30
new file mode 100644
... ...
@@ -0,0 +1,58 @@
0
+{
1
+     "Hostname": "",
2
+     "Domainname": "",
3
+     "User": "",
4
+     "AttachStdin": false,
5
+     "AttachStdout": true,
6
+     "AttachStderr": true,
7
+     "Tty": false,
8
+     "OpenStdin": false,
9
+     "StdinOnce": false,
10
+     "Env": null,
11
+     "Cmd": [
12
+             "date"
13
+     ],
14
+     "Entrypoint": "cmd",
15
+     "Image": "windows",
16
+     "Labels": {
17
+             "com.example.vendor": "Acme",
18
+             "com.example.license": "GPL",
19
+             "com.example.version": "1.0"
20
+     },
21
+     "Volumes": {
22
+             "c:/windows": {}
23
+     },
24
+     "WorkingDir": "",
25
+     "NetworkDisabled": false,
26
+     "MacAddress": "12:34:56:78:9a:bc",
27
+     "ExposedPorts": {
28
+             "22/tcp": {}
29
+     },
30
+     "HostConfig": {
31
+       "Binds": ["c:/windows:d:/tmp"],
32
+       "Links": ["redis3:redis"],
33
+       "LxcConf": {"lxc.utsname":"docker"},
34
+       "Memory": 1000,
35
+       "MemorySwap": 0,
36
+       "CpuShares": 512,
37
+       "CpusetCpus": "0,1",
38
+       "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] },
39
+       "PublishAllPorts": false,
40
+       "Privileged": false,
41
+       "ReadonlyRootfs": false,
42
+       "Dns": ["8.8.8.8"],
43
+       "DnsSearch": [""],
44
+       "DnsOptions": [""],
45
+       "ExtraHosts": null,
46
+       "VolumesFrom": ["parent", "other:ro"],
47
+       "CapAdd": ["NET_ADMIN"],
48
+       "CapDrop": ["MKNOD"],
49
+       "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
50
+       "NetworkMode": "default",
51
+       "Devices": [],
52
+       "Ulimits": [{}],
53
+       "LogConfig": { "Type": "json-file", "Config": {} },
54
+       "SecurityOpt": [""],
55
+       "CgroupParent": ""
56
+    }
57
+}
... ...
@@ -234,8 +234,8 @@ func TestDecodeHostConfig(t *testing.T) {
234 234
 	fixtures := []struct {
235 235
 		file string
236 236
 	}{
237
-		{"fixtures/container_hostconfig_1_14.json"},
238
-		{"fixtures/container_hostconfig_1_19.json"},
237
+		{"fixtures/unix/container_hostconfig_1_14.json"},
238
+		{"fixtures/unix/container_hostconfig_1_19.json"},
239 239
 	}
240 240
 
241 241
 	for _, f := range fixtures {
... ...
@@ -12,6 +12,7 @@ import (
12 12
 	"github.com/docker/docker/pkg/signal"
13 13
 	"github.com/docker/docker/pkg/stringutils"
14 14
 	"github.com/docker/docker/pkg/units"
15
+	"github.com/docker/docker/volume"
15 16
 )
16 17
 
17 18
 var (
... ...
@@ -46,7 +47,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
46 46
 	var (
47 47
 		// FIXME: use utils.ListOpts for attach and volumes?
48 48
 		flAttach  = opts.NewListOpts(opts.ValidateAttach)
49
-		flVolumes = opts.NewListOpts(opts.ValidatePath)
49
+		flVolumes = opts.NewListOpts(nil)
50 50
 		flLinks   = opts.NewListOpts(opts.ValidateLink)
51 51
 		flEnv     = opts.NewListOpts(opts.ValidateEnv)
52 52
 		flLabels  = opts.NewListOpts(opts.ValidateEnv)
... ...
@@ -201,16 +202,11 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
201 201
 	var binds []string
202 202
 	// add any bind targets to the list of container volumes
203 203
 	for bind := range flVolumes.GetMap() {
204
-		if arr := strings.Split(bind, ":"); len(arr) > 1 {
205
-			if arr[1] == "/" {
206
-				return nil, nil, cmd, fmt.Errorf("Invalid bind mount: destination can't be '/'")
207
-			}
204
+		if arr := volume.SplitN(bind, 2); len(arr) > 1 {
208 205
 			// after creating the bind mount we want to delete it from the flVolumes values because
209 206
 			// we do not want bind mounts being committed to image configs
210 207
 			binds = append(binds, bind)
211 208
 			flVolumes.Delete(bind)
212
-		} else if bind == "/" {
213
-			return nil, nil, cmd, fmt.Errorf("Invalid volume: path can't be '/'")
214 209
 		}
215 210
 	}
216 211
 
... ...
@@ -1,8 +1,12 @@
1 1
 package runconfig
2 2
 
3 3
 import (
4
+	"bytes"
5
+	"encoding/json"
4 6
 	"fmt"
5 7
 	"io/ioutil"
8
+	"os"
9
+	"runtime"
6 10
 	"strings"
7 11
 	"testing"
8 12
 
... ...
@@ -31,17 +35,6 @@ func mustParse(t *testing.T, args string) (*Config, *HostConfig) {
31 31
 	return config, hostConfig
32 32
 }
33 33
 
34
-// check if (a == c && b == d) || (a == d && b == c)
35
-// because maps are randomized
36
-func compareRandomizedStrings(a, b, c, d string) error {
37
-	if a == c && b == d {
38
-		return nil
39
-	}
40
-	if a == d && b == c {
41
-		return nil
42
-	}
43
-	return fmt.Errorf("strings don't match")
44
-}
45 34
 func TestParseRunLinks(t *testing.T) {
46 35
 	if _, hostConfig := mustParse(t, "--link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" {
47 36
 		t.Fatalf("Error parsing links. Expected []string{\"a:b\"}, received: %v", hostConfig.Links)
... ...
@@ -98,81 +91,257 @@ func TestParseRunAttach(t *testing.T) {
98 98
 }
99 99
 
100 100
 func TestParseRunVolumes(t *testing.T) {
101
-	if config, hostConfig := mustParse(t, "-v /tmp"); hostConfig.Binds != nil {
102
-		t.Fatalf("Error parsing volume flags, `-v /tmp` should not mount-bind anything. Received %v", hostConfig.Binds)
103
-	} else if _, exists := config.Volumes["/tmp"]; !exists {
104
-		t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Received %v", config.Volumes)
101
+
102
+	// A single volume
103
+	arr, tryit := setupPlatformVolume([]string{`/tmp`}, []string{`c:\tmp`})
104
+	if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil {
105
+		t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds)
106
+	} else if _, exists := config.Volumes[arr[0]]; !exists {
107
+		t.Fatalf("Error parsing volume flags, %q is missing from volumes. Received %v", tryit, config.Volumes)
105 108
 	}
106 109
 
107
-	if config, hostConfig := mustParse(t, "-v /tmp -v /var"); hostConfig.Binds != nil {
108
-		t.Fatalf("Error parsing volume flags, `-v /tmp -v /var` should not mount-bind anything. Received %v", hostConfig.Binds)
109
-	} else if _, exists := config.Volumes["/tmp"]; !exists {
110
-		t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Received %v", config.Volumes)
111
-	} else if _, exists := config.Volumes["/var"]; !exists {
112
-		t.Fatalf("Error parsing volume flags, `-v /var` is missing from volumes. Received %v", config.Volumes)
110
+	// Two volumes
111
+	arr, tryit = setupPlatformVolume([]string{`/tmp`, `/var`}, []string{`c:\tmp`, `c:\var`})
112
+	if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil {
113
+		t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds)
114
+	} else if _, exists := config.Volumes[arr[0]]; !exists {
115
+		t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[0], config.Volumes)
116
+	} else if _, exists := config.Volumes[arr[1]]; !exists {
117
+		t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[1], config.Volumes)
113 118
 	}
114 119
 
115
-	if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp" {
116
-		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp` should mount-bind /hostTmp into /containerTmp. Received %v", hostConfig.Binds)
120
+	// A single bind-mount
121
+	arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`})
122
+	if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || hostConfig.Binds[0] != arr[0] {
123
+		t.Fatalf("Error parsing volume flags, %q should mount-bind the path before the colon into the path after the colon. Received %v %v", arr[0], hostConfig.Binds, config.Volumes)
117 124
 	}
118 125
 
119
-	if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /hostVar:/containerVar"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/hostTmp:/containerTmp", "/hostVar:/containerVar") != nil {
120
-		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /hostVar:/containerVar` should mount-bind /hostTmp into /containerTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
126
+	// Two bind-mounts.
127
+	arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/hostVar:/containerVar`}, []string{os.Getenv("ProgramData") + `:c:\ContainerPD`, os.Getenv("TEMP") + `:c:\containerTmp`})
128
+	if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
129
+		t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
121 130
 	}
122 131
 
123
-	if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/hostTmp:/containerTmp:ro", "/hostVar:/containerVar:rw") != nil {
124
-		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw` should mount-bind /hostTmp into /containerTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
132
+	// Two bind-mounts, first read-only, second read-write.
133
+	// TODO Windows: The Windows version uses read-write as that's the only mode it supports. Can change this post TP4
134
+	arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro`, `/hostVar:/containerVar:rw`}, []string{os.Getenv("TEMP") + `:c:\containerTmp:rw`, os.Getenv("ProgramData") + `:c:\ContainerPD:rw`})
135
+	if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
136
+		t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
125 137
 	}
126 138
 
127
-	if _, hostConfig := mustParse(t, "-v /containerTmp:ro -v /containerVar:rw"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/containerTmp:ro", "/containerVar:rw") != nil {
128
-		t.Fatalf("Error parsing volume flags, `-v /containerTmp:ro -v /containerVar:rw` should mount-bind /containerTmp into /ro and /containerVar into /rw. Received %v", hostConfig.Binds)
139
+	// Similar to previous test but with alternate modes which are only supported by Linux
140
+	if runtime.GOOS != "windows" {
141
+		arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro,Z`, `/hostVar:/containerVar:rw,Z`}, []string{})
142
+		if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
143
+			t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
144
+		}
145
+
146
+		arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:Z`, `/hostVar:/containerVar:z`}, []string{})
147
+		if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
148
+			t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
149
+		}
129 150
 	}
130 151
 
131
-	if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:ro,Z -v /hostVar:/containerVar:rw,Z"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/hostTmp:/containerTmp:ro,Z", "/hostVar:/containerVar:rw,Z") != nil {
132
-		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:ro,Z -v /hostVar:/containerVar:rw,Z` should mount-bind /hostTmp into /containerTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
152
+	// One bind mount and one volume
153
+	arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/containerVar`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`, `c:\containerTmp`})
154
+	if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] {
155
+		t.Fatalf("Error parsing volume flags, %s and %s should only one and only one bind mount %s. Received %s", arr[0], arr[1], arr[0], hostConfig.Binds)
156
+	} else if _, exists := config.Volumes[arr[1]]; !exists {
157
+		t.Fatalf("Error parsing volume flags %s and %s. %s is missing from volumes. Received %v", arr[0], arr[1], arr[1], config.Volumes)
133 158
 	}
134 159
 
135
-	if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:Z -v /hostVar:/containerVar:z"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/hostTmp:/containerTmp:Z", "/hostVar:/containerVar:z") != nil {
136
-		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:Z -v /hostVar:/containerVar:z` should mount-bind /hostTmp into /containerTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
160
+	// Root to non-c: drive letter (Windows specific)
161
+	if runtime.GOOS == "windows" {
162
+		arr, tryit = setupPlatformVolume([]string{}, []string{os.Getenv("SystemDrive") + `\:d:`})
163
+		if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] || len(config.Volumes) != 0 {
164
+			t.Fatalf("Error parsing %s. Should have a single bind mount and no volumes", arr[0])
165
+		}
137 166
 	}
138 167
 
139
-	if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /containerVar"); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != "/hostTmp:/containerTmp" {
140
-		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /containerVar` should mount-bind only /hostTmp into /containerTmp. Received %v", hostConfig.Binds)
141
-	} else if _, exists := config.Volumes["/containerVar"]; !exists {
142
-		t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes)
168
+}
169
+
170
+// This tests the cases for binds which are generated through
171
+// DecodeContainerConfig rather than Parse()
172
+func TestDecodeContainerConfigVolumes(t *testing.T) {
173
+
174
+	// Root to root
175
+	bindsOrVols, _ := setupPlatformVolume([]string{`/:/`}, []string{os.Getenv("SystemDrive") + `\:c:\`})
176
+	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
177
+		t.Fatalf("binds %v should have failed", bindsOrVols)
178
+	}
179
+	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
180
+		t.Fatalf("volume %v should have failed", bindsOrVols)
143 181
 	}
144 182
 
145
-	if config, hostConfig := mustParse(t, ""); hostConfig.Binds != nil {
146
-		t.Fatalf("Error parsing volume flags, without volume, nothing should be mount-binded. Received %v", hostConfig.Binds)
147
-	} else if len(config.Volumes) != 0 {
148
-		t.Fatalf("Error parsing volume flags, without volume, no volume should be present. Received %v", config.Volumes)
183
+	// No destination path
184
+	bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:`}, []string{os.Getenv("TEMP") + `\:`})
185
+	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
186
+		t.Fatalf("binds %v should have failed", bindsOrVols)
187
+	}
188
+	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
189
+		t.Fatalf("binds %v should have failed", bindsOrVols)
149 190
 	}
150 191
 
151
-	if _, _, err := parse(t, "-v /"); err == nil {
152
-		t.Fatalf("Expected error, but got none")
192
+	//	// No destination path or mode
193
+	bindsOrVols, _ = setupPlatformVolume([]string{`/tmp::`}, []string{os.Getenv("TEMP") + `\::`})
194
+	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
195
+		t.Fatalf("binds %v should have failed", bindsOrVols)
196
+	}
197
+	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
198
+		t.Fatalf("binds %v should have failed", bindsOrVols)
153 199
 	}
154 200
 
155
-	if _, _, err := parse(t, "-v /:/"); err == nil {
156
-		t.Fatalf("Error parsing volume flags, `-v /:/` should fail but didn't")
201
+	// A whole lot of nothing
202
+	bindsOrVols = []string{`:`}
203
+	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
204
+		t.Fatalf("binds %v should have failed", bindsOrVols)
157 205
 	}
158
-	if _, _, err := parse(t, "-v"); err == nil {
159
-		t.Fatalf("Error parsing volume flags, `-v` should fail but didn't")
206
+	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
207
+		t.Fatalf("binds %v should have failed", bindsOrVols)
160 208
 	}
161
-	if _, _, err := parse(t, "-v /tmp:"); err == nil {
162
-		t.Fatalf("Error parsing volume flags, `-v /tmp:` should fail but didn't")
209
+
210
+	// A whole lot of nothing with no mode
211
+	bindsOrVols = []string{`::`}
212
+	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
213
+		t.Fatalf("binds %v should have failed", bindsOrVols)
163 214
 	}
164
-	if _, _, err := parse(t, "-v /tmp::"); err == nil {
165
-		t.Fatalf("Error parsing volume flags, `-v /tmp::` should fail but didn't")
215
+	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
216
+		t.Fatalf("binds %v should have failed", bindsOrVols)
166 217
 	}
167
-	if _, _, err := parse(t, "-v :"); err == nil {
168
-		t.Fatalf("Error parsing volume flags, `-v :` should fail but didn't")
218
+
219
+	// Too much including an invalid mode
220
+	wTmp := os.Getenv("TEMP")
221
+	bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:/tmp:/tmp:/tmp`}, []string{wTmp + ":" + wTmp + ":" + wTmp + ":" + wTmp})
222
+	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
223
+		t.Fatalf("binds %v should have failed", bindsOrVols)
169 224
 	}
170
-	if _, _, err := parse(t, "-v ::"); err == nil {
171
-		t.Fatalf("Error parsing volume flags, `-v ::` should fail but didn't")
225
+	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
226
+		t.Fatalf("binds %v should have failed", bindsOrVols)
172 227
 	}
173
-	if _, _, err := parse(t, "-v /tmp:/tmp:/tmp:/tmp"); err == nil {
174
-		t.Fatalf("Error parsing volume flags, `-v /tmp:/tmp:/tmp:/tmp` should fail but didn't")
228
+
229
+	// Windows specific error tests
230
+	if runtime.GOOS == "windows" {
231
+		// Volume which does not include a drive letter
232
+		bindsOrVols = []string{`\tmp`}
233
+		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
234
+			t.Fatalf("binds %v should have failed", bindsOrVols)
235
+		}
236
+		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
237
+			t.Fatalf("binds %v should have failed", bindsOrVols)
238
+		}
239
+
240
+		// Root to C-Drive
241
+		bindsOrVols = []string{os.Getenv("SystemDrive") + `\:c:`}
242
+		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
243
+			t.Fatalf("binds %v should have failed", bindsOrVols)
244
+		}
245
+		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
246
+			t.Fatalf("binds %v should have failed", bindsOrVols)
247
+		}
248
+
249
+		// Container path that does not include a drive letter
250
+		bindsOrVols = []string{`c:\windows:\somewhere`}
251
+		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
252
+			t.Fatalf("binds %v should have failed", bindsOrVols)
253
+		}
254
+		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
255
+			t.Fatalf("binds %v should have failed", bindsOrVols)
256
+		}
175 257
 	}
258
+
259
+	// Linux-specific error tests
260
+	if runtime.GOOS != "windows" {
261
+		// Just root
262
+		bindsOrVols = []string{`/`}
263
+		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
264
+			t.Fatalf("binds %v should have failed", bindsOrVols)
265
+		}
266
+		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
267
+			t.Fatalf("binds %v should have failed", bindsOrVols)
268
+		}
269
+
270
+		// A single volume that looks like a bind mount passed in Volumes.
271
+		// This should be handled as a bind mount, not a volume.
272
+		vols := []string{`/foo:/bar`}
273
+		if config, hostConfig, err := callDecodeContainerConfig(vols, nil); err != nil {
274
+			t.Fatal("Volume /foo:/bar should have succeeded as a volume name")
275
+		} else if hostConfig.Binds != nil {
276
+			t.Fatalf("Error parsing volume flags, /foo:/bar should not mount-bind anything. Received %v", hostConfig.Binds)
277
+		} else if _, exists := config.Volumes[vols[0]]; !exists {
278
+			t.Fatalf("Error parsing volume flags, /foo:/bar is missing from volumes. Received %v", config.Volumes)
279
+		}
280
+
281
+	}
282
+}
283
+
284
+// callDecodeContainerConfig is a utility function used by TestDecodeContainerConfigVolumes
285
+// to call DecodeContainerConfig. It effectively does what a client would
286
+// do when calling the daemon by constructing a JSON stream of a
287
+// ContainerConfigWrapper which is populated by the set of volume specs
288
+// passed into it. It returns a config and a hostconfig which can be
289
+// validated to ensure DecodeContainerConfig has manipulated the structures
290
+// correctly.
291
+func callDecodeContainerConfig(volumes []string, binds []string) (*Config, *HostConfig, error) {
292
+	var (
293
+		b   []byte
294
+		err error
295
+		c   *Config
296
+		h   *HostConfig
297
+	)
298
+	w := ContainerConfigWrapper{
299
+		Config: &Config{
300
+			Volumes: map[string]struct{}{},
301
+		},
302
+		HostConfig: &HostConfig{
303
+			NetworkMode: "none",
304
+			Binds:       binds,
305
+		},
306
+	}
307
+	for _, v := range volumes {
308
+		w.Config.Volumes[v] = struct{}{}
309
+	}
310
+	if b, err = json.Marshal(w); err != nil {
311
+		return nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
312
+	}
313
+	c, h, err = DecodeContainerConfig(bytes.NewReader(b))
314
+	if err != nil {
315
+		return nil, nil, fmt.Errorf("Error parsing %s: %v", string(b), err)
316
+	}
317
+	if c == nil || h == nil {
318
+		return nil, nil, fmt.Errorf("Empty config or hostconfig")
319
+	}
320
+
321
+	return c, h, err
322
+}
323
+
324
+// check if (a == c && b == d) || (a == d && b == c)
325
+// because maps are randomized
326
+func compareRandomizedStrings(a, b, c, d string) error {
327
+	if a == c && b == d {
328
+		return nil
329
+	}
330
+	if a == d && b == c {
331
+		return nil
332
+	}
333
+	return fmt.Errorf("strings don't match")
334
+}
335
+
336
+// setupPlatformVolume takes two arrays of volume specs - a Unix style
337
+// spec and a Windows style spec. Depending on the platform being unit tested,
338
+// it returns one of them, along with a volume string that would be passed
339
+// on the docker CLI (eg -v /bar -v /foo).
340
+func setupPlatformVolume(u []string, w []string) ([]string, string) {
341
+	var a []string
342
+	if runtime.GOOS == "windows" {
343
+		a = w
344
+	} else {
345
+		a = u
346
+	}
347
+	s := ""
348
+	for _, v := range a {
349
+		s = s + "-v " + v + " "
350
+	}
351
+	return a, s
176 352
 }
177 353
 
178 354
 func TestParseLxcConfOpt(t *testing.T) {
... ...
@@ -438,9 +607,13 @@ func TestParseLoggingOpts(t *testing.T) {
438 438
 }
439 439
 
440 440
 func TestParseEnvfileVariables(t *testing.T) {
441
+	e := "open nonexistent: no such file or directory"
442
+	if runtime.GOOS == "windows" {
443
+		e = "open nonexistent: The system cannot find the file specified."
444
+	}
441 445
 	// env ko
442
-	if _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != "open nonexistent: no such file or directory" {
443
-		t.Fatalf("Expected an error with message 'open nonexistent: no such file or directory', got %v", err)
446
+	if _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
447
+		t.Fatalf("Expected an error with message '%s', got %v", e, err)
444 448
 	}
445 449
 	// env ok
446 450
 	config, _, _, err := parseRun([]string{"--env-file=fixtures/valid.env", "img", "cmd"})
... ...
@@ -460,9 +633,13 @@ func TestParseEnvfileVariables(t *testing.T) {
460 460
 }
461 461
 
462 462
 func TestParseLabelfileVariables(t *testing.T) {
463
+	e := "open nonexistent: no such file or directory"
464
+	if runtime.GOOS == "windows" {
465
+		e = "open nonexistent: The system cannot find the file specified."
466
+	}
463 467
 	// label ko
464
-	if _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != "open nonexistent: no such file or directory" {
465
-		t.Fatalf("Expected an error with message 'open nonexistent: no such file or directory', got %v", err)
468
+	if _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
469
+		t.Fatalf("Expected an error with message '%s', got %v", e, err)
466 470
 	}
467 471
 	// label ok
468 472
 	config, _, _, err := parseRun([]string{"--label-file=fixtures/valid.label", "img", "cmd"})
... ...
@@ -11,7 +11,6 @@ func TestGetDriver(t *testing.T) {
11 11
 	if err == nil {
12 12
 		t.Fatal("Expected error, was nil")
13 13
 	}
14
-
15 14
 	Register(volumetestutils.FakeDriver{}, "fake")
16 15
 	d, err := GetDriver("fake")
17 16
 	if err != nil {
... ...
@@ -14,6 +14,8 @@ var (
14 14
 	ErrVolumeInUse = errors.New("volume is in use")
15 15
 	// ErrNoSuchVolume is a typed error returned if the requested volume doesn't exist in the volume store
16 16
 	ErrNoSuchVolume = errors.New("no such volume")
17
+	// ErrInvalidName is a typed error returned when creating a volume with a name that is not valid on the platform
18
+	ErrInvalidName = errors.New("volume name is not valid on this platform")
17 19
 )
18 20
 
19 21
 // New initializes a VolumeStore to keep
... ...
@@ -39,13 +41,14 @@ type volumeCounter struct {
39 39
 // AddAll adds a list of volumes to the store
40 40
 func (s *VolumeStore) AddAll(vols []volume.Volume) {
41 41
 	for _, v := range vols {
42
-		s.vols[v.Name()] = &volumeCounter{v, 0}
42
+		s.vols[normaliseVolumeName(v.Name())] = &volumeCounter{v, 0}
43 43
 	}
44 44
 }
45 45
 
46 46
 // Create tries to find an existing volume with the given name or create a new one from the passed in driver
47 47
 func (s *VolumeStore) Create(name, driverName string, opts map[string]string) (volume.Volume, error) {
48 48
 	s.mu.Lock()
49
+	name = normaliseVolumeName(name)
49 50
 	if vc, exists := s.vols[name]; exists {
50 51
 		v := vc.Volume
51 52
 		s.mu.Unlock()
... ...
@@ -59,13 +62,22 @@ func (s *VolumeStore) Create(name, driverName string, opts map[string]string) (v
59 59
 		return nil, err
60 60
 	}
61 61
 
62
+	// Validate the name in a platform-specific manner
63
+	valid, err := volume.IsVolumeNameValid(name)
64
+	if err != nil {
65
+		return nil, err
66
+	}
67
+	if !valid {
68
+		return nil, ErrInvalidName
69
+	}
70
+
62 71
 	v, err := vd.Create(name, opts)
63 72
 	if err != nil {
64 73
 		return nil, err
65 74
 	}
66 75
 
67 76
 	s.mu.Lock()
68
-	s.vols[v.Name()] = &volumeCounter{v, 0}
77
+	s.vols[normaliseVolumeName(v.Name())] = &volumeCounter{v, 0}
69 78
 	s.mu.Unlock()
70 79
 
71 80
 	return v, nil
... ...
@@ -73,6 +85,7 @@ func (s *VolumeStore) Create(name, driverName string, opts map[string]string) (v
73 73
 
74 74
 // Get looks if a volume with the given name exists and returns it if so
75 75
 func (s *VolumeStore) Get(name string) (volume.Volume, error) {
76
+	name = normaliseVolumeName(name)
76 77
 	s.mu.Lock()
77 78
 	defer s.mu.Unlock()
78 79
 	vc, exists := s.vols[name]
... ...
@@ -86,7 +99,7 @@ func (s *VolumeStore) Get(name string) (volume.Volume, error) {
86 86
 func (s *VolumeStore) Remove(v volume.Volume) error {
87 87
 	s.mu.Lock()
88 88
 	defer s.mu.Unlock()
89
-	name := v.Name()
89
+	name := normaliseVolumeName(v.Name())
90 90
 	logrus.Debugf("Removing volume reference: driver %s, name %s", v.DriverName(), name)
91 91
 	vc, exists := s.vols[name]
92 92
 	if !exists {
... ...
@@ -112,11 +125,12 @@ func (s *VolumeStore) Remove(v volume.Volume) error {
112 112
 func (s *VolumeStore) Increment(v volume.Volume) {
113 113
 	s.mu.Lock()
114 114
 	defer s.mu.Unlock()
115
-	logrus.Debugf("Incrementing volume reference: driver %s, name %s", v.DriverName(), v.Name())
115
+	name := normaliseVolumeName(v.Name())
116
+	logrus.Debugf("Incrementing volume reference: driver %s, name %s", v.DriverName(), name)
116 117
 
117
-	vc, exists := s.vols[v.Name()]
118
+	vc, exists := s.vols[name]
118 119
 	if !exists {
119
-		s.vols[v.Name()] = &volumeCounter{v, 1}
120
+		s.vols[name] = &volumeCounter{v, 1}
120 121
 		return
121 122
 	}
122 123
 	vc.count++
... ...
@@ -126,9 +140,10 @@ func (s *VolumeStore) Increment(v volume.Volume) {
126 126
 func (s *VolumeStore) Decrement(v volume.Volume) {
127 127
 	s.mu.Lock()
128 128
 	defer s.mu.Unlock()
129
-	logrus.Debugf("Decrementing volume reference: driver %s, name %s", v.DriverName(), v.Name())
129
+	name := normaliseVolumeName(v.Name())
130
+	logrus.Debugf("Decrementing volume reference: driver %s, name %s", v.DriverName(), name)
130 131
 
131
-	vc, exists := s.vols[v.Name()]
132
+	vc, exists := s.vols[name]
132 133
 	if !exists {
133 134
 		return
134 135
 	}
... ...
@@ -142,7 +157,7 @@ func (s *VolumeStore) Decrement(v volume.Volume) {
142 142
 func (s *VolumeStore) Count(v volume.Volume) uint {
143 143
 	s.mu.Lock()
144 144
 	defer s.mu.Unlock()
145
-	vc, exists := s.vols[v.Name()]
145
+	vc, exists := s.vols[normaliseVolumeName(v.Name())]
146 146
 	if !exists {
147 147
 		return 0
148 148
 	}
149 149
new file mode 100644
... ...
@@ -0,0 +1,9 @@
0
+// +build linux freebsd
1
+
2
+package store
3
+
4
+// normaliseVolumeName is a platform specific function to normalise the name
5
+// of a volume. This is a no-op on Unix-like platforms
6
+func normaliseVolumeName(name string) string {
7
+	return name
8
+}
0 9
new file mode 100644
... ...
@@ -0,0 +1,12 @@
0
+package store
1
+
2
+import "strings"
3
+
4
+// normaliseVolumeName is a platform specific function to normalise the name
5
+// of a volume. On Windows, as NTFS is case insensitive, under
6
+// c:\ProgramData\Docker\Volumes\, the folders John and john would be synonymous.
7
+// Hence we can't allow the volume "John" and "john" to be created as seperate
8
+// volumes.
9
+func normaliseVolumeName(name string) string {
10
+	return strings.ToLower(name)
11
+}
... ...
@@ -1,5 +1,15 @@
1 1
 package volume
2 2
 
3
+import (
4
+	"os"
5
+	"runtime"
6
+	"strings"
7
+
8
+	"github.com/Sirupsen/logrus"
9
+	derr "github.com/docker/docker/errors"
10
+	"github.com/docker/docker/pkg/system"
11
+)
12
+
3 13
 // DefaultDriverName is the driver name used for the driver
4 14
 // implemented in the local package.
5 15
 const DefaultDriverName string = "local"
... ...
@@ -29,33 +39,134 @@ type Volume interface {
29 29
 	Unmount() error
30 30
 }
31 31
 
32
-// read-write modes
33
-var rwModes = map[string]bool{
34
-	"rw":   true,
35
-	"rw,Z": true,
36
-	"rw,z": true,
37
-	"z,rw": true,
38
-	"Z,rw": true,
39
-	"Z":    true,
40
-	"z":    true,
32
+// MountPoint is the intersection point between a volume and a container. It
33
+// specifies which volume is to be used and where inside a container it should
34
+// be mounted.
35
+type MountPoint struct {
36
+	Source      string // Container host directory
37
+	Destination string // Inside the container
38
+	RW          bool   // True if writable
39
+	Name        string // Name set by user
40
+	Driver      string // Volume driver to use
41
+	Volume      Volume `json:"-"`
42
+
43
+	// Note Mode is not used on Windows
44
+	Mode string `json:"Relabel"` // Originally field was `Relabel`"
45
+}
46
+
47
+// Setup sets up a mount point by either mounting the volume if it is
48
+// configured, or creating the source directory if supplied.
49
+func (m *MountPoint) Setup() (string, error) {
50
+	if m.Volume != nil {
51
+		return m.Volume.Mount()
52
+	}
53
+	if len(m.Source) > 0 {
54
+		if _, err := os.Stat(m.Source); err != nil {
55
+			if !os.IsNotExist(err) {
56
+				return "", err
57
+			}
58
+			if runtime.GOOS != "windows" { // Windows does not have deprecation issues here
59
+				logrus.Warnf("Auto-creating non-existant volume host path %s, this is deprecated and will be removed soon", m.Source)
60
+				if err := system.MkdirAll(m.Source, 0755); err != nil {
61
+					return "", err
62
+				}
63
+			}
64
+		}
65
+		return m.Source, nil
66
+	}
67
+	return "", derr.ErrorCodeMountSetup
41 68
 }
42 69
 
43
-// read-only modes
44
-var roModes = map[string]bool{
45
-	"ro":   true,
46
-	"ro,Z": true,
47
-	"ro,z": true,
48
-	"z,ro": true,
49
-	"Z,ro": true,
70
+// Path returns the path of a volume in a mount point.
71
+func (m *MountPoint) Path() string {
72
+	if m.Volume != nil {
73
+		return m.Volume.Path()
74
+	}
75
+	return m.Source
50 76
 }
51 77
 
52 78
 // ValidMountMode will make sure the mount mode is valid.
53 79
 // returns if it's a valid mount mode or not.
54 80
 func ValidMountMode(mode string) bool {
55
-	return roModes[mode] || rwModes[mode]
81
+	return roModes[strings.ToLower(mode)] || rwModes[strings.ToLower(mode)]
56 82
 }
57 83
 
58 84
 // ReadWrite tells you if a mode string is a valid read-write mode or not.
59 85
 func ReadWrite(mode string) bool {
60
-	return rwModes[mode]
86
+	return rwModes[strings.ToLower(mode)]
87
+}
88
+
89
+// ParseVolumesFrom ensure that the supplied volumes-from is valid.
90
+func ParseVolumesFrom(spec string) (string, string, error) {
91
+	if len(spec) == 0 {
92
+		return "", "", derr.ErrorCodeVolumeFromBlank.WithArgs(spec)
93
+	}
94
+
95
+	specParts := strings.SplitN(spec, ":", 2)
96
+	id := specParts[0]
97
+	mode := "rw"
98
+
99
+	if len(specParts) == 2 {
100
+		mode = specParts[1]
101
+		if !ValidMountMode(mode) {
102
+			return "", "", derr.ErrorCodeVolumeInvalidMode.WithArgs(mode)
103
+		}
104
+	}
105
+	return id, mode, nil
106
+}
107
+
108
+// SplitN splits raw into a maximum of n parts, separated by a separator colon.
109
+// A separator colon is the last `:` character in the regex `[/:\\]?[a-zA-Z]:` (note `\\` is `\` escaped).
110
+// This allows to correctly split strings such as `C:\foo:D:\:rw`.
111
+func SplitN(raw string, n int) []string {
112
+	var array []string
113
+	if len(raw) == 0 || raw[0] == ':' {
114
+		// invalid
115
+		return nil
116
+	}
117
+	// numberOfParts counts the number of parts separated by a separator colon
118
+	numberOfParts := 0
119
+	// left represents the left-most cursor in raw, updated at every `:` character considered as a separator.
120
+	left := 0
121
+	// right represents the right-most cursor in raw incremented with the loop. Note this
122
+	// starts at index 1 as index 0 is already handle above as a special case.
123
+	for right := 1; right < len(raw); right++ {
124
+		// stop parsing if reached maximum number of parts
125
+		if n >= 0 && numberOfParts >= n {
126
+			break
127
+		}
128
+		if raw[right] != ':' {
129
+			continue
130
+		}
131
+		potentialDriveLetter := raw[right-1]
132
+		if (potentialDriveLetter >= 'A' && potentialDriveLetter <= 'Z') || (potentialDriveLetter >= 'a' && potentialDriveLetter <= 'z') {
133
+			if right > 1 {
134
+				beforePotentialDriveLetter := raw[right-2]
135
+				if beforePotentialDriveLetter != ':' && beforePotentialDriveLetter != '/' && beforePotentialDriveLetter != '\\' {
136
+					// e.g. `C:` is not preceded by any delimiter, therefore it was not a drive letter but a path ending with `C:`.
137
+					array = append(array, raw[left:right])
138
+					left = right + 1
139
+					numberOfParts++
140
+				}
141
+				// else, `C:` is considered as a drive letter and not as a delimiter, so we continue parsing.
142
+			}
143
+			// if right == 1, then `C:` is the beginning of the raw string, therefore `:` is again not considered a delimiter and we continue parsing.
144
+		} else {
145
+			// if `:` is not preceded by a potential drive letter, then consider it as a delimiter.
146
+			array = append(array, raw[left:right])
147
+			left = right + 1
148
+			numberOfParts++
149
+		}
150
+	}
151
+	// need to take care of the last part
152
+	if left < len(raw) {
153
+		if n >= 0 && numberOfParts >= n {
154
+			// if the maximum number of parts is reached, just append the rest to the last part
155
+			// left-1 is at the last `:` that needs to be included since not considered a separator.
156
+			array[n-1] += raw[left-1:]
157
+		} else {
158
+			array = append(array, raw[left:])
159
+		}
160
+	}
161
+	return array
61 162
 }
62 163
new file mode 100644
... ...
@@ -0,0 +1,261 @@
0
+package volume
1
+
2
+import (
3
+	"runtime"
4
+	"strings"
5
+	"testing"
6
+)
7
+
8
+func TestParseMountSpec(t *testing.T) {
9
+	var (
10
+		valid   []string
11
+		invalid map[string]string
12
+	)
13
+
14
+	if runtime.GOOS == "windows" {
15
+		valid = []string{
16
+			`d:\`,
17
+			`d:`,
18
+			`d:\path`,
19
+			`d:\path with space`,
20
+			// TODO Windows post TP4 - readonly support `d:\pathandmode:ro`,
21
+			`c:\:d:\`,
22
+			`c:\windows\:d:`,
23
+			`c:\windows:d:\s p a c e`,
24
+			`c:\windows:d:\s p a c e:RW`,
25
+			`c:\program files:d:\s p a c e i n h o s t d i r`,
26
+			`0123456789name:d:`,
27
+			`MiXeDcAsEnAmE:d:`,
28
+			`name:D:`,
29
+			`name:D::rW`,
30
+			`name:D::RW`,
31
+			// TODO Windows post TP4 - readonly support `name:D::RO`,
32
+			`c:/:d:/forward/slashes/are/good/too`,
33
+			// TODO Windows post TP4 - readonly support `c:/:d:/including with/spaces:ro`,
34
+			`c:\Windows`,             // With capital
35
+			`c:\Program Files (x86)`, // With capitals and brackets
36
+		}
37
+		invalid = map[string]string{
38
+			``:                                 "Invalid volume specification: ",
39
+			`.`:                                "Invalid volume specification: ",
40
+			`..\`:                              "Invalid volume specification: ",
41
+			`c:\:..\`:                          "Invalid volume specification: ",
42
+			`c:\:d:\:xyzzy`:                    "Invalid volume specification: ",
43
+			`c:`:                               "cannot be c:",
44
+			`c:\`:                              `cannot be c:\`,
45
+			`c:\notexist:d:`:                   `The system cannot find the file specified`,
46
+			`c:\windows\system32\ntdll.dll:d:`: `Source 'c:\windows\system32\ntdll.dll' is not a directory`,
47
+			`name<:d:`:                         `Invalid volume specification`,
48
+			`name>:d:`:                         `Invalid volume specification`,
49
+			`name::d:`:                         `Invalid volume specification`,
50
+			`name":d:`:                         `Invalid volume specification`,
51
+			`name\:d:`:                         `Invalid volume specification`,
52
+			`name*:d:`:                         `Invalid volume specification`,
53
+			`name|:d:`:                         `Invalid volume specification`,
54
+			`name?:d:`:                         `Invalid volume specification`,
55
+			`name/:d:`:                         `Invalid volume specification`,
56
+			`d:\pathandmode:rw`:                `Invalid volume specification`,
57
+			`con:d:`:                           `cannot be a reserved word for Windows filenames`,
58
+			`PRN:d:`:                           `cannot be a reserved word for Windows filenames`,
59
+			`aUx:d:`:                           `cannot be a reserved word for Windows filenames`,
60
+			`nul:d:`:                           `cannot be a reserved word for Windows filenames`,
61
+			`com1:d:`:                          `cannot be a reserved word for Windows filenames`,
62
+			`com2:d:`:                          `cannot be a reserved word for Windows filenames`,
63
+			`com3:d:`:                          `cannot be a reserved word for Windows filenames`,
64
+			`com4:d:`:                          `cannot be a reserved word for Windows filenames`,
65
+			`com5:d:`:                          `cannot be a reserved word for Windows filenames`,
66
+			`com6:d:`:                          `cannot be a reserved word for Windows filenames`,
67
+			`com7:d:`:                          `cannot be a reserved word for Windows filenames`,
68
+			`com8:d:`:                          `cannot be a reserved word for Windows filenames`,
69
+			`com9:d:`:                          `cannot be a reserved word for Windows filenames`,
70
+			`lpt1:d:`:                          `cannot be a reserved word for Windows filenames`,
71
+			`lpt2:d:`:                          `cannot be a reserved word for Windows filenames`,
72
+			`lpt3:d:`:                          `cannot be a reserved word for Windows filenames`,
73
+			`lpt4:d:`:                          `cannot be a reserved word for Windows filenames`,
74
+			`lpt5:d:`:                          `cannot be a reserved word for Windows filenames`,
75
+			`lpt6:d:`:                          `cannot be a reserved word for Windows filenames`,
76
+			`lpt7:d:`:                          `cannot be a reserved word for Windows filenames`,
77
+			`lpt8:d:`:                          `cannot be a reserved word for Windows filenames`,
78
+			`lpt9:d:`:                          `cannot be a reserved word for Windows filenames`,
79
+		}
80
+
81
+	} else {
82
+		valid = []string{
83
+			"/home",
84
+			"/home:/home",
85
+			"/home:/something/else",
86
+			"/with space",
87
+			"/home:/with space",
88
+			"relative:/absolute-path",
89
+			"hostPath:/containerPath:ro",
90
+			"/hostPath:/containerPath:rw",
91
+			"/rw:/ro",
92
+		}
93
+		invalid = map[string]string{
94
+			"":                "Invalid volume specification",
95
+			"./":              "Invalid volume destination",
96
+			"../":             "Invalid volume destination",
97
+			"/:../":           "Invalid volume destination",
98
+			"/:path":          "Invalid volume destination",
99
+			":":               "Invalid volume specification",
100
+			"/tmp:":           "Invalid volume destination",
101
+			":test":           "Invalid volume specification",
102
+			":/test":          "Invalid volume specification",
103
+			"tmp:":            "Invalid volume destination",
104
+			":test:":          "Invalid volume specification",
105
+			"::":              "Invalid volume specification",
106
+			":::":             "Invalid volume specification",
107
+			"/tmp:::":         "Invalid volume specification",
108
+			":/tmp::":         "Invalid volume specification",
109
+			"/path:rw":        "Invalid volume specification",
110
+			"/path:ro":        "Invalid volume specification",
111
+			"/rw:rw":          "Invalid volume specification",
112
+			"path:ro":         "Invalid volume specification",
113
+			"/path:/path:sw":  "invalid mode: sw",
114
+			"/path:/path:rwz": "invalid mode: rwz",
115
+		}
116
+	}
117
+
118
+	for _, path := range valid {
119
+		if _, err := ParseMountSpec(path, "local"); err != nil {
120
+			t.Fatalf("ParseMountSpec(`%q`) should succeed: error %q", path, err)
121
+		}
122
+	}
123
+
124
+	for path, expectedError := range invalid {
125
+		if _, err := ParseMountSpec(path, "local"); err == nil {
126
+			t.Fatalf("ParseMountSpec(`%q`) should have failed validation. Err %v", path, err)
127
+		} else {
128
+			if !strings.Contains(err.Error(), expectedError) {
129
+				t.Fatalf("ParseMountSpec(`%q`) error should contain %q, got %v", path, expectedError, err.Error())
130
+			}
131
+		}
132
+	}
133
+}
134
+
135
+func TestSplitN(t *testing.T) {
136
+	for _, x := range []struct {
137
+		input    string
138
+		n        int
139
+		expected []string
140
+	}{
141
+		{`C:\foo:d:`, -1, []string{`C:\foo`, `d:`}},
142
+		{`:C:\foo:d:`, -1, nil},
143
+		{`/foo:/bar:ro`, 3, []string{`/foo`, `/bar`, `ro`}},
144
+		{`/foo:/bar:ro`, 2, []string{`/foo`, `/bar:ro`}},
145
+		{`C:\foo\:/foo`, -1, []string{`C:\foo\`, `/foo`}},
146
+
147
+		{`d:\`, -1, []string{`d:\`}},
148
+		{`d:`, -1, []string{`d:`}},
149
+		{`d:\path`, -1, []string{`d:\path`}},
150
+		{`d:\path with space`, -1, []string{`d:\path with space`}},
151
+		{`d:\pathandmode:rw`, -1, []string{`d:\pathandmode`, `rw`}},
152
+		{`c:\:d:\`, -1, []string{`c:\`, `d:\`}},
153
+		{`c:\windows\:d:`, -1, []string{`c:\windows\`, `d:`}},
154
+		{`c:\windows:d:\s p a c e`, -1, []string{`c:\windows`, `d:\s p a c e`}},
155
+		{`c:\windows:d:\s p a c e:RW`, -1, []string{`c:\windows`, `d:\s p a c e`, `RW`}},
156
+		{`c:\program files:d:\s p a c e i n h o s t d i r`, -1, []string{`c:\program files`, `d:\s p a c e i n h o s t d i r`}},
157
+		{`0123456789name:d:`, -1, []string{`0123456789name`, `d:`}},
158
+		{`MiXeDcAsEnAmE:d:`, -1, []string{`MiXeDcAsEnAmE`, `d:`}},
159
+		{`name:D:`, -1, []string{`name`, `D:`}},
160
+		{`name:D::rW`, -1, []string{`name`, `D:`, `rW`}},
161
+		{`name:D::RW`, -1, []string{`name`, `D:`, `RW`}},
162
+		{`c:/:d:/forward/slashes/are/good/too`, -1, []string{`c:/`, `d:/forward/slashes/are/good/too`}},
163
+		{`c:\Windows`, -1, []string{`c:\Windows`}},
164
+		{`c:\Program Files (x86)`, -1, []string{`c:\Program Files (x86)`}},
165
+
166
+		{``, -1, nil},
167
+		{`.`, -1, []string{`.`}},
168
+		{`..\`, -1, []string{`..\`}},
169
+		{`c:\:..\`, -1, []string{`c:\`, `..\`}},
170
+		{`c:\:d:\:xyzzy`, -1, []string{`c:\`, `d:\`, `xyzzy`}},
171
+	} {
172
+		res := SplitN(x.input, x.n)
173
+		if len(res) < len(x.expected) {
174
+			t.Fatalf("input: %v, expected: %v, got: %v", x.input, x.expected, res)
175
+		}
176
+		for i, e := range res {
177
+			if e != x.expected[i] {
178
+				t.Fatalf("input: %v, expected: %v, got: %v", x.input, x.expected, res)
179
+			}
180
+		}
181
+	}
182
+}
183
+
184
+// testParseMountSpec is a structure used by TestParseMountSpecSplit for
185
+// specifying test cases for the ParseMountSpec() function.
186
+type testParseMountSpec struct {
187
+	bind      string
188
+	driver    string
189
+	expDest   string
190
+	expSource string
191
+	expName   string
192
+	expDriver string
193
+	expRW     bool
194
+	fail      bool
195
+}
196
+
197
+func TestParseMountSpecSplit(t *testing.T) {
198
+	var cases []testParseMountSpec
199
+	if runtime.GOOS == "windows" {
200
+		cases = []testParseMountSpec{
201
+			{`c:\:d:`, "local", `d:`, `c:\`, ``, "", true, false},
202
+			{`c:\:d:\`, "local", `d:\`, `c:\`, ``, "", true, false},
203
+			// TODO Windows post TP4 - Add readonly support {`c:\:d:\:ro`, "local", `d:\`, `c:\`, ``, "", false, false},
204
+			{`c:\:d:\:rw`, "local", `d:\`, `c:\`, ``, "", true, false},
205
+			{`c:\:d:\:foo`, "local", `d:\`, `c:\`, ``, "", false, true},
206
+			{`name:d::rw`, "local", `d:`, ``, `name`, "local", true, false},
207
+			{`name:d:`, "local", `d:`, ``, `name`, "local", true, false},
208
+			// TODO Windows post TP4 - Add readonly support {`name:d::ro`, "local", `d:`, ``, `name`, "local", false, false},
209
+			{`name:c:`, "", ``, ``, ``, "", true, true},
210
+			{`driver/name:c:`, "", ``, ``, ``, "", true, true},
211
+		}
212
+	} else {
213
+		cases = []testParseMountSpec{
214
+			{"/tmp:/tmp1", "", "/tmp1", "/tmp", "", "", true, false},
215
+			{"/tmp:/tmp2:ro", "", "/tmp2", "/tmp", "", "", false, false},
216
+			{"/tmp:/tmp3:rw", "", "/tmp3", "/tmp", "", "", true, false},
217
+			{"/tmp:/tmp4:foo", "", "", "", "", "", false, true},
218
+			{"name:/named1", "", "/named1", "", "name", "local", true, false},
219
+			{"name:/named2", "external", "/named2", "", "name", "external", true, false},
220
+			{"name:/named3:ro", "local", "/named3", "", "name", "local", false, false},
221
+			{"local/name:/tmp:rw", "", "/tmp", "", "local/name", "local", true, false},
222
+			{"/tmp:tmp", "", "", "", "", "", true, true},
223
+		}
224
+	}
225
+
226
+	for _, c := range cases {
227
+		m, err := ParseMountSpec(c.bind, c.driver)
228
+		if c.fail {
229
+			if err == nil {
230
+				t.Fatalf("Expected error, was nil, for spec %s\n", c.bind)
231
+			}
232
+			continue
233
+		}
234
+
235
+		if m == nil || err != nil {
236
+			t.Fatalf("ParseMountSpec failed for spec %s driver %s error %v\n", c.bind, c.driver, err.Error())
237
+			continue
238
+		}
239
+
240
+		if m.Destination != c.expDest {
241
+			t.Fatalf("Expected destination %s, was %s, for spec %s\n", c.expDest, m.Destination, c.bind)
242
+		}
243
+
244
+		if m.Source != c.expSource {
245
+			t.Fatalf("Expected source %s, was %s, for spec %s\n", c.expSource, m.Source, c.bind)
246
+		}
247
+
248
+		if m.Name != c.expName {
249
+			t.Fatalf("Expected name %s, was %s for spec %s\n", c.expName, m.Name, c.bind)
250
+		}
251
+
252
+		if m.Driver != c.expDriver {
253
+			t.Fatalf("Expected driver %s, was %s, for spec %s\n", c.expDriver, m.Driver, c.bind)
254
+		}
255
+
256
+		if m.RW != c.expRW {
257
+			t.Fatalf("Expected RW %v, was %v for spec %s\n", c.expRW, m.RW, c.bind)
258
+		}
259
+	}
260
+}
0 261
new file mode 100644
... ...
@@ -0,0 +1,132 @@
0
+// +build linux freebsd darwin
1
+
2
+package volume
3
+
4
+import (
5
+	"fmt"
6
+	"path/filepath"
7
+	"strings"
8
+
9
+	derr "github.com/docker/docker/errors"
10
+)
11
+
12
+// read-write modes
13
+var rwModes = map[string]bool{
14
+	"rw":   true,
15
+	"rw,Z": true,
16
+	"rw,z": true,
17
+	"z,rw": true,
18
+	"Z,rw": true,
19
+	"Z":    true,
20
+	"z":    true,
21
+}
22
+
23
+// read-only modes
24
+var roModes = map[string]bool{
25
+	"ro":   true,
26
+	"ro,Z": true,
27
+	"ro,z": true,
28
+	"z,ro": true,
29
+	"Z,ro": true,
30
+}
31
+
32
+// BackwardsCompatible decides whether this mount point can be
33
+// used in old versions of Docker or not.
34
+// Only bind mounts and local volumes can be used in old versions of Docker.
35
+func (m *MountPoint) BackwardsCompatible() bool {
36
+	return len(m.Source) > 0 || m.Driver == DefaultDriverName
37
+}
38
+
39
+// HasResource checks whether the given absolute path for a container is in
40
+// this mount point. If the relative path starts with `../` then the resource
41
+// is outside of this mount point, but we can't simply check for this prefix
42
+// because it misses `..` which is also outside of the mount, so check both.
43
+func (m *MountPoint) HasResource(absolutePath string) bool {
44
+	relPath, err := filepath.Rel(m.Destination, absolutePath)
45
+	return err == nil && relPath != ".." && !strings.HasPrefix(relPath, fmt.Sprintf("..%c", filepath.Separator))
46
+}
47
+
48
+// ParseMountSpec validates the configuration of mount information is valid.
49
+func ParseMountSpec(spec, volumeDriver string) (*MountPoint, error) {
50
+	spec = filepath.ToSlash(spec)
51
+
52
+	mp := &MountPoint{
53
+		RW: true,
54
+	}
55
+	if strings.Count(spec, ":") > 2 {
56
+		return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec)
57
+	}
58
+
59
+	arr := strings.SplitN(spec, ":", 3)
60
+	if arr[0] == "" {
61
+		return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec)
62
+	}
63
+
64
+	switch len(arr) {
65
+	case 1:
66
+		// Just a destination path in the container
67
+		mp.Destination = filepath.Clean(arr[0])
68
+	case 2:
69
+		if isValid := ValidMountMode(arr[1]); isValid {
70
+			// Destination + Mode is not a valid volume - volumes
71
+			// cannot include a mode. eg /foo:rw
72
+			return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec)
73
+		}
74
+		// Host Source Path or Name + Destination
75
+		mp.Source = arr[0]
76
+		mp.Destination = arr[1]
77
+	case 3:
78
+		// HostSourcePath+DestinationPath+Mode
79
+		mp.Source = arr[0]
80
+		mp.Destination = arr[1]
81
+		mp.Mode = arr[2] // Mode field is used by SELinux to decide whether to apply label
82
+		if !ValidMountMode(mp.Mode) {
83
+			return nil, derr.ErrorCodeVolumeInvalidMode.WithArgs(mp.Mode)
84
+		}
85
+		mp.RW = ReadWrite(mp.Mode)
86
+	default:
87
+		return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec)
88
+	}
89
+
90
+	//validate the volumes destination path
91
+	mp.Destination = filepath.Clean(mp.Destination)
92
+	if !filepath.IsAbs(mp.Destination) {
93
+		return nil, derr.ErrorCodeVolumeAbs.WithArgs(mp.Destination)
94
+	}
95
+
96
+	// Destination cannot be "/"
97
+	if mp.Destination == "/" {
98
+		return nil, derr.ErrorCodeVolumeSlash.WithArgs(spec)
99
+	}
100
+
101
+	name, source := ParseVolumeSource(mp.Source)
102
+	if len(source) == 0 {
103
+		mp.Source = "" // Clear it out as we previously assumed it was not a name
104
+		mp.Driver = volumeDriver
105
+		if len(mp.Driver) == 0 {
106
+			mp.Driver = DefaultDriverName
107
+		}
108
+	} else {
109
+		mp.Source = filepath.Clean(source)
110
+	}
111
+
112
+	mp.Name = name
113
+
114
+	return mp, nil
115
+}
116
+
117
+// ParseVolumeSource parses the origin sources that's mounted into the container.
118
+// It returns a name and a source. It looks to see if the spec passed in
119
+// is an absolute file. If it is, it assumes the spec is a source. If not,
120
+// it assumes the spec is a name.
121
+func ParseVolumeSource(spec string) (string, string) {
122
+	if !filepath.IsAbs(spec) {
123
+		return spec, ""
124
+	}
125
+	return "", spec
126
+}
127
+
128
+// IsVolumeNameValid checks a volume name in a platform specific manner.
129
+func IsVolumeNameValid(name string) (bool, error) {
130
+	return true, nil
131
+}
0 132
new file mode 100644
... ...
@@ -0,0 +1,181 @@
0
+package volume
1
+
2
+import (
3
+	"os"
4
+	"path/filepath"
5
+	"regexp"
6
+	"strings"
7
+
8
+	"github.com/Sirupsen/logrus"
9
+	derr "github.com/docker/docker/errors"
10
+)
11
+
12
+// read-write modes
13
+var rwModes = map[string]bool{
14
+	"rw": true,
15
+}
16
+
17
+// read-only modes
18
+var roModes = map[string]bool{
19
+	"ro": true,
20
+}
21
+
22
+const (
23
+	// Spec should be in the format [source:]destination[:mode]
24
+	//
25
+	// Examples: c:\foo bar:d:rw
26
+	//           c:\foo:d:\bar
27
+	//           myname:d:
28
+	//           d:\
29
+	//
30
+	// Explanation of this regex! Thanks @thaJeztah on IRC and gist for help. See
31
+	// https://gist.github.com/thaJeztah/6185659e4978789fb2b2. A good place to
32
+	// test is https://regex-golang.appspot.com/assets/html/index.html
33
+	//
34
+	// Useful link for referencing named capturing groups:
35
+	// http://stackoverflow.com/questions/20750843/using-named-matches-from-go-regex
36
+	//
37
+	// There are three match groups: source, destination and mode.
38
+	//
39
+
40
+	// RXHostDir is the first option of a source
41
+	RXHostDir = `[a-z]:\\(?:[^\\/:*?"<>|\r\n]+\\?)*`
42
+	// RXName is the second option of a source
43
+	RXName = `[^\\/:*?"<>|\r\n]+`
44
+	// RXReservedNames are reserved names not possible on Windows
45
+	RXReservedNames = `(con)|(prn)|(nul)|(aux)|(com[1-9])|(lpt[1-9])`
46
+
47
+	// RXSource is the combined possiblities for a source
48
+	RXSource = `((?P<source>((` + RXHostDir + `)|(` + RXName + `))):)?`
49
+
50
+	// Source. Can be either a host directory, a name, or omitted:
51
+	//  HostDir:
52
+	//    -  Essentially using the folder solution from
53
+	//       https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch08s18.html
54
+	//       but adding case insensitivity.
55
+	//    -  Must be an absolute path such as c:\path
56
+	//    -  Can include spaces such as `c:\program files`
57
+	//    -  And then followed by a colon which is not in the capture group
58
+	//    -  And can be optional
59
+	//  Name:
60
+	//    -  Must not contain invalid NTFS filename characters (https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx)
61
+	//    -  And then followed by a colon which is not in the capture group
62
+	//    -  And can be optional
63
+
64
+	// RXDestination is the regex expression for the mount destination
65
+	RXDestination = `(?P<destination>([a-z]):((?:\\[^\\/:*?"<>\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
+
72
+	// RXMode is the regex expression for the mode of the mount
73
+	RXMode = `(:(?P<mode>(?i)rw))?`
74
+	// Temporarily for TP4, disabling the use of ro as it's not supported yet
75
+	// in the platform. TODO Windows: `(:(?P<mode>(?i)ro|rw))?`
76
+	// mode (optional)
77
+	//    -  Hopefully self explanatory in comparison to above.
78
+	//    -  Colon is not in the capture group
79
+	//
80
+)
81
+
82
+// ParseMountSpec validates the configuration of mount information is valid.
83
+func ParseMountSpec(spec string, volumeDriver string) (*MountPoint, error) {
84
+	var specExp = regexp.MustCompile(`^` + RXSource + RXDestination + RXMode + `$`)
85
+
86
+	// Ensure in platform semantics for matching. The CLI will send in Unix semantics.
87
+	match := specExp.FindStringSubmatch(filepath.FromSlash(strings.ToLower(spec)))
88
+
89
+	// Must have something back
90
+	if len(match) == 0 {
91
+		return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec)
92
+	}
93
+
94
+	// Pull out the sub expressions from the named capture groups
95
+	matchgroups := make(map[string]string)
96
+	for i, name := range specExp.SubexpNames() {
97
+		matchgroups[name] = strings.ToLower(match[i])
98
+	}
99
+
100
+	mp := &MountPoint{
101
+		Source:      matchgroups["source"],
102
+		Destination: matchgroups["destination"],
103
+		RW:          true,
104
+	}
105
+	if strings.ToLower(matchgroups["mode"]) == "ro" {
106
+		mp.RW = false
107
+	}
108
+
109
+	// Volumes cannot include an explicitly supplied mode eg c:\path:rw
110
+	if mp.Source == "" && mp.Destination != "" && matchgroups["mode"] != "" {
111
+		return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec)
112
+	}
113
+
114
+	// Note: No need to check if destination is absolute as it must be by
115
+	// definition of matching the regex.
116
+
117
+	if filepath.VolumeName(mp.Destination) == mp.Destination {
118
+		// Ensure the destination path, if a drive letter, is not the c drive
119
+		if strings.ToLower(mp.Destination) == "c:" {
120
+			return nil, derr.ErrorCodeVolumeDestIsC.WithArgs(spec)
121
+		}
122
+	} else {
123
+		// So we know the destination is a path, not drive letter. Clean it up.
124
+		mp.Destination = filepath.Clean(mp.Destination)
125
+		// Ensure the destination path, if a path, is not the c root directory
126
+		if strings.ToLower(mp.Destination) == `c:\` {
127
+			return nil, derr.ErrorCodeVolumeDestIsCRoot.WithArgs(spec)
128
+		}
129
+	}
130
+
131
+	// See if the source is a name instead of a host directory
132
+	if len(mp.Source) > 0 {
133
+		validName, err := IsVolumeNameValid(mp.Source)
134
+		if err != nil {
135
+			return nil, err
136
+		}
137
+		if validName {
138
+			// OK, so the source is a name.
139
+			mp.Name = mp.Source
140
+			mp.Source = ""
141
+
142
+			// Set the driver accordingly
143
+			mp.Driver = volumeDriver
144
+			if len(mp.Driver) == 0 {
145
+				mp.Driver = DefaultDriverName
146
+			}
147
+		} else {
148
+			// OK, so the source must be a host directory. Make sure it's clean.
149
+			mp.Source = filepath.Clean(mp.Source)
150
+		}
151
+	}
152
+
153
+	// Ensure the host path source, if supplied, exists and is a directory
154
+	if len(mp.Source) > 0 {
155
+		var fi os.FileInfo
156
+		var err error
157
+		if fi, err = os.Stat(mp.Source); err != nil {
158
+			return nil, derr.ErrorCodeVolumeSourceNotFound.WithArgs(mp.Source, err)
159
+		}
160
+		if !fi.IsDir() {
161
+			return nil, derr.ErrorCodeVolumeSourceNotDirectory.WithArgs(mp.Source)
162
+		}
163
+	}
164
+
165
+	logrus.Debugf("MP: Source '%s', Dest '%s', RW %t, Name '%s', Driver '%s'", mp.Source, mp.Destination, mp.RW, mp.Name, mp.Driver)
166
+	return mp, nil
167
+}
168
+
169
+// IsVolumeNameValid checks a volume name in a platform specific manner.
170
+func IsVolumeNameValid(name string) (bool, error) {
171
+	nameExp := regexp.MustCompile(`^` + RXName + `$`)
172
+	if !nameExp.MatchString(name) {
173
+		return false, nil
174
+	}
175
+	nameExp = regexp.MustCompile(`^` + RXReservedNames + `$`)
176
+	if nameExp.MatchString(name) {
177
+		return false, derr.ErrorCodeVolumeNameReservedWord.WithArgs(name)
178
+	}
179
+	return true, nil
180
+}