github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/libpod/runtime_volume_linux.go (about) 1 //go:build linux 2 // +build linux 3 4 package libpod 5 6 import ( 7 "context" 8 "os" 9 "path/filepath" 10 "strings" 11 "time" 12 13 "github.com/hanks177/podman/v4/libpod/define" 14 "github.com/hanks177/podman/v4/libpod/events" 15 volplugin "github.com/hanks177/podman/v4/libpod/plugin" 16 "github.com/containers/storage/drivers/quota" 17 "github.com/containers/storage/pkg/stringid" 18 pluginapi "github.com/docker/go-plugins-helpers/volume" 19 "github.com/pkg/errors" 20 "github.com/sirupsen/logrus" 21 ) 22 23 // NewVolume creates a new empty volume 24 func (r *Runtime) NewVolume(ctx context.Context, options ...VolumeCreateOption) (*Volume, error) { 25 if !r.valid { 26 return nil, define.ErrRuntimeStopped 27 } 28 return r.newVolume(options...) 29 } 30 31 // newVolume creates a new empty volume 32 func (r *Runtime) newVolume(options ...VolumeCreateOption) (_ *Volume, deferredErr error) { 33 volume := newVolume(r) 34 for _, option := range options { 35 if err := option(volume); err != nil { 36 return nil, errors.Wrapf(err, "running volume create option") 37 } 38 } 39 40 if volume.config.Name == "" { 41 volume.config.Name = stringid.GenerateNonCryptoID() 42 } 43 if volume.config.Driver == "" { 44 volume.config.Driver = define.VolumeDriverLocal 45 } 46 volume.config.CreatedTime = time.Now() 47 48 // Check if volume with given name exists. 49 exists, err := r.state.HasVolume(volume.config.Name) 50 if err != nil { 51 return nil, errors.Wrapf(err, "checking if volume with name %s exists", volume.config.Name) 52 } 53 if exists { 54 return nil, errors.Wrapf(define.ErrVolumeExists, "volume with name %s already exists", volume.config.Name) 55 } 56 57 // Plugin can be nil if driver is local, but that's OK - superfluous 58 // assignment doesn't hurt much. 59 plugin, err := r.getVolumePlugin(volume.config.Driver) 60 if err != nil { 61 return nil, errors.Wrapf(err, "volume %s uses volume plugin %s but it could not be retrieved", volume.config.Name, volume.config.Driver) 62 } 63 volume.plugin = plugin 64 65 if volume.config.Driver == define.VolumeDriverLocal { 66 logrus.Debugf("Validating options for local driver") 67 // Validate options 68 for key, val := range volume.config.Options { 69 switch strings.ToLower(key) { 70 case "device": 71 if strings.ToLower(volume.config.Options["type"]) == "bind" { 72 if _, err := os.Stat(val); err != nil { 73 return nil, errors.Wrapf(err, "invalid volume option %s for driver 'local'", key) 74 } 75 } 76 case "o", "type", "uid", "gid", "size", "inodes", "noquota": 77 // Do nothing, valid keys 78 default: 79 return nil, errors.Wrapf(define.ErrInvalidArg, "invalid mount option %s for driver 'local'", key) 80 } 81 } 82 } 83 84 // Now we get conditional: we either need to make the volume in the 85 // volume plugin, or on disk if not using a plugin. 86 if volume.plugin != nil { 87 // We can't chown, or relabel, or similar the path the volume is 88 // using, because it's not managed by us. 89 // TODO: reevaluate this once we actually have volume plugins in 90 // use in production - it may be safe, but I can't tell without 91 // knowing what the actual plugin does... 92 if err := makeVolumeInPluginIfNotExist(volume.config.Name, volume.config.Options, volume.plugin); err != nil { 93 return nil, err 94 } 95 } else { 96 // Create the mountpoint of this volume 97 volPathRoot := filepath.Join(r.config.Engine.VolumePath, volume.config.Name) 98 if err := os.MkdirAll(volPathRoot, 0700); err != nil { 99 return nil, errors.Wrapf(err, "creating volume directory %q", volPathRoot) 100 } 101 if err := os.Chown(volPathRoot, volume.config.UID, volume.config.GID); err != nil { 102 return nil, errors.Wrapf(err, "chowning volume directory %q to %d:%d", volPathRoot, volume.config.UID, volume.config.GID) 103 } 104 fullVolPath := filepath.Join(volPathRoot, "_data") 105 if err := os.MkdirAll(fullVolPath, 0755); err != nil { 106 return nil, errors.Wrapf(err, "creating volume directory %q", fullVolPath) 107 } 108 if err := os.Chown(fullVolPath, volume.config.UID, volume.config.GID); err != nil { 109 return nil, errors.Wrapf(err, "chowning volume directory %q to %d:%d", fullVolPath, volume.config.UID, volume.config.GID) 110 } 111 if err := LabelVolumePath(fullVolPath); err != nil { 112 return nil, err 113 } 114 if volume.config.DisableQuota { 115 if volume.config.Size > 0 || volume.config.Inodes > 0 { 116 return nil, errors.New("volume options size and inodes cannot be used without quota") 117 } 118 } else { 119 projectQuotaSupported := false 120 q, err := quota.NewControl(r.config.Engine.VolumePath) 121 if err == nil { 122 projectQuotaSupported = true 123 } 124 quota := quota.Quota{} 125 if volume.config.Size > 0 || volume.config.Inodes > 0 { 126 if !projectQuotaSupported { 127 return nil, errors.New("volume options size and inodes not supported. Filesystem does not support Project Quota") 128 } 129 quota.Size = volume.config.Size 130 quota.Inodes = volume.config.Inodes 131 } 132 if projectQuotaSupported { 133 if err := q.SetQuota(fullVolPath, quota); err != nil { 134 return nil, errors.Wrapf(err, "failed to set size quota size=%d inodes=%d for volume directory %q", volume.config.Size, volume.config.Inodes, fullVolPath) 135 } 136 } 137 } 138 139 volume.config.MountPoint = fullVolPath 140 } 141 142 lock, err := r.lockManager.AllocateLock() 143 if err != nil { 144 return nil, errors.Wrapf(err, "allocating lock for new volume") 145 } 146 volume.lock = lock 147 volume.config.LockID = volume.lock.ID() 148 149 defer func() { 150 if deferredErr != nil { 151 if err := volume.lock.Free(); err != nil { 152 logrus.Errorf("Freeing volume lock after failed creation: %v", err) 153 } 154 } 155 }() 156 157 volume.valid = true 158 159 // Add the volume to state 160 if err := r.state.AddVolume(volume); err != nil { 161 return nil, errors.Wrapf(err, "adding volume to state") 162 } 163 defer volume.newVolumeEvent(events.Create) 164 return volume, nil 165 } 166 167 // makeVolumeInPluginIfNotExist makes a volume in the given volume plugin if it 168 // does not already exist. 169 func makeVolumeInPluginIfNotExist(name string, options map[string]string, plugin *volplugin.VolumePlugin) error { 170 // Ping the volume plugin to see if it exists first. 171 // If it does, use the existing volume in the plugin. 172 // Options may not match exactly, but not much we can do about 173 // that. Not complaining avoids a lot of the sync issues we see 174 // with c/storage and libpod DB. 175 needsCreate := true 176 getReq := new(pluginapi.GetRequest) 177 getReq.Name = name 178 if resp, err := plugin.GetVolume(getReq); err == nil { 179 // TODO: What do we do if we get a 200 response, but the 180 // Volume is nil? The docs on the Plugin API are very 181 // nonspecific, so I don't know if this is valid or 182 // not... 183 if resp != nil { 184 needsCreate = false 185 logrus.Infof("Volume %q already exists in plugin %q, using existing volume", name, plugin.Name) 186 } 187 } 188 if needsCreate { 189 createReq := new(pluginapi.CreateRequest) 190 createReq.Name = name 191 createReq.Options = options 192 if err := plugin.CreateVolume(createReq); err != nil { 193 return errors.Wrapf(err, "creating volume %q in plugin %s", name, plugin.Name) 194 } 195 } 196 197 return nil 198 } 199 200 // removeVolume removes the specified volume from state as well tears down its mountpoint and storage 201 func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool, timeout *uint) error { 202 if !v.valid { 203 if ok, _ := r.state.HasVolume(v.Name()); !ok { 204 return nil 205 } 206 return define.ErrVolumeRemoved 207 } 208 209 v.lock.Lock() 210 defer v.lock.Unlock() 211 212 // Update volume status to pick up a potential removal from state 213 if err := v.update(); err != nil { 214 return err 215 } 216 217 deps, err := r.state.VolumeInUse(v) 218 if err != nil { 219 return err 220 } 221 if len(deps) != 0 { 222 depsStr := strings.Join(deps, ", ") 223 if !force { 224 return errors.Wrapf(define.ErrVolumeBeingUsed, "volume %s is being used by the following container(s): %s", v.Name(), depsStr) 225 } 226 227 // We need to remove all containers using the volume 228 for _, dep := range deps { 229 ctr, err := r.state.Container(dep) 230 if err != nil { 231 // If the container's removed, no point in 232 // erroring. 233 if errors.Cause(err) == define.ErrNoSuchCtr || errors.Cause(err) == define.ErrCtrRemoved { 234 continue 235 } 236 237 return errors.Wrapf(err, "removing container %s that depends on volume %s", dep, v.Name()) 238 } 239 240 logrus.Debugf("Removing container %s (depends on volume %q)", ctr.ID(), v.Name()) 241 242 if err := r.removeContainer(ctx, ctr, force, false, false, timeout); err != nil { 243 return errors.Wrapf(err, "removing container %s that depends on volume %s", ctr.ID(), v.Name()) 244 } 245 } 246 } 247 248 // If the volume is still mounted - force unmount it 249 if err := v.unmount(true); err != nil { 250 if force { 251 // If force is set, evict the volume, even if errors 252 // occur. Otherwise we'll never be able to get rid of 253 // them. 254 logrus.Errorf("Unmounting volume %s: %v", v.Name(), err) 255 } else { 256 return errors.Wrapf(err, "unmounting volume %s", v.Name()) 257 } 258 } 259 260 // Set volume as invalid so it can no longer be used 261 v.valid = false 262 263 var removalErr error 264 265 // If we use a volume plugin, we need to remove from the plugin. 266 if v.UsesVolumeDriver() { 267 canRemove := true 268 269 // Do we have a volume driver? 270 if v.plugin == nil { 271 canRemove = false 272 removalErr = errors.Wrapf(define.ErrMissingPlugin, "cannot remove volume %s from plugin %s, but it has been removed from Podman", v.Name(), v.Driver()) 273 } else { 274 // Ping the plugin first to verify the volume still 275 // exists. 276 // We're trying to be very tolerant of missing volumes 277 // in the backend, to avoid the problems we see with 278 // sync between c/storage and the Libpod DB. 279 getReq := new(pluginapi.GetRequest) 280 getReq.Name = v.Name() 281 if _, err := v.plugin.GetVolume(getReq); err != nil { 282 canRemove = false 283 removalErr = errors.Wrapf(err, "volume %s could not be retrieved from plugin %s, but it has been removed from Podman", v.Name(), v.Driver()) 284 } 285 } 286 if canRemove { 287 req := new(pluginapi.RemoveRequest) 288 req.Name = v.Name() 289 if err := v.plugin.RemoveVolume(req); err != nil { 290 return errors.Wrapf(err, "volume %s could not be removed from plugin %s", v.Name(), v.Driver()) 291 } 292 } 293 } 294 295 // Remove the volume from the state 296 if err := r.state.RemoveVolume(v); err != nil { 297 if removalErr != nil { 298 logrus.Errorf("Removing volume %s from plugin %s: %v", v.Name(), v.Driver(), removalErr) 299 } 300 return errors.Wrapf(err, "removing volume %s", v.Name()) 301 } 302 303 // Free the volume's lock 304 if err := v.lock.Free(); err != nil { 305 if removalErr == nil { 306 removalErr = errors.Wrapf(err, "freeing lock for volume %s", v.Name()) 307 } else { 308 logrus.Errorf("Freeing lock for volume %q: %v", v.Name(), err) 309 } 310 } 311 312 // Delete the mountpoint path of the volume, that is delete the volume 313 // from /var/lib/containers/storage/volumes 314 if err := v.teardownStorage(); err != nil { 315 if removalErr == nil { 316 removalErr = errors.Wrapf(err, "cleaning up volume storage for %q", v.Name()) 317 } else { 318 logrus.Errorf("Cleaning up volume storage for volume %q: %v", v.Name(), err) 319 } 320 } 321 322 defer v.newVolumeEvent(events.Remove) 323 logrus.Debugf("Removed volume %s", v.Name()) 324 return removalErr 325 }