volume/volume_unix.go
c12dbb8c
 // +build linux freebsd darwin solaris
a7e686a7
 
 package volume
 
 import (
 	"fmt"
 	"path/filepath"
 	"strings"
 )
 
 // read-write modes
 var rwModes = map[string]bool{
a2dc4f79
 	"rw": true,
 	"ro": true,
a7e686a7
 }
 
a2dc4f79
 // label modes
 var labelModes = map[string]bool{
 	"Z": true,
 	"z": true,
a7e686a7
 }
 
 // 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{
a2dc4f79
 		RW:          true,
 		Propagation: DefaultPropagationMode,
a7e686a7
 	}
 	if strings.Count(spec, ":") > 2 {
a793564b
 		return nil, errInvalidSpec(spec)
a7e686a7
 	}
 
 	arr := strings.SplitN(spec, ":", 3)
 	if arr[0] == "" {
a793564b
 		return nil, errInvalidSpec(spec)
a7e686a7
 	}
 
 	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
a793564b
 			return nil, errInvalidSpec(spec)
a7e686a7
 		}
 		// 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) {
a793564b
 			return nil, errInvalidMode(mp.Mode)
a7e686a7
 		}
 		mp.RW = ReadWrite(mp.Mode)
a2dc4f79
 		mp.Propagation = GetPropagation(mp.Mode)
a7e686a7
 	default:
a793564b
 		return nil, errInvalidSpec(spec)
a7e686a7
 	}
 
 	//validate the volumes destination path
 	mp.Destination = filepath.Clean(mp.Destination)
 	if !filepath.IsAbs(mp.Destination) {
a793564b
 		return nil, fmt.Errorf("Invalid volume destination path: '%s' mount path must be absolute.", mp.Destination)
a7e686a7
 	}
 
 	// Destination cannot be "/"
 	if mp.Destination == "/" {
a793564b
 		return nil, fmt.Errorf("Invalid specification: destination can't be '/' in '%s'", spec)
a7e686a7
 	}
 
 	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
a2dc4f79
 		// Named volumes can't have propagation properties specified.
 		// Their defaults will be decided by docker. This is just a
 		// safeguard. Don't want to get into situations where named
 		// volumes were mounted as '[r]shared' inside container and
 		// container does further mounts under that volume and these
 		// mounts become visible on  host and later original volume
 		// cleanup becomes an issue if container does not unmount
 		// submounts explicitly.
 		if HasPropagation(mp.Mode) {
a793564b
 			return nil, errInvalidSpec(spec)
a2dc4f79
 		}
a7e686a7
 	} else {
 		mp.Source = filepath.Clean(source)
 	}
 
b0ac69b6
 	copyData, isSet := getCopyMode(mp.Mode)
 	// do not allow copy modes on binds
 	if len(name) == 0 && isSet {
 		return nil, errInvalidMode(mp.Mode)
 	}
 
 	mp.CopyData = copyData
a7e686a7
 	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
 }
a2dc4f79
 
 // ValidMountMode will make sure the mount mode is valid.
 // returns if it's a valid mount mode or not.
 func ValidMountMode(mode string) bool {
 	rwModeCount := 0
 	labelModeCount := 0
 	propagationModeCount := 0
b0ac69b6
 	copyModeCount := 0
a2dc4f79
 
 	for _, o := range strings.Split(mode, ",") {
b0ac69b6
 		switch {
 		case rwModes[o]:
a2dc4f79
 			rwModeCount++
b0ac69b6
 		case labelModes[o]:
a2dc4f79
 			labelModeCount++
b0ac69b6
 		case propagationModes[o]:
a2dc4f79
 			propagationModeCount++
b0ac69b6
 		case copyModeExists(o):
 			copyModeCount++
 		default:
 			return false
a2dc4f79
 		}
 	}
 
 	// Only one string for each mode is allowed.
b0ac69b6
 	if rwModeCount > 1 || labelModeCount > 1 || propagationModeCount > 1 || copyModeCount > 1 {
a2dc4f79
 		return false
 	}
 	return true
 }
 
 // ReadWrite tells you if a mode string is a valid read-write mode or not.
 // If there are no specifications w.r.t read write mode, then by default
34b82a69
 // it returns true.
a2dc4f79
 func ReadWrite(mode string) bool {
 	if !ValidMountMode(mode) {
 		return false
 	}
 
 	for _, o := range strings.Split(mode, ",") {
 		if o == "ro" {
 			return false
 		}
 	}
 
 	return true
 }