github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/mountinfo/mountinfo_linux_test.go (about)

     1  //go:build linux
     2  // +build linux
     3  
     4  // Copyright (c) 2015-2021 MinIO, Inc.
     5  //
     6  // This file is part of MinIO Object Storage stack
     7  //
     8  // This program is free software: you can redistribute it and/or modify
     9  // it under the terms of the GNU Affero General Public License as published by
    10  // the Free Software Foundation, either version 3 of the License, or
    11  // (at your option) any later version.
    12  //
    13  // This program is distributed in the hope that it will be useful
    14  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    15  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    16  // GNU Affero General Public License for more details.
    17  //
    18  // You should have received a copy of the GNU Affero General Public License
    19  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    20  
    21  package mountinfo
    22  
    23  import (
    24  	"fmt"
    25  	"os"
    26  	"path/filepath"
    27  	"strings"
    28  	"testing"
    29  )
    30  
    31  // Tests cross device mount verification function, for both failure
    32  // and success cases.
    33  func TestCrossDeviceMountPaths(t *testing.T) {
    34  	successCase := `/dev/0 /path/to/0/1 type0 flags 0 0
    35  		/dev/1    /path/to/1   type1	flags 1 1
    36  		/dev/2 /path/to/1/2 type2 flags,1,2=3 2 2
    37                  /dev/3 /path/to/1.1 type3 flags,1,2=3 3 3
    38  		`
    39  	var err error
    40  	dir := t.TempDir()
    41  	mountsPath := filepath.Join(dir, "mounts")
    42  	if err = os.WriteFile(mountsPath, []byte(successCase), 0o666); err != nil {
    43  		t.Fatal(err)
    44  	}
    45  	// Failure case where we detected successfully cross device mounts.
    46  	{
    47  		absPaths := []string{"/path/to/1"}
    48  		if err = checkCrossDevice(absPaths, mountsPath); err == nil {
    49  			t.Fatal("Expected to fail, but found success")
    50  		}
    51  
    52  		mp := []mountInfo{
    53  			{"/dev/2", "/path/to/1/2", "type2", []string{"flags"}, "2", "2"},
    54  		}
    55  		msg := fmt.Sprintf("Cross-device mounts detected on path (/path/to/1) at following locations %s. Export path should not have any sub-mounts, refusing to start.", mp)
    56  		if err.Error() != msg {
    57  			t.Fatalf("Expected msg %s, got %s", msg, err)
    58  		}
    59  	}
    60  	// Failure case when input path is not absolute.
    61  	{
    62  		absPaths := []string{"."}
    63  		if err = checkCrossDevice(absPaths, mountsPath); err == nil {
    64  			t.Fatal("Expected to fail for non absolute paths")
    65  		}
    66  		expectedErrMsg := fmt.Sprintf("Invalid argument, path (%s) is expected to be absolute", ".")
    67  		if err.Error() != expectedErrMsg {
    68  			t.Fatalf("Expected %s, got %s", expectedErrMsg, err)
    69  		}
    70  	}
    71  	// Success case, where path doesn't have any mounts.
    72  	{
    73  		absPaths := []string{"/path/to/x"}
    74  		if err = checkCrossDevice(absPaths, mountsPath); err != nil {
    75  			t.Fatalf("Expected success, failed instead (%s)", err)
    76  		}
    77  	}
    78  }
    79  
    80  // Tests cross device mount verification function, for both failure
    81  // and success cases.
    82  func TestCrossDeviceMount(t *testing.T) {
    83  	successCase := `/dev/0 /path/to/0/1 type0 flags 0 0
    84  		/dev/1    /path/to/1   type1	flags 1 1
    85  		/dev/2 /path/to/1/2 type2 flags,1,2=3 2 2
    86                  /dev/3 /path/to/1.1 type3 flags,1,2=3 3 3
    87  		`
    88  	var err error
    89  	dir := t.TempDir()
    90  	mountsPath := filepath.Join(dir, "mounts")
    91  	if err = os.WriteFile(mountsPath, []byte(successCase), 0o666); err != nil {
    92  		t.Fatal(err)
    93  	}
    94  	mounts, err := readProcMounts(mountsPath)
    95  	if err != nil {
    96  		t.Fatal(err)
    97  	}
    98  	// Failure case where we detected successfully cross device mounts.
    99  	{
   100  		if err = mounts.checkCrossMounts("/path/to/1"); err == nil {
   101  			t.Fatal("Expected to fail, but found success")
   102  		}
   103  
   104  		mp := []mountInfo{
   105  			{"/dev/2", "/path/to/1/2", "type2", []string{"flags"}, "2", "2"},
   106  		}
   107  		msg := fmt.Sprintf("Cross-device mounts detected on path (/path/to/1) at following locations %s. Export path should not have any sub-mounts, refusing to start.", mp)
   108  		if err.Error() != msg {
   109  			t.Fatalf("Expected msg %s, got %s", msg, err)
   110  		}
   111  	}
   112  	// Failure case when input path is not absolute.
   113  	{
   114  		if err = mounts.checkCrossMounts("."); err == nil {
   115  			t.Fatal("Expected to fail for non absolute paths")
   116  		}
   117  		expectedErrMsg := fmt.Sprintf("Invalid argument, path (%s) is expected to be absolute", ".")
   118  		if err.Error() != expectedErrMsg {
   119  			t.Fatalf("Expected %s, got %s", expectedErrMsg, err)
   120  		}
   121  	}
   122  	// Success case, where path doesn't have any mounts.
   123  	{
   124  		if err = mounts.checkCrossMounts("/path/to/x"); err != nil {
   125  			t.Fatalf("Expected success, failed instead (%s)", err)
   126  		}
   127  	}
   128  }
   129  
   130  // Tests read proc mounts file.
   131  func TestReadProcmountInfos(t *testing.T) {
   132  	successCase := `/dev/0 /path/to/0 type0 flags 0 0
   133  		/dev/1    /path/to/1   type1	flags 1 1
   134  		/dev/2 /path/to/2 type2 flags,1,2=3 2 2
   135  		`
   136  	var err error
   137  	dir := t.TempDir()
   138  
   139  	mountsPath := filepath.Join(dir, "mounts")
   140  	if err = os.WriteFile(mountsPath, []byte(successCase), 0o666); err != nil {
   141  		t.Fatal(err)
   142  	}
   143  	// Verifies if reading each line worked properly.
   144  	{
   145  		var mounts mountInfos
   146  		mounts, err = readProcMounts(mountsPath)
   147  		if err != nil {
   148  			t.Fatal(err)
   149  		}
   150  		if len(mounts) != 3 {
   151  			t.Fatalf("expected 3 mounts, got %d", len(mounts))
   152  		}
   153  		mp := mountInfo{"/dev/0", "/path/to/0", "type0", []string{"flags"}, "0", "0"}
   154  		if !mountPointsEqual(mounts[0], mp) {
   155  			t.Errorf("got unexpected MountPoint[0]: %#v", mounts[0])
   156  		}
   157  		mp = mountInfo{"/dev/1", "/path/to/1", "type1", []string{"flags"}, "1", "1"}
   158  		if !mountPointsEqual(mounts[1], mp) {
   159  			t.Errorf("got unexpected mountInfo[1]: %#v", mounts[1])
   160  		}
   161  		mp = mountInfo{"/dev/2", "/path/to/2", "type2", []string{"flags", "1", "2=3"}, "2", "2"}
   162  		if !mountPointsEqual(mounts[2], mp) {
   163  			t.Errorf("got unexpected mountInfo[2]: %#v", mounts[2])
   164  		}
   165  	}
   166  	// Failure case mounts path doesn't exist, if not fail.
   167  	{
   168  		if _, err = readProcMounts(filepath.Join(dir, "non-existent")); err != nil && !os.IsNotExist(err) {
   169  			t.Fatal(err)
   170  		}
   171  	}
   172  }
   173  
   174  // Tests read proc mounts reader.
   175  func TestReadProcMountFrom(t *testing.T) {
   176  	successCase := `/dev/0 /path/to/0 type0 flags 0 0
   177  		/dev/1    /path/to/1   type1	flags 1 1
   178  		/dev/2 /path/to/2 type2 flags,1,2=3 2 2
   179  		`
   180  	// Success case, verifies if parsing works properly.
   181  	{
   182  		mounts, err := parseMountFrom(strings.NewReader(successCase))
   183  		if err != nil {
   184  			t.Errorf("expected success")
   185  		}
   186  		if len(mounts) != 3 {
   187  			t.Fatalf("expected 3 mounts, got %d", len(mounts))
   188  		}
   189  		mp := mountInfo{"/dev/0", "/path/to/0", "type0", []string{"flags"}, "0", "0"}
   190  		if !mountPointsEqual(mounts[0], mp) {
   191  			t.Errorf("got unexpected mountInfo[0]: %#v", mounts[0])
   192  		}
   193  		mp = mountInfo{"/dev/1", "/path/to/1", "type1", []string{"flags"}, "1", "1"}
   194  		if !mountPointsEqual(mounts[1], mp) {
   195  			t.Errorf("got unexpected mountInfo[1]: %#v", mounts[1])
   196  		}
   197  		mp = mountInfo{"/dev/2", "/path/to/2", "type2", []string{"flags", "1", "2=3"}, "2", "2"}
   198  		if !mountPointsEqual(mounts[2], mp) {
   199  			t.Errorf("got unexpected mountInfo[2]: %#v", mounts[2])
   200  		}
   201  	}
   202  	// Error cases where parsing fails with invalid Freq and Pass params.
   203  	{
   204  		errorCases := []string{
   205  			"/dev/1 /path/to/mount type flags a 0\n",
   206  			"/dev/2 /path/to/mount type flags 0 b\n",
   207  		}
   208  		for _, ec := range errorCases {
   209  			_, rerr := parseMountFrom(strings.NewReader(ec))
   210  			if rerr == nil {
   211  				t.Errorf("expected error")
   212  			}
   213  		}
   214  	}
   215  }
   216  
   217  // Helpers for tests.
   218  
   219  // Check if two `mountInfo` are equal.
   220  func mountPointsEqual(a, b mountInfo) bool {
   221  	if a.Device != b.Device || a.Path != b.Path || a.FSType != b.FSType || !slicesEqual(a.Options, b.Options) || a.Pass != b.Pass || a.Freq != b.Freq {
   222  		return false
   223  	}
   224  	return true
   225  }
   226  
   227  // Checks if two string slices are equal.
   228  func slicesEqual(a, b []string) bool {
   229  	if len(a) != len(b) {
   230  		return false
   231  	}
   232  	for i := range a {
   233  		if a[i] != b[i] {
   234  			return false
   235  		}
   236  	}
   237  	return true
   238  }