14
"github.com/containers/common/libimage"
15
"github.com/containers/common/pkg/config"
16
"github.com/containers/common/pkg/parse"
17
"github.com/containers/podman/v5/libpod"
18
"github.com/containers/podman/v5/libpod/define"
19
"github.com/containers/podman/v5/pkg/specgen"
20
"github.com/containers/podman/v5/pkg/util"
21
"github.com/containers/storage/pkg/fileutils"
22
spec "github.com/opencontainers/runtime-spec/specs-go"
23
"github.com/sirupsen/logrus"
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) {
29
baseMounts, baseVolumes, err := getImageVolumes(ctx, img, s)
31
return nil, nil, nil, err
34
// Get volumes-from mounts
35
volFromMounts, volFromVolumes, err := getVolumesFrom(s.VolumesFrom, rt)
37
return nil, nil, nil, err
40
// Supersede from --volumes-from.
41
for dest, mount := range volFromMounts {
42
baseMounts[dest] = mount
44
// Necessary to ensure that mounts override image volumes
45
// Ref: https://github.com/containers/podman/issues/19529
46
delete(baseVolumes, dest)
48
for dest, volume := range volFromVolumes {
49
baseVolumes[dest] = volume
51
// I don't think this can happen, but best to be safe.
52
delete(baseMounts, dest)
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{}
60
// Need to make map forms of specgen mounts/volumes.
61
commonMounts, commonVolumes, commonOverlayVolumes, err := specgen.GenVolumeMounts(rtc.Volumes())
63
return nil, nil, nil, err
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
72
cleanDestination := filepath.Clean(m.Destination)
73
if _, ok := unifiedMounts[cleanDestination]; ok {
74
return nil, nil, nil, fmt.Errorf("%q: %w", cleanDestination, specgen.ErrDuplicateDest)
76
unifiedMounts[cleanDestination] = m
79
for _, m := range commonMounts {
80
if err = parse.ValidateVolumeCtrDir(m.Destination); err != nil {
81
return nil, nil, nil, err
83
cleanDestination := filepath.Clean(m.Destination)
84
if _, ok := unifiedMounts[cleanDestination]; !ok {
85
unifiedMounts[cleanDestination] = m
89
for _, v := range s.Volumes {
90
if err = parse.ValidateVolumeCtrDir(v.Dest); err != nil {
91
return nil, nil, nil, err
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)
97
unifiedVolumes[cleanDestination] = v
100
for _, v := range commonVolumes {
101
if err = parse.ValidateVolumeCtrDir(v.Dest); err != nil {
102
return nil, nil, nil, err
104
cleanDestination := filepath.Clean(v.Dest)
105
if _, ok := unifiedVolumes[cleanDestination]; !ok {
106
unifiedVolumes[cleanDestination] = v
110
for _, v := range s.OverlayVolumes {
111
if err = parse.ValidateVolumeCtrDir(v.Destination); err != nil {
112
return nil, nil, nil, err
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)
118
unifiedOverlays[cleanDestination] = v
121
for _, v := range commonOverlayVolumes {
122
if err = parse.ValidateVolumeCtrDir(v.Destination); err != nil {
123
return nil, nil, nil, err
125
cleanDestination := filepath.Clean(v.Destination)
126
if _, ok := unifiedOverlays[cleanDestination]; !ok {
127
unifiedOverlays[cleanDestination] = v
131
// If requested, add container init binary
132
if s.Init != nil && *s.Init {
133
initPath := s.InitPath
135
initPath, err = rtc.FindInitBinary()
137
return nil, nil, nil, fmt.Errorf("lookup init binary: %w", err)
140
initMount, err := addContainerInitBinary(s, initPath)
142
return nil, nil, nil, err
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)
147
unifiedMounts[initMount.Destination] = initMount
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)
156
for dest := range unifiedVolumes {
157
delete(baseMounts, dest)
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
165
for dest, volume := range unifiedVolumes {
166
baseVolumes[dest] = volume
169
// TODO: Investigate moving readonlyTmpfs into here. Would be more
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)
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)
184
if s.ReadWriteTmpfs != nil && *s.ReadWriteTmpfs {
185
runPath, err := imageRunPath(ctx, img)
187
return nil, nil, nil, err
189
baseMounts = addReadWriteTmpfsMounts(baseMounts, s.Volumes, runPath)
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)
198
return nil, nil, nil, fmt.Errorf("getting absolute path of %s: %w", mount.Source, err)
200
mount.Source = absSrc
202
finalMounts = append(finalMounts, mount)
204
finalVolumes := make([]*specgen.NamedVolume, 0, len(baseVolumes))
205
for _, volume := range baseVolumes {
206
finalVolumes = append(finalVolumes, volume)
209
finalOverlays := make([]*specgen.OverlayVolume, 0, len(unifiedOverlays))
210
for _, volume := range unifiedOverlays {
211
finalOverlays = append(finalOverlays, volume)
214
return finalMounts, finalVolumes, finalOverlays, nil
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)
222
mode := strings.ToLower(s.ImageVolumeMode)
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
229
inspect, err := img.Inspect(ctx, nil)
231
return nil, nil, fmt.Errorf("inspecting image to get image volumes: %w", err)
233
for volume := range inspect.Config.Volumes {
234
logrus.Debugf("Image has volume at %q", volume)
235
cleanDest := filepath.Clean(volume)
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:
246
Destination: cleanDest,
247
Source: define.TypeTmpfs,
248
Type: define.TypeTmpfs,
249
Options: []string{"rprivate", "rw", "nodev", "exec"},
251
mounts[cleanDest] = mount
252
logrus.Debugf("Adding tmpfs image volume at %q", cleanDest)
256
return mounts, volumes, nil
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)
263
for _, volume := range volumesFrom {
266
idOrName, volOpts, hasVolOpts := strings.Cut(volume, ":")
268
splitOpts := strings.Split(volOpts, ",")
271
for _, opt := range splitOpts {
275
return nil, nil, errors.New("cannot set :z more than once in mount options")
280
return nil, nil, errors.New("cannot set ro or rw options more than once")
284
return nil, nil, fmt.Errorf("invalid option %q specified - volumes from another container can only use z,ro,rw options", opt)
290
ctr, err := runtime.LookupContainer(idOrName)
292
return nil, nil, fmt.Errorf("looking up container %q for volumes-from: %w", idOrName, err)
295
logrus.Debugf("Adding volumes from container %s", ctr.ID())
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()
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
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
313
return nil, nil, fmt.Errorf("retrieving container %s spec for volumes-from", ctr.ID())
315
for _, mnt := range spec.Mounts {
316
if mnt.Type != define.TypeBind {
319
if _, exists := userVolumes[mnt.Destination]; exists {
320
userVolumes[mnt.Destination] = true
322
if len(options) != 0 {
323
mnt.Options = options
326
if _, ok := finalMounts[mnt.Destination]; ok {
327
logrus.Debugf("Overriding mount to %s with new mount from container %s", mnt.Destination, ctr.ID())
329
finalMounts[mnt.Destination] = mnt
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
342
if len(options) != 0 {
343
namedVol.Options = options
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())
349
if err = parse.ValidateVolumeCtrDir(namedVol.Dest); err != nil {
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
359
finalNamedVolumes[namedVol.Dest] = newVol
362
// Check if we missed any volumes
363
for volDest, found := range userVolumes {
365
logrus.Warnf("Unable to match volume %s from container %s for volumes-from", volDest, ctr.ID())
370
return finalMounts, finalNamedVolumes, nil
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
377
// This does *NOT* modify the container command - that must be done elsewhere.
378
func addContainerInitBinary(s *specgen.SpecGenerator, path string) (spec.Mount, error) {
380
Destination: define.ContainerInitPath,
381
Type: define.TypeBind,
383
Options: append(define.BindOptions, "ro"),
387
return mount, errors.New("please specify a path to the container-init binary")
389
if !s.PidNS.IsPrivate() {
390
return mount, errors.New("cannot add init binary as PID 1 (PID namespace isn't private)")
392
if s.Systemd == "always" {
393
return mount, errors.New("cannot use container-init binary with systemd=always")
395
if err := fileutils.Exists(path); errors.Is(err, fs.ErrNotExist) {
396
return mount, fmt.Errorf("container-init binary not found on the host: %w", err)
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 {
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
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
424
logrus.Debugf("Adding mount %s", mount.Destination)
425
mounts = append(mounts, mount)
433
func InitFSMounts(mounts []spec.Mount) error {
434
for i, m := range mounts {
436
case m.Type == define.TypeBind:
437
opts, err := util.ProcessOptions(m.Options, false, m.Source)
441
mounts[i].Options = opts
442
case m.Type == define.TypeTmpfs && filepath.Clean(m.Destination) != "/dev":
443
opts, err := util.ProcessOptions(m.Options, true, "")
447
mounts[i].Options = opts
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 {
460
for _, m := range volumes {
467
Type: define.TypeTmpfs,
468
Source: define.TypeTmpfs,