github.com/rigado/snapd@v2.42.5-go-mod+incompatible/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 // LoadMountInfo loads list of mounted entries from a given file. 95 // 96 // The file is typically ProcSelfMountInfo but any other process mount table 97 // can be read the same way. 98 func LoadMountInfo(fname string) ([]*MountInfoEntry, error) { 99 f, err := os.Open(fname) 100 if err != nil { 101 return nil, err 102 } 103 defer f.Close() 104 return ReadMountInfo(f) 105 } 106 107 // ReadMountInfo reads and parses a mountinfo file. 108 func ReadMountInfo(reader io.Reader) ([]*MountInfoEntry, error) { 109 scanner := bufio.NewScanner(reader) 110 var entries []*MountInfoEntry 111 for scanner.Scan() { 112 s := scanner.Text() 113 entry, err := ParseMountInfoEntry(s) 114 if err != nil { 115 return nil, err 116 } 117 entries = append(entries, entry) 118 } 119 if err := scanner.Err(); err != nil { 120 return nil, err 121 } 122 return entries, nil 123 } 124 125 // ParseMountInfoEntry parses a single line of /proc/$PID/mountinfo file. 126 func ParseMountInfoEntry(s string) (*MountInfoEntry, error) { 127 var e MountInfoEntry 128 var err error 129 fields := strings.FieldsFunc(s, func(r rune) bool { return r == ' ' }) 130 131 // The format is variable-length, but at least 10 fields are mandatory. 132 // The (7) below is a list of optional field which is terminated with (8). 133 // 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue 134 // (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11) 135 if len(fields) < 10 { 136 return nil, fmt.Errorf("incorrect number of fields, expected at least 10 but found %d", len(fields)) 137 } 138 // Parse MountID (decimal number). 139 e.MountID, err = strconv.Atoi(fields[0]) 140 if err != nil { 141 return nil, fmt.Errorf("cannot parse mount ID: %q", fields[0]) 142 } 143 // Parse ParentID (decimal number). 144 e.ParentID, err = strconv.Atoi(fields[1]) 145 if err != nil { 146 return nil, fmt.Errorf("cannot parse parent mount ID: %q", fields[1]) 147 } 148 // Parses DevMajor:DevMinor pair (decimal numbers separated by colon). 149 subFields := strings.FieldsFunc(fields[2], func(r rune) bool { return r == ':' }) 150 if len(subFields) != 2 { 151 return nil, fmt.Errorf("cannot parse device major:minor number pair: %q", fields[2]) 152 } 153 e.DevMajor, err = strconv.Atoi(subFields[0]) 154 if err != nil { 155 return nil, fmt.Errorf("cannot parse device major number: %q", subFields[0]) 156 } 157 e.DevMinor, err = strconv.Atoi(subFields[1]) 158 if err != nil { 159 return nil, fmt.Errorf("cannot parse device minor number: %q", subFields[1]) 160 } 161 // NOTE: All string fields use the same escape/unescape logic as fstab files. 162 // Parse Root, MountDir and MountOptions fields. 163 e.Root = unescape(fields[3]) 164 e.MountDir = unescape(fields[4]) 165 e.MountOptions = parseMountOpts(unescape(fields[5])) 166 // Optional fields are terminated with a "-" value and start 167 // after the mount options field. Skip ahead until we see the "-" 168 // marker. 169 var i int 170 for i = 6; i < len(fields) && fields[i] != "-"; i++ { 171 } 172 if i == len(fields) { 173 return nil, fmt.Errorf("list of optional fields is not terminated properly") 174 } 175 e.OptionalFields = fields[6:i] 176 for j := range e.OptionalFields { 177 e.OptionalFields[j] = unescape(e.OptionalFields[j]) 178 } 179 // Parse the last three fixed fields. 180 tailFields := fields[i+1:] 181 if len(tailFields) != 3 { 182 return nil, fmt.Errorf("incorrect number of tail fields, expected 3 but found %d", len(tailFields)) 183 } 184 e.FsType = unescape(tailFields[0]) 185 e.MountSource = unescape(tailFields[1]) 186 e.SuperOptions = parseMountOpts(unescape(tailFields[2])) 187 return &e, nil 188 } 189 190 func parseMountOpts(opts string) map[string]string { 191 result := make(map[string]string) 192 for _, opt := range strings.Split(opts, ",") { 193 keyValue := strings.SplitN(opt, "=", 2) 194 key := keyValue[0] 195 if len(keyValue) == 2 { 196 value := keyValue[1] 197 result[key] = value 198 } else { 199 result[key] = "" 200 } 201 } 202 return result 203 }