github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/volume/mounts/linux_parser.go (about) 1 package mounts // import "github.com/demonoid81/moby/volume/mounts" 2 3 import ( 4 "errors" 5 "fmt" 6 "path" 7 "path/filepath" 8 "strings" 9 10 "github.com/demonoid81/moby/api/types/mount" 11 "github.com/demonoid81/moby/pkg/stringid" 12 "github.com/demonoid81/moby/volume" 13 ) 14 15 type linuxParser struct { 16 } 17 18 func linuxSplitRawSpec(raw string) ([]string, error) { 19 if strings.Count(raw, ":") > 2 { 20 return nil, errInvalidSpec(raw) 21 } 22 23 arr := strings.SplitN(raw, ":", 3) 24 if arr[0] == "" { 25 return nil, errInvalidSpec(raw) 26 } 27 return arr, nil 28 } 29 30 func linuxValidateNotRoot(p string) error { 31 p = path.Clean(strings.Replace(p, `\`, `/`, -1)) 32 if p == "/" { 33 return ErrVolumeTargetIsRoot 34 } 35 return nil 36 } 37 func linuxValidateAbsolute(p string) error { 38 p = strings.Replace(p, `\`, `/`, -1) 39 if path.IsAbs(p) { 40 return nil 41 } 42 return fmt.Errorf("invalid mount path: '%s' mount path must be absolute", p) 43 } 44 func (p *linuxParser) ValidateMountConfig(mnt *mount.Mount) error { 45 // there was something looking like a bug in existing codebase: 46 // - validateMountConfig on linux was called with options skipping bind source existence when calling ParseMountRaw 47 // - but not when calling ParseMountSpec directly... nor when the unit test called it directly 48 return p.validateMountConfigImpl(mnt, true) 49 } 50 func (p *linuxParser) validateMountConfigImpl(mnt *mount.Mount, validateBindSourceExists bool) error { 51 if len(mnt.Target) == 0 { 52 return &errMountConfig{mnt, errMissingField("Target")} 53 } 54 55 if err := linuxValidateNotRoot(mnt.Target); err != nil { 56 return &errMountConfig{mnt, err} 57 } 58 59 if err := linuxValidateAbsolute(mnt.Target); err != nil { 60 return &errMountConfig{mnt, err} 61 } 62 63 switch mnt.Type { 64 case mount.TypeBind: 65 if len(mnt.Source) == 0 { 66 return &errMountConfig{mnt, errMissingField("Source")} 67 } 68 // Don't error out just because the propagation mode is not supported on the platform 69 if opts := mnt.BindOptions; opts != nil { 70 if len(opts.Propagation) > 0 && len(linuxPropagationModes) > 0 { 71 if _, ok := linuxPropagationModes[opts.Propagation]; !ok { 72 return &errMountConfig{mnt, fmt.Errorf("invalid propagation mode: %s", opts.Propagation)} 73 } 74 } 75 } 76 if mnt.VolumeOptions != nil { 77 return &errMountConfig{mnt, errExtraField("VolumeOptions")} 78 } 79 80 if err := linuxValidateAbsolute(mnt.Source); err != nil { 81 return &errMountConfig{mnt, err} 82 } 83 84 if validateBindSourceExists { 85 exists, _, err := currentFileInfoProvider.fileInfo(mnt.Source) 86 if err != nil { 87 return &errMountConfig{mnt, err} 88 } 89 if !exists { 90 return &errMountConfig{mnt, errBindSourceDoesNotExist(mnt.Source)} 91 } 92 } 93 94 case mount.TypeVolume: 95 if mnt.BindOptions != nil { 96 return &errMountConfig{mnt, errExtraField("BindOptions")} 97 } 98 99 if len(mnt.Source) == 0 && mnt.ReadOnly { 100 return &errMountConfig{mnt, fmt.Errorf("must not set ReadOnly mode when using anonymous volumes")} 101 } 102 case mount.TypeTmpfs: 103 if mnt.BindOptions != nil { 104 return &errMountConfig{mnt, errExtraField("BindOptions")} 105 } 106 if len(mnt.Source) != 0 { 107 return &errMountConfig{mnt, errExtraField("Source")} 108 } 109 if _, err := p.ConvertTmpfsOptions(mnt.TmpfsOptions, mnt.ReadOnly); err != nil { 110 return &errMountConfig{mnt, err} 111 } 112 default: 113 return &errMountConfig{mnt, errors.New("mount type unknown")} 114 } 115 return nil 116 } 117 118 // read-write modes 119 var rwModes = map[string]bool{ 120 "rw": true, 121 "ro": true, 122 } 123 124 // label modes 125 var linuxLabelModes = map[string]bool{ 126 "Z": true, 127 "z": true, 128 } 129 130 // consistency modes 131 var linuxConsistencyModes = map[mount.Consistency]bool{ 132 mount.ConsistencyFull: true, 133 mount.ConsistencyCached: true, 134 mount.ConsistencyDelegated: true, 135 } 136 var linuxPropagationModes = map[mount.Propagation]bool{ 137 mount.PropagationPrivate: true, 138 mount.PropagationRPrivate: true, 139 mount.PropagationSlave: true, 140 mount.PropagationRSlave: true, 141 mount.PropagationShared: true, 142 mount.PropagationRShared: true, 143 } 144 145 const linuxDefaultPropagationMode = mount.PropagationRPrivate 146 147 func linuxGetPropagation(mode string) mount.Propagation { 148 for _, o := range strings.Split(mode, ",") { 149 prop := mount.Propagation(o) 150 if linuxPropagationModes[prop] { 151 return prop 152 } 153 } 154 return linuxDefaultPropagationMode 155 } 156 157 func linuxHasPropagation(mode string) bool { 158 for _, o := range strings.Split(mode, ",") { 159 if linuxPropagationModes[mount.Propagation(o)] { 160 return true 161 } 162 } 163 return false 164 } 165 166 func linuxValidMountMode(mode string) bool { 167 if mode == "" { 168 return true 169 } 170 171 rwModeCount := 0 172 labelModeCount := 0 173 propagationModeCount := 0 174 copyModeCount := 0 175 consistencyModeCount := 0 176 177 for _, o := range strings.Split(mode, ",") { 178 switch { 179 case rwModes[o]: 180 rwModeCount++ 181 case linuxLabelModes[o]: 182 labelModeCount++ 183 case linuxPropagationModes[mount.Propagation(o)]: 184 propagationModeCount++ 185 case copyModeExists(o): 186 copyModeCount++ 187 case linuxConsistencyModes[mount.Consistency(o)]: 188 consistencyModeCount++ 189 default: 190 return false 191 } 192 } 193 194 // Only one string for each mode is allowed. 195 if rwModeCount > 1 || labelModeCount > 1 || propagationModeCount > 1 || copyModeCount > 1 || consistencyModeCount > 1 { 196 return false 197 } 198 return true 199 } 200 201 func (p *linuxParser) ReadWrite(mode string) bool { 202 if !linuxValidMountMode(mode) { 203 return false 204 } 205 206 for _, o := range strings.Split(mode, ",") { 207 if o == "ro" { 208 return false 209 } 210 } 211 return true 212 } 213 214 func (p *linuxParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) { 215 arr, err := linuxSplitRawSpec(raw) 216 if err != nil { 217 return nil, err 218 } 219 220 var spec mount.Mount 221 var mode string 222 switch len(arr) { 223 case 1: 224 // Just a destination path in the container 225 spec.Target = arr[0] 226 case 2: 227 if linuxValidMountMode(arr[1]) { 228 // Destination + Mode is not a valid volume - volumes 229 // cannot include a mode. e.g. /foo:rw 230 return nil, errInvalidSpec(raw) 231 } 232 // Host Source Path or Name + Destination 233 spec.Source = arr[0] 234 spec.Target = arr[1] 235 case 3: 236 // HostSourcePath+DestinationPath+Mode 237 spec.Source = arr[0] 238 spec.Target = arr[1] 239 mode = arr[2] 240 default: 241 return nil, errInvalidSpec(raw) 242 } 243 244 if !linuxValidMountMode(mode) { 245 return nil, errInvalidMode(mode) 246 } 247 248 if path.IsAbs(spec.Source) { 249 spec.Type = mount.TypeBind 250 } else { 251 spec.Type = mount.TypeVolume 252 } 253 254 spec.ReadOnly = !p.ReadWrite(mode) 255 256 // cannot assume that if a volume driver is passed in that we should set it 257 if volumeDriver != "" && spec.Type == mount.TypeVolume { 258 spec.VolumeOptions = &mount.VolumeOptions{ 259 DriverConfig: &mount.Driver{Name: volumeDriver}, 260 } 261 } 262 263 if copyData, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet { 264 if spec.VolumeOptions == nil { 265 spec.VolumeOptions = &mount.VolumeOptions{} 266 } 267 spec.VolumeOptions.NoCopy = !copyData 268 } 269 if linuxHasPropagation(mode) { 270 spec.BindOptions = &mount.BindOptions{ 271 Propagation: linuxGetPropagation(mode), 272 } 273 } 274 275 mp, err := p.parseMountSpec(spec, false) 276 if mp != nil { 277 mp.Mode = mode 278 } 279 if err != nil { 280 err = fmt.Errorf("%v: %v", errInvalidSpec(raw), err) 281 } 282 return mp, err 283 } 284 func (p *linuxParser) ParseMountSpec(cfg mount.Mount) (*MountPoint, error) { 285 return p.parseMountSpec(cfg, true) 286 } 287 func (p *linuxParser) parseMountSpec(cfg mount.Mount, validateBindSourceExists bool) (*MountPoint, error) { 288 if err := p.validateMountConfigImpl(&cfg, validateBindSourceExists); err != nil { 289 return nil, err 290 } 291 mp := &MountPoint{ 292 RW: !cfg.ReadOnly, 293 Destination: path.Clean(filepath.ToSlash(cfg.Target)), 294 Type: cfg.Type, 295 Spec: cfg, 296 } 297 298 switch cfg.Type { 299 case mount.TypeVolume: 300 if cfg.Source == "" { 301 mp.Name = stringid.GenerateRandomID() 302 } else { 303 mp.Name = cfg.Source 304 } 305 mp.CopyData = p.DefaultCopyMode() 306 307 if cfg.VolumeOptions != nil { 308 if cfg.VolumeOptions.DriverConfig != nil { 309 mp.Driver = cfg.VolumeOptions.DriverConfig.Name 310 } 311 if cfg.VolumeOptions.NoCopy { 312 mp.CopyData = false 313 } 314 } 315 case mount.TypeBind: 316 mp.Source = path.Clean(filepath.ToSlash(cfg.Source)) 317 if cfg.BindOptions != nil && len(cfg.BindOptions.Propagation) > 0 { 318 mp.Propagation = cfg.BindOptions.Propagation 319 } else { 320 // If user did not specify a propagation mode, get 321 // default propagation mode. 322 mp.Propagation = linuxDefaultPropagationMode 323 } 324 case mount.TypeTmpfs: 325 // NOP 326 } 327 return mp, nil 328 } 329 330 func (p *linuxParser) ParseVolumesFrom(spec string) (string, string, error) { 331 if len(spec) == 0 { 332 return "", "", fmt.Errorf("volumes-from specification cannot be an empty string") 333 } 334 335 specParts := strings.SplitN(spec, ":", 2) 336 id := specParts[0] 337 mode := "rw" 338 339 if len(specParts) == 2 { 340 mode = specParts[1] 341 if !linuxValidMountMode(mode) { 342 return "", "", errInvalidMode(mode) 343 } 344 // For now don't allow propagation properties while importing 345 // volumes from data container. These volumes will inherit 346 // the same propagation property as of the original volume 347 // in data container. This probably can be relaxed in future. 348 if linuxHasPropagation(mode) { 349 return "", "", errInvalidMode(mode) 350 } 351 // Do not allow copy modes on volumes-from 352 if _, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet { 353 return "", "", errInvalidMode(mode) 354 } 355 } 356 return id, mode, nil 357 } 358 359 func (p *linuxParser) DefaultPropagationMode() mount.Propagation { 360 return linuxDefaultPropagationMode 361 } 362 363 func (p *linuxParser) ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error) { 364 var rawOpts []string 365 if readOnly { 366 rawOpts = append(rawOpts, "ro") 367 } 368 369 if opt != nil && opt.Mode != 0 { 370 rawOpts = append(rawOpts, fmt.Sprintf("mode=%o", opt.Mode)) 371 } 372 373 if opt != nil && opt.SizeBytes != 0 { 374 // calculate suffix here, making this linux specific, but that is 375 // okay, since API is that way anyways. 376 377 // we do this by finding the suffix that divides evenly into the 378 // value, returning the value itself, with no suffix, if it fails. 379 // 380 // For the most part, we don't enforce any semantic to this values. 381 // The operating system will usually align this and enforce minimum 382 // and maximums. 383 var ( 384 size = opt.SizeBytes 385 suffix string 386 ) 387 for _, r := range []struct { 388 suffix string 389 divisor int64 390 }{ 391 {"g", 1 << 30}, 392 {"m", 1 << 20}, 393 {"k", 1 << 10}, 394 } { 395 if size%r.divisor == 0 { 396 size = size / r.divisor 397 suffix = r.suffix 398 break 399 } 400 } 401 402 rawOpts = append(rawOpts, fmt.Sprintf("size=%d%s", size, suffix)) 403 } 404 return strings.Join(rawOpts, ","), nil 405 } 406 407 func (p *linuxParser) DefaultCopyMode() bool { 408 return true 409 } 410 func (p *linuxParser) ValidateVolumeName(name string) error { 411 return nil 412 } 413 414 func (p *linuxParser) IsBackwardCompatible(m *MountPoint) bool { 415 return len(m.Source) > 0 || m.Driver == volume.DefaultDriverName 416 } 417 418 func (p *linuxParser) ValidateTmpfsMountDestination(dest string) error { 419 if err := linuxValidateNotRoot(dest); err != nil { 420 return err 421 } 422 return linuxValidateAbsolute(dest) 423 }