Browse Source

Merge pull request #41458 from thaJeztah/bump_mountinfo

vendor: moby/sys mountinfo/v0.4.0
Akihiro Suda 4 years ago
parent
commit
0b93c6e131
37 changed files with 547 additions and 256 deletions
  1. 4 0
      daemon/daemon_unix.go
  2. 4 0
      daemon/daemon_windows.go
  3. 1 1
      daemon/graphdriver/zfs/zfs.go
  4. 1 2
      daemon/start.go
  5. 44 7
      pkg/mount/deprecated.go
  6. 2 5
      plugin/manager.go
  7. 4 0
      plugin/manager_linux.go
  8. 4 0
      plugin/manager_windows.go
  9. 0 10
      testutil/daemon/daemon.go
  10. 10 0
      testutil/daemon/daemon_unix.go
  11. 2 0
      testutil/daemon/daemon_windows.go
  12. 7 1
      vendor.conf
  13. 4 0
      vendor/github.com/moby/sys/mount/doc.go
  14. 7 10
      vendor/github.com/moby/sys/mount/flags_bsd.go
  15. 2 0
      vendor/github.com/moby/sys/mount/flags_unix.go
  16. 0 30
      vendor/github.com/moby/sys/mount/flags_unsupported.go
  17. 2 2
      vendor/github.com/moby/sys/mount/go.mod
  18. 0 56
      vendor/github.com/moby/sys/mount/mount.go
  19. 87 0
      vendor/github.com/moby/sys/mount/mount_unix.go
  20. 2 0
      vendor/github.com/moby/sys/mount/mounter_bsd.go
  21. 2 2
      vendor/github.com/moby/sys/mount/mounter_unsupported.go
  22. 0 22
      vendor/github.com/moby/sys/mount/unmount_unix.go
  23. 0 7
      vendor/github.com/moby/sys/mount/unmount_unsupported.go
  24. 44 0
      vendor/github.com/moby/sys/mountinfo/doc.go
  25. 2 0
      vendor/github.com/moby/sys/mountinfo/go.mod
  26. 58 0
      vendor/github.com/moby/sys/mountinfo/mounted_linux.go
  27. 66 0
      vendor/github.com/moby/sys/mountinfo/mounted_unix.go
  28. 19 23
      vendor/github.com/moby/sys/mountinfo/mountinfo.go
  29. 15 1
      vendor/github.com/moby/sys/mountinfo/mountinfo_bsd.go
  30. 13 13
      vendor/github.com/moby/sys/mountinfo/mountinfo_filters.go
  31. 102 32
      vendor/github.com/moby/sys/mountinfo/mountinfo_linux.go
  32. 6 5
      vendor/github.com/moby/sys/mountinfo/mountinfo_unsupported.go
  33. 2 4
      vendor/github.com/moby/sys/mountinfo/mountinfo_windows.go
  34. 3 17
      volume/local/local.go
  35. 6 6
      volume/local/local_test.go
  36. 17 0
      volume/local/local_unix.go
  37. 5 0
      volume/local/local_windows.go

+ 4 - 0
daemon/daemon_unix.go

@@ -1711,3 +1711,7 @@ func (daemon *Daemon) RawSysInfo(quiet bool) *sysinfo.SysInfo {
 	}
 	return sysinfo.New(quiet, opts...)
 }
+
+func recursiveUnmount(target string) error {
+	return mount.RecursiveUnmount(target)
+}

+ 4 - 0
daemon/daemon_windows.go

@@ -467,6 +467,10 @@ func (daemon *Daemon) cleanupMounts() error {
 	return nil
 }
 
+func recursiveUnmount(_ string) error {
+	return nil
+}
+
 func setupRemappedRoot(config *config.Config) (*idtools.IdentityMapping, error) {
 	return &idtools.IdentityMapping{}, nil
 }

+ 1 - 1
daemon/graphdriver/zfs/zfs.go

@@ -159,7 +159,7 @@ func lookupZfsDataset(rootdir string) (string, error) {
 			continue // may fail on fuse file systems
 		}
 
-		if stat.Dev == wantedDev && m.Fstype == "zfs" {
+		if stat.Dev == wantedDev && m.FSType == "zfs" {
 			return m.Source, nil
 		}
 	}

+ 1 - 2
daemon/start.go

@@ -12,7 +12,6 @@ import (
 	containertypes "github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/errdefs"
-	"github.com/moby/sys/mount"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 )
@@ -253,7 +252,7 @@ func (daemon *Daemon) Cleanup(container *container.Container) {
 		logrus.Warnf("%s cleanup: failed to unmount secrets: %s", container.ID, err)
 	}
 
-	if err := mount.RecursiveUnmount(container.Root); err != nil {
+	if err := recursiveUnmount(container.Root); err != nil {
 		logrus.WithError(err).WithField("container", container.ID).Warn("Error while cleaning up container resource mounts.")
 	}
 

+ 44 - 7
pkg/mount/deprecated.go

@@ -54,20 +54,57 @@ var (
 type (
 	// FilterFunc is a type.
 	// Deprecated: use github.com/moby/sys/mountinfo instead.
-	FilterFunc = mountinfo.FilterFunc
-	// Info is a type.
+	FilterFunc = func(*Info) (skip, stop bool)
+
+	// Info is a type
 	// Deprecated: use github.com/moby/sys/mountinfo instead.
-	Info = mountinfo.Info // Info is deprecated
+	Info struct {
+		ID, Parent, Major, Minor                                  int
+		Root, Mountpoint, Opts, Optional, Fstype, Source, VfsOpts string
+	}
 )
 
 // Deprecated: use github.com/moby/sys/mountinfo instead.
 //nolint:golint
 var (
-	Mounted   = mountinfo.Mounted
-	GetMounts = mountinfo.GetMounts
-
+	Mounted           = mountinfo.Mounted
 	PrefixFilter      = mountinfo.PrefixFilter
 	SingleEntryFilter = mountinfo.SingleEntryFilter
 	ParentsFilter     = mountinfo.ParentsFilter
-	FstypeFilter      = mountinfo.FstypeFilter
+	FstypeFilter      = mountinfo.FSTypeFilter
 )
+
+// GetMounts is a function.
+//
+// Deprecated: use github.com/moby/sys/mountinfo.GetMounts() instead.
+//nolint:golint
+func GetMounts(f FilterFunc) ([]*Info, error) {
+	fi := func(i *mountinfo.Info) (skip, stop bool) {
+		return f(toLegacyInfo(i))
+	}
+
+	mounts, err := mountinfo.GetMounts(fi)
+	if err != nil {
+		return nil, err
+	}
+	mi := make([]*Info, len(mounts))
+	for i, m := range mounts {
+		mi[i] = toLegacyInfo(m)
+	}
+	return mi, nil
+}
+
+func toLegacyInfo(m *mountinfo.Info) *Info {
+	return &Info{
+		ID:         m.ID,
+		Parent:     m.Parent,
+		Major:      m.Major,
+		Minor:      m.Minor,
+		Root:       m.Root,
+		Mountpoint: m.Mountpoint,
+		Opts:       m.Options,
+		Fstype:     m.FSType,
+		Source:     m.Source,
+		VfsOpts:    m.VFSOptions,
+	}
+}

+ 2 - 5
plugin/manager.go

@@ -23,7 +23,6 @@ import (
 	"github.com/docker/docker/pkg/system"
 	v2 "github.com/docker/docker/plugin/v2"
 	"github.com/docker/docker/registry"
-	"github.com/moby/sys/mount"
 	digest "github.com/opencontainers/go-digest"
 	specs "github.com/opencontainers/runtime-spec/specs-go"
 	"github.com/pkg/errors"
@@ -159,10 +158,8 @@ func (pm *Manager) HandleExitEvent(id string) error {
 
 	if restart {
 		pm.enable(p, c, true)
-	} else {
-		if err := mount.RecursiveUnmount(filepath.Join(pm.config.Root, id)); err != nil {
-			return errors.Wrap(err, "error cleaning up plugin mounts")
-		}
+	} else if err := recursiveUnmount(filepath.Join(pm.config.Root, id)); err != nil {
+		return errors.Wrap(err, "error cleaning up plugin mounts")
 	}
 	return nil
 }

+ 4 - 0
plugin/manager_linux.go

@@ -346,3 +346,7 @@ func (pm *Manager) createPlugin(name string, configDigest, manifestDigest digest
 
 	return p, nil
 }
+
+func recursiveUnmount(target string) error {
+	return mount.RecursiveUnmount(target)
+}

+ 4 - 0
plugin/manager_windows.go

@@ -26,3 +26,7 @@ func (pm *Manager) restore(p *v2.Plugin, c *controller) error {
 // Shutdown plugins
 func (pm *Manager) Shutdown() {
 }
+
+func recursiveUnmount(_ string) error {
+	return nil
+}

+ 0 - 10
testutil/daemon/daemon.go

@@ -24,7 +24,6 @@ import (
 	"github.com/docker/docker/testutil/request"
 	"github.com/docker/go-connections/sockets"
 	"github.com/docker/go-connections/tlsconfig"
-	"github.com/moby/sys/mount"
 	"github.com/pkg/errors"
 	"gotest.tools/v3/assert"
 )
@@ -812,15 +811,6 @@ func (d *Daemon) Info(t testing.TB) types.Info {
 	return info
 }
 
-// cleanupMount unmounts the daemon root directory, or logs a message if
-// unmounting failed.
-func cleanupMount(t testing.TB, d *Daemon) {
-	t.Helper()
-	if err := mount.Unmount(d.Root); err != nil {
-		d.log.Logf("[%s] unable to unmount daemon root (%s): %v", d.id, d.Root, err)
-	}
-}
-
 // cleanupRaftDir removes swarmkit wal files if present
 func cleanupRaftDir(t testing.TB, d *Daemon) {
 	t.Helper()

+ 10 - 0
testutil/daemon/daemon_unix.go

@@ -11,10 +11,20 @@ import (
 	"syscall"
 	"testing"
 
+	"github.com/docker/docker/pkg/mount"
 	"golang.org/x/sys/unix"
 	"gotest.tools/v3/assert"
 )
 
+// cleanupMount unmounts the daemon root directory, or logs a message if
+// unmounting failed.
+func cleanupMount(t testing.TB, d *Daemon) {
+	t.Helper()
+	if err := mount.Unmount(d.Root); err != nil {
+		d.log.Logf("[%s] unable to unmount daemon root (%s): %v", d.id, d.Root, err)
+	}
+}
+
 func cleanupNetworkNamespace(t testing.TB, d *Daemon) {
 	t.Helper()
 	// Cleanup network namespaces in the exec root of this

+ 2 - 0
testutil/daemon/daemon_windows.go

@@ -24,6 +24,8 @@ func signalDaemonReload(pid int) error {
 	return fmt.Errorf("daemon reload not supported")
 }
 
+func cleanupMount(_ testing.TB, _ *Daemon) {}
+
 func cleanupNetworkNamespace(_ testing.TB, _ *Daemon) {}
 
 // CgroupNamespace returns the cgroup namespace the daemon is running in

+ 7 - 1
vendor.conf

@@ -9,6 +9,13 @@ github.com/Microsoft/opengcs                        a10967154e143a36014584a6f664
 github.com/moby/locker                              281af2d563954745bea9d1487c965f24d30742fe # v1.0.1
 github.com/moby/term                                7f0af18e79f2784809e9cef63d0df5aa2c79d76e
 
+# Note that this dependency uses submodules, providing the github.com/moby/sys/mount
+# and github.com/moby/sys/mountinfo modules. Our vendoring tool (vndr) currently
+# does not support submodules / vendoring sub-paths, so we vendor the top-level
+# moby/sys repository (which contains both) and pick the most recent tag, which
+# could be either `mountinfo/vX.Y.Z` or `mount/vX.Y.Z`.
+github.com/moby/sys                                 9a75fe61baf4b9788826b48b0518abecffb79b16 # mountinfo v0.4.0
+
 github.com/creack/pty                               3a6a957789163cacdfe0e291617a1c8e80612c11 # v1.1.9
 github.com/sirupsen/logrus                          6699a89a232f3db797f2e280639854bbc4b89725 # v1.7.0
 github.com/tchap/go-patricia                        a7f0089c6f496e8e70402f61733606daa326cac5 # v2.3.0
@@ -16,7 +23,6 @@ golang.org/x/net                                    ab34263943818b32f575efc978a3
 golang.org/x/sys                                    eeed37f84f13f52d35e095e8023ba65671ff86a1
 github.com/docker/go-units                          519db1ee28dcc9fd2474ae59fca29a810482bfb1 # v0.4.0
 github.com/docker/go-connections                    7395e3f8aa162843a74ed6d48e79627d9792ac55 # v0.4.0
-github.com/moby/sys                                 6154f11e6840c0d6b0dbb23f4125a6134b3013c9 # mountinfo/v0.1.3
 golang.org/x/text                                   23ae387dee1f90d29a23c0e87ee0b46038fbed0e # v0.3.3
 gotest.tools/v3                                     bb0d8a963040ea5048dcef1a14d8f8b58a33d4b3 # v3.0.2
 github.com/google/go-cmp                            3af367b6b30c263d47e8895973edcca9a49cf029 # v0.2.0

+ 4 - 0
vendor/github.com/moby/sys/mount/doc.go

@@ -0,0 +1,4 @@
+// Package mount provides a set of functions to mount and unmount mounts.
+//
+// Currently it supports Linux. For historical reasons, there is also some support for FreeBSD.
+package mount

+ 7 - 10
vendor/github.com/moby/sys/mount/flags_freebsd.go → vendor/github.com/moby/sys/mount/flags_bsd.go

@@ -1,28 +1,25 @@
-// +build freebsd,cgo
+// +build freebsd openbsd
 
 package mount
 
-/*
-#include <sys/mount.h>
-*/
-import "C"
+import "golang.org/x/sys/unix"
 
 const (
 	// RDONLY will mount the filesystem as read-only.
-	RDONLY = C.MNT_RDONLY
+	RDONLY = unix.MNT_RDONLY
 
 	// NOSUID will not allow set-user-identifier or set-group-identifier bits to
 	// take effect.
-	NOSUID = C.MNT_NOSUID
+	NOSUID = unix.MNT_NOSUID
 
 	// NOEXEC will not allow execution of any binaries on the mounted file system.
-	NOEXEC = C.MNT_NOEXEC
+	NOEXEC = unix.MNT_NOEXEC
 
 	// SYNCHRONOUS will allow any I/O to the file system to be done synchronously.
-	SYNCHRONOUS = C.MNT_SYNCHRONOUS
+	SYNCHRONOUS = unix.MNT_SYNCHRONOUS
 
 	// NOATIME will not update the file access time when reading from a file.
-	NOATIME = C.MNT_NOATIME
+	NOATIME = unix.MNT_NOATIME
 )
 
 // These flags are unsupported.

+ 2 - 0
vendor/github.com/moby/sys/mount/flags.go → vendor/github.com/moby/sys/mount/flags_unix.go

@@ -1,3 +1,5 @@
+// +build !darwin,!windows
+
 package mount
 
 import (

+ 0 - 30
vendor/github.com/moby/sys/mount/flags_unsupported.go

@@ -1,30 +0,0 @@
-// +build !linux,!freebsd freebsd,!cgo
-
-package mount
-
-// These flags are unsupported.
-const (
-	BIND        = 0
-	DIRSYNC     = 0
-	MANDLOCK    = 0
-	NOATIME     = 0
-	NODEV       = 0
-	NODIRATIME  = 0
-	NOEXEC      = 0
-	NOSUID      = 0
-	UNBINDABLE  = 0
-	RUNBINDABLE = 0
-	PRIVATE     = 0
-	RPRIVATE    = 0
-	SHARED      = 0
-	RSHARED     = 0
-	SLAVE       = 0
-	RSLAVE      = 0
-	RBIND       = 0
-	RELATIME    = 0
-	REMOUNT     = 0
-	STRICTATIME = 0
-	SYNCHRONOUS = 0
-	RDONLY      = 0
-	mntDetach   = 0
-)

+ 2 - 2
vendor/github.com/moby/sys/mount/go.mod

@@ -3,6 +3,6 @@ module github.com/moby/sys/mount
 go 1.14
 
 require (
-	github.com/moby/sys/mountinfo v0.1.0
-	golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae
+	github.com/moby/sys/mountinfo v0.3.1
+	golang.org/x/sys v0.0.0-20200922070232-aee5d888a860
 )

+ 0 - 56
vendor/github.com/moby/sys/mount/mount.go

@@ -1,56 +0,0 @@
-// +build go1.13
-
-package mount
-
-import (
-	"fmt"
-	"sort"
-
-	"github.com/moby/sys/mountinfo"
-)
-
-// Mount will mount filesystem according to the specified configuration.
-// Options must be specified like the mount or fstab unix commands:
-// "opt1=val1,opt2=val2". See flags.go for supported option flags.
-func Mount(device, target, mType, options string) error {
-	flag, data := parseOptions(options)
-	return mount(device, target, mType, uintptr(flag), data)
-}
-
-// Unmount lazily unmounts a filesystem on supported platforms, otherwise
-// does a normal unmount.
-func Unmount(target string) error {
-	return unmount(target, mntDetach)
-}
-
-// RecursiveUnmount unmounts the target and all mounts underneath, starting with
-// the deepsest mount first.
-func RecursiveUnmount(target string) error {
-	mounts, err := mountinfo.GetMounts(mountinfo.PrefixFilter(target))
-	if err != nil {
-		return err
-	}
-
-	// Make the deepest mount be first
-	sort.Slice(mounts, func(i, j int) bool {
-		return len(mounts[i].Mountpoint) > len(mounts[j].Mountpoint)
-	})
-
-	var suberr error
-	for i, m := range mounts {
-		err = unmount(m.Mountpoint, mntDetach)
-		if err != nil {
-			if i == len(mounts)-1 { // last mount
-				return fmt.Errorf("%w (possible cause: %s)", err, suberr)
-			}
-			// This is a submount, we can ignore the error for now,
-			// the final unmount will fail if this is a real problem.
-			// With that in mind, the _first_ failed unmount error
-			// might be the real error cause, so let's keep it.
-			if suberr == nil {
-				suberr = err
-			}
-		}
-	}
-	return nil
-}

+ 87 - 0
vendor/github.com/moby/sys/mount/mount_unix.go

@@ -0,0 +1,87 @@
+// +build !darwin,!windows
+
+package mount
+
+import (
+	"fmt"
+	"sort"
+
+	"github.com/moby/sys/mountinfo"
+	"golang.org/x/sys/unix"
+)
+
+// Mount will mount filesystem according to the specified configuration.
+// Options must be specified like the mount or fstab unix commands:
+// "opt1=val1,opt2=val2". See flags.go for supported option flags.
+func Mount(device, target, mType, options string) error {
+	flag, data := parseOptions(options)
+	return mount(device, target, mType, uintptr(flag), data)
+}
+
+// Unmount lazily unmounts a filesystem on supported platforms, otherwise does
+// a normal unmount. If target is not a mount point, no error is returned.
+func Unmount(target string) error {
+	err := unix.Unmount(target, mntDetach)
+	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.
+		return nil
+	}
+
+	return &mountError{
+		op:     "umount",
+		target: target,
+		flags:  uintptr(mntDetach),
+		err:    err,
+	}
+}
+
+// RecursiveUnmount unmounts the target and all mounts underneath, starting
+// with the deepest mount first. The argument does not have to be a mount
+// point itself.
+func RecursiveUnmount(target string) error {
+	// Fast path, works if target is a mount point that can be unmounted.
+	// On Linux, mntDetach flag ensures a recursive unmount.  For other
+	// platforms, if there are submounts, we'll get EBUSY (and fall back
+	// to the slow path). NOTE we do not ignore EINVAL here as target might
+	// not be a mount point itself (but there can be mounts underneath).
+	if err := unix.Unmount(target, mntDetach); err == nil {
+		return nil
+	}
+
+	// Slow path: get all submounts, sort, unmount one by one.
+	mounts, err := mountinfo.GetMounts(mountinfo.PrefixFilter(target))
+	if err != nil {
+		return err
+	}
+
+	// Make the deepest mount be first
+	sort.Slice(mounts, func(i, j int) bool {
+		return len(mounts[i].Mountpoint) > len(mounts[j].Mountpoint)
+	})
+
+	var (
+		suberr    error
+		lastMount = len(mounts) - 1
+	)
+	for i, m := range mounts {
+		err = Unmount(m.Mountpoint)
+		if err != nil {
+			if i == lastMount {
+				if suberr != nil {
+					return fmt.Errorf("%w (possible cause: %s)", err, suberr)
+				}
+				return err
+			}
+			// This is a submount, we can ignore the error for now,
+			// the final unmount will fail if this is a real problem.
+			// With that in mind, the _first_ failed unmount error
+			// might be the real error cause, so let's keep it.
+			if suberr == nil {
+				suberr = err
+			}
+		}
+	}
+	return nil
+}

+ 2 - 0
vendor/github.com/moby/sys/mount/mounter_freebsd.go → vendor/github.com/moby/sys/mount/mounter_bsd.go

@@ -1,3 +1,5 @@
+// +build freebsd,cgo openbsd,cgo
+
 package mount
 
 /*

+ 2 - 2
vendor/github.com/moby/sys/mount/mounter_unsupported.go

@@ -1,7 +1,7 @@
-// +build !linux,!freebsd freebsd,!cgo
+// +build !linux,!freebsd,!openbsd,!windows freebsd,!cgo openbsd,!cgo
 
 package mount
 
 func mount(device, target, mType string, flag uintptr, data string) error {
-	panic("Not implemented")
+	panic("cgo required on freebsd and openbsd")
 }

+ 0 - 22
vendor/github.com/moby/sys/mount/unmount_unix.go

@@ -1,22 +0,0 @@
-// +build !windows
-
-package mount
-
-import "golang.org/x/sys/unix"
-
-func unmount(target string, flags int) error {
-	err := unix.Unmount(target, flags)
-	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.
-		return nil
-	}
-
-	return &mountError{
-		op:     "umount",
-		target: target,
-		flags:  uintptr(flags),
-		err:    err,
-	}
-}

+ 0 - 7
vendor/github.com/moby/sys/mount/unmount_unsupported.go

@@ -1,7 +0,0 @@
-// +build windows
-
-package mount
-
-func unmount(target string, flag int) error {
-	panic("Not implemented")
-}

+ 44 - 0
vendor/github.com/moby/sys/mountinfo/doc.go

@@ -0,0 +1,44 @@
+// Package mountinfo provides a set of functions to retrieve information about OS mounts.
+//
+// Currently it supports Linux. For historical reasons, there is also some support for FreeBSD and OpenBSD,
+// and a shallow implementation for Windows, but in general this is Linux-only package, so
+// the rest of the document only applies to Linux, unless explicitly specified otherwise.
+//
+// In Linux, information about mounts seen by the current process is available from
+// /proc/self/mountinfo. Note that due to mount namespaces, different processes can
+// see different mounts. A per-process mountinfo table is available from /proc/<PID>/mountinfo,
+// where <PID> is a numerical process identifier.
+//
+// In general, /proc is not a very efficient interface, and mountinfo is not an exception.
+// For example, there is no way to get information about a specific mount point (i.e. it
+// is all-or-nothing). This package tries to hide the /proc ineffectiveness by using
+// parse filters while reading mountinfo. A filter can skip some entries, or stop
+// processing the rest of the file once the needed information is found.
+//
+// For mountinfo filters that accept path as an argument, the path must be absolute,
+// having all symlinks resolved, and being cleaned (i.e. no extra slashes or dots).
+// One way to achieve all of the above is to employ filepath.Abs followed by
+// filepath.EvalSymlinks (the latter calls filepath.Clean on the result so
+// there is no need to explicitly call filepath.Clean).
+//
+// NOTE that in many cases there is no need to consult mountinfo at all. Here are some
+// of the cases where mountinfo should not be parsed:
+//
+// 1. Before performing a mount. Usually, this is not needed, but if required (say to
+// prevent over-mounts), to check whether a directory is mounted, call os.Lstat
+// on it and its parent directory, and compare their st.Sys().(*syscall.Stat_t).Dev
+// fields -- if they differ, then the directory is the mount point. NOTE this does
+// not work for bind mounts. Optionally, the filesystem type can also be checked
+// by calling unix.Statfs and checking the Type field (i.e. filesystem type).
+//
+// 2. After performing a mount. If there is no error returned, the mount succeeded;
+// checking the mount table for a new mount is redundant and expensive.
+//
+// 3. Before performing an unmount. It is more efficient to do an unmount and ignore
+// a specific error (EINVAL) which tells the directory is not mounted.
+//
+// 4. After performing an unmount. If there is no error returned, the unmount succeeded.
+//
+// 5. To find the mount point root of a specific directory. You can perform os.Stat()
+// on the directory and traverse up until the Dev field of a parent directory differs.
+package mountinfo

+ 2 - 0
vendor/github.com/moby/sys/mountinfo/go.mod

@@ -1,3 +1,5 @@
 module github.com/moby/sys/mountinfo
 
 go 1.14
+
+require golang.org/x/sys v0.0.0-20200909081042-eff7692f9009

+ 58 - 0
vendor/github.com/moby/sys/mountinfo/mounted_linux.go

@@ -0,0 +1,58 @@
+package mountinfo
+
+import (
+	"os"
+	"path/filepath"
+
+	"golang.org/x/sys/unix"
+)
+
+// mountedByOpenat2 is a method of detecting a mount that works for all kinds
+// of mounts (incl. bind mounts), but requires a recent (v5.6+) linux kernel.
+func mountedByOpenat2(path string) (bool, error) {
+	dir, last := filepath.Split(path)
+
+	dirfd, err := unix.Openat2(unix.AT_FDCWD, dir, &unix.OpenHow{
+		Flags: unix.O_PATH | unix.O_CLOEXEC,
+	})
+	if err != nil {
+		if err == unix.ENOENT { // not a mount
+			return false, nil
+		}
+		return false, &os.PathError{Op: "openat2", Path: dir, Err: err}
+	}
+	fd, err := unix.Openat2(dirfd, last, &unix.OpenHow{
+		Flags:   unix.O_PATH | unix.O_CLOEXEC | unix.O_NOFOLLOW,
+		Resolve: unix.RESOLVE_NO_XDEV,
+	})
+	_ = unix.Close(dirfd)
+	switch err {
+	case nil: // definitely not a mount
+		_ = unix.Close(fd)
+		return false, nil
+	case unix.EXDEV: // definitely a mount
+		return true, nil
+	case unix.ENOENT: // not a mount
+		return false, nil
+	}
+	// not sure
+	return false, &os.PathError{Op: "openat2", Path: path, Err: err}
+}
+
+func mounted(path string) (bool, error) {
+	// Try a fast path, using openat2() with RESOLVE_NO_XDEV.
+	mounted, err := mountedByOpenat2(path)
+	if err == nil {
+		return mounted, nil
+	}
+	// Another fast path: compare st.st_dev fields.
+	mounted, err = mountedByStat(path)
+	// This does not work for bind mounts, so false negative
+	// is possible, therefore only trust if return is true.
+	if mounted && err == nil {
+		return mounted, nil
+	}
+
+	// Fallback to parsing mountinfo
+	return mountedByMountinfo(path)
+}

+ 66 - 0
vendor/github.com/moby/sys/mountinfo/mounted_unix.go

@@ -0,0 +1,66 @@
+// +build linux freebsd,cgo openbsd,cgo
+
+package mountinfo
+
+import (
+	"errors"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"golang.org/x/sys/unix"
+)
+
+func mountedByStat(path string) (bool, error) {
+	var st unix.Stat_t
+
+	if err := unix.Lstat(path, &st); err != nil {
+		if err == unix.ENOENT {
+			// Treat ENOENT as "not mounted".
+			return false, nil
+		}
+		return false, &os.PathError{Op: "stat", Path: path, Err: err}
+	}
+	dev := st.Dev
+	parent := filepath.Dir(path)
+	if err := unix.Lstat(parent, &st); err != nil {
+		return false, &os.PathError{Op: "stat", Path: parent, Err: err}
+	}
+	if dev != st.Dev {
+		// Device differs from that of parent,
+		// so definitely a mount point.
+		return true, nil
+	}
+	// NB: this does not detect bind mounts on Linux.
+	return false, nil
+}
+
+func normalizePath(path string) (realPath string, err error) {
+	if realPath, err = filepath.Abs(path); err != nil {
+		return "", fmt.Errorf("unable to get absolute path for %q: %w", path, err)
+	}
+	if realPath, err = filepath.EvalSymlinks(realPath); err != nil {
+		return "", fmt.Errorf("failed to canonicalise path for %q: %w", path, err)
+	}
+	if _, err := os.Stat(realPath); err != nil {
+		return "", fmt.Errorf("failed to stat target of %q: %w", path, err)
+	}
+	return realPath, nil
+}
+
+func mountedByMountinfo(path string) (bool, error) {
+	path, err := normalizePath(path)
+	if err != nil {
+		if errors.Is(err, unix.ENOENT) {
+			// treat ENOENT as "not mounted"
+			return false, nil
+		}
+		return false, err
+	}
+	entries, err := GetMounts(SingleEntryFilter(path))
+	if err != nil {
+		return false, err
+	}
+
+	return len(entries) > 0, nil
+}

+ 19 - 23
vendor/github.com/moby/sys/mountinfo/mountinfo.go

@@ -1,6 +1,8 @@
 package mountinfo
 
-import "io"
+import (
+	"os"
+)
 
 // GetMounts retrieves a list of mounts for the current running process,
 // with an optional filter applied (use nil for no filter).
@@ -8,23 +10,17 @@ func GetMounts(f FilterFunc) ([]*Info, error) {
 	return parseMountTable(f)
 }
 
-// GetMountsFromReader retrieves a list of mounts from the
-// reader provided, with an optional filter applied (use nil
-// for no filter). This can be useful in tests or benchmarks
-// that provide a fake mountinfo data.
-func GetMountsFromReader(reader io.Reader, f FilterFunc) ([]*Info, error) {
-	return parseInfoFile(reader, f)
-}
-
-// Mounted determines if a specified mountpoint has been mounted.
-// On Linux it looks at /proc/self/mountinfo.
-func Mounted(mountpoint string) (bool, error) {
-	entries, err := GetMounts(SingleEntryFilter(mountpoint))
-	if err != nil {
-		return false, err
+// Mounted determines if a specified path is a mount point.
+//
+// The argument must be an absolute path, with all symlinks resolved, and clean.
+// One way to ensure it is to process the path using filepath.Abs followed by
+// filepath.EvalSymlinks before calling this function.
+func Mounted(path string) (bool, error) {
+	// root is always mounted
+	if path == string(os.PathSeparator) {
+		return true, nil
 	}
-
-	return len(entries) > 0, nil
+	return mounted(path)
 }
 
 // Info reveals information about a particular mounted filesystem. This
@@ -50,18 +46,18 @@ type Info struct {
 	// Mountpoint indicates the mount point relative to the process's root.
 	Mountpoint string
 
-	// Opts represents mount-specific options.
-	Opts string
+	// Options represents mount-specific options.
+	Options string
 
 	// Optional represents optional fields.
 	Optional string
 
-	// Fstype indicates the type of filesystem, such as EXT3.
-	Fstype string
+	// FSType indicates the type of filesystem, such as EXT3.
+	FSType string
 
 	// Source indicates filesystem specific information or "none".
 	Source string
 
-	// VfsOpts represents per super block options.
-	VfsOpts string
+	// VFSOptions represents per super block options.
+	VFSOptions string
 }

+ 15 - 1
vendor/github.com/moby/sys/mountinfo/mountinfo_freebsd.go → vendor/github.com/moby/sys/mountinfo/mountinfo_bsd.go

@@ -1,3 +1,5 @@
+// +build freebsd,cgo openbsd,cgo
+
 package mountinfo
 
 /*
@@ -33,7 +35,7 @@ func parseMountTable(filter FilterFunc) ([]*Info, error) {
 		var mountinfo Info
 		var skip, stop bool
 		mountinfo.Mountpoint = C.GoString(&entry.f_mntonname[0])
-		mountinfo.Fstype = C.GoString(&entry.f_fstypename[0])
+		mountinfo.FSType = C.GoString(&entry.f_fstypename[0])
 		mountinfo.Source = C.GoString(&entry.f_mntfromname[0])
 
 		if filter != nil {
@@ -51,3 +53,15 @@ func parseMountTable(filter FilterFunc) ([]*Info, error) {
 	}
 	return out, nil
 }
+
+func mounted(path string) (bool, error) {
+	// Fast path: compare st.st_dev fields.
+	// This should always work for FreeBSD and OpenBSD.
+	mounted, err := mountedByStat(path)
+	if err == nil {
+		return mounted, nil
+	}
+
+	// Fallback to parsing mountinfo
+	return mountedByMountinfo(path)
+}

+ 13 - 13
vendor/github.com/moby/sys/mountinfo/mountinfo_filters.go

@@ -6,16 +6,16 @@ import "strings"
 // used to filter out mountinfo entries we're not interested in,
 // and/or stop further processing if we found what we wanted.
 //
-// It takes a pointer to the Info struct (not fully populated,
-// currently only Mountpoint, Fstype, Source, and (on Linux)
-// VfsOpts are filled in), and returns two booleans:
+// It takes a pointer to the Info struct (fully populated with all available
+// fields on the GOOS platform), and returns two booleans:
 //
-//  - skip: true if the entry should be skipped
-//  - stop: true if parsing should be stopped after the entry
+// skip: true if the entry should be skipped;
+//
+// stop: true if parsing should be stopped after the entry.
 type FilterFunc func(*Info) (skip, stop bool)
 
 // PrefixFilter discards all entries whose mount points
-// do not start with a specific prefix
+// do not start with a specific prefix.
 func PrefixFilter(prefix string) FilterFunc {
 	return func(m *Info) (bool, bool) {
 		skip := !strings.HasPrefix(m.Mountpoint, prefix)
@@ -23,7 +23,7 @@ func PrefixFilter(prefix string) FilterFunc {
 	}
 }
 
-// SingleEntryFilter looks for a specific entry
+// SingleEntryFilter looks for a specific entry.
 func SingleEntryFilter(mp string) FilterFunc {
 	return func(m *Info) (bool, bool) {
 		if m.Mountpoint == mp {
@@ -36,8 +36,8 @@ func SingleEntryFilter(mp string) FilterFunc {
 // ParentsFilter returns all entries whose mount points
 // can be parents of a path specified, discarding others.
 //
-// For example, given `/var/lib/docker/something`, entries
-// like `/var/lib/docker`, `/var` and `/` are returned.
+// For example, given /var/lib/docker/something, entries
+// like /var/lib/docker, /var and / are returned.
 func ParentsFilter(path string) FilterFunc {
 	return func(m *Info) (bool, bool) {
 		skip := !strings.HasPrefix(path, m.Mountpoint)
@@ -45,12 +45,12 @@ func ParentsFilter(path string) FilterFunc {
 	}
 }
 
-// FstypeFilter returns all entries that match provided fstype(s).
-func FstypeFilter(fstype ...string) FilterFunc {
+// FSTypeFilter returns all entries that match provided fstype(s).
+func FSTypeFilter(fstype ...string) FilterFunc {
 	return func(m *Info) (bool, bool) {
 		for _, t := range fstype {
-			if m.Fstype == t {
-				return false, false // don't skeep, keep going
+			if m.FSType == t {
+				return false, false // don't skip, keep going
 			}
 		}
 		return true, false // skip, keep going

+ 102 - 32
vendor/github.com/moby/sys/mountinfo/mountinfo_linux.go

@@ -1,5 +1,3 @@
-// +build go1.13
-
 package mountinfo
 
 import (
@@ -11,14 +9,18 @@ import (
 	"strings"
 )
 
-func parseInfoFile(r io.Reader, filter FilterFunc) ([]*Info, error) {
+// GetMountsFromReader retrieves a list of mounts from the
+// reader provided, with an optional filter applied (use nil
+// for no filter). This can be useful in tests or benchmarks
+// that provide a fake mountinfo data.
+//
+// This function is Linux-specific.
+func GetMountsFromReader(r io.Reader, filter FilterFunc) ([]*Info, error) {
 	s := bufio.NewScanner(r)
 	out := []*Info{}
-	var err error
 	for s.Scan() {
-		if err = s.Err(); err != nil {
-			return nil, err
-		}
+		var err error
+
 		/*
 		   See http://man7.org/linux/man-pages/man5/proc.5.html
 
@@ -70,26 +72,19 @@ func parseInfoFile(r io.Reader, filter FilterFunc) ([]*Info, error) {
 
 		p := &Info{}
 
-		// Fill in the fields that a filter might check
-		p.Mountpoint, err = strconv.Unquote(`"` + fields[4] + `"`)
+		p.Mountpoint, err = unescape(fields[4])
 		if err != nil {
-			return nil, fmt.Errorf("Parsing '%s' failed: unable to unquote mount point field: %w", fields[4], err)
+			return nil, fmt.Errorf("Parsing '%s' failed: mount point: %w", fields[4], err)
 		}
-		p.Fstype = fields[sepIdx+1]
-		p.Source = fields[sepIdx+2]
-		p.VfsOpts = fields[sepIdx+3]
-
-		// Run a filter soon so we can skip parsing/adding entries
-		// the caller is not interested in
-		var skip, stop bool
-		if filter != nil {
-			skip, stop = filter(p)
-			if skip {
-				continue
-			}
+		p.FSType, err = unescape(fields[sepIdx+1])
+		if err != nil {
+			return nil, fmt.Errorf("Parsing '%s' failed: fstype: %w", fields[sepIdx+1], err)
 		}
-
-		// Fill in the rest of the fields
+		p.Source, err = unescape(fields[sepIdx+2])
+		if err != nil {
+			return nil, fmt.Errorf("Parsing '%s' failed: source: %w", fields[sepIdx+2], err)
+		}
+		p.VFSOptions = fields[sepIdx+3]
 
 		// ignore any numbers parsing errors, as there should not be any
 		p.ID, _ = strconv.Atoi(fields[0])
@@ -101,12 +96,12 @@ func parseInfoFile(r io.Reader, filter FilterFunc) ([]*Info, error) {
 		p.Major, _ = strconv.Atoi(mm[0])
 		p.Minor, _ = strconv.Atoi(mm[1])
 
-		p.Root, err = strconv.Unquote(`"` + fields[3] + `"`)
+		p.Root, err = unescape(fields[3])
 		if err != nil {
-			return nil, fmt.Errorf("Parsing '%s' failed: unable to unquote root field: %w", fields[3], err)
+			return nil, fmt.Errorf("Parsing '%s' failed: root: %w", fields[3], err)
 		}
 
-		p.Opts = fields[5]
+		p.Options = fields[5]
 
 		// zero or more optional fields
 		switch {
@@ -118,11 +113,23 @@ func parseInfoFile(r io.Reader, filter FilterFunc) ([]*Info, error) {
 			p.Optional = strings.Join(fields[6:sepIdx-1], " ")
 		}
 
+		// Run the filter after parsing all of the fields.
+		var skip, stop bool
+		if filter != nil {
+			skip, stop = filter(p)
+			if skip {
+				continue
+			}
+		}
+
 		out = append(out, p)
 		if stop {
 			break
 		}
 	}
+	if err := s.Err(); err != nil {
+		return nil, err
+	}
 	return out, nil
 }
 
@@ -135,12 +142,17 @@ func parseMountTable(filter FilterFunc) ([]*Info, error) {
 	}
 	defer f.Close()
 
-	return parseInfoFile(f, filter)
+	return GetMountsFromReader(f, filter)
 }
 
-// PidMountInfo collects the mounts for a specific process ID. If the process
-// ID is unknown, it is better to use `GetMounts` which will inspect
-// "/proc/self/mountinfo" instead.
+// PidMountInfo retrieves the list of mounts from a given process' mount
+// namespace. Unless there is a need to get mounts from a mount namespace
+// different from that of a calling process, use GetMounts.
+//
+// This function is Linux-specific.
+//
+// Deprecated: this will be removed before v1; use GetMountsFromReader with
+// opened /proc/<pid>/mountinfo as an argument instead.
 func PidMountInfo(pid int) ([]*Info, error) {
 	f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid))
 	if err != nil {
@@ -148,5 +160,63 @@ func PidMountInfo(pid int) ([]*Info, error) {
 	}
 	defer f.Close()
 
-	return parseInfoFile(f, nil)
+	return GetMountsFromReader(f, nil)
+}
+
+// A few specific characters in mountinfo path entries (root and mountpoint)
+// are escaped using a backslash followed by a character's ascii code in octal.
+//
+//   space              -- as \040
+//   tab (aka \t)       -- as \011
+//   newline (aka \n)   -- as \012
+//   backslash (aka \\) -- as \134
+//
+// This function converts path from mountinfo back, i.e. it unescapes the above sequences.
+func unescape(path string) (string, error) {
+	// try to avoid copying
+	if strings.IndexByte(path, '\\') == -1 {
+		return path, nil
+	}
+
+	// The following code is UTF-8 transparent as it only looks for some
+	// specific characters (backslash and 0..7) with values < utf8.RuneSelf,
+	// and everything else is passed through as is.
+	buf := make([]byte, len(path))
+	bufLen := 0
+	for i := 0; i < len(path); i++ {
+		if path[i] != '\\' {
+			buf[bufLen] = path[i]
+			bufLen++
+			continue
+		}
+		s := path[i:]
+		if len(s) < 4 {
+			// too short
+			return "", fmt.Errorf("bad escape sequence %q: too short", s)
+		}
+		c := s[1]
+		switch c {
+		case '0', '1', '2', '3', '4', '5', '6', '7':
+			v := c - '0'
+			for j := 2; j < 4; j++ { // one digit already; two more
+				if s[j] < '0' || s[j] > '7' {
+					return "", fmt.Errorf("bad escape sequence %q: not a digit", s[:3])
+				}
+				x := s[j] - '0'
+				v = (v << 3) | x
+			}
+			if v > 255 {
+				return "", fmt.Errorf("bad escape sequence %q: out of range" + s[:3])
+			}
+			buf[bufLen] = v
+			bufLen++
+			i += 3
+			continue
+		default:
+			return "", fmt.Errorf("bad escape sequence %q: not a digit" + s[:3])
+
+		}
+	}
+
+	return string(buf[:bufLen]), nil
 }

+ 6 - 5
vendor/github.com/moby/sys/mountinfo/mountinfo_unsupported.go

@@ -1,17 +1,18 @@
-// +build !windows,!linux,!freebsd freebsd,!cgo
+// +build !windows,!linux,!freebsd,!openbsd freebsd,!cgo openbsd,!cgo
 
 package mountinfo
 
 import (
 	"fmt"
-	"io"
 	"runtime"
 )
 
+var errNotImplemented = fmt.Errorf("not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
+
 func parseMountTable(_ FilterFunc) ([]*Info, error) {
-	return nil, fmt.Errorf("mount.parseMountTable is not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
+	return nil, errNotImplemented
 }
 
-func parseInfoFile(_ io.Reader, f FilterFunc) ([]*Info, error) {
-	return parseMountTable(f)
+func mounted(path string) (bool, error) {
+	return false, errNotImplemented
 }

+ 2 - 4
vendor/github.com/moby/sys/mountinfo/mountinfo_windows.go

@@ -1,12 +1,10 @@
 package mountinfo
 
-import "io"
-
 func parseMountTable(_ FilterFunc) ([]*Info, error) {
 	// Do NOT return an error!
 	return nil, nil
 }
 
-func parseInfoFile(_ io.Reader, f FilterFunc) ([]*Info, error) {
-	return parseMountTable(f)
+func mounted(_ string) (bool, error) {
+	return false, nil
 }

+ 3 - 17
volume/local/local.go

@@ -18,8 +18,6 @@ import (
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/quota"
 	"github.com/docker/docker/volume"
-	"github.com/moby/sys/mount"
-	"github.com/moby/sys/mountinfo"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 )
@@ -96,9 +94,9 @@ func New(scope string, rootIdentity idtools.Identity) (*Root, error) {
 			if !reflect.DeepEqual(opts, optsConfig{}) {
 				v.opts = &opts
 			}
-
-			// unmount anything that may still be mounted (for example, from an unclean shutdown)
-			mount.Unmount(v.path)
+			// unmount anything that may still be mounted (for example, from an
+			// unclean shutdown). This is a no-op on windows
+			unmount(v.path)
 		}
 	}
 
@@ -347,18 +345,6 @@ func (v *localVolume) Unmount(id string) error {
 	return v.unmount()
 }
 
-func (v *localVolume) unmount() error {
-	if v.needsMount() {
-		if err := mount.Unmount(v.path); err != nil {
-			if mounted, mErr := mountinfo.Mounted(v.path); mounted || mErr != nil {
-				return errdefs.System(err)
-			}
-		}
-		v.active.mounted = false
-	}
-	return nil
-}
-
 func (v *localVolume) Status() map[string]interface{} {
 	return nil
 }

+ 6 - 6
volume/local/local_test.go

@@ -221,17 +221,17 @@ func TestCreateWithOpts(t *testing.T) {
 
 	info := mountInfos[0]
 	t.Logf("%+v", info)
-	if info.Fstype != "tmpfs" {
-		t.Fatalf("expected tmpfs mount, got %q", info.Fstype)
+	if info.FSType != "tmpfs" {
+		t.Fatalf("expected tmpfs mount, got %q", info.FSType)
 	}
 	if info.Source != "tmpfs" {
 		t.Fatalf("expected tmpfs mount, got %q", info.Source)
 	}
-	if !strings.Contains(info.VfsOpts, "uid=1000") {
-		t.Fatalf("expected mount info to have uid=1000: %q", info.VfsOpts)
+	if !strings.Contains(info.VFSOptions, "uid=1000") {
+		t.Fatalf("expected mount info to have uid=1000: %q", info.VFSOptions)
 	}
-	if !strings.Contains(info.VfsOpts, "size=1024k") {
-		t.Fatalf("expected mount info to have size=1024k: %q", info.VfsOpts)
+	if !strings.Contains(info.VFSOptions, "size=1024k") {
+		t.Fatalf("expected mount info to have size=1024k: %q", info.VFSOptions)
 	}
 
 	if v.active.count != 1 {

+ 17 - 0
volume/local/local_unix.go

@@ -18,6 +18,7 @@ import (
 	"github.com/docker/docker/quota"
 	units "github.com/docker/go-units"
 	"github.com/moby/sys/mount"
+	"github.com/moby/sys/mountinfo"
 	"github.com/pkg/errors"
 )
 
@@ -111,6 +112,10 @@ func validateOpts(opts map[string]string) error {
 	return nil
 }
 
+func unmount(path string) {
+	_ = mount.Unmount(path)
+}
+
 func (v *localVolume) needsMount() bool {
 	if v.opts == nil {
 		return false
@@ -157,6 +162,18 @@ func (v *localVolume) postMount() error {
 	return nil
 }
 
+func (v *localVolume) unmount() error {
+	if v.needsMount() {
+		if err := mount.Unmount(v.path); err != nil {
+			if mounted, mErr := mountinfo.Mounted(v.path); mounted || mErr != nil {
+				return errdefs.System(err)
+			}
+		}
+		v.active.mounted = false
+	}
+	return nil
+}
+
 func (v *localVolume) CreatedAt() (time.Time, error) {
 	fileInfo, err := os.Stat(v.path)
 	if err != nil {

+ 5 - 0
volume/local/local_windows.go

@@ -39,6 +39,11 @@ func (v *localVolume) needsMount() bool {
 func (v *localVolume) mount() error {
 	return nil
 }
+func (v *localVolume) unmount() error {
+	return nil
+}
+
+func unmount(_ string) {}
 
 func (v *localVolume) postMount() error {
 	return nil