1// +build !windows 2 3/* 4Copyright 2019 The Kubernetes Authors. 5 6Licensed under the Apache License, Version 2.0 (the "License"); 7you may not use this file except in compliance with the License. 8You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12Unless required by applicable law or agreed to in writing, software 13distributed under the License is distributed on an "AS IS" BASIS, 14WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15See the License for the specific language governing permissions and 16limitations under the License. 17*/ 18 19package mount 20 21import ( 22 "fmt" 23 "os" 24 "strconv" 25 "strings" 26 "syscall" 27 28 utilio "k8s.io/utils/io" 29) 30 31const ( 32 // At least number of fields per line in /proc/<pid>/mountinfo. 33 expectedAtLeastNumFieldsPerMountInfo = 10 34 // How many times to retry for a consistent read of /proc/mounts. 35 maxListTries = 3 36) 37 38// IsCorruptedMnt return true if err is about corrupted mount point 39func IsCorruptedMnt(err error) bool { 40 if err == nil { 41 return false 42 } 43 var underlyingError error 44 switch pe := err.(type) { 45 case nil: 46 return false 47 case *os.PathError: 48 underlyingError = pe.Err 49 case *os.LinkError: 50 underlyingError = pe.Err 51 case *os.SyscallError: 52 underlyingError = pe.Err 53 } 54 55 return underlyingError == syscall.ENOTCONN || underlyingError == syscall.ESTALE || underlyingError == syscall.EIO || underlyingError == syscall.EACCES 56} 57 58// MountInfo represents a single line in /proc/<pid>/mountinfo. 59type MountInfo struct { // nolint: golint 60 // Unique ID for the mount (maybe reused after umount). 61 ID int 62 // The ID of the parent mount (or of self for the root of this mount namespace's mount tree). 63 ParentID int 64 // Major indicates one half of the device ID which identifies the device class 65 // (parsed from `st_dev` for files on this filesystem). 66 Major int 67 // Minor indicates one half of the device ID which identifies a specific 68 // instance of device (parsed from `st_dev` for files on this filesystem). 69 Minor int 70 // The pathname of the directory in the filesystem which forms the root of this mount. 71 Root string 72 // Mount source, filesystem-specific information. e.g. device, tmpfs name. 73 Source string 74 // Mount point, the pathname of the mount point. 75 MountPoint string 76 // Optional fieds, zero or more fields of the form "tag[:value]". 77 OptionalFields []string 78 // The filesystem type in the form "type[.subtype]". 79 FsType string 80 // Per-mount options. 81 MountOptions []string 82 // Per-superblock options. 83 SuperOptions []string 84} 85 86// ParseMountInfo parses /proc/xxx/mountinfo. 87func ParseMountInfo(filename string) ([]MountInfo, error) { 88 content, err := utilio.ConsistentRead(filename, maxListTries) 89 if err != nil { 90 return []MountInfo{}, err 91 } 92 contentStr := string(content) 93 infos := []MountInfo{} 94 95 for _, line := range strings.Split(contentStr, "\n") { 96 if line == "" { 97 // the last split() item is empty string following the last \n 98 continue 99 } 100 // See `man proc` for authoritative description of format of the file. 101 fields := strings.Fields(line) 102 if len(fields) < expectedAtLeastNumFieldsPerMountInfo { 103 return nil, fmt.Errorf("wrong number of fields in (expected at least %d, got %d): %s", expectedAtLeastNumFieldsPerMountInfo, len(fields), line) 104 } 105 id, err := strconv.Atoi(fields[0]) 106 if err != nil { 107 return nil, err 108 } 109 parentID, err := strconv.Atoi(fields[1]) 110 if err != nil { 111 return nil, err 112 } 113 mm := strings.Split(fields[2], ":") 114 if len(mm) != 2 { 115 return nil, fmt.Errorf("parsing '%s' failed: unexpected minor:major pair %s", line, mm) 116 } 117 major, err := strconv.Atoi(mm[0]) 118 if err != nil { 119 return nil, fmt.Errorf("parsing '%s' failed: unable to parse major device id, err:%v", mm[0], err) 120 } 121 minor, err := strconv.Atoi(mm[1]) 122 if err != nil { 123 return nil, fmt.Errorf("parsing '%s' failed: unable to parse minor device id, err:%v", mm[1], err) 124 } 125 126 info := MountInfo{ 127 ID: id, 128 ParentID: parentID, 129 Major: major, 130 Minor: minor, 131 Root: fields[3], 132 MountPoint: fields[4], 133 MountOptions: strings.Split(fields[5], ","), 134 } 135 // All fields until "-" are "optional fields". 136 i := 6 137 for ; i < len(fields) && fields[i] != "-"; i++ { 138 info.OptionalFields = append(info.OptionalFields, fields[i]) 139 } 140 // Parse the rest 3 fields. 141 i++ 142 if len(fields)-i < 3 { 143 return nil, fmt.Errorf("expect 3 fields in %s, got %d", line, len(fields)-i) 144 } 145 info.FsType = fields[i] 146 info.Source = fields[i+1] 147 info.SuperOptions = strings.Split(fields[i+2], ",") 148 infos = append(infos, info) 149 } 150 return infos, nil 151} 152 153// isMountPointMatch returns true if the path in mp is the same as dir. 154// Handles case where mountpoint dir has been renamed due to stale NFS mount. 155func isMountPointMatch(mp MountPoint, dir string) bool { 156 deletedDir := fmt.Sprintf("%s\\040(deleted)", dir) 157 return ((mp.Path == dir) || (mp.Path == deletedDir)) 158} 159