github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/osutil/mountentry.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 "strings" 28 ) 29 30 // MountEntry describes an /etc/fstab-like mount entry. 31 // 32 // Fields are named after names in struct returned by getmntent(3). 33 // 34 // struct mntent { 35 // char *mnt_fsname; /* name of mounted filesystem */ 36 // char *mnt_dir; /* filesystem path prefix */ 37 // char *mnt_type; /* mount type (see Mntent.h) */ 38 // char *mnt_opts; /* mount options (see Mntent.h) */ 39 // int mnt_freq; /* dump frequency in days */ 40 // int mnt_passno; /* pass number on parallel fsck */ 41 // }; 42 type MountEntry struct { 43 Name string 44 Dir string 45 Type string 46 Options []string 47 48 DumpFrequency int 49 CheckPassNumber int 50 } 51 52 func equalStrings(a, b []string) bool { 53 if len(a) != len(b) { 54 return false 55 } 56 for i := 0; i < len(a); i++ { 57 if a[i] != b[i] { 58 return false 59 } 60 } 61 return true 62 } 63 64 // Equal checks if one entry is equal to another 65 func (e *MountEntry) Equal(o *MountEntry) bool { 66 return (e.Name == o.Name && e.Dir == o.Dir && e.Type == o.Type && 67 equalStrings(e.Options, o.Options) && e.DumpFrequency == o.DumpFrequency && 68 e.CheckPassNumber == o.CheckPassNumber) 69 } 70 71 // escape replaces whitespace characters so that getmntent can parse it correctly. 72 var escape = strings.NewReplacer( 73 " ", `\040`, 74 "\t", `\011`, 75 "\n", `\012`, 76 "\\", `\134`, 77 ).Replace 78 79 // unescape replaces escape sequences used by setmnt with whitespace characters. 80 var unescape = strings.NewReplacer( 81 `\040`, " ", 82 `\011`, "\t", 83 `\012`, "\n", 84 `\134`, "\\", 85 ).Replace 86 87 // Escape returns the given path with space, tab, newline and forward slash escaped. 88 func Escape(path string) string { 89 return escape(path) 90 } 91 92 // Unescape returns the given path with space, tab, newline and forward slash unescaped. 93 func Unescape(path string) string { 94 return unescape(path) 95 } 96 97 func (e MountEntry) String() string { 98 // Name represents name of the device in a mount entry. 99 name := "none" 100 if e.Name != "" { 101 name = escape(e.Name) 102 } 103 // Dir represents mount directory in a mount entry. 104 dir := "none" 105 if e.Dir != "" { 106 dir = escape(e.Dir) 107 } 108 // Type represents file system type in a mount entry. 109 fsType := "none" 110 if e.Type != "" { 111 fsType = escape(e.Type) 112 } 113 // Options represents mount options in a mount entry. 114 options := "defaults" 115 if len(e.Options) != 0 { 116 options = escape(strings.Join(e.Options, ",")) 117 } 118 return fmt.Sprintf("%s %s %s %s %d %d", 119 name, dir, fsType, options, e.DumpFrequency, e.CheckPassNumber) 120 } 121 122 // OptStr returns the value part of a key=value mount option. 123 // The name of the option must not contain the trailing "=" character. 124 func (e *MountEntry) OptStr(name string) (string, bool) { 125 prefix := name + "=" 126 for _, opt := range e.Options { 127 if strings.HasPrefix(opt, prefix) { 128 kv := strings.SplitN(opt, "=", 2) 129 return kv[1], true 130 } 131 } 132 return "", false 133 } 134 135 // OptBool returns true if a given mount option is present. 136 func (e *MountEntry) OptBool(name string) bool { 137 for _, opt := range e.Options { 138 if opt == name { 139 return true 140 } 141 } 142 return false 143 } 144 145 var ( 146 validModeRe = regexp.MustCompile("^0[0-7]{3}$") 147 validUserGroupRe = regexp.MustCompile("(^[0-9]+$)") 148 ) 149 150 // XSnapdMode returns the file mode associated with x-snapd.mode mount option. 151 // If the mode is not specified explicitly then a default mode of 0755 is assumed. 152 func (e *MountEntry) XSnapdMode() (os.FileMode, error) { 153 if opt, ok := e.OptStr("x-snapd.mode"); ok { 154 if !validModeRe.MatchString(opt) { 155 return 0, fmt.Errorf("cannot parse octal file mode from %q", opt) 156 } 157 var mode os.FileMode 158 n, err := fmt.Sscanf(opt, "%o", &mode) 159 if err != nil || n != 1 { 160 return 0, fmt.Errorf("cannot parse octal file mode from %q", opt) 161 } 162 return mode, nil 163 } 164 return 0755, nil 165 } 166 167 // XSnapdUID returns the user associated with x-snapd-user mount option. If 168 // the mode is not specified explicitly then a default "root" use is 169 // returned. 170 func (e *MountEntry) XSnapdUID() (uid uint64, err error) { 171 if opt, ok := e.OptStr("x-snapd.uid"); ok { 172 if !validUserGroupRe.MatchString(opt) { 173 return math.MaxUint64, fmt.Errorf("cannot parse user name %q", opt) 174 } 175 // Try to parse a numeric ID first. 176 if n, err := fmt.Sscanf(opt, "%d", &uid); n == 1 && err == nil { 177 return uid, nil 178 } 179 return uid, nil 180 } 181 return 0, nil 182 } 183 184 // XSnapdGID returns the user associated with x-snapd-user mount option. If 185 // the mode is not specified explicitly then a default "root" use is 186 // returned. 187 func (e *MountEntry) XSnapdGID() (gid uint64, err error) { 188 if opt, ok := e.OptStr("x-snapd.gid"); ok { 189 if !validUserGroupRe.MatchString(opt) { 190 return math.MaxUint64, fmt.Errorf("cannot parse group name %q", opt) 191 } 192 // Try to parse a numeric ID first. 193 if n, err := fmt.Sscanf(opt, "%d", &gid); n == 1 && err == nil { 194 return gid, nil 195 } 196 return gid, nil 197 } 198 return 0, nil 199 } 200 201 // XSnapdEntryID returns the identifier of a given mount enrty. 202 // 203 // Identifiers are kept in the x-snapd.id mount option. The value is a string 204 // that identifies a mount entry and is stable across invocations of snapd. In 205 // absence of that identifier the entry mount point is returned. 206 func (e *MountEntry) XSnapdEntryID() string { 207 if val, ok := e.OptStr("x-snapd.id"); ok { 208 return val 209 } 210 return e.Dir 211 } 212 213 // XSnapdNeededBy the identifier of an entry which needs this entry to function. 214 // 215 // The "needed by" identifiers are kept in the x-snapd.needed-by mount option. 216 // The value is a string that identifies another mount entry which, in order to 217 // be feasible, has spawned one or more additional support entries. Each such 218 // entry contains the needed-by attribute. 219 func (e *MountEntry) XSnapdNeededBy() string { 220 val, _ := e.OptStr("x-snapd.needed-by") 221 return val 222 } 223 224 // XSnapdOrigin returns the origin of a given mount entry. 225 // 226 // Currently only "layout" entries are identified with a unique origin string. 227 func (e *MountEntry) XSnapdOrigin() string { 228 val, _ := e.OptStr("x-snapd.origin") 229 return val 230 } 231 232 // XSnapdSynthetic returns true of a given mount entry is synthetic. 233 // 234 // Synthetic mount entries are created by snap-update-ns itself, separately 235 // from what snapd instructed. Such entries are needed to make other things 236 // possible. They are identified by having the "x-snapd.synthetic" mount 237 // option. 238 func (e *MountEntry) XSnapdSynthetic() bool { 239 return e.OptBool("x-snapd.synthetic") 240 } 241 242 // XSnapdKind returns the kind of a given mount entry. 243 // 244 // There are three kinds of mount entries today: one for directories, one for 245 // files and one for symlinks. The values are "", "file" and "symlink" respectively. 246 // 247 // Directories use the empty string (in fact they don't need the option at 248 // all) as this was the default and is retained for backwards compatibility. 249 func (e *MountEntry) XSnapdKind() string { 250 val, _ := e.OptStr("x-snapd.kind") 251 return val 252 } 253 254 // XSnapdDetach returns true if a mount entry should be detached rather than unmounted. 255 // 256 // Whenever we create a recursive bind mount we don't want to just unmount it 257 // as it may have replicated additional mount entries. For simplicity and 258 // race-free behavior we just detach such mount entries and let the kernel do 259 // the rest. 260 func (e *MountEntry) XSnapdDetach() bool { 261 return e.OptBool("x-snapd.detach") 262 } 263 264 // XSnapdSymlink returns the target for a symlink mount entry. 265 // 266 // For non-symlinks an empty string is returned. 267 func (e *MountEntry) XSnapdSymlink() string { 268 val, _ := e.OptStr("x-snapd.symlink") 269 return val 270 } 271 272 // XSnapdIgnoreMissing returns true if a mount entry should be ignored 273 // if the source or target are missing. 274 // 275 // By default, snap-update-ns will try to create missing source and 276 // target paths when processing a mount entry. In some cases, this 277 // behaviour is not desired and it would be better to ignore the mount 278 // entry when the source or target are missing. 279 func (e *MountEntry) XSnapdIgnoreMissing() bool { 280 return e.OptBool("x-snapd.ignore-missing") 281 } 282 283 // XSnapdNeededBy returns the string "x-snapd.needed-by=..." with the given path appended. 284 func XSnapdNeededBy(path string) string { 285 return fmt.Sprintf("x-snapd.needed-by=%s", path) 286 } 287 288 // XSnapdSynthetic returns the string "x-snapd.synthetic". 289 func XSnapdSynthetic() string { 290 return "x-snapd.synthetic" 291 } 292 293 // XSnapdDetach returns the string "x-snapd.detach". 294 func XSnapdDetach() string { 295 return "x-snapd.detach" 296 } 297 298 // XSnapdKindSymlink returns the string "x-snapd.kind=symlink". 299 func XSnapdKindSymlink() string { 300 return "x-snapd.kind=symlink" 301 } 302 303 // XSnapdKindFile returns the string "x-snapd.kind=file". 304 func XSnapdKindFile() string { 305 return "x-snapd.kind=file" 306 } 307 308 // XSnapdOriginLayout returns the string "x-snapd.origin=layout" 309 func XSnapdOriginLayout() string { 310 return "x-snapd.origin=layout" 311 } 312 313 // XSnapdOriginOvername returns the string "x-snapd.origin=overname" 314 func XSnapdOriginOvername() string { 315 return "x-snapd.origin=overname" 316 } 317 318 // XSnapdUser returns the string "x-snapd.user=%d". 319 func XSnapdUser(uid uint32) string { 320 return fmt.Sprintf("x-snapd.user=%d", uid) 321 } 322 323 // XSnapdGroup returns the string "x-snapd.group=%d". 324 func XSnapdGroup(gid uint32) string { 325 return fmt.Sprintf("x-snapd.group=%d", gid) 326 } 327 328 // XSnapdMode returns the string "x-snapd.mode=%#o". 329 func XSnapdMode(mode uint32) string { 330 return fmt.Sprintf("x-snapd.mode=%#o", mode) 331 } 332 333 // XSnapdSymlink returns the string "x-snapd.symlink=%s". 334 func XSnapdSymlink(oldname string) string { 335 return fmt.Sprintf("x-snapd.symlink=%s", oldname) 336 } 337 338 // XSnapdIgnoreMissing returns the string "x-snapd.ignore-missing". 339 func XSnapdIgnoreMissing() string { 340 return "x-snapd.ignore-missing" 341 }