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 }