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 }