gitee.com/mysnapcore/mysnapd@v0.1.0/cmd/snap-update-ns/change.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  	"errors"
    24  	"fmt"
    25  	"os"
    26  	"path/filepath"
    27  	"sort"
    28  	"strings"
    29  	"syscall"
    30  
    31  	"gitee.com/mysnapcore/mysnapd/logger"
    32  	"gitee.com/mysnapcore/mysnapd/osutil"
    33  	"gitee.com/mysnapcore/mysnapd/osutil/mount"
    34  )
    35  
    36  // Action represents a mount action (mount, remount, unmount, etc).
    37  type Action string
    38  
    39  const (
    40  	// Keep indicates that a given mount entry should be kept as-is.
    41  	Keep Action = "keep"
    42  	// Mount represents an action that results in mounting something somewhere.
    43  	Mount Action = "mount"
    44  	// Unmount represents an action that results in unmounting something from somewhere.
    45  	Unmount Action = "unmount"
    46  	// Remount when needed
    47  )
    48  
    49  var (
    50  	// function calls for mocking
    51  	osutilIsDirectory = osutil.IsDirectory
    52  )
    53  
    54  var (
    55  	// ErrIgnoredMissingMount is returned when a mount entry has
    56  	// been marked with x-snapd.ignore-missing, and the mount
    57  	// source or target do not exist.
    58  	ErrIgnoredMissingMount = errors.New("mount source or target are missing")
    59  )
    60  
    61  // Change describes a change to the mount table (action and the entry to act on).
    62  type Change struct {
    63  	Entry  osutil.MountEntry
    64  	Action Action
    65  }
    66  
    67  // String formats mount change to a human-readable line.
    68  func (c Change) String() string {
    69  	return fmt.Sprintf("%s (%s)", c.Action, c.Entry)
    70  }
    71  
    72  // changePerform is Change.Perform that can be mocked for testing.
    73  var changePerform func(*Change, *Assumptions) ([]*Change, error)
    74  
    75  // mimicRequired provides information if an error warrants a writable mimic.
    76  //
    77  // The returned path is the location where a mimic should be constructed.
    78  func mimicRequired(err error) (needsMimic bool, path string) {
    79  	switch err := err.(type) {
    80  	case *ReadOnlyFsError:
    81  		rofsErr := err
    82  		return true, rofsErr.Path
    83  	case *TrespassingError:
    84  		tErr := err
    85  		return true, tErr.ViolatedPath
    86  	}
    87  	return false, ""
    88  }
    89  
    90  func (c *Change) createPath(path string, pokeHoles bool, as *Assumptions) ([]*Change, error) {
    91  	// If we've been asked to create a missing path, and the mount
    92  	// entry uses the ignore-missing option, return an error.
    93  	if c.Entry.XSnapdIgnoreMissing() {
    94  		return nil, ErrIgnoredMissingMount
    95  	}
    96  
    97  	var err error
    98  	var changes []*Change
    99  
   100  	// In case we need to create something, some constants.
   101  	const (
   102  		uid = 0
   103  		gid = 0
   104  	)
   105  	mode := as.ModeForPath(path)
   106  
   107  	// If the element doesn't exist we can attempt to create it.  We will
   108  	// create the parent directory and then the final element relative to it.
   109  	// The traversed space may be writable so we just try to create things
   110  	// first.
   111  	kind := c.Entry.XSnapdKind()
   112  
   113  	// TODO: re-factor this, if possible, with inspection and preemptive
   114  	// creation after the current release ships. This should be possible but
   115  	// will affect tests heavily (churn, not safe before release).
   116  	rs := as.RestrictionsFor(path)
   117  	switch kind {
   118  	case "":
   119  		err = MkdirAll(path, mode, uid, gid, rs)
   120  	case "file":
   121  		err = MkfileAll(path, mode, uid, gid, rs)
   122  	case "symlink":
   123  		err = MksymlinkAll(path, mode, uid, gid, c.Entry.XSnapdSymlink(), rs)
   124  	}
   125  	if needsMimic, mimicPath := mimicRequired(err); needsMimic && pokeHoles {
   126  		// If the error can be recovered by using a writable mimic
   127  		// then construct one and try again.
   128  		logger.Debugf("need to create writable mimic needed to create path %q (original error: %v)", path, err)
   129  		changes, err = createWritableMimic(mimicPath, path, as)
   130  		if err != nil {
   131  			err = fmt.Errorf("cannot create writable mimic over %q: %s", mimicPath, err)
   132  		} else {
   133  			// Try once again. Note that we care *just* about the error. We have already
   134  			// performed the hole poking and thus additional changes must be nil.
   135  			_, err = c.createPath(path, false, as)
   136  		}
   137  	}
   138  	return changes, err
   139  }
   140  
   141  func (c *Change) ensureTarget(as *Assumptions) ([]*Change, error) {
   142  	var changes []*Change
   143  
   144  	kind := c.Entry.XSnapdKind()
   145  	path := c.Entry.Dir
   146  
   147  	// We use lstat to ensure that we don't follow a symlink in case one was
   148  	// set up by the snap. Note that at the time this is run, all the snap's
   149  	// processes are frozen but if the path is a directory controlled by the
   150  	// user (typically in /home) then we may still race with user processes
   151  	// that change it.
   152  	fi, err := osLstat(path)
   153  
   154  	if err == nil {
   155  		// If the element already exists we just need to ensure it is of
   156  		// the correct type. The desired type depends on the kind of entry
   157  		// we are working with.
   158  		switch kind {
   159  		case "":
   160  			if !fi.Mode().IsDir() {
   161  				err = fmt.Errorf("cannot use %q as mount point: not a directory", path)
   162  			}
   163  		case "file":
   164  			if !fi.Mode().IsRegular() {
   165  				err = fmt.Errorf("cannot use %q as mount point: not a regular file", path)
   166  			}
   167  		case "symlink":
   168  			if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
   169  				// Create path verifies the symlink or fails if it is not what we wanted.
   170  				_, err = c.createPath(path, false, as)
   171  			} else {
   172  				err = fmt.Errorf("cannot create symlink in %q: existing file in the way", path)
   173  			}
   174  		}
   175  	} else if os.IsNotExist(err) {
   176  		changes, err = c.createPath(path, true, as)
   177  	} else {
   178  		// If we cannot inspect the element let's just bail out.
   179  		err = fmt.Errorf("cannot inspect %q: %v", path, err)
   180  	}
   181  	return changes, err
   182  }
   183  
   184  func (c *Change) ensureSource(as *Assumptions) ([]*Change, error) {
   185  	var changes []*Change
   186  
   187  	// We only have to do ensure bind mount source exists.
   188  	// This also rules out symlinks.
   189  	flags, _ := osutil.MountOptsToCommonFlags(c.Entry.Options)
   190  	if flags&syscall.MS_BIND == 0 {
   191  		return nil, nil
   192  	}
   193  
   194  	kind := c.Entry.XSnapdKind()
   195  	path := c.Entry.Name
   196  	fi, err := osLstat(path)
   197  
   198  	if err == nil {
   199  		// If the element already exists we just need to ensure it is of
   200  		// the correct type. The desired type depends on the kind of entry
   201  		// we are working with.
   202  		switch kind {
   203  		case "":
   204  			if !fi.Mode().IsDir() {
   205  				err = fmt.Errorf("cannot use %q as bind-mount source: not a directory", path)
   206  			}
   207  		case "file":
   208  			if !fi.Mode().IsRegular() {
   209  				err = fmt.Errorf("cannot use %q as bind-mount source: not a regular file", path)
   210  			}
   211  		}
   212  	} else if os.IsNotExist(err) {
   213  		// NOTE: This createPath is using pokeHoles, to make read-only places
   214  		// writable, but only for layouts and not for other (typically content
   215  		// sharing) mount entries.
   216  		//
   217  		// This is done because the changes made with pokeHoles=true are only
   218  		// visible in this current mount namespace and are not generally
   219  		// visible from other snaps because they inhabit different namespaces.
   220  		//
   221  		// In other words, changes made here are only observable by the single
   222  		// snap they apply to. As such they are useless for content sharing but
   223  		// very much useful to layouts.
   224  		pokeHoles := c.Entry.XSnapdOrigin() == "layout"
   225  		changes, err = c.createPath(path, pokeHoles, as)
   226  	} else {
   227  		// If we cannot inspect the element let's just bail out.
   228  		err = fmt.Errorf("cannot inspect %q: %v", path, err)
   229  	}
   230  
   231  	return changes, err
   232  }
   233  
   234  // changePerformImpl is the real implementation of Change.Perform
   235  func changePerformImpl(c *Change, as *Assumptions) (changes []*Change, err error) {
   236  	if c.Action == Mount {
   237  		var changesSource, changesTarget []*Change
   238  		// We may be asked to bind mount a file, bind mount a directory, mount
   239  		// a filesystem over a directory, or create a symlink (which is abusing
   240  		// the "mount" concept slightly). That actual operation is performed in
   241  		// c.lowLevelPerform. Here we just set the stage to make that possible.
   242  		//
   243  		// As a result of this ensure call we may need to make the medium writable
   244  		// and that's why we may return more changes as a result of performing this
   245  		// one.
   246  		changesTarget, err = c.ensureTarget(as)
   247  		// NOTE: we are collecting changes even if things fail. This is so that
   248  		// upper layers can perform undo correctly.
   249  		changes = append(changes, changesTarget...)
   250  		if err != nil {
   251  			return changes, err
   252  		}
   253  
   254  		// At this time we can be sure that the target element (for files and
   255  		// directories) exists and is of the right type or that it (for
   256  		// symlinks) doesn't exist but the parent directory does.
   257  		// This property holds as long as we don't interact with locations that
   258  		// are under the control of regular (non-snap) processes that are not
   259  		// suspended and may be racing with us.
   260  		changesSource, err = c.ensureSource(as)
   261  		// NOTE: we are collecting changes even if things fail. This is so that
   262  		// upper layers can perform undo correctly.
   263  		changes = append(changes, changesSource...)
   264  		if err != nil {
   265  			return changes, err
   266  		}
   267  	}
   268  
   269  	// Perform the underlying mount / unmount / unlink call.
   270  	err = c.lowLevelPerform(as)
   271  	return changes, err
   272  }
   273  
   274  func init() {
   275  	changePerform = changePerformImpl
   276  }
   277  
   278  // Perform executes the desired mount or unmount change using system calls.
   279  // Filesystems that depend on helper programs or multiple independent calls to
   280  // the kernel (--make-shared, for example) are unsupported.
   281  //
   282  // Perform may synthesize *additional* changes that were necessary to perform
   283  // this change (such as mounted tmpfs or overlayfs).
   284  func (c *Change) Perform(as *Assumptions) ([]*Change, error) {
   285  	return changePerform(c, as)
   286  }
   287  
   288  // lowLevelPerform is simple bridge from Change to mount / unmount syscall.
   289  func (c *Change) lowLevelPerform(as *Assumptions) error {
   290  	var err error
   291  	switch c.Action {
   292  	case Mount:
   293  		kind := c.Entry.XSnapdKind()
   294  		switch kind {
   295  		case "symlink":
   296  			// symlinks are handled in createInode directly, nothing to do here.
   297  		case "", "file":
   298  			flags, unparsed := osutil.MountOptsToCommonFlags(c.Entry.Options)
   299  			// Split the mount flags from the event propagation changes.
   300  			// Those have to be applied separately.
   301  			const propagationMask = syscall.MS_SHARED | syscall.MS_SLAVE | syscall.MS_PRIVATE | syscall.MS_UNBINDABLE
   302  			maskedFlagsRecursive := flags & syscall.MS_REC
   303  			maskedFlagsPropagation := flags & propagationMask
   304  			maskedFlagsNotPropagationNotRecursive := flags & ^(propagationMask | syscall.MS_REC)
   305  
   306  			var flagsForMount uintptr
   307  			if flags&syscall.MS_BIND == syscall.MS_BIND {
   308  				// bind / rbind mount
   309  				flagsForMount = uintptr(maskedFlagsNotPropagationNotRecursive | maskedFlagsRecursive)
   310  				err = BindMount(c.Entry.Name, c.Entry.Dir, uint(flagsForMount))
   311  			} else {
   312  				// normal mount, not bind / rbind, not propagation change
   313  				flagsForMount = uintptr(maskedFlagsNotPropagationNotRecursive)
   314  				err = sysMount(c.Entry.Name, c.Entry.Dir, c.Entry.Type, uintptr(flagsForMount), strings.Join(unparsed, ","))
   315  			}
   316  			mountOpts, unknownFlags := mount.MountFlagsToOpts(int(flagsForMount))
   317  			if unknownFlags != 0 {
   318  				mountOpts = append(mountOpts, fmt.Sprintf("%#x", unknownFlags))
   319  			}
   320  			logger.Debugf("mount name:%q dir:%q type:%q opts:%s unparsed:%q (error: %v)",
   321  				c.Entry.Name, c.Entry.Dir, c.Entry.Type, strings.Join(mountOpts, "|"), strings.Join(unparsed, ","), err)
   322  			if err == nil && maskedFlagsPropagation != 0 {
   323  				// now change mount propagation (shared/rshared, private/rprivate,
   324  				// slave/rslave, unbindable/runbindable).
   325  				flagsForMount := uintptr(maskedFlagsPropagation | maskedFlagsRecursive)
   326  				mountOpts, unknownFlags := mount.MountFlagsToOpts(int(flagsForMount))
   327  				if unknownFlags != 0 {
   328  					mountOpts = append(mountOpts, fmt.Sprintf("%#x", unknownFlags))
   329  				}
   330  				err = sysMount("none", c.Entry.Dir, "", flagsForMount, "")
   331  				logger.Debugf("mount name:%q dir:%q type:%q opts:%s unparsed:%q (error: %v)",
   332  					"none", c.Entry.Dir, "", strings.Join(mountOpts, "|"), strings.Join(unparsed, ","), err)
   333  			}
   334  			if err == nil {
   335  				as.AddChange(c)
   336  			}
   337  		}
   338  		return err
   339  	case Unmount:
   340  		kind := c.Entry.XSnapdKind()
   341  		switch kind {
   342  		case "symlink":
   343  			err = osRemove(c.Entry.Dir)
   344  			logger.Debugf("remove %q (error: %v)", c.Entry.Dir, err)
   345  		case "", "file":
   346  			// Unmount and remount operations can fail with EINVAL if the given
   347  			// mount does not exist; since here we only care about the
   348  			// resulting configuration, let's not treat such situations as
   349  			// errors.
   350  			clearMissingMountError := func(err error) error {
   351  				if err == syscall.EINVAL {
   352  					// We attempted to unmount but got an EINVAL, one of the
   353  					// possibilities and the only one unless we provided wrong
   354  					// flags, is that the mount no longer exists.
   355  					//
   356  					// We can verify that now by scanning mountinfo:
   357  					entries, _ := osutil.LoadMountInfo()
   358  					for _, entry := range entries {
   359  						if entry.MountDir == c.Entry.Dir {
   360  							// Mount point still exists, EINVAL was unexpected.
   361  							return err
   362  						}
   363  					}
   364  					// We didn't find a mount point at the location we tried to
   365  					// unmount. The EINVAL we observed indicates that the mount
   366  					// profile no longer agrees with reality. The mount point
   367  					// no longer exists. As such, consume the error and carry on.
   368  					logger.Debugf("ignoring EINVAL from unmount, %q is not mounted", c.Entry.Dir)
   369  					err = nil
   370  				}
   371  				return err
   372  			}
   373  			// Detach the mount point instead of unmounting it if requested.
   374  			flags := umountNoFollow
   375  			if c.Entry.XSnapdDetach() {
   376  				flags |= syscall.MNT_DETACH
   377  				// If we are detaching something then before performing the actual detach
   378  				// switch the entire hierarchy to private event propagation (that is,
   379  				// none). This works around a bit of peculiar kernel behavior when the
   380  				// kernel reports EBUSY during a detach operation, because the changes
   381  				// propagate in a way that conflicts with itself. This is also documented
   382  				// in umount(2).
   383  				err = sysMount("none", c.Entry.Dir, "", syscall.MS_REC|syscall.MS_PRIVATE, "")
   384  				logger.Debugf("mount --make-rprivate %q (error: %v)", c.Entry.Dir, err)
   385  				err = clearMissingMountError(err)
   386  			}
   387  
   388  			// Perform the raw unmount operation.
   389  			if err == nil {
   390  				err = sysUnmount(c.Entry.Dir, flags)
   391  				umountOpts, unknownFlags := mount.UnmountFlagsToOpts(flags)
   392  				if unknownFlags != 0 {
   393  					umountOpts = append(umountOpts, fmt.Sprintf("%#x", unknownFlags))
   394  				}
   395  				logger.Debugf("umount %q %s (error: %v)", c.Entry.Dir, strings.Join(umountOpts, "|"), err)
   396  				err = clearMissingMountError(err)
   397  				if err != nil {
   398  					return err
   399  				}
   400  			}
   401  			if err == nil {
   402  				as.AddChange(c)
   403  			}
   404  
   405  			// Open a path of the file we are considering the removal of.
   406  			path := c.Entry.Dir
   407  			var fd int
   408  			fd, err = OpenPath(path)
   409  			// If the place does not exist anymore, we are done.
   410  			if os.IsNotExist(err) {
   411  				return nil
   412  			}
   413  			if err != nil {
   414  				return err
   415  			}
   416  			defer sysClose(fd)
   417  
   418  			// Don't attempt to remove anything from squashfs.
   419  			// Note that this is not a perfect check and we also handle EROFS below.
   420  			var statfsBuf syscall.Statfs_t
   421  			err = sysFstatfs(fd, &statfsBuf)
   422  			if err != nil {
   423  				return err
   424  			}
   425  			if statfsBuf.Type == SquashfsMagic {
   426  				return nil
   427  			}
   428  
   429  			if kind == "file" {
   430  				// Don't attempt to remove non-empty files since they cannot be
   431  				// the placeholders we created.
   432  				var statBuf syscall.Stat_t
   433  				err = sysFstat(fd, &statBuf)
   434  				if err != nil {
   435  					return err
   436  				}
   437  				if statBuf.Size != 0 {
   438  					return nil
   439  				}
   440  			}
   441  
   442  			// Remove the file or directory while using the full path. There's
   443  			// no way to avoid a race here since there's no way to unlink a
   444  			// file solely by file descriptor.
   445  			err = osRemove(path)
   446  			logger.Debugf("remove %q (error: %v)", path, err)
   447  			// Unpack the low-level error that osRemove wraps into PathError.
   448  			if packed, ok := err.(*os.PathError); ok {
   449  				err = packed.Err
   450  			}
   451  			if err == syscall.EROFS {
   452  				// If the underlying medium is read-only then ignore the error.
   453  				// Instead of checking up front we just try to remove because
   454  				// of https://bugs.launchpad.net/snapd/+bug/1867752 which showed us
   455  				// two important properties:
   456  				// 1) inside containers we cannot detect squashfs reliably and
   457  				//    will always see FUSE instead. The problem is that there's no
   458  				//    indication as to what is really mounted via statfs(2) and
   459  				//    we would have to deduce that from mountinfo, trusting
   460  				//    that fuse.<name> is not spoofed (as in, the name is not
   461  				//    spoofed).
   462  				// 2) rmdir of a bind mount (from a normal writable filesystem like ext4)
   463  				//    over a read-only filesystem also yields EROFS without any indication
   464  				//    that this is to be expected.
   465  				logger.Debugf("cannot remove a mount point on read-only filesystem %q", path)
   466  				return nil
   467  			}
   468  			if err == syscall.EBUSY {
   469  				// It's still unclear how this can happen. For the time being
   470  				// let the operation succeed and log the event.
   471  				logger.Noticef("cannot remove mount point, got EBUSY: %q", path)
   472  				if isMount, err := osutil.IsMounted(path); isMount {
   473  					mounts, _ := osutil.LoadMountInfo()
   474  					logger.Noticef("%q is still a mount point:\n%s", path, mounts)
   475  				} else if err != nil {
   476  					logger.Noticef("cannot read mountinfo: %v", err)
   477  				}
   478  				return nil
   479  			}
   480  			// If we were removing a directory but it was not empty then just
   481  			// ignore the error. This is the equivalent of the non-empty file
   482  			// check we do above. See rmdir(2) for explanation why we accept
   483  			// more than one errno value.
   484  			if kind == "" && (err == syscall.ENOTEMPTY || err == syscall.EEXIST) {
   485  				return nil
   486  			}
   487  		}
   488  		return err
   489  	case Keep:
   490  		as.AddChange(c)
   491  		return nil
   492  	}
   493  	return fmt.Errorf("cannot process mount change: unknown action: %q", c.Action)
   494  }
   495  
   496  // neededChanges is the real implementation of NeededChanges
   497  func neededChanges(currentProfile, desiredProfile *osutil.MountProfile) []*Change {
   498  	// Copy both profiles as we will want to mutate them.
   499  	current := make([]osutil.MountEntry, len(currentProfile.Entries))
   500  	copy(current, currentProfile.Entries)
   501  	desired := make([]osutil.MountEntry, len(desiredProfile.Entries))
   502  	copy(desired, desiredProfile.Entries)
   503  
   504  	// Clean the directory part of both profiles. This is done so that we can
   505  	// easily test if a given directory is a subdirectory with
   506  	// strings.HasPrefix coupled with an extra slash character.
   507  	for i := range current {
   508  		current[i].Dir = filepath.Clean(current[i].Dir)
   509  	}
   510  	for i := range desired {
   511  		desired[i].Dir = filepath.Clean(desired[i].Dir)
   512  	}
   513  
   514  	// Make yet another copy of the current entries, to retain their original
   515  	// order (the "current" variable is going to be sorted soon); just using
   516  	// currentProfile.Entries is not reliable because it didn't undergo the
   517  	// cleanup of the Dir paths.
   518  	unsortedCurrent := make([]osutil.MountEntry, len(current))
   519  	copy(unsortedCurrent, current)
   520  
   521  	dumpMountEntries := func(entries []osutil.MountEntry, pfx string) {
   522  		logger.Debugf(pfx)
   523  		for _, en := range entries {
   524  			logger.Debugf("- %v", en)
   525  		}
   526  	}
   527  	dumpMountEntries(current, "current mount entries")
   528  	// Sort only the desired lists by directory name with implicit trailing
   529  	// slash and the mount kind.
   530  	// Note that the current profile is a log of what was applied and should
   531  	// not be sorted at all.
   532  	sort.Sort(byOriginAndMountPoint(desired))
   533  	dumpMountEntries(desired, "desired mount entries (sorted)")
   534  
   535  	// Construct a desired directory map.
   536  	desiredMap := make(map[string]*osutil.MountEntry)
   537  	for i := range desired {
   538  		desiredMap[desired[i].Dir] = &desired[i]
   539  	}
   540  
   541  	// Indexed by mount point path.
   542  	reuse := make(map[string]bool)
   543  	// Indexed by entry ID
   544  	desiredIDs := make(map[string]bool)
   545  	var skipDir string
   546  
   547  	// Collect the IDs of desired changes.
   548  	// We need that below to keep implicit changes from the current profile.
   549  	for i := range desired {
   550  		desiredIDs[desired[i].XSnapdEntryID()] = true
   551  	}
   552  
   553  	// Compute reusable entries: those which are equal in current and desired and which
   554  	// are not prefixed by another entry that changed.
   555  	// sort them first
   556  	sort.Sort(byOvernameAndMountPoint(current))
   557  	for i := range current {
   558  		dir := current[i].Dir
   559  		if skipDir != "" && strings.HasPrefix(dir, skipDir) {
   560  			logger.Debugf("skipping entry %q", current[i])
   561  			continue
   562  		}
   563  		skipDir = "" // reset skip prefix as it no longer applies
   564  
   565  		if current[i].XSnapdOrigin() == "rootfs" {
   566  			// This is the rootfs setup by snap-confine, we should not touch it
   567  			logger.Debugf("reusing rootfs")
   568  			reuse[dir] = true
   569  			continue
   570  		}
   571  
   572  		// Reuse synthetic entries if their needed-by entry is desired.
   573  		// Synthetic entries cannot exist on their own and always couple to a
   574  		// non-synthetic entry.
   575  
   576  		// NOTE: Synthetic changes have a special purpose.
   577  		//
   578  		// They are a "shadow" of mount events that occurred to allow one of
   579  		// the desired mount entries to be possible. The changes have only one
   580  		// goal: tell snap-update-ns how those mount events can be undone in
   581  		// case they are no longer needed. The actual changes may have been
   582  		// different and may have involved steps not represented as synthetic
   583  		// mount entires as long as those synthetic entries can be undone to
   584  		// reverse the effect. In reality each non-tmpfs synthetic entry was
   585  		// constructed using a temporary bind mount that contained the original
   586  		// mount entries of a directory that was hidden with a tmpfs, but this
   587  		// fact was lost.
   588  		if current[i].XSnapdSynthetic() && desiredIDs[current[i].XSnapdNeededBy()] {
   589  			logger.Debugf("reusing synthetic entry %q", current[i])
   590  			reuse[dir] = true
   591  			continue
   592  		}
   593  
   594  		// Reuse entries that are desired and identical in the current profile.
   595  		if entry, ok := desiredMap[dir]; ok && current[i].Equal(entry) {
   596  			logger.Debugf("reusing unchanged entry %q", current[i])
   597  			reuse[dir] = true
   598  			continue
   599  		}
   600  
   601  		skipDir = strings.TrimSuffix(dir, "/") + "/"
   602  	}
   603  
   604  	logger.Debugf("desiredIDs: %v", desiredIDs)
   605  	logger.Debugf("reuse: %v", reuse)
   606  
   607  	// We are now ready to compute the necessary mount changes.
   608  	var changes []*Change
   609  
   610  	// Unmount entries not reused in reverse to handle children before their parent.
   611  	unmountOrder := unsortedCurrent
   612  	for i := len(unmountOrder) - 1; i >= 0; i-- {
   613  		if reuse[unmountOrder[i].Dir] {
   614  			changes = append(changes, &Change{Action: Keep, Entry: unmountOrder[i]})
   615  		} else {
   616  			var entry osutil.MountEntry = unmountOrder[i]
   617  			entry.Options = append([]string(nil), entry.Options...)
   618  			// If the mount entry can potentially host nested mount points then detach
   619  			// rather than unmount, since detach will always succeed.
   620  			shouldDetach := entry.Type == "tmpfs" || entry.OptBool("bind") || entry.OptBool("rbind")
   621  			if shouldDetach && !entry.XSnapdDetach() {
   622  				entry.Options = append(entry.Options, osutil.XSnapdDetach())
   623  			}
   624  			changes = append(changes, &Change{Action: Unmount, Entry: entry})
   625  		}
   626  	}
   627  
   628  	var desiredNotReused []osutil.MountEntry
   629  	for _, entry := range desired {
   630  		if !reuse[entry.Dir] {
   631  			desiredNotReused = append(desiredNotReused, entry)
   632  		}
   633  	}
   634  
   635  	// Mount desired entries not reused, ordering by the mimic directories they
   636  	// need created
   637  	// We proceeds in three steps:
   638  	// 1. Perform the mounts for the "overname" entries
   639  	// 2. Perform the mounts for the entries which need a mimic
   640  	// 3. Perform all the remaining desired mounts
   641  
   642  	var newDesiredEntries []osutil.MountEntry
   643  	var newIndependentDesiredEntries []osutil.MountEntry
   644  	// Indexed by mount point path.
   645  	addedDesiredEntries := make(map[string]bool)
   646  	// This function is idempotent, it won't add the same entry twice
   647  	addDesiredEntry := func(entry osutil.MountEntry) {
   648  		if !addedDesiredEntries[entry.Dir] {
   649  			logger.Debugf("adding entry: %s", entry)
   650  			newDesiredEntries = append(newDesiredEntries, entry)
   651  			addedDesiredEntries[entry.Dir] = true
   652  		}
   653  	}
   654  	addIndependentDesiredEntry := func(entry osutil.MountEntry) {
   655  		if !addedDesiredEntries[entry.Dir] {
   656  			logger.Debugf("adding independent entry: %s", entry)
   657  			newIndependentDesiredEntries = append(newIndependentDesiredEntries, entry)
   658  			addedDesiredEntries[entry.Dir] = true
   659  		}
   660  	}
   661  
   662  	logger.Debugf("processing mount entries")
   663  	// Create a map of the target directories (mimics) needed for the visited
   664  	// entries
   665  	affectedTargetCreationDirs := map[string][]osutil.MountEntry{}
   666  	for _, entry := range desiredNotReused {
   667  		if entry.XSnapdOrigin() == "overname" {
   668  			addIndependentDesiredEntry(entry)
   669  		}
   670  
   671  		// collect all entries, so that we know what mimics are needed
   672  		parentTargetDir := filepath.Dir(entry.Dir)
   673  		affectedTargetCreationDirs[parentTargetDir] = append(affectedTargetCreationDirs[parentTargetDir], entry)
   674  	}
   675  
   676  	if len(affectedTargetCreationDirs) != 0 {
   677  		entriesForMimicDir := map[string][]osutil.MountEntry{}
   678  		for parentTargetDir, entriesNeedingDir := range affectedTargetCreationDirs {
   679  			// First check if any of the mount entries for the changes will potentially
   680  			// result in creating a mimic. Note that to actually know if a given mount
   681  			// entry will require a mimic when the mount target doesn't exist, we would
   682  			// have to try and create a file/directory/symlink at the desired target,
   683  			// however that would be a destructive change which is not appropriate here
   684  			// (that is done in ChangePerform() instead), but for our purposes of sorting
   685  			// mount entries it is sufficient to use this assumption.
   686  			// We check if a mount entry would result in a potential mimic by just
   687  			// checking if the file/dir/symlink that is the target of the mount exists
   688  			// already in the form we need to to bind mount on top of it. If it
   689  			// doesn't then we need to create a mimic and so we then go looking for
   690  			// where to create the mimic.
   691  			for _, entry := range entriesNeedingDir {
   692  				exists := true
   693  				switch entry.XSnapdKind() {
   694  				case "":
   695  					exists = osutilIsDirectory(entry.Dir)
   696  				case "file":
   697  					exists = osutil.FileExists(entry.Dir)
   698  				case "symlink":
   699  					exists = osutil.IsSymlink(entry.Dir)
   700  				}
   701  
   702  				// if it doesn't exist we may need a mimic
   703  				if !exists {
   704  					neededMimicDir := findFirstRootDirectoryThatExists(parentTargetDir)
   705  					entriesForMimicDir[neededMimicDir] = append(entriesForMimicDir[neededMimicDir], entry)
   706  					logger.Debugf("entry that requires %q: %v", neededMimicDir, entry)
   707  				} else {
   708  					// entry is independent
   709  					addIndependentDesiredEntry(entry)
   710  				}
   711  			}
   712  		}
   713  
   714  		// sort the mimic creation dirs to get the correct ordering of mimics to
   715  		// create dirs in: the sorting algorithm places parent directories
   716  		// before children.
   717  		allMimicCreationDirs := []string{}
   718  		for mimicDir := range entriesForMimicDir {
   719  			allMimicCreationDirs = append(allMimicCreationDirs, mimicDir)
   720  		}
   721  
   722  		sort.Strings(allMimicCreationDirs)
   723  
   724  		logger.Debugf("all mimics:")
   725  		for _, mimicDir := range allMimicCreationDirs {
   726  			logger.Debugf("- %v", mimicDir)
   727  		}
   728  
   729  		for _, mimicDir := range allMimicCreationDirs {
   730  			// make sure to sort the entries for each mimic dir in a consistent
   731  			// order
   732  			entries := entriesForMimicDir[mimicDir]
   733  			sort.Sort(byOriginAndMountPoint(entries))
   734  			for _, entry := range entries {
   735  				addDesiredEntry(entry)
   736  			}
   737  		}
   738  	}
   739  
   740  	sort.Sort(byOriginAndMountPoint(newIndependentDesiredEntries))
   741  	allEntries := append(newIndependentDesiredEntries, newDesiredEntries...)
   742  	dumpMountEntries(allEntries, "mount entries ordered as they will be applied")
   743  	for _, entry := range allEntries {
   744  		changes = append(changes, &Change{Action: Mount, Entry: entry})
   745  	}
   746  
   747  	return changes
   748  }
   749  
   750  func findFirstRootDirectoryThatExists(desiredParentDir string) string {
   751  	// trivial case - the dir already exists
   752  	if osutilIsDirectory(desiredParentDir) {
   753  		return desiredParentDir
   754  	}
   755  
   756  	// otherwise we need to recurse up to find the first dir that exists where
   757  	// we would place the mimic - note that this cannot recurse infinitely,
   758  	// since at some point we will reach "/" which always exists
   759  	return findFirstRootDirectoryThatExists(filepath.Dir(desiredParentDir))
   760  }
   761  
   762  // NeededChanges computes the changes required to change current to desired mount entries.
   763  //
   764  // A diff-like operation on the mount profile is computed. Some of the mount
   765  // entries from the current profile may be reused.
   766  var NeededChanges = func(current, desired *osutil.MountProfile) []*Change {
   767  	return neededChanges(current, desired)
   768  }