mirror of
				https://github.com/moby/moby.git
				synced 2022-11-09 12:21:53 -05:00 
			
		
		
		
	Merge pull request #16433 from Microsoft/10662-volumes5
Windows: [TP4] Add volume support
This commit is contained in:
		
						commit
						e74cf8acad
					
				
					 47 changed files with 1711 additions and 732 deletions
				
			
		| 
						 | 
				
			
			@ -331,7 +331,12 @@ func (s *router) postContainersCreate(ctx context.Context, w http.ResponseWriter
 | 
			
		|||
	version := httputils.VersionFromContext(ctx)
 | 
			
		||||
	adjustCPUShares := version.LessThan("1.19")
 | 
			
		||||
 | 
			
		||||
	ccr, err := s.daemon.ContainerCreate(name, config, hostConfig, adjustCPUShares)
 | 
			
		||||
	ccr, err := s.daemon.ContainerCreate(&daemon.ContainerCreateConfig{
 | 
			
		||||
		Name:            name,
 | 
			
		||||
		Config:          config,
 | 
			
		||||
		HostConfig:      hostConfig,
 | 
			
		||||
		AdjustCPUShares: adjustCPUShares,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -186,7 +186,7 @@ func platformSupports(command string) error {
 | 
			
		|||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	switch command {
 | 
			
		||||
	case "expose", "volume", "user", "stopsignal", "arg":
 | 
			
		||||
	case "expose", "user", "stopsignal", "arg":
 | 
			
		||||
		return fmt.Errorf("The daemon on this platform does not support the command '%s'", command)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,7 @@ package daemon
 | 
			
		|||
func checkIfPathIsInAVolume(container *Container, absPath string) (bool, error) {
 | 
			
		||||
	var toVolume bool
 | 
			
		||||
	for _, mnt := range container.MountPoints {
 | 
			
		||||
		if toVolume = mnt.hasResource(absPath); toVolume {
 | 
			
		||||
		if toVolume = mnt.HasResource(absPath); toVolume {
 | 
			
		||||
			if mnt.RW {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,6 +8,7 @@ import (
 | 
			
		|||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"syscall"
 | 
			
		||||
	"time"
 | 
			
		||||
| 
						 | 
				
			
			@ -30,8 +31,10 @@ import (
 | 
			
		|||
	"github.com/docker/docker/pkg/promise"
 | 
			
		||||
	"github.com/docker/docker/pkg/signal"
 | 
			
		||||
	"github.com/docker/docker/pkg/symlink"
 | 
			
		||||
	"github.com/docker/docker/pkg/system"
 | 
			
		||||
	"github.com/docker/docker/runconfig"
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
	"github.com/docker/docker/volume/store"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
| 
						 | 
				
			
			@ -72,6 +75,7 @@ type CommonContainer struct {
 | 
			
		|||
	RestartCount           int
 | 
			
		||||
	HasBeenStartedBefore   bool
 | 
			
		||||
	HasBeenManuallyStopped bool // used for unless-stopped restart policy
 | 
			
		||||
	MountPoints            map[string]*volume.MountPoint
 | 
			
		||||
	hostConfig             *runconfig.HostConfig
 | 
			
		||||
	command                *execdriver.Command
 | 
			
		||||
	monitor                *containerMonitor
 | 
			
		||||
| 
						 | 
				
			
			@ -1108,29 +1112,109 @@ func (container *Container) mountVolumes() error {
 | 
			
		|||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (container *Container) copyImagePathContent(v volume.Volume, destination string) error {
 | 
			
		||||
	rootfs, err := symlink.FollowSymlinkInScope(filepath.Join(container.basefs, destination), container.basefs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err = ioutil.ReadDir(rootfs); err != nil {
 | 
			
		||||
		if os.IsNotExist(err) {
 | 
			
		||||
			return nil
 | 
			
		||||
func (container *Container) prepareMountPoints() error {
 | 
			
		||||
	for _, config := range container.MountPoints {
 | 
			
		||||
		if len(config.Driver) > 0 {
 | 
			
		||||
			v, err := container.daemon.createVolume(config.Name, config.Driver, nil)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			config.Volume = v
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (container *Container) removeMountPoints(rm bool) error {
 | 
			
		||||
	var rmErrors []string
 | 
			
		||||
	for _, m := range container.MountPoints {
 | 
			
		||||
		if m.Volume == nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		container.daemon.volumes.Decrement(m.Volume)
 | 
			
		||||
		if rm {
 | 
			
		||||
			err := container.daemon.volumes.Remove(m.Volume)
 | 
			
		||||
			// ErrVolumeInUse is ignored because having this
 | 
			
		||||
			// volume being referenced by other container is
 | 
			
		||||
			// not an error, but an implementation detail.
 | 
			
		||||
			// This prevents docker from logging "ERROR: Volume in use"
 | 
			
		||||
			// where there is another container using the volume.
 | 
			
		||||
			if err != nil && err != store.ErrVolumeInUse {
 | 
			
		||||
				rmErrors = append(rmErrors, err.Error())
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(rmErrors) > 0 {
 | 
			
		||||
		return derr.ErrorCodeRemovingVolume.WithArgs(strings.Join(rmErrors, "\n"))
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (container *Container) unmountVolumes(forceSyscall bool) error {
 | 
			
		||||
	var (
 | 
			
		||||
		volumeMounts []volume.MountPoint
 | 
			
		||||
		err          error
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	for _, mntPoint := range container.MountPoints {
 | 
			
		||||
		dest, err := container.GetResourcePath(mntPoint.Destination)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		volumeMounts = append(volumeMounts, volume.MountPoint{Destination: dest, Volume: mntPoint.Volume})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Append any network mounts to the list (this is a no-op on Windows)
 | 
			
		||||
	if volumeMounts, err = appendNetworkMounts(container, volumeMounts); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	path, err := v.Mount()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	for _, volumeMount := range volumeMounts {
 | 
			
		||||
		if forceSyscall {
 | 
			
		||||
			system.UnmountWithSyscall(volumeMount.Destination)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if volumeMount.Volume != nil {
 | 
			
		||||
			if err := volumeMount.Volume.Unmount(); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := copyExistingContents(rootfs, path); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	return v.Unmount()
 | 
			
		||||
func (container *Container) addBindMountPoint(name, source, destination string, rw bool) {
 | 
			
		||||
	container.MountPoints[destination] = &volume.MountPoint{
 | 
			
		||||
		Name:        name,
 | 
			
		||||
		Source:      source,
 | 
			
		||||
		Destination: destination,
 | 
			
		||||
		RW:          rw,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (container *Container) addLocalMountPoint(name, destination string, rw bool) {
 | 
			
		||||
	container.MountPoints[destination] = &volume.MountPoint{
 | 
			
		||||
		Name:        name,
 | 
			
		||||
		Driver:      volume.DefaultDriverName,
 | 
			
		||||
		Destination: destination,
 | 
			
		||||
		RW:          rw,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (container *Container) addMountPointWithVolume(destination string, vol volume.Volume, rw bool) {
 | 
			
		||||
	container.MountPoints[destination] = &volume.MountPoint{
 | 
			
		||||
		Name:        vol.Name(),
 | 
			
		||||
		Driver:      vol.DriverName(),
 | 
			
		||||
		Destination: destination,
 | 
			
		||||
		RW:          rw,
 | 
			
		||||
		Volume:      vol,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (container *Container) isDestinationMounted(destination string) bool {
 | 
			
		||||
	return container.MountPoints[destination] != nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (container *Container) stopSignal() int {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,12 +23,12 @@ import (
 | 
			
		|||
	"github.com/docker/docker/pkg/idtools"
 | 
			
		||||
	"github.com/docker/docker/pkg/nat"
 | 
			
		||||
	"github.com/docker/docker/pkg/stringid"
 | 
			
		||||
	"github.com/docker/docker/pkg/symlink"
 | 
			
		||||
	"github.com/docker/docker/pkg/system"
 | 
			
		||||
	"github.com/docker/docker/pkg/ulimit"
 | 
			
		||||
	"github.com/docker/docker/runconfig"
 | 
			
		||||
	"github.com/docker/docker/utils"
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
	"github.com/docker/docker/volume/store"
 | 
			
		||||
	"github.com/docker/libnetwork"
 | 
			
		||||
	"github.com/docker/libnetwork/drivers/bridge"
 | 
			
		||||
	"github.com/docker/libnetwork/netlabel"
 | 
			
		||||
| 
						 | 
				
			
			@ -54,9 +54,8 @@ type Container struct {
 | 
			
		|||
	AppArmorProfile string
 | 
			
		||||
	HostnamePath    string
 | 
			
		||||
	HostsPath       string
 | 
			
		||||
	ShmPath         string
 | 
			
		||||
	MqueuePath      string
 | 
			
		||||
	MountPoints     map[string]*mountPoint
 | 
			
		||||
	ShmPath         string // TODO Windows - Factor this out (GH15862)
 | 
			
		||||
	MqueuePath      string // TODO Windows - Factor this out (GH15862)
 | 
			
		||||
	ResolvConfPath  string
 | 
			
		||||
 | 
			
		||||
	Volumes   map[string]string // Deprecated since 1.7, kept for backwards compatibility
 | 
			
		||||
| 
						 | 
				
			
			@ -1201,40 +1200,16 @@ func (container *Container) disconnectFromNetwork(n libnetwork.Network) error {
 | 
			
		|||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (container *Container) unmountVolumes(forceSyscall bool) error {
 | 
			
		||||
	var volumeMounts []mountPoint
 | 
			
		||||
 | 
			
		||||
	for _, mntPoint := range container.MountPoints {
 | 
			
		||||
		dest, err := container.GetResourcePath(mntPoint.Destination)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		volumeMounts = append(volumeMounts, mountPoint{Destination: dest, Volume: mntPoint.Volume})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
// appendNetworkMounts appends any network mounts to the array of mount points passed in
 | 
			
		||||
func appendNetworkMounts(container *Container, volumeMounts []volume.MountPoint) ([]volume.MountPoint, error) {
 | 
			
		||||
	for _, mnt := range container.networkMounts() {
 | 
			
		||||
		dest, err := container.GetResourcePath(mnt.Destination)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		volumeMounts = append(volumeMounts, mountPoint{Destination: dest})
 | 
			
		||||
		volumeMounts = append(volumeMounts, volume.MountPoint{Destination: dest})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, volumeMount := range volumeMounts {
 | 
			
		||||
		if forceSyscall {
 | 
			
		||||
			syscall.Unmount(volumeMount.Destination, 0)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if volumeMount.Volume != nil {
 | 
			
		||||
			if err := volumeMount.Volume.Unmount(); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
	return volumeMounts, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (container *Container) networkMounts() []execdriver.Mount {
 | 
			
		||||
| 
						 | 
				
			
			@ -1294,74 +1269,29 @@ func (container *Container) networkMounts() []execdriver.Mount {
 | 
			
		|||
	return mounts
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (container *Container) addBindMountPoint(name, source, destination string, rw bool) {
 | 
			
		||||
	container.MountPoints[destination] = &mountPoint{
 | 
			
		||||
		Name:        name,
 | 
			
		||||
		Source:      source,
 | 
			
		||||
		Destination: destination,
 | 
			
		||||
		RW:          rw,
 | 
			
		||||
func (container *Container) copyImagePathContent(v volume.Volume, destination string) error {
 | 
			
		||||
	rootfs, err := symlink.FollowSymlinkInScope(filepath.Join(container.basefs, destination), container.basefs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (container *Container) addLocalMountPoint(name, destination string, rw bool) {
 | 
			
		||||
	container.MountPoints[destination] = &mountPoint{
 | 
			
		||||
		Name:        name,
 | 
			
		||||
		Driver:      volume.DefaultDriverName,
 | 
			
		||||
		Destination: destination,
 | 
			
		||||
		RW:          rw,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (container *Container) addMountPointWithVolume(destination string, vol volume.Volume, rw bool) {
 | 
			
		||||
	container.MountPoints[destination] = &mountPoint{
 | 
			
		||||
		Name:        vol.Name(),
 | 
			
		||||
		Driver:      vol.DriverName(),
 | 
			
		||||
		Destination: destination,
 | 
			
		||||
		RW:          rw,
 | 
			
		||||
		Volume:      vol,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (container *Container) isDestinationMounted(destination string) bool {
 | 
			
		||||
	return container.MountPoints[destination] != nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (container *Container) prepareMountPoints() error {
 | 
			
		||||
	for _, config := range container.MountPoints {
 | 
			
		||||
		if len(config.Driver) > 0 {
 | 
			
		||||
			v, err := container.daemon.createVolume(config.Name, config.Driver, nil)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			config.Volume = v
 | 
			
		||||
	if _, err = ioutil.ReadDir(rootfs); err != nil {
 | 
			
		||||
		if os.IsNotExist(err) {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (container *Container) removeMountPoints(rm bool) error {
 | 
			
		||||
	var rmErrors []string
 | 
			
		||||
	for _, m := range container.MountPoints {
 | 
			
		||||
		if m.Volume == nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		container.daemon.volumes.Decrement(m.Volume)
 | 
			
		||||
		if rm {
 | 
			
		||||
			err := container.daemon.volumes.Remove(m.Volume)
 | 
			
		||||
			// ErrVolumeInUse is ignored because having this
 | 
			
		||||
			// volume being referenced by othe container is
 | 
			
		||||
			// not an error, but an implementation detail.
 | 
			
		||||
			// This prevents docker from logging "ERROR: Volume in use"
 | 
			
		||||
			// where there is another container using the volume.
 | 
			
		||||
			if err != nil && err != store.ErrVolumeInUse {
 | 
			
		||||
				rmErrors = append(rmErrors, err.Error())
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	path, err := v.Mount()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if len(rmErrors) > 0 {
 | 
			
		||||
		return derr.ErrorCodeRemovingVolume.WithArgs(strings.Join(rmErrors, "\n"))
 | 
			
		||||
 | 
			
		||||
	if err := copyExistingContents(rootfs, path); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
 | 
			
		||||
	return v.Unmount()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (container *Container) shmPath() (string, error) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,7 @@ import (
 | 
			
		|||
 | 
			
		||||
	"github.com/docker/docker/daemon/execdriver"
 | 
			
		||||
	derr "github.com/docker/docker/errors"
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
	"github.com/docker/libnetwork"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -169,18 +170,11 @@ func (container *Container) updateNetwork() error {
 | 
			
		|||
func (container *Container) releaseNetwork() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (container *Container) unmountVolumes(forceSyscall bool) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// prepareMountPoints is a no-op on Windows
 | 
			
		||||
func (container *Container) prepareMountPoints() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// removeMountPoints is a no-op on Windows.
 | 
			
		||||
func (container *Container) removeMountPoints(_ bool) error {
 | 
			
		||||
	return nil
 | 
			
		||||
// appendNetworkMounts appends any network mounts to the array of mount points passed in.
 | 
			
		||||
// Windows does not support network mounts (not to be confused with SMB network mounts), so
 | 
			
		||||
// this is a no-op.
 | 
			
		||||
func appendNetworkMounts(container *Container, volumeMounts []volume.MountPoint) ([]volume.MountPoint, error) {
 | 
			
		||||
	return volumeMounts, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (container *Container) setupIpcDirs() error {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,26 +15,34 @@ import (
 | 
			
		|||
	"github.com/opencontainers/runc/libcontainer/label"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ContainerCreateConfig is the parameter set to ContainerCreate()
 | 
			
		||||
type ContainerCreateConfig struct {
 | 
			
		||||
	Name            string
 | 
			
		||||
	Config          *runconfig.Config
 | 
			
		||||
	HostConfig      *runconfig.HostConfig
 | 
			
		||||
	AdjustCPUShares bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ContainerCreate takes configs and creates a container.
 | 
			
		||||
func (daemon *Daemon) ContainerCreate(name string, config *runconfig.Config, hostConfig *runconfig.HostConfig, adjustCPUShares bool) (types.ContainerCreateResponse, error) {
 | 
			
		||||
	if config == nil {
 | 
			
		||||
func (daemon *Daemon) ContainerCreate(params *ContainerCreateConfig) (types.ContainerCreateResponse, error) {
 | 
			
		||||
	if params.Config == nil {
 | 
			
		||||
		return types.ContainerCreateResponse{}, derr.ErrorCodeEmptyConfig
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	warnings, err := daemon.verifyContainerSettings(hostConfig, config)
 | 
			
		||||
	warnings, err := daemon.verifyContainerSettings(params.HostConfig, params.Config)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return types.ContainerCreateResponse{"", warnings}, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	daemon.adaptContainerSettings(hostConfig, adjustCPUShares)
 | 
			
		||||
	daemon.adaptContainerSettings(params.HostConfig, params.AdjustCPUShares)
 | 
			
		||||
 | 
			
		||||
	container, err := daemon.Create(config, hostConfig, name)
 | 
			
		||||
	container, err := daemon.create(params)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if daemon.Graph().IsNotExist(err, config.Image) {
 | 
			
		||||
			if strings.Contains(config.Image, "@") {
 | 
			
		||||
				return types.ContainerCreateResponse{"", warnings}, derr.ErrorCodeNoSuchImageHash.WithArgs(config.Image)
 | 
			
		||||
		if daemon.Graph().IsNotExist(err, params.Config.Image) {
 | 
			
		||||
			if strings.Contains(params.Config.Image, "@") {
 | 
			
		||||
				return types.ContainerCreateResponse{"", warnings}, derr.ErrorCodeNoSuchImageHash.WithArgs(params.Config.Image)
 | 
			
		||||
			}
 | 
			
		||||
			img, tag := parsers.ParseRepositoryTag(config.Image)
 | 
			
		||||
			img, tag := parsers.ParseRepositoryTag(params.Config.Image)
 | 
			
		||||
			if tag == "" {
 | 
			
		||||
				tag = tags.DefaultTag
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			@ -47,7 +55,7 @@ func (daemon *Daemon) ContainerCreate(name string, config *runconfig.Config, hos
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// Create creates a new container from the given configuration with a given name.
 | 
			
		||||
func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.HostConfig, name string) (retC *Container, retErr error) {
 | 
			
		||||
func (daemon *Daemon) create(params *ContainerCreateConfig) (retC *Container, retErr error) {
 | 
			
		||||
	var (
 | 
			
		||||
		container *Container
 | 
			
		||||
		img       *image.Image
 | 
			
		||||
| 
						 | 
				
			
			@ -55,8 +63,8 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos
 | 
			
		|||
		err       error
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	if config.Image != "" {
 | 
			
		||||
		img, err = daemon.repositories.LookupImage(config.Image)
 | 
			
		||||
	if params.Config.Image != "" {
 | 
			
		||||
		img, err = daemon.repositories.LookupImage(params.Config.Image)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -66,20 +74,20 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos
 | 
			
		|||
		imgID = img.ID
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := daemon.mergeAndVerifyConfig(config, img); err != nil {
 | 
			
		||||
	if err := daemon.mergeAndVerifyConfig(params.Config, img); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if hostConfig == nil {
 | 
			
		||||
		hostConfig = &runconfig.HostConfig{}
 | 
			
		||||
	if params.HostConfig == nil {
 | 
			
		||||
		params.HostConfig = &runconfig.HostConfig{}
 | 
			
		||||
	}
 | 
			
		||||
	if hostConfig.SecurityOpt == nil {
 | 
			
		||||
		hostConfig.SecurityOpt, err = daemon.generateSecurityOpt(hostConfig.IpcMode, hostConfig.PidMode)
 | 
			
		||||
	if params.HostConfig.SecurityOpt == nil {
 | 
			
		||||
		params.HostConfig.SecurityOpt, err = daemon.generateSecurityOpt(params.HostConfig.IpcMode, params.HostConfig.PidMode)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if container, err = daemon.newContainer(name, config, imgID); err != nil {
 | 
			
		||||
	if container, err = daemon.newContainer(params.Name, params.Config, imgID); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
| 
						 | 
				
			
			@ -96,7 +104,7 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos
 | 
			
		|||
	if err := daemon.createRootfs(container); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if err := daemon.setHostConfig(container, hostConfig); err != nil {
 | 
			
		||||
	if err := daemon.setHostConfig(container, params.HostConfig); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
| 
						 | 
				
			
			@ -111,7 +119,7 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos
 | 
			
		|||
	}
 | 
			
		||||
	defer container.Unmount()
 | 
			
		||||
 | 
			
		||||
	if err := createContainerPlatformSpecificSettings(container, config, hostConfig, img); err != nil {
 | 
			
		||||
	if err := createContainerPlatformSpecificSettings(container, params.Config, params.HostConfig, img); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,9 +16,11 @@ import (
 | 
			
		|||
 | 
			
		||||
// createContainerPlatformSpecificSettings performs platform specific container create functionality
 | 
			
		||||
func createContainerPlatformSpecificSettings(container *Container, config *runconfig.Config, hostConfig *runconfig.HostConfig, img *image.Image) error {
 | 
			
		||||
	var name, destination string
 | 
			
		||||
 | 
			
		||||
	for spec := range config.Volumes {
 | 
			
		||||
		name := stringid.GenerateNonCryptoID()
 | 
			
		||||
		destination := filepath.Clean(spec)
 | 
			
		||||
		name = stringid.GenerateNonCryptoID()
 | 
			
		||||
		destination = filepath.Clean(spec)
 | 
			
		||||
 | 
			
		||||
		// Skip volumes for which we already have something mounted on that
 | 
			
		||||
		// destination because of a --volume-from.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,11 +1,83 @@
 | 
			
		|||
package daemon
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/image"
 | 
			
		||||
	"github.com/docker/docker/pkg/stringid"
 | 
			
		||||
	"github.com/docker/docker/runconfig"
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// createContainerPlatformSpecificSettings performs platform specific container create functionality
 | 
			
		||||
func createContainerPlatformSpecificSettings(container *Container, config *runconfig.Config, hostConfig *runconfig.HostConfig, img *image.Image) error {
 | 
			
		||||
	for spec := range config.Volumes {
 | 
			
		||||
 | 
			
		||||
		mp, err := volume.ParseMountSpec(spec, hostConfig.VolumeDriver)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("Unrecognised volume spec: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// If the mountpoint doesn't have a name, generate one.
 | 
			
		||||
		if len(mp.Name) == 0 {
 | 
			
		||||
			mp.Name = stringid.GenerateNonCryptoID()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Skip volumes for which we already have something mounted on that
 | 
			
		||||
		// destination because of a --volume-from.
 | 
			
		||||
		if container.isDestinationMounted(mp.Destination) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		volumeDriver := hostConfig.VolumeDriver
 | 
			
		||||
		if mp.Destination != "" && img != nil {
 | 
			
		||||
			if _, ok := img.ContainerConfig.Volumes[mp.Destination]; ok {
 | 
			
		||||
				// check for whether bind is not specified and then set to local
 | 
			
		||||
				if _, ok := container.MountPoints[mp.Destination]; !ok {
 | 
			
		||||
					volumeDriver = volume.DefaultDriverName
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Create the volume in the volume driver. If it doesn't exist,
 | 
			
		||||
		// a new one will be created.
 | 
			
		||||
		v, err := container.daemon.createVolume(mp.Name, volumeDriver, nil)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// FIXME Windows: This code block is present in the Linux version and
 | 
			
		||||
		// allows the contents to be copied to the container FS prior to it
 | 
			
		||||
		// being started. However, the function utilises the FollowSymLinkInScope
 | 
			
		||||
		// path which does not cope with Windows volume-style file paths. There
 | 
			
		||||
		// is a seperate effort to resolve this (@swernli), so this processing
 | 
			
		||||
		// is deferred for now. A case where this would be useful is when
 | 
			
		||||
		// a dockerfile includes a VOLUME statement, but something is created
 | 
			
		||||
		// in that directory during the dockerfile processing. What this means
 | 
			
		||||
		// on Windows for TP4 is that in that scenario, the contents will not
 | 
			
		||||
		// copied, but that's (somewhat) OK as HCS will bomb out soon after
 | 
			
		||||
		// at it doesn't support mapped directories which have contents in the
 | 
			
		||||
		// destination path anyway.
 | 
			
		||||
		//
 | 
			
		||||
		// Example for repro later:
 | 
			
		||||
		//   FROM windowsservercore
 | 
			
		||||
		//   RUN mkdir c:\myvol
 | 
			
		||||
		//   RUN copy c:\windows\system32\ntdll.dll c:\myvol
 | 
			
		||||
		//   VOLUME "c:\myvol"
 | 
			
		||||
		//
 | 
			
		||||
		// Then
 | 
			
		||||
		//   docker build -t vol .
 | 
			
		||||
		//   docker run -it --rm vol cmd  <-- This is where HCS will error out.
 | 
			
		||||
		//
 | 
			
		||||
		//	// never attempt to copy existing content in a container FS to a shared volume
 | 
			
		||||
		//	if v.DriverName() == volume.DefaultDriverName {
 | 
			
		||||
		//		if err := container.copyImagePathContent(v, mp.Destination); err != nil {
 | 
			
		||||
		//			return err
 | 
			
		||||
		//		}
 | 
			
		||||
		//	}
 | 
			
		||||
 | 
			
		||||
		// Add it to container.MountPoints
 | 
			
		||||
		container.addMountPointWithVolume(mp.Destination, v, mp.RW)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,6 +22,7 @@ import (
 | 
			
		|||
	"github.com/docker/docker/pkg/sysinfo"
 | 
			
		||||
	"github.com/docker/docker/runconfig"
 | 
			
		||||
	"github.com/docker/docker/utils"
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
	"github.com/docker/libnetwork"
 | 
			
		||||
	nwconfig "github.com/docker/libnetwork/config"
 | 
			
		||||
	"github.com/docker/libnetwork/drivers/bridge"
 | 
			
		||||
| 
						 | 
				
			
			@ -603,10 +604,10 @@ func (daemon *Daemon) newBaseContainer(id string) Container {
 | 
			
		|||
			State:        NewState(),
 | 
			
		||||
			execCommands: newExecStore(),
 | 
			
		||||
			root:         daemon.containerRoot(id),
 | 
			
		||||
			MountPoints:  make(map[string]*volume.MountPoint),
 | 
			
		||||
		},
 | 
			
		||||
		MountPoints: make(map[string]*mountPoint),
 | 
			
		||||
		Volumes:     make(map[string]string),
 | 
			
		||||
		VolumesRW:   make(map[string]bool),
 | 
			
		||||
		Volumes:   make(map[string]string),
 | 
			
		||||
		VolumesRW: make(map[string]bool),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -83,7 +83,12 @@ func (d Docker) Container(id string) (*daemon.Container, error) {
 | 
			
		|||
 | 
			
		||||
// Create creates a new Docker container and returns potential warnings
 | 
			
		||||
func (d Docker) Create(cfg *runconfig.Config, hostCfg *runconfig.HostConfig) (*daemon.Container, []string, error) {
 | 
			
		||||
	ccr, err := d.Daemon.ContainerCreate("", cfg, hostCfg, true)
 | 
			
		||||
	ccr, err := d.Daemon.ContainerCreate(&daemon.ContainerCreateConfig{
 | 
			
		||||
		Name:            "",
 | 
			
		||||
		Config:          cfg,
 | 
			
		||||
		HostConfig:      hostCfg,
 | 
			
		||||
		AdjustCPUShares: true,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -165,16 +165,8 @@ type ResourceStats struct {
 | 
			
		|||
	SystemUsage uint64    `json:"system_usage"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Mount contains information for a mount operation.
 | 
			
		||||
type Mount struct {
 | 
			
		||||
	Source      string `json:"source"`
 | 
			
		||||
	Destination string `json:"destination"`
 | 
			
		||||
	Writable    bool   `json:"writable"`
 | 
			
		||||
	Private     bool   `json:"private"`
 | 
			
		||||
	Slave       bool   `json:"slave"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// User contains the uid and gid representing a Unix user
 | 
			
		||||
// TODO Windows: Factor out User
 | 
			
		||||
type User struct {
 | 
			
		||||
	UID int `json:"root_uid"`
 | 
			
		||||
	GID int `json:"root_gid"`
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,6 +18,15 @@ import (
 | 
			
		|||
	"github.com/opencontainers/runc/libcontainer/configs"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Mount contains information for a mount operation.
 | 
			
		||||
type Mount struct {
 | 
			
		||||
	Source      string `json:"source"`
 | 
			
		||||
	Destination string `json:"destination"`
 | 
			
		||||
	Writable    bool   `json:"writable"`
 | 
			
		||||
	Private     bool   `json:"private"`
 | 
			
		||||
	Slave       bool   `json:"slave"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Network settings of the container
 | 
			
		||||
type Network struct {
 | 
			
		||||
	Mtu            int    `json:"mtu"`
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,13 @@ package execdriver
 | 
			
		|||
 | 
			
		||||
import "github.com/docker/docker/pkg/nat"
 | 
			
		||||
 | 
			
		||||
// Mount contains information for a mount operation.
 | 
			
		||||
type Mount struct {
 | 
			
		||||
	Source      string `json:"source"`
 | 
			
		||||
	Destination string `json:"destination"`
 | 
			
		||||
	Writable    bool   `json:"writable"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Network settings of the container
 | 
			
		||||
type Network struct {
 | 
			
		||||
	Interface   *NetworkInterface `json:"interface"`
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,8 +2,6 @@
 | 
			
		|||
 | 
			
		||||
package windows
 | 
			
		||||
 | 
			
		||||
// Note this is alpha code for the bring up of containers on Windows.
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
| 
						 | 
				
			
			@ -60,18 +58,25 @@ type device struct {
 | 
			
		|||
	Settings   interface{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type mappedDir struct {
 | 
			
		||||
	HostPath      string
 | 
			
		||||
	ContainerPath string
 | 
			
		||||
	ReadOnly      bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type containerInit struct {
 | 
			
		||||
	SystemType              string   // HCS requires this to be hard-coded to "Container"
 | 
			
		||||
	Name                    string   // Name of the container. We use the docker ID.
 | 
			
		||||
	Owner                   string   // The management platform that created this container
 | 
			
		||||
	IsDummy                 bool     // Used for development purposes.
 | 
			
		||||
	VolumePath              string   // Windows volume path for scratch space
 | 
			
		||||
	Devices                 []device // Devices used by the container
 | 
			
		||||
	IgnoreFlushesDuringBoot bool     // Optimisation hint for container startup in Windows
 | 
			
		||||
	LayerFolderPath         string   // Where the layer folders are located
 | 
			
		||||
	Layers                  []layer  // List of storage layers
 | 
			
		||||
	ProcessorWeight         int64    // CPU Shares 1..9 on Windows; or 0 is platform default.
 | 
			
		||||
	HostName                string   // Hostname
 | 
			
		||||
	SystemType              string      // HCS requires this to be hard-coded to "Container"
 | 
			
		||||
	Name                    string      // Name of the container. We use the docker ID.
 | 
			
		||||
	Owner                   string      // The management platform that created this container
 | 
			
		||||
	IsDummy                 bool        // Used for development purposes.
 | 
			
		||||
	VolumePath              string      // Windows volume path for scratch space
 | 
			
		||||
	Devices                 []device    // Devices used by the container
 | 
			
		||||
	IgnoreFlushesDuringBoot bool        // Optimisation hint for container startup in Windows
 | 
			
		||||
	LayerFolderPath         string      // Where the layer folders are located
 | 
			
		||||
	Layers                  []layer     // List of storage layers
 | 
			
		||||
	ProcessorWeight         int64       // CPU Shares 1..9 on Windows; or 0 is platform default.
 | 
			
		||||
	HostName                string      // Hostname
 | 
			
		||||
	MappedDirectories       []mappedDir // List of mapped directories (volumes/mounts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// defaultOwner is a tag passed to HCS to allow it to differentiate between
 | 
			
		||||
| 
						 | 
				
			
			@ -105,18 +110,28 @@ func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execd
 | 
			
		|||
		HostName:                c.Hostname,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < len(c.LayerPaths); i++ {
 | 
			
		||||
		_, filename := filepath.Split(c.LayerPaths[i])
 | 
			
		||||
	for _, layerPath := range c.LayerPaths {
 | 
			
		||||
		_, filename := filepath.Split(layerPath)
 | 
			
		||||
		g, err := hcsshim.NameToGuid(filename)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return execdriver.ExitStatus{ExitCode: -1}, err
 | 
			
		||||
		}
 | 
			
		||||
		cu.Layers = append(cu.Layers, layer{
 | 
			
		||||
			ID:   g.ToString(),
 | 
			
		||||
			Path: c.LayerPaths[i],
 | 
			
		||||
			Path: layerPath,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Add the mounts (volumes, bind mounts etc) to the structure
 | 
			
		||||
	mds := make([]mappedDir, len(c.Mounts))
 | 
			
		||||
	for i, mount := range c.Mounts {
 | 
			
		||||
		mds[i] = mappedDir{
 | 
			
		||||
			HostPath:      mount.Source,
 | 
			
		||||
			ContainerPath: mount.Destination,
 | 
			
		||||
			ReadOnly:      !mount.Writable}
 | 
			
		||||
	}
 | 
			
		||||
	cu.MappedDirectories = mds
 | 
			
		||||
 | 
			
		||||
	// TODO Windows. At some point, when there is CLI on docker run to
 | 
			
		||||
	// enable the IP Address of the container to be passed into docker run,
 | 
			
		||||
	// the IP Address needs to be wired through to HCS in the JSON. It
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,17 @@ func setPlatformSpecificContainerFields(container *Container, contJSONBase *type
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func addMountPoints(container *Container) []types.MountPoint {
 | 
			
		||||
	return nil
 | 
			
		||||
	mountPoints := make([]types.MountPoint, 0, len(container.MountPoints))
 | 
			
		||||
	for _, m := range container.MountPoints {
 | 
			
		||||
		mountPoints = append(mountPoints, types.MountPoint{
 | 
			
		||||
			Name:        m.Name,
 | 
			
		||||
			Source:      m.Path(),
 | 
			
		||||
			Destination: m.Destination,
 | 
			
		||||
			Driver:      m.Driver,
 | 
			
		||||
			RW:          m.RW,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return mountPoints
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ContainerInspectPre120 get containers for pre 1.20 APIs.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,18 +2,16 @@ package daemon
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/docker/docker/api/types"
 | 
			
		||||
	"github.com/docker/docker/daemon/execdriver"
 | 
			
		||||
	derr "github.com/docker/docker/errors"
 | 
			
		||||
	"github.com/docker/docker/pkg/chrootarchive"
 | 
			
		||||
	"github.com/docker/docker/pkg/system"
 | 
			
		||||
	"github.com/docker/docker/runconfig"
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
	"github.com/opencontainers/runc/libcontainer/label"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
| 
						 | 
				
			
			@ -22,82 +20,7 @@ var (
 | 
			
		|||
	ErrVolumeReadonly = errors.New("mounted volume is marked read-only")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// mountPoint is the intersection point between a volume and a container. It
 | 
			
		||||
// specifies which volume is to be used and where inside a container it should
 | 
			
		||||
// be mounted.
 | 
			
		||||
type mountPoint struct {
 | 
			
		||||
	Name        string
 | 
			
		||||
	Destination string
 | 
			
		||||
	Driver      string
 | 
			
		||||
	RW          bool
 | 
			
		||||
	Volume      volume.Volume `json:"-"`
 | 
			
		||||
	Source      string
 | 
			
		||||
	Mode        string `json:"Relabel"` // Originally field was `Relabel`"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Setup sets up a mount point by either mounting the volume if it is
 | 
			
		||||
// configured, or creating the source directory if supplied.
 | 
			
		||||
func (m *mountPoint) Setup() (string, error) {
 | 
			
		||||
	if m.Volume != nil {
 | 
			
		||||
		return m.Volume.Mount()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(m.Source) > 0 {
 | 
			
		||||
		if _, err := os.Stat(m.Source); err != nil {
 | 
			
		||||
			if !os.IsNotExist(err) {
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
			logrus.Warnf("Auto-creating non-existant volume host path %s, this is deprecated and will be removed soon", m.Source)
 | 
			
		||||
			if err := system.MkdirAll(m.Source, 0755); err != nil {
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return m.Source, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return "", derr.ErrorCodeMountSetup
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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 {
 | 
			
		||||
	relPath, err := filepath.Rel(m.Destination, absolutePath)
 | 
			
		||||
 | 
			
		||||
	return err == nil && relPath != ".." && !strings.HasPrefix(relPath, fmt.Sprintf("..%c", filepath.Separator))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Path returns the path of a volume in a mount point.
 | 
			
		||||
func (m *mountPoint) Path() string {
 | 
			
		||||
	if m.Volume != nil {
 | 
			
		||||
		return m.Volume.Path()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return m.Source
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// copyExistingContents copies from the source to the destination and
 | 
			
		||||
// ensures the ownership is appropriately set.
 | 
			
		||||
func copyExistingContents(source, destination string) error {
 | 
			
		||||
	volList, err := ioutil.ReadDir(source)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if len(volList) > 0 {
 | 
			
		||||
		srcList, err := ioutil.ReadDir(destination)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if len(srcList) == 0 {
 | 
			
		||||
			// If the source volume is empty copy files from the root into the volume
 | 
			
		||||
			if err := chrootarchive.CopyWithTar(source, destination); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return copyOwnership(source, destination)
 | 
			
		||||
}
 | 
			
		||||
type mounts []execdriver.Mount
 | 
			
		||||
 | 
			
		||||
// volumeToAPIType converts a volume.Volume to the type used by the remote API
 | 
			
		||||
func volumeToAPIType(v volume.Volume) *types.Volume {
 | 
			
		||||
| 
						 | 
				
			
			@ -107,3 +30,126 @@ func volumeToAPIType(v volume.Volume) *types.Volume {
 | 
			
		|||
		Mountpoint: v.Path(),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// createVolume creates a volume.
 | 
			
		||||
func (daemon *Daemon) createVolume(name, driverName string, opts map[string]string) (volume.Volume, error) {
 | 
			
		||||
	v, err := daemon.volumes.Create(name, driverName, opts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	daemon.volumes.Increment(v)
 | 
			
		||||
	return v, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Len returns the number of mounts. Used in sorting.
 | 
			
		||||
func (m mounts) Len() int {
 | 
			
		||||
	return len(m)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Less returns true if the number of parts (a/b/c would be 3 parts) in the
 | 
			
		||||
// mount indexed by parameter 1 is less than that of the mount indexed by
 | 
			
		||||
// parameter 2. Used in sorting.
 | 
			
		||||
func (m mounts) Less(i, j int) bool {
 | 
			
		||||
	return m.parts(i) < m.parts(j)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Swap swaps two items in an array of mounts. Used in sorting
 | 
			
		||||
func (m mounts) Swap(i, j int) {
 | 
			
		||||
	m[i], m[j] = m[j], m[i]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parts returns the number of parts in the destination of a mount. Used in sorting.
 | 
			
		||||
func (m mounts) parts(i int) int {
 | 
			
		||||
	return strings.Count(filepath.Clean(m[i].Destination), string(os.PathSeparator))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// registerMountPoints initializes the container mount points with the configured volumes and bind mounts.
 | 
			
		||||
// It follows the next sequence to decide what to mount in each final destination:
 | 
			
		||||
//
 | 
			
		||||
// 1. Select the previously configured mount points for the containers, if any.
 | 
			
		||||
// 2. Select the volumes mounted from another containers. Overrides previously configured mount point destination.
 | 
			
		||||
// 3. Select the bind mounts set by the client. Overrides previously configured mount point destinations.
 | 
			
		||||
func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runconfig.HostConfig) error {
 | 
			
		||||
	binds := map[string]bool{}
 | 
			
		||||
	mountPoints := map[string]*volume.MountPoint{}
 | 
			
		||||
 | 
			
		||||
	// 1. Read already configured mount points.
 | 
			
		||||
	for name, point := range container.MountPoints {
 | 
			
		||||
		mountPoints[name] = point
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 2. Read volumes from other containers.
 | 
			
		||||
	for _, v := range hostConfig.VolumesFrom {
 | 
			
		||||
		containerID, mode, err := volume.ParseVolumesFrom(v)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c, err := daemon.Get(containerID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, m := range c.MountPoints {
 | 
			
		||||
			cp := &volume.MountPoint{
 | 
			
		||||
				Name:        m.Name,
 | 
			
		||||
				Source:      m.Source,
 | 
			
		||||
				RW:          m.RW && volume.ReadWrite(mode),
 | 
			
		||||
				Driver:      m.Driver,
 | 
			
		||||
				Destination: m.Destination,
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if len(cp.Source) == 0 {
 | 
			
		||||
				v, err := daemon.createVolume(cp.Name, cp.Driver, nil)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
				cp.Volume = v
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			mountPoints[cp.Destination] = cp
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 3. Read bind mounts
 | 
			
		||||
	for _, b := range hostConfig.Binds {
 | 
			
		||||
		// #10618
 | 
			
		||||
		bind, err := volume.ParseMountSpec(b, hostConfig.VolumeDriver)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if binds[bind.Destination] {
 | 
			
		||||
			return derr.ErrorCodeVolumeDup.WithArgs(bind.Destination)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(bind.Name) > 0 && len(bind.Driver) > 0 {
 | 
			
		||||
			// create the volume
 | 
			
		||||
			v, err := daemon.createVolume(bind.Name, bind.Driver, nil)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			bind.Volume = v
 | 
			
		||||
			bind.Source = v.Path()
 | 
			
		||||
			// bind.Name is an already existing volume, we need to use that here
 | 
			
		||||
			bind.Driver = v.DriverName()
 | 
			
		||||
			bind = setBindModeIfNull(bind)
 | 
			
		||||
		}
 | 
			
		||||
		shared := label.IsShared(bind.Mode)
 | 
			
		||||
		if err := label.Relabel(bind.Source, container.MountLabel, shared); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		binds[bind.Destination] = true
 | 
			
		||||
		mountPoints[bind.Destination] = bind
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bcVolumes, bcVolumesRW := configureBackCompatStructures(daemon, container, mountPoints)
 | 
			
		||||
 | 
			
		||||
	container.Lock()
 | 
			
		||||
	container.MountPoints = mountPoints
 | 
			
		||||
	setBackCompatStructures(container, bcVolumes, bcVolumesRW)
 | 
			
		||||
 | 
			
		||||
	container.Unlock()
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,58 +0,0 @@
 | 
			
		|||
// +build experimental
 | 
			
		||||
 | 
			
		||||
package daemon
 | 
			
		||||
 | 
			
		||||
import "testing"
 | 
			
		||||
 | 
			
		||||
func TestParseBindMount(t *testing.T) {
 | 
			
		||||
	cases := []struct {
 | 
			
		||||
		bind      string
 | 
			
		||||
		driver    string
 | 
			
		||||
		expDest   string
 | 
			
		||||
		expSource string
 | 
			
		||||
		expName   string
 | 
			
		||||
		expDriver string
 | 
			
		||||
		expRW     bool
 | 
			
		||||
		fail      bool
 | 
			
		||||
	}{
 | 
			
		||||
		{"/tmp:/tmp", "", "/tmp", "/tmp", "", "", true, false},
 | 
			
		||||
		{"/tmp:/tmp:ro", "", "/tmp", "/tmp", "", "", false, false},
 | 
			
		||||
		{"/tmp:/tmp:rw", "", "/tmp", "/tmp", "", "", true, false},
 | 
			
		||||
		{"/tmp:/tmp:foo", "", "/tmp", "/tmp", "", "", false, true},
 | 
			
		||||
		{"name:/tmp", "", "/tmp", "", "name", "local", true, false},
 | 
			
		||||
		{"name:/tmp", "external", "/tmp", "", "name", "external", true, false},
 | 
			
		||||
		{"name:/tmp:ro", "local", "/tmp", "", "name", "local", false, false},
 | 
			
		||||
		{"local/name:/tmp:rw", "", "/tmp", "", "local/name", "local", true, false},
 | 
			
		||||
		{"/tmp:tmp", "", "", "", "", "", true, true},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, c := range cases {
 | 
			
		||||
		m, err := parseBindMount(c.bind, c.driver)
 | 
			
		||||
		if c.fail {
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				t.Fatalf("Expected error, was nil, for spec %s\n", c.bind)
 | 
			
		||||
			}
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if m.Destination != c.expDest {
 | 
			
		||||
			t.Fatalf("Expected destination %s, was %s, for spec %s\n", c.expDest, m.Destination, c.bind)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if m.Source != c.expSource {
 | 
			
		||||
			t.Fatalf("Expected source %s, was %s, for spec %s\n", c.expSource, m.Source, c.bind)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if m.Name != c.expName {
 | 
			
		||||
			t.Fatalf("Expected name %s, was %s for spec %s\n", c.expName, m.Name, c.bind)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if m.Driver != c.expDriver {
 | 
			
		||||
			t.Fatalf("Expected driver %s, was %s, for spec %s\n", c.expDriver, m.Driver, c.bind)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if m.RW != c.expRW {
 | 
			
		||||
			t.Fatalf("Expected RW %v, was %v for spec %s\n", c.expRW, m.RW, c.bind)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +1,9 @@
 | 
			
		|||
package daemon
 | 
			
		||||
 | 
			
		||||
import "testing"
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestParseVolumesFrom(t *testing.T) {
 | 
			
		||||
	cases := []struct {
 | 
			
		||||
| 
						 | 
				
			
			@ -17,7 +20,7 @@ func TestParseVolumesFrom(t *testing.T) {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	for _, c := range cases {
 | 
			
		||||
		id, mode, err := parseVolumesFrom(c.spec)
 | 
			
		||||
		id, mode, err := volume.ParseVolumesFrom(c.spec)
 | 
			
		||||
		if c.fail {
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				t.Fatalf("Expected error, was nil, for spec %s\n", c.spec)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,15 +11,35 @@ import (
 | 
			
		|||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/docker/docker/daemon/execdriver"
 | 
			
		||||
	derr "github.com/docker/docker/errors"
 | 
			
		||||
	"github.com/docker/docker/pkg/chrootarchive"
 | 
			
		||||
	"github.com/docker/docker/pkg/system"
 | 
			
		||||
	"github.com/docker/docker/runconfig"
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
	volumedrivers "github.com/docker/docker/volume/drivers"
 | 
			
		||||
	"github.com/docker/docker/volume/local"
 | 
			
		||||
	"github.com/opencontainers/runc/libcontainer/label"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// copyExistingContents copies from the source to the destination and
 | 
			
		||||
// ensures the ownership is appropriately set.
 | 
			
		||||
func copyExistingContents(source, destination string) error {
 | 
			
		||||
	volList, err := ioutil.ReadDir(source)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if len(volList) > 0 {
 | 
			
		||||
		srcList, err := ioutil.ReadDir(destination)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if len(srcList) == 0 {
 | 
			
		||||
			// If the source volume is empty copy files from the root into the volume
 | 
			
		||||
			if err := chrootarchive.CopyWithTar(source, destination); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return copyOwnership(source, destination)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// copyOwnership copies the permissions and uid:gid of the source file
 | 
			
		||||
// to the destination file
 | 
			
		||||
func copyOwnership(source, destination string) error {
 | 
			
		||||
| 
						 | 
				
			
			@ -68,53 +88,6 @@ func (container *Container) setupMounts() ([]execdriver.Mount, error) {
 | 
			
		|||
	return append(mounts, netMounts...), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseBindMount validates the configuration of mount information in runconfig is valid.
 | 
			
		||||
func parseBindMount(spec, volumeDriver string) (*mountPoint, error) {
 | 
			
		||||
	bind := &mountPoint{
 | 
			
		||||
		RW: true,
 | 
			
		||||
	}
 | 
			
		||||
	arr := strings.Split(spec, ":")
 | 
			
		||||
 | 
			
		||||
	switch len(arr) {
 | 
			
		||||
	case 2:
 | 
			
		||||
		bind.Destination = arr[1]
 | 
			
		||||
	case 3:
 | 
			
		||||
		bind.Destination = arr[1]
 | 
			
		||||
		mode := arr[2]
 | 
			
		||||
		if !volume.ValidMountMode(mode) {
 | 
			
		||||
			return nil, derr.ErrorCodeVolumeInvalidMode.WithArgs(mode)
 | 
			
		||||
		}
 | 
			
		||||
		bind.RW = volume.ReadWrite(mode)
 | 
			
		||||
		// Mode field is used by SELinux to decide whether to apply label
 | 
			
		||||
		bind.Mode = mode
 | 
			
		||||
	default:
 | 
			
		||||
		return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//validate the volumes destination path
 | 
			
		||||
	if !filepath.IsAbs(bind.Destination) {
 | 
			
		||||
		return nil, derr.ErrorCodeVolumeAbs.WithArgs(bind.Destination)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	name, source, err := parseVolumeSource(arr[0])
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(source) == 0 {
 | 
			
		||||
		bind.Driver = volumeDriver
 | 
			
		||||
		if len(bind.Driver) == 0 {
 | 
			
		||||
			bind.Driver = volume.DefaultDriverName
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		bind.Source = filepath.Clean(source)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bind.Name = name
 | 
			
		||||
	bind.Destination = filepath.Clean(bind.Destination)
 | 
			
		||||
	return bind, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// sortMounts sorts an array of mounts in lexicographic order. This ensure that
 | 
			
		||||
// when mounting, the mounts don't shadow other mounts. For example, if mounting
 | 
			
		||||
// /etc and /etc/resolv.conf, /etc/resolv.conf must not be mounted first.
 | 
			
		||||
| 
						 | 
				
			
			@ -123,30 +96,6 @@ func sortMounts(m []execdriver.Mount) []execdriver.Mount {
 | 
			
		|||
	return m
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type mounts []execdriver.Mount
 | 
			
		||||
 | 
			
		||||
// Len returns the number of mounts
 | 
			
		||||
func (m mounts) Len() int {
 | 
			
		||||
	return len(m)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Less returns true if the number of parts (a/b/c would be 3 parts) in the
 | 
			
		||||
// mount indexed by parameter 1 is less than that of the mount indexed by
 | 
			
		||||
// parameter 2.
 | 
			
		||||
func (m mounts) Less(i, j int) bool {
 | 
			
		||||
	return m.parts(i) < m.parts(j)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Swap swaps two items in an array of mounts.
 | 
			
		||||
func (m mounts) Swap(i, j int) {
 | 
			
		||||
	m[i], m[j] = m[j], m[i]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parts returns the number of parts in the destination of a mount.
 | 
			
		||||
func (m mounts) parts(i int) int {
 | 
			
		||||
	return len(strings.Split(filepath.Clean(m[i].Destination), string(os.PathSeparator)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// migrateVolume links the contents of a volume created pre Docker 1.7
 | 
			
		||||
// into the location expected by the local driver.
 | 
			
		||||
// It creates a symlink from DOCKER_ROOT/vfs/dir/VOLUME_ID to DOCKER_ROOT/volumes/VOLUME_ID/_container_data.
 | 
			
		||||
| 
						 | 
				
			
			@ -211,12 +160,7 @@ func (daemon *Daemon) verifyVolumesInfo(container *Container) error {
 | 
			
		|||
				}
 | 
			
		||||
				container.addLocalMountPoint(id, destination, rw)
 | 
			
		||||
			} else { // Bind mount
 | 
			
		||||
				id, source, err := parseVolumeSource(hostPath)
 | 
			
		||||
				// We should not find an error here coming
 | 
			
		||||
				// from the old configuration, but who knows.
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
				id, source := volume.ParseVolumeSource(hostPath)
 | 
			
		||||
				container.addBindMountPoint(id, source, destination, rw)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -270,109 +214,19 @@ func (daemon *Daemon) verifyVolumesInfo(container *Container) error {
 | 
			
		|||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseVolumesFrom ensure that the supplied volumes-from is valid.
 | 
			
		||||
func parseVolumesFrom(spec string) (string, string, error) {
 | 
			
		||||
	if len(spec) == 0 {
 | 
			
		||||
		return "", "", derr.ErrorCodeVolumeFromBlank.WithArgs(spec)
 | 
			
		||||
// setBindModeIfNull is platform specific processing to ensure the
 | 
			
		||||
// shared mode is set to 'z' if it is null. This is called in the case
 | 
			
		||||
// of processing a named volume and not a typical bind.
 | 
			
		||||
func setBindModeIfNull(bind *volume.MountPoint) *volume.MountPoint {
 | 
			
		||||
	if bind.Mode == "" {
 | 
			
		||||
		bind.Mode = "z"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	specParts := strings.SplitN(spec, ":", 2)
 | 
			
		||||
	id := specParts[0]
 | 
			
		||||
	mode := "rw"
 | 
			
		||||
 | 
			
		||||
	if len(specParts) == 2 {
 | 
			
		||||
		mode = specParts[1]
 | 
			
		||||
		if !volume.ValidMountMode(mode) {
 | 
			
		||||
			return "", "", derr.ErrorCodeVolumeMode.WithArgs(mode)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return id, mode, nil
 | 
			
		||||
	return bind
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// registerMountPoints initializes the container mount points with the configured volumes and bind mounts.
 | 
			
		||||
// It follows the next sequence to decide what to mount in each final destination:
 | 
			
		||||
//
 | 
			
		||||
// 1. Select the previously configured mount points for the containers, if any.
 | 
			
		||||
// 2. Select the volumes mounted from another containers. Overrides previously configured mount point destination.
 | 
			
		||||
// 3. Select the bind mounts set by the client. Overrides previously configured mount point destinations.
 | 
			
		||||
func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runconfig.HostConfig) error {
 | 
			
		||||
	binds := map[string]bool{}
 | 
			
		||||
	mountPoints := map[string]*mountPoint{}
 | 
			
		||||
 | 
			
		||||
	// 1. Read already configured mount points.
 | 
			
		||||
	for name, point := range container.MountPoints {
 | 
			
		||||
		mountPoints[name] = point
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 2. Read volumes from other containers.
 | 
			
		||||
	for _, v := range hostConfig.VolumesFrom {
 | 
			
		||||
		containerID, mode, err := parseVolumesFrom(v)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		c, err := daemon.Get(containerID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, m := range c.MountPoints {
 | 
			
		||||
			cp := &mountPoint{
 | 
			
		||||
				Name:        m.Name,
 | 
			
		||||
				Source:      m.Source,
 | 
			
		||||
				RW:          m.RW && volume.ReadWrite(mode),
 | 
			
		||||
				Driver:      m.Driver,
 | 
			
		||||
				Destination: m.Destination,
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if len(cp.Source) == 0 {
 | 
			
		||||
				v, err := daemon.createVolume(cp.Name, cp.Driver, nil)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
				cp.Volume = v
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			mountPoints[cp.Destination] = cp
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 3. Read bind mounts
 | 
			
		||||
	for _, b := range hostConfig.Binds {
 | 
			
		||||
		// #10618
 | 
			
		||||
		bind, err := parseBindMount(b, hostConfig.VolumeDriver)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if binds[bind.Destination] {
 | 
			
		||||
			return derr.ErrorCodeVolumeDup.WithArgs(bind.Destination)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(bind.Name) > 0 && len(bind.Driver) > 0 {
 | 
			
		||||
			// create the volume
 | 
			
		||||
			v, err := daemon.createVolume(bind.Name, bind.Driver, nil)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			bind.Volume = v
 | 
			
		||||
			bind.Source = v.Path()
 | 
			
		||||
			// bind.Name is an already existing volume, we need to use that here
 | 
			
		||||
			bind.Driver = v.DriverName()
 | 
			
		||||
			// Since this is just a named volume and not a typical bind, set to shared mode `z`
 | 
			
		||||
			if bind.Mode == "" {
 | 
			
		||||
				bind.Mode = "z"
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		shared := label.IsShared(bind.Mode)
 | 
			
		||||
		if err := label.Relabel(bind.Source, container.MountLabel, shared); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		binds[bind.Destination] = true
 | 
			
		||||
		mountPoints[bind.Destination] = bind
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
// configureBackCompatStructures is platform specific processing for
 | 
			
		||||
// registering mount points to populate old structures.
 | 
			
		||||
func configureBackCompatStructures(daemon *Daemon, container *Container, mountPoints map[string]*volume.MountPoint) (map[string]string, map[string]bool) {
 | 
			
		||||
	// Keep backwards compatible structures
 | 
			
		||||
	bcVolumes := map[string]string{}
 | 
			
		||||
	bcVolumesRW := map[string]bool{}
 | 
			
		||||
| 
						 | 
				
			
			@ -387,38 +241,12 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc
 | 
			
		|||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return bcVolumes, bcVolumesRW
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	container.Lock()
 | 
			
		||||
	container.MountPoints = mountPoints
 | 
			
		||||
// setBackCompatStructures is a platform specific helper function to set
 | 
			
		||||
// backwards compatible structures in the container when registering volumes.
 | 
			
		||||
func setBackCompatStructures(container *Container, bcVolumes map[string]string, bcVolumesRW map[string]bool) {
 | 
			
		||||
	container.Volumes = bcVolumes
 | 
			
		||||
	container.VolumesRW = bcVolumesRW
 | 
			
		||||
	container.Unlock()
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// createVolume creates a volume.
 | 
			
		||||
func (daemon *Daemon) createVolume(name, driverName string, opts map[string]string) (volume.Volume, error) {
 | 
			
		||||
	v, err := daemon.volumes.Create(name, driverName, opts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	daemon.volumes.Increment(v)
 | 
			
		||||
	return v, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseVolumeSource parses the origin sources that's mounted into the container.
 | 
			
		||||
func parseVolumeSource(spec string) (string, string, error) {
 | 
			
		||||
	if !filepath.IsAbs(spec) {
 | 
			
		||||
		return spec, "", nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return "", spec, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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 == volume.DefaultDriverName
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,22 +4,35 @@ package daemon
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/docker/docker/daemon/execdriver"
 | 
			
		||||
	"github.com/docker/docker/runconfig"
 | 
			
		||||
	derr "github.com/docker/docker/errors"
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
	"sort"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// copyOwnership copies the permissions and group of a source file to the
 | 
			
		||||
// destination file. This is a no-op on Windows.
 | 
			
		||||
func copyOwnership(source, destination string) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// setupMounts configures the mount points for a container.
 | 
			
		||||
// setupMounts on Linux iterates through each of the mount points for a
 | 
			
		||||
// container and calls Setup() on each. It also looks to see if is a network
 | 
			
		||||
// mount such as /etc/resolv.conf, and if it is not, appends it to the array
 | 
			
		||||
// of mounts. As Windows does not support mount points, this is a no-op.
 | 
			
		||||
// setupMounts configures the mount points for a container by appending each
 | 
			
		||||
// of the configured mounts on the container to the execdriver mount structure
 | 
			
		||||
// which will ultimately be passed into the exec driver during container creation.
 | 
			
		||||
// It also ensures each of the mounts are lexographically sorted.
 | 
			
		||||
func (container *Container) setupMounts() ([]execdriver.Mount, error) {
 | 
			
		||||
	return nil, nil
 | 
			
		||||
	var mnts []execdriver.Mount
 | 
			
		||||
	for _, mount := range container.MountPoints { // type is volume.MountPoint
 | 
			
		||||
		// If there is no source, take it from the volume path
 | 
			
		||||
		s := mount.Source
 | 
			
		||||
		if s == "" && mount.Volume != nil {
 | 
			
		||||
			s = mount.Volume.Path()
 | 
			
		||||
		}
 | 
			
		||||
		if s == "" {
 | 
			
		||||
			return nil, derr.ErrorCodeVolumeNoSourceForMount.WithArgs(mount.Name, mount.Driver, mount.Destination)
 | 
			
		||||
		}
 | 
			
		||||
		mnts = append(mnts, execdriver.Mount{
 | 
			
		||||
			Source:      s,
 | 
			
		||||
			Destination: mount.Destination,
 | 
			
		||||
			Writable:    mount.RW,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sort.Sort(mounts(mnts))
 | 
			
		||||
	return mnts, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// verifyVolumesInfo ports volumes configured for the containers pre docker 1.7.
 | 
			
		||||
| 
						 | 
				
			
			@ -28,9 +41,20 @@ func (daemon *Daemon) verifyVolumesInfo(container *Container) error {
 | 
			
		|||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// registerMountPoints initializes the container mount points with the
 | 
			
		||||
// configured volumes and bind mounts. Windows does not support volumes or
 | 
			
		||||
// mount points.
 | 
			
		||||
func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runconfig.HostConfig) error {
 | 
			
		||||
	return nil
 | 
			
		||||
// setBindModeIfNull is platform specific processing which is a no-op on
 | 
			
		||||
// Windows.
 | 
			
		||||
func setBindModeIfNull(bind *volume.MountPoint) *volume.MountPoint {
 | 
			
		||||
	return bind
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// configureBackCompatStructures is platform specific processing for
 | 
			
		||||
// registering mount points to populate old structures. This is a no-op on Windows.
 | 
			
		||||
func configureBackCompatStructures(*Daemon, *Container, map[string]*volume.MountPoint) (map[string]string, map[string]bool) {
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// setBackCompatStructures is a platform specific helper function to set
 | 
			
		||||
// backwards compatible structures in the container when registering volumes.
 | 
			
		||||
// This is a no-op on Windows.
 | 
			
		||||
func setBackCompatStructures(*Container, map[string]string, map[string]bool) {
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -359,12 +359,12 @@ var (
 | 
			
		|||
		HTTPStatusCode: http.StatusInternalServerError,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// ErrorCodeVolumeInvalidMode is generated when we the mode of a volume
 | 
			
		||||
	// ErrorCodeVolumeInvalidMode is generated when we the mode of a volume/bind
 | 
			
		||||
	// mount is invalid.
 | 
			
		||||
	ErrorCodeVolumeInvalidMode = errcode.Register(errGroup, errcode.ErrorDescriptor{
 | 
			
		||||
		Value:          "VOLUMEINVALIDMODE",
 | 
			
		||||
		Message:        "invalid mode for volumes-from: %s",
 | 
			
		||||
		Description:    "An invalid 'mode' was specified in the mount request",
 | 
			
		||||
		Message:        "invalid mode: %s",
 | 
			
		||||
		Description:    "An invalid 'mode' was specified",
 | 
			
		||||
		HTTPStatusCode: http.StatusInternalServerError,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -393,6 +393,41 @@ var (
 | 
			
		|||
		HTTPStatusCode: http.StatusBadRequest,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// ErrorCodeVolumeSlash is generated when destination path to a volume is /
 | 
			
		||||
	ErrorCodeVolumeSlash = errcode.Register(errGroup, errcode.ErrorDescriptor{
 | 
			
		||||
		Value:          "VOLUMESLASH",
 | 
			
		||||
		Message:        "Invalid specification: destination can't be '/' in '%s'",
 | 
			
		||||
		HTTPStatusCode: http.StatusInternalServerError,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// ErrorCodeVolumeDestIsC is generated the destination is c: (Windows specific)
 | 
			
		||||
	ErrorCodeVolumeDestIsC = errcode.Register(errGroup, errcode.ErrorDescriptor{
 | 
			
		||||
		Value:          "VOLUMEDESTISC",
 | 
			
		||||
		Message:        "Destination drive letter in '%s' cannot be c:",
 | 
			
		||||
		HTTPStatusCode: http.StatusInternalServerError,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// ErrorCodeVolumeDestIsCRoot is generated the destination path is c:\ (Windows specific)
 | 
			
		||||
	ErrorCodeVolumeDestIsCRoot = errcode.Register(errGroup, errcode.ErrorDescriptor{
 | 
			
		||||
		Value:          "VOLUMEDESTISCROOT",
 | 
			
		||||
		Message:        `Destination path in '%s' cannot be c:\`,
 | 
			
		||||
		HTTPStatusCode: http.StatusInternalServerError,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// ErrorCodeVolumeSourceNotFound is generated the source directory could not be found (Windows specific)
 | 
			
		||||
	ErrorCodeVolumeSourceNotFound = errcode.Register(errGroup, errcode.ErrorDescriptor{
 | 
			
		||||
		Value:          "VOLUMESOURCENOTFOUND",
 | 
			
		||||
		Message:        "Source directory '%s' could not be found: %v",
 | 
			
		||||
		HTTPStatusCode: http.StatusInternalServerError,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// ErrorCodeVolumeSourceNotDirectory is generated the source is not a directory (Windows specific)
 | 
			
		||||
	ErrorCodeVolumeSourceNotDirectory = errcode.Register(errGroup, errcode.ErrorDescriptor{
 | 
			
		||||
		Value:          "VOLUMESOURCENOTDIRECTORY",
 | 
			
		||||
		Message:        "Source '%s' is not a directory",
 | 
			
		||||
		HTTPStatusCode: http.StatusInternalServerError,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// ErrorCodeVolumeFromBlank is generated when path to a volume is blank.
 | 
			
		||||
	ErrorCodeVolumeFromBlank = errcode.Register(errGroup, errcode.ErrorDescriptor{
 | 
			
		||||
		Value:          "VOLUMEFROMBLANK",
 | 
			
		||||
| 
						 | 
				
			
			@ -401,15 +436,6 @@ var (
 | 
			
		|||
		HTTPStatusCode: http.StatusInternalServerError,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// ErrorCodeVolumeMode is generated when 'mode' for a volume
 | 
			
		||||
	// isn't a valid.
 | 
			
		||||
	ErrorCodeVolumeMode = errcode.Register(errGroup, errcode.ErrorDescriptor{
 | 
			
		||||
		Value:          "VOLUMEMODE",
 | 
			
		||||
		Message:        "invalid mode for volumes-from: %s",
 | 
			
		||||
		Description:    "An invalid 'mode' path was specified in the mount request",
 | 
			
		||||
		HTTPStatusCode: http.StatusInternalServerError,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// ErrorCodeVolumeDup is generated when we try to mount two volumes
 | 
			
		||||
	// to the same path.
 | 
			
		||||
	ErrorCodeVolumeDup = errcode.Register(errGroup, errcode.ErrorDescriptor{
 | 
			
		||||
| 
						 | 
				
			
			@ -419,6 +445,22 @@ var (
 | 
			
		|||
		HTTPStatusCode: http.StatusInternalServerError,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// ErrorCodeVolumeNoSourceForMount is generated when no source directory
 | 
			
		||||
	// for a volume mount was found. (Windows specific)
 | 
			
		||||
	ErrorCodeVolumeNoSourceForMount = errcode.Register(errGroup, errcode.ErrorDescriptor{
 | 
			
		||||
		Value:          "VOLUMENOSOURCEFORMOUNT",
 | 
			
		||||
		Message:        "No source for mount name %q driver %q destination %s",
 | 
			
		||||
		HTTPStatusCode: http.StatusInternalServerError,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// ErrorCodeVolumeNameReservedWord is generated when the name in a volume
 | 
			
		||||
	// uses a reserved word for filenames. (Windows specific)
 | 
			
		||||
	ErrorCodeVolumeNameReservedWord = errcode.Register(errGroup, errcode.ErrorDescriptor{
 | 
			
		||||
		Value:          "VOLUMENAMERESERVEDWORD",
 | 
			
		||||
		Message:        "Volume name %q cannot be a reserved word for Windows filenames",
 | 
			
		||||
		HTTPStatusCode: http.StatusInternalServerError,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// ErrorCodeCantUnpause is generated when there's an error while trying
 | 
			
		||||
	// to unpause a container.
 | 
			
		||||
	ErrorCodeCantUnpause = errcode.Register(errGroup, errcode.ErrorDescriptor{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1 +1,2 @@
 | 
			
		|||
{"architecture":"amd64","config":{"Hostname":"03797203757d","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","GOLANG_VERSION=1.4.1","GOPATH=/go"],"Cmd":null,"Image":"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02","Volumes":null,"WorkingDir":"/go","Entrypoint":["/go/bin/dnsdock"],"OnBuild":[],"Labels":{}},"container":"d91be3479d5b1e84b0c00d18eea9dc777ca0ad166d51174b24283e2e6f104253","container_config":{"Hostname":"03797203757d","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","GOLANG_VERSION=1.4.1","GOPATH=/go"],"Cmd":["/bin/sh","-c","#(nop) ENTRYPOINT [\"/go/bin/dnsdock\"]"],"Image":"ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02","Volumes":null,"WorkingDir":"/go","Entrypoint":["/go/bin/dnsdock"],"OnBuild":[],"Labels":{}},"created":"2015-08-19T16:49:11.368300679Z","docker_version":"1.6.2","layer_id":"sha256:31176893850e05d308cdbfef88877e460d50c8063883fb13eb5753097da6422a","os":"linux","parent_id":"sha256:ec3025ca8cc9bcab039e193e20ec647c2da3c53a74020f2ba611601f9b2c6c02"}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -293,8 +293,8 @@ func (s *DockerSuite) TestRunVolumesFromInReadWriteMode(c *check.C) {
 | 
			
		|||
	dockerCmd(c, "run", "--name", "parent", "-v", "/test", "busybox", "true")
 | 
			
		||||
	dockerCmd(c, "run", "--volumes-from", "parent:rw", "busybox", "touch", "/test/file")
 | 
			
		||||
 | 
			
		||||
	if out, _, err := dockerCmdWithError("run", "--volumes-from", "parent:bar", "busybox", "touch", "/test/file"); err == nil || !strings.Contains(out, "invalid mode for volumes-from: bar") {
 | 
			
		||||
		c.Fatalf("running --volumes-from foo:bar should have failed with invalid mount mode: %q", out)
 | 
			
		||||
	if out, _, err := dockerCmdWithError("run", "--volumes-from", "parent:bar", "busybox", "touch", "/test/file"); err == nil || !strings.Contains(out, "invalid mode: bar") {
 | 
			
		||||
		c.Fatalf("running --volumes-from foo:bar should have failed with invalid mode: %q", out)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dockerCmd(c, "run", "--volumes-from", "parent", "busybox", "touch", "/test/file")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,7 +9,6 @@ import (
 | 
			
		|||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/pkg/parsers"
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
| 
						 | 
				
			
			@ -212,14 +211,6 @@ func ValidateDevice(val string) (string, error) {
 | 
			
		|||
	return validatePath(val, ValidDeviceMode)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidatePath validates a path for volumes
 | 
			
		||||
// It will make sure 'val' is in the form:
 | 
			
		||||
//    [host-dir:]container-path[:rw|ro]
 | 
			
		||||
// It also validates the mount mode.
 | 
			
		||||
func ValidatePath(val string) (string, error) {
 | 
			
		||||
	return validatePath(val, volume.ValidMountMode)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func validatePath(val string, validator func(string) bool) (string, error) {
 | 
			
		||||
	var containerPath string
 | 
			
		||||
	var mode string
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -274,58 +274,6 @@ func TestValidateLink(t *testing.T) {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestValidatePath(t *testing.T) {
 | 
			
		||||
	valid := []string{
 | 
			
		||||
		"/home",
 | 
			
		||||
		"/home:/home",
 | 
			
		||||
		"/home:/something/else",
 | 
			
		||||
		"/with space",
 | 
			
		||||
		"/home:/with space",
 | 
			
		||||
		"relative:/absolute-path",
 | 
			
		||||
		"hostPath:/containerPath:ro",
 | 
			
		||||
		"/hostPath:/containerPath:rw",
 | 
			
		||||
		"/rw:/ro",
 | 
			
		||||
		"/path:rw",
 | 
			
		||||
		"/path:ro",
 | 
			
		||||
		"/rw:rw",
 | 
			
		||||
	}
 | 
			
		||||
	invalid := map[string]string{
 | 
			
		||||
		"":                "bad format for path: ",
 | 
			
		||||
		"./":              "./ is not an absolute path",
 | 
			
		||||
		"../":             "../ is not an absolute path",
 | 
			
		||||
		"/:../":           "../ is not an absolute path",
 | 
			
		||||
		"/:path":          "path is not an absolute path",
 | 
			
		||||
		":":               "bad format for path: :",
 | 
			
		||||
		"/tmp:":           " is not an absolute path",
 | 
			
		||||
		":test":           "bad format for path: :test",
 | 
			
		||||
		":/test":          "bad format for path: :/test",
 | 
			
		||||
		"tmp:":            " is not an absolute path",
 | 
			
		||||
		":test:":          "bad format for path: :test:",
 | 
			
		||||
		"::":              "bad format for path: ::",
 | 
			
		||||
		":::":             "bad format for path: :::",
 | 
			
		||||
		"/tmp:::":         "bad format for path: /tmp:::",
 | 
			
		||||
		":/tmp::":         "bad format for path: :/tmp::",
 | 
			
		||||
		"path:ro":         "path is not an absolute path",
 | 
			
		||||
		"/path:/path:sw":  "bad mode specified: sw",
 | 
			
		||||
		"/path:/path:rwz": "bad mode specified: rwz",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, path := range valid {
 | 
			
		||||
		if _, err := ValidatePath(path); err != nil {
 | 
			
		||||
			t.Fatalf("ValidatePath(`%q`) should succeed: error %q", path, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for path, expectedError := range invalid {
 | 
			
		||||
		if _, err := ValidatePath(path); err == nil {
 | 
			
		||||
			t.Fatalf("ValidatePath(`%q`) should have failed validation", path)
 | 
			
		||||
		} else {
 | 
			
		||||
			if err.Error() != expectedError {
 | 
			
		||||
				t.Fatalf("ValidatePath(`%q`) error should contain %q, got %q", path, expectedError, err.Error())
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
func TestValidateDevice(t *testing.T) {
 | 
			
		||||
	valid := []string{
 | 
			
		||||
		"/home",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										11
									
								
								pkg/system/syscall_unix.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								pkg/system/syscall_unix.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
// +build linux freebsd
 | 
			
		||||
 | 
			
		||||
package system
 | 
			
		||||
 | 
			
		||||
import "syscall"
 | 
			
		||||
 | 
			
		||||
// UnmountWithSyscall is a platform-specific helper function to call
 | 
			
		||||
// the unmount syscall.
 | 
			
		||||
func UnmountWithSyscall(dest string) {
 | 
			
		||||
	syscall.Unmount(dest, 0)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								pkg/system/syscall_windows.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								pkg/system/syscall_windows.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
package system
 | 
			
		||||
 | 
			
		||||
// UnmountWithSyscall is a platform-specific helper function to call
 | 
			
		||||
// the unmount syscall. Not supported on Windows
 | 
			
		||||
func UnmountWithSyscall(dest string) {
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -2,10 +2,12 @@ package runconfig
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/pkg/nat"
 | 
			
		||||
	"github.com/docker/docker/pkg/stringutils"
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Config contains the configuration data about a container.
 | 
			
		||||
| 
						 | 
				
			
			@ -44,15 +46,29 @@ type Config struct {
 | 
			
		|||
// Be aware this function is not checking whether the resulted structs are nil,
 | 
			
		||||
// it's your business to do so
 | 
			
		||||
func DecodeContainerConfig(src io.Reader) (*Config, *HostConfig, error) {
 | 
			
		||||
	decoder := json.NewDecoder(src)
 | 
			
		||||
 | 
			
		||||
	var w ContainerConfigWrapper
 | 
			
		||||
 | 
			
		||||
	decoder := json.NewDecoder(src)
 | 
			
		||||
	if err := decoder.Decode(&w); err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hc := w.getHostConfig()
 | 
			
		||||
 | 
			
		||||
	// Perform platform-specific processing of Volumes and Binds.
 | 
			
		||||
	if w.Config != nil && hc != nil {
 | 
			
		||||
 | 
			
		||||
		// Initialise the volumes map if currently nil
 | 
			
		||||
		if w.Config.Volumes == nil {
 | 
			
		||||
			w.Config.Volumes = make(map[string]struct{})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Now validate all the volumes and binds
 | 
			
		||||
		if err := validateVolumesAndBindSettings(w.Config, hc); err != nil {
 | 
			
		||||
			return nil, nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Certain parameters need daemon-side validation that cannot be done
 | 
			
		||||
	// on the client, as only the daemon knows what is valid for the platform.
 | 
			
		||||
	if err := ValidateNetMode(w.Config, hc); err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -61,3 +77,22 @@ func DecodeContainerConfig(src io.Reader) (*Config, *HostConfig, error) {
 | 
			
		|||
 | 
			
		||||
	return w.Config, hc, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// validateVolumesAndBindSettings validates each of the volumes and bind settings
 | 
			
		||||
// passed by the caller to ensure they are valid.
 | 
			
		||||
func validateVolumesAndBindSettings(c *Config, hc *HostConfig) error {
 | 
			
		||||
 | 
			
		||||
	// Ensure all volumes and binds are valid.
 | 
			
		||||
	for spec := range c.Volumes {
 | 
			
		||||
		if _, err := volume.ParseMountSpec(spec, hc.VolumeDriver); err != nil {
 | 
			
		||||
			return fmt.Errorf("Invalid volume spec %q: %v", spec, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, spec := range hc.Binds {
 | 
			
		||||
		if _, err := volume.ParseMountSpec(spec, hc.VolumeDriver); err != nil {
 | 
			
		||||
			return fmt.Errorf("Invalid bind mount spec %q: %v", spec, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,19 +4,36 @@ import (
 | 
			
		|||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/pkg/stringutils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type f struct {
 | 
			
		||||
	file       string
 | 
			
		||||
	entrypoint *stringutils.StrSlice
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDecodeContainerConfig(t *testing.T) {
 | 
			
		||||
	fixtures := []struct {
 | 
			
		||||
		file       string
 | 
			
		||||
		entrypoint *stringutils.StrSlice
 | 
			
		||||
	}{
 | 
			
		||||
		{"fixtures/container_config_1_14.json", stringutils.NewStrSlice()},
 | 
			
		||||
		{"fixtures/container_config_1_17.json", stringutils.NewStrSlice("bash")},
 | 
			
		||||
		{"fixtures/container_config_1_19.json", stringutils.NewStrSlice("bash")},
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		fixtures []f
 | 
			
		||||
		image    string
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	if runtime.GOOS != "windows" {
 | 
			
		||||
		image = "ubuntu"
 | 
			
		||||
		fixtures = []f{
 | 
			
		||||
			{"fixtures/unix/container_config_1_14.json", stringutils.NewStrSlice()},
 | 
			
		||||
			{"fixtures/unix/container_config_1_17.json", stringutils.NewStrSlice("bash")},
 | 
			
		||||
			{"fixtures/unix/container_config_1_19.json", stringutils.NewStrSlice("bash")},
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		image = "windows"
 | 
			
		||||
		fixtures = []f{
 | 
			
		||||
			{"fixtures/windows/container_config_1_19.json", stringutils.NewStrSlice("cmd")},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, f := range fixtures {
 | 
			
		||||
| 
						 | 
				
			
			@ -30,15 +47,15 @@ func TestDecodeContainerConfig(t *testing.T) {
 | 
			
		|||
			t.Fatal(fmt.Errorf("Error parsing %s: %v", f, err))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Image != "ubuntu" {
 | 
			
		||||
			t.Fatalf("Expected ubuntu image, found %s\n", c.Image)
 | 
			
		||||
		if c.Image != image {
 | 
			
		||||
			t.Fatalf("Expected %s image, found %s\n", image, c.Image)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Entrypoint.Len() != f.entrypoint.Len() {
 | 
			
		||||
			t.Fatalf("Expected %v, found %v\n", f.entrypoint, c.Entrypoint)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if h.Memory != 1000 {
 | 
			
		||||
		if h != nil && h.Memory != 1000 {
 | 
			
		||||
			t.Fatalf("Expected memory to be 1000, found %d\n", h.Memory)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										58
									
								
								runconfig/fixtures/windows/container_config_1_19.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								runconfig/fixtures/windows/container_config_1_19.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,58 @@
 | 
			
		|||
{
 | 
			
		||||
     "Hostname": "",
 | 
			
		||||
     "Domainname": "",
 | 
			
		||||
     "User": "",
 | 
			
		||||
     "AttachStdin": false,
 | 
			
		||||
     "AttachStdout": true,
 | 
			
		||||
     "AttachStderr": true,
 | 
			
		||||
     "Tty": false,
 | 
			
		||||
     "OpenStdin": false,
 | 
			
		||||
     "StdinOnce": false,
 | 
			
		||||
     "Env": null,
 | 
			
		||||
     "Cmd": [
 | 
			
		||||
             "date"
 | 
			
		||||
     ],
 | 
			
		||||
     "Entrypoint": "cmd",
 | 
			
		||||
     "Image": "windows",
 | 
			
		||||
     "Labels": {
 | 
			
		||||
             "com.example.vendor": "Acme",
 | 
			
		||||
             "com.example.license": "GPL",
 | 
			
		||||
             "com.example.version": "1.0"
 | 
			
		||||
     },
 | 
			
		||||
     "Volumes": {
 | 
			
		||||
             "c:/windows": {}
 | 
			
		||||
     },
 | 
			
		||||
     "WorkingDir": "",
 | 
			
		||||
     "NetworkDisabled": false,
 | 
			
		||||
     "MacAddress": "12:34:56:78:9a:bc",
 | 
			
		||||
     "ExposedPorts": {
 | 
			
		||||
             "22/tcp": {}
 | 
			
		||||
     },
 | 
			
		||||
     "HostConfig": {
 | 
			
		||||
       "Binds": ["c:/windows:d:/tmp"],
 | 
			
		||||
       "Links": ["redis3:redis"],
 | 
			
		||||
       "LxcConf": {"lxc.utsname":"docker"},
 | 
			
		||||
       "Memory": 1000,
 | 
			
		||||
       "MemorySwap": 0,
 | 
			
		||||
       "CpuShares": 512,
 | 
			
		||||
       "CpusetCpus": "0,1",
 | 
			
		||||
       "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] },
 | 
			
		||||
       "PublishAllPorts": false,
 | 
			
		||||
       "Privileged": false,
 | 
			
		||||
       "ReadonlyRootfs": false,
 | 
			
		||||
       "Dns": ["8.8.8.8"],
 | 
			
		||||
       "DnsSearch": [""],
 | 
			
		||||
       "DnsOptions": [""],
 | 
			
		||||
       "ExtraHosts": null,
 | 
			
		||||
       "VolumesFrom": ["parent", "other:ro"],
 | 
			
		||||
       "CapAdd": ["NET_ADMIN"],
 | 
			
		||||
       "CapDrop": ["MKNOD"],
 | 
			
		||||
       "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
 | 
			
		||||
       "NetworkMode": "default",
 | 
			
		||||
       "Devices": [],
 | 
			
		||||
       "Ulimits": [{}],
 | 
			
		||||
       "LogConfig": { "Type": "json-file", "Config": {} },
 | 
			
		||||
       "SecurityOpt": [""],
 | 
			
		||||
       "CgroupParent": ""
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -234,8 +234,8 @@ func TestDecodeHostConfig(t *testing.T) {
 | 
			
		|||
	fixtures := []struct {
 | 
			
		||||
		file string
 | 
			
		||||
	}{
 | 
			
		||||
		{"fixtures/container_hostconfig_1_14.json"},
 | 
			
		||||
		{"fixtures/container_hostconfig_1_19.json"},
 | 
			
		||||
		{"fixtures/unix/container_hostconfig_1_14.json"},
 | 
			
		||||
		{"fixtures/unix/container_hostconfig_1_19.json"},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, f := range fixtures {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,6 +12,7 @@ import (
 | 
			
		|||
	"github.com/docker/docker/pkg/signal"
 | 
			
		||||
	"github.com/docker/docker/pkg/stringutils"
 | 
			
		||||
	"github.com/docker/docker/pkg/units"
 | 
			
		||||
	"github.com/docker/docker/volume"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
| 
						 | 
				
			
			@ -46,7 +47,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
 | 
			
		|||
	var (
 | 
			
		||||
		// FIXME: use utils.ListOpts for attach and volumes?
 | 
			
		||||
		flAttach  = opts.NewListOpts(opts.ValidateAttach)
 | 
			
		||||
		flVolumes = opts.NewListOpts(opts.ValidatePath)
 | 
			
		||||
		flVolumes = opts.NewListOpts(nil)
 | 
			
		||||
		flLinks   = opts.NewListOpts(opts.ValidateLink)
 | 
			
		||||
		flEnv     = opts.NewListOpts(opts.ValidateEnv)
 | 
			
		||||
		flLabels  = opts.NewListOpts(opts.ValidateEnv)
 | 
			
		||||
| 
						 | 
				
			
			@ -201,16 +202,11 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
 | 
			
		|||
	var binds []string
 | 
			
		||||
	// add any bind targets to the list of container volumes
 | 
			
		||||
	for bind := range flVolumes.GetMap() {
 | 
			
		||||
		if arr := strings.Split(bind, ":"); len(arr) > 1 {
 | 
			
		||||
			if arr[1] == "/" {
 | 
			
		||||
				return nil, nil, cmd, fmt.Errorf("Invalid bind mount: destination can't be '/'")
 | 
			
		||||
			}
 | 
			
		||||
		if arr := volume.SplitN(bind, 2); len(arr) > 1 {
 | 
			
		||||
			// after creating the bind mount we want to delete it from the flVolumes values because
 | 
			
		||||
			// we do not want bind mounts being committed to image configs
 | 
			
		||||
			binds = append(binds, bind)
 | 
			
		||||
			flVolumes.Delete(bind)
 | 
			
		||||
		} else if bind == "/" {
 | 
			
		||||
			return nil, nil, cmd, fmt.Errorf("Invalid volume: path can't be '/'")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,12 @@
 | 
			
		|||
package runconfig
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -31,17 +35,6 @@ func mustParse(t *testing.T, args string) (*Config, *HostConfig) {
 | 
			
		|||
	return config, hostConfig
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// check if (a == c && b == d) || (a == d && b == c)
 | 
			
		||||
// because maps are randomized
 | 
			
		||||
func compareRandomizedStrings(a, b, c, d string) error {
 | 
			
		||||
	if a == c && b == d {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if a == d && b == c {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Errorf("strings don't match")
 | 
			
		||||
}
 | 
			
		||||
func TestParseRunLinks(t *testing.T) {
 | 
			
		||||
	if _, hostConfig := mustParse(t, "--link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" {
 | 
			
		||||
		t.Fatalf("Error parsing links. Expected []string{\"a:b\"}, received: %v", hostConfig.Links)
 | 
			
		||||
| 
						 | 
				
			
			@ -98,81 +91,257 @@ func TestParseRunAttach(t *testing.T) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func TestParseRunVolumes(t *testing.T) {
 | 
			
		||||
	if config, hostConfig := mustParse(t, "-v /tmp"); hostConfig.Binds != nil {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, `-v /tmp` should not mount-bind anything. Received %v", hostConfig.Binds)
 | 
			
		||||
	} else if _, exists := config.Volumes["/tmp"]; !exists {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Received %v", config.Volumes)
 | 
			
		||||
 | 
			
		||||
	// A single volume
 | 
			
		||||
	arr, tryit := setupPlatformVolume([]string{`/tmp`}, []string{`c:\tmp`})
 | 
			
		||||
	if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds)
 | 
			
		||||
	} else if _, exists := config.Volumes[arr[0]]; !exists {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, %q is missing from volumes. Received %v", tryit, config.Volumes)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if config, hostConfig := mustParse(t, "-v /tmp -v /var"); hostConfig.Binds != nil {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, `-v /tmp -v /var` should not mount-bind anything. Received %v", hostConfig.Binds)
 | 
			
		||||
	} else if _, exists := config.Volumes["/tmp"]; !exists {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Received %v", config.Volumes)
 | 
			
		||||
	} else if _, exists := config.Volumes["/var"]; !exists {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, `-v /var` is missing from volumes. Received %v", config.Volumes)
 | 
			
		||||
	// Two volumes
 | 
			
		||||
	arr, tryit = setupPlatformVolume([]string{`/tmp`, `/var`}, []string{`c:\tmp`, `c:\var`})
 | 
			
		||||
	if config, hostConfig := mustParse(t, tryit); hostConfig.Binds != nil {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, %q should not mount-bind anything. Received %v", tryit, hostConfig.Binds)
 | 
			
		||||
	} else if _, exists := config.Volumes[arr[0]]; !exists {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[0], config.Volumes)
 | 
			
		||||
	} else if _, exists := config.Volumes[arr[1]]; !exists {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[1], config.Volumes)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp" {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp` should mount-bind /hostTmp into /containerTmp. Received %v", hostConfig.Binds)
 | 
			
		||||
	// A single bind-mount
 | 
			
		||||
	arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`})
 | 
			
		||||
	if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || hostConfig.Binds[0] != arr[0] {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, %q should mount-bind the path before the colon into the path after the colon. Received %v %v", arr[0], hostConfig.Binds, config.Volumes)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /hostVar:/containerVar"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/hostTmp:/containerTmp", "/hostVar:/containerVar") != nil {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /hostVar:/containerVar` should mount-bind /hostTmp into /containerTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
 | 
			
		||||
	// Two bind-mounts.
 | 
			
		||||
	arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/hostVar:/containerVar`}, []string{os.Getenv("ProgramData") + `:c:\ContainerPD`, os.Getenv("TEMP") + `:c:\containerTmp`})
 | 
			
		||||
	if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/hostTmp:/containerTmp:ro", "/hostVar:/containerVar:rw") != nil {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw` should mount-bind /hostTmp into /containerTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
 | 
			
		||||
	// Two bind-mounts, first read-only, second read-write.
 | 
			
		||||
	// TODO Windows: The Windows version uses read-write as that's the only mode it supports. Can change this post TP4
 | 
			
		||||
	arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro`, `/hostVar:/containerVar:rw`}, []string{os.Getenv("TEMP") + `:c:\containerTmp:rw`, os.Getenv("ProgramData") + `:c:\ContainerPD:rw`})
 | 
			
		||||
	if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, hostConfig := mustParse(t, "-v /containerTmp:ro -v /containerVar:rw"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/containerTmp:ro", "/containerVar:rw") != nil {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, `-v /containerTmp:ro -v /containerVar:rw` should mount-bind /containerTmp into /ro and /containerVar into /rw. Received %v", hostConfig.Binds)
 | 
			
		||||
	// Similar to previous test but with alternate modes which are only supported by Linux
 | 
			
		||||
	if runtime.GOOS != "windows" {
 | 
			
		||||
		arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:ro,Z`, `/hostVar:/containerVar:rw,Z`}, []string{})
 | 
			
		||||
		if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
 | 
			
		||||
			t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp:Z`, `/hostVar:/containerVar:z`}, []string{})
 | 
			
		||||
		if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil {
 | 
			
		||||
			t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:ro,Z -v /hostVar:/containerVar:rw,Z"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/hostTmp:/containerTmp:ro,Z", "/hostVar:/containerVar:rw,Z") != nil {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:ro,Z -v /hostVar:/containerVar:rw,Z` should mount-bind /hostTmp into /containerTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
 | 
			
		||||
	// One bind mount and one volume
 | 
			
		||||
	arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/containerVar`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`, `c:\containerTmp`})
 | 
			
		||||
	if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, %s and %s should only one and only one bind mount %s. Received %s", arr[0], arr[1], arr[0], hostConfig.Binds)
 | 
			
		||||
	} else if _, exists := config.Volumes[arr[1]]; !exists {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags %s and %s. %s is missing from volumes. Received %v", arr[0], arr[1], arr[1], config.Volumes)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:Z -v /hostVar:/containerVar:z"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/hostTmp:/containerTmp:Z", "/hostVar:/containerVar:z") != nil {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:Z -v /hostVar:/containerVar:z` should mount-bind /hostTmp into /containerTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
 | 
			
		||||
	// Root to non-c: drive letter (Windows specific)
 | 
			
		||||
	if runtime.GOOS == "windows" {
 | 
			
		||||
		arr, tryit = setupPlatformVolume([]string{}, []string{os.Getenv("SystemDrive") + `\:d:`})
 | 
			
		||||
		if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != arr[0] || len(config.Volumes) != 0 {
 | 
			
		||||
			t.Fatalf("Error parsing %s. Should have a single bind mount and no volumes", arr[0])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /containerVar"); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != "/hostTmp:/containerTmp" {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /containerVar` should mount-bind only /hostTmp into /containerTmp. Received %v", hostConfig.Binds)
 | 
			
		||||
	} else if _, exists := config.Volumes["/containerVar"]; !exists {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This tests the cases for binds which are generated through
 | 
			
		||||
// DecodeContainerConfig rather than Parse()
 | 
			
		||||
func TestDecodeContainerConfigVolumes(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	// Root to root
 | 
			
		||||
	bindsOrVols, _ := setupPlatformVolume([]string{`/:/`}, []string{os.Getenv("SystemDrive") + `\:c:\`})
 | 
			
		||||
	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
 | 
			
		||||
		t.Fatalf("binds %v should have failed", bindsOrVols)
 | 
			
		||||
	}
 | 
			
		||||
	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
 | 
			
		||||
		t.Fatalf("volume %v should have failed", bindsOrVols)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if config, hostConfig := mustParse(t, ""); hostConfig.Binds != nil {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, without volume, nothing should be mount-binded. Received %v", hostConfig.Binds)
 | 
			
		||||
	} else if len(config.Volumes) != 0 {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, without volume, no volume should be present. Received %v", config.Volumes)
 | 
			
		||||
	// No destination path
 | 
			
		||||
	bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:`}, []string{os.Getenv("TEMP") + `\:`})
 | 
			
		||||
	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
 | 
			
		||||
		t.Fatalf("binds %v should have failed", bindsOrVols)
 | 
			
		||||
	}
 | 
			
		||||
	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
 | 
			
		||||
		t.Fatalf("binds %v should have failed", bindsOrVols)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, _, err := parse(t, "-v /"); err == nil {
 | 
			
		||||
		t.Fatalf("Expected error, but got none")
 | 
			
		||||
	//	// No destination path or mode
 | 
			
		||||
	bindsOrVols, _ = setupPlatformVolume([]string{`/tmp::`}, []string{os.Getenv("TEMP") + `\::`})
 | 
			
		||||
	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
 | 
			
		||||
		t.Fatalf("binds %v should have failed", bindsOrVols)
 | 
			
		||||
	}
 | 
			
		||||
	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
 | 
			
		||||
		t.Fatalf("binds %v should have failed", bindsOrVols)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, _, err := parse(t, "-v /:/"); err == nil {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, `-v /:/` should fail but didn't")
 | 
			
		||||
	// A whole lot of nothing
 | 
			
		||||
	bindsOrVols = []string{`:`}
 | 
			
		||||
	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
 | 
			
		||||
		t.Fatalf("binds %v should have failed", bindsOrVols)
 | 
			
		||||
	}
 | 
			
		||||
	if _, _, err := parse(t, "-v"); err == nil {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, `-v` should fail but didn't")
 | 
			
		||||
	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
 | 
			
		||||
		t.Fatalf("binds %v should have failed", bindsOrVols)
 | 
			
		||||
	}
 | 
			
		||||
	if _, _, err := parse(t, "-v /tmp:"); err == nil {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, `-v /tmp:` should fail but didn't")
 | 
			
		||||
 | 
			
		||||
	// A whole lot of nothing with no mode
 | 
			
		||||
	bindsOrVols = []string{`::`}
 | 
			
		||||
	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
 | 
			
		||||
		t.Fatalf("binds %v should have failed", bindsOrVols)
 | 
			
		||||
	}
 | 
			
		||||
	if _, _, err := parse(t, "-v /tmp::"); err == nil {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, `-v /tmp::` should fail but didn't")
 | 
			
		||||
	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
 | 
			
		||||
		t.Fatalf("binds %v should have failed", bindsOrVols)
 | 
			
		||||
	}
 | 
			
		||||
	if _, _, err := parse(t, "-v :"); err == nil {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, `-v :` should fail but didn't")
 | 
			
		||||
 | 
			
		||||
	// Too much including an invalid mode
 | 
			
		||||
	wTmp := os.Getenv("TEMP")
 | 
			
		||||
	bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:/tmp:/tmp:/tmp`}, []string{wTmp + ":" + wTmp + ":" + wTmp + ":" + wTmp})
 | 
			
		||||
	if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
 | 
			
		||||
		t.Fatalf("binds %v should have failed", bindsOrVols)
 | 
			
		||||
	}
 | 
			
		||||
	if _, _, err := parse(t, "-v ::"); err == nil {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, `-v ::` should fail but didn't")
 | 
			
		||||
	if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
 | 
			
		||||
		t.Fatalf("binds %v should have failed", bindsOrVols)
 | 
			
		||||
	}
 | 
			
		||||
	if _, _, err := parse(t, "-v /tmp:/tmp:/tmp:/tmp"); err == nil {
 | 
			
		||||
		t.Fatalf("Error parsing volume flags, `-v /tmp:/tmp:/tmp:/tmp` should fail but didn't")
 | 
			
		||||
 | 
			
		||||
	// Windows specific error tests
 | 
			
		||||
	if runtime.GOOS == "windows" {
 | 
			
		||||
		// Volume which does not include a drive letter
 | 
			
		||||
		bindsOrVols = []string{`\tmp`}
 | 
			
		||||
		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
 | 
			
		||||
			t.Fatalf("binds %v should have failed", bindsOrVols)
 | 
			
		||||
		}
 | 
			
		||||
		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
 | 
			
		||||
			t.Fatalf("binds %v should have failed", bindsOrVols)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Root to C-Drive
 | 
			
		||||
		bindsOrVols = []string{os.Getenv("SystemDrive") + `\:c:`}
 | 
			
		||||
		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
 | 
			
		||||
			t.Fatalf("binds %v should have failed", bindsOrVols)
 | 
			
		||||
		}
 | 
			
		||||
		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
 | 
			
		||||
			t.Fatalf("binds %v should have failed", bindsOrVols)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Container path that does not include a drive letter
 | 
			
		||||
		bindsOrVols = []string{`c:\windows:\somewhere`}
 | 
			
		||||
		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
 | 
			
		||||
			t.Fatalf("binds %v should have failed", bindsOrVols)
 | 
			
		||||
		}
 | 
			
		||||
		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
 | 
			
		||||
			t.Fatalf("binds %v should have failed", bindsOrVols)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Linux-specific error tests
 | 
			
		||||
	if runtime.GOOS != "windows" {
 | 
			
		||||
		// Just root
 | 
			
		||||
		bindsOrVols = []string{`/`}
 | 
			
		||||
		if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil {
 | 
			
		||||
			t.Fatalf("binds %v should have failed", bindsOrVols)
 | 
			
		||||
		}
 | 
			
		||||
		if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil {
 | 
			
		||||
			t.Fatalf("binds %v should have failed", bindsOrVols)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// A single volume that looks like a bind mount passed in Volumes.
 | 
			
		||||
		// This should be handled as a bind mount, not a volume.
 | 
			
		||||
		vols := []string{`/foo:/bar`}
 | 
			
		||||
		if config, hostConfig, err := callDecodeContainerConfig(vols, nil); err != nil {
 | 
			
		||||
			t.Fatal("Volume /foo:/bar should have succeeded as a volume name")
 | 
			
		||||
		} else if hostConfig.Binds != nil {
 | 
			
		||||
			t.Fatalf("Error parsing volume flags, /foo:/bar should not mount-bind anything. Received %v", hostConfig.Binds)
 | 
			
		||||
		} else if _, exists := config.Volumes[vols[0]]; !exists {
 | 
			
		||||
			t.Fatalf("Error parsing volume flags, /foo:/bar is missing from volumes. Received %v", config.Volumes)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// callDecodeContainerConfig is a utility function used by TestDecodeContainerConfigVolumes
 | 
			
		||||
// to call DecodeContainerConfig. It effectively does what a client would
 | 
			
		||||
// do when calling the daemon by constructing a JSON stream of a
 | 
			
		||||
// ContainerConfigWrapper which is populated by the set of volume specs
 | 
			
		||||
// passed into it. It returns a config and a hostconfig which can be
 | 
			
		||||
// validated to ensure DecodeContainerConfig has manipulated the structures
 | 
			
		||||
// correctly.
 | 
			
		||||
func callDecodeContainerConfig(volumes []string, binds []string) (*Config, *HostConfig, error) {
 | 
			
		||||
	var (
 | 
			
		||||
		b   []byte
 | 
			
		||||
		err error
 | 
			
		||||
		c   *Config
 | 
			
		||||
		h   *HostConfig
 | 
			
		||||
	)
 | 
			
		||||
	w := ContainerConfigWrapper{
 | 
			
		||||
		Config: &Config{
 | 
			
		||||
			Volumes: map[string]struct{}{},
 | 
			
		||||
		},
 | 
			
		||||
		HostConfig: &HostConfig{
 | 
			
		||||
			NetworkMode: "none",
 | 
			
		||||
			Binds:       binds,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, v := range volumes {
 | 
			
		||||
		w.Config.Volumes[v] = struct{}{}
 | 
			
		||||
	}
 | 
			
		||||
	if b, err = json.Marshal(w); err != nil {
 | 
			
		||||
		return nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
 | 
			
		||||
	}
 | 
			
		||||
	c, h, err = DecodeContainerConfig(bytes.NewReader(b))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, fmt.Errorf("Error parsing %s: %v", string(b), err)
 | 
			
		||||
	}
 | 
			
		||||
	if c == nil || h == nil {
 | 
			
		||||
		return nil, nil, fmt.Errorf("Empty config or hostconfig")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return c, h, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// check if (a == c && b == d) || (a == d && b == c)
 | 
			
		||||
// because maps are randomized
 | 
			
		||||
func compareRandomizedStrings(a, b, c, d string) error {
 | 
			
		||||
	if a == c && b == d {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if a == d && b == c {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Errorf("strings don't match")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// setupPlatformVolume takes two arrays of volume specs - a Unix style
 | 
			
		||||
// spec and a Windows style spec. Depending on the platform being unit tested,
 | 
			
		||||
// it returns one of them, along with a volume string that would be passed
 | 
			
		||||
// on the docker CLI (eg -v /bar -v /foo).
 | 
			
		||||
func setupPlatformVolume(u []string, w []string) ([]string, string) {
 | 
			
		||||
	var a []string
 | 
			
		||||
	if runtime.GOOS == "windows" {
 | 
			
		||||
		a = w
 | 
			
		||||
	} else {
 | 
			
		||||
		a = u
 | 
			
		||||
	}
 | 
			
		||||
	s := ""
 | 
			
		||||
	for _, v := range a {
 | 
			
		||||
		s = s + "-v " + v + " "
 | 
			
		||||
	}
 | 
			
		||||
	return a, s
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseLxcConfOpt(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -438,9 +607,13 @@ func TestParseLoggingOpts(t *testing.T) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func TestParseEnvfileVariables(t *testing.T) {
 | 
			
		||||
	e := "open nonexistent: no such file or directory"
 | 
			
		||||
	if runtime.GOOS == "windows" {
 | 
			
		||||
		e = "open nonexistent: The system cannot find the file specified."
 | 
			
		||||
	}
 | 
			
		||||
	// env ko
 | 
			
		||||
	if _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != "open nonexistent: no such file or directory" {
 | 
			
		||||
		t.Fatalf("Expected an error with message 'open nonexistent: no such file or directory', got %v", err)
 | 
			
		||||
	if _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
 | 
			
		||||
		t.Fatalf("Expected an error with message '%s', got %v", e, err)
 | 
			
		||||
	}
 | 
			
		||||
	// env ok
 | 
			
		||||
	config, _, _, err := parseRun([]string{"--env-file=fixtures/valid.env", "img", "cmd"})
 | 
			
		||||
| 
						 | 
				
			
			@ -460,9 +633,13 @@ func TestParseEnvfileVariables(t *testing.T) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func TestParseLabelfileVariables(t *testing.T) {
 | 
			
		||||
	e := "open nonexistent: no such file or directory"
 | 
			
		||||
	if runtime.GOOS == "windows" {
 | 
			
		||||
		e = "open nonexistent: The system cannot find the file specified."
 | 
			
		||||
	}
 | 
			
		||||
	// label ko
 | 
			
		||||
	if _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != "open nonexistent: no such file or directory" {
 | 
			
		||||
		t.Fatalf("Expected an error with message 'open nonexistent: no such file or directory', got %v", err)
 | 
			
		||||
	if _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != e {
 | 
			
		||||
		t.Fatalf("Expected an error with message '%s', got %v", e, err)
 | 
			
		||||
	}
 | 
			
		||||
	// label ok
 | 
			
		||||
	config, _, _, err := parseRun([]string{"--label-file=fixtures/valid.label", "img", "cmd"})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,7 +11,6 @@ func TestGetDriver(t *testing.T) {
 | 
			
		|||
	if err == nil {
 | 
			
		||||
		t.Fatal("Expected error, was nil")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Register(volumetestutils.FakeDriver{}, "fake")
 | 
			
		||||
	d, err := GetDriver("fake")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,6 +14,8 @@ var (
 | 
			
		|||
	ErrVolumeInUse = errors.New("volume is in use")
 | 
			
		||||
	// ErrNoSuchVolume is a typed error returned if the requested volume doesn't exist in the volume store
 | 
			
		||||
	ErrNoSuchVolume = errors.New("no such volume")
 | 
			
		||||
	// ErrInvalidName is a typed error returned when creating a volume with a name that is not valid on the platform
 | 
			
		||||
	ErrInvalidName = errors.New("volume name is not valid on this platform")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// New initializes a VolumeStore to keep
 | 
			
		||||
| 
						 | 
				
			
			@ -39,13 +41,14 @@ type volumeCounter struct {
 | 
			
		|||
// AddAll adds a list of volumes to the store
 | 
			
		||||
func (s *VolumeStore) AddAll(vols []volume.Volume) {
 | 
			
		||||
	for _, v := range vols {
 | 
			
		||||
		s.vols[v.Name()] = &volumeCounter{v, 0}
 | 
			
		||||
		s.vols[normaliseVolumeName(v.Name())] = &volumeCounter{v, 0}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Create tries to find an existing volume with the given name or create a new one from the passed in driver
 | 
			
		||||
func (s *VolumeStore) Create(name, driverName string, opts map[string]string) (volume.Volume, error) {
 | 
			
		||||
	s.mu.Lock()
 | 
			
		||||
	name = normaliseVolumeName(name)
 | 
			
		||||
	if vc, exists := s.vols[name]; exists {
 | 
			
		||||
		v := vc.Volume
 | 
			
		||||
		s.mu.Unlock()
 | 
			
		||||
| 
						 | 
				
			
			@ -59,13 +62,22 @@ func (s *VolumeStore) Create(name, driverName string, opts map[string]string) (v
 | 
			
		|||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Validate the name in a platform-specific manner
 | 
			
		||||
	valid, err := volume.IsVolumeNameValid(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if !valid {
 | 
			
		||||
		return nil, ErrInvalidName
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	v, err := vd.Create(name, opts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	s.mu.Lock()
 | 
			
		||||
	s.vols[v.Name()] = &volumeCounter{v, 0}
 | 
			
		||||
	s.vols[normaliseVolumeName(v.Name())] = &volumeCounter{v, 0}
 | 
			
		||||
	s.mu.Unlock()
 | 
			
		||||
 | 
			
		||||
	return v, nil
 | 
			
		||||
| 
						 | 
				
			
			@ -73,6 +85,7 @@ func (s *VolumeStore) Create(name, driverName string, opts map[string]string) (v
 | 
			
		|||
 | 
			
		||||
// Get looks if a volume with the given name exists and returns it if so
 | 
			
		||||
func (s *VolumeStore) Get(name string) (volume.Volume, error) {
 | 
			
		||||
	name = normaliseVolumeName(name)
 | 
			
		||||
	s.mu.Lock()
 | 
			
		||||
	defer s.mu.Unlock()
 | 
			
		||||
	vc, exists := s.vols[name]
 | 
			
		||||
| 
						 | 
				
			
			@ -86,7 +99,7 @@ func (s *VolumeStore) Get(name string) (volume.Volume, error) {
 | 
			
		|||
func (s *VolumeStore) Remove(v volume.Volume) error {
 | 
			
		||||
	s.mu.Lock()
 | 
			
		||||
	defer s.mu.Unlock()
 | 
			
		||||
	name := v.Name()
 | 
			
		||||
	name := normaliseVolumeName(v.Name())
 | 
			
		||||
	logrus.Debugf("Removing volume reference: driver %s, name %s", v.DriverName(), name)
 | 
			
		||||
	vc, exists := s.vols[name]
 | 
			
		||||
	if !exists {
 | 
			
		||||
| 
						 | 
				
			
			@ -112,11 +125,12 @@ func (s *VolumeStore) Remove(v volume.Volume) error {
 | 
			
		|||
func (s *VolumeStore) Increment(v volume.Volume) {
 | 
			
		||||
	s.mu.Lock()
 | 
			
		||||
	defer s.mu.Unlock()
 | 
			
		||||
	logrus.Debugf("Incrementing volume reference: driver %s, name %s", v.DriverName(), v.Name())
 | 
			
		||||
	name := normaliseVolumeName(v.Name())
 | 
			
		||||
	logrus.Debugf("Incrementing volume reference: driver %s, name %s", v.DriverName(), name)
 | 
			
		||||
 | 
			
		||||
	vc, exists := s.vols[v.Name()]
 | 
			
		||||
	vc, exists := s.vols[name]
 | 
			
		||||
	if !exists {
 | 
			
		||||
		s.vols[v.Name()] = &volumeCounter{v, 1}
 | 
			
		||||
		s.vols[name] = &volumeCounter{v, 1}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	vc.count++
 | 
			
		||||
| 
						 | 
				
			
			@ -126,9 +140,10 @@ func (s *VolumeStore) Increment(v volume.Volume) {
 | 
			
		|||
func (s *VolumeStore) Decrement(v volume.Volume) {
 | 
			
		||||
	s.mu.Lock()
 | 
			
		||||
	defer s.mu.Unlock()
 | 
			
		||||
	logrus.Debugf("Decrementing volume reference: driver %s, name %s", v.DriverName(), v.Name())
 | 
			
		||||
	name := normaliseVolumeName(v.Name())
 | 
			
		||||
	logrus.Debugf("Decrementing volume reference: driver %s, name %s", v.DriverName(), name)
 | 
			
		||||
 | 
			
		||||
	vc, exists := s.vols[v.Name()]
 | 
			
		||||
	vc, exists := s.vols[name]
 | 
			
		||||
	if !exists {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -142,7 +157,7 @@ func (s *VolumeStore) Decrement(v volume.Volume) {
 | 
			
		|||
func (s *VolumeStore) Count(v volume.Volume) uint {
 | 
			
		||||
	s.mu.Lock()
 | 
			
		||||
	defer s.mu.Unlock()
 | 
			
		||||
	vc, exists := s.vols[v.Name()]
 | 
			
		||||
	vc, exists := s.vols[normaliseVolumeName(v.Name())]
 | 
			
		||||
	if !exists {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										9
									
								
								volume/store/store_unix.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								volume/store/store_unix.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
// +build linux freebsd
 | 
			
		||||
 | 
			
		||||
package store
 | 
			
		||||
 | 
			
		||||
// normaliseVolumeName is a platform specific function to normalise the name
 | 
			
		||||
// of a volume. This is a no-op on Unix-like platforms
 | 
			
		||||
func normaliseVolumeName(name string) string {
 | 
			
		||||
	return name
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								volume/store/store_windows.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								volume/store/store_windows.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
package store
 | 
			
		||||
 | 
			
		||||
import "strings"
 | 
			
		||||
 | 
			
		||||
// normaliseVolumeName is a platform specific function to normalise the name
 | 
			
		||||
// of a volume. On Windows, as NTFS is case insensitive, under
 | 
			
		||||
// c:\ProgramData\Docker\Volumes\, the folders John and john would be synonymous.
 | 
			
		||||
// Hence we can't allow the volume "John" and "john" to be created as seperate
 | 
			
		||||
// volumes.
 | 
			
		||||
func normaliseVolumeName(name string) string {
 | 
			
		||||
	return strings.ToLower(name)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										147
									
								
								volume/volume.go
									
										
									
									
									
								
							
							
						
						
									
										147
									
								
								volume/volume.go
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,5 +1,15 @@
 | 
			
		|||
package volume
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	derr "github.com/docker/docker/errors"
 | 
			
		||||
	"github.com/docker/docker/pkg/system"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// DefaultDriverName is the driver name used for the driver
 | 
			
		||||
// implemented in the local package.
 | 
			
		||||
const DefaultDriverName string = "local"
 | 
			
		||||
| 
						 | 
				
			
			@ -29,33 +39,134 @@ type Volume interface {
 | 
			
		|||
	Unmount() error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// read-write modes
 | 
			
		||||
var rwModes = map[string]bool{
 | 
			
		||||
	"rw":   true,
 | 
			
		||||
	"rw,Z": true,
 | 
			
		||||
	"rw,z": true,
 | 
			
		||||
	"z,rw": true,
 | 
			
		||||
	"Z,rw": true,
 | 
			
		||||
	"Z":    true,
 | 
			
		||||
	"z":    true,
 | 
			
		||||
// MountPoint is the intersection point between a volume and a container. It
 | 
			
		||||
// specifies which volume is to be used and where inside a container it should
 | 
			
		||||
// be mounted.
 | 
			
		||||
type MountPoint struct {
 | 
			
		||||
	Source      string // Container host directory
 | 
			
		||||
	Destination string // Inside the container
 | 
			
		||||
	RW          bool   // True if writable
 | 
			
		||||
	Name        string // Name set by user
 | 
			
		||||
	Driver      string // Volume driver to use
 | 
			
		||||
	Volume      Volume `json:"-"`
 | 
			
		||||
 | 
			
		||||
	// Note Mode is not used on Windows
 | 
			
		||||
	Mode string `json:"Relabel"` // Originally field was `Relabel`"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// read-only modes
 | 
			
		||||
var roModes = map[string]bool{
 | 
			
		||||
	"ro":   true,
 | 
			
		||||
	"ro,Z": true,
 | 
			
		||||
	"ro,z": true,
 | 
			
		||||
	"z,ro": true,
 | 
			
		||||
	"Z,ro": true,
 | 
			
		||||
// Setup sets up a mount point by either mounting the volume if it is
 | 
			
		||||
// configured, or creating the source directory if supplied.
 | 
			
		||||
func (m *MountPoint) Setup() (string, error) {
 | 
			
		||||
	if m.Volume != nil {
 | 
			
		||||
		return m.Volume.Mount()
 | 
			
		||||
	}
 | 
			
		||||
	if len(m.Source) > 0 {
 | 
			
		||||
		if _, err := os.Stat(m.Source); err != nil {
 | 
			
		||||
			if !os.IsNotExist(err) {
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
			if runtime.GOOS != "windows" { // Windows does not have deprecation issues here
 | 
			
		||||
				logrus.Warnf("Auto-creating non-existant volume host path %s, this is deprecated and will be removed soon", m.Source)
 | 
			
		||||
				if err := system.MkdirAll(m.Source, 0755); err != nil {
 | 
			
		||||
					return "", err
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return m.Source, nil
 | 
			
		||||
	}
 | 
			
		||||
	return "", derr.ErrorCodeMountSetup
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Path returns the path of a volume in a mount point.
 | 
			
		||||
func (m *MountPoint) Path() string {
 | 
			
		||||
	if m.Volume != nil {
 | 
			
		||||
		return m.Volume.Path()
 | 
			
		||||
	}
 | 
			
		||||
	return m.Source
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidMountMode will make sure the mount mode is valid.
 | 
			
		||||
// returns if it's a valid mount mode or not.
 | 
			
		||||
func ValidMountMode(mode string) bool {
 | 
			
		||||
	return roModes[mode] || rwModes[mode]
 | 
			
		||||
	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[mode]
 | 
			
		||||
	return rwModes[strings.ToLower(mode)]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParseVolumesFrom ensure that the supplied volumes-from is valid.
 | 
			
		||||
func ParseVolumesFrom(spec string) (string, string, error) {
 | 
			
		||||
	if len(spec) == 0 {
 | 
			
		||||
		return "", "", derr.ErrorCodeVolumeFromBlank.WithArgs(spec)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	specParts := strings.SplitN(spec, ":", 2)
 | 
			
		||||
	id := specParts[0]
 | 
			
		||||
	mode := "rw"
 | 
			
		||||
 | 
			
		||||
	if len(specParts) == 2 {
 | 
			
		||||
		mode = specParts[1]
 | 
			
		||||
		if !ValidMountMode(mode) {
 | 
			
		||||
			return "", "", derr.ErrorCodeVolumeInvalidMode.WithArgs(mode)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return id, mode, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SplitN splits raw into a maximum of n parts, separated by a separator colon.
 | 
			
		||||
// A separator colon is the last `:` character in the regex `[/:\\]?[a-zA-Z]:` (note `\\` is `\` escaped).
 | 
			
		||||
// This allows to correctly split strings such as `C:\foo:D:\:rw`.
 | 
			
		||||
func SplitN(raw string, n int) []string {
 | 
			
		||||
	var array []string
 | 
			
		||||
	if len(raw) == 0 || raw[0] == ':' {
 | 
			
		||||
		// invalid
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	// numberOfParts counts the number of parts separated by a separator colon
 | 
			
		||||
	numberOfParts := 0
 | 
			
		||||
	// left represents the left-most cursor in raw, updated at every `:` character considered as a separator.
 | 
			
		||||
	left := 0
 | 
			
		||||
	// right represents the right-most cursor in raw incremented with the loop. Note this
 | 
			
		||||
	// starts at index 1 as index 0 is already handle above as a special case.
 | 
			
		||||
	for right := 1; right < len(raw); right++ {
 | 
			
		||||
		// stop parsing if reached maximum number of parts
 | 
			
		||||
		if n >= 0 && numberOfParts >= n {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		if raw[right] != ':' {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		potentialDriveLetter := raw[right-1]
 | 
			
		||||
		if (potentialDriveLetter >= 'A' && potentialDriveLetter <= 'Z') || (potentialDriveLetter >= 'a' && potentialDriveLetter <= 'z') {
 | 
			
		||||
			if right > 1 {
 | 
			
		||||
				beforePotentialDriveLetter := raw[right-2]
 | 
			
		||||
				if beforePotentialDriveLetter != ':' && beforePotentialDriveLetter != '/' && beforePotentialDriveLetter != '\\' {
 | 
			
		||||
					// e.g. `C:` is not preceded by any delimiter, therefore it was not a drive letter but a path ending with `C:`.
 | 
			
		||||
					array = append(array, raw[left:right])
 | 
			
		||||
					left = right + 1
 | 
			
		||||
					numberOfParts++
 | 
			
		||||
				}
 | 
			
		||||
				// else, `C:` is considered as a drive letter and not as a delimiter, so we continue parsing.
 | 
			
		||||
			}
 | 
			
		||||
			// if right == 1, then `C:` is the beginning of the raw string, therefore `:` is again not considered a delimiter and we continue parsing.
 | 
			
		||||
		} else {
 | 
			
		||||
			// if `:` is not preceded by a potential drive letter, then consider it as a delimiter.
 | 
			
		||||
			array = append(array, raw[left:right])
 | 
			
		||||
			left = right + 1
 | 
			
		||||
			numberOfParts++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// need to take care of the last part
 | 
			
		||||
	if left < len(raw) {
 | 
			
		||||
		if n >= 0 && numberOfParts >= n {
 | 
			
		||||
			// if the maximum number of parts is reached, just append the rest to the last part
 | 
			
		||||
			// left-1 is at the last `:` that needs to be included since not considered a separator.
 | 
			
		||||
			array[n-1] += raw[left-1:]
 | 
			
		||||
		} else {
 | 
			
		||||
			array = append(array, raw[left:])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return array
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										261
									
								
								volume/volume_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										261
									
								
								volume/volume_test.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,261 @@
 | 
			
		|||
package volume
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestParseMountSpec(t *testing.T) {
 | 
			
		||||
	var (
 | 
			
		||||
		valid   []string
 | 
			
		||||
		invalid map[string]string
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	if runtime.GOOS == "windows" {
 | 
			
		||||
		valid = []string{
 | 
			
		||||
			`d:\`,
 | 
			
		||||
			`d:`,
 | 
			
		||||
			`d:\path`,
 | 
			
		||||
			`d:\path with space`,
 | 
			
		||||
			// TODO Windows post TP4 - readonly support `d:\pathandmode:ro`,
 | 
			
		||||
			`c:\:d:\`,
 | 
			
		||||
			`c:\windows\:d:`,
 | 
			
		||||
			`c:\windows:d:\s p a c e`,
 | 
			
		||||
			`c:\windows:d:\s p a c e:RW`,
 | 
			
		||||
			`c:\program files:d:\s p a c e i n h o s t d i r`,
 | 
			
		||||
			`0123456789name:d:`,
 | 
			
		||||
			`MiXeDcAsEnAmE:d:`,
 | 
			
		||||
			`name:D:`,
 | 
			
		||||
			`name:D::rW`,
 | 
			
		||||
			`name:D::RW`,
 | 
			
		||||
			// TODO Windows post TP4 - readonly support `name:D::RO`,
 | 
			
		||||
			`c:/:d:/forward/slashes/are/good/too`,
 | 
			
		||||
			// TODO Windows post TP4 - readonly support `c:/:d:/including with/spaces:ro`,
 | 
			
		||||
			`c:\Windows`,             // With capital
 | 
			
		||||
			`c:\Program Files (x86)`, // With capitals and brackets
 | 
			
		||||
		}
 | 
			
		||||
		invalid = map[string]string{
 | 
			
		||||
			``:                                 "Invalid volume specification: ",
 | 
			
		||||
			`.`:                                "Invalid volume specification: ",
 | 
			
		||||
			`..\`:                              "Invalid volume specification: ",
 | 
			
		||||
			`c:\:..\`:                          "Invalid volume specification: ",
 | 
			
		||||
			`c:\:d:\:xyzzy`:                    "Invalid volume specification: ",
 | 
			
		||||
			`c:`:                               "cannot be c:",
 | 
			
		||||
			`c:\`:                              `cannot be c:\`,
 | 
			
		||||
			`c:\notexist:d:`:                   `The system cannot find the file specified`,
 | 
			
		||||
			`c:\windows\system32\ntdll.dll:d:`: `Source 'c:\windows\system32\ntdll.dll' is not a directory`,
 | 
			
		||||
			`name<:d:`:                         `Invalid volume specification`,
 | 
			
		||||
			`name>:d:`:                         `Invalid volume specification`,
 | 
			
		||||
			`name::d:`:                         `Invalid volume specification`,
 | 
			
		||||
			`name":d:`:                         `Invalid volume specification`,
 | 
			
		||||
			`name\:d:`:                         `Invalid volume specification`,
 | 
			
		||||
			`name*:d:`:                         `Invalid volume specification`,
 | 
			
		||||
			`name|:d:`:                         `Invalid volume specification`,
 | 
			
		||||
			`name?:d:`:                         `Invalid volume specification`,
 | 
			
		||||
			`name/:d:`:                         `Invalid volume specification`,
 | 
			
		||||
			`d:\pathandmode:rw`:                `Invalid volume specification`,
 | 
			
		||||
			`con:d:`:                           `cannot be a reserved word for Windows filenames`,
 | 
			
		||||
			`PRN:d:`:                           `cannot be a reserved word for Windows filenames`,
 | 
			
		||||
			`aUx:d:`:                           `cannot be a reserved word for Windows filenames`,
 | 
			
		||||
			`nul:d:`:                           `cannot be a reserved word for Windows filenames`,
 | 
			
		||||
			`com1:d:`:                          `cannot be a reserved word for Windows filenames`,
 | 
			
		||||
			`com2:d:`:                          `cannot be a reserved word for Windows filenames`,
 | 
			
		||||
			`com3:d:`:                          `cannot be a reserved word for Windows filenames`,
 | 
			
		||||
			`com4:d:`:                          `cannot be a reserved word for Windows filenames`,
 | 
			
		||||
			`com5:d:`:                          `cannot be a reserved word for Windows filenames`,
 | 
			
		||||
			`com6:d:`:                          `cannot be a reserved word for Windows filenames`,
 | 
			
		||||
			`com7:d:`:                          `cannot be a reserved word for Windows filenames`,
 | 
			
		||||
			`com8:d:`:                          `cannot be a reserved word for Windows filenames`,
 | 
			
		||||
			`com9:d:`:                          `cannot be a reserved word for Windows filenames`,
 | 
			
		||||
			`lpt1:d:`:                          `cannot be a reserved word for Windows filenames`,
 | 
			
		||||
			`lpt2:d:`:                          `cannot be a reserved word for Windows filenames`,
 | 
			
		||||
			`lpt3:d:`:                          `cannot be a reserved word for Windows filenames`,
 | 
			
		||||
			`lpt4:d:`:                          `cannot be a reserved word for Windows filenames`,
 | 
			
		||||
			`lpt5:d:`:                          `cannot be a reserved word for Windows filenames`,
 | 
			
		||||
			`lpt6:d:`:                          `cannot be a reserved word for Windows filenames`,
 | 
			
		||||
			`lpt7:d:`:                          `cannot be a reserved word for Windows filenames`,
 | 
			
		||||
			`lpt8:d:`:                          `cannot be a reserved word for Windows filenames`,
 | 
			
		||||
			`lpt9:d:`:                          `cannot be a reserved word for Windows filenames`,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	} else {
 | 
			
		||||
		valid = []string{
 | 
			
		||||
			"/home",
 | 
			
		||||
			"/home:/home",
 | 
			
		||||
			"/home:/something/else",
 | 
			
		||||
			"/with space",
 | 
			
		||||
			"/home:/with space",
 | 
			
		||||
			"relative:/absolute-path",
 | 
			
		||||
			"hostPath:/containerPath:ro",
 | 
			
		||||
			"/hostPath:/containerPath:rw",
 | 
			
		||||
			"/rw:/ro",
 | 
			
		||||
		}
 | 
			
		||||
		invalid = map[string]string{
 | 
			
		||||
			"":                "Invalid volume specification",
 | 
			
		||||
			"./":              "Invalid volume destination",
 | 
			
		||||
			"../":             "Invalid volume destination",
 | 
			
		||||
			"/:../":           "Invalid volume destination",
 | 
			
		||||
			"/:path":          "Invalid volume destination",
 | 
			
		||||
			":":               "Invalid volume specification",
 | 
			
		||||
			"/tmp:":           "Invalid volume destination",
 | 
			
		||||
			":test":           "Invalid volume specification",
 | 
			
		||||
			":/test":          "Invalid volume specification",
 | 
			
		||||
			"tmp:":            "Invalid volume destination",
 | 
			
		||||
			":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: sw",
 | 
			
		||||
			"/path:/path:rwz": "invalid mode: rwz",
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, path := range valid {
 | 
			
		||||
		if _, err := ParseMountSpec(path, "local"); err != nil {
 | 
			
		||||
			t.Fatalf("ParseMountSpec(`%q`) should succeed: error %q", path, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for path, expectedError := range invalid {
 | 
			
		||||
		if _, err := ParseMountSpec(path, "local"); err == nil {
 | 
			
		||||
			t.Fatalf("ParseMountSpec(`%q`) should have failed validation. Err %v", path, err)
 | 
			
		||||
		} else {
 | 
			
		||||
			if !strings.Contains(err.Error(), expectedError) {
 | 
			
		||||
				t.Fatalf("ParseMountSpec(`%q`) error should contain %q, got %v", path, expectedError, err.Error())
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSplitN(t *testing.T) {
 | 
			
		||||
	for _, x := range []struct {
 | 
			
		||||
		input    string
 | 
			
		||||
		n        int
 | 
			
		||||
		expected []string
 | 
			
		||||
	}{
 | 
			
		||||
		{`C:\foo:d:`, -1, []string{`C:\foo`, `d:`}},
 | 
			
		||||
		{`:C:\foo:d:`, -1, nil},
 | 
			
		||||
		{`/foo:/bar:ro`, 3, []string{`/foo`, `/bar`, `ro`}},
 | 
			
		||||
		{`/foo:/bar:ro`, 2, []string{`/foo`, `/bar:ro`}},
 | 
			
		||||
		{`C:\foo\:/foo`, -1, []string{`C:\foo\`, `/foo`}},
 | 
			
		||||
 | 
			
		||||
		{`d:\`, -1, []string{`d:\`}},
 | 
			
		||||
		{`d:`, -1, []string{`d:`}},
 | 
			
		||||
		{`d:\path`, -1, []string{`d:\path`}},
 | 
			
		||||
		{`d:\path with space`, -1, []string{`d:\path with space`}},
 | 
			
		||||
		{`d:\pathandmode:rw`, -1, []string{`d:\pathandmode`, `rw`}},
 | 
			
		||||
		{`c:\:d:\`, -1, []string{`c:\`, `d:\`}},
 | 
			
		||||
		{`c:\windows\:d:`, -1, []string{`c:\windows\`, `d:`}},
 | 
			
		||||
		{`c:\windows:d:\s p a c e`, -1, []string{`c:\windows`, `d:\s p a c e`}},
 | 
			
		||||
		{`c:\windows:d:\s p a c e:RW`, -1, []string{`c:\windows`, `d:\s p a c e`, `RW`}},
 | 
			
		||||
		{`c:\program files:d:\s p a c e i n h o s t d i r`, -1, []string{`c:\program files`, `d:\s p a c e i n h o s t d i r`}},
 | 
			
		||||
		{`0123456789name:d:`, -1, []string{`0123456789name`, `d:`}},
 | 
			
		||||
		{`MiXeDcAsEnAmE:d:`, -1, []string{`MiXeDcAsEnAmE`, `d:`}},
 | 
			
		||||
		{`name:D:`, -1, []string{`name`, `D:`}},
 | 
			
		||||
		{`name:D::rW`, -1, []string{`name`, `D:`, `rW`}},
 | 
			
		||||
		{`name:D::RW`, -1, []string{`name`, `D:`, `RW`}},
 | 
			
		||||
		{`c:/:d:/forward/slashes/are/good/too`, -1, []string{`c:/`, `d:/forward/slashes/are/good/too`}},
 | 
			
		||||
		{`c:\Windows`, -1, []string{`c:\Windows`}},
 | 
			
		||||
		{`c:\Program Files (x86)`, -1, []string{`c:\Program Files (x86)`}},
 | 
			
		||||
 | 
			
		||||
		{``, -1, nil},
 | 
			
		||||
		{`.`, -1, []string{`.`}},
 | 
			
		||||
		{`..\`, -1, []string{`..\`}},
 | 
			
		||||
		{`c:\:..\`, -1, []string{`c:\`, `..\`}},
 | 
			
		||||
		{`c:\:d:\:xyzzy`, -1, []string{`c:\`, `d:\`, `xyzzy`}},
 | 
			
		||||
	} {
 | 
			
		||||
		res := SplitN(x.input, x.n)
 | 
			
		||||
		if len(res) < len(x.expected) {
 | 
			
		||||
			t.Fatalf("input: %v, expected: %v, got: %v", x.input, x.expected, res)
 | 
			
		||||
		}
 | 
			
		||||
		for i, e := range res {
 | 
			
		||||
			if e != x.expected[i] {
 | 
			
		||||
				t.Fatalf("input: %v, expected: %v, got: %v", x.input, x.expected, res)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// testParseMountSpec is a structure used by TestParseMountSpecSplit for
 | 
			
		||||
// specifying test cases for the ParseMountSpec() function.
 | 
			
		||||
type testParseMountSpec struct {
 | 
			
		||||
	bind      string
 | 
			
		||||
	driver    string
 | 
			
		||||
	expDest   string
 | 
			
		||||
	expSource string
 | 
			
		||||
	expName   string
 | 
			
		||||
	expDriver string
 | 
			
		||||
	expRW     bool
 | 
			
		||||
	fail      bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseMountSpecSplit(t *testing.T) {
 | 
			
		||||
	var cases []testParseMountSpec
 | 
			
		||||
	if runtime.GOOS == "windows" {
 | 
			
		||||
		cases = []testParseMountSpec{
 | 
			
		||||
			{`c:\:d:`, "local", `d:`, `c:\`, ``, "", true, false},
 | 
			
		||||
			{`c:\:d:\`, "local", `d:\`, `c:\`, ``, "", true, false},
 | 
			
		||||
			// TODO Windows post TP4 - Add readonly support {`c:\:d:\:ro`, "local", `d:\`, `c:\`, ``, "", false, false},
 | 
			
		||||
			{`c:\:d:\:rw`, "local", `d:\`, `c:\`, ``, "", true, false},
 | 
			
		||||
			{`c:\:d:\:foo`, "local", `d:\`, `c:\`, ``, "", false, true},
 | 
			
		||||
			{`name:d::rw`, "local", `d:`, ``, `name`, "local", true, false},
 | 
			
		||||
			{`name:d:`, "local", `d:`, ``, `name`, "local", true, false},
 | 
			
		||||
			// TODO Windows post TP4 - Add readonly support {`name:d::ro`, "local", `d:`, ``, `name`, "local", false, false},
 | 
			
		||||
			{`name:c:`, "", ``, ``, ``, "", true, true},
 | 
			
		||||
			{`driver/name:c:`, "", ``, ``, ``, "", true, true},
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		cases = []testParseMountSpec{
 | 
			
		||||
			{"/tmp:/tmp1", "", "/tmp1", "/tmp", "", "", true, false},
 | 
			
		||||
			{"/tmp:/tmp2:ro", "", "/tmp2", "/tmp", "", "", false, false},
 | 
			
		||||
			{"/tmp:/tmp3:rw", "", "/tmp3", "/tmp", "", "", true, false},
 | 
			
		||||
			{"/tmp:/tmp4:foo", "", "", "", "", "", false, true},
 | 
			
		||||
			{"name:/named1", "", "/named1", "", "name", "local", true, false},
 | 
			
		||||
			{"name:/named2", "external", "/named2", "", "name", "external", true, false},
 | 
			
		||||
			{"name:/named3:ro", "local", "/named3", "", "name", "local", false, false},
 | 
			
		||||
			{"local/name:/tmp:rw", "", "/tmp", "", "local/name", "local", true, false},
 | 
			
		||||
			{"/tmp:tmp", "", "", "", "", "", true, true},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, c := range cases {
 | 
			
		||||
		m, err := ParseMountSpec(c.bind, c.driver)
 | 
			
		||||
		if c.fail {
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				t.Fatalf("Expected error, was nil, for spec %s\n", c.bind)
 | 
			
		||||
			}
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if m == nil || err != nil {
 | 
			
		||||
			t.Fatalf("ParseMountSpec failed for spec %s driver %s error %v\n", c.bind, c.driver, err.Error())
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if m.Destination != c.expDest {
 | 
			
		||||
			t.Fatalf("Expected destination %s, was %s, for spec %s\n", c.expDest, m.Destination, c.bind)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if m.Source != c.expSource {
 | 
			
		||||
			t.Fatalf("Expected source %s, was %s, for spec %s\n", c.expSource, m.Source, c.bind)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if m.Name != c.expName {
 | 
			
		||||
			t.Fatalf("Expected name %s, was %s for spec %s\n", c.expName, m.Name, c.bind)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if m.Driver != c.expDriver {
 | 
			
		||||
			t.Fatalf("Expected driver %s, was %s, for spec %s\n", c.expDriver, m.Driver, c.bind)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if m.RW != c.expRW {
 | 
			
		||||
			t.Fatalf("Expected RW %v, was %v for spec %s\n", c.expRW, m.RW, c.bind)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										132
									
								
								volume/volume_unix.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								volume/volume_unix.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,132 @@
 | 
			
		|||
// +build linux freebsd darwin
 | 
			
		||||
 | 
			
		||||
package volume
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	derr "github.com/docker/docker/errors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// read-write modes
 | 
			
		||||
var rwModes = map[string]bool{
 | 
			
		||||
	"rw":   true,
 | 
			
		||||
	"rw,Z": true,
 | 
			
		||||
	"rw,z": true,
 | 
			
		||||
	"z,rw": true,
 | 
			
		||||
	"Z,rw": true,
 | 
			
		||||
	"Z":    true,
 | 
			
		||||
	"z":    true,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// read-only modes
 | 
			
		||||
var roModes = map[string]bool{
 | 
			
		||||
	"ro":   true,
 | 
			
		||||
	"ro,Z": true,
 | 
			
		||||
	"ro,z": true,
 | 
			
		||||
	"z,ro": true,
 | 
			
		||||
	"Z,ro": 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 {
 | 
			
		||||
	relPath, err := filepath.Rel(m.Destination, absolutePath)
 | 
			
		||||
	return err == nil && relPath != ".." && !strings.HasPrefix(relPath, fmt.Sprintf("..%c", filepath.Separator))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParseMountSpec validates the configuration of mount information is valid.
 | 
			
		||||
func ParseMountSpec(spec, volumeDriver string) (*MountPoint, error) {
 | 
			
		||||
	spec = filepath.ToSlash(spec)
 | 
			
		||||
 | 
			
		||||
	mp := &MountPoint{
 | 
			
		||||
		RW: true,
 | 
			
		||||
	}
 | 
			
		||||
	if strings.Count(spec, ":") > 2 {
 | 
			
		||||
		return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	arr := strings.SplitN(spec, ":", 3)
 | 
			
		||||
	if arr[0] == "" {
 | 
			
		||||
		return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch len(arr) {
 | 
			
		||||
	case 1:
 | 
			
		||||
		// Just a destination path in the container
 | 
			
		||||
		mp.Destination = filepath.Clean(arr[0])
 | 
			
		||||
	case 2:
 | 
			
		||||
		if isValid := ValidMountMode(arr[1]); isValid {
 | 
			
		||||
			// Destination + Mode is not a valid volume - volumes
 | 
			
		||||
			// cannot include a mode. eg /foo:rw
 | 
			
		||||
			return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec)
 | 
			
		||||
		}
 | 
			
		||||
		// Host Source Path or Name + Destination
 | 
			
		||||
		mp.Source = arr[0]
 | 
			
		||||
		mp.Destination = arr[1]
 | 
			
		||||
	case 3:
 | 
			
		||||
		// HostSourcePath+DestinationPath+Mode
 | 
			
		||||
		mp.Source = arr[0]
 | 
			
		||||
		mp.Destination = arr[1]
 | 
			
		||||
		mp.Mode = arr[2] // Mode field is used by SELinux to decide whether to apply label
 | 
			
		||||
		if !ValidMountMode(mp.Mode) {
 | 
			
		||||
			return nil, derr.ErrorCodeVolumeInvalidMode.WithArgs(mp.Mode)
 | 
			
		||||
		}
 | 
			
		||||
		mp.RW = ReadWrite(mp.Mode)
 | 
			
		||||
	default:
 | 
			
		||||
		return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//validate the volumes destination path
 | 
			
		||||
	mp.Destination = filepath.Clean(mp.Destination)
 | 
			
		||||
	if !filepath.IsAbs(mp.Destination) {
 | 
			
		||||
		return nil, derr.ErrorCodeVolumeAbs.WithArgs(mp.Destination)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Destination cannot be "/"
 | 
			
		||||
	if mp.Destination == "/" {
 | 
			
		||||
		return nil, derr.ErrorCodeVolumeSlash.WithArgs(spec)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	name, source := ParseVolumeSource(mp.Source)
 | 
			
		||||
	if len(source) == 0 {
 | 
			
		||||
		mp.Source = "" // Clear it out as we previously assumed it was not a name
 | 
			
		||||
		mp.Driver = volumeDriver
 | 
			
		||||
		if len(mp.Driver) == 0 {
 | 
			
		||||
			mp.Driver = DefaultDriverName
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		mp.Source = filepath.Clean(source)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mp.Name = name
 | 
			
		||||
 | 
			
		||||
	return mp, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParseVolumeSource parses the origin sources that's mounted into the container.
 | 
			
		||||
// It returns a name and a source. It looks to see if the spec passed in
 | 
			
		||||
// is an absolute file. If it is, it assumes the spec is a source. If not,
 | 
			
		||||
// it assumes the spec is a name.
 | 
			
		||||
func ParseVolumeSource(spec string) (string, string) {
 | 
			
		||||
	if !filepath.IsAbs(spec) {
 | 
			
		||||
		return spec, ""
 | 
			
		||||
	}
 | 
			
		||||
	return "", spec
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsVolumeNameValid checks a volume name in a platform specific manner.
 | 
			
		||||
func IsVolumeNameValid(name string) (bool, error) {
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										181
									
								
								volume/volume_windows.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								volume/volume_windows.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,181 @@
 | 
			
		|||
package volume
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	derr "github.com/docker/docker/errors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// read-write modes
 | 
			
		||||
var rwModes = map[string]bool{
 | 
			
		||||
	"rw": true,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// read-only modes
 | 
			
		||||
var roModes = map[string]bool{
 | 
			
		||||
	"ro": true,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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])`
 | 
			
		||||
 | 
			
		||||
	// RXSource is the combined possiblities for a source
 | 
			
		||||
	RXSource = `((?P<source>((` + RXHostDir + `)|(` + RXName + `))):)?`
 | 
			
		||||
 | 
			
		||||
	// 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]+)*\\?))`
 | 
			
		||||
	// 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
 | 
			
		||||
	RXMode = `(:(?P<mode>(?i)rw))?`
 | 
			
		||||
	// Temporarily for TP4, disabling the use of ro as it's not supported yet
 | 
			
		||||
	// in the platform. TODO Windows: `(:(?P<mode>(?i)ro|rw))?`
 | 
			
		||||
	// mode (optional)
 | 
			
		||||
	//    -  Hopefully self explanatory in comparison to above.
 | 
			
		||||
	//    -  Colon is not in the capture group
 | 
			
		||||
	//
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ParseMountSpec validates the configuration of mount information is valid.
 | 
			
		||||
func ParseMountSpec(spec string, volumeDriver string) (*MountPoint, error) {
 | 
			
		||||
	var specExp = regexp.MustCompile(`^` + RXSource + RXDestination + RXMode + `$`)
 | 
			
		||||
 | 
			
		||||
	// Ensure in platform semantics for matching. The CLI will send in Unix semantics.
 | 
			
		||||
	match := specExp.FindStringSubmatch(filepath.FromSlash(strings.ToLower(spec)))
 | 
			
		||||
 | 
			
		||||
	// Must have something back
 | 
			
		||||
	if len(match) == 0 {
 | 
			
		||||
		return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Pull out the sub expressions from the named capture groups
 | 
			
		||||
	matchgroups := make(map[string]string)
 | 
			
		||||
	for i, name := range specExp.SubexpNames() {
 | 
			
		||||
		matchgroups[name] = strings.ToLower(match[i])
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mp := &MountPoint{
 | 
			
		||||
		Source:      matchgroups["source"],
 | 
			
		||||
		Destination: matchgroups["destination"],
 | 
			
		||||
		RW:          true,
 | 
			
		||||
	}
 | 
			
		||||
	if strings.ToLower(matchgroups["mode"]) == "ro" {
 | 
			
		||||
		mp.RW = false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Volumes cannot include an explicitly supplied mode eg c:\path:rw
 | 
			
		||||
	if mp.Source == "" && mp.Destination != "" && matchgroups["mode"] != "" {
 | 
			
		||||
		return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Note: No need to check if destination is absolute as it must be by
 | 
			
		||||
	// definition of matching the regex.
 | 
			
		||||
 | 
			
		||||
	if filepath.VolumeName(mp.Destination) == mp.Destination {
 | 
			
		||||
		// Ensure the destination path, if a drive letter, is not the c drive
 | 
			
		||||
		if strings.ToLower(mp.Destination) == "c:" {
 | 
			
		||||
			return nil, derr.ErrorCodeVolumeDestIsC.WithArgs(spec)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		// So we know the destination is a path, not drive letter. Clean it up.
 | 
			
		||||
		mp.Destination = filepath.Clean(mp.Destination)
 | 
			
		||||
		// Ensure the destination path, if a path, is not the c root directory
 | 
			
		||||
		if strings.ToLower(mp.Destination) == `c:\` {
 | 
			
		||||
			return nil, derr.ErrorCodeVolumeDestIsCRoot.WithArgs(spec)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// See if the source is a name instead of a host directory
 | 
			
		||||
	if len(mp.Source) > 0 {
 | 
			
		||||
		validName, err := IsVolumeNameValid(mp.Source)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		if validName {
 | 
			
		||||
			// OK, so the source is a name.
 | 
			
		||||
			mp.Name = mp.Source
 | 
			
		||||
			mp.Source = ""
 | 
			
		||||
 | 
			
		||||
			// Set the driver accordingly
 | 
			
		||||
			mp.Driver = volumeDriver
 | 
			
		||||
			if len(mp.Driver) == 0 {
 | 
			
		||||
				mp.Driver = DefaultDriverName
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			// OK, so the source must be a host directory. Make sure it's clean.
 | 
			
		||||
			mp.Source = filepath.Clean(mp.Source)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Ensure the host path source, if supplied, exists and is a directory
 | 
			
		||||
	if len(mp.Source) > 0 {
 | 
			
		||||
		var fi os.FileInfo
 | 
			
		||||
		var err error
 | 
			
		||||
		if fi, err = os.Stat(mp.Source); err != nil {
 | 
			
		||||
			return nil, derr.ErrorCodeVolumeSourceNotFound.WithArgs(mp.Source, err)
 | 
			
		||||
		}
 | 
			
		||||
		if !fi.IsDir() {
 | 
			
		||||
			return nil, derr.ErrorCodeVolumeSourceNotDirectory.WithArgs(mp.Source)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logrus.Debugf("MP: Source '%s', Dest '%s', RW %t, Name '%s', Driver '%s'", mp.Source, mp.Destination, mp.RW, mp.Name, mp.Driver)
 | 
			
		||||
	return mp, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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, derr.ErrorCodeVolumeNameReservedWord.WithArgs(name)
 | 
			
		||||
	}
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue