github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/specgen/volumes.go (about)

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