blob: 2520d7c1442d1c83d11eff4b8595a6c7245d3d47 [file] [log] [blame]
// +build linux freebsd darwin solaris
package volume
import (
"fmt"
"path/filepath"
"strings"
)
// read-write modes
var rwModes = map[string]bool{
"rw": true,
"ro": true,
}
// label modes
var labelModes = map[string]bool{
"Z": true,
"z": 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,
Propagation: DefaultPropagationMode,
}
if strings.Count(spec, ":") > 2 {
return nil, errInvalidSpec(spec)
}
arr := strings.SplitN(spec, ":", 3)
if arr[0] == "" {
return nil, errInvalidSpec(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, errInvalidSpec(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, errInvalidMode(mp.Mode)
}
mp.RW = ReadWrite(mp.Mode)
mp.Propagation = GetPropagation(mp.Mode)
default:
return nil, errInvalidSpec(spec)
}
//validate the volumes destination path
mp.Destination = filepath.Clean(mp.Destination)
if !filepath.IsAbs(mp.Destination) {
return nil, fmt.Errorf("Invalid volume destination path: '%s' mount path must be absolute.", mp.Destination)
}
// Destination cannot be "/"
if mp.Destination == "/" {
return nil, fmt.Errorf("Invalid specification: destination can't be '/' in '%s'", 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
// 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) {
return nil, errInvalidSpec(spec)
}
} else {
mp.Source = filepath.Clean(source)
}
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
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
}
// 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
copyModeCount := 0
for _, o := range strings.Split(mode, ",") {
switch {
case rwModes[o]:
rwModeCount++
case labelModes[o]:
labelModeCount++
case propagationModes[o]:
propagationModeCount++
case copyModeExists(o):
copyModeCount++
default:
return false
}
}
// Only one string for each mode is allowed.
if rwModeCount > 1 || labelModeCount > 1 || propagationModeCount > 1 || copyModeCount > 1 {
return false
}
return true
}
// ReadWrite tells you if a mode string is a valid read-write mode or not.
// If there are no specifications w.r.t read write mode, then by default
// it returns true.
func ReadWrite(mode string) bool {
if !ValidMountMode(mode) {
return false
}
for _, o := range strings.Split(mode, ",") {
if o == "ro" {
return false
}
}
return true
}