github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/pkg/specgen/generate/storage.go (about) 1 package generate 2 3 //nolint 4 5 import ( 6 "fmt" 7 "path" 8 "path/filepath" 9 "strings" 10 11 "github.com/containers/buildah/pkg/parse" 12 "github.com/containers/libpod/libpod" 13 "github.com/containers/libpod/pkg/specgen" 14 "github.com/containers/libpod/pkg/util" 15 spec "github.com/opencontainers/runtime-spec/specs-go" 16 "github.com/pkg/errors" 17 "github.com/sirupsen/logrus" 18 ) 19 20 const ( 21 // TypeBind is the type for mounting host dir 22 TypeBind = "bind" 23 // TypeVolume is the type for named volumes 24 TypeVolume = "volume" 25 // TypeTmpfs is the type for mounting tmpfs 26 TypeTmpfs = "tmpfs" 27 ) 28 29 var ( 30 errDuplicateDest = errors.Errorf("duplicate mount destination") //nolint 31 optionArgError = errors.Errorf("must provide an argument for option") //nolint 32 noDestError = errors.Errorf("must set volume destination") //nolint 33 ) 34 35 // Parse all volume-related options in the create config into a set of mounts 36 // and named volumes to add to the container. 37 // Handles --volumes-from, --volumes, --tmpfs, --init, and --init-path flags. 38 // TODO: Named volume options - should we default to rprivate? It bakes into a 39 // bind mount under the hood... 40 // TODO: handle options parsing/processing via containers/storage/pkg/mount 41 func parseVolumes(s *specgen.SpecGenerator, mounts, volMounts, tmpMounts []string) error { //nolint 42 43 // TODO this needs to come from the image and erquires a runtime 44 45 // Add image volumes. 46 //baseMounts, baseVolumes, err := config.getImageVolumes() 47 //if err != nil { 48 // return nil, nil, err 49 //} 50 51 // Add --volumes-from. 52 // Overrides image volumes unconditionally. 53 //vFromMounts, vFromVolumes, err := config.getVolumesFrom(runtime) 54 //if err != nil { 55 // return nil, nil, err 56 //} 57 //for dest, mount := range vFromMounts { 58 // baseMounts[dest] = mount 59 //} 60 //for dest, volume := range vFromVolumes { 61 // baseVolumes[dest] = volume 62 //} 63 64 // Next mounts from the --mounts flag. 65 // Do not override yet. 66 //unifiedMounts, _, err := getMounts(mounts) 67 //if err != nil { 68 // return err 69 //} 70 // 71 //// Next --volumes flag. 72 //// Do not override yet. 73 //volumeMounts, _ , err := getVolumeMounts(volMounts) 74 //if err != nil { 75 // return err 76 //} 77 // 78 //// Next --tmpfs flag. 79 //// Do not override yet. 80 //tmpfsMounts, err := getTmpfsMounts(tmpMounts) 81 //if err != nil { 82 // return err 83 //} 84 85 //// Unify mounts from --mount, --volume, --tmpfs. 86 //// Also add mounts + volumes directly from createconfig. 87 //// Start with --volume. 88 //for dest, mount := range volumeMounts { 89 // if _, ok := unifiedMounts[dest]; ok { 90 // return nil, nil, errors.Wrapf(errDuplicateDest, dest) 91 // } 92 // unifiedMounts[dest] = mount 93 //} 94 //for dest, volume := range volumeVolumes { 95 // if _, ok := unifiedVolumes[dest]; ok { 96 // return nil, nil, errors.Wrapf(errDuplicateDest, dest) 97 // } 98 // unifiedVolumes[dest] = volume 99 //} 100 //// Now --tmpfs 101 //for dest, tmpfs := range tmpfsMounts { 102 // if _, ok := unifiedMounts[dest]; ok { 103 // return nil, nil, errors.Wrapf(errDuplicateDest, dest) 104 // } 105 // unifiedMounts[dest] = tmpfs 106 //} 107 //// Now spec mounts and volumes 108 //for _, mount := range config.Mounts { 109 // dest := mount.Destination 110 // if _, ok := unifiedMounts[dest]; ok { 111 // return nil, nil, errors.Wrapf(errDuplicateDest, dest) 112 // } 113 // unifiedMounts[dest] = mount 114 //} 115 //for _, volume := range config.NamedVolumes { 116 // dest := volume.Dest 117 // if _, ok := unifiedVolumes[dest]; ok { 118 // return nil, nil, errors.Wrapf(errDuplicateDest, dest) 119 // } 120 // unifiedVolumes[dest] = volume 121 //} 122 // 123 //// If requested, add container init binary 124 //if config.Init { 125 // initPath := config.InitPath 126 // if initPath == "" { 127 // rtc, err := runtime.GetConfig() 128 // if err != nil { 129 // return nil, nil, err 130 // } 131 // initPath = rtc.Engine.InitPath 132 // } 133 // initMount, err := config.addContainerInitBinary(initPath) 134 // if err != nil { 135 // return nil, nil, err 136 // } 137 // if _, ok := unifiedMounts[initMount.Destination]; ok { 138 // return nil, nil, errors.Wrapf(errDuplicateDest, "conflict with mount added by --init to %q", initMount.Destination) 139 // } 140 // unifiedMounts[initMount.Destination] = initMount 141 //} 142 // 143 //// Before superseding, we need to find volume mounts which conflict with 144 //// named volumes, and vice versa. 145 //// We'll delete the conflicts here as we supersede. 146 //for dest := range unifiedMounts { 147 // if _, ok := baseVolumes[dest]; ok { 148 // delete(baseVolumes, dest) 149 // } 150 //} 151 //for dest := range unifiedVolumes { 152 // if _, ok := baseMounts[dest]; ok { 153 // delete(baseMounts, dest) 154 // } 155 //} 156 // 157 //// Supersede volumes-from/image volumes with unified volumes from above. 158 //// This is an unconditional replacement. 159 //for dest, mount := range unifiedMounts { 160 // baseMounts[dest] = mount 161 //} 162 //for dest, volume := range unifiedVolumes { 163 // baseVolumes[dest] = volume 164 //} 165 // 166 //// If requested, add tmpfs filesystems for read-only containers. 167 //if config.Security.ReadOnlyRootfs && config.Security.ReadOnlyTmpfs { 168 // readonlyTmpfs := []string{"/tmp", "/var/tmp", "/run"} 169 // options := []string{"rw", "rprivate", "nosuid", "nodev", "tmpcopyup"} 170 // for _, dest := range readonlyTmpfs { 171 // if _, ok := baseMounts[dest]; ok { 172 // continue 173 // } 174 // if _, ok := baseVolumes[dest]; ok { 175 // continue 176 // } 177 // localOpts := options 178 // if dest == "/run" { 179 // localOpts = append(localOpts, "noexec", "size=65536k") 180 // } else { 181 // localOpts = append(localOpts, "exec") 182 // } 183 // baseMounts[dest] = spec.Mount{ 184 // Destination: dest, 185 // Type: "tmpfs", 186 // Source: "tmpfs", 187 // Options: localOpts, 188 // } 189 // } 190 //} 191 // 192 //// Check for conflicts between named volumes and mounts 193 //for dest := range baseMounts { 194 // if _, ok := baseVolumes[dest]; ok { 195 // return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) 196 // } 197 //} 198 //for dest := range baseVolumes { 199 // if _, ok := baseMounts[dest]; ok { 200 // return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) 201 // } 202 //} 203 // 204 //// Final step: maps to arrays 205 //finalMounts := make([]spec.Mount, 0, len(baseMounts)) 206 //for _, mount := range baseMounts { 207 // if mount.Type == TypeBind { 208 // absSrc, err := filepath.Abs(mount.Source) 209 // if err != nil { 210 // return nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source) 211 // } 212 // mount.Source = absSrc 213 // } 214 // finalMounts = append(finalMounts, mount) 215 //} 216 //finalVolumes := make([]*define.ContainerNamedVolume, 0, len(baseVolumes)) 217 //for _, volume := range baseVolumes { 218 // finalVolumes = append(finalVolumes, volume) 219 //} 220 221 //return finalMounts, finalVolumes, nil 222 return nil 223 } 224 225 // Parse volumes from - a set of containers whose volumes we will mount in. 226 // Grab the containers, retrieve any user-created spec mounts and all named 227 // volumes, and return a list of them. 228 // Conflicts are resolved simply - the last container specified wins. 229 // Container names may be suffixed by mount options after a colon. 230 // TODO: We should clean these paths if possible 231 // TODO deferred baude 232 func getVolumesFrom() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint 233 // Both of these are maps of mount destination to mount type. 234 // We ensure that each destination is only mounted to once in this way. 235 //finalMounts := make(map[string]spec.Mount) 236 //finalNamedVolumes := make(map[string]*define.ContainerNamedVolume) 237 // 238 //for _, vol := range config.VolumesFrom { 239 // var ( 240 // options = []string{} 241 // err error 242 // splitVol = strings.SplitN(vol, ":", 2) 243 // ) 244 // if len(splitVol) == 2 { 245 // splitOpts := strings.Split(splitVol[1], ",") 246 // for _, checkOpt := range splitOpts { 247 // switch checkOpt { 248 // case "z", "ro", "rw": 249 // // Do nothing, these are valid options 250 // default: 251 // return nil, nil, errors.Errorf("invalid options %q, can only specify 'ro', 'rw', and 'z'", splitVol[1]) 252 // } 253 // } 254 // 255 // if options, err = parse.ValidateVolumeOpts(splitOpts); err != nil { 256 // return nil, nil, err 257 // } 258 // } 259 // ctr, err := runtime.LookupContainer(splitVol[0]) 260 // if err != nil { 261 // return nil, nil, errors.Wrapf(err, "error looking up container %q for volumes-from", splitVol[0]) 262 // } 263 // 264 // logrus.Debugf("Adding volumes from container %s", ctr.ID()) 265 // 266 // // Look up the container's user volumes. This gets us the 267 // // destinations of all mounts the user added to the container. 268 // userVolumesArr := ctr.UserVolumes() 269 // 270 // // We're going to need to access them a lot, so convert to a map 271 // // to reduce looping. 272 // // We'll also use the map to indicate if we missed any volumes along the way. 273 // userVolumes := make(map[string]bool) 274 // for _, dest := range userVolumesArr { 275 // userVolumes[dest] = false 276 // } 277 // 278 // // Now we get the container's spec and loop through its volumes 279 // // and append them in if we can find them. 280 // spec := ctr.Spec() 281 // if spec == nil { 282 // return nil, nil, errors.Errorf("error retrieving container %s spec for volumes-from", ctr.ID()) 283 // } 284 // for _, mnt := range spec.Mounts { 285 // if mnt.Type != TypeBind { 286 // continue 287 // } 288 // if _, exists := userVolumes[mnt.Destination]; exists { 289 // userVolumes[mnt.Destination] = true 290 // 291 // if len(options) != 0 { 292 // mnt.Options = options 293 // } 294 // 295 // if _, ok := finalMounts[mnt.Destination]; ok { 296 // logrus.Debugf("Overriding mount to %s with new mount from container %s", mnt.Destination, ctr.ID()) 297 // } 298 // finalMounts[mnt.Destination] = mnt 299 // } 300 // } 301 // 302 // // We're done with the spec mounts. Add named volumes. 303 // // Add these unconditionally - none of them are automatically 304 // // part of the container, as some spec mounts are. 305 // namedVolumes := ctr.NamedVolumes() 306 // for _, namedVol := range namedVolumes { 307 // if _, exists := userVolumes[namedVol.Dest]; exists { 308 // userVolumes[namedVol.Dest] = true 309 // } 310 // 311 // if len(options) != 0 { 312 // namedVol.Options = options 313 // } 314 // 315 // if _, ok := finalMounts[namedVol.Dest]; ok { 316 // logrus.Debugf("Overriding named volume mount to %s with new named volume from container %s", namedVol.Dest, ctr.ID()) 317 // } 318 // finalNamedVolumes[namedVol.Dest] = namedVol 319 // } 320 // 321 // // Check if we missed any volumes 322 // for volDest, found := range userVolumes { 323 // if !found { 324 // logrus.Warnf("Unable to match volume %s from container %s for volumes-from", volDest, ctr.ID()) 325 // } 326 // } 327 //} 328 // 329 //return finalMounts, finalNamedVolumes, nil 330 return nil, nil, nil 331 } 332 333 // getMounts takes user-provided input from the --mount flag and creates OCI 334 // spec mounts and Libpod named volumes. 335 // podman run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ... 336 // podman run --mount type=tmpfs,target=/dev/shm ... 337 // podman run --mount type=volume,source=test-volume, ... 338 func getMounts(mounts []string) (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint 339 finalMounts := make(map[string]spec.Mount) 340 finalNamedVolumes := make(map[string]*libpod.ContainerNamedVolume) 341 342 errInvalidSyntax := errors.Errorf("incorrect mount format: should be --mount type=<bind|tmpfs|volume>,[src=<host-dir|volume-name>,]target=<ctr-dir>[,options]") 343 344 // TODO(vrothberg): the manual parsing can be replaced with a regular expression 345 // to allow a more robust parsing of the mount format and to give 346 // precise errors regarding supported format versus supported options. 347 for _, mount := range mounts { 348 arr := strings.SplitN(mount, ",", 2) 349 if len(arr) < 2 { 350 return nil, nil, errors.Wrapf(errInvalidSyntax, "%q", mount) 351 } 352 kv := strings.Split(arr[0], "=") 353 // TODO: type is not explicitly required in Docker. 354 // If not specified, it defaults to "volume". 355 if len(kv) != 2 || kv[0] != "type" { 356 return nil, nil, errors.Wrapf(errInvalidSyntax, "%q", mount) 357 } 358 359 tokens := strings.Split(arr[1], ",") 360 switch kv[1] { 361 case TypeBind: 362 mount, err := getBindMount(tokens) 363 if err != nil { 364 return nil, nil, err 365 } 366 if _, ok := finalMounts[mount.Destination]; ok { 367 return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) 368 } 369 finalMounts[mount.Destination] = mount 370 case TypeTmpfs: 371 mount, err := getTmpfsMount(tokens) 372 if err != nil { 373 return nil, nil, err 374 } 375 if _, ok := finalMounts[mount.Destination]; ok { 376 return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) 377 } 378 finalMounts[mount.Destination] = mount 379 case "volume": 380 volume, err := getNamedVolume(tokens) 381 if err != nil { 382 return nil, nil, err 383 } 384 if _, ok := finalNamedVolumes[volume.Dest]; ok { 385 return nil, nil, errors.Wrapf(errDuplicateDest, volume.Dest) 386 } 387 finalNamedVolumes[volume.Dest] = volume 388 default: 389 return nil, nil, errors.Errorf("invalid filesystem type %q", kv[1]) 390 } 391 } 392 393 return finalMounts, finalNamedVolumes, nil 394 } 395 396 // Parse a single bind mount entry from the --mount flag. 397 func getBindMount(args []string) (spec.Mount, error) { //nolint 398 newMount := spec.Mount{ 399 Type: TypeBind, 400 } 401 402 var setSource, setDest, setRORW, setSuid, setDev, setExec, setRelabel bool 403 404 for _, val := range args { 405 kv := strings.Split(val, "=") 406 switch kv[0] { 407 case "bind-nonrecursive": 408 newMount.Options = append(newMount.Options, "bind") 409 case "ro", "rw": 410 if setRORW { 411 return newMount, errors.Wrapf(optionArgError, "cannot pass 'ro' or 'rw' options more than once") 412 } 413 setRORW = true 414 // Can be formatted as one of: 415 // ro 416 // ro=[true|false] 417 // rw 418 // rw=[true|false] 419 switch len(kv) { 420 case 1: 421 newMount.Options = append(newMount.Options, kv[0]) 422 case 2: 423 switch strings.ToLower(kv[1]) { 424 case "true": 425 newMount.Options = append(newMount.Options, kv[0]) 426 case "false": 427 // Set the opposite only for rw 428 // ro's opposite is the default 429 if kv[0] == "rw" { 430 newMount.Options = append(newMount.Options, "ro") 431 } 432 default: 433 return newMount, errors.Wrapf(optionArgError, "%s must be set to true or false, instead received %q", kv[0], kv[1]) 434 } 435 default: 436 return newMount, errors.Wrapf(optionArgError, "badly formatted option %q", val) 437 } 438 case "nosuid", "suid": 439 if setSuid { 440 return newMount, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once") 441 } 442 setSuid = true 443 newMount.Options = append(newMount.Options, kv[0]) 444 case "nodev", "dev": 445 if setDev { 446 return newMount, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once") 447 } 448 setDev = true 449 newMount.Options = append(newMount.Options, kv[0]) 450 case "noexec", "exec": 451 if setExec { 452 return newMount, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once") 453 } 454 setExec = true 455 newMount.Options = append(newMount.Options, kv[0]) 456 case "shared", "rshared", "private", "rprivate", "slave", "rslave", "Z", "z": 457 newMount.Options = append(newMount.Options, kv[0]) 458 case "bind-propagation": 459 if len(kv) == 1 { 460 return newMount, errors.Wrapf(optionArgError, kv[0]) 461 } 462 newMount.Options = append(newMount.Options, kv[1]) 463 case "src", "source": 464 if len(kv) == 1 { 465 return newMount, errors.Wrapf(optionArgError, kv[0]) 466 } 467 if err := parse.ValidateVolumeHostDir(kv[1]); err != nil { 468 return newMount, err 469 } 470 newMount.Source = kv[1] 471 setSource = true 472 case "target", "dst", "destination": 473 if len(kv) == 1 { 474 return newMount, errors.Wrapf(optionArgError, kv[0]) 475 } 476 if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { 477 return newMount, err 478 } 479 newMount.Destination = filepath.Clean(kv[1]) 480 setDest = true 481 case "relabel": 482 if setRelabel { 483 return newMount, errors.Wrapf(optionArgError, "cannot pass 'relabel' option more than once") 484 } 485 setRelabel = true 486 if len(kv) != 2 { 487 return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0]) 488 } 489 switch kv[1] { 490 case "private": 491 newMount.Options = append(newMount.Options, "z") 492 case "shared": 493 newMount.Options = append(newMount.Options, "Z") 494 default: 495 return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0]) 496 } 497 default: 498 return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0]) 499 } 500 } 501 502 if !setDest { 503 return newMount, noDestError 504 } 505 506 if !setSource { 507 newMount.Source = newMount.Destination 508 } 509 510 options, err := parse.ValidateVolumeOpts(newMount.Options) 511 if err != nil { 512 return newMount, err 513 } 514 newMount.Options = options 515 return newMount, nil 516 } 517 518 // Parse a single tmpfs mount entry from the --mount flag 519 func getTmpfsMount(args []string) (spec.Mount, error) { //nolint 520 newMount := spec.Mount{ 521 Type: TypeTmpfs, 522 Source: TypeTmpfs, 523 } 524 525 var setDest, setRORW, setSuid, setDev, setExec, setTmpcopyup bool 526 527 for _, val := range args { 528 kv := strings.Split(val, "=") 529 switch kv[0] { 530 case "tmpcopyup", "notmpcopyup": 531 if setTmpcopyup { 532 return newMount, errors.Wrapf(optionArgError, "cannot pass 'tmpcopyup' and 'notmpcopyup' options more than once") 533 } 534 setTmpcopyup = true 535 newMount.Options = append(newMount.Options, kv[0]) 536 case "ro", "rw": 537 if setRORW { 538 return newMount, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once") 539 } 540 setRORW = true 541 newMount.Options = append(newMount.Options, kv[0]) 542 case "nosuid", "suid": 543 if setSuid { 544 return newMount, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once") 545 } 546 setSuid = true 547 newMount.Options = append(newMount.Options, kv[0]) 548 case "nodev", "dev": 549 if setDev { 550 return newMount, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once") 551 } 552 setDev = true 553 newMount.Options = append(newMount.Options, kv[0]) 554 case "noexec", "exec": 555 if setExec { 556 return newMount, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once") 557 } 558 setExec = true 559 newMount.Options = append(newMount.Options, kv[0]) 560 case "tmpfs-mode": 561 if len(kv) == 1 { 562 return newMount, errors.Wrapf(optionArgError, kv[0]) 563 } 564 newMount.Options = append(newMount.Options, fmt.Sprintf("mode=%s", kv[1])) 565 case "tmpfs-size": 566 if len(kv) == 1 { 567 return newMount, errors.Wrapf(optionArgError, kv[0]) 568 } 569 newMount.Options = append(newMount.Options, fmt.Sprintf("size=%s", kv[1])) 570 case "src", "source": 571 return newMount, errors.Errorf("source is not supported with tmpfs mounts") 572 case "target", "dst", "destination": 573 if len(kv) == 1 { 574 return newMount, errors.Wrapf(optionArgError, kv[0]) 575 } 576 if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { 577 return newMount, err 578 } 579 newMount.Destination = filepath.Clean(kv[1]) 580 setDest = true 581 default: 582 return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0]) 583 } 584 } 585 586 if !setDest { 587 return newMount, noDestError 588 } 589 590 return newMount, nil 591 } 592 593 // Parse a single volume mount entry from the --mount flag. 594 // Note that the volume-label option for named volumes is currently NOT supported. 595 // TODO: add support for --volume-label 596 func getNamedVolume(args []string) (*libpod.ContainerNamedVolume, error) { //nolint 597 newVolume := new(libpod.ContainerNamedVolume) 598 599 var setSource, setDest, setRORW, setSuid, setDev, setExec bool 600 601 for _, val := range args { 602 kv := strings.Split(val, "=") 603 switch kv[0] { 604 case "ro", "rw": 605 if setRORW { 606 return nil, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once") 607 } 608 setRORW = true 609 newVolume.Options = append(newVolume.Options, kv[0]) 610 case "nosuid", "suid": 611 if setSuid { 612 return nil, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once") 613 } 614 setSuid = true 615 newVolume.Options = append(newVolume.Options, kv[0]) 616 case "nodev", "dev": 617 if setDev { 618 return nil, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once") 619 } 620 setDev = true 621 newVolume.Options = append(newVolume.Options, kv[0]) 622 case "noexec", "exec": 623 if setExec { 624 return nil, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once") 625 } 626 setExec = true 627 newVolume.Options = append(newVolume.Options, kv[0]) 628 case "volume-label": 629 return nil, errors.Errorf("the --volume-label option is not presently implemented") 630 case "src", "source": 631 if len(kv) == 1 { 632 return nil, errors.Wrapf(optionArgError, kv[0]) 633 } 634 newVolume.Name = kv[1] 635 setSource = true 636 case "target", "dst", "destination": 637 if len(kv) == 1 { 638 return nil, errors.Wrapf(optionArgError, kv[0]) 639 } 640 if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { 641 return nil, err 642 } 643 newVolume.Dest = filepath.Clean(kv[1]) 644 setDest = true 645 default: 646 return nil, errors.Wrapf(util.ErrBadMntOption, kv[0]) 647 } 648 } 649 650 if !setSource { 651 return nil, errors.Errorf("must set source volume") 652 } 653 if !setDest { 654 return nil, noDestError 655 } 656 657 return newVolume, nil 658 } 659 660 func getVolumeMounts(vols []string) (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint 661 mounts := make(map[string]spec.Mount) 662 volumes := make(map[string]*libpod.ContainerNamedVolume) 663 664 volumeFormatErr := errors.Errorf("incorrect volume format, should be [host-dir:]ctr-dir[:option]") 665 666 for _, vol := range vols { 667 var ( 668 options []string 669 src string 670 dest string 671 err error 672 ) 673 674 splitVol := strings.Split(vol, ":") 675 if len(splitVol) > 3 { 676 return nil, nil, errors.Wrapf(volumeFormatErr, vol) 677 } 678 679 src = splitVol[0] 680 if len(splitVol) == 1 { 681 // This is an anonymous named volume. Only thing given 682 // is destination. 683 // Name/source will be blank, and populated by libpod. 684 src = "" 685 dest = splitVol[0] 686 } else if len(splitVol) > 1 { 687 dest = splitVol[1] 688 } 689 if len(splitVol) > 2 { 690 if options, err = parse.ValidateVolumeOpts(strings.Split(splitVol[2], ",")); err != nil { 691 return nil, nil, err 692 } 693 } 694 695 // Do not check source dir for anonymous volumes 696 if len(splitVol) > 1 { 697 if err := parse.ValidateVolumeHostDir(src); err != nil { 698 return nil, nil, err 699 } 700 } 701 if err := parse.ValidateVolumeCtrDir(dest); err != nil { 702 return nil, nil, err 703 } 704 705 cleanDest := filepath.Clean(dest) 706 707 if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") { 708 // This is not a named volume 709 newMount := spec.Mount{ 710 Destination: cleanDest, 711 Type: string(TypeBind), 712 Source: src, 713 Options: options, 714 } 715 if _, ok := mounts[newMount.Destination]; ok { 716 return nil, nil, errors.Wrapf(errDuplicateDest, newMount.Destination) 717 } 718 mounts[newMount.Destination] = newMount 719 } else { 720 // This is a named volume 721 newNamedVol := new(libpod.ContainerNamedVolume) 722 newNamedVol.Name = src 723 newNamedVol.Dest = cleanDest 724 newNamedVol.Options = options 725 726 if _, ok := volumes[newNamedVol.Dest]; ok { 727 return nil, nil, errors.Wrapf(errDuplicateDest, newNamedVol.Dest) 728 } 729 volumes[newNamedVol.Dest] = newNamedVol 730 } 731 732 logrus.Debugf("User mount %s:%s options %v", src, dest, options) 733 } 734 735 return mounts, volumes, nil 736 } 737 738 // Get mounts for container's image volumes 739 // TODO deferred baude 740 func getImageVolumes() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint 741 //mounts := make(map[string]spec.Mount) 742 //volumes := make(map[string]*define.ContainerNamedVolume) 743 // 744 //if config.ImageVolumeType == "ignore" { 745 // return mounts, volumes, nil 746 //} 747 // 748 //for vol := range config.BuiltinImgVolumes { 749 // cleanDest := filepath.Clean(vol) 750 // logrus.Debugf("Adding image volume at %s", cleanDest) 751 // if config.ImageVolumeType == "tmpfs" { 752 // // Tmpfs image volumes are handled as mounts 753 // mount := spec.Mount{ 754 // Destination: cleanDest, 755 // Source: TypeTmpfs, 756 // Type: TypeTmpfs, 757 // Options: []string{"rprivate", "rw", "nodev", "exec"}, 758 // } 759 // mounts[cleanDest] = mount 760 // } else { 761 // // Anonymous volumes have no name. 762 // namedVolume := new(define.ContainerNamedVolume) 763 // namedVolume.Options = []string{"rprivate", "rw", "nodev", "exec"} 764 // namedVolume.Dest = cleanDest 765 // volumes[cleanDest] = namedVolume 766 // } 767 //} 768 // 769 //return mounts, volumes, nil 770 return nil, nil, nil 771 } 772 773 // GetTmpfsMounts creates spec.Mount structs for user-requested tmpfs mounts 774 func getTmpfsMounts(mounts []string) (map[string]spec.Mount, error) { //nolint 775 m := make(map[string]spec.Mount) 776 for _, i := range mounts { 777 // Default options if nothing passed 778 var options []string 779 spliti := strings.Split(i, ":") 780 destPath := spliti[0] 781 if err := parse.ValidateVolumeCtrDir(spliti[0]); err != nil { 782 return nil, err 783 } 784 if len(spliti) > 1 { 785 options = strings.Split(spliti[1], ",") 786 } 787 788 if _, ok := m[destPath]; ok { 789 return nil, errors.Wrapf(errDuplicateDest, destPath) 790 } 791 792 mount := spec.Mount{ 793 Destination: filepath.Clean(destPath), 794 Type: string(TypeTmpfs), 795 Options: options, 796 Source: string(TypeTmpfs), 797 } 798 m[destPath] = mount 799 } 800 return m, nil 801 } 802 803 // AddContainerInitBinary adds the init binary specified by path iff the 804 // container will run in a private PID namespace that is not shared with the 805 // host or another pre-existing container, where an init-like process is 806 // already running. 807 // 808 // Note that AddContainerInitBinary prepends "/dev/init" "--" to the command 809 // to execute the bind-mounted binary as PID 1. 810 // TODO this needs to be worked on to work in new env 811 func addContainerInitBinary(path string) (spec.Mount, error) { //nolint 812 mount := spec.Mount{ 813 Destination: "/dev/init", 814 Type: TypeBind, 815 Source: path, 816 Options: []string{TypeBind, "ro"}, 817 } 818 819 //if path == "" { 820 // return mount, fmt.Errorf("please specify a path to the container-init binary") 821 //} 822 //if !config.Pid.PidMode.IsPrivate() { 823 // return mount, fmt.Errorf("cannot add init binary as PID 1 (PID namespace isn't private)") 824 //} 825 //if config.Systemd { 826 // return mount, fmt.Errorf("cannot use container-init binary with systemd") 827 //} 828 //if _, err := os.Stat(path); os.IsNotExist(err) { 829 // return mount, errors.Wrap(err, "container-init binary not found on the host") 830 //} 831 //config.Command = append([]string{"/dev/init", "--"}, config.Command...) 832 return mount, nil 833 } 834 835 // Supersede existing mounts in the spec with new, user-specified mounts. 836 // TODO: Should we unmount subtree mounts? E.g., if /tmp/ is mounted by 837 // one mount, and we already have /tmp/a and /tmp/b, should we remove 838 // the /tmp/a and /tmp/b mounts in favor of the more general /tmp? 839 func SupercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.Mount { 840 if len(mounts) > 0 { 841 // If we have overlappings mounts, remove them from the spec in favor of 842 // the user-added volume mounts 843 destinations := make(map[string]bool) 844 for _, mount := range mounts { 845 destinations[path.Clean(mount.Destination)] = true 846 } 847 // Copy all mounts from spec to defaultMounts, except for 848 // - mounts overridden by a user supplied mount; 849 // - all mounts under /dev if a user supplied /dev is present; 850 mountDev := destinations["/dev"] 851 for _, mount := range configMount { 852 if _, ok := destinations[path.Clean(mount.Destination)]; !ok { 853 if mountDev && strings.HasPrefix(mount.Destination, "/dev/") { 854 // filter out everything under /dev if /dev is user-mounted 855 continue 856 } 857 858 logrus.Debugf("Adding mount %s", mount.Destination) 859 mounts = append(mounts, mount) 860 } 861 } 862 return mounts 863 } 864 return configMount 865 } 866 867 func InitFSMounts(mounts []spec.Mount) error { 868 for i, m := range mounts { 869 switch { 870 case m.Type == TypeBind: 871 opts, err := util.ProcessOptions(m.Options, false, m.Source) 872 if err != nil { 873 return err 874 } 875 mounts[i].Options = opts 876 case m.Type == TypeTmpfs && filepath.Clean(m.Destination) != "/dev": 877 opts, err := util.ProcessOptions(m.Options, true, "") 878 if err != nil { 879 return err 880 } 881 mounts[i].Options = opts 882 } 883 } 884 return nil 885 }