...

Source file src/github.com/moby/sys/mountinfo/mounted_linux_test.go

Documentation: github.com/moby/sys/mountinfo

     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  // tMount is a wrapper for unix.Mount which is used to prepare test cases.
    17  // It skips the test case if mounting is not possible (i.e. user is not root),
    18  // adds more context to mount error, if any, and installs a cleanup handler to
    19  // undo the mount.
    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  	// prepare returns a path that needs to be checked, and the error, if any.
    43  	//
    44  	// It is responsible for cleanup (by using t.Cleanup).
    45  	//
    46  	// It should not fail the test (i.e. no calls to t.Error/t.Fatal).
    47  	// The only exception to this rule is some cases use t.TempDir() for
    48  	// simplicity (no need to check for errors or call t.Cleanup()), and
    49  	// it may call t.Fatal, but practically we don't expect it.
    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  			// Create link -> mnt under tmp dir.
   182  			link := filepath.Join(tmp, "link")
   183  			err = os.Symlink(filepath.Base(mnt), link)
   184  			// Path to check is /<tmp>/link/file.
   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  		// Got an error; is it expected?
   287  		if !(tc.isNotExist && errors.Is(err, os.ErrNotExist)) {
   288  			t.Errorf("MountedFast: unexpected error: %v", err)
   289  		}
   290  
   291  		// In case of an error, sure and mounted must be false.
   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  		// No more checks.
   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  		// No more checks.
   309  		return
   310  	}
   311  
   312  	if tc.isBind {
   313  		// For bind mounts, in case openat2 is not supported,
   314  		// sure and mounted must be false.
   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  	// List of individual implementations to check.
   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  			// Check the public Mounted() function as a whole.
   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  				// Got an error; is it expected?
   361  				if !(tc.isNotExist && errors.Is(err, os.ErrNotExist)) {
   362  					t.Errorf("Mounted: unexpected error: %v", err)
   363  				}
   364  				// Check false is returned in error case.
   365  				if mounted {
   366  					t.Error("Mounted: expected false on error")
   367  				}
   368  			}
   369  
   370  			// Check the public MountedFast() function as a whole.
   371  			testMountedFast(t, m, &tc, openat2Supported)
   372  
   373  			// Check individual mountedBy* implementations.
   374  
   375  			// All mountedBy* functions should be called with normalized paths.
   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  				// Figure out function name.
   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  					// Check false is returned in error case.
   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  						// mountedByStat can not detect bind mounts.
   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  			// mountedBy*() won't work for /, so skip it
   424  			// (this special case is handled by Mounted).
   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  // TestMountedRoot checks that Mounted* functions always return true for root
   439  // directory (since / is always mounted).
   440  func TestMountedRoot(t *testing.T) {
   441  	for _, path := range []string{
   442  		"/",
   443  		"/../../",
   444  		"/tmp/..",
   445  		strings.Repeat("../", unix.PathMax/3), // Hope $CWD is not too deep down.
   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