diff options
| author | Pawel Stolowski <stolowski@gmail.com> | 2019-01-29 13:07:25 +0100 |
|---|---|---|
| committer | Pawel Stolowski <stolowski@gmail.com> | 2019-01-29 13:07:25 +0100 |
| commit | ef9e72251a38aacdfceaf3f282767668c4155896 (patch) | |
| tree | dba34fb7f71b8c74b471eb8fba94a94cf829e497 | |
| parent | 4d376a19aed0294393dc89ee4dfd3302b217ed1a (diff) | |
| parent | 740eec88724e87c2eb4b96c2e9ef65b086acc773 (diff) | |
Merge branch 'master' into hotplug-update-slothotplug-update-slot
135 files changed, 2048 insertions, 551 deletions
diff --git a/.travis.yml b/.travis.yml index 0c02f33f3b..52404c8e09 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,9 @@ git: jobs: include: - stage: quick - name: go 1.6/xenial static and unit test suites + name: go 1.9/xenial static and unit test suites dist: xenial - go: 1.6 + go: 1.9 before_install: - sudo apt --quiet -o Dpkg::Progress-Fancy=false update install: @@ -36,6 +36,7 @@ jobs: - /tmp/snp pack tests/lib/snaps/test-snapd-tools/ /tmp - stage: quick name: CLA check + dist: xenial if: type = pull_request language: bash addons: @@ -46,7 +47,7 @@ jobs: - ./tests/lib/cla_check.py - stage: integration name: spread - os: linux + dist: xenial addons: apt: packages: diff --git a/cmd/snap-update-ns/bootstrap.c b/cmd/snap-update-ns/bootstrap.c index 1d774ca679..8fd3d82d27 100644 --- a/cmd/snap-update-ns/bootstrap.c +++ b/cmd/snap-update-ns/bootstrap.c @@ -344,6 +344,7 @@ static int parse_arg_u(int argc, char * const *argv, int *optind, unsigned long errno = 0; char *uid_text_end = NULL; unsigned long parsed_uid = strtoul(uid_text, &uid_text_end, 10); + int saved_errno = errno; char c = *uid_text; if ( /* Reject overflow in parsed representation */ @@ -359,7 +360,7 @@ static int parse_arg_u(int argc, char * const *argv, int *optind, unsigned long || (*uid_text != '\0' && uid_text_end != NULL && *uid_text_end != '\0')) { bootstrap_msg = "cannot parse user id"; - bootstrap_errno = errno; + bootstrap_errno = saved_errno; return -1; } if ((long)parsed_uid < 0) { diff --git a/cmd/snap/cmd_info.go b/cmd/snap/cmd_info.go index 74d7eca985..760d15bee0 100644 --- a/cmd/snap/cmd_info.go +++ b/cmd/snap/cmd_info.go @@ -23,6 +23,7 @@ import ( "fmt" "io" "path/filepath" + "strconv" "strings" "text/tabwriter" "time" @@ -118,7 +119,7 @@ func maybePrintBase(w io.Writer, base string, verbose bool) { } } -func tryDirect(w io.Writer, path string, verbose bool) bool { +func tryDirect(w io.Writer, path string, verbose bool, termWidth int) bool { path = norm(path) snapf, err := snap.Open(path) @@ -141,7 +142,7 @@ func tryDirect(w io.Writer, path string, verbose bool) bool { } fmt.Fprintf(w, "path:\t%q\n", path) fmt.Fprintf(w, "name:\t%s\n", info.InstanceName()) - fmt.Fprintf(w, "summary:\t%s\n", formatSummary(info.Summary())) + printSummary(w, info.Summary(), termWidth) var notes *Notes if verbose { @@ -195,9 +196,31 @@ func runesLastIndexSpace(text []rune) int { return -1 } -// wrapLine wraps a line to fit into width, preserving the line's indent, and +// wrapLine wraps a line, assumed to be part of a block-style yaml +// string, to fit into termWidth, preserving the line's indent, and // writes it out prepending padding to each line. -func wrapLine(out io.Writer, text []rune, pad string, width int) error { +func wrapLine(out io.Writer, text []rune, pad string, termWidth int) error { + // discard any trailing whitespace + text = runesTrimRightSpace(text) + // establish the indent of the whole block + idx := 0 + for idx < len(text) && unicode.IsSpace(text[idx]) { + idx++ + } + indent := pad + string(text[:idx]) + text = text[idx:] + return wrapGeneric(out, text, indent, indent, termWidth) +} + +// wrapFlow wraps the text using yaml's flow style, allowing indent +// characters for the first line. +func wrapFlow(out io.Writer, text []rune, indent string, termWidth int) error { + return wrapGeneric(out, text, indent, " ", termWidth) +} + +// wrapGeneric wraps the given text to the given width, prefixing the +// first line with indent and the remaining lines with indent2 +func wrapGeneric(out io.Writer, text []rune, indent, indent2 string, termWidth int) error { // Note: this is _wrong_ for much of unicode (because the width of a rune on // the terminal is anything between 0 and 2, not always 1 as this code // assumes) but fixing that is Hard. Long story short, you can get close @@ -210,16 +233,12 @@ func wrapLine(out io.Writer, text []rune, pad string, width int) error { // This (and possibly printDescr below) should move to strutil once // we're happy with it getting wider (heh heh) use. - // discard any trailing whitespace - text = runesTrimRightSpace(text) + indentWidth := utf8.RuneCountInString(indent) + delta := indentWidth - utf8.RuneCountInString(indent2) + width := termWidth - indentWidth + // establish the indent of the whole block idx := 0 - for idx < len(text) && unicode.IsSpace(text[idx]) { - idx++ - } - indent := pad + string(text[:idx]) - text = text[idx:] - width -= idx + utf8.RuneCountInString(pad) var err error for len(text) > width && err == nil { // find a good place to chop the text @@ -234,6 +253,9 @@ func wrapLine(out io.Writer, text []rune, pad string, width int) error { idx++ } text = text[idx:] + width += delta + indent = indent2 + delta = 0 } if err != nil { return err @@ -242,6 +264,21 @@ func wrapLine(out io.Writer, text []rune, pad string, width int) error { return err } +func printSummary(w io.Writer, raw string, termWidth int) error { + // simplest way of checking to see if it needs quoting is to try + raw = strings.TrimSpace(raw) + type T struct { + S string + } + if len(raw) == 0 { + raw = `""` + } else if err := yaml.UnmarshalStrict([]byte("s: "+raw), &T{}); err != nil { + raw = strconv.Quote(raw) + } + + return wrapFlow(w, []rune(raw), "summary:\t", termWidth) +} + // printDescr formats a given string (typically a snap description) // in a user friendly way. // @@ -249,11 +286,11 @@ func wrapLine(out io.Writer, text []rune, pad string, width int) error { // - trim trailing whitespace // - word wrap at "max" chars preserving line indent // - keep \n intact and break there -func printDescr(w io.Writer, descr string, max int) error { +func printDescr(w io.Writer, descr string, termWidth int) error { var err error descr = strings.TrimRightFunc(descr, unicode.IsSpace) for _, line := range strings.Split(descr, "\n") { - err = wrapLine(w, []rune(line), " ", max) + err = wrapLine(w, []rune(line), " ", termWidth) if err != nil { break } @@ -321,21 +358,59 @@ func maybePrintServices(w io.Writer, snapName string, allApps []client.AppInfo, var channelRisks = []string{"stable", "candidate", "beta", "edge"} -// displayChannels displays channels and tracks in the right order -func (x *infoCmd) displayChannels(w io.Writer, chantpl string, esc *escapes, remote *client.Snap, revLen, sizeLen int) (maxRevLen, maxSizeLen int) { - fmt.Fprintln(w, "channels:") +type channelInfo struct { + indent, name, version, released, revision, size, notes string +} + +type channelInfos struct { + channels []*channelInfo + maxRevLen, maxSizeLen int + releasedfmt, chantpl string + needsHeader bool + esc *escapes +} - releasedfmt := "2006-01-02" - if x.AbsTime { - releasedfmt = time.RFC3339 +func (chInfos *channelInfos) add(indent, name, version string, revision snap.Revision, released time.Time, size int64, notes *Notes) { + chInfo := &channelInfo{ + indent: indent, + name: name, + version: version, + revision: fmt.Sprintf("(%s)", revision), + size: strutil.SizeToStr(size), + notes: notes.String(), + } + if !released.IsZero() { + chInfo.released = released.Format(chInfos.releasedfmt) + } + if len(chInfo.revision) > chInfos.maxRevLen { + chInfos.maxRevLen = len(chInfo.revision) } + if len(chInfo.size) > chInfos.maxSizeLen { + chInfos.maxSizeLen = len(chInfo.size) + } + chInfos.channels = append(chInfos.channels, chInfo) +} + +func (chInfos *channelInfos) addFromLocal(local *client.Snap) { + chInfos.add("", "installed", local.Version, local.Revision, time.Time{}, local.InstalledSize, NotesFromLocal(local)) +} + +func (chInfos *channelInfos) addOpenChannel(name, version string, revision snap.Revision, released time.Time, size int64, notes *Notes) { + chInfos.add(" ", name, version, revision, released, size, notes) +} - type chInfoT struct { - name, version, released, revision, size, notes string +func (chInfos *channelInfos) addClosedChannel(name string, trackHasOpenChannel bool) { + chInfo := &channelInfo{indent: " ", name: name} + if trackHasOpenChannel { + chInfo.version = chInfos.esc.uparrow + } else { + chInfo.version = chInfos.esc.dash } - var chInfos []*chInfoT - maxRevLen, maxSizeLen = revLen, sizeLen + chInfos.channels = append(chInfos.channels, chInfo) +} + +func (chInfos *channelInfos) addFromRemote(remote *client.Snap) { // order by tracks for _, tr := range remote.Tracks { trackHasOpenChannel := false @@ -345,44 +420,24 @@ func (x *infoCmd) displayChannels(w io.Writer, chantpl string, esc *escapes, rem if tr == "latest" { chName = risk } - chInfo := chInfoT{name: chName} if ok { - chInfo.version = ch.Version - chInfo.revision = fmt.Sprintf("(%s)", ch.Revision) - if len(chInfo.revision) > maxRevLen { - maxRevLen = len(chInfo.revision) - } - chInfo.released = ch.ReleasedAt.Format(releasedfmt) - chInfo.size = strutil.SizeToStr(ch.Size) - if len(chInfo.size) > maxSizeLen { - maxSizeLen = len(chInfo.size) - } - chInfo.notes = NotesFromChannelSnapInfo(ch).String() + chInfos.addOpenChannel(chName, ch.Version, ch.Revision, ch.ReleasedAt, ch.Size, NotesFromChannelSnapInfo(ch)) trackHasOpenChannel = true } else { - if trackHasOpenChannel { - chInfo.version = esc.uparrow - } else { - chInfo.version = esc.dash - } + chInfos.addClosedChannel(chName, trackHasOpenChannel) } - chInfos = append(chInfos, &chInfo) } } - - for _, chInfo := range chInfos { - fmt.Fprintf(w, " "+chantpl, chInfo.name, chInfo.version, chInfo.released, maxRevLen, chInfo.revision, maxSizeLen, chInfo.size, chInfo.notes) - } - - return maxRevLen, maxSizeLen + chInfos.needsHeader = len(chInfos.channels) > 0 } -func formatSummary(raw string) string { - s, err := yaml.Marshal(raw) - if err != nil { - return fmt.Sprintf("cannot marshal summary: %s", err) +func (chInfos *channelInfos) dump(w io.Writer) { + if chInfos.needsHeader { + fmt.Fprintln(w, "channels:") + } + for _, c := range chInfos.channels { + fmt.Fprintf(w, chInfos.chantpl, c.indent, c.name, c.version, c.released, chInfos.maxRevLen, c.revision, chInfos.maxSizeLen, c.size, c.notes) } - return strings.TrimSpace(string(s)) } func (x *infoCmd) Execute([]string) error { @@ -407,7 +462,7 @@ func (x *infoCmd) Execute([]string) error { continue } - if tryDirect(w, snapName, x.Verbose) { + if tryDirect(w, snapName, x.Verbose, termWidth) { noneOK = false continue } @@ -427,7 +482,7 @@ func (x *infoCmd) Execute([]string) error { noneOK = false fmt.Fprintf(w, "name:\t%s\n", both.Name) - fmt.Fprintf(w, "summary:\t%s\n", formatSummary(both.Summary)) + printSummary(w, both.Summary, termWidth) fmt.Fprintf(w, "publisher:\t%s\n", longPublisher(esc, both.Publisher)) if both.Contact != "" { fmt.Fprintf(w, "contact:\t%s\n", strings.TrimPrefix(both.Contact, "mailto:")) @@ -449,7 +504,6 @@ func (x *infoCmd) Execute([]string) error { fmt.Fprintf(w, " confinement:\t%s\n", both.Confinement) } - var notes *Notes if local != nil { if x.Verbose { jailMode := local.Confinement == client.DevModeConfinement && !local.DevMode @@ -464,8 +518,6 @@ func (x *infoCmd) Execute([]string) error { } fmt.Fprintf(w, " ignore-validation:\t%t\n", local.IgnoreValidation) - } else { - notes = NotesFromLocal(local) } } // stops the notes etc trying to be aligned with channels @@ -473,8 +525,6 @@ func (x *infoCmd) Execute([]string) error { maybePrintType(w, both.Type) maybePrintBase(w, both.Base, x.Verbose) maybePrintID(w, both) - var localRev, localSize string - var revLen, sizeLen int if local != nil { if local.TrackingChannel != "" { fmt.Fprintf(w, "tracking:\t%s\n", local.TrackingChannel) @@ -482,24 +532,25 @@ func (x *infoCmd) Execute([]string) error { if !local.InstallDate.IsZero() { fmt.Fprintf(w, "refresh-date:\t%s\n", x.fmtTime(local.InstallDate)) } - localRev = fmt.Sprintf("(%s)", local.Revision) - revLen = len(localRev) - localSize = strutil.SizeToStr(local.InstalledSize) - sizeLen = len(localSize) } - chantpl := "%s:\t%s %s%*s %*s %s\n" + chInfos := channelInfos{ + chantpl: "%s%s:\t%s %s%*s %*s %s\n", + releasedfmt: "2006-01-02", + esc: esc, + } + if x.AbsTime { + chInfos.releasedfmt = time.RFC3339 + } if remote != nil && remote.Channels != nil && remote.Tracks != nil { - chantpl = "%s:\t%s\t%s\t%*s\t%*s\t%s\n" - w.Flush() - revLen, sizeLen = x.displayChannels(w, chantpl, esc, remote, revLen, sizeLen) + chInfos.chantpl = "%s%s:\t%s\t%s\t%*s\t%*s\t%s\n" + chInfos.addFromRemote(remote) } if local != nil { - fmt.Fprintf(w, chantpl, - "installed", local.Version, "", revLen, localRev, sizeLen, localSize, notes) + chInfos.addFromLocal(local) } - + chInfos.dump(w) } w.Flush() diff --git a/cmd/snap/cmd_info_test.go b/cmd/snap/cmd_info_test.go index 185fd7df92..4fac59bb7f 100644 --- a/cmd/snap/cmd_info_test.go +++ b/cmd/snap/cmd_info_test.go @@ -97,6 +97,45 @@ func (s *infoSuite) TestMaybePrintCommandsNoCommands(c *check.C) { } } +func (s *infoSuite) TestInfoPricedNarrowTerminal(c *check.C) { + defer snap.MockTermSize(func() (int, int) { return 44, 25 })() + + n := 0 + s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { + switch n { + case 0: + c.Check(r.Method, check.Equals, "GET") + c.Check(r.URL.Path, check.Equals, "/v2/find") + fmt.Fprintln(w, findPricedJSON) + case 1: + c.Check(r.Method, check.Equals, "GET") + c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello") + fmt.Fprintln(w, "{}") + default: + c.Fatalf("expected to get 1 requests, now on %d (%v)", n+1, r) + } + + n++ + }) + rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"}) + c.Assert(err, check.IsNil) + c.Assert(rest, check.DeepEquals, []string{}) + c.Check(s.Stdout(), check.Equals, ` +name: hello +summary: GNU Hello, the "hello world" + snap +publisher: Canonical* +license: Proprietary +price: 1.99GBP +description: | + GNU hello prints a friendly greeting. + This is part of the snapcraft tour at + https://snapcraft.io/ +snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 +`[1:]) + c.Check(s.Stderr(), check.Equals, "") +} + func (s *infoSuite) TestInfoPriced(c *check.C) { n := 0 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { @@ -550,3 +589,17 @@ func (infoSuite) TestDescr(c *check.C) { c.Check(buf.String(), check.Equals, v, check.Commentf("%q", k)) } } + +func (infoSuite) TestWrapCornerCase(c *check.C) { + // this particular corner case isn't currently reachable from + // printDescr nor printSummary, but best to have it covered + var buf bytes.Buffer + const s = "This is a paragraph indented with leading spaces that are encoded as multiple bytes. All hail EN SPACE." + snap.WrapFlow(&buf, []rune(s), "\u2002\u2002", 30) + c.Check(buf.String(), check.Equals, ` +  This is a paragraph indented + with leading spaces that are + encoded as multiple bytes. + All hail EN SPACE. +`[1:]) +} diff --git a/cmd/snap/cmd_run_test.go b/cmd/snap/cmd_run_test.go index beb7b2e236..b0779f4e39 100644 --- a/cmd/snap/cmd_run_test.go +++ b/cmd/snap/cmd_run_test.go @@ -50,7 +50,25 @@ hooks: configure: `) -func (s *SnapSuite) TestInvalidParameters(c *check.C) { +type RunSuite struct { + fakeHome string + SnapSuite +} + +var _ = check.Suite(&RunSuite{}) + +func (s *RunSuite) SetUpTest(c *check.C) { + s.SnapSuite.SetUpTest(c) + s.fakeHome = c.MkDir() + + u, err := user.Current() + c.Assert(err, check.IsNil) + s.AddCleanup(snaprun.MockUserCurrent(func() (*user.User, error) { + return &user.User{Uid: u.Uid, HomeDir: s.fakeHome}, nil + })) +} + +func (s *RunSuite) TestInvalidParameters(c *check.C) { invalidParameters := []string{"run", "--hook=configure", "--command=command-name", "--", "snap-name"} _, err := snaprun.Parser(snaprun.Client()).ParseArgs(invalidParameters) c.Check(err, check.ErrorMatches, ".*you can only use one of --hook, --command, and --timer.*") @@ -76,7 +94,7 @@ func (s *SnapSuite) TestInvalidParameters(c *check.C) { c.Check(err, check.ErrorMatches, ".*too many arguments for hook \"configure\": bar.*") } -func (s *SnapSuite) TestSnapRunWhenMissingConfine(c *check.C) { +func (s *RunSuite) TestSnapRunWhenMissingConfine(c *check.C) { _, r := logger.MockLogger() defer r() @@ -105,7 +123,7 @@ func (s *SnapSuite) TestSnapRunWhenMissingConfine(c *check.C) { c.Check(execs, check.IsNil) } -func (s *SnapSuite) TestSnapRunAppIntegration(c *check.C) { +func (s *RunSuite) TestSnapRunAppIntegration(c *check.C) { defer mockSnapConfine(dirs.DistroLibExecDir)() // mock installed snap @@ -138,7 +156,7 @@ func (s *SnapSuite) TestSnapRunAppIntegration(c *check.C) { c.Check(execEnv, testutil.Contains, "SNAP_REVISION=x2") } -func (s *SnapSuite) TestSnapRunClassicAppIntegration(c *check.C) { +func (s *RunSuite) TestSnapRunClassicAppIntegration(c *check.C) { defer mockSnapConfine(dirs.DistroLibExecDir)() // mock installed snap @@ -172,7 +190,7 @@ func (s *SnapSuite) TestSnapRunClassicAppIntegration(c *check.C) { } -func (s *SnapSuite) TestSnapRunClassicAppIntegrationReexeced(c *check.C) { +func (s *RunSuite) TestSnapRunClassicAppIntegrationReexeced(c *check.C) { mountedCorePath := filepath.Join(dirs.SnapMountDir, "core/current") mountedCoreLibExecPath := filepath.Join(mountedCorePath, dirs.CoreLibExecDir) @@ -205,7 +223,7 @@ func (s *SnapSuite) TestSnapRunClassicAppIntegrationReexeced(c *check.C) { "snapname.app", "--arg1", "arg2"}) } -func (s *SnapSuite) TestSnapRunAppWithCommandIntegration(c *check.C) { +func (s *RunSuite) TestSnapRunAppWithCommandIntegration(c *check.C) { defer mockSnapConfine(dirs.DistroLibExecDir)() // mock installed snap @@ -237,48 +255,36 @@ func (s *SnapSuite) TestSnapRunAppWithCommandIntegration(c *check.C) { c.Check(execEnv, testutil.Contains, "SNAP_REVISION=42") } -func (s *SnapSuite) TestSnapRunCreateDataDirs(c *check.C) { +func (s *RunSuite) TestSnapRunCreateDataDirs(c *check.C) { info, err := snap.InfoFromSnapYaml(mockYaml) c.Assert(err, check.IsNil) info.SideInfo.Revision = snap.R(42) - fakeHome := c.MkDir() - restorer := snaprun.MockUserCurrent(func() (*user.User, error) { - return &user.User{HomeDir: fakeHome}, nil - }) - defer restorer() - err = snaprun.CreateUserDataDirs(info) c.Assert(err, check.IsNil) - c.Check(osutil.FileExists(filepath.Join(fakeHome, "/snap/snapname/42")), check.Equals, true) - c.Check(osutil.FileExists(filepath.Join(fakeHome, "/snap/snapname/common")), check.Equals, true) + c.Check(osutil.FileExists(filepath.Join(s.fakeHome, "/snap/snapname/42")), check.Equals, true) + c.Check(osutil.FileExists(filepath.Join(s.fakeHome, "/snap/snapname/common")), check.Equals, true) } -func (s *SnapSuite) TestParallelInstanceSnapRunCreateDataDirs(c *check.C) { +func (s *RunSuite) TestParallelInstanceSnapRunCreateDataDirs(c *check.C) { info, err := snap.InfoFromSnapYaml(mockYaml) c.Assert(err, check.IsNil) info.SideInfo.Revision = snap.R(42) info.InstanceKey = "foo" - fakeHome := c.MkDir() - restorer := snaprun.MockUserCurrent(func() (*user.User, error) { - return &user.User{HomeDir: fakeHome}, nil - }) - defer restorer() - err = snaprun.CreateUserDataDirs(info) c.Assert(err, check.IsNil) - c.Check(osutil.FileExists(filepath.Join(fakeHome, "/snap/snapname_foo/42")), check.Equals, true) - c.Check(osutil.FileExists(filepath.Join(fakeHome, "/snap/snapname_foo/common")), check.Equals, true) + c.Check(osutil.FileExists(filepath.Join(s.fakeHome, "/snap/snapname_foo/42")), check.Equals, true) + c.Check(osutil.FileExists(filepath.Join(s.fakeHome, "/snap/snapname_foo/common")), check.Equals, true) // mount point for snap instance mapping has been created - c.Check(osutil.FileExists(filepath.Join(fakeHome, "/snap/snapname")), check.Equals, true) + c.Check(osutil.FileExists(filepath.Join(s.fakeHome, "/snap/snapname")), check.Equals, true) // and it's empty inside - m, err := filepath.Glob(filepath.Join(fakeHome, "/snap/snapname/*")) + m, err := filepath.Glob(filepath.Join(s.fakeHome, "/snap/snapname/*")) c.Assert(err, check.IsNil) c.Assert(m, check.HasLen, 0) } -func (s *SnapSuite) TestSnapRunHookIntegration(c *check.C) { +func (s *RunSuite) TestSnapRunHookIntegration(c *check.C) { defer mockSnapConfine(dirs.DistroLibExecDir)() // mock installed snap @@ -310,7 +316,7 @@ func (s *SnapSuite) TestSnapRunHookIntegration(c *check.C) { c.Check(execEnv, testutil.Contains, "SNAP_REVISION=42") } -func (s *SnapSuite) TestSnapRunHookUnsetRevisionIntegration(c *check.C) { +func (s *RunSuite) TestSnapRunHookUnsetRevisionIntegration(c *check.C) { defer mockSnapConfine(dirs.DistroLibExecDir)() // mock installed snap @@ -342,7 +348,7 @@ func (s *SnapSuite) TestSnapRunHookUnsetRevisionIntegration(c *check.C) { c.Check(execEnv, testutil.Contains, "SNAP_REVISION=42") } -func (s *SnapSuite) TestSnapRunHookSpecificRevisionIntegration(c *check.C) { +func (s *RunSuite) TestSnapRunHookSpecificRevisionIntegration(c *check.C) { defer mockSnapConfine(dirs.DistroLibExecDir)() // mock installed snap @@ -378,7 +384,7 @@ func (s *SnapSuite) TestSnapRunHookSpecificRevisionIntegration(c *check.C) { c.Check(execEnv, testutil.Contains, "SNAP_REVISION=41") } -func (s *SnapSuite) TestSnapRunHookMissingRevisionIntegration(c *check.C) { +func (s *RunSuite) TestSnapRunHookMissingRevisionIntegration(c *check.C) { // Only create revision 42 snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ Revision: snap.R(42), @@ -396,13 +402,13 @@ func (s *SnapSuite) TestSnapRunHookMissingRevisionIntegration(c *check.C) { c.Check(err, check.ErrorMatches, "cannot find .*") } -func (s *SnapSuite) TestSnapRunHookInvalidRevisionIntegration(c *check.C) { +func (s *RunSuite) TestSnapRunHookInvalidRevisionIntegration(c *check.C) { _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--hook=configure", "-r=invalid", "--", "snapname"}) c.Assert(err, check.NotNil) c.Check(err, check.ErrorMatches, "invalid snap revision: \"invalid\"") } -func (s *SnapSuite) TestSnapRunHookMissingHookIntegration(c *check.C) { +func (s *RunSuite) TestSnapRunHookMissingHookIntegration(c *check.C) { // Only create revision 42 snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ Revision: snap.R(42), @@ -421,22 +427,22 @@ func (s *SnapSuite) TestSnapRunHookMissingHookIntegration(c *check.C) { c.Check(called, check.Equals, false) } -func (s *SnapSuite) TestSnapRunErorsForUnknownRunArg(c *check.C) { +func (s *RunSuite) TestSnapRunErorsForUnknownRunArg(c *check.C) { _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--unknown", "--", "snapname.app", "--arg1", "arg2"}) c.Assert(err, check.ErrorMatches, "unknown flag `unknown'") } -func (s *SnapSuite) TestSnapRunErorsForMissingApp(c *check.C) { +func (s *RunSuite) TestSnapRunErorsForMissingApp(c *check.C) { _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--command=shell"}) c.Assert(err, check.ErrorMatches, "need the application to run as argument") } -func (s *SnapSuite) TestSnapRunErorrForUnavailableApp(c *check.C) { +func (s *RunSuite) TestSnapRunErorrForUnavailableApp(c *check.C) { _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "not-there"}) c.Assert(err, check.ErrorMatches, fmt.Sprintf("cannot find current revision for snap not-there: readlink %s/not-there/current: no such file or directory", dirs.SnapMountDir)) } -func (s *SnapSuite) TestSnapRunSaneEnvironmentHandling(c *check.C) { +func (s *RunSuite) TestSnapRunSaneEnvironmentHandling(c *check.C) { defer mockSnapConfine(dirs.DistroLibExecDir)() // mock installed snap @@ -471,7 +477,7 @@ func (s *SnapSuite) TestSnapRunSaneEnvironmentHandling(c *check.C) { c.Check(execEnv, testutil.Contains, "SNAP_THE_WORLD=YES") } -func (s *SnapSuite) TestSnapRunIsReexeced(c *check.C) { +func (s *RunSuite) TestSnapRunIsReexeced(c *check.C) { var osReadlinkResult string restore := snaprun.MockOsReadlink(func(name string) (string, error) { return osReadlinkResult, nil @@ -490,7 +496,7 @@ func (s *SnapSuite) TestSnapRunIsReexeced(c *check.C) { } } -func (s *SnapSuite) TestSnapRunAppIntegrationFromCore(c *check.C) { +func (s *RunSuite) TestSnapRunAppIntegrationFromCore(c *check.C) { defer mockSnapConfine(filepath.Join(dirs.SnapMountDir, "core", "111", dirs.CoreLibExecDir))() // mock installed snap @@ -529,7 +535,7 @@ func (s *SnapSuite) TestSnapRunAppIntegrationFromCore(c *check.C) { c.Check(execEnv, testutil.Contains, "SNAP_REVISION=x2") } -func (s *SnapSuite) TestSnapRunAppIntegrationFromSnapd(c *check.C) { +func (s *RunSuite) TestSnapRunAppIntegrationFromSnapd(c *check.C) { defer mockSnapConfine(filepath.Join(dirs.SnapMountDir, "snapd", "222", dirs.CoreLibExecDir))() // mock installed snap @@ -568,7 +574,7 @@ func (s *SnapSuite) TestSnapRunAppIntegrationFromSnapd(c *check.C) { c.Check(execEnv, testutil.Contains, "SNAP_REVISION=x2") } -func (s *SnapSuite) TestSnapRunXauthorityMigration(c *check.C) { +func (s *RunSuite) TestSnapRunXauthorityMigration(c *check.C) { defer mockSnapConfine(dirs.DistroLibExecDir)() u, err := user.Current() @@ -644,7 +650,7 @@ func mkCompArgs(compPoint string, argv ...string) []string { return out } -func (s *SnapSuite) TestAntialiasHappy(c *check.C) { +func (s *RunSuite) TestAntialiasHappy(c *check.C) { c.Assert(os.MkdirAll(dirs.SnapBinariesDir, 0755), check.IsNil) inArgs := mkCompArgs("10", "alias", "alias", "bo-alias") @@ -672,7 +678,7 @@ func (s *SnapSuite) TestAntialiasHappy(c *check.C) { }) } -func (s *SnapSuite) TestAntialiasBailsIfUnhappy(c *check.C) { +func (s *RunSuite) TestAntialiasBailsIfUnhappy(c *check.C) { // alias exists but args are somehow wonky c.Assert(os.MkdirAll(dirs.SnapBinariesDir, 0755), check.IsNil) c.Assert(os.Symlink("an-app", filepath.Join(dirs.SnapBinariesDir, "alias")), check.IsNil) @@ -701,7 +707,7 @@ func (s *SnapSuite) TestAntialiasBailsIfUnhappy(c *check.C) { } } -func (s *SnapSuite) TestSnapRunAppWithStraceIntegration(c *check.C) { +func (s *RunSuite) TestSnapRunAppWithStraceIntegration(c *check.C) { defer mockSnapConfine(dirs.DistroLibExecDir)() // mock installed snap @@ -781,7 +787,7 @@ and more c.Check(s.Stderr(), check.Equals, fmt.Sprintf(expectedFullFmt, dirs.SnapMountDir)) } -func (s *SnapSuite) TestSnapRunAppWithStraceOptions(c *check.C) { +func (s *RunSuite) TestSnapRunAppWithStraceOptions(c *check.C) { defer mockSnapConfine(dirs.DistroLibExecDir)() // mock installed snap @@ -822,7 +828,7 @@ func (s *SnapSuite) TestSnapRunAppWithStraceOptions(c *check.C) { }) } -func (s *SnapSuite) TestSnapRunShellIntegration(c *check.C) { +func (s *RunSuite) TestSnapRunShellIntegration(c *check.C) { defer mockSnapConfine(dirs.DistroLibExecDir)() // mock installed snap @@ -855,7 +861,7 @@ func (s *SnapSuite) TestSnapRunShellIntegration(c *check.C) { c.Check(execEnv, testutil.Contains, "SNAP_REVISION=x2") } -func (s *SnapSuite) TestSnapRunAppTimer(c *check.C) { +func (s *RunSuite) TestSnapRunAppTimer(c *check.C) { defer mockSnapConfine(dirs.DistroLibExecDir)() // mock installed snap @@ -910,7 +916,7 @@ func (s *SnapSuite) TestSnapRunAppTimer(c *check.C) { "snapname.app", "--arg1", "arg2"}) } -func (s *SnapSuite) TestRunCmdWithTraceExecUnhappy(c *check.C) { +func (s *RunSuite) TestRunCmdWithTraceExecUnhappy(c *check.C) { defer mockSnapConfine(dirs.DistroLibExecDir)() // mock installed snap @@ -933,7 +939,7 @@ func (s *SnapSuite) TestRunCmdWithTraceExecUnhappy(c *check.C) { c.Check(s.Stderr(), check.Equals, "") } -func (s *SnapSuite) TestSnapRunRestoreSecurityContextHappy(c *check.C) { +func (s *RunSuite) TestSnapRunRestoreSecurityContextHappy(c *check.C) { logbuf, restorer := logger.MockLogger() defer restorer() @@ -944,12 +950,6 @@ func (s *SnapSuite) TestSnapRunRestoreSecurityContextHappy(c *check.C) { Revision: snap.R("x2"), }) - fakeHome := c.MkDir() - restorer = snaprun.MockUserCurrent(func() (*user.User, error) { - return &user.User{HomeDir: fakeHome}, nil - }) - defer restorer() - // redirect exec execCalled := 0 restorer = snaprun.MockSyscallExec(func(_ string, args []string, envv []string) error { @@ -964,7 +964,7 @@ func (s *SnapSuite) TestSnapRunRestoreSecurityContextHappy(c *check.C) { enabled := false verify := true - snapUserDir := filepath.Join(fakeHome, dirs.UserHomeSnapDir) + snapUserDir := filepath.Join(s.fakeHome, dirs.UserHomeSnapDir) restorer = snaprun.MockSELinuxVerifyPathContext(func(what string) (bool, error) { c.Check(what, check.Equals, snapUserDir) @@ -1020,7 +1020,7 @@ func (s *SnapSuite) TestSnapRunRestoreSecurityContextHappy(c *check.C) { c.Check(logbuf.String(), testutil.Contains, fmt.Sprintf("restoring default SELinux context of %s", snapUserDir)) } -func (s *SnapSuite) TestSnapRunRestoreSecurityContextFail(c *check.C) { +func (s *RunSuite) TestSnapRunRestoreSecurityContextFail(c *check.C) { logbuf, restorer := logger.MockLogger() defer restorer() @@ -1031,12 +1031,6 @@ func (s *SnapSuite) TestSnapRunRestoreSecurityContextFail(c *check.C) { Revision: snap.R("x2"), }) - fakeHome := c.MkDir() - restorer = snaprun.MockUserCurrent(func() (*user.User, error) { - return &user.User{HomeDir: fakeHome}, nil - }) - defer restorer() - // redirect exec execCalled := 0 restorer = snaprun.MockSyscallExec(func(_ string, args []string, envv []string) error { @@ -1052,7 +1046,7 @@ func (s *SnapSuite) TestSnapRunRestoreSecurityContextFail(c *check.C) { verifyErr := errors.New("verify failed") restoreErr := errors.New("restore failed") - snapUserDir := filepath.Join(fakeHome, dirs.UserHomeSnapDir) + snapUserDir := filepath.Join(s.fakeHome, dirs.UserHomeSnapDir) restorer = snaprun.MockSELinuxVerifyPathContext(func(what string) (bool, error) { c.Check(what, check.Equals, snapUserDir) diff --git a/cmd/snap/error.go b/cmd/snap/error.go index 841e331cc1..5174ad6a01 100644 --- a/cmd/snap/error.go +++ b/cmd/snap/error.go @@ -41,7 +41,9 @@ import ( var errorPrefix = i18n.G("error: %v\n") -func termSize() (width, height int) { +var termSize = termSizeImpl + +func termSizeImpl() (width, height int) { if f, ok := Stdout.(*os.File); ok { width, height, _ = terminal.GetSize(int(f.Fd())) } diff --git a/cmd/snap/export_test.go b/cmd/snap/export_test.go index 0dc4b1cef7..92b0afbac8 100644 --- a/cmd/snap/export_test.go +++ b/cmd/snap/export_test.go @@ -48,6 +48,7 @@ var ( Antialias = antialias FormatChannel = fmtChannel PrintDescr = printDescr + WrapFlow = wrapFlow TrueishJSON = trueishJSON CanUnicode = canUnicode @@ -241,3 +242,11 @@ func MockSELinuxRestoreContext(restorecon func(string, selinux.RestoreMode) erro selinuxRestoreContext = old } } + +func MockTermSize(newTermSize func() (int, int)) (restore func()) { + old := termSize + termSize = newTermSize + return func() { + termSize = old + } +} diff --git a/daemon/api.go b/daemon/api.go index b871f0ee33..86563ac2a4 100644 --- a/daemon/api.go +++ b/daemon/api.go @@ -20,6 +20,7 @@ package daemon import ( + "context" "encoding/json" "errors" "fmt" @@ -41,7 +42,6 @@ import ( "github.com/gorilla/mux" "github.com/jessevdk/go-flags" - "golang.org/x/net/context" "github.com/snapcore/snapd/asserts" "github.com/snapcore/snapd/asserts/snapasserts" @@ -1618,21 +1618,29 @@ func unsafeReadSnapInfoImpl(snapPath string) (*snap.Info, error) { var unsafeReadSnapInfo = unsafeReadSnapInfoImpl func iconGet(st *state.State, name string) Response { - about, err := localSnapInfo(st, name) + st.Lock() + defer st.Unlock() + + var snapst snapstate.SnapState + err := snapstate.Get(st, name, &snapst) if err != nil { - if err == errNoSnap { + if err == state.ErrNoState { return SnapNotFound(name, err) } - return InternalError("%v", err) + return InternalError("cannot consult state: %v", err) + } + sideInfo := snapst.CurrentSideInfo() + if sideInfo == nil { + return NotFound("snap has no current revision") } - path := filepath.Clean(snapIcon(about.info)) - if !strings.HasPrefix(path, dirs.SnapMountDir) { - // XXX: how could this happen? - return BadRequest("requested icon is not in snap path") + icon := snapIcon(snap.MinimalPlaceInfo(name, sideInfo.Revision)) + + if icon == "" { + return NotFound("local snap has no icon") } - return FileResponse(path) + return FileResponse(icon) } func appIconGet(c *Command, r *http.Request, user *auth.UserState) Response { diff --git a/daemon/api_snapshots.go b/daemon/api_snapshots.go index f52eed9305..bdc23aaf3c 100644 --- a/daemon/api_snapshots.go +++ b/daemon/api_snapshots.go @@ -20,14 +20,13 @@ package daemon import ( + "context" "encoding/json" "fmt" "net/http" "strconv" "strings" - "golang.org/x/net/context" - "github.com/snapcore/snapd/client" "github.com/snapcore/snapd/overlord/auth" "github.com/snapcore/snapd/overlord/state" diff --git a/daemon/api_snapshots_test.go b/daemon/api_snapshots_test.go index e869437725..1a7fe8e5b1 100644 --- a/daemon/api_snapshots_test.go +++ b/daemon/api_snapshots_test.go @@ -20,12 +20,12 @@ package daemon_test import ( + "context" "errors" "fmt" "net/http" "strings" - "golang.org/x/net/context" "gopkg.in/check.v1" "github.com/snapcore/snapd/client" diff --git a/daemon/api_test.go b/daemon/api_test.go index dac4607526..f8f1e1c3a6 100644 --- a/daemon/api_test.go +++ b/daemon/api_test.go @@ -21,6 +21,7 @@ package daemon import ( "bytes" + "context" "crypto" "encoding/json" "errors" @@ -42,7 +43,6 @@ import ( "time" "golang.org/x/crypto/sha3" - "golang.org/x/net/context" "gopkg.in/check.v1" "gopkg.in/tomb.v2" @@ -3236,7 +3236,7 @@ func (s *apiSuite) TestAppIconGetNoApp(c *check.C) { func (s *apiSuite) TestNotInstalledSnapIcon(c *check.C) { info := &snap.Info{SuggestedName: "notInstalledSnap", Media: []snap.MediaInfo{{Type: "icon", URL: "icon.svg"}}} iconfile := snapIcon(info) - c.Check(iconfile, testutil.Contains, "icon.svg") + c.Check(iconfile, check.Equals, "") } func (s *apiSuite) TestInstallOnNonDevModeDistro(c *check.C) { diff --git a/daemon/export_snapshots_test.go b/daemon/export_snapshots_test.go index 5468518a95..dec50774d3 100644 --- a/daemon/export_snapshots_test.go +++ b/daemon/export_snapshots_test.go @@ -20,10 +20,10 @@ package daemon import ( + "context" "encoding/json" "net/http" - "golang.org/x/net/context" "gopkg.in/check.v1" "github.com/snapcore/snapd/client" diff --git a/daemon/snap.go b/daemon/snap.go index e9c1c86858..f7017761a7 100644 --- a/daemon/snap.go +++ b/daemon/snap.go @@ -39,11 +39,10 @@ import ( var errNoSnap = errors.New("snap not installed") // snapIcon tries to find the icon inside the snap -func snapIcon(info *snap.Info) string { - // XXX: copy of snap.Snap.Icon which will go away +func snapIcon(info snap.PlaceInfo) string { found, _ := filepath.Glob(filepath.Join(info.MountDir(), "meta", "gui", "icon.*")) if len(found) == 0 { - return info.Media.IconURL() + return "" } return found[0] @@ -358,7 +357,7 @@ func mapRemote(remoteSnap *snap.Info) *client.Snap { Developer: remoteSnap.Publisher.Username, Publisher: &publisher, DownloadSize: remoteSnap.Size, - Icon: snapIcon(remoteSnap), + Icon: remoteSnap.Media.IconURL(), ID: remoteSnap.SnapID, Name: remoteSnap.InstanceName(), Revision: remoteSnap.Revision, diff --git a/image/helpers.go b/image/helpers.go index 921db3c58f..35f586ab21 100644 --- a/image/helpers.go +++ b/image/helpers.go @@ -23,6 +23,7 @@ package image import ( "bytes" + "context" "crypto" "encoding/json" "fmt" @@ -34,7 +35,6 @@ import ( "syscall" "github.com/mvo5/goconfigparser" - "golang.org/x/net/context" "github.com/snapcore/snapd/asserts" "github.com/snapcore/snapd/asserts/snapasserts" diff --git a/image/image_test.go b/image/image_test.go index 002aafc12a..627a341111 100644 --- a/image/image_test.go +++ b/image/image_test.go @@ -21,6 +21,7 @@ package image_test import ( "bytes" + "context" "fmt" "io/ioutil" "net/url" @@ -30,7 +31,6 @@ import ( "testing" "time" - "golang.org/x/net/context" . "gopkg.in/check.v1" "github.com/snapcore/snapd/asserts" diff --git a/interfaces/apparmor/backend_test.go b/interfaces/apparmor/backend_test.go index 7cd9555e70..f1eac265c7 100644 --- a/interfaces/apparmor/backend_test.go +++ b/interfaces/apparmor/backend_test.go @@ -939,6 +939,10 @@ func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyNoNFS(c *C) { restore := apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, nil }) defer restore() + // Make it appear as if overlay was not used. + restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) + defer restore() + // Intercept interaction with apparmor_parser cmd := testutil.MockCommand(c, "apparmor_parser", "") defer cmd.Restore() @@ -974,6 +978,10 @@ func (s *backendSuite) testSetupSnapConfineGeneratedPolicyWithNFS(c *C, profileF restore := apparmor.MockIsHomeUsingNFS(func() (bool, error) { return true, nil }) defer restore() + // Make it appear as if overlay was not used. + restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) + defer restore() + // Intercept interaction with apparmor_parser cmd := testutil.MockCommand(c, "apparmor_parser", "") defer cmd.Restore() @@ -1031,6 +1039,10 @@ func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyWithNFSAndReExec(c *C) restore := apparmor.MockIsHomeUsingNFS(func() (bool, error) { return true, nil }) defer restore() + // Make it appear as if overlay was not used. + restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) + defer restore() + // Intercept interaction with apparmor_parser cmd := testutil.MockCommand(c, "apparmor_parser", "") defer cmd.Restore() @@ -1072,6 +1084,10 @@ func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyError1(c *C) { restore := apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, fmt.Errorf("broken") }) defer restore() + // Make it appear as if overlay was not used. + restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) + defer restore() + // Intercept interaction with apparmor_parser cmd := testutil.MockCommand(c, "apparmor_parser", "") defer cmd.Restore() @@ -1108,6 +1124,10 @@ func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyError2(c *C) { restore := apparmor.MockIsHomeUsingNFS(func() (bool, error) { return true, nil }) defer restore() + // Make it appear as if overlay was not used. + restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) + defer restore() + // Intercept interaction with apparmor_parser cmd := testutil.MockCommand(c, "apparmor_parser", "") defer cmd.Restore() @@ -1137,6 +1157,10 @@ func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyError3(c *C) { restore := apparmor.MockIsHomeUsingNFS(func() (bool, error) { return true, nil }) defer restore() + // Make it appear as if overlay was not used. + restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) + defer restore() + // Intercept interaction with apparmor_parser and make it fail. cmd := testutil.MockCommand(c, "apparmor_parser", "echo testing; exit 1") defer cmd.Restore() @@ -1193,6 +1217,10 @@ func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyError5(c *C) { restore := apparmor.MockIsHomeUsingNFS(func() (bool, error) { return false, nil }) defer restore() + // Make it appear as if overlay was not used. + restore = apparmor.MockIsRootWritableOverlay(func() (string, error) { return "", nil }) + defer restore() + // Intercept interaction with apparmor_parser and make it fail. cmd := testutil.MockCommand(c, "apparmor_parser", "") defer cmd.Restore() diff --git a/interfaces/apparmor/template.go b/interfaces/apparmor/template.go index ea917e8730..849c9cf965 100644 --- a/interfaces/apparmor/template.go +++ b/interfaces/apparmor/template.go @@ -375,6 +375,9 @@ var defaultTemplate = ` @{PROC}/net/dev r, @{PROC}/@{pid}/net/dev r, + # Read-only of this snap + /var/lib/snapd/snaps/@{SNAP_NAME}_*.snap r, + # Read-only for the install directory # bind mount used here (see 'parallel installs', above) @{INSTALL_DIR}/{@{SNAP_NAME},@{SNAP_INSTANCE_NAME}}/ r, @@ -635,6 +638,9 @@ profile snap-update-ns.###SNAP_INSTANCE_NAME### (attach_disconnected) { # Allow reading /proc/version. For release.go WSL detection. @{PROC}/version r, + # Allow reading somaxconn, required in newer distro releases + @{PROC}/sys/net/core/somaxconn r, + # Allow reading the os-release file (possibly a symlink to /usr/lib). /{etc/,usr/lib/}os-release r, diff --git a/interfaces/builtin/block_devices.go b/interfaces/builtin/block_devices.go new file mode 100644 index 0000000000..47711b33a4 --- /dev/null +++ b/interfaces/builtin/block_devices.go @@ -0,0 +1,98 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2019 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +package builtin + +// Only allow raw disk devices; not loop, ram, CDROM, generic SCSI, network, +// tape, raid, etc devices or disk partitions +const blockDevicesSummary = `allows access to disk block devices` + +const blockDevicesBaseDeclarationPlugs = ` + block-devices: + allow-installation: false + deny-auto-connection: true +` + +const blockDevicesBaseDeclarationSlots = ` + block-devices: + allow-installation: + slot-snap-type: + - core + deny-auto-connection: true +` + +// https://www.kernel.org/doc/Documentation/admin-guide/devices.txt +// For now, only list common devices and skip the following: +// /dev/mfm{a,b} rw, # Acorn MFM +// /dev/ad[a-p] rw, # ACSI +// /dev/pd[a-d] rw, # Parallel port IDE +// /dev/pf[0-3] rw, # Parallel port ATAPI +// /dev/ub[a-z] rw, # USB block device +const blockDevicesConnectedPlugAppArmor = ` +# Description: Allow write access to raw disk block devices. + +@{PROC}/devices r, +/run/udev/data/b[0-9]*:[0-9]* r, +/sys/block/ r, +/sys/devices/**/block/** r, + +# Access to raw devices, not individual partitions +/dev/hd[a-t] rw, # IDE, MFM, RLL +/dev/sd{,[a-h]}[a-z] rw, # SCSI +/dev/sdi[a-v] rw, # SCSI continued +/dev/i2o/hd{,[a-c]}[a-z] rw, # I2O hard disk +/dev/i2o/hdd[a-x] rw, # I2O hard disk continued +/dev/mmcblk[0-9]{,[0-9],[0-9][0-9]} rw, # MMC (up to 1000 devices) +/dev/nvme[0-9]{,[0-9]} rw, # NVMe (up to 100 devices) +/dev/vd[a-z] rw, # virtio + +# SCSI device commands, et al +capability sys_rawio, + +# Perform various privileged block-device ioctl operations +capability sys_admin, + +# Devices for various controllers used with ioctl() +/dev/mpt2ctl{,_wd} rw, +/dev/megaraid_sas_ioctl_node rw, +` + +var blockDevicesConnectedPlugUDev = []string{ + `SUBSYSTEM=="block"`, + `KERNEL=="mpt2ctl*"`, + `KERNEL=="megaraid_sas_ioctl_node"`, +} + +type blockDevicesInterface struct { + commonInterface +} + +func init() { + registerIface(&blockDevicesInterface{commonInterface{ + name: "block-devices", + summary: blockDevicesSummary, + implicitOnCore: true, + implicitOnClassic: true, + baseDeclarationPlugs: blockDevicesBaseDeclarationPlugs, + baseDeclarationSlots: blockDevicesBaseDeclarationSlots, + connectedPlugAppArmor: blockDevicesConnectedPlugAppArmor, + connectedPlugUDev: blockDevicesConnectedPlugUDev, + reservedForOS: true, + }}) +} diff --git a/interfaces/builtin/block_devices_test.go b/interfaces/builtin/block_devices_test.go new file mode 100644 index 0000000000..e8a9bdf74a --- /dev/null +++ b/interfaces/builtin/block_devices_test.go @@ -0,0 +1,118 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2019 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +package builtin_test + +import ( + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/apparmor" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/interfaces/udev" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/testutil" +) + +type blockDevicesInterfaceSuite struct { + testutil.BaseTest + + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug +} + +var _ = Suite(&blockDevicesInterfaceSuite{ + iface: builtin.MustInterface("block-devices"), +}) + +const blockDevicesConsumerYaml = `name: consumer +version: 0 +apps: + app: + plugs: [block-devices] +` + +const blockDevicesCoreYaml = `name: core +version: 0 +type: os +slots: + block-devices: +` + +func (s *blockDevicesInterfaceSuite) SetUpTest(c *C) { + s.BaseTest.SetUpTest(c) + + s.plug, s.plugInfo = MockConnectedPlug(c, blockDevicesConsumerYaml, nil, "block-devices") + s.slot, s.slotInfo = MockConnectedSlot(c, blockDevicesCoreYaml, nil, "block-devices") +} + +func (s *blockDevicesInterfaceSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "block-devices") +} + +func (s *blockDevicesInterfaceSuite) TestSanitizeSlot(c *C) { + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "some-snap"}, + Name: "block-devices", + Interface: "block-devices", + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, + "block-devices slots are reserved for the core snap") +} + +func (s *blockDevicesInterfaceSuite) TestSanitizePlug(c *C) { + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) +} + +func (s *blockDevicesInterfaceSuite) TestAppArmorSpec(c *C) { + spec := &apparmor.Specification{} + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `# Description: Allow write access to raw disk block devices.`) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `/dev/sd{,[a-h]}[a-z] rw,`) +} + +func (s *blockDevicesInterfaceSuite) TestUDevSpec(c *C) { + spec := &udev.Specification{} + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Snippets(), HasLen, 4) + c.Assert(spec.Snippets()[0], Equals, `# block-devices +KERNEL=="megaraid_sas_ioctl_node", TAG+="snap_consumer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_consumer_app", RUN+="/usr/lib/snapd/snap-device-helper $env{ACTION} snap_consumer_app $devpath $major:$minor"`) +} + +func (s *blockDevicesInterfaceSuite) TestStaticInfo(c *C) { + si := interfaces.StaticInfoOf(s.iface) + c.Assert(si.ImplicitOnCore, Equals, true) + c.Assert(si.ImplicitOnClassic, Equals, true) + c.Assert(si.Summary, Equals, `allows access to disk block devices`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "block-devices") +} + +func (s *blockDevicesInterfaceSuite) TestAutoConnect(c *C) { + c.Assert(s.iface.AutoConnect(s.plugInfo, s.slotInfo), Equals, true) +} + +func (s *blockDevicesInterfaceSuite) TestInterfaces(c *C) { + c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) +} diff --git a/interfaces/builtin/camera.go b/interfaces/builtin/camera.go index 639bb96a30..5c44a62b30 100644 --- a/interfaces/builtin/camera.go +++ b/interfaces/builtin/camera.go @@ -43,6 +43,7 @@ const cameraConnectedPlugAppArmor = ` /sys/devices/pci**/usb*/**/modalias r, /sys/devices/pci**/usb*/**/speed r, /run/udev/data/c81:[0-9]* r, # video4linux (/dev/video*, etc) +/run/udev/data/+usb:* r, /sys/class/video4linux/ r, /sys/devices/pci**/usb*/**/video4linux/** r, ` diff --git a/interfaces/builtin/common.go b/interfaces/builtin/common.go index aa5fe280c6..7809cff320 100644 --- a/interfaces/builtin/common.go +++ b/interfaces/builtin/common.go @@ -20,6 +20,7 @@ package builtin import ( + "io/ioutil" "path/filepath" "github.com/snapcore/snapd/interfaces" @@ -34,6 +35,10 @@ import ( // applicable for testing. var evalSymlinks = filepath.EvalSymlinks +// readDir is either ioutil.ReadDir or a mocked function for applicable for +// testing. +var readDir = ioutil.ReadDir + type commonInterface struct { name string summary string diff --git a/interfaces/builtin/common_test.go b/interfaces/builtin/common_test.go index 0d37dcbc3b..acdbfe6ab6 100644 --- a/interfaces/builtin/common_test.go +++ b/interfaces/builtin/common_test.go @@ -21,6 +21,7 @@ package builtin import ( . "gopkg.in/check.v1" + "os" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/udev" @@ -84,6 +85,15 @@ func MockEvalSymlinks(test *testutil.BaseTest, fn func(string) (string, error)) }) } +// MockReadDir replaces the io/ioutil.ReadDir function used inside the caps package. +func MockReadDir(test *testutil.BaseTest, fn func(string) ([]os.FileInfo, error)) { + orig := readDir + readDir = fn + test.AddCleanup(func() { + readDir = orig + }) +} + func (s *commonIfaceSuite) TestSuppressPtraceTrace(c *C) { plug, _ := MockConnectedPlug(c, ` name: consumer diff --git a/interfaces/builtin/dbus.go b/interfaces/builtin/dbus.go index 3413840547..c3f25f4eb4 100644 --- a/interfaces/builtin/dbus.go +++ b/interfaces/builtin/dbus.go @@ -78,12 +78,16 @@ dbus (bind) bus=###DBUS_BUS### name=###DBUS_NAME###, -# For KDE applications, also support alternation since they use org.kde.foo-PID -# as their 'well-known' name. snapd does not allow declaring a 'well-known' -# name that ends with '-[0-9]+', so this is ok. +# For KDE applications and some other cases, also support alternation for: +# - using org.kde.foo-PID as the 'well-known' name +# - using org.foo.cmd_<num>_<num> as the 'well-known' name +# Note, snapd does not allow declaring a 'well-known' name that ends with +# '-[0-9]+' or that contains '_'. Parallel installs of DBus services aren't +# supported at this time, but if they were, this could allow a parallel +# install'swell-known name to overlap with the normal install. dbus (bind) bus=###DBUS_BUS### - name=###DBUS_NAME###-[1-9]{,[0-9]}{,[0-9]}{,[0-9]}{,[0-9]}{,[0-9]}, + name=###DBUS_NAME###{_,-}[1-9]{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9]}{,_[1-9]{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9]}}, # Allow us to talk to dbus-daemon dbus (receive) diff --git a/interfaces/builtin/display_control.go b/interfaces/builtin/display_control.go new file mode 100644 index 0000000000..0ab36eb42b --- /dev/null +++ b/interfaces/builtin/display_control.go @@ -0,0 +1,137 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2019 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +package builtin + +import ( + "bytes" + "fmt" + "path" + "path/filepath" + + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/apparmor" +) + +const displayControlSummary = `allows configuring display parameters` + +const displayControlBaseDeclarationSlots = ` + display-control: + allow-installation: + slot-snap-type: + - core + deny-auto-connection: true +` + +// gnome-settings-daemon also provides an API via setting the Brightness +// property via a Set() method on the org.gnome.SettingsDaemon.Power.Screen +// interface, but we can't mediate member data. This could instead be supported +// via userd... +const displayControlConnectedPlugAppArmor = ` +# Description: This interface allows getting information about a connected +# display and setting parameters like backlight brightness. + +# keyboard backlight key +/sys/class/leds/ r, +/sys/devices/**/leds/**kbd_backlight/{,**} r, +/sys/devices/**/leds/**kbd_backlight/brightness w, + +# upower +#include <abstractions/dbus-strict> +dbus (send) + bus=system + path=/org/freedesktop/UPower/KbdBacklight + interface=org.freedesktop.DBus.Introspectable + member=Introspect + peer=(label=unconfined), +dbus (send) + bus=system + path=/org/freedesktop/UPower/KbdBacklight + interface=org.freedesktop.UPower.KbdBacklight + member={GetBrightness,GetMaxBrightness,SetBrightness} + peer=(label=unconfined), + +# gnome-settings-daemon +#include <abstractions/dbus-session-strict> +dbus (send) + bus=session + path=/org/gnome/SettingsDaemon/Power + interface=org.freedesktop.DBus.Introspectable + member=Introspect + peer=(label=unconfined), +dbus (send) + bus=session + path=/org/gnome/SettingsDaemon/Power + interface=org.gnome.SettingsDaemon.Power.Screen + member=Step{Down,Up} + peer=(label=unconfined), + +/sys/class/backlight/ r, +` + +type displayControlInterface struct { + commonInterface +} + +func (iface *displayControlInterface) dereferencedBacklightPaths() []string { + var paths []string + sysClass := "/sys/class/backlight" + dirs, err := readDir(sysClass) + if err != nil { + return paths + } + + for _, s := range dirs { + p, err := evalSymlinks(filepath.Join(sysClass, s.Name())) + if err != nil { + continue + } + paths = append(paths, filepath.Clean(p)) + } + return paths +} + +func (iface *displayControlInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { + // add the static rules + spec.AddSnippet(displayControlConnectedPlugAppArmor) + + // add the detected rules + for _, p := range iface.dereferencedBacklightPaths() { + var buf bytes.Buffer + fmt.Fprintf(&buf, "# autodetected backlight: %s\n", path.Base(p)) + fmt.Fprintf(&buf, "%s/{,**} r,\n", p) + fmt.Fprintf(&buf, "%s/bl_power w,\n", p) + fmt.Fprintf(&buf, "%s/brightness w,\n", p) + spec.AddSnippet(buf.String()) + } + + return nil +} + +func init() { + registerIface(&displayControlInterface{commonInterface{ + name: "display-control", + summary: displayControlSummary, + implicitOnCore: true, + implicitOnClassic: true, + baseDeclarationSlots: displayControlBaseDeclarationSlots, + connectedPlugAppArmor: displayControlConnectedPlugAppArmor, + reservedForOS: true, + }}) +} diff --git a/interfaces/builtin/display_control_test.go b/interfaces/builtin/display_control_test.go new file mode 100644 index 0000000000..0178f832ce --- /dev/null +++ b/interfaces/builtin/display_control_test.go @@ -0,0 +1,125 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2019 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +package builtin_test + +import ( + . "gopkg.in/check.v1" + + "io/ioutil" + "os" + "path/filepath" + + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/apparmor" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/testutil" +) + +type displayControlInterfaceSuite struct { + testutil.BaseTest + + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug + + tmpdir string +} + +var _ = Suite(&displayControlInterfaceSuite{ + iface: builtin.MustInterface("display-control"), +}) + +const displayControlConsumerYaml = `name: consumer +version: 0 +apps: + app: + plugs: [display-control] +` + +const displayControlCoreYaml = `name: core +version: 0 +type: os +slots: + display-control: +` + +func (s *displayControlInterfaceSuite) SetUpTest(c *C) { + s.plug, s.plugInfo = MockConnectedPlug(c, displayControlConsumerYaml, nil, "display-control") + s.slot, s.slotInfo = MockConnectedSlot(c, displayControlCoreYaml, nil, "display-control") + + s.tmpdir = c.MkDir() +} + +func (s *displayControlInterfaceSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "display-control") +} + +func (s *displayControlInterfaceSuite) TestSanitizeSlot(c *C) { + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "some-snap"}, + Name: "display-control", + Interface: "display-control", + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, + "display-control slots are reserved for the core snap") +} + +func (s *displayControlInterfaceSuite) TestSanitizePlug(c *C) { + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) +} + +func (s *displayControlInterfaceSuite) TestAppArmorSpec(c *C) { + c.Assert(os.MkdirAll(filepath.Join(s.tmpdir, "foo_backlight"), 0755), IsNil) + c.Assert(os.MkdirAll(filepath.Join(s.tmpdir, "bar_backlight"), 0755), IsNil) + builtin.MockReadDir(&s.BaseTest, func(path string) ([]os.FileInfo, error) { + return ioutil.ReadDir(s.tmpdir) + }) + builtin.MockEvalSymlinks(&s.BaseTest, func(path string) (string, error) { + return "(dereferenced)" + path, nil + }) + spec := &apparmor.Specification{} + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "/sys/class/backlight/ r,\n") + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "autodetected backlight: bar_backlight\n") + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "(dereferenced)/sys/class/backlight/bar_backlight/{,**} r,\n") + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "autodetected backlight: foo_backlight\n") + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "(dereferenced)/sys/class/backlight/foo_backlight/{,**} r,\n") +} + +func (s *displayControlInterfaceSuite) TestStaticInfo(c *C) { + si := interfaces.StaticInfoOf(s.iface) + c.Assert(si.ImplicitOnCore, Equals, true) + c.Assert(si.ImplicitOnClassic, Equals, true) + c.Assert(si.Summary, Equals, `allows configuring display parameters`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "display-control") +} + +func (s *displayControlInterfaceSuite) TestAutoConnect(c *C) { + c.Assert(s.iface.AutoConnect(s.plugInfo, s.slotInfo), Equals, true) +} + +func (s *displayControlInterfaceSuite) TestInterfaces(c *C) { + c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) +} diff --git a/interfaces/builtin/export_test.go b/interfaces/builtin/export_test.go index fbe7410e9a..2315e924be 100644 --- a/interfaces/builtin/export_test.go +++ b/interfaces/builtin/export_test.go @@ -102,3 +102,13 @@ func MockOsGetenv(mock func(string) string) (restore func()) { return restore } + +func MockProcCpuinfo(filename string) (restore func()) { + old := procCpuinfo + restore = func() { + procCpuinfo = old + } + procCpuinfo = filename + + return restore +} diff --git a/interfaces/builtin/hardware_observe.go b/interfaces/builtin/hardware_observe.go index 12a376263f..6b281306b7 100644 --- a/interfaces/builtin/hardware_observe.go +++ b/interfaces/builtin/hardware_observe.go @@ -57,6 +57,7 @@ capability sys_admin, # power information /sys/power/{,**} r, +/run/udev/data/+power_supply:* r, # interrupts @{PROC}/interrupts r, diff --git a/interfaces/builtin/home.go b/interfaces/builtin/home.go index a9cfd1f74f..d95493ae75 100644 --- a/interfaces/builtin/home.go +++ b/interfaces/builtin/home.go @@ -82,6 +82,7 @@ audit deny @{HOME}/bin/{,**} wl, const homeConnectedPlugAppArmorWithAllRead = ` # Allow non-owner read to non-hidden and non-snap files and directories +capability dac_read_search, @{HOME}/ r, @{HOME}/[^s.]** r, @{HOME}/s[^n]** r, diff --git a/interfaces/builtin/kvm.go b/interfaces/builtin/kvm.go index cb254c5c7c..157d033ea6 100644 --- a/interfaces/builtin/kvm.go +++ b/interfaces/builtin/kvm.go @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2017 Canonical Ltd + * Copyright (C) 2017-2019 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -19,6 +19,18 @@ package builtin +import ( + "fmt" + "io/ioutil" + "regexp" + "strings" + + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/kmod" + "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/strutil" +) + const kvmSummary = `allows access to the kvm device` const kvmBaseDeclarationSlots = ` @@ -38,8 +50,58 @@ const kvmConnectedPlugAppArmor = ` var kvmConnectedPlugUDev = []string{`KERNEL=="kvm"`} +type kvmInterface struct { + commonInterface +} + +var procCpuinfo = "/proc/cpuinfo" +var flagsMatcher = regexp.MustCompile(`(?m)^flags\s+:\s+(.*)$`).FindSubmatch + +func getCpuFlags() (flags []string, err error) { + buf, err := ioutil.ReadFile(procCpuinfo) + if err != nil { + // if we can't read cpuinfo, we want to know _why_ + return nil, fmt.Errorf("unable to read %v: %v", procCpuinfo, err) + } + + // want to capture the text after 'flags:' entry + match := flagsMatcher(buf) + if len(match) == 0 { + return nil, fmt.Errorf("%v does not contain a 'flags:' entry", procCpuinfo) + } + + // match[0] has whole matching line, match[1] must exist as it has the captured text after 'flags:' + cpu_flags := strings.Fields(string(match[1])) + return cpu_flags, nil +} + +func (iface *kvmInterface) KModConnectedPlug(spec *kmod.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { + // Check CPU capabilities to load suitable module + // NOTE: this only considers i386, x86_64 and amd64 CPUs, but some ARM, PPC and S390 CPUs also support KVM + m := "kvm" + cpu_flags, err := getCpuFlags() + if err != nil { + logger.Debugf("kvm: fetching cpu info failed: %v", err) + } + + if strutil.ListContains(cpu_flags, "vmx") { + m = "kvm_intel" + } else if strutil.ListContains(cpu_flags, "svm") { + m = "kvm_amd" + } else { + // CPU appears not to support KVM extensions, fall back to bare kvm module as it appears + // sufficient for some architectures + logger.Noticef("kvm: failed to detect CPU specific KVM support, will attempt to modprobe generic KVM support") + } + + if err := spec.AddModule(m); err != nil { + return nil + } + return nil +} + func init() { - registerIface(&commonInterface{ + registerIface(&kvmInterface{commonInterface{ name: "kvm", summary: kvmSummary, implicitOnCore: true, @@ -48,5 +110,5 @@ func init() { connectedPlugAppArmor: kvmConnectedPlugAppArmor, connectedPlugUDev: kvmConnectedPlugUDev, reservedForOS: true, - }) + }}) } diff --git a/interfaces/builtin/kvm_test.go b/interfaces/builtin/kvm_test.go index ceeb171159..46410976c9 100644 --- a/interfaces/builtin/kvm_test.go +++ b/interfaces/builtin/kvm_test.go @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2017 Canonical Ltd + * Copyright (C) 2017-2019 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -20,22 +20,31 @@ package builtin_test import ( + "io/ioutil" + "path/filepath" + . "gopkg.in/check.v1" + "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/interfaces/kmod" "github.com/snapcore/snapd/interfaces/udev" "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/testutil" ) type kvmInterfaceSuite struct { + testutil.BaseTest + iface interfaces.Interface slotInfo *snap.SlotInfo slot *interfaces.ConnectedSlot plugInfo *snap.PlugInfo plug *interfaces.ConnectedPlug + + tmpdir string } var _ = Suite(&kvmInterfaceSuite{ @@ -57,8 +66,25 @@ slots: ` func (s *kvmInterfaceSuite) SetUpTest(c *C) { + s.BaseTest.SetUpTest(c) + s.plug, s.plugInfo = MockConnectedPlug(c, kvmConsumerYaml, nil, "kvm") s.slot, s.slotInfo = MockConnectedSlot(c, kvmCoreYaml, nil, "kvm") + + // Need to Mock output of /proc/cpuinfo + s.tmpdir = c.MkDir() + dirs.SetRootDir(s.tmpdir) + s.AddCleanup(func() { dirs.SetRootDir("/") }) + + mockCpuinfo := filepath.Join(s.tmpdir, "cpuinfo") + c.Assert(ioutil.WriteFile(mockCpuinfo, []byte(` +processor : 0 +flags : cpuflags without kvm support + +processor : 42 +flags : another cpu also without kvm support +`[1:]), 0644), IsNil) + s.AddCleanup(builtin.MockProcCpuinfo(mockCpuinfo)) } func (s *kvmInterfaceSuite) TestName(c *C) { @@ -116,3 +142,67 @@ func (s *kvmInterfaceSuite) TestAutoConnect(c *C) { func (s *kvmInterfaceSuite) TestInterfaces(c *C) { c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) } + +func (s *kvmInterfaceSuite) TestKModSpecWithUnknownCpu(c *C) { + spec := &kmod.Specification{} + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Modules(), DeepEquals, map[string]bool{ + "kvm": true, + }) +} + +func (s *kvmInterfaceSuite) TestKModSpecWithIntel(c *C) { + mockCpuinfo := filepath.Join(s.tmpdir, "cpuinfo") + c.Assert(ioutil.WriteFile(mockCpuinfo, []byte(` +processor : 0 +flags : stuff vmx other +`[1:]), 0644), IsNil) + + spec := &kmod.Specification{} + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Modules(), DeepEquals, map[string]bool{ + "kvm_intel": true, + }) +} + +func (s *kvmInterfaceSuite) TestKModSpecWithAMD(c *C) { + mockCpuinfo := filepath.Join(s.tmpdir, "cpuinfo") + c.Assert(ioutil.WriteFile(mockCpuinfo, []byte(` +processor : 0 +flags : stuff svm other +`[1:]), 0644), IsNil) + + s.AddCleanup(builtin.MockProcCpuinfo(mockCpuinfo)) + + spec := &kmod.Specification{} + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Modules(), DeepEquals, map[string]bool{ + "kvm_amd": true, + }) +} + +func (s *kvmInterfaceSuite) TestKModSpecWithEmptyCpuinfo(c *C) { + mockCpuinfo := filepath.Join(s.tmpdir, "cpuinfo") + c.Assert(ioutil.WriteFile(mockCpuinfo, []byte(` +`[1:]), 0644), IsNil) + + s.AddCleanup(builtin.MockProcCpuinfo(mockCpuinfo)) + + spec := &kmod.Specification{} + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Modules(), DeepEquals, map[string]bool{ + "kvm": true, + }) +} + +func (s *kvmInterfaceSuite) TestKModSpecWithMissingCpuinfo(c *C) { + mockCpuinfo := filepath.Join(s.tmpdir, "non-existent-cpuinfo") + + s.AddCleanup(builtin.MockProcCpuinfo(mockCpuinfo)) + + spec := &kmod.Specification{} + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Modules(), DeepEquals, map[string]bool{ + "kvm": true, + }) +} diff --git a/interfaces/builtin/pulseaudio.go b/interfaces/builtin/pulseaudio.go index b4dc10175d..ab14aada92 100644 --- a/interfaces/builtin/pulseaudio.go +++ b/interfaces/builtin/pulseaudio.go @@ -59,7 +59,7 @@ const pulseaudioConnectedPlugAppArmorDesktop = ` # to read available client side configuration settings. On an Ubuntu Core # device those things will be stored inside the snap directory. /etc/pulse/ r, -/etc/pulse/* r, +/etc/pulse/** r, owner @{HOME}/.pulse-cookie rk, owner @{HOME}/.config/pulse/cookie rk, owner /{,var/}run/user/*/pulse/ rwk, diff --git a/interfaces/builtin/system_observe.go b/interfaces/builtin/system_observe.go index e60850b273..4d97b74aa6 100644 --- a/interfaces/builtin/system_observe.go +++ b/interfaces/builtin/system_observe.go @@ -42,6 +42,7 @@ const systemObserveConnectedPlugAppArmor = ` ptrace (read), # Other miscellaneous accesses for observing the system +@{PROC}/locks r, @{PROC}/modules r, @{PROC}/stat r, @{PROC}/vmstat r, diff --git a/interfaces/builtin/u2f_devices.go b/interfaces/builtin/u2f_devices.go new file mode 100644 index 0000000000..681d32f8ec --- /dev/null +++ b/interfaces/builtin/u2f_devices.go @@ -0,0 +1,149 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2019 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +package builtin + +import ( + "fmt" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/udev" +) + +const u2fDevicesSummary = `allows access to u2f devices` + +const u2fDevicesBaseDeclarationSlots = ` + u2f-devices: + allow-installation: + slot-snap-type: + - core + deny-auto-connection: true +` + +type u2fDevice struct { + Name, VendorIDPattern, ProductIDPattern string +} + +// https://github.com/Yubico/libu2f-host/blob/master/70-u2f.rules +var u2fDevices = []u2fDevice{ + { + Name: "Yubico YubiKey", + VendorIDPattern: "1050", + ProductIDPattern: "0113|0114|0115|0116|0120|0200|0402|0403|0406|0407|0410", + }, + { + Name: "Happlink (formerly Plug-Up) Security KEY", + VendorIDPattern: "2581", + ProductIDPattern: "f1d0", + }, + { + Name: "Neowave Keydo and Keydo AES", + VendorIDPattern: "1e0d", + ProductIDPattern: "f1d0|f1ae", + }, + { + Name: "HyperSecu HyperFIDO", + VendorIDPattern: "096e|2ccf", + ProductIDPattern: "0880", + }, + { + Name: "Feitian ePass FIDO, BioPass FIDO2", + VendorIDPattern: "096e", + ProductIDPattern: "0850|0852|0853|0854|0856|0858|085a|085b|085d", + }, + { + Name: "JaCarta U2F", + VendorIDPattern: "24dc", + ProductIDPattern: "0101", + }, + { + Name: "U2F Zero", + VendorIDPattern: "10c4", + ProductIDPattern: "8acf", + }, + { + Name: "VASCO SeccureClick", + VendorIDPattern: "1a44", + ProductIDPattern: "00bb", + }, + { + Name: "Bluink Key", + VendorIDPattern: "2abe", + ProductIDPattern: "1002", + }, + { + Name: "Thetis Key", + VendorIDPattern: "1ea8", + ProductIDPattern: "f025", + }, + { + Name: "Nitrokey FIDO U2F", + VendorIDPattern: "20a0", + ProductIDPattern: "4287", + }, + { + Name: "Google Titan U2F", + VendorIDPattern: "18d1", + ProductIDPattern: "5026", + }, + { + Name: "Tomu board + chopstx U2F", + VendorIDPattern: "0483", + ProductIDPattern: "cdab", + }, +} + +const u2fDevicesConnectedPlugAppArmor = ` +# Description: Allow write access to u2f hidraw devices. + +# Use a glob rule and rely on device cgroup for mediation. +/dev/hidraw* rw, + +# char 234-254 are used for dynamic assignment, which u2f devices are +/run/udev/data/c23[4-9]:* r, +/run/udev/data/c24[0-9]:* r, +/run/udev/data/c25[0-4]:* r, + +# misc required accesses +/run/udev/data/+power_supply:hid* r, +/run/udev/data/c14:[0-9]* r, +/sys/devices/**/usb*/**/report_descriptor r, +` + +type u2fDevicesInterface struct { + commonInterface +} + +func (iface *u2fDevicesInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { + for _, d := range u2fDevices { + spec.TagDevice(fmt.Sprintf("# %s\nSUBSYSTEM==\"hidraw\", KERNEL==\"hidraw*\", ATTRS{idVendor}==\"%s\", ATTRS{idProduct}==\"%s\"", d.Name, d.VendorIDPattern, d.ProductIDPattern)) + } + return nil +} + +func init() { + registerIface(&u2fDevicesInterface{commonInterface{ + name: "u2f-devices", + summary: u2fDevicesSummary, + implicitOnCore: true, + implicitOnClassic: true, + baseDeclarationSlots: u2fDevicesBaseDeclarationSlots, + connectedPlugAppArmor: u2fDevicesConnectedPlugAppArmor, + reservedForOS: true, + }}) +} diff --git a/interfaces/builtin/u2f_devices_test.go b/interfaces/builtin/u2f_devices_test.go new file mode 100644 index 0000000000..22fe06bec6 --- /dev/null +++ b/interfaces/builtin/u2f_devices_test.go @@ -0,0 +1,116 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2019 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +package builtin_test + +import ( + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/apparmor" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/interfaces/udev" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/testutil" +) + +type u2fDevicesInterfaceSuite struct { + testutil.BaseTest + + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug +} + +var _ = Suite(&u2fDevicesInterfaceSuite{ + iface: builtin.MustInterface("u2f-devices"), +}) + +const u2fDevicesConsumerYaml = `name: consumer +version: 0 +apps: + app: + plugs: [u2f-devices] +` + +const u2fDevicesCoreYaml = `name: core +version: 0 +type: os +slots: + u2f-devices: +` + +func (s *u2fDevicesInterfaceSuite) SetUpTest(c *C) { + s.plug, s.plugInfo = MockConnectedPlug(c, u2fDevicesConsumerYaml, nil, "u2f-devices") + s.slot, s.slotInfo = MockConnectedSlot(c, u2fDevicesCoreYaml, nil, "u2f-devices") +} + +func (s *u2fDevicesInterfaceSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "u2f-devices") +} + +func (s *u2fDevicesInterfaceSuite) TestSanitizeSlot(c *C) { + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "some-snap"}, + Name: "u2f-devices", + Interface: "u2f-devices", + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "u2f-devices slots are reserved for the core snap") +} + +func (s *u2fDevicesInterfaceSuite) TestSanitizePlug(c *C) { + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) +} + +func (s *u2fDevicesInterfaceSuite) TestAppArmorSpec(c *C) { + spec := &apparmor.Specification{} + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `# Description: Allow write access to u2f hidraw devices.`) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `/dev/hidraw* rw,`) +} + +func (s *u2fDevicesInterfaceSuite) TestUDevSpec(c *C) { + spec := &udev.Specification{} + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Snippets(), HasLen, 14) + c.Assert(spec.Snippets(), testutil.Contains, `# u2f-devices +# Yubico YubiKey +SUBSYSTEM=="hidraw", KERNEL=="hidraw*", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0113|0114|0115|0116|0120|0200|0402|0403|0406|0407|0410", TAG+="snap_consumer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_consumer_app", RUN+="/usr/lib/snapd/snap-device-helper $env{ACTION} snap_consumer_app $devpath $major:$minor"`) +} + +func (s *u2fDevicesInterfaceSuite) TestStaticInfo(c *C) { + si := interfaces.StaticInfoOf(s.iface) + c.Assert(si.ImplicitOnCore, Equals, true) + c.Assert(si.ImplicitOnClassic, Equals, true) + c.Assert(si.Summary, Equals, `allows access to u2f devices`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "u2f-devices") +} + +func (s *u2fDevicesInterfaceSuite) TestAutoConnect(c *C) { + c.Assert(s.iface.AutoConnect(s.plugInfo, s.slotInfo), Equals, true) +} + +func (s *u2fDevicesInterfaceSuite) TestInterfaces(c *C) { + c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) +} diff --git a/interfaces/core.go b/interfaces/core.go index fe64b814d6..7de4616e32 100644 --- a/interfaces/core.go +++ b/interfaces/core.go @@ -51,6 +51,14 @@ func (ref PlugRef) String() string { return fmt.Sprintf("%s:%s", ref.Snap, ref.Name) } +// SortsBefore returns true when plug should be sorted before the other +func (ref PlugRef) SortsBefore(other PlugRef) bool { + if ref.Snap != other.Snap { + return ref.Snap < other.Snap + } + return ref.Name < other.Name +} + // Sanitize slot with a given snapd interface. func BeforePrepareSlot(iface Interface, slotInfo *snap.SlotInfo) error { if iface.Name() != slotInfo.Interface { @@ -75,6 +83,14 @@ func (ref SlotRef) String() string { return fmt.Sprintf("%s:%s", ref.Snap, ref.Name) } +// SortsBefore returns true when slot should be sorted before the other +func (ref SlotRef) SortsBefore(other SlotRef) bool { + if ref.Snap != other.Snap { + return ref.Snap < other.Snap + } + return ref.Name < other.Name +} + // Interfaces holds information about a list of plugs, slots and their connections. type Interfaces struct { Plugs []*snap.PlugInfo @@ -110,6 +126,14 @@ func (conn *ConnRef) ID() string { return fmt.Sprintf("%s:%s %s:%s", conn.PlugRef.Snap, conn.PlugRef.Name, conn.SlotRef.Snap, conn.SlotRef.Name) } +// SortsBefore returns true when connection should be sorted before the other +func (conn *ConnRef) SortsBefore(other *ConnRef) bool { + if conn.PlugRef != other.PlugRef { + return conn.PlugRef.SortsBefore(other.PlugRef) + } + return conn.SlotRef.SortsBefore(other.SlotRef) +} + // ParseConnRef parses an ID string func ParseConnRef(id string) (*ConnRef, error) { var conn ConnRef diff --git a/interfaces/policy/basedeclaration_test.go b/interfaces/policy/basedeclaration_test.go index d49a7f1e0b..f17be4dd1a 100644 --- a/interfaces/policy/basedeclaration_test.go +++ b/interfaces/policy/basedeclaration_test.go @@ -471,6 +471,24 @@ plugs: c.Check(err, IsNil) } +func (s *baseDeclSuite) TestAutoConnectionBlockDevicesOverride(c *C) { + cand := s.connectCand(c, "block-devices", "", "") + err := cand.CheckAutoConnect() + c.Check(err, NotNil) + c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"block-devices\"") + + plugsSlots := ` +plugs: + block-devices: + allow-auto-connection: true +` + + snapDecl := s.mockSnapDecl(c, "some-snap", "J60k4JY0HppjwOjW8dZdYc8obXKxujRu", "canonical", plugsSlots) + cand.PlugSnapDeclaration = snapDecl + err = cand.CheckAutoConnect() + c.Check(err, IsNil) +} + func (s *baseDeclSuite) TestAutoConnectionOverrideMultiple(c *C) { plugsSlots := ` plugs: @@ -640,6 +658,7 @@ func (s *baseDeclSuite) TestPlugInstallation(c *C) { all := builtin.Interfaces() restricted := map[string]bool{ + "block-devices": true, "classic-support": true, "docker-support": true, "greengrass-support": true, @@ -766,6 +785,7 @@ func (s *baseDeclSuite) TestSanity(c *C) { // given how the rules work this can be delicate, // listed here to make sure that was a conscious decision bothSides := map[string]bool{ + "block-devices": true, "classic-support": true, "core-support": true, "docker-support": true, diff --git a/interfaces/seccomp/template.go b/interfaces/seccomp/template.go index 7f4b470f64..cce88f0e12 100644 --- a/interfaces/seccomp/template.go +++ b/interfaces/seccomp/template.go @@ -337,6 +337,8 @@ _newselect pselect pselect6 +# Allow use of SysV semaphores. Note that allocated resources are not freed by +# OOM which can lead to global kernel resource leakage. semctl semget semop diff --git a/interfaces/sorting.go b/interfaces/sorting.go index f5bb3a345d..b836117ee8 100644 --- a/interfaces/sorting.go +++ b/interfaces/sorting.go @@ -30,16 +30,7 @@ type byConnRef []*ConnRef func (c byConnRef) Len() int { return len(c) } func (c byConnRef) Swap(i, j int) { c[i], c[j] = c[j], c[i] } func (c byConnRef) Less(i, j int) bool { - if c[i].PlugRef.Snap != c[j].PlugRef.Snap { - return c[i].PlugRef.Snap < c[j].PlugRef.Snap - } - if c[i].PlugRef.Name != c[j].PlugRef.Name { - return c[i].PlugRef.Name < c[j].PlugRef.Name - } - if c[i].SlotRef.Snap != c[j].SlotRef.Snap { - return c[i].SlotRef.Snap < c[j].SlotRef.Snap - } - return c[i].SlotRef.Name < c[j].SlotRef.Name + return c[i].SortsBefore(c[j]) } type byPlugSnapAndName []*snap.PlugInfo diff --git a/interfaces/sorting_test.go b/interfaces/sorting_test.go index f396fa3f78..afd7d682e4 100644 --- a/interfaces/sorting_test.go +++ b/interfaces/sorting_test.go @@ -70,3 +70,65 @@ func (s *SortingSuite) TestByConnRef(c *C) { newConnRef("name-1_instance", "plug-1", "name-2", "slot-1"), }) } + +func newSlotRef(snap, name string) *interfaces.SlotRef { + return &interfaces.SlotRef{Snap: snap, Name: name} +} + +type bySlotRef []*interfaces.SlotRef + +func (b bySlotRef) Len() int { return len(b) } +func (b bySlotRef) Swap(i, j int) { b[i], b[j] = b[j], b[i] } +func (b bySlotRef) Less(i, j int) bool { + return b[i].SortsBefore(*b[j]) +} + +func (s *SortingSuite) TestSortSlotRef(c *C) { + list := []*interfaces.SlotRef{ + newSlotRef("name-2", "slot-3"), + newSlotRef("name-2_instance", "slot-1"), + newSlotRef("name-2", "slot-2"), + newSlotRef("name-2", "slot-4"), + newSlotRef("name-2", "slot-1"), + } + sort.Sort(bySlotRef(list)) + + c.Assert(list, DeepEquals, []*interfaces.SlotRef{ + newSlotRef("name-2", "slot-1"), + newSlotRef("name-2", "slot-2"), + newSlotRef("name-2", "slot-3"), + newSlotRef("name-2", "slot-4"), + newSlotRef("name-2_instance", "slot-1"), + }) +} + +type byPlugRef []*interfaces.PlugRef + +func (b byPlugRef) Len() int { return len(b) } +func (b byPlugRef) Swap(i, j int) { b[i], b[j] = b[j], b[i] } +func (b byPlugRef) Less(i, j int) bool { + return b[i].SortsBefore(*b[j]) +} + +func newPlugRef(snap, name string) *interfaces.PlugRef { + return &interfaces.PlugRef{Snap: snap, Name: name} +} + +func (s *SortingSuite) TestSortPlugRef(c *C) { + list := []*interfaces.PlugRef{ + newPlugRef("name-2", "plug-3"), + newPlugRef("name-2_instance", "plug-1"), + newPlugRef("name-2", "plug-4"), + newPlugRef("name-2", "plug-2"), + newPlugRef("name-2", "plug-1"), + } + sort.Sort(byPlugRef(list)) + + c.Assert(list, DeepEquals, []*interfaces.PlugRef{ + newPlugRef("name-2", "plug-1"), + newPlugRef("name-2", "plug-2"), + newPlugRef("name-2", "plug-3"), + newPlugRef("name-2", "plug-4"), + newPlugRef("name-2_instance", "plug-1"), + }) +} diff --git a/osutil/context.go b/osutil/context.go index 0d59cf3152..0c5fb26476 100644 --- a/osutil/context.go +++ b/osutil/context.go @@ -20,12 +20,11 @@ package osutil import ( + "context" "io" "os/exec" "sync/atomic" "syscall" - - "golang.org/x/net/context" ) // ContextWriter returns a discarding io.Writer which Write method diff --git a/osutil/context_test.go b/osutil/context_test.go index 52ecbcd42b..92ee31d6a5 100644 --- a/osutil/context_test.go +++ b/osutil/context_test.go @@ -20,13 +20,13 @@ package osutil_test import ( + "context" "io" "os/exec" "strings" "testing" "time" - "golang.org/x/net/context" "gopkg.in/check.v1" "github.com/snapcore/snapd/osutil" @@ -43,7 +43,8 @@ func (dumbReader) Read([]byte) (int, error) { var _ = check.Suite(&ctxSuite{}) func (ctxSuite) TestWriter(c *check.C) { - ctx, _ := context.WithTimeout(context.Background(), time.Second/100) + ctx, cancel := context.WithTimeout(context.Background(), time.Second/100) + defer cancel() n, err := io.Copy(osutil.ContextWriter(ctx), dumbReader{}) c.Assert(err, check.Equals, context.DeadlineExceeded) // but we copied things until the deadline hit @@ -60,7 +61,8 @@ func (ctxSuite) TestWriterDone(c *check.C) { } func (ctxSuite) TestWriterSuccess(c *check.C) { - ctx, _ := context.WithTimeout(context.Background(), time.Second/100) + ctx, cancel := context.WithTimeout(context.Background(), time.Second/100) + defer cancel() // check we can copy if we're quick n, err := io.Copy(osutil.ContextWriter(ctx), strings.NewReader("hello")) c.Check(err, check.IsNil) @@ -68,7 +70,8 @@ func (ctxSuite) TestWriterSuccess(c *check.C) { } func (ctxSuite) TestRun(c *check.C) { - ctx, _ := context.WithTimeout(context.Background(), time.Second/100) + ctx, cancel := context.WithTimeout(context.Background(), time.Second/100) + defer cancel() cmd := exec.Command("/bin/sleep", "1") err := osutil.RunWithContext(ctx, cmd) c.Check(err, check.Equals, context.DeadlineExceeded) @@ -94,8 +97,9 @@ func (ctxSuite) TestRunRace(c *check.C) { nfailed := 0 for nfailed == 0 || nkilled == 0 { cmd := exec.Command("/bin/false") - ctx, _ := context.WithTimeout(context.Background(), dt) + ctx, cancel := context.WithTimeout(context.Background(), dt) err := osutil.RunWithContext(ctx, cmd) + cancel() switch err.Error() { case killedstr: nkilled++ @@ -118,14 +122,16 @@ func (ctxSuite) TestRunDone(c *check.C) { } func (ctxSuite) TestRunSuccess(c *check.C) { - ctx, _ := context.WithTimeout(context.Background(), time.Second) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() cmd := exec.Command("/bin/sleep", "0.01") err := osutil.RunWithContext(ctx, cmd) c.Check(err, check.IsNil) } func (ctxSuite) TestRunSuccessfulFailure(c *check.C) { - ctx, _ := context.WithTimeout(context.Background(), time.Second) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() cmd := exec.Command("not/something/you/can/run") err := osutil.RunWithContext(ctx, cmd) c.Check(err, check.ErrorMatches, `fork/exec \S+: no such file or directory`) diff --git a/overlord/auth/auth.go b/overlord/auth/auth.go index e33b6c9e29..b747b92596 100644 --- a/overlord/auth/auth.go +++ b/overlord/auth/auth.go @@ -20,6 +20,7 @@ package auth import ( + "context" "crypto/rand" "encoding/base64" "errors" @@ -29,7 +30,6 @@ import ( "sort" "strconv" - "golang.org/x/net/context" "gopkg.in/macaroon.v1" "github.com/snapcore/snapd/asserts" diff --git a/overlord/auth/auth_test.go b/overlord/auth/auth_test.go index 4357f73c80..3248f61858 100644 --- a/overlord/auth/auth_test.go +++ b/overlord/auth/auth_test.go @@ -20,14 +20,13 @@ package auth_test import ( + "context" "net/url" "os" "strings" "testing" "time" - "golang.org/x/net/context" - . "gopkg.in/check.v1" "gopkg.in/macaroon.v1" diff --git a/overlord/hookstate/ctlcmd/services_test.go b/overlord/hookstate/ctlcmd/services_test.go index 88c17e49f9..5da02f6de7 100644 --- a/overlord/hookstate/ctlcmd/services_test.go +++ b/overlord/hookstate/ctlcmd/services_test.go @@ -20,11 +20,10 @@ package ctlcmd_test import ( + "context" "fmt" "sort" - "golang.org/x/net/context" - . "gopkg.in/check.v1" "github.com/snapcore/snapd/asserts" diff --git a/overlord/ifacestate/ifacestate_test.go b/overlord/ifacestate/ifacestate_test.go index 5bcd86a1d4..89bb7d4516 100644 --- a/overlord/ifacestate/ifacestate_test.go +++ b/overlord/ifacestate/ifacestate_test.go @@ -5230,6 +5230,74 @@ func (s *interfaceManagerSuite) TestHotplugAutoconnectConflictRetry(c *C) { c.Check(hotplugConnectTask.Log()[0], Matches, `.*hotplug connect will be retried: conflicting snap core with task "link-snap"`) } +// mockConsumer mocks a consumer snap and its single plug in the repository +func mockConsumer(c *C, st *state.State, repo *interfaces.Repository, snapYaml, consumerSnapName, plugName string) { + si := &snap.SideInfo{RealName: consumerSnapName, Revision: snap.R(1)} + consumer := snaptest.MockSnapInstance(c, "", snapYaml, si) + c.Assert(consumer.Plugs[plugName], NotNil) + c.Assert(repo.AddPlug(consumer.Plugs[plugName]), IsNil) + snapstate.Set(st, consumerSnapName, &snapstate.SnapState{ + Active: true, + Sequence: []*snap.SideInfo{si}, + Current: snap.R(1), + SnapType: "app", + }) +} + +func (s *interfaceManagerSuite) TestHotplugConnectAndAutoconnect(c *C) { + s.MockModel(c, nil) + + coreInfo := s.mockSnap(c, coreSnapYaml) + repo := s.manager(c).Repository() + c.Assert(repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "test"}), IsNil) + + // mock hotplug slot in the repo and state + c.Assert(repo.AddSlot(&snap.SlotInfo{Snap: coreInfo, Name: "hotplugslot", Interface: "test", HotplugKey: "1234"}), IsNil) + + s.state.Lock() + s.state.Set("hotplug-slots", map[string]interface{}{ + "hotplugslot": map[string]interface{}{"name": "hotplugslot", "interface": "test", "hotplug-key": "1234"}, + }) + + mockConsumer(c, s.state, repo, consumerYaml, "consumer", "plug") + mockConsumer(c, s.state, repo, consumer2Yaml, "consumer2", "plug") + + chg := s.state.NewChange("hotplug change", "") + t := s.state.NewTask("hotplug-connect", "") + ifacestate.SetHotplugAttrs(t, "test", "1234") + chg.AddTask(t) + + // simulate a device that was known and connected before to only one consumer, this connection will be restored + s.state.Set("conns", map[string]interface{}{ + "consumer:plug core:hotplugslot": map[string]interface{}{ + "interface": "test", + "hotplug-key": "1234", + "hotplug-gone": true, + }}) + + s.state.Unlock() + s.settle(c) + s.state.Lock() + + c.Assert(chg.Err(), IsNil) + + // two connections now present (restored one for consumer, and new one for consumer2) + var conns map[string]interface{} + c.Assert(s.state.Get("conns", &conns), IsNil) + c.Assert(conns, DeepEquals, map[string]interface{}{ + "consumer:plug core:hotplugslot": map[string]interface{}{ + "interface": "test", + "hotplug-key": "1234", + "plug-static": map[string]interface{}{"attr1": "value1"}, + }, + "consumer2:plug core:hotplugslot": map[string]interface{}{ + "interface": "test", + "hotplug-key": "1234", + "auto": true, + "plug-static": map[string]interface{}{"attr1": "value1"}, + }}) +} + func (s *interfaceManagerSuite) TestHotplugDisconnect(c *C) { coreInfo := s.mockSnap(c, coreSnapYaml) repo := s.manager(c).Repository() diff --git a/overlord/managers_test.go b/overlord/managers_test.go index 21f9d04156..5ebb861c50 100644 --- a/overlord/managers_test.go +++ b/overlord/managers_test.go @@ -22,6 +22,7 @@ package overlord_test // test the various managers and their operation together through overlord import ( + "context" "encoding/json" "fmt" "io" @@ -34,8 +35,6 @@ import ( "strings" "time" - "golang.org/x/net/context" - . "gopkg.in/check.v1" "github.com/snapcore/snapd/asserts" diff --git a/overlord/snapshotstate/backend/backend.go b/overlord/snapshotstate/backend/backend.go index 590f291618..30b46a7dd5 100644 --- a/overlord/snapshotstate/backend/backend.go +++ b/overlord/snapshotstate/backend/backend.go @@ -21,6 +21,7 @@ package backend import ( "archive/zip" + "context" "crypto" "encoding/json" "errors" @@ -31,8 +32,6 @@ import ( "sort" "time" - "golang.org/x/net/context" - "github.com/snapcore/snapd/client" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/logger" diff --git a/overlord/snapshotstate/backend/backend_test.go b/overlord/snapshotstate/backend/backend_test.go index ff363bbe8a..f20d91152f 100644 --- a/overlord/snapshotstate/backend/backend_test.go +++ b/overlord/snapshotstate/backend/backend_test.go @@ -22,6 +22,7 @@ package backend_test import ( "archive/zip" "bytes" + "context" "errors" "fmt" "io" @@ -34,7 +35,6 @@ import ( "strings" "testing" - "golang.org/x/net/context" "gopkg.in/check.v1" "github.com/snapcore/snapd/client" diff --git a/overlord/snapshotstate/backend/reader.go b/overlord/snapshotstate/backend/reader.go index 61c273c5c3..94ac8cf82f 100644 --- a/overlord/snapshotstate/backend/reader.go +++ b/overlord/snapshotstate/backend/reader.go @@ -21,6 +21,7 @@ package backend import ( "bytes" + "context" "crypto" "errors" "fmt" @@ -32,8 +33,6 @@ import ( "sort" "syscall" - "golang.org/x/net/context" - "github.com/snapcore/snapd/client" "github.com/snapcore/snapd/jsonutil" "github.com/snapcore/snapd/logger" diff --git a/overlord/snapshotstate/export_test.go b/overlord/snapshotstate/export_test.go index 99bf47e419..c5fa581a0d 100644 --- a/overlord/snapshotstate/export_test.go +++ b/overlord/snapshotstate/export_test.go @@ -20,10 +20,9 @@ package snapshotstate import ( + "context" "encoding/json" - "golang.org/x/net/context" - "github.com/snapcore/snapd/client" "github.com/snapcore/snapd/overlord/snapshotstate/backend" "github.com/snapcore/snapd/overlord/snapstate" diff --git a/overlord/snapshotstate/snapshotmgr_test.go b/overlord/snapshotstate/snapshotmgr_test.go index c13aee7725..f1ee73eb86 100644 --- a/overlord/snapshotstate/snapshotmgr_test.go +++ b/overlord/snapshotstate/snapshotmgr_test.go @@ -20,12 +20,12 @@ package snapshotstate_test import ( + "context" "encoding/json" "errors" "path/filepath" "sort" - "golang.org/x/net/context" "gopkg.in/check.v1" "gopkg.in/tomb.v2" diff --git a/overlord/snapshotstate/snapshotstate.go b/overlord/snapshotstate/snapshotstate.go index 7860863b49..0621610669 100644 --- a/overlord/snapshotstate/snapshotstate.go +++ b/overlord/snapshotstate/snapshotstate.go @@ -20,11 +20,10 @@ package snapshotstate import ( + "context" "fmt" "sort" - "golang.org/x/net/context" - "github.com/snapcore/snapd/client" "github.com/snapcore/snapd/overlord/snapshotstate/backend" "github.com/snapcore/snapd/overlord/snapstate" diff --git a/overlord/snapshotstate/snapshotstate_test.go b/overlord/snapshotstate/snapshotstate_test.go index ed17956936..b023d9928a 100644 --- a/overlord/snapshotstate/snapshotstate_test.go +++ b/overlord/snapshotstate/snapshotstate_test.go @@ -20,6 +20,7 @@ package snapshotstate_test import ( + "context" "errors" "fmt" "os" @@ -31,7 +32,6 @@ import ( "testing" "time" - "golang.org/x/net/context" "gopkg.in/check.v1" "github.com/snapcore/snapd/client" diff --git a/overlord/snapstate/autorefresh_test.go b/overlord/snapstate/autorefresh_test.go index bf44ca9514..3fb32ee748 100644 --- a/overlord/snapstate/autorefresh_test.go +++ b/overlord/snapstate/autorefresh_test.go @@ -20,13 +20,12 @@ package snapstate_test import ( + "context" "fmt" "os" "path/filepath" "time" - "golang.org/x/net/context" - . "gopkg.in/check.v1" "github.com/snapcore/snapd/dirs" diff --git a/overlord/snapstate/backend.go b/overlord/snapstate/backend.go index ab04b9fe31..5cfc9d39f2 100644 --- a/overlord/snapstate/backend.go +++ b/overlord/snapstate/backend.go @@ -20,10 +20,9 @@ package snapstate import ( + "context" "io" - "golang.org/x/net/context" - "github.com/snapcore/snapd/asserts" "github.com/snapcore/snapd/client" "github.com/snapcore/snapd/overlord/auth" diff --git a/overlord/snapstate/backend/export_test.go b/overlord/snapstate/backend/export_test.go index 2ff5ecf906..648e3f16fe 100644 --- a/overlord/snapstate/backend/export_test.go +++ b/overlord/snapstate/backend/export_test.go @@ -19,6 +19,10 @@ package backend +import ( + "os/exec" +) + var ( AddMountUnit = addMountUnit RemoveMountUnit = removeMountUnit @@ -31,3 +35,11 @@ func MockUpdateFontconfigCaches(f func() error) (restore func()) { updateFontconfigCaches = oldUpdateFontconfigCaches } } + +func MockCommandFromCore(f func(string, string, ...string) (*exec.Cmd, error)) (restore func()) { + old := commandFromCore + commandFromCore = f + return func() { + commandFromCore = old + } +} diff --git a/overlord/snapstate/backend/fontconfig.go b/overlord/snapstate/backend/fontconfig.go index 578b84713d..57e8cbf845 100644 --- a/overlord/snapstate/backend/fontconfig.go +++ b/overlord/snapstate/backend/fontconfig.go @@ -27,12 +27,13 @@ import ( ) var updateFontconfigCaches = updateFontconfigCachesImpl +var commandFromCore = osutil.CommandFromCore // updateFontconfigCaches always update the fontconfig caches func updateFontconfigCachesImpl() error { for _, fc := range []string{"fc-cache-v6", "fc-cache-v7"} { // FIXME: also use the snapd snap if available - cmd, err := osutil.CommandFromCore(dirs.SnapMountDir, "/bin/"+fc) + cmd, err := commandFromCore(dirs.SnapMountDir, "/bin/"+fc) if err != nil { return fmt.Errorf("cannot get %s from core: %v", fc, err) } diff --git a/overlord/snapstate/backend/link.go b/overlord/snapstate/backend/link.go index 8543bd557f..6e24c7bbcc 100644 --- a/overlord/snapstate/backend/link.go +++ b/overlord/snapstate/backend/link.go @@ -58,6 +58,13 @@ func updateCurrentSymlinks(info *snap.Info) error { return os.Symlink(filepath.Base(mountDir), currentActiveSymlink) } +func hasFontConfigCache(info *snap.Info) bool { + if info.InstanceName() == "core" || info.InstanceName() == "snapd" { + return true + } + return false +} + // LinkSnap makes the snap available by generating wrappers and setting the current symlinks. func (b Backend) LinkSnap(info *snap.Info, model *asserts.Model) error { if info.Revision.Unset() { @@ -68,9 +75,11 @@ func (b Backend) LinkSnap(info *snap.Info, model *asserts.Model) error { return err } - // fontconfig is only relevant on classic - // TODO: consider moving this to a less hidden place - if release.OnClassic { + // fontconfig is only relevant on classic and is carried by 'core' or + // 'snapd' snaps + // for non-core snaps, fontconfig cache needs to be updated before the + // snap applications are runnable + if release.OnClassic && !hasFontConfigCache(info) { if err := updateFontconfigCaches(); err != nil { logger.Noticef("cannot update fontconfig cache: %v", err) } @@ -90,7 +99,18 @@ func (b Backend) LinkSnap(info *snap.Info, model *asserts.Model) error { } } - return updateCurrentSymlinks(info) + if err := updateCurrentSymlinks(info); err != nil { + return err + } + + // for core snap, fontconfig cache can be updated after the snap has + // been made available + if release.OnClassic && hasFontConfigCache(info) { + if err := updateFontconfigCaches(); err != nil { + logger.Noticef("cannot update fontconfig cache: %v", err) + } + } + return nil } func (b Backend) StartServices(apps []*snap.AppInfo, meter progress.Meter) error { diff --git a/overlord/snapstate/backend/link_test.go b/overlord/snapstate/backend/link_test.go index 17fe766d65..b5ff515f3b 100644 --- a/overlord/snapstate/backend/link_test.go +++ b/overlord/snapstate/backend/link_test.go @@ -23,6 +23,7 @@ import ( "errors" "io/ioutil" "os" + "os/exec" "path/filepath" . "gopkg.in/check.v1" @@ -34,6 +35,7 @@ import ( "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap/snaptest" "github.com/snapcore/snapd/systemd" + "github.com/snapcore/snapd/testutil" "github.com/snapcore/snapd/overlord/snapstate/backend" ) @@ -315,13 +317,15 @@ func (s *linkCleanupSuite) TestLinkCleanupOnSystemctlFail(c *C) { } func (s *linkCleanupSuite) TestLinkRunsUpdateFontconfigCachesClassic(c *C) { - for _, onClassic := range []bool{false, true} { + current := filepath.Join(s.info.MountDir(), "..", "current") + for _, onClassic := range []bool{false, true} { restore := release.MockOnClassic(onClassic) defer restore() var updateFontconfigCaches int restore = backend.MockUpdateFontconfigCaches(func() error { + c.Assert(osutil.FileExists(current), Equals, false) updateFontconfigCaches += 1 return nil }) @@ -334,5 +338,53 @@ func (s *linkCleanupSuite) TestLinkRunsUpdateFontconfigCachesClassic(c *C) { } else { c.Assert(updateFontconfigCaches, Equals, 0) } + c.Assert(os.Remove(current), IsNil) } } + +func (s *linkCleanupSuite) TestLinkRunsUpdateFontconfigCachesCallsFromNewCurrent(c *C) { + restore := release.MockOnClassic(true) + defer restore() + + const yaml = `name: core +version: 1.0 +type: os +` + // old version is 'current' + infoOld := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(11)}) + mountDirOld := infoOld.MountDir() + err := os.Symlink(filepath.Base(mountDirOld), filepath.Join(mountDirOld, "..", "current")) + c.Assert(err, IsNil) + + err = os.MkdirAll(filepath.Join(mountDirOld, "bin"), 0755) + c.Assert(err, IsNil) + + oldCmdV6 := testutil.MockCommand(c, filepath.Join(mountDirOld, "bin", "fc-cache-v6"), "") + oldCmdV7 := testutil.MockCommand(c, filepath.Join(mountDirOld, "bin", "fc-cache-v7"), "") + + infoNew := snaptest.MockSnap(c, yaml, &snap.SideInfo{Revision: snap.R(12)}) + mountDirNew := infoNew.MountDir() + + err = os.MkdirAll(filepath.Join(mountDirNew, "bin"), 0755) + c.Assert(err, IsNil) + + newCmdV6 := testutil.MockCommand(c, filepath.Join(mountDirNew, "bin", "fc-cache-v6"), "") + newCmdV7 := testutil.MockCommand(c, filepath.Join(mountDirNew, "bin", "fc-cache-v7"), "") + + // provide our own mock, osutil.CommandFromCore expects an ELF binary + restore = backend.MockCommandFromCore(func(mountDir, name string, args ...string) (*exec.Cmd, error) { + cmd := filepath.Join(mountDir, "core", "current", name) + c.Logf("command from core: %v", cmd) + return exec.Command(cmd, args...), nil + }) + defer restore() + + err = s.be.LinkSnap(infoNew, nil) + c.Assert(err, IsNil) + + c.Check(oldCmdV6.Calls(), HasLen, 0) + c.Check(oldCmdV7.Calls(), HasLen, 0) + + c.Check(newCmdV6.Calls(), HasLen, 1) + c.Check(newCmdV7.Calls(), HasLen, 1) +} diff --git a/overlord/snapstate/backend_test.go b/overlord/snapstate/backend_test.go index 2fc378c8d0..fbeb37f0a5 100644 --- a/overlord/snapstate/backend_test.go +++ b/overlord/snapstate/backend_test.go @@ -20,6 +20,7 @@ package snapstate_test import ( + "context" "errors" "fmt" "io" @@ -28,8 +29,6 @@ import ( "strings" "sync" - "golang.org/x/net/context" - "github.com/snapcore/snapd/asserts" "github.com/snapcore/snapd/osutil" "github.com/snapcore/snapd/overlord/auth" diff --git a/overlord/snapstate/catalogrefresh_test.go b/overlord/snapstate/catalogrefresh_test.go index 541d5b0e01..59d8fca731 100644 --- a/overlord/snapstate/catalogrefresh_test.go +++ b/overlord/snapstate/catalogrefresh_test.go @@ -20,14 +20,13 @@ package snapstate_test import ( + "context" "io" "io/ioutil" "os" "path/filepath" "time" - "golang.org/x/net/context" - . "gopkg.in/check.v1" "github.com/snapcore/snapd/advisor" diff --git a/overlord/snapstate/refreshhints_test.go b/overlord/snapstate/refreshhints_test.go index 916c0659d8..c8a33f8d00 100644 --- a/overlord/snapstate/refreshhints_test.go +++ b/overlord/snapstate/refreshhints_test.go @@ -20,10 +20,9 @@ package snapstate_test import ( + "context" "time" - "golang.org/x/net/context" - . "gopkg.in/check.v1" "github.com/snapcore/snapd/overlord/auth" diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go index 8209aa9fde..d21e65fd98 100644 --- a/overlord/snapstate/snapstate.go +++ b/overlord/snapstate/snapstate.go @@ -21,6 +21,7 @@ package snapstate import ( + "context" "encoding/json" "fmt" "os" @@ -28,8 +29,6 @@ import ( "strings" "time" - "golang.org/x/net/context" - "github.com/snapcore/snapd/asserts" "github.com/snapcore/snapd/boot" "github.com/snapcore/snapd/dirs" diff --git a/overlord/snapstate/snapstate_test.go b/overlord/snapstate/snapstate_test.go index 228adc7831..c910d80194 100644 --- a/overlord/snapstate/snapstate_test.go +++ b/overlord/snapstate/snapstate_test.go @@ -21,6 +21,7 @@ package snapstate_test import ( "bytes" + "context" "encoding/json" "errors" "fmt" @@ -32,8 +33,6 @@ import ( "testing" "time" - "golang.org/x/net/context" - . "gopkg.in/check.v1" "gopkg.in/tomb.v2" @@ -4346,7 +4345,33 @@ func (s *snapmgrTestSuite) TestUpdateNoStoreResults(c *C) { }) _, err := snapstate.Update(s.state, "some-snap", "channel-for-7", snap.R(0), s.user.ID, snapstate.Flags{}) - c.Assert(err, Equals, store.ErrNoUpdateAvailable) + c.Assert(err, Equals, snapstate.ErrMissingExpectedResult) +} + +func (s *snapmgrTestSuite) TestUpdateNoStoreResultsWithChannelChange(c *C) { + s.state.Lock() + defer s.state.Unlock() + + snapstate.ReplaceStore(s.state, noResultsStore{fakeStore: s.fakeStore}) + + // this is an atypical case in which the store didn't return + // an error nor a result, we are defensive and return + // a reasonable error + si := snap.SideInfo{ + RealName: "some-snap", + SnapID: "some-snap-id", + Revision: snap.R(7), + } + + snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ + Active: true, + Sequence: []*snap.SideInfo{&si}, + Channel: "channel-for-9", + Current: si.Revision, + }) + + _, err := snapstate.Update(s.state, "some-snap", "channel-for-7", snap.R(0), s.user.ID, snapstate.Flags{}) + c.Assert(err, Equals, snapstate.ErrMissingExpectedResult) } func (s *snapmgrTestSuite) TestUpdateSameRevisionSwitchesChannel(c *C) { diff --git a/overlord/snapstate/storehelpers.go b/overlord/snapstate/storehelpers.go index 3458c77862..f4eaf46fcc 100644 --- a/overlord/snapstate/storehelpers.go +++ b/overlord/snapstate/storehelpers.go @@ -20,11 +20,10 @@ package snapstate import ( + "context" "fmt" "sort" - "golang.org/x/net/context" - "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/overlord/auth" "github.com/snapcore/snapd/overlord/state" @@ -198,6 +197,8 @@ func preUpdateInfo(st *state.State, snapst *SnapState, amend bool, userID int) ( return curInfo, user, nil } +var ErrMissingExpectedResult = fmt.Errorf("unexpectedly empty response from the server (try again later)") + func singleActionResult(name, action string, results []*snap.Info, e error) (info *snap.Info, err error) { if len(results) > 1 { return nil, fmt.Errorf("internal error: multiple store results for a single snap op") @@ -225,12 +226,7 @@ func singleActionResult(name, action string, results []*snap.Info, e error) (inf // no result, atypical case if saErr.NoResults { - switch action { - case "refresh": - return nil, store.ErrNoUpdateAvailable - case "install": - return nil, store.ErrSnapNotFound - } + return nil, ErrMissingExpectedResult } } diff --git a/packaging/fedora/snapd.spec b/packaging/fedora/snapd.spec index 671a2c9ceb..a8e64fa729 100644 --- a/packaging/fedora/snapd.spec +++ b/packaging/fedora/snapd.spec @@ -112,7 +112,7 @@ ExclusiveArch: %{ix86} x86_64 %{arm} aarch64 ppc64le s390x %endif # If go_compiler is not set to 1, there is no virtual provide. Use golang instead. -BuildRequires: %{?go_compiler:compiler(go-compiler)}%{!?go_compiler:golang} +BuildRequires: %{?go_compiler:compiler(go-compiler)}%{!?go_compiler:golang} >= 1.9 BuildRequires: systemd %{?systemd_requires} @@ -164,8 +164,6 @@ BuildRequires: golang(golang.org/x/crypto/openpgp/armor) BuildRequires: golang(golang.org/x/crypto/openpgp/packet) BuildRequires: golang(golang.org/x/crypto/sha3) BuildRequires: golang(golang.org/x/crypto/ssh/terminal) -BuildRequires: golang(golang.org/x/net/context) -BuildRequires: golang(golang.org/x/net/context/ctxhttp) BuildRequires: golang(gopkg.in/check.v1) BuildRequires: golang(gopkg.in/macaroon.v1) BuildRequires: golang(gopkg.in/mgo.v2/bson) @@ -262,8 +260,6 @@ Requires: golang(golang.org/x/crypto/openpgp/armor) Requires: golang(golang.org/x/crypto/openpgp/packet) Requires: golang(golang.org/x/crypto/sha3) Requires: golang(golang.org/x/crypto/ssh/terminal) -Requires: golang(golang.org/x/net/context) -Requires: golang(golang.org/x/net/context/ctxhttp) Requires: golang(gopkg.in/check.v1) Requires: golang(gopkg.in/macaroon.v1) Requires: golang(gopkg.in/mgo.v2/bson) @@ -291,8 +287,6 @@ Provides: bundled(golang(golang.org/x/crypto/openpgp/armor)) Provides: bundled(golang(golang.org/x/crypto/openpgp/packet)) Provides: bundled(golang(golang.org/x/crypto/sha3)) Provides: bundled(golang(golang.org/x/crypto/ssh/terminal)) -Provides: bundled(golang(golang.org/x/net/context)) -Provides: bundled(golang(golang.org/x/net/context/ctxhttp)) Provides: bundled(golang(gopkg.in/check.v1)) Provides: bundled(golang(gopkg.in/macaroon.v1)) Provides: bundled(golang(gopkg.in/mgo.v2/bson)) @@ -535,6 +529,7 @@ install -d -p %{buildroot}%{_sharedstatedir}/snapd/device install -d -p %{buildroot}%{_sharedstatedir}/snapd/hostfs install -d -p %{buildroot}%{_sharedstatedir}/snapd/lib/gl install -d -p %{buildroot}%{_sharedstatedir}/snapd/lib/gl32 +install -d -p %{buildroot}%{_sharedstatedir}/snapd/lib/glvnd install -d -p %{buildroot}%{_sharedstatedir}/snapd/lib/vulkan install -d -p %{buildroot}%{_sharedstatedir}/snapd/mount install -d -p %{buildroot}%{_sharedstatedir}/snapd/seccomp/bpf @@ -723,6 +718,7 @@ popd %dir %{_sharedstatedir}/snapd/lib %dir %{_sharedstatedir}/snapd/lib/gl %dir %{_sharedstatedir}/snapd/lib/gl32 +%dir %{_sharedstatedir}/snapd/lib/glvnd %dir %{_sharedstatedir}/snapd/lib/vulkan %dir %{_sharedstatedir}/snapd/mount %dir %{_sharedstatedir}/snapd/seccomp diff --git a/packaging/opensuse/snapd.spec b/packaging/opensuse/snapd.spec index 7cf0f165aa..713922ed12 100644 --- a/packaging/opensuse/snapd.spec +++ b/packaging/opensuse/snapd.spec @@ -91,7 +91,7 @@ BuildRequires: autoconf BuildRequires: automake BuildRequires: glib2-devel BuildRequires: glibc-devel-static -BuildRequires: go +BuildRequires: go >= 1.9 BuildRequires: gpg2 BuildRequires: indent BuildRequires: libapparmor-devel @@ -274,7 +274,7 @@ rm -f %{buildroot}%{_libexecdir}/snapd/system-shutdown # Install the directories that snapd creates by itself so that they can be a part of the package install -d %{buildroot}%{_sharedstatedir}/snapd/{assertions,cookie,desktop/applications,device,hostfs,mount,apparmor/profiles,seccomp/bpf,snaps} -install -d %{buildroot}%{_sharedstatedir}/snapd/{lib/gl,lib/gl32,lib/vulkan} +install -d %{buildroot}%{_sharedstatedir}/snapd/{lib/gl,lib/gl32,lib/glvnd,lib/vulkan} install -d %{buildroot}%{_localstatedir}/cache/snapd install -d %{buildroot}%{_datadir}/polkit-1/actions install -d %{buildroot}%{snap_mount_dir}/bin @@ -372,6 +372,7 @@ fi %dir %{_sharedstatedir}/snapd/lib %dir %{_sharedstatedir}/snapd/lib/gl %dir %{_sharedstatedir}/snapd/lib/gl32 +%dir %{_sharedstatedir}/snapd/lib/glvnd %dir %{_sharedstatedir}/snapd/lib/vulkan %dir %{_localstatedir}/cache/snapd %dir %{_environmentdir} diff --git a/packaging/ubuntu-14.04/control b/packaging/ubuntu-14.04/control index 46f0bfd614..f5df3a8fc7 100644 --- a/packaging/ubuntu-14.04/control +++ b/packaging/ubuntu-14.04/control @@ -17,7 +17,7 @@ Build-Depends: autoconf, gettext, grub-common, gnupg2, - golang-any (>=2:1.6) | golang-1.6, + golang-any (>=2:1.10) | golang-1.10, indent, init-system-helpers, libcap-dev, diff --git a/packaging/ubuntu-14.04/rules b/packaging/ubuntu-14.04/rules index 2cac5f7a1b..3b3e59ca4b 100755 --- a/packaging/ubuntu-14.04/rules +++ b/packaging/ubuntu-14.04/rules @@ -20,7 +20,7 @@ export DH_GOLANG_GO_GENERATE=1 export PATH:=${PATH}:${CURDIR} # make sure that correct go version is found on trusty -export PATH:=/usr/lib/go-1.6/bin:${PATH} +export PATH:=/usr/lib/go-1.10/bin:${PATH} include /etc/os-release @@ -31,9 +31,6 @@ include /etc/os-release # from /lib/systemd/upstart. SYSTEMD_UNITS_DESTDIR="lib/systemd/upstart/" -# make sure that trusty's golang-1.6 is picked up correctly. -export PATH:=/usr/lib/go-1.6/bin:${PATH} - # The go tool does not fully support vendoring with gccgo, but we can # work around that by constructing the appropriate -I flag by hand. GCCGO := $(shell go tool dist env > /dev/null 2>&1 && echo no || echo yes) diff --git a/packaging/ubuntu-14.04/snapd.dirs b/packaging/ubuntu-14.04/snapd.dirs index 3add9c7a56..c66ae5cc02 100644 --- a/packaging/ubuntu-14.04/snapd.dirs +++ b/packaging/ubuntu-14.04/snapd.dirs @@ -8,6 +8,7 @@ var/lib/snapd/environment var/lib/snapd/firstboot var/lib/snapd/lib/gl var/lib/snapd/lib/gl32 +var/lib/snapd/lib/glvnd var/lib/snapd/lib/vulkan var/lib/snapd/snaps/partial var/lib/snapd/void diff --git a/packaging/ubuntu-16.04/control b/packaging/ubuntu-16.04/control index 25eb1e6273..fe61e7d594 100644 --- a/packaging/ubuntu-16.04/control +++ b/packaging/ubuntu-16.04/control @@ -17,7 +17,7 @@ Build-Depends: autoconf, gettext, grub-common, gnupg2, - golang-any (>=2:1.6) | golang-1.6, + golang-any (>=2:1.10) | golang-1.10, indent, init-system-helpers, libapparmor-dev, diff --git a/packaging/ubuntu-16.04/rules b/packaging/ubuntu-16.04/rules index ecc7f6bbb4..852a919398 100755 --- a/packaging/ubuntu-16.04/rules +++ b/packaging/ubuntu-16.04/rules @@ -18,7 +18,7 @@ export DH_GOLANG_GO_GENERATE=1 export PATH:=${PATH}:${CURDIR} # make sure that correct go version is found on trusty -export PATH:=/usr/lib/go-1.6/bin:${PATH} +export PATH:=/usr/lib/go-1.10/bin:${PATH} include /etc/os-release diff --git a/packaging/ubuntu-16.04/snapd.dirs b/packaging/ubuntu-16.04/snapd.dirs index 3add9c7a56..c66ae5cc02 100644 --- a/packaging/ubuntu-16.04/snapd.dirs +++ b/packaging/ubuntu-16.04/snapd.dirs @@ -8,6 +8,7 @@ var/lib/snapd/environment var/lib/snapd/firstboot var/lib/snapd/lib/gl var/lib/snapd/lib/gl32 +var/lib/snapd/lib/glvnd var/lib/snapd/lib/vulkan var/lib/snapd/snaps/partial var/lib/snapd/void diff --git a/parts/plugins/x_builddeb.py b/parts/plugins/x_builddeb.py index f2604fbab6..d12fe699ee 100644 --- a/parts/plugins/x_builddeb.py +++ b/parts/plugins/x_builddeb.py @@ -45,6 +45,9 @@ class XBuildDeb(snapcraft.BasePlugin): # XXX: get this from "debian/gbp.conf:postexport" self.run(["./get-deps.sh", "--skip-unused-check"]) env=os.environ.copy() + # ensure build with go-1.10 if available + if os.path.exists("/usr/lib/go-1.10/bin"): + env["PATH"] = "/usr/lib/go-1.10/bin:{}".format(env["PATH"]) if os.getuid() == 0: # disable running the tests during the build when run as root # because quite a few of them will break diff --git a/run-checks b/run-checks index 11cc19500c..dc3deb5d49 100755 --- a/run-checks +++ b/run-checks @@ -16,6 +16,9 @@ else fi COVERMODE=${COVERMODE:-atomic} +# ensure we use go-1.10 by default +export PATH=/usr/lib/go-1.10/bin:${PATH} + # add workaround for https://github.com/golang/go/issues/24449 if [ "$(uname -m)" = "s390x" ]; then if go version | grep -q go1.10; then diff --git a/spread.yaml b/spread.yaml index 14046cee59..f7c4f79619 100644 --- a/spread.yaml +++ b/spread.yaml @@ -80,7 +80,7 @@ backends: workers: 4 manual: true - fedora-29-64: - workers: 4 + workers: 6 - opensuse-42.3-64: workers: 4 # golang stack cannot compile anything, needs investigation @@ -117,8 +117,6 @@ backends: workers: 1 - fedora-29-64: workers: 1 - # https://twitter.com/zygoon/status/1073629342884864000 - manual: true - arch-linux-64: workers: 1 - amazon-linux-2-64: @@ -138,10 +136,11 @@ backends: workers: 4 - ubuntu-16.04-64: workers: 4 - - ubuntu-17.10-64: - workers: 4 - ubuntu-18.04-64: workers: 4 + - ubuntu-18.10-64: + image: ubuntu-1810 + workers: 4 google-nested: type: google @@ -474,7 +473,7 @@ repack: | tar c current.delta >&4 fi -kill-timeout: 20m +kill-timeout: 30m prepare: | # NOTE: This part of the code needs to be in spread.yaml as it runs before diff --git a/store/download_test.go b/store/download_test.go index d97e633371..13d21a0280 100644 --- a/store/download_test.go +++ b/store/download_test.go @@ -21,6 +21,7 @@ package store_test import ( "bytes" + "context" "crypto" "errors" "fmt" @@ -33,7 +34,6 @@ import ( "time" "github.com/juju/ratelimit" - "golang.org/x/net/context" . "gopkg.in/check.v1" "gopkg.in/retry.v1" diff --git a/store/export_test.go b/store/export_test.go index 2537b45e5a..e0e326b67e 100644 --- a/store/export_test.go +++ b/store/export_test.go @@ -22,11 +22,11 @@ package store import ( "io" + "context" "net/http" "net/url" "github.com/juju/ratelimit" - "golang.org/x/net/context" "gopkg.in/retry.v1" "github.com/snapcore/snapd/overlord/auth" diff --git a/store/store.go b/store/store.go index a27151a9e4..b4936ea711 100644 --- a/store/store.go +++ b/store/store.go @@ -22,6 +22,7 @@ package store import ( "bytes" + "context" "crypto" "encoding/base64" "encoding/json" @@ -40,8 +41,6 @@ import ( "time" "github.com/juju/ratelimit" - "golang.org/x/net/context" - "golang.org/x/net/context/ctxhttp" "gopkg.in/retry.v1" "github.com/snapcore/snapd/arch" @@ -763,13 +762,11 @@ func (s *Store) doRequest(ctx context.Context, client *http.Client, reqOptions * if err != nil { return nil, err } - - var resp *http.Response if ctx != nil { - resp, err = ctxhttp.Do(ctx, client, req) - } else { - resp, err = client.Do(req) + req = req.WithContext(ctx) } + + resp, err := client.Do(req) if err != nil { return nil, err } diff --git a/store/store_test.go b/store/store_test.go index b1a79ee649..70d51e96f7 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -21,6 +21,7 @@ package store_test import ( "bytes" + "context" "crypto" "encoding/json" "fmt" @@ -37,7 +38,6 @@ import ( "time" "golang.org/x/crypto/sha3" - "golang.org/x/net/context" . "gopkg.in/check.v1" "gopkg.in/macaroon.v1" "gopkg.in/retry.v1" diff --git a/store/storetest/storetest.go b/store/storetest/storetest.go index 82556655b8..fde4c14cf3 100644 --- a/store/storetest/storetest.go +++ b/store/storetest/storetest.go @@ -20,10 +20,9 @@ package storetest import ( + "context" "io" - "golang.org/x/net/context" - "github.com/snapcore/snapd/asserts" "github.com/snapcore/snapd/client" "github.com/snapcore/snapd/overlord/auth" diff --git a/tests/lib/best_golang.py b/tests/lib/best_golang.py new file mode 100755 index 0000000000..862a12b1cf --- /dev/null +++ b/tests/lib/best_golang.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 + +import apt +import re + +best_golang=None +for p in apt.Cache(): + if re.match(r"golang-([0-9.]+)$", p.name): + if best_golang is None or apt.apt_pkg.version_compare(best_golang.candidate.version, p.candidate.version) < 0: + best_golang = p + +print(best_golang.name) diff --git a/tests/lib/prepare-restore.sh b/tests/lib/prepare-restore.sh index d516d8f357..162cccd695 100755 --- a/tests/lib/prepare-restore.sh +++ b/tests/lib/prepare-restore.sh @@ -4,9 +4,6 @@ set -x # statements we execute stops the build. The code is not (yet) written to # handle errors in general. set -e -# Set pipefail option so that "foo | bar" behaves with fewer surprises by -# failing if foo fails, not just if bar fails. -set -o pipefail # shellcheck source=tests/lib/quiet.sh . "$TESTSLIB/quiet.sh" @@ -332,7 +329,20 @@ prepare_project() { case "$SPREAD_SYSTEM" in debian-*|ubuntu-*) # in 16.04: apt build-dep -y ./ + if [[ "$SPREAD_SYSTEM" == debian-9-* ]]; then + best_golang="$(python3 ./tests/lib/best_golang.py)" + test -n "$best_golang" + sed -i -e "s/golang-1.10/$best_golang/" ./debian/control + else + best_golang=golang-1.10 + fi gdebi --quiet --apt-line ./debian/control | quiet xargs -r apt-get install -y + # The go 1.10 backport is not using alternatives or anything else so + # we need to get it on path somehow. This is not perfect but simple. + if [ -z "$(command -v go)" ]; then + # the path filesystem path is: /usr/lib/go-1.10/bin + ln -s "/usr/lib/${best_golang/lang/}/bin/go" /usr/bin/go + fi ;; esac diff --git a/tests/lib/snaps/test-snapd-dbus-consumer/consumer.py b/tests/lib/snaps/test-snapd-dbus-consumer/consumer.py index 8cd3d2a992..a5139d06c1 100755 --- a/tests/lib/snaps/test-snapd-dbus-consumer/consumer.py +++ b/tests/lib/snaps/test-snapd-dbus-consumer/consumer.py @@ -2,9 +2,13 @@ import dbus import sys -def run(): - obj = dbus.SessionBus().get_object("com.dbustest.HelloWorld", "/com/dbustest/HelloWorld") +def run(bus): + obj = bus.get_object("com.dbustest.HelloWorld", "/com/dbustest/HelloWorld") print(obj.SayHello(dbus_interface="com.dbustest.HelloWorld")) if __name__ == "__main__": - sys.exit(run()) + if sys.argv[1] == "system": + bus = dbus.SystemBus() + else: + bus = dbus.SessionBus() + run(bus) diff --git a/tests/lib/snaps/test-snapd-dbus-consumer/snapcraft.yaml b/tests/lib/snaps/test-snapd-dbus-consumer/snapcraft.yaml index fe1699f5ba..50595078c0 100644 --- a/tests/lib/snaps/test-snapd-dbus-consumer/snapcraft.yaml +++ b/tests/lib/snaps/test-snapd-dbus-consumer/snapcraft.yaml @@ -6,13 +6,20 @@ description: A basic snap declaring a plug on dbus apps: dbus-consumer: plugs: [dbus-test] - command: bin/consumer + command: bin/consumer session + dbus-system-consumer: + plugs: [dbus-system-test] + command: bin/consumer system plugs: dbus-test: interface: dbus bus: session name: com.dbustest.HelloWorld + dbus-system-test: + interface: dbus + bus: system + name: com.dbustest.HelloWorld parts: consumer: diff --git a/tests/lib/snaps/test-snapd-dbus-provider/consumer.py b/tests/lib/snaps/test-snapd-dbus-provider/consumer.py new file mode 100755 index 0000000000..a5139d06c1 --- /dev/null +++ b/tests/lib/snaps/test-snapd-dbus-provider/consumer.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +import dbus +import sys + +def run(bus): + obj = bus.get_object("com.dbustest.HelloWorld", "/com/dbustest/HelloWorld") + print(obj.SayHello(dbus_interface="com.dbustest.HelloWorld")) + +if __name__ == "__main__": + if sys.argv[1] == "system": + bus = dbus.SystemBus() + else: + bus = dbus.SessionBus() + run(bus) diff --git a/tests/lib/snaps/test-snapd-dbus-provider/provider.py b/tests/lib/snaps/test-snapd-dbus-provider/provider.py index 867939041c..21f32274df 100644 --- a/tests/lib/snaps/test-snapd-dbus-provider/provider.py +++ b/tests/lib/snaps/test-snapd-dbus-provider/provider.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import sys from gi.repository import GLib import dbus import dbus.service @@ -8,8 +9,7 @@ from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) class DBusProvider(dbus.service.Object): - def __init__(self): - bus = dbus.SessionBus() + def __init__(self, bus): bus_name = dbus.service.BusName("com.dbustest.HelloWorld", bus=bus) dbus.service.Object.__init__(self, bus_name, "/com/dbustest/HelloWorld") @@ -19,6 +19,13 @@ class DBusProvider(dbus.service.Object): return "hello world" if __name__ == "__main__": - DBusProvider() + if sys.argv[1] == "system": + bus = dbus.SystemBus() + elif sys.argv[1] == "session": + bus = dbus.SessionBus() + else: + print("unknown bus: %s", sys.argv[1:]) + sys.exit(1) + DBusProvider(bus) loop = GLib.MainLoop() loop.run() diff --git a/tests/lib/snaps/test-snapd-dbus-provider/snapcraft.yaml b/tests/lib/snaps/test-snapd-dbus-provider/snapcraft.yaml index ec3db0f6cf..0f5ca5d212 100644 --- a/tests/lib/snaps/test-snapd-dbus-provider/snapcraft.yaml +++ b/tests/lib/snaps/test-snapd-dbus-provider/snapcraft.yaml @@ -5,14 +5,40 @@ description: A basic snap declaring a dbus slot apps: provider: - command: wrapper + command: wrapper session slots: [dbus-test] + system-provider: + command: wrapper system + daemon: simple + slots: [dbus-system-test] + # provide an in-snap consumer as well for testing + consumer: + command: consumer.py session + plugs: [dbus-test-plug] + system-consumer: + command: consumer.py system + plugs: [dbus-system-test-plug] + + +plugs: + dbus-test-plug: + interface: dbus + bus: session + name: com.dbustest.HelloWorld + dbus-system-test-plug: + interface: dbus + bus: system + name: com.dbustest.HelloWorld slots: dbus-test: interface: dbus bus: session name: com.dbustest.HelloWorld + dbus-system-test: + interface: dbus + bus: system + name: com.dbustest.HelloWorld parts: provider: diff --git a/tests/lib/snaps/test-snapd-dbus-provider/wrapper b/tests/lib/snaps/test-snapd-dbus-provider/wrapper index 5398d19960..7423e88f82 100755 --- a/tests/lib/snaps/test-snapd-dbus-provider/wrapper +++ b/tests/lib/snaps/test-snapd-dbus-provider/wrapper @@ -1,3 +1,8 @@ -export GI_TYPELIB_PATH=$SNAP/usr/lib/girepository-1.0:$(ls -d $SNAP/usr/lib/*/girepository-1.0) +#!/bin/sh -$SNAP/usr/bin/python3 $SNAP/provider.py +set -e + +GI_TYPELIB_PATH="$SNAP"/usr/lib/girepository-1.0:"$(ls -d "$SNAP"/usr/lib/*/girepository-1.0)" +export GI_TYPELIB_PATH + +"$SNAP"/usr/bin/python3 "$SNAP"/provider.py "$@" diff --git a/tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml b/tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml index aee7bb07ca..e4ab7d3a9c 100644 --- a/tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml +++ b/tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml @@ -23,6 +23,9 @@ apps: avahi-observe: command: bin/run plugs: [ avahi-observe ] + block-devices: + command: bin/run + plugs: [ block-devices ] bluetooth-control: command: bin/run plugs: [ bluetooth-control ] @@ -83,6 +86,9 @@ apps: device-buttons: command: bin/run plugs: [ device-buttons ] + display-control: + command: bin/run + plugs: [ display-control ] docker: command: bin/run plugs: [ docker ] @@ -326,6 +332,9 @@ apps: tpm: command: bin/run plugs: [ tpm ] + u2f-devices: + command: bin/run + plugs: [ u2f-devices ] ubuntu-download-manager: command: bin/run plugs: [ ubuntu-download-manager ] diff --git a/tests/main/auth-errors/task.yaml b/tests/main/auth-errors/task.yaml index ceefb4089c..d808146980 100644 --- a/tests/main/auth-errors/task.yaml +++ b/tests/main/auth-errors/task.yaml @@ -12,7 +12,7 @@ restore: | execute: | echo "An unauthenticated user cannot install snaps" - if su - -c "snap install test-snapd-tools" test 2>"${PWD}/install.output" ; then + if su - -c "snap install test-snapd-tools" test 2> install.output; then echo "Expected error installing snap from unauthenticated account" exit 1 fi @@ -20,7 +20,7 @@ execute: | [ "$(cat install.output)" = "$expected" ] echo "An unauthenticated user cannot connect plugs to slots" - if su - -c "snap connect foo:bar baz:fromp" test 2>"${PWD}/connect.output" ; then + if su - -c "snap connect foo:bar baz:fromp" test 2> connect.output; then echo "Expected error connecting plugs to slots from unauthenticated account" exit 1 fi diff --git a/tests/main/install-snaps/task.yaml b/tests/main/install-snaps/task.yaml index a387010649..36962c98ef 100644 --- a/tests/main/install-snaps/task.yaml +++ b/tests/main/install-snaps/task.yaml @@ -97,11 +97,11 @@ execute: | CHANNELS="stable candidate beta edge" for CHANNEL in $CHANNELS; do # shellcheck disable=SC2153 - if ! CHANNEL_INFO="$(snap info "$SNAP" | grep " $CHANNEL: ")"; then + if ! CHANNEL_INFO="$(snap info --unicode=never "$SNAP" | grep " $CHANNEL: ")"; then echo "Snap $SNAP not found" exit fi - if echo "$CHANNEL_INFO" | MATCH "$CHANNEL:.*–"; then + if echo "$CHANNEL_INFO" | MATCH "$CHANNEL:.*--"; then continue fi diff --git a/tests/main/interfaces-autopilot-introspection/task.yaml b/tests/main/interfaces-autopilot-introspection/task.yaml index 6d02073f8d..8a4f5c3baf 100644 --- a/tests/main/interfaces-autopilot-introspection/task.yaml +++ b/tests/main/interfaces-autopilot-introspection/task.yaml @@ -58,25 +58,23 @@ execute: | echo "And the snap app state can be intrsopected" $SNAP_MOUNT_DIR/bin/test-snapd-autopilot-consumer.consumer GetState | MATCH "my-ap-state" - if [ "$(snap debug confinement)" = none ]; then + if [ "$(snap debug confinement)" = partial ] ; then exit 0 fi - if [ "$(snap debug confinement)" = strict ] ; then - echo "When the plug is disconnected" - snap disconnect test-snapd-autopilot-consumer:autopilot-introspection + echo "When the plug is disconnected" + snap disconnect test-snapd-autopilot-consumer:autopilot-introspection - echo "Then the snap version is not introspectable" - if $SNAP_MOUNT_DIR/bin/test-snapd-autopilot-consumer.consumer GetVersion 2>"${PWD}"/getversion.error ; then - echo "Expected permission error trying to introspect version with disconnected plug" - exit 1 - fi - MATCH "Permission denied" < getversion.error + echo "Then the snap version is not introspectable" + if $SNAP_MOUNT_DIR/bin/test-snapd-autopilot-consumer.consumer GetVersion 2> getversion.error ; then + echo "Expected permission error trying to introspect version with disconnected plug" + exit 1 + fi + MATCH "Permission denied" < getversion.error - echo "And the snap state is not introspectable" - if $SNAP_MOUNT_DIR/bin/test-snapd-autopilot-consumer.consumer GetState 2>"${PWD}"/getstate.error; then - echo "Expected permission error trying to introspect state with disconnected plug" - exit 1 - fi - MATCH "Permission denied" < getstate.error + echo "And the snap state is not introspectable" + if $SNAP_MOUNT_DIR/bin/test-snapd-autopilot-consumer.consumer GetState 2> getstate.error; then + echo "Expected permission error trying to introspect state with disconnected plug" + exit 1 fi + MATCH "Permission denied" < getstate.error diff --git a/tests/main/interfaces-avahi-observe/task.yaml b/tests/main/interfaces-avahi-observe/task.yaml index da475fa6ca..7fea3c723a 100644 --- a/tests/main/interfaces-avahi-observe/task.yaml +++ b/tests/main/interfaces-avahi-observe/task.yaml @@ -26,18 +26,23 @@ execute: | echo "Then the plug is disconnected by default" snap interfaces -i avahi-observe | MATCH '^\- +generic-consumer:avahi-observe' - if [ "$(snap debug confinement)" = strict ] ; then - echo "And the snap is not able to access avahi provided info" - if avahi_dbus_call 2>avahi.error; then - echo "Expected error with disconnected plug didn't happen" - exit 1 - fi - MATCH "org.freedesktop.DBus.Error.AccessDenied" < avahi.error - fi - echo "When the plug is connected" snap connect generic-consumer:avahi-observe echo "Then the snap is able to access avahi provided info" hostname=$(hostname) avahi_dbus_call | MATCH "$hostname" + + if [ "$(snap debug confinement)" = partial ]; then + exit 0 + fi + + echo "When the plug is disconnected" + snap disconnect generic-consumer:avahi-observe + + echo "And the snap is not able to access avahi provided info" + if avahi_dbus_call 2> avahi.error; then + echo "Expected error with disconnected plug didn't happen" + exit 1 + fi + MATCH "org.freedesktop.DBus.Error.AccessDenied" < avahi.error diff --git a/tests/main/interfaces-bluetooth-control/task.yaml b/tests/main/interfaces-bluetooth-control/task.yaml index 52ac631c5c..d83eeb5f66 100644 --- a/tests/main/interfaces-bluetooth-control/task.yaml +++ b/tests/main/interfaces-bluetooth-control/task.yaml @@ -16,29 +16,6 @@ execute: | #shellcheck disable=SC1117 snap interfaces -i bluetooth-control | MATCH "^\- +generic-consumer:bluetooth-control" - if [ "$(snap debug confinement)" = strict ] ; then - echo "And the snap is not able to read usb" - if su -l -c "/snap/bin/generic-consumer.cmd cat /sys/bus/usb/drivers/btusb/module/version" test 2>"${PWD}/btusb.error"; then - echo "Expected error with disconnected plug didn't happen" - exit 1 - fi - MATCH "Permission denied" < btusb.error - - echo "And the snap is not able to read class" - if su -l -c "/snap/bin/generic-consumer.cmd cat /sys/class/bluetooth/*/name" test 2>"${PWD}/btclass.error"; then - echo "Expected error with disconnected plug didn't happen" - exit 1 - fi - MATCH "Permission denied" < btclass.error - - echo "And the snap is not able to read dev" - if su -l -c "/snap/bin/generic-consumer.cmd cat $BTDEV/*/power/control" test 2>"${PWD}/btdev-read.error"; then - echo "Expected error with disconnected plug didn't happen" - exit 1 - fi - MATCH "Permission denied" < btdev-read.error - fi - echo "When the plug is connected" snap connect generic-consumer:bluetooth-control @@ -55,3 +32,31 @@ execute: | echo "And the snap is able to read dev" cat "$BTDEV"/*/power/control | tee control [ "$(su -l -c '/snap/bin/generic-consumer.cmd cat '"$BTDEV"'/*/power/control' test)" = "$(cat control)" ] + + if [ "$(snap debug confinement)" = partial ] ; then + exit 0 + fi + + echo "When the plug is disconnected" + snap disconnect generic-consumer:bluetooth-control + + echo "And the snap is not able to read usb" + if su -l -c "/snap/bin/generic-consumer.cmd cat /sys/bus/usb/drivers/btusb/module/version" test 2> btusb.error; then + echo "Expected error with disconnected plug didn't happen" + exit 1 + fi + MATCH "Permission denied" < btusb.error + + echo "And the snap is not able to read class" + if su -l -c "/snap/bin/generic-consumer.cmd cat /sys/class/bluetooth/*/name" test 2> btclass.error; then + echo "Expected error with disconnected plug didn't happen" + exit 1 + fi + MATCH "Permission denied" < btclass.error + + echo "And the snap is not able to read dev" + if su -l -c "/snap/bin/generic-consumer.cmd cat $BTDEV/*/power/control" test 2> btdev-read.error; then + echo "Expected error with disconnected plug didn't happen" + exit 1 + fi + MATCH "Permission denied" < btdev-read.error diff --git a/tests/main/interfaces-broadcom-asic-control/task.yaml b/tests/main/interfaces-broadcom-asic-control/task.yaml index 766f48bcd4..7155b704c5 100644 --- a/tests/main/interfaces-broadcom-asic-control/task.yaml +++ b/tests/main/interfaces-broadcom-asic-control/task.yaml @@ -77,7 +77,7 @@ execute: | snap disconnect test-snapd-sh:broadcom-asic-control echo "Then the snap is not able to read the device" - if test-snapd-sh.with-broadcom-asic-control-plug -c "cat /dev/linux-bcm-knet" 2>"${PWD}"/call.error; then + if test-snapd-sh.with-broadcom-asic-control-plug -c "cat /dev/linux-bcm-knet" 2> call.error; then echo "Expected permission error accessing to device" exit 1 fi diff --git a/tests/main/interfaces-browser-support/task.yaml b/tests/main/interfaces-browser-support/task.yaml index 3ccaeeb39c..670ea73f34 100644 --- a/tests/main/interfaces-browser-support/task.yaml +++ b/tests/main/interfaces-browser-support/task.yaml @@ -106,62 +106,65 @@ execute: | done fi - if [ "$(snap debug confinement)" = strict ] ; then - if [ "$ALLOW_SANDBOX" = "false" ]; then - echo "And the policy has the ptrace suppression rule without sandbox" - MATCH '^deny ptrace \(trace\),' < /var/lib/snapd/apparmor/profiles/snap.browser-support-consumer.cmd - else - echo "And the policy has the ptrace suppression rule with sandbox" - MATCH '^deny ptrace \(trace\),' < /var/lib/snapd/apparmor/profiles/snap.browser-support-consumer.cmd && echo "Found ptrace rule, but shouldn't have" && exit 1 - fi + if [ "$(snap debug confinement)" = partial ] ; then + exit 0 + fi - echo "And the resources available with sandbox are not reachable without it" - if [ "$ALLOW_SANDBOX" = "false" ]; then - for readable_file in $READABLE_WITH_SANDBOX_FILES; do - if su -l -c "$SNAP_MOUNT_DIR/bin/browser-support-consumer.cmd cat $readable_file" test 2>"${PWD}/readable-without-sandbox-read.err"; then - echo "Expected error without sandbox didn't happen" - exit 1 - fi - MATCH "Permission denied" < readable-without-sandbox-read.err - done - fi + if [ "$ALLOW_SANDBOX" = "false" ]; then + echo "And the policy has the ptrace suppression rule without sandbox" + MATCH '^deny ptrace \(trace\),' < /var/lib/snapd/apparmor/profiles/snap.browser-support-consumer.cmd + else + echo "And the policy has the ptrace suppression rule with sandbox" + MATCH '^deny ptrace \(trace\),' < /var/lib/snapd/apparmor/profiles/snap.browser-support-consumer.cmd && echo "Found ptrace rule, but shouldn't have" && exit 1 + fi - echo "When the plug is disconnected" - snap disconnect browser-support-consumer:browser-support + echo "And the resources available with sandbox are not reachable without it" + if [ "$ALLOW_SANDBOX" = "false" ]; then + for readable_file in $READABLE_WITH_SANDBOX_FILES; do + if su -l -c "$SNAP_MOUNT_DIR/bin/browser-support-consumer.cmd cat $readable_file" test 2> readable-without-sandbox-read.err; then + echo "Expected error without sandbox didn't happen" + exit 1 + fi + MATCH "Permission denied" < readable-without-sandbox-read.err + done + fi - echo "Then the snap is not able to access tmp" - if su -l -c "$SNAP_MOUNT_DIR/bin/browser-support-consumer.cmd ls /var/tmp/" test 2>"${PWD}/tmpdir-access.err"; then + echo "When the plug is disconnected" + snap disconnect browser-support-consumer:browser-support + + echo "Then the snap is not able to access tmp" + if su -l -c "$SNAP_MOUNT_DIR/bin/browser-support-consumer.cmd ls /var/tmp/" test 2> tmpdir-access.err; then + echo "Expected error with disconnected plug didn't happen" + exit 1 + fi + MATCH "Permission denied" < tmpdir-access.err + if su -l -c "$SNAP_MOUNT_DIR/bin/browser-support-consumer.cmd cat /var/tmp/etilqs_test" test 2> tmpfile-read.err; then + echo "Expected error with disconnected plug didn't happen" + exit 1 + fi + MATCH "Permission denied" < tmpfile-read.err + + for owned_file in $OWNED_FILES; do + if su -l -c "$SNAP_MOUNT_DIR/bin/browser-support-consumer.cmd cat $owned_file" test 2> owned-read.err; then echo "Expected error with disconnected plug didn't happen" exit 1 fi - MATCH "Permission denied" < tmpdir-access.err - if su -l -c "$SNAP_MOUNT_DIR/bin/browser-support-consumer.cmd cat /var/tmp/etilqs_test" test 2>"${PWD}/tmpfile-read.err"; then + MATCH "Permission denied" < owned-read.err + done + for readable_file in $READABLE_FILES; do + if [ -f "$readable_file" ] && su -l -c "$SNAP_MOUNT_DIR/bin/browser-support-consumer.cmd cat $readable_file" test 2> readable-read.err; then echo "Expected error with disconnected plug didn't happen" exit 1 fi - MATCH "Permission denied" < tmpfile-read.err - - for owned_file in $OWNED_FILES; do - if su -l -c "$SNAP_MOUNT_DIR/bin/browser-support-consumer.cmd cat $owned_file" test 2>"${PWD}/owned-read.err"; then - echo "Expected error with disconnected plug didn't happen" - exit 1 - fi - MATCH "Permission denied" < owned-read.err - done - for readable_file in $READABLE_FILES; do - if [ -f "$readable_file" ] && su -l -c "$SNAP_MOUNT_DIR/bin/browser-support-consumer.cmd cat $readable_file" test 2>"${PWD}/readable-read.err"; then + MATCH "Permission denied" < readable-read.err + done + if [ "$ALLOW_SANDBOX" = "true" ]; then + for readable_file in $READABLE_WITH_SANDBOX_FILES; do + if su -l -c "$SNAP_MOUNT_DIR/bin/browser-support-consumer.cmd cat $readable_file" test 2> readable-with-sandbox-read.err; then echo "Expected error with disconnected plug didn't happen" exit 1 fi - MATCH "Permission denied" < readable-read.err + MATCH "Permission denied" < readable-with-sandbox-read.err done - if [ "$ALLOW_SANDBOX" = "true" ]; then - for readable_file in $READABLE_WITH_SANDBOX_FILES; do - if su -l -c "$SNAP_MOUNT_DIR/bin/browser-support-consumer.cmd cat $readable_file" test 2>"${PWD}/readable-with-sandbox-read.err"; then - echo "Expected error with disconnected plug didn't happen" - exit 1 - fi - MATCH "Permission denied" < readable-with-sandbox-read.err - done - fi fi + diff --git a/tests/main/interfaces-contacts-service/task.yaml b/tests/main/interfaces-contacts-service/task.yaml index 3109e931fa..4e2036e49e 100644 --- a/tests/main/interfaces-contacts-service/task.yaml +++ b/tests/main/interfaces-contacts-service/task.yaml @@ -65,29 +65,12 @@ execute: | END:VCARD EOF - test-snapd-eds.contacts load test-address-book << EOF - BEGIN:VCARD - VERSION:3.0 - FN:John Doe - N:Doe;John;;; - EMAIL;type=WORK:john@example.com - END:VCARD - EOF - echo "We can also retrieve those contacts" # Filter out ID and revision, which are unpredictable test-snapd-eds.contacts list test-address-book | sed -E 's/^(UID|REV):.*/\1:.../' > /tmp/contacts.vcf diff -uw - /tmp/contacts.vcf << EOF BEGIN:VCARD VERSION:3.0 - FN:John Doe - N:Doe;John;;; - EMAIL;type=WORK:john@example.com - UID:... - REV:... - END:VCARD - BEGIN:VCARD - VERSION:3.0 FN:Fred Smith N:Smith;Fred;;; EMAIL;type=HOME:fred@example.org diff --git a/tests/main/interfaces-dbus/task.yaml b/tests/main/interfaces-dbus/task.yaml index b7723b176e..53b4f2c7b1 100644 --- a/tests/main/interfaces-dbus/task.yaml +++ b/tests/main/interfaces-dbus/task.yaml @@ -18,10 +18,10 @@ prepare: | . "$TESTSLIB/dirs.sh" echo "Give a snap declaring a dbus slot in installed" - snap install --edge test-snapd-dbus-provider + snap install --beta test-snapd-dbus-provider echo "And a snap declaring a matching dbus plug is installed" - snap install --edge test-snapd-dbus-consumer + snap install --beta test-snapd-dbus-consumer echo "And the provider dbus loop is started" #shellcheck source=tests/lib/dbus.sh @@ -45,19 +45,22 @@ execute: | echo "And plug is disconnected by default" snap interfaces -i dbus | MATCH '^- +test-snapd-dbus-consumer:dbus-test' + echo "When the plug is connected" + snap connect test-snapd-dbus-consumer:dbus-test test-snapd-dbus-provider:dbus-test + + echo "Then the consumer is able to call the provided method" + test-snapd-dbus-consumer.dbus-consumer | MATCH "hello world" + if [ "$(snap debug confinement)" = partial ]; then exit 0 fi - echo "And the consumer is not able to access the provided method" + echo "When the plug is disconnected" + snap disconnect test-snapd-dbus-consumer:dbus-test test-snapd-dbus-provider:dbus-test + + echo "The consumer is not able to access the provided method" if test-snapd-dbus-consumer.dbus-consumer 2> call.error; then echo "Expected permission error calling dbus method with disconnected plug" exit 1 fi MATCH "Permission denied" < call.error - - echo "When the plug is connected" - snap connect test-snapd-dbus-consumer:dbus-test test-snapd-dbus-provider:dbus-test - - echo "Then the consumer is able to call the provided method" - test-snapd-dbus-consumer.dbus-consumer | MATCH "hello world" diff --git a/tests/main/interfaces-device-buttons/task.yaml b/tests/main/interfaces-device-buttons/task.yaml index 5a4dbf422d..d7261a8cc0 100644 --- a/tests/main/interfaces-device-buttons/task.yaml +++ b/tests/main/interfaces-device-buttons/task.yaml @@ -49,7 +49,7 @@ execute: | snap disconnect test-snapd-sh:device-buttons echo "Then the snap is not able to read the input device" - if test-snapd-sh.with-device-buttons-plug -c "cat /dev/input/event1" 2>"${PWD}"/call.error; then + if test-snapd-sh.with-device-buttons-plug -c "cat /dev/input/event1" 2> call.error; then echo "Expected permission error accessing to input device" exit 1 fi diff --git a/tests/main/interfaces-dvb/task.yaml b/tests/main/interfaces-dvb/task.yaml index d25dadf27e..83987c3b82 100644 --- a/tests/main/interfaces-dvb/task.yaml +++ b/tests/main/interfaces-dvb/task.yaml @@ -35,13 +35,13 @@ execute: | test-snapd-sh.with-dvb-plug -c "echo test >> /dev/dvb/adapter9/video9" test-snapd-sh.with-dvb-plug -c "cat /run/udev/data/c212:9" - echo "When the plug is disconnected" - snap disconnect test-snapd-sh:dvb - if [ "$(snap debug confinement)" = partial ]; then exit 0 fi + echo "When the plug is disconnected" + snap disconnect test-snapd-sh:dvb + echo "Then the snap is not able to read the input device" if test-snapd-sh.with-dvb-plug -c 'cat /dev/dvb/adapter9/video9' 2>call.error; then echo "Expected permission error accessing to input device" diff --git a/tests/main/interfaces-fuse_support/task.yaml b/tests/main/interfaces-fuse_support/task.yaml index 8399d984a1..11b07062f3 100644 --- a/tests/main/interfaces-fuse_support/task.yaml +++ b/tests/main/interfaces-fuse_support/task.yaml @@ -19,7 +19,7 @@ environment: MOUNT_POINT_OUTSIDE/regular: /var/snap/test-snapd-fuse-consumer/current/mount_point NAME/regular: test-snapd-fuse-consumer # snap with instance key 'foo' - MOUNT_POINT/parallel: /var/snap/test-snapd-fuse-consumer/current/mount_point + MOUNT_POINT/parallel: /var/snap/test-snapd-fuse-consumer_foo/current/mount_point MOUNT_POINT_OUTSIDE/parallel: /var/snap/test-snapd-fuse-consumer_foo/current/mount_point NAME/parallel: test-snapd-fuse-consumer_foo @@ -50,8 +50,8 @@ execute: | echo "The interface is disconnected by default" snap interfaces -i fuse-support | MATCH "^- +$NAME:fuse-support" - if [ "$(snap debug confinement)" = strict ] ; then - echo "Then the snap is not able to create a fuse file system" + if [ "$(snap debug confinement)" = strict ]; then + echo "The snap is not able to create a fuse file system with the plug disconnected" if "$NAME.create" -f "$MOUNT_POINT" 2> fuse.error; then echo "Expected permission error creating fuse filesystem with disconnected plug" exit 1 @@ -95,4 +95,3 @@ execute: | mountpath=$(nsenter "--mount=/run/snapd/ns/$NAME.mnt" cat /proc/mounts | \ grep "$(basename "$MOUNT_POINT") fuse" | cut -f2 -d' ') test -n "$mountpath" - diff --git a/tests/main/interfaces-hostname-control/task.yaml b/tests/main/interfaces-hostname-control/task.yaml index 0982a41d8d..11b9cb5b39 100644 --- a/tests/main/interfaces-hostname-control/task.yaml +++ b/tests/main/interfaces-hostname-control/task.yaml @@ -38,22 +38,22 @@ execute: | test-snapd-sh.with-hostname-control-plug -c "hostnamectl" | MATCH "$hostname" test-snapd-sh.with-hostname-control-plug -c "hostnamectl set-hostname $hostname" - echo "When the plug is disconnected" - snap disconnect test-snapd-sh:hostname-control - if [ "$(snap debug confinement)" = partial ]; then exit 0 fi + echo "When the plug is disconnected" + snap disconnect test-snapd-sh:hostname-control + echo "Then the hostname method cannot be accessed through dbus" - if test-snapd-sh.with-hostname-control-plug -c "hostnamectl set-hostname $hostname" 2>access.error; then + if test-snapd-sh.with-hostname-control-plug -c "hostnamectl set-hostname $hostname" 2> access.error; then echo "Expected permission error trying to set hostname with disconnected plug" exit 1 fi MATCH "Permission denied" < access.error echo "And the /etc/hostname file cannot be written" - if test-snapd-sh.with-hostname-control-plug -c "echo $hostname > /etc/hostname" 2>hostname.error; then + if test-snapd-sh.with-hostname-control-plug -c "echo $hostname > /etc/hostname" 2> hostname.error; then echo "Expected permission error trying to acess to /etc/hostname with disconnected plug" exit 1 fi diff --git a/tests/main/interfaces-joystick/task.yaml b/tests/main/interfaces-joystick/task.yaml index 714b480b1f..a1b5c0749e 100644 --- a/tests/main/interfaces-joystick/task.yaml +++ b/tests/main/interfaces-joystick/task.yaml @@ -59,7 +59,7 @@ execute: | snap disconnect test-snapd-sh:joystick echo "Then the snap is not able to read the input device" - if test-snapd-sh.with-joystick-plug -c "cat /dev/input/js31" 2>"${PWD}"/call.error; then + if test-snapd-sh.with-joystick-plug -c "cat /dev/input/js31" 2> call.error; then echo "Expected permission error accessing to input device" exit 1 fi diff --git a/tests/main/interfaces-juju-client-observe/task.yaml b/tests/main/interfaces-juju-client-observe/task.yaml index 9320b36095..db18c3bbb8 100644 --- a/tests/main/interfaces-juju-client-observe/task.yaml +++ b/tests/main/interfaces-juju-client-observe/task.yaml @@ -35,13 +35,13 @@ execute: | echo "Then the snap is able to access the juju client configuration" test-snapd-sh.with-juju-client-observe-plug -c "cat $HOME/.local/share/juju/juju.conf" - echo "When the plug is disconnected" - snap disconnect test-snapd-sh:juju-client-observe - if [ "$(snap debug confinement)" = partial ]; then exit 0 fi + echo "When the plug is disconnected" + snap disconnect test-snapd-sh:juju-client-observe + echo "Then the snap is not able to read the juju client configuration" if test-snapd-sh.with-juju-client-observe-plug -c "cat $HOME/.local/share/juju/juju.conf" 2>call.error; then echo "Expected permission error accessing to input device" diff --git a/tests/main/interfaces-kernel-module-control/task.yaml b/tests/main/interfaces-kernel-module-control/task.yaml index c472fcfd8b..f2cd9dc60c 100644 --- a/tests/main/interfaces-kernel-module-control/task.yaml +++ b/tests/main/interfaces-kernel-module-control/task.yaml @@ -64,7 +64,7 @@ execute: | generic-consumer.cmd ls /sys/module | MATCH "$MODULE" echo "And the snap is not able to write to /sys/module" - if su -l -c "generic-consumer.cmd touch /sys/module/test" test 2>"${PWD}/touch.error"; then + if su -l -c "generic-consumer.cmd touch /sys/module/test" test 2> touch.error; then echo "Expected permission error writing to /sys/module" exit 1 fi @@ -83,7 +83,7 @@ execute: | snap disconnect generic-consumer:kernel-module-control echo "Then the snap is not able to list modules" - if su -l -c "test-snapd-kernel-module-consumer.lsmod" test 2>"${PWD}/list.error"; then + if su -l -c "test-snapd-kernel-module-consumer.lsmod" test 2> list.error; then echo "Expected permission error listing modules with disconnected plug" exit 1 fi @@ -107,14 +107,14 @@ execute: | MATCH "Permission denied" < remove.error echo "And the snap is not able to read /sys/module" - if su -l -c "generic-consumer.cmd ls /sys/module" test 2>"${PWD}/read.error"; then + if su -l -c "generic-consumer.cmd ls /sys/module" test 2> read.error; then echo "Expected permission error reading /sys/module with disconnected plug" exit 1 fi MATCH "Permission denied" < read.error echo "And the snap is not able to write to /sys/module" - if su -l -c "generic-consumer.cmd touch /sys/module/test" test 2>"${PWD}/touch.error"; then + if su -l -c "generic-consumer.cmd touch /sys/module/test" test 2> touch.error; then echo "Expected permission error writing to /sys/module" exit 1 fi diff --git a/tests/main/interfaces-libvirt/task.yaml b/tests/main/interfaces-libvirt/task.yaml index 5337a3ef55..7b003ae868 100644 --- a/tests/main/interfaces-libvirt/task.yaml +++ b/tests/main/interfaces-libvirt/task.yaml @@ -62,7 +62,7 @@ execute: | snap disconnect test-snapd-libvirt-consumer:libvirt echo "Then the snap is not able to create a domain" - if su -l -c "test-snapd-libvirt-consumer.machine-up" test 2>"${PWD}/creation.error"; then + if su -l -c "test-snapd-libvirt-consumer.machine-up" test 2> creation.error; then echo "Expected permission error accessing libvirtd socket with disconnected plug" exit 1 fi diff --git a/tests/main/interfaces-locale-control/task.yaml b/tests/main/interfaces-locale-control/task.yaml index 6d2630867b..c684d1b568 100644 --- a/tests/main/interfaces-locale-control/task.yaml +++ b/tests/main/interfaces-locale-control/task.yaml @@ -70,7 +70,7 @@ execute: | snap disconnect locale-control-consumer:locale-control echo "Then the snap is not able to read the locale configuration" - if su -l -c "locale-control-consumer.get LANG" test 2>"${PWD}/locale-read.error"; then + if su -l -c "locale-control-consumer.get LANG" test 2> locale-read.error; then echo "Expected permission error accessing locale configuration with disconnected plug" exit 1 fi @@ -84,14 +84,16 @@ execute: | locale-control-consumer.set LANG mylang grep -q "LANG=\"mylang\"" /etc/default/locale - if [ "$(snap debug confinement)" = strict ] ; then - echo "When the plug is disconnected" - snap disconnect locale-control-consumer:locale-control + if [ "$(snap debug confinement)" = partial ] ; then + exit 0 + fi - echo "Then the snap is not able to read the locale configuration" - if locale-control-consumer.set LANG mysecondlang 2> locale-write.error; then - echo "Expected permission error accessing locale configuration with disconnected plug" - exit 1 - fi - grep -q "Permission denied" locale-write.error + echo "When the plug is disconnected" + snap disconnect locale-control-consumer:locale-control + + echo "Then the snap is not able to read the locale configuration" + if locale-control-consumer.set LANG mysecondlang 2> locale-write.error; then + echo "Expected permission error accessing locale configuration with disconnected plug" + exit 1 fi + grep -q "Permission denied" locale-write.error diff --git a/tests/main/interfaces-location-control/task.yaml b/tests/main/interfaces-location-control/task.yaml index 42552f510d..d18381c82b 100644 --- a/tests/main/interfaces-location-control/task.yaml +++ b/tests/main/interfaces-location-control/task.yaml @@ -55,7 +55,7 @@ execute: | snap disconnect test-snapd-location-control-provider:location-control test-snapd-location-control-provider:location-control-test echo "And the location provider props cannot be accessed" - if test-snapd-location-control-provider.consumer Get 2>"${PWD}"/getprop.error; then + if test-snapd-location-control-provider.consumer Get 2> getprop.error; then echo "Expected permission error trying to get props with disconnected plug" exit 1 fi diff --git a/tests/main/interfaces-mount-observe/task.yaml b/tests/main/interfaces-mount-observe/task.yaml index 0231d56313..b31a730631 100644 --- a/tests/main/interfaces-mount-observe/task.yaml +++ b/tests/main/interfaces-mount-observe/task.yaml @@ -32,17 +32,6 @@ execute: | expected="$SNAP_MOUNT_DIR/mount-observe-consumer" su -l -c "mount-observe-consumer" test | grep -Pq "$expected" - if [ "$(snap debug confinement)" = strict ] ; then - echo "When the plug is disconnected" - snap disconnect mount-observe-consumer:mount-observe - - echo "Then the mount info is not reachable" - if su -l -c "mount-observe-consumer" test; then - echo "Expected error accessing mount info with disconnected plug" - exit 1 - fi - fi - echo "When the plug is connected" snap connect mount-observe-consumer:mount-observe @@ -54,3 +43,16 @@ execute: | echo "Then the new mount info is reachable" expected="$SNAP_MOUNT_DIR/test-snapd-tools" su -l -c "mount-observe-consumer" test | grep -Pq "$expected" + + if [ "$(snap debug confinement)" = partial ] ; then + exit 0 + fi + + echo "When the plug is disconnected" + snap disconnect mount-observe-consumer:mount-observe + + echo "Then the mount info is not reachable" + if su -l -c "mount-observe-consumer" test; then + echo "Expected error accessing mount info with disconnected plug" + exit 1 + fi diff --git a/tests/main/interfaces-network-control-tuntap/task.yaml b/tests/main/interfaces-network-control-tuntap/task.yaml index 5c61433112..6af57ab3dd 100644 --- a/tests/main/interfaces-network-control-tuntap/task.yaml +++ b/tests/main/interfaces-network-control-tuntap/task.yaml @@ -31,13 +31,15 @@ execute: | echo "The interface is disconnected by default" snap interfaces -i network-control | MATCH -- '^- +test-snapd-tuntap:network-control$' - if [ "$(snap debug confinement)" = strict ] ; then - echo "And cannot allocate the $DEV device" - test-snapd-tuntap.tuntap "$DEV" 2>&1 | MATCH "Permission denied" - fi - echo "When the plug is connected" snap connect test-snapd-tuntap:network-control echo "Then the snap command can allocate the $DEV device" test-snapd-tuntap.tuntap "$DEV" | MATCH "PASS" + + if [ "$(snap debug confinement)" = partial ] ; then + exit 0 + fi + + echo "And cannot allocate the $DEV device" + test-snapd-tuntap.tuntap "$DEV" 2>&1 | MATCH "Permission denied" diff --git a/tests/main/interfaces-network-manager/task.yaml b/tests/main/interfaces-network-manager/task.yaml index abadb693cd..d6c61b2690 100644 --- a/tests/main/interfaces-network-manager/task.yaml +++ b/tests/main/interfaces-network-manager/task.yaml @@ -46,7 +46,7 @@ execute: | snap disconnect network-manager:nmcli echo "Then the consumer is not able to access the provided methods" - if network-manager.nmcli general 2>"${PWD}"/call.error; then + if network-manager.nmcli general 2> call.error; then echo "Expected permission error calling nmcli method with disconnected plug" exit 1 fi diff --git a/tests/main/interfaces-network-observe/task.yaml b/tests/main/interfaces-network-observe/task.yaml index 14a4b8cd61..c683d48cf2 100644 --- a/tests/main/interfaces-network-observe/task.yaml +++ b/tests/main/interfaces-network-observe/task.yaml @@ -48,13 +48,16 @@ execute: | echo "Then the snap command can query network status information" network-observe-consumer | MATCH "LISTEN.*:$PORT" - if [ "$(snap debug confinement)" = strict ] ; then - echo "When the plug is disconnected" - snap disconnect network-observe-consumer:network-observe - - echo "Then the snap command can not query network status information" - if network-observe-consumer 2>net-query.output; then - echo "Expected error caling command with disconnected plug" - fi - grep -Pq "Permission denied" < net-query.output + if [ "$(snap debug confinement)" = partial ] ; then + exit 0 fi + + echo "When the plug is disconnected" + snap disconnect network-observe-consumer:network-observe + + echo "Then the snap command can not query network status information" + if network-observe-consumer 2> net-query.output; then + echo "Expected error caling command with disconnected plug" + fi + grep -Pq "Permission denied" < net-query.output + diff --git a/tests/main/interfaces-network-status/task.yaml b/tests/main/interfaces-network-status/task.yaml index dc23975ab6..e681c54314 100644 --- a/tests/main/interfaces-network-status/task.yaml +++ b/tests/main/interfaces-network-status/task.yaml @@ -53,7 +53,7 @@ execute: | snap disconnect test-snapd-network-status-provider:network-status test-snapd-network-status-provider:network-status-test echo "And the snap state cannot be accessed" - if test-snapd-network-status-provider.consumer GetState 2>"${PWD}"/getstate.error; then + if test-snapd-network-status-provider.consumer GetState 2> getstate.error; then echo "Expected permission error trying to introspect state with disconnected plug" exit 1 fi diff --git a/tests/main/interfaces-openvswitch-support/task.yaml b/tests/main/interfaces-openvswitch-support/task.yaml index 07f3d834e3..756bbd5f28 100644 --- a/tests/main/interfaces-openvswitch-support/task.yaml +++ b/tests/main/interfaces-openvswitch-support/task.yaml @@ -36,7 +36,7 @@ execute: | snap disconnect test-snapd-openvswitch-support:openvswitch-support echo "Then the snap is not able to get a random uuid" - if test-snapd-openvswitch-support.random-uuid 2>"${PWD}"/call.error; then + if test-snapd-openvswitch-support.random-uuid 2> call.error; then echo "Expected permission error getting random uuid" exit 1 fi diff --git a/tests/main/interfaces-openvswitch/task.yaml b/tests/main/interfaces-openvswitch/task.yaml index 3ded271425..d06c810c22 100644 --- a/tests/main/interfaces-openvswitch/task.yaml +++ b/tests/main/interfaces-openvswitch/task.yaml @@ -79,7 +79,7 @@ execute: | snap disconnect test-snapd-openvswitch-consumer:openvswitch echo "Then the snap is not able to create a bridge" - if test-snapd-openvswitch-consumer.ovs-vsctl add-br br0 2>"${PWD}"/bridge-creation.error; then + if test-snapd-openvswitch-consumer.ovs-vsctl add-br br0 2> bridge-creation.error; then echo "Expected permission error accessing openvswitch socket with disconnected plug" exit 1 fi @@ -88,7 +88,7 @@ execute: | ovs-vsctl add-br br0 echo "And the snap is not able to create a port" - if test-snapd-openvswitch-consumer.ovs-vsctl add-port br0 tap1 2>"${PWD}"/port-creation.error; then + if test-snapd-openvswitch-consumer.ovs-vsctl add-port br0 tap1 2> port-creation.error; then echo "Expected permission error accessing openvswitch socket with disconnected plug" exit 1 fi @@ -97,14 +97,14 @@ execute: | ovs-vsctl add-port br0 tap1 echo "And the snap is not able to delete a port" - if test-snapd-openvswitch-consumer.ovs-vsctl del-port br0 tap1 2>"${PWD}"/port-deletion.error; then + if test-snapd-openvswitch-consumer.ovs-vsctl del-port br0 tap1 2> port-deletion.error; then echo "Expected permission error accessing openvswitch socket with disconnected plug" exit 1 fi MATCH 'database connection failed \(Permission denied\)' < port-deletion.error echo "And the snap is not able to delete a bridge" - if test-snapd-openvswitch-consumer.ovs-vsctl del-br br0 2>"${PWD}"/br-creation.error; then + if test-snapd-openvswitch-consumer.ovs-vsctl del-br br0 2> br-creation.error; then echo "Expected permission error accessing openvswitch socket with disconnected plug" exit 1 fi diff --git a/tests/main/interfaces-physical-memory-observe/task.yaml b/tests/main/interfaces-physical-memory-observe/task.yaml index 39c4885640..fd4e9a530e 100644 --- a/tests/main/interfaces-physical-memory-observe/task.yaml +++ b/tests/main/interfaces-physical-memory-observe/task.yaml @@ -41,7 +41,7 @@ execute: | snap disconnect test-snapd-physical-memory-observe:physical-memory-observe echo "Then the snap is not able to access the physical memory" - if test-snapd-physical-memory-observe.head 2>"${PWD}"/call.error; then + if test-snapd-physical-memory-observe.head 2> call.error; then echo "Expected permission error accessing to physical memory with disconnected plug" exit 1 fi diff --git a/tests/main/interfaces-process-control/task.yaml b/tests/main/interfaces-process-control/task.yaml index 3d725ecaa9..539bae1e53 100644 --- a/tests/main/interfaces-process-control/task.yaml +++ b/tests/main/interfaces-process-control/task.yaml @@ -45,7 +45,7 @@ execute: | echo "Then the snap is not able to kill an existing process" while :; do sleep 1; done & pid=$! - if process-control-consumer.signal SIGTERM $pid 2>"${PWD}"/process-kill.error; then + if process-control-consumer.signal SIGTERM $pid 2> process-kill.error; then echo "Expected permission error accessing killing a process with disconnected plug" exit 1 fi diff --git a/tests/main/interfaces-raw-usb/task.yaml b/tests/main/interfaces-raw-usb/task.yaml index 44136fb224..777164a752 100644 --- a/tests/main/interfaces-raw-usb/task.yaml +++ b/tests/main/interfaces-raw-usb/task.yaml @@ -42,7 +42,7 @@ execute: | snap disconnect test-snapd-sh:raw-usb echo "Then the snap is not able to check for USB devices" - if test-snapd-sh.with-raw-usb-plug -c "cat /sys/bus/usb/devices/usb1/serial" 2>"${PWD}"/call.error; then + if test-snapd-sh.with-raw-usb-plug -c "cat /sys/bus/usb/devices/usb1/serial" 2> call.error; then echo "Expected permission error accessing to check for USB devices" exit 1 fi diff --git a/tests/main/interfaces-removable-media/task.yaml b/tests/main/interfaces-removable-media/task.yaml index 722474282f..e6be78d62e 100644 --- a/tests/main/interfaces-removable-media/task.yaml +++ b/tests/main/interfaces-removable-media/task.yaml @@ -74,7 +74,7 @@ execute: | snap disconnect test-snapd-sh:removable-media echo "Then the snap is not able to access read the test media file" - if test-snapd-sh.with-removable-media-plug -c "ls /run" 2>"${PWD}"/call.error; then + if test-snapd-sh.with-removable-media-plug -c "ls /run" 2> call.error; then echo "Expected permission error accessing to removable storage filesystems" exit 1 fi diff --git a/tests/main/interfaces-shutdown-introspection/task.yaml b/tests/main/interfaces-shutdown-introspection/task.yaml index 9045dc71f5..0aa39b96b1 100644 --- a/tests/main/interfaces-shutdown-introspection/task.yaml +++ b/tests/main/interfaces-shutdown-introspection/task.yaml @@ -26,13 +26,13 @@ execute: | expected="<interface name=\"org.freedesktop.login1.Manager\">" su -l -c "shutdown-introspection-consumer" test | MATCH "$expected" - echo "When the plug is disconnected" - snap disconnect shutdown-introspection-consumer:shutdown - if [ "$(snap debug confinement)" = partial ]; then exit fi + echo "When the plug is disconnected" + snap disconnect shutdown-introspection-consumer:shutdown + echo "Then the snap is not able to get system information" if su -l -c "shutdown-introspection-consumer" test; then echo "Expected error with plug disconnected" diff --git a/tests/main/interfaces-snapd-control/task.yaml b/tests/main/interfaces-snapd-control/task.yaml index 218fb3bcc1..32ad0d2664 100644 --- a/tests/main/interfaces-snapd-control/task.yaml +++ b/tests/main/interfaces-snapd-control/task.yaml @@ -22,22 +22,21 @@ execute: | echo "The interface is connected by default" snap interfaces -i snapd-control | MATCH ":snapd-control .*test-snapd-control-consumer" - if [ "$(snap debug confinement)" = strict ] ; then - echo "When the plug is disconnected" - snap disconnect test-snapd-control-consumer:snapd-control - - echo "Then the snap command is not able to control snapd" - if test-snapd-control-consumer.list 2>snapd.error; then - echo "Expected error with plug disconnected" - exit 1 - fi - grep -q "Permission denied" snapd.error - fi - - echo "When the plug is connected" - snap connect test-snapd-control-consumer:snapd-control - echo "Then the snap command is able to control snapd" ! test-snapd-control-consumer.list | grep -q test-snapd-tools test-snapd-control-consumer.install test-snapd-tools while ! test-snapd-control-consumer.list | grep -q test-snapd-tools; do sleep 1; done + + if [ "$(snap debug confinement)" = partial ] ; then + exit 0 + fi + + echo "When the plug is disconnected" + snap disconnect test-snapd-control-consumer:snapd-control + + echo "Then the snap command is not able to control snapd" + if test-snapd-control-consumer.list 2> snapd.error; then + echo "Expected error with plug disconnected" + exit 1 + fi + grep -q "Permission denied" snapd.error diff --git a/tests/main/interfaces-ssh-keys/task.yaml b/tests/main/interfaces-ssh-keys/task.yaml index 54408d9931..fc02b17f66 100644 --- a/tests/main/interfaces-ssh-keys/task.yaml +++ b/tests/main/interfaces-ssh-keys/task.yaml @@ -50,7 +50,7 @@ execute: | snap disconnect test-snapd-sh:ssh-keys echo "Then the snap is not able to read a ssh private key" - if test-snapd-sh.with-ssh-keys-plug -c "cat $TESTKEY" 2>"${PWD}"/call.error; then + if test-snapd-sh.with-ssh-keys-plug -c "cat $TESTKEY" 2> call.error; then echo "Expected permission error accessing to ssh" exit 1 fi diff --git a/tests/main/interfaces-ssh-public-keys/task.yaml b/tests/main/interfaces-ssh-public-keys/task.yaml index 66e02aafd8..7c2a052eff 100644 --- a/tests/main/interfaces-ssh-public-keys/task.yaml +++ b/tests/main/interfaces-ssh-public-keys/task.yaml @@ -46,7 +46,7 @@ execute: | fi echo "And then the snap is not able to access to private keys" - if test-snapd-sh.with-ssh-public-keys-plug -c "cat $TESTKEY" 2>"${PWD}"/call.error; then + if test-snapd-sh.with-ssh-public-keys-plug -c "cat $TESTKEY" 2> call.error; then echo "Expected permission error accessing to ssh" exit 1 fi @@ -56,7 +56,7 @@ execute: | snap disconnect test-snapd-sh:ssh-public-keys echo "Then the snap is not able to access the ssh public keys" - if test-snapd-sh.with-ssh-public-keys-plug -c "cat $TESTKEY.pub" 2>"${PWD}"/call.error; then + if test-snapd-sh.with-ssh-public-keys-plug -c "cat $TESTKEY.pub" 2> call.error; then echo "Expected permission error accessing to ssh" exit 1 fi diff --git a/tests/main/interfaces-system-dbus/task.yaml b/tests/main/interfaces-system-dbus/task.yaml new file mode 100644 index 0000000000..7290c1772b --- /dev/null +++ b/tests/main/interfaces-system-dbus/task.yaml @@ -0,0 +1,50 @@ +summary: Ensure that the dbus interface works on the system bus. + +prepare: | + echo "Install dbus system test snaps" + snap install --edge test-snapd-dbus-provider + snap install --edge test-snapd-dbus-consumer + # we can only talk from an unconfied dbus-send to the service on classic, + # on ubuntu-core devices *all* dbus calls are mediated. + echo "Ensure the dbus service is up" + if ! grep -q ID=ubuntu-core /etc/os-release; then + dbus-send --system --print-reply --dest=com.dbustest.HelloWorld /com/dbustest/HelloWorld com.dbustest.HelloWorld.SayHello | MATCH "hello world" + fi + +execute: | + #shellcheck source=tests/lib/dirs.sh + . "$TESTSLIB/dirs.sh" + + echo "The dbus consumer plug is disconnected by default" + snap interfaces -i dbus | MATCH '^- +test-snapd-dbus-consumer:dbus-system-test' + + echo "When the plug is connected" + snap connect test-snapd-dbus-consumer:dbus-system-test test-snapd-dbus-provider:dbus-system-test + + echo "Then the consumer is able to call the provided method" + test-snapd-dbus-consumer.dbus-system-consumer | MATCH "hello world" + + echo "Also check if dbus works if plug/slot come from the same snap" + + echo "When the plug is connected (same snap)" + snap connect test-snapd-dbus-provider:dbus-system-test-plug test-snapd-dbus-provider:dbus-system-test + echo "Then the consumer (same snap) is able to call the provided method" + test-snapd-dbus-provider.system-consumer | MATCH "hello world" + echo "And the connection to the other snap still works" + test-snapd-dbus-consumer.dbus-system-consumer | MATCH "hello world" + + if [ "$(snap debug confinement)" = partial ] ; then + exit 0 + fi + + echo "When the plugs are disconnected" + snap disconnect test-snapd-dbus-consumer:dbus-system-test + snap disconnect test-snapd-dbus-provider:dbus-system-test-plug + + echo "And the consumer is not able to access the provided method" + ! test-snapd-dbus-consumer.dbus-system-consumer 2> call.error + MATCH "Permission denied" < call.error + + echo "And the consumer is not able to access the provided method (same snap)" + ! test-snapd-dbus-provider.system-consumer 2> call.error + MATCH "Permission denied" < call.error diff --git a/tests/main/interfaces-system-observe/task.yaml b/tests/main/interfaces-system-observe/task.yaml index 8d469724c9..892adb37ab 100644 --- a/tests/main/interfaces-system-observe/task.yaml +++ b/tests/main/interfaces-system-observe/task.yaml @@ -42,26 +42,29 @@ execute: | su -l -c "test-snapd-system-observe-consumer.dbus-introspect" test | MATCH "$expected" fi - if [ "$(snap debug confinement)" = strict ] ; then - echo "And the policy has the ptrace suppression rule" - MATCH '^deny ptrace \(trace\),' < /var/lib/snapd/apparmor/profiles/snap.test-snapd-system-observe-consumer.consumer + if [ "$(snap debug confinement)" = partial ] ; then + exit 0 + fi + + echo "And the policy has the ptrace suppression rule" + MATCH '^deny ptrace \(trace\),' < /var/lib/snapd/apparmor/profiles/snap.test-snapd-system-observe-consumer.consumer - echo "When the plug is disconnected" - snap disconnect test-snapd-system-observe-consumer:system-observe + echo "When the plug is disconnected" + snap disconnect test-snapd-system-observe-consumer:system-observe + + echo "Then the snap is not able to get system information" + if su -l -c "test-snapd-system-observe-consumer.consumer" test 2> consumer.error; then + echo "Expected error with plug disconnected" + exit 1 + fi + MATCH "Permission denied" < consumer.error - echo "Then the snap is not able to get system information" - if su -l -c "test-snapd-system-observe-consumer.consumer" test 2>"${PWD}/consumer.error"; then + if [[ "$SPREAD_SYSTEM" != ubuntu-14.04-* ]]; then + echo "And the snap is not able to introspect hostname1" + if su -l -c "test-snapd-system-observe-consumer.dbus-introspect" test 2> introspect.error; then echo "Expected error with plug disconnected" exit 1 fi - MATCH "Permission denied" < consumer.error - - if [[ "$SPREAD_SYSTEM" != ubuntu-14.04-* ]]; then - echo "And the snap is not able to introspect hostname1" - if su -l -c "test-snapd-system-observe-consumer.dbus-introspect" test 2>"${PWD}/introspect.error"; then - echo "Expected error with plug disconnected" - exit 1 - fi - MATCH "Permission denied" < introspect.error - fi + MATCH "Permission denied" < introspect.error fi + diff --git a/tests/main/interfaces-time-control/task.yaml b/tests/main/interfaces-time-control/task.yaml index c42b4fde6b..dbb422669e 100644 --- a/tests/main/interfaces-time-control/task.yaml +++ b/tests/main/interfaces-time-control/task.yaml @@ -30,24 +30,24 @@ execute: | echo "The interface is disconnected by default" snap interfaces -i time-control | MATCH -- '^- +test-snapd-timedate-control-consumer:time-control' - # When the interface is connected + echo "When the interface is connected" snap connect test-snapd-timedate-control-consumer:time-control - # Read/write to hwclock access should be possible + echo "Then read/write hwclock access should be possible" test-snapd-timedate-control-consumer.hwclock-time -r -f /dev/rtc test-snapd-timedate-control-consumer.hwclock-time --systohc -f /dev/rtc - # Read access should be possible + echo "And read access should be possible" test-snapd-timedate-control-consumer.timedatectl-time status | MATCH "RTC in local TZ:" - # Save the RTC initial value + echo "And writing the initial value to RTC should be possible" test-snapd-timedate-control-consumer.timedatectl-time status | grep -oP 'RTC in local TZ: \K(.*)' > rtc.txt - # Set the local rtc and check the status + echo "And reading/writing local RTC status should be possible" test-snapd-timedate-control-consumer.timedatectl-time set-local-rtc yes [ "$(test-snapd-timedate-control-consumer.timedatectl-time status | grep -oP 'RTC in local TZ: \K(.*)')" = "yes" ] - # Re-set the local rtc and check the status + echo "And resetting the local rtc status should be possible (reading the status is implied AIUI)" test-snapd-timedate-control-consumer.timedatectl-time set-local-rtc no [ "$(test-snapd-timedate-control-consumer.timedatectl-time status | grep -oP 'RTC in local TZ: \K(.*)')" = "no" ] @@ -55,9 +55,11 @@ execute: | exit 0 fi - # Disconnect the interface and check access to timedatectl status + echo "When the plug is disconnected" snap disconnect test-snapd-timedate-control-consumer:time-control - if test-snapd-timedate-control-consumer.timedatectl-time status 2>"${PWD}"/call.error; then + + echo "The timedatectl status cannot be retrieved" + if test-snapd-timedate-control-consumer.timedatectl-time status 2> call.error; then echo "Expected permission error calling timedatectl status with disconnected plug" exit 1 fi diff --git a/tests/main/interfaces-timeserver-control/task.yaml b/tests/main/interfaces-timeserver-control/task.yaml index f8164e8ad0..23828fdca3 100644 --- a/tests/main/interfaces-timeserver-control/task.yaml +++ b/tests/main/interfaces-timeserver-control/task.yaml @@ -64,8 +64,10 @@ execute: | exit 0 fi - # Disconnect the interface and check access to timedatectl status + echo "When the plug is disconnected" snap disconnect test-snapd-timedate-control-consumer:timeserver-control + + echo "The timedatectl status cannot be retrieved" if test-snapd-timedate-control-consumer.timedatectl-timeserver status 2> call.error; then echo "Expected permission error calling timedatectl status with disconnected plug" exit 1 diff --git a/tests/main/interfaces-timezone-control/task.yaml b/tests/main/interfaces-timezone-control/task.yaml index b58c73351f..e53f5c2178 100644 --- a/tests/main/interfaces-timezone-control/task.yaml +++ b/tests/main/interfaces-timezone-control/task.yaml @@ -54,9 +54,11 @@ execute: | exit 0 fi - # Disconnect the interface and check access to timedatectl status + echo "When the plug is disconnected" snap disconnect test-snapd-timedate-control-consumer:timezone-control - if test-snapd-timedate-control-consumer.timedatectl-timezone status 2>"${PWD}"/call.error; then + + echo "The timedatectl status cannot be retrieved" + if test-snapd-timedate-control-consumer.timedatectl-timezone status 2> call.error; then echo "Expected permission error calling timedatectl status with disconnected plug" exit 1 fi diff --git a/tests/main/interfaces-uhid/task.yaml b/tests/main/interfaces-uhid/task.yaml index 6bbe33bf25..6c093540fe 100644 --- a/tests/main/interfaces-uhid/task.yaml +++ b/tests/main/interfaces-uhid/task.yaml @@ -35,7 +35,7 @@ execute: | snap disconnect test-snapd-uhid:uhid echo "Then the snap is not able to create/destroy a device on /dev/uhid" - if test-snapd-uhid.test-device 2>"${PWD}"/call.error; then + if test-snapd-uhid.test-device 2> call.error; then echo "Expected permission error calling uhid with disconnected plug" exit 1 fi diff --git a/tests/main/interfaces-upower-observe/task.yaml b/tests/main/interfaces-upower-observe/task.yaml index 3a89e594d9..f11ef5f000 100644 --- a/tests/main/interfaces-upower-observe/task.yaml +++ b/tests/main/interfaces-upower-observe/task.yaml @@ -44,14 +44,16 @@ execute: | done test-snapd-upower-observe-consumer.upower --dump | MATCH "$expected" - if [ "$(snap debug confinement)" = strict ] ; then - echo "When the plug is disconnected" - snap disconnect test-snapd-upower-observe-consumer:upower-observe - - echo "Then the snap is not able to dump info about the upower devices" - if test-snapd-upower-observe-consumer.upower --dump 2>"${PWD}"/upower.error; then - echo "Expected permission error accessing upower info with disconnected plug" - exit 1 - fi - MATCH "Permission denied" < upower.error + if [ "$(snap debug confinement)" = partial ] ; then + exit 0 + fi + + echo "When the plug is disconnected" + snap disconnect test-snapd-upower-observe-consumer:upower-observe + + echo "Then the snap is not able to dump info about the upower devices" + if test-snapd-upower-observe-consumer.upower --dump 2> upower.error; then + echo "Expected permission error accessing upower info with disconnected plug" + exit 1 fi + MATCH "Permission denied" < upower.error diff --git a/tests/main/searching/task.yaml b/tests/main/searching/task.yaml index adb73201ae..5b606c519e 100644 --- a/tests/main/searching/task.yaml +++ b/tests/main/searching/task.yaml @@ -49,9 +49,12 @@ execute: | # TODO: discuss with the store how we can make this test stable, i.e. # that section/snap changes do not break us if [ "$(uname -m)" = "x86_64" ]; then - snap find --section=video vlc | MATCH vlc + snap find --section=photo-and-video vlc | MATCH vlc else - snap find --section=video vlc 2>&1 | MATCH 'No matching snaps' + # actual output: + # Name Version Publisher Notes Summary + # mjpg-streamer 2.0 ogra - UVC webcam streaming tool + snap find --section=photo-and-video vlc 2>&1 | MATCH -v vlc fi # LP: 1740605 diff --git a/tests/main/security-dev-input-event-denied/task.yaml b/tests/main/security-dev-input-event-denied/task.yaml index 6e293aa977..9174fe5a59 100644 --- a/tests/main/security-dev-input-event-denied/task.yaml +++ b/tests/main/security-dev-input-event-denied/task.yaml @@ -47,7 +47,7 @@ execute: | # 1. Joystick echo "Then the snap is not able to access an evdev keyboard" - if test-snapd-event "-event-kbd" 2>"${PWD}"/call.error; then + if test-snapd-event "-event-kbd" 2> call.error; then echo "Expected permission error calling evtest with disconnected plug" exit 1 fi @@ -62,7 +62,7 @@ execute: | # (evdev event*) and -joystick (js*)) and therefore shouldn't be added to # the device cgroup when the joystick interface is plugged. echo "Then the snap is still not able to access an evdev keyboard" - if test-snapd-event "-event-kbd" 2>"${PWD}"/call.error; then + if test-snapd-event "-event-kbd" 2> call.error; then echo "Expected permission error calling evtest with connected joystick plug" exit 1 fi @@ -80,7 +80,7 @@ execute: | # 2. Device Buttons echo "Then the snap is not able to access an evdev keyboard" - if test-snapd-event "-event-kbd" 2>"${PWD}"/call.error; then + if test-snapd-event "-event-kbd" 2> call.error; then echo "Expected permission error calling evtest with disconnected plug" exit 1 fi @@ -95,7 +95,7 @@ execute: | # -gpio-keys-event (evdev event*) and therefore shouldn't be added to # the device cgroup when the device-buttons interface is plugged. echo "Then the snap is still not able to access an evdev keyboard" - if test-snapd-event "-event-kbd" 2>"${PWD}"/call.error; then + if test-snapd-event "-event-kbd" 2> call.error; then echo "Expected permission error calling evtest with connected device-buttons plug" exit 1 fi diff --git a/tests/main/security-udev-input-subsystem/task.yaml b/tests/main/security-udev-input-subsystem/task.yaml index 4fbdb540bb..fbe1e9c7d5 100644 --- a/tests/main/security-udev-input-subsystem/task.yaml +++ b/tests/main/security-udev-input-subsystem/task.yaml @@ -38,7 +38,7 @@ execute: | fi echo "The snap's plug cannot access an evdev keyboard when connected" - if test-snapd-udev-input-subsystem.plug 2>"${PWD}"/call.error; then + if test-snapd-udev-input-subsystem.plug 2> call.error; then echo "Expected permission error with connected plug" exit 1 fi @@ -50,7 +50,7 @@ execute: | snap interfaces -i mir | MATCH -- '- +test-snapd-udev-input-subsystem:mir-plug' echo "The snap's plug still cannot access an evdev keyboard" - if test-snapd-udev-input-subsystem.plug 2>"${PWD}"/call.error; then + if test-snapd-udev-input-subsystem.plug 2> call.error; then echo "Expected permission error with disconnected plug" exit 1 fi @@ -61,7 +61,7 @@ execute: | snap interfaces -i time-control | MATCH ':time-control +-' echo "The snap's time-control plug cannot access an evdev keyboard when disconnected" - if test-snapd-udev-input-subsystem.plug-with-time-control 2>"${PWD}"/call.error; then + if test-snapd-udev-input-subsystem.plug-with-time-control 2> call.error; then echo "Expected permission error with disconnected time-control plug" exit 1 fi @@ -73,7 +73,7 @@ execute: | snap interfaces -i time-control | MATCH ":time-control +test-snapd-udev-input-subsystem" echo "The snap's plug still cannot access an evdev keyboard" - if test-snapd-udev-input-subsystem.plug-with-time-control 2>"${PWD}"/call.error; then + if test-snapd-udev-input-subsystem.plug-with-time-control 2> call.error; then echo "Expected permission error with disconnected time-control plug" exit 1 fi diff --git a/vendor/vendor.json b/vendor/vendor.json index 5fe112ed13..c383e5dd57 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -171,12 +171,6 @@ "revisionTime": "2017-06-28T23:42:41Z" }, { - "checksumSHA1": "WHc3uByvGaMcnSoI21fhzYgbOgg=", - "path": "golang.org/x/net/context/ctxhttp", - "revision": "c81e7f25cb61200d8bf0ae971a0bac8cb638d5bc", - "revisionTime": "2017-06-28T23:42:41Z" - }, - { "checksumSHA1": "9gypWnVZJEaH3jMK9KqOp4xgQD4=", "path": "gopkg.in/check.v1", "revision": "788fd78401277ebd861206a03c884797c6ec5541", |
