moby/volume/volume_unix.go
Brian Goff b0ac69b67e Add explicit flags for volume cp/no-cp
This allows a user to specify explicitly to enable
automatic copying of data from the container path to the volume path.
This does not change the default behavior of automatically copying, but
does allow a user to disable it at runtime.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
2016-03-21 20:38:44 -04:00

186 lines
5.1 KiB
Go

// +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
}