github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/gadget/mountedfilesystem.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019-2020 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 gadget
    21  
    22  import (
    23  	"bytes"
    24  	"crypto"
    25  	_ "crypto/sha1"
    26  	"fmt"
    27  	"io"
    28  	"io/ioutil"
    29  	"os"
    30  	"path/filepath"
    31  	"sort"
    32  	"strings"
    33  
    34  	"github.com/snapcore/snapd/logger"
    35  	"github.com/snapcore/snapd/osutil"
    36  	"github.com/snapcore/snapd/strutil"
    37  )
    38  
    39  func checkSourceIsDir(src string) error {
    40  	if !osutil.IsDirectory(src) {
    41  		if strings.HasSuffix(src, "/") {
    42  			return fmt.Errorf("cannot specify trailing / for a source which is not a directory")
    43  		}
    44  		return fmt.Errorf("source is not a directory")
    45  	}
    46  	return nil
    47  }
    48  
    49  func checkContent(content *VolumeContent) error {
    50  	if content.Source == "" {
    51  		return fmt.Errorf("internal error: source cannot be unset")
    52  	}
    53  	if content.Target == "" {
    54  		return fmt.Errorf("internal error: target cannot be unset")
    55  	}
    56  	return nil
    57  }
    58  
    59  func observe(observer ContentObserver, op ContentOperation, ps *LaidOutStructure, root, dst string, data *ContentChange) (ContentChangeAction, error) {
    60  	if observer == nil {
    61  		return ChangeApply, nil
    62  	}
    63  	relativeTarget := dst
    64  	if strings.HasPrefix(dst, root) {
    65  		// target path isn't really relative, make it so now
    66  		relative, err := filepath.Rel(root, dst)
    67  		if err != nil {
    68  			return ChangeAbort, err
    69  		}
    70  		relativeTarget = relative
    71  	}
    72  	return observer.Observe(op, ps, root, relativeTarget, data)
    73  }
    74  
    75  // TODO: MountedFilesystemWriter should not be exported
    76  
    77  // MountedFilesystemWriter assists in writing contents of a structure to a
    78  // mounted filesystem.
    79  type MountedFilesystemWriter struct {
    80  	contentDir string
    81  	ps         *LaidOutStructure
    82  	observer   ContentObserver
    83  }
    84  
    85  // NewMountedFilesystemWriter returns a writer capable of writing provided
    86  // structure, with content of the structure stored in the given root directory.
    87  func NewMountedFilesystemWriter(contentDir string, ps *LaidOutStructure, observer ContentObserver) (*MountedFilesystemWriter, error) {
    88  	if ps == nil {
    89  		return nil, fmt.Errorf("internal error: *LaidOutStructure is nil")
    90  	}
    91  	if !ps.HasFilesystem() {
    92  		return nil, fmt.Errorf("structure %v has no filesystem", ps)
    93  	}
    94  	if contentDir == "" {
    95  		return nil, fmt.Errorf("internal error: gadget content directory cannot be unset")
    96  	}
    97  	fw := &MountedFilesystemWriter{
    98  		contentDir: contentDir,
    99  		ps:         ps,
   100  		observer:   observer,
   101  	}
   102  	return fw, nil
   103  }
   104  
   105  func mapPreserve(dstDir string, preserve []string) ([]string, error) {
   106  	preserveInDst := make([]string, len(preserve))
   107  	for i, p := range preserve {
   108  		inDst := filepath.Join(dstDir, p)
   109  
   110  		if osutil.IsDirectory(inDst) {
   111  			return nil, fmt.Errorf("preserved entry %q cannot be a directory", p)
   112  		}
   113  
   114  		preserveInDst[i] = inDst
   115  	}
   116  	sort.Strings(preserveInDst)
   117  
   118  	return preserveInDst, nil
   119  }
   120  
   121  // Write writes structure data into provided directory. All existing files are
   122  // overwritten, unless their paths, relative to target directory, are listed in
   123  // the preserve list. Permission bits and ownership of updated entries is not
   124  // preserved.
   125  func (m *MountedFilesystemWriter) Write(whereDir string, preserve []string) error {
   126  	if whereDir == "" {
   127  		return fmt.Errorf("internal error: destination directory cannot be unset")
   128  	}
   129  
   130  	// TODO:UC20: preserve managed boot assets
   131  	preserveInDst, err := mapPreserve(whereDir, preserve)
   132  	if err != nil {
   133  		return fmt.Errorf("cannot map preserve entries for destination %q: %v", whereDir, err)
   134  	}
   135  
   136  	for _, c := range m.ps.Content {
   137  		if err := m.writeVolumeContent(whereDir, &c, preserveInDst); err != nil {
   138  			return fmt.Errorf("cannot write filesystem content of %s: %v", c, err)
   139  		}
   140  	}
   141  	return nil
   142  }
   143  
   144  // writeDirectory copies the source directory, or its contents under target
   145  // location dst. Follows rsync like semantics, that is:
   146  //   /foo/ -> /bar - writes contents of foo under /bar
   147  //   /foo  -> /bar - writes foo and its subtree under /bar
   148  func (m *MountedFilesystemWriter) writeDirectory(volumeRoot, src, dst string, preserveInDst []string) error {
   149  	hasDirSourceSlash := strings.HasSuffix(src, "/")
   150  
   151  	if err := checkSourceIsDir(src); err != nil {
   152  		return err
   153  	}
   154  
   155  	if !hasDirSourceSlash {
   156  		// /foo -> /bar (write foo and subtree)
   157  		dst = filepath.Join(dst, filepath.Base(src))
   158  	}
   159  
   160  	fis, err := ioutil.ReadDir(src)
   161  	if err != nil {
   162  		return fmt.Errorf("cannot list directory entries: %v", err)
   163  	}
   164  
   165  	for _, fi := range fis {
   166  		pSrc := filepath.Join(src, fi.Name())
   167  		pDst := filepath.Join(dst, fi.Name())
   168  
   169  		write := m.observedWriteFileOrSymlink
   170  		if fi.IsDir() {
   171  			if err := os.MkdirAll(pDst, 0755); err != nil {
   172  				return fmt.Errorf("cannot create directory prefix: %v", err)
   173  			}
   174  
   175  			write = m.writeDirectory
   176  			pSrc += "/"
   177  		}
   178  		if err := write(volumeRoot, pSrc, pDst, preserveInDst); err != nil {
   179  			return err
   180  		}
   181  	}
   182  
   183  	return nil
   184  }
   185  
   186  func (m *MountedFilesystemWriter) observedWriteFileOrSymlink(volumeRoot, src, dst string, preserveInDst []string) error {
   187  	if strings.HasSuffix(dst, "/") {
   188  		// write to directory
   189  		dst = filepath.Join(dst, filepath.Base(src))
   190  	}
   191  
   192  	data := &ContentChange{
   193  		// we are writing a new thing
   194  		Before: "",
   195  		// with content in this file
   196  		After: src,
   197  	}
   198  	act, err := observe(m.observer, ContentWrite, m.ps, volumeRoot, dst, data)
   199  	if err != nil {
   200  		return fmt.Errorf("cannot observe file write: %v", err)
   201  	}
   202  	if act == ChangeIgnore {
   203  		return nil
   204  	}
   205  	return writeFileOrSymlink(src, dst, preserveInDst)
   206  }
   207  
   208  // writeFileOrSymlink writes the source file or a symlink at given location or
   209  // under given directory. Follows rsync like semantics, that is:
   210  //   /foo -> /bar/ - writes foo as /bar/foo
   211  //   /foo  -> /bar - writes foo as /bar
   212  // The destination location is overwritten.
   213  func writeFileOrSymlink(src, dst string, preserveInDst []string) error {
   214  	if strings.HasSuffix(dst, "/") {
   215  		// write to directory
   216  		dst = filepath.Join(dst, filepath.Base(src))
   217  	}
   218  
   219  	if osutil.FileExists(dst) && strutil.SortedListContains(preserveInDst, dst) {
   220  		// entry shall be preserved
   221  		return nil
   222  	}
   223  
   224  	if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
   225  		return fmt.Errorf("cannot create prefix directory: %v", err)
   226  	}
   227  
   228  	if osutil.IsSymlink(src) {
   229  		// recreate the symlinks as they are
   230  		to, err := os.Readlink(src)
   231  		if err != nil {
   232  			return fmt.Errorf("cannot read symlink: %v", err)
   233  		}
   234  		if err := os.Symlink(to, dst); err != nil {
   235  			return fmt.Errorf("cannot write a symlink: %v", err)
   236  		}
   237  	} else {
   238  		// TODO try to preserve ownership and permission bits
   239  
   240  		// do not follow sylimks, dst is a reflection of the src which
   241  		// is a file
   242  		if err := osutil.AtomicWriteFileCopy(dst, src, 0); err != nil {
   243  			return fmt.Errorf("cannot copy %s: %v", src, err)
   244  		}
   245  	}
   246  	return nil
   247  }
   248  
   249  func (m *MountedFilesystemWriter) writeVolumeContent(volumeRoot string, content *VolumeContent, preserveInDst []string) error {
   250  	if err := checkContent(content); err != nil {
   251  		return err
   252  	}
   253  	realSource := filepath.Join(m.contentDir, content.Source)
   254  	realTarget := filepath.Join(volumeRoot, content.Target)
   255  
   256  	// filepath trims the trailing /, restore if needed
   257  	if strings.HasSuffix(content.Target, "/") {
   258  		realTarget += "/"
   259  	}
   260  	if strings.HasSuffix(content.Source, "/") {
   261  		realSource += "/"
   262  	}
   263  
   264  	if osutil.IsDirectory(realSource) || strings.HasSuffix(content.Source, "/") {
   265  		// write a directory
   266  		return m.writeDirectory(volumeRoot, realSource, realTarget, preserveInDst)
   267  	} else {
   268  		// write a file
   269  		return m.observedWriteFileOrSymlink(volumeRoot, realSource, realTarget, preserveInDst)
   270  	}
   271  }
   272  
   273  func newStampFile(stamp string) (*osutil.AtomicFile, error) {
   274  	if err := os.MkdirAll(filepath.Dir(stamp), 0755); err != nil {
   275  		return nil, fmt.Errorf("cannot create stamp file prefix: %v", err)
   276  	}
   277  	return osutil.NewAtomicFile(stamp, 0644, 0, osutil.NoChown, osutil.NoChown)
   278  }
   279  
   280  func makeStamp(stamp string) error {
   281  	f, err := newStampFile(stamp)
   282  	if err != nil {
   283  		return err
   284  	}
   285  	return f.Commit()
   286  }
   287  
   288  type mountLookupFunc func(ps *LaidOutStructure) (string, error)
   289  
   290  // mountedFilesystemUpdater assists in applying updates to a mounted filesystem.
   291  //
   292  // The update process is composed of 2 main passes, and an optional rollback:
   293  //
   294  // 1) backup, where update data and current data is analyzed to identify
   295  // identical content, stamp files are created for entries that are to be
   296  // preserved, modified or otherwise touched by the update, that is, for existing
   297  // files that would be created/overwritten, ones that are explicitly listed as
   298  // preserved, or directories to be written to
   299  //
   300  // 2) update, where update data is written to the target location
   301  //
   302  // 3) rollback (optional), where update data is rolled back and replaced with
   303  // backup copies of files, newly created directories are removed
   304  type mountedFilesystemUpdater struct {
   305  	*MountedFilesystemWriter
   306  	backupDir      string
   307  	mountPoint     string
   308  	updateObserver ContentObserver
   309  }
   310  
   311  // newMountedFilesystemUpdater returns an updater for given filesystem
   312  // structure, with structure content coming from provided root directory. The
   313  // mount is located by calling a mount lookup helper. The backup directory
   314  // contains backup state information for use during rollback.
   315  func newMountedFilesystemUpdater(rootDir string, ps *LaidOutStructure, backupDir string, mountLookup mountLookupFunc, observer ContentObserver) (*mountedFilesystemUpdater, error) {
   316  	// avoid passing observer, writes will not be observed
   317  	fw, err := NewMountedFilesystemWriter(rootDir, ps, nil)
   318  	if err != nil {
   319  		return nil, err
   320  	}
   321  	if mountLookup == nil {
   322  		return nil, fmt.Errorf("internal error: mount lookup helper must be provided")
   323  	}
   324  	if backupDir == "" {
   325  		return nil, fmt.Errorf("internal error: backup directory must not be unset")
   326  	}
   327  	mount, err := mountLookup(ps)
   328  	if err != nil {
   329  		return nil, fmt.Errorf("cannot find mount location of structure %v: %v", ps, err)
   330  	}
   331  
   332  	fu := &mountedFilesystemUpdater{
   333  		MountedFilesystemWriter: fw,
   334  		backupDir:               backupDir,
   335  		mountPoint:              mount,
   336  		updateObserver:          observer,
   337  	}
   338  	return fu, nil
   339  }
   340  
   341  func fsStructBackupPath(backupDir string, ps *LaidOutStructure) string {
   342  	return filepath.Join(backupDir, fmt.Sprintf("struct-%v", ps.Index))
   343  }
   344  
   345  // entryDestPaths resolves destination and backup paths for given
   346  // source/target combination. Backup location is within provided
   347  // backup directory or empty if directory was not provided.
   348  func (f *mountedFilesystemUpdater) entryDestPaths(dstRoot, source, target, backupDir string) (dstPath, backupPath string) {
   349  	dstBasePath := target
   350  	if strings.HasSuffix(target, "/") {
   351  		// write to a directory
   352  		dstBasePath = filepath.Join(dstBasePath, filepath.Base(source))
   353  	}
   354  	dstPath = filepath.Join(dstRoot, dstBasePath)
   355  
   356  	if backupDir != "" {
   357  		backupPath = filepath.Join(backupDir, dstBasePath)
   358  	}
   359  
   360  	return dstPath, backupPath
   361  }
   362  
   363  // entrySourcePath returns the path of given source entry within the root
   364  // directory provided during initialization.
   365  func (f *mountedFilesystemUpdater) entrySourcePath(source string) string {
   366  	srcPath := filepath.Join(f.contentDir, source)
   367  
   368  	if strings.HasSuffix(source, "/") {
   369  		// restore trailing / if one was there
   370  		srcPath += "/"
   371  	}
   372  	return srcPath
   373  }
   374  
   375  // Update applies an update to a mounted filesystem. The caller must have
   376  // executed a Backup() before, to prepare a data set for rollback purpose.
   377  func (f *mountedFilesystemUpdater) Update() error {
   378  	preserveInDst, err := mapPreserve(f.mountPoint, f.ps.Update.Preserve)
   379  	if err != nil {
   380  		return fmt.Errorf("cannot map preserve entries for mount location %q: %v", f.mountPoint, err)
   381  	}
   382  
   383  	backupRoot := fsStructBackupPath(f.backupDir, f.ps)
   384  
   385  	skipped := 0
   386  	for _, c := range f.ps.Content {
   387  		if err := f.updateVolumeContent(f.mountPoint, &c, preserveInDst, backupRoot); err != nil {
   388  			if err == ErrNoUpdate {
   389  				skipped++
   390  				continue
   391  			}
   392  			return fmt.Errorf("cannot update content: %v", err)
   393  		}
   394  	}
   395  
   396  	if skipped == len(f.ps.Content) {
   397  		return ErrNoUpdate
   398  	}
   399  
   400  	return nil
   401  }
   402  
   403  func (f *mountedFilesystemUpdater) sourceDirectoryEntries(source string) ([]os.FileInfo, error) {
   404  	srcPath := f.entrySourcePath(source)
   405  
   406  	if err := checkSourceIsDir(srcPath); err != nil {
   407  		return nil, err
   408  	}
   409  
   410  	// TODO: enable support for symlinks when needed
   411  	if osutil.IsSymlink(srcPath) {
   412  		return nil, fmt.Errorf("source is a symbolic link")
   413  	}
   414  
   415  	return ioutil.ReadDir(srcPath)
   416  }
   417  
   418  // targetInSourceDir resolves the actual target for given source directory name
   419  // and target specification.
   420  //  source: /foo/bar/ target: /baz => /bar/    (contents of /foo/bar/ under /baz)
   421  //  source: /foo/bar  target: /baz => /bar/bar (directory /foo/bar under /baz, contents under /baz/bar)
   422  func targetForSourceDir(source, target string) string {
   423  	if strings.HasSuffix(source, "/") {
   424  		// contents of source directory land under target
   425  		return target
   426  	}
   427  	// source directory lands under target
   428  	return filepath.Join(target, filepath.Base(source))
   429  }
   430  
   431  func (f *mountedFilesystemUpdater) updateDirectory(dstRoot, source, target string, preserveInDst []string, backupDir string) error {
   432  	fis, err := f.sourceDirectoryEntries(source)
   433  	if err != nil {
   434  		return fmt.Errorf("cannot list source directory %q: %v", source, err)
   435  	}
   436  
   437  	target = targetForSourceDir(source, target)
   438  
   439  	// create current target directory if needed
   440  	if err := os.MkdirAll(filepath.Join(dstRoot, target), 0755); err != nil {
   441  		return fmt.Errorf("cannot write directory: %v", err)
   442  	}
   443  	// and write the content of source to target
   444  	skipped := 0
   445  	for _, fi := range fis {
   446  		pSrc := filepath.Join(source, fi.Name())
   447  		pDst := filepath.Join(target, fi.Name())
   448  
   449  		update := f.updateOrSkipFile
   450  		if fi.IsDir() {
   451  			// continue updating contents of the directory rather
   452  			// than the directory itself
   453  			pSrc += "/"
   454  			pDst += "/"
   455  			update = f.updateDirectory
   456  		}
   457  		if err := update(dstRoot, pSrc, pDst, preserveInDst, backupDir); err != nil {
   458  			if err == ErrNoUpdate {
   459  				skipped++
   460  				continue
   461  			}
   462  			return err
   463  		}
   464  	}
   465  
   466  	if skipped == len(fis) {
   467  		return ErrNoUpdate
   468  	}
   469  
   470  	return nil
   471  }
   472  
   473  func (f *mountedFilesystemUpdater) updateOrSkipFile(dstRoot, source, target string, preserveInDst []string, backupDir string) error {
   474  	srcPath := f.entrySourcePath(source)
   475  	dstPath, backupPath := f.entryDestPaths(dstRoot, source, target, backupDir)
   476  	backupName := backupPath + ".backup"
   477  	sameStamp := backupPath + ".same"
   478  	preserveStamp := backupPath + ".preserve"
   479  	ignoreStamp := backupPath + ".ignore"
   480  
   481  	// TODO: enable support for symlinks when needed
   482  	if osutil.IsSymlink(srcPath) {
   483  		return fmt.Errorf("cannot update file %s: symbolic links are not supported", source)
   484  	}
   485  
   486  	if osutil.FileExists(ignoreStamp) {
   487  		// explicitly ignored by request of the observer
   488  		return ErrNoUpdate
   489  	}
   490  
   491  	if osutil.FileExists(dstPath) {
   492  		if strutil.SortedListContains(preserveInDst, dstPath) || osutil.FileExists(preserveStamp) {
   493  			// file is to be preserved
   494  			return ErrNoUpdate
   495  		}
   496  		if osutil.FileExists(sameStamp) {
   497  			// file is the same as current copy
   498  			return ErrNoUpdate
   499  		}
   500  		if !osutil.FileExists(backupName) {
   501  			// not preserved & different than the update, error out
   502  			// as there is no backup
   503  			return fmt.Errorf("missing backup file %q for %v", backupName, target)
   504  		}
   505  	}
   506  
   507  	return writeFileOrSymlink(srcPath, dstPath, preserveInDst)
   508  }
   509  
   510  func (f *mountedFilesystemUpdater) updateVolumeContent(volumeRoot string, content *VolumeContent, preserveInDst []string, backupDir string) error {
   511  	if err := checkContent(content); err != nil {
   512  		return err
   513  	}
   514  
   515  	srcPath := f.entrySourcePath(content.Source)
   516  
   517  	if osutil.IsDirectory(srcPath) || strings.HasSuffix(content.Source, "/") {
   518  		return f.updateDirectory(volumeRoot, content.Source, content.Target, preserveInDst, backupDir)
   519  	} else {
   520  		return f.updateOrSkipFile(volumeRoot, content.Source, content.Target, preserveInDst, backupDir)
   521  	}
   522  }
   523  
   524  // Backup analyzes a mounted filesystem and prepares a rollback state should the
   525  // update be applied. The content of the filesystem is processed, files and
   526  // directories that would be modified by the update are backed up, while
   527  // identical/preserved files may be stamped to improve the later step of update
   528  // process.
   529  //
   530  // The backup directory structure mirrors the structure of destination
   531  // location. Given the following destination structure:
   532  //
   533  // foo
   534  // ├── a
   535  // ├── b
   536  // ├── bar
   537  // │   ├── baz
   538  // │   │   └── d
   539  // │   └── z
   540  // ├── c
   541  // └── d
   542  //
   543  // The structure of backup looks like this:
   544  //
   545  // foo-backup
   546  // ├── a.backup           <-- backup copy of ./a
   547  // ├── bar
   548  // │   ├── baz
   549  // │   │   └── d.backup   <-- backup copy of ./bar/baz/d
   550  // │   └── baz.backup     <-- stamp indicating ./bar/baz existed before the update
   551  // ├── bar.backup         <-- stamp indicating ./bar existed before the update
   552  // ├── b.same             <-- stamp indicating ./b is identical to the update data
   553  // ├── c.ignore           <-- stamp indicating change to ./c was requested to be ignored
   554  // └── d.preserve         <-- stamp indicating ./d is to be preserved
   555  //
   556  func (f *mountedFilesystemUpdater) Backup() error {
   557  	backupRoot := fsStructBackupPath(f.backupDir, f.ps)
   558  
   559  	if err := os.MkdirAll(backupRoot, 0755); err != nil {
   560  		return fmt.Errorf("cannot create backup directory: %v", err)
   561  	}
   562  
   563  	preserveInDst, err := mapPreserve(f.mountPoint, f.ps.Update.Preserve)
   564  	if err != nil {
   565  		return fmt.Errorf("cannot map preserve entries for mount location %q: %v", f.mountPoint, err)
   566  	}
   567  
   568  	for _, c := range f.ps.Content {
   569  		if err := f.backupVolumeContent(f.mountPoint, &c, preserveInDst, backupRoot); err != nil {
   570  			return fmt.Errorf("cannot backup content: %v", err)
   571  		}
   572  	}
   573  
   574  	return nil
   575  }
   576  
   577  func (f *mountedFilesystemUpdater) backupOrCheckpointDirectory(dstRoot, source, target string, preserveInDst []string, backupDir string) error {
   578  	fis, err := f.sourceDirectoryEntries(source)
   579  	if err != nil {
   580  		return fmt.Errorf("cannot backup directory %q: %v", source, err)
   581  	}
   582  
   583  	target = targetForSourceDir(source, target)
   584  
   585  	for _, fi := range fis {
   586  		pSrc := filepath.Join(source, fi.Name())
   587  		pDst := filepath.Join(target, fi.Name())
   588  
   589  		backup := f.observedBackupOrCheckpointFile
   590  		if fi.IsDir() {
   591  			// continue backing up the contents of the directory
   592  			// rather than the directory itself
   593  			pSrc += "/"
   594  			pDst += "/"
   595  			backup = f.backupOrCheckpointDirectory
   596  		}
   597  		if err := f.checkpointPrefix(dstRoot, pDst, backupDir); err != nil {
   598  			return err
   599  		}
   600  		if err := backup(dstRoot, pSrc, pDst, preserveInDst, backupDir); err != nil {
   601  			return err
   602  		}
   603  	}
   604  
   605  	return nil
   606  }
   607  
   608  // checkpointPrefix creates stamps for each part of the destination prefix that exists
   609  func (f *mountedFilesystemUpdater) checkpointPrefix(dstRoot, target string, backupDir string) error {
   610  	// check how much of the prefix needs to be created
   611  	for prefix := filepath.Dir(target); prefix != "." && prefix != "/"; prefix = filepath.Dir(prefix) {
   612  		prefixDst, prefixBackupBase := f.entryDestPaths(dstRoot, "", prefix, backupDir)
   613  
   614  		// TODO: enable support for symlinks when needed
   615  		if osutil.IsSymlink(prefixDst) {
   616  			return fmt.Errorf("cannot create a checkpoint for directory %v: symbolic links are not supported", prefix)
   617  		}
   618  
   619  		prefixBackupName := prefixBackupBase + ".backup"
   620  		if osutil.FileExists(prefixBackupName) {
   621  			continue
   622  		}
   623  		if !osutil.IsDirectory(prefixDst) {
   624  			// does not exist now, will be created on the fly and
   625  			// removed during rollback
   626  			continue
   627  		}
   628  		if err := os.MkdirAll(filepath.Dir(prefixBackupName), 0755); err != nil {
   629  			return fmt.Errorf("cannot create backup prefix: %v", err)
   630  		}
   631  		if err := makeStamp(prefixBackupName); err != nil {
   632  			return fmt.Errorf("cannot create a checkpoint for directory: %v", err)
   633  		}
   634  	}
   635  	return nil
   636  }
   637  
   638  func (f *mountedFilesystemUpdater) ignoreChange(change *ContentChange, backupPath string) error {
   639  	preserveStamp := backupPath + ".preserve"
   640  	backupName := backupPath + ".backup"
   641  	sameStamp := backupPath + ".same"
   642  	ignoreStamp := backupPath + ".ignore"
   643  	if err := makeStamp(ignoreStamp); err != nil {
   644  		return fmt.Errorf("cannot create a checkpoint file: %v", err)
   645  	}
   646  	for _, name := range []string{backupName, sameStamp, preserveStamp} {
   647  		if err := os.Remove(name); err != nil && !os.IsNotExist(err) {
   648  			return fmt.Errorf("cannot remove existing stamp file: %v", err)
   649  		}
   650  	}
   651  	return nil
   652  }
   653  
   654  func (f *mountedFilesystemUpdater) observedBackupOrCheckpointFile(dstRoot, source, target string, preserveInDst []string, backupDir string) error {
   655  	change, err := f.backupOrCheckpointFile(dstRoot, source, target, preserveInDst, backupDir)
   656  	if err != nil {
   657  		return err
   658  	}
   659  	if change != nil {
   660  		dstPath, backupPath := f.entryDestPaths(dstRoot, source, target, backupDir)
   661  		act, err := observe(f.updateObserver, ContentUpdate, f.ps, f.mountPoint, dstPath, change)
   662  		if err != nil {
   663  			return fmt.Errorf("cannot observe pending file write %v\n", err)
   664  		}
   665  		if act == ChangeIgnore {
   666  			// observer asked for the change to be ignored
   667  			if err := f.ignoreChange(change, backupPath); err != nil {
   668  				return fmt.Errorf("cannot ignore content change: %v", err)
   669  			}
   670  		}
   671  	}
   672  	return nil
   673  }
   674  
   675  // backupOrCheckpointFile analyzes a given source file from the gadget and a
   676  // target location under the provided destination root directory. When both
   677  // files are identical, creates a stamp that allows the update to skip the file.
   678  // When content of the new file is different, a backup of the original file is
   679  // created. Returns a content change if a file will be written by the update
   680  // pass.
   681  func (f *mountedFilesystemUpdater) backupOrCheckpointFile(dstRoot, source, target string, preserveInDst []string, backupDir string) (change *ContentChange, err error) {
   682  	srcPath := f.entrySourcePath(source)
   683  	dstPath, backupPath := f.entryDestPaths(dstRoot, source, target, backupDir)
   684  
   685  	backupName := backupPath + ".backup"
   686  	sameStamp := backupPath + ".same"
   687  	preserveStamp := backupPath + ".preserve"
   688  	ignoreStamp := backupPath + ".ignore"
   689  
   690  	changeWithBackup := &ContentChange{
   691  		// content of the new data
   692  		After: srcPath,
   693  		// this is the original data that was present before the
   694  		// update
   695  		Before: backupName,
   696  	}
   697  	changeNewFile := &ContentChange{
   698  		// content of the new data
   699  		After: srcPath,
   700  	}
   701  
   702  	if osutil.FileExists(ignoreStamp) {
   703  		// observer already requested the change to the target location
   704  		// to be ignored
   705  		return nil, nil
   706  	}
   707  
   708  	// TODO: enable support for symlinks when needed
   709  	if osutil.IsSymlink(dstPath) {
   710  		return nil, fmt.Errorf("cannot backup file %s: symbolic links are not supported", target)
   711  	}
   712  
   713  	if !osutil.FileExists(dstPath) {
   714  		// destination does not exist and will be created when writing
   715  		// the udpate, no need for backup
   716  		return changeNewFile, nil
   717  	}
   718  	// destination file exists beyond this point
   719  
   720  	if osutil.FileExists(backupName) {
   721  		// file already checked and backed up
   722  		return changeWithBackup, nil
   723  	}
   724  	if osutil.FileExists(sameStamp) {
   725  		// file already checked, same as the update, move on
   726  		return nil, nil
   727  	}
   728  	// TODO: correctly identify new files that were written by a partially
   729  	// executed update pass
   730  
   731  	if strutil.SortedListContains(preserveInDst, dstPath) {
   732  		if osutil.FileExists(preserveStamp) {
   733  			// already stamped
   734  			return nil, nil
   735  		}
   736  		// make a stamp
   737  		if err := makeStamp(preserveStamp); err != nil {
   738  			return nil, fmt.Errorf("cannot create preserve stamp: %v", err)
   739  		}
   740  		return nil, nil
   741  	}
   742  
   743  	// try to find out whether the update and the existing file are
   744  	// identical
   745  
   746  	orig, err := os.Open(dstPath)
   747  	if err != nil {
   748  		return nil, fmt.Errorf("cannot open destination file: %v", err)
   749  	}
   750  
   751  	// backup of the original content
   752  	backup, err := newStampFile(backupName)
   753  	if err != nil {
   754  		return nil, fmt.Errorf("cannot create backup file: %v", err)
   755  	}
   756  	// becomes a backup copy or a noop if canceled
   757  	defer backup.Commit()
   758  
   759  	// checksum the original data while it's being copied
   760  	origHash := crypto.SHA1.New()
   761  	htr := io.TeeReader(orig, origHash)
   762  
   763  	_, err = io.Copy(backup, htr)
   764  	if err != nil {
   765  		backup.Cancel()
   766  		return nil, fmt.Errorf("cannot backup original file: %v", err)
   767  	}
   768  
   769  	// digest of the update
   770  	updateDigest, _, err := osutil.FileDigest(srcPath, crypto.SHA1)
   771  	if err != nil {
   772  		backup.Cancel()
   773  		return nil, fmt.Errorf("cannot checksum update file: %v", err)
   774  	}
   775  	// digest of the currently present data
   776  	origDigest := origHash.Sum(nil)
   777  
   778  	// TODO: look into comparing the streams directly
   779  	if bytes.Equal(origDigest, updateDigest) {
   780  		// mark that files are identical and update can be skipped, no
   781  		// backup is needed
   782  		if err := makeStamp(sameStamp); err != nil {
   783  			return nil, fmt.Errorf("cannot create a checkpoint file: %v", err)
   784  		}
   785  
   786  		// makes the deferred commit a noop
   787  		backup.Cancel()
   788  		return nil, nil
   789  	}
   790  
   791  	// update will overwrite existing file, a backup copy is created on
   792  	// Commit()
   793  	return changeWithBackup, nil
   794  }
   795  
   796  func (f *mountedFilesystemUpdater) backupVolumeContent(volumeRoot string, content *VolumeContent, preserveInDst []string, backupDir string) error {
   797  	if err := checkContent(content); err != nil {
   798  		return err
   799  	}
   800  
   801  	srcPath := f.entrySourcePath(content.Source)
   802  
   803  	if err := f.checkpointPrefix(volumeRoot, content.Target, backupDir); err != nil {
   804  		return err
   805  	}
   806  	if osutil.IsDirectory(srcPath) || strings.HasSuffix(content.Source, "/") {
   807  		// backup directory contents
   808  		return f.backupOrCheckpointDirectory(volumeRoot, content.Source, content.Target, preserveInDst, backupDir)
   809  	} else {
   810  		// backup a file
   811  		return f.observedBackupOrCheckpointFile(volumeRoot, content.Source, content.Target, preserveInDst, backupDir)
   812  	}
   813  }
   814  
   815  // Rollback attempts to revert changes done by the update step, using state
   816  // information collected during backup phase. Files that were modified by the
   817  // update are stored from their backup copies, newly added directories are
   818  // removed.
   819  func (f *mountedFilesystemUpdater) Rollback() error {
   820  	backupRoot := fsStructBackupPath(f.backupDir, f.ps)
   821  
   822  	preserveInDst, err := mapPreserve(f.mountPoint, f.ps.Update.Preserve)
   823  	if err != nil {
   824  		return fmt.Errorf("cannot map preserve entries for mount location %q: %v", f.mountPoint, err)
   825  	}
   826  
   827  	for _, c := range f.ps.Content {
   828  		if err := f.rollbackVolumeContent(f.mountPoint, &c, preserveInDst, backupRoot); err != nil {
   829  			return fmt.Errorf("cannot rollback content: %v", err)
   830  		}
   831  	}
   832  	return nil
   833  }
   834  
   835  func (f *mountedFilesystemUpdater) rollbackPrefix(dstRoot, target string, backupDir string) error {
   836  	for prefix := filepath.Dir(target); prefix != "/" && prefix != "."; prefix = filepath.Dir(prefix) {
   837  		prefixDstPath, prefixBackupPath := f.entryDestPaths(dstRoot, "", prefix, backupDir)
   838  		if !osutil.FileExists(prefixBackupPath + ".backup") {
   839  			// try remove
   840  			if err := os.Remove(prefixDstPath); err != nil {
   841  				logger.Noticef("cannot remove gadget directory %q: %v", prefix, err)
   842  			}
   843  		}
   844  	}
   845  	return nil
   846  }
   847  
   848  func (f *mountedFilesystemUpdater) rollbackDirectory(dstRoot, source, target string, preserveInDst []string, backupDir string) error {
   849  	fis, err := f.sourceDirectoryEntries(source)
   850  	if err != nil {
   851  		return fmt.Errorf("cannot rollback directory %q: %v", source, err)
   852  	}
   853  
   854  	target = targetForSourceDir(source, target)
   855  
   856  	for _, fi := range fis {
   857  		pSrc := filepath.Join(source, fi.Name())
   858  		pDst := filepath.Join(target, fi.Name())
   859  
   860  		rollback := f.rollbackFile
   861  		if fi.IsDir() {
   862  			// continue rolling back the contents of the directory
   863  			// rather than the directory itself
   864  			rollback = f.rollbackDirectory
   865  			pSrc += "/"
   866  			pDst += "/"
   867  		}
   868  		if err := rollback(dstRoot, pSrc, pDst, preserveInDst, backupDir); err != nil {
   869  			return err
   870  		}
   871  		if err := f.rollbackPrefix(dstRoot, pDst, backupDir); err != nil {
   872  			return err
   873  		}
   874  	}
   875  
   876  	return nil
   877  }
   878  
   879  func (f *mountedFilesystemUpdater) rollbackFile(dstRoot, source, target string, preserveInDst []string, backupDir string) error {
   880  	srcPath := f.entrySourcePath(source)
   881  	dstPath, backupPath := f.entryDestPaths(dstRoot, source, target, backupDir)
   882  
   883  	backupName := backupPath + ".backup"
   884  	sameStamp := backupPath + ".same"
   885  	preserveStamp := backupPath + ".preserve"
   886  	ignoreStamp := backupPath + ".ignore"
   887  
   888  	if strutil.SortedListContains(preserveInDst, dstPath) && osutil.FileExists(preserveStamp) {
   889  		// file was preserved at original location by being
   890  		// explicitly listed
   891  		return nil
   892  	}
   893  	if osutil.FileExists(sameStamp) {
   894  		// contents are the same as original, do nothing
   895  		return nil
   896  	}
   897  	if osutil.FileExists(ignoreStamp) {
   898  		// observer requested the changes to the target to be ignored
   899  		// previously
   900  		return nil
   901  	}
   902  
   903  	data := &ContentChange{
   904  		After: srcPath,
   905  		// original content was in the backup file
   906  		Before: backupName,
   907  	}
   908  
   909  	if osutil.FileExists(backupName) {
   910  		// restore backup -> destination
   911  		if err := writeFileOrSymlink(backupName, dstPath, nil); err != nil {
   912  			return err
   913  		}
   914  	} else {
   915  		if err := os.Remove(dstPath); err != nil && !os.IsNotExist(err) {
   916  			return fmt.Errorf("cannot remove written update: %v", err)
   917  		}
   918  		// since it's a new file, there was no original content
   919  		data.Before = ""
   920  	}
   921  	// avoid passing source path during rollback, the file has been restored
   922  	// to the disk already
   923  	_, err := observe(f.updateObserver, ContentRollback, f.ps, f.mountPoint, dstPath, data)
   924  	if err != nil {
   925  		return fmt.Errorf("cannot observe pending file rollback %v\n", err)
   926  	}
   927  
   928  	return nil
   929  }
   930  
   931  func (f *mountedFilesystemUpdater) rollbackVolumeContent(volumeRoot string, content *VolumeContent, preserveInDst []string, backupDir string) error {
   932  	if err := checkContent(content); err != nil {
   933  		return err
   934  	}
   935  
   936  	srcPath := f.entrySourcePath(content.Source)
   937  
   938  	var err error
   939  	if osutil.IsDirectory(srcPath) || strings.HasSuffix(content.Source, "/") {
   940  		// rollback directory
   941  		err = f.rollbackDirectory(volumeRoot, content.Source, content.Target, preserveInDst, backupDir)
   942  	} else {
   943  		// rollback file
   944  		err = f.rollbackFile(volumeRoot, content.Source, content.Target, preserveInDst, backupDir)
   945  	}
   946  	if err != nil {
   947  		return err
   948  	}
   949  
   950  	return f.rollbackPrefix(volumeRoot, content.Target, backupDir)
   951  }