github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/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 }