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 }