From 065474755b7bcca1521bc9e3d30583768518a5cb Mon Sep 17 00:00:00 2001 From: Doug Rabson Date: Tue, 20 Sep 2022 11:25:03 +0100 Subject: libpod: Move runtime_volume_linux.go to runtime_volume_common.go [NO NEW TESTS NEEDED] Signed-off-by: Doug Rabson --- libpod/runtime_volume_common.go | 465 ++++++++++++++++++++++++++++++++++++++++ libpod/runtime_volume_linux.go | 465 ---------------------------------------- 2 files changed, 465 insertions(+), 465 deletions(-) create mode 100644 libpod/runtime_volume_common.go delete mode 100644 libpod/runtime_volume_linux.go (limited to 'libpod') diff --git a/libpod/runtime_volume_common.go b/libpod/runtime_volume_common.go new file mode 100644 index 000000000..c59417979 --- /dev/null +++ b/libpod/runtime_volume_common.go @@ -0,0 +1,465 @@ +//go:build linux +// +build linux + +package libpod + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "github.com/containers/podman/v4/libpod/define" + "github.com/containers/podman/v4/libpod/events" + volplugin "github.com/containers/podman/v4/libpod/plugin" + "github.com/containers/storage" + "github.com/containers/storage/drivers/quota" + "github.com/containers/storage/pkg/idtools" + "github.com/containers/storage/pkg/stringid" + pluginapi "github.com/docker/go-plugins-helpers/volume" + "github.com/sirupsen/logrus" +) + +const volumeSuffix = "+volume" + +// NewVolume creates a new empty volume +func (r *Runtime) NewVolume(ctx context.Context, options ...VolumeCreateOption) (*Volume, error) { + if !r.valid { + return nil, define.ErrRuntimeStopped + } + return r.newVolume(ctx, false, options...) +} + +// newVolume creates a new empty volume with the given options. +// The createPluginVolume can be set to true to make it not create the volume in the volume plugin, +// this is required for the UpdateVolumePlugins() function. If you are not sure, set this to false. +func (r *Runtime) newVolume(ctx context.Context, noCreatePluginVolume bool, options ...VolumeCreateOption) (_ *Volume, deferredErr error) { + volume := newVolume(r) + for _, option := range options { + if err := option(volume); err != nil { + return nil, fmt.Errorf("running volume create option: %w", err) + } + } + + if volume.config.Name == "" { + volume.config.Name = stringid.GenerateRandomID() + } + if volume.config.Driver == "" { + volume.config.Driver = define.VolumeDriverLocal + } + volume.config.CreatedTime = time.Now() + + // Check if volume with given name exists. + exists, err := r.state.HasVolume(volume.config.Name) + if err != nil { + return nil, fmt.Errorf("checking if volume with name %s exists: %w", volume.config.Name, err) + } + if exists { + return nil, fmt.Errorf("volume with name %s already exists: %w", volume.config.Name, define.ErrVolumeExists) + } + + // Plugin can be nil if driver is local, but that's OK - superfluous + // assignment doesn't hurt much. + plugin, err := r.getVolumePlugin(volume.config) + if err != nil { + return nil, fmt.Errorf("volume %s uses volume plugin %s but it could not be retrieved: %w", volume.config.Name, volume.config.Driver, err) + } + volume.plugin = plugin + + if volume.config.Driver == define.VolumeDriverLocal { + logrus.Debugf("Validating options for local driver") + // Validate options + for key, val := range volume.config.Options { + switch strings.ToLower(key) { + case "device": + if strings.ToLower(volume.config.Options["type"]) == "bind" { + if _, err := os.Stat(val); err != nil { + return nil, fmt.Errorf("invalid volume option %s for driver 'local': %w", key, err) + } + } + case "o", "type", "uid", "gid", "size", "inodes", "noquota", "copy", "nocopy": + // Do nothing, valid keys + default: + return nil, fmt.Errorf("invalid mount option %s for driver 'local': %w", key, define.ErrInvalidArg) + } + } + } else if volume.config.Driver == define.VolumeDriverImage && !volume.UsesVolumeDriver() { + logrus.Debugf("Creating image-based volume") + var imgString string + // Validate options + for key, val := range volume.config.Options { + switch strings.ToLower(key) { + case "image": + imgString = val + default: + return nil, fmt.Errorf("invalid mount option %s for driver 'image': %w", key, define.ErrInvalidArg) + } + } + + if imgString == "" { + return nil, fmt.Errorf("must provide an image name when creating a volume with the image driver: %w", define.ErrInvalidArg) + } + + // Look up the image + image, _, err := r.libimageRuntime.LookupImage(imgString, nil) + if err != nil { + return nil, fmt.Errorf("looking up image %s to create volume failed: %w", imgString, err) + } + + // Generate a c/storage name and ID for the volume. + // Use characters Podman does not allow for the name, to ensure + // no collision with containers. + volume.config.StorageID = stringid.GenerateRandomID() + volume.config.StorageName = volume.config.Name + volumeSuffix + volume.config.StorageImageID = image.ID() + + // Create a backing container in c/storage. + storageConfig := storage.ContainerOptions{ + LabelOpts: []string{"filetype:container_file_t:s0"}, + } + if _, err := r.storageService.CreateContainerStorage(ctx, r.imageContext, imgString, image.ID(), volume.config.StorageName, volume.config.StorageID, storageConfig); err != nil { + return nil, fmt.Errorf("creating backing storage for image driver: %w", err) + } + defer func() { + if deferredErr != nil { + if err := r.storageService.DeleteContainer(volume.config.StorageID); err != nil { + logrus.Errorf("Error removing volume %s backing storage: %v", volume.config.Name, err) + } + } + }() + } + + // Now we get conditional: we either need to make the volume in the + // volume plugin, or on disk if not using a plugin. + if volume.plugin != nil && !noCreatePluginVolume { + // We can't chown, or relabel, or similar the path the volume is + // using, because it's not managed by us. + // TODO: reevaluate this once we actually have volume plugins in + // use in production - it may be safe, but I can't tell without + // knowing what the actual plugin does... + if err := makeVolumeInPluginIfNotExist(volume.config.Name, volume.config.Options, volume.plugin); err != nil { + return nil, err + } + } else { + // Create the mountpoint of this volume + volPathRoot := filepath.Join(r.config.Engine.VolumePath, volume.config.Name) + if err := os.MkdirAll(volPathRoot, 0700); err != nil { + return nil, fmt.Errorf("creating volume directory %q: %w", volPathRoot, err) + } + if err := idtools.SafeChown(volPathRoot, volume.config.UID, volume.config.GID); err != nil { + return nil, fmt.Errorf("chowning volume directory %q to %d:%d: %w", volPathRoot, volume.config.UID, volume.config.GID, err) + } + fullVolPath := filepath.Join(volPathRoot, "_data") + if err := os.MkdirAll(fullVolPath, 0755); err != nil { + return nil, fmt.Errorf("creating volume directory %q: %w", fullVolPath, err) + } + if err := idtools.SafeChown(fullVolPath, volume.config.UID, volume.config.GID); err != nil { + return nil, fmt.Errorf("chowning volume directory %q to %d:%d: %w", fullVolPath, volume.config.UID, volume.config.GID, err) + } + if err := LabelVolumePath(fullVolPath); err != nil { + return nil, err + } + if volume.config.DisableQuota { + if volume.config.Size > 0 || volume.config.Inodes > 0 { + return nil, errors.New("volume options size and inodes cannot be used without quota") + } + } else { + projectQuotaSupported := false + q, err := quota.NewControl(r.config.Engine.VolumePath) + if err == nil { + projectQuotaSupported = true + } + quota := quota.Quota{} + if volume.config.Size > 0 || volume.config.Inodes > 0 { + if !projectQuotaSupported { + return nil, errors.New("volume options size and inodes not supported. Filesystem does not support Project Quota") + } + quota.Size = volume.config.Size + quota.Inodes = volume.config.Inodes + } + if projectQuotaSupported { + if err := q.SetQuota(fullVolPath, quota); err != nil { + return nil, fmt.Errorf("failed to set size quota size=%d inodes=%d for volume directory %q: %w", volume.config.Size, volume.config.Inodes, fullVolPath, err) + } + } + } + + volume.config.MountPoint = fullVolPath + } + + lock, err := r.lockManager.AllocateLock() + if err != nil { + return nil, fmt.Errorf("allocating lock for new volume: %w", err) + } + volume.lock = lock + volume.config.LockID = volume.lock.ID() + + defer func() { + if deferredErr != nil { + if err := volume.lock.Free(); err != nil { + logrus.Errorf("Freeing volume lock after failed creation: %v", err) + } + } + }() + + volume.valid = true + + // Add the volume to state + if err := r.state.AddVolume(volume); err != nil { + return nil, fmt.Errorf("adding volume to state: %w", err) + } + defer volume.newVolumeEvent(events.Create) + return volume, nil +} + +// UpdateVolumePlugins reads all volumes from all configured volume plugins and +// imports them into the libpod db. It also checks if existing libpod volumes +// are removed in the plugin, in this case we try to remove it from libpod. +// On errors we continue and try to do as much as possible. all errors are +// returned as array in the returned struct. +// This function has many race conditions, it is best effort but cannot guarantee +// a perfect state since plugins can be modified from the outside at any time. +func (r *Runtime) UpdateVolumePlugins(ctx context.Context) *define.VolumeReload { + var ( + added []string + removed []string + errs []error + allPluginVolumes = map[string]struct{}{} + ) + + for driverName, socket := range r.config.Engine.VolumePlugins { + driver, err := volplugin.GetVolumePlugin(driverName, socket, nil, r.config) + if err != nil { + errs = append(errs, err) + continue + } + vols, err := driver.ListVolumes() + if err != nil { + errs = append(errs, fmt.Errorf("failed to read volumes from plugin %q: %w", driverName, err)) + continue + } + for _, vol := range vols { + allPluginVolumes[vol.Name] = struct{}{} + if _, err := r.newVolume(ctx, true, WithVolumeName(vol.Name), WithVolumeDriver(driverName)); err != nil { + // If the volume exists this is not an error, just ignore it and log. It is very likely + // that the volume from the plugin was already in our db. + if !errors.Is(err, define.ErrVolumeExists) { + errs = append(errs, err) + continue + } + logrus.Infof("Volume %q already exists: %v", vol.Name, err) + continue + } + added = append(added, vol.Name) + } + } + + libpodVolumes, err := r.state.AllVolumes() + if err != nil { + errs = append(errs, fmt.Errorf("cannot delete dangling plugin volumes: failed to read libpod volumes: %w", err)) + } + for _, vol := range libpodVolumes { + if vol.UsesVolumeDriver() { + if _, ok := allPluginVolumes[vol.Name()]; !ok { + // The volume is no longer in the plugin. Let's remove it from the libpod db. + if err := r.removeVolume(ctx, vol, false, nil, true); err != nil { + if errors.Is(err, define.ErrVolumeBeingUsed) { + // Volume is still used by at least one container. This is very bad, + // the plugin no longer has this but we still need it. + errs = append(errs, fmt.Errorf("volume was removed from the plugin %q but containers still require it: %w", vol.config.Driver, err)) + continue + } + if errors.Is(err, define.ErrNoSuchVolume) || errors.Is(err, define.ErrVolumeRemoved) || errors.Is(err, define.ErrMissingPlugin) { + // Volume was already removed, no problem just ignore it and continue. + continue + } + + // some other error + errs = append(errs, err) + continue + } + // Volume was successfully removed + removed = append(removed, vol.Name()) + } + } + } + + return &define.VolumeReload{ + Added: added, + Removed: removed, + Errors: errs, + } +} + +// makeVolumeInPluginIfNotExist makes a volume in the given volume plugin if it +// does not already exist. +func makeVolumeInPluginIfNotExist(name string, options map[string]string, plugin *volplugin.VolumePlugin) error { + // Ping the volume plugin to see if it exists first. + // If it does, use the existing volume in the plugin. + // Options may not match exactly, but not much we can do about + // that. Not complaining avoids a lot of the sync issues we see + // with c/storage and libpod DB. + needsCreate := true + getReq := new(pluginapi.GetRequest) + getReq.Name = name + if resp, err := plugin.GetVolume(getReq); err == nil { + // TODO: What do we do if we get a 200 response, but the + // Volume is nil? The docs on the Plugin API are very + // nonspecific, so I don't know if this is valid or + // not... + if resp != nil { + needsCreate = false + logrus.Infof("Volume %q already exists in plugin %q, using existing volume", name, plugin.Name) + } + } + if needsCreate { + createReq := new(pluginapi.CreateRequest) + createReq.Name = name + createReq.Options = options + if err := plugin.CreateVolume(createReq); err != nil { + return fmt.Errorf("creating volume %q in plugin %s: %w", name, plugin.Name, err) + } + } + + return nil +} + +// removeVolume removes the specified volume from state as well tears down its mountpoint and storage. +// ignoreVolumePlugin is used to only remove the volume from the db and not the plugin, +// this is required when the volume was already removed from the plugin, i.e. in UpdateVolumePlugins(). +func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool, timeout *uint, ignoreVolumePlugin bool) error { + if !v.valid { + if ok, _ := r.state.HasVolume(v.Name()); !ok { + return nil + } + return define.ErrVolumeRemoved + } + + v.lock.Lock() + defer v.lock.Unlock() + + // Update volume status to pick up a potential removal from state + if err := v.update(); err != nil { + return err + } + + deps, err := r.state.VolumeInUse(v) + if err != nil { + return err + } + if len(deps) != 0 { + depsStr := strings.Join(deps, ", ") + if !force { + return fmt.Errorf("volume %s is being used by the following container(s): %s: %w", v.Name(), depsStr, define.ErrVolumeBeingUsed) + } + + // We need to remove all containers using the volume + for _, dep := range deps { + ctr, err := r.state.Container(dep) + if err != nil { + // If the container's removed, no point in + // erroring. + if errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrRemoved) { + continue + } + + return fmt.Errorf("removing container %s that depends on volume %s: %w", dep, v.Name(), err) + } + + logrus.Debugf("Removing container %s (depends on volume %q)", ctr.ID(), v.Name()) + + if err := r.removeContainer(ctx, ctr, force, false, false, false, timeout); err != nil { + return fmt.Errorf("removing container %s that depends on volume %s: %w", ctr.ID(), v.Name(), err) + } + } + } + + // If the volume is still mounted - force unmount it + if err := v.unmount(true); err != nil { + if force { + // If force is set, evict the volume, even if errors + // occur. Otherwise we'll never be able to get rid of + // them. + logrus.Errorf("Unmounting volume %s: %v", v.Name(), err) + } else { + return fmt.Errorf("unmounting volume %s: %w", v.Name(), err) + } + } + + // Set volume as invalid so it can no longer be used + v.valid = false + + var removalErr error + + // If we use a volume plugin, we need to remove from the plugin. + if v.UsesVolumeDriver() && !ignoreVolumePlugin { + canRemove := true + + // Do we have a volume driver? + if v.plugin == nil { + canRemove = false + removalErr = fmt.Errorf("cannot remove volume %s from plugin %s, but it has been removed from Podman: %w", v.Name(), v.Driver(), define.ErrMissingPlugin) + } else { + // Ping the plugin first to verify the volume still + // exists. + // We're trying to be very tolerant of missing volumes + // in the backend, to avoid the problems we see with + // sync between c/storage and the Libpod DB. + getReq := new(pluginapi.GetRequest) + getReq.Name = v.Name() + if _, err := v.plugin.GetVolume(getReq); err != nil { + canRemove = false + removalErr = fmt.Errorf("volume %s could not be retrieved from plugin %s, but it has been removed from Podman: %w", v.Name(), v.Driver(), err) + } + } + if canRemove { + req := new(pluginapi.RemoveRequest) + req.Name = v.Name() + if err := v.plugin.RemoveVolume(req); err != nil { + return fmt.Errorf("volume %s could not be removed from plugin %s: %w", v.Name(), v.Driver(), err) + } + } + } else if v.config.Driver == define.VolumeDriverImage { + if err := v.runtime.storageService.DeleteContainer(v.config.StorageID); err != nil { + // Storage container is already gone, no problem. + if !(errors.Is(err, storage.ErrNotAContainer) || errors.Is(err, storage.ErrContainerUnknown)) { + return fmt.Errorf("removing volume %s storage: %w", v.Name(), err) + } + logrus.Infof("Storage for volume %s already removed", v.Name()) + } + } + + // Remove the volume from the state + if err := r.state.RemoveVolume(v); err != nil { + if removalErr != nil { + logrus.Errorf("Removing volume %s from plugin %s: %v", v.Name(), v.Driver(), removalErr) + } + return fmt.Errorf("removing volume %s: %w", v.Name(), err) + } + + // Free the volume's lock + if err := v.lock.Free(); err != nil { + if removalErr == nil { + removalErr = fmt.Errorf("freeing lock for volume %s: %w", v.Name(), err) + } else { + logrus.Errorf("Freeing lock for volume %q: %v", v.Name(), err) + } + } + + // Delete the mountpoint path of the volume, that is delete the volume + // from /var/lib/containers/storage/volumes + if err := v.teardownStorage(); err != nil { + if removalErr == nil { + removalErr = fmt.Errorf("cleaning up volume storage for %q: %w", v.Name(), err) + } else { + logrus.Errorf("Cleaning up volume storage for volume %q: %v", v.Name(), err) + } + } + + defer v.newVolumeEvent(events.Remove) + logrus.Debugf("Removed volume %s", v.Name()) + return removalErr +} diff --git a/libpod/runtime_volume_linux.go b/libpod/runtime_volume_linux.go deleted file mode 100644 index c59417979..000000000 --- a/libpod/runtime_volume_linux.go +++ /dev/null @@ -1,465 +0,0 @@ -//go:build linux -// +build linux - -package libpod - -import ( - "context" - "errors" - "fmt" - "os" - "path/filepath" - "strings" - "time" - - "github.com/containers/podman/v4/libpod/define" - "github.com/containers/podman/v4/libpod/events" - volplugin "github.com/containers/podman/v4/libpod/plugin" - "github.com/containers/storage" - "github.com/containers/storage/drivers/quota" - "github.com/containers/storage/pkg/idtools" - "github.com/containers/storage/pkg/stringid" - pluginapi "github.com/docker/go-plugins-helpers/volume" - "github.com/sirupsen/logrus" -) - -const volumeSuffix = "+volume" - -// NewVolume creates a new empty volume -func (r *Runtime) NewVolume(ctx context.Context, options ...VolumeCreateOption) (*Volume, error) { - if !r.valid { - return nil, define.ErrRuntimeStopped - } - return r.newVolume(ctx, false, options...) -} - -// newVolume creates a new empty volume with the given options. -// The createPluginVolume can be set to true to make it not create the volume in the volume plugin, -// this is required for the UpdateVolumePlugins() function. If you are not sure, set this to false. -func (r *Runtime) newVolume(ctx context.Context, noCreatePluginVolume bool, options ...VolumeCreateOption) (_ *Volume, deferredErr error) { - volume := newVolume(r) - for _, option := range options { - if err := option(volume); err != nil { - return nil, fmt.Errorf("running volume create option: %w", err) - } - } - - if volume.config.Name == "" { - volume.config.Name = stringid.GenerateRandomID() - } - if volume.config.Driver == "" { - volume.config.Driver = define.VolumeDriverLocal - } - volume.config.CreatedTime = time.Now() - - // Check if volume with given name exists. - exists, err := r.state.HasVolume(volume.config.Name) - if err != nil { - return nil, fmt.Errorf("checking if volume with name %s exists: %w", volume.config.Name, err) - } - if exists { - return nil, fmt.Errorf("volume with name %s already exists: %w", volume.config.Name, define.ErrVolumeExists) - } - - // Plugin can be nil if driver is local, but that's OK - superfluous - // assignment doesn't hurt much. - plugin, err := r.getVolumePlugin(volume.config) - if err != nil { - return nil, fmt.Errorf("volume %s uses volume plugin %s but it could not be retrieved: %w", volume.config.Name, volume.config.Driver, err) - } - volume.plugin = plugin - - if volume.config.Driver == define.VolumeDriverLocal { - logrus.Debugf("Validating options for local driver") - // Validate options - for key, val := range volume.config.Options { - switch strings.ToLower(key) { - case "device": - if strings.ToLower(volume.config.Options["type"]) == "bind" { - if _, err := os.Stat(val); err != nil { - return nil, fmt.Errorf("invalid volume option %s for driver 'local': %w", key, err) - } - } - case "o", "type", "uid", "gid", "size", "inodes", "noquota", "copy", "nocopy": - // Do nothing, valid keys - default: - return nil, fmt.Errorf("invalid mount option %s for driver 'local': %w", key, define.ErrInvalidArg) - } - } - } else if volume.config.Driver == define.VolumeDriverImage && !volume.UsesVolumeDriver() { - logrus.Debugf("Creating image-based volume") - var imgString string - // Validate options - for key, val := range volume.config.Options { - switch strings.ToLower(key) { - case "image": - imgString = val - default: - return nil, fmt.Errorf("invalid mount option %s for driver 'image': %w", key, define.ErrInvalidArg) - } - } - - if imgString == "" { - return nil, fmt.Errorf("must provide an image name when creating a volume with the image driver: %w", define.ErrInvalidArg) - } - - // Look up the image - image, _, err := r.libimageRuntime.LookupImage(imgString, nil) - if err != nil { - return nil, fmt.Errorf("looking up image %s to create volume failed: %w", imgString, err) - } - - // Generate a c/storage name and ID for the volume. - // Use characters Podman does not allow for the name, to ensure - // no collision with containers. - volume.config.StorageID = stringid.GenerateRandomID() - volume.config.StorageName = volume.config.Name + volumeSuffix - volume.config.StorageImageID = image.ID() - - // Create a backing container in c/storage. - storageConfig := storage.ContainerOptions{ - LabelOpts: []string{"filetype:container_file_t:s0"}, - } - if _, err := r.storageService.CreateContainerStorage(ctx, r.imageContext, imgString, image.ID(), volume.config.StorageName, volume.config.StorageID, storageConfig); err != nil { - return nil, fmt.Errorf("creating backing storage for image driver: %w", err) - } - defer func() { - if deferredErr != nil { - if err := r.storageService.DeleteContainer(volume.config.StorageID); err != nil { - logrus.Errorf("Error removing volume %s backing storage: %v", volume.config.Name, err) - } - } - }() - } - - // Now we get conditional: we either need to make the volume in the - // volume plugin, or on disk if not using a plugin. - if volume.plugin != nil && !noCreatePluginVolume { - // We can't chown, or relabel, or similar the path the volume is - // using, because it's not managed by us. - // TODO: reevaluate this once we actually have volume plugins in - // use in production - it may be safe, but I can't tell without - // knowing what the actual plugin does... - if err := makeVolumeInPluginIfNotExist(volume.config.Name, volume.config.Options, volume.plugin); err != nil { - return nil, err - } - } else { - // Create the mountpoint of this volume - volPathRoot := filepath.Join(r.config.Engine.VolumePath, volume.config.Name) - if err := os.MkdirAll(volPathRoot, 0700); err != nil { - return nil, fmt.Errorf("creating volume directory %q: %w", volPathRoot, err) - } - if err := idtools.SafeChown(volPathRoot, volume.config.UID, volume.config.GID); err != nil { - return nil, fmt.Errorf("chowning volume directory %q to %d:%d: %w", volPathRoot, volume.config.UID, volume.config.GID, err) - } - fullVolPath := filepath.Join(volPathRoot, "_data") - if err := os.MkdirAll(fullVolPath, 0755); err != nil { - return nil, fmt.Errorf("creating volume directory %q: %w", fullVolPath, err) - } - if err := idtools.SafeChown(fullVolPath, volume.config.UID, volume.config.GID); err != nil { - return nil, fmt.Errorf("chowning volume directory %q to %d:%d: %w", fullVolPath, volume.config.UID, volume.config.GID, err) - } - if err := LabelVolumePath(fullVolPath); err != nil { - return nil, err - } - if volume.config.DisableQuota { - if volume.config.Size > 0 || volume.config.Inodes > 0 { - return nil, errors.New("volume options size and inodes cannot be used without quota") - } - } else { - projectQuotaSupported := false - q, err := quota.NewControl(r.config.Engine.VolumePath) - if err == nil { - projectQuotaSupported = true - } - quota := quota.Quota{} - if volume.config.Size > 0 || volume.config.Inodes > 0 { - if !projectQuotaSupported { - return nil, errors.New("volume options size and inodes not supported. Filesystem does not support Project Quota") - } - quota.Size = volume.config.Size - quota.Inodes = volume.config.Inodes - } - if projectQuotaSupported { - if err := q.SetQuota(fullVolPath, quota); err != nil { - return nil, fmt.Errorf("failed to set size quota size=%d inodes=%d for volume directory %q: %w", volume.config.Size, volume.config.Inodes, fullVolPath, err) - } - } - } - - volume.config.MountPoint = fullVolPath - } - - lock, err := r.lockManager.AllocateLock() - if err != nil { - return nil, fmt.Errorf("allocating lock for new volume: %w", err) - } - volume.lock = lock - volume.config.LockID = volume.lock.ID() - - defer func() { - if deferredErr != nil { - if err := volume.lock.Free(); err != nil { - logrus.Errorf("Freeing volume lock after failed creation: %v", err) - } - } - }() - - volume.valid = true - - // Add the volume to state - if err := r.state.AddVolume(volume); err != nil { - return nil, fmt.Errorf("adding volume to state: %w", err) - } - defer volume.newVolumeEvent(events.Create) - return volume, nil -} - -// UpdateVolumePlugins reads all volumes from all configured volume plugins and -// imports them into the libpod db. It also checks if existing libpod volumes -// are removed in the plugin, in this case we try to remove it from libpod. -// On errors we continue and try to do as much as possible. all errors are -// returned as array in the returned struct. -// This function has many race conditions, it is best effort but cannot guarantee -// a perfect state since plugins can be modified from the outside at any time. -func (r *Runtime) UpdateVolumePlugins(ctx context.Context) *define.VolumeReload { - var ( - added []string - removed []string - errs []error - allPluginVolumes = map[string]struct{}{} - ) - - for driverName, socket := range r.config.Engine.VolumePlugins { - driver, err := volplugin.GetVolumePlugin(driverName, socket, nil, r.config) - if err != nil { - errs = append(errs, err) - continue - } - vols, err := driver.ListVolumes() - if err != nil { - errs = append(errs, fmt.Errorf("failed to read volumes from plugin %q: %w", driverName, err)) - continue - } - for _, vol := range vols { - allPluginVolumes[vol.Name] = struct{}{} - if _, err := r.newVolume(ctx, true, WithVolumeName(vol.Name), WithVolumeDriver(driverName)); err != nil { - // If the volume exists this is not an error, just ignore it and log. It is very likely - // that the volume from the plugin was already in our db. - if !errors.Is(err, define.ErrVolumeExists) { - errs = append(errs, err) - continue - } - logrus.Infof("Volume %q already exists: %v", vol.Name, err) - continue - } - added = append(added, vol.Name) - } - } - - libpodVolumes, err := r.state.AllVolumes() - if err != nil { - errs = append(errs, fmt.Errorf("cannot delete dangling plugin volumes: failed to read libpod volumes: %w", err)) - } - for _, vol := range libpodVolumes { - if vol.UsesVolumeDriver() { - if _, ok := allPluginVolumes[vol.Name()]; !ok { - // The volume is no longer in the plugin. Let's remove it from the libpod db. - if err := r.removeVolume(ctx, vol, false, nil, true); err != nil { - if errors.Is(err, define.ErrVolumeBeingUsed) { - // Volume is still used by at least one container. This is very bad, - // the plugin no longer has this but we still need it. - errs = append(errs, fmt.Errorf("volume was removed from the plugin %q but containers still require it: %w", vol.config.Driver, err)) - continue - } - if errors.Is(err, define.ErrNoSuchVolume) || errors.Is(err, define.ErrVolumeRemoved) || errors.Is(err, define.ErrMissingPlugin) { - // Volume was already removed, no problem just ignore it and continue. - continue - } - - // some other error - errs = append(errs, err) - continue - } - // Volume was successfully removed - removed = append(removed, vol.Name()) - } - } - } - - return &define.VolumeReload{ - Added: added, - Removed: removed, - Errors: errs, - } -} - -// makeVolumeInPluginIfNotExist makes a volume in the given volume plugin if it -// does not already exist. -func makeVolumeInPluginIfNotExist(name string, options map[string]string, plugin *volplugin.VolumePlugin) error { - // Ping the volume plugin to see if it exists first. - // If it does, use the existing volume in the plugin. - // Options may not match exactly, but not much we can do about - // that. Not complaining avoids a lot of the sync issues we see - // with c/storage and libpod DB. - needsCreate := true - getReq := new(pluginapi.GetRequest) - getReq.Name = name - if resp, err := plugin.GetVolume(getReq); err == nil { - // TODO: What do we do if we get a 200 response, but the - // Volume is nil? The docs on the Plugin API are very - // nonspecific, so I don't know if this is valid or - // not... - if resp != nil { - needsCreate = false - logrus.Infof("Volume %q already exists in plugin %q, using existing volume", name, plugin.Name) - } - } - if needsCreate { - createReq := new(pluginapi.CreateRequest) - createReq.Name = name - createReq.Options = options - if err := plugin.CreateVolume(createReq); err != nil { - return fmt.Errorf("creating volume %q in plugin %s: %w", name, plugin.Name, err) - } - } - - return nil -} - -// removeVolume removes the specified volume from state as well tears down its mountpoint and storage. -// ignoreVolumePlugin is used to only remove the volume from the db and not the plugin, -// this is required when the volume was already removed from the plugin, i.e. in UpdateVolumePlugins(). -func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool, timeout *uint, ignoreVolumePlugin bool) error { - if !v.valid { - if ok, _ := r.state.HasVolume(v.Name()); !ok { - return nil - } - return define.ErrVolumeRemoved - } - - v.lock.Lock() - defer v.lock.Unlock() - - // Update volume status to pick up a potential removal from state - if err := v.update(); err != nil { - return err - } - - deps, err := r.state.VolumeInUse(v) - if err != nil { - return err - } - if len(deps) != 0 { - depsStr := strings.Join(deps, ", ") - if !force { - return fmt.Errorf("volume %s is being used by the following container(s): %s: %w", v.Name(), depsStr, define.ErrVolumeBeingUsed) - } - - // We need to remove all containers using the volume - for _, dep := range deps { - ctr, err := r.state.Container(dep) - if err != nil { - // If the container's removed, no point in - // erroring. - if errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrRemoved) { - continue - } - - return fmt.Errorf("removing container %s that depends on volume %s: %w", dep, v.Name(), err) - } - - logrus.Debugf("Removing container %s (depends on volume %q)", ctr.ID(), v.Name()) - - if err := r.removeContainer(ctx, ctr, force, false, false, false, timeout); err != nil { - return fmt.Errorf("removing container %s that depends on volume %s: %w", ctr.ID(), v.Name(), err) - } - } - } - - // If the volume is still mounted - force unmount it - if err := v.unmount(true); err != nil { - if force { - // If force is set, evict the volume, even if errors - // occur. Otherwise we'll never be able to get rid of - // them. - logrus.Errorf("Unmounting volume %s: %v", v.Name(), err) - } else { - return fmt.Errorf("unmounting volume %s: %w", v.Name(), err) - } - } - - // Set volume as invalid so it can no longer be used - v.valid = false - - var removalErr error - - // If we use a volume plugin, we need to remove from the plugin. - if v.UsesVolumeDriver() && !ignoreVolumePlugin { - canRemove := true - - // Do we have a volume driver? - if v.plugin == nil { - canRemove = false - removalErr = fmt.Errorf("cannot remove volume %s from plugin %s, but it has been removed from Podman: %w", v.Name(), v.Driver(), define.ErrMissingPlugin) - } else { - // Ping the plugin first to verify the volume still - // exists. - // We're trying to be very tolerant of missing volumes - // in the backend, to avoid the problems we see with - // sync between c/storage and the Libpod DB. - getReq := new(pluginapi.GetRequest) - getReq.Name = v.Name() - if _, err := v.plugin.GetVolume(getReq); err != nil { - canRemove = false - removalErr = fmt.Errorf("volume %s could not be retrieved from plugin %s, but it has been removed from Podman: %w", v.Name(), v.Driver(), err) - } - } - if canRemove { - req := new(pluginapi.RemoveRequest) - req.Name = v.Name() - if err := v.plugin.RemoveVolume(req); err != nil { - return fmt.Errorf("volume %s could not be removed from plugin %s: %w", v.Name(), v.Driver(), err) - } - } - } else if v.config.Driver == define.VolumeDriverImage { - if err := v.runtime.storageService.DeleteContainer(v.config.StorageID); err != nil { - // Storage container is already gone, no problem. - if !(errors.Is(err, storage.ErrNotAContainer) || errors.Is(err, storage.ErrContainerUnknown)) { - return fmt.Errorf("removing volume %s storage: %w", v.Name(), err) - } - logrus.Infof("Storage for volume %s already removed", v.Name()) - } - } - - // Remove the volume from the state - if err := r.state.RemoveVolume(v); err != nil { - if removalErr != nil { - logrus.Errorf("Removing volume %s from plugin %s: %v", v.Name(), v.Driver(), removalErr) - } - return fmt.Errorf("removing volume %s: %w", v.Name(), err) - } - - // Free the volume's lock - if err := v.lock.Free(); err != nil { - if removalErr == nil { - removalErr = fmt.Errorf("freeing lock for volume %s: %w", v.Name(), err) - } else { - logrus.Errorf("Freeing lock for volume %q: %v", v.Name(), err) - } - } - - // Delete the mountpoint path of the volume, that is delete the volume - // from /var/lib/containers/storage/volumes - if err := v.teardownStorage(); err != nil { - if removalErr == nil { - removalErr = fmt.Errorf("cleaning up volume storage for %q: %w", v.Name(), err) - } else { - logrus.Errorf("Cleaning up volume storage for volume %q: %v", v.Name(), err) - } - } - - defer v.newVolumeEvent(events.Remove) - logrus.Debugf("Removed volume %s", v.Name()) - return removalErr -} -- cgit v1.2.3-54-g00ecf From 9de2a5ff795f25da0d887304db3559d06832caef Mon Sep 17 00:00:00 2001 From: Doug Rabson Date: Tue, 20 Sep 2022 11:25:20 +0100 Subject: libpod: Move volume_internal_linux.go to volume_internal_common.go [NO NEW TESTS NEEDED] Signed-off-by: Doug Rabson --- libpod/volume_internal_common.go | 194 +++++++++++++++++++++++++++++++++++++++ libpod/volume_internal_linux.go | 194 --------------------------------------- 2 files changed, 194 insertions(+), 194 deletions(-) create mode 100644 libpod/volume_internal_common.go delete mode 100644 libpod/volume_internal_linux.go (limited to 'libpod') diff --git a/libpod/volume_internal_common.go b/libpod/volume_internal_common.go new file mode 100644 index 000000000..440bceec3 --- /dev/null +++ b/libpod/volume_internal_common.go @@ -0,0 +1,194 @@ +//go:build linux +// +build linux + +package libpod + +import ( + "errors" + "fmt" + "os/exec" + "strings" + + "github.com/containers/podman/v4/libpod/define" + pluginapi "github.com/docker/go-plugins-helpers/volume" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +// This is a pseudo-container ID to use when requesting a mount or unmount from +// the volume plugins. +// This is the shas256 of the string "placeholder\n". +const pseudoCtrID = "2f73349cfc4630255319c6c8dfc1b46a8996ace9d14d8e07563b165915918ec2" + +// mount mounts the volume if necessary. +// A mount is necessary if a volume has any options set. +// If a mount is necessary, v.state.MountCount will be incremented. +// If it was 0 when the increment occurred, the volume will be mounted on the +// host. Otherwise, we assume it is already mounted. +// Must be done while the volume is locked. +// Is a no-op on volumes that do not require a mount (as defined by +// volumeNeedsMount()). +func (v *Volume) mount() error { + if !v.needsMount() { + return nil + } + + // Update the volume from the DB to get an accurate mount counter. + if err := v.update(); err != nil { + return err + } + + // If the count is non-zero, the volume is already mounted. + // Nothing to do. + if v.state.MountCount > 0 { + v.state.MountCount++ + logrus.Debugf("Volume %s mount count now at %d", v.Name(), v.state.MountCount) + return v.save() + } + + // Volume plugins implement their own mount counter, based on the ID of + // the mounting container. But we already have one, and honestly I trust + // ours more. So hardcode container ID to something reasonable, and use + // the same one for everything. + if v.UsesVolumeDriver() { + if v.plugin == nil { + return fmt.Errorf("volume plugin %s (needed by volume %s) missing: %w", v.Driver(), v.Name(), define.ErrMissingPlugin) + } + + req := new(pluginapi.MountRequest) + req.Name = v.Name() + req.ID = pseudoCtrID + mountPoint, err := v.plugin.MountVolume(req) + if err != nil { + return err + } + + v.state.MountCount++ + v.state.MountPoint = mountPoint + return v.save() + } else if v.config.Driver == define.VolumeDriverImage { + mountPoint, err := v.runtime.storageService.MountContainerImage(v.config.StorageID) + if err != nil { + return fmt.Errorf("mounting volume %s image failed: %w", v.Name(), err) + } + + v.state.MountCount++ + v.state.MountPoint = mountPoint + return v.save() + } + + volDevice := v.config.Options["device"] + volType := v.config.Options["type"] + volOptions := v.config.Options["o"] + + // Some filesystems (tmpfs) don't have a device, but we still need to + // give the kernel something. + if volDevice == "" && volType != "" { + volDevice = volType + } + + // We need to use the actual mount command. + // Convincing unix.Mount to use the same semantics as the mount command + // itself seems prohibitively difficult. + // TODO: might want to cache this path in the runtime? + mountPath, err := exec.LookPath("mount") + if err != nil { + return fmt.Errorf("locating 'mount' binary: %w", err) + } + mountArgs := []string{} + if volOptions != "" { + mountArgs = append(mountArgs, "-o", volOptions) + } + switch volType { + case "": + case "bind": + mountArgs = append(mountArgs, "-o", volType) + default: + mountArgs = append(mountArgs, "-t", volType) + } + + mountArgs = append(mountArgs, volDevice, v.config.MountPoint) + mountCmd := exec.Command(mountPath, mountArgs...) + + logrus.Debugf("Running mount command: %s %s", mountPath, strings.Join(mountArgs, " ")) + if output, err := mountCmd.CombinedOutput(); err != nil { + logrus.Debugf("Mount %v failed with %v", mountCmd, err) + return errors.New(string(output)) + } + + logrus.Debugf("Mounted volume %s", v.Name()) + + // Increment the mount counter + v.state.MountCount++ + logrus.Debugf("Volume %s mount count now at %d", v.Name(), v.state.MountCount) + return v.save() +} + +// unmount unmounts the volume if necessary. +// Unmounting a volume that is not mounted is a no-op. +// Unmounting a volume that does not require a mount is a no-op. +// The volume must be locked for this to occur. +// The mount counter will be decremented if non-zero. If the counter reaches 0, +// the volume will really be unmounted, as no further containers are using the +// volume. +// If force is set, the volume will be unmounted regardless of mount counter. +func (v *Volume) unmount(force bool) error { + if !v.needsMount() { + return nil + } + + // Update the volume from the DB to get an accurate mount counter. + if err := v.update(); err != nil { + return err + } + + if v.state.MountCount == 0 { + logrus.Debugf("Volume %s already unmounted", v.Name()) + return nil + } + + if !force { + v.state.MountCount-- + } else { + v.state.MountCount = 0 + } + + logrus.Debugf("Volume %s mount count now at %d", v.Name(), v.state.MountCount) + + if v.state.MountCount == 0 { + if v.UsesVolumeDriver() { + if v.plugin == nil { + return fmt.Errorf("volume plugin %s (needed by volume %s) missing: %w", v.Driver(), v.Name(), define.ErrMissingPlugin) + } + + req := new(pluginapi.UnmountRequest) + req.Name = v.Name() + req.ID = pseudoCtrID + if err := v.plugin.UnmountVolume(req); err != nil { + return err + } + + v.state.MountPoint = "" + return v.save() + } else if v.config.Driver == define.VolumeDriverImage { + if _, err := v.runtime.storageService.UnmountContainerImage(v.config.StorageID, force); err != nil { + return fmt.Errorf("unmounting volume %s image: %w", v.Name(), err) + } + + v.state.MountPoint = "" + return v.save() + } + + // Unmount the volume + if err := unix.Unmount(v.config.MountPoint, unix.MNT_DETACH); err != nil { + if err == unix.EINVAL { + // Ignore EINVAL - the mount no longer exists. + return nil + } + return fmt.Errorf("unmounting volume %s: %w", v.Name(), err) + } + logrus.Debugf("Unmounted volume %s", v.Name()) + } + + return v.save() +} diff --git a/libpod/volume_internal_linux.go b/libpod/volume_internal_linux.go deleted file mode 100644 index 440bceec3..000000000 --- a/libpod/volume_internal_linux.go +++ /dev/null @@ -1,194 +0,0 @@ -//go:build linux -// +build linux - -package libpod - -import ( - "errors" - "fmt" - "os/exec" - "strings" - - "github.com/containers/podman/v4/libpod/define" - pluginapi "github.com/docker/go-plugins-helpers/volume" - "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" -) - -// This is a pseudo-container ID to use when requesting a mount or unmount from -// the volume plugins. -// This is the shas256 of the string "placeholder\n". -const pseudoCtrID = "2f73349cfc4630255319c6c8dfc1b46a8996ace9d14d8e07563b165915918ec2" - -// mount mounts the volume if necessary. -// A mount is necessary if a volume has any options set. -// If a mount is necessary, v.state.MountCount will be incremented. -// If it was 0 when the increment occurred, the volume will be mounted on the -// host. Otherwise, we assume it is already mounted. -// Must be done while the volume is locked. -// Is a no-op on volumes that do not require a mount (as defined by -// volumeNeedsMount()). -func (v *Volume) mount() error { - if !v.needsMount() { - return nil - } - - // Update the volume from the DB to get an accurate mount counter. - if err := v.update(); err != nil { - return err - } - - // If the count is non-zero, the volume is already mounted. - // Nothing to do. - if v.state.MountCount > 0 { - v.state.MountCount++ - logrus.Debugf("Volume %s mount count now at %d", v.Name(), v.state.MountCount) - return v.save() - } - - // Volume plugins implement their own mount counter, based on the ID of - // the mounting container. But we already have one, and honestly I trust - // ours more. So hardcode container ID to something reasonable, and use - // the same one for everything. - if v.UsesVolumeDriver() { - if v.plugin == nil { - return fmt.Errorf("volume plugin %s (needed by volume %s) missing: %w", v.Driver(), v.Name(), define.ErrMissingPlugin) - } - - req := new(pluginapi.MountRequest) - req.Name = v.Name() - req.ID = pseudoCtrID - mountPoint, err := v.plugin.MountVolume(req) - if err != nil { - return err - } - - v.state.MountCount++ - v.state.MountPoint = mountPoint - return v.save() - } else if v.config.Driver == define.VolumeDriverImage { - mountPoint, err := v.runtime.storageService.MountContainerImage(v.config.StorageID) - if err != nil { - return fmt.Errorf("mounting volume %s image failed: %w", v.Name(), err) - } - - v.state.MountCount++ - v.state.MountPoint = mountPoint - return v.save() - } - - volDevice := v.config.Options["device"] - volType := v.config.Options["type"] - volOptions := v.config.Options["o"] - - // Some filesystems (tmpfs) don't have a device, but we still need to - // give the kernel something. - if volDevice == "" && volType != "" { - volDevice = volType - } - - // We need to use the actual mount command. - // Convincing unix.Mount to use the same semantics as the mount command - // itself seems prohibitively difficult. - // TODO: might want to cache this path in the runtime? - mountPath, err := exec.LookPath("mount") - if err != nil { - return fmt.Errorf("locating 'mount' binary: %w", err) - } - mountArgs := []string{} - if volOptions != "" { - mountArgs = append(mountArgs, "-o", volOptions) - } - switch volType { - case "": - case "bind": - mountArgs = append(mountArgs, "-o", volType) - default: - mountArgs = append(mountArgs, "-t", volType) - } - - mountArgs = append(mountArgs, volDevice, v.config.MountPoint) - mountCmd := exec.Command(mountPath, mountArgs...) - - logrus.Debugf("Running mount command: %s %s", mountPath, strings.Join(mountArgs, " ")) - if output, err := mountCmd.CombinedOutput(); err != nil { - logrus.Debugf("Mount %v failed with %v", mountCmd, err) - return errors.New(string(output)) - } - - logrus.Debugf("Mounted volume %s", v.Name()) - - // Increment the mount counter - v.state.MountCount++ - logrus.Debugf("Volume %s mount count now at %d", v.Name(), v.state.MountCount) - return v.save() -} - -// unmount unmounts the volume if necessary. -// Unmounting a volume that is not mounted is a no-op. -// Unmounting a volume that does not require a mount is a no-op. -// The volume must be locked for this to occur. -// The mount counter will be decremented if non-zero. If the counter reaches 0, -// the volume will really be unmounted, as no further containers are using the -// volume. -// If force is set, the volume will be unmounted regardless of mount counter. -func (v *Volume) unmount(force bool) error { - if !v.needsMount() { - return nil - } - - // Update the volume from the DB to get an accurate mount counter. - if err := v.update(); err != nil { - return err - } - - if v.state.MountCount == 0 { - logrus.Debugf("Volume %s already unmounted", v.Name()) - return nil - } - - if !force { - v.state.MountCount-- - } else { - v.state.MountCount = 0 - } - - logrus.Debugf("Volume %s mount count now at %d", v.Name(), v.state.MountCount) - - if v.state.MountCount == 0 { - if v.UsesVolumeDriver() { - if v.plugin == nil { - return fmt.Errorf("volume plugin %s (needed by volume %s) missing: %w", v.Driver(), v.Name(), define.ErrMissingPlugin) - } - - req := new(pluginapi.UnmountRequest) - req.Name = v.Name() - req.ID = pseudoCtrID - if err := v.plugin.UnmountVolume(req); err != nil { - return err - } - - v.state.MountPoint = "" - return v.save() - } else if v.config.Driver == define.VolumeDriverImage { - if _, err := v.runtime.storageService.UnmountContainerImage(v.config.StorageID, force); err != nil { - return fmt.Errorf("unmounting volume %s image: %w", v.Name(), err) - } - - v.state.MountPoint = "" - return v.save() - } - - // Unmount the volume - if err := unix.Unmount(v.config.MountPoint, unix.MNT_DETACH); err != nil { - if err == unix.EINVAL { - // Ignore EINVAL - the mount no longer exists. - return nil - } - return fmt.Errorf("unmounting volume %s: %w", v.Name(), err) - } - logrus.Debugf("Unmounted volume %s", v.Name()) - } - - return v.save() -} -- cgit v1.2.3-54-g00ecf From abe8dad3449b38dee845358d0da5ababa8d8c157 Mon Sep 17 00:00:00 2001 From: Doug Rabson Date: Tue, 20 Sep 2022 11:30:12 +0100 Subject: libpod: Factor out usage of unix.MNT_DETACH from (*Volume).unmount There is an existing wrapper for unix.Unmount(..., MNT_DETACH) in util_linux.go but that filters all errors and for volumes, we only want to filter EINVAL. The existing libpod.Unmount seems to only have one call site so perhaps these can be merged. [NO NEW TESTS NEEDED] Signed-off-by: Doug Rabson --- libpod/volume_internal_common.go | 2 +- libpod/volume_internal_linux.go | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 libpod/volume_internal_linux.go (limited to 'libpod') diff --git a/libpod/volume_internal_common.go b/libpod/volume_internal_common.go index 440bceec3..c85782e4b 100644 --- a/libpod/volume_internal_common.go +++ b/libpod/volume_internal_common.go @@ -180,7 +180,7 @@ func (v *Volume) unmount(force bool) error { } // Unmount the volume - if err := unix.Unmount(v.config.MountPoint, unix.MNT_DETACH); err != nil { + if err := detachUnmount(v.config.MountPoint); err != nil { if err == unix.EINVAL { // Ignore EINVAL - the mount no longer exists. return nil diff --git a/libpod/volume_internal_linux.go b/libpod/volume_internal_linux.go new file mode 100644 index 000000000..eb4309dc3 --- /dev/null +++ b/libpod/volume_internal_linux.go @@ -0,0 +1,9 @@ +package libpod + +import ( + "golang.org/x/sys/unix" +) + +func detachUnmount(mountPoint string) error { + return unix.Unmount(mountPoint, unix.MNT_DETACH) +} -- cgit v1.2.3-54-g00ecf From 359e397443093f038ae7a8c4d0d0e5f07b5c6506 Mon Sep 17 00:00:00 2001 From: Doug Rabson Date: Tue, 20 Sep 2022 11:26:58 +0100 Subject: libpod: Add volume support for FreeBSD [NO NEW TESTS NEEDED] Signed-off-by: Doug Rabson --- libpod/runtime_volume_common.go | 4 ++-- libpod/runtime_volume_unsupported.go | 4 ++-- libpod/util_freebsd.go | 36 +++++++++++++++++++++++++++++++++++ libpod/util_unsupported.go | 4 ++-- libpod/volume_internal_common.go | 4 ++-- libpod/volume_internal_freebsd.go | 9 +++++++++ libpod/volume_internal_unsupported.go | 4 ++-- 7 files changed, 55 insertions(+), 10 deletions(-) create mode 100644 libpod/util_freebsd.go create mode 100644 libpod/volume_internal_freebsd.go (limited to 'libpod') diff --git a/libpod/runtime_volume_common.go b/libpod/runtime_volume_common.go index c59417979..b1de2be86 100644 --- a/libpod/runtime_volume_common.go +++ b/libpod/runtime_volume_common.go @@ -1,5 +1,5 @@ -//go:build linux -// +build linux +//go:build linux || freebsd +// +build linux freebsd package libpod diff --git a/libpod/runtime_volume_unsupported.go b/libpod/runtime_volume_unsupported.go index c2816b817..7b7758894 100644 --- a/libpod/runtime_volume_unsupported.go +++ b/libpod/runtime_volume_unsupported.go @@ -1,5 +1,5 @@ -//go:build !linux -// +build !linux +//go:build !linux && !freebsd +// +build !linux,!freebsd package libpod diff --git a/libpod/util_freebsd.go b/libpod/util_freebsd.go new file mode 100644 index 000000000..72019743c --- /dev/null +++ b/libpod/util_freebsd.go @@ -0,0 +1,36 @@ +//go:build freebsd +// +build freebsd + +package libpod + +import ( + "errors" + "syscall" + + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +// systemdSliceFromPath makes a new systemd slice under the given parent with +// the given name. +// The parent must be a slice. The name must NOT include ".slice" +func systemdSliceFromPath(parent, name string, resources *spec.LinuxResources) (string, error) { + return "", errors.New("not implemented systemdSliceFromPath") +} + +// No equivalent on FreeBSD? +func LabelVolumePath(path string) error { + return nil +} + +// Unmount umounts a target directory +func Unmount(mount string) { + if err := unix.Unmount(mount, unix.MNT_FORCE); err != nil { + if err != syscall.EINVAL { + logrus.Warnf("Failed to unmount %s : %v", mount, err) + } else { + logrus.Debugf("failed to unmount %s : %v", mount, err) + } + } +} diff --git a/libpod/util_unsupported.go b/libpod/util_unsupported.go index d2ec3ae7b..fc3d00274 100644 --- a/libpod/util_unsupported.go +++ b/libpod/util_unsupported.go @@ -1,5 +1,5 @@ -//go:build !linux -// +build !linux +//go:build !linux && !freebsd +// +build !linux,!freebsd package libpod diff --git a/libpod/volume_internal_common.go b/libpod/volume_internal_common.go index c85782e4b..4ff7ac790 100644 --- a/libpod/volume_internal_common.go +++ b/libpod/volume_internal_common.go @@ -1,5 +1,5 @@ -//go:build linux -// +build linux +//go:build linux || freebsd +// +build linux freebsd package libpod diff --git a/libpod/volume_internal_freebsd.go b/libpod/volume_internal_freebsd.go new file mode 100644 index 000000000..cf71f1e32 --- /dev/null +++ b/libpod/volume_internal_freebsd.go @@ -0,0 +1,9 @@ +package libpod + +import ( + "golang.org/x/sys/unix" +) + +func detachUnmount(mountPoint string) error { + return unix.Unmount(mountPoint, unix.MNT_FORCE) +} diff --git a/libpod/volume_internal_unsupported.go b/libpod/volume_internal_unsupported.go index 50515e692..d138c15fb 100644 --- a/libpod/volume_internal_unsupported.go +++ b/libpod/volume_internal_unsupported.go @@ -1,5 +1,5 @@ -//go:build !linux -// +build !linux +//go:build !linux && !freebsd +// +build !linux,!freebsd package libpod -- cgit v1.2.3-54-g00ecf