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