diff options
| author | Michael Vogt <mvo@ubuntu.com> | 2018-03-20 12:21:39 +0100 |
|---|---|---|
| committer | Michael Vogt <mvo@ubuntu.com> | 2018-03-20 12:21:39 +0100 |
| commit | 609a29f02683e1aa92bc5cf365b44585d6eb15ec (patch) | |
| tree | bcb8b1d9f28b250f4793c29de8c316cbf9597e53 | |
| parent | 97f2683d2b0c0356df24db5275ef7c29500e4437 (diff) | |
| parent | 482f8cb4761b92660a63a8b17a0ab6d10cf33ff6 (diff) | |
Merge remote-tracking branch 'upstream/master' into bionic-gnupg-recommendsbionic-gnupg-recommends
44 files changed, 698 insertions, 146 deletions
diff --git a/cmd/snap-mgmt/snap-mgmt.sh.in b/cmd/snap-mgmt/snap-mgmt.sh.in index 907d64c1ca..360fb4bd6e 100644 --- a/cmd/snap-mgmt/snap-mgmt.sh.in +++ b/cmd/snap-mgmt/snap-mgmt.sh.in @@ -33,7 +33,7 @@ systemctl_stop() { } purge() { - # debian-9, fedora-27, ubuntu-16.04... + # shellcheck disable=SC1091 distribution=$(. /etc/os-release; echo "${ID}-${VERSION_ID}") if [ "$distribution" = "ubuntu-14.04" ]; then @@ -108,13 +108,13 @@ purge() { echo "Discarding preserved snap namespaces" # opportunistic as those might not be actually mounted if [ -d /run/snapd/ns ]; then - if [ $(ls /run/snapd/ns/*.mnt | wc -l) -gt 0 ]; then + if [ "$(find /run/snapd/ns/ -name "*.mnt" | wc -l)" -gt 0 ]; then for mnt in /run/snapd/ns/*.mnt; do umount -l "$mnt" || true rm -f "$mnt" done fi - if [ $(ls /run/snapd/ns/*.fstab | wc -l) -gt 0 ]; then + if [ "$(find /run/snapd/ns/ -name "*.fstab" | wc -l)" -gt 0 ]; then for fstab in /run/snapd/ns/*.fstab; do rm -f "$fstab" done diff --git a/cmd/snap-update-ns/change.go b/cmd/snap-update-ns/change.go index c1450dcc8c..075aed7aac 100644 --- a/cmd/snap-update-ns/change.go +++ b/cmd/snap-update-ns/change.go @@ -94,7 +94,7 @@ func (c *Change) createPath(path string, pokeHoles bool) ([]*Change, error) { if err2, ok := err.(*ReadOnlyFsError); ok && pokeHoles { // If the writing failed because the underlying file-system is read-only // we can construct a writable mimic to fix that. - changes, err = createWritableMimic(err2.Path) + changes, err = createWritableMimic(err2.Path, path) if err != nil { err = fmt.Errorf("cannot create writable mimic over %q: %s", err2.Path, err) } else { @@ -135,9 +135,12 @@ func (c *Change) ensureTarget() ([]*Change, error) { err = fmt.Errorf("cannot use %q as mount point: not a regular file", path) } case "symlink": - // When we want to create a symlink we just need the empty - // space so anything that is in the way is a problem. - err = fmt.Errorf("cannot create symlink in %q: existing file in the way", path) + if fi.Mode()&os.ModeSymlink == os.ModeSymlink { + // Create path verifies the symlink or fails if it is not what we wanted. + _, err = c.createPath(path, false) + } else { + err = fmt.Errorf("cannot create symlink in %q: existing file in the way", path) + } } } else if os.IsNotExist(err) { changes, err = c.createPath(path, true) diff --git a/cmd/snap-update-ns/change_test.go b/cmd/snap-update-ns/change_test.go index 17fd54d146..25acdaa2c8 100644 --- a/cmd/snap-update-ns/change_test.go +++ b/cmd/snap-update-ns/change_test.go @@ -390,7 +390,10 @@ func (s *changeSuite) TestPerformFilesystemMountWithoutMountPointAndReadOnlyBase synth, err := chg.Perform() c.Assert(err, IsNil) c.Assert(synth, DeepEquals, []*update.Change{ - {Action: update.Mount, Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/rofs", Type: "tmpfs"}}, + {Action: update.Mount, Entry: osutil.MountEntry{ + Name: "tmpfs", Dir: "/rofs", Type: "tmpfs", + Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/rofs/target"}}, + }, }) c.Assert(s.sys.Calls(), DeepEquals, []string{ // sniff mount target @@ -746,7 +749,10 @@ func (s *changeSuite) TestPerformDirectoryBindMountWithoutMountPointAndReadOnlyB synth, err := chg.Perform() c.Assert(err, IsNil) c.Assert(synth, DeepEquals, []*update.Change{ - {Action: update.Mount, Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/rofs", Type: "tmpfs"}}, + {Action: update.Mount, Entry: osutil.MountEntry{ + Name: "tmpfs", Dir: "/rofs", Type: "tmpfs", + Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/rofs/target"}}, + }, }) c.Assert(s.sys.Calls(), DeepEquals, []string{ // sniff mount target @@ -1021,7 +1027,10 @@ func (s *changeSuite) TestPerformFileBindMountWithoutMountPointAndReadOnlyBase(c synth, err := chg.Perform() c.Assert(err, IsNil) c.Assert(synth, DeepEquals, []*update.Change{ - {Action: update.Mount, Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/rofs", Type: "tmpfs"}}, + {Action: update.Mount, Entry: osutil.MountEntry{ + Name: "tmpfs", Dir: "/rofs", Type: "tmpfs", + Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/rofs/target"}}, + }, }) c.Assert(s.sys.Calls(), DeepEquals, []string{ // sniff mount target @@ -1253,7 +1262,10 @@ func (s *changeSuite) TestPerformCreateSymlinkWithoutBaseDirAndReadOnlyBase(c *C synth, err := chg.Perform() c.Assert(err, IsNil) c.Assert(synth, DeepEquals, []*update.Change{ - {Action: update.Mount, Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/rofs", Type: "tmpfs"}}, + {Action: update.Mount, Entry: osutil.MountEntry{ + Name: "tmpfs", Dir: "/rofs", Type: "tmpfs", + Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/rofs/name"}}, + }, }) c.Assert(s.sys.Calls(), DeepEquals, []string{ // sniff symlink name @@ -1314,6 +1326,48 @@ func (s *changeSuite) TestPerformCreateSymlinkWithFileInTheWay(c *C) { }) } +// Change.Perform wants to create a symlink but a correct symlink is already present. +func (s *changeSuite) TestPerformCreateSymlinkWithGoodSymlinkPresent(c *C) { + s.sys.InsertLstatResult(`lstat "/name"`, testutil.FileInfoSymlink) + s.sys.InsertFault(`symlinkat "/oldname" 3 "name"`, syscall.EEXIST) + s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{Mode: syscall.S_IFLNK}) + s.sys.InsertReadlinkatResult(`readlinkat 4 "" <ptr>`, "/oldname") + chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "unused", Dir: "/name", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/oldname"}}} + synth, err := chg.Perform() + c.Assert(err, IsNil) + c.Assert(synth, HasLen, 0) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `lstat "/name"`, + `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 3 + `symlinkat "/oldname" 3 "name"`, // -> EEXIST + `openat 3 "name" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, // -> 4 + `fstat 4 <ptr>`, + `readlinkat 4 "" <ptr>`, + `close 3`, + }) +} + +// Change.Perform wants to create a symlink but a incorrect symlink is already present. +func (s *changeSuite) TestPerformCreateSymlinkWithBadSymlinkPresent(c *C) { + s.sys.InsertLstatResult(`lstat "/name"`, testutil.FileInfoSymlink) + s.sys.InsertFault(`symlinkat "/oldname" 3 "name"`, syscall.EEXIST) + s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{Mode: syscall.S_IFLNK}) + s.sys.InsertReadlinkatResult(`readlinkat 4 "" <ptr>`, "/evil") + chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "unused", Dir: "/name", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/oldname"}}} + synth, err := chg.Perform() + c.Assert(err, ErrorMatches, `cannot create path "/name": cannot create symbolic link "name": existing symbolic link in the way`) + c.Assert(synth, HasLen, 0) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `lstat "/name"`, + `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 3 + `symlinkat "/oldname" 3 "name"`, // -> EEXIST + `openat 3 "name" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, // -> 4 + `fstat 4 <ptr>`, + `readlinkat 4 "" <ptr>`, + `close 3`, + }) +} + func (s *changeSuite) TestPerformRemoveSymlink(c *C) { chg := &update.Change{Action: update.Unmount, Entry: osutil.MountEntry{Name: "unused", Dir: "/name", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/oldname"}}} synth, err := chg.Perform() diff --git a/cmd/snap-update-ns/export_test.go b/cmd/snap-update-ns/export_test.go index 36c723755f..591155a471 100644 --- a/cmd/snap-update-ns/export_test.go +++ b/cmd/snap-update-ns/export_test.go @@ -21,6 +21,7 @@ package main import ( "os" + "syscall" . "gopkg.in/check.v1" @@ -61,6 +62,7 @@ type SystemCalls interface { Open(path string, flags int, mode uint32) (fd int, err error) Openat(dirfd int, path string, flags int, mode uint32) (fd int, err error) Unmount(target string, flags int) error + Fstat(fd int, buf *syscall.Stat_t) error } // MockSystemCalls replaces real system calls with those of the argument. @@ -79,6 +81,7 @@ func MockSystemCalls(sc SystemCalls) (restore func()) { oldSysUnmount := sysUnmount oldSysSymlinkat := sysSymlinkat oldReadlinkat := sysReadlinkat + oldFstat := sysFstat // override osLstat = sc.Lstat @@ -94,6 +97,7 @@ func MockSystemCalls(sc SystemCalls) (restore func()) { sysUnmount = sc.Unmount sysSymlinkat = sc.Symlinkat sysReadlinkat = sc.Readlinkat + sysFstat = sc.Fstat return func() { // restore @@ -110,6 +114,7 @@ func MockSystemCalls(sc SystemCalls) (restore func()) { sysUnmount = oldSysUnmount sysSymlinkat = oldSysSymlinkat sysReadlinkat = oldReadlinkat + sysFstat = oldFstat } } diff --git a/cmd/snap-update-ns/utils.go b/cmd/snap-update-ns/utils.go index cbbfefdd36..a6825878e1 100644 --- a/cmd/snap-update-ns/utils.go +++ b/cmd/snap-update-ns/utils.go @@ -222,10 +222,9 @@ func secureMkSymlink(fd int, segments []string, i int, oldname string) error { switch err { case syscall.EEXIST: var objFd int - const O_PATH = 010000000 // If the file exists then just open it for examination. // Maybe it's the symlink we were hoping to create. - objFd, err = sysOpenat(fd, segment, syscall.O_CLOEXEC|O_PATH|syscall.O_NOFOLLOW, 0) + objFd, err = sysOpenat(fd, segment, syscall.O_CLOEXEC|sys.O_PATH|syscall.O_NOFOLLOW, 0) if err != nil { return fmt.Errorf("cannot open existing file %q: %v", segment, err) } @@ -397,7 +396,7 @@ func secureMksymlinkAll(name string, perm os.FileMode, uid sys.UserID, gid sys.G // be used with. Since the original directory is hidden the algorithm relies on // a temporary directory where the original is bind-mounted during the // progression of the algorithm. -func planWritableMimic(dir string) ([]*Change, error) { +func planWritableMimic(dir, neededBy string) ([]*Change, error) { // We need a place for "safe keeping" of what is present in the original // directory as we are about to attach a tmpfs there, which will hide // everything inside. @@ -427,7 +426,10 @@ func planWritableMimic(dir string) ([]*Change, error) { }) // Mount tmpfs over the original directory, hiding its contents. changes = append(changes, &Change{ - Action: Mount, Entry: osutil.MountEntry{Name: "tmpfs", Dir: dir, Type: "tmpfs"}, + Action: Mount, Entry: osutil.MountEntry{ + Name: "tmpfs", Dir: dir, Type: "tmpfs", + Options: []string{"x-snapd.synthetic", fmt.Sprintf("x-snapd.needed-by=%s", neededBy)}, + }, }) // Iterate over the items in the original directory (nothing is mounted _yet_). entries, err := ioutilReadDir(dir) @@ -446,18 +448,21 @@ func planWritableMimic(dir string) ([]*Change, error) { switch { case m.IsDir(): ch.Entry.Options = []string{"rbind"} - changes = append(changes, ch) case m.IsRegular(): ch.Entry.Options = []string{"bind", "x-snapd.kind=file"} - changes = append(changes, ch) case m&os.ModeSymlink != 0: if target, err := osReadlink(filepath.Join(dir, fi.Name())); err == nil { ch.Entry.Options = []string{"x-snapd.kind=symlink", fmt.Sprintf("x-snapd.symlink=%s", target)} - changes = append(changes, ch) + } else { + continue } default: logger.Noticef("skipping unsupported file %s", fi) + continue } + ch.Entry.Options = append(ch.Entry.Options, "x-snapd.synthetic") + ch.Entry.Options = append(ch.Entry.Options, fmt.Sprintf("x-snapd.needed-by=%s", neededBy)) + changes = append(changes, ch) } // Finally unbind the safe-keeping directory as we don't need it anymore. changes = append(changes, &Change{ @@ -568,8 +573,8 @@ func execWritableMimic(plan []*Change) ([]*Change, error) { return undoChanges, nil } -func createWritableMimic(dir string) ([]*Change, error) { - plan, err := planWritableMimic(dir) +func createWritableMimic(dir, neededBy string) ([]*Change, error) { + plan, err := planWritableMimic(dir, neededBy) if err != nil { return nil, err } diff --git a/cmd/snap-update-ns/utils_test.go b/cmd/snap-update-ns/utils_test.go index c94040114c..c64347ee7f 100644 --- a/cmd/snap-update-ns/utils_test.go +++ b/cmd/snap-update-ns/utils_test.go @@ -252,20 +252,21 @@ func (s *utilsSuite) TestPlanWritableMimic(c *C) { }) defer restore() - changes, err := update.PlanWritableMimic("/foo") + changes, err := update.PlanWritableMimic("/foo", "/foo/bar") c.Assert(err, IsNil) + c.Assert(changes, DeepEquals, []*update.Change{ // Store /foo in /tmp/.snap/foo while we set things up {Entry: osutil.MountEntry{Name: "/foo", Dir: "/tmp/.snap/foo", Options: []string{"rbind"}}, Action: update.Mount}, // Put a tmpfs over /foo - {Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs"}, Action: update.Mount}, + {Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, // Bind mount files and directories over. Note that files are identified by x-snapd.kind=file option. - {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file"}}, Action: update.Mount}, - {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/dir", Dir: "/foo/dir", Options: []string{"rbind"}}, Action: update.Mount}, + {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, + {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/dir", Dir: "/foo/dir", Options: []string{"rbind", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, // Create symlinks. // Bad symlinks and all other file types are skipped and not // recorded in mount changes. - {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/symlink", Dir: "/foo/symlink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=target"}}, Action: update.Mount}, + {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/symlink", Dir: "/foo/symlink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=target", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, // Unmount the safe-keeping directory {Entry: osutil.MountEntry{Name: "none", Dir: "/tmp/.snap/foo", Options: []string{"x-snapd.detach"}}, Action: update.Unmount}, }) @@ -282,7 +283,7 @@ func (s *utilsSuite) TestPlanWritableMimicErrors(c *C) { }) defer restore() - changes, err := update.PlanWritableMimic("/foo") + changes, err := update.PlanWritableMimic("/foo", "/foo/bar") c.Assert(err, ErrorMatches, "testing") c.Assert(changes, HasLen, 0) } @@ -291,10 +292,10 @@ func (s *utilsSuite) TestExecWirableMimicSuccess(c *C) { // This plan is the same as in the test above. This is what comes out of planWritableMimic. plan := []*update.Change{ {Entry: osutil.MountEntry{Name: "/foo", Dir: "/tmp/.snap/foo", Options: []string{"rbind"}}, Action: update.Mount}, - {Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs"}, Action: update.Mount}, - {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file"}}, Action: update.Mount}, - {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/dir", Dir: "/foo/dir", Options: []string{"rbind"}}, Action: update.Mount}, - {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/symlink", Dir: "/foo/symlink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=target"}}, Action: update.Mount}, + {Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, + {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, + {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/dir", Dir: "/foo/dir", Options: []string{"rbind", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, + {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/symlink", Dir: "/foo/symlink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=target", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, {Entry: osutil.MountEntry{Name: "none", Dir: "/tmp/.snap/foo", Options: []string{"x-snapd.detach"}}, Action: update.Unmount}, } @@ -309,9 +310,9 @@ func (s *utilsSuite) TestExecWirableMimicSuccess(c *C) { undoPlan, err := update.ExecWritableMimic(plan) c.Assert(err, IsNil) c.Assert(undoPlan, DeepEquals, []*update.Change{ - {Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs"}, Action: update.Mount}, - {Entry: osutil.MountEntry{Name: "/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file"}}, Action: update.Mount}, - {Entry: osutil.MountEntry{Name: "/foo/dir", Dir: "/foo/dir", Options: []string{"rbind", "x-snapd.detach"}}, Action: update.Mount}, + {Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, + {Entry: osutil.MountEntry{Name: "/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, + {Entry: osutil.MountEntry{Name: "/foo/dir", Dir: "/foo/dir", Options: []string{"rbind", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar", "x-snapd.detach"}}, Action: update.Mount}, }) } @@ -319,9 +320,9 @@ func (s *utilsSuite) TestExecWirableMimicErrorWithRecovery(c *C) { // This plan is the same as in the test above. This is what comes out of planWritableMimic. plan := []*update.Change{ {Entry: osutil.MountEntry{Name: "/foo", Dir: "/tmp/.snap/foo", Options: []string{"rbind"}}, Action: update.Mount}, - {Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs"}, Action: update.Mount}, - {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file"}}, Action: update.Mount}, - {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/symlink", Dir: "/foo/symlink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=target"}}, Action: update.Mount}, + {Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, + {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, + {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/symlink", Dir: "/foo/symlink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=target", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, // NOTE: the next perform will fail. Notably the symlink did not fail. {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/dir", Dir: "/foo/dir", Options: []string{"rbind"}}, Action: update.Mount}, {Entry: osutil.MountEntry{Name: "none", Dir: "/tmp/.snap/foo", Options: []string{"x-snapd.detach"}}, Action: update.Unmount}, @@ -355,8 +356,8 @@ func (s *utilsSuite) TestExecWirableMimicErrorWithRecovery(c *C) { // The changes we managed to perform were undone correctly. c.Assert(recoveryPlan, DeepEquals, []*update.Change{ // NOTE: there is no symlink undo entry as it is implicitly undone by unmounting the tmpfs. - {Entry: osutil.MountEntry{Name: "/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file"}}, Action: update.Unmount}, - {Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs"}, Action: update.Unmount}, + {Entry: osutil.MountEntry{Name: "/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Unmount}, + {Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Unmount}, {Entry: osutil.MountEntry{Name: "/foo", Dir: "/tmp/.snap/foo", Options: []string{"rbind", "x-snapd.detach"}}, Action: update.Unmount}, }) } @@ -365,10 +366,10 @@ func (s *utilsSuite) TestExecWirableMimicErrorNothingDone(c *C) { // This plan is the same as in the test above. This is what comes out of planWritableMimic. plan := []*update.Change{ {Entry: osutil.MountEntry{Name: "/foo", Dir: "/tmp/.snap/foo", Options: []string{"rbind"}}, Action: update.Mount}, - {Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs"}, Action: update.Mount}, - {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file"}}, Action: update.Mount}, - {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/dir", Dir: "/foo/dir", Options: []string{"rbind"}}, Action: update.Mount}, - {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/symlink", Dir: "/foo/symlink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=target"}}, Action: update.Mount}, + {Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, + {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, + {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/dir", Dir: "/foo/dir", Options: []string{"rbind", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, + {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/symlink", Dir: "/foo/symlink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=target", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, {Entry: osutil.MountEntry{Name: "none", Dir: "/tmp/.snap/foo", Options: []string{"x-snapd.detach"}}, Action: update.Unmount}, } @@ -388,10 +389,10 @@ func (s *utilsSuite) TestExecWirableMimicErrorCannotUndo(c *C) { // This plan is the same as in the test above. This is what comes out of planWritableMimic. plan := []*update.Change{ {Entry: osutil.MountEntry{Name: "/foo", Dir: "/tmp/.snap/foo", Options: []string{"rbind"}}, Action: update.Mount}, - {Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs"}, Action: update.Mount}, - {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file"}}, Action: update.Mount}, - {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/dir", Dir: "/foo/dir", Options: []string{"rbind"}}, Action: update.Mount}, - {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/symlink", Dir: "/foo/symlink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=target"}}, Action: update.Mount}, + {Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, + {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, + {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/dir", Dir: "/foo/dir", Options: []string{"rbind", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, + {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/symlink", Dir: "/foo/symlink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=target", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, {Entry: osutil.MountEntry{Name: "none", Dir: "/tmp/.snap/foo", Options: []string{"x-snapd.detach"}}, Action: update.Unmount}, } diff --git a/cmd/snap/cmd_pack.go b/cmd/snap/cmd_pack.go index 2d8a3c21d7..68c91bbe2d 100644 --- a/cmd/snap/cmd_pack.go +++ b/cmd/snap/cmd_pack.go @@ -25,19 +25,30 @@ import ( "github.com/jessevdk/go-flags" "github.com/snapcore/snapd/i18n" + "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap/pack" ) type packCmd struct { - Positional struct { + CheckSkeleton bool `long:"check-skeleton"` + Positional struct { SnapDir string `positional-arg-name:"<snap-dir>"` TargetDir string `positional-arg-name:"<target-dir>"` } `positional-args:"yes"` } -var shortPackHelp = i18n.G("Pack the given target dir as a snap") +var shortPackHelp = i18n.G("Pack the given directory as a snap") var longPackHelp = i18n.G(` -The pack command packs the given snap-dir as a snap. +The pack command packs the given snap-dir as a snap and writes the result to +target-dir. If target-dir is omitted, the result is written to current +directory. If both source-dir and target-dir are omitted, the pack command packs +the current directory. + +When used with --check-skeleton, pack only checks whether snap-dir contains +valid snap metadata and raises an error otherwise. Application commands listed +in snap metadata file, but appearing with incorrect permission bits result in an +error. Commands that are missing from snap-dir are listed in diagnostic +messages. `) func init() { @@ -46,7 +57,9 @@ func init() { longPackHelp, func() flags.Commander { return &packCmd{} - }, nil, nil) + }, map[string]string{ + "check-skeleton": i18n.G("Validate snap-dir metadata only"), + }, nil) } func (x *packCmd) Execute([]string) error { @@ -57,6 +70,14 @@ func (x *packCmd) Execute([]string) error { x.Positional.TargetDir = "." } + if x.CheckSkeleton { + err := pack.CheckSkeleton(x.Positional.SnapDir) + if err == snap.ErrMissingPaths { + return nil + } + return err + } + snapPath, err := pack.Snap(x.Positional.SnapDir, x.Positional.TargetDir) if err != nil { return fmt.Errorf("cannot pack %q: %v", x.Positional.SnapDir, err) diff --git a/cmd/snap/cmd_pack_test.go b/cmd/snap/cmd_pack_test.go new file mode 100644 index 0000000000..2cbf568961 --- /dev/null +++ b/cmd/snap/cmd_pack_test.go @@ -0,0 +1,80 @@ +package main_test + +import ( + "io/ioutil" + "os" + "path/filepath" + + "gopkg.in/check.v1" + + snaprun "github.com/snapcore/snapd/cmd/snap" +) + +const packSnapYaml = `name: hello +version: 1.0.1 +apps: + app: + command: bin/hello +` + +func makeSnapDirForPack(c *check.C, snapYaml string) string { + tempdir := c.MkDir() + c.Assert(os.Chmod(tempdir, 0755), check.IsNil) + + // use meta/snap.yaml + metaDir := filepath.Join(tempdir, "meta") + err := os.Mkdir(metaDir, 0755) + c.Assert(err, check.IsNil) + err = ioutil.WriteFile(filepath.Join(metaDir, "snap.yaml"), []byte(snapYaml), 0644) + c.Assert(err, check.IsNil) + + return tempdir +} + +func (s *SnapSuite) TestPackCheckSkeletonNoAppFiles(c *check.C) { + snapDir := makeSnapDirForPack(c, packSnapYaml) + + // check-skeleton does not fail due to missing files + _, err := snaprun.Parser().ParseArgs([]string{"pack", "--check-skeleton", snapDir}) + c.Assert(err, check.IsNil) +} + +func (s *SnapSuite) TestPackCheckSkeletonBadMeta(c *check.C) { + // no snap name + snapYaml := ` +version: foobar +apps: +` + snapDir := makeSnapDirForPack(c, snapYaml) + + _, err := snaprun.Parser().ParseArgs([]string{"pack", "--check-skeleton", snapDir}) + c.Assert(err, check.ErrorMatches, "snap name cannot be empty") +} + +func (s *SnapSuite) TestPackPacksFailsForMissingPaths(c *check.C) { + snapDir := makeSnapDirForPack(c, packSnapYaml) + + _, err := snaprun.Parser().ParseArgs([]string{"pack", snapDir, snapDir}) + c.Assert(err, check.ErrorMatches, ".* snap is unusable due to missing files") +} + +func (s *SnapSuite) TestPackPacksASnap(c *check.C) { + snapDir := makeSnapDirForPack(c, packSnapYaml) + + const helloBinContent = `#!/bin/sh +printf "hello world" +` + // an example binary + binDir := filepath.Join(snapDir, "bin") + err := os.Mkdir(binDir, 0755) + c.Assert(err, check.IsNil) + err = ioutil.WriteFile(filepath.Join(binDir, "hello"), []byte(helloBinContent), 0755) + c.Assert(err, check.IsNil) + + _, err = snaprun.Parser().ParseArgs([]string{"pack", snapDir, snapDir}) + c.Assert(err, check.IsNil) + + matches, err := filepath.Glob(snapDir + "/hello*.snap") + c.Assert(err, check.IsNil) + c.Assert(matches, check.HasLen, 1) +} diff --git a/daemon/api.go b/daemon/api.go index 5814bceb6c..242f3f9c03 100644 --- a/daemon/api.go +++ b/daemon/api.go @@ -1593,7 +1593,7 @@ func splitQS(qs string) []string { func getSnapConf(c *Command, r *http.Request, user *auth.UserState) Response { vars := muxVars(r) - snapName := vars["name"] + snapName := systemCoreSnapUnalias(vars["name"]) keys := splitQS(r.URL.Query().Get("keys")) @@ -1636,7 +1636,7 @@ func getSnapConf(c *Command, r *http.Request, user *auth.UserState) Response { func setSnapConf(c *Command, r *http.Request, user *auth.UserState) Response { vars := muxVars(r) - snapName := vars["name"] + snapName := systemCoreSnapUnalias(vars["name"]) var patchValues map[string]interface{} if err := jsonutil.DecodeWithNumber(r.Body, &patchValues); err != nil { @@ -2799,3 +2799,10 @@ func postApps(c *Command, r *http.Request, user *auth.UserState) Response { st.EnsureBefore(0) return AsyncResponse(nil, &Meta{Change: chg.ID()}) } + +func systemCoreSnapUnalias(name string) string { + if name == "system" { + return "core" + } + return name +} diff --git a/daemon/api_test.go b/daemon/api_test.go index 5da5016ad9..f4247a4668 100644 --- a/daemon/api_test.go +++ b/daemon/api_test.go @@ -2543,9 +2543,9 @@ func (s *apiSuite) sideloadCheck(c *check.C, content string, head map[string]str return chg.Summary() } -func (s *apiSuite) runGetConf(c *check.C, keys []string, statusCode int) map[string]interface{} { - s.vars = map[string]string{"name": "test-snap"} - req, err := http.NewRequest("GET", "/v2/snaps/test-snap/conf?keys="+strings.Join(keys, ","), nil) +func (s *apiSuite) runGetConf(c *check.C, snapName string, keys []string, statusCode int) map[string]interface{} { + s.vars = map[string]string{"name": snapName} + req, err := http.NewRequest("GET", "/v2/snaps/"+snapName+"/conf?keys="+strings.Join(keys, ","), nil) c.Check(err, check.IsNil) rec := httptest.NewRecorder() snapConfCmd.GET(snapConfCmd, req, nil).ServeHTTP(rec, req) @@ -2568,15 +2568,32 @@ func (s *apiSuite) TestGetConfSingleKey(c *check.C) { tr.Commit() d.overlord.State().Unlock() - result := s.runGetConf(c, []string{"test-key1"}, 200) + result := s.runGetConf(c, "test-snap", []string{"test-key1"}, 200) c.Check(result, check.DeepEquals, map[string]interface{}{"test-key1": "test-value1"}) - result = s.runGetConf(c, []string{"test-key1", "test-key2"}, 200) + result = s.runGetConf(c, "test-snap", []string{"test-key1", "test-key2"}, 200) c.Check(result, check.DeepEquals, map[string]interface{}{"test-key1": "test-value1", "test-key2": "test-value2"}) } +func (s *apiSuite) TestGetConfCoreSystemAlias(c *check.C) { + d := s.daemon(c) + + // Set a config that we'll get in a moment + d.overlord.State().Lock() + tr := config.NewTransaction(d.overlord.State()) + tr.Set("core", "test-key1", "test-value1") + tr.Commit() + d.overlord.State().Unlock() + + result := s.runGetConf(c, "core", []string{"test-key1"}, 200) + c.Check(result, check.DeepEquals, map[string]interface{}{"test-key1": "test-value1"}) + + result = s.runGetConf(c, "system", []string{"test-key1"}, 200) + c.Check(result, check.DeepEquals, map[string]interface{}{"test-key1": "test-value1"}) +} + func (s *apiSuite) TestGetConfMissingKey(c *check.C) { - result := s.runGetConf(c, []string{"test-key2"}, 400) + result := s.runGetConf(c, "test-snap", []string{"test-key2"}, 400) c.Check(result, check.DeepEquals, map[string]interface{}{"message": `snap "test-snap" has no "test-key2" configuration option`}) } @@ -2589,13 +2606,13 @@ func (s *apiSuite) TestGetRootDocument(c *check.C) { tr.Commit() d.overlord.State().Unlock() - result := s.runGetConf(c, nil, 200) + result := s.runGetConf(c, "test-snap", nil, 200) c.Check(result, check.DeepEquals, map[string]interface{}{"test-key1": "test-value1", "test-key2": "test-value2"}) } func (s *apiSuite) TestGetConfBadKey(c *check.C) { // TODO: this one in particular should really be a 400 also - result := s.runGetConf(c, []string{"."}, 500) + result := s.runGetConf(c, "test-snap", []string{"."}, 500) c.Check(result, check.DeepEquals, map[string]interface{}{"message": `invalid option name: ""`}) } @@ -2647,6 +2664,57 @@ func (s *apiSuite) TestSetConf(c *check.C) { }}) } +func (s *apiSuite) TestSetConfCoreSystemAlias(c *check.C) { + d := s.daemon(c) + s.mockSnap(c, ` +name: core +version: 1 +`) + // Mock the hook runner + hookRunner := testutil.MockCommand(c, "snap", "") + defer hookRunner.Restore() + + d.overlord.Loop() + defer d.overlord.Stop() + + text, err := json.Marshal(map[string]interface{}{"key": "value"}) + c.Assert(err, check.IsNil) + + buffer := bytes.NewBuffer(text) + req, err := http.NewRequest("PUT", "/v2/snaps/system/conf", buffer) + c.Assert(err, check.IsNil) + + s.vars = map[string]string{"name": "system"} + + rec := httptest.NewRecorder() + snapConfCmd.PUT(snapConfCmd, req, nil).ServeHTTP(rec, req) + c.Check(rec.Code, check.Equals, 202) + + var body map[string]interface{} + err = json.Unmarshal(rec.Body.Bytes(), &body) + c.Assert(err, check.IsNil) + id := body["change"].(string) + + st := d.overlord.State() + st.Lock() + chg := st.Change(id) + st.Unlock() + c.Assert(chg, check.NotNil) + + <-chg.Ready() + + st.Lock() + err = chg.Err() + tr := config.NewTransaction(st) + st.Unlock() + c.Assert(err, check.IsNil) + + var value string + tr.Get("core", "key", &value) + c.Assert(value, check.Equals, "value") + +} + func (s *apiSuite) TestSetConfNumber(c *check.C) { d := s.daemon(c) s.mockSnap(c, configYaml) diff --git a/overlord/devicestate/devicemgr.go b/overlord/devicestate/devicemgr.go index 504c5c90cd..da7e206897 100644 --- a/overlord/devicestate/devicemgr.go +++ b/overlord/devicestate/devicemgr.go @@ -51,6 +51,8 @@ type DeviceManager struct { lastBecomeOperationalAttempt time.Time becomeOperationalBackoff time.Duration + registered bool + reg chan struct{} } // Manager returns a new device manager. @@ -65,7 +67,11 @@ func Manager(s *state.State, hookManager *hookstate.HookManager) (*DeviceManager } - m := &DeviceManager{state: s, keypairMgr: keypairMgr, runner: runner} + m := &DeviceManager{state: s, keypairMgr: keypairMgr, runner: runner, reg: make(chan struct{})} + + if err := m.confirmRegistered(); err != nil { + return nil, err + } hookManager.Register(regexp.MustCompile("^prepare-device$"), newPrepareDeviceHandler) @@ -76,6 +82,29 @@ func Manager(s *state.State, hookManager *hookstate.HookManager) (*DeviceManager return m, nil } +func (m *DeviceManager) confirmRegistered() error { + m.state.Lock() + defer m.state.Unlock() + + device, err := auth.Device(m.state) + if err != nil { + return err + } + + if device.Serial != "" { + m.markRegistered() + } + return nil +} + +func (m *DeviceManager) markRegistered() { + if m.registered { + return + } + m.registered = true + close(m.reg) +} + type prepareDeviceHandler struct{} func newPrepareDeviceHandler(context *hookstate.Context) hookstate.Handler { @@ -432,6 +461,11 @@ func (m *DeviceManager) Serial() (*asserts.Serial, error) { return Serial(m.state) } +// Registered returns a channel that is closed when the device is known to have been registered. +func (m *DeviceManager) Registered() <-chan struct{} { + return m.reg +} + // DeviceSessionRequestParams produces a device-session-request with the given nonce, together with other required parameters, the device serial and model assertions. func (m *DeviceManager) DeviceSessionRequestParams(nonce string) (*auth.DeviceSessionRequestParams, error) { m.state.Lock() diff --git a/overlord/devicestate/devicestate_test.go b/overlord/devicestate/devicestate_test.go index 14dde7c2ae..87506ded5c 100644 --- a/overlord/devicestate/devicestate_test.go +++ b/overlord/devicestate/devicestate_test.go @@ -392,6 +392,15 @@ version: gadget c.Check(device.Model, Equals, "pc") c.Check(device.Serial, Equals, "9999") + ok := false + select { + case <-s.mgr.Registered(): + ok = true + case <-time.After(5 * time.Second): + c.Fatal("should have been marked registered") + } + c.Check(ok, Equals, true) + a, err := s.db.Find(asserts.SerialType, map[string]string{ "brand-id": "canonical", "model": "pc", @@ -689,6 +698,14 @@ version: gadget }) c.Assert(err, IsNil) + ok := false + select { + case <-s.mgr.Registered(): + default: + ok = true + } + c.Check(ok, Equals, true) + s.state.Unlock() s.mgr.Ensure() s.mgr.Wait() @@ -699,6 +716,15 @@ version: gadget device, err = auth.Device(s.state) c.Check(err, IsNil) c.Check(device.Serial, Equals, "9999") + + ok = false + select { + case <-s.mgr.Registered(): + ok = true + case <-time.After(5 * time.Second): + c.Fatal("should have been marked registered") + } + c.Check(ok, Equals, true) } func (s *deviceMgrSuite) TestDoRequestSerialIdempotentAfterGotSerial(c *C) { @@ -2150,3 +2176,42 @@ func (s *deviceMgrSuite) TestCanManageRefreshesNoRefreshScheduleManaged(c *C) { c.Check(devicestate.CanManageRefreshes(st), Equals, false) } + +func (s *deviceMgrSuite) TestReloadRegistered(c *C) { + st := state.New(nil) + + hookMgr1, err := hookstate.Manager(st) + c.Assert(err, IsNil) + mgr1, err := devicestate.Manager(st, hookMgr1) + c.Assert(err, IsNil) + + ok := false + select { + case <-mgr1.Registered(): + default: + ok = true + } + c.Check(ok, Equals, true) + + st.Lock() + auth.SetDevice(st, &auth.DeviceState{ + Brand: "canonical", + Model: "pc", + Serial: "serial", + }) + st.Unlock() + + hookMgr2, err := hookstate.Manager(st) + c.Assert(err, IsNil) + mgr2, err := devicestate.Manager(st, hookMgr2) + c.Assert(err, IsNil) + + ok = false + select { + case <-mgr2.Registered(): + ok = true + case <-time.After(5 * time.Second): + c.Fatal("should have been marked registered") + } + c.Check(ok, Equals, true) +} diff --git a/overlord/devicestate/handlers.go b/overlord/devicestate/handlers.go index 5639483481..49ace1c860 100644 --- a/overlord/devicestate/handlers.go +++ b/overlord/devicestate/handlers.go @@ -420,6 +420,17 @@ func getSerialRequestConfig(t *state.Task) (*serialRequestConfig, error) { return &cfg, nil } +func (m *DeviceManager) finishRegistration(t *state.Task, device *auth.DeviceState, serial *asserts.Serial) error { + device.Serial = serial.Serial() + err := auth.SetDevice(t.State(), device) + if err != nil { + return err + } + m.markRegistered() + t.SetStatus(state.DoneStatus) + return nil +} + func (m *DeviceManager) doRequestSerial(t *state.Task, _ *tomb.Tomb) error { st := t.State() st.Lock() @@ -456,13 +467,7 @@ func (m *DeviceManager) doRequestSerial(t *state.Task, _ *tomb.Tomb) error { if len(serials) == 1 { // means we saved the assertion but didn't get to the end of the task - device.Serial = serials[0].(*asserts.Serial).Serial() - err := auth.SetDevice(st, device) - if err != nil { - return err - } - t.SetStatus(state.DoneStatus) - return nil + return m.finishRegistration(t, device, serials[0].(*asserts.Serial)) } if len(serials) > 1 { return fmt.Errorf("internal error: multiple serial assertions for the same device key") @@ -499,13 +504,7 @@ func (m *DeviceManager) doRequestSerial(t *state.Task, _ *tomb.Tomb) error { return &state.Retry{} } - device.Serial = serial.Serial() - err = auth.SetDevice(st, device) - if err != nil { - return err - } - t.SetStatus(state.DoneStatus) - return nil + return m.finishRegistration(t, device, serial) } var repeatRequestSerial string // for tests diff --git a/packaging/ubuntu-14.04/snapd.postrm b/packaging/ubuntu-14.04/snapd.postrm index c58e52fd86..ff324c4fb3 100644 --- a/packaging/ubuntu-14.04/snapd.postrm +++ b/packaging/ubuntu-14.04/snapd.postrm @@ -87,13 +87,13 @@ if [ "$1" = "purge" ]; then echo "Discarding preserved snap namespaces" # opportunistic as those might not be actually mounted if [ -d /run/snapd/ns ]; then - if [ $(ls /run/snapd/ns/*.mnt | wc -l) -gt 0 ]; then + if [ "$(find /run/snapd/ns/ -name "*.mnt" | wc -l)" -gt 0 ]; then for mnt in /run/snapd/ns/*.mnt; do umount -l "$mnt" || true rm -f "$mnt" done fi - if [ $(ls /run/snapd/ns/*.fstab | wc -l) -gt 0 ]; then + if [ "$(find /run/snapd/ns/ -name "*.fstab" | wc -l)" -gt 0 ]; then for fstab in /run/snapd/ns/*.fstab; do rm -f "$fstab" done diff --git a/packaging/ubuntu-16.04/snapd.postinst b/packaging/ubuntu-16.04/snapd.postinst index 9aefd971a3..67a5b1334b 100644 --- a/packaging/ubuntu-16.04/snapd.postinst +++ b/packaging/ubuntu-16.04/snapd.postinst @@ -11,4 +11,29 @@ case "$1" in if dpkg --compare-versions "$2" lt-nl "2.0.7"; then ldconfig fi -esac + + # Ensure that we undo the damage done by the snap.mount unit that was present + # in snapd 2.31. + # + # We found that update scripts make systemd stop inactive mount units and this + # in turn stops all the units that depend on it so when the snap.mount unit is + # stopped all the per-snap mount units gets stopped along with them. The 2.31 + # release was only out briefly in xenial-proposed and bionic but to keep the + # affected users safe let's start all the per-snap mount units so that snaps no + # longer appear as broken after update. + if dpkg --compare-versions "$2" ge-nl "2.31" && \ + dpkg --compare-versions "$2" lt-nl "2.31.2"; then + units=$(systemctl list-unit-files --full | grep '^snap[-.]' | cut -f1 -d ' ' | grep -vF snap.mount.service || true) + mounts=$(echo "$units" | grep '^snap[-.].*\.mount$' || true) + for unit in $mounts; do + # ensure its really a snap mount unit or systemd unit + if ! grep -q 'What=/var/lib/snapd/snaps/' "/etc/systemd/system/$unit" && ! grep -q 'X-Snappy=yes' "/etc/systemd/system/$unit"; then + echo "Skipping non-snapd systemd unit $unit" + continue + fi + + echo "Starting $unit" + systemctl start "$unit" || true + done + fi +esac \ No newline at end of file diff --git a/packaging/ubuntu-16.04/snapd.postrm b/packaging/ubuntu-16.04/snapd.postrm index efe936f69e..429ed13e5e 100644 --- a/packaging/ubuntu-16.04/snapd.postrm +++ b/packaging/ubuntu-16.04/snapd.postrm @@ -90,13 +90,13 @@ if [ "$1" = "purge" ]; then echo "Discarding preserved snap namespaces" # opportunistic as those might not be actually mounted if [ -d /run/snapd/ns ]; then - if [ $(ls /run/snapd/ns/*.mnt | wc -l) -gt 0 ]; then + if [ "$(find /run/snapd/ns/ -name "*.mnt" | wc -l)" -gt 0 ]; then for mnt in /run/snapd/ns/*.mnt; do umount -l "$mnt" || true rm -f "$mnt" done fi - if [ $(ls /run/snapd/ns/*.fstab | wc -l) -gt 0 ]; then + if [ "$(find /run/snapd/ns/ -name "*.fstab" | wc -l)" -gt 0 ]; then for fstab in /run/snapd/ns/*.fstab; do rm -f "$fstab" done diff --git a/packaging/ubuntu-16.04/tests/integrationtests b/packaging/ubuntu-16.04/tests/integrationtests index aec488e290..191ab592a3 100644 --- a/packaging/ubuntu-16.04/tests/integrationtests +++ b/packaging/ubuntu-16.04/tests/integrationtests @@ -18,7 +18,7 @@ fi systemctl daemon-reload # ensure we are not get killed too easily -echo -950 > /proc/$$/oom_score_adj +printf "%s\n" "-950" > /proc/$$/oom_score_adj # see what mem we have (for debugging) cat /proc/meminfo @@ -45,4 +45,4 @@ go get -u github.com/snapcore/spread/cmd/spread # store journal info for inspectsion journalctl --sync -journalctl -ab > $ADT_ARTIFACTS/journal.txt +journalctl -ab > "$ADT_ARTIFACTS"/journal.txt diff --git a/run-checks b/run-checks index 925dcc1696..472f71478d 100755 --- a/run-checks +++ b/run-checks @@ -16,6 +16,16 @@ else goctest="go test" fi COVERMODE=${COVERMODE:-atomic} + +# add workaround for https://github.com/golang/go/issues/24449 +if [ "$(uname -m)" = "s390x" ]; then + if go version | grep -q go1.10; then + echo "covermode 'atomic' crashes on s390x with go1.10, reseting " + echo "to 'set'. see https://github.com/golang/go/issues/24449" + COVERMODE="set" + fi +fi + export GOPATH="${GOPATH:-$(realpath "$(dirname "$0")"/../../../../)}" export PATH="$PATH:${GOPATH%%:*}/bin" diff --git a/snap/pack/pack.go b/snap/pack/pack.go index 9092159abd..5bf9e6e6a7 100644 --- a/snap/pack/pack.go +++ b/snap/pack/pack.go @@ -211,24 +211,36 @@ func copyToBuildDir(sourceDir, buildDir string) error { }) } -func prepare(sourceDir, targetDir, buildDir string) (snapName string, err error) { +// CheckSkeleton attempts to validate snap data in source directory +func CheckSkeleton(sourceDir string) error { + _, err := loadAndValidate(sourceDir) + return err +} + +func loadAndValidate(sourceDir string) (*snap.Info, error) { // ensure we have valid content yaml, err := ioutil.ReadFile(filepath.Join(sourceDir, "meta", "snap.yaml")) if err != nil { - return "", err + return nil, err } info, err := snap.InfoFromSnapYaml(yaml) if err != nil { - return "", err + return nil, err } - err = snap.Validate(info) - if err != nil { - return "", err + if err := snap.Validate(info); err != nil { + return nil, err } - err = snap.ValidateContainer(snapdir.New(sourceDir), info, logger.Noticef) + if err := snap.ValidateContainer(snapdir.New(sourceDir), info, logger.Noticef); err != nil { + return nil, err + } + return info, nil +} + +func prepare(sourceDir, targetDir, buildDir string) (snapName string, err error) { + info, err := loadAndValidate(sourceDir) if err != nil { return "", err } diff --git a/snap/pack/pack_test.go b/snap/pack/pack_test.go index b442e0c560..245d7ed3ab 100644 --- a/snap/pack/pack_test.go +++ b/snap/pack/pack_test.go @@ -123,6 +123,18 @@ apps: c.Assert(err, Equals, snap.ErrMissingPaths) } +func (s *packSuite) TestValidateMissingAppFailsWithErrMissingPaths(c *C) { + sourceDir := makeExampleSnapSourceDir(c, `name: hello +version: 0 +apps: + foo: + command: bin/hello-world +`) + c.Assert(os.Remove(filepath.Join(sourceDir, "bin", "hello-world")), IsNil) + err := pack.CheckSkeleton(sourceDir) + c.Assert(err, Equals, snap.ErrMissingPaths) +} + func (s *packSuite) TestCopyCopies(c *C) { sourceDir := makeExampleSnapSourceDir(c, "{name: hello, version: 0}") // actually this'll be on /tmp so it'll be a link diff --git a/snap/squashfs/squashfs_test.go b/snap/squashfs/squashfs_test.go index ad121c924e..f1c1e702e9 100644 --- a/snap/squashfs/squashfs_test.go +++ b/snap/squashfs/squashfs_test.go @@ -423,5 +423,5 @@ func (s *SquashfsTestSuite) TestBuildDate(c *C) { c.Assert(snap.Build(d), IsNil) // and see it's BuildDate is _now_, not _then_. c.Check(BuildDate(filename), Equals, snap.BuildDate()) - c.Check(math.Abs(now.Sub(snap.BuildDate()).Seconds()) <= 61, Equals, true) + c.Check(math.Abs(now.Sub(snap.BuildDate()).Seconds()) <= 61, Equals, true, Commentf("Unexpected build date %s", snap.BuildDate())) } diff --git a/spread.yaml b/spread.yaml index 0f67e25470..7a55e94c3b 100644 --- a/spread.yaml +++ b/spread.yaml @@ -49,10 +49,14 @@ backends: key: "$(HOST: echo $SPREAD_GOOGLE_KEY)" location: computeengine/us-east1-b systems: - - ubuntu-16.04-64: - workers: 8 - ubuntu-14.04-64: workers: 6 + - ubuntu-16.04-64: + workers: 8 + # disabled because of: cannot connect: cannot connect to google:ubuntu-18.04-64 (mar200647-634341): ssh: handshake failed: EOF + #- ubuntu-18.04-64: + # image: ubuntu-os-cloud-devel/ubuntu-1804-lts + # workers: 6 - ubuntu-core-16-64: image: ubuntu-16.04-64 @@ -439,6 +443,8 @@ suites: # we keep them disabled. A later PR will enable most tests and # drop this blacklist. systems: [-ubuntu-core-16-*, -fedora-*, -opensuse-*] + # unittests are run as part of the autopkgtest build already + backends: [-autopkgtest] environment: # env vars required for coverage reporting from a spread task TRAVIS_BUILD_NUMBER: "$(HOST: echo $TRAVIS_BUILD_NUMBER)" diff --git a/store/store.go b/store/store.go index 238595e79c..826f67c862 100644 --- a/store/store.go +++ b/store/store.go @@ -585,7 +585,7 @@ func authenticateDevice(r *http.Request, device *auth.DeviceState) { } } -func (s *Store) setStoreID(r *http.Request) { +func (s *Store) setStoreID(r *http.Request) (customStore bool) { storeID := s.fallbackStoreID if s.authContext != nil { cand, err := s.authContext.StoreID(storeID) @@ -597,9 +597,18 @@ func (s *Store) setStoreID(r *http.Request) { } if storeID != "" { r.Header.Set("X-Ubuntu-Store", storeID) + return true } + return false } +type deviceAuthNeed int + +const ( + deviceAuthPreferred deviceAuthNeed = iota + deviceAuthCustomStoreOnly +) + // requestOptions specifies parameters for store requests. type requestOptions struct { Method string @@ -608,6 +617,13 @@ type requestOptions struct { ContentType string ExtraHeaders map[string]string Data []byte + + // DeviceAuthNeed indicates the level of need to supply device + // authorization for this request, can be: + // - deviceAuthPreferred: should be provided if available + // - deviceAuthCustomStoreOnly: should be provided only in case + // of a custom store + DeviceAuthNeed deviceAuthNeed } func (r *requestOptions) addHeader(k, v string) { @@ -814,7 +830,9 @@ func (s *Store) newRequest(reqOptions *requestOptions, user *auth.UserState) (*h return nil, err } - if s.authContext != nil { + customStore := s.setStoreID(req) + + if s.authContext != nil && (customStore || reqOptions.DeviceAuthNeed != deviceAuthCustomStoreOnly) { device, err := s.authContext.Device() if err != nil { return nil, err @@ -861,8 +879,6 @@ func (s *Store) newRequest(reqOptions *requestOptions, user *auth.UserState) (*h req.Header.Set(header, value) } - s.setStoreID(req) - return req, nil } @@ -1175,9 +1191,10 @@ func (s *Store) Find(search *Search, user *auth.UserState) ([]*snap.Info, error) // Sections retrieves the list of available store sections. func (s *Store) Sections(ctx context.Context, user *auth.UserState) ([]string, error) { reqOptions := &requestOptions{ - Method: "GET", - URL: s.endpointURL(sectionsEndpPath, nil), - Accept: halJsonContentType, + Method: "GET", + URL: s.endpointURL(sectionsEndpPath, nil), + Accept: halJsonContentType, + DeviceAuthNeed: deviceAuthCustomStoreOnly, } var sectionData sectionResults @@ -1216,9 +1233,10 @@ func (s *Store) WriteCatalogs(ctx context.Context, names io.Writer, adder SnapAd u.RawQuery = q.Encode() reqOptions := &requestOptions{ - Method: "GET", - URL: &u, - Accept: halJsonContentType, + Method: "GET", + URL: &u, + Accept: halJsonContentType, + DeviceAuthNeed: deviceAuthCustomStoreOnly, } // do not log body for catalog updates (its huge) diff --git a/store/store_test.go b/store/store_test.go index 9c9a471ee2..7b914704da 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -2777,6 +2777,8 @@ func (s *storeTestSuite) TestSectionsQuery(c *C) { n := 0 mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assertRequest(c, r, "GET", sectionsPath) + c.Check(r.Header.Get("X-Device-Authorization"), Equals, "") + switch n { case 0: // All good. @@ -2796,7 +2798,41 @@ func (s *storeTestSuite) TestSectionsQuery(c *C) { cfg := Config{ StoreBaseURL: serverURL, } - sto := New(&cfg, nil) + authContext := &testAuthContext{c: c, device: s.device} + sto := New(&cfg, authContext) + + sections, err := sto.Sections(context.TODO(), s.user) + c.Check(err, IsNil) + c.Check(sections, DeepEquals, []string{"featured", "database"}) +} + +func (s *storeTestSuite) TestSectionsQueryCustomStore(c *C) { + n := 0 + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assertRequest(c, r, "GET", sectionsPath) + c.Check(r.Header.Get("X-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`) + + switch n { + case 0: + // All good. + default: + c.Fatalf("what? %d", n) + } + + w.Header().Set("Content-Type", "application/hal+json") + w.WriteHeader(200) + io.WriteString(w, MockSectionsJSON) + n++ + })) + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + serverURL, _ := url.Parse(mockServer.URL) + cfg := Config{ + StoreBaseURL: serverURL, + } + authContext := &testAuthContext{c: c, device: s.device, storeID: "my-brand-store"} + sto := New(&cfg, authContext) sections, err := sto.Sections(context.TODO(), s.user) c.Check(err, IsNil) @@ -2846,6 +2882,8 @@ func (s *storeTestSuite) testSnapCommands(c *C, onClassic bool) { n := 0 mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + c.Check(r.Header.Get("X-Device-Authorization"), Equals, "") + switch n { case 0: query := r.URL.Query() @@ -2870,7 +2908,8 @@ func (s *storeTestSuite) testSnapCommands(c *C, onClassic bool) { defer mockServer.Close() serverURL, _ := url.Parse(mockServer.URL) - sto := New(&Config{StoreBaseURL: serverURL}, nil) + authContext := &testAuthContext{c: c, device: s.device} + sto := New(&Config{StoreBaseURL: serverURL}, authContext) db, err := advisor.Create() c.Assert(err, IsNil) diff --git a/tests/lib/names.sh b/tests/lib/names.sh index 9a0a6e5d70..6b898f5b61 100644 --- a/tests/lib/names.sh +++ b/tests/lib/names.sh @@ -1,4 +1,5 @@ #!/bin/bash +# shellcheck disable=SC2034 gadget_name=$(snap list | grep 'gadget$' | awk '{ print $1 }') kernel_name=$(snap list | grep 'kernel$' | awk '{ print $1 }') diff --git a/tests/lib/pkgdb.sh b/tests/lib/pkgdb.sh index 2b77f43115..ec31ec9a03 100755 --- a/tests/lib/pkgdb.sh +++ b/tests/lib/pkgdb.sh @@ -205,6 +205,7 @@ distro_install_package() { } distro_purge_package() { + # shellcheck disable=SC2046 set -- $( for pkg in "$@" ; do package_name=$(distro_name_package "$pkg") @@ -428,6 +429,12 @@ pkg_dependencies_ubuntu_classic(){ linux-image-extra-4.13.0-16-generic " ;; + ubuntu-18.04-64) + echo " + linux-image-extra-$(uname -r) + squashfs-tools + " + ;; ubuntu-*) echo " linux-image-extra-$(uname -r) diff --git a/tests/lib/prepare.sh b/tests/lib/prepare.sh index 44c7d8042e..6efeb3f4ca 100755 --- a/tests/lib/prepare.sh +++ b/tests/lib/prepare.sh @@ -210,7 +210,7 @@ prepare_classic() { done # Copy all of the snaps back to the spool directory. From there we # will reuse them during subsequent `snap install` operations. - cp *.snap /var/lib/snapd/snaps/ + cp -- *.snap /var/lib/snapd/snaps/ set +x ) diff --git a/tests/lib/reset.sh b/tests/lib/reset.sh index db64729c2a..a690042fe8 100755 --- a/tests/lib/reset.sh +++ b/tests/lib/reset.sh @@ -13,6 +13,18 @@ reset_classic() { # have changed on the disk. systemctl daemon-reload + echo "Ensure the service is active before stopping it" + retries=20 + systemctl status snapd.service snapd.socket || true + while systemctl status snapd.service snapd.socket | grep "Active: activating"; do + if [ $retries -eq 0 ]; then + echo "snapd service or socket not active" + exit 1 + fi + retries=$(( retries - 1 )) + sleep 1 + done + systemd_stop_units snapd.service snapd.socket case "$SPREAD_SYSTEM" in diff --git a/tests/lib/snaps/test-snapd-service/bin/reload b/tests/lib/snaps/test-snapd-service/bin/reload index e04f606e14..4e14f5aa90 100755 --- a/tests/lib/snaps/test-snapd-service/bin/reload +++ b/tests/lib/snaps/test-snapd-service/bin/reload @@ -5,4 +5,4 @@ if [ -z "$MAINPID" ]; then exit 1 fi echo "reloading main PID: $MAINPID" -kill -HUP $MAINPID +kill -HUP "$MAINPID" diff --git a/tests/lib/store.sh b/tests/lib/store.sh index 3915b3e9f9..84db342d44 100644 --- a/tests/lib/store.sh +++ b/tests/lib/store.sh @@ -48,6 +48,9 @@ make_snap_installable(){ } setup_fake_store(){ + # before switching make sure we have a session macaroon + snap find test-snapd-tools + local top_dir=$1 mkdir -p "$top_dir/asserts" diff --git a/tests/main/classic-ubuntu-core-transition-auth/task.yaml b/tests/main/classic-ubuntu-core-transition-auth/task.yaml index ffeef91408..34fffc7976 100644 --- a/tests/main/classic-ubuntu-core-transition-auth/task.yaml +++ b/tests/main/classic-ubuntu-core-transition-auth/task.yaml @@ -6,6 +6,10 @@ summary: Ensure that the ubuntu-core -> core transition works with auth.json # fishy going on and the snapd service gets terminated during the process. systems: [-ubuntu-core-16-*, -ubuntu-*-ppc64el, -fedora-*, -opensuse-*] +# autopkgtest run only a subset of tests that deals with the integration +# with the distro +backends: [-autopkgtest] + warn-timeout: 1m kill-timeout: 5m debug: | diff --git a/tests/main/classic-ubuntu-core-transition-two-cores/task.yaml b/tests/main/classic-ubuntu-core-transition-two-cores/task.yaml index 93a46d37a9..4bdd118a03 100644 --- a/tests/main/classic-ubuntu-core-transition-two-cores/task.yaml +++ b/tests/main/classic-ubuntu-core-transition-two-cores/task.yaml @@ -4,6 +4,10 @@ summary: Ensure that the ubuntu-core -> core transition works with two cores # we disable on ppc64el because the downloads are very slow there systems: [-ubuntu-core-16-*, -ubuntu-*-ppc64el] +# autopkgtest run only a subset of tests that deals with the integration +# with the distro +backends: [-autopkgtest] + warn-timeout: 1m kill-timeout: 5m debug: | diff --git a/tests/main/classic-ubuntu-core-transition/task.yaml b/tests/main/classic-ubuntu-core-transition/task.yaml index 7a6204a0c1..c58c302004 100644 --- a/tests/main/classic-ubuntu-core-transition/task.yaml +++ b/tests/main/classic-ubuntu-core-transition/task.yaml @@ -6,6 +6,10 @@ summary: Ensure that the ubuntu-core -> core transition works # fishy going on and the snapd service gets terminated during the process. systems: [-ubuntu-core-16-*, -ubuntu-*-ppc64el, -fedora-*, -opensuse-*, -ubuntu-*-i386] +# autopkgtest run only a subset of tests that deals with the integration +# with the distro +backends: [-autopkgtest] + warn-timeout: 1m kill-timeout: 5m diff --git a/tests/main/econnreset/task.yaml b/tests/main/econnreset/task.yaml index a05216395d..f9b5b409c4 100644 --- a/tests/main/econnreset/task.yaml +++ b/tests/main/econnreset/task.yaml @@ -5,6 +5,10 @@ restore: | rm -f test-snapd-huge_* +# autopkgtest run only a subset of tests that deals with the integration +# with the distro +backends: [-autopkgtest] + execute: | echo "Downloading a large snap in the background" su -c "/usr/bin/env SNAPD_DEBUG=1 snap download --edge test-snapd-huge 2>snap-download.log" test & diff --git a/tests/main/interfaces-kernel-module-control/task.yaml b/tests/main/interfaces-kernel-module-control/task.yaml index f8dcdeb828..905be7a9f4 100644 --- a/tests/main/interfaces-kernel-module-control/task.yaml +++ b/tests/main/interfaces-kernel-module-control/task.yaml @@ -40,6 +40,11 @@ execute: | CONNECTED_PATTERN=":kernel-module-control +.*test-snapd-kernel-module-consumer" DISCONNECTED_PATTERN="\- +test-snapd-kernel-module-consumer:kernel-module-control" + if ! [ -f /lib/modules/$(uname -r)/kernel/fs/$MODULE/$MODULE.ko ]; then + echo "$MODULE module not available in the system" + exit 0 + fi + echo "The plug is disconnected by default" snap interfaces | MATCH "$DISCONNECTED_PATTERN" @@ -57,7 +62,7 @@ execute: | echo "And the snap is able to insert a module" if lsmod | MATCH $MODULE; then touch module_present - rmmod minix + rmmod $MODULE fi lsmod | MATCH -v $MODULE test-snapd-kernel-module-consumer.insmod $MODULE_PATH diff --git a/tests/main/interfaces-snapd-control-with-manage/task.yaml b/tests/main/interfaces-snapd-control-with-manage/task.yaml index 46ab4303db..d3e8f53cdf 100644 --- a/tests/main/interfaces-snapd-control-with-manage/task.yaml +++ b/tests/main/interfaces-snapd-control-with-manage/task.yaml @@ -49,7 +49,10 @@ restore: | fi . $TESTSLIB/store.sh teardown_fake_store $BLOB_DIR - + +debug: | + jq .data.auth.device /var/lib/snapd/state.json + execute: | if [ "$TRUST_TEST_KEYS" = "false" ]; then echo "This test needs test keys to be trusted" diff --git a/tests/main/layout/task.yaml b/tests/main/layout/task.yaml index 15ab8516ea..49cecba482 100644 --- a/tests/main/layout/task.yaml +++ b/tests/main/layout/task.yaml @@ -9,49 +9,49 @@ prepare: | snap set core experimental.layouts=true . $TESTSLIB/snaps.sh install_local test-snapd-layout -debug: | - # We saw some failures where it looked like we were confined by the most - # wrong thing imaginable. To get an idea what is going on let's show - # the profile we were using when the test fails. - # Before per-snap update-ns profiles are here. - cat /etc/apparmor.d/*snap.core.*.usr.lib.snapd.snap-confine* || : - # Once per-snap update-ns profiles are here. - cat /var/lib/snapd/apparmor/profiles/snap-update-ns.test-snapd-layout || : execute: | - echo "snap declaring layouts doesn't explode on startup" - test-snapd-layout.sh -c "true" - - echo "layout declarations are honored" + . $TESTSLIB/snaps.sh + for i in $(seq 2); do + if [ "$i" -eq 2 ]; then + echo "The snap works across refreshes" + install_local test-snapd-layout + fi + + echo "snap declaring layouts doesn't explode on startup" + test-snapd-layout.sh -c "true" + + echo "layout declarations are honored" - test-snapd-layout.sh -c "test -d /etc/demo" - test-snapd-layout.sh -c "test -f /etc/demo.conf" - test-snapd-layout.sh -c "test -h /etc/demo.cfg" - test "$(test-snapd-layout.sh -c "readlink /etc/demo.cfg")" = "$(test-snapd-layout.sh -c 'echo $SNAP_COMMON/etc/demo.conf')" - test-snapd-layout.sh -c "test -d /usr/share/demo" - test-snapd-layout.sh -c "test -d /var/lib/demo" - test-snapd-layout.sh -c "test -d /var/cache/demo" - test-snapd-layout.sh -c "test -d /opt/demo" + test-snapd-layout.sh -c "test -d /etc/demo" + test-snapd-layout.sh -c "test -f /etc/demo.conf" + test-snapd-layout.sh -c "test -h /etc/demo.cfg" + test "$(test-snapd-layout.sh -c "readlink /etc/demo.cfg")" = "$(test-snapd-layout.sh -c 'echo $SNAP_COMMON/etc/demo.conf')" + test-snapd-layout.sh -c "test -d /usr/share/demo" + test-snapd-layout.sh -c "test -d /var/lib/demo" + test-snapd-layout.sh -c "test -d /var/cache/demo" + test-snapd-layout.sh -c "test -d /opt/demo" - echo "layout locations pointing to SNAP_DATA and SNAP_COMMON are writable" - echo "and the writes go to the right place in the backing store" + echo "layout locations pointing to SNAP_DATA and SNAP_COMMON are writable" + echo "and the writes go to the right place in the backing store" - test-snapd-layout.sh -c "echo foo-1 > /etc/demo/writable" - test "$(test-snapd-layout.sh -c 'cat $SNAP_COMMON/etc/demo/writable')" = "foo-1" + test-snapd-layout.sh -c "echo foo-1 > /etc/demo/writable" + test "$(test-snapd-layout.sh -c 'cat $SNAP_COMMON/etc/demo/writable')" = "foo-1" - test-snapd-layout.sh -c "echo foo-2 > /etc/demo.conf" - test "$(test-snapd-layout.sh -c 'cat $SNAP_COMMON/etc/demo.conf')" = "foo-2" + test-snapd-layout.sh -c "echo foo-2 > /etc/demo.conf" + test "$(test-snapd-layout.sh -c 'cat $SNAP_COMMON/etc/demo.conf')" = "foo-2" - # NOTE: this is a symlink to demo.conf, effectively - test-snapd-layout.sh -c "echo foo-3 > /etc/demo.cfg" - test "$(test-snapd-layout.sh -c 'cat $SNAP_COMMON/etc/demo.conf')" = "foo-3" + # NOTE: this is a symlink to demo.conf, effectively + test-snapd-layout.sh -c "echo foo-3 > /etc/demo.cfg" + test "$(test-snapd-layout.sh -c 'cat $SNAP_COMMON/etc/demo.conf')" = "foo-3" - test-snapd-layout.sh -c "echo foo-4 > /var/lib/demo/writable" - test "$(test-snapd-layout.sh -c 'cat $SNAP_DATA/var/lib/demo/writable')" = "foo-4" + test-snapd-layout.sh -c "echo foo-4 > /var/lib/demo/writable" + test "$(test-snapd-layout.sh -c 'cat $SNAP_DATA/var/lib/demo/writable')" = "foo-4" - test-snapd-layout.sh -c "echo foo-5 > /var/cache/demo/writable" - test "$(test-snapd-layout.sh -c 'cat $SNAP_DATA/var/cache/demo/writable')" = "foo-5" + test-snapd-layout.sh -c "echo foo-5 > /var/cache/demo/writable" + test "$(test-snapd-layout.sh -c 'cat $SNAP_DATA/var/cache/demo/writable')" = "foo-5" - echo "layout locations pointing to SNAP are readable" + echo "layout locations pointing to SNAP are readable" - test-snapd-layout.sh -c "test -r /usr/share/demo/file" - test-snapd-layout.sh -c "test -r /opt/demo/file" + test-snapd-layout.sh -c "test -r /usr/share/demo/file" + test-snapd-layout.sh -c "test -r /opt/demo/file" + done diff --git a/tests/main/listing/task.yaml b/tests/main/listing/task.yaml index a7567ee187..067a4f9dfe 100644 --- a/tests/main/listing/task.yaml +++ b/tests/main/listing/task.yaml @@ -4,8 +4,11 @@ prepare: | . $TESTSLIB/snaps.sh install_local test-snapd-tools -execute: | +# autopkgtest run only a subset of tests that deals with the integration +# with the distro +backends: [-autopkgtest] +execute: | echo "List prints core snap version" # most core versions should be like "16-2", so [0-9]{2}-[0-9.]+ # but edge will have a timestamp in there, "16.2+201701010932", so add an optional \+[0-9]+ to the end diff --git a/tests/main/lxd/task.yaml b/tests/main/lxd/task.yaml index 7cc1d1fb4a..e5c61e3167 100644 --- a/tests/main/lxd/task.yaml +++ b/tests/main/lxd/task.yaml @@ -7,6 +7,10 @@ summary: Ensure that lxd works # FIXME LXD has some issue on Google images. systems: [ubuntu-16.04-32, ubuntu-core-*] +# autopkgtest run only a subset of tests that deals with the integration +# with the distro +backends: [-autopkgtest] + # lxd downloads can be quite slow kill-timeout: 25m diff --git a/tests/main/prepare-image-uboot/task.yaml b/tests/main/prepare-image-uboot/task.yaml index a233f7a29f..5285c77fe8 100644 --- a/tests/main/prepare-image-uboot/task.yaml +++ b/tests/main/prepare-image-uboot/task.yaml @@ -1,5 +1,9 @@ summary: Check that prepare-image works for uboot-systems +# autopkgtest run only a subset of tests that deals with the integration +# with the distro +backends: [-autopkgtest] + environment: ROOT: /home/test/tmp/ IMAGE: /home/test/tmp/image diff --git a/tests/main/searching/task.yaml b/tests/main/searching/task.yaml index 67629ebe53..337236da86 100644 --- a/tests/main/searching/task.yaml +++ b/tests/main/searching/task.yaml @@ -3,6 +3,10 @@ summary: Check snap search # s390x,ppc64el have nothing featured systems: [-ubuntu-*-ppc64el, -ubuntu-*-s390x] +# autopkgtest run only a subset of tests that deals with the integration +# with the distro +backends: [-autopkgtest] + restore: | rm -f featured.txt diff --git a/tests/main/system-core-alias/task.yaml b/tests/main/system-core-alias/task.yaml new file mode 100644 index 0000000000..a7c0c8c8a0 --- /dev/null +++ b/tests/main/system-core-alias/task.yaml @@ -0,0 +1,19 @@ +summary: Verify that 'system' can be used as an alias for 'core' + +execute: | + echo "When a configuration is set for the core snap" + snap set core foo.bar=true + snap get core foo.bar | MATCH "true" + + echo "It can be retrieved using system alias" + snap get system foo.bar | MATCH "true" + + echo "When a configuration is set using the system alias" + snap set system foo.bar=baz + snap get system foo.bar | MATCH "baz" + + echo "It is also visible through the core snap" + snap get core foo.bar | MATCH "baz" +debug: | + snap get core -d || true + snap get system -d || true diff --git a/tests/upgrade/basic/task.yaml b/tests/upgrade/basic/task.yaml index b4903ce9c1..b907dbdf8e 100644 --- a/tests/upgrade/basic/task.yaml +++ b/tests/upgrade/basic/task.yaml @@ -18,7 +18,10 @@ execute: | . "$TESTSLIB/snaps.sh" echo "Remove snapd and snap-confine" - distro_purge_package snapd snapd-selinux snap-confine || true + distro_purge_package snapd snap-confine || true + if [[ "$SPREAD_SYSTEM" = fedora-* ]] ; then + distro_purge_package snapd-selinux || true + fi echo "Install previous snapd version from the store" distro_install_package snap-confine snapd diff --git a/testutil/lowlevel.go b/testutil/lowlevel.go index 035ab085ef..18d431736e 100644 --- a/testutil/lowlevel.go +++ b/testutil/lowlevel.go @@ -90,6 +90,10 @@ func formatOpenFlags(flags int) string { flags ^= syscall.O_EXCL fl = append(fl, "O_EXCL") } + if flags&sys.O_PATH != 0 { + flags ^= sys.O_PATH + fl = append(fl, "O_PATH") + } if flags != 0 { panic(fmt.Errorf("unrecognized open flags %d", flags)) } |
