github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/specgenutil/volumes.go (about) 1 package specgenutil 2 3 import ( 4 "encoding/csv" 5 "fmt" 6 "path" 7 "strings" 8 9 "github.com/containers/common/pkg/parse" 10 "github.com/hanks177/podman/v4/libpod/define" 11 "github.com/hanks177/podman/v4/pkg/specgen" 12 "github.com/hanks177/podman/v4/pkg/util" 13 spec "github.com/opencontainers/runtime-spec/specs-go" 14 "github.com/pkg/errors" 15 ) 16 17 var ( 18 errDuplicateDest = errors.Errorf("duplicate mount destination") 19 optionArgError = errors.Errorf("must provide an argument for option") 20 noDestError = errors.Errorf("must set volume destination") 21 errInvalidSyntax = errors.Errorf("incorrect mount format: should be --mount type=<bind|tmpfs|volume>,[src=<host-dir|volume-name>,]target=<ctr-dir>[,options]") 22 ) 23 24 // Parse all volume-related options in the create config into a set of mounts 25 // and named volumes to add to the container. 26 // Handles --volumes, --mount, and --tmpfs flags. 27 // Does not handle image volumes, init, and --volumes-from flags. 28 // Can also add tmpfs mounts from read-only tmpfs. 29 // TODO: handle options parsing/processing via containers/storage/pkg/mount 30 func parseVolumes(volumeFlag, mountFlag, tmpfsFlag []string, addReadOnlyTmpfs bool) ([]spec.Mount, []*specgen.NamedVolume, []*specgen.OverlayVolume, []*specgen.ImageVolume, error) { 31 // Get mounts from the --mounts flag. 32 unifiedMounts, unifiedVolumes, unifiedImageVolumes, err := Mounts(mountFlag) 33 if err != nil { 34 return nil, nil, nil, nil, err 35 } 36 37 // Next --volumes flag. 38 volumeMounts, volumeVolumes, overlayVolumes, err := specgen.GenVolumeMounts(volumeFlag) 39 if err != nil { 40 return nil, nil, nil, nil, err 41 } 42 43 // Next --tmpfs flag. 44 tmpfsMounts, err := getTmpfsMounts(tmpfsFlag) 45 if err != nil { 46 return nil, nil, nil, nil, err 47 } 48 49 // Unify mounts from --mount, --volume, --tmpfs. 50 // Start with --volume. 51 for dest, mount := range volumeMounts { 52 if _, ok := unifiedMounts[dest]; ok { 53 return nil, nil, nil, nil, errors.Wrapf(errDuplicateDest, dest) 54 } 55 unifiedMounts[dest] = mount 56 } 57 for dest, volume := range volumeVolumes { 58 if _, ok := unifiedVolumes[dest]; ok { 59 return nil, nil, nil, nil, errors.Wrapf(errDuplicateDest, dest) 60 } 61 unifiedVolumes[dest] = volume 62 } 63 // Now --tmpfs 64 for dest, tmpfs := range tmpfsMounts { 65 if _, ok := unifiedMounts[dest]; ok { 66 return nil, nil, nil, nil, errors.Wrapf(errDuplicateDest, dest) 67 } 68 unifiedMounts[dest] = tmpfs 69 } 70 71 // If requested, add tmpfs filesystems for read-only containers. 72 if addReadOnlyTmpfs { 73 readonlyTmpfs := []string{"/tmp", "/var/tmp", "/run"} 74 options := []string{"rw", "rprivate", "nosuid", "nodev", "tmpcopyup"} 75 for _, dest := range readonlyTmpfs { 76 if _, ok := unifiedMounts[dest]; ok { 77 continue 78 } 79 if _, ok := unifiedVolumes[dest]; ok { 80 continue 81 } 82 unifiedMounts[dest] = spec.Mount{ 83 Destination: dest, 84 Type: define.TypeTmpfs, 85 Source: "tmpfs", 86 Options: options, 87 } 88 } 89 } 90 91 // Check for conflicts between named volumes, overlay & image volumes, 92 // and mounts 93 allMounts := make(map[string]bool) 94 testAndSet := func(dest string) error { 95 if _, ok := allMounts[dest]; ok { 96 return errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) 97 } 98 allMounts[dest] = true 99 return nil 100 } 101 for dest := range unifiedMounts { 102 if err := testAndSet(dest); err != nil { 103 return nil, nil, nil, nil, err 104 } 105 } 106 for dest := range unifiedVolumes { 107 if err := testAndSet(dest); err != nil { 108 return nil, nil, nil, nil, err 109 } 110 } 111 for dest := range overlayVolumes { 112 if err := testAndSet(dest); err != nil { 113 return nil, nil, nil, nil, err 114 } 115 } 116 for dest := range unifiedImageVolumes { 117 if err := testAndSet(dest); err != nil { 118 return nil, nil, nil, nil, err 119 } 120 } 121 122 // Final step: maps to arrays 123 finalMounts := make([]spec.Mount, 0, len(unifiedMounts)) 124 for _, mount := range unifiedMounts { 125 if mount.Type == define.TypeBind { 126 absSrc, err := specgen.ConvertWinMountPath(mount.Source) 127 if err != nil { 128 return nil, nil, nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source) 129 } 130 mount.Source = absSrc 131 } 132 finalMounts = append(finalMounts, mount) 133 } 134 finalVolumes := make([]*specgen.NamedVolume, 0, len(unifiedVolumes)) 135 for _, volume := range unifiedVolumes { 136 finalVolumes = append(finalVolumes, volume) 137 } 138 finalOverlayVolume := make([]*specgen.OverlayVolume, 0) 139 for _, volume := range overlayVolumes { 140 finalOverlayVolume = append(finalOverlayVolume, volume) 141 } 142 finalImageVolumes := make([]*specgen.ImageVolume, 0, len(unifiedImageVolumes)) 143 for _, volume := range unifiedImageVolumes { 144 finalImageVolumes = append(finalImageVolumes, volume) 145 } 146 147 return finalMounts, finalVolumes, finalOverlayVolume, finalImageVolumes, nil 148 } 149 150 // findMountType parses the input and extracts the type of the mount type and 151 // the remaining non-type tokens. 152 func findMountType(input string) (mountType string, tokens []string, err error) { 153 // Split by comma, iterate over the slice and look for 154 // "type=$mountType". Everything else is appended to tokens. 155 found := false 156 csvReader := csv.NewReader(strings.NewReader(input)) 157 records, err := csvReader.ReadAll() 158 if err != nil { 159 return "", nil, err 160 } 161 if len(records) != 1 { 162 return "", nil, errInvalidSyntax 163 } 164 for _, s := range records[0] { 165 kv := strings.Split(s, "=") 166 if found || !(len(kv) == 2 && kv[0] == "type") { 167 tokens = append(tokens, s) 168 continue 169 } 170 mountType = kv[1] 171 found = true 172 } 173 if !found { 174 err = errInvalidSyntax 175 } 176 return 177 } 178 179 // Mounts takes user-provided input from the --mount flag and creates OCI 180 // spec mounts and Libpod named volumes. 181 // podman run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ... 182 // podman run --mount type=tmpfs,target=/dev/shm ... 183 // podman run --mount type=volume,source=test-volume, ... 184 func Mounts(mountFlag []string) (map[string]spec.Mount, map[string]*specgen.NamedVolume, map[string]*specgen.ImageVolume, error) { 185 finalMounts := make(map[string]spec.Mount) 186 finalNamedVolumes := make(map[string]*specgen.NamedVolume) 187 finalImageVolumes := make(map[string]*specgen.ImageVolume) 188 189 for _, mount := range mountFlag { 190 // TODO: Docker defaults to "volume" if no mount type is specified. 191 mountType, tokens, err := findMountType(mount) 192 if err != nil { 193 return nil, nil, nil, err 194 } 195 switch mountType { 196 case define.TypeBind: 197 mount, err := getBindMount(tokens) 198 if err != nil { 199 return nil, nil, nil, err 200 } 201 if _, ok := finalMounts[mount.Destination]; ok { 202 return nil, nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) 203 } 204 finalMounts[mount.Destination] = mount 205 case define.TypeTmpfs: 206 mount, err := getTmpfsMount(tokens) 207 if err != nil { 208 return nil, nil, nil, err 209 } 210 if _, ok := finalMounts[mount.Destination]; ok { 211 return nil, nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) 212 } 213 finalMounts[mount.Destination] = mount 214 case define.TypeDevpts: 215 mount, err := getDevptsMount(tokens) 216 if err != nil { 217 return nil, nil, nil, err 218 } 219 if _, ok := finalMounts[mount.Destination]; ok { 220 return nil, nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) 221 } 222 finalMounts[mount.Destination] = mount 223 case "image": 224 volume, err := getImageVolume(tokens) 225 if err != nil { 226 return nil, nil, nil, err 227 } 228 if _, ok := finalImageVolumes[volume.Destination]; ok { 229 return nil, nil, nil, errors.Wrapf(errDuplicateDest, volume.Destination) 230 } 231 finalImageVolumes[volume.Destination] = volume 232 case "volume": 233 volume, err := getNamedVolume(tokens) 234 if err != nil { 235 return nil, nil, nil, err 236 } 237 if _, ok := finalNamedVolumes[volume.Dest]; ok { 238 return nil, nil, nil, errors.Wrapf(errDuplicateDest, volume.Dest) 239 } 240 finalNamedVolumes[volume.Dest] = volume 241 default: 242 return nil, nil, nil, errors.Errorf("invalid filesystem type %q", mountType) 243 } 244 } 245 246 return finalMounts, finalNamedVolumes, finalImageVolumes, nil 247 } 248 249 // Parse a single bind mount entry from the --mount flag. 250 func getBindMount(args []string) (spec.Mount, error) { 251 newMount := spec.Mount{ 252 Type: define.TypeBind, 253 } 254 255 var setSource, setDest, setRORW, setSuid, setDev, setExec, setRelabel, setOwnership bool 256 257 for _, val := range args { 258 kv := strings.SplitN(val, "=", 2) 259 switch kv[0] { 260 case "bind-nonrecursive": 261 newMount.Options = append(newMount.Options, "bind") 262 case "readonly", "ro", "rw": 263 if setRORW { 264 return newMount, errors.Wrapf(optionArgError, "cannot pass 'readonly', 'ro', or 'rw' options more than once") 265 } 266 setRORW = true 267 // Can be formatted as one of: 268 // readonly 269 // readonly=[true|false] 270 // ro 271 // ro=[true|false] 272 // rw 273 // rw=[true|false] 274 if kv[0] == "readonly" { 275 kv[0] = "ro" 276 } 277 switch len(kv) { 278 case 1: 279 newMount.Options = append(newMount.Options, kv[0]) 280 case 2: 281 switch strings.ToLower(kv[1]) { 282 case "true": 283 newMount.Options = append(newMount.Options, kv[0]) 284 case "false": 285 // Set the opposite only for rw 286 // ro's opposite is the default 287 if kv[0] == "rw" { 288 newMount.Options = append(newMount.Options, "ro") 289 } 290 default: 291 return newMount, errors.Wrapf(optionArgError, "'readonly', 'ro', or 'rw' must be set to true or false, instead received %q", kv[1]) 292 } 293 default: 294 return newMount, errors.Wrapf(optionArgError, "badly formatted option %q", val) 295 } 296 case "nosuid", "suid": 297 if setSuid { 298 return newMount, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once") 299 } 300 setSuid = true 301 newMount.Options = append(newMount.Options, kv[0]) 302 case "nodev", "dev": 303 if setDev { 304 return newMount, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once") 305 } 306 setDev = true 307 newMount.Options = append(newMount.Options, kv[0]) 308 case "noexec", "exec": 309 if setExec { 310 return newMount, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once") 311 } 312 setExec = true 313 newMount.Options = append(newMount.Options, kv[0]) 314 case "shared", "rshared", "private", "rprivate", "slave", "rslave", "unbindable", "runbindable", "Z", "z": 315 newMount.Options = append(newMount.Options, kv[0]) 316 case "bind-propagation": 317 if len(kv) == 1 { 318 return newMount, errors.Wrapf(optionArgError, kv[0]) 319 } 320 newMount.Options = append(newMount.Options, kv[1]) 321 case "src", "source": 322 if len(kv) == 1 { 323 return newMount, errors.Wrapf(optionArgError, kv[0]) 324 } 325 if len(kv[1]) == 0 { 326 return newMount, errors.Wrapf(optionArgError, "host directory cannot be empty") 327 } 328 newMount.Source = kv[1] 329 setSource = true 330 case "target", "dst", "destination": 331 if len(kv) == 1 { 332 return newMount, errors.Wrapf(optionArgError, kv[0]) 333 } 334 if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { 335 return newMount, err 336 } 337 newMount.Destination = unixPathClean(kv[1]) 338 setDest = true 339 case "relabel": 340 if setRelabel { 341 return newMount, errors.Wrapf(optionArgError, "cannot pass 'relabel' option more than once") 342 } 343 setRelabel = true 344 if len(kv) != 2 { 345 return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0]) 346 } 347 switch kv[1] { 348 case "private": 349 newMount.Options = append(newMount.Options, "Z") 350 case "shared": 351 newMount.Options = append(newMount.Options, "z") 352 default: 353 return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0]) 354 } 355 case "U", "chown": 356 if setOwnership { 357 return newMount, errors.Wrapf(optionArgError, "cannot pass 'U' or 'chown' option more than once") 358 } 359 ok, err := validChownFlag(val) 360 if err != nil { 361 return newMount, err 362 } 363 if ok { 364 newMount.Options = append(newMount.Options, "U") 365 } 366 setOwnership = true 367 case "idmap": 368 if len(kv) > 1 { 369 newMount.Options = append(newMount.Options, fmt.Sprintf("idmap=%s", kv[1])) 370 } else { 371 newMount.Options = append(newMount.Options, "idmap") 372 } 373 case "consistency": 374 // Often used on MACs and mistakenly on Linux platforms. 375 // Since Docker ignores this option so shall we. 376 continue 377 default: 378 return newMount, errors.Wrapf(util.ErrBadMntOption, "%s", kv[0]) 379 } 380 } 381 382 if !setDest { 383 return newMount, noDestError 384 } 385 386 if !setSource { 387 newMount.Source = newMount.Destination 388 } 389 390 options, err := parse.ValidateVolumeOpts(newMount.Options) 391 if err != nil { 392 return newMount, err 393 } 394 newMount.Options = options 395 return newMount, nil 396 } 397 398 // Parse a single tmpfs mount entry from the --mount flag 399 func getTmpfsMount(args []string) (spec.Mount, error) { 400 newMount := spec.Mount{ 401 Type: define.TypeTmpfs, 402 Source: define.TypeTmpfs, 403 } 404 405 var setDest, setRORW, setSuid, setDev, setExec, setTmpcopyup, setOwnership bool 406 407 for _, val := range args { 408 kv := strings.SplitN(val, "=", 2) 409 switch kv[0] { 410 case "tmpcopyup", "notmpcopyup": 411 if setTmpcopyup { 412 return newMount, errors.Wrapf(optionArgError, "cannot pass 'tmpcopyup' and 'notmpcopyup' options more than once") 413 } 414 setTmpcopyup = true 415 newMount.Options = append(newMount.Options, kv[0]) 416 case "ro", "rw": 417 if setRORW { 418 return newMount, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once") 419 } 420 setRORW = true 421 newMount.Options = append(newMount.Options, kv[0]) 422 case "nosuid", "suid": 423 if setSuid { 424 return newMount, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once") 425 } 426 setSuid = true 427 newMount.Options = append(newMount.Options, kv[0]) 428 case "nodev", "dev": 429 if setDev { 430 return newMount, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once") 431 } 432 setDev = true 433 newMount.Options = append(newMount.Options, kv[0]) 434 case "noexec", "exec": 435 if setExec { 436 return newMount, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once") 437 } 438 setExec = true 439 newMount.Options = append(newMount.Options, kv[0]) 440 case "tmpfs-mode": 441 if len(kv) == 1 { 442 return newMount, errors.Wrapf(optionArgError, kv[0]) 443 } 444 newMount.Options = append(newMount.Options, fmt.Sprintf("mode=%s", kv[1])) 445 case "tmpfs-size": 446 if len(kv) == 1 { 447 return newMount, errors.Wrapf(optionArgError, kv[0]) 448 } 449 newMount.Options = append(newMount.Options, fmt.Sprintf("size=%s", kv[1])) 450 case "src", "source": 451 return newMount, errors.Errorf("source is not supported with tmpfs mounts") 452 case "target", "dst", "destination": 453 if len(kv) == 1 { 454 return newMount, errors.Wrapf(optionArgError, kv[0]) 455 } 456 if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { 457 return newMount, err 458 } 459 newMount.Destination = unixPathClean(kv[1]) 460 setDest = true 461 case "U", "chown": 462 if setOwnership { 463 return newMount, errors.Wrapf(optionArgError, "cannot pass 'U' or 'chown' option more than once") 464 } 465 ok, err := validChownFlag(val) 466 if err != nil { 467 return newMount, err 468 } 469 if ok { 470 newMount.Options = append(newMount.Options, "U") 471 } 472 setOwnership = true 473 case "consistency": 474 // Often used on MACs and mistakenly on Linux platforms. 475 // Since Docker ignores this option so shall we. 476 continue 477 default: 478 return newMount, errors.Wrapf(util.ErrBadMntOption, "%s", kv[0]) 479 } 480 } 481 482 if !setDest { 483 return newMount, noDestError 484 } 485 486 return newMount, nil 487 } 488 489 // Parse a single devpts mount entry from the --mount flag 490 func getDevptsMount(args []string) (spec.Mount, error) { 491 newMount := spec.Mount{ 492 Type: define.TypeDevpts, 493 Source: define.TypeDevpts, 494 } 495 496 var setDest bool 497 498 for _, val := range args { 499 kv := strings.SplitN(val, "=", 2) 500 switch kv[0] { 501 case "uid", "gid", "mode", "ptxmode", "newinstance", "max": 502 newMount.Options = append(newMount.Options, val) 503 case "target", "dst", "destination": 504 if len(kv) == 1 { 505 return newMount, errors.Wrapf(optionArgError, kv[0]) 506 } 507 if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { 508 return newMount, err 509 } 510 newMount.Destination = unixPathClean(kv[1]) 511 setDest = true 512 default: 513 return newMount, errors.Wrapf(util.ErrBadMntOption, "%s", kv[0]) 514 } 515 } 516 517 if !setDest { 518 return newMount, noDestError 519 } 520 521 return newMount, nil 522 } 523 524 // Parse a single volume mount entry from the --mount flag. 525 // Note that the volume-label option for named volumes is currently NOT supported. 526 // TODO: add support for --volume-label 527 func getNamedVolume(args []string) (*specgen.NamedVolume, error) { 528 newVolume := new(specgen.NamedVolume) 529 530 var setDest, setRORW, setSuid, setDev, setExec, setOwnership bool 531 532 for _, val := range args { 533 kv := strings.SplitN(val, "=", 2) 534 switch kv[0] { 535 case "volume-opt": 536 newVolume.Options = append(newVolume.Options, val) 537 case "ro", "rw": 538 if setRORW { 539 return nil, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once") 540 } 541 setRORW = true 542 newVolume.Options = append(newVolume.Options, kv[0]) 543 case "nosuid", "suid": 544 if setSuid { 545 return nil, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once") 546 } 547 setSuid = true 548 newVolume.Options = append(newVolume.Options, kv[0]) 549 case "nodev", "dev": 550 if setDev { 551 return nil, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once") 552 } 553 setDev = true 554 newVolume.Options = append(newVolume.Options, kv[0]) 555 case "noexec", "exec": 556 if setExec { 557 return nil, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once") 558 } 559 setExec = true 560 newVolume.Options = append(newVolume.Options, kv[0]) 561 case "volume-label": 562 return nil, errors.Errorf("the --volume-label option is not presently implemented") 563 case "src", "source": 564 if len(kv) == 1 { 565 return nil, errors.Wrapf(optionArgError, kv[0]) 566 } 567 newVolume.Name = kv[1] 568 case "target", "dst", "destination": 569 if len(kv) == 1 { 570 return nil, errors.Wrapf(optionArgError, kv[0]) 571 } 572 if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { 573 return nil, err 574 } 575 newVolume.Dest = unixPathClean(kv[1]) 576 setDest = true 577 case "U", "chown": 578 if setOwnership { 579 return newVolume, errors.Wrapf(optionArgError, "cannot pass 'U' or 'chown' option more than once") 580 } 581 ok, err := validChownFlag(val) 582 if err != nil { 583 return newVolume, err 584 } 585 if ok { 586 newVolume.Options = append(newVolume.Options, "U") 587 } 588 setOwnership = true 589 case "consistency": 590 // Often used on MACs and mistakenly on Linux platforms. 591 // Since Docker ignores this option so shall we. 592 continue 593 default: 594 return nil, errors.Wrapf(util.ErrBadMntOption, "%s", kv[0]) 595 } 596 } 597 598 if !setDest { 599 return nil, noDestError 600 } 601 602 return newVolume, nil 603 } 604 605 // Parse the arguments into an image volume. An image volume is a volume based 606 // on a container image. The container image is first mounted on the host and 607 // is then bind-mounted into the container. An ImageVolume is always mounted 608 // read only. 609 func getImageVolume(args []string) (*specgen.ImageVolume, error) { 610 newVolume := new(specgen.ImageVolume) 611 612 for _, val := range args { 613 kv := strings.SplitN(val, "=", 2) 614 switch kv[0] { 615 case "src", "source": 616 if len(kv) == 1 { 617 return nil, errors.Wrapf(optionArgError, kv[0]) 618 } 619 newVolume.Source = kv[1] 620 case "target", "dst", "destination": 621 if len(kv) == 1 { 622 return nil, errors.Wrapf(optionArgError, kv[0]) 623 } 624 if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { 625 return nil, err 626 } 627 newVolume.Destination = unixPathClean(kv[1]) 628 case "rw", "readwrite": 629 switch kv[1] { 630 case "true": 631 newVolume.ReadWrite = true 632 case "false": 633 // Nothing to do. RO is default. 634 default: 635 return nil, errors.Wrapf(util.ErrBadMntOption, "invalid rw value %q", kv[1]) 636 } 637 case "consistency": 638 // Often used on MACs and mistakenly on Linux platforms. 639 // Since Docker ignores this option so shall we. 640 continue 641 default: 642 return nil, errors.Wrapf(util.ErrBadMntOption, "%s", kv[0]) 643 } 644 } 645 646 if len(newVolume.Source)*len(newVolume.Destination) == 0 { 647 return nil, errors.Errorf("must set source and destination for image volume") 648 } 649 650 return newVolume, nil 651 } 652 653 // GetTmpfsMounts creates spec.Mount structs for user-requested tmpfs mounts 654 func getTmpfsMounts(tmpfsFlag []string) (map[string]spec.Mount, error) { 655 m := make(map[string]spec.Mount) 656 for _, i := range tmpfsFlag { 657 // Default options if nothing passed 658 var options []string 659 spliti := strings.Split(i, ":") 660 destPath := spliti[0] 661 if err := parse.ValidateVolumeCtrDir(spliti[0]); err != nil { 662 return nil, err 663 } 664 if len(spliti) > 1 { 665 options = strings.Split(spliti[1], ",") 666 } 667 668 if _, ok := m[destPath]; ok { 669 return nil, errors.Wrapf(errDuplicateDest, destPath) 670 } 671 672 mount := spec.Mount{ 673 Destination: unixPathClean(destPath), 674 Type: define.TypeTmpfs, 675 Options: options, 676 Source: define.TypeTmpfs, 677 } 678 m[destPath] = mount 679 } 680 return m, nil 681 } 682 683 // validChownFlag ensures that the U or chown flag is correctly used 684 func validChownFlag(flag string) (bool, error) { 685 kv := strings.SplitN(flag, "=", 2) 686 switch len(kv) { 687 case 1: 688 case 2: 689 // U=[true|false] 690 switch strings.ToLower(kv[1]) { 691 case "true": 692 case "false": 693 return false, nil 694 default: 695 return false, errors.Wrapf(optionArgError, "'U' or 'chown' must be set to true or false, instead received %q", kv[1]) 696 } 697 default: 698 return false, errors.Wrapf(optionArgError, "badly formatted option %q", flag) 699 } 700 701 return true, nil 702 } 703 704 // Use path instead of filepath to preserve Unix style paths on Windows 705 func unixPathClean(p string) string { 706 return path.Clean(p) 707 }