pkg/mount: wrap mount/umount errors

The errors returned from Mount and Unmount functions are raw
syscall.Errno errors (like EPERM or EINVAL), which provides
no context about what has happened and why.

Similar to os.PathError type, introduce mount.Error type
with some context. The error messages will now look like this:

> mount /tmp/mount-tests/source:/tmp/mount-tests/target, flags: 0x1001: operation not permitted

or

> mount tmpfs:/tmp/mount-test-source-516297835: operation not permitted

Before this patch, it was just

> operation not permitted

[v2: add Cause()]
[v3: rename MountError to Error, document Cause()]
[v4: fixes; audited all users]
[v5: make Error type private; changes after @cpuguy83 reviews]

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
This commit is contained in:
Kir Kolyshkin 2018-10-22 18:30:34 -07:00
Родитель 90be078fe5
Коммит 6533136961
13 изменённых файлов: 88 добавлений и 25 удалений

Просмотреть файл

@ -17,7 +17,6 @@ import (
"github.com/docker/docker/volume"
volumemounts "github.com/docker/docker/volume/mounts"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
@ -190,7 +189,7 @@ func (container *Container) UnmountIpcMount() error {
return nil
}
if err = mount.Unmount(shmPath); err != nil && !os.IsNotExist(err) {
return errors.Wrapf(err, "umount %s", shmPath)
return err
}
return nil
}
@ -380,7 +379,8 @@ func (container *Container) DetachAndUnmount(volumeEventLog func(name, action st
for _, mountPath := range mountPaths {
if err := mount.Unmount(mountPath); err != nil {
logrus.Warnf("%s unmountVolumes: Failed to do lazy umount for volume '%s': %v", container.ID, mountPath, err)
logrus.WithError(err).WithField("container", container.ID).
Warn("Unable to unmount")
}
}
return container.UnmountVolumes(volumeEventLog)

Просмотреть файл

@ -178,7 +178,7 @@ func (d *Driver) Cleanup() error {
}
if umountErr != nil {
return errors.Wrapf(umountErr, "error unmounting %s", d.home)
return umountErr
}
return nil

Просмотреть файл

@ -1200,7 +1200,7 @@ func (devices *DeviceSet) growFS(info *devInfo) error {
options = joinMountOptions(options, devices.mountOptions)
if err := mount.Mount(info.DevName(), fsMountPoint, devices.BaseDeviceFilesystem, options); err != nil {
return fmt.Errorf("Error mounting '%s' on '%s' (fstype='%s' options='%s'): %s\n%v", info.DevName(), fsMountPoint, devices.BaseDeviceFilesystem, options, err, string(dmesg.Dmesg(256)))
return errors.Wrapf(err, "Failed to mount; dmesg: %s", string(dmesg.Dmesg(256)))
}
defer unix.Unmount(fsMountPoint, unix.MNT_DETACH)
@ -2381,7 +2381,7 @@ func (devices *DeviceSet) MountDevice(hash, path, mountLabel string) error {
options = joinMountOptions(options, label.FormatMountLabel("", mountLabel))
if err := mount.Mount(info.DevName(), path, fstype, options); err != nil {
return fmt.Errorf("devmapper: Error mounting '%s' on '%s' (fstype='%s' options='%s'): %s\n%v", info.DevName(), path, fstype, options, err, string(dmesg.Dmesg(256)))
return errors.Wrapf(err, "Failed to mount; dmesg: %s", string(dmesg.Dmesg(256)))
}
if fstype == "xfs" && devices.xfsNospaceRetries != "" {

Просмотреть файл

@ -16,7 +16,6 @@ import (
"github.com/docker/docker/pkg/locker"
"github.com/docker/docker/pkg/mount"
"github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
@ -129,11 +128,7 @@ func (d *Driver) Cleanup() error {
return err
}
if umountErr != nil {
return errors.Wrapf(umountErr, "error unmounting %s", d.home)
}
return nil
return umountErr
}
// CreateReadWrite creates a layer that is writable for use as a container

Просмотреть файл

@ -19,6 +19,7 @@ import (
"github.com/docker/docker/pkg/parsers"
"github.com/mistifyio/go-zfs"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
@ -390,7 +391,7 @@ func (d *Driver) Get(id, mountLabel string) (_ containerfs.ContainerFS, retErr e
}
if err := mount.Mount(filesystem, mountpoint, "zfs", options); err != nil {
return nil, fmt.Errorf("error creating zfs mount of %s to %s: %v", filesystem, mountpoint, err)
return nil, errors.Wrap(err, "error creating zfs mount")
}
// this could be our first mount after creation of the filesystem, and the root dir may still have root

Просмотреть файл

@ -2,11 +2,46 @@ package mount // import "github.com/docker/docker/pkg/mount"
import (
"sort"
"strconv"
"strings"
"github.com/sirupsen/logrus"
)
// mountError records an error from mount or unmount operation
type mountError struct {
op string
source, target string
flags uintptr
data string
err error
}
func (e *mountError) Error() string {
out := e.op + " "
if e.source != "" {
out += e.source + ":" + e.target
} else {
out += e.target
}
if e.flags != uintptr(0) {
out += ", flags: 0x" + strconv.FormatUint(uint64(e.flags), 16)
}
if e.data != "" {
out += ", data: " + e.data
}
out += ": " + e.err.Error()
return out
}
// Cause returns the underlying cause of the error
func (e *mountError) Cause() error {
return e.err
}
// FilterFunc is a type defining a callback function
// to filter out unwanted entries. It takes a pointer
// to an Info struct (not fully populated, currently

Просмотреть файл

@ -11,8 +11,8 @@ package mount // import "github.com/docker/docker/pkg/mount"
import "C"
import (
"fmt"
"strings"
"syscall"
"unsafe"
)
@ -47,8 +47,13 @@ func mount(device, target, mType string, flag uintptr, data string) error {
}
if errno := C.nmount(&rawOptions[0], C.uint(len(options)), C.int(flag)); errno != 0 {
reason := C.GoString(C.strerror(*C.__error()))
return fmt.Errorf("Failed to call nmount: %s", reason)
return &mountError{
op: "mount",
source: device,
target: target,
flags: flag,
err: syscall.Errno(errno),
}
}
return nil
}

Просмотреть файл

@ -33,20 +33,41 @@ func mount(device, target, mType string, flags uintptr, data string) error {
// Initial call applying all non-propagation flags for mount
// or remount with changed data
if err := unix.Mount(device, target, mType, oflags, data); err != nil {
return err
return &mountError{
op: "mount",
source: device,
target: target,
flags: oflags,
data: data,
err: err,
}
}
}
if flags&ptypes != 0 {
// Change the propagation type.
if err := unix.Mount("", target, "", flags&pflags, ""); err != nil {
return &mountError{
op: "remount",
target: target,
flags: flags & pflags,
err: err,
}
return err
}
}
if oflags&broflags == broflags {
// Remount the bind to apply read only.
return unix.Mount("", target, "", oflags|unix.MS_REMOUNT, "")
if err := unix.Mount("", target, "", oflags|unix.MS_REMOUNT, ""); err != nil {
return &mountError{
op: "remount-ro",
target: target,
flags: oflags | unix.MS_REMOUNT,
err: err,
}
}
}
return nil

Просмотреть файл

@ -7,6 +7,7 @@ import (
"path"
"testing"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
@ -326,7 +327,7 @@ func TestSubtreeUnbindable(t *testing.T) {
}()
// then attempt to mount it to target. It should fail
if err := Mount(sourceDir, targetDir, "none", "bind,rw"); err != nil && err != unix.EINVAL {
if err := Mount(sourceDir, targetDir, "none", "bind,rw"); err != nil && errors.Cause(err) != unix.EINVAL {
t.Fatal(err)
} else if err == nil {
t.Fatalf("%q should not have been bindable", sourceDir)

Просмотреть файл

@ -6,12 +6,17 @@ import "golang.org/x/sys/unix"
func unmount(target string, flags int) error {
err := unix.Unmount(target, flags)
if err == unix.EINVAL {
if err == nil || err == unix.EINVAL {
// Ignore "not mounted" error here. Note the same error
// can be returned if flags are invalid, so this code
// assumes that the flags value is always correct.
err = nil
return nil
}
return err
return &mountError{
op: "umount",
target: target,
flags: uintptr(flags),
err: err,
}
}

Просмотреть файл

@ -61,7 +61,7 @@ func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) error {
if err := pm.executor.Create(p.GetID(), *spec, stdout, stderr); err != nil {
if p.PluginObj.Config.PropagatedMount != "" {
if err := mount.Unmount(propRoot); err != nil {
logrus.Warnf("Could not unmount %s: %v", propRoot, err)
logrus.WithField("plugin", p.Name()).WithError(err).Warn("Failed to unmount vplugin propagated mount root")
}
}
return errors.WithStack(err)

Просмотреть файл

@ -344,7 +344,7 @@ func (v *localVolume) unmount() error {
if v.opts != nil {
if err := mount.Unmount(v.path); err != nil {
if mounted, mErr := mount.Mounted(v.path); mounted || mErr != nil {
return errdefs.System(errors.Wrapf(err, "error while unmounting volume path '%s'", v.path))
return errdefs.System(err)
}
}
v.active.mounted = false

Просмотреть файл

@ -86,7 +86,7 @@ func (v *localVolume) mount() error {
}
}
err := mount.Mount(v.opts.MountDevice, v.path, v.opts.MountType, mountOpts)
return errors.Wrapf(err, "error while mounting volume with options: %s", v.opts)
return errors.Wrap(err, "failed to mount local volume")
}
func (v *localVolume) CreatedAt() (time.Time, error) {