github.com/dpiddy/docker@v1.12.2-rc1/volume/volume_unix.go (about) 1 // +build linux freebsd darwin solaris 2 3 package volume 4 5 import ( 6 "fmt" 7 "path/filepath" 8 "strings" 9 ) 10 11 // read-write modes 12 var rwModes = map[string]bool{ 13 "rw": true, 14 "ro": true, 15 } 16 17 // label modes 18 var labelModes = map[string]bool{ 19 "Z": true, 20 "z": true, 21 } 22 23 // BackwardsCompatible decides whether this mount point can be 24 // used in old versions of Docker or not. 25 // Only bind mounts and local volumes can be used in old versions of Docker. 26 func (m *MountPoint) BackwardsCompatible() bool { 27 return len(m.Source) > 0 || m.Driver == DefaultDriverName 28 } 29 30 // HasResource checks whether the given absolute path for a container is in 31 // this mount point. If the relative path starts with `../` then the resource 32 // is outside of this mount point, but we can't simply check for this prefix 33 // because it misses `..` which is also outside of the mount, so check both. 34 func (m *MountPoint) HasResource(absolutePath string) bool { 35 relPath, err := filepath.Rel(m.Destination, absolutePath) 36 return err == nil && relPath != ".." && !strings.HasPrefix(relPath, fmt.Sprintf("..%c", filepath.Separator)) 37 } 38 39 // ParseMountSpec validates the configuration of mount information is valid. 40 func ParseMountSpec(spec, volumeDriver string) (*MountPoint, error) { 41 spec = filepath.ToSlash(spec) 42 43 mp := &MountPoint{ 44 RW: true, 45 Propagation: DefaultPropagationMode, 46 } 47 if strings.Count(spec, ":") > 2 { 48 return nil, errInvalidSpec(spec) 49 } 50 51 arr := strings.SplitN(spec, ":", 3) 52 if arr[0] == "" { 53 return nil, errInvalidSpec(spec) 54 } 55 56 switch len(arr) { 57 case 1: 58 // Just a destination path in the container 59 mp.Destination = filepath.Clean(arr[0]) 60 case 2: 61 if isValid := ValidMountMode(arr[1]); isValid { 62 // Destination + Mode is not a valid volume - volumes 63 // cannot include a mode. eg /foo:rw 64 return nil, errInvalidSpec(spec) 65 } 66 // Host Source Path or Name + Destination 67 mp.Source = arr[0] 68 mp.Destination = arr[1] 69 case 3: 70 // HostSourcePath+DestinationPath+Mode 71 mp.Source = arr[0] 72 mp.Destination = arr[1] 73 mp.Mode = arr[2] // Mode field is used by SELinux to decide whether to apply label 74 if !ValidMountMode(mp.Mode) { 75 return nil, errInvalidMode(mp.Mode) 76 } 77 mp.RW = ReadWrite(mp.Mode) 78 mp.Propagation = GetPropagation(mp.Mode) 79 default: 80 return nil, errInvalidSpec(spec) 81 } 82 83 //validate the volumes destination path 84 mp.Destination = filepath.Clean(mp.Destination) 85 if !filepath.IsAbs(mp.Destination) { 86 return nil, fmt.Errorf("Invalid volume destination path: '%s' mount path must be absolute.", mp.Destination) 87 } 88 89 // Destination cannot be "/" 90 if mp.Destination == "/" { 91 return nil, fmt.Errorf("Invalid specification: destination can't be '/' in '%s'", spec) 92 } 93 94 name, source := ParseVolumeSource(mp.Source) 95 if len(source) == 0 { 96 mp.Source = "" // Clear it out as we previously assumed it was not a name 97 mp.Driver = volumeDriver 98 // Named volumes can't have propagation properties specified. 99 // Their defaults will be decided by docker. This is just a 100 // safeguard. Don't want to get into situations where named 101 // volumes were mounted as '[r]shared' inside container and 102 // container does further mounts under that volume and these 103 // mounts become visible on host and later original volume 104 // cleanup becomes an issue if container does not unmount 105 // submounts explicitly. 106 if HasPropagation(mp.Mode) { 107 return nil, errInvalidSpec(spec) 108 } 109 } else { 110 mp.Source = filepath.Clean(source) 111 } 112 113 copyData, isSet := getCopyMode(mp.Mode) 114 // do not allow copy modes on binds 115 if len(name) == 0 && isSet { 116 return nil, errInvalidMode(mp.Mode) 117 } 118 119 mp.CopyData = copyData 120 mp.Name = name 121 122 return mp, nil 123 } 124 125 // ParseVolumeSource parses the origin sources that's mounted into the container. 126 // It returns a name and a source. It looks to see if the spec passed in 127 // is an absolute file. If it is, it assumes the spec is a source. If not, 128 // it assumes the spec is a name. 129 func ParseVolumeSource(spec string) (string, string) { 130 if !filepath.IsAbs(spec) { 131 return spec, "" 132 } 133 return "", spec 134 } 135 136 // IsVolumeNameValid checks a volume name in a platform specific manner. 137 func IsVolumeNameValid(name string) (bool, error) { 138 return true, nil 139 } 140 141 // ValidMountMode will make sure the mount mode is valid. 142 // returns if it's a valid mount mode or not. 143 func ValidMountMode(mode string) bool { 144 rwModeCount := 0 145 labelModeCount := 0 146 propagationModeCount := 0 147 copyModeCount := 0 148 149 for _, o := range strings.Split(mode, ",") { 150 switch { 151 case rwModes[o]: 152 rwModeCount++ 153 case labelModes[o]: 154 labelModeCount++ 155 case propagationModes[o]: 156 propagationModeCount++ 157 case copyModeExists(o): 158 copyModeCount++ 159 default: 160 return false 161 } 162 } 163 164 // Only one string for each mode is allowed. 165 if rwModeCount > 1 || labelModeCount > 1 || propagationModeCount > 1 || copyModeCount > 1 { 166 return false 167 } 168 return true 169 } 170 171 // ReadWrite tells you if a mode string is a valid read-write mode or not. 172 // If there are no specifications w.r.t read write mode, then by default 173 // it returns true. 174 func ReadWrite(mode string) bool { 175 if !ValidMountMode(mode) { 176 return false 177 } 178 179 for _, o := range strings.Split(mode, ",") { 180 if o == "ro" { 181 return false 182 } 183 } 184 185 return true 186 }