1 package mountinfo
2
3 import (
4 "errors"
5 "net"
6 "os"
7 "path/filepath"
8 "reflect"
9 "runtime"
10 "strings"
11 "testing"
12
13 "golang.org/x/sys/unix"
14 )
15
16
17
18
19
20 func tMount(t *testing.T, src, dst, fstype string, flags uintptr, options string) error {
21 if os.Getuid() != 0 {
22 t.Skip("root required for mounting")
23 }
24
25 err := unix.Mount(src, dst, fstype, flags, options)
26 if err != nil {
27 return &os.PathError{Path: dst, Op: "mount", Err: err}
28 }
29 t.Cleanup(func() {
30 if err := unix.Unmount(dst, unix.MNT_DETACH); err != nil {
31 t.Errorf("cleanup: unmount %q failed: %v", dst, err)
32 }
33 })
34 return nil
35 }
36
37 type testMount struct {
38 desc string
39 isNotExist bool
40 isMount bool
41 isBind bool
42
43
44
45
46
47
48
49
50 prepare func(t *testing.T) (string, error)
51 }
52
53 var testMounts = []testMount{
54 {
55 desc: "non-existent path",
56 isNotExist: true,
57 prepare: func(t *testing.T) (string, error) {
58 return "/non/existent/path", nil
59 },
60 },
61 {
62 desc: "not mounted directory",
63 prepare: func(t *testing.T) (dir string, err error) {
64 dir = t.TempDir()
65 return dir, err
66 },
67 },
68 {
69 desc: "tmpfs mount",
70 isMount: true,
71 prepare: func(t *testing.T) (mnt string, err error) {
72 mnt = t.TempDir()
73 err = tMount(t, "tmpfs", mnt, "tmpfs", 0, "")
74 return mnt, err
75 },
76 },
77 {
78 desc: "tmpfs mount ending with a slash",
79 isMount: true,
80 prepare: func(t *testing.T) (mnt string, err error) {
81 mnt = t.TempDir() + "/"
82 err = tMount(t, "tmpfs", mnt, "tmpfs", 0, "")
83 return mnt, err
84 },
85 },
86 {
87 desc: "broken symlink",
88 isNotExist: true,
89 prepare: func(t *testing.T) (link string, err error) {
90 dir := t.TempDir()
91 link = filepath.Join(dir, "broken-symlink")
92 err = os.Symlink("/some/non/existent/dest", link)
93 return link, err
94 },
95 },
96 {
97 desc: "symlink to not mounted directory",
98 prepare: func(t *testing.T) (link string, err error) {
99 tmp := t.TempDir()
100
101 dir, err := os.MkdirTemp(tmp, "dir")
102 if err != nil {
103 return
104 }
105
106 link = filepath.Join(tmp, "symlink")
107 err = os.Symlink(dir, link)
108
109 return link, err
110 },
111 },
112 {
113 desc: "symlink to mounted directory",
114 isMount: true,
115 prepare: func(t *testing.T) (link string, err error) {
116 tmp := t.TempDir()
117
118 dir, err := os.MkdirTemp(tmp, "dir")
119 if err != nil {
120 return
121 }
122
123 err = tMount(t, "tmpfs", dir, "tmpfs", 0, "")
124 if err != nil {
125 return
126 }
127
128 link = filepath.Join(tmp, "symlink")
129 err = os.Symlink(dir, link)
130
131 return link, err
132 },
133 },
134 {
135 desc: "symlink to a file on a different filesystem",
136 isMount: false,
137 prepare: func(t *testing.T) (link string, err error) {
138 tmp := t.TempDir()
139
140 mnt, err := os.MkdirTemp(tmp, "dir")
141 if err != nil {
142 return
143 }
144
145 err = tMount(t, "tmpfs", mnt, "tmpfs", 0, "")
146 if err != nil {
147 return
148 }
149 file, err := os.CreateTemp(mnt, "file")
150 if err != nil {
151 return
152 }
153 file.Close()
154 link = filepath.Join(tmp, "link")
155 err = os.Symlink(file.Name(), link)
156
157 return link, err
158 },
159 },
160 {
161 desc: "path whose parent is a symlink to directory on another device",
162 isMount: false,
163 prepare: func(t *testing.T) (path string, err error) {
164 tmp := t.TempDir()
165
166 mnt, err := os.MkdirTemp(tmp, "dir")
167 if err != nil {
168 return
169 }
170
171 err = tMount(t, "tmpfs", mnt, "tmpfs", 0, "")
172 if err != nil {
173 return
174 }
175 file, err := os.CreateTemp(mnt, "file")
176 if err != nil {
177 return
178 }
179 file.Close()
180
181
182 link := filepath.Join(tmp, "link")
183 err = os.Symlink(filepath.Base(mnt), link)
184
185 path = filepath.Join(link, filepath.Base(file.Name()))
186
187 return path, err
188 },
189 },
190 {
191 desc: "directory bind mounted to itself",
192 isMount: true,
193 isBind: true,
194 prepare: func(t *testing.T) (mnt string, err error) {
195 mnt = t.TempDir()
196 err = tMount(t, mnt, mnt, "", unix.MS_BIND, "")
197 return mnt, err
198 },
199 },
200 {
201 desc: "directory bind-mounted to other directory",
202 isMount: true,
203 isBind: true,
204 prepare: func(t *testing.T) (mnt string, err error) {
205 dir := t.TempDir()
206 mnt = t.TempDir()
207 err = tMount(t, dir, mnt, "", unix.MS_BIND, "")
208 return mnt, err
209 },
210 },
211 {
212 desc: "not mounted file",
213 prepare: func(t *testing.T) (path string, err error) {
214 dir := t.TempDir()
215 file, err := os.CreateTemp(dir, "file")
216 if err != nil {
217 return
218 }
219 return file.Name(), err
220 },
221 },
222 {
223 desc: "regular file bind-mounted to itself",
224 isMount: true,
225 isBind: true,
226 prepare: func(t *testing.T) (path string, err error) {
227 dir := t.TempDir()
228
229 file, err := os.CreateTemp(dir, "file")
230 if err != nil {
231 return
232 }
233 file.Close()
234 path = file.Name()
235
236 err = tMount(t, path, path, "", unix.MS_BIND, "")
237
238 return path, err
239 },
240 },
241 {
242 desc: "not mounted socket",
243 prepare: func(t *testing.T) (path string, err error) {
244 dir := t.TempDir()
245 path = filepath.Join(dir, "sock")
246 _, err = net.Listen("unix", path)
247 return path, err
248 },
249 },
250 {
251 desc: "socket bind-mounted to itself",
252 isMount: true,
253 isBind: true,
254 prepare: func(t *testing.T) (path string, err error) {
255 dir := t.TempDir()
256 path = filepath.Join(dir, "sock")
257 _, err = net.Listen("unix", path)
258 if err != nil {
259 return
260 }
261 err = tMount(t, path, path, "", unix.MS_BIND, "")
262
263 return path, err
264 },
265 },
266 }
267
268 func requireOpenat2(t *testing.T) {
269 t.Helper()
270 if err := tryOpenat2(); err != nil {
271 t.Skipf("openat2: %v (old kernel? need Linux 5.6+)", err)
272 }
273 }
274
275 func tryOpenat2() error {
276 fd, err := unix.Openat2(unix.AT_FDCWD, ".", &unix.OpenHow{Flags: unix.O_RDONLY})
277 if err == nil {
278 _ = unix.Close(fd)
279 }
280 return err
281 }
282
283 func testMountedFast(t *testing.T, path string, tc *testMount, openat2Supported bool) {
284 mounted, sure, err := MountedFast(path)
285 if err != nil {
286
287 if !(tc.isNotExist && errors.Is(err, os.ErrNotExist)) {
288 t.Errorf("MountedFast: unexpected error: %v", err)
289 }
290
291
292 if sure {
293 t.Error("MountedFast: expected sure to be false on error")
294 }
295 if mounted {
296 t.Error("MountedFast: expected mounted to be false on error")
297 }
298
299
300 return
301 }
302
303 if openat2Supported {
304 if mounted != tc.isMount {
305 t.Errorf("MountedFast: expected mounted to be %v, got %v", tc.isMount, mounted)
306 }
307
308
309 return
310 }
311
312 if tc.isBind {
313
314
315 if sure {
316 t.Error("MountedFast: expected sure to be false for a bind mount")
317 }
318 if mounted {
319 t.Error("MountedFast: expected mounted to be false for a bind mount")
320 }
321 } else {
322 if mounted != tc.isMount {
323 t.Errorf("MountFast: expected mounted to be %v, got %v", tc.isMount, mounted)
324 }
325 if tc.isMount && !sure {
326 t.Error("MountFast: expected sure to be true for normal mount")
327 }
328 if !tc.isMount && sure {
329 t.Error("MountFast: expected sure to be false for non-mount")
330 }
331 }
332 }
333
334 func TestMountedBy(t *testing.T) {
335 checked := false
336 openat2Supported := false
337
338
339 toCheck := []func(string) (bool, error){mountedByMountinfo, mountedByStat}
340 if tryOpenat2() == nil {
341 openat2Supported = true
342 toCheck = append(toCheck, mountedByOpenat2)
343 }
344
345 for _, tc := range testMounts {
346 tc := tc
347 t.Run(tc.desc, func(t *testing.T) {
348 m, err := tc.prepare(t)
349 if err != nil {
350 t.Fatalf("prepare: %v", err)
351 }
352
353
354 mounted, err := Mounted(m)
355 if err == nil {
356 if mounted != tc.isMount {
357 t.Errorf("Mounted: expected %v, got %v", tc.isMount, mounted)
358 }
359 } else {
360
361 if !(tc.isNotExist && errors.Is(err, os.ErrNotExist)) {
362 t.Errorf("Mounted: unexpected error: %v", err)
363 }
364
365 if mounted {
366 t.Error("Mounted: expected false on error")
367 }
368 }
369
370
371 testMountedFast(t, m, &tc, openat2Supported)
372
373
374
375
376 m, err = normalizePath(m)
377 if err != nil {
378 if tc.isNotExist && errors.Is(err, os.ErrNotExist) {
379 return
380 }
381 t.Fatalf("normalizePath: %v", err)
382 }
383
384 for _, fn := range toCheck {
385
386 name := runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name()
387
388 mounted, err = fn(m)
389 if err != nil {
390 t.Errorf("%s: %v", name, err)
391
392 if mounted {
393 t.Errorf("%s: expected false on error", name)
394 }
395 } else if mounted != tc.isMount {
396 if tc.isBind && strings.HasSuffix(name, "mountedByStat") {
397
398 } else {
399 t.Errorf("%s: expected %v, got %v", name, tc.isMount, mounted)
400 }
401 }
402 checked = true
403 }
404 })
405 }
406
407 if !checked {
408 t.Skip("no mounts to check")
409 }
410 }
411
412 func TestMountedByOpenat2VsMountinfo(t *testing.T) {
413 requireOpenat2(t)
414
415 mounts, err := GetMounts(nil)
416 if err != nil {
417 t.Fatalf("GetMounts error: %v", err)
418 }
419
420 for _, mount := range mounts {
421 m := mount.Mountpoint
422 if m == "/" {
423
424
425 continue
426 }
427 mounted, err := mountedByOpenat2(m)
428 if err != nil {
429 if !errors.Is(err, os.ErrPermission) {
430 t.Errorf("mountedByOpenat2(%q) error: %+v", m, err)
431 }
432 } else if !mounted {
433 t.Errorf("mountedByOpenat2(%q): expected true, got false", m)
434 }
435 }
436 }
437
438
439
440 func TestMountedRoot(t *testing.T) {
441 for _, path := range []string{
442 "/",
443 "/../../",
444 "/tmp/..",
445 strings.Repeat("../", unix.PathMax/3),
446 } {
447 mounted, err := Mounted(path)
448 if err != nil || !mounted {
449 t.Errorf("Mounted(%q): expected true, <nil>; got %v, %v", path, mounted, err)
450 }
451
452 mounted, sure, err := MountedFast(path)
453 if err != nil || !mounted || !sure {
454 t.Errorf("MountedFast(%q): expected true, true, <nil>; got %v, %v, %v", path, mounted, sure, err)
455 }
456 }
457 }
458
View as plain text