gitee.com/mysnapcore/mysnapd@v0.1.0/cmd/snap-update-ns/utils.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 main
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  	"strings"
    28  	"syscall"
    29  
    30  	"gitee.com/mysnapcore/mysnapd/logger"
    31  	"gitee.com/mysnapcore/mysnapd/osutil"
    32  	"gitee.com/mysnapcore/mysnapd/osutil/sys"
    33  	"gitee.com/mysnapcore/mysnapd/strutil"
    34  )
    35  
    36  // not available through syscall
    37  const (
    38  	umountNoFollow = 8
    39  	// StReadOnly is the equivalent of ST_RDONLY
    40  	StReadOnly = 1
    41  	// SquashfsMagic is the equivalent of SQUASHFS_MAGIC
    42  	SquashfsMagic = 0x73717368
    43  	// Ext4Magic is the equivalent of EXT4_SUPER_MAGIC
    44  	//nolint:deadcode
    45  	Ext4Magic = 0xef53
    46  	// TmpfsMagic is the equivalent of TMPFS_MAGIC
    47  	TmpfsMagic = 0x01021994
    48  )
    49  
    50  // For mocking everything during testing.
    51  var (
    52  	osLstat    = os.Lstat
    53  	osReadlink = os.Readlink
    54  	osRemove   = os.Remove
    55  
    56  	sysClose      = syscall.Close
    57  	sysMkdirat    = syscall.Mkdirat
    58  	sysMount      = syscall.Mount
    59  	sysOpen       = syscall.Open
    60  	sysOpenat     = syscall.Openat
    61  	sysUnmount    = syscall.Unmount
    62  	sysFchown     = sys.Fchown
    63  	sysFstat      = syscall.Fstat
    64  	sysFstatfs    = syscall.Fstatfs
    65  	sysSymlinkat  = osutil.Symlinkat
    66  	sysReadlinkat = osutil.Readlinkat
    67  	sysFchdir     = syscall.Fchdir
    68  	sysLstat      = syscall.Lstat
    69  
    70  	ioutilReadDir = ioutil.ReadDir
    71  )
    72  
    73  // ReadOnlyFsError is an error encapsulating encountered EROFS.
    74  type ReadOnlyFsError struct {
    75  	Path string
    76  }
    77  
    78  func (e *ReadOnlyFsError) Error() string {
    79  	return fmt.Sprintf("cannot operate on read-only filesystem at %s", e.Path)
    80  }
    81  
    82  // OpenPath creates a path file descriptor for the given
    83  // path, making sure no components are symbolic links.
    84  //
    85  // The file descriptor is opened using the O_PATH, O_NOFOLLOW,
    86  // and O_CLOEXEC flags.
    87  func OpenPath(path string) (int, error) {
    88  	iter, err := strutil.NewPathIterator(path)
    89  	if err != nil {
    90  		return -1, fmt.Errorf("cannot open path: %s", err)
    91  	}
    92  	if !filepath.IsAbs(iter.Path()) {
    93  		return -1, fmt.Errorf("path %v is not absolute", iter.Path())
    94  	}
    95  	iter.Next() // Advance iterator to '/'
    96  	// We use the following flags to open:
    97  	//  O_PATH: we don't intend to use the fd for IO
    98  	//  O_NOFOLLOW: don't follow symlinks
    99  	//  O_DIRECTORY: we expect to find directories (except for the leaf)
   100  	//  O_CLOEXEC: don't leak file descriptors over exec() boundaries
   101  	openFlags := sys.O_PATH | syscall.O_NOFOLLOW | syscall.O_DIRECTORY | syscall.O_CLOEXEC
   102  	fd, err := sysOpen("/", openFlags, 0)
   103  	if err != nil {
   104  		return -1, err
   105  	}
   106  	for iter.Next() {
   107  		// Ensure the parent file descriptor is closed
   108  		defer sysClose(fd)
   109  		if !strings.HasSuffix(iter.CurrentName(), "/") {
   110  			openFlags &^= syscall.O_DIRECTORY
   111  		}
   112  		fd, err = sysOpenat(fd, iter.CurrentCleanName(), openFlags, 0)
   113  		if err != nil {
   114  			return -1, err
   115  		}
   116  	}
   117  
   118  	var statBuf syscall.Stat_t
   119  	err = sysFstat(fd, &statBuf)
   120  	if err != nil {
   121  		sysClose(fd)
   122  		return -1, err
   123  	}
   124  	if statBuf.Mode&syscall.S_IFMT == syscall.S_IFLNK {
   125  		sysClose(fd)
   126  		return -1, fmt.Errorf("%q is a symbolic link", path)
   127  	}
   128  	return fd, nil
   129  }
   130  
   131  // syscallMode returns the syscall-specific mode bits from Go's portable mode bits.
   132  // This is a copy of the same helper in Go's os package.
   133  func syscallMode(i os.FileMode) (o uint32) {
   134  	o |= uint32(i.Perm())
   135  	if i&os.ModeSetuid != 0 {
   136  		o |= syscall.S_ISUID
   137  	}
   138  	if i&os.ModeSetgid != 0 {
   139  		o |= syscall.S_ISGID
   140  	}
   141  	if i&os.ModeSticky != 0 {
   142  		o |= syscall.S_ISVTX
   143  	}
   144  	// No mapping for Go's ModeTemporary (plan9 only).
   145  	return o
   146  }
   147  
   148  // MkPrefix creates all the missing directories in a given base path and
   149  // returns the file descriptor to the leaf directory as well as the restricted
   150  // flag. This function is a base for secure variants of mkdir, touch and
   151  // symlink. None of the traversed directories can be symbolic links.
   152  func MkPrefix(base string, perm os.FileMode, uid sys.UserID, gid sys.GroupID, rs *Restrictions) (int, error) {
   153  	iter, err := strutil.NewPathIterator(base)
   154  	if err != nil {
   155  		// TODO: Reword the error and adjust the tests.
   156  		return -1, fmt.Errorf("cannot split unclean path %q", base)
   157  	}
   158  	if !filepath.IsAbs(iter.Path()) {
   159  		return -1, fmt.Errorf("path %v is not absolute", iter.Path())
   160  	}
   161  	iter.Next() // Advance iterator to '/'
   162  
   163  	const openFlags = syscall.O_NOFOLLOW | syscall.O_CLOEXEC | syscall.O_DIRECTORY
   164  	// Open the root directory and start there.
   165  	//
   166  	// We don't have to check for possible trespassing on / here because we are
   167  	// going to check for it in sec.MkDir call below which verifies that
   168  	// trespassing restrictions are not violated.
   169  	fd, err := sysOpen("/", openFlags, 0)
   170  	if err != nil {
   171  		return -1, fmt.Errorf("cannot open root directory: %v", err)
   172  	}
   173  	for iter.Next() {
   174  		// Keep closing the previous descriptor as we go, so that we have the
   175  		// last one handy from the MkDir below.
   176  		defer sysClose(fd)
   177  		fd, err = MkDir(fd, iter.CurrentBase(), iter.CurrentCleanName(), perm, uid, gid, rs)
   178  		if err != nil {
   179  			return -1, err
   180  		}
   181  	}
   182  
   183  	return fd, nil
   184  }
   185  
   186  // MkDir creates a directory with a given name.
   187  //
   188  // The directory is represented with a file descriptor and its name (for
   189  // convenience). This function is meant to be used to construct subsequent
   190  // elements of some path. The return value contains the newly created file
   191  // descriptor for the new directory or -1 on error.
   192  func MkDir(dirFd int, dirName string, name string, perm os.FileMode, uid sys.UserID, gid sys.GroupID, rs *Restrictions) (int, error) {
   193  	if err := rs.Check(dirFd, dirName); err != nil {
   194  		return -1, err
   195  	}
   196  
   197  	made := true
   198  	const openFlags = syscall.O_NOFOLLOW | syscall.O_CLOEXEC | syscall.O_DIRECTORY
   199  
   200  	if err := sysMkdirat(dirFd, name, syscallMode(perm)); err != nil {
   201  		switch err {
   202  		case syscall.EEXIST:
   203  			made = false
   204  		case syscall.EROFS:
   205  			// Treat EROFS specially: this is a hint that we have to poke a
   206  			// hole using tmpfs. The path below is the location where we
   207  			// need to poke the hole.
   208  			return -1, &ReadOnlyFsError{Path: dirName}
   209  		default:
   210  			return -1, fmt.Errorf("cannot create directory %q: %v", filepath.Join(dirName, name), err)
   211  		}
   212  	}
   213  	newFd, err := sysOpenat(dirFd, name, openFlags, 0)
   214  	if err != nil {
   215  		return -1, fmt.Errorf("cannot open directory %q: %v", filepath.Join(dirName, name), err)
   216  	}
   217  	if made {
   218  		// Chown each segment that we made.
   219  		if err := sysFchown(newFd, uid, gid); err != nil {
   220  			// Close the FD we opened if we fail here since the caller will get
   221  			// an error and won't assume responsibility for the FD.
   222  			sysClose(newFd)
   223  			return -1, fmt.Errorf("cannot chown directory %q to %d.%d: %v", filepath.Join(dirName, name), uid, gid, err)
   224  		}
   225  		// As soon as we find a place that is safe to write we can switch off
   226  		// the restricted mode (and thus any subsequent checks). This is
   227  		// because we only allow "writing" to read-only filesystems where
   228  		// writes fail with EROFS or to a tmpfs that snapd has privately
   229  		// mounted inside the per-snap mount namespace. As soon as we start
   230  		// walking over such tmpfs any subsequent children are either read-
   231  		// only bind mounts from $SNAP, other tmpfs'es  (e.g. one explicitly
   232  		// constructed for a layout) or writable places that are bind-mounted
   233  		// from $SNAP_DATA or similar.
   234  		rs.Lift()
   235  	}
   236  	return newFd, err
   237  }
   238  
   239  // MkFile creates a file with a given name.
   240  //
   241  // The directory is represented with a file descriptor and its name (for
   242  // convenience). This function is meant to be used to create the leaf file as
   243  // a preparation for a mount point. Existing files are reused without errors.
   244  // Newly created files have the specified mode and ownership.
   245  func MkFile(dirFd int, dirName string, name string, perm os.FileMode, uid sys.UserID, gid sys.GroupID, rs *Restrictions) error {
   246  	if err := rs.Check(dirFd, dirName); err != nil {
   247  		return err
   248  	}
   249  
   250  	made := true
   251  	// NOTE: Tests don't show O_RDONLY as has a value of 0 and is not
   252  	// translated to textual form. It is added here for explicitness.
   253  	const openFlags = syscall.O_NOFOLLOW | syscall.O_CLOEXEC | syscall.O_RDONLY
   254  
   255  	// Open the final path segment as a file. Try to create the file (so that
   256  	// we know if we need to chown it) but fall back to just opening an
   257  	// existing one.
   258  
   259  	newFd, err := sysOpenat(dirFd, name, openFlags|syscall.O_CREAT|syscall.O_EXCL, syscallMode(perm))
   260  	if err != nil {
   261  		switch err {
   262  		case syscall.EEXIST:
   263  			// If the file exists then just open it without O_CREAT and O_EXCL
   264  			newFd, err = sysOpenat(dirFd, name, openFlags, 0)
   265  			if err != nil {
   266  				return fmt.Errorf("cannot open file %q: %v", filepath.Join(dirName, name), err)
   267  			}
   268  			made = false
   269  		case syscall.EROFS:
   270  			// Treat EROFS specially: this is a hint that we have to poke a
   271  			// hole using tmpfs. The path below is the location where we
   272  			// need to poke the hole.
   273  			return &ReadOnlyFsError{Path: dirName}
   274  		default:
   275  			return fmt.Errorf("cannot open file %q: %v", filepath.Join(dirName, name), err)
   276  		}
   277  	}
   278  	defer sysClose(newFd)
   279  
   280  	if made {
   281  		// Chown the file if we made it.
   282  		if err := sysFchown(newFd, uid, gid); err != nil {
   283  			return fmt.Errorf("cannot chown file %q to %d.%d: %v", filepath.Join(dirName, name), uid, gid, err)
   284  		}
   285  	}
   286  
   287  	return nil
   288  }
   289  
   290  // MkSymlink creates a symlink with a given name.
   291  //
   292  // The directory is represented with a file descriptor and its name (for
   293  // convenience). This function is meant to be used to create the leaf symlink.
   294  // Existing and identical symlinks are reused without errors.
   295  func MkSymlink(dirFd int, dirName string, name string, oldname string, rs *Restrictions) error {
   296  	if err := rs.Check(dirFd, dirName); err != nil {
   297  		return err
   298  	}
   299  
   300  	// Create the final path segment as a symlink.
   301  	if err := sysSymlinkat(oldname, dirFd, name); err != nil {
   302  		switch err {
   303  		case syscall.EEXIST:
   304  			var objFd int
   305  			// If the file exists then just open it for examination.
   306  			// Maybe it's the symlink we were hoping to create.
   307  			objFd, err = sysOpenat(dirFd, name, syscall.O_CLOEXEC|sys.O_PATH|syscall.O_NOFOLLOW, 0)
   308  			if err != nil {
   309  				return fmt.Errorf("cannot open existing file %q: %v", filepath.Join(dirName, name), err)
   310  			}
   311  			defer sysClose(objFd)
   312  			var statBuf syscall.Stat_t
   313  			err = sysFstat(objFd, &statBuf)
   314  			if err != nil {
   315  				return fmt.Errorf("cannot inspect existing file %q: %v", filepath.Join(dirName, name), err)
   316  			}
   317  			if statBuf.Mode&syscall.S_IFMT != syscall.S_IFLNK {
   318  				return fmt.Errorf("cannot create symbolic link %q: existing file in the way", filepath.Join(dirName, name))
   319  			}
   320  			var n int
   321  			buf := make([]byte, len(oldname)+2)
   322  			n, err = sysReadlinkat(objFd, "", buf)
   323  			if err != nil {
   324  				return fmt.Errorf("cannot read symbolic link %q: %v", filepath.Join(dirName, name), err)
   325  			}
   326  			if string(buf[:n]) != oldname {
   327  				return fmt.Errorf("cannot create symbolic link %q: existing symbolic link in the way", filepath.Join(dirName, name))
   328  			}
   329  			return nil
   330  		case syscall.EROFS:
   331  			// Treat EROFS specially: this is a hint that we have to poke a
   332  			// hole using tmpfs. The path below is the location where we
   333  			// need to poke the hole.
   334  			return &ReadOnlyFsError{Path: dirName}
   335  		default:
   336  			return fmt.Errorf("cannot create symlink %q: %v", filepath.Join(dirName, name), err)
   337  		}
   338  	}
   339  
   340  	return nil
   341  }
   342  
   343  // MkdirAll is the secure variant of os.MkdirAll.
   344  //
   345  // Unlike the regular version this implementation does not follow any symbolic
   346  // links. At all times the new directory segment is created using mkdirat(2)
   347  // while holding an open file descriptor to the parent directory.
   348  //
   349  // The only handled error is mkdirat(2) that fails with EEXIST. All other
   350  // errors are fatal but there is no attempt to undo anything that was created.
   351  //
   352  // The uid and gid are used for the fchown(2) system call which is performed
   353  // after each segment is created and opened. The special value -1 may be used
   354  // to request that ownership is not changed.
   355  func MkdirAll(path string, perm os.FileMode, uid sys.UserID, gid sys.GroupID, rs *Restrictions) error {
   356  	if path != filepath.Clean(path) {
   357  		// TODO: Reword the error and adjust the tests.
   358  		return fmt.Errorf("cannot split unclean path %q", path)
   359  	}
   360  	// Only support absolute paths to avoid bugs in snap-confine when
   361  	// called from anywhere.
   362  	if !filepath.IsAbs(path) {
   363  		return fmt.Errorf("cannot create directory with relative path: %q", path)
   364  	}
   365  	base, name := filepath.Split(path)
   366  	base = filepath.Clean(base) // Needed to chomp the trailing slash.
   367  
   368  	// Create the prefix.
   369  	dirFd, err := MkPrefix(base, perm, uid, gid, rs)
   370  	if err != nil {
   371  		return err
   372  	}
   373  	defer sysClose(dirFd)
   374  
   375  	if name != "" {
   376  		// Create the leaf as a directory.
   377  		leafFd, err := MkDir(dirFd, base, name, perm, uid, gid, rs)
   378  		if err != nil {
   379  			return err
   380  		}
   381  		defer sysClose(leafFd)
   382  	}
   383  
   384  	return nil
   385  }
   386  
   387  // MkfileAll is a secure implementation of "mkdir -p $(dirname $1) && touch $1".
   388  //
   389  // This function is like MkdirAll but it creates an empty file instead of
   390  // a directory for the final path component. Each created directory component
   391  // is chowned to the desired user and group.
   392  func MkfileAll(path string, perm os.FileMode, uid sys.UserID, gid sys.GroupID, rs *Restrictions) error {
   393  	if path != filepath.Clean(path) {
   394  		// TODO: Reword the error and adjust the tests.
   395  		return fmt.Errorf("cannot split unclean path %q", path)
   396  	}
   397  	// Only support absolute paths to avoid bugs in snap-confine when
   398  	// called from anywhere.
   399  	if !filepath.IsAbs(path) {
   400  		return fmt.Errorf("cannot create file with relative path: %q", path)
   401  	}
   402  	// Only support file names, not directory names.
   403  	if strings.HasSuffix(path, "/") {
   404  		return fmt.Errorf("cannot create non-file path: %q", path)
   405  	}
   406  	base, name := filepath.Split(path)
   407  	base = filepath.Clean(base) // Needed to chomp the trailing slash.
   408  
   409  	// Create the prefix.
   410  	dirFd, err := MkPrefix(base, perm, uid, gid, rs)
   411  	if err != nil {
   412  		return err
   413  	}
   414  	defer sysClose(dirFd)
   415  
   416  	if name != "" {
   417  		// Create the leaf as a file.
   418  		err = MkFile(dirFd, base, name, perm, uid, gid, rs)
   419  	}
   420  	return err
   421  }
   422  
   423  // MksymlinkAll is a secure implementation of "ln -s".
   424  func MksymlinkAll(path string, perm os.FileMode, uid sys.UserID, gid sys.GroupID, oldname string, rs *Restrictions) error {
   425  	if path != filepath.Clean(path) {
   426  		// TODO: Reword the error and adjust the tests.
   427  		return fmt.Errorf("cannot split unclean path %q", path)
   428  	}
   429  	// Only support absolute paths to avoid bugs in snap-confine when
   430  	// called from anywhere.
   431  	if !filepath.IsAbs(path) {
   432  		return fmt.Errorf("cannot create symlink with relative path: %q", path)
   433  	}
   434  	// Only support file names, not directory names.
   435  	if strings.HasSuffix(path, "/") {
   436  		return fmt.Errorf("cannot create non-file path: %q", path)
   437  	}
   438  	if oldname == "" {
   439  		return fmt.Errorf("cannot create symlink with empty target: %q", path)
   440  	}
   441  
   442  	base, name := filepath.Split(path)
   443  	base = filepath.Clean(base) // Needed to chomp the trailing slash.
   444  
   445  	// Create the prefix.
   446  	dirFd, err := MkPrefix(base, perm, uid, gid, rs)
   447  	if err != nil {
   448  		return err
   449  	}
   450  	defer sysClose(dirFd)
   451  
   452  	if name != "" {
   453  		// Create the leaf as a symlink.
   454  		err = MkSymlink(dirFd, base, name, oldname, rs)
   455  	}
   456  	return err
   457  }
   458  
   459  // planWritableMimic plans how to transform a given directory from read-only to writable.
   460  //
   461  // The algorithm is designed to be universally reversible so that it can be
   462  // always de-constructed back to the original directory. The original directory
   463  // is hidden by tmpfs and a subset of things that were present there originally
   464  // is bind mounted back on top of empty directories or empty files. Symlinks
   465  // are re-created directly. Devices and all other elements are not supported
   466  // because they are forbidden in snaps for which this function is designed to
   467  // be used with. Since the original directory is hidden the algorithm relies on
   468  // a temporary directory where the original is bind-mounted during the
   469  // progression of the algorithm.
   470  func planWritableMimic(dir, neededBy string) ([]*Change, error) {
   471  	// We need a place for "safe keeping" of what is present in the original
   472  	// directory as we are about to attach a tmpfs there, which will hide
   473  	// everything inside.
   474  	logger.Debugf("create-writable-mimic %q", dir)
   475  	safeKeepingDir := filepath.Join("/tmp/.snap/", dir)
   476  
   477  	var changes []*Change
   478  
   479  	// Stat the original directory to know which mode and ownership to
   480  	// replicate on top of the tmpfs we are about to create below.
   481  	var sb syscall.Stat_t
   482  	if err := sysLstat(dir, &sb); err != nil {
   483  		return nil, err
   484  	}
   485  
   486  	// Bind mount the original directory elsewhere for safe-keeping.
   487  	changes = append(changes, &Change{
   488  		Action: Mount, Entry: osutil.MountEntry{
   489  			// NOTE: Here we recursively bind because we realized that not
   490  			// doing so doesn't work on core devices which use bind mounts
   491  			// extensively to construct writable spaces in /etc and /var and
   492  			// elsewhere.
   493  			//
   494  			// All directories present in the original are also recursively
   495  			// bind mounted back to their original location. To unmount this
   496  			// contraption we use MNT_DETACH which frees us from having to
   497  			// enumerate the mount table, unmount all the things (starting
   498  			// with most nested).
   499  			//
   500  			// The undo logic handles rbind mounts and adds x-snapd.unbind
   501  			// flag to them, which in turns translates to MNT_DETACH on
   502  			// umount2(2) system call.
   503  			Name: dir, Dir: safeKeepingDir, Options: []string{"rbind"}},
   504  	})
   505  
   506  	// Mount tmpfs over the original directory, hiding its contents.
   507  	// The mounted tmpfs will mimic the mode and ownership of the original
   508  	// directory.
   509  	changes = append(changes, &Change{
   510  		Action: Mount, Entry: osutil.MountEntry{
   511  			Name: "tmpfs", Dir: dir, Type: "tmpfs",
   512  			Options: []string{
   513  				osutil.XSnapdSynthetic(),
   514  				osutil.XSnapdNeededBy(neededBy),
   515  				fmt.Sprintf("mode=%#o", sb.Mode&07777),
   516  				fmt.Sprintf("uid=%d", sb.Uid),
   517  				fmt.Sprintf("gid=%d", sb.Gid),
   518  			},
   519  		},
   520  	})
   521  	// Iterate over the items in the original directory (nothing is mounted _yet_).
   522  	entries, err := ioutilReadDir(dir)
   523  	if err != nil {
   524  		return nil, err
   525  	}
   526  	for _, fi := range entries {
   527  		ch := &Change{Action: Mount, Entry: osutil.MountEntry{
   528  			Name: filepath.Join(safeKeepingDir, fi.Name()),
   529  			Dir:  filepath.Join(dir, fi.Name()),
   530  		}}
   531  		// Bind mount each element from the safe-keeping directory into the
   532  		// tmpfs. Our Change.Perform() engine can create the missing
   533  		// directories automatically so we don't bother creating those.
   534  		m := fi.Mode()
   535  		switch {
   536  		case m.IsDir():
   537  			ch.Entry.Options = []string{"rbind"}
   538  		case m.IsRegular():
   539  			ch.Entry.Options = []string{"bind", osutil.XSnapdKindFile()}
   540  		case m&os.ModeSymlink != 0:
   541  			if target, err := osReadlink(filepath.Join(dir, fi.Name())); err == nil {
   542  				ch.Entry.Options = []string{osutil.XSnapdKindSymlink(), osutil.XSnapdSymlink(target)}
   543  			} else {
   544  				continue
   545  			}
   546  		default:
   547  			logger.Noticef("skipping unsupported file %s", fi)
   548  			continue
   549  		}
   550  		ch.Entry.Options = append(ch.Entry.Options, osutil.XSnapdSynthetic())
   551  		ch.Entry.Options = append(ch.Entry.Options, osutil.XSnapdNeededBy(neededBy))
   552  		changes = append(changes, ch)
   553  	}
   554  	// Finally unbind the safe-keeping directory as we don't need it anymore.
   555  	changes = append(changes, &Change{
   556  		Action: Unmount, Entry: osutil.MountEntry{Name: "none", Dir: safeKeepingDir, Options: []string{osutil.XSnapdDetach()}},
   557  	})
   558  	return changes, nil
   559  }
   560  
   561  // FatalError is an error that we cannot correct.
   562  type FatalError struct {
   563  	error
   564  }
   565  
   566  // execWritableMimic executes the plan for a writable mimic.
   567  // The result is a transformed mount namespace and a set of fake mount changes
   568  // that only exist in order to undo the plan.
   569  //
   570  // Certain assumptions are made about the plan, it must closely resemble that
   571  // created by planWritableMimic, in particular the sequence must look like this:
   572  //
   573  // - bind a directory aside into safekeeping location
   574  // - cover the original with tmpfs
   575  // - bind mount something from safekeeping location to an empty file or
   576  //   directory in the tmpfs; this step can repeat any number of times
   577  // - unbind the safekeeping location
   578  //
   579  // Apart from merely executing the plan a fake plan is returned for undo. The
   580  // undo plan skips the following elements as compared to the original plan:
   581  //
   582  // - the initial bind mount that constructs the safekeeping directory is gone
   583  // - the final unmount that removes the safekeeping directory
   584  // - the source of each of the bind mounts that re-populate tmpfs.
   585  //
   586  // In the event of a failure the undo plan is executed and an error is
   587  // returned. If the undo plan fails the function returns a FatalError as it
   588  // cannot fix the system from an inconsistent state.
   589  func execWritableMimic(plan []*Change, as *Assumptions) ([]*Change, error) {
   590  	undoChanges := make([]*Change, 0, len(plan)-2)
   591  	for i, change := range plan {
   592  		if _, err := change.Perform(as); err != nil {
   593  			// Drat, we failed! Let's undo everything according to our own undo
   594  			// plan, by following it in reverse order.
   595  
   596  			recoveryUndoChanges := make([]*Change, 0, len(undoChanges)+1)
   597  			if i > 0 {
   598  				// The undo plan doesn't contain the entry for the initial bind
   599  				// mount of the safe keeping directory but we have already
   600  				// performed it. For this recovery phase we need to insert that
   601  				// in front of the undo plan manually.
   602  				recoveryUndoChanges = append(recoveryUndoChanges, plan[0])
   603  			}
   604  			recoveryUndoChanges = append(recoveryUndoChanges, undoChanges...)
   605  
   606  			for j := len(recoveryUndoChanges) - 1; j >= 0; j-- {
   607  				recoveryUndoChange := recoveryUndoChanges[j]
   608  				// All the changes mount something, we need to reverse that.
   609  				// The "undo plan" is "a plan that can be undone" not "the plan
   610  				// for how to undo" so we need to flip the actions.
   611  				recoveryUndoChange.Action = Unmount
   612  				if recoveryUndoChange.Entry.OptBool("rbind") {
   613  					recoveryUndoChange.Entry.Options = append(recoveryUndoChange.Entry.Options, osutil.XSnapdDetach())
   614  				}
   615  				if _, err2 := recoveryUndoChange.Perform(as); err2 != nil {
   616  					// Drat, we failed when trying to recover from an error.
   617  					// We cannot do anything at this stage.
   618  					return nil, &FatalError{error: fmt.Errorf("cannot undo change %q while recovering from earlier error %v: %v", recoveryUndoChange, err, err2)}
   619  				}
   620  			}
   621  			return nil, err
   622  		}
   623  		if i == 0 || i == len(plan)-1 {
   624  			// Don't represent the initial and final changes in the undo plan.
   625  			// The initial change is the safe-keeping bind mount, the final
   626  			// change is the safe-keeping unmount.
   627  			continue
   628  		}
   629  		if change.Entry.XSnapdKind() == "symlink" {
   630  			// Don't represent symlinks in the undo plan. They are removed when
   631  			// the tmpfs is unmounted.
   632  			continue
   633  
   634  		}
   635  		// Store an undo change for the change we just performed.
   636  		undoOpts := change.Entry.Options
   637  		if change.Entry.OptBool("rbind") {
   638  			undoOpts = make([]string, 0, len(change.Entry.Options)+1)
   639  			undoOpts = append(undoOpts, change.Entry.Options...)
   640  			undoOpts = append(undoOpts, "x-snapd.detach")
   641  		}
   642  		undoChange := &Change{
   643  			Action: Mount,
   644  			Entry:  osutil.MountEntry{Dir: change.Entry.Dir, Name: change.Entry.Name, Type: change.Entry.Type, Options: undoOpts},
   645  		}
   646  		// Because of the use of a temporary bind mount (aka the safe-keeping
   647  		// directory) we cannot represent bind mounts fully (the temporary bind
   648  		// mount is unmounted as the last stage of this process). For that
   649  		// reason let's hide the original location and overwrite it so to
   650  		// appear as if the directory was a bind mount over itself. This is not
   651  		// fully true (it is a bind mount from the old self to the new empty
   652  		// directory or file in the same path, with the tmpfs in place already)
   653  		// but this is closer to the truth and more in line with the idea that
   654  		// this is just a plan for undoing the operation.
   655  		if undoChange.Entry.OptBool("bind") || undoChange.Entry.OptBool("rbind") {
   656  			undoChange.Entry.Name = undoChange.Entry.Dir
   657  		}
   658  		undoChanges = append(undoChanges, undoChange)
   659  	}
   660  	return undoChanges, nil
   661  }
   662  
   663  func createWritableMimic(dir, neededBy string, as *Assumptions) ([]*Change, error) {
   664  	plan, err := planWritableMimic(dir, neededBy)
   665  	if err != nil {
   666  		return nil, err
   667  	}
   668  	changes, err := execWritableMimic(plan, as)
   669  	if err != nil {
   670  		return nil, err
   671  	}
   672  	return changes, nil
   673  }