Browse code

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>

Kir Kolyshkin authored on 2018/10/23 10:30:34
Showing 13 changed files
... ...
@@ -17,7 +17,6 @@ import (
17 17
 	"github.com/docker/docker/volume"
18 18
 	volumemounts "github.com/docker/docker/volume/mounts"
19 19
 	"github.com/opencontainers/selinux/go-selinux/label"
20
-	"github.com/pkg/errors"
21 20
 	"github.com/sirupsen/logrus"
22 21
 	"golang.org/x/sys/unix"
23 22
 )
... ...
@@ -190,7 +189,7 @@ func (container *Container) UnmountIpcMount() error {
190 190
 		return nil
191 191
 	}
192 192
 	if err = mount.Unmount(shmPath); err != nil && !os.IsNotExist(err) {
193
-		return errors.Wrapf(err, "umount %s", shmPath)
193
+		return err
194 194
 	}
195 195
 	return nil
196 196
 }
... ...
@@ -380,7 +379,8 @@ func (container *Container) DetachAndUnmount(volumeEventLog func(name, action st
380 380
 
381 381
 	for _, mountPath := range mountPaths {
382 382
 		if err := mount.Unmount(mountPath); err != nil {
383
-			logrus.Warnf("%s unmountVolumes: Failed to do lazy umount for volume '%s': %v", container.ID, mountPath, err)
383
+			logrus.WithError(err).WithField("container", container.ID).
384
+				Warn("Unable to unmount")
384 385
 		}
385 386
 	}
386 387
 	return container.UnmountVolumes(volumeEventLog)
... ...
@@ -178,7 +178,7 @@ func (d *Driver) Cleanup() error {
178 178
 	}
179 179
 
180 180
 	if umountErr != nil {
181
-		return errors.Wrapf(umountErr, "error unmounting %s", d.home)
181
+		return umountErr
182 182
 	}
183 183
 
184 184
 	return nil
... ...
@@ -1200,7 +1200,7 @@ func (devices *DeviceSet) growFS(info *devInfo) error {
1200 1200
 	options = joinMountOptions(options, devices.mountOptions)
1201 1201
 
1202 1202
 	if err := mount.Mount(info.DevName(), fsMountPoint, devices.BaseDeviceFilesystem, options); err != nil {
1203
-		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)))
1203
+		return errors.Wrapf(err, "Failed to mount; dmesg: %s", string(dmesg.Dmesg(256)))
1204 1204
 	}
1205 1205
 
1206 1206
 	defer unix.Unmount(fsMountPoint, unix.MNT_DETACH)
... ...
@@ -2381,7 +2381,7 @@ func (devices *DeviceSet) MountDevice(hash, path, mountLabel string) error {
2381 2381
 	options = joinMountOptions(options, label.FormatMountLabel("", mountLabel))
2382 2382
 
2383 2383
 	if err := mount.Mount(info.DevName(), path, fstype, options); err != nil {
2384
-		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)))
2384
+		return errors.Wrapf(err, "Failed to mount; dmesg: %s", string(dmesg.Dmesg(256)))
2385 2385
 	}
2386 2386
 
2387 2387
 	if fstype == "xfs" && devices.xfsNospaceRetries != "" {
... ...
@@ -16,7 +16,6 @@ import (
16 16
 	"github.com/docker/docker/pkg/locker"
17 17
 	"github.com/docker/docker/pkg/mount"
18 18
 	"github.com/docker/go-units"
19
-	"github.com/pkg/errors"
20 19
 	"github.com/sirupsen/logrus"
21 20
 	"golang.org/x/sys/unix"
22 21
 )
... ...
@@ -129,11 +128,7 @@ func (d *Driver) Cleanup() error {
129 129
 		return err
130 130
 	}
131 131
 
132
-	if umountErr != nil {
133
-		return errors.Wrapf(umountErr, "error unmounting %s", d.home)
134
-	}
135
-
136
-	return nil
132
+	return umountErr
137 133
 }
138 134
 
139 135
 // CreateReadWrite creates a layer that is writable for use as a container
... ...
@@ -19,6 +19,7 @@ import (
19 19
 	"github.com/docker/docker/pkg/parsers"
20 20
 	"github.com/mistifyio/go-zfs"
21 21
 	"github.com/opencontainers/selinux/go-selinux/label"
22
+	"github.com/pkg/errors"
22 23
 	"github.com/sirupsen/logrus"
23 24
 	"golang.org/x/sys/unix"
24 25
 )
... ...
@@ -390,7 +391,7 @@ func (d *Driver) Get(id, mountLabel string) (_ containerfs.ContainerFS, retErr e
390 390
 	}
391 391
 
392 392
 	if err := mount.Mount(filesystem, mountpoint, "zfs", options); err != nil {
393
-		return nil, fmt.Errorf("error creating zfs mount of %s to %s: %v", filesystem, mountpoint, err)
393
+		return nil, errors.Wrap(err, "error creating zfs mount")
394 394
 	}
395 395
 
396 396
 	// 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"
2 2
 
3 3
 import (
4 4
 	"sort"
5
+	"strconv"
5 6
 	"strings"
6 7
 
7 8
 	"github.com/sirupsen/logrus"
8 9
 )
9 10
 
11
+// mountError records an error from mount or unmount operation
12
+type mountError struct {
13
+	op             string
14
+	source, target string
15
+	flags          uintptr
16
+	data           string
17
+	err            error
18
+}
19
+
20
+func (e *mountError) Error() string {
21
+	out := e.op + " "
22
+
23
+	if e.source != "" {
24
+		out += e.source + ":" + e.target
25
+	} else {
26
+		out += e.target
27
+	}
28
+
29
+	if e.flags != uintptr(0) {
30
+		out += ", flags: 0x" + strconv.FormatUint(uint64(e.flags), 16)
31
+	}
32
+	if e.data != "" {
33
+		out += ", data: " + e.data
34
+	}
35
+
36
+	out += ": " + e.err.Error()
37
+	return out
38
+}
39
+
40
+// Cause returns the underlying cause of the error
41
+func (e *mountError) Cause() error {
42
+	return e.err
43
+}
44
+
10 45
 // FilterFunc is a type defining a callback function
11 46
 // to filter out unwanted entries. It takes a pointer
12 47
 // to an Info struct (not fully populated, currently
... ...
@@ -11,8 +11,8 @@ package mount // import "github.com/docker/docker/pkg/mount"
11 11
 import "C"
12 12
 
13 13
 import (
14
-	"fmt"
15 14
 	"strings"
15
+	"syscall"
16 16
 	"unsafe"
17 17
 )
18 18
 
... ...
@@ -47,8 +47,13 @@ func mount(device, target, mType string, flag uintptr, data string) error {
47 47
 	}
48 48
 
49 49
 	if errno := C.nmount(&rawOptions[0], C.uint(len(options)), C.int(flag)); errno != 0 {
50
-		reason := C.GoString(C.strerror(*C.__error()))
51
-		return fmt.Errorf("Failed to call nmount: %s", reason)
50
+		return &mountError{
51
+			op:     "mount",
52
+			source: device,
53
+			target: target,
54
+			flags:  flag,
55
+			err:    syscall.Errno(errno),
56
+		}
52 57
 	}
53 58
 	return nil
54 59
 }
... ...
@@ -33,20 +33,41 @@ func mount(device, target, mType string, flags uintptr, data string) error {
33 33
 		// Initial call applying all non-propagation flags for mount
34 34
 		// or remount with changed data
35 35
 		if err := unix.Mount(device, target, mType, oflags, data); err != nil {
36
-			return err
36
+			return &mountError{
37
+				op:     "mount",
38
+				source: device,
39
+				target: target,
40
+				flags:  oflags,
41
+				data:   data,
42
+				err:    err,
43
+			}
37 44
 		}
38 45
 	}
39 46
 
40 47
 	if flags&ptypes != 0 {
41 48
 		// Change the propagation type.
42 49
 		if err := unix.Mount("", target, "", flags&pflags, ""); err != nil {
50
+			return &mountError{
51
+				op:     "remount",
52
+				target: target,
53
+				flags:  flags & pflags,
54
+				err:    err,
55
+			}
43 56
 			return err
44 57
 		}
45 58
 	}
46 59
 
47 60
 	if oflags&broflags == broflags {
48 61
 		// Remount the bind to apply read only.
49
-		return unix.Mount("", target, "", oflags|unix.MS_REMOUNT, "")
62
+		if err := unix.Mount("", target, "", oflags|unix.MS_REMOUNT, ""); err != nil {
63
+			return &mountError{
64
+				op:     "remount-ro",
65
+				target: target,
66
+				flags:  oflags | unix.MS_REMOUNT,
67
+				err:    err,
68
+			}
69
+
70
+		}
50 71
 	}
51 72
 
52 73
 	return nil
... ...
@@ -7,6 +7,7 @@ import (
7 7
 	"path"
8 8
 	"testing"
9 9
 
10
+	"github.com/pkg/errors"
10 11
 	"golang.org/x/sys/unix"
11 12
 )
12 13
 
... ...
@@ -326,7 +327,7 @@ func TestSubtreeUnbindable(t *testing.T) {
326 326
 	}()
327 327
 
328 328
 	// then attempt to mount it to target. It should fail
329
-	if err := Mount(sourceDir, targetDir, "none", "bind,rw"); err != nil && err != unix.EINVAL {
329
+	if err := Mount(sourceDir, targetDir, "none", "bind,rw"); err != nil && errors.Cause(err) != unix.EINVAL {
330 330
 		t.Fatal(err)
331 331
 	} else if err == nil {
332 332
 		t.Fatalf("%q should not have been bindable", sourceDir)
... ...
@@ -6,12 +6,17 @@ import "golang.org/x/sys/unix"
6 6
 
7 7
 func unmount(target string, flags int) error {
8 8
 	err := unix.Unmount(target, flags)
9
-	if err == unix.EINVAL {
9
+	if err == nil || err == unix.EINVAL {
10 10
 		// Ignore "not mounted" error here. Note the same error
11 11
 		// can be returned if flags are invalid, so this code
12 12
 		// assumes that the flags value is always correct.
13
-		err = nil
13
+		return nil
14 14
 	}
15 15
 
16
-	return err
16
+	return &mountError{
17
+		op:     "umount",
18
+		target: target,
19
+		flags:  uintptr(flags),
20
+		err:    err,
21
+	}
17 22
 }
... ...
@@ -61,7 +61,7 @@ func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) error {
61 61
 	if err := pm.executor.Create(p.GetID(), *spec, stdout, stderr); err != nil {
62 62
 		if p.PluginObj.Config.PropagatedMount != "" {
63 63
 			if err := mount.Unmount(propRoot); err != nil {
64
-				logrus.Warnf("Could not unmount %s: %v", propRoot, err)
64
+				logrus.WithField("plugin", p.Name()).WithError(err).Warn("Failed to unmount vplugin propagated mount root")
65 65
 			}
66 66
 		}
67 67
 		return errors.WithStack(err)
... ...
@@ -344,7 +344,7 @@ func (v *localVolume) unmount() error {
344 344
 	if v.opts != nil {
345 345
 		if err := mount.Unmount(v.path); err != nil {
346 346
 			if mounted, mErr := mount.Mounted(v.path); mounted || mErr != nil {
347
-				return errdefs.System(errors.Wrapf(err, "error while unmounting volume path '%s'", v.path))
347
+				return errdefs.System(err)
348 348
 			}
349 349
 		}
350 350
 		v.active.mounted = false
... ...
@@ -86,7 +86,7 @@ func (v *localVolume) mount() error {
86 86
 		}
87 87
 	}
88 88
 	err := mount.Mount(v.opts.MountDevice, v.path, v.opts.MountType, mountOpts)
89
-	return errors.Wrapf(err, "error while mounting volume with options: %s", v.opts)
89
+	return errors.Wrap(err, "failed to mount local volume")
90 90
 }
91 91
 
92 92
 func (v *localVolume) CreatedAt() (time.Time, error) {