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