Przeglądaj źródła

Volume refactoring for LCOW

Signed-off-by: Simon Ferquel <simon.ferquel@docker.com>
Simon Ferquel 8 lat temu
rodzic
commit
e89b6e8c2d

+ 6 - 1
container/container.go

@@ -435,6 +435,11 @@ func (container *Container) ShouldRestart() bool {
 
 // AddMountPointWithVolume adds a new mount point configured with a volume to the container.
 func (container *Container) AddMountPointWithVolume(destination string, vol volume.Volume, rw bool) {
+	operatingSystem := container.Platform
+	if operatingSystem == "" {
+		operatingSystem = runtime.GOOS
+	}
+	volumeParser := volume.NewParser(operatingSystem)
 	container.MountPoints[destination] = &volume.MountPoint{
 		Type:        mounttypes.TypeVolume,
 		Name:        vol.Name(),
@@ -442,7 +447,7 @@ func (container *Container) AddMountPointWithVolume(destination string, vol volu
 		Destination: destination,
 		RW:          rw,
 		Volume:      vol,
-		CopyData:    volume.DefaultCopyMode,
+		CopyData:    volumeParser.DefaultCopyMode(),
 	}
 }
 

+ 8 - 5
container/container_unix.go

@@ -68,6 +68,7 @@ func (container *Container) BuildHostnameFile() error {
 func (container *Container) NetworkMounts() []Mount {
 	var mounts []Mount
 	shared := container.HostConfig.NetworkMode.IsContainer()
+	parser := volume.NewParser(container.Platform)
 	if container.ResolvConfPath != "" {
 		if _, err := os.Stat(container.ResolvConfPath); err != nil {
 			logrus.Warnf("ResolvConfPath set to %q, but can't stat this filename (err = %v); skipping", container.ResolvConfPath, err)
@@ -83,7 +84,7 @@ func (container *Container) NetworkMounts() []Mount {
 				Source:      container.ResolvConfPath,
 				Destination: "/etc/resolv.conf",
 				Writable:    writable,
-				Propagation: string(volume.DefaultPropagationMode),
+				Propagation: string(parser.DefaultPropagationMode()),
 			})
 		}
 	}
@@ -102,7 +103,7 @@ func (container *Container) NetworkMounts() []Mount {
 				Source:      container.HostnamePath,
 				Destination: "/etc/hostname",
 				Writable:    writable,
-				Propagation: string(volume.DefaultPropagationMode),
+				Propagation: string(parser.DefaultPropagationMode()),
 			})
 		}
 	}
@@ -121,7 +122,7 @@ func (container *Container) NetworkMounts() []Mount {
 				Source:      container.HostsPath,
 				Destination: "/etc/hosts",
 				Writable:    writable,
-				Propagation: string(volume.DefaultPropagationMode),
+				Propagation: string(parser.DefaultPropagationMode()),
 			})
 		}
 	}
@@ -196,6 +197,7 @@ func (container *Container) UnmountIpcMount(unmount func(pth string) error) erro
 // IpcMounts returns the list of IPC mounts
 func (container *Container) IpcMounts() []Mount {
 	var mounts []Mount
+	parser := volume.NewParser(container.Platform)
 
 	if container.HasMountFor("/dev/shm") {
 		return mounts
@@ -209,7 +211,7 @@ func (container *Container) IpcMounts() []Mount {
 		Source:      container.ShmPath,
 		Destination: "/dev/shm",
 		Writable:    true,
-		Propagation: string(volume.DefaultPropagationMode),
+		Propagation: string(parser.DefaultPropagationMode()),
 	})
 
 	return mounts
@@ -429,6 +431,7 @@ func copyOwnership(source, destination string) error {
 
 // TmpfsMounts returns the list of tmpfs mounts
 func (container *Container) TmpfsMounts() ([]Mount, error) {
+	parser := volume.NewParser(container.Platform)
 	var mounts []Mount
 	for dest, data := range container.HostConfig.Tmpfs {
 		mounts = append(mounts, Mount{
@@ -439,7 +442,7 @@ func (container *Container) TmpfsMounts() ([]Mount, error) {
 	}
 	for dest, mnt := range container.MountPoints {
 		if mnt.Type == mounttypes.TypeTmpfs {
-			data, err := volume.ConvertTmpfsOptions(mnt.Spec.TmpfsOptions, mnt.Spec.ReadOnly)
+			data, err := parser.ConvertTmpfsOptions(mnt.Spec.TmpfsOptions, mnt.Spec.ReadOnly)
 			if err != nil {
 				return nil, err
 			}

+ 3 - 1
daemon/archive_unix.go

@@ -4,6 +4,7 @@ package daemon
 
 import (
 	"github.com/docker/docker/container"
+	"github.com/docker/docker/volume"
 )
 
 // checkIfPathIsInAVolume checks if the path is in a volume. If it is, it
@@ -11,8 +12,9 @@ import (
 // cannot be configured with a read-only rootfs.
 func checkIfPathIsInAVolume(container *container.Container, absPath string) (bool, error) {
 	var toVolume bool
+	parser := volume.NewParser(container.Platform)
 	for _, mnt := range container.MountPoints {
-		if toVolume = mnt.HasResource(absPath); toVolume {
+		if toVolume = parser.HasResource(mnt, absPath); toVolume {
 			if mnt.RW {
 				break
 			}

+ 2 - 2
daemon/create_windows.go

@@ -26,10 +26,10 @@ func (daemon *Daemon) createContainerPlatformSpecificSettings(container *contain
 		}
 		hostConfig.Isolation = "hyperv"
 	}
-
+	parser := volume.NewParser(container.Platform)
 	for spec := range config.Volumes {
 
-		mp, err := volume.ParseMountRaw(spec, hostConfig.VolumeDriver)
+		mp, err := parser.ParseMountRaw(spec, hostConfig.VolumeDriver)
 		if err != nil {
 			return fmt.Errorf("Unrecognised volume spec: %v", err)
 		}

+ 2 - 1
daemon/daemon_unix.go

@@ -612,8 +612,9 @@ func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes.
 		return warnings, fmt.Errorf("Unknown runtime specified %s", hostConfig.Runtime)
 	}
 
+	parser := volume.NewParser(runtime.GOOS)
 	for dest := range hostConfig.Tmpfs {
-		if err := volume.ValidateTmpfsMountDestination(dest); err != nil {
+		if err := parser.ValidateTmpfsMountDestination(dest); err != nil {
 			return warnings, err
 		}
 	}

+ 3 - 1
daemon/oci_linux.go

@@ -498,6 +498,7 @@ func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []c
 
 	// Filter out mounts from spec
 	noIpc := c.HostConfig.IpcMode.IsNone()
+	// Filter out mounts that are overridden by user supplied mounts
 	var defaultMounts []specs.Mount
 	_, mountDev := userMounts["/dev"]
 	for _, m := range s.Mounts {
@@ -524,7 +525,8 @@ func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []c
 
 		if m.Source == "tmpfs" {
 			data := m.Data
-			options := []string{"noexec", "nosuid", "nodev", string(volume.DefaultPropagationMode)}
+			parser := volume.NewParser("linux")
+			options := []string{"noexec", "nosuid", "nodev", string(parser.DefaultPropagationMode())}
 			if data != "" {
 				options = append(options, strings.Split(data, ",")...)
 			}

+ 6 - 0
daemon/oci_windows.go

@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"io/ioutil"
 	"path/filepath"
+	"runtime"
 	"strings"
 
 	containertypes "github.com/docker/docker/api/types/container"
@@ -108,6 +109,11 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
 		if !mount.Writable {
 			m.Options = append(m.Options, "ro")
 		}
+		if img.OS != runtime.GOOS {
+			m.Type = "bind"
+			m.Options = append(m.Options, "rbind")
+			m.Options = append(m.Options, fmt.Sprintf("uvmpath=/tmp/gcs/%s/binds", c.ID))
+		}
 		s.Mounts = append(s.Mounts, m)
 	}
 

+ 10 - 7
daemon/volumes.go

@@ -75,6 +75,7 @@ func (m mounts) parts(i int) int {
 func (daemon *Daemon) registerMountPoints(container *container.Container, hostConfig *containertypes.HostConfig) (retErr error) {
 	binds := map[string]bool{}
 	mountPoints := map[string]*volume.MountPoint{}
+	parser := volume.NewParser(container.Platform)
 	defer func() {
 		// clean up the container mountpoints once return with error
 		if retErr != nil {
@@ -103,7 +104,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
 
 	// 2. Read volumes from other containers.
 	for _, v := range hostConfig.VolumesFrom {
-		containerID, mode, err := volume.ParseVolumesFrom(v)
+		containerID, mode, err := parser.ParseVolumesFrom(v)
 		if err != nil {
 			return err
 		}
@@ -118,7 +119,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
 				Type:        m.Type,
 				Name:        m.Name,
 				Source:      m.Source,
-				RW:          m.RW && volume.ReadWrite(mode),
+				RW:          m.RW && parser.ReadWrite(mode),
 				Driver:      m.Driver,
 				Destination: m.Destination,
 				Propagation: m.Propagation,
@@ -140,7 +141,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
 
 	// 3. Read bind mounts
 	for _, b := range hostConfig.Binds {
-		bind, err := volume.ParseMountRaw(b, hostConfig.VolumeDriver)
+		bind, err := parser.ParseMountRaw(b, hostConfig.VolumeDriver)
 		if err != nil {
 			return err
 		}
@@ -172,7 +173,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
 	}
 
 	for _, cfg := range hostConfig.Mounts {
-		mp, err := volume.ParseMountSpec(cfg)
+		mp, err := parser.ParseMountSpec(cfg)
 		if err != nil {
 			return validationError{err}
 		}
@@ -217,7 +218,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
 
 	// 4. Cleanup old volumes that are about to be reassigned.
 	for _, m := range mountPoints {
-		if m.BackwardsCompatible() {
+		if parser.IsBackwardCompatible(m) {
 			if mp, exists := container.MountPoints[m.Destination]; exists && mp.Volume != nil {
 				daemon.volumes.Dereference(mp.Volume, container.ID)
 			}
@@ -252,6 +253,8 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) {
 	container.Lock()
 	defer container.Unlock()
 
+	parser := volume.NewParser(container.Platform)
+
 	maybeUpdate := make(map[string]bool)
 	for _, mp := range container.MountPoints {
 		if mp.Spec.Source != "" && mp.Type != "" {
@@ -270,7 +273,7 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) {
 
 	binds := make(map[string]*volume.MountPoint, len(container.HostConfig.Binds))
 	for _, rawSpec := range container.HostConfig.Binds {
-		mp, err := volume.ParseMountRaw(rawSpec, container.HostConfig.VolumeDriver)
+		mp, err := parser.ParseMountRaw(rawSpec, container.HostConfig.VolumeDriver)
 		if err != nil {
 			logrus.WithError(err).Error("Got unexpected error while re-parsing raw volume spec during spec backport")
 			continue
@@ -280,7 +283,7 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) {
 
 	volumesFrom := make(map[string]volume.MountPoint)
 	for _, fromSpec := range container.HostConfig.VolumesFrom {
-		from, _, err := volume.ParseVolumesFrom(fromSpec)
+		from, _, err := parser.ParseVolumesFrom(fromSpec)
 		if err != nil {
 			logrus.WithError(err).WithField("id", container.ID).Error("Error reading volumes-from spec during mount spec backport")
 			continue

+ 4 - 1
daemon/volumes_unit_test.go

@@ -1,6 +1,7 @@
 package daemon
 
 import (
+	"runtime"
 	"testing"
 
 	"github.com/docker/docker/volume"
@@ -20,8 +21,10 @@ func TestParseVolumesFrom(t *testing.T) {
 		{"foobar:baz", "", "", true},
 	}
 
+	parser := volume.NewParser(runtime.GOOS)
+
 	for _, c := range cases {
-		id, mode, err := volume.ParseVolumesFrom(c.spec)
+		id, mode, err := parser.ParseVolumesFrom(c.spec)
 		if c.fail {
 			if err == nil {
 				t.Fatalf("Expected error, was nil, for spec %s\n", c.spec)

+ 91 - 0
libcontainerd/client_windows.go

@@ -7,6 +7,7 @@ import (
 	"io"
 	"io/ioutil"
 	"os"
+	"path"
 	"path/filepath"
 	"regexp"
 	"strings"
@@ -388,11 +389,101 @@ func (clnt *client) createLinux(containerID string, checkpoint string, checkpoin
 		configuration.NetworkSharedContainerName = spec.Windows.Network.NetworkSharedContainerName
 	}
 
+	// Add the mounts (volumes, bind mounts etc) to the structure. We have to do
+	// some translation for both the mapped directories passed into HCS and in
+	// the spec.
+	//
+	// For HCS, we only pass in the mounts from the spec which are type "bind".
+	// Further, the "ContainerPath" field (which is a little mis-leadingly
+	// named when it applies to the utility VM rather than the container in the
+	// utility VM) is moved to under /tmp/gcs/<ID>/binds, where this is passed
+	// by the caller through a 'uvmpath' option.
+	//
+	// We do similar translation for the mounts in the spec by stripping out
+	// the uvmpath option, and translating the Source path to the location in the
+	// utility VM calculated above.
+	//
+	// From inside the utility VM, you would see a 9p mount such as in the following
+	// where a host folder has been mapped to /target. The line with /tmp/gcs/<ID>/binds
+	// specifically:
+	//
+	//	/ # mount
+	//	rootfs on / type rootfs (rw,size=463736k,nr_inodes=115934)
+	//	proc on /proc type proc (rw,relatime)
+	//	sysfs on /sys type sysfs (rw,relatime)
+	//	udev on /dev type devtmpfs (rw,relatime,size=498100k,nr_inodes=124525,mode=755)
+	//	tmpfs on /run type tmpfs (rw,relatime)
+	//	cgroup on /sys/fs/cgroup type cgroup (rw,relatime,cpuset,cpu,cpuacct,blkio,memory,devices,freezer,net_cls,perf_event,net_prio,hugetlb,pids,rdma)
+	//	mqueue on /dev/mqueue type mqueue (rw,relatime)
+	//	devpts on /dev/pts type devpts (rw,relatime,mode=600,ptmxmode=000)
+	//	/binds/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/target on /binds/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/target type 9p (rw,sync,dirsync,relatime,trans=fd,rfdno=6,wfdno=6)
+	//	/dev/pmem0 on /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/layer0 type ext4 (ro,relatime,block_validity,delalloc,norecovery,barrier,dax,user_xattr,acl)
+	//	/dev/sda on /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/scratch type ext4 (rw,relatime,block_validity,delalloc,barrier,user_xattr,acl)
+	//	overlay on /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/rootfs type overlay (rw,relatime,lowerdir=/tmp/base/:/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/layer0,upperdir=/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/scratch/upper,workdir=/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/scratch/work)
+	//
+	//  /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc # ls -l
+	//	total 16
+	//	drwx------    3 0        0               60 Sep  7 18:54 binds
+	//	-rw-r--r--    1 0        0             3345 Sep  7 18:54 config.json
+	//	drwxr-xr-x   10 0        0             4096 Sep  6 17:26 layer0
+	//	drwxr-xr-x    1 0        0             4096 Sep  7 18:54 rootfs
+	//	drwxr-xr-x    5 0        0             4096 Sep  7 18:54 scratch
+	//
+	//	/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc # ls -l binds
+	//	total 0
+	//	drwxrwxrwt    2 0        0             4096 Sep  7 16:51 target
+
+	mds := []hcsshim.MappedDir{}
+	specMounts := []specs.Mount{}
+	for _, mount := range spec.Mounts {
+		specMount := mount
+		if mount.Type == "bind" {
+			// Strip out the uvmpath from the options
+			updatedOptions := []string{}
+			uvmPath := ""
+			readonly := false
+			for _, opt := range mount.Options {
+				dropOption := false
+				elements := strings.SplitN(opt, "=", 2)
+				switch elements[0] {
+				case "uvmpath":
+					uvmPath = elements[1]
+					dropOption = true
+				case "rw":
+				case "ro":
+					readonly = true
+				case "rbind":
+				default:
+					return fmt.Errorf("unsupported option %q", opt)
+				}
+				if !dropOption {
+					updatedOptions = append(updatedOptions, opt)
+				}
+			}
+			mount.Options = updatedOptions
+			if uvmPath == "" {
+				return fmt.Errorf("no uvmpath for bind mount %+v", mount)
+			}
+			md := hcsshim.MappedDir{
+				HostPath:          mount.Source,
+				ContainerPath:     path.Join(uvmPath, mount.Destination),
+				CreateInUtilityVM: true,
+				ReadOnly:          readonly,
+			}
+			mds = append(mds, md)
+			specMount.Source = path.Join(uvmPath, mount.Destination)
+		}
+		specMounts = append(specMounts, specMount)
+	}
+	configuration.MappedDirectories = mds
+
 	hcsContainer, err := hcsshim.CreateContainer(containerID, configuration)
 	if err != nil {
 		return err
 	}
 
+	spec.Mounts = specMounts
+
 	// Construct a container object for calling start on it.
 	container := &container{
 		containerCommon: containerCommon{

+ 0 - 27
runconfig/config.go

@@ -7,8 +7,6 @@ import (
 	"github.com/docker/docker/api/types/container"
 	networktypes "github.com/docker/docker/api/types/network"
 	"github.com/docker/docker/pkg/sysinfo"
-	"github.com/docker/docker/volume"
-	"github.com/pkg/errors"
 )
 
 // ContainerDecoder implements httputils.ContainerDecoder
@@ -46,11 +44,6 @@ func decodeContainerConfig(src io.Reader) (*container.Config, *container.HostCon
 		if w.Config.Volumes == nil {
 			w.Config.Volumes = make(map[string]struct{})
 		}
-
-		// Now validate all the volumes and binds
-		if err := validateMountSettings(w.Config, hc); err != nil {
-			return nil, nil, nil, err
-		}
 	}
 
 	// Certain parameters need daemon-side validation that cannot be done
@@ -86,23 +79,3 @@ func decodeContainerConfig(src io.Reader) (*container.Config, *container.HostCon
 
 	return w.Config, hc, w.NetworkingConfig, nil
 }
-
-// validateMountSettings validates each of the volumes and bind settings
-// passed by the caller to ensure they are valid.
-func validateMountSettings(c *container.Config, hc *container.HostConfig) error {
-	// it is ok to have len(hc.Mounts) > 0 && (len(hc.Binds) > 0 || len (c.Volumes) > 0 || len (hc.Tmpfs) > 0 )
-
-	// Ensure all volumes and binds are valid.
-	for spec := range c.Volumes {
-		if _, err := volume.ParseMountRaw(spec, hc.VolumeDriver); err != nil {
-			return errors.Wrapf(err, "invalid volume spec %q", spec)
-		}
-	}
-	for _, spec := range hc.Binds {
-		if _, err := volume.ParseMountRaw(spec, hc.VolumeDriver); err != nil {
-			return errors.Wrapf(err, "invalid bind mount spec %q", spec)
-		}
-	}
-
-	return nil
-}

+ 0 - 183
runconfig/config_test.go

@@ -9,12 +9,9 @@ import (
 	"strings"
 	"testing"
 
-	"os"
-
 	"github.com/docker/docker/api/types/container"
 	networktypes "github.com/docker/docker/api/types/network"
 	"github.com/docker/docker/api/types/strslice"
-	"github.com/gotestyourself/gotestyourself/skip"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
@@ -143,103 +140,6 @@ func callDecodeContainerConfigIsolation(isolation string) (*container.Config, *c
 	return decodeContainerConfig(bytes.NewReader(b))
 }
 
-func TestDecodeContainerConfigWithVolumes(t *testing.T) {
-	var testcases = []decodeConfigTestcase{
-		{
-			doc:         "no paths volume",
-			wrapper:     containerWrapperWithVolume(":"),
-			expectedErr: `invalid volume specification: ':'`,
-		},
-		{
-			doc:         "no paths bind",
-			wrapper:     containerWrapperWithBind(":"),
-			expectedErr: `invalid volume specification: ':'`,
-		},
-		{
-			doc:         "no paths or mode volume",
-			wrapper:     containerWrapperWithVolume("::"),
-			expectedErr: `invalid volume specification: '::'`,
-		},
-		{
-			doc:         "no paths or mode bind",
-			wrapper:     containerWrapperWithBind("::"),
-			expectedErr: `invalid volume specification: '::'`,
-		},
-	}
-	for _, testcase := range testcases {
-		t.Run(testcase.doc, runDecodeContainerConfigTestCase(testcase))
-	}
-}
-
-func TestDecodeContainerConfigWithVolumesUnix(t *testing.T) {
-	skip.IfCondition(t, runtime.GOOS == "windows")
-
-	baseErr := `invalid mount config for type "volume": invalid specification: `
-	var testcases = []decodeConfigTestcase{
-		{
-			doc:         "root to root volume",
-			wrapper:     containerWrapperWithVolume("/:/"),
-			expectedErr: `invalid volume specification: '/:/'`,
-		},
-		{
-			doc:         "root to root bind",
-			wrapper:     containerWrapperWithBind("/:/"),
-			expectedErr: `invalid volume specification: '/:/'`,
-		},
-		{
-			doc:         "no destination path volume",
-			wrapper:     containerWrapperWithVolume(`/tmp:`),
-			expectedErr: ` invalid volume specification: '/tmp:'`,
-		},
-		{
-			doc:         "no destination path bind",
-			wrapper:     containerWrapperWithBind(`/tmp:`),
-			expectedErr: ` invalid volume specification: '/tmp:'`,
-		},
-		{
-			doc:         "no destination path or mode volume",
-			wrapper:     containerWrapperWithVolume(`/tmp::`),
-			expectedErr: `invalid mount config for type "bind": field Target must not be empty`,
-		},
-		{
-			doc:         "no destination path or mode bind",
-			wrapper:     containerWrapperWithBind(`/tmp::`),
-			expectedErr: `invalid mount config for type "bind": field Target must not be empty`,
-		},
-		{
-			doc:         "too many sections volume",
-			wrapper:     containerWrapperWithVolume(`/tmp:/tmp:/tmp:/tmp`),
-			expectedErr: `invalid volume specification: '/tmp:/tmp:/tmp:/tmp'`,
-		},
-		{
-			doc:         "too many sections bind",
-			wrapper:     containerWrapperWithBind(`/tmp:/tmp:/tmp:/tmp`),
-			expectedErr: `invalid volume specification: '/tmp:/tmp:/tmp:/tmp'`,
-		},
-		{
-			doc:         "just root volume",
-			wrapper:     containerWrapperWithVolume("/"),
-			expectedErr: baseErr + `destination can't be '/'`,
-		},
-		{
-			doc:         "just root bind",
-			wrapper:     containerWrapperWithBind("/"),
-			expectedErr: baseErr + `destination can't be '/'`,
-		},
-		{
-			doc:     "bind mount passed as a volume",
-			wrapper: containerWrapperWithVolume(`/foo:/bar`),
-			expectedConfig: &container.Config{
-				Volumes: map[string]struct{}{`/foo:/bar`: {}},
-			},
-			expectedHostConfig: &container.HostConfig{NetworkMode: "default"},
-		},
-	}
-	for _, testcase := range testcases {
-		t.Run(testcase.doc, runDecodeContainerConfigTestCase(testcase))
-	}
-}
-
 type decodeConfigTestcase struct {
 	doc                string
 	wrapper            ContainerConfigWrapper
@@ -266,89 +166,6 @@ func runDecodeContainerConfigTestCase(testcase decodeConfigTestcase) func(t *tes
 	}
 }
 
-func TestDecodeContainerConfigWithVolumesWindows(t *testing.T) {
-	skip.IfCondition(t, runtime.GOOS != "windows")
-
-	tmpDir := os.Getenv("TEMP")
-	systemDrive := os.Getenv("SystemDrive")
-	var testcases = []decodeConfigTestcase{
-		{
-			doc:         "root to root volume",
-			wrapper:     containerWrapperWithVolume(systemDrive + `\:c:\`),
-			expectedErr: `invalid volume specification: `,
-		},
-		{
-			doc:         "root to root bind",
-			wrapper:     containerWrapperWithBind(systemDrive + `\:c:\`),
-			expectedErr: `invalid volume specification: `,
-		},
-		{
-			doc:         "no destination path volume",
-			wrapper:     containerWrapperWithVolume(tmpDir + `\:`),
-			expectedErr: `invalid volume specification: `,
-		},
-		{
-			doc:         "no destination path bind",
-			wrapper:     containerWrapperWithBind(tmpDir + `\:`),
-			expectedErr: `invalid volume specification: `,
-		},
-		{
-			doc:         "no destination path or mode volume",
-			wrapper:     containerWrapperWithVolume(tmpDir + `\::`),
-			expectedErr: `invalid volume specification: `,
-		},
-		{
-			doc:         "no destination path or mode bind",
-			wrapper:     containerWrapperWithBind(tmpDir + `\::`),
-			expectedErr: `invalid volume specification: `,
-		},
-		{
-			doc:         "too many sections volume",
-			wrapper:     containerWrapperWithVolume(tmpDir + ":" + tmpDir + ":" + tmpDir + ":" + tmpDir),
-			expectedErr: `invalid volume specification: `,
-		},
-		{
-			doc:         "too many sections bind",
-			wrapper:     containerWrapperWithBind(tmpDir + ":" + tmpDir + ":" + tmpDir + ":" + tmpDir),
-			expectedErr: `invalid volume specification: `,
-		},
-		{
-			doc:         "no drive letter volume",
-			wrapper:     containerWrapperWithVolume(`\tmp`),
-			expectedErr: `invalid volume specification: `,
-		},
-		{
-			doc:         "no drive letter bind",
-			wrapper:     containerWrapperWithBind(`\tmp`),
-			expectedErr: `invalid volume specification: `,
-		},
-		{
-			doc:         "root to c-drive volume",
-			wrapper:     containerWrapperWithVolume(systemDrive + `\:c:`),
-			expectedErr: `invalid volume specification: `,
-		},
-		{
-			doc:         "root to c-drive bind",
-			wrapper:     containerWrapperWithBind(systemDrive + `\:c:`),
-			expectedErr: `invalid volume specification: `,
-		},
-		{
-			doc:         "container path without driver letter volume",
-			wrapper:     containerWrapperWithVolume(`c:\windows:\somewhere`),
-			expectedErr: `invalid volume specification: `,
-		},
-		{
-			doc:         "container path without driver letter bind",
-			wrapper:     containerWrapperWithBind(`c:\windows:\somewhere`),
-			expectedErr: `invalid volume specification: `,
-		},
-	}
-
-	for _, testcase := range testcases {
-		t.Run(testcase.doc, runDecodeContainerConfigTestCase(testcase))
-	}
-}
-
 func marshal(t *testing.T, w ContainerConfigWrapper, doc string) []byte {
 	b, err := json.Marshal(w)
 	require.NoError(t, err, "%s: failed to encode config wrapper", doc)

+ 35 - 0
volume/lcow_parser.go

@@ -0,0 +1,35 @@
+package volume
+
+import (
+	"errors"
+	"fmt"
+	"path"
+
+	"github.com/docker/docker/api/types/mount"
+)
+
+var lcowSpecificValidators mountValidator = func(m *mount.Mount) error {
+	if path.Clean(m.Target) == "/" {
+		return fmt.Errorf("invalid specification: destination can't be '/'")
+	}
+	if m.Type == mount.TypeNamedPipe {
+		return errors.New("Linux containers on Windows do not support named pipe mounts")
+	}
+	return nil
+}
+
+type lcowParser struct {
+	windowsParser
+}
+
+func (p *lcowParser) validateMountConfig(mnt *mount.Mount) error {
+	return p.validateMountConfigReg(mnt, rxLCOWDestination, lcowSpecificValidators)
+}
+
+func (p *lcowParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
+	return p.parseMountRaw(raw, volumeDriver, rxLCOWDestination, false, lcowSpecificValidators)
+}
+
+func (p *lcowParser) ParseMountSpec(cfg mount.Mount) (*MountPoint, error) {
+	return p.parseMountSpec(cfg, rxLCOWDestination, false, lcowSpecificValidators)
+}

+ 416 - 0
volume/linux_parser.go

@@ -0,0 +1,416 @@
+package volume
+
+import (
+	"errors"
+	"fmt"
+	"path"
+	"path/filepath"
+	"strings"
+
+	"github.com/docker/docker/api/types/mount"
+	"github.com/docker/docker/pkg/stringid"
+)
+
+type linuxParser struct {
+}
+
+func linuxSplitRawSpec(raw string) ([]string, error) {
+	if strings.Count(raw, ":") > 2 {
+		return nil, errInvalidSpec(raw)
+	}
+
+	arr := strings.SplitN(raw, ":", 3)
+	if arr[0] == "" {
+		return nil, errInvalidSpec(raw)
+	}
+	return arr, nil
+}
+
+func linuxValidateNotRoot(p string) error {
+	p = path.Clean(strings.Replace(p, `\`, `/`, -1))
+	if p == "/" {
+		return fmt.Errorf("invalid specification: destination can't be '/'")
+	}
+	return nil
+}
+func linuxValidateAbsolute(p string) error {
+	p = strings.Replace(p, `\`, `/`, -1)
+	if path.IsAbs(p) {
+		return nil
+	}
+	return fmt.Errorf("invalid mount path: '%s' mount path must be absolute", p)
+}
+func (p *linuxParser) validateMountConfig(mnt *mount.Mount) error {
+	// there was something looking like a bug in existing codebase:
+	// - validateMountConfig on linux was called with options skipping bind source existance when calling ParseMountRaw
+	// - but not when calling ParseMountSpec directly... nor when the unit test called it directly
+	return p.validateMountConfigImpl(mnt, true)
+}
+func (p *linuxParser) validateMountConfigImpl(mnt *mount.Mount, validateBindSourceExists bool) error {
+	if len(mnt.Target) == 0 {
+		return &errMountConfig{mnt, errMissingField("Target")}
+	}
+
+	if err := linuxValidateNotRoot(mnt.Target); err != nil {
+		return &errMountConfig{mnt, err}
+	}
+
+	if err := linuxValidateAbsolute(mnt.Target); err != nil {
+		return &errMountConfig{mnt, err}
+	}
+
+	switch mnt.Type {
+	case mount.TypeBind:
+		if len(mnt.Source) == 0 {
+			return &errMountConfig{mnt, errMissingField("Source")}
+		}
+		// Don't error out just because the propagation mode is not supported on the platform
+		if opts := mnt.BindOptions; opts != nil {
+			if len(opts.Propagation) > 0 && len(linuxPropagationModes) > 0 {
+				if _, ok := linuxPropagationModes[opts.Propagation]; !ok {
+					return &errMountConfig{mnt, fmt.Errorf("invalid propagation mode: %s", opts.Propagation)}
+				}
+			}
+		}
+		if mnt.VolumeOptions != nil {
+			return &errMountConfig{mnt, errExtraField("VolumeOptions")}
+		}
+
+		if err := linuxValidateAbsolute(mnt.Source); err != nil {
+			return &errMountConfig{mnt, err}
+		}
+
+		if validateBindSourceExists {
+			exists, _, _ := currentFileInfoProvider.fileInfo(mnt.Source)
+			if !exists {
+				return &errMountConfig{mnt, errBindNotExist}
+			}
+		}
+
+	case mount.TypeVolume:
+		if mnt.BindOptions != nil {
+			return &errMountConfig{mnt, errExtraField("BindOptions")}
+		}
+
+		if len(mnt.Source) == 0 && mnt.ReadOnly {
+			return &errMountConfig{mnt, fmt.Errorf("must not set ReadOnly mode when using anonymous volumes")}
+		}
+	case mount.TypeTmpfs:
+		if len(mnt.Source) != 0 {
+			return &errMountConfig{mnt, errExtraField("Source")}
+		}
+		if _, err := p.ConvertTmpfsOptions(mnt.TmpfsOptions, mnt.ReadOnly); err != nil {
+			return &errMountConfig{mnt, err}
+		}
+	default:
+		return &errMountConfig{mnt, errors.New("mount type unknown")}
+	}
+	return nil
+}
+
+// read-write modes
+var rwModes = map[string]bool{
+	"rw": true,
+	"ro": true,
+}
+
+// label modes
+var linuxLabelModes = map[string]bool{
+	"Z": true,
+	"z": true,
+}
+
+// consistency modes
+var linuxConsistencyModes = map[mount.Consistency]bool{
+	mount.ConsistencyFull:      true,
+	mount.ConsistencyCached:    true,
+	mount.ConsistencyDelegated: true,
+}
+var linuxPropagationModes = map[mount.Propagation]bool{
+	mount.PropagationPrivate:  true,
+	mount.PropagationRPrivate: true,
+	mount.PropagationSlave:    true,
+	mount.PropagationRSlave:   true,
+	mount.PropagationShared:   true,
+	mount.PropagationRShared:  true,
+}
+
+const linuxDefaultPropagationMode = mount.PropagationRPrivate
+
+func linuxGetPropagation(mode string) mount.Propagation {
+	for _, o := range strings.Split(mode, ",") {
+		prop := mount.Propagation(o)
+		if linuxPropagationModes[prop] {
+			return prop
+		}
+	}
+	return linuxDefaultPropagationMode
+}
+
+func linuxHasPropagation(mode string) bool {
+	for _, o := range strings.Split(mode, ",") {
+		if linuxPropagationModes[mount.Propagation(o)] {
+			return true
+		}
+	}
+	return false
+}
+
+func linuxValidMountMode(mode string) bool {
+	if mode == "" {
+		return true
+	}
+
+	rwModeCount := 0
+	labelModeCount := 0
+	propagationModeCount := 0
+	copyModeCount := 0
+	consistencyModeCount := 0
+
+	for _, o := range strings.Split(mode, ",") {
+		switch {
+		case rwModes[o]:
+			rwModeCount++
+		case linuxLabelModes[o]:
+			labelModeCount++
+		case linuxPropagationModes[mount.Propagation(o)]:
+			propagationModeCount++
+		case copyModeExists(o):
+			copyModeCount++
+		case linuxConsistencyModes[mount.Consistency(o)]:
+			consistencyModeCount++
+		default:
+			return false
+		}
+	}
+
+	// Only one string for each mode is allowed.
+	if rwModeCount > 1 || labelModeCount > 1 || propagationModeCount > 1 || copyModeCount > 1 || consistencyModeCount > 1 {
+		return false
+	}
+	return true
+}
+
+func (p *linuxParser) ReadWrite(mode string) bool {
+	if !linuxValidMountMode(mode) {
+		return false
+	}
+
+	for _, o := range strings.Split(mode, ",") {
+		if o == "ro" {
+			return false
+		}
+	}
+	return true
+}
+
+func (p *linuxParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
+	arr, err := linuxSplitRawSpec(raw)
+	if err != nil {
+		return nil, err
+	}
+
+	var spec mount.Mount
+	var mode string
+	switch len(arr) {
+	case 1:
+		// Just a destination path in the container
+		spec.Target = arr[0]
+	case 2:
+		if linuxValidMountMode(arr[1]) {
+			// Destination + Mode is not a valid volume - volumes
+			// cannot include a mode. e.g. /foo:rw
+			return nil, errInvalidSpec(raw)
+		}
+		// Host Source Path or Name + Destination
+		spec.Source = arr[0]
+		spec.Target = arr[1]
+	case 3:
+		// HostSourcePath+DestinationPath+Mode
+		spec.Source = arr[0]
+		spec.Target = arr[1]
+		mode = arr[2]
+	default:
+		return nil, errInvalidSpec(raw)
+	}
+
+	if !linuxValidMountMode(mode) {
+		return nil, errInvalidMode(mode)
+	}
+
+	if path.IsAbs(spec.Source) {
+		spec.Type = mount.TypeBind
+	} else {
+		spec.Type = mount.TypeVolume
+	}
+
+	spec.ReadOnly = !p.ReadWrite(mode)
+
+	// cannot assume that if a volume driver is passed in that we should set it
+	if volumeDriver != "" && spec.Type == mount.TypeVolume {
+		spec.VolumeOptions = &mount.VolumeOptions{
+			DriverConfig: &mount.Driver{Name: volumeDriver},
+		}
+	}
+
+	if copyData, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
+		if spec.VolumeOptions == nil {
+			spec.VolumeOptions = &mount.VolumeOptions{}
+		}
+		spec.VolumeOptions.NoCopy = !copyData
+	}
+	if linuxHasPropagation(mode) {
+		spec.BindOptions = &mount.BindOptions{
+			Propagation: linuxGetPropagation(mode),
+		}
+	}
+
+	mp, err := p.parseMountSpec(spec, false)
+	if mp != nil {
+		mp.Mode = mode
+	}
+	if err != nil {
+		err = fmt.Errorf("%v: %v", errInvalidSpec(raw), err)
+	}
+	return mp, err
+}
+func (p *linuxParser) ParseMountSpec(cfg mount.Mount) (*MountPoint, error) {
+	return p.parseMountSpec(cfg, true)
+}
+func (p *linuxParser) parseMountSpec(cfg mount.Mount, validateBindSourceExists bool) (*MountPoint, error) {
+	if err := p.validateMountConfigImpl(&cfg, validateBindSourceExists); err != nil {
+		return nil, err
+	}
+	mp := &MountPoint{
+		RW:          !cfg.ReadOnly,
+		Destination: path.Clean(filepath.ToSlash(cfg.Target)),
+		Type:        cfg.Type,
+		Spec:        cfg,
+	}
+
+	switch cfg.Type {
+	case mount.TypeVolume:
+		if cfg.Source == "" {
+			mp.Name = stringid.GenerateNonCryptoID()
+		} else {
+			mp.Name = cfg.Source
+		}
+		mp.CopyData = p.DefaultCopyMode()
+
+		if cfg.VolumeOptions != nil {
+			if cfg.VolumeOptions.DriverConfig != nil {
+				mp.Driver = cfg.VolumeOptions.DriverConfig.Name
+			}
+			if cfg.VolumeOptions.NoCopy {
+				mp.CopyData = false
+			}
+		}
+	case mount.TypeBind:
+		mp.Source = path.Clean(filepath.ToSlash(cfg.Source))
+		if cfg.BindOptions != nil && len(cfg.BindOptions.Propagation) > 0 {
+			mp.Propagation = cfg.BindOptions.Propagation
+		} else {
+			// If user did not specify a propagation mode, get
+			// default propagation mode.
+			mp.Propagation = linuxDefaultPropagationMode
+		}
+	case mount.TypeTmpfs:
+		// NOP
+	}
+	return mp, nil
+}
+
+func (p *linuxParser) ParseVolumesFrom(spec string) (string, string, error) {
+	if len(spec) == 0 {
+		return "", "", fmt.Errorf("volumes-from specification cannot be an empty string")
+	}
+
+	specParts := strings.SplitN(spec, ":", 2)
+	id := specParts[0]
+	mode := "rw"
+
+	if len(specParts) == 2 {
+		mode = specParts[1]
+		if !linuxValidMountMode(mode) {
+			return "", "", errInvalidMode(mode)
+		}
+		// For now don't allow propagation properties while importing
+		// volumes from data container. These volumes will inherit
+		// the same propagation property as of the original volume
+		// in data container. This probably can be relaxed in future.
+		if linuxHasPropagation(mode) {
+			return "", "", errInvalidMode(mode)
+		}
+		// Do not allow copy modes on volumes-from
+		if _, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
+			return "", "", errInvalidMode(mode)
+		}
+	}
+	return id, mode, nil
+}
+
+func (p *linuxParser) DefaultPropagationMode() mount.Propagation {
+	return linuxDefaultPropagationMode
+}
+
+func (p *linuxParser) ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error) {
+	var rawOpts []string
+	if readOnly {
+		rawOpts = append(rawOpts, "ro")
+	}
+
+	if opt != nil && opt.Mode != 0 {
+		rawOpts = append(rawOpts, fmt.Sprintf("mode=%o", opt.Mode))
+	}
+
+	if opt != nil && opt.SizeBytes != 0 {
+		// calculate suffix here, making this linux specific, but that is
+		// okay, since API is that way anyways.
+
+		// we do this by finding the suffix that divides evenly into the
+		// value, returning the value itself, with no suffix, if it fails.
+		//
+		// For the most part, we don't enforce any semantic to this values.
+		// The operating system will usually align this and enforce minimum
+		// and maximums.
+		var (
+			size   = opt.SizeBytes
+			suffix string
+		)
+		for _, r := range []struct {
+			suffix  string
+			divisor int64
+		}{
+			{"g", 1 << 30},
+			{"m", 1 << 20},
+			{"k", 1 << 10},
+		} {
+			if size%r.divisor == 0 {
+				size = size / r.divisor
+				suffix = r.suffix
+				break
+			}
+		}
+
+		rawOpts = append(rawOpts, fmt.Sprintf("size=%d%s", size, suffix))
+	}
+	return strings.Join(rawOpts, ","), nil
+}
+
+func (p *linuxParser) DefaultCopyMode() bool {
+	return true
+}
+func (p *linuxParser) ValidateVolumeName(name string) error {
+	return nil
+}
+
+func (p *linuxParser) IsBackwardCompatible(m *MountPoint) bool {
+	return len(m.Source) > 0 || m.Driver == DefaultDriverName
+}
+
+func (p *linuxParser) ValidateTmpfsMountDestination(dest string) error {
+	if err := linuxValidateNotRoot(dest); err != nil {
+		return err
+	}
+	return linuxValidateAbsolute(dest)
+}

+ 43 - 0
volume/parser.go

@@ -0,0 +1,43 @@
+package volume
+
+import (
+	"runtime"
+
+	"github.com/docker/docker/api/types/mount"
+)
+
+const (
+	// OSLinux is the same as runtime.GOOS on linux
+	OSLinux = "linux"
+	// OSWindows is the same as runtime.GOOS on windows
+	OSWindows = "windows"
+)
+
+// Parser represents a platform specific parser for mount expressions
+type Parser interface {
+	ParseMountRaw(raw, volumeDriver string) (*MountPoint, error)
+	ParseMountSpec(cfg mount.Mount) (*MountPoint, error)
+	ParseVolumesFrom(spec string) (string, string, error)
+	DefaultPropagationMode() mount.Propagation
+	ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error)
+	DefaultCopyMode() bool
+	ValidateVolumeName(name string) error
+	ReadWrite(mode string) bool
+	IsBackwardCompatible(m *MountPoint) bool
+	HasResource(m *MountPoint, absPath string) bool
+	ValidateTmpfsMountDestination(dest string) error
+
+	validateMountConfig(mt *mount.Mount) error
+}
+
+// NewParser creates a parser for a given container OS, depending on the current host OS (linux on a windows host will resolve to an lcowParser)
+func NewParser(containerOS string) Parser {
+	switch containerOS {
+	case OSWindows:
+		return &windowsParser{}
+	}
+	if runtime.GOOS == OSWindows {
+		return &lcowParser{}
+	}
+	return &linuxParser{}
+}

+ 0 - 9
volume/store/errors.go

@@ -9,8 +9,6 @@ const (
 	errVolumeInUse conflictError = "volume is in use"
 	// errNoSuchVolume is a typed error returned if the requested volume doesn't exist in the volume store
 	errNoSuchVolume notFoundError = "no such volume"
-	// errInvalidName is a typed error returned when creating a volume with a name that is not valid on the platform
-	errInvalidName invalidName = "volume name is not valid on this platform"
 	// errNameConflict is a typed error returned on create when a volume exists with the given name, but for a different driver
 	errNameConflict conflictError = "volume name must be unique"
 )
@@ -30,13 +28,6 @@ func (e notFoundError) Error() string {
 
 func (notFoundError) NotFound() {}
 
-type invalidName string
-
-func (e invalidName) Error() string {
-	return string(e)
-}
-func (invalidName) InvalidParameter() {}
-
 // OpErr is the error type returned by functions in the store package. It describes
 // the operation, volume name, and error.
 type OpErr struct {

+ 6 - 4
volume/store/store.go

@@ -4,6 +4,7 @@ import (
 	"net"
 	"os"
 	"path/filepath"
+	"runtime"
 	"sync"
 	"time"
 
@@ -369,13 +370,14 @@ func volumeExists(v volume.Volume) (bool, error) {
 // It is expected that callers of this function hold any necessary locks.
 func (s *VolumeStore) create(name, driverName string, opts, labels map[string]string) (volume.Volume, error) {
 	// Validate the name in a platform-specific manner
-	valid, err := volume.IsVolumeNameValid(name)
+
+	// volume name validation is specific to the host os and not on container image
+	// windows/lcow should have an equivalent volumename validation logic so we create a parser for current host OS
+	parser := volume.NewParser(runtime.GOOS)
+	err := parser.ValidateVolumeName(name)
 	if err != nil {
 		return nil, err
 	}
-	if !valid {
-		return nil, &OpErr{Err: errInvalidName, Name: name, Op: "create"}
-	}
 
 	v, err := s.checkConflict(name, driverName)
 	if err != nil {

+ 0 - 150
volume/validate.go

@@ -2,8 +2,6 @@ package volume
 
 import (
 	"fmt"
-	"os"
-	"runtime"
 
 	"github.com/docker/docker/api/types/mount"
 	"github.com/pkg/errors"
@@ -11,120 +9,6 @@ import (
 
 var errBindNotExist = errors.New("bind source path does not exist")
 
-type validateOpts struct {
-	skipBindSourceCheck bool
-}
-
-func validateMountConfig(mnt *mount.Mount, options ...func(*validateOpts)) error {
-	opts := validateOpts{}
-	for _, o := range options {
-		o(&opts)
-	}
-
-	if len(mnt.Target) == 0 {
-		return &errMountConfig{mnt, errMissingField("Target")}
-	}
-
-	if err := validateNotRoot(mnt.Target); err != nil {
-		return &errMountConfig{mnt, err}
-	}
-
-	if err := validateAbsolute(mnt.Target); err != nil {
-		return &errMountConfig{mnt, err}
-	}
-
-	switch mnt.Type {
-	case mount.TypeBind:
-		if len(mnt.Source) == 0 {
-			return &errMountConfig{mnt, errMissingField("Source")}
-		}
-		// Don't error out just because the propagation mode is not supported on the platform
-		if opts := mnt.BindOptions; opts != nil {
-			if len(opts.Propagation) > 0 && len(propagationModes) > 0 {
-				if _, ok := propagationModes[opts.Propagation]; !ok {
-					return &errMountConfig{mnt, fmt.Errorf("invalid propagation mode: %s", opts.Propagation)}
-				}
-			}
-		}
-		if mnt.VolumeOptions != nil {
-			return &errMountConfig{mnt, errExtraField("VolumeOptions")}
-		}
-
-		if err := validateAbsolute(mnt.Source); err != nil {
-			return &errMountConfig{mnt, err}
-		}
-
-		// Do not allow binding to non-existent path
-		if !opts.skipBindSourceCheck {
-			fi, err := os.Stat(mnt.Source)
-			if err != nil {
-				if !os.IsNotExist(err) {
-					return &errMountConfig{mnt, err}
-				}
-				return &errMountConfig{mnt, errBindNotExist}
-			}
-			if err := validateStat(fi); err != nil {
-				return &errMountConfig{mnt, err}
-			}
-		}
-	case mount.TypeVolume:
-		if mnt.BindOptions != nil {
-			return &errMountConfig{mnt, errExtraField("BindOptions")}
-		}
-
-		if len(mnt.Source) == 0 && mnt.ReadOnly {
-			return &errMountConfig{mnt, fmt.Errorf("must not set ReadOnly mode when using anonymous volumes")}
-		}
-
-		if len(mnt.Source) != 0 {
-			if valid, err := IsVolumeNameValid(mnt.Source); !valid {
-				if err == nil {
-					err = errors.New("invalid volume name")
-				}
-				return &errMountConfig{mnt, err}
-			}
-		}
-	case mount.TypeTmpfs:
-		if len(mnt.Source) != 0 {
-			return &errMountConfig{mnt, errExtraField("Source")}
-		}
-		if err := ValidateTmpfsMountDestination(mnt.Target); err != nil {
-			return &errMountConfig{mnt, err}
-		}
-		if _, err := ConvertTmpfsOptions(mnt.TmpfsOptions, mnt.ReadOnly); err != nil {
-			return &errMountConfig{mnt, err}
-		}
-	case mount.TypeNamedPipe:
-		if runtime.GOOS != "windows" {
-			return &errMountConfig{mnt, errors.New("named pipe bind mounts are not supported on this OS")}
-		}
-
-		if len(mnt.Source) == 0 {
-			return &errMountConfig{mnt, errMissingField("Source")}
-		}
-
-		if mnt.BindOptions != nil {
-			return &errMountConfig{mnt, errExtraField("BindOptions")}
-		}
-
-		if mnt.ReadOnly {
-			return &errMountConfig{mnt, errExtraField("ReadOnly")}
-		}
-
-		if detectMountType(mnt.Source) != mount.TypeNamedPipe {
-			return &errMountConfig{mnt, fmt.Errorf("'%s' is not a valid pipe path", mnt.Source)}
-		}
-
-		if detectMountType(mnt.Target) != mount.TypeNamedPipe {
-			return &errMountConfig{mnt, fmt.Errorf("'%s' is not a valid pipe path", mnt.Target)}
-		}
-
-	default:
-		return &errMountConfig{mnt, errors.New("mount type unknown")}
-	}
-	return nil
-}
-
 type errMountConfig struct {
 	mount *mount.Mount
 	err   error
@@ -140,37 +24,3 @@ func errExtraField(name string) error {
 func errMissingField(name string) error {
 	return errors.Errorf("field %s must not be empty", name)
 }
-
-func validateAbsolute(p string) error {
-	p = convertSlash(p)
-	if isAbsPath(p) {
-		return nil
-	}
-	return errors.Errorf("invalid mount path: '%s' mount path must be absolute", p)
-}
-
-// ValidateTmpfsMountDestination validates the destination of tmpfs mount.
-// Currently, we have only two obvious rule for validation:
-//  - path must not be "/"
-//  - path must be absolute
-// We should add more rules carefully (#30166)
-func ValidateTmpfsMountDestination(dest string) error {
-	if err := validateNotRoot(dest); err != nil {
-		return err
-	}
-	return validateAbsolute(dest)
-}
-
-type validationError struct {
-	err error
-}
-
-func (e validationError) Error() string {
-	return e.err.Error()
-}
-
-func (e validationError) InvalidParameter() {}
-
-func (e validationError) Cause() error {
-	return e.err
-}

+ 37 - 3
volume/validate_test.go

@@ -4,6 +4,7 @@ import (
 	"errors"
 	"io/ioutil"
 	"os"
+	"runtime"
 	"strings"
 	"testing"
 
@@ -27,17 +28,50 @@ func TestValidateMount(t *testing.T) {
 		{mount.Mount{Type: mount.TypeBind}, errMissingField("Target")},
 		{mount.Mount{Type: mount.TypeBind, Target: testDestinationPath}, errMissingField("Source")},
 		{mount.Mount{Type: mount.TypeBind, Target: testDestinationPath, Source: testSourcePath, VolumeOptions: &mount.VolumeOptions{}}, errExtraField("VolumeOptions")},
-		{mount.Mount{Type: mount.TypeBind, Source: testSourcePath, Target: testDestinationPath}, errBindNotExist},
+
 		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath}, nil},
 		{mount.Mount{Type: "invalid", Target: testDestinationPath}, errors.New("mount type unknown")},
 	}
+	if runtime.GOOS == "windows" {
+		cases = append(cases, struct {
+			input    mount.Mount
+			expected error
+		}{mount.Mount{Type: mount.TypeBind, Source: testSourcePath, Target: testDestinationPath}, errBindNotExist}) // bind source existance is not checked on linux
+	}
+	lcowCases := []struct {
+		input    mount.Mount
+		expected error
+	}{
+		{mount.Mount{Type: mount.TypeVolume}, errMissingField("Target")},
+		{mount.Mount{Type: mount.TypeVolume, Target: "/foo", Source: "hello"}, nil},
+		{mount.Mount{Type: mount.TypeVolume, Target: "/foo"}, nil},
+		{mount.Mount{Type: mount.TypeBind}, errMissingField("Target")},
+		{mount.Mount{Type: mount.TypeBind, Target: "/foo"}, errMissingField("Source")},
+		{mount.Mount{Type: mount.TypeBind, Target: "/foo", Source: "c:\\foo", VolumeOptions: &mount.VolumeOptions{}}, errExtraField("VolumeOptions")},
+		{mount.Mount{Type: mount.TypeBind, Source: "c:\\foo", Target: "/foo"}, errBindNotExist},
+		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: "/foo"}, nil},
+		{mount.Mount{Type: "invalid", Target: "/foo"}, errors.New("mount type unknown")},
+	}
+	parser := NewParser(runtime.GOOS)
 	for i, x := range cases {
-		err := validateMountConfig(&x.input)
+		err := parser.validateMountConfig(&x.input)
 		if err == nil && x.expected == nil {
 			continue
 		}
 		if (err == nil && x.expected != nil) || (x.expected == nil && err != nil) || !strings.Contains(err.Error(), x.expected.Error()) {
-			t.Fatalf("expected %q, got %q, case: %d", x.expected, err, i)
+			t.Errorf("expected %q, got %q, case: %d", x.expected, err, i)
+		}
+	}
+	if runtime.GOOS == "windows" {
+		parser = &lcowParser{}
+		for i, x := range lcowCases {
+			err := parser.validateMountConfig(&x.input)
+			if err == nil && x.expected == nil {
+				continue
+			}
+			if (err == nil && x.expected != nil) || (x.expected == nil && err != nil) || !strings.Contains(err.Error(), x.expected.Error()) {
+				t.Errorf("expected %q, got %q, case: %d", x.expected, err, i)
+			}
 		}
 	}
 }

+ 2 - 146
volume/volume.go

@@ -3,7 +3,6 @@ package volume
 import (
 	"fmt"
 	"os"
-	"strings"
 	"syscall"
 	"time"
 
@@ -216,153 +215,10 @@ func (m *MountPoint) Path() string {
 	return m.Source
 }
 
-// ParseVolumesFrom ensures that the supplied volumes-from is valid.
-func ParseVolumesFrom(spec string) (string, string, error) {
-	if len(spec) == 0 {
-		return "", "", fmt.Errorf("volumes-from specification cannot be an empty string")
-	}
-
-	specParts := strings.SplitN(spec, ":", 2)
-	id := specParts[0]
-	mode := "rw"
-
-	if len(specParts) == 2 {
-		mode = specParts[1]
-		if !ValidMountMode(mode) {
-			return "", "", errInvalidMode(mode)
-		}
-		// For now don't allow propagation properties while importing
-		// volumes from data container. These volumes will inherit
-		// the same propagation property as of the original volume
-		// in data container. This probably can be relaxed in future.
-		if HasPropagation(mode) {
-			return "", "", errInvalidMode(mode)
-		}
-		// Do not allow copy modes on volumes-from
-		if _, isSet := getCopyMode(mode); isSet {
-			return "", "", errInvalidMode(mode)
-		}
-	}
-	return id, mode, nil
-}
-
-// ParseMountRaw parses a raw volume spec (e.g. `-v /foo:/bar:shared`) into a
-// structured spec. Once the raw spec is parsed it relies on `ParseMountSpec` to
-// validate the spec and create a MountPoint
-func ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
-	arr, err := splitRawSpec(convertSlash(raw))
-	if err != nil {
-		return nil, err
-	}
-
-	var spec mounttypes.Mount
-	var mode string
-	switch len(arr) {
-	case 1:
-		// Just a destination path in the container
-		spec.Target = arr[0]
-	case 2:
-		if ValidMountMode(arr[1]) {
-			// Destination + Mode is not a valid volume - volumes
-			// cannot include a mode. e.g. /foo:rw
-			return nil, errInvalidSpec(raw)
-		}
-		// Host Source Path or Name + Destination
-		spec.Source = arr[0]
-		spec.Target = arr[1]
-	case 3:
-		// HostSourcePath+DestinationPath+Mode
-		spec.Source = arr[0]
-		spec.Target = arr[1]
-		mode = arr[2]
-	default:
-		return nil, errInvalidSpec(raw)
-	}
-
-	if !ValidMountMode(mode) {
-		return nil, errInvalidMode(mode)
-	}
-
-	spec.Type = detectMountType(spec.Source)
-	spec.ReadOnly = !ReadWrite(mode)
-
-	// cannot assume that if a volume driver is passed in that we should set it
-	if volumeDriver != "" && spec.Type == mounttypes.TypeVolume {
-		spec.VolumeOptions = &mounttypes.VolumeOptions{
-			DriverConfig: &mounttypes.Driver{Name: volumeDriver},
-		}
-	}
-
-	if copyData, isSet := getCopyMode(mode); isSet {
-		if spec.VolumeOptions == nil {
-			spec.VolumeOptions = &mounttypes.VolumeOptions{}
-		}
-		spec.VolumeOptions.NoCopy = !copyData
-	}
-	if HasPropagation(mode) {
-		spec.BindOptions = &mounttypes.BindOptions{
-			Propagation: GetPropagation(mode),
-		}
-	}
-
-	mp, err := ParseMountSpec(spec, platformRawValidationOpts...)
-	if mp != nil {
-		mp.Mode = mode
-	}
-	if err != nil {
-		err = errors.Wrap(err, errInvalidSpec(raw).Error())
-	}
-	return mp, err
-}
-
-// ParseMountSpec reads a mount config, validates it, and configures a mountpoint from it.
-func ParseMountSpec(cfg mounttypes.Mount, options ...func(*validateOpts)) (*MountPoint, error) {
-	if err := validateMountConfig(&cfg, options...); err != nil {
-		return nil, validationError{err}
-	}
-	mp := &MountPoint{
-		RW:          !cfg.ReadOnly,
-		Destination: clean(convertSlash(cfg.Target)),
-		Type:        cfg.Type,
-		Spec:        cfg,
-	}
-
-	switch cfg.Type {
-	case mounttypes.TypeVolume:
-		if cfg.Source == "" {
-			mp.Name = stringid.GenerateNonCryptoID()
-		} else {
-			mp.Name = cfg.Source
-		}
-		mp.CopyData = DefaultCopyMode
-
-		if cfg.VolumeOptions != nil {
-			if cfg.VolumeOptions.DriverConfig != nil {
-				mp.Driver = cfg.VolumeOptions.DriverConfig.Name
-			}
-			if cfg.VolumeOptions.NoCopy {
-				mp.CopyData = false
-			}
-		}
-	case mounttypes.TypeBind, mounttypes.TypeNamedPipe:
-		mp.Source = clean(convertSlash(cfg.Source))
-		if cfg.BindOptions != nil && len(cfg.BindOptions.Propagation) > 0 {
-			mp.Propagation = cfg.BindOptions.Propagation
-		} else {
-			// If user did not specify a propagation mode, get
-			// default propagation mode.
-			mp.Propagation = DefaultPropagationMode
-		}
-	case mounttypes.TypeTmpfs:
-		// NOP
-	}
-	return mp, nil
-}
-
 func errInvalidMode(mode string) error {
-	return validationError{errors.Errorf("invalid mode: %v", mode)}
+	return errors.Errorf("invalid mode: %v", mode)
 }
 
 func errInvalidSpec(spec string) error {
-	return validationError{errors.Errorf("invalid volume specification: '%s'", spec)}
+	return errors.Errorf("invalid volume specification: '%s'", spec)
 }

+ 2 - 2
volume/volume_copy.go

@@ -13,11 +13,11 @@ func copyModeExists(mode string) bool {
 }
 
 // GetCopyMode gets the copy mode from the mode string for mounts
-func getCopyMode(mode string) (bool, bool) {
+func getCopyMode(mode string, def bool) (bool, bool) {
 	for _, o := range strings.Split(mode, ",") {
 		if isEnabled, exists := copyModes[o]; exists {
 			return isEnabled, true
 		}
 	}
-	return DefaultCopyMode, false
+	return def, false
 }

+ 0 - 8
volume/volume_copy_unix.go

@@ -1,8 +0,0 @@
-// +build !windows
-
-package volume
-
-const (
-	// DefaultCopyMode is the copy mode used by default for normal/named volumes
-	DefaultCopyMode = true
-)

+ 0 - 6
volume/volume_copy_windows.go

@@ -1,6 +0,0 @@
-package volume
-
-const (
-	// DefaultCopyMode is the copy mode used by default for normal/named volumes
-	DefaultCopyMode = false
-)

+ 0 - 56
volume/volume_linux.go

@@ -1,56 +0,0 @@
-// +build linux
-
-package volume
-
-import (
-	"fmt"
-	"strings"
-
-	mounttypes "github.com/docker/docker/api/types/mount"
-)
-
-// ConvertTmpfsOptions converts *mounttypes.TmpfsOptions to the raw option string
-// for mount(2).
-func ConvertTmpfsOptions(opt *mounttypes.TmpfsOptions, readOnly bool) (string, error) {
-	var rawOpts []string
-	if readOnly {
-		rawOpts = append(rawOpts, "ro")
-	}
-
-	if opt != nil && opt.Mode != 0 {
-		rawOpts = append(rawOpts, fmt.Sprintf("mode=%o", opt.Mode))
-	}
-
-	if opt != nil && opt.SizeBytes != 0 {
-		// calculate suffix here, making this linux specific, but that is
-		// okay, since API is that way anyways.
-
-		// we do this by finding the suffix that divides evenly into the
-		// value, returning the value itself, with no suffix, if it fails.
-		//
-		// For the most part, we don't enforce any semantic to this values.
-		// The operating system will usually align this and enforce minimum
-		// and maximums.
-		var (
-			size   = opt.SizeBytes
-			suffix string
-		)
-		for _, r := range []struct {
-			suffix  string
-			divisor int64
-		}{
-			{"g", 1 << 30},
-			{"m", 1 << 20},
-			{"k", 1 << 10},
-		} {
-			if size%r.divisor == 0 {
-				size = size / r.divisor
-				suffix = r.suffix
-				break
-			}
-		}
-
-		rawOpts = append(rawOpts, fmt.Sprintf("size=%d%s", size, suffix))
-	}
-	return strings.Join(rawOpts, ","), nil
-}

+ 0 - 51
volume/volume_linux_test.go

@@ -1,51 +0,0 @@
-// +build linux
-
-package volume
-
-import (
-	"strings"
-	"testing"
-
-	mounttypes "github.com/docker/docker/api/types/mount"
-)
-
-func TestConvertTmpfsOptions(t *testing.T) {
-	type testCase struct {
-		opt                  mounttypes.TmpfsOptions
-		readOnly             bool
-		expectedSubstrings   []string
-		unexpectedSubstrings []string
-	}
-	cases := []testCase{
-		{
-			opt:                  mounttypes.TmpfsOptions{SizeBytes: 1024 * 1024, Mode: 0700},
-			readOnly:             false,
-			expectedSubstrings:   []string{"size=1m", "mode=700"},
-			unexpectedSubstrings: []string{"ro"},
-		},
-		{
-			opt:                  mounttypes.TmpfsOptions{},
-			readOnly:             true,
-			expectedSubstrings:   []string{"ro"},
-			unexpectedSubstrings: []string{},
-		},
-	}
-	for _, c := range cases {
-		data, err := ConvertTmpfsOptions(&c.opt, c.readOnly)
-		if err != nil {
-			t.Fatalf("could not convert %+v (readOnly: %v) to string: %v",
-				c.opt, c.readOnly, err)
-		}
-		t.Logf("data=%q", data)
-		for _, s := range c.expectedSubstrings {
-			if !strings.Contains(data, s) {
-				t.Fatalf("expected substring: %s, got %v (case=%+v)", s, data, c)
-			}
-		}
-		for _, s := range c.unexpectedSubstrings {
-			if strings.Contains(data, s) {
-				t.Fatalf("unexpected substring: %s, got %v (case=%+v)", s, data, c)
-			}
-		}
-	}
-}

+ 0 - 47
volume/volume_propagation_linux.go

@@ -1,47 +0,0 @@
-// +build linux
-
-package volume
-
-import (
-	"strings"
-
-	mounttypes "github.com/docker/docker/api/types/mount"
-)
-
-// DefaultPropagationMode defines what propagation mode should be used by
-// default if user has not specified one explicitly.
-// propagation modes
-const DefaultPropagationMode = mounttypes.PropagationRPrivate
-
-var propagationModes = map[mounttypes.Propagation]bool{
-	mounttypes.PropagationPrivate:  true,
-	mounttypes.PropagationRPrivate: true,
-	mounttypes.PropagationSlave:    true,
-	mounttypes.PropagationRSlave:   true,
-	mounttypes.PropagationShared:   true,
-	mounttypes.PropagationRShared:  true,
-}
-
-// GetPropagation extracts and returns the mount propagation mode. If there
-// are no specifications, then by default it is "private".
-func GetPropagation(mode string) mounttypes.Propagation {
-	for _, o := range strings.Split(mode, ",") {
-		prop := mounttypes.Propagation(o)
-		if propagationModes[prop] {
-			return prop
-		}
-	}
-	return DefaultPropagationMode
-}
-
-// HasPropagation checks if there is a valid propagation mode present in
-// passed string. Returns true if a valid propagation mode specifier is
-// present, false otherwise.
-func HasPropagation(mode string) bool {
-	for _, o := range strings.Split(mode, ",") {
-		if propagationModes[mounttypes.Propagation(o)] {
-			return true
-		}
-	}
-	return false
-}

+ 0 - 65
volume/volume_propagation_linux_test.go

@@ -1,65 +0,0 @@
-// +build linux
-
-package volume
-
-import (
-	"strings"
-	"testing"
-)
-
-func TestParseMountRawPropagation(t *testing.T) {
-	var (
-		valid   []string
-		invalid map[string]string
-	)
-
-	valid = []string{
-		"/hostPath:/containerPath:shared",
-		"/hostPath:/containerPath:rshared",
-		"/hostPath:/containerPath:slave",
-		"/hostPath:/containerPath:rslave",
-		"/hostPath:/containerPath:private",
-		"/hostPath:/containerPath:rprivate",
-		"/hostPath:/containerPath:ro,shared",
-		"/hostPath:/containerPath:ro,slave",
-		"/hostPath:/containerPath:ro,private",
-		"/hostPath:/containerPath:ro,z,shared",
-		"/hostPath:/containerPath:ro,Z,slave",
-		"/hostPath:/containerPath:Z,ro,slave",
-		"/hostPath:/containerPath:slave,Z,ro",
-		"/hostPath:/containerPath:Z,slave,ro",
-		"/hostPath:/containerPath:slave,ro,Z",
-		"/hostPath:/containerPath:rslave,ro,Z",
-		"/hostPath:/containerPath:ro,rshared,Z",
-		"/hostPath:/containerPath:ro,Z,rprivate",
-	}
-	invalid = map[string]string{
-		"/path:/path:ro,rshared,rslave":   `invalid mode`,
-		"/path:/path:ro,z,rshared,rslave": `invalid mode`,
-		"/path:shared":                    "invalid volume specification",
-		"/path:slave":                     "invalid volume specification",
-		"/path:private":                   "invalid volume specification",
-		"name:/absolute-path:shared":      "invalid volume specification",
-		"name:/absolute-path:rshared":     "invalid volume specification",
-		"name:/absolute-path:slave":       "invalid volume specification",
-		"name:/absolute-path:rslave":      "invalid volume specification",
-		"name:/absolute-path:private":     "invalid volume specification",
-		"name:/absolute-path:rprivate":    "invalid volume specification",
-	}
-
-	for _, path := range valid {
-		if _, err := ParseMountRaw(path, "local"); err != nil {
-			t.Fatalf("ParseMountRaw(`%q`) should succeed: error %q", path, err)
-		}
-	}
-
-	for path, expectedError := range invalid {
-		if _, err := ParseMountRaw(path, "local"); err == nil {
-			t.Fatalf("ParseMountRaw(`%q`) should have failed validation. Err %v", path, err)
-		} else {
-			if !strings.Contains(err.Error(), expectedError) {
-				t.Fatalf("ParseMountRaw(`%q`) error should contain %q, got %v", path, expectedError, err.Error())
-			}
-		}
-	}
-}

+ 0 - 24
volume/volume_propagation_unsupported.go

@@ -1,24 +0,0 @@
-// +build !linux
-
-package volume
-
-import mounttypes "github.com/docker/docker/api/types/mount"
-
-// DefaultPropagationMode is used only in linux. In other cases it returns
-// empty string.
-const DefaultPropagationMode mounttypes.Propagation = ""
-
-// propagation modes not supported on this platform.
-var propagationModes = map[mounttypes.Propagation]bool{}
-
-// GetPropagation is not supported. Return empty string.
-func GetPropagation(mode string) mounttypes.Propagation {
-	return DefaultPropagationMode
-}
-
-// HasPropagation checks if there is a valid propagation mode present in
-// passed string. Returns true if a valid propagation mode specifier is
-// present, false otherwise.
-func HasPropagation(mode string) bool {
-	return false
-}

+ 327 - 124
volume/volume_test.go

@@ -10,14 +10,84 @@ import (
 	"github.com/docker/docker/api/types/mount"
 )
 
+type parseMountRawTestSet struct {
+	valid   []string
+	invalid map[string]string
+}
+
+func TestConvertTmpfsOptions(t *testing.T) {
+	type testCase struct {
+		opt                  mount.TmpfsOptions
+		readOnly             bool
+		expectedSubstrings   []string
+		unexpectedSubstrings []string
+	}
+	cases := []testCase{
+		{
+			opt:                  mount.TmpfsOptions{SizeBytes: 1024 * 1024, Mode: 0700},
+			readOnly:             false,
+			expectedSubstrings:   []string{"size=1m", "mode=700"},
+			unexpectedSubstrings: []string{"ro"},
+		},
+		{
+			opt:                  mount.TmpfsOptions{},
+			readOnly:             true,
+			expectedSubstrings:   []string{"ro"},
+			unexpectedSubstrings: []string{},
+		},
+	}
+	p := &linuxParser{}
+	for _, c := range cases {
+		data, err := p.ConvertTmpfsOptions(&c.opt, c.readOnly)
+		if err != nil {
+			t.Fatalf("could not convert %+v (readOnly: %v) to string: %v",
+				c.opt, c.readOnly, err)
+		}
+		t.Logf("data=%q", data)
+		for _, s := range c.expectedSubstrings {
+			if !strings.Contains(data, s) {
+				t.Fatalf("expected substring: %s, got %v (case=%+v)", s, data, c)
+			}
+		}
+		for _, s := range c.unexpectedSubstrings {
+			if strings.Contains(data, s) {
+				t.Fatalf("unexpected substring: %s, got %v (case=%+v)", s, data, c)
+			}
+		}
+	}
+}
+
+type mockFiProvider struct{}
+
+func (mockFiProvider) fileInfo(path string) (exists, isDir bool, err error) {
+	dirs := map[string]struct{}{
+		`c:\`:                    {},
+		`c:\windows\`:            {},
+		`c:\windows`:             {},
+		`c:\program files`:       {},
+		`c:\Windows`:             {},
+		`c:\Program Files (x86)`: {},
+		`\\?\c:\windows\`:        {},
+	}
+	files := map[string]struct{}{
+		`c:\windows\system32\ntdll.dll`: {},
+	}
+	if _, ok := dirs[path]; ok {
+		return true, true, nil
+	}
+	if _, ok := files[path]; ok {
+		return true, false, nil
+	}
+	return false, false, nil
+}
+
 func TestParseMountRaw(t *testing.T) {
-	var (
-		valid   []string
-		invalid map[string]string
-	)
 
-	if runtime.GOOS == "windows" {
-		valid = []string{
+	previousProvider := currentFileInfoProvider
+	defer func() { currentFileInfoProvider = previousProvider }()
+	currentFileInfoProvider = mockFiProvider{}
+	windowsSet := parseMountRawTestSet{
+		valid: []string{
 			`d:\`,
 			`d:`,
 			`d:\path`,
@@ -35,10 +105,14 @@ func TestParseMountRaw(t *testing.T) {
 			`name:D::RO`,
 			`c:/:d:/forward/slashes/are/good/too`,
 			`c:/:d:/including with/spaces:ro`,
-			`c:\Windows`,             // With capital
-			`c:\Program Files (x86)`, // With capitals and brackets
-		}
-		invalid = map[string]string{
+			`c:\Windows`,                // With capital
+			`c:\Program Files (x86)`,    // With capitals and brackets
+			`\\?\c:\windows\:d:`,        // Long path handling (source)
+			`c:\windows\:\\?\d:\`,       // Long path handling (target)
+			`\\.\pipe\foo:\\.\pipe\foo`, // named pipe
+			`//./pipe/foo://./pipe/foo`, // named pipe forward slashes
+		},
+		invalid: map[string]string{
 			``:                                 "invalid volume specification: ",
 			`.`:                                "invalid volume specification: ",
 			`..\`:                              "invalid volume specification: ",
@@ -82,10 +156,79 @@ func TestParseMountRaw(t *testing.T) {
 			`lpt8:d:`:                          `cannot be a reserved word for Windows filenames`,
 			`lpt9:d:`:                          `cannot be a reserved word for Windows filenames`,
 			`c:\windows\system32\ntdll.dll`:    `Only directories can be mapped on this platform`,
-		}
-
-	} else {
-		valid = []string{
+			`\\.\pipe\foo:c:\pipe`:             `'c:\pipe' is not a valid pipe path`,
+		},
+	}
+	lcowSet := parseMountRawTestSet{
+		valid: []string{
+			`/foo`,
+			`/foo/`,
+			`/foo bar`,
+			`c:\:/foo`,
+			`c:\windows\:/foo`,
+			`c:\windows:/s p a c e`,
+			`c:\windows:/s p a c e:RW`,
+			`c:\program files:/s p a c e i n h o s t d i r`,
+			`0123456789name:/foo`,
+			`MiXeDcAsEnAmE:/foo`,
+			`name:/foo`,
+			`name:/foo:rW`,
+			`name:/foo:RW`,
+			`name:/foo:RO`,
+			`c:/:/forward/slashes/are/good/too`,
+			`c:/:/including with/spaces:ro`,
+			`/Program Files (x86)`, // With capitals and brackets
+		},
+		invalid: map[string]string{
+			``:                                   "invalid volume specification: ",
+			`.`:                                  "invalid volume specification: ",
+			`c:`:                                 "invalid volume specification: ",
+			`c:\`:                                "invalid volume specification: ",
+			`../`:                                "invalid volume specification: ",
+			`c:\:../`:                            "invalid volume specification: ",
+			`c:\:/foo:xyzzy`:                     "invalid volume specification: ",
+			`/`:                                  "destination can't be '/'",
+			`/..`:                                "destination can't be '/'",
+			`c:\notexist:/foo`:                   `source path does not exist`,
+			`c:\windows\system32\ntdll.dll:/foo`: `source path must be a directory`,
+			`name<:/foo`:                         `invalid volume specification`,
+			`name>:/foo`:                         `invalid volume specification`,
+			`name::/foo`:                         `invalid volume specification`,
+			`name":/foo`:                         `invalid volume specification`,
+			`name\:/foo`:                         `invalid volume specification`,
+			`name*:/foo`:                         `invalid volume specification`,
+			`name|:/foo`:                         `invalid volume specification`,
+			`name?:/foo`:                         `invalid volume specification`,
+			`name/:/foo`:                         `invalid volume specification`,
+			`/foo:rw`:                            `invalid volume specification`,
+			`/foo:ro`:                            `invalid volume specification`,
+			`con:/foo`:                           `cannot be a reserved word for Windows filenames`,
+			`PRN:/foo`:                           `cannot be a reserved word for Windows filenames`,
+			`aUx:/foo`:                           `cannot be a reserved word for Windows filenames`,
+			`nul:/foo`:                           `cannot be a reserved word for Windows filenames`,
+			`com1:/foo`:                          `cannot be a reserved word for Windows filenames`,
+			`com2:/foo`:                          `cannot be a reserved word for Windows filenames`,
+			`com3:/foo`:                          `cannot be a reserved word for Windows filenames`,
+			`com4:/foo`:                          `cannot be a reserved word for Windows filenames`,
+			`com5:/foo`:                          `cannot be a reserved word for Windows filenames`,
+			`com6:/foo`:                          `cannot be a reserved word for Windows filenames`,
+			`com7:/foo`:                          `cannot be a reserved word for Windows filenames`,
+			`com8:/foo`:                          `cannot be a reserved word for Windows filenames`,
+			`com9:/foo`:                          `cannot be a reserved word for Windows filenames`,
+			`lpt1:/foo`:                          `cannot be a reserved word for Windows filenames`,
+			`lpt2:/foo`:                          `cannot be a reserved word for Windows filenames`,
+			`lpt3:/foo`:                          `cannot be a reserved word for Windows filenames`,
+			`lpt4:/foo`:                          `cannot be a reserved word for Windows filenames`,
+			`lpt5:/foo`:                          `cannot be a reserved word for Windows filenames`,
+			`lpt6:/foo`:                          `cannot be a reserved word for Windows filenames`,
+			`lpt7:/foo`:                          `cannot be a reserved word for Windows filenames`,
+			`lpt8:/foo`:                          `cannot be a reserved word for Windows filenames`,
+			`lpt9:/foo`:                          `cannot be a reserved word for Windows filenames`,
+			`\\.\pipe\foo:/foo`:                  `Linux containers on Windows do not support named pipe mounts`,
+		},
+	}
+	linuxSet := parseMountRawTestSet{
+		valid: []string{
 			"/home",
 			"/home:/home",
 			"/home:/something/else",
@@ -95,47 +238,87 @@ func TestParseMountRaw(t *testing.T) {
 			"hostPath:/containerPath:ro",
 			"/hostPath:/containerPath:rw",
 			"/rw:/ro",
-		}
-		invalid = map[string]string{
-			"":                "invalid volume specification",
-			"./":              "mount path must be absolute",
-			"../":             "mount path must be absolute",
-			"/:../":           "mount path must be absolute",
-			"/:path":          "mount path must be absolute",
-			":":               "invalid volume specification",
-			"/tmp:":           "invalid volume specification",
-			":test":           "invalid volume specification",
-			":/test":          "invalid volume specification",
-			"tmp:":            "invalid volume specification",
-			":test:":          "invalid volume specification",
-			"::":              "invalid volume specification",
-			":::":             "invalid volume specification",
-			"/tmp:::":         "invalid volume specification",
-			":/tmp::":         "invalid volume specification",
-			"/path:rw":        "invalid volume specification",
-			"/path:ro":        "invalid volume specification",
-			"/rw:rw":          "invalid volume specification",
-			"path:ro":         "invalid volume specification",
-			"/path:/path:sw":  `invalid mode`,
-			"/path:/path:rwz": `invalid mode`,
-		}
+			"/hostPath:/containerPath:shared",
+			"/hostPath:/containerPath:rshared",
+			"/hostPath:/containerPath:slave",
+			"/hostPath:/containerPath:rslave",
+			"/hostPath:/containerPath:private",
+			"/hostPath:/containerPath:rprivate",
+			"/hostPath:/containerPath:ro,shared",
+			"/hostPath:/containerPath:ro,slave",
+			"/hostPath:/containerPath:ro,private",
+			"/hostPath:/containerPath:ro,z,shared",
+			"/hostPath:/containerPath:ro,Z,slave",
+			"/hostPath:/containerPath:Z,ro,slave",
+			"/hostPath:/containerPath:slave,Z,ro",
+			"/hostPath:/containerPath:Z,slave,ro",
+			"/hostPath:/containerPath:slave,ro,Z",
+			"/hostPath:/containerPath:rslave,ro,Z",
+			"/hostPath:/containerPath:ro,rshared,Z",
+			"/hostPath:/containerPath:ro,Z,rprivate",
+		},
+		invalid: map[string]string{
+			"":                                "invalid volume specification",
+			"./":                              "mount path must be absolute",
+			"../":                             "mount path must be absolute",
+			"/:../":                           "mount path must be absolute",
+			"/:path":                          "mount path must be absolute",
+			":":                               "invalid volume specification",
+			"/tmp:":                           "invalid volume specification",
+			":test":                           "invalid volume specification",
+			":/test":                          "invalid volume specification",
+			"tmp:":                            "invalid volume specification",
+			":test:":                          "invalid volume specification",
+			"::":                              "invalid volume specification",
+			":::":                             "invalid volume specification",
+			"/tmp:::":                         "invalid volume specification",
+			":/tmp::":                         "invalid volume specification",
+			"/path:rw":                        "invalid volume specification",
+			"/path:ro":                        "invalid volume specification",
+			"/rw:rw":                          "invalid volume specification",
+			"path:ro":                         "invalid volume specification",
+			"/path:/path:sw":                  `invalid mode`,
+			"/path:/path:rwz":                 `invalid mode`,
+			"/path:/path:ro,rshared,rslave":   `invalid mode`,
+			"/path:/path:ro,z,rshared,rslave": `invalid mode`,
+			"/path:shared":                    "invalid volume specification",
+			"/path:slave":                     "invalid volume specification",
+			"/path:private":                   "invalid volume specification",
+			"name:/absolute-path:shared":      "invalid volume specification",
+			"name:/absolute-path:rshared":     "invalid volume specification",
+			"name:/absolute-path:slave":       "invalid volume specification",
+			"name:/absolute-path:rslave":      "invalid volume specification",
+			"name:/absolute-path:private":     "invalid volume specification",
+			"name:/absolute-path:rprivate":    "invalid volume specification",
+		},
 	}
 
-	for _, path := range valid {
-		if _, err := ParseMountRaw(path, "local"); err != nil {
-			t.Fatalf("ParseMountRaw(`%q`) should succeed: error %q", path, err)
+	linParser := &linuxParser{}
+	winParser := &windowsParser{}
+	lcowParser := &lcowParser{}
+	tester := func(parser Parser, set parseMountRawTestSet) {
+
+		for _, path := range set.valid {
+
+			if _, err := parser.ParseMountRaw(path, "local"); err != nil {
+				t.Errorf("ParseMountRaw(`%q`) should succeed: error %q", path, err)
+			}
 		}
-	}
 
-	for path, expectedError := range invalid {
-		if mp, err := ParseMountRaw(path, "local"); err == nil {
-			t.Fatalf("ParseMountRaw(`%q`) should have failed validation. Err '%v' - MP: %v", path, err, mp)
-		} else {
-			if !strings.Contains(err.Error(), expectedError) {
-				t.Fatalf("ParseMountRaw(`%q`) error should contain %q, got %v", path, expectedError, err.Error())
+		for path, expectedError := range set.invalid {
+			if mp, err := parser.ParseMountRaw(path, "local"); err == nil {
+				t.Errorf("ParseMountRaw(`%q`) should have failed validation. Err '%v' - MP: %v", path, err, mp)
+			} else {
+				if !strings.Contains(err.Error(), expectedError) {
+					t.Errorf("ParseMountRaw(`%q`) error should contain %q, got %v", path, expectedError, err.Error())
+				}
 			}
 		}
 	}
+	tester(linParser, linuxSet)
+	tester(winParser, windowsSet)
+	tester(lcowParser, lcowSet)
+
 }
 
 // testParseMountRaw is a structure used by TestParseMountRawSplit for
@@ -153,76 +336,96 @@ type testParseMountRaw struct {
 }
 
 func TestParseMountRawSplit(t *testing.T) {
-	var cases []testParseMountRaw
-	if runtime.GOOS == "windows" {
-		cases = []testParseMountRaw{
-			{`c:\:d:`, "local", mount.TypeBind, `d:`, `c:\`, ``, "", true, false},
-			{`c:\:d:\`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false},
-			{`c:\:d:\:ro`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, false},
-			{`c:\:d:\:rw`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false},
-			{`c:\:d:\:foo`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, true},
-			{`\\.\pipe\foo:\\.\pipe\bar`, "local", mount.TypeNamedPipe, `\\.\pipe\bar`, `\\.\pipe\foo`, "", "", true, false},
-			{`\\.\pipe\foo:c:\foo\bar`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
-			{`c:\foo\bar:\\.\pipe\foo`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
-			{`name:d::rw`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false},
-			{`name:d:`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false},
-			{`name:d::ro`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", false, false},
-			{`name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
-			{`driver/name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
-		}
-	} else {
-		cases = []testParseMountRaw{
-			{"/tmp:/tmp1", "", mount.TypeBind, "/tmp1", "/tmp", "", "", true, false},
-			{"/tmp:/tmp2:ro", "", mount.TypeBind, "/tmp2", "/tmp", "", "", false, false},
-			{"/tmp:/tmp3:rw", "", mount.TypeBind, "/tmp3", "/tmp", "", "", true, false},
-			{"/tmp:/tmp4:foo", "", mount.TypeBind, "", "", "", "", false, true},
-			{"name:/named1", "", mount.TypeVolume, "/named1", "", "name", "", true, false},
-			{"name:/named2", "external", mount.TypeVolume, "/named2", "", "name", "external", true, false},
-			{"name:/named3:ro", "local", mount.TypeVolume, "/named3", "", "name", "local", false, false},
-			{"local/name:/tmp:rw", "", mount.TypeVolume, "/tmp", "", "local/name", "", true, false},
-			{"/tmp:tmp", "", mount.TypeBind, "", "", "", "", true, true},
-		}
+	previousProvider := currentFileInfoProvider
+	defer func() { currentFileInfoProvider = previousProvider }()
+	currentFileInfoProvider = mockFiProvider{}
+	windowsCases := []testParseMountRaw{
+		{`c:\:d:`, "local", mount.TypeBind, `d:`, `c:\`, ``, "", true, false},
+		{`c:\:d:\`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false},
+		{`c:\:d:\:ro`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, false},
+		{`c:\:d:\:rw`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false},
+		{`c:\:d:\:foo`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, true},
+		{`name:d::rw`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false},
+		{`name:d:`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false},
+		{`name:d::ro`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", false, false},
+		{`name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
+		{`driver/name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
+		{`\\.\pipe\foo:\\.\pipe\bar`, "local", mount.TypeNamedPipe, `\\.\pipe\bar`, `\\.\pipe\foo`, "", "", true, false},
+		{`\\.\pipe\foo:c:\foo\bar`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
+		{`c:\foo\bar:\\.\pipe\foo`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
 	}
-
-	for i, c := range cases {
-		t.Logf("case %d", i)
-		m, err := ParseMountRaw(c.bind, c.driver)
-		if c.fail {
-			if err == nil {
-				t.Fatalf("Expected error, was nil, for spec %s\n", c.bind)
+	lcowCases := []testParseMountRaw{
+		{`c:\:/foo`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", true, false},
+		{`c:\:/foo:ro`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", false, false},
+		{`c:\:/foo:rw`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", true, false},
+		{`c:\:/foo:foo`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", false, true},
+		{`name:/foo:rw`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", true, false},
+		{`name:/foo`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", true, false},
+		{`name:/foo:ro`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", false, false},
+		{`name:/`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
+		{`driver/name:/`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
+		{`\\.\pipe\foo:\\.\pipe\bar`, "local", mount.TypeNamedPipe, `\\.\pipe\bar`, `\\.\pipe\foo`, "", "", true, true},
+		{`\\.\pipe\foo:/data`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
+		{`c:\foo\bar:\\.\pipe\foo`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
+	}
+	linuxCases := []testParseMountRaw{
+		{"/tmp:/tmp1", "", mount.TypeBind, "/tmp1", "/tmp", "", "", true, false},
+		{"/tmp:/tmp2:ro", "", mount.TypeBind, "/tmp2", "/tmp", "", "", false, false},
+		{"/tmp:/tmp3:rw", "", mount.TypeBind, "/tmp3", "/tmp", "", "", true, false},
+		{"/tmp:/tmp4:foo", "", mount.TypeBind, "", "", "", "", false, true},
+		{"name:/named1", "", mount.TypeVolume, "/named1", "", "name", "", true, false},
+		{"name:/named2", "external", mount.TypeVolume, "/named2", "", "name", "external", true, false},
+		{"name:/named3:ro", "local", mount.TypeVolume, "/named3", "", "name", "local", false, false},
+		{"local/name:/tmp:rw", "", mount.TypeVolume, "/tmp", "", "local/name", "", true, false},
+		{"/tmp:tmp", "", mount.TypeBind, "", "", "", "", true, true},
+	}
+	linParser := &linuxParser{}
+	winParser := &windowsParser{}
+	lcowParser := &lcowParser{}
+	tester := func(parser Parser, cases []testParseMountRaw) {
+		for i, c := range cases {
+			t.Logf("case %d", i)
+			m, err := parser.ParseMountRaw(c.bind, c.driver)
+			if c.fail {
+				if err == nil {
+					t.Errorf("Expected error, was nil, for spec %s\n", c.bind)
+				}
+				continue
 			}
-			continue
-		}
-
-		if m == nil || err != nil {
-			t.Fatalf("ParseMountRaw failed for spec '%s', driver '%s', error '%v'", c.bind, c.driver, err.Error())
-			continue
-		}
 
-		if m.Type != c.expType {
-			t.Fatalf("Expected type '%s', was '%s', for spec '%s'", c.expType, m.Type, c.bind)
-		}
+			if m == nil || err != nil {
+				t.Errorf("ParseMountRaw failed for spec '%s', driver '%s', error '%v'", c.bind, c.driver, err.Error())
+				continue
+			}
 
-		if m.Destination != c.expDest {
-			t.Fatalf("Expected destination '%s', was '%s', for spec '%s'", c.expDest, m.Destination, c.bind)
-		}
+			if m.Destination != c.expDest {
+				t.Errorf("Expected destination '%s, was %s', for spec '%s'", c.expDest, m.Destination, c.bind)
+			}
 
-		if m.Source != c.expSource {
-			t.Fatalf("Expected source '%s', was '%s', for spec '%s'", c.expSource, m.Source, c.bind)
-		}
+			if m.Source != c.expSource {
+				t.Errorf("Expected source '%s', was '%s', for spec '%s'", c.expSource, m.Source, c.bind)
+			}
 
-		if m.Name != c.expName {
-			t.Fatalf("Expected name '%s', was '%s' for spec '%s'", c.expName, m.Name, c.bind)
-		}
+			if m.Name != c.expName {
+				t.Errorf("Expected name '%s', was '%s' for spec '%s'", c.expName, m.Name, c.bind)
+			}
 
-		if m.Driver != c.expDriver {
-			t.Fatalf("Expected driver '%s', was '%s', for spec '%s'", c.expDriver, m.Driver, c.bind)
-		}
+			if m.Driver != c.expDriver {
+				t.Errorf("Expected driver '%s', was '%s', for spec '%s'", c.expDriver, m.Driver, c.bind)
+			}
 
-		if m.RW != c.expRW {
-			t.Fatalf("Expected RW '%v', was '%v' for spec '%s'", c.expRW, m.RW, c.bind)
+			if m.RW != c.expRW {
+				t.Errorf("Expected RW '%v', was '%v' for spec '%s'", c.expRW, m.RW, c.bind)
+			}
+			if m.Type != c.expType {
+				t.Fatalf("Expected type '%s', was '%s', for spec '%s'", c.expType, m.Type, c.bind)
+			}
 		}
 	}
+
+	tester(linParser, linuxCases)
+	tester(winParser, windowsCases)
+	tester(lcowParser, lcowCases)
 }
 
 func TestParseMountSpec(t *testing.T) {
@@ -235,43 +438,43 @@ func TestParseMountSpec(t *testing.T) {
 		t.Fatal(err)
 	}
 	defer os.RemoveAll(testDir)
-
+	parser := NewParser(runtime.GOOS)
 	cases := []c{
-		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: DefaultPropagationMode}},
-		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, RW: true, Propagation: DefaultPropagationMode}},
-		{mount.Mount{Type: mount.TypeBind, Source: testDir + string(os.PathSeparator), Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: DefaultPropagationMode}},
-		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath + string(os.PathSeparator), ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: DefaultPropagationMode}},
-		{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: DefaultCopyMode}},
-		{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath + string(os.PathSeparator)}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: DefaultCopyMode}},
+		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: parser.DefaultPropagationMode()}},
+		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, RW: true, Propagation: parser.DefaultPropagationMode()}},
+		{mount.Mount{Type: mount.TypeBind, Source: testDir + string(os.PathSeparator), Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: parser.DefaultPropagationMode()}},
+		{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath + string(os.PathSeparator), ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: parser.DefaultPropagationMode()}},
+		{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: parser.DefaultCopyMode()}},
+		{mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath + string(os.PathSeparator)}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: parser.DefaultCopyMode()}},
 	}
 
 	for i, c := range cases {
 		t.Logf("case %d", i)
-		mp, err := ParseMountSpec(c.input)
+		mp, err := parser.ParseMountSpec(c.input)
 		if err != nil {
-			t.Fatal(err)
+			t.Error(err)
 		}
 
 		if c.expected.Type != mp.Type {
-			t.Fatalf("Expected mount types to match. Expected: '%s', Actual: '%s'", c.expected.Type, mp.Type)
+			t.Errorf("Expected mount types to match. Expected: '%s', Actual: '%s'", c.expected.Type, mp.Type)
 		}
 		if c.expected.Destination != mp.Destination {
-			t.Fatalf("Expected mount destination to match. Expected: '%s', Actual: '%s'", c.expected.Destination, mp.Destination)
+			t.Errorf("Expected mount destination to match. Expected: '%s', Actual: '%s'", c.expected.Destination, mp.Destination)
 		}
 		if c.expected.Source != mp.Source {
-			t.Fatalf("Expected mount source to match. Expected: '%s', Actual: '%s'", c.expected.Source, mp.Source)
+			t.Errorf("Expected mount source to match. Expected: '%s', Actual: '%s'", c.expected.Source, mp.Source)
 		}
 		if c.expected.RW != mp.RW {
-			t.Fatalf("Expected mount writable to match. Expected: '%v', Actual: '%v'", c.expected.RW, mp.RW)
+			t.Errorf("Expected mount writable to match. Expected: '%v', Actual: '%v'", c.expected.RW, mp.RW)
 		}
 		if c.expected.Propagation != mp.Propagation {
-			t.Fatalf("Expected mount propagation to match. Expected: '%v', Actual: '%s'", c.expected.Propagation, mp.Propagation)
+			t.Errorf("Expected mount propagation to match. Expected: '%v', Actual: '%s'", c.expected.Propagation, mp.Propagation)
 		}
 		if c.expected.Driver != mp.Driver {
-			t.Fatalf("Expected mount driver to match. Expected: '%v', Actual: '%s'", c.expected.Driver, mp.Driver)
+			t.Errorf("Expected mount driver to match. Expected: '%v', Actual: '%s'", c.expected.Driver, mp.Driver)
 		}
 		if c.expected.CopyData != mp.CopyData {
-			t.Fatalf("Expected mount copy data to match. Expected: '%v', Actual: '%v'", c.expected.CopyData, mp.CopyData)
+			t.Errorf("Expected mount copy data to match. Expected: '%v', Actual: '%v'", c.expected.CopyData, mp.CopyData)
 		}
 	}
 }

+ 3 - 141
volume/volume_unix.go

@@ -4,153 +4,15 @@ package volume
 
 import (
 	"fmt"
-	"os"
 	"path/filepath"
 	"strings"
-
-	mounttypes "github.com/docker/docker/api/types/mount"
 )
 
-var platformRawValidationOpts = []func(o *validateOpts){
-	// need to make sure to not error out if the bind source does not exist on unix
-	// this is supported for historical reasons, the path will be automatically
-	// created later.
-	func(o *validateOpts) { o.skipBindSourceCheck = true },
-}
-
-// read-write modes
-var rwModes = map[string]bool{
-	"rw": true,
-	"ro": true,
-}
-
-// label modes
-var labelModes = map[string]bool{
-	"Z": true,
-	"z": true,
-}
-
-// consistency modes
-var consistencyModes = map[mounttypes.Consistency]bool{
-	mounttypes.ConsistencyFull:      true,
-	mounttypes.ConsistencyCached:    true,
-	mounttypes.ConsistencyDelegated: true,
-}
-
-// BackwardsCompatible decides whether this mount point can be
-// used in old versions of Docker or not.
-// Only bind mounts and local volumes can be used in old versions of Docker.
-func (m *MountPoint) BackwardsCompatible() bool {
-	return len(m.Source) > 0 || m.Driver == DefaultDriverName
-}
-
-// HasResource checks whether the given absolute path for a container is in
-// this mount point. If the relative path starts with `../` then the resource
-// is outside of this mount point, but we can't simply check for this prefix
-// because it misses `..` which is also outside of the mount, so check both.
-func (m *MountPoint) HasResource(absolutePath string) bool {
+func (p *linuxParser) HasResource(m *MountPoint, absolutePath string) bool {
 	relPath, err := filepath.Rel(m.Destination, absolutePath)
 	return err == nil && relPath != ".." && !strings.HasPrefix(relPath, fmt.Sprintf("..%c", filepath.Separator))
 }
 
-// IsVolumeNameValid checks a volume name in a platform specific manner.
-func IsVolumeNameValid(name string) (bool, error) {
-	return true, nil
-}
-
-// ValidMountMode will make sure the mount mode is valid.
-// returns if it's a valid mount mode or not.
-func ValidMountMode(mode string) bool {
-	if mode == "" {
-		return true
-	}
-
-	rwModeCount := 0
-	labelModeCount := 0
-	propagationModeCount := 0
-	copyModeCount := 0
-	consistencyModeCount := 0
-
-	for _, o := range strings.Split(mode, ",") {
-		switch {
-		case rwModes[o]:
-			rwModeCount++
-		case labelModes[o]:
-			labelModeCount++
-		case propagationModes[mounttypes.Propagation(o)]:
-			propagationModeCount++
-		case copyModeExists(o):
-			copyModeCount++
-		case consistencyModes[mounttypes.Consistency(o)]:
-			consistencyModeCount++
-		default:
-			return false
-		}
-	}
-
-	// Only one string for each mode is allowed.
-	if rwModeCount > 1 || labelModeCount > 1 || propagationModeCount > 1 || copyModeCount > 1 || consistencyModeCount > 1 {
-		return false
-	}
-	return true
-}
-
-// ReadWrite tells you if a mode string is a valid read-write mode or not.
-// If there are no specifications w.r.t read write mode, then by default
-// it returns true.
-func ReadWrite(mode string) bool {
-	if !ValidMountMode(mode) {
-		return false
-	}
-
-	for _, o := range strings.Split(mode, ",") {
-		if o == "ro" {
-			return false
-		}
-	}
-	return true
-}
-
-func validateNotRoot(p string) error {
-	p = filepath.Clean(convertSlash(p))
-	if p == "/" {
-		return fmt.Errorf("invalid specification: destination can't be '/'")
-	}
-	return nil
-}
-
-func convertSlash(p string) string {
-	return p
-}
-
-// isAbsPath reports whether the path is absolute.
-func isAbsPath(p string) bool {
-	return filepath.IsAbs(p)
-}
-
-func splitRawSpec(raw string) ([]string, error) {
-	if strings.Count(raw, ":") > 2 {
-		return nil, errInvalidSpec(raw)
-	}
-
-	arr := strings.SplitN(raw, ":", 3)
-	if arr[0] == "" {
-		return nil, errInvalidSpec(raw)
-	}
-	return arr, nil
-}
-
-func detectMountType(p string) mounttypes.Type {
-	if filepath.IsAbs(p) {
-		return mounttypes.TypeBind
-	}
-	return mounttypes.TypeVolume
-}
-
-func clean(p string) string {
-	return filepath.Clean(p)
-}
-
-func validateStat(fi os.FileInfo) error {
-	return nil
+func (p *windowsParser) HasResource(m *MountPoint, absolutePath string) bool {
+	return false
 }

+ 0 - 16
volume/volume_unsupported.go

@@ -1,16 +0,0 @@
-// +build !linux
-
-package volume
-
-import (
-	"fmt"
-	"runtime"
-
-	mounttypes "github.com/docker/docker/api/types/mount"
-)
-
-// ConvertTmpfsOptions converts *mounttypes.TmpfsOptions to the raw option string
-// for mount(2).
-func ConvertTmpfsOptions(opt *mounttypes.TmpfsOptions, readOnly bool) (string, error) {
-	return "", fmt.Errorf("%s does not support tmpfs", runtime.GOOS)
-}

+ 3 - 208
volume/volume_windows.go

@@ -1,213 +1,8 @@
 package volume
 
-import (
-	"fmt"
-	"os"
-	"path/filepath"
-	"regexp"
-	"strings"
-
-	mounttypes "github.com/docker/docker/api/types/mount"
-)
-
-// read-write modes
-var rwModes = map[string]bool{
-	"rw": true,
-}
-
-// read-only modes
-var roModes = map[string]bool{
-	"ro": true,
-}
-
-var platformRawValidationOpts = []func(*validateOpts){}
-
-const (
-	// Spec should be in the format [source:]destination[:mode]
-	//
-	// Examples: c:\foo bar:d:rw
-	//           c:\foo:d:\bar
-	//           myname:d:
-	//           d:\
-	//
-	// Explanation of this regex! Thanks @thaJeztah on IRC and gist for help. See
-	// https://gist.github.com/thaJeztah/6185659e4978789fb2b2. A good place to
-	// test is https://regex-golang.appspot.com/assets/html/index.html
-	//
-	// Useful link for referencing named capturing groups:
-	// http://stackoverflow.com/questions/20750843/using-named-matches-from-go-regex
-	//
-	// There are three match groups: source, destination and mode.
-	//
-
-	// RXHostDir is the first option of a source
-	RXHostDir = `[a-z]:\\(?:[^\\/:*?"<>|\r\n]+\\?)*`
-	// RXName is the second option of a source
-	RXName = `[^\\/:*?"<>|\r\n]+`
-	// RXPipe is a named path pipe (starts with `\\.\pipe\`, possibly with / instead of \)
-	RXPipe = `[/\\]{2}.[/\\]pipe[/\\][^:*?"<>|\r\n]+`
-	// RXReservedNames are reserved names not possible on Windows
-	RXReservedNames = `(con)|(prn)|(nul)|(aux)|(com[1-9])|(lpt[1-9])`
-
-	// RXSource is the combined possibilities for a source
-	RXSource = `((?P<source>((` + RXHostDir + `)|(` + RXName + `)|(` + RXPipe + `))):)?`
-
-	// Source. Can be either a host directory, a name, or omitted:
-	//  HostDir:
-	//    -  Essentially using the folder solution from
-	//       https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch08s18.html
-	//       but adding case insensitivity.
-	//    -  Must be an absolute path such as c:\path
-	//    -  Can include spaces such as `c:\program files`
-	//    -  And then followed by a colon which is not in the capture group
-	//    -  And can be optional
-	//  Name:
-	//    -  Must not contain invalid NTFS filename characters (https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx)
-	//    -  And then followed by a colon which is not in the capture group
-	//    -  And can be optional
-
-	// RXDestinationDir is the file path option for the mount destination
-	RXDestinationDir = `([a-z]):((?:\\[^\\/:*?"<>\r\n]+)*\\?)`
-	// RXDestination is the regex expression for the mount destination
-	RXDestination = `(?P<destination>(` + RXDestinationDir + `)|(` + RXPipe + `))`
-	// Destination (aka container path):
-	//    -  Variation on hostdir but can be a drive followed by colon as well
-	//    -  If a path, must be absolute. Can include spaces
-	//    -  Drive cannot be c: (explicitly checked in code, not RegEx)
-
-	// RXMode is the regex expression for the mode of the mount
-	// Mode (optional):
-	//    -  Hopefully self explanatory in comparison to above regex's.
-	//    -  Colon is not in the capture group
-	RXMode = `(:(?P<mode>(?i)ro|rw))?`
-)
-
-// BackwardsCompatible decides whether this mount point can be
-// used in old versions of Docker or not.
-// Windows volumes are never backwards compatible.
-func (m *MountPoint) BackwardsCompatible() bool {
+func (p *windowsParser) HasResource(m *MountPoint, absolutePath string) bool {
 	return false
 }
-
-func splitRawSpec(raw string) ([]string, error) {
-	specExp := regexp.MustCompile(`^` + RXSource + RXDestination + RXMode + `$`)
-	match := specExp.FindStringSubmatch(strings.ToLower(raw))
-
-	// Must have something back
-	if len(match) == 0 {
-		return nil, errInvalidSpec(raw)
-	}
-
-	var split []string
-	matchgroups := make(map[string]string)
-	// Pull out the sub expressions from the named capture groups
-	for i, name := range specExp.SubexpNames() {
-		matchgroups[name] = strings.ToLower(match[i])
-	}
-	if source, exists := matchgroups["source"]; exists {
-		if source != "" {
-			split = append(split, source)
-		}
-	}
-	if destination, exists := matchgroups["destination"]; exists {
-		if destination != "" {
-			split = append(split, destination)
-		}
-	}
-	if mode, exists := matchgroups["mode"]; exists {
-		if mode != "" {
-			split = append(split, mode)
-		}
-	}
-	// Fix #26329. If the destination appears to be a file, and the source is null,
-	// it may be because we've fallen through the possible naming regex and hit a
-	// situation where the user intention was to map a file into a container through
-	// a local volume, but this is not supported by the platform.
-	if matchgroups["source"] == "" && matchgroups["destination"] != "" {
-		validName, err := IsVolumeNameValid(matchgroups["destination"])
-		if err != nil {
-			return nil, err
-		}
-		if !validName {
-			if fi, err := os.Stat(matchgroups["destination"]); err == nil {
-				if !fi.IsDir() {
-					return nil, fmt.Errorf("file '%s' cannot be mapped. Only directories can be mapped on this platform", matchgroups["destination"])
-				}
-			}
-		}
-	}
-	return split, nil
-}
-
-func detectMountType(p string) mounttypes.Type {
-	if strings.HasPrefix(filepath.FromSlash(p), `\\.\pipe\`) {
-		return mounttypes.TypeNamedPipe
-	} else if filepath.IsAbs(p) {
-		return mounttypes.TypeBind
-	}
-	return mounttypes.TypeVolume
-}
-
-// IsVolumeNameValid checks a volume name in a platform specific manner.
-func IsVolumeNameValid(name string) (bool, error) {
-	nameExp := regexp.MustCompile(`^` + RXName + `$`)
-	if !nameExp.MatchString(name) {
-		return false, nil
-	}
-	nameExp = regexp.MustCompile(`^` + RXReservedNames + `$`)
-	if nameExp.MatchString(name) {
-		return false, fmt.Errorf("volume name %q cannot be a reserved word for Windows filenames", name)
-	}
-	return true, nil
-}
-
-// ValidMountMode will make sure the mount mode is valid.
-// returns if it's a valid mount mode or not.
-func ValidMountMode(mode string) bool {
-	if mode == "" {
-		return true
-	}
-	return roModes[strings.ToLower(mode)] || rwModes[strings.ToLower(mode)]
-}
-
-// ReadWrite tells you if a mode string is a valid read-write mode or not.
-func ReadWrite(mode string) bool {
-	return rwModes[strings.ToLower(mode)] || mode == ""
-}
-
-func validateNotRoot(p string) error {
-	p = strings.ToLower(convertSlash(p))
-	if p == "c:" || p == `c:\` {
-		return fmt.Errorf("destination path cannot be `c:` or `c:\\`: %v", p)
-	}
-	return nil
-}
-
-func convertSlash(p string) string {
-	return filepath.FromSlash(p)
-}
-
-// isAbsPath returns whether a path is absolute for the purposes of mounting into a container
-// (absolute paths, drive letter paths such as X:, and paths starting with `\\.\` to support named pipes).
-func isAbsPath(p string) bool {
-	return filepath.IsAbs(p) ||
-		strings.HasPrefix(p, `\\.\`) ||
-		(len(p) == 2 && p[1] == ':' && ((p[0] >= 'a' && p[0] <= 'z') || (p[0] >= 'A' && p[0] <= 'Z')))
-}
-
-// Do not clean plain drive letters or paths starting with `\\.\`.
-var cleanRegexp = regexp.MustCompile(`^([a-z]:|[/\\]{2}\.[/\\].*)$`)
-
-func clean(p string) string {
-	if match := cleanRegexp.MatchString(p); match {
-		return p
-	}
-	return filepath.Clean(p)
-}
-
-func validateStat(fi os.FileInfo) error {
-	if !fi.IsDir() {
-		return fmt.Errorf("source path must be a directory")
-	}
-	return nil
+func (p *linuxParser) HasResource(m *MountPoint, absolutePath string) bool {
+	return false
 }

+ 456 - 0
volume/windows_parser.go

@@ -0,0 +1,456 @@
+package volume
+
+import (
+	"errors"
+	"fmt"
+	"os"
+	"regexp"
+	"runtime"
+	"strings"
+
+	"github.com/docker/docker/api/types/mount"
+	"github.com/docker/docker/pkg/stringid"
+)
+
+type windowsParser struct {
+}
+
+const (
+	// Spec should be in the format [source:]destination[:mode]
+	//
+	// Examples: c:\foo bar:d:rw
+	//           c:\foo:d:\bar
+	//           myname:d:
+	//           d:\
+	//
+	// Explanation of this regex! Thanks @thaJeztah on IRC and gist for help. See
+	// https://gist.github.com/thaJeztah/6185659e4978789fb2b2. A good place to
+	// test is https://regex-golang.appspot.com/assets/html/index.html
+	//
+	// Useful link for referencing named capturing groups:
+	// http://stackoverflow.com/questions/20750843/using-named-matches-from-go-regex
+	//
+	// There are three match groups: source, destination and mode.
+	//
+
+	// rxHostDir is the first option of a source
+	rxHostDir = `(?:\\\\\?\\)?[a-z]:[\\/](?:[^\\/:*?"<>|\r\n]+[\\/]?)*`
+	// rxName is the second option of a source
+	rxName = `[^\\/:*?"<>|\r\n]+`
+
+	// RXReservedNames are reserved names not possible on Windows
+	rxReservedNames = `(con)|(prn)|(nul)|(aux)|(com[1-9])|(lpt[1-9])`
+
+	// rxPipe is a named path pipe (starts with `\\.\pipe\`, possibly with / instead of \)
+	rxPipe = `[/\\]{2}.[/\\]pipe[/\\][^:*?"<>|\r\n]+`
+	// rxSource is the combined possibilities for a source
+	rxSource = `((?P<source>((` + rxHostDir + `)|(` + rxName + `)|(` + rxPipe + `))):)?`
+
+	// Source. Can be either a host directory, a name, or omitted:
+	//  HostDir:
+	//    -  Essentially using the folder solution from
+	//       https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch08s18.html
+	//       but adding case insensitivity.
+	//    -  Must be an absolute path such as c:\path
+	//    -  Can include spaces such as `c:\program files`
+	//    -  And then followed by a colon which is not in the capture group
+	//    -  And can be optional
+	//  Name:
+	//    -  Must not contain invalid NTFS filename characters (https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx)
+	//    -  And then followed by a colon which is not in the capture group
+	//    -  And can be optional
+
+	// rxDestination is the regex expression for the mount destination
+	rxDestination = `(?P<destination>((?:\\\\\?\\)?([a-z]):((?:[\\/][^\\/:*?"<>\r\n]+)*[\\/]?))|(` + rxPipe + `))`
+
+	rxLCOWDestination = `(?P<destination>/(?:[^\\/:*?"<>\r\n]+[/]?)*)`
+	// Destination (aka container path):
+	//    -  Variation on hostdir but can be a drive followed by colon as well
+	//    -  If a path, must be absolute. Can include spaces
+	//    -  Drive cannot be c: (explicitly checked in code, not RegEx)
+
+	// rxMode is the regex expression for the mode of the mount
+	// Mode (optional):
+	//    -  Hopefully self explanatory in comparison to above regex's.
+	//    -  Colon is not in the capture group
+	rxMode = `(:(?P<mode>(?i)ro|rw))?`
+)
+
+type mountValidator func(mnt *mount.Mount) error
+
+func windowsSplitRawSpec(raw, destRegex string) ([]string, error) {
+	specExp := regexp.MustCompile(`^` + rxSource + destRegex + rxMode + `$`)
+	match := specExp.FindStringSubmatch(strings.ToLower(raw))
+
+	// Must have something back
+	if len(match) == 0 {
+		return nil, errInvalidSpec(raw)
+	}
+
+	var split []string
+	matchgroups := make(map[string]string)
+	// Pull out the sub expressions from the named capture groups
+	for i, name := range specExp.SubexpNames() {
+		matchgroups[name] = strings.ToLower(match[i])
+	}
+	if source, exists := matchgroups["source"]; exists {
+		if source != "" {
+			split = append(split, source)
+		}
+	}
+	if destination, exists := matchgroups["destination"]; exists {
+		if destination != "" {
+			split = append(split, destination)
+		}
+	}
+	if mode, exists := matchgroups["mode"]; exists {
+		if mode != "" {
+			split = append(split, mode)
+		}
+	}
+	// Fix #26329. If the destination appears to be a file, and the source is null,
+	// it may be because we've fallen through the possible naming regex and hit a
+	// situation where the user intention was to map a file into a container through
+	// a local volume, but this is not supported by the platform.
+	if matchgroups["source"] == "" && matchgroups["destination"] != "" {
+		volExp := regexp.MustCompile(`^` + rxName + `$`)
+		reservedNameExp := regexp.MustCompile(`^` + rxReservedNames + `$`)
+
+		if volExp.MatchString(matchgroups["destination"]) {
+			if reservedNameExp.MatchString(matchgroups["destination"]) {
+				return nil, fmt.Errorf("volume name %q cannot be a reserved word for Windows filenames", matchgroups["destination"])
+			}
+		} else {
+
+			exists, isDir, _ := currentFileInfoProvider.fileInfo(matchgroups["destination"])
+			if exists && !isDir {
+				return nil, fmt.Errorf("file '%s' cannot be mapped. Only directories can be mapped on this platform", matchgroups["destination"])
+
+			}
+		}
+	}
+	return split, nil
+}
+
+func windowsValidMountMode(mode string) bool {
+	if mode == "" {
+		return true
+	}
+	return rwModes[strings.ToLower(mode)]
+}
+func windowsValidateNotRoot(p string) error {
+	p = strings.ToLower(strings.Replace(p, `/`, `\`, -1))
+	if p == "c:" || p == `c:\` {
+		return fmt.Errorf("destination path cannot be `c:` or `c:\\`: %v", p)
+	}
+	return nil
+}
+
+var windowsSpecificValidators mountValidator = func(mnt *mount.Mount) error {
+	return windowsValidateNotRoot(mnt.Target)
+}
+
+func windowsValidateRegex(p, r string) error {
+	if regexp.MustCompile(`^` + r + `$`).MatchString(strings.ToLower(p)) {
+		return nil
+	}
+	return fmt.Errorf("invalid mount path: '%s'", p)
+}
+func windowsValidateAbsolute(p string) error {
+	if err := windowsValidateRegex(p, rxDestination); err != nil {
+		return fmt.Errorf("invalid mount path: '%s' mount path must be absolute", p)
+	}
+	return nil
+}
+
+func windowsDetectMountType(p string) mount.Type {
+	if strings.HasPrefix(p, `\\.\pipe\`) {
+		return mount.TypeNamedPipe
+	} else if regexp.MustCompile(`^` + rxHostDir + `$`).MatchString(p) {
+		return mount.TypeBind
+	} else {
+		return mount.TypeVolume
+	}
+}
+
+func (p *windowsParser) ReadWrite(mode string) bool {
+	return strings.ToLower(mode) != "ro"
+}
+
+// IsVolumeNameValid checks a volume name in a platform specific manner.
+func (p *windowsParser) ValidateVolumeName(name string) error {
+	nameExp := regexp.MustCompile(`^` + rxName + `$`)
+	if !nameExp.MatchString(name) {
+		return errors.New("invalid volume name")
+	}
+	nameExp = regexp.MustCompile(`^` + rxReservedNames + `$`)
+	if nameExp.MatchString(name) {
+		return fmt.Errorf("volume name %q cannot be a reserved word for Windows filenames", name)
+	}
+	return nil
+}
+func (p *windowsParser) validateMountConfig(mnt *mount.Mount) error {
+	return p.validateMountConfigReg(mnt, rxDestination, windowsSpecificValidators)
+}
+
+type fileInfoProvider interface {
+	fileInfo(path string) (exist, isDir bool, err error)
+}
+
+type defaultFileInfoProvider struct {
+}
+
+func (defaultFileInfoProvider) fileInfo(path string) (exist, isDir bool, err error) {
+	fi, err := os.Stat(path)
+	if err != nil {
+		if !os.IsNotExist(err) {
+			return false, false, err
+		}
+		return false, false, nil
+	}
+	return true, fi.IsDir(), nil
+}
+
+var currentFileInfoProvider fileInfoProvider = defaultFileInfoProvider{}
+
+func (p *windowsParser) validateMountConfigReg(mnt *mount.Mount, destRegex string, additionalValidators ...mountValidator) error {
+
+	for _, v := range additionalValidators {
+		if err := v(mnt); err != nil {
+			return &errMountConfig{mnt, err}
+		}
+	}
+	if len(mnt.Target) == 0 {
+		return &errMountConfig{mnt, errMissingField("Target")}
+	}
+
+	if err := windowsValidateRegex(mnt.Target, destRegex); err != nil {
+		return &errMountConfig{mnt, err}
+	}
+
+	switch mnt.Type {
+	case mount.TypeBind:
+		if len(mnt.Source) == 0 {
+			return &errMountConfig{mnt, errMissingField("Source")}
+		}
+		// Don't error out just because the propagation mode is not supported on the platform
+		if opts := mnt.BindOptions; opts != nil {
+			if len(opts.Propagation) > 0 {
+				return &errMountConfig{mnt, fmt.Errorf("invalid propagation mode: %s", opts.Propagation)}
+			}
+		}
+		if mnt.VolumeOptions != nil {
+			return &errMountConfig{mnt, errExtraField("VolumeOptions")}
+		}
+
+		if err := windowsValidateAbsolute(mnt.Source); err != nil {
+			return &errMountConfig{mnt, err}
+		}
+
+		exists, isdir, err := currentFileInfoProvider.fileInfo(mnt.Source)
+		if err != nil {
+			return &errMountConfig{mnt, err}
+		}
+		if !exists {
+			return &errMountConfig{mnt, errBindNotExist}
+		}
+		if !isdir {
+			return &errMountConfig{mnt, fmt.Errorf("source path must be a directory")}
+		}
+
+	case mount.TypeVolume:
+		if mnt.BindOptions != nil {
+			return &errMountConfig{mnt, errExtraField("BindOptions")}
+		}
+
+		if len(mnt.Source) == 0 && mnt.ReadOnly {
+			return &errMountConfig{mnt, fmt.Errorf("must not set ReadOnly mode when using anonymous volumes")}
+		}
+
+		if len(mnt.Source) != 0 {
+			if err := p.ValidateVolumeName(mnt.Source); err != nil {
+				return &errMountConfig{mnt, err}
+			}
+		}
+	case mount.TypeNamedPipe:
+		if len(mnt.Source) == 0 {
+			return &errMountConfig{mnt, errMissingField("Source")}
+		}
+
+		if mnt.BindOptions != nil {
+			return &errMountConfig{mnt, errExtraField("BindOptions")}
+		}
+
+		if mnt.ReadOnly {
+			return &errMountConfig{mnt, errExtraField("ReadOnly")}
+		}
+
+		if windowsDetectMountType(mnt.Source) != mount.TypeNamedPipe {
+			return &errMountConfig{mnt, fmt.Errorf("'%s' is not a valid pipe path", mnt.Source)}
+		}
+
+		if windowsDetectMountType(mnt.Target) != mount.TypeNamedPipe {
+			return &errMountConfig{mnt, fmt.Errorf("'%s' is not a valid pipe path", mnt.Target)}
+		}
+	default:
+		return &errMountConfig{mnt, errors.New("mount type unknown")}
+	}
+	return nil
+}
+func (p *windowsParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
+	return p.parseMountRaw(raw, volumeDriver, rxDestination, true, windowsSpecificValidators)
+}
+
+func (p *windowsParser) parseMountRaw(raw, volumeDriver, destRegex string, convertTargetToBackslash bool, additionalValidators ...mountValidator) (*MountPoint, error) {
+	arr, err := windowsSplitRawSpec(raw, destRegex)
+	if err != nil {
+		return nil, err
+	}
+
+	var spec mount.Mount
+	var mode string
+	switch len(arr) {
+	case 1:
+		// Just a destination path in the container
+		spec.Target = arr[0]
+	case 2:
+		if windowsValidMountMode(arr[1]) {
+			// Destination + Mode is not a valid volume - volumes
+			// cannot include a mode. e.g. /foo:rw
+			return nil, errInvalidSpec(raw)
+		}
+		// Host Source Path or Name + Destination
+		spec.Source = strings.Replace(arr[0], `/`, `\`, -1)
+		spec.Target = arr[1]
+	case 3:
+		// HostSourcePath+DestinationPath+Mode
+		spec.Source = strings.Replace(arr[0], `/`, `\`, -1)
+		spec.Target = arr[1]
+		mode = arr[2]
+	default:
+		return nil, errInvalidSpec(raw)
+	}
+	if convertTargetToBackslash {
+		spec.Target = strings.Replace(spec.Target, `/`, `\`, -1)
+	}
+
+	if !windowsValidMountMode(mode) {
+		return nil, errInvalidMode(mode)
+	}
+
+	spec.Type = windowsDetectMountType(spec.Source)
+	spec.ReadOnly = !p.ReadWrite(mode)
+
+	// cannot assume that if a volume driver is passed in that we should set it
+	if volumeDriver != "" && spec.Type == mount.TypeVolume {
+		spec.VolumeOptions = &mount.VolumeOptions{
+			DriverConfig: &mount.Driver{Name: volumeDriver},
+		}
+	}
+
+	if copyData, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
+		if spec.VolumeOptions == nil {
+			spec.VolumeOptions = &mount.VolumeOptions{}
+		}
+		spec.VolumeOptions.NoCopy = !copyData
+	}
+
+	mp, err := p.parseMountSpec(spec, destRegex, convertTargetToBackslash, additionalValidators...)
+	if mp != nil {
+		mp.Mode = mode
+	}
+	if err != nil {
+		err = fmt.Errorf("%v: %v", errInvalidSpec(raw), err)
+	}
+	return mp, err
+}
+
+func (p *windowsParser) ParseMountSpec(cfg mount.Mount) (*MountPoint, error) {
+	return p.parseMountSpec(cfg, rxDestination, true, windowsSpecificValidators)
+}
+func (p *windowsParser) parseMountSpec(cfg mount.Mount, destRegex string, convertTargetToBackslash bool, additionalValidators ...mountValidator) (*MountPoint, error) {
+	if err := p.validateMountConfigReg(&cfg, destRegex, additionalValidators...); err != nil {
+		return nil, err
+	}
+	mp := &MountPoint{
+		RW:          !cfg.ReadOnly,
+		Destination: cfg.Target,
+		Type:        cfg.Type,
+		Spec:        cfg,
+	}
+	if convertTargetToBackslash {
+		mp.Destination = strings.Replace(cfg.Target, `/`, `\`, -1)
+	}
+
+	switch cfg.Type {
+	case mount.TypeVolume:
+		if cfg.Source == "" {
+			mp.Name = stringid.GenerateNonCryptoID()
+		} else {
+			mp.Name = cfg.Source
+		}
+		mp.CopyData = p.DefaultCopyMode()
+
+		if cfg.VolumeOptions != nil {
+			if cfg.VolumeOptions.DriverConfig != nil {
+				mp.Driver = cfg.VolumeOptions.DriverConfig.Name
+			}
+			if cfg.VolumeOptions.NoCopy {
+				mp.CopyData = false
+			}
+		}
+	case mount.TypeBind:
+		mp.Source = strings.Replace(cfg.Source, `/`, `\`, -1)
+	case mount.TypeNamedPipe:
+		mp.Source = strings.Replace(cfg.Source, `/`, `\`, -1)
+	}
+	// cleanup trailing `\` except for paths like `c:\`
+	if len(mp.Source) > 3 && mp.Source[len(mp.Source)-1] == '\\' {
+		mp.Source = mp.Source[:len(mp.Source)-1]
+	}
+	if len(mp.Destination) > 3 && mp.Destination[len(mp.Destination)-1] == '\\' {
+		mp.Destination = mp.Destination[:len(mp.Destination)-1]
+	}
+	return mp, nil
+}
+
+func (p *windowsParser) ParseVolumesFrom(spec string) (string, string, error) {
+	if len(spec) == 0 {
+		return "", "", fmt.Errorf("volumes-from specification cannot be an empty string")
+	}
+
+	specParts := strings.SplitN(spec, ":", 2)
+	id := specParts[0]
+	mode := "rw"
+
+	if len(specParts) == 2 {
+		mode = specParts[1]
+		if !windowsValidMountMode(mode) {
+			return "", "", errInvalidMode(mode)
+		}
+
+		// Do not allow copy modes on volumes-from
+		if _, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
+			return "", "", errInvalidMode(mode)
+		}
+	}
+	return id, mode, nil
+}
+
+func (p *windowsParser) DefaultPropagationMode() mount.Propagation {
+	return mount.Propagation("")
+}
+
+func (p *windowsParser) ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error) {
+	return "", fmt.Errorf("%s does not support tmpfs", runtime.GOOS)
+}
+func (p *windowsParser) DefaultCopyMode() bool {
+	return false
+}
+func (p *windowsParser) IsBackwardCompatible(m *MountPoint) bool {
+	return false
+}
+
+func (p *windowsParser) ValidateTmpfsMountDestination(dest string) error {
+	return errors.New("Platform does not support tmpfs")
+}