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  }