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