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