storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/mountinfo/mountinfo_linux.go (about) 1 //go:build linux 2 // +build linux 3 4 /* 5 * MinIO Cloud Storage, (C) 2017, 2018 MinIO, Inc. 6 * 7 * Licensed under the Apache License, Version 2.0 (the "License"); 8 * you may not use this file except in compliance with the License. 9 * You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, software 14 * distributed under the License is distributed on an "AS IS" BASIS, 15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 * See the License for the specific language governing permissions and 17 * limitations under the License. 18 */ 19 20 package mountinfo 21 22 import ( 23 "bufio" 24 "fmt" 25 "io" 26 "os" 27 "path/filepath" 28 "strconv" 29 "strings" 30 "syscall" 31 ) 32 33 const ( 34 // Number of fields per line in /proc/mounts as per the fstab man page. 35 expectedNumFieldsPerLine = 6 36 // Location of the mount file to use 37 procMountsPath = "/proc/mounts" 38 ) 39 40 // IsLikelyMountPoint determines if a directory is a mountpoint. 41 func IsLikelyMountPoint(path string) bool { 42 s1, err := os.Lstat(path) 43 if err != nil { 44 return false 45 } 46 47 // A symlink can never be a mount point 48 if s1.Mode()&os.ModeSymlink != 0 { 49 return false 50 } 51 52 s2, err := os.Lstat(filepath.Dir(strings.TrimSuffix(path, "/"))) 53 if err != nil { 54 return false 55 } 56 57 // If the directory has a different device as parent, then it is a mountpoint. 58 if s1.Sys().(*syscall.Stat_t).Dev != s2.Sys().(*syscall.Stat_t).Dev { 59 // path/.. on a different device as path 60 return true 61 } 62 63 // path/.. is the same i-node as path - this check is for bind mounts. 64 return s1.Sys().(*syscall.Stat_t).Ino == s2.Sys().(*syscall.Stat_t).Ino 65 } 66 67 // CheckCrossDevice - check if any list of paths has any sub-mounts at /proc/mounts. 68 func CheckCrossDevice(absPaths []string) error { 69 return checkCrossDevice(absPaths, procMountsPath) 70 } 71 72 // Check cross device is an internal function. 73 func checkCrossDevice(absPaths []string, mountsPath string) error { 74 mounts, err := readProcMounts(mountsPath) 75 if err != nil { 76 return err 77 } 78 for _, path := range absPaths { 79 if err := mounts.checkCrossMounts(path); err != nil { 80 return err 81 } 82 } 83 return nil 84 } 85 86 // CheckCrossDevice - check if given path has any sub-mounts in the input mounts list. 87 func (mts mountInfos) checkCrossMounts(path string) error { 88 if !filepath.IsAbs(path) { 89 return fmt.Errorf("Invalid argument, path (%s) is expected to be absolute", path) 90 } 91 var crossMounts mountInfos 92 for _, mount := range mts { 93 // Add a separator to indicate that this is a proper mount-point. 94 // This is to avoid a situation where prefix is '/tmp/fsmount' 95 // and mount path is /tmp/fs. In such a scenario we need to check for 96 // `/tmp/fs/` to be a common prefix amount other mounts. 97 mpath := strings.TrimSuffix(mount.Path, "/") + "/" 98 ppath := strings.TrimSuffix(path, "/") + "/" 99 if strings.HasPrefix(mpath, ppath) { 100 // At this point if the mount point has a common prefix two conditions can happen. 101 // - mount.Path matches exact with `path` means we can proceed no error here. 102 // - mount.Path doesn't match (means cross-device mount), should error out. 103 if mount.Path != path { 104 crossMounts = append(crossMounts, mount) 105 } 106 } 107 } 108 msg := `Cross-device mounts detected on path (%s) at following locations %s. Export path should not have any sub-mounts, refusing to start.` 109 if len(crossMounts) > 0 { 110 // if paths didn't match then we do have cross-device mount. 111 return fmt.Errorf(msg, path, crossMounts) 112 } 113 return nil 114 } 115 116 // readProcMounts reads the given mountFilePath (normally /proc/mounts) and produces a hash 117 // of the contents. If the out argument is not nil, this fills it with MountPoint structs. 118 func readProcMounts(mountFilePath string) (mountInfos, error) { 119 file, err := os.Open(mountFilePath) 120 if err != nil { 121 return nil, err 122 } 123 defer file.Close() 124 return parseMountFrom(file) 125 } 126 127 func parseMountFrom(file io.Reader) (mountInfos, error) { 128 var mounts = mountInfos{} 129 scanner := bufio.NewReader(file) 130 for { 131 line, err := scanner.ReadString('\n') 132 if err == io.EOF { 133 break 134 } 135 136 fields := strings.Fields(line) 137 if len(fields) != expectedNumFieldsPerLine { 138 // ignore incorrect lines. 139 continue 140 } 141 142 // Freq should be an integer. 143 if _, err := strconv.Atoi(fields[4]); err != nil { 144 return nil, err 145 } 146 147 // Pass should be an integer. 148 if _, err := strconv.Atoi(fields[5]); err != nil { 149 return nil, err 150 } 151 152 mounts = append(mounts, mountInfo{ 153 Device: fields[0], 154 Path: fields[1], 155 FSType: fields[2], 156 Options: strings.Split(fields[3], ","), 157 Freq: fields[4], 158 Pass: fields[5], 159 }) 160 } 161 return mounts, nil 162 }