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>
... | ... |
@@ -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 |
} |
... | ... |
@@ -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 |
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 |
-) |
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 |
-} |
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 |
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 { |