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  }