podman/libpod/volume_internal_linux.go
Matthew Heon b53cb57680 Initial implementation of volume plugins
This implements support for mounting and unmounting volumes
backed by volume plugins. Support for actually retrieving
plugins requires a pull request to land in containers.conf and
then that to be vendored, and as such is not yet ready. Given
this, this code is only compile tested. However, the code for
everything past retrieving the plugin has been written - there is
support for creating, removing, mounting, and unmounting volumes,
which should allow full functionality once the c/common PR is
merged.

A major change is the signature of the MountPoint function for
volumes, which now, by necessity, returns an error. Named volumes
managed by a plugin do not have a mountpoint we control; instead,
it is managed entirely by the plugin. As such, we need to cache
the path in the DB, and calls to retrieve it now need to access
the DB (and may fail as such).

Notably absent is support for SELinux relabelling and chowning
these volumes. Given that we don't manage the mountpoint for
these volumes, I am extremely reluctant to try and modify it - we
could easily break the plugin trying to chown or relabel it.

Also, we had no less than *5* separate implementations of
inspecting a volume floating around in pkg/infra/abi and
pkg/api/handlers/libpod. And none of them used volume.Inspect(),
the only correct way of inspecting volumes. Remove them all and
consolidate to using the correct way. Compat API is likely still
doing things the wrong way, but that is an issue for another day.

Fixes #4304

Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2021-01-14 15:35:33 -05:00

192 lines
5.7 KiB
Go

// +build linux
package libpod
import (
"os/exec"
"strings"
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/pkg/rootless"
pluginapi "github.com/docker/go-plugins-helpers/volume"
"github.com/pkg/errors"
"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
}
// We cannot mount volumes as rootless.
if rootless.IsRootless() {
return errors.Wrapf(define.ErrRootless, "cannot mount volumes without root privileges")
}
// 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 += 1
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 errors.Wrapf(define.ErrMissingPlugin, "volume plugin %s (needed by volume %s) missing", v.Driver(), v.Name())
}
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 += 1
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 errors.Wrapf(err, "error locating 'mount' binary")
}
mountArgs := []string{}
if volOptions != "" {
mountArgs = append(mountArgs, "-o", volOptions)
}
if volType != "" {
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 failed with %v", err)
return errors.Wrapf(errors.Errorf(string(output)), "error mounting volume %s", v.Name())
}
logrus.Debugf("Mounted volume %s", v.Name())
// Increment the mount counter
v.state.MountCount += 1
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
}
// We cannot unmount volumes as rootless.
if rootless.IsRootless() {
// If force is set, just clear the counter and bail without
// error, so we can remove volumes from the state if they are in
// an awkward configuration.
if force {
logrus.Errorf("Volume %s is mounted despite being rootless - state is not sane", v.Name())
v.state.MountCount = 0
return v.save()
}
return errors.Wrapf(define.ErrRootless, "cannot mount or unmount volumes without root privileges")
}
if !force {
v.state.MountCount -= 1
} 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 errors.Wrapf(define.ErrMissingPlugin, "volume plugin %s (needed by volume %s) missing", v.Driver(), v.Name())
}
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()
}
// 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 errors.Wrapf(err, "error unmounting volume %s", v.Name())
}
logrus.Debugf("Unmounted volume %s", v.Name())
}
return v.save()
}