Browse code

vendor: moby/sys mountinfo/v0.4.0

full diff: https://github.com/moby/sys/compare/mountinfo/v0.1.3...mountinfo/v0.4.0

> Note that this dependency uses submodules, providing "github.com/moby/sys/mount"
> and "github.com/moby/sys/mountinfo". Our vendoring tool (vndr) currently doesn't
> support submodules, so we vendor the top-level moby/sys repository (which contains
> both) and pick the most recent tag, which could be either `mountinfo/vXXX` or
> `mount/vXXX`.

github.com/moby/sys/mountinfo v0.4.0
--------------------------------------------------------------------------------

Breaking changes:

- `PidMountInfo` is now deprecated and will be removed before v1.0; users should switch to `GetMountsFromReader`

Fixes and improvements:

- run filter after all fields are parsed
- correct handling errors from bufio.Scan
- documentation formatting fixes

github.com/moby/sys/mountinfo v0.3.1
--------------------------------------------------------------------------------

- mount: use MNT_* flags from golang.org/x/sys/unix on freebsd
- various godoc and CI fixes
- mountinfo: make GetMountinfoFromReader Linux-specific
- Add support for OpenBSD in addition to FreeBSD
- mountinfo: use idiomatic naming for fields

github.com/moby/sys/mountinfo v0.2.0
--------------------------------------------------------------------------------

Bug fixes:

- Fix path unescaping for paths with double quotes

Improvements:

- Mounted: speed up by adding fast paths using openat2 (Linux-only) and stat
- Mounted: relax path requirements (allow relative, non-cleaned paths, symlinks)
- Unescape fstype and source fields
- Documentation improvements

Testing/CI:

- Unit tests: exclude darwin
- CI: run tests under Fedora 32 to test openat2
- TestGetMounts: fix for Ubuntu build system
- Makefile: fix ignoring test failures
- CI: add cross build

github.com/moby/sys/mount v0.1.1
--------------------------------------------------------------------------------

https://github.com/moby/sys/releases/tag/mount%2Fv0.1.1

Improvements:

- RecursiveUnmount: add a fast path (#26)
- Unmount: improve doc
- fix CI linter warning on Windows

Testing/CI:

- Unit tests: exclude darwin
- Makefile: fix ignoring test failures
- CI: add cross build

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2020/09/16 17:27:03
Showing 29 changed files
... ...
@@ -159,7 +159,7 @@ func lookupZfsDataset(rootdir string) (string, error) {
159 159
 			continue // may fail on fuse file systems
160 160
 		}
161 161
 
162
-		if stat.Dev == wantedDev && m.Fstype == "zfs" {
162
+		if stat.Dev == wantedDev && m.FSType == "zfs" {
163 163
 			return m.Source, nil
164 164
 		}
165 165
 	}
... ...
@@ -69,5 +69,5 @@ var (
69 69
 	PrefixFilter      = mountinfo.PrefixFilter
70 70
 	SingleEntryFilter = mountinfo.SingleEntryFilter
71 71
 	ParentsFilter     = mountinfo.ParentsFilter
72
-	FstypeFilter      = mountinfo.FstypeFilter
72
+	FstypeFilter      = mountinfo.FSTypeFilter
73 73
 )
... ...
@@ -9,6 +9,13 @@ github.com/Microsoft/opengcs                        a10967154e143a36014584a6f664
9 9
 github.com/moby/locker                              281af2d563954745bea9d1487c965f24d30742fe # v1.0.1
10 10
 github.com/moby/term                                7f0af18e79f2784809e9cef63d0df5aa2c79d76e
11 11
 
12
+# Note that this dependency uses submodules, providing the github.com/moby/sys/mount
13
+# and github.com/moby/sys/mountinfo modules. Our vendoring tool (vndr) currently
14
+# does not support submodules / vendoring sub-paths, so we vendor the top-level
15
+# moby/sys repository (which contains both) and pick the most recent tag, which
16
+# could be either `mountinfo/vX.Y.Z` or `mount/vX.Y.Z`.
17
+github.com/moby/sys                                 9a75fe61baf4b9788826b48b0518abecffb79b16 # mountinfo v0.4.0
18
+
12 19
 github.com/creack/pty                               3a6a957789163cacdfe0e291617a1c8e80612c11 # v1.1.9
13 20
 github.com/sirupsen/logrus                          6699a89a232f3db797f2e280639854bbc4b89725 # v1.7.0
14 21
 github.com/tchap/go-patricia                        a7f0089c6f496e8e70402f61733606daa326cac5 # v2.3.0
... ...
@@ -16,7 +23,6 @@ golang.org/x/net                                    ab34263943818b32f575efc978a3
16 16
 golang.org/x/sys                                    aee5d888a86055dc6ab0342f9cdc7b53aaeaec62
17 17
 github.com/docker/go-units                          519db1ee28dcc9fd2474ae59fca29a810482bfb1 # v0.4.0
18 18
 github.com/docker/go-connections                    7395e3f8aa162843a74ed6d48e79627d9792ac55 # v0.4.0
19
-github.com/moby/sys                                 6154f11e6840c0d6b0dbb23f4125a6134b3013c9 # mountinfo/v0.1.3
20 19
 golang.org/x/text                                   23ae387dee1f90d29a23c0e87ee0b46038fbed0e # v0.3.3
21 20
 gotest.tools/v3                                     bb0d8a963040ea5048dcef1a14d8f8b58a33d4b3 # v3.0.2
22 21
 github.com/google/go-cmp                            3af367b6b30c263d47e8895973edcca9a49cf029 # v0.2.0
23 22
new file mode 100644
... ...
@@ -0,0 +1,4 @@
0
+// Package mount provides a set of functions to mount and unmount mounts.
1
+//
2
+// Currently it supports Linux. For historical reasons, there is also some support for FreeBSD.
3
+package mount
0 4
deleted file mode 100644
... ...
@@ -1,137 +0,0 @@
1
-package mount
2
-
3
-import (
4
-	"fmt"
5
-	"strings"
6
-)
7
-
8
-var flags = map[string]struct {
9
-	clear bool
10
-	flag  int
11
-}{
12
-	"defaults":      {false, 0},
13
-	"ro":            {false, RDONLY},
14
-	"rw":            {true, RDONLY},
15
-	"suid":          {true, NOSUID},
16
-	"nosuid":        {false, NOSUID},
17
-	"dev":           {true, NODEV},
18
-	"nodev":         {false, NODEV},
19
-	"exec":          {true, NOEXEC},
20
-	"noexec":        {false, NOEXEC},
21
-	"sync":          {false, SYNCHRONOUS},
22
-	"async":         {true, SYNCHRONOUS},
23
-	"dirsync":       {false, DIRSYNC},
24
-	"remount":       {false, REMOUNT},
25
-	"mand":          {false, MANDLOCK},
26
-	"nomand":        {true, MANDLOCK},
27
-	"atime":         {true, NOATIME},
28
-	"noatime":       {false, NOATIME},
29
-	"diratime":      {true, NODIRATIME},
30
-	"nodiratime":    {false, NODIRATIME},
31
-	"bind":          {false, BIND},
32
-	"rbind":         {false, RBIND},
33
-	"unbindable":    {false, UNBINDABLE},
34
-	"runbindable":   {false, RUNBINDABLE},
35
-	"private":       {false, PRIVATE},
36
-	"rprivate":      {false, RPRIVATE},
37
-	"shared":        {false, SHARED},
38
-	"rshared":       {false, RSHARED},
39
-	"slave":         {false, SLAVE},
40
-	"rslave":        {false, RSLAVE},
41
-	"relatime":      {false, RELATIME},
42
-	"norelatime":    {true, RELATIME},
43
-	"strictatime":   {false, STRICTATIME},
44
-	"nostrictatime": {true, STRICTATIME},
45
-}
46
-
47
-var validFlags = map[string]bool{
48
-	"":          true,
49
-	"size":      true,
50
-	"mode":      true,
51
-	"uid":       true,
52
-	"gid":       true,
53
-	"nr_inodes": true,
54
-	"nr_blocks": true,
55
-	"mpol":      true,
56
-}
57
-
58
-var propagationFlags = map[string]bool{
59
-	"bind":        true,
60
-	"rbind":       true,
61
-	"unbindable":  true,
62
-	"runbindable": true,
63
-	"private":     true,
64
-	"rprivate":    true,
65
-	"shared":      true,
66
-	"rshared":     true,
67
-	"slave":       true,
68
-	"rslave":      true,
69
-}
70
-
71
-// MergeTmpfsOptions merge mount options to make sure there is no duplicate.
72
-func MergeTmpfsOptions(options []string) ([]string, error) {
73
-	// We use collisions maps to remove duplicates.
74
-	// For flag, the key is the flag value (the key for propagation flag is -1)
75
-	// For data=value, the key is the data
76
-	flagCollisions := map[int]bool{}
77
-	dataCollisions := map[string]bool{}
78
-
79
-	var newOptions []string
80
-	// We process in reverse order
81
-	for i := len(options) - 1; i >= 0; i-- {
82
-		option := options[i]
83
-		if option == "defaults" {
84
-			continue
85
-		}
86
-		if f, ok := flags[option]; ok && f.flag != 0 {
87
-			// There is only one propagation mode
88
-			key := f.flag
89
-			if propagationFlags[option] {
90
-				key = -1
91
-			}
92
-			// Check to see if there is collision for flag
93
-			if !flagCollisions[key] {
94
-				// We prepend the option and add to collision map
95
-				newOptions = append([]string{option}, newOptions...)
96
-				flagCollisions[key] = true
97
-			}
98
-			continue
99
-		}
100
-		opt := strings.SplitN(option, "=", 2)
101
-		if len(opt) != 2 || !validFlags[opt[0]] {
102
-			return nil, fmt.Errorf("Invalid tmpfs option %q", opt)
103
-		}
104
-		if !dataCollisions[opt[0]] {
105
-			// We prepend the option and add to collision map
106
-			newOptions = append([]string{option}, newOptions...)
107
-			dataCollisions[opt[0]] = true
108
-		}
109
-	}
110
-
111
-	return newOptions, nil
112
-}
113
-
114
-// Parse fstab type mount options into mount() flags
115
-// and device specific data
116
-func parseOptions(options string) (int, string) {
117
-	var (
118
-		flag int
119
-		data []string
120
-	)
121
-
122
-	for _, o := range strings.Split(options, ",") {
123
-		// If the option does not exist in the flags table or the flag
124
-		// is not supported on the platform,
125
-		// then it is a data value for a specific fs type
126
-		if f, exists := flags[o]; exists && f.flag != 0 {
127
-			if f.clear {
128
-				flag &= ^f.flag
129
-			} else {
130
-				flag |= f.flag
131
-			}
132
-		} else {
133
-			data = append(data, o)
134
-		}
135
-	}
136
-	return flag, strings.Join(data, ",")
137
-}
138 1
new file mode 100644
... ...
@@ -0,0 +1,45 @@
0
+// +build freebsd openbsd
1
+
2
+package mount
3
+
4
+import "golang.org/x/sys/unix"
5
+
6
+const (
7
+	// RDONLY will mount the filesystem as read-only.
8
+	RDONLY = unix.MNT_RDONLY
9
+
10
+	// NOSUID will not allow set-user-identifier or set-group-identifier bits to
11
+	// take effect.
12
+	NOSUID = unix.MNT_NOSUID
13
+
14
+	// NOEXEC will not allow execution of any binaries on the mounted file system.
15
+	NOEXEC = unix.MNT_NOEXEC
16
+
17
+	// SYNCHRONOUS will allow any I/O to the file system to be done synchronously.
18
+	SYNCHRONOUS = unix.MNT_SYNCHRONOUS
19
+
20
+	// NOATIME will not update the file access time when reading from a file.
21
+	NOATIME = unix.MNT_NOATIME
22
+)
23
+
24
+// These flags are unsupported.
25
+const (
26
+	BIND        = 0
27
+	DIRSYNC     = 0
28
+	MANDLOCK    = 0
29
+	NODEV       = 0
30
+	NODIRATIME  = 0
31
+	UNBINDABLE  = 0
32
+	RUNBINDABLE = 0
33
+	PRIVATE     = 0
34
+	RPRIVATE    = 0
35
+	SHARED      = 0
36
+	RSHARED     = 0
37
+	SLAVE       = 0
38
+	RSLAVE      = 0
39
+	RBIND       = 0
40
+	RELATIME    = 0
41
+	REMOUNT     = 0
42
+	STRICTATIME = 0
43
+	mntDetach   = 0
44
+)
0 45
deleted file mode 100644
... ...
@@ -1,48 +0,0 @@
1
-// +build freebsd,cgo
2
-
3
-package mount
4
-
5
-/*
6
-#include <sys/mount.h>
7
-*/
8
-import "C"
9
-
10
-const (
11
-	// RDONLY will mount the filesystem as read-only.
12
-	RDONLY = C.MNT_RDONLY
13
-
14
-	// NOSUID will not allow set-user-identifier or set-group-identifier bits to
15
-	// take effect.
16
-	NOSUID = C.MNT_NOSUID
17
-
18
-	// NOEXEC will not allow execution of any binaries on the mounted file system.
19
-	NOEXEC = C.MNT_NOEXEC
20
-
21
-	// SYNCHRONOUS will allow any I/O to the file system to be done synchronously.
22
-	SYNCHRONOUS = C.MNT_SYNCHRONOUS
23
-
24
-	// NOATIME will not update the file access time when reading from a file.
25
-	NOATIME = C.MNT_NOATIME
26
-)
27
-
28
-// These flags are unsupported.
29
-const (
30
-	BIND        = 0
31
-	DIRSYNC     = 0
32
-	MANDLOCK    = 0
33
-	NODEV       = 0
34
-	NODIRATIME  = 0
35
-	UNBINDABLE  = 0
36
-	RUNBINDABLE = 0
37
-	PRIVATE     = 0
38
-	RPRIVATE    = 0
39
-	SHARED      = 0
40
-	RSHARED     = 0
41
-	SLAVE       = 0
42
-	RSLAVE      = 0
43
-	RBIND       = 0
44
-	RELATIME    = 0
45
-	REMOUNT     = 0
46
-	STRICTATIME = 0
47
-	mntDetach   = 0
48
-)
49 1
new file mode 100644
... ...
@@ -0,0 +1,139 @@
0
+// +build !darwin,!windows
1
+
2
+package mount
3
+
4
+import (
5
+	"fmt"
6
+	"strings"
7
+)
8
+
9
+var flags = map[string]struct {
10
+	clear bool
11
+	flag  int
12
+}{
13
+	"defaults":      {false, 0},
14
+	"ro":            {false, RDONLY},
15
+	"rw":            {true, RDONLY},
16
+	"suid":          {true, NOSUID},
17
+	"nosuid":        {false, NOSUID},
18
+	"dev":           {true, NODEV},
19
+	"nodev":         {false, NODEV},
20
+	"exec":          {true, NOEXEC},
21
+	"noexec":        {false, NOEXEC},
22
+	"sync":          {false, SYNCHRONOUS},
23
+	"async":         {true, SYNCHRONOUS},
24
+	"dirsync":       {false, DIRSYNC},
25
+	"remount":       {false, REMOUNT},
26
+	"mand":          {false, MANDLOCK},
27
+	"nomand":        {true, MANDLOCK},
28
+	"atime":         {true, NOATIME},
29
+	"noatime":       {false, NOATIME},
30
+	"diratime":      {true, NODIRATIME},
31
+	"nodiratime":    {false, NODIRATIME},
32
+	"bind":          {false, BIND},
33
+	"rbind":         {false, RBIND},
34
+	"unbindable":    {false, UNBINDABLE},
35
+	"runbindable":   {false, RUNBINDABLE},
36
+	"private":       {false, PRIVATE},
37
+	"rprivate":      {false, RPRIVATE},
38
+	"shared":        {false, SHARED},
39
+	"rshared":       {false, RSHARED},
40
+	"slave":         {false, SLAVE},
41
+	"rslave":        {false, RSLAVE},
42
+	"relatime":      {false, RELATIME},
43
+	"norelatime":    {true, RELATIME},
44
+	"strictatime":   {false, STRICTATIME},
45
+	"nostrictatime": {true, STRICTATIME},
46
+}
47
+
48
+var validFlags = map[string]bool{
49
+	"":          true,
50
+	"size":      true,
51
+	"mode":      true,
52
+	"uid":       true,
53
+	"gid":       true,
54
+	"nr_inodes": true,
55
+	"nr_blocks": true,
56
+	"mpol":      true,
57
+}
58
+
59
+var propagationFlags = map[string]bool{
60
+	"bind":        true,
61
+	"rbind":       true,
62
+	"unbindable":  true,
63
+	"runbindable": true,
64
+	"private":     true,
65
+	"rprivate":    true,
66
+	"shared":      true,
67
+	"rshared":     true,
68
+	"slave":       true,
69
+	"rslave":      true,
70
+}
71
+
72
+// MergeTmpfsOptions merge mount options to make sure there is no duplicate.
73
+func MergeTmpfsOptions(options []string) ([]string, error) {
74
+	// We use collisions maps to remove duplicates.
75
+	// For flag, the key is the flag value (the key for propagation flag is -1)
76
+	// For data=value, the key is the data
77
+	flagCollisions := map[int]bool{}
78
+	dataCollisions := map[string]bool{}
79
+
80
+	var newOptions []string
81
+	// We process in reverse order
82
+	for i := len(options) - 1; i >= 0; i-- {
83
+		option := options[i]
84
+		if option == "defaults" {
85
+			continue
86
+		}
87
+		if f, ok := flags[option]; ok && f.flag != 0 {
88
+			// There is only one propagation mode
89
+			key := f.flag
90
+			if propagationFlags[option] {
91
+				key = -1
92
+			}
93
+			// Check to see if there is collision for flag
94
+			if !flagCollisions[key] {
95
+				// We prepend the option and add to collision map
96
+				newOptions = append([]string{option}, newOptions...)
97
+				flagCollisions[key] = true
98
+			}
99
+			continue
100
+		}
101
+		opt := strings.SplitN(option, "=", 2)
102
+		if len(opt) != 2 || !validFlags[opt[0]] {
103
+			return nil, fmt.Errorf("Invalid tmpfs option %q", opt)
104
+		}
105
+		if !dataCollisions[opt[0]] {
106
+			// We prepend the option and add to collision map
107
+			newOptions = append([]string{option}, newOptions...)
108
+			dataCollisions[opt[0]] = true
109
+		}
110
+	}
111
+
112
+	return newOptions, nil
113
+}
114
+
115
+// Parse fstab type mount options into mount() flags
116
+// and device specific data
117
+func parseOptions(options string) (int, string) {
118
+	var (
119
+		flag int
120
+		data []string
121
+	)
122
+
123
+	for _, o := range strings.Split(options, ",") {
124
+		// If the option does not exist in the flags table or the flag
125
+		// is not supported on the platform,
126
+		// then it is a data value for a specific fs type
127
+		if f, exists := flags[o]; exists && f.flag != 0 {
128
+			if f.clear {
129
+				flag &= ^f.flag
130
+			} else {
131
+				flag |= f.flag
132
+			}
133
+		} else {
134
+			data = append(data, o)
135
+		}
136
+	}
137
+	return flag, strings.Join(data, ",")
138
+}
0 139
deleted file mode 100644
... ...
@@ -1,30 +0,0 @@
1
-// +build !linux,!freebsd freebsd,!cgo
2
-
3
-package mount
4
-
5
-// These flags are unsupported.
6
-const (
7
-	BIND        = 0
8
-	DIRSYNC     = 0
9
-	MANDLOCK    = 0
10
-	NOATIME     = 0
11
-	NODEV       = 0
12
-	NODIRATIME  = 0
13
-	NOEXEC      = 0
14
-	NOSUID      = 0
15
-	UNBINDABLE  = 0
16
-	RUNBINDABLE = 0
17
-	PRIVATE     = 0
18
-	RPRIVATE    = 0
19
-	SHARED      = 0
20
-	RSHARED     = 0
21
-	SLAVE       = 0
22
-	RSLAVE      = 0
23
-	RBIND       = 0
24
-	RELATIME    = 0
25
-	REMOUNT     = 0
26
-	STRICTATIME = 0
27
-	SYNCHRONOUS = 0
28
-	RDONLY      = 0
29
-	mntDetach   = 0
30
-)
... ...
@@ -3,6 +3,6 @@ module github.com/moby/sys/mount
3 3
 go 1.14
4 4
 
5 5
 require (
6
-	github.com/moby/sys/mountinfo v0.1.0
7
-	golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae
6
+	github.com/moby/sys/mountinfo v0.3.1
7
+	golang.org/x/sys v0.0.0-20200922070232-aee5d888a860
8 8
 )
9 9
deleted file mode 100644
... ...
@@ -1,56 +0,0 @@
1
-// +build go1.13
2
-
3
-package mount
4
-
5
-import (
6
-	"fmt"
7
-	"sort"
8
-
9
-	"github.com/moby/sys/mountinfo"
10
-)
11
-
12
-// Mount will mount filesystem according to the specified configuration.
13
-// Options must be specified like the mount or fstab unix commands:
14
-// "opt1=val1,opt2=val2". See flags.go for supported option flags.
15
-func Mount(device, target, mType, options string) error {
16
-	flag, data := parseOptions(options)
17
-	return mount(device, target, mType, uintptr(flag), data)
18
-}
19
-
20
-// Unmount lazily unmounts a filesystem on supported platforms, otherwise
21
-// does a normal unmount.
22
-func Unmount(target string) error {
23
-	return unmount(target, mntDetach)
24
-}
25
-
26
-// RecursiveUnmount unmounts the target and all mounts underneath, starting with
27
-// the deepsest mount first.
28
-func RecursiveUnmount(target string) error {
29
-	mounts, err := mountinfo.GetMounts(mountinfo.PrefixFilter(target))
30
-	if err != nil {
31
-		return err
32
-	}
33
-
34
-	// Make the deepest mount be first
35
-	sort.Slice(mounts, func(i, j int) bool {
36
-		return len(mounts[i].Mountpoint) > len(mounts[j].Mountpoint)
37
-	})
38
-
39
-	var suberr error
40
-	for i, m := range mounts {
41
-		err = unmount(m.Mountpoint, mntDetach)
42
-		if err != nil {
43
-			if i == len(mounts)-1 { // last mount
44
-				return fmt.Errorf("%w (possible cause: %s)", err, suberr)
45
-			}
46
-			// This is a submount, we can ignore the error for now,
47
-			// the final unmount will fail if this is a real problem.
48
-			// With that in mind, the _first_ failed unmount error
49
-			// might be the real error cause, so let's keep it.
50
-			if suberr == nil {
51
-				suberr = err
52
-			}
53
-		}
54
-	}
55
-	return nil
56
-}
57 1
new file mode 100644
... ...
@@ -0,0 +1,87 @@
0
+// +build !darwin,!windows
1
+
2
+package mount
3
+
4
+import (
5
+	"fmt"
6
+	"sort"
7
+
8
+	"github.com/moby/sys/mountinfo"
9
+	"golang.org/x/sys/unix"
10
+)
11
+
12
+// Mount will mount filesystem according to the specified configuration.
13
+// Options must be specified like the mount or fstab unix commands:
14
+// "opt1=val1,opt2=val2". See flags.go for supported option flags.
15
+func Mount(device, target, mType, options string) error {
16
+	flag, data := parseOptions(options)
17
+	return mount(device, target, mType, uintptr(flag), data)
18
+}
19
+
20
+// Unmount lazily unmounts a filesystem on supported platforms, otherwise does
21
+// a normal unmount. If target is not a mount point, no error is returned.
22
+func Unmount(target string) error {
23
+	err := unix.Unmount(target, mntDetach)
24
+	if err == nil || err == unix.EINVAL {
25
+		// Ignore "not mounted" error here. Note the same error
26
+		// can be returned if flags are invalid, so this code
27
+		// assumes that the flags value is always correct.
28
+		return nil
29
+	}
30
+
31
+	return &mountError{
32
+		op:     "umount",
33
+		target: target,
34
+		flags:  uintptr(mntDetach),
35
+		err:    err,
36
+	}
37
+}
38
+
39
+// RecursiveUnmount unmounts the target and all mounts underneath, starting
40
+// with the deepest mount first. The argument does not have to be a mount
41
+// point itself.
42
+func RecursiveUnmount(target string) error {
43
+	// Fast path, works if target is a mount point that can be unmounted.
44
+	// On Linux, mntDetach flag ensures a recursive unmount.  For other
45
+	// platforms, if there are submounts, we'll get EBUSY (and fall back
46
+	// to the slow path). NOTE we do not ignore EINVAL here as target might
47
+	// not be a mount point itself (but there can be mounts underneath).
48
+	if err := unix.Unmount(target, mntDetach); err == nil {
49
+		return nil
50
+	}
51
+
52
+	// Slow path: get all submounts, sort, unmount one by one.
53
+	mounts, err := mountinfo.GetMounts(mountinfo.PrefixFilter(target))
54
+	if err != nil {
55
+		return err
56
+	}
57
+
58
+	// Make the deepest mount be first
59
+	sort.Slice(mounts, func(i, j int) bool {
60
+		return len(mounts[i].Mountpoint) > len(mounts[j].Mountpoint)
61
+	})
62
+
63
+	var (
64
+		suberr    error
65
+		lastMount = len(mounts) - 1
66
+	)
67
+	for i, m := range mounts {
68
+		err = Unmount(m.Mountpoint)
69
+		if err != nil {
70
+			if i == lastMount {
71
+				if suberr != nil {
72
+					return fmt.Errorf("%w (possible cause: %s)", err, suberr)
73
+				}
74
+				return err
75
+			}
76
+			// This is a submount, we can ignore the error for now,
77
+			// the final unmount will fail if this is a real problem.
78
+			// With that in mind, the _first_ failed unmount error
79
+			// might be the real error cause, so let's keep it.
80
+			if suberr == nil {
81
+				suberr = err
82
+			}
83
+		}
84
+	}
85
+	return nil
86
+}
0 87
new file mode 100644
... ...
@@ -0,0 +1,61 @@
0
+// +build freebsd,cgo openbsd,cgo
1
+
2
+package mount
3
+
4
+/*
5
+#include <errno.h>
6
+#include <stdlib.h>
7
+#include <string.h>
8
+#include <sys/_iovec.h>
9
+#include <sys/mount.h>
10
+#include <sys/param.h>
11
+*/
12
+import "C"
13
+
14
+import (
15
+	"strings"
16
+	"syscall"
17
+	"unsafe"
18
+)
19
+
20
+func allocateIOVecs(options []string) []C.struct_iovec {
21
+	out := make([]C.struct_iovec, len(options))
22
+	for i, option := range options {
23
+		out[i].iov_base = unsafe.Pointer(C.CString(option))
24
+		out[i].iov_len = C.size_t(len(option) + 1)
25
+	}
26
+	return out
27
+}
28
+
29
+func mount(device, target, mType string, flag uintptr, data string) error {
30
+	isNullFS := false
31
+
32
+	xs := strings.Split(data, ",")
33
+	for _, x := range xs {
34
+		if x == "bind" {
35
+			isNullFS = true
36
+		}
37
+	}
38
+
39
+	options := []string{"fspath", target}
40
+	if isNullFS {
41
+		options = append(options, "fstype", "nullfs", "target", device)
42
+	} else {
43
+		options = append(options, "fstype", mType, "from", device)
44
+	}
45
+	rawOptions := allocateIOVecs(options)
46
+	for _, rawOption := range rawOptions {
47
+		defer C.free(rawOption.iov_base)
48
+	}
49
+
50
+	if errno := C.nmount(&rawOptions[0], C.uint(len(options)), C.int(flag)); errno != 0 {
51
+		return &mountError{
52
+			op:     "mount",
53
+			source: device,
54
+			target: target,
55
+			flags:  flag,
56
+			err:    syscall.Errno(errno),
57
+		}
58
+	}
59
+	return nil
60
+}
0 61
deleted file mode 100644
... ...
@@ -1,59 +0,0 @@
1
-package mount
2
-
3
-/*
4
-#include <errno.h>
5
-#include <stdlib.h>
6
-#include <string.h>
7
-#include <sys/_iovec.h>
8
-#include <sys/mount.h>
9
-#include <sys/param.h>
10
-*/
11
-import "C"
12
-
13
-import (
14
-	"strings"
15
-	"syscall"
16
-	"unsafe"
17
-)
18
-
19
-func allocateIOVecs(options []string) []C.struct_iovec {
20
-	out := make([]C.struct_iovec, len(options))
21
-	for i, option := range options {
22
-		out[i].iov_base = unsafe.Pointer(C.CString(option))
23
-		out[i].iov_len = C.size_t(len(option) + 1)
24
-	}
25
-	return out
26
-}
27
-
28
-func mount(device, target, mType string, flag uintptr, data string) error {
29
-	isNullFS := false
30
-
31
-	xs := strings.Split(data, ",")
32
-	for _, x := range xs {
33
-		if x == "bind" {
34
-			isNullFS = true
35
-		}
36
-	}
37
-
38
-	options := []string{"fspath", target}
39
-	if isNullFS {
40
-		options = append(options, "fstype", "nullfs", "target", device)
41
-	} else {
42
-		options = append(options, "fstype", mType, "from", device)
43
-	}
44
-	rawOptions := allocateIOVecs(options)
45
-	for _, rawOption := range rawOptions {
46
-		defer C.free(rawOption.iov_base)
47
-	}
48
-
49
-	if errno := C.nmount(&rawOptions[0], C.uint(len(options)), C.int(flag)); errno != 0 {
50
-		return &mountError{
51
-			op:     "mount",
52
-			source: device,
53
-			target: target,
54
-			flags:  flag,
55
-			err:    syscall.Errno(errno),
56
-		}
57
-	}
58
-	return nil
59
-}
... ...
@@ -1,7 +1,7 @@
1
-// +build !linux,!freebsd freebsd,!cgo
1
+// +build !linux,!freebsd,!openbsd,!windows freebsd,!cgo openbsd,!cgo
2 2
 
3 3
 package mount
4 4
 
5 5
 func mount(device, target, mType string, flag uintptr, data string) error {
6
-	panic("Not implemented")
6
+	panic("cgo required on freebsd and openbsd")
7 7
 }
8 8
deleted file mode 100644
... ...
@@ -1,22 +0,0 @@
1
-// +build !windows
2
-
3
-package mount
4
-
5
-import "golang.org/x/sys/unix"
6
-
7
-func unmount(target string, flags int) error {
8
-	err := unix.Unmount(target, flags)
9
-	if err == nil || err == unix.EINVAL {
10
-		// Ignore "not mounted" error here. Note the same error
11
-		// can be returned if flags are invalid, so this code
12
-		// assumes that the flags value is always correct.
13
-		return nil
14
-	}
15
-
16
-	return &mountError{
17
-		op:     "umount",
18
-		target: target,
19
-		flags:  uintptr(flags),
20
-		err:    err,
21
-	}
22
-}
23 1
deleted file mode 100644
... ...
@@ -1,7 +0,0 @@
1
-// +build windows
2
-
3
-package mount
4
-
5
-func unmount(target string, flag int) error {
6
-	panic("Not implemented")
7
-}
8 1
new file mode 100644
... ...
@@ -0,0 +1,44 @@
0
+// Package mountinfo provides a set of functions to retrieve information about OS mounts.
1
+//
2
+// Currently it supports Linux. For historical reasons, there is also some support for FreeBSD and OpenBSD,
3
+// and a shallow implementation for Windows, but in general this is Linux-only package, so
4
+// the rest of the document only applies to Linux, unless explicitly specified otherwise.
5
+//
6
+// In Linux, information about mounts seen by the current process is available from
7
+// /proc/self/mountinfo. Note that due to mount namespaces, different processes can
8
+// see different mounts. A per-process mountinfo table is available from /proc/<PID>/mountinfo,
9
+// where <PID> is a numerical process identifier.
10
+//
11
+// In general, /proc is not a very efficient interface, and mountinfo is not an exception.
12
+// For example, there is no way to get information about a specific mount point (i.e. it
13
+// is all-or-nothing). This package tries to hide the /proc ineffectiveness by using
14
+// parse filters while reading mountinfo. A filter can skip some entries, or stop
15
+// processing the rest of the file once the needed information is found.
16
+//
17
+// For mountinfo filters that accept path as an argument, the path must be absolute,
18
+// having all symlinks resolved, and being cleaned (i.e. no extra slashes or dots).
19
+// One way to achieve all of the above is to employ filepath.Abs followed by
20
+// filepath.EvalSymlinks (the latter calls filepath.Clean on the result so
21
+// there is no need to explicitly call filepath.Clean).
22
+//
23
+// NOTE that in many cases there is no need to consult mountinfo at all. Here are some
24
+// of the cases where mountinfo should not be parsed:
25
+//
26
+// 1. Before performing a mount. Usually, this is not needed, but if required (say to
27
+// prevent over-mounts), to check whether a directory is mounted, call os.Lstat
28
+// on it and its parent directory, and compare their st.Sys().(*syscall.Stat_t).Dev
29
+// fields -- if they differ, then the directory is the mount point. NOTE this does
30
+// not work for bind mounts. Optionally, the filesystem type can also be checked
31
+// by calling unix.Statfs and checking the Type field (i.e. filesystem type).
32
+//
33
+// 2. After performing a mount. If there is no error returned, the mount succeeded;
34
+// checking the mount table for a new mount is redundant and expensive.
35
+//
36
+// 3. Before performing an unmount. It is more efficient to do an unmount and ignore
37
+// a specific error (EINVAL) which tells the directory is not mounted.
38
+//
39
+// 4. After performing an unmount. If there is no error returned, the unmount succeeded.
40
+//
41
+// 5. To find the mount point root of a specific directory. You can perform os.Stat()
42
+// on the directory and traverse up until the Dev field of a parent directory differs.
43
+package mountinfo
... ...
@@ -1,3 +1,5 @@
1 1
 module github.com/moby/sys/mountinfo
2 2
 
3 3
 go 1.14
4
+
5
+require golang.org/x/sys v0.0.0-20200909081042-eff7692f9009
4 6
new file mode 100644
... ...
@@ -0,0 +1,58 @@
0
+package mountinfo
1
+
2
+import (
3
+	"os"
4
+	"path/filepath"
5
+
6
+	"golang.org/x/sys/unix"
7
+)
8
+
9
+// mountedByOpenat2 is a method of detecting a mount that works for all kinds
10
+// of mounts (incl. bind mounts), but requires a recent (v5.6+) linux kernel.
11
+func mountedByOpenat2(path string) (bool, error) {
12
+	dir, last := filepath.Split(path)
13
+
14
+	dirfd, err := unix.Openat2(unix.AT_FDCWD, dir, &unix.OpenHow{
15
+		Flags: unix.O_PATH | unix.O_CLOEXEC,
16
+	})
17
+	if err != nil {
18
+		if err == unix.ENOENT { // not a mount
19
+			return false, nil
20
+		}
21
+		return false, &os.PathError{Op: "openat2", Path: dir, Err: err}
22
+	}
23
+	fd, err := unix.Openat2(dirfd, last, &unix.OpenHow{
24
+		Flags:   unix.O_PATH | unix.O_CLOEXEC | unix.O_NOFOLLOW,
25
+		Resolve: unix.RESOLVE_NO_XDEV,
26
+	})
27
+	_ = unix.Close(dirfd)
28
+	switch err {
29
+	case nil: // definitely not a mount
30
+		_ = unix.Close(fd)
31
+		return false, nil
32
+	case unix.EXDEV: // definitely a mount
33
+		return true, nil
34
+	case unix.ENOENT: // not a mount
35
+		return false, nil
36
+	}
37
+	// not sure
38
+	return false, &os.PathError{Op: "openat2", Path: path, Err: err}
39
+}
40
+
41
+func mounted(path string) (bool, error) {
42
+	// Try a fast path, using openat2() with RESOLVE_NO_XDEV.
43
+	mounted, err := mountedByOpenat2(path)
44
+	if err == nil {
45
+		return mounted, nil
46
+	}
47
+	// Another fast path: compare st.st_dev fields.
48
+	mounted, err = mountedByStat(path)
49
+	// This does not work for bind mounts, so false negative
50
+	// is possible, therefore only trust if return is true.
51
+	if mounted && err == nil {
52
+		return mounted, nil
53
+	}
54
+
55
+	// Fallback to parsing mountinfo
56
+	return mountedByMountinfo(path)
57
+}
0 58
new file mode 100644
... ...
@@ -0,0 +1,66 @@
0
+// +build linux freebsd,cgo openbsd,cgo
1
+
2
+package mountinfo
3
+
4
+import (
5
+	"errors"
6
+	"fmt"
7
+	"os"
8
+	"path/filepath"
9
+
10
+	"golang.org/x/sys/unix"
11
+)
12
+
13
+func mountedByStat(path string) (bool, error) {
14
+	var st unix.Stat_t
15
+
16
+	if err := unix.Lstat(path, &st); err != nil {
17
+		if err == unix.ENOENT {
18
+			// Treat ENOENT as "not mounted".
19
+			return false, nil
20
+		}
21
+		return false, &os.PathError{Op: "stat", Path: path, Err: err}
22
+	}
23
+	dev := st.Dev
24
+	parent := filepath.Dir(path)
25
+	if err := unix.Lstat(parent, &st); err != nil {
26
+		return false, &os.PathError{Op: "stat", Path: parent, Err: err}
27
+	}
28
+	if dev != st.Dev {
29
+		// Device differs from that of parent,
30
+		// so definitely a mount point.
31
+		return true, nil
32
+	}
33
+	// NB: this does not detect bind mounts on Linux.
34
+	return false, nil
35
+}
36
+
37
+func normalizePath(path string) (realPath string, err error) {
38
+	if realPath, err = filepath.Abs(path); err != nil {
39
+		return "", fmt.Errorf("unable to get absolute path for %q: %w", path, err)
40
+	}
41
+	if realPath, err = filepath.EvalSymlinks(realPath); err != nil {
42
+		return "", fmt.Errorf("failed to canonicalise path for %q: %w", path, err)
43
+	}
44
+	if _, err := os.Stat(realPath); err != nil {
45
+		return "", fmt.Errorf("failed to stat target of %q: %w", path, err)
46
+	}
47
+	return realPath, nil
48
+}
49
+
50
+func mountedByMountinfo(path string) (bool, error) {
51
+	path, err := normalizePath(path)
52
+	if err != nil {
53
+		if errors.Is(err, unix.ENOENT) {
54
+			// treat ENOENT as "not mounted"
55
+			return false, nil
56
+		}
57
+		return false, err
58
+	}
59
+	entries, err := GetMounts(SingleEntryFilter(path))
60
+	if err != nil {
61
+		return false, err
62
+	}
63
+
64
+	return len(entries) > 0, nil
65
+}
... ...
@@ -1,6 +1,8 @@
1 1
 package mountinfo
2 2
 
3
-import "io"
3
+import (
4
+	"os"
5
+)
4 6
 
5 7
 // GetMounts retrieves a list of mounts for the current running process,
6 8
 // with an optional filter applied (use nil for no filter).
... ...
@@ -8,23 +10,17 @@ func GetMounts(f FilterFunc) ([]*Info, error) {
8 8
 	return parseMountTable(f)
9 9
 }
10 10
 
11
-// GetMountsFromReader retrieves a list of mounts from the
12
-// reader provided, with an optional filter applied (use nil
13
-// for no filter). This can be useful in tests or benchmarks
14
-// that provide a fake mountinfo data.
15
-func GetMountsFromReader(reader io.Reader, f FilterFunc) ([]*Info, error) {
16
-	return parseInfoFile(reader, f)
17
-}
18
-
19
-// Mounted determines if a specified mountpoint has been mounted.
20
-// On Linux it looks at /proc/self/mountinfo.
21
-func Mounted(mountpoint string) (bool, error) {
22
-	entries, err := GetMounts(SingleEntryFilter(mountpoint))
23
-	if err != nil {
24
-		return false, err
11
+// Mounted determines if a specified path is a mount point.
12
+//
13
+// The argument must be an absolute path, with all symlinks resolved, and clean.
14
+// One way to ensure it is to process the path using filepath.Abs followed by
15
+// filepath.EvalSymlinks before calling this function.
16
+func Mounted(path string) (bool, error) {
17
+	// root is always mounted
18
+	if path == string(os.PathSeparator) {
19
+		return true, nil
25 20
 	}
26
-
27
-	return len(entries) > 0, nil
21
+	return mounted(path)
28 22
 }
29 23
 
30 24
 // Info reveals information about a particular mounted filesystem. This
... ...
@@ -50,18 +46,18 @@ type Info struct {
50 50
 	// Mountpoint indicates the mount point relative to the process's root.
51 51
 	Mountpoint string
52 52
 
53
-	// Opts represents mount-specific options.
54
-	Opts string
53
+	// Options represents mount-specific options.
54
+	Options string
55 55
 
56 56
 	// Optional represents optional fields.
57 57
 	Optional string
58 58
 
59
-	// Fstype indicates the type of filesystem, such as EXT3.
60
-	Fstype string
59
+	// FSType indicates the type of filesystem, such as EXT3.
60
+	FSType string
61 61
 
62 62
 	// Source indicates filesystem specific information or "none".
63 63
 	Source string
64 64
 
65
-	// VfsOpts represents per super block options.
66
-	VfsOpts string
65
+	// VFSOptions represents per super block options.
66
+	VFSOptions string
67 67
 }
68 68
new file mode 100644
... ...
@@ -0,0 +1,67 @@
0
+// +build freebsd,cgo openbsd,cgo
1
+
2
+package mountinfo
3
+
4
+/*
5
+#include <sys/param.h>
6
+#include <sys/ucred.h>
7
+#include <sys/mount.h>
8
+*/
9
+import "C"
10
+
11
+import (
12
+	"fmt"
13
+	"reflect"
14
+	"unsafe"
15
+)
16
+
17
+// parseMountTable returns information about mounted filesystems
18
+func parseMountTable(filter FilterFunc) ([]*Info, error) {
19
+	var rawEntries *C.struct_statfs
20
+
21
+	count := int(C.getmntinfo(&rawEntries, C.MNT_WAIT))
22
+	if count == 0 {
23
+		return nil, fmt.Errorf("Failed to call getmntinfo")
24
+	}
25
+
26
+	var entries []C.struct_statfs
27
+	header := (*reflect.SliceHeader)(unsafe.Pointer(&entries))
28
+	header.Cap = count
29
+	header.Len = count
30
+	header.Data = uintptr(unsafe.Pointer(rawEntries))
31
+
32
+	var out []*Info
33
+	for _, entry := range entries {
34
+		var mountinfo Info
35
+		var skip, stop bool
36
+		mountinfo.Mountpoint = C.GoString(&entry.f_mntonname[0])
37
+		mountinfo.FSType = C.GoString(&entry.f_fstypename[0])
38
+		mountinfo.Source = C.GoString(&entry.f_mntfromname[0])
39
+
40
+		if filter != nil {
41
+			// filter out entries we're not interested in
42
+			skip, stop = filter(&mountinfo)
43
+			if skip {
44
+				continue
45
+			}
46
+		}
47
+
48
+		out = append(out, &mountinfo)
49
+		if stop {
50
+			break
51
+		}
52
+	}
53
+	return out, nil
54
+}
55
+
56
+func mounted(path string) (bool, error) {
57
+	// Fast path: compare st.st_dev fields.
58
+	// This should always work for FreeBSD and OpenBSD.
59
+	mounted, err := mountedByStat(path)
60
+	if err == nil {
61
+		return mounted, nil
62
+	}
63
+
64
+	// Fallback to parsing mountinfo
65
+	return mountedByMountinfo(path)
66
+}
... ...
@@ -6,16 +6,16 @@ import "strings"
6 6
 // used to filter out mountinfo entries we're not interested in,
7 7
 // and/or stop further processing if we found what we wanted.
8 8
 //
9
-// It takes a pointer to the Info struct (not fully populated,
10
-// currently only Mountpoint, Fstype, Source, and (on Linux)
11
-// VfsOpts are filled in), and returns two booleans:
9
+// It takes a pointer to the Info struct (fully populated with all available
10
+// fields on the GOOS platform), and returns two booleans:
12 11
 //
13
-//  - skip: true if the entry should be skipped
14
-//  - stop: true if parsing should be stopped after the entry
12
+// skip: true if the entry should be skipped;
13
+//
14
+// stop: true if parsing should be stopped after the entry.
15 15
 type FilterFunc func(*Info) (skip, stop bool)
16 16
 
17 17
 // PrefixFilter discards all entries whose mount points
18
-// do not start with a specific prefix
18
+// do not start with a specific prefix.
19 19
 func PrefixFilter(prefix string) FilterFunc {
20 20
 	return func(m *Info) (bool, bool) {
21 21
 		skip := !strings.HasPrefix(m.Mountpoint, prefix)
... ...
@@ -23,7 +23,7 @@ func PrefixFilter(prefix string) FilterFunc {
23 23
 	}
24 24
 }
25 25
 
26
-// SingleEntryFilter looks for a specific entry
26
+// SingleEntryFilter looks for a specific entry.
27 27
 func SingleEntryFilter(mp string) FilterFunc {
28 28
 	return func(m *Info) (bool, bool) {
29 29
 		if m.Mountpoint == mp {
... ...
@@ -36,8 +36,8 @@ func SingleEntryFilter(mp string) FilterFunc {
36 36
 // ParentsFilter returns all entries whose mount points
37 37
 // can be parents of a path specified, discarding others.
38 38
 //
39
-// For example, given `/var/lib/docker/something`, entries
40
-// like `/var/lib/docker`, `/var` and `/` are returned.
39
+// For example, given /var/lib/docker/something, entries
40
+// like /var/lib/docker, /var and / are returned.
41 41
 func ParentsFilter(path string) FilterFunc {
42 42
 	return func(m *Info) (bool, bool) {
43 43
 		skip := !strings.HasPrefix(path, m.Mountpoint)
... ...
@@ -45,12 +45,12 @@ func ParentsFilter(path string) FilterFunc {
45 45
 	}
46 46
 }
47 47
 
48
-// FstypeFilter returns all entries that match provided fstype(s).
49
-func FstypeFilter(fstype ...string) FilterFunc {
48
+// FSTypeFilter returns all entries that match provided fstype(s).
49
+func FSTypeFilter(fstype ...string) FilterFunc {
50 50
 	return func(m *Info) (bool, bool) {
51 51
 		for _, t := range fstype {
52
-			if m.Fstype == t {
53
-				return false, false // don't skeep, keep going
52
+			if m.FSType == t {
53
+				return false, false // don't skip, keep going
54 54
 			}
55 55
 		}
56 56
 		return true, false // skip, keep going
57 57
deleted file mode 100644
... ...
@@ -1,53 +0,0 @@
1
-package mountinfo
2
-
3
-/*
4
-#include <sys/param.h>
5
-#include <sys/ucred.h>
6
-#include <sys/mount.h>
7
-*/
8
-import "C"
9
-
10
-import (
11
-	"fmt"
12
-	"reflect"
13
-	"unsafe"
14
-)
15
-
16
-// parseMountTable returns information about mounted filesystems
17
-func parseMountTable(filter FilterFunc) ([]*Info, error) {
18
-	var rawEntries *C.struct_statfs
19
-
20
-	count := int(C.getmntinfo(&rawEntries, C.MNT_WAIT))
21
-	if count == 0 {
22
-		return nil, fmt.Errorf("Failed to call getmntinfo")
23
-	}
24
-
25
-	var entries []C.struct_statfs
26
-	header := (*reflect.SliceHeader)(unsafe.Pointer(&entries))
27
-	header.Cap = count
28
-	header.Len = count
29
-	header.Data = uintptr(unsafe.Pointer(rawEntries))
30
-
31
-	var out []*Info
32
-	for _, entry := range entries {
33
-		var mountinfo Info
34
-		var skip, stop bool
35
-		mountinfo.Mountpoint = C.GoString(&entry.f_mntonname[0])
36
-		mountinfo.Fstype = C.GoString(&entry.f_fstypename[0])
37
-		mountinfo.Source = C.GoString(&entry.f_mntfromname[0])
38
-
39
-		if filter != nil {
40
-			// filter out entries we're not interested in
41
-			skip, stop = filter(&mountinfo)
42
-			if skip {
43
-				continue
44
-			}
45
-		}
46
-
47
-		out = append(out, &mountinfo)
48
-		if stop {
49
-			break
50
-		}
51
-	}
52
-	return out, nil
53
-}
... ...
@@ -1,5 +1,3 @@
1
-// +build go1.13
2
-
3 1
 package mountinfo
4 2
 
5 3
 import (
... ...
@@ -11,14 +9,18 @@ import (
11 11
 	"strings"
12 12
 )
13 13
 
14
-func parseInfoFile(r io.Reader, filter FilterFunc) ([]*Info, error) {
14
+// GetMountsFromReader retrieves a list of mounts from the
15
+// reader provided, with an optional filter applied (use nil
16
+// for no filter). This can be useful in tests or benchmarks
17
+// that provide a fake mountinfo data.
18
+//
19
+// This function is Linux-specific.
20
+func GetMountsFromReader(r io.Reader, filter FilterFunc) ([]*Info, error) {
15 21
 	s := bufio.NewScanner(r)
16 22
 	out := []*Info{}
17
-	var err error
18 23
 	for s.Scan() {
19
-		if err = s.Err(); err != nil {
20
-			return nil, err
21
-		}
24
+		var err error
25
+
22 26
 		/*
23 27
 		   See http://man7.org/linux/man-pages/man5/proc.5.html
24 28
 
... ...
@@ -70,26 +72,19 @@ func parseInfoFile(r io.Reader, filter FilterFunc) ([]*Info, error) {
70 70
 
71 71
 		p := &Info{}
72 72
 
73
-		// Fill in the fields that a filter might check
74
-		p.Mountpoint, err = strconv.Unquote(`"` + fields[4] + `"`)
73
+		p.Mountpoint, err = unescape(fields[4])
75 74
 		if err != nil {
76
-			return nil, fmt.Errorf("Parsing '%s' failed: unable to unquote mount point field: %w", fields[4], err)
75
+			return nil, fmt.Errorf("Parsing '%s' failed: mount point: %w", fields[4], err)
77 76
 		}
78
-		p.Fstype = fields[sepIdx+1]
79
-		p.Source = fields[sepIdx+2]
80
-		p.VfsOpts = fields[sepIdx+3]
81
-
82
-		// Run a filter soon so we can skip parsing/adding entries
83
-		// the caller is not interested in
84
-		var skip, stop bool
85
-		if filter != nil {
86
-			skip, stop = filter(p)
87
-			if skip {
88
-				continue
89
-			}
77
+		p.FSType, err = unescape(fields[sepIdx+1])
78
+		if err != nil {
79
+			return nil, fmt.Errorf("Parsing '%s' failed: fstype: %w", fields[sepIdx+1], err)
90 80
 		}
91
-
92
-		// Fill in the rest of the fields
81
+		p.Source, err = unescape(fields[sepIdx+2])
82
+		if err != nil {
83
+			return nil, fmt.Errorf("Parsing '%s' failed: source: %w", fields[sepIdx+2], err)
84
+		}
85
+		p.VFSOptions = fields[sepIdx+3]
93 86
 
94 87
 		// ignore any numbers parsing errors, as there should not be any
95 88
 		p.ID, _ = strconv.Atoi(fields[0])
... ...
@@ -101,12 +96,12 @@ func parseInfoFile(r io.Reader, filter FilterFunc) ([]*Info, error) {
101 101
 		p.Major, _ = strconv.Atoi(mm[0])
102 102
 		p.Minor, _ = strconv.Atoi(mm[1])
103 103
 
104
-		p.Root, err = strconv.Unquote(`"` + fields[3] + `"`)
104
+		p.Root, err = unescape(fields[3])
105 105
 		if err != nil {
106
-			return nil, fmt.Errorf("Parsing '%s' failed: unable to unquote root field: %w", fields[3], err)
106
+			return nil, fmt.Errorf("Parsing '%s' failed: root: %w", fields[3], err)
107 107
 		}
108 108
 
109
-		p.Opts = fields[5]
109
+		p.Options = fields[5]
110 110
 
111 111
 		// zero or more optional fields
112 112
 		switch {
... ...
@@ -118,11 +113,23 @@ func parseInfoFile(r io.Reader, filter FilterFunc) ([]*Info, error) {
118 118
 			p.Optional = strings.Join(fields[6:sepIdx-1], " ")
119 119
 		}
120 120
 
121
+		// Run the filter after parsing all of the fields.
122
+		var skip, stop bool
123
+		if filter != nil {
124
+			skip, stop = filter(p)
125
+			if skip {
126
+				continue
127
+			}
128
+		}
129
+
121 130
 		out = append(out, p)
122 131
 		if stop {
123 132
 			break
124 133
 		}
125 134
 	}
135
+	if err := s.Err(); err != nil {
136
+		return nil, err
137
+	}
126 138
 	return out, nil
127 139
 }
128 140
 
... ...
@@ -135,12 +142,17 @@ func parseMountTable(filter FilterFunc) ([]*Info, error) {
135 135
 	}
136 136
 	defer f.Close()
137 137
 
138
-	return parseInfoFile(f, filter)
138
+	return GetMountsFromReader(f, filter)
139 139
 }
140 140
 
141
-// PidMountInfo collects the mounts for a specific process ID. If the process
142
-// ID is unknown, it is better to use `GetMounts` which will inspect
143
-// "/proc/self/mountinfo" instead.
141
+// PidMountInfo retrieves the list of mounts from a given process' mount
142
+// namespace. Unless there is a need to get mounts from a mount namespace
143
+// different from that of a calling process, use GetMounts.
144
+//
145
+// This function is Linux-specific.
146
+//
147
+// Deprecated: this will be removed before v1; use GetMountsFromReader with
148
+// opened /proc/<pid>/mountinfo as an argument instead.
144 149
 func PidMountInfo(pid int) ([]*Info, error) {
145 150
 	f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid))
146 151
 	if err != nil {
... ...
@@ -148,5 +160,63 @@ func PidMountInfo(pid int) ([]*Info, error) {
148 148
 	}
149 149
 	defer f.Close()
150 150
 
151
-	return parseInfoFile(f, nil)
151
+	return GetMountsFromReader(f, nil)
152
+}
153
+
154
+// A few specific characters in mountinfo path entries (root and mountpoint)
155
+// are escaped using a backslash followed by a character's ascii code in octal.
156
+//
157
+//   space              -- as \040
158
+//   tab (aka \t)       -- as \011
159
+//   newline (aka \n)   -- as \012
160
+//   backslash (aka \\) -- as \134
161
+//
162
+// This function converts path from mountinfo back, i.e. it unescapes the above sequences.
163
+func unescape(path string) (string, error) {
164
+	// try to avoid copying
165
+	if strings.IndexByte(path, '\\') == -1 {
166
+		return path, nil
167
+	}
168
+
169
+	// The following code is UTF-8 transparent as it only looks for some
170
+	// specific characters (backslash and 0..7) with values < utf8.RuneSelf,
171
+	// and everything else is passed through as is.
172
+	buf := make([]byte, len(path))
173
+	bufLen := 0
174
+	for i := 0; i < len(path); i++ {
175
+		if path[i] != '\\' {
176
+			buf[bufLen] = path[i]
177
+			bufLen++
178
+			continue
179
+		}
180
+		s := path[i:]
181
+		if len(s) < 4 {
182
+			// too short
183
+			return "", fmt.Errorf("bad escape sequence %q: too short", s)
184
+		}
185
+		c := s[1]
186
+		switch c {
187
+		case '0', '1', '2', '3', '4', '5', '6', '7':
188
+			v := c - '0'
189
+			for j := 2; j < 4; j++ { // one digit already; two more
190
+				if s[j] < '0' || s[j] > '7' {
191
+					return "", fmt.Errorf("bad escape sequence %q: not a digit", s[:3])
192
+				}
193
+				x := s[j] - '0'
194
+				v = (v << 3) | x
195
+			}
196
+			if v > 255 {
197
+				return "", fmt.Errorf("bad escape sequence %q: out of range" + s[:3])
198
+			}
199
+			buf[bufLen] = v
200
+			bufLen++
201
+			i += 3
202
+			continue
203
+		default:
204
+			return "", fmt.Errorf("bad escape sequence %q: not a digit" + s[:3])
205
+
206
+		}
207
+	}
208
+
209
+	return string(buf[:bufLen]), nil
152 210
 }
... ...
@@ -1,17 +1,18 @@
1
-// +build !windows,!linux,!freebsd freebsd,!cgo
1
+// +build !windows,!linux,!freebsd,!openbsd freebsd,!cgo openbsd,!cgo
2 2
 
3 3
 package mountinfo
4 4
 
5 5
 import (
6 6
 	"fmt"
7
-	"io"
8 7
 	"runtime"
9 8
 )
10 9
 
10
+var errNotImplemented = fmt.Errorf("not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
11
+
11 12
 func parseMountTable(_ FilterFunc) ([]*Info, error) {
12
-	return nil, fmt.Errorf("mount.parseMountTable is not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
13
+	return nil, errNotImplemented
13 14
 }
14 15
 
15
-func parseInfoFile(_ io.Reader, f FilterFunc) ([]*Info, error) {
16
-	return parseMountTable(f)
16
+func mounted(path string) (bool, error) {
17
+	return false, errNotImplemented
17 18
 }
... ...
@@ -1,12 +1,10 @@
1 1
 package mountinfo
2 2
 
3
-import "io"
4
-
5 3
 func parseMountTable(_ FilterFunc) ([]*Info, error) {
6 4
 	// Do NOT return an error!
7 5
 	return nil, nil
8 6
 }
9 7
 
10
-func parseInfoFile(_ io.Reader, f FilterFunc) ([]*Info, error) {
11
-	return parseMountTable(f)
8
+func mounted(_ string) (bool, error) {
9
+	return false, nil
12 10
 }
... ...
@@ -221,17 +221,17 @@ func TestCreateWithOpts(t *testing.T) {
221 221
 
222 222
 	info := mountInfos[0]
223 223
 	t.Logf("%+v", info)
224
-	if info.Fstype != "tmpfs" {
225
-		t.Fatalf("expected tmpfs mount, got %q", info.Fstype)
224
+	if info.FSType != "tmpfs" {
225
+		t.Fatalf("expected tmpfs mount, got %q", info.FSType)
226 226
 	}
227 227
 	if info.Source != "tmpfs" {
228 228
 		t.Fatalf("expected tmpfs mount, got %q", info.Source)
229 229
 	}
230
-	if !strings.Contains(info.VfsOpts, "uid=1000") {
231
-		t.Fatalf("expected mount info to have uid=1000: %q", info.VfsOpts)
230
+	if !strings.Contains(info.VFSOptions, "uid=1000") {
231
+		t.Fatalf("expected mount info to have uid=1000: %q", info.VFSOptions)
232 232
 	}
233
-	if !strings.Contains(info.VfsOpts, "size=1024k") {
234
-		t.Fatalf("expected mount info to have size=1024k: %q", info.VfsOpts)
233
+	if !strings.Contains(info.VFSOptions, "size=1024k") {
234
+		t.Fatalf("expected mount info to have size=1024k: %q", info.VFSOptions)
235 235
 	}
236 236
 
237 237
 	if v.active.count != 1 {