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  }