github.com/containers/podman/v4@v4.9.4/pkg/specgen/volumes.go (about)

     1  package specgen
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"path/filepath"
     7  	"strings"
     8  
     9  	"github.com/containers/common/pkg/parse"
    10  	"github.com/containers/podman/v4/libpod/define"
    11  	spec "github.com/opencontainers/runtime-spec/specs-go"
    12  	"github.com/sirupsen/logrus"
    13  )
    14  
    15  // NamedVolume holds information about a named volume that will be mounted into
    16  // the container.
    17  type NamedVolume struct {
    18  	// Name is the name of the named volume to be mounted. May be empty.
    19  	// If empty, a new named volume with a pseudorandomly generated name
    20  	// will be mounted at the given destination.
    21  	Name string
    22  	// Destination to mount the named volume within the container. Must be
    23  	// an absolute path. Path will be created if it does not exist.
    24  	Dest string
    25  	// Options are options that the named volume will be mounted with.
    26  	Options []string
    27  	// IsAnonymous sets the named volume as anonymous even if it has a name
    28  	// This is used for emptyDir volumes from a kube yaml
    29  	IsAnonymous bool
    30  	// SubPath stores the sub directory of the named volume to be mounted in the container
    31  	SubPath string
    32  }
    33  
    34  // OverlayVolume holds information about an overlay volume that will be mounted into
    35  // the container.
    36  type OverlayVolume struct {
    37  	// Destination is the absolute path where the mount will be placed in the container.
    38  	Destination string `json:"destination"`
    39  	// Source specifies the source path of the mount.
    40  	Source string `json:"source,omitempty"`
    41  	// Options holds overlay volume options.
    42  	Options []string `json:"options,omitempty"`
    43  }
    44  
    45  // ImageVolume is a volume based on a container image.  The container image is
    46  // first mounted on the host and is then bind-mounted into the container.  An
    47  // ImageVolume is always mounted read-only.
    48  type ImageVolume struct {
    49  	// Source is the source of the image volume.  The image can be referred
    50  	// to by name and by ID.
    51  	Source string
    52  	// Destination is the absolute path of the mount in the container.
    53  	Destination string
    54  	// ReadWrite sets the volume writable.
    55  	ReadWrite bool
    56  }
    57  
    58  // GenVolumeMounts parses user input into mounts, volumes and overlay volumes
    59  func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*NamedVolume, map[string]*OverlayVolume, error) {
    60  	mounts := make(map[string]spec.Mount)
    61  	volumes := make(map[string]*NamedVolume)
    62  	overlayVolumes := make(map[string]*OverlayVolume)
    63  
    64  	volumeFormatErr := errors.New("incorrect volume format, should be [host-dir:]ctr-dir[:option]")
    65  	for _, vol := range volumeFlag {
    66  		var (
    67  			options []string
    68  			src     string
    69  			dest    string
    70  			err     error
    71  		)
    72  
    73  		splitVol := SplitVolumeString(vol)
    74  		if len(splitVol) > 3 {
    75  			return nil, nil, nil, fmt.Errorf("%v: %w", vol, volumeFormatErr)
    76  		}
    77  
    78  		src = splitVol[0]
    79  
    80  		// Support relative paths beginning with ./
    81  		if strings.HasPrefix(src, "./") {
    82  			path, err := filepath.EvalSymlinks(src)
    83  			if err != nil {
    84  				return nil, nil, nil, err
    85  			}
    86  			src, err = filepath.Abs(path)
    87  			if err != nil {
    88  				return nil, nil, nil, err
    89  			}
    90  			splitVol[0] = src
    91  		}
    92  
    93  		if len(splitVol) == 1 {
    94  			// This is an anonymous named volume. Only thing given
    95  			// is destination.
    96  			// Name/source will be blank, and populated by libpod.
    97  			src = ""
    98  			dest = splitVol[0]
    99  		} else if len(splitVol) > 1 {
   100  			dest = splitVol[1]
   101  		}
   102  		if len(splitVol) > 2 {
   103  			if options, err = parse.ValidateVolumeOpts(strings.Split(splitVol[2], ",")); err != nil {
   104  				return nil, nil, nil, err
   105  			}
   106  		}
   107  
   108  		// Do not check source dir for anonymous volumes
   109  		if len(splitVol) > 1 {
   110  			if len(src) == 0 {
   111  				return nil, nil, nil, errors.New("host directory cannot be empty")
   112  			}
   113  		}
   114  
   115  		if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") || isHostWinPath(src) {
   116  			// This is not a named volume
   117  			overlayFlag := false
   118  			chownFlag := false
   119  			upperDirFlag := false
   120  			workDirFlag := false
   121  			for _, o := range options {
   122  				if o == "O" {
   123  					overlayFlag = true
   124  
   125  					joinedOpts := strings.Join(options, "")
   126  					if strings.Contains(joinedOpts, "U") {
   127  						chownFlag = true
   128  					}
   129  					if strings.Contains(joinedOpts, "upperdir") {
   130  						upperDirFlag = true
   131  					}
   132  					if strings.Contains(joinedOpts, "workdir") {
   133  						workDirFlag = true
   134  					}
   135  					if (workDirFlag && !upperDirFlag) || (!workDirFlag && upperDirFlag) {
   136  						return nil, nil, nil, errors.New("must set both `upperdir` and `workdir`")
   137  					}
   138  					if len(options) > 2 && !(len(options) == 3 && upperDirFlag && workDirFlag) || (len(options) == 2 && !chownFlag) {
   139  						return nil, nil, nil, errors.New("can't use 'O' with other options")
   140  					}
   141  				}
   142  			}
   143  			if overlayFlag {
   144  				// This is an overlay volume
   145  				newOverlayVol := new(OverlayVolume)
   146  				newOverlayVol.Destination = dest
   147  				// convert src to absolute path so we don't end up passing
   148  				// relative values as lowerdir for overlay mounts
   149  				source, err := filepath.Abs(src)
   150  				if err != nil {
   151  					return nil, nil, nil, fmt.Errorf("failed while resolving absolute path for source %v for overlay mount: %w", src, err)
   152  				}
   153  				newOverlayVol.Source = source
   154  				newOverlayVol.Options = options
   155  
   156  				if vol, ok := overlayVolumes[newOverlayVol.Destination]; ok {
   157  					if vol.Source == newOverlayVol.Source &&
   158  						StringSlicesEqual(vol.Options, newOverlayVol.Options) {
   159  						continue
   160  					}
   161  					return nil, nil, nil, fmt.Errorf("%v: %w", newOverlayVol.Destination, ErrDuplicateDest)
   162  				}
   163  				overlayVolumes[newOverlayVol.Destination] = newOverlayVol
   164  			} else {
   165  				newMount := spec.Mount{
   166  					Destination: dest,
   167  					Type:        define.TypeBind,
   168  					Source:      src,
   169  					Options:     options,
   170  				}
   171  				if vol, ok := mounts[newMount.Destination]; ok {
   172  					if vol.Source == newMount.Source &&
   173  						StringSlicesEqual(vol.Options, newMount.Options) {
   174  						continue
   175  					}
   176  
   177  					return nil, nil, nil, fmt.Errorf("%v: %w", newMount.Destination, ErrDuplicateDest)
   178  				}
   179  				mounts[newMount.Destination] = newMount
   180  			}
   181  		} else {
   182  			// This is a named volume
   183  			newNamedVol := new(NamedVolume)
   184  			newNamedVol.Name = src
   185  			newNamedVol.Dest = dest
   186  			newNamedVol.Options = options
   187  
   188  			if vol, ok := volumes[newNamedVol.Dest]; ok {
   189  				if vol.Name == newNamedVol.Name {
   190  					continue
   191  				}
   192  				return nil, nil, nil, fmt.Errorf("%v: %w", newNamedVol.Dest, ErrDuplicateDest)
   193  			}
   194  			volumes[newNamedVol.Dest] = newNamedVol
   195  		}
   196  
   197  		logrus.Debugf("User mount %s:%s options %v", src, dest, options)
   198  	}
   199  
   200  	return mounts, volumes, overlayVolumes, nil
   201  }
   202  
   203  // Splits a volume string, accounting for Win drive paths
   204  // when running as a WSL linux guest or Windows client
   205  func SplitVolumeString(vol string) []string {
   206  	parts := strings.Split(vol, ":")
   207  	if !shouldResolveWinPaths() {
   208  		return parts
   209  	}
   210  
   211  	// Skip extended marker prefix if present
   212  	n := 0
   213  	if strings.HasPrefix(vol, `\\?\`) {
   214  		n = 4
   215  	}
   216  
   217  	if hasWinDriveScheme(vol, n) {
   218  		first := parts[0] + ":" + parts[1]
   219  		parts = parts[1:]
   220  		parts[0] = first
   221  	}
   222  
   223  	return parts
   224  }