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  }