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