diff options
Diffstat (limited to 'vendor/github.com/moby/sys/mountinfo')
11 files changed, 284 insertions, 18 deletions
diff --git a/vendor/github.com/moby/sys/mountinfo/doc.go b/vendor/github.com/moby/sys/mountinfo/doc.go new file mode 100644 index 000000000..21aa8dd59 --- /dev/null +++ b/vendor/github.com/moby/sys/mountinfo/doc.go @@ -0,0 +1,47 @@ +// 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 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 effective 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; +// - being cleaned. +// +// 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 overmounts), 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 diff --git a/vendor/github.com/moby/sys/mountinfo/go.mod b/vendor/github.com/moby/sys/mountinfo/go.mod index 10d9a15a6..9749ea96d 100644 --- a/vendor/github.com/moby/sys/mountinfo/go.mod +++ b/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 diff --git a/vendor/github.com/moby/sys/mountinfo/go.sum b/vendor/github.com/moby/sys/mountinfo/go.sum new file mode 100644 index 000000000..2a5be7ea8 --- /dev/null +++ b/vendor/github.com/moby/sys/mountinfo/go.sum @@ -0,0 +1,2 @@ +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 h1:W0lCpv29Hv0UaM1LXb9QlBHLNP8UFfcKjblhVCWftOM= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/vendor/github.com/moby/sys/mountinfo/mounted_linux.go b/vendor/github.com/moby/sys/mountinfo/mounted_linux.go new file mode 100644 index 000000000..bc9f6b2ad --- /dev/null +++ b/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) +} diff --git a/vendor/github.com/moby/sys/mountinfo/mounted_unix.go b/vendor/github.com/moby/sys/mountinfo/mounted_unix.go new file mode 100644 index 000000000..c4d66b2f4 --- /dev/null +++ b/vendor/github.com/moby/sys/mountinfo/mounted_unix.go @@ -0,0 +1,66 @@ +// +build linux freebsd,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 +} diff --git a/vendor/github.com/moby/sys/mountinfo/mountinfo.go b/vendor/github.com/moby/sys/mountinfo/mountinfo.go index 136b14167..1987fcbb2 100644 --- a/vendor/github.com/moby/sys/mountinfo/mountinfo.go +++ b/vendor/github.com/moby/sys/mountinfo/mountinfo.go @@ -1,6 +1,9 @@ package mountinfo -import "io" +import ( + "io" + "os" +) // GetMounts retrieves a list of mounts for the current running process, // with an optional filter applied (use nil for no filter). @@ -16,15 +19,17 @@ 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 diff --git a/vendor/github.com/moby/sys/mountinfo/mountinfo_filters.go b/vendor/github.com/moby/sys/mountinfo/mountinfo_filters.go index 795026465..8aebe1ad4 100644 --- a/vendor/github.com/moby/sys/mountinfo/mountinfo_filters.go +++ b/vendor/github.com/moby/sys/mountinfo/mountinfo_filters.go @@ -15,7 +15,7 @@ import "strings" 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 { diff --git a/vendor/github.com/moby/sys/mountinfo/mountinfo_freebsd.go b/vendor/github.com/moby/sys/mountinfo/mountinfo_freebsd.go index a7dbb1b46..b30dc1625 100644 --- a/vendor/github.com/moby/sys/mountinfo/mountinfo_freebsd.go +++ b/vendor/github.com/moby/sys/mountinfo/mountinfo_freebsd.go @@ -51,3 +51,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. + mounted, err := mountedByStat(path) + if err == nil { + return mounted, nil + } + + // Fallback to parsing mountinfo + return mountedByMountinfo(path) +} diff --git a/vendor/github.com/moby/sys/mountinfo/mountinfo_linux.go b/vendor/github.com/moby/sys/mountinfo/mountinfo_linux.go index 2d630c8dc..cdfd37da5 100644 --- a/vendor/github.com/moby/sys/mountinfo/mountinfo_linux.go +++ b/vendor/github.com/moby/sys/mountinfo/mountinfo_linux.go @@ -71,12 +71,18 @@ 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, err = unescape(fields[sepIdx+1]) + if err != nil { + return nil, fmt.Errorf("Parsing '%s' failed: fstype: %w", fields[sepIdx+1], err) + } + p.Source, err = unescape(fields[sepIdx+2]) + if err != nil { + return nil, fmt.Errorf("Parsing '%s' failed: source: %w", fields[sepIdx+2], 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 @@ -101,9 +107,9 @@ 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] @@ -150,3 +156,61 @@ func PidMountInfo(pid int) ([]*Info, error) { return parseInfoFile(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 (backslach 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 +} diff --git a/vendor/github.com/moby/sys/mountinfo/mountinfo_unsupported.go b/vendor/github.com/moby/sys/mountinfo/mountinfo_unsupported.go index dc1869211..1eb8558c8 100644 --- a/vendor/github.com/moby/sys/mountinfo/mountinfo_unsupported.go +++ b/vendor/github.com/moby/sys/mountinfo/mountinfo_unsupported.go @@ -8,10 +8,16 @@ import ( "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 +} diff --git a/vendor/github.com/moby/sys/mountinfo/mountinfo_windows.go b/vendor/github.com/moby/sys/mountinfo/mountinfo_windows.go index 69ffdc52b..5659c1b0f 100644 --- a/vendor/github.com/moby/sys/mountinfo/mountinfo_windows.go +++ b/vendor/github.com/moby/sys/mountinfo/mountinfo_windows.go @@ -10,3 +10,7 @@ func parseMountTable(_ FilterFunc) ([]*Info, error) { func parseInfoFile(_ io.Reader, f FilterFunc) ([]*Info, error) { return parseMountTable(f) } + +func mounted(_ string) (bool, error) { + return false, nil +} |