github.com/containers/podman/v4@v4.9.4/pkg/specgen/generate/storage.go (about) 1 //go:build !remote 2 // +build !remote 3 4 package generate 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "os" 11 "path" 12 "path/filepath" 13 "strings" 14 15 "github.com/containers/common/libimage" 16 "github.com/containers/common/pkg/config" 17 "github.com/containers/common/pkg/parse" 18 "github.com/containers/podman/v4/libpod" 19 "github.com/containers/podman/v4/libpod/define" 20 "github.com/containers/podman/v4/pkg/specgen" 21 "github.com/containers/podman/v4/pkg/util" 22 spec "github.com/opencontainers/runtime-spec/specs-go" 23 "github.com/sirupsen/logrus" 24 ) 25 26 // Produce final mounts and named volumes for a container 27 func finalizeMounts(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, img *libimage.Image) ([]spec.Mount, []*specgen.NamedVolume, []*specgen.OverlayVolume, error) { 28 // Get image volumes 29 baseMounts, baseVolumes, err := getImageVolumes(ctx, img, s) 30 if err != nil { 31 return nil, nil, nil, err 32 } 33 34 // Get volumes-from mounts 35 volFromMounts, volFromVolumes, err := getVolumesFrom(s.VolumesFrom, rt) 36 if err != nil { 37 return nil, nil, nil, err 38 } 39 40 // Supersede from --volumes-from. 41 for dest, mount := range volFromMounts { 42 baseMounts[dest] = mount 43 44 // Necessary to ensure that mounts override image volumes 45 // Ref: https://github.com/containers/podman/issues/19529 46 delete(baseVolumes, dest) 47 } 48 for dest, volume := range volFromVolumes { 49 baseVolumes[dest] = volume 50 51 // I don't think this can happen, but best to be safe. 52 delete(baseMounts, dest) 53 } 54 55 // Need to make map forms of specgen mounts/volumes. 56 unifiedMounts := map[string]spec.Mount{} 57 unifiedVolumes := map[string]*specgen.NamedVolume{} 58 unifiedOverlays := map[string]*specgen.OverlayVolume{} 59 60 // Need to make map forms of specgen mounts/volumes. 61 commonMounts, commonVolumes, commonOverlayVolumes, err := specgen.GenVolumeMounts(rtc.Volumes()) 62 if err != nil { 63 return nil, nil, nil, err 64 } 65 66 for _, m := range s.Mounts { 67 // Ensure that mount dest is clean, so that it can be 68 // compared against named volumes and avoid duplicate mounts. 69 if err = parse.ValidateVolumeCtrDir(m.Destination); err != nil { 70 return nil, nil, nil, err 71 } 72 cleanDestination := filepath.Clean(m.Destination) 73 if _, ok := unifiedMounts[cleanDestination]; ok { 74 return nil, nil, nil, fmt.Errorf("%q: %w", cleanDestination, specgen.ErrDuplicateDest) 75 } 76 unifiedMounts[cleanDestination] = m 77 } 78 79 for _, m := range commonMounts { 80 if err = parse.ValidateVolumeCtrDir(m.Destination); err != nil { 81 return nil, nil, nil, err 82 } 83 cleanDestination := filepath.Clean(m.Destination) 84 if _, ok := unifiedMounts[cleanDestination]; !ok { 85 unifiedMounts[cleanDestination] = m 86 } 87 } 88 89 for _, v := range s.Volumes { 90 if err = parse.ValidateVolumeCtrDir(v.Dest); err != nil { 91 return nil, nil, nil, err 92 } 93 cleanDestination := filepath.Clean(v.Dest) 94 if _, ok := unifiedVolumes[cleanDestination]; ok { 95 return nil, nil, nil, fmt.Errorf("conflict in specified volumes - multiple volumes at %q: %w", cleanDestination, specgen.ErrDuplicateDest) 96 } 97 unifiedVolumes[cleanDestination] = v 98 } 99 100 for _, v := range commonVolumes { 101 if err = parse.ValidateVolumeCtrDir(v.Dest); err != nil { 102 return nil, nil, nil, err 103 } 104 cleanDestination := filepath.Clean(v.Dest) 105 if _, ok := unifiedVolumes[cleanDestination]; !ok { 106 unifiedVolumes[cleanDestination] = v 107 } 108 } 109 110 for _, v := range s.OverlayVolumes { 111 if err = parse.ValidateVolumeCtrDir(v.Destination); err != nil { 112 return nil, nil, nil, err 113 } 114 cleanDestination := filepath.Clean(v.Destination) 115 if _, ok := unifiedOverlays[cleanDestination]; ok { 116 return nil, nil, nil, fmt.Errorf("conflict in specified volumes - multiple volumes at %q: %w", cleanDestination, specgen.ErrDuplicateDest) 117 } 118 unifiedOverlays[cleanDestination] = v 119 } 120 121 for _, v := range commonOverlayVolumes { 122 if err = parse.ValidateVolumeCtrDir(v.Destination); err != nil { 123 return nil, nil, nil, err 124 } 125 cleanDestination := filepath.Clean(v.Destination) 126 if _, ok := unifiedOverlays[cleanDestination]; !ok { 127 unifiedOverlays[cleanDestination] = v 128 } 129 } 130 131 // If requested, add container init binary 132 if s.Init { 133 initPath := s.InitPath 134 if initPath == "" { 135 initPath, err = rtc.FindInitBinary() 136 if err != nil { 137 return nil, nil, nil, fmt.Errorf("lookup init binary: %w", err) 138 } 139 } 140 initMount, err := addContainerInitBinary(s, initPath) 141 if err != nil { 142 return nil, nil, nil, err 143 } 144 if _, ok := unifiedMounts[initMount.Destination]; ok { 145 return nil, nil, nil, fmt.Errorf("conflict with mount added by --init to %q: %w", initMount.Destination, specgen.ErrDuplicateDest) 146 } 147 unifiedMounts[initMount.Destination] = initMount 148 } 149 150 // Before superseding, we need to find volume mounts which conflict with 151 // named volumes, and vice versa. 152 // We'll delete the conflicts here as we supersede. 153 for dest := range unifiedMounts { 154 delete(baseVolumes, dest) 155 } 156 for dest := range unifiedVolumes { 157 delete(baseMounts, dest) 158 } 159 160 // Supersede volumes-from/image volumes with unified volumes from above. 161 // This is an unconditional replacement. 162 for dest, mount := range unifiedMounts { 163 baseMounts[dest] = mount 164 } 165 for dest, volume := range unifiedVolumes { 166 baseVolumes[dest] = volume 167 } 168 169 // TODO: Investigate moving readonlyTmpfs into here. Would be more 170 // correct. 171 172 // Check for conflicts between named volumes and mounts 173 for dest := range baseMounts { 174 if _, ok := baseVolumes[dest]; ok { 175 return nil, nil, nil, fmt.Errorf("baseMounts conflict at mount destination %v: %w", dest, specgen.ErrDuplicateDest) 176 } 177 } 178 for dest := range baseVolumes { 179 if _, ok := baseMounts[dest]; ok { 180 return nil, nil, nil, fmt.Errorf("baseVolumes conflict at mount destination %v: %w", dest, specgen.ErrDuplicateDest) 181 } 182 } 183 184 if s.ReadWriteTmpfs { 185 runPath, err := imageRunPath(ctx, img) 186 if err != nil { 187 return nil, nil, nil, err 188 } 189 baseMounts = addReadWriteTmpfsMounts(baseMounts, s.Volumes, runPath) 190 } 191 192 // Final step: maps to arrays 193 finalMounts := make([]spec.Mount, 0, len(baseMounts)) 194 for _, mount := range baseMounts { 195 if mount.Type == define.TypeBind { 196 absSrc, err := filepath.Abs(mount.Source) 197 if err != nil { 198 return nil, nil, nil, fmt.Errorf("getting absolute path of %s: %w", mount.Source, err) 199 } 200 mount.Source = absSrc 201 } 202 finalMounts = append(finalMounts, mount) 203 } 204 finalVolumes := make([]*specgen.NamedVolume, 0, len(baseVolumes)) 205 for _, volume := range baseVolumes { 206 finalVolumes = append(finalVolumes, volume) 207 } 208 209 finalOverlays := make([]*specgen.OverlayVolume, 0, len(unifiedOverlays)) 210 for _, volume := range unifiedOverlays { 211 finalOverlays = append(finalOverlays, volume) 212 } 213 214 return finalMounts, finalVolumes, finalOverlays, nil 215 } 216 217 // Get image volumes from the given image 218 func getImageVolumes(ctx context.Context, img *libimage.Image, s *specgen.SpecGenerator) (map[string]spec.Mount, map[string]*specgen.NamedVolume, error) { 219 mounts := make(map[string]spec.Mount) 220 volumes := make(map[string]*specgen.NamedVolume) 221 222 mode := strings.ToLower(s.ImageVolumeMode) 223 224 // Image may be nil (rootfs in use), or image volume mode may be ignore. 225 if img == nil || mode == "ignore" { 226 return mounts, volumes, nil 227 } 228 229 inspect, err := img.Inspect(ctx, nil) 230 if err != nil { 231 return nil, nil, fmt.Errorf("inspecting image to get image volumes: %w", err) 232 } 233 for volume := range inspect.Config.Volumes { 234 logrus.Debugf("Image has volume at %q", volume) 235 cleanDest := filepath.Clean(volume) 236 switch mode { 237 case "", "anonymous": 238 // Anonymous volumes have no name. 239 newVol := new(specgen.NamedVolume) 240 newVol.Dest = cleanDest 241 newVol.Options = []string{"rprivate", "rw", "nodev", "exec"} 242 volumes[cleanDest] = newVol 243 logrus.Debugf("Adding anonymous image volume at %q", cleanDest) 244 case define.TypeTmpfs: 245 mount := spec.Mount{ 246 Destination: cleanDest, 247 Source: define.TypeTmpfs, 248 Type: define.TypeTmpfs, 249 Options: []string{"rprivate", "rw", "nodev", "exec"}, 250 } 251 mounts[cleanDest] = mount 252 logrus.Debugf("Adding tmpfs image volume at %q", cleanDest) 253 } 254 } 255 256 return mounts, volumes, nil 257 } 258 259 func getVolumesFrom(volumesFrom []string, runtime *libpod.Runtime) (map[string]spec.Mount, map[string]*specgen.NamedVolume, error) { 260 finalMounts := make(map[string]spec.Mount) 261 finalNamedVolumes := make(map[string]*specgen.NamedVolume) 262 263 for _, volume := range volumesFrom { 264 var options []string 265 266 splitVol := strings.SplitN(volume, ":", 2) 267 if len(splitVol) == 2 { 268 splitOpts := strings.Split(splitVol[1], ",") 269 setRORW := false 270 setZ := false 271 for _, opt := range splitOpts { 272 switch opt { 273 case "z": 274 if setZ { 275 return nil, nil, errors.New("cannot set :z more than once in mount options") 276 } 277 setZ = true 278 case "ro", "rw": 279 if setRORW { 280 return nil, nil, errors.New("cannot set ro or rw options more than once") 281 } 282 setRORW = true 283 default: 284 return nil, nil, fmt.Errorf("invalid option %q specified - volumes from another container can only use z,ro,rw options", opt) 285 } 286 } 287 options = splitOpts 288 } 289 290 ctr, err := runtime.LookupContainer(splitVol[0]) 291 if err != nil { 292 return nil, nil, fmt.Errorf("looking up container %q for volumes-from: %w", splitVol[0], err) 293 } 294 295 logrus.Debugf("Adding volumes from container %s", ctr.ID()) 296 297 // Look up the container's user volumes. This gets us the 298 // destinations of all mounts the user added to the container. 299 userVolumesArr := ctr.UserVolumes() 300 301 // We're going to need to access them a lot, so convert to a map 302 // to reduce looping. 303 // We'll also use the map to indicate if we missed any volumes along the way. 304 userVolumes := make(map[string]bool) 305 for _, dest := range userVolumesArr { 306 userVolumes[dest] = false 307 } 308 309 // Now we get the container's spec and loop through its volumes 310 // and append them in if we can find them. 311 spec := ctr.ConfigNoCopy().Spec 312 if spec == nil { 313 return nil, nil, fmt.Errorf("retrieving container %s spec for volumes-from", ctr.ID()) 314 } 315 for _, mnt := range spec.Mounts { 316 if mnt.Type != define.TypeBind { 317 continue 318 } 319 if _, exists := userVolumes[mnt.Destination]; exists { 320 userVolumes[mnt.Destination] = true 321 322 if len(options) != 0 { 323 mnt.Options = options 324 } 325 326 if _, ok := finalMounts[mnt.Destination]; ok { 327 logrus.Debugf("Overriding mount to %s with new mount from container %s", mnt.Destination, ctr.ID()) 328 } 329 finalMounts[mnt.Destination] = mnt 330 } 331 } 332 333 // We're done with the spec mounts. Add named volumes. 334 // Add these unconditionally - none of them are automatically 335 // part of the container, as some spec mounts are. 336 namedVolumes := ctr.NamedVolumes() 337 for _, namedVol := range namedVolumes { 338 if _, exists := userVolumes[namedVol.Dest]; exists { 339 userVolumes[namedVol.Dest] = true 340 } 341 342 if len(options) != 0 { 343 namedVol.Options = options 344 } 345 346 if _, ok := finalMounts[namedVol.Dest]; ok { 347 logrus.Debugf("Overriding named volume mount to %s with new named volume from container %s", namedVol.Dest, ctr.ID()) 348 } 349 if err = parse.ValidateVolumeCtrDir(namedVol.Dest); err != nil { 350 return nil, nil, err 351 } 352 353 cleanDest := filepath.Clean(namedVol.Dest) 354 newVol := new(specgen.NamedVolume) 355 newVol.Dest = cleanDest 356 newVol.Options = namedVol.Options 357 newVol.Name = namedVol.Name 358 359 finalNamedVolumes[namedVol.Dest] = newVol 360 } 361 362 // Check if we missed any volumes 363 for volDest, found := range userVolumes { 364 if !found { 365 logrus.Warnf("Unable to match volume %s from container %s for volumes-from", volDest, ctr.ID()) 366 } 367 } 368 } 369 370 return finalMounts, finalNamedVolumes, nil 371 } 372 373 // AddContainerInitBinary adds the init binary specified by path iff the 374 // container will run in a private PID namespace that is not shared with the 375 // host or another pre-existing container, where an init-like process is 376 // already running. 377 // This does *NOT* modify the container command - that must be done elsewhere. 378 func addContainerInitBinary(s *specgen.SpecGenerator, path string) (spec.Mount, error) { 379 mount := spec.Mount{ 380 Destination: define.ContainerInitPath, 381 Type: define.TypeBind, 382 Source: path, 383 Options: append(define.BindOptions, "ro"), 384 } 385 386 if path == "" { 387 return mount, errors.New("please specify a path to the container-init binary") 388 } 389 if !s.PidNS.IsPrivate() { 390 return mount, errors.New("cannot add init binary as PID 1 (PID namespace isn't private)") 391 } 392 if s.Systemd == "always" { 393 return mount, errors.New("cannot use container-init binary with systemd=always") 394 } 395 if _, err := os.Stat(path); os.IsNotExist(err) { 396 return mount, fmt.Errorf("container-init binary not found on the host: %w", err) 397 } 398 return mount, nil 399 } 400 401 // Supersede existing mounts in the spec with new, user-specified mounts. 402 // TODO: Should we unmount subtree mounts? E.g., if /tmp/ is mounted by 403 // one mount, and we already have /tmp/a and /tmp/b, should we remove 404 // the /tmp/a and /tmp/b mounts in favor of the more general /tmp? 405 func SupersedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.Mount { 406 if len(mounts) > 0 { 407 // If we have overlappings mounts, remove them from the spec in favor of 408 // the user-added volume mounts 409 destinations := make(map[string]bool) 410 for _, mount := range mounts { 411 destinations[path.Clean(mount.Destination)] = true 412 } 413 // Copy all mounts from spec to defaultMounts, except for 414 // - mounts overridden by a user supplied mount; 415 // - all mounts under /dev if a user supplied /dev is present; 416 mountDev := destinations["/dev"] 417 for _, mount := range configMount { 418 if _, ok := destinations[path.Clean(mount.Destination)]; !ok { 419 if mountDev && strings.HasPrefix(mount.Destination, "/dev/") { 420 // filter out everything under /dev if /dev is user-mounted 421 continue 422 } 423 424 logrus.Debugf("Adding mount %s", mount.Destination) 425 mounts = append(mounts, mount) 426 } 427 } 428 return mounts 429 } 430 return configMount 431 } 432 433 func InitFSMounts(mounts []spec.Mount) error { 434 for i, m := range mounts { 435 switch { 436 case m.Type == define.TypeBind: 437 opts, err := util.ProcessOptions(m.Options, false, m.Source) 438 if err != nil { 439 return err 440 } 441 mounts[i].Options = opts 442 case m.Type == define.TypeTmpfs && filepath.Clean(m.Destination) != "/dev": 443 opts, err := util.ProcessOptions(m.Options, true, "") 444 if err != nil { 445 return err 446 } 447 mounts[i].Options = opts 448 } 449 } 450 return nil 451 } 452 453 func addReadWriteTmpfsMounts(mounts map[string]spec.Mount, volumes []*specgen.NamedVolume, runPath string) map[string]spec.Mount { 454 readonlyTmpfs := []string{"/tmp", "/var/tmp", runPath} 455 options := []string{"rw", "rprivate", "nosuid", "nodev", "tmpcopyup"} 456 for _, dest := range readonlyTmpfs { 457 if _, ok := mounts[dest]; ok { 458 continue 459 } 460 for _, m := range volumes { 461 if m.Dest == dest { 462 continue 463 } 464 } 465 mnt := spec.Mount{ 466 Destination: dest, 467 Type: define.TypeTmpfs, 468 Source: define.TypeTmpfs, 469 Options: options, 470 } 471 mounts[dest] = mnt 472 } 473 return mounts 474 }