github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/osutil/mountinfo_linux.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2017 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package osutil 21 22 import ( 23 "bufio" 24 "bytes" 25 "fmt" 26 "io" 27 "os" 28 "sort" 29 "strconv" 30 "strings" 31 ) 32 33 // MountInfoEntry contains data from /proc/$PID/mountinfo 34 // 35 // For details please refer to mountinfo documentation at 36 // https://www.kernel.org/doc/Documentation/filesystems/proc.txt 37 type MountInfoEntry struct { 38 MountID int 39 ParentID int 40 DevMajor int 41 DevMinor int 42 Root string 43 MountDir string 44 MountOptions map[string]string 45 OptionalFields []string 46 FsType string 47 MountSource string 48 SuperOptions map[string]string 49 } 50 51 func flattenMap(m map[string]string) string { 52 keys := make([]string, 0, len(m)) 53 for key := range m { 54 keys = append(keys, key) 55 } 56 sort.Strings(keys) 57 var buf bytes.Buffer 58 for i, key := range keys { 59 if i > 0 { 60 buf.WriteRune(',') 61 } 62 if m[key] != "" { 63 fmt.Fprintf(&buf, "%s=%s", escape(key), escape(m[key])) 64 } else { 65 buf.WriteString(escape(key)) 66 } 67 } 68 return buf.String() 69 } 70 71 func flattenList(l []string) string { 72 var buf bytes.Buffer 73 for i, item := range l { 74 if i > 0 { 75 buf.WriteRune(',') 76 } 77 buf.WriteString(escape(item)) 78 } 79 return buf.String() 80 } 81 82 func (mi *MountInfoEntry) String() string { 83 maybeSpace := " " 84 if len(mi.OptionalFields) == 0 { 85 maybeSpace = "" 86 } 87 return fmt.Sprintf("%d %d %d:%d %s %s %s %s%s- %s %s %s", 88 mi.MountID, mi.ParentID, mi.DevMajor, mi.DevMinor, escape(mi.Root), 89 escape(mi.MountDir), flattenMap(mi.MountOptions), flattenList(mi.OptionalFields), 90 maybeSpace, escape(mi.FsType), escape(mi.MountSource), 91 flattenMap(mi.SuperOptions)) 92 } 93 94 var mountInfoMustMockInTests = true 95 96 // LoadMountInfo loads list of mounted entries from /proc/self/mountinfo. This 97 // can be mocked by using osutil.MockMountInfo to hard-code a specific mountinfo 98 // file content to be loaded by this function 99 func LoadMountInfo() ([]*MountInfoEntry, error) { 100 if mockedMountInfo != nil { 101 return ReadMountInfo(bytes.NewBufferString(*mockedMountInfo)) 102 } 103 if IsTestBinary() && mountInfoMustMockInTests { 104 // if we are in testing and we didn't mock a mountinfo panic, since the 105 // mountinfo is used in many places and really should be mocked for tests 106 panic("/proc/self/mountinfo must be mocked in tests") 107 } 108 109 f, err := os.Open(procSelfMountInfo) 110 if err != nil { 111 return nil, err 112 } 113 defer f.Close() 114 return ReadMountInfo(f) 115 } 116 117 // ReadMountInfo reads and parses a mountinfo file. 118 func ReadMountInfo(reader io.Reader) ([]*MountInfoEntry, error) { 119 scanner := bufio.NewScanner(reader) 120 var entries []*MountInfoEntry 121 for scanner.Scan() { 122 s := scanner.Text() 123 entry, err := ParseMountInfoEntry(s) 124 if err != nil { 125 return nil, err 126 } 127 entries = append(entries, entry) 128 } 129 if err := scanner.Err(); err != nil { 130 return nil, err 131 } 132 return entries, nil 133 } 134 135 // ParseMountInfoEntry parses a single line of /proc/$PID/mountinfo file. 136 func ParseMountInfoEntry(s string) (*MountInfoEntry, error) { 137 var e MountInfoEntry 138 var err error 139 fields := strings.FieldsFunc(s, func(r rune) bool { return r == ' ' }) 140 141 // The format is variable-length, but at least 10 fields are mandatory. 142 // The (7) below is a list of optional field which is terminated with (8). 143 // 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue 144 // (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11) 145 if len(fields) < 10 { 146 return nil, fmt.Errorf("incorrect number of fields, expected at least 10 but found %d", len(fields)) 147 } 148 // Parse MountID (decimal number). 149 e.MountID, err = strconv.Atoi(fields[0]) 150 if err != nil { 151 return nil, fmt.Errorf("cannot parse mount ID: %q", fields[0]) 152 } 153 // Parse ParentID (decimal number). 154 e.ParentID, err = strconv.Atoi(fields[1]) 155 if err != nil { 156 return nil, fmt.Errorf("cannot parse parent mount ID: %q", fields[1]) 157 } 158 // Parses DevMajor:DevMinor pair (decimal numbers separated by colon). 159 subFields := strings.FieldsFunc(fields[2], func(r rune) bool { return r == ':' }) 160 if len(subFields) != 2 { 161 return nil, fmt.Errorf("cannot parse device major:minor number pair: %q", fields[2]) 162 } 163 e.DevMajor, err = strconv.Atoi(subFields[0]) 164 if err != nil { 165 return nil, fmt.Errorf("cannot parse device major number: %q", subFields[0]) 166 } 167 e.DevMinor, err = strconv.Atoi(subFields[1]) 168 if err != nil { 169 return nil, fmt.Errorf("cannot parse device minor number: %q", subFields[1]) 170 } 171 // NOTE: All string fields use the same escape/unescape logic as fstab files. 172 // Parse Root, MountDir and MountOptions fields. 173 e.Root = unescape(fields[3]) 174 e.MountDir = unescape(fields[4]) 175 e.MountOptions = parseMountOpts(unescape(fields[5])) 176 // Optional fields are terminated with a "-" value and start 177 // after the mount options field. Skip ahead until we see the "-" 178 // marker. 179 var i int 180 for i = 6; i < len(fields) && fields[i] != "-"; i++ { 181 } 182 if i == len(fields) { 183 return nil, fmt.Errorf("list of optional fields is not terminated properly") 184 } 185 e.OptionalFields = fields[6:i] 186 for j := range e.OptionalFields { 187 e.OptionalFields[j] = unescape(e.OptionalFields[j]) 188 } 189 // Parse the last three fixed fields. 190 tailFields := fields[i+1:] 191 if len(tailFields) != 3 { 192 return nil, fmt.Errorf("incorrect number of tail fields, expected 3 but found %d", len(tailFields)) 193 } 194 e.FsType = unescape(tailFields[0]) 195 e.MountSource = unescape(tailFields[1]) 196 e.SuperOptions = parseMountOpts(unescape(tailFields[2])) 197 return &e, nil 198 } 199 200 func parseMountOpts(opts string) map[string]string { 201 result := make(map[string]string) 202 for _, opt := range strings.Split(opts, ",") { 203 keyValue := strings.SplitN(opt, "=", 2) 204 key := keyValue[0] 205 if len(keyValue) == 2 { 206 value := keyValue[1] 207 result[key] = value 208 } else { 209 result[key] = "" 210 } 211 } 212 return result 213 }