github.com/rigado/snapd@v2.42.5-go-mod+incompatible/osutil/mountentry_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 "fmt" 24 "math" 25 "os" 26 "regexp" 27 "strconv" 28 "strings" 29 "syscall" 30 ) 31 32 // MountEntry describes an /etc/fstab-like mount entry. 33 // 34 // Fields are named after names in struct returned by getmntent(3). 35 // 36 // struct mntent { 37 // char *mnt_fsname; /* name of mounted filesystem */ 38 // char *mnt_dir; /* filesystem path prefix */ 39 // char *mnt_type; /* mount type (see Mntent.h) */ 40 // char *mnt_opts; /* mount options (see Mntent.h) */ 41 // int mnt_freq; /* dump frequency in days */ 42 // int mnt_passno; /* pass number on parallel fsck */ 43 // }; 44 type MountEntry struct { 45 Name string 46 Dir string 47 Type string 48 Options []string 49 50 DumpFrequency int 51 CheckPassNumber int 52 } 53 54 func equalStrings(a, b []string) bool { 55 if len(a) != len(b) { 56 return false 57 } 58 for i := 0; i < len(a); i++ { 59 if a[i] != b[i] { 60 return false 61 } 62 } 63 return true 64 } 65 66 // Equal checks if one entry is equal to another 67 func (e *MountEntry) Equal(o *MountEntry) bool { 68 return (e.Name == o.Name && e.Dir == o.Dir && e.Type == o.Type && 69 equalStrings(e.Options, o.Options) && e.DumpFrequency == o.DumpFrequency && 70 e.CheckPassNumber == o.CheckPassNumber) 71 } 72 73 // escape replaces whitespace characters so that getmntent can parse it correctly. 74 var escape = strings.NewReplacer( 75 " ", `\040`, 76 "\t", `\011`, 77 "\n", `\012`, 78 "\\", `\134`, 79 ).Replace 80 81 // unescape replaces escape sequences used by setmnt with whitespace characters. 82 var unescape = strings.NewReplacer( 83 `\040`, " ", 84 `\011`, "\t", 85 `\012`, "\n", 86 `\134`, "\\", 87 ).Replace 88 89 // Escape returns the given path with space, tab, newline and forward slash escaped. 90 func Escape(path string) string { 91 return escape(path) 92 } 93 94 // Unescape returns the given path with space, tab, newline and forward slash unescaped. 95 func Unescape(path string) string { 96 return unescape(path) 97 } 98 99 func (e MountEntry) String() string { 100 // Name represents name of the device in a mount entry. 101 name := "none" 102 if e.Name != "" { 103 name = escape(e.Name) 104 } 105 // Dir represents mount directory in a mount entry. 106 dir := "none" 107 if e.Dir != "" { 108 dir = escape(e.Dir) 109 } 110 // Type represents file system type in a mount entry. 111 fsType := "none" 112 if e.Type != "" { 113 fsType = escape(e.Type) 114 } 115 // Options represents mount options in a mount entry. 116 options := "defaults" 117 if len(e.Options) != 0 { 118 options = escape(strings.Join(e.Options, ",")) 119 } 120 return fmt.Sprintf("%s %s %s %s %d %d", 121 name, dir, fsType, options, e.DumpFrequency, e.CheckPassNumber) 122 } 123 124 // ParseMountEntry parses a fstab-like entry. 125 func ParseMountEntry(s string) (MountEntry, error) { 126 var e MountEntry 127 var err error 128 var df, cpn int 129 fields := strings.FieldsFunc(s, func(r rune) bool { return r == ' ' || r == '\t' }) 130 // Look for any inline comments. The first field that starts with '#' is a comment. 131 for i, field := range fields { 132 if strings.HasPrefix(field, "#") { 133 fields = fields[:i] 134 break 135 } 136 } 137 // Do all error checks before any assignments to `e' 138 if len(fields) < 3 || len(fields) > 6 { 139 return e, fmt.Errorf("expected between 3 and 6 fields, found %d", len(fields)) 140 } 141 e.Name = unescape(fields[0]) 142 e.Dir = unescape(fields[1]) 143 e.Type = unescape(fields[2]) 144 // Parse Options if we have at least 4 fields 145 if len(fields) > 3 { 146 e.Options = strings.Split(unescape(fields[3]), ",") 147 } 148 // Parse DumpFrequency if we have at least 5 fields 149 if len(fields) > 4 { 150 df, err = strconv.Atoi(fields[4]) 151 if err != nil { 152 return e, fmt.Errorf("cannot parse dump frequency: %q", fields[4]) 153 } 154 } 155 e.DumpFrequency = df 156 // Parse CheckPassNumber if we have at least 6 fields 157 if len(fields) > 5 { 158 cpn, err = strconv.Atoi(fields[5]) 159 if err != nil { 160 return e, fmt.Errorf("cannot parse check pass number: %q", fields[5]) 161 } 162 } 163 e.CheckPassNumber = cpn 164 return e, nil 165 } 166 167 // MountOptsToCommonFlags converts mount options strings to a mount flag, 168 // returning unparsed flags. The unparsed flags will not contain any snapd- 169 // specific mount option, those starting with the string "x-snapd." 170 func MountOptsToCommonFlags(opts []string) (flags int, unparsed []string) { 171 for _, opt := range opts { 172 switch opt { 173 case "ro": 174 flags |= syscall.MS_RDONLY 175 case "nosuid": 176 flags |= syscall.MS_NOSUID 177 case "nodev": 178 flags |= syscall.MS_NODEV 179 case "noexec": 180 flags |= syscall.MS_NOEXEC 181 case "sync": 182 flags |= syscall.MS_SYNCHRONOUS 183 case "remount": 184 flags |= syscall.MS_REMOUNT 185 case "mand": 186 flags |= syscall.MS_MANDLOCK 187 case "dirsync": 188 flags |= syscall.MS_DIRSYNC 189 case "noatime": 190 flags |= syscall.MS_NOATIME 191 case "nodiratime": 192 flags |= syscall.MS_NODIRATIME 193 case "bind": 194 flags |= syscall.MS_BIND 195 case "rbind": 196 flags |= syscall.MS_BIND | syscall.MS_REC 197 case "move": 198 flags |= syscall.MS_MOVE 199 case "silent": 200 flags |= syscall.MS_SILENT 201 case "acl": 202 flags |= syscall.MS_POSIXACL 203 case "private": 204 flags |= syscall.MS_PRIVATE 205 case "rprivate": 206 flags |= syscall.MS_PRIVATE | syscall.MS_REC 207 case "slave": 208 flags |= syscall.MS_SLAVE 209 case "rslave": 210 flags |= syscall.MS_SLAVE | syscall.MS_REC 211 case "shared": 212 flags |= syscall.MS_SHARED 213 case "rshared": 214 flags |= syscall.MS_SHARED | syscall.MS_REC 215 case "relatime": 216 flags |= syscall.MS_RELATIME 217 case "strictatime": 218 flags |= syscall.MS_STRICTATIME 219 default: 220 if !strings.HasPrefix(opt, "x-snapd.") { 221 unparsed = append(unparsed, opt) 222 } 223 } 224 } 225 return flags, unparsed 226 } 227 228 // MountOptsToFlags converts mount options strings to a mount flag. 229 func MountOptsToFlags(opts []string) (flags int, err error) { 230 flags, unparsed := MountOptsToCommonFlags(opts) 231 for _, opt := range unparsed { 232 if !strings.HasPrefix(opt, "x-snapd.") { 233 return 0, fmt.Errorf("unsupported mount option: %q", opt) 234 } 235 } 236 return flags, nil 237 } 238 239 // OptStr returns the value part of a key=value mount option. 240 // The name of the option must not contain the trailing "=" character. 241 func (e *MountEntry) OptStr(name string) (string, bool) { 242 prefix := name + "=" 243 for _, opt := range e.Options { 244 if strings.HasPrefix(opt, prefix) { 245 kv := strings.SplitN(opt, "=", 2) 246 return kv[1], true 247 } 248 } 249 return "", false 250 } 251 252 // OptBool returns true if a given mount option is present. 253 func (e *MountEntry) OptBool(name string) bool { 254 for _, opt := range e.Options { 255 if opt == name { 256 return true 257 } 258 } 259 return false 260 } 261 262 var ( 263 validModeRe = regexp.MustCompile("^0[0-7]{3}$") 264 validUserGroupRe = regexp.MustCompile("(^[0-9]+$)") 265 ) 266 267 // XSnapdMode returns the file mode associated with x-snapd.mode mount option. 268 // If the mode is not specified explicitly then a default mode of 0755 is assumed. 269 func (e *MountEntry) XSnapdMode() (os.FileMode, error) { 270 if opt, ok := e.OptStr("x-snapd.mode"); ok { 271 if !validModeRe.MatchString(opt) { 272 return 0, fmt.Errorf("cannot parse octal file mode from %q", opt) 273 } 274 var mode os.FileMode 275 n, err := fmt.Sscanf(opt, "%o", &mode) 276 if err != nil || n != 1 { 277 return 0, fmt.Errorf("cannot parse octal file mode from %q", opt) 278 } 279 return mode, nil 280 } 281 return 0755, nil 282 } 283 284 // XSnapdUID returns the user associated with x-snapd-user mount option. If 285 // the mode is not specified explicitly then a default "root" use is 286 // returned. 287 func (e *MountEntry) XSnapdUID() (uid uint64, err error) { 288 if opt, ok := e.OptStr("x-snapd.uid"); ok { 289 if !validUserGroupRe.MatchString(opt) { 290 return math.MaxUint64, fmt.Errorf("cannot parse user name %q", opt) 291 } 292 // Try to parse a numeric ID first. 293 if n, err := fmt.Sscanf(opt, "%d", &uid); n == 1 && err == nil { 294 return uid, nil 295 } 296 return uid, nil 297 } 298 return 0, nil 299 } 300 301 // XSnapdGID returns the user associated with x-snapd-user mount option. If 302 // the mode is not specified explicitly then a default "root" use is 303 // returned. 304 func (e *MountEntry) XSnapdGID() (gid uint64, err error) { 305 if opt, ok := e.OptStr("x-snapd.gid"); ok { 306 if !validUserGroupRe.MatchString(opt) { 307 return math.MaxUint64, fmt.Errorf("cannot parse group name %q", opt) 308 } 309 // Try to parse a numeric ID first. 310 if n, err := fmt.Sscanf(opt, "%d", &gid); n == 1 && err == nil { 311 return gid, nil 312 } 313 return gid, nil 314 } 315 return 0, nil 316 } 317 318 // XSnapdEntryID returns the identifier of a given mount enrty. 319 // 320 // Identifiers are kept in the x-snapd.id mount option. The value is a string 321 // that identifies a mount entry and is stable across invocations of snapd. In 322 // absence of that identifier the entry mount point is returned. 323 func (e *MountEntry) XSnapdEntryID() string { 324 if val, ok := e.OptStr("x-snapd.id"); ok { 325 return val 326 } 327 return e.Dir 328 } 329 330 // XSnapdNeededBy the identifier of an entry which needs this entry to function. 331 // 332 // The "needed by" identifiers are kept in the x-snapd.needed-by mount option. 333 // The value is a string that identifies another mount entry which, in order to 334 // be feasible, has spawned one or more additional support entries. Each such 335 // entry contains the needed-by attribute. 336 func (e *MountEntry) XSnapdNeededBy() string { 337 val, _ := e.OptStr("x-snapd.needed-by") 338 return val 339 } 340 341 // XSnapdOrigin returns the origin of a given mount entry. 342 // 343 // Currently only "layout" entries are identified with a unique origin string. 344 func (e *MountEntry) XSnapdOrigin() string { 345 val, _ := e.OptStr("x-snapd.origin") 346 return val 347 } 348 349 // XSnapdSynthetic returns true of a given mount entry is synthetic. 350 // 351 // Synthetic mount entries are created by snap-update-ns itself, separately 352 // from what snapd instructed. Such entries are needed to make other things 353 // possible. They are identified by having the "x-snapd.synthetic" mount 354 // option. 355 func (e *MountEntry) XSnapdSynthetic() bool { 356 return e.OptBool("x-snapd.synthetic") 357 } 358 359 // XSnapdKind returns the kind of a given mount entry. 360 // 361 // There are three kinds of mount entries today: one for directories, one for 362 // files and one for symlinks. The values are "", "file" and "symlink" respectively. 363 // 364 // Directories use the empty string (in fact they don't need the option at 365 // all) as this was the default and is retained for backwards compatibility. 366 func (e *MountEntry) XSnapdKind() string { 367 val, _ := e.OptStr("x-snapd.kind") 368 return val 369 } 370 371 // XSnapdDetach returns true if a mount entry should be detached rather than unmounted. 372 // 373 // Whenever we create a recursive bind mount we don't want to just unmount it 374 // as it may have replicated additional mount entries. For simplicity and 375 // race-free behavior we just detach such mount entries and let the kernel do 376 // the rest. 377 func (e *MountEntry) XSnapdDetach() bool { 378 return e.OptBool("x-snapd.detach") 379 } 380 381 // XSnapdSymlink returns the target for a symlink mount entry. 382 // 383 // For non-symlinks an empty string is returned. 384 func (e *MountEntry) XSnapdSymlink() string { 385 val, _ := e.OptStr("x-snapd.symlink") 386 return val 387 } 388 389 // XSnapdIgnoreMissing returns true if a mount entry should be ignored 390 // if the source or target are missing. 391 // 392 // By default, snap-update-ns will try to create missing source and 393 // target paths when processing a mount entry. In some cases, this 394 // behaviour is not desired and it would be better to ignore the mount 395 // entry when the source or target are missing. 396 func (e *MountEntry) XSnapdIgnoreMissing() bool { 397 return e.OptBool("x-snapd.ignore-missing") 398 } 399 400 // XSnapdNeededBy returns the string "x-snapd.needed-by=..." with the given path appended. 401 func XSnapdNeededBy(path string) string { 402 return fmt.Sprintf("x-snapd.needed-by=%s", path) 403 } 404 405 // XSnapdSynthetic returns the string "x-snapd.synthetic". 406 func XSnapdSynthetic() string { 407 return "x-snapd.synthetic" 408 } 409 410 // XSnapdDetach returns the string "x-snapd.detach". 411 func XSnapdDetach() string { 412 return "x-snapd.detach" 413 } 414 415 // XSnapdKindSymlink returns the string "x-snapd.kind=symlink". 416 func XSnapdKindSymlink() string { 417 return "x-snapd.kind=symlink" 418 } 419 420 // XSnapdKindFile returns the string "x-snapd.kind=file". 421 func XSnapdKindFile() string { 422 return "x-snapd.kind=file" 423 } 424 425 // XSnapdOriginLayout returns the string "x-snapd.origin=layout" 426 func XSnapdOriginLayout() string { 427 return "x-snapd.origin=layout" 428 } 429 430 // XSnapdOriginOvername returns the string "x-snapd.origin=overname" 431 func XSnapdOriginOvername() string { 432 return "x-snapd.origin=overname" 433 } 434 435 // XSnapdUser returns the string "x-snapd.user=%d". 436 func XSnapdUser(uid uint32) string { 437 return fmt.Sprintf("x-snapd.user=%d", uid) 438 } 439 440 // XSnapdGroup returns the string "x-snapd.group=%d". 441 func XSnapdGroup(gid uint32) string { 442 return fmt.Sprintf("x-snapd.group=%d", gid) 443 } 444 445 // XSnapdMode returns the string "x-snapd.mode=%#o". 446 func XSnapdMode(mode uint32) string { 447 return fmt.Sprintf("x-snapd.mode=%#o", mode) 448 } 449 450 // XSnapdSymlink returns the string "x-snapd.symlink=%s". 451 func XSnapdSymlink(oldname string) string { 452 return fmt.Sprintf("x-snapd.symlink=%s", oldname) 453 } 454 455 // XSnapdIgnoreMissing returns the string "x-snapd.ignore-missing". 456 func XSnapdIgnoreMissing() string { 457 return "x-snapd.ignore-missing" 458 }