Merge lp:~mvo/snappy/snappy-snapfs-mount into lp:~snappy-dev/snappy/snappy-moved-to-github

Proposed by Michael Vogt
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
Reviewer Review Type Date Requested Status
Gustavo Niemeyer Approve
John Lenton (community) Approve
Review via email: mp+273883@code.launchpad.net

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/$pkg/$version/blob.snap
and will mount that to:
  /apps/$pkg/$version/run

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/$pkg/$pkg_$version.snap" and auto-mount it to /apps/$pkg/$version
    (which complicates the removal a bit because its no longer enough to just rm -rf the /apps/$pkg/$version dir)

To post a comment you must log in.
Revision history for this message
John Lenton (chipaca) wrote :

4. put the .snap in /var/snappy/blobs?

Revision history for this message
John Lenton (chipaca) wrote :

*/var/lib/snappy/blobs i meant. But /var/lib/snappy/snaps is probably better.

A small complication of remove and purge, but other than that it'd work, right?

Revision history for this message
John Lenton (chipaca) :
Revision history for this message
Michael Vogt (mvo) wrote :

Thanks John! So with /var/lib/snappy/snaps we would setup the automount unit to mount /apps/$name/$ver - this would mean that a "snappy list" would always automount the snaps. My current approach was to only mount them when they are used and unpack the meta/* only. But maybe thats premature optimization(?).

Revision history for this message
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.

Revision history for this message
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,

lp:~mvo/snappy/snappy-snapfs-mount updated
754. By Michael Vogt

merged lp:snappy

755. By Michael Vogt

refactor and get rid of runPrefix

Revision history for this message
Michael Vogt (mvo) wrote :

Thanks, I pondered over this too and I like the approach of /var/lib/snappy/snaps and that we can use husks^Wleightweight to introspect without the full mount dance. I will update the MP accordingly.

lp:~mvo/snappy/snappy-snapfs-mount updated
756. By Michael Vogt

ensure removal removes the snapfs and refactor

757. By Michael Vogt

update comments

758. By Michael Vogt

unmount after deactivate

Revision history for this message
Michael Vogt (mvo) wrote :

A new look would be welcome :)

lp:~mvo/snappy/snappy-snapfs-mount updated
759. By Michael Vogt

improve comments

Revision history for this message
John Lenton (chipaca) wrote :

Some comments.

Need to iterate the review a little.

Revision history for this message
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

lp:~mvo/snappy/snappy-snapfs-mount updated
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

Revision history for this message
Michael Vogt (mvo) wrote :

Thanks for the review! I addressed the comemnts, please let me know if its sufficient.

Revision history for this message
John Lenton (chipaca) :
lp:~mvo/snappy/snappy-snapfs-mount updated
764. By Michael Vogt

pkg/snapfs/snapfs.go: use filepath.Clean() in BlobPath

Revision history for this message
Michael Vogt (mvo) wrote :

Thanks! Adressing more stuff.

Revision history for this message
John Lenton (chipaca) wrote :

This looks good enough, now :)

review: Approve
Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

I'm adding a few comments below. Please feel free to merge this once you're personally happy about it.

review: Approve
Revision history for this message
John Lenton (chipaca) :
lp:~mvo/snappy/snappy-snapfs-mount updated
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)

Revision history for this message
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'debian/ubuntu-snappy-cli.dirs'
--- debian/ubuntu-snappy-cli.dirs 1970-01-01 00:00:00 +0000
+++ debian/ubuntu-snappy-cli.dirs 2015-10-20 15:38:57 +0000
@@ -0,0 +1,1 @@
1/var/lib/snappy/snaps
02
=== modified file 'dirs/dirs.go'
--- dirs/dirs.go 2015-09-25 15:27:11 +0000
+++ dirs/dirs.go 2015-10-20 15:38:57 +0000
@@ -35,6 +35,7 @@
35 LocaleDir string35 LocaleDir string
36 SnapIconsDir string36 SnapIconsDir string
37 SnapMetaDir string37 SnapMetaDir string
38 SnapBlobDir string
3839
39 SnapBinariesDir string40 SnapBinariesDir string
40 SnapServicesDir string41 SnapServicesDir string
@@ -59,6 +60,7 @@
59 SnapSeccompDir = filepath.Join(rootdir, SnappyDir, "seccomp", "profiles")60 SnapSeccompDir = filepath.Join(rootdir, SnappyDir, "seccomp", "profiles")
60 SnapIconsDir = filepath.Join(rootdir, SnappyDir, "icons")61 SnapIconsDir = filepath.Join(rootdir, SnappyDir, "icons")
61 SnapMetaDir = filepath.Join(rootdir, SnappyDir, "meta")62 SnapMetaDir = filepath.Join(rootdir, SnappyDir, "meta")
63 SnapBlobDir = filepath.Join(rootdir, SnappyDir, "snaps")
6264
63 SnapBinariesDir = filepath.Join(SnapAppsDir, "bin")65 SnapBinariesDir = filepath.Join(SnapAppsDir, "bin")
64 SnapServicesDir = filepath.Join(rootdir, "/etc/systemd/system")66 SnapServicesDir = filepath.Join(rootdir, "/etc/systemd/system")
6567
=== modified file 'pkg/clickdeb/deb.go'
--- pkg/clickdeb/deb.go 2015-10-08 08:29:16 +0000
+++ pkg/clickdeb/deb.go 2015-10-20 15:38:57 +0000
@@ -40,33 +40,34 @@
4040
41var (41var (
42 // ErrSnapInvalidContent is returned if a snap package contains42 // ErrSnapInvalidContent is returned if a snap package contains
43 // invalid content43 // invalid content.
44 ErrSnapInvalidContent = errors.New("snap contains invalid content")44 ErrSnapInvalidContent = errors.New("snap contains invalid content")
4545
46 // ErrMemberNotFound is returned when a tar member is not found in the archive46 // ErrMemberNotFound is returned when a tar member is not found in
47 // the archive.
47 ErrMemberNotFound = errors.New("member not found")48 ErrMemberNotFound = errors.New("member not found")
48)49)
4950
50// ErrUnpackFailed is the error type for a snap unpack problem51// ErrUnpackFailed is the error type for a snap unpack problem.
51type ErrUnpackFailed struct {52type ErrUnpackFailed struct {
52 snapFile string53 snapFile string
53 instDir string54 instDir string
54 origErr error55 origErr error
55}56}
5657
57// ErrUnpackFailed is returned if unpacking a snap fails58// ErrUnpackFailed is returned if unpacking a snap fails.
58func (e *ErrUnpackFailed) Error() string {59func (e *ErrUnpackFailed) Error() string {
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)
60}61}
6162
62// simple pipe based xz reader63// Simple pipe based xz reader.
63func xzPipeReader(r io.Reader) io.Reader {64func xzPipeReader(r io.Reader) io.Reader {
64 pr, pw := io.Pipe()65 pr, pw := io.Pipe()
65 cmd := exec.Command("xz", "--decompress", "--stdout")66 cmd := exec.Command("xz", "--decompress", "--stdout")
66 cmd.Stdin = r67 cmd.Stdin = r
67 cmd.Stdout = pw68 cmd.Stdout = pw
6869
69 // run xz in its own go-routine70 // run xz in its own go-routine.
70 go func() {71 go func() {
71 pw.CloseWithError(cmd.Run())72 pw.CloseWithError(cmd.Run())
72 }()73 }()
@@ -74,7 +75,7 @@
74 return pr75 return pr
75}76}
7677
77// simple pipe based xz writer78// Simple pipe based xz writer.
78type xzPipeWriter struct {79type xzPipeWriter struct {
79 cmd *exec.Cmd80 cmd *exec.Cmd
80 w io.Writer81 w io.Writer
@@ -93,7 +94,7 @@
93 x.cmd.Stdout = x.w94 x.cmd.Stdout = x.w
94 x.cmd.Stderr = os.Stderr95 x.cmd.Stderr = os.Stderr
9596
96 // Start is async97 // Start is async.
97 x.cmd.Start()98 x.cmd.Start()
9899
99 return x100 return x
@@ -108,7 +109,7 @@
108 return x.cmd.Wait()109 return x.cmd.Wait()
109}110}
110111
111// ensure that the content of our data is valid:112// Ensure that the content of our data is valid:
112// - no relative path allowed to prevent writing outside of the parent dir113// - no relative path allowed to prevent writing outside of the parent dir
113func clickVerifyContentFn(path string) (string, error) {114func clickVerifyContentFn(path string) (string, error) {
114 path = filepath.Clean(path)115 path = filepath.Clean(path)
@@ -122,7 +123,7 @@
122}123}
123124
124// ClickDeb provides support for the "click" containers (a special kind of125// ClickDeb provides support for the "click" containers (a special kind of
125// deb package)126// deb package).
126type ClickDeb struct {127type ClickDeb struct {
127 file *os.File128 file *os.File
128}129}
@@ -145,29 +146,34 @@
145 return &ClickDeb{f}, nil146 return &ClickDeb{f}, nil
146}147}
147148
148// Name returns the Name of the backing file149// Name returns the Name of the backing file.
149func (d *ClickDeb) Name() string {150func (d *ClickDeb) Name() string {
150 return d.file.Name()151 return d.file.Name()
151}152}
152153
153// Close closes the backing file154// NeedsAutoMountUnit returns false.
155func (d *ClickDeb) NeedsAutoMountUnit() bool {
156 return false
157}
158
159// Close closes the backing file.
154func (d *ClickDeb) Close() error {160func (d *ClickDeb) Close() error {
155 return d.file.Close()161 return d.file.Close()
156}162}
157163
158// Verify checks that the clickdeb is signed164// Verify checks that the clickdeb is signed.
159func (d *ClickDeb) Verify(allowUnauthenticated bool) error {165func (d *ClickDeb) Verify(allowUnauthenticated bool) error {
160 return Verify(d.Name(), allowUnauthenticated)166 return Verify(d.Name(), allowUnauthenticated)
161}167}
162168
163// ControlMember returns the content of the given control member file169// ControlMember returns the content of the given control member file
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).
165func (d *ClickDeb) ControlMember(controlMember string) (content []byte, err error) {171func (d *ClickDeb) ControlMember(controlMember string) (content []byte, err error) {
166 return d.member("control.tar", controlMember)172 return d.member("control.tar", controlMember)
167}173}
168174
169// MetaMember returns the content of the given meta file (e.g. the content of175// MetaMember returns the content of the given meta file (e.g. the content of
170// the "package.yaml" file) from the data.tar.gz ar member's meta/ directory176// the "package.yaml" file) from the data.tar.gz ar member's meta/ directory.
171func (d *ClickDeb) MetaMember(metaMember string) (content []byte, err error) {177func (d *ClickDeb) MetaMember(metaMember string) (content []byte, err error) {
172 return d.member("data.tar", filepath.Join("meta", metaMember))178 return d.member("data.tar", filepath.Join("meta", metaMember))
173}179}
@@ -213,7 +219,7 @@
213}219}
214220
215// ExtractHashes reads "hashes.yaml" from the clickdeb and writes it to221// ExtractHashes reads "hashes.yaml" from the clickdeb and writes it to
216// the given directory222// the given directory.
217func (d *ClickDeb) ExtractHashes(dir string) error {223func (d *ClickDeb) ExtractHashes(dir string) error {
218 hashesFile := filepath.Join(dir, "hashes.yaml")224 hashesFile := filepath.Join(dir, "hashes.yaml")
219 hashesData, err := d.ControlMember("hashes.yaml")225 hashesData, err := d.ControlMember("hashes.yaml")
@@ -226,7 +232,7 @@
226232
227// Unpack unpacks the data.tar.{gz,bz2,xz} into the given target directory233// Unpack unpacks the data.tar.{gz,bz2,xz} into the given target directory
228// with click specific verification, i.e. no files will be extracted outside234// with click specific verification, i.e. no files will be extracted outside
229// of the targetdir (no ".." inside the data.tar is allowed)235// of the targetdir (no ".." inside the data.tar is allowed).
230func (d *ClickDeb) Unpack(targetDir string) error {236func (d *ClickDeb) Unpack(targetDir string) error {
231 var err error237 var err error
232238
@@ -244,7 +250,7 @@
244 return helpers.UnpackTar(dataReader, targetDir, clickVerifyContentFn)250 return helpers.UnpackTar(dataReader, targetDir, clickVerifyContentFn)
245}251}
246252
247// FIXME: this should move into the "ar" library itself253// FIXME: this should move into the "ar" library itself.
248func addFileToAr(arWriter *ar.Writer, filename string) error {254func addFileToAr(arWriter *ar.Writer, filename string) error {
249 dataF, err := os.Open(filename)255 dataF, err := os.Open(filename)
250 if err != nil {256 if err != nil {
@@ -276,7 +282,7 @@
276 return nil282 return nil
277}283}
278284
279// FIXME: this should move into the "ar" library itself285// FIXME: this should move into the "ar" library itself.
280func addDataToAr(arWriter *ar.Writer, filename string, data []byte) error {286func addDataToAr(arWriter *ar.Writer, filename string, data []byte) error {
281 size := int64(len(data))287 size := int64(len(data))
282 hdr := &ar.Header{288 hdr := &ar.Header{
@@ -295,11 +301,11 @@
295}301}
296302
297// tarExcludeFunc is a helper for tarCreate that is called for each file303// tarExcludeFunc is a helper for tarCreate that is called for each file
298// that is about to be added. If it returns "false" the file is skipped304// that is about to be added. If it returns "false" the file is skipped.
299type tarExcludeFunc func(path string) bool305type tarExcludeFunc func(path string) bool
300306
301// tarCreate creates a tarfile for a clickdeb, all files in the archive307// tarCreate creates a tarfile for a clickdeb, all files in the archive
302// belong to root (same as dpkg-deb)308// belong to root (same as dpkg-deb).
303func tarCreate(tarname string, sourceDir string, fn tarExcludeFunc) error {309func tarCreate(tarname string, sourceDir string, fn tarExcludeFunc) error {
304 w, err := os.Create(tarname)310 w, err := os.Create(tarname)
305 if err != nil {311 if err != nil {
@@ -397,7 +403,7 @@
397}403}
398404
399// Build takes a build debian directory with DEBIAN/ dir and creates a405// Build takes a build debian directory with DEBIAN/ dir and creates a
400// clickdeb from it406// clickdeb from it.
401func (d *ClickDeb) Build(sourceDir string, dataTarFinishedCallback func(dataName string) error) error {407func (d *ClickDeb) Build(sourceDir string, dataTarFinishedCallback func(dataName string) error) error {
402 var err error408 var err error
403409
@@ -496,7 +502,7 @@
496// target dir and drop privs when doing this.502// target dir and drop privs when doing this.
497//503//
498// To do this reliably in go we need to exec a helper as we can not504// To do this reliably in go we need to exec a helper as we can not
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).
500func (d *ClickDeb) UnpackWithDropPrivs(instDir, rootdir string) error {506func (d *ClickDeb) UnpackWithDropPrivs(instDir, rootdir string) error {
501 // no need to drop privs, we are not root507 // no need to drop privs, we are not root
502 if !helpers.ShouldDropPrivs() {508 if !helpers.ShouldDropPrivs() {
503509
=== added file 'pkg/file.go'
--- pkg/file.go 1970-01-01 00:00:00 +0000
+++ pkg/file.go 2015-10-20 15:38:57 +0000
@@ -0,0 +1,81 @@
1// -*- Mode: Go; indent-tabs-mode: t -*-
2
3/*
4 * Copyright (C) 2015 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
20package pkg
21
22import (
23 "bytes"
24 "fmt"
25 "os"
26 "strings"
27
28 "launchpad.net/snappy/pkg/clickdeb"
29 "launchpad.net/snappy/pkg/snapfs"
30)
31
32// File is the interface to interact with the low-level snap files.
33type File interface {
34 // Verify verfies the integrity of the file.
35 // FIXME: use flags here instead of a boolean
36 Verify(allowUnauthenticated bool) error
37 // Close closes the snapfile.
38 // FIXME: this can go away once we no longer support clickdebs.
39 Close() error
40 // UnpackWithDropPrivs unpacks the given the snap to the given
41 // targetdir relative to the given rootDir.
42 // FIXME: name leaks implementation details, should be Unpack()
43 UnpackWithDropPrivs(targetDir, rootDir string) error
44 // ControlMember returns the content of snap meta data files.
45 ControlMember(name string) ([]byte, error)
46 // MetaMember returns the content of snap meta data files.
47 // FIXME: redundant
48 MetaMember(name string) ([]byte, error)
49 // ExtractHashes extracs the hashes from the snap and puts
50 // them into the filesystem for verification.
51 ExtractHashes(targetDir string) error
52
53 // NeedsAutoMountUnit determines if it's required to setup
54 // an automount unit for the snap when the snap is activated
55 NeedsAutoMountUnit() bool
56}
57
58// Open opens a given snap file with the right backend.
59func Open(path string) (File, error) {
60 f, err := os.Open(path)
61 if err != nil {
62 return nil, fmt.Errorf("cannot open snap: %v", err)
63 }
64 defer f.Close()
65
66 // look, libmagic!
67 header := make([]byte, 20)
68 if _, err := f.ReadAt(header, 0); err != nil {
69 return nil, fmt.Errorf("cannot read snap: %v", err)
70 }
71 // Note that we only support little endian squashfs. There
72 // is nothing else with squashfs 4.0.
73 if bytes.HasPrefix(header, []byte{'h', 's', 'q', 's'}) {
74 return snapfs.New(path), nil
75 }
76 if strings.HasPrefix(string(header), "!<arch>\ndebian") {
77 return clickdeb.Open(path)
78 }
79
80 return nil, fmt.Errorf("cannot open snap: unknown header: %q", header)
81}
082
=== renamed file 'pkg/snapfs/pkg.go' => 'pkg/snapfs/snapfs.go'
--- pkg/snapfs/pkg.go 2015-10-08 17:38:48 +0000
+++ pkg/snapfs/snapfs.go 2015-10-20 15:38:57 +0000
@@ -27,51 +27,82 @@
27 "path/filepath"27 "path/filepath"
28 "strings"28 "strings"
2929
30 "launchpad.net/snappy/dirs"
30 "launchpad.net/snappy/helpers"31 "launchpad.net/snappy/helpers"
31)32)
3233
33// Snap is the squashfs based snap34// BlobPath is a helper that calculates the blob path from the baseDir.
35// FIXME: feels wrong (both location and approach). need something better.
36func BlobPath(instDir string) string {
37 l := strings.Split(filepath.Clean(instDir), string(filepath.Separator))
38 if len(l) < 2 {
39 panic(fmt.Sprintf("invalid path for BlobPath: %q", instDir))
40 }
41
42 return filepath.Join(dirs.SnapBlobDir, fmt.Sprintf("%s_%s.snap", l[len(l)-2], l[len(l)-1]))
43}
44
45// Snap is the squashfs based snap.
34type Snap struct {46type Snap struct {
35 path string47 path string
36}48}
3749
38// Name returns the Name of the backing file50// Name returns the Name of the backing file.
39func (s *Snap) Name() string {51func (s *Snap) Name() string {
40 return filepath.Base(s.path)52 return filepath.Base(s.path)
41}53}
4254
43// New returns a new Snapfs snap55// NeedsAutoMountUnit returns true.
56func (s *Snap) NeedsAutoMountUnit() bool {
57 return true
58}
59
60// New returns a new Snapfs snap.
44func New(path string) *Snap {61func New(path string) *Snap {
45 return &Snap{path: path}62 return &Snap{path: path}
46}63}
4764
48// Close is not doing anything for snapfs - COMPAT65// Close is not doing anything for snapfs. - COMPAT
49func (s *Snap) Close() error {66func (s *Snap) Close() error {
50 return nil67 return nil
51}68}
5269
53// ControlMember extracts from meta/ - COMPAT70// ControlMember extracts from meta/. - COMPAT
54func (s *Snap) ControlMember(controlMember string) ([]byte, error) {71func (s *Snap) ControlMember(controlMember string) ([]byte, error) {
55 return s.ReadFile(filepath.Join("DEBIAN", controlMember))72 return s.ReadFile(filepath.Join("DEBIAN", controlMember))
56}73}
5774
58// MetaMember extracts from meta/ - COMPAT75// MetaMember extracts from meta/. - COMPAT
59func (s *Snap) MetaMember(metaMember string) ([]byte, error) {76func (s *Snap) MetaMember(metaMember string) ([]byte, error) {
60 return s.ReadFile(filepath.Join("meta", metaMember))77 return s.ReadFile(filepath.Join("meta", metaMember))
61}78}
6279
63// ExtractHashes does notthing for snapfs snaps - COMAPT80// ExtractHashes does notthing for snapfs snaps. - COMAPT
64func (s *Snap) ExtractHashes(dir string) error {81func (s *Snap) ExtractHashes(dir string) error {
65 return nil82 return nil
66}83}
6784
68// UnpackWithDropPrivs unpacks the meta and puts stuff in place - COMAPT85// UnpackWithDropPrivs just copies the blob into place. - COMPAT
69func (s *Snap) UnpackWithDropPrivs(instDir, rootdir string) error {86func (s *Snap) UnpackWithDropPrivs(instDir, rootdir string) error {
70 // FIXME: actually drop privs87 // FIXME: we need to unpack "meta/*" here because otherwise there
71 return s.Unpack("*", instDir)88 // is no meta/package.yaml for "snappy list -v" for
89 // inactive versions.
90 if err := s.UnpackMeta(instDir); err != nil {
91 return err
92 }
93
94 // ensure mount-point and blob dir.
95 for _, dir := range []string{instDir, dirs.SnapBlobDir} {
96 if err := os.MkdirAll(dir, 0755); err != nil {
97 return err
98 }
99 }
100
101 // FIXME: helpers.CopyFile() has no preserve attribute flag yet
102 return runCommand("cp", "-a", s.path, BlobPath(instDir))
72}103}
73104
74// UnpackMeta unpacks just the meta/* directory of the given snap105// UnpackMeta unpacks just the meta/* directory of the given snap.
75func (s *Snap) UnpackMeta(dst string) error {106func (s *Snap) UnpackMeta(dst string) error {
76 if err := s.Unpack("meta/*", dst); err != nil {107 if err := s.Unpack("meta/*", dst); err != nil {
77 return err108 return err
@@ -89,12 +120,12 @@
89 return nil120 return nil
90}121}
91122
92// Unpack unpacks the src (which may be a glob into the given target dir123// Unpack unpacks the src (which may be a glob into the given target dir.
93func (s *Snap) Unpack(src, dstDir string) error {124func (s *Snap) Unpack(src, dstDir string) error {
94 return runCommand("unsquashfs", "-f", "-i", "-d", dstDir, s.path, src)125 return runCommand("unsquashfs", "-f", "-i", "-d", dstDir, s.path, src)
95}126}
96127
97// ReadFile returns the content of a single file inside a snapfs snap128// ReadFile returns the content of a single file inside a snapfs snap.
98func (s *Snap) ReadFile(path string) (content []byte, err error) {129func (s *Snap) ReadFile(path string) (content []byte, err error) {
99 tmpdir, err := ioutil.TempDir("", "read-file")130 tmpdir, err := ioutil.TempDir("", "read-file")
100 if err != nil {131 if err != nil {
@@ -110,21 +141,15 @@
110 return ioutil.ReadFile(filepath.Join(unpackDir, path))141 return ioutil.ReadFile(filepath.Join(unpackDir, path))
111}142}
112143
113// CopyBlob copies the snap to a new place144// Verify verifies the snap.
114func (s *Snap) CopyBlob(targetFile string) error {
115 // FIXME: helpers.CopyFile() has no preserve attribute flag yet
116 return runCommand("cp", "-a", s.path, targetFile)
117}
118
119// Verify verifies the snap
120func (s *Snap) Verify(unauthOk bool) error {145func (s *Snap) Verify(unauthOk bool) error {
121 // FIXME: there is no verification yet for snapfs packages, this146 // FIXME: there is no verification yet for snapfs packages, this
122 // will be done via assertions later for now we rely on147 // will be done via assertions later for now we rely on
123 // the https security148 // the https security.
124 return nil149 return nil
125}150}
126151
127// Build builds the snap152// Build builds the snap.
128func (s *Snap) Build(buildDir string) error {153func (s *Snap) Build(buildDir string) error {
129 fullSnapPath, err := filepath.Abs(s.path)154 fullSnapPath, err := filepath.Abs(s.path)
130 if err != nil {155 if err != nil {
131156
=== renamed file 'pkg/snapfs/pkg_test.go' => 'pkg/snapfs/snapfs_test.go'
--- pkg/snapfs/pkg_test.go 2015-10-08 14:12:43 +0000
+++ pkg/snapfs/snapfs_test.go 2015-10-20 15:38:57 +0000
@@ -93,15 +93,6 @@
93 c.Assert(string(content), Equals, "name: foo")93 c.Assert(string(content), Equals, "name: foo")
94}94}
9595
96func (s *SquashfsTestSuite) TestCopyBlob(c *C) {
97 snap := makeSnap(c, "name: foo", "")
98 dst := filepath.Join(c.MkDir(), "blob.snap")
99
100 err := snap.CopyBlob(dst)
101 c.Assert(err, IsNil)
102 c.Assert(helpers.FilesAreEqual(snap.Name(), dst), Equals, true)
103}
104
105func (s *SquashfsTestSuite) TestUnpackGlob(c *C) {96func (s *SquashfsTestSuite) TestUnpackGlob(c *C) {
106 data := "some random data"97 data := "some random data"
107 snap := makeSnap(c, "", data)98 snap := makeSnap(c, "", data)
10899
=== removed file 'snappy/pkgformat.go'
--- snappy/pkgformat.go 2015-10-08 17:42:22 +0000
+++ snappy/pkgformat.go 1970-01-01 00:00:00 +0000
@@ -1,64 +0,0 @@
1// -*- Mode: Go; indent-tabs-mode: t -*-
2
3/*
4 * Copyright (C) 2014-2015 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
20package snappy
21
22import (
23 "bytes"
24 "fmt"
25 "os"
26 "strings"
27
28 "launchpad.net/snappy/pkg/clickdeb"
29 "launchpad.net/snappy/pkg/snapfs"
30)
31
32// PackageFile is the interface to interact with the low-level snap files
33type PackageFile interface {
34 Verify(allowUnauthenticated bool) error
35 Close() error
36 UnpackWithDropPrivs(targetDir, rootDir string) error
37 ControlMember(name string) ([]byte, error)
38 MetaMember(name string) ([]byte, error)
39 ExtractHashes(targetDir string) error
40}
41
42// OpenPackageFile opens a given snap file with the right backend
43func OpenPackageFile(path string) (PackageFile, error) {
44 f, err := os.Open(path)
45 if err != nil {
46 return nil, err
47 }
48 defer f.Close()
49
50 // look, libmagic!
51 header := make([]byte, 20)
52 if _, err := f.Read(header); err != nil {
53 return nil, err
54 }
55 // note that we only support little endian squashfs for now
56 if bytes.HasPrefix(header, []byte{'h', 's', 'q', 's'}) {
57 return snapfs.New(path), nil
58 }
59 if strings.HasPrefix(string(header), "!<arch>\ndebian") {
60 return clickdeb.Open(path)
61 }
62
63 return nil, fmt.Errorf("unknown header %v", header)
64}
650
=== modified file 'snappy/snapp.go'
--- snappy/snapp.go 2015-10-15 08:26:11 +0000
+++ snappy/snapp.go 2015-10-20 15:38:57 +0000
@@ -44,6 +44,7 @@
44 "launchpad.net/snappy/oauth"44 "launchpad.net/snappy/oauth"
45 "launchpad.net/snappy/pkg"45 "launchpad.net/snappy/pkg"
46 "launchpad.net/snappy/pkg/remote"46 "launchpad.net/snappy/pkg/remote"
47 "launchpad.net/snappy/pkg/snapfs"
47 "launchpad.net/snappy/policy"48 "launchpad.net/snappy/policy"
48 "launchpad.net/snappy/progress"49 "launchpad.net/snappy/progress"
49 "launchpad.net/snappy/release"50 "launchpad.net/snappy/release"
@@ -181,7 +182,7 @@
181 isActive bool182 isActive bool
182 isInstalled bool183 isInstalled bool
183 description string184 description string
184 deb PackageFile185 deb pkg.File
185 basedir string186 basedir string
186}187}
187188
@@ -430,7 +431,7 @@
430// package, as deduced from the license agreement (which might involve asking431// package, as deduced from the license agreement (which might involve asking
431// the user), or an error that explains the reason why installation should not432// the user), or an error that explains the reason why installation should not
432// proceed.433// proceed.
433func (m *packageYaml) checkLicenseAgreement(ag agreer, d PackageFile, currentActiveDir string) error {434func (m *packageYaml) checkLicenseAgreement(ag agreer, d pkg.File, currentActiveDir string) error {
434 if !m.ExplicitLicenseAgreement {435 if !m.ExplicitLicenseAgreement {
435 return nil436 return nil
436 }437 }
@@ -541,7 +542,7 @@
541// Caller should call Close on the pkg.542// Caller should call Close on the pkg.
542// TODO: expose that Close.543// TODO: expose that Close.
543func NewSnapPartFromSnapFile(snapFile string, origin string, unauthOk bool) (*SnapPart, error) {544func NewSnapPartFromSnapFile(snapFile string, origin string, unauthOk bool) (*SnapPart, error) {
544 d, err := OpenPackageFile(snapFile)545 d, err := pkg.Open(snapFile)
545 if err != nil {546 if err != nil {
546 return nil, err547 return nil, err
547 }548 }
@@ -620,7 +621,7 @@
620 // read hash, its ok if its not there, some older versions of621 // read hash, its ok if its not there, some older versions of
621 // snappy did not write this file622 // snappy did not write this file
622 hashesData, err := ioutil.ReadFile(filepath.Join(part.basedir, "meta", "hashes.yaml"))623 hashesData, err := ioutil.ReadFile(filepath.Join(part.basedir, "meta", "hashes.yaml"))
623 if err != nil {624 if err != nil && !os.IsNotExist(err) {
624 return nil, err625 return nil, err
625 }626 }
626627
@@ -999,12 +1000,26 @@
999 }1000 }
1000 }1001 }
10011002
1003 // the hooks must run before the snap is (auto)mounted
1004 // the reason is that the click hooks use:
1005 // ".click/$name.$origin.manifest"
1006 // but because the origin is not known at build time it needs
1007 // to be generated at runtime but will be mounted over once
1008 // the snap is mounted
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 {
1003 // cleanup the failed hooks1010 // cleanup the failed hooks
1004 removeClickHooks(s.m, s.origin, inhibitHooks)1011 removeClickHooks(s.m, s.origin, inhibitHooks)
1005 return err1012 return err
1006 }1013 }
10071014
1015 // generate the automount unit for the squashfs
1016 // FIXME: this is ugly
1017 if s.deb != nil && s.deb.NeedsAutoMountUnit() {
1018 if err := s.m.addSnapfsAutomount(s.basedir, inhibitHooks, inter); err != nil {
1019 return err
1020 }
1021 }
1022
1008 // generate the security policy from the package.yaml1023 // generate the security policy from the package.yaml
1009 if err := s.m.addSecurityPolicy(s.basedir); err != nil {1024 if err := s.m.addSecurityPolicy(s.basedir); err != nil {
1010 return err1025 return err
@@ -1018,7 +1033,6 @@
1018 if err := s.m.addPackageServices(s.basedir, inhibitHooks, inter); err != nil {1033 if err := s.m.addPackageServices(s.basedir, inhibitHooks, inter); err != nil {
1019 return err1034 return err
1020 }1035 }
1021
1022 if err := os.Remove(currentActiveSymlink); err != nil && !os.IsNotExist(err) {1036 if err := os.Remove(currentActiveSymlink); err != nil && !os.IsNotExist(err) {
1023 logger.Noticef("Failed to remove %q: %v", currentActiveSymlink, err)1037 logger.Noticef("Failed to remove %q: %v", currentActiveSymlink, err)
1024 }1038 }
@@ -1068,6 +1082,9 @@
1068 if err := s.m.removeSecurityPolicy(s.basedir); err != nil {1082 if err := s.m.removeSecurityPolicy(s.basedir); err != nil {
1069 return err1083 return err
1070 }1084 }
1085 if err := s.m.removeSnapfsAutomount(s.basedir, inter); err != nil {
1086 return err
1087 }
10711088
1072 if s.Type() == pkg.TypeFramework {1089 if s.Type() == pkg.TypeFramework {
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 {
@@ -1132,11 +1149,19 @@
1132 return err1149 return err
1133 }1150 }
11341151
1152 // unmount squashfs but ignore errors as its ok if the fs is not mounted
1153 exec.Command("unmount", "--lazy", filepath.Join(s.basedir)).CombinedOutput()
1154
1135 err = os.RemoveAll(s.basedir)1155 err = os.RemoveAll(s.basedir)
1136 if err != nil {1156 if err != nil {
1137 return err1157 return err
1138 }1158 }
11391159
1160 err = os.RemoveAll(snapfs.BlobPath(s.basedir))
1161 if err != nil {
1162 return err
1163 }
1164
1140 // best effort(?)1165 // best effort(?)
1141 os.Remove(filepath.Dir(s.basedir))1166 os.Remove(filepath.Dir(s.basedir))
11421167
@@ -1996,3 +2021,46 @@
19962021
1997 return env2022 return env
1998}2023}
2024
2025func (m *packageYaml) addSnapfsAutomount(baseDir string, inhibitHooks bool, inter interacter) error {
2026 squashfsPath := stripGlobalRootDir(snapfs.BlobPath(baseDir))
2027 whereDir := stripGlobalRootDir(baseDir)
2028
2029 sysd := systemd.New(dirs.GlobalRootDir, inter)
2030 _, err := sysd.WriteMountUnitFile(m.Name, squashfsPath, whereDir)
2031 if err != nil {
2032 return err
2033 }
2034 autoMountUnitName, err := sysd.WriteAutoMountUnitFile(m.Name, whereDir)
2035 if err != nil {
2036 return err
2037 }
2038
2039 // we always enable the mount unit even in inhibit hooks
2040 if err := sysd.Enable(autoMountUnitName); err != nil {
2041 return err
2042 }
2043
2044 if !inhibitHooks {
2045 return sysd.Start(autoMountUnitName)
2046 }
2047
2048 return nil
2049}
2050
2051func (m *packageYaml) removeSnapfsAutomount(baseDir string, inter interacter) error {
2052 sysd := systemd.New(dirs.GlobalRootDir, inter)
2053 for _, s := range []string{"mount", "automount"} {
2054 unit := systemd.MountUnitPath(stripGlobalRootDir(baseDir), s)
2055 if helpers.FileExists(unit) {
2056 // we ignore errors, nothing should stop removals
2057 _ = sysd.Disable(filepath.Base(unit))
2058 _ = sysd.Stop(filepath.Base(unit), time.Duration(1*time.Second))
2059 if err := os.Remove(unit); err != nil {
2060 return err
2061 }
2062 }
2063 }
2064
2065 return nil
2066}
19992067
=== modified file 'snappy/snapp_snapfs_test.go'
--- snappy/snapp_snapfs_test.go 2015-10-08 10:33:37 +0000
+++ snappy/snapp_snapfs_test.go 2015-10-20 15:38:57 +0000
@@ -20,11 +20,14 @@
20package snappy20package snappy
2121
22import (22import (
23 "io/ioutil"
24 "os"
23 "path/filepath"25 "path/filepath"
2426
25 "launchpad.net/snappy/dirs"27 "launchpad.net/snappy/dirs"
26 "launchpad.net/snappy/helpers"28 "launchpad.net/snappy/helpers"
27 "launchpad.net/snappy/pkg/snapfs"29 "launchpad.net/snappy/pkg/snapfs"
30 "launchpad.net/snappy/systemd"
2831
29 . "gopkg.in/check.v1"32 . "gopkg.in/check.v1"
30)33)
@@ -36,6 +39,12 @@
36 // mocks39 // mocks
37 aaClickHookCmd = "/bin/true"40 aaClickHookCmd = "/bin/true"
38 dirs.SetRootDir(c.MkDir())41 dirs.SetRootDir(c.MkDir())
42 os.MkdirAll(filepath.Join(dirs.SnapServicesDir, "multi-user.target.wants"), 0755)
43
44 // ensure we do not run a real systemd (slows down tests)
45 systemd.SystemctlCmd = func(cmd ...string) ([]byte, error) {
46 return []byte("ActiveState=inactive\n"), nil
47 }
3948
40 // ensure we use the right builder func (snapfs)49 // ensure we use the right builder func (snapfs)
41 snapBuilderFunc = BuildSnapfsSnap50 snapBuilderFunc = BuildSnapfsSnap
@@ -70,15 +79,88 @@
70 _, err = part.Install(&MockProgressMeter{}, 0)79 _, err = part.Install(&MockProgressMeter{}, 0)
71 c.Assert(err, IsNil)80 c.Assert(err, IsNil)
7281
73 // after install its just on disk for now, note that this will82 // after install the blob is in the right dir
74 // change once the mounting gets added83 c.Assert(helpers.FileExists(filepath.Join(dirs.SnapBlobDir, "hello-app.origin_1.10.snap")), Equals, true)
75 base := filepath.Join(dirs.SnapAppsDir, "hello-app.origin", "1.10")84
76 for _, needle := range []string{85 // ensure the right unit is created
77 "bin/foo",86 mup := systemd.MountUnitPath("/apps/hello-app.origin/1.10", "mount")
78 "meta/package.yaml",87 content, err := ioutil.ReadFile(mup)
79 ".click/info/hello-app.origin.manifest",88 c.Assert(err, IsNil)
80 } {89 c.Assert(string(content), Matches, "(?ms).*^Where=/apps/hello-app.origin/1.10")
81 println(needle)90 c.Assert(string(content), Matches, "(?ms).*^What=/var/lib/snappy/snaps/hello-app.origin_1.10.snap")
82 c.Assert(helpers.FileExists(filepath.Join(base, needle)), Equals, true)91}
83 }92
93func (s *SnapfsTestSuite) TestAddSnapfsAutomount(c *C) {
94 m := packageYaml{
95 Name: "foo.origin",
96 Version: "1.0",
97 Architectures: []string{"all"},
98 }
99 inter := &MockProgressMeter{}
100 err := m.addSnapfsAutomount(filepath.Join(dirs.SnapAppsDir, "foo.origin/1.0"), true, inter)
101 c.Assert(err, IsNil)
102
103 // ensure correct mount unit
104 mount, err := ioutil.ReadFile(filepath.Join(dirs.SnapServicesDir, "apps-foo.origin-1.0.mount"))
105 c.Assert(err, IsNil)
106 c.Assert(string(mount), Equals, `[Unit]
107Description=Snapfs mount unit for foo.origin
108
109[Mount]
110What=/var/lib/snappy/snaps/foo.origin_1.0.snap
111Where=/apps/foo.origin/1.0
112`)
113
114 // and correct automount unit
115 automount, err := ioutil.ReadFile(filepath.Join(dirs.SnapServicesDir, "apps-foo.origin-1.0.automount"))
116 c.Assert(err, IsNil)
117 c.Assert(string(automount), Equals, `[Unit]
118Description=Snapfs automount unit for foo.origin
119
120[Automount]
121Where=/apps/foo.origin/1.0
122TimeoutIdleSec=30
123
124[Install]
125WantedBy=multi-user.target
126`)
127}
128
129func (s *SnapfsTestSuite) TestRemoveSnapfsAutomount(c *C) {
130 m := packageYaml{}
131 inter := &MockProgressMeter{}
132 err := m.addSnapfsAutomount(filepath.Join(dirs.SnapAppsDir, "foo.origin/1.0"), true, inter)
133 c.Assert(err, IsNil)
134
135 // ensure we have the files
136 for _, ext := range []string{"mount", "automount"} {
137 p := filepath.Join(dirs.SnapServicesDir, "apps-foo.origin-1.0.") + ext
138 c.Assert(helpers.FileExists(p), Equals, true)
139 }
140
141 // now call remove and ensure they are gone
142 err = m.removeSnapfsAutomount(filepath.Join(dirs.SnapAppsDir, "foo.origin/1.0"), inter)
143 c.Assert(err, IsNil)
144 for _, ext := range []string{"mount", "automount"} {
145 p := filepath.Join(dirs.SnapServicesDir, "apps-foo.origin-1.0.") + ext
146 c.Assert(helpers.FileExists(p), Equals, false)
147 }
148}
149
150func (s *SnapfsTestSuite) TestRemoveViaSnapfsWorks(c *C) {
151 snapPkg := makeTestSnapPackage(c, packageHello)
152 part, err := NewSnapPartFromSnapFile(snapPkg, "origin", true)
153 c.Assert(err, IsNil)
154
155 _, err = part.Install(&MockProgressMeter{}, 0)
156 c.Assert(err, IsNil)
157
158 // after install the blob is in the right dir
159 c.Assert(helpers.FileExists(filepath.Join(dirs.SnapBlobDir, "hello-app.origin_1.10.snap")), Equals, true)
160
161 // now remove and ensure its gone
162 err = part.Uninstall(&MockProgressMeter{})
163 c.Assert(err, IsNil)
164 c.Assert(helpers.FileExists(filepath.Join(dirs.SnapBlobDir, "hello-app.origin_1.10.snap")), Equals, false)
165
84}166}
85167
=== modified file 'systemd/systemd.go'
--- systemd/systemd.go 2015-09-15 14:53:38 +0000
+++ systemd/systemd.go 2015-10-20 15:38:57 +0000
@@ -33,6 +33,7 @@
33 "text/template"33 "text/template"
34 "time"34 "time"
3535
36 "launchpad.net/snappy/dirs"
36 "launchpad.net/snappy/helpers"37 "launchpad.net/snappy/helpers"
37 "launchpad.net/snappy/logger"38 "launchpad.net/snappy/logger"
38)39)
@@ -95,6 +96,8 @@
95 Status(service string) (string, error)96 Status(service string) (string, error)
96 ServiceStatus(service string) (*ServiceStatus, error)97 ServiceStatus(service string) (*ServiceStatus, error)
97 Logs(services []string) ([]Log, error)98 Logs(services []string) ([]Log, error)
99 WriteMountUnitFile(name, what, where string) (string, error)
100 WriteAutoMountUnitFile(name, where string) (string, error)
98}101}
99102
100// A Log is a single entry in the systemd journal103// A Log is a single entry in the systemd journal
@@ -510,3 +513,46 @@
510func (l Log) String() string {513func (l Log) String() string {
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())
512}515}
516
517// MountUnitPath returns the path of a {,auto}mount unit
518func MountUnitPath(baseDir, ext string) string {
519 // FIXME: use github.com/coreos/go-systemd/unit/escape.go
520 // once we can update go-systemd. we can not right now
521 // because versions >= 2 are not compatible with go1.3 from
522 // 15.04
523 p, err := exec.Command("systemd-escape", "--path", baseDir).CombinedOutput()
524 if err != nil {
525 panic(fmt.Sprintf("systemd-escape failed for %s with %q", baseDir, p))
526 }
527 escapedPath := strings.TrimSpace(string(p))
528 return filepath.Join(dirs.SnapServicesDir, fmt.Sprintf("%s.%s", escapedPath, ext))
529}
530
531func (s *systemd) WriteMountUnitFile(name, what, where string) (string, error) {
532 c := fmt.Sprintf(`[Unit]
533Description=Snapfs mount unit for %s
534
535[Mount]
536What=%s
537Where=%s
538`, name, what, where)
539
540 mu := MountUnitPath(where, "mount")
541 return filepath.Base(mu), helpers.AtomicWriteFile(mu, []byte(c), 0644)
542}
543
544func (s *systemd) WriteAutoMountUnitFile(name, where string) (string, error) {
545 c := fmt.Sprintf(`[Unit]
546Description=Snapfs automount unit for %s
547
548[Automount]
549Where=%s
550TimeoutIdleSec=30
551
552[Install]
553WantedBy=multi-user.target
554`, name, where)
555
556 mu := MountUnitPath(where, "automount")
557 return filepath.Base(mu), helpers.AtomicWriteFile(MountUnitPath(where, "automount"), []byte(c), 0644)
558}
513559
=== modified file 'systemd/systemd_test.go'
--- systemd/systemd_test.go 2015-09-14 12:29:15 +0000
+++ systemd/systemd_test.go 2015-10-20 15:38:57 +0000
@@ -21,6 +21,7 @@
2121
22import (22import (
23 "fmt"23 "fmt"
24 "io/ioutil"
24 "os"25 "os"
25 "path/filepath"26 "path/filepath"
26 "testing"27 "testing"
@@ -28,6 +29,7 @@
2829
29 . "gopkg.in/check.v1"30 . "gopkg.in/check.v1"
3031
32 "launchpad.net/snappy/dirs"
31 "launchpad.net/snappy/helpers"33 "launchpad.net/snappy/helpers"
32)34)
3335
@@ -411,3 +413,40 @@
411 }.String(), Equals, "1970-01-01T00:00:00.000042Z me hi")413 }.String(), Equals, "1970-01-01T00:00:00.000042Z me hi")
412414
413}415}
416
417func (s *SystemdTestSuite) TestMountUnitPath(c *C) {
418 c.Assert(MountUnitPath("/apps/hello.origin/1.1", "mount"), Equals, filepath.Join(dirs.SnapServicesDir, "apps-hello.origin-1.1.mount"))
419}
420
421func (s *SystemdTestSuite) TestWriteMountUnit(c *C) {
422 mountUnitName, err := New("", nil).WriteMountUnitFile("foo.origin", "/var/lib/snappy/snaps/foo.origin_1.0.snap", "/apps/foo.origin/1.0")
423 c.Assert(err, IsNil)
424
425 mount, err := ioutil.ReadFile(filepath.Join(dirs.SnapServicesDir, mountUnitName))
426 c.Assert(err, IsNil)
427 c.Assert(string(mount), Equals, `[Unit]
428Description=Snapfs mount unit for foo.origin
429
430[Mount]
431What=/var/lib/snappy/snaps/foo.origin_1.0.snap
432Where=/apps/foo.origin/1.0
433`)
434}
435
436func (s *SystemdTestSuite) TestWriteAutoMountUnit(c *C) {
437 mountUnitName, err := New("", nil).WriteAutoMountUnitFile("foo.origin", "/apps/foo.origin/1.0")
438 c.Assert(err, IsNil)
439
440 automount, err := ioutil.ReadFile(filepath.Join(dirs.SnapServicesDir, mountUnitName))
441 c.Assert(err, IsNil)
442 c.Assert(string(automount), Equals, `[Unit]
443Description=Snapfs automount unit for foo.origin
444
445[Automount]
446Where=/apps/foo.origin/1.0
447TimeoutIdleSec=30
448
449[Install]
450WantedBy=multi-user.target
451`)
452}

Subscribers

People subscribed via source and target branches