github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/overlord/snapstate/backend/snapdata.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2016 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 backend
    21  
    22  import (
    23  	"errors"
    24  	"fmt"
    25  	"os"
    26  	"path/filepath"
    27  	unix "syscall"
    28  
    29  	"github.com/snapcore/snapd/dirs"
    30  	"github.com/snapcore/snapd/logger"
    31  	"github.com/snapcore/snapd/osutil"
    32  	"github.com/snapcore/snapd/snap"
    33  )
    34  
    35  // RemoveSnapData removes the data for the given version of the given snap.
    36  func (b Backend) RemoveSnapData(snap *snap.Info) error {
    37  	dirs, err := snapDataDirs(snap)
    38  	if err != nil {
    39  		return err
    40  	}
    41  
    42  	return removeDirs(dirs)
    43  }
    44  
    45  // RemoveSnapCommonData removes the data common between versions of the given snap.
    46  func (b Backend) RemoveSnapCommonData(snap *snap.Info) error {
    47  	dirs, err := snapCommonDataDirs(snap)
    48  	if err != nil {
    49  		return err
    50  	}
    51  
    52  	return removeDirs(dirs)
    53  }
    54  
    55  // RemoveSnapDataDir removes base snap data directory
    56  func (b Backend) RemoveSnapDataDir(info *snap.Info, hasOtherInstances bool) error {
    57  	if info.InstanceKey != "" {
    58  		// data directories of snaps with instance key are never used by
    59  		// other instances
    60  		if err := os.Remove(snap.BaseDataDir(info.InstanceName())); err != nil && !os.IsNotExist(err) {
    61  			return fmt.Errorf("failed to remove snap %q base directory: %v", info.InstanceName(), err)
    62  		}
    63  	}
    64  	if !hasOtherInstances {
    65  		// remove the snap base directory only if there are no other
    66  		// snap instances using it
    67  		if err := os.Remove(snap.BaseDataDir(info.SnapName())); err != nil && !os.IsNotExist(err) {
    68  			return fmt.Errorf("failed to remove snap %q base directory: %v", info.SnapName(), err)
    69  		}
    70  	}
    71  
    72  	return nil
    73  }
    74  
    75  func (b Backend) untrashData(snap *snap.Info) error {
    76  	dirs, err := snapDataDirs(snap)
    77  	if err != nil {
    78  		return err
    79  	}
    80  
    81  	for _, d := range dirs {
    82  		if e := untrash(d); e != nil {
    83  			err = e
    84  		}
    85  	}
    86  
    87  	return err
    88  }
    89  
    90  func removeDirs(dirs []string) error {
    91  	for _, dir := range dirs {
    92  		if err := os.RemoveAll(dir); err != nil {
    93  			return err
    94  		}
    95  	}
    96  
    97  	return nil
    98  }
    99  
   100  // snapDataDirs returns the list of data directories for the given snap version
   101  func snapDataDirs(snap *snap.Info) ([]string, error) {
   102  	// collect the directories, homes first
   103  	found, err := filepath.Glob(snap.DataHomeDir())
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  	// then the /root user (including GlobalRootDir for tests)
   108  	found = append(found, snap.UserDataDir(filepath.Join(dirs.GlobalRootDir, "/root/")))
   109  	// then system data
   110  	found = append(found, snap.DataDir())
   111  
   112  	return found, nil
   113  }
   114  
   115  // snapCommonDataDirs returns the list of data directories common between versions of the given snap
   116  func snapCommonDataDirs(snap *snap.Info) ([]string, error) {
   117  	// collect the directories, homes first
   118  	found, err := filepath.Glob(snap.CommonDataHomeDir())
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  
   123  	// then XDG_RUNTIME_DIRs for the users
   124  	foundXdg, err := filepath.Glob(snap.XdgRuntimeDirs())
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  	found = append(found, foundXdg...)
   129  
   130  	// then system data
   131  	found = append(found, snap.CommonDataDir())
   132  
   133  	return found, nil
   134  }
   135  
   136  // Copy all data for oldSnap to newSnap
   137  // (but never overwrite)
   138  func copySnapData(oldSnap, newSnap *snap.Info) (err error) {
   139  	oldDataDirs, err := snapDataDirs(oldSnap)
   140  	if err != nil {
   141  		return err
   142  	}
   143  	done := make([]string, 0, len(oldDataDirs))
   144  	defer func() {
   145  		if err == nil {
   146  			return
   147  		}
   148  		// something went wrong, but we'd already written stuff. Fix that.
   149  		for _, newDir := range done {
   150  			if err := os.RemoveAll(newDir); err != nil {
   151  				logger.Noticef("while undoing creation of new data directory %q: %v", newDir, err)
   152  			}
   153  			if err := untrash(newDir); err != nil {
   154  				logger.Noticef("while restoring the old version of data directory %q: %v", newDir, err)
   155  			}
   156  		}
   157  	}()
   158  
   159  	newSuffix := filepath.Base(newSnap.DataDir())
   160  	for _, oldDir := range oldDataDirs {
   161  		// replace the trailing "../$old-suffix" with the "../$new-suffix"
   162  		newDir := filepath.Join(filepath.Dir(oldDir), newSuffix)
   163  		if err := copySnapDataDirectory(oldDir, newDir); err != nil {
   164  			return err
   165  		}
   166  		done = append(done, newDir)
   167  	}
   168  
   169  	return nil
   170  }
   171  
   172  // trashPath returns the trash path for the given path. This will
   173  // differ only in the last element.
   174  func trashPath(path string) string {
   175  	return path + ".old"
   176  }
   177  
   178  // trash moves path aside, if it exists. If the trash for the path
   179  // already exists and is not empty it will be removed first.
   180  func trash(path string) error {
   181  	trash := trashPath(path)
   182  	err := os.Rename(path, trash)
   183  	if err == nil {
   184  		return nil
   185  	}
   186  	// os.Rename says it always returns *os.LinkError. Be wary.
   187  	e, ok := err.(*os.LinkError)
   188  	if !ok {
   189  		return err
   190  	}
   191  
   192  	switch e.Err {
   193  	case unix.ENOENT:
   194  		// path does not exist (here we use that trashPath(path) and path differ only in the last element)
   195  		return nil
   196  	case unix.ENOTEMPTY, unix.EEXIST:
   197  		// path exists, but trash already exists and is non-empty
   198  		// (empirically always ENOTEMPTY but rename(2) says it can also be EEXIST)
   199  		// nuke the old trash and try again
   200  		if err := os.RemoveAll(trash); err != nil {
   201  			// well, that didn't work :-(
   202  			return err
   203  		}
   204  		return os.Rename(path, trash)
   205  	default:
   206  		// WAT
   207  		return err
   208  	}
   209  }
   210  
   211  // untrash moves the trash for path back in, if it exists.
   212  func untrash(path string) error {
   213  	err := os.Rename(trashPath(path), path)
   214  	if !os.IsNotExist(err) {
   215  		return err
   216  	}
   217  
   218  	return nil
   219  }
   220  
   221  // clearTrash removes the trash made for path, if it exists.
   222  func clearTrash(path string) error {
   223  	err := os.RemoveAll(trashPath(path))
   224  	if !os.IsNotExist(err) {
   225  		return err
   226  	}
   227  
   228  	return nil
   229  }
   230  
   231  // Lowlevel copy the snap data (but never override existing data)
   232  func copySnapDataDirectory(oldPath, newPath string) (err error) {
   233  	if _, err := os.Stat(oldPath); err == nil {
   234  		if err := trash(newPath); err != nil {
   235  			return err
   236  		}
   237  
   238  		if _, err := os.Stat(newPath); err != nil {
   239  			if err := osutil.CopyFile(oldPath, newPath, osutil.CopyFlagPreserveAll|osutil.CopyFlagSync); err != nil {
   240  				msg := fmt.Sprintf("cannot copy %q to %q: %v", oldPath, newPath, err)
   241  				// remove the directory, in case it was a partial success
   242  				if e := os.RemoveAll(newPath); e != nil && !os.IsNotExist(e) {
   243  					msg += fmt.Sprintf("; and when trying to remove the partially-copied new data directory: %v", e)
   244  				}
   245  				// something went wrong but we already trashed what was there
   246  				// try to fix that; hope for the best
   247  				if e := untrash(newPath); e != nil {
   248  					// oh noes
   249  					// TODO: issue a warning to the user that data was lost
   250  					msg += fmt.Sprintf("; and when trying to restore the old data directory: %v", e)
   251  				}
   252  
   253  				return errors.New(msg)
   254  			}
   255  		}
   256  	} else if !os.IsNotExist(err) {
   257  		return err
   258  	}
   259  
   260  	return nil
   261  }