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