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