Merge lp:~mvo/snappy/snappy-snapfs-mount into lp:~snappy-dev/snappy/snappy-moved-to-github
- snappy-snapfs-mount
- Merge into snappy-moved-to-github
Status: | Needs review |
---|---|
Proposed branch: | lp:~mvo/snappy/snappy-snapfs-mount |
Merge into: | lp:~snappy-dev/snappy/snappy-moved-to-github |
Prerequisite: | lp:~mvo/snappy/snappy-snapfs-install-via-unpack |
Diff against target: |
948 lines (+411/-134) 11 files modified
debian/ubuntu-snappy-cli.dirs (+1/-0) dirs/dirs.go (+2/-0) pkg/clickdeb/deb.go (+29/-23) pkg/file.go (+81/-0) pkg/snapfs/snapfs.go (+47/-22) pkg/snapfs/snapfs_test.go (+0/-9) snappy/pkgformat.go (+0/-64) snappy/snapp.go (+73/-5) snappy/snapp_snapfs_test.go (+93/-11) systemd/systemd.go (+46/-0) systemd/systemd_test.go (+39/-0) |
To merge this branch: | bzr merge lp:~mvo/snappy/snappy-snapfs-mount |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gustavo Niemeyer | Approve | ||
John Lenton (community) | Approve | ||
Review via email: mp+273883@code.launchpad.net |
Commit message
Description of the change
Add support to (auto)mount the snapfs based snaps. It will unpack the meta-data to disk (to speedup snappy list etc) but keep the rest in the squashfs blob and (auto)mount at runtime.
This is a bit of a RFC branch, I'm not yet super happy about it. It will put the blob into
/apps/
and will mount that to:
/apps/
The downside of this approach is of course that the binary/service paths of the deb backend and the squashfs backend are no longer identical which makes the code that writes that a bit ugly.
I think we have three option and would love to get feedback which one is best:
1. keep it like its done in this branch given and cleanup once the clickdeb backend is killed
2. change deb backend to write the actual (non-meta) data to "run/" as well
3. put the blob.snap as "/apps/
(which complicates the removal a bit because its no longer enough to just rm -rf the /apps/$pkg/$version dir)
John Lenton (chipaca) wrote : | # |
*/var/lib/
A small complication of remove and purge, but other than that it'd work, right?
John Lenton (chipaca) : | # |
Michael Vogt (mvo) wrote : | # |
Thanks John! So with /var/lib/
John Lenton (chipaca) wrote : | # |
I think it might be. Let me tinker with list a bit tomorrow -- i think it can be made to work without loading package.yaml's.
John Lenton (chipaca) wrote : | # |
Tinkered now instead.
To make list not load package.yaml's, you'd need to
* move it to husks, or to the rest api. Latter needs doing anyway; former would be short-term quick if needed.
* add a concreter that knew about squashfs:
- `IsInstalled` checks for presence of blob instead of package.yaml
- `Load` loaded a RemoteSnapPart from the locally-stored remote manifest. Would be missing InstalledSize, which list doesn't output. Would probably mean moving RemoteSnapPart to its own package (\o/)
If doing it via the REST API, /1.0/packages would need to recover the ?sources=local option, and an additional flag to tell it to do things this way. Piece of cake.
So, I'd suggest do it the clean way, mounting to /$type/$name/$ver (i think we agree it's the clean way?). Then we benchmark and decide whether to do the above now, or later.
HTH,
- 754. By Michael Vogt
-
merged lp:snappy
- 755. By Michael Vogt
-
refactor and get rid of runPrefix
Michael Vogt (mvo) wrote : | # |
Thanks, I pondered over this too and I like the approach of /var/lib/
- 756. By Michael Vogt
-
ensure removal removes the snapfs and refactor
- 757. By Michael Vogt
-
update comments
- 758. By Michael Vogt
-
unmount after deactivate
- 759. By Michael Vogt
-
improve comments
John Lenton (chipaca) wrote : | # |
Some comments.
Need to iterate the review a little.
Sergio Schvezov (sergiusens) wrote : | # |
I can't beat Chipaca on reviews but I did manage to add a bite of a comment that is not a problem today but will avoid headaches later. It is an 'if you want to do this' type of comment.
Good work
- 760. By Michael Vogt
-
address review comments
- 761. By Michael Vogt
-
refactor and ove MountUnit handling to systemd package
- 762. By Michael Vogt
-
add more tests
- 763. By Michael Vogt
-
add comment about little endian squashfs
Michael Vogt (mvo) wrote : | # |
Thanks for the review! I addressed the comemnts, please let me know if its sufficient.
John Lenton (chipaca) : | # |
- 764. By Michael Vogt
-
pkg/snapfs/
snapfs. go: use filepath.Clean() in BlobPath
John Lenton (chipaca) wrote : | # |
This looks good enough, now :)
Gustavo Niemeyer (niemeyer) wrote : | # |
I'm adding a few comments below. Please feel free to merge this once you're personally happy about it.
John Lenton (chipaca) : | # |
- 765. By Michael Vogt
-
merged lp:snappy
- 766. By Michael Vogt
-
use properly terminated sentences (thanks gustavo)
- 767. By Michael Vogt
-
more documenation and improve error reporting (thanks Gustavo)
Michael Vogt (mvo) wrote : | # |
Thanks Gustavo for this excellent review. I started fixing some of the outlined issues and it looks like I'm running out of time. I will continue later but be assured that I will address every point in the review :)
Unmerged revisions
- 767. By Michael Vogt
-
more documenation and improve error reporting (thanks Gustavo)
- 766. By Michael Vogt
-
use properly terminated sentences (thanks gustavo)
- 765. By Michael Vogt
-
merged lp:snappy
- 764. By Michael Vogt
-
pkg/snapfs/
snapfs. go: use filepath.Clean() in BlobPath - 763. By Michael Vogt
-
add comment about little endian squashfs
- 762. By Michael Vogt
-
add more tests
- 761. By Michael Vogt
-
refactor and ove MountUnit handling to systemd package
- 760. By Michael Vogt
-
address review comments
- 759. By Michael Vogt
-
improve comments
- 758. By Michael Vogt
-
unmount after deactivate
Preview Diff
1 | === added file 'debian/ubuntu-snappy-cli.dirs' | |||
2 | --- debian/ubuntu-snappy-cli.dirs 1970-01-01 00:00:00 +0000 | |||
3 | +++ debian/ubuntu-snappy-cli.dirs 2015-10-20 15:38:57 +0000 | |||
4 | @@ -0,0 +1,1 @@ | |||
5 | 1 | /var/lib/snappy/snaps | ||
6 | 0 | 2 | ||
7 | === modified file 'dirs/dirs.go' | |||
8 | --- dirs/dirs.go 2015-09-25 15:27:11 +0000 | |||
9 | +++ dirs/dirs.go 2015-10-20 15:38:57 +0000 | |||
10 | @@ -35,6 +35,7 @@ | |||
11 | 35 | LocaleDir string | 35 | LocaleDir string |
12 | 36 | SnapIconsDir string | 36 | SnapIconsDir string |
13 | 37 | SnapMetaDir string | 37 | SnapMetaDir string |
14 | 38 | SnapBlobDir string | ||
15 | 38 | 39 | ||
16 | 39 | SnapBinariesDir string | 40 | SnapBinariesDir string |
17 | 40 | SnapServicesDir string | 41 | SnapServicesDir string |
18 | @@ -59,6 +60,7 @@ | |||
19 | 59 | SnapSeccompDir = filepath.Join(rootdir, SnappyDir, "seccomp", "profiles") | 60 | SnapSeccompDir = filepath.Join(rootdir, SnappyDir, "seccomp", "profiles") |
20 | 60 | SnapIconsDir = filepath.Join(rootdir, SnappyDir, "icons") | 61 | SnapIconsDir = filepath.Join(rootdir, SnappyDir, "icons") |
21 | 61 | SnapMetaDir = filepath.Join(rootdir, SnappyDir, "meta") | 62 | SnapMetaDir = filepath.Join(rootdir, SnappyDir, "meta") |
22 | 63 | SnapBlobDir = filepath.Join(rootdir, SnappyDir, "snaps") | ||
23 | 62 | 64 | ||
24 | 63 | SnapBinariesDir = filepath.Join(SnapAppsDir, "bin") | 65 | SnapBinariesDir = filepath.Join(SnapAppsDir, "bin") |
25 | 64 | SnapServicesDir = filepath.Join(rootdir, "/etc/systemd/system") | 66 | SnapServicesDir = filepath.Join(rootdir, "/etc/systemd/system") |
26 | 65 | 67 | ||
27 | === modified file 'pkg/clickdeb/deb.go' | |||
28 | --- pkg/clickdeb/deb.go 2015-10-08 08:29:16 +0000 | |||
29 | +++ pkg/clickdeb/deb.go 2015-10-20 15:38:57 +0000 | |||
30 | @@ -40,33 +40,34 @@ | |||
31 | 40 | 40 | ||
32 | 41 | var ( | 41 | var ( |
33 | 42 | // ErrSnapInvalidContent is returned if a snap package contains | 42 | // ErrSnapInvalidContent is returned if a snap package contains |
35 | 43 | // invalid content | 43 | // invalid content. |
36 | 44 | ErrSnapInvalidContent = errors.New("snap contains invalid content") | 44 | ErrSnapInvalidContent = errors.New("snap contains invalid content") |
37 | 45 | 45 | ||
39 | 46 | // ErrMemberNotFound is returned when a tar member is not found in the archive | 46 | // ErrMemberNotFound is returned when a tar member is not found in |
40 | 47 | // the archive. | ||
41 | 47 | ErrMemberNotFound = errors.New("member not found") | 48 | ErrMemberNotFound = errors.New("member not found") |
42 | 48 | ) | 49 | ) |
43 | 49 | 50 | ||
45 | 50 | // ErrUnpackFailed is the error type for a snap unpack problem | 51 | // ErrUnpackFailed is the error type for a snap unpack problem. |
46 | 51 | type ErrUnpackFailed struct { | 52 | type ErrUnpackFailed struct { |
47 | 52 | snapFile string | 53 | snapFile string |
48 | 53 | instDir string | 54 | instDir string |
49 | 54 | origErr error | 55 | origErr error |
50 | 55 | } | 56 | } |
51 | 56 | 57 | ||
53 | 57 | // ErrUnpackFailed is returned if unpacking a snap fails | 58 | // ErrUnpackFailed is returned if unpacking a snap fails. |
54 | 58 | func (e *ErrUnpackFailed) Error() string { | 59 | func (e *ErrUnpackFailed) Error() string { |
55 | 59 | return fmt.Sprintf("unpack %s to %s failed with %s", e.snapFile, e.instDir, e.origErr) | 60 | return fmt.Sprintf("unpack %s to %s failed with %s", e.snapFile, e.instDir, e.origErr) |
56 | 60 | } | 61 | } |
57 | 61 | 62 | ||
59 | 62 | // simple pipe based xz reader | 63 | // Simple pipe based xz reader. |
60 | 63 | func xzPipeReader(r io.Reader) io.Reader { | 64 | func xzPipeReader(r io.Reader) io.Reader { |
61 | 64 | pr, pw := io.Pipe() | 65 | pr, pw := io.Pipe() |
62 | 65 | cmd := exec.Command("xz", "--decompress", "--stdout") | 66 | cmd := exec.Command("xz", "--decompress", "--stdout") |
63 | 66 | cmd.Stdin = r | 67 | cmd.Stdin = r |
64 | 67 | cmd.Stdout = pw | 68 | cmd.Stdout = pw |
65 | 68 | 69 | ||
67 | 69 | // run xz in its own go-routine | 70 | // run xz in its own go-routine. |
68 | 70 | go func() { | 71 | go func() { |
69 | 71 | pw.CloseWithError(cmd.Run()) | 72 | pw.CloseWithError(cmd.Run()) |
70 | 72 | }() | 73 | }() |
71 | @@ -74,7 +75,7 @@ | |||
72 | 74 | return pr | 75 | return pr |
73 | 75 | } | 76 | } |
74 | 76 | 77 | ||
76 | 77 | // simple pipe based xz writer | 78 | // Simple pipe based xz writer. |
77 | 78 | type xzPipeWriter struct { | 79 | type xzPipeWriter struct { |
78 | 79 | cmd *exec.Cmd | 80 | cmd *exec.Cmd |
79 | 80 | w io.Writer | 81 | w io.Writer |
80 | @@ -93,7 +94,7 @@ | |||
81 | 93 | x.cmd.Stdout = x.w | 94 | x.cmd.Stdout = x.w |
82 | 94 | x.cmd.Stderr = os.Stderr | 95 | x.cmd.Stderr = os.Stderr |
83 | 95 | 96 | ||
85 | 96 | // Start is async | 97 | // Start is async. |
86 | 97 | x.cmd.Start() | 98 | x.cmd.Start() |
87 | 98 | 99 | ||
88 | 99 | return x | 100 | return x |
89 | @@ -108,7 +109,7 @@ | |||
90 | 108 | return x.cmd.Wait() | 109 | return x.cmd.Wait() |
91 | 109 | } | 110 | } |
92 | 110 | 111 | ||
94 | 111 | // ensure that the content of our data is valid: | 112 | // Ensure that the content of our data is valid: |
95 | 112 | // - no relative path allowed to prevent writing outside of the parent dir | 113 | // - no relative path allowed to prevent writing outside of the parent dir |
96 | 113 | func clickVerifyContentFn(path string) (string, error) { | 114 | func clickVerifyContentFn(path string) (string, error) { |
97 | 114 | path = filepath.Clean(path) | 115 | path = filepath.Clean(path) |
98 | @@ -122,7 +123,7 @@ | |||
99 | 122 | } | 123 | } |
100 | 123 | 124 | ||
101 | 124 | // ClickDeb provides support for the "click" containers (a special kind of | 125 | // ClickDeb provides support for the "click" containers (a special kind of |
103 | 125 | // deb package) | 126 | // deb package). |
104 | 126 | type ClickDeb struct { | 127 | type ClickDeb struct { |
105 | 127 | file *os.File | 128 | file *os.File |
106 | 128 | } | 129 | } |
107 | @@ -145,29 +146,34 @@ | |||
108 | 145 | return &ClickDeb{f}, nil | 146 | return &ClickDeb{f}, nil |
109 | 146 | } | 147 | } |
110 | 147 | 148 | ||
112 | 148 | // Name returns the Name of the backing file | 149 | // Name returns the Name of the backing file. |
113 | 149 | func (d *ClickDeb) Name() string { | 150 | func (d *ClickDeb) Name() string { |
114 | 150 | return d.file.Name() | 151 | return d.file.Name() |
115 | 151 | } | 152 | } |
116 | 152 | 153 | ||
118 | 153 | // Close closes the backing file | 154 | // NeedsAutoMountUnit returns false. |
119 | 155 | func (d *ClickDeb) NeedsAutoMountUnit() bool { | ||
120 | 156 | return false | ||
121 | 157 | } | ||
122 | 158 | |||
123 | 159 | // Close closes the backing file. | ||
124 | 154 | func (d *ClickDeb) Close() error { | 160 | func (d *ClickDeb) Close() error { |
125 | 155 | return d.file.Close() | 161 | return d.file.Close() |
126 | 156 | } | 162 | } |
127 | 157 | 163 | ||
129 | 158 | // Verify checks that the clickdeb is signed | 164 | // Verify checks that the clickdeb is signed. |
130 | 159 | func (d *ClickDeb) Verify(allowUnauthenticated bool) error { | 165 | func (d *ClickDeb) Verify(allowUnauthenticated bool) error { |
131 | 160 | return Verify(d.Name(), allowUnauthenticated) | 166 | return Verify(d.Name(), allowUnauthenticated) |
132 | 161 | } | 167 | } |
133 | 162 | 168 | ||
134 | 163 | // ControlMember returns the content of the given control member file | 169 | // ControlMember returns the content of the given control member file |
136 | 164 | // (e.g. the content of the "manifest" file in the control.tar.gz ar member) | 170 | // (e.g. the content of the "manifest" file in the control.tar.gz ar member). |
137 | 165 | func (d *ClickDeb) ControlMember(controlMember string) (content []byte, err error) { | 171 | func (d *ClickDeb) ControlMember(controlMember string) (content []byte, err error) { |
138 | 166 | return d.member("control.tar", controlMember) | 172 | return d.member("control.tar", controlMember) |
139 | 167 | } | 173 | } |
140 | 168 | 174 | ||
141 | 169 | // MetaMember returns the content of the given meta file (e.g. the content of | 175 | // MetaMember returns the content of the given meta file (e.g. the content of |
143 | 170 | // the "package.yaml" file) from the data.tar.gz ar member's meta/ directory | 176 | // the "package.yaml" file) from the data.tar.gz ar member's meta/ directory. |
144 | 171 | func (d *ClickDeb) MetaMember(metaMember string) (content []byte, err error) { | 177 | func (d *ClickDeb) MetaMember(metaMember string) (content []byte, err error) { |
145 | 172 | return d.member("data.tar", filepath.Join("meta", metaMember)) | 178 | return d.member("data.tar", filepath.Join("meta", metaMember)) |
146 | 173 | } | 179 | } |
147 | @@ -213,7 +219,7 @@ | |||
148 | 213 | } | 219 | } |
149 | 214 | 220 | ||
150 | 215 | // ExtractHashes reads "hashes.yaml" from the clickdeb and writes it to | 221 | // ExtractHashes reads "hashes.yaml" from the clickdeb and writes it to |
152 | 216 | // the given directory | 222 | // the given directory. |
153 | 217 | func (d *ClickDeb) ExtractHashes(dir string) error { | 223 | func (d *ClickDeb) ExtractHashes(dir string) error { |
154 | 218 | hashesFile := filepath.Join(dir, "hashes.yaml") | 224 | hashesFile := filepath.Join(dir, "hashes.yaml") |
155 | 219 | hashesData, err := d.ControlMember("hashes.yaml") | 225 | hashesData, err := d.ControlMember("hashes.yaml") |
156 | @@ -226,7 +232,7 @@ | |||
157 | 226 | 232 | ||
158 | 227 | // Unpack unpacks the data.tar.{gz,bz2,xz} into the given target directory | 233 | // Unpack unpacks the data.tar.{gz,bz2,xz} into the given target directory |
159 | 228 | // with click specific verification, i.e. no files will be extracted outside | 234 | // with click specific verification, i.e. no files will be extracted outside |
161 | 229 | // of the targetdir (no ".." inside the data.tar is allowed) | 235 | // of the targetdir (no ".." inside the data.tar is allowed). |
162 | 230 | func (d *ClickDeb) Unpack(targetDir string) error { | 236 | func (d *ClickDeb) Unpack(targetDir string) error { |
163 | 231 | var err error | 237 | var err error |
164 | 232 | 238 | ||
165 | @@ -244,7 +250,7 @@ | |||
166 | 244 | return helpers.UnpackTar(dataReader, targetDir, clickVerifyContentFn) | 250 | return helpers.UnpackTar(dataReader, targetDir, clickVerifyContentFn) |
167 | 245 | } | 251 | } |
168 | 246 | 252 | ||
170 | 247 | // FIXME: this should move into the "ar" library itself | 253 | // FIXME: this should move into the "ar" library itself. |
171 | 248 | func addFileToAr(arWriter *ar.Writer, filename string) error { | 254 | func addFileToAr(arWriter *ar.Writer, filename string) error { |
172 | 249 | dataF, err := os.Open(filename) | 255 | dataF, err := os.Open(filename) |
173 | 250 | if err != nil { | 256 | if err != nil { |
174 | @@ -276,7 +282,7 @@ | |||
175 | 276 | return nil | 282 | return nil |
176 | 277 | } | 283 | } |
177 | 278 | 284 | ||
179 | 279 | // FIXME: this should move into the "ar" library itself | 285 | // FIXME: this should move into the "ar" library itself. |
180 | 280 | func addDataToAr(arWriter *ar.Writer, filename string, data []byte) error { | 286 | func addDataToAr(arWriter *ar.Writer, filename string, data []byte) error { |
181 | 281 | size := int64(len(data)) | 287 | size := int64(len(data)) |
182 | 282 | hdr := &ar.Header{ | 288 | hdr := &ar.Header{ |
183 | @@ -295,11 +301,11 @@ | |||
184 | 295 | } | 301 | } |
185 | 296 | 302 | ||
186 | 297 | // tarExcludeFunc is a helper for tarCreate that is called for each file | 303 | // tarExcludeFunc is a helper for tarCreate that is called for each file |
188 | 298 | // that is about to be added. If it returns "false" the file is skipped | 304 | // that is about to be added. If it returns "false" the file is skipped. |
189 | 299 | type tarExcludeFunc func(path string) bool | 305 | type tarExcludeFunc func(path string) bool |
190 | 300 | 306 | ||
191 | 301 | // tarCreate creates a tarfile for a clickdeb, all files in the archive | 307 | // tarCreate creates a tarfile for a clickdeb, all files in the archive |
193 | 302 | // belong to root (same as dpkg-deb) | 308 | // belong to root (same as dpkg-deb). |
194 | 303 | func tarCreate(tarname string, sourceDir string, fn tarExcludeFunc) error { | 309 | func tarCreate(tarname string, sourceDir string, fn tarExcludeFunc) error { |
195 | 304 | w, err := os.Create(tarname) | 310 | w, err := os.Create(tarname) |
196 | 305 | if err != nil { | 311 | if err != nil { |
197 | @@ -397,7 +403,7 @@ | |||
198 | 397 | } | 403 | } |
199 | 398 | 404 | ||
200 | 399 | // Build takes a build debian directory with DEBIAN/ dir and creates a | 405 | // Build takes a build debian directory with DEBIAN/ dir and creates a |
202 | 400 | // clickdeb from it | 406 | // clickdeb from it. |
203 | 401 | func (d *ClickDeb) Build(sourceDir string, dataTarFinishedCallback func(dataName string) error) error { | 407 | func (d *ClickDeb) Build(sourceDir string, dataTarFinishedCallback func(dataName string) error) error { |
204 | 402 | var err error | 408 | var err error |
205 | 403 | 409 | ||
206 | @@ -496,7 +502,7 @@ | |||
207 | 496 | // target dir and drop privs when doing this. | 502 | // target dir and drop privs when doing this. |
208 | 497 | // | 503 | // |
209 | 498 | // To do this reliably in go we need to exec a helper as we can not | 504 | // To do this reliably in go we need to exec a helper as we can not |
211 | 499 | // just fork() and drop privs in the child (no support for stock fork in go) | 505 | // just fork() and drop privs in the child (no support for stock fork in go). |
212 | 500 | func (d *ClickDeb) UnpackWithDropPrivs(instDir, rootdir string) error { | 506 | func (d *ClickDeb) UnpackWithDropPrivs(instDir, rootdir string) error { |
213 | 501 | // no need to drop privs, we are not root | 507 | // no need to drop privs, we are not root |
214 | 502 | if !helpers.ShouldDropPrivs() { | 508 | if !helpers.ShouldDropPrivs() { |
215 | 503 | 509 | ||
216 | === added file 'pkg/file.go' | |||
217 | --- pkg/file.go 1970-01-01 00:00:00 +0000 | |||
218 | +++ pkg/file.go 2015-10-20 15:38:57 +0000 | |||
219 | @@ -0,0 +1,81 @@ | |||
220 | 1 | // -*- Mode: Go; indent-tabs-mode: t -*- | ||
221 | 2 | |||
222 | 3 | /* | ||
223 | 4 | * Copyright (C) 2015 Canonical Ltd | ||
224 | 5 | * | ||
225 | 6 | * This program is free software: you can redistribute it and/or modify | ||
226 | 7 | * it under the terms of the GNU General Public License version 3 as | ||
227 | 8 | * published by the Free Software Foundation. | ||
228 | 9 | * | ||
229 | 10 | * This program is distributed in the hope that it will be useful, | ||
230 | 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
231 | 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
232 | 13 | * GNU General Public License for more details. | ||
233 | 14 | * | ||
234 | 15 | * You should have received a copy of the GNU General Public License | ||
235 | 16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
236 | 17 | * | ||
237 | 18 | */ | ||
238 | 19 | |||
239 | 20 | package pkg | ||
240 | 21 | |||
241 | 22 | import ( | ||
242 | 23 | "bytes" | ||
243 | 24 | "fmt" | ||
244 | 25 | "os" | ||
245 | 26 | "strings" | ||
246 | 27 | |||
247 | 28 | "launchpad.net/snappy/pkg/clickdeb" | ||
248 | 29 | "launchpad.net/snappy/pkg/snapfs" | ||
249 | 30 | ) | ||
250 | 31 | |||
251 | 32 | // File is the interface to interact with the low-level snap files. | ||
252 | 33 | type File interface { | ||
253 | 34 | // Verify verfies the integrity of the file. | ||
254 | 35 | // FIXME: use flags here instead of a boolean | ||
255 | 36 | Verify(allowUnauthenticated bool) error | ||
256 | 37 | // Close closes the snapfile. | ||
257 | 38 | // FIXME: this can go away once we no longer support clickdebs. | ||
258 | 39 | Close() error | ||
259 | 40 | // UnpackWithDropPrivs unpacks the given the snap to the given | ||
260 | 41 | // targetdir relative to the given rootDir. | ||
261 | 42 | // FIXME: name leaks implementation details, should be Unpack() | ||
262 | 43 | UnpackWithDropPrivs(targetDir, rootDir string) error | ||
263 | 44 | // ControlMember returns the content of snap meta data files. | ||
264 | 45 | ControlMember(name string) ([]byte, error) | ||
265 | 46 | // MetaMember returns the content of snap meta data files. | ||
266 | 47 | // FIXME: redundant | ||
267 | 48 | MetaMember(name string) ([]byte, error) | ||
268 | 49 | // ExtractHashes extracs the hashes from the snap and puts | ||
269 | 50 | // them into the filesystem for verification. | ||
270 | 51 | ExtractHashes(targetDir string) error | ||
271 | 52 | |||
272 | 53 | // NeedsAutoMountUnit determines if it's required to setup | ||
273 | 54 | // an automount unit for the snap when the snap is activated | ||
274 | 55 | NeedsAutoMountUnit() bool | ||
275 | 56 | } | ||
276 | 57 | |||
277 | 58 | // Open opens a given snap file with the right backend. | ||
278 | 59 | func Open(path string) (File, error) { | ||
279 | 60 | f, err := os.Open(path) | ||
280 | 61 | if err != nil { | ||
281 | 62 | return nil, fmt.Errorf("cannot open snap: %v", err) | ||
282 | 63 | } | ||
283 | 64 | defer f.Close() | ||
284 | 65 | |||
285 | 66 | // look, libmagic! | ||
286 | 67 | header := make([]byte, 20) | ||
287 | 68 | if _, err := f.ReadAt(header, 0); err != nil { | ||
288 | 69 | return nil, fmt.Errorf("cannot read snap: %v", err) | ||
289 | 70 | } | ||
290 | 71 | // Note that we only support little endian squashfs. There | ||
291 | 72 | // is nothing else with squashfs 4.0. | ||
292 | 73 | if bytes.HasPrefix(header, []byte{'h', 's', 'q', 's'}) { | ||
293 | 74 | return snapfs.New(path), nil | ||
294 | 75 | } | ||
295 | 76 | if strings.HasPrefix(string(header), "!<arch>\ndebian") { | ||
296 | 77 | return clickdeb.Open(path) | ||
297 | 78 | } | ||
298 | 79 | |||
299 | 80 | return nil, fmt.Errorf("cannot open snap: unknown header: %q", header) | ||
300 | 81 | } | ||
301 | 0 | 82 | ||
302 | === renamed file 'pkg/snapfs/pkg.go' => 'pkg/snapfs/snapfs.go' | |||
303 | --- pkg/snapfs/pkg.go 2015-10-08 17:38:48 +0000 | |||
304 | +++ pkg/snapfs/snapfs.go 2015-10-20 15:38:57 +0000 | |||
305 | @@ -27,51 +27,82 @@ | |||
306 | 27 | "path/filepath" | 27 | "path/filepath" |
307 | 28 | "strings" | 28 | "strings" |
308 | 29 | 29 | ||
309 | 30 | "launchpad.net/snappy/dirs" | ||
310 | 30 | "launchpad.net/snappy/helpers" | 31 | "launchpad.net/snappy/helpers" |
311 | 31 | ) | 32 | ) |
312 | 32 | 33 | ||
314 | 33 | // Snap is the squashfs based snap | 34 | // BlobPath is a helper that calculates the blob path from the baseDir. |
315 | 35 | // FIXME: feels wrong (both location and approach). need something better. | ||
316 | 36 | func BlobPath(instDir string) string { | ||
317 | 37 | l := strings.Split(filepath.Clean(instDir), string(filepath.Separator)) | ||
318 | 38 | if len(l) < 2 { | ||
319 | 39 | panic(fmt.Sprintf("invalid path for BlobPath: %q", instDir)) | ||
320 | 40 | } | ||
321 | 41 | |||
322 | 42 | return filepath.Join(dirs.SnapBlobDir, fmt.Sprintf("%s_%s.snap", l[len(l)-2], l[len(l)-1])) | ||
323 | 43 | } | ||
324 | 44 | |||
325 | 45 | // Snap is the squashfs based snap. | ||
326 | 34 | type Snap struct { | 46 | type Snap struct { |
327 | 35 | path string | 47 | path string |
328 | 36 | } | 48 | } |
329 | 37 | 49 | ||
331 | 38 | // Name returns the Name of the backing file | 50 | // Name returns the Name of the backing file. |
332 | 39 | func (s *Snap) Name() string { | 51 | func (s *Snap) Name() string { |
333 | 40 | return filepath.Base(s.path) | 52 | return filepath.Base(s.path) |
334 | 41 | } | 53 | } |
335 | 42 | 54 | ||
337 | 43 | // New returns a new Snapfs snap | 55 | // NeedsAutoMountUnit returns true. |
338 | 56 | func (s *Snap) NeedsAutoMountUnit() bool { | ||
339 | 57 | return true | ||
340 | 58 | } | ||
341 | 59 | |||
342 | 60 | // New returns a new Snapfs snap. | ||
343 | 44 | func New(path string) *Snap { | 61 | func New(path string) *Snap { |
344 | 45 | return &Snap{path: path} | 62 | return &Snap{path: path} |
345 | 46 | } | 63 | } |
346 | 47 | 64 | ||
348 | 48 | // Close is not doing anything for snapfs - COMPAT | 65 | // Close is not doing anything for snapfs. - COMPAT |
349 | 49 | func (s *Snap) Close() error { | 66 | func (s *Snap) Close() error { |
350 | 50 | return nil | 67 | return nil |
351 | 51 | } | 68 | } |
352 | 52 | 69 | ||
354 | 53 | // ControlMember extracts from meta/ - COMPAT | 70 | // ControlMember extracts from meta/. - COMPAT |
355 | 54 | func (s *Snap) ControlMember(controlMember string) ([]byte, error) { | 71 | func (s *Snap) ControlMember(controlMember string) ([]byte, error) { |
356 | 55 | return s.ReadFile(filepath.Join("DEBIAN", controlMember)) | 72 | return s.ReadFile(filepath.Join("DEBIAN", controlMember)) |
357 | 56 | } | 73 | } |
358 | 57 | 74 | ||
360 | 58 | // MetaMember extracts from meta/ - COMPAT | 75 | // MetaMember extracts from meta/. - COMPAT |
361 | 59 | func (s *Snap) MetaMember(metaMember string) ([]byte, error) { | 76 | func (s *Snap) MetaMember(metaMember string) ([]byte, error) { |
362 | 60 | return s.ReadFile(filepath.Join("meta", metaMember)) | 77 | return s.ReadFile(filepath.Join("meta", metaMember)) |
363 | 61 | } | 78 | } |
364 | 62 | 79 | ||
366 | 63 | // ExtractHashes does notthing for snapfs snaps - COMAPT | 80 | // ExtractHashes does notthing for snapfs snaps. - COMAPT |
367 | 64 | func (s *Snap) ExtractHashes(dir string) error { | 81 | func (s *Snap) ExtractHashes(dir string) error { |
368 | 65 | return nil | 82 | return nil |
369 | 66 | } | 83 | } |
370 | 67 | 84 | ||
372 | 68 | // UnpackWithDropPrivs unpacks the meta and puts stuff in place - COMAPT | 85 | // UnpackWithDropPrivs just copies the blob into place. - COMPAT |
373 | 69 | func (s *Snap) UnpackWithDropPrivs(instDir, rootdir string) error { | 86 | func (s *Snap) UnpackWithDropPrivs(instDir, rootdir string) error { |
376 | 70 | // FIXME: actually drop privs | 87 | // FIXME: we need to unpack "meta/*" here because otherwise there |
377 | 71 | return s.Unpack("*", instDir) | 88 | // is no meta/package.yaml for "snappy list -v" for |
378 | 89 | // inactive versions. | ||
379 | 90 | if err := s.UnpackMeta(instDir); err != nil { | ||
380 | 91 | return err | ||
381 | 92 | } | ||
382 | 93 | |||
383 | 94 | // ensure mount-point and blob dir. | ||
384 | 95 | for _, dir := range []string{instDir, dirs.SnapBlobDir} { | ||
385 | 96 | if err := os.MkdirAll(dir, 0755); err != nil { | ||
386 | 97 | return err | ||
387 | 98 | } | ||
388 | 99 | } | ||
389 | 100 | |||
390 | 101 | // FIXME: helpers.CopyFile() has no preserve attribute flag yet | ||
391 | 102 | return runCommand("cp", "-a", s.path, BlobPath(instDir)) | ||
392 | 72 | } | 103 | } |
393 | 73 | 104 | ||
395 | 74 | // UnpackMeta unpacks just the meta/* directory of the given snap | 105 | // UnpackMeta unpacks just the meta/* directory of the given snap. |
396 | 75 | func (s *Snap) UnpackMeta(dst string) error { | 106 | func (s *Snap) UnpackMeta(dst string) error { |
397 | 76 | if err := s.Unpack("meta/*", dst); err != nil { | 107 | if err := s.Unpack("meta/*", dst); err != nil { |
398 | 77 | return err | 108 | return err |
399 | @@ -89,12 +120,12 @@ | |||
400 | 89 | return nil | 120 | return nil |
401 | 90 | } | 121 | } |
402 | 91 | 122 | ||
404 | 92 | // Unpack unpacks the src (which may be a glob into the given target dir | 123 | // Unpack unpacks the src (which may be a glob into the given target dir. |
405 | 93 | func (s *Snap) Unpack(src, dstDir string) error { | 124 | func (s *Snap) Unpack(src, dstDir string) error { |
406 | 94 | return runCommand("unsquashfs", "-f", "-i", "-d", dstDir, s.path, src) | 125 | return runCommand("unsquashfs", "-f", "-i", "-d", dstDir, s.path, src) |
407 | 95 | } | 126 | } |
408 | 96 | 127 | ||
410 | 97 | // ReadFile returns the content of a single file inside a snapfs snap | 128 | // ReadFile returns the content of a single file inside a snapfs snap. |
411 | 98 | func (s *Snap) ReadFile(path string) (content []byte, err error) { | 129 | func (s *Snap) ReadFile(path string) (content []byte, err error) { |
412 | 99 | tmpdir, err := ioutil.TempDir("", "read-file") | 130 | tmpdir, err := ioutil.TempDir("", "read-file") |
413 | 100 | if err != nil { | 131 | if err != nil { |
414 | @@ -110,21 +141,15 @@ | |||
415 | 110 | return ioutil.ReadFile(filepath.Join(unpackDir, path)) | 141 | return ioutil.ReadFile(filepath.Join(unpackDir, path)) |
416 | 111 | } | 142 | } |
417 | 112 | 143 | ||
425 | 113 | // CopyBlob copies the snap to a new place | 144 | // Verify verifies the snap. |
419 | 114 | func (s *Snap) CopyBlob(targetFile string) error { | ||
420 | 115 | // FIXME: helpers.CopyFile() has no preserve attribute flag yet | ||
421 | 116 | return runCommand("cp", "-a", s.path, targetFile) | ||
422 | 117 | } | ||
423 | 118 | |||
424 | 119 | // Verify verifies the snap | ||
426 | 120 | func (s *Snap) Verify(unauthOk bool) error { | 145 | func (s *Snap) Verify(unauthOk bool) error { |
427 | 121 | // FIXME: there is no verification yet for snapfs packages, this | 146 | // FIXME: there is no verification yet for snapfs packages, this |
428 | 122 | // will be done via assertions later for now we rely on | 147 | // will be done via assertions later for now we rely on |
430 | 123 | // the https security | 148 | // the https security. |
431 | 124 | return nil | 149 | return nil |
432 | 125 | } | 150 | } |
433 | 126 | 151 | ||
435 | 127 | // Build builds the snap | 152 | // Build builds the snap. |
436 | 128 | func (s *Snap) Build(buildDir string) error { | 153 | func (s *Snap) Build(buildDir string) error { |
437 | 129 | fullSnapPath, err := filepath.Abs(s.path) | 154 | fullSnapPath, err := filepath.Abs(s.path) |
438 | 130 | if err != nil { | 155 | if err != nil { |
439 | 131 | 156 | ||
440 | === renamed file 'pkg/snapfs/pkg_test.go' => 'pkg/snapfs/snapfs_test.go' | |||
441 | --- pkg/snapfs/pkg_test.go 2015-10-08 14:12:43 +0000 | |||
442 | +++ pkg/snapfs/snapfs_test.go 2015-10-20 15:38:57 +0000 | |||
443 | @@ -93,15 +93,6 @@ | |||
444 | 93 | c.Assert(string(content), Equals, "name: foo") | 93 | c.Assert(string(content), Equals, "name: foo") |
445 | 94 | } | 94 | } |
446 | 95 | 95 | ||
447 | 96 | func (s *SquashfsTestSuite) TestCopyBlob(c *C) { | ||
448 | 97 | snap := makeSnap(c, "name: foo", "") | ||
449 | 98 | dst := filepath.Join(c.MkDir(), "blob.snap") | ||
450 | 99 | |||
451 | 100 | err := snap.CopyBlob(dst) | ||
452 | 101 | c.Assert(err, IsNil) | ||
453 | 102 | c.Assert(helpers.FilesAreEqual(snap.Name(), dst), Equals, true) | ||
454 | 103 | } | ||
455 | 104 | |||
456 | 105 | func (s *SquashfsTestSuite) TestUnpackGlob(c *C) { | 96 | func (s *SquashfsTestSuite) TestUnpackGlob(c *C) { |
457 | 106 | data := "some random data" | 97 | data := "some random data" |
458 | 107 | snap := makeSnap(c, "", data) | 98 | snap := makeSnap(c, "", data) |
459 | 108 | 99 | ||
460 | === removed file 'snappy/pkgformat.go' | |||
461 | --- snappy/pkgformat.go 2015-10-08 17:42:22 +0000 | |||
462 | +++ snappy/pkgformat.go 1970-01-01 00:00:00 +0000 | |||
463 | @@ -1,64 +0,0 @@ | |||
464 | 1 | // -*- Mode: Go; indent-tabs-mode: t -*- | ||
465 | 2 | |||
466 | 3 | /* | ||
467 | 4 | * Copyright (C) 2014-2015 Canonical Ltd | ||
468 | 5 | * | ||
469 | 6 | * This program is free software: you can redistribute it and/or modify | ||
470 | 7 | * it under the terms of the GNU General Public License version 3 as | ||
471 | 8 | * published by the Free Software Foundation. | ||
472 | 9 | * | ||
473 | 10 | * This program is distributed in the hope that it will be useful, | ||
474 | 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
475 | 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
476 | 13 | * GNU General Public License for more details. | ||
477 | 14 | * | ||
478 | 15 | * You should have received a copy of the GNU General Public License | ||
479 | 16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
480 | 17 | * | ||
481 | 18 | */ | ||
482 | 19 | |||
483 | 20 | package snappy | ||
484 | 21 | |||
485 | 22 | import ( | ||
486 | 23 | "bytes" | ||
487 | 24 | "fmt" | ||
488 | 25 | "os" | ||
489 | 26 | "strings" | ||
490 | 27 | |||
491 | 28 | "launchpad.net/snappy/pkg/clickdeb" | ||
492 | 29 | "launchpad.net/snappy/pkg/snapfs" | ||
493 | 30 | ) | ||
494 | 31 | |||
495 | 32 | // PackageFile is the interface to interact with the low-level snap files | ||
496 | 33 | type PackageFile interface { | ||
497 | 34 | Verify(allowUnauthenticated bool) error | ||
498 | 35 | Close() error | ||
499 | 36 | UnpackWithDropPrivs(targetDir, rootDir string) error | ||
500 | 37 | ControlMember(name string) ([]byte, error) | ||
501 | 38 | MetaMember(name string) ([]byte, error) | ||
502 | 39 | ExtractHashes(targetDir string) error | ||
503 | 40 | } | ||
504 | 41 | |||
505 | 42 | // OpenPackageFile opens a given snap file with the right backend | ||
506 | 43 | func OpenPackageFile(path string) (PackageFile, error) { | ||
507 | 44 | f, err := os.Open(path) | ||
508 | 45 | if err != nil { | ||
509 | 46 | return nil, err | ||
510 | 47 | } | ||
511 | 48 | defer f.Close() | ||
512 | 49 | |||
513 | 50 | // look, libmagic! | ||
514 | 51 | header := make([]byte, 20) | ||
515 | 52 | if _, err := f.Read(header); err != nil { | ||
516 | 53 | return nil, err | ||
517 | 54 | } | ||
518 | 55 | // note that we only support little endian squashfs for now | ||
519 | 56 | if bytes.HasPrefix(header, []byte{'h', 's', 'q', 's'}) { | ||
520 | 57 | return snapfs.New(path), nil | ||
521 | 58 | } | ||
522 | 59 | if strings.HasPrefix(string(header), "!<arch>\ndebian") { | ||
523 | 60 | return clickdeb.Open(path) | ||
524 | 61 | } | ||
525 | 62 | |||
526 | 63 | return nil, fmt.Errorf("unknown header %v", header) | ||
527 | 64 | } | ||
528 | 65 | 0 | ||
529 | === modified file 'snappy/snapp.go' | |||
530 | --- snappy/snapp.go 2015-10-15 08:26:11 +0000 | |||
531 | +++ snappy/snapp.go 2015-10-20 15:38:57 +0000 | |||
532 | @@ -44,6 +44,7 @@ | |||
533 | 44 | "launchpad.net/snappy/oauth" | 44 | "launchpad.net/snappy/oauth" |
534 | 45 | "launchpad.net/snappy/pkg" | 45 | "launchpad.net/snappy/pkg" |
535 | 46 | "launchpad.net/snappy/pkg/remote" | 46 | "launchpad.net/snappy/pkg/remote" |
536 | 47 | "launchpad.net/snappy/pkg/snapfs" | ||
537 | 47 | "launchpad.net/snappy/policy" | 48 | "launchpad.net/snappy/policy" |
538 | 48 | "launchpad.net/snappy/progress" | 49 | "launchpad.net/snappy/progress" |
539 | 49 | "launchpad.net/snappy/release" | 50 | "launchpad.net/snappy/release" |
540 | @@ -181,7 +182,7 @@ | |||
541 | 181 | isActive bool | 182 | isActive bool |
542 | 182 | isInstalled bool | 183 | isInstalled bool |
543 | 183 | description string | 184 | description string |
545 | 184 | deb PackageFile | 185 | deb pkg.File |
546 | 185 | basedir string | 186 | basedir string |
547 | 186 | } | 187 | } |
548 | 187 | 188 | ||
549 | @@ -430,7 +431,7 @@ | |||
550 | 430 | // package, as deduced from the license agreement (which might involve asking | 431 | // package, as deduced from the license agreement (which might involve asking |
551 | 431 | // the user), or an error that explains the reason why installation should not | 432 | // the user), or an error that explains the reason why installation should not |
552 | 432 | // proceed. | 433 | // proceed. |
554 | 433 | func (m *packageYaml) checkLicenseAgreement(ag agreer, d PackageFile, currentActiveDir string) error { | 434 | func (m *packageYaml) checkLicenseAgreement(ag agreer, d pkg.File, currentActiveDir string) error { |
555 | 434 | if !m.ExplicitLicenseAgreement { | 435 | if !m.ExplicitLicenseAgreement { |
556 | 435 | return nil | 436 | return nil |
557 | 436 | } | 437 | } |
558 | @@ -541,7 +542,7 @@ | |||
559 | 541 | // Caller should call Close on the pkg. | 542 | // Caller should call Close on the pkg. |
560 | 542 | // TODO: expose that Close. | 543 | // TODO: expose that Close. |
561 | 543 | func NewSnapPartFromSnapFile(snapFile string, origin string, unauthOk bool) (*SnapPart, error) { | 544 | func NewSnapPartFromSnapFile(snapFile string, origin string, unauthOk bool) (*SnapPart, error) { |
563 | 544 | d, err := OpenPackageFile(snapFile) | 545 | d, err := pkg.Open(snapFile) |
564 | 545 | if err != nil { | 546 | if err != nil { |
565 | 546 | return nil, err | 547 | return nil, err |
566 | 547 | } | 548 | } |
567 | @@ -620,7 +621,7 @@ | |||
568 | 620 | // read hash, its ok if its not there, some older versions of | 621 | // read hash, its ok if its not there, some older versions of |
569 | 621 | // snappy did not write this file | 622 | // snappy did not write this file |
570 | 622 | hashesData, err := ioutil.ReadFile(filepath.Join(part.basedir, "meta", "hashes.yaml")) | 623 | hashesData, err := ioutil.ReadFile(filepath.Join(part.basedir, "meta", "hashes.yaml")) |
572 | 623 | if err != nil { | 624 | if err != nil && !os.IsNotExist(err) { |
573 | 624 | return nil, err | 625 | return nil, err |
574 | 625 | } | 626 | } |
575 | 626 | 627 | ||
576 | @@ -999,12 +1000,26 @@ | |||
577 | 999 | } | 1000 | } |
578 | 1000 | } | 1001 | } |
579 | 1001 | 1002 | ||
580 | 1003 | // the hooks must run before the snap is (auto)mounted | ||
581 | 1004 | // the reason is that the click hooks use: | ||
582 | 1005 | // ".click/$name.$origin.manifest" | ||
583 | 1006 | // but because the origin is not known at build time it needs | ||
584 | 1007 | // to be generated at runtime but will be mounted over once | ||
585 | 1008 | // the snap is mounted | ||
586 | 1002 | if err := installClickHooks(s.basedir, s.m, s.origin, inhibitHooks); err != nil { | 1009 | if err := installClickHooks(s.basedir, s.m, s.origin, inhibitHooks); err != nil { |
587 | 1003 | // cleanup the failed hooks | 1010 | // cleanup the failed hooks |
588 | 1004 | removeClickHooks(s.m, s.origin, inhibitHooks) | 1011 | removeClickHooks(s.m, s.origin, inhibitHooks) |
589 | 1005 | return err | 1012 | return err |
590 | 1006 | } | 1013 | } |
591 | 1007 | 1014 | ||
592 | 1015 | // generate the automount unit for the squashfs | ||
593 | 1016 | // FIXME: this is ugly | ||
594 | 1017 | if s.deb != nil && s.deb.NeedsAutoMountUnit() { | ||
595 | 1018 | if err := s.m.addSnapfsAutomount(s.basedir, inhibitHooks, inter); err != nil { | ||
596 | 1019 | return err | ||
597 | 1020 | } | ||
598 | 1021 | } | ||
599 | 1022 | |||
600 | 1008 | // generate the security policy from the package.yaml | 1023 | // generate the security policy from the package.yaml |
601 | 1009 | if err := s.m.addSecurityPolicy(s.basedir); err != nil { | 1024 | if err := s.m.addSecurityPolicy(s.basedir); err != nil { |
602 | 1010 | return err | 1025 | return err |
603 | @@ -1018,7 +1033,6 @@ | |||
604 | 1018 | if err := s.m.addPackageServices(s.basedir, inhibitHooks, inter); err != nil { | 1033 | if err := s.m.addPackageServices(s.basedir, inhibitHooks, inter); err != nil { |
605 | 1019 | return err | 1034 | return err |
606 | 1020 | } | 1035 | } |
607 | 1021 | |||
608 | 1022 | if err := os.Remove(currentActiveSymlink); err != nil && !os.IsNotExist(err) { | 1036 | if err := os.Remove(currentActiveSymlink); err != nil && !os.IsNotExist(err) { |
609 | 1023 | logger.Noticef("Failed to remove %q: %v", currentActiveSymlink, err) | 1037 | logger.Noticef("Failed to remove %q: %v", currentActiveSymlink, err) |
610 | 1024 | } | 1038 | } |
611 | @@ -1068,6 +1082,9 @@ | |||
612 | 1068 | if err := s.m.removeSecurityPolicy(s.basedir); err != nil { | 1082 | if err := s.m.removeSecurityPolicy(s.basedir); err != nil { |
613 | 1069 | return err | 1083 | return err |
614 | 1070 | } | 1084 | } |
615 | 1085 | if err := s.m.removeSnapfsAutomount(s.basedir, inter); err != nil { | ||
616 | 1086 | return err | ||
617 | 1087 | } | ||
618 | 1071 | 1088 | ||
619 | 1072 | if s.Type() == pkg.TypeFramework { | 1089 | if s.Type() == pkg.TypeFramework { |
620 | 1073 | if err := policy.Remove(s.Name(), s.basedir, dirs.GlobalRootDir); err != nil { | 1090 | if err := policy.Remove(s.Name(), s.basedir, dirs.GlobalRootDir); err != nil { |
621 | @@ -1132,11 +1149,19 @@ | |||
622 | 1132 | return err | 1149 | return err |
623 | 1133 | } | 1150 | } |
624 | 1134 | 1151 | ||
625 | 1152 | // unmount squashfs but ignore errors as its ok if the fs is not mounted | ||
626 | 1153 | exec.Command("unmount", "--lazy", filepath.Join(s.basedir)).CombinedOutput() | ||
627 | 1154 | |||
628 | 1135 | err = os.RemoveAll(s.basedir) | 1155 | err = os.RemoveAll(s.basedir) |
629 | 1136 | if err != nil { | 1156 | if err != nil { |
630 | 1137 | return err | 1157 | return err |
631 | 1138 | } | 1158 | } |
632 | 1139 | 1159 | ||
633 | 1160 | err = os.RemoveAll(snapfs.BlobPath(s.basedir)) | ||
634 | 1161 | if err != nil { | ||
635 | 1162 | return err | ||
636 | 1163 | } | ||
637 | 1164 | |||
638 | 1140 | // best effort(?) | 1165 | // best effort(?) |
639 | 1141 | os.Remove(filepath.Dir(s.basedir)) | 1166 | os.Remove(filepath.Dir(s.basedir)) |
640 | 1142 | 1167 | ||
641 | @@ -1996,3 +2021,46 @@ | |||
642 | 1996 | 2021 | ||
643 | 1997 | return env | 2022 | return env |
644 | 1998 | } | 2023 | } |
645 | 2024 | |||
646 | 2025 | func (m *packageYaml) addSnapfsAutomount(baseDir string, inhibitHooks bool, inter interacter) error { | ||
647 | 2026 | squashfsPath := stripGlobalRootDir(snapfs.BlobPath(baseDir)) | ||
648 | 2027 | whereDir := stripGlobalRootDir(baseDir) | ||
649 | 2028 | |||
650 | 2029 | sysd := systemd.New(dirs.GlobalRootDir, inter) | ||
651 | 2030 | _, err := sysd.WriteMountUnitFile(m.Name, squashfsPath, whereDir) | ||
652 | 2031 | if err != nil { | ||
653 | 2032 | return err | ||
654 | 2033 | } | ||
655 | 2034 | autoMountUnitName, err := sysd.WriteAutoMountUnitFile(m.Name, whereDir) | ||
656 | 2035 | if err != nil { | ||
657 | 2036 | return err | ||
658 | 2037 | } | ||
659 | 2038 | |||
660 | 2039 | // we always enable the mount unit even in inhibit hooks | ||
661 | 2040 | if err := sysd.Enable(autoMountUnitName); err != nil { | ||
662 | 2041 | return err | ||
663 | 2042 | } | ||
664 | 2043 | |||
665 | 2044 | if !inhibitHooks { | ||
666 | 2045 | return sysd.Start(autoMountUnitName) | ||
667 | 2046 | } | ||
668 | 2047 | |||
669 | 2048 | return nil | ||
670 | 2049 | } | ||
671 | 2050 | |||
672 | 2051 | func (m *packageYaml) removeSnapfsAutomount(baseDir string, inter interacter) error { | ||
673 | 2052 | sysd := systemd.New(dirs.GlobalRootDir, inter) | ||
674 | 2053 | for _, s := range []string{"mount", "automount"} { | ||
675 | 2054 | unit := systemd.MountUnitPath(stripGlobalRootDir(baseDir), s) | ||
676 | 2055 | if helpers.FileExists(unit) { | ||
677 | 2056 | // we ignore errors, nothing should stop removals | ||
678 | 2057 | _ = sysd.Disable(filepath.Base(unit)) | ||
679 | 2058 | _ = sysd.Stop(filepath.Base(unit), time.Duration(1*time.Second)) | ||
680 | 2059 | if err := os.Remove(unit); err != nil { | ||
681 | 2060 | return err | ||
682 | 2061 | } | ||
683 | 2062 | } | ||
684 | 2063 | } | ||
685 | 2064 | |||
686 | 2065 | return nil | ||
687 | 2066 | } | ||
688 | 1999 | 2067 | ||
689 | === modified file 'snappy/snapp_snapfs_test.go' | |||
690 | --- snappy/snapp_snapfs_test.go 2015-10-08 10:33:37 +0000 | |||
691 | +++ snappy/snapp_snapfs_test.go 2015-10-20 15:38:57 +0000 | |||
692 | @@ -20,11 +20,14 @@ | |||
693 | 20 | package snappy | 20 | package snappy |
694 | 21 | 21 | ||
695 | 22 | import ( | 22 | import ( |
696 | 23 | "io/ioutil" | ||
697 | 24 | "os" | ||
698 | 23 | "path/filepath" | 25 | "path/filepath" |
699 | 24 | 26 | ||
700 | 25 | "launchpad.net/snappy/dirs" | 27 | "launchpad.net/snappy/dirs" |
701 | 26 | "launchpad.net/snappy/helpers" | 28 | "launchpad.net/snappy/helpers" |
702 | 27 | "launchpad.net/snappy/pkg/snapfs" | 29 | "launchpad.net/snappy/pkg/snapfs" |
703 | 30 | "launchpad.net/snappy/systemd" | ||
704 | 28 | 31 | ||
705 | 29 | . "gopkg.in/check.v1" | 32 | . "gopkg.in/check.v1" |
706 | 30 | ) | 33 | ) |
707 | @@ -36,6 +39,12 @@ | |||
708 | 36 | // mocks | 39 | // mocks |
709 | 37 | aaClickHookCmd = "/bin/true" | 40 | aaClickHookCmd = "/bin/true" |
710 | 38 | dirs.SetRootDir(c.MkDir()) | 41 | dirs.SetRootDir(c.MkDir()) |
711 | 42 | os.MkdirAll(filepath.Join(dirs.SnapServicesDir, "multi-user.target.wants"), 0755) | ||
712 | 43 | |||
713 | 44 | // ensure we do not run a real systemd (slows down tests) | ||
714 | 45 | systemd.SystemctlCmd = func(cmd ...string) ([]byte, error) { | ||
715 | 46 | return []byte("ActiveState=inactive\n"), nil | ||
716 | 47 | } | ||
717 | 39 | 48 | ||
718 | 40 | // ensure we use the right builder func (snapfs) | 49 | // ensure we use the right builder func (snapfs) |
719 | 41 | snapBuilderFunc = BuildSnapfsSnap | 50 | snapBuilderFunc = BuildSnapfsSnap |
720 | @@ -70,15 +79,88 @@ | |||
721 | 70 | _, err = part.Install(&MockProgressMeter{}, 0) | 79 | _, err = part.Install(&MockProgressMeter{}, 0) |
722 | 71 | c.Assert(err, IsNil) | 80 | c.Assert(err, IsNil) |
723 | 72 | 81 | ||
735 | 73 | // after install its just on disk for now, note that this will | 82 | // after install the blob is in the right dir |
736 | 74 | // change once the mounting gets added | 83 | c.Assert(helpers.FileExists(filepath.Join(dirs.SnapBlobDir, "hello-app.origin_1.10.snap")), Equals, true) |
737 | 75 | base := filepath.Join(dirs.SnapAppsDir, "hello-app.origin", "1.10") | 84 | |
738 | 76 | for _, needle := range []string{ | 85 | // ensure the right unit is created |
739 | 77 | "bin/foo", | 86 | mup := systemd.MountUnitPath("/apps/hello-app.origin/1.10", "mount") |
740 | 78 | "meta/package.yaml", | 87 | content, err := ioutil.ReadFile(mup) |
741 | 79 | ".click/info/hello-app.origin.manifest", | 88 | c.Assert(err, IsNil) |
742 | 80 | } { | 89 | c.Assert(string(content), Matches, "(?ms).*^Where=/apps/hello-app.origin/1.10") |
743 | 81 | println(needle) | 90 | c.Assert(string(content), Matches, "(?ms).*^What=/var/lib/snappy/snaps/hello-app.origin_1.10.snap") |
744 | 82 | c.Assert(helpers.FileExists(filepath.Join(base, needle)), Equals, true) | 91 | } |
745 | 83 | } | 92 | |
746 | 93 | func (s *SnapfsTestSuite) TestAddSnapfsAutomount(c *C) { | ||
747 | 94 | m := packageYaml{ | ||
748 | 95 | Name: "foo.origin", | ||
749 | 96 | Version: "1.0", | ||
750 | 97 | Architectures: []string{"all"}, | ||
751 | 98 | } | ||
752 | 99 | inter := &MockProgressMeter{} | ||
753 | 100 | err := m.addSnapfsAutomount(filepath.Join(dirs.SnapAppsDir, "foo.origin/1.0"), true, inter) | ||
754 | 101 | c.Assert(err, IsNil) | ||
755 | 102 | |||
756 | 103 | // ensure correct mount unit | ||
757 | 104 | mount, err := ioutil.ReadFile(filepath.Join(dirs.SnapServicesDir, "apps-foo.origin-1.0.mount")) | ||
758 | 105 | c.Assert(err, IsNil) | ||
759 | 106 | c.Assert(string(mount), Equals, `[Unit] | ||
760 | 107 | Description=Snapfs mount unit for foo.origin | ||
761 | 108 | |||
762 | 109 | [Mount] | ||
763 | 110 | What=/var/lib/snappy/snaps/foo.origin_1.0.snap | ||
764 | 111 | Where=/apps/foo.origin/1.0 | ||
765 | 112 | `) | ||
766 | 113 | |||
767 | 114 | // and correct automount unit | ||
768 | 115 | automount, err := ioutil.ReadFile(filepath.Join(dirs.SnapServicesDir, "apps-foo.origin-1.0.automount")) | ||
769 | 116 | c.Assert(err, IsNil) | ||
770 | 117 | c.Assert(string(automount), Equals, `[Unit] | ||
771 | 118 | Description=Snapfs automount unit for foo.origin | ||
772 | 119 | |||
773 | 120 | [Automount] | ||
774 | 121 | Where=/apps/foo.origin/1.0 | ||
775 | 122 | TimeoutIdleSec=30 | ||
776 | 123 | |||
777 | 124 | [Install] | ||
778 | 125 | WantedBy=multi-user.target | ||
779 | 126 | `) | ||
780 | 127 | } | ||
781 | 128 | |||
782 | 129 | func (s *SnapfsTestSuite) TestRemoveSnapfsAutomount(c *C) { | ||
783 | 130 | m := packageYaml{} | ||
784 | 131 | inter := &MockProgressMeter{} | ||
785 | 132 | err := m.addSnapfsAutomount(filepath.Join(dirs.SnapAppsDir, "foo.origin/1.0"), true, inter) | ||
786 | 133 | c.Assert(err, IsNil) | ||
787 | 134 | |||
788 | 135 | // ensure we have the files | ||
789 | 136 | for _, ext := range []string{"mount", "automount"} { | ||
790 | 137 | p := filepath.Join(dirs.SnapServicesDir, "apps-foo.origin-1.0.") + ext | ||
791 | 138 | c.Assert(helpers.FileExists(p), Equals, true) | ||
792 | 139 | } | ||
793 | 140 | |||
794 | 141 | // now call remove and ensure they are gone | ||
795 | 142 | err = m.removeSnapfsAutomount(filepath.Join(dirs.SnapAppsDir, "foo.origin/1.0"), inter) | ||
796 | 143 | c.Assert(err, IsNil) | ||
797 | 144 | for _, ext := range []string{"mount", "automount"} { | ||
798 | 145 | p := filepath.Join(dirs.SnapServicesDir, "apps-foo.origin-1.0.") + ext | ||
799 | 146 | c.Assert(helpers.FileExists(p), Equals, false) | ||
800 | 147 | } | ||
801 | 148 | } | ||
802 | 149 | |||
803 | 150 | func (s *SnapfsTestSuite) TestRemoveViaSnapfsWorks(c *C) { | ||
804 | 151 | snapPkg := makeTestSnapPackage(c, packageHello) | ||
805 | 152 | part, err := NewSnapPartFromSnapFile(snapPkg, "origin", true) | ||
806 | 153 | c.Assert(err, IsNil) | ||
807 | 154 | |||
808 | 155 | _, err = part.Install(&MockProgressMeter{}, 0) | ||
809 | 156 | c.Assert(err, IsNil) | ||
810 | 157 | |||
811 | 158 | // after install the blob is in the right dir | ||
812 | 159 | c.Assert(helpers.FileExists(filepath.Join(dirs.SnapBlobDir, "hello-app.origin_1.10.snap")), Equals, true) | ||
813 | 160 | |||
814 | 161 | // now remove and ensure its gone | ||
815 | 162 | err = part.Uninstall(&MockProgressMeter{}) | ||
816 | 163 | c.Assert(err, IsNil) | ||
817 | 164 | c.Assert(helpers.FileExists(filepath.Join(dirs.SnapBlobDir, "hello-app.origin_1.10.snap")), Equals, false) | ||
818 | 165 | |||
819 | 84 | } | 166 | } |
820 | 85 | 167 | ||
821 | === modified file 'systemd/systemd.go' | |||
822 | --- systemd/systemd.go 2015-09-15 14:53:38 +0000 | |||
823 | +++ systemd/systemd.go 2015-10-20 15:38:57 +0000 | |||
824 | @@ -33,6 +33,7 @@ | |||
825 | 33 | "text/template" | 33 | "text/template" |
826 | 34 | "time" | 34 | "time" |
827 | 35 | 35 | ||
828 | 36 | "launchpad.net/snappy/dirs" | ||
829 | 36 | "launchpad.net/snappy/helpers" | 37 | "launchpad.net/snappy/helpers" |
830 | 37 | "launchpad.net/snappy/logger" | 38 | "launchpad.net/snappy/logger" |
831 | 38 | ) | 39 | ) |
832 | @@ -95,6 +96,8 @@ | |||
833 | 95 | Status(service string) (string, error) | 96 | Status(service string) (string, error) |
834 | 96 | ServiceStatus(service string) (*ServiceStatus, error) | 97 | ServiceStatus(service string) (*ServiceStatus, error) |
835 | 97 | Logs(services []string) ([]Log, error) | 98 | Logs(services []string) ([]Log, error) |
836 | 99 | WriteMountUnitFile(name, what, where string) (string, error) | ||
837 | 100 | WriteAutoMountUnitFile(name, where string) (string, error) | ||
838 | 98 | } | 101 | } |
839 | 99 | 102 | ||
840 | 100 | // A Log is a single entry in the systemd journal | 103 | // A Log is a single entry in the systemd journal |
841 | @@ -510,3 +513,46 @@ | |||
842 | 510 | func (l Log) String() string { | 513 | func (l Log) String() string { |
843 | 511 | return fmt.Sprintf("%s %s %s", l.Timestamp(), l.SID(), l.Message()) | 514 | return fmt.Sprintf("%s %s %s", l.Timestamp(), l.SID(), l.Message()) |
844 | 512 | } | 515 | } |
845 | 516 | |||
846 | 517 | // MountUnitPath returns the path of a {,auto}mount unit | ||
847 | 518 | func MountUnitPath(baseDir, ext string) string { | ||
848 | 519 | // FIXME: use github.com/coreos/go-systemd/unit/escape.go | ||
849 | 520 | // once we can update go-systemd. we can not right now | ||
850 | 521 | // because versions >= 2 are not compatible with go1.3 from | ||
851 | 522 | // 15.04 | ||
852 | 523 | p, err := exec.Command("systemd-escape", "--path", baseDir).CombinedOutput() | ||
853 | 524 | if err != nil { | ||
854 | 525 | panic(fmt.Sprintf("systemd-escape failed for %s with %q", baseDir, p)) | ||
855 | 526 | } | ||
856 | 527 | escapedPath := strings.TrimSpace(string(p)) | ||
857 | 528 | return filepath.Join(dirs.SnapServicesDir, fmt.Sprintf("%s.%s", escapedPath, ext)) | ||
858 | 529 | } | ||
859 | 530 | |||
860 | 531 | func (s *systemd) WriteMountUnitFile(name, what, where string) (string, error) { | ||
861 | 532 | c := fmt.Sprintf(`[Unit] | ||
862 | 533 | Description=Snapfs mount unit for %s | ||
863 | 534 | |||
864 | 535 | [Mount] | ||
865 | 536 | What=%s | ||
866 | 537 | Where=%s | ||
867 | 538 | `, name, what, where) | ||
868 | 539 | |||
869 | 540 | mu := MountUnitPath(where, "mount") | ||
870 | 541 | return filepath.Base(mu), helpers.AtomicWriteFile(mu, []byte(c), 0644) | ||
871 | 542 | } | ||
872 | 543 | |||
873 | 544 | func (s *systemd) WriteAutoMountUnitFile(name, where string) (string, error) { | ||
874 | 545 | c := fmt.Sprintf(`[Unit] | ||
875 | 546 | Description=Snapfs automount unit for %s | ||
876 | 547 | |||
877 | 548 | [Automount] | ||
878 | 549 | Where=%s | ||
879 | 550 | TimeoutIdleSec=30 | ||
880 | 551 | |||
881 | 552 | [Install] | ||
882 | 553 | WantedBy=multi-user.target | ||
883 | 554 | `, name, where) | ||
884 | 555 | |||
885 | 556 | mu := MountUnitPath(where, "automount") | ||
886 | 557 | return filepath.Base(mu), helpers.AtomicWriteFile(MountUnitPath(where, "automount"), []byte(c), 0644) | ||
887 | 558 | } | ||
888 | 513 | 559 | ||
889 | === modified file 'systemd/systemd_test.go' | |||
890 | --- systemd/systemd_test.go 2015-09-14 12:29:15 +0000 | |||
891 | +++ systemd/systemd_test.go 2015-10-20 15:38:57 +0000 | |||
892 | @@ -21,6 +21,7 @@ | |||
893 | 21 | 21 | ||
894 | 22 | import ( | 22 | import ( |
895 | 23 | "fmt" | 23 | "fmt" |
896 | 24 | "io/ioutil" | ||
897 | 24 | "os" | 25 | "os" |
898 | 25 | "path/filepath" | 26 | "path/filepath" |
899 | 26 | "testing" | 27 | "testing" |
900 | @@ -28,6 +29,7 @@ | |||
901 | 28 | 29 | ||
902 | 29 | . "gopkg.in/check.v1" | 30 | . "gopkg.in/check.v1" |
903 | 30 | 31 | ||
904 | 32 | "launchpad.net/snappy/dirs" | ||
905 | 31 | "launchpad.net/snappy/helpers" | 33 | "launchpad.net/snappy/helpers" |
906 | 32 | ) | 34 | ) |
907 | 33 | 35 | ||
908 | @@ -411,3 +413,40 @@ | |||
909 | 411 | }.String(), Equals, "1970-01-01T00:00:00.000042Z me hi") | 413 | }.String(), Equals, "1970-01-01T00:00:00.000042Z me hi") |
910 | 412 | 414 | ||
911 | 413 | } | 415 | } |
912 | 416 | |||
913 | 417 | func (s *SystemdTestSuite) TestMountUnitPath(c *C) { | ||
914 | 418 | c.Assert(MountUnitPath("/apps/hello.origin/1.1", "mount"), Equals, filepath.Join(dirs.SnapServicesDir, "apps-hello.origin-1.1.mount")) | ||
915 | 419 | } | ||
916 | 420 | |||
917 | 421 | func (s *SystemdTestSuite) TestWriteMountUnit(c *C) { | ||
918 | 422 | mountUnitName, err := New("", nil).WriteMountUnitFile("foo.origin", "/var/lib/snappy/snaps/foo.origin_1.0.snap", "/apps/foo.origin/1.0") | ||
919 | 423 | c.Assert(err, IsNil) | ||
920 | 424 | |||
921 | 425 | mount, err := ioutil.ReadFile(filepath.Join(dirs.SnapServicesDir, mountUnitName)) | ||
922 | 426 | c.Assert(err, IsNil) | ||
923 | 427 | c.Assert(string(mount), Equals, `[Unit] | ||
924 | 428 | Description=Snapfs mount unit for foo.origin | ||
925 | 429 | |||
926 | 430 | [Mount] | ||
927 | 431 | What=/var/lib/snappy/snaps/foo.origin_1.0.snap | ||
928 | 432 | Where=/apps/foo.origin/1.0 | ||
929 | 433 | `) | ||
930 | 434 | } | ||
931 | 435 | |||
932 | 436 | func (s *SystemdTestSuite) TestWriteAutoMountUnit(c *C) { | ||
933 | 437 | mountUnitName, err := New("", nil).WriteAutoMountUnitFile("foo.origin", "/apps/foo.origin/1.0") | ||
934 | 438 | c.Assert(err, IsNil) | ||
935 | 439 | |||
936 | 440 | automount, err := ioutil.ReadFile(filepath.Join(dirs.SnapServicesDir, mountUnitName)) | ||
937 | 441 | c.Assert(err, IsNil) | ||
938 | 442 | c.Assert(string(automount), Equals, `[Unit] | ||
939 | 443 | Description=Snapfs automount unit for foo.origin | ||
940 | 444 | |||
941 | 445 | [Automount] | ||
942 | 446 | Where=/apps/foo.origin/1.0 | ||
943 | 447 | TimeoutIdleSec=30 | ||
944 | 448 | |||
945 | 449 | [Install] | ||
946 | 450 | WantedBy=multi-user.target | ||
947 | 451 | `) | ||
948 | 452 | } |
4. put the .snap in /var/snappy/blobs?