diff -Nru snapd-2.29.4.2+17.10/advisor/backend.go snapd-2.31.1+17.10/advisor/backend.go --- snapd-2.29.4.2+17.10/advisor/backend.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/advisor/backend.go 2018-02-02 16:55:28.000000000 +0000 @@ -0,0 +1,208 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2018 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 . + * + */ + +package advisor + +import ( + "strings" + "time" + + "github.com/snapcore/bolt" + + "github.com/snapcore/snapd/dirs" +) + +var cmdBucketKey = []byte("Commands") + +type writer struct { + db *bolt.DB + tx *bolt.Tx + bucket *bolt.Bucket +} + +type CommandDB interface { + // AddSnap adds the entries for commands pointing to the given + // snap name to the commands database. + AddSnap(snapName string, commands []string) error + // Commit persist the changes, and closes the database. If the + // database has already been committed/rollbacked, does nothing. + Commit() error + // Rollback aborts the changes, and closes the database. If the + // database has already been committed/rollbacked, does nothing. + Rollback() error +} + +// Create opens the commands database for writing, and starts a +// transaction that drops and recreates the buckets. You should then +// call AddSnap with each snap you wish to add, and them Commit the +// results to make the changes live, or Rollback to abort; either of +// these closes the database again. +func Create() (CommandDB, error) { + var err error + t := &writer{} + + t.db, err = bolt.Open(dirs.SnapCommandsDB, 0644, &bolt.Options{Timeout: 1 * time.Second}) + if err != nil { + return nil, err + } + + t.tx, err = t.db.Begin(true) + if err == nil { + err = t.tx.DeleteBucket(cmdBucketKey) + if err == nil || err == bolt.ErrBucketNotFound { + t.bucket, err = t.tx.CreateBucket(cmdBucketKey) + } + if err != nil { + t.tx.Rollback() + } + } + + if err != nil { + t.db.Close() + return nil, err + } + + return t, nil +} + +func (t *writer) AddSnap(snapName string, commands []string) error { + bname := []byte(snapName) + + for _, cmd := range commands { + bcmd := []byte(cmd) + row := t.bucket.Get(bcmd) + if row == nil { + row = bname + } else { + row = append(append(row, ','), bname...) + } + if err := t.bucket.Put(bcmd, row); err != nil { + return err + } + } + + return nil +} + +func (t *writer) Commit() error { + return t.done(true) +} + +func (t *writer) Rollback() error { + return t.done(false) +} + +func (t *writer) done(commit bool) error { + var e1, e2 error + + t.bucket = nil + if t.tx != nil { + if commit { + e1 = t.tx.Commit() + } else { + e1 = t.tx.Rollback() + } + t.tx = nil + } + if t.db != nil { + e2 = t.db.Close() + t.db = nil + } + if e1 == nil { + return e2 + } + return e1 +} + +// Dump returns the whole database as a map. For use in testing and debugging. +func Dump() (map[string][]string, error) { + db, err := bolt.Open(dirs.SnapCommandsDB, 0644, &bolt.Options{ + ReadOnly: true, + Timeout: 1 * time.Second, + }) + if err != nil { + return nil, err + } + defer db.Close() + + tx, err := db.Begin(false) + if err != nil { + return nil, err + } + defer tx.Rollback() + + b := tx.Bucket(cmdBucketKey) + if b == nil { + return nil, nil + } + + m := map[string][]string{} + c := b.Cursor() + for k, v := c.First(); k != nil; k, v = c.Next() { + m[string(k)] = strings.Split(string(v), ",") + } + + return m, nil +} + +type boltFinder struct { + bolt.DB +} + +// Open the database for reading. +func Open() (Finder, error) { + db, err := bolt.Open(dirs.SnapCommandsDB, 0644, &bolt.Options{ + ReadOnly: true, + Timeout: 1 * time.Second, + }) + if err != nil { + return nil, err + } + + return &boltFinder{*db}, nil +} + +func (f *boltFinder) Find(command string) ([]Command, error) { + tx, err := f.Begin(false) + if err != nil { + return nil, err + } + defer tx.Rollback() + + b := tx.Bucket(cmdBucketKey) + if b == nil { + return nil, nil + } + + buf := b.Get([]byte(command)) + if buf == nil { + return nil, nil + } + + snaps := strings.Split(string(buf), ",") + cmds := make([]Command, len(snaps)) + for i, snap := range snaps { + cmds[i] = Command{ + Snap: snap, + Command: command, + } + } + + return cmds, nil +} diff -Nru snapd-2.29.4.2+17.10/advisor/cmdfinder.go snapd-2.31.1+17.10/advisor/cmdfinder.go --- snapd-2.29.4.2+17.10/advisor/cmdfinder.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/advisor/cmdfinder.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,114 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2018 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 . + * + */ + +package advisor + +type Command struct { + Snap string + Command string +} + +var newFinder = Open + +func FindCommand(command string) ([]Command, error) { + finder, err := newFinder() + if err != nil { + return nil, err + } + defer finder.Close() + + return finder.Find(command) +} + +const ( + minLen = 3 + maxLen = 256 +) + +// based on CommandNotFound.py:similar_words.py +func similarWords(word string) []string { + const alphabet = "abcdefghijklmnopqrstuvwxyz-_0123456789" + similar := make(map[string]bool, 2*len(word)+2*len(word)*len(alphabet)) + + // deletes + for i := range word { + similar[word[:i]+word[i+1:]] = true + } + // transpose + for i := 0; i < len(word)-1; i++ { + similar[word[:i]+word[i+1:i+2]+word[i:i+1]+word[i+2:]] = true + } + // replaces + for i := range word { + for _, r := range alphabet { + similar[word[:i]+string(r)+word[i+1:]] = true + } + } + // inserts + for i := range word { + for _, r := range alphabet { + similar[word[:i]+string(r)+word[i:]] = true + } + } + + // convert for output + ret := make([]string, 0, len(similar)) + for w := range similar { + ret = append(ret, w) + } + + return ret +} + +func FindMisspelledCommand(command string) ([]Command, error) { + if len(command) < minLen || len(command) > maxLen { + return nil, nil + } + finder, err := newFinder() + if err != nil { + return nil, err + } + defer finder.Close() + + alternatives := make([]Command, 0, 32) + for _, w := range similarWords(command) { + res, err := finder.Find(w) + if err != nil { + return nil, err + } + if len(res) > 0 { + alternatives = append(alternatives, res...) + } + } + + return alternatives, nil +} + +type Finder interface { + Find(command string) ([]Command, error) + Close() error +} + +func ReplaceCommandsFinder(constructor func() (Finder, error)) (restore func()) { + old := newFinder + newFinder = constructor + return func() { + newFinder = old + } +} diff -Nru snapd-2.29.4.2+17.10/advisor/cmdfinder_test.go snapd-2.31.1+17.10/advisor/cmdfinder_test.go --- snapd-2.29.4.2+17.10/advisor/cmdfinder_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/advisor/cmdfinder_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,136 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2018 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 . + * + */ + +package advisor_test + +import ( + "os" + "sort" + "testing" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/advisor" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/testutil" +) + +// Hook up check.v1 into the "go test" runner +func Test(t *testing.T) { TestingT(t) } + +type cmdfinderSuite struct{} + +var _ = Suite(&cmdfinderSuite{}) + +func (s *cmdfinderSuite) SetUpTest(c *C) { + dirs.SetRootDir(c.MkDir()) + c.Assert(os.MkdirAll(dirs.SnapCacheDir, 0755), IsNil) + + db, err := advisor.Create() + c.Assert(err, IsNil) + c.Assert(db.AddSnap("foo", []string{"foo", "meh"}), IsNil) + c.Assert(db.AddSnap("bar", []string{"bar", "meh"}), IsNil) + c.Assert(db.Commit(), IsNil) +} + +func (s *cmdfinderSuite) TestFindSimilarWordsCnf(c *C) { + words := advisor.SimilarWords("123") + sort.Strings(words) + c.Check(words, DeepEquals, []string{ + // calculated using CommandNotFound.py:similar_words("123") + "-123", "-23", "0123", "023", "1-23", "1-3", "1023", + "103", "1123", "113", "12", "12-", "12-3", "120", + "1203", "121", "1213", "122", "1223", "123", "1233", + "124", "1243", "125", "1253", "126", "1263", "127", + "1273", "128", "1283", "129", "1293", "12_", "12_3", + "12a", "12a3", "12b", "12b3", "12c", "12c3", "12d", + "12d3", "12e", "12e3", "12f", "12f3", "12g", "12g3", + "12h", "12h3", "12i", "12i3", "12j", "12j3", "12k", + "12k3", "12l", "12l3", "12m", "12m3", "12n", "12n3", + "12o", "12o3", "12p", "12p3", "12q", "12q3", "12r", + "12r3", "12s", "12s3", "12t", "12t3", "12u", "12u3", + "12v", "12v3", "12w", "12w3", "12x", "12x3", "12y", + "12y3", "12z", "12z3", "13", "132", "1323", "133", + "1423", "143", "1523", "153", "1623", "163", "1723", + "173", "1823", "183", "1923", "193", "1_23", "1_3", + "1a23", "1a3", "1b23", "1b3", "1c23", "1c3", "1d23", + "1d3", "1e23", "1e3", "1f23", "1f3", "1g23", "1g3", + "1h23", "1h3", "1i23", "1i3", "1j23", "1j3", "1k23", + "1k3", "1l23", "1l3", "1m23", "1m3", "1n23", "1n3", + "1o23", "1o3", "1p23", "1p3", "1q23", "1q3", "1r23", + "1r3", "1s23", "1s3", "1t23", "1t3", "1u23", "1u3", + "1v23", "1v3", "1w23", "1w3", "1x23", "1x3", "1y23", + "1y3", "1z23", "1z3", "2123", "213", "223", "23", + "3123", "323", "4123", "423", "5123", "523", "6123", + "623", "7123", "723", "8123", "823", "9123", "923", + "_123", "_23", "a123", "a23", "b123", "b23", "c123", + "c23", "d123", "d23", "e123", "e23", "f123", "f23", + "g123", "g23", "h123", "h23", "i123", "i23", "j123", + "j23", "k123", "k23", "l123", "l23", "m123", "m23", + "n123", "n23", "o123", "o23", "p123", "p23", "q123", + "q23", "r123", "r23", "s123", "s23", "t123", "t23", + "u123", "u23", "v123", "v23", "w123", "w23", "x123", + "x23", "y123", "y23", "z123", "z23", + }) +} + +func (s *cmdfinderSuite) TestFindSimilarWordsTrivial(c *C) { + words := advisor.SimilarWords("hella") + c.Check(words, testutil.Contains, "hello") +} + +func (s *cmdfinderSuite) TestFindCommandHit(c *C) { + cmds, err := advisor.FindCommand("meh") + c.Assert(err, IsNil) + c.Check(cmds, DeepEquals, []advisor.Command{ + {Snap: "foo", Command: "meh"}, + {Snap: "bar", Command: "meh"}, + }) +} + +func (s *cmdfinderSuite) TestFindCommandMiss(c *C) { + cmds, err := advisor.FindCommand("moh") + c.Assert(err, IsNil) + c.Check(cmds, HasLen, 0) +} + +func (s *cmdfinderSuite) TestFindMisspelledCommandHit(c *C) { + cmds, err := advisor.FindMisspelledCommand("moh") + c.Assert(err, IsNil) + c.Check(cmds, DeepEquals, []advisor.Command{ + {Snap: "foo", Command: "meh"}, + {Snap: "bar", Command: "meh"}, + }) +} + +func (s *cmdfinderSuite) TestFindMisspelledCommandMiss(c *C) { + cmds, err := advisor.FindMisspelledCommand("hello") + c.Assert(err, IsNil) + c.Check(cmds, HasLen, 0) +} + +func (s *cmdfinderSuite) TestDump(c *C) { + cmds, err := advisor.Dump() + c.Assert(err, IsNil) + c.Check(cmds, DeepEquals, map[string][]string{ + "foo": {"foo"}, + "bar": {"bar"}, + "meh": {"foo", "bar"}, + }) +} diff -Nru snapd-2.29.4.2+17.10/advisor/export_test.go snapd-2.31.1+17.10/advisor/export_test.go --- snapd-2.29.4.2+17.10/advisor/export_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/advisor/export_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,22 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2018 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 . + * + */ + +package advisor + +var SimilarWords = similarWords diff -Nru snapd-2.29.4.2+17.10/arch/arch.go snapd-2.31.1+17.10/arch/arch.go --- snapd-2.29.4.2+17.10/arch/arch.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/arch/arch.go 2018-01-24 20:02:44.000000000 +0000 @@ -125,6 +125,7 @@ "i686": "i386", "x86_64": "amd64", "armv7l": "armhf", + "armv8l": "arm64", "aarch64": "arm64", "ppc64le": "ppc64el", "s390x": "s390x", diff -Nru snapd-2.29.4.2+17.10/asserts/assertstest/assertstest.go snapd-2.31.1+17.10/asserts/assertstest/assertstest.go --- snapd-2.29.4.2+17.10/asserts/assertstest/assertstest.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/asserts/assertstest/assertstest.go 2017-12-01 15:51:55.000000000 +0000 @@ -335,7 +335,7 @@ genericModelsKey := NewAccountKey(rootSigning, genericAcct, map[string]interface{}{ "name": "models", "since": ts, - }, genericModelsPrivKey.PublicKey(), "") + }, keys.GenericModels.PublicKey(), "") generic := []asserts.Assertion{genericAcct, genericModelsKey} db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{ diff -Nru snapd-2.29.4.2+17.10/asserts/gpgkeypairmgr.go snapd-2.31.1+17.10/asserts/gpgkeypairmgr.go --- snapd-2.29.4.2+17.10/asserts/gpgkeypairmgr.go 2016-09-15 18:55:10.000000000 +0000 +++ snapd-2.31.1+17.10/asserts/gpgkeypairmgr.go 2018-01-24 20:02:44.000000000 +0000 @@ -26,7 +26,6 @@ "os" "os/exec" "path/filepath" - "strconv" "strings" "github.com/snapcore/snapd/osutil" @@ -38,12 +37,7 @@ return "", err } - uid, err := strconv.Atoi(real.Uid) - if err != nil { - return "", err - } - - gid, err := strconv.Atoi(real.Gid) + uid, gid, err := osutil.UidGid(real) if err != nil { return "", err } diff -Nru snapd-2.29.4.2+17.10/asserts/signtool/sign.go snapd-2.31.1+17.10/asserts/signtool/sign.go --- snapd-2.29.4.2+17.10/asserts/signtool/sign.go 2016-09-01 16:51:18.000000000 +0000 +++ snapd-2.31.1+17.10/asserts/signtool/sign.go 2018-01-24 20:02:44.000000000 +0000 @@ -38,6 +38,14 @@ // plus an optional pseudo-header "body" to specify // the body of the assertion Statement []byte + + // Complement specifies complementary headers to what is in + // Statement, for use by tools that fill-in/compute some of + // the headers. Headers appearing both in Statement and + // Complement are an error, except for "type" that needs + // instead to match if present. Pseudo-header "body" can also + // be specified here. + Complement map[string]interface{} } // Sign produces the text of a signed assertion as specified by opts. @@ -47,6 +55,20 @@ if err != nil { return nil, fmt.Errorf("cannot parse the assertion input as JSON: %v", err) } + + for name, value := range opts.Complement { + if v, ok := headers[name]; ok { + if name == "type" { + if v != value { + return nil, fmt.Errorf("repeated assertion type does not match") + } + } else { + return nil, fmt.Errorf("complementary header %q clashes with assertion input", name) + } + } + headers[name] = value + } + typCand, ok := headers["type"] if !ok { return nil, fmt.Errorf("missing assertion type header") diff -Nru snapd-2.29.4.2+17.10/asserts/signtool/sign_test.go snapd-2.31.1+17.10/asserts/signtool/sign_test.go --- snapd-2.29.4.2+17.10/asserts/signtool/sign_test.go 2016-09-01 16:51:18.000000000 +0000 +++ snapd-2.31.1+17.10/asserts/signtool/sign_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -137,6 +137,69 @@ c.Check(a.Body(), DeepEquals, []byte("BODY")) } +func (s *signSuite) TestSignJSONWithBodyAndComplementRevision(c *C) { + statement := exampleJSON(map[string]interface{}{ + "body": "BODY", + }) + opts := signtool.Options{ + KeyID: s.testKeyID, + + Statement: statement, + Complement: map[string]interface{}{ + "revision": "11", + }, + } + + assertText, err := signtool.Sign(&opts, s.keypairMgr) + c.Assert(err, IsNil) + + a, err := asserts.Decode(assertText) + c.Assert(err, IsNil) + + c.Check(a.Type(), Equals, asserts.ModelType) + c.Check(a.Revision(), Equals, 11) + + expectedHeaders := expectedModelHeaders(a) + expectedHeaders["revision"] = "11" + expectedHeaders["body-length"] = "4" + + c.Check(a.Headers(), DeepEquals, expectedHeaders) + + c.Check(a.Body(), DeepEquals, []byte("BODY")) +} + +func (s *signSuite) TestSignJSONWithRevisionAndComplementBodyAndRepeatedType(c *C) { + statement := exampleJSON(map[string]interface{}{ + "revision": "11", + }) + opts := signtool.Options{ + KeyID: s.testKeyID, + + Statement: statement, + Complement: map[string]interface{}{ + "type": "model", + "body": "BODY", + }, + } + + assertText, err := signtool.Sign(&opts, s.keypairMgr) + c.Assert(err, IsNil) + + a, err := asserts.Decode(assertText) + c.Assert(err, IsNil) + + c.Check(a.Type(), Equals, asserts.ModelType) + c.Check(a.Revision(), Equals, 11) + + expectedHeaders := expectedModelHeaders(a) + expectedHeaders["revision"] = "11" + expectedHeaders["body-length"] = "4" + + c.Check(a.Headers(), DeepEquals, expectedHeaders) + + c.Check(a.Body(), DeepEquals, []byte("BODY")) +} + func (s *signSuite) TestSignErrors(c *C) { opts := signtool.Options{ KeyID: s.testKeyID, @@ -147,31 +210,51 @@ tests := []struct { expError string brokenStatement []byte + complement map[string]interface{} }{ {`cannot parse the assertion input as JSON:.*`, []byte("\x00"), + nil, }, {`invalid assertion type: what`, exampleJSON(map[string]interface{}{"type": "what"}), + nil, }, {`assertion type must be a string, not: \[\]`, exampleJSON(map[string]interface{}{"type": emptyList}), + nil, }, {`missing assertion type header`, exampleJSON(map[string]interface{}{"type": nil}), + nil, }, {"revision should be positive: -10", - exampleJSON(map[string]interface{}{"revision": "-10"})}, + exampleJSON(map[string]interface{}{"revision": "-10"}), + nil, + }, {`"authority-id" header is mandatory`, - exampleJSON(map[string]interface{}{"authority-id": nil})}, + exampleJSON(map[string]interface{}{"authority-id": nil}), + nil, + }, {`body if specified must be a string`, - exampleJSON(map[string]interface{}{"body": emptyList})}, + exampleJSON(map[string]interface{}{"body": emptyList}), + nil, + }, + {`repeated assertion type does not match`, + exampleJSON(nil), + map[string]interface{}{"type": "foo"}, + }, + {`complementary header "kernel" clashes with assertion input`, + exampleJSON(nil), + map[string]interface{}{"kernel": "foo"}, + }, } for _, t := range tests { fresh := opts fresh.Statement = t.brokenStatement + fresh.Complement = t.complement _, err := signtool.Sign(&fresh, s.keypairMgr) c.Check(err, ErrorMatches, t.expError) diff -Nru snapd-2.29.4.2+17.10/asserts/store_asserts.go snapd-2.31.1+17.10/asserts/store_asserts.go --- snapd-2.29.4.2+17.10/asserts/store_asserts.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/asserts/store_asserts.go 2017-12-01 15:51:55.000000000 +0000 @@ -22,13 +22,15 @@ import ( "fmt" "net/url" + "time" ) // Store holds a store assertion, defining the configuration needed to connect // a device to the store. type Store struct { assertionBase - url *url.URL + url *url.URL + timestamp time.Time } // Store returns the identifying name of the operator's store. @@ -51,6 +53,11 @@ return store.HeaderString("location") } +// Timestamp returns the time when the store assertion was issued. +func (store *Store) Timestamp() time.Time { + return store.timestamp +} + func (store *Store) checkConsistency(db RODatabase, acck *AccountKey) error { // Will be applied to a system's snapd so must be signed by a trusted authority. if !db.IsTrustedAccount(store.AuthorityID()) { @@ -127,5 +134,14 @@ return nil, err } - return &Store{assertionBase: assert, url: url}, nil + timestamp, err := checkRFC3339Date(assert.headers, "timestamp") + if err != nil { + return nil, err + } + + return &Store{ + assertionBase: assert, + url: url, + timestamp: timestamp, + }, nil } diff -Nru snapd-2.29.4.2+17.10/asserts/store_asserts_test.go snapd-2.31.1+17.10/asserts/store_asserts_test.go --- snapd-2.29.4.2+17.10/asserts/store_asserts_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/asserts/store_asserts_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -22,6 +22,7 @@ import ( "fmt" "strings" + "time" "github.com/snapcore/snapd/asserts" "github.com/snapcore/snapd/asserts/assertstest" @@ -31,16 +32,21 @@ var _ = Suite(&storeSuite{}) type storeSuite struct { + ts time.Time + tsLine string validExample string } func (s *storeSuite) SetUpSuite(c *C) { + s.ts = time.Now().Truncate(time.Second).UTC() + s.tsLine = "timestamp: " + s.ts.Format(time.RFC3339) + "\n" s.validExample = "type: store\n" + "authority-id: canonical\n" + "store: store1\n" + "operator-id: op-id1\n" + "url: https://store.example.com\n" + "location: upstairs\n" + + s.tsLine + "sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij\n" + "\n" + "AXNpZw==" @@ -56,6 +62,7 @@ c.Check(store.Store(), Equals, "store1") c.Check(store.URL().String(), Equals, "https://store.example.com") c.Check(store.Location(), Equals, "upstairs") + c.Check(store.Timestamp().Equal(s.ts), Equals, true) } var storeErrPrefix = "assertion store: " @@ -68,6 +75,9 @@ {"operator-id: op-id1\n", "operator-id: \n", `"operator-id" header should not be empty`}, {"url: https://store.example.com\n", "url:\n - foo\n", `"url" header must be a string`}, {"location: upstairs\n", "location:\n - foo\n", `"location" header must be a string`}, + {s.tsLine, "", `"timestamp" header is mandatory`}, + {s.tsLine, "timestamp: \n", `"timestamp" header should not be empty`}, + {s.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`}, } for _, test := range tests { @@ -159,6 +169,7 @@ storeHeaders := map[string]interface{}{ "store": "store1", "operator-id": operator.HeaderString("account-id"), + "timestamp": time.Now().Format(time.RFC3339), } // store signed by some other account fails. @@ -181,6 +192,7 @@ store, err := storeDB.Sign(asserts.StoreType, map[string]interface{}{ "store": "store1", "operator-id": "op-id1", + "timestamp": time.Now().Format(time.RFC3339), }, nil, "") c.Assert(err, IsNil) diff -Nru snapd-2.29.4.2+17.10/asserts/sysdb/trusted.go snapd-2.31.1+17.10/asserts/sysdb/trusted.go --- snapd-2.29.4.2+17.10/asserts/sysdb/trusted.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/asserts/sysdb/trusted.go 2017-12-01 15:51:55.000000000 +0000 @@ -133,6 +133,9 @@ if !osutil.GetenvBool("SNAPPY_USE_STAGING_STORE") { trusted = append(trusted, trustedAssertions...) } else { + if len(trustedStagingAssertions) == 0 { + panic("cannot work with the staging store without a testing build with compiled-in staging keys") + } trusted = append(trusted, trustedStagingAssertions...) } trusted = append(trusted, trustedExtraAssertions...) diff -Nru snapd-2.29.4.2+17.10/client/client.go snapd-2.31.1+17.10/client/client.go --- snapd-2.29.4.2+17.10/client/client.go 2017-10-23 06:17:24.000000000 +0000 +++ snapd-2.31.1+17.10/client/client.go 2018-01-31 08:47:06.000000000 +0000 @@ -374,6 +374,8 @@ ErrorKindNoUpdateAvailable = "snap-no-update-available" ErrorKindNotSnap = "snap-not-a-snap" + + ErrorKindNetworkTimeout = "network-timeout" ) // IsTwoFactorError returns whether the given error is due to problems @@ -393,8 +395,12 @@ VersionID string `json:"version-id,omitempty"` } +// RefreshInfo contains information about refreshes. type RefreshInfo struct { - Schedule string `json:"schedule"` + // Timer contains the refresh.timer setting. + Timer string `json:"timer,omitempty"` + // Schedule contains the legacy refresh.schedule setting. + Schedule string `json:"schedule,omitempty"` Last string `json:"last,omitempty"` Next string `json:"next,omitempty"` } diff -Nru snapd-2.29.4.2+17.10/client/login.go snapd-2.31.1+17.10/client/login.go --- snapd-2.29.4.2+17.10/client/login.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.31.1+17.10/client/login.go 2018-01-24 20:02:44.000000000 +0000 @@ -25,7 +25,6 @@ "fmt" "os" "path/filepath" - "strconv" "github.com/snapcore/snapd/osutil" ) @@ -112,12 +111,7 @@ return err } - uid, err := strconv.Atoi(real.Uid) - if err != nil { - return err - } - - gid, err := strconv.Atoi(real.Gid) + uid, gid, err := osutil.UidGid(real) if err != nil { return err } diff -Nru snapd-2.29.4.2+17.10/client/login_test.go snapd-2.31.1+17.10/client/login_test.go --- snapd-2.29.4.2+17.10/client/login_test.go 2016-11-24 09:36:03.000000000 +0000 +++ snapd-2.31.1+17.10/client/login_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -58,6 +58,43 @@ c.Check(string(content), check.Equals, `{"username":"the-user-name","macaroon":"the-root-macaroon","discharges":["discharge-macaroon"]}`) } +func (cs *clientSuite) TestClientLoginWhenLoggedIn(c *check.C) { + cs.rsp = `{"type": "sync", "result": + {"username": "the-user-name", + "email": "zed@bar.com", + "macaroon": "the-root-macaroon", + "discharges": ["discharge-macaroon"]}}` + + outfile := filepath.Join(c.MkDir(), "json") + os.Setenv(client.TestAuthFileEnvKey, outfile) + defer os.Unsetenv(client.TestAuthFileEnvKey) + + err := ioutil.WriteFile(outfile, []byte(`{"email":"foo@bar.com","macaroon":"macaroon"}`), 0600) + c.Assert(err, check.IsNil) + c.Assert(cs.cli.LoggedInUser(), check.DeepEquals, &client.User{ + Email: "foo@bar.com", + Macaroon: "macaroon", + }) + + user, err := cs.cli.Login("username", "pass", "") + expected := &client.User{ + Email: "zed@bar.com", + Username: "the-user-name", + Macaroon: "the-root-macaroon", + Discharges: []string{"discharge-macaroon"}, + } + c.Check(err, check.IsNil) + c.Check(user, check.DeepEquals, expected) + c.Check(cs.req.Header.Get("Authorization"), check.Matches, `Macaroon root="macaroon"`) + + c.Assert(cs.cli.LoggedInUser(), check.DeepEquals, expected) + + c.Check(osutil.FileExists(outfile), check.Equals, true) + content, err := ioutil.ReadFile(outfile) + c.Check(err, check.IsNil) + c.Check(string(content), check.Equals, `{"username":"the-user-name","email":"zed@bar.com","macaroon":"the-root-macaroon","discharges":["discharge-macaroon"]}`) +} + func (cs *clientSuite) TestClientLoginError(c *check.C) { cs.rsp = `{ "result": {}, diff -Nru snapd-2.29.4.2+17.10/client/packages.go snapd-2.31.1+17.10/client/packages.go --- snapd-2.29.4.2+17.10/client/packages.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/client/packages.go 2018-01-24 20:02:44.000000000 +0000 @@ -20,6 +20,7 @@ package client import ( + "encoding/json" "errors" "fmt" "net/url" @@ -31,40 +32,56 @@ // Snap holds the data for a snap as obtained from snapd. type Snap struct { - ID string `json:"id"` - Title string `json:"title,omitempty"` - Summary string `json:"summary"` - Description string `json:"description"` - DownloadSize int64 `json:"download-size"` - Icon string `json:"icon"` - InstalledSize int64 `json:"installed-size"` - InstallDate time.Time `json:"install-date"` - Name string `json:"name"` - Developer string `json:"developer"` - Status string `json:"status"` - Type string `json:"type"` - Version string `json:"version"` - Channel string `json:"channel"` - TrackingChannel string `json:"tracking-channel"` - Revision snap.Revision `json:"revision"` - Confinement string `json:"confinement"` - Private bool `json:"private"` - DevMode bool `json:"devmode"` - JailMode bool `json:"jailmode"` - TryMode bool `json:"trymode"` - Apps []AppInfo `json:"apps"` - Broken string `json:"broken"` - Contact string `json:"contact"` - License string `json:"license,omitempty"` + ID string `json:"id"` + Title string `json:"title,omitempty"` + Summary string `json:"summary"` + Description string `json:"description"` + DownloadSize int64 `json:"download-size,omitempty"` + Icon string `json:"icon,omitempty"` + InstalledSize int64 `json:"installed-size,omitempty"` + InstallDate time.Time `json:"install-date,omitempty"` + Name string `json:"name"` + Developer string `json:"developer"` + Status string `json:"status"` + Type string `json:"type"` + Version string `json:"version"` + Channel string `json:"channel"` + TrackingChannel string `json:"tracking-channel,omitempty"` + IgnoreValidation bool `json:"ignore-validation"` + Revision snap.Revision `json:"revision"` + Confinement string `json:"confinement"` + Private bool `json:"private"` + DevMode bool `json:"devmode"` + JailMode bool `json:"jailmode"` + TryMode bool `json:"trymode,omitempty"` + Apps []AppInfo `json:"apps,omitempty"` + Broken string `json:"broken,omitempty"` + Contact string `json:"contact"` + License string `json:"license,omitempty"` - Prices map[string]float64 `json:"prices"` - Screenshots []Screenshot `json:"screenshots"` + Prices map[string]float64 `json:"prices,omitempty"` + Screenshots []Screenshot `json:"screenshots,omitempty"` // The flattended channel map with $track/$risk - Channels map[string]*snap.ChannelSnapInfo `json:"channels"` + Channels map[string]*snap.ChannelSnapInfo `json:"channels,omitempty"` // The ordered list of tracks that contains channels - Tracks []string + Tracks []string `json:"tracks,omitempty"` +} + +func (s *Snap) MarshalJSON() ([]byte, error) { + type auxSnap Snap // use auxiliary type so that Go does not call Snap.MarshalJSON() + // separate type just for marshalling + m := struct { + auxSnap + InstallDate *time.Time `json:"install-date,omitempty"` + }{ + auxSnap: auxSnap(*s), + } + if !s.InstallDate.IsZero() { + m.InstallDate = &s.InstallDate + } + return json.Marshal(&m) } type Screenshot struct { @@ -197,6 +214,9 @@ func (client *Client) snapsFromPath(path string, query url.Values) ([]*Snap, *ResultInfo, error) { var snaps []*Snap ri, err := client.doSync("GET", path, query, nil, nil, &snaps) + if e, ok := err.(*Error); ok { + return nil, nil, e + } if err != nil { return nil, nil, fmt.Errorf("cannot list snaps: %s", err) } diff -Nru snapd-2.29.4.2+17.10/client/snap_op.go snapd-2.31.1+17.10/client/snap_op.go --- snapd-2.29.4.2+17.10/client/snap_op.go 2017-10-23 06:17:24.000000000 +0000 +++ snapd-2.31.1+17.10/client/snap_op.go 2018-01-31 08:47:06.000000000 +0000 @@ -30,6 +30,7 @@ ) type SnapOptions struct { + Amend bool `json:"amend,omitempty"` Channel string `json:"channel,omitempty"` Revision string `json:"revision,omitempty"` DevMode bool `json:"devmode,omitempty"` diff -Nru snapd-2.29.4.2+17.10/cmd/autogen.sh snapd-2.31.1+17.10/cmd/autogen.sh --- snapd-2.29.4.2+17.10/cmd/autogen.sh 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/autogen.sh 2017-12-01 15:51:55.000000000 +0000 @@ -1,7 +1,14 @@ #!/bin/sh # Welcome to the Happy Maintainer's Utility Script +# +# Set BUILD_DIR to the directory where the build will happen, otherwise $PWD +# will be used set -eux +BUILD_DIR=${BUILD_DIR:-.} +selfdir=$(dirname "$0") +SRC_DIR=$(readlink -f "$selfdir") + # We need the VERSION file to configure if [ ! -e VERSION ]; then ( cd .. && ./mkversion.sh ) @@ -46,6 +53,7 @@ ;; esac -echo "Configuring with: $extra_opts" +echo "Configuring in build directory $BUILD_DIR with: $extra_opts" +mkdir -p "$BUILD_DIR" && cd "$BUILD_DIR" # shellcheck disable=SC2086 -./configure --enable-maintainer-mode --prefix=/usr $extra_opts +"${SRC_DIR}/configure" --enable-maintainer-mode --prefix=/usr $extra_opts diff -Nru snapd-2.29.4.2+17.10/cmd/cmd.go snapd-2.31.1+17.10/cmd/cmd.go --- snapd-2.29.4.2+17.10/cmd/cmd.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/cmd.go 2017-12-01 15:51:55.000000000 +0000 @@ -130,7 +130,7 @@ } if !strings.HasPrefix(exe, dirs.SnapMountDir) { - logger.Noticef("exe doesn't have snap mount dir prefix: %q vs %q", exe, dirs.SnapMountDir) + logger.Debugf("exe doesn't have snap mount dir prefix: %q vs %q", exe, dirs.SnapMountDir) return distroTool } @@ -176,15 +176,6 @@ // Did we already re-exec? if strings.HasPrefix(exe, dirs.SnapMountDir) { - // Older version of snapd (before 2.28) did use this env - // to check if they should re-exec or not. We still need - // to unset it because the host snap tool may be old and - // using this key. So if e.g. the host has snapd 2.27 and - // snapd re-execs to 2.29 then `snap run --shell classic-snap` - // will go into an environment where the snapd 2.27 sees - // this key and stops re-execing - which is not what we - // want. C.f. https://forum.snapcraft.io/t/seccomp-error-calling-snap-from-another-classic-snap-on-core-candidate/2736/7 - mustUnsetenv("SNAP_DID_REEXEC") return } @@ -210,7 +201,5 @@ } logger.Debugf("restarting into %q", full) - // we keep this for e.g. the errtracker - env := append(os.Environ(), "SNAP_DID_REEXEC=1") - panic(syscallExec(full, os.Args, env)) + panic(syscallExec(full, os.Args, os.Environ())) } diff -Nru snapd-2.29.4.2+17.10/cmd/cmd_test.go snapd-2.31.1+17.10/cmd/cmd_test.go --- snapd-2.29.4.2+17.10/cmd/cmd_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/cmd_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -32,7 +32,6 @@ "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/release" - "github.com/snapcore/snapd/testutil" ) func Test(t *testing.T) { TestingT(t) } @@ -236,7 +235,6 @@ c.Check(s.execCalled, Equals, 1) c.Check(s.lastExecArgv0, Equals, filepath.Join(s.newCore, "/usr/lib/snapd/potato")) c.Check(s.lastExecArgv, DeepEquals, os.Args) - c.Check(s.lastExecEnvv, testutil.Contains, "SNAP_DID_REEXEC=1") } func (s *cmdSuite) TestExecInOldCoreSnap(c *C) { @@ -246,7 +244,6 @@ c.Check(s.execCalled, Equals, 1) c.Check(s.lastExecArgv0, Equals, filepath.Join(s.oldCore, "/usr/lib/snapd/potato")) c.Check(s.lastExecArgv, DeepEquals, os.Args) - c.Check(s.lastExecEnvv, testutil.Contains, "SNAP_DID_REEXEC=1") } func (s *cmdSuite) TestExecInCoreSnapBailsNoCoreSupport(c *C) { @@ -308,17 +305,3 @@ cmd.ExecInCoreSnap() c.Check(s.execCalled, Equals, 0) } - -func (s *cmdSuite) TestExecInCoreSnapUnsetsDidReexec(c *C) { - os.Setenv("SNAP_DID_REEXEC", "1") - defer os.Unsetenv("SNAP_DID_REEXEC") - - selfExe := filepath.Join(s.fakeroot, "proc/self/exe") - err := os.Symlink(filepath.Join(s.fakeroot, "/snap/core/42/usr/lib/snapd"), selfExe) - c.Assert(err, IsNil) - cmd.MockSelfExe(selfExe) - - cmd.ExecInCoreSnap() - c.Check(s.execCalled, Equals, 0) - c.Check(os.Getenv("SNAP_DID_REEXEC"), Equals, "") -} diff -Nru snapd-2.29.4.2+17.10/cmd/configure.ac snapd-2.31.1+17.10/cmd/configure.ac --- snapd-2.29.4.2+17.10/cmd/configure.ac 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/configure.ac 2018-01-31 08:47:06.000000000 +0000 @@ -209,5 +209,12 @@ esac], [enable_static_libseccomp=no]) AM_CONDITIONAL([STATIC_LIBSECCOMP], [test "x$enable_static_libseccomp" = "xyes"]) +LIB32_DIR="${prefix}/lib32" +AC_ARG_WITH([32bit-libdir], + AS_HELP_STRING([--with-32bit-libdir=DIR], [Use an alternate lib32 directory]), + [LIB32_DIR="$withval"]) +AC_SUBST(LIB32_DIR) +AC_DEFINE_UNQUOTED([LIB32_DIR], "${LIB32_DIR}", [Location of the lib32 directory]) + AC_CONFIG_FILES([Makefile]) AC_OUTPUT diff -Nru snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/cgroup-freezer-support.c snapd-2.31.1+17.10/cmd/libsnap-confine-private/cgroup-freezer-support.c --- snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/cgroup-freezer-support.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/libsnap-confine-private/cgroup-freezer-support.c 2018-01-24 20:02:44.000000000 +0000 @@ -19,7 +19,7 @@ void sc_cgroup_freezer_join(const char *snap_name, pid_t pid) { - // Format the name of the cgroup hierarchy. + // Format the name of the cgroup hierarchy. char buf[PATH_MAX] = { 0 }; sc_must_snprintf(buf, sizeof buf, "snap.%s", snap_name); @@ -44,7 +44,7 @@ snap_name); } // Since we may be running from a setuid but not setgid executable, ensure - // that the group and owner of the hierarchy directory is root.root. + // that the group and owner of the hierarchy directory is root.root. if (fchownat(hierarchy_fd, "", 0, 0, AT_EMPTY_PATH) < 0) { die("cannot change owner of freezer cgroup hierarchy for snap %s to root.root", snap_name); } @@ -65,3 +65,85 @@ debug("moved process %ld to freezer cgroup hierarchy for snap %s", (long)pid, snap_name); } + +bool sc_cgroup_freezer_occupied(const char *snap_name) +{ + // Format the name of the cgroup hierarchy. + char buf[PATH_MAX] = { 0 }; + sc_must_snprintf(buf, sizeof buf, "snap.%s", snap_name); + + // Open the freezer cgroup directory. + int cgroup_fd SC_CLEANUP(sc_cleanup_close) = -1; + cgroup_fd = open(freezer_cgroup_dir, + O_PATH | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); + if (cgroup_fd < 0) { + die("cannot open freezer cgroup (%s)", freezer_cgroup_dir); + } + // Open the proc directory. + int proc_fd SC_CLEANUP(sc_cleanup_close) = -1; + proc_fd = open("/proc", O_PATH | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); + if (proc_fd < 0) { + die("cannot open /proc"); + } + // Open the hierarchy directory for the given snap. + int hierarchy_fd SC_CLEANUP(sc_cleanup_close) = -1; + hierarchy_fd = openat(cgroup_fd, buf, + O_PATH | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); + if (hierarchy_fd < 0) { + if (errno == ENOENT) { + return false; + } + die("cannot open freezer cgroup hierarchy for snap %s", + snap_name); + } + // Open the "cgroup.procs" file. Alternatively we could open the "tasks" + // file and see per-thread data but we don't need that. + int cgroup_procs_fd SC_CLEANUP(sc_cleanup_close) = -1; + cgroup_procs_fd = openat(hierarchy_fd, "cgroup.procs", + O_RDONLY | O_NOFOLLOW | O_CLOEXEC); + if (cgroup_procs_fd < 0) { + die("cannot open cgroup.procs file for freezer cgroup hierarchy for snap %s", snap_name); + } + + FILE *cgroup_procs SC_CLEANUP(sc_cleanup_file) = NULL; + cgroup_procs = fdopen(cgroup_procs_fd, "r"); + if (cgroup_procs == NULL) { + die("cannot convert tasks file descriptor to FILE"); + } + cgroup_procs_fd = -1; // cgroup_procs_fd will now be closed by fclose. + + char *line_buf SC_CLEANUP(sc_cleanup_string) = NULL; + size_t line_buf_size = 0; + ssize_t num_read; + struct stat statbuf; + do { + num_read = getline(&line_buf, &line_buf_size, cgroup_procs); + if (num_read < 0 && errno != 0) { + die("cannot read next PID belonging to snap %s", + snap_name); + } + if (num_read <= 0) { + break; + } else { + if (line_buf[num_read - 1] == '\n') { + line_buf[num_read - 1] = '\0'; + } else { + die("could not find newline in cgroup.procs"); + } + } + debug("found process id: %s\n", line_buf); + + if (fstatat(proc_fd, line_buf, &statbuf, AT_SYMLINK_NOFOLLOW) < + 0) { + // The process may have died already. + if (errno != ENOENT) { + die("cannot stat /proc/%s", line_buf); + } + } + debug("found process %s belonging to user %d", + line_buf, statbuf.st_uid); + return true; + } while (num_read > 0); + + return false; +} diff -Nru snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/cgroup-freezer-support.h snapd-2.31.1+17.10/cmd/libsnap-confine-private/cgroup-freezer-support.h --- snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/cgroup-freezer-support.h 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/libsnap-confine-private/cgroup-freezer-support.h 2018-01-24 20:02:44.000000000 +0000 @@ -23,4 +23,13 @@ **/ void sc_cgroup_freezer_join(const char *snap_name, pid_t pid); +/** + * Check if a freezer cgroup for given snap has any processes belonging to a given user. + * + * This function examines the freezer cgroup called "snap.$snap_name" and looks + * at each of its processes. If any process exists then the function returns true. +**/ +// TODO: Support per user filtering for eventual per-user mount namespaces +bool sc_cgroup_freezer_occupied(const char *snap_name); + #endif diff -Nru snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/classic.h snapd-2.31.1+17.10/cmd/libsnap-confine-private/classic.h --- snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/classic.h 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/libsnap-confine-private/classic.h 2017-12-01 15:51:55.000000000 +0000 @@ -22,6 +22,6 @@ // Location of the host filesystem directory in the core snap. #define SC_HOSTFS_DIR "/var/lib/snapd/hostfs" -bool is_running_on_classic_distribution(); +bool is_running_on_classic_distribution(void); #endif diff -Nru snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/classic-test.c snapd-2.31.1+17.10/cmd/libsnap-confine-private/classic-test.c --- snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/classic-test.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/libsnap-confine-private/classic-test.c 2017-12-01 15:51:55.000000000 +0000 @@ -24,7 +24,7 @@ "NAME=\"Ubuntu\"\n" "VERSION=\"17.04 (Zesty Zapus)\"\n" "ID=ubuntu\n" "ID_LIKE=debian\n"; -static void test_is_on_classic() +static void test_is_on_classic(void) { g_file_set_contents("os-release.classic", os_release_classic, strlen(os_release_classic), NULL); @@ -36,7 +36,7 @@ const char *os_release_core = "" "NAME=\"Ubuntu Core\"\n" "VERSION=\"16\"\n" "ID=ubuntu-core\n"; -static void test_is_on_core() +static void test_is_on_core(void) { g_file_set_contents("os-release.core", os_release_core, strlen(os_release_core), NULL); @@ -52,7 +52,7 @@ "ID_LIKE=debian\n" "LONG=line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line."; -static void test_is_on_classic_with_long_line() +static void test_is_on_classic_with_long_line(void) { g_file_set_contents("os-release.classic-with-long-line", os_release_classic, strlen(os_release_classic), @@ -62,7 +62,7 @@ unlink("os-release.classic-with-long-line"); } -static void __attribute__ ((constructor)) init() +static void __attribute__ ((constructor)) init(void) { g_test_add_func("/classic/on-classic", test_is_on_classic); g_test_add_func("/classic/on-classic-with-long-line", diff -Nru snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/cleanup-funcs-test.c snapd-2.31.1+17.10/cmd/libsnap-confine-private/cleanup-funcs-test.c --- snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/cleanup-funcs-test.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/libsnap-confine-private/cleanup-funcs-test.c 2017-12-01 15:51:55.000000000 +0000 @@ -20,22 +20,25 @@ #include +static int called = 0; + +static void cleanup_fn(int *ptr) +{ + called = 1; +} + // Test that cleanup functions are applied as expected -static void test_cleanup_sanity() +static void test_cleanup_sanity(void) { - int called = 0; - void fn(int *ptr) { - called = 1; - } { - int test SC_CLEANUP(fn); + int test SC_CLEANUP(cleanup_fn); test = 0; test++; } g_assert_cmpint(called, ==, 1); } -static void __attribute__ ((constructor)) init() +static void __attribute__ ((constructor)) init(void) { g_test_add_func("/cleanup/sanity", test_cleanup_sanity); } diff -Nru snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/error-test.c snapd-2.31.1+17.10/cmd/libsnap-confine-private/error-test.c --- snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/error-test.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/libsnap-confine-private/error-test.c 2017-12-01 15:51:55.000000000 +0000 @@ -21,7 +21,7 @@ #include #include -static void test_sc_error_init() +static void test_sc_error_init(void) { struct sc_error *err; // Create an error @@ -35,7 +35,7 @@ g_assert_cmpstr(sc_error_msg(err), ==, "printer is on fire"); } -static void test_sc_error_init_from_errno() +static void test_sc_error_init_from_errno(void) { struct sc_error *err; // Create an error @@ -49,7 +49,7 @@ g_assert_cmpstr(sc_error_msg(err), ==, "printer is on fire"); } -static void test_sc_error_cleanup() +static void test_sc_error_cleanup(void) { // Check that sc_error_cleanup() is safe to use. @@ -64,7 +64,7 @@ g_assert_null(err); } -static void test_sc_error_domain__NULL() +static void test_sc_error_domain__NULL(void) { // Check that sc_error_domain() dies if called with NULL error. if (g_test_subprocess()) { @@ -82,7 +82,7 @@ ("cannot obtain error domain from NULL error\n"); } -static void test_sc_error_code__NULL() +static void test_sc_error_code__NULL(void) { // Check that sc_error_code() dies if called with NULL error. if (g_test_subprocess()) { @@ -99,7 +99,7 @@ g_test_trap_assert_stderr("cannot obtain error code from NULL error\n"); } -static void test_sc_error_msg__NULL() +static void test_sc_error_msg__NULL(void) { // Check that sc_error_msg() dies if called with NULL error. if (g_test_subprocess()) { @@ -117,7 +117,7 @@ ("cannot obtain error message from NULL error\n"); } -static void test_sc_die_on_error__NULL() +static void test_sc_die_on_error__NULL(void) { // Check that sc_die_on_error() does nothing if called with NULL error. if (g_test_subprocess()) { @@ -128,7 +128,7 @@ g_test_trap_assert_passed(); } -static void test_sc_die_on_error__regular() +static void test_sc_die_on_error__regular(void) { // Check that sc_die_on_error() dies if called with an error. if (g_test_subprocess()) { @@ -142,7 +142,7 @@ g_test_trap_assert_stderr("just testing\n"); } -static void test_sc_die_on_error__errno() +static void test_sc_die_on_error__errno(void) { // Check that sc_die_on_error() dies if called with an errno-based error. if (g_test_subprocess()) { @@ -156,7 +156,7 @@ g_test_trap_assert_stderr("just testing: No such file or directory\n"); } -static void test_sc_error_forward__nothing() +static void test_sc_error_forward__nothing(void) { // Check that forwarding NULL does exactly that. struct sc_error *recipient = (void *)0xDEADBEEF; @@ -165,7 +165,7 @@ g_assert_null(recipient); } -static void test_sc_error_forward__something_somewhere() +static void test_sc_error_forward__something_somewhere(void) { // Check that forwarding a real error works OK. struct sc_error *recipient = NULL; @@ -176,7 +176,7 @@ g_assert_nonnull(recipient); } -static void test_sc_error_forward__something_nowhere() +static void test_sc_error_forward__something_nowhere(void) { // Check that forwarding a real error nowhere calls die() if (g_test_subprocess()) { @@ -196,7 +196,7 @@ g_test_trap_assert_stderr("just testing\n"); } -static void test_sc_error_match__typical() +static void test_sc_error_match__typical(void) { // NULL error doesn't match anything. g_assert_false(sc_error_match(NULL, "domain", 42)); @@ -210,7 +210,7 @@ g_assert_false(sc_error_match(err, "other-domain", 1)); } -static void test_sc_error_match__NULL_domain() +static void test_sc_error_match__NULL_domain(void) { // Using a NULL domain is a fatal bug. if (g_test_subprocess()) { @@ -227,7 +227,7 @@ g_test_trap_assert_stderr("cannot match error to a NULL domain\n"); } -static void __attribute__ ((constructor)) init() +static void __attribute__ ((constructor)) init(void) { g_test_add_func("/error/sc_error_init", test_sc_error_init); g_test_add_func("/error/sc_error_init_from_errno", diff -Nru snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/fault-injection.c snapd-2.31.1+17.10/cmd/libsnap-confine-private/fault-injection.c --- snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/fault-injection.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/libsnap-confine-private/fault-injection.c 2017-12-01 15:51:55.000000000 +0000 @@ -57,7 +57,7 @@ sc_faults = fault; } -void sc_reset_faults() +void sc_reset_faults(void) { struct sc_fault *next_fault; for (struct sc_fault * fault = sc_faults; fault != NULL; diff -Nru snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/fault-injection.h snapd-2.31.1+17.10/cmd/libsnap-confine-private/fault-injection.h --- snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/fault-injection.h 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/libsnap-confine-private/fault-injection.h 2017-12-01 15:51:55.000000000 +0000 @@ -60,7 +60,7 @@ /** * Remove all the injected faults. **/ -void sc_reset_faults(); +void sc_reset_faults(void); #endif // ifndef _ENABLE_FAULT_INJECTION diff -Nru snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/fault-injection-test.c snapd-2.31.1+17.10/cmd/libsnap-confine-private/fault-injection-test.c --- snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/fault-injection-test.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/libsnap-confine-private/fault-injection-test.c 2017-12-01 15:51:55.000000000 +0000 @@ -33,7 +33,7 @@ return true; } -static void test_fault_injection() +static void test_fault_injection(void) { g_assert_false(sc_faulty("foo", NULL)); @@ -57,7 +57,7 @@ sc_reset_faults(); } -static void __attribute__ ((constructor)) init() +static void __attribute__ ((constructor)) init(void) { g_test_add_func("/fault-injection", test_fault_injection); } diff -Nru snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/locking.c snapd-2.31.1+17.10/cmd/libsnap-confine-private/locking.c --- snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/locking.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/libsnap-confine-private/locking.c 2017-12-01 15:51:55.000000000 +0000 @@ -46,7 +46,7 @@ sanity_timeout_expired = 1; } -void sc_enable_sanity_timeout() +void sc_enable_sanity_timeout(void) { sanity_timeout_expired = 0; struct sigaction act = {.sa_handler = sc_SIGALRM_handler }; @@ -64,7 +64,7 @@ debug("sanity timeout initialized and set for three seconds"); } -void sc_disable_sanity_timeout() +void sc_disable_sanity_timeout(void) { if (sanity_timeout_expired) { die("sanity timeout expired"); @@ -134,7 +134,7 @@ close(lock_fd); } -int sc_lock_global() +int sc_lock_global(void) { return sc_lock(NULL); } diff -Nru snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/locking.h snapd-2.31.1+17.10/cmd/libsnap-confine-private/locking.h --- snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/locking.h 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/libsnap-confine-private/locking.h 2017-12-01 15:51:55.000000000 +0000 @@ -50,7 +50,7 @@ * This function is exactly like sc_lock(NULL), that is the acquired lock is * not specific to any snap but global. **/ -int sc_lock_global(); +int sc_lock_global(void); /** * Release a flock-based, globally scoped, lock @@ -71,7 +71,7 @@ * disables the alarm and acts on the flag, aborting the process if the timeout * gets exceeded. **/ -void sc_enable_sanity_timeout(); +void sc_enable_sanity_timeout(void); /** * Disable sanity-check timeout and abort the process if it expired. @@ -79,6 +79,6 @@ * This call has to be paired with sc_enable_sanity_timeout(), see the function * description for more details. **/ -void sc_disable_sanity_timeout(); +void sc_disable_sanity_timeout(void); #endif // SNAP_CONFINE_LOCKING_H diff -Nru snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/locking-test.c snapd-2.31.1+17.10/cmd/libsnap-confine-private/locking-test.c --- snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/locking-test.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/libsnap-confine-private/locking-test.c 2017-12-01 15:51:55.000000000 +0000 @@ -36,7 +36,7 @@ // // The directory is automatically reset to the real value at the end of the // test. -static const char *sc_test_use_fake_lock_dir() +static const char *sc_test_use_fake_lock_dir(void) { char *lock_dir = NULL; if (g_test_subprocess()) { @@ -60,7 +60,7 @@ } // Check that locking a namespace actually flock's the mutex with LOCK_EX -static void test_sc_lock_unlock() +static void test_sc_lock_unlock(void) { const char *lock_dir = sc_test_use_fake_lock_dir(); int fd = sc_lock("foo"); @@ -86,7 +86,7 @@ g_assert_cmpint(err, ==, 0); } -static void test_sc_enable_sanity_timeout() +static void test_sc_enable_sanity_timeout(void) { if (g_test_subprocess()) { sc_enable_sanity_timeout(); @@ -101,7 +101,7 @@ g_test_trap_assert_failed(); } -static void __attribute__ ((constructor)) init() +static void __attribute__ ((constructor)) init(void) { g_test_add_func("/locking/sc_lock_unlock", test_sc_lock_unlock); g_test_add_func("/locking/sc_enable_sanity_timeout", diff -Nru snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/mountinfo.c snapd-2.31.1+17.10/cmd/libsnap-confine-private/mountinfo.c --- snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/mountinfo.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/libsnap-confine-private/mountinfo.c 2017-12-01 15:51:55.000000000 +0000 @@ -111,6 +111,60 @@ return info; } +static void show_buffers(const char *line, int offset, + struct sc_mountinfo_entry *entry) +{ +#ifdef MOUNTINFO_DEBUG + fprintf(stderr, "Input buffer (first), with offset arrow\n"); + fprintf(stderr, "Output buffer (second)\n"); + + fputc(' ', stderr); + for (int i = 0; i < offset - 1; ++i) + fputc('-', stderr); + fputc('v', stderr); + fputc('\n', stderr); + + fprintf(stderr, ">%s<\n", line); + + fputc('>', stderr); + for (int i = 0; i < strlen(line); ++i) { + int c = entry->line_buf[i]; + fputc(c == 0 ? '@' : c == 1 ? '#' : c, stderr); + } + fputc('<', stderr); + fputc('\n', stderr); + + fputc('>', stderr); + for (int i = 0; i < strlen(line); ++i) + fputc('=', stderr); + fputc('<', stderr); + fputc('\n', stderr); +#endif // MOUNTINFO_DEBUG +} + +static char *parse_next_string_field(struct sc_mountinfo_entry *entry, + const char *line, int *offset) +{ + int offset_delta = 0; + char *field = &entry->line_buf[0] + *offset; + if (line[*offset] == ' ') { + // Special case for empty fields which cannot be parsed with %s. + *field = '\0'; + *offset += 1; + } else { + int nscanned = + sscanf(line + *offset, "%s%n", field, &offset_delta); + if (nscanned != 1) + return NULL; + *offset += offset_delta; + if (line[*offset] == ' ') { + *offset += 1; + } + } + show_buffers(line, *offset, entry); + return field; +} + static struct sc_mountinfo_entry *sc_parse_mountinfo_entry(const char *line) { // NOTE: the sc_mountinfo structure is allocated along with enough extra @@ -154,58 +208,22 @@ goto fail; offset += offset_delta; - void show_buffers() { -#ifdef MOUNTINFO_DEBUG - fprintf(stderr, "Input buffer (first), with offset arrow\n"); - fprintf(stderr, "Output buffer (second)\n"); + show_buffers(line, offset, entry); - fputc(' ', stderr); - for (int i = 0; i < offset - 1; ++i) - fputc('-', stderr); - fputc('v', stderr); - fputc('\n', stderr); - - fprintf(stderr, ">%s<\n", line); - - fputc('>', stderr); - for (int i = 0; i < strlen(line); ++i) { - int c = entry->line_buf[i]; - fputc(c == 0 ? '@' : c == 1 ? '#' : c, stderr); - } - fputc('<', stderr); - fputc('\n', stderr); - - fputc('>', stderr); - for (int i = 0; i < strlen(line); ++i) - fputc('=', stderr); - fputc('<', stderr); - fputc('\n', stderr); -#endif // MOUNTINFO_DEBUG - } - - show_buffers(); - - char *parse_next_string_field() { - char *field = &entry->line_buf[0] + offset; - int nscanned = - sscanf(line + offset, "%s %n", field, &offset_delta); - if (nscanned != 1) - return NULL; - offset += offset_delta; - show_buffers(); - return field; - } - if ((entry->root = parse_next_string_field()) == NULL) + if ((entry->root = + parse_next_string_field(entry, line, &offset)) == NULL) goto fail; - if ((entry->mount_dir = parse_next_string_field()) == NULL) + if ((entry->mount_dir = + parse_next_string_field(entry, line, &offset)) == NULL) goto fail; - if ((entry->mount_opts = parse_next_string_field()) == NULL) + if ((entry->mount_opts = + parse_next_string_field(entry, line, &offset)) == NULL) goto fail; entry->optional_fields = &entry->line_buf[0] + offset; // NOTE: This ensures that optional_fields is never NULL. If this changes, // must adjust all callers of parse_mountinfo_entry() accordingly. for (int field_num = 0;; ++field_num) { - char *opt_field = parse_next_string_field(); + char *opt_field = parse_next_string_field(entry, line, &offset); if (opt_field == NULL) goto fail; if (strcmp(opt_field, "-") == 0) { @@ -216,13 +234,16 @@ opt_field[-1] = ' '; } } - if ((entry->fs_type = parse_next_string_field()) == NULL) + if ((entry->fs_type = + parse_next_string_field(entry, line, &offset)) == NULL) goto fail; - if ((entry->mount_source = parse_next_string_field()) == NULL) + if ((entry->mount_source = + parse_next_string_field(entry, line, &offset)) == NULL) goto fail; - if ((entry->super_opts = parse_next_string_field()) == NULL) + if ((entry->super_opts = + parse_next_string_field(entry, line, &offset)) == NULL) goto fail; - show_buffers(); + show_buffers(line, offset, entry); return entry; fail: free(entry); diff -Nru snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/mountinfo-test.c snapd-2.31.1+17.10/cmd/libsnap-confine-private/mountinfo-test.c --- snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/mountinfo-test.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/libsnap-confine-private/mountinfo-test.c 2017-12-01 15:51:55.000000000 +0000 @@ -20,7 +20,7 @@ #include -static void test_parse_mountinfo_entry__sysfs() +static void test_parse_mountinfo_entry__sysfs(void) { const char *line = "19 25 0:18 / /sys rw,nosuid,nodev,noexec,relatime shared:7 - sysfs sysfs rw"; @@ -44,7 +44,7 @@ // Parse the /run/snapd/ns bind mount (over itself) // Note that /run is itself a tmpfs mount point. -static void test_parse_mountinfo_entry__snapd_ns() +static void test_parse_mountinfo_entry__snapd_ns(void) { const char *line = "104 23 0:19 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=99840k,mode=755"; @@ -65,7 +65,7 @@ g_assert_null(entry->next); } -static void test_parse_mountinfo_entry__snapd_mnt() +static void test_parse_mountinfo_entry__snapd_mnt(void) { const char *line = "256 104 0:3 mnt:[4026532509] /run/snapd/ns/hello-world.mnt rw - nsfs nsfs rw"; @@ -86,14 +86,14 @@ g_assert_null(entry->next); } -static void test_parse_mountinfo_entry__garbage() +static void test_parse_mountinfo_entry__garbage(void) { const char *line = "256 104 0:3"; struct sc_mountinfo_entry *entry = sc_parse_mountinfo_entry(line); g_assert_null(entry); } -static void test_parse_mountinfo_entry__no_tags() +static void test_parse_mountinfo_entry__no_tags(void) { const char *line = "1 2 3:4 root mount-dir mount-opts - fs-type mount-source super-opts"; @@ -114,7 +114,7 @@ g_assert_null(entry->next); } -static void test_parse_mountinfo_entry__one_tag() +static void test_parse_mountinfo_entry__one_tag(void) { const char *line = "1 2 3:4 root mount-dir mount-opts tag:1 - fs-type mount-source super-opts"; @@ -135,7 +135,7 @@ g_assert_null(entry->next); } -static void test_parse_mountinfo_entry__many_tags() +static void test_parse_mountinfo_entry__many_tags(void) { const char *line = "1 2 3:4 root mount-dir mount-opts tag:1 tag:2 tag:3 tag:4 - fs-type mount-source super-opts"; @@ -156,7 +156,29 @@ g_assert_null(entry->next); } -static void __attribute__ ((constructor)) init() +static void test_parse_mountinfo_entry__empty_source(void) +{ + const char *line = + "304 301 0:45 / /snap/test-snapd-content-advanced-plug/x1 rw,relatime - tmpfs rw"; + struct sc_mountinfo_entry *entry = sc_parse_mountinfo_entry(line); + g_assert_nonnull(entry); + g_test_queue_destroy((GDestroyNotify) sc_free_mountinfo_entry, entry); + g_assert_cmpint(entry->mount_id, ==, 304); + g_assert_cmpint(entry->parent_id, ==, 301); + g_assert_cmpint(entry->dev_major, ==, 0); + g_assert_cmpint(entry->dev_minor, ==, 45); + g_assert_cmpstr(entry->root, ==, "/"); + g_assert_cmpstr(entry->mount_dir, ==, + "/snap/test-snapd-content-advanced-plug/x1"); + g_assert_cmpstr(entry->mount_opts, ==, "rw,relatime"); + g_assert_cmpstr(entry->optional_fields, ==, ""); + g_assert_cmpstr(entry->fs_type, ==, "tmpfs"); + g_assert_cmpstr(entry->mount_source, ==, ""); + g_assert_cmpstr(entry->super_opts, ==, "rw"); + g_assert_null(entry->next); +} + +static void __attribute__ ((constructor)) init(void) { g_test_add_func("/mountinfo/parse_mountinfo_entry/sysfs", test_parse_mountinfo_entry__sysfs); @@ -172,4 +194,7 @@ test_parse_mountinfo_entry__one_tag); g_test_add_func("/mountinfo/parse_mountinfo_entry/many_tags", test_parse_mountinfo_entry__many_tags); + g_test_add_func + ("/mountinfo/parse_mountinfo_entry/empty_source", + test_parse_mountinfo_entry__empty_source); } diff -Nru snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/mount-opt.c snapd-2.31.1+17.10/cmd/libsnap-confine-private/mount-opt.c --- snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/mount-opt.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/libsnap-confine-private/mount-opt.c 2018-01-24 20:02:44.000000000 +0000 @@ -33,7 +33,13 @@ { unsigned long used = 0; sc_string_init(buf, buf_size); -#define F(FLAG, TEXT) do if (flags & (FLAG)) { sc_string_append(buf, buf_size, #TEXT ","); flags ^= (FLAG); } while (0) + +#define F(FLAG, TEXT) do { \ + if (flags & (FLAG)) { \ + sc_string_append(buf, buf_size, #TEXT ","); flags ^= (FLAG); \ + } \ + } while (0) + F(MS_RDONLY, ro); F(MS_NOSUID, nosuid); F(MS_NODEV, nodev); @@ -245,6 +251,11 @@ return buf; } +#ifndef SNAP_CONFINE_DEBUG_BUILD +static const char *use_debug_build = + "(disabled) use debug build to see details"; +#endif + void sc_do_mount(const char *source, const char *target, const char *fs_type, unsigned long mountflags, const void *data) @@ -252,19 +263,12 @@ char buf[10000] = { 0 }; const char *mount_cmd = NULL; - void ensure_mount_cmd() { - if (mount_cmd != NULL) { - return; - } - mount_cmd = sc_mount_cmd(buf, sizeof buf, source, - target, fs_type, mountflags, data); - } - if (sc_is_debug_enabled()) { #ifdef SNAP_CONFINE_DEBUG_BUILD - ensure_mount_cmd(); + mount_cmd = sc_mount_cmd(buf, sizeof(buf), source, + target, fs_type, mountflags, data); #else - mount_cmd = "(disabled) use debug build to see details"; + mount_cmd = use_debug_build; #endif debug("performing operation: %s", mount_cmd); } @@ -278,8 +282,8 @@ sc_privs_drop(); // Compute the equivalent mount command. - ensure_mount_cmd(); - + mount_cmd = sc_mount_cmd(buf, sizeof(buf), source, + target, fs_type, mountflags, data); // Restore errno and die. errno = saved_errno; die("cannot perform operation: %s", mount_cmd); @@ -291,18 +295,11 @@ char buf[10000] = { 0 }; const char *umount_cmd = NULL; - void ensure_umount_cmd() { - if (umount_cmd != NULL) { - return; - } - umount_cmd = sc_umount_cmd(buf, sizeof buf, target, flags); - } - if (sc_is_debug_enabled()) { #ifdef SNAP_CONFINE_DEBUG_BUILD - ensure_umount_cmd(); + umount_cmd = sc_umount_cmd(buf, sizeof(buf), target, flags); #else - umount_cmd = "(disabled) use debug build to see details"; + umount_cmd = use_debug_build; #endif debug("performing operation: %s", umount_cmd); } @@ -315,8 +312,7 @@ sc_privs_drop(); // Compute the equivalent umount command. - ensure_umount_cmd(); - + umount_cmd = sc_umount_cmd(buf, sizeof(buf), target, flags); // Restore errno and die. errno = saved_errno; die("cannot perform operation: %s", umount_cmd); diff -Nru snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/mount-opt-test.c snapd-2.31.1+17.10/cmd/libsnap-confine-private/mount-opt-test.c --- snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/mount-opt-test.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/libsnap-confine-private/mount-opt-test.c 2018-01-24 20:02:44.000000000 +0000 @@ -23,7 +23,7 @@ #include -static void test_sc_mount_opt2str() +static void test_sc_mount_opt2str(void) { char buf[1000] = { 0 }; g_assert_cmpstr(sc_mount_opt2str(buf, sizeof buf, 0), ==, ""); @@ -91,7 +91,7 @@ "ro,noexec,bind"); } -static void test_sc_mount_cmd() +static void test_sc_mount_cmd(void) { char cmd[10000] = { 0 }; @@ -174,7 +174,7 @@ g_assert_cmpstr(cmd, ==, expected); } -static void test_sc_umount_cmd() +static void test_sc_umount_cmd(void) { char cmd[1000] = { 0 }; @@ -205,14 +205,19 @@ "umount --force --lazy --expire --no-follow /mnt/foo"); } -static void test_sc_do_mount() +static bool broken_mount(struct sc_fault_state *state, void *ptr) +{ + errno = EACCES; + return true; +} + +static void test_sc_do_mount(gconstpointer snap_debug) { if (g_test_subprocess()) { - bool broken_mount(struct sc_fault_state *state, void *ptr) { - errno = EACCES; - return true; - } sc_break("mount", broken_mount); + if (GPOINTER_TO_INT(snap_debug) == 1) { + g_setenv("SNAP_CONFINE_DEBUG", "1", true); + } sc_do_mount("/foo", "/bar", "ext4", MS_RDONLY, NULL); g_test_message("expected sc_do_mount not to return"); @@ -222,18 +227,32 @@ } g_test_trap_subprocess(NULL, 0, 0); g_test_trap_assert_failed(); - g_test_trap_assert_stderr - ("cannot perform operation: mount -t ext4 -o ro /foo /bar: Permission denied\n"); + if (GPOINTER_TO_INT(snap_debug) == 0) { + g_test_trap_assert_stderr + ("cannot perform operation: mount -t ext4 -o ro /foo /bar: Permission denied\n"); + } else { + /* with snap_debug the debug output hides the actual mount commands *but* + * they are still shown if there was an error + */ + g_test_trap_assert_stderr + ("DEBUG: performing operation: (disabled) use debug build to see details\n" + "cannot perform operation: mount -t ext4 -o ro /foo /bar: Permission denied\n"); + } } -static void test_sc_do_umount() +static bool broken_umount(struct sc_fault_state *state, void *ptr) +{ + errno = EACCES; + return true; +} + +static void test_sc_do_umount(gconstpointer snap_debug) { if (g_test_subprocess()) { - bool broken_umount(struct sc_fault_state *state, void *ptr) { - errno = EACCES; - return true; - } sc_break("umount", broken_umount); + if (GPOINTER_TO_INT(snap_debug) == 1) { + g_setenv("SNAP_CONFINE_DEBUG", "1", true); + } sc_do_umount("/foo", MNT_DETACH); g_test_message("expected sc_do_umount not to return"); @@ -243,15 +262,30 @@ } g_test_trap_subprocess(NULL, 0, 0); g_test_trap_assert_failed(); - g_test_trap_assert_stderr - ("cannot perform operation: umount --lazy /foo: Permission denied\n"); + if (GPOINTER_TO_INT(snap_debug) == 0) { + g_test_trap_assert_stderr + ("cannot perform operation: umount --lazy /foo: Permission denied\n"); + } else { + /* with snap_debug the debug output hides the actual mount commands *but* + * they are still shown if there was an error + */ + g_test_trap_assert_stderr + ("DEBUG: performing operation: (disabled) use debug build to see details\n" + "cannot perform operation: umount --lazy /foo: Permission denied\n"); + } } -static void __attribute__ ((constructor)) init() +static void __attribute__ ((constructor)) init(void) { g_test_add_func("/mount/sc_mount_opt2str", test_sc_mount_opt2str); g_test_add_func("/mount/sc_mount_cmd", test_sc_mount_cmd); g_test_add_func("/mount/sc_umount_cmd", test_sc_umount_cmd); - g_test_add_func("/mount/sc_do_mount", test_sc_do_mount); - g_test_add_func("/mount/sc_do_umount", test_sc_do_umount); + g_test_add_data_func("/mount/sc_do_mount", GINT_TO_POINTER(0), + test_sc_do_mount); + g_test_add_data_func("/mount/sc_do_umount", GINT_TO_POINTER(0), + test_sc_do_umount); + g_test_add_data_func("/mount/sc_do_mount_with_debug", + GINT_TO_POINTER(1), test_sc_do_mount); + g_test_add_data_func("/mount/sc_do_umount_with_debug", + GINT_TO_POINTER(1), test_sc_do_umount); } diff -Nru snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/privs.c snapd-2.31.1+17.10/cmd/libsnap-confine-private/privs.c --- snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/privs.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/libsnap-confine-private/privs.c 2017-12-01 15:51:55.000000000 +0000 @@ -55,7 +55,7 @@ return cap_flags_value == CAP_SET; } -void sc_privs_drop() +void sc_privs_drop(void) { gid_t gid = getgid(); uid_t uid = getuid(); diff -Nru snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/privs.h snapd-2.31.1+17.10/cmd/libsnap-confine-private/privs.h --- snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/privs.h 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/libsnap-confine-private/privs.h 2017-12-01 15:51:55.000000000 +0000 @@ -33,6 +33,6 @@ * dropped. When the process itself was started by root then this function does * nothing at all. **/ -void sc_privs_drop(); +void sc_privs_drop(void); #endif diff -Nru snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/privs-test.c snapd-2.31.1+17.10/cmd/libsnap-confine-private/privs-test.c --- snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/privs-test.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/libsnap-confine-private/privs-test.c 2017-12-01 15:51:55.000000000 +0000 @@ -21,7 +21,7 @@ #include // Test that dropping permissions really works -static void test_sc_privs_drop() +static void test_sc_privs_drop(void) { if (geteuid() != 0 || getuid() == 0) { g_test_skip("run this test after chown root.root; chmod u+s"); @@ -61,7 +61,7 @@ g_test_trap_assert_passed(); } -static void __attribute__ ((constructor)) init() +static void __attribute__ ((constructor)) init(void) { g_test_add_func("/privs/sc_privs_drop", test_sc_privs_drop); } diff -Nru snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/snap-test.c snapd-2.31.1+17.10/cmd/libsnap-confine-private/snap-test.c --- snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/snap-test.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/libsnap-confine-private/snap-test.c 2017-12-01 15:51:55.000000000 +0000 @@ -20,7 +20,7 @@ #include -static void test_verify_security_tag() +static void test_verify_security_tag(void) { // First, test the names we know are good g_assert_true(verify_security_tag("snap.name.app", "name")); @@ -77,7 +77,7 @@ } -static void test_sc_snap_name_validate() +static void test_sc_snap_name_validate(void) { struct sc_error *err = NULL; @@ -144,7 +144,7 @@ "a0", "a-0", "a-0a", "01game", "1-or-2" }; - for (int i = 0; i < sizeof valid_names / sizeof *valid_names; ++i) { + for (size_t i = 0; i < sizeof valid_names / sizeof *valid_names; ++i) { g_test_message("checking valid snap name: %s", valid_names[i]); sc_snap_name_validate(valid_names[i], &err); g_assert_null(err); @@ -165,7 +165,8 @@ // identifier must be plain ASCII "日本語", "한글", "ру́сский язы́к", }; - for (int i = 0; i < sizeof invalid_names / sizeof *invalid_names; ++i) { + for (size_t i = 0; i < sizeof invalid_names / sizeof *invalid_names; + ++i) { g_test_message("checking invalid snap name: >%s<", invalid_names[i]); sc_snap_name_validate(invalid_names[i], &err); @@ -182,7 +183,7 @@ } -static void test_sc_snap_name_validate__respects_error_protocol() +static void test_sc_snap_name_validate__respects_error_protocol(void) { if (g_test_subprocess()) { sc_snap_name_validate("hello world", NULL); @@ -196,7 +197,7 @@ ("snap name must use lower case letters, digits or dashes\n"); } -static void __attribute__ ((constructor)) init() +static void __attribute__ ((constructor)) init(void) { g_test_add_func("/snap/verify_security_tag", test_verify_security_tag); g_test_add_func("/snap/sc_snap_name_validate", diff -Nru snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/string-utils.c snapd-2.31.1+17.10/cmd/libsnap-confine-private/string-utils.c --- snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/string-utils.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/libsnap-confine-private/string-utils.c 2017-12-01 15:51:55.000000000 +0000 @@ -65,7 +65,7 @@ n = vsnprintf(str, size, format, va); va_end(va); - if (n < 0 || n >= size) + if (n < 0 || (size_t) n >= size) die("cannot format string: %s", str); return n; diff -Nru snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/string-utils-test.c snapd-2.31.1+17.10/cmd/libsnap-confine-private/string-utils-test.c --- snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/string-utils-test.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/libsnap-confine-private/string-utils-test.c 2017-12-01 15:51:55.000000000 +0000 @@ -20,7 +20,7 @@ #include -static void test_sc_streq() +static void test_sc_streq(void) { g_assert_false(sc_streq(NULL, NULL)); g_assert_false(sc_streq(NULL, "text")); @@ -32,7 +32,7 @@ g_assert_true(sc_streq("", "")); } -static void test_sc_endswith() +static void test_sc_endswith(void) { // NULL doesn't end with anything, nothing ends with NULL g_assert_false(sc_endswith("", NULL)); @@ -53,14 +53,14 @@ g_assert_false(sc_endswith("ba", "bar")); } -static void test_sc_must_snprintf() +static void test_sc_must_snprintf(void) { char buf[5] = { 0 }; sc_must_snprintf(buf, sizeof buf, "1234"); g_assert_cmpstr(buf, ==, "1234"); } -static void test_sc_must_snprintf__fail() +static void test_sc_must_snprintf__fail(void) { if (g_test_subprocess()) { char buf[5]; @@ -75,7 +75,7 @@ } // Check that appending to a buffer works OK. -static void test_sc_string_append() +static void test_sc_string_append(void) { union { char bigbuf[6]; @@ -106,7 +106,7 @@ } // Check that appending an empty string to a full buffer is valid. -static void test_sc_string_append__empty_to_full() +static void test_sc_string_append__empty_to_full(void) { union { char bigbuf[6]; @@ -137,7 +137,7 @@ } // Check that the overflow detection works. -static void test_sc_string_append__overflow() +static void test_sc_string_append__overflow(void) { if (g_test_subprocess()) { char buf[4] = { 0 }; @@ -156,7 +156,7 @@ } // Check that the uninitialized buffer detection works. -static void test_sc_string_append__uninitialized_buf() +static void test_sc_string_append__uninitialized_buf(void) { if (g_test_subprocess()) { char buf[4] = { 0xFF, 0xFF, 0xFF, 0xFF }; @@ -175,7 +175,7 @@ } // Check that `buf' cannot be NULL. -static void test_sc_string_append__NULL_buf() +static void test_sc_string_append__NULL_buf(void) { if (g_test_subprocess()) { char buf[4]; @@ -192,7 +192,7 @@ } // Check that `src' cannot be NULL. -static void test_sc_string_append__NULL_str() +static void test_sc_string_append__NULL_str(void) { if (g_test_subprocess()) { char buf[4]; @@ -208,7 +208,7 @@ g_test_trap_assert_stderr("cannot append string: string is NULL\n"); } -static void test_sc_string_init__normal() +static void test_sc_string_init__normal(void) { char buf[1] = { 0xFF }; @@ -216,7 +216,7 @@ g_assert_cmpint(buf[0], ==, 0); } -static void test_sc_string_init__empty_buf() +static void test_sc_string_init__empty_buf(void) { if (g_test_subprocess()) { char buf[1] = { 0xFF }; @@ -233,7 +233,7 @@ ("cannot initialize string, buffer is too small\n"); } -static void test_sc_string_init__NULL_buf() +static void test_sc_string_init__NULL_buf(void) { if (g_test_subprocess()) { sc_string_init(NULL, 1); @@ -247,7 +247,7 @@ g_test_trap_assert_stderr("cannot initialize string, buffer is NULL\n"); } -static void test_sc_string_append_char__uninitialized_buf() +static void test_sc_string_append_char__uninitialized_buf(void) { if (g_test_subprocess()) { char buf[2] = { 0xFF, 0xFF }; @@ -263,7 +263,7 @@ ("cannot append character: dst is unterminated\n"); } -static void test_sc_string_append_char__NULL_buf() +static void test_sc_string_append_char__NULL_buf(void) { if (g_test_subprocess()) { sc_string_append_char(NULL, 2, 'a'); @@ -277,7 +277,7 @@ g_test_trap_assert_stderr("cannot append character: buffer is NULL\n"); } -static void test_sc_string_append_char__overflow() +static void test_sc_string_append_char__overflow(void) { if (g_test_subprocess()) { char buf[1] = { 0 }; @@ -293,7 +293,7 @@ ("cannot append character: not enough space\n"); } -static void test_sc_string_append_char__invalid_zero() +static void test_sc_string_append_char__invalid_zero(void) { if (g_test_subprocess()) { char buf[2] = { 0 }; @@ -309,7 +309,7 @@ ("cannot append character: cannot append string terminator\n"); } -static void test_sc_string_append_char__normal() +static void test_sc_string_append_char__normal(void) { char buf[16]; size_t len; @@ -332,7 +332,7 @@ g_assert_cmpint(len, ==, 5); } -static void test_sc_string_append_char_pair__uninitialized_buf() +static void test_sc_string_append_char_pair__uninitialized_buf(void) { if (g_test_subprocess()) { char buf[3] = { 0xFF, 0xFF, 0xFF }; @@ -349,7 +349,7 @@ ("cannot append character pair: dst is unterminated\n"); } -static void test_sc_string_append_char_pair__NULL_buf() +static void test_sc_string_append_char_pair__NULL_buf(void) { if (g_test_subprocess()) { sc_string_append_char_pair(NULL, 3, 'a', 'b'); @@ -365,7 +365,7 @@ ("cannot append character pair: buffer is NULL\n"); } -static void test_sc_string_append_char_pair__overflow() +static void test_sc_string_append_char_pair__overflow(void) { if (g_test_subprocess()) { char buf[2] = { 0 }; @@ -382,7 +382,7 @@ ("cannot append character pair: not enough space\n"); } -static void test_sc_string_append_char_pair__invalid_zero_c1() +static void test_sc_string_append_char_pair__invalid_zero_c1(void) { if (g_test_subprocess()) { char buf[3] = { 0 }; @@ -399,7 +399,7 @@ ("cannot append character pair: cannot append string terminator\n"); } -static void test_sc_string_append_char_pair__invalid_zero_c2() +static void test_sc_string_append_char_pair__invalid_zero_c2(void) { if (g_test_subprocess()) { char buf[3] = { 0 }; @@ -416,7 +416,7 @@ ("cannot append character pair: cannot append string terminator\n"); } -static void test_sc_string_append_char_pair__normal() +static void test_sc_string_append_char_pair__normal(void) { char buf[16]; size_t len; @@ -433,7 +433,7 @@ g_assert_cmpint(len, ==, 6); } -static void test_sc_string_quote_NULL_str() +static void test_sc_string_quote_NULL_str(void) { if (g_test_subprocess()) { char buf[16] = { 0 }; @@ -448,302 +448,305 @@ g_test_trap_assert_stderr("cannot quote string: string is NULL\n"); } -static void test_sc_string_quote() +static void test_quoting_of(bool tested[], int c, const char *expected) { -#define DQ "\"" char buf[16]; - bool is_tested[256] = { false }; - void test_quoting_of(int c, const char *expected) { - g_assert_cmpint(c, >=, 0); - g_assert_cmpint(c, <=, 255); - - // Create an input string with one character. - char input[2] = { (unsigned char)c, 0 }; - sc_string_quote(buf, sizeof buf, input); + g_assert_cmpint(c, >=, 0); + g_assert_cmpint(c, <=, 255); - // Ensure it was quoted as we expected. - g_assert_cmpstr(buf, ==, expected); + // Create an input string with one character. + char input[2] = { (unsigned char)c, 0 }; + sc_string_quote(buf, sizeof buf, input); - is_tested[c] = true; - } + // Ensure it was quoted as we expected. + g_assert_cmpstr(buf, ==, expected); + + tested[c] = true; +} + +static void test_sc_string_quote(void) +{ +#define DQ "\"" + char buf[16]; + bool is_tested[256] = { false }; // Exhaustive test for quoting of every 8bit input. This is very verbose // but the goal is to have a very obvious and correct test that ensures no // edge case is lost. // // block 1: 0x00 - 0x0f - test_quoting_of(0x00, DQ "" DQ); - test_quoting_of(0x01, DQ "\\x01" DQ); - test_quoting_of(0x02, DQ "\\x02" DQ); - test_quoting_of(0x03, DQ "\\x03" DQ); - test_quoting_of(0x04, DQ "\\x04" DQ); - test_quoting_of(0x05, DQ "\\x05" DQ); - test_quoting_of(0x06, DQ "\\x06" DQ); - test_quoting_of(0x07, DQ "\\x07" DQ); - test_quoting_of(0x08, DQ "\\x08" DQ); - test_quoting_of(0x09, DQ "\\t" DQ); - test_quoting_of(0x0a, DQ "\\n" DQ); - test_quoting_of(0x0b, DQ "\\v" DQ); - test_quoting_of(0x0c, DQ "\\x0c" DQ); - test_quoting_of(0x0d, DQ "\\r" DQ); - test_quoting_of(0x0e, DQ "\\x0e" DQ); - test_quoting_of(0x0f, DQ "\\x0f" DQ); + test_quoting_of(is_tested, 0x00, DQ "" DQ); + test_quoting_of(is_tested, 0x01, DQ "\\x01" DQ); + test_quoting_of(is_tested, 0x02, DQ "\\x02" DQ); + test_quoting_of(is_tested, 0x03, DQ "\\x03" DQ); + test_quoting_of(is_tested, 0x04, DQ "\\x04" DQ); + test_quoting_of(is_tested, 0x05, DQ "\\x05" DQ); + test_quoting_of(is_tested, 0x06, DQ "\\x06" DQ); + test_quoting_of(is_tested, 0x07, DQ "\\x07" DQ); + test_quoting_of(is_tested, 0x08, DQ "\\x08" DQ); + test_quoting_of(is_tested, 0x09, DQ "\\t" DQ); + test_quoting_of(is_tested, 0x0a, DQ "\\n" DQ); + test_quoting_of(is_tested, 0x0b, DQ "\\v" DQ); + test_quoting_of(is_tested, 0x0c, DQ "\\x0c" DQ); + test_quoting_of(is_tested, 0x0d, DQ "\\r" DQ); + test_quoting_of(is_tested, 0x0e, DQ "\\x0e" DQ); + test_quoting_of(is_tested, 0x0f, DQ "\\x0f" DQ); // block 2: 0x10 - 0x1f - test_quoting_of(0x10, DQ "\\x10" DQ); - test_quoting_of(0x11, DQ "\\x11" DQ); - test_quoting_of(0x12, DQ "\\x12" DQ); - test_quoting_of(0x13, DQ "\\x13" DQ); - test_quoting_of(0x14, DQ "\\x14" DQ); - test_quoting_of(0x15, DQ "\\x15" DQ); - test_quoting_of(0x16, DQ "\\x16" DQ); - test_quoting_of(0x17, DQ "\\x17" DQ); - test_quoting_of(0x18, DQ "\\x18" DQ); - test_quoting_of(0x19, DQ "\\x19" DQ); - test_quoting_of(0x1a, DQ "\\x1a" DQ); - test_quoting_of(0x1b, DQ "\\x1b" DQ); - test_quoting_of(0x1c, DQ "\\x1c" DQ); - test_quoting_of(0x1d, DQ "\\x1d" DQ); - test_quoting_of(0x1e, DQ "\\x1e" DQ); - test_quoting_of(0x1f, DQ "\\x1f" DQ); + test_quoting_of(is_tested, 0x10, DQ "\\x10" DQ); + test_quoting_of(is_tested, 0x11, DQ "\\x11" DQ); + test_quoting_of(is_tested, 0x12, DQ "\\x12" DQ); + test_quoting_of(is_tested, 0x13, DQ "\\x13" DQ); + test_quoting_of(is_tested, 0x14, DQ "\\x14" DQ); + test_quoting_of(is_tested, 0x15, DQ "\\x15" DQ); + test_quoting_of(is_tested, 0x16, DQ "\\x16" DQ); + test_quoting_of(is_tested, 0x17, DQ "\\x17" DQ); + test_quoting_of(is_tested, 0x18, DQ "\\x18" DQ); + test_quoting_of(is_tested, 0x19, DQ "\\x19" DQ); + test_quoting_of(is_tested, 0x1a, DQ "\\x1a" DQ); + test_quoting_of(is_tested, 0x1b, DQ "\\x1b" DQ); + test_quoting_of(is_tested, 0x1c, DQ "\\x1c" DQ); + test_quoting_of(is_tested, 0x1d, DQ "\\x1d" DQ); + test_quoting_of(is_tested, 0x1e, DQ "\\x1e" DQ); + test_quoting_of(is_tested, 0x1f, DQ "\\x1f" DQ); // block 3: 0x20 - 0x2f - test_quoting_of(0x20, DQ " " DQ); - test_quoting_of(0x21, DQ "!" DQ); - test_quoting_of(0x22, DQ "\\\"" DQ); - test_quoting_of(0x23, DQ "#" DQ); - test_quoting_of(0x24, DQ "$" DQ); - test_quoting_of(0x25, DQ "%" DQ); - test_quoting_of(0x26, DQ "&" DQ); - test_quoting_of(0x27, DQ "'" DQ); - test_quoting_of(0x28, DQ "(" DQ); - test_quoting_of(0x29, DQ ")" DQ); - test_quoting_of(0x2a, DQ "*" DQ); - test_quoting_of(0x2b, DQ "+" DQ); - test_quoting_of(0x2c, DQ "," DQ); - test_quoting_of(0x2d, DQ "-" DQ); - test_quoting_of(0x2e, DQ "." DQ); - test_quoting_of(0x2f, DQ "/" DQ); + test_quoting_of(is_tested, 0x20, DQ " " DQ); + test_quoting_of(is_tested, 0x21, DQ "!" DQ); + test_quoting_of(is_tested, 0x22, DQ "\\\"" DQ); + test_quoting_of(is_tested, 0x23, DQ "#" DQ); + test_quoting_of(is_tested, 0x24, DQ "$" DQ); + test_quoting_of(is_tested, 0x25, DQ "%" DQ); + test_quoting_of(is_tested, 0x26, DQ "&" DQ); + test_quoting_of(is_tested, 0x27, DQ "'" DQ); + test_quoting_of(is_tested, 0x28, DQ "(" DQ); + test_quoting_of(is_tested, 0x29, DQ ")" DQ); + test_quoting_of(is_tested, 0x2a, DQ "*" DQ); + test_quoting_of(is_tested, 0x2b, DQ "+" DQ); + test_quoting_of(is_tested, 0x2c, DQ "," DQ); + test_quoting_of(is_tested, 0x2d, DQ "-" DQ); + test_quoting_of(is_tested, 0x2e, DQ "." DQ); + test_quoting_of(is_tested, 0x2f, DQ "/" DQ); // block 4: 0x30 - 0x3f - test_quoting_of(0x30, DQ "0" DQ); - test_quoting_of(0x31, DQ "1" DQ); - test_quoting_of(0x32, DQ "2" DQ); - test_quoting_of(0x33, DQ "3" DQ); - test_quoting_of(0x34, DQ "4" DQ); - test_quoting_of(0x35, DQ "5" DQ); - test_quoting_of(0x36, DQ "6" DQ); - test_quoting_of(0x37, DQ "7" DQ); - test_quoting_of(0x38, DQ "8" DQ); - test_quoting_of(0x39, DQ "9" DQ); - test_quoting_of(0x3a, DQ ":" DQ); - test_quoting_of(0x3b, DQ ";" DQ); - test_quoting_of(0x3c, DQ "<" DQ); - test_quoting_of(0x3d, DQ "=" DQ); - test_quoting_of(0x3e, DQ ">" DQ); - test_quoting_of(0x3f, DQ "?" DQ); + test_quoting_of(is_tested, 0x30, DQ "0" DQ); + test_quoting_of(is_tested, 0x31, DQ "1" DQ); + test_quoting_of(is_tested, 0x32, DQ "2" DQ); + test_quoting_of(is_tested, 0x33, DQ "3" DQ); + test_quoting_of(is_tested, 0x34, DQ "4" DQ); + test_quoting_of(is_tested, 0x35, DQ "5" DQ); + test_quoting_of(is_tested, 0x36, DQ "6" DQ); + test_quoting_of(is_tested, 0x37, DQ "7" DQ); + test_quoting_of(is_tested, 0x38, DQ "8" DQ); + test_quoting_of(is_tested, 0x39, DQ "9" DQ); + test_quoting_of(is_tested, 0x3a, DQ ":" DQ); + test_quoting_of(is_tested, 0x3b, DQ ";" DQ); + test_quoting_of(is_tested, 0x3c, DQ "<" DQ); + test_quoting_of(is_tested, 0x3d, DQ "=" DQ); + test_quoting_of(is_tested, 0x3e, DQ ">" DQ); + test_quoting_of(is_tested, 0x3f, DQ "?" DQ); // block 5: 0x40 - 0x4f - test_quoting_of(0x40, DQ "@" DQ); - test_quoting_of(0x41, DQ "A" DQ); - test_quoting_of(0x42, DQ "B" DQ); - test_quoting_of(0x43, DQ "C" DQ); - test_quoting_of(0x44, DQ "D" DQ); - test_quoting_of(0x45, DQ "E" DQ); - test_quoting_of(0x46, DQ "F" DQ); - test_quoting_of(0x47, DQ "G" DQ); - test_quoting_of(0x48, DQ "H" DQ); - test_quoting_of(0x49, DQ "I" DQ); - test_quoting_of(0x4a, DQ "J" DQ); - test_quoting_of(0x4b, DQ "K" DQ); - test_quoting_of(0x4c, DQ "L" DQ); - test_quoting_of(0x4d, DQ "M" DQ); - test_quoting_of(0x4e, DQ "N" DQ); - test_quoting_of(0x4f, DQ "O" DQ); + test_quoting_of(is_tested, 0x40, DQ "@" DQ); + test_quoting_of(is_tested, 0x41, DQ "A" DQ); + test_quoting_of(is_tested, 0x42, DQ "B" DQ); + test_quoting_of(is_tested, 0x43, DQ "C" DQ); + test_quoting_of(is_tested, 0x44, DQ "D" DQ); + test_quoting_of(is_tested, 0x45, DQ "E" DQ); + test_quoting_of(is_tested, 0x46, DQ "F" DQ); + test_quoting_of(is_tested, 0x47, DQ "G" DQ); + test_quoting_of(is_tested, 0x48, DQ "H" DQ); + test_quoting_of(is_tested, 0x49, DQ "I" DQ); + test_quoting_of(is_tested, 0x4a, DQ "J" DQ); + test_quoting_of(is_tested, 0x4b, DQ "K" DQ); + test_quoting_of(is_tested, 0x4c, DQ "L" DQ); + test_quoting_of(is_tested, 0x4d, DQ "M" DQ); + test_quoting_of(is_tested, 0x4e, DQ "N" DQ); + test_quoting_of(is_tested, 0x4f, DQ "O" DQ); // block 6: 0x50 - 0x5f - test_quoting_of(0x50, DQ "P" DQ); - test_quoting_of(0x51, DQ "Q" DQ); - test_quoting_of(0x52, DQ "R" DQ); - test_quoting_of(0x53, DQ "S" DQ); - test_quoting_of(0x54, DQ "T" DQ); - test_quoting_of(0x55, DQ "U" DQ); - test_quoting_of(0x56, DQ "V" DQ); - test_quoting_of(0x57, DQ "W" DQ); - test_quoting_of(0x58, DQ "X" DQ); - test_quoting_of(0x59, DQ "Y" DQ); - test_quoting_of(0x5a, DQ "Z" DQ); - test_quoting_of(0x5b, DQ "[" DQ); - test_quoting_of(0x5c, DQ "\\\\" DQ); - test_quoting_of(0x5d, DQ "]" DQ); - test_quoting_of(0x5e, DQ "^" DQ); - test_quoting_of(0x5f, DQ "_" DQ); + test_quoting_of(is_tested, 0x50, DQ "P" DQ); + test_quoting_of(is_tested, 0x51, DQ "Q" DQ); + test_quoting_of(is_tested, 0x52, DQ "R" DQ); + test_quoting_of(is_tested, 0x53, DQ "S" DQ); + test_quoting_of(is_tested, 0x54, DQ "T" DQ); + test_quoting_of(is_tested, 0x55, DQ "U" DQ); + test_quoting_of(is_tested, 0x56, DQ "V" DQ); + test_quoting_of(is_tested, 0x57, DQ "W" DQ); + test_quoting_of(is_tested, 0x58, DQ "X" DQ); + test_quoting_of(is_tested, 0x59, DQ "Y" DQ); + test_quoting_of(is_tested, 0x5a, DQ "Z" DQ); + test_quoting_of(is_tested, 0x5b, DQ "[" DQ); + test_quoting_of(is_tested, 0x5c, DQ "\\\\" DQ); + test_quoting_of(is_tested, 0x5d, DQ "]" DQ); + test_quoting_of(is_tested, 0x5e, DQ "^" DQ); + test_quoting_of(is_tested, 0x5f, DQ "_" DQ); // block 7: 0x60 - 0x6f - test_quoting_of(0x60, DQ "`" DQ); - test_quoting_of(0x61, DQ "a" DQ); - test_quoting_of(0x62, DQ "b" DQ); - test_quoting_of(0x63, DQ "c" DQ); - test_quoting_of(0x64, DQ "d" DQ); - test_quoting_of(0x65, DQ "e" DQ); - test_quoting_of(0x66, DQ "f" DQ); - test_quoting_of(0x67, DQ "g" DQ); - test_quoting_of(0x68, DQ "h" DQ); - test_quoting_of(0x69, DQ "i" DQ); - test_quoting_of(0x6a, DQ "j" DQ); - test_quoting_of(0x6b, DQ "k" DQ); - test_quoting_of(0x6c, DQ "l" DQ); - test_quoting_of(0x6d, DQ "m" DQ); - test_quoting_of(0x6e, DQ "n" DQ); - test_quoting_of(0x6f, DQ "o" DQ); + test_quoting_of(is_tested, 0x60, DQ "`" DQ); + test_quoting_of(is_tested, 0x61, DQ "a" DQ); + test_quoting_of(is_tested, 0x62, DQ "b" DQ); + test_quoting_of(is_tested, 0x63, DQ "c" DQ); + test_quoting_of(is_tested, 0x64, DQ "d" DQ); + test_quoting_of(is_tested, 0x65, DQ "e" DQ); + test_quoting_of(is_tested, 0x66, DQ "f" DQ); + test_quoting_of(is_tested, 0x67, DQ "g" DQ); + test_quoting_of(is_tested, 0x68, DQ "h" DQ); + test_quoting_of(is_tested, 0x69, DQ "i" DQ); + test_quoting_of(is_tested, 0x6a, DQ "j" DQ); + test_quoting_of(is_tested, 0x6b, DQ "k" DQ); + test_quoting_of(is_tested, 0x6c, DQ "l" DQ); + test_quoting_of(is_tested, 0x6d, DQ "m" DQ); + test_quoting_of(is_tested, 0x6e, DQ "n" DQ); + test_quoting_of(is_tested, 0x6f, DQ "o" DQ); // block 8: 0x70 - 0x7f - test_quoting_of(0x70, DQ "p" DQ); - test_quoting_of(0x71, DQ "q" DQ); - test_quoting_of(0x72, DQ "r" DQ); - test_quoting_of(0x73, DQ "s" DQ); - test_quoting_of(0x74, DQ "t" DQ); - test_quoting_of(0x75, DQ "u" DQ); - test_quoting_of(0x76, DQ "v" DQ); - test_quoting_of(0x77, DQ "w" DQ); - test_quoting_of(0x78, DQ "x" DQ); - test_quoting_of(0x79, DQ "y" DQ); - test_quoting_of(0x7a, DQ "z" DQ); - test_quoting_of(0x7b, DQ "{" DQ); - test_quoting_of(0x7c, DQ "|" DQ); - test_quoting_of(0x7d, DQ "}" DQ); - test_quoting_of(0x7e, DQ "~" DQ); - test_quoting_of(0x7f, DQ "\\x7f" DQ); + test_quoting_of(is_tested, 0x70, DQ "p" DQ); + test_quoting_of(is_tested, 0x71, DQ "q" DQ); + test_quoting_of(is_tested, 0x72, DQ "r" DQ); + test_quoting_of(is_tested, 0x73, DQ "s" DQ); + test_quoting_of(is_tested, 0x74, DQ "t" DQ); + test_quoting_of(is_tested, 0x75, DQ "u" DQ); + test_quoting_of(is_tested, 0x76, DQ "v" DQ); + test_quoting_of(is_tested, 0x77, DQ "w" DQ); + test_quoting_of(is_tested, 0x78, DQ "x" DQ); + test_quoting_of(is_tested, 0x79, DQ "y" DQ); + test_quoting_of(is_tested, 0x7a, DQ "z" DQ); + test_quoting_of(is_tested, 0x7b, DQ "{" DQ); + test_quoting_of(is_tested, 0x7c, DQ "|" DQ); + test_quoting_of(is_tested, 0x7d, DQ "}" DQ); + test_quoting_of(is_tested, 0x7e, DQ "~" DQ); + test_quoting_of(is_tested, 0x7f, DQ "\\x7f" DQ); // block 9 (8-bit): 0x80 - 0x8f - test_quoting_of(0x80, DQ "\\x80" DQ); - test_quoting_of(0x81, DQ "\\x81" DQ); - test_quoting_of(0x82, DQ "\\x82" DQ); - test_quoting_of(0x83, DQ "\\x83" DQ); - test_quoting_of(0x84, DQ "\\x84" DQ); - test_quoting_of(0x85, DQ "\\x85" DQ); - test_quoting_of(0x86, DQ "\\x86" DQ); - test_quoting_of(0x87, DQ "\\x87" DQ); - test_quoting_of(0x88, DQ "\\x88" DQ); - test_quoting_of(0x89, DQ "\\x89" DQ); - test_quoting_of(0x8a, DQ "\\x8a" DQ); - test_quoting_of(0x8b, DQ "\\x8b" DQ); - test_quoting_of(0x8c, DQ "\\x8c" DQ); - test_quoting_of(0x8d, DQ "\\x8d" DQ); - test_quoting_of(0x8e, DQ "\\x8e" DQ); - test_quoting_of(0x8f, DQ "\\x8f" DQ); + test_quoting_of(is_tested, 0x80, DQ "\\x80" DQ); + test_quoting_of(is_tested, 0x81, DQ "\\x81" DQ); + test_quoting_of(is_tested, 0x82, DQ "\\x82" DQ); + test_quoting_of(is_tested, 0x83, DQ "\\x83" DQ); + test_quoting_of(is_tested, 0x84, DQ "\\x84" DQ); + test_quoting_of(is_tested, 0x85, DQ "\\x85" DQ); + test_quoting_of(is_tested, 0x86, DQ "\\x86" DQ); + test_quoting_of(is_tested, 0x87, DQ "\\x87" DQ); + test_quoting_of(is_tested, 0x88, DQ "\\x88" DQ); + test_quoting_of(is_tested, 0x89, DQ "\\x89" DQ); + test_quoting_of(is_tested, 0x8a, DQ "\\x8a" DQ); + test_quoting_of(is_tested, 0x8b, DQ "\\x8b" DQ); + test_quoting_of(is_tested, 0x8c, DQ "\\x8c" DQ); + test_quoting_of(is_tested, 0x8d, DQ "\\x8d" DQ); + test_quoting_of(is_tested, 0x8e, DQ "\\x8e" DQ); + test_quoting_of(is_tested, 0x8f, DQ "\\x8f" DQ); // block 10 (8-bit): 0x90 - 0x9f - test_quoting_of(0x90, DQ "\\x90" DQ); - test_quoting_of(0x91, DQ "\\x91" DQ); - test_quoting_of(0x92, DQ "\\x92" DQ); - test_quoting_of(0x93, DQ "\\x93" DQ); - test_quoting_of(0x94, DQ "\\x94" DQ); - test_quoting_of(0x95, DQ "\\x95" DQ); - test_quoting_of(0x96, DQ "\\x96" DQ); - test_quoting_of(0x97, DQ "\\x97" DQ); - test_quoting_of(0x98, DQ "\\x98" DQ); - test_quoting_of(0x99, DQ "\\x99" DQ); - test_quoting_of(0x9a, DQ "\\x9a" DQ); - test_quoting_of(0x9b, DQ "\\x9b" DQ); - test_quoting_of(0x9c, DQ "\\x9c" DQ); - test_quoting_of(0x9d, DQ "\\x9d" DQ); - test_quoting_of(0x9e, DQ "\\x9e" DQ); - test_quoting_of(0x9f, DQ "\\x9f" DQ); + test_quoting_of(is_tested, 0x90, DQ "\\x90" DQ); + test_quoting_of(is_tested, 0x91, DQ "\\x91" DQ); + test_quoting_of(is_tested, 0x92, DQ "\\x92" DQ); + test_quoting_of(is_tested, 0x93, DQ "\\x93" DQ); + test_quoting_of(is_tested, 0x94, DQ "\\x94" DQ); + test_quoting_of(is_tested, 0x95, DQ "\\x95" DQ); + test_quoting_of(is_tested, 0x96, DQ "\\x96" DQ); + test_quoting_of(is_tested, 0x97, DQ "\\x97" DQ); + test_quoting_of(is_tested, 0x98, DQ "\\x98" DQ); + test_quoting_of(is_tested, 0x99, DQ "\\x99" DQ); + test_quoting_of(is_tested, 0x9a, DQ "\\x9a" DQ); + test_quoting_of(is_tested, 0x9b, DQ "\\x9b" DQ); + test_quoting_of(is_tested, 0x9c, DQ "\\x9c" DQ); + test_quoting_of(is_tested, 0x9d, DQ "\\x9d" DQ); + test_quoting_of(is_tested, 0x9e, DQ "\\x9e" DQ); + test_quoting_of(is_tested, 0x9f, DQ "\\x9f" DQ); // block 11 (8-bit): 0xa0 - 0xaf - test_quoting_of(0xa0, DQ "\\xa0" DQ); - test_quoting_of(0xa1, DQ "\\xa1" DQ); - test_quoting_of(0xa2, DQ "\\xa2" DQ); - test_quoting_of(0xa3, DQ "\\xa3" DQ); - test_quoting_of(0xa4, DQ "\\xa4" DQ); - test_quoting_of(0xa5, DQ "\\xa5" DQ); - test_quoting_of(0xa6, DQ "\\xa6" DQ); - test_quoting_of(0xa7, DQ "\\xa7" DQ); - test_quoting_of(0xa8, DQ "\\xa8" DQ); - test_quoting_of(0xa9, DQ "\\xa9" DQ); - test_quoting_of(0xaa, DQ "\\xaa" DQ); - test_quoting_of(0xab, DQ "\\xab" DQ); - test_quoting_of(0xac, DQ "\\xac" DQ); - test_quoting_of(0xad, DQ "\\xad" DQ); - test_quoting_of(0xae, DQ "\\xae" DQ); - test_quoting_of(0xaf, DQ "\\xaf" DQ); + test_quoting_of(is_tested, 0xa0, DQ "\\xa0" DQ); + test_quoting_of(is_tested, 0xa1, DQ "\\xa1" DQ); + test_quoting_of(is_tested, 0xa2, DQ "\\xa2" DQ); + test_quoting_of(is_tested, 0xa3, DQ "\\xa3" DQ); + test_quoting_of(is_tested, 0xa4, DQ "\\xa4" DQ); + test_quoting_of(is_tested, 0xa5, DQ "\\xa5" DQ); + test_quoting_of(is_tested, 0xa6, DQ "\\xa6" DQ); + test_quoting_of(is_tested, 0xa7, DQ "\\xa7" DQ); + test_quoting_of(is_tested, 0xa8, DQ "\\xa8" DQ); + test_quoting_of(is_tested, 0xa9, DQ "\\xa9" DQ); + test_quoting_of(is_tested, 0xaa, DQ "\\xaa" DQ); + test_quoting_of(is_tested, 0xab, DQ "\\xab" DQ); + test_quoting_of(is_tested, 0xac, DQ "\\xac" DQ); + test_quoting_of(is_tested, 0xad, DQ "\\xad" DQ); + test_quoting_of(is_tested, 0xae, DQ "\\xae" DQ); + test_quoting_of(is_tested, 0xaf, DQ "\\xaf" DQ); // block 12 (8-bit): 0xb0 - 0xbf - test_quoting_of(0xb0, DQ "\\xb0" DQ); - test_quoting_of(0xb1, DQ "\\xb1" DQ); - test_quoting_of(0xb2, DQ "\\xb2" DQ); - test_quoting_of(0xb3, DQ "\\xb3" DQ); - test_quoting_of(0xb4, DQ "\\xb4" DQ); - test_quoting_of(0xb5, DQ "\\xb5" DQ); - test_quoting_of(0xb6, DQ "\\xb6" DQ); - test_quoting_of(0xb7, DQ "\\xb7" DQ); - test_quoting_of(0xb8, DQ "\\xb8" DQ); - test_quoting_of(0xb9, DQ "\\xb9" DQ); - test_quoting_of(0xba, DQ "\\xba" DQ); - test_quoting_of(0xbb, DQ "\\xbb" DQ); - test_quoting_of(0xbc, DQ "\\xbc" DQ); - test_quoting_of(0xbd, DQ "\\xbd" DQ); - test_quoting_of(0xbe, DQ "\\xbe" DQ); - test_quoting_of(0xbf, DQ "\\xbf" DQ); + test_quoting_of(is_tested, 0xb0, DQ "\\xb0" DQ); + test_quoting_of(is_tested, 0xb1, DQ "\\xb1" DQ); + test_quoting_of(is_tested, 0xb2, DQ "\\xb2" DQ); + test_quoting_of(is_tested, 0xb3, DQ "\\xb3" DQ); + test_quoting_of(is_tested, 0xb4, DQ "\\xb4" DQ); + test_quoting_of(is_tested, 0xb5, DQ "\\xb5" DQ); + test_quoting_of(is_tested, 0xb6, DQ "\\xb6" DQ); + test_quoting_of(is_tested, 0xb7, DQ "\\xb7" DQ); + test_quoting_of(is_tested, 0xb8, DQ "\\xb8" DQ); + test_quoting_of(is_tested, 0xb9, DQ "\\xb9" DQ); + test_quoting_of(is_tested, 0xba, DQ "\\xba" DQ); + test_quoting_of(is_tested, 0xbb, DQ "\\xbb" DQ); + test_quoting_of(is_tested, 0xbc, DQ "\\xbc" DQ); + test_quoting_of(is_tested, 0xbd, DQ "\\xbd" DQ); + test_quoting_of(is_tested, 0xbe, DQ "\\xbe" DQ); + test_quoting_of(is_tested, 0xbf, DQ "\\xbf" DQ); // block 13 (8-bit): 0xc0 - 0xcf - test_quoting_of(0xc0, DQ "\\xc0" DQ); - test_quoting_of(0xc1, DQ "\\xc1" DQ); - test_quoting_of(0xc2, DQ "\\xc2" DQ); - test_quoting_of(0xc3, DQ "\\xc3" DQ); - test_quoting_of(0xc4, DQ "\\xc4" DQ); - test_quoting_of(0xc5, DQ "\\xc5" DQ); - test_quoting_of(0xc6, DQ "\\xc6" DQ); - test_quoting_of(0xc7, DQ "\\xc7" DQ); - test_quoting_of(0xc8, DQ "\\xc8" DQ); - test_quoting_of(0xc9, DQ "\\xc9" DQ); - test_quoting_of(0xca, DQ "\\xca" DQ); - test_quoting_of(0xcb, DQ "\\xcb" DQ); - test_quoting_of(0xcc, DQ "\\xcc" DQ); - test_quoting_of(0xcd, DQ "\\xcd" DQ); - test_quoting_of(0xce, DQ "\\xce" DQ); - test_quoting_of(0xcf, DQ "\\xcf" DQ); + test_quoting_of(is_tested, 0xc0, DQ "\\xc0" DQ); + test_quoting_of(is_tested, 0xc1, DQ "\\xc1" DQ); + test_quoting_of(is_tested, 0xc2, DQ "\\xc2" DQ); + test_quoting_of(is_tested, 0xc3, DQ "\\xc3" DQ); + test_quoting_of(is_tested, 0xc4, DQ "\\xc4" DQ); + test_quoting_of(is_tested, 0xc5, DQ "\\xc5" DQ); + test_quoting_of(is_tested, 0xc6, DQ "\\xc6" DQ); + test_quoting_of(is_tested, 0xc7, DQ "\\xc7" DQ); + test_quoting_of(is_tested, 0xc8, DQ "\\xc8" DQ); + test_quoting_of(is_tested, 0xc9, DQ "\\xc9" DQ); + test_quoting_of(is_tested, 0xca, DQ "\\xca" DQ); + test_quoting_of(is_tested, 0xcb, DQ "\\xcb" DQ); + test_quoting_of(is_tested, 0xcc, DQ "\\xcc" DQ); + test_quoting_of(is_tested, 0xcd, DQ "\\xcd" DQ); + test_quoting_of(is_tested, 0xce, DQ "\\xce" DQ); + test_quoting_of(is_tested, 0xcf, DQ "\\xcf" DQ); // block 14 (8-bit): 0xd0 - 0xdf - test_quoting_of(0xd0, DQ "\\xd0" DQ); - test_quoting_of(0xd1, DQ "\\xd1" DQ); - test_quoting_of(0xd2, DQ "\\xd2" DQ); - test_quoting_of(0xd3, DQ "\\xd3" DQ); - test_quoting_of(0xd4, DQ "\\xd4" DQ); - test_quoting_of(0xd5, DQ "\\xd5" DQ); - test_quoting_of(0xd6, DQ "\\xd6" DQ); - test_quoting_of(0xd7, DQ "\\xd7" DQ); - test_quoting_of(0xd8, DQ "\\xd8" DQ); - test_quoting_of(0xd9, DQ "\\xd9" DQ); - test_quoting_of(0xda, DQ "\\xda" DQ); - test_quoting_of(0xdb, DQ "\\xdb" DQ); - test_quoting_of(0xdc, DQ "\\xdc" DQ); - test_quoting_of(0xdd, DQ "\\xdd" DQ); - test_quoting_of(0xde, DQ "\\xde" DQ); - test_quoting_of(0xdf, DQ "\\xdf" DQ); + test_quoting_of(is_tested, 0xd0, DQ "\\xd0" DQ); + test_quoting_of(is_tested, 0xd1, DQ "\\xd1" DQ); + test_quoting_of(is_tested, 0xd2, DQ "\\xd2" DQ); + test_quoting_of(is_tested, 0xd3, DQ "\\xd3" DQ); + test_quoting_of(is_tested, 0xd4, DQ "\\xd4" DQ); + test_quoting_of(is_tested, 0xd5, DQ "\\xd5" DQ); + test_quoting_of(is_tested, 0xd6, DQ "\\xd6" DQ); + test_quoting_of(is_tested, 0xd7, DQ "\\xd7" DQ); + test_quoting_of(is_tested, 0xd8, DQ "\\xd8" DQ); + test_quoting_of(is_tested, 0xd9, DQ "\\xd9" DQ); + test_quoting_of(is_tested, 0xda, DQ "\\xda" DQ); + test_quoting_of(is_tested, 0xdb, DQ "\\xdb" DQ); + test_quoting_of(is_tested, 0xdc, DQ "\\xdc" DQ); + test_quoting_of(is_tested, 0xdd, DQ "\\xdd" DQ); + test_quoting_of(is_tested, 0xde, DQ "\\xde" DQ); + test_quoting_of(is_tested, 0xdf, DQ "\\xdf" DQ); // block 15 (8-bit): 0xe0 - 0xef - test_quoting_of(0xe0, DQ "\\xe0" DQ); - test_quoting_of(0xe1, DQ "\\xe1" DQ); - test_quoting_of(0xe2, DQ "\\xe2" DQ); - test_quoting_of(0xe3, DQ "\\xe3" DQ); - test_quoting_of(0xe4, DQ "\\xe4" DQ); - test_quoting_of(0xe5, DQ "\\xe5" DQ); - test_quoting_of(0xe6, DQ "\\xe6" DQ); - test_quoting_of(0xe7, DQ "\\xe7" DQ); - test_quoting_of(0xe8, DQ "\\xe8" DQ); - test_quoting_of(0xe9, DQ "\\xe9" DQ); - test_quoting_of(0xea, DQ "\\xea" DQ); - test_quoting_of(0xeb, DQ "\\xeb" DQ); - test_quoting_of(0xec, DQ "\\xec" DQ); - test_quoting_of(0xed, DQ "\\xed" DQ); - test_quoting_of(0xee, DQ "\\xee" DQ); - test_quoting_of(0xef, DQ "\\xef" DQ); + test_quoting_of(is_tested, 0xe0, DQ "\\xe0" DQ); + test_quoting_of(is_tested, 0xe1, DQ "\\xe1" DQ); + test_quoting_of(is_tested, 0xe2, DQ "\\xe2" DQ); + test_quoting_of(is_tested, 0xe3, DQ "\\xe3" DQ); + test_quoting_of(is_tested, 0xe4, DQ "\\xe4" DQ); + test_quoting_of(is_tested, 0xe5, DQ "\\xe5" DQ); + test_quoting_of(is_tested, 0xe6, DQ "\\xe6" DQ); + test_quoting_of(is_tested, 0xe7, DQ "\\xe7" DQ); + test_quoting_of(is_tested, 0xe8, DQ "\\xe8" DQ); + test_quoting_of(is_tested, 0xe9, DQ "\\xe9" DQ); + test_quoting_of(is_tested, 0xea, DQ "\\xea" DQ); + test_quoting_of(is_tested, 0xeb, DQ "\\xeb" DQ); + test_quoting_of(is_tested, 0xec, DQ "\\xec" DQ); + test_quoting_of(is_tested, 0xed, DQ "\\xed" DQ); + test_quoting_of(is_tested, 0xee, DQ "\\xee" DQ); + test_quoting_of(is_tested, 0xef, DQ "\\xef" DQ); // block 16 (8-bit): 0xf0 - 0xff - test_quoting_of(0xf0, DQ "\\xf0" DQ); - test_quoting_of(0xf1, DQ "\\xf1" DQ); - test_quoting_of(0xf2, DQ "\\xf2" DQ); - test_quoting_of(0xf3, DQ "\\xf3" DQ); - test_quoting_of(0xf4, DQ "\\xf4" DQ); - test_quoting_of(0xf5, DQ "\\xf5" DQ); - test_quoting_of(0xf6, DQ "\\xf6" DQ); - test_quoting_of(0xf7, DQ "\\xf7" DQ); - test_quoting_of(0xf8, DQ "\\xf8" DQ); - test_quoting_of(0xf9, DQ "\\xf9" DQ); - test_quoting_of(0xfa, DQ "\\xfa" DQ); - test_quoting_of(0xfb, DQ "\\xfb" DQ); - test_quoting_of(0xfc, DQ "\\xfc" DQ); - test_quoting_of(0xfd, DQ "\\xfd" DQ); - test_quoting_of(0xfe, DQ "\\xfe" DQ); - test_quoting_of(0xff, DQ "\\xff" DQ); + test_quoting_of(is_tested, 0xf0, DQ "\\xf0" DQ); + test_quoting_of(is_tested, 0xf1, DQ "\\xf1" DQ); + test_quoting_of(is_tested, 0xf2, DQ "\\xf2" DQ); + test_quoting_of(is_tested, 0xf3, DQ "\\xf3" DQ); + test_quoting_of(is_tested, 0xf4, DQ "\\xf4" DQ); + test_quoting_of(is_tested, 0xf5, DQ "\\xf5" DQ); + test_quoting_of(is_tested, 0xf6, DQ "\\xf6" DQ); + test_quoting_of(is_tested, 0xf7, DQ "\\xf7" DQ); + test_quoting_of(is_tested, 0xf8, DQ "\\xf8" DQ); + test_quoting_of(is_tested, 0xf9, DQ "\\xf9" DQ); + test_quoting_of(is_tested, 0xfa, DQ "\\xfa" DQ); + test_quoting_of(is_tested, 0xfb, DQ "\\xfb" DQ); + test_quoting_of(is_tested, 0xfc, DQ "\\xfc" DQ); + test_quoting_of(is_tested, 0xfd, DQ "\\xfd" DQ); + test_quoting_of(is_tested, 0xfe, DQ "\\xfe" DQ); + test_quoting_of(is_tested, 0xff, DQ "\\xff" DQ); // Ensure the search was exhaustive. for (int i = 0; i <= 0xff; ++i) { @@ -777,7 +780,7 @@ #undef DQ } -static void __attribute__ ((constructor)) init() +static void __attribute__ ((constructor)) init(void) { g_test_add_func("/string-utils/sc_streq", test_sc_streq); g_test_add_func("/string-utils/sc_endswith", test_sc_endswith); diff -Nru snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/test-utils-test.c snapd-2.31.1+17.10/cmd/libsnap-confine-private/test-utils-test.c --- snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/test-utils-test.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/libsnap-confine-private/test-utils-test.c 2017-12-01 15:51:55.000000000 +0000 @@ -23,7 +23,7 @@ #include // Check that rm_rf_tmp doesn't remove things outside of /tmp -static void test_rm_rf_tmp() +static void test_rm_rf_tmp(void) { if (access("/nonexistent", F_OK) == 0) { g_test_message @@ -39,7 +39,7 @@ g_test_trap_assert_failed(); } -static void __attribute__ ((constructor)) init() +static void __attribute__ ((constructor)) init(void) { g_test_add_func("/test-utils/rm_rf_tmp", test_rm_rf_tmp); } diff -Nru snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/utils.c snapd-2.31.1+17.10/cmd/libsnap-confine-private/utils.c --- snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/utils.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/libsnap-confine-private/utils.c 2017-12-01 15:51:55.000000000 +0000 @@ -84,7 +84,8 @@ *value = default_value; return 0; } - for (int i = 0; i < sizeof sc_bool_names / sizeof *sc_bool_names; ++i) { + for (size_t i = 0; i < sizeof sc_bool_names / sizeof *sc_bool_names; + ++i) { if (strcmp(text, sc_bool_names[i].text) == 0) { *value = sc_bool_names[i].value; return 0; @@ -119,12 +120,13 @@ return value; } -bool sc_is_debug_enabled() +bool sc_is_debug_enabled(void) { - return getenv_bool("SNAP_CONFINE_DEBUG", false); + return getenv_bool("SNAP_CONFINE_DEBUG", false) + || getenv_bool("SNAPD_DEBUG", false); } -bool sc_is_reexec_enabled() +bool sc_is_reexec_enabled(void) { return getenv_bool("SNAP_REEXEC", true); } diff -Nru snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/utils.h snapd-2.31.1+17.10/cmd/libsnap-confine-private/utils.h --- snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/utils.h 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/libsnap-confine-private/utils.h 2017-12-01 15:51:55.000000000 +0000 @@ -35,12 +35,12 @@ * * This can used to avoid costly computation that is only useful for debugging. **/ -bool sc_is_debug_enabled(); +bool sc_is_debug_enabled(void); /** * Return true if re-execution is enabled. **/ -bool sc_is_reexec_enabled(); +bool sc_is_reexec_enabled(void); void write_string_to_file(const char *filepath, const char *buf); diff -Nru snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/utils-test.c snapd-2.31.1+17.10/cmd/libsnap-confine-private/utils-test.c --- snapd-2.29.4.2+17.10/cmd/libsnap-confine-private/utils-test.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/libsnap-confine-private/utils-test.c 2017-12-01 15:51:55.000000000 +0000 @@ -20,7 +20,7 @@ #include -static void test_parse_bool() +static void test_parse_bool(void) { int err; bool value; @@ -71,7 +71,7 @@ g_assert_cmpint(errno, ==, EFAULT); } -static void test_die() +static void test_die(void) { if (g_test_subprocess()) { errno = 0; @@ -85,7 +85,7 @@ g_test_trap_assert_stderr("death message\n"); } -static void test_die_with_errno() +static void test_die_with_errno(void) { if (g_test_subprocess()) { errno = EPERM; @@ -106,7 +106,7 @@ * operations at the end of the test. If any additional directories or files * are created in this directory they must be removed by the caller. **/ -static void g_test_in_ephemeral_dir() +static void g_test_in_ephemeral_dir(void) { gchar *temp_dir = g_dir_make_tmp(NULL, NULL); gchar *orig_dir = g_get_current_dir(); @@ -152,7 +152,7 @@ /** * Test that sc_nonfatal_mkpath behaves when using relative paths. **/ -static void test_sc_nonfatal_mkpath__relative() +static void test_sc_nonfatal_mkpath__relative(void) { g_test_in_ephemeral_dir(); gchar *current_dir = g_get_current_dir(); @@ -167,7 +167,7 @@ /** * Test that sc_nonfatal_mkpath behaves when using absolute paths. **/ -static void test_sc_nonfatal_mkpath__absolute() +static void test_sc_nonfatal_mkpath__absolute(void) { g_test_in_ephemeral_dir(); const char *dirname = "foo"; @@ -175,7 +175,7 @@ _test_sc_nonfatal_mkpath(dirname, subdirname); } -static void __attribute__ ((constructor)) init() +static void __attribute__ ((constructor)) init(void) { g_test_add_func("/utils/parse_bool", test_parse_bool); g_test_add_func("/utils/die", test_die); diff -Nru snapd-2.29.4.2+17.10/cmd/Makefile.am snapd-2.31.1+17.10/cmd/Makefile.am --- snapd-2.29.4.2+17.10/cmd/Makefile.am 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/Makefile.am 2018-01-31 08:47:06.000000000 +0000 @@ -7,15 +7,25 @@ noinst_PROGRAMS = noinst_LIBRARIES = +CHECK_CFLAGS = -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \ + -Wno-missing-field-initializers -Wno-unused-parameter + +# Make all warnings errors when building for unit tests +if WITH_UNIT_TESTS +CHECK_CFLAGS += -Werror +endif + subdirs = snap-confine snap-discard-ns system-shutdown libsnap-confine-private # Run check-syntax when checking # TODO: conver those to autotools-style tests later -check: check-syntax-c check-unit-tests +check: check-unit-tests # Force particular coding style on all source and header files. .PHONY: check-syntax-c check-syntax-c: + echo "WARNING: check-syntax-c produces different results for different version of indent" + echo "Your version of indent: `indent --version`" @d=`mktemp -d`; \ trap 'rm -rf $d' EXIT; \ for f in $(foreach dir,$(subdirs),$(wildcard $(srcdir)/$(dir)/*.[ch])) ; do \ @@ -46,7 +56,7 @@ hack: snap-confine/snap-confine snap-confine/snap-confine.apparmor snap-update-ns/snap-update-ns snap-seccomp/snap-seccomp sudo install -D -m 6755 snap-confine/snap-confine $(DESTDIR)$(libexecdir)/snap-confine sudo install -m 644 snap-confine/snap-confine.apparmor $(DESTDIR)/etc/apparmor.d/$(patsubst .%,%,$(subst /,.,$(libexecdir))).snap-confine.real - sudo install -d -m 755 $(DESTDIR)/var/lib/snapd/apparmor/snap-confine.d/ + sudo install -d -m 755 $(DESTDIR)/var/lib/snapd/apparmor/snap-confine/ sudo apparmor_parser -r snap-confine/snap-confine.apparmor sudo install -m 755 snap-update-ns/snap-update-ns $(DESTDIR)$(libexecdir)/snap-update-ns sudo install -m 755 snap-seccomp/snap-seccomp $(DESTDIR)$(libexecdir)/snap-seccomp @@ -90,6 +100,11 @@ libsnap-confine-private/string-utils.h \ libsnap-confine-private/utils.c \ libsnap-confine-private/utils.h +libsnap_confine_private_a_CFLAGS = $(CHECK_CFLAGS) + +noinst_LIBRARIES += libsnap-confine-private-debug.a +libsnap_confine_private_debug_a_SOURCES = $(libsnap_confine_private_a_SOURCES) +libsnap_confine_private_debug_a_CFLAGS = $(CHECK_CFLAGS) -DSNAP_CONFINE_DEBUG_BUILD=1 if WITH_UNIT_TESTS noinst_PROGRAMS += libsnap-confine-private/unit-tests @@ -111,7 +126,7 @@ libsnap-confine-private/unit-tests.c \ libsnap-confine-private/unit-tests.h \ libsnap-confine-private/utils-test.c -libsnap_confine_private_unit_tests_CFLAGS = $(GLIB_CFLAGS) +libsnap_confine_private_unit_tests_CFLAGS = $(CHECK_CFLAGS) $(GLIB_CFLAGS) libsnap_confine_private_unit_tests_LDADD = $(GLIB_LIBS) libsnap_confine_private_unit_tests_CFLAGS += -D_ENABLE_FAULT_INJECTION libsnap_confine_private_unit_tests_STATIC = @@ -190,18 +205,23 @@ snap-confine/user-support.c \ snap-confine/user-support.h -snap_confine_snap_confine_CFLAGS = -Wall -Werror $(AM_CFLAGS) -DLIBEXECDIR=\"$(libexecdir)\" +snap_confine_snap_confine_CFLAGS = $(CHECK_CFLAGS) $(AM_CFLAGS) -DLIBEXECDIR=\"$(libexecdir)\" -DNATIVE_LIBDIR=\"$(libdir)\" snap_confine_snap_confine_LDFLAGS = $(AM_LDFLAGS) snap_confine_snap_confine_LDADD = libsnap-confine-private.a snap_confine_snap_confine_CFLAGS += $(LIBUDEV_CFLAGS) -snap_confine_snap_confine_LDADD += $(LIBUDEV_LIBS) +snap_confine_snap_confine_LDADD += $(snap_confine_snap_confine_extra_libs) # _STATIC is where we collect statically linked in libraries snap_confine_snap_confine_STATIC = +# use a separate variable instead of snap_confine_snap_confine_LDADD to collect +# all external libraries, this way it can be reused in +# snap_confine_snap_confine_debug_LDADD withouth applying any text +# transformations +snap_confine_snap_confine_extra_libs = $(LIBUDEV_LIBS) if STATIC_LIBCAP snap_confine_snap_confine_STATIC += -lcap else -snap_confine_snap_confine_LDADD += -lcap +snap_confine_snap_confine_extra_libs += -lcap endif # STATIC_LIBCAP # Use a hacked rule if we're doing static build. This allows us to inject the LIBS += .. rule below. @@ -224,7 +244,7 @@ if STATIC_LIBSECCOMP snap_confine_snap_confine_STATIC += $(shell pkg-config --static --libs libseccomp) else -snap_confine_snap_confine_LDADD += $(SECCOMP_LIBS) +snap_confine_snap_confine_extra_libs += $(SECCOMP_LIBS) endif # STATIC_LIBSECCOMP endif # SECCOMP @@ -233,7 +253,7 @@ if STATIC_LIBAPPARMOR snap_confine_snap_confine_STATIC += $(shell pkg-config --static --libs libapparmor) else -snap_confine_snap_confine_LDADD += $(APPARMOR_LIBS) +snap_confine_snap_confine_extra_libs += $(APPARMOR_LIBS) endif # STATIC_LIBAPPARMOR endif # APPARMOR @@ -243,7 +263,7 @@ snap_confine_snap_confine_debug_SOURCES = $(snap_confine_snap_confine_SOURCES) snap_confine_snap_confine_debug_CFLAGS = $(snap_confine_snap_confine_CFLAGS) snap_confine_snap_confine_debug_LDFLAGS = $(snap_confine_snap_confine_LDFLAGS) -snap_confine_snap_confine_debug_LDADD = $(snap_confine_snap_confine_LDADD) +snap_confine_snap_confine_debug_LDADD = libsnap-confine-private-debug.a $(snap_confine_snap_confine_extra_libs) snap_confine_snap_confine_debug_CFLAGS += -DSNAP_CONFINE_DEBUG_BUILD=1 snap_confine_snap_confine_debug_STATIC = $(snap_confine_snap_confine_STATIC) @@ -301,7 +321,7 @@ install -d -m 755 $(DESTDIR)/etc/apparmor.d/ install -m 644 snap-confine/snap-confine.apparmor $(DESTDIR)/etc/apparmor.d/$(patsubst .%,%,$(subst /,.,$(libexecdir))).snap-confine endif - install -d -m 755 $(DESTDIR)/var/lib/snapd/apparmor/snap-confine.d/ + install -d -m 755 $(DESTDIR)/var/lib/snapd/apparmor/snap-confine/ # NOTE: The 'void' directory *has to* be chmod 000 install-data-local:: @@ -317,6 +337,16 @@ endif ## +## snap-mgmt +## + +libexec_PROGRAMS += snap-mgmt/snap-mgmt + +snap-mgmt/snap-mgmt: snap-mgmt/snap-mgmt.sh.in Makefile snap-mgmt/$(am__dirstamp) + sed -e 's,[@]SNAP_MOUNT_DIR[@],$(SNAP_MOUNT_DIR),' <$< >$@ + + +## ## ubuntu-core-launcher ## @@ -329,17 +359,11 @@ ## EXTRA_DIST += \ - snap-confine/80-snappy-assign.rules \ snap-confine/snappy-app-dev # NOTE: This makes distcheck fail but it is required for udev, so go figure. # http://www.gnu.org/software/automake/manual/automake.html#Hard_002dCoded-Install-Paths # -# Install udev rules -install-data-local:: - install -d -m 755 $(DESTDIR)$(shell pkg-config udev --variable=udevdir)/rules.d - install -m 644 $(srcdir)/snap-confine/80-snappy-assign.rules $(DESTDIR)$(shell pkg-config udev --variable=udevdir)/rules.d - # Install support script for udev rules install-exec-local:: install -d -m 755 $(DESTDIR)$(shell pkg-config udev --variable=udevdir) @@ -362,7 +386,7 @@ snap-confine/apparmor-support.c \ snap-confine/apparmor-support.h \ snap-discard-ns/snap-discard-ns.c -snap_discard_ns_snap_discard_ns_CFLAGS = -Wall -Werror $(AM_CFLAGS) +snap_discard_ns_snap_discard_ns_CFLAGS = $(CHECK_CFLAGS) $(AM_CFLAGS) snap_discard_ns_snap_discard_ns_LDFLAGS = $(AM_LDFLAGS) snap_discard_ns_snap_discard_ns_LDADD = libsnap-confine-private.a snap_discard_ns_snap_discard_ns_STATIC = @@ -406,7 +430,7 @@ system-shutdown/system-shutdown-utils.h \ system-shutdown/system-shutdown.c system_shutdown_system_shutdown_LDADD = libsnap-confine-private.a -system_shutdown_system_shutdown_CFLAGS = $(filter-out -fPIE -pie,$(CFLAGS)) -static +system_shutdown_system_shutdown_CFLAGS = $(CHECK_CFLAGS) $(filter-out -fPIE -pie,$(CFLAGS)) -static system_shutdown_system_shutdown_LDFLAGS = $(filter-out -fPIE -pie,$(LDFLAGS)) -static if WITH_UNIT_TESTS diff -Nru snapd-2.29.4.2+17.10/cmd/snap/cmd_advise.go snapd-2.31.1+17.10/cmd/snap/cmd_advise.go --- snapd-2.29.4.2+17.10/cmd/snap/cmd_advise.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap/cmd_advise.go 2018-02-02 15:30:39.000000000 +0000 @@ -0,0 +1,128 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2018 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 . + * + */ + +package main + +import ( + "encoding/json" + "fmt" + + "github.com/jessevdk/go-flags" + + "github.com/snapcore/snapd/advisor" + "github.com/snapcore/snapd/i18n" +) + +type cmdAdviseSnap struct { + Positionals struct { + CommandOrPkg string `required:"yes"` + } `positional-args:"true"` + + Format string `long:"format" default:"pretty"` + Command bool `long:"command"` +} + +var shortAdviseSnapHelp = i18n.G("Advise on available snaps.") +var longAdviseSnapHelp = i18n.G(` +The advise-command command shows what snaps with the given command are +available. +`) + +func init() { + cmd := addCommand("advise-snap", shortAdviseSnapHelp, longAdviseSnapHelp, func() flags.Commander { + return &cmdAdviseSnap{} + }, map[string]string{ + "command": i18n.G("Advise on snaps that provide the given command"), + "format": i18n.G("Use the given output format (pretty or json)"), + }, []argDesc{ + {name: ""}, + }) + cmd.hidden = true +} + +func outputAdviseExactText(command string, result []advisor.Command) error { + fmt.Fprintf(Stdout, i18n.G("The program %q can be found in the following snaps:\n"), command) + for _, snap := range result { + fmt.Fprintf(Stdout, " * %s\n", snap.Snap) + } + fmt.Fprintf(Stdout, i18n.G("Try: snap install \n")) + return nil +} + +func outputAdviseMisspellText(command string, result []advisor.Command) error { + fmt.Fprintf(Stdout, i18n.G("No command %q found, did you mean:\n"), command) + for _, snap := range result { + fmt.Fprintf(Stdout, " Command %q from snap %q\n", snap.Command, snap.Snap) + } + return nil +} + +func outputAdviseJSON(command string, results []advisor.Command) error { + enc := json.NewEncoder(Stdout) + enc.Encode(results) + return nil +} + +func (x *cmdAdviseSnap) Execute(args []string) error { + if len(args) > 0 { + return ErrExtraArgs + } + + if x.Command { + return adviseCommand(x.Positionals.CommandOrPkg, x.Format) + } + + return fmt.Errorf("snap advise-snap is only implemented with --command") +} + +func adviseCommand(cmd string, format string) error { + // find exact matches + matches, err := advisor.FindCommand(cmd) + if err != nil { + return fmt.Errorf("advise-command error: %s", err) + } + if len(matches) > 0 { + switch format { + case "json": + return outputAdviseJSON(cmd, matches) + case "pretty": + return outputAdviseExactText(cmd, matches) + default: + return fmt.Errorf("unsupported format %q", format) + } + } + + // find misspellings + matches, err = advisor.FindMisspelledCommand(cmd) + if err != nil { + return err + } + if len(matches) > 0 { + switch format { + case "json": + return outputAdviseJSON(cmd, matches) + case "pretty": + return outputAdviseMisspellText(cmd, matches) + default: + return fmt.Errorf("unsupported format %q", format) + } + } + + return fmt.Errorf("%s: command not found", cmd) +} diff -Nru snapd-2.29.4.2+17.10/cmd/snap/cmd_advise_test.go snapd-2.31.1+17.10/cmd/snap/cmd_advise_test.go --- snapd-2.29.4.2+17.10/cmd/snap/cmd_advise_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap/cmd_advise_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,95 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2018 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 . + * + */ + +package main_test + +import ( + "fmt" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/advisor" + snap "github.com/snapcore/snapd/cmd/snap" +) + +type sillyFinder struct{} + +func mkSillyFinder() (advisor.Finder, error) { + return &sillyFinder{}, nil +} + +func (sf *sillyFinder) Find(command string) ([]advisor.Command, error) { + switch command { + case "hello": + return []advisor.Command{ + {Snap: "hello", Command: "hello"}, + {Snap: "hello-wcm", Command: "hello"}, + }, nil + case "error-please": + return nil, fmt.Errorf("get failed") + default: + return nil, nil + } +} + +func (*sillyFinder) Close() error { return nil } + +func (s *SnapSuite) TestAdviseCommandHappyText(c *C) { + restore := advisor.ReplaceCommandsFinder(mkSillyFinder) + defer restore() + + rest, err := snap.Parser().ParseArgs([]string{"advise-snap", "--command", "hello"}) + c.Assert(err, IsNil) + c.Assert(rest, DeepEquals, []string{}) + c.Assert(s.Stdout(), Equals, `The program "hello" can be found in the following snaps: + * hello + * hello-wcm +Try: snap install +`) + c.Assert(s.Stderr(), Equals, "") +} + +func (s *SnapSuite) TestAdviseCommandHappyJSON(c *C) { + restore := advisor.ReplaceCommandsFinder(mkSillyFinder) + defer restore() + + rest, err := snap.Parser().ParseArgs([]string{"advise-snap", "--command", "--format=json", "hello"}) + c.Assert(err, IsNil) + c.Assert(rest, DeepEquals, []string{}) + c.Assert(s.Stdout(), Equals, `[{"Snap":"hello","Command":"hello"},{"Snap":"hello-wcm","Command":"hello"}]`+"\n") + c.Assert(s.Stderr(), Equals, "") +} + +func (s *SnapSuite) TestAdviseCommandMisspellText(c *C) { + restore := advisor.ReplaceCommandsFinder(mkSillyFinder) + defer restore() + + for _, misspelling := range []string{"helo", "0hello", "hell0", "hello0"} { + err := snap.AdviseCommand(misspelling, "pretty") + c.Assert(err, IsNil) + c.Assert(s.Stdout(), Equals, fmt.Sprintf(`No command "%s" found, did you mean: + Command "hello" from snap "hello" + Command "hello" from snap "hello-wcm" +`, misspelling)) + c.Assert(s.Stderr(), Equals, "") + + s.stdout.Reset() + s.stderr.Reset() + } +} diff -Nru snapd-2.29.4.2+17.10/cmd/snap/cmd_aliases.go snapd-2.31.1+17.10/cmd/snap/cmd_aliases.go --- snapd-2.29.4.2+17.10/cmd/snap/cmd_aliases.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap/cmd_aliases.go 2018-01-24 20:02:44.000000000 +0000 @@ -90,28 +90,34 @@ } allStatuses, err := Client().Aliases() - if err == nil { + if err != nil { + return err + } + + var infos aliasInfos + filterSnap := string(x.Positionals.Snap) + if filterSnap != "" { + allStatuses = map[string]map[string]client.AliasStatus{ + filterSnap: allStatuses[filterSnap], + } + } + for snapName, aliasStatuses := range allStatuses { + for alias, aliasStatus := range aliasStatuses { + infos = append(infos, &aliasInfo{ + Snap: snapName, + Command: aliasStatus.Command, + Alias: alias, + Status: aliasStatus.Status, + Auto: aliasStatus.Auto, + }) + } + } + + if len(infos) > 0 { w := tabWriter() fmt.Fprintln(w, i18n.G("Command\tAlias\tNotes")) defer w.Flush() - var infos aliasInfos - filterSnap := string(x.Positionals.Snap) - if filterSnap != "" { - allStatuses = map[string]map[string]client.AliasStatus{ - filterSnap: allStatuses[filterSnap], - } - } - for snapName, aliasStatuses := range allStatuses { - for alias, aliasStatus := range aliasStatuses { - infos = append(infos, &aliasInfo{ - Snap: snapName, - Command: aliasStatus.Command, - Alias: alias, - Status: aliasStatus.Status, - Auto: aliasStatus.Auto, - }) - } - } + sort.Sort(infos) for _, info := range infos { @@ -128,6 +134,13 @@ } fmt.Fprintf(w, "%s\t%s\t%s\n", info.Command, info.Alias, notesStr) } + } else { + if filterSnap != "" { + fmt.Fprintf(Stderr, i18n.G("No aliases are currently defined for snap %q.\n"), filterSnap) + } else { + fmt.Fprintln(Stderr, i18n.G("No aliases are currently defined.")) + } + fmt.Fprintln(Stderr, i18n.G("\nUse snap alias --help to learn how to create aliases manually.")) } - return err + return nil } diff -Nru snapd-2.29.4.2+17.10/cmd/snap/cmd_aliases_test.go snapd-2.31.1+17.10/cmd/snap/cmd_aliases_test.go --- snapd-2.29.4.2+17.10/cmd/snap/cmd_aliases_test.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap/cmd_aliases_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -136,10 +136,29 @@ }) _, err := Parser().ParseArgs([]string{"aliases"}) c.Assert(err, IsNil) - expectedStdout := "" + - "Command Alias Notes\n" - c.Assert(s.Stdout(), Equals, expectedStdout) - c.Assert(s.Stderr(), Equals, "") + c.Assert(s.Stdout(), Equals, "") + c.Assert(s.Stderr(), Equals, "No aliases are currently defined.\n\nUse snap alias --help to learn how to create aliases manually.\n") +} + +func (s *SnapSuite) TestAliasesNoneFilterSnap(c *C) { + s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { + c.Check(r.Method, Equals, "GET") + c.Check(r.URL.Path, Equals, "/v2/aliases") + body, err := ioutil.ReadAll(r.Body) + c.Check(err, IsNil) + c.Check(body, DeepEquals, []byte{}) + EncodeResponseBody(c, w, map[string]interface{}{ + "type": "sync", + "result": map[string]map[string]client.AliasStatus{ + "bar": { + "bar0": {Command: "foo", Status: "auto", Auto: "foo"}, + }}, + }) + }) + _, err := Parser().ParseArgs([]string{"aliases", "not-bar"}) + c.Assert(err, IsNil) + c.Assert(s.Stdout(), Equals, "") + c.Assert(s.Stderr(), Equals, "No aliases are currently defined for snap \"not-bar\".\n\nUse snap alias --help to learn how to create aliases manually.\n") } func (s *SnapSuite) TestAliasesSorting(c *C) { diff -Nru snapd-2.29.4.2+17.10/cmd/snap/cmd_can_manage_refreshes.go snapd-2.31.1+17.10/cmd/snap/cmd_can_manage_refreshes.go --- snapd-2.29.4.2+17.10/cmd/snap/cmd_can_manage_refreshes.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap/cmd_can_manage_refreshes.go 2017-12-01 15:51:55.000000000 +0000 @@ -0,0 +1,51 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2016 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package main + +import ( + "fmt" + + "github.com/jessevdk/go-flags" +) + +type cmdCanManageRefreshes struct{} + +func init() { + cmd := addDebugCommand("can-manage-refreshes", + "(internal) return if refresh.schedule=managed can be used", + "(internal) return if refresh.schedule=managed can be used", + func() flags.Commander { + return &cmdCanManageRefreshes{} + }) + cmd.hidden = true +} + +func (x *cmdCanManageRefreshes) Execute(args []string) error { + if len(args) > 0 { + return ErrExtraArgs + } + + var resp bool + if err := Client().Debug("can-manage-refreshes", nil, &resp); err != nil { + return err + } + fmt.Fprintf(Stdout, "%v\n", resp) + return nil +} diff -Nru snapd-2.29.4.2+17.10/cmd/snap/cmd_download.go snapd-2.31.1+17.10/cmd/snap/cmd_download.go --- snapd-2.29.4.2+17.10/cmd/snap/cmd_download.go 2017-11-30 07:12:26.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap/cmd_download.go 2018-01-24 20:02:44.000000000 +0000 @@ -61,19 +61,19 @@ }}) } -func fetchSnapAssertions(tsto *image.ToolingStore, snapPath string, snapInfo *snap.Info) error { +func fetchSnapAssertions(tsto *image.ToolingStore, snapPath string, snapInfo *snap.Info) (string, error) { db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{ Backstore: asserts.NewMemoryBackstore(), Trusted: sysdb.Trusted(), }) if err != nil { - return err + return "", err } assertPath := strings.TrimSuffix(snapPath, filepath.Ext(snapPath)) + ".assert" w, err := os.Create(assertPath) if err != nil { - return fmt.Errorf(i18n.G("cannot create assertions file: %v"), err) + return "", fmt.Errorf(i18n.G("cannot create assertions file: %v"), err) } defer w.Close() @@ -84,7 +84,7 @@ f := tsto.AssertionFetcher(db, save) _, err = image.FetchAndCheckSnapAssertions(snapPath, snapInfo, f, db) - return err + return assertPath, err } func (x *cmdDownload) Execute(args []string) error { @@ -114,7 +114,7 @@ return err } - fmt.Fprintf(Stderr, i18n.G("Fetching snap %q\n"), snapName) + fmt.Fprintf(Stdout, i18n.G("Fetching snap %q\n"), snapName) dlOpts := image.DownloadOptions{ TargetDir: "", // cwd Channel: x.Channel, @@ -124,11 +124,25 @@ return err } - fmt.Fprintf(Stderr, i18n.G("Fetching assertions for %q\n"), snapName) - err = fetchSnapAssertions(tsto, snapPath, snapInfo) + fmt.Fprintf(Stdout, i18n.G("Fetching assertions for %q\n"), snapName) + assertPath, err := fetchSnapAssertions(tsto, snapPath, snapInfo) if err != nil { return err } + // simplify paths + wd, _ := os.Getwd() + if p, err := filepath.Rel(wd, assertPath); err == nil { + assertPath = p + } + if p, err := filepath.Rel(wd, snapPath); err == nil { + snapPath = p + } + // add a hint what to do with the downloaded snap (LP:1676707) + fmt.Fprintf(Stdout, i18n.G(`Install the snap with: + snap ack %s + snap install %s +`), assertPath, snapPath) + return nil } diff -Nru snapd-2.29.4.2+17.10/cmd/snap/cmd_find.go snapd-2.31.1+17.10/cmd/snap/cmd_find.go --- snapd-2.29.4.2+17.10/cmd/snap/cmd_find.go 2017-11-30 07:12:26.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap/cmd_find.go 2018-01-31 08:47:06.000000000 +0000 @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2016 Canonical Ltd + * Copyright (C) 2016-2018 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,8 +20,11 @@ package main import ( + "bufio" "errors" "fmt" + "os" + "sort" "strings" "github.com/jessevdk/go-flags" @@ -29,6 +32,8 @@ "github.com/snapcore/snapd/client" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/i18n" + "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/strutil" ) var shortFindHelp = i18n.G("Finds packages to install") @@ -87,9 +92,44 @@ return ret } +func getSections() (sections []string, err error) { + if cachedSections, err := os.Open(dirs.SnapSectionsFile); err == nil { + // try loading from cached sections file + defer cachedSections.Close() + + r := bufio.NewScanner(cachedSections) + sections = make([]string, 0, 10) + for r.Scan() { + sections = append(sections, r.Text()) + } + if r.Err() == nil && len(sections) > 0 { + return sections, nil + } + } + + // fallback to listing from the daemon + cli := Client() + return cli.Sections() +} + +func showSections() error { + sections, err := getSections() + if err != nil { + return err + } + sort.Strings(sections) + + fmt.Fprintf(Stdout, i18n.G("No section specified. Available sections:\n")) + for _, sec := range sections { + fmt.Fprintf(Stdout, " * %s\n", sec) + } + fmt.Fprintf(Stdout, i18n.G("Please try: snap find --section=\n")) + return nil +} + type cmdFind struct { Private bool `long:"private"` - Section SectionName `long:"section"` + Section SectionName `long:"section" optional:"true" optional-value:"show-all-sections-please" default:"no-section-specified"` Positional struct { Query string } `positional-args:"yes"` @@ -112,40 +152,83 @@ return ErrExtraArgs } + // LP: 1740605 + if strings.TrimSpace(x.Positional.Query) == "" { + x.Positional.Query = "" + } + + // section will be: + // - "show-all-sections-please" if the user specified --section + // without any argument + // - "no-section-specified" if "--section" was not specified on + // the commandline at all + switch x.Section { + case "show-all-sections-please": + return showSections() + case "no-section-specified": + x.Section = "" + } + // magic! `snap find` returns the featured snaps - if x.Positional.Query == "" && x.Section == "" { + showFeatured := (x.Positional.Query == "" && x.Section == "") + if showFeatured { x.Section = "featured" } - return findSnaps(&client.FindOptions{ + cli := Client() + + if x.Section != "" && x.Section != "featured" { + sections, err := getSections() + if err != nil { + return err + } + if !strutil.ListContains(sections, string(x.Section)) { + // TRANSLATORS: the %q is the (quoted) name of the section the user entered + return fmt.Errorf(i18n.G("No matching section %q, use --section to list existing sections"), x.Section) + } + } + + opts := &client.FindOptions{ Private: x.Private, Section: string(x.Section), Query: x.Positional.Query, - }) -} - -func findSnaps(opts *client.FindOptions) error { - cli := Client() + } snaps, resInfo, err := cli.Find(opts) + if e, ok := err.(*client.Error); ok && e.Kind == client.ErrorKindNetworkTimeout { + logger.Debugf("cannot list snaps: %v", e) + return fmt.Errorf("unable to contact snap store") + } if err != nil { return err } - if len(snaps) == 0 { - // TRANSLATORS: the %q is the (quoted) query the user entered - fmt.Fprintf(Stderr, i18n.G("The search %q returned 0 snaps\n"), opts.Query) + if x.Section == "" { + // TRANSLATORS: the %q is the (quoted) query the user entered + fmt.Fprintf(Stderr, i18n.G("No matching snaps for %q\n"), opts.Query) + } else { + // TRANSLATORS: the first %q is the (quoted) query, the + // second %q is the (quoted) name of the section the + // user entered + fmt.Fprintf(Stderr, i18n.G("No matching snaps for %q in section %q\n"), opts.Query, x.Section) + } return nil } - w := tabWriter() - defer w.Flush() + // show featured header *after* we checked for errors from the find + if showFeatured { + fmt.Fprintf(Stdout, i18n.G("No search term specified. Here are some interesting snaps:\n\n")) + } + w := tabWriter() fmt.Fprintln(w, i18n.G("Name\tVersion\tDeveloper\tNotes\tSummary")) - for _, snap := range snaps { // TODO: get snap.Publisher, so we can only show snap.Developer if it's different fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", snap.Name, snap.Version, snap.Developer, NotesFromRemote(snap, resInfo), snap.Summary) } + w.Flush() + if showFeatured { + fmt.Fprintf(Stdout, i18n.G("\nProvide a search term for more specific results.\n")) + } return nil } diff -Nru snapd-2.29.4.2+17.10/cmd/snap/cmd_find_test.go snapd-2.31.1+17.10/cmd/snap/cmd_find_test.go --- snapd-2.29.4.2+17.10/cmd/snap/cmd_find_test.go 2016-12-08 15:14:07.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap/cmd_find_test.go 2018-01-31 15:22:48.000000000 +0000 @@ -21,12 +21,16 @@ import ( "fmt" + "io/ioutil" "net/http" + "os" + "path" "github.com/jessevdk/go-flags" "gopkg.in/check.v1" snap "github.com/snapcore/snapd/cmd/snap" + "github.com/snapcore/snapd/dirs" ) const findJSON = ` @@ -226,7 +230,8 @@ "status": "priced", "summary": "GNU Hello, the \"hello world\" snap", "type": "app", - "version": "2.10" + "version": "2.10", + "license": "Proprietary" } ], "sources": [ @@ -362,3 +367,149 @@ {Item: "foo"}, }) } + +const findNetworkTimeoutErrorJSON = ` +{ + "type": "error", + "result": { + "message": "Get https://search.apps.ubuntu.com/api/v1/snaps/search?confinement=strict%2Cclassic&fields=anon_download_url%2Carchitecture%2Cchannel%2Cdownload_sha3_384%2Csummary%2Cdescription%2Cdeltas%2Cbinary_filesize%2Cdownload_url%2Cepoch%2Cicon_url%2Clast_updated%2Cpackage_name%2Cprices%2Cpublisher%2Cratings_average%2Crevision%2Cscreenshot_urls%2Csnap_id%2Csupport_url%2Ccontact%2Ctitle%2Ccontent%2Cversion%2Corigin%2Cdeveloper_id%2Cprivate%2Cconfinement%2Cchannel_maps_list&q=test: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)", + "kind": "network-timeout" + }, + "status-code": 400 +}` + +func (s *SnapSuite) TestFindNetworkTimeoutError(c *check.C) { + 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, findNetworkTimeoutErrorJSON) + default: + c.Fatalf("expected to get 1 requests, now on %d", n+1) + } + + n++ + }) + _, err := snap.Parser().ParseArgs([]string{"find", "hello"}) + c.Assert(err, check.ErrorMatches, `unable to contact snap store`) + c.Check(s.Stdout(), check.Equals, "") +} + +func (s *SnapSuite) TestFindSnapSectionOverview(c *check.C) { + n := 0 + s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { + switch n { + case 0, 1: + c.Check(r.Method, check.Equals, "GET") + c.Check(r.URL.Path, check.Equals, "/v2/sections") + EncodeResponseBody(c, w, map[string]interface{}{ + "type": "sync", + "result": []string{"sec2", "sec1"}, + }) + default: + c.Fatalf("expected to get 2 requests, now on #%d", n+1) + } + n++ + }) + + rest, err := snap.Parser().ParseArgs([]string{"find", "--section"}) + + c.Assert(err, check.IsNil) + c.Assert(rest, check.DeepEquals, []string{}) + + c.Check(s.Stdout(), check.Equals, `No section specified. Available sections: + * sec1 + * sec2 +Please try: snap find --section= +`) + c.Check(s.Stderr(), check.Equals, "") + + s.ResetStdStreams() +} + +func (s *SnapSuite) TestFindSnapInvalidSection(c *check.C) { + 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/sections") + EncodeResponseBody(c, w, map[string]interface{}{ + "type": "sync", + "result": []string{"sec1"}, + }) + default: + c.Fatalf("expected to get 1 request, now on %d", n+1) + } + + n++ + }) + _, err := snap.Parser().ParseArgs([]string{"find", "--section=foobar", "hello"}) + c.Assert(err, check.ErrorMatches, `No matching section "foobar", use --section to list existing sections`) +} + +func (s *SnapSuite) TestFindSnapNotFoundInSection(c *check.C) { + 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/sections") + EncodeResponseBody(c, w, map[string]interface{}{ + "type": "sync", + "result": []string{"foobar"}, + }) + case 1: + c.Check(r.Method, check.Equals, "GET") + c.Check(r.URL.Path, check.Equals, "/v2/find") + v, ok := r.URL.Query()["section"] + c.Check(ok, check.Equals, true) + c.Check(v, check.DeepEquals, []string{"foobar"}) + EncodeResponseBody(c, w, map[string]interface{}{ + "type": "sync", + "result": []string{}, + }) + default: + c.Fatalf("expected to get 2 requests, now on #%d", n+1) + } + n++ + }) + + _, err := snap.Parser().ParseArgs([]string{"find", "--section=foobar", "hello"}) + c.Assert(err, check.IsNil) + c.Check(s.Stderr(), check.Equals, "No matching snaps for \"hello\" in section \"foobar\"\n") + c.Check(s.Stdout(), check.Equals, "") + + s.ResetStdStreams() +} + +func (s *SnapSuite) TestFindSnapCachedSection(c *check.C) { + s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { + c.Fatalf("not expecting any requests") + }) + + os.MkdirAll(path.Dir(dirs.SnapSectionsFile), 0755) + ioutil.WriteFile(dirs.SnapSectionsFile, []byte("sec1\nsec2\nsec3"), 0644) + + _, err := snap.Parser().ParseArgs([]string{"find", "--section=foobar", "hello"}) + c.Logf("stdout: %s", s.Stdout()) + c.Assert(err, check.ErrorMatches, `No matching section "foobar", use --section to list existing sections`) + + s.ResetStdStreams() + + rest, err := snap.Parser().ParseArgs([]string{"find", "--section"}) + + c.Assert(err, check.IsNil) + c.Assert(rest, check.DeepEquals, []string{}) + + c.Check(s.Stdout(), check.Equals, `No section specified. Available sections: + * sec1 + * sec2 + * sec3 +Please try: snap find --section= +`) + + s.ResetStdStreams() +} diff -Nru snapd-2.29.4.2+17.10/cmd/snap/cmd_info.go snapd-2.31.1+17.10/cmd/snap/cmd_info.go --- snapd-2.29.4.2+17.10/cmd/snap/cmd_info.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap/cmd_info.go 2018-01-31 15:22:48.000000000 +0000 @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2016 Canonical Ltd + * Copyright (C) 2016-2018 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 @@ -46,7 +46,7 @@ } `positional-args:"yes" required:"yes"` } -var shortInfoHelp = i18n.G("show detailed information about a snap") +var shortInfoHelp = i18n.G("Show detailed information about a snap") var longInfoHelp = i18n.G(` The info command shows detailed information about a snap, be it by name or by path.`) @@ -300,7 +300,11 @@ both := coalesce(local, remote) if both == nil { - fmt.Fprintf(w, "argument:\t%q\nwarning:\t%s\n", snapName, i18n.G("not a valid snap")) + if len(x.Positional.Snaps) == 1 { + return fmt.Errorf("no snap found for %q", snapName) + } + + fmt.Fprintf(w, fmt.Sprintf(i18n.G("warning:\tno snap found for %q\n"), snapName)) continue } noneOK = false @@ -313,6 +317,11 @@ if both.Contact != "" { fmt.Fprintf(w, "contact:\t%s\n", strings.TrimPrefix(both.Contact, "mailto:")) } + license := both.License + if license == "" { + license = "unknown" + } + fmt.Fprintf(w, "license:\t%s\n", license) maybePrintPrice(w, remote, resInfo) // FIXME: find out for real termWidth := 77 @@ -341,6 +350,8 @@ } else { fmt.Fprintf(w, " broken:\t%t (%s)\n", true, local.Broken) } + + fmt.Fprintf(w, " ignore-validation:\t%t\n", local.IgnoreValidation) } else { notes = NotesFromLocal(local) } diff -Nru snapd-2.29.4.2+17.10/cmd/snap/cmd_info_test.go snapd-2.31.1+17.10/cmd/snap/cmd_info_test.go --- snapd-2.29.4.2+17.10/cmd/snap/cmd_info_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap/cmd_info_test.go 2018-01-31 15:22:48.000000000 +0000 @@ -114,6 +114,7 @@ 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 @@ -144,7 +145,8 @@ "status": "available", "summary": "The GNU Hello snap", "type": "app", - "version": "2.10" + "version": "2.10", + "license": "MIT" } ], "sources": [ @@ -178,6 +180,7 @@ c.Check(s.Stdout(), check.Equals, `name: hello summary: The GNU Hello snap publisher: canonical +license: MIT description: | GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/ @@ -185,3 +188,126 @@ `) c.Check(s.Stderr(), check.Equals, "") } + +const mockInfoJSONOtherLicense = ` +{ + "type": "sync", + "status-code": 200, + "status": "OK", + "result": { + "channel": "stable", + "confinement": "strict", + "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/", + "developer": "canonical", + "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6", + "name": "hello", + "private": false, + "resource": "/v2/snaps/hello", + "revision": "1", + "status": "available", + "summary": "The GNU Hello snap", + "type": "app", + "version": "2.10", + "license": "BSD-3", + "tracking-channel": "beta", + "installed-size": 1024 + } +} +` +const mockInfoJSONNoLicense = ` +{ + "type": "sync", + "status-code": 200, + "status": "OK", + "result": { + "channel": "stable", + "confinement": "strict", + "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/", + "developer": "canonical", + "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6", + "name": "hello", + "private": false, + "resource": "/v2/snaps/hello", + "revision": "1", + "status": "available", + "summary": "The GNU Hello snap", + "type": "app", + "version": "2.10", + "license": "", + "tracking-channel": "beta", + "installed-size": 1024 + } +} +` + +func (s *SnapSuite) TestInfoWithLocalDifferentLicense(c *check.C) { + 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, mockInfoJSON) + case 1: + c.Check(r.Method, check.Equals, "GET") + c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello") + fmt.Fprintln(w, mockInfoJSONOtherLicense) + default: + c.Fatalf("expected to get 1 requests, now on %d (%v)", n+1, r) + } + + n++ + }) + rest, err := snap.Parser().ParseArgs([]string{"info", "hello"}) + c.Assert(err, check.IsNil) + c.Assert(rest, check.DeepEquals, []string{}) + c.Check(s.Stdout(), check.Equals, `name: hello +summary: The GNU Hello snap +publisher: canonical +license: BSD-3 +description: | + GNU hello prints a friendly greeting. This is part of the snapcraft tour at + https://snapcraft.io/ +snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 +tracking: beta +installed: 2.10 (1) 1kB disabled +refreshed: 0001-01-01 00:00:00 +0000 UTC +`) + c.Check(s.Stderr(), check.Equals, "") +} + +func (s *SnapSuite) TestInfoWithLocalNoLicense(c *check.C) { + 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, mockInfoJSON) + case 1: + c.Check(r.Method, check.Equals, "GET") + c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello") + fmt.Fprintln(w, mockInfoJSONNoLicense) + default: + c.Fatalf("expected to get 1 requests, now on %d (%v)", n+1, r) + } + + n++ + }) + rest, err := snap.Parser().ParseArgs([]string{"info", "hello"}) + c.Assert(err, check.IsNil) + c.Assert(rest, check.DeepEquals, []string{}) + c.Check(s.Stdout(), check.Equals, `name: hello +summary: The GNU Hello snap +publisher: canonical +license: unknown +description: | + GNU hello prints a friendly greeting. This is part of the snapcraft tour at + https://snapcraft.io/ +snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 +tracking: beta +installed: 2.10 (1) 1kB disabled +refreshed: 0001-01-01 00:00:00 +0000 UTC +`) + c.Check(s.Stderr(), check.Equals, "") +} diff -Nru snapd-2.29.4.2+17.10/cmd/snap/cmd_keys.go snapd-2.31.1+17.10/cmd/snap/cmd_keys.go --- snapd-2.29.4.2+17.10/cmd/snap/cmd_keys.go 2016-09-15 18:55:10.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap/cmd_keys.go 2018-01-24 20:02:44.000000000 +0000 @@ -49,42 +49,54 @@ Sha3_384 string `json:"sha3-384"` } +func outputJSON(keys []Key) error { + obj, err := json.Marshal(keys) + if err != nil { + return err + } + fmt.Fprintf(Stdout, "%s\n", obj) + return nil +} + +func outputText(keys []Key) error { + if len(keys) == 0 { + fmt.Fprintf(Stdout, "No keys registered, see `snapcraft create-key`") + return nil + } + + w := tabWriter() + defer w.Flush() + + fmt.Fprintln(w, i18n.G("Name\tSHA3-384")) + for _, key := range keys { + fmt.Fprintf(w, "%s\t%s\n", key.Name, key.Sha3_384) + } + return nil +} + func (x *cmdKeys) Execute(args []string) error { if len(args) > 0 { return ErrExtraArgs } - w := tabWriter() - if !x.JSON { - fmt.Fprintln(w, i18n.G("Name\tSHA3-384")) - defer w.Flush() - } keys := []Key{} manager := asserts.NewGPGKeypairManager() - display := func(privk asserts.PrivateKey, fpr string, uid string) error { + collect := func(privk asserts.PrivateKey, fpr string, uid string) error { key := Key{ Name: uid, Sha3_384: privk.PublicKey().ID(), } - if x.JSON { - keys = append(keys, key) - } else { - fmt.Fprintf(w, "%s\t%s\n", key.Name, key.Sha3_384) - } + keys = append(keys, key) return nil } - err := manager.Walk(display) + err := manager.Walk(collect) if err != nil { return err } if x.JSON { - obj, err := json.Marshal(keys) - if err != nil { - return err - } - fmt.Fprintf(Stdout, "%s\n", obj) + return outputJSON(keys) } - return nil + return outputText(keys) } diff -Nru snapd-2.29.4.2+17.10/cmd/snap/cmd_keys_test.go snapd-2.31.1+17.10/cmd/snap/cmd_keys_test.go --- snapd-2.29.4.2+17.10/cmd/snap/cmd_keys_test.go 2016-11-24 09:36:03.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap/cmd_keys_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -35,6 +35,7 @@ BaseSnapSuite GnupgCmd string + tempdir string } // FIXME: Ideally we would just use gpg2 and remove the gnupg2_test.go file. @@ -61,21 +62,21 @@ func (s *SnapKeysSuite) SetUpTest(c *C) { s.BaseSnapSuite.SetUpTest(c) - tempdir := c.MkDir() + s.tempdir = c.MkDir() for _, fileName := range []string{"pubring.gpg", "secring.gpg", "trustdb.gpg"} { data, err := ioutil.ReadFile(filepath.Join("test-data", fileName)) c.Assert(err, IsNil) - err = ioutil.WriteFile(filepath.Join(tempdir, fileName), data, 0644) + err = ioutil.WriteFile(filepath.Join(s.tempdir, fileName), data, 0644) c.Assert(err, IsNil) } - fakePinentryFn := filepath.Join(tempdir, "pinentry-fake") + fakePinentryFn := filepath.Join(s.tempdir, "pinentry-fake") err := ioutil.WriteFile(fakePinentryFn, fakePinentryData, 0755) c.Assert(err, IsNil) - gpgAgentConfFn := filepath.Join(tempdir, "gpg-agent.conf") + gpgAgentConfFn := filepath.Join(s.tempdir, "gpg-agent.conf") err = ioutil.WriteFile(gpgAgentConfFn, []byte(fmt.Sprintf(`pinentry-program %s`, fakePinentryFn)), 0644) c.Assert(err, IsNil) - os.Setenv("SNAP_GNUPG_HOME", tempdir) + os.Setenv("SNAP_GNUPG_HOME", s.tempdir) os.Setenv("SNAP_GNUPG_CMD", s.GnupgCmd) } @@ -96,6 +97,18 @@ c.Check(s.Stderr(), Equals, "") } +func (s *SnapKeysSuite) TestKeysEmptyNoHeader(c *C) { + // simulate empty keys + err := os.RemoveAll(s.tempdir) + c.Assert(err, IsNil) + + rest, err := snap.Parser().ParseArgs([]string{"keys"}) + c.Assert(err, IsNil) + c.Assert(rest, DeepEquals, []string{}) + c.Check(s.Stdout(), Equals, "No keys registered, see `snapcraft create-key`") + c.Check(s.Stderr(), Equals, "") +} + func (s *SnapKeysSuite) TestKeysJSON(c *C) { rest, err := snap.Parser().ParseArgs([]string{"keys", "--json"}) c.Assert(err, IsNil) diff -Nru snapd-2.29.4.2+17.10/cmd/snap/cmd_known_test.go snapd-2.31.1+17.10/cmd/snap/cmd_known_test.go --- snapd-2.29.4.2+17.10/cmd/snap/cmd_known_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap/cmd_known_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -69,7 +69,7 @@ switch n { case 0: c.Check(r.Method, check.Equals, "GET") - c.Check(r.URL.Path, check.Equals, "/assertions/model/16/canonical/pi99") + c.Check(r.URL.Path, check.Equals, "/api/v1/snaps/assertions/model/16/canonical/pi99") fmt.Fprintln(w, mockModelAssertion) default: c.Fatalf("expected to get 1 requests, now on %d", n+1) diff -Nru snapd-2.29.4.2+17.10/cmd/snap/cmd_pack.go snapd-2.31.1+17.10/cmd/snap/cmd_pack.go --- snapd-2.29.4.2+17.10/cmd/snap/cmd_pack.go 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap/cmd_pack.go 2018-01-31 08:47:06.000000000 +0000 @@ -35,7 +35,7 @@ } `positional-args:"yes"` } -var shortPackHelp = i18n.G("pack the given target dir as a snap") +var shortPackHelp = i18n.G("Pack the given target dir as a snap") var longPackHelp = i18n.G(` The pack command packs the given snap-dir as a snap.`) diff -Nru snapd-2.29.4.2+17.10/cmd/snap/cmd_run.go snapd-2.31.1+17.10/cmd/snap/cmd_run.go --- snapd-2.29.4.2+17.10/cmd/snap/cmd_run.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap/cmd_run.go 2018-01-31 08:47:06.000000000 +0000 @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2014-2016 Canonical Ltd + * Copyright (C) 2014-2018 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,11 +20,15 @@ package main import ( + "bufio" "fmt" "io" "os" + "os/exec" "os/user" "path/filepath" + "regexp" + "strconv" "strings" "syscall" @@ -50,6 +54,8 @@ Hook string `long:"hook" hidden:"yes"` Revision string `short:"r" default:"unset" hidden:"yes"` Shell bool `long:"shell" ` + // FIXME: provide a way to pass options to strace + Strace bool `long:"strace"` } func init() { @@ -63,6 +69,7 @@ "hook": i18n.G("Hook to run"), "r": i18n.G("Use a specific snap revision when running hook"), "shell": i18n.G("Run a shell instead of the command (useful for debugging)"), + "strace": i18n.G("Run the command under strace (useful for debugging"), }, nil) } @@ -91,13 +98,74 @@ } // pass shell as a special command to snap-exec - if x.Shell { + switch { + case x.Shell: x.Command = "shell" + case x.Strace: + x.Command = "strace" + } + + if x.Command == "complete" { + snapApp, args = antialias(snapApp, args) } return snapRunApp(snapApp, x.Command, args) } +// antialias changes snapApp and args if snapApp is actually an alias +// for something else. If not, or if the args aren't what's expected +// for completion, it returns them unchanged. +func antialias(snapApp string, args []string) (string, []string) { + if len(args) < 7 { + // NOTE if len(args) < 7, Something is Wrong (at least WRT complete.sh and etelpmoc.sh) + return snapApp, args + } + + actualApp, err := resolveApp(snapApp) + if err != nil || actualApp == snapApp { + // no alias! woop. + return snapApp, args + } + + compPoint, err := strconv.Atoi(args[2]) + if err != nil { + // args[2] is not COMP_POINT + return snapApp, args + } + + if compPoint <= len(snapApp) { + // COMP_POINT is inside $0 + return snapApp, args + } + + if compPoint > len(args[5]) { + // COMP_POINT is bigger than $# + return snapApp, args + } + + if args[6] != snapApp { + // args[6] is not COMP_WORDS[0] + return snapApp, args + } + + // it _should_ be COMP_LINE followed by one of + // COMP_WORDBREAKS, but that's hard to do + re, err := regexp.Compile(`^` + regexp.QuoteMeta(snapApp) + `\b`) + if err != nil || !re.MatchString(args[5]) { + // (weird regexp error, or) args[5] is not COMP_LINE + return snapApp, args + } + + argsOut := make([]string, len(args)) + copy(argsOut, args) + + argsOut[2] = strconv.Itoa(compPoint - len(snapApp) + len(actualApp)) + argsOut[5] = re.ReplaceAllLiteralString(args[5], actualApp) + argsOut[6] = actualApp + + return actualApp, argsOut +} + func getSnapInfo(snapName string, revision snap.Revision) (*snap.Info, error) { if revision.Unset() { curFn := filepath.Join(dirs.SnapMountDir, snapName, "current") @@ -363,6 +431,114 @@ return targetPath, nil } +func straceCmd() ([]string, error) { + current, err := user.Current() + if err != nil { + return nil, err + } + sudoPath, err := exec.LookPath("sudo") + if err != nil { + return nil, fmt.Errorf("cannot use strace without sudo: %s", err) + } + + // try strace from the snap first, we use new syscalls like + // "_newselect" that are known to not work with the strace of e.g. + // ubuntu 14.04 + var stracePath string + cand := filepath.Join(dirs.SnapMountDir, "strace-static", "current", "bin", "strace") + if osutil.FileExists(cand) { + stracePath = cand + } + if stracePath == "" { + stracePath, err = exec.LookPath("strace") + if err != nil { + return nil, fmt.Errorf("cannot find an installed strace, please try: `snap install strace-static`") + } + } + + return []string{ + sudoPath, "-E", + stracePath, + "-u", current.Username, + "-f", + "-e", "!select,pselect6,_newselect,clock_gettime,sigaltstack,gettid", + }, nil +} + +func runCmdUnderStrace(origCmd, env []string) error { + // prepend strace magic + cmd, err := straceCmd() + if err != nil { + return err + } + cmd = append(cmd, origCmd...) + + // run with filter + gcmd := exec.Command(cmd[0], cmd[1:]...) + gcmd.Env = env + gcmd.Stdin = Stdin + gcmd.Stdout = Stdout + stderr, err := gcmd.StderrPipe() + if err != nil { + return err + } + filterDone := make(chan bool, 1) + go func() { + defer func() { filterDone <- true }() + + r := bufio.NewReader(stderr) + + // the first thing from strace if things work is + // "exeve" - show everything until we see this to + // not swallow real strace errors + for { + s, err := r.ReadString('\n') + if err != nil { + break + } + if strings.Contains(s, "execve(") { + break + } + fmt.Fprint(Stderr, s) + } + + // The last thing that snap-exec does is to + // execve() something inside the snap dir so + // we know that from that point on the output + // will be interessting to the user. + // + // We need check both /snap (which is where snaps + // are located inside the mount namespace) and the + // distro snap mount dir (which is different on e.g. + // fedora/arch) to fully work with classic snaps. + needle1 := fmt.Sprintf(`execve("%s`, dirs.SnapMountDir) + needle2 := `execve("/snap` + for { + s, err := r.ReadString('\n') + if err != nil { + if err != io.EOF { + fmt.Fprintf(Stderr, "cannot read strace output: %s\n", err) + } + break + } + // ensure we catch the execve but *not* the + // exec into + // /snap/core/current/usr/lib/snapd/snap-confine + if (strings.Contains(s, needle1) || strings.Contains(s, needle2)) && !strings.Contains(s, "usr/lib/snapd/snap-confine") { + fmt.Fprint(Stderr, s) + break + } + } + io.Copy(Stderr, r) + }() + if err := gcmd.Start(); err != nil { + return err + } + <-filterDone + err = gcmd.Wait() + return err +} + func runSnapConfine(info *snap.Info, securityTag, snapApp, command, hook string, args []string) error { snapConfine := filepath.Join(dirs.DistroLibExecDir, "snap-confine") // if we re-exec, we must run the snap-confine from the core snap @@ -391,7 +567,15 @@ logger.Noticef("WARNING: cannot copy user Xauthority file: %s", err) } - cmd := []string{snapConfine} + var cmd []string + + var useStrace bool + if command == "strace" { + command = "" + useStrace = true + } + cmd = append(cmd, snapConfine) + if info.NeedsClassic() { cmd = append(cmd, "--classic") } @@ -399,7 +583,24 @@ cmd = append(cmd, "--base", info.Base) } cmd = append(cmd, securityTag) - cmd = append(cmd, filepath.Join(dirs.CoreLibExecDir, "snap-exec")) + + // when under confinement, snap-exec is run from 'core' snap rootfs + snapExecPath := filepath.Join(dirs.CoreLibExecDir, "snap-exec") + + if info.NeedsClassic() { + // running with classic confinement, carefully pick snap-exec we + // are going to use + if isReexeced() { + // same rule as when choosing the location of snap-confine + snapExecPath = filepath.Join(dirs.SnapMountDir, "core/current", + dirs.CoreLibExecDir, "snap-exec") + } else { + // there is no mount namespace where 'core' is the + // rootfs, hence we need to use distro's snap-exec + snapExecPath = filepath.Join(dirs.DistroLibExecDir, "snap-exec") + } + } + cmd = append(cmd, snapExecPath) if command != "" { cmd = append(cmd, "--command="+command) @@ -419,5 +620,9 @@ } env := snapenv.ExecEnv(info, extraEnv) - return syscallExec(cmd[0], cmd, env) + if useStrace { + return runCmdUnderStrace(cmd, env) + } else { + return syscallExec(cmd[0], cmd, env) + } } diff -Nru snapd-2.29.4.2+17.10/cmd/snap/cmd_run_test.go snapd-2.31.1+17.10/cmd/snap/cmd_run_test.go --- snapd-2.29.4.2+17.10/cmd/snap/cmd_run_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap/cmd_run_test.go 2018-01-31 08:47:06.000000000 +0000 @@ -24,6 +24,7 @@ "os" "os/user" "path/filepath" + "strings" "gopkg.in/check.v1" @@ -157,9 +158,45 @@ c.Check(execArgs, check.DeepEquals, []string{ filepath.Join(dirs.DistroLibExecDir, "snap-confine"), "--classic", "snap.snapname.app", - filepath.Join(dirs.CoreLibExecDir, "snap-exec"), + filepath.Join(dirs.DistroLibExecDir, "snap-exec"), "snapname.app", "--arg1", "arg2"}) c.Check(execEnv, testutil.Contains, "SNAP_REVISION=x2") + +} + +func (s *SnapSuite) TestSnapRunClassicAppIntegrationReexeced(c *check.C) { + mountedCorePath := filepath.Join(dirs.SnapMountDir, "core/current") + mountedCoreLibExecPath := filepath.Join(mountedCorePath, dirs.CoreLibExecDir) + + defer mockSnapConfine(mountedCoreLibExecPath)() + + // mock installed snap + si := snaptest.MockSnap(c, string(mockYaml)+"confinement: classic\n", string(mockContents), &snap.SideInfo{ + Revision: snap.R("x2"), + }) + err := os.Symlink(si.MountDir(), filepath.Join(si.MountDir(), "../current")) + c.Assert(err, check.IsNil) + + restore := snaprun.MockOsReadlink(func(name string) (string, error) { + // pretend 'snap' is reexeced from 'core' + return filepath.Join(mountedCorePath, "usr/bin/snap"), nil + }) + defer restore() + + execArgs := []string{} + restorer := snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error { + execArgs = args + return nil + }) + defer restorer() + rest, err := snaprun.Parser().ParseArgs([]string{"run", "snapname.app", "--arg1", "arg2"}) + c.Assert(err, check.IsNil) + c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) + c.Check(execArgs, check.DeepEquals, []string{ + filepath.Join(mountedCoreLibExecPath, "snap-confine"), "--classic", + "snap.snapname.app", + filepath.Join(mountedCoreLibExecPath, "snap-exec"), + "snapname.app", "--arg1", "arg2"}) } func (s *SnapSuite) TestSnapRunAppWithCommandIntegration(c *check.C) { @@ -538,3 +575,127 @@ err = x11.ValidateXauthorityFile(expectedXauthPath) c.Assert(err, check.IsNil) } + +// build the args for a hypothetical completer +func mkCompArgs(compPoint string, argv ...string) []string { + out := []string{ + "99", // COMP_TYPE + "99", // COMP_KEY + "", // COMP_POINT + "2", // COMP_CWORD + " ", // COMP_WORDBREAKS + } + out[2] = compPoint + out = append(out, strings.Join(argv, " ")) + out = append(out, argv...) + return out +} + +func (s *SnapSuite) TestAntialiasHappy(c *check.C) { + c.Assert(os.MkdirAll(dirs.SnapBinariesDir, 0755), check.IsNil) + + inArgs := mkCompArgs("10", "alias", "alias", "bo-alias") + + // first not so happy because no alias symlink + app, outArgs := snaprun.Antialias("alias", inArgs) + c.Check(app, check.Equals, "alias") + c.Check(outArgs, check.DeepEquals, inArgs) + + c.Assert(os.Symlink("an-app", filepath.Join(dirs.SnapBinariesDir, "alias")), check.IsNil) + + // now really happy + app, outArgs = snaprun.Antialias("alias", inArgs) + c.Check(app, check.Equals, "an-app") + c.Check(outArgs, check.DeepEquals, []string{ + "99", // COMP_TYPE (no change) + "99", // COMP_KEY (no change) + "11", // COMP_POINT (+1 because "an-app" is one longer than "alias") + "2", // COMP_CWORD (no change) + " ", // COMP_WORDBREAKS (no change) + "an-app alias bo-alias", // COMP_LINE (argv[0] changed) + "an-app", // argv (arv[0] changed) + "alias", + "bo-alias", + }) +} + +func (s *SnapSuite) 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) + + // weird1 has COMP_LINE not start with COMP_WORDS[0], argv[0] equal to COMP_WORDS[0] + weird1 := mkCompArgs("6", "alias", "") + weird1[5] = "xxxxx " + // weird2 has COMP_LINE not start with COMP_WORDS[0], argv[0] equal to the first word in COMP_LINE + weird2 := mkCompArgs("6", "xxxxx", "") + weird2[5] = "alias " + + for desc, inArgs := range map[string][]string{ + "nil args": nil, + "too-short args": {"alias"}, + "COMP_POINT not a number": mkCompArgs("hello", "alias"), + "COMP_POINT is inside argv[0]": mkCompArgs("2", "alias", ""), + "COMP_POINT is outside argv": mkCompArgs("99", "alias", ""), + "COMP_WORDS[0] is not argv[0]": mkCompArgs("10", "not-alias", ""), + "mismatch between argv[0], COMP_LINE and COMP_WORDS, #1": weird1, + "mismatch between argv[0], COMP_LINE and COMP_WORDS, #2": weird2, + } { + // antialias leaves args alone if it's too short + app, outArgs := snaprun.Antialias("alias", inArgs) + c.Check(app, check.Equals, "alias", check.Commentf(desc)) + c.Check(outArgs, check.DeepEquals, inArgs, check.Commentf(desc)) + } +} + +func (s *SnapSuite) TestSnapRunAppWithStraceIntegration(c *check.C) { + defer mockSnapConfine(dirs.DistroLibExecDir)() + + // mock installed snap + si := snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{ + Revision: snap.R("x2"), + }) + err := os.Symlink(si.MountDir(), filepath.Join(si.MountDir(), "../current")) + c.Assert(err, check.IsNil) + + // pretend we have sudo and simulate some useful output that would + // normally come from strace + sudoCmd := testutil.MockCommand(c, "sudo", fmt.Sprintf(` +echo "stdout output 1" +>&2 echo 'execve("/path/to/snap-confine")' +>&2 echo "snap-confine/snap-exec strace stuff" +>&2 echo "getuid() = 1000" +>&2 echo 'execve("%s/snapName/x2/bin/foo")' +>&2 echo "interessting strace output" +>&2 echo "and more" +echo "stdout output 2" +`, dirs.SnapMountDir)) + defer sudoCmd.Restore() + + // pretend we have strace + straceCmd := testutil.MockCommand(c, "strace", "") + defer straceCmd.Restore() + + user, err := user.Current() + c.Assert(err, check.IsNil) + + // and run it under strace + rest, err := snaprun.Parser().ParseArgs([]string{"run", "--strace", "snapname.app", "--arg1", "arg2"}) + c.Assert(err, check.IsNil) + c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) + c.Check(sudoCmd.Calls(), check.DeepEquals, [][]string{ + { + "sudo", "-E", + filepath.Join(straceCmd.BinDir(), "strace"), + "-u", user.Username, + "-f", + "-e", "!select,pselect6,_newselect,clock_gettime,sigaltstack,gettid", + filepath.Join(dirs.DistroLibExecDir, "snap-confine"), + "snap.snapname.app", + filepath.Join(dirs.CoreLibExecDir, "snap-exec"), + "snapname.app", "--arg1", "arg2", + }, + }) + c.Check(s.Stdout(), check.Equals, "stdout output 1\nstdout output 2\n") + c.Check(s.Stderr(), check.Equals, fmt.Sprintf("execve(%q)\ninteressting strace output\nand more\n", filepath.Join(dirs.SnapMountDir, "snapName/x2/bin/foo"))) +} diff -Nru snapd-2.29.4.2+17.10/cmd/snap/cmd_snap_op.go snapd-2.31.1+17.10/cmd/snap/cmd_snap_op.go --- snapd-2.29.4.2+17.10/cmd/snap/cmd_snap_op.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap/cmd_snap_op.go 2018-01-31 08:47:06.000000000 +0000 @@ -357,20 +357,32 @@ switch op { case "install": if snap.Developer != "" { + // TRANSLATORS: the args are a snap name optionally followed by a channel, then a version, then the developer name (e.g. "some-snap (beta) 1.3 from 'alice' installed") fmt.Fprintf(Stdout, i18n.G("%s%s %s from '%s' installed\n"), snap.Name, channelStr, snap.Version, snap.Developer) } else { + // TRANSLATORS: the args are a snap name optionally followed by a channel, then a version (e.g. "some-snap (beta) 1.3 installed") fmt.Fprintf(Stdout, i18n.G("%s%s %s installed\n"), snap.Name, channelStr, snap.Version) } case "refresh": if snap.Developer != "" { + // TRANSLATORS: the args are a snap name optionally followed by a channel, then a version, then the developer name (e.g. "some-snap (beta) 1.3 from 'alice' refreshed") fmt.Fprintf(Stdout, i18n.G("%s%s %s from '%s' refreshed\n"), snap.Name, channelStr, snap.Version, snap.Developer) } else { + // TRANSLATORS: the args are a snap name optionally followed by a channel, then a version (e.g. "some-snap (beta) 1.3 refreshed") fmt.Fprintf(Stdout, i18n.G("%s%s %s refreshed\n"), snap.Name, channelStr, snap.Version) } + case "revert": + // TRANSLATORS: first %s is a snap name, second %s is a revision + fmt.Fprintf(Stdout, i18n.G("%s reverted to %s\n"), snap.Name, snap.Version) default: - fmt.Fprintf(Stdout, "internal error, unknown op %q", op) + fmt.Fprintf(Stdout, "internal error: unknown op %q", op) + } + if snap.TrackingChannel != snap.Channel { + // TRANSLATORS: first %s is a snap name, following %s is a channel name + fmt.Fprintf(Stdout, i18n.G("Snap %s is no longer tracking %s.\n"), snap.Name, snap.TrackingChannel) } } + return nil } @@ -497,7 +509,16 @@ cli := Client() changeID, err := cli.InstallMany(names, opts) if err != nil { - return err + var snapName string + if err, ok := err.(*client.Error); ok { + snapName, _ = err.Value.(string) + } + msg, err := errorToCmdMessage(snapName, err, opts) + if err != nil { + return err + } + fmt.Fprintln(Stderr, msg) + return nil } setupAbortHandler(changeID) @@ -576,6 +597,7 @@ channelMixin modeMixin + Amend bool `long:"amend"` Revision string `long:"revision"` List bool `long:"list"` Time bool `long:"time"` @@ -644,7 +666,13 @@ return err } - fmt.Fprintf(Stdout, "schedule: %s\n", sysinfo.Refresh.Schedule) + if sysinfo.Refresh.Timer != "" { + fmt.Fprintf(Stdout, "timer: %s\n", sysinfo.Refresh.Timer) + } else if sysinfo.Refresh.Schedule != "" { + fmt.Fprintf(Stdout, "schedule: %s\n", sysinfo.Refresh.Schedule) + } else { + return errors.New("internal error: both refresh.timer and refresh.schedule are empty") + } if sysinfo.Refresh.Last != "" { fmt.Fprintf(Stdout, "last: %s\n", sysinfo.Refresh.Last) } else { @@ -696,7 +724,6 @@ if x.asksForMode() || x.asksForChannel() { return errors.New(i18n.G("--time does not take mode nor channel flags")) } - return x.showRefreshTimes() } @@ -719,6 +746,7 @@ } if len(x.Positional.Snaps) == 1 { opts := &client.SnapOptions{ + Amend: x.Amend, Channel: x.Channel, IgnoreValidation: x.IgnoreValidation, Revision: x.Revision, @@ -934,18 +962,7 @@ return err } - // show output as speced - snaps, err := cli.List([]string{name}, nil) - if err != nil { - return err - } - if len(snaps) != 1 { - // TRANSLATORS: %q gets the snap name, %v the list of things found when trying to list it - return fmt.Errorf(i18n.G("cannot get data for %q: %v"), name, snaps) - } - snap := snaps[0] - fmt.Fprintf(Stdout, i18n.G("%s reverted to %s\n"), name, snap.Version) - return nil + return showDone([]string{name}, "revert") } var shortSwitchHelp = i18n.G("Switches snap to a different channel") @@ -1001,6 +1018,7 @@ }), nil) addCommand("refresh", shortRefreshHelp, longRefreshHelp, func() flags.Commander { return &cmdRefresh{} }, waitDescs.also(channelDescs).also(modeDescs).also(map[string]string{ + "amend": i18n.G("Allow refresh attempt on snap unknown to the store"), "revision": i18n.G("Refresh to the given revision"), "list": i18n.G("Show available snaps for refresh but do not perform a refresh"), "time": i18n.G("Show auto refresh information but do not perform a refresh"), diff -Nru snapd-2.29.4.2+17.10/cmd/snap/cmd_snap_op_test.go snapd-2.31.1+17.10/cmd/snap/cmd_snap_op_test.go --- snapd-2.29.4.2+17.10/cmd/snap/cmd_snap_op_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap/cmd_snap_op_test.go 2018-01-31 08:47:06.000000000 +0000 @@ -423,6 +423,29 @@ c.Check(s.srv.n, check.Equals, s.srv.total) } +func (s *SnapOpSuite) TestRevertRunthrough(c *check.C) { + s.srv.total = 4 + s.srv.channel = "potato" + s.srv.checker = func(r *http.Request) { + c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo") + c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ + "action": "revert", + }) + } + + s.RedirectClientToTestServer(s.srv.handle) + rest, err := snap.Parser().ParseArgs([]string{"revert", "foo"}) + c.Assert(err, check.IsNil) + c.Assert(rest, check.DeepEquals, []string{}) + // tracking channel is "" in the test server + c.Check(s.Stdout(), check.Equals, `foo reverted to 1.0 +Snap foo is no longer tracking . +`) + c.Check(s.Stderr(), check.Equals, "") + // ensure that the fake server api was actually hit + c.Check(s.srv.n, check.Equals, s.srv.total) +} + func (s *SnapOpSuite) runRevertTest(c *check.C, opts *client.SnapOptions) { modes := []struct { enabled bool @@ -518,7 +541,7 @@ c.Check(n, check.Equals, 1) } -func (s *SnapSuite) TestRefreshTime(c *check.C) { +func (s *SnapSuite) TestRefreshLegacyTime(c *check.C) { n := 0 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { switch n { @@ -544,6 +567,42 @@ c.Check(n, check.Equals, 1) } +func (s *SnapSuite) TestRefreshTimer(c *check.C) { + 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/system-info") + fmt.Fprintln(w, `{"type": "sync", "status-code": 200, "result": {"refresh": {"timer": "0:00-24:00/4", "last": "2017-04-25T17:35:00+0200", "next": "2017-04-26T00:58:00+0200"}}}`) + default: + c.Fatalf("expected to get 1 requests, now on %d", n+1) + } + + n++ + }) + rest, err := snap.Parser().ParseArgs([]string{"refresh", "--time"}) + c.Assert(err, check.IsNil) + c.Assert(rest, check.DeepEquals, []string{}) + c.Check(s.Stdout(), check.Equals, `timer: 0:00-24:00/4 +last: 2017-04-25T17:35:00+0200 +next: 2017-04-26T00:58:00+0200 +`) + c.Check(s.Stderr(), check.Equals, "") + // ensure that the fake server api was actually hit + c.Check(n, check.Equals, 1) +} + +func (s *SnapSuite) TestRefreshNoTimerNoSchedule(c *check.C) { + s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { + c.Check(r.Method, check.Equals, "GET") + c.Check(r.URL.Path, check.Equals, "/v2/system-info") + fmt.Fprintln(w, `{"type": "sync", "status-code": 200, "result": {"refresh": {"last": "2017-04-25T17:35:00+0200", "next": "2017-04-26T00:58:00+0200"}}}`) + }) + _, err := snap.Parser().ParseArgs([]string{"refresh", "--time"}) + c.Assert(err, check.ErrorMatches, `internal error: both refresh.timer and refresh.schedule are empty`) +} + func (s *SnapSuite) TestRefreshListErr(c *check.C) { s.RedirectClientToTestServer(nil) _, err := snap.Parser().ParseArgs([]string{"refresh", "--list", "--beta"}) @@ -673,6 +732,20 @@ c.Assert(err, check.ErrorMatches, `a single snap name is needed to specify mode or channel flags`) } +func (s *SnapOpSuite) TestRefreshOneAmend(c *check.C) { + s.RedirectClientToTestServer(s.srv.handle) + s.srv.checker = func(r *http.Request) { + c.Check(r.Method, check.Equals, "POST") + c.Check(r.URL.Path, check.Equals, "/v2/snaps/one") + c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ + "action": "refresh", + "amend": true, + }) + } + _, err := snap.Parser().ParseArgs([]string{"refresh", "--amend", "one"}) + c.Assert(err, check.IsNil) +} + func (s *SnapOpSuite) runTryTest(c *check.C, opts *client.SnapOptions) { // pass relative path to cmd tryDir := "some-dir" @@ -976,8 +1049,8 @@ {"try", "--no-wait", "."}, } + s.RedirectClientToTestServer(s.srv.handle) for _, cmd := range cmds { - s.RedirectClientToTestServer(s.srv.handle) rest, err := snap.Parser().ParseArgs(cmd) c.Assert(err, check.IsNil, check.Commentf("%v", cmd)) c.Assert(rest, check.DeepEquals, []string{}) @@ -1019,3 +1092,25 @@ _, err := snap.Parser().ParseArgs([]string{"switch", "foo"}) c.Assert(err, check.ErrorMatches, `missing --channel= parameter`) } + +func (s *SnapOpSuite) TestSnapOpNetworkTimeoutError(c *check.C) { + s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { + c.Check(r.Method, check.Equals, "POST") + w.WriteHeader(202) + w.Write([]byte(` +{ + "type": "error", + "result": { + "message":"Get https://api.snapcraft.io/api/v1/snaps/details/hello?channel=stable&fields=anon_download_url%2Carchitecture%2Cchannel%2Cdownload_sha3_384%2Csummary%2Cdescription%2Cdeltas%2Cbinary_filesize%2Cdownload_url%2Cepoch%2Cicon_url%2Clast_updated%2Cpackage_name%2Cprices%2Cpublisher%2Cratings_average%2Crevision%2Cscreenshot_urls%2Csnap_id%2Clicense%2Cbase%2Csupport_url%2Ccontact%2Ctitle%2Ccontent%2Cversion%2Corigin%2Cdeveloper_id%2Cprivate%2Cconfinement%2Cchannel_maps_list: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)", + "kind":"network-timeout" + }, + "status-code": 400 +} +`)) + + }) + + cmd := []string{"install", "hello"} + _, err := snap.Parser().ParseArgs(cmd) + c.Assert(err, check.ErrorMatches, `unable to contact snap store`) +} diff -Nru snapd-2.29.4.2+17.10/cmd/snap/cmd_userd.go snapd-2.31.1+17.10/cmd/snap/cmd_userd.go --- snapd-2.29.4.2+17.10/cmd/snap/cmd_userd.go 2017-10-23 06:17:24.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap/cmd_userd.go 2018-02-16 15:58:27.000000000 +0000 @@ -40,8 +40,8 @@ func init() { cmd := addCommand("userd", - shortAbortHelp, - longAbortHelp, + shortUserdHelp, + longUserdHelp, func() flags.Commander { return &cmdUserd{} }, diff -Nru snapd-2.29.4.2+17.10/cmd/snap/cmd_whoami.go snapd-2.31.1+17.10/cmd/snap/cmd_whoami.go --- snapd-2.29.4.2+17.10/cmd/snap/cmd_whoami.go 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap/cmd_whoami.go 2018-01-31 08:47:06.000000000 +0000 @@ -27,7 +27,7 @@ "github.com/jessevdk/go-flags" ) -var shortWhoAmIHelp = i18n.G("Prints the email the user is logged in with.") +var shortWhoAmIHelp = i18n.G("Prints the email the user is logged in with") var longWhoAmIHelp = i18n.G(` The whoami command prints the email the user is logged in with. `) diff -Nru snapd-2.29.4.2+17.10/cmd/snap/error.go snapd-2.31.1+17.10/cmd/snap/error.go --- snapd-2.29.4.2+17.10/cmd/snap/error.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap/error.go 2018-01-31 08:47:06.000000000 +0000 @@ -32,6 +32,7 @@ "github.com/snapcore/snapd/client" "github.com/snapcore/snapd/i18n" + "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/osutil" ) @@ -87,9 +88,11 @@ return "", e } + // ensure the "real" error is available if we ask for it + logger.Debugf("error: %s", err) + // FIXME: using err.Message in user-facing messaging is not // l10n-friendly, and probably means we're missing ad-hoc messaging. - isError := true usesSnapName := true var msg string @@ -150,7 +153,7 @@ msg = fmt.Sprintf(i18n.G(`%s (try with sudo)`), err.Message) } case client.ErrorKindSnapLocal: - msg = i18n.G("snap %q is local") + msg = i18n.G("local snap %q is unknown to the store, use --amend to proceed anyway") case client.ErrorKindNoUpdateAvailable: isError = false msg = i18n.G("snap %q has no updates available") @@ -158,6 +161,10 @@ isError = false usesSnapName = false msg = err.Message + case client.ErrorKindNetworkTimeout: + isError = true + usesSnapName = false + msg = i18n.G("unable to contact snap store") default: usesSnapName = false msg = err.Message diff -Nru snapd-2.29.4.2+17.10/cmd/snap/export_test.go snapd-2.31.1+17.10/cmd/snap/export_test.go --- snapd-2.29.4.2+17.10/cmd/snap/export_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap/export_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -41,6 +41,7 @@ MaybePrintServices = maybePrintServices MaybePrintCommands = maybePrintCommands SortByPath = sortByPath + AdviseCommand = adviseCommand ) func MockPollTime(d time.Duration) (restore func()) { @@ -136,3 +137,5 @@ isTerminal = oldIsTerminal } } + +var Antialias = antialias diff -Nru snapd-2.29.4.2+17.10/cmd/snap/main.go snapd-2.31.1+17.10/cmd/snap/main.go --- snapd-2.29.4.2+17.10/cmd/snap/main.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap/main.go 2018-02-02 15:30:39.000000000 +0000 @@ -46,13 +46,13 @@ // set User-Agent for when 'snap' talks to the store directly (snap download etc...) httputil.SetUserAgentFromVersion(cmd.Version, "snap") - // plug/slot sanitization not used nor possible from snap command, make it no-op - snap.SanitizePlugsSlots = func(snapInfo *snap.Info) {} - if osutil.GetenvBool("SNAPD_DEBUG") || osutil.GetenvBool("SNAPPY_TESTING") { // in tests or when debugging, enforce the "tidy" lint checks noticef = logger.Panicf } + + // plug/slot sanitization not used nor possible from snap command, make it no-op + snap.SanitizePlugsSlots = func(snapInfo *snap.Info) {} } var ( @@ -279,7 +279,29 @@ func main() { cmd.ExecInCoreSnap() - // magic \o/ + // check for magic symlink to /usr/bin/snap: + // 1. symlink from command-not-found to /usr/bin/snap: run c-n-f + if os.Args[0] == filepath.Join(dirs.GlobalRootDir, "/usr/lib/command-not-found") { + cmd := &cmdAdviseSnap{ + Command: true, + Format: "pretty", + } + // the bash.bashrc handler runs: + // /usr/lib/command-not-found -- "$1" + // so skip over any "--" + for _, arg := range os.Args[1:] { + if arg != "--" { + cmd.Positionals.CommandOrPkg = arg + break + } + } + if err := cmd.Execute(nil); err != nil { + fmt.Fprintf(Stderr, "%s\n", err) + } + return + } + + // 2. symlink from /snap/bin/$foo to /usr/bin/snap: run snapApp snapApp := filepath.Base(os.Args[0]) if osutil.IsSymlink(filepath.Join(dirs.SnapBinariesDir, snapApp)) { var err error diff -Nru snapd-2.29.4.2+17.10/cmd/snap/notes.go snapd-2.31.1+17.10/cmd/snap/notes.go --- snapd-2.29.4.2+17.10/cmd/snap/notes.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap/notes.go 2017-12-01 15:51:55.000000000 +0000 @@ -51,15 +51,16 @@ // Notes encapsulate everything that might be interesting about a // snap, in order to present a brief summary of it. type Notes struct { - Price string - SnapType snap.Type - Private bool - DevMode bool - JailMode bool - Classic bool - TryMode bool - Disabled bool - Broken bool + Price string + SnapType snap.Type + Private bool + DevMode bool + JailMode bool + Classic bool + TryMode bool + Disabled bool + Broken bool + IgnoreValidation bool } func NotesFromChannelSnapInfo(ref *snap.ChannelSnapInfo) *Notes { @@ -85,14 +86,15 @@ func NotesFromLocal(snp *client.Snap) *Notes { return &Notes{ - SnapType: snap.Type(snp.Type), - Private: snp.Private, - DevMode: snp.DevMode, - Classic: !snp.JailMode && (snp.Confinement == client.ClassicConfinement), - JailMode: snp.JailMode, - TryMode: snp.TryMode, - Disabled: snp.Status != client.StatusActive, - Broken: snp.Broken != "", + SnapType: snap.Type(snp.Type), + Private: snp.Private, + DevMode: snp.DevMode, + Classic: !snp.JailMode && (snp.Confinement == client.ClassicConfinement), + JailMode: snp.JailMode, + TryMode: snp.TryMode, + Disabled: snp.Status != client.StatusActive, + Broken: snp.Broken != "", + IgnoreValidation: snp.IgnoreValidation, } } @@ -155,6 +157,10 @@ ns = append(ns, i18n.G("broken")) } + if n.IgnoreValidation { + ns = append(ns, i18n.G("ignore-validation")) + } + if len(ns) == 0 { return "-" } diff -Nru snapd-2.29.4.2+17.10/cmd/snap/notes_test.go snapd-2.31.1+17.10/cmd/snap/notes_test.go --- snapd-2.29.4.2+17.10/cmd/snap/notes_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap/notes_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -82,6 +82,12 @@ }).String(), check.Equals, "broken") } +func (notesSuite) TestNotesIgnoreValidation(c *check.C) { + c.Check((&snap.Notes{ + IgnoreValidation: true, + }).String(), check.Equals, "ignore-validation") +} + func (notesSuite) TestNotesNothing(c *check.C) { c.Check((&snap.Notes{}).String(), check.Equals, "-") } @@ -97,4 +103,5 @@ // Check that DevMode note is derived from DevMode flag, not DevModeConfinement type. c.Check(snap.NotesFromLocal(&client.Snap{DevMode: true}).DevMode, check.Equals, true) c.Check(snap.NotesFromLocal(&client.Snap{Confinement: client.DevModeConfinement}).DevMode, check.Equals, false) + c.Check(snap.NotesFromLocal(&client.Snap{IgnoreValidation: true}).IgnoreValidation, check.Equals, true) } diff -Nru snapd-2.29.4.2+17.10/cmd/snap-confine/80-snappy-assign.rules snapd-2.31.1+17.10/cmd/snap-confine/80-snappy-assign.rules --- snapd-2.29.4.2+17.10/cmd/snap-confine/80-snappy-assign.rules 2016-12-08 15:14:07.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-confine/80-snappy-assign.rules 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ -# add/remove snap package access to assigned devices -TAG=="snap_*", RUN+="/lib/udev/snappy-app-dev $env{ACTION} $env{TAG} $devpath $major:$minor" diff -Nru snapd-2.29.4.2+17.10/cmd/snap-confine/apparmor-support.c snapd-2.31.1+17.10/cmd/snap-confine/apparmor-support.c --- snapd-2.29.4.2+17.10/cmd/snap-confine/apparmor-support.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-confine/apparmor-support.c 2017-12-01 15:51:55.000000000 +0000 @@ -56,6 +56,7 @@ case ENOENT: debug ("apparmor is available but the interface but the interface is not available"); + break; case EPERM: // NOTE: fall-through case EACCES: diff -Nru snapd-2.29.4.2+17.10/cmd/snap-confine/cookie-support-test.c snapd-2.31.1+17.10/cmd/snap-confine/cookie-support-test.c --- snapd-2.29.4.2+17.10/cmd/snap-confine/cookie-support-test.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-confine/cookie-support-test.c 2017-12-01 15:51:55.000000000 +0000 @@ -31,7 +31,7 @@ sc_cookie_dir = dir; } -static void set_fake_cookie_dir() +static void set_fake_cookie_dir(void) { char *ctx_dir = NULL; ctx_dir = g_dir_make_tmp(NULL, NULL); @@ -62,7 +62,7 @@ fclose(f); } -static void test_cookie_get_from_snapd__successful() +static void test_cookie_get_from_snapd__successful(void) { struct sc_error *err = NULL; char *cookie; @@ -79,7 +79,7 @@ g_assert_cmpstr(cookie, ==, dummy); } -static void test_cookie_get_from_snapd__nofile() +static void test_cookie_get_from_snapd__nofile(void) { struct sc_error *err = NULL; char *cookie; @@ -93,7 +93,7 @@ g_assert_null(cookie); } -static void __attribute__ ((constructor)) init() +static void __attribute__ ((constructor)) init(void) { g_test_add_func("/snap-cookie/cookie_get_from_snapd/successful", test_cookie_get_from_snapd__successful); diff -Nru snapd-2.29.4.2+17.10/cmd/snap-confine/mount-support.c snapd-2.31.1+17.10/cmd/snap-confine/mount-support.c --- snapd-2.29.4.2+17.10/cmd/snap-confine/mount-support.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-confine/mount-support.c 2018-01-31 08:47:06.000000000 +0000 @@ -109,7 +109,7 @@ } // TODO: fold this into bootstrap -static void setup_private_pts() +static void setup_private_pts(void) { // See https://www.kernel.org/doc/Documentation/filesystems/devpts.txt // @@ -165,7 +165,10 @@ "snap-update-ns", "--from-snap-confine", snap_name_copy, NULL }; - char *envp[] = { NULL }; + char *envp[3] = { NULL }; + if (sc_is_debug_enabled()) { + envp[0] = "SNAPD_DEBUG=1"; + } debug("fexecv(%d (snap-update-ns), %s %s %s,)", snap_update_ns_fd, argv[0], argv[1], argv[2]); fexecve(snap_update_ns_fd, argv, envp); @@ -486,7 +489,7 @@ static char * __attribute__ ((used)) get_nextpath(char *path, size_t * offsetp, size_t fulllen) { - int offset = *offsetp; + size_t offset = *offsetp; if (offset >= fulllen) return NULL; @@ -532,7 +535,7 @@ return false; } -static int sc_open_snap_update_ns() +static int sc_open_snap_update_ns(void) { // +1 is for the case where the link is exactly PATH_MAX long but we also // want to store the terminating '\0'. The readlink system call doesn't add @@ -700,10 +703,15 @@ return false; } -void sc_ensure_shared_snap_mount() +void sc_ensure_shared_snap_mount(void) { if (!is_mounted_with_shared_option("/") && !is_mounted_with_shared_option(SNAP_MOUNT_DIR)) { + // TODO: We could be more aggressive and refuse to function but since + // we have no data on actual environments that happen to limp along in + // this configuration let's not do that yet. This code should be + // removed once we have a measurement and feedback mechanism that lets + // us decide based on measurable data. sc_do_mount(SNAP_MOUNT_DIR, SNAP_MOUNT_DIR, "none", MS_BIND | MS_REC, 0); sc_do_mount("none", SNAP_MOUNT_DIR, NULL, MS_SHARED | MS_REC, diff -Nru snapd-2.29.4.2+17.10/cmd/snap-confine/mount-support.h snapd-2.31.1+17.10/cmd/snap-confine/mount-support.h --- snapd-2.29.4.2+17.10/cmd/snap-confine/mount-support.h 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-confine/mount-support.h 2017-12-01 15:51:55.000000000 +0000 @@ -40,5 +40,5 @@ * snap-confine will create a shared bind mount for "/snap" to * ensure that "/snap" is mounted shared. See LP:#1668659 */ -void sc_ensure_shared_snap_mount(); +void sc_ensure_shared_snap_mount(void); #endif diff -Nru snapd-2.29.4.2+17.10/cmd/snap-confine/mount-support-nvidia.c snapd-2.31.1+17.10/cmd/snap-confine/mount-support-nvidia.c --- snapd-2.29.4.2+17.10/cmd/snap-confine/mount-support-nvidia.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-confine/mount-support-nvidia.c 2018-02-05 09:32:02.000000000 +0000 @@ -26,6 +26,7 @@ #include #include #include +#include #include #include "../libsnap-confine-private/classic.h" @@ -35,6 +36,23 @@ #define SC_NVIDIA_DRIVER_VERSION_FILE "/sys/module/nvidia/version" +// note: if the parent dir changes to something other than +// the current /var/lib/snapd/lib then sc_mkdir_and_mount_and_bind +// and sc_mkdir_and_mount_and_bind need updating. +#define SC_LIBGL_DIR "/var/lib/snapd/lib/gl" +#define SC_LIBGL32_DIR "/var/lib/snapd/lib/gl32" +#define SC_VULKAN_DIR "/var/lib/snapd/lib/vulkan" + +#define SC_VULKAN_SOURCE_DIR "/usr/share/vulkan" + +// Location for NVIDIA vulkan files (including _wayland) +static const char *vulkan_globs[] = { + "icd.d/*nvidia*.json", +}; + +static const size_t vulkan_globs_len = + sizeof vulkan_globs / sizeof *vulkan_globs; + #ifdef NVIDIA_BIARCH // List of globs that describe nvidia userspace libraries. @@ -50,40 +68,44 @@ // FIXME: this doesn't yet work with libGLX and libglvnd redirector // FIXME: this still doesn't work with the 361 driver static const char *nvidia_globs[] = { - "/usr/lib/libEGL.so*", - "/usr/lib/libEGL_nvidia.so*", - "/usr/lib/libGL.so*", - "/usr/lib/libOpenGL.so*", - "/usr/lib/libGLESv1_CM.so*", - "/usr/lib/libGLESv1_CM_nvidia.so*", - "/usr/lib/libGLESv2.so*", - "/usr/lib/libGLESv2_nvidia.so*", - "/usr/lib/libGLX_indirect.so*", - "/usr/lib/libGLX_nvidia.so*", - "/usr/lib/libGLX.so*", - "/usr/lib/libGLdispatch.so*", - "/usr/lib/libGLU.so*", - "/usr/lib/libXvMCNVIDIA.so*", - "/usr/lib/libXvMCNVIDIA_dynamic.so*", - "/usr/lib/libcuda.so*", - "/usr/lib/libnvcuvid.so*", - "/usr/lib/libnvidia-cfg.so*", - "/usr/lib/libnvidia-compiler.so*", - "/usr/lib/libnvidia-eglcore.so*", - "/usr/lib/libnvidia-encode.so*", - "/usr/lib/libnvidia-fatbinaryloader.so*", - "/usr/lib/libnvidia-fbc.so*", - "/usr/lib/libnvidia-glcore.so*", - "/usr/lib/libnvidia-glsi.so*", - "/usr/lib/libnvidia-ifr.so*", - "/usr/lib/libnvidia-ml.so*", - "/usr/lib/libnvidia-ptxjitcompiler.so*", - "/usr/lib/libnvidia-tls.so*", + "libEGL.so*", + "libEGL_nvidia.so*", + "libGL.so*", + "libOpenGL.so*", + "libGLESv1_CM.so*", + "libGLESv1_CM_nvidia.so*", + "libGLESv2.so*", + "libGLESv2_nvidia.so*", + "libGLX_indirect.so*", + "libGLX_nvidia.so*", + "libGLX.so*", + "libGLdispatch.so*", + "libGLU.so*", + "libXvMCNVIDIA.so*", + "libXvMCNVIDIA_dynamic.so*", + "libcuda.so*", + "libnvcuvid.so*", + "libnvidia-cfg.so*", + "libnvidia-compiler.so*", + "libnvidia-eglcore.so*", + "libnvidia-egl-wayland*", + "libnvidia-encode.so*", + "libnvidia-fatbinaryloader.so*", + "libnvidia-fbc.so*", + "libnvidia-glcore.so*", + "libnvidia-glsi.so*", + "libnvidia-ifr.so*", + "libnvidia-ml.so*", + "libnvidia-ptxjitcompiler.so*", + "libnvidia-tls.so*", + "vdpau/libvdpau_nvidia.so*", }; static const size_t nvidia_globs_len = sizeof nvidia_globs / sizeof *nvidia_globs; +#endif // ifdef NVIDIA_BIARCH + // Populate libgl_dir with a symlink farm to files matching glob_list. // // The symbolic links are made in one of two ways. If the library found is a @@ -91,7 +113,11 @@ // If the library is a symbolic link then relative links are kept as-is but // absolute links are translated to have "/path/to/hostfs" up front so that // they work after the pivot_root elsewhere. +// +// The glob list passed to us is produced with paths relative to source dir, +// to simplify the various tie-in points with this function. static void sc_populate_libgl_with_hostfs_symlinks(const char *libgl_dir, + const char *source_dir, const char *glob_list[], size_t glob_list_len) { @@ -100,13 +126,17 @@ // Find all the entries matching the list of globs for (size_t i = 0; i < glob_list_len; ++i) { const char *glob_pattern = glob_list[i]; - int err = - glob(glob_pattern, i ? GLOB_APPEND : 0, NULL, &glob_res); + char glob_pattern_full[512] = { 0 }; + sc_must_snprintf(glob_pattern_full, sizeof glob_pattern_full, + "%s/%s", source_dir, glob_pattern); + + int err = glob(glob_pattern_full, i ? GLOB_APPEND : 0, NULL, + &glob_res); // Not all of the files have to be there (they differ depending on the // driver version used). Ignore all errors that are not GLOB_NOMATCH. if (err != 0 && err != GLOB_NOMATCH) { die("cannot search using glob pattern %s: %d", - glob_pattern, err); + glob_pattern_full, err); } } // Symlink each file found @@ -160,6 +190,15 @@ "%s/%s", libgl_dir, filename); debug("creating symbolic link %s -> %s", symlink_name, symlink_target); + + // Make sure we don't have some link already (merged GLVND systems) + if (lstat(symlink_name, &stat_buf) == 0) { + if (unlink(symlink_name) != 0) { + die("cannot remove symbolic link target %s", + symlink_name); + } + } + if (symlink(symlink_target, symlink_name) != 0) { die("cannot create symbolic link %s -> %s", symlink_name, symlink_target); @@ -167,27 +206,94 @@ } } -static void sc_mount_nvidia_driver_biarch(const char *rootfs_dir) +static void sc_mkdir_and_mount_and_glob_files(const char *rootfs_dir, + const char *source_dir[], + size_t source_dir_len, + const char *tgt_dir, + const char *glob_list[], + size_t glob_list_len) { - // Bind mount a tmpfs on $rootfs_dir/var/lib/snapd/lib/gl + // Bind mount a tmpfs on $rootfs_dir/$tgt_dir (i.e. /var/lib/snapd/lib/gl) char buf[512] = { 0 }; - sc_must_snprintf(buf, sizeof(buf), "%s%s", rootfs_dir, - "/var/lib/snapd/lib/gl"); + sc_must_snprintf(buf, sizeof(buf), "%s%s", rootfs_dir, tgt_dir); const char *libgl_dir = buf; + + int res = mkdir(libgl_dir, 0755); + if (res != 0 && errno != EEXIST) { + die("cannot create tmpfs target %s", libgl_dir); + } + debug("mounting tmpfs at %s", libgl_dir); if (mount("none", libgl_dir, "tmpfs", MS_NODEV | MS_NOEXEC, NULL) != 0) { die("cannot mount tmpfs at %s", libgl_dir); }; - // Populate libgl_dir with symlinks to libraries from hostfs - sc_populate_libgl_with_hostfs_symlinks(libgl_dir, nvidia_globs, - nvidia_globs_len); - // Remount .../lib/gl read only + + for (size_t i = 0; i < source_dir_len; i++) { + // Populate libgl_dir with symlinks to libraries from hostfs + sc_populate_libgl_with_hostfs_symlinks(libgl_dir, source_dir[i], + glob_list, + glob_list_len); + } + // Remount $tgt_dir (i.e. .../lib/gl) read only debug("remounting tmpfs as read-only %s", libgl_dir); if (mount(NULL, buf, NULL, MS_REMOUNT | MS_RDONLY, NULL) != 0) { die("cannot remount %s as read-only", buf); } } +#ifdef NVIDIA_BIARCH + +// Expose host NVIDIA drivers to the snap on biarch systems. +// +// Order is absolutely imperative here. We'll attempt to find the +// primary files for the architecture in the main directory, and end +// up copying any files across. However it is possible we're using a +// GLVND enabled host, in which case we copied libGL* to the farm. +// The next step in the list is to look within the private nvidia +// directory, exposed using ld.so.conf tricks within the host OS. +// In some distros (i.e. Solus) only the private libGL/libEGL files +// may be found here, and they'll clobber the existing GLVND files from +// the previous run. +// In other distros (like Fedora) all NVIDIA libraries are contained +// within the private directory, so we clobber the GLVND files and we +// also grab all the private NVIDIA libraries. +// +// In non GLVND cases we just copy across the exposed libGLs and NVIDIA +// libraries from wherever we find, and clobbering is also harmless. +static void sc_mount_nvidia_driver_biarch(const char *rootfs_dir) +{ + + const char *native_sources[] = { + NATIVE_LIBDIR, + NATIVE_LIBDIR "/nvidia*", + }; + const size_t native_sources_len = + sizeof native_sources / sizeof *native_sources; + +#if UINTPTR_MAX == 0xffffffffffffffff + // Alternative 32-bit support + const char *lib32_sources[] = { + LIB32_DIR, + LIB32_DIR "/nvidia*", + }; + const size_t lib32_sources_len = + sizeof lib32_sources / sizeof *lib32_sources; +#endif + + // Primary arch + sc_mkdir_and_mount_and_glob_files(rootfs_dir, + native_sources, native_sources_len, + SC_LIBGL_DIR, nvidia_globs, + nvidia_globs_len); + +#if UINTPTR_MAX == 0xffffffffffffffff + // Alternative 32-bit support + sc_mkdir_and_mount_and_glob_files(rootfs_dir, lib32_sources, + lib32_sources_len, SC_LIBGL32_DIR, + nvidia_globs, nvidia_globs_len); +#endif +} + #endif // ifdef NVIDIA_BIARCH #ifdef NVIDIA_MULTIARCH @@ -197,8 +303,6 @@ int minor_version; }; -#define SC_LIBGL_DIR "/var/lib/snapd/lib/gl" - static void sc_probe_nvidia_driver(struct sc_nvidia_driver *driver) { FILE *file SC_CLEANUP(sc_cleanup_file) = NULL; @@ -224,7 +328,9 @@ driver->minor_version); } -static void sc_mount_nvidia_driver_multiarch(const char *rootfs_dir) +static void sc_mkdir_and_mount_and_bind(const char *rootfs_dir, + const char *src_dir, + const char *tgt_dir) { struct sc_nvidia_driver driver; @@ -239,9 +345,9 @@ // and for the gl directory. char src[PATH_MAX] = { 0 }; char dst[PATH_MAX] = { 0 }; - sc_must_snprintf(src, sizeof src, "/usr/lib/nvidia-%d", + sc_must_snprintf(src, sizeof src, "%s-%d", src_dir, driver.major_version); - sc_must_snprintf(dst, sizeof dst, "%s%s", rootfs_dir, SC_LIBGL_DIR); + sc_must_snprintf(dst, sizeof dst, "%s%s", rootfs_dir, tgt_dir); // If there is no userspace driver available then don't try to mount it. // This can happen for any number of reasons but one interesting one is @@ -251,14 +357,41 @@ if (access(src, F_OK) != 0) { return; } - // Bind mount the binary nvidia driver into /var/lib/snapd/lib/gl. + int res = mkdir(dst, 0755); + if (res != 0 && errno != EEXIST) { + die("cannot create directory %s", dst); + } + // Bind mount the binary nvidia driver into $tgt_dir (i.e. /var/lib/snapd/lib/gl). debug("bind mounting nvidia driver %s -> %s", src, dst); if (mount(src, dst, NULL, MS_BIND, NULL) != 0) { die("cannot bind mount nvidia driver %s -> %s", src, dst); } } + +static void sc_mount_nvidia_driver_multiarch(const char *rootfs_dir) +{ + // Attempt mount of both the native and 32-bit variants of the driver if they exist + sc_mkdir_and_mount_and_bind(rootfs_dir, "/usr/lib/nvidia", + SC_LIBGL_DIR); + sc_mkdir_and_mount_and_bind(rootfs_dir, "/usr/lib32/nvidia", + SC_LIBGL32_DIR); +} + #endif // ifdef NVIDIA_MULTIARCH +static void sc_mount_vulkan(const char *rootfs_dir) +{ + const char *vulkan_sources[] = { + SC_VULKAN_SOURCE_DIR, + }; + const size_t vulkan_sources_len = + sizeof vulkan_sources / sizeof *vulkan_sources; + + sc_mkdir_and_mount_and_glob_files(rootfs_dir, vulkan_sources, + vulkan_sources_len, SC_VULKAN_DIR, + vulkan_globs, vulkan_globs_len); +} + void sc_mount_nvidia_driver(const char *rootfs_dir) { /* If NVIDIA module isn't loaded, don't attempt to mount the drivers */ @@ -271,4 +404,7 @@ #ifdef NVIDIA_BIARCH sc_mount_nvidia_driver_biarch(rootfs_dir); #endif // ifdef NVIDIA_BIARCH + + // Common for both driver mechanisms + sc_mount_vulkan(rootfs_dir); } diff -Nru snapd-2.29.4.2+17.10/cmd/snap-confine/mount-support-test.c snapd-2.31.1+17.10/cmd/snap-confine/mount-support-test.c --- snapd-2.29.4.2+17.10/cmd/snap-confine/mount-support-test.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-confine/mount-support-test.c 2017-12-01 15:51:55.000000000 +0000 @@ -30,7 +30,7 @@ } } -static void test_get_nextpath__typical() +static void test_get_nextpath__typical(void) { char path[] = "/some/path"; size_t offset = 0; @@ -50,7 +50,7 @@ g_assert_cmpstr(result, ==, NULL); } -static void test_get_nextpath__weird() +static void test_get_nextpath__weird(void) { char path[] = "..///path"; size_t offset = 0; @@ -68,7 +68,7 @@ g_assert_cmpstr(result, ==, NULL); } -static void test_is_subdir() +static void test_is_subdir(void) { // Sensible exaples are sensible g_assert_true(is_subdir("/dir/subdir", "/dir/")); @@ -91,7 +91,7 @@ g_assert_false(is_subdir("/", "")); } -static void __attribute__ ((constructor)) init() +static void __attribute__ ((constructor)) init(void) { g_test_add_func("/mount/get_nextpath/typical", test_get_nextpath__typical); diff -Nru snapd-2.29.4.2+17.10/cmd/snap-confine/ns-support.c snapd-2.31.1+17.10/cmd/snap-confine/ns-support.c --- snapd-2.29.4.2+17.10/cmd/snap-confine/ns-support.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-confine/ns-support.c 2018-01-31 08:47:06.000000000 +0000 @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -37,11 +38,13 @@ #include #include +#include "../libsnap-confine-private/cgroup-freezer-support.h" +#include "../libsnap-confine-private/classic.h" #include "../libsnap-confine-private/cleanup-funcs.h" +#include "../libsnap-confine-private/locking.h" #include "../libsnap-confine-private/mountinfo.h" #include "../libsnap-confine-private/string-utils.h" #include "../libsnap-confine-private/utils.h" -#include "../libsnap-confine-private/locking.h" #include "user-support.h" /*! @@ -76,7 +79,7 @@ * We do this because /run/snapd/ns cannot be shared with any other peers as per: * https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt **/ -static bool sc_is_ns_group_dir_private() +static bool sc_is_ns_group_dir_private(void) { struct sc_mountinfo *info SC_CLEANUP(sc_cleanup_mountinfo) = NULL; info = sc_parse_mountinfo(NULL); @@ -98,7 +101,7 @@ return false; } -void sc_reassociate_with_pid1_mount_ns() +void sc_reassociate_with_pid1_mount_ns(void) { int init_mnt_fd SC_CLEANUP(sc_cleanup_close) = -1; int self_mnt_fd SC_CLEANUP(sc_cleanup_close) = -1; @@ -154,7 +157,7 @@ } } -void sc_initialize_ns_groups() +void sc_initialize_ns_groups(void) { debug("creating namespace group directory %s", sc_ns_dir); if (sc_nonfatal_mkpath(sc_ns_dir, 0755) < 0) { @@ -194,7 +197,7 @@ bool should_populate; }; -static struct sc_ns_group *sc_alloc_ns_group() +static struct sc_ns_group *sc_alloc_ns_group(void) { struct sc_ns_group *group = calloc(1, sizeof *group); if (group == NULL) { @@ -239,8 +242,233 @@ free(group); } -void sc_create_or_join_ns_group(struct sc_ns_group *group, - struct sc_apparmor *apparmor) +static dev_t find_base_snap_device(const char *base_snap_name, + const char *base_snap_rev) +{ + // Find the backing device of the base snap. + // TODO: add support for "try mode" base snaps that also need + // consideration of the mie->root component. + dev_t base_snap_dev = 0; + char base_squashfs_path[PATH_MAX]; + sc_must_snprintf(base_squashfs_path, + sizeof base_squashfs_path, "%s/%s/%s", + SNAP_MOUNT_DIR, base_snap_name, base_snap_rev); + struct sc_mountinfo *mi SC_CLEANUP(sc_cleanup_mountinfo) = NULL; + mi = sc_parse_mountinfo(NULL); + if (mi == NULL) { + die("cannot parse mountinfo of the current process"); + } + bool found = false; + for (struct sc_mountinfo_entry * mie = + sc_first_mountinfo_entry(mi); mie != NULL; + mie = sc_next_mountinfo_entry(mie)) { + if (sc_streq(mie->mount_dir, base_squashfs_path)) { + base_snap_dev = MKDEV(mie->dev_major, mie->dev_minor); + debug("found base snap filesystem device %d:%d", + mie->dev_major, mie->dev_minor); + // Don't break when found, we are interested in the last + // entry as this is the "effective" one. + found = true; + } + } + if (!found) { + die("cannot find device backing the base snap %s", + base_snap_name); + } + return base_snap_dev; +} + +static bool should_discard_current_ns(dev_t base_snap_dev) +{ + // Inspect the namespace and check if we should discard it. + // + // The namespace may become "stale" when the rootfs is not the same + // device we found above. This will happen whenever the base snap is + // refreshed since the namespace was first created. + struct sc_mountinfo_entry *mie; + struct sc_mountinfo *mi SC_CLEANUP(sc_cleanup_mountinfo) = NULL; + + mi = sc_parse_mountinfo(NULL); + if (mi == NULL) { + die("cannot parse mountinfo of the current process"); + } + for (mie = sc_first_mountinfo_entry(mi); mie != NULL; + mie = sc_next_mountinfo_entry(mie)) { + if (!sc_streq(mie->mount_dir, "/")) { + continue; + } + // NOTE: we want the initial rootfs just in case overmount + // was used to do something weird. The initial rootfs was + // set up by snap-confine and that is the one we want to + // measure. + debug("found root filesystem inside the mount namespace %d:%d", + mie->dev_major, mie->dev_minor); + return base_snap_dev != MKDEV(mie->dev_major, mie->dev_minor); + } + die("cannot find mount entry of the root filesystem inside snap namespace"); +} + +enum sc_discard_vote { + SC_DISCARD_NO = 1, + SC_DISCARD_YES = 2, +}; + +// The namespace may be stale. To check this we must actually switch into it +// but then we use up our setns call (the kernel misbehaves if we setns twice). +// To work around this we'll fork a child and use it to probe. The child will +// inspect the namespace and send information back via eventfd and then exit +// unconditionally. +static int sc_inspect_and_maybe_discard_stale_ns(int mnt_fd, + const char *snap_name, + const char *base_snap_name) +{ + char base_snap_rev[PATH_MAX] = { 0 }; + char fname[PATH_MAX] = { 0 }; + char mnt_fname[PATH_MAX] = { 0 }; + dev_t base_snap_dev; + int event_fd SC_CLEANUP(sc_cleanup_close) = -1; + + // Read the revision of the base snap by looking at the current symlink. + sc_must_snprintf(fname, sizeof fname, "%s/%s/current", + SNAP_MOUNT_DIR, base_snap_name); + if (readlink(fname, base_snap_rev, sizeof base_snap_rev) < 0) { + die("cannot read revision of base snap %s", fname); + } + if (base_snap_rev[sizeof base_snap_rev - 1] != '\0') { + die("cannot use symbolic link %s - value is too long", fname); + } + // Find the device that is backing the current revision of the base snap. + base_snap_dev = find_base_snap_device(base_snap_name, base_snap_rev); + + // Check if we are running on classic. Do it here because we will always + // (seemingly) run on a core system once we are inside a mount namespace. + bool is_classic = is_running_on_classic_distribution(); + + // Store the PID of this process. This is done instead of calls to + // getppid() below because then we can reliably track the PID of the + // parent even if the child process is re-parented. + pid_t parent = getpid(); + + // Create an eventfd for the communication with the child. + event_fd = eventfd(0, EFD_CLOEXEC); + if (event_fd < 0) { + die("cannot create eventfd for communication with inspection process"); + } + // Fork a child, it will do the inspection for us. + pid_t child = fork(); + if (child < 0) { + die("cannot fork support process for namespace inspection"); + } + + if (child == 0) { + // This is the child process which will inspect the mount namespace. + // + // Configure the child to die as soon as the parent dies. In an odd + // case where the parent is killed then we don't want to complete our + // task or wait for anything. + if (prctl(PR_SET_PDEATHSIG, SIGINT, 0, 0, 0) < 0) { + die("cannot set parent process death notification signal to SIGINT"); + } + // Check that parent process is still alive. If this is the case then + // we can *almost* reliably rely on the PR_SET_PDEATHSIG signal to wake + // us up from eventfd_read() below. In the rare case that the PID + // numbers overflow and the now-dead parent PID is recycled we will + // still hang forever on the read from eventfd below. + debug("ensuring that parent process is still alive"); + if (kill(parent, 0) < 0) { + switch (errno) { + case ESRCH: + debug("parent process has already terminated"); + abort(); + default: + die("cannot ensure that parent process is still alive"); + break; + } + } + + debug("joining the namespace that we are about to probe"); + // Move to the mount namespace of the snap we're trying to inspect. + if (setns(mnt_fd, CLONE_NEWNS) < 0) { + die("cannot join the mount namespace in order to inspect it"); + } + // Check if the namespace needs to be discarded. + // + // TODO: enable this for core distributions. This is complex because on + // core the rootfs is mounted in initrd and is _not_ changed (no + // pivot_root) and the base snap is again mounted (2nd time) by + // systemd. This makes us end up in a situation where the outer base + // snap will never match the rootfs inside the mount namespace. + bool should_discard = + is_classic ? should_discard_current_ns(base_snap_dev) : + false; + + // Send this back to the parent: 2 - discard, 1 - keep. + // Note that we cannot just use 0 and 1 because of the semantics of eventfd(2). + debug + ("sending information about the state of the mount namespace (%s)", + should_discard ? "discard" : "keep"); + if (eventfd_write + (event_fd, + should_discard ? SC_DISCARD_YES : SC_DISCARD_NO) < 0) { + die("cannot send information about the state of the mount namespace"); + } + // Exit, we're done. + debug + ("support process for mount namespace inspection is about to finish"); + exit(0); + } + // This is back in the parent process. + // + // Enable a sanity timeout in case the read blocks for unbound amount of + // time. This will ensure we will not hang around while holding the lock. + // Next, read the value written by the child process. + sc_enable_sanity_timeout(); + eventfd_t value = 0; + debug("receiving information about the state of the mount namespace"); + if (eventfd_read(event_fd, &value) < 0) { + die("cannot receive information about the state of the mount namespace"); + } + sc_disable_sanity_timeout(); + + // Wait for the child process to exit and collect its exit status. + errno = 0; + int status = 0; + if (waitpid(child, &status, 0) < 0) { + die("cannot wait for the support process for mount namespace inspection"); + } + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + die("support process for mount namespace inspection exited abnormally"); + } + // If the namespace is up-to-date then we are done. + if (value == SC_DISCARD_NO) { + debug("the mount namespace is up-to-date and can be reused"); + return 0; + } + // The namespace is stale, let's check if we can discard it. + debug("the mount namespace is stale and should be discarded"); + if (sc_cgroup_freezer_occupied(snap_name)) { + // Some processes are still using the namespace so we cannot discard it + // as that would fracture the view that the set of processes inside + // have on what is mounted. + return 0; + } + // The namespace is both stale and empty. We can discard it now. + debug("discarding stale and empty mount namespace"); + sc_must_snprintf(mnt_fname, sizeof mnt_fname, + "%s/%s%s", sc_ns_dir, snap_name, SC_NS_MNT_FILE); + + // Use MNT_DETACH as otherwise we get EBUSY. + if (umount2(mnt_fname, MNT_DETACH | UMOUNT_NOFOLLOW) < 0) { + die("cannot umount stale mount namespace %s", mnt_fname); + } + debug("stale mount namespace discarded"); + return EAGAIN; +} + +int sc_create_or_join_ns_group(struct sc_ns_group *group, + struct sc_apparmor *apparmor, + const char *base_snap_name, + const char *snap_name) { // Open the mount namespace file. char mnt_fname[PATH_MAX] = { 0 }; @@ -254,32 +482,47 @@ // sc_discard_preserved_ns_group() it will revert to a regular file. If // snap-confine is killed for whatever reason after the file is created but // before the file is bind-mounted it will also be a regular file. - mnt_fd = - openat(group->dir_fd, mnt_fname, - O_CREAT | O_RDONLY | O_CLOEXEC | O_NOFOLLOW, 0600); + mnt_fd = openat(group->dir_fd, mnt_fname, + O_CREAT | O_RDONLY | O_CLOEXEC | O_NOFOLLOW, 0600); if (mnt_fd < 0) { die("cannot open mount namespace file for namespace group %s", group->name); } - // Check if we got an nsfs-based file or a regular file. This can be - // reliably tested because nsfs has an unique filesystem type NSFS_MAGIC. - // On older kernels that don't support nsfs yet we can look for - // PROC_SUPER_MAGIC instead. + // Check if we got an nsfs-based or procfs file or a regular file. This can + // be reliably tested because nsfs has an unique filesystem type + // NSFS_MAGIC. On older kernels that don't support nsfs yet we can look + // for PROC_SUPER_MAGIC instead. // We can just ensure that this is the case thanks to fstatfs. - struct statfs buf; - if (fstatfs(mnt_fd, &buf) < 0) { - die("cannot perform fstatfs() on an mount namespace file descriptor"); + struct statfs ns_statfs_buf; + if (fstatfs(mnt_fd, &ns_statfs_buf) < 0) { + die("cannot perform fstatfs() on the mount namespace file descriptor"); + } + // Stat the mount namespace as well, this is later used to check if the + // namespace is used by other processes if we are considering discarding a + // stale namespace. + struct stat ns_stat_buf; + if (fstat(mnt_fd, &ns_stat_buf) < 0) { + die("cannot perform fstat() on the mount namespace file descriptor"); } #ifndef NSFS_MAGIC // Account for kernel headers old enough to not know about NSFS_MAGIC. #define NSFS_MAGIC 0x6e736673 #endif - if (buf.f_type == NSFS_MAGIC || buf.f_type == PROC_SUPER_MAGIC) { + if (ns_statfs_buf.f_type == NSFS_MAGIC + || ns_statfs_buf.f_type == PROC_SUPER_MAGIC) { + + // Inspect and perhaps discard the preserved mount namespace. + if (sc_inspect_and_maybe_discard_stale_ns + (mnt_fd, snap_name, base_snap_name) == EAGAIN) { + return EAGAIN; + } + // Remember the vanilla working directory so that we may attempt to restore it later. char *vanilla_cwd SC_CLEANUP(sc_cleanup_string) = NULL; vanilla_cwd = get_current_dir_name(); if (vanilla_cwd == NULL) { die("cannot get the current working directory"); } + // Move to the mount namespace of the snap we're trying to start. debug ("attempting to re-associate the mount namespace with the namespace group %s", group->name); @@ -289,6 +532,7 @@ debug ("successfully re-associated the mount namespace with the namespace group %s", group->name); + // Try to re-locate back to vanilla working directory. This can fail // because that directory is no longer present. if (chdir(vanilla_cwd) != 0) { @@ -301,7 +545,7 @@ } debug("successfully moved to %s", SC_VOID_DIR); } - return; + return 0; } debug("initializing new namespace group %s", group->name); // Create a new namespace and ask the caller to populate it. @@ -398,9 +642,10 @@ } group->should_populate = true; } + return 0; } -bool sc_should_populate_ns_group(struct sc_ns_group *group) +bool sc_should_populate_ns_group(struct sc_ns_group * group) { return group->should_populate; } diff -Nru snapd-2.29.4.2+17.10/cmd/snap-confine/ns-support.h snapd-2.31.1+17.10/cmd/snap-confine/ns-support.h --- snapd-2.29.4.2+17.10/cmd/snap-confine/ns-support.h 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-confine/ns-support.h 2018-01-24 20:02:44.000000000 +0000 @@ -31,7 +31,7 @@ * * This function should be called before sc_initialize_ns_groups(). **/ -void sc_reassociate_with_pid1_mount_ns(); +void sc_reassociate_with_pid1_mount_ns(void); /** * Initialize namespace sharing. @@ -54,7 +54,7 @@ * * For more details see namespaces(7). **/ -void sc_initialize_ns_groups(); +void sc_initialize_ns_groups(void); /** * Data required to manage namespaces amongst a group of processes. @@ -105,10 +105,13 @@ * the parent process unshares the mount namespace and sets a flag so that * sc_should_populate_ns_group() returns true. * - * @returns true if the mount namespace needs to be populated + * @returns 0 on success and EAGAIN if the namespace was stale and needs + * to be re-made. **/ -void sc_create_or_join_ns_group(struct sc_ns_group *group, - struct sc_apparmor *apparmor); +int sc_create_or_join_ns_group(struct sc_ns_group *group, + struct sc_apparmor *apparmor, + const char *base_snap_name, + const char *snap_name); /** * Check if the namespace needs to be populated. diff -Nru snapd-2.29.4.2+17.10/cmd/snap-confine/ns-support-test.c snapd-2.31.1+17.10/cmd/snap-confine/ns-support-test.c --- snapd-2.29.4.2+17.10/cmd/snap-confine/ns-support-test.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-confine/ns-support-test.c 2017-12-01 15:51:55.000000000 +0000 @@ -39,7 +39,7 @@ // // The directory is automatically reset to the real value at the end of the // test. -static const char *sc_test_use_fake_ns_dir() +static const char *sc_test_use_fake_ns_dir(void) { char *ns_dir = NULL; if (g_test_subprocess()) { @@ -64,7 +64,7 @@ // Check that allocating a namespace group sets up internal data structures to // safe values. -static void test_sc_alloc_ns_group() +static void test_sc_alloc_ns_group(void) { struct sc_ns_group *group = NULL; group = sc_alloc_ns_group(); @@ -101,7 +101,7 @@ // Check that initializing a namespace group creates the appropriate // filesystem structure. -static void test_sc_open_ns_group() +static void test_sc_open_ns_group(void) { const char *ns_dir = sc_test_use_fake_ns_dir(); sc_test_open_ns_group(NULL); @@ -110,7 +110,7 @@ (ns_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)); } -static void test_sc_open_ns_group_graceful() +static void test_sc_open_ns_group_graceful(void) { sc_set_ns_dir("/nonexistent"); g_test_queue_destroy((GDestroyNotify) sc_set_ns_dir, SC_NS_DIR); @@ -124,7 +124,7 @@ umount(dir); } -static void test_sc_is_ns_group_dir_private() +static void test_sc_is_ns_group_dir_private(void) { if (geteuid() != 0) { g_test_skip("this test needs to run as root"); @@ -152,7 +152,7 @@ g_test_trap_assert_passed(); } -static void test_sc_initialize_ns_groups() +static void test_sc_initialize_ns_groups(void) { if (geteuid() != 0) { g_test_skip("this test needs to run as root"); @@ -174,7 +174,7 @@ // Sanity check, ensure that the namespace filesystem identifier is what we // expect, aka NSFS_MAGIC. -static void test_nsfs_fs_id() +static void test_nsfs_fs_id(void) { struct utsname uts; if (uname(&uts) < 0) { @@ -198,7 +198,7 @@ g_assert_cmpint(buf.f_type, ==, NSFS_MAGIC); } -static void __attribute__ ((constructor)) init() +static void __attribute__ ((constructor)) init(void) { g_test_add_func("/ns/sc_alloc_ns_group", test_sc_alloc_ns_group); g_test_add_func("/ns/sc_open_ns_group", test_sc_open_ns_group); diff -Nru snapd-2.29.4.2+17.10/cmd/snap-confine/quirks.c snapd-2.31.1+17.10/cmd/snap-confine/quirks.c --- snapd-2.29.4.2+17.10/cmd/snap-confine/quirks.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-confine/quirks.c 2017-12-01 15:51:55.000000000 +0000 @@ -40,7 +40,7 @@ * (legacy). The mount point does not depend on build-time configuration and * does not differ from distribution to distribution. **/ -static const char *sc_get_inner_core_mount_point() +static const char *sc_get_inner_core_mount_point(void) { const char *core_path = "/snap/core/current/"; const char *ubuntu_core_path = "/snap/ubuntu-core/current/"; @@ -176,7 +176,7 @@ * * See: https://bugs.launchpad.net/snap-confine/+bug/1613845 **/ -static void sc_setup_lxd_quirk() +static void sc_setup_lxd_quirk(void) { const char *hostfs_lxd_dir = SC_HOSTFS_DIR "/var/lib/lxd"; if (access(hostfs_lxd_dir, F_OK) == 0) { @@ -188,7 +188,7 @@ } } -void sc_setup_quirks() +void sc_setup_quirks(void) { // because /var/lib/snapd is essential let's move it to /tmp/snapd for a sec char snapd_tmp[] = "/tmp/snapd.quirks_XXXXXX"; diff -Nru snapd-2.29.4.2+17.10/cmd/snap-confine/quirks.h snapd-2.31.1+17.10/cmd/snap-confine/quirks.h --- snapd-2.29.4.2+17.10/cmd/snap-confine/quirks.h 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-confine/quirks.h 2017-12-01 15:51:55.000000000 +0000 @@ -25,6 +25,6 @@ * because of requirement to stay compatible with certain snaps * that were tested with pre-chroot layout. **/ -void sc_setup_quirks(); +void sc_setup_quirks(void); #endif diff -Nru snapd-2.29.4.2+17.10/cmd/snap-confine/snap-confine.apparmor.in snapd-2.31.1+17.10/cmd/snap-confine/snap-confine.apparmor.in --- snapd-2.29.4.2+17.10/cmd/snap-confine/snap-confine.apparmor.in 2017-11-30 16:41:45.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-confine/snap-confine.apparmor.in 2018-02-06 08:39:52.000000000 +0000 @@ -8,7 +8,7 @@ # # Those are discussed on https://forum.snapcraft.io/t/snapd-vs-upstream-kernel-vs-apparmor # and https://forum.snapcraft.io/t/snaps-and-nfs-home/ - #include "/var/lib/snapd/apparmor/snap-confine.d" + #include "/var/lib/snapd/apparmor/snap-confine" # We run privileged, so be fanatical about what we include and don't use # any abstractions @@ -17,8 +17,10 @@ # libc, you are funny /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libc{,-[0-9]*}.so* mr, /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libpthread{,-[0-9]*}.so* mr, + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libreadline{,-[0-9]*}.so* mr, /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}librt{,-[0-9]*}.so* mr, /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libgcc_s.so* mr, + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libncursesw{,-[0-9]*}.so* mr, /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libresolv{,-[0-9]*}.so* mr, /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libselinux.so* mr, /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libpcre.so* mr, @@ -53,10 +55,12 @@ # cgroup: freezer # Allow creating per-snap cgroup freezers and adding snap command (task) # invocations to the freezer. This allows for reliably enumerating all - # running tasks for the snap. + # running tasks for the snap. In addition, allow enumerating processes in + # the cgroup to determine if it is occupied. /sys/fs/cgroup/freezer/ r, /sys/fs/cgroup/freezer/snap.*/ w, /sys/fs/cgroup/freezer/snap.*/tasks w, + /sys/fs/cgroup/freezer/snap.*/cgroup.procs r, # querying udev /etc/udev/udev.conf r, @@ -171,8 +175,8 @@ mount options=(rw rbind) /run/ -> /tmp/snap.rootfs_*/run/, mount options=(rw rslave) -> /tmp/snap.rootfs_*/run/, - mount options=(rw rbind) {/usr,}/lib/modules/ -> /tmp/snap.rootfs_*{/usr,}/lib/modules/, - mount options=(rw rslave) -> /tmp/snap.rootfs_*{/usr,}/lib/modules/, + mount options=(rw rbind) {,/usr}/lib{,32,64,x32}/modules/ -> /tmp/snap.rootfs_*{,/usr}/lib/modules/, + mount options=(rw rslave) -> /tmp/snap.rootfs_*{,/usr}/lib/modules/, mount options=(rw rbind) /var/log/ -> /tmp/snap.rootfs_*/var/log/, mount options=(rw rslave) -> /tmp/snap.rootfs_*/var/log/, @@ -250,7 +254,20 @@ /dev/nvidiactl r, /dev/nvidia-uvm r, /usr/** r, - mount options=(rw bind) /usr/lib/nvidia-*/ -> /{tmp/snap.rootfs_*/,}var/lib/snapd/lib/gl/, + mount options=(rw bind) /usr/lib{,32}/nvidia-*/ -> /{tmp/snap.rootfs_*/,}var/lib/snapd/lib/gl{,32}/, + mount options=(rw bind) /usr/lib{,32}/nvidia-*/ -> /{tmp/snap.rootfs_*/,}var/lib/snapd/lib/gl{,32}/, + /tmp/snap.rootfs_*/var/lib/snapd/lib/gl{,32}/* w, + mount fstype=tmpfs options=(rw nodev noexec) none -> /tmp/snap.rootfs_*/var/lib/snapd/lib/gl{,32}/, + mount options=(remount ro) -> /tmp/snap.rootfs_*/var/lib/snapd/lib/gl{,32}/, + + # Vulkan support + /tmp/snap.rootfs_*/var/lib/snapd/lib/vulkan/* w, + mount fstype=tmpfs options=(rw nodev noexec) none -> /tmp/snap.rootfs_*/var/lib/snapd/lib/vulkan/, + mount options=(remount ro) -> /tmp/snap.rootfs_*/var/lib/snapd/lib/vulkan/, + + # create gl dirs as needed + /tmp/snap.rootfs_*/var/lib/snapd/lib/gl{,32}/ w, + /tmp/snap.rootfs_*/var/lib/snapd/lib/vulkan/ w, # for chroot on steroids, we use pivot_root as a better chroot that makes # apparmor rules behave the same on classic and outside of classic. @@ -288,6 +305,12 @@ # Allow snap-confine to read snap contexts /var/lib/snapd/context/snap.* r, + # Allow snap-confine to unmount stale mount namespaces. + umount /run/snapd/ns/*.mnt, + # Required to correctly unmount bound mount namespace. + # See LP: #1735459 for details. + umount /, + # Support for the quirk system /var/ r, /var/lib/ r, @@ -336,9 +359,13 @@ capability sys_admin, signal (send, receive) set=(abrt) peer=@LIBEXECDIR@/snap-confine, signal (send) set=(int) peer=@LIBEXECDIR@/snap-confine//mount-namespace-capture-helper, - signal (send, receive) set=(alrm, exists) peer=@LIBEXECDIR@/snap-confine, + signal (send, receive) set=(int, alrm, exists) peer=@LIBEXECDIR@/snap-confine, signal (receive) set=(exists) peer=@LIBEXECDIR@/snap-confine//mount-namespace-capture-helper, + # workaround for linux 4.13/upstream, see + # https://forum.snapcraft.io/t/snapd-2-27-6-2-in-debian-sid-blocked-on-apparmor-in-kernel-4-13-0-1/2813/3 + ptrace (trace, tracedby) peer=@LIBEXECDIR@/snapd/snap-confine, + # For aa_change_hat() to go into ^mount-namespace-capture-helper @{PROC}/[0-9]*/attr/current w, @@ -350,8 +377,10 @@ # libc, you are funny /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libc{,-[0-9]*}.so* mr, /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libpthread{,-[0-9]*}.so* mr, + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libreadline{,-[0-9]*}.so* mr, /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}librt{,-[0-9]*}.so* mr, /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libgcc_s.so* mr, + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libncursesw{,-[0-9]*}.so* mr, /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libresolv{,-[0-9]*}.so* mr, /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libselinux.so* mr, /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libpcre.so* mr, @@ -483,6 +512,11 @@ # Needed to perform mount/unmounts. capability sys_admin, + # Needed to chown files for writable mimic. + capability chown, + + # Allow freezing and thawing the per-snap cgroup freezers + /sys/fs/cgroup/freezer/snap.*/freezer.state rw, # Support mount profiles via the content interface. This should correspond # to permutations of $SNAP -> $SNAP for reading and $SNAP_{DATA,COMMON} -> @@ -501,6 +535,40 @@ mount options=(rw bind) /var/snap/*/** -> /var/snap/*/**, mount options=(ro bind) /var/snap/*/** -> /var/snap/*/**, + # Allow creating missing mount directories under $SNAP_DATA. + # + # The "tree" of permissions is needed for SecureMkdirAll that uses + # open(..., O_NOFOLLOW) and mkdirat() using the resulting file + # descriptor. + / r, + /var/ r, + /var/snap/{,*/} r, + /var/snap/*/**/ rw, + + # Allow creating placeholder directory in /tmp/.snap/ as support for + # the writable mimic code that can poke holes in arbitrary read-only + # places using tmpfs and bind mounts. + /tmp/ r, + /tmp/.snap/{,**} rw, + # Allow mounting/unmounting any part of $SNAP over to a temporary place + # in /tmp/.snap/ during the preparation of a writable mimic. + mount options=(bind, rw) /snap/*/** -> /tmp/.snap/**, + # Allow mounting tmpfs over the original $SNAP/** directory. + mount fstype=tmpfs options=(rw) tmpfs -> /snap/*/**, + # Allow bind mounting anything from the temporary place in /tmp/.snap/ + # back to $SNAP/** (to re-construct the data that was there before). + mount options=(bind, rw) /tmp/.snap/** -> /snap/*/**, + # Allow unmounting the temporary directory in /tmp once it is no longer + # necessary. + umount /tmp/.snap/**, + + # Allow creating missing directories under $SNAP once the writable + # mimic is ready. + / r, + /snap/ r, + /snap/{,**} r, + /snap/*/** w, + # Allow the content interface to bind fonts from the host filesystem mount options=(ro bind) /var/lib/snapd/hostfs/usr/share/fonts/ -> /snap/*/*/**, # Allow the desktop interface to bind fonts from the host filesystem diff -Nru snapd-2.29.4.2+17.10/cmd/snap-confine/snap-confine-args-test.c snapd-2.31.1+17.10/cmd/snap-confine/snap-confine-args-test.c --- snapd-2.29.4.2+17.10/cmd/snap-confine/snap-confine-args-test.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-confine/snap-confine-args-test.c 2017-12-01 15:51:55.000000000 +0000 @@ -56,7 +56,7 @@ *argvp = argv; } -static void test_test_argc_argv() +static void test_test_argc_argv(void) { // Check that test_argc_argv() correctly stores data int argc; @@ -74,7 +74,7 @@ g_assert_null(argv[3]); } -static void test_sc_nonfatal_parse_args__typical() +static void test_sc_nonfatal_parse_args__typical(void) { // Test that typical invocation of snap-confine is parsed correctly. struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL; @@ -107,7 +107,7 @@ g_assert_null(argv[3]); } -static void test_sc_cleanup_args() +static void test_sc_cleanup_args(void) { // Check that NULL argument parser can be cleaned up struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL; @@ -128,7 +128,7 @@ g_assert_null(args); } -static void test_sc_nonfatal_parse_args__typical_classic() +static void test_sc_nonfatal_parse_args__typical_classic(void) { // Test that typical invocation of snap-confine is parsed correctly. struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL; @@ -161,7 +161,7 @@ g_assert_null(argv[3]); } -static void test_sc_nonfatal_parse_args__ubuntu_core_launcher() +static void test_sc_nonfatal_parse_args__ubuntu_core_launcher(void) { // Test that typical legacy invocation of snap-confine via the // ubuntu-core-launcher symlink, with duplicated security tag, is parsed @@ -196,7 +196,7 @@ g_assert_null(argv[3]); } -static void test_sc_nonfatal_parse_args__version() +static void test_sc_nonfatal_parse_args__version(void) { // Test that snap-confine --version is detected. struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL; @@ -226,7 +226,7 @@ g_assert_null(argv[3]); } -static void test_sc_nonfatal_parse_args__evil_input() +static void test_sc_nonfatal_parse_args__evil_input(void) { // Check that calling without any arguments is reported as error. struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL; @@ -266,7 +266,7 @@ "cannot parse arguments, argument at index 1 is NULL"); } -static void test_sc_nonfatal_parse_args__nothing_to_parse() +static void test_sc_nonfatal_parse_args__nothing_to_parse(void) { // Check that calling without any arguments is reported as error. struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL; @@ -285,7 +285,7 @@ "cannot parse arguments, argc is zero or argv is NULL"); } -static void test_sc_nonfatal_parse_args__no_security_tag() +static void test_sc_nonfatal_parse_args__no_security_tag(void) { // Check that lack of security tag is reported as error. struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL; @@ -307,7 +307,7 @@ g_assert_true(sc_error_match(err, SC_ARGS_DOMAIN, SC_ARGS_ERR_USAGE)); } -static void test_sc_nonfatal_parse_args__no_executable() +static void test_sc_nonfatal_parse_args__no_executable(void) { // Check that lack of security tag is reported as error. struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL; @@ -329,7 +329,7 @@ g_assert_true(sc_error_match(err, SC_ARGS_DOMAIN, SC_ARGS_ERR_USAGE)); } -static void test_sc_nonfatal_parse_args__unknown_option() +static void test_sc_nonfatal_parse_args__unknown_option(void) { // Check that unrecognized option switch is reported as error. struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL; @@ -351,7 +351,7 @@ g_assert_true(sc_error_match(err, SC_ARGS_DOMAIN, SC_ARGS_ERR_USAGE)); } -static void test_sc_nonfatal_parse_args__forwards_error() +static void test_sc_nonfatal_parse_args__forwards_error(void) { // Check that sc_nonfatal_parse_args() forwards errors. if (g_test_subprocess()) { @@ -376,7 +376,7 @@ "\nunrecognized command line option: --frozbonicator\n"); } -static void test_sc_nonfatal_parse_args__base_snap() +static void test_sc_nonfatal_parse_args__base_snap(void) { // Check that --base specifies the name of the base snap. struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL; @@ -404,7 +404,7 @@ g_assert_cmpint(sc_args_is_classic_confinement(args), ==, false); } -static void test_sc_nonfatal_parse_args__base_snap__missing_arg() +static void test_sc_nonfatal_parse_args__base_snap__missing_arg(void) { // Check that --base specifies the name of the base snap. struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL; @@ -426,7 +426,7 @@ g_assert_true(sc_error_match(err, SC_ARGS_DOMAIN, SC_ARGS_ERR_USAGE)); } -static void test_sc_nonfatal_parse_args__base_snap__twice() +static void test_sc_nonfatal_parse_args__base_snap__twice(void) { // Check that --base specifies the name of the base snap. struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL; @@ -449,7 +449,7 @@ g_assert_true(sc_error_match(err, SC_ARGS_DOMAIN, SC_ARGS_ERR_USAGE)); } -static void __attribute__ ((constructor)) init() +static void __attribute__ ((constructor)) init(void) { g_test_add_func("/args/test_argc_argv", test_test_argc_argv); g_test_add_func("/args/sc_cleanup_args", test_sc_cleanup_args); diff -Nru snapd-2.29.4.2+17.10/cmd/snap-confine/snap-confine.c snapd-2.31.1+17.10/cmd/snap-confine/snap-confine.c --- snapd-2.29.4.2+17.10/cmd/snap-confine/snap-confine.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-confine/snap-confine.c 2018-01-24 20:02:44.000000000 +0000 @@ -18,12 +18,13 @@ #include "config.h" #endif +#include #include #include #include #include -#include #include +#include #include #include "../libsnap-confine-private/cgroup-freezer-support.h" @@ -48,7 +49,7 @@ // sc_maybe_fixup_permissions fixes incorrect permissions // inside the mount namespace for /var/lib. Before 1ccce4 // this directory was created with permissions 1777. -void sc_maybe_fixup_permissions() +static void sc_maybe_fixup_permissions(void) { struct stat buf; if (stat("/var/lib", &buf) != 0) { @@ -68,7 +69,7 @@ // that cause libudev on 16.04 to fail with "udev_enumerate_scan failed". // See also: // https://forum.snapcraft.io/t/weird-udev-enumerate-error/2360/17 -void sc_maybe_fixup_udev() +static void sc_maybe_fixup_udev(void) { glob_t glob_res SC_CLEANUP(globfree) = { .gl_pathv = NULL,.gl_pathc = 0,.gl_offs = 0,}; @@ -222,7 +223,19 @@ debug("initializing mount namespace: %s", snap_name); struct sc_ns_group *group = NULL; group = sc_open_ns_group(snap_name, 0); - sc_create_or_join_ns_group(group, &apparmor); + if (sc_create_or_join_ns_group(group, &apparmor, + base_snap_name, + snap_name) == EAGAIN) { + // If the namespace was stale and was discarded we just need to + // try again. Since this is done with the per-snap lock held + // there are no races here. + if (sc_create_or_join_ns_group(group, &apparmor, + base_snap_name, + snap_name) == + EAGAIN) { + die("unexpectedly the namespace needs to be discarded again"); + } + } if (sc_should_populate_ns_group(group)) { sc_populate_mount_ns(base_snap_name, snap_name); sc_preserve_populated_ns_group(group); diff -Nru snapd-2.29.4.2+17.10/cmd/snap-confine/spread-tests/regression/lp-1599608/task.yaml snapd-2.31.1+17.10/cmd/snap-confine/spread-tests/regression/lp-1599608/task.yaml --- snapd-2.29.4.2+17.10/cmd/snap-confine/spread-tests/regression/lp-1599608/task.yaml 2017-03-06 13:33:50.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-confine/spread-tests/regression/lp-1599608/task.yaml 2018-01-24 20:02:44.000000000 +0000 @@ -27,7 +27,7 @@ unsquashfs $(ls -1 /var/lib/snapd/snaps/ubuntu-core_*.snap.orig | tail -1) echo 'echo PATH=$PATH > /run/udev/spread-test.out' >> ./squashfs-root/lib/udev/snappy-app-dev echo 'echo TESTVAR=$TESTVAR >> /run/udev/spread-test.out' >> ./squashfs-root/lib/udev/snappy-app-dev - mksquashfs ./squashfs-root $(ls -1 /var/lib/snapd/snaps/ubuntu-core_*.snap.orig | tail -1 | sed 's/.orig//') -comp xz + mksquashfs ./squashfs-root $(ls -1 /var/lib/snapd/snaps/ubuntu-core_*.snap.orig | tail -1 | sed 's/.orig//') -comp xz -no-fragments if [ ! -e $(ls -1 /var/lib/snapd/snaps/ubuntu-core_*.snap | tail -1) ]; then exit 1; fi echo "Mount modified core snap" mount $(ls -1 /var/lib/snapd/snaps/ubuntu-core_*.snap | tail -1) $(ls -1d /snap/ubuntu-core/* | grep -v current | tail -1) diff -Nru snapd-2.29.4.2+17.10/cmd/snap-confine/udev-support.c snapd-2.31.1+17.10/cmd/snap-confine/udev-support.c --- snapd-2.29.4.2+17.10/cmd/snap-confine/udev-support.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-confine/udev-support.c 2017-12-01 15:51:55.000000000 +0000 @@ -32,24 +32,24 @@ #include "../libsnap-confine-private/utils.h" #include "udev-support.h" -void +static void _run_snappy_app_dev_add_majmin(struct snappy_udev *udev_s, const char *path, unsigned major, unsigned minor) { int status = 0; pid_t pid = fork(); if (pid < 0) { - die("could not fork"); + die("cannot fork support process for device cgroup assignment"); } if (pid == 0) { uid_t real_uid, effective_uid, saved_uid; if (getresuid(&real_uid, &effective_uid, &saved_uid) != 0) - die("could not find user IDs"); + die("cannot get real, effective and saved user IDs"); // can't update the cgroup unless the real_uid is 0, euid as // 0 is not enough if (real_uid != 0 && effective_uid == 0) if (setuid(0) != 0) - die("setuid failed"); + die("cannot set user ID to zero"); char buf[64] = { 0 }; // pass snappy-add-dev an empty environment so the // user-controlled environment can't be used to subvert @@ -92,7 +92,7 @@ struct udev_device *d = udev_device_new_from_syspath(udev_s->udev, path); if (d == NULL) - die("can not find %s", path); + die("cannot find device from syspath %s", path); dev_t devnum = udev_device_get_devnum(d); udev_device_unref(d); @@ -116,7 +116,7 @@ // TAG+="snap_" (udev doesn't like '.' in the tag name) udev_s->tagname_len = sc_must_snprintf(udev_s->tagname, MAX_BUF, "%s", security_tag); - for (int i = 0; i < udev_s->tagname_len; i++) + for (size_t i = 0; i < udev_s->tagname_len; i++) if (udev_s->tagname[i] == '.') udev_s->tagname[i] = '_'; @@ -188,7 +188,7 @@ "/sys/fs/cgroup/devices/%s/", security_tag); if (mkdir(cgroup_dir, 0755) < 0 && errno != EEXIST) - die("mkdir failed"); + die("cannot create cgroup hierarchy %s", cgroup_dir); // move ourselves into it char cgroup_file[PATH_MAX] = { 0 }; diff -Nru snapd-2.29.4.2+17.10/cmd/snap-confine/user-support.c snapd-2.31.1+17.10/cmd/snap-confine/user-support.c --- snapd-2.29.4.2+17.10/cmd/snap-confine/user-support.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-confine/user-support.c 2017-12-01 15:51:55.000000000 +0000 @@ -23,7 +23,7 @@ #include "../libsnap-confine-private/utils.h" -void setup_user_data() +void setup_user_data(void) { const char *user_data = getenv("SNAP_USER_DATA"); @@ -41,7 +41,7 @@ }; } -void setup_user_xdg_runtime_dir() +void setup_user_xdg_runtime_dir(void) { const char *xdg_runtime_dir = getenv("XDG_RUNTIME_DIR"); diff -Nru snapd-2.29.4.2+17.10/cmd/snap-confine/user-support.h snapd-2.31.1+17.10/cmd/snap-confine/user-support.h --- snapd-2.29.4.2+17.10/cmd/snap-confine/user-support.h 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-confine/user-support.h 2017-12-01 15:51:55.000000000 +0000 @@ -18,8 +18,8 @@ #ifndef SNAP_CONFINE_USER_SUPPORT_H #define SNAP_CONFINE_USER_SUPPORT_H -void setup_user_data(); -void setup_user_xdg_runtime_dir(); +void setup_user_data(void); +void setup_user_xdg_runtime_dir(void); void mkpath(const char *const path); #endif diff -Nru snapd-2.29.4.2+17.10/cmd/snapctl/main.go snapd-2.31.1+17.10/cmd/snapctl/main.go --- snapd-2.29.4.2+17.10/cmd/snapctl/main.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snapctl/main.go 2017-12-01 15:51:55.000000000 +0000 @@ -24,7 +24,6 @@ "os" "github.com/snapcore/snapd/client" - "github.com/snapcore/snapd/corecfg" "github.com/snapcore/snapd/dirs" ) @@ -43,11 +42,8 @@ if len(os.Args) > 2 && os.Args[1] == "internal" { switch os.Args[2] { case "configure-core": - if err := corecfg.Run(); err != nil { - fmt.Fprintf(os.Stderr, "core configuration error: %v\n", err) - os.Exit(1) - } - os.Exit(0) + fmt.Fprintf(os.Stderr, "no internal core configuration anymore") + os.Exit(1) } } Binary files /tmp/tmpo2uzGS/Gh8oKDmUdI/snapd-2.29.4.2+17.10/cmd/snapd/snapd and /tmp/tmpo2uzGS/iJ1RfqcrWs/snapd-2.31.1+17.10/cmd/snapd/snapd differ diff -Nru snapd-2.29.4.2+17.10/cmd/snap-exec/main.go snapd-2.31.1+17.10/cmd/snap-exec/main.go --- snapd-2.29.4.2+17.10/cmd/snap-exec/main.go 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-exec/main.go 2018-01-24 20:02:44.000000000 +0000 @@ -175,9 +175,9 @@ } env = append(env, osutil.SubstituteEnv(app.Env())...) - // strings.Split() is ok here because we validate all app fields - // and the whitelist is pretty strict (see - // snap/validate.go:appContentWhitelist) + // strings.Split() is ok here because we validate all app fields and the + // whitelist is pretty strict (see snap/validate.go:appContentWhitelist) + // (see also overlord/snapstate/check_snap.go's normPath) tmpArgv := strings.Split(cmdAndArgs, " ") cmd := tmpArgv[0] cmdArgs := expandEnvCmdArgs(tmpArgv[1:], osutil.EnvMap(env)) diff -Nru snapd-2.29.4.2+17.10/cmd/snap-mgmt/snap-mgmt.sh.in snapd-2.31.1+17.10/cmd/snap-mgmt/snap-mgmt.sh.in --- snapd-2.29.4.2+17.10/cmd/snap-mgmt/snap-mgmt.sh.in 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-mgmt/snap-mgmt.sh.in 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,172 @@ +#!/bin/bash + +# Overlord management of snapd for package manager actions. +# Implements actions that would be invoked in %pre(un) actions for snapd. +# Derived from the snapd.postrm scriptlet used in the Ubuntu packaging for +# snapd. + +set -e + +SNAP_MOUNT_DIR="@SNAP_MOUNT_DIR@" + +show_help() { + exec cat <<'EOF' +Usage: snap-mgmt.sh [OPTIONS] + +A simple script to cleanup snap installations. + +optional arguments: + --help Show this help message and exit + --snap-mount-dir= Provide a path to be used as $SNAP_MOUNT_DIR + --purge Purge all data from $SNAP_MOUNT_DIR +EOF +} + +SNAP_UNIT_PREFIX="$(systemd-escape -p ${SNAP_MOUNT_DIR})" + +systemctl_stop() { + unit="$1" + if systemctl is-active -q "$unit"; then + echo "Stopping $unit" + systemctl stop -q "$unit" || true + fi +} + +purge() { + # debian-9, fedora-27, ubuntu-16.04... + distribution=$(. /etc/os-release; echo "${ID}-${VERSION_ID}") + + if [ "$distribution" = "ubuntu-14.04" ]; then + # snap.mount.service is a trusty thing + systemctl_stop snap.mount.service + else + # undo any bind mount to ${SNAP_MOUNT_DIR} that resulted from LP:#1668659 + # (that bug can't happen in trusty -- and doing this would mess up snap.mount.service there) + if grep -q "${SNAP_MOUNT_DIR} ${SNAP_MOUNT_DIR}" /proc/self/mountinfo; then + umount -l "${SNAP_MOUNT_DIR}" || true + fi + fi + + units=$(systemctl list-unit-files --no-legend --full | grep -vF snap.mount.service || true) + # *.snap mount points + mounts=$(echo "$units" | grep "^${SNAP_UNIT_PREFIX}[-.].*\.mount" | cut -f1 -d ' ') + # services from snaps + services=$(echo "$units" | grep "^snap\..*\.service" | cut -f1 -d ' ') + for unit in $services $mounts; do + # ensure its really a snap mount unit or systemd unit + if ! grep -q 'What=/var/lib/snapd/snaps/' "/etc/systemd/system/$unit" && ! grep -q 'X-Snappy=yes' "/etc/systemd/system/$unit"; then + echo "Skipping non-snapd systemd unit $unit" + continue + fi + + echo "Stopping $unit" + systemctl_stop "$unit" + + if echo "$unit" | grep -q ".*\.mount" ; then + # Transform ${SNAP_MOUNT_DIR}/core/3440 -> core/3440 removing any + # extra / preceding snap name, eg: + # /var/lib/snapd/snap/core/3440 -> core/3440 + # /snap/core/3440 -> core/3440 + # /snap/core//3440 -> core/3440 + # NOTE: we could have used `systemctl show $unit -p Where --value` + # but systemd 204 shipped with Ubuntu 14.04 does not support this + snap_rev=$(systemctl show "$unit" -p Where | sed -e 's#Where=##' -e "s#$SNAP_MOUNT_DIR##" -e 's#^/*##') + snap=$(echo "$snap_rev" |cut -f1 -d/) + rev=$(echo "$snap_rev" |cut -f2 -d/) + if [ -n "$snap" ]; then + echo "Removing snap $snap" + # aliases + if [ -d "${SNAP_MOUNT_DIR}/bin" ]; then + find "${SNAP_MOUNT_DIR}/bin" -maxdepth 1 -lname "$snap" -delete + find "${SNAP_MOUNT_DIR}/bin" -maxdepth 1 -lname "$snap.*" -delete + fi + # generated binaries + rm -f "${SNAP_MOUNT_DIR}/bin/$snap" + rm -f "${SNAP_MOUNT_DIR}/bin/$snap".* + # snap mount dir + umount -l "${SNAP_MOUNT_DIR}/$snap/$rev" 2> /dev/null || true + rm -rf "${SNAP_MOUNT_DIR:?}/$snap/$rev" + rm -f "${SNAP_MOUNT_DIR}/$snap/current" + # snap data dir + rm -rf "/var/snap/$snap/$rev" + rm -rf "/var/snap/$snap/common" + rm -f "/var/snap/$snap/current" + # opportunistic remove (may fail if there are still revisions left) + for d in "${SNAP_MOUNT_DIR}/$snap" "/var/snap/$snap"; do + if [ -d "$d" ]; then + rmdir --ignore-fail-on-non-empty "$d" + fi + done + fi + fi + + echo "Removing $unit" + rm -f "/etc/systemd/system/$unit" + rm -f "/etc/systemd/system/multi-user.target.wants/$unit" + done + + echo "Discarding preserved snap namespaces" + # opportunistic as those might not be actually mounted + if [ -d /run/snapd/ns ]; then + if [ $(ls /run/snapd/ns/*.mnt | wc -l) -gt 0 ]; then + for mnt in /run/snapd/ns/*.mnt; do + umount -l "$mnt" || true + rm -f "$mnt" + done + fi + if [ $(ls /run/snapd/ns/*.fstab | wc -l) -gt 0 ]; then + for fstab in /run/snapd/ns/*.fstab; do + rm -f "$fstab" + done + fi + umount -l /run/snapd/ns/ || true + fi + + echo "Removing downloaded snaps" + rm -rf /var/lib/snapd/snaps/* + + echo "Final directory cleanup" + rm -rf "${SNAP_MOUNT_DIR}" + rm -rf /var/snap + + echo "Removing leftover snap shared state data" + rm -rf /var/lib/snapd/desktop/applications/* + rm -rf /var/lib/snapd/seccomp/bpf/* + rm -rf /var/lib/snapd/device/* + rm -rf /var/lib/snapd/assertions/* + rm -rf /var/lib/snapd/cookie/* + rm -rf /var/lib/snapd/cache/* + rm -f /var/lib/snapd/state.json + + echo "Removing snapd catalog cache" + rm -f /var/cache/snapd/* + + if test -d /etc/apparmor.d; then + # Remove auto-generated rules for snap-confine from the 'core' snap + echo "Removing extra snap-confine apparmor rules" + # shellcheck disable=SC2046 + rm -f /etc/apparmor.d/$(echo "$SNAP_UNIT_PREFIX" | tr '-' '.').core.*.usr.lib.snapd.snap-confine + fi +} + +while [ -n "$1" ]; do + case "$1" in + --help) + show_help + exit + ;; + --snap-mount-dir=*) + SNAP_MOUNT_DIR=${1#*=} + SNAP_UNIT_PREFIX=$(systemd-escape -p "$SNAP_MOUNT_DIR") + shift + ;; + --purge) + purge + shift + ;; + *) + echo "Unknown command: $1" + exit 1 + ;; + esac +done diff -Nru snapd-2.29.4.2+17.10/cmd/snap-repair/cmd_run_test.go snapd-2.31.1+17.10/cmd/snap-repair/cmd_run_test.go --- snapd-2.29.4.2+17.10/cmd/snap-repair/cmd_run_test.go 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-repair/cmd_run_test.go 2018-02-19 16:54:28.000000000 +0000 @@ -30,9 +30,12 @@ repair "github.com/snapcore/snapd/cmd/snap-repair" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/release" ) func (r *repairSuite) TestRun(c *C) { + defer release.MockOnClassic(false)() + r1 := sysdb.InjectTrusted(r.storeSigning.Trusted) defer r1() r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{r.repairRootAcctKey}) @@ -51,7 +54,10 @@ repair.MockBaseURL(mockServer.URL) - err := repair.ParseArgs([]string{"run"}) + origArgs := os.Args + defer func() { os.Args = origArgs }() + os.Args = []string{"snap-repair", "run"} + err := repair.Run() c.Check(err, IsNil) c.Check(r.Stdout(), HasLen, 0) diff -Nru snapd-2.29.4.2+17.10/cmd/snap-repair/main_test.go snapd-2.31.1+17.10/cmd/snap-repair/main_test.go --- snapd-2.29.4.2+17.10/cmd/snap-repair/main_test.go 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-repair/main_test.go 2018-02-19 16:54:28.000000000 +0000 @@ -27,6 +27,7 @@ repair "github.com/snapcore/snapd/cmd/snap-repair" "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/httputil" "github.com/snapcore/snapd/release" "github.com/snapcore/snapd/testutil" ) @@ -42,6 +43,17 @@ stdout *bytes.Buffer stderr *bytes.Buffer + + restore func() +} + +func (r *repairSuite) SetUpSuite(c *C) { + r.baseRunnerSuite.SetUpSuite(c) + r.restore = httputil.SetUserAgentFromVersion("", "") +} + +func (r *repairSuite) TearDownSuite(c *C) { + r.restore() } func (r *repairSuite) SetUpTest(c *C) { diff -Nru snapd-2.29.4.2+17.10/cmd/snap-repair/runner.go snapd-2.31.1+17.10/cmd/snap-repair/runner.go --- snapd-2.29.4.2+17.10/cmd/snap-repair/runner.go 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-repair/runner.go 2018-02-19 16:54:28.000000000 +0000 @@ -332,6 +332,7 @@ if err != nil { return nil, err } + req.Header.Set("User-Agent", httputil.UserAgent()) req.Header.Set("Accept", "application/x.ubuntu.assertion") if revision >= 0 { req.Header.Set("If-None-Match", fmt.Sprintf(`"%d"`, revision)) @@ -436,6 +437,7 @@ if err != nil { return nil, err } + req.Header.Set("User-Agent", httputil.UserAgent()) req.Header.Set("Accept", "application/json") return run.cli.Do(req) }, func(resp *http.Response) error { diff -Nru snapd-2.29.4.2+17.10/cmd/snap-repair/runner_test.go snapd-2.31.1+17.10/cmd/snap-repair/runner_test.go --- snapd-2.29.4.2+17.10/cmd/snap-repair/runner_test.go 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-repair/runner_test.go 2018-02-19 16:54:28.000000000 +0000 @@ -43,6 +43,7 @@ "github.com/snapcore/snapd/asserts/sysdb" repair "github.com/snapcore/snapd/cmd/snap-repair" "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/httputil" "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/osutil" ) @@ -162,6 +163,17 @@ type runnerSuite struct { baseRunnerSuite + + restore func() +} + +func (s *runnerSuite) SetUpSuite(c *C) { + s.baseRunnerSuite.SetUpSuite(c) + s.restore = httputil.SetUserAgentFromVersion("1", "snap-repair") +} + +func (s *runnerSuite) TearDownSuite(c *C) { + s.restore() } var _ = Suite(&runnerSuite{}) @@ -230,6 +242,8 @@ func (s *runnerSuite) TestFetchJustRepair(c *C) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ua := r.Header.Get("User-Agent") + c.Check(strings.Contains(ua, "snap-repair"), Equals, true) c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion") c.Check(r.URL.Path, Equals, "/repairs/canonical/2") io.WriteString(w, testRepair) @@ -477,6 +491,8 @@ func (s *runnerSuite) TestPeek(c *C) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ua := r.Header.Get("User-Agent") + c.Check(strings.Contains(ua, "snap-repair"), Equals, true) c.Check(r.Header.Get("Accept"), Equals, "application/json") c.Check(r.URL.Path, Equals, "/repairs/canonical/2") io.WriteString(w, testHeadersResp) @@ -875,6 +891,9 @@ func makeMockServer(c *C, seqRepairs *[]string, redirectFirst bool) *httptest.Server { var mockServer *httptest.Server mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ua := r.Header.Get("User-Agent") + c.Check(strings.Contains(ua, "snap-repair"), Equals, true) + urlPath := r.URL.Path if redirectFirst && r.Header.Get("Accept") == asserts.MediaType { if !strings.HasPrefix(urlPath, "/final/") { diff -Nru snapd-2.29.4.2+17.10/cmd/snap-seccomp/main.go snapd-2.31.1+17.10/cmd/snap-seccomp/main.go --- snapd-2.29.4.2+17.10/cmd/snap-seccomp/main.go 2017-11-30 07:12:26.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-seccomp/main.go 2018-02-16 20:27:57.000000000 +0000 @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2017 Canonical Ltd + * Copyright (C) 2017-2018 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,8 +20,8 @@ package main //#cgo CFLAGS: -D_FILE_OFFSET_BITS=64 -//#cgo pkg-config: --static --cflags libseccomp -//#cgo LDFLAGS: -Wl,-Bstatic -lseccomp -Wl,-Bdynamic +//#cgo pkg-config: libseccomp +//#cgo LDFLAGS: // //#include //#include @@ -68,6 +68,12 @@ //#define PF_MPLS AF_MPLS //#endif // AF_MPLS // +// // https://github.com/sctplab/usrsctp/blob/master/usrsctplib/usrsctp.h +//#ifndef AF_CONN +//#define AF_CONN 123 +//#define PF_CONN AF_CONN +//#endif // AF_CONN +// //#ifndef PR_CAP_AMBIENT //#define PR_CAP_AMBIENT 47 //#define PR_CAP_AMBIENT_IS_SET 1 @@ -244,6 +250,8 @@ "PF_MPLS": C.PF_MPLS, "AF_CAN": syscall.AF_CAN, "PF_CAN": C.PF_CAN, + "AF_CONN": C.AF_CONN, // 123 + "PF_CONN": C.PF_CONN, // man 2 socket - type "SOCK_STREAM": syscall.SOCK_STREAM, @@ -651,7 +659,7 @@ } // write atomically - fout, err := osutil.NewAtomicFile(out, 0644, 0, -1, -1) + fout, err := osutil.NewAtomicFile(out, 0644, 0, osutil.NoChown, osutil.NoChown) if err != nil { return err } diff -Nru snapd-2.29.4.2+17.10/cmd/snap-seccomp/main_test.go snapd-2.31.1+17.10/cmd/snap-seccomp/main_test.go --- snapd-2.29.4.2+17.10/cmd/snap-seccomp/main_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-seccomp/main_test.go 2018-02-16 20:27:57.000000000 +0000 @@ -350,9 +350,6 @@ // {"read >=2", "read;native;1", main.SeccompRetKill}, // {"read >=2", "read;native;0", main.SeccompRetKill}, func (s *snapSeccompSuite) TestCompile(c *C) { - // The 'shadow' group is different in different distributions - shadowGid, err := osutil.FindGid("shadow") - c.Assert(err, IsNil) for _, t := range []struct { seccompWhitelist string @@ -447,11 +444,11 @@ {"ioctl - TIOCSTI", "ioctl;native;-,TIOCSTI", main.SeccompRetAllow}, {"ioctl - TIOCSTI", "ioctl;native;-,99", main.SeccompRetKill}, - // u:root g:shadow - {"fchown - u:root g:shadow", fmt.Sprintf("fchown;native;-,0,%d", shadowGid), main.SeccompRetAllow}, - {"fchown - u:root g:shadow", fmt.Sprintf("fchown;native;-,99,%d", shadowGid), main.SeccompRetKill}, - {"chown - u:root g:shadow", fmt.Sprintf("chown;native;-,0,%d", shadowGid), main.SeccompRetAllow}, - {"chown - u:root g:shadow", fmt.Sprintf("chown;native;-,99,%d", shadowGid), main.SeccompRetKill}, + // u:root g:root + {"fchown - u:root g:root", "fchown;native;-,0,0", main.SeccompRetAllow}, + {"fchown - u:root g:root", "fchown;native;-,99,0", main.SeccompRetKill}, + {"chown - u:root g:root", "chown;native;-,0,0", main.SeccompRetAllow}, + {"chown - u:root g:root", "chown;native;-,99,0", main.SeccompRetKill}, } { s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected) } @@ -478,6 +475,8 @@ {"socket AF_UNIX", "socket;native;99", main.SeccompRetKill}, {"socket - SOCK_STREAM", "socket;native;-,SOCK_STREAM", main.SeccompRetAllow}, {"socket - SOCK_STREAM", "socket;native;-,99", main.SeccompRetKill}, + {"socket AF_CONN", "socket;native;AF_CONN", main.SeccompRetAllow}, + {"socket AF_CONN", "socket;native;99", main.SeccompRetKill}, } { s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected) } @@ -741,17 +740,24 @@ } func (s *snapSeccompSuite) TestRestrictionsWorkingArgsUidGid(c *C) { + // while 'root' user usually has uid 0, 'daemon' user uid may vary + // across distributions, best lookup the uid directly + daemonUid, err := osutil.FindUid("daemon") + c.Assert(err, IsNil) + for _, t := range []struct { seccompWhitelist string bpfInput string expected int }{ - // good input. 'root' and 'daemon' are guaranteed to be '0' and - // '1' respectively + // good input. 'root' is guaranteed to be '0' and 'daemon' uid + // was determined at runtime {"setuid u:root", "setuid;native;0", main.SeccompRetAllow}, - {"setuid u:daemon", "setuid;native;1", main.SeccompRetAllow}, + {"setuid u:daemon", fmt.Sprintf("setuid;native;%v", daemonUid), + main.SeccompRetAllow}, {"setgid g:root", "setgid;native;0", main.SeccompRetAllow}, - {"setgid g:daemon", "setgid;native;1", main.SeccompRetAllow}, + {"setgid g:daemon", fmt.Sprintf("setgid;native;%v", daemonUid), + main.SeccompRetAllow}, // bad input {"setuid u:root", "setuid;native;99", main.SeccompRetKill}, {"setuid u:daemon", "setuid;native;99", main.SeccompRetKill}, diff -Nru snapd-2.29.4.2+17.10/cmd/snap-update-ns/bootstrap.c snapd-2.31.1+17.10/cmd/snap-update-ns/bootstrap.c --- snapd-2.29.4.2+17.10/cmd/snap-update-ns/bootstrap.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-update-ns/bootstrap.c 2017-12-01 15:51:55.000000000 +0000 @@ -41,70 +41,6 @@ // bootstrap_msg contains a static string if something fails. const char* bootstrap_msg = NULL; -// read_cmdline reads /proc/self/cmdline into the specified buffer, returning -// number of bytes read. -ssize_t read_cmdline(char* buf, size_t buf_size) -{ - int fd = open("/proc/self/cmdline", O_RDONLY | O_CLOEXEC | O_NOFOLLOW); - if (fd < 0) { - bootstrap_errno = errno; - bootstrap_msg = "cannot open /proc/self/cmdline"; - return -1; - } - // Ensure buf is initialized to all NULLs since our parsing of cmdline with - // its embedded NULLs depends on this. Use for loop instead of memset() to - // guarantee this initialization won't be optimized away. - size_t i; - for (i = 0; i < buf_size; ++i) { - buf[i] = '\0'; - } - ssize_t num_read = read(fd, buf, buf_size); - if (num_read < 0) { - bootstrap_errno = errno; - bootstrap_msg = "cannot read /proc/self/cmdline"; - } else if (num_read == 0) { - bootstrap_errno = errno; - bootstrap_msg = "unexpectedly /proc/self/cmdline is empty"; - } else if (num_read == buf_size && buf_size > 0 && buf[buf_size - 1] != '\0') { - bootstrap_errno = 0; - bootstrap_msg = "cannot fit all of /proc/self/cmdline, buffer too small"; - num_read = -1; - } - close(fd); - return num_read; -} - -// find_snap_name scans the command line buffer and looks for the 1st argument. -// if the 1st argument exists but is empty NULL is returned. -const char* -find_snap_name(const char* buf) -{ - // Skip the zeroth argument as well as any options. We know buf is NUL - // padded and terminated from read_cmdline(). - do { - size_t arg_len = strlen(buf); - buf += arg_len + 1; - } while (buf[0] == '-'); - - const char* snap_name = buf; - if (strlen(snap_name) == 0) { - return NULL; - } - return snap_name; -} - -const char* -find_1st_option(const char* buf) -{ - // Skip the zeroth argument and find the first command line option. - // We know buf is NUL padded and terminated from read_cmdline(). - size_t pos = strlen(buf) + 1; - if (buf[pos] == '-') { - return &buf[pos]; - } - return NULL; -} - // setns_into_snap switches mount namespace into that of a given snap. static int setns_into_snap(const char* snap_name) @@ -228,21 +164,20 @@ return 0; } -// process_arguments parses given cmdline which must be list of strings separated with NUL bytes. -// cmdline is an array of NUL ('\0') separated strings and guaranteed to be -// NUL-terminated via read_cmdline(). -void process_arguments(const char* cmdline, const char** snap_name_out, bool* should_setns_out) +// process_arguments parses given a command line +// argc and argv are defined as for the main() function +void process_arguments(int argc, char *const *argv, const char** snap_name_out, bool* should_setns_out) { // Find the name of the called program. If it is ending with ".test" then do nothing. // NOTE: This lets us use cgo/go to write tests without running the bulk // of the code automatically. // - const char* argv0 = cmdline; - if (argv0 == NULL) { + if (argv == NULL || argc < 1) { bootstrap_errno = 0; bootstrap_msg = "argv0 is corrupted"; return; } + const char* argv0 = argv[0]; const char* argv0_suffix_maybe = strstr(argv0, ".test"); if (argv0_suffix_maybe != NULL && argv0_suffix_maybe[strlen(".test")] == '\0') { bootstrap_errno = 0; @@ -250,23 +185,38 @@ return; } - // When we are running under "--from-snap-confine" option skip the setns - // call as snap-confine has already placed us in the right namespace. - const char* option = find_1st_option(cmdline); bool should_setns = true; - if (option != NULL) { - if (strncmp(option, "--from-snap-confine", strlen("--from-snap-confine")) == 0) { - should_setns = false; + const char* snap_name = NULL; + + // Sanity check the command line arguments. The go parts will + // scan this too. + int i; + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (arg[0] == '-') { + /* We have an option */ + if (!strcmp(arg, "--from-snap-confine")) { + // When we are running under "--from-snap-confine" + // option skip the setns call as snap-confine has + // already placed us in the right namespace. + should_setns = false; + } else { + bootstrap_errno = 0; + bootstrap_msg = "unsupported option"; + return; + } } else { - bootstrap_errno = 0; - bootstrap_msg = "unsupported option"; - return; + // We expect a single positional argument: the snap name + if (snap_name != NULL) { + bootstrap_errno = 0; + bootstrap_msg = "too many positional arguments"; + return; + } + snap_name = arg; } } - // Find the name of the snap by scanning the cmdline. If there's no snap - // name given, just bail out. The go parts will scan this too. - const char* snap_name = find_snap_name(cmdline); + // If there's no snap name given, just bail out. if (snap_name == NULL) { bootstrap_errno = 0; bootstrap_msg = "snap name not provided"; @@ -293,25 +243,19 @@ // bootstrap prepares snap-update-ns to work in the namespace of the snap given // on command line. -void bootstrap(void) +void bootstrap(int argc, char **argv, char **envp) { // We may have been started via a setuid-root snap-confine. In order to // prevent environment-based attacks we start by erasing all environment // variables. + char *snapd_debug = getenv("SNAPD_DEBUG"); if (clearenv() != 0) { bootstrap_errno = 0; bootstrap_msg = "bootstrap could not clear the environment"; return; } - // We don't have argc/argv so let's imitate that by reading cmdline - // NOTE: use explicit initialization to avoid optimizing-out memset. - char cmdline[1024] = { - 0, - }; - ssize_t num_read; - if ((num_read = read_cmdline(cmdline, sizeof cmdline)) < 0) { - // read_cmdline sets bootstrap_{errno,msg} - return; + if (snapd_debug != NULL) { + setenv("SNAPD_DEBUG", snapd_debug, 0); } // Analyze the read process cmdline to find the snap name and decide if we @@ -319,7 +263,7 @@ // This is spread out for easier testability. const char* snap_name = NULL; bool should_setns = false; - process_arguments(cmdline, &snap_name, &should_setns); + process_arguments(argc, argv, &snap_name, &should_setns); if (snap_name != NULL && should_setns) { setns_into_snap(snap_name); // setns_into_snap sets bootstrap_{errno,msg} diff -Nru snapd-2.29.4.2+17.10/cmd/snap-update-ns/bootstrap.go snapd-2.31.1+17.10/cmd/snap-update-ns/bootstrap.go --- snapd-2.29.4.2+17.10/cmd/snap-update-ns/bootstrap.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-update-ns/bootstrap.go 2017-12-01 15:51:55.000000000 +0000 @@ -28,9 +28,15 @@ #include #include "bootstrap.h" -__attribute__((constructor)) static void init(void) { - bootstrap(); -} +// The bootstrap function is called by the loader before passing +// control to main. We are using `preinit_array` rather than +// `init_array` because the Go linker adds its own initialisation +// function to `init_array`, and having ours run second would defeat +// the purpose of the C bootstrap code. +// +// The `used` attribute ensures that the compiler doesn't oprimise out +// the variable on the mistaken belief that it isn't used. +__attribute__((section(".preinit_array"), used)) static typeof(&bootstrap) init = &bootstrap; // NOTE: do not add anything before the following `import "C"' */ @@ -69,47 +75,39 @@ // END IMPORTANT -// readCmdline is a wrapper around the C function read_cmdline. -func readCmdline(buf []byte) C.ssize_t { - return C.read_cmdline((*C.char)(unsafe.Pointer(&buf[0])), C.size_t(cap(buf))) -} - -// findSnapName parses the argv-like array and finds the 1st argument. -func findSnapName(buf []byte) *string { - if ptr := C.find_snap_name((*C.char)(unsafe.Pointer(&buf[0]))); ptr != nil { - str := C.GoString(ptr) - return &str - } - return nil +func makeArgv(args []string) []*C.char { + // Create argv array with terminating NULL element + argv := make([]*C.char, len(args)+1) + for i, arg := range args { + argv[i] = C.CString(arg) + } + return argv } -// findFirstOption returns the first "-option" string in argv-like array. -func findFirstOption(buf []byte) *string { - if ptr := C.find_1st_option((*C.char)(unsafe.Pointer(&buf[0]))); ptr != nil { - str := C.GoString(ptr) - return &str +func freeArgv(argv []*C.char) { + for _, arg := range argv { + C.free(unsafe.Pointer(arg)) } - return nil } // validateSnapName checks if snap name is valid. // This also sets bootstrap_msg on failure. func validateSnapName(snapName string) int { cStr := C.CString(snapName) + defer C.free(unsafe.Pointer(cStr)) return int(C.validate_snap_name(cStr)) } // processArguments parses commnad line arguments. // The argument cmdline is a string with embedded // NUL bytes, separating particular arguments. -func processArguments(cmdline []byte) (snapName string, shouldSetNs bool) { +func processArguments(args []string) (snapName string, shouldSetNs bool) { + argv := makeArgv(args) + defer freeArgv(argv) + var snapNameOut *C.char var shouldSetNsOut C.bool - var buf *C.char - if len(cmdline) > 0 { - buf = (*C.char)(unsafe.Pointer(&cmdline[0])) - } - C.process_arguments(buf, &snapNameOut, &shouldSetNsOut) + C.process_arguments(C.int(len(args)), &argv[0], &snapNameOut, &shouldSetNsOut) if snapNameOut != nil { snapName = C.GoString(snapNameOut) } diff -Nru snapd-2.29.4.2+17.10/cmd/snap-update-ns/bootstrap.h snapd-2.31.1+17.10/cmd/snap-update-ns/bootstrap.h --- snapd-2.29.4.2+17.10/cmd/snap-update-ns/bootstrap.h 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-update-ns/bootstrap.h 2017-12-01 15:51:55.000000000 +0000 @@ -26,12 +26,8 @@ extern int bootstrap_errno; extern const char* bootstrap_msg; -void bootstrap(void); -void process_arguments(const char* cmdline, const char** snap_name_out, bool* should_setns_out); -ssize_t read_cmdline(char* buf, size_t buf_size); -const char* find_argv0(const char* buf); -const char* find_snap_name(const char* buf); -const char* find_1st_option(const char* buf); +void bootstrap(int argc, char **argv, char **envp); +void process_arguments(int argc, char *const *argv, const char** snap_name_out, bool* should_setns_out); int validate_snap_name(const char* snap_name); #endif diff -Nru snapd-2.29.4.2+17.10/cmd/snap-update-ns/bootstrap_test.go snapd-2.31.1+17.10/cmd/snap-update-ns/bootstrap_test.go --- snapd-2.29.4.2+17.10/cmd/snap-update-ns/bootstrap_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-update-ns/bootstrap_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -20,8 +20,6 @@ package main_test import ( - "strings" - . "gopkg.in/check.v1" update "github.com/snapcore/snapd/cmd/snap-update-ns" @@ -31,94 +29,6 @@ var _ = Suite(&bootstrapSuite{}) -func (s *bootstrapSuite) TestReadCmdLine(c *C) { - buf := make([]byte, 1024) - numRead := update.ReadCmdline(buf) - c.Assert(numRead, Not(Equals), -1) - c.Assert(numRead, Not(Equals), 1) - // Individual arguments are separated with NUL byte. - argv := strings.Split(string(buf[0:numRead]), "\x00") - // Smoke test, the actual value looks like - // "/tmp/go-build020699516/github.com/snapcore/snapd/cmd/snap-update-ns/_test/snap-update-ns.test" - c.Assert(strings.HasSuffix(argv[0], "snap-update-ns.test"), Equals, true, Commentf("argv[0] is %q", argv[0])) -} - -// Check that if there is only one argument we return nil. -func (s *bootstrapSuite) TestFindSnapName1(c *C) { - buf := []byte("arg0\x00") - result := update.FindSnapName(buf) - c.Assert(result, Equals, (*string)(nil)) -} - -// Check that if there are multiple arguments we return the 2nd one. -func (s *bootstrapSuite) TestFindSnapName2(c *C) { - buf := []byte("arg0\x00arg1\x00arg2\x00") - result := update.FindSnapName(buf) - c.Assert(result, Not(Equals), (*string)(nil)) - c.Assert(*result, Equals, "arg1") -} - -// Check that if the 1st argument in the buffer is not terminated we don't crash. -func (s *bootstrapSuite) TestFindSnapName3(c *C) { - buf := []byte("arg0") - result := update.FindSnapName(buf) - c.Assert(result, Equals, (*string)(nil)) -} - -// Check that if the 2nd argument in the buffer is not terminated we don't crash. -func (s *bootstrapSuite) TestFindSnapName4(c *C) { - buf := []byte("arg0\x00arg1") - result := update.FindSnapName(buf) - c.Assert(result, Not(Equals), (*string)(nil)) - c.Assert(*result, Equals, "arg1") -} - -// Check that if the 2nd argument an empty string we return NULL. -func (s *bootstrapSuite) TestFindSnapName5(c *C) { - buf := []byte("arg0\x00\x00") - result := update.FindSnapName(buf) - c.Assert(result, Equals, (*string)(nil)) -} - -// Check that if the 1st argument is an option then it is skipped. -func (s *bootstrapSuite) TestFindSnapName6(c *C) { - buf := []byte("arg0\x00--option\x00snap\x00\x00") - result := update.FindSnapName(buf) - c.Assert(result, NotNil) - c.Assert(*result, Equals, "snap") -} - -// Check that if many options are skipped. -func (s *bootstrapSuite) TestFindSnapName7(c *C) { - buf := []byte("arg0\x00--option\x00--another\x00snap\x00\x00") - result := update.FindSnapName(buf) - c.Assert(result, NotNil) - c.Assert(*result, Equals, "snap") -} - -// Check that if there are no options we just return nil. -func (s *bootstrapSuite) TestFindFirstOption1(c *C) { - for _, str := range []string{"\x00", "arg0\x00", "arg0\x00arg1\x00"} { - result := update.FindFirstOption([]byte(str)) - c.Assert(result, Equals, (*string)(nil)) - } -} - -// Check that if there are are options we return the first one. -func (s *bootstrapSuite) TestFindFirstOption2(c *C) { - expected := "-o1" - for _, str := range []string{ - "\x00-o1\x00", - "arg0\x00-o1\x00", - "arg0\x00-o1\x00arg1\x00", - "arg0\x00-o1\x00-o2\x00", - "arg0\x00-o1\x00-o2\x00arg1\x00", - } { - result := update.FindFirstOption([]byte(str)) - c.Assert(result, DeepEquals, &expected, Commentf("got %q", *result)) - } -} - // Check that ValidateSnapName rejects "/" and "..". func (s *bootstrapSuite) TestValidateSnapName(c *C) { c.Assert(update.ValidateSnapName("hello-world"), Equals, 0) @@ -126,39 +36,43 @@ c.Assert(update.ValidateSnapName("hello..world"), Equals, -1) c.Assert(update.ValidateSnapName("INVALID"), Equals, -1) c.Assert(update.ValidateSnapName("-invalid"), Equals, -1) + c.Assert(update.ValidateSnapName(""), Equals, -1) } // Test various cases of command line handling. func (s *bootstrapSuite) TestProcessArguments(c *C) { cases := []struct { - cmdline string + cmdline []string snapName string shouldSetNs bool errPattern string }{ // Corrupted buffer is dealt with. - {"", "", false, "argv0 is corrupted"}, + {[]string{}, "", false, "argv0 is corrupted"}, // When testing real bootstrap is identified and disabled. - {"argv0.test\x00", "", false, "bootstrap is not enabled while testing"}, + {[]string{"argv0.test"}, "", false, "bootstrap is not enabled while testing"}, // Snap name is mandatory. - {"argv0\x00", "", false, "snap name not provided"}, - {"argv0\x00\x00", "", false, "snap name not provided"}, + {[]string{"argv0"}, "", false, "snap name not provided"}, // Snap name is parsed correctly. - {"argv0\x00snapname\x00", "snapname", true, ""}, + {[]string{"argv0", "snapname"}, "snapname", true, ""}, + // Onlye one snap name is allowed. + {[]string{"argv0", "snapone", "snaptwo"}, "", false, "too many positional arguments"}, // Snap name is validated correctly. - {"argv0\x00in--valid\x00", "", false, "snap name cannot contain two consecutive dashes"}, - {"argv0\x00invalid-\x00", "", false, "snap name cannot end with a dash"}, - {"argv0\x00@invalid\x00", "", false, "snap name must use lower case letters, digits or dashes"}, - {"argv0\x00INVALID\x00", "", false, "snap name must use lower case letters, digits or dashes"}, + {[]string{"argv0", ""}, "", false, "snap name must contain at least one letter"}, + {[]string{"argv0", "in--valid"}, "", false, "snap name cannot contain two consecutive dashes"}, + {[]string{"argv0", "invalid-"}, "", false, "snap name cannot end with a dash"}, + {[]string{"argv0", "@invalid"}, "", false, "snap name must use lower case letters, digits or dashes"}, + {[]string{"argv0", "INVALID"}, "", false, "snap name must use lower case letters, digits or dashes"}, // The option --from-snap-confine disables setns. - {"argv0\x00--from-snap-confine\x00snapname\x00", "snapname", false, ""}, + {[]string{"argv0", "--from-snap-confine", "snapname"}, "snapname", false, ""}, + {[]string{"argv0", "snapname", "--from-snap-confine"}, "snapname", false, ""}, // Unknown options are reported. - {"argv0\x00-invalid\x00", "", false, "unsupported option"}, - {"argv0\x00--option\x00", "", false, "unsupported option"}, + {[]string{"argv0", "-invalid"}, "", false, "unsupported option"}, + {[]string{"argv0", "--option"}, "", false, "unsupported option"}, + {[]string{"argv0", "--from-snap-confine", "-invalid", "snapname"}, "", false, "unsupported option"}, } for _, tc := range cases { - buf := []byte(tc.cmdline) - snapName, shouldSetNs := update.ProcessArguments(buf) + snapName, shouldSetNs := update.ProcessArguments(tc.cmdline) err := update.BootstrapError() comment := Commentf("failed with cmdline %q, expected error pattern %q, actual error %q", tc.cmdline, tc.errPattern, err) diff -Nru snapd-2.29.4.2+17.10/cmd/snap-update-ns/change.go snapd-2.31.1+17.10/cmd/snap-update-ns/change.go --- snapd-2.29.4.2+17.10/cmd/snap-update-ns/change.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-update-ns/change.go 2018-01-31 08:47:06.000000000 +0000 @@ -21,12 +21,14 @@ import ( "fmt" - "path" + "os" + "path/filepath" "sort" "strings" "syscall" "github.com/snapcore/snapd/interfaces/mount" + "github.com/snapcore/snapd/logger" ) // Action represents a mount action (mount, remount, unmount, etc). @@ -53,28 +55,215 @@ return fmt.Sprintf("%s (%s)", c.Action, c.Entry) } -var ( - sysMount = syscall.Mount - sysUnmount = syscall.Unmount -) +// changePerform is Change.Perform that can be mocked for testing. +var changePerform func(*Change) ([]*Change, error) + +func (c *Change) createPath(path string, pokeHoles bool) ([]*Change, error) { + var err error + var changes []*Change + + // In case we need to create something, some constants. + const ( + mode = 0755 + uid = 0 + gid = 0 + ) + + // If the element doesn't exist we can attempt to create it. We will + // create the parent directory and then the final element relative to it. + // The traversed space may be writable so we just try to create things + // first. + kind, _ := c.Entry.OptStr("x-snapd.kind") + + // TODO: re-factor this, if possible, with inspection and preemptive + // creation after the current release ships. This should be possible but + // will affect tests heavily (churn, not safe before release). + switch kind { + case "": + err = secureMkdirAll(path, mode, uid, gid) + case "file": + err = secureMkfileAll(path, mode, uid, gid) + case "symlink": + target, _ := c.Entry.OptStr("x-snapd.symlink") + if target == "" { + err = fmt.Errorf("cannot create symlink with empty target") + } else { + err = secureMklinkAll(path, mode, uid, gid, target) + } + } + if err2, ok := err.(*ReadOnlyFsError); ok && pokeHoles { + // If the writing failed because the underlying file-system is read-only + // we can construct a writable mimic to fix that. + changes, err = createWritableMimic(err2.Path) + if err != nil { + err = fmt.Errorf("cannot create writable mimic over %q: %s", err2.Path, err) + } else { + // Try once again. Note that we care *just* about the error. We have already + // performed the hole poking and thus additional changes must be nil. + _, err = c.createPath(path, false) + } + } + return changes, err +} + +func (c *Change) ensureTarget() ([]*Change, error) { + var changes []*Change + + kind, _ := c.Entry.OptStr("x-snapd.kind") + path := c.Entry.Dir + + // We use lstat to ensure that we don't follow a symlink in case one was + // set up by the snap. Note that at the time this is run, all the snap's + // processes are frozen but if the path is a directory controlled by the + // user (typically in /home) then we may still race with user processes + // that change it. + fi, err := osLstat(path) + + if err == nil { + // If the element already exists we just need to ensure it is of + // the correct type. The desired type depends on the kind of entry + // we are working with. + switch kind { + case "": + if !fi.Mode().IsDir() { + err = fmt.Errorf("cannot use %q for mounting: not a directory", path) + } + case "file": + if !fi.Mode().IsRegular() { + err = fmt.Errorf("cannot use %q for mounting: not a regular file", path) + } + case "symlink": + // When we want to create a symlink we just need the empty + // space so anything that is in the way is a problem. + err = fmt.Errorf("cannot create symlink in %q: existing file in the way", path) + } + } else if os.IsNotExist(err) { + changes, err = c.createPath(path, true) + if err != nil { + err = fmt.Errorf("cannot create path %q: %s", path, err) + } + } else { + // If we cannot inspect the element let's just bail out. + err = fmt.Errorf("cannot inspect %q: %v", path, err) + } + return changes, err +} + +func (c *Change) ensureSource() error { + // We only have to do ensure bind mount source exists. + // This also rules out symlinks. + flags, _ := mount.OptsToCommonFlags(c.Entry.Options) + if flags&syscall.MS_BIND == 0 { + return nil + } + + kind, _ := c.Entry.OptStr("x-snapd.kind") + path := c.Entry.Name + fi, err := osLstat(path) + + if err == nil { + // If the element already exists we just need to ensure it is of + // the correct type. The desired type depends on the kind of entry + // we are working with. + switch kind { + case "": + if !fi.Mode().IsDir() { + err = fmt.Errorf("cannot use %q for mounting: not a directory", path) + } + case "file": + if !fi.Mode().IsRegular() { + err = fmt.Errorf("cannot use %q for mounting: not a regular file", path) + } + } + } else if os.IsNotExist(err) { + _, err = c.createPath(path, false) + if err != nil { + err = fmt.Errorf("cannot create path %q: %s", path, err) + } + } else { + // If we cannot inspect the element let's just bail out. + err = fmt.Errorf("cannot inspect %q: %v", path, err) + } + return err +} + +// changePerformImpl is the real implementation of Change.Perform +func changePerformImpl(c *Change) (changes []*Change, err error) { + if c.Action == Mount { + // We may be asked to bind mount a file, bind mount a directory, mount + // a filesystem over a directory, or create a symlink (which is abusing + // the "mount" concept slightly). That actual operation is performed in + // c.lowLevelPerform. Here we just set the stage to make that possible. + // + // As a result of this ensure call we may need to make the medium writable + // and that's why we may return more changes as a result of performing this + // one. + changes, err = c.ensureTarget() + if err != nil { + return changes, err + } -const unmountNoFollow = 8 + // At this time we can be sure that the target element (for files and + // directories) exists and is of the right type or that it (for + // symlinks) doesn't exist but the parent directory does. + // This property holds as long as we don't interact with locations that + // are under the control of regular (non-snap) processes that are not + // suspended and may be racing with us. + err = c.ensureSource() + if err != nil { + return changes, err + } + } + + // Perform the underlying mount / unmount / unlink call. + err = c.lowLevelPerform() + return changes, err +} + +func init() { + changePerform = changePerformImpl +} // Perform executes the desired mount or unmount change using system calls. // Filesystems that depend on helper programs or multiple independent calls to // the kernel (--make-shared, for example) are unsupported. -func (c *Change) Perform() error { +// +// Perform may synthesize *additional* changes that were necessary to perform +// this change (such as mounted tmpfs or overlayfs). +func (c *Change) Perform() ([]*Change, error) { + return changePerform(c) +} + +// lowLevelPerform is simple bridge from Change to mount / unmount syscall. +func (c *Change) lowLevelPerform() error { + var err error switch c.Action { case Mount: - flags, err := mount.OptsToFlags(c.Entry.Options) - if err != nil { - return err + kind, _ := c.Entry.OptStr("x-snapd.kind") + switch kind { + case "symlink": + // symlinks are handled in createInode directly, nothing to do here. + case "", "file": + flags, unparsed := mount.OptsToCommonFlags(c.Entry.Options) + err = sysMount(c.Entry.Name, c.Entry.Dir, c.Entry.Type, uintptr(flags), strings.Join(unparsed, ",")) + logger.Debugf("mount %q %q %q %d %q (error: %v)", c.Entry.Name, c.Entry.Dir, c.Entry.Type, uintptr(flags), strings.Join(unparsed, ","), err) } - return sysMount(c.Entry.Name, c.Entry.Dir, c.Entry.Type, uintptr(flags), "") + return err case Unmount: - return sysUnmount(c.Entry.Dir, unmountNoFollow) + kind, _ := c.Entry.OptStr("x-snapd.kind") + switch kind { + case "symlink": + err = osRemove(c.Entry.Dir) + logger.Debugf("remove %q (error: %v)", c.Entry.Dir, err) + case "", "file": + err = sysUnmount(c.Entry.Dir, umountNoFollow) + logger.Debugf("umount %q (error: %v)", c.Entry.Dir, err) + } + return err + case Keep: + return nil } - return fmt.Errorf("cannot process mount change, unknown action: %q", c.Action) + return fmt.Errorf("cannot process mount change: unknown action: %q", c.Action) } // NeededChanges computes the changes required to change current to desired mount entries. @@ -94,10 +283,10 @@ // easily test if a given directory is a subdirectory with // strings.HasPrefix coupled with an extra slash character. for i := range current { - current[i].Dir = path.Clean(current[i].Dir) + current[i].Dir = filepath.Clean(current[i].Dir) } for i := range desired { - desired[i].Dir = path.Clean(desired[i].Dir) + desired[i].Dir = filepath.Clean(desired[i].Dir) } // Sort both lists by directory name with implicit trailing slash. @@ -110,26 +299,63 @@ desiredMap[desired[i].Dir] = &desired[i] } + // Indexed by mount point path. + reuse := make(map[string]bool) + // Indexed by entry ID + desiredIDs := make(map[string]bool) + var skipDir string + + // Collect the IDs of desired changes. + // We need that below to keep implicit changes from the current profile. + for i := range desired { + desiredIDs[XSnapdEntryID(&desired[i])] = true + } + // Compute reusable entries: those which are equal in current and desired and which // are not prefixed by another entry that changed. - var reuse map[string]bool - var skipDir string for i := range current { dir := current[i].Dir if skipDir != "" && strings.HasPrefix(dir, skipDir) { + logger.Debugf("skipping entry %q", current[i]) continue } skipDir = "" // reset skip prefix as it no longer applies + + // Reuse synthetic entries if their needed-by entry is desired. + // Synthetic entries cannot exist on their own and always couple to a + // non-synthetic entry. + + // NOTE: Synthetic changes have a special purpose. + // + // They are a "shadow" of mount events that occurred to allow one of + // the desired mount entries to be possible. The changes have only one + // goal: tell snap-update-ns how those mount events can be undone in + // case they are no longer needed. The actual changes may have been + // different and may have involved steps not represented as synthetic + // mount entires as long as those synthetic entries can be undone to + // reverse the effect. In reality each non-tmpfs synthetic entry was + // constructed using a temporary bind mount that contained the original + // mount entries of a directory that was hidden with a tmpfs, but this + // fact was lost. + if XSnapdSynthetic(¤t[i]) && desiredIDs[XSnapdNeededBy(¤t[i])] { + logger.Debugf("reusing synthetic entry %q", current[i]) + reuse[dir] = true + continue + } + + // Reuse entries that are desired and identical in the current profile. if entry, ok := desiredMap[dir]; ok && current[i].Equal(entry) { - if reuse == nil { - reuse = make(map[string]bool) - } + logger.Debugf("reusing unchanged entry %q", current[i]) reuse[dir] = true continue } + skipDir = strings.TrimSuffix(dir, "/") + "/" } + logger.Debugf("desiredIDs: %v", desiredIDs) + logger.Debugf("reuse: %v", reuse) + // We are now ready to compute the necessary mount changes. var changes []*Change diff -Nru snapd-2.29.4.2+17.10/cmd/snap-update-ns/change_test.go snapd-2.31.1+17.10/cmd/snap-update-ns/change_test.go --- snapd-2.29.4.2+17.10/cmd/snap-update-ns/change_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-update-ns/change_test.go 2018-01-31 08:47:06.000000000 +0000 @@ -21,6 +21,7 @@ import ( "errors" + "os" . "gopkg.in/check.v1" @@ -47,6 +48,12 @@ s.BaseTest.AddCleanup(update.MockSystemCalls(s.sys)) } +func (s *changeSuite) TestFakeFileInfo(c *C) { + c.Assert(update.FileInfoDir.IsDir(), Equals, true) + c.Assert(update.FileInfoFile.IsDir(), Equals, false) + c.Assert(update.FileInfoSymlink.IsDir(), Equals, false) +} + func (s *changeSuite) TestString(c *C) { change := update.Change{ Entry: mount.Entry{Dir: "/a/b", Name: "/dev/sda1"}, @@ -65,18 +72,18 @@ // When the profiles are the same we don't do anything. func (s *changeSuite) TestNeededChangesNoChange(c *C) { - current := &mount.Profile{Entries: []mount.Entry{{Dir: "/common/stuf"}}} - desired := &mount.Profile{Entries: []mount.Entry{{Dir: "/common/stuf"}}} + current := &mount.Profile{Entries: []mount.Entry{{Dir: "/common/stuff"}}} + desired := &mount.Profile{Entries: []mount.Entry{{Dir: "/common/stuff"}}} changes := update.NeededChanges(current, desired) c.Assert(changes, DeepEquals, []*update.Change{ - {Entry: mount.Entry{Dir: "/common/stuf"}, Action: update.Keep}, + {Entry: mount.Entry{Dir: "/common/stuff"}, Action: update.Keep}, }) } // When the content interface is connected we should mount the new entry. func (s *changeSuite) TestNeededChangesTrivialMount(c *C) { current := &mount.Profile{} - desired := &mount.Profile{Entries: []mount.Entry{{Dir: "/common/stuf"}}} + desired := &mount.Profile{Entries: []mount.Entry{{Dir: "/common/stuff"}}} changes := update.NeededChanges(current, desired) c.Assert(changes, DeepEquals, []*update.Change{ {Entry: desired.Entries[0], Action: update.Mount}, @@ -85,7 +92,7 @@ // When the content interface is disconnected we should unmount the mounted entry. func (s *changeSuite) TestNeededChangesTrivialUnmount(c *C) { - current := &mount.Profile{Entries: []mount.Entry{{Dir: "/common/stuf"}}} + current := &mount.Profile{Entries: []mount.Entry{{Dir: "/common/stuff"}}} desired := &mount.Profile{} changes := update.NeededChanges(current, desired) c.Assert(changes, DeepEquals, []*update.Change{ @@ -96,14 +103,14 @@ // When umounting we unmount children before parents. func (s *changeSuite) TestNeededChangesUnmountOrder(c *C) { current := &mount.Profile{Entries: []mount.Entry{ - {Dir: "/common/stuf/extra"}, - {Dir: "/common/stuf"}, + {Dir: "/common/stuff/extra"}, + {Dir: "/common/stuff"}, }} desired := &mount.Profile{} changes := update.NeededChanges(current, desired) c.Assert(changes, DeepEquals, []*update.Change{ - {Entry: mount.Entry{Dir: "/common/stuf/extra"}, Action: update.Unmount}, - {Entry: mount.Entry{Dir: "/common/stuf"}, Action: update.Unmount}, + {Entry: mount.Entry{Dir: "/common/stuff/extra"}, Action: update.Unmount}, + {Entry: mount.Entry{Dir: "/common/stuff"}, Action: update.Unmount}, }) } @@ -111,56 +118,156 @@ func (s *changeSuite) TestNeededChangesMountOrder(c *C) { current := &mount.Profile{} desired := &mount.Profile{Entries: []mount.Entry{ - {Dir: "/common/stuf/extra"}, - {Dir: "/common/stuf"}, + {Dir: "/common/stuff/extra"}, + {Dir: "/common/stuff"}, }} changes := update.NeededChanges(current, desired) c.Assert(changes, DeepEquals, []*update.Change{ - {Entry: mount.Entry{Dir: "/common/stuf"}, Action: update.Mount}, - {Entry: mount.Entry{Dir: "/common/stuf/extra"}, Action: update.Mount}, + {Entry: mount.Entry{Dir: "/common/stuff"}, Action: update.Mount}, + {Entry: mount.Entry{Dir: "/common/stuff/extra"}, Action: update.Mount}, }) } // When parent changes we don't reuse its children func (s *changeSuite) TestNeededChangesChangedParentSameChild(c *C) { current := &mount.Profile{Entries: []mount.Entry{ - {Dir: "/common/stuf", Name: "/dev/sda1"}, - {Dir: "/common/stuf/extra"}, + {Dir: "/common/stuff", Name: "/dev/sda1"}, + {Dir: "/common/stuff/extra"}, {Dir: "/common/unrelated"}, }} desired := &mount.Profile{Entries: []mount.Entry{ - {Dir: "/common/stuf", Name: "/dev/sda2"}, - {Dir: "/common/stuf/extra"}, + {Dir: "/common/stuff", Name: "/dev/sda2"}, + {Dir: "/common/stuff/extra"}, {Dir: "/common/unrelated"}, }} changes := update.NeededChanges(current, desired) c.Assert(changes, DeepEquals, []*update.Change{ {Entry: mount.Entry{Dir: "/common/unrelated"}, Action: update.Keep}, - {Entry: mount.Entry{Dir: "/common/stuf/extra"}, Action: update.Unmount}, - {Entry: mount.Entry{Dir: "/common/stuf", Name: "/dev/sda1"}, Action: update.Unmount}, - {Entry: mount.Entry{Dir: "/common/stuf", Name: "/dev/sda2"}, Action: update.Mount}, - {Entry: mount.Entry{Dir: "/common/stuf/extra"}, Action: update.Mount}, + {Entry: mount.Entry{Dir: "/common/stuff/extra"}, Action: update.Unmount}, + {Entry: mount.Entry{Dir: "/common/stuff", Name: "/dev/sda1"}, Action: update.Unmount}, + {Entry: mount.Entry{Dir: "/common/stuff", Name: "/dev/sda2"}, Action: update.Mount}, + {Entry: mount.Entry{Dir: "/common/stuff/extra"}, Action: update.Mount}, }) } // When child changes we don't touch the unchanged parent func (s *changeSuite) TestNeededChangesSameParentChangedChild(c *C) { current := &mount.Profile{Entries: []mount.Entry{ - {Dir: "/common/stuf"}, - {Dir: "/common/stuf/extra", Name: "/dev/sda1"}, + {Dir: "/common/stuff"}, + {Dir: "/common/stuff/extra", Name: "/dev/sda1"}, {Dir: "/common/unrelated"}, }} desired := &mount.Profile{Entries: []mount.Entry{ - {Dir: "/common/stuf"}, - {Dir: "/common/stuf/extra", Name: "/dev/sda2"}, + {Dir: "/common/stuff"}, + {Dir: "/common/stuff/extra", Name: "/dev/sda2"}, {Dir: "/common/unrelated"}, }} changes := update.NeededChanges(current, desired) c.Assert(changes, DeepEquals, []*update.Change{ {Entry: mount.Entry{Dir: "/common/unrelated"}, Action: update.Keep}, - {Entry: mount.Entry{Dir: "/common/stuf/extra", Name: "/dev/sda1"}, Action: update.Unmount}, - {Entry: mount.Entry{Dir: "/common/stuf"}, Action: update.Keep}, - {Entry: mount.Entry{Dir: "/common/stuf/extra", Name: "/dev/sda2"}, Action: update.Mount}, + {Entry: mount.Entry{Dir: "/common/stuff/extra", Name: "/dev/sda1"}, Action: update.Unmount}, + {Entry: mount.Entry{Dir: "/common/stuff"}, Action: update.Keep}, + {Entry: mount.Entry{Dir: "/common/stuff/extra", Name: "/dev/sda2"}, Action: update.Mount}, + }) +} + +// Unused bind mount farms are unmounted. +func (s *changeSuite) TestNeededChangesTmpfsBindMountFarmUnused(c *C) { + current := &mount.Profile{Entries: []mount.Entry{{ + // The tmpfs that lets us write into immutable squashfs. We mock + // x-snapd.needed-by to the last entry in the current profile (the bind + // mount). Mark it synthetic since it is a helper mount that is needed + // to facilitate the following mounts. + Name: "tmpfs", + Dir: "/snap/name/42/subdir", + Type: "tmpfs", + Options: []string{"x-snapd.needed-by=/snap/name/42/subdir", "x-snapd.synthetic"}, + }, { + // A bind mount to preserve a directory hidden by the tmpfs (the mount + // point is created elsewhere). We mock x-snapd.needed-by to the + // location of the bind mount below that is no longer desired. + Name: "/var/lib/snapd/hostfs/snap/name/42/subdir/existing", + Dir: "/snap/name/42/subdir/existing", + Options: []string{"bind", "ro", "x-snapd.needed-by=/snap/name/42/subdir", "x-snapd.synthetic"}, + }, { + // A bind mount to put some content from another snap. The bind mount + // is nothing special but the fact that it is possible is the reason + // the two entries above exist. The mount point (created) is created + // elsewhere. + Name: "/snap/other/123/libs", + Dir: "/snap/name/42/subdir/created", + Options: []string{"bind", "ro"}, + }}} + + desired := &mount.Profile{} + + changes := update.NeededChanges(current, desired) + + c.Assert(changes, DeepEquals, []*update.Change{ + {Entry: mount.Entry{ + Name: "/var/lib/snapd/hostfs/snap/name/42/subdir/existing", + Dir: "/snap/name/42/subdir/existing", + Options: []string{"bind", "ro", "x-snapd.needed-by=/snap/name/42/subdir", "x-snapd.synthetic"}, + }, Action: update.Unmount}, + {Entry: mount.Entry{ + Name: "/snap/other/123/libs", + Dir: "/snap/name/42/subdir/created", + Options: []string{"bind", "ro"}, + }, Action: update.Unmount}, + {Entry: mount.Entry{ + Name: "tmpfs", + Dir: "/snap/name/42/subdir", + Type: "tmpfs", + Options: []string{"x-snapd.needed-by=/snap/name/42/subdir", "x-snapd.synthetic"}, + }, Action: update.Unmount}, + }) +} + +func (s *changeSuite) TestNeededChangesTmpfsBindMountFarmUsed(c *C) { + // NOTE: the current profile is the same as in the test + // TestNeededChangesTmpfsBindMountFarmUnused written above. + current := &mount.Profile{Entries: []mount.Entry{{ + Name: "tmpfs", + Dir: "/snap/name/42/subdir", + Type: "tmpfs", + Options: []string{"x-snapd.needed-by=/snap/name/42/subdir/created", "x-snapd.synthetic"}, + }, { + Name: "/var/lib/snapd/hostfs/snap/name/42/subdir/existing", + Dir: "/snap/name/42/subdir/existing", + Options: []string{"bind", "ro", "x-snapd.needed-by=/snap/name/42/subdir/created", "x-snapd.synthetic"}, + }, { + Name: "/snap/other/123/libs", + Dir: "/snap/name/42/subdir/created", + Options: []string{"bind", "ro"}, + }}} + + desired := &mount.Profile{Entries: []mount.Entry{{ + // This is the only entry that we explicitly want but in order to + // support it we need to keep the remaining implicit entries. + Name: "/snap/other/123/libs", + Dir: "/snap/name/42/subdir/created", + Options: []string{"bind", "ro"}, + }}} + + changes := update.NeededChanges(current, desired) + + c.Assert(changes, DeepEquals, []*update.Change{ + {Entry: mount.Entry{ + Name: "/var/lib/snapd/hostfs/snap/name/42/subdir/existing", + Dir: "/snap/name/42/subdir/existing", + Options: []string{"bind", "ro", "x-snapd.needed-by=/snap/name/42/subdir/created", "x-snapd.synthetic"}, + }, Action: update.Keep}, + {Entry: mount.Entry{ + Name: "/snap/other/123/libs", + Dir: "/snap/name/42/subdir/created", + Options: []string{"bind", "ro"}, + }, Action: update.Keep}, + {Entry: mount.Entry{ + Name: "tmpfs", + Dir: "/snap/name/42/subdir", + Type: "tmpfs", + Options: []string{"x-snapd.needed-by=/snap/name/42/subdir/created", "x-snapd.synthetic"}, + }, Action: update.Keep}, }) } @@ -195,45 +302,187 @@ // Change.Perform calls the mount system call. func (s *changeSuite) TestPerformMount(c *C) { - chg := &update.Change{Action: update.Mount, Entry: mount.Entry{Name: "source", Dir: "target", Type: "type"}} - c.Assert(chg.Perform(), IsNil) - c.Assert(s.sys.Calls(), DeepEquals, []string{`mount "source" "target" "type" 0 ""`}) + s.sys.InsertLstatResult(`lstat "/target"`, update.FileInfoDir) + chg := &update.Change{Action: update.Mount, Entry: mount.Entry{Name: "/source", Dir: "/target", Type: "type"}} + synth, err := chg.Perform() + c.Assert(err, IsNil) + c.Assert(synth, HasLen, 0) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `lstat "/target"`, + `mount "/source" "/target" "type" 0 ""`, + }) +} + +// Change.Perform calls the mount system call (for bind mounts). +func (s *changeSuite) TestPerformBindMount(c *C) { + s.sys.InsertLstatResult(`lstat "/source"`, update.FileInfoDir) + s.sys.InsertLstatResult(`lstat "/target"`, update.FileInfoDir) + chg := &update.Change{Action: update.Mount, Entry: mount.Entry{Name: "/source", Dir: "/target", Type: "type", Options: []string{"bind"}}} + synth, err := chg.Perform() + c.Check(err, IsNil) + c.Assert(synth, HasLen, 0) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `lstat "/target"`, + `lstat "/source"`, + `mount "/source" "/target" "type" MS_BIND ""`, + }) +} + +// Change.Perform creates the missing mount target. +func (s *changeSuite) TestPerformMountAutomaticMkdirTarget(c *C) { + s.sys.InsertFault(`lstat "/target"`, os.ErrNotExist) + chg := &update.Change{Action: update.Mount, Entry: mount.Entry{Name: "/source", Dir: "/target", Type: "type"}} + synth, err := chg.Perform() + c.Assert(err, IsNil) + c.Assert(synth, HasLen, 0) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `lstat "/target"`, + `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, + `mkdirat 3 "target" 0755`, + `openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, + `fchown 4 0 0`, + `close 4`, + `close 3`, + `mount "/source" "/target" "type" 0 ""`, + }) +} + +// Change.Perform creates the missing bind-mount source. +func (s *changeSuite) TestPerformMountAutomaticMkdirSource(c *C) { + s.sys.InsertLstatResult(`lstat "/target"`, update.FileInfoDir) + s.sys.InsertFault(`lstat "/source"`, os.ErrNotExist) + chg := &update.Change{Action: update.Mount, Entry: mount.Entry{Name: "/source", Dir: "/target", Type: "type", Options: []string{"bind"}}} + synth, err := chg.Perform() + c.Assert(err, IsNil) + c.Assert(synth, HasLen, 0) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `lstat "/target"`, + `lstat "/source"`, + `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, + `mkdirat 3 "source" 0755`, + `openat 3 "source" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, + `fchown 4 0 0`, + `close 4`, + `close 3`, + `mount "/source" "/target" "type" MS_BIND ""`, + }) +} + +// Change.Perform rejects mount target if it is a symlink. +func (s *changeSuite) TestPerformMountRejectsTargetSymlink(c *C) { + s.sys.InsertLstatResult(`lstat "/target"`, update.FileInfoSymlink) + chg := &update.Change{Action: update.Mount, Entry: mount.Entry{Name: "/source", Dir: "/target", Type: "type"}} + synth, err := chg.Perform() + c.Assert(err, ErrorMatches, `cannot use "/target" for mounting: not a directory`) + c.Assert(synth, HasLen, 0) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `lstat "/target"`, + }) +} + +// Change.Perform rejects bind-mount target if it is a symlink. +func (s *changeSuite) TestPerformBindMountRejectsTargetSymlink(c *C) { + s.sys.InsertLstatResult(`lstat "/target"`, update.FileInfoSymlink) + chg := &update.Change{Action: update.Mount, Entry: mount.Entry{Name: "/source", Dir: "/target", Type: "type", Options: []string{"bind"}}} + synth, err := chg.Perform() + c.Assert(err, ErrorMatches, `cannot use "/target" for mounting: not a directory`) + c.Assert(synth, HasLen, 0) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `lstat "/target"`, + }) +} + +// Change.Perform rejects bind-mount source if it is a symlink. +func (s *changeSuite) TestPerformBindMountRejectsSourceSymlink(c *C) { + s.sys.InsertLstatResult(`lstat "/target"`, update.FileInfoDir) + s.sys.InsertLstatResult(`lstat "/source"`, update.FileInfoSymlink) + chg := &update.Change{Action: update.Mount, Entry: mount.Entry{Name: "/source", Dir: "/target", Type: "type", Options: []string{"bind"}}} + synth, err := chg.Perform() + c.Assert(err, ErrorMatches, `cannot use "/source" for mounting: not a directory`) + c.Assert(synth, HasLen, 0) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `lstat "/target"`, + `lstat "/source"`, + }) +} + +// Change.Perform returns errors from os.Lstat (apart from ErrNotExist) +func (s *changeSuite) TestPerformMountLstatError(c *C) { + s.sys.InsertFault(`lstat "/target"`, errTesting) + chg := &update.Change{Action: update.Mount, Entry: mount.Entry{Name: "/source", Dir: "/target", Type: "type"}} + synth, err := chg.Perform() + c.Assert(err, ErrorMatches, `cannot inspect "/target": testing`) + c.Assert(synth, HasLen, 0) + c.Assert(s.sys.Calls(), DeepEquals, []string{`lstat "/target"`}) +} + +// Change.Perform returns errors from os.MkdirAll +func (s *changeSuite) TestPerformMountMkdirAllError(c *C) { + s.sys.InsertFault(`lstat "/target"`, os.ErrNotExist) + s.sys.InsertFault(`mkdirat 3 "target" 0755`, errTesting) + chg := &update.Change{Action: update.Mount, Entry: mount.Entry{Name: "/source", Dir: "/target", Type: "type"}} + synth, err := chg.Perform() + c.Assert(err, ErrorMatches, `cannot create path "/target": cannot mkdir path segment "target": testing`) + c.Assert(synth, HasLen, 0) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `lstat "/target"`, + `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, + `mkdirat 3 "target" 0755`, + `close 3`, + }) } // Change.Perform returns errors from mount system call func (s *changeSuite) TestPerformMountError(c *C) { - s.sys.InsertFault(`mount "source" "target" "type" 0 ""`, errTesting) - chg := &update.Change{Action: update.Mount, Entry: mount.Entry{Name: "source", Dir: "target", Type: "type"}} - c.Assert(chg.Perform(), Equals, errTesting) - c.Assert(s.sys.Calls(), DeepEquals, []string{`mount "source" "target" "type" 0 ""`}) + s.sys.InsertLstatResult(`lstat "/target"`, update.FileInfoDir) + s.sys.InsertFault(`mount "/source" "/target" "type" 0 ""`, errTesting) + chg := &update.Change{Action: update.Mount, Entry: mount.Entry{Name: "/source", Dir: "/target", Type: "type"}} + synth, err := chg.Perform() + c.Assert(err, Equals, errTesting) + c.Assert(synth, HasLen, 0) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `lstat "/target"`, + `mount "/source" "/target" "type" 0 ""`, + }) } -// Change.Perform returns errors from bad flags -func (s *changeSuite) TestPerformMountOptionError(c *C) { - chg := &update.Change{Action: update.Mount, Entry: mount.Entry{Name: "source", Dir: "target", Type: "type", Options: []string{"bogus"}}} - c.Assert(chg.Perform(), ErrorMatches, `unsupported mount option: "bogus"`) - c.Assert(s.sys.Calls(), HasLen, 0) +// Change.Perform passes unrecognized options to mount. +func (s *changeSuite) TestPerformMountOptions(c *C) { + s.sys.InsertLstatResult(`lstat "target"`, update.FileInfoDir) + chg := &update.Change{Action: update.Mount, Entry: mount.Entry{Name: "source", Dir: "target", Type: "type", Options: []string{"funky"}}} + synth, err := chg.Perform() + c.Assert(err, IsNil) + c.Assert(synth, HasLen, 0) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `lstat "target"`, + `mount "source" "target" "type" 0 "funky"`, + }) } // Change.Perform calls the unmount system call. func (s *changeSuite) TestPerformUnmount(c *C) { chg := &update.Change{Action: update.Unmount, Entry: mount.Entry{Name: "source", Dir: "target", Type: "type"}} - c.Assert(chg.Perform(), IsNil) - // The flag 8 is UMOUNT_NOFOLLOW + synth, err := chg.Perform() + c.Assert(err, IsNil) c.Assert(s.sys.Calls(), DeepEquals, []string{`unmount "target" UMOUNT_NOFOLLOW`}) + c.Assert(synth, HasLen, 0) } // Change.Perform returns errors from unmount system call func (s *changeSuite) TestPerformUnountError(c *C) { s.sys.InsertFault(`unmount "target" UMOUNT_NOFOLLOW`, errTesting) chg := &update.Change{Action: update.Unmount, Entry: mount.Entry{Name: "source", Dir: "target", Type: "type"}} - c.Assert(chg.Perform(), Equals, errTesting) + synth, err := chg.Perform() + c.Assert(err, Equals, errTesting) c.Assert(s.sys.Calls(), DeepEquals, []string{`unmount "target" UMOUNT_NOFOLLOW`}) + c.Assert(synth, HasLen, 0) } // Change.Perform handles unknown actions. func (s *changeSuite) TestPerformUnknownAction(c *C) { chg := &update.Change{Action: update.Action(42)} - c.Assert(chg.Perform(), ErrorMatches, `cannot process mount change, unknown action: .*`) + synth, err := chg.Perform() + c.Assert(err, ErrorMatches, `cannot process mount change: unknown action: .*`) + c.Assert(synth, HasLen, 0) c.Assert(s.sys.Calls(), HasLen, 0) } diff -Nru snapd-2.29.4.2+17.10/cmd/snap-update-ns/entry.go snapd-2.31.1+17.10/cmd/snap-update-ns/entry.go --- snapd-2.29.4.2+17.10/cmd/snap-update-ns/entry.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-update-ns/entry.go 2017-12-01 15:51:55.000000000 +0000 @@ -0,0 +1,129 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package main + +import ( + "fmt" + "math" + "os" + "regexp" + + "github.com/snapcore/snapd/interfaces/mount" + "github.com/snapcore/snapd/osutil" +) + +var ( + validModeRe = regexp.MustCompile("^0[0-7]{3}$") + validUserGroupRe = regexp.MustCompile("(^[0-9]+$)|(^[a-z_][a-z0-9_-]*[$]?$)") +) + +// XSnapdMode returns the file mode associated with x-snapd.mode mount option. +// If the mode is not specified explicitly then a default mode of 0755 is assumed. +func XSnapdMode(e *mount.Entry) (os.FileMode, error) { + if opt, ok := e.OptStr("x-snapd.mode"); ok { + if !validModeRe.MatchString(opt) { + return 0, fmt.Errorf("cannot parse octal file mode from %q", opt) + } + var mode os.FileMode + n, err := fmt.Sscanf(opt, "%o", &mode) + if err != nil || n != 1 { + return 0, fmt.Errorf("cannot parse octal file mode from %q", opt) + } + return mode, nil + } + return 0755, nil +} + +// XSnapdUID returns the user associated with x-snapd-user mount option. If +// the mode is not specified explicitly then a default "root" use is +// returned. +func XSnapdUID(e *mount.Entry) (uid uint64, err error) { + if opt, ok := e.OptStr("x-snapd.uid"); ok { + if !validUserGroupRe.MatchString(opt) { + return math.MaxUint64, fmt.Errorf("cannot parse user name %q", opt) + } + // Try to parse a numeric ID first. + if n, err := fmt.Sscanf(opt, "%d", &uid); n == 1 && err == nil { + return uid, nil + } + // Fall-back to system name lookup. + if uid, err = osutil.FindUid(opt); err != nil { + // The error message from FindUid is not very useful so just skip it. + return math.MaxUint64, fmt.Errorf("cannot resolve user name %q", opt) + } + return uid, nil + } + return 0, nil +} + +// XSnapdGID returns the user associated with x-snapd-user mount option. If +// the mode is not specified explicitly then a default "root" use is +// returned. +func XSnapdGID(e *mount.Entry) (gid uint64, err error) { + if opt, ok := e.OptStr("x-snapd.gid"); ok { + if !validUserGroupRe.MatchString(opt) { + return math.MaxUint64, fmt.Errorf("cannot parse group name %q", opt) + } + // Try to parse a numeric ID first. + if n, err := fmt.Sscanf(opt, "%d", &gid); n == 1 && err == nil { + return gid, nil + } + // Fall-back to system name lookup. + if gid, err = osutil.FindGid(opt); err != nil { + // The error message from FindGid is not very useful so just skip it. + return math.MaxUint64, fmt.Errorf("cannot resolve group name %q", opt) + } + return gid, nil + } + return 0, nil +} + +// XSnapdEntryID returns the identifier of a given mount enrty. +// +// Identifiers are kept in the x-snapd.id mount option. The value is a string +// that identifies a mount entry and is stable across invocations of snapd. In +// absence of that identifier the entry mount point is returned. +func XSnapdEntryID(e *mount.Entry) string { + if val, ok := e.OptStr("x-snapd.id"); ok { + return val + } + return e.Dir +} + +// XSnapdNeededBy the identifier of an entry which needs this entry to function. +// +// The "needed by" identifiers are kept in the x-snapd.needed-by mount option. +// The value is a string that identifies another mount entry which, in order to +// be feasible, has spawned one or more additional support entries. Each such +// entry contains the needed-by attribute. +func XSnapdNeededBy(e *mount.Entry) string { + val, _ := e.OptStr("x-snapd.needed-by") + return val +} + +// XSnapdSynthetic returns true of a given mount entry is synthetic. +// +// Synthetic mount entries are created by snap-update-ns itself, separately +// from what snapd instructed. Such entries are needed to make other things +// possible. They are identified by having the "x-snapd.synthetic" mount +// option. +func XSnapdSynthetic(e *mount.Entry) bool { + return e.OptBool("x-snapd.synthetic") +} diff -Nru snapd-2.29.4.2+17.10/cmd/snap-update-ns/entry_test.go snapd-2.31.1+17.10/cmd/snap-update-ns/entry_test.go --- snapd-2.29.4.2+17.10/cmd/snap-update-ns/entry_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-update-ns/entry_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,157 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package main_test + +import ( + "math" + "os" + + . "gopkg.in/check.v1" + + update "github.com/snapcore/snapd/cmd/snap-update-ns" + "github.com/snapcore/snapd/interfaces/mount" +) + +type entrySuite struct{} + +var _ = Suite(&entrySuite{}) + +func (s *entrySuite) TestXSnapdMode(c *C) { + // Mode has a default value. + e := &mount.Entry{} + mode, err := update.XSnapdMode(e) + c.Assert(err, IsNil) + c.Assert(mode, Equals, os.FileMode(0755)) + + // Mode is parsed from the x-snapd.mode= option. + e = &mount.Entry{Options: []string{"x-snapd.mode=0700"}} + mode, err = update.XSnapdMode(e) + c.Assert(err, IsNil) + c.Assert(mode, Equals, os.FileMode(0700)) + + // Empty value is invalid. + e = &mount.Entry{Options: []string{"x-snapd.mode="}} + _, err = update.XSnapdMode(e) + c.Assert(err, ErrorMatches, `cannot parse octal file mode from ""`) + + // As well as other bogus values. + e = &mount.Entry{Options: []string{"x-snapd.mode=pasta"}} + _, err = update.XSnapdMode(e) + c.Assert(err, ErrorMatches, `cannot parse octal file mode from "pasta"`) + + // And even valid values with trailing garbage. + e = &mount.Entry{Options: []string{"x-snapd.mode=0700pasta"}} + mode, err = update.XSnapdMode(e) + c.Assert(err, ErrorMatches, `cannot parse octal file mode from "0700pasta"`) + c.Assert(mode, Equals, os.FileMode(0)) +} + +func (s *entrySuite) TestXSnapdUID(c *C) { + // User has a default value. + e := &mount.Entry{} + uid, err := update.XSnapdUID(e) + c.Assert(err, IsNil) + c.Assert(uid, Equals, uint64(0)) + + // User is parsed from the x-snapd.uid= option. + e = &mount.Entry{Options: []string{"x-snapd.uid=root"}} + uid, err = update.XSnapdUID(e) + c.Assert(err, IsNil) + c.Assert(uid, Equals, uint64(0)) + + // Numeric names are used as-is. + e = &mount.Entry{Options: []string{"x-snapd.uid=123"}} + uid, err = update.XSnapdUID(e) + c.Assert(err, IsNil) + c.Assert(uid, Equals, uint64(123)) + + // Unknown user names are invalid. + e = &mount.Entry{Options: []string{"x-snapd.uid=bogus"}} + uid, err = update.XSnapdUID(e) + c.Assert(err, ErrorMatches, `cannot resolve user name "bogus"`) + c.Assert(uid, Equals, uint64(math.MaxUint64)) + + // And even valid values with trailing garbage. + e = &mount.Entry{Options: []string{"x-snapd.uid=0bogus"}} + uid, err = update.XSnapdUID(e) + c.Assert(err, ErrorMatches, `cannot parse user name "0bogus"`) + c.Assert(uid, Equals, uint64(math.MaxUint64)) +} + +func (s *entrySuite) TestXSnapdGID(c *C) { + // Group has a default value. + e := &mount.Entry{} + gid, err := update.XSnapdGID(e) + c.Assert(err, IsNil) + c.Assert(gid, Equals, uint64(0)) + + e = &mount.Entry{Options: []string{"x-snapd.gid=root"}} + gid, err = update.XSnapdGID(e) + c.Assert(err, IsNil) + c.Assert(gid, Equals, uint64(0)) + + // Numeric names are used as-is. + e = &mount.Entry{Options: []string{"x-snapd.gid=456"}} + gid, err = update.XSnapdGID(e) + c.Assert(err, IsNil) + c.Assert(gid, Equals, uint64(456)) + + // Unknown group names are invalid. + e = &mount.Entry{Options: []string{"x-snapd.gid=bogus"}} + gid, err = update.XSnapdGID(e) + c.Assert(err, ErrorMatches, `cannot resolve group name "bogus"`) + c.Assert(gid, Equals, uint64(math.MaxUint64)) + + // And even valid values with trailing garbage. + e = &mount.Entry{Options: []string{"x-snapd.gid=0bogus"}} + gid, err = update.XSnapdGID(e) + c.Assert(err, ErrorMatches, `cannot parse group name "0bogus"`) + c.Assert(gid, Equals, uint64(math.MaxUint64)) +} + +func (s *entrySuite) TestXSnapdEntryID(c *C) { + // Entry ID is optional and defaults to the mount point. + e := &mount.Entry{Dir: "/foo"} + c.Assert(update.XSnapdEntryID(e), Equals, "/foo") + + // Entry ID is parsed from the x-snapd.id= option. + e = &mount.Entry{Dir: "/foo", Options: []string{"x-snapd.id=foo"}} + c.Assert(update.XSnapdEntryID(e), Equals, "foo") +} + +func (s *entrySuite) TestXSnapdNeededBy(c *C) { + // The needed-by attribute is optional. + e := &mount.Entry{} + c.Assert(update.XSnapdNeededBy(e), Equals, "") + + // The needed-by attribute parsed from the x-snapd.needed-by= option. + e = &mount.Entry{Options: []string{"x-snap.id=foo", "x-snapd.needed-by=bar"}} + c.Assert(update.XSnapdNeededBy(e), Equals, "bar") +} + +func (s *entrySuite) TestXSnapdSynthetic(c *C) { + // Entries are not synthetic unless tagged as such. + e := &mount.Entry{} + c.Assert(update.XSnapdSynthetic(e), Equals, false) + + // Tagging is done with x-snapd.synthetic option. + e = &mount.Entry{Options: []string{"x-snapd.synthetic"}} + c.Assert(update.XSnapdSynthetic(e), Equals, true) +} diff -Nru snapd-2.29.4.2+17.10/cmd/snap-update-ns/export_test.go snapd-2.31.1+17.10/cmd/snap-update-ns/export_test.go --- snapd-2.29.4.2+17.10/cmd/snap-update-ns/export_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-update-ns/export_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -21,34 +21,184 @@ import ( "fmt" + "os" + "strings" + "syscall" + "time" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/osutil/sys" ) var ( - ReadCmdline = readCmdline - FindSnapName = findSnapName - FindFirstOption = findFirstOption + // change ValidateSnapName = validateSnapName ProcessArguments = processArguments + // freezer + FreezeSnapProcesses = freezeSnapProcesses + ThawSnapProcesses = thawSnapProcesses + // utils + PlanWritableMimic = planWritableMimic + ExecWritableMimic = execWritableMimic + SecureMkdirAll = secureMkdirAll + SecureMkfileAll = secureMkfileAll + SplitIntoSegments = splitIntoSegments + + // main + ComputeAndSaveChanges = computeAndSaveChanges +) + +// fakeFileInfo implements os.FileInfo for one of the tests. +// Most of the functions panic as we don't expect them to be called. +type fakeFileInfo struct { + name string + mode os.FileMode +} + +func (fi *fakeFileInfo) Name() string { return fi.name } +func (*fakeFileInfo) Size() int64 { panic("unexpected call") } +func (fi *fakeFileInfo) Mode() os.FileMode { return fi.mode } +func (*fakeFileInfo) ModTime() time.Time { panic("unexpected call") } +func (fi *fakeFileInfo) IsDir() bool { return fi.Mode().IsDir() } +func (*fakeFileInfo) Sys() interface{} { panic("unexpected call") } + +// Fake FileInfo objects for InsertLstatResult +var ( + FileInfoFile = &fakeFileInfo{} + FileInfoDir = &fakeFileInfo{mode: os.ModeDir} + FileInfoSymlink = &fakeFileInfo{mode: os.ModeSymlink} ) +func FakeFileInfo(name string, mode os.FileMode) os.FileInfo { + return &fakeFileInfo{name: name, mode: mode} +} + +// Formatter for flags passed to open syscall. +func formatOpenFlags(flags int) string { + var fl []string + if flags&syscall.O_NOFOLLOW != 0 { + flags ^= syscall.O_NOFOLLOW + fl = append(fl, "O_NOFOLLOW") + } + if flags&syscall.O_CLOEXEC != 0 { + flags ^= syscall.O_CLOEXEC + fl = append(fl, "O_CLOEXEC") + } + if flags&syscall.O_DIRECTORY != 0 { + flags ^= syscall.O_DIRECTORY + fl = append(fl, "O_DIRECTORY") + } + if flags&syscall.O_RDWR != 0 { + flags ^= syscall.O_RDWR + fl = append(fl, "O_RDWR") + } + if flags&syscall.O_CREAT != 0 { + flags ^= syscall.O_CREAT + fl = append(fl, "O_CREAT") + } + if flags&syscall.O_EXCL != 0 { + flags ^= syscall.O_EXCL + fl = append(fl, "O_EXCL") + } + if flags != 0 { + panic(fmt.Errorf("unrecognized open flags %d", flags)) + } + if len(fl) == 0 { + return "0" + } + return strings.Join(fl, "|") +} + +// Formatter for flags passed to mount syscall. +func formatMountFlags(flags int) string { + var fl []string + if flags&syscall.MS_BIND == syscall.MS_BIND { + flags ^= syscall.MS_BIND + fl = append(fl, "MS_BIND") + } + if flags&syscall.MS_RDONLY == syscall.MS_RDONLY { + flags ^= syscall.MS_RDONLY + fl = append(fl, "MS_RDONLY") + } + if flags != 0 { + panic(fmt.Errorf("unrecognized mount flags %d", flags)) + } + if len(fl) == 0 { + return "0" + } + return strings.Join(fl, "|") +} + // SystemCalls encapsulates various system interactions performed by this module. type SystemCalls interface { + Lstat(name string) (os.FileInfo, error) + ReadDir(dirname string) ([]os.FileInfo, error) + Symlink(oldname, newname string) error + Remove(name string) error + + Close(fd int) error + Fchown(fd int, uid sys.UserID, gid sys.GroupID) error + Mkdirat(dirfd int, path string, mode uint32) error Mount(source string, target string, fstype string, flags uintptr, data string) (err error) - Unmount(target string, flags int) (err error) + Open(path string, flags int, mode uint32) (fd int, err error) + Openat(dirfd int, path string, flags int, mode uint32) (fd int, err error) + Unmount(target string, flags int) error } // SyscallRecorder stores which system calls were invoked. type SyscallRecorder struct { - calls []string - errors map[string]error + calls []string + errors map[string]func() error + lstats map[string]os.FileInfo + readdirs map[string][]os.FileInfo + fds map[int]string } // InsertFault makes given subsequent call to return the specified error. -func (sys *SyscallRecorder) InsertFault(call string, err error) { +func (sys *SyscallRecorder) InsertFault(call string, errors ...error) { + if sys.errors == nil { + sys.errors = make(map[string]func() error) + } + if len(errors) == 1 { + // deterministic error + sys.errors[call] = func() error { + return errors[0] + } + } else { + // error sequence + sys.errors[call] = func() error { + if len(errors) > 0 { + err := errors[0] + errors = errors[1:] + return err + } + return nil + } + } +} + +func (sys *SyscallRecorder) InsertFaultFunc(call string, fn func() error) { if sys.errors == nil { - sys.errors = make(map[string]error) + sys.errors = make(map[string]func() error) + } + sys.errors[call] = fn +} + +// InsertLstatResult makes given subsequent call lstat return the specified fake file info. +func (sys *SyscallRecorder) InsertLstatResult(call string, fi os.FileInfo) { + if sys.lstats == nil { + sys.lstats = make(map[string]os.FileInfo) } - sys.errors[call] = err + sys.lstats[call] = fi +} + +// InsertReadDirResult makes given subsequent call readdir return the specified fake file infos. +func (sys *SyscallRecorder) InsertReadDirResult(call string, infos []os.FileInfo) { + if sys.readdirs == nil { + sys.readdirs = make(map[string][]os.FileInfo) + } + sys.readdirs[call] = infos } // Calls returns the sequence of mocked calls that have been made. @@ -59,30 +209,196 @@ // call remembers that a given call has occurred and returns a pre-arranged error, if any func (sys *SyscallRecorder) call(call string) error { sys.calls = append(sys.calls, call) - return sys.errors[call] + if fn := sys.errors[call]; fn != nil { + return fn() + } + return nil +} + +// allocFd assigns a file descriptor to a given operation. +func (sys *SyscallRecorder) allocFd(name string) int { + if sys.fds == nil { + sys.fds = make(map[int]string) + } + + // Use 3 as the lowest number for tests to look more plausible. + for i := 3; i < 100; i++ { + if _, ok := sys.fds[i]; !ok { + sys.fds[i] = name + return i + } + } + panic("cannot find unused file descriptor") +} + +// freeFd closes an open file descriptor. +func (sys *SyscallRecorder) freeFd(fd int) error { + if _, ok := sys.fds[fd]; !ok { + return fmt.Errorf("attempting to close closed file descriptor %d", fd) + } + delete(sys.fds, fd) + return nil +} + +func (sys *SyscallRecorder) CheckForStrayDescriptors(c *C) { + for fd, ok := range sys.fds { + c.Assert(ok, Equals, false, Commentf("unclosed file descriptor %d", fd)) + } +} + +func (sys *SyscallRecorder) Close(fd int) error { + if err := sys.call(fmt.Sprintf("close %d", fd)); err != nil { + return err + } + return sys.freeFd(fd) +} + +func (sys *SyscallRecorder) Fchown(fd int, uid sys.UserID, gid sys.GroupID) error { + return sys.call(fmt.Sprintf("fchown %d %d %d", fd, uid, gid)) +} + +func (sys *SyscallRecorder) Mkdirat(dirfd int, path string, mode uint32) error { + return sys.call(fmt.Sprintf("mkdirat %d %q %#o", dirfd, path, mode)) +} + +func (sys *SyscallRecorder) Open(path string, flags int, mode uint32) (int, error) { + call := fmt.Sprintf("open %q %s %#o", path, formatOpenFlags(flags), mode) + if err := sys.call(call); err != nil { + return -1, err + } + return sys.allocFd(call), nil +} + +func (sys *SyscallRecorder) Openat(dirfd int, path string, flags int, mode uint32) (int, error) { + call := fmt.Sprintf("openat %d %q %s %#o", dirfd, path, formatOpenFlags(flags), mode) + if err := sys.call(call); err != nil { + return -1, err + } + return sys.allocFd(call), nil } func (sys *SyscallRecorder) Mount(source string, target string, fstype string, flags uintptr, data string) (err error) { - return sys.call(fmt.Sprintf("mount %q %q %q %d %q", source, target, fstype, flags, data)) + return sys.call(fmt.Sprintf("mount %q %q %q %s %q", source, target, fstype, formatMountFlags(int(flags)), data)) } func (sys *SyscallRecorder) Unmount(target string, flags int) (err error) { - if flags == unmountNoFollow { + if flags == umountNoFollow { return sys.call(fmt.Sprintf("unmount %q %s", target, "UMOUNT_NOFOLLOW")) } return sys.call(fmt.Sprintf("unmount %q %d", target, flags)) } +func (sys *SyscallRecorder) Lstat(name string) (os.FileInfo, error) { + call := fmt.Sprintf("lstat %q", name) + if err := sys.call(call); err != nil { + return nil, err + } + if fi, ok := sys.lstats[call]; ok { + return fi, nil + } + panic(fmt.Sprintf("one of InsertLstatResult() or InsertFault() for %q must be used", call)) +} + +func (sys *SyscallRecorder) ReadDir(dirname string) ([]os.FileInfo, error) { + call := fmt.Sprintf("readdir %q", dirname) + if err := sys.call(call); err != nil { + return nil, err + } + if fi, ok := sys.readdirs[call]; ok { + return fi, nil + } + panic(fmt.Sprintf("one of InsertReadDirResult() or InsertFault() for %q must be used", call)) +} + +func (sys *SyscallRecorder) Symlink(oldname, newname string) error { + call := fmt.Sprintf("symlink %q -> %q", newname, oldname) + return sys.call(call) +} + +func (sys *SyscallRecorder) Remove(name string) error { + call := fmt.Sprintf("remove %q", name) + return sys.call(call) +} + // MockSystemCalls replaces real system calls with those of the argument. func MockSystemCalls(sc SystemCalls) (restore func()) { + // save + oldOsLstat := osLstat + oldSymlink := osSymlink + oldRemove := osRemove + oldIoutilReadDir := ioutilReadDir + + oldSysClose := sysClose + oldSysFchown := sysFchown + oldSysMkdirat := sysMkdirat oldSysMount := sysMount + oldSysOpen := sysOpen + oldSysOpenat := sysOpenat oldSysUnmount := sysUnmount + // override + osLstat = sc.Lstat + osSymlink = sc.Symlink + osRemove = sc.Remove + ioutilReadDir = sc.ReadDir + + sysClose = sc.Close + sysFchown = sc.Fchown + sysMkdirat = sc.Mkdirat sysMount = sc.Mount + sysOpen = sc.Open + sysOpenat = sc.Openat sysUnmount = sc.Unmount return func() { + // restore + osLstat = oldOsLstat + osSymlink = oldSymlink + osRemove = oldRemove + ioutilReadDir = oldIoutilReadDir + + sysClose = oldSysClose + sysFchown = oldSysFchown + sysMkdirat = oldSysMkdirat sysMount = oldSysMount + sysOpen = oldSysOpen + sysOpenat = oldSysOpenat sysUnmount = oldSysUnmount } } + +func MockFreezerCgroupDir(c *C) (restore func()) { + old := freezerCgroupDir + freezerCgroupDir = c.MkDir() + return func() { + freezerCgroupDir = old + } +} + +func FreezerCgroupDir() string { + return freezerCgroupDir +} + +func MockChangePerform(f func(chg *Change) ([]*Change, error)) func() { + origChangePerform := changePerform + changePerform = f + return func() { + changePerform = origChangePerform + } +} + +func MockReadDir(fn func(string) ([]os.FileInfo, error)) (restore func()) { + old := ioutilReadDir + ioutilReadDir = fn + return func() { + ioutilReadDir = old + } +} + +func MockReadlink(fn func(string) (string, error)) (restore func()) { + old := osReadlink + osReadlink = fn + return func() { + osReadlink = old + } +} diff -Nru snapd-2.29.4.2+17.10/cmd/snap-update-ns/freezer.go snapd-2.31.1+17.10/cmd/snap-update-ns/freezer.go --- snapd-2.29.4.2+17.10/cmd/snap-update-ns/freezer.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-update-ns/freezer.go 2017-12-01 15:51:55.000000000 +0000 @@ -0,0 +1,74 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "time" +) + +var freezerCgroupDir = "/sys/fs/cgroup/freezer" + +// freezeSnapProcesses freezes all the processes originating from the given snap. +// Processes are frozen regardless of which particular snap application they +// originate from. +func freezeSnapProcesses(snapName string) error { + fname := filepath.Join(freezerCgroupDir, fmt.Sprintf("snap.%s", snapName), "freezer.state") + if err := ioutil.WriteFile(fname, []byte("FROZEN"), 0644); err != nil && os.IsNotExist(err) { + // When there's no freezer cgroup we don't have to freeze anything. + // This can happen when no process belonging to a given snap has been + // started yet. + return nil + } else if err != nil { + return fmt.Errorf("cannot freeze processes of snap %q, %v", snapName, err) + } + for i := 0; i < 30; i++ { + data, err := ioutil.ReadFile(fname) + if err != nil { + return fmt.Errorf("cannot determine the freeze state of processes of snap %q, %v", snapName, err) + } + // If the cgroup is still freezing then wait a moment and try again. + if bytes.Equal(data, []byte("FREEZING")) { + time.Sleep(100 * time.Millisecond) + continue + } + return nil + } + // If we got here then we timed out after seeing FREEZING for too long. + thawSnapProcesses(snapName) // ignore the error, this is best-effort. + return fmt.Errorf("cannot finish freezing processes of snap %q", snapName) +} + +func thawSnapProcesses(snapName string) error { + fname := filepath.Join(freezerCgroupDir, fmt.Sprintf("snap.%s", snapName), "freezer.state") + if err := ioutil.WriteFile(fname, []byte("THAWED"), 0644); err != nil && os.IsNotExist(err) { + // When there's no freezer cgroup we don't have to thaw anything. + // This can happen when no process belonging to a given snap has been + // started yet. + return nil + } else if err != nil { + return fmt.Errorf("cannot thaw processes of snap %q", snapName) + } + return nil +} diff -Nru snapd-2.29.4.2+17.10/cmd/snap-update-ns/freezer_test.go snapd-2.31.1+17.10/cmd/snap-update-ns/freezer_test.go --- snapd-2.29.4.2+17.10/cmd/snap-update-ns/freezer_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-update-ns/freezer_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -0,0 +1,95 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package main_test + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + + . "gopkg.in/check.v1" + + update "github.com/snapcore/snapd/cmd/snap-update-ns" +) + +type freezerSuite struct{} + +var _ = Suite(&freezerSuite{}) + +func (s *freezerSuite) TestFreezeSnapProcesses(c *C) { + restore := update.MockFreezerCgroupDir(c) + defer restore() + + n := "foo" // snap name + p := filepath.Join(update.FreezerCgroupDir(), fmt.Sprintf("snap.%s", n)) // snap freezer cgroup + f := filepath.Join(p, "freezer.state") // freezer.state file of the cgroup + + // When the freezer cgroup filesystem doesn't exist we do nothing at all. + c.Assert(update.FreezeSnapProcesses(n), IsNil) + _, err := os.Stat(f) + c.Assert(os.IsNotExist(err), Equals, true) + + // When the freezer cgroup filesystem exists but the particular cgroup + // doesn't exist we don nothing at all. + c.Assert(os.MkdirAll(update.FreezerCgroupDir(), 0755), IsNil) + c.Assert(update.FreezeSnapProcesses(n), IsNil) + _, err = os.Stat(f) + c.Assert(os.IsNotExist(err), Equals, true) + + // When the cgroup exists we write FROZEN the freezer.state file. + c.Assert(os.MkdirAll(p, 0755), IsNil) + c.Assert(update.FreezeSnapProcesses(n), IsNil) + _, err = os.Stat(f) + c.Assert(err, IsNil) + data, err := ioutil.ReadFile(f) + c.Assert(err, IsNil) + c.Assert(data, DeepEquals, []byte(`FROZEN`)) +} + +func (s *freezerSuite) TestThawSnapProcesses(c *C) { + restore := update.MockFreezerCgroupDir(c) + defer restore() + + n := "foo" // snap name + p := filepath.Join(update.FreezerCgroupDir(), fmt.Sprintf("snap.%s", n)) // snap freezer cgroup + f := filepath.Join(p, "freezer.state") // freezer.state file of the cgroup + + // When the freezer cgroup filesystem doesn't exist we do nothing at all. + c.Assert(update.ThawSnapProcesses(n), IsNil) + _, err := os.Stat(f) + c.Assert(os.IsNotExist(err), Equals, true) + + // When the freezer cgroup filesystem exists but the particular cgroup + // doesn't exist we don nothing at all. + c.Assert(os.MkdirAll(update.FreezerCgroupDir(), 0755), IsNil) + c.Assert(update.ThawSnapProcesses(n), IsNil) + _, err = os.Stat(f) + c.Assert(os.IsNotExist(err), Equals, true) + + // When the cgroup exists we write THAWED the freezer.state file. + c.Assert(os.MkdirAll(p, 0755), IsNil) + c.Assert(update.ThawSnapProcesses(n), IsNil) + _, err = os.Stat(f) + c.Assert(err, IsNil) + data, err := ioutil.ReadFile(f) + c.Assert(err, IsNil) + c.Assert(data, DeepEquals, []byte(`THAWED`)) +} diff -Nru snapd-2.29.4.2+17.10/cmd/snap-update-ns/main.go snapd-2.31.1+17.10/cmd/snap-update-ns/main.go --- snapd-2.29.4.2+17.10/cmd/snap-update-ns/main.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-update-ns/main.go 2017-12-01 15:51:55.000000000 +0000 @@ -69,6 +69,7 @@ // If there is no mount namespace to transition to let's just quit // instantly without any errors as there is nothing to do anymore. if err == ErrNoNamespace { + logger.Debugf("no preserved mount namespace, nothing to update") return nil } return err @@ -87,10 +88,14 @@ if err != nil { return fmt.Errorf("cannot open lock file for mount namespace of snap %q: %s", snapName, err) } - defer lock.Close() + defer func() { + logger.Debugf("unlocking mount namespace of snap %q", snapName) + lock.Close() + }() + logger.Debugf("locking mount namespace of snap %q", snapName) if opts.FromSnapConfine { - // When --from-snap-conifne is passed then we just ensure that the + // When --from-snap-confine is passed then we just ensure that the // namespace is locked. This is used by snap-confine to use // snap-update-ns to apply mount profiles. if err := lock.TryLock(); err != osutil.ErrAlreadyLocked { @@ -102,6 +107,24 @@ } } + // Freeze the mount namespace and unfreeze it later. This lets us perform + // modifications without snap processes attempting to construct + // symlinks or perform other malicious activity (such as attempting to + // introduce a symlink that would cause us to mount something other + // than what we expected). + logger.Debugf("freezing processes of snap %q", snapName) + if err := freezeSnapProcesses(opts.Positionals.SnapName); err != nil { + return err + } + defer func() { + logger.Debugf("thawing processes of snap %q", snapName) + thawSnapProcesses(opts.Positionals.SnapName) + }() + + return computeAndSaveChanges(snapName) +} + +func computeAndSaveChanges(snapName string) error { // Read the desired and current mount profiles. Note that missing files // count as empty profiles so that we can gracefully handle a mount // interface connection/disconnection. @@ -110,23 +133,35 @@ if err != nil { return fmt.Errorf("cannot load desired mount profile of snap %q: %s", snapName, err) } + debugShowProfile(desired, "desired mount profile") currentProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapRunNsDir, snapName) currentBefore, err := mount.LoadProfile(currentProfilePath) if err != nil { return fmt.Errorf("cannot load current mount profile of snap %q: %s", snapName, err) } + debugShowProfile(currentBefore, "current mount profile (before applying changes)") // Compute the needed changes and perform each change if needed, collecting // those that we managed to perform or that were performed already. changesNeeded := NeededChanges(currentBefore, desired) + debugShowChanges(changesNeeded, "mount changes needed") + + logger.Debugf("performing mount changes:") var changesMade []*Change for _, change := range changesNeeded { - if change.Action == Keep { - changesMade = append(changesMade, change) - continue + logger.Debugf("\t * %s", change) + synthesised, err := changePerform(change) + // NOTE: we may have done something even if Perform itself has failed. + // We need to collect synthesized changes and store them. + changesMade = append(changesMade, synthesised...) + if len(synthesised) > 0 { + logger.Debugf("\tsynthesised additional mount changes:") + for _, synth := range synthesised { + logger.Debugf(" * \t\t%s", synth) + } } - if err := change.Perform(); err != nil { + if err != nil { logger.Noticef("cannot change mount namespace of snap %q according to change %s: %s", snapName, change, err) continue } @@ -141,8 +176,33 @@ currentAfter.Entries = append(currentAfter.Entries, change.Entry) } } + debugShowProfile(¤tAfter, "current mount profile (after applying changes)") + + logger.Debugf("saving current mount profile of snap %q", snapName) if err := currentAfter.Save(currentProfilePath); err != nil { return fmt.Errorf("cannot save current mount profile of snap %q: %s", snapName, err) } return nil } + +func debugShowProfile(profile *mount.Profile, header string) { + if len(profile.Entries) > 0 { + logger.Debugf("%s:", header) + for _, entry := range profile.Entries { + logger.Debugf("\t%s", entry) + } + } else { + logger.Debugf("%s: (none)", header) + } +} + +func debugShowChanges(changes []*Change, header string) { + if len(changes) > 0 { + logger.Debugf("%s:", header) + for _, change := range changes { + logger.Debugf("\t%s", change) + } + } else { + logger.Debugf("%s: (none)", header) + } +} diff -Nru snapd-2.29.4.2+17.10/cmd/snap-update-ns/main_test.go snapd-2.31.1+17.10/cmd/snap-update-ns/main_test.go --- snapd-2.29.4.2+17.10/cmd/snap-update-ns/main_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-update-ns/main_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -17,16 +17,204 @@ * */ -package main +package main_test import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" "testing" . "gopkg.in/check.v1" + + update "github.com/snapcore/snapd/cmd/snap-update-ns" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/interfaces/mount" ) func Test(t *testing.T) { TestingT(t) } -type snapUpdateNsSuite struct{} +type mainSuite struct{} + +var _ = Suite(&mainSuite{}) -var _ = Suite(&snapUpdateNsSuite{}) +func (s *mainSuite) TestComputeAndSaveChanges(c *C) { + dirs.SetRootDir(c.MkDir()) + defer dirs.SetRootDir("/") + + restore := update.MockChangePerform(func(chg *update.Change) ([]*update.Change, error) { + return nil, nil + }) + defer restore() + + snapName := "foo" + desiredProfileContent := `/var/lib/snapd/hostfs/usr/share/fonts /usr/share/fonts none bind,ro 0 0 +/var/lib/snapd/hostfs/usr/local/share/fonts /usr/local/share/fonts none bind,ro 0 0` + + desiredProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapMountPolicyDir, snapName) + err := os.MkdirAll(filepath.Dir(desiredProfilePath), 0755) + c.Assert(err, IsNil) + err = ioutil.WriteFile(desiredProfilePath, []byte(desiredProfileContent), 0644) + c.Assert(err, IsNil) + + currentProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapRunNsDir, snapName) + err = os.MkdirAll(filepath.Dir(currentProfilePath), 0755) + c.Assert(err, IsNil) + err = ioutil.WriteFile(currentProfilePath, nil, 0644) + c.Assert(err, IsNil) + + err = update.ComputeAndSaveChanges(snapName) + c.Assert(err, IsNil) + + content, err := ioutil.ReadFile(currentProfilePath) + c.Assert(err, IsNil) + c.Check(string(content), Equals, `/var/lib/snapd/hostfs/usr/local/share/fonts /usr/local/share/fonts none bind,ro 0 0 +/var/lib/snapd/hostfs/usr/share/fonts /usr/share/fonts none bind,ro 0 0 +`) +} + +func (s *mainSuite) TestAddingSyntheticChanges(c *C) { + dirs.SetRootDir(c.MkDir()) + defer dirs.SetRootDir("/") + + // The snap `mysnap` wishes to export it's usr/share/mysnap directory and + // make it appear as if it was in /usr/share/mysnap directly. + const snapName = "mysnap" + const currentProfileContent = "" + const desiredProfileContent = "/snap/mysnap/42/usr/share/mysnap /usr/share/mysnap none bind,ro 0 0" + + currentProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapRunNsDir, snapName) + desiredProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapMountPolicyDir, snapName) + + c.Assert(os.MkdirAll(filepath.Dir(currentProfilePath), 0755), IsNil) + c.Assert(os.MkdirAll(filepath.Dir(desiredProfilePath), 0755), IsNil) + c.Assert(ioutil.WriteFile(currentProfilePath, []byte(currentProfileContent), 0644), IsNil) + c.Assert(ioutil.WriteFile(desiredProfilePath, []byte(desiredProfileContent), 0644), IsNil) + + // In order to make that work, /usr/share had to be converted to a writable + // mimic. Some actions were performed under the hood and now we see a + // subset of them as synthetic changes here. + // + // Note that if you compare this to the code that plans a writable mimic + // you will see that there are additional changes that are _not_ + // represented here. The changes have only one goal: tell + // snap-update-ns how the mimic can be undone in case it is no longer + // needed. + restore := update.MockChangePerform(func(chg *update.Change) ([]*update.Change, error) { + // The change that we were asked to perform is to create a bind mount + // from within the snap to /usr/share/mysnap. + c.Assert(chg, DeepEquals, &update.Change{ + Action: update.Mount, Entry: mount.Entry{ + Name: "/snap/mysnap/42/usr/share/mysnap", + Dir: "/usr/share/mysnap", Type: "none", + Options: []string{"bind", "ro"}}}) + synthetic := []*update.Change{ + // The original directory (which was a part of the core snap and is + // read only) was hidden with a tmpfs. + {Action: update.Mount, Entry: mount.Entry{ + Dir: "/usr/share", Name: "tmpfs", Type: "tmpfs", + Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/usr/share/mysnap"}}}, + // For the sake of brevity we will only represent a few of the + // entries typically there. Normally this list can get quite long. + // Also note that the entry is a little fake. In reality it was + // constructed using a temporary bind mount that contained the + // original mount entries of /usr/share but this fact was lost. + // Again, the only point of this entry is to correctly perform an + // undo operation when /usr/share/mysnap is no longer needed. + {Action: update.Mount, Entry: mount.Entry{ + Dir: "/usr/share/adduser", Name: "/usr/share/adduser", + Options: []string{"bind", "ro", "x-snapd.synthetic", "x-snapd.needed-by=/usr/share/mysnap"}}}, + {Action: update.Mount, Entry: mount.Entry{ + Dir: "/usr/share/awk", Name: "/usr/share/awk", + Options: []string{"bind", "ro", "x-snapd.synthetic", "x-snapd.needed-by=/usr/share/mysnap"}}}, + } + return synthetic, nil + }) + defer restore() + + c.Assert(update.ComputeAndSaveChanges(snapName), IsNil) + + content, err := ioutil.ReadFile(currentProfilePath) + c.Assert(err, IsNil) + c.Check(string(content), Equals, + `tmpfs /usr/share tmpfs x-snapd.synthetic,x-snapd.needed-by=/usr/share/mysnap 0 0 +/usr/share/adduser /usr/share/adduser none bind,ro,x-snapd.synthetic,x-snapd.needed-by=/usr/share/mysnap 0 0 +/usr/share/awk /usr/share/awk none bind,ro,x-snapd.synthetic,x-snapd.needed-by=/usr/share/mysnap 0 0 +/snap/mysnap/42/usr/share/mysnap /usr/share/mysnap none bind,ro 0 0 +`) +} + +func (s *mainSuite) TestRemovingSyntheticChanges(c *C) { + dirs.SetRootDir(c.MkDir()) + defer dirs.SetRootDir("/") + + // The snap `mysnap` no longer wishes to export it's usr/share/mysnap + // directory. All the synthetic changes that were associated with that mount + // entry can be discarded. + const snapName = "mysnap" + const currentProfileContent = `tmpfs /usr/share tmpfs x-snapd.synthetic,x-snapd.needed-by=/usr/share/mysnap 0 0 +/usr/share/adduser /usr/share/adduser none bind,ro,x-snapd.synthetic,x-snapd.needed-by=/usr/share/mysnap 0 0 +/usr/share/awk /usr/share/awk none bind,ro,x-snapd.synthetic,x-snapd.needed-by=/usr/share/mysnap 0 0 +/snap/mysnap/42/usr/share/mysnap /usr/share/mysnap none bind,ro 0 0 +` + const desiredProfileContent = "" + + currentProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapRunNsDir, snapName) + desiredProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapMountPolicyDir, snapName) + + c.Assert(os.MkdirAll(filepath.Dir(currentProfilePath), 0755), IsNil) + c.Assert(os.MkdirAll(filepath.Dir(desiredProfilePath), 0755), IsNil) + c.Assert(ioutil.WriteFile(currentProfilePath, []byte(currentProfileContent), 0644), IsNil) + c.Assert(ioutil.WriteFile(desiredProfilePath, []byte(desiredProfileContent), 0644), IsNil) + + n := -1 + restore := update.MockChangePerform(func(chg *update.Change) ([]*update.Change, error) { + n += 1 + switch n { + case 0: + c.Assert(chg, DeepEquals, &update.Change{ + Action: update.Unmount, + Entry: mount.Entry{ + Name: "/snap/mysnap/42/usr/share/mysnap", + Dir: "/usr/share/mysnap", Type: "none", + Options: []string{"bind", "ro"}, + }, + }) + case 1: + c.Assert(chg, DeepEquals, &update.Change{ + Action: update.Unmount, + Entry: mount.Entry{ + Name: "/usr/share/awk", Dir: "/usr/share/awk", Type: "none", + Options: []string{"bind", "ro", "x-snapd.synthetic", "x-snapd.needed-by=/usr/share/mysnap"}, + }, + }) + case 2: + c.Assert(chg, DeepEquals, &update.Change{ + Action: update.Unmount, + Entry: mount.Entry{ + Name: "/usr/share/adduser", Dir: "/usr/share/adduser", Type: "none", + Options: []string{"bind", "ro", "x-snapd.synthetic", "x-snapd.needed-by=/usr/share/mysnap"}, + }, + }) + case 3: + c.Assert(chg, DeepEquals, &update.Change{ + Action: update.Unmount, + Entry: mount.Entry{ + Name: "tmpfs", Dir: "/usr/share", Type: "tmpfs", + Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/usr/share/mysnap"}, + }, + }) + default: + panic(fmt.Sprintf("unexpected call n=%d, chg: %v", n, *chg)) + } + return nil, nil + }) + defer restore() + + c.Assert(update.ComputeAndSaveChanges(snapName), IsNil) + + content, err := ioutil.ReadFile(currentProfilePath) + c.Assert(err, IsNil) + c.Check(string(content), Equals, "") +} diff -Nru snapd-2.29.4.2+17.10/cmd/snap-update-ns/utils.go snapd-2.31.1+17.10/cmd/snap-update-ns/utils.go --- snapd-2.29.4.2+17.10/cmd/snap-update-ns/utils.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-update-ns/utils.go 2018-01-31 08:47:06.000000000 +0000 @@ -0,0 +1,485 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package main + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "syscall" + + "github.com/snapcore/snapd/interfaces/mount" + "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/osutil/sys" +) + +// not available through syscall +const ( + umountNoFollow = 8 +) + +// For mocking everything during testing. +var ( + osLstat = os.Lstat + osReadlink = os.Readlink + osSymlink = os.Symlink + osRemove = os.Remove + + sysClose = syscall.Close + sysMkdirat = syscall.Mkdirat + sysMount = syscall.Mount + sysOpen = syscall.Open + sysOpenat = syscall.Openat + sysUnmount = syscall.Unmount + sysFchown = sys.Fchown + + ioutilReadDir = ioutil.ReadDir +) + +// ReadOnlyFsError is an error encapsulating encountered EROFS. +type ReadOnlyFsError struct { + Path string +} + +func (e *ReadOnlyFsError) Error() string { + return fmt.Sprintf("cannot operate on read-only filesystem at %s", e.Path) +} + +// Create directories for all but the last segments and return the file +// descriptor to the leaf directory. This function is a base for secure +// variants of mkdir, touch and symlink. +func secureMkPrefix(segments []string, perm os.FileMode, uid sys.UserID, gid sys.GroupID) (int, error) { + logger.Debugf("secure-mk-prefix %q %v %d %d -> ...", segments, perm, uid, gid) + + // Declare var and don't assign-declare below to ensure we don't swallow + // any errors by mistake. + var err error + var fd int + + const openFlags = syscall.O_NOFOLLOW | syscall.O_CLOEXEC | syscall.O_DIRECTORY + + // Open the root directory and start there. + fd, err = sysOpen("/", openFlags, 0) + if err != nil { + return -1, fmt.Errorf("cannot open root directory: %v", err) + } + if len(segments) > 1 { + defer sysClose(fd) + } + + if len(segments) > 0 { + // Process all but the last segment. + for i := range segments[:len(segments)-1] { + fd, err = secureMkDir(fd, segments, i, perm, uid, gid) + if err != nil { + return -1, err + } + // Keep the final FD open (caller needs to close it). + if i < len(segments)-2 { + defer sysClose(fd) + } + } + } + + logger.Debugf("secure-mk-prefix %q %v %d %d -> %d", segments, perm, uid, gid, fd) + return fd, nil +} + +// secureMkDir creates a directory at i-th entry of absolute path represented +// by segments. This function can be used to construct subsequent elements of +// the constructed path. The return value contains the newly created file +// descriptor or -1 on error. +func secureMkDir(fd int, segments []string, i int, perm os.FileMode, uid sys.UserID, gid sys.GroupID) (int, error) { + logger.Debugf("secure-mk-dir %d %q %d %v %d %d -> ...", fd, segments, i, perm, uid, gid) + + segment := segments[i] + made := true + var err error + var newFd int + + const openFlags = syscall.O_NOFOLLOW | syscall.O_CLOEXEC | syscall.O_DIRECTORY + + if err = sysMkdirat(fd, segment, uint32(perm.Perm())); err != nil { + switch err { + case syscall.EEXIST: + made = false + case syscall.EROFS: + // Treat EROFS specially: this is a hint that we have to poke a + // hole using tmpfs. The path below is the location where we + // need to poke the hole. + p := "/" + strings.Join(segments[:i], "/") + return -1, &ReadOnlyFsError{Path: p} + default: + return -1, fmt.Errorf("cannot mkdir path segment %q: %v", segment, err) + } + } + newFd, err = sysOpenat(fd, segment, openFlags, 0) + if err != nil { + return -1, fmt.Errorf("cannot open path segment %q (got up to %q): %v", segment, + "/"+strings.Join(segments[:i], "/"), err) + } + if made { + // Chown each segment that we made. + if err := sysFchown(newFd, uid, gid); err != nil { + // Close the FD we opened if we fail here since the caller will get + // an error and won't assume responsibility for the FD. + sysClose(newFd) + return -1, fmt.Errorf("cannot chown path segment %q to %d.%d (got up to %q): %v", segment, uid, gid, + "/"+strings.Join(segments[:i], "/"), err) + } + } + logger.Debugf("secure-mk-dir %d %q %d %v %d %d -> %d", fd, segments, i, perm, uid, gid, newFd) + return newFd, err +} + +// secureMkFile creates a file at i-th entry of absolute path represented by +// segments. This function is meant to be used to create the leaf file as a +// preparation for a mount point. Existing files are reused without errors. +// Newly created files have the specified mode and ownership. +func secureMkFile(fd int, segments []string, i int, perm os.FileMode, uid sys.UserID, gid sys.GroupID) error { + logger.Debugf("secure-mk-file %d %q %d %v %d %d", fd, segments, i, perm, uid, gid) + segment := segments[i] + made := true + var newFd int + var err error + + // NOTE: Tests don't show O_RDONLY as has a value of 0 and is not + // translated to textual form. It is added here for explicitness. + const openFlags = syscall.O_NOFOLLOW | syscall.O_CLOEXEC | syscall.O_RDONLY + + // Open the final path segment as a file. Try to create the file (so that + // we know if we need to chown it) but fall back to just opening an + // existing one. + + newFd, err = sysOpenat(fd, segment, openFlags|syscall.O_CREAT|syscall.O_EXCL, uint32(perm.Perm())) + if err != nil { + switch err { + case syscall.EEXIST: + // If the file exists then just open it without O_CREAT and O_EXCL + newFd, err = sysOpenat(fd, segment, openFlags, 0) + if err != nil { + return fmt.Errorf("cannot open file %q: %v", segment, err) + } + made = false + case syscall.EROFS: + // Treat EROFS specially: this is a hint that we have to poke a + // hole using tmpfs. The path below is the location where we + // need to poke the hole. + p := "/" + strings.Join(segments[:i], "/") + return &ReadOnlyFsError{Path: p} + default: + return fmt.Errorf("cannot open file %q: %v", segment, err) + } + } + defer sysClose(newFd) + + if made { + // Chown the file if we made it. + if err := sysFchown(newFd, uid, gid); err != nil { + return fmt.Errorf("cannot chown file %q to %d.%d: %v", segment, uid, gid, err) + } + } + + return nil +} + +func splitIntoSegments(name string) ([]string, error) { + if name != filepath.Clean(name) { + return nil, fmt.Errorf("cannot split unclean path %q", name) + } + segments := strings.FieldsFunc(filepath.Clean(name), func(c rune) bool { return c == '/' }) + return segments, nil +} + +// secureMkdirAll is the secure variant of os.MkdirAll. +// +// Unlike the regular version this implementation does not follow any symbolic +// links. At all times the new directory segment is created using mkdirat(2) +// while holding an open file descriptor to the parent directory. +// +// The only handled error is mkdirat(2) that fails with EEXIST. All other +// errors are fatal but there is no attempt to undo anything that was created. +// +// The uid and gid are used for the fchown(2) system call which is performed +// after each segment is created and opened. The special value -1 may be used +// to request that ownership is not changed. +func secureMkdirAll(name string, perm os.FileMode, uid sys.UserID, gid sys.GroupID) error { + logger.Debugf("secure-mkdir-all %q %v %d %d", name, perm, uid, gid) + + // Only support absolute paths to avoid bugs in snap-confine when + // called from anywhere. + if !filepath.IsAbs(name) { + return fmt.Errorf("cannot create directory with relative path: %q", name) + } + + // Split the path into segments. + segments, err := splitIntoSegments(name) + if err != nil { + return err + } + + // Create the prefix. + fd, err := secureMkPrefix(segments, perm, uid, gid) + if err != nil { + return err + } + defer sysClose(fd) + + if len(segments) > 0 { + // Create the final segment as a directory. + fd, err = secureMkDir(fd, segments, len(segments)-1, perm, uid, gid) + if err != nil { + return err + } + defer sysClose(fd) + } + + return nil +} + +// secureMkfileAll is a secure implementation of "mkdir -p $(dirname $1) && touch $1". +// +// This function is like secureMkdirAll but it creates an empty file instead of +// a directory for the final path component. Each created directory component +// is chowned to the desired user and group. +func secureMkfileAll(name string, perm os.FileMode, uid sys.UserID, gid sys.GroupID) error { + logger.Debugf("secure-mkfile-all %q %q %d %d", name, perm, uid, gid) + + // Only support absolute paths to avoid bugs in snap-confine when + // called from anywhere. + if !filepath.IsAbs(name) { + return fmt.Errorf("cannot create file with relative path: %q", name) + } + // Only support file names, not directory names. + if strings.HasSuffix(name, "/") { + return fmt.Errorf("cannot create non-file path: %q", name) + } + + // Split the path into segments. + segments, err := splitIntoSegments(name) + if err != nil { + return err + } + + // Create the prefix. + fd, err := secureMkPrefix(segments, perm, uid, gid) + if err != nil { + return err + } + defer sysClose(fd) + + if len(segments) > 0 { + // Create the final segment as a file. + err = secureMkFile(fd, segments, len(segments)-1, perm, uid, gid) + } + return err +} + +func secureMklinkAll(name string, perm os.FileMode, uid sys.UserID, gid sys.GroupID, oldname string) error { + parent := filepath.Dir(name) + err := secureMkdirAll(parent, perm, uid, gid) + if err != nil { + return err + } + // TODO: roll this uber securely like the code above does using linkat(2). + err = osSymlink(oldname, name) + if err == syscall.EROFS { + return &ReadOnlyFsError{Path: parent} + } + return err +} + +// planWritableMimic plans how to transform a given directory from read-only to writable. +// +// The algorithm is designed to be universally reversible so that it can be +// always de-constructed back to the original directory. The original directory +// is hidden by tmpfs and a subset of things that were present there originally +// is bind mounted back on top of empty directories or empty files. Symlinks +// are re-created directly. Devices and all other elements are not supported +// because they are forbidden in snaps for which this function is designed to +// be used with. Since the original directory is hidden the algorithm relies on +// a temporary directory where the original is bind-mounted during the +// progression of the algorithm. +func planWritableMimic(dir string) ([]*Change, error) { + // We need a place for "safe keeping" of what is present in the original + // directory as we are about to attach a tmpfs there, which will hide + // everything inside. + logger.Debugf("create-writable-mimic %q", dir) + safeKeepingDir := filepath.Join("/tmp/.snap/", dir) + + var changes []*Change + + // Bind mount the original directory elsewhere for safe-keeping. + changes = append(changes, &Change{ + Action: Mount, Entry: mount.Entry{ + // NOTE: Here we bind instead of recursively binding + // because recursive binds cannot be undone without + // parsing the mount table and exploring what is really + // there and this is not how the undo logic is + // designed. + Name: dir, Dir: safeKeepingDir, Options: []string{"bind"}}, + }) + // Mount tmpfs over the original directory, hiding its contents. + changes = append(changes, &Change{ + Action: Mount, Entry: mount.Entry{Name: "tmpfs", Dir: dir, Type: "tmpfs"}, + }) + // Iterate over the items in the original directory (nothing is mounted _yet_). + entries, err := ioutilReadDir(dir) + if err != nil { + return nil, err + } + for _, fi := range entries { + ch := &Change{Action: Mount, Entry: mount.Entry{ + Name: filepath.Join(safeKeepingDir, fi.Name()), + Dir: filepath.Join(dir, fi.Name()), + Options: []string{"bind"}, + }} + // Bind mount each element from the safe-keeping directory into the + // tmpfs. Our Change.Perform() engine can create the missing + // directories automatically so we don't bother creating those. + m := fi.Mode() + switch { + case m.IsDir(): + changes = append(changes, ch) + case m.IsRegular(): + ch.Entry.Options = append(ch.Entry.Options, "x-snapd.kind=file") + changes = append(changes, ch) + case m&os.ModeSymlink != 0: + if target, err := osReadlink(filepath.Join(dir, fi.Name())); err == nil { + ch.Entry.Options = []string{"x-snapd.kind=symlink", fmt.Sprintf("x-snapd.symlink=%s", target)} + changes = append(changes, ch) + } + default: + logger.Noticef("skipping unsupported file %s", fi) + } + } + // Finally unbind the safe-keeping directory as we don't need it anymore. + changes = append(changes, &Change{ + Action: Unmount, Entry: mount.Entry{Name: "none", Dir: safeKeepingDir}, + }) + return changes, nil +} + +// FatalError is an error that we cannot correct. +type FatalError struct { + error +} + +// execWritableMimic executes the plan for a writable mimic. +// The result is a transformed mount namespace and a set of fake mount changes +// that only exist in order to undo the plan. +// +// Certain assumptions are made about the plan, it must closely resemble that +// created by planWritableMimic, in particular the sequence must look like this: +// +// - bind a directory aside into safekeeping location +// - cover the original with tmpfs +// - bind mount something from safekeeping location to an empty file or +// directory in the tmpfs; this step can repeat any number of times +// - unbind the safekeeping location +// +// Apart from merely executing the plan a fake plan is returned for undo. The +// undo plan skips the following elements as compared to the original plan: +// +// - the initial bind mount that constructs the safekeeping directory is gone +// - the final unmount that removes the safekeeping directory +// - the source of each of the bind mounts that re-populate tmpfs. +// +// In the event of a failure the undo plan is executed and an error is +// returned. If the undo plan fails the function returns a FatalError as it +// cannot fix the system from an inconsistent state. +func execWritableMimic(plan []*Change) ([]*Change, error) { + undoChanges := make([]*Change, 0, len(plan)-2) + for i, change := range plan { + if _, err := changePerform(change); err != nil { + // Drat, we failed! Let's undo everything according to our own undo + // plan, by following it in reverse order. + + recoveryUndoChanges := make([]*Change, 0, len(undoChanges)+1) + if i > 0 { + // The undo plan doesn't contain the entry for the initial bind + // mount of the safe keeping directory but we have already + // performed it. For this recovery phase we need to insert that + // in front of the undo plan manually. + recoveryUndoChanges = append(recoveryUndoChanges, plan[0]) + } + recoveryUndoChanges = append(recoveryUndoChanges, undoChanges...) + + for j := len(recoveryUndoChanges) - 1; j >= 0; j-- { + recoveryUndoChange := recoveryUndoChanges[j] + // All the changes mount something, we need to reverse that. + // The "undo plan" is "a plan that can be undone" not "the plan + // for how to undo" so we need to flip the actions. + recoveryUndoChange.Action = Unmount + if _, err2 := changePerform(recoveryUndoChange); err2 != nil { + // Drat, we failed when trying to recover from an error. + // We cannot do anything at this stage. + return nil, &FatalError{error: fmt.Errorf("cannot undo change %q while recovering from earlier error %v: %v", recoveryUndoChange, err, err2)} + } + } + return nil, err + } + if i == 0 || i == len(plan)-1 { + // Don't represent the initial and final changes in the undo plan. + // The initial change is the safe-keeping bind mount, the final + // change is the safe-keeping unmount. + continue + } + if kind, _ := change.Entry.OptStr("x-snapd.kind"); kind == "symlink" { + // Don't represent symlinks in the undo plan. They are removed when + // the tmpfs is unmounted. + continue + + } + // Store an undo change for the change we just performed. + undoChange := &Change{ + Action: Mount, + Entry: mount.Entry{Dir: change.Entry.Dir, Name: change.Entry.Name, Type: change.Entry.Type, Options: change.Entry.Options}, + } + // Because of the use of a temporary bind mount (aka the safe-keeping + // directory) we cannot represent bind mounts fully (the temporary bind + // mount is unmounted as the last stage of this process). For that + // reason let's hide the original location and overwrite it so to + // appear as if the directory was a bind mount over itself. This is not + // fully true (it is a bind mount from the old self to the new empty + // directory or file in the same path, with the tmpfs in place already) + // but this is closer to the truth and more in line with the idea that + // this is just a plan for undoing the operation. + if undoChange.Entry.OptBool("bind") { + undoChange.Entry.Name = undoChange.Entry.Dir + } + undoChanges = append(undoChanges, undoChange) + } + return undoChanges, nil +} + +func createWritableMimic(dir string) ([]*Change, error) { + plan, err := planWritableMimic(dir) + if err != nil { + return nil, err + } + changes, err := execWritableMimic(plan) + if err != nil { + return nil, err + } + return changes, nil +} diff -Nru snapd-2.29.4.2+17.10/cmd/snap-update-ns/utils_test.go snapd-2.31.1+17.10/cmd/snap-update-ns/utils_test.go --- snapd-2.29.4.2+17.10/cmd/snap-update-ns/utils_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/snap-update-ns/utils_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,668 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package main_test + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + "syscall" + + . "gopkg.in/check.v1" + + update "github.com/snapcore/snapd/cmd/snap-update-ns" + "github.com/snapcore/snapd/interfaces/mount" + "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/osutil/sys" + "github.com/snapcore/snapd/testutil" +) + +type utilsSuite struct { + testutil.BaseTest + sys *update.SyscallRecorder + log *bytes.Buffer +} + +var _ = Suite(&utilsSuite{}) + +func (s *utilsSuite) SetUpTest(c *C) { + s.BaseTest.SetUpTest(c) + s.sys = &update.SyscallRecorder{} + s.BaseTest.AddCleanup(update.MockSystemCalls(s.sys)) + buf, restore := logger.MockLogger() + s.BaseTest.AddCleanup(restore) + s.log = buf +} + +func (s *utilsSuite) TearDownTest(c *C) { + s.sys.CheckForStrayDescriptors(c) + s.BaseTest.TearDownTest(c) +} + +// secure-mkdir-all + +// Ensure that we reject unclean paths. +func (s *utilsSuite) TestSecureMkdirAllUnclean(c *C) { + err := update.SecureMkdirAll("/unclean//path", 0755, 123, 456) + c.Assert(err, ErrorMatches, `cannot split unclean path .*`) + c.Assert(s.sys.Calls(), HasLen, 0) +} + +// Ensure that we refuse to create a directory with an relative path. +func (s *utilsSuite) TestSecureMkdirAllRelative(c *C) { + err := update.SecureMkdirAll("rel/path", 0755, 123, 456) + c.Assert(err, ErrorMatches, `cannot create directory with relative path: "rel/path"`) + c.Assert(s.sys.Calls(), HasLen, 0) +} + +// Ensure that we can "create the root directory. +func (s *utilsSuite) TestSecureMkdirAllLevel0(c *C) { + c.Assert(update.SecureMkdirAll("/", 0755, 123, 456), IsNil) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 3 + `close 3`, + }) +} + +// Ensure that we can create a directory in the top-level directory. +func (s *utilsSuite) TestSecureMkdirAllLevel1(c *C) { + os.Setenv("SNAPD_DEBUG", "1") + defer os.Unsetenv("SNAPD_DEBUG") + c.Assert(update.SecureMkdirAll("/path", 0755, 123, 456), IsNil) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 3 + `mkdirat 3 "path" 0755`, + `openat 3 "path" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 4 + `fchown 4 123 456`, + `close 4`, + `close 3`, + }) + c.Assert(s.log.String(), testutil.Contains, `secure-mk-dir 3 ["path"] 0 -rwxr-xr-x 123 456 -> ...`) + c.Assert(s.log.String(), testutil.Contains, `secure-mk-dir 3 ["path"] 0 -rwxr-xr-x 123 456 -> 4`) +} + +// Ensure that we can create a directory two levels from the top-level directory. +func (s *utilsSuite) TestSecureMkdirAllLevel2(c *C) { + c.Assert(update.SecureMkdirAll("/path/to", 0755, 123, 456), IsNil) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 3 + `mkdirat 3 "path" 0755`, + `openat 3 "path" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 4 + `fchown 4 123 456`, + `close 3`, + `mkdirat 4 "to" 0755`, + `openat 4 "to" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 3 + `fchown 3 123 456`, + `close 3`, + `close 4`, + }) +} + +// Ensure that we can create a directory three levels from the top-level directory. +func (s *utilsSuite) TestSecureMkdirAllLevel3(c *C) { + c.Assert(update.SecureMkdirAll("/path/to/something", 0755, 123, 456), IsNil) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 3 + `mkdirat 3 "path" 0755`, + `openat 3 "path" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 4 + `fchown 4 123 456`, + `mkdirat 4 "to" 0755`, + `openat 4 "to" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 5 + `fchown 5 123 456`, + `close 4`, + `close 3`, + `mkdirat 5 "something" 0755`, + `openat 5 "something" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 3 + `fchown 3 123 456`, + `close 3`, + `close 5`, + }) +} + +// Ensure that we can detect read only filesystems. +func (s *utilsSuite) TestSecureMkdirAllROFS(c *C) { + s.sys.InsertFault(`mkdirat 3 "rofs" 0755`, syscall.EEXIST) // just realistic + s.sys.InsertFault(`mkdirat 4 "path" 0755`, syscall.EROFS) + err := update.SecureMkdirAll("/rofs/path", 0755, 123, 456) + c.Assert(err, ErrorMatches, `cannot operate on read-only filesystem at /rofs`) + c.Assert(err.(*update.ReadOnlyFsError).Path, Equals, "/rofs") + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 3 + `mkdirat 3 "rofs" 0755`, // -> EEXIST + `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 4 + `close 3`, + `mkdirat 4 "path" 0755`, // -> EROFS + `close 4`, + }) +} + +// Ensure that we don't chown existing directories. +func (s *utilsSuite) TestSecureMkdirAllExistingDirsDontChown(c *C) { + s.sys.InsertFault(`mkdirat 3 "abs" 0755`, syscall.EEXIST) + s.sys.InsertFault(`mkdirat 4 "path" 0755`, syscall.EEXIST) + err := update.SecureMkdirAll("/abs/path", 0755, 123, 456) + c.Assert(err, IsNil) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 3 + `mkdirat 3 "abs" 0755`, + `openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 4 + `close 3`, + `mkdirat 4 "path" 0755`, + `openat 4 "path" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 3 + `close 3`, + `close 4`, + }) +} + +// Ensure that we we close everything when mkdirat fails. +func (s *utilsSuite) TestSecureMkdirAllMkdiratError(c *C) { + s.sys.InsertFault(`mkdirat 3 "abs" 0755`, errTesting) + err := update.SecureMkdirAll("/abs", 0755, 123, 456) + c.Assert(err, ErrorMatches, `cannot mkdir path segment "abs": testing`) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 3 + `mkdirat 3 "abs" 0755`, + `close 3`, + }) +} + +// Ensure that we we close everything when fchown fails. +func (s *utilsSuite) TestSecureMkdirAllFchownError(c *C) { + s.sys.InsertFault(`fchown 4 123 456`, errTesting) + err := update.SecureMkdirAll("/path", 0755, 123, 456) + c.Assert(err, ErrorMatches, `cannot chown path segment "path" to 123.456 \(got up to "/"\): testing`) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 3 + `mkdirat 3 "path" 0755`, + `openat 3 "path" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 4 + `fchown 4 123 456`, + `close 4`, + `close 3`, + }) +} + +// Check error path when we cannot open root directory. +func (s *utilsSuite) TestSecureMkdirAllOpenRootError(c *C) { + s.sys.InsertFault(`open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, errTesting) + err := update.SecureMkdirAll("/abs/path", 0755, 123, 456) + c.Assert(err, ErrorMatches, "cannot open root directory: testing") + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> err + }) +} + +// Check error path when we cannot open non-root directory. +func (s *utilsSuite) TestSecureMkdirAllOpenError(c *C) { + s.sys.InsertFault(`openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, errTesting) + err := update.SecureMkdirAll("/abs/path", 0755, 123, 456) + c.Assert(err, ErrorMatches, `cannot open path segment "abs" \(got up to "/"\): testing`) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 3 + `mkdirat 3 "abs" 0755`, + `openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> err + `close 3`, + }) +} + +func (s *utilsSuite) TestPlanWritableMimic(c *C) { + restore := update.MockReadDir(func(dir string) ([]os.FileInfo, error) { + c.Assert(dir, Equals, "/foo") + return []os.FileInfo{ + update.FakeFileInfo("file", 0), + update.FakeFileInfo("dir", os.ModeDir), + update.FakeFileInfo("symlink", os.ModeSymlink), + update.FakeFileInfo("error-symlink-readlink", os.ModeSymlink), + // NOTE: None of the filesystem entries below are supported because + // they cannot be placed inside snaps or can only be created at + // runtime in areas that are already writable and this would never + // have to be handled in a writable mimic. + update.FakeFileInfo("block-dev", os.ModeDevice), + update.FakeFileInfo("char-dev", os.ModeDevice|os.ModeCharDevice), + update.FakeFileInfo("socket", os.ModeSocket), + update.FakeFileInfo("pipe", os.ModeNamedPipe), + }, nil + }) + defer restore() + restore = update.MockReadlink(func(name string) (string, error) { + switch name { + case "/foo/symlink": + return "target", nil + case "/foo/error-symlink-readlink": + return "", errTesting + } + panic("unexpected") + }) + defer restore() + + changes, err := update.PlanWritableMimic("/foo") + c.Assert(err, IsNil) + c.Assert(changes, DeepEquals, []*update.Change{ + // Store /foo in /tmp/.snap/foo while we set things up + {Entry: mount.Entry{Name: "/foo", Dir: "/tmp/.snap/foo", Options: []string{"bind"}}, Action: update.Mount}, + // Put a tmpfs over /foo + {Entry: mount.Entry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs"}, Action: update.Mount}, + // Bind mount files and directories over. Note that files are identified by x-snapd.kind=file option. + {Entry: mount.Entry{Name: "/tmp/.snap/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file"}}, Action: update.Mount}, + {Entry: mount.Entry{Name: "/tmp/.snap/foo/dir", Dir: "/foo/dir", Options: []string{"bind"}}, Action: update.Mount}, + // Create symlinks. + // Bad symlinks and all other file types are skipped and not + // recorded in mount changes. + {Entry: mount.Entry{Name: "/tmp/.snap/foo/symlink", Dir: "/foo/symlink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=target"}}, Action: update.Mount}, + // Unmount the safe-keeping directory + {Entry: mount.Entry{Name: "none", Dir: "/tmp/.snap/foo"}, Action: update.Unmount}, + }) +} + +func (s *utilsSuite) TestPlanWritableMimicErrors(c *C) { + restore := update.MockReadDir(func(dir string) ([]os.FileInfo, error) { + c.Assert(dir, Equals, "/foo") + return nil, errTesting + }) + defer restore() + restore = update.MockReadlink(func(name string) (string, error) { + return "", errTesting + }) + defer restore() + + changes, err := update.PlanWritableMimic("/foo") + c.Assert(err, ErrorMatches, "testing") + c.Assert(changes, HasLen, 0) +} + +func (s *utilsSuite) TestExecWirableMimicSuccess(c *C) { + // This plan is the same as in the test above. This is what comes out of planWritableMimic. + plan := []*update.Change{ + {Entry: mount.Entry{Name: "/foo", Dir: "/tmp/.snap/foo", Options: []string{"bind"}}, Action: update.Mount}, + {Entry: mount.Entry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs"}, Action: update.Mount}, + {Entry: mount.Entry{Name: "/tmp/.snap/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file"}}, Action: update.Mount}, + {Entry: mount.Entry{Name: "/tmp/.snap/foo/dir", Dir: "/foo/dir", Options: []string{"bind"}}, Action: update.Mount}, + {Entry: mount.Entry{Name: "/tmp/.snap/foo/symlink", Dir: "/foo/symlink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=target"}}, Action: update.Mount}, + {Entry: mount.Entry{Name: "none", Dir: "/tmp/.snap/foo"}, Action: update.Unmount}, + } + + // Mock the act of performing changes, each of the change we perform is coming from the plan. + restore := update.MockChangePerform(func(chg *update.Change) ([]*update.Change, error) { + c.Assert(plan, testutil.DeepContains, chg) + return nil, nil + }) + defer restore() + + // The executed plan leaves us with a simplified view of the plan that is suitable for undo. + undoPlan, err := update.ExecWritableMimic(plan) + c.Assert(err, IsNil) + c.Assert(undoPlan, DeepEquals, []*update.Change{ + {Entry: mount.Entry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs"}, Action: update.Mount}, + {Entry: mount.Entry{Name: "/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file"}}, Action: update.Mount}, + {Entry: mount.Entry{Name: "/foo/dir", Dir: "/foo/dir", Options: []string{"bind"}}, Action: update.Mount}, + }) +} + +func (s *utilsSuite) TestExecWirableMimicErrorWithRecovery(c *C) { + // This plan is the same as in the test above. This is what comes out of planWritableMimic. + plan := []*update.Change{ + {Entry: mount.Entry{Name: "/foo", Dir: "/tmp/.snap/foo", Options: []string{"bind"}}, Action: update.Mount}, + {Entry: mount.Entry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs"}, Action: update.Mount}, + {Entry: mount.Entry{Name: "/tmp/.snap/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file"}}, Action: update.Mount}, + {Entry: mount.Entry{Name: "/tmp/.snap/foo/symlink", Dir: "/foo/symlink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=target"}}, Action: update.Mount}, + // NOTE: the next perform will fail. Notably the symlink did not fail. + {Entry: mount.Entry{Name: "/tmp/.snap/foo/dir", Dir: "/foo/dir", Options: []string{"bind"}}, Action: update.Mount}, + {Entry: mount.Entry{Name: "none", Dir: "/tmp/.snap/foo"}, Action: update.Unmount}, + } + + // Mock the act of performing changes. Before we inject a failure we ensure + // that each of the change we perform is coming from the plan. For the + // purpose of the test the change that bind mounts the "dir" over itself + // will fail and will trigger an recovery path. The changes performed in + // the recovery path are recorded. + var recoveryPlan []*update.Change + recovery := false + restore := update.MockChangePerform(func(chg *update.Change) ([]*update.Change, error) { + if !recovery { + c.Assert(plan, testutil.DeepContains, chg) + if chg.Entry.Name == "/tmp/.snap/foo/dir" { + recovery = true // switch to recovery mode + return nil, errTesting + } + } else { + recoveryPlan = append(recoveryPlan, chg) + } + return nil, nil + }) + defer restore() + + // The executed plan fails, leaving us with the error and an empty undo plan. + undoPlan, err := update.ExecWritableMimic(plan) + c.Assert(err, Equals, errTesting) + c.Assert(undoPlan, HasLen, 0) + // The changes we managed to perform were undone correctly. + c.Assert(recoveryPlan, DeepEquals, []*update.Change{ + // NOTE: there is no symlink undo entry as it is implicitly undone by unmounting the tmpfs. + {Entry: mount.Entry{Name: "/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file"}}, Action: update.Unmount}, + {Entry: mount.Entry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs"}, Action: update.Unmount}, + {Entry: mount.Entry{Name: "/foo", Dir: "/tmp/.snap/foo", Options: []string{"bind"}}, Action: update.Unmount}, + }) +} + +func (s *utilsSuite) TestExecWirableMimicErrorNothingDone(c *C) { + // This plan is the same as in the test above. This is what comes out of planWritableMimic. + plan := []*update.Change{ + {Entry: mount.Entry{Name: "/foo", Dir: "/tmp/.snap/foo", Options: []string{"bind"}}, Action: update.Mount}, + {Entry: mount.Entry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs"}, Action: update.Mount}, + {Entry: mount.Entry{Name: "/tmp/.snap/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file"}}, Action: update.Mount}, + {Entry: mount.Entry{Name: "/tmp/.snap/foo/dir", Dir: "/foo/dir", Options: []string{"bind"}}, Action: update.Mount}, + {Entry: mount.Entry{Name: "/tmp/.snap/foo/symlink", Dir: "/foo/symlink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=target"}}, Action: update.Mount}, + {Entry: mount.Entry{Name: "none", Dir: "/tmp/.snap/foo"}, Action: update.Unmount}, + } + + // Mock the act of performing changes and just fail on any request. + restore := update.MockChangePerform(func(chg *update.Change) ([]*update.Change, error) { + return nil, errTesting + }) + defer restore() + + // The executed plan fails, the recovery didn't fail (it's empty) so we just return that error. + undoPlan, err := update.ExecWritableMimic(plan) + c.Assert(err, Equals, errTesting) + c.Assert(undoPlan, HasLen, 0) +} + +func (s *utilsSuite) TestExecWirableMimicErrorCannotUndo(c *C) { + // This plan is the same as in the test above. This is what comes out of planWritableMimic. + plan := []*update.Change{ + {Entry: mount.Entry{Name: "/foo", Dir: "/tmp/.snap/foo", Options: []string{"bind"}}, Action: update.Mount}, + {Entry: mount.Entry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs"}, Action: update.Mount}, + {Entry: mount.Entry{Name: "/tmp/.snap/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file"}}, Action: update.Mount}, + {Entry: mount.Entry{Name: "/tmp/.snap/foo/dir", Dir: "/foo/dir", Options: []string{"bind"}}, Action: update.Mount}, + {Entry: mount.Entry{Name: "/tmp/.snap/foo/symlink", Dir: "/foo/symlink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=target"}}, Action: update.Mount}, + {Entry: mount.Entry{Name: "none", Dir: "/tmp/.snap/foo"}, Action: update.Unmount}, + } + + // Mock the act of performing changes. After performing the first change + // correctly we will fail forever (this includes the recovery path) so the + // execute function ends up in a situation where it cannot perform the + // recovery path and will have to return a fatal error. + i := -1 + restore := update.MockChangePerform(func(chg *update.Change) ([]*update.Change, error) { + i++ + if i > 0 { + return nil, fmt.Errorf("failure-%d", i) + } + return nil, nil + }) + defer restore() + + // The plan partially succeeded and we cannot undo those changes. + _, err := update.ExecWritableMimic(plan) + c.Assert(err, ErrorMatches, `cannot undo change ".*" while recovering from earlier error failure-1: failure-2`) + c.Assert(err, FitsTypeOf, &update.FatalError{}) +} + +// realSystemSuite is not isolated / mocked from the system. +type realSystemSuite struct{} + +var _ = Suite(&realSystemSuite{}) + +// Check that we can actually create directories. +// This doesn't test the chown logic as that requires root. +func (s *realSystemSuite) TestSecureMkdirAllForReal(c *C) { + d := c.MkDir() + + // Create d (which already exists) with mode 0777 (but c.MkDir() used 0700 + // internally and since we are not creating the directory we should not be + // changing that. + c.Assert(update.SecureMkdirAll(d, 0777, sys.FlagID, sys.FlagID), IsNil) + fi, err := os.Stat(d) + c.Assert(err, IsNil) + c.Check(fi.IsDir(), Equals, true) + c.Check(fi.Mode().Perm(), Equals, os.FileMode(0700)) + + // Create d1, which is a simple subdirectory, with a distinct mode and + // check that it was applied. Note that default umask 022 is subtracted so + // effective directory has different permissions. + d1 := filepath.Join(d, "subdir") + c.Assert(update.SecureMkdirAll(d1, 0707, sys.FlagID, sys.FlagID), IsNil) + fi, err = os.Stat(d1) + c.Assert(err, IsNil) + c.Check(fi.IsDir(), Equals, true) + c.Check(fi.Mode().Perm(), Equals, os.FileMode(0705)) + + // Create d2, which is a deeper subdirectory, with another distinct mode + // and check that it was applied. + d2 := filepath.Join(d, "subdir/subdir/subdir") + c.Assert(update.SecureMkdirAll(d2, 0750, sys.FlagID, sys.FlagID), IsNil) + fi, err = os.Stat(d2) + c.Assert(err, IsNil) + c.Check(fi.IsDir(), Equals, true) + c.Check(fi.Mode().Perm(), Equals, os.FileMode(0750)) +} + +// secure-mkfile-all + +// Ensure that we reject unclean paths. +func (s *utilsSuite) TestSecureMkfileAllUnclean(c *C) { + err := update.SecureMkfileAll("/unclean//path", 0755, 123, 456) + c.Assert(err, ErrorMatches, `cannot split unclean path .*`) + c.Assert(s.sys.Calls(), HasLen, 0) +} + +// Ensure that we refuse to create a file with an relative path. +func (s *utilsSuite) TestSecureMkfileAllRelative(c *C) { + err := update.SecureMkfileAll("rel/path", 0755, 123, 456) + c.Assert(err, ErrorMatches, `cannot create file with relative path: "rel/path"`) + c.Assert(s.sys.Calls(), HasLen, 0) +} + +// Ensure that we refuse creating the root directory as a file. +func (s *utilsSuite) TestSecureMkfileAllLevel0(c *C) { + err := update.SecureMkfileAll("/", 0755, 123, 456) + c.Assert(err, ErrorMatches, `cannot create non-file path: "/"`) + c.Assert(s.sys.Calls(), HasLen, 0) +} + +// Ensure that we can create a file in the top-level directory. +func (s *utilsSuite) TestSecureMkfileAllLevel1(c *C) { + c.Assert(update.SecureMkfileAll("/path", 0755, 123, 456), IsNil) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 3 + `openat 3 "path" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, // -> 4 + `fchown 4 123 456`, + `close 4`, + `close 3`, + }) +} + +// Ensure that we can create a file two levels from the top-level directory. +func (s *utilsSuite) TestSecureMkfileAllLevel2(c *C) { + c.Assert(update.SecureMkfileAll("/path/to", 0755, 123, 456), IsNil) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 3 + `mkdirat 3 "path" 0755`, + `openat 3 "path" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 4 + `fchown 4 123 456`, + `close 3`, + `openat 4 "to" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, // -> 3 + `fchown 3 123 456`, + `close 3`, + `close 4`, + }) +} + +// Ensure that we can create a file three levels from the top-level directory. +func (s *utilsSuite) TestSecureMkfileAllLevel3(c *C) { + c.Assert(update.SecureMkfileAll("/path/to/something", 0755, 123, 456), IsNil) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 3 + `mkdirat 3 "path" 0755`, + `openat 3 "path" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 4 + `fchown 4 123 456`, + `mkdirat 4 "to" 0755`, + `openat 4 "to" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 5 + `fchown 5 123 456`, + `close 4`, + `close 3`, + `openat 5 "something" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, // -> 3 + `fchown 3 123 456`, + `close 3`, + `close 5`, + }) +} + +// Ensure that we can detect read only filesystems. +func (s *utilsSuite) TestSecureMkfileAllROFS(c *C) { + s.sys.InsertFault(`mkdirat 3 "rofs" 0755`, syscall.EEXIST) // just realistic + s.sys.InsertFault(`openat 4 "path" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, syscall.EROFS) + err := update.SecureMkfileAll("/rofs/path", 0755, 123, 456) + c.Check(err, ErrorMatches, `cannot operate on read-only filesystem at /rofs`) + c.Assert(err.(*update.ReadOnlyFsError).Path, Equals, "/rofs") + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 3 + `mkdirat 3 "rofs" 0755`, // -> EEXIST + `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 4 + `close 3`, + `openat 4 "path" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, // -> EROFS + `close 4`, + }) +} + +// Ensure that we don't chown existing files or directories. +func (s *utilsSuite) TestSecureMkfileAllExistingDirsDontChown(c *C) { + s.sys.InsertFault(`mkdirat 3 "abs" 0755`, syscall.EEXIST) + s.sys.InsertFault(`openat 4 "path" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, syscall.EEXIST) + err := update.SecureMkfileAll("/abs/path", 0755, 123, 456) + c.Check(err, IsNil) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 3 + `mkdirat 3 "abs" 0755`, + `openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 4 + `close 3`, + `openat 4 "path" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, // -> EEXIST + `openat 4 "path" O_NOFOLLOW|O_CLOEXEC 0`, // -> 3 + `close 3`, + `close 4`, + }) +} + +// Ensure that we we close everything when openat fails. +func (s *utilsSuite) TestSecureMkfileAllOpenat2ndError(c *C) { + s.sys.InsertFault(`openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, syscall.EEXIST) + s.sys.InsertFault(`openat 3 "abs" O_NOFOLLOW|O_CLOEXEC 0`, errTesting) + err := update.SecureMkfileAll("/abs", 0755, 123, 456) + c.Assert(err, ErrorMatches, `cannot open file "abs": testing`) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 3 + `openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, // -> EEXIST + `openat 3 "abs" O_NOFOLLOW|O_CLOEXEC 0`, // -> err + `close 3`, + }) +} + +// Ensure that we we close everything when openat (non-exclusive) fails. +func (s *utilsSuite) TestSecureMkfileAllOpenatError(c *C) { + s.sys.InsertFault(`openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, errTesting) + err := update.SecureMkfileAll("/abs", 0755, 123, 456) + c.Assert(err, ErrorMatches, `cannot open file "abs": testing`) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 3 + `openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, // -> err + `close 3`, + }) +} + +// Ensure that we we close everything when fchown fails. +func (s *utilsSuite) TestSecureMkfileAllFchownError(c *C) { + s.sys.InsertFault(`fchown 4 123 456`, errTesting) + err := update.SecureMkfileAll("/path", 0755, 123, 456) + c.Assert(err, ErrorMatches, `cannot chown file "path" to 123.456: testing`) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 3 + `openat 3 "path" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, // -> 4 + `fchown 4 123 456`, + `close 4`, + `close 3`, + }) +} + +// Check error path when we cannot open root directory. +func (s *utilsSuite) TestSecureMkfileAllOpenRootError(c *C) { + s.sys.InsertFault(`open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, errTesting) + err := update.SecureMkfileAll("/abs/path", 0755, 123, 456) + c.Assert(err, ErrorMatches, "cannot open root directory: testing") + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> err + }) +} + +// Check error path when we cannot open non-root directory. +func (s *utilsSuite) TestSecureMkfileAllOpenError(c *C) { + s.sys.InsertFault(`openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, errTesting) + err := update.SecureMkfileAll("/abs/path", 0755, 123, 456) + c.Assert(err, ErrorMatches, `cannot open path segment "abs" \(got up to "/"\): testing`) + c.Assert(s.sys.Calls(), DeepEquals, []string{ + `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> 3 + `mkdirat 3 "abs" 0755`, + `openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, // -> err + `close 3`, + }) +} + +// Check that we can actually create files. +// This doesn't test the chown logic as that requires root. +func (s *realSystemSuite) TestSecureMkfileAllForReal(c *C) { + d := c.MkDir() + + // Create f1, which is a simple subdirectory, with a distinct mode and + // check that it was applied. Note that default umask 022 is subtracted so + // effective directory has different permissions. + f1 := filepath.Join(d, "file") + c.Assert(update.SecureMkfileAll(f1, 0707, sys.FlagID, sys.FlagID), IsNil) + fi, err := os.Stat(f1) + c.Assert(err, IsNil) + c.Check(fi.Mode().IsRegular(), Equals, true) + c.Check(fi.Mode().Perm(), Equals, os.FileMode(0705)) + + // Create f2, which is a deeper subdirectory, with another distinct mode + // and check that it was applied. + f2 := filepath.Join(d, "subdir/subdir/file") + c.Assert(update.SecureMkfileAll(f2, 0750, sys.FlagID, sys.FlagID), IsNil) + fi, err = os.Stat(f2) + c.Assert(err, IsNil) + c.Check(fi.Mode().IsRegular(), Equals, true) + c.Check(fi.Mode().Perm(), Equals, os.FileMode(0750)) +} + +func (s *utilsSuite) TestCleanTrailingSlash(c *C) { + // This is a sanity test for the use of filepath.Clean in secureMk{dir,file}All + c.Assert(filepath.Clean("/path/"), Equals, "/path") + c.Assert(filepath.Clean("path/"), Equals, "path") + c.Assert(filepath.Clean("path/."), Equals, "path") + c.Assert(filepath.Clean("path/.."), Equals, ".") + c.Assert(filepath.Clean("other/path/.."), Equals, "other") +} + +func (s *utilsSuite) TestSplitIntoSegments(c *C) { + sg, err := update.SplitIntoSegments("/foo/bar/froz") + c.Assert(err, IsNil) + c.Assert(sg, DeepEquals, []string{"foo", "bar", "froz"}) + + sg, err = update.SplitIntoSegments("/foo//fii/../.") + c.Assert(err, ErrorMatches, `cannot split unclean path ".+"`) + c.Assert(sg, HasLen, 0) +} diff -Nru snapd-2.29.4.2+17.10/cmd/system-shutdown/system-shutdown-utils.c snapd-2.31.1+17.10/cmd/system-shutdown/system-shutdown-utils.c --- snapd-2.29.4.2+17.10/cmd/system-shutdown/system-shutdown-utils.c 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/system-shutdown/system-shutdown-utils.c 2017-12-01 15:51:55.000000000 +0000 @@ -105,7 +105,7 @@ // tries to umount all (well, most) things. Returns whether in the last pass it // no longer found writable. -bool umount_all() +bool umount_all(void) { bool did_umount = true; bool had_writable = false; diff -Nru snapd-2.29.4.2+17.10/cmd/system-shutdown/system-shutdown-utils.h snapd-2.31.1+17.10/cmd/system-shutdown/system-shutdown-utils.h --- snapd-2.29.4.2+17.10/cmd/system-shutdown/system-shutdown-utils.h 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/cmd/system-shutdown/system-shutdown-utils.h 2017-12-01 15:51:55.000000000 +0000 @@ -23,7 +23,7 @@ // tries to umount all (well, most) things. Returns whether in the last pass it // no longer found writable. -bool umount_all(); +bool umount_all(void); __attribute__ ((noreturn)) void die(const char *msg); diff -Nru snapd-2.29.4.2+17.10/corecfg/corecfg.go snapd-2.31.1+17.10/corecfg/corecfg.go --- snapd-2.29.4.2+17.10/corecfg/corecfg.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/corecfg/corecfg.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,82 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2017 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 . - * - */ - -package corecfg - -import ( - "fmt" - "os" - "os/exec" - "strings" - - "github.com/snapcore/snapd/osutil" - "github.com/snapcore/snapd/release" - "github.com/snapcore/snapd/systemd" -) - -var ( - Stdout = os.Stdout - Stderr = os.Stderr -) - -// ensureSupportInterface checks that the system has the core-support -// interface. An error is returned if this is not the case -func ensureSupportInterface() error { - return systemd.Available() -} - -func snapctlGet(key string) (string, error) { - raw, err := exec.Command("snapctl", "get", key).CombinedOutput() - if err != nil { - return "", fmt.Errorf("cannot run snapctl: %s", osutil.OutputErr(raw, err)) - } - - output := strings.TrimRight(string(raw), "\n") - return output, nil -} - -func Run() error { - // see if it makes sense to run at all - if release.OnClassic { - return fmt.Errorf("cannot run core-configure on classic distribution") - } - if err := ensureSupportInterface(); err != nil { - return fmt.Errorf("cannot run systemctl - core-support interface seems disconnected: %v", err) - } - - // handle the various core config options: - // service.*.disable - if err := handleServiceDisableConfiguration(); err != nil { - return err - } - // system.power-key-action - if err := handlePowerButtonConfiguration(); err != nil { - return err - } - // pi-config.* - if err := handlePiConfiguration(); err != nil { - return err - } - // proxy.{http,https,ftp} - if err := handleProxyConfiguration(); err != nil { - return err - } - - return nil -} diff -Nru snapd-2.29.4.2+17.10/corecfg/corecfg_test.go snapd-2.31.1+17.10/corecfg/corecfg_test.go --- snapd-2.29.4.2+17.10/corecfg/corecfg_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/corecfg/corecfg_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,81 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2017 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 . - * - */ - -package corecfg_test - -import ( - "fmt" - "testing" - - . "gopkg.in/check.v1" - - "github.com/snapcore/snapd/corecfg" - "github.com/snapcore/snapd/release" - "github.com/snapcore/snapd/systemd" -) - -func Test(t *testing.T) { TestingT(t) } - -// coreCfgSuite is the base for all the corecfg tests -type coreCfgSuite struct { - systemctlArgs [][]string - systemctlRestorer func() -} - -var _ = Suite(&coreCfgSuite{}) - -func (s *coreCfgSuite) SetUpSuite(c *C) { - s.systemctlRestorer = systemd.MockSystemctl(func(args ...string) ([]byte, error) { - s.systemctlArgs = append(s.systemctlArgs, args[:]) - output := []byte("ActiveState=inactive") - return output, nil - }) -} - -func (s *coreCfgSuite) TearDownSuite(c *C) { - s.systemctlRestorer() -} - -// runCfgSuite tests corecfg.Run() -type runCfgSuite struct { - coreCfgSuite -} - -var _ = Suite(&runCfgSuite{}) - -func (s *runCfgSuite) TestConfigureErrorsOnClassic(c *C) { - restore := release.MockOnClassic(true) - defer restore() - - err := corecfg.Run() - c.Check(err, ErrorMatches, "cannot run core-configure on classic distribution") -} - -func (s *runCfgSuite) TestConfigureErrorOnMissingCoreSupport(c *C) { - restore := release.MockOnClassic(false) - defer restore() - - r := systemd.MockSystemctl(func(args ...string) ([]byte, error) { - return nil, fmt.Errorf("simulate missing core-support") - }) - defer r() - - err := corecfg.Run() - c.Check(err, ErrorMatches, `(?m)cannot run systemctl - core-support interface seems disconnected: simulate missing core-support`) -} diff -Nru snapd-2.29.4.2+17.10/corecfg/export_test.go snapd-2.31.1+17.10/corecfg/export_test.go --- snapd-2.29.4.2+17.10/corecfg/export_test.go 2017-10-23 06:17:24.000000000 +0000 +++ snapd-2.31.1+17.10/corecfg/export_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,27 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2017 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 . - * - */ - -package corecfg - -var ( - UpdatePiConfig = updatePiConfig - SwitchHandlePowerKey = switchHandlePowerKey - SwitchDisableService = switchDisableService - UpdateKeyValueStream = updateKeyValueStream -) diff -Nru snapd-2.29.4.2+17.10/corecfg/picfg.go snapd-2.31.1+17.10/corecfg/picfg.go --- snapd-2.29.4.2+17.10/corecfg/picfg.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/corecfg/picfg.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,98 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2017 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 . - * - */ - -package corecfg - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/snapcore/snapd/dirs" - "github.com/snapcore/snapd/osutil" -) - -// valid pi config keys -var piConfigKeys = map[string]bool{ - "disable_overscan": true, - "framebuffer_width": true, - "framebuffer_height": true, - "framebuffer_depth": true, - "framebuffer_ignore_alpha": true, - "overscan_left": true, - "overscan_right": true, - "overscan_top": true, - "overscan_bottom": true, - "overscan_scale": true, - "display_rotate": true, - "hdmi_group": true, - "hdmi_mode": true, - "hdmi_drive": true, - "avoid_warnings": true, - "gpu_mem_256": true, - "gpu_mem_512": true, - "gpu_mem": true, - "sdtv_aspect": true, - "config_hdmi_boost": true, - "hdmi_force_hotplug": true, -} - -func updatePiConfig(path string, config map[string]string) error { - f, err := os.Open(path) - if err != nil { - return err - } - defer f.Close() - - toWrite, err := updateKeyValueStream(f, piConfigKeys, config) - if err != nil { - return err - } - - if toWrite != nil { - s := strings.Join(toWrite, "\n") - return osutil.AtomicWriteFile(path, []byte(s), 0644, 0) - } - - return nil -} - -func piConfigFile() string { - return filepath.Join(dirs.GlobalRootDir, "/boot/uboot/config.txt") -} - -func handlePiConfiguration() error { - if osutil.FileExists(piConfigFile()) { - // snapctl can actually give us the whole dict in - // JSON, in a single call; use that instead of this. - config := map[string]string{} - for key := range piConfigKeys { - output, err := snapctlGet(fmt.Sprintf("pi-config.%s", strings.Replace(key, "_", "-", -1))) - if err != nil { - return err - } - config[key] = output - } - if err := updatePiConfig(piConfigFile(), config); err != nil { - return err - } - } - return nil -} diff -Nru snapd-2.29.4.2+17.10/corecfg/picfg_test.go snapd-2.31.1+17.10/corecfg/picfg_test.go --- snapd-2.29.4.2+17.10/corecfg/picfg_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/corecfg/picfg_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,164 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2017 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 . - * - */ - -package corecfg_test - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strings" - - . "gopkg.in/check.v1" - - "github.com/snapcore/snapd/corecfg" - "github.com/snapcore/snapd/dirs" - "github.com/snapcore/snapd/release" - "github.com/snapcore/snapd/testutil" -) - -type piCfgSuite struct { - coreCfgSuite - - mockConfigPath string -} - -var _ = Suite(&piCfgSuite{}) - -var mockConfigTxt = ` -# For more options and information see -# http://www.raspberrypi.org/documentation/configuration/config-txt.md -#hdmi_group=1 -# uncomment this if your display has a black border of unused pixels visible -# and your display can output without overscan -#disable_overscan=1 -unrelated_options=are-kept` - -func (s *piCfgSuite) SetUpTest(c *C) { - dirs.SetRootDir(c.MkDir()) - c.Assert(os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "etc"), 0755), IsNil) - - s.mockConfigPath = filepath.Join(dirs.GlobalRootDir, "/boot/uboot/config.txt") - err := os.MkdirAll(filepath.Dir(s.mockConfigPath), 0755) - c.Assert(err, IsNil) - s.mockConfig(c, mockConfigTxt) -} - -func (s *piCfgSuite) TearDownTest(c *C) { - dirs.SetRootDir("/") -} - -func (s *piCfgSuite) mockConfig(c *C, txt string) { - err := ioutil.WriteFile(s.mockConfigPath, []byte(txt), 0644) - c.Assert(err, IsNil) -} - -func (s *piCfgSuite) checkMockConfig(c *C, expected string) { - newContent, err := ioutil.ReadFile(s.mockConfigPath) - c.Assert(err, IsNil) - c.Check(string(newContent), Equals, expected) -} - -func (s *piCfgSuite) TestConfigurePiConfigUncommentExisting(c *C) { - err := corecfg.UpdatePiConfig(s.mockConfigPath, map[string]string{"disable_overscan": "1"}) - c.Assert(err, IsNil) - - expected := strings.Replace(mockConfigTxt, "#disable_overscan=1", "disable_overscan=1", -1) - s.checkMockConfig(c, expected) -} - -func (s *piCfgSuite) TestConfigurePiConfigCommentExisting(c *C) { - s.mockConfig(c, mockConfigTxt+"\navoid_warnings=1\n") - - err := corecfg.UpdatePiConfig(s.mockConfigPath, map[string]string{"avoid_warnings": ""}) - c.Assert(err, IsNil) - - expected := mockConfigTxt + "\n" + "#avoid_warnings=1" - s.checkMockConfig(c, expected) -} - -func (s *piCfgSuite) TestConfigurePiConfigAddNewOption(c *C) { - err := corecfg.UpdatePiConfig(s.mockConfigPath, map[string]string{"framebuffer_depth": "16"}) - c.Assert(err, IsNil) - - expected := mockConfigTxt + "\n" + "framebuffer_depth=16" - s.checkMockConfig(c, expected) - - // add again, verify its not added twice but updated - err = corecfg.UpdatePiConfig(s.mockConfigPath, map[string]string{"framebuffer_depth": "32"}) - c.Assert(err, IsNil) - expected = mockConfigTxt + "\n" + "framebuffer_depth=32" - s.checkMockConfig(c, expected) -} - -func (s *piCfgSuite) TestConfigurePiConfigNoChangeUnset(c *C) { - // ensure we cannot write to the dir to test that we really - // do not update the file - err := os.Chmod(filepath.Dir(s.mockConfigPath), 0500) - c.Assert(err, IsNil) - defer os.Chmod(filepath.Dir(s.mockConfigPath), 0755) - - err = corecfg.UpdatePiConfig(s.mockConfigPath, map[string]string{"hdmi_group": ""}) - c.Assert(err, IsNil) -} - -func (s *piCfgSuite) TestConfigurePiConfigNoChangeSet(c *C) { - // ensure we cannot write to the dir to test that we really - // do not update the file - err := os.Chmod(filepath.Dir(s.mockConfigPath), 0500) - c.Assert(err, IsNil) - defer os.Chmod(filepath.Dir(s.mockConfigPath), 0755) - - err = corecfg.UpdatePiConfig(s.mockConfigPath, map[string]string{"unrelated_options": "cannot-be-set"}) - c.Assert(err, ErrorMatches, `cannot set unsupported configuration value "unrelated_options"`) -} - -func (s *piCfgSuite) TestConfigurePiConfigIntegration(c *C) { - restore := release.MockOnClassic(false) - defer restore() - - mockSnapctl := testutil.MockCommand(c, "snapctl", fmt.Sprintf(` -if [ "$1" = "get" ] && [ "$2" = "pi-config.disable-overscan" ]; then - echo "1" -fi -`)) - defer mockSnapctl.Restore() - - err := corecfg.Run() - c.Assert(err, IsNil) - - expected := strings.Replace(mockConfigTxt, "#disable_overscan=1", "disable_overscan=1", -1) - s.checkMockConfig(c, expected) - - // run again with the inverse result and ensure we are back - // as before - mockSnapctl = testutil.MockCommand(c, "snapctl", fmt.Sprintf(` -if [ "$1" = "get" ] && [ "$2" = "pi-config.disable-overscan" ]; then - echo "" -fi -`)) - defer mockSnapctl.Restore() - - err = corecfg.Run() - c.Assert(err, IsNil) - - s.checkMockConfig(c, mockConfigTxt) - -} diff -Nru snapd-2.29.4.2+17.10/corecfg/powerbtn.go snapd-2.31.1+17.10/corecfg/powerbtn.go --- snapd-2.29.4.2+17.10/corecfg/powerbtn.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/corecfg/powerbtn.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,81 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2017 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 . - * - */ - -package corecfg - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/snapcore/snapd/dirs" - "github.com/snapcore/snapd/osutil" -) - -func powerBtnCfg() string { - return filepath.Join(dirs.GlobalRootDir, "/etc/systemd/logind.conf.d/00-snap-core.conf") -} - -// switchHandlePowerKey changes the behavior when the power key is pressed -func switchHandlePowerKey(action string) error { - validActions := map[string]bool{ - "ignore": true, - "poweroff": true, - "reboot": true, - "halt": true, - "kexec": true, - "suspend": true, - "hibernate": true, - "hybrid-sleep": true, - "lock": true, - } - - cfgDir := filepath.Dir(powerBtnCfg()) - if !osutil.IsDirectory(cfgDir) { - if err := os.MkdirAll(cfgDir, 0755); err != nil { - return err - } - } - if !validActions[action] { - return fmt.Errorf("invalid action %q supplied for system.power-key-action option", action) - } - - content := fmt.Sprintf(`[Login] -HandlePowerKey=%s -`, action) - return osutil.AtomicWriteFile(powerBtnCfg(), []byte(content), 0644, 0) -} - -func handlePowerButtonConfiguration() error { - output, err := snapctlGet("system.power-key-action") - if err != nil { - return err - } - if output == "" { - if err := os.Remove(powerBtnCfg()); err != nil && !os.IsNotExist(err) { - return err - } - - } else { - if err := switchHandlePowerKey(output); err != nil { - return err - } - } - return nil -} diff -Nru snapd-2.29.4.2+17.10/corecfg/powerbtn_test.go snapd-2.31.1+17.10/corecfg/powerbtn_test.go --- snapd-2.29.4.2+17.10/corecfg/powerbtn_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/corecfg/powerbtn_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,84 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2017 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 . - * - */ - -package corecfg_test - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - - . "gopkg.in/check.v1" - - "github.com/snapcore/snapd/corecfg" - "github.com/snapcore/snapd/dirs" - "github.com/snapcore/snapd/release" - "github.com/snapcore/snapd/testutil" -) - -type powerbtnSuite struct { - coreCfgSuite - - mockPowerBtnCfg string -} - -var _ = Suite(&powerbtnSuite{}) - -func (s *powerbtnSuite) SetUpTest(c *C) { - dirs.SetRootDir(c.MkDir()) - c.Assert(os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "etc"), 0755), IsNil) - - s.mockPowerBtnCfg = filepath.Join(dirs.GlobalRootDir, "/etc/systemd/logind.conf.d/00-snap-core.conf") -} - -func (s *powerbtnSuite) TearDownTest(c *C) { - dirs.SetRootDir("/") -} - -func (s *powerbtnSuite) TestConfigurePowerButtonInvalid(c *C) { - err := corecfg.SwitchHandlePowerKey("invalid-action") - c.Check(err, ErrorMatches, `invalid action "invalid-action" supplied for system.power-key-action option`) -} - -func (s *powerbtnSuite) TestConfigurePowerIntegration(c *C) { - restore := release.MockOnClassic(false) - defer restore() - - for _, action := range []string{"ignore", "poweroff", "reboot", "halt", "kexec", "suspend", "hibernate", "hybrid-sleep", "lock"} { - - mockSnapctl := testutil.MockCommand(c, "snapctl", fmt.Sprintf(` -if [ "$1" = "get" ] && [ "$2" = "system.power-key-action" ]; then - echo "%s" -fi -`, action)) - defer mockSnapctl.Restore() - - err := corecfg.Run() - c.Assert(err, IsNil) - - // ensure nothing gets enabled/disabled when an unsupported - // service is set for disable - c.Check(mockSnapctl.Calls(), Not(HasLen), 0) - content, err := ioutil.ReadFile(s.mockPowerBtnCfg) - c.Assert(err, IsNil) - c.Check(string(content), Equals, fmt.Sprintf("[Login]\nHandlePowerKey=%s\n", action)) - } - -} diff -Nru snapd-2.29.4.2+17.10/corecfg/proxy.go snapd-2.31.1+17.10/corecfg/proxy.go --- snapd-2.29.4.2+17.10/corecfg/proxy.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/corecfg/proxy.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,73 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2017 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 . - * - */ - -package corecfg - -import ( - "io/ioutil" - "os" - "path/filepath" - "strings" - - "github.com/snapcore/snapd/dirs" -) - -var proxyConfigKeys = map[string]bool{ - "http_proxy": true, - "https_proxy": true, - "ftp_proxy": true, -} - -func etcEnvironment() string { - return filepath.Join(dirs.GlobalRootDir, "/etc/environment") -} - -func updateEtcEnvironmentConfig(path string, config map[string]string) error { - f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644) - if err != nil { - return nil - } - defer f.Close() - - toWrite, err := updateKeyValueStream(f, proxyConfigKeys, config) - if err != nil { - return err - } - if toWrite != nil { - return ioutil.WriteFile(path, []byte(strings.Join(toWrite, "\n")), 0644) - } - - return nil -} - -func handleProxyConfiguration() error { - config := map[string]string{} - for _, key := range []string{"http", "https", "ftp"} { - output, err := snapctlGet("proxy." + key) - if err != nil { - return err - } - config[key+"_proxy"] = output - } - if err := updateEtcEnvironmentConfig(etcEnvironment(), config); err != nil { - return err - } - - return nil -} diff -Nru snapd-2.29.4.2+17.10/corecfg/proxy_test.go snapd-2.31.1+17.10/corecfg/proxy_test.go --- snapd-2.29.4.2+17.10/corecfg/proxy_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/corecfg/proxy_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,82 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2017 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 . - * - */ - -package corecfg_test - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - - . "gopkg.in/check.v1" - - "github.com/snapcore/snapd/corecfg" - "github.com/snapcore/snapd/dirs" - "github.com/snapcore/snapd/release" - "github.com/snapcore/snapd/testutil" -) - -type proxySuite struct { - coreCfgSuite - - mockEtcEnvironment string -} - -var _ = Suite(&proxySuite{}) - -func (s *proxySuite) SetUpTest(c *C) { - dirs.SetRootDir(c.MkDir()) - err := os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "/etc/"), 0755) - c.Assert(err, IsNil) - s.mockEtcEnvironment = filepath.Join(dirs.GlobalRootDir, "/etc/environment") -} - -func (s *proxySuite) TearDownTest(c *C) { - dirs.SetRootDir("/") -} - -func (s *proxySuite) TestConfigureProxy(c *C) { - restore := release.MockOnClassic(false) - defer restore() - - for _, action := range []string{"http", "https", "ftp"} { - mockSnapctl := testutil.MockCommand(c, "snapctl", fmt.Sprintf(` -if [ "$1" = "get" ] && [ "$2" = "proxy.%[1]s" ]; then - echo "%[1]s://example.com" -fi -`, action)) - defer mockSnapctl.Restore() - - // populate with content - err := ioutil.WriteFile(s.mockEtcEnvironment, []byte(` -PATH="/usr/bin" -`), 0644) - c.Assert(err, IsNil) - - err = corecfg.Run() - c.Assert(err, IsNil) - - content, err := ioutil.ReadFile(s.mockEtcEnvironment) - c.Assert(err, IsNil) - c.Check(string(content), Equals, fmt.Sprintf(` -PATH="/usr/bin" -%[1]s_proxy=%[1]s://example.com`, action)) - } -} diff -Nru snapd-2.29.4.2+17.10/corecfg/services.go snapd-2.31.1+17.10/corecfg/services.go --- snapd-2.29.4.2+17.10/corecfg/services.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/corecfg/services.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,75 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2017 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 . - * - */ - -package corecfg - -import ( - "fmt" - "time" - - "github.com/snapcore/snapd/dirs" - "github.com/snapcore/snapd/systemd" -) - -type sysdLogger struct{} - -func (l *sysdLogger) Notify(status string) { - fmt.Fprintf(Stderr, "sysd: %s\n", status) -} - -// swtichDisableService switches a service in/out of disabled state -// where "true" means disabled and "false" means enabled. -func switchDisableService(service, value string) error { - sysd := systemd.New(dirs.GlobalRootDir, &sysdLogger{}) - serviceName := fmt.Sprintf("%s.service", service) - - switch value { - case "true": - if err := sysd.Disable(serviceName); err != nil { - return err - } - return sysd.Stop(serviceName, 5*time.Minute) - case "false": - if err := sysd.Enable(serviceName); err != nil { - return err - } - return sysd.Start(serviceName) - default: - return fmt.Errorf("option %q has invalid value %q", serviceName, value) - } -} - -// services that can be disabled -var services = []string{"ssh", "rsyslog"} - -func handleServiceDisableConfiguration() error { - for _, service := range services { - output, err := snapctlGet(fmt.Sprintf("service.%s.disable", service)) - if err != nil { - return err - } - if output != "" { - if err := switchDisableService(service, output); err != nil { - return err - } - } - } - - return nil -} diff -Nru snapd-2.29.4.2+17.10/corecfg/services_test.go snapd-2.31.1+17.10/corecfg/services_test.go --- snapd-2.29.4.2+17.10/corecfg/services_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/corecfg/services_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,154 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2017 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 . - * - */ - -package corecfg_test - -import ( - "fmt" - "os" - "path/filepath" - - . "gopkg.in/check.v1" - - "github.com/snapcore/snapd/corecfg" - "github.com/snapcore/snapd/dirs" - "github.com/snapcore/snapd/release" - "github.com/snapcore/snapd/snap" - "github.com/snapcore/snapd/testutil" -) - -type servicesSuite struct { - coreCfgSuite - testutil.BaseTest -} - -var _ = Suite(&servicesSuite{}) - -func (s *servicesSuite) SetUpTest(c *C) { - s.BaseTest.SetUpTest(c) - dirs.SetRootDir(c.MkDir()) - c.Assert(os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "etc"), 0755), IsNil) - s.systemctlArgs = nil - s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})) -} - -func (s *servicesSuite) TearDownTest(c *C) { - dirs.SetRootDir("/") - s.BaseTest.TearDownTest(c) -} - -func (s *servicesSuite) TestConfigureServiceInvalidValue(c *C) { - err := corecfg.SwitchDisableService("ssh", "xxx") - c.Check(err, ErrorMatches, `option "ssh.service" has invalid value "xxx"`) -} - -func (s *servicesSuite) TestConfigureServiceNotDisabled(c *C) { - err := corecfg.SwitchDisableService("ssh", "false") - c.Assert(err, IsNil) - c.Check(s.systemctlArgs, DeepEquals, [][]string{ - {"--root", dirs.GlobalRootDir, "enable", "ssh.service"}, - {"start", "ssh.service"}, - }) -} - -func (s *servicesSuite) TestConfigureServiceDisabled(c *C) { - err := corecfg.SwitchDisableService("ssh", "true") - c.Assert(err, IsNil) - c.Check(s.systemctlArgs, DeepEquals, [][]string{ - {"--root", dirs.GlobalRootDir, "disable", "ssh.service"}, - {"stop", "ssh.service"}, - {"show", "--property=ActiveState", "ssh.service"}, - }) -} - -func (s *servicesSuite) TestConfigureServiceDisabledIntegration(c *C) { - restore := release.MockOnClassic(false) - defer restore() - - for _, srvName := range []string{"ssh", "rsyslog"} { - srv := fmt.Sprintf("%s.service", srvName) - - s.systemctlArgs = nil - mockSnapctl := testutil.MockCommand(c, "snapctl", fmt.Sprintf(` -if [ "$1" = "get" ] && [ "$2" = "service.%s.disable" ]; then - echo "true" -fi -`, srvName)) - defer mockSnapctl.Restore() - - err := corecfg.Run() - c.Assert(err, IsNil) - c.Check(mockSnapctl.Calls(), Not(HasLen), 0) - c.Check(s.systemctlArgs, DeepEquals, [][]string{ - {"--version"}, - {"--root", dirs.GlobalRootDir, "disable", srv}, - {"stop", srv}, - {"show", "--property=ActiveState", srv}, - }) - } -} - -func (s *servicesSuite) TestConfigureServiceEnableIntegration(c *C) { - restore := release.MockOnClassic(false) - defer restore() - - for _, srvName := range []string{"ssh", "rsyslog"} { - srv := fmt.Sprintf("%s.service", srvName) - - s.systemctlArgs = nil - mockSnapctl := testutil.MockCommand(c, "snapctl", fmt.Sprintf(` -if [ "$1" = "get" ] && [ "$2" = "service.%s.disable" ]; then - echo "false" -fi -`, srvName)) - defer mockSnapctl.Restore() - - err := corecfg.Run() - c.Assert(err, IsNil) - c.Check(mockSnapctl.Calls(), Not(HasLen), 0) - c.Check(s.systemctlArgs, DeepEquals, [][]string{ - {"--version"}, - {"--root", dirs.GlobalRootDir, "enable", srv}, - {"start", srv}, - }) - } -} - -func (s *servicesSuite) TestConfigureServiceUnsupportedService(c *C) { - restore := release.MockOnClassic(false) - defer restore() - - s.systemctlArgs = nil - mockSnapctl := testutil.MockCommand(c, "snapctl", ` -if [ "$1" = "get" ] && [ "$2" = "service.snapd.disable" ]; then - echo "true" -fi -`) - defer mockSnapctl.Restore() - - err := corecfg.Run() - c.Assert(err, IsNil) - - // ensure nothing gets enabled/disabled when an unsupported - // service is set for disable - c.Check(mockSnapctl.Calls(), Not(HasLen), 0) - c.Check(s.systemctlArgs, DeepEquals, [][]string{ - {"--version"}, - }) -} diff -Nru snapd-2.29.4.2+17.10/corecfg/utils.go snapd-2.31.1+17.10/corecfg/utils.go --- snapd-2.29.4.2+17.10/corecfg/utils.go 2017-10-23 06:17:24.000000000 +0000 +++ snapd-2.31.1+17.10/corecfg/utils.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,97 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2017 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 . - * - */ - -package corecfg - -import ( - "bufio" - "fmt" - "io" - "regexp" -) - -// first match is if it is comment, second is key, third value -var rx = regexp.MustCompile(`^[ \t]*(#?)[ \t#]*([a-z_]+)=(.*)$`) - -// updateKeyValueStream updates simple key=value files with comments. -// Example for such formats are: /etc/environment or /boot/uboot/config.txt -// -// An r io.Reader, map of supported config keys and a configuration -// "patch" is taken as input, the r is read line-by-line and any line -// and any required configuration change from the "config" input is -// applied. -// -// If changes need to be written a []string -// that contains the full file is returned. On error an error is returned. -func updateKeyValueStream(r io.Reader, supportedConfigKeys map[string]bool, newConfig map[string]string) (toWrite []string, err error) { - cfgKeys := make([]string, len(newConfig)) - i := 0 - for k := range newConfig { - if !supportedConfigKeys[k] { - return nil, fmt.Errorf("cannot set unsupported configuration value %q", k) - } - cfgKeys[i] = k - i++ - } - - // now go over the content - found := map[string]bool{} - needsWrite := false - - scanner := bufio.NewScanner(r) - for scanner.Scan() { - line := scanner.Text() - matches := rx.FindStringSubmatch(line) - if len(matches) > 0 && supportedConfigKeys[matches[2]] { - wasComment := (matches[1] == "#") - key := matches[2] - oldValue := matches[3] - found[key] = true - if newConfig[key] != "" { - if wasComment || oldValue != newConfig[key] { - line = fmt.Sprintf("%s=%s", key, newConfig[key]) - needsWrite = true - } - } else { - if !wasComment { - line = fmt.Sprintf("#%s=%s", key, oldValue) - needsWrite = true - } - } - } - toWrite = append(toWrite, line) - } - if err := scanner.Err(); err != nil { - return nil, err - } - - // write anything that is missing - for key := range newConfig { - if !found[key] && newConfig[key] != "" { - needsWrite = true - toWrite = append(toWrite, fmt.Sprintf("%s=%s", key, newConfig[key])) - } - } - - if needsWrite { - return toWrite, nil - } - - return nil, nil -} diff -Nru snapd-2.29.4.2+17.10/corecfg/utils_test.go snapd-2.31.1+17.10/corecfg/utils_test.go --- snapd-2.29.4.2+17.10/corecfg/utils_test.go 2017-10-23 06:17:24.000000000 +0000 +++ snapd-2.31.1+17.10/corecfg/utils_test.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,65 +0,0 @@ -// -*- Mode: Go; indent-tabs-mode: t -*- - -/* - * Copyright (C) 2017 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 . - * - */ - -package corecfg_test - -import ( - "bytes" - - . "gopkg.in/check.v1" - - "github.com/snapcore/snapd/corecfg" -) - -type utilsSuite struct{} - -var _ = Suite(&utilsSuite{}) - -func (s *utilsSuite) TestUpdateKeyValueStreamNoNewConfig(c *C) { - in := bytes.NewBufferString("foo=bar") - newConfig := map[string]string{} - supportedConfigKeys := map[string]bool{} - - toWrite, err := corecfg.UpdateKeyValueStream(in, supportedConfigKeys, newConfig) - c.Check(err, IsNil) - c.Check(toWrite, IsNil) -} - -func (s *utilsSuite) TestUpdateKeyValueStreamConfigNotInAllConfig(c *C) { - in := bytes.NewBufferString("") - newConfig := map[string]string{"unsupported-options": "cannot be set"} - supportedConfigKeys := map[string]bool{ - "foo": true, - } - - _, err := corecfg.UpdateKeyValueStream(in, supportedConfigKeys, newConfig) - c.Check(err, ErrorMatches, `cannot set unsupported configuration value "unsupported-options"`) -} - -func (s *utilsSuite) TestUpdateKeyValueStreamOneChange(c *C) { - in := bytes.NewBufferString("foo=bar") - newConfig := map[string]string{"foo": "baz"} - supportedConfigKeys := map[string]bool{ - "foo": true, - } - - toWrite, err := corecfg.UpdateKeyValueStream(in, supportedConfigKeys, newConfig) - c.Check(err, IsNil) - c.Check(toWrite, DeepEquals, []string{"foo=baz"}) -} diff -Nru snapd-2.29.4.2+17.10/daemon/api.go snapd-2.31.1+17.10/daemon/api.go --- snapd-2.29.4.2+17.10/daemon/api.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/daemon/api.go 2018-02-13 07:54:57.000000000 +0000 @@ -27,6 +27,7 @@ "io/ioutil" "mime" "mime/multipart" + "net" "net/http" "os" "os/user" @@ -59,7 +60,6 @@ "github.com/snapcore/snapd/overlord/servicestate" "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" - "github.com/snapcore/snapd/overlord/storestate" "github.com/snapcore/snapd/progress" "github.com/snapcore/snapd/release" "github.com/snapcore/snapd/snap" @@ -168,10 +168,11 @@ } interfacesCmd = &Command{ - Path: "/v2/interfaces", - UserOK: true, - GET: interfacesConnectionsMultiplexer, - POST: changeInterfaces, + Path: "/v2/interfaces", + UserOK: true, + PolkitOK: "io.snapcraft.snapd.manage-interfaces", + GET: interfacesConnectionsMultiplexer, + POST: changeInterfaces, } // TODO: allow to post assertions for UserOK? they are verified anyway @@ -266,15 +267,28 @@ st := c.d.overlord.State() snapMgr := c.d.overlord.SnapManager() st.Lock() + defer st.Unlock() nextRefresh := snapMgr.NextRefresh() lastRefresh, _ := snapMgr.LastRefresh() - refreshScheduleStr := snapMgr.RefreshSchedule() + refreshScheduleStr, legacySchedule, err := snapMgr.RefreshSchedule() + if err != nil { + return InternalError("cannot get refresh schedule: %s", err) + } users, err := auth.Users(st) - st.Unlock() if err != nil && err != state.ErrNoState { return InternalError("cannot get user auth data: %s", err) } + refreshInfo := client.RefreshInfo{ + Last: formatRefreshTime(lastRefresh), + Next: formatRefreshTime(nextRefresh), + } + if !legacySchedule { + refreshInfo.Timer = refreshScheduleStr + } else { + refreshInfo.Schedule = refreshScheduleStr + } + m := map[string]interface{}{ "series": release.Series, "version": c.d.Version, @@ -286,11 +300,7 @@ "snap-mount-dir": dirs.SnapMountDir, "snap-bin-dir": dirs.SnapBinariesDir, }, - "refresh": client.RefreshInfo{ - Schedule: refreshScheduleStr, - Last: formatRefreshTime(lastRefresh), - Next: formatRefreshTime(nextRefresh), - }, + "refresh": refreshInfo, } // NOTE: Right now we don't have a good way to differentiate if we // only have partial confinement (ala AppArmor disabled and Seccomp @@ -409,6 +419,8 @@ // local user logged-in, set its store macaroons user.StoreMacaroon = macaroon user.StoreDischarges = []string{discharge} + // user's email address authenticated by the store + user.Email = loginData.Email err = auth.UpdateUser(state, user) } else { user, err = auth.NewUser(state, loginData.Username, loginData.Email, macaroon, []string{discharge}) @@ -524,12 +536,12 @@ return result } -func getStore(c *Command) storestate.StoreService { +func getStore(c *Command) snapstate.StoreService { st := c.d.overlord.State() st.Lock() defer st.Unlock() - return storestate.Store(st) + return snapstate.Store(st) } func getSections(c *Command, r *http.Request, user *auth.UserState) Response { @@ -544,8 +556,12 @@ switch err { case nil: // pass - case store.ErrEmptyQuery, store.ErrBadQuery: - return BadRequest("%v", err) + case store.ErrBadQuery: + return SyncResponse(&resp{ + Type: ResponseTypeError, + Result: &errorResult{Message: err.Error(), Kind: errorKindBadQuery}, + Status: 400, + }, nil) case store.ErrUnauthenticated, store.ErrInvalidCredentials: return Unauthorized("%v", err) default: @@ -605,11 +621,23 @@ switch err { case nil: // pass - case store.ErrEmptyQuery, store.ErrBadQuery: - return BadRequest("%v", err) + case store.ErrBadQuery: + return SyncResponse(&resp{ + Type: ResponseTypeError, + Result: &errorResult{Message: err.Error(), Kind: errorKindBadQuery}, + Status: 400, + }, nil) case store.ErrUnauthenticated, store.ErrInvalidCredentials: return Unauthorized(err.Error()) default: + if e, ok := err.(net.Error); ok && e.Timeout() { + return SyncResponse(&resp{ + Type: ResponseTypeError, + Result: &errorResult{Message: err.Error(), Kind: errorKindNetworkTimeout}, + Status: 400, + }, nil) + } + return InternalError("%v", err) } @@ -803,6 +831,7 @@ type snapInstruction struct { progress.NullMeter Action string `json:"action"` + Amend bool `json:"amend"` Channel string `json:"channel"` Revision snap.Revision `json:"revision"` DevMode bool `json:"devmode"` @@ -977,6 +1006,9 @@ if inst.IgnoreValidation { flags.IgnoreValidation = true } + if inst.Amend { + flags.Amend = true + } // we need refreshed snap-declarations to enforce refresh-control as best as we can if err = assertstateRefreshSnapDeclarations(st, inst.userID); err != nil { @@ -1107,34 +1139,63 @@ func (inst *snapInstruction) errToResponse(err error) Response { var kind errorKind + var snapName string switch err { case store.ErrSnapNotFound: - return SnapNotFound(inst.Snaps[0], err) + switch len(inst.Snaps) { + case 1: + return SnapNotFound(inst.Snaps[0], err) + // store.ErrSnapNotFound should only be returned for individual + // snap queries; in all other cases something's wrong + case 0: + return InternalError("store.SnapNotFound with no snap given") + default: + return InternalError("store.SnapNotFound with %d snaps", len(inst.Snaps)) + } case store.ErrNoUpdateAvailable: kind = errorKindSnapNoUpdateAvailable case store.ErrLocalSnap: kind = errorKindSnapLocal default: + handled := true switch err := err.(type) { case *snap.AlreadyInstalledError: kind = errorKindSnapAlreadyInstalled + snapName = err.Snap case *snap.NotInstalledError: kind = errorKindSnapNotInstalled + snapName = err.Snap case *snapstate.SnapNeedsDevModeError: kind = errorKindSnapNeedsDevMode + snapName = err.Snap case *snapstate.SnapNeedsClassicError: kind = errorKindSnapNeedsClassic + snapName = err.Snap case *snapstate.SnapNeedsClassicSystemError: kind = errorKindSnapNeedsClassicSystem + snapName = err.Snap + case net.Error: + if err.Timeout() { + kind = errorKindNetworkTimeout + } else { + handled = false + } default: - return BadRequest("cannot %s %q: %v", inst.Action, inst.Snaps[0], err) + handled = false + } + + if !handled { + if len(inst.Snaps) == 0 { + return BadRequest("cannot %s: %v", inst.Action, err) + } + return BadRequest("cannot %s %s: %v", inst.Action, strutil.Quoted(inst.Snaps), err) } } return SyncResponse(&resp{ Type: ResponseTypeError, - Result: &errorResult{Message: err.Error(), Kind: kind}, + Result: &errorResult{Message: err.Error(), Kind: kind, Value: snapName}, Status: 400, }, nil) } @@ -1290,7 +1351,7 @@ return BadRequest("unsupported multi-snap operation %q", inst.Action) } if err != nil { - return InternalError("cannot %s %q: %v", inst.Action, inst.Snaps, err) + return inst.errToResponse(err) } var chg *state.Change @@ -1563,17 +1624,14 @@ st.Lock() defer st.Unlock() - var snapst snapstate.SnapState - if err := snapstate.Get(st, snapName, &snapst); err != nil { - if err == state.ErrNoState { + taskset, err := configstate.ConfigureInstalled(st, snapName, patchValues, 0) + if err != nil { + if _, ok := err.(*snap.NotInstalledError); ok { return SnapNotFound(snapName, err) - } else { - return InternalError("%v", err) } + return InternalError("%v", err) } - taskset := configstate.Configure(st, snapName, patchValues, 0) - summary := fmt.Sprintf("Change configuration of %q snap", snapName) change := newChange(st, "configure-snap", summary, []*state.TaskSet{taskset}, []string{snapName}) @@ -1618,7 +1676,55 @@ func getLegacyConnections(c *Command, r *http.Request, user *auth.UserState) Response { repo := c.d.overlord.InterfaceManager().Repository() - return SyncResponse(repo.Interfaces(), nil) + ifaces := repo.Interfaces() + + var ifjson interfacesJSON + + plugConns := map[string][]interfaces.SlotRef{} + slotConns := map[string][]interfaces.PlugRef{} + + for _, conn := range ifaces.Connections { + plugRef := conn.PlugRef.String() + slotRef := conn.SlotRef.String() + plugConns[plugRef] = append(plugConns[plugRef], conn.SlotRef) + slotConns[slotRef] = append(slotConns[slotRef], conn.PlugRef) + } + + for _, plug := range ifaces.Plugs { + var apps []string + for _, app := range plug.Apps { + apps = append(apps, app.Name) + } + pj := plugJSON{ + Snap: plug.Snap.Name(), + Name: plug.Name, + Interface: plug.Interface, + Attrs: plug.Attrs, + Apps: apps, + Label: plug.Label, + Connections: plugConns[plug.String()], + } + ifjson.Plugs = append(ifjson.Plugs, pj) + } + for _, slot := range ifaces.Slots { + var apps []string + for _, app := range slot.Apps { + apps = append(apps, app.Name) + } + + sj := slotJSON{ + Snap: slot.Snap.Name(), + Name: slot.Name, + Interface: slot.Interface, + Attrs: slot.Attrs, + Apps: apps, + Label: slot.Label, + Connections: slotConns[slot.String()], + } + ifjson.Slots = append(ifjson.Slots, sj) + } + + return SyncResponse(ifjson, nil) } // plugJSON aids in marshaling Plug into JSON. @@ -1643,6 +1749,12 @@ Connections []interfaces.PlugRef `json:"connections,omitempty"` } +// interfacesJSON aids in marshaling plugs, slots and their connections into JSON. +type interfacesJSON struct { + Plugs []plugJSON `json:"plugs,omitempty"` + Slots []slotJSON `json:"slots,omitempty"` +} + // interfaceAction is an action performed on the interface system. type interfaceAction struct { Action string `json:"action"` @@ -1986,6 +2098,8 @@ var ( postCreateUserUcrednetGet = ucrednetGet + runSnapctlUcrednetGet = ucrednetGet + ctlcmdRun = ctlcmd.Run storeUserInfo = store.UserInfo osutilAddUser = osutil.AddUser ) @@ -2126,13 +2240,9 @@ if err != nil { return fmt.Errorf("cannot lookup user %q: %s", username, err) } - uid, err := strconv.Atoi(user.Uid) - if err != nil { - return fmt.Errorf("cannot get uid of user %q: %s", username, err) - } - gid, err := strconv.Atoi(user.Gid) + uid, gid, err := osutil.UidGid(user) if err != nil { - return fmt.Errorf("cannot get gid of user %q: %s", username, err) + return err } authDataFn := filepath.Join(user.HomeDir, ".snap", "auth.json") if err := osutil.MkdirAllChown(filepath.Dir(authDataFn), 0700, uid, gid); err != nil { @@ -2146,10 +2256,17 @@ if err != nil { return fmt.Errorf("cannot persist authentication details: %v", err) } - // store macaroon auth in auth.json in the new users home dir + // store macaroon auth, user's ID, email and username in auth.json in + // the new users home dir outStr, err := json.Marshal(struct { + ID int `json:"id"` + Username string `json:"username"` + Email string `json:"email"` Macaroon string `json:"macaroon"` }{ + ID: authUser.ID, + Username: authUser.Username, + Email: authUser.Email, Macaroon: authUser.Macaroon, }) if err != nil { @@ -2163,7 +2280,7 @@ } func postCreateUser(c *Command, r *http.Request, user *auth.UserState) Response { - _, uid, err := postCreateUserUcrednetGet(r.RemoteAddr) + _, uid, _, err := postCreateUserUcrednetGet(r.RemoteAddr) if err != nil { return BadRequest("cannot get ucrednet uid: %v", err) } @@ -2308,6 +2425,8 @@ return SyncResponse(map[string]interface{}{ "base-declaration": string(asserts.Encode(bd)), }, nil) + case "can-manage-refreshes": + return SyncResponse(devicestate.CanManageRefreshes(st), nil) default: return BadRequest("unknown debug action: %v", a.Action) } @@ -2353,10 +2472,19 @@ return BadRequest("snapctl cannot run without args") } + _, uid, _, err := runSnapctlUcrednetGet(r.RemoteAddr) + if err != nil { + return Forbidden("cannot get remote user: %s", err) + } + // we only allow "get" from regular users in snapctl + if uid != 0 && snapctlOptions.Args[0] != "get" { + return Forbidden("cannot use %q with uid %d, try with sudo", snapctlOptions.Args[0], uid) + } + // Ignore missing context error to allow 'snapctl -h' without a context; // Actual context is validated later by get/set. context, _ := c.d.overlord.HookManager().Context(snapctlOptions.ContextID) - stdout, stderr, err := ctlcmd.Run(context, snapctlOptions.Args) + stdout, stderr, err := ctlcmdRun(context, snapctlOptions.Args) if err != nil { if e, ok := err.(*flags.Error); ok && e.Type == flags.ErrHelp { stdout = []byte(e.Error()) @@ -2382,7 +2510,7 @@ } func getUsers(c *Command, r *http.Request, user *auth.UserState) Response { - _, uid, err := postCreateUserUcrednetGet(r.RemoteAddr) + _, uid, _, err := postCreateUserUcrednetGet(r.RemoteAddr) if err != nil { return BadRequest("cannot get ucrednet uid: %v", err) } @@ -2482,10 +2610,7 @@ summary = fmt.Sprintf(i18n.G("Prefer aliases of snap %q"), a.Snap) } - change := st.NewChange(a.Action, summary) - change.Set("snap-names", []string{a.Snap}) - change.AddAll(taskset) - + change := newChange(st, a.Action, summary, []*state.TaskSet{taskset}, []string{a.Snap}) st.EnsureBefore(0) return AsyncResponse(nil, &Meta{Change: change.ID()}) @@ -2635,7 +2760,7 @@ return InternalError("no services found") } - ts, err := servicestate.Control(st, appInfos, &inst) + ts, err := servicestate.Control(st, appInfos, &inst, nil) if err != nil { if _, ok := err.(servicestate.ServiceActionConflictError); ok { return Conflict(err.Error()) diff -Nru snapd-2.29.4.2+17.10/daemon/api_test.go snapd-2.31.1+17.10/daemon/api_test.go --- snapd-2.29.4.2+17.10/daemon/api_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/daemon/api_test.go 2018-02-13 07:54:57.000000000 +0000 @@ -61,11 +61,12 @@ "github.com/snapcore/snapd/overlord/assertstate" "github.com/snapcore/snapd/overlord/auth" "github.com/snapcore/snapd/overlord/configstate/config" + "github.com/snapcore/snapd/overlord/hookstate" + "github.com/snapcore/snapd/overlord/hookstate/ctlcmd" "github.com/snapcore/snapd/overlord/ifacestate" "github.com/snapcore/snapd/overlord/servicestate" "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" - "github.com/snapcore/snapd/overlord/storestate" "github.com/snapcore/snapd/release" "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap/snaptest" @@ -131,7 +132,7 @@ return s.rsnaps[0], s.err } -func (s *apiBaseSuite) ListRefresh(snaps []*store.RefreshCandidate, user *auth.UserState) ([]*snap.Info, error) { +func (s *apiBaseSuite) ListRefresh(snaps []*store.RefreshCandidate, user *auth.UserState, flags *store.RefreshOptions) ([]*snap.Info, error) { s.refreshCandidates = snaps s.user = user @@ -174,7 +175,7 @@ func (s *apiBaseSuite) systemctl(args ...string) (buf []byte, err error) { s.sysctlArgses = append(s.sysctlArgses, args) - if args[0] != "show" { + if args[0] != "show" && args[0] != "start" && args[0] != "stop" && args[0] != "restart" { panic(fmt.Sprintf("unexpected systemctl call: %v", args)) } @@ -280,7 +281,7 @@ st := d.overlord.State() st.Lock() defer st.Unlock() - storestate.ReplaceStore(st, s) + snapstate.ReplaceStore(st, s) // mark as already seeded st.Set("seeded", true) // registered @@ -315,7 +316,7 @@ assertstate.Manager(st) st.Lock() defer st.Unlock() - storestate.ReplaceStore(st, s) + snapstate.ReplaceStore(st, s) s.d = d return d @@ -338,6 +339,10 @@ return &fakeSnapManager{runner: runner} } +func (m *fakeSnapManager) KnownTaskKinds() []string { + return m.runner.KnownTaskKinds() +} + func (m *fakeSnapManager) Ensure() error { m.runner.Ensure() return nil @@ -457,7 +462,7 @@ snapst.Active = active snapst.Sequence = append(snapst.Sequence, &snapInfo.SideInfo) snapst.Current = snapInfo.SideInfo.Revision - snapst.Channel = "beta" + snapst.Channel = "stable" snapstate.Set(st, name, &snapst) } @@ -535,6 +540,20 @@ `), } + var snapst snapstate.SnapState + st := s.d.overlord.State() + st.Lock() + err := snapstate.Get(st, "foo", &snapst) + st.Unlock() + c.Assert(err, check.IsNil) + + // modify state + snapst.Channel = "beta" + snapst.IgnoreValidation = true + st.Lock() + snapstate.Set(st, "foo", &snapst) + st.Unlock() + req, err := http.NewRequest("GET", "/v2/snaps/foo", nil) c.Assert(err, check.IsNil) rsp, ok := getSnapInfo(snapCmd, req, nil).(*resp) @@ -556,24 +575,25 @@ Type: ResponseTypeSync, Status: 200, Result: &client.Snap{ - ID: "foo-id", - Name: "foo", - Revision: snap.R(10), - Version: "v1", - Channel: "stable", - TrackingChannel: "beta", - Title: "title", - Summary: "summary", - Description: "description", - Developer: "bar", - Status: "active", - Icon: "/v2/icons/foo/icon", - Type: string(snap.TypeApp), - Private: false, - DevMode: false, - JailMode: false, - Confinement: string(snap.StrictConfinement), - TryMode: false, + ID: "foo-id", + Name: "foo", + Revision: snap.R(10), + Version: "v1", + Channel: "stable", + TrackingChannel: "beta", + IgnoreValidation: true, + Title: "title", + Summary: "summary", + Description: "description", + Developer: "bar", + Status: "active", + Icon: "/v2/icons/foo/icon", + Type: string(snap.TypeApp), + Private: false, + DevMode: false, + JailMode: false, + Confinement: string(snap.StrictConfinement), + TryMode: false, Apps: []client.AppInfo{ { Snap: "foo", Name: "cmd", @@ -718,6 +738,8 @@ "storeUserInfo", "postCreateUserUcrednetGet", "ensureStateSoon", + "ctlcmdRun", + "runSnapctlUcrednetGet", } c.Check(found, check.Equals, len(api)+len(exceptions), check.Commentf(`At a glance it looks like you've not added all the Commands defined in api to the api list. If that is not the case, please add the exception to the "exceptions" list in this test.`)) @@ -754,7 +776,17 @@ rec := httptest.NewRecorder() c.Check(sysInfoCmd.Path, check.Equals, "/v2/system-info") - s.daemon(c).Version = "42b1" + d := s.daemon(c) + d.Version = "42b1" + + // set both legacy and new refresh schedules. new one takes priority + st := d.overlord.State() + st.Lock() + tr := config.NewTransaction(st) + tr.Set("core", "refresh.schedule", "00:00-9:00/12:00-13:00") + tr.Set("core", "refresh.timer", "8:00~9:00/2") + tr.Commit() + st.Unlock() restore := release.MockReleaseInfo(&release.OS{ID: "distro-id", VersionID: "1.2"}) defer restore() @@ -781,7 +813,8 @@ "snap-bin-dir": dirs.SnapBinariesDir, }, "refresh": map[string]interface{}{ - "schedule": "", + // only the "timer" field + "timer": "8:00~9:00/2", }, "confinement": "partial", } @@ -796,13 +829,67 @@ c.Check(rsp.Result, check.DeepEquals, expected) } -func (s *apiSuite) makeMyAppsServer(statusCode int, data string) *httptest.Server { - mockMyAppsServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { +func (s *apiSuite) TestSysInfoLegacyRefresh(c *check.C) { + rec := httptest.NewRecorder() + + d := s.daemon(c) + d.Version = "42b1" + + restore := release.MockReleaseInfo(&release.OS{ID: "distro-id", VersionID: "1.2"}) + defer restore() + restore = release.MockOnClassic(true) + defer restore() + restore = release.MockForcedDevmode(true) + defer restore() + + // set the legacy refresh schedule + st := d.overlord.State() + st.Lock() + tr := config.NewTransaction(st) + tr.Set("core", "refresh.schedule", "00:00-9:00/12:00-13:00") + tr.Set("core", "refresh.timer", "") + tr.Commit() + st.Unlock() + + sysInfoCmd.GET(sysInfoCmd, nil, nil).ServeHTTP(rec, nil) + c.Check(rec.Code, check.Equals, 200) + c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json") + + expected := map[string]interface{}{ + "series": "16", + "version": "42b1", + "os-release": map[string]interface{}{ + "id": "distro-id", + "version-id": "1.2", + }, + "on-classic": true, + "managed": false, + "locations": map[string]interface{}{ + "snap-mount-dir": dirs.SnapMountDir, + "snap-bin-dir": dirs.SnapBinariesDir, + }, + "refresh": map[string]interface{}{ + // only the "schedule" field + "schedule": "00:00-9:00/12:00-13:00", + }, + "confinement": "partial", + } + var rsp resp + c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), check.IsNil) + c.Check(rsp.Status, check.Equals, 200) + c.Check(rsp.Type, check.Equals, ResponseTypeSync) + const kernelVersionKey = "kernel-version" + delete(rsp.Result.(map[string]interface{}), kernelVersionKey) + c.Check(rsp.Result, check.DeepEquals, expected) +} + +func (s *apiSuite) makeDeveloperAPIServer(statusCode int, data string) *httptest.Server { + mockDeveloperAPIServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(statusCode) io.WriteString(w, data) })) - store.MyAppsMacaroonACLAPI = mockMyAppsServer.URL + "/acl/" - return mockMyAppsServer + store.MacaroonACLAPI = mockDeveloperAPIServer.URL + "/acl/" + return mockDeveloperAPIServer } func (s *apiSuite) makeSSOServer(statusCode int, data string) *httptest.Server { @@ -851,8 +938,8 @@ c.Assert(err, check.IsNil) responseData, err := s.makeStoreMacaroonResponse(serializedMacaroon) c.Assert(err, check.IsNil) - mockMyAppsServer := s.makeMyAppsServer(200, responseData) - defer mockMyAppsServer.Close() + mockDeveloperAPIServer := s.makeDeveloperAPIServer(200, responseData) + defer mockDeveloperAPIServer.Close() discharge := `{"discharge_macaroon": "the-discharge-macaroon-serialized-data"}` mockSSOServer := s.makeSSOServer(200, discharge) @@ -903,8 +990,8 @@ c.Assert(err, check.IsNil) responseData, err := s.makeStoreMacaroonResponse(serializedMacaroon) c.Assert(err, check.IsNil) - mockMyAppsServer := s.makeMyAppsServer(200, responseData) - defer mockMyAppsServer.Close() + mockDeveloperAPIServer := s.makeDeveloperAPIServer(200, responseData) + defer mockDeveloperAPIServer.Close() discharge := `{"discharge_macaroon": "the-discharge-macaroon-serialized-data"}` mockSSOServer := s.makeSSOServer(200, discharge) @@ -960,8 +1047,8 @@ c.Assert(err, check.IsNil) responseData, err := s.makeStoreMacaroonResponse(serializedMacaroon) c.Assert(err, check.IsNil) - mockMyAppsServer := s.makeMyAppsServer(200, responseData) - defer mockMyAppsServer.Close() + mockDeveloperAPIServer := s.makeDeveloperAPIServer(200, responseData) + defer mockDeveloperAPIServer.Close() discharge := `{"discharge_macaroon": "the-discharge-macaroon-serialized-data"}` mockSSOServer := s.makeSSOServer(200, discharge) @@ -1013,8 +1100,8 @@ c.Assert(err, check.IsNil) responseData, err := s.makeStoreMacaroonResponse(serializedMacaroon) c.Assert(err, check.IsNil) - mockMyAppsServer := s.makeMyAppsServer(200, responseData) - defer mockMyAppsServer.Close() + mockDeveloperAPIServer := s.makeDeveloperAPIServer(200, responseData) + defer mockDeveloperAPIServer.Close() discharge := `{"discharge_macaroon": "the-discharge-macaroon-serialized-data"}` mockSSOServer := s.makeSSOServer(200, discharge) @@ -1052,6 +1139,60 @@ c.Check(user.StoreDischarges, check.DeepEquals, []string{"the-discharge-macaroon-serialized-data"}) } +func (s *apiSuite) TestLoginUserNewEmailWithExistentLocalUser(c *check.C) { + d := s.daemon(c) + state := d.overlord.State() + + // setup local-only user + state.Lock() + localUser, err := auth.NewUser(state, "username", "email@test.com", "", nil) + state.Unlock() + c.Assert(err, check.IsNil) + + serializedMacaroon, err := s.makeStoreMacaroon() + c.Assert(err, check.IsNil) + responseData, err := s.makeStoreMacaroonResponse(serializedMacaroon) + c.Assert(err, check.IsNil) + mockDeveloperAPIServer := s.makeDeveloperAPIServer(200, responseData) + defer mockDeveloperAPIServer.Close() + + discharge := `{"discharge_macaroon": "the-discharge-macaroon-serialized-data"}` + mockSSOServer := s.makeSSOServer(200, discharge) + defer mockSSOServer.Close() + + // same local user, but using a new SSO account + buf := bytes.NewBufferString(`{"username": "username", "email": "new.email@test.com", "password": "password"}`) + req, err := http.NewRequest("POST", "/v2/login", buf) + c.Assert(err, check.IsNil) + req.Header.Set("Authorization", fmt.Sprintf(`Macaroon root="%s"`, localUser.Macaroon)) + + rsp := loginUser(loginCmd, req, localUser).(*resp) + + expected := userResponseData{ + ID: 1, + Username: "username", + Email: "new.email@test.com", + + Macaroon: localUser.Macaroon, + Discharges: localUser.Discharges, + } + c.Check(rsp.Status, check.Equals, 200) + c.Check(rsp.Type, check.Equals, ResponseTypeSync) + c.Assert(rsp.Result, check.FitsTypeOf, expected) + c.Check(rsp.Result, check.DeepEquals, expected) + + state.Lock() + user, err := auth.User(state, localUser.ID) + state.Unlock() + c.Check(err, check.IsNil) + c.Check(user.Username, check.Equals, "username") + c.Check(user.Email, check.Equals, expected.Email) + c.Check(user.Macaroon, check.Equals, localUser.Macaroon) + c.Check(user.Discharges, check.IsNil) + c.Check(user.StoreMacaroon, check.Equals, serializedMacaroon) + c.Check(user.StoreDischarges, check.DeepEquals, []string{"the-discharge-macaroon-serialized-data"}) +} + func (s *apiSuite) TestLogoutUser(c *check.C) { d := s.daemon(c) state := d.overlord.State() @@ -1071,7 +1212,7 @@ state.Lock() _, err = auth.User(state, user.ID) state.Unlock() - c.Check(err, check.ErrorMatches, "invalid user") + c.Check(err, check.Equals, auth.ErrInvalidUser) } func (s *apiSuite) TestLoginUserBadRequest(c *check.C) { @@ -1086,9 +1227,9 @@ c.Check(rsp.Result, check.NotNil) } -func (s *apiSuite) TestLoginUserMyAppsError(c *check.C) { - mockMyAppsServer := s.makeMyAppsServer(200, "{}") - defer mockMyAppsServer.Close() +func (s *apiSuite) TestLoginUserDeveloperAPIError(c *check.C) { + mockDeveloperAPIServer := s.makeDeveloperAPIServer(200, "{}") + defer mockDeveloperAPIServer.Close() buf := bytes.NewBufferString(`{"username": "email@.com", "password": "password"}`) req, err := http.NewRequest("POST", "/v2/login", buf) @@ -1106,8 +1247,8 @@ c.Assert(err, check.IsNil) responseData, err := s.makeStoreMacaroonResponse(serializedMacaroon) c.Assert(err, check.IsNil) - mockMyAppsServer := s.makeMyAppsServer(200, responseData) - defer mockMyAppsServer.Close() + mockDeveloperAPIServer := s.makeDeveloperAPIServer(200, responseData) + defer mockDeveloperAPIServer.Close() discharge := `{"code": "TWOFACTOR_REQUIRED"}` mockSSOServer := s.makeSSOServer(401, discharge) @@ -1129,8 +1270,8 @@ c.Assert(err, check.IsNil) responseData, err := s.makeStoreMacaroonResponse(serializedMacaroon) c.Assert(err, check.IsNil) - mockMyAppsServer := s.makeMyAppsServer(200, responseData) - defer mockMyAppsServer.Close() + mockDeveloperAPIServer := s.makeDeveloperAPIServer(200, responseData) + defer mockDeveloperAPIServer.Close() discharge := `{"code": "TWOFACTOR_FAILURE"}` mockSSOServer := s.makeSSOServer(403, discharge) @@ -1152,8 +1293,8 @@ c.Assert(err, check.IsNil) responseData, err := s.makeStoreMacaroonResponse(serializedMacaroon) c.Assert(err, check.IsNil) - mockMyAppsServer := s.makeMyAppsServer(200, responseData) - defer mockMyAppsServer.Close() + mockDeveloperAPIServer := s.makeDeveloperAPIServer(200, responseData) + defer mockDeveloperAPIServer.Close() discharge := `{"code": "INVALID_CREDENTIALS"}` mockSSOServer := s.makeSSOServer(401, discharge) @@ -1405,7 +1546,7 @@ c.Assert(snaps, check.HasLen, 1) c.Assert(snaps[0]["name"], check.Equals, "store") c.Check(snaps[0]["prices"], check.IsNil) - c.Check(snaps[0]["screenshots"], check.HasLen, 0) + c.Check(snaps[0]["screenshots"], check.IsNil) c.Check(snaps[0]["channels"], check.IsNil) c.Check(rsp.SuggestedCurrency, check.Equals, "EUR") @@ -1576,6 +1717,18 @@ c.Check(rsp.Result.(*errorResult).Message, check.Matches, "cannot use 'q' with 'select=refresh'") } +func (s *apiSuite) TestFindBadQueryReturnsCorrectErrorKind(c *check.C) { + s.err = store.ErrBadQuery + req, err := http.NewRequest("GET", "/v2/find?q=return-bad-query-please", nil) + c.Assert(err, check.IsNil) + + rsp := searchStore(findCmd, req, nil).(*resp) + c.Check(rsp.Type, check.Equals, ResponseTypeError) + c.Check(rsp.Status, check.Equals, 400) + c.Check(rsp.Result.(*errorResult).Message, check.Matches, "bad query") + c.Check(rsp.Result.(*errorResult).Kind, check.Equals, errorKindBadQuery) +} + func (s *apiSuite) TestFindPriced(c *check.C) { s.suggestedCurrency = "GBP" @@ -2618,7 +2771,7 @@ "status-code": 404., "status": "Not Found", "result": map[string]interface{}{ - "message": "no state entry for key", + "message": `snap "config-snap" is not installed`, "kind": "snap-not-found", "value": "config-snap", }, @@ -3539,12 +3692,9 @@ c.Assert(err, check.IsNil) repo := d.overlord.InterfaceManager().Repository() - plug := repo.Plug("consumer", "plug") - slot := repo.Slot("producer", "slot") - c.Assert(plug.Connections, check.HasLen, 1) - c.Assert(slot.Connections, check.HasLen, 1) - c.Check(plug.Connections[0], check.DeepEquals, interfaces.SlotRef{Snap: "producer", Name: "slot"}) - c.Check(slot.Connections[0], check.DeepEquals, interfaces.PlugRef{Snap: "consumer", Name: "plug"}) + ifaces := repo.Interfaces() + c.Assert(ifaces.Connections, check.HasLen, 1) + c.Check(ifaces.Connections, check.DeepEquals, []*interfaces.ConnRef{{interfaces.PlugRef{Snap: "consumer", Name: "plug"}, interfaces.SlotRef{Snap: "producer", Name: "slot"}}}) } func (s *apiSuite) TestConnectPlugFailureInterfaceMismatch(c *check.C) { @@ -3580,10 +3730,8 @@ "type": "error", }) repo := d.overlord.InterfaceManager().Repository() - plug := repo.Plug("consumer", "plug") - slot := repo.Slot("producer", "slot") - c.Assert(plug.Connections, check.HasLen, 0) - c.Assert(slot.Connections, check.HasLen, 0) + ifaces := repo.Interfaces() + c.Assert(ifaces.Connections, check.HasLen, 0) } func (s *apiSuite) TestConnectPlugFailureNoSuchPlug(c *check.C) { @@ -3621,8 +3769,8 @@ }) repo := d.overlord.InterfaceManager().Repository() - slot := repo.Slot("producer", "slot") - c.Assert(slot.Connections, check.HasLen, 0) + ifaces := repo.Interfaces() + c.Assert(ifaces.Connections, check.HasLen, 0) } func (s *apiSuite) TestConnectPlugFailureNoSuchSlot(c *check.C) { @@ -3660,8 +3808,8 @@ }) repo := d.overlord.InterfaceManager().Repository() - plug := repo.Plug("consumer", "plug") - c.Assert(plug.Connections, check.HasLen, 0) + ifaces := repo.Interfaces() + c.Assert(ifaces.Connections, check.HasLen, 0) } func (s *apiSuite) testDisconnect(c *check.C, plugSnap, plugName, slotSnap, slotName string) { @@ -3712,10 +3860,8 @@ st.Unlock() c.Assert(err, check.IsNil) - plug := repo.Plug("consumer", "plug") - slot := repo.Slot("producer", "slot") - c.Assert(plug.Connections, check.HasLen, 0) - c.Assert(slot.Connections, check.HasLen, 0) + ifaces := repo.Interfaces() + c.Assert(ifaces.Connections, check.HasLen, 0) } func (s *apiSuite) TestDisconnectPlugSuccess(c *check.C) { @@ -4637,8 +4783,8 @@ s.apiBaseSuite.SetUpTest(c) s.daemon(c) - postCreateUserUcrednetGet = func(string) (uint32, uint32, error) { - return 100, 0, nil + postCreateUserUcrednetGet = func(string) (uint32, uint32, string, error) { + return 100, 0, dirs.SnapdSocket, nil } s.mockUserHome = c.MkDir() userLookup = mkUserLookup(s.mockUserHome) @@ -4688,30 +4834,33 @@ restore := release.MockOnClassic(false) defer restore() + expectedEmail := "popper@lse.ac.uk" + expectedUsername := "karl" + storeUserInfo = func(user string) (*store.User, error) { - c.Check(user, check.Equals, "popper@lse.ac.uk") + c.Check(user, check.Equals, expectedEmail) return &store.User{ - Username: "karl", + Username: expectedUsername, SSHKeys: []string{"ssh1", "ssh2"}, OpenIDIdentifier: "xxyyzz", }, nil } osutilAddUser = func(username string, opts *osutil.AddUserOptions) error { - c.Check(username, check.Equals, "karl") + c.Check(username, check.Equals, expectedUsername) c.Check(opts.SSHKeys, check.DeepEquals, []string{"ssh1", "ssh2"}) c.Check(opts.Gecos, check.Equals, "popper@lse.ac.uk,xxyyzz") c.Check(opts.Sudoer, check.Equals, false) return nil } - buf := bytes.NewBufferString(`{"email": "popper@lse.ac.uk"}`) + buf := bytes.NewBufferString(fmt.Sprintf(`{"email": "%s"}`, expectedEmail)) req, err := http.NewRequest("POST", "/v2/create-user", buf) c.Assert(err, check.IsNil) rsp := postCreateUser(createUserCmd, req, nil).(*resp) expected := &userResponseData{ - Username: "karl", + Username: expectedUsername, SSHKeys: []string{"ssh1", "ssh2"}, } @@ -4725,15 +4874,17 @@ user, err := auth.User(state, 1) state.Unlock() c.Check(err, check.IsNil) - c.Check(user.Username, check.Equals, "karl") - c.Check(user.Email, check.Equals, "popper@lse.ac.uk") + c.Check(user.Username, check.Equals, expectedUsername) + c.Check(user.Email, check.Equals, expectedEmail) c.Check(user.Macaroon, check.NotNil) // auth saved to user home dir outfile := filepath.Join(s.mockUserHome, ".snap", "auth.json") c.Check(osutil.FileExists(outfile), check.Equals, true) content, err := ioutil.ReadFile(outfile) c.Check(err, check.IsNil) - c.Check(string(content), check.Equals, fmt.Sprintf(`{"macaroon":"%s"}`, user.Macaroon)) + c.Check(string(content), check.Equals, + fmt.Sprintf(`{"id":%d,"username":"%s","email":"%s","macaroon":"%s"}`, + 1, expectedUsername, expectedEmail, user.Macaroon)) } func (s *postCreateUserSuite) TestGetUserDetailsFromAssertionModelNotFound(c *check.C) { @@ -4998,8 +5149,8 @@ s.makeSystemUsers(c, []map[string]interface{}{goodUser}) - postCreateUserUcrednetGet = func(string) (uint32, uint32, error) { - return 100, 0, nil + postCreateUserUcrednetGet = func(string) (uint32, uint32, string, error) { + return 100, 0, dirs.SnapdSocket, nil } defer func() { postCreateUserUcrednetGet = ucrednetGet @@ -5191,7 +5342,7 @@ }{ {func(a *aliasAction) { a.Action = "" }, `unsupported alias action: ""`}, {func(a *aliasAction) { a.Action = "what" }, `unsupported alias action: "what"`}, - {func(a *aliasAction) { a.Snap = "lalala" }, `cannot find snap "lalala"`}, + {func(a *aliasAction) { a.Snap = "lalala" }, `snap "lalala" is not installed`}, {func(a *aliasAction) { a.Alias = ".foo" }, `invalid alias name: ".foo"`}, {func(a *aliasAction) { a.Aliases = []string{"baz"} }, `cannot interpret request, snaps can no longer be expected to declare their aliases`}, } @@ -5654,6 +5805,48 @@ c.Check(splitQS(","), check.HasLen, 0) } +func (s *apiSuite) TestSnapctlGetNoUID(c *check.C) { + buf := bytes.NewBufferString(`{"context-id": "some-context", "args": ["get", "something"]}`) + req, err := http.NewRequest("POST", "/v2/snapctl", buf) + c.Assert(err, check.IsNil) + rsp := runSnapctl(snapctlCmd, req, nil).(*resp) + c.Assert(rsp.Status, check.Equals, 403) +} + +func (s *apiSuite) TestSnapctlGetUID(c *check.C) { + var uid uint32 + _ = s.daemon(c) + + runSnapctlUcrednetGet = func(string) (uint32, uint32, string, error) { + return 100, uid, dirs.SnapSocket, nil + } + defer func() { runSnapctlUcrednetGet = ucrednetGet }() + ctlcmdRun = func(*hookstate.Context, []string) ([]byte, []byte, error) { + return nil, nil, nil + } + defer func() { ctlcmdRun = ctlcmd.Run }() + + for _, t := range []struct { + uid uint32 + cmd string + arg string + + expectedCode int + }{ + {1000, "get", "something", 200}, + {0, "get", "something", 200}, + {1000, "set", "some=thing", 403}, + {0, "set", "some=thing", 200}, + } { + uid = t.uid + buf := bytes.NewBufferString(fmt.Sprintf(`{"context-id": "some-context", "args": [%q, %q]}`, t.cmd, t.arg)) + req, err := http.NewRequest("POST", "/v2/snapctl", buf) + c.Assert(err, check.IsNil) + rsp := runSnapctl(snapctlCmd, req, nil).(*resp) + c.Assert(rsp.Status, check.Equals, t.expectedCode) + } +} + var _ = check.Suite(&postDebugSuite{}) type postDebugSuite struct { @@ -6281,5 +6474,41 @@ rsp := postApps(appsCmd, req, nil).(*resp) c.Check(rsp.Status, check.Equals, 400) c.Check(rsp.Type, check.Equals, ResponseTypeError) - c.Check(rsp.Result.(*errorResult).Message, check.Equals, `snap "snap-a" has changes in progress`) + c.Check(rsp.Result.(*errorResult).Message, check.Equals, `snap "snap-a" has "enable" change in progress`) +} + +type fakeNetError struct { + message string + timeout bool + temporary bool +} + +func (e fakeNetError) Error() string { return e.message } +func (e fakeNetError) Timeout() bool { return e.timeout } +func (e fakeNetError) Temporary() bool { return e.temporary } + +func (s *appSuite) TestErrToResponseNoSnapsDoesNotPanic(c *check.C) { + si := &snapInstruction{Action: "frobble"} + errors := []error{ + store.ErrSnapNotFound, + store.ErrNoUpdateAvailable, + store.ErrLocalSnap, + &snap.AlreadyInstalledError{Snap: "foo"}, + &snap.NotInstalledError{Snap: "foo"}, + &snapstate.SnapNeedsDevModeError{Snap: "foo"}, + &snapstate.SnapNeedsClassicError{Snap: "foo"}, + &snapstate.SnapNeedsClassicSystemError{Snap: "foo"}, + fakeNetError{message: "other"}, + fakeNetError{message: "timeout", timeout: true}, + fakeNetError{message: "temp", temporary: true}, + errors.New("some other error"), + } + + for _, err := range errors { + rsp := si.errToResponse(err) + com := check.Commentf("%v", err) + c.Check(rsp, check.NotNil, com) + status := rsp.(*resp).Status + c.Check(status/100 == 4 || status/100 == 5, check.Equals, true, com) + } } diff -Nru snapd-2.29.4.2+17.10/daemon/daemon.go snapd-2.31.1+17.10/daemon/daemon.go --- snapd-2.29.4.2+17.10/daemon/daemon.go 2017-11-30 07:12:26.000000000 +0000 +++ snapd-2.31.1+17.10/daemon/daemon.go 2018-02-13 07:54:57.000000000 +0000 @@ -102,15 +102,23 @@ return accessOK } + // isUser means we have a UID for the request isUser := false - pid, uid, err := ucrednetGet(r.RemoteAddr) + pid, uid, socket, err := ucrednetGet(r.RemoteAddr) if err == nil { isUser = true } else if err != errNoID { logger.Noticef("unexpected error when attempting to get UID: %s", err) return accessForbidden - } else if c.SnapOK { - return accessOK + } + isSnap := (socket == dirs.SnapSocket) + + // ensure that snaps can only access SnapOK things + if isSnap { + if c.SnapOK { + return accessOK + } + return accessUnauthorized } if r.Method == "GET" { @@ -292,11 +300,9 @@ } if listener, err := getListener(dirs.SnapSocket, listenerMap); err == nil { - // Note that the SnapSocket listener does not use ucrednet. We use the lack - // of remote information as an indication that the request originated with - // this socket. This listener may also be nil if that socket wasn't among + // This listener may also be nil if that socket wasn't among // the listeners, so check it before using it. - d.snapListener = listener + d.snapListener = &ucrednetListener{listener} } else { logger.Debugf("cannot get listener for %q: %v", dirs.SnapSocket, err) } diff -Nru snapd-2.29.4.2+17.10/daemon/daemon_test.go snapd-2.31.1+17.10/daemon/daemon_test.go --- snapd-2.29.4.2+17.10/daemon/daemon_test.go 2017-11-30 07:12:26.000000000 +0000 +++ snapd-2.31.1+17.10/daemon/daemon_test.go 2018-02-13 07:54:57.000000000 +0000 @@ -163,11 +163,30 @@ c.Check(cmd.canAccess(put, nil), check.Equals, accessUnauthorized) c.Check(cmd.canAccess(pst, nil), check.Equals, accessUnauthorized) c.Check(cmd.canAccess(del, nil), check.Equals, accessUnauthorized) +} - // Since this request has no RemoteAddr, it must be coming from the snap - // socket instead of the snapd one. In that case, if SnapOK is true, this - // command should be wide open for all HTTP methods. - cmd = &Command{d: newTestDaemon(c), SnapOK: true} +func (s *daemonSuite) TestSnapctlAccessSnapOKWithUser(c *check.C) { + remoteAddr := "pid=100;uid=1000;socket=" + dirs.SnapSocket + get := &http.Request{Method: "GET", RemoteAddr: remoteAddr} + put := &http.Request{Method: "PUT", RemoteAddr: remoteAddr} + pst := &http.Request{Method: "POST", RemoteAddr: remoteAddr} + del := &http.Request{Method: "DELETE", RemoteAddr: remoteAddr} + + cmd := &Command{d: newTestDaemon(c), SnapOK: true} + c.Check(cmd.canAccess(get, nil), check.Equals, accessOK) + c.Check(cmd.canAccess(put, nil), check.Equals, accessOK) + c.Check(cmd.canAccess(pst, nil), check.Equals, accessOK) + c.Check(cmd.canAccess(del, nil), check.Equals, accessOK) +} + +func (s *daemonSuite) TestSnapctlAccessSnapOKWithRoot(c *check.C) { + remoteAddr := "pid=100;uid=0;socket=" + dirs.SnapSocket + get := &http.Request{Method: "GET", RemoteAddr: remoteAddr} + put := &http.Request{Method: "PUT", RemoteAddr: remoteAddr} + pst := &http.Request{Method: "POST", RemoteAddr: remoteAddr} + del := &http.Request{Method: "DELETE", RemoteAddr: remoteAddr} + + cmd := &Command{d: newTestDaemon(c), SnapOK: true} c.Check(cmd.canAccess(get, nil), check.Equals, accessOK) c.Check(cmd.canAccess(put, nil), check.Equals, accessOK) c.Check(cmd.canAccess(pst, nil), check.Equals, accessOK) diff -Nru snapd-2.29.4.2+17.10/daemon/response.go snapd-2.31.1+17.10/daemon/response.go --- snapd-2.29.4.2+17.10/daemon/response.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/daemon/response.go 2018-01-24 20:02:44.000000000 +0000 @@ -142,6 +142,10 @@ errorKindSnapNeedsDevMode = errorKind("snap-needs-devmode") errorKindSnapNeedsClassic = errorKind("snap-needs-classic") errorKindSnapNeedsClassicSystem = errorKind("snap-needs-classic-system") + + errorKindBadQuery = errorKind("bad-query") + + errorKindNetworkTimeout = errorKind("network-timeout") ) type errorValue interface{} diff -Nru snapd-2.29.4.2+17.10/daemon/response_test.go snapd-2.31.1+17.10/daemon/response_test.go --- snapd-2.29.4.2+17.10/daemon/response_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/daemon/response_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -20,6 +20,7 @@ package daemon import ( + "encoding/json" "fmt" "io/ioutil" "net/http" @@ -97,3 +98,12 @@ c.Check(hdr.Get("Content-Disposition"), check.Equals, fmt.Sprintf("attachment; filename=%s", filename)) } + +// Due to how the protocol was defined the result must be sent, even if it is +// null. Older clients rely on this. +func (s *responseSuite) TestRespJSONWithNullResult(c *check.C) { + rj := &respJSON{Result: nil} + data, err := json.Marshal(rj) + c.Assert(err, check.IsNil) + c.Check(string(data), check.Equals, `{"type":"","status-code":0,"status":"","result":null}`) +} diff -Nru snapd-2.29.4.2+17.10/daemon/snap.go snapd-2.31.1+17.10/daemon/snap.go --- snapd-2.29.4.2+17.10/daemon/snap.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/daemon/snap.go 2017-12-01 15:51:55.000000000 +0000 @@ -325,31 +325,31 @@ // TODO: expose aliases information and state? result := &client.Snap{ - Description: localSnap.Description(), - Developer: about.publisher, - Icon: snapIcon(localSnap), - ID: localSnap.SnapID, - InstallDate: snapDate(localSnap), - InstalledSize: localSnap.Size, - Name: localSnap.Name(), - Revision: localSnap.Revision, - Status: status, - Summary: localSnap.Summary(), - Type: string(localSnap.Type), - Version: localSnap.Version, - Channel: localSnap.Channel, - TrackingChannel: snapst.Channel, - // TODO: send ignore-validation - Confinement: string(localSnap.Confinement), - DevMode: snapst.DevMode, - TryMode: snapst.TryMode, - JailMode: snapst.JailMode, - Private: localSnap.Private, - Apps: apps, - Broken: localSnap.Broken, - Contact: localSnap.Contact, - Title: localSnap.Title(), - License: localSnap.License, + Description: localSnap.Description(), + Developer: about.publisher, + Icon: snapIcon(localSnap), + ID: localSnap.SnapID, + InstallDate: snapDate(localSnap), + InstalledSize: localSnap.Size, + Name: localSnap.Name(), + Revision: localSnap.Revision, + Status: status, + Summary: localSnap.Summary(), + Type: string(localSnap.Type), + Version: localSnap.Version, + Channel: localSnap.Channel, + TrackingChannel: snapst.Channel, + IgnoreValidation: snapst.IgnoreValidation, + Confinement: string(localSnap.Confinement), + DevMode: snapst.DevMode, + TryMode: snapst.TryMode, + JailMode: snapst.JailMode, + Private: localSnap.Private, + Apps: apps, + Broken: localSnap.Broken, + Contact: localSnap.Contact, + Title: localSnap.Title(), + License: localSnap.License, } return result diff -Nru snapd-2.29.4.2+17.10/daemon/ucrednet.go snapd-2.31.1+17.10/daemon/ucrednet.go --- snapd-2.29.4.2+17.10/daemon/ucrednet.go 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/daemon/ucrednet.go 2018-02-07 18:13:02.000000000 +0000 @@ -35,7 +35,7 @@ ucrednetNobody = uint32((1 << 32) - 1) ) -func ucrednetGet(remoteAddr string) (pid uint32, uid uint32, err error) { +func ucrednetGet(remoteAddr string) (pid uint32, uid uint32, socket string, err error) { pid = ucrednetNoProcess uid = ucrednetNobody for _, token := range strings.Split(remoteAddr, ";") { @@ -53,32 +53,38 @@ break } } + if strings.HasPrefix(token, "socket=") { + socket = token[7:] + } + } if pid == ucrednetNoProcess || uid == ucrednetNobody { err = errNoID } - return pid, uid, err + return pid, uid, socket, err } type ucrednetAddr struct { net.Addr - pid string - uid string + pid string + uid string + socket string } func (wa *ucrednetAddr) String() string { - return fmt.Sprintf("pid=%s;uid=%s;%s", wa.pid, wa.uid, wa.Addr) + return fmt.Sprintf("pid=%s;uid=%s;socket=%s;%s", wa.pid, wa.uid, wa.socket, wa.Addr) } type ucrednetConn struct { net.Conn - pid string - uid string + pid string + uid string + socket string } func (wc *ucrednetConn) RemoteAddr() net.Addr { - return &ucrednetAddr{wc.Conn.RemoteAddr(), wc.pid, wc.uid} + return &ucrednetAddr{wc.Conn.RemoteAddr(), wc.pid, wc.uid, wc.socket} } type ucrednetListener struct{ net.Listener } @@ -91,7 +97,7 @@ return nil, err } - var pid, uid string + var pid, uid, socket string if ucon, ok := con.(*net.UnixConn); ok { f, err := ucon.File() if err != nil { @@ -107,7 +113,8 @@ pid = strconv.FormatUint(uint64(ucred.Pid), 10) uid = strconv.FormatUint(uint64(ucred.Uid), 10) + socket = ucon.LocalAddr().String() } - return &ucrednetConn{con, pid, uid}, err + return &ucrednetConn{con, pid, uid, socket}, err } diff -Nru snapd-2.29.4.2+17.10/daemon/ucrednet_test.go snapd-2.31.1+17.10/daemon/ucrednet_test.go --- snapd-2.29.4.2+17.10/daemon/ucrednet_test.go 2017-10-23 06:17:24.000000000 +0000 +++ snapd-2.31.1+17.10/daemon/ucrednet_test.go 2018-02-07 18:13:02.000000000 +0000 @@ -74,7 +74,7 @@ remoteAddr := conn.RemoteAddr().String() c.Check(remoteAddr, check.Matches, "pid=100;uid=42;.*") - pid, uid, err := ucrednetGet(remoteAddr) + pid, uid, _, err := ucrednetGet(remoteAddr) c.Check(pid, check.Equals, uint32(100)) c.Check(uid, check.Equals, uint32(42)) c.Check(err, check.IsNil) @@ -101,7 +101,7 @@ remoteAddr := conn.RemoteAddr().String() c.Check(remoteAddr, check.Matches, "pid=;uid=;.*") - pid, uid, err := ucrednetGet(remoteAddr) + pid, uid, _, err := ucrednetGet(remoteAddr) c.Check(pid, check.Equals, ucrednetNoProcess) c.Check(uid, check.Equals, ucrednetNobody) c.Check(err, check.Equals, errNoID) @@ -144,36 +144,37 @@ } func (s *ucrednetSuite) TestGetNoUid(c *check.C) { - pid, uid, err := ucrednetGet("pid=100;uid=;") + pid, uid, _, err := ucrednetGet("pid=100;uid=;") c.Check(err, check.Equals, errNoID) c.Check(pid, check.Equals, uint32(100)) c.Check(uid, check.Equals, ucrednetNobody) } func (s *ucrednetSuite) TestGetBadUid(c *check.C) { - pid, uid, err := ucrednetGet("pid=100;uid=hello;") + pid, uid, _, err := ucrednetGet("pid=100;uid=hello;") c.Check(err, check.NotNil) c.Check(pid, check.Equals, uint32(100)) c.Check(uid, check.Equals, ucrednetNobody) } func (s *ucrednetSuite) TestGetNonUcrednet(c *check.C) { - pid, uid, err := ucrednetGet("hello") + pid, uid, _, err := ucrednetGet("hello") c.Check(err, check.Equals, errNoID) c.Check(pid, check.Equals, ucrednetNoProcess) c.Check(uid, check.Equals, ucrednetNobody) } func (s *ucrednetSuite) TestGetNothing(c *check.C) { - pid, uid, err := ucrednetGet("") + pid, uid, _, err := ucrednetGet("") c.Check(err, check.Equals, errNoID) c.Check(pid, check.Equals, ucrednetNoProcess) c.Check(uid, check.Equals, ucrednetNobody) } func (s *ucrednetSuite) TestGet(c *check.C) { - pid, uid, err := ucrednetGet("pid=100;uid=42;") + pid, uid, socket, err := ucrednetGet("pid=100;uid=42;socket=/run/snap.socket") c.Check(err, check.IsNil) c.Check(pid, check.Equals, uint32(100)) c.Check(uid, check.Equals, uint32(42)) + c.Check(socket, check.Equals, "/run/snap.socket") } diff -Nru snapd-2.29.4.2+17.10/data/dbus/io.snapcraft.Launcher.service.in snapd-2.31.1+17.10/data/dbus/io.snapcraft.Launcher.service.in --- snapd-2.29.4.2+17.10/data/dbus/io.snapcraft.Launcher.service.in 2017-10-23 06:17:24.000000000 +0000 +++ snapd-2.31.1+17.10/data/dbus/io.snapcraft.Launcher.service.in 2018-01-24 20:02:44.000000000 +0000 @@ -1,3 +1,4 @@ [D-BUS Service] Name=io.snapcraft.Launcher Exec=@bindir@/snap userd +AssumedAppArmorLabel=unconfined diff -Nru snapd-2.29.4.2+17.10/data/dbus/io.snapcraft.Settings.service.in snapd-2.31.1+17.10/data/dbus/io.snapcraft.Settings.service.in --- snapd-2.29.4.2+17.10/data/dbus/io.snapcraft.Settings.service.in 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/data/dbus/io.snapcraft.Settings.service.in 2018-01-31 08:47:06.000000000 +0000 @@ -0,0 +1,4 @@ +[D-BUS Service] +Name=io.snapcraft.Settings +Exec=@bindir@/snap userd +AssumedAppArmorLabel=unconfined diff -Nru snapd-2.29.4.2+17.10/data/polkit/io.snapcraft.snapd.policy snapd-2.31.1+17.10/data/polkit/io.snapcraft.snapd.policy --- snapd-2.29.4.2+17.10/data/polkit/io.snapcraft.snapd.policy 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/data/polkit/io.snapcraft.snapd.policy 2018-01-24 20:02:44.000000000 +0000 @@ -26,4 +26,15 @@ auth_admin_keep + + + Connect, disconnect interfaces + Authentication is required to connect or disconnect interfaces + + auth_admin + auth_admin + auth_admin_keep + + + diff -Nru snapd-2.29.4.2+17.10/data/selinux/snappy.te snapd-2.31.1+17.10/data/selinux/snappy.te --- snapd-2.29.4.2+17.10/data/selinux/snappy.te 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.31.1+17.10/data/selinux/snappy.te 2018-01-24 20:02:44.000000000 +0000 @@ -17,7 +17,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -policy_module(snappy,0.0.12) +policy_module(snappy,0.0.13) ######################################## # @@ -215,3 +215,8 @@ corenet_udp_sendrecv_dns_port(snappy_t) corenet_tcp_connect_dns_port(snappy_t) corenet_sendrecv_dns_client_packets(snappy_t) + +# allow communication with polkit over dbus +optional_policy(` + policykit_dbus_chat(snappy_t) +') diff -Nru snapd-2.29.4.2+17.10/data/systemd/Makefile snapd-2.31.1+17.10/data/systemd/Makefile --- snapd-2.29.4.2+17.10/data/systemd/Makefile 2017-10-23 06:17:24.000000000 +0000 +++ snapd-2.31.1+17.10/data/systemd/Makefile 2018-01-31 08:47:06.000000000 +0000 @@ -14,29 +14,48 @@ # along with this program. If not, see . SNAPD_ENVIRONMENT_FILE := /etc/environment -SNAP_MOUNT_DIR := /snap +SNAP_MOUNT_DIR ?= /snap BINDIR := /usr/bin LIBEXECDIR := /usr/lib SYSTEMDSYSTEMUNITDIR := /lib/systemd/system -SYSTEMD_UNITS_GENERATED := $(patsubst %.service.in,%.service,$(wildcard *.service.in)) -SYSTEMD_UNITS := ${SYSTEMD_UNITS_GENERATED} $(wildcard *.timer) $(wildcard *.socket) +SYSTEMD_UNITS_GENERATED := $(wildcard *.in) +SYSTEMD_UNITS = $(SYSTEMD_UNITS_GENERATED:.in=) $(wildcard *.timer) $(wildcard *.socket) -%.service: %.service.in - cat $< | \ - sed s:@libexecdir@:${LIBEXECDIR}:g | \ - sed s:@SNAPD_ENVIRONMENT_FILE@:${SNAPD_ENVIRONMENT_FILE}:g | \ - sed s:@bindir@:${BINDIR}:g | \ - sed s:@SNAP_MOUNT_DIR@:${SNAP_MOUNT_DIR}:g | \ - cat > $@ +# The special mount unit for "/snap" must be named after the path and the path +# is a variable. Compute it in make for simplicity. This is equivalent to +# calling systemd-escape --path $(SNAP_MOUNT_DIR) +snap_mount_unit=$(subst /,-,$(patsubst %/,%,$(patsubst /%,%,$(SNAP_MOUNT_DIR)))).mount -all: ${SYSTEMD_UNITS} +# Yes, we want the extra unit too, thank you +SYSTEMD_UNITS += $(snap_mount_unit) -install: $(SYSTEMD_UNITS) +.PHONY: all +all: $(SYSTEMD_UNITS) + +.PHONY: install +install: $(filter-out snap.mount,$(SYSTEMD_UNITS)) $(snap_mount_unit) # NOTE: old (e.g. 14.04) GNU coreutils doesn't -D with -t - install -d -m 0755 ${DESTDIR}/${SYSTEMDSYSTEMUNITDIR} - install -m 0644 -t ${DESTDIR}/${SYSTEMDSYSTEMUNITDIR} $^ - install -m 0755 -t ${DESTDIR}/${LIBEXECDIR}/snapd snapd.core-fixup.sh + install -d -m 0755 $(DESTDIR)/$(SYSTEMDSYSTEMUNITDIR) + install -m 0644 -t $(DESTDIR)/$(SYSTEMDSYSTEMUNITDIR) $^ + install -d -m 0755 $(DESTDIR)/$(LIBEXECDIR)/snapd + install -m 0755 -t $(DESTDIR)/$(LIBEXECDIR)/snapd snapd.core-fixup.sh +.PHONY: clean clean: - rm -f ${SYSTEMD_UNITS_GENERATED} + rm -f $(SYSTEMD_UNITS_GENERATED:.in=) $(snap_mount_unit) + +%: %.in + cat $< | \ + sed s:@libexecdir@:$(LIBEXECDIR):g | \ + sed s:@SNAPD_ENVIRONMENT_FILE@:$(SNAPD_ENVIRONMENT_FILE):g | \ + sed s:@bindir@:$(BINDIR):g | \ + sed s:@SNAP_MOUNT_DIR@:$(SNAP_MOUNT_DIR):g | \ + cat > $@ + +# If SNAP_MOUNT_DIR uses non-default location then rename snap.mount +# so that the directory name is encoded in the file name. +ifneq ($(snap_mount_unit),snap.mount) +$(snap_mount_unit): snap.mount + cp $< $@ +endif diff -Nru snapd-2.29.4.2+17.10/data/systemd/snapd.refresh.service.in snapd-2.31.1+17.10/data/systemd/snapd.refresh.service.in --- snapd-2.29.4.2+17.10/data/systemd/snapd.refresh.service.in 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/data/systemd/snapd.refresh.service.in 2017-12-01 15:51:55.000000000 +0000 @@ -7,5 +7,5 @@ [Service] Type=oneshot -ExecStart=@bindir@/snap refresh +ExecStart=/bin/sh -c 'if ! @bindir@/snap refresh --time|grep "schedule:.*managed"; then @bindir@/snap refresh; fi' Environment=SNAP_REFRESH_FROM_EMERGENCY_TIMER=1 diff -Nru snapd-2.29.4.2+17.10/data/systemd/snap.mount.in snapd-2.31.1+17.10/data/systemd/snap.mount.in --- snapd-2.29.4.2+17.10/data/systemd/snap.mount.in 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/data/systemd/snap.mount.in 2018-01-31 08:47:06.000000000 +0000 @@ -0,0 +1,13 @@ +# Ensure $(SNAP_MOUNT_DIR) is mounted "shared" so lxd works correctly (LP: #1668759). +[Unit] +Description=Ensure that the snap directory shares mount events. +ConditionVirtualization=container + +[Mount] +What=@SNAP_MOUNT_DIR@ +Where=@SNAP_MOUNT_DIR@ +Type=none +Options=bind,shared + +[Install] +WantedBy=local-fs.target diff -Nru snapd-2.29.4.2+17.10/debian/changelog snapd-2.31.1+17.10/debian/changelog --- snapd-2.29.4.2+17.10/debian/changelog 2017-11-30 16:42:33.000000000 +0000 +++ snapd-2.31.1+17.10/debian/changelog 2018-02-20 16:27:42.000000000 +0000 @@ -1,9 +1,508 @@ -snapd (2.29.4.2+17.10) artful; urgency=medium +snapd (2.31.1+17.10) artful; urgency=medium - * New upstream release, LP: #1726258 - - snap-confine: use #include in snap-confine.apparmor.in + * New upstream release, LP: #1745217 + - tests: multiple autopkgtest related fixes for 18.04 + - overlord/snapstate: use spread in the default refresh schedule + - timeutil: fix scheduling on nth weekday of the month + - interfaces: miscellaneous policy updates for home, opengl, time- + control, network, et al + - cmd/snap: use proper help strings for `snap userd --help` + - interfaces/time-control,netlink-audit: adjust for util-linux + compiled with libaudit + - rules: do not static link on powerpc + - packaging: revert LDFLAGS rewrite again after building snap- + seccomp + - store: revert PR#4532 and do not display displayname + - daemon: allow `snapctl get` from any uid + - debian, snap: only static link libseccomp in snap-seccomp on + ubuntu + - daemon: improve ucrednet code for the snap.socket - -- Michael Vogt Thu, 30 Nov 2017 17:42:33 +0100 + -- Michael Vogt Tue, 20 Feb 2018 17:27:42 +0100 + +snapd (2.31) xenial; urgency=medium + + * New upstream release, LP: #1745217 + - cmd/snap-confine: allow snap-update-ns to chown things + - cmd/snap-confine: fix read-only filesystem when mounting nvidia + files in biarch + - packaging: create /var/lib/snapd/lib/{gl,gl32,vulkan} as part of + packaging + - advisor: ensure commands.db has mode 0644 and add test + - interfaces/desktop-legacy,unity7: support gtk2/gvfs gtk_show_uri() + - snap: improve validation of snap layoutsRules for validating + layouts: + - snap: fix command-not-found on core devices + - cmd/snap: display snap license information + - tests: enable content sharing test for $SNAP + - userd: add support for a simple UI that can be used from userd + - snap-confine/nvidia: Support legacy biarch trees for GLVND systems + - tests: generic detection of gadget and kernel snaps + - cmd/snap-update-ns: refactor and improve Change.Perform to handle + EROFS + - cmd/snap: improve output when snaps were found in a section or the + section is invalid + - cmd/snap-confine,tests: hide message about stale base snap + - cmd/snap-mgmt: fix out of source tree build + - strutil/quantity: new package that exports formatFoo (from + progress) + - cmd/snap: snap refresh --time with new and legacy schedules + - state: unknown tasks handler + - cmd/snap-confine,data/systemd: fix removal of snaps inside LXD + - snap: add io.snapcraft.Settings to `snap userd` + - spread: remove more EOLed releases + - snap: tidy up top-level help output + - snap: fix race in `snap run --strace` + - tests: update "searching" test to match store changes + - store: use the "publisher" when populating the "publisher" field + - snap: make `snap find --section` show all sections + - tests: new test to validate location control interface + - many: add new `snap refresh --amend ` command + - tests/main/kernel-snap-refresh-on-core: skip the whole test if + edge and stable are the same version + - tests: set test kernel-snap-refresh-on-core to manual + - tests: new spread test for interface gpg-keys + - packaging/fedora: Merge changes from Fedora Dist-Git plus trivial + fix + - interfaces: miscellaneous policy updates + - interfaces/builtin: Replace Solus support with GLVND support + - tests/main/kernel-snap-refresh-on-core: do not fail if edge and + stable kernels are the same version + - snap: add `snap run --strace` to be able to strace snap apps + - tests: new spread test for ssh-keys interface + - errtracker: include detected virtualisation + - tests: add new kernel refresh/revert test for spread-cron + - interfaces/builtin: blacklist zigbee dongle + - cmd/snap-confine: discard stale mount namespaces + - cmd: remove unused execArg0/execEnv + - snap,interfaces/mount: disallow nobody/nogroup + - cmd/snap: improve `snap aliases` output when no aliases are + defined + - tests/lib/snaps/test-snapd-service: refactor service reload + - tests: new spread test for gpg-public-keys interface + - tests: new spread test for ssh-public-keys interface + - spread: setup machine creation on Linode + - interfaces/builtin: allow introspecting UDisks2 + - interfaces/builtin: add support for content "source" section + - tests: new spread test for netlink-audit interface + - daemon: avoid panic'ing building an error response w/no snaps + given + - interfaces/mount,snap: early support for snap layouts + - daemon: unlock state even if RefreshSchedule() fails + - arch: add "armv8l" to ubuntuArchFromKernelArch table + - tests: fix for test interface-netlink-connector + - data/dbus: add AssumedAppArmorLabel=unconfined + - advisor: use forked bolt to make it work on ppc + - overlord/snapstate: record the 'kind' of conflicting change + - dirs: fix snap mount dir on Manjaro + - overlord/{snapstate,configstate}, daemon: introduce refresh.timer, + fallback to refresh.schedule + - config: add support for `snap set core proxy.no_proxy=...` + - snap-mgmt: extend spread tests, stop, disable and cleanup snap + services + - spread.yaml: add fedora 27 + - cmd/snap-confine: allow snap-update-ns to poke writable holes in + $SNAP + - packaging/14.04: move linux-generic-lts-xenial to recommends + - osutil/sys: ppc has 32-bit getuid already + - snapstate: make no autorefresh message clearer + - spread: try to enable Fedora once more + - overlord/snapstate: do a minimal sanity check on containers + - configcore: ensure config.txt has a final newline + - cmd/libsnap-confine-private: print failed mount/umount regardless + of SNAP_CONFINE_DEBUG + - debian/tests: add missing autopkgtest test dependencies for debian + - image: port ini handling to goconfigparser + - tests/main/snap-service-after-before: add test for after/before + service ordering + - tests: enabling opensuse for tests + - tests: update auto-refresh-private to match messages from current + master + - dirs: check if distro 'is like' fedora when picking path to + libexecdir + - tests: fix "job canceled" issue and improve cleanup for snaps + - cmd/libsnap-confine-private: add debug build of libsnap-confine- + private.a, link it into snap-confine-debug + - vendor: remove x/sys/unix to fix builds on arm64 and powerpc + - image: let consume snapcraft export-login files from tooling + - interfaces/mir: allow Wayland socket and non-root sockets + - interfaces/builtin: use snap.{Plug,Slot}Info over + interfaces.{Plug,Slot} + - tests: add simple snap-mgmt test + - wrappers: autogenerate After/Before in systemd's service files for + apps + - snap: add usage hints in `snap download` + - snap: provide more meaningful errors for installMany and friends + - cmd/snap: show header/footer when `snap find` is used without + arguments + - overlord/snapstate: for Enable's tasks refer to the first task + with snap-setup, do not duplicate + - tests: add hard-coded fully expired macaroons to run related tests + - cmd/snap-update-ns: new test features + - cmd/snap-update-ns: we don't want to bind mount symlinks + - interfaces/mount: test OptsToCommonFlags, filter out x-snapd. + options + - cmd/snap-update-ns: untangle upcoming cyclic initialization + - client, daemon: update user's email when logging in with new + account + - tests: ensure snap-confine apparmor profile is parsable + - snap: do not leak internal errors on install/refresh etc + - snap: fix missing error check when multiple snaps are refreshed + - spread: trying to re-enable tests on Fedora + - snap: fix gadget.yaml parsing for multi volume gadgets + - snap: give the snap.Container interface a Walk method + - snap: rename `snap advise-command` to `snap advise-snap --command` + - overlord/snapstate: no refresh just for hints if there was a + recent regular full refresh + - progress: switch ansimeter's Spin() to use a spinner + - snap: support `command-not-found` symlink for `snap advise- + command` + - daemon: store email, ID and macaroon when creating a new user + - snap: app startup after/before validation + - timeutil: refresh timer take 2 + - store, daemon/api: Rename MyAppsServer, point to + dashboard.snapcraft.io instead + - tests: use "quiet" helper instead of "dnf -q" to get errors on + failures + - cmd/snap-update-ns: improve mocking for tests + - many: implement the advisor backend, populate it from the store + - tests: make less calls to the package manager + - tests/main/confinement-classic: enable the test on Fedora + - snap: do not leak internal network errors to the user + - snap: use stdout instead of stderr for "fetching" message + - tests: fix test whoami, share successful_login.exp + - many: refresh with appropriate creds + - snap: add new `snap advice-command` skeleton + - tests: add test that ensures we never parse versions as numbers + - overlord/snapstate: override Snapstate.UserID in refresh if the + installing user is gone + - interfaces: allow socket "shutdown" syscall in default profile + - snap: print friendly message if `snap keys` is empty + - cmd/snap-update-ns: add execWritableMimic + - snap: make `snap info invalid-snap` output more user friendly + - cmd/snap, tests/main/classic-confinement: fix snap-exec path when + running under classic confinement + - overlord/ifacestate: fix disable/enable cycle to setup security + - snap: fix snap find " " output + - daemon: add new polkit action to manage interfaces + - packaging/arch: disable services when removing + - asserts/signtool: support for building tools on top that fill- + in/compute some headers + - cmd: clarify "This leaves %s tracking %s." message + - daemon: return "bad-query" error kind for store.ErrBadQuery + - taskrunner/many: KnownTaskKinds helper + - tests/main/interfaces-fuse_support: fix confinement, allow + unmount, fix spread tests + - snap: use the -no-fragments mksquashfs option + - data/selinux: allow messages from policykit + - tests: fix catalog-update wait loop + - tests/lib/prepare-restore: disable rate limiting in journald + - tests: change interfaces-fuse_support to be debug friendly + - tests/main/postrm-purge: stop snapd before purge + - This is an example of test log:https://paste.ubuntu.com/26215170/ + - tests/main/interfaces-fuse_support: dump more debugging + information + - interfaces/dbus: adjust slot policy for listen, accept and accept4 + syscalls + - tests: save the snapd-state without compression + - tests/main/searching: handle changes in featured snaps list + - overlord/snapstate: fix auto-refresh summary for 2 snaps + - overlord/auth,daemon: introduce an explicit auth.ErrInvalidUser + - interfaces: add /proc/partitions to system-observe (This addresses + LP#1708527.) + - tests/lib: introduce helpers for setting up /dev/random using + /dev/urandom in project prepare + - tests: new test for interface network status + - interfaces: interfaces: also add an app/hook-specific udev RUN + rule for hotplugging + - tests: fix external backend for tests that need DEBUG output + - tests: do not disable refresh timer on external backend + - client: send all snap related bool json fields + - interfaces/desktop,unity7: allow status/activate/lock of + screensavers + - tests/main: source mkpinentry.sh + - tests: fix security-device-cgroups-serial-port test for rpi and db + - cmd/snap-mgmt: add more directories for cleanup and refactor + purge() code + - snap: YAML and data structures for app before/after ordering + - tests: set TRUST_TEST_KEYS=false for all the external backends + - packaging/arch: install snap-mgmt tool + - tests: add support on tests for cm3 gadget + - interfaces/removable-media: also allow 'k' (lock) + - interfaces: use ConnectedPlug/ConnectedSlot types (step 2) + - interfaces: rename sanitize methods + - devicestate: fix misbehaving test when using systemd-resolved + - interfaces: added Ref() helpers, restored more detailed error + message on spi iface + - debian: make "gnupg" a recommends + - interfaces/many: misc updates for default, browser-support, + opengl, desktop, unity7, x11 + - interfaces: PlugInfo/SlotInfo/ConnectedPlug/ConnectedSlot + attribute helpers + - interfaces: update fixme comments + - tests: make interfaces-snapd-control-with-manage more robust + - userd: generalize dbusInterface + - interfaces: use ConnectedPlug/ConnectedSlot types (step 1) + - hookstate: add compat "configure-snapd" task. + - config, overlord/snapstate, timeutil: rename ParseSchedule to + ParseLegacySchedule + - tests: adding tests for time*-control interfaces + - tests: new test to check interfaces after reboot the system + - cmd/snap-mgmt: fixes + - packaging/opensuse-42.2: package and use snap-mgmt + - corecfg: also "mask" services when disabling them + - cmd/snap-mgmt: introduce snap-mgmt tool + - configstate: simplify ConfigManager + - interfaces: add gpio-memory-control interface + - cmd: disable check-syntax-c + - packaging/arch: add bash-completion as optional dependency + - corecfg: rename package to overlord/configstate/configcore + - wrappers: fix unit tests to use dirs.SnapMountDir + - osutil/sys: reimplement getuid and chown with the right int type + - interfaces-netlink-connector: fix sourcing snaps.sh + + -- Michael Vogt Tue, 06 Feb 2018 09:43:22 +0100 + +snapd (2.30) xenial; urgency=medium + + * New upstream release, LP: #1735344 + - tests: set TRUST_TEST_KEYS=false for all the external backends + - tests: fix external backend for tests that need DEBUG output + - tests: do not disable refresh timer on external backend + - client: send all snap related bool json fields + - interfaces: interfaces: also add an app/hook-specific udev RUN + rule for hotplugging + - interfaces/desktop,unity7: allow status/activate/lock of + screensavers + - tests/main: source mkpinentry.sh + - devicestate: use a different nowhere domain + - interfaces: add ssh-keys, ssh-public-keys, gpg-keys and gpg-public + keys interfaces + - interfaces/many: misc updates for default, browser-support, opengl, + desktop, unity7, x11 + - devicestate: fix misbehaving test when using systemd-resolved + - interfaces/removable-media: also allow 'k' (lock) + - interfaces/many: misc updates for default, browser-support, + opengl, desktop, unity7, x11 + - corecfg: also "mask" services when disabling them + - tests: add support for autopkgtests on s390x + - snapstate: support for pre-refresh hook + - many: allow to configure core before it is installed + - devicestate: fix unkeyed fields error + - snap-confine: create mount target for lib32,vulkan on demand + - snapstate: add support for refresh.schedule=managed + - cmd/snap-update-ns: teach update logic to handle synthetic changes + - many: remove configure-snapd task again and handle internally + - snap: fix TestDirAndFileMethods() test to work with gccgo + - debian: ensure /var/lib/snapd/lib/vulkan is available + - cmd/snap-confine: use #include instead of bare include + - snapstate: store userID in snapstate + - snapd.dirs: add var/lib/snapd/lib/gl32 + - timeutil, overlod/snapstate: cleanup remaining pieces of timeutil + weekday support + - packaging/arch: install missing directories, manpages and version + info + - snapstate,store: store if a snap is a paid snap in the sideinfo + - packaging/arch: pre-create snapd directories when packaging + - tests/main/manpages: set LC_ALL=C as man may complain if the + locale is unset or unsupported + - repo: ConnectedPlug and ConnectedSlot types + - snapd: fix handling of undo in the taskrunner + - store: fix download caching and add integration test + - snapstate: move autorefresh code into autoRefresh helper + - snapctl: don't error out on start/stop/restart from configure hook + during install or refresh + - cmd/snap-update-ns: add planWritableMimic + - deamon: don't omit responses, even if null + - tests: add test for frame buffer interface + - tests/lib: fix shellcheck errors + - apparmor: generate the snap-confine re-exec profile for + AppArmor{Partial,Full} + - tests: remove obsolete workaround + - snap: use existing files in `snap download` if digest/size matches + - tests: merge pepare-project.sh into prepare-restore.sh + - tests: cache snaps to $TESTSLIB/cache + - tests: set -e, -o pipefail in prepare-restore.sh + - apparmor: generate the snap-confine re-exec profile for + AppArmor{Partial,Full} + - cmd/snap-seccomp: fix uid/gid restrictions tests on Arch + - tests: document and slightly refactor prepare/restore code + - snapstate: ensure RefreshSchedule() gives accurate results + - snapstate: add new refresh-hints helper and use it + - spread.yaml,tests: move most of project-wide prepare/restore to + separate file + - timeutil: introduce helpers for weekdays and TimeOfDay + - tests: adding new test for uhid interface + - cmd/libsnap: fix parsing of empty mountinfo fields + - overlord/devicestate: best effort to go to early full retries for + registration on the like of DNS no host + - spread.yaml: bump delta ref to 2.29 + - tests: adding test to test physical memory observe interface + - cmd, errtracker: get rid of SNAP_DID_REEXEC environment + - timeutil: remove support to parse weekday schedules + - snap-confine: add workaround for snap-confine on 4.13/upstream + - store: do not log the http body for catalog updates + - snapstate: move catalogRefresh into its own helper + - spread.yaml: fix shellcheck issues and trivial refactor + - spread.yaml: move prepare-each closer to restore-each + - spread.yaml: increase workers for opensuse to 3 + - tests: force delete when tests are restore to avoid suite failure + - test: ignore /snap/README + - interfaces/opengl: also allow read on 'revision' in + /sys/devices/pci... + - interfaces/screen-inhibit-control: fix case in screen inhibit + control + - asserts/sysdb: panic early if pointed to staging but staging keys + are not compiled-in + - interfaces: allow /bin/chown and fchownat to root:root + - timeutil: include test input in error message in + TestParseSchedule() + - interfaces/browser-support: adjust base declaration for auto- + connection + - snap-confine: fix snap-confine under lxd + - store: bit less aggressive retry strategy + - tests: add new `fakestore new-snap-{declaration,revision}` helpers + - cmd/snap-update-ns: add secureMkfileAll + - snap: use field names when initializing composite literals + - HACKING: fix path in snap install + - store: add support for flags in ListRefresh() + - interfaces: remove invalid plugs/slots from SnapInfo on + sanitization. + - debian: add missing udev dependency + - snap/validate: extend socket validation tests + - interfaces: add "refresh-schedule" attribute to snapd-control + - interfaces/builtin/account_control: use gid owning /etc/shadow to + setup seccomp rules + - cmd/snap-update-ns: tweak changePerform + - interfaces,tests: skip unknown plug/slot interfaces + - tests: disable interfaces-network-control-tuntap + - cmd: use a preinit_array function rather than parsing + /proc/self/cmdline + - interfaces/time*_control: explicitly deny noisy read on + /proc/1/environ + - cmd/snap-update-ns: misc cleanups + - snapd: allow hooks to have slots + - fakestore: add go-flags to prepare for `new-snap-declaration` cmd + - interfaces/browser-support: add shm path for nwjs + - many: add magic /snap/README file + - overlord/snapstate: support completion for command aliases + - tests: re-enable tun/tap test on Debian + - snap,wrappers: add support for socket activation + - repo: use PlugInfo and SlotInfo for permanent plugs/slots + - tests/interfaces-network-control-tuntap: disable on debian- + unstable for now + - cmd/snap-confine: Loosen the NVIDIA Vulkan ICD glob + - cmd/snap-update-ns: detect and report read-only filesystems + - cmd/snap-update-ns: re-factor secureMkdirAll into + secureMk{Prefix,Dir} + - run-checks, tests/lib/snaps/: shellcheck fixes + - corecfg: validate refresh.schedule when it is applied + - tests: adjust test to match stderr + - snapd: fix snap cookie bugs + - packaging/arch: do not quote MAKEFLAGS + - state: add change.LaneTasks helper + - cmd/snap-update-ns: do not assume 'nogroup' exists + - tests/lib: handle distro specific grub-editenv naming + - cmd/snap-confine: Add missing bi-arch NVIDIA filesthe + `/var/lib/snapd/lib/gl:/var/lib/snapd/lib/gl/vdpau` paths within + - cmd: Support exposing NVIDIA Vulkan ICD files to the snaps + - cmd/snap-confine: Implement full 32-bit NVIDIA driver support + - packaging/arch: packaging update + - cmd/snap-confine: Support bash as base runtime entry + - wrappers: do not error on incorrect Exec= lines + - interfaces: fix udev tagging for hooks + - tests/set-proxy-store: exclude ubuntu-core-16 via systems: key + - tests: new tests for network setup control and observe interfaces + - osutil: add helper for obtaining group ID of given file path + - daemon,overlord/snapstate: return snap-not-installed error in more + cases + - interfaces/builtin/lxd_support: allow discovering of host's os- + release + - configstate: add support for configure-snapd for + snapstate.IgnoreHookError + - tests: add a spread test for proxy.store setting together with + store assertion + - cmd/snap-seccomp: do not use group 'shadow' in tests + - asserts/assertstest: fix use of hardcoded value when the passed + or default keys should be used + - interfaces/many: misc policy updates for browser-support, cups- + control and network-status + - tests: fix xdg-open-compat + - daemon: for /v2/logs, 404 when no services are found + - packaging/fedora: Merge changes from Fedora Dist-Git + - cmd/snap-update-ns: add new helpers for mount entries + - cmd/snap-confine: Respect biarch nature of libdirs + - cmd/snap-confine: Ensure snap-confine is allowed to access os- + release + - cmd: fix re-exec bug with classic confinement for host snapd < + 2.28 + - interfaces/kmod: simplify loadModules now that errors are ignored + - tests: disable xdg-open-compat test + - tests: add test that checks core reverts on core devices + - dirs: use alt root when checking classic confinement support + without … + - interfaces/kmod: treat failure to load module as non-fatal + - cmd/snap-update-ns: fix golint and some stale comments + - corecfg: support setting proxy.store if there's a matching store + assertion + - overlord/snapstate: toggle ignore-validation as needed as we do + for channel + - tests: fix security-device-cgroup* tests on devices with + framebuffer + - interfaces/raw-usb: match on SUBSYSTEM, not SUBSYSTEMS + - interfaces: add USB interface number attribute in udev rule for + serial-port interface + - overlord/devicestate: switch to the new endpoints for registration + - snap-update-ns: add missing unit test for desired/current profile + handling + - cmd/{snap-confine,libsnap-confine-private,snap-shutdown}: cleanup + low-level C bits + - ifacestate: make interfaces.Repository available via state cache + - overlord/snapstate: cleanups around switch-snap* + - cmd/snapd,client,daemon: display ignore-validation flag through + the notes mechanism + - cmd/snap-update-ns: add logging to snap-update-ns + - many: have a timestamp on store assertions + - many: lookup and use the URL from a store assertion if one is set + for use + - tests/test-snapd-service: fix shellcheck issues + - tests: new test for hardware-random-control interface + - tests: use `snap change --last=install` in snapd-reexec test + - repo, daemon: use PlugInfo, SlotInfo + - many: handle core configuration internally instead of using the + core configure hook + - tests: refactor and expand content interface test + - snap-seccomp: skip in-kernel bpf tests for socket() in trusty/i386 + - cmd/snap-update-ns: allow Change.Perform to return changes + - snap-confine: Support biarch Linux distribution confinement + - partition/ubootenv: don't panic when uboot.env is missing the eof + marker + - cmd/snap-update-ns: allow fault injection to provide dynamic + result + - interfaces/mount: exspose mount.{Escape,Unescape} + - snapctl: added long help to stop/start/restart command + - cmd/snap-update-ns: create missing mount points automatically. + - cmd: downgrade log message in InternalToolPath to Debugf() + - tests: wait for service status change & file update in the test to + avoid races + - daemon, store: forward SSO invalid credentials errors as 401 + Unauthorized responses + - spdx: fix for WITH syntax, require a license name before the + operator + - many: reorg things in preparation to make handling of the base url + in store dynamic + - hooks/configure: queue service restarts + - cmd/snap: warn when a snap is not from the tracking channel + - interfaces/mount: add support for parsing x-snapd.{mode,uid,gid}= + - cmd/snap-confine: add detection of stale mount namespace + - interfaces: add plugRef/slotRef helpers for PlugInfo/SlotInfo + - tests: check for invalid udev files during all tests + - daemon: use newChange() in changeAliases for consistency + - servicestate: use taskset + - many: add support for /home on NFS + - packaging,spread: fix and re-enable opensuse builds + + -- Michael Vogt Mon, 18 Dec 2017 15:31:04 +0100 snapd (2.29.4.1) xenial; urgency=medium diff -Nru snapd-2.29.4.2+17.10/debian/control snapd-2.31.1+17.10/debian/control --- snapd-2.29.4.2+17.10/debian/control 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/debian/control 2018-01-24 20:02:44.000000000 +0000 @@ -60,14 +60,15 @@ Depends: adduser, apparmor (>= 2.10.95-0ubuntu2.2), ca-certificates, - gnupg1 | gnupg, openssh-client, squashfs-tools, systemd, + udev, ${misc:Depends}, ${shlibs:Depends} Replaces: ubuntu-snappy (<< 1.9), ubuntu-snappy-cli (<< 1.9), snap-confine (<< 2.23), ubuntu-core-launcher (<< 2.22), snapd-xdg-open (<= 0.0.0) Breaks: ubuntu-snappy (<< 1.9), ubuntu-snappy-cli (<< 1.9), snap-confine (<< 2.23), ubuntu-core-launcher (<< 2.22), snapd-xdg-open (<= 0.0.0) +Recommends: gnupg1 | gnupg Conflicts: snap (<< 2013-11-29-1ubuntu1) Built-Using: ${Built-Using} ${misc:Built-Using} Description: Daemon and tooling that enable snap packages diff -Nru snapd-2.29.4.2+17.10/debian/rules snapd-2.31.1+17.10/debian/rules --- snapd-2.29.4.2+17.10/debian/rules 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/debian/rules 2018-02-15 11:31:36.000000000 +0000 @@ -74,7 +74,6 @@ BUILT_USING_PACKAGES=libcap-dev libapparmor-dev libseccomp-dev else ifeq ($(shell dpkg-vendor --query Vendor),Debian) - VENDOR_ARGS=--disable-apparmor --disable-seccomp BUILT_USING_PACKAGES=libcap-dev else VENDOR_ARGS=--disable-apparmor @@ -138,6 +137,20 @@ $(shell if ldd _build/bin/snap-update-ns; then false "need static build"; fi) endif + # ensure snap-seccomp is build with a static libseccomp on Ubuntu +ifeq ($(shell dpkg-vendor --query Vendor),Ubuntu) + # (static linking on powerpc with cgo is broken) + ifneq ($(shell dpkg-architecture -qDEB_HOST_ARCH),powerpc) + sed -i "s|#cgo LDFLAGS:|#cgo LDFLAGS: /usr/lib/$(shell dpkg-architecture -qDEB_TARGET_MULTIARCH)/libseccomp.a|" _build/src/$(DH_GOPKG)/cmd/snap-seccomp/main.go + (cd _build/bin && GOPATH=$$(pwd)/.. CGO_LDFLAGS_ALLOW="/.*/libseccomp.a" go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snap-seccomp) + # ensure that libseccomp is not dynamically linked + ldd _build/bin/snap-seccomp + test "$$(ldd _build/bin/snap-seccomp | grep libseccomp)" = "" + # revert again so that the subsequent tests work + sed -i "s|#cgo LDFLAGS: /usr/lib/$(shell dpkg-architecture -qDEB_TARGET_MULTIARCH)/libseccomp.a|#cgo LDFLAGS:|" _build/src/$(DH_GOPKG)/cmd/snap-seccomp/main.go + endif +endif + # Build C bits, sadly manually cd cmd && ( autoreconf -i -f ) cd cmd && ( ./configure --prefix=/usr --libexecdir=/usr/lib/snapd $(VENDOR_ARGS)) @@ -192,15 +205,7 @@ $(MAKE) -C cmd install DESTDIR=$(CURDIR)/debian/tmp # Rename the apparmor profile, see dh_apparmor call above for an explanation. -ifeq ($(shell dpkg-vendor --query Vendor),Ubuntu) mv $(CURDIR)/debian/tmp/etc/apparmor.d/usr.lib.snapd.snap-confine $(CURDIR)/debian/tmp/etc/apparmor.d/usr.lib.snapd.snap-confine.real -else - # HACK: As we want to share the packaging bits between Debian and Ubuntu - # we have to get an empty file in place which satisfies the item in - # the snapd.install file. - install -d $(CURDIR)/debian/tmp/etc/apparmor.d - touch $(CURDIR)/debian/tmp/etc/apparmor.d/usr.lib.snapd.snap-confine.real -endif dh_install diff -Nru snapd-2.29.4.2+17.10/debian/snapd.dirs snapd-2.31.1+17.10/debian/snapd.dirs --- snapd-2.29.4.2+17.10/debian/snapd.dirs 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/debian/snapd.dirs 2017-12-01 15:51:55.000000000 +0000 @@ -1,12 +1,14 @@ snap var/cache/snapd usr/lib/snapd -var/lib/snapd/apparmor/snap-confine.d +var/lib/snapd/apparmor/snap-confine var/lib/snapd/auto-import var/lib/snapd/desktop var/lib/snapd/environment var/lib/snapd/firstboot var/lib/snapd/lib/gl +var/lib/snapd/lib/gl32 +var/lib/snapd/lib/vulkan var/lib/snapd/snaps/partial var/lib/snapd/void var/snap diff -Nru snapd-2.29.4.2+17.10/debian/snapd.install snapd-2.31.1+17.10/debian/snapd.install --- snapd-2.29.4.2+17.10/debian/snapd.install 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/debian/snapd.install 2018-01-24 20:02:44.000000000 +0000 @@ -20,8 +20,8 @@ # snap-confine stuff etc/apparmor.d/usr.lib.snapd.snap-confine.real -lib/udev/rules.d/80-snappy-assign.rules lib/udev/snappy-app-dev +usr/lib/snapd/snap-mgmt usr/lib/snapd/snap-confine usr/lib/snapd/snap-discard-ns usr/share/man/man1/snap-confine.1 diff -Nru snapd-2.29.4.2+17.10/debian/snapd.postrm snapd-2.31.1+17.10/debian/snapd.postrm --- snapd-2.29.4.2+17.10/debian/snapd.postrm 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/debian/snapd.postrm 2018-01-24 20:02:44.000000000 +0000 @@ -4,13 +4,16 @@ systemctl_stop() { unit="$1" - for i in $(seq 10); do + + echo "Stopping unit $unit" + systemctl stop -q "$unit" || true + + for i in $(seq 20); do + echo "Waiting until unit $unit is stopped [attempt $i]" if ! systemctl is-active -q "$unit"; then echo "$unit is stopped." - break + return fi - echo "Stoping $unit [attempt $i]" - systemctl stop -q "$unit" || true sleep .1 done } @@ -41,7 +44,7 @@ snap=$(grep 'Where=/snap/' "/etc/systemd/system/$unit"|cut -f3 -d/) rev=$(grep 'Where=/snap/' "/etc/systemd/system/$unit"|cut -f4 -d/) if [ -n "$snap" ]; then - echo "Removing snap $snap" + echo "Removing snap $snap and revision $rev" # aliases if [ -d /snap/bin ]; then find /snap/bin -maxdepth 1 -lname "$snap" -delete @@ -72,23 +75,34 @@ rm -f "/etc/systemd/system/multi-user.target.wants/$unit" done + # generated readme files + rm -f "/snap/README" + echo "Final directory cleanup" for d in "/snap/bin" "/snap" "/var/snap"; do + # Force remove due to directories for old revisions could still exist + rm -rf "$d" if [ -d "$d" ]; then - rmdir --ignore-fail-on-non-empty $d + echo "Cannot remove directory $d" fi done echo "Discarding preserved snap namespaces" # opportunistic as those might not be actually mounted - for mnt in /run/snapd/ns/*.mnt; do - umount -l "$mnt" || true - rm -f "$mnt" - done - for fstab in /run/snapd/ns/*.fstab; do - rm -f "$fstab" - done - umount -l /run/snapd/ns/ || true + if [ -d /run/snapd/ns ]; then + if [ $(ls /run/snapd/ns/*.mnt | wc -l) -gt 0 ]; then + for mnt in /run/snapd/ns/*.mnt; do + umount -l "$mnt" || true + rm -f "$mnt" + done + fi + if [ $(ls /run/snapd/ns/*.fstab | wc -l) -gt 0 ]; then + for fstab in /run/snapd/ns/*.fstab; do + rm -f "$fstab" + done + fi + umount -l /run/snapd/ns/ || true + fi echo "Removing extra snap-confine apparmor rules" rm -f /etc/apparmor.d/snap.core.*.usr.lib.snapd.snap-confine diff -Nru snapd-2.29.4.2+17.10/debian/tests/control snapd-2.31.1+17.10/debian/tests/control --- snapd-2.29.4.2+17.10/debian/tests/control 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/debian/tests/control 2018-01-24 20:02:44.000000000 +0000 @@ -5,5 +5,8 @@ ca-certificates, git, golang-golang-x-net-dev, + locales, openssh-server, + netcat-openbsd, + net-tools, snapd diff -Nru snapd-2.29.4.2+17.10/debian/tests/integrationtests snapd-2.31.1+17.10/debian/tests/integrationtests --- snapd-2.29.4.2+17.10/debian/tests/integrationtests 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.31.1+17.10/debian/tests/integrationtests 2018-02-20 16:15:57.000000000 +0000 @@ -17,6 +17,12 @@ fi systemctl daemon-reload +# ensure we are not get killed too easily +echo -950 > /proc/$$/oom_score_adj + +# see what mem we have (for debugging) +cat /proc/meminfo + # ensure we can do a connect to localhost echo ubuntu:ubuntu|chpasswd sed -i 's/\(PermitRootLogin\|PasswordAuthentication\)\>.*/\1 yes/' /etc/ssh/sshd_config @@ -36,3 +42,7 @@ export GOPATH=/tmp/go go get -u github.com/snapcore/spread/cmd/spread /tmp/go/bin/spread -v "autopkgtest:${ID}-${VERSION_ID}-$(dpkg --print-architecture)" + +# store journal info for inspectsion +journalctl --sync +journalctl -ab > $ADT_ARTIFACTS/journal.txt diff -Nru snapd-2.29.4.2+17.10/dirs/dirs.go snapd-2.31.1+17.10/dirs/dirs.go --- snapd-2.29.4.2+17.10/dirs/dirs.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/dirs/dirs.go 2018-01-31 08:47:06.000000000 +0000 @@ -43,7 +43,7 @@ SnapAppArmorDir string AppArmorCacheDir string SnapAppArmorAdditionalDir string - SnapAppArmorConfineDir string + SnapConfineAppArmorDir string SnapSeccompDir string SnapMountPolicyDir string SnapUdevRulesDir string @@ -75,6 +75,7 @@ SnapCacheDir string SnapNamesFile string SnapSectionsFile string + SnapCommandsDB string SnapBinariesDir string SnapServicesDir string @@ -98,6 +99,8 @@ SystemFontsDir string SystemLocalFontsDir string SystemFontconfigCacheDir string + + FreezerCgroupDir string ) const ( @@ -167,7 +170,7 @@ } GlobalRootDir = rootdir - if release.DistroLike("fedora", "arch") { + if release.DistroLike("fedora", "arch", "manjaro") { SnapMountDir = filepath.Join(rootdir, "/var/lib/snapd/snap") } else { SnapMountDir = filepath.Join(rootdir, defaultSnapMountDir) @@ -176,9 +179,9 @@ SnapDataDir = filepath.Join(rootdir, "/var/snap") SnapDataHomeGlob = filepath.Join(rootdir, "/home/*/snap/") SnapAppArmorDir = filepath.Join(rootdir, snappyDir, "apparmor", "profiles") + SnapConfineAppArmorDir = filepath.Join(rootdir, snappyDir, "apparmor", "snap-confine") AppArmorCacheDir = filepath.Join(rootdir, "/var/cache/apparmor") SnapAppArmorAdditionalDir = filepath.Join(rootdir, snappyDir, "apparmor", "additional") - SnapAppArmorConfineDir = filepath.Join(rootdir, snappyDir, "apparmor", "snap-confine.d") SnapDownloadCacheDir = filepath.Join(rootdir, snappyDir, "cache") SnapSeccompDir = filepath.Join(rootdir, snappyDir, "seccomp", "bpf") SnapMountPolicyDir = filepath.Join(rootdir, snappyDir, "mount") @@ -202,6 +205,7 @@ SnapCacheDir = filepath.Join(rootdir, "/var/cache/snapd") SnapNamesFile = filepath.Join(SnapCacheDir, "names") SnapSectionsFile = filepath.Join(SnapCacheDir, "sections") + SnapCommandsDB = filepath.Join(SnapCacheDir, "commands.db") SnapSeedDir = filepath.Join(rootdir, snappyDir, "seed") SnapDeviceDir = filepath.Join(rootdir, snappyDir, "device") @@ -228,10 +232,11 @@ LocaleDir = filepath.Join(rootdir, "/usr/share/locale") ClassicDir = filepath.Join(rootdir, "/writable/classic") - switch release.ReleaseInfo.ID { - case "fedora", "centos", "rhel": + if release.DistroLike("fedora") { + // rhel, centos, fedora and derivatives + // both rhel and centos list "fedora" in ID_LIKE DistroLibExecDir = filepath.Join(rootdir, "/usr/libexec/snapd") - default: + } else { DistroLibExecDir = filepath.Join(rootdir, "/usr/lib/snapd") } @@ -245,4 +250,6 @@ SystemFontsDir = filepath.Join(rootdir, "/usr/share/fonts") SystemLocalFontsDir = filepath.Join(rootdir, "/usr/local/share/fonts") SystemFontconfigCacheDir = filepath.Join(rootdir, "/var/cache/fontconfig") + + FreezerCgroupDir = filepath.Join(rootdir, "/sys/fs/cgroup/freezer/") } diff -Nru snapd-2.29.4.2+17.10/dirs/dirs_test.go snapd-2.31.1+17.10/dirs/dirs_test.go --- snapd-2.29.4.2+17.10/dirs/dirs_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/dirs/dirs_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -78,6 +78,9 @@ } func (s *DirsTestSuite) TestClassicConfinementSupportOnSpecificDistributions(c *C) { + // the test changes RootDir, restore correct one when retuning + defer dirs.SetRootDir("/") + for _, t := range []struct { ID string IDLike []string @@ -93,7 +96,10 @@ } { reset := release.MockReleaseInfo(&release.OS{ID: t.ID, IDLike: t.IDLike}) defer reset() - dirs.SetRootDir("/") + + // make a new root directory each time to isolate the test from + // local filesystem state and any previous test runs + dirs.SetRootDir(c.MkDir()) c.Check(dirs.SupportsClassicConfinement(), Equals, t.Expected, Commentf("unexpected result for %v", t.ID)) } } diff -Nru snapd-2.29.4.2+17.10/errtracker/errtracker.go snapd-2.31.1+17.10/errtracker/errtracker.go --- snapd-2.29.4.2+17.10/errtracker/errtracker.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/errtracker/errtracker.go 2018-01-31 08:47:06.000000000 +0000 @@ -27,7 +27,9 @@ "io/ioutil" "net/http" "os" + "os/exec" "path/filepath" + "strings" "time" "gopkg.in/mgo.v2/bson" @@ -91,8 +93,13 @@ return fmt.Sprintf("%x", md5.Sum(profileText)) } -func didSnapdReExec() string { - if osutil.GetenvBool("SNAP_DID_REEXEC") { +var didSnapdReExec = func() string { + // TODO: move this into osutil.Reexeced() ? + exe, err := os.Readlink("/proc/self/exe") + if err != nil { + return "unknown" + } + if strings.HasPrefix(exe, dirs.SnapMountDir) { return "yes" } return "no" @@ -121,6 +128,15 @@ return report(errMsg, dupSig, extra) } +func detectVirt() string { + cmd := exec.Command("systemd-detect-virt") + output, err := cmd.CombinedOutput() + if err != nil { + return "" + } + return strings.TrimSpace(string(output)) +} + func report(errMsg, dupSig string, extra map[string]string) (string, error) { if CrashDbURLBase == "" { return "", nil @@ -154,6 +170,7 @@ if coreBuildID == "" { coreBuildID = "unknown" } + detectedVirt := detectVirt() report := map[string]string{ "Architecture": arch.UbuntuArchitecture(), @@ -174,6 +191,7 @@ report[k] = v } } + report["DetectedVirt"] = detectedVirt // include md5 hashes of the apparmor conffile for easier debbuging // of not-updated snap-confine apparmor profiles diff -Nru snapd-2.29.4.2+17.10/errtracker/errtracker_test.go snapd-2.31.1+17.10/errtracker/errtracker_test.go --- snapd-2.29.4.2+17.10/errtracker/errtracker_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/errtracker/errtracker_test.go 2018-01-31 08:47:06.000000000 +0000 @@ -73,7 +73,11 @@ s.AddCleanup(errtracker.MockMachineIDPaths([]string{p})) s.AddCleanup(errtracker.MockHostSnapd(truePath)) s.AddCleanup(errtracker.MockCoreSnapd(falsePath)) - s.AddCleanup(errtracker.MockReExec(true)) + s.AddCleanup(errtracker.MockReExec(func() string { + return "yes" + })) + mockDetectVirt := testutil.MockCommand(c, "systemd-detect-virt", "echo none") + s.AddCleanup(mockDetectVirt.Restore) s.hostBuildID, err = osutil.ReadBuildID(truePath) c.Assert(err, IsNil) @@ -131,9 +135,10 @@ "Architecture": arch.UbuntuArchitecture(), "DidSnapdReExec": "yes", - "ProblemType": "Snap", - "Snap": "some-snap", - "Channel": "beta", + "ProblemType": "Snap", + "Snap": "some-snap", + "Channel": "beta", + "DetectedVirt": "none", "MD5SumSnapConfineAppArmorProfile": "7a7aa5f21063170c1991b84eb8d86de1", "MD5SumSnapConfineAppArmorProfileDpkgNew": "93b885adfe0da089cdf634904fd59f71", @@ -262,6 +267,7 @@ "ErrorMessage": "failure in script", "DuplicateSignature": "[dupSig]", "BrandID": "canonical", + "DetectedVirt": "none", }) fmt.Fprintf(w, "c14388aa-f78d-11e6-8df0-fa163eaf9b83 OOPSID") default: diff -Nru snapd-2.29.4.2+17.10/errtracker/export_test.go snapd-2.31.1+17.10/errtracker/export_test.go --- snapd-2.29.4.2+17.10/errtracker/export_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/errtracker/export_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -20,10 +20,7 @@ package errtracker import ( - "os" "time" - - "github.com/snapcore/snapd/osutil" ) func MockCrashDbURL(url string) (restorer func()) { @@ -66,18 +63,10 @@ } } -func MockReExec(didReExec bool) (restorer func()) { - old := osutil.GetenvBool("SNAP_DID_REEXEC") - if didReExec { - os.Setenv("SNAP_DID_REEXEC", "1") - } else { - os.Unsetenv("SNAP_DID_REEXEC") - } +func MockReExec(f func() string) (restorer func()) { + oldDidSnapdReExec := didSnapdReExec + didSnapdReExec = f return func() { - if old { - os.Setenv("SNAP_DID_REEXEC", "1") - } else { - os.Unsetenv("SNAP_DID_REEXEC") - } + didSnapdReExec = oldDidSnapdReExec } } diff -Nru snapd-2.29.4.2+17.10/HACKING.md snapd-2.31.1+17.10/HACKING.md --- snapd-2.29.4.2+17.10/HACKING.md 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/HACKING.md 2018-01-31 08:47:06.000000000 +0000 @@ -6,6 +6,10 @@ ## Development +### Supported Go versions + +snapd is supported on Go 1.6 onwards. + ### Setting up a GOPATH When working with the source of Go programs, you should define a path within @@ -57,7 +61,7 @@ ### Dependencies handling -Dependencies are handled via `govendor`. Get it via: +Go dependencies are handled via `govendor`. Get it via: go get -u github.com/kardianos/govendor @@ -75,6 +79,12 @@ govendor fetch github.com/path/of/dependency +Other dependencies are handled via distribution packages and you should ensure +that dependencies for your distribution are installed. For example, on Ubuntu, +run: + + sudo apt-get build-dep ./ + ### Building To build, once the sources are available and `GOPATH` is set, you can just run @@ -84,7 +94,7 @@ to get the `snap` binary in /tmp (or without -o to get it in the current working directory). Alternatively: - go install github.com/snapcore/snapd/... + go install github.com/snapcore/snapd/cmd/snap/... to have it available in `$GOPATH/bin` @@ -116,6 +126,8 @@ This will check if the source format is consistent, that it builds, all tests work as expected and that "go vet" has nothing to complain. +The source format follows the `gofmt -s` formating. Please run this on your sources files if `run-checks` complains about the format. + You can run individual test for a sub-package by changing into that directory and: go test -check.f $testname diff -Nru snapd-2.29.4.2+17.10/httputil/useragent.go snapd-2.31.1+17.10/httputil/useragent.go --- snapd-2.29.4.2+17.10/httputil/useragent.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.31.1+17.10/httputil/useragent.go 2018-02-19 16:54:28.000000000 +0000 @@ -41,7 +41,7 @@ } // SetUserAgentFromVersion sets up a user-agent string. -func SetUserAgentFromVersion(version string, extraProds ...string) { +func SetUserAgentFromVersion(version string, extraProds ...string) (restore func()) { extras := make([]string, 1, 3) extras[0] = "series " + release.Series if release.OnClassic { @@ -57,6 +57,7 @@ if len(extraProds) != 0 { extraProdStr = " " + strings.Join(extraProds, " ") } + origUserAgent := userAgent // xxx this assumes ReleaseInfo's ID and VersionID don't have weird characters // (see rfc 7231 for values of weird) // assumption checks out in practice, q.v. https://github.com/zyga/os-release-zoo @@ -64,6 +65,9 @@ strings.Join(extras, "; "), extraProdStr, release.ReleaseInfo.ID, release.ReleaseInfo.VersionID, string(arch.UbuntuArchitecture()), sanitizeKernelVersion(release.KernelVersion())) + return func() { + userAgent = origUserAgent + } } func sanitizeKernelVersion(in string) string { diff -Nru snapd-2.29.4.2+17.10/image/helpers.go snapd-2.31.1+17.10/image/helpers.go --- snapd-2.29.4.2+17.10/image/helpers.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/image/helpers.go 2018-01-24 20:02:44.000000000 +0000 @@ -22,22 +22,28 @@ // TODO: put these in appropriate package(s) once they are clarified a bit more import ( + "bytes" + "crypto" "encoding/json" "fmt" + "io/ioutil" "os" "os/signal" "path/filepath" "syscall" + "github.com/mvo5/goconfigparser" + "golang.org/x/net/context" + "github.com/snapcore/snapd/asserts" "github.com/snapcore/snapd/asserts/snapasserts" + "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/osutil" "github.com/snapcore/snapd/overlord/auth" "github.com/snapcore/snapd/progress" "github.com/snapcore/snapd/release" "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/store" - - "golang.org/x/net/context" ) // A Store can find metadata on snaps, download snaps and fetch assertions. @@ -79,23 +85,72 @@ } func readAuthFile(authFn string) (*auth.UserState, error) { - f, err := os.Open(authFn) + data, err := ioutil.ReadFile(authFn) if err != nil { - return nil, fmt.Errorf("cannot open auth file %q: %v", authFn, err) + return nil, fmt.Errorf("cannot read auth file %q: %v", authFn, err) + } + + creds, err := parseAuthFile(authFn, data) + if err != nil { + // try snapcraft login format instead + var err2 error + creds, err2 = parseSnapcraftLoginFile(authFn, data) + if err2 != nil { + trimmed := bytes.TrimSpace(data) + if len(trimmed) > 0 && trimmed[0] == '[' { + return nil, err2 + } + return nil, err + } } - defer f.Close() + return &auth.UserState{ + StoreMacaroon: creds.Macaroon, + StoreDischarges: creds.Discharges, + }, nil +} + +func parseAuthFile(authFn string, data []byte) (*authData, error) { var creds authData - dec := json.NewDecoder(f) - if err := dec.Decode(&creds); err != nil { + err := json.Unmarshal(data, &creds) + if err != nil { return nil, fmt.Errorf("cannot decode auth file %q: %v", authFn, err) } if creds.Macaroon == "" || len(creds.Discharges) == 0 { return nil, fmt.Errorf("invalid auth file %q: missing fields", authFn) } - return &auth.UserState{ - StoreMacaroon: creds.Macaroon, - StoreDischarges: creds.Discharges, + return &creds, nil +} + +func snapcraftLoginSection() string { + if osutil.GetenvBool("SNAPPY_USE_STAGING_STORE") { + return "login.staging.ubuntu.com" + } + return "login.ubuntu.com" +} + +func parseSnapcraftLoginFile(authFn string, data []byte) (*authData, error) { + errPrefix := fmt.Sprintf("invalid snapcraft login file %q", authFn) + + cfg := goconfigparser.New() + if err := cfg.ReadString(string(data)); err != nil { + return nil, fmt.Errorf("%s: %v", errPrefix, err) + } + sec := snapcraftLoginSection() + macaroon, err := cfg.Get(sec, "macaroon") + if err != nil { + return nil, fmt.Errorf("%s: %s", errPrefix, err) + } + unboundDischarge, err := cfg.Get(sec, "unbound_discharge") + if err != nil { + return nil, fmt.Errorf("%s: %v", errPrefix, err) + } + if macaroon == "" || unboundDischarge == "" { + return nil, fmt.Errorf("invalid snapcraft login file %q: empty fields", authFn) + } + return &authData{ + Macaroon: macaroon, + Discharges: []string{unboundDischarge}, }, nil } @@ -144,6 +199,15 @@ baseName := filepath.Base(snap.MountFile()) targetFn = filepath.Join(targetDir, baseName) + // check if we already have the right file + if osutil.FileExists(targetFn) { + sha3_384Dgst, size, err := osutil.FileDigest(targetFn, crypto.SHA3_384) + if err == nil && size == uint64(snap.DownloadInfo.Size) && fmt.Sprintf("%x", sha3_384Dgst) == snap.DownloadInfo.Sha3_384 { + logger.Debugf("not downloading, using existing file %s", targetFn) + return targetFn, snap, nil + } + } + pb := progress.MakeProgressBar() defer pb.Finished() diff -Nru snapd-2.29.4.2+17.10/image/image_test.go snapd-2.31.1+17.10/image/image_test.go --- snapd-2.29.4.2+17.10/image/image_test.go 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/image/image_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -710,6 +710,26 @@ c.Check(user.StoreDischarges, DeepEquals, []string{"DISCHARGE"}) } +func (s *imageSuite) TestNewToolingStoreWithAuthFromSnapcraftLoginFile(c *C) { + tmpdir := c.MkDir() + authFn := filepath.Join(tmpdir, "auth.json") + err := ioutil.WriteFile(authFn, []byte(`[login.ubuntu.com] +macaroon = MACAROON +unbound_discharge = DISCHARGE + +`), 0600) + c.Assert(err, IsNil) + + os.Setenv("UBUNTU_STORE_AUTH_DATA_FILENAME", authFn) + defer os.Unsetenv("UBUNTU_STORE_AUTH_DATA_FILENAME") + + tsto, err := image.NewToolingStore() + c.Assert(err, IsNil) + user := tsto.User() + c.Check(user.StoreMacaroon, Equals, "MACAROON") + c.Check(user.StoreDischarges, DeepEquals, []string{"DISCHARGE"}) +} + func (s *imageSuite) TestBootstrapToRootDirLocalSnapsWithStoreAsserts(c *C) { restore := image.MockTrusted(s.storeSigning.Trusted) defer restore() diff -Nru snapd-2.29.4.2+17.10/interfaces/apparmor/backend.go snapd-2.31.1+17.10/interfaces/apparmor/backend.go --- snapd-2.29.4.2+17.10/interfaces/apparmor/backend.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/apparmor/backend.go 2017-12-01 15:51:55.000000000 +0000 @@ -49,12 +49,19 @@ "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/mount" "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/osutil" "github.com/snapcore/snapd/release" "github.com/snapcore/snapd/snap" ) +var ( + procSelfMountInfo = mount.ProcSelfMountInfo + procSelfExe = "/proc/self/exe" + etcFstab = "/etc/fstab" +) + // Backend is responsible for maintaining apparmor profiles for ubuntu-core-launcher. type Backend struct{} @@ -63,6 +70,131 @@ return interfaces.SecurityAppArmor } +// Initialize prepares customized apparmor policy for snap-confine. +func (b *Backend) Initialize() error { + // NOTE: It would be nice if we could also generate the profile for + // snap-confine executing from the core snap, right here, and not have to + // do this in the Setup function below. I sadly don't think this is + // possible because snapd must be able to install a new core and only at + // that moment generate it. + + // Inspect the system and sets up local apparmor policy for snap-confine. + // Local policy is included by the system-wide policy. If the local policy + // has changed then the apparmor profile for snap-confine is reloaded. + + // Create the local policy directory if it is not there. + if err := os.MkdirAll(dirs.SnapConfineAppArmorDir, 0755); err != nil { + return fmt.Errorf("cannot create snap-confine policy directory: %s", err) + } + + // Check the /proc/self/exe symlink, this is needed below but we want to + // fail early if this fails for whatever reason. + exe, err := os.Readlink(procSelfExe) + if err != nil { + return fmt.Errorf("cannot read %s: %s", procSelfExe, err) + } + + // Location of the generated policy. + glob := "*" + policy := make(map[string]*osutil.FileState) + + // Check if NFS is mounted at or under $HOME. Because NFS is not + // transparent to apparmor we must alter our profile to counter that and + // allow snap-confine to work. + if nfs, err := isHomeUsingNFS(); err != nil { + logger.Noticef("cannot determine if NFS is in use: %v", err) + } else if nfs { + policy["nfs-support"] = &osutil.FileState{ + Content: []byte(nfsSnippet), + Mode: 0644, + } + logger.Noticef("snapd enabled NFS support, additional implicit network permissions granted") + } + + // Ensure that generated policy is what we computed above. + created, removed, err := osutil.EnsureDirState(dirs.SnapConfineAppArmorDir, glob, policy) + if err != nil { + return fmt.Errorf("cannot synchronize snap-confine policy: %s", err) + } + if len(created) == 0 && len(removed) == 0 { + // If the generated policy didn't change, we're all done. + return nil + } + + // If snapd is executing from the core snap the it means it has + // re-executed. In that case we are no longer using the copy of + // snap-confine from the host distribution but our own copy. We don't have + // to re-compile and load the updated profile as that is performed by + // setupSnapConfineReexec below. + if strings.HasPrefix(exe, dirs.SnapMountDir) { + return nil + } + + // Reload the apparmor profile of snap-confine. This points to the main + // file in /etc/apparmor.d/ as that file contains include statements that + // load any of the files placed in /var/lib/snapd/apparmor/snap-confine/. + // For historical reasons we may have a filename that ends with .real or + // not. If we do then we prefer the file ending with the name .real as + // that is the more recent name we use. + var profilePath string + for _, profileFname := range []string{"usr.lib.snapd.snap-confine.real", "usr.lib.snapd.snap-confine"} { + profilePath = filepath.Join(dirs.SystemApparmorDir, profileFname) + if _, err := os.Stat(profilePath); err != nil { + if os.IsNotExist(err) { + continue + } + return err + } + break + } + if profilePath == "" { + return fmt.Errorf("cannot find system apparmor profile for snap-confine") + } + + // We are not using apparmor.LoadProfile() because it uses other cache. + cmd := exec.Command("apparmor_parser", "--replace", + // Use no-expr-simplify since expr-simplify is actually slower on armhf (LP: #1383858) + "-O", "no-expr-simplify", + "--write-cache", "--cache-loc", dirs.SystemApparmorCacheDir, + profilePath) + + if output, err := cmd.CombinedOutput(); err != nil { + // When we cannot reload the profile then let's remove the generated + // policy. Maybe we have caused the problem so it's better to let other + // things work. + osutil.EnsureDirState(dirs.SnapConfineAppArmorDir, glob, nil) + return fmt.Errorf("cannot reload snap-confine apparmor profile: %v", osutil.OutputErr(output, err)) + } + return nil +} + +// isHomeUsingNFS returns true if NFS mounts are defined or mounted under /home. +// +// Internally /proc/self/mountinfo and /etc/fstab are interrogated (for current +// and possible mounted filesystems). If either of those describes NFS +// filesystem mounted under or beneath /home/ then the return value is true. +func isHomeUsingNFS() (bool, error) { + mountinfo, err := mount.LoadMountInfo(procSelfMountInfo) + if err != nil { + return false, fmt.Errorf("cannot parse %s: %s", procSelfMountInfo, err) + } + for _, entry := range mountinfo { + if (entry.FsType == "nfs4" || entry.FsType == "nfs") && (strings.HasPrefix(entry.MountDir, "/home/") || entry.MountDir == "/home") { + return true, nil + } + } + fstab, err := mount.LoadProfile(etcFstab) + if err != nil { + return false, fmt.Errorf("cannot parse %s: %s", etcFstab, err) + } + for _, entry := range fstab.Entries { + if (entry.Type == "nfs4" || entry.Type == "nfs") && (strings.HasPrefix(entry.Dir, "/home/") || entry.Dir == "/home") { + return true, nil + } + } + return false, nil +} + // setupSnapConfineReexec will setup apparmor profiles on a classic // system on the hosts /etc/apparmor.d directory. This is needed for // running snap-confine from the core snap. @@ -121,7 +253,7 @@ // create for policy extensions for snap-confine. This is required for the // profiles to compile but distribution package may not yet contain this // directory. - if err := os.MkdirAll(dirs.SnapAppArmorConfineDir, 0755); err != nil { + if err := os.MkdirAll(dirs.SnapConfineAppArmorDir, 0755); err != nil { return err } @@ -147,7 +279,7 @@ } // core on classic is special - if snapName == "core" && release.OnClassic && !release.ReleaseInfo.ForceDevMode() { + if snapName == "core" && release.OnClassic && release.AppArmorLevel() != release.NoAppArmor { if err := setupSnapConfineReexec(snapInfo); err != nil { logger.Noticef("cannot create host snap-confine apparmor configuration: %s", err) } @@ -268,7 +400,13 @@ // are ignoring all apparmor snippets as they may conflict with // the super-broad template we are starting with. } else { + // Check if NFS is mounted at or under $HOME. Because NFS is not + // transparent to apparmor we must alter the profile to counter that and + // allow access to SNAP_USER_* files. tagSnippets = snippetForTag + if nfs, _ := isHomeUsingNFS(); nfs { + tagSnippets += nfsSnippet + } } return tagSnippets } diff -Nru snapd-2.29.4.2+17.10/interfaces/apparmor/backend_test.go snapd-2.31.1+17.10/interfaces/apparmor/backend_test.go --- snapd-2.29.4.2+17.10/interfaces/apparmor/backend_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/apparmor/backend_test.go 2018-02-13 15:39:40.000000000 +0000 @@ -23,6 +23,7 @@ "fmt" "io/ioutil" "os" + "os/user" "path/filepath" "strings" @@ -34,6 +35,7 @@ "github.com/snapcore/snapd/interfaces/ifacetest" "github.com/snapcore/snapd/osutil" "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap/snaptest" "github.com/snapcore/snapd/testutil" ) @@ -363,6 +365,9 @@ # This is related to LP: #1666897 @{INSTALL_DIR}/core/*/{,usr/}lib/@{multiarch}/{,**/}lib*.so* m, + # For snappy reexec on 4.8+ kernels + @{INSTALL_DIR}/core/*/usr/lib/snapd/snap-exec m, + snippet } `, @@ -371,6 +376,8 @@ func (s *backendSuite) TestCombineSnippets(c *C) { restore := release.MockAppArmorLevel(release.FullAppArmor) defer restore() + restore = apparmor.MockMountInfo("") // mock away NFS detection + defer restore() // NOTE: replace the real template with a shorter variant restoreTemplate := apparmor.MockTemplate("\n" + @@ -387,7 +394,7 @@ "}\n") defer restoreClassicTemplate() for _, scenario := range combineSnippetsScenarios { - s.Iface.AppArmorPermanentSlotCallback = func(spec *apparmor.Specification, slot *interfaces.Slot) error { + s.Iface.AppArmorPermanentSlotCallback = func(spec *apparmor.Specification, slot *snap.SlotInfo) error { if scenario.snippet == "" { return nil } @@ -491,8 +498,8 @@ {"apparmor_parser", "--replace", "--write-cache", newAA[0], "--cache-loc", dirs.SystemApparmorCacheDir}, }) - // snap-confine.d was created - _, err = os.Stat(dirs.SnapAppArmorConfineDir) + // snap-confine directory was created + _, err = os.Stat(dirs.SnapConfineAppArmorDir) c.Check(err, IsNil) } @@ -524,3 +531,389 @@ // canary is gone, extra stuff is kept c.Check(l, DeepEquals, []string{dirsAreKept, symlinksAreKept}) } + +func (s *backendSuite) TestIsHomeUsingNFS(c *C) { + cases := []struct { + mountinfo, fstab string + nfs bool + errorPattern string + }{{ + // Errors from parsing mountinfo and fstab are propagated. + mountinfo: "bad syntax", + errorPattern: "cannot parse .*/mountinfo.*, .*", + }, { + fstab: "bad syntax", + errorPattern: "cannot parse .*/fstab.*, .*", + }, { + // NFSv3 {tcp,udp} and NFSv4 currently mounted at /home/zyga/nfs are recognized. + mountinfo: "1074 28 0:59 / /home/zyga/nfs rw,relatime shared:342 - nfs localhost:/srv/nfs rw,vers=3,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,mountaddr=127.0.0.1,mountvers=3,mountport=54125,mountproto=tcp,local_lock=none,addr=127.0.0.1", + nfs: true, + }, { + mountinfo: "1074 28 0:59 / /home/zyga/nfs rw,relatime shared:342 - nfs localhost:/srv/nfs rw,vers=3,rsize=32768,wsize=32768,namlen=255,hard,proto=udp,timeo=11,retrans=3,sec=sys,mountaddr=127.0.0.1,mountvers=3,mountport=47875,mountproto=udp,local_lock=none,addr=127.0.0.1", + nfs: true, + }, { + mountinfo: "680 27 0:59 / /home/zyga/nfs rw,relatime shared:478 - nfs4 localhost:/srv/nfs rw,vers=4.2,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=127.0.0.1,local_lock=none,addr=127.0.0.1", + nfs: true, + }, { + // NFSv3 {tcp,udp} and NFSv4 currently mounted at /home/zyga/nfs are ignored (not in $HOME). + mountinfo: "1074 28 0:59 / /mnt/nfs rw,relatime shared:342 - nfs localhost:/srv/nfs rw,vers=3,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,mountaddr=127.0.0.1,mountvers=3,mountport=54125,mountproto=tcp,local_lock=none,addr=127.0.0.1", + }, { + mountinfo: "1074 28 0:59 / /mnt/nfs rw,relatime shared:342 - nfs localhost:/srv/nfs rw,vers=3,rsize=32768,wsize=32768,namlen=255,hard,proto=udp,timeo=11,retrans=3,sec=sys,mountaddr=127.0.0.1,mountvers=3,mountport=47875,mountproto=udp,local_lock=none,addr=127.0.0.1", + }, { + mountinfo: "680 27 0:59 / /mnt/nfs rw,relatime shared:478 - nfs4 localhost:/srv/nfs rw,vers=4.2,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=127.0.0.1,local_lock=none,addr=127.0.0.1", + }, { + // NFS that may be mounted at /home and /home/zyga/nfs is recognized. + // Two spellings are possible, "nfs" and "nfs4" (they are equivalent + // nowadays). + fstab: "localhost:/srv/nfs /home nfs defaults 0 0", + nfs: true, + }, { + fstab: "localhost:/srv/nfs /home nfs4 defaults 0 0", + nfs: true, + }, { + fstab: "localhost:/srv/nfs /home/zyga/nfs nfs defaults 0 0", + nfs: true, + }, { + fstab: "localhost:/srv/nfs /home/zyga/nfs nfs4 defaults 0 0", + nfs: true, + }, { + // NFS that may be mounted at /mnt/nfs is ignored (not in $HOME). + fstab: "localhost:/srv/nfs /mnt/nfs nfs defaults 0 0", + }} + for _, tc := range cases { + restore := apparmor.MockMountInfo(tc.mountinfo) + defer restore() + restore = apparmor.MockEtcFstab(tc.fstab) + defer restore() + + nfs, err := apparmor.IsHomeUsingNFS() + if tc.errorPattern != "" { + c.Assert(err, ErrorMatches, tc.errorPattern, Commentf("test case %#v", tc)) + } else { + c.Assert(err, IsNil) + } + c.Assert(nfs, Equals, tc.nfs) + } +} + +// snap-confine policy when NFS is not used. +func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyNoNFS(c *C) { + // Make it appear as if NFS was not used. + restore := apparmor.MockMountInfo("") + defer restore() + restore = apparmor.MockEtcFstab("") + defer restore() + + // Intercept interaction with apparmor_parser + cmd := testutil.MockCommand(c, "apparmor_parser", "") + defer cmd.Restore() + + // Setup generated policy for snap-confine. + err := (&apparmor.Backend{}).Initialize() + c.Assert(err, IsNil) + c.Assert(cmd.Calls(), HasLen, 0) + + // Because NFS is not used there are no local policy files but the + // directory was created. + files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) + c.Assert(err, IsNil) + c.Assert(files, HasLen, 0) + + // The policy was not reloaded. + c.Assert(cmd.Calls(), HasLen, 0) +} + +func MockEnableNFSWorkaroundCondition() (restore func()) { + // Mock mountinfo and fstab so that snapd thinks that NFS workaround + // is necessary. The details don't matter here. See TestIsHomeUsingNFS + // for details about what triggers the workaround. + restore1 := apparmor.MockMountInfo("") + restore2 := apparmor.MockEtcFstab("localhost:/srv/nfs /home nfs4 defaults 0 0") + return func() { + restore1() + restore2() + } +} + +func MockDisableNFSWorkaroundCondition() (restore func()) { + // Mock mountinfo and fstab so that snapd thinks that NFS workaround is not + // necessary. The details don't matter here. See TestIsHomeUsingNFS for + // details about what triggers the workaround. + restore1 := apparmor.MockMountInfo("") + restore2 := apparmor.MockEtcFstab("") + return func() { + restore1() + restore2() + } +} + +// Ensure that both names of the snap-confine apparmor profile are supported. + +func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyWithNFS1(c *C) { + s.testSetupSnapConfineGeneratedPolicyWithNFS(c, "usr.lib.snapd.snap-confine") +} + +func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyWithNFS2(c *C) { + s.testSetupSnapConfineGeneratedPolicyWithNFS(c, "usr.lib.snapd.snap-confine.real") +} + +// snap-confine policy when NFS is used and snapd has not re-executed. +func (s *backendSuite) testSetupSnapConfineGeneratedPolicyWithNFS(c *C, profileFname string) { + // Make it appear as if NFS workaround was needed. + restore := MockEnableNFSWorkaroundCondition() + defer restore() + + // Intercept interaction with apparmor_parser + cmd := testutil.MockCommand(c, "apparmor_parser", "") + defer cmd.Restore() + + // Intercept the /proc/self/exe symlink and point it to the distribution + // executable (the path doesn't matter as long as it is not from the + // mounted core snap). This indicates that snapd is not re-executing + // and that we should reload snap-confine profile. + fakeExe := filepath.Join(s.RootDir, "fake-proc-self-exe") + err := os.Symlink("/usr/lib/snapd/snapd", fakeExe) + c.Assert(err, IsNil) + restore = apparmor.MockProcSelfExe(fakeExe) + defer restore() + + profilePath := filepath.Join(dirs.SystemApparmorDir, profileFname) + + // Create the directory where system apparmor profiles are stored and write + // the system apparmor profile of snap-confine. + c.Assert(os.MkdirAll(dirs.SystemApparmorDir, 0755), IsNil) + c.Assert(ioutil.WriteFile(profilePath, []byte(""), 0644), IsNil) + + // Setup generated policy for snap-confine. + err = (&apparmor.Backend{}).Initialize() + c.Assert(err, IsNil) + + // Because NFS is being used, we have the extra policy file. + files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) + c.Assert(err, IsNil) + c.Assert(files, HasLen, 1) + c.Assert(files[0].Name(), Equals, "nfs-support") + c.Assert(files[0].Mode(), Equals, os.FileMode(0644)) + c.Assert(files[0].IsDir(), Equals, false) + + // The policy allows network access. + data, err := ioutil.ReadFile(filepath.Join(dirs.SnapConfineAppArmorDir, files[0].Name())) + c.Assert(err, IsNil) + c.Assert(string(data), testutil.Contains, "network inet,") + c.Assert(string(data), testutil.Contains, "network inet6,") + + // The system apparmor profile of snap-confine was reloaded. + c.Assert(cmd.Calls(), HasLen, 1) + c.Assert(cmd.Calls(), DeepEquals, [][]string{{ + "apparmor_parser", "--replace", + "-O", "no-expr-simplify", + "--write-cache", + "--cache-loc", dirs.SystemApparmorCacheDir, + profilePath, + }}) +} + +// snap-confine policy when NFS is used and snapd has re-executed. +func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyWithNFSAndReExec(c *C) { + // Make it appear as if NFS workaround was needed. + restore := MockEnableNFSWorkaroundCondition() + defer restore() + + // Intercept interaction with apparmor_parser + cmd := testutil.MockCommand(c, "apparmor_parser", "") + defer cmd.Restore() + + // Intercept the /proc/self/exe symlink and point it to the snapd from the + // mounted core snap. This indicates that snapd has re-executed and + // should not reload snap-confine policy. + fakeExe := filepath.Join(s.RootDir, "fake-proc-self-exe") + err := os.Symlink(filepath.Join(dirs.SnapMountDir, "/core/1234/usr/lib/snapd/snapd"), fakeExe) + c.Assert(err, IsNil) + restore = apparmor.MockProcSelfExe(fakeExe) + defer restore() + + // Setup generated policy for snap-confine. + err = (&apparmor.Backend{}).Initialize() + c.Assert(err, IsNil) + + // Because NFS is being used, we have the extra policy file. + files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) + c.Assert(err, IsNil) + c.Assert(files, HasLen, 1) + c.Assert(files[0].Name(), Equals, "nfs-support") + c.Assert(files[0].Mode(), Equals, os.FileMode(0644)) + c.Assert(files[0].IsDir(), Equals, false) + + // The policy allows network access. + data, err := ioutil.ReadFile(filepath.Join(dirs.SnapConfineAppArmorDir, files[0].Name())) + c.Assert(err, IsNil) + c.Assert(string(data), testutil.Contains, "network inet,") + c.Assert(string(data), testutil.Contains, "network inet6,") + + // The distribution policy was not reloaded because snap-confine executes + // from core snap. This is handled separately by per-profile Setup. + c.Assert(cmd.Calls(), HasLen, 0) +} + +// Test behavior when isHomeUsingNFS fails. +func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyError1(c *C) { + // Make corrupt mountinfo. + restore := apparmor.MockMountInfo("corrupt") + defer restore() + + // Intercept interaction with apparmor_parser + cmd := testutil.MockCommand(c, "apparmor_parser", "") + defer cmd.Restore() + + // Intercept the /proc/self/exe symlink and point it to the snapd from the + // distribution. This indicates that snapd has not re-executed and should + // reload snap-confine policy. + fakeExe := filepath.Join(s.RootDir, "fake-proc-self-exe") + err := os.Symlink(filepath.Join(dirs.SnapMountDir, "/usr/lib/snapd/snapd"), fakeExe) + c.Assert(err, IsNil) + restore = apparmor.MockProcSelfExe(fakeExe) + defer restore() + + // Setup generated policy for snap-confine. + err = (&apparmor.Backend{}).Initialize() + // NOTE: Errors in determining NFS are non-fatal to prevent snapd from + // failing to operate. A warning message is logged but system operates as + // if NFS was not active. + c.Assert(err, IsNil) + + // While other stuff failed we created the policy directory and didn't + // write any files to it. + files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) + c.Assert(err, IsNil) + c.Assert(files, HasLen, 0) + + // We didn't reload the policy. + c.Assert(cmd.Calls(), HasLen, 0) +} + +// Test behavior when os.Readlink "/proc/self/exe" fails. +func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyError2(c *C) { + // Make it appear as if NFS workaround was needed. + restore := MockEnableNFSWorkaroundCondition() + defer restore() + + // Intercept interaction with apparmor_parser + cmd := testutil.MockCommand(c, "apparmor_parser", "") + defer cmd.Restore() + + // Intercept the /proc/self/exe symlink and make it point to something that + // doesn't exist (break it). + fakeExe := filepath.Join(s.RootDir, "corrupt-proc-self-exe") + restore = apparmor.MockProcSelfExe(fakeExe) + defer restore() + + // Setup generated policy for snap-confine. + err := (&apparmor.Backend{}).Initialize() + c.Assert(err, ErrorMatches, "cannot read .*corrupt-proc-self-exe: .*") + + // We didn't create the policy file. + files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) + c.Assert(err, IsNil) + c.Assert(files, HasLen, 0) + + // We didn't reload the policy though. + c.Assert(cmd.Calls(), HasLen, 0) +} + +// Test behavior when exec.Command "apparmor_parser" fails +func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyError3(c *C) { + // Make it appear as if NFS workaround was needed. + restore := MockEnableNFSWorkaroundCondition() + defer restore() + + // Intercept interaction with apparmor_parser and make it fail. + cmd := testutil.MockCommand(c, "apparmor_parser", "echo testing; exit 1") + defer cmd.Restore() + + // Intercept the /proc/self/exe symlink. + fakeExe := filepath.Join(s.RootDir, "fake-proc-self-exe") + err := os.Symlink("/usr/lib/snapd/snapd", fakeExe) + c.Assert(err, IsNil) + restore = apparmor.MockProcSelfExe(fakeExe) + defer restore() + + // Create the directory where system apparmor profiles are stored and Write + // the system apparmor profile of snap-confine. + c.Assert(os.MkdirAll(dirs.SystemApparmorDir, 0755), IsNil) + c.Assert(ioutil.WriteFile(filepath.Join(dirs.SystemApparmorDir, "usr.lib.snapd.snap-confine"), []byte(""), 0644), IsNil) + + // Setup generated policy for snap-confine. + err = (&apparmor.Backend{}).Initialize() + c.Assert(err, ErrorMatches, "cannot reload snap-confine apparmor profile: testing") + + // While created the policy file initially we also removed it so that + // no side-effects remain. + files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) + c.Assert(err, IsNil) + c.Assert(files, HasLen, 0) + + // We tried to reload the policy. + c.Assert(cmd.Calls(), HasLen, 1) +} + +// Test behavior when MkdirAll fails +func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyError4(c *C) { + // Create a directory where we would expect to find the local policy. + err := ioutil.WriteFile(dirs.SnapConfineAppArmorDir, []byte(""), 0644) + c.Assert(err, IsNil) + + // Setup generated policy for snap-confine. + err = (&apparmor.Backend{}).Initialize() + c.Assert(err, ErrorMatches, "*.: not a directory") +} + +// Test behavior when EnsureDirState fails +func (s *backendSuite) TestSetupSnapConfineGeneratedPolicyError5(c *C) { + // This test cannot run as root as root bypassed DAC checks. + u, err := user.Current() + c.Assert(err, IsNil) + if u.Uid == "0" { + c.Skip("this test cannot run as root") + } + + // Make it appear as if NFS workaround was not needed. + restore := MockDisableNFSWorkaroundCondition() + defer restore() + + // Intercept interaction with apparmor_parser and make it fail. + cmd := testutil.MockCommand(c, "apparmor_parser", "") + defer cmd.Restore() + + // Intercept the /proc/self/exe symlink. + fakeExe := filepath.Join(s.RootDir, "fake-proc-self-exe") + err = os.Symlink("/usr/lib/snapd/snapd", fakeExe) + c.Assert(err, IsNil) + restore = apparmor.MockProcSelfExe(fakeExe) + defer restore() + + // Create the snap-confine directory and put a file. Because the file name + // matches the glob generated-* snapd will attempt to remove it but because + // the directory is not writable, that operation will fail. + err = os.MkdirAll(dirs.SnapConfineAppArmorDir, 0755) + c.Assert(err, IsNil) + f := filepath.Join(dirs.SnapConfineAppArmorDir, "generated-test") + err = ioutil.WriteFile(f, []byte("spurious content"), 0644) + c.Assert(err, IsNil) + err = os.Chmod(dirs.SnapConfineAppArmorDir, 0555) + c.Assert(err, IsNil) + + // Make the directory writable for cleanup. + defer os.Chmod(dirs.SnapConfineAppArmorDir, 0755) + + // Setup generated policy for snap-confine. + err = (&apparmor.Backend{}).Initialize() + c.Assert(err, ErrorMatches, `cannot synchronize snap-confine policy: remove .*/generated-test: permission denied`) + + // The policy directory was unchanged. + files, err := ioutil.ReadDir(dirs.SnapConfineAppArmorDir) + c.Assert(err, IsNil) + c.Assert(files, HasLen, 1) + + // We didn't try to reload the policy. + c.Assert(cmd.Calls(), HasLen, 0) +} diff -Nru snapd-2.29.4.2+17.10/interfaces/apparmor/export_test.go snapd-2.31.1+17.10/interfaces/apparmor/export_test.go --- snapd-2.29.4.2+17.10/interfaces/apparmor/export_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/apparmor/export_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -20,9 +20,64 @@ package apparmor import ( + "fmt" + "io/ioutil" + "os" + "github.com/snapcore/snapd/testutil" ) +var ( + IsHomeUsingNFS = isHomeUsingNFS +) + +//MockMountInfo mocks content of /proc/self/mountinfo read by isHomeUsingNFS +func MockMountInfo(text string) (restore func()) { + old := procSelfMountInfo + f, err := ioutil.TempFile("", "mountinfo") + if err != nil { + panic(fmt.Errorf("cannot open temporary file: %s", err)) + } + if err := ioutil.WriteFile(f.Name(), []byte(text), 0644); err != nil { + panic(fmt.Errorf("cannot write mock mountinfo file: %s", err)) + } + procSelfMountInfo = f.Name() + return func() { + os.Remove(procSelfMountInfo) + procSelfMountInfo = old + } +} + +// MockEtcFstab mocks content of /etc/fstab read by isHomeUsingNFS +func MockEtcFstab(text string) (restore func()) { + old := etcFstab + f, err := ioutil.TempFile("", "fstab") + if err != nil { + panic(fmt.Errorf("cannot open temporary file: %s", err)) + } + if err := ioutil.WriteFile(f.Name(), []byte(text), 0644); err != nil { + panic(fmt.Errorf("cannot write mock fstab file: %s", err)) + } + etcFstab = f.Name() + return func() { + if etcFstab == "/etc/fstab" { + panic("respectfully refusing to remove /etc/fstab") + } + os.Remove(etcFstab) + etcFstab = old + } +} + +// MockProcSelfExe mocks the location of /proc/self/exe read by setupSnapConfineGeneratedPolicy. +func MockProcSelfExe(symlink string) (restore func()) { + old := procSelfExe + procSelfExe = symlink + return func() { + os.Remove(procSelfExe) + procSelfExe = old + } +} + // MockProfilesPath mocks the file read by LoadedProfiles() func MockProfilesPath(t *testutil.BaseTest, profiles string) { profilesPath = profiles diff -Nru snapd-2.29.4.2+17.10/interfaces/apparmor/spec.go snapd-2.31.1+17.10/interfaces/apparmor/spec.go --- snapd-2.29.4.2+17.10/interfaces/apparmor/spec.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/apparmor/spec.go 2018-01-24 20:02:44.000000000 +0000 @@ -21,6 +21,7 @@ import ( "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/snap" "sort" "strings" @@ -75,35 +76,35 @@ // Implementation of methods required by interfaces.Specification // AddConnectedPlug records apparmor-specific side-effects of having a connected plug. -func (spec *Specification) AddConnectedPlug(iface interfaces.Interface, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (spec *Specification) AddConnectedPlug(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { type definer interface { - AppArmorConnectedPlug(spec *Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + AppArmorConnectedPlug(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } if iface, ok := iface.(definer); ok { spec.securityTags = plug.SecurityTags() defer func() { spec.securityTags = nil }() - return iface.AppArmorConnectedPlug(spec, plug, plugAttrs, slot, slotAttrs) + return iface.AppArmorConnectedPlug(spec, plug, slot) } return nil } // AddConnectedSlot records mount-specific side-effects of having a connected slot. -func (spec *Specification) AddConnectedSlot(iface interfaces.Interface, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (spec *Specification) AddConnectedSlot(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { type definer interface { - AppArmorConnectedSlot(spec *Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + AppArmorConnectedSlot(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } if iface, ok := iface.(definer); ok { spec.securityTags = slot.SecurityTags() defer func() { spec.securityTags = nil }() - return iface.AppArmorConnectedSlot(spec, plug, plugAttrs, slot, slotAttrs) + return iface.AppArmorConnectedSlot(spec, plug, slot) } return nil } // AddPermanentPlug records mount-specific side-effects of having a plug. -func (spec *Specification) AddPermanentPlug(iface interfaces.Interface, plug *interfaces.Plug) error { +func (spec *Specification) AddPermanentPlug(iface interfaces.Interface, plug *snap.PlugInfo) error { type definer interface { - AppArmorPermanentPlug(spec *Specification, plug *interfaces.Plug) error + AppArmorPermanentPlug(spec *Specification, plug *snap.PlugInfo) error } if iface, ok := iface.(definer); ok { spec.securityTags = plug.SecurityTags() @@ -114,9 +115,9 @@ } // AddPermanentSlot records mount-specific side-effects of having a slot. -func (spec *Specification) AddPermanentSlot(iface interfaces.Interface, slot *interfaces.Slot) error { +func (spec *Specification) AddPermanentSlot(iface interfaces.Interface, slot *snap.SlotInfo) error { type definer interface { - AppArmorPermanentSlot(spec *Specification, slot *interfaces.Slot) error + AppArmorPermanentSlot(spec *Specification, slot *snap.SlotInfo) error } if iface, ok := iface.(definer); ok { spec.securityTags = slot.SecurityTags() diff -Nru snapd-2.29.4.2+17.10/interfaces/apparmor/spec_test.go snapd-2.31.1+17.10/interfaces/apparmor/spec_test.go --- snapd-2.29.4.2+17.10/interfaces/apparmor/spec_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/apparmor/spec_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -29,71 +29,71 @@ ) type specSuite struct { - iface *ifacetest.TestInterface - spec *apparmor.Specification - plug *interfaces.Plug - slot *interfaces.Slot + iface *ifacetest.TestInterface + spec *apparmor.Specification + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot } var _ = Suite(&specSuite{ iface: &ifacetest.TestInterface{ InterfaceName: "test", - AppArmorConnectedPlugCallback: func(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { + AppArmorConnectedPlugCallback: func(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { spec.AddSnippet("connected-plug") return nil }, - AppArmorConnectedSlotCallback: func(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { + AppArmorConnectedSlotCallback: func(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { spec.AddSnippet("connected-slot") return nil }, - AppArmorPermanentPlugCallback: func(spec *apparmor.Specification, plug *interfaces.Plug) error { + AppArmorPermanentPlugCallback: func(spec *apparmor.Specification, plug *snap.PlugInfo) error { spec.AddSnippet("permanent-plug") return nil }, - AppArmorPermanentSlotCallback: func(spec *apparmor.Specification, slot *interfaces.Slot) error { + AppArmorPermanentSlotCallback: func(spec *apparmor.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("permanent-slot") return nil }, }, - plug: &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{SuggestedName: "snap1"}, - Name: "name", - Interface: "test", - Apps: map[string]*snap.AppInfo{ - "app1": { - Snap: &snap.Info{ - SuggestedName: "snap1", - }, - Name: "app1"}}, - }, + plugInfo: &snap.PlugInfo{ + Snap: &snap.Info{SuggestedName: "snap1"}, + Name: "name", + Interface: "test", + Apps: map[string]*snap.AppInfo{ + "app1": { + Snap: &snap.Info{ + SuggestedName: "snap1", + }, + Name: "app1"}}, }, - slot: &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "snap2"}, - Name: "name", - Interface: "test", - Apps: map[string]*snap.AppInfo{ - "app2": { - Snap: &snap.Info{ - SuggestedName: "snap2", - }, - Name: "app2"}}, - }, + slotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "snap2"}, + Name: "name", + Interface: "test", + Apps: map[string]*snap.AppInfo{ + "app2": { + Snap: &snap.Info{ + SuggestedName: "snap2", + }, + Name: "app2"}}, }, }) func (s *specSuite) SetUpTest(c *C) { s.spec = &apparmor.Specification{} + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) } // The spec.Specification can be used through the interfaces.Specification interface func (s *specSuite) TestSpecificationIface(c *C) { var r interfaces.Specification = s.spec - c.Assert(r.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(r.AddConnectedSlot(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(r.AddPermanentPlug(s.iface, s.plug), IsNil) - c.Assert(r.AddPermanentSlot(s.iface, s.slot), IsNil) + c.Assert(r.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(r.AddConnectedSlot(s.iface, s.plug, s.slot), IsNil) + c.Assert(r.AddPermanentPlug(s.iface, s.plugInfo), IsNil) + c.Assert(r.AddPermanentSlot(s.iface, s.slotInfo), IsNil) c.Assert(s.spec.Snippets(), DeepEquals, map[string][]string{ "snap.snap1.app1": {"connected-plug", "permanent-plug"}, "snap.snap2.app2": {"connected-slot", "permanent-slot"}, diff -Nru snapd-2.29.4.2+17.10/interfaces/apparmor/template.go snapd-2.31.1+17.10/interfaces/apparmor/template.go --- snapd-2.29.4.2+17.10/interfaces/apparmor/template.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/apparmor/template.go 2018-02-16 20:27:57.000000000 +0000 @@ -96,6 +96,7 @@ /bin/dash ixr, /etc/bash.bashrc r, /etc/{passwd,group,nsswitch.conf} r, # very common + /etc/default/nss r, /etc/libnl-3/{classid,pktloc} r, # apps that use libnl /var/lib/extrausers/{passwd,group} r, /etc/profile r, @@ -113,6 +114,7 @@ /{,usr/}bin/bzip2 ixr, /{,usr/}bin/cat ixr, /{,usr/}bin/chmod ixr, + /{,usr/}bin/chown ixr, /{,usr/}bin/clear ixr, /{,usr/}bin/cmp ixr, /{,usr/}bin/cp ixr, @@ -123,6 +125,7 @@ /{,usr/}bin/diff{,3} ixr, /{,usr/}bin/dir ixr, /{,usr/}bin/dirname ixr, + /{,usr/}bin/du ixr, /{,usr/}bin/echo ixr, /{,usr/}bin/{,e,f,r}grep ixr, /{,usr/}bin/env ixr, @@ -181,6 +184,7 @@ /{,usr/}bin/stat ixr, /{,usr/}bin/stdbuf ixr, /{,usr/}bin/stty ixr, + /{,usr/}bin/sync ixr, /{,usr/}bin/systemd-cat ixr, /{,usr/}bin/tac ixr, /{,usr/}bin/tail ixr, @@ -305,6 +309,7 @@ @{PROC}/sys/kernel/yama/ptrace_scope r, @{PROC}/sys/kernel/shmmax r, @{PROC}/sys/fs/file-max r, + @{PROC}/sys/fs/inotify/max_* r, @{PROC}/sys/kernel/pid_max r, @{PROC}/sys/kernel/random/uuid r, @{PROC}/sys/kernel/random/boot_id r, @@ -485,4 +490,18 @@ # Read only access to the core snap to load libc from. # This is related to LP: #1666897 @{INSTALL_DIR}/core/*/{,usr/}lib/@{multiarch}/{,**/}lib*.so* m, + + # For snappy reexec on 4.8+ kernels + @{INSTALL_DIR}/core/*/usr/lib/snapd/snap-exec m, +` + +// nfsSnippet contains extra permissions necessary for snaps and snap-confine +// to operate when NFS is used. This is an imperfect solution as this grants +// some network access to all the snaps on the system. +// For tracking see https://bugs.launchpad.net/apparmor/+bug/1724903 +var nfsSnippet = ` + # snapd autogenerated workaround for systems using NFS, for details see: + # https://bugs.launchpad.net/ubuntu/+source/snapd/+bug/1662552 + network inet, + network inet6, ` diff -Nru snapd-2.29.4.2+17.10/interfaces/backend.go snapd-2.31.1+17.10/interfaces/backend.go --- snapd-2.29.4.2+17.10/interfaces/backend.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/backend.go 2017-12-01 15:51:55.000000000 +0000 @@ -67,6 +67,10 @@ // SecurityBackend abstracts interactions between the interface system and the // needs of a particular security system. type SecurityBackend interface { + // Initialize performs any initialization required by the backend. + // It is called during snapd startup process. + Initialize() error + // Name returns the name of the backend. // This is intended for diagnostic messages. Name() SecuritySystem diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/account_control.go snapd-2.31.1+17.10/interfaces/builtin/account_control.go --- snapd-2.29.4.2+17.10/interfaces/builtin/account_control.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/account_control.go 2018-01-24 20:02:44.000000000 +0000 @@ -19,6 +19,15 @@ package builtin +import ( + "strconv" + "strings" + + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/seccomp" + "github.com/snapcore/snapd/osutil" +) + const accountControlSummary = `allows managing non-system user accounts` const accountControlBaseDeclarationSlots = ` @@ -56,26 +65,58 @@ /var/log/faillog rwk, ` -// Needed because useradd uses a netlink socket -const accountControlConnectedPlugSecComp = ` -# useradd requires chowning to 'shadow' -fchown - u:root g:shadow -fchown32 - u:root g:shadow +// Needed because useradd uses a netlink socket, {{group}} is used as a +// placeholder argument for the actual ID of a group owning /etc/shadow +const accountControlConnectedPlugSecCompTemplate = ` +# useradd requires chowning to 0:'{{group}}' +fchown - u:root {{group}} +fchown32 - u:root {{group}} # from libaudit1 bind socket AF_NETLINK - NETLINK_AUDIT ` +type accountControlInterface struct { + commonInterface + secCompSnippet string +} + +func makeAccountControlSecCompSnippet() (string, error) { + gid, err := osutil.FindGidOwning("/etc/shadow") + if err != nil { + return "", err + } + + snippet := strings.Replace(accountControlConnectedPlugSecCompTemplate, + "{{group}}", strconv.FormatUint(gid, 10), -1) + + return snippet, nil +} + +func (iface *accountControlInterface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { + if iface.secCompSnippet == "" { + // Cache the snippet after it's successfully computed once + snippet, err := makeAccountControlSecCompSnippet() + if err != nil { + return err + } + iface.secCompSnippet = snippet + } + spec.AddSnippet(iface.secCompSnippet) + return nil +} + func init() { - registerIface(&commonInterface{ + registerIface(&accountControlInterface{commonInterface: commonInterface{ name: "account-control", summary: accountControlSummary, implicitOnCore: true, implicitOnClassic: true, baseDeclarationSlots: accountControlBaseDeclarationSlots, connectedPlugAppArmor: accountControlConnectedPlugAppArmor, - connectedPlugSecComp: accountControlConnectedPlugSecComp, - reservedForOS: true, - }) + // handled by SecCompConnectedPlug + connectedPlugSecComp: "", + reservedForOS: true, + }}) } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/account_control_test.go snapd-2.31.1+17.10/interfaces/builtin/account_control_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/account_control_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/account_control_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -20,21 +20,26 @@ package builtin_test import ( + "fmt" + . "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/seccomp" + "github.com/snapcore/snapd/osutil" "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap/snaptest" "github.com/snapcore/snapd/testutil" ) type AccountControlSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&AccountControlSuite{ @@ -50,22 +55,22 @@ ` func (s *AccountControlSuite) SetUpTest(c *C) { - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "account-control", - Interface: "account-control", - Apps: map[string]*snap.AppInfo{ - "app1": { - Snap: &snap.Info{ - SuggestedName: "core", - }, - Name: "app1"}}, - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "account-control", + Interface: "account-control", + Apps: map[string]*snap.AppInfo{ + "app1": { + Snap: &snap.Info{ + SuggestedName: "core", + }, + Name: "app1"}}, } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, accountCtlMockPlugSnapInfo, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["account-control"]} + s.plugInfo = plugSnap.Plugs["account-control"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *AccountControlSuite) TestName(c *C) { @@ -73,33 +78,36 @@ } func (s *AccountControlSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + si := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "account-control", Interface: "account-control", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, si), ErrorMatches, "account-control slots are reserved for the core snap") } func (s *AccountControlSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *AccountControlSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "/{,usr/}sbin/chpasswd") seccompSpec := &seccomp.Specification{} - err = seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err = seccompSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) - c.Check(seccompSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "\nfchown - u:root g:shadow\n") + group, err := osutil.FindGidOwning("/etc/shadow") + c.Assert(err, IsNil) + c.Check(seccompSpec.SnippetForTag("snap.other.app2"), testutil.Contains, + fmt.Sprintf("\nfchown - u:root %v\n", group)) } func (s *AccountControlSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/all.go snapd-2.31.1+17.10/interfaces/builtin/all.go --- snapd-2.29.4.2+17.10/interfaces/builtin/all.go 2017-11-30 07:12:26.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/all.go 2018-01-24 20:02:44.000000000 +0000 @@ -73,8 +73,7 @@ badPlugs = append(badPlugs, plugName) continue } - plug := &interfaces.Plug{PlugInfo: plugInfo} - if err := plug.Sanitize(iface); err != nil { + if err := interfaces.BeforePreparePlug(iface, plugInfo); err != nil { snapInfo.BadInterfaces[plugName] = err.Error() badPlugs = append(badPlugs, plugName) continue @@ -94,8 +93,7 @@ badSlots = append(badSlots, slotName) continue } - slot := &interfaces.Slot{SlotInfo: slotInfo} - if err := slot.Sanitize(iface); err != nil { + if err := interfaces.BeforePrepareSlot(iface, slotInfo); err != nil { snapInfo.BadInterfaces[slotName] = err.Error() badSlots = append(badSlots, slotName) continue diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/all_test.go snapd-2.31.1+17.10/interfaces/builtin/all_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/all_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/all_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -50,94 +50,94 @@ // essentially, the only valid methods that a snapd interface can have, apart // from what is defined in the Interface golang interface. type apparmorDefiner1 interface { - AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } type apparmorDefiner2 interface { - AppArmorConnestedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } type apparmorDefiner3 interface { - AppArmorPermanentPlug(spec *apparmor.Specification, plug *interfaces.Plug) error + AppArmorPermanentPlug(spec *apparmor.Specification, plug *snap.PlugInfo) error } type apparmorDefiner4 interface { - AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error + AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error } type dbusDefiner1 interface { - DBusConnectedPlug(spec *dbus.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + DBusConnectedPlug(spec *dbus.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } type dbusDefiner2 interface { - DBusConnectedSlot(spec *dbus.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + DBusConnectedSlot(spec *dbus.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } type dbusDefiner3 interface { - DBusPermanestPlug(spec *dbus.Specification, plug *interfaces.Plug) error + DBusPermanentPlug(spec *dbus.Specification, plug *snap.PlugInfo) error } type dbusDefiner4 interface { - DBusPermanentSlot(spec *dbus.Specification, slot *interfaces.Slot) error + DBusPermanentSlot(spec *dbus.Specification, slot *snap.SlotInfo) error } type kmodDefiner1 interface { - KModConnectedPlug(spec *kmod.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + KModConnectedPlug(spec *kmod.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } type kmodDefiner2 interface { - KModConnectedSlot(spec *kmod.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + KModConnectedSlot(spec *kmod.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } type kmodDefiner3 interface { - KModPermanentPlug(spec *kmod.Specification, plug *interfaces.Plug) error + KModPermanentPlug(spec *kmod.Specification, plug *snap.PlugInfo) error } type kmodDefiner4 interface { - KModPermanentSlot(spec *kmod.Specification, slot *interfaces.Slot) error + KModPermanentSlot(spec *kmod.Specification, slot *snap.SlotInfo) error } type mountDefiner1 interface { - MountConnectedPlug(spec *mount.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + MountConnectedPlug(spec *mount.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } type mountDefiner2 interface { - MountConnectedSlot(spec *mount.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + MountConnectedSlot(spec *mount.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } type mountDefiner3 interface { - MountPermanentPlug(spec *mount.Specification, plug *interfaces.Plug) error + MountPermanentPlug(spec *mount.Specification, plug *snap.PlugInfo) error } type mountDefiner4 interface { - MountPermanentSlot(spec *mount.Specification, slot *interfaces.Slot) error + MountPermanentSlot(spec *mount.Specification, slot *snap.SlotInfo) error } type seccompDefiner1 interface { - SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } type seccompDefiner2 interface { - SecCompConnectedSlot(spec *seccomp.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + SecCompConnectedSlot(spec *seccomp.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } type seccompDefiner3 interface { - SecCompPermanentPlug(spec *seccomp.Specification, plug *interfaces.Plug) error + SecCompPermanentPlug(spec *seccomp.Specification, plug *snap.PlugInfo) error } type seccompDefiner4 interface { - SecCompPermanentSlot(spec *seccomp.Specification, slot *interfaces.Slot) error + SecCompPermanentSlot(spec *seccomp.Specification, slot *snap.SlotInfo) error } type systemdDefiner1 interface { - SystemdConnectedPlug(spec *systemd.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + SystemdConnectedPlug(spec *systemd.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } type systemdDefiner2 interface { - SystemdConnectedSlot(spec *systemd.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + SystemdConnectedSlot(spec *systemd.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } type systemdDefiner3 interface { - SystemdPermanentPlug(spec *systemd.Specification, plug *interfaces.Plug) error + SystemdPermanentPlug(spec *systemd.Specification, plug *snap.PlugInfo) error } type systemdDefiner4 interface { - SystemdPermanentSlot(spec *systemd.Specification, slot *interfaces.Slot) error + SystemdPermanentSlot(spec *systemd.Specification, slot *snap.SlotInfo) error } type udevDefiner1 interface { - UDevConnectedPlug(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + UDevConnectedPlug(spec *udev.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } type udevDefiner2 interface { - UDevConnectedSlot(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + UDevConnectedSlot(spec *udev.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } type udevDefiner3 interface { - UDevPermanentPlug(spec *udev.Specification, plug *interfaces.Plug) error + UDevPermanentPlug(spec *udev.Specification, plug *snap.PlugInfo) error } type udevDefiner4 interface { - UDevPermanentSlot(spec *udev.Specification, slot *interfaces.Slot) error + UDevPermanentSlot(spec *udev.Specification, slot *snap.SlotInfo) error } // allGoodDefiners contains all valid specification definers for all known backends. @@ -202,10 +202,10 @@ ConnectedSlotSnippet(plug *interfaces.Plug, slot *interfaces.Slot, sec interfaces.SecuritySystem) error } type snippetDefiner3 interface { - PermanentPlugSnippet(plug *interfaces.Plug, sec interfaces.SecuritySystem) error + PermanentPlugSnippet(plug *snap.PlugInfo, sec interfaces.SecuritySystem) error } type snippetDefiner4 interface { - PermanentSlotSnippet(slot *interfaces.Slot, sec interfaces.SecuritySystem) error + PermanentSlotSnippet(slot *snap.SlotInfo, sec interfaces.SecuritySystem) error } // old auto-connect function @@ -213,12 +213,37 @@ LegacyAutoConnect() bool } +type oldSanitizePlug1 interface { + SanitizePlug(plug *interfaces.Plug) error +} +type oldSanitizePlug2 interface { + SanitizePlug(plug *snap.PlugInfo) error +} +type oldSanitizeSlot1 interface { + SanitizeSlot(slot *interfaces.Slot) error +} +type oldSanitizeSlot2 interface { + SanitizeSlot(slot *snap.SlotInfo) error +} + // specification definers before the introduction of connection attributes type oldApparmorDefiner1 interface { AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, slot *interfaces.Slot) error } type oldApparmorDefiner2 interface { - AppArmorConnestedSlot(spec *apparmor.Specification, plug *interfaces.Plug, slot *interfaces.Slot) error + AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, slot *interfaces.Slot) error +} +type oldApparmorDefiner3 interface { + AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error +} +type oldApparmorDefiner4 interface { + AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error +} +type oldApparmorDefiner5 interface { + AppArmorPermanentPlug(spec *apparmor.Specification, plug *interfaces.Plug) error +} +type oldApparmorDefiner6 interface { + AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error } type oldDbusDefiner1 interface { @@ -227,6 +252,18 @@ type oldDbusDefiner2 interface { DBusConnectedSlot(spec *dbus.Specification, plug *interfaces.Plug, slot *interfaces.Slot) error } +type oldDbusDefiner3 interface { + DBusConnectedPlug(spec *dbus.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error +} +type oldDbusDefiner4 interface { + DBusConnectedSlot(spec *dbus.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error +} +type oldDbusDefiner5 interface { + DBusPermanentPlug(spec *dbus.Specification, plug *interfaces.Plug) error +} +type oldDbusDefiner6 interface { + DBusPermanentSlot(spec *dbus.Specification, slot *interfaces.Slot) error +} type oldKmodDefiner1 interface { KModConnectedPlug(spec *kmod.Specification, plug *interfaces.Plug, slot *interfaces.Slot) error @@ -234,6 +271,18 @@ type oldKmodDefiner2 interface { KModConnectedSlot(spec *kmod.Specification, plug *interfaces.Plug, slot *interfaces.Slot) error } +type oldKmodDefiner3 interface { + KModConnectedPlug(spec *kmod.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error +} +type oldKmodDefiner4 interface { + KModConnectedSlot(spec *kmod.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error +} +type oldKmodDefiner5 interface { + KModPermanentPlug(spec *kmod.Specification, plug *interfaces.Plug) error +} +type oldKmodDefiner6 interface { + KModPermanentSlot(spec *kmod.Specification, slot *interfaces.Slot) error +} type oldMountDefiner1 interface { MountConnectedPlug(spec *mount.Specification, plug *interfaces.Plug, slot *interfaces.Slot) error @@ -241,6 +290,18 @@ type oldMountDefiner2 interface { MountConnectedSlot(spec *mount.Specification, plug *interfaces.Plug, slot *interfaces.Slot) error } +type oldMountDefiner3 interface { + MountConnectedPlug(spec *mount.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error +} +type oldMountDefiner4 interface { + MountConnectedSlot(spec *mount.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error +} +type oldMountDefiner5 interface { + MountPermanentPlug(spec *mount.Specification, plug *interfaces.Plug) error +} +type oldMountDefiner6 interface { + MountPermanentSlot(spec *mount.Specification, slot *interfaces.Slot) error +} type oldSeccompDefiner1 interface { SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.Plug, slot *interfaces.Slot) error @@ -248,6 +309,18 @@ type oldSeccompDefiner2 interface { SecCompConnectedSlot(spec *seccomp.Specification, plug *interfaces.Plug, slot *interfaces.Slot) error } +type oldSeccompDefiner3 interface { + SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error +} +type oldSeccompDefiner4 interface { + SecCompConnectedSlot(spec *seccomp.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error +} +type oldSeccompDefiner5 interface { + SecCompPermanentPlug(spec *seccomp.Specification, plug *interfaces.Plug) error +} +type oldSeccompDefiner6 interface { + SecCompPermanentSlot(spec *seccomp.Specification, slot *interfaces.Slot) error +} type oldSystemdDefiner1 interface { SystemdConnectedPlug(spec *systemd.Specification, plug *interfaces.Plug, slot *interfaces.Slot) error @@ -255,6 +328,18 @@ type oldSystemdDefiner2 interface { SystemdConnectedSlot(spec *systemd.Specification, plug *interfaces.Plug, slot *interfaces.Slot) error } +type oldSystemdDefiner3 interface { + SystemdConnectedPlug(spec *systemd.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error +} +type oldSystemdDefiner4 interface { + SystemdConnectedSlot(spec *systemd.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error +} +type oldSystemdDefiner5 interface { + SystemdPermanentPlug(spec *seccomp.Specification, plug *interfaces.Plug) error +} +type oldSystemdDefiner6 interface { + SystemdPermanentSlot(spec *seccomp.Specification, slot *interfaces.Slot) error +} type oldUdevDefiner1 interface { UDevConnectedPlug(spec *udev.Specification, plug *interfaces.Plug, slot *interfaces.Slot) error @@ -262,6 +347,18 @@ type oldUdevDefiner2 interface { UDevConnectedSlot(spec *udev.Specification, plug *interfaces.Plug, slot *interfaces.Slot) error } +type oldUdevDefiner3 interface { + UDevConnectedPlug(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error +} +type oldUdevDefiner4 interface { + UDevConnectedSlot(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error +} +type oldUdevDefiner5 interface { + UDevPermanentPlug(spec *udev.Specification, plug *interfaces.Plug) error +} +type oldUdevDefiner6 interface { + UDevPermanentSlot(spec *udev.Specification, slot *interfaces.Slot) error +} // allBadDefiners contains all old/unused specification definers for all known backends. var allBadDefiners = []reflect.Type{ @@ -272,21 +369,54 @@ reflect.TypeOf((*snippetDefiner4)(nil)).Elem(), // old auto-connect function reflect.TypeOf((*legacyAutoConnect)(nil)).Elem(), + // old sanitize methods + reflect.TypeOf((*oldSanitizePlug1)(nil)).Elem(), + reflect.TypeOf((*oldSanitizePlug2)(nil)).Elem(), + reflect.TypeOf((*oldSanitizeSlot1)(nil)).Elem(), + reflect.TypeOf((*oldSanitizeSlot2)(nil)).Elem(), // pre-attribute definers reflect.TypeOf((*oldApparmorDefiner1)(nil)).Elem(), reflect.TypeOf((*oldApparmorDefiner2)(nil)).Elem(), + reflect.TypeOf((*oldApparmorDefiner3)(nil)).Elem(), + reflect.TypeOf((*oldApparmorDefiner4)(nil)).Elem(), + reflect.TypeOf((*oldApparmorDefiner5)(nil)).Elem(), + reflect.TypeOf((*oldApparmorDefiner6)(nil)).Elem(), reflect.TypeOf((*oldDbusDefiner1)(nil)).Elem(), reflect.TypeOf((*oldDbusDefiner2)(nil)).Elem(), + reflect.TypeOf((*oldDbusDefiner3)(nil)).Elem(), + reflect.TypeOf((*oldDbusDefiner4)(nil)).Elem(), + reflect.TypeOf((*oldDbusDefiner5)(nil)).Elem(), + reflect.TypeOf((*oldDbusDefiner6)(nil)).Elem(), reflect.TypeOf((*oldKmodDefiner1)(nil)).Elem(), reflect.TypeOf((*oldKmodDefiner2)(nil)).Elem(), + reflect.TypeOf((*oldKmodDefiner3)(nil)).Elem(), + reflect.TypeOf((*oldKmodDefiner4)(nil)).Elem(), + reflect.TypeOf((*oldKmodDefiner5)(nil)).Elem(), + reflect.TypeOf((*oldKmodDefiner6)(nil)).Elem(), reflect.TypeOf((*oldMountDefiner1)(nil)).Elem(), reflect.TypeOf((*oldMountDefiner2)(nil)).Elem(), + reflect.TypeOf((*oldMountDefiner3)(nil)).Elem(), + reflect.TypeOf((*oldMountDefiner4)(nil)).Elem(), + reflect.TypeOf((*oldMountDefiner5)(nil)).Elem(), + reflect.TypeOf((*oldMountDefiner6)(nil)).Elem(), reflect.TypeOf((*oldSeccompDefiner1)(nil)).Elem(), reflect.TypeOf((*oldSeccompDefiner2)(nil)).Elem(), + reflect.TypeOf((*oldSeccompDefiner3)(nil)).Elem(), + reflect.TypeOf((*oldSeccompDefiner4)(nil)).Elem(), + reflect.TypeOf((*oldSeccompDefiner5)(nil)).Elem(), + reflect.TypeOf((*oldSeccompDefiner6)(nil)).Elem(), reflect.TypeOf((*oldSystemdDefiner1)(nil)).Elem(), reflect.TypeOf((*oldSystemdDefiner2)(nil)).Elem(), + reflect.TypeOf((*oldSystemdDefiner3)(nil)).Elem(), + reflect.TypeOf((*oldSystemdDefiner4)(nil)).Elem(), + reflect.TypeOf((*oldSystemdDefiner5)(nil)).Elem(), + reflect.TypeOf((*oldSystemdDefiner6)(nil)).Elem(), reflect.TypeOf((*oldUdevDefiner1)(nil)).Elem(), reflect.TypeOf((*oldUdevDefiner2)(nil)).Elem(), + reflect.TypeOf((*oldUdevDefiner3)(nil)).Elem(), + reflect.TypeOf((*oldUdevDefiner4)(nil)).Elem(), + reflect.TypeOf((*oldUdevDefiner5)(nil)).Elem(), + reflect.TypeOf((*oldUdevDefiner6)(nil)).Elem(), } // Check that no interface defines older definer methods. @@ -425,34 +555,30 @@ name: fmt.Sprintf("%sPermanentPlug", backend), in: []string{ fmt.Sprintf("*%s.Specification", backendLower), - "*interfaces.Plug", + "*snap.PlugInfo", }, out: []string{"error"}, }, { name: fmt.Sprintf("%sPermanentSlot", backend), in: []string{ fmt.Sprintf("*%s.Specification", backendLower), - "*interfaces.Slot", + "*snap.SlotInfo", }, out: []string{"error"}, }, { name: fmt.Sprintf("%sConnectedPlug", backend), in: []string{ fmt.Sprintf("*%s.Specification", backendLower), - "*interfaces.Plug", - "map[string]interface {}", - "*interfaces.Slot", - "map[string]interface {}", + "*interfaces.ConnectedPlug", + "*interfaces.ConnectedSlot", }, out: []string{"error"}, }, { name: fmt.Sprintf("%sConnectedSlot", backend), in: []string{ fmt.Sprintf("*%s.Specification", backendLower), - "*interfaces.Plug", - "map[string]interface {}", - "*interfaces.Slot", - "map[string]interface {}", + "*interfaces.ConnectedPlug", + "*interfaces.ConnectedSlot", }, out: []string{"error"}, }}...) diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/alsa_test.go snapd-2.31.1+17.10/interfaces/builtin/alsa_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/alsa_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/alsa_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type AlsaInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&AlsaInterfaceSuite{ @@ -53,8 +55,8 @@ ` func (s *AlsaInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, alsaConsumerYaml, nil, "alsa") - s.slot = MockSlot(c, alsaCoreYaml, nil, "alsa") + s.plug, s.plugInfo = MockConnectedPlug(c, alsaConsumerYaml, nil, "alsa") + s.slot, s.slotInfo = MockConnectedSlot(c, alsaCoreYaml, nil, "alsa") } func (s *AlsaInterfaceSuite) TestName(c *C) { @@ -62,32 +64,34 @@ } func (s *AlsaInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "alsa", Interface: "alsa", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "alsa slots are reserved for the core snap") } func (s *AlsaInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *AlsaInterfaceSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, "/dev/snd/* rw,") } func (s *AlsaInterfaceSuite) TestUDevpec(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 7) - c.Assert(spec.Snippets(), testutil.Contains, `KERNEL=="pcmC[0-9]*D[0-9]*[cp]", TAG+="snap_consumer_app"`) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Snippets(), HasLen, 8) + c.Assert(spec.Snippets(), testutil.Contains, `# alsa +KERNEL=="pcmC[0-9]*D[0-9]*[cp]", TAG+="snap_consumer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_consumer_app", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_consumer_app $devpath $major:$minor"`) } func (s *AlsaInterfaceSuite) TestStaticInfo(c *C) { @@ -99,7 +103,8 @@ } func (s *AlsaInterfaceSuite) TestAutoConnect(c *C) { - c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) + // FIXME: fix AutoConnect methods to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) } func (s *AlsaInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/autopilot_test.go snapd-2.31.1+17.10/interfaces/builtin/autopilot_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/autopilot_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/autopilot_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -32,9 +32,11 @@ ) type AutopilotInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } const mockAutopilotPlugSnapInfo = `name: other @@ -50,15 +52,15 @@ }) func (s *AutopilotInterfaceSuite) SetUpTest(c *C) { - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "autopilot-introspection", - Interface: "autopilot-introspection", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "autopilot-introspection", + Interface: "autopilot-introspection", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, mockAutopilotPlugSnapInfo, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["autopilot-introspection"]} + s.plugInfo = plugSnap.Plugs["autopilot-introspection"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *AutopilotInterfaceSuite) TestName(c *C) { @@ -66,31 +68,31 @@ } func (s *AutopilotInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "autopilot-introspection", Interface: "autopilot-introspection", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "autopilot-introspection slots are reserved for the core snap") } func (s *AutopilotInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *AutopilotInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, "path=/com/canonical/Autopilot/Introspection\n") // connected plugs have a non-nil security snippet for seccomp seccompSpec := &seccomp.Specification{} - err = seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err = seccompSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Check(seccompSpec.SnippetForTag("snap.other.app"), testutil.Contains, "recvmsg\n") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/avahi_control.go snapd-2.31.1+17.10/interfaces/builtin/avahi_control.go --- snapd-2.29.4.2+17.10/interfaces/builtin/avahi_control.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/avahi_control.go 2018-01-24 20:02:44.000000000 +0000 @@ -26,6 +26,7 @@ "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/dbus" "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" ) const avahiControlBaseDeclarationSlots = ` @@ -115,7 +116,7 @@ } } -func (iface *avahiControlInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *avahiControlInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###SLOT_SECURITY_TAGS###" var new string if release.OnClassic { @@ -133,7 +134,7 @@ return nil } -func (iface *avahiControlInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { +func (iface *avahiControlInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { if !release.OnClassic { // NOTE: this is using avahi-observe permanent slot as it contains // base declarations for running as the avahi service. @@ -142,7 +143,7 @@ return nil } -func (iface *avahiControlInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *avahiControlInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { if !release.OnClassic { old := "###PLUG_SECURITY_TAGS###" new := plugAppLabelExpr(plug) @@ -155,7 +156,7 @@ return nil } -func (iface *avahiControlInterface) DBusPermanentSlot(spec *dbus.Specification, slot *interfaces.Slot) error { +func (iface *avahiControlInterface) DBusPermanentSlot(spec *dbus.Specification, slot *snap.SlotInfo) error { if !release.OnClassic { // NOTE: this is using avahi-observe permanent slot as it contains // base declarations for running as the avahi service. diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/avahi_control_test.go snapd-2.31.1+17.10/interfaces/builtin/avahi_control_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/avahi_control_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/avahi_control_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -32,10 +32,13 @@ ) type AvahiControlInterfaceSuite struct { - iface interfaces.Interface - plug *interfaces.Plug - appSlot *interfaces.Slot - coreSlot *interfaces.Slot + iface interfaces.Interface + plug *interfaces.ConnectedPlug + plugInfo *snap.PlugInfo + appSlot *interfaces.ConnectedSlot + appSlotInfo *snap.SlotInfo + coreSlot *interfaces.ConnectedSlot + coreSlotInfo *snap.SlotInfo } var _ = Suite(&AvahiControlInterfaceSuite{ @@ -60,9 +63,9 @@ ` func (s *AvahiControlInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, avahiControlConsumerYaml, nil, "avahi-control") - s.appSlot = MockSlot(c, avahiControlProducerYaml, nil, "avahi-control") - s.coreSlot = MockSlot(c, avahiControlCoreYaml, nil, "avahi-control") + s.plug, s.plugInfo = MockConnectedPlug(c, avahiControlConsumerYaml, nil, "avahi-control") + s.appSlot, s.appSlotInfo = MockConnectedSlot(c, avahiControlProducerYaml, nil, "avahi-control") + s.coreSlot, s.coreSlotInfo = MockConnectedSlot(c, avahiControlCoreYaml, nil, "avahi-control") } func (s *AvahiControlInterfaceSuite) TestName(c *C) { @@ -70,19 +73,19 @@ } func (s *AvahiControlInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.coreSlot.Sanitize(s.iface), IsNil) - c.Assert(s.appSlot.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.coreSlotInfo), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.appSlotInfo), IsNil) // avahi-control slot can now be used on snap other than core. - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "avahi-control", Interface: "avahi-control", - }} - c.Assert(slot.Sanitize(s.iface), IsNil) + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), IsNil) } func (s *AvahiControlInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *AvahiControlInterfaceSuite) TestAppArmorSpec(c *C) { @@ -92,7 +95,7 @@ // connected plug to app slot spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.appSlot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.appSlot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "name=org.freedesktop.Avahi") c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `peer=(label="snap.producer.app"),`) @@ -108,7 +111,7 @@ // connected app slot to plug spec = &apparmor.Specification{} - c.Assert(spec.AddConnectedSlot(s.iface, s.plug, nil, s.appSlot, nil), IsNil) + c.Assert(spec.AddConnectedSlot(s.iface, s.plug, s.appSlot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.producer.app"}) c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, `interface=org.freedesktop.Avahi`) c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, `peer=(label="snap.consumer.app"),`) @@ -122,7 +125,7 @@ // permanent app slot spec = &apparmor.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.appSlot), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.appSlotInfo), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.producer.app"}) c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, `dbus (bind) bus=system @@ -134,7 +137,7 @@ // connected plug to core slot spec = &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.coreSlot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.coreSlot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "name=org.freedesktop.Avahi") c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "peer=(label=unconfined),") @@ -149,12 +152,12 @@ // connected core slot to plug spec = &apparmor.Specification{} - c.Assert(spec.AddConnectedSlot(s.iface, s.plug, nil, s.coreSlot, nil), IsNil) + c.Assert(spec.AddConnectedSlot(s.iface, s.plug, s.coreSlot), IsNil) c.Assert(spec.SecurityTags(), HasLen, 0) // permanent core slot spec = &apparmor.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.coreSlot), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.coreSlotInfo), IsNil) c.Assert(spec.SecurityTags(), HasLen, 0) } @@ -164,7 +167,7 @@ defer restore() spec := &dbus.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.appSlot), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.appSlotInfo), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.producer.app"}) c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, ``) @@ -173,7 +176,7 @@ defer restore() spec = &dbus.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.coreSlot), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.coreSlotInfo), IsNil) c.Assert(spec.SecurityTags(), HasLen, 0) } @@ -186,8 +189,9 @@ } func (s *AvahiControlInterfaceSuite) TestAutoConnect(c *C) { - c.Assert(s.iface.AutoConnect(s.plug, s.coreSlot), Equals, true) - c.Assert(s.iface.AutoConnect(s.plug, s.appSlot), Equals, true) + // FIXME: fix AutoConnect methods to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.coreSlotInfo}), Equals, true) + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.appSlotInfo}), Equals, true) } func (s *AvahiControlInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/avahi_observe.go snapd-2.31.1+17.10/interfaces/builtin/avahi_observe.go --- snapd-2.29.4.2+17.10/interfaces/builtin/avahi_observe.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/avahi_observe.go 2018-02-07 15:45:32.000000000 +0000 @@ -26,6 +26,7 @@ "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/dbus" "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" ) const avahiObserveSummary = `allows discovery on a local network via the mDNS/DNS-SD protocol suite` @@ -115,7 +116,7 @@ bus=system interface=org.freedesktop.Avahi.Server member=StateChanged - peer=(name=org.freedesktop.Avahi, label=###PLUG_SECURITY_TAGS###), + peer=(label=###PLUG_SECURITY_TAGS###), # address resolving dbus (receive) @@ -139,7 +140,7 @@ dbus (send) bus=system interface=org.freedesktop.Avahi.HostNameResolver - peer=(name=org.freedesktop.Avahi, label=###PLUG_SECURITY_TAGS###), + peer=(label=###PLUG_SECURITY_TAGS###), # service resolving dbus (receive) @@ -151,7 +152,7 @@ dbus (send) bus=system interface=org.freedesktop.Avahi.ServiceResolver - peer=(name=org.freedesktop.Avahi, label=###PLUG_SECURITY_TAGS###), + peer=(label=###PLUG_SECURITY_TAGS###), # domain browsing dbus (receive) @@ -163,7 +164,7 @@ dbus (send) bus=system interface=org.freedesktop.Avahi.DomainBrowser - peer=(name=org.freedesktop.Avahi, label=###PLUG_SECURITY_TAGS###), + peer=(label=###PLUG_SECURITY_TAGS###), # record browsing dbus (receive) @@ -175,7 +176,7 @@ dbus (send) bus=system interface=org.freedesktop.Avahi.RecordBrowser - peer=(name=org.freedesktop.Avahi, label=###PLUG_SECURITY_TAGS###), + peer=(label=###PLUG_SECURITY_TAGS###), # service browsing dbus (receive) @@ -187,7 +188,7 @@ dbus (send) bus=system interface=org.freedesktop.Avahi.ServiceBrowser - peer=(name=org.freedesktop.Avahi, label=###PLUG_SECURITY_TAGS###), + peer=(label=###PLUG_SECURITY_TAGS###), # service type browsing dbus (receive) @@ -199,7 +200,7 @@ dbus (send) bus=system interface=org.freedesktop.Avahi.ServiceTypeBrowser - peer=(name=org.freedesktop.Avahi, label=###PLUG_SECURITY_TAGS###), + peer=(label=###PLUG_SECURITY_TAGS###), ` // Note: avahiObservePermanentSlotDBus is used by avahi-control in DBusPermanentSlot @@ -420,7 +421,7 @@ } } -func (iface *avahiObserveInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *avahiObserveInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###SLOT_SECURITY_TAGS###" var new string if release.OnClassic { @@ -435,14 +436,14 @@ return nil } -func (iface *avahiObserveInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { +func (iface *avahiObserveInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { if !release.OnClassic { spec.AddSnippet(avahiObservePermanentSlotAppArmor) } return nil } -func (iface *avahiObserveInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *avahiObserveInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { if !release.OnClassic { old := "###PLUG_SECURITY_TAGS###" new := plugAppLabelExpr(plug) @@ -452,7 +453,7 @@ return nil } -func (iface *avahiObserveInterface) DBusPermanentSlot(spec *dbus.Specification, slot *interfaces.Slot) error { +func (iface *avahiObserveInterface) DBusPermanentSlot(spec *dbus.Specification, slot *snap.SlotInfo) error { if !release.OnClassic { spec.AddSnippet(avahiObservePermanentSlotDBus) } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/avahi_observe_test.go snapd-2.31.1+17.10/interfaces/builtin/avahi_observe_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/avahi_observe_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/avahi_observe_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -32,10 +32,13 @@ ) type AvahiObserveInterfaceSuite struct { - iface interfaces.Interface - plug *interfaces.Plug - appSlot *interfaces.Slot - coreSlot *interfaces.Slot + iface interfaces.Interface + plug *interfaces.ConnectedPlug + plugInfo *snap.PlugInfo + appSlot *interfaces.ConnectedSlot + appSlotInfo *snap.SlotInfo + coreSlot *interfaces.ConnectedSlot + coreSlotInfo *snap.SlotInfo } var _ = Suite(&AvahiObserveInterfaceSuite{ @@ -60,9 +63,9 @@ ` func (s *AvahiObserveInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, avahiObserveConsumerYaml, nil, "avahi-observe") - s.appSlot = MockSlot(c, avahiObserveProducerYaml, nil, "avahi-observe") - s.coreSlot = MockSlot(c, avahiObserveCoreYaml, nil, "avahi-observe") + s.plug, s.plugInfo = MockConnectedPlug(c, avahiObserveConsumerYaml, nil, "avahi-observe") + s.appSlot, s.appSlotInfo = MockConnectedSlot(c, avahiObserveProducerYaml, nil, "avahi-observe") + s.coreSlot, s.coreSlotInfo = MockConnectedSlot(c, avahiObserveCoreYaml, nil, "avahi-observe") } func (s *AvahiObserveInterfaceSuite) TestName(c *C) { @@ -70,19 +73,19 @@ } func (s *AvahiObserveInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.coreSlot.Sanitize(s.iface), IsNil) - c.Assert(s.appSlot.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.coreSlotInfo), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.appSlotInfo), IsNil) // avahi-observe slot can now be used on snap other than core. - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "avahi-observe", Interface: "avahi-observe", - }} - c.Assert(slot.Sanitize(s.iface), IsNil) + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), IsNil) } func (s *AvahiObserveInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *AvahiObserveInterfaceSuite) TestAppArmorSpec(c *C) { @@ -92,7 +95,7 @@ // connected plug to app slot spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.appSlot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.appSlot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "name=org.freedesktop.Avahi") c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `peer=(label="snap.producer.app"),`) @@ -108,7 +111,7 @@ // connected app slot to plug spec = &apparmor.Specification{} - c.Assert(spec.AddConnectedSlot(s.iface, s.plug, nil, s.appSlot, nil), IsNil) + c.Assert(spec.AddConnectedSlot(s.iface, s.plug, s.appSlot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.producer.app"}) c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, `interface=org.freedesktop.Avahi`) c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, `peer=(label="snap.consumer.app"),`) @@ -122,7 +125,7 @@ // permanent app slot spec = &apparmor.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.appSlot), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.appSlotInfo), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.producer.app"}) c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, `dbus (bind) bus=system @@ -134,7 +137,7 @@ // connected plug to core slot spec = &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.coreSlot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.coreSlot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "name=org.freedesktop.Avahi") c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "peer=(label=unconfined),") @@ -149,12 +152,12 @@ // connected core slot to plug spec = &apparmor.Specification{} - c.Assert(spec.AddConnectedSlot(s.iface, s.plug, nil, s.coreSlot, nil), IsNil) + c.Assert(spec.AddConnectedSlot(s.iface, s.plug, s.coreSlot), IsNil) c.Assert(spec.SecurityTags(), HasLen, 0) // permanent core slot spec = &apparmor.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.coreSlot), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.coreSlotInfo), IsNil) c.Assert(spec.SecurityTags(), HasLen, 0) } @@ -164,7 +167,7 @@ defer restore() spec := &dbus.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.appSlot), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.appSlotInfo), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.producer.app"}) c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, ``) @@ -173,7 +176,7 @@ defer restore() spec = &dbus.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.coreSlot), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.coreSlotInfo), IsNil) c.Assert(spec.SecurityTags(), HasLen, 0) } @@ -186,8 +189,9 @@ } func (s *AvahiObserveInterfaceSuite) TestAutoConnect(c *C) { - c.Assert(s.iface.AutoConnect(s.plug, s.coreSlot), Equals, true) - c.Assert(s.iface.AutoConnect(s.plug, s.appSlot), Equals, true) + // FIXME fix AutoConnect methods to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.coreSlotInfo}), Equals, true) + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.appSlotInfo}), Equals, true) } func (s *AvahiObserveInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/bluetooth_control.go snapd-2.31.1+17.10/interfaces/builtin/bluetooth_control.go --- snapd-2.29.4.2+17.10/interfaces/builtin/bluetooth_control.go 2017-11-30 07:12:26.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/bluetooth_control.go 2018-01-31 08:46:54.000000000 +0000 @@ -42,6 +42,8 @@ # File accesses /sys/bus/usb/drivers/btusb/ r, /sys/bus/usb/drivers/btusb/** r, + /sys/module/btusb/ r, + /sys/module/btusb/** r, /sys/class/bluetooth/ r, /sys/devices/**/bluetooth/ rw, /sys/devices/**/bluetooth/** rw, diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/bluetooth_control_test.go snapd-2.31.1+17.10/interfaces/builtin/bluetooth_control_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/bluetooth_control_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/bluetooth_control_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -33,9 +33,11 @@ ) type BluetoothControlInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slot *interfaces.ConnectedSlot + slotInfo *snap.SlotInfo + plug *interfaces.ConnectedPlug + plugInfo *snap.PlugInfo } const btcontrolMockPlugSnapInfoYaml = `name: other @@ -51,21 +53,21 @@ }) func (s *BluetoothControlInterfaceSuite) SetUpTest(c *C) { - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "bluetooth-control", - Interface: "bluetooth-control", - Apps: map[string]*snap.AppInfo{ - "app1": { - Snap: &snap.Info{ - SuggestedName: "core", - }, - Name: "app1"}}, - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "bluetooth-control", + Interface: "bluetooth-control", + Apps: map[string]*snap.AppInfo{ + "app1": { + Snap: &snap.Info{ + SuggestedName: "core", + }, + Name: "app1"}}, } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, btcontrolMockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["bluetooth-control"]} + s.plugInfo = plugSnap.Plugs["bluetooth-control"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *BluetoothControlInterfaceSuite) TestName(c *C) { @@ -73,39 +75,41 @@ } func (s *BluetoothControlInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "bluetooth-control", Interface: "bluetooth-control", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "bluetooth-control slots are reserved for the core snap") } func (s *BluetoothControlInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *BluetoothControlInterfaceSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) c.Assert(spec.SnippetForTag("snap.other.app2"), testutil.Contains, "capability net_admin") } func (s *BluetoothControlInterfaceSuite) TestSecCompSpec(c *C) { spec := &seccomp.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) c.Assert(spec.SnippetForTag("snap.other.app2"), testutil.Contains, "\nbind\n") } func (s *BluetoothControlInterfaceSuite) TestUDevSpec(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 1) - c.Assert(spec.Snippets()[0], testutil.Contains, `SUBSYSTEM=="bluetooth", TAG+="snap_other_app2"`) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Snippets(), HasLen, 2) + c.Assert(spec.Snippets(), testutil.Contains, `# bluetooth-control +SUBSYSTEM=="bluetooth", TAG+="snap_other_app2"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_other_app2", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_other_app2 $devpath $major:$minor"`) } func (s *BluetoothControlInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/bluez.go snapd-2.31.1+17.10/interfaces/builtin/bluez.go --- snapd-2.29.4.2+17.10/interfaces/builtin/bluez.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/bluez.go 2018-01-24 20:02:44.000000000 +0000 @@ -28,6 +28,7 @@ "github.com/snapcore/snapd/interfaces/seccomp" "github.com/snapcore/snapd/interfaces/udev" "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" ) const bluezSummary = `allows operating as the bluez service` @@ -170,7 +171,6 @@ accept4 bind listen -shutdown # libudev socket AF_NETLINK - NETLINK_KOBJECT_UEVENT ` @@ -182,6 +182,8 @@ + + @@ -211,14 +213,14 @@ } } -func (iface *bluezInterface) DBusPermanentSlot(spec *dbus.Specification, slot *interfaces.Slot) error { +func (iface *bluezInterface) DBusPermanentSlot(spec *dbus.Specification, slot *snap.SlotInfo) error { if !release.OnClassic { spec.AddSnippet(bluezPermanentSlotDBus) } return nil } -func (iface *bluezInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *bluezInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###SLOT_SECURITY_TAGS###" var new string if release.OnClassic { @@ -231,7 +233,7 @@ return nil } -func (iface *bluezInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *bluezInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { if !release.OnClassic { old := "###PLUG_SECURITY_TAGS###" new := plugAppLabelExpr(plug) @@ -241,19 +243,19 @@ return nil } -func (iface *bluezInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *bluezInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { spec.TagDevice(`KERNEL=="rfkill"`) return nil } -func (iface *bluezInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { +func (iface *bluezInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { if !release.OnClassic { spec.AddSnippet(bluezPermanentSlotAppArmor) } return nil } -func (iface *bluezInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *interfaces.Slot) error { +func (iface *bluezInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *snap.SlotInfo) error { if !release.OnClassic { spec.AddSnippet(bluezPermanentSlotSecComp) } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/bluez_test.go snapd-2.31.1+17.10/interfaces/builtin/bluez_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/bluez_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/bluez_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -29,14 +29,18 @@ "github.com/snapcore/snapd/interfaces/seccomp" "github.com/snapcore/snapd/interfaces/udev" "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/testutil" ) type BluezInterfaceSuite struct { - iface interfaces.Interface - appSlot *interfaces.Slot - coreSlot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + appSlot *interfaces.ConnectedSlot + appSlotInfo *snap.SlotInfo + coreSlot *interfaces.ConnectedSlot + coreSlotInfo *snap.SlotInfo + plug *interfaces.ConnectedPlug + plugInfo *snap.PlugInfo } var _ = Suite(&BluezInterfaceSuite{ @@ -95,9 +99,9 @@ ` func (s *BluezInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, bluezConsumerYaml, nil, "bluez") - s.appSlot = MockSlot(c, bluezProducerYaml, nil, "bluez") - s.coreSlot = MockSlot(c, bluezCoreYaml, nil, "bluez") + s.plug, s.plugInfo = MockConnectedPlug(c, bluezConsumerYaml, nil, "bluez") + s.appSlot, s.appSlotInfo = MockConnectedSlot(c, bluezProducerYaml, nil, "bluez") + s.coreSlot, s.coreSlotInfo = MockConnectedSlot(c, bluezCoreYaml, nil, "bluez") } func (s *BluezInterfaceSuite) TestName(c *C) { @@ -111,48 +115,48 @@ // The label uses short form when exactly one app is bound to the bluez slot spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.appSlot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.appSlot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `peer=(label="snap.producer.app"),`) // The label glob when all apps are bound to the bluez slot - slot := MockSlot(c, bluezProducerTwoAppsYaml, nil, "bluez") + slot, _ := MockConnectedSlot(c, bluezProducerTwoAppsYaml, nil, "bluez") spec = &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, slot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `peer=(label="snap.producer.*"),`) // The label uses alternation when some, but not all, apps is bound to the bluez slot - slot = MockSlot(c, bluezProducerThreeAppsYaml, nil, "bluez") + slot, _ = MockConnectedSlot(c, bluezProducerThreeAppsYaml, nil, "bluez") spec = &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, slot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `peer=(label="snap.producer.{app1,app3}"),`) // The label uses short form when exactly one app is bound to the bluez plug spec = &apparmor.Specification{} - c.Assert(spec.AddConnectedSlot(s.iface, s.plug, nil, s.appSlot, nil), IsNil) + c.Assert(spec.AddConnectedSlot(s.iface, s.plug, s.appSlot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.producer.app"}) c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, `peer=(label="snap.consumer.app"),`) // The label glob when all apps are bound to the bluez plug - plug := MockPlug(c, bluezConsumerTwoAppsYaml, nil, "bluez") + plug, _ := MockConnectedPlug(c, bluezConsumerTwoAppsYaml, nil, "bluez") spec = &apparmor.Specification{} - c.Assert(spec.AddConnectedSlot(s.iface, plug, nil, s.appSlot, nil), IsNil) + c.Assert(spec.AddConnectedSlot(s.iface, plug, s.appSlot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.producer.app"}) c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, `peer=(label="snap.consumer.*"),`) // The label uses alternation when some, but not all, apps is bound to the bluez plug - plug = MockPlug(c, bluezConsumerThreeAppsYaml, nil, "bluez") + plug, _ = MockConnectedPlug(c, bluezConsumerThreeAppsYaml, nil, "bluez") spec = &apparmor.Specification{} - c.Assert(spec.AddConnectedSlot(s.iface, plug, nil, s.appSlot, nil), IsNil) + c.Assert(spec.AddConnectedSlot(s.iface, plug, s.appSlot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.producer.app"}) c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, `peer=(label="snap.consumer.{app1,app2}"),`) // permanent slot have a non-nil security snippet for apparmor spec = &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.appSlot, nil), IsNil) - c.Assert(spec.AddPermanentSlot(s.iface, s.appSlot), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.appSlot), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.appSlotInfo), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app", "snap.producer.app"}) c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `peer=(label="snap.producer.app"),`) c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, `peer=(label=unconfined),`) @@ -163,7 +167,7 @@ // connected plug to core slot spec = &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.coreSlot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.coreSlot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "peer=(name=org.bluez, label=unconfined)") c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "peer=(name=org.bluez.obex, label=unconfined)") @@ -173,12 +177,12 @@ // connected core slot to plug spec = &apparmor.Specification{} - c.Assert(spec.AddConnectedSlot(s.iface, s.plug, nil, s.coreSlot, nil), IsNil) + c.Assert(spec.AddConnectedSlot(s.iface, s.plug, s.coreSlot), IsNil) c.Assert(spec.SecurityTags(), HasLen, 0) // permanent core slot spec = &apparmor.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.coreSlot), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.coreSlotInfo), IsNil) c.Assert(spec.SecurityTags(), HasLen, 0) } @@ -188,7 +192,7 @@ defer restore() spec := &dbus.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.appSlot), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.appSlotInfo), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.producer.app"}) c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, ``) @@ -197,7 +201,7 @@ defer restore() spec = &dbus.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.coreSlot), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.coreSlotInfo), IsNil) c.Assert(spec.SecurityTags(), HasLen, 0) } @@ -207,7 +211,7 @@ defer restore() spec := &seccomp.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.appSlot), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.appSlotInfo), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.producer.app"}) c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, "listen\n") @@ -216,7 +220,7 @@ defer restore() spec = &seccomp.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.coreSlot), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.coreSlotInfo), IsNil) c.Assert(spec.SecurityTags(), HasLen, 0) } @@ -227,18 +231,21 @@ defer restore() spec := &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.appSlot, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 1) - c.Assert(spec.Snippets()[0], testutil.Contains, `KERNEL=="rfkill", TAG+="snap_consumer_app"`) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.appSlot), IsNil) + c.Assert(spec.Snippets(), HasLen, 2) + c.Assert(spec.Snippets(), testutil.Contains, `# bluez +KERNEL=="rfkill", TAG+="snap_consumer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_consumer_app", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_consumer_app $devpath $major:$minor"`) // on a classic system with bluez slot coming from the core snap. restore = release.MockOnClassic(true) defer restore() spec = &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.coreSlot, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 1) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.coreSlot), IsNil) + c.Assert(spec.Snippets(), HasLen, 2) c.Assert(spec.Snippets()[0], testutil.Contains, `KERNEL=="rfkill", TAG+="snap_consumer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_consumer_app", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_consumer_app $devpath $major:$minor"`) } @@ -251,8 +258,9 @@ } func (s *BluezInterfaceSuite) TestAutoConnect(c *C) { - c.Assert(s.iface.AutoConnect(s.plug, s.coreSlot), Equals, true) - c.Assert(s.iface.AutoConnect(s.plug, s.appSlot), Equals, true) + // FIXME: fix AutoConnect methods to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.coreSlotInfo}), Equals, true) + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.appSlotInfo}), Equals, true) } func (s *BluezInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/bool_file.go snapd-2.31.1+17.10/interfaces/builtin/bool_file.go --- snapd-2.29.4.2+17.10/interfaces/builtin/bool_file.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/bool_file.go 2018-01-24 20:02:44.000000000 +0000 @@ -26,6 +26,7 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" + "github.com/snapcore/snapd/snap" ) const boolFileSummary = `allows access to specific file with bool semantics` @@ -68,9 +69,9 @@ boolFileGPIOValuePattern, } -// SanitizeSlot checks and possibly modifies a slot. +// BeforePrepareSlot checks and possibly modifies a slot. // Valid "bool-file" slots must contain the attribute "path". -func (iface *boolFileInterface) SanitizeSlot(slot *interfaces.Slot) error { +func (iface *boolFileInterface) BeforePrepareSlot(slot *snap.SlotInfo) error { path, ok := slot.Attrs["path"].(string) if !ok || path == "" { return fmt.Errorf("bool-file must contain the path attribute") @@ -84,7 +85,7 @@ return fmt.Errorf("bool-file can only point at LED brightness or GPIO value") } -func (iface *boolFileInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { +func (iface *boolFileInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { gpioSnippet := ` /sys/class/gpio/export rw, /sys/class/gpio/unexport rw, @@ -97,7 +98,7 @@ return nil } -func (iface *boolFileInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *boolFileInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { // Allow write and lock on the file designated by the path. // Dereference symbolic links to file path handed out to apparmor since // sysfs is full of symlinks and apparmor requires uses real path for @@ -110,9 +111,10 @@ return nil } -func (iface *boolFileInterface) dereferencedPath(slot *interfaces.Slot) (string, error) { - if path, ok := slot.Attrs["path"].(string); ok { - path, err := evalSymlinks(path) +func (iface *boolFileInterface) dereferencedPath(slot *interfaces.ConnectedSlot) (string, error) { + var path string + if err := slot.Attr("path", &path); err == nil { + path, err = evalSymlinks(path) if err != nil { return "", err } @@ -122,8 +124,9 @@ } // isGPIO checks if a given bool-file slot refers to a GPIO pin. -func (iface *boolFileInterface) isGPIO(slot *interfaces.Slot) bool { - if path, ok := slot.Attrs["path"].(string); ok { +func (iface *boolFileInterface) isGPIO(slot *snap.SlotInfo) bool { + var path string + if err := slot.Attr("path", &path); err == nil { path = filepath.Clean(path) return boolFileGPIOValuePattern.MatchString(path) } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/bool_file_test.go snapd-2.31.1+17.10/interfaces/builtin/bool_file_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/bool_file_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/bool_file_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -42,15 +42,23 @@ type BoolFileInterfaceSuite struct { testutil.BaseTest - iface interfaces.Interface - gpioSlot *interfaces.Slot - ledSlot *interfaces.Slot - badPathSlot *interfaces.Slot - parentDirPathSlot *interfaces.Slot - missingPathSlot *interfaces.Slot - badInterfaceSlot *interfaces.Slot - plug *interfaces.Plug - badInterfacePlug *interfaces.Plug + iface interfaces.Interface + gpioSlotInfo *snap.SlotInfo + gpioSlot *interfaces.ConnectedSlot + ledSlotInfo *snap.SlotInfo + ledSlot *interfaces.ConnectedSlot + badPathSlotInfo *snap.SlotInfo + badPathSlot *interfaces.ConnectedSlot + parentDirPathSlotInfo *snap.SlotInfo + parentDirPathSlot *interfaces.ConnectedSlot + missingPathSlotInfo *snap.SlotInfo + missingPathSlot *interfaces.ConnectedSlot + badInterfaceSlot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug + badInterfaceSlotInfo *snap.SlotInfo + badInterfacePlugInfo *snap.PlugInfo + badInterfacePlug *interfaces.ConnectedPlug } var _ = Suite(&BoolFileInterfaceSuite{ @@ -87,14 +95,22 @@ plug: bool-file bad-interface-plug: other-interface `, &snap.SideInfo{}) - s.gpioSlot = &interfaces.Slot{SlotInfo: info.Slots["gpio"]} - s.ledSlot = &interfaces.Slot{SlotInfo: info.Slots["led"]} - s.missingPathSlot = &interfaces.Slot{SlotInfo: info.Slots["missing-path"]} - s.badPathSlot = &interfaces.Slot{SlotInfo: info.Slots["bad-path"]} - s.parentDirPathSlot = &interfaces.Slot{SlotInfo: info.Slots["parent-dir-path"]} - s.badInterfaceSlot = &interfaces.Slot{SlotInfo: info.Slots["bad-interface-slot"]} - s.plug = &interfaces.Plug{PlugInfo: plugSnapinfo.Plugs["plug"]} - s.badInterfacePlug = &interfaces.Plug{PlugInfo: info.Plugs["bad-interface-plug"]} + s.gpioSlotInfo = info.Slots["gpio"] + s.gpioSlot = interfaces.NewConnectedSlot(s.gpioSlotInfo, nil) + s.ledSlotInfo = info.Slots["led"] + s.ledSlot = interfaces.NewConnectedSlot(s.ledSlotInfo, nil) + s.missingPathSlotInfo = info.Slots["missing-path"] + s.missingPathSlot = interfaces.NewConnectedSlot(s.missingPathSlotInfo, nil) + s.badPathSlotInfo = info.Slots["bad-path"] + s.badPathSlot = interfaces.NewConnectedSlot(s.badPathSlotInfo, nil) + s.parentDirPathSlotInfo = info.Slots["parent-dir-path"] + s.parentDirPathSlot = interfaces.NewConnectedSlot(s.parentDirPathSlotInfo, nil) + s.badInterfaceSlotInfo = info.Slots["bad-interface-slot"] + s.badInterfaceSlot = interfaces.NewConnectedSlot(s.badInterfaceSlotInfo, nil) + s.plugInfo = plugSnapinfo.Plugs["plug"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) + s.badInterfacePlugInfo = info.Plugs["bad-interface-plug"] + s.badInterfacePlug = interfaces.NewConnectedPlug(s.badInterfacePlugInfo, nil) } // TODO: add test for permanent slot when we have hook support. @@ -105,21 +121,21 @@ func (s *BoolFileInterfaceSuite) TestSanitizeSlot(c *C) { // Both LED and GPIO slots are accepted - c.Assert(s.ledSlot.Sanitize(s.iface), IsNil) - c.Assert(s.gpioSlot.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.ledSlotInfo), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.gpioSlotInfo), IsNil) // Slots without the "path" attribute are rejected. - c.Assert(s.missingPathSlot.Sanitize(s.iface), ErrorMatches, + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.missingPathSlotInfo), ErrorMatches, "bool-file must contain the path attribute") // Slots without the "path" attribute are rejected. - c.Assert(s.parentDirPathSlot.Sanitize(s.iface), ErrorMatches, + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.parentDirPathSlotInfo), ErrorMatches, "bool-file can only point at LED brightness or GPIO value") // Slots with incorrect value of the "path" attribute are rejected. - c.Assert(s.badPathSlot.Sanitize(s.iface), ErrorMatches, + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.badPathSlotInfo), ErrorMatches, "bool-file can only point at LED brightness or GPIO value") } func (s *BoolFileInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *BoolFileInterfaceSuite) TestPlugSnippetHandlesSymlinkErrors(c *C) { @@ -129,7 +145,7 @@ }) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.gpioSlot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.gpioSlot) c.Assert(err, ErrorMatches, "cannot compute plug security snippet: broken symbolic link") c.Assert(apparmorSpec.SecurityTags(), HasLen, 0) } @@ -142,14 +158,14 @@ // Extra apparmor permission to access GPIO value // The path uses dereferenced symbolic links. apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.gpioSlot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.gpioSlot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), Equals, "(dereferenced)/sys/class/gpio/gpio13/value rwk,") // Extra apparmor permission to access LED brightness. // The path uses dereferenced symbolic links. apparmorSpec = &apparmor.Specification{} - err = apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.ledSlot, nil) + err = apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.ledSlot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), Equals, "(dereferenced)/sys/class/leds/input27::capslock/brightness rwk,") @@ -159,25 +175,25 @@ // Unsanitized slots should never be used and cause a panic. c.Assert(func() { apparmorSpec := &apparmor.Specification{} - apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.missingPathSlot, nil) + apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.missingPathSlot) }, PanicMatches, "slot is not sanitized") } func (s *BoolFileInterfaceSuite) TestConnectedPlugSnippetUnusedSecuritySystems(c *C) { - for _, slot := range []*interfaces.Slot{s.ledSlot, s.gpioSlot} { + for _, slot := range []*interfaces.ConnectedSlot{s.ledSlot, s.gpioSlot} { // No extra seccomp permissions for plug seccompSpec := &seccomp.Specification{} - err := seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := seccompSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(seccompSpec.Snippets(), HasLen, 0) // No extra dbus permissions for plug dbusSpec := &dbus.Specification{} - err = dbusSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err = dbusSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(dbusSpec.Snippets(), HasLen, 0) // No extra udev permissions for plug udevSpec := &udev.Specification{} - c.Assert(udevSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil), IsNil) + c.Assert(udevSpec.AddConnectedPlug(s.iface, s.plug, slot), IsNil) c.Assert(udevSpec.Snippets(), HasLen, 0) } } @@ -185,17 +201,17 @@ func (s *BoolFileInterfaceSuite) TestPermanentPlugSnippetUnusedSecuritySystems(c *C) { // No extra seccomp permissions for plug seccompSpec := &seccomp.Specification{} - err := seccompSpec.AddPermanentPlug(s.iface, s.plug) + err := seccompSpec.AddPermanentPlug(s.iface, s.plugInfo) c.Assert(err, IsNil) c.Assert(seccompSpec.Snippets(), HasLen, 0) // No extra dbus permissions for plug dbusSpec := &dbus.Specification{} - err = dbusSpec.AddPermanentPlug(s.iface, s.plug) + err = dbusSpec.AddPermanentPlug(s.iface, s.plugInfo) c.Assert(err, IsNil) c.Assert(dbusSpec.Snippets(), HasLen, 0) // No extra udev permissions for plug udevSpec := &udev.Specification{} - c.Assert(udevSpec.AddPermanentPlug(s.iface, s.plug), IsNil) + c.Assert(udevSpec.AddPermanentPlug(s.iface, s.plugInfo), IsNil) c.Assert(udevSpec.Snippets(), HasLen, 0) } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/broadcom_asic_control.go snapd-2.31.1+17.10/interfaces/builtin/broadcom_asic_control.go --- snapd-2.29.4.2+17.10/interfaces/builtin/broadcom_asic_control.go 2017-11-30 07:12:26.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/broadcom_asic_control.go 2018-02-16 20:27:57.000000000 +0000 @@ -41,12 +41,12 @@ # These are broader than they needs to be, but until we query udev # for specific devices, use a broader glob -/sys/devices/pci[0-9]*/**/config r, -/sys/devices/pci[0-9]*/**/{,subsystem_}device r, -/sys/devices/pci[0-9]*/**/{,subsystem_}vendor r, +/sys/devices/pci[0-9a-f]*/**/config r, +/sys/devices/pci[0-9a-f]*/**/{,subsystem_}device r, +/sys/devices/pci[0-9a-f]*/**/{,subsystem_}vendor r, /sys/bus/pci/devices/ r, -/run/udev/data/+pci:[0-9]* r, +/run/udev/data/+pci:[0-9a-f]* r, ` var broadcomAsicControlConnectedPlugUDev = []string{ diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/broadcom_asic_control_test.go snapd-2.31.1+17.10/interfaces/builtin/broadcom_asic_control_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/broadcom_asic_control_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/broadcom_asic_control_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -33,9 +33,11 @@ ) type BroadcomAsicControlSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&BroadcomAsicControlSuite{ @@ -49,7 +51,8 @@ broadcom-asic-control: ` info := snaptest.MockInfo(c, producerYaml, nil) - s.slot = &interfaces.Slot{SlotInfo: info.Slots["broadcom-asic-control"]} + s.slotInfo = info.Slots["broadcom-asic-control"] + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) const consumerYaml = `name: consumer apps: @@ -57,7 +60,8 @@ plugs: [broadcom-asic-control] ` info = snaptest.MockInfo(c, consumerYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: info.Plugs["broadcom-asic-control"]} + s.plugInfo = info.Plugs["broadcom-asic-control"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *BroadcomAsicControlSuite) TestName(c *C) { @@ -65,37 +69,39 @@ } func (s *BroadcomAsicControlSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "broadcom-asic-control", Interface: "broadcom-asic-control", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "broadcom-asic-control slots are reserved for the core snap") } func (s *BroadcomAsicControlSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *BroadcomAsicControlSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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/module/linux_kernel_bde/{,**} r,") } func (s *BroadcomAsicControlSuite) TestUDevSpec(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 2) - c.Assert(spec.Snippets(), testutil.Contains, `SUBSYSTEM=="net", KERNEL=="bcm[0-9]*", TAG+="snap_consumer_app"`) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Snippets(), HasLen, 3) + c.Assert(spec.Snippets(), testutil.Contains, `# broadcom-asic-control +SUBSYSTEM=="net", KERNEL=="bcm[0-9]*", TAG+="snap_consumer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_consumer_app", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_consumer_app $devpath $major:$minor"`) } func (s *BroadcomAsicControlSuite) TestKModSpec(c *C) { spec := &kmod.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) c.Assert(spec.Modules(), DeepEquals, map[string]bool{ "linux-user-bde": true, "linux-kernel-bde": true, @@ -112,7 +118,8 @@ } func (s *BroadcomAsicControlSuite) TestAutoConnect(c *C) { - c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) + // FIXME: fix AutoConnect methods to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) } func (s *BroadcomAsicControlSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/browser_support.go snapd-2.31.1+17.10/interfaces/builtin/browser_support.go --- snapd-2.29.4.2+17.10/interfaces/builtin/browser_support.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/browser_support.go 2018-01-24 20:02:44.000000000 +0000 @@ -25,6 +25,7 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/seccomp" + "github.com/snapcore/snapd/snap" ) const browserSupportSummary = `allows access to various APIs needed by modern web browsers` @@ -37,6 +38,9 @@ deny-connection: plug-attributes: allow-sandbox: true + deny-auto-connection: + plug-attributes: + allow-sandbox: true ` const browserSupportConnectedPlugAppArmor = ` @@ -57,6 +61,7 @@ # packaging adjusted to use LD_PRELOAD technique from LP: #1577514 owner /{dev,run}/shm/{,.}org.chromium.* mrw, owner /{dev,run}/shm/{,.}com.google.Chrome.* mrw, +owner /{dev,run}/shm/.io.nwjs.* mrw, # Chrome's Singleton API sometimes causes an ouid/fsuid mismatch denial, so # for now, allow non-owner read on the singleton socket (LP: #1731012). See @@ -89,6 +94,15 @@ # warning and the noisy AppArmor denial. owner @{PROC}/@{pid}/mounts r, owner @{PROC}/@{pid}/mountinfo r, + +# Since snapd still uses SECCOMP_RET_KILL, we have added a workaround rule to +# allow mknod on character devices since chromium unconditionally performs +# a mknod() to create the /dev/nvidiactl device, regardless of if it exists or +# not or if the process has CAP_MKNOD or not. Since we don't want to actually +# grant the ability to create character devices, explicitly deny the +# capability. When snapd uses SECCOMP_RET_ERRNO, we can remove this rule. +# https://forum.snapcraft.io/t/call-for-testing-chromium-62-0-3202-62/2569/46 +deny capability mknod, ` const browserSupportConnectedPlugAppArmorWithoutSandbox = ` @@ -243,6 +257,17 @@ # TODO: fine-tune when seccomp arg filtering available in stable distro # releases setpriority + +# Since snapd still uses SECCOMP_RET_KILL, add a workaround rule to allow mknod +# on character devices since chromium unconditionally performs a mknod() to +# create the /dev/nvidiactl device, regardless of if it exists or not or if the +# process has CAP_MKNOD or not. Since we don't want to actually grant the +# ability to create character devices, we added an explicit deny AppArmor rule +# for this capability. When snapd uses SECCOMP_RET_ERRNO, we can remove this +# rule. +# https://forum.snapcraft.io/t/call-for-testing-chromium-62-0-3202-62/2569/46 +mknod - |S_IFCHR - +mknodat - - |S_IFCHR - ` const browserSupportConnectedPlugSecCompWithSandbox = ` @@ -275,7 +300,7 @@ } } -func (iface *browserSupportInterface) SanitizePlug(plug *interfaces.Plug) error { +func (iface *browserSupportInterface) BeforePreparePlug(plug *snap.PlugInfo) error { // It's fine if allow-sandbox isn't specified, but it it is, // it needs to be bool if v, ok := plug.Attrs["allow-sandbox"]; ok { @@ -287,8 +312,9 @@ return nil } -func (iface *browserSupportInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - allowSandbox, _ := plug.Attrs["allow-sandbox"].(bool) +func (iface *browserSupportInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { + var allowSandbox bool + _ = plug.Attr("allow-sandbox", &allowSandbox) spec.AddSnippet(browserSupportConnectedPlugAppArmor) if allowSandbox { spec.AddSnippet(browserSupportConnectedPlugAppArmorWithSandbox) @@ -298,8 +324,9 @@ return nil } -func (iface *browserSupportInterface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - allowSandbox, _ := plug.Attrs["allow-sandbox"].(bool) +func (iface *browserSupportInterface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { + var allowSandbox bool + _ = plug.Attr("allow-sandbox", &allowSandbox) snippet := browserSupportConnectedPlugSecComp if allowSandbox { snippet += browserSupportConnectedPlugSecCompWithSandbox diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/browser_support_test.go snapd-2.31.1+17.10/interfaces/builtin/browser_support_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/browser_support_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/browser_support_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -32,9 +32,11 @@ ) type BrowserSupportInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slot *interfaces.ConnectedSlot + slotInfo *snap.SlotInfo + plug *interfaces.ConnectedPlug + plugInfo *snap.PlugInfo } const browserMockPlugSnapInfoYaml = `name: other @@ -50,15 +52,15 @@ }) func (s *BrowserSupportInterfaceSuite) SetUpTest(c *C) { - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "browser-support", - Interface: "browser-support", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "browser-support", + Interface: "browser-support", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, browserMockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["browser-support"]} + s.plugInfo = plugSnap.Plugs["browser-support"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *BrowserSupportInterfaceSuite) TestName(c *C) { @@ -66,11 +68,11 @@ } func (s *BrowserSupportInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) } func (s *BrowserSupportInterfaceSuite) TestSanitizePlugNoAttrib(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *BrowserSupportInterfaceSuite) TestSanitizePlugWithAttrib(c *C) { @@ -81,8 +83,8 @@ allow-sandbox: true ` info := snaptest.MockInfo(c, mockSnapYaml, nil) - plug := &interfaces.Plug{PlugInfo: info.Plugs["browser-support"]} - c.Assert(plug.Sanitize(s.iface), IsNil) + plug := info.Plugs["browser-support"] + c.Assert(interfaces.BeforePreparePlug(s.iface, plug), IsNil) } func (s *BrowserSupportInterfaceSuite) TestSanitizePlugWithBadAttrib(c *C) { @@ -93,14 +95,14 @@ allow-sandbox: bad ` info := snaptest.MockInfo(c, mockSnapYaml, nil) - plug := &interfaces.Plug{PlugInfo: info.Plugs["browser-support"]} - c.Assert(plug.Sanitize(s.iface), ErrorMatches, + plug := info.Plugs["browser-support"] + c.Assert(interfaces.BeforePreparePlug(s.iface, plug), ErrorMatches, "browser-support plug requires bool with 'allow-sandbox'") } func (s *BrowserSupportInterfaceSuite) TestConnectedPlugSnippetWithoutAttrib(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) snippet := apparmorSpec.SnippetForTag("snap.other.app2") @@ -109,7 +111,7 @@ c.Assert(string(snippet), testutil.Contains, `deny ptrace (trace) peer=snap.@{SNAP_NAME}.**`) seccompSpec := &seccomp.Specification{} - err = seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err = seccompSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) secCompSnippet := seccompSpec.SnippetForTag("snap.other.app2") @@ -130,10 +132,10 @@ ` info := snaptest.MockInfo(c, mockSnapYaml, nil) - plug := &interfaces.Plug{PlugInfo: info.Plugs["browser-support"]} + plug := interfaces.NewConnectedPlug(info.Plugs["browser-support"], nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.browser-support-plug-snap.app2"}) snippet := apparmorSpec.SnippetForTag("snap.browser-support-plug-snap.app2") @@ -142,7 +144,7 @@ c.Assert(snippet, testutil.Contains, `deny ptrace (trace) peer=snap.@{SNAP_NAME}.**`) seccompSpec := &seccomp.Specification{} - err = seccompSpec.AddConnectedPlug(s.iface, plug, nil, s.slot, nil) + err = seccompSpec.AddConnectedPlug(s.iface, plug, s.slot) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.browser-support-plug-snap.app2"}) secCompSnippet := seccompSpec.SnippetForTag("snap.browser-support-plug-snap.app2") @@ -162,10 +164,10 @@ plugs: [browser-support] ` info := snaptest.MockInfo(c, mockSnapYaml, nil) - plug := &interfaces.Plug{PlugInfo: info.Plugs["browser-support"]} + plug := interfaces.NewConnectedPlug(info.Plugs["browser-support"], nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.browser-support-plug-snap.app2"}) snippet := apparmorSpec.SnippetForTag("snap.browser-support-plug-snap.app2") @@ -174,7 +176,7 @@ c.Assert(snippet, Not(testutil.Contains), `deny ptrace (trace) peer=snap.@{SNAP_NAME}.**`) seccompSpec := &seccomp.Specification{} - err = seccompSpec.AddConnectedPlug(s.iface, plug, nil, s.slot, nil) + err = seccompSpec.AddConnectedPlug(s.iface, plug, s.slot) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.browser-support-plug-snap.app2"}) secCompSnippet := seccompSpec.SnippetForTag("snap.browser-support-plug-snap.app2") @@ -185,13 +187,13 @@ func (s *BrowserSupportInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), HasLen, 1) // connected plugs have a non-nil security snippet for apparmor seccompSpec := &seccomp.Specification{} - err = seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err = seccompSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(seccompSpec.Snippets(), HasLen, 1) } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/camera_test.go snapd-2.31.1+17.10/interfaces/builtin/camera_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/camera_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/camera_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type CameraInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slot *interfaces.ConnectedSlot + slotInfo *snap.SlotInfo + plug *interfaces.ConnectedPlug + plugInfo *snap.PlugInfo } var _ = Suite(&CameraInterfaceSuite{ @@ -53,8 +55,8 @@ ` func (s *CameraInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, cameraConsumerYaml, nil, "camera") - s.slot = MockSlot(c, cameraCoreYaml, nil, "camera") + s.plug, s.plugInfo = MockConnectedPlug(c, cameraConsumerYaml, nil, "camera") + s.slot, s.slotInfo = MockConnectedSlot(c, cameraCoreYaml, nil, "camera") } func (s *CameraInterfaceSuite) TestName(c *C) { @@ -62,32 +64,34 @@ } func (s *CameraInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "camera", Interface: "camera", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "camera slots are reserved for the core snap") } func (s *CameraInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *CameraInterfaceSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, "/dev/video[0-9]* rw") } func (s *CameraInterfaceSuite) TestUDevSpec(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 1) - c.Assert(spec.Snippets()[0], testutil.Contains, `KERNEL=="video[0-9]*", TAG+="snap_consumer_app"`) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Snippets(), HasLen, 2) + c.Assert(spec.Snippets(), testutil.Contains, `# camera +KERNEL=="video[0-9]*", TAG+="snap_consumer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_consumer_app", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_consumer_app $devpath $major:$minor"`) } func (s *CameraInterfaceSuite) TestStaticInfo(c *C) { @@ -99,7 +103,8 @@ } func (s *CameraInterfaceSuite) TestAutoConnect(c *C) { - c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) + // FIXME: fix AutoConnect methods to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) } func (s *CameraInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/classic_support_test.go snapd-2.31.1+17.10/interfaces/builtin/classic_support_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/classic_support_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/classic_support_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -32,9 +32,11 @@ ) type ClassicSupportInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } const classicSupportMockPlugSnapInfoYaml = `name: other @@ -50,15 +52,15 @@ }) func (s *ClassicSupportInterfaceSuite) SetUpTest(c *C) { - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "classic-support", - Interface: "classic-support", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "classic-support", + Interface: "classic-support", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, classicSupportMockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["classic-support"]} + s.plugInfo = plugSnap.Plugs["classic-support"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *ClassicSupportInterfaceSuite) TestName(c *C) { @@ -66,17 +68,17 @@ } func (s *ClassicSupportInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) } func (s *ClassicSupportInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *ClassicSupportInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) snippet := apparmorSpec.SnippetForTag("snap.other.app") @@ -85,7 +87,7 @@ // connected plugs have a non-nil security snippet for seccomp seccompSpec := &seccomp.Specification{} - err = seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err = seccompSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Check(seccompSpec.SnippetForTag("snap.other.app"), testutil.Contains, "mount\n") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/common.go snapd-2.31.1+17.10/interfaces/builtin/common.go --- snapd-2.29.4.2+17.10/interfaces/builtin/common.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/common.go 2018-01-24 20:02:44.000000000 +0000 @@ -27,6 +27,7 @@ "github.com/snapcore/snapd/interfaces/kmod" "github.com/snapcore/snapd/interfaces/seccomp" "github.com/snapcore/snapd/interfaces/udev" + "github.com/snapcore/snapd/snap" ) type evalSymlinksFn func(string) (string, error) @@ -75,18 +76,18 @@ } } -// SanitizeSlot checks and possibly modifies a slot. +// BeforePrepareSlot checks and possibly modifies a slot. // // If the reservedForOS flag is set then only slots on core snap // are allowed. -func (iface *commonInterface) SanitizeSlot(slot *interfaces.Slot) error { +func (iface *commonInterface) BeforePrepareSlot(slot *snap.SlotInfo) error { if iface.reservedForOS { return sanitizeSlotReservedForOS(iface, slot) } return nil } -func (iface *commonInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *commonInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { if iface.connectedPlugAppArmor != "" { spec.AddSnippet(iface.connectedPlugAppArmor) } @@ -102,7 +103,7 @@ return !iface.rejectAutoConnectPairs } -func (iface *commonInterface) KModConnectedPlug(spec *kmod.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *commonInterface) KModConnectedPlug(spec *kmod.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { for _, m := range iface.connectedPlugKModModules { if err := spec.AddModule(m); err != nil { return err @@ -111,7 +112,7 @@ return nil } -func (iface *commonInterface) KModConnectedSlot(spec *kmod.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *commonInterface) KModConnectedSlot(spec *kmod.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { for _, m := range iface.connectedSlotKModModules { if err := spec.AddModule(m); err != nil { return err @@ -120,7 +121,7 @@ return nil } -func (iface *commonInterface) KModPermanentPlug(spec *kmod.Specification, plug *interfaces.Plug) error { +func (iface *commonInterface) KModPermanentPlug(spec *kmod.Specification, plug *snap.PlugInfo) error { for _, m := range iface.permanentPlugKModModules { if err := spec.AddModule(m); err != nil { return err @@ -129,7 +130,7 @@ return nil } -func (iface *commonInterface) KModPermanentSlot(spec *kmod.Specification, slot *interfaces.Slot) error { +func (iface *commonInterface) KModPermanentSlot(spec *kmod.Specification, slot *snap.SlotInfo) error { for _, m := range iface.permanentSlotKModModules { if err := spec.AddModule(m); err != nil { return err @@ -138,14 +139,14 @@ return nil } -func (iface *commonInterface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *commonInterface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { if iface.connectedPlugSecComp != "" { spec.AddSnippet(iface.connectedPlugSecComp) } return nil } -func (iface *commonInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *commonInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { for _, rule := range iface.connectedPlugUDev { spec.TagDevice(rule) } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/common_test.go snapd-2.31.1+17.10/interfaces/builtin/common_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/common_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/common_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,7 +31,7 @@ var _ = Suite(&commonIfaceSuite{}) func (s *commonIfaceSuite) TestUDevSpec(c *C) { - plug := MockPlug(c, ` + plug, _ := MockConnectedPlug(c, ` name: consumer apps: app-a: @@ -40,7 +40,7 @@ app-c: plugs: [common] `, nil, "common") - slot := MockSlot(c, ` + slot, _ := MockConnectedSlot(c, ` name: producer slots: common: @@ -52,11 +52,15 @@ connectedPlugUDev: []string{`KERNEL=="foo"`}, } spec := &udev.Specification{} - c.Assert(spec.AddConnectedPlug(iface, plug, nil, slot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(iface, plug, slot), IsNil) c.Assert(spec.Snippets(), DeepEquals, []string{ - `KERNEL=="foo", TAG+="snap_consumer_app-a"`, + `# common +KERNEL=="foo", TAG+="snap_consumer_app-a"`, + `TAG=="snap_consumer_app-a", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_consumer_app-a $devpath $major:$minor"`, // NOTE: app-b is unaffected as it doesn't have a plug reference. - `KERNEL=="foo", TAG+="snap_consumer_app-c"`, + `# common +KERNEL=="foo", TAG+="snap_consumer_app-c"`, + `TAG=="snap_consumer_app-c", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_consumer_app-c $devpath $major:$minor"`, }) // connected plug udev rules are optional @@ -64,7 +68,7 @@ name: "common", } spec = &udev.Specification{} - c.Assert(spec.AddConnectedPlug(iface, plug, nil, slot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(iface, plug, slot), IsNil) c.Assert(spec.Snippets(), HasLen, 0) } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/content.go snapd-2.31.1+17.10/interfaces/builtin/content.go --- snapd-2.29.4.2+17.10/interfaces/builtin/content.go 2017-10-23 06:17:24.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/content.go 2018-01-24 20:02:44.000000000 +0000 @@ -68,7 +68,7 @@ return filepath.Clean(path) == path && path != ".." && !strings.HasPrefix(path, "../") } -func (iface *contentInterface) SanitizeSlot(slot *interfaces.Slot) error { +func (iface *contentInterface) BeforePrepareSlot(slot *snap.SlotInfo) error { content, ok := slot.Attrs["content"].(string) if !ok || len(content) == 0 { if slot.Attrs == nil { @@ -78,6 +78,19 @@ slot.Attrs["content"] = slot.Name } + // Error if "read" or "write" are present alongside "source". + // TODO: use slot.Lookup() once PR 4510 lands. + var unused map[string]interface{} + if err := slot.Attr("source", &unused); err == nil { + var unused []interface{} + if err := slot.Attr("read", &unused); err == nil { + return fmt.Errorf(`move the "read" attribute into the "source" section`) + } + if err := slot.Attr("write", &unused); err == nil { + return fmt.Errorf(`move the "write" attribute into the "source" section`) + } + } + // check that we have either a read or write path rpath := iface.path(slot, "read") wpath := iface.path(slot, "write") @@ -93,11 +106,10 @@ return fmt.Errorf("content interface path is not clean: %q", p) } } - return nil } -func (iface *contentInterface) SanitizePlug(plug *interfaces.Plug) error { +func (iface *contentInterface) BeforePreparePlug(plug *snap.PlugInfo) error { content, ok := plug.Attrs["content"].(string) if !ok || len(content) == 0 { if plug.Attrs == nil { @@ -119,18 +131,30 @@ // path is an internal helper that extract the "read" and "write" attribute // of the slot -func (iface *contentInterface) path(slot *interfaces.Slot, name string) []string { +func (iface *contentInterface) path(attrs interfaces.Attrer, name string) []string { if name != "read" && name != "write" { panic("internal error, path can only be used with read/write") } - paths, ok := slot.Attrs[name].([]interface{}) - if !ok { - return nil + var paths []interface{} + var source map[string]interface{} + + if err := attrs.Attr("source", &source); err == nil { + // Access either "source.read" or "source.write" attribute. + var ok bool + if paths, ok = source[name].([]interface{}); !ok { + return nil + } + } else { + // Access either "read" or "write" attribute directly (legacy). + if err := attrs.Attr(name, &paths); err != nil { + return nil + } } out := make([]string, len(paths)) for i, p := range paths { + var ok bool out[i], ok = p.(string) if !ok { return nil @@ -161,18 +185,32 @@ return filepath.Join(filepath.Join(dirs.CoreSnapMountDir, snapInfo.Name(), snapInfo.Revision.String()), path) } -func mountEntry(plug *interfaces.Plug, slot *interfaces.Slot, relSrc string, extraOptions ...string) mount.Entry { +func mountEntry(plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot, relSrc string, extraOptions ...string) mount.Entry { options := make([]string, 0, len(extraOptions)+1) options = append(options, "bind") options = append(options, extraOptions...) + + var target string + // The 'target' attribute has already been verified in BeforePreparePlug. + _ = plug.Attr("target", &target) + source := resolveSpecialVariable(relSrc, slot.Snap()) + target = resolveSpecialVariable(target, plug.Snap()) + + // Check if the "source" section is present. + var unused map[string]interface{} + if err := slot.Attr("source", &unused); err == nil { + _, sourceName := filepath.Split(source) + target = filepath.Join(target, sourceName) + } + return mount.Entry{ - Name: resolveSpecialVariable(relSrc, slot.Snap), - Dir: resolveSpecialVariable(plug.Attrs["target"].(string), plug.Snap), + Name: source, + Dir: target, Options: options, } } -func (iface *contentInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *contentInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { contentSnippet := bytes.NewBuffer(nil) writePaths := iface.path(slot, "write") if len(writePaths) > 0 { @@ -185,7 +223,7 @@ `) for _, w := range writePaths { fmt.Fprintf(contentSnippet, "%s/** mrwklix,\n", - resolveSpecialVariable(w, slot.Snap)) + resolveSpecialVariable(w, slot.Snap())) } } @@ -198,7 +236,7 @@ `) for _, r := range readPaths { fmt.Fprintf(contentSnippet, "%s/** mrkix,\n", - resolveSpecialVariable(r, slot.Snap)) + resolveSpecialVariable(r, slot.Snap())) } } @@ -213,7 +251,7 @@ // Interactions with the mount backend. -func (iface *contentInterface) MountConnectedPlug(spec *mount.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *contentInterface) MountConnectedPlug(spec *mount.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { for _, r := range iface.path(slot, "read") { err := spec.AddMountEntry(mountEntry(plug, slot, r, "ro")) if err != nil { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/content_test.go snapd-2.31.1+17.10/interfaces/builtin/content_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/content_test.go 2017-10-23 06:17:24.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/content_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -58,8 +58,8 @@ - shared/read ` info := snaptest.MockInfo(c, mockSnapYaml, nil) - slot := &interfaces.Slot{SlotInfo: info.Slots["content-slot"]} - c.Assert(slot.Sanitize(s.iface), IsNil) + slot := info.Slots["content-slot"] + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), IsNil) } func (s *ContentSuite) TestSanitizeSlotContentLabelDefault(c *C) { @@ -72,8 +72,8 @@ - shared/read ` info := snaptest.MockInfo(c, mockSnapYaml, nil) - slot := &interfaces.Slot{SlotInfo: info.Slots["content-slot"]} - c.Assert(slot.Sanitize(s.iface), IsNil) + slot := info.Slots["content-slot"] + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), IsNil) c.Assert(slot.Attrs["content"], Equals, slot.Name) } @@ -86,8 +86,8 @@ content: mycont ` info := snaptest.MockInfo(c, mockSnapYaml, nil) - slot := &interfaces.Slot{SlotInfo: info.Slots["content-slot"]} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, "read or write path must be set") + slot := info.Slots["content-slot"] + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "read or write path must be set") } func (s *ContentSuite) TestSanitizeSlotEmptyPaths(c *C) { @@ -101,8 +101,8 @@ write: [] ` info := snaptest.MockInfo(c, mockSnapYaml, nil) - slot := &interfaces.Slot{SlotInfo: info.Slots["content-slot"]} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, "read or write path must be set") + slot := info.Slots["content-slot"] + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "read or write path must be set") } func (s *ContentSuite) TestSanitizeSlotHasRelativePath(c *C) { @@ -115,11 +115,30 @@ ` for _, rw := range []string{"read: [../foo]", "write: [../bar]"} { info := snaptest.MockInfo(c, mockSnapYaml+" "+rw, nil) - slot := &interfaces.Slot{SlotInfo: info.Slots["content-slot"]} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, "content interface path is not clean:.*") + slot := info.Slots["content-slot"] + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "content interface path is not clean:.*") } } +func (s *ContentSuite) TestSanitizeSlotSourceAndLegacy(c *C) { + slot := MockSlot(c, `name: snap +slots: + content: + source: + write: [$SNAP_DATA/stuff] + read: [$SNAP/shared] +`, nil, "content") + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, `move the "read" attribute into the "source" section`) + slot = MockSlot(c, `name: snap +slots: + content: + source: + read: [$SNAP/shared] + write: [$SNAP_DATA/stuff] +`, nil, "content") + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, `move the "write" attribute into the "source" section`) +} + func (s *ContentSuite) TestSanitizePlugSimple(c *C) { const mockSnapYaml = `name: content-slot-snap version: 1.0 @@ -130,8 +149,8 @@ target: import ` info := snaptest.MockInfo(c, mockSnapYaml, nil) - plug := &interfaces.Plug{PlugInfo: info.Plugs["content-plug"]} - c.Assert(plug.Sanitize(s.iface), IsNil) + plug := info.Plugs["content-plug"] + c.Assert(interfaces.BeforePreparePlug(s.iface, plug), IsNil) } func (s *ContentSuite) TestSanitizePlugContentLabelDefault(c *C) { @@ -143,8 +162,8 @@ target: import ` info := snaptest.MockInfo(c, mockSnapYaml, nil) - plug := &interfaces.Plug{PlugInfo: info.Plugs["content-plug"]} - c.Assert(plug.Sanitize(s.iface), IsNil) + plug := info.Plugs["content-plug"] + c.Assert(interfaces.BeforePreparePlug(s.iface, plug), IsNil) c.Assert(plug.Attrs["content"], Equals, plug.Name) } @@ -157,8 +176,8 @@ content: mycont ` info := snaptest.MockInfo(c, mockSnapYaml, nil) - plug := &interfaces.Plug{PlugInfo: info.Plugs["content-plug"]} - c.Assert(plug.Sanitize(s.iface), ErrorMatches, "content plug must contain target path") + plug := info.Plugs["content-plug"] + c.Assert(interfaces.BeforePreparePlug(s.iface, plug), ErrorMatches, "content plug must contain target path") } func (s *ContentSuite) TestSanitizePlugSimpleTargetRelative(c *C) { @@ -171,8 +190,8 @@ target: ../foo ` info := snaptest.MockInfo(c, mockSnapYaml, nil) - plug := &interfaces.Plug{PlugInfo: info.Plugs["content-plug"]} - c.Assert(plug.Sanitize(s.iface), ErrorMatches, "content interface target path is not clean:.*") + plug := info.Plugs["content-plug"] + c.Assert(interfaces.BeforePreparePlug(s.iface, plug), ErrorMatches, "content interface target path is not clean:.*") } func (s *ContentSuite) TestSanitizePlugNilAttrMap(c *C) { @@ -184,8 +203,8 @@ plugs: [content] ` info := snaptest.MockInfo(c, mockSnapYaml, nil) - plug := &interfaces.Plug{PlugInfo: info.Plugs["content"]} - c.Assert(plug.Sanitize(s.iface), ErrorMatches, "content plug must contain target path") + plug := info.Plugs["content"] + c.Assert(interfaces.BeforePreparePlug(s.iface, plug), ErrorMatches, "content plug must contain target path") } func (s *ContentSuite) TestSanitizeSlotNilAttrMap(c *C) { @@ -197,8 +216,8 @@ slots: [content] ` info := snaptest.MockInfo(c, mockSnapYaml, nil) - slot := &interfaces.Slot{SlotInfo: info.Slots["content"]} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, "read or write path must be set") + slot := info.Slots["content"] + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "read or write path must be set") } func (s *ContentSuite) TestResolveSpecialVariable(c *C) { @@ -220,7 +239,7 @@ target: import ` consumerInfo := snaptest.MockInfo(c, consumerYaml, &snap.SideInfo{Revision: snap.R(7)}) - plug := &interfaces.Plug{PlugInfo: consumerInfo.Plugs["content"]} + plug := interfaces.NewConnectedPlug(consumerInfo.Plugs["content"], nil) const producerYaml = `name: producer slots: content: @@ -228,10 +247,10 @@ - export ` producerInfo := snaptest.MockInfo(c, producerYaml, &snap.SideInfo{Revision: snap.R(5)}) - slot := &interfaces.Slot{SlotInfo: producerInfo.Slots["content"]} + slot := interfaces.NewConnectedSlot(producerInfo.Slots["content"], nil) spec := &mount.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, plug, nil, slot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, plug, slot), IsNil) expectedMnt := []mount.Entry{{ Name: filepath.Join(dirs.CoreSnapMountDir, "producer/5/export"), Dir: filepath.Join(dirs.CoreSnapMountDir, "consumer/7/import"), @@ -251,7 +270,7 @@ command: foo ` consumerInfo := snaptest.MockInfo(c, consumerYaml, &snap.SideInfo{Revision: snap.R(7)}) - plug := &interfaces.Plug{PlugInfo: consumerInfo.Plugs["content"]} + plug := interfaces.NewConnectedPlug(consumerInfo.Plugs["content"], nil) const producerYaml = `name: producer slots: content: @@ -259,10 +278,10 @@ - $SNAP/export ` producerInfo := snaptest.MockInfo(c, producerYaml, &snap.SideInfo{Revision: snap.R(5)}) - slot := &interfaces.Slot{SlotInfo: producerInfo.Slots["content"]} + slot := interfaces.NewConnectedSlot(producerInfo.Slots["content"], nil) spec := &mount.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, plug, nil, slot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, plug, slot), IsNil) expectedMnt := []mount.Entry{{ Name: filepath.Join(dirs.CoreSnapMountDir, "producer/5/export"), Dir: filepath.Join(dirs.CoreSnapMountDir, "consumer/7/import"), @@ -271,7 +290,7 @@ c.Assert(spec.MountEntries(), DeepEquals, expectedMnt) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) expected := fmt.Sprintf(` @@ -294,7 +313,7 @@ command: foo ` consumerInfo := snaptest.MockInfo(c, consumerYaml, &snap.SideInfo{Revision: snap.R(7)}) - plug := &interfaces.Plug{PlugInfo: consumerInfo.Plugs["content"]} + plug := interfaces.NewConnectedPlug(consumerInfo.Plugs["content"], nil) const producerYaml = `name: producer slots: content: @@ -302,10 +321,10 @@ - $SNAP_DATA/export ` producerInfo := snaptest.MockInfo(c, producerYaml, &snap.SideInfo{Revision: snap.R(5)}) - slot := &interfaces.Slot{SlotInfo: producerInfo.Slots["content"]} + slot := interfaces.NewConnectedSlot(producerInfo.Slots["content"], nil) spec := &mount.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, plug, nil, slot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, plug, slot), IsNil) expectedMnt := []mount.Entry{{ Name: "/var/snap/producer/5/export", Dir: "/var/snap/consumer/7/import", @@ -314,7 +333,7 @@ c.Assert(spec.MountEntries(), DeepEquals, expectedMnt) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) expected := ` @@ -339,7 +358,7 @@ command: foo ` consumerInfo := snaptest.MockInfo(c, consumerYaml, &snap.SideInfo{Revision: snap.R(7)}) - plug := &interfaces.Plug{PlugInfo: consumerInfo.Plugs["content"]} + plug := interfaces.NewConnectedPlug(consumerInfo.Plugs["content"], nil) const producerYaml = `name: producer slots: content: @@ -347,10 +366,10 @@ - $SNAP_COMMON/export ` producerInfo := snaptest.MockInfo(c, producerYaml, &snap.SideInfo{Revision: snap.R(5)}) - slot := &interfaces.Slot{SlotInfo: producerInfo.Slots["content"]} + slot := interfaces.NewConnectedSlot(producerInfo.Slots["content"], nil) spec := &mount.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, plug, nil, slot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, plug, slot), IsNil) expectedMnt := []mount.Entry{{ Name: "/var/snap/producer/common/export", Dir: "/var/snap/consumer/common/import", @@ -359,7 +378,7 @@ c.Assert(spec.MountEntries(), DeepEquals, expectedMnt) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) expected := ` @@ -376,3 +395,216 @@ func (s *ContentSuite) TestInterfaces(c *C) { c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) } + +func (s *ContentSuite) TestModernContentInterface(c *C) { + plug := MockPlug(c, `name: consumer +plugs: + content: + target: $SNAP_COMMON/import +apps: + app: + command: foo +`, &snap.SideInfo{Revision: snap.R(1)}, "content") + connectedPlug := interfaces.NewConnectedPlug(plug, nil) + + slot := MockSlot(c, `name: producer +slots: + content: + source: + read: + - $SNAP_COMMON/read-common + - $SNAP_DATA/read-data + - $SNAP/read-snap + write: + - $SNAP_COMMON/write-common + - $SNAP_DATA/write-data +`, &snap.SideInfo{Revision: snap.R(2)}, "content") + connectedSlot := interfaces.NewConnectedSlot(slot, nil) + + // Create the mount and apparmor specifications. + mountSpec := &mount.Specification{} + c.Assert(mountSpec.AddConnectedPlug(s.iface, connectedPlug, connectedSlot), IsNil) + apparmorSpec := &apparmor.Specification{} + c.Assert(apparmorSpec.AddConnectedPlug(s.iface, connectedPlug, connectedSlot), IsNil) + + // Analyze the mount specification. + expectedMnt := []mount.Entry{{ + Name: "/var/snap/producer/common/read-common", + Dir: "/var/snap/consumer/common/import/read-common", + Options: []string{"bind", "ro"}, + }, { + Name: "/var/snap/producer/2/read-data", + Dir: "/var/snap/consumer/common/import/read-data", + Options: []string{"bind", "ro"}, + }, { + Name: "/snap/producer/2/read-snap", + Dir: "/var/snap/consumer/common/import/read-snap", + Options: []string{"bind", "ro"}, + }, { + Name: "/var/snap/producer/common/write-common", + Dir: "/var/snap/consumer/common/import/write-common", + Options: []string{"bind"}, + }, { + Name: "/var/snap/producer/2/write-data", + Dir: "/var/snap/consumer/common/import/write-data", + Options: []string{"bind"}, + }} + c.Assert(mountSpec.MountEntries(), DeepEquals, expectedMnt) + + // Analyze the apparmor specification. + c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) + expected := ` +# In addition to the bind mount, add any AppArmor rules so that +# snaps may directly access the slot implementation's files. Due +# to a limitation in the kernel's LSM hooks for AF_UNIX, these +# are needed for using named sockets within the exported +# directory. +/var/snap/producer/common/write-common/** mrwklix, +/var/snap/producer/2/write-data/** mrwklix, + +# In addition to the bind mount, add any AppArmor rules so that +# snaps may directly access the slot implementation's files +# read-only. +/var/snap/producer/common/read-common/** mrkix, +/var/snap/producer/2/read-data/** mrkix, +/snap/producer/2/read-snap/** mrkix, +` + c.Assert(apparmorSpec.SnippetForTag("snap.consumer.app"), Equals, expected) +} + +func (s *ContentSuite) TestModernContentInterfacePlugins(c *C) { + // Define one app snap and two snaps plugin snaps. + plug := MockPlug(c, `name: app +plugs: + plugins: + interface: content + content: plugin-for-app + target: $SNAP/plugins +apps: + app: + command: foo + +`, &snap.SideInfo{Revision: snap.R(1)}, "plugins") + connectedPlug := interfaces.NewConnectedPlug(plug, nil) + + // XXX: realistically the plugin may be a single file and we don't support + // those very well. + slotOne := MockSlot(c, `name: plugin-one +slots: + plugin-for-app: + interface: content + source: + read: [$SNAP/plugin] +`, &snap.SideInfo{Revision: snap.R(1)}, "plugin-for-app") + connectedSlotOne := interfaces.NewConnectedSlot(slotOne, nil) + + slotTwo := MockSlot(c, `name: plugin-two +slots: + plugin-for-app: + interface: content + source: + read: [$SNAP/plugin] +`, &snap.SideInfo{Revision: snap.R(1)}, "plugin-for-app") + connectedSlotTwo := interfaces.NewConnectedSlot(slotTwo, nil) + + // Create the mount and apparmor specifications. + mountSpec := &mount.Specification{} + apparmorSpec := &apparmor.Specification{} + for _, connectedSlot := range []*interfaces.ConnectedSlot{connectedSlotOne, connectedSlotTwo} { + c.Assert(mountSpec.AddConnectedPlug(s.iface, connectedPlug, connectedSlot), IsNil) + c.Assert(apparmorSpec.AddConnectedPlug(s.iface, connectedPlug, connectedSlot), IsNil) + } + + // Analyze the mount specification. + expectedMnt := []mount.Entry{{ + Name: "/snap/plugin-one/1/plugin", + Dir: "/snap/app/1/plugins/plugin", + Options: []string{"bind", "ro"}, + }, { + Name: "/snap/plugin-two/1/plugin", + Dir: "/snap/app/1/plugins/plugin-2", + Options: []string{"bind", "ro"}, + }} + c.Assert(mountSpec.MountEntries(), DeepEquals, expectedMnt) + + // Analyze the apparmor specification. + // + // NOTE: the paths below refer to the original locations and are *NOT* + // altered like the mount entries above. This is intended. See the comment + // below for explanation as to why those are necessary. + c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.app.app"}) + expected := ` +# In addition to the bind mount, add any AppArmor rules so that +# snaps may directly access the slot implementation's files +# read-only. +/snap/plugin-one/1/plugin/** mrkix, + + +# In addition to the bind mount, add any AppArmor rules so that +# snaps may directly access the slot implementation's files +# read-only. +/snap/plugin-two/1/plugin/** mrkix, +` + c.Assert(apparmorSpec.SnippetForTag("snap.app.app"), Equals, expected) +} + +func (s *ContentSuite) TestModernContentSameReadAndWriteClash(c *C) { + plug := MockPlug(c, `name: consumer +plugs: + content: + target: $SNAP_COMMON/import +apps: + app: + command: foo +`, &snap.SideInfo{Revision: snap.R(1)}, "content") + connectedPlug := interfaces.NewConnectedPlug(plug, nil) + + slot := MockSlot(c, `name: producer +slots: + content: + source: + read: + - $SNAP_DATA/directory + write: + - $SNAP_DATA/directory +`, &snap.SideInfo{Revision: snap.R(2)}, "content") + connectedSlot := interfaces.NewConnectedSlot(slot, nil) + + // Create the mount and apparmor specifications. + mountSpec := &mount.Specification{} + c.Assert(mountSpec.AddConnectedPlug(s.iface, connectedPlug, connectedSlot), IsNil) + apparmorSpec := &apparmor.Specification{} + c.Assert(apparmorSpec.AddConnectedPlug(s.iface, connectedPlug, connectedSlot), IsNil) + + // Analyze the mount specification + expectedMnt := []mount.Entry{{ + Name: "/var/snap/producer/2/directory", + Dir: "/var/snap/consumer/common/import/directory", + Options: []string{"bind", "ro"}, + }, { + Name: "/var/snap/producer/2/directory", + Dir: "/var/snap/consumer/common/import/directory-2", + Options: []string{"bind"}, + }} + c.Assert(mountSpec.MountEntries(), DeepEquals, expectedMnt) + + // Analyze the apparmor specification. + // + // NOTE: Although there are duplicate entries with different permissions + // one is a superset of the other so they do not conflict. + c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) + expected := ` +# In addition to the bind mount, add any AppArmor rules so that +# snaps may directly access the slot implementation's files. Due +# to a limitation in the kernel's LSM hooks for AF_UNIX, these +# are needed for using named sockets within the exported +# directory. +/var/snap/producer/2/directory/** mrwklix, + +# In addition to the bind mount, add any AppArmor rules so that +# snaps may directly access the slot implementation's files +# read-only. +/var/snap/producer/2/directory/** mrkix, +` + c.Assert(apparmorSpec.SnippetForTag("snap.consumer.app"), Equals, expected) +} diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/core_support_test.go snapd-2.31.1+17.10/interfaces/builtin/core_support_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/core_support_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/core_support_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type CoreSupportInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&CoreSupportInterfaceSuite{ @@ -47,16 +49,16 @@ prepare-device: plugs: [core-support] ` - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "core-support", - Interface: "core-support", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "core-support", + Interface: "core-support", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, mockPlugSnapInfo, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["core-support"]} + s.plugInfo = plugSnap.Plugs["core-support"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *CoreSupportInterfaceSuite) TestName(c *C) { @@ -64,31 +66,31 @@ } func (s *CoreSupportInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "core-support", Interface: "core-support", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "core-support slots are reserved for the core snap") } func (s *CoreSupportInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *CoreSupportInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), HasLen, 1) } func (s *CoreSupportInterfaceSuite) TestConnectedPlugSnippet(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.hook.prepare-device"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.hook.prepare-device"), testutil.Contains, `/bin/systemctl Uxr,`) diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/dbus.go snapd-2.31.1+17.10/interfaces/builtin/dbus.go --- snapd-2.29.4.2+17.10/interfaces/builtin/dbus.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/dbus.go 2018-01-24 20:02:44.000000000 +0000 @@ -28,7 +28,9 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/dbus" + "github.com/snapcore/snapd/interfaces/seccomp" "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" ) const dbusSummary = `allows owning a specifc name on DBus` @@ -136,6 +138,13 @@ ` +const dbusPermanentSlotSecComp = ` +# Description: Allow owning a name and listening on DBus public bus +listen +accept +accept4 +` + const dbusConnectedSlotAppArmor = ` # allow snaps to introspect us. This allows clients to introspect all # DBus interfaces of this service (but not use them). @@ -212,10 +221,10 @@ } // Obtain yaml-specified bus well-known name -func (iface *dbusInterface) getAttribs(attribs map[string]interface{}) (string, string, error) { +func (iface *dbusInterface) getAttribs(attribs interfaces.Attrer) (string, string, error) { // bus attribute - bus, ok := attribs["bus"].(string) - if !ok { + var bus string + if err := attribs.Attr("bus", &bus); err != nil { return "", "", fmt.Errorf("cannot find attribute 'bus'") } @@ -224,8 +233,8 @@ } // name attribute - name, ok := attribs["name"].(string) - if !ok { + var name string + if err := attribs.Attr("name", &name); err != nil { return "", "", fmt.Errorf("cannot find attribute 'name'") } @@ -292,13 +301,13 @@ return snippet } -func (iface *dbusInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - bus, name, err := iface.getAttribs(plug.Attrs) +func (iface *dbusInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { + bus, name, err := iface.getAttribs(plug) if err != nil { return err } - busSlot, nameSlot, err := iface.getAttribs(slot.Attrs) + busSlot, nameSlot, err := iface.getAttribs(slot) if err != nil { return err } @@ -329,8 +338,8 @@ return nil } -func (iface *dbusInterface) DBusPermanentSlot(spec *dbus.Specification, slot *interfaces.Slot) error { - bus, name, err := iface.getAttribs(slot.Attrs) +func (iface *dbusInterface) DBusPermanentSlot(spec *dbus.Specification, slot *snap.SlotInfo) error { + bus, name, err := iface.getAttribs(slot) if err != nil { return err } @@ -346,8 +355,8 @@ return nil } -func (iface *dbusInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { - bus, name, err := iface.getAttribs(slot.Attrs) +func (iface *dbusInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { + bus, name, err := iface.getAttribs(slot) if err != nil { return err } @@ -373,13 +382,18 @@ return nil } -func (iface *dbusInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - bus, name, err := iface.getAttribs(slot.Attrs) +func (iface *dbusInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *snap.SlotInfo) error { + spec.AddSnippet(dbusPermanentSlotSecComp) + return nil +} + +func (iface *dbusInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { + bus, name, err := iface.getAttribs(slot) if err != nil { return err } - busPlug, namePlug, err := iface.getAttribs(plug.Attrs) + busPlug, namePlug, err := iface.getAttribs(plug) if err != nil { return err } @@ -403,13 +417,13 @@ return nil } -func (iface *dbusInterface) SanitizePlug(plug *interfaces.Plug) error { - _, _, err := iface.getAttribs(plug.Attrs) +func (iface *dbusInterface) BeforePreparePlug(plug *snap.PlugInfo) error { + _, _, err := iface.getAttribs(plug) return err } -func (iface *dbusInterface) SanitizeSlot(slot *interfaces.Slot) error { - _, _, err := iface.getAttribs(slot.Attrs) +func (iface *dbusInterface) BeforePrepareSlot(slot *snap.SlotInfo) error { + _, _, err := iface.getAttribs(slot) return err } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/dbus_test.go snapd-2.31.1+17.10/interfaces/builtin/dbus_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/dbus_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/dbus_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -26,6 +26,7 @@ "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/builtin" "github.com/snapcore/snapd/interfaces/dbus" + "github.com/snapcore/snapd/interfaces/seccomp" "github.com/snapcore/snapd/release" "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap/snaptest" @@ -38,15 +39,23 @@ snapInfo *snap.Info - sessionPlug *interfaces.Plug - systemPlug *interfaces.Plug - connectedSessionPlug *interfaces.Plug - connectedSystemPlug *interfaces.Plug - - sessionSlot *interfaces.Slot - systemSlot *interfaces.Slot - connectedSessionSlot *interfaces.Slot - connectedSystemSlot *interfaces.Slot + sessionPlugInfo *snap.PlugInfo + sessionPlug *interfaces.ConnectedPlug + systemPlugInfo *snap.PlugInfo + systemPlug *interfaces.ConnectedPlug + connectedSessionPlugInfo *snap.PlugInfo + connectedSessionPlug *interfaces.ConnectedPlug + connectedSystemPlugInfo *snap.PlugInfo + connectedSystemPlug *interfaces.ConnectedPlug + + sessionSlotInfo *snap.SlotInfo + sessionSlot *interfaces.ConnectedSlot + systemSlotInfo *snap.SlotInfo + systemSlot *interfaces.ConnectedSlot + connectedSessionSlotInfo *snap.SlotInfo + connectedSessionSlot *interfaces.ConnectedSlot + connectedSystemSlotInfo *snap.SlotInfo + connectedSystemSlot *interfaces.ConnectedSlot } var _ = Suite(&DbusInterfaceSuite{ @@ -109,15 +118,23 @@ } func (s *DbusInterfaceSuite) SetUpTest(c *C) { - s.sessionSlot = &interfaces.Slot{SlotInfo: s.snapInfo.Slots["test-session-slot"]} - s.systemSlot = &interfaces.Slot{SlotInfo: s.snapInfo.Slots["test-system-slot"]} - s.connectedSessionSlot = &interfaces.Slot{SlotInfo: s.snapInfo.Slots["test-session-connected-slot"]} - s.connectedSystemSlot = &interfaces.Slot{SlotInfo: s.snapInfo.Slots["test-system-connected-slot"]} - - s.sessionPlug = &interfaces.Plug{PlugInfo: s.snapInfo.Plugs["test-session-plug"]} - s.systemPlug = &interfaces.Plug{PlugInfo: s.snapInfo.Plugs["test-system-plug"]} - s.connectedSessionPlug = &interfaces.Plug{PlugInfo: s.snapInfo.Plugs["test-session-connected-plug"]} - s.connectedSystemPlug = &interfaces.Plug{PlugInfo: s.snapInfo.Plugs["test-system-connected-plug"]} + s.sessionSlotInfo = s.snapInfo.Slots["test-session-slot"] + s.sessionSlot = interfaces.NewConnectedSlot(s.sessionSlotInfo, nil) + s.systemSlotInfo = s.snapInfo.Slots["test-system-slot"] + s.systemSlot = interfaces.NewConnectedSlot(s.systemSlotInfo, nil) + s.connectedSessionSlotInfo = s.snapInfo.Slots["test-session-connected-slot"] + s.connectedSessionSlot = interfaces.NewConnectedSlot(s.connectedSessionSlotInfo, nil) + s.connectedSystemSlotInfo = s.snapInfo.Slots["test-system-connected-slot"] + s.connectedSystemSlot = interfaces.NewConnectedSlot(s.connectedSystemSlotInfo, nil) + + s.sessionPlugInfo = s.snapInfo.Plugs["test-session-plug"] + s.sessionPlug = interfaces.NewConnectedPlug(s.sessionPlugInfo, nil) + s.systemPlugInfo = s.snapInfo.Plugs["test-system-plug"] + s.systemPlug = interfaces.NewConnectedPlug(s.systemPlugInfo, nil) + s.connectedSessionPlugInfo = s.snapInfo.Plugs["test-session-connected-plug"] + s.connectedSessionPlug = interfaces.NewConnectedPlug(s.connectedSessionPlugInfo, nil) + s.connectedSystemPlugInfo = s.snapInfo.Plugs["test-system-connected-plug"] + s.connectedSystemPlug = interfaces.NewConnectedPlug(s.connectedSystemPlugInfo, nil) } func (s *DbusInterfaceSuite) TestName(c *C) { @@ -137,8 +154,8 @@ info, err := snap.InfoFromSnapYaml(mockSnapYaml) c.Assert(err, IsNil) - slot := &interfaces.Slot{SlotInfo: info.Slots["dbus-slot"]} - c.Assert(slot.Sanitize(s.iface), IsNil) + slot := info.Slots["dbus-slot"] + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), IsNil) } func (s *DbusInterfaceSuite) TestValidSystemBusName(c *C) { @@ -154,8 +171,8 @@ info, err := snap.InfoFromSnapYaml(mockSnapYaml) c.Assert(err, IsNil) - slot := &interfaces.Slot{SlotInfo: info.Slots["dbus-slot"]} - c.Assert(slot.Sanitize(s.iface), IsNil) + slot := info.Slots["dbus-slot"] + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), IsNil) } func (s *DbusInterfaceSuite) TestValidFullBusName(c *C) { @@ -171,8 +188,8 @@ info, err := snap.InfoFromSnapYaml(mockSnapYaml) c.Assert(err, IsNil) - slot := &interfaces.Slot{SlotInfo: info.Slots["dbus-slot"]} - c.Assert(slot.Sanitize(s.iface), IsNil) + slot := info.Slots["dbus-slot"] + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), IsNil) } func (s *DbusInterfaceSuite) TestNonexistentBusName(c *C) { @@ -188,8 +205,8 @@ info, err := snap.InfoFromSnapYaml(mockSnapYaml) c.Assert(err, IsNil) - slot := &interfaces.Slot{SlotInfo: info.Slots["dbus-slot"]} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, "bus 'nonexistent' must be one of 'session' or 'system'") + slot := info.Slots["dbus-slot"] + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "bus 'nonexistent' must be one of 'session' or 'system'") } // If this test is failing, be sure to verify the AppArmor rules for binding to @@ -207,8 +224,8 @@ info, err := snap.InfoFromSnapYaml(mockSnapYaml) c.Assert(err, IsNil) - slot := &interfaces.Slot{SlotInfo: info.Slots["dbus-slot"]} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, "DBus bus name must not end with -NUMBER") + slot := info.Slots["dbus-slot"] + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "DBus bus name must not end with -NUMBER") } func (s *DbusInterfaceSuite) TestSanitizeSlotSystem(c *C) { @@ -224,8 +241,8 @@ info, err := snap.InfoFromSnapYaml(mockSnapYaml) c.Assert(err, IsNil) - slot := &interfaces.Slot{SlotInfo: info.Slots["dbus-slot"]} - c.Assert(slot.Sanitize(s.iface), IsNil) + slot := info.Slots["dbus-slot"] + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), IsNil) } func (s *DbusInterfaceSuite) TestSanitizeSlotSession(c *C) { @@ -241,8 +258,8 @@ info, err := snap.InfoFromSnapYaml(mockSnapYaml) c.Assert(err, IsNil) - slot := &interfaces.Slot{SlotInfo: info.Slots["dbus-slot"]} - c.Assert(slot.Sanitize(s.iface), IsNil) + slot := info.Slots["dbus-slot"] + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), IsNil) } func (s *DbusInterfaceSuite) TestSanitizePlugSystem(c *C) { @@ -258,8 +275,8 @@ info, err := snap.InfoFromSnapYaml(mockSnapYaml) c.Assert(err, IsNil) - plug := &interfaces.Plug{PlugInfo: info.Plugs["dbus-plug"]} - c.Assert(plug.Sanitize(s.iface), IsNil) + plug := info.Plugs["dbus-plug"] + c.Assert(interfaces.BeforePreparePlug(s.iface, plug), IsNil) } func (s *DbusInterfaceSuite) TestSanitizePlugSession(c *C) { @@ -275,13 +292,13 @@ info, err := snap.InfoFromSnapYaml(mockSnapYaml) c.Assert(err, IsNil) - plug := &interfaces.Plug{PlugInfo: info.Plugs["dbus-plug"]} - c.Assert(plug.Sanitize(s.iface), IsNil) + plug := info.Plugs["dbus-plug"] + c.Assert(interfaces.BeforePreparePlug(s.iface, plug), IsNil) } func (s *DbusInterfaceSuite) TestPermanentSlotAppArmorSession(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddPermanentSlot(s.iface, s.sessionSlot) + err := apparmorSpec.AddPermanentSlot(s.iface, s.sessionSlotInfo) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.test-dbus.test-session-provider"}) snippet := apparmorSpec.SnippetForTag("snap.test-dbus.test-session-provider") @@ -307,7 +324,7 @@ defer restore() apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddPermanentSlot(s.iface, s.sessionSlot) + err := apparmorSpec.AddPermanentSlot(s.iface, s.sessionSlotInfo) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.test-dbus.test-session-provider"}) @@ -320,7 +337,7 @@ defer restore() apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddPermanentSlot(s.iface, s.sessionSlot) + err := apparmorSpec.AddPermanentSlot(s.iface, s.sessionSlotInfo) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.test-dbus.test-session-provider"}) @@ -330,7 +347,7 @@ func (s *DbusInterfaceSuite) TestPermanentSlotAppArmorSystem(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddPermanentSlot(s.iface, s.systemSlot) + err := apparmorSpec.AddPermanentSlot(s.iface, s.systemSlotInfo) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.test-dbus.test-system-provider"}) snippet := apparmorSpec.SnippetForTag("snap.test-dbus.test-system-provider") @@ -353,14 +370,14 @@ func (s *DbusInterfaceSuite) TestPermanentSlotDBusSession(c *C) { dbusSpec := &dbus.Specification{} - err := dbusSpec.AddPermanentSlot(s.iface, s.sessionSlot) + err := dbusSpec.AddPermanentSlot(s.iface, s.sessionSlotInfo) c.Assert(err, IsNil) c.Assert(dbusSpec.SecurityTags(), HasLen, 0) } func (s *DbusInterfaceSuite) TestPermanentSlotDBusSystem(c *C) { dbusSpec := &dbus.Specification{} - err := dbusSpec.AddPermanentSlot(s.iface, s.systemSlot) + err := dbusSpec.AddPermanentSlot(s.iface, s.systemSlotInfo) c.Assert(err, IsNil) c.Assert(dbusSpec.SecurityTags(), DeepEquals, []string{"snap.test-dbus.test-system-provider"}) snippet := dbusSpec.SnippetForTag("snap.test-dbus.test-system-provider") @@ -368,9 +385,27 @@ c.Check(snippet, testutil.Contains, "\n ") } +func (s *DbusInterfaceSuite) TestPermanentSlotSecCompSystem(c *C) { + seccompSpec := &seccomp.Specification{} + err := seccompSpec.AddPermanentSlot(s.iface, s.systemSlotInfo) + c.Assert(err, IsNil) + c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.test-dbus.test-system-provider"}) + snippet := seccompSpec.SnippetForTag("snap.test-dbus.test-system-provider") + c.Check(snippet, testutil.Contains, "listen\naccept\naccept4\n") +} + +func (s *DbusInterfaceSuite) TestPermanentSlotSecCompSession(c *C) { + seccompSpec := &seccomp.Specification{} + err := seccompSpec.AddPermanentSlot(s.iface, s.sessionSlotInfo) + c.Assert(err, IsNil) + c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.test-dbus.test-session-provider"}) + snippet := seccompSpec.SnippetForTag("snap.test-dbus.test-session-provider") + c.Check(snippet, testutil.Contains, "listen\naccept\naccept4\n") +} + func (s *DbusInterfaceSuite) TestConnectedSlotAppArmorSession(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedSlot(s.iface, s.connectedSessionPlug, nil, s.connectedSessionSlot, nil) + err := apparmorSpec.AddConnectedSlot(s.iface, s.connectedSessionPlug, s.connectedSessionSlot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.test-dbus.test-session-consumer", "snap.test-dbus.test-session-provider", "snap.test-dbus.test-system-consumer", "snap.test-dbus.test-system-provider"}) snippet := apparmorSpec.SnippetForTag("snap.test-dbus.test-session-provider") @@ -390,7 +425,7 @@ func (s *DbusInterfaceSuite) TestConnectedSlotAppArmorSystem(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedSlot(s.iface, s.connectedSystemPlug, nil, s.connectedSystemSlot, nil) + err := apparmorSpec.AddConnectedSlot(s.iface, s.connectedSystemPlug, s.connectedSystemSlot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.test-dbus.test-session-consumer", "snap.test-dbus.test-session-provider", "snap.test-dbus.test-system-consumer", "snap.test-dbus.test-system-provider"}) snippet := apparmorSpec.SnippetForTag("snap.test-dbus.test-session-provider") @@ -410,7 +445,7 @@ func (s *DbusInterfaceSuite) TestConnectedPlugAppArmorSession(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.connectedSessionPlug, nil, s.connectedSessionSlot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.connectedSessionPlug, s.connectedSessionSlot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.test-dbus.test-session-consumer", "snap.test-dbus.test-session-provider", "snap.test-dbus.test-system-consumer", "snap.test-dbus.test-system-provider"}) snippet := apparmorSpec.SnippetForTag("snap.test-dbus.test-session-consumer") @@ -435,7 +470,7 @@ func (s *DbusInterfaceSuite) TestConnectedPlugAppArmorSystem(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.connectedSystemPlug, nil, s.connectedSystemSlot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.connectedSystemPlug, s.connectedSystemSlot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.test-dbus.test-session-consumer", "snap.test-dbus.test-session-provider", "snap.test-dbus.test-system-consumer", "snap.test-dbus.test-system-provider"}) snippet := apparmorSpec.SnippetForTag("snap.test-dbus.test-session-consumer") @@ -482,14 +517,14 @@ ` plugInfo := snaptest.MockInfo(c, plugYaml, nil) - matchingPlug := &interfaces.Plug{PlugInfo: plugInfo.Plugs["this"]} + matchingPlug := interfaces.NewConnectedPlug(plugInfo.Plugs["this"], nil) slotInfo := snaptest.MockInfo(c, slotYaml, nil) - matchingSlot := &interfaces.Slot{SlotInfo: slotInfo.Slots["this"]} - nonmatchingSlot := &interfaces.Slot{SlotInfo: slotInfo.Slots["that"]} + matchingSlot := interfaces.NewConnectedSlot(slotInfo.Slots["this"], nil) + nonmatchingSlot := interfaces.NewConnectedSlot(slotInfo.Slots["that"], nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, matchingPlug, nil, matchingSlot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, matchingPlug, matchingSlot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.plugger.app"}) snippet := apparmorSpec.SnippetForTag("snap.plugger.app") @@ -500,7 +535,7 @@ c.Check(snippet, Not(testutil.Contains), "bus=system") apparmorSpec = &apparmor.Specification{} - err = apparmorSpec.AddConnectedPlug(s.iface, matchingPlug, nil, nonmatchingSlot, nil) + err = apparmorSpec.AddConnectedPlug(s.iface, matchingPlug, nonmatchingSlot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), HasLen, 0) } @@ -531,14 +566,14 @@ ` plugInfo := snaptest.MockInfo(c, plugYaml, nil) - matchingPlug := &interfaces.Plug{PlugInfo: plugInfo.Plugs["that"]} + matchingPlug := interfaces.NewConnectedPlug(plugInfo.Plugs["that"], nil) slotInfo := snaptest.MockInfo(c, slotYaml, nil) - matchingSlot := &interfaces.Slot{SlotInfo: slotInfo.Slots["that"]} - nonmatchingSlot := &interfaces.Slot{SlotInfo: slotInfo.Slots["this"]} + matchingSlot := interfaces.NewConnectedSlot(slotInfo.Slots["that"], nil) + nonmatchingSlot := interfaces.NewConnectedSlot(slotInfo.Slots["this"], nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, matchingPlug, nil, matchingSlot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, matchingPlug, matchingSlot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.plugger.app"}) snippet := apparmorSpec.SnippetForTag("snap.plugger.app") @@ -549,7 +584,7 @@ c.Check(snippet, Not(testutil.Contains), "bus=session") apparmorSpec = &apparmor.Specification{} - err = apparmorSpec.AddConnectedPlug(s.iface, matchingPlug, nil, nonmatchingSlot, nil) + err = apparmorSpec.AddConnectedPlug(s.iface, matchingPlug, nonmatchingSlot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), HasLen, 0) } @@ -584,15 +619,15 @@ ` plugInfo := snaptest.MockInfo(c, plugYaml, nil) - matchingPlug1 := &interfaces.Plug{PlugInfo: plugInfo.Plugs["this"]} - matchingPlug2 := &interfaces.Plug{PlugInfo: plugInfo.Plugs["that"]} + matchingPlug1 := interfaces.NewConnectedPlug(plugInfo.Plugs["this"], nil) + matchingPlug2 := interfaces.NewConnectedPlug(plugInfo.Plugs["that"], nil) slotInfo := snaptest.MockInfo(c, slotYaml, nil) - matchingSlot1 := &interfaces.Slot{SlotInfo: slotInfo.Slots["this"]} - matchingSlot2 := &interfaces.Slot{SlotInfo: slotInfo.Slots["that"]} + matchingSlot1 := interfaces.NewConnectedSlot(slotInfo.Slots["this"], nil) + matchingSlot2 := interfaces.NewConnectedSlot(slotInfo.Slots["that"], nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, matchingPlug1, nil, matchingSlot1, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, matchingPlug1, matchingSlot1) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.plugger.app"}) snippet := apparmorSpec.SnippetForTag("snap.plugger.app") @@ -600,7 +635,7 @@ c.Check(snippet, testutil.Contains, "bus=session") apparmorSpec = &apparmor.Specification{} - err = apparmorSpec.AddConnectedPlug(s.iface, matchingPlug2, nil, matchingSlot2, nil) + err = apparmorSpec.AddConnectedPlug(s.iface, matchingPlug2, matchingSlot2) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.plugger.app"}) snippet = apparmorSpec.SnippetForTag("snap.plugger.app") @@ -627,13 +662,13 @@ ` plugInfo := snaptest.MockInfo(c, plugYaml, nil) - plug := &interfaces.Plug{PlugInfo: plugInfo.Plugs["this"]} + plug := interfaces.NewConnectedPlug(plugInfo.Plugs["this"], nil) slotInfo := snaptest.MockInfo(c, slotYaml, nil) - slot := &interfaces.Slot{SlotInfo: slotInfo.Slots["this"]} + slot := interfaces.NewConnectedSlot(slotInfo.Slots["this"], nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), HasLen, 0) } @@ -657,13 +692,13 @@ ` plugInfo := snaptest.MockInfo(c, plugYaml, nil) - plug := &interfaces.Plug{PlugInfo: plugInfo.Plugs["this"]} + plug := interfaces.NewConnectedPlug(plugInfo.Plugs["this"], nil) slotInfo := snaptest.MockInfo(c, slotYaml, nil) - slot := &interfaces.Slot{SlotInfo: slotInfo.Slots["this"]} + slot := interfaces.NewConnectedSlot(slotInfo.Slots["this"], nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), HasLen, 0) } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/dcdbas_control_test.go snapd-2.31.1+17.10/interfaces/builtin/dcdbas_control_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/dcdbas_control_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/dcdbas_control_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type DcdbasControlInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&DcdbasControlInterfaceSuite{ @@ -48,14 +50,14 @@ command: foo plugs: [dcdbas-control] `, nil) - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "dcdbas-control", - Interface: "dcdbas-control", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "dcdbas-control", + Interface: "dcdbas-control", } - s.plug = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["dcdbas-control"]} + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) + s.plugInfo = consumingSnapInfo.Plugs["dcdbas-control"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *DcdbasControlInterfaceSuite) TestName(c *C) { @@ -63,24 +65,24 @@ } func (s *DcdbasControlInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "dcdbas-control", Interface: "dcdbas-control", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "dcdbas-control slots are reserved for the core snap") } func (s *DcdbasControlInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *DcdbasControlInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `/dcdbas/smi_data`) diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/desktop.go snapd-2.31.1+17.10/interfaces/builtin/desktop.go --- snapd-2.29.4.2+17.10/interfaces/builtin/desktop.go 2017-11-30 07:12:26.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/desktop.go 2018-01-31 08:47:17.000000000 +0000 @@ -26,6 +26,7 @@ "github.com/snapcore/snapd/interfaces/mount" "github.com/snapcore/snapd/osutil" "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" ) const desktopSummary = `allows access to basic graphical desktop resources` @@ -44,7 +45,16 @@ #include #include +# Allow finding the DBus session bus id (eg, via dbus_bus_get_id()) +dbus (send) + bus=session + path=/org/freedesktop/DBus + interface=org.freedesktop.DBus + member=GetId + peer=(name=org.freedesktop.DBus, label=unconfined), + #include +owner @{HOME}/.local/share/fonts/{,**} r, /var/cache/fontconfig/ r, /var/cache/fontconfig/** mr, @@ -88,14 +98,14 @@ bus=session path=/org/freedesktop/Notifications interface=org.freedesktop.Notifications - member="{GetCapabilities,GetServerInformation,Notify}" + member="{GetCapabilities,GetServerInformation,Notify,CloseNotification}" peer=(label=unconfined), dbus (receive) bus=session path=/org/freedesktop/Notifications interface=org.freedesktop.Notifications - member=NotificationClosed + member={ActionInvoked,NotificationClosed} peer=(label=unconfined), # Allow requesting interest in receiving media key events. This tells Gnome @@ -131,6 +141,39 @@ interface=io.snapcraft.Launcher member=OpenURL peer=(label=unconfined), + +# Allow checking status, activating and locking the screensaver +# gnome/kde/freedesktop.org +dbus (send) + bus=session + path="/{,org/freedesktop/,org/gnome/}ScreenSaver" + interface="org.{freedesktop,gnome}.ScreenSaver" + member="{GetActive,GetActiveTime,Lock,SetActive}" + peer=(label=unconfined), + +dbus (receive) + bus=session + path="/{,org/freedesktop/,org/gnome/}ScreenSaver" + interface="org.{freedesktop,gnome}.ScreenSaver" + member=ActiveChanged + peer=(label=unconfined), + +# Allow unconfined to introspect us +dbus (receive) + bus=session + interface=org.freedesktop.DBus.Introspectable + member=Introspect + peer=(label=unconfined), + +# Allow use of snapd's internal 'xdg-settings' +/usr/bin/xdg-settings ixr, +/usr/bin/dbus-send ixr, +dbus (send) + bus=session + path=/io/snapcraft/Settings + interface=io.snapcraft.Settings + member={Check,Get,Set} + peer=(label=unconfined), ` type desktopInterface struct{} @@ -147,7 +190,7 @@ } } -func (iface *desktopInterface) SanitizeSlot(slot *interfaces.Slot) error { +func (iface *desktopInterface) BeforePrepareSlot(slot *snap.SlotInfo) error { return sanitizeSlotReservedForOS(iface, slot) } @@ -156,12 +199,12 @@ return true } -func (iface *desktopInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *desktopInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { spec.AddSnippet(desktopConnectedPlugAppArmor) return nil } -func (iface *desktopInterface) MountConnectedPlug(spec *mount.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *desktopInterface) MountConnectedPlug(spec *mount.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { if !release.OnClassic { // There is nothing to expose on an all-snaps system return nil diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/desktop_legacy.go snapd-2.31.1+17.10/interfaces/builtin/desktop_legacy.go --- snapd-2.29.4.2+17.10/interfaces/builtin/desktop_legacy.go 2017-10-23 06:17:24.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/desktop_legacy.go 2018-02-02 16:53:04.000000000 +0000 @@ -212,6 +212,18 @@ interface=org.freedesktop.DBus.Properties member=GetAll peer=(label=unconfined), + +# gtk2/gvfs gtk_show_uri() +dbus (send) + bus=session + path=/org/gtk/vfs/mounttracker + interface=org.gtk.vfs.MountTracker + member=ListMountableInfo, +dbus (send) + bus=session + path=/org/gtk/vfs/mounttracker + interface=org.gtk.vfs.MountTracker + member=LookupMount, ` const desktopLegacyConnectedPlugSecComp = ` diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/desktop_legacy_test.go snapd-2.31.1+17.10/interfaces/builtin/desktop_legacy_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/desktop_legacy_test.go 2017-10-23 06:17:24.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/desktop_legacy_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -30,9 +30,11 @@ ) type DesktopLegacyInterfaceSuite struct { - iface interfaces.Interface - coreSlot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + coreSlotInfo *snap.SlotInfo + coreSlot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&DesktopLegacyInterfaceSuite{ @@ -52,8 +54,8 @@ ` func (s *DesktopLegacyInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, desktopLegacyConsumerYaml, nil, "desktop-legacy") - s.coreSlot = MockSlot(c, desktopLegacyCoreYaml, nil, "desktop-legacy") + s.plug, s.plugInfo = MockConnectedPlug(c, desktopLegacyConsumerYaml, nil, "desktop-legacy") + s.coreSlot, s.coreSlotInfo = MockConnectedSlot(c, desktopLegacyCoreYaml, nil, "desktop-legacy") } func (s *DesktopLegacyInterfaceSuite) TestName(c *C) { @@ -61,25 +63,25 @@ } func (s *DesktopLegacyInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.coreSlot.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.coreSlotInfo), IsNil) // desktop-legacy slot currently only used with core - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "desktop-legacy", Interface: "desktop-legacy", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "desktop-legacy slots are reserved for the core snap") } func (s *DesktopLegacyInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *DesktopLegacyInterfaceSuite) TestAppArmorSpec(c *C) { // connected plug to core slot spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.coreSlot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.coreSlot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "# Description: Can access common desktop legacy methods") c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "#include ") @@ -87,7 +89,7 @@ // connected plug to core slot spec = &apparmor.Specification{} - c.Assert(spec.AddConnectedSlot(s.iface, s.plug, nil, s.coreSlot, nil), IsNil) + c.Assert(spec.AddConnectedSlot(s.iface, s.plug, s.coreSlot), IsNil) c.Assert(spec.SecurityTags(), HasLen, 0) } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/desktop_test.go snapd-2.31.1+17.10/interfaces/builtin/desktop_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/desktop_test.go 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/desktop_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -36,9 +36,11 @@ ) type DesktopInterfaceSuite struct { - iface interfaces.Interface - coreSlot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + coreSlotInfo *snap.SlotInfo + coreSlot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&DesktopInterfaceSuite{ @@ -58,8 +60,8 @@ ` func (s *DesktopInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, desktopConsumerYaml, nil, "desktop") - s.coreSlot = MockSlot(c, desktopCoreYaml, nil, "desktop") + s.plug, s.plugInfo = MockConnectedPlug(c, desktopConsumerYaml, nil, "desktop") + s.coreSlot, s.coreSlotInfo = MockConnectedSlot(c, desktopCoreYaml, nil, "desktop") } func (s *DesktopInterfaceSuite) TearDownTest(c *C) { @@ -71,25 +73,26 @@ } func (s *DesktopInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.coreSlot.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.coreSlotInfo), IsNil) + // desktop slot currently only used with core - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "desktop", Interface: "desktop", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "desktop slots are reserved for the core snap") } func (s *DesktopInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *DesktopInterfaceSuite) TestAppArmorSpec(c *C) { // connected plug to core slot spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.coreSlot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.coreSlot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "# Description: Can access basic graphical desktop resources") c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "#include ") @@ -97,7 +100,7 @@ // connected plug to core slot spec = &apparmor.Specification{} - c.Assert(spec.AddConnectedSlot(s.iface, s.plug, nil, s.coreSlot, nil), IsNil) + c.Assert(spec.AddConnectedSlot(s.iface, s.plug, s.coreSlot), IsNil) c.Assert(spec.SecurityTags(), HasLen, 0) } @@ -113,7 +116,7 @@ // On all-snaps systems, no mount entries are added spec := &mount.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.coreSlot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.coreSlot), IsNil) c.Check(spec.MountEntries(), HasLen, 0) // On classic systems, a number of font related directories @@ -121,7 +124,7 @@ restore = release.MockOnClassic(true) defer restore() spec = &mount.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.coreSlot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.coreSlot), IsNil) entries := spec.MountEntries() c.Assert(entries, HasLen, 3) diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/docker.go snapd-2.31.1+17.10/interfaces/builtin/docker.go --- snapd-2.29.4.2+17.10/interfaces/builtin/docker.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/docker.go 2018-01-24 20:02:44.000000000 +0000 @@ -63,12 +63,12 @@ } } -func (iface *dockerInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *dockerInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { spec.AddSnippet(dockerConnectedPlugAppArmor) return nil } -func (iface *dockerInterface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *dockerInterface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { spec.AddSnippet(dockerConnectedPlugSecComp) return nil } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/docker_support.go snapd-2.31.1+17.10/interfaces/builtin/docker_support.go --- snapd-2.29.4.2+17.10/interfaces/builtin/docker_support.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/docker_support.go 2018-01-24 20:02:44.000000000 +0000 @@ -25,6 +25,7 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/seccomp" + "github.com/snapcore/snapd/snap" ) const dockerSupportSummary = `allows operating as the Docker daemon` @@ -552,8 +553,9 @@ } } -func (iface *dockerSupportInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - privileged, _ := plug.Attrs["privileged-containers"].(bool) +func (iface *dockerSupportInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { + var privileged bool + _ = plug.Attr("privileged-containers", &privileged) spec.AddSnippet(dockerSupportConnectedPlugAppArmor) if privileged { spec.AddSnippet(dockerSupportPrivilegedAppArmor) @@ -561,8 +563,9 @@ return nil } -func (iface *dockerSupportInterface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - privileged, _ := plug.Attrs["privileged-containers"].(bool) +func (iface *dockerSupportInterface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { + var privileged bool + _ = plug.Attr("privileged-containers", &privileged) snippet := dockerSupportConnectedPlugSecComp if privileged { snippet += dockerSupportPrivilegedSecComp @@ -571,7 +574,7 @@ return nil } -func (iface *dockerSupportInterface) SanitizePlug(plug *interfaces.Plug) error { +func (iface *dockerSupportInterface) BeforePreparePlug(plug *snap.PlugInfo) error { if v, ok := plug.Attrs["privileged-containers"]; ok { if _, ok = v.(bool); !ok { return fmt.Errorf("docker-support plug requires bool with 'privileged-containers'") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/docker_support_test.go snapd-2.31.1+17.10/interfaces/builtin/docker_support_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/docker_support_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/docker_support_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -32,9 +32,11 @@ ) type DockerSupportInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } const dockerSupportMockPlugSnapInfoYaml = `name: docker @@ -50,17 +52,17 @@ }) func (s *DockerSupportInterfaceSuite) SetUpTest(c *C) { - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "core", - Type: snap.TypeOS}, - Name: "docker-support", - Interface: "docker-support", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "core", + Type: snap.TypeOS}, + Name: "docker-support", + Interface: "docker-support", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, dockerSupportMockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["docker-support"]} + s.plugInfo = plugSnap.Plugs["docker-support"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *DockerSupportInterfaceSuite) TestName(c *C) { @@ -70,37 +72,37 @@ func (s *DockerSupportInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), HasLen, 1) // connected plugs have a non-nil security snippet for seccomp seccompSpec := &seccomp.Specification{} - err = seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err = seccompSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(seccompSpec.Snippets(), HasLen, 1) } func (s *DockerSupportInterfaceSuite) TestConnectedPlugSnippet(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.docker.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.docker.app"), testutil.Contains, `pivot_root`) seccompSpec := &seccomp.Specification{} - err = seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err = seccompSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.docker.app"}) c.Check(seccompSpec.SnippetForTag("snap.docker.app"), testutil.Contains, "pivot_root\n") } func (s *DockerSupportInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) } func (s *DockerSupportInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *DockerSupportInterfaceSuite) TestSanitizePlugWithPrivilegedTrue(c *C) { @@ -120,17 +122,17 @@ info, err := snap.InfoFromSnapYaml(mockSnapYaml) c.Assert(err, IsNil) - plug := &interfaces.Plug{PlugInfo: info.Plugs["privileged"]} - c.Assert(plug.Sanitize(s.iface), IsNil) + plug := info.Plugs["privileged"] + c.Assert(interfaces.BeforePreparePlug(s.iface, plug), IsNil) apparmorSpec := &apparmor.Specification{} - err = apparmorSpec.AddConnectedPlug(s.iface, plug, nil, s.slot, nil) + err = apparmorSpec.AddConnectedPlug(s.iface, interfaces.NewConnectedPlug(plug, nil), s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.docker.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.docker.app"), testutil.Contains, `change_profile -> *,`) seccompSpec := &seccomp.Specification{} - err = seccompSpec.AddConnectedPlug(s.iface, plug, nil, s.slot, nil) + err = seccompSpec.AddConnectedPlug(s.iface, interfaces.NewConnectedPlug(plug, nil), s.slot) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.docker.app"}) c.Check(seccompSpec.SnippetForTag("snap.docker.app"), testutil.Contains, "@unrestricted") @@ -153,17 +155,17 @@ info, err := snap.InfoFromSnapYaml(mockSnapYaml) c.Assert(err, IsNil) - plug := &interfaces.Plug{PlugInfo: info.Plugs["privileged"]} - c.Assert(plug.Sanitize(s.iface), IsNil) + plug := info.Plugs["privileged"] + c.Assert(interfaces.BeforePreparePlug(s.iface, plug), IsNil) apparmorSpec := &apparmor.Specification{} - err = apparmorSpec.AddConnectedPlug(s.iface, plug, nil, s.slot, nil) + err = apparmorSpec.AddConnectedPlug(s.iface, interfaces.NewConnectedPlug(plug, nil), s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.docker.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.docker.app"), Not(testutil.Contains), `change_profile -> *,`) seccompSpec := &seccomp.Specification{} - err = seccompSpec.AddConnectedPlug(s.iface, plug, nil, s.slot, nil) + err = seccompSpec.AddConnectedPlug(s.iface, interfaces.NewConnectedPlug(plug, nil), s.slot) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.docker.app"}) c.Check(seccompSpec.SnippetForTag("snap.docker.app"), Not(testutil.Contains), "@unrestricted") @@ -181,8 +183,8 @@ info, err := snap.InfoFromSnapYaml(mockSnapYaml) c.Assert(err, IsNil) - plug := &interfaces.Plug{PlugInfo: info.Plugs["privileged"]} - c.Assert(plug.Sanitize(s.iface), ErrorMatches, "docker-support plug requires bool with 'privileged-containers'") + plug := info.Plugs["privileged"] + c.Assert(interfaces.BeforePreparePlug(s.iface, plug), ErrorMatches, "docker-support plug requires bool with 'privileged-containers'") } func (s *DockerSupportInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/docker_test.go snapd-2.31.1+17.10/interfaces/builtin/docker_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/docker_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/docker_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -32,9 +32,11 @@ ) type DockerInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } const dockerMockPlugSnapInfoYaml = `name: docker @@ -50,17 +52,17 @@ }) func (s *DockerInterfaceSuite) SetUpTest(c *C) { - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "docker", - }, - Name: "docker-daemon", - Interface: "docker", + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "docker", }, + Name: "docker-daemon", + Interface: "docker", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, dockerMockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["docker"]} + s.plugInfo = plugSnap.Plugs["docker"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *DockerInterfaceSuite) TestName(c *C) { @@ -69,24 +71,24 @@ func (s *DockerInterfaceSuite) TestConnectedPlugSnippet(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.docker.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.docker.app"), testutil.Contains, `run/docker.sock`) seccompSpec := &seccomp.Specification{} - err = seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err = seccompSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.docker.app"}) c.Check(seccompSpec.SnippetForTag("snap.docker.app"), testutil.Contains, "bind\n") } func (s *DockerInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) } func (s *DockerInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *DockerInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/export_test.go snapd-2.31.1+17.10/interfaces/builtin/export_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/export_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/export_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -61,18 +61,34 @@ panic(fmt.Errorf("cannot find interface with name %q", name)) } -func MockPlug(c *C, yaml string, si *snap.SideInfo, plugName string) *interfaces.Plug { +func MockPlug(c *C, yaml string, si *snap.SideInfo, plugName string) *snap.PlugInfo { info := snaptest.MockInfo(c, yaml, si) if plugInfo, ok := info.Plugs[plugName]; ok { - return &interfaces.Plug{PlugInfo: plugInfo} + return plugInfo } panic(fmt.Sprintf("cannot find plug %q in snap %q", plugName, info.Name())) } -func MockSlot(c *C, yaml string, si *snap.SideInfo, slotName string) *interfaces.Slot { +func MockSlot(c *C, yaml string, si *snap.SideInfo, slotName string) *snap.SlotInfo { info := snaptest.MockInfo(c, yaml, si) if slotInfo, ok := info.Slots[slotName]; ok { - return &interfaces.Slot{SlotInfo: slotInfo} + return slotInfo + } + panic(fmt.Sprintf("cannot find slot %q in snap %q", slotName, info.Name())) +} + +func MockConnectedPlug(c *C, yaml string, si *snap.SideInfo, plugName string) (*interfaces.ConnectedPlug, *snap.PlugInfo) { + info := snaptest.MockInfo(c, yaml, si) + if plugInfo, ok := info.Plugs[plugName]; ok { + return interfaces.NewConnectedPlug(plugInfo, nil), plugInfo + } + panic(fmt.Sprintf("cannot find plug %q in snap %q", plugName, info.Name())) +} + +func MockConnectedSlot(c *C, yaml string, si *snap.SideInfo, slotName string) (*interfaces.ConnectedSlot, *snap.SlotInfo) { + info := snaptest.MockInfo(c, yaml, si) + if slotInfo, ok := info.Slots[slotName]; ok { + return interfaces.NewConnectedSlot(slotInfo, nil), slotInfo } panic(fmt.Sprintf("cannot find slot %q in snap %q", slotName, info.Name())) } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/firewall_control_test.go snapd-2.31.1+17.10/interfaces/builtin/firewall_control_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/firewall_control_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/firewall_control_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -32,9 +32,11 @@ ) type FirewallControlInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } const firewallControlConsumerYaml = `name: consumer @@ -54,8 +56,8 @@ }) func (s *FirewallControlInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, firewallControlConsumerYaml, nil, "firewall-control") - s.slot = MockSlot(c, firewallControlCoreYaml, nil, "firewall-control") + s.plug, s.plugInfo = MockConnectedPlug(c, firewallControlConsumerYaml, nil, "firewall-control") + s.slot, s.slotInfo = MockConnectedSlot(c, firewallControlCoreYaml, nil, "firewall-control") } func (s *FirewallControlInterfaceSuite) TestName(c *C) { @@ -63,37 +65,37 @@ } func (s *FirewallControlInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "firewall-control", Interface: "firewall-control", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "firewall-control slots are reserved for the core snap") } func (s *FirewallControlInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *FirewallControlInterfaceSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, `capability net_raw`) } func (s *FirewallControlInterfaceSuite) TestSecCompSpec(c *C) { spec := &seccomp.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, "bind\n") } func (s *FirewallControlInterfaceSuite) TestKModSpec(c *C) { spec := &kmod.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) c.Assert(spec.Modules(), DeepEquals, map[string]bool{ "arp_tables": true, "br_netfilter": true, @@ -111,7 +113,8 @@ } func (s *FirewallControlInterfaceSuite) TestAutoConnect(c *C) { - c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) + // FIXME: fix AutoConnect methods to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) } func (s *FirewallControlInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/framebuffer_test.go snapd-2.31.1+17.10/interfaces/builtin/framebuffer_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/framebuffer_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/framebuffer_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type FramebufferInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } const framebufferConsumerYaml = ` @@ -55,8 +57,8 @@ }) func (s *FramebufferInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, framebufferConsumerYaml, nil, "framebuffer") - s.slot = MockSlot(c, framebufferOsYaml, nil, "framebuffer") + s.plug, s.plugInfo = MockConnectedPlug(c, framebufferConsumerYaml, nil, "framebuffer") + s.slot, s.slotInfo = MockConnectedSlot(c, framebufferOsYaml, nil, "framebuffer") } func (s *FramebufferInterfaceSuite) TestName(c *C) { @@ -64,32 +66,34 @@ } func (s *FramebufferInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "framebuffer", Interface: "framebuffer", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "framebuffer slots are reserved for the core snap") } func (s *FramebufferInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *FramebufferInterfaceSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, `/dev/fb[0-9]* rw,`) } func (s *FramebufferInterfaceSuite) TestUDevSpec(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 1) - c.Assert(spec.Snippets()[0], Equals, `KERNEL=="fb[0-9]*", TAG+="snap_consumer_app"`) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Snippets(), HasLen, 2) + c.Assert(spec.Snippets()[0], Equals, `# framebuffer +KERNEL=="fb[0-9]*", TAG+="snap_consumer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_consumer_app", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_consumer_app $devpath $major:$minor"`) } func (s *FramebufferInterfaceSuite) TestStaticInfo(c *C) { @@ -101,7 +105,8 @@ } func (s *FramebufferInterfaceSuite) TestAutoConnect(c *C) { - c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) + // FIXME: fix AutoConnect methods to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) } func (s *FramebufferInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/fuse_support_test.go snapd-2.31.1+17.10/interfaces/builtin/fuse_support_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/fuse_support_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/fuse_support_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -33,9 +33,11 @@ ) type FuseSupportInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } const fuseSupportConsumerYaml = `name: consumer @@ -55,8 +57,8 @@ }) func (s *FuseSupportInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, fuseSupportConsumerYaml, nil, "fuse-support") - s.slot = MockSlot(c, fuseSupportCoreYaml, nil, "fuse-support") + s.plug, s.plugInfo = MockConnectedPlug(c, fuseSupportConsumerYaml, nil, "fuse-support") + s.slot, s.slotInfo = MockConnectedSlot(c, fuseSupportCoreYaml, nil, "fuse-support") } func (s *FuseSupportInterfaceSuite) TestName(c *C) { @@ -64,39 +66,41 @@ } func (s *FuseSupportInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "fuse-support", Interface: "fuse-support", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "fuse-support slots are reserved for the core snap") } func (s *FuseSupportInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *FuseSupportInterfaceSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, `/dev/fuse`) } func (s *FuseSupportInterfaceSuite) TestSecCompSpec(c *C) { spec := &seccomp.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, "mount\n") } func (s *FuseSupportInterfaceSuite) TestUDevSpec(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 1) - c.Assert(spec.Snippets()[0], testutil.Contains, `KERNEL=="fuse", TAG+="snap_consumer_app"`) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Snippets(), HasLen, 2) + c.Assert(spec.Snippets(), testutil.Contains, `# fuse-support +KERNEL=="fuse", TAG+="snap_consumer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_consumer_app", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_consumer_app $devpath $major:$minor"`) } func (s *FuseSupportInterfaceSuite) TestStaticInfo(c *C) { @@ -108,7 +112,8 @@ } func (s *FuseSupportInterfaceSuite) TestAutoConnect(c *C) { - c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) + // FIXME: fix AutoConnect methods to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) } func (s *FuseSupportInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/fwupd.go snapd-2.31.1+17.10/interfaces/builtin/fwupd.go --- snapd-2.29.4.2+17.10/interfaces/builtin/fwupd.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/fwupd.go 2018-01-24 20:02:44.000000000 +0000 @@ -26,6 +26,7 @@ "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/dbus" "github.com/snapcore/snapd/interfaces/seccomp" + "github.com/snapcore/snapd/snap" ) const fwupdSummary = `allows operating as the fwupd service` @@ -226,12 +227,12 @@ } } -func (iface *fwupdInterface) DBusPermanentSlot(spec *dbus.Specification, slot *interfaces.Slot) error { +func (iface *fwupdInterface) DBusPermanentSlot(spec *dbus.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(fwupdPermanentSlotDBus) return nil } -func (iface *fwupdInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *fwupdInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###SLOT_SECURITY_TAGS###" new := slotAppLabelExpr(slot) snippet := strings.Replace(fwupdConnectedPlugAppArmor, old, new, -1) @@ -239,13 +240,13 @@ return nil } -func (iface *fwupdInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { +func (iface *fwupdInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(fwupdPermanentSlotAppArmor) return nil } -func (iface *fwupdInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *fwupdInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###PLUG_SECURITY_TAGS###" new := plugAppLabelExpr(plug) snippet := strings.Replace(fwupdConnectedSlotAppArmor, old, new, -1) @@ -253,12 +254,12 @@ return nil } -func (iface *fwupdInterface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *fwupdInterface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { spec.AddSnippet(fwupdConnectedPlugSecComp) return nil } -func (iface *fwupdInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *interfaces.Slot) error { +func (iface *fwupdInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(fwupdPermanentSlotSecComp) return nil } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/fwupd_test.go snapd-2.31.1+17.10/interfaces/builtin/fwupd_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/fwupd_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/fwupd_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -33,9 +33,11 @@ ) type FwupdInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } const mockPlugSnapInfoYaml = `name: uefi-fw-tools @@ -61,8 +63,10 @@ func (s *FwupdInterfaceSuite) SetUpTest(c *C) { slotSnap := snaptest.MockInfo(c, mockSlotSnapInfoYaml, nil) plugSnap := snaptest.MockInfo(c, mockPlugSnapInfoYaml, nil) - s.slot = &interfaces.Slot{SlotInfo: slotSnap.Slots["fwupd"]} - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["fwupd"]} + s.slotInfo = slotSnap.Slots["fwupd"] + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) + s.plugInfo = plugSnap.Plugs["fwupd"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *FwupdInterfaceSuite) TestName(c *C) { @@ -73,21 +77,19 @@ func (s *FwupdInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelAll(c *C) { app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "uefi-fw-tools", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - Name: "fwupd", - Interface: "fwupd", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + slot := &snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "uefi-fw-tools", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, }, + Name: "fwupd", + Interface: "fwupd", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, } // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, interfaces.NewConnectedSlot(slot, nil)) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.uefi-fw-tools.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.uefi-fw-tools.app"), testutil.Contains, `peer=(label="snap.uefi-fw-tools.*"),`) @@ -98,20 +100,18 @@ app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} app3 := &snap.AppInfo{Name: "app3"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "uefi-fw-tools", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, - }, - Name: "fwupd", - Interface: "fwupd", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + slot := &snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "uefi-fw-tools", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, }, + Name: "fwupd", + Interface: "fwupd", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, } apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, interfaces.NewConnectedSlot(slot, nil)) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.uefi-fw-tools.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.uefi-fw-tools.app"), testutil.Contains, `peer=(label="snap.uefi-fw-tools.{app1,app2}"),`) @@ -120,7 +120,7 @@ // The label uses short form when exactly one app is bound to the fwupd slot func (s *FwupdInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelOne(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.uefi-fw-tools.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.uefi-fw-tools.app"), testutil.Contains, `peer=(label="snap.uefi-fw-tools.app2"),`) @@ -129,23 +129,23 @@ func (s *FwupdInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) - err = apparmorSpec.AddConnectedSlot(s.iface, s.plug, nil, s.slot, nil) + err = apparmorSpec.AddConnectedSlot(s.iface, s.plug, s.slot) c.Assert(err, IsNil) - err = apparmorSpec.AddPermanentSlot(s.iface, s.slot) + err = apparmorSpec.AddPermanentSlot(s.iface, s.slotInfo) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.uefi-fw-tools.app", "snap.uefi-fw-tools.app2"}) dbusSpec := &dbus.Specification{} - err = dbusSpec.AddPermanentSlot(s.iface, s.slot) + err = dbusSpec.AddPermanentSlot(s.iface, s.slotInfo) c.Assert(err, IsNil) c.Assert(dbusSpec.SecurityTags(), HasLen, 1) } func (s *FwupdInterfaceSuite) TestPermanentSlotSnippetSecComp(c *C) { seccompSpec := &seccomp.Specification{} - err := seccompSpec.AddPermanentSlot(s.iface, s.slot) + err := seccompSpec.AddPermanentSlot(s.iface, s.slotInfo) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.uefi-fw-tools.app2"}) c.Check(seccompSpec.SnippetForTag("snap.uefi-fw-tools.app2"), testutil.Contains, "bind\n") @@ -153,7 +153,7 @@ func (s *FwupdInterfaceSuite) TestPermanentSlotDBus(c *C) { dbusSpec := &dbus.Specification{} - err := dbusSpec.AddPermanentSlot(s.iface, s.slot) + err := dbusSpec.AddPermanentSlot(s.iface, s.slotInfo) c.Assert(err, IsNil) c.Assert(dbusSpec.SecurityTags(), DeepEquals, []string{"snap.uefi-fw-tools.app2"}) c.Assert(dbusSpec.SnippetForTag("snap.uefi-fw-tools.app2"), testutil.Contains, ``) @@ -161,7 +161,7 @@ func (s *FwupdInterfaceSuite) TestConnectedPlugSnippetSecComp(c *C) { seccompSpec := &seccomp.Specification{} - err := seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := seccompSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.uefi-fw-tools.app"}) c.Check(seccompSpec.SnippetForTag("snap.uefi-fw-tools.app"), testutil.Contains, "bind\n") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/gpg_keys.go snapd-2.31.1+17.10/interfaces/builtin/gpg_keys.go --- snapd-2.29.4.2+17.10/interfaces/builtin/gpg_keys.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/gpg_keys.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,59 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package builtin + +const gpgKeysSummary = `allows reading gpg user configuration and keys` + +const gpgKeysBaseDeclarationSlots = ` + gpg-keys: + allow-installation: + slot-snap-type: + - core + deny-auto-connection: true +` + +const gpgKeysConnectedPlugAppArmor = ` +# Description: Can read gpg user configuration as well as public and private +# keys. + +# Allow gpg encrypt, decrypt, list-keys, verify, sign, etc +/usr/bin/gpg{,1,2,v} ixr, +/usr/share/gnupg/options.skel r, + +owner @{HOME}/.gnupg/{,**} r, +# gpg sometimes updates the trustdb to decide whether or not to update the +# trustdb. For now, silence the denial since no other policy references this +deny @{HOME}/.gnupg/trustdb.gpg w, + +# 'wk' is required for gpg encrypt and sign +owner @{HOME}/.gnupg/random_seed wk, +` + +func init() { + registerIface(&commonInterface{ + name: "gpg-keys", + summary: gpgKeysSummary, + implicitOnCore: true, + implicitOnClassic: true, + baseDeclarationSlots: gpgKeysBaseDeclarationSlots, + connectedPlugAppArmor: gpgKeysConnectedPlugAppArmor, + reservedForOS: true, + }) +} diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/gpg_keys_test.go snapd-2.31.1+17.10/interfaces/builtin/gpg_keys_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/gpg_keys_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/gpg_keys_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,102 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +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/snap" + "github.com/snapcore/snapd/testutil" +) + +type GpgKeysInterfaceSuite struct { + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug +} + +var _ = Suite(&GpgKeysInterfaceSuite{ + iface: builtin.MustInterface("gpg-keys"), +}) + +const gpgKeysConsumerYaml = `name: consumer +apps: + app: + plugs: [gpg-keys] + ` + +const gpgKeysCoreYaml = `name: core +type: os +slots: + gpg-keys: +` + +func (s *GpgKeysInterfaceSuite) SetUpTest(c *C) { + s.plug, s.plugInfo = MockConnectedPlug(c, gpgKeysConsumerYaml, nil, "gpg-keys") + s.slot, s.slotInfo = MockConnectedSlot(c, gpgKeysCoreYaml, nil, "gpg-keys") +} + +func (s *GpgKeysInterfaceSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "gpg-keys") +} + +func (s *GpgKeysInterfaceSuite) TestSanitizeSlot(c *C) { + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slotInfo := &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "some-snap"}, + Name: "gpg-keys", + Interface: "gpg-keys", + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slotInfo), ErrorMatches, + "gpg-keys slots are reserved for the core snap") +} + +func (s *GpgKeysInterfaceSuite) TestSanitizePlug(c *C) { + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) +} + +func (s *GpgKeysInterfaceSuite) 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, `owner @{HOME}/.gnupg/{,**} r,`) +} + +func (s *GpgKeysInterfaceSuite) 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 reading gpg user configuration and keys`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "gpg-keys") +} + +func (s *GpgKeysInterfaceSuite) TestAutoConnect(c *C) { + // FIXME: fix AutoConnect + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) +} + +func (s *GpgKeysInterfaceSuite) TestInterfaces(c *C) { + c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) +} diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/gpg_public_keys.go snapd-2.31.1+17.10/interfaces/builtin/gpg_public_keys.go --- snapd-2.29.4.2+17.10/interfaces/builtin/gpg_public_keys.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/gpg_public_keys.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,62 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package builtin + +const gpgPublicKeysSummary = `allows reading gpg public keys and non-sensitive configuration` + +const gpgPublicKeysBaseDeclarationSlots = ` + gpg-public-keys: + allow-installation: + slot-snap-type: + - core + deny-auto-connection: true +` + +const gpgPublicKeysConnectedPlugAppArmor = ` +# Description: Can read gpg public keys and non-sensitive configuration + +# Allow gpg encrypt, list-keys, verify, etc +/usr/bin/gpg{,1,2,v} ixr, +/usr/share/gnupg/options.skel r, + +owner @{HOME}/.gnupg/ r, +owner @{HOME}/.gnupg/gpg.conf r, +owner @{HOME}/.gnupg/openpgp-revocs.d/{,*} r, +owner @{HOME}/.gnupg/pubring.gpg r, +owner @{HOME}/.gnupg/pubring.kbx r, +owner @{HOME}/.gnupg/trustedkeys.gpg r, + +owner @{HOME}/.gnupg/trustdb.gpg r, +# gpg sometimes updates the trustdb to decide whether or not to update the +# trustdb. For now, silence the denial since no other policy references this +deny @{HOME}/.gnupg/trustdb.gpg w, +` + +func init() { + registerIface(&commonInterface{ + name: "gpg-public-keys", + summary: gpgPublicKeysSummary, + implicitOnCore: true, + implicitOnClassic: true, + baseDeclarationSlots: gpgPublicKeysBaseDeclarationSlots, + connectedPlugAppArmor: gpgPublicKeysConnectedPlugAppArmor, + reservedForOS: true, + }) +} diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/gpg_public_keys_test.go snapd-2.31.1+17.10/interfaces/builtin/gpg_public_keys_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/gpg_public_keys_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/gpg_public_keys_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,102 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +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/snap" + "github.com/snapcore/snapd/testutil" +) + +type GpgPublicKeysInterfaceSuite struct { + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug +} + +var _ = Suite(&GpgPublicKeysInterfaceSuite{ + iface: builtin.MustInterface("gpg-public-keys"), +}) + +const gpgPublicKeysConsumerYaml = `name: consumer +apps: + app: + plugs: [gpg-public-keys] + ` + +const gpgPublicKeysCoreYaml = `name: core +type: os +slots: + gpg-public-keys: +` + +func (s *GpgPublicKeysInterfaceSuite) SetUpTest(c *C) { + s.plug, s.plugInfo = MockConnectedPlug(c, gpgPublicKeysConsumerYaml, nil, "gpg-public-keys") + s.slot, s.slotInfo = MockConnectedSlot(c, gpgPublicKeysCoreYaml, nil, "gpg-public-keys") +} + +func (s *GpgPublicKeysInterfaceSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "gpg-public-keys") +} + +func (s *GpgPublicKeysInterfaceSuite) TestSanitizeSlot(c *C) { + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slotInfo := &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "some-snap"}, + Name: "gpg-public-keys", + Interface: "gpg-public-keys", + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slotInfo), ErrorMatches, + "gpg-public-keys slots are reserved for the core snap") +} + +func (s *GpgPublicKeysInterfaceSuite) TestSanitizePlug(c *C) { + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) +} + +func (s *GpgPublicKeysInterfaceSuite) 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, `owner @{HOME}/.gnupg/gpg.conf r,`) +} + +func (s *GpgPublicKeysInterfaceSuite) 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 reading gpg public keys and non-sensitive configuration`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "gpg-public-keys") +} + +func (s *GpgPublicKeysInterfaceSuite) TestAutoConnect(c *C) { + // FIXME: fix AutoConnect + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) +} + +func (s *GpgPublicKeysInterfaceSuite) TestInterfaces(c *C) { + c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) +} diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/gpio.go snapd-2.31.1+17.10/interfaces/builtin/gpio.go --- snapd-2.29.4.2+17.10/interfaces/builtin/gpio.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/gpio.go 2018-01-24 20:02:44.000000000 +0000 @@ -25,6 +25,7 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/systemd" + "github.com/snapcore/snapd/snap" ) const gpioSummary = `allows access to specifc GPIO pin` @@ -61,8 +62,8 @@ } } -// SanitizeSlot checks the slot definition is valid -func (iface *gpioInterface) SanitizeSlot(slot *interfaces.Slot) error { +// BeforePrepareSlot checks the slot definition is valid +func (iface *gpioInterface) BeforePrepareSlot(slot *snap.SlotInfo) error { if err := sanitizeSlotReservedForOSOrGadget(iface, slot); err != nil { return err } @@ -82,8 +83,12 @@ return nil } -func (iface *gpioInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - path := fmt.Sprint(gpioSysfsGpioBase, slot.Attrs["number"]) +func (iface *gpioInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { + var number int64 + if err := slot.Attr("number", &number); err != nil { + return err + } + path := fmt.Sprint(gpioSysfsGpioBase, number) // Entries in /sys/class/gpio for single GPIO's are just symlinks // to their correct device part in the sysfs tree. Given AppArmor // requires symlinks to be dereferenced, evaluate the GPIO @@ -97,12 +102,13 @@ } -func (iface *gpioInterface) SystemdConnectedSlot(spec *systemd.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - gpioNum, ok := slot.Attrs["number"].(int64) - if !ok { - return fmt.Errorf("gpio slot has invalid number attribute: %q", slot.Attrs["number"]) +func (iface *gpioInterface) SystemdConnectedSlot(spec *systemd.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { + var gpioNum int64 + if err := slot.Attr("number", &gpioNum); err != nil { + return err } - serviceName := interfaces.InterfaceServiceName(slot.Snap.Name(), fmt.Sprintf("gpio-%d", gpioNum)) + + serviceName := interfaces.InterfaceServiceName(slot.Snap().Name(), fmt.Sprintf("gpio-%d", gpioNum)) service := &systemd.Service{ Type: "oneshot", RemainAfterExit: true, diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/gpio_memory_control.go snapd-2.31.1+17.10/interfaces/builtin/gpio_memory_control.go --- snapd-2.29.4.2+17.10/interfaces/builtin/gpio_memory_control.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/gpio_memory_control.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,54 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package builtin + +// https://github.com/raspberrypi/linux/blob/rpi-4.4.y/drivers/char/broadcom/bcm2835-gpiomem.c +const gpioMemoryControlSummary = `allows write access to all gpio memory` + +const gpioMemoryControlBaseDeclarationSlots = ` + gpio-memory-control: + allow-installation: + slot-snap-type: + - core + deny-auto-connection: true +` + +const gpioMemoryControlConnectedPlugAppArmor = ` +# Description: Allow writing to /dev/gpiomem on kernels that provide it (eg, +# via the bcm2835-gpiomem kernel module). This allows direct access to the +# physical memory for GPIO devices (i.e. a subset of /dev/mem) and therefore +# grants access to all GPIO devices on the system. +/dev/gpiomem rw, +` + +var gpioMemoryControlConnectedPlugUDev = []string{`KERNEL=="gpiomem"`} + +func init() { + registerIface(&commonInterface{ + name: "gpio-memory-control", + summary: gpioMemoryControlSummary, + implicitOnCore: true, + implicitOnClassic: true, + baseDeclarationSlots: gpioMemoryControlBaseDeclarationSlots, + connectedPlugAppArmor: gpioMemoryControlConnectedPlugAppArmor, + connectedPlugUDev: gpioMemoryControlConnectedPlugUDev, + reservedForOS: true, + }) +} diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/gpio_memory_control_test.go snapd-2.31.1+17.10/interfaces/builtin/gpio_memory_control_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/gpio_memory_control_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/gpio_memory_control_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,111 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +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 GpioMemoryControlInterfaceSuite struct { + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug +} + +var _ = Suite(&GpioMemoryControlInterfaceSuite{ + iface: builtin.MustInterface("gpio-memory-control"), +}) + +const gpioMemoryControlConsumerYaml = `name: consumer +apps: + app: + plugs: [gpio-memory-control] +` + +const gpioMemoryControlCoreYaml = `name: core +type: os +slots: + gpio-memory-control: +` + +func (s *GpioMemoryControlInterfaceSuite) SetUpTest(c *C) { + s.plug, s.plugInfo = MockConnectedPlug(c, gpioMemoryControlConsumerYaml, nil, "gpio-memory-control") + s.slot, s.slotInfo = MockConnectedSlot(c, gpioMemoryControlCoreYaml, nil, "gpio-memory-control") +} + +func (s *GpioMemoryControlInterfaceSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "gpio-memory-control") +} + +func (s *GpioMemoryControlInterfaceSuite) TestSanitizeSlot(c *C) { + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slotInfo := &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "some-snap"}, + Name: "gpio-memory-control", + Interface: "gpio-memory-control", + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slotInfo), ErrorMatches, + "gpio-memory-control slots are reserved for the core snap") +} + +func (s *GpioMemoryControlInterfaceSuite) TestSanitizePlug(c *C) { + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) +} + +func (s *GpioMemoryControlInterfaceSuite) 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, `/dev/gpiomem rw,`) +} + +func (s *GpioMemoryControlInterfaceSuite) TestUDevSpec(c *C) { + spec := &udev.Specification{} + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Snippets(), HasLen, 2) + c.Assert(spec.Snippets(), testutil.Contains, `# gpio-memory-control +KERNEL=="gpiomem", TAG+="snap_consumer_app"`) +} + +func (s *GpioMemoryControlInterfaceSuite) 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 write access to all gpio memory`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "gpio-memory-control") +} + +func (s *GpioMemoryControlInterfaceSuite) TestAutoConnect(c *C) { + // FIXME: fix AutoConnect to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) +} + +func (s *GpioMemoryControlInterfaceSuite) TestInterfaces(c *C) { + c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) +} diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/gpio_test.go snapd-2.31.1+17.10/interfaces/builtin/gpio_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/gpio_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/gpio_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -25,20 +25,29 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/builtin" "github.com/snapcore/snapd/interfaces/systemd" + "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap/snaptest" "github.com/snapcore/snapd/testutil" ) type GpioInterfaceSuite struct { - iface interfaces.Interface - gadgetGpioSlot *interfaces.Slot - gadgetMissingNumberSlot *interfaces.Slot - gadgetBadNumberSlot *interfaces.Slot - gadgetBadInterfaceSlot *interfaces.Slot - gadgetPlug *interfaces.Plug - gadgetBadInterfacePlug *interfaces.Plug - osGpioSlot *interfaces.Slot - appGpioSlot *interfaces.Slot + iface interfaces.Interface + gadgetGpioSlotInfo *snap.SlotInfo + gadgetGpioSlot *interfaces.ConnectedSlot + gadgetMissingNumberSlotInfo *snap.SlotInfo + gadgetMissingNumberSlot *interfaces.ConnectedSlot + gadgetBadNumberSlotInfo *snap.SlotInfo + gadgetBadNumberSlot *interfaces.ConnectedSlot + gadgetBadInterfaceSlotInfo *snap.SlotInfo + gadgetBadInterfaceSlot *interfaces.ConnectedSlot + gadgetPlugInfo *snap.PlugInfo + gadgetPlug *interfaces.ConnectedPlug + gadgetBadInterfacePlugInfo *snap.PlugInfo + gadgetBadInterfacePlug *interfaces.ConnectedPlug + osGpioSlotInfo *snap.SlotInfo + osGpioSlot *interfaces.ConnectedSlot + appGpioSlotInfo *snap.SlotInfo + appGpioSlot *interfaces.ConnectedSlot } var _ = Suite(&GpioInterfaceSuite{ @@ -63,12 +72,18 @@ plug: gpio bad-interface-plug: other-interface `, nil) - s.gadgetGpioSlot = &interfaces.Slot{SlotInfo: gadgetInfo.Slots["my-pin"]} - s.gadgetMissingNumberSlot = &interfaces.Slot{SlotInfo: gadgetInfo.Slots["missing-number"]} - s.gadgetBadNumberSlot = &interfaces.Slot{SlotInfo: gadgetInfo.Slots["bad-number"]} - s.gadgetBadInterfaceSlot = &interfaces.Slot{SlotInfo: gadgetInfo.Slots["bad-interface-slot"]} - s.gadgetPlug = &interfaces.Plug{PlugInfo: gadgetInfo.Plugs["plug"]} - s.gadgetBadInterfacePlug = &interfaces.Plug{PlugInfo: gadgetInfo.Plugs["bad-interface-plug"]} + s.gadgetGpioSlotInfo = gadgetInfo.Slots["my-pin"] + s.gadgetGpioSlot = interfaces.NewConnectedSlot(s.gadgetGpioSlotInfo, nil) + s.gadgetMissingNumberSlotInfo = gadgetInfo.Slots["missing-number"] + s.gadgetMissingNumberSlot = interfaces.NewConnectedSlot(s.gadgetMissingNumberSlotInfo, nil) + s.gadgetBadNumberSlotInfo = gadgetInfo.Slots["bad-number"] + s.gadgetBadNumberSlot = interfaces.NewConnectedSlot(s.gadgetBadNumberSlotInfo, nil) + s.gadgetBadInterfaceSlotInfo = gadgetInfo.Slots["bad-interface-slot"] + s.gadgetBadInterfaceSlot = interfaces.NewConnectedSlot(s.gadgetBadInterfaceSlotInfo, nil) + s.gadgetPlugInfo = gadgetInfo.Plugs["plug"] + s.gadgetPlug = interfaces.NewConnectedPlug(s.gadgetPlugInfo, nil) + s.gadgetBadInterfacePlugInfo = gadgetInfo.Plugs["bad-interface-plug"] + s.gadgetBadInterfacePlug = interfaces.NewConnectedPlug(s.gadgetBadInterfacePlugInfo, nil) osInfo := snaptest.MockInfo(c, ` name: my-core @@ -79,7 +94,8 @@ number: 777 direction: out `, nil) - s.osGpioSlot = &interfaces.Slot{SlotInfo: osInfo.Slots["my-pin"]} + s.osGpioSlotInfo = osInfo.Slots["my-pin"] + s.osGpioSlot = interfaces.NewConnectedSlot(s.osGpioSlotInfo, nil) appInfo := snaptest.MockInfo(c, ` name: my-app @@ -89,7 +105,8 @@ number: 154 direction: out `, nil) - s.appGpioSlot = &interfaces.Slot{SlotInfo: appInfo.Slots["my-pin"]} + s.appGpioSlotInfo = appInfo.Slots["my-pin"] + s.appGpioSlot = interfaces.NewConnectedSlot(s.appGpioSlotInfo, nil) } func (s *GpioInterfaceSuite) TestName(c *C) { @@ -98,35 +115,35 @@ func (s *GpioInterfaceSuite) TestSanitizeSlotGadgetSnap(c *C) { // gpio slot on gadget accepeted - c.Assert(s.gadgetGpioSlot.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.gadgetGpioSlotInfo), IsNil) // slots without number attribute are rejected - c.Assert(s.gadgetMissingNumberSlot.Sanitize(s.iface), ErrorMatches, + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.gadgetMissingNumberSlotInfo), ErrorMatches, "gpio slot must have a number attribute") // slots with number attribute that isnt a number - c.Assert(s.gadgetBadNumberSlot.Sanitize(s.iface), ErrorMatches, + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.gadgetBadNumberSlotInfo), ErrorMatches, "gpio slot number attribute must be an int") } func (s *GpioInterfaceSuite) TestSanitizeSlotOsSnap(c *C) { // gpio slot on OS accepeted - c.Assert(s.osGpioSlot.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.osGpioSlotInfo), IsNil) } func (s *GpioInterfaceSuite) TestSanitizeSlotAppSnap(c *C) { // gpio slot not accepted on app snap - c.Assert(s.appGpioSlot.Sanitize(s.iface), ErrorMatches, + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.appGpioSlotInfo), ErrorMatches, "gpio slots are reserved for the core and gadget snaps") } func (s *GpioInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.gadgetPlug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.gadgetPlugInfo), IsNil) } func (s *GpioInterfaceSuite) TestSystemdConnectedSlot(c *C) { spec := &systemd.Specification{} - err := spec.AddConnectedSlot(s.iface, s.gadgetPlug, nil, s.gadgetGpioSlot, nil) + err := spec.AddConnectedSlot(s.iface, s.gadgetPlug, s.gadgetGpioSlot) c.Assert(err, IsNil) c.Assert(spec.Services(), DeepEquals, map[string]*systemd.Service{ "snap.my-device.interface.gpio-100.service": { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/greengrass_support_test.go snapd-2.31.1+17.10/interfaces/builtin/greengrass_support_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/greengrass_support_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/greengrass_support_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -32,9 +32,11 @@ ) type GreengrassSupportInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } const ggMockPlugSnapInfoYaml = `name: other @@ -50,15 +52,15 @@ }) func (s *GreengrassSupportInterfaceSuite) SetUpTest(c *C) { - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "greengrass-support", - Interface: "greengrass-support", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "greengrass-support", + Interface: "greengrass-support", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, ggMockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["greengrass-support"]} + s.plugInfo = plugSnap.Plugs["greengrass-support"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *GreengrassSupportInterfaceSuite) TestName(c *C) { @@ -66,28 +68,28 @@ } func (s *GreengrassSupportInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "greengrass-support", Interface: "greengrass-support", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "greengrass-support slots are reserved for the core snap") } func (s *GreengrassSupportInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *GreengrassSupportInterfaceSuite) TestUsedSecuritySystems(c *C) { seccompSpec := &seccomp.Specification{} - err := seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := seccompSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Check(seccompSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "# for overlayfs and various bind mounts\nmount\numount2\npivot_root\n") apparmorSpec := &apparmor.Specification{} - err = apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err = apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) c.Check(apparmorSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "mount fstype=overlay no_source -> /var/snap/@{SNAP_NAME}/**,\n") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/gsettings_test.go snapd-2.31.1+17.10/interfaces/builtin/gsettings_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/gsettings_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/gsettings_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -32,9 +32,11 @@ ) type GsettingsInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } const gsettingsMockPlugSnapInfoYaml = `name: other @@ -50,15 +52,15 @@ }) func (s *GsettingsInterfaceSuite) SetUpTest(c *C) { - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "gsettings", - Interface: "gsettings", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "gsettings", + Interface: "gsettings", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, gsettingsMockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["gsettings"]} + s.plugInfo = plugSnap.Plugs["gsettings"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *GsettingsInterfaceSuite) TestName(c *C) { @@ -66,24 +68,24 @@ } func (s *GsettingsInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "gsettings", Interface: "gsettings", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "gsettings slots are reserved for the core snap") } func (s *GsettingsInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *GsettingsInterfaceSuite) TestConnectedPlugSnippet(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app2"), testutil.Contains, `dconf.Writer`) @@ -92,13 +94,13 @@ func (s *GsettingsInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), HasLen, 1) // connected plugs have nil security snippet for seccomp seccompSpec := &seccomp.Specification{} - err = seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err = seccompSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) snippets := seccompSpec.Snippets() c.Assert(len(snippets), Equals, 0) diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/hardware_observe_test.go snapd-2.31.1+17.10/interfaces/builtin/hardware_observe_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/hardware_observe_test.go 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/hardware_observe_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -32,9 +32,11 @@ ) type HardwareObserveInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } const hwobserveMockPlugSnapInfoYaml = `name: other @@ -50,15 +52,15 @@ }) func (s *HardwareObserveInterfaceSuite) SetUpTest(c *C) { - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "hardware-observe", - Interface: "hardware-observe", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "hardware-observe", + Interface: "hardware-observe", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, hwobserveMockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["hardware-observe"]} + s.plugInfo = plugSnap.Plugs["hardware-observe"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *HardwareObserveInterfaceSuite) TestName(c *C) { @@ -66,24 +68,24 @@ } func (s *HardwareObserveInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "hardware-observe", Interface: "hardware-observe", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "hardware-observe slots are reserved for the core snap") } func (s *HardwareObserveInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *HardwareObserveInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "capability sys_rawio,\n") @@ -91,7 +93,7 @@ // connected plugs have a non-nil security snippet for seccomp seccompSpec := &seccomp.Specification{} - err = seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err = seccompSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) c.Check(seccompSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "iopl\n") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/hardware_random_control_test.go snapd-2.31.1+17.10/interfaces/builtin/hardware_random_control_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/hardware_random_control_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/hardware_random_control_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type HardwareRandomControlInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&HardwareRandomControlInterfaceSuite{ @@ -53,8 +55,8 @@ ` func (s *HardwareRandomControlInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, hardwareRandomControlConsumerYaml, nil, "hardware-random-control") - s.slot = MockSlot(c, hardwareRandomControlCoreYaml, nil, "hardware-random-control") + s.plug, s.plugInfo = MockConnectedPlug(c, hardwareRandomControlConsumerYaml, nil, "hardware-random-control") + s.slot, s.slotInfo = MockConnectedSlot(c, hardwareRandomControlCoreYaml, nil, "hardware-random-control") } func (s *HardwareRandomControlInterfaceSuite) TestName(c *C) { @@ -62,32 +64,34 @@ } func (s *HardwareRandomControlInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "hardware-random-control", Interface: "hardware-random-control", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "hardware-random-control slots are reserved for the core snap") } func (s *HardwareRandomControlInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *HardwareRandomControlInterfaceSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, "hw_random/rng_current w,") } func (s *HardwareRandomControlInterfaceSuite) TestUDevSpec(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 1) - c.Assert(spec.Snippets()[0], Equals, `KERNEL=="hwrng", TAG+="snap_consumer_app"`) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Snippets(), HasLen, 2) + c.Assert(spec.Snippets(), testutil.Contains, `# hardware-random-control +KERNEL=="hwrng", TAG+="snap_consumer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_consumer_app", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_consumer_app $devpath $major:$minor"`) } func (s *HardwareRandomControlInterfaceSuite) TestStaticInfo(c *C) { @@ -99,7 +103,8 @@ } func (s *HardwareRandomControlInterfaceSuite) TestAutoConnect(c *C) { - c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) + // FIXME: fix AutoConnect methods to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) } func (s *HardwareRandomControlInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/hardware_random_observe_test.go snapd-2.31.1+17.10/interfaces/builtin/hardware_random_observe_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/hardware_random_observe_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/hardware_random_observe_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type HardwareRandomObserveInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&HardwareRandomObserveInterfaceSuite{ @@ -53,8 +55,8 @@ ` func (s *HardwareRandomObserveInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, hardwareRandomObserveConsumerYaml, nil, "hardware-random-observe") - s.slot = MockSlot(c, hardwareRandomObserveCoreYaml, nil, "hardware-random-observe") + s.plug, s.plugInfo = MockConnectedPlug(c, hardwareRandomObserveConsumerYaml, nil, "hardware-random-observe") + s.slot, s.slotInfo = MockConnectedSlot(c, hardwareRandomObserveCoreYaml, nil, "hardware-random-observe") } func (s *HardwareRandomObserveInterfaceSuite) TestName(c *C) { @@ -62,32 +64,34 @@ } func (s *HardwareRandomObserveInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "hardware-random-observe", Interface: "hardware-random-observe", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "hardware-random-observe slots are reserved for the core snap") } func (s *HardwareRandomObserveInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *HardwareRandomObserveInterfaceSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, "hw_random/rng_{available,current} r,") } func (s *HardwareRandomObserveInterfaceSuite) TestUDevSpec(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 1) - c.Assert(spec.Snippets()[0], Equals, `KERNEL=="hwrng", TAG+="snap_consumer_app"`) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Snippets(), HasLen, 2) + c.Assert(spec.Snippets(), testutil.Contains, `# hardware-random-observe +KERNEL=="hwrng", TAG+="snap_consumer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_consumer_app", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_consumer_app $devpath $major:$minor"`) } func (s *HardwareRandomObserveInterfaceSuite) TestStaticInfo(c *C) { @@ -99,7 +103,8 @@ } func (s *HardwareRandomObserveInterfaceSuite) TestAutoConnect(c *C) { - c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) + // FIXME: fix AutoConnect methods to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) } func (s *HardwareRandomObserveInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/hidraw.go snapd-2.31.1+17.10/interfaces/builtin/hidraw.go --- snapd-2.29.4.2+17.10/interfaces/builtin/hidraw.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/hidraw.go 2018-01-24 20:02:44.000000000 +0000 @@ -28,6 +28,7 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/udev" + "github.com/snapcore/snapd/snap" ) const hidrawSummary = `allows access to specific hidraw device` @@ -69,8 +70,8 @@ // are also specified var hidrawUDevSymlinkPattern = regexp.MustCompile("^/dev/hidraw-[a-z0-9]+$") -// SanitizeSlot checks validity of the defined slot -func (iface *hidrawInterface) SanitizeSlot(slot *interfaces.Slot) error { +// BeforePrepareSlot checks validity of the defined slot +func (iface *hidrawInterface) BeforePrepareSlot(slot *snap.SlotInfo) error { if err := sanitizeSlotReservedForOSOrGadget(iface, slot); err != nil { return err } @@ -116,24 +117,27 @@ return nil } -func (iface *hidrawInterface) UDevPermanentSlot(spec *udev.Specification, slot *interfaces.Slot) error { - usbVendor, vOk := slot.Attrs["usb-vendor"].(int64) - if !vOk { +func (iface *hidrawInterface) UDevPermanentSlot(spec *udev.Specification, slot *snap.SlotInfo) error { + usbVendor, ok := slot.Attrs["usb-vendor"].(int64) + if !ok { return nil } - usbProduct, pOk := slot.Attrs["usb-product"].(int64) - if !pOk { + usbProduct, ok := slot.Attrs["usb-product"].(int64) + if !ok { return nil } path, ok := slot.Attrs["path"].(string) if !ok || path == "" { return nil } - spec.AddSnippet(udevUsbDeviceSnippet("hidraw", usbVendor, usbProduct, "SYMLINK", strings.TrimPrefix(path, "/dev/"))) + spec.AddSnippet(fmt.Sprintf(`# hidraw +IMPORT{builtin}="usb_id" +SUBSYSTEM=="hidraw", SUBSYSTEMS=="usb", ATTRS{idVendor}=="%04x", ATTRS{idProduct}=="%04x", SYMLINK+="%s"`, + usbVendor, usbProduct, strings.TrimPrefix(path, "/dev/"))) return nil } -func (iface *hidrawInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *hidrawInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { if iface.hasUsbAttrs(slot) { // This apparmor rule must match hidrawDeviceNodePattern // UDev tagging and device cgroups will restrict down to the specific device @@ -142,9 +146,9 @@ } // Path to fixed device node - path, pathOk := slot.Attrs["path"].(string) - if !pathOk { - return nil + var path string + if err := slot.Attr("path", &path); err != nil { + return err } cleanedPath := filepath.Clean(path) spec.AddSnippet(fmt.Sprintf("%s rw,", cleanedPath)) @@ -152,23 +156,27 @@ } -func (iface *hidrawInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *hidrawInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { hasOnlyPath := true if iface.hasUsbAttrs(slot) { hasOnlyPath = false } - usbVendor, vOk := slot.Attrs["usb-vendor"].(int64) - if !vOk && !hasOnlyPath { + var usbVendor int64 + var usbProduct int64 + var path string + + err := slot.Attr("usb-vendor", &usbVendor) + if err != nil && !hasOnlyPath { return nil } - usbProduct, pOk := slot.Attrs["usb-product"].(int64) - if !pOk && !hasOnlyPath { + err = slot.Attr("usb-product", &usbProduct) + if err != nil && !hasOnlyPath { return nil } - path, pathOk := slot.Attrs["path"].(string) - if !pathOk && hasOnlyPath { + err = slot.Attr("path", &path) + if err != nil && hasOnlyPath { return nil } @@ -186,11 +194,12 @@ return true } -func (iface *hidrawInterface) hasUsbAttrs(slot *interfaces.Slot) bool { - if _, ok := slot.Attrs["usb-vendor"]; ok { +func (iface *hidrawInterface) hasUsbAttrs(attrs interfaces.Attrer) bool { + var v int64 + if err := attrs.Attr("usb-vendor", &v); err == nil { return true } - if _, ok := slot.Attrs["usb-product"]; ok { + if err := attrs.Attr("usb-product", &v); err == nil { return true } return false diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/hidraw_test.go snapd-2.31.1+17.10/interfaces/builtin/hidraw_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/hidraw_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/hidraw_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -26,6 +26,7 @@ "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/snap/snaptest" "github.com/snapcore/snapd/testutil" ) @@ -35,25 +36,40 @@ iface interfaces.Interface // OS Snap - testSlot1 *interfaces.Slot - testSlot2 *interfaces.Slot - missingPathSlot *interfaces.Slot - badPathSlot1 *interfaces.Slot - badPathSlot2 *interfaces.Slot - badPathSlot3 *interfaces.Slot - badInterfaceSlot *interfaces.Slot + testSlot1 *interfaces.ConnectedSlot + testSlot1Info *snap.SlotInfo + testSlot2 *interfaces.ConnectedSlot + testSlot2Info *snap.SlotInfo + missingPathSlot *interfaces.ConnectedSlot + missingPathSlotInfo *snap.SlotInfo + badPathSlot1 *interfaces.ConnectedSlot + badPathSlot1Info *snap.SlotInfo + badPathSlot2 *interfaces.ConnectedSlot + badPathSlot2Info *snap.SlotInfo + badPathSlot3 *interfaces.ConnectedSlot + badPathSlot3Info *snap.SlotInfo + badInterfaceSlot *interfaces.ConnectedSlot + badInterfaceSlotInfo *snap.SlotInfo // Gadget Snap - testUDev1 *interfaces.Slot - testUDev2 *interfaces.Slot - testUDevBadValue1 *interfaces.Slot - testUDevBadValue2 *interfaces.Slot - testUDevBadValue3 *interfaces.Slot + testUDev1 *interfaces.ConnectedSlot + testUDev1Info *snap.SlotInfo + testUDev2 *interfaces.ConnectedSlot + testUDev2Info *snap.SlotInfo + testUDevBadValue1 *interfaces.ConnectedSlot + testUDevBadValue1Info *snap.SlotInfo + testUDevBadValue2 *interfaces.ConnectedSlot + testUDevBadValue2Info *snap.SlotInfo + testUDevBadValue3 *interfaces.ConnectedSlot + testUDevBadValue3Info *snap.SlotInfo // Consuming Snap - testPlugPort1 *interfaces.Plug - testPlugPort2 *interfaces.Plug - testPlugPort3 *interfaces.Plug + testPlugPort1 *interfaces.ConnectedPlug + testPlugPort1Info *snap.PlugInfo + testPlugPort2 *interfaces.ConnectedPlug + testPlugPort2Info *snap.PlugInfo + testPlugPort3 *interfaces.ConnectedPlug + testPlugPort3Info *snap.PlugInfo } var _ = Suite(&HidrawInterfaceSuite{ @@ -83,13 +99,20 @@ path: /dev/hidraw9271 bad-interface: other-interface `, nil) - s.testSlot1 = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["test-port-1"]} - s.testSlot2 = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["test-port-2"]} - s.missingPathSlot = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["missing-path"]} - s.badPathSlot1 = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["bad-path-1"]} - s.badPathSlot2 = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["bad-path-2"]} - s.badPathSlot3 = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["bad-path-3"]} - s.badInterfaceSlot = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["bad-interface"]} + s.testSlot1Info = osSnapInfo.Slots["test-port-1"] + s.testSlot1 = interfaces.NewConnectedSlot(s.testSlot1Info, nil) + s.testSlot2Info = osSnapInfo.Slots["test-port-2"] + s.testSlot2 = interfaces.NewConnectedSlot(s.testSlot2Info, nil) + s.missingPathSlotInfo = osSnapInfo.Slots["missing-path"] + s.missingPathSlot = interfaces.NewConnectedSlot(s.missingPathSlotInfo, nil) + s.badPathSlot1Info = osSnapInfo.Slots["bad-path-1"] + s.badPathSlot1 = interfaces.NewConnectedSlot(s.badPathSlot1Info, nil) + s.badPathSlot2Info = osSnapInfo.Slots["bad-path-2"] + s.badPathSlot2 = interfaces.NewConnectedSlot(s.badPathSlot2Info, nil) + s.badPathSlot3Info = osSnapInfo.Slots["bad-path-3"] + s.badPathSlot3 = interfaces.NewConnectedSlot(s.badPathSlot3Info, nil) + s.badInterfaceSlotInfo = osSnapInfo.Slots["bad-interface"] + s.badInterfaceSlot = interfaces.NewConnectedSlot(s.badInterfaceSlotInfo, nil) gadgetSnapInfo := snaptest.MockInfo(c, ` name: some-device @@ -121,11 +144,16 @@ usb-product: 0x4321 path: /dev/my-device `, nil) - s.testUDev1 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-1"]} - s.testUDev2 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-2"]} - s.testUDevBadValue1 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-1"]} - s.testUDevBadValue2 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-2"]} - s.testUDevBadValue3 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-3"]} + s.testUDev1Info = gadgetSnapInfo.Slots["test-udev-1"] + s.testUDev1 = interfaces.NewConnectedSlot(s.testUDev1Info, nil) + s.testUDev2Info = gadgetSnapInfo.Slots["test-udev-2"] + s.testUDev2 = interfaces.NewConnectedSlot(s.testUDev2Info, nil) + s.testUDevBadValue1Info = gadgetSnapInfo.Slots["test-udev-bad-value-1"] + s.testUDevBadValue1 = interfaces.NewConnectedSlot(s.testUDevBadValue1Info, nil) + s.testUDevBadValue2Info = gadgetSnapInfo.Slots["test-udev-bad-value-2"] + s.testUDevBadValue2 = interfaces.NewConnectedSlot(s.testUDevBadValue2Info, nil) + s.testUDevBadValue3Info = gadgetSnapInfo.Slots["test-udev-bad-value-3"] + s.testUDevBadValue3 = interfaces.NewConnectedSlot(s.testUDevBadValue3Info, nil) consumingSnapInfo := snaptest.MockInfo(c, ` name: client-snap @@ -148,9 +176,12 @@ command: baz plugs: [plug-for-device-3] `, nil) - s.testPlugPort1 = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["plug-for-device-1"]} - s.testPlugPort2 = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["plug-for-device-2"]} - s.testPlugPort3 = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["plug-for-device-3"]} + s.testPlugPort1Info = consumingSnapInfo.Plugs["plug-for-device-1"] + s.testPlugPort1 = interfaces.NewConnectedPlug(s.testPlugPort1Info, nil) + s.testPlugPort2Info = consumingSnapInfo.Plugs["plug-for-device-2"] + s.testPlugPort2 = interfaces.NewConnectedPlug(s.testPlugPort2Info, nil) + s.testPlugPort3Info = consumingSnapInfo.Plugs["plug-for-device-3"] + s.testPlugPort3 = interfaces.NewConnectedPlug(s.testPlugPort3Info, nil) } func (s *HidrawInterfaceSuite) TestName(c *C) { @@ -158,135 +189,162 @@ } func (s *HidrawInterfaceSuite) TestSanitizeCoreSnapSlots(c *C) { - for _, slot := range []*interfaces.Slot{s.testSlot1, s.testSlot2} { - c.Assert(slot.Sanitize(s.iface), IsNil) + for _, slot := range []*snap.SlotInfo{s.testSlot1Info, s.testSlot2Info} { + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), IsNil) } } func (s *HidrawInterfaceSuite) TestSanitizeBadCoreSnapSlots(c *C) { // Slots without the "path" attribute are rejected. - c.Assert(s.missingPathSlot.Sanitize(s.iface), ErrorMatches, `hidraw slots must have a path attribute`) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.missingPathSlotInfo), ErrorMatches, + `hidraw slots must have a path attribute`) // Slots with incorrect value of the "path" attribute are rejected. - for _, slot := range []*interfaces.Slot{s.badPathSlot1, s.badPathSlot2, s.badPathSlot3} { - c.Assert(slot.Sanitize(s.iface), ErrorMatches, "hidraw path attribute must be a valid device node") + for _, slot := range []*snap.SlotInfo{s.badPathSlot1Info, s.badPathSlot2Info, s.badPathSlot3Info} { + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "hidraw path attribute must be a valid device node") } } func (s *HidrawInterfaceSuite) TestSanitizeGadgetSnapSlots(c *C) { - c.Assert(s.testUDev1.Sanitize(s.iface), IsNil) - c.Assert(s.testUDev2.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDev1Info), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDev2Info), IsNil) } func (s *HidrawInterfaceSuite) TestSanitizeBadGadgetSnapSlots(c *C) { - c.Assert(s.testUDevBadValue1.Sanitize(s.iface), ErrorMatches, "hidraw usb-vendor attribute not valid: -1") - c.Assert(s.testUDevBadValue2.Sanitize(s.iface), ErrorMatches, "hidraw usb-product attribute not valid: 65536") - c.Assert(s.testUDevBadValue3.Sanitize(s.iface), ErrorMatches, "hidraw path attribute specifies invalid symlink location") + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue1Info), ErrorMatches, "hidraw usb-vendor attribute not valid: -1") + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue2Info), ErrorMatches, "hidraw usb-product attribute not valid: 65536") + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue3Info), ErrorMatches, "hidraw path attribute specifies invalid symlink location") } func (s *HidrawInterfaceSuite) TestPermanentSlotUDevSnippets(c *C) { spec := &udev.Specification{} - for _, slot := range []*interfaces.Slot{s.testSlot1, s.testSlot2} { + for _, slot := range []*snap.SlotInfo{s.testSlot1Info, s.testSlot2Info} { c.Assert(spec.AddPermanentSlot(s.iface, slot), IsNil) c.Assert(spec.Snippets(), HasLen, 0) } - expectedSnippet1 := `IMPORT{builtin}="usb_id" + expectedSnippet1 := `# hidraw +IMPORT{builtin}="usb_id" SUBSYSTEM=="hidraw", SUBSYSTEMS=="usb", ATTRS{idVendor}=="0001", ATTRS{idProduct}=="0001", SYMLINK+="hidraw-canbus"` - c.Assert(spec.AddPermanentSlot(s.iface, s.testUDev1), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.testUDev1Info), IsNil) c.Assert(spec.Snippets(), HasLen, 1) snippet := spec.Snippets()[0] - c.Assert(snippet, Equals, expectedSnippet1, Commentf("\nexpected:\n%s\nfound:\n%s", expectedSnippet1, snippet)) + c.Assert(snippet, Equals, expectedSnippet1) - expectedSnippet2 := `IMPORT{builtin}="usb_id" + expectedSnippet2 := `# hidraw +IMPORT{builtin}="usb_id" SUBSYSTEM=="hidraw", SUBSYSTEMS=="usb", ATTRS{idVendor}=="ffff", ATTRS{idProduct}=="ffff", SYMLINK+="hidraw-mydevice"` spec = &udev.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.testUDev2), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.testUDev2Info), IsNil) c.Assert(spec.Snippets(), HasLen, 1) snippet = spec.Snippets()[0] - c.Assert(snippet, Equals, expectedSnippet2, Commentf("\nexpected:\n%s\nfound:\n%s", expectedSnippet2, snippet)) + c.Assert(snippet, Equals, expectedSnippet2) } func (s *HidrawInterfaceSuite) TestConnectedPlugUDevSnippets(c *C) { // add the plug for the slot with just path spec := &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPort1, nil, s.testSlot1, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 1) + c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPort1, s.testSlot1), IsNil) + c.Assert(spec.Snippets(), HasLen, 2) snippet := spec.Snippets()[0] - expectedSnippet1 := `SUBSYSTEM=="hidraw", KERNEL=="hidraw0", TAG+="snap_client-snap_app-accessing-2-devices"` + expectedSnippet1 := `# hidraw +SUBSYSTEM=="hidraw", KERNEL=="hidraw0", TAG+="snap_client-snap_app-accessing-2-devices"` c.Assert(snippet, Equals, expectedSnippet1) + extraSnippet := spec.Snippets()[1] + expectedExtraSnippet1 := `TAG=="snap_client-snap_app-accessing-2-devices", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_client-snap_app-accessing-2-devices $devpath $major:$minor"` + c.Assert(extraSnippet, Equals, expectedExtraSnippet1) // add the plug for the first slot with vendor and product ids spec = &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPort1, nil, s.testUDev1, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 1) + c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPort1, s.testUDev1), IsNil) + c.Assert(spec.Snippets(), HasLen, 2) snippet = spec.Snippets()[0] - expectedSnippet2 := `IMPORT{builtin}="usb_id" + expectedSnippet2 := `# hidraw +IMPORT{builtin}="usb_id" SUBSYSTEM=="hidraw", SUBSYSTEMS=="usb", ATTRS{idVendor}=="0001", ATTRS{idProduct}=="0001", TAG+="snap_client-snap_app-accessing-2-devices"` c.Assert(snippet, Equals, expectedSnippet2) + extraSnippet = spec.Snippets()[1] + expectedExtraSnippet2 := `TAG=="snap_client-snap_app-accessing-2-devices", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_client-snap_app-accessing-2-devices $devpath $major:$minor"` + c.Assert(extraSnippet, Equals, expectedExtraSnippet2) // add the plug for the second slot with vendor and product ids spec = &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPort2, nil, s.testUDev2, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 1) + c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPort2, s.testUDev2), IsNil) + c.Assert(spec.Snippets(), HasLen, 2) snippet = spec.Snippets()[0] - expectedSnippet3 := `IMPORT{builtin}="usb_id" + expectedSnippet3 := `# hidraw +IMPORT{builtin}="usb_id" SUBSYSTEM=="hidraw", SUBSYSTEMS=="usb", ATTRS{idVendor}=="ffff", ATTRS{idProduct}=="ffff", TAG+="snap_client-snap_app-accessing-2-devices"` c.Assert(snippet, Equals, expectedSnippet3) + extraSnippet = spec.Snippets()[1] + expectedExtraSnippet3 := `TAG=="snap_client-snap_app-accessing-2-devices", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_client-snap_app-accessing-2-devices $devpath $major:$minor"` + c.Assert(extraSnippet, Equals, expectedExtraSnippet3) } func (s *HidrawInterfaceSuite) TestConnectedPlugAppArmorSnippets(c *C) { expectedSnippet1 := `/dev/hidraw0 rw,` apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.testPlugPort1, nil, s.testSlot1, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.testPlugPort1, s.testSlot1) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.client-snap.app-accessing-2-devices"}) snippet := apparmorSpec.SnippetForTag("snap.client-snap.app-accessing-2-devices") - c.Assert(snippet, DeepEquals, expectedSnippet1, Commentf("\nexpected:\n%s\nfound:\n%s", expectedSnippet1, snippet)) + c.Assert(snippet, DeepEquals, expectedSnippet1) expectedSnippet2 := `/dev/hidraw[0-9]{,[0-9],[0-9][0-9]} rw,` apparmorSpec = &apparmor.Specification{} - err = apparmorSpec.AddConnectedPlug(s.iface, s.testPlugPort1, nil, s.testUDev1, nil) + err = apparmorSpec.AddConnectedPlug(s.iface, s.testPlugPort1, s.testUDev1) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.client-snap.app-accessing-2-devices"}) snippet = apparmorSpec.SnippetForTag("snap.client-snap.app-accessing-2-devices") - c.Assert(snippet, DeepEquals, expectedSnippet2, Commentf("\nexpected:\n%s\nfound:\n%s", expectedSnippet2, snippet)) + c.Assert(snippet, DeepEquals, expectedSnippet2) expectedSnippet3 := `/dev/hidraw[0-9]{,[0-9],[0-9][0-9]} rw,` apparmorSpec = &apparmor.Specification{} - err = apparmorSpec.AddConnectedPlug(s.iface, s.testPlugPort2, nil, s.testUDev2, nil) + err = apparmorSpec.AddConnectedPlug(s.iface, s.testPlugPort2, s.testUDev2) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.client-snap.app-accessing-2-devices"}) snippet = apparmorSpec.SnippetForTag("snap.client-snap.app-accessing-2-devices") - c.Assert(snippet, DeepEquals, expectedSnippet3, Commentf("\nexpected:\n%s\nfound:\n%s", expectedSnippet3, snippet)) + c.Assert(snippet, DeepEquals, expectedSnippet3) } func (s *HidrawInterfaceSuite) TestConnectedPlugUDevSnippetsForPath(c *C) { - expectedSnippet1 := `SUBSYSTEM=="hidraw", KERNEL=="hidraw0", TAG+="snap_client-snap_app-accessing-2-devices"` + expectedSnippet1 := `# hidraw +SUBSYSTEM=="hidraw", KERNEL=="hidraw0", TAG+="snap_client-snap_app-accessing-2-devices"` + expectedExtraSnippet1 := `TAG=="snap_client-snap_app-accessing-2-devices", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_client-snap_app-accessing-2-devices $devpath $major:$minor"` udevSpec := &udev.Specification{} - err := udevSpec.AddConnectedPlug(s.iface, s.testPlugPort1, nil, s.testSlot1, nil) + err := udevSpec.AddConnectedPlug(s.iface, s.testPlugPort1, s.testSlot1) c.Assert(err, IsNil) - c.Assert(udevSpec.Snippets(), HasLen, 1) + c.Assert(udevSpec.Snippets(), HasLen, 2) snippet := udevSpec.Snippets()[0] - c.Assert(snippet, DeepEquals, expectedSnippet1, Commentf("\nexpected:\n%s\nfound:\n%s", expectedSnippet1, snippet)) + c.Assert(snippet, Equals, expectedSnippet1) + extraSnippet := udevSpec.Snippets()[1] + c.Assert(extraSnippet, Equals, expectedExtraSnippet1) - expectedSnippet2 := `IMPORT{builtin}="usb_id" + expectedSnippet2 := `# hidraw +IMPORT{builtin}="usb_id" SUBSYSTEM=="hidraw", SUBSYSTEMS=="usb", ATTRS{idVendor}=="0001", ATTRS{idProduct}=="0001", TAG+="snap_client-snap_app-accessing-2-devices"` + expectedExtraSnippet2 := `TAG=="snap_client-snap_app-accessing-2-devices", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_client-snap_app-accessing-2-devices $devpath $major:$minor"` udevSpec = &udev.Specification{} - err = udevSpec.AddConnectedPlug(s.iface, s.testPlugPort1, nil, s.testUDev1, nil) + err = udevSpec.AddConnectedPlug(s.iface, s.testPlugPort1, s.testUDev1) c.Assert(err, IsNil) - c.Assert(udevSpec.Snippets(), HasLen, 1) + c.Assert(udevSpec.Snippets(), HasLen, 2) snippet = udevSpec.Snippets()[0] - c.Assert(snippet, DeepEquals, expectedSnippet2, Commentf("\nexpected:\n%s\nfound:\n%s", expectedSnippet2, snippet)) + c.Assert(snippet, Equals, expectedSnippet2) + extraSnippet = udevSpec.Snippets()[1] + c.Assert(extraSnippet, Equals, expectedExtraSnippet2) - expectedSnippet3 := `IMPORT{builtin}="usb_id" + expectedSnippet3 := `# hidraw +IMPORT{builtin}="usb_id" SUBSYSTEM=="hidraw", SUBSYSTEMS=="usb", ATTRS{idVendor}=="ffff", ATTRS{idProduct}=="ffff", TAG+="snap_client-snap_app-accessing-2-devices"` + expectedExtraSnippet3 := `TAG=="snap_client-snap_app-accessing-2-devices", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_client-snap_app-accessing-2-devices $devpath $major:$minor"` udevSpec = &udev.Specification{} - err = udevSpec.AddConnectedPlug(s.iface, s.testPlugPort2, nil, s.testUDev2, nil) + err = udevSpec.AddConnectedPlug(s.iface, s.testPlugPort2, s.testUDev2) c.Assert(err, IsNil) - c.Assert(udevSpec.Snippets(), HasLen, 1) + c.Assert(udevSpec.Snippets(), HasLen, 2) snippet = udevSpec.Snippets()[0] - c.Assert(snippet, DeepEquals, expectedSnippet3, Commentf("\nexpected:\n%s\nfound:\n%s", expectedSnippet3, snippet)) + c.Assert(snippet, Equals, expectedSnippet3) + extraSnippet = udevSpec.Snippets()[1] + c.Assert(extraSnippet, Equals, expectedExtraSnippet3) } func (s *HidrawInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/home.go snapd-2.31.1+17.10/interfaces/builtin/home.go --- snapd-2.29.4.2+17.10/interfaces/builtin/home.go 2017-11-30 07:12:26.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/home.go 2018-02-16 20:27:57.000000000 +0000 @@ -41,15 +41,22 @@ owner @{HOME}/ r, # Allow read/write access to all files in @{HOME}, except snap application -# data in @{HOME}/snaps and toplevel hidden directories in @{HOME}. +# data in @{HOME}/snap and toplevel hidden directories in @{HOME}. owner @{HOME}/[^s.]** rwklix, owner @{HOME}/s[^n]** rwklix, owner @{HOME}/sn[^a]** rwklix, owner @{HOME}/sna[^p]** rwklix, owner @{HOME}/snap[^/]** rwklix, + # Allow creating a few files not caught above owner @{HOME}/{s,sn,sna}{,/} rwklix, +# Allow access to @{HOME}/snap/ to allow directory traversals from +# @{HOME}/snap/@{SNAP_NAME} through @{HOME}/snap to @{HOME}. While this leaks +# snap names, it fixes usability issues for snaps that require this +# transitional interface. +owner @{HOME}/snap/ r, + # Allow access to gvfs mounts for files owned by the user (including hidden # files; only allow writes to files, not the mount point). owner /run/user/[0-9]*/gvfs/{,**} r, diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/home_test.go snapd-2.31.1+17.10/interfaces/builtin/home_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/home_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/home_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type HomeInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slot *interfaces.ConnectedSlot + slotInfo *snap.SlotInfo + plug *interfaces.ConnectedPlug + plugInfo *snap.PlugInfo } var _ = Suite(&HomeInterfaceSuite{ @@ -48,15 +50,15 @@ command: foo plugs: [home] ` - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "home", - Interface: "home", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "home", + Interface: "home", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, mockPlugSnapInfo, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["home"]} + s.plugInfo = plugSnap.Plugs["home"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *HomeInterfaceSuite) TestName(c *C) { @@ -64,24 +66,24 @@ } func (s *HomeInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "home", Interface: "home", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "home slots are reserved for the core snap") } func (s *HomeInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *HomeInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Check(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `owner @{HOME}/ r,`) diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/i2c.go snapd-2.31.1+17.10/interfaces/builtin/i2c.go --- snapd-2.29.4.2+17.10/interfaces/builtin/i2c.go 2017-11-30 07:12:26.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/i2c.go 2018-01-24 20:02:44.000000000 +0000 @@ -28,6 +28,7 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/udev" + "github.com/snapcore/snapd/snap" ) const i2cSummary = `allows access to specific I2C controller` @@ -73,7 +74,7 @@ var i2cControlDeviceNodePattern = regexp.MustCompile("^/dev/i2c-[0-9]+$") // Check validity of the defined slot -func (iface *i2cInterface) SanitizeSlot(slot *interfaces.Slot) error { +func (iface *i2cInterface) BeforePrepareSlot(slot *snap.SlotInfo) error { if err := sanitizeSlotReservedForOSOrGadget(iface, slot); err != nil { return err } @@ -93,9 +94,9 @@ return nil } -func (iface *i2cInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - path, pathOk := slot.Attrs["path"].(string) - if !pathOk { +func (iface *i2cInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { + var path string + if err := slot.Attr("path", &path); err != nil { return nil } @@ -104,9 +105,9 @@ return nil } -func (iface *i2cInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - path, pathOk := slot.Attrs["path"].(string) - if !pathOk { +func (iface *i2cInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { + var path string + if err := slot.Attr("path", &path); err != nil { return nil } spec.TagDevice(fmt.Sprintf(`KERNEL=="%s"`, strings.TrimPrefix(path, "/dev/"))) diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/i2c_test.go snapd-2.31.1+17.10/interfaces/builtin/i2c_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/i2c_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/i2c_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -26,6 +26,7 @@ "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/snap/snaptest" "github.com/snapcore/snapd/testutil" ) @@ -35,23 +36,36 @@ iface interfaces.Interface // OS Snap - testSlot1 *interfaces.Slot + testSlot1 *interfaces.ConnectedSlot + testSlot1Info *snap.SlotInfo // Gadget Snap - testUDev1 *interfaces.Slot - testUDev2 *interfaces.Slot - testUDev3 *interfaces.Slot - testUDevBadValue1 *interfaces.Slot - testUDevBadValue2 *interfaces.Slot - testUDevBadValue3 *interfaces.Slot - testUDevBadValue4 *interfaces.Slot - testUDevBadValue5 *interfaces.Slot - testUDevBadValue6 *interfaces.Slot - testUDevBadValue7 *interfaces.Slot - testUDevBadInterface1 *interfaces.Slot + testUDev1 *interfaces.ConnectedSlot + testUDev1Info *snap.SlotInfo + testUDev2 *interfaces.ConnectedSlot + testUDev2Info *snap.SlotInfo + testUDev3 *interfaces.ConnectedSlot + testUDev3Info *snap.SlotInfo + testUDevBadValue1 *interfaces.ConnectedSlot + testUDevBadValue1Info *snap.SlotInfo + testUDevBadValue2 *interfaces.ConnectedSlot + testUDevBadValue2Info *snap.SlotInfo + testUDevBadValue3 *interfaces.ConnectedSlot + testUDevBadValue3Info *snap.SlotInfo + testUDevBadValue4 *interfaces.ConnectedSlot + testUDevBadValue4Info *snap.SlotInfo + testUDevBadValue5 *interfaces.ConnectedSlot + testUDevBadValue5Info *snap.SlotInfo + testUDevBadValue6 *interfaces.ConnectedSlot + testUDevBadValue6Info *snap.SlotInfo + testUDevBadValue7 *interfaces.ConnectedSlot + testUDevBadValue7Info *snap.SlotInfo + testUDevBadInterface1 *interfaces.ConnectedSlot + testUDevBadInterface1Info *snap.SlotInfo // Consuming Snap - testPlugPort1 *interfaces.Plug + testPlugPort1 *interfaces.ConnectedPlug + testPlugPort1Info *snap.PlugInfo } var _ = Suite(&I2cInterfaceSuite{ @@ -68,7 +82,7 @@ interface: i2c path: /dev/i2c-0 `, nil) - s.testSlot1 = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["test-port-1"]} + s.testSlot1Info = osSnapInfo.Slots["test-port-1"] // Mock for Gadget Snap gadgetSnapInfo := snaptest.MockInfo(c, ` @@ -107,17 +121,27 @@ test-udev-bad-interface-1: interface: other-interface `, nil) - s.testUDev1 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-1"]} - s.testUDev2 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-2"]} - s.testUDev3 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-3"]} - s.testUDevBadValue1 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-1"]} - s.testUDevBadValue2 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-2"]} - s.testUDevBadValue3 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-3"]} - s.testUDevBadValue4 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-4"]} - s.testUDevBadValue5 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-5"]} - s.testUDevBadValue6 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-6"]} - s.testUDevBadValue7 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-7"]} - s.testUDevBadInterface1 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-interface-1"]} + s.testUDev1Info = gadgetSnapInfo.Slots["test-udev-1"] + s.testUDev1 = interfaces.NewConnectedSlot(s.testUDev1Info, nil) + s.testUDev2Info = gadgetSnapInfo.Slots["test-udev-2"] + s.testUDev2 = interfaces.NewConnectedSlot(s.testUDev2Info, nil) + s.testUDev3Info = gadgetSnapInfo.Slots["test-udev-3"] + s.testUDev3 = interfaces.NewConnectedSlot(s.testUDev3Info, nil) + s.testUDevBadValue1Info = gadgetSnapInfo.Slots["test-udev-bad-value-1"] + s.testUDevBadValue1 = interfaces.NewConnectedSlot(s.testUDevBadValue1Info, nil) + s.testUDevBadValue2Info = gadgetSnapInfo.Slots["test-udev-bad-value-2"] + s.testUDevBadValue2 = interfaces.NewConnectedSlot(s.testUDevBadValue2Info, nil) + s.testUDevBadValue3Info = gadgetSnapInfo.Slots["test-udev-bad-value-3"] + s.testUDevBadValue3 = interfaces.NewConnectedSlot(s.testUDevBadValue3Info, nil) + s.testUDevBadValue4Info = gadgetSnapInfo.Slots["test-udev-bad-value-4"] + s.testUDevBadValue4 = interfaces.NewConnectedSlot(s.testUDevBadValue4Info, nil) + s.testUDevBadValue5Info = gadgetSnapInfo.Slots["test-udev-bad-value-5"] + s.testUDevBadValue5 = interfaces.NewConnectedSlot(s.testUDevBadValue5Info, nil) + s.testUDevBadValue6Info = gadgetSnapInfo.Slots["test-udev-bad-value-6"] + s.testUDevBadValue6 = interfaces.NewConnectedSlot(s.testUDevBadValue6Info, nil) + s.testUDevBadValue7Info = gadgetSnapInfo.Slots["test-udev-bad-value-7"] + s.testUDevBadValue7 = interfaces.NewConnectedSlot(s.testUDevBadValue7Info, nil) + s.testUDevBadInterface1Info = gadgetSnapInfo.Slots["test-udev-bad-interface-1"] // Snap Consumers consumingSnapInfo := snaptest.MockInfo(c, ` @@ -130,7 +154,8 @@ command: foo plugs: [i2c] `, nil) - s.testPlugPort1 = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["plug-for-port-1"]} + s.testPlugPort1Info = consumingSnapInfo.Plugs["plug-for-port-1"] + s.testPlugPort1 = interfaces.NewConnectedPlug(s.testPlugPort1Info, nil) } func (s *I2cInterfaceSuite) TestName(c *C) { @@ -138,35 +163,37 @@ } func (s *I2cInterfaceSuite) TestSanitizeCoreSnapSlot(c *C) { - c.Assert(s.testSlot1.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testSlot1Info), IsNil) } func (s *I2cInterfaceSuite) TestSanitizeGadgetSnapSlot(c *C) { - c.Assert(s.testUDev1.Sanitize(s.iface), IsNil) - c.Assert(s.testUDev2.Sanitize(s.iface), IsNil) - c.Assert(s.testUDev3.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDev1Info), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDev2Info), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDev3Info), IsNil) } func (s *I2cInterfaceSuite) TestSanitizeBadGadgetSnapSlot(c *C) { - c.Assert(s.testUDevBadValue1.Sanitize(s.iface), ErrorMatches, "i2c path attribute must be a valid device node") - c.Assert(s.testUDevBadValue2.Sanitize(s.iface), ErrorMatches, "i2c path attribute must be a valid device node") - c.Assert(s.testUDevBadValue3.Sanitize(s.iface), ErrorMatches, "i2c path attribute must be a valid device node") - c.Assert(s.testUDevBadValue4.Sanitize(s.iface), ErrorMatches, "i2c path attribute must be a valid device node") - c.Assert(s.testUDevBadValue5.Sanitize(s.iface), ErrorMatches, "i2c path attribute must be a valid device node") - c.Assert(s.testUDevBadValue6.Sanitize(s.iface), ErrorMatches, "i2c slot must have a path attribute") - c.Assert(s.testUDevBadValue7.Sanitize(s.iface), ErrorMatches, "i2c slot must have a path attribute") + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue1Info), ErrorMatches, "i2c path attribute must be a valid device node") + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue2Info), ErrorMatches, "i2c path attribute must be a valid device node") + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue3Info), ErrorMatches, "i2c path attribute must be a valid device node") + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue4Info), ErrorMatches, "i2c path attribute must be a valid device node") + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue5Info), ErrorMatches, "i2c path attribute must be a valid device node") + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue6Info), ErrorMatches, "i2c slot must have a path attribute") + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue7Info), ErrorMatches, "i2c slot must have a path attribute") } func (s *I2cInterfaceSuite) TestUDevSpec(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPort1, nil, s.testUDev1, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 1) - c.Assert(spec.Snippets()[0], testutil.Contains, `KERNEL=="i2c-1", TAG+="snap_client-snap_app-accessing-1-port"`) + c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPort1, s.testUDev1), IsNil) + c.Assert(spec.Snippets(), HasLen, 2) + c.Assert(spec.Snippets(), testutil.Contains, `# i2c +KERNEL=="i2c-1", TAG+="snap_client-snap_app-accessing-1-port"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_client-snap_app-accessing-1-port", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_client-snap_app-accessing-1-port $devpath $major:$minor"`) } func (s *I2cInterfaceSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPort1, nil, s.testUDev1, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPort1, s.testUDev1), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.client-snap.app-accessing-1-port"}) c.Assert(spec.SnippetForTag("snap.client-snap.app-accessing-1-port"), testutil.Contains, `/dev/i2c-1 rw,`) c.Assert(spec.SnippetForTag("snap.client-snap.app-accessing-1-port"), testutil.Contains, `/sys/devices/platform/{*,**.i2c}/i2c-1/** rw,`) diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/iio.go snapd-2.31.1+17.10/interfaces/builtin/iio.go --- snapd-2.29.4.2+17.10/interfaces/builtin/iio.go 2017-11-30 07:12:26.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/iio.go 2018-01-24 20:02:44.000000000 +0000 @@ -28,6 +28,7 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/udev" + "github.com/snapcore/snapd/snap" ) const iioSummary = `allows access to a specific IIO device` @@ -76,7 +77,7 @@ var iioControlDeviceNodePattern = regexp.MustCompile("^/dev/iio:device[0-9]+$") // Check validity of the defined slot -func (iface *iioInterface) SanitizeSlot(slot *interfaces.Slot) error { +func (iface *iioInterface) BeforePrepareSlot(slot *snap.SlotInfo) error { if err := sanitizeSlotReservedForOSOrGadget(iface, slot); err != nil { return err } @@ -96,9 +97,9 @@ return nil } -func (iface *iioInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - path, pathOk := slot.Attrs["path"].(string) - if !pathOk { +func (iface *iioInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { + var path string + if err := slot.Attr("path", &path); err != nil { return nil } @@ -106,7 +107,7 @@ snippet := strings.Replace(iioConnectedPlugAppArmor, "###IIO_DEVICE_PATH###", cleanedPath, -1) // The path is already verified against a regular expression - // in SanitizeSlot so we can rely on its structure here and + // in BeforePrepareSlot so we can rely on its structure here and // safely strip the '/dev/' prefix to get the actual name of // the IIO device. deviceName := strings.TrimPrefix(path, "/dev/") @@ -116,9 +117,9 @@ return nil } -func (iface *iioInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - path, pathOk := slot.Attrs["path"].(string) - if !pathOk { +func (iface *iioInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { + var path string + if err := slot.Attr("path", &path); err != nil { return nil } spec.TagDevice(fmt.Sprintf(`KERNEL=="%s"`, strings.TrimPrefix(path, "/dev/"))) diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/iio_test.go snapd-2.31.1+17.10/interfaces/builtin/iio_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/iio_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/iio_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -26,6 +26,7 @@ "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/snap/snaptest" "github.com/snapcore/snapd/testutil" ) @@ -35,24 +36,38 @@ iface interfaces.Interface // OS Snap - testSlot1 *interfaces.Slot + testSlot1 *interfaces.ConnectedSlot + testSlot1Info *snap.SlotInfo // Gadget Snap - testUDev1 *interfaces.Slot - testUDev2 *interfaces.Slot - testUDev3 *interfaces.Slot - testUDevBadValue1 *interfaces.Slot - testUDevBadValue2 *interfaces.Slot - testUDevBadValue3 *interfaces.Slot - testUDevBadValue4 *interfaces.Slot - testUDevBadValue5 *interfaces.Slot - testUDevBadValue6 *interfaces.Slot - testUDevBadValue7 *interfaces.Slot - testUDevBadValue8 *interfaces.Slot - testUDevBadInterface1 *interfaces.Slot + testUDev1 *interfaces.ConnectedSlot + testUDev1Info *snap.SlotInfo + testUDev2 *interfaces.ConnectedSlot + testUDev2Info *snap.SlotInfo + testUDev3 *interfaces.ConnectedSlot + testUDev3Info *snap.SlotInfo + testUDevBadValue1 *interfaces.ConnectedSlot + testUDevBadValue1Info *snap.SlotInfo + testUDevBadValue2 *interfaces.ConnectedSlot + testUDevBadValue2Info *snap.SlotInfo + testUDevBadValue3 *interfaces.ConnectedSlot + testUDevBadValue3Info *snap.SlotInfo + testUDevBadValue4 *interfaces.ConnectedSlot + testUDevBadValue4Info *snap.SlotInfo + testUDevBadValue5 *interfaces.ConnectedSlot + testUDevBadValue5Info *snap.SlotInfo + testUDevBadValue6 *interfaces.ConnectedSlot + testUDevBadValue6Info *snap.SlotInfo + testUDevBadValue7 *interfaces.ConnectedSlot + testUDevBadValue7Info *snap.SlotInfo + testUDevBadValue8 *interfaces.ConnectedSlot + testUDevBadValue8Info *snap.SlotInfo + testUDevBadInterface1 *interfaces.ConnectedSlot + testUDevBadInterface1Info *snap.SlotInfo // Consuming Snap - testPlugPort1 *interfaces.Plug + testPlugPort1 *interfaces.ConnectedPlug + testPlugPort1Info *snap.PlugInfo } var _ = Suite(&IioInterfaceSuite{ @@ -69,7 +84,7 @@ interface: iio path: /dev/iio:device0 `, nil) - s.testSlot1 = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["test-port-1"]} + s.testSlot1Info = osSnapInfo.Slots["test-port-1"] // Mock for Gadget Snap gadgetSnapInfo := snaptest.MockInfo(c, ` @@ -111,18 +126,30 @@ test-udev-bad-interface-1: interface: other-interface `, nil) - s.testUDev1 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-1"]} - s.testUDev2 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-2"]} - s.testUDev3 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-3"]} - s.testUDevBadValue1 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-1"]} - s.testUDevBadValue2 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-2"]} - s.testUDevBadValue3 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-3"]} - s.testUDevBadValue4 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-4"]} - s.testUDevBadValue5 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-5"]} - s.testUDevBadValue6 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-6"]} - s.testUDevBadValue7 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-7"]} - s.testUDevBadValue8 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-8"]} - s.testUDevBadInterface1 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-interface-1"]} + s.testUDev1Info = gadgetSnapInfo.Slots["test-udev-1"] + s.testUDev1 = interfaces.NewConnectedSlot(s.testUDev1Info, nil) + s.testUDev2Info = gadgetSnapInfo.Slots["test-udev-2"] + s.testUDev2 = interfaces.NewConnectedSlot(s.testUDev2Info, nil) + s.testUDev3Info = gadgetSnapInfo.Slots["test-udev-3"] + s.testUDev3 = interfaces.NewConnectedSlot(s.testUDev3Info, nil) + s.testUDevBadValue1Info = gadgetSnapInfo.Slots["test-udev-bad-value-1"] + s.testUDevBadValue1 = interfaces.NewConnectedSlot(s.testUDevBadValue1Info, nil) + s.testUDevBadValue2Info = gadgetSnapInfo.Slots["test-udev-bad-value-2"] + s.testUDevBadValue2 = interfaces.NewConnectedSlot(s.testUDevBadValue2Info, nil) + s.testUDevBadValue3Info = gadgetSnapInfo.Slots["test-udev-bad-value-3"] + s.testUDevBadValue3 = interfaces.NewConnectedSlot(s.testUDevBadValue3Info, nil) + s.testUDevBadValue4Info = gadgetSnapInfo.Slots["test-udev-bad-value-4"] + s.testUDevBadValue4 = interfaces.NewConnectedSlot(s.testUDevBadValue4Info, nil) + s.testUDevBadValue5Info = gadgetSnapInfo.Slots["test-udev-bad-value-5"] + s.testUDevBadValue5 = interfaces.NewConnectedSlot(s.testUDevBadValue5Info, nil) + s.testUDevBadValue6Info = gadgetSnapInfo.Slots["test-udev-bad-value-6"] + s.testUDevBadValue6 = interfaces.NewConnectedSlot(s.testUDevBadValue6Info, nil) + s.testUDevBadValue7Info = gadgetSnapInfo.Slots["test-udev-bad-value-7"] + s.testUDevBadValue7 = interfaces.NewConnectedSlot(s.testUDevBadValue7Info, nil) + s.testUDevBadValue8Info = gadgetSnapInfo.Slots["test-udev-bad-value-8"] + s.testUDevBadValue8 = interfaces.NewConnectedSlot(s.testUDevBadValue8Info, nil) + s.testUDevBadInterface1Info = gadgetSnapInfo.Slots["test-udev-bad-interface-1"] + s.testUDevBadInterface1 = interfaces.NewConnectedSlot(s.testUDevBadInterface1Info, nil) // Snap Consumers consumingSnapInfo := snaptest.MockInfo(c, ` @@ -135,7 +162,8 @@ command: foo plugs: [iio] `, nil) - s.testPlugPort1 = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["plug-for-port-1"]} + s.testPlugPort1Info = consumingSnapInfo.Plugs["plug-for-port-1"] + s.testPlugPort1 = interfaces.NewConnectedPlug(s.testPlugPort1Info, nil) } func (s *IioInterfaceSuite) TestName(c *C) { @@ -143,21 +171,23 @@ } func (s *IioInterfaceSuite) TestSanitizeBadGadgetSnapSlot(c *C) { - c.Assert(s.testUDevBadValue1.Sanitize(s.iface), ErrorMatches, "iio path attribute must be a valid device node") - c.Assert(s.testUDevBadValue2.Sanitize(s.iface), ErrorMatches, "iio path attribute must be a valid device node") - c.Assert(s.testUDevBadValue3.Sanitize(s.iface), ErrorMatches, "iio path attribute must be a valid device node") - c.Assert(s.testUDevBadValue4.Sanitize(s.iface), ErrorMatches, "iio path attribute must be a valid device node") - c.Assert(s.testUDevBadValue5.Sanitize(s.iface), ErrorMatches, "iio path attribute must be a valid device node") - c.Assert(s.testUDevBadValue6.Sanitize(s.iface), ErrorMatches, "iio path attribute must be a valid device node") - c.Assert(s.testUDevBadValue7.Sanitize(s.iface), ErrorMatches, "iio slot must have a path attribute") - c.Assert(s.testUDevBadValue8.Sanitize(s.iface), ErrorMatches, "iio slot must have a path attribute") + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue1Info), ErrorMatches, "iio path attribute must be a valid device node") + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue2Info), ErrorMatches, "iio path attribute must be a valid device node") + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue3Info), ErrorMatches, "iio path attribute must be a valid device node") + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue4Info), ErrorMatches, "iio path attribute must be a valid device node") + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue5Info), ErrorMatches, "iio path attribute must be a valid device node") + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue6Info), ErrorMatches, "iio path attribute must be a valid device node") + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue7Info), ErrorMatches, "iio slot must have a path attribute") + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue8Info), ErrorMatches, "iio slot must have a path attribute") } func (s *IioInterfaceSuite) TestConnectedPlugUDevSnippets(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPort1, nil, s.testUDev1, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 1) - c.Assert(spec.Snippets()[0], Equals, `KERNEL=="iio:device1", TAG+="snap_client-snap_app-accessing-1-port"`) + c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPort1, s.testUDev1), IsNil) + c.Assert(spec.Snippets(), HasLen, 2) + c.Assert(spec.Snippets(), testutil.Contains, `# iio +KERNEL=="iio:device1", TAG+="snap_client-snap_app-accessing-1-port"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_client-snap_app-accessing-1-port", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_client-snap_app-accessing-1-port $devpath $major:$minor"`) } func (s *IioInterfaceSuite) TestConnectedPlugAppArmorSnippets(c *C) { @@ -171,11 +201,11 @@ /sys/devices/**/iio:device1/** rwk, ` apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.testPlugPort1, nil, s.testUDev1, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.testPlugPort1, s.testUDev1) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.client-snap.app-accessing-1-port"}) snippet := apparmorSpec.SnippetForTag("snap.client-snap.app-accessing-1-port") - c.Assert(snippet, DeepEquals, expectedSnippet1, Commentf("\nexpected:\n%s\nfound:\n%s", expectedSnippet1, snippet)) + c.Assert(snippet, Equals, expectedSnippet1) } func (s *IioInterfaceSuite) TestAutoConnect(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/io_ports_control_test.go snapd-2.31.1+17.10/interfaces/builtin/io_ports_control_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/io_ports_control_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/io_ports_control_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -32,9 +32,11 @@ ) type ioPortsControlInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&ioPortsControlInterfaceSuite{ @@ -54,8 +56,8 @@ ` func (s *ioPortsControlInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, ioPortsControlConsumerYaml, nil, "io-ports-control") - s.slot = MockSlot(c, ioPortsControlCoreYaml, nil, "io-ports-control") + s.plug, s.plugInfo = MockConnectedPlug(c, ioPortsControlConsumerYaml, nil, "io-ports-control") + s.slot, s.slotInfo = MockConnectedSlot(c, ioPortsControlCoreYaml, nil, "io-ports-control") } func (s *ioPortsControlInterfaceSuite) TestName(c *C) { @@ -63,39 +65,41 @@ } func (s *ioPortsControlInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "io-ports-control", Interface: "io-ports-control", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "io-ports-control slots are reserved for the core snap") } func (s *ioPortsControlInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *ioPortsControlInterfaceSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, "/dev/port rw,") } func (s *ioPortsControlInterfaceSuite) TestSecCompSpec(c *C) { spec := &seccomp.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, "ioperm\n") } func (s *ioPortsControlInterfaceSuite) TestUDevSpec(c *C) { udevSpec := &udev.Specification{} - c.Assert(udevSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(udevSpec.Snippets(), HasLen, 1) - c.Assert(udevSpec.Snippets()[0], Equals, `KERNEL=="port", TAG+="snap_consumer_app"`) + c.Assert(udevSpec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(udevSpec.Snippets(), HasLen, 2) + c.Assert(udevSpec.Snippets(), testutil.Contains, `# io-ports-control +KERNEL=="port", TAG+="snap_consumer_app"`) + c.Assert(udevSpec.Snippets(), testutil.Contains, `TAG=="snap_consumer_app", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_consumer_app $devpath $major:$minor"`) } func (s *ioPortsControlInterfaceSuite) TestStaticInfo(c *C) { @@ -107,7 +111,8 @@ } func (s *ioPortsControlInterfaceSuite) TestAutoConnect(c *C) { - c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) + // FIXME: fix AutoConnect methods to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) } func (s *ioPortsControlInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/joystick.go snapd-2.31.1+17.10/interfaces/builtin/joystick.go --- snapd-2.29.4.2+17.10/interfaces/builtin/joystick.go 2017-11-30 07:12:26.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/joystick.go 2018-01-24 20:02:44.000000000 +0000 @@ -36,6 +36,12 @@ # only js0-js31 is valid so limit the /dev and udev entries to those devices. /dev/input/js{[0-9],[12][0-9],3[01]} rw, /run/udev/data/c13:{[0-9],[12][0-9],3[01]} r, + +# Allow reading for supported event reports for all input devices. See +# https://www.kernel.org/doc/Documentation/input/event-codes.txt +# FIXME: this is a very minor information leak and snapd should instead query +# udev for the specific accesses associated with the above devices. +/sys/devices/**/input[0-9]*/capabilities/* r, ` var joystickConnectedPlugUDev = []string{`KERNEL=="js[0-9]*"`} diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/joystick_test.go snapd-2.31.1+17.10/interfaces/builtin/joystick_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/joystick_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/joystick_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type JoystickInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&JoystickInterfaceSuite{ @@ -53,8 +55,8 @@ ` func (s *JoystickInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, joystickConsumerYaml, nil, "joystick") - s.slot = MockSlot(c, joystickCoreYaml, nil, "joystick") + s.plug, s.plugInfo = MockConnectedPlug(c, joystickConsumerYaml, nil, "joystick") + s.slot, s.slotInfo = MockConnectedSlot(c, joystickCoreYaml, nil, "joystick") } func (s *JoystickInterfaceSuite) TestName(c *C) { @@ -62,32 +64,34 @@ } func (s *JoystickInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "joystick", Interface: "joystick", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "joystick slots are reserved for the core snap") } func (s *JoystickInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *JoystickInterfaceSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, `/dev/input/js{[0-9],[12][0-9],3[01]} rw,`) } func (s *JoystickInterfaceSuite) TestUDevSpec(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 1) - c.Assert(spec.Snippets()[0], testutil.Contains, `KERNEL=="js[0-9]*", TAG+="snap_consumer_app"`) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Snippets(), HasLen, 2) + c.Assert(spec.Snippets(), testutil.Contains, `# joystick +KERNEL=="js[0-9]*", TAG+="snap_consumer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_consumer_app", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_consumer_app $devpath $major:$minor"`) } func (s *JoystickInterfaceSuite) TestStaticInfo(c *C) { @@ -99,7 +103,8 @@ } func (s *JoystickInterfaceSuite) TestAutoConnect(c *C) { - c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) + // FIXME: fix AutoConnect methods to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) } func (s *JoystickInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/kernel_module_control_test.go snapd-2.31.1+17.10/interfaces/builtin/kernel_module_control_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/kernel_module_control_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/kernel_module_control_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -32,9 +32,11 @@ ) type KernelModuleControlInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&KernelModuleControlInterfaceSuite{ @@ -54,8 +56,8 @@ ` func (s *KernelModuleControlInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, kernelmodctlConsumerYaml, nil, "kernel-module-control") - s.slot = MockSlot(c, kernelmodctlCoreYaml, nil, "kernel-module-control") + s.plug, s.plugInfo = MockConnectedPlug(c, kernelmodctlConsumerYaml, nil, "kernel-module-control") + s.slot, s.slotInfo = MockConnectedSlot(c, kernelmodctlCoreYaml, nil, "kernel-module-control") } func (s *KernelModuleControlInterfaceSuite) TestName(c *C) { @@ -63,39 +65,41 @@ } func (s *KernelModuleControlInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "kernel-module-control", Interface: "kernel-module-control", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "kernel-module-control slots are reserved for the core snap") } func (s *KernelModuleControlInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *KernelModuleControlInterfaceSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, "capability sys_module,") } func (s *KernelModuleControlInterfaceSuite) TestSecCompSpec(c *C) { spec := &seccomp.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, "finit_module\n") } func (s *KernelModuleControlInterfaceSuite) TestUDevSpec(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 1) - c.Assert(spec.Snippets()[0], testutil.Contains, `KERNEL=="mem", TAG+="snap_consumer_app"`) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Snippets(), HasLen, 2) + c.Assert(spec.Snippets(), testutil.Contains, `# kernel-module-control +KERNEL=="mem", TAG+="snap_consumer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_consumer_app", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_consumer_app $devpath $major:$minor"`) } func (s *KernelModuleControlInterfaceSuite) TestStaticInfo(c *C) { @@ -107,7 +111,8 @@ } func (s *KernelModuleControlInterfaceSuite) TestAutoConnect(c *C) { - c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) + // FIXME: fix AutoConnect methods to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) } func (s *KernelModuleControlInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/kubernetes_support_test.go snapd-2.31.1+17.10/interfaces/builtin/kubernetes_support_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/kubernetes_support_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/kubernetes_support_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -32,9 +32,11 @@ ) type KubernetesSupportInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } const k8sMockPlugSnapInfoYaml = `name: other @@ -50,15 +52,15 @@ }) func (s *KubernetesSupportInterfaceSuite) SetUpTest(c *C) { - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "kubernetes-support", - Interface: "kubernetes-support", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "kubernetes-support", + Interface: "kubernetes-support", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, k8sMockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["kubernetes-support"]} + s.plugInfo = plugSnap.Plugs["kubernetes-support"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *KubernetesSupportInterfaceSuite) TestName(c *C) { @@ -66,23 +68,23 @@ } func (s *KubernetesSupportInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "kubernetes-support", Interface: "kubernetes-support", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "kubernetes-support slots are reserved for the core snap") } func (s *KubernetesSupportInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *KubernetesSupportInterfaceSuite) TestUsedSecuritySystems(c *C) { kmodSpec := &kmod.Specification{} - err := kmodSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := kmodSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(kmodSpec.Modules(), DeepEquals, map[string]bool{ "llc": true, @@ -90,7 +92,7 @@ }) apparmorSpec := &apparmor.Specification{} - err = apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err = apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) c.Check(apparmorSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "# Allow reading the state of modules kubernetes needs\n") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/kvm_test.go snapd-2.31.1+17.10/interfaces/builtin/kvm_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/kvm_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/kvm_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type kvmInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&kvmInterfaceSuite{ @@ -53,8 +55,8 @@ ` func (s *kvmInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, kvmConsumerYaml, nil, "kvm") - s.slot = MockSlot(c, kvmCoreYaml, nil, "kvm") + s.plug, s.plugInfo = MockConnectedPlug(c, kvmConsumerYaml, nil, "kvm") + s.slot, s.slotInfo = MockConnectedSlot(c, kvmCoreYaml, nil, "kvm") } func (s *kvmInterfaceSuite) TestName(c *C) { @@ -62,23 +64,23 @@ } func (s *kvmInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "kvm", Interface: "kvm", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "kvm slots are reserved for the core snap") } func (s *kvmInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *kvmInterfaceSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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"), Equals, ` # Description: Allow write access to kvm. @@ -90,9 +92,11 @@ func (s *kvmInterfaceSuite) TestUDevSpec(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 1) - c.Assert(spec.Snippets()[0], Equals, `KERNEL=="kvm", TAG+="snap_consumer_app"`) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Snippets(), HasLen, 2) + c.Assert(spec.Snippets()[0], Equals, `# kvm +KERNEL=="kvm", TAG+="snap_consumer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_consumer_app", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_consumer_app $devpath $major:$minor"`) } func (s *kvmInterfaceSuite) TestStaticInfo(c *C) { @@ -104,7 +108,8 @@ } func (s *kvmInterfaceSuite) TestAutoConnect(c *C) { - c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) + // FIXME: fix AutoConnect methods to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) } func (s *kvmInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/libvirt_test.go snapd-2.31.1+17.10/interfaces/builtin/libvirt_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/libvirt_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/libvirt_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -29,26 +29,22 @@ ) type LibvirtInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + plugInfo *snap.PlugInfo } var _ = Suite(&LibvirtInterfaceSuite{ iface: builtin.MustInterface("libvirt"), - slot: &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "libvirt"}, - Name: "libvirt", - Interface: "libvirt", - }, + slotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "libvirt"}, + Name: "libvirt", + Interface: "libvirt", }, - plug: &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{SuggestedName: "other"}, - Name: "libvirt", - Interface: "libvirt", - }, + plugInfo: &snap.PlugInfo{ + Snap: &snap.Info{SuggestedName: "other"}, + Name: "libvirt", + Interface: "libvirt", }, }) @@ -57,11 +53,11 @@ } func (s *LibvirtInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), ErrorMatches, ".*libvirt slots are reserved for the core snap.*") + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), ErrorMatches, ".*libvirt slots are reserved for the core snap.*") } func (s *LibvirtInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *LibvirtInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/locale_control_test.go snapd-2.31.1+17.10/interfaces/builtin/locale_control_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/locale_control_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/locale_control_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type LocaleControlInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&LocaleControlInterfaceSuite{ @@ -49,14 +51,14 @@ plugs: [locale-control] ` snapInfo := snaptest.MockInfo(c, mockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: snapInfo.Plugs["locale-control"]} - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "locale-control", - Interface: "locale-control", - }, + s.plugInfo = snapInfo.Plugs["locale-control"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "locale-control", + Interface: "locale-control", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) } func (s *LocaleControlInterfaceSuite) TestName(c *C) { @@ -64,24 +66,24 @@ } func (s *LocaleControlInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "locale-control", Interface: "locale-control", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "locale-control slots are reserved for the core snap") } func (s *LocaleControlInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *LocaleControlInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) aasnippets := apparmorSpec.Snippets() c.Assert(aasnippets, HasLen, 1) diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/location_control.go snapd-2.31.1+17.10/interfaces/builtin/location_control.go --- snapd-2.29.4.2+17.10/interfaces/builtin/location_control.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/location_control.go 2018-01-24 20:02:44.000000000 +0000 @@ -25,6 +25,7 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/dbus" + "github.com/snapcore/snapd/snap" ) const locationControlSummary = `allows operating as the location service` @@ -214,7 +215,7 @@ } } -func (iface *locationControlInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *locationControlInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###SLOT_SECURITY_TAGS###" new := slotAppLabelExpr(slot) snippet := strings.Replace(locationControlConnectedPlugAppArmor, old, new, -1) @@ -222,22 +223,22 @@ return nil } -func (iface *locationControlInterface) DBusConnectedPlug(spec *dbus.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *locationControlInterface) DBusConnectedPlug(spec *dbus.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { spec.AddSnippet(locationControlConnectedPlugDBus) return nil } -func (iface *locationControlInterface) DBusPermanentSlot(spec *dbus.Specification, slot *interfaces.Slot) error { +func (iface *locationControlInterface) DBusPermanentSlot(spec *dbus.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(locationControlPermanentSlotDBus) return nil } -func (iface *locationControlInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { +func (iface *locationControlInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(locationControlPermanentSlotAppArmor) return nil } -func (iface *locationControlInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *locationControlInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###PLUG_SECURITY_TAGS###" new := plugAppLabelExpr(plug) snippet := strings.Replace(locationControlConnectedSlotAppArmor, old, new, -1) diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/location_control_test.go snapd-2.31.1+17.10/interfaces/builtin/location_control_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/location_control_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/location_control_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type LocationControlInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&LocationControlInterfaceSuite{ @@ -62,9 +64,11 @@ slots: [location] ` snapInfo := snaptest.MockInfo(c, plugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: snapInfo.Plugs["location-client"]} + s.plugInfo = snapInfo.Plugs["location-client"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) snapInfo = snaptest.MockInfo(c, slotSnapInfoYaml, nil) - s.slot = &interfaces.Slot{SlotInfo: snapInfo.Slots["location"]} + s.slotInfo = snapInfo.Slots["location"] + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) } func (s *LocationControlInterfaceSuite) TestName(c *C) { @@ -75,20 +79,18 @@ func (s *LocationControlInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelAll(c *C) { app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "location", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - Name: "location", - Interface: "location", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - } + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, + Name: "location", + Interface: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.location-consumer.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.location-consumer.app"), testutil.Contains, `peer=(label="snap.location.*"),`) @@ -99,20 +101,18 @@ app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} app3 := &snap.AppInfo{Name: "app3"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "location", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, - }, - Name: "location", - Interface: "location", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - } + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, + }, + Name: "location", + Interface: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.location-consumer.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.location-consumer.app"), testutil.Contains, `peer=(label="snap.location.{app1,app2}"),`) @@ -121,20 +121,18 @@ // The label uses short form when exactly one app is bound to the location slot func (s *LocationControlInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelOne(c *C) { app := &snap.AppInfo{Name: "app"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "location", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - Name: "location", - Interface: "location", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - } + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "location", + Apps: map[string]*snap.AppInfo{"app": app}, + }, + Name: "location", + Interface: "location", + Apps: map[string]*snap.AppInfo{"app": app}, + }, nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.location-consumer.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.location-consumer.app"), testutil.Contains, `peer=(label="snap.location.app"),`) @@ -144,20 +142,18 @@ func (s *LocationControlInterfaceSuite) TestConnectedSlotSnippetUsesPlugLabelAll(c *C) { app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} - plug := &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{ - SuggestedName: "location", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - Name: "location", - Interface: "location", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - } + plug := interfaces.NewConnectedPlug(&snap.PlugInfo{ + Snap: &snap.Info{ + SuggestedName: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, + Name: "location", + Interface: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedSlot(s.iface, plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedSlot(s.iface, plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.location.app2"}) c.Assert(apparmorSpec.SnippetForTag("snap.location.app2"), testutil.Contains, `peer=(label="snap.location.*"),`) @@ -168,20 +164,18 @@ app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} app3 := &snap.AppInfo{Name: "app3"} - plug := &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{ - SuggestedName: "location", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, - }, - Name: "location", - Interface: "location", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - } + plug := interfaces.NewConnectedPlug(&snap.PlugInfo{ + Snap: &snap.Info{ + SuggestedName: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, + }, + Name: "location", + Interface: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedSlot(s.iface, plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedSlot(s.iface, plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.location.app2"}) c.Assert(apparmorSpec.SnippetForTag("snap.location.app2"), testutil.Contains, `peer=(label="snap.location.{app1,app2}"),`) @@ -190,20 +184,18 @@ // The label uses short form when exactly one app is bound to the location plug func (s *LocationControlInterfaceSuite) TestConnectedSlotSnippetUsesPlugLabelOne(c *C) { app := &snap.AppInfo{Name: "app"} - plug := &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{ - SuggestedName: "location", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - Name: "location", - Interface: "location", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - } + plug := interfaces.NewConnectedPlug(&snap.PlugInfo{ + Snap: &snap.Info{ + SuggestedName: "location", + Apps: map[string]*snap.AppInfo{"app": app}, + }, + Name: "location", + Interface: "location", + Apps: map[string]*snap.AppInfo{"app": app}, + }, nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedSlot(s.iface, plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedSlot(s.iface, plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.location.app2"}) c.Assert(apparmorSpec.SnippetForTag("snap.location.app2"), testutil.Contains, `peer=(label="snap.location.app"),`) diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/location_observe.go snapd-2.31.1+17.10/interfaces/builtin/location_observe.go --- snapd-2.29.4.2+17.10/interfaces/builtin/location_observe.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/location_observe.go 2018-01-24 20:02:44.000000000 +0000 @@ -25,6 +25,7 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/dbus" + "github.com/snapcore/snapd/snap" ) const locationObserveSummary = `allows access to the current physical location` @@ -268,17 +269,17 @@ } } -func (iface *locationObserveInterface) DBusConnectedPlug(spec *dbus.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *locationObserveInterface) DBusConnectedPlug(spec *dbus.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { spec.AddSnippet(locationObserveConnectedPlugDBus) return nil } -func (iface *locationObserveInterface) DBusPermanentSlot(spec *dbus.Specification, slot *interfaces.Slot) error { +func (iface *locationObserveInterface) DBusPermanentSlot(spec *dbus.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(locationObservePermanentSlotDBus) return nil } -func (iface *locationObserveInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *locationObserveInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###SLOT_SECURITY_TAGS###" new := slotAppLabelExpr(slot) snippet := strings.Replace(locationObserveConnectedPlugAppArmor, old, new, -1) @@ -286,12 +287,12 @@ return nil } -func (iface *locationObserveInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { +func (iface *locationObserveInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(locationObservePermanentSlotAppArmor) return nil } -func (iface *locationObserveInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *locationObserveInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###PLUG_SECURITY_TAGS###" new := plugAppLabelExpr(plug) snippet := strings.Replace(locationObserveConnectedSlotAppArmor, old, new, -1) diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/location_observe_test.go snapd-2.31.1+17.10/interfaces/builtin/location_observe_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/location_observe_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/location_observe_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type LocationObserveInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&LocationObserveInterfaceSuite{ @@ -56,9 +58,11 @@ slots: [location-observe] ` snapInfo := snaptest.MockInfo(c, mockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: snapInfo.Plugs["location-observe"]} + s.plugInfo = snapInfo.Plugs["location-observe"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) snapInfo = snaptest.MockInfo(c, mockSlotSnapInfoYaml, nil) - s.slot = &interfaces.Slot{SlotInfo: snapInfo.Slots["location-observe"]} + s.slotInfo = snapInfo.Slots["location-observe"] + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) } func (s *LocationObserveInterfaceSuite) TestName(c *C) { @@ -69,20 +73,18 @@ func (s *LocationObserveInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelAll(c *C) { app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "location", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - Name: "location", - Interface: "location", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - } + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, + Name: "location", + Interface: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `peer=(label="snap.location.*"),`) @@ -93,20 +95,18 @@ app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} app3 := &snap.AppInfo{Name: "app3"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "location", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, - }, - Name: "location", - Interface: "location", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - } + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, + }, + Name: "location", + Interface: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `peer=(label="snap.location.{app1,app2}"),`) @@ -115,20 +115,18 @@ // The label uses short form when exactly one app is bound to the location slot func (s *LocationObserveInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelOne(c *C) { app := &snap.AppInfo{Name: "app"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "location", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - Name: "location", - Interface: "location", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - } + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "location", + Apps: map[string]*snap.AppInfo{"app": app}, + }, + Name: "location", + Interface: "location", + Apps: map[string]*snap.AppInfo{"app": app}, + }, nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `peer=(label="snap.location.app"),`) @@ -138,20 +136,18 @@ func (s *LocationObserveInterfaceSuite) TestConnectedSlotSnippetUsesPlugLabelAll(c *C) { app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} - plug := &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{ - SuggestedName: "location", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - Name: "location", - Interface: "location", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - } + plug := interfaces.NewConnectedPlug(&snap.PlugInfo{ + Snap: &snap.Info{ + SuggestedName: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, + Name: "location", + Interface: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedSlot(s.iface, plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedSlot(s.iface, plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.location.app2"}) c.Assert(apparmorSpec.SnippetForTag("snap.location.app2"), testutil.Contains, `peer=(label="snap.location.*"),`) @@ -162,20 +158,18 @@ app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} app3 := &snap.AppInfo{Name: "app3"} - plug := &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{ - SuggestedName: "location", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, - }, - Name: "location", - Interface: "location", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - } + plug := interfaces.NewConnectedPlug(&snap.PlugInfo{ + Snap: &snap.Info{ + SuggestedName: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, + }, + Name: "location", + Interface: "location", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedSlot(s.iface, plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedSlot(s.iface, plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.location.app2"}) c.Assert(apparmorSpec.SnippetForTag("snap.location.app2"), testutil.Contains, `peer=(label="snap.location.{app1,app2}"),`) @@ -184,20 +178,18 @@ // The label uses short form when exactly one app is bound to the location plug func (s *LocationObserveInterfaceSuite) TestConnectedSlotSnippetUsesPlugLabelOne(c *C) { app := &snap.AppInfo{Name: "app"} - plug := &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{ - SuggestedName: "location", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - Name: "location", - Interface: "location", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - } + plug := interfaces.NewConnectedPlug(&snap.PlugInfo{ + Snap: &snap.Info{ + SuggestedName: "location", + Apps: map[string]*snap.AppInfo{"app": app}, + }, + Name: "location", + Interface: "location", + Apps: map[string]*snap.AppInfo{"app": app}, + }, nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedSlot(s.iface, plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedSlot(s.iface, plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.location.app2"}) c.Assert(apparmorSpec.SnippetForTag("snap.location.app2"), testutil.Contains, `peer=(label="snap.location.app"),`) diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/log_observe_test.go snapd-2.31.1+17.10/interfaces/builtin/log_observe_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/log_observe_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/log_observe_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type LogObserveInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&LogObserveInterfaceSuite{ @@ -48,15 +50,15 @@ command: foo plugs: [log-observe] ` - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "log-observe", - Interface: "log-observe", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "log-observe", + Interface: "log-observe", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) snapInfo := snaptest.MockInfo(c, mockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: snapInfo.Plugs["log-observe"]} + s.plugInfo = snapInfo.Plugs["log-observe"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *LogObserveInterfaceSuite) TestName(c *C) { @@ -64,24 +66,24 @@ } func (s *LogObserveInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "log-observe", Interface: "log-observe", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "log-observe slots are reserved for the core snap") } func (s *LogObserveInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *LogObserveInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, "/var/log/") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/lxd.go snapd-2.31.1+17.10/interfaces/builtin/lxd.go --- snapd-2.29.4.2+17.10/interfaces/builtin/lxd.go 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/lxd.go 2018-01-24 20:02:44.000000000 +0000 @@ -39,7 +39,6 @@ # Description: allow access to the LXD daemon socket. This gives privileged # access to the system via LXD's socket API. -shutdown socket AF_NETLINK - NETLINK_GENERIC ` diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/lxd_support.go snapd-2.31.1+17.10/interfaces/builtin/lxd_support.go --- snapd-2.29.4.2+17.10/interfaces/builtin/lxd_support.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/lxd_support.go 2017-12-01 15:51:55.000000000 +0000 @@ -41,6 +41,9 @@ # to its containers. This gives device ownership to connected snaps. @{PROC}/**/attr/current r, /usr/sbin/aa-exec ux, + +# Allow discovering the os-release of the host +/var/lib/snapd/hostfs/{etc,usr/lib}/os-release r, ` const lxdSupportConnectedPlugSecComp = ` diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/lxd_support_test.go snapd-2.31.1+17.10/interfaces/builtin/lxd_support_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/lxd_support_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/lxd_support_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type LxdSupportInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&LxdSupportInterfaceSuite{ @@ -53,8 +55,8 @@ ` func (s *LxdSupportInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, lxdSupportConsumerYaml, nil, "lxd-support") - s.slot = MockSlot(c, lxdSupportCoreYaml, nil, "lxd-support") + s.plug, s.plugInfo = MockConnectedPlug(c, lxdSupportConsumerYaml, nil, "lxd-support") + s.slot, s.slotInfo = MockConnectedSlot(c, lxdSupportCoreYaml, nil, "lxd-support") } func (s *LxdSupportInterfaceSuite) TestName(c *C) { @@ -62,31 +64,31 @@ } func (s *LxdSupportInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "lxd-support", Interface: "lxd-support", - }} + } - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "lxd-support slots are reserved for the core snap") } func (s *LxdSupportInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *LxdSupportInterfaceSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, "/usr/sbin/aa-exec ux,\n") } func (s *LxdSupportInterfaceSuite) TestSecCompSpec(c *C) { spec := &seccomp.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, "@unrestricted\n") } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/lxd_test.go snapd-2.31.1+17.10/interfaces/builtin/lxd_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/lxd_test.go 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/lxd_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type LxdInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&LxdInterfaceSuite{ @@ -53,8 +55,8 @@ ` func (s *LxdInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, lxdConsumerYaml, nil, "lxd") - s.slot = MockSlot(c, lxdCoreYaml, nil, "lxd") + s.plug, s.plugInfo = MockConnectedPlug(c, lxdConsumerYaml, nil, "lxd") + s.slot, s.slotInfo = MockConnectedSlot(c, lxdCoreYaml, nil, "lxd") } func (s *LxdInterfaceSuite) TestName(c *C) { @@ -62,32 +64,32 @@ } func (s *LxdInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "lxd", Interface: "lxd", - }} + } - c.Assert(slot.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), IsNil) } func (s *LxdInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *LxdInterfaceSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, "/var/snap/lxd/common/lxd/unix.socket rw,\n") } func (s *LxdInterfaceSuite) TestSecCompSpec(c *C) { spec := &seccomp.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) - c.Check(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "shutdown\n") + c.Check(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "socket AF_NETLINK - NETLINK_GENERIC\n") } func (s *LxdInterfaceSuite) TestStaticInfo(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/maliit.go snapd-2.31.1+17.10/interfaces/builtin/maliit.go --- snapd-2.29.4.2+17.10/interfaces/builtin/maliit.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/maliit.go 2018-01-24 20:02:44.000000000 +0000 @@ -25,6 +25,7 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/seccomp" + "github.com/snapcore/snapd/snap" ) const maliitSummary = `allows operating as the Maliit service` @@ -136,7 +137,7 @@ } } -func (iface *maliitInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *maliitInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###SLOT_SECURITY_TAGS###" new := slotAppLabelExpr(slot) snippet := strings.Replace(maliitConnectedPlugAppArmor, old, new, -1) @@ -144,17 +145,17 @@ return nil } -func (iface *maliitInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *interfaces.Slot) error { +func (iface *maliitInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(maliitPermanentSlotSecComp) return nil } -func (iface *maliitInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { +func (iface *maliitInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(maliitPermanentSlotAppArmor) return nil } -func (iface *maliitInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *maliitInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###PLUG_SECURITY_TAGS###" new := plugAppLabelExpr(plug) snippet := strings.Replace(maliitConnectedSlotAppArmor, old, new, -1) diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/maliit_test.go snapd-2.31.1+17.10/interfaces/builtin/maliit_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/maliit_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/maliit_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -33,9 +33,11 @@ ) type MaliitInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&MaliitInterfaceSuite{ @@ -58,9 +60,11 @@ slots: [maliit] ` slotSnap := snaptest.MockInfo(c, mockCoreSlotSnapInfoYaml, nil) - s.slot = &interfaces.Slot{SlotInfo: slotSnap.Slots["maliit"]} + s.slotInfo = slotSnap.Slots["maliit"] + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, mockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["maliit"]} + s.plugInfo = plugSnap.Plugs["maliit"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *MaliitInterfaceSuite) TestName(c *C) { @@ -71,20 +75,18 @@ func (s *MaliitInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelAll(c *C) { app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "maliit", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - Name: "maliit", - Interface: "maliit", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - } + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "maliit", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, + Name: "maliit", + Interface: "maliit", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `peer=(label="snap.maliit.*"),`) @@ -95,20 +97,18 @@ app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} app3 := &snap.AppInfo{Name: "app3"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "maliit", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, - }, - Name: "maliit", - Interface: "maliit", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - } + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "maliit", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, + }, + Name: "maliit", + Interface: "maliit", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `peer=(label="snap.maliit.{app1,app2}"),`) @@ -116,7 +116,7 @@ func (s *MaliitInterfaceSuite) TestConnectedPlugSecComp(c *C) { seccompSpec := &seccomp.Specification{} - err := seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := seccompSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), HasLen, 0) } @@ -124,20 +124,18 @@ // The label uses short form when exactly one app is bound to the maliit slot func (s *MaliitInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelOne(c *C) { app := &snap.AppInfo{Name: "app"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "maliit", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - Name: "maliit", - Interface: "maliit", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - } + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "maliit", + Apps: map[string]*snap.AppInfo{"app": app}, + }, + Name: "maliit", + Interface: "maliit", + Apps: map[string]*snap.AppInfo{"app": app}, + }, nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `peer=(label="snap.maliit.app"),`) @@ -147,20 +145,18 @@ func (s *MaliitInterfaceSuite) TestConnectedSlotSnippetUsesPlugLabelAll(c *C) { app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} - plug := &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{ - SuggestedName: "maliit", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - Name: "maliit", - Interface: "maliit", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - } + plug := interfaces.NewConnectedPlug(&snap.PlugInfo{ + Snap: &snap.Info{ + SuggestedName: "maliit", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, + Name: "maliit", + Interface: "maliit", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedSlot(s.iface, plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedSlot(s.iface, plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.maliit.maliit"}) c.Assert(apparmorSpec.SnippetForTag("snap.maliit.maliit"), testutil.Contains, `peer=(label="snap.maliit.*"),`) @@ -171,21 +167,19 @@ app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} app3 := &snap.AppInfo{Name: "app3"} - plug := &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{ - SuggestedName: "maliit", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, - }, - Name: "maliit", - Interface: "maliit", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - } + plug := interfaces.NewConnectedPlug(&snap.PlugInfo{ + Snap: &snap.Info{ + SuggestedName: "maliit", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, + }, + Name: "maliit", + Interface: "maliit", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) apparmorSpec := &apparmor.Specification{} c.Assert(s.slot, NotNil) - err := apparmorSpec.AddConnectedSlot(s.iface, plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedSlot(s.iface, plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.maliit.maliit"}) c.Assert(apparmorSpec.SnippetForTag("snap.maliit.maliit"), testutil.Contains, `peer=(label="snap.maliit.{app1,app2}"),`) @@ -194,20 +188,18 @@ // The label uses short form when exactly one app is bound to the maliit plug func (s *MaliitInterfaceSuite) TestConnectedSlotSnippetUsesPlugLabelOne(c *C) { app := &snap.AppInfo{Name: "app"} - plug := &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{ - SuggestedName: "maliit", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - Name: "maliit", - Interface: "maliit", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - } + plug := interfaces.NewConnectedPlug(&snap.PlugInfo{ + Snap: &snap.Info{ + SuggestedName: "maliit", + Apps: map[string]*snap.AppInfo{"app": app}, + }, + Name: "maliit", + Interface: "maliit", + Apps: map[string]*snap.AppInfo{"app": app}, + }, nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedSlot(s.iface, plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedSlot(s.iface, plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.maliit.maliit"}) c.Assert(apparmorSpec.SnippetForTag("snap.maliit.maliit"), testutil.Contains, `peer=(label="snap.maliit.app"),`) @@ -215,7 +207,7 @@ func (s *MaliitInterfaceSuite) TestPermanentSlotSecComp(c *C) { seccompSpec := &seccomp.Specification{} - err := seccompSpec.AddPermanentSlot(s.iface, s.slot) + err := seccompSpec.AddPermanentSlot(s.iface, s.slotInfo) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.maliit.maliit"}) c.Assert(seccompSpec.SnippetForTag("snap.maliit.maliit"), testutil.Contains, "listen\n") @@ -225,7 +217,7 @@ release.OnClassic = false apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) snippet := apparmorSpec.SnippetForTag("snap.other.app") @@ -237,7 +229,7 @@ func (s *MaliitInterfaceSuite) TestPermanentSlotSnippetAppArmor(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddPermanentSlot(s.iface, s.slot) + err := apparmorSpec.AddPermanentSlot(s.iface, s.slotInfo) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.maliit.maliit"}) c.Assert(apparmorSpec.SnippetForTag("snap.maliit.maliit"), testutil.Contains, "org.maliit.Server.Address") @@ -245,7 +237,7 @@ func (s *MaliitInterfaceSuite) TestPermanentSlotSnippetSecComp(c *C) { seccompSpec := &seccomp.Specification{} - err := seccompSpec.AddPermanentSlot(s.iface, s.slot) + err := seccompSpec.AddPermanentSlot(s.iface, s.slotInfo) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.maliit.maliit"}) c.Check(seccompSpec.SnippetForTag("snap.maliit.maliit"), testutil.Contains, "listen\n") @@ -253,7 +245,7 @@ func (s *MaliitInterfaceSuite) TestConnectedSlotSnippetAppArmor(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedSlot(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedSlot(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.maliit.maliit"}) c.Assert(apparmorSpec.SnippetForTag("snap.maliit.maliit"), testutil.Contains, "peer=(label=\"snap.other.app\"") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/media_hub.go snapd-2.31.1+17.10/interfaces/builtin/media_hub.go --- snapd-2.29.4.2+17.10/interfaces/builtin/media_hub.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/media_hub.go 2018-01-24 20:02:44.000000000 +0000 @@ -25,6 +25,7 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/seccomp" + "github.com/snapcore/snapd/snap" ) const mediaHubSummary = `allows operating as the media-hub service` @@ -169,26 +170,26 @@ } } -func (iface *mediaHubInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *mediaHubInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###SLOT_SECURITY_TAGS###" new := slotAppLabelExpr(slot) spec.AddSnippet(strings.Replace(mediaHubConnectedPlugAppArmor, old, new, -1)) return nil } -func (iface *mediaHubInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { +func (iface *mediaHubInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(mediaHubPermanentSlotAppArmor) return nil } -func (iface *mediaHubInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *mediaHubInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###PLUG_SECURITY_TAGS###" new := plugAppLabelExpr(plug) spec.AddSnippet(strings.Replace(mediaHubConnectedSlotAppArmor, old, new, -1)) return nil } -func (iface *mediaHubInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *interfaces.Slot) error { +func (iface *mediaHubInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(mediaHubPermanentSlotSecComp) return nil } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/media_hub_test.go snapd-2.31.1+17.10/interfaces/builtin/media_hub_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/media_hub_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/media_hub_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -33,9 +33,11 @@ ) type MediaHubInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&MediaHubInterfaceSuite{ @@ -61,9 +63,11 @@ slots: [media-hub] ` snapInfo := snaptest.MockInfo(c, mockSlotSnapInfoYaml, nil) - s.slot = &interfaces.Slot{SlotInfo: snapInfo.Slots["media-hub"]} + s.slotInfo = snapInfo.Slots["media-hub"] + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) snapInfo = snaptest.MockInfo(c, mockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: snapInfo.Plugs["media-hub"]} + s.plugInfo = snapInfo.Plugs["media-hub"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *MediaHubInterfaceSuite) TestName(c *C) { @@ -74,22 +78,20 @@ func (s *MediaHubInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelAll(c *C) { app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "media-hub", - Apps: map[string]*snap.AppInfo{"app1": app1, - "app2": app2}, - }, - Name: "media-hub", - Interface: "media-hub", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "media-hub", + Apps: map[string]*snap.AppInfo{"app1": app1, + "app2": app2}, }, - } + Name: "media-hub", + Interface: "media-hub", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) release.OnClassic = false apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, @@ -101,23 +103,21 @@ app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} app3 := &snap.AppInfo{Name: "app3"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "media-hub", - Apps: map[string]*snap.AppInfo{"app1": app1, - "app2": app2, - "app3": app3}, - }, - Name: "media-hub", - Interface: "media-hub", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "media-hub", + Apps: map[string]*snap.AppInfo{"app1": app1, + "app2": app2, + "app3": app3}, }, - } + Name: "media-hub", + Interface: "media-hub", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) release.OnClassic = false apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, @@ -127,21 +127,19 @@ // The label uses short form when exactly one app is bound to the media-hub slot func (s *MediaHubInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelOne(c *C) { app := &snap.AppInfo{Name: "app"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "media-hub", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - Name: "media-hub", - Interface: "media-hub", - Apps: map[string]*snap.AppInfo{"app": app}, + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "media-hub", + Apps: map[string]*snap.AppInfo{"app": app}, }, - } + Name: "media-hub", + Interface: "media-hub", + Apps: map[string]*snap.AppInfo{"app": app}, + }, nil) release.OnClassic = false apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, @@ -151,7 +149,7 @@ func (s *MediaHubInterfaceSuite) TestConnectedPlugSnippetAppArmor(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), Not(IsNil)) @@ -164,7 +162,7 @@ func (s *MediaHubInterfaceSuite) TestPermanentSlotSnippetAppArmor(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddPermanentSlot(s.iface, s.slot) + err := apparmorSpec.AddPermanentSlot(s.iface, s.slotInfo) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.media-hub.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.media-hub.app"), Not(IsNil)) @@ -177,7 +175,7 @@ func (s *MediaHubInterfaceSuite) TestConnectedSlotSnippetAppArmor(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedSlot(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedSlot(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.media-hub.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.media-hub.app"), Not(IsNil)) @@ -187,7 +185,7 @@ func (s *MediaHubInterfaceSuite) TestPermanentSlotSnippetSecComp(c *C) { spec := &seccomp.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.slot), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.slotInfo), IsNil) c.Assert(spec.SnippetForTag("snap.media-hub.app"), testutil.Contains, "bind\n") } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/mir.go snapd-2.31.1+17.10/interfaces/builtin/mir.go --- snapd-2.29.4.2+17.10/interfaces/builtin/mir.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/mir.go 2018-01-24 20:02:44.000000000 +0000 @@ -26,6 +26,7 @@ "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/seccomp" "github.com/snapcore/snapd/interfaces/udev" + "github.com/snapcore/snapd/snap" ) const mirSummary = `allows operating as the Mir server` @@ -48,6 +49,7 @@ /{dev,run}/shm/\#* mrw, /run/mir_socket rw, +/run/user/[0-9]*/mir_socket rw, # Needed for mode setting via drmSetMaster() and drmDropMaster() capability sys_admin, @@ -101,7 +103,7 @@ } } -func (iface *mirInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *mirInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###SLOT_SECURITY_TAGS###" new := slotAppLabelExpr(slot) snippet := strings.Replace(mirConnectedPlugAppArmor, old, new, -1) @@ -109,7 +111,7 @@ return nil } -func (iface *mirInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *mirInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###PLUG_SECURITY_TAGS###" new := plugAppLabelExpr(plug) snippet := strings.Replace(mirConnectedSlotAppArmor, old, new, -1) @@ -117,17 +119,17 @@ return nil } -func (iface *mirInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { +func (iface *mirInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(mirPermanentSlotAppArmor) return nil } -func (iface *mirInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *interfaces.Slot) error { +func (iface *mirInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(mirPermanentSlotSecComp) return nil } -func (iface *mirInterface) UDevPermanentSlot(spec *udev.Specification, slot *interfaces.Slot) error { +func (iface *mirInterface) UDevPermanentSlot(spec *udev.Specification, slot *snap.SlotInfo) error { spec.TagDevice(`KERNEL=="tty[0-9]*"`) spec.TagDevice(`KERNEL=="mice"`) spec.TagDevice(`KERNEL=="mouse[0-9]*"`) diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/mir_test.go snapd-2.31.1+17.10/interfaces/builtin/mir_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/mir_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/mir_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -27,15 +27,19 @@ "github.com/snapcore/snapd/interfaces/builtin" "github.com/snapcore/snapd/interfaces/seccomp" "github.com/snapcore/snapd/interfaces/udev" + "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap/snaptest" "github.com/snapcore/snapd/testutil" ) type MirInterfaceSuite struct { - iface interfaces.Interface - coreSlot *interfaces.Slot - classicSlot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + coreSlotInfo *snap.SlotInfo + coreSlot *interfaces.ConnectedSlot + classicSlotInfo *snap.SlotInfo + classicSlot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&MirInterfaceSuite{ @@ -69,13 +73,16 @@ ` // mir snap with mir-server slot on an core/all-snap install. snapInfo := snaptest.MockInfo(c, mirMockSlotSnapInfoYaml, nil) - s.coreSlot = &interfaces.Slot{SlotInfo: snapInfo.Slots["mir"]} + s.coreSlotInfo = snapInfo.Slots["mir"] + s.coreSlot = interfaces.NewConnectedSlot(s.coreSlotInfo, nil) // mir slot on a core snap in a classic install. snapInfo = snaptest.MockInfo(c, mirMockClassicSlotSnapInfoYaml, nil) - s.classicSlot = &interfaces.Slot{SlotInfo: snapInfo.Slots["mir"]} + s.classicSlotInfo = snapInfo.Slots["mir"] + s.classicSlot = interfaces.NewConnectedSlot(s.classicSlotInfo, nil) // snap with the mir plug snapInfo = snaptest.MockInfo(c, mockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: snapInfo.Plugs["mir"]} + s.plugInfo = snapInfo.Plugs["mir"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *MirInterfaceSuite) TestName(c *C) { @@ -84,24 +91,24 @@ func (s *MirInterfaceSuite) TestUsedSecuritySystems(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddPermanentSlot(s.iface, s.coreSlot) + err := apparmorSpec.AddPermanentSlot(s.iface, s.coreSlotInfo) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.mir-server.mir"}) c.Assert(apparmorSpec.SnippetForTag("snap.mir-server.mir"), testutil.Contains, "capability sys_tty_config") apparmorSpec = &apparmor.Specification{} - err = apparmorSpec.AddPermanentSlot(s.iface, s.classicSlot) + err = apparmorSpec.AddPermanentSlot(s.iface, s.classicSlotInfo) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), HasLen, 0) apparmorSpec = &apparmor.Specification{} - err = apparmorSpec.AddConnectedSlot(s.iface, s.plug, nil, s.coreSlot, nil) + err = apparmorSpec.AddConnectedSlot(s.iface, s.plug, s.coreSlot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.mir-server.mir"}) c.Assert(apparmorSpec.SnippetForTag("snap.mir-server.mir"), testutil.Contains, "unix (receive, send) type=seqpacket addr=none peer=(label=\"snap.other") apparmorSpec = &apparmor.Specification{} - err = apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.coreSlot, nil) + err = apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.coreSlot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "/run/mir_socket rw,") @@ -121,7 +128,7 @@ func (s *MirInterfaceSuite) TestSecComp(c *C) { seccompSpec := &seccomp.Specification{} - err := seccompSpec.AddPermanentSlot(s.iface, s.coreSlot) + err := seccompSpec.AddPermanentSlot(s.iface, s.coreSlotInfo) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.mir-server.mir"}) c.Check(seccompSpec.SnippetForTag("snap.mir-server.mir"), testutil.Contains, "listen\n") @@ -129,7 +136,7 @@ func (s *MirInterfaceSuite) TestSecCompOnClassic(c *C) { seccompSpec := &seccomp.Specification{} - err := seccompSpec.AddPermanentSlot(s.iface, s.classicSlot) + err := seccompSpec.AddPermanentSlot(s.iface, s.classicSlotInfo) c.Assert(err, IsNil) snippets := seccompSpec.Snippets() // no permanent seccomp snippet for the slot @@ -138,9 +145,19 @@ func (s *MirInterfaceSuite) TestUDevSpec(c *C) { udevSpec := &udev.Specification{} - c.Assert(udevSpec.AddPermanentSlot(s.iface, s.coreSlot), IsNil) - c.Assert(udevSpec.Snippets(), HasLen, 5) - c.Assert(udevSpec.Snippets(), testutil.Contains, `KERNEL=="event[0-9]*", TAG+="snap_mir-server_mir"`) + c.Assert(udevSpec.AddPermanentSlot(s.iface, s.coreSlotInfo), IsNil) + c.Assert(udevSpec.Snippets(), HasLen, 6) + c.Assert(udevSpec.Snippets(), testutil.Contains, `# mir +KERNEL=="tty[0-9]*", TAG+="snap_mir-server_mir"`) + c.Assert(udevSpec.Snippets(), testutil.Contains, `# mir +KERNEL=="mice", TAG+="snap_mir-server_mir"`) + c.Assert(udevSpec.Snippets(), testutil.Contains, `# mir +KERNEL=="mouse[0-9]*", TAG+="snap_mir-server_mir"`) + c.Assert(udevSpec.Snippets(), testutil.Contains, `# mir +KERNEL=="event[0-9]*", TAG+="snap_mir-server_mir"`) + c.Assert(udevSpec.Snippets(), testutil.Contains, `# mir +KERNEL=="ts[0-9]*", TAG+="snap_mir-server_mir"`) + c.Assert(udevSpec.Snippets(), testutil.Contains, `TAG=="snap_mir-server_mir", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_mir-server_mir $devpath $major:$minor"`) } func (s *MirInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/modem_manager.go snapd-2.31.1+17.10/interfaces/builtin/modem_manager.go --- snapd-2.29.4.2+17.10/interfaces/builtin/modem_manager.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/modem_manager.go 2018-01-31 08:47:06.000000000 +0000 @@ -28,6 +28,7 @@ "github.com/snapcore/snapd/interfaces/seccomp" "github.com/snapcore/snapd/interfaces/udev" "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" ) const modemManagerSummary = `allows operating as the ModemManager service` @@ -178,7 +179,6 @@ accept4 bind listen -shutdown # libgudev socket AF_NETLINK - NETLINK_KOBJECT_UEVENT ` @@ -745,6 +745,9 @@ SUBSYSTEM!="usb", GOTO="mm_usb_device_blacklist_end" ENV{DEVTYPE}!="usb_device", GOTO="mm_usb_device_blacklist_end" +# Telegesis zigbee dongle +ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="0003", ENV{ID_MM_DEVICE_IGNORE}="1" + # APC UPS devices ATTRS{idVendor}=="051d", ENV{ID_MM_DEVICE_IGNORE}="1" @@ -1202,7 +1205,7 @@ } } -func (iface *modemManagerInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *modemManagerInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###SLOT_SECURITY_TAGS###" new := slotAppLabelExpr(slot) spec.AddSnippet(strings.Replace(modemManagerConnectedPlugAppArmor, old, new, -1)) @@ -1213,28 +1216,28 @@ return nil } -func (iface *modemManagerInterface) DBusConnectedPlug(spec *dbus.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *modemManagerInterface) DBusConnectedPlug(spec *dbus.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { spec.AddSnippet(modemManagerConnectedPlugDBus) return nil } -func (iface *modemManagerInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { +func (iface *modemManagerInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(modemManagerPermanentSlotAppArmor) return nil } -func (iface *modemManagerInterface) DBusPermanentSlot(spec *dbus.Specification, slot *interfaces.Slot) error { +func (iface *modemManagerInterface) DBusPermanentSlot(spec *dbus.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(modemManagerPermanentSlotDBus) return nil } -func (iface *modemManagerInterface) UDevPermanentSlot(spec *udev.Specification, slot *interfaces.Slot) error { +func (iface *modemManagerInterface) UDevPermanentSlot(spec *udev.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(modemManagerPermanentSlotUDev) spec.TagDevice(`KERNEL=="tty[A-Z]*[0-9]*|cdc-wdm[0-9]*"`) return nil } -func (iface *modemManagerInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *modemManagerInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###PLUG_SECURITY_TAGS###" new := plugAppLabelExpr(plug) snippet := strings.Replace(modemManagerConnectedSlotAppArmor, old, new, -1) @@ -1242,7 +1245,7 @@ return nil } -func (iface *modemManagerInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *interfaces.Slot) error { +func (iface *modemManagerInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(modemManagerPermanentSlotSecComp) return nil } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/modem_manager_test.go snapd-2.31.1+17.10/interfaces/builtin/modem_manager_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/modem_manager_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/modem_manager_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -35,9 +35,11 @@ ) type ModemManagerInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } const modemmgrMockSlotSnapInfoYaml = `name: modem-manager @@ -65,15 +67,15 @@ }) func (s *ModemManagerInterfaceSuite) SetUpTest(c *C) { - s.plug = &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{SuggestedName: "modem-manager"}, - Name: "mmcli", - Interface: "modem-manager", - }, + s.plugInfo = &snap.PlugInfo{ + Snap: &snap.Info{SuggestedName: "modem-manager"}, + Name: "mmcli", + Interface: "modem-manager", } + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) slotSnap := snaptest.MockInfo(c, modemmgrMockSlotSnapInfoYaml, nil) - s.slot = &interfaces.Slot{SlotInfo: slotSnap.Slots["modem-manager"]} + s.slotInfo = slotSnap.Slots["modem-manager"] + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) } func (s *ModemManagerInterfaceSuite) TestName(c *C) { @@ -84,24 +86,22 @@ func (s *ModemManagerInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelAll(c *C) { app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "modem-manager-prod", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - Name: "modem-manager", - Interface: "modem-manager", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "modem-manager-prod", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, }, - } + Name: "modem-manager", + Interface: "modem-manager", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) release.OnClassic = false plugSnap := snaptest.MockInfo(c, modemmgrMockPlugSnapInfoYaml, nil) - plug := &interfaces.Plug{PlugInfo: plugSnap.Plugs["modem-manager"]} + plug := interfaces.NewConnectedPlug(plugSnap.Plugs["modem-manager"], nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.modem-manager.mmcli"}) c.Assert(apparmorSpec.SnippetForTag("snap.modem-manager.mmcli"), testutil.Contains, `peer=(label="snap.modem-manager-prod.*"),`) @@ -112,24 +112,22 @@ app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} app3 := &snap.AppInfo{Name: "app3"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "modem-manager", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, - }, - Name: "modem-manager", - Interface: "modem-manager", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "modem-manager", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, }, - } + Name: "modem-manager", + Interface: "modem-manager", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) release.OnClassic = false plugSnap := snaptest.MockInfo(c, modemmgrMockPlugSnapInfoYaml, nil) - plug := &interfaces.Plug{PlugInfo: plugSnap.Plugs["modem-manager"]} + plug := interfaces.NewConnectedPlug(plugSnap.Plugs["modem-manager"], nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.modem-manager.mmcli"}) c.Assert(apparmorSpec.SnippetForTag("snap.modem-manager.mmcli"), testutil.Contains, `peer=(label="snap.modem-manager.{app1,app2}"),`) @@ -138,24 +136,22 @@ // The label uses short form when exactly one app is bound to the modem-manager slot func (s *ModemManagerInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelOne(c *C) { app := &snap.AppInfo{Name: "app"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "modem-manager", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - Name: "modem-manager", - Interface: "modem-manager", - Apps: map[string]*snap.AppInfo{"app": app}, + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "modem-manager", + Apps: map[string]*snap.AppInfo{"app": app}, }, - } + Name: "modem-manager", + Interface: "modem-manager", + Apps: map[string]*snap.AppInfo{"app": app}, + }, nil) release.OnClassic = false plugSnap := snaptest.MockInfo(c, modemmgrMockPlugSnapInfoYaml, nil) - plug := &interfaces.Plug{PlugInfo: plugSnap.Plugs["modem-manager"]} + plug := interfaces.NewConnectedPlug(plugSnap.Plugs["modem-manager"], nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.modem-manager.mmcli"}) c.Assert(apparmorSpec.SnippetForTag("snap.modem-manager.mmcli"), testutil.Contains, `peer=(label="snap.modem-manager.app"),`) @@ -164,10 +160,10 @@ func (s *ModemManagerInterfaceSuite) TestConnectedPlugSnippetUsesUnconfinedLabelNot(c *C) { release.OnClassic = false plugSnap := snaptest.MockInfo(c, modemmgrMockPlugSnapInfoYaml, nil) - plug := &interfaces.Plug{PlugInfo: plugSnap.Plugs["modem-manager"]} + plug := interfaces.NewConnectedPlug(plugSnap.Plugs["modem-manager"], nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.modem-manager.mmcli"}) snippet := apparmorSpec.SnippetForTag("snap.modem-manager.mmcli") @@ -179,9 +175,9 @@ release.OnClassic = true plugSnap := snaptest.MockInfo(c, modemmgrMockPlugSnapInfoYaml, nil) - plug := &interfaces.Plug{PlugInfo: plugSnap.Plugs["modem-manager"]} + plug := interfaces.NewConnectedPlug(plugSnap.Plugs["modem-manager"], nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.modem-manager.mmcli"}) c.Assert(apparmorSpec.SnippetForTag("snap.modem-manager.mmcli"), testutil.Contains, "peer=(label=unconfined),") @@ -189,32 +185,34 @@ func (s *ModemManagerInterfaceSuite) TestUsedSecuritySystems(c *C) { plugSnap := snaptest.MockInfo(c, modemmgrMockPlugSnapInfoYaml, nil) - plug := &interfaces.Plug{PlugInfo: plugSnap.Plugs["modem-manager"]} + plug := interfaces.NewConnectedPlug(plugSnap.Plugs["modem-manager"], nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), HasLen, 1) dbusSpec := &dbus.Specification{} - err = dbusSpec.AddConnectedPlug(s.iface, plug, nil, s.slot, nil) + err = dbusSpec.AddConnectedPlug(s.iface, plug, s.slot) c.Assert(err, IsNil) c.Assert(dbusSpec.SecurityTags(), HasLen, 1) dbusSpec = &dbus.Specification{} - err = dbusSpec.AddPermanentSlot(s.iface, s.slot) + err = dbusSpec.AddPermanentSlot(s.iface, s.slotInfo) c.Assert(err, IsNil) c.Assert(dbusSpec.SecurityTags(), HasLen, 1) udevSpec := &udev.Specification{} - c.Assert(udevSpec.AddPermanentSlot(s.iface, s.slot), IsNil) - c.Assert(udevSpec.Snippets(), HasLen, 2) + c.Assert(udevSpec.AddPermanentSlot(s.iface, s.slotInfo), IsNil) + c.Assert(udevSpec.Snippets(), HasLen, 3) c.Assert(udevSpec.Snippets()[0], testutil.Contains, `SUBSYSTEMS=="usb"`) - c.Assert(udevSpec.Snippets(), testutil.Contains, `KERNEL=="tty[A-Z]*[0-9]*|cdc-wdm[0-9]*", TAG+="snap_modem-manager_mm"`) + c.Assert(udevSpec.Snippets(), testutil.Contains, `# modem-manager +KERNEL=="tty[A-Z]*[0-9]*|cdc-wdm[0-9]*", TAG+="snap_modem-manager_mm"`) + c.Assert(udevSpec.Snippets(), testutil.Contains, `TAG=="snap_modem-manager_mm", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_modem-manager_mm $devpath $major:$minor"`) } func (s *ModemManagerInterfaceSuite) TestPermanentSlotDBus(c *C) { dbusSpec := &dbus.Specification{} - err := dbusSpec.AddPermanentSlot(s.iface, s.slot) + err := dbusSpec.AddPermanentSlot(s.iface, s.slotInfo) c.Assert(err, IsNil) c.Assert(dbusSpec.SecurityTags(), DeepEquals, []string{"snap.modem-manager.mm"}) snippet := dbusSpec.SnippetForTag("snap.modem-manager.mm") @@ -224,7 +222,7 @@ func (s *ModemManagerInterfaceSuite) TestPermanentSlotSecComp(c *C) { seccompSpec := &seccomp.Specification{} - err := seccompSpec.AddPermanentSlot(s.iface, s.slot) + err := seccompSpec.AddPermanentSlot(s.iface, s.slotInfo) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.modem-manager.mm"}) c.Check(seccompSpec.SnippetForTag("snap.modem-manager.mm"), testutil.Contains, "listen\n") @@ -232,10 +230,10 @@ func (s *ModemManagerInterfaceSuite) TestConnectedPlugDBus(c *C) { plugSnap := snaptest.MockInfo(c, modemmgrMockPlugSnapInfoYaml, nil) - plug := &interfaces.Plug{PlugInfo: plugSnap.Plugs["modem-manager"]} + plug := interfaces.NewConnectedPlug(plugSnap.Plugs["modem-manager"], nil) dbusSpec := &dbus.Specification{} - err := dbusSpec.AddConnectedPlug(s.iface, plug, nil, s.slot, nil) + err := dbusSpec.AddConnectedPlug(s.iface, plug, s.slot) c.Assert(err, IsNil) c.Assert(dbusSpec.SecurityTags(), DeepEquals, []string{"snap.modem-manager.mmcli"}) snippet := dbusSpec.SnippetForTag("snap.modem-manager.mmcli") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/mount_observe_test.go snapd-2.31.1+17.10/interfaces/builtin/mount_observe_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/mount_observe_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/mount_observe_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type MountObserveInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&MountObserveInterfaceSuite{ @@ -48,15 +50,15 @@ command: foo plugs: [mount-observe] ` - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "mount-observe", - Interface: "mount-observe", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "mount-observe", + Interface: "mount-observe", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) snapInfo := snaptest.MockInfo(c, mockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: snapInfo.Plugs["mount-observe"]} + s.plugInfo = snapInfo.Plugs["mount-observe"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *MountObserveInterfaceSuite) TestName(c *C) { @@ -64,23 +66,23 @@ } func (s *MountObserveInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "mount-observe", Interface: "mount-observe", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, "mount-observe slots are reserved for the core snap") + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "mount-observe slots are reserved for the core snap") } func (s *MountObserveInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *MountObserveInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, "/etc/fstab") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/mpris.go snapd-2.31.1+17.10/interfaces/builtin/mpris.go --- snapd-2.29.4.2+17.10/interfaces/builtin/mpris.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/mpris.go 2018-01-24 20:02:44.000000000 +0000 @@ -27,6 +27,7 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" ) const mprisSummary = `allows operating as an MPRIS player` @@ -164,14 +165,14 @@ } } -func (iface *mprisInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *mprisInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###SLOT_SECURITY_TAGS###" new := slotAppLabelExpr(slot) spec.AddSnippet(strings.Replace(mprisConnectedPlugAppArmor, old, new, -1)) return nil } -func (iface *mprisInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { +func (iface *mprisInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { name, err := iface.getName(slot.Attrs) if err != nil { return err @@ -188,7 +189,7 @@ return nil } -func (iface *mprisInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *mprisInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###PLUG_SECURITY_TAGS###" new := plugAppLabelExpr(plug) spec.AddSnippet(strings.Replace(mprisConnectedSlotAppArmor, old, new, -1)) @@ -220,7 +221,7 @@ return mprisName, nil } -func (iface *mprisInterface) SanitizeSlot(slot *interfaces.Slot) error { +func (iface *mprisInterface) BeforePrepareSlot(slot *snap.SlotInfo) error { _, err := iface.getName(slot.Attrs) return err } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/mpris_test.go snapd-2.31.1+17.10/interfaces/builtin/mpris_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/mpris_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/mpris_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -32,9 +32,11 @@ ) type MprisInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&MprisInterfaceSuite{ @@ -58,9 +60,11 @@ ` snapInfo := snaptest.MockInfo(c, mockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: snapInfo.Plugs["mpris"]} + s.plugInfo = snapInfo.Plugs["mpris"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) snapInfo = snaptest.MockInfo(c, mockSlotSnapInfoYaml, nil) - s.slot = &interfaces.Slot{SlotInfo: snapInfo.Slots["mpris"]} + s.slotInfo = snapInfo.Slots["mpris"] + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) } func (s *MprisInterfaceSuite) TestName(c *C) { @@ -76,7 +80,7 @@ name: foo ` info := snaptest.MockInfo(c, mockSnapYaml, nil) - slot := &interfaces.Slot{SlotInfo: info.Slots["mpris-slot"]} + slot := info.Slots["mpris-slot"] name, err := builtin.MprisGetName(s.iface, slot.Attrs) c.Assert(err, IsNil) c.Assert(name, Equals, "foo") @@ -90,7 +94,7 @@ interface: mpris ` info := snaptest.MockInfo(c, mockSnapYaml, nil) - slot := &interfaces.Slot{SlotInfo: info.Slots["mpris-slot"]} + slot := info.Slots["mpris-slot"] name, err := builtin.MprisGetName(s.iface, slot.Attrs) c.Assert(err, IsNil) c.Assert(name, Equals, "@{SNAP_NAME}") @@ -104,7 +108,7 @@ name: foo.bar ` info := snaptest.MockInfo(c, mockSnapYaml, nil) - slot := &interfaces.Slot{SlotInfo: info.Slots["mpris-slot"]} + slot := info.Slots["mpris-slot"] name, err := builtin.MprisGetName(s.iface, slot.Attrs) c.Assert(err, Not(IsNil)) c.Assert(err, ErrorMatches, "invalid name element: \"foo.bar\"") @@ -121,7 +125,7 @@ - foo ` info := snaptest.MockInfo(c, mockSnapYaml, nil) - slot := &interfaces.Slot{SlotInfo: info.Slots["mpris-slot"]} + slot := info.Slots["mpris-slot"] name, err := builtin.MprisGetName(s.iface, slot.Attrs) c.Assert(err, Not(IsNil)) c.Assert(err, ErrorMatches, `name element \[foo\] is not a string`) @@ -137,7 +141,7 @@ unknown: foo ` info := snaptest.MockInfo(c, mockSnapYaml, nil) - slot := &interfaces.Slot{SlotInfo: info.Slots["mpris-slot"]} + slot := info.Slots["mpris-slot"] name, err := builtin.MprisGetName(s.iface, slot.Attrs) c.Assert(err, Not(IsNil)) c.Assert(err, ErrorMatches, "unknown attribute 'unknown'") @@ -148,20 +152,18 @@ func (s *MprisInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelAll(c *C) { app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "mpris", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - Name: "mpris", - Interface: "mpris", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "mpris", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, }, - } + Name: "mpris", + Interface: "mpris", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `peer=(label="snap.mpris.*"),`) @@ -172,20 +174,18 @@ app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} app3 := &snap.AppInfo{Name: "app3"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "mpris", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, - }, - Name: "mpris", - Interface: "mpris", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "mpris", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, }, - } + Name: "mpris", + Interface: "mpris", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `peer=(label="snap.mpris.{app1,app2}"),`) @@ -194,20 +194,18 @@ // The label uses short form when exactly one app is bound to the mpris slot func (s *MprisInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelOne(c *C) { app := &snap.AppInfo{Name: "app"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "mpris", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - Name: "mpris", - Interface: "mpris", - Apps: map[string]*snap.AppInfo{"app": app}, + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "mpris", + Apps: map[string]*snap.AppInfo{"app": app}, }, - } + Name: "mpris", + Interface: "mpris", + Apps: map[string]*snap.AppInfo{"app": app}, + }, nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `peer=(label="snap.mpris.app"),`) @@ -217,20 +215,18 @@ func (s *MprisInterfaceSuite) TestConnectedSlotSnippetUsesPlugLabelAll(c *C) { app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} - plug := &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{ - SuggestedName: "mpris", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - Name: "mpris", - Interface: "mpris", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + plug := interfaces.NewConnectedPlug(&snap.PlugInfo{ + Snap: &snap.Info{ + SuggestedName: "mpris", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, }, - } + Name: "mpris", + Interface: "mpris", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedSlot(s.iface, plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedSlot(s.iface, plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.mpris.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.mpris.app"), testutil.Contains, `peer=(label="snap.mpris.*"),`) @@ -241,20 +237,18 @@ app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} app3 := &snap.AppInfo{Name: "app3"} - plug := &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{ - SuggestedName: "mpris", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, - }, - Name: "mpris", - Interface: "mpris", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + plug := interfaces.NewConnectedPlug(&snap.PlugInfo{ + Snap: &snap.Info{ + SuggestedName: "mpris", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, }, - } + Name: "mpris", + Interface: "mpris", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedSlot(s.iface, plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedSlot(s.iface, plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.mpris.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.mpris.app"), testutil.Contains, `peer=(label="snap.mpris.{app1,app2}"),`) @@ -263,20 +257,18 @@ // The label uses short form when exactly one app is bound to the mpris plug func (s *MprisInterfaceSuite) TestConnectedSlotSnippetUsesPlugLabelOne(c *C) { app := &snap.AppInfo{Name: "app"} - plug := &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{ - SuggestedName: "mpris", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - Name: "mpris", - Interface: "mpris", - Apps: map[string]*snap.AppInfo{"app": app}, + plug := interfaces.NewConnectedPlug(&snap.PlugInfo{ + Snap: &snap.Info{ + SuggestedName: "mpris", + Apps: map[string]*snap.AppInfo{"app": app}, }, - } + Name: "mpris", + Interface: "mpris", + Apps: map[string]*snap.AppInfo{"app": app}, + }, nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedSlot(s.iface, plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedSlot(s.iface, plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.mpris.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.mpris.app"), testutil.Contains, `peer=(label="snap.mpris.app"),`) @@ -284,7 +276,7 @@ func (s *MprisInterfaceSuite) TestPermanentSlotAppArmor(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddPermanentSlot(s.iface, s.slot) + err := apparmorSpec.AddPermanentSlot(s.iface, s.slotInfo) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.mpris.app"}) @@ -304,7 +296,7 @@ command: foo ` info := snaptest.MockInfo(c, mockSnapYaml, nil) - slot := &interfaces.Slot{SlotInfo: info.Slots["mpris-slot"]} + slot := info.Slots["mpris-slot"] apparmorSpec := &apparmor.Specification{} err := apparmorSpec.AddPermanentSlot(s.iface, slot) @@ -320,7 +312,7 @@ defer restore() apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddPermanentSlot(s.iface, s.slot) + err := apparmorSpec.AddPermanentSlot(s.iface, s.slotInfo) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.mpris.app"}) @@ -333,7 +325,7 @@ defer restore() apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddPermanentSlot(s.iface, s.slot) + err := apparmorSpec.AddPermanentSlot(s.iface, s.slotInfo) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.mpris.app"}) diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/netlink_audit.go snapd-2.31.1+17.10/interfaces/builtin/netlink_audit.go --- snapd-2.29.4.2+17.10/interfaces/builtin/netlink_audit.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/netlink_audit.go 2018-02-16 15:57:58.000000000 +0000 @@ -35,14 +35,31 @@ socket AF_NETLINK - NETLINK_AUDIT ` +const netlinkAuditConnectedPlugAppArmor = ` +# Description: Can use netlink to read/write to kernel audit system. +network netlink, + +# CAP_NET_ADMIN required for multicast netlink sockets per 'man 7 netlink' +capability net_admin, + +# CAP_AUDIT_READ required to read the audit log via the netlink multicast socket +# per 'man 7 capabilities' +capability audit_read, + +# CAP_AUDIT_WRITE required to write to the audit log via the netlink multicast +# socket per 'man 7 capabilities' +capability audit_write, +` + func init() { registerIface(&commonInterface{ - name: "netlink-audit", - summary: netlinkAuditSummary, - implicitOnCore: true, - implicitOnClassic: true, - baseDeclarationSlots: netlinkAuditBaseDeclarationSlots, - connectedPlugSecComp: netlinkAuditConnectedPlugSecComp, - reservedForOS: true, + name: "netlink-audit", + summary: netlinkAuditSummary, + implicitOnCore: true, + implicitOnClassic: true, + baseDeclarationSlots: netlinkAuditBaseDeclarationSlots, + connectedPlugSecComp: netlinkAuditConnectedPlugSecComp, + connectedPlugAppArmor: netlinkAuditConnectedPlugAppArmor, + reservedForOS: true, }) } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/netlink_audit_test.go snapd-2.31.1+17.10/interfaces/builtin/netlink_audit_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/netlink_audit_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/netlink_audit_test.go 2018-02-16 15:57:58.000000000 +0000 @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2017 Canonical Ltd + * Copyright (C) 2017-2018 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 @@ -23,6 +23,7 @@ . "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/seccomp" "github.com/snapcore/snapd/snap" @@ -31,9 +32,11 @@ ) type NetlinkAuditInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } const netlinkAuditMockPlugSnapInfoYaml = `name: other @@ -49,15 +52,15 @@ }) func (s *NetlinkAuditInterfaceSuite) SetUpTest(c *C) { - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "netlink-audit", - Interface: "netlink-audit", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "netlink-audit", + Interface: "netlink-audit", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, netlinkAuditMockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["netlink-audit"]} + s.plugInfo = plugSnap.Plugs["netlink-audit"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *NetlinkAuditInterfaceSuite) TestName(c *C) { @@ -65,26 +68,34 @@ } func (s *NetlinkAuditInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "netlink-audit", Interface: "netlink-audit", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "netlink-audit slots are reserved for the core snap") } func (s *NetlinkAuditInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) +} + +func (s *NetlinkAuditInterfaceSuite) TestAppArmorSpec(c *C) { + spec := &apparmor.Specification{} + err := spec.AddConnectedPlug(s.iface, s.plug, s.slot) + c.Assert(err, IsNil) + c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) + c.Check(spec.SnippetForTag("snap.other.app2"), testutil.Contains, "capability audit_write,\n") } -func (s *NetlinkAuditInterfaceSuite) TestUsedSecuritySystems(c *C) { - seccompSpec := &seccomp.Specification{} - err := seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) +func (s *NetlinkAuditInterfaceSuite) TestSecCompSpec(c *C) { + spec := &seccomp.Specification{} + err := spec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) - c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) - c.Check(seccompSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "socket AF_NETLINK - NETLINK_AUDIT\n") + c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) + c.Check(spec.SnippetForTag("snap.other.app2"), testutil.Contains, "socket AF_NETLINK - NETLINK_AUDIT\n") } func (s *NetlinkAuditInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/netlink_connector.go snapd-2.31.1+17.10/interfaces/builtin/netlink_connector.go --- snapd-2.29.4.2+17.10/interfaces/builtin/netlink_connector.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/netlink_connector.go 2018-01-24 20:02:44.000000000 +0000 @@ -38,14 +38,25 @@ socket AF_NETLINK - NETLINK_CONNECTOR ` +const netlinkConnectorConnectedPlugAppArmor = ` +# Description: Can use netlink to communicate with kernel connector. Because +# NETLINK_CONNECTOR is not finely mediated and app-specific, use of this +# interface allows communications via all netlink connectors. +# https://github.com/torvalds/linux/blob/master/Documentation/connector/connector.txt +network netlink, +# CAP_NET_ADMIN required per 'man 7 netlink' +capability net_admin, +` + func init() { registerIface(&commonInterface{ - name: "netlink-connector", - summary: netlinkConnectorSummary, - implicitOnCore: true, - implicitOnClassic: true, - baseDeclarationSlots: netlinkConnectorBaseDeclarationSlots, - connectedPlugSecComp: netlinkConnectorConnectedPlugSecComp, - reservedForOS: true, + name: "netlink-connector", + summary: netlinkConnectorSummary, + implicitOnCore: true, + implicitOnClassic: true, + baseDeclarationSlots: netlinkConnectorBaseDeclarationSlots, + connectedPlugSecComp: netlinkConnectorConnectedPlugSecComp, + connectedPlugAppArmor: netlinkConnectorConnectedPlugAppArmor, + reservedForOS: true, }) } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/netlink_connector_test.go snapd-2.31.1+17.10/interfaces/builtin/netlink_connector_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/netlink_connector_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/netlink_connector_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type NetlinkConnectorInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } const netlinkConnectorMockPlugSnapInfoYaml = `name: other @@ -49,15 +51,15 @@ }) func (s *NetlinkConnectorInterfaceSuite) SetUpTest(c *C) { - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "netlink-connector", - Interface: "netlink-connector", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "netlink-connector", + Interface: "netlink-connector", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, netlinkConnectorMockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["netlink-connector"]} + s.plugInfo = plugSnap.Plugs["netlink-connector"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *NetlinkConnectorInterfaceSuite) TestName(c *C) { @@ -65,23 +67,23 @@ } func (s *NetlinkConnectorInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "netlink-connector", Interface: "netlink-connector", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "netlink-connector slots are reserved for the core snap") } func (s *NetlinkConnectorInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *NetlinkConnectorInterfaceSuite) TestUsedSecuritySystems(c *C) { seccompSpec := &seccomp.Specification{} - err := seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := seccompSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) c.Check(seccompSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "socket AF_NETLINK - NETLINK_CONNECTOR\n") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/network_bind.go snapd-2.31.1+17.10/interfaces/builtin/network_bind.go --- snapd-2.29.4.2+17.10/interfaces/builtin/network_bind.go 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/network_bind.go 2018-01-24 20:02:44.000000000 +0000 @@ -85,7 +85,6 @@ accept4 bind listen -shutdown # TODO: remove this rule once seccomp errno with logging is implemented. # java apps attempt this, presumably to handle interface changes, but a # corresponding AppArmor rule is required (eg, network netlink dgram) to use diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/network_bind_test.go snapd-2.31.1+17.10/interfaces/builtin/network_bind_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/network_bind_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/network_bind_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -32,9 +32,11 @@ ) type NetworkBindInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } const netbindMockPlugSnapInfoYaml = `name: other @@ -50,46 +52,46 @@ }) func (s *NetworkBindInterfaceSuite) SetUpTest(c *C) { - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "network-bind", - Interface: "network-bind", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "network-bind", + Interface: "network-bind", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, netbindMockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["network-bind"]} + s.plugInfo = plugSnap.Plugs["network-bind"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *NetworkBindInterfaceSuite) TestName(c *C) { c.Assert(s.iface.Name(), Equals, "network-bind") } func (s *NetworkBindInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "network-bind", Interface: "network-bind", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "network-bind slots are reserved for the core snap") } func (s *NetworkBindInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *NetworkBindInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app2"), testutil.Contains, `/sys/net`) // connected plugs have a non-nil security snippet for seccomp seccompSpec := &seccomp.Specification{} - err = seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err = seccompSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) c.Check(seccompSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "listen\n") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/network_control.go snapd-2.31.1+17.10/interfaces/builtin/network_control.go --- snapd-2.29.4.2+17.10/interfaces/builtin/network_control.go 2017-11-30 07:12:26.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/network_control.go 2018-02-16 20:27:57.000000000 +0000 @@ -128,8 +128,8 @@ /dev/rfkill rw, /sys/class/rfkill/ r, -/sys/devices/{pci[0-9]*,platform,virtual}/**/rfkill[0-9]*/{,**} r, -/sys/devices/{pci[0-9]*,platform,virtual}/**/rfkill[0-9]*/state w, +/sys/devices/{pci[0-9a-f]*,platform,virtual}/**/rfkill[0-9]*/{,**} r, +/sys/devices/{pci[0-9a-f]*,platform,virtual}/**/rfkill[0-9]*/state w, # arp network netlink dgram, diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/network_control_test.go snapd-2.31.1+17.10/interfaces/builtin/network_control_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/network_control_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/network_control_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -32,9 +32,11 @@ ) type NetworkControlInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&NetworkControlInterfaceSuite{ @@ -54,8 +56,8 @@ ` func (s *NetworkControlInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, networkControlConsumerYaml, nil, "network-control") - s.slot = MockSlot(c, networkControlCoreYaml, nil, "network-control") + s.plug, s.plugInfo = MockConnectedPlug(c, networkControlConsumerYaml, nil, "network-control") + s.slot, s.slotInfo = MockConnectedSlot(c, networkControlCoreYaml, nil, "network-control") } func (s *NetworkControlInterfaceSuite) TestName(c *C) { @@ -63,39 +65,41 @@ } func (s *NetworkControlInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "network-control", Interface: "network-control", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "network-control slots are reserved for the core snap") } func (s *NetworkControlInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *NetworkControlInterfaceSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, "/run/netns/* rw,\n") } func (s *NetworkControlInterfaceSuite) TestSecCompSpec(c *C) { spec := &seccomp.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, "setns - CLONE_NEWNET\n") } func (s *NetworkControlInterfaceSuite) TestUDevSpec(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 2) - c.Assert(spec.Snippets(), testutil.Contains, `KERNEL=="tun", TAG+="snap_consumer_app"`) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Snippets(), HasLen, 3) + c.Assert(spec.Snippets(), testutil.Contains, `# network-control +KERNEL=="tun", TAG+="snap_consumer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_consumer_app", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_consumer_app $devpath $major:$minor"`) } func (s *NetworkControlInterfaceSuite) TestStaticInfo(c *C) { @@ -107,7 +111,8 @@ } func (s *NetworkControlInterfaceSuite) TestAutoConnect(c *C) { - c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) + // FIXME: fix AutoConnect methods to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) } func (s *NetworkControlInterfaceSuite) TestInterfaces(c *C) { c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/network.go snapd-2.31.1+17.10/interfaces/builtin/network.go --- snapd-2.29.4.2+17.10/interfaces/builtin/network.go 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/network.go 2018-02-16 20:27:57.000000000 +0000 @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2016 Canonical Ltd + * Copyright (C) 2016-2018 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 @@ -66,13 +66,16 @@ const networkConnectedPlugSecComp = ` # Description: Can access the network as a client. bind -shutdown # FIXME: some kernels require this with common functions in go's 'net' library. # While this should remain in network-bind, network-control and # network-observe, for series 16 also have it here to not break existing snaps. # Future snapd series may remove this in the future. LP: #1689536 socket AF_NETLINK - NETLINK_ROUTE + +# Userspace SCTP +# https://github.com/sctplab/usrsctp/blob/master/usrsctplib/usrsctp.h +socket AF_CONN ` func init() { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/network_manager.go snapd-2.31.1+17.10/interfaces/builtin/network_manager.go --- snapd-2.29.4.2+17.10/interfaces/builtin/network_manager.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/network_manager.go 2018-01-24 20:02:44.000000000 +0000 @@ -28,6 +28,7 @@ "github.com/snapcore/snapd/interfaces/seccomp" "github.com/snapcore/snapd/interfaces/udev" "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" ) const networkManagerSummary = `allows operating as the NetworkManager service` @@ -265,7 +266,6 @@ bind listen sethostname -shutdown # netlink socket AF_NETLINK - - ` @@ -428,7 +428,7 @@ } } -func (iface *networkManagerInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *networkManagerInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###SLOT_SECURITY_TAGS###" var new string if release.OnClassic { @@ -443,7 +443,7 @@ return nil } -func (iface *networkManagerInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *networkManagerInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###PLUG_SECURITY_TAGS###" new := plugAppLabelExpr(plug) snippet := strings.Replace(networkManagerConnectedSlotAppArmor, old, new, -1) @@ -451,27 +451,27 @@ return nil } -func (iface *networkManagerInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { +func (iface *networkManagerInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(networkManagerPermanentSlotAppArmor) return nil } -func (iface *networkManagerInterface) DBusPermanentSlot(spec *dbus.Specification, slot *interfaces.Slot) error { +func (iface *networkManagerInterface) DBusPermanentSlot(spec *dbus.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(networkManagerPermanentSlotDBus) return nil } -func (iface *networkManagerInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *interfaces.Slot) error { +func (iface *networkManagerInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(networkManagerPermanentSlotSecComp) return nil } -func (iface *networkManagerInterface) UDevPermanentSlot(spec *udev.Specification, slot *interfaces.Slot) error { +func (iface *networkManagerInterface) UDevPermanentSlot(spec *udev.Specification, slot *snap.SlotInfo) error { spec.TagDevice(`KERNEL=="rfkill"`) return nil } -func (iface *networkManagerInterface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *networkManagerInterface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { spec.AddSnippet(networkManagerConnectedPlugSecComp) return nil } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/network_manager_test.go snapd-2.31.1+17.10/interfaces/builtin/network_manager_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/network_manager_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/network_manager_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -35,9 +35,11 @@ ) type NetworkManagerInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } const netmgrMockPlugSnapInfoYaml = `name: network-manager-client @@ -65,10 +67,12 @@ func (s *NetworkManagerInterfaceSuite) SetUpTest(c *C) { plugSnap := snaptest.MockInfo(c, netmgrMockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["network-manager"]} + s.plugInfo = plugSnap.Plugs["network-manager"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) slotSnap := snaptest.MockInfo(c, netmgrMockSlotSnapInfoYaml, nil) - s.slot = &interfaces.Slot{SlotInfo: slotSnap.Slots["network-manager"]} + s.slotInfo = slotSnap.Slots["network-manager"] + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) } func (s *NetworkManagerInterfaceSuite) TestName(c *C) { @@ -79,22 +83,20 @@ func (s *NetworkManagerInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelAll(c *C) { app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "network-manager", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - Name: "network-manager", - Interface: "network-manager", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "network-manager", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, }, - } + Name: "network-manager", + Interface: "network-manager", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) release.OnClassic = false // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.network-manager-client.nmcli"}) c.Assert(apparmorSpec.SnippetForTag("snap.network-manager-client.nmcli"), testutil.Contains, `peer=(label="snap.network-manager.*"),`) @@ -105,21 +107,19 @@ app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} app3 := &snap.AppInfo{Name: "app3"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "network-manager", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, - }, - Name: "network-manager", - Interface: "network-manager", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "network-manager", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, }, - } + Name: "network-manager", + Interface: "network-manager", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) release.OnClassic = false apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.network-manager-client.nmcli"}) c.Assert(apparmorSpec.SnippetForTag("snap.network-manager-client.nmcli"), testutil.Contains, `peer=(label="snap.network-manager.{app1,app2}"),`) @@ -128,30 +128,28 @@ // The label uses short form when exactly one app is bound to the network-manager slot func (s *NetworkManagerInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelOne(c *C) { app := &snap.AppInfo{Name: "app"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "network-manager", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - Name: "network-manager", - Interface: "network-manager", - Apps: map[string]*snap.AppInfo{"app": app}, + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "network-manager", + Apps: map[string]*snap.AppInfo{"app": app}, }, - } + Name: "network-manager", + Interface: "network-manager", + Apps: map[string]*snap.AppInfo{"app": app}, + }, nil) release.OnClassic = false apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.network-manager-client.nmcli"}) c.Assert(apparmorSpec.SnippetForTag("snap.network-manager-client.nmcli"), testutil.Contains, `peer=(label="snap.network-manager.app"),`) } func (s *NetworkManagerInterfaceSuite) TestConnectedPlugSnippedUsesUnconfinedLabelOnClassic(c *C) { - slot := &interfaces.Slot{} + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{}, nil) release.OnClassic = true apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.network-manager-client.nmcli"}) c.Assert(apparmorSpec.SnippetForTag("snap.network-manager-client.nmcli"), testutil.Contains, "peer=(label=unconfined),") @@ -159,7 +157,7 @@ func (s *NetworkManagerInterfaceSuite) TestConnectedSlotSnippetAppArmor(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedSlot(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedSlot(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.network-manager.nm"}) c.Assert(apparmorSpec.SnippetForTag("snap.network-manager.nm"), testutil.Contains, `/org/freedesktop/NetworkManager`) @@ -167,26 +165,26 @@ func (s *NetworkManagerInterfaceSuite) TestUsedSecuritySystems(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) - err = apparmorSpec.AddPermanentSlot(s.iface, s.slot) + err = apparmorSpec.AddPermanentSlot(s.iface, s.slotInfo) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), HasLen, 2) dbusSpec := &dbus.Specification{} - err = dbusSpec.AddPermanentSlot(s.iface, s.slot) + err = dbusSpec.AddPermanentSlot(s.iface, s.slotInfo) c.Assert(err, IsNil) c.Assert(dbusSpec.SecurityTags(), HasLen, 1) dbusSpec = &dbus.Specification{} - err = dbusSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err = dbusSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(dbusSpec.SecurityTags(), HasLen, 0) } func (s *NetworkManagerInterfaceSuite) TestSecCompPermanentSlot(c *C) { seccompSpec := &seccomp.Specification{} - err := seccompSpec.AddPermanentSlot(s.iface, s.slot) + err := seccompSpec.AddPermanentSlot(s.iface, s.slotInfo) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.network-manager.nm"}) c.Check(seccompSpec.SnippetForTag("snap.network-manager.nm"), testutil.Contains, "listen\n") @@ -194,9 +192,11 @@ func (s *NetworkManagerInterfaceSuite) TestUDevPermanentSlot(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.slot), IsNil) - c.Assert(spec.Snippets(), HasLen, 1) - c.Assert(spec.Snippets()[0], Equals, `KERNEL=="rfkill", TAG+="snap_network-manager_nm"`) + c.Assert(spec.AddPermanentSlot(s.iface, s.slotInfo), IsNil) + c.Assert(spec.Snippets(), HasLen, 2) + c.Assert(spec.Snippets(), testutil.Contains, `# network-manager +KERNEL=="rfkill", TAG+="snap_network-manager_nm"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_network-manager_nm", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_network-manager_nm $devpath $major:$minor"`) } func (s *NetworkManagerInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/network_observe.go snapd-2.31.1+17.10/interfaces/builtin/network_observe.go --- snapd-2.29.4.2+17.10/interfaces/builtin/network_observe.go 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/network_observe.go 2018-01-31 08:46:54.000000000 +0000 @@ -35,9 +35,9 @@ # it gives privileged read-only access to networking information and should # only be used with trusted apps. -# network-monitor can't allow this otherwise we are basically -# network-management, but don't explicitly deny since someone might try to use -# network-management with network-monitor and that shouldn't fail weirdly +# network-observe can't allow this otherwise we are basically network-control, +# but don't explicitly deny since someone might try to use network-control with +# network-observe and that shouldn't fail weirdly #capability net_admin, #include diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/network_observe_test.go snapd-2.31.1+17.10/interfaces/builtin/network_observe_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/network_observe_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/network_observe_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -32,9 +32,11 @@ ) type NetworkObserveInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } const netobsMockPlugSnapInfoYaml = `name: other @@ -50,15 +52,15 @@ }) func (s *NetworkObserveInterfaceSuite) SetUpTest(c *C) { - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "network-observe", - Interface: "network-observe", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "network-observe", + Interface: "network-observe", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, netobsMockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["network-observe"]} + s.plugInfo = plugSnap.Plugs["network-observe"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *NetworkObserveInterfaceSuite) TestName(c *C) { @@ -66,31 +68,31 @@ } func (s *NetworkObserveInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "network-observe", Interface: "network-observe", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "network-observe slots are reserved for the core snap") } func (s *NetworkObserveInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *NetworkObserveInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app2"), testutil.Contains, `net_raw`) // connected plugs have a non-nil security snippet for seccomp seccompSpec := &seccomp.Specification{} - err = seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err = seccompSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) c.Check(seccompSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "capset\n") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/network_setup_control_test.go snapd-2.31.1+17.10/interfaces/builtin/network_setup_control_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/network_setup_control_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/network_setup_control_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type NetworkSetupControlInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&NetworkSetupControlInterfaceSuite{ @@ -48,14 +50,14 @@ command: foo plugs: [network-setup-control] `, nil) - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "network-setup-control", - Interface: "network-setup-control", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "network-setup-control", + Interface: "network-setup-control", } - s.plug = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["network-setup-control"]} + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) + s.plugInfo = consumingSnapInfo.Plugs["network-setup-control"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *NetworkSetupControlInterfaceSuite) TestName(c *C) { @@ -63,24 +65,24 @@ } func (s *NetworkSetupControlInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "network-setup-control", Interface: "network-setup-control", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "network-setup-control slots are reserved for the core snap") } func (s *NetworkSetupControlInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *NetworkSetupControlInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `/etc/netplan/{,**} rw,`) diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/network_setup_observe_test.go snapd-2.31.1+17.10/interfaces/builtin/network_setup_observe_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/network_setup_observe_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/network_setup_observe_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type NetworkSetupObserveInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&NetworkSetupObserveInterfaceSuite{ @@ -48,15 +50,16 @@ command: foo plugs: [network-setup-observe] ` - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "network-setup-observe", - Interface: "network-setup-observe", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "network-setup-observe", + Interface: "network-setup-observe", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) + snapInfo := snaptest.MockInfo(c, mockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: snapInfo.Plugs["network-setup-observe"]} + s.plugInfo = snapInfo.Plugs["network-setup-observe"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *NetworkSetupObserveInterfaceSuite) TestName(c *C) { @@ -64,24 +67,24 @@ } func (s *NetworkSetupObserveInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "network-setup-observe", Interface: "network-setup-observe", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "network-setup-observe slots are reserved for the core snap") } func (s *NetworkSetupObserveInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *NetworkSetupObserveInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, "/etc/netplan") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/network_status.go snapd-2.31.1+17.10/interfaces/builtin/network_status.go --- snapd-2.29.4.2+17.10/interfaces/builtin/network_status.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/network_status.go 2018-01-24 20:02:44.000000000 +0000 @@ -25,6 +25,7 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/dbus" + "github.com/snapcore/snapd/snap" ) const networkStatusSummary = `allows operating as the NetworkingStatus service` @@ -119,26 +120,26 @@ } } -func (iface *networkStatusInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *networkStatusInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { const old = "###SLOT_SECURITY_TAGS###" new := slotAppLabelExpr(slot) spec.AddSnippet(strings.Replace(networkStatusConnectedPlugAppArmor, old, new, -1)) return nil } -func (iface *networkStatusInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *networkStatusInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { const old = "###PLUG_SECURITY_TAGS###" new := plugAppLabelExpr(plug) spec.AddSnippet(strings.Replace(networkStatusConnectedSlotAppArmor, old, new, -1)) return nil } -func (iface *networkStatusInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { +func (iface *networkStatusInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(networkStatusPermanentSlotAppArmor) return nil } -func (iface *networkStatusInterface) DBusPermanentSlot(spec *dbus.Specification, slot *interfaces.Slot) error { +func (iface *networkStatusInterface) DBusPermanentSlot(spec *dbus.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(networkStatusPermanentSlotDBus) return nil } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/network_status_test.go snapd-2.31.1+17.10/interfaces/builtin/network_status_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/network_status_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/network_status_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -26,14 +26,17 @@ "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/builtin" "github.com/snapcore/snapd/interfaces/dbus" + "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap/snaptest" "github.com/snapcore/snapd/testutil" ) type NetworkStatusSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&NetworkStatusSuite{ @@ -49,7 +52,8 @@ slots: [network-status] ` providerInfo := snaptest.MockInfo(c, providerYaml, nil) - s.slot = &interfaces.Slot{SlotInfo: providerInfo.Slots["network-status"]} + s.slotInfo = providerInfo.Slots["network-status"] + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) const consumerYaml = `name: consumer version: 1.0 @@ -59,7 +63,8 @@ plugs: [network-status] ` consumerInfo := snaptest.MockInfo(c, consumerYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: consumerInfo.Plugs["network-status"]} + s.plugInfo = consumerInfo.Plugs["network-status"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *NetworkStatusSuite) TestName(c *C) { @@ -68,7 +73,7 @@ func (s *NetworkStatusSuite) TestAppArmorConnectedPlug(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, `peer=(label="snap.provider.app"`) c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "interface=com.ubuntu.connectivity1.NetworkingStatus{,/**}") @@ -76,7 +81,7 @@ func (s *NetworkStatusSuite) TestAppArmorConnectedSlot(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedSlot(s.iface, s.plug, nil, s.slot, nil), IsNil) + c.Assert(spec.AddConnectedSlot(s.iface, s.plug, s.slot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.provider.app"}) c.Assert(spec.SnippetForTag("snap.provider.app"), testutil.Contains, "interface=org.freedesktop.DBus.*") c.Assert(spec.SnippetForTag("snap.provider.app"), testutil.Contains, `peer=(label="snap.consumer.app")`) @@ -84,7 +89,7 @@ func (s *NetworkStatusSuite) TestAppArmorPermanentSlot(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.slot), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.slotInfo), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.provider.app"}) c.Assert(spec.SnippetForTag("snap.provider.app"), testutil.Contains, "dbus (bind)") c.Assert(spec.SnippetForTag("snap.provider.app"), testutil.Contains, `name="com.ubuntu.connectivity1.NetworkingStatus"`) @@ -92,7 +97,7 @@ func (s *NetworkStatusSuite) TestDBusPermanentSlot(c *C) { spec := &dbus.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.slot), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.slotInfo), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.provider.app"}) c.Assert(spec.SnippetForTag("snap.provider.app"), testutil.Contains, ``) c.Assert(spec.SnippetForTag("snap.provider.app"), testutil.Contains, ``) diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/network_test.go snapd-2.31.1+17.10/interfaces/builtin/network_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/network_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/network_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -32,9 +32,11 @@ ) type NetworkInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } const netMockPlugSnapInfoYaml = `name: other @@ -50,15 +52,15 @@ }) func (s *NetworkInterfaceSuite) SetUpTest(c *C) { - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "network", - Interface: "network", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "network", + Interface: "network", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, netMockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["network"]} + s.plugInfo = plugSnap.Plugs["network"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *NetworkInterfaceSuite) TestName(c *C) { @@ -66,31 +68,31 @@ } func (s *NetworkInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "network", Interface: "network", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "network slots are reserved for the core snap") } func (s *NetworkInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *NetworkInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app2"), testutil.Contains, `tcp_fastopen`) // connected plugs have a non-nil security snippet for seccomp seccompSpec := &seccomp.Specification{} - err = seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err = seccompSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) c.Check(seccompSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "bind\n") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/ofono.go snapd-2.31.1+17.10/interfaces/builtin/ofono.go --- snapd-2.29.4.2+17.10/interfaces/builtin/ofono.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/ofono.go 2018-01-24 20:02:44.000000000 +0000 @@ -28,6 +28,7 @@ "github.com/snapcore/snapd/interfaces/seccomp" "github.com/snapcore/snapd/interfaces/udev" "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" ) const ofonoSummary = `allows operating as the ofono service` @@ -194,7 +195,6 @@ accept4 bind listen -shutdown socket AF_NETLINK - NETLINK_ROUTE # libudev socket AF_NETLINK - NETLINK_KOBJECT_UEVENT @@ -303,7 +303,7 @@ } } -func (iface *ofonoInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *ofonoInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###SLOT_SECURITY_TAGS###" new := slotAppLabelExpr(slot) spec.AddSnippet(strings.Replace(ofonoConnectedPlugAppArmor, old, new, -1)) @@ -315,17 +315,17 @@ } -func (iface *ofonoInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { +func (iface *ofonoInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(ofonoPermanentSlotAppArmor) return nil } -func (iface *ofonoInterface) DBusPermanentSlot(spec *dbus.Specification, slot *interfaces.Slot) error { +func (iface *ofonoInterface) DBusPermanentSlot(spec *dbus.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(ofonoPermanentSlotDBus) return nil } -func (iface *ofonoInterface) UDevPermanentSlot(spec *udev.Specification, slot *interfaces.Slot) error { +func (iface *ofonoInterface) UDevPermanentSlot(spec *udev.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(ofonoPermanentSlotUDev) /* 1.Linux modem drivers set up the modem device /dev/modem as a symbolic link @@ -336,19 +336,18 @@ */ spec.TagDevice(`KERNEL=="tty[A-Z]*[0-9]*|cdc-wdm[0-9]*"`) spec.TagDevice(`KERNEL=="tun"`) - spec.TagDevice(`KERNEL=="tun[0-9]*"`) spec.TagDevice(`KERNEL=="dsp"`) return nil } -func (iface *ofonoInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *ofonoInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###PLUG_SECURITY_TAGS###" new := plugAppLabelExpr(plug) spec.AddSnippet(strings.Replace(ofonoConnectedSlotAppArmor, old, new, -1)) return nil } -func (iface *ofonoInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *interfaces.Slot) error { +func (iface *ofonoInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(ofonoPermanentSlotSecComp) return nil } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/ofono_test.go snapd-2.31.1+17.10/interfaces/builtin/ofono_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/ofono_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/ofono_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -34,9 +34,11 @@ ) type OfonoInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&OfonoInterfaceSuite{ @@ -62,9 +64,11 @@ slots: [ofono] ` snapInfo := snaptest.MockInfo(c, mockSlotSnapInfoYaml, nil) - s.slot = &interfaces.Slot{SlotInfo: snapInfo.Slots["ofono"]} + s.slotInfo = snapInfo.Slots["ofono"] + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) snapInfo = snaptest.MockInfo(c, mockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: snapInfo.Plugs["ofono"]} + s.plugInfo = snapInfo.Plugs["ofono"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *OfonoInterfaceSuite) TestName(c *C) { @@ -75,21 +79,19 @@ func (s *OfonoInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelAll(c *C) { app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "ofono", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - Name: "ofono", - Interface: "ofono", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "ofono", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, }, - } + Name: "ofono", + Interface: "ofono", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) release.OnClassic = false apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `peer=(label="snap.ofono.*"),`) @@ -100,21 +102,19 @@ app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} app3 := &snap.AppInfo{Name: "app3"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "ofono", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, - }, - Name: "ofono", - Interface: "ofono", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "ofono", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, }, - } + Name: "ofono", + Interface: "ofono", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) release.OnClassic = false apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `peer=(label="snap.ofono.{app1,app2}"),`) @@ -123,21 +123,19 @@ // The label uses short form when exactly one app is bound to the ofono slot func (s *OfonoInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelOne(c *C) { app := &snap.AppInfo{Name: "app"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "ofono", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - Name: "ofono", - Interface: "ofono", - Apps: map[string]*snap.AppInfo{"app": app}, + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "ofono", + Apps: map[string]*snap.AppInfo{"app": app}, }, - } + Name: "ofono", + Interface: "ofono", + Apps: map[string]*snap.AppInfo{"app": app}, + }, nil) release.OnClassic = false apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `peer=(label="snap.ofono.app"),`) @@ -147,7 +145,7 @@ release.OnClassic = true apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) snippet := apparmorSpec.SnippetForTag("snap.other.app") @@ -160,7 +158,7 @@ func (s *OfonoInterfaceSuite) TestConnectedPlugSnippetAppArmor(c *C) { release.OnClassic = false apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) snippet := apparmorSpec.SnippetForTag("snap.other.app") @@ -172,7 +170,7 @@ func (s *OfonoInterfaceSuite) TestConnectedSlotSnippetAppArmor(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedSlot(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedSlot(s.iface, s.plug, s.slot) c.Assert(err, IsNil) aasnippets := apparmorSpec.Snippets() c.Assert(aasnippets, HasLen, 1) @@ -183,7 +181,7 @@ func (s *OfonoInterfaceSuite) TestPermanentSlotSnippetAppArmor(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddPermanentSlot(s.iface, s.slot) + err := apparmorSpec.AddPermanentSlot(s.iface, s.slotInfo) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.ofono.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.ofono.app"), testutil.Contains, "/dev/net/tun rw,") @@ -191,7 +189,7 @@ func (s *OfonoInterfaceSuite) TestPermanentSlotSnippetSecComp(c *C) { seccompSpec := &seccomp.Specification{} - err := seccompSpec.AddPermanentSlot(s.iface, s.slot) + err := seccompSpec.AddPermanentSlot(s.iface, s.slotInfo) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.ofono.app"}) c.Assert(seccompSpec.SnippetForTag("snap.ofono.app"), testutil.Contains, "listen\n") @@ -199,10 +197,16 @@ func (s *OfonoInterfaceSuite) TestPermanentSlotSnippetUDev(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.slot), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.slotInfo), IsNil) c.Assert(spec.Snippets(), HasLen, 5) c.Assert(spec.Snippets()[0], testutil.Contains, `LABEL="ofono_isi_end"`) - c.Assert(spec.Snippets(), testutil.Contains, `KERNEL=="tty[A-Z]*[0-9]*|cdc-wdm[0-9]*", TAG+="snap_ofono_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `# ofono +KERNEL=="tty[A-Z]*[0-9]*|cdc-wdm[0-9]*", TAG+="snap_ofono_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `# ofono +KERNEL=="tun", TAG+="snap_ofono_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `# ofono +KERNEL=="dsp", TAG+="snap_ofono_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_ofono_app", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_ofono_app $devpath $major:$minor"`) } func (s *OfonoInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/online_accounts_service.go snapd-2.31.1+17.10/interfaces/builtin/online_accounts_service.go --- snapd-2.29.4.2+17.10/interfaces/builtin/online_accounts_service.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/online_accounts_service.go 2018-01-24 20:02:44.000000000 +0000 @@ -25,6 +25,7 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/seccomp" + "github.com/snapcore/snapd/snap" ) const onlineAccountsServiceSummary = `allows operating as the Online Accounts service` @@ -94,7 +95,6 @@ accept4 bind listen -shutdown ` type onlineAccountsServiceInterface struct{} @@ -110,26 +110,26 @@ } } -func (iface *onlineAccountsServiceInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *onlineAccountsServiceInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###SLOT_SECURITY_TAGS###" new := slotAppLabelExpr(slot) spec.AddSnippet(strings.Replace(onlineAccountsServiceConnectedPlugAppArmor, old, new, -1)) return nil } -func (iface *onlineAccountsServiceInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *onlineAccountsServiceInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###PLUG_SECURITY_TAGS###" new := plugAppLabelExpr(plug) spec.AddSnippet(strings.Replace(onlineAccountsServiceConnectedSlotAppArmor, old, new, -1)) return nil } -func (iface *onlineAccountsServiceInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { +func (iface *onlineAccountsServiceInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(onlineAccountsServicePermanentSlotAppArmor) return nil } -func (iface *onlineAccountsServiceInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *interfaces.Slot) error { +func (iface *onlineAccountsServiceInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(onlineAccountsServicePermanentSlotSecComp) return nil } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/online_accounts_service_test.go snapd-2.31.1+17.10/interfaces/builtin/online_accounts_service_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/online_accounts_service_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/online_accounts_service_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -26,14 +26,17 @@ "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/builtin" "github.com/snapcore/snapd/interfaces/seccomp" + "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap/snaptest" "github.com/snapcore/snapd/testutil" ) type OnlineAccountsServiceInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&OnlineAccountsServiceInterfaceSuite{ @@ -52,7 +55,8 @@ slots: [online-accounts-service] ` providerInfo := snaptest.MockInfo(c, providerYaml, nil) - s.slot = &interfaces.Slot{SlotInfo: providerInfo.Slots["online-accounts-service"]} + s.slotInfo = providerInfo.Slots["online-accounts-service"] + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) var consumerYaml = `name: consumer version: 1.0 @@ -62,7 +66,8 @@ plugs: [online-accounts-service] ` consumerInfo := snaptest.MockInfo(c, consumerYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: consumerInfo.Plugs["online-accounts-service"]} + s.plugInfo = consumerInfo.Plugs["online-accounts-service"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *OnlineAccountsServiceInterfaceSuite) TestName(c *C) { @@ -70,26 +75,26 @@ } func (s *OnlineAccountsServiceInterfaceSuite) TestSanitize(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) - c.Assert(s.slot.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) } func (s *OnlineAccountsServiceInterfaceSuite) TestAppArmorConnectedPlug(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) c.Assert(spec.SecurityTags(), HasLen, 1) c.Check(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `peer=(label="snap.provider.app")`) } func (s *OnlineAccountsServiceInterfaceSuite) TestAppArmorConnectedSlot(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedSlot(s.iface, s.plug, nil, s.slot, nil), IsNil) + c.Assert(spec.AddConnectedSlot(s.iface, s.plug, s.slot), IsNil) c.Check(spec.SnippetForTag("snap.provider.app"), testutil.Contains, `peer=(label="snap.consumer.app")`) } func (s *OnlineAccountsServiceInterfaceSuite) TestAppArrmorPermanentSlot(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.slot), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.slotInfo), IsNil) c.Assert(spec.Snippets(), HasLen, 1) c.Assert(spec.SnippetForTag("snap.provider.app"), testutil.Contains, `member={RequestName,ReleaseName,GetConnectionCredentials}`) c.Assert(spec.SnippetForTag("snap.provider.app"), testutil.Contains, `name="com.ubuntu.OnlineAccounts.Manager"`) @@ -97,7 +102,7 @@ func (s *OnlineAccountsServiceInterfaceSuite) TestSecCompPermanentSlot(c *C) { spec := &seccomp.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.slot), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.slotInfo), IsNil) c.Check(spec.SnippetForTag("snap.provider.app"), testutil.Contains, "listen\n") } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/opengl.go snapd-2.31.1+17.10/interfaces/builtin/opengl.go --- snapd-2.29.4.2+17.10/interfaces/builtin/opengl.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/opengl.go 2018-02-16 20:27:57.000000000 +0000 @@ -31,54 +31,61 @@ const openglConnectedPlugAppArmor = ` # Description: Can access opengl. - # specific gl libs - /var/lib/snapd/lib/gl/ r, - /var/lib/snapd/lib/gl/** rm, - - # Supports linux-driver-management from Solus (staged symlink trees into libdirs) - /var/lib/snapd/hostfs/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}glx-provider/**.so{,.*} rm, - - # Bi-arch distribution nvidia support - /var/lib/snapd/hostfs/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libcuda*.so{,.*} rm, - /var/lib/snapd/hostfs/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libnvidia*.so{,.*} rm, - /var/lib/snapd/hostfs/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libnvcuvid.so{,.*} rm, - /var/lib/snapd/hostfs/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}lib{GL,EGL}*nvidia.so{,.*} rm, - /var/lib/snapd/hostfs/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libGLdispatch.so{,.*} rm, - - # Main bi-arch GL libraries - /var/lib/snapd/hostfs/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}lib{GL,EGL}.so{,.*} rm, - - /dev/dri/ r, - /dev/dri/card0 rw, - # nvidia - @{PROC}/driver/nvidia/params r, - @{PROC}/modules r, - /dev/nvidia* rw, - unix (send, receive) type=dgram peer=(addr="@nvidia[0-9a-f]*"), - - # eglfs - /dev/vchiq rw, - - # /sys/devices - /sys/devices/pci[0-9]*/**/config r, - /sys/devices/pci[0-9]*/**/{,subsystem_}device r, - /sys/devices/pci[0-9]*/**/{,subsystem_}vendor r, - /sys/devices/**/drm{,_dp_aux_dev}/** r, - - # FIXME: this is an information leak and snapd should instead query udev for - # the specific accesses associated with the above devices. - /sys/bus/pci/devices/ r, - /sys/bus/platform/devices/soc:gpu/ r, - /run/udev/data/+drm:card* r, - /run/udev/data/+pci:[0-9]* r, - /run/udev/data/+platform:soc:gpu* r, - - # FIXME: for each device in /dev that this policy references, lookup the - # device type, major and minor and create rules of this form: - # /run/udev/data/: r, - # For now, allow 'c'haracter devices and 'b'lock devices based on - # https://www.kernel.org/doc/Documentation/devices.txt - /run/udev/data/c226:[0-9]* r, # 226 drm +# specific gl libs +/var/lib/snapd/lib/gl{,32}/ r, +/var/lib/snapd/lib/gl{,32}/** rm, + +# Bi-arch distribution nvidia support +/var/lib/snapd/hostfs/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libcuda*.so{,.*} rm, +/var/lib/snapd/hostfs/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libnvidia*.so{,.*} rm, +/var/lib/snapd/hostfs/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libnvcuvid.so{,.*} rm, +/var/lib/snapd/hostfs/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}lib{GL,EGL}*nvidia.so{,.*} rm, +/var/lib/snapd/hostfs/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libGLdispatch.so{,.*} rm, + +# Support reading the Vulkan ICD files +/var/lib/snapd/lib/vulkan/ r, +/var/lib/snapd/lib/vulkan/** r, +/var/lib/snapd/hostfs/usr/share/vulkan/icd.d/*nvidia*.json r, + +# Main bi-arch GL libraries +/var/lib/snapd/hostfs/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}{,nvidia*/}lib{GL,EGL,GLX}.so{,.*} rm, + +/dev/dri/ r, +/dev/dri/card0 rw, +# nvidia +/etc/vdpau_wrapper.cfg r, +@{PROC}/driver/nvidia/params r, +@{PROC}/modules r, +/dev/nvidia* rw, +unix (send, receive) type=dgram peer=(addr="@nvidia[0-9a-f]*"), + +# eglfs +/dev/vchiq rw, + +# Parallels guest tools 3D acceleration (video toolgate) +@{PROC}/driver/prl_vtg rw, + +# /sys/devices +/sys/devices/pci[0-9a-f]*/**/config r, +/sys/devices/pci[0-9a-f]*/**/revision r, +/sys/devices/pci[0-9a-f]*/**/{,subsystem_}device r, +/sys/devices/pci[0-9a-f]*/**/{,subsystem_}vendor r, +/sys/devices/**/drm{,_dp_aux_dev}/** r, + +# FIXME: this is an information leak and snapd should instead query udev for +# the specific accesses associated with the above devices. +/sys/bus/pci/devices/ r, +/sys/bus/platform/devices/soc:gpu/ r, +/run/udev/data/+drm:card* r, +/run/udev/data/+pci:[0-9a-f]* r, +/run/udev/data/+platform:soc:gpu* r, + +# FIXME: for each device in /dev that this policy references, lookup the +# device type, major and minor and create rules of this form: +# /run/udev/data/: r, +# For now, allow 'c'haracter devices and 'b'lock devices based on +# https://www.kernel.org/doc/Documentation/devices.txt +/run/udev/data/c226:[0-9]* r, # 226 drm ` // The nvidia modules don't use sysfs (therefore they can't be udev tagged) and diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/opengl_test.go snapd-2.31.1+17.10/interfaces/builtin/opengl_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/opengl_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/opengl_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type OpenglInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&OpenglInterfaceSuite{ @@ -53,8 +55,8 @@ ` func (s *OpenglInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, openglConsumerYaml, nil, "opengl") - s.slot = MockSlot(c, openglCoreYaml, nil, "opengl") + s.plug, s.plugInfo = MockConnectedPlug(c, openglConsumerYaml, nil, "opengl") + s.slot, s.slotInfo = MockConnectedSlot(c, openglCoreYaml, nil, "opengl") } func (s *OpenglInterfaceSuite) TestName(c *C) { @@ -62,32 +64,35 @@ } func (s *OpenglInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "opengl", Interface: "opengl", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "opengl slots are reserved for the core snap") } func (s *OpenglInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *OpenglInterfaceSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, `/dev/nvidia* rw,`) } func (s *OpenglInterfaceSuite) TestUDevSpec(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 2) - c.Assert(spec.Snippets(), testutil.Contains, `SUBSYSTEM=="drm", KERNEL=="card[0-9]*", TAG+="snap_consumer_app"`) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Snippets(), HasLen, 3) + c.Assert(spec.Snippets(), testutil.Contains, `# opengl +SUBSYSTEM=="drm", KERNEL=="card[0-9]*", TAG+="snap_consumer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_consumer_app", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_consumer_app $devpath $major:$minor"`) } func (s *OpenglInterfaceSuite) TestStaticInfo(c *C) { @@ -99,7 +104,8 @@ } func (s *OpenglInterfaceSuite) TestAutoConnect(c *C) { - c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) + // FIXME: fix AutoConnect methods to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) } func (s *OpenglInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/openvswitch_support_test.go snapd-2.31.1+17.10/interfaces/builtin/openvswitch_support_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/openvswitch_support_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/openvswitch_support_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -32,27 +32,15 @@ ) type OpenvSwitchSupportInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&OpenvSwitchSupportInterfaceSuite{ iface: builtin.MustInterface("openvswitch-support"), - slot: &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "openvswitch-support", - Interface: "openvswitch-support", - }, - }, - plug: &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{SuggestedName: "other"}, - Name: "openvswitch-support", - Interface: "openvswitch-support", - }, - }, }) func (s *OpenvSwitchSupportInterfaceSuite) SetUpTest(c *C) { @@ -63,15 +51,15 @@ command: foo plugs: [openvswitch-support] ` - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "openvswitch-support", - Interface: "openvswitch-support", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "openvswitch-support", + Interface: "openvswitch-support", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) snapInfo := snaptest.MockInfo(c, mockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: snapInfo.Plugs["openvswitch-support"]} + s.plugInfo = snapInfo.Plugs["openvswitch-support"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *OpenvSwitchSupportInterfaceSuite) TestName(c *C) { @@ -79,30 +67,30 @@ } func (s *OpenvSwitchSupportInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "openvswitch-support", Interface: "openvswitch-support", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "openvswitch-support slots are reserved for the core snap") } func (s *OpenvSwitchSupportInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *OpenvSwitchSupportInterfaceSuite) TestUsedSecuritySystems(c *C) { spec := &kmod.Specification{} - err := spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := spec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(spec.Modules(), DeepEquals, map[string]bool{ "openvswitch": true, }) apparmorSpec := &apparmor.Specification{} - err = apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err = apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, "/run/uuidd/request rw") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/openvswitch_test.go snapd-2.31.1+17.10/interfaces/builtin/openvswitch_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/openvswitch_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/openvswitch_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type OpenvSwitchInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&OpenvSwitchInterfaceSuite{ @@ -48,15 +50,15 @@ command: foo plugs: [openvswitch] ` - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "openvswitch", - Interface: "openvswitch", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "openvswitch", + Interface: "openvswitch", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) snapInfo := snaptest.MockInfo(c, mockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: snapInfo.Plugs["openvswitch"]} + s.plugInfo = snapInfo.Plugs["openvswitch"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *OpenvSwitchInterfaceSuite) TestName(c *C) { @@ -64,23 +66,23 @@ } func (s *OpenvSwitchInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "openvswitch", Interface: "openvswitch", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "openvswitch slots are reserved for the core snap") } func (s *OpenvSwitchInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *OpenvSwitchInterfaceSuite) TestUsedSecuritySystems(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, "run/openvswitch/db.sock rw") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/optical_drive_test.go snapd-2.31.1+17.10/interfaces/builtin/optical_drive_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/optical_drive_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/optical_drive_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type OpticalDriveInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&OpticalDriveInterfaceSuite{ @@ -53,8 +55,8 @@ ` func (s *OpticalDriveInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, opticalDriveConsumerYaml, nil, "optical-drive") - s.slot = MockSlot(c, opticalDriveCoreYaml, nil, "optical-drive") + s.plug, s.plugInfo = MockConnectedPlug(c, opticalDriveConsumerYaml, nil, "optical-drive") + s.slot, s.slotInfo = MockConnectedSlot(c, opticalDriveCoreYaml, nil, "optical-drive") } func (s *OpticalDriveInterfaceSuite) TestName(c *C) { @@ -62,32 +64,34 @@ } func (s *OpticalDriveInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "optical-drive", Interface: "optical-drive", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "optical-drive slots are reserved for the core snap") } func (s *OpticalDriveInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *OpticalDriveInterfaceSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, `/dev/sr[0-9]* r,`) } func (s *OpticalDriveInterfaceSuite) TestUDevSpec(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 2) - c.Assert(spec.Snippets(), testutil.Contains, `KERNEL=="sr[0-9]*", TAG+="snap_consumer_app"`) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Snippets(), HasLen, 3) + c.Assert(spec.Snippets(), testutil.Contains, `# optical-drive +KERNEL=="sr[0-9]*", TAG+="snap_consumer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_consumer_app", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_consumer_app $devpath $major:$minor"`) } func (s *OpticalDriveInterfaceSuite) TestStaticInfo(c *C) { @@ -99,7 +103,8 @@ } func (s *OpticalDriveInterfaceSuite) TestAutoConnect(c *C) { - c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) + // FIXME: fix AutoConnect methods to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) } func (s *OpticalDriveInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/password_manager_service_test.go snapd-2.31.1+17.10/interfaces/builtin/password_manager_service_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/password_manager_service_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/password_manager_service_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type passwordManagerServiceInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&passwordManagerServiceInterfaceSuite{ @@ -48,15 +50,15 @@ command: foo plugs: [password-manager-service] ` - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "password-manager-service", - Interface: "password-manager-service", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "password-manager-service", + Interface: "password-manager-service", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) snapInfo := snaptest.MockInfo(c, mockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: snapInfo.Plugs["password-manager-service"]} + s.plugInfo = snapInfo.Plugs["password-manager-service"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *passwordManagerServiceInterfaceSuite) TestName(c *C) { @@ -64,24 +66,24 @@ } func (s *passwordManagerServiceInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "password-manager-service", Interface: "password-manager-service", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "password-manager-service slots are reserved for the core snap") } func (s *passwordManagerServiceInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *passwordManagerServiceInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Check(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, "interface=org.freedesktop.Secret") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/physical_memory_control_test.go snapd-2.31.1+17.10/interfaces/builtin/physical_memory_control_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/physical_memory_control_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/physical_memory_control_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type PhysicalMemoryControlInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&PhysicalMemoryControlInterfaceSuite{ @@ -53,8 +55,8 @@ ` func (s *PhysicalMemoryControlInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, physicalMemoryControlConsumerYaml, nil, "physical-memory-control") - s.slot = MockSlot(c, physicalMemoryControlCoreYaml, nil, "physical-memory-control") + s.plug, s.plugInfo = MockConnectedPlug(c, physicalMemoryControlConsumerYaml, nil, "physical-memory-control") + s.slot, s.slotInfo = MockConnectedSlot(c, physicalMemoryControlCoreYaml, nil, "physical-memory-control") } func (s *PhysicalMemoryControlInterfaceSuite) TestName(c *C) { @@ -62,32 +64,34 @@ } func (s *PhysicalMemoryControlInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "physical-memory-control", Interface: "physical-memory-control", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "physical-memory-control slots are reserved for the core snap") } func (s *PhysicalMemoryControlInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *PhysicalMemoryControlInterfaceSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, `/dev/mem rw,`) } func (s *PhysicalMemoryControlInterfaceSuite) TestUDevSpec(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 1) - c.Assert(spec.Snippets()[0], DeepEquals, `KERNEL=="mem", TAG+="snap_consumer_app"`) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Snippets(), HasLen, 2) + c.Assert(spec.Snippets(), testutil.Contains, `# physical-memory-control +KERNEL=="mem", TAG+="snap_consumer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_consumer_app", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_consumer_app $devpath $major:$minor"`) } func (s *PhysicalMemoryControlInterfaceSuite) TestStaticInfo(c *C) { @@ -99,7 +103,8 @@ } func (s *PhysicalMemoryControlInterfaceSuite) TestAutoConnect(c *C) { - c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) + // FIXME: fix AutoConnect methods to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) } func (s *PhysicalMemoryControlInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/physical_memory_observe_test.go snapd-2.31.1+17.10/interfaces/builtin/physical_memory_observe_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/physical_memory_observe_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/physical_memory_observe_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -32,9 +32,11 @@ ) type PhysicalMemoryObserveInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&PhysicalMemoryObserveInterfaceSuite{ @@ -54,8 +56,8 @@ ` func (s *PhysicalMemoryObserveInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, physicalMemoryObserveConsumerYaml, nil, "physical-memory-observe") - s.slot = MockSlot(c, physicalMemoryObserveCoreYaml, nil, "physical-memory-observe") + s.plug, s.plugInfo = MockConnectedPlug(c, physicalMemoryObserveConsumerYaml, nil, "physical-memory-observe") + s.slot, s.slotInfo = MockConnectedSlot(c, physicalMemoryObserveCoreYaml, nil, "physical-memory-observe") } func (s *PhysicalMemoryObserveInterfaceSuite) TestName(c *C) { @@ -63,32 +65,34 @@ } func (s *PhysicalMemoryObserveInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "physical-memory-observe", Interface: "physical-memory-observe", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "physical-memory-observe slots are reserved for the core snap") } func (s *PhysicalMemoryObserveInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *PhysicalMemoryObserveInterfaceSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, `/dev/mem r,`) } func (s *PhysicalMemoryObserveInterfaceSuite) TestUDevSpec(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 1) - c.Assert(spec.Snippets()[0], DeepEquals, `KERNEL=="mem", TAG+="snap_consumer_app"`) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Snippets(), HasLen, 2) + c.Assert(spec.Snippets(), testutil.Contains, `# physical-memory-observe +KERNEL=="mem", TAG+="snap_consumer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_consumer_app", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_consumer_app $devpath $major:$minor"`) } func (s *PhysicalMemoryObserveInterfaceSuite) TestStaticInfo(c *C) { @@ -100,7 +104,8 @@ } func (s *PhysicalMemoryObserveInterfaceSuite) TestAutoConnect(c *C) { - c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) + // FIXME: fix AutoConnect methods to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) } func (s *PhysicalMemoryObserveInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/ppp_test.go snapd-2.31.1+17.10/interfaces/builtin/ppp_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/ppp_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/ppp_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -32,9 +32,11 @@ ) type PppInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&PppInterfaceSuite{ @@ -54,8 +56,8 @@ ` func (s *PppInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, pppConsumerYaml, nil, "ppp") - s.slot = MockSlot(c, pppCoreYaml, nil, "ppp") + s.plug, s.plugInfo = MockConnectedPlug(c, pppConsumerYaml, nil, "ppp") + s.slot, s.slotInfo = MockConnectedSlot(c, pppCoreYaml, nil, "ppp") } func (s *PppInterfaceSuite) TestName(c *C) { @@ -63,31 +65,31 @@ } func (s *PppInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "ppp", Interface: "ppp", - }} + } - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "ppp slots are reserved for the core snap") } func (s *PppInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *PppInterfaceSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, `/dev/ppp rw,`) } func (s *PppInterfaceSuite) TestKModSpec(c *C) { spec := &kmod.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) c.Assert(spec.Modules(), DeepEquals, map[string]bool{ "ppp_generic": true, }) @@ -95,9 +97,11 @@ func (s *PppInterfaceSuite) TestUDevSpec(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 2) - c.Assert(spec.Snippets(), testutil.Contains, `KERNEL=="ppp", TAG+="snap_consumer_app"`) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Snippets(), HasLen, 3) + c.Assert(spec.Snippets(), testutil.Contains, `# ppp +KERNEL=="ppp", TAG+="snap_consumer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_consumer_app", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_consumer_app $devpath $major:$minor"`) } func (s *PppInterfaceSuite) TestStaticInfo(c *C) { @@ -109,7 +113,8 @@ } func (s *PppInterfaceSuite) TestAutoConnect(c *C) { - c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) + // FIXME: fix AutoConnect methods to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) } func (s *PppInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/process_control_test.go snapd-2.31.1+17.10/interfaces/builtin/process_control_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/process_control_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/process_control_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -32,9 +32,11 @@ ) type ProcessControlInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } const procctlMockPlugSnapInfoYaml = `name: other @@ -50,15 +52,15 @@ }) func (s *ProcessControlInterfaceSuite) SetUpTest(c *C) { - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "process-control", - Interface: "process-control", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "process-control", + Interface: "process-control", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, procctlMockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["process-control"]} + s.plugInfo = plugSnap.Plugs["process-control"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *ProcessControlInterfaceSuite) TestName(c *C) { @@ -66,30 +68,30 @@ } func (s *ProcessControlInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "process-control", Interface: "process-control", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "process-control slots are reserved for the core snap") } func (s *ProcessControlInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *ProcessControlInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "capability sys_resource") seccompSpec := &seccomp.Specification{} - err = seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err = seccompSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) c.Check(seccompSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "sched_setaffinity\n") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/pulseaudio.go snapd-2.31.1+17.10/interfaces/builtin/pulseaudio.go --- snapd-2.29.4.2+17.10/interfaces/builtin/pulseaudio.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/pulseaudio.go 2018-01-24 20:02:44.000000000 +0000 @@ -25,6 +25,7 @@ "github.com/snapcore/snapd/interfaces/seccomp" "github.com/snapcore/snapd/interfaces/udev" "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" ) const pulseaudioSummary = `allows operating as or interacting with the pulseaudio service` @@ -141,7 +142,7 @@ } } -func (iface *pulseAudioInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *pulseAudioInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { spec.AddSnippet(pulseaudioConnectedPlugAppArmor) if release.OnClassic { spec.AddSnippet(pulseaudioConnectedPlugAppArmorDesktop) @@ -149,24 +150,24 @@ return nil } -func (iface *pulseAudioInterface) UDevPermanentSlot(spec *udev.Specification, slot *interfaces.Slot) error { +func (iface *pulseAudioInterface) UDevPermanentSlot(spec *udev.Specification, slot *snap.SlotInfo) error { spec.TagDevice(`KERNEL=="controlC[0-9]*"`) spec.TagDevice(`KERNEL=="pcmC[0-9]*D[0-9]*[cp]"`) spec.TagDevice(`KERNEL=="timer"`) return nil } -func (iface *pulseAudioInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { +func (iface *pulseAudioInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(pulseaudioPermanentSlotAppArmor) return nil } -func (iface *pulseAudioInterface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *pulseAudioInterface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { spec.AddSnippet(pulseaudioConnectedPlugSecComp) return nil } -func (iface *pulseAudioInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *interfaces.Slot) error { +func (iface *pulseAudioInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(pulseaudioPermanentSlotSecComp) return nil } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/pulseaudio_test.go snapd-2.31.1+17.10/interfaces/builtin/pulseaudio_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/pulseaudio_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/pulseaudio_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -26,15 +26,19 @@ "github.com/snapcore/snapd/interfaces/builtin" "github.com/snapcore/snapd/interfaces/seccomp" "github.com/snapcore/snapd/interfaces/udev" + "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap/snaptest" "github.com/snapcore/snapd/testutil" ) type PulseAudioInterfaceSuite struct { - iface interfaces.Interface - coreSlot *interfaces.Slot - classicSlot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + coreSlotInfo *snap.SlotInfo + coreSlot *interfaces.ConnectedSlot + classicSlotInfo *snap.SlotInfo + classicSlot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&PulseAudioInterfaceSuite{ @@ -69,13 +73,16 @@ func (s *PulseAudioInterfaceSuite) SetUpTest(c *C) { // pulseaudio snap with pulseaudio slot on an core/all-snap install. snapInfo := snaptest.MockInfo(c, pulseaudioMockCoreSlotSnapInfoYaml, nil) - s.coreSlot = &interfaces.Slot{SlotInfo: snapInfo.Slots["pulseaudio"]} + s.coreSlotInfo = snapInfo.Slots["pulseaudio"] + s.coreSlot = interfaces.NewConnectedSlot(s.coreSlotInfo, nil) // pulseaudio slot on a core snap in a classic install. snapInfo = snaptest.MockInfo(c, pulseaudioMockClassicSlotSnapInfoYaml, nil) - s.classicSlot = &interfaces.Slot{SlotInfo: snapInfo.Slots["pulseaudio"]} + s.classicSlotInfo = snapInfo.Slots["pulseaudio"] + s.classicSlot = interfaces.NewConnectedSlot(s.classicSlotInfo, nil) // snap with the pulseaudio plug snapInfo = snaptest.MockInfo(c, pulseaudioMockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: snapInfo.Plugs["pulseaudio"]} + s.plugInfo = snapInfo.Plugs["pulseaudio"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *PulseAudioInterfaceSuite) TestName(c *C) { @@ -83,19 +90,19 @@ } func (s *PulseAudioInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.coreSlot.Sanitize(s.iface), IsNil) - c.Assert(s.classicSlot.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.coreSlotInfo), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.classicSlotInfo), IsNil) } func (s *PulseAudioInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *PulseAudioInterfaceSuite) TestSecCompOnClassic(c *C) { seccompSpec := &seccomp.Specification{} - err := seccompSpec.AddPermanentSlot(s.iface, s.classicSlot) + err := seccompSpec.AddPermanentSlot(s.iface, s.classicSlotInfo) c.Assert(err, IsNil) - err = seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, s.classicSlot, nil) + err = seccompSpec.AddConnectedPlug(s.iface, s.plug, s.classicSlot) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) c.Check(seccompSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "shmctl\n") @@ -103,9 +110,9 @@ func (s *PulseAudioInterfaceSuite) TestSecCompOnAllSnaps(c *C) { seccompSpec := &seccomp.Specification{} - err := seccompSpec.AddPermanentSlot(s.iface, s.coreSlot) + err := seccompSpec.AddPermanentSlot(s.iface, s.coreSlotInfo) c.Assert(err, IsNil) - err = seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, s.coreSlot, nil) + err = seccompSpec.AddConnectedPlug(s.iface, s.plug, s.coreSlot) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2", "snap.pulseaudio.app1"}) c.Assert(seccompSpec.SnippetForTag("snap.pulseaudio.app1"), testutil.Contains, "listen\n") @@ -114,9 +121,15 @@ func (s *PulseAudioInterfaceSuite) TestUDev(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.coreSlot), IsNil) - c.Assert(spec.Snippets(), HasLen, 3) - c.Assert(spec.Snippets(), testutil.Contains, `KERNEL=="pcmC[0-9]*D[0-9]*[cp]", TAG+="snap_pulseaudio_app1"`) + c.Assert(spec.AddPermanentSlot(s.iface, s.coreSlotInfo), IsNil) + c.Assert(spec.Snippets(), HasLen, 4) + c.Assert(spec.Snippets(), testutil.Contains, `# pulseaudio +KERNEL=="controlC[0-9]*", TAG+="snap_pulseaudio_app1"`) + c.Assert(spec.Snippets(), testutil.Contains, `# pulseaudio +KERNEL=="pcmC[0-9]*D[0-9]*[cp]", TAG+="snap_pulseaudio_app1"`) + c.Assert(spec.Snippets(), testutil.Contains, `# pulseaudio +KERNEL=="timer", TAG+="snap_pulseaudio_app1"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_pulseaudio_app1", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_pulseaudio_app1 $devpath $major:$minor"`) } func (s *PulseAudioInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/raw_usb_test.go snapd-2.31.1+17.10/interfaces/builtin/raw_usb_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/raw_usb_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/raw_usb_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type RawUsbInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&RawUsbInterfaceSuite{ @@ -53,8 +55,8 @@ ` func (s *RawUsbInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, rawusbConsumerYaml, nil, "raw-usb") - s.slot = MockSlot(c, rawusbCoreYaml, nil, "raw-usb") + s.plug, s.plugInfo = MockConnectedPlug(c, rawusbConsumerYaml, nil, "raw-usb") + s.slot, s.slotInfo = MockConnectedSlot(c, rawusbCoreYaml, nil, "raw-usb") } func (s *RawUsbInterfaceSuite) TestName(c *C) { @@ -62,32 +64,34 @@ } func (s *RawUsbInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "raw-usb", Interface: "raw-usb", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "raw-usb slots are reserved for the core snap") } func (s *RawUsbInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *RawUsbInterfaceSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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/bus/usb/devices/`) } func (s *RawUsbInterfaceSuite) TestUDevSpec(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 1) - c.Assert(spec.Snippets()[0], testutil.Contains, `SUBSYSTEM=="usb", TAG+="snap_consumer_app"`) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Snippets(), HasLen, 2) + c.Assert(spec.Snippets(), testutil.Contains, `# raw-usb +SUBSYSTEM=="usb", TAG+="snap_consumer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_consumer_app", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_consumer_app $devpath $major:$minor"`) } func (s *RawUsbInterfaceSuite) TestStaticInfo(c *C) { @@ -99,7 +103,8 @@ } func (s *RawUsbInterfaceSuite) TestAutoConnect(c *C) { - c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) + // FIXME: fix AutoConnect methods to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) } func (s *RawUsbInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/removable_media.go snapd-2.31.1+17.10/interfaces/builtin/removable_media.go --- snapd-2.29.4.2+17.10/interfaces/builtin/removable_media.go 2017-11-30 07:12:26.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/removable_media.go 2018-01-24 20:02:44.000000000 +0000 @@ -41,7 +41,7 @@ # Mount points could be in /run/media//* or /media//* /{,run/}media/*/ r, -/{,run/}media/*/** rw, +/{,run/}media/*/** rwk, ` func init() { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/removable_media_test.go snapd-2.31.1+17.10/interfaces/builtin/removable_media_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/removable_media_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/removable_media_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type RemovableMediaInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&RemovableMediaInterfaceSuite{ @@ -48,14 +50,14 @@ command: foo plugs: [removable-media] `, nil) - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "removable-media", - Interface: "removable-media", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "removable-media", + Interface: "removable-media", } - s.plug = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["removable-media"]} + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) + s.plugInfo = consumingSnapInfo.Plugs["removable-media"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *RemovableMediaInterfaceSuite) TestName(c *C) { @@ -63,24 +65,24 @@ } func (s *RemovableMediaInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "removable-media", Interface: "removable-media", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "removable-media slots are reserved for the core snap") } func (s *RemovableMediaInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *RemovableMediaInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.client-snap.other"}) c.Check(apparmorSpec.SnippetForTag("snap.client-snap.other"), testutil.Contains, "/{,run/}media/*/ r") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/screen_inhibit_control.go snapd-2.31.1+17.10/interfaces/builtin/screen_inhibit_control.go --- snapd-2.29.4.2+17.10/interfaces/builtin/screen_inhibit_control.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/screen_inhibit_control.go 2018-01-31 08:46:54.000000000 +0000 @@ -56,11 +56,20 @@ peer=(label=unconfined), # freedesktop.org ScreenSaver +# compatibility rule dbus (send) bus=session - path=/{,org/freedesktop/,org.gnome/}Screensaver + path=/Screensaver interface=org.freedesktop.ScreenSaver - member=org.freedesktop.ScreenSaver.{Inhibit,UnInhibit,SimulateUserActivity} + member={Inhibit,UnInhibit,SimulateUserActivity} + peer=(label=unconfined), + +# API rule +dbus (send) + bus=session + path=/{,org/freedesktop/,org.gnome/}ScreenSaver + interface=org.freedesktop.ScreenSaver + member={Inhibit,UnInhibit,SimulateUserActivity} peer=(label=unconfined), # gnome, kde and cinnamon screensaver diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/screen_inhibit_control_test.go snapd-2.31.1+17.10/interfaces/builtin/screen_inhibit_control_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/screen_inhibit_control_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/screen_inhibit_control_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type ScreenInhibitControlInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&ScreenInhibitControlInterfaceSuite{ @@ -48,15 +50,15 @@ command: foo plugs: [screen-inhibit-control] ` - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "screen-inhibit-control", - Interface: "screen-inhibit-control", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "screen-inhibit-control", + Interface: "screen-inhibit-control", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) snapInfo := snaptest.MockInfo(c, mockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: snapInfo.Plugs["screen-inhibit-control"]} + s.plugInfo = snapInfo.Plugs["screen-inhibit-control"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *ScreenInhibitControlInterfaceSuite) TestName(c *C) { @@ -64,24 +66,24 @@ } func (s *ScreenInhibitControlInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "screen-inhibit-control", Interface: "screen-inhibit-control", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "screen-inhibit-control slots are reserved for the core snap") } func (s *ScreenInhibitControlInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *ScreenInhibitControlInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, "/com/canonical/Unity/Screen") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/serial_port.go snapd-2.31.1+17.10/interfaces/builtin/serial_port.go --- snapd-2.29.4.2+17.10/interfaces/builtin/serial_port.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/serial_port.go 2018-01-24 20:02:44.000000000 +0000 @@ -28,6 +28,7 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/udev" + "github.com/snapcore/snapd/snap" ) const serialPortSummary = `allows accessing a specific serial port` @@ -75,8 +76,8 @@ // are also specified var serialUDevSymlinkPattern = regexp.MustCompile("^/dev/serial-port-[a-z0-9]+$") -// SanitizeSlot checks validity of the defined slot -func (iface *serialPortInterface) SanitizeSlot(slot *interfaces.Slot) error { +// BeforePrepareSlot checks validity of the defined slot +func (iface *serialPortInterface) BeforePrepareSlot(slot *snap.SlotInfo) error { if err := sanitizeSlotReservedForOSOrGadget(iface, slot); err != nil { return err } @@ -112,6 +113,11 @@ if (usbProduct < 0x0) || (usbProduct > 0xFFFF) { return fmt.Errorf("serial-port usb-product attribute not valid: %d", usbProduct) } + + usbInterfaceNumber, ok := slot.Attrs["usb-interface-number"].(int64) + if ok && (usbInterfaceNumber < 0 || usbInterfaceNumber >= UsbMaxInterfaces) { + return fmt.Errorf("serial-port usb-interface-number attribute cannot be negative or larger than %d", UsbMaxInterfaces-1) + } } else { // Just a path attribute - must be a valid usb device node // Check the path attribute is in the allowable pattern @@ -122,24 +128,31 @@ return nil } -func (iface *serialPortInterface) UDevPermanentSlot(spec *udev.Specification, slot *interfaces.Slot) error { - usbVendor, vOk := slot.Attrs["usb-vendor"].(int64) - if !vOk { +func (iface *serialPortInterface) UDevPermanentSlot(spec *udev.Specification, slot *snap.SlotInfo) error { + var usbVendor, usbProduct, usbInterfaceNumber int64 + var path string + if err := slot.Attr("usb-vendor", &usbVendor); err != nil { return nil } - usbProduct, pOk := slot.Attrs["usb-product"].(int64) - if !pOk { + if err := slot.Attr("usb-product", &usbProduct); err != nil { return nil } - path, ok := slot.Attrs["path"].(string) - if !ok || path == "" { + if err := slot.Attr("path", &path); err != nil || path == "" { return nil } - spec.AddSnippet(string(udevUsbDeviceSnippet("tty", usbVendor, usbProduct, "SYMLINK", strings.TrimPrefix(path, "/dev/")))) + if err := slot.Attr("usb-interface-number", &usbInterfaceNumber); err == nil { + spec.AddSnippet(fmt.Sprintf(`# serial-port +IMPORT{builtin}="usb_id" +SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="%04x", ATTRS{idProduct}=="%04x", ENV{ID_USB_INTERFACE_NUM}=="%02x", SYMLINK+="%s"`, usbVendor, usbProduct, usbInterfaceNumber, strings.TrimPrefix(path, "/dev/"))) + } else { + spec.AddSnippet(fmt.Sprintf(`# serial-port +IMPORT{builtin}="usb_id" +SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="%04x", ATTRS{idProduct}=="%04x", SYMLINK+="%s"`, usbVendor, usbProduct, strings.TrimPrefix(path, "/dev/"))) + } return nil } -func (iface *serialPortInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *serialPortInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { if iface.hasUsbAttrs(slot) { // This apparmor rule is an approximation of serialDeviceNodePattern // (AARE is different than regex, so we must approximate). @@ -149,8 +162,8 @@ } // Path to fixed device node - path, pathOk := slot.Attrs["path"].(string) - if !pathOk { + var path string + if err := slot.Attr("path", &path); err != nil { return nil } cleanedPath := filepath.Clean(path) @@ -158,33 +171,33 @@ return nil } -func (iface *serialPortInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *serialPortInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { // For connected plugs, we use vendor and product ids if available, // otherwise add the kernel device - hasOnlyPath := true - if iface.hasUsbAttrs(slot) { - hasOnlyPath = false - } - - usbVendor, vOk := slot.Attrs["usb-vendor"].(int64) - if !vOk && !hasOnlyPath { + hasOnlyPath := !iface.hasUsbAttrs(slot) + var usbVendor, usbProduct int64 + var path string + if err := slot.Attr("usb-vendor", &usbVendor); err != nil && !hasOnlyPath { return nil } - usbProduct, pOk := slot.Attrs["usb-product"].(int64) - if !pOk && !hasOnlyPath { + if err := slot.Attr("usb-product", &usbProduct); err != nil && !hasOnlyPath { return nil } - - path, pathOk := slot.Attrs["path"].(string) - if !pathOk && hasOnlyPath { + if err := slot.Attr("path", &path); err != nil && hasOnlyPath { return nil } if hasOnlyPath { spec.TagDevice(fmt.Sprintf(`SUBSYSTEM=="tty", KERNEL=="%s"`, strings.TrimPrefix(path, "/dev/"))) } else { - spec.TagDevice(fmt.Sprintf(`IMPORT{builtin}="usb_id" + var usbInterfaceNumber int64 + if err := slot.Attr("usb-interface-number", &usbInterfaceNumber); err == nil { + spec.TagDevice(fmt.Sprintf(`IMPORT{builtin}="usb_id" +SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="%04x", ATTRS{idProduct}=="%04x", ENV{ID_USB_INTERFACE_NUM}=="%02x"`, usbVendor, usbProduct, usbInterfaceNumber)) + } else { + spec.TagDevice(fmt.Sprintf(`IMPORT{builtin}="usb_id" SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="%04x", ATTRS{idProduct}=="%04x"`, usbVendor, usbProduct)) + } } return nil } @@ -194,11 +207,15 @@ return true } -func (iface *serialPortInterface) hasUsbAttrs(slot *interfaces.Slot) bool { - if _, ok := slot.Attrs["usb-vendor"]; ok { +func (iface *serialPortInterface) hasUsbAttrs(attrs interfaces.Attrer) bool { + var v int64 + if err := attrs.Attr("usb-vendor", &v); err == nil { + return true + } + if err := attrs.Attr("usb-product", &v); err == nil { return true } - if _, ok := slot.Attrs["usb-product"]; ok { + if err := attrs.Attr("usb-interface-number", &v); err == nil { return true } return false diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/serial_port_test.go snapd-2.31.1+17.10/interfaces/builtin/serial_port_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/serial_port_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/serial_port_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -26,6 +26,7 @@ "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/snap/snaptest" "github.com/snapcore/snapd/testutil" ) @@ -35,37 +36,70 @@ iface interfaces.Interface // OS Snap - testSlot1 *interfaces.Slot - testSlot2 *interfaces.Slot - testSlot3 *interfaces.Slot - testSlot4 *interfaces.Slot - testSlot5 *interfaces.Slot - testSlot6 *interfaces.Slot - testSlot7 *interfaces.Slot - missingPathSlot *interfaces.Slot - badPathSlot1 *interfaces.Slot - badPathSlot2 *interfaces.Slot - badPathSlot3 *interfaces.Slot - badPathSlot4 *interfaces.Slot - badPathSlot5 *interfaces.Slot - badPathSlot6 *interfaces.Slot - badPathSlot7 *interfaces.Slot - badPathSlot8 *interfaces.Slot - badPathSlot9 *interfaces.Slot - badPathSlot10 *interfaces.Slot - badInterfaceSlot *interfaces.Slot + testSlot1 *interfaces.ConnectedSlot + testSlot1Info *snap.SlotInfo + testSlot2 *interfaces.ConnectedSlot + testSlot2Info *snap.SlotInfo + testSlot3 *interfaces.ConnectedSlot + testSlot3Info *snap.SlotInfo + testSlot4 *interfaces.ConnectedSlot + testSlot4Info *snap.SlotInfo + testSlot5 *interfaces.ConnectedSlot + testSlot5Info *snap.SlotInfo + testSlot6 *interfaces.ConnectedSlot + testSlot6Info *snap.SlotInfo + testSlot7 *interfaces.ConnectedSlot + testSlot7Info *snap.SlotInfo + missingPathSlot *interfaces.ConnectedSlot + missingPathSlotInfo *snap.SlotInfo + badPathSlot1 *interfaces.ConnectedSlot + badPathSlot1Info *snap.SlotInfo + badPathSlot2 *interfaces.ConnectedSlot + badPathSlot2Info *snap.SlotInfo + badPathSlot3 *interfaces.ConnectedSlot + badPathSlot3Info *snap.SlotInfo + badPathSlot4 *interfaces.ConnectedSlot + badPathSlot4Info *snap.SlotInfo + badPathSlot5 *interfaces.ConnectedSlot + badPathSlot5Info *snap.SlotInfo + badPathSlot6 *interfaces.ConnectedSlot + badPathSlot6Info *snap.SlotInfo + badPathSlot7 *interfaces.ConnectedSlot + badPathSlot7Info *snap.SlotInfo + badPathSlot8 *interfaces.ConnectedSlot + badPathSlot8Info *snap.SlotInfo + badPathSlot9 *interfaces.ConnectedSlot + badPathSlot9Info *snap.SlotInfo + badPathSlot10 *interfaces.ConnectedSlot + badPathSlot10Info *snap.SlotInfo + badInterfaceSlot *interfaces.ConnectedSlot + badInterfaceSlotInfo *snap.SlotInfo // Gadget Snap - testUDev1 *interfaces.Slot - testUDev2 *interfaces.Slot - testUDevBadValue1 *interfaces.Slot - testUDevBadValue2 *interfaces.Slot - testUDevBadValue3 *interfaces.Slot + testUDev1 *interfaces.ConnectedSlot + testUDev1Info *snap.SlotInfo + testUDev2 *interfaces.ConnectedSlot + testUDev2Info *snap.SlotInfo + testUDev3 *interfaces.ConnectedSlot + testUDev3Info *snap.SlotInfo + testUDevBadValue1 *interfaces.ConnectedSlot + testUDevBadValue1Info *snap.SlotInfo + testUDevBadValue2 *interfaces.ConnectedSlot + testUDevBadValue2Info *snap.SlotInfo + testUDevBadValue3 *interfaces.ConnectedSlot + testUDevBadValue3Info *snap.SlotInfo + testUDevBadValue4 *interfaces.ConnectedSlot + testUDevBadValue4Info *snap.SlotInfo + testUDevBadValue5 *interfaces.ConnectedSlot + testUDevBadValue5Info *snap.SlotInfo // Consuming Snap - testPlugPort1 *interfaces.Plug - testPlugPort2 *interfaces.Plug - testPlugPort3 *interfaces.Plug + testPlugPort1 *interfaces.ConnectedPlug + testPlugPort1Info *snap.PlugInfo + testPlugPort2 *interfaces.ConnectedPlug + testPlugPort2Info *snap.PlugInfo + testPlugPort3 *interfaces.ConnectedPlug + testPlugPort3Info *snap.PlugInfo } var _ = Suite(&SerialPortInterfaceSuite{ @@ -131,25 +165,44 @@ path: /dev/ttyillegal0 bad-interface: other-interface `, nil) - s.testSlot1 = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["test-port-1"]} - s.testSlot2 = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["test-port-2"]} - s.testSlot3 = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["test-port-3"]} - s.testSlot4 = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["test-port-4"]} - s.testSlot5 = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["test-port-5"]} - s.testSlot6 = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["test-port-6"]} - s.testSlot7 = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["test-port-7"]} - s.missingPathSlot = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["missing-path"]} - s.badPathSlot1 = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["bad-path-1"]} - s.badPathSlot2 = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["bad-path-2"]} - s.badPathSlot3 = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["bad-path-3"]} - s.badPathSlot4 = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["bad-path-4"]} - s.badPathSlot5 = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["bad-path-5"]} - s.badPathSlot6 = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["bad-path-6"]} - s.badPathSlot7 = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["bad-path-7"]} - s.badPathSlot8 = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["bad-path-8"]} - s.badPathSlot9 = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["bad-path-9"]} - s.badPathSlot10 = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["bad-path-10"]} - s.badInterfaceSlot = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["bad-interface"]} + s.testSlot1Info = osSnapInfo.Slots["test-port-1"] + s.testSlot1 = interfaces.NewConnectedSlot(s.testSlot1Info, nil) + s.testSlot2Info = osSnapInfo.Slots["test-port-2"] + s.testSlot2 = interfaces.NewConnectedSlot(s.testSlot2Info, nil) + s.testSlot3Info = osSnapInfo.Slots["test-port-3"] + s.testSlot3 = interfaces.NewConnectedSlot(s.testSlot3Info, nil) + s.testSlot4Info = osSnapInfo.Slots["test-port-4"] + s.testSlot4 = interfaces.NewConnectedSlot(s.testSlot4Info, nil) + s.testSlot5Info = osSnapInfo.Slots["test-port-5"] + s.testSlot5 = interfaces.NewConnectedSlot(s.testSlot5Info, nil) + s.testSlot6Info = osSnapInfo.Slots["test-port-6"] + s.testSlot6 = interfaces.NewConnectedSlot(s.testSlot6Info, nil) + s.testSlot7Info = osSnapInfo.Slots["test-port-7"] + s.testSlot7 = interfaces.NewConnectedSlot(s.testSlot7Info, nil) + s.missingPathSlotInfo = osSnapInfo.Slots["missing-path"] + s.missingPathSlot = interfaces.NewConnectedSlot(s.missingPathSlotInfo, nil) + s.badPathSlot1Info = osSnapInfo.Slots["bad-path-1"] + s.badPathSlot1 = interfaces.NewConnectedSlot(s.badPathSlot1Info, nil) + s.badPathSlot2Info = osSnapInfo.Slots["bad-path-2"] + s.badPathSlot2 = interfaces.NewConnectedSlot(s.badPathSlot2Info, nil) + s.badPathSlot3Info = osSnapInfo.Slots["bad-path-3"] + s.badPathSlot3 = interfaces.NewConnectedSlot(s.badPathSlot3Info, nil) + s.badPathSlot4Info = osSnapInfo.Slots["bad-path-4"] + s.badPathSlot4 = interfaces.NewConnectedSlot(s.badPathSlot4Info, nil) + s.badPathSlot5Info = osSnapInfo.Slots["bad-path-5"] + s.badPathSlot5 = interfaces.NewConnectedSlot(s.badPathSlot5Info, nil) + s.badPathSlot6Info = osSnapInfo.Slots["bad-path-6"] + s.badPathSlot6 = interfaces.NewConnectedSlot(s.badPathSlot6Info, nil) + s.badPathSlot7Info = osSnapInfo.Slots["bad-path-7"] + s.badPathSlot7 = interfaces.NewConnectedSlot(s.badPathSlot7Info, nil) + s.badPathSlot8Info = osSnapInfo.Slots["bad-path-8"] + s.badPathSlot8 = interfaces.NewConnectedSlot(s.badPathSlot8Info, nil) + s.badPathSlot9Info = osSnapInfo.Slots["bad-path-9"] + s.badPathSlot9 = interfaces.NewConnectedSlot(s.badPathSlot9Info, nil) + s.badPathSlot10Info = osSnapInfo.Slots["bad-path-10"] + s.badPathSlot10 = interfaces.NewConnectedSlot(s.badPathSlot10Info, nil) + s.badInterfaceSlotInfo = osSnapInfo.Slots["bad-interface"] + s.badInterfaceSlot = interfaces.NewConnectedSlot(s.badInterfaceSlotInfo, nil) gadgetSnapInfo := snaptest.MockInfo(c, ` name: some-device @@ -165,6 +218,12 @@ usb-vendor: 0xffff usb-product: 0xffff path: /dev/serial-port-mydevice + test-udev-3: + interface: serial-port + usb-vendor: 0xabcd + usb-product: 0x1234 + usb-interface-number: 0 + path: /dev/serial-port-myserial test-udev-bad-value-1: interface: serial-port usb-vendor: -1 @@ -180,12 +239,35 @@ usb-vendor: 0x789a usb-product: 0x4321 path: /dev/my-device + test-udev-bad-value-4: + interface: serial-port + usb-vendor: 0x1234 + usb-product: 0x4321 + usb-interface-number: -1 + path: /dev/serial-port-mybadinterface + test-udev-bad-value-5: + interface: serial-port + usb-vendor: 0x1234 + usb-product: 0x4321 + usb-interface-number: 32 + path: /dev/serial-port-overinterfacenumber `, nil) - s.testUDev1 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-1"]} - s.testUDev2 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-2"]} - s.testUDevBadValue1 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-1"]} - s.testUDevBadValue2 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-2"]} - s.testUDevBadValue3 = &interfaces.Slot{SlotInfo: gadgetSnapInfo.Slots["test-udev-bad-value-3"]} + s.testUDev1Info = gadgetSnapInfo.Slots["test-udev-1"] + s.testUDev1 = interfaces.NewConnectedSlot(s.testUDev1Info, nil) + s.testUDev2Info = gadgetSnapInfo.Slots["test-udev-2"] + s.testUDev2 = interfaces.NewConnectedSlot(s.testUDev2Info, nil) + s.testUDev3Info = gadgetSnapInfo.Slots["test-udev-3"] + s.testUDev3 = interfaces.NewConnectedSlot(s.testUDev3Info, nil) + s.testUDevBadValue1Info = gadgetSnapInfo.Slots["test-udev-bad-value-1"] + s.testUDevBadValue1 = interfaces.NewConnectedSlot(s.testUDevBadValue1Info, nil) + s.testUDevBadValue2Info = gadgetSnapInfo.Slots["test-udev-bad-value-2"] + s.testUDevBadValue3 = interfaces.NewConnectedSlot(s.testUDevBadValue2Info, nil) + s.testUDevBadValue3Info = gadgetSnapInfo.Slots["test-udev-bad-value-3"] + s.testUDevBadValue3 = interfaces.NewConnectedSlot(s.testUDevBadValue3Info, nil) + s.testUDevBadValue4Info = gadgetSnapInfo.Slots["test-udev-bad-value-4"] + s.testUDevBadValue4 = interfaces.NewConnectedSlot(s.testUDevBadValue4Info, nil) + s.testUDevBadValue5Info = gadgetSnapInfo.Slots["test-udev-bad-value-5"] + s.testUDevBadValue5 = interfaces.NewConnectedSlot(s.testUDevBadValue5Info, nil) consumingSnapInfo := snaptest.MockInfo(c, ` name: client-snap @@ -208,9 +290,12 @@ command: foo plugs: [plug-for-port-3] `, nil) - s.testPlugPort1 = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["plug-for-port-1"]} - s.testPlugPort2 = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["plug-for-port-2"]} - s.testPlugPort3 = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["plug-for-port-3"]} + s.testPlugPort1Info = consumingSnapInfo.Plugs["plug-for-port-1"] + s.testPlugPort1 = interfaces.NewConnectedPlug(s.testPlugPort1Info, nil) + s.testPlugPort2Info = consumingSnapInfo.Plugs["plug-for-port-2"] + s.testPlugPort2 = interfaces.NewConnectedPlug(s.testPlugPort2Info, nil) + s.testPlugPort3Info = consumingSnapInfo.Plugs["plug-for-port-3"] + s.testPlugPort3 = interfaces.NewConnectedPlug(s.testPlugPort3Info, nil) } func (s *SerialPortInterfaceSuite) TestName(c *C) { @@ -218,98 +303,141 @@ } func (s *SerialPortInterfaceSuite) TestSanitizeCoreSnapSlots(c *C) { - for _, slot := range []*interfaces.Slot{s.testSlot1, s.testSlot2, s.testSlot3, s.testSlot4, s.testSlot5, s.testSlot6, s.testSlot7} { - c.Assert(slot.Sanitize(s.iface), IsNil) + for _, slot := range []*snap.SlotInfo{s.testSlot1Info, s.testSlot2Info, s.testSlot3Info, s.testSlot4Info, s.testSlot5Info, s.testSlot6Info, s.testSlot7Info} { + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), IsNil) } } func (s *SerialPortInterfaceSuite) TestSanitizeBadCoreSnapSlots(c *C) { // Slots without the "path" attribute are rejected. - c.Assert(s.missingPathSlot.Sanitize(s.iface), ErrorMatches, `serial-port slot must have a path attribute`) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.missingPathSlotInfo), ErrorMatches, `serial-port slot must have a path attribute`) // Slots with incorrect value of the "path" attribute are rejected. - for _, slot := range []*interfaces.Slot{s.badPathSlot1, s.badPathSlot2, s.badPathSlot3, s.badPathSlot4, s.badPathSlot5, s.badPathSlot6, s.badPathSlot7, s.badPathSlot8, s.badPathSlot9, s.badPathSlot10} { - c.Assert(slot.Sanitize(s.iface), ErrorMatches, "serial-port path attribute must be a valid device node") + for _, slot := range []*snap.SlotInfo{s.badPathSlot1Info, s.badPathSlot2Info, s.badPathSlot3Info, s.badPathSlot4Info, s.badPathSlot5Info, s.badPathSlot6Info, s.badPathSlot7Info, s.badPathSlot8Info, s.badPathSlot9Info, s.badPathSlot10Info} { + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "serial-port path attribute must be a valid device node") } } func (s *SerialPortInterfaceSuite) TestSanitizeGadgetSnapSlots(c *C) { - c.Assert(s.testUDev1.Sanitize(s.iface), IsNil) - c.Assert(s.testUDev2.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDev1Info), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDev2Info), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDev3Info), IsNil) } func (s *SerialPortInterfaceSuite) TestSanitizeBadGadgetSnapSlots(c *C) { - c.Assert(s.testUDevBadValue1.Sanitize(s.iface), ErrorMatches, "serial-port usb-vendor attribute not valid: -1") - c.Assert(s.testUDevBadValue2.Sanitize(s.iface), ErrorMatches, "serial-port usb-product attribute not valid: 65536") - c.Assert(s.testUDevBadValue3.Sanitize(s.iface), ErrorMatches, "serial-port path attribute specifies invalid symlink location") + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue1Info), ErrorMatches, "serial-port usb-vendor attribute not valid: -1") + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue2Info), ErrorMatches, "serial-port usb-product attribute not valid: 65536") + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue3Info), ErrorMatches, "serial-port path attribute specifies invalid symlink location") + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue4Info), ErrorMatches, "serial-port usb-interface-number attribute cannot be negative or larger than 31") + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue5Info), ErrorMatches, "serial-port usb-interface-number attribute cannot be negative or larger than 31") } func (s *SerialPortInterfaceSuite) TestPermanentSlotUDevSnippets(c *C) { spec := &udev.Specification{} - for _, slot := range []*interfaces.Slot{s.testSlot1, s.testSlot2, s.testSlot3, s.testSlot4} { + for _, slot := range []*snap.SlotInfo{s.testSlot1Info, s.testSlot2Info, s.testSlot3Info, s.testSlot4Info} { err := spec.AddPermanentSlot(s.iface, slot) c.Assert(err, IsNil) c.Assert(spec.Snippets(), HasLen, 0) } - expectedSnippet1 := `IMPORT{builtin}="usb_id" + expectedSnippet1 := `# serial-port +IMPORT{builtin}="usb_id" SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="0001", ATTRS{idProduct}=="0001", SYMLINK+="serial-port-zigbee"` - err := spec.AddPermanentSlot(s.iface, s.testUDev1) + err := spec.AddPermanentSlot(s.iface, s.testUDev1Info) c.Assert(err, IsNil) c.Assert(spec.Snippets(), HasLen, 1) snippet := spec.Snippets()[0] c.Assert(snippet, Equals, expectedSnippet1) spec = &udev.Specification{} - expectedSnippet2 := `IMPORT{builtin}="usb_id" + expectedSnippet2 := `# serial-port +IMPORT{builtin}="usb_id" SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="ffff", ATTRS{idProduct}=="ffff", SYMLINK+="serial-port-mydevice"` - err = spec.AddPermanentSlot(s.iface, s.testUDev2) + err = spec.AddPermanentSlot(s.iface, s.testUDev2Info) c.Assert(err, IsNil) c.Assert(spec.Snippets(), HasLen, 1) snippet = spec.Snippets()[0] c.Assert(snippet, Equals, expectedSnippet2) + + spec = &udev.Specification{} + // The ENV{ID_USB_INTERFACE_NUM} is set to two hex digits + // For instance, the expectedSnippet3 is set to 00 + expectedSnippet3 := `# serial-port +IMPORT{builtin}="usb_id" +SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="abcd", ATTRS{idProduct}=="1234", ENV{ID_USB_INTERFACE_NUM}=="00", SYMLINK+="serial-port-myserial"` + err = spec.AddPermanentSlot(s.iface, s.testUDev3Info) + c.Assert(err, IsNil) + c.Assert(spec.Snippets(), HasLen, 1) + snippet = spec.Snippets()[0] + c.Assert(snippet, Equals, expectedSnippet3) } func (s *SerialPortInterfaceSuite) TestConnectedPlugUDevSnippets(c *C) { // add the plug for the slot with just path spec := &udev.Specification{} - err := spec.AddConnectedPlug(s.iface, s.testPlugPort1, nil, s.testSlot1, nil) + err := spec.AddConnectedPlug(s.iface, s.testPlugPort1, s.testSlot1) c.Assert(err, IsNil) - c.Assert(spec.Snippets(), HasLen, 1) + c.Assert(spec.Snippets(), HasLen, 2) snippet := spec.Snippets()[0] - expectedSnippet1 := `SUBSYSTEM=="tty", KERNEL=="ttyS0", TAG+="snap_client-snap_app-accessing-2-ports"` + expectedSnippet1 := `# serial-port +SUBSYSTEM=="tty", KERNEL=="ttyS0", TAG+="snap_client-snap_app-accessing-2-ports"` c.Assert(snippet, Equals, expectedSnippet1) + extraSnippet := spec.Snippets()[1] + expectedExtraSnippet1 := `TAG=="snap_client-snap_app-accessing-2-ports", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_client-snap_app-accessing-2-ports $devpath $major:$minor"` + c.Assert(extraSnippet, Equals, expectedExtraSnippet1) // add plug for the first slot with product and vendor ids spec = &udev.Specification{} - err = spec.AddConnectedPlug(s.iface, s.testPlugPort1, nil, s.testUDev1, nil) + err = spec.AddConnectedPlug(s.iface, s.testPlugPort1, s.testUDev1) c.Assert(err, IsNil) - c.Assert(spec.Snippets(), HasLen, 1) + c.Assert(spec.Snippets(), HasLen, 2) snippet = spec.Snippets()[0] - expectedSnippet2 := `IMPORT{builtin}="usb_id" + expectedSnippet2 := `# serial-port +IMPORT{builtin}="usb_id" SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="0001", ATTRS{idProduct}=="0001", TAG+="snap_client-snap_app-accessing-2-ports"` c.Assert(snippet, Equals, expectedSnippet2) + extraSnippet = spec.Snippets()[1] + expectedExtraSnippet2 := `TAG=="snap_client-snap_app-accessing-2-ports", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_client-snap_app-accessing-2-ports $devpath $major:$minor"` + c.Assert(extraSnippet, Equals, expectedExtraSnippet2) // add plug for the first slot with product and vendor ids spec = &udev.Specification{} - err = spec.AddConnectedPlug(s.iface, s.testPlugPort2, nil, s.testUDev2, nil) + err = spec.AddConnectedPlug(s.iface, s.testPlugPort2, s.testUDev2) c.Assert(err, IsNil) - c.Assert(spec.Snippets(), HasLen, 1) + c.Assert(spec.Snippets(), HasLen, 2) snippet = spec.Snippets()[0] - expectedSnippet3 := `IMPORT{builtin}="usb_id" + expectedSnippet3 := `# serial-port +IMPORT{builtin}="usb_id" SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="ffff", ATTRS{idProduct}=="ffff", TAG+="snap_client-snap_app-accessing-2-ports"` c.Assert(snippet, Equals, expectedSnippet3) + extraSnippet = spec.Snippets()[1] + expectedExtraSnippet3 := `TAG=="snap_client-snap_app-accessing-2-ports", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_client-snap_app-accessing-2-ports $devpath $major:$minor"` + c.Assert(extraSnippet, Equals, expectedExtraSnippet3) + + // add plug for the first slot with product and vendor ids and usb interface number + spec = &udev.Specification{} + err = spec.AddConnectedPlug(s.iface, s.testPlugPort2, s.testUDev3) + c.Assert(err, IsNil) + c.Assert(spec.Snippets(), HasLen, 2) + snippet = spec.Snippets()[0] + expectedSnippet4 := `# serial-port +IMPORT{builtin}="usb_id" +SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="abcd", ATTRS{idProduct}=="1234", ENV{ID_USB_INTERFACE_NUM}=="00", TAG+="snap_client-snap_app-accessing-2-ports"` + c.Assert(snippet, Equals, expectedSnippet4) + extraSnippet = spec.Snippets()[1] + expectedExtraSnippet4 := `TAG=="snap_client-snap_app-accessing-2-ports", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_client-snap_app-accessing-2-ports $devpath $major:$minor"` + c.Assert(extraSnippet, Equals, expectedExtraSnippet4) } func (s *SerialPortInterfaceSuite) TestConnectedPlugAppArmorSnippets(c *C) { - checkConnectedPlugSnippet := func(plug *interfaces.Plug, slot *interfaces.Slot, expectedSnippet string) { + checkConnectedPlugSnippet := func(plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot, expectedSnippet string) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.client-snap.app-accessing-2-ports"}) snippet := apparmorSpec.SnippetForTag("snap.client-snap.app-accessing-2-ports") - c.Assert(snippet, DeepEquals, expectedSnippet, Commentf("\nexpected:\n%s\nfound:\n%s", expectedSnippet, snippet)) + c.Assert(snippet, DeepEquals, expectedSnippet) } expectedSnippet1 := `/dev/ttyS0 rw,` @@ -337,49 +465,72 @@ expectedSnippet9 := `/dev/tty[A-Z]*[0-9] rw,` checkConnectedPlugSnippet(s.testPlugPort2, s.testUDev2, expectedSnippet9) + + expectedSnippet10 := `/dev/tty[A-Z]*[0-9] rw,` + checkConnectedPlugSnippet(s.testPlugPort2, s.testUDev3, expectedSnippet10) } func (s *SerialPortInterfaceSuite) TestConnectedPlugUDevSnippetsForPath(c *C) { - checkConnectedPlugSnippet := func(plug *interfaces.Plug, slot *interfaces.Slot, expectedSnippet string) { + checkConnectedPlugSnippet := func(plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot, expectedSnippet string, expectedExtraSnippet string) { udevSpec := &udev.Specification{} - err := udevSpec.AddConnectedPlug(s.iface, plug, nil, slot, nil) + err := udevSpec.AddConnectedPlug(s.iface, plug, slot) c.Assert(err, IsNil) - c.Assert(udevSpec.Snippets(), HasLen, 1) + c.Assert(udevSpec.Snippets(), HasLen, 2) snippet := udevSpec.Snippets()[0] - c.Assert(snippet, DeepEquals, expectedSnippet, Commentf("\nexpected:\n%s\nfound:\n%s", expectedSnippet, snippet)) + c.Assert(snippet, Equals, expectedSnippet) + extraSnippet := udevSpec.Snippets()[1] + c.Assert(extraSnippet, Equals, expectedExtraSnippet) } // these have only path - expectedSnippet1 := `SUBSYSTEM=="tty", KERNEL=="ttyS0", TAG+="snap_client-snap_app-accessing-3rd-port"` - checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot1, expectedSnippet1) - - expectedSnippet2 := `SUBSYSTEM=="tty", KERNEL=="ttyUSB927", TAG+="snap_client-snap_app-accessing-3rd-port"` - checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot2, expectedSnippet2) - - expectedSnippet3 := `SUBSYSTEM=="tty", KERNEL=="ttyS42", TAG+="snap_client-snap_app-accessing-3rd-port"` - checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot3, expectedSnippet3) - - expectedSnippet4 := `SUBSYSTEM=="tty", KERNEL=="ttyO0", TAG+="snap_client-snap_app-accessing-3rd-port"` - checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot4, expectedSnippet4) - - expectedSnippet5 := `SUBSYSTEM=="tty", KERNEL=="ttyACM0", TAG+="snap_client-snap_app-accessing-3rd-port"` - checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot5, expectedSnippet5) - - expectedSnippet6 := `SUBSYSTEM=="tty", KERNEL=="ttyAMA0", TAG+="snap_client-snap_app-accessing-3rd-port"` - checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot6, expectedSnippet6) - - expectedSnippet7 := `SUBSYSTEM=="tty", KERNEL=="ttyXRUSB0", TAG+="snap_client-snap_app-accessing-3rd-port"` - checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot7, expectedSnippet7) + expectedSnippet1 := `# serial-port +SUBSYSTEM=="tty", KERNEL=="ttyS0", TAG+="snap_client-snap_app-accessing-3rd-port"` + expectedExtraSnippet1 := `TAG=="snap_client-snap_app-accessing-3rd-port", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_client-snap_app-accessing-3rd-port $devpath $major:$minor"` + checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot1, expectedSnippet1, expectedExtraSnippet1) + + expectedSnippet2 := `# serial-port +SUBSYSTEM=="tty", KERNEL=="ttyUSB927", TAG+="snap_client-snap_app-accessing-3rd-port"` + expectedExtraSnippet2 := `TAG=="snap_client-snap_app-accessing-3rd-port", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_client-snap_app-accessing-3rd-port $devpath $major:$minor"` + checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot2, expectedSnippet2, expectedExtraSnippet2) + + expectedSnippet3 := `# serial-port +SUBSYSTEM=="tty", KERNEL=="ttyS42", TAG+="snap_client-snap_app-accessing-3rd-port"` + expectedExtraSnippet3 := `TAG=="snap_client-snap_app-accessing-3rd-port", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_client-snap_app-accessing-3rd-port $devpath $major:$minor"` + checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot3, expectedSnippet3, expectedExtraSnippet3) + + expectedSnippet4 := `# serial-port +SUBSYSTEM=="tty", KERNEL=="ttyO0", TAG+="snap_client-snap_app-accessing-3rd-port"` + expectedExtraSnippet4 := `TAG=="snap_client-snap_app-accessing-3rd-port", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_client-snap_app-accessing-3rd-port $devpath $major:$minor"` + checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot4, expectedSnippet4, expectedExtraSnippet4) + + expectedSnippet5 := `# serial-port +SUBSYSTEM=="tty", KERNEL=="ttyACM0", TAG+="snap_client-snap_app-accessing-3rd-port"` + expectedExtraSnippet5 := `TAG=="snap_client-snap_app-accessing-3rd-port", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_client-snap_app-accessing-3rd-port $devpath $major:$minor"` + checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot5, expectedSnippet5, expectedExtraSnippet5) + + expectedSnippet6 := `# serial-port +SUBSYSTEM=="tty", KERNEL=="ttyAMA0", TAG+="snap_client-snap_app-accessing-3rd-port"` + expectedExtraSnippet6 := `TAG=="snap_client-snap_app-accessing-3rd-port", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_client-snap_app-accessing-3rd-port $devpath $major:$minor"` + checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot6, expectedSnippet6, expectedExtraSnippet6) + + expectedSnippet7 := `# serial-port +SUBSYSTEM=="tty", KERNEL=="ttyXRUSB0", TAG+="snap_client-snap_app-accessing-3rd-port"` + expectedExtraSnippet7 := `TAG=="snap_client-snap_app-accessing-3rd-port", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_client-snap_app-accessing-3rd-port $devpath $major:$minor"` + checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot7, expectedSnippet7, expectedExtraSnippet7) // these have product and vendor ids - expectedSnippet8 := `IMPORT{builtin}="usb_id" + expectedSnippet8 := `# serial-port +IMPORT{builtin}="usb_id" SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="0001", ATTRS{idProduct}=="0001", TAG+="snap_client-snap_app-accessing-3rd-port"` - checkConnectedPlugSnippet(s.testPlugPort3, s.testUDev1, expectedSnippet8) + expectedExtraSnippet8 := `TAG=="snap_client-snap_app-accessing-3rd-port", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_client-snap_app-accessing-3rd-port $devpath $major:$minor"` + checkConnectedPlugSnippet(s.testPlugPort3, s.testUDev1, expectedSnippet8, expectedExtraSnippet8) - expectedSnippet9 := `IMPORT{builtin}="usb_id" + expectedSnippet9 := `# serial-port +IMPORT{builtin}="usb_id" SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="ffff", ATTRS{idProduct}=="ffff", TAG+="snap_client-snap_app-accessing-3rd-port"` - checkConnectedPlugSnippet(s.testPlugPort3, s.testUDev2, expectedSnippet9) + expectedExtraSnippet9 := `TAG=="snap_client-snap_app-accessing-3rd-port", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_client-snap_app-accessing-3rd-port $devpath $major:$minor"` + checkConnectedPlugSnippet(s.testPlugPort3, s.testUDev2, expectedSnippet9, expectedExtraSnippet9) } func (s *SerialPortInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/shutdown_test.go snapd-2.31.1+17.10/interfaces/builtin/shutdown_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/shutdown_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/shutdown_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type ShutdownInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&ShutdownInterfaceSuite{ @@ -48,14 +50,14 @@ command: foo plugs: [shutdown] `, nil) - s.plug = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["shutdown"]} - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "shutdown", - Interface: "shutdown", - }, + s.plugInfo = consumingSnapInfo.Plugs["shutdown"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "shutdown", + Interface: "shutdown", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) } func (s *ShutdownInterfaceSuite) TestName(c *C) { @@ -63,22 +65,22 @@ } func (s *ShutdownInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "shutdown", Interface: "shutdown", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, "shutdown slots are reserved for the core snap") + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "shutdown slots are reserved for the core snap") } func (s *ShutdownInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *ShutdownInterfaceSuite) TestConnectedPlugSnippet(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `org.freedesktop.systemd1`) diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/snapd_control.go snapd-2.31.1+17.10/interfaces/builtin/snapd_control.go --- snapd-2.29.4.2+17.10/interfaces/builtin/snapd_control.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/snapd_control.go 2018-01-24 20:02:44.000000000 +0000 @@ -19,6 +19,12 @@ package builtin +import ( + "fmt" + + "github.com/snapcore/snapd/snap" +) + const snapdControlSummary = `allows communicating with snapd` const snapdControlBaseDeclarationPlugs = ` @@ -35,15 +41,28 @@ deny-auto-connection: true ` -// http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/apparmor/policygroups/ubuntu-core/16.04/snapd-control const snapdControlConnectedPlugAppArmor = ` # Description: Can manage snaps via snapd. /run/snapd.socket rw, ` +type snapControlInterface struct { + commonInterface +} + +func (iface *snapControlInterface) BeforePreparePlug(plug *snap.PlugInfo) error { + if refreshSchedule, ok := plug.Attrs["refresh-schedule"].(string); ok { + if refreshSchedule != "managed" { + return fmt.Errorf("unsupported refresh-schedule value: %q", refreshSchedule) + } + } + + return nil +} + func init() { - registerIface(&commonInterface{ + registerIface(&snapControlInterface{commonInterface{ name: "snapd-control", summary: snapdControlSummary, implicitOnCore: true, @@ -52,5 +71,5 @@ baseDeclarationSlots: snapdControlBaseDeclarationSlots, connectedPlugAppArmor: snapdControlConnectedPlugAppArmor, reservedForOS: true, - }) + }}) } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/snapd_control_test.go snapd-2.31.1+17.10/interfaces/builtin/snapd_control_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/snapd_control_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/snapd_control_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type SnapdControlInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&SnapdControlInterfaceSuite{ @@ -48,14 +50,14 @@ command: foo plugs: [snapd-control] `, nil) - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "snapd-control", - Interface: "snapd-control", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "snapd-control", + Interface: "snapd-control", } - s.plug = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["snapd-control"]} + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) + s.plugInfo = consumingSnapInfo.Plugs["snapd-control"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *SnapdControlInterfaceSuite) TestName(c *C) { @@ -63,24 +65,48 @@ } func (s *SnapdControlInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "snapd-control", Interface: "snapd-control", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "snapd-control slots are reserved for the core snap") } func (s *SnapdControlInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) +} + +func (s *SnapdControlInterfaceSuite) TestSanitizePlugWithAttrHappy(c *C) { + const mockSnapYaml = `name: snapd-manager +version: 1.0 +plugs: + snapd-control: + refresh-schedule: managed +` + info := snaptest.MockInfo(c, mockSnapYaml, nil) + plug := info.Plugs["snapd-control"] + c.Assert(interfaces.BeforePreparePlug(s.iface, plug), IsNil) +} + +func (s *SnapdControlInterfaceSuite) TestSanitizePlugWithAttrNotHappy(c *C) { + const mockSnapYaml = `name: snapd-manager +version: 1.0 +plugs: + snapd-control: + refresh-schedule: unsupported-value +` + info := snaptest.MockInfo(c, mockSnapYaml, nil) + plug := info.Plugs["snapd-control"] + c.Assert(interfaces.BeforePreparePlug(s.iface, plug), ErrorMatches, `unsupported refresh-schedule value: "unsupported-value"`) } func (s *SnapdControlInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `/run/snapd.socket rw,`) diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/spi.go snapd-2.31.1+17.10/interfaces/builtin/spi.go --- snapd-2.29.4.2+17.10/interfaces/builtin/spi.go 2017-11-30 07:12:26.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/spi.go 2018-01-24 20:02:44.000000000 +0000 @@ -28,6 +28,7 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/udev" + "github.com/snapcore/snapd/snap" ) const spiSummary = `allows access to specific spi controller` @@ -56,10 +57,10 @@ var spiDevPattern = regexp.MustCompile("^/dev/spidev[0-9].[0-9]+$") -func (iface *spiInterface) path(slot *interfaces.Slot) (string, error) { - path, ok := slot.Attrs["path"].(string) - if !ok || path == "" { - return "", fmt.Errorf("slot %q must have a path attribute", slot.Ref()) +func (iface *spiInterface) path(slotRef *interfaces.SlotRef, attrs interfaces.Attrer) (string, error) { + var path string + if err := attrs.Attr("path", &path); err != nil || path == "" { + return "", fmt.Errorf("slot %q must have a path attribute", slotRef) } path = filepath.Clean(path) if !spiDevPattern.MatchString(path) { @@ -68,16 +69,16 @@ return path, nil } -func (iface *spiInterface) SanitizeSlot(slot *interfaces.Slot) error { +func (iface *spiInterface) BeforePrepareSlot(slot *snap.SlotInfo) error { if err := sanitizeSlotReservedForOSOrGadget(iface, slot); err != nil { return err } - _, err := iface.path(slot) + _, err := iface.path(&interfaces.SlotRef{Snap: slot.Snap.Name(), Name: slot.Name}, slot) return err } -func (iface *spiInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - path, err := iface.path(slot) +func (iface *spiInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { + path, err := iface.path(slot.Ref(), slot) if err != nil { return nil } @@ -86,8 +87,8 @@ return nil } -func (iface *spiInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - path, err := iface.path(slot) +func (iface *spiInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { + path, err := iface.path(slot.Ref(), slot) if err != nil { return nil } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/spi_test.go snapd-2.31.1+17.10/interfaces/builtin/spi_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/spi_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/spi_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -35,20 +35,32 @@ testutil.BaseTest iface interfaces.Interface - slotOs1 *interfaces.Slot - slotOs2 *interfaces.Slot - - slotGadget1 *interfaces.Slot - slotGadget2 *interfaces.Slot - slotGadgetBad1 *interfaces.Slot - slotGadgetBad2 *interfaces.Slot - slotGadgetBad3 *interfaces.Slot - slotGadgetBad4 *interfaces.Slot - slotGadgetBad5 *interfaces.Slot - slotGadgetBad6 *interfaces.Slot - - plug1 *interfaces.Plug - plug2 *interfaces.Plug + slotOs1Info *snap.SlotInfo + slotOs1 *interfaces.ConnectedSlot + slotOs2Info *snap.SlotInfo + slotOs2 *interfaces.ConnectedSlot + + slotGadget1Info *snap.SlotInfo + slotGadget1 *interfaces.ConnectedSlot + slotGadget2Info *snap.SlotInfo + slotGadget2 *interfaces.ConnectedSlot + slotGadgetBad1Info *snap.SlotInfo + slotGadgetBad1 *interfaces.ConnectedSlot + slotGadgetBad2Info *snap.SlotInfo + slotGadgetBad2 *interfaces.ConnectedSlot + slotGadgetBad3Info *snap.SlotInfo + slotGadgetBad3 *interfaces.ConnectedSlot + slotGadgetBad4Info *snap.SlotInfo + slotGadgetBad4 *interfaces.ConnectedSlot + slotGadgetBad5Info *snap.SlotInfo + slotGadgetBad5 *interfaces.ConnectedSlot + slotGadgetBad6Info *snap.SlotInfo + slotGadgetBad6 *interfaces.ConnectedSlot + + plug1Info *snap.PlugInfo + plug1 *interfaces.ConnectedPlug + plug2Info *snap.PlugInfo + plug2 *interfaces.ConnectedPlug } var _ = Suite(&spiInterfaceSuite{ @@ -67,8 +79,10 @@ interface: spi path: /dev/spidev0.1 `, nil) - s.slotOs1 = &interfaces.Slot{SlotInfo: info.Slots["spi-1"]} - s.slotOs2 = &interfaces.Slot{SlotInfo: info.Slots["spi-2"]} + s.slotOs1Info = info.Slots["spi-1"] + s.slotOs1 = interfaces.NewConnectedSlot(s.slotOs1Info, nil) + s.slotOs2Info = info.Slots["spi-2"] + s.slotOs2 = interfaces.NewConnectedSlot(s.slotOs2Info, nil) info = snaptest.MockInfo(c, ` name: gadget @@ -98,14 +112,22 @@ bad-spi-6: interface: spi `, nil) - s.slotGadget1 = &interfaces.Slot{SlotInfo: info.Slots["spi-1"]} - s.slotGadget2 = &interfaces.Slot{SlotInfo: info.Slots["spi-2"]} - s.slotGadgetBad1 = &interfaces.Slot{SlotInfo: info.Slots["bad-spi-1"]} - s.slotGadgetBad2 = &interfaces.Slot{SlotInfo: info.Slots["bad-spi-2"]} - s.slotGadgetBad3 = &interfaces.Slot{SlotInfo: info.Slots["bad-spi-3"]} - s.slotGadgetBad4 = &interfaces.Slot{SlotInfo: info.Slots["bad-spi-4"]} - s.slotGadgetBad5 = &interfaces.Slot{SlotInfo: info.Slots["bad-spi-5"]} - s.slotGadgetBad6 = &interfaces.Slot{SlotInfo: info.Slots["bad-spi-6"]} + s.slotGadget1Info = info.Slots["spi-1"] + s.slotGadget1 = interfaces.NewConnectedSlot(s.slotGadget1Info, nil) + s.slotGadget2Info = info.Slots["spi-2"] + s.slotGadget2 = interfaces.NewConnectedSlot(s.slotGadget2Info, nil) + s.slotGadgetBad1Info = info.Slots["bad-spi-1"] + s.slotGadgetBad1 = interfaces.NewConnectedSlot(s.slotGadgetBad1Info, nil) + s.slotGadgetBad2Info = info.Slots["bad-spi-2"] + s.slotGadgetBad2 = interfaces.NewConnectedSlot(s.slotGadgetBad2Info, nil) + s.slotGadgetBad3Info = info.Slots["bad-spi-3"] + s.slotGadgetBad3 = interfaces.NewConnectedSlot(s.slotGadgetBad3Info, nil) + s.slotGadgetBad4Info = info.Slots["bad-spi-4"] + s.slotGadgetBad4 = interfaces.NewConnectedSlot(s.slotGadgetBad4Info, nil) + s.slotGadgetBad5Info = info.Slots["bad-spi-5"] + s.slotGadgetBad5 = interfaces.NewConnectedSlot(s.slotGadgetBad5Info, nil) + s.slotGadgetBad6Info = info.Slots["bad-spi-6"] + s.slotGadgetBad6 = interfaces.NewConnectedSlot(s.slotGadgetBad6Info, nil) info = snaptest.MockInfo(c, ` name: consumer @@ -121,8 +143,10 @@ command: foo plugs: [spi-1] `, nil) - s.plug1 = &interfaces.Plug{PlugInfo: info.Plugs["spi-1"]} - s.plug2 = &interfaces.Plug{PlugInfo: info.Plugs["spi-2"]} + s.plug1Info = info.Plugs["spi-1"] + s.plug1 = interfaces.NewConnectedPlug(s.plug1Info, nil) + s.plug2Info = info.Plugs["spi-2"] + s.plug2 = interfaces.NewConnectedPlug(s.plug2Info, nil) } func (s *spiInterfaceSuite) TestName(c *C) { @@ -130,41 +154,43 @@ } func (s *spiInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slotOs1.Sanitize(s.iface), IsNil) - c.Assert(s.slotOs2.Sanitize(s.iface), IsNil) - c.Assert(s.slotGadget1.Sanitize(s.iface), IsNil) - c.Assert(s.slotGadget2.Sanitize(s.iface), IsNil) - err := s.slotGadgetBad1.Sanitize(s.iface) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotOs1Info), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotOs2Info), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotGadget1Info), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotGadget2Info), IsNil) + err := interfaces.BeforePrepareSlot(s.iface, s.slotGadgetBad1Info) c.Assert(err, ErrorMatches, `"/dev/spev0.0" is not a valid SPI device`) - err = s.slotGadgetBad2.Sanitize(s.iface) + err = interfaces.BeforePrepareSlot(s.iface, s.slotGadgetBad2Info) c.Assert(err, ErrorMatches, `"/dev/sidv0.0" is not a valid SPI device`) - err = s.slotGadgetBad3.Sanitize(s.iface) + err = interfaces.BeforePrepareSlot(s.iface, s.slotGadgetBad3Info) c.Assert(err, ErrorMatches, `"/dev/slpiv0.3" is not a valid SPI device`) - err = s.slotGadgetBad4.Sanitize(s.iface) + err = interfaces.BeforePrepareSlot(s.iface, s.slotGadgetBad4Info) c.Assert(err, ErrorMatches, `"/dev/sdev-00" is not a valid SPI device`) - err = s.slotGadgetBad5.Sanitize(s.iface) + err = interfaces.BeforePrepareSlot(s.iface, s.slotGadgetBad5Info) c.Assert(err, ErrorMatches, `"/dev/spi-foo" is not a valid SPI device`) - err = s.slotGadgetBad6.Sanitize(s.iface) + err = interfaces.BeforePrepareSlot(s.iface, s.slotGadgetBad6Info) c.Assert(err, ErrorMatches, `slot "gadget:bad-spi-6" must have a path attribute`) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "spi", Interface: "spi", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "spi slots are reserved for the core and gadget snaps") } func (s *spiInterfaceSuite) TestUDevSpec(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug1, nil, s.slotGadget1, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 1) - c.Assert(spec.Snippets()[0], Equals, `KERNEL=="spidev0.0", TAG+="snap_consumer_app"`) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug1, s.slotGadget1), IsNil) + c.Assert(spec.Snippets(), HasLen, 2) + c.Assert(spec.Snippets(), testutil.Contains, `# spi +KERNEL=="spidev0.0", TAG+="snap_consumer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_consumer_app", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_consumer_app $devpath $major:$minor"`) } func (s *spiInterfaceSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug1, nil, s.slotGadget1, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug1, s.slotGadget1), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) c.Assert(spec.SnippetForTag("snap.consumer.app"), Equals, ""+ "/dev/spidev0.0 rw,\n"+ diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/ssh_keys.go snapd-2.31.1+17.10/interfaces/builtin/ssh_keys.go --- snapd-2.29.4.2+17.10/interfaces/builtin/ssh_keys.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/ssh_keys.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,51 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package builtin + +const sshKeysSummary = `allows reading ssh user configuration and keys` + +const sshKeysBaseDeclarationSlots = ` + ssh-keys: + allow-installation: + slot-snap-type: + - core + deny-auto-connection: true +` + +const sshKeysConnectedPlugAppArmor = ` +# Description: Can read ssh user configuration as well as public and private +# keys. + +/usr/bin/ssh ixr, +/etc/ssh/ssh_config r, +owner @{HOME}/.ssh/{,**} r, +` + +func init() { + registerIface(&commonInterface{ + name: "ssh-keys", + summary: sshKeysSummary, + implicitOnCore: true, + implicitOnClassic: true, + baseDeclarationSlots: sshKeysBaseDeclarationSlots, + connectedPlugAppArmor: sshKeysConnectedPlugAppArmor, + reservedForOS: true, + }) +} diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/ssh_keys_test.go snapd-2.31.1+17.10/interfaces/builtin/ssh_keys_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/ssh_keys_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/ssh_keys_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,102 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +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/snap" + "github.com/snapcore/snapd/testutil" +) + +type SshKeysInterfaceSuite struct { + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug +} + +var _ = Suite(&SshKeysInterfaceSuite{ + iface: builtin.MustInterface("ssh-keys"), +}) + +const sshKeysConsumerYaml = `name: consumer +apps: + app: + plugs: [ssh-keys] + ` + +const sshKeysCoreYaml = `name: core +type: os +slots: + ssh-keys: +` + +func (s *SshKeysInterfaceSuite) SetUpTest(c *C) { + s.plug, s.plugInfo = MockConnectedPlug(c, sshKeysConsumerYaml, nil, "ssh-keys") + s.slot, s.slotInfo = MockConnectedSlot(c, sshKeysCoreYaml, nil, "ssh-keys") +} + +func (s *SshKeysInterfaceSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "ssh-keys") +} + +func (s *SshKeysInterfaceSuite) TestSanitizeSlot(c *C) { + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slotInfo := &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "some-snap"}, + Name: "ssh-keys", + Interface: "ssh-keys", + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slotInfo), ErrorMatches, + "ssh-keys slots are reserved for the core snap") +} + +func (s *SshKeysInterfaceSuite) TestSanitizePlug(c *C) { + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) +} + +func (s *SshKeysInterfaceSuite) 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, `owner @{HOME}/.ssh/{,**} r,`) +} + +func (s *SshKeysInterfaceSuite) 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 reading ssh user configuration and keys`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "ssh-keys") +} + +func (s *SshKeysInterfaceSuite) TestAutoConnect(c *C) { + // FIXME: fix AutoConnect + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) +} + +func (s *SshKeysInterfaceSuite) TestInterfaces(c *C) { + c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) +} diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/ssh_public_keys.go snapd-2.31.1+17.10/interfaces/builtin/ssh_public_keys.go --- snapd-2.29.4.2+17.10/interfaces/builtin/ssh_public_keys.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/ssh_public_keys.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,51 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package builtin + +const sshPublicKeysSummary = `allows reading ssh public keys and non-sensitive configuration` + +const sshPublicKeysBaseDeclarationSlots = ` + ssh-public-keys: + allow-installation: + slot-snap-type: + - core + deny-auto-connection: true +` + +const sshPublicKeysConnectedPlugAppArmor = ` +# Description: Can read ssh public keys and non-sensitive configuration + +/usr/bin/ssh ixr, +owner @{HOME}/.ssh/ r, +owner @{HOME}/.ssh/environment r, +owner @{HOME}/.ssh/*.pub r, +` + +func init() { + registerIface(&commonInterface{ + name: "ssh-public-keys", + summary: sshPublicKeysSummary, + implicitOnCore: true, + implicitOnClassic: true, + baseDeclarationSlots: sshPublicKeysBaseDeclarationSlots, + connectedPlugAppArmor: sshPublicKeysConnectedPlugAppArmor, + reservedForOS: true, + }) +} diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/ssh_public_keys_test.go snapd-2.31.1+17.10/interfaces/builtin/ssh_public_keys_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/ssh_public_keys_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/ssh_public_keys_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,102 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +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/snap" + "github.com/snapcore/snapd/testutil" +) + +type SshPublicKeysInterfaceSuite struct { + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug +} + +var _ = Suite(&SshPublicKeysInterfaceSuite{ + iface: builtin.MustInterface("ssh-public-keys"), +}) + +const sshPublicKeysConsumerYaml = `name: consumer +apps: + app: + plugs: [ssh-public-keys] + ` + +const sshPublicKeysCoreYaml = `name: core +type: os +slots: + ssh-public-keys: +` + +func (s *SshPublicKeysInterfaceSuite) SetUpTest(c *C) { + s.plug, s.plugInfo = MockConnectedPlug(c, sshPublicKeysConsumerYaml, nil, "ssh-public-keys") + s.slot, s.slotInfo = MockConnectedSlot(c, sshPublicKeysCoreYaml, nil, "ssh-public-keys") +} + +func (s *SshPublicKeysInterfaceSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "ssh-public-keys") +} + +func (s *SshPublicKeysInterfaceSuite) TestSanitizeSlot(c *C) { + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slotInfo := &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "some-snap"}, + Name: "ssh-public-keys", + Interface: "ssh-public-keys", + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slotInfo), ErrorMatches, + "ssh-public-keys slots are reserved for the core snap") +} + +func (s *SshPublicKeysInterfaceSuite) TestSanitizePlug(c *C) { + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) +} + +func (s *SshPublicKeysInterfaceSuite) 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, `owner @{HOME}/.ssh/*.pub r,`) +} + +func (s *SshPublicKeysInterfaceSuite) 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 reading ssh public keys and non-sensitive configuration`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "ssh-public-keys") +} + +func (s *SshPublicKeysInterfaceSuite) TestAutoConnect(c *C) { + // FIXME: fix AutoConnect + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) +} + +func (s *SshPublicKeysInterfaceSuite) TestInterfaces(c *C) { + c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) +} diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/storage_framework_service.go snapd-2.31.1+17.10/interfaces/builtin/storage_framework_service.go --- snapd-2.29.4.2+17.10/interfaces/builtin/storage_framework_service.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/storage_framework_service.go 2018-01-24 20:02:44.000000000 +0000 @@ -25,6 +25,7 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/seccomp" + "github.com/snapcore/snapd/snap" ) const storageFrameworkServiceSummary = `allows operating as or interacting with the Storage Framework` @@ -124,7 +125,7 @@ } } -func (iface *storageFrameworkServiceInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *storageFrameworkServiceInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { snippet := storageFrameworkServiceConnectedPlugAppArmor old := "###SLOT_SECURITY_TAGS###" new := slotAppLabelExpr(slot) @@ -133,12 +134,12 @@ return nil } -func (iface *storageFrameworkServiceInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { +func (iface *storageFrameworkServiceInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(storageFrameworkServicePermanentSlotAppArmor) return nil } -func (iface *storageFrameworkServiceInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *storageFrameworkServiceInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { snippet := storageFrameworkServiceConnectedSlotAppArmor old := "###PLUG_SECURITY_TAGS###" new := plugAppLabelExpr(plug) @@ -147,7 +148,7 @@ return nil } -func (iface *storageFrameworkServiceInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *interfaces.Slot) error { +func (iface *storageFrameworkServiceInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(storageFrameworkServicePermanentSlotSecComp) return nil } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/storage_framework_service_test.go snapd-2.31.1+17.10/interfaces/builtin/storage_framework_service_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/storage_framework_service_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/storage_framework_service_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -26,14 +26,17 @@ "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/builtin" "github.com/snapcore/snapd/interfaces/seccomp" + "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap/snaptest" "github.com/snapcore/snapd/testutil" ) type StorageFrameworkServiceInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&StorageFrameworkServiceInterfaceSuite{ @@ -49,7 +52,8 @@ slots: [storage-framework-service] ` providerInfo := snaptest.MockInfo(c, providerYaml, nil) - s.slot = &interfaces.Slot{SlotInfo: providerInfo.Slots["storage-framework-service"]} + s.slotInfo = providerInfo.Slots["storage-framework-service"] + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) const consumerYaml = `name: consumer version: 1.0 @@ -59,7 +63,8 @@ plugs: [storage-framework-service] ` consumerInfo := snaptest.MockInfo(c, consumerYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: consumerInfo.Plugs["storage-framework-service"]} + s.plugInfo = consumerInfo.Plugs["storage-framework-service"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } @@ -69,28 +74,28 @@ func (s *StorageFrameworkServiceInterfaceSuite) TestAppArmorConnectedPlug(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, `interface=com.canonical.StorageFramework.Registry`) } func (s *StorageFrameworkServiceInterfaceSuite) TestAppArmorConnectedSlot(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedSlot(s.iface, s.plug, nil, s.slot, nil), IsNil) + c.Assert(spec.AddConnectedSlot(s.iface, s.plug, s.slot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.provider.app"}) c.Assert(spec.SnippetForTag("snap.provider.app"), testutil.Contains, `interface=com.canonical.StorageFramework`) } func (s *StorageFrameworkServiceInterfaceSuite) TestAppArmorPermanentSlot(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.slot), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.slotInfo), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.provider.app"}) c.Assert(spec.SnippetForTag("snap.provider.app"), testutil.Contains, `member={RequestName,ReleaseName,GetConnectionCredentials}`) } func (s *StorageFrameworkServiceInterfaceSuite) TestSecCompPermanentSlot(c *C) { spec := &seccomp.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.slot), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.slotInfo), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.provider.app"}) c.Assert(spec.SnippetForTag("snap.provider.app"), testutil.Contains, "bind\n") } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/system_observe.go snapd-2.31.1+17.10/interfaces/builtin/system_observe.go --- snapd-2.29.4.2+17.10/interfaces/builtin/system_observe.go 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/system_observe.go 2018-01-24 20:02:44.000000000 +0000 @@ -56,6 +56,7 @@ @{PROC}/vmstat r, @{PROC}/diskstats r, @{PROC}/kallsyms r, +@{PROC}/partitions r, # These are not process-specific (/proc/*/... and /proc/*/task/*/...) @{PROC}/*/{,task/,task/*/} r, diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/system_observe_test.go snapd-2.31.1+17.10/interfaces/builtin/system_observe_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/system_observe_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/system_observe_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -32,9 +32,11 @@ ) type SystemObserveInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } const sysobsMockPlugSnapInfoYaml = `name: other @@ -50,15 +52,15 @@ }) func (s *SystemObserveInterfaceSuite) SetUpTest(c *C) { - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "system-observe", - Interface: "system-observe", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "system-observe", + Interface: "system-observe", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, sysobsMockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["system-observe"]} + s.plugInfo = plugSnap.Plugs["system-observe"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *SystemObserveInterfaceSuite) TestName(c *C) { @@ -66,31 +68,32 @@ } func (s *SystemObserveInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "system-observe", Interface: "system-observe", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "system-observe slots are reserved for the core snap") } func (s *SystemObserveInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *SystemObserveInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "ptrace") + c.Assert(apparmorSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "@{PROC}/partitions r,") // connected plugs have a non-nil security snippet for seccomp seccompSpec := &seccomp.Specification{} - err = seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err = seccompSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) c.Check(seccompSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "ptrace\n") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/system_trace_test.go snapd-2.31.1+17.10/interfaces/builtin/system_trace_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/system_trace_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/system_trace_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type SystemTraceInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + plugInfo *snap.PlugInfo + slot *interfaces.ConnectedSlot + slotInfo *snap.SlotInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&SystemTraceInterfaceSuite{ @@ -48,15 +50,15 @@ command: foo plugs: [system-trace] ` - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "system-trace", - Interface: "system-trace", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "system-trace", + Interface: "system-trace", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, mockPlugSnapInfo, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["system-trace"]} + s.plugInfo = plugSnap.Plugs["system-trace"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *SystemTraceInterfaceSuite) TestName(c *C) { @@ -64,23 +66,23 @@ } func (s *SystemTraceInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "system-trace", Interface: "system-trace", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, "system-trace slots are reserved for the core snap") + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "system-trace slots are reserved for the core snap") } func (s *SystemTraceInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *SystemTraceInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Check(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, "/sys/kernel/debug/tracing/ r,") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/thumbnailer_service.go snapd-2.31.1+17.10/interfaces/builtin/thumbnailer_service.go --- snapd-2.29.4.2+17.10/interfaces/builtin/thumbnailer_service.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/thumbnailer_service.go 2018-01-24 20:02:44.000000000 +0000 @@ -24,6 +24,7 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" + "github.com/snapcore/snapd/snap" ) const thumbnailerServiceSummary = `allows operating as or interacting with the Thumbnailer service` @@ -109,7 +110,7 @@ } } -func (iface *thumbnailerServiceInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *thumbnailerServiceInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { snippet := thumbnailerServiceConnectedPlugAppArmor old := "###SLOT_SECURITY_TAGS###" new := slotAppLabelExpr(slot) @@ -118,15 +119,15 @@ return nil } -func (iface *thumbnailerServiceInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { +func (iface *thumbnailerServiceInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(thumbnailerServicePermanentSlotAppArmor) return nil } -func (iface *thumbnailerServiceInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *thumbnailerServiceInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { snippet := thumbnailerServiceConnectedSlotAppArmor old := "###PLUG_SNAP_NAME###" - new := plug.Snap.Name() + new := plug.Snap().Name() snippet = strings.Replace(snippet, old, new, -1) old = "###PLUG_SECURITY_TAGS###" diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/thumbnailer_service_test.go snapd-2.31.1+17.10/interfaces/builtin/thumbnailer_service_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/thumbnailer_service_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/thumbnailer_service_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -25,15 +25,19 @@ "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/snap/snaptest" "github.com/snapcore/snapd/testutil" ) type ThumbnailerServiceInterfaceSuite struct { - iface interfaces.Interface - coreSlot *interfaces.Slot - classicSlot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + coreSlotInfo *snap.SlotInfo + coreSlot *interfaces.ConnectedSlot + classicSlotInfo *snap.SlotInfo + classicSlot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&ThumbnailerServiceInterfaceSuite{ @@ -65,13 +69,16 @@ ` // thumbnailer-service snap with thumbnailer-service slot on an core/all-snap install. snapInfo := snaptest.MockInfo(c, thumbnailerServiceMockCoreSlotSnapInfoYaml, nil) - s.coreSlot = &interfaces.Slot{SlotInfo: snapInfo.Slots["thumbnailer-service"]} + s.coreSlotInfo = snapInfo.Slots["thumbnailer-service"] + s.coreSlot = interfaces.NewConnectedSlot(s.coreSlotInfo, nil) // thumbnailer-service slot on a core snap in a classic install. snapInfo = snaptest.MockInfo(c, thumbnailerServiceMockClassicSlotSnapInfoYaml, nil) - s.classicSlot = &interfaces.Slot{SlotInfo: snapInfo.Slots["thumbnailer-service"]} + s.classicSlotInfo = snapInfo.Slots["thumbnailer-service"] + s.classicSlot = interfaces.NewConnectedSlot(s.classicSlotInfo, nil) plugSnap := snaptest.MockInfo(c, mockPlugSnapInfo, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["thumbnailer-service"]} + s.plugInfo = plugSnap.Plugs["thumbnailer-service"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *ThumbnailerServiceInterfaceSuite) TestName(c *C) { @@ -81,20 +88,20 @@ func (s *ThumbnailerServiceInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected slots have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedSlot(s.iface, s.plug, nil, s.coreSlot, nil) + err := apparmorSpec.AddConnectedSlot(s.iface, s.plug, s.coreSlot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.thumbnailer-service.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.thumbnailer-service.app"), testutil.Contains, `interface=com.canonical.Thumbnailer`) // slots have no permanent snippet on classic apparmorSpec = &apparmor.Specification{} - err = apparmorSpec.AddConnectedSlot(s.iface, s.plug, nil, s.classicSlot, nil) + err = apparmorSpec.AddConnectedSlot(s.iface, s.plug, s.classicSlot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), HasLen, 0) // slots have a permanent non-nil security snippet for apparmor apparmorSpec = &apparmor.Specification{} - err = apparmorSpec.AddPermanentSlot(s.iface, s.coreSlot) + err = apparmorSpec.AddPermanentSlot(s.iface, s.coreSlotInfo) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.thumbnailer-service.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.thumbnailer-service.app"), testutil.Contains, `member={RequestName,ReleaseName,GetConnectionCredentials}`) @@ -102,7 +109,7 @@ func (s *ThumbnailerServiceInterfaceSuite) TestSlotGrantedAccessToPlugFiles(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedSlot(s.iface, s.plug, nil, s.coreSlot, nil) + err := apparmorSpec.AddConnectedSlot(s.iface, s.plug, s.coreSlot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.thumbnailer-service.app"}) snippet := apparmorSpec.SnippetForTag("snap.thumbnailer-service.app") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/time_control.go snapd-2.31.1+17.10/interfaces/builtin/time_control.go --- snapd-2.29.4.2+17.10/interfaces/builtin/time_control.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/time_control.go 2018-02-16 20:27:57.000000000 +0000 @@ -74,6 +74,13 @@ # set-local-rtc commands. /usr/bin/timedatectl{,.real} ixr, +# Silence this noisy denial. systemd utilities look at /proc/1/environ to see +# if running in a container, but they will fallback gracefully. No other +# interfaces allow this denial, so no problems with silencing it for now. Note +# that allowing this triggers a 'ptrace trace peer=unconfined' denial, which we +# want to avoid. +deny @{PROC}/1/environ r, + # Allow write access to system real-time clock # See 'man 4 rtc' for details. @@ -88,10 +95,28 @@ # As the core snap ships the hwclock utility we can also allow # clients to use it now that they have access to the relevant -# device nodes. +# device nodes. Note: some invocations of hwclock will try to +# write to the audit subsystem. We omit 'capability audit_write' +# and 'capability net_admin' here. Applications requiring audit +# logging should plug 'netlink-audit'. /sbin/hwclock ixr, ` +const timeControlConnectedPlugSecComp = ` +# Description: Can set time and date via systemd' timedated D-Bus interface. +# Can read all properties of /org/freedesktop/timedate1 D-Bus object; see +# https://www.freedesktop.org/wiki/Software/systemd/timedated/; This also +# gives full access to the RTC device nodes and relevant parts of sysfs. + +settimeofday + +# util-linux built with libaudit tries to write to the audit subsystem. We +# allow the socket call here to avoid seccomp kill, but omit the AppArmor +# capability rules. +bind +socket AF_NETLINK - NETLINK_AUDIT +` + var timeControlConnectedPlugUDev = []string{`SUBSYSTEM=="rtc"`} func init() { @@ -102,6 +127,7 @@ implicitOnClassic: true, baseDeclarationSlots: timeControlBaseDeclarationSlots, connectedPlugAppArmor: timeControlConnectedPlugAppArmor, + connectedPlugSecComp: timeControlConnectedPlugSecComp, connectedPlugUDev: timeControlConnectedPlugUDev, reservedForOS: true, }) diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/time_control_test.go snapd-2.31.1+17.10/interfaces/builtin/time_control_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/time_control_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/time_control_test.go 2018-02-16 20:27:57.000000000 +0000 @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2016-2017 Canonical Ltd + * Copyright (C) 2016-2018 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 @@ -25,27 +25,22 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/interfaces/seccomp" "github.com/snapcore/snapd/interfaces/udev" "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/testutil" ) type TimeControlInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&TimeControlInterfaceSuite{ iface: builtin.MustInterface("time-control"), - slot: &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "time-control", - Interface: "time-control", - }, - }, - plug: nil, }) const timectlConsumerYaml = `name: consumer @@ -61,8 +56,8 @@ ` func (s *TimeControlInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, timectlConsumerYaml, nil, "time-control") - s.slot = MockSlot(c, timectlCoreYaml, nil, "time-control") + s.plug, s.plugInfo = MockConnectedPlug(c, timectlConsumerYaml, nil, "time-control") + s.slot, s.slotInfo = MockConnectedSlot(c, timectlCoreYaml, nil, "time-control") } func (s *TimeControlInterfaceSuite) TestName(c *C) { @@ -70,32 +65,42 @@ } func (s *TimeControlInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "time-control", Interface: "time-control", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "time-control slots are reserved for the core snap") } func (s *TimeControlInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *TimeControlInterfaceSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) c.Check(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "org/freedesktop/timedate1") } +func (s *TimeControlInterfaceSuite) TestSecCompSpec(c *C) { + spec := &seccomp.Specification{} + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) + c.Check(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "settimeofday") + c.Check(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "socket AF_NETLINK - NETLINK_AUDIT") +} + func (s *TimeControlInterfaceSuite) TestUDevSpec(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 1) - c.Assert(spec.Snippets()[0], testutil.Contains, `SUBSYSTEM=="rtc", TAG+="snap_consumer_app"`) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Snippets(), HasLen, 2) + c.Assert(spec.Snippets(), testutil.Contains, `# time-control +SUBSYSTEM=="rtc", TAG+="snap_consumer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_consumer_app", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_consumer_app $devpath $major:$minor"`) } func (s *TimeControlInterfaceSuite) TestStaticInfo(c *C) { @@ -107,7 +112,8 @@ } func (s *TimeControlInterfaceSuite) TestAutoConnect(c *C) { - c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) + // FIXME: fix AutoConnect methods to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) } func (s *TimeControlInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/timeserver_control.go snapd-2.31.1+17.10/interfaces/builtin/timeserver_control.go --- snapd-2.29.4.2+17.10/interfaces/builtin/timeserver_control.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/timeserver_control.go 2017-12-01 15:51:55.000000000 +0000 @@ -78,6 +78,13 @@ # D-Bus method for controlling network time synchronization via # timedatectl's set-ntp command. /usr/bin/timedatectl{,.real} ixr, + +# Silence this noisy denial. systemd utilities look at /proc/1/environ to see +# if running in a container, but they will fallback gracefully. No other +# interfaces allow this denial, so no problems with silencing it for now. Note +# that allowing this triggers a 'ptrace trace peer=unconfined' denial, which we +# want to avoid. +deny @{PROC}/1/environ r, ` func init() { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/timeserver_control_test.go snapd-2.31.1+17.10/interfaces/builtin/timeserver_control_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/timeserver_control_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/timeserver_control_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type TimeserverControlInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&TimeserverControlInterfaceSuite{ @@ -48,15 +50,15 @@ command: foo plugs: [timeserver-control] ` - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "timeserver-control", - Interface: "timeserver-control", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "timeserver-control", + Interface: "timeserver-control", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) snapInfo := snaptest.MockInfo(c, mockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: snapInfo.Plugs["timeserver-control"]} + s.plugInfo = snapInfo.Plugs["timeserver-control"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *TimeserverControlInterfaceSuite) TestName(c *C) { @@ -64,24 +66,24 @@ } func (s *TimeserverControlInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "timeserver-control", Interface: "timeserver-control", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "timeserver-control slots are reserved for the core snap") } func (s *TimeserverControlInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *TimeserverControlInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, "path=/org/freedesktop/timedate1") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/timezone_control.go snapd-2.31.1+17.10/interfaces/builtin/timezone_control.go --- snapd-2.29.4.2+17.10/interfaces/builtin/timezone_control.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/timezone_control.go 2017-12-01 15:51:55.000000000 +0000 @@ -80,6 +80,13 @@ # D-Bus method for setting the timezone via timedatectl's set-timezone # command. /usr/bin/timedatectl{,.real} ixr, + +# Silence this noisy denial. systemd utilities look at /proc/1/environ to see +# if running in a container, but they will fallback gracefully. No other +# interfaces allow this denial, so no problems with silencing it for now. Note +# that allowing this triggers a 'ptrace trace peer=unconfined' denial, which we +# want to avoid. +deny @{PROC}/1/environ r, ` func init() { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/timezone_control_test.go snapd-2.31.1+17.10/interfaces/builtin/timezone_control_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/timezone_control_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/timezone_control_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type TimezoneControlInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&TimezoneControlInterfaceSuite{ @@ -48,15 +50,15 @@ command: foo plugs: [timezone-control] ` - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "timezone-control", - Interface: "timezone-control", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "timezone-control", + Interface: "timezone-control", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, mockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["timezone-control"]} + s.plugInfo = plugSnap.Plugs["timezone-control"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *TimezoneControlInterfaceSuite) TestName(c *C) { @@ -64,24 +66,24 @@ } func (s *TimezoneControlInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "timezone-control", Interface: "timezone-control", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "timezone-control slots are reserved for the core snap") } func (s *TimezoneControlInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *TimezoneControlInterfaceSuite) TestConnectedPlug(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `timedate1`) diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/tpm_test.go snapd-2.31.1+17.10/interfaces/builtin/tpm_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/tpm_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/tpm_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type TpmInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&TpmInterfaceSuite{ @@ -53,8 +55,8 @@ ` func (s *TpmInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, tpmConsumerYaml, nil, "tpm") - s.slot = MockSlot(c, tpmCoreYaml, nil, "tpm") + s.plug, s.plugInfo = MockConnectedPlug(c, tpmConsumerYaml, nil, "tpm") + s.slot, s.slotInfo = MockConnectedSlot(c, tpmCoreYaml, nil, "tpm") } func (s *TpmInterfaceSuite) TestName(c *C) { @@ -62,32 +64,34 @@ } func (s *TpmInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "tpm", Interface: "tpm", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "tpm slots are reserved for the core snap") } func (s *TpmInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *TpmInterfaceSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, "/dev/tpm0") } func (s *TpmInterfaceSuite) TestUDevSpec(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(spec.Snippets(), HasLen, 1) - c.Assert(spec.Snippets()[0], testutil.Contains, `KERNEL=="tpm[0-9]*", TAG+="snap_consumer_app"`) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.Snippets(), HasLen, 2) + c.Assert(spec.Snippets(), testutil.Contains, `# tpm +KERNEL=="tpm[0-9]*", TAG+="snap_consumer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_consumer_app", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_consumer_app $devpath $major:$minor"`) } func (s *TpmInterfaceSuite) TestStaticInfo(c *C) { @@ -99,7 +103,8 @@ } func (s *TpmInterfaceSuite) TestAutoConnect(c *C) { - c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) + // FIXME: fix AutoConnect methods to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) } func (s *TpmInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/ubuntu_download_manager.go snapd-2.31.1+17.10/interfaces/builtin/ubuntu_download_manager.go --- snapd-2.29.4.2+17.10/interfaces/builtin/ubuntu_download_manager.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/ubuntu_download_manager.go 2018-01-24 20:02:44.000000000 +0000 @@ -24,6 +24,7 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" + "github.com/snapcore/snapd/snap" ) const ubuntuDownloadManagerSummary = `allows operating as or interacting with the Ubuntu download manager` @@ -211,7 +212,7 @@ return iface.Name() } -func (iface *ubuntuDownloadManagerInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *ubuntuDownloadManagerInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###SLOT_SECURITY_TAGS###" new := slotAppLabelExpr(slot) snippet := strings.Replace(downloadConnectedPlugAppArmor, old, new, -1) @@ -219,17 +220,17 @@ return nil } -func (iface *ubuntuDownloadManagerInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { +func (iface *ubuntuDownloadManagerInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(downloadPermanentSlotAppArmor) return nil } -func (iface *ubuntuDownloadManagerInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *ubuntuDownloadManagerInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###PLUG_SECURITY_TAGS###" new := plugAppLabelExpr(plug) snippet := strings.Replace(downloadConnectedSlotAppArmor, old, new, -1) old = "###PLUG_NAME###" - new = plug.Snap.Name() + new = plug.Snap().Name() snippet = strings.Replace(snippet, old, new, -1) spec.AddSnippet(snippet) return nil diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/ubuntu_download_manager_test.go snapd-2.31.1+17.10/interfaces/builtin/ubuntu_download_manager_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/ubuntu_download_manager_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/ubuntu_download_manager_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,9 +31,11 @@ ) type UbuntuDownloadManagerInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&UbuntuDownloadManagerInterfaceSuite{ @@ -49,14 +51,14 @@ plugs: [ubuntu-download-manager] ` snapInfo := snaptest.MockInfo(c, mockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: snapInfo.Plugs["ubuntu-download-manager"]} - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "ubuntu-download-manager", - Interface: "ubuntu-download-manager", - }, + s.plugInfo = snapInfo.Plugs["ubuntu-download-manager"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "ubuntu-download-manager", + Interface: "ubuntu-download-manager", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) } func (s *UbuntuDownloadManagerInterfaceSuite) TestName(c *C) { @@ -64,23 +66,23 @@ } func (s *UbuntuDownloadManagerInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *UbuntuDownloadManagerInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "ubuntu-download-manager", Interface: "ubuntu-download-manager", - }} - c.Assert(slot.Sanitize(s.iface), IsNil) + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), IsNil) } func (s *UbuntuDownloadManagerInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, "path=/com/canonical/applications/download/**") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/udisks2.go snapd-2.31.1+17.10/interfaces/builtin/udisks2.go --- snapd-2.29.4.2+17.10/interfaces/builtin/udisks2.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/udisks2.go 2018-01-24 20:02:44.000000000 +0000 @@ -27,6 +27,7 @@ "github.com/snapcore/snapd/interfaces/dbus" "github.com/snapcore/snapd/interfaces/seccomp" "github.com/snapcore/snapd/interfaces/udev" + "github.com/snapcore/snapd/snap" ) const udisks2Summary = `allows operating as or interacting with the UDisks2 service` @@ -130,6 +131,14 @@ path=/org/freedesktop/UDisks2/** interface=org.freedesktop.UDisks2.* peer=(label=###PLUG_SECURITY_TAGS###), + +# Allow clients to introspect the service +dbus (receive) + bus=system + path=/org/freedesktop/UDisks2 + interface=org.freedesktop.DBus.Introspectable + member=Introspect + peer=(label=###PLUG_SECURITY_TAGS###), ` const udisks2ConnectedPlugAppArmor = ` @@ -187,6 +196,10 @@ + + + + ` const udisks2ConnectedPlugDBus = ` @@ -366,17 +379,17 @@ } } -func (iface *udisks2Interface) DBusConnectedPlug(spec *dbus.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *udisks2Interface) DBusConnectedPlug(spec *dbus.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { spec.AddSnippet(udisks2ConnectedPlugDBus) return nil } -func (iface *udisks2Interface) DBusPermanentSlot(spec *dbus.Specification, slot *interfaces.Slot) error { +func (iface *udisks2Interface) DBusPermanentSlot(spec *dbus.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(udisks2PermanentSlotDBus) return nil } -func (iface *udisks2Interface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *udisks2Interface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###SLOT_SECURITY_TAGS###" new := slotAppLabelExpr(slot) snippet := strings.Replace(udisks2ConnectedPlugAppArmor, old, new, -1) @@ -384,12 +397,12 @@ return nil } -func (iface *udisks2Interface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { +func (iface *udisks2Interface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(udisks2PermanentSlotAppArmor) return nil } -func (iface *udisks2Interface) UDevPermanentSlot(spec *udev.Specification, slot *interfaces.Slot) error { +func (iface *udisks2Interface) UDevPermanentSlot(spec *udev.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(udisks2PermanentSlotUDev) spec.TagDevice(`SUBSYSTEM=="block"`) // # This tags all USB devices, so we'll use AppArmor to mediate specific access (eg, /dev/sd* and /dev/mmcblk*) @@ -397,7 +410,7 @@ return nil } -func (iface *udisks2Interface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *udisks2Interface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###PLUG_SECURITY_TAGS###" new := plugAppLabelExpr(plug) snippet := strings.Replace(udisks2ConnectedSlotAppArmor, old, new, -1) @@ -405,7 +418,7 @@ return nil } -func (iface *udisks2Interface) SecCompPermanentSlot(spec *seccomp.Specification, slot *interfaces.Slot) error { +func (iface *udisks2Interface) SecCompPermanentSlot(spec *seccomp.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(udisks2PermanentSlotSecComp) return nil } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/udisks2_test.go snapd-2.31.1+17.10/interfaces/builtin/udisks2_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/udisks2_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/udisks2_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -28,13 +28,16 @@ "github.com/snapcore/snapd/interfaces/dbus" "github.com/snapcore/snapd/interfaces/seccomp" "github.com/snapcore/snapd/interfaces/udev" + "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/testutil" ) type UDisks2InterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&UDisks2InterfaceSuite{ @@ -88,8 +91,8 @@ ` func (s *UDisks2InterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, udisks2ConsumerYaml, nil, "udisks2") - s.slot = MockSlot(c, udisks2ProducerYaml, nil, "udisks2") + s.plug, s.plugInfo = MockConnectedPlug(c, udisks2ConsumerYaml, nil, "udisks2") + s.slot, s.slotInfo = MockConnectedSlot(c, udisks2ProducerYaml, nil, "udisks2") } func (s *UDisks2InterfaceSuite) TestName(c *C) { @@ -97,54 +100,54 @@ } func (s *UDisks2InterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) } func (s *UDisks2InterfaceSuite) TestAppArmorSpec(c *C) { // The label uses short form when exactly one app is bound to the udisks2 slot spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, `peer=(label="snap.producer.app"),`) // The label glob when all apps are bound to the udisks2 slot - slot := MockSlot(c, udisks2ProducerTwoAppsYaml, nil, "udisks2") + slot, _ := MockConnectedSlot(c, udisks2ProducerTwoAppsYaml, nil, "udisks2") spec = &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, slot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `peer=(label="snap.producer.*"),`) // The label uses alternation when some, but not all, apps is bound to the udisks2 slot - slot = MockSlot(c, udisks2ProducerThreeAppsYaml, nil, "udisks2") + slot, _ = MockConnectedSlot(c, udisks2ProducerThreeAppsYaml, nil, "udisks2") spec = &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, slot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `peer=(label="snap.producer.{app1,app3}"),`) // The label uses short form when exactly one app is bound to the udisks2 plug spec = &apparmor.Specification{} - c.Assert(spec.AddConnectedSlot(s.iface, s.plug, nil, s.slot, nil), IsNil) + c.Assert(spec.AddConnectedSlot(s.iface, s.plug, s.slot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.producer.app"}) c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, `peer=(label="snap.consumer.app"),`) // The label glob when all apps are bound to the udisks2 plug - plug := MockPlug(c, udisks2ConsumerTwoAppsYaml, nil, "udisks2") + plug, _ := MockConnectedPlug(c, udisks2ConsumerTwoAppsYaml, nil, "udisks2") spec = &apparmor.Specification{} - c.Assert(spec.AddConnectedSlot(s.iface, plug, nil, s.slot, nil), IsNil) + c.Assert(spec.AddConnectedSlot(s.iface, plug, s.slot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.producer.app"}) c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, `peer=(label="snap.consumer.*"),`) // The label uses alternation when some, but not all, apps is bound to the udisks2 plug - plug = MockPlug(c, udisks2ConsumerThreeAppsYaml, nil, "udisks2") + plug, _ = MockConnectedPlug(c, udisks2ConsumerThreeAppsYaml, nil, "udisks2") spec = &apparmor.Specification{} - c.Assert(spec.AddConnectedSlot(s.iface, plug, nil, s.slot, nil), IsNil) + c.Assert(spec.AddConnectedSlot(s.iface, plug, s.slot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.producer.app"}) c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, `peer=(label="snap.consumer.{app1,app2}"),`) // permanent slot have a non-nil security snippet for apparmor spec = &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(spec.AddPermanentSlot(s.iface, s.slot), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.slotInfo), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app", "snap.producer.app"}) c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `peer=(label="snap.producer.app"),`) c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, `peer=(label=unconfined),`) @@ -152,27 +155,32 @@ func (s *UDisks2InterfaceSuite) TestDBusSpec(c *C) { spec := &dbus.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, ``) spec = &dbus.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.slot), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.slotInfo), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.producer.app"}) c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, ``) + c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, `send_interface="org.freedesktop.DBus.Introspectable"`) } func (s *UDisks2InterfaceSuite) TestUDevSpec(c *C) { spec := &udev.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.slot), IsNil) - c.Assert(spec.Snippets(), HasLen, 3) + c.Assert(spec.AddPermanentSlot(s.iface, s.slotInfo), IsNil) + c.Assert(spec.Snippets(), HasLen, 4) c.Assert(spec.Snippets()[0], testutil.Contains, `LABEL="udisks_probe_end"`) - c.Assert(spec.Snippets(), testutil.Contains, `SUBSYSTEM=="usb", TAG+="snap_producer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `# udisks2 +SUBSYSTEM=="block", TAG+="snap_producer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `# udisks2 +SUBSYSTEM=="usb", TAG+="snap_producer_app"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_producer_app", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_producer_app $devpath $major:$minor"`) } func (s *UDisks2InterfaceSuite) TestSecCompSpec(c *C) { spec := &seccomp.Specification{} - c.Assert(spec.AddPermanentSlot(s.iface, s.slot), IsNil) + c.Assert(spec.AddPermanentSlot(s.iface, s.slotInfo), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.producer.app"}) c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, "mount\n") } @@ -186,7 +194,8 @@ } func (s *UDisks2InterfaceSuite) TestAutoConnect(c *C) { - c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) + // FIXME: fix AutoConnect methods to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.slotInfo}), Equals, true) } func (s *UDisks2InterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/uhid_test.go snapd-2.31.1+17.10/interfaces/builtin/uhid_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/uhid_test.go 2017-11-30 07:12:26.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/uhid_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -30,9 +30,11 @@ ) type UhidInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&UhidInterfaceSuite{ @@ -52,8 +54,8 @@ ` func (s *UhidInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, uhidConsumerYaml, nil, "uhid") - s.slot = MockSlot(c, uhidCoreYaml, nil, "uhid") + s.plug, s.plugInfo = MockConnectedPlug(c, uhidConsumerYaml, nil, "uhid") + s.slot, s.slotInfo = MockConnectedSlot(c, uhidCoreYaml, nil, "uhid") } func (s *UhidInterfaceSuite) TestName(c *C) { @@ -61,24 +63,24 @@ } func (s *UhidInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "uhid", Interface: "uhid", - }} + } - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "uhid slots are reserved for the core snap") } func (s *UhidInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *UhidInterfaceSuite) TestAppArmorSpec(c *C) { spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) + 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, "/dev/uhid rw,\n") } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/unity7.go snapd-2.31.1+17.10/interfaces/builtin/unity7.go --- snapd-2.29.4.2+17.10/interfaces/builtin/unity7.go 2017-11-30 07:12:26.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/unity7.go 2018-02-02 16:53:04.000000000 +0000 @@ -25,6 +25,7 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/seccomp" + "github.com/snapcore/snapd/snap" ) const unity7Summary = `allows interacting with Unity 7 services` @@ -43,9 +44,19 @@ #include #include + +# Allow finding the DBus session bus id (eg, via dbus_bus_get_id()) +dbus (send) + bus=session + path=/org/freedesktop/DBus + interface=org.freedesktop.DBus + member=GetId + peer=(name=org.freedesktop.DBus, label=unconfined), + #include #include +owner @{HOME}/.local/share/fonts/{,**} r, /var/cache/fontconfig/ r, /var/cache/fontconfig/** mr, @@ -104,6 +115,16 @@ member=OpenURL peer=(label=unconfined), +# Allow use of snapd's internal 'xdg-settings' +/usr/bin/xdg-settings ixr, +/usr/bin/dbus-send ixr, +dbus (send) + bus=session + path=/io/snapcraft/Settings + interface=io.snapcraft.Settings + member={Check,Get,Set} + peer=(label=unconfined), + # input methods (ibus) # subset of ibus abstraction /usr/lib/@{multiarch}/gtk-2.0/[0-9]*/immodules/im-ibus.so mr, @@ -418,14 +439,14 @@ bus=session path=/org/freedesktop/Notifications interface=org.freedesktop.Notifications - member="{GetCapabilities,GetServerInformation,Notify}" + member="{GetCapabilities,GetServerInformation,Notify,CloseNotification}" peer=(label=unconfined), dbus (receive) bus=session path=/org/freedesktop/Notifications interface=org.freedesktop.Notifications - member=NotificationClosed + member={ActionInvoked,NotificationClosed} peer=(label=unconfined), dbus (send) @@ -546,6 +567,49 @@ path=/org/gnome/SettingsDaemon/MediaKeys member="Get{,All}" peer=(label=unconfined), + +# Allow checking status, activating and locking the screensaver +# mate +dbus (send) + bus=session + path="/{,org/mate/}ScreenSaver" + interface=org.mate.ScreenSaver + member="{GetActive,GetActiveTime,Lock,SetActive}" + peer=(label=unconfined), + +dbus (receive) + bus=session + path="/{,org/mate/}ScreenSaver" + interface=org.mate.ScreenSaver + member=ActiveChanged + peer=(label=unconfined), + +# Unity +dbus (send) + bus=session + interface=com.canonical.Unity.Session + path=/com/canonical/Unity/Session + member="{ActivateScreenSaver,IsLocked,Lock}" + peer=(label=unconfined), + +# Allow unconfined to introspect us +dbus (receive) + bus=session + interface=org.freedesktop.DBus.Introspectable + member=Introspect + peer=(label=unconfined), + +# gtk2/gvfs gtk_show_uri() +dbus (send) + bus=session + path=/org/gtk/vfs/mounttracker + interface=org.gtk.vfs.MountTracker + member=ListMountableInfo, +dbus (send) + bus=session + path=/org/gtk/vfs/mounttracker + interface=org.gtk.vfs.MountTracker + member=LookupMount, ` const unity7ConnectedPlugSeccomp = ` @@ -553,9 +617,6 @@ # to various DBus services and this environment does not prevent eavesdropping # or apps interfering with one another. -# X -shutdown - # Needed by QtSystems on X to detect mouse and keyboard socket AF_NETLINK - NETLINK_KOBJECT_UEVENT bind @@ -575,24 +636,24 @@ } } -func (iface *unity7Interface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *unity7Interface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { // Unity7 will take the desktop filename and convert all '-' (and '.', // but we don't care about that here because the rule above already // does that) to '_'. Since we know that the desktop filename starts // with the snap name, perform this conversion on the snap name. - new := strings.Replace(plug.Snap.Name(), "-", "_", -1) + new := strings.Replace(plug.Snap().Name(), "-", "_", -1) old := "###UNITY_SNAP_NAME###" snippet := strings.Replace(unity7ConnectedPlugAppArmor, old, new, -1) spec.AddSnippet(snippet) return nil } -func (iface *unity7Interface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *unity7Interface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { spec.AddSnippet(unity7ConnectedPlugSeccomp) return nil } -func (iface *unity7Interface) SanitizeSlot(slot *interfaces.Slot) error { +func (iface *unity7Interface) BeforePrepareSlot(slot *snap.SlotInfo) error { return sanitizeSlotReservedForOS(iface, slot) } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/unity7_test.go snapd-2.31.1+17.10/interfaces/builtin/unity7_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/unity7_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/unity7_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -32,9 +32,11 @@ ) type Unity7InterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&Unity7InterfaceSuite{ @@ -50,15 +52,15 @@ ` func (s *Unity7InterfaceSuite) SetUpTest(c *C) { - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "unity7", - Interface: "unity7", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "unity7", + Interface: "unity7", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, unity7mockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["unity7"]} + s.plugInfo = plugSnap.Plugs["unity7"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *Unity7InterfaceSuite) TestName(c *C) { @@ -66,24 +68,24 @@ } func (s *Unity7InterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "unity7", Interface: "unity7", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "unity7 slots are reserved for the core snap") } func (s *Unity7InterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *Unity7InterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other-snap.app2"}) c.Assert(apparmorSpec.SnippetForTag("snap.other-snap.app2"), testutil.Contains, `/usr/share/pixmaps`) @@ -91,10 +93,10 @@ // connected plugs have a non-nil security snippet for seccomp seccompSpec := &seccomp.Specification{} - err = seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err = seccompSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.other-snap.app2"}) - c.Check(seccompSpec.SnippetForTag("snap.other-snap.app2"), testutil.Contains, "shutdown\n") + c.Check(seccompSpec.SnippetForTag("snap.other-snap.app2"), testutil.Contains, "bind\n") } func (s *Unity7InterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/unity8_calendar_test.go snapd-2.31.1+17.10/interfaces/builtin/unity8_calendar_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/unity8_calendar_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/unity8_calendar_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -33,10 +33,13 @@ ) type Unity8CalendarInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - coreSlot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + coreSlotInfo *snap.SlotInfo + coreSlot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&Unity8CalendarInterfaceSuite{ @@ -58,19 +61,20 @@ command: foo plugs: [unity8-calendar] ` - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "unity8-calendar", - Interface: "unity8-calendar", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "unity8-calendar", + Interface: "unity8-calendar", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, mockPlugSnapInfo, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["unity8-calendar"]} + s.plugInfo = plugSnap.Plugs["unity8-calendar"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) slotSnap := snaptest.MockInfo(c, mockCoreSlotInfoYaml, nil) - s.coreSlot = &interfaces.Slot{SlotInfo: slotSnap.Slots["unity8-calendar"]} + s.coreSlotInfo = slotSnap.Slots["unity8-calendar"] + s.coreSlot = interfaces.NewConnectedSlot(s.coreSlotInfo, nil) } func (s *Unity8CalendarInterfaceSuite) TestName(c *C) { @@ -78,13 +82,13 @@ } func (s *Unity8CalendarInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *Unity8CalendarInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), HasLen, 1) } @@ -93,21 +97,19 @@ func (s *Unity8CalendarInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelAll(c *C) { app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "unity8", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - Name: "unity8-calendar", - Interface: "unity8-calendar", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "unity8", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, }, - } + Name: "unity8-calendar", + Interface: "unity8-calendar", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) release.OnClassic = false apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `peer=(label="snap.unity8.*"),`) @@ -118,21 +120,19 @@ app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} app3 := &snap.AppInfo{Name: "app3"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "unity8", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, - }, - Name: "unity8-calendar", - Interface: "unity8-calendar", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "unity8", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, }, - } + Name: "unity8-calendar", + Interface: "unity8-calendar", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) release.OnClassic = false apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `peer=(label="snap.unity8.{app1,app2}"),`) @@ -141,21 +141,19 @@ // The label uses short form when exactly one app is bound to the calendar slot func (s *Unity8CalendarInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelOne(c *C) { app := &snap.AppInfo{Name: "app"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "unity8", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - Name: "unity8-calendar", - Interface: "unity8-calendar", - Apps: map[string]*snap.AppInfo{"app": app}, + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "unity8", + Apps: map[string]*snap.AppInfo{"app": app}, }, - } + Name: "unity8-calendar", + Interface: "unity8-calendar", + Apps: map[string]*snap.AppInfo{"app": app}, + }, nil) release.OnClassic = false apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `peer=(label="snap.unity8.app"),`) @@ -165,7 +163,7 @@ release.OnClassic = true apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) snippet := apparmorSpec.SnippetForTag("snap.other.app") @@ -178,7 +176,7 @@ func (s *Unity8CalendarInterfaceSuite) TestConnectedPlugSnippetAppArmor(c *C) { release.OnClassic = false apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) snippet := apparmorSpec.SnippetForTag("snap.other.app") @@ -190,7 +188,7 @@ func (s *Unity8CalendarInterfaceSuite) TestConnectedSlotSnippetAppArmor(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedSlot(s.iface, s.plug, nil, s.coreSlot, nil) + err := apparmorSpec.AddConnectedSlot(s.iface, s.plug, s.coreSlot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.unity8-calendar.app"}) c.Check(apparmorSpec.SnippetForTag("snap.unity8-calendar.app"), testutil.Contains, "peer=(label=\"snap.other.app\")") @@ -198,7 +196,7 @@ func (s *Unity8CalendarInterfaceSuite) TestPermanentSlotSnippetAppArmor(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddPermanentSlot(s.iface, s.coreSlot) + err := apparmorSpec.AddPermanentSlot(s.iface, s.coreSlotInfo) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.unity8-calendar.app"}) c.Check(apparmorSpec.SnippetForTag("snap.unity8-calendar.app"), testutil.Contains, "name=\"org.gnome.evolution.dataserver.Sources5\"") @@ -206,7 +204,7 @@ func (s *Unity8CalendarInterfaceSuite) TestPermanentSlotSnippetSecComp(c *C) { seccompSpec := &seccomp.Specification{} - err := seccompSpec.AddPermanentSlot(s.iface, s.coreSlot) + err := seccompSpec.AddPermanentSlot(s.iface, s.coreSlotInfo) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.unity8-calendar.app"}) c.Check(seccompSpec.SnippetForTag("snap.unity8-calendar.app"), testutil.Contains, "listen\n") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/unity8_contacts_test.go snapd-2.31.1+17.10/interfaces/builtin/unity8_contacts_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/unity8_contacts_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/unity8_contacts_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -33,10 +33,13 @@ ) type Unity8ContactsInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - coreSlot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + coreSlotInfo *snap.SlotInfo + coreSlot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&Unity8ContactsInterfaceSuite{ @@ -59,19 +62,20 @@ command: foo slots: [unity8-contacts] ` - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "unity8-contacts", - Interface: "unity8-contacts", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "unity8-contacts", + Interface: "unity8-contacts", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, mockPlugSnapInfo, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["unity8-contacts"]} + s.plugInfo = plugSnap.Plugs["unity8-contacts"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) slotSnap := snaptest.MockInfo(c, mockCoreSlotInfoYaml, nil) - s.coreSlot = &interfaces.Slot{SlotInfo: slotSnap.Slots["unity8-contacts"]} + s.coreSlotInfo = slotSnap.Slots["unity8-contacts"] + s.coreSlot = interfaces.NewConnectedSlot(s.coreSlotInfo, nil) } func (s *Unity8ContactsInterfaceSuite) TestName(c *C) { @@ -79,13 +83,13 @@ } func (s *Unity8ContactsInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *Unity8ContactsInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), HasLen, 1) } @@ -94,21 +98,19 @@ func (s *Unity8ContactsInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelAll(c *C) { app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "unity8", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - Name: "unity8-contacts", - Interface: "unity8-contacts", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "unity8", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, }, - } + Name: "unity8-contacts", + Interface: "unity8-contacts", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) release.OnClassic = false apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `peer=(label="snap.unity8.*"),`) @@ -119,21 +121,19 @@ app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} app3 := &snap.AppInfo{Name: "app3"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "unity8", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, - }, - Name: "unity8-contacts", - Interface: "unity8-contacts", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "unity8", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, }, - } + Name: "unity8-contacts", + Interface: "unity8-contacts", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) release.OnClassic = false apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `peer=(label="snap.unity8.{app1,app2}"),`) @@ -142,21 +142,19 @@ // The label uses short form when exactly one app is bound to the calendar slot func (s *Unity8ContactsInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelOne(c *C) { app := &snap.AppInfo{Name: "app"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "unity8", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - Name: "unity8-contacts", - Interface: "unity8-contacts", - Apps: map[string]*snap.AppInfo{"app": app}, + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "unity8", + Apps: map[string]*snap.AppInfo{"app": app}, }, - } + Name: "unity8-contacts", + Interface: "unity8-contacts", + Apps: map[string]*snap.AppInfo{"app": app}, + }, nil) release.OnClassic = false apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `peer=(label="snap.unity8.app"),`) @@ -166,7 +164,7 @@ release.OnClassic = true apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) snippet := apparmorSpec.SnippetForTag("snap.other.app") @@ -181,7 +179,7 @@ release.OnClassic = false apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) snippet := apparmorSpec.SnippetForTag("snap.other.app") @@ -193,7 +191,7 @@ func (s *Unity8ContactsInterfaceSuite) TestConnectedSlotSnippetAppArmor(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedSlot(s.iface, s.plug, nil, s.coreSlot, nil) + err := apparmorSpec.AddConnectedSlot(s.iface, s.plug, s.coreSlot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.contacts.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.contacts.app"), testutil.Contains, "peer=(label=\"snap.other.app\")") @@ -201,7 +199,7 @@ func (s *Unity8ContactsInterfaceSuite) TestPermanentSlotSnippetAppArmor(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddPermanentSlot(s.iface, s.coreSlot) + err := apparmorSpec.AddPermanentSlot(s.iface, s.coreSlotInfo) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.contacts.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.contacts.app"), testutil.Contains, "name=\"org.gnome.evolution.dataserver.Sources5\"") @@ -209,7 +207,7 @@ func (s *Unity8ContactsInterfaceSuite) TestPermanentSlotSnippetSecComp(c *C) { seccompSpec := &seccomp.Specification{} - err := seccompSpec.AddPermanentSlot(s.iface, s.coreSlot) + err := seccompSpec.AddPermanentSlot(s.iface, s.coreSlotInfo) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.contacts.app"}) c.Check(seccompSpec.SnippetForTag("snap.contacts.app"), testutil.Contains, "listen\n") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/unity8.go snapd-2.31.1+17.10/interfaces/builtin/unity8.go --- snapd-2.29.4.2+17.10/interfaces/builtin/unity8.go 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/unity8.go 2018-01-24 20:02:44.000000000 +0000 @@ -24,7 +24,6 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" - "github.com/snapcore/snapd/interfaces/seccomp" ) const unity8Summary = `allows operating as or interacting with Unity 8` @@ -88,10 +87,6 @@ peer=(name=com.ubuntu.content.dbus.Service,label=###SLOT_SECURITY_TAGS###), ` -const unity8ConnectedPlugSecComp = ` -shutdown -` - type unity8Interface struct{} func (iface *unity8Interface) Name() string { @@ -110,7 +105,7 @@ return iface.Name() } -func (iface *unity8Interface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *unity8Interface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { oldTags := "###SLOT_SECURITY_TAGS###" newTags := slotAppLabelExpr(slot) snippet := strings.Replace(unity8ConnectedPlugAppArmor, oldTags, newTags, -1) @@ -118,11 +113,6 @@ return nil } -func (iface *unity8Interface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - spec.AddSnippet(unity8ConnectedPlugSecComp) - return nil -} - func (iface *unity8Interface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/unity8_pim_common.go snapd-2.31.1+17.10/interfaces/builtin/unity8_pim_common.go --- snapd-2.29.4.2+17.10/interfaces/builtin/unity8_pim_common.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/unity8_pim_common.go 2018-01-24 20:02:44.000000000 +0000 @@ -27,6 +27,7 @@ "github.com/snapcore/snapd/interfaces/dbus" "github.com/snapcore/snapd/interfaces/seccomp" "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" ) const unity8PimCommonPermanentSlotAppArmor = ` @@ -122,12 +123,12 @@ } } -func (iface *unity8PimCommonInterface) DBusPermanentSlot(spec *dbus.Specification, slot *interfaces.Slot) error { +func (iface *unity8PimCommonInterface) DBusPermanentSlot(spec *dbus.Specification, slot *snap.SlotInfo) error { //FIXME: Implement support after session services are available. return nil } -func (iface *unity8PimCommonInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *unity8PimCommonInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###SLOT_SECURITY_TAGS###" new := slotAppLabelExpr(slot) @@ -143,13 +144,13 @@ return nil } -func (iface *unity8PimCommonInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { +func (iface *unity8PimCommonInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(unity8PimCommonPermanentSlotAppArmor) spec.AddSnippet(iface.permanentSlotAppArmor) return nil } -func (iface *unity8PimCommonInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *unity8PimCommonInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###PLUG_SECURITY_TAGS###" new := plugAppLabelExpr(plug) snippet := unity8PimCommonConnectedSlotAppArmor @@ -159,7 +160,7 @@ return nil } -func (iface *unity8PimCommonInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *interfaces.Slot) error { +func (iface *unity8PimCommonInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(unity8PimCommonPermanentSlotSecComp) return nil } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/unity8_test.go snapd-2.31.1+17.10/interfaces/builtin/unity8_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/unity8_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/unity8_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -20,53 +20,29 @@ package builtin_test import ( - "io/ioutil" - "os" - "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/seccomp" "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap/snaptest" "github.com/snapcore/snapd/testutil" ) type unity8InterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&unity8InterfaceSuite{ iface: builtin.MustInterface("unity8"), }) -func createMockFooPlug(c *C, content string) *interfaces.Plug { - info := snaptest.MockSnap(c, content, "", &snap.SideInfo{Revision: snap.R(3)}) - - desktopDir := filepath.Join(info.MountDir(), "meta", "gui") - err := os.MkdirAll(desktopDir, 0755) - c.Assert(err, IsNil) - - desktopPath := filepath.Join(desktopDir, "foo.desktop") - err = ioutil.WriteFile(desktopPath, []byte(""), 0644) - c.Assert(err, IsNil) - - return &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: info, - Name: info.Name(), - Interface: "unity8", - Apps: info.Apps, - }, - } -} - func (s *unity8InterfaceSuite) SetUpTest(c *C) { const mockPlugSnapInfoYaml = `name: other version: 1.0 @@ -76,15 +52,15 @@ plugs: [unity8] ` dirs.SetRootDir(c.MkDir()) - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "unity8-session"}, - Name: "unity8-session", - Interface: "unity8", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "unity8-session"}, + Name: "unity8-session", + Interface: "unity8", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, mockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["unity8"]} + s.plugInfo = plugSnap.Plugs["unity8"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } @@ -99,22 +75,15 @@ func (s *unity8InterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.unity8-app"}) c.Check(apparmorSpec.SnippetForTag("snap.other.unity8-app"), testutil.Contains, "name=com.canonical.URLDispatcher") - - // connected plugs have a non-nil security snippet for seccomp - seccompSpec := &seccomp.Specification{} - err = seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) - c.Assert(err, IsNil) - c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.other.unity8-app"}) - c.Check(seccompSpec.SnippetForTag("snap.other.unity8-app"), testutil.Contains, "shutdown\n") } func (s *unity8InterfaceSuite) TestSecurityTags(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.unity8-app"}) c.Check(apparmorSpec.SnippetForTag("snap.other.unity8-app"), testutil.Contains, "label=\"snap.unity8-session.*\"") diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/upower_observe.go snapd-2.31.1+17.10/interfaces/builtin/upower_observe.go --- snapd-2.29.4.2+17.10/interfaces/builtin/upower_observe.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/upower_observe.go 2018-01-24 20:02:44.000000000 +0000 @@ -27,6 +27,7 @@ "github.com/snapcore/snapd/interfaces/dbus" "github.com/snapcore/snapd/interfaces/seccomp" "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" ) const upowerObserveSummary = `allows operating as or reading from the UPower service` @@ -234,7 +235,7 @@ } } -func (iface *upowerObserveInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *upowerObserveInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###SLOT_SECURITY_TAGS###" new := slotAppLabelExpr(slot) if release.OnClassic { @@ -246,22 +247,22 @@ return nil } -func (iface *upowerObserveInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { +func (iface *upowerObserveInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(upowerObservePermanentSlotAppArmor) return nil } -func (iface *upowerObserveInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *interfaces.Slot) error { +func (iface *upowerObserveInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(upowerObservePermanentSlotSeccomp) return nil } -func (iface *upowerObserveInterface) DBusPermanentSlot(spec *dbus.Specification, slot *interfaces.Slot) error { +func (iface *upowerObserveInterface) DBusPermanentSlot(spec *dbus.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(upowerObservePermanentSlotDBus) return nil } -func (iface *upowerObserveInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (iface *upowerObserveInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { old := "###PLUG_SECURITY_TAGS###" new := plugAppLabelExpr(plug) snippet := strings.Replace(upowerObserveConnectedSlotAppArmor, old, new, -1) @@ -269,7 +270,7 @@ return nil } -func (iface *upowerObserveInterface) SanitizeSlot(slot *interfaces.Slot) error { +func (iface *upowerObserveInterface) BeforePrepareSlot(slot *snap.SlotInfo) error { return sanitizeSlotReservedForOSOrApp(iface, slot) } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/upower_observe_test.go snapd-2.31.1+17.10/interfaces/builtin/upower_observe_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/upower_observe_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/upower_observe_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -33,10 +33,13 @@ ) type UPowerObserveInterfaceSuite struct { - iface interfaces.Interface - coreSlot *interfaces.Slot - classicSlot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + coreSlotInfo *snap.SlotInfo + coreSlot *interfaces.ConnectedSlot + classicSlotInfo *snap.SlotInfo + classicSlot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&UPowerObserveInterfaceSuite{ @@ -69,13 +72,16 @@ ` // upower snap with upower-server slot on an core/all-snap install. snapInfo := snaptest.MockInfo(c, upowerMockSlotSnapInfoYaml, nil) - s.coreSlot = &interfaces.Slot{SlotInfo: snapInfo.Slots["upower-observe"]} + s.coreSlotInfo = snapInfo.Slots["upower-observe"] + s.coreSlot = interfaces.NewConnectedSlot(s.coreSlotInfo, nil) // upower-observe slot on a core snap in a classic install. snapInfo = snaptest.MockInfo(c, upowerMockClassicSlotSnapInfoYaml, nil) - s.classicSlot = &interfaces.Slot{SlotInfo: snapInfo.Slots["upower-observe"]} + s.classicSlotInfo = snapInfo.Slots["upower-observe"] + s.classicSlot = interfaces.NewConnectedSlot(s.classicSlotInfo, nil) // snap with the upower-observe plug snapInfo = snaptest.MockInfo(c, mockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: snapInfo.Plugs["upower-observe"]} + s.plugInfo = snapInfo.Plugs["upower-observe"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *UPowerObserveInterfaceSuite) TestName(c *C) { @@ -83,40 +89,38 @@ } func (s *UPowerObserveInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.coreSlot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.coreSlotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "upower-observe", Interface: "upower-observe", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "upower-observe slots are reserved for the core and app snaps") } func (s *UPowerObserveInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } // The label glob when all apps are bound to the ofono slot func (s *UPowerObserveInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelAll(c *C) { app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "upower", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - Name: "upower", - Interface: "upower-observe", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "upower", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, }, - } + Name: "upower", + Interface: "upower-observe", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) release.OnClassic = false apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `peer=(label="snap.upower.*"),`) @@ -127,21 +131,19 @@ app1 := &snap.AppInfo{Name: "app1"} app2 := &snap.AppInfo{Name: "app2"} app3 := &snap.AppInfo{Name: "app3"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "upower", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, - }, - Name: "upower", - Interface: "upower", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "upower", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, }, - } + Name: "upower", + Interface: "upower", + Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, + }, nil) release.OnClassic = false apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `peer=(label="snap.upower.{app1,app2}"),`) @@ -150,21 +152,19 @@ // The label uses short form when exactly one app is bound to the upower-observe slot func (s *UPowerObserveInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelOne(c *C) { app := &snap.AppInfo{Name: "app"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "upower", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - Name: "upower", - Interface: "upower", - Apps: map[string]*snap.AppInfo{"app": app}, + slot := interfaces.NewConnectedSlot(&snap.SlotInfo{ + Snap: &snap.Info{ + SuggestedName: "upower", + Apps: map[string]*snap.AppInfo{"app": app}, }, - } + Name: "upower", + Interface: "upower", + Apps: map[string]*snap.AppInfo{"app": app}, + }, nil) release.OnClassic = false apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `peer=(label="snap.upower.app"),`) @@ -173,7 +173,7 @@ func (s *UPowerObserveInterfaceSuite) TestConnectedPlugSnippetUsesUnconfinedLabelOnClassic(c *C) { release.OnClassic = true apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.classicSlot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.classicSlot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) snippet := apparmorSpec.SnippetForTag("snap.other.app") @@ -186,7 +186,7 @@ func (s *UPowerObserveInterfaceSuite) TestConnectedPlugSnippetAppArmor(c *C) { release.OnClassic = false apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.coreSlot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.coreSlot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) snippet := apparmorSpec.SnippetForTag("snap.other.app") @@ -198,7 +198,7 @@ func (s *UPowerObserveInterfaceSuite) TestPermanentSlotSnippetAppArmor(c *C) { apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddPermanentSlot(s.iface, s.coreSlot) + err := apparmorSpec.AddPermanentSlot(s.iface, s.coreSlotInfo) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.upowerd.app"}) c.Check(apparmorSpec.SnippetForTag("snap.upowerd.app"), testutil.Contains, "org.freedesktop.UPower") @@ -214,7 +214,7 @@ func (s *UPowerObserveInterfaceSuite) TestPermanentSlotSnippetSecComp(c *C) { seccompSpec := &seccomp.Specification{} - err := seccompSpec.AddPermanentSlot(s.iface, s.coreSlot) + err := seccompSpec.AddPermanentSlot(s.iface, s.coreSlotInfo) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.upowerd.app"}) c.Check(seccompSpec.SnippetForTag("snap.upowerd.app"), testutil.Contains, "bind\n") @@ -222,20 +222,18 @@ func (s *UPowerObserveInterfaceSuite) TestConnectedSlotSnippetUsesPlugLabelOne(c *C) { app := &snap.AppInfo{Name: "app"} - plug := &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{ - SuggestedName: "upower", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - Name: "upower", - Interface: "upower-observe", - Apps: map[string]*snap.AppInfo{"app": app}, + plug := interfaces.NewConnectedPlug(&snap.PlugInfo{ + Snap: &snap.Info{ + SuggestedName: "upower", + Apps: map[string]*snap.AppInfo{"app": app}, }, - } + Name: "upower", + Interface: "upower-observe", + Apps: map[string]*snap.AppInfo{"app": app}, + }, nil) apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedSlot(s.iface, plug, nil, s.coreSlot, nil) + err := apparmorSpec.AddConnectedSlot(s.iface, plug, s.coreSlot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.upowerd.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.upowerd.app"), testutil.Contains, `peer=(label="snap.upower.app"),`) diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/utils.go snapd-2.31.1+17.10/interfaces/builtin/utils.go --- snapd-2.29.4.2+17.10/interfaces/builtin/utils.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/utils.go 2018-01-24 20:02:44.000000000 +0000 @@ -28,6 +28,9 @@ "github.com/snapcore/snapd/snap" ) +// The maximum number of Usb bInterfaceNumber. +const UsbMaxInterfaces = 32 + // AppLabelExpr returns the specification of the apparmor label describing // all the apps bound to a given slot. The result has one of three forms, // depending on how apps are bound to the slot: @@ -62,38 +65,16 @@ return buf.String() } -func slotAppLabelExpr(slot *interfaces.Slot) string { - return appLabelExpr(slot.Apps, slot.Snap) -} - -func plugAppLabelExpr(plug *interfaces.Plug) string { - return appLabelExpr(plug.Apps, plug.Snap) -} - -// Function to support creation of udev snippet -func udevUsbDeviceSnippet(subsystem string, usbVendor int64, usbProduct int64, key string, data string) string { - const udevHeader string = `IMPORT{builtin}="usb_id"` - const udevDevicePrefix string = `SUBSYSTEM=="%s", SUBSYSTEMS=="usb", ATTRS{idVendor}=="%04x", ATTRS{idProduct}=="%04x"` - const udevSuffix string = `, %s+="%s"` - - var udevSnippet bytes.Buffer - udevSnippet.WriteString(udevHeader + "\n") - udevSnippet.WriteString(fmt.Sprintf(udevDevicePrefix, subsystem, usbVendor, usbProduct)) - udevSnippet.WriteString(fmt.Sprintf(udevSuffix, key, data)) - return udevSnippet.String() +func slotAppLabelExpr(slot *interfaces.ConnectedSlot) string { + return appLabelExpr(slot.Apps(), slot.Snap()) } -// Function to create an udev TAG, essentially the cgroup name for -// the snap application. -// @param snapName is the name of the snap -// @param appName is the name of the application -// @return string "snap__" -func udevSnapSecurityName(snapName string, appName string) string { - return fmt.Sprintf(`snap_%s_%s`, snapName, appName) +func plugAppLabelExpr(plug *interfaces.ConnectedPlug) string { + return appLabelExpr(plug.Apps(), plug.Snap()) } // sanitizeSlotReservedForOS checks if slot is of type os. -func sanitizeSlotReservedForOS(iface interfaces.Interface, slot *interfaces.Slot) error { +func sanitizeSlotReservedForOS(iface interfaces.Interface, slot *snap.SlotInfo) error { if slot.Snap.Type != snap.TypeOS { return fmt.Errorf("%s slots are reserved for the core snap", iface.Name()) } @@ -101,7 +82,7 @@ } // sanitizeSlotReservedForOSOrGadget checks if the slot is of type os or gadget. -func sanitizeSlotReservedForOSOrGadget(iface interfaces.Interface, slot *interfaces.Slot) error { +func sanitizeSlotReservedForOSOrGadget(iface interfaces.Interface, slot *snap.SlotInfo) error { if slot.Snap.Type != snap.TypeOS && slot.Snap.Type != snap.TypeGadget { return fmt.Errorf("%s slots are reserved for the core and gadget snaps", iface.Name()) } @@ -109,7 +90,7 @@ } // sanitizeSlotReservedForOSOrApp checks if the slot is of type os or app. -func sanitizeSlotReservedForOSOrApp(iface interfaces.Interface, slot *interfaces.Slot) error { +func sanitizeSlotReservedForOSOrApp(iface interfaces.Interface, slot *snap.SlotInfo) error { if slot.Snap.Type != snap.TypeOS && slot.Snap.Type != snap.TypeApp { return fmt.Errorf("%s slots are reserved for the core and app snaps", iface.Name()) } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/utils_test.go snapd-2.31.1+17.10/interfaces/builtin/utils_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/utils_test.go 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/utils_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -20,26 +20,29 @@ package builtin_test import ( + "fmt" + . "gopkg.in/check.v1" "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/builtin" "github.com/snapcore/snapd/interfaces/ifacetest" "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snaptest" ) type utilsSuite struct { iface interfaces.Interface - slotOS *interfaces.Slot - slotApp *interfaces.Slot - slotGadget *interfaces.Slot + slotOS *snap.SlotInfo + slotApp *snap.SlotInfo + slotGadget *snap.SlotInfo } var _ = Suite(&utilsSuite{ iface: &ifacetest.TestInterface{InterfaceName: "iface"}, - slotOS: &interfaces.Slot{SlotInfo: &snap.SlotInfo{Snap: &snap.Info{Type: snap.TypeOS}}}, - slotApp: &interfaces.Slot{SlotInfo: &snap.SlotInfo{Snap: &snap.Info{Type: snap.TypeApp}}}, - slotGadget: &interfaces.Slot{SlotInfo: &snap.SlotInfo{Snap: &snap.Info{Type: snap.TypeGadget}}}, + slotOS: &snap.SlotInfo{Snap: &snap.Info{Type: snap.TypeOS}}, + slotApp: &snap.SlotInfo{Snap: &snap.Info{Type: snap.TypeApp}}, + slotGadget: &snap.SlotInfo{Snap: &snap.Info{Type: snap.TypeGadget}}, }) func (s *utilsSuite) TestSanitizeSlotReservedForOS(c *C) { @@ -63,10 +66,26 @@ c.Assert(builtin.SanitizeSlotReservedForOSOrApp(s.iface, s.slotGadget), ErrorMatches, errmsg) } -func MockPlug(c *C, yaml string, si *snap.SideInfo, plugName string) *interfaces.Plug { +func MockPlug(c *C, yaml string, si *snap.SideInfo, plugName string) *snap.PlugInfo { return builtin.MockPlug(c, yaml, si, plugName) } -func MockSlot(c *C, yaml string, si *snap.SideInfo, slotName string) *interfaces.Slot { +func MockSlot(c *C, yaml string, si *snap.SideInfo, slotName string) *snap.SlotInfo { return builtin.MockSlot(c, yaml, si, slotName) } + +func MockConnectedPlug(c *C, yaml string, si *snap.SideInfo, plugName string) (*interfaces.ConnectedPlug, *snap.PlugInfo) { + info := snaptest.MockInfo(c, yaml, si) + if plugInfo, ok := info.Plugs[plugName]; ok { + return interfaces.NewConnectedPlug(plugInfo, nil), plugInfo + } + panic(fmt.Sprintf("cannot find plug %q in snap %q", plugName, info.Name())) +} + +func MockConnectedSlot(c *C, yaml string, si *snap.SideInfo, slotName string) (*interfaces.ConnectedSlot, *snap.SlotInfo) { + info := snaptest.MockInfo(c, yaml, si) + if slotInfo, ok := info.Slots[slotName]; ok { + return interfaces.NewConnectedSlot(slotInfo, nil), slotInfo + } + panic(fmt.Sprintf("cannot find slot %q in snap %q", slotName, info.Name())) +} diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/wayland.go snapd-2.31.1+17.10/interfaces/builtin/wayland.go --- snapd-2.29.4.2+17.10/interfaces/builtin/wayland.go 2017-10-23 06:17:24.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/wayland.go 2018-01-24 20:02:44.000000000 +0000 @@ -19,32 +19,152 @@ package builtin +import ( + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/apparmor" + "github.com/snapcore/snapd/interfaces/seccomp" + "github.com/snapcore/snapd/interfaces/udev" + "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" + + "strings" +) + const waylandSummary = `allows access to compositors supporting wayland protocol` const waylandBaseDeclarationSlots = ` wayland: allow-installation: slot-snap-type: + - app - core + deny-connection: + on-classic: false + deny-auto-connection: + on-classic: false ` -const waylandConnectedPlugAppArmor = ` -# Description: Can access compositors supporting the wayland protocol +const waylandPermanentSlotAppArmor = ` +# Description: Allow operating as a Wayland display server. This gives privileged access +# to the system. + +# needed since Wayland is a display server and needs to configure tty devices +capability sys_tty_config, +/dev/tty[0-9]* rw, + +# Create the Wayland socket and lock file +owner /run/user/[0-9]*/wayland-[0-9]* rw, +# Allow access to common client Wayland sockets from non-snap clients +/run/user/[0-9]*/wayland-shared-* rw, +/run/user/[0-9]*/wayland-cursor-shared-* rw, +/run/user/[0-9]*/xwayland-shared-* rw, + +# Allow write access to create /run/user/* to create XDG_RUNTIME_DIR (until lp:1738197 is fixed) +/run/user/[0-9]*/ w, + +# Needed for mode setting via drmSetMaster() and drmDropMaster() +capability sys_admin, + +# Weston probes this on start +/sys/devices/pci**/boot_vga r, + +# NOTE: this allows reading and inserting all input events +/dev/input/* rw, + +# For using udev +network netlink raw, +/run/udev/data/c13:[0-9]* r, +/run/udev/data/+input:input[0-9]* r, +/run/udev/data/+platform:* r, +` -# Allow access to the wayland compsitor server socket -owner /run/user/*/wayland-[0-9]* rw, +const waylandPermanentSlotSecComp = ` +# Description: Allow operating as a Wayland server. This gives privileged access +# to the system. +# Needed for server launch +bind +listen +# Needed by server upon client connect +accept +accept4 +# for udev +socket AF_NETLINK - NETLINK_KOBJECT_UEVENT +` + +const waylandConnectedSlotAppArmor = ` +# Allow access to common client Wayland sockets for connected snaps +owner /run/user/[0-9]*/###PLUG_SECURITY_TAGS###/wayland-shared-* rw, +owner /run/user/[0-9]*/###PLUG_SECURITY_TAGS###/wayland-cursor-shared-* rw, +owner /run/user/[0-9]*/###PLUG_SECURITY_TAGS###/xwayland-shared-* rw, +` + +const waylandConnectedPlugAppArmor = ` +# Allow access to the Wayland compositor server socket +owner /run/user/[0-9]*/wayland-[0-9]* rw, # Needed when using QT_QPA_PLATFORM=wayland-egl /etc/drirc r, ` +type waylandInterface struct{} + +func (iface *waylandInterface) Name() string { + return "wayland" +} + +func (iface *waylandInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ + Summary: waylandSummary, + ImplicitOnClassic: true, + BaseDeclarationSlots: waylandBaseDeclarationSlots, + } +} + +func (iface *waylandInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { + spec.AddSnippet(waylandConnectedPlugAppArmor) + return nil +} + +func (iface *waylandInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { + if !release.OnClassic { + old := "###PLUG_SECURITY_TAGS###" + new := "snap." + plug.Snap().Name() // forms the snap-specific subdirectory name of /run/user/*/ used for XDG_RUNTIME_DIR + snippet := strings.Replace(waylandConnectedSlotAppArmor, old, new, -1) + spec.AddSnippet(snippet) + } + return nil +} + +func (iface *waylandInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *snap.SlotInfo) error { + if !release.OnClassic { + spec.AddSnippet(waylandPermanentSlotSecComp) + } + return nil +} + +func (iface *waylandInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { + if !release.OnClassic { + spec.AddSnippet(waylandPermanentSlotAppArmor) + } + return nil +} + +func (iface *waylandInterface) UDevPermanentSlot(spec *udev.Specification, slot *snap.SlotInfo) error { + if !release.OnClassic { + spec.TagDevice(`KERNEL=="tty[0-9]*"`) + spec.TagDevice(`KERNEL=="mice"`) + spec.TagDevice(`KERNEL=="mouse[0-9]*"`) + spec.TagDevice(`KERNEL=="event[0-9]*"`) + spec.TagDevice(`KERNEL=="ts[0-9]*"`) + } + return nil +} + +func (iface *waylandInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { + // allow what declarations allowed + return true +} + func init() { - registerIface(&commonInterface{ - name: "wayland", - summary: waylandSummary, - implicitOnClassic: true, - baseDeclarationSlots: waylandBaseDeclarationSlots, - connectedPlugAppArmor: waylandConnectedPlugAppArmor, - reservedForOS: true, - }) + registerIface(&waylandInterface{}) } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/wayland_test.go snapd-2.31.1+17.10/interfaces/builtin/wayland_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/wayland_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/wayland_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -25,14 +25,21 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/interfaces/seccomp" + "github.com/snapcore/snapd/interfaces/udev" + "github.com/snapcore/snapd/release" "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/testutil" ) type WaylandInterfaceSuite struct { - iface interfaces.Interface - coreSlot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + coreSlotInfo *snap.SlotInfo + coreSlot *interfaces.ConnectedSlot + classicSlotInfo *snap.SlotInfo + classicSlot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } var _ = Suite(&WaylandInterfaceSuite{ @@ -45,15 +52,25 @@ plugs: [wayland] ` -const waylandCoreYaml = `name: core +// a wayland slot on a wayland snap (as installed on a core/all-snap system) +const waylandCoreYaml = `name: wayland +apps: + app1: + slots: [wayland] +` + +// a wayland slot on the core snap (as automatically added on classic) +const waylandClassicYaml = `name: core type: os slots: - wayland: + wayland: + interface: wayland ` func (s *WaylandInterfaceSuite) SetUpTest(c *C) { - s.plug = MockPlug(c, waylandConsumerYaml, nil, "wayland") - s.coreSlot = MockSlot(c, waylandCoreYaml, nil, "wayland") + s.plug, s.plugInfo = MockConnectedPlug(c, waylandConsumerYaml, nil, "wayland") + s.coreSlot, s.coreSlotInfo = MockConnectedSlot(c, waylandCoreYaml, nil, "wayland") + s.classicSlot, s.classicSlotInfo = MockConnectedSlot(c, waylandClassicYaml, nil, "wayland") } func (s *WaylandInterfaceSuite) TestName(c *C) { @@ -61,34 +78,117 @@ } func (s *WaylandInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.coreSlot.Sanitize(s.iface), IsNil) - // wayland slot currently only used with core - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "some-snap"}, - Name: "wayland", - Interface: "wayland", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, - "wayland slots are reserved for the core snap") + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.coreSlotInfo), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.classicSlotInfo), IsNil) } func (s *WaylandInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *WaylandInterfaceSuite) TestAppArmorSpec(c *C) { + // on a core system with wayland slot coming from a regular app snap. + restore := release.MockOnClassic(false) + defer restore() + // connected plug to core slot spec := &apparmor.Specification{} - c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.coreSlot, nil), IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.coreSlot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) - c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "owner /run/user/*/wayland-[0-9]* rw,") + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "/etc/drirc r,") - // connected plug to core slot + // connected core slot to plug spec = &apparmor.Specification{} - c.Assert(spec.AddConnectedSlot(s.iface, s.plug, nil, s.coreSlot, nil), IsNil) + c.Assert(spec.AddConnectedSlot(s.iface, s.plug, s.coreSlot), IsNil) + c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.wayland.app1"}) + c.Assert(spec.SnippetForTag("snap.wayland.app1"), testutil.Contains, "owner /run/user/[0-9]*/snap.consumer/wayland-shared-* rw,") + + // permanent core slot + spec = &apparmor.Specification{} + c.Assert(spec.AddPermanentSlot(s.iface, s.coreSlotInfo), IsNil) + c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.wayland.app1"}) + c.Assert(spec.SnippetForTag("snap.wayland.app1"), testutil.Contains, "capability sys_tty_config,") +} + +func (s *WaylandInterfaceSuite) TestAppArmorSpecOnClassic(c *C) { + // on a classic system with wayland slot coming from the core snap. + restore := release.MockOnClassic(true) + defer restore() + + // connected plug to classic slot + spec := &apparmor.Specification{} + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.classicSlot), IsNil) + c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "owner /run/user/[0-9]*/wayland-[0-9]* rw,") + + // connected classic slot to plug + spec = &apparmor.Specification{} + c.Assert(spec.AddConnectedSlot(s.iface, s.plug, s.classicSlot), IsNil) + c.Assert(spec.SecurityTags(), HasLen, 0) + + // permanent classic slot + spec = &apparmor.Specification{} + c.Assert(spec.AddPermanentSlot(s.iface, s.classicSlotInfo), IsNil) c.Assert(spec.SecurityTags(), HasLen, 0) } +func (s *WaylandInterfaceSuite) TestSecCompOnClassic(c *C) { + // on a classic system with wayland slot coming from the core snap. + restore := release.MockOnClassic(true) + defer restore() + + seccompSpec := &seccomp.Specification{} + err := seccompSpec.AddPermanentSlot(s.iface, s.classicSlotInfo) + c.Assert(err, IsNil) + err = seccompSpec.AddConnectedPlug(s.iface, s.plug, s.classicSlot) + c.Assert(err, IsNil) + // No SecComp on Classic + c.Assert(seccompSpec.SecurityTags(), IsNil) +} + +func (s *WaylandInterfaceSuite) TestSecCompOnCore(c *C) { + // on a core system with wayland slot coming from a regular app snap. + restore := release.MockOnClassic(false) + defer restore() + + seccompSpec := &seccomp.Specification{} + err := seccompSpec.AddPermanentSlot(s.iface, s.coreSlotInfo) + c.Assert(err, IsNil) + err = seccompSpec.AddConnectedPlug(s.iface, s.plug, s.coreSlot) + c.Assert(err, IsNil) + c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.wayland.app1"}) + c.Assert(seccompSpec.SnippetForTag("snap.wayland.app1"), testutil.Contains, "listen\n") +} + +func (s *WaylandInterfaceSuite) TestUDev(c *C) { + // on a core system with wayland slot coming from a regular app snap. + restore := release.MockOnClassic(false) + defer restore() + + spec := &udev.Specification{} + c.Assert(spec.AddPermanentSlot(s.iface, s.coreSlotInfo), IsNil) + c.Assert(spec.Snippets(), HasLen, 6) + c.Assert(spec.Snippets(), testutil.Contains, `# wayland +KERNEL=="event[0-9]*", TAG+="snap_wayland_app1"`) + c.Assert(spec.Snippets(), testutil.Contains, `# wayland +KERNEL=="mice", TAG+="snap_wayland_app1"`) + c.Assert(spec.Snippets(), testutil.Contains, `# wayland +KERNEL=="mouse[0-9]*", TAG+="snap_wayland_app1"`) + c.Assert(spec.Snippets(), testutil.Contains, `# wayland +KERNEL=="ts[0-9]*", TAG+="snap_wayland_app1"`) + c.Assert(spec.Snippets(), testutil.Contains, `# wayland +KERNEL=="tty[0-9]*", TAG+="snap_wayland_app1"`) + c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_wayland_app1", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_wayland_app1 $devpath $major:$minor"`) + + // on a classic system with wayland slot coming from the core snap. + restore = release.MockOnClassic(true) + defer restore() + + spec = &udev.Specification{} + c.Assert(spec.AddPermanentSlot(s.iface, s.coreSlotInfo), IsNil) + c.Assert(spec.Snippets(), HasLen, 0) +} + func (s *WaylandInterfaceSuite) TestStaticInfo(c *C) { si := interfaces.StaticInfoOf(s.iface) c.Assert(si.ImplicitOnCore, Equals, false) @@ -97,6 +197,12 @@ c.Assert(si.BaseDeclarationSlots, testutil.Contains, "wayland") } +func (s *WaylandInterfaceSuite) TestAutoConnect(c *C) { + // FIXME fix AutoConnect methods to use ConnectedPlug/Slot + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.coreSlotInfo}), Equals, true) + c.Assert(s.iface.AutoConnect(&interfaces.Plug{PlugInfo: s.plugInfo}, &interfaces.Slot{SlotInfo: s.classicSlotInfo}), Equals, true) +} + func (s *WaylandInterfaceSuite) TestInterfaces(c *C) { c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) } diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/x11.go snapd-2.31.1+17.10/interfaces/builtin/x11.go --- snapd-2.29.4.2+17.10/interfaces/builtin/x11.go 2017-08-30 09:16:36.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/x11.go 2018-01-24 20:02:44.000000000 +0000 @@ -34,7 +34,7 @@ #include #include - +owner @{HOME}/.local/share/fonts/{,**} r, /var/cache/fontconfig/ r, /var/cache/fontconfig/** mr, @@ -55,8 +55,6 @@ # Description: Can access the X server. Restricted because X does not prevent # eavesdropping or apps interfering with one another. -shutdown - # Needed by QtSystems on X to detect mouse and keyboard socket AF_NETLINK - NETLINK_KOBJECT_UEVENT bind diff -Nru snapd-2.29.4.2+17.10/interfaces/builtin/x11_test.go snapd-2.31.1+17.10/interfaces/builtin/x11_test.go --- snapd-2.29.4.2+17.10/interfaces/builtin/x11_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/builtin/x11_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -32,9 +32,11 @@ ) type X11InterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug } const x11MockPlugSnapInfoYaml = `name: other @@ -50,15 +52,15 @@ }) func (s *X11InterfaceSuite) SetUpTest(c *C) { - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "x11", - Interface: "x11", - }, + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "x11", + Interface: "x11", } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) plugSnap := snaptest.MockInfo(c, x11MockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["x11"]} + s.plugInfo = plugSnap.Plugs["x11"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) } func (s *X11InterfaceSuite) TestName(c *C) { @@ -66,24 +68,24 @@ } func (s *X11InterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) - slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) + slot := &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "x11", Interface: "x11", - }} - c.Assert(slot.Sanitize(s.iface), ErrorMatches, + } + c.Assert(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, "x11 slots are reserved for the core snap") } func (s *X11InterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } func (s *X11InterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app2"), testutil.Contains, `fontconfig`) @@ -92,10 +94,10 @@ // The shutdown system call is allowed func (s *X11InterfaceSuite) TestLP1574526(c *C) { seccompSpec := &seccomp.Specification{} - err := seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + err := seccompSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) - c.Check(seccompSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "shutdown\n") + c.Check(seccompSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "bind\n") } func (s *X11InterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.29.4.2+17.10/interfaces/connection.go snapd-2.31.1+17.10/interfaces/connection.go --- snapd-2.29.4.2+17.10/interfaces/connection.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/connection.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,276 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package interfaces + +import ( + "fmt" + "reflect" + + "github.com/snapcore/snapd/snap" +) + +// Connection represents a connection between a particular plug and slot. +type Connection struct { + plug *ConnectedPlug + slot *ConnectedSlot +} + +// ConnectedPlug represents a plug that is connected to a slot. +type ConnectedPlug struct { + plugInfo *snap.PlugInfo + staticAttrs map[string]interface{} + dynamicAttrs map[string]interface{} +} + +// ConnectedSlot represents a slot that is connected to a plug. +type ConnectedSlot struct { + slotInfo *snap.SlotInfo + staticAttrs map[string]interface{} + dynamicAttrs map[string]interface{} +} + +// Attrer is an interface with Attr getter method common +// to ConnectedSlot, ConnectedPlug, PlugInfo and SlotInfo types. +type Attrer interface { + Attr(key string, val interface{}) error +} + +func getAttribute(snapName string, ifaceName string, staticAttrs map[string]interface{}, dynamicAttrs map[string]interface{}, key string, val interface{}) error { + var v interface{} + var ok bool + + v, ok = dynamicAttrs[key] + if !ok { + v, ok = staticAttrs[key] + } + + if !ok { + return fmt.Errorf("snap %q does not have attribute %q for interface %q", snapName, key, ifaceName) + } + + rt := reflect.TypeOf(val) + if rt.Kind() != reflect.Ptr || val == nil { + return fmt.Errorf("internal error: cannot get %q attribute of interface %q with non-pointer value", key, ifaceName) + } + + if reflect.TypeOf(v) != rt.Elem() { + return fmt.Errorf("snap %q has interface %q with invalid value type for %q attribute", snapName, ifaceName, key) + } + rv := reflect.ValueOf(val) + rv.Elem().Set(reflect.ValueOf(v)) + return nil +} + +// NewConnectedSlot creates an object representing a connected slot. +func NewConnectedSlot(slot *snap.SlotInfo, dynamicAttrs map[string]interface{}) *ConnectedSlot { + return &ConnectedSlot{ + slotInfo: slot, + staticAttrs: copyAttributes(slot.Attrs), + dynamicAttrs: normalize(dynamicAttrs).(map[string]interface{}), + } +} + +// NewConnectedPlug creates an object representing a connected plug. +func NewConnectedPlug(plug *snap.PlugInfo, dynamicAttrs map[string]interface{}) *ConnectedPlug { + return &ConnectedPlug{ + plugInfo: plug, + staticAttrs: copyAttributes(plug.Attrs), + dynamicAttrs: normalize(dynamicAttrs).(map[string]interface{}), + } +} + +// Interface returns the name of the interface for this plug. +func (plug *ConnectedPlug) Interface() string { + return plug.plugInfo.Interface +} + +// Name returns the name of this plug. +func (plug *ConnectedPlug) Name() string { + return plug.plugInfo.Name +} + +// Snap returns the snap Info of this plug. +func (plug *ConnectedPlug) Snap() *snap.Info { + return plug.plugInfo.Snap +} + +// Apps returns all the apps associated with this plug. +func (plug *ConnectedPlug) Apps() map[string]*snap.AppInfo { + return plug.plugInfo.Apps +} + +// Hooks returns all the hooks associated with this plug. +func (plug *ConnectedPlug) Hooks() map[string]*snap.HookInfo { + return plug.plugInfo.Hooks +} + +// SecurityTags returns the security tags for this plug. +func (plug *ConnectedPlug) SecurityTags() []string { + return plug.plugInfo.SecurityTags() +} + +// StaticAttr returns a static attribute with the given key, or error if attribute doesn't exist. +func (plug *ConnectedPlug) StaticAttr(key string, val interface{}) error { + return getAttribute(plug.Snap().Name(), plug.Interface(), plug.staticAttrs, nil, key, val) +} + +// StaticAttrs returns all static attributes. +func (plug *ConnectedPlug) StaticAttrs() map[string]interface{} { + return plug.staticAttrs +} + +// Attr returns a dynamic attribute with the given name. It falls back to returning static +// attribute if dynamic one doesn't exist. Error is returned if neither dynamic nor static +// attribute exist. +func (plug *ConnectedPlug) Attr(key string, val interface{}) error { + return getAttribute(plug.Snap().Name(), plug.Interface(), plug.staticAttrs, plug.dynamicAttrs, key, val) +} + +// SetAttr sets the given dynamic attribute. Error is returned if the key is already used by a static attribute. +func (plug *ConnectedPlug) SetAttr(key string, value interface{}) error { + if _, ok := plug.staticAttrs[key]; ok { + return fmt.Errorf("cannot change attribute %q as it was statically specified in the snap details", key) + } + if plug.dynamicAttrs == nil { + plug.dynamicAttrs = make(map[string]interface{}) + } + plug.dynamicAttrs[key] = normalize(value) + return nil +} + +// Ref returns the PlugRef for this plug. +func (plug *ConnectedPlug) Ref() *PlugRef { + return &PlugRef{Snap: plug.Snap().Name(), Name: plug.Name()} +} + +// Interface returns the name of the interface for this slot. +func (slot *ConnectedSlot) Interface() string { + return slot.slotInfo.Interface +} + +// Name returns the name of this slot. +func (slot *ConnectedSlot) Name() string { + return slot.slotInfo.Name +} + +// Snap returns the snap Info of this slot. +func (slot *ConnectedSlot) Snap() *snap.Info { + return slot.slotInfo.Snap +} + +// Apps returns all the apps associated with this slot. +func (slot *ConnectedSlot) Apps() map[string]*snap.AppInfo { + return slot.slotInfo.Apps +} + +// Hooks returns all the hooks associated with this slot. +func (slot *ConnectedSlot) Hooks() map[string]*snap.HookInfo { + return slot.slotInfo.Hooks +} + +// SecurityTags returns the security tags for this slot. +func (slot *ConnectedSlot) SecurityTags() []string { + return slot.slotInfo.SecurityTags() +} + +// StaticAttr returns a static attribute with the given key, or error if attribute doesn't exist. +func (slot *ConnectedSlot) StaticAttr(key string, val interface{}) error { + return getAttribute(slot.Snap().Name(), slot.Interface(), slot.staticAttrs, nil, key, val) +} + +// StaticAttrs returns all static attributes. +func (slot *ConnectedSlot) StaticAttrs() map[string]interface{} { + return slot.staticAttrs +} + +// Attr returns a dynamic attribute with the given name. It falls back to returning static +// attribute if dynamic one doesn't exist. Error is returned if neither dynamic nor static +// attribute exist. +func (slot *ConnectedSlot) Attr(key string, val interface{}) error { + return getAttribute(slot.Snap().Name(), slot.Interface(), slot.staticAttrs, slot.dynamicAttrs, key, val) +} + +// SetAttr sets the given dynamic attribute. Error is returned if the key is already used by a static attribute. +func (slot *ConnectedSlot) SetAttr(key string, value interface{}) error { + if _, ok := slot.staticAttrs[key]; ok { + return fmt.Errorf("cannot change attribute %q as it was statically specified in the snap details", key) + } + if slot.dynamicAttrs == nil { + slot.dynamicAttrs = make(map[string]interface{}) + } + slot.dynamicAttrs[key] = normalize(value) + return nil +} + +// Ref returns the SlotRef for this slot. +func (slot *ConnectedSlot) Ref() *SlotRef { + return &SlotRef{Snap: slot.Snap().Name(), Name: slot.Name()} +} + +// Interface returns the name of the interface for this connection. +func (conn *Connection) Interface() string { + return conn.plug.plugInfo.Interface +} + +func copyAttributes(value map[string]interface{}) map[string]interface{} { + return copyRecursive(value).(map[string]interface{}) +} + +func copyRecursive(value interface{}) interface{} { + // note: ensure all the mutable types (or types that need a conversion) + // are handled here. + switch v := value.(type) { + case []interface{}: + arr := make([]interface{}, len(v)) + for i, el := range v { + arr[i] = copyRecursive(el) + } + return arr + case map[string]interface{}: + mp := make(map[string]interface{}, len(v)) + for key, item := range v { + mp[key] = copyRecursive(item) + } + return mp + } + return value +} + +func normalize(value interface{}) interface{} { + // Normalize ints/floats using their 64-bit variants. + // That kind of normalization happens in normalizeYamlValue(..) for static attributes + // when the yaml is loaded, but it needs to be done here as well because we're also + // dealing with dynamic attributes set by the code of interfaces. + switch v := value.(type) { + case int: + return int64(v) + case float32: + return float64(v) + case []interface{}: + for i, el := range v { + v[i] = normalize(el) + } + case map[string]interface{}: + for key, item := range v { + v[key] = normalize(item) + } + } + return value +} diff -Nru snapd-2.29.4.2+17.10/interfaces/connection_test.go snapd-2.31.1+17.10/interfaces/connection_test.go --- snapd-2.29.4.2+17.10/interfaces/connection_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/connection_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,214 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package interfaces + +import ( + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snaptest" +) + +type connSuite struct { + plug *snap.PlugInfo + slot *snap.SlotInfo +} + +var _ = Suite(&connSuite{}) + +func (s *connSuite) SetUpTest(c *C) { + consumer := snaptest.MockInfo(c, ` +name: consumer +apps: + app: +plugs: + plug: + interface: interface + attr: value +`, nil) + s.plug = consumer.Plugs["plug"] + producer := snaptest.MockInfo(c, ` +name: producer +apps: + app: +slots: + slot: + interface: interface + attr: value +`, nil) + s.slot = producer.Slots["slot"] +} + +// Make sure ConnectedPlug,ConnectedSlot, PlugInfo, SlotInfo implement Attrer. +var _ Attrer = (*ConnectedPlug)(nil) +var _ Attrer = (*ConnectedSlot)(nil) +var _ Attrer = (*snap.PlugInfo)(nil) +var _ Attrer = (*snap.SlotInfo)(nil) + +func (s *connSuite) TestStaticSlotAttrs(c *C) { + slot := NewConnectedSlot(s.slot, nil) + c.Assert(slot, NotNil) + + var val string + var intVal int + c.Assert(slot.StaticAttr("unknown", &val), ErrorMatches, `snap "producer" does not have attribute "unknown" for interface "interface"`) + + attrs := slot.StaticAttrs() + c.Assert(attrs, DeepEquals, map[string]interface{}{ + "attr": "value", + }) + slot.StaticAttr("attr", &val) + c.Assert(val, Equals, "value") + + c.Assert(slot.StaticAttr("unknown", &val), ErrorMatches, `snap "producer" does not have attribute "unknown" for interface "interface"`) + c.Check(slot.StaticAttr("attr", &intVal), ErrorMatches, `snap "producer" has interface "interface" with invalid value type for "attr" attribute`) + c.Check(slot.StaticAttr("attr", val), ErrorMatches, `internal error: cannot get "attr" attribute of interface "interface" with non-pointer value`) +} + +func (s *connSuite) TestSlotRef(c *C) { + slot := NewConnectedSlot(s.slot, nil) + c.Assert(slot, NotNil) + c.Assert(*slot.Ref(), DeepEquals, SlotRef{Snap: "producer", Name: "slot"}) +} + +func (s *connSuite) TestPlugRef(c *C) { + plug := NewConnectedPlug(s.plug, nil) + c.Assert(plug, NotNil) + c.Assert(*plug.Ref(), DeepEquals, PlugRef{Snap: "consumer", Name: "plug"}) +} + +func (s *connSuite) TestStaticPlugAttrs(c *C) { + plug := NewConnectedPlug(s.plug, nil) + c.Assert(plug, NotNil) + + var val string + var intVal int + c.Assert(plug.StaticAttr("unknown", &val), ErrorMatches, `snap "consumer" does not have attribute "unknown" for interface "interface"`) + + attrs := plug.StaticAttrs() + c.Assert(attrs, DeepEquals, map[string]interface{}{ + "attr": "value", + }) + plug.StaticAttr("attr", &val) + c.Assert(val, Equals, "value") + + c.Assert(plug.StaticAttr("unknown", &val), ErrorMatches, `snap "consumer" does not have attribute "unknown" for interface "interface"`) + c.Check(plug.StaticAttr("attr", &intVal), ErrorMatches, `snap "consumer" has interface "interface" with invalid value type for "attr" attribute`) + c.Check(plug.StaticAttr("attr", val), ErrorMatches, `internal error: cannot get "attr" attribute of interface "interface" with non-pointer value`) +} + +func (s *connSuite) TestDynamicSlotAttrs(c *C) { + attrs := map[string]interface{}{ + "foo": "bar", + "number": int(100), + } + slot := NewConnectedSlot(s.slot, attrs) + c.Assert(slot, NotNil) + + var strVal string + var intVal int64 + var mapVal map[string]interface{} + + c.Assert(slot.Attr("foo", &strVal), IsNil) + c.Assert(strVal, Equals, "bar") + + c.Assert(slot.Attr("attr", &strVal), IsNil) + c.Assert(strVal, Equals, "value") + + c.Assert(slot.Attr("number", &intVal), IsNil) + c.Assert(intVal, Equals, int64(100)) + + err := slot.SetAttr("other", map[string]interface{}{"number-two": int(222)}) + c.Assert(err, IsNil) + c.Assert(slot.Attr("other", &mapVal), IsNil) + num := mapVal["number-two"] + c.Assert(num, Equals, int64(222)) + + c.Check(slot.Attr("unknown", &strVal), ErrorMatches, `snap "producer" does not have attribute "unknown" for interface "interface"`) + c.Check(slot.Attr("foo", &intVal), ErrorMatches, `snap "producer" has interface "interface" with invalid value type for "foo" attribute`) + c.Check(slot.Attr("number", intVal), ErrorMatches, `internal error: cannot get "number" attribute of interface "interface" with non-pointer value`) +} + +func (s *connSuite) TestDynamicPlugAttrs(c *C) { + attrs := map[string]interface{}{ + "foo": "bar", + "number": int(100), + } + plug := NewConnectedPlug(s.plug, attrs) + c.Assert(plug, NotNil) + + var strVal string + var intVal int64 + var mapVal map[string]interface{} + + c.Assert(plug.Attr("foo", &strVal), IsNil) + c.Assert(strVal, Equals, "bar") + + c.Assert(plug.Attr("attr", &strVal), IsNil) + c.Assert(strVal, Equals, "value") + + c.Assert(plug.Attr("number", &intVal), IsNil) + c.Assert(intVal, Equals, int64(100)) + + err := plug.SetAttr("other", map[string]interface{}{"number-two": int(222)}) + c.Assert(err, IsNil) + c.Assert(plug.Attr("other", &mapVal), IsNil) + num := mapVal["number-two"] + c.Assert(num, Equals, int64(222)) + + c.Check(plug.Attr("unknown", &strVal), ErrorMatches, `snap "consumer" does not have attribute "unknown" for interface "interface"`) + c.Check(plug.Attr("foo", &intVal), ErrorMatches, `snap "consumer" has interface "interface" with invalid value type for "foo" attribute`) + c.Check(plug.Attr("number", intVal), ErrorMatches, `internal error: cannot get "number" attribute of interface "interface" with non-pointer value`) +} + +func (s *connSuite) TestOverwriteStaticAttrError(c *C) { + attrs := map[string]interface{}{} + + plug := NewConnectedPlug(s.plug, attrs) + c.Assert(plug, NotNil) + slot := NewConnectedSlot(s.slot, attrs) + c.Assert(slot, NotNil) + + err := plug.SetAttr("attr", "overwrite") + c.Assert(err, NotNil) + c.Assert(err, ErrorMatches, `cannot change attribute "attr" as it was statically specified in the snap details`) + + err = slot.SetAttr("attr", "overwrite") + c.Assert(err, NotNil) + c.Assert(err, ErrorMatches, `cannot change attribute "attr" as it was statically specified in the snap details`) +} + +func (s *connSuite) TestCopyAttributes(c *C) { + orig := map[string]interface{}{ + "a": "A", + "b": true, + "c": int(100), + "d": []interface{}{"x", "y", true}, + "e": map[string]interface{}{"e1": "E1"}, + } + + cpy := CopyAttributes(orig) + c.Check(cpy, DeepEquals, orig) + + cpy["d"].([]interface{})[0] = 999 + c.Check(orig["d"].([]interface{})[0], Equals, "x") + cpy["e"].(map[string]interface{})["e1"] = "x" + c.Check(orig["e"].(map[string]interface{})["e1"], Equals, "E1") +} diff -Nru snapd-2.29.4.2+17.10/interfaces/core.go snapd-2.31.1+17.10/interfaces/core.go --- snapd-2.29.4.2+17.10/interfaces/core.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/core.go 2018-01-24 20:02:44.000000000 +0000 @@ -39,14 +39,14 @@ } // Sanitize plug with a given snapd interface. -func (plug *Plug) Sanitize(iface Interface) error { - if iface.Name() != plug.Interface { +func BeforePreparePlug(iface Interface, plugInfo *snap.PlugInfo) error { + if iface.Name() != plugInfo.Interface { return fmt.Errorf("cannot sanitize plug %q (interface %q) using interface %q", - plug.Ref(), plug.Interface, iface.Name()) + PlugRef{Snap: plugInfo.Snap.Name(), Name: plugInfo.Name}, plugInfo.Interface, iface.Name()) } var err error if iface, ok := iface.(PlugSanitizer); ok { - err = iface.SanitizePlug(plug) + err = iface.BeforePreparePlug(plugInfo) } return err } @@ -74,14 +74,14 @@ } // Sanitize slot with a given snapd interface. -func (slot *Slot) Sanitize(iface Interface) error { - if iface.Name() != slot.Interface { +func BeforePrepareSlot(iface Interface, slotInfo *snap.SlotInfo) error { + if iface.Name() != slotInfo.Interface { return fmt.Errorf("cannot sanitize slot %q (interface %q) using interface %q", - slot.Ref(), slot.Interface, iface.Name()) + SlotRef{Snap: slotInfo.Snap.Name(), Name: slotInfo.Name}, slotInfo.Interface, iface.Name()) } var err error if iface, ok := iface.(SlotSanitizer); ok { - err = iface.SanitizeSlot(slot) + err = iface.BeforePrepareSlot(slotInfo) } return err } @@ -99,8 +99,9 @@ // Interfaces holds information about a list of plugs, slots and their connections. type Interfaces struct { - Plugs []*Plug `json:"plugs"` - Slots []*Slot `json:"slots"` + Plugs []*snap.PlugInfo + Slots []*snap.SlotInfo + Connections []*ConnRef } // Info holds information about a given interface and its instances. @@ -118,13 +119,12 @@ SlotRef SlotRef } -type Connection struct { - plugInfo *snap.PlugInfo - slotInfo *snap.SlotInfo -} - -func (conn *Connection) Interface() string { - return conn.plugInfo.Interface +// NewConnRef creates a connection reference for given plug and slot +func NewConnRef(plug *snap.PlugInfo, slot *snap.SlotInfo) *ConnRef { + return &ConnRef{ + PlugRef: PlugRef{Snap: plug.Snap.Name(), Name: plug.Name}, + SlotRef: SlotRef{Snap: slot.Snap.Name(), Name: slot.Name}, + } } // ID returns a string identifying a given connection. @@ -167,12 +167,12 @@ // PlugSanitizer can be implemented by Interfaces that have reasons to sanitize their plugs. type PlugSanitizer interface { - SanitizePlug(plug *Plug) error + BeforePreparePlug(plug *snap.PlugInfo) error } // SlotSanitizer can be implemented by Interfaces that have reasons to sanitize their slots. type SlotSanitizer interface { - SanitizeSlot(slot *Slot) error + BeforePrepareSlot(slot *snap.SlotInfo) error } // StaticInfo describes various static-info of a given interface. @@ -209,13 +209,13 @@ // Specification describes interactions between backends and interfaces. type Specification interface { // AddPermanentSlot records side-effects of having a slot. - AddPermanentSlot(iface Interface, slot *Slot) error + AddPermanentSlot(iface Interface, slot *snap.SlotInfo) error // AddPermanentPlug records side-effects of having a plug. - AddPermanentPlug(iface Interface, plug *Plug) error + AddPermanentPlug(iface Interface, plug *snap.PlugInfo) error // AddConnectedSlot records side-effects of having a connected slot. - AddConnectedSlot(iface Interface, plug *Plug, plugAttrs map[string]interface{}, slot *Slot, slotAttrs map[string]interface{}) error + AddConnectedSlot(iface Interface, plug *ConnectedPlug, slot *ConnectedSlot) error // AddConnectedPlug records side-effects of having a connected plug. - AddConnectedPlug(iface Interface, plug *Plug, plugAttrs map[string]interface{}, slot *Slot, slotAttrs map[string]interface{}) error + AddConnectedPlug(iface Interface, plug *ConnectedPlug, slot *ConnectedSlot) error } // SecuritySystem is a name of a security system. diff -Nru snapd-2.29.4.2+17.10/interfaces/core_test.go snapd-2.31.1+17.10/interfaces/core_test.go --- snapd-2.29.4.2+17.10/interfaces/core_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/core_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -188,17 +188,17 @@ plug: interface: iface `, nil) - plug := &Plug{PlugInfo: info.Plugs["plug"]} - c.Assert(plug.Sanitize(&ifacetest.TestInterface{ + plug := info.Plugs["plug"] + c.Assert(BeforePreparePlug(&ifacetest.TestInterface{ InterfaceName: "iface", - }), IsNil) - c.Assert(plug.Sanitize(&ifacetest.TestInterface{ - InterfaceName: "iface", - SanitizePlugCallback: func(plug *Plug) error { return fmt.Errorf("broken") }, - }), ErrorMatches, "broken") - c.Assert(plug.Sanitize(&ifacetest.TestInterface{ + }, plug), IsNil) + c.Assert(BeforePreparePlug(&ifacetest.TestInterface{ + InterfaceName: "iface", + BeforePreparePlugCallback: func(plug *snap.PlugInfo) error { return fmt.Errorf("broken") }, + }, plug), ErrorMatches, "broken") + c.Assert(BeforePreparePlug(&ifacetest.TestInterface{ InterfaceName: "other", - }), ErrorMatches, `cannot sanitize plug "snap:plug" \(interface "iface"\) using interface "other"`) + }, plug), ErrorMatches, `cannot sanitize plug "snap:plug" \(interface "iface"\) using interface "other"`) } func (s *CoreSuite) TestSanitizeSlot(c *C) { @@ -208,15 +208,15 @@ slot: interface: iface `, nil) - slot := &Slot{SlotInfo: info.Slots["slot"]} - c.Assert(slot.Sanitize(&ifacetest.TestInterface{ + slot := info.Slots["slot"] + c.Assert(BeforePrepareSlot(&ifacetest.TestInterface{ InterfaceName: "iface", - }), IsNil) - c.Assert(slot.Sanitize(&ifacetest.TestInterface{ - InterfaceName: "iface", - SanitizeSlotCallback: func(slot *Slot) error { return fmt.Errorf("broken") }, - }), ErrorMatches, "broken") - c.Assert(slot.Sanitize(&ifacetest.TestInterface{ + }, slot), IsNil) + c.Assert(BeforePrepareSlot(&ifacetest.TestInterface{ + InterfaceName: "iface", + BeforePrepareSlotCallback: func(slot *snap.SlotInfo) error { return fmt.Errorf("broken") }, + }, slot), ErrorMatches, "broken") + c.Assert(BeforePrepareSlot(&ifacetest.TestInterface{ InterfaceName: "other", - }), ErrorMatches, `cannot sanitize slot "snap:slot" \(interface "iface"\) using interface "other"`) + }, slot), ErrorMatches, `cannot sanitize slot "snap:slot" \(interface "iface"\) using interface "other"`) } diff -Nru snapd-2.29.4.2+17.10/interfaces/dbus/backend.go snapd-2.31.1+17.10/interfaces/dbus/backend.go --- snapd-2.29.4.2+17.10/interfaces/dbus/backend.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/dbus/backend.go 2018-01-31 08:47:06.000000000 +0000 @@ -44,6 +44,11 @@ // Backend is responsible for maintaining DBus policy files. type Backend struct{} +// Initialize does nothing. +func (b *Backend) Initialize() error { + return nil +} + // Name returns the name of the backend. func (b *Backend) Name() interfaces.SecuritySystem { return "dbus" @@ -53,11 +58,20 @@ // `snap userd` instance on re-exec func setupDbusServiceForUserd(snapInfo *snap.Info) error { coreRoot := snapInfo.MountDir() - dst := "/usr/share/dbus-1/services/io.snapcraft.Launcher.service" - if osutil.FileExists(dst) { - return nil + + for _, srv := range []string{ + "io.snapcraft.Launcher.service", + "io.snapcraft.Settings.service", + } { + dst := filepath.Join("/usr/share/dbus-1/services/", srv) + src := filepath.Join(coreRoot, dst) + if !osutil.FilesAreEqual(src, dst) { + if err := osutil.CopyFile(src, dst, osutil.CopyFlagPreserveAll); err != nil { + return err + } + } } - return osutil.CopyFile(filepath.Join(coreRoot, dst), dst, osutil.CopyFlagPreserveAll) + return nil } // Setup creates dbus configuration files specific to a given snap. diff -Nru snapd-2.29.4.2+17.10/interfaces/dbus/backend_test.go snapd-2.31.1+17.10/interfaces/dbus/backend_test.go --- snapd-2.29.4.2+17.10/interfaces/dbus/backend_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/dbus/backend_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -30,6 +30,7 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/dbus" "github.com/snapcore/snapd/interfaces/ifacetest" + "github.com/snapcore/snapd/snap" ) type backendSuite struct { @@ -67,7 +68,7 @@ func (s *backendSuite) TestInstallingSnapWritesConfigFiles(c *C) { // NOTE: Hand out a permanent snippet so that .conf file is generated. - s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *interfaces.Slot) error { + s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("") return nil } @@ -83,11 +84,11 @@ func (s *backendSuite) TestInstallingSnapWithHookWritesConfigFiles(c *C) { // NOTE: Hand out a permanent snippet so that .conf file is generated. - s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *interfaces.Slot) error { + s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("") return nil } - s.Iface.DBusPermanentPlugCallback = func(spec *dbus.Specification, plug *interfaces.Plug) error { + s.Iface.DBusPermanentPlugCallback = func(spec *dbus.Specification, plug *snap.PlugInfo) error { spec.AddSnippet("") return nil } @@ -104,7 +105,7 @@ func (s *backendSuite) TestRemovingSnapRemovesConfigFiles(c *C) { // NOTE: Hand out a permanent snippet so that .conf file is generated. - s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *interfaces.Slot) error { + s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("") return nil } @@ -120,11 +121,11 @@ func (s *backendSuite) TestRemovingSnapWithHookRemovesConfigFiles(c *C) { // NOTE: Hand out a permanent snippet so that .conf file is generated. - s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *interfaces.Slot) error { + s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("") return nil } - s.Iface.DBusPermanentPlugCallback = func(spec *dbus.Specification, plug *interfaces.Plug) error { + s.Iface.DBusPermanentPlugCallback = func(spec *dbus.Specification, plug *snap.PlugInfo) error { spec.AddSnippet("") return nil } @@ -141,7 +142,7 @@ func (s *backendSuite) TestUpdatingSnapToOneWithMoreApps(c *C) { // NOTE: Hand out a permanent snippet so that .conf file is generated. - s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *interfaces.Slot) error { + s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("") return nil } @@ -158,11 +159,11 @@ func (s *backendSuite) TestUpdatingSnapToOneWithMoreHooks(c *C) { // NOTE: Hand out a permanent snippet so that .conf file is generated. - s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *interfaces.Slot) error { + s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("") return nil } - s.Iface.DBusPermanentPlugCallback = func(spec *dbus.Specification, plug *interfaces.Plug) error { + s.Iface.DBusPermanentPlugCallback = func(spec *dbus.Specification, plug *snap.PlugInfo) error { spec.AddSnippet("") return nil } @@ -180,7 +181,7 @@ func (s *backendSuite) TestUpdatingSnapToOneWithFewerApps(c *C) { // NOTE: Hand out a permanent snippet so that .conf file is generated. - s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *interfaces.Slot) error { + s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("") return nil } @@ -197,11 +198,11 @@ func (s *backendSuite) TestUpdatingSnapToOneWithFewerHooks(c *C) { // NOTE: Hand out a permanent snippet so that .conf file is generated. - s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *interfaces.Slot) error { + s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("") return nil } - s.Iface.DBusPermanentPlugCallback = func(spec *dbus.Specification, plug *interfaces.Plug) error { + s.Iface.DBusPermanentPlugCallback = func(spec *dbus.Specification, plug *snap.PlugInfo) error { spec.AddSnippet("") return nil } @@ -221,7 +222,7 @@ // NOTE: replace the real template with a shorter variant restore := dbus.MockXMLEnvelope([]byte("\n"), []byte("")) defer restore() - s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *interfaces.Slot) error { + s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("...") return nil } @@ -261,7 +262,7 @@ func (s *backendSuite) TestAppBoundIfaces(c *C) { // NOTE: Hand out a permanent snippet so that .conf file is generated. - s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *interfaces.Slot) error { + s.Iface.DBusPermanentSlotCallback = func(spec *dbus.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("") return nil } diff -Nru snapd-2.29.4.2+17.10/interfaces/dbus/spec.go snapd-2.31.1+17.10/interfaces/dbus/spec.go --- snapd-2.29.4.2+17.10/interfaces/dbus/spec.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/dbus/spec.go 2018-01-24 20:02:44.000000000 +0000 @@ -24,6 +24,7 @@ "sort" "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/snap" ) // Specification keeps all the dbus snippets. @@ -79,35 +80,35 @@ // Implementation of methods required by interfaces.Specification // AddConnectedPlug records dbus-specific side-effects of having a connected plug. -func (spec *Specification) AddConnectedPlug(iface interfaces.Interface, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (spec *Specification) AddConnectedPlug(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { type definer interface { - DBusConnectedPlug(spec *Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + DBusConnectedPlug(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } if iface, ok := iface.(definer); ok { spec.securityTags = plug.SecurityTags() defer func() { spec.securityTags = nil }() - return iface.DBusConnectedPlug(spec, plug, plugAttrs, slot, slotAttrs) + return iface.DBusConnectedPlug(spec, plug, slot) } return nil } // AddConnectedSlot records dbus-specific side-effects of having a connected slot. -func (spec *Specification) AddConnectedSlot(iface interfaces.Interface, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (spec *Specification) AddConnectedSlot(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { type definer interface { - DBusConnectedSlot(spec *Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + DBusConnectedSlot(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } if iface, ok := iface.(definer); ok { spec.securityTags = slot.SecurityTags() defer func() { spec.securityTags = nil }() - return iface.DBusConnectedSlot(spec, plug, plugAttrs, slot, slotAttrs) + return iface.DBusConnectedSlot(spec, plug, slot) } return nil } // AddPermanentPlug records dbus-specific side-effects of having a plug. -func (spec *Specification) AddPermanentPlug(iface interfaces.Interface, plug *interfaces.Plug) error { +func (spec *Specification) AddPermanentPlug(iface interfaces.Interface, plug *snap.PlugInfo) error { type definer interface { - DBusPermanentPlug(spec *Specification, plug *interfaces.Plug) error + DBusPermanentPlug(spec *Specification, plug *snap.PlugInfo) error } if iface, ok := iface.(definer); ok { spec.securityTags = plug.SecurityTags() @@ -118,9 +119,9 @@ } // AddPermanentSlot records dbus-specific side-effects of having a slot. -func (spec *Specification) AddPermanentSlot(iface interfaces.Interface, slot *interfaces.Slot) error { +func (spec *Specification) AddPermanentSlot(iface interfaces.Interface, slot *snap.SlotInfo) error { type definer interface { - DBusPermanentSlot(spec *Specification, slot *interfaces.Slot) error + DBusPermanentSlot(spec *Specification, slot *snap.SlotInfo) error } if iface, ok := iface.(definer); ok { spec.securityTags = slot.SecurityTags() diff -Nru snapd-2.29.4.2+17.10/interfaces/dbus/spec_test.go snapd-2.31.1+17.10/interfaces/dbus/spec_test.go --- snapd-2.29.4.2+17.10/interfaces/dbus/spec_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/dbus/spec_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -29,71 +29,71 @@ ) type specSuite struct { - iface *ifacetest.TestInterface - spec *dbus.Specification - plug *interfaces.Plug - slot *interfaces.Slot + iface *ifacetest.TestInterface + spec *dbus.Specification + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot } var _ = Suite(&specSuite{ iface: &ifacetest.TestInterface{ InterfaceName: "test", - DBusConnectedPlugCallback: func(spec *dbus.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { + DBusConnectedPlugCallback: func(spec *dbus.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { spec.AddSnippet("connected-plug") return nil }, - DBusConnectedSlotCallback: func(spec *dbus.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { + DBusConnectedSlotCallback: func(spec *dbus.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { spec.AddSnippet("connected-slot") return nil }, - DBusPermanentPlugCallback: func(spec *dbus.Specification, plug *interfaces.Plug) error { + DBusPermanentPlugCallback: func(spec *dbus.Specification, plug *snap.PlugInfo) error { spec.AddSnippet("permanent-plug") return nil }, - DBusPermanentSlotCallback: func(spec *dbus.Specification, slot *interfaces.Slot) error { + DBusPermanentSlotCallback: func(spec *dbus.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("permanent-slot") return nil }, }, - plug: &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{SuggestedName: "snap1"}, - Name: "name", - Interface: "test", - Apps: map[string]*snap.AppInfo{ - "app1": { - Snap: &snap.Info{ - SuggestedName: "snap1", - }, - Name: "app1"}}, - }, + plugInfo: &snap.PlugInfo{ + Snap: &snap.Info{SuggestedName: "snap1"}, + Name: "name", + Interface: "test", + Apps: map[string]*snap.AppInfo{ + "app1": { + Snap: &snap.Info{ + SuggestedName: "snap1", + }, + Name: "app1"}}, }, - slot: &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "snap2"}, - Name: "name", - Interface: "test", - Apps: map[string]*snap.AppInfo{ - "app2": { - Snap: &snap.Info{ - SuggestedName: "snap2", - }, - Name: "app2"}}, - }, + slotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "snap2"}, + Name: "name", + Interface: "test", + Apps: map[string]*snap.AppInfo{ + "app2": { + Snap: &snap.Info{ + SuggestedName: "snap2", + }, + Name: "app2"}}, }, }) func (s *specSuite) SetUpTest(c *C) { s.spec = &dbus.Specification{} + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) } // The spec.Specification can be used through the interfaces.Specification interface func (s *specSuite) TestSpecificationIface(c *C) { var r interfaces.Specification = s.spec - c.Assert(r.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(r.AddConnectedSlot(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(r.AddPermanentPlug(s.iface, s.plug), IsNil) - c.Assert(r.AddPermanentSlot(s.iface, s.slot), IsNil) + c.Assert(r.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(r.AddConnectedSlot(s.iface, s.plug, s.slot), IsNil) + c.Assert(r.AddPermanentPlug(s.iface, s.plugInfo), IsNil) + c.Assert(r.AddPermanentSlot(s.iface, s.slotInfo), IsNil) c.Assert(s.spec.Snippets(), DeepEquals, map[string][]string{ "snap.snap1.app1": {"connected-plug", "permanent-plug"}, "snap.snap2.app2": {"connected-slot", "permanent-slot"}, diff -Nru snapd-2.29.4.2+17.10/interfaces/export_test.go snapd-2.31.1+17.10/interfaces/export_test.go --- snapd-2.29.4.2+17.10/interfaces/export_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/export_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -19,6 +19,12 @@ package interfaces +type ByConnRef byConnRef + +func (c ByConnRef) Len() int { return byConnRef(c).Len() } +func (c ByConnRef) Swap(i, j int) { byConnRef(c).Swap(i, j) } +func (c ByConnRef) Less(i, j int) bool { return byConnRef(c).Less(i, j) } + type BySlotRef bySlotRef func (c BySlotRef) Len() int { return bySlotRef(c).Len() } @@ -66,3 +72,5 @@ func (c BySlotInfo) Len() int { return bySlotInfo(c).Len() } func (c BySlotInfo) Swap(i, j int) { bySlotInfo(c).Swap(i, j) } func (c BySlotInfo) Less(i, j int) bool { return bySlotInfo(c).Less(i, j) } + +var CopyAttributes = copyAttributes diff -Nru snapd-2.29.4.2+17.10/interfaces/ifacetest/backend.go snapd-2.31.1+17.10/interfaces/ifacetest/backend.go --- snapd-2.29.4.2+17.10/interfaces/ifacetest/backend.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/ifacetest/backend.go 2017-12-01 15:51:55.000000000 +0000 @@ -45,6 +45,11 @@ Options interfaces.ConfinementOptions } +// Initialize does nothing. +func (b *TestSecurityBackend) Initialize() error { + return nil +} + // Name returns the name of the security backend. func (b *TestSecurityBackend) Name() interfaces.SecuritySystem { return b.BackendName diff -Nru snapd-2.29.4.2+17.10/interfaces/ifacetest/backendtest.go snapd-2.31.1+17.10/interfaces/ifacetest/backendtest.go --- snapd-2.29.4.2+17.10/interfaces/ifacetest/backendtest.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/ifacetest/backendtest.go 2017-12-01 15:51:55.000000000 +0000 @@ -174,13 +174,11 @@ func (s *BackendSuite) addPlugsSlots(c *C, snapInfo *snap.Info) { for _, plugInfo := range snapInfo.Plugs { - plug := &interfaces.Plug{PlugInfo: plugInfo} - err := s.Repo.AddPlug(plug) + err := s.Repo.AddPlug(plugInfo) c.Assert(err, IsNil) } for _, slotInfo := range snapInfo.Slots { - slot := &interfaces.Slot{SlotInfo: slotInfo} - err := s.Repo.AddSlot(slot) + err := s.Repo.AddSlot(slotInfo) c.Assert(err, IsNil) } } diff -Nru snapd-2.29.4.2+17.10/interfaces/ifacetest/spec.go snapd-2.31.1+17.10/interfaces/ifacetest/spec.go --- snapd-2.29.4.2+17.10/interfaces/ifacetest/spec.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/ifacetest/spec.go 2018-01-24 20:02:44.000000000 +0000 @@ -21,6 +21,7 @@ import ( "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/snap" ) // Specification is a specification intended for testing. @@ -36,31 +37,31 @@ // Implementation of methods required by interfaces.Specification // AddConnectedPlug records test side-effects of having a connected plug. -func (spec *Specification) AddConnectedPlug(iface interfaces.Interface, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (spec *Specification) AddConnectedPlug(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { type definer interface { - TestConnectedPlug(spec *Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + TestConnectedPlug(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } if iface, ok := iface.(definer); ok { - return iface.TestConnectedPlug(spec, plug, plugAttrs, slot, slotAttrs) + return iface.TestConnectedPlug(spec, plug, slot) } return nil } // AddConnectedSlot records test side-effects of having a connected slot. -func (spec *Specification) AddConnectedSlot(iface interfaces.Interface, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (spec *Specification) AddConnectedSlot(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { type definer interface { - TestConnectedSlot(spec *Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + TestConnectedSlot(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } if iface, ok := iface.(definer); ok { - return iface.TestConnectedSlot(spec, plug, plugAttrs, slot, slotAttrs) + return iface.TestConnectedSlot(spec, plug, slot) } return nil } // AddPermanentPlug records test side-effects of having a plug. -func (spec *Specification) AddPermanentPlug(iface interfaces.Interface, plug *interfaces.Plug) error { +func (spec *Specification) AddPermanentPlug(iface interfaces.Interface, plug *snap.PlugInfo) error { type definer interface { - TestPermanentPlug(spec *Specification, plug *interfaces.Plug) error + TestPermanentPlug(spec *Specification, plug *snap.PlugInfo) error } if iface, ok := iface.(definer); ok { return iface.TestPermanentPlug(spec, plug) @@ -69,9 +70,9 @@ } // AddPermanentSlot records test side-effects of having a slot. -func (spec *Specification) AddPermanentSlot(iface interfaces.Interface, slot *interfaces.Slot) error { +func (spec *Specification) AddPermanentSlot(iface interfaces.Interface, slot *snap.SlotInfo) error { type definer interface { - TestPermanentSlot(spec *Specification, slot *interfaces.Slot) error + TestPermanentSlot(spec *Specification, slot *snap.SlotInfo) error } if iface, ok := iface.(definer); ok { return iface.TestPermanentSlot(spec, slot) diff -Nru snapd-2.29.4.2+17.10/interfaces/ifacetest/spec_test.go snapd-2.31.1+17.10/interfaces/ifacetest/spec_test.go --- snapd-2.29.4.2+17.10/interfaces/ifacetest/spec_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/ifacetest/spec_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -28,50 +28,50 @@ ) type SpecificationSuite struct { - iface *ifacetest.TestInterface - spec *ifacetest.Specification - plug *interfaces.Plug - slot *interfaces.Slot + iface *ifacetest.TestInterface + spec *ifacetest.Specification + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot } var _ = Suite(&SpecificationSuite{ iface: &ifacetest.TestInterface{ InterfaceName: "test", - TestConnectedPlugCallback: func(spec *ifacetest.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { + TestConnectedPlugCallback: func(spec *ifacetest.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { spec.AddSnippet("connected-plug") return nil }, - TestConnectedSlotCallback: func(spec *ifacetest.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { + TestConnectedSlotCallback: func(spec *ifacetest.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { spec.AddSnippet("connected-slot") return nil }, - TestPermanentPlugCallback: func(spec *ifacetest.Specification, plug *interfaces.Plug) error { + TestPermanentPlugCallback: func(spec *ifacetest.Specification, plug *snap.PlugInfo) error { spec.AddSnippet("permanent-plug") return nil }, - TestPermanentSlotCallback: func(spec *ifacetest.Specification, slot *interfaces.Slot) error { + TestPermanentSlotCallback: func(spec *ifacetest.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("permanent-slot") return nil }, }, - plug: &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{SuggestedName: "snap"}, - Name: "name", - Interface: "test", - }, + plugInfo: &snap.PlugInfo{ + Snap: &snap.Info{SuggestedName: "snap"}, + Name: "name", + Interface: "test", }, - slot: &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "snap"}, - Name: "name", - Interface: "test", - }, + slotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "snap"}, + Name: "name", + Interface: "test", }, }) func (s *SpecificationSuite) SetUpTest(c *C) { s.spec = &ifacetest.Specification{} + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) } // AddSnippet is not broken @@ -84,10 +84,10 @@ // The Specification can be used through the interfaces.Specification interface func (s *SpecificationSuite) SpecificationIface(c *C) { var r interfaces.Specification = s.spec - c.Assert(r.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(r.AddConnectedSlot(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(r.AddPermanentPlug(s.iface, s.plug), IsNil) - c.Assert(r.AddPermanentSlot(s.iface, s.slot), IsNil) + c.Assert(r.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(r.AddConnectedSlot(s.iface, s.plug, s.slot), IsNil) + c.Assert(r.AddPermanentPlug(s.iface, s.plugInfo), IsNil) + c.Assert(r.AddPermanentSlot(s.iface, s.slotInfo), IsNil) c.Assert(s.spec.Snippets, DeepEquals, []string{ "connected-plug", "connected-slot", "permanent-plug", "permanent-slot"}) } diff -Nru snapd-2.29.4.2+17.10/interfaces/ifacetest/testiface.go snapd-2.31.1+17.10/interfaces/ifacetest/testiface.go --- snapd-2.29.4.2+17.10/interfaces/ifacetest/testiface.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/ifacetest/testiface.go 2018-01-24 20:02:44.000000000 +0000 @@ -28,6 +28,7 @@ "github.com/snapcore/snapd/interfaces/seccomp" "github.com/snapcore/snapd/interfaces/systemd" "github.com/snapcore/snapd/interfaces/udev" + "github.com/snapcore/snapd/snap" ) // TestInterface is a interface for various kind of tests. @@ -38,69 +39,69 @@ InterfaceStaticInfo interfaces.StaticInfo // AutoConnectCallback is the callback invoked inside AutoConnect AutoConnectCallback func(*interfaces.Plug, *interfaces.Slot) bool - // SanitizePlugCallback is the callback invoked inside SanitizePlug() - SanitizePlugCallback func(plug *interfaces.Plug) error - // SanitizeSlotCallback is the callback invoked inside SanitizeSlot() - SanitizeSlotCallback func(slot *interfaces.Slot) error + // BeforePreparePlugCallback is the callback invoked inside BeforePreparePlug() + BeforePreparePlugCallback func(plug *snap.PlugInfo) error + // BeforePrepareSlotCallback is the callback invoked inside BeforePrepareSlot() + BeforePrepareSlotCallback func(slot *snap.SlotInfo) error ValidatePlugCallback func(plug *interfaces.Plug, attrs map[string]interface{}) error ValidateSlotCallback func(slot *interfaces.Slot, attrs map[string]interface{}) error // Support for interacting with the test backend. - TestConnectedPlugCallback func(spec *Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error - TestConnectedSlotCallback func(spec *Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error - TestPermanentPlugCallback func(spec *Specification, plug *interfaces.Plug) error - TestPermanentSlotCallback func(spec *Specification, slot *interfaces.Slot) error + TestConnectedPlugCallback func(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error + TestConnectedSlotCallback func(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error + TestPermanentPlugCallback func(spec *Specification, plug *snap.PlugInfo) error + TestPermanentSlotCallback func(spec *Specification, slot *snap.SlotInfo) error // Support for interacting with the mount backend. - MountConnectedPlugCallback func(spec *mount.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error - MountConnectedSlotCallback func(spec *mount.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error - MountPermanentPlugCallback func(spec *mount.Specification, plug *interfaces.Plug) error - MountPermanentSlotCallback func(spec *mount.Specification, slot *interfaces.Slot) error + MountConnectedPlugCallback func(spec *mount.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error + MountConnectedSlotCallback func(spec *mount.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error + MountPermanentPlugCallback func(spec *mount.Specification, plug *snap.PlugInfo) error + MountPermanentSlotCallback func(spec *mount.Specification, slot *snap.SlotInfo) error // Support for interacting with the udev backend. - UDevConnectedPlugCallback func(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error - UDevConnectedSlotCallback func(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error - UDevPermanentPlugCallback func(spec *udev.Specification, plug *interfaces.Plug) error - UDevPermanentSlotCallback func(spec *udev.Specification, slot *interfaces.Slot) error + UDevConnectedPlugCallback func(spec *udev.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error + UDevConnectedSlotCallback func(spec *udev.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error + UDevPermanentPlugCallback func(spec *udev.Specification, plug *snap.PlugInfo) error + UDevPermanentSlotCallback func(spec *udev.Specification, slot *snap.SlotInfo) error // Support for interacting with the apparmor backend. - AppArmorConnectedPlugCallback func(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error - AppArmorConnectedSlotCallback func(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error - AppArmorPermanentPlugCallback func(spec *apparmor.Specification, plug *interfaces.Plug) error - AppArmorPermanentSlotCallback func(spec *apparmor.Specification, slot *interfaces.Slot) error + AppArmorConnectedPlugCallback func(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error + AppArmorConnectedSlotCallback func(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error + AppArmorPermanentPlugCallback func(spec *apparmor.Specification, plug *snap.PlugInfo) error + AppArmorPermanentSlotCallback func(spec *apparmor.Specification, slot *snap.SlotInfo) error // Support for interacting with the kmod backend. - KModConnectedPlugCallback func(spec *kmod.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error - KModConnectedSlotCallback func(spec *kmod.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error - KModPermanentPlugCallback func(spec *kmod.Specification, plug *interfaces.Plug) error - KModPermanentSlotCallback func(spec *kmod.Specification, slot *interfaces.Slot) error + KModConnectedPlugCallback func(spec *kmod.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error + KModConnectedSlotCallback func(spec *kmod.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error + KModPermanentPlugCallback func(spec *kmod.Specification, plug *snap.PlugInfo) error + KModPermanentSlotCallback func(spec *kmod.Specification, slot *snap.SlotInfo) error // Support for interacting with the seccomp backend. - SecCompConnectedPlugCallback func(spec *seccomp.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error - SecCompConnectedSlotCallback func(spec *seccomp.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error - SecCompPermanentPlugCallback func(spec *seccomp.Specification, plug *interfaces.Plug) error - SecCompPermanentSlotCallback func(spec *seccomp.Specification, slot *interfaces.Slot) error + SecCompConnectedPlugCallback func(spec *seccomp.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error + SecCompConnectedSlotCallback func(spec *seccomp.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error + SecCompPermanentPlugCallback func(spec *seccomp.Specification, plug *snap.PlugInfo) error + SecCompPermanentSlotCallback func(spec *seccomp.Specification, slot *snap.SlotInfo) error // Support for interacting with the dbus backend. - DBusConnectedPlugCallback func(spec *dbus.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error - DBusConnectedSlotCallback func(spec *dbus.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error - DBusPermanentPlugCallback func(spec *dbus.Specification, plug *interfaces.Plug) error - DBusPermanentSlotCallback func(spec *dbus.Specification, slot *interfaces.Slot) error + DBusConnectedPlugCallback func(spec *dbus.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error + DBusConnectedSlotCallback func(spec *dbus.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error + DBusPermanentPlugCallback func(spec *dbus.Specification, plug *snap.PlugInfo) error + DBusPermanentSlotCallback func(spec *dbus.Specification, slot *snap.SlotInfo) error // Support for interacting with the systemd backend. - SystemdConnectedPlugCallback func(spec *systemd.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error - SystemdConnectedSlotCallback func(spec *systemd.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error - SystemdPermanentPlugCallback func(spec *systemd.Specification, plug *interfaces.Plug) error - SystemdPermanentSlotCallback func(spec *systemd.Specification, slot *interfaces.Slot) error + SystemdConnectedPlugCallback func(spec *systemd.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error + SystemdConnectedSlotCallback func(spec *systemd.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error + SystemdPermanentPlugCallback func(spec *systemd.Specification, plug *snap.PlugInfo) error + SystemdPermanentSlotCallback func(spec *systemd.Specification, slot *snap.SlotInfo) error } // String() returns the same value as Name(). @@ -117,18 +118,18 @@ return t.InterfaceStaticInfo } -// SanitizePlug checks and possibly modifies a plug. -func (t *TestInterface) SanitizePlug(plug *interfaces.Plug) error { - if t.SanitizePlugCallback != nil { - return t.SanitizePlugCallback(plug) +// BeforePreparePlug checks and possibly modifies a plug. +func (t *TestInterface) BeforePreparePlug(plug *snap.PlugInfo) error { + if t.BeforePreparePlugCallback != nil { + return t.BeforePreparePlugCallback(plug) } return nil } -// SanitizeSlot checks and possibly modifies a slot. -func (t *TestInterface) SanitizeSlot(slot *interfaces.Slot) error { - if t.SanitizeSlotCallback != nil { - return t.SanitizeSlotCallback(slot) +// BeforePrepareSlot checks and possibly modifies a slot. +func (t *TestInterface) BeforePrepareSlot(slot *snap.SlotInfo) error { + if t.BeforePrepareSlotCallback != nil { + return t.BeforePrepareSlotCallback(slot) } return nil } @@ -159,28 +160,28 @@ // Support for interacting with the test backend. -func (t *TestInterface) TestConnectedPlug(spec *Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (t *TestInterface) TestConnectedPlug(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { if t.TestConnectedPlugCallback != nil { - return t.TestConnectedPlugCallback(spec, plug, plugAttrs, slot, slotAttrs) + return t.TestConnectedPlugCallback(spec, plug, slot) } return nil } -func (t *TestInterface) TestConnectedSlot(spec *Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (t *TestInterface) TestConnectedSlot(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { if t.TestConnectedSlotCallback != nil { - return t.TestConnectedSlotCallback(spec, plug, plugAttrs, slot, slotAttrs) + return t.TestConnectedSlotCallback(spec, plug, slot) } return nil } -func (t *TestInterface) TestPermanentPlug(spec *Specification, plug *interfaces.Plug) error { +func (t *TestInterface) TestPermanentPlug(spec *Specification, plug *snap.PlugInfo) error { if t.TestPermanentPlugCallback != nil { return t.TestPermanentPlugCallback(spec, plug) } return nil } -func (t *TestInterface) TestPermanentSlot(spec *Specification, slot *interfaces.Slot) error { +func (t *TestInterface) TestPermanentSlot(spec *Specification, slot *snap.SlotInfo) error { if t.TestPermanentSlotCallback != nil { return t.TestPermanentSlotCallback(spec, slot) } @@ -189,28 +190,28 @@ // Support for interacting with the mount backend. -func (t *TestInterface) MountConnectedPlug(spec *mount.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (t *TestInterface) MountConnectedPlug(spec *mount.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { if t.MountConnectedPlugCallback != nil { - return t.MountConnectedPlugCallback(spec, plug, plugAttrs, slot, slotAttrs) + return t.MountConnectedPlugCallback(spec, plug, slot) } return nil } -func (t *TestInterface) MountConnectedSlot(spec *mount.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (t *TestInterface) MountConnectedSlot(spec *mount.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { if t.MountConnectedSlotCallback != nil { - return t.MountConnectedSlotCallback(spec, plug, plugAttrs, slot, slotAttrs) + return t.MountConnectedSlotCallback(spec, plug, slot) } return nil } -func (t *TestInterface) MountPermanentPlug(spec *mount.Specification, plug *interfaces.Plug) error { +func (t *TestInterface) MountPermanentPlug(spec *mount.Specification, plug *snap.PlugInfo) error { if t.MountPermanentPlugCallback != nil { return t.MountPermanentPlugCallback(spec, plug) } return nil } -func (t *TestInterface) MountPermanentSlot(spec *mount.Specification, slot *interfaces.Slot) error { +func (t *TestInterface) MountPermanentSlot(spec *mount.Specification, slot *snap.SlotInfo) error { if t.MountPermanentSlotCallback != nil { return t.MountPermanentSlotCallback(spec, slot) } @@ -219,59 +220,59 @@ // Support for interacting with the udev backend. -func (t *TestInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (t *TestInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { if t.UDevConnectedPlugCallback != nil { - return t.UDevConnectedPlugCallback(spec, plug, plugAttrs, slot, slotAttrs) + return t.UDevConnectedPlugCallback(spec, plug, slot) } return nil } -func (t *TestInterface) UDevPermanentPlug(spec *udev.Specification, plug *interfaces.Plug) error { +func (t *TestInterface) UDevPermanentPlug(spec *udev.Specification, plug *snap.PlugInfo) error { if t.UDevPermanentPlugCallback != nil { return t.UDevPermanentPlugCallback(spec, plug) } return nil } -func (t *TestInterface) UDevPermanentSlot(spec *udev.Specification, slot *interfaces.Slot) error { +func (t *TestInterface) UDevPermanentSlot(spec *udev.Specification, slot *snap.SlotInfo) error { if t.UDevPermanentSlotCallback != nil { return t.UDevPermanentSlotCallback(spec, slot) } return nil } -func (t *TestInterface) UDevConnectedSlot(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (t *TestInterface) UDevConnectedSlot(spec *udev.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { if t.UDevConnectedSlotCallback != nil { - return t.UDevConnectedSlotCallback(spec, plug, plugAttrs, slot, slotAttrs) + return t.UDevConnectedSlotCallback(spec, plug, slot) } return nil } // Support for interacting with the apparmor backend. -func (t *TestInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (t *TestInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { if t.AppArmorConnectedPlugCallback != nil { - return t.AppArmorConnectedPlugCallback(spec, plug, plugAttrs, slot, slotAttrs) + return t.AppArmorConnectedPlugCallback(spec, plug, slot) } return nil } -func (t *TestInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { +func (t *TestInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error { if t.AppArmorPermanentSlotCallback != nil { return t.AppArmorPermanentSlotCallback(spec, slot) } return nil } -func (t *TestInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (t *TestInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { if t.AppArmorConnectedSlotCallback != nil { - return t.AppArmorConnectedSlotCallback(spec, plug, plugAttrs, slot, slotAttrs) + return t.AppArmorConnectedSlotCallback(spec, plug, slot) } return nil } -func (t *TestInterface) AppArmorPermanentPlug(spec *apparmor.Specification, plug *interfaces.Plug) error { +func (t *TestInterface) AppArmorPermanentPlug(spec *apparmor.Specification, plug *snap.PlugInfo) error { if t.AppArmorPermanentPlugCallback != nil { return t.AppArmorPermanentPlugCallback(spec, plug) } @@ -280,28 +281,28 @@ // Support for interacting with the seccomp backend. -func (t *TestInterface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (t *TestInterface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { if t.SecCompConnectedPlugCallback != nil { - return t.SecCompConnectedPlugCallback(spec, plug, plugAttrs, slot, slotAttrs) + return t.SecCompConnectedPlugCallback(spec, plug, slot) } return nil } -func (t *TestInterface) SecCompConnectedSlot(spec *seccomp.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (t *TestInterface) SecCompConnectedSlot(spec *seccomp.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { if t.SecCompConnectedSlotCallback != nil { - return t.SecCompConnectedSlotCallback(spec, plug, plugAttrs, slot, slotAttrs) + return t.SecCompConnectedSlotCallback(spec, plug, slot) } return nil } -func (t *TestInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *interfaces.Slot) error { +func (t *TestInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *snap.SlotInfo) error { if t.SecCompPermanentSlotCallback != nil { return t.SecCompPermanentSlotCallback(spec, slot) } return nil } -func (t *TestInterface) SecCompPermanentPlug(spec *seccomp.Specification, plug *interfaces.Plug) error { +func (t *TestInterface) SecCompPermanentPlug(spec *seccomp.Specification, plug *snap.PlugInfo) error { if t.SecCompPermanentPlugCallback != nil { return t.SecCompPermanentPlugCallback(spec, plug) } @@ -310,28 +311,28 @@ // Support for interacting with the kmod backend. -func (t *TestInterface) KModConnectedPlug(spec *kmod.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (t *TestInterface) KModConnectedPlug(spec *kmod.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { if t.KModConnectedPlugCallback != nil { - return t.KModConnectedPlugCallback(spec, plug, plugAttrs, slot, slotAttrs) + return t.KModConnectedPlugCallback(spec, plug, slot) } return nil } -func (t *TestInterface) KModConnectedSlot(spec *kmod.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (t *TestInterface) KModConnectedSlot(spec *kmod.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { if t.KModConnectedSlotCallback != nil { - return t.KModConnectedSlotCallback(spec, plug, plugAttrs, slot, slotAttrs) + return t.KModConnectedSlotCallback(spec, plug, slot) } return nil } -func (t *TestInterface) KModPermanentPlug(spec *kmod.Specification, plug *interfaces.Plug) error { +func (t *TestInterface) KModPermanentPlug(spec *kmod.Specification, plug *snap.PlugInfo) error { if t.KModPermanentPlugCallback != nil { return t.KModPermanentPlugCallback(spec, plug) } return nil } -func (t *TestInterface) KModPermanentSlot(spec *kmod.Specification, slot *interfaces.Slot) error { +func (t *TestInterface) KModPermanentSlot(spec *kmod.Specification, slot *snap.SlotInfo) error { if t.KModPermanentSlotCallback != nil { return t.KModPermanentSlotCallback(spec, slot) } @@ -340,28 +341,28 @@ // Support for interacting with the dbus backend. -func (t *TestInterface) DBusConnectedPlug(spec *dbus.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (t *TestInterface) DBusConnectedPlug(spec *dbus.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { if t.DBusConnectedPlugCallback != nil { - return t.DBusConnectedPlugCallback(spec, plug, plugAttrs, slot, slotAttrs) + return t.DBusConnectedPlugCallback(spec, plug, slot) } return nil } -func (t *TestInterface) DBusConnectedSlot(spec *dbus.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (t *TestInterface) DBusConnectedSlot(spec *dbus.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { if t.DBusConnectedSlotCallback != nil { - return t.DBusConnectedSlotCallback(spec, plug, plugAttrs, slot, slotAttrs) + return t.DBusConnectedSlotCallback(spec, plug, slot) } return nil } -func (t *TestInterface) DBusPermanentSlot(spec *dbus.Specification, slot *interfaces.Slot) error { +func (t *TestInterface) DBusPermanentSlot(spec *dbus.Specification, slot *snap.SlotInfo) error { if t.DBusPermanentSlotCallback != nil { return t.DBusPermanentSlotCallback(spec, slot) } return nil } -func (t *TestInterface) DBusPermanentPlug(spec *dbus.Specification, plug *interfaces.Plug) error { +func (t *TestInterface) DBusPermanentPlug(spec *dbus.Specification, plug *snap.PlugInfo) error { if t.DBusPermanentPlugCallback != nil { return t.DBusPermanentPlugCallback(spec, plug) } @@ -370,28 +371,28 @@ // Support for interacting with the systemd backend. -func (t *TestInterface) SystemdConnectedPlug(spec *systemd.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (t *TestInterface) SystemdConnectedPlug(spec *systemd.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { if t.SystemdConnectedPlugCallback != nil { - return t.SystemdConnectedPlugCallback(spec, plug, plugAttrs, slot, slotAttrs) + return t.SystemdConnectedPlugCallback(spec, plug, slot) } return nil } -func (t *TestInterface) SystemdConnectedSlot(spec *systemd.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (t *TestInterface) SystemdConnectedSlot(spec *systemd.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { if t.SystemdConnectedSlotCallback != nil { - return t.SystemdConnectedSlotCallback(spec, plug, plugAttrs, slot, slotAttrs) + return t.SystemdConnectedSlotCallback(spec, plug, slot) } return nil } -func (t *TestInterface) SystemdPermanentSlot(spec *systemd.Specification, slot *interfaces.Slot) error { +func (t *TestInterface) SystemdPermanentSlot(spec *systemd.Specification, slot *snap.SlotInfo) error { if t.SystemdPermanentSlotCallback != nil { return t.SystemdPermanentSlotCallback(spec, slot) } return nil } -func (t *TestInterface) SystemdPermanentPlug(spec *systemd.Specification, plug *interfaces.Plug) error { +func (t *TestInterface) SystemdPermanentPlug(spec *systemd.Specification, plug *snap.PlugInfo) error { if t.SystemdPermanentPlugCallback != nil { return t.SystemdPermanentPlugCallback(spec, plug) } diff -Nru snapd-2.29.4.2+17.10/interfaces/ifacetest/testiface_test.go snapd-2.31.1+17.10/interfaces/ifacetest/testiface_test.go --- snapd-2.29.4.2+17.10/interfaces/ifacetest/testiface_test.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/ifacetest/testiface_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -33,9 +33,11 @@ ) type TestInterfaceSuite struct { - iface interfaces.Interface - plug *interfaces.Plug - slot *interfaces.Slot + iface interfaces.Interface + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot } var _ = Suite(&TestInterfaceSuite{ @@ -45,25 +47,23 @@ Summary: "summary", }, }, - plug: &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{SuggestedName: "snap"}, - Name: "name", - Interface: "test", - }, + plugInfo: &snap.PlugInfo{ + Snap: &snap.Info{SuggestedName: "snap"}, + Name: "name", + Interface: "test", }, - slot: &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "snap"}, - Name: "name", - Interface: "test", - }, + slotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "snap"}, + Name: "name", + Interface: "test", }, }) // TestInterface has a working Name() function func (s *TestInterfaceSuite) TestName(c *C) { c.Assert(s.iface.Name(), Equals, "test") + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) } func (s *TestInterfaceSuite) TestStaticInfo(c *C) { @@ -73,7 +73,7 @@ } // TestInterface has provisions to customize validation -func (s *TestInterfaceSuite) TestValidatePlugError(c *C) { +/*func (s *TestInterfaceSuite) TestValidatePlugError(c *C) { iface := &ifacetest.TestInterface{ InterfaceName: "test", ValidatePlugCallback: func(plug *interfaces.Plug, attrs map[string]interface{}) error { @@ -93,38 +93,38 @@ } err := iface.ValidateSlot(s.slot, nil) c.Assert(err, ErrorMatches, "validate slot failed") -} +}*/ // TestInterface doesn't do any sanitization by default func (s *TestInterfaceSuite) TestSanitizePlugOK(c *C) { - c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } // TestInterface has provisions to customize sanitization func (s *TestInterfaceSuite) TestSanitizePlugError(c *C) { iface := &ifacetest.TestInterface{ InterfaceName: "test", - SanitizePlugCallback: func(plug *interfaces.Plug) error { + BeforePreparePlugCallback: func(plug *snap.PlugInfo) error { return fmt.Errorf("sanitize plug failed") }, } - c.Assert(s.plug.Sanitize(iface), ErrorMatches, "sanitize plug failed") + c.Assert(interfaces.BeforePreparePlug(iface, s.plugInfo), ErrorMatches, "sanitize plug failed") } // TestInterface doesn't do any sanitization by default func (s *TestInterfaceSuite) TestSanitizeSlotOK(c *C) { - c.Assert(s.slot.Sanitize(s.iface), IsNil) + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) } // TestInterface has provisions to customize sanitization func (s *TestInterfaceSuite) TestSanitizeSlotError(c *C) { iface := &ifacetest.TestInterface{ InterfaceName: "test", - SanitizeSlotCallback: func(slot *interfaces.Slot) error { + BeforePrepareSlotCallback: func(slot *snap.SlotInfo) error { return fmt.Errorf("sanitize slot failed") }, } - c.Assert(s.slot.Sanitize(iface), ErrorMatches, "sanitize slot failed") + c.Assert(interfaces.BeforePrepareSlot(iface, s.slotInfo), ErrorMatches, "sanitize slot failed") } // TestInterface hands out empty plug security snippets @@ -132,15 +132,15 @@ iface := s.iface.(*ifacetest.TestInterface) apparmorSpec := &apparmor.Specification{} - c.Assert(iface.AppArmorConnectedPlug(apparmorSpec, s.plug, nil, s.slot, nil), IsNil) + c.Assert(iface.AppArmorConnectedPlug(apparmorSpec, s.plug, s.slot), IsNil) c.Assert(apparmorSpec.Snippets(), HasLen, 0) seccompSpec := &seccomp.Specification{} - c.Assert(iface.SecCompConnectedPlug(seccompSpec, s.plug, nil, s.slot, nil), IsNil) + c.Assert(iface.SecCompConnectedPlug(seccompSpec, s.plug, s.slot), IsNil) c.Assert(seccompSpec.Snippets(), HasLen, 0) dbusSpec := &dbus.Specification{} - c.Assert(iface.DBusConnectedPlug(dbusSpec, s.plug, nil, s.slot, nil), IsNil) + c.Assert(iface.DBusConnectedPlug(dbusSpec, s.plug, s.slot), IsNil) c.Assert(dbusSpec.Snippets(), HasLen, 0) } @@ -149,15 +149,15 @@ iface := s.iface.(*ifacetest.TestInterface) apparmorSpec := &apparmor.Specification{} - c.Assert(iface.AppArmorConnectedSlot(apparmorSpec, s.plug, nil, s.slot, nil), IsNil) + c.Assert(iface.AppArmorConnectedSlot(apparmorSpec, s.plug, s.slot), IsNil) c.Assert(apparmorSpec.Snippets(), HasLen, 0) seccompSpec := &seccomp.Specification{} - c.Assert(iface.SecCompConnectedSlot(seccompSpec, s.plug, nil, s.slot, nil), IsNil) + c.Assert(iface.SecCompConnectedSlot(seccompSpec, s.plug, s.slot), IsNil) c.Assert(seccompSpec.Snippets(), HasLen, 0) dbusSpec := &dbus.Specification{} - c.Assert(iface.DBusConnectedSlot(dbusSpec, s.plug, nil, s.slot, nil), IsNil) + c.Assert(iface.DBusConnectedSlot(dbusSpec, s.plug, s.slot), IsNil) c.Assert(dbusSpec.Snippets(), HasLen, 0) } diff -Nru snapd-2.29.4.2+17.10/interfaces/kmod/backend.go snapd-2.31.1+17.10/interfaces/kmod/backend.go --- snapd-2.29.4.2+17.10/interfaces/kmod/backend.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/kmod/backend.go 2017-12-01 15:51:55.000000000 +0000 @@ -51,6 +51,11 @@ // Backend is responsible for maintaining kernel modules type Backend struct{} +// Initialize does nothing. +func (b *Backend) Initialize() error { + return nil +} + // Name returns the name of the backend. func (b *Backend) Name() interfaces.SecuritySystem { return "kmod" @@ -83,7 +88,7 @@ } if len(changed) > 0 { - return loadModules(modules) + loadModules(modules) } return nil } diff -Nru snapd-2.29.4.2+17.10/interfaces/kmod/backend_test.go snapd-2.31.1+17.10/interfaces/kmod/backend_test.go --- snapd-2.29.4.2+17.10/interfaces/kmod/backend_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/kmod/backend_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -32,6 +32,7 @@ "github.com/snapcore/snapd/interfaces/ifacetest" "github.com/snapcore/snapd/interfaces/kmod" "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/snap" ) func Test(t *testing.T) { @@ -70,7 +71,7 @@ func (s *backendSuite) TestInstallingSnapCreatesModulesConf(c *C) { // NOTE: Hand out a permanent snippet so that .conf file is generated. - s.Iface.KModPermanentSlotCallback = func(spec *kmod.Specification, slot *interfaces.Slot) error { + s.Iface.KModPermanentSlotCallback = func(spec *kmod.Specification, slot *snap.SlotInfo) error { spec.AddModule("module1") spec.AddModule("module2") return nil @@ -98,7 +99,7 @@ func (s *backendSuite) TestRemovingSnapRemovesModulesConf(c *C) { // NOTE: Hand out a permanent snippet so that .conf file is generated. - s.Iface.KModPermanentSlotCallback = func(spec *kmod.Specification, slot *interfaces.Slot) error { + s.Iface.KModPermanentSlotCallback = func(spec *kmod.Specification, slot *snap.SlotInfo) error { spec.AddModule("module1") spec.AddModule("module2") return nil @@ -117,7 +118,7 @@ func (s *backendSuite) TestSecurityIsStable(c *C) { // NOTE: Hand out a permanent snippet so that .conf file is generated. - s.Iface.KModPermanentSlotCallback = func(spec *kmod.Specification, slot *interfaces.Slot) error { + s.Iface.KModPermanentSlotCallback = func(spec *kmod.Specification, slot *snap.SlotInfo) error { spec.AddModule("module1") spec.AddModule("module2") return nil diff -Nru snapd-2.29.4.2+17.10/interfaces/kmod/kmod.go snapd-2.31.1+17.10/interfaces/kmod/kmod.go --- snapd-2.29.4.2+17.10/interfaces/kmod/kmod.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/kmod/kmod.go 2017-12-01 15:51:55.000000000 +0000 @@ -20,26 +20,17 @@ package kmod import ( - "fmt" "os/exec" - - "github.com/snapcore/snapd/osutil" ) -func LoadModule(module string) error { - if output, err := exec.Command("modprobe", "--syslog", module).CombinedOutput(); err != nil { - return fmt.Errorf("cannot load module %s: %s", module, osutil.OutputErr(output, err)) - } - return nil -} - // loadModules loads given list of modules via modprobe. -// Any error from modprobe interrupts loading of subsequent modules and returns the error. -func loadModules(modules []string) error { +// Since different kernels may not have the requested module, we treat any +// error from modprobe as non-fatal and subsequent module loads are attempted +// (otherwise failure to load a module means failure to connect the interface +// and the other security backends) +func loadModules(modules []string) { for _, mod := range modules { - if err := LoadModule(mod); err != nil { - return err - } + // ignore errors which are logged by loadModule() via syslog + _ = exec.Command("modprobe", "--syslog", mod).Run() } - return nil } diff -Nru snapd-2.29.4.2+17.10/interfaces/kmod/kmod_test.go snapd-2.31.1+17.10/interfaces/kmod/kmod_test.go --- snapd-2.29.4.2+17.10/interfaces/kmod/kmod_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/kmod/kmod_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -45,11 +45,10 @@ cmd := testutil.MockCommand(c, "modprobe", "") defer cmd.Restore() - err := kmod.LoadModules([]string{ + kmod.LoadModules([]string{ "module1", "module2", }) - c.Assert(err, IsNil) c.Assert(cmd.Calls(), DeepEquals, [][]string{ {"modprobe", "--syslog", "module1"}, {"modprobe", "--syslog", "module2"}, diff -Nru snapd-2.29.4.2+17.10/interfaces/kmod/spec.go snapd-2.31.1+17.10/interfaces/kmod/spec.go --- snapd-2.29.4.2+17.10/interfaces/kmod/spec.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/kmod/spec.go 2018-01-24 20:02:44.000000000 +0000 @@ -23,6 +23,7 @@ "strings" "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/snap" ) // Specification assists in collecting kernel modules associated with an interface. @@ -59,31 +60,31 @@ // Implementation of methods required by interfaces.Specification // AddConnectedPlug records kmod-specific side-effects of having a connected plug. -func (spec *Specification) AddConnectedPlug(iface interfaces.Interface, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (spec *Specification) AddConnectedPlug(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { type definer interface { - KModConnectedPlug(spec *Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + KModConnectedPlug(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } if iface, ok := iface.(definer); ok { - return iface.KModConnectedPlug(spec, plug, plugAttrs, slot, slotAttrs) + return iface.KModConnectedPlug(spec, plug, slot) } return nil } // AddConnectedSlot records mount-specific side-effects of having a connected slot. -func (spec *Specification) AddConnectedSlot(iface interfaces.Interface, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (spec *Specification) AddConnectedSlot(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { type definer interface { - KModConnectedSlot(spec *Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + KModConnectedSlot(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } if iface, ok := iface.(definer); ok { - return iface.KModConnectedSlot(spec, plug, plugAttrs, slot, slotAttrs) + return iface.KModConnectedSlot(spec, plug, slot) } return nil } // AddPermanentPlug records mount-specific side-effects of having a plug. -func (spec *Specification) AddPermanentPlug(iface interfaces.Interface, plug *interfaces.Plug) error { +func (spec *Specification) AddPermanentPlug(iface interfaces.Interface, plug *snap.PlugInfo) error { type definer interface { - KModPermanentPlug(spec *Specification, plug *interfaces.Plug) error + KModPermanentPlug(spec *Specification, plug *snap.PlugInfo) error } if iface, ok := iface.(definer); ok { return iface.KModPermanentPlug(spec, plug) @@ -92,9 +93,9 @@ } // AddPermanentSlot records mount-specific side-effects of having a slot. -func (spec *Specification) AddPermanentSlot(iface interfaces.Interface, slot *interfaces.Slot) error { +func (spec *Specification) AddPermanentSlot(iface interfaces.Interface, slot *snap.SlotInfo) error { type definer interface { - KModPermanentSlot(spec *Specification, slot *interfaces.Slot) error + KModPermanentSlot(spec *Specification, slot *snap.SlotInfo) error } if iface, ok := iface.(definer); ok { return iface.KModPermanentSlot(spec, slot) diff -Nru snapd-2.29.4.2+17.10/interfaces/kmod/spec_test.go snapd-2.31.1+17.10/interfaces/kmod/spec_test.go --- snapd-2.29.4.2+17.10/interfaces/kmod/spec_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/kmod/spec_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -31,59 +31,59 @@ type specSuite struct { iface1, iface2 *ifacetest.TestInterface spec *kmod.Specification - plug *interfaces.Plug - slot *interfaces.Slot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot } var _ = Suite(&specSuite{ iface1: &ifacetest.TestInterface{ InterfaceName: "test", - KModConnectedPlugCallback: func(spec *kmod.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { + KModConnectedPlugCallback: func(spec *kmod.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { return spec.AddModule("module1") }, - KModConnectedSlotCallback: func(spec *kmod.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { + KModConnectedSlotCallback: func(spec *kmod.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { return spec.AddModule("module2") }, - KModPermanentPlugCallback: func(spec *kmod.Specification, plug *interfaces.Plug) error { + KModPermanentPlugCallback: func(spec *kmod.Specification, plug *snap.PlugInfo) error { return spec.AddModule("module3") }, - KModPermanentSlotCallback: func(spec *kmod.Specification, slot *interfaces.Slot) error { + KModPermanentSlotCallback: func(spec *kmod.Specification, slot *snap.SlotInfo) error { return spec.AddModule("module4") }, }, iface2: &ifacetest.TestInterface{ InterfaceName: "test-two", - KModConnectedPlugCallback: func(spec *kmod.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { + KModConnectedPlugCallback: func(spec *kmod.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { return spec.AddModule("module1") }, - KModConnectedSlotCallback: func(spec *kmod.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { + KModConnectedSlotCallback: func(spec *kmod.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { return spec.AddModule("module2") }, - KModPermanentPlugCallback: func(spec *kmod.Specification, plug *interfaces.Plug) error { + KModPermanentPlugCallback: func(spec *kmod.Specification, plug *snap.PlugInfo) error { return spec.AddModule("module5") }, - KModPermanentSlotCallback: func(spec *kmod.Specification, slot *interfaces.Slot) error { + KModPermanentSlotCallback: func(spec *kmod.Specification, slot *snap.SlotInfo) error { return spec.AddModule("module6") }, }, - plug: &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{SuggestedName: "snap"}, - Name: "name", - Interface: "test", - }, + plugInfo: &snap.PlugInfo{ + Snap: &snap.Info{SuggestedName: "snap"}, + Name: "name", + Interface: "test", }, - slot: &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "snap"}, - Name: "name", - Interface: "test", - }, + slotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "snap"}, + Name: "name", + Interface: "test", }, }) func (s *specSuite) SetUpTest(c *C) { s.spec = &kmod.Specification{} + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) } // AddModule is not broken @@ -104,15 +104,15 @@ c.Assert(s.spec.Modules(), DeepEquals, map[string]bool{"module1": true}) var r interfaces.Specification = s.spec - c.Assert(r.AddConnectedPlug(s.iface1, s.plug, nil, s.slot, nil), IsNil) - c.Assert(r.AddConnectedSlot(s.iface1, s.plug, nil, s.slot, nil), IsNil) - c.Assert(r.AddPermanentPlug(s.iface1, s.plug), IsNil) - c.Assert(r.AddPermanentSlot(s.iface1, s.slot), IsNil) - - c.Assert(r.AddConnectedPlug(s.iface2, s.plug, nil, s.slot, nil), IsNil) - c.Assert(r.AddConnectedSlot(s.iface2, s.plug, nil, s.slot, nil), IsNil) - c.Assert(r.AddPermanentPlug(s.iface2, s.plug), IsNil) - c.Assert(r.AddPermanentSlot(s.iface2, s.slot), IsNil) + c.Assert(r.AddConnectedPlug(s.iface1, s.plug, s.slot), IsNil) + c.Assert(r.AddConnectedSlot(s.iface1, s.plug, s.slot), IsNil) + c.Assert(r.AddPermanentPlug(s.iface1, s.plugInfo), IsNil) + c.Assert(r.AddPermanentSlot(s.iface1, s.slotInfo), IsNil) + + c.Assert(r.AddConnectedPlug(s.iface2, s.plug, s.slot), IsNil) + c.Assert(r.AddConnectedSlot(s.iface2, s.plug, s.slot), IsNil) + c.Assert(r.AddPermanentPlug(s.iface2, s.plugInfo), IsNil) + c.Assert(r.AddPermanentSlot(s.iface2, s.slotInfo), IsNil) c.Assert(s.spec.Modules(), DeepEquals, map[string]bool{ "module1": true, "module2": true, "module3": true, "module4": true, "module5": true, "module6": true}) } @@ -120,10 +120,10 @@ // The kmod.Specification can be used through the interfaces.Specification interface func (s *specSuite) TestSpecificationIface(c *C) { var r interfaces.Specification = s.spec - c.Assert(r.AddConnectedPlug(s.iface1, s.plug, nil, s.slot, nil), IsNil) - c.Assert(r.AddConnectedSlot(s.iface1, s.plug, nil, s.slot, nil), IsNil) - c.Assert(r.AddPermanentPlug(s.iface1, s.plug), IsNil) - c.Assert(r.AddPermanentSlot(s.iface1, s.slot), IsNil) + c.Assert(r.AddConnectedPlug(s.iface1, s.plug, s.slot), IsNil) + c.Assert(r.AddConnectedSlot(s.iface1, s.plug, s.slot), IsNil) + c.Assert(r.AddPermanentPlug(s.iface1, s.plugInfo), IsNil) + c.Assert(r.AddPermanentSlot(s.iface1, s.slotInfo), IsNil) c.Assert(s.spec.Modules(), DeepEquals, map[string]bool{ "module1": true, "module2": true, "module3": true, "module4": true}) } diff -Nru snapd-2.29.4.2+17.10/interfaces/mount/backend.go snapd-2.31.1+17.10/interfaces/mount/backend.go --- snapd-2.29.4.2+17.10/interfaces/mount/backend.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/mount/backend.go 2018-01-24 20:02:44.000000000 +0000 @@ -43,6 +43,11 @@ // Backend is responsible for maintaining mount files for snap-confine type Backend struct{} +// Initialize does nothing. +func (b *Backend) Initialize() error { + return nil +} + // Name returns the name of the backend. func (b *Backend) Name() interfaces.SecuritySystem { return interfaces.SecurityMount @@ -56,6 +61,7 @@ if err != nil { return fmt.Errorf("cannot obtain mount security snippets for snap %q: %s", snapName, err) } + spec.(*Specification).AddSnapLayout(snapInfo) content := deriveContent(spec.(*Specification), snapInfo) // synchronize the content with the filesystem glob := fmt.Sprintf("snap.%s.*fstab", snapName) @@ -87,13 +93,14 @@ // deriveContent computes .fstab tables based on requests made to the specification. func deriveContent(spec *Specification, snapInfo *snap.Info) map[string]*osutil.FileState { // No entries? Nothing to do! - if len(spec.mountEntries) == 0 { + entries := spec.MountEntries() + if len(entries) == 0 { return nil } // Compute the contents of the fstab file. It should contain all the mount // rules collected by the backend controller. var buffer bytes.Buffer - for _, entry := range spec.mountEntries { + for _, entry := range entries { fmt.Fprintf(&buffer, "%s\n", entry) } fstate := &osutil.FileState{Content: buffer.Bytes(), Mode: 0644} diff -Nru snapd-2.29.4.2+17.10/interfaces/mount/backend_test.go snapd-2.31.1+17.10/interfaces/mount/backend_test.go --- snapd-2.29.4.2+17.10/interfaces/mount/backend_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/mount/backend_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -35,6 +35,7 @@ "github.com/snapcore/snapd/interfaces/ifacetest" "github.com/snapcore/snapd/interfaces/mount" "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/snap" ) func Test(t *testing.T) { @@ -128,11 +129,11 @@ fsEntry2 := mount.Entry{Name: "/src-2", Dir: "/dst-2", Type: "none", Options: []string{"bind", "ro"}, DumpFrequency: 0, CheckPassNumber: 0} // Give the plug a permanent effect - s.Iface.MountPermanentPlugCallback = func(spec *mount.Specification, plug *interfaces.Plug) error { + s.Iface.MountPermanentPlugCallback = func(spec *mount.Specification, plug *snap.PlugInfo) error { return spec.AddMountEntry(fsEntry1) } // Give the slot a permanent effect - s.iface2.MountPermanentSlotCallback = func(spec *mount.Specification, slot *interfaces.Slot) error { + s.iface2.MountPermanentSlotCallback = func(spec *mount.Specification, slot *snap.SlotInfo) error { return spec.AddMountEntry(fsEntry2) } @@ -153,7 +154,7 @@ } func (s *backendSuite) TestSetupSetsupWithoutDir(c *C) { - s.Iface.MountPermanentPlugCallback = func(spec *mount.Specification, plug *interfaces.Plug) error { + s.Iface.MountPermanentPlugCallback = func(spec *mount.Specification, plug *snap.PlugInfo) error { return spec.AddMountEntry(mount.Entry{}) } diff -Nru snapd-2.29.4.2+17.10/interfaces/mount/entry.go snapd-2.31.1+17.10/interfaces/mount/entry.go --- snapd-2.29.4.2+17.10/interfaces/mount/entry.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/mount/entry.go 2018-01-24 20:02:44.000000000 +0000 @@ -83,6 +83,14 @@ `\134`, "\\", ).Replace +func Escape(path string) string { + return escape(path) +} + +func Unescape(path string) string { + return unescape(path) +} + func (e Entry) String() string { // Name represents name of the device in a mount entry. name := "none" @@ -141,8 +149,10 @@ return e, nil } -// OptsToFlags converts mount options strings to a mount flag. -func OptsToFlags(opts []string) (flags int, err error) { +// OptsToCommonFlags converts mount options strings to a mount flag, returning unparsed flags. +// The unparsed flags will not contain any snapd-specific mount option, those +// starting with the string "x-snapd." +func OptsToCommonFlags(opts []string) (flags int, unparsed []string) { for _, opt := range opts { switch opt { case "ro": @@ -192,8 +202,74 @@ case "strictatime": flags |= syscall.MS_STRICTATIME default: + if !strings.HasPrefix(opt, "x-snapd.") { + unparsed = append(unparsed, opt) + } + } + } + return flags, unparsed +} + +// OptsToFlags converts mount options strings to a mount flag. +func OptsToFlags(opts []string) (flags int, err error) { + flags, unparsed := OptsToCommonFlags(opts) + for _, opt := range unparsed { + if !strings.HasPrefix(opt, "x-snapd.") { return 0, fmt.Errorf("unsupported mount option: %q", opt) } } return flags, nil } + +// OptStr returns the value part of a key=value mount option. +// The name of the option must not contain the trailing "=" character. +func (e *Entry) OptStr(name string) (string, bool) { + prefix := name + "=" + for _, opt := range e.Options { + if strings.HasPrefix(opt, prefix) { + kv := strings.SplitN(opt, "=", 2) + return kv[1], true + } + } + return "", false +} + +// OptBool returns true if a given mount option is present. +func (e *Entry) OptBool(name string) bool { + for _, opt := range e.Options { + if opt == name { + return true + } + } + return false +} + +// XSnapdKindSymlink returns the string "x-snapd.kind=symlink". +func XSnapdKindSymlink() string { + return "x-snapd.kind=symlink" +} + +// XSnapdKindFile returns the string "x-snapd.kind=file". +func XSnapdKindFile() string { + return "x-snapd.kind=file" +} + +// XSnapdUser returns the string "x-snapd.user=%d". +func XSnapdUser(uid int) string { + return fmt.Sprintf("x-snapd.user=%d", uid) +} + +// XSnapdGroup returns the string "x-snapd.group=%d". +func XSnapdGroup(gid int) string { + return fmt.Sprintf("x-snapd.group=%d", gid) +} + +// XSnapdMode returns the string "x-snapd.mode=%#o". +func XSnapdMode(mode uint32) string { + return fmt.Sprintf("x-snapd.mode=%#o", mode) +} + +// XSnapdSymlink returns the string "x-snapd.symlink=%s". +func XSnapdSymlink(oldname string) string { + return fmt.Sprintf("x-snapd.symlink=%s", oldname) +} diff -Nru snapd-2.29.4.2+17.10/interfaces/mount/entry_test.go snapd-2.31.1+17.10/interfaces/mount/entry_test.go --- snapd-2.29.4.2+17.10/interfaces/mount/entry_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/mount/entry_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -161,4 +161,59 @@ c.Assert(flags, Equals, syscall.MS_RDONLY|syscall.MS_NODEV|syscall.MS_NOSUID) _, err = mount.OptsToFlags([]string{"bogus"}) c.Assert(err, ErrorMatches, `unsupported mount option: "bogus"`) + // The x-snapd-prefix is reserved for non-kernel parameters that do not + // translate to kernel level mount flags. This is similar to systemd or + // udisks that use fstab options to convey additional data. + flags, err = mount.OptsToFlags([]string{"x-snapd.foo"}) + c.Assert(err, IsNil) + c.Assert(flags, Equals, 0) +} + +// Test (string) options -> (int, unparsed) flag conversion code. +func (s *entrySuite) TestOptsToCommonFlags(c *C) { + flags, unparsed := mount.OptsToCommonFlags(nil) + c.Assert(flags, Equals, 0) + c.Assert(unparsed, HasLen, 0) + flags, unparsed = mount.OptsToCommonFlags([]string{"ro", "nodev", "nosuid"}) + c.Assert(flags, Equals, syscall.MS_RDONLY|syscall.MS_NODEV|syscall.MS_NOSUID) + c.Assert(unparsed, HasLen, 0) + flags, unparsed = mount.OptsToCommonFlags([]string{"bogus"}) + c.Assert(flags, Equals, 0) + c.Assert(unparsed, DeepEquals, []string{"bogus"}) + // The x-snapd-prefix is reserved for non-kernel parameters that do not + // translate to kernel level mount flags. This is similar to systemd or + // udisks that use fstab options to convey additional data. Those are not + // returned as "unparsed" as we don't want to pass them to the kernel. + flags, unparsed = mount.OptsToCommonFlags([]string{"x-snapd.foo"}) + c.Assert(flags, Equals, 0) + c.Assert(unparsed, HasLen, 0) +} + +func (s *entrySuite) TestOptStr(c *C) { + e := &mount.Entry{Options: []string{"key=value"}} + val, ok := e.OptStr("key") + c.Assert(ok, Equals, true) + c.Assert(val, Equals, "value") + + val, ok = e.OptStr("missing") + c.Assert(ok, Equals, false) + c.Assert(val, Equals, "") +} + +func (s *entrySuite) TestOptBool(c *C) { + e := &mount.Entry{Options: []string{"key"}} + val := e.OptBool("key") + c.Assert(val, Equals, true) + + val = e.OptBool("missing") + c.Assert(val, Equals, false) +} + +func (s *entrySuite) TestOptionHelpers(c *C) { + c.Assert(mount.XSnapdKindSymlink(), Equals, "x-snapd.kind=symlink") + c.Assert(mount.XSnapdKindFile(), Equals, "x-snapd.kind=file") + c.Assert(mount.XSnapdUser(1000), Equals, "x-snapd.user=1000") + c.Assert(mount.XSnapdGroup(1000), Equals, "x-snapd.group=1000") + c.Assert(mount.XSnapdMode(0755), Equals, "x-snapd.mode=0755") + c.Assert(mount.XSnapdSymlink("oldname"), Equals, "x-snapd.symlink=oldname") } diff -Nru snapd-2.29.4.2+17.10/interfaces/mount/spec.go snapd-2.31.1+17.10/interfaces/mount/spec.go --- snapd-2.29.4.2+17.10/interfaces/mount/spec.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/mount/spec.go 2018-02-02 15:31:34.000000000 +0000 @@ -20,7 +20,12 @@ package mount import ( + "fmt" + "sort" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/snap" ) // Specification assists in collecting mount entries associated with an interface. @@ -29,7 +34,8 @@ // holds internal state that is used by the mount backend during the interface // setup process. type Specification struct { - mountEntries []Entry + layoutMountEntries []Entry + mountEntries []Entry } // AddMountEntry adds a new mount entry. @@ -38,41 +44,122 @@ return nil } +func mountEntryFromLayout(layout *snap.Layout) Entry { + var entry Entry + + mountPoint := layout.Snap.ExpandSnapVariables(layout.Path) + entry.Dir = mountPoint + + if layout.Bind != "" { + mountSource := layout.Snap.ExpandSnapVariables(layout.Bind) + // XXX: what about ro mounts? + // XXX: what about file mounts, those need x-snapd.kind=file to create correctly? + entry.Options = []string{"bind", "rw"} + entry.Name = mountSource + } + + if layout.Type == "tmpfs" { + entry.Type = "tmpfs" + entry.Name = "tmpfs" + } + + if layout.Symlink != "" { + oldname := layout.Snap.ExpandSnapVariables(layout.Symlink) + entry.Options = []string{XSnapdKindSymlink(), XSnapdSymlink(oldname)} + } + + var uid int + // Only root is allowed here until we support custom users. Root is default. + switch layout.User { + case "root", "": + uid = 0 + } + if uid != 0 { + entry.Options = append(entry.Options, XSnapdUser(uid)) + } + + var gid int + // Only root is allowed here until we support custom groups. Root is default. + // This is validated in spec.go. + switch layout.Group { + case "root", "": + gid = 0 + } + if gid != 0 { + entry.Options = append(entry.Options, XSnapdGroup(gid)) + } + + if layout.Mode != 0755 { + entry.Options = append(entry.Options, XSnapdMode(uint32(layout.Mode))) + } + return entry +} + +// AddSnapLayout adds mount entries based on the layout of the snap. +func (spec *Specification) AddSnapLayout(si *snap.Info) { + // TODO: handle layouts in base snaps as well as in this snap. + + // walk the layout elements in deterministic order, by mount point name + paths := make([]string, 0, len(si.Layout)) + for path := range si.Layout { + paths = append(paths, path) + } + sort.Strings(paths) + + for _, path := range paths { + entry := mountEntryFromLayout(si.Layout[path]) + spec.layoutMountEntries = append(spec.layoutMountEntries, entry) + } +} + // MountEntries returns a copy of the added mount entries. func (spec *Specification) MountEntries() []Entry { - result := make([]Entry, len(spec.mountEntries)) - copy(result, spec.mountEntries) + result := make([]Entry, 0, len(spec.layoutMountEntries)+len(spec.mountEntries)) + result = append(result, spec.layoutMountEntries...) + result = append(result, spec.mountEntries...) + // Number each entry, in case we get clashes this will automatically give + // them unique names. + count := make(map[string]int, len(result)) + for i := range result { + path := result[i].Dir + count[path] += 1 + if c := count[path]; c > 1 { + newDir := fmt.Sprintf("%s-%d", result[i].Dir, c) + logger.Noticef("renaming mount entry for directory %q to %q to avoid a clash", result[i].Dir, newDir) + result[i].Dir = newDir + } + } return result } // Implementation of methods required by interfaces.Specification // AddConnectedPlug records mount-specific side-effects of having a connected plug. -func (spec *Specification) AddConnectedPlug(iface interfaces.Interface, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (spec *Specification) AddConnectedPlug(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { type definer interface { - MountConnectedPlug(spec *Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + MountConnectedPlug(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } if iface, ok := iface.(definer); ok { - return iface.MountConnectedPlug(spec, plug, plugAttrs, slot, slotAttrs) + return iface.MountConnectedPlug(spec, plug, slot) } return nil } // AddConnectedSlot records mount-specific side-effects of having a connected slot. -func (spec *Specification) AddConnectedSlot(iface interfaces.Interface, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (spec *Specification) AddConnectedSlot(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { type definer interface { - MountConnectedSlot(spec *Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + MountConnectedSlot(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } if iface, ok := iface.(definer); ok { - return iface.MountConnectedSlot(spec, plug, plugAttrs, slot, slotAttrs) + return iface.MountConnectedSlot(spec, plug, slot) } return nil } // AddPermanentPlug records mount-specific side-effects of having a plug. -func (spec *Specification) AddPermanentPlug(iface interfaces.Interface, plug *interfaces.Plug) error { +func (spec *Specification) AddPermanentPlug(iface interfaces.Interface, plug *snap.PlugInfo) error { type definer interface { - MountPermanentPlug(spec *Specification, plug *interfaces.Plug) error + MountPermanentPlug(spec *Specification, plug *snap.PlugInfo) error } if iface, ok := iface.(definer); ok { return iface.MountPermanentPlug(spec, plug) @@ -81,9 +168,9 @@ } // AddPermanentSlot records mount-specific side-effects of having a slot. -func (spec *Specification) AddPermanentSlot(iface interfaces.Interface, slot *interfaces.Slot) error { +func (spec *Specification) AddPermanentSlot(iface interfaces.Interface, slot *snap.SlotInfo) error { type definer interface { - MountPermanentSlot(spec *Specification, slot *interfaces.Slot) error + MountPermanentSlot(spec *Specification, slot *snap.SlotInfo) error } if iface, ok := iface.(definer); ok { return iface.MountPermanentSlot(spec, slot) diff -Nru snapd-2.29.4.2+17.10/interfaces/mount/spec_test.go snapd-2.31.1+17.10/interfaces/mount/spec_test.go --- snapd-2.29.4.2+17.10/interfaces/mount/spec_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/mount/spec_test.go 2018-02-02 15:31:34.000000000 +0000 @@ -20,74 +20,117 @@ package mount_test import ( + "strings" + . "gopkg.in/check.v1" "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/ifacetest" "github.com/snapcore/snapd/interfaces/mount" + "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snaptest" ) type specSuite struct { - iface *ifacetest.TestInterface - spec *mount.Specification - plug *interfaces.Plug - slot *interfaces.Slot + iface *ifacetest.TestInterface + spec *mount.Specification + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot } var _ = Suite(&specSuite{ iface: &ifacetest.TestInterface{ InterfaceName: "test", - MountConnectedPlugCallback: func(spec *mount.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - return spec.AddMountEntry(mount.Entry{Name: "connected-plug"}) + MountConnectedPlugCallback: func(spec *mount.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { + return spec.AddMountEntry(mount.Entry{Dir: "dir-a", Name: "connected-plug"}) }, - MountConnectedSlotCallback: func(spec *mount.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - return spec.AddMountEntry(mount.Entry{Name: "connected-slot"}) + MountConnectedSlotCallback: func(spec *mount.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { + return spec.AddMountEntry(mount.Entry{Dir: "dir-b", Name: "connected-slot"}) }, - MountPermanentPlugCallback: func(spec *mount.Specification, plug *interfaces.Plug) error { - return spec.AddMountEntry(mount.Entry{Name: "permanent-plug"}) + MountPermanentPlugCallback: func(spec *mount.Specification, plug *snap.PlugInfo) error { + return spec.AddMountEntry(mount.Entry{Dir: "dir-c", Name: "permanent-plug"}) }, - MountPermanentSlotCallback: func(spec *mount.Specification, slot *interfaces.Slot) error { - return spec.AddMountEntry(mount.Entry{Name: "permanent-slot"}) + MountPermanentSlotCallback: func(spec *mount.Specification, slot *snap.SlotInfo) error { + return spec.AddMountEntry(mount.Entry{Dir: "dir-d", Name: "permanent-slot"}) }, }, - plug: &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{SuggestedName: "snap"}, - Name: "name", - Interface: "test", - }, + plugInfo: &snap.PlugInfo{ + Snap: &snap.Info{SuggestedName: "snap"}, + Name: "name", + Interface: "test", }, - slot: &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "snap"}, - Name: "name", - Interface: "test", - }, + slotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "snap"}, + Name: "name", + Interface: "test", }, }) func (s *specSuite) SetUpTest(c *C) { s.spec = &mount.Specification{} + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) } // AddMountEntry is not broken func (s *specSuite) TestSmoke(c *C) { - ent0 := mount.Entry{Name: "fs1"} - ent1 := mount.Entry{Name: "fs2"} + ent0 := mount.Entry{Dir: "dir-a", Name: "fs1"} + ent1 := mount.Entry{Dir: "dir-b", Name: "fs2"} c.Assert(s.spec.AddMountEntry(ent0), IsNil) c.Assert(s.spec.AddMountEntry(ent1), IsNil) c.Assert(s.spec.MountEntries(), DeepEquals, []mount.Entry{ent0, ent1}) } +// Added entries can clash and are automatically renamed by MountEntries +func (s *specSuite) TestMountEntriesDeclash(c *C) { + buf, restore := logger.MockLogger() + defer restore() + c.Assert(s.spec.AddMountEntry(mount.Entry{Dir: "foo", Name: "fs1"}), IsNil) + c.Assert(s.spec.AddMountEntry(mount.Entry{Dir: "foo", Name: "fs2"}), IsNil) + c.Assert(s.spec.MountEntries(), DeepEquals, []mount.Entry{ + {Dir: "foo", Name: "fs1"}, + {Dir: "foo-2", Name: "fs2"}, + }) + // extract the relevant part of the log + msg := strings.SplitAfter(strings.TrimSpace(buf.String()), ": ")[1] + c.Assert(msg, Equals, `renaming mount entry for directory "foo" to "foo-2" to avoid a clash`) +} + // The mount.Specification can be used through the interfaces.Specification interface func (s *specSuite) TestSpecificationIface(c *C) { var r interfaces.Specification = s.spec - c.Assert(r.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(r.AddConnectedSlot(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(r.AddPermanentPlug(s.iface, s.plug), IsNil) - c.Assert(r.AddPermanentSlot(s.iface, s.slot), IsNil) + c.Assert(r.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(r.AddConnectedSlot(s.iface, s.plug, s.slot), IsNil) + c.Assert(r.AddPermanentPlug(s.iface, s.plugInfo), IsNil) + c.Assert(r.AddPermanentSlot(s.iface, s.slotInfo), IsNil) + c.Assert(s.spec.MountEntries(), DeepEquals, []mount.Entry{ + {Dir: "dir-a", Name: "connected-plug"}, + {Dir: "dir-b", Name: "connected-slot"}, + {Dir: "dir-c", Name: "permanent-plug"}, + {Dir: "dir-d", Name: "permanent-slot"}}) +} + +const snapWithLayout = ` +name: vanguard +layout: + /usr: + bind: $SNAP/usr + /mytmp: + type: tmpfs + mode: 1777 + /mylink: + symlink: $SNAP/link/target +` + +func (s *specSuite) TestMountEntryFromLayout(c *C) { + snapInfo := snaptest.MockInfo(c, snapWithLayout, &snap.SideInfo{Revision: snap.R(42)}) + s.spec.AddSnapLayout(snapInfo) c.Assert(s.spec.MountEntries(), DeepEquals, []mount.Entry{ - {Name: "connected-plug"}, {Name: "connected-slot"}, - {Name: "permanent-plug"}, {Name: "permanent-slot"}}) + {Dir: "/mylink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/snap/vanguard/42/link/target"}}, + {Name: "tmpfs", Dir: "/mytmp", Type: "tmpfs", Options: []string{"x-snapd.mode=01777"}}, + {Name: "/snap/vanguard/42/usr", Dir: "/usr", Options: []string{"bind", "rw"}}, + }) } diff -Nru snapd-2.29.4.2+17.10/interfaces/policy/basedeclaration_test.go snapd-2.31.1+17.10/interfaces/policy/basedeclaration_test.go --- snapd-2.29.4.2+17.10/interfaces/policy/basedeclaration_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/policy/basedeclaration_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -448,7 +448,7 @@ // describe installation rules for slots succinctly for cross-checking, // if an interface is not mentioned here a slot of its type can only // be installed by a core snap (and this was taken care by -// SanitizeSlot), +// BeforePrepareSlot), // otherwise the entry for the interface is the list of snap types it // can be installed by (using the declaration naming); // ATM a nil entry means even stricter rules that would need be tested @@ -500,6 +500,7 @@ "unity8-calendar": {"app"}, "unity8-contacts": {"app"}, "upower-observe": {"app", "core"}, + "wayland": {"app", "core"}, // snowflakes "classic-support": nil, "docker": nil, @@ -525,7 +526,7 @@ types, ok := slotInstallation[iface.Name()] compareWithSanitize := false if !ok { // common ones, only core can install them, - // their plain SanitizeSlot checked for that + // their plain BeforePrepareSlot checked for that types = []string{"core"} compareWithSanitize = true } @@ -545,8 +546,7 @@ c.Check(err, NotNil, comm) } if compareWithSanitize { - slot := &interfaces.Slot{SlotInfo: slotInfo} - sanitizeErr := slot.Sanitize(iface) + sanitizeErr := interfaces.BeforePrepareSlot(iface, slotInfo) if err == nil { c.Check(sanitizeErr, IsNil, comm) } else { @@ -707,6 +707,7 @@ "lxd-support": true, "snapd-control": true, "unity8": true, + "wayland": true, } for _, iface := range all { @@ -791,3 +792,17 @@ revision: 0 `) } + +func (s *baseDeclSuite) TestBrowserSupportAllowSandbox(c *C) { + const plugYaml = `name: plug-snap +plugs: + browser-support: + allow-sandbox: true +` + cand := s.connectCand(c, "browser-support", "", plugYaml) + err := cand.Check() + c.Check(err, NotNil) + + err = cand.CheckAutoConnect() + c.Check(err, NotNil) +} diff -Nru snapd-2.29.4.2+17.10/interfaces/repo.go snapd-2.31.1+17.10/interfaces/repo.go --- snapd-2.29.4.2+17.10/interfaces/repo.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/repo.go 2018-01-24 20:02:44.000000000 +0000 @@ -34,12 +34,12 @@ m sync.Mutex ifaces map[string]Interface // Indexed by [snapName][plugName] - plugs map[string]map[string]*Plug - slots map[string]map[string]*Slot + plugs map[string]map[string]*snap.PlugInfo + slots map[string]map[string]*snap.SlotInfo // given a slot and a plug, are they connected? - slotPlugs map[*Slot]map[*Plug]*Connection + slotPlugs map[*snap.SlotInfo]map[*snap.PlugInfo]*Connection // given a plug and a slot, are they connected? - plugSlots map[*Plug]map[*Slot]*Connection + plugSlots map[*snap.PlugInfo]map[*snap.SlotInfo]*Connection backends map[SecuritySystem]SecurityBackend } @@ -47,10 +47,10 @@ func NewRepository() *Repository { repo := &Repository{ ifaces: make(map[string]Interface), - plugs: make(map[string]map[string]*Plug), - slots: make(map[string]map[string]*Slot), - slotPlugs: make(map[*Slot]map[*Plug]*Connection), - plugSlots: make(map[*Plug]map[*Slot]*Connection), + plugs: make(map[string]map[string]*snap.PlugInfo), + slots: make(map[string]map[string]*snap.SlotInfo), + slotPlugs: make(map[*snap.SlotInfo]map[*snap.PlugInfo]*Connection), + plugSlots: make(map[*snap.PlugInfo]map[*snap.SlotInfo]*Connection), backends: make(map[SecuritySystem]SecurityBackend), } @@ -112,7 +112,7 @@ // Collect all plugs of this interface type. for _, snapName := range sortedSnapNamesWithPlugs(r.plugs) { for _, plugName := range sortedPlugNames(r.plugs[snapName]) { - plugInfo := r.plugs[snapName][plugName].PlugInfo + plugInfo := r.plugs[snapName][plugName] if plugInfo.Interface == ifaceName { ii.Plugs = append(ii.Plugs, plugInfo) } @@ -123,7 +123,7 @@ // Collect all slots of this interface type. for _, snapName := range sortedSnapNamesWithSlots(r.slots) { for _, slotName := range sortedSlotNames(r.slots[snapName]) { - slotInfo := r.slots[snapName][slotName].SlotInfo + slotInfo := r.slots[snapName][slotName] if slotInfo.Interface == ifaceName { ii.Slots = append(ii.Slots, slotInfo) } @@ -205,11 +205,11 @@ // AllPlugs returns all plugs of the given interface. // If interfaceName is the empty string, all plugs are returned. -func (r *Repository) AllPlugs(interfaceName string) []*Plug { +func (r *Repository) AllPlugs(interfaceName string) []*snap.PlugInfo { r.m.Lock() defer r.m.Unlock() - var result []*Plug + var result []*snap.PlugInfo for _, plugsForSnap := range r.plugs { for _, plug := range plugsForSnap { if interfaceName == "" || plug.Interface == interfaceName { @@ -222,11 +222,11 @@ } // Plugs returns the plugs offered by the named snap. -func (r *Repository) Plugs(snapName string) []*Plug { +func (r *Repository) Plugs(snapName string) []*snap.PlugInfo { r.m.Lock() defer r.m.Unlock() - var result []*Plug + var result []*snap.PlugInfo for _, plug := range r.plugs[snapName] { result = append(result, plug) } @@ -235,7 +235,7 @@ } // Plug returns the specified plug from the named snap. -func (r *Repository) Plug(snapName, plugName string) *Plug { +func (r *Repository) Plug(snapName, plugName string) *snap.PlugInfo { r.m.Lock() defer r.m.Unlock() @@ -245,7 +245,7 @@ // AddPlug adds a plug to the repository. // Plug names must be valid snap names, as defined by ValidateName. // Plug name must be unique within a particular snap. -func (r *Repository) AddPlug(plug *Plug) error { +func (r *Repository) AddPlug(plug *snap.PlugInfo) error { r.m.Lock() defer r.m.Unlock() @@ -263,10 +263,6 @@ if i == nil { return fmt.Errorf("cannot add plug, interface %q is not known", plug.Interface) } - // Reject plug that don't pass interface-specific sanitization - if err := plug.Sanitize(i); err != nil { - return fmt.Errorf("cannot add plug: %v", err) - } if _, ok := r.plugs[snapName][plug.Name]; ok { return fmt.Errorf("snap %q has plugs conflicting on name %q", snapName, plug.Name) } @@ -274,7 +270,7 @@ return fmt.Errorf("snap %q has plug and slot conflicting on name %q", snapName, plug.Name) } if r.plugs[snapName] == nil { - r.plugs[snapName] = make(map[string]*Plug) + r.plugs[snapName] = make(map[string]*snap.PlugInfo) } r.plugs[snapName][plug.Name] = plug return nil @@ -304,11 +300,11 @@ // AllSlots returns all slots of the given interface. // If interfaceName is the empty string, all slots are returned. -func (r *Repository) AllSlots(interfaceName string) []*Slot { +func (r *Repository) AllSlots(interfaceName string) []*snap.SlotInfo { r.m.Lock() defer r.m.Unlock() - var result []*Slot + var result []*snap.SlotInfo for _, slotsForSnap := range r.slots { for _, slot := range slotsForSnap { if interfaceName == "" || slot.Interface == interfaceName { @@ -321,11 +317,11 @@ } // Slots returns the slots offered by the named snap. -func (r *Repository) Slots(snapName string) []*Slot { +func (r *Repository) Slots(snapName string) []*snap.SlotInfo { r.m.Lock() defer r.m.Unlock() - var result []*Slot + var result []*snap.SlotInfo for _, slot := range r.slots[snapName] { result = append(result, slot) } @@ -334,7 +330,7 @@ } // Slot returns the specified slot from the named snap. -func (r *Repository) Slot(snapName, slotName string) *Slot { +func (r *Repository) Slot(snapName, slotName string) *snap.SlotInfo { r.m.Lock() defer r.m.Unlock() @@ -344,7 +340,7 @@ // AddSlot adds a new slot to the repository. // Adding a slot with invalid name returns an error. // Adding a slot that has the same name and snap name as another slot returns an error. -func (r *Repository) AddSlot(slot *Slot) error { +func (r *Repository) AddSlot(slot *snap.SlotInfo) error { r.m.Lock() defer r.m.Unlock() @@ -363,9 +359,6 @@ if i == nil { return fmt.Errorf("cannot add slot, interface %q is not known", slot.Interface) } - if err := slot.Sanitize(i); err != nil { - return fmt.Errorf("cannot add slot: %v", err) - } if _, ok := r.slots[snapName][slot.Name]; ok { return fmt.Errorf("snap %q has slots conflicting on name %q", snapName, slot.Name) } @@ -373,7 +366,7 @@ return fmt.Errorf("snap %q has plug and slot conflicting on name %q", snapName, slot.Name) } if r.slots[snapName] == nil { - r.slots[snapName] = make(map[string]*Slot) + r.slots[snapName] = make(map[string]*snap.SlotInfo) } r.slots[snapName][slot.Name] = slot return nil @@ -465,7 +458,7 @@ return ref, fmt.Errorf("cannot connect %s:%s (%q interface) to %s:%s (%q interface)", plugSnapName, plugName, plug.Interface, slotSnapName, slotName, slot.Interface) } - ref = ConnRef{PlugRef: plug.Ref(), SlotRef: slot.Ref()} + ref = *NewConnRef(plug, slot) return ref, nil } @@ -524,7 +517,7 @@ return nil, fmt.Errorf("cannot disconnect %s:%s from %s:%s, it is not connected", plugSnapName, plugName, slotSnapName, slotName) } - return []ConnRef{{PlugRef: plug.Ref(), SlotRef: slot.Ref()}}, nil + return []ConnRef{*NewConnRef(plug, slot)}, nil // 2: : (through 1st pair) // Return a list of connections involving specified plug or slot. case plugName != "" && slotName == "" && slotSnapName == "": @@ -579,16 +572,19 @@ } // Connect the plug if r.slotPlugs[slot] == nil { - r.slotPlugs[slot] = make(map[*Plug]*Connection) + r.slotPlugs[slot] = make(map[*snap.PlugInfo]*Connection) } if r.plugSlots[plug] == nil { - r.plugSlots[plug] = make(map[*Slot]*Connection) + r.plugSlots[plug] = make(map[*snap.SlotInfo]*Connection) } - conn := &Connection{plugInfo: plug.PlugInfo, slotInfo: slot.SlotInfo} + + // TODO: store copy of attributes from hooks + cplug := NewConnectedPlug(plug, nil) + cslot := NewConnectedSlot(slot, nil) + + conn := &Connection{plug: cplug, slot: cslot} r.slotPlugs[slot][plug] = conn r.plugSlots[plug][slot] = conn - slot.Connections = append(slot.Connections, PlugRef{plug.Snap.Name(), plug.Name}) - plug.Connections = append(plug.Connections, SlotRef{slot.Snap.Name(), slot.Name}) return nil } @@ -659,18 +655,21 @@ return nil, fmt.Errorf("snap %q has no plug or slot named %q", snapName, plugOrSlotName) } // Collect all the relevant connections + if plug, ok := r.plugs[snapName][plugOrSlotName]; ok { - for _, slotRef := range plug.Connections { - connRef := ConnRef{PlugRef: plug.Ref(), SlotRef: slotRef} + for slotInfo := range r.plugSlots[plug] { + connRef := *NewConnRef(plug, slotInfo) conns = append(conns, connRef) } } + if slot, ok := r.slots[snapName][plugOrSlotName]; ok { - for _, plugRef := range slot.Connections { - connRef := ConnRef{PlugRef: plugRef, SlotRef: slot.Ref()} + for plugInfo := range r.slotPlugs[slot] { + connRef := *NewConnRef(plugInfo, slot) conns = append(conns, connRef) } } + return conns, nil } @@ -701,7 +700,7 @@ } // disconnect disconnects a plug from a slot. -func (r *Repository) disconnect(plug *Plug, slot *Slot) { +func (r *Repository) disconnect(plug *snap.PlugInfo, slot *snap.SlotInfo) { delete(r.slotPlugs[slot], plug) if len(r.slotPlugs[slot]) == 0 { delete(r.slotPlugs, slot) @@ -710,26 +709,6 @@ if len(r.plugSlots[plug]) == 0 { delete(r.plugSlots, plug) } - for i, plugRef := range slot.Connections { - if plugRef.Snap == plug.Snap.Name() && plugRef.Name == plug.Name { - slot.Connections[i] = slot.Connections[len(slot.Connections)-1] - slot.Connections = slot.Connections[:len(slot.Connections)-1] - if len(slot.Connections) == 0 { - slot.Connections = nil - } - break - } - } - for i, slotRef := range plug.Connections { - if slotRef.Snap == slot.Snap.Name() && slotRef.Name == slot.Name { - plug.Connections[i] = plug.Connections[len(plug.Connections)-1] - plug.Connections = plug.Connections[:len(plug.Connections)-1] - if len(plug.Connections) == 0 { - plug.Connections = nil - } - break - } - } } // Backends returns all the security backends. @@ -751,29 +730,28 @@ defer r.m.Unlock() ifaces := &Interfaces{} + // Copy and flatten plugs and slots for _, plugs := range r.plugs { - for _, plug := range plugs { - p := &Plug{ - PlugInfo: plug.PlugInfo, - Connections: append([]SlotRef(nil), plug.Connections...), - } - sort.Sort(bySlotRef(p.Connections)) - ifaces.Plugs = append(ifaces.Plugs, p) + for _, plugInfo := range plugs { + ifaces.Plugs = append(ifaces.Plugs, plugInfo) } } for _, slots := range r.slots { - for _, slot := range slots { - s := &Slot{ - SlotInfo: slot.SlotInfo, - Connections: append([]PlugRef(nil), slot.Connections...), - } - sort.Sort(byPlugRef(s.Connections)) - ifaces.Slots = append(ifaces.Slots, s) + for _, slotInfo := range slots { + ifaces.Slots = append(ifaces.Slots, slotInfo) } } + + for plug, slots := range r.plugSlots { + for slot := range slots { + ifaces.Connections = append(ifaces.Connections, NewConnRef(plug, slot)) + } + } + sort.Sort(byPlugSnapAndName(ifaces.Plugs)) sort.Sort(bySlotSnapAndName(ifaces.Slots)) + sort.Sort(byConnRef(ifaces.Connections)) return ifaces } @@ -790,25 +768,25 @@ spec := backend.NewSpecification() // slot side - for _, slot := range r.slots[snapName] { - iface := r.ifaces[slot.Interface] - if err := spec.AddPermanentSlot(iface, slot); err != nil { + for _, slotInfo := range r.slots[snapName] { + iface := r.ifaces[slotInfo.Interface] + if err := spec.AddPermanentSlot(iface, slotInfo); err != nil { return nil, err } - for plug := range r.slotPlugs[slot] { - if err := spec.AddConnectedSlot(iface, plug, nil, slot, nil); err != nil { + for _, conn := range r.slotPlugs[slotInfo] { + if err := spec.AddConnectedSlot(iface, conn.plug, conn.slot); err != nil { return nil, err } } } // plug side - for _, plug := range r.plugs[snapName] { - iface := r.ifaces[plug.Interface] - if err := spec.AddPermanentPlug(iface, plug); err != nil { + for _, plugInfo := range r.plugs[snapName] { + iface := r.ifaces[plugInfo.Interface] + if err := spec.AddPermanentPlug(iface, plugInfo); err != nil { return nil, err } - for slot := range r.plugSlots[plug] { - if err := spec.AddConnectedPlug(iface, plug, nil, slot, nil); err != nil { + for _, conn := range r.plugSlots[plugInfo] { + if err := spec.AddConnectedPlug(iface, conn.plug, conn.slot); err != nil { return nil, err } } @@ -849,10 +827,9 @@ continue } if r.plugs[snapName] == nil { - r.plugs[snapName] = make(map[string]*Plug) + r.plugs[snapName] = make(map[string]*snap.PlugInfo) } - plug := &Plug{PlugInfo: plugInfo} - r.plugs[snapName][plugName] = plug + r.plugs[snapName][plugName] = plugInfo } for slotName, slotInfo := range snapInfo.Slots { @@ -860,10 +837,9 @@ continue } if r.slots[snapName] == nil { - r.slots[snapName] = make(map[string]*Slot) + r.slots[snapName] = make(map[string]*snap.SlotInfo) } - slot := &Slot{SlotInfo: slotInfo} - r.slots[snapName][slotName] = slot + r.slots[snapName][slotName] = slotInfo } return nil } @@ -881,12 +857,12 @@ defer r.m.Unlock() for plugName, plug := range r.plugs[snapName] { - if len(plug.Connections) > 0 { + if len(r.plugSlots[plug]) > 0 { return fmt.Errorf("cannot remove connected plug %s.%s", snapName, plugName) } } for slotName, slot := range r.slots[snapName] { - if len(slot.Connections) > 0 { + if len(r.slotPlugs[slot]) > 0 { return fmt.Errorf("cannot remove connected slot %s.%s", snapName, slotName) } } @@ -938,30 +914,33 @@ // AutoConnectCandidateSlots finds and returns viable auto-connection candidates // for a given plug. -func (r *Repository) AutoConnectCandidateSlots(plugSnapName, plugName string, policyCheck func(*Plug, *Slot) bool) []*Slot { +func (r *Repository) AutoConnectCandidateSlots(plugSnapName, plugName string, policyCheck func(*Plug, *Slot) bool) []*snap.SlotInfo { r.m.Lock() defer r.m.Unlock() - plug := r.plugs[plugSnapName][plugName] - if plug == nil { + plugInfo := r.plugs[plugSnapName][plugName] + if plugInfo == nil { return nil } - var candidates []*Slot + var candidates []*snap.SlotInfo for _, slotsForSnap := range r.slots { - for _, slot := range slotsForSnap { - if slot.Interface != plug.Interface { + for _, slotInfo := range slotsForSnap { + if slotInfo.Interface != plugInfo.Interface { continue } - iface := slot.Interface + iface := slotInfo.Interface + // FIXME: use ConnectedPlug/Slot for AutoConnect (once it's refactored to use tasks). + plug := &Plug{PlugInfo: plugInfo} + slot := &Slot{SlotInfo: slotInfo} // declaration based checks disallow if !policyCheck(plug, slot) { continue } if r.ifaces[iface].AutoConnect(plug, slot) { - candidates = append(candidates, slot) + candidates = append(candidates, slotInfo) } } } @@ -970,30 +949,33 @@ // AutoConnectCandidatePlugs finds and returns viable auto-connection candidates // for a given slot. -func (r *Repository) AutoConnectCandidatePlugs(slotSnapName, slotName string, policyCheck func(*Plug, *Slot) bool) []*Plug { +func (r *Repository) AutoConnectCandidatePlugs(slotSnapName, slotName string, policyCheck func(*Plug, *Slot) bool) []*snap.PlugInfo { r.m.Lock() defer r.m.Unlock() - slot := r.slots[slotSnapName][slotName] - if slot == nil { + slotInfo := r.slots[slotSnapName][slotName] + if slotInfo == nil { return nil } - var candidates []*Plug + var candidates []*snap.PlugInfo for _, plugsForSnap := range r.plugs { - for _, plug := range plugsForSnap { - if slot.Interface != plug.Interface { + for _, plugInfo := range plugsForSnap { + if slotInfo.Interface != plugInfo.Interface { continue } - iface := slot.Interface + iface := slotInfo.Interface + // FIXME: use ConnectedPlug/Slot for AutoConnect (once it's refactored to use tasks). + plug := &Plug{PlugInfo: plugInfo} + slot := &Slot{SlotInfo: slotInfo} // declaration based checks disallow if !policyCheck(plug, slot) { continue } if r.ifaces[iface].AutoConnect(plug, slot) { - candidates = append(candidates, plug) + candidates = append(candidates, plugInfo) } } } diff -Nru snapd-2.29.4.2+17.10/interfaces/repo_test.go snapd-2.31.1+17.10/interfaces/repo_test.go --- snapd-2.29.4.2+17.10/interfaces/repo_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/repo_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -33,8 +33,8 @@ type RepositorySuite struct { iface Interface - plug *Plug - slot *Slot + plug *snap.PlugInfo + slot *snap.SlotInfo coreSnap *snap.Info emptyRepo *Repository // Repository pre-populated with s.iface @@ -60,7 +60,7 @@ label: label attr: value `, nil) - s.plug = &Plug{PlugInfo: consumer.Plugs["plug"]} + s.plug = consumer.Plugs["plug"] producer := snaptest.MockInfo(c, ` name: producer apps: @@ -73,7 +73,7 @@ label: label attr: value `, nil) - s.slot = &Slot{SlotInfo: producer.Slots["slot"]} + s.slot = producer.Slots["slot"] // NOTE: The core snap has a slot so that it shows up in the // repository. The repository doesn't record snaps unless they // have at least one interface. @@ -96,11 +96,11 @@ info := snaptest.MockInfo(c, yaml, nil) result[i] = info for _, plugInfo := range info.Plugs { - err := repo.AddPlug(&Plug{PlugInfo: plugInfo}) + err := repo.AddPlug(plugInfo) c.Assert(err, IsNil) } for _, slotInfo := range info.Slots { - err := repo.AddSlot(&Slot{SlotInfo: slotInfo}) + err := repo.AddSlot(slotInfo) c.Assert(err, IsNil) } } @@ -201,16 +201,16 @@ func (s *RepositorySuite) TestAddPlugClashingSlot(c *C) { snapInfo := &snap.Info{SuggestedName: "snap"} - plug := &Plug{PlugInfo: &snap.PlugInfo{ + plug := &snap.PlugInfo{ Snap: snapInfo, Name: "clashing", Interface: "interface", - }} - slot := &Slot{SlotInfo: &snap.SlotInfo{ + } + slot := &snap.SlotInfo{ Snap: snapInfo, Name: "clashing", Interface: "interface", - }} + } err := s.testRepo.AddSlot(slot) c.Assert(err, IsNil) err = s.testRepo.AddPlug(plug) @@ -220,12 +220,10 @@ } func (s *RepositorySuite) TestAddPlugFailsWithInvalidSnapName(c *C) { - plug := &Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{SuggestedName: "bad-snap-"}, - Name: "interface", - Interface: "interface", - }, + plug := &snap.PlugInfo{ + Snap: &snap.Info{SuggestedName: "bad-snap-"}, + Name: "interface", + Interface: "interface", } err := s.testRepo.AddPlug(plug) c.Assert(err, ErrorMatches, `invalid snap name: "bad-snap-"`) @@ -233,12 +231,10 @@ } func (s *RepositorySuite) TestAddPlugFailsWithInvalidPlugName(c *C) { - plug := &Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{SuggestedName: "snap"}, - Name: "bad-name-", - Interface: "interface", - }, + plug := &snap.PlugInfo{ + Snap: &snap.Info{SuggestedName: "snap"}, + Name: "bad-name-", + Interface: "interface", } err := s.testRepo.AddPlug(plug) c.Assert(err, ErrorMatches, `invalid interface name: "bad-name-"`) @@ -251,20 +247,6 @@ c.Assert(s.emptyRepo.AllPlugs(""), HasLen, 0) } -func (s *RepositorySuite) TestAddPlugFailsWithUnsanitizedPlug(c *C) { - iface := &ifacetest.TestInterface{ - InterfaceName: "interface", - SanitizePlugCallback: func(plug *Plug) error { - return fmt.Errorf("plug is dirty") - }, - } - err := s.emptyRepo.AddInterface(iface) - c.Assert(err, IsNil) - err = s.emptyRepo.AddPlug(s.plug) - c.Assert(err, ErrorMatches, "cannot add plug: plug is dirty") - c.Assert(s.emptyRepo.AllPlugs(""), HasLen, 0) -} - // Tests for Repository.Plug() func (s *RepositorySuite) TestPlug(c *C) { @@ -317,8 +299,8 @@ c.Assert(err, IsNil) err = s.testRepo.AddSlot(s.slot) c.Assert(err, IsNil) - connRef := ConnRef{PlugRef: s.plug.Ref(), SlotRef: s.slot.Ref()} - err = s.testRepo.Connect(connRef) + connRef := NewConnRef(s.plug, s.slot) + err = s.testRepo.Connect(*connRef) c.Assert(err, IsNil) // Removing a plug used by a slot returns an appropriate error err = s.testRepo.RemovePlug(s.plug.Snap.Name(), s.plug.Name) @@ -343,11 +325,11 @@ name-c: interface `) // The result is sorted by snap and name - c.Assert(s.testRepo.AllPlugs(""), DeepEquals, []*Plug{ - {PlugInfo: snaps[0].Plugs["name-a"]}, - {PlugInfo: snaps[1].Plugs["name-a"]}, - {PlugInfo: snaps[1].Plugs["name-b"]}, - {PlugInfo: snaps[1].Plugs["name-c"]}, + c.Assert(s.testRepo.AllPlugs(""), DeepEquals, []*snap.PlugInfo{ + snaps[0].Plugs["name-a"], + snaps[1].Plugs["name-a"], + snaps[1].Plugs["name-b"], + snaps[1].Plugs["name-c"], }) } @@ -366,9 +348,7 @@ name-b: other-interface name-c: interface `) - c.Assert(s.testRepo.AllPlugs("other-interface"), DeepEquals, []*Plug{ - {PlugInfo: snaps[1].Plugs["name-b"]}, - }) + c.Assert(s.testRepo.AllPlugs("other-interface"), DeepEquals, []*snap.PlugInfo{snaps[1].Plugs["name-b"]}) } // Tests for Repository.Plugs() @@ -386,10 +366,10 @@ name-c: interface `) // The result is sorted by snap and name - c.Assert(s.testRepo.Plugs("snap-b"), DeepEquals, []*Plug{ - {PlugInfo: snaps[1].Plugs["name-a"]}, - {PlugInfo: snaps[1].Plugs["name-b"]}, - {PlugInfo: snaps[1].Plugs["name-c"]}, + c.Assert(s.testRepo.Plugs("snap-b"), DeepEquals, []*snap.PlugInfo{ + snaps[1].Plugs["name-a"], + snaps[1].Plugs["name-b"], + snaps[1].Plugs["name-c"], }) // The result is empty if the snap is not known c.Assert(s.testRepo.Plugs("snap-x"), HasLen, 0) @@ -411,14 +391,14 @@ name-a: other-interface `) // AllSlots("") returns all slots, sorted by snap and slot name - c.Assert(s.testRepo.AllSlots(""), DeepEquals, []*Slot{ - {SlotInfo: snaps[0].Slots["name-a"]}, - {SlotInfo: snaps[0].Slots["name-b"]}, - {SlotInfo: snaps[1].Slots["name-a"]}, + c.Assert(s.testRepo.AllSlots(""), DeepEquals, []*snap.SlotInfo{ + snaps[0].Slots["name-a"], + snaps[0].Slots["name-b"], + snaps[1].Slots["name-a"], }) // AllSlots("") returns all slots, sorted by snap and slot name - c.Assert(s.testRepo.AllSlots("other-interface"), DeepEquals, []*Slot{ - {SlotInfo: snaps[1].Slots["name-a"]}, + c.Assert(s.testRepo.AllSlots("other-interface"), DeepEquals, []*snap.SlotInfo{ + snaps[1].Slots["name-a"], }) } @@ -436,13 +416,13 @@ name-a: interface `) // Slots("snap-a") returns slots present in that snap - c.Assert(s.testRepo.Slots("snap-a"), DeepEquals, []*Slot{ - {SlotInfo: snaps[0].Slots["name-a"]}, - {SlotInfo: snaps[0].Slots["name-b"]}, + c.Assert(s.testRepo.Slots("snap-a"), DeepEquals, []*snap.SlotInfo{ + snaps[0].Slots["name-a"], + snaps[0].Slots["name-b"], }) // Slots("snap-b") returns slots present in that snap - c.Assert(s.testRepo.Slots("snap-b"), DeepEquals, []*Slot{ - {SlotInfo: snaps[1].Slots["name-a"]}, + c.Assert(s.testRepo.Slots("snap-b"), DeepEquals, []*snap.SlotInfo{ + snaps[1].Slots["name-a"], }) // Slots("snap-c") returns no slots (because that snap doesn't exist) c.Assert(s.testRepo.Slots("snap-c"), HasLen, 0) @@ -472,12 +452,10 @@ } func (s *RepositorySuite) TestAddSlotFailsWhenSlotNameIsInvalid(c *C) { - slot := &Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "snap"}, - Name: "bad-name-", - Interface: "interface", - }, + slot := &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "snap"}, + Name: "bad-name-", + Interface: "interface", } err := s.emptyRepo.AddSlot(slot) c.Assert(err, ErrorMatches, `invalid interface name: "bad-name-"`) @@ -485,12 +463,10 @@ } func (s *RepositorySuite) TestAddSlotFailsWithInvalidSnapName(c *C) { - slot := &Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "bad-snap-"}, - Name: "slot", - Interface: "interface", - }, + slot := &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "bad-snap-"}, + Name: "slot", + Interface: "interface", } err := s.emptyRepo.AddSlot(slot) c.Assert(err, ErrorMatches, `invalid snap name: "bad-snap-"`) @@ -508,16 +484,16 @@ func (s *RepositorySuite) TestAddSlotClashingPlug(c *C) { snapInfo := &snap.Info{SuggestedName: "snap"} - plug := &Plug{PlugInfo: &snap.PlugInfo{ + plug := &snap.PlugInfo{ Snap: snapInfo, Name: "clashing", Interface: "interface", - }} - slot := &Slot{SlotInfo: &snap.SlotInfo{ + } + slot := &snap.SlotInfo{ Snap: snapInfo, Name: "clashing", Interface: "interface", - }} + } err := s.testRepo.AddPlug(plug) c.Assert(err, IsNil) err = s.testRepo.AddSlot(slot) @@ -526,20 +502,6 @@ c.Assert(s.testRepo.Plug(plug.Snap.Name(), plug.Name), DeepEquals, plug) } -func (s *RepositorySuite) TestAddSlotFailsWithUnsanitizedSlot(c *C) { - iface := &ifacetest.TestInterface{ - InterfaceName: "interface", - SanitizeSlotCallback: func(slot *Slot) error { - return fmt.Errorf("slot is dirty") - }, - } - err := s.emptyRepo.AddInterface(iface) - c.Assert(err, IsNil) - err = s.emptyRepo.AddSlot(s.slot) - c.Assert(err, ErrorMatches, "cannot add slot: slot is dirty") - c.Assert(s.emptyRepo.AllSlots(""), HasLen, 0) -} - func (s *RepositorySuite) TestAddSlotStoresCorrectData(c *C) { err := s.testRepo.AddSlot(s.slot) c.Assert(err, IsNil) @@ -573,8 +535,8 @@ c.Assert(err, IsNil) err = s.testRepo.AddSlot(s.slot) c.Assert(err, IsNil) - connRef := ConnRef{PlugRef: s.plug.Ref(), SlotRef: s.slot.Ref()} - err = s.testRepo.Connect(connRef) + connRef := NewConnRef(s.plug, s.slot) + err = s.testRepo.Connect(*connRef) c.Assert(err, IsNil) // Removing a slot occupied by a plug returns an appropriate error err = s.testRepo.RemoveSlot(s.slot.Snap.Name(), s.slot.Name) @@ -744,12 +706,10 @@ // Plug and slot must have matching types func (s *RepositorySuite) TestResolveIncompatibleTypes(c *C) { c.Assert(s.testRepo.AddInterface(&ifacetest.TestInterface{InterfaceName: "other-interface"}), IsNil) - plug := &Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{SuggestedName: "consumer"}, - Name: "plug", - Interface: "other-interface", - }, + plug := &snap.PlugInfo{ + Snap: &snap.Info{SuggestedName: "consumer"}, + Name: "plug", + Interface: "other-interface", } c.Assert(s.testRepo.AddPlug(plug), IsNil) c.Assert(s.testRepo.AddSlot(s.slot), IsNil) @@ -987,8 +947,8 @@ c.Assert(s.testRepo.AddSnap(s.coreSnap), IsNil) c.Assert(s.testRepo.AddPlug(s.plug), IsNil) c.Assert(s.testRepo.AddSlot(s.slot), IsNil) - connRef := ConnRef{PlugRef: s.plug.Ref(), SlotRef: s.slot.Ref()} - c.Assert(s.testRepo.Connect(connRef), IsNil) + connRef := NewConnRef(s.plug, s.slot) + c.Assert(s.testRepo.Connect(*connRef), IsNil) scenarios := []struct { plugSnapName, plugName, slotSnapName, slotName string @@ -1057,7 +1017,7 @@ c.Check(connRefList, HasLen, 0) } else { c.Check(err, IsNil) - c.Check(connRefList, DeepEquals, []ConnRef{connRef}) + c.Check(connRefList, DeepEquals, []ConnRef{*connRef}) } } } @@ -1068,8 +1028,8 @@ err := s.testRepo.AddSlot(s.slot) c.Assert(err, IsNil) // Connecting an unknown plug returns an appropriate error - connRef := ConnRef{PlugRef: s.plug.Ref(), SlotRef: s.slot.Ref()} - err = s.testRepo.Connect(connRef) + connRef := NewConnRef(s.plug, s.slot) + err = s.testRepo.Connect(*connRef) c.Assert(err, ErrorMatches, `cannot connect plug "plug" from snap "consumer", no such plug`) } @@ -1077,8 +1037,8 @@ err := s.testRepo.AddPlug(s.plug) c.Assert(err, IsNil) // Connecting to an unknown slot returns an error - connRef := ConnRef{PlugRef: s.plug.Ref(), SlotRef: s.slot.Ref()} - err = s.testRepo.Connect(connRef) + connRef := NewConnRef(s.plug, s.slot) + err = s.testRepo.Connect(*connRef) c.Assert(err, ErrorMatches, `cannot connect plug to slot "slot" from snap "producer", no such slot`) } @@ -1087,34 +1047,27 @@ c.Assert(err, IsNil) err = s.testRepo.AddSlot(s.slot) c.Assert(err, IsNil) - connRef := ConnRef{PlugRef: s.plug.Ref(), SlotRef: s.slot.Ref()} - err = s.testRepo.Connect(connRef) + connRef := NewConnRef(s.plug, s.slot) + err = s.testRepo.Connect(*connRef) c.Assert(err, IsNil) // Connecting exactly the same thing twice succeeds without an error but does nothing. - err = s.testRepo.Connect(connRef) + err = s.testRepo.Connect(*connRef) c.Assert(err, IsNil) // Only one connection is actually present. c.Assert(s.testRepo.Interfaces(), DeepEquals, &Interfaces{ - Plugs: []*Plug{{ - PlugInfo: s.plug.PlugInfo, - Connections: []SlotRef{{Snap: s.slot.Snap.Name(), Name: s.slot.Name}}, - }}, - Slots: []*Slot{{ - SlotInfo: s.slot.SlotInfo, - Connections: []PlugRef{{Snap: s.plug.Snap.Name(), Name: s.plug.Name}}, - }}, + Plugs: []*snap.PlugInfo{s.plug}, + Slots: []*snap.SlotInfo{s.slot}, + Connections: []*ConnRef{NewConnRef(s.plug, s.slot)}, }) } func (s *RepositorySuite) TestConnectFailsWhenSlotAndPlugAreIncompatible(c *C) { otherInterface := &ifacetest.TestInterface{InterfaceName: "other-interface"} err := s.testRepo.AddInterface(otherInterface) - plug := &Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{SuggestedName: "consumer"}, - Name: "plug", - Interface: "other-interface", - }, + plug := &snap.PlugInfo{ + Snap: &snap.Info{SuggestedName: "consumer"}, + Name: "plug", + Interface: "other-interface", } c.Assert(err, IsNil) err = s.testRepo.AddPlug(plug) @@ -1122,8 +1075,8 @@ err = s.testRepo.AddSlot(s.slot) c.Assert(err, IsNil) // Connecting a plug to an incompatible slot fails with an appropriate error - connRef := ConnRef{PlugRef: s.plug.Ref(), SlotRef: s.slot.Ref()} - err = s.testRepo.Connect(connRef) + connRef := NewConnRef(s.plug, s.slot) + err = s.testRepo.Connect(*connRef) c.Assert(err, ErrorMatches, `cannot connect plug "consumer:plug" \(interface "other-interface"\) to "producer:slot" \(interface "interface"\)`) } @@ -1133,8 +1086,8 @@ err = s.testRepo.AddSlot(s.slot) c.Assert(err, IsNil) // Connecting a plug works okay - connRef := ConnRef{PlugRef: s.plug.Ref(), SlotRef: s.slot.Ref()} - err = s.testRepo.Connect(connRef) + connRef := NewConnRef(s.plug, s.slot) + err = s.testRepo.Connect(*connRef) c.Assert(err, IsNil) } @@ -1178,12 +1131,12 @@ func (s *RepositorySuite) TestDisconnectSucceeds(c *C) { c.Assert(s.testRepo.AddPlug(s.plug), IsNil) c.Assert(s.testRepo.AddSlot(s.slot), IsNil) - c.Assert(s.testRepo.Connect(ConnRef{PlugRef: s.plug.Ref(), SlotRef: s.slot.Ref()}), IsNil) + c.Assert(s.testRepo.Connect(*NewConnRef(s.plug, s.slot)), IsNil) err := s.testRepo.Disconnect(s.plug.Snap.Name(), s.plug.Name, s.slot.Snap.Name(), s.slot.Name) c.Assert(err, IsNil) c.Assert(s.testRepo.Interfaces(), DeepEquals, &Interfaces{ - Plugs: []*Plug{{PlugInfo: s.plug.PlugInfo}}, - Slots: []*Slot{{SlotInfo: s.slot.SlotInfo}}, + Plugs: []*snap.PlugInfo{s.plug}, + Slots: []*snap.SlotInfo{s.slot}, }) } @@ -1213,39 +1166,31 @@ func (s *RepositorySuite) TestConnectedFindsConnections(c *C) { c.Assert(s.testRepo.AddPlug(s.plug), IsNil) c.Assert(s.testRepo.AddSlot(s.slot), IsNil) - c.Assert(s.testRepo.Connect(ConnRef{PlugRef: s.plug.Ref(), SlotRef: s.slot.Ref()}), IsNil) + c.Assert(s.testRepo.Connect(*NewConnRef(s.plug, s.slot)), IsNil) conns, err := s.testRepo.Connected(s.plug.Snap.Name(), s.plug.Name) c.Assert(err, IsNil) - c.Check(conns, DeepEquals, []ConnRef{ - {PlugRef: s.plug.Ref(), SlotRef: s.slot.Ref()}, - }) + c.Check(conns, DeepEquals, []ConnRef{*NewConnRef(s.plug, s.slot)}) conns, err = s.testRepo.Connected(s.slot.Snap.Name(), s.slot.Name) c.Assert(err, IsNil) - c.Check(conns, DeepEquals, []ConnRef{ - {PlugRef: s.plug.Ref(), SlotRef: s.slot.Ref()}, - }) + c.Check(conns, DeepEquals, []ConnRef{*NewConnRef(s.plug, s.slot)}) } // Connected uses the core snap if snap name is empty func (s *RepositorySuite) TestConnectedFindsCoreSnap(c *C) { - slot := &Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "slot", - Interface: "interface", - }, + slot := &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, + Name: "slot", + Interface: "interface", } c.Assert(s.testRepo.AddPlug(s.plug), IsNil) c.Assert(s.testRepo.AddSlot(slot), IsNil) - c.Assert(s.testRepo.Connect(ConnRef{PlugRef: s.plug.Ref(), SlotRef: slot.Ref()}), IsNil) + c.Assert(s.testRepo.Connect(*NewConnRef(s.plug, slot)), IsNil) conns, err := s.testRepo.Connected("", s.slot.Name) c.Assert(err, IsNil) - c.Check(conns, DeepEquals, []ConnRef{ - {PlugRef: s.plug.Ref(), SlotRef: slot.Ref()}, - }) + c.Check(conns, DeepEquals, []ConnRef{*NewConnRef(s.plug, slot)}) } // Tests for Repository.DisconnectAll() @@ -1253,13 +1198,13 @@ func (s *RepositorySuite) TestDisconnectAll(c *C) { c.Assert(s.testRepo.AddPlug(s.plug), IsNil) c.Assert(s.testRepo.AddSlot(s.slot), IsNil) - c.Assert(s.testRepo.Connect(ConnRef{PlugRef: s.plug.Ref(), SlotRef: s.slot.Ref()}), IsNil) + c.Assert(s.testRepo.Connect(*NewConnRef(s.plug, s.slot)), IsNil) - conns := []ConnRef{{PlugRef: s.plug.Ref(), SlotRef: s.slot.Ref()}} + conns := []ConnRef{*NewConnRef(s.plug, s.slot)} s.testRepo.DisconnectAll(conns) c.Assert(s.testRepo.Interfaces(), DeepEquals, &Interfaces{ - Plugs: []*Plug{{PlugInfo: s.plug.PlugInfo}}, - Slots: []*Slot{{SlotInfo: s.slot.SlotInfo}}, + Plugs: []*snap.PlugInfo{s.plug}, + Slots: []*snap.SlotInfo{s.slot}, }) } @@ -1271,27 +1216,22 @@ err = s.testRepo.AddSlot(s.slot) c.Assert(err, IsNil) // After connecting the result is as expected - connRef := ConnRef{PlugRef: s.plug.Ref(), SlotRef: s.slot.Ref()} - err = s.testRepo.Connect(connRef) + connRef := NewConnRef(s.plug, s.slot) + err = s.testRepo.Connect(*connRef) c.Assert(err, IsNil) ifaces := s.testRepo.Interfaces() c.Assert(ifaces, DeepEquals, &Interfaces{ - Plugs: []*Plug{{ - PlugInfo: s.plug.PlugInfo, - Connections: []SlotRef{{Snap: s.slot.Snap.Name(), Name: s.slot.Name}}, - }}, - Slots: []*Slot{{ - SlotInfo: s.slot.SlotInfo, - Connections: []PlugRef{{Snap: s.plug.Snap.Name(), Name: s.plug.Name}}, - }}, + Plugs: []*snap.PlugInfo{s.plug}, + Slots: []*snap.SlotInfo{s.slot}, + Connections: []*ConnRef{NewConnRef(s.plug, s.slot)}, }) // After disconnecting the connections become empty err = s.testRepo.Disconnect(s.plug.Snap.Name(), s.plug.Name, s.slot.Snap.Name(), s.slot.Name) c.Assert(err, IsNil) ifaces = s.testRepo.Interfaces() c.Assert(ifaces, DeepEquals, &Interfaces{ - Plugs: []*Plug{{PlugInfo: s.plug.PlugInfo}}, - Slots: []*Slot{{SlotInfo: s.slot.SlotInfo}}, + Plugs: []*snap.PlugInfo{s.plug}, + Slots: []*snap.SlotInfo{s.slot}, }) } @@ -1301,19 +1241,19 @@ var testInterface = &ifacetest.TestInterface{ InterfaceName: "interface", - TestPermanentPlugCallback: func(spec *ifacetest.Specification, plug *Plug) error { + TestPermanentPlugCallback: func(spec *ifacetest.Specification, plug *snap.PlugInfo) error { spec.AddSnippet("static plug snippet") return nil }, - TestConnectedPlugCallback: func(spec *ifacetest.Specification, plug *Plug, plugAttrs map[string]interface{}, slot *Slot, slotAttrs map[string]interface{}) error { + TestConnectedPlugCallback: func(spec *ifacetest.Specification, plug *ConnectedPlug, slot *ConnectedSlot) error { spec.AddSnippet("connection-specific plug snippet") return nil }, - TestPermanentSlotCallback: func(spec *ifacetest.Specification, slot *Slot) error { + TestPermanentSlotCallback: func(spec *ifacetest.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("static slot snippet") return nil }, - TestConnectedSlotCallback: func(spec *ifacetest.Specification, plug *Plug, plugAttrs map[string]interface{}, slot *Slot, slotAttrs map[string]interface{}) error { + TestConnectedSlotCallback: func(spec *ifacetest.Specification, plug *ConnectedPlug, slot *ConnectedSlot) error { spec.AddSnippet("connection-specific slot snippet") return nil }, @@ -1337,8 +1277,8 @@ c.Check(spec.(*ifacetest.Specification).Snippets, DeepEquals, []string{"static slot snippet"}) // Establish connection between plug and slot - connRef := ConnRef{PlugRef: s.plug.Ref(), SlotRef: s.slot.Ref()} - err = repo.Connect(connRef) + connRef := NewConnRef(s.plug, s.slot) + err = repo.Connect(*connRef) c.Assert(err, IsNil) // Snaps should get static and connection-specific security now @@ -1362,10 +1302,10 @@ backend := &ifacetest.TestSecurityBackend{BackendName: testSecurity} iface := &ifacetest.TestInterface{ InterfaceName: "interface", - TestConnectedSlotCallback: func(spec *ifacetest.Specification, plug *Plug, plugAttrs map[string]interface{}, slot *Slot, slotAttrs map[string]interface{}) error { + TestConnectedSlotCallback: func(spec *ifacetest.Specification, plug *ConnectedPlug, slot *ConnectedSlot) error { return fmt.Errorf("cannot compute snippet for provider") }, - TestConnectedPlugCallback: func(spec *ifacetest.Specification, plug *Plug, plugAttrs map[string]interface{}, slot *Slot, slotAttrs map[string]interface{}) error { + TestConnectedPlugCallback: func(spec *ifacetest.Specification, plug *ConnectedPlug, slot *ConnectedSlot) error { return fmt.Errorf("cannot compute snippet for consumer") }, } @@ -1375,8 +1315,8 @@ c.Assert(repo.AddInterface(iface), IsNil) c.Assert(repo.AddPlug(s.plug), IsNil) c.Assert(repo.AddSlot(s.slot), IsNil) - connRef := ConnRef{PlugRef: s.plug.Ref(), SlotRef: s.slot.Ref()} - c.Assert(repo.Connect(connRef), IsNil) + connRef := NewConnRef(s.plug, s.slot) + c.Assert(repo.Connect(*connRef), IsNil) spec, err := repo.SnapSpecification(testSecurity, s.plug.Snap.Name()) c.Assert(err, ErrorMatches, "cannot compute snippet for consumer") @@ -1391,10 +1331,10 @@ var testSecurity SecuritySystem = "security" iface := &ifacetest.TestInterface{ InterfaceName: "interface", - TestPermanentSlotCallback: func(spec *ifacetest.Specification, slot *Slot) error { + TestPermanentSlotCallback: func(spec *ifacetest.Specification, slot *snap.SlotInfo) error { return fmt.Errorf("cannot compute snippet for provider") }, - TestPermanentPlugCallback: func(spec *ifacetest.Specification, plug *Plug) error { + TestPermanentPlugCallback: func(spec *ifacetest.Specification, plug *snap.PlugInfo) error { return fmt.Errorf("cannot compute snippet for consumer") }, } @@ -1404,8 +1344,8 @@ c.Assert(repo.AddInterface(iface), IsNil) c.Assert(repo.AddPlug(s.plug), IsNil) c.Assert(repo.AddSlot(s.slot), IsNil) - connRef := ConnRef{PlugRef: s.plug.Ref(), SlotRef: s.slot.Ref()} - c.Assert(repo.Connect(connRef), IsNil) + connRef := NewConnRef(s.plug, s.slot) + c.Assert(repo.Connect(*connRef), IsNil) spec, err := repo.SnapSpecification(testSecurity, s.plug.Snap.Name()) c.Assert(err, ErrorMatches, "cannot compute snippet for consumer") @@ -1532,9 +1472,9 @@ err := s.repo.AddInterface(&ifacetest.TestInterface{InterfaceName: "iface"}) c.Assert(err, IsNil) err = s.repo.AddInterface(&ifacetest.TestInterface{ - InterfaceName: "invalid", - SanitizePlugCallback: func(plug *Plug) error { return fmt.Errorf("plug is invalid") }, - SanitizeSlotCallback: func(slot *Slot) error { return fmt.Errorf("slot is invalid") }, + InterfaceName: "invalid", + BeforePreparePlugCallback: func(plug *snap.PlugInfo) error { return fmt.Errorf("plug is invalid") }, + BeforePrepareSlotCallback: func(slot *snap.SlotInfo) error { return fmt.Errorf("slot is invalid") }, }) c.Assert(err, IsNil) } diff -Nru snapd-2.29.4.2+17.10/interfaces/seccomp/backend.go snapd-2.31.1+17.10/interfaces/seccomp/backend.go --- snapd-2.29.4.2+17.10/interfaces/seccomp/backend.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/seccomp/backend.go 2017-12-01 15:51:55.000000000 +0000 @@ -73,6 +73,11 @@ // Backend is responsible for maintaining seccomp profiles for ubuntu-core-launcher. type Backend struct{} +// Initialize does nothing. +func (b *Backend) Initialize() error { + return nil +} + // Name returns the name of the backend. func (b *Backend) Name() interfaces.SecuritySystem { return interfaces.SecuritySecComp diff -Nru snapd-2.29.4.2+17.10/interfaces/seccomp/backend_test.go snapd-2.31.1+17.10/interfaces/seccomp/backend_test.go --- snapd-2.29.4.2+17.10/interfaces/seccomp/backend_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/seccomp/backend_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -31,6 +31,7 @@ "github.com/snapcore/snapd/interfaces/ifacetest" "github.com/snapcore/snapd/interfaces/seccomp" "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap/snaptest" "github.com/snapcore/snapd/testutil" ) @@ -267,7 +268,7 @@ restore = seccomp.MockTemplate([]byte("default\n")) defer restore() for _, scenario := range combineSnippetsScenarios { - s.Iface.SecCompPermanentSlotCallback = func(spec *seccomp.Specification, slot *interfaces.Slot) error { + s.Iface.SecCompPermanentSlotCallback = func(spec *seccomp.Specification, slot *snap.SlotInfo) error { if scenario.snippet != "" { spec.AddSnippet(scenario.snippet) } @@ -307,11 +308,11 @@ iface2 := &ifacetest.TestInterface{InterfaceName: "iface2"} s.Repo.AddInterface(iface2) - s.Iface.SecCompPermanentSlotCallback = func(spec *seccomp.Specification, slot *interfaces.Slot) error { + s.Iface.SecCompPermanentSlotCallback = func(spec *seccomp.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("zzz") return nil } - iface2.SecCompPermanentSlotCallback = func(spec *seccomp.Specification, slot *interfaces.Slot) error { + iface2.SecCompPermanentSlotCallback = func(spec *seccomp.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("aaa") return nil } diff -Nru snapd-2.29.4.2+17.10/interfaces/seccomp/spec.go snapd-2.31.1+17.10/interfaces/seccomp/spec.go --- snapd-2.29.4.2+17.10/interfaces/seccomp/spec.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/seccomp/spec.go 2018-01-24 20:02:44.000000000 +0000 @@ -24,6 +24,7 @@ "sort" "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/snap" ) // Specification keeps all the seccomp snippets. @@ -84,35 +85,35 @@ // Implementation of methods required by interfaces.Specification // AddConnectedPlug records seccomp-specific side-effects of having a connected plug. -func (spec *Specification) AddConnectedPlug(iface interfaces.Interface, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (spec *Specification) AddConnectedPlug(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { type definer interface { - SecCompConnectedPlug(spec *Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + SecCompConnectedPlug(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } if iface, ok := iface.(definer); ok { spec.securityTags = plug.SecurityTags() defer func() { spec.securityTags = nil }() - return iface.SecCompConnectedPlug(spec, plug, plugAttrs, slot, slotAttrs) + return iface.SecCompConnectedPlug(spec, plug, slot) } return nil } // AddConnectedSlot records seccomp-specific side-effects of having a connected slot. -func (spec *Specification) AddConnectedSlot(iface interfaces.Interface, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (spec *Specification) AddConnectedSlot(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { type definer interface { - SecCompConnectedSlot(spec *Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + SecCompConnectedSlot(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } if iface, ok := iface.(definer); ok { spec.securityTags = slot.SecurityTags() defer func() { spec.securityTags = nil }() - return iface.SecCompConnectedSlot(spec, plug, plugAttrs, slot, slotAttrs) + return iface.SecCompConnectedSlot(spec, plug, slot) } return nil } // AddPermanentPlug records seccomp-specific side-effects of having a plug. -func (spec *Specification) AddPermanentPlug(iface interfaces.Interface, plug *interfaces.Plug) error { +func (spec *Specification) AddPermanentPlug(iface interfaces.Interface, plug *snap.PlugInfo) error { type definer interface { - SecCompPermanentPlug(spec *Specification, plug *interfaces.Plug) error + SecCompPermanentPlug(spec *Specification, plug *snap.PlugInfo) error } if iface, ok := iface.(definer); ok { spec.securityTags = plug.SecurityTags() @@ -123,9 +124,9 @@ } // AddPermanentSlot records seccomp-specific side-effects of having a slot. -func (spec *Specification) AddPermanentSlot(iface interfaces.Interface, slot *interfaces.Slot) error { +func (spec *Specification) AddPermanentSlot(iface interfaces.Interface, slot *snap.SlotInfo) error { type definer interface { - SecCompPermanentSlot(spec *Specification, slot *interfaces.Slot) error + SecCompPermanentSlot(spec *Specification, slot *snap.SlotInfo) error } if iface, ok := iface.(definer); ok { spec.securityTags = slot.SecurityTags() diff -Nru snapd-2.29.4.2+17.10/interfaces/seccomp/spec_test.go snapd-2.31.1+17.10/interfaces/seccomp/spec_test.go --- snapd-2.29.4.2+17.10/interfaces/seccomp/spec_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/seccomp/spec_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -29,71 +29,71 @@ ) type specSuite struct { - iface *ifacetest.TestInterface - spec *seccomp.Specification - plug *interfaces.Plug - slot *interfaces.Slot + iface *ifacetest.TestInterface + spec *seccomp.Specification + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot } var _ = Suite(&specSuite{ iface: &ifacetest.TestInterface{ InterfaceName: "test", - SecCompConnectedPlugCallback: func(spec *seccomp.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { + SecCompConnectedPlugCallback: func(spec *seccomp.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { spec.AddSnippet("connected-plug") return nil }, - SecCompConnectedSlotCallback: func(spec *seccomp.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { + SecCompConnectedSlotCallback: func(spec *seccomp.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { spec.AddSnippet("connected-slot") return nil }, - SecCompPermanentPlugCallback: func(spec *seccomp.Specification, plug *interfaces.Plug) error { + SecCompPermanentPlugCallback: func(spec *seccomp.Specification, plug *snap.PlugInfo) error { spec.AddSnippet("permanent-plug") return nil }, - SecCompPermanentSlotCallback: func(spec *seccomp.Specification, slot *interfaces.Slot) error { + SecCompPermanentSlotCallback: func(spec *seccomp.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("permanent-slot") return nil }, }, - plug: &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{SuggestedName: "snap1"}, - Name: "name", - Interface: "test", - Apps: map[string]*snap.AppInfo{ - "app1": { - Snap: &snap.Info{ - SuggestedName: "snap1", - }, - Name: "app1"}}, - }, + plugInfo: &snap.PlugInfo{ + Snap: &snap.Info{SuggestedName: "snap1"}, + Name: "name", + Interface: "test", + Apps: map[string]*snap.AppInfo{ + "app1": { + Snap: &snap.Info{ + SuggestedName: "snap1", + }, + Name: "app1"}}, }, - slot: &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "snap2"}, - Name: "name", - Interface: "test", - Apps: map[string]*snap.AppInfo{ - "app2": { - Snap: &snap.Info{ - SuggestedName: "snap2", - }, - Name: "app2"}}, - }, + slotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "snap2"}, + Name: "name", + Interface: "test", + Apps: map[string]*snap.AppInfo{ + "app2": { + Snap: &snap.Info{ + SuggestedName: "snap2", + }, + Name: "app2"}}, }, }) func (s *specSuite) SetUpTest(c *C) { s.spec = &seccomp.Specification{} + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) } // The spec.Specification can be used through the interfaces.Specification interface func (s *specSuite) TestSpecificationIface(c *C) { var r interfaces.Specification = s.spec - c.Assert(r.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(r.AddConnectedSlot(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(r.AddPermanentPlug(s.iface, s.plug), IsNil) - c.Assert(r.AddPermanentSlot(s.iface, s.slot), IsNil) + c.Assert(r.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(r.AddConnectedSlot(s.iface, s.plug, s.slot), IsNil) + c.Assert(r.AddPermanentPlug(s.iface, s.plugInfo), IsNil) + c.Assert(r.AddPermanentSlot(s.iface, s.slotInfo), IsNil) c.Assert(s.spec.Snippets(), DeepEquals, map[string][]string{ "snap.snap1.app1": {"connected-plug", "permanent-plug"}, "snap.snap2.app2": {"connected-slot", "permanent-slot"}, diff -Nru snapd-2.29.4.2+17.10/interfaces/seccomp/template.go snapd-2.31.1+17.10/interfaces/seccomp/template.go --- snapd-2.29.4.2+17.10/interfaces/seccomp/template.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/seccomp/template.go 2018-02-16 20:27:57.000000000 +0000 @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2016-2017 Canonical Ltd + * Copyright (C) 2016-2018 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 @@ -73,6 +73,7 @@ chown32 - u:root g:root fchown - u:root g:root fchown32 - u:root g:root +fchownat - - u:root g:root lchown - u:root g:root lchown32 - u:root g:root @@ -395,6 +396,7 @@ shmctl shmdt shmget +shutdown signal sigaction signalfd @@ -450,6 +452,11 @@ socket AF_MPLS socket AF_IB +# For usrsctp, AppArmor doesn't support 'network conn,' since AF_CONN is +# userspace and encapsulated in other domains that are mediated. As such, do +# not allow AF_CONN by default here. +# socket AF_CONN + # For AF_NETLINK, we'll use a combination of AppArmor coarse mediation and # seccomp arg filtering of netlink families. # socket AF_NETLINK - - diff -Nru snapd-2.29.4.2+17.10/interfaces/sorting.go snapd-2.31.1+17.10/interfaces/sorting.go --- snapd-2.29.4.2+17.10/interfaces/sorting.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/sorting.go 2017-12-01 15:51:55.000000000 +0000 @@ -25,6 +25,23 @@ "github.com/snapcore/snapd/snap" ) +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 +} + type bySlotRef []SlotRef func (c bySlotRef) Len() int { return len(c) } @@ -47,7 +64,7 @@ return c[i].Name < c[j].Name } -type byPlugSnapAndName []*Plug +type byPlugSnapAndName []*snap.PlugInfo func (c byPlugSnapAndName) Len() int { return len(c) } func (c byPlugSnapAndName) Swap(i, j int) { c[i], c[j] = c[j], c[i] } @@ -58,7 +75,7 @@ return c[i].Name < c[j].Name } -type bySlotSnapAndName []*Slot +type bySlotSnapAndName []*snap.SlotInfo func (c bySlotSnapAndName) Len() int { return len(c) } func (c bySlotSnapAndName) Swap(i, j int) { c[i], c[j] = c[j], c[i] } @@ -77,7 +94,7 @@ return c[i].Name() < c[j].Name() } -func sortedSnapNamesWithPlugs(m map[string]map[string]*Plug) []string { +func sortedSnapNamesWithPlugs(m map[string]map[string]*snap.PlugInfo) []string { keys := make([]string, 0, len(m)) for key := range m { keys = append(keys, key) @@ -86,7 +103,7 @@ return keys } -func sortedPlugNames(m map[string]*Plug) []string { +func sortedPlugNames(m map[string]*snap.PlugInfo) []string { keys := make([]string, 0, len(m)) for key := range m { keys = append(keys, key) @@ -95,7 +112,7 @@ return keys } -func sortedSnapNamesWithSlots(m map[string]map[string]*Slot) []string { +func sortedSnapNamesWithSlots(m map[string]map[string]*snap.SlotInfo) []string { keys := make([]string, 0, len(m)) for key := range m { keys = append(keys, key) @@ -104,7 +121,7 @@ return keys } -func sortedSlotNames(m map[string]*Slot) []string { +func sortedSlotNames(m map[string]*snap.SlotInfo) []string { keys := make([]string, 0, len(m)) for key := range m { keys = append(keys, key) diff -Nru snapd-2.29.4.2+17.10/interfaces/sorting_test.go snapd-2.31.1+17.10/interfaces/sorting_test.go --- snapd-2.29.4.2+17.10/interfaces/sorting_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/sorting_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -33,6 +33,10 @@ var _ = Suite(&SortingSuite{}) +func newConnRef(plugSnap, plug, slotSnap, slot string) *interfaces.ConnRef { + return &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: plugSnap, Name: plug}, SlotRef: interfaces.SlotRef{Snap: slotSnap, Name: slot}} +} + func (s *SortingSuite) TestSortBySlotRef(c *C) { list := []interfaces.SlotRef{ { @@ -152,3 +156,22 @@ {Snap: &snap.Info{SuggestedName: "name-2"}, Name: "plug-2"}, }) } + +func (s *SortingSuite) TestByConnRef(c *C) { + list := []*interfaces.ConnRef{ + newConnRef("name-1", "plug-3", "name-2", "slot-1"), + newConnRef("name-1", "plug-1", "name-2", "slot-3"), + newConnRef("name-1", "plug-2", "name-2", "slot-2"), + newConnRef("name-1", "plug-1", "name-2", "slot-4"), + newConnRef("name-1", "plug-1", "name-2", "slot-1"), + } + sort.Sort(interfaces.ByConnRef(list)) + + c.Assert(list, DeepEquals, []*interfaces.ConnRef{ + newConnRef("name-1", "plug-1", "name-2", "slot-1"), + newConnRef("name-1", "plug-1", "name-2", "slot-3"), + newConnRef("name-1", "plug-1", "name-2", "slot-4"), + newConnRef("name-1", "plug-2", "name-2", "slot-2"), + newConnRef("name-1", "plug-3", "name-2", "slot-1"), + }) +} diff -Nru snapd-2.29.4.2+17.10/interfaces/systemd/backend.go snapd-2.31.1+17.10/interfaces/systemd/backend.go --- snapd-2.29.4.2+17.10/interfaces/systemd/backend.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/systemd/backend.go 2017-12-01 15:51:55.000000000 +0000 @@ -38,6 +38,11 @@ // Backend is responsible for maintaining apparmor profiles for ubuntu-core-launcher. type Backend struct{} +// Initialize does nothing. +func (b *Backend) Initialize() error { + return nil +} + // Name returns the name of the backend. func (b *Backend) Name() interfaces.SecuritySystem { return interfaces.SecuritySystemd diff -Nru snapd-2.29.4.2+17.10/interfaces/systemd/backend_test.go snapd-2.31.1+17.10/interfaces/systemd/backend_test.go --- snapd-2.29.4.2+17.10/interfaces/systemd/backend_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/systemd/backend_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -29,6 +29,7 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/ifacetest" "github.com/snapcore/snapd/interfaces/systemd" + "github.com/snapcore/snapd/snap" sysd "github.com/snapcore/snapd/systemd" ) @@ -80,7 +81,7 @@ }) defer r() - s.Iface.SystemdPermanentSlotCallback = func(spec *systemd.Specification, slot *interfaces.Slot) error { + s.Iface.SystemdPermanentSlotCallback = func(spec *systemd.Specification, slot *snap.SlotInfo) error { return spec.AddService("snap.samba.interface.foo.service", &systemd.Service{ExecStart: "/bin/true"}) } s.InstallSnap(c, interfaces.ConfinementOptions{}, ifacetest.SambaYamlV1, 1) @@ -99,7 +100,7 @@ } func (s *backendSuite) TestRemovingSnapRemovesAndStopsServices(c *C) { - s.Iface.SystemdPermanentSlotCallback = func(spec *systemd.Specification, slot *interfaces.Slot) error { + s.Iface.SystemdPermanentSlotCallback = func(spec *systemd.Specification, slot *snap.SlotInfo) error { return spec.AddService("snap.samba.interface.foo.service", &systemd.Service{ExecStart: "/bin/true"}) } for _, opts := range testedConfinementOpts { @@ -121,7 +122,7 @@ } func (s *backendSuite) TestSettingUpSecurityWithFewerServices(c *C) { - s.Iface.SystemdPermanentSlotCallback = func(spec *systemd.Specification, slot *interfaces.Slot) error { + s.Iface.SystemdPermanentSlotCallback = func(spec *systemd.Specification, slot *snap.SlotInfo) error { err := spec.AddService("snap.samba.interface.foo.service", &systemd.Service{ExecStart: "/bin/true"}) if err != nil { return err @@ -139,7 +140,7 @@ c.Check(err, IsNil) // Change what the interface returns to simulate some useful change - s.Iface.SystemdPermanentSlotCallback = func(spec *systemd.Specification, slot *interfaces.Slot) error { + s.Iface.SystemdPermanentSlotCallback = func(spec *systemd.Specification, slot *snap.SlotInfo) error { return spec.AddService("snap.samba.interface.foo.service", &systemd.Service{ExecStart: "/bin/true"}) } // Update over to the same snap to regenerate security diff -Nru snapd-2.29.4.2+17.10/interfaces/systemd/spec.go snapd-2.31.1+17.10/interfaces/systemd/spec.go --- snapd-2.29.4.2+17.10/interfaces/systemd/spec.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/systemd/spec.go 2018-01-24 20:02:44.000000000 +0000 @@ -23,6 +23,7 @@ "fmt" "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/snap" ) // Specification assists in collecting custom systemd services associated with an interface. @@ -62,31 +63,31 @@ // Implementation of methods required by interfaces.Specification // AddConnectedPlug records systemd-specific side-effects of having a connected plug. -func (spec *Specification) AddConnectedPlug(iface interfaces.Interface, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (spec *Specification) AddConnectedPlug(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { type definer interface { - SystemdConnectedPlug(spec *Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + SystemdConnectedPlug(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } if iface, ok := iface.(definer); ok { - return iface.SystemdConnectedPlug(spec, plug, plugAttrs, slot, slotAttrs) + return iface.SystemdConnectedPlug(spec, plug, slot) } return nil } // AddConnectedSlot records systemd-specific side-effects of having a connected slot. -func (spec *Specification) AddConnectedSlot(iface interfaces.Interface, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (spec *Specification) AddConnectedSlot(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { type definer interface { - SystemdConnectedSlot(spec *Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + SystemdConnectedSlot(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } if iface, ok := iface.(definer); ok { - return iface.SystemdConnectedSlot(spec, plug, plugAttrs, slot, slotAttrs) + return iface.SystemdConnectedSlot(spec, plug, slot) } return nil } // AddPermanentPlug records systemd-specific side-effects of having a plug. -func (spec *Specification) AddPermanentPlug(iface interfaces.Interface, plug *interfaces.Plug) error { +func (spec *Specification) AddPermanentPlug(iface interfaces.Interface, plug *snap.PlugInfo) error { type definer interface { - SystemdPermanentPlug(spec *Specification, plug *interfaces.Plug) error + SystemdPermanentPlug(spec *Specification, plug *snap.PlugInfo) error } if iface, ok := iface.(definer); ok { return iface.SystemdPermanentPlug(spec, plug) @@ -95,9 +96,9 @@ } // AddPermanentSlot records systemd-specific side-effects of having a slot. -func (spec *Specification) AddPermanentSlot(iface interfaces.Interface, slot *interfaces.Slot) error { +func (spec *Specification) AddPermanentSlot(iface interfaces.Interface, slot *snap.SlotInfo) error { type definer interface { - SystemdPermanentSlot(spec *Specification, slot *interfaces.Slot) error + SystemdPermanentSlot(spec *Specification, slot *snap.SlotInfo) error } if iface, ok := iface.(definer); ok { return iface.SystemdPermanentSlot(spec, slot) diff -Nru snapd-2.29.4.2+17.10/interfaces/udev/backend.go snapd-2.31.1+17.10/interfaces/udev/backend.go --- snapd-2.29.4.2+17.10/interfaces/udev/backend.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/udev/backend.go 2017-12-01 15:51:55.000000000 +0000 @@ -40,6 +40,11 @@ // Backend is responsible for maintaining udev rules. type Backend struct{} +// Initialize does nothing. +func (b *Backend) Initialize() error { + return nil +} + // Name returns the name of the backend. func (b *Backend) Name() interfaces.SecuritySystem { return interfaces.SecurityUDev diff -Nru snapd-2.29.4.2+17.10/interfaces/udev/backend_test.go snapd-2.31.1+17.10/interfaces/udev/backend_test.go --- snapd-2.29.4.2+17.10/interfaces/udev/backend_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/udev/backend_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -85,7 +85,7 @@ func (s *backendSuite) TestInstallingSnapWritesAndLoadsRules(c *C) { // NOTE: Hand out a permanent snippet so that .rules file is generated. - s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *interfaces.Slot) error { + s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("dummy") return nil } @@ -107,11 +107,11 @@ func (s *backendSuite) TestInstallingSnapWithHookWritesAndLoadsRules(c *C) { // NOTE: Hand out a permanent snippet so that .rules file is generated. - s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *interfaces.Slot) error { + s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("dummy") return nil } - s.Iface.UDevPermanentPlugCallback = func(spec *udev.Specification, slot *interfaces.Plug) error { + s.Iface.UDevPermanentPlugCallback = func(spec *udev.Specification, slot *snap.PlugInfo) error { spec.AddSnippet("dummy") return nil } @@ -135,7 +135,7 @@ func (s *backendSuite) TestSecurityIsStable(c *C) { // NOTE: Hand out a permanent snippet so that .rules file is generated. - s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *interfaces.Slot) error { + s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("dummy") return nil } @@ -152,7 +152,7 @@ func (s *backendSuite) TestRemovingSnapRemovesAndReloadsRules(c *C) { // NOTE: Hand out a permanent snippet so that .rules file is generated. - s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *interfaces.Slot) error { + s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("dummy") return nil } @@ -174,7 +174,7 @@ func (s *backendSuite) TestUpdatingSnapToOneWithMoreApps(c *C) { // NOTE: Hand out a permanent snippet so that .rules file is generated. - s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *interfaces.Slot) error { + s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(createSnippetForApps(slot.Apps)) return nil } @@ -197,11 +197,11 @@ func (s *backendSuite) TestUpdatingSnapToOneWithMoreHooks(c *C) { // NOTE: Hand out a permanent snippet so that .rules file is generated. - s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *interfaces.Slot) error { + s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(createSnippetForApps(slot.Apps)) return nil } - s.Iface.UDevPermanentPlugCallback = func(spec *udev.Specification, slot *interfaces.Plug) error { + s.Iface.UDevPermanentPlugCallback = func(spec *udev.Specification, slot *snap.PlugInfo) error { spec.AddSnippet("dummy") return nil } @@ -226,7 +226,7 @@ func (s *backendSuite) TestUpdatingSnapToOneWithFewerApps(c *C) { // NOTE: Hand out a permanent snippet so that .rules file is generated. - s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *interfaces.Slot) error { + s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(createSnippetForApps(slot.Apps)) return nil } @@ -249,11 +249,11 @@ func (s *backendSuite) TestUpdatingSnapToOneWithFewerHooks(c *C) { // NOTE: Hand out a permanent snippet so that .rules file is generated. - s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *interfaces.Slot) error { + s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *snap.SlotInfo) error { spec.AddSnippet(createSnippetForApps(slot.Apps)) return nil } - s.Iface.UDevPermanentPlugCallback = func(spec *udev.Specification, slot *interfaces.Plug) error { + s.Iface.UDevPermanentPlugCallback = func(spec *udev.Specification, slot *snap.PlugInfo) error { spec.AddSnippet("dummy") return nil } @@ -276,7 +276,7 @@ func (s *backendSuite) TestCombineSnippetsWithActualSnippets(c *C) { // NOTE: Hand out a permanent snippet so that .rules file is generated. - s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *interfaces.Slot) error { + s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("dummy") return nil } @@ -299,7 +299,7 @@ func (s *backendSuite) TestCombineSnippetsWithActualSnippetsWithNewline(c *C) { // NOTE: Hand out a permanent snippet so that .rules file is generated. - s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *interfaces.Slot) error { + s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("dummy1\ndummy2") return nil } @@ -321,7 +321,7 @@ } func (s *backendSuite) TestCombineSnippetsWithActualSnippetsWhenPlugNoApps(c *C) { // NOTE: Hand out a permanent snippet so that .rules file is generated. - s.Iface.UDevPermanentPlugCallback = func(spec *udev.Specification, slot *interfaces.Plug) error { + s.Iface.UDevPermanentPlugCallback = func(spec *udev.Specification, slot *snap.PlugInfo) error { spec.AddSnippet("dummy") return nil } @@ -344,7 +344,7 @@ func (s *backendSuite) TestCombineSnippetsWithActualSnippetsWhenSlotNoApps(c *C) { // NOTE: Hand out a permanent snippet so that .rules file is generated. - s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *interfaces.Slot) error { + s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("dummy") return nil } @@ -378,7 +378,7 @@ func (s *backendSuite) TestUpdatingSnapToOneWithoutSlots(c *C) { // NOTE: Hand out a permanent snippet so that .rules file is generated. - s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *interfaces.Slot) error { + s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("dummy") return nil } @@ -401,7 +401,7 @@ func (s *backendSuite) TestUpdatingSnapWithoutSlotsToOneWithoutSlots(c *C) { // NOTE: Hand out a permanent snippet so that .rules file is generated. - s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *interfaces.Slot) error { + s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("dummy") return nil } diff -Nru snapd-2.29.4.2+17.10/interfaces/udev/spec.go snapd-2.31.1+17.10/interfaces/udev/spec.go --- snapd-2.29.4.2+17.10/interfaces/udev/spec.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/udev/spec.go 2018-01-24 20:02:44.000000000 +0000 @@ -25,92 +25,140 @@ "strings" "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/snap" ) +type entry struct { + snippet string + iface string + tag string +} + // Specification assists in collecting udev snippets associated with an interface. type Specification struct { // Snippets are stored in a map for de-duplication - snippets map[string]bool + snippets map[string]bool + entries []entry + iface string + securityTags []string } -// AddSnippet adds a new udev snippet. -func (spec *Specification) AddSnippet(snippet string) { +func (spec *Specification) addEntry(snippet, tag string) { if spec.snippets == nil { spec.snippets = make(map[string]bool) } - spec.snippets[snippet] = true + if !spec.snippets[snippet] { + spec.snippets[snippet] = true + e := entry{ + snippet: snippet, + iface: spec.iface, + tag: tag, + } + spec.entries = append(spec.entries, e) + } +} + +// AddSnippet adds a new udev snippet. +func (spec *Specification) AddSnippet(snippet string) { + spec.addEntry(snippet, "") } func udevTag(securityTag string) string { return strings.Replace(securityTag, ".", "_", -1) } -// TagDevice adds an app/hook specific udev tag to devices described by the snippet. +// TagDevice adds an app/hook specific udev tag to devices described by the +// snippet and adds an app/hook-specific RUN rule for hotplugging. func (spec *Specification) TagDevice(snippet string) { for _, securityTag := range spec.securityTags { - spec.AddSnippet(fmt.Sprintf(`%s, TAG+="%s"`, snippet, udevTag(securityTag))) + tag := udevTag(securityTag) + spec.addEntry(fmt.Sprintf("# %s\n%s, TAG+=\"%s\"", spec.iface, snippet, tag), tag) + spec.addEntry(fmt.Sprintf("TAG==\"%s\", RUN+=\"/lib/udev/snappy-app-dev $env{ACTION} %s $devpath $major:$minor\"", tag, tag), tag) + } +} + +type byTagAndSnippet []entry + +func (c byTagAndSnippet) Len() int { return len(c) } +func (c byTagAndSnippet) Swap(i, j int) { c[i], c[j] = c[j], c[i] } +func (c byTagAndSnippet) Less(i, j int) bool { + if c[i].tag != c[j].tag { + return c[i].tag < c[j].tag } + return c[i].snippet < c[j].snippet } // Snippets returns a copy of all the snippets added so far. func (spec *Specification) Snippets() (result []string) { - for k := range spec.snippets { - result = append(result, k) + entries := make([]entry, len(spec.entries)) + copy(entries, spec.entries) + sort.Sort(byTagAndSnippet(entries)) + + result = make([]string, 0, len(spec.entries)) + for _, entry := range entries { + result = append(result, entry.snippet) } - sort.Strings(result) return result } // Implementation of methods required by interfaces.Specification // AddConnectedPlug records udev-specific side-effects of having a connected plug. -func (spec *Specification) AddConnectedPlug(iface interfaces.Interface, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (spec *Specification) AddConnectedPlug(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { type definer interface { - UDevConnectedPlug(spec *Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + UDevConnectedPlug(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } + ifname := iface.Name() if iface, ok := iface.(definer); ok { spec.securityTags = plug.SecurityTags() - defer func() { spec.securityTags = nil }() - return iface.UDevConnectedPlug(spec, plug, plugAttrs, slot, slotAttrs) + spec.iface = ifname + defer func() { spec.securityTags = nil; spec.iface = "" }() + return iface.UDevConnectedPlug(spec, plug, slot) } return nil } // AddConnectedSlot records mount-specific side-effects of having a connected slot. -func (spec *Specification) AddConnectedSlot(iface interfaces.Interface, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { +func (spec *Specification) AddConnectedSlot(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { type definer interface { - UDevConnectedSlot(spec *Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error + UDevConnectedSlot(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error } + ifname := iface.Name() if iface, ok := iface.(definer); ok { spec.securityTags = slot.SecurityTags() - defer func() { spec.securityTags = nil }() - return iface.UDevConnectedSlot(spec, plug, plugAttrs, slot, slotAttrs) + spec.iface = ifname + defer func() { spec.securityTags = nil; spec.iface = "" }() + return iface.UDevConnectedSlot(spec, plug, slot) } return nil } // AddPermanentPlug records mount-specific side-effects of having a plug. -func (spec *Specification) AddPermanentPlug(iface interfaces.Interface, plug *interfaces.Plug) error { +func (spec *Specification) AddPermanentPlug(iface interfaces.Interface, plug *snap.PlugInfo) error { type definer interface { - UDevPermanentPlug(spec *Specification, plug *interfaces.Plug) error + UDevPermanentPlug(spec *Specification, plug *snap.PlugInfo) error } + ifname := iface.Name() if iface, ok := iface.(definer); ok { spec.securityTags = plug.SecurityTags() - defer func() { spec.securityTags = nil }() + spec.iface = ifname + defer func() { spec.securityTags = nil; spec.iface = "" }() return iface.UDevPermanentPlug(spec, plug) } return nil } // AddPermanentSlot records mount-specific side-effects of having a slot. -func (spec *Specification) AddPermanentSlot(iface interfaces.Interface, slot *interfaces.Slot) error { +func (spec *Specification) AddPermanentSlot(iface interfaces.Interface, slot *snap.SlotInfo) error { type definer interface { - UDevPermanentSlot(spec *Specification, slot *interfaces.Slot) error + UDevPermanentSlot(spec *Specification, slot *snap.SlotInfo) error } + ifname := iface.Name() if iface, ok := iface.(definer); ok { spec.securityTags = slot.SecurityTags() - defer func() { spec.securityTags = nil }() + spec.iface = ifname + defer func() { spec.securityTags = nil; spec.iface = "" }() return iface.UDevPermanentSlot(spec, slot) } return nil diff -Nru snapd-2.29.4.2+17.10/interfaces/udev/spec_test.go snapd-2.31.1+17.10/interfaces/udev/spec_test.go --- snapd-2.29.4.2+17.10/interfaces/udev/spec_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/interfaces/udev/spec_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -25,32 +25,35 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/ifacetest" "github.com/snapcore/snapd/interfaces/udev" + "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap/snaptest" ) type specSuite struct { - iface *ifacetest.TestInterface - spec *udev.Specification - plug *interfaces.Plug - slot *interfaces.Slot + iface *ifacetest.TestInterface + spec *udev.Specification + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot } var _ = Suite(&specSuite{ iface: &ifacetest.TestInterface{ InterfaceName: "test", - UDevConnectedPlugCallback: func(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { + UDevConnectedPlugCallback: func(spec *udev.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { spec.AddSnippet("connected-plug") return nil }, - UDevConnectedSlotCallback: func(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { + UDevConnectedSlotCallback: func(spec *udev.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { spec.AddSnippet("connected-slot") return nil }, - UDevPermanentPlugCallback: func(spec *udev.Specification, plug *interfaces.Plug) error { + UDevPermanentPlugCallback: func(spec *udev.Specification, plug *snap.PlugInfo) error { spec.AddSnippet("permanent-plug") return nil }, - UDevPermanentSlotCallback: func(spec *udev.Specification, slot *interfaces.Slot) error { + UDevPermanentSlotCallback: func(spec *udev.Specification, slot *snap.SlotInfo) error { spec.AddSnippet("permanent-slot") return nil }, @@ -73,8 +76,10 @@ name: interface: test `, nil) - s.plug = &interfaces.Plug{PlugInfo: info1.Plugs["name"]} - s.slot = &interfaces.Slot{SlotInfo: info2.Slots["name"]} + s.plugInfo = info1.Plugs["name"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil) + s.slotInfo = info2.Slots["name"] + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil) } func (s *specSuite) SetUpTest(c *C) { @@ -91,25 +96,43 @@ // affects all of the apps and hooks related to the given plug or slot // (with the exception that slots cannot have hooks). iface := &ifacetest.TestInterface{ - InterfaceName: "test", - UDevConnectedPlugCallback: func(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { + InterfaceName: "iface-1", + UDevConnectedPlugCallback: func(spec *udev.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { spec.TagDevice(`kernel="voodoo"`) return nil }, } - c.Assert(s.spec.AddConnectedPlug(iface, s.plug, nil, s.slot, nil), IsNil) + c.Assert(s.spec.AddConnectedPlug(iface, s.plug, s.slot), IsNil) + + iface = &ifacetest.TestInterface{ + InterfaceName: "iface-2", + UDevConnectedPlugCallback: func(spec *udev.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { + spec.TagDevice(`kernel="hoodoo"`) + return nil + }, + } + c.Assert(s.spec.AddConnectedPlug(iface, s.plug, s.slot), IsNil) + c.Assert(s.spec.Snippets(), DeepEquals, []string{ - `kernel="voodoo", TAG+="snap_snap1_foo"`, - `kernel="voodoo", TAG+="snap_snap1_hook_configure"`, + `# iface-1 +kernel="voodoo", TAG+="snap_snap1_foo"`, + `# iface-2 +kernel="hoodoo", TAG+="snap_snap1_foo"`, + `TAG=="snap_snap1_foo", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_snap1_foo $devpath $major:$minor"`, + `# iface-1 +kernel="voodoo", TAG+="snap_snap1_hook_configure"`, + `# iface-2 +kernel="hoodoo", TAG+="snap_snap1_hook_configure"`, + `TAG=="snap_snap1_hook_configure", RUN+="/lib/udev/snappy-app-dev $env{ACTION} snap_snap1_hook_configure $devpath $major:$minor"`, }) } // The spec.Specification can be used through the interfaces.Specification interface func (s *specSuite) TestSpecificationIface(c *C) { var r interfaces.Specification = s.spec - c.Assert(r.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(r.AddConnectedSlot(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(r.AddPermanentPlug(s.iface, s.plug), IsNil) - c.Assert(r.AddPermanentSlot(s.iface, s.slot), IsNil) + c.Assert(r.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) + c.Assert(r.AddConnectedSlot(s.iface, s.plug, s.slot), IsNil) + c.Assert(r.AddPermanentPlug(s.iface, s.plugInfo), IsNil) + c.Assert(r.AddPermanentSlot(s.iface, s.slotInfo), IsNil) c.Assert(s.spec.Snippets(), DeepEquals, []string{"connected-plug", "connected-slot", "permanent-plug", "permanent-slot"}) } diff -Nru snapd-2.29.4.2+17.10/mdlint.py snapd-2.31.1+17.10/mdlint.py --- snapd-2.29.4.2+17.10/mdlint.py 2016-08-11 17:27:49.000000000 +0000 +++ snapd-2.31.1+17.10/mdlint.py 2018-02-13 15:39:36.000000000 +0000 @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python3 # # see http://daringfireball.net/projects/markdown/syntax # for the "canonical" reference diff -Nru snapd-2.29.4.2+17.10/osutil/export_test.go snapd-2.31.1+17.10/osutil/export_test.go --- snapd-2.29.4.2+17.10/osutil/export_test.go 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/osutil/export_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -26,6 +26,8 @@ "os/user" "syscall" "time" + + "github.com/snapcore/snapd/osutil/sys" ) func MockUserLookup(mock func(name string) (*user.User, error)) func() { @@ -85,7 +87,7 @@ return wr.reader, wr.cmd } -func MockChown(f func(*os.File, int, int) error) func() { +func MockChown(f func(*os.File, sys.UserID, sys.GroupID) error) func() { oldChown := chown chown = f return func() { diff -Nru snapd-2.29.4.2+17.10/osutil/fshelpers.go snapd-2.31.1+17.10/osutil/fshelpers.go --- snapd-2.29.4.2+17.10/osutil/fshelpers.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/osutil/fshelpers.go 2017-12-01 15:51:55.000000000 +0000 @@ -0,0 +1,38 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ +package osutil + +import ( + "os" + "syscall" +) + +// FindGidOwning obtains UNIX group ID and name owning file `path`. +func FindGidOwning(path string) (uint64, error) { + var stat syscall.Stat_t + if err := syscall.Stat(path, &stat); err != nil { + if err == syscall.ENOENT { + return 0, os.ErrNotExist + } + return 0, err + } + + gid := uint64(stat.Gid) + return gid, nil +} diff -Nru snapd-2.29.4.2+17.10/osutil/fshelpers_test.go snapd-2.31.1+17.10/osutil/fshelpers_test.go --- snapd-2.29.4.2+17.10/osutil/fshelpers_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/osutil/fshelpers_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -0,0 +1,51 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package osutil + +import ( + "io/ioutil" + "os" + "path/filepath" + "strconv" + + . "gopkg.in/check.v1" +) + +type groupFindGidOwningSuite struct{} + +var _ = Suite(&groupFindGidOwningSuite{}) + +func (s *groupFindGidOwningSuite) TestSelfOwnedFile(c *C) { + name := filepath.Join(c.MkDir(), "testownedfile") + err := ioutil.WriteFile(name, nil, 0644) + c.Assert(err, IsNil) + + gid, err := FindGidOwning(name) + c.Check(err, IsNil) + + self, err := RealUser() + c.Assert(err, IsNil) + c.Check(strconv.FormatUint(gid, 10), Equals, self.Gid) +} + +func (s *groupFindGidOwningSuite) TestNoOwnedFile(c *C) { + _, err := FindGidOwning("/tmp/filedoesnotexistbutwhy") + c.Assert(err, DeepEquals, os.ErrNotExist) +} diff -Nru snapd-2.29.4.2+17.10/osutil/io.go snapd-2.31.1+17.10/osutil/io.go --- snapd-2.29.4.2+17.10/osutil/io.go 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/osutil/io.go 2018-01-24 20:02:44.000000000 +0000 @@ -27,6 +27,7 @@ "path/filepath" "strings" + "github.com/snapcore/snapd/osutil/sys" "github.com/snapcore/snapd/strutil" ) @@ -53,8 +54,8 @@ target string tmpname string - uid int - gid int + uid sys.UserID + gid sys.GroupID closed bool renamed bool } @@ -75,11 +76,7 @@ // Also note that there are a number of scenarios where Commit fails and then // Cancel also fails. In all these scenarios your filesystem was probably in a // rather poor state. Good luck. -func NewAtomicFile(filename string, perm os.FileMode, flags AtomicWriteFlags, uid, gid int) (aw *AtomicFile, err error) { - if (uid < 0) != (gid < 0) { - return nil, errors.New("internal error: AtomicFile needs none or both of uid and gid set") - } - +func NewAtomicFile(filename string, perm os.FileMode, flags AtomicWriteFlags, uid sys.UserID, gid sys.GroupID) (aw *AtomicFile, err error) { if flags&AtomicWriteFollow != 0 { if fn, err := os.Readlink(filename); err == nil || (fn != "" && os.IsNotExist(err)) { if filepath.IsAbs(fn) { @@ -136,7 +133,9 @@ return e2 } -var chown = (*os.File).Chown +var chown = sys.Chown + +const NoChown = sys.FlagID // Commit the modification; make it permanent. // @@ -144,7 +143,7 @@ // write will fail. If Commit fails, the writer _might_ be closed; // Cancel() needs to be called to clean up. func (aw *AtomicFile) Commit() error { - if aw.uid > -1 && aw.gid > -1 { + if aw.uid != NoChown || aw.gid != NoChown { if err := chown(aw.File, aw.uid, aw.gid); err != nil { return err } @@ -185,27 +184,27 @@ // file created is an AtomicWriter, which is Committed before returning. // // AtomicWriteChown and AtomicWriteFileChown take an uid and a gid that can be -// used to specify the ownership of the created file. They must be both -// non-negative (in which case chown is called), or both negative (in which -// case it isn't). +// used to specify the ownership of the created file. A special value of +// 0xffffffff (math.MaxUint32, or NoChown for convenience) can be used to +// request no change to that attribute. // // AtomicWriteFile and AtomicWriteFileChown take the content to be written as a // []byte, and so work exactly like io.WriteFile(); AtomicWrite and // AtomicWriteChown take an io.Reader which is copied into the file instead, // and so are more amenable to streaming. func AtomicWrite(filename string, reader io.Reader, perm os.FileMode, flags AtomicWriteFlags) (err error) { - return AtomicWriteChown(filename, reader, perm, flags, -1, -1) + return AtomicWriteChown(filename, reader, perm, flags, NoChown, NoChown) } func AtomicWriteFile(filename string, data []byte, perm os.FileMode, flags AtomicWriteFlags) (err error) { - return AtomicWriteChown(filename, bytes.NewReader(data), perm, flags, -1, -1) + return AtomicWriteChown(filename, bytes.NewReader(data), perm, flags, NoChown, NoChown) } -func AtomicWriteFileChown(filename string, data []byte, perm os.FileMode, flags AtomicWriteFlags, uid, gid int) (err error) { +func AtomicWriteFileChown(filename string, data []byte, perm os.FileMode, flags AtomicWriteFlags, uid sys.UserID, gid sys.GroupID) (err error) { return AtomicWriteChown(filename, bytes.NewReader(data), perm, flags, uid, gid) } -func AtomicWriteChown(filename string, reader io.Reader, perm os.FileMode, flags AtomicWriteFlags, uid, gid int) (err error) { +func AtomicWriteChown(filename string, reader io.Reader, perm os.FileMode, flags AtomicWriteFlags, uid sys.UserID, gid sys.GroupID) (err error) { aw, err := NewAtomicFile(filename, perm, flags, uid, gid) if err != nil { return err diff -Nru snapd-2.29.4.2+17.10/osutil/io_test.go snapd-2.31.1+17.10/osutil/io_test.go --- snapd-2.29.4.2+17.10/osutil/io_test.go 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/osutil/io_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -27,6 +27,7 @@ "path/filepath" "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/osutil/sys" "github.com/snapcore/snapd/strutil" . "gopkg.in/check.v1" @@ -177,19 +178,11 @@ c.Assert(err, ErrorMatches, "open .*: file exists") } -func (ts *AtomicWriteTestSuite) TestAtomicFileUidGidError(c *C) { - d := c.MkDir() - p := filepath.Join(d, "foo") - - _, err := osutil.NewAtomicFile(p, 0644, 0, -1, 1) - c.Check(err, ErrorMatches, ".*needs none or both of uid and gid set") -} - func (ts *AtomicWriteTestSuite) TestAtomicFileChownError(c *C) { - eUid := 42 - eGid := 74 + eUid := sys.UserID(42) + eGid := sys.GroupID(74) eErr := errors.New("this didn't work") - defer osutil.MockChown(func(fd *os.File, uid int, gid int) error { + defer osutil.MockChown(func(fd *os.File, uid sys.UserID, gid sys.GroupID) error { c.Check(uid, Equals, eUid) c.Check(gid, Equals, eGid) return eErr @@ -211,7 +204,7 @@ func (ts *AtomicWriteTestSuite) TestAtomicFileCancelError(c *C) { d := c.MkDir() p := filepath.Join(d, "foo") - aw, err := osutil.NewAtomicFile(p, 0644, 0, -1, -1) + aw, err := osutil.NewAtomicFile(p, 0644, 0, osutil.NoChown, osutil.NoChown) c.Assert(err, IsNil) c.Assert(aw.File.Close(), IsNil) @@ -222,7 +215,7 @@ func (ts *AtomicWriteTestSuite) TestAtomicFileCancelBadError(c *C) { d := c.MkDir() p := filepath.Join(d, "foo") - aw, err := osutil.NewAtomicFile(p, 0644, 0, -1, -1) + aw, err := osutil.NewAtomicFile(p, 0644, 0, osutil.NoChown, osutil.NoChown) c.Assert(err, IsNil) defer aw.Close() @@ -234,7 +227,7 @@ func (ts *AtomicWriteTestSuite) TestAtomicFileCancelNoClose(c *C) { d := c.MkDir() p := filepath.Join(d, "foo") - aw, err := osutil.NewAtomicFile(p, 0644, 0, -1, -1) + aw, err := osutil.NewAtomicFile(p, 0644, 0, osutil.NoChown, osutil.NoChown) c.Assert(err, IsNil) c.Assert(aw.Close(), IsNil) @@ -245,7 +238,7 @@ d := c.MkDir() p := filepath.Join(d, "foo") - aw, err := osutil.NewAtomicFile(p, 0644, 0, -1, -1) + aw, err := osutil.NewAtomicFile(p, 0644, 0, osutil.NoChown, osutil.NoChown) c.Assert(err, IsNil) fn := aw.File.Name() c.Check(osutil.FileExists(fn), Equals, true) diff -Nru snapd-2.29.4.2+17.10/osutil/mkdirallchown.go snapd-2.31.1+17.10/osutil/mkdirallchown.go --- snapd-2.29.4.2+17.10/osutil/mkdirallchown.go 2016-08-11 17:27:59.000000000 +0000 +++ snapd-2.31.1+17.10/osutil/mkdirallchown.go 2018-01-24 20:02:44.000000000 +0000 @@ -23,11 +23,13 @@ "os" "path/filepath" "syscall" + + "github.com/snapcore/snapd/osutil/sys" ) // MkdirAllChown is like os.MkdirAll but it calls os.Chown on any // directories it creates. -func MkdirAllChown(path string, perm os.FileMode, uid, gid int) error { +func MkdirAllChown(path string, perm os.FileMode, uid sys.UserID, gid sys.GroupID) error { if s, err := os.Stat(path); err == nil { if s.IsDir() { return nil @@ -54,7 +56,7 @@ return err } - if err := os.Chown(cand, uid, gid); err != nil { + if err := sys.ChownPath(cand, uid, gid); err != nil { return err } diff -Nru snapd-2.29.4.2+17.10/osutil/sys/syscall.go snapd-2.31.1+17.10/osutil/sys/syscall.go --- snapd-2.29.4.2+17.10/osutil/sys/syscall.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/osutil/sys/syscall.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,97 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package sys + +import ( + "os" + "syscall" + "unsafe" +) + +// FlagID can be passed to chown-ish functions to mean "no change", +// and can be returned from getuid-ish functions to mean "not found". +const FlagID = 1<<32 - 1 + +// UserID is the type of the system's user identifiers (in C, uid_t). +// +// We give it its own explicit type so you don't have to remember that +// it's a uint32 (which lead to the bug this package fixes in the +// first place) +type UserID uint32 + +// GroupID is the type of the system's group identifiers (in C, gid_t). +type GroupID uint32 + +// uid_t is an unsigned 32-bit integer in linux right now. +// so syscall.Gete?[ug]id are wrong, and break in 32 bits +// (see https://github.com/golang/go/issues/22739) +func Getuid() UserID { + return UserID(getid(_SYS_GETUID)) +} + +func Geteuid() UserID { + return UserID(getid(_SYS_GETEUID)) +} + +func Getgid() GroupID { + return GroupID(getid(_SYS_GETGID)) +} + +func Getegid() GroupID { + return GroupID(getid(_SYS_GETEGID)) +} + +func getid(id uintptr) uint32 { + // these are documented as not failing, but see golang#22924 + r0, _, errno := syscall.RawSyscall(id, 0, 0, 0) + if errno != 0 { + return uint32(-errno) + } + return uint32(r0) +} + +func Chown(f *os.File, uid UserID, gid GroupID) error { + return Fchown(int(f.Fd()), uid, gid) +} + +func Fchown(fd int, uid UserID, gid GroupID) error { + _, _, errno := syscall.Syscall(syscall.SYS_FCHOWN, uintptr(fd), uintptr(uid), uintptr(gid)) + if errno == 0 { + return nil + } + return errno +} + +func ChownPath(path string, uid UserID, gid GroupID) error { + AT_FDCWD := -100 // also written as -0x64 in ztypes_linux_*.go (but -100 in sys_linux_*.s, and /usr/include/linux/fcntl.h) + return FchownAt(uintptr(AT_FDCWD), path, uid, gid, 0) +} + +func FchownAt(dirfd uintptr, path string, uid UserID, gid GroupID, flags int) error { + p0, err := syscall.BytePtrFromString(path) + if err != nil { + return err + } + _, _, errno := syscall.Syscall6(syscall.SYS_FCHOWNAT, dirfd, uintptr(unsafe.Pointer(p0)), uintptr(uid), uintptr(gid), uintptr(flags), 0) + if errno == 0 { + return nil + } + return errno +} diff -Nru snapd-2.29.4.2+17.10/osutil/sys/sysnum_getpid_16.go snapd-2.31.1+17.10/osutil/sys/sysnum_getpid_16.go --- snapd-2.29.4.2+17.10/osutil/sys/sysnum_getpid_16.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/osutil/sys/sysnum_getpid_16.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,33 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +build arm 386 + +/* + * Copyright (C) 2017 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 . + * + */ + +package sys + +import "syscall" + +// these are the constants for where getuid et al are 16-bit versions +// (and so the 32 bit version is called getuid32 etc) + +const ( + _SYS_GETUID = syscall.SYS_GETUID32 + _SYS_GETGID = syscall.SYS_GETGID32 + _SYS_GETEUID = syscall.SYS_GETEUID32 + _SYS_GETEGID = syscall.SYS_GETEGID32 +) diff -Nru snapd-2.29.4.2+17.10/osutil/sys/sysnum_getpid_32.go snapd-2.31.1+17.10/osutil/sys/sysnum_getpid_32.go --- snapd-2.29.4.2+17.10/osutil/sys/sysnum_getpid_32.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/osutil/sys/sysnum_getpid_32.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,32 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +build arm64 amd64 ppc64le s390x ppc + +/* + * Copyright (C) 2017 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 . + * + */ + +package sys + +import "syscall" + +// these are the constants for where getuid et al are already 32-bit + +const ( + _SYS_GETUID = syscall.SYS_GETUID + _SYS_GETGID = syscall.SYS_GETGID + _SYS_GETEUID = syscall.SYS_GETEUID + _SYS_GETEGID = syscall.SYS_GETEGID +) diff -Nru snapd-2.29.4.2+17.10/osutil/user.go snapd-2.31.1+17.10/osutil/user.go --- snapd-2.29.4.2+17.10/osutil/user.go 2016-11-24 09:36:04.000000000 +0000 +++ snapd-2.31.1+17.10/osutil/user.go 2018-01-24 20:02:44.000000000 +0000 @@ -28,6 +28,8 @@ "regexp" "strconv" "strings" + + "github.com/snapcore/snapd/osutil/sys" ) var userLookup = user.Lookup @@ -104,13 +106,9 @@ return fmt.Errorf("cannot find user %q: %s", name, err) } - uid, err := strconv.Atoi(u.Uid) - if err != nil { - return fmt.Errorf("cannot parse user id %s: %s", u.Uid, err) - } - gid, err := strconv.Atoi(u.Gid) + uid, gid, err := UidGid(u) if err != nil { - return fmt.Errorf("cannot parse group id %s: %s", u.Gid, err) + return err } sshDir := filepath.Join(u.HomeDir, ".ssh") @@ -161,3 +159,20 @@ return real, nil } + +// UidGid returns the uid and gid of the given user, as uint32s +// +// XXX this should go away soon +func UidGid(u *user.User) (sys.UserID, sys.GroupID, error) { + // XXX this will be wrong for high uids on 32-bit arches (for now) + uid, err := strconv.Atoi(u.Uid) + if err != nil { + return sys.FlagID, sys.FlagID, fmt.Errorf("cannot parse user id %s: %s", u.Uid, err) + } + gid, err := strconv.Atoi(u.Gid) + if err != nil { + return sys.FlagID, sys.FlagID, fmt.Errorf("cannot parse group id %s: %s", u.Gid, err) + } + + return sys.UserID(uid), sys.GroupID(gid), nil +} diff -Nru snapd-2.29.4.2+17.10/osutil/user_test.go snapd-2.31.1+17.10/osutil/user_test.go --- snapd-2.29.4.2+17.10/osutil/user_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.31.1+17.10/osutil/user_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -29,6 +29,7 @@ "gopkg.in/check.v1" "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/osutil/sys" "github.com/snapcore/snapd/testutil" ) @@ -187,3 +188,25 @@ c.Check(cur.Username, check.Equals, t.CurrentUsername) } } + +func (s *createUserSuite) TestUidGid(c *check.C) { + for k, t := range map[string]struct { + User *user.User + Uid sys.UserID + Gid sys.GroupID + Err string + }{ + "happy": {&user.User{Uid: "10", Gid: "10"}, 10, 10, ""}, + "bad uid": {&user.User{Uid: "x", Gid: "10"}, sys.FlagID, sys.FlagID, "cannot parse user id x"}, + "bad gid": {&user.User{Uid: "10", Gid: "x"}, sys.FlagID, sys.FlagID, "cannot parse group id x"}, + } { + uid, gid, err := osutil.UidGid(t.User) + c.Check(uid, check.Equals, t.Uid, check.Commentf(k)) + c.Check(gid, check.Equals, t.Gid, check.Commentf(k)) + if t.Err == "" { + c.Check(err, check.IsNil, check.Commentf(k)) + } else { + c.Check(err, check.ErrorMatches, ".*"+t.Err+".*", check.Commentf(k)) + } + } +} diff -Nru snapd-2.29.4.2+17.10/overlord/assertstate/assertmgr.go snapd-2.31.1+17.10/overlord/assertstate/assertmgr.go --- snapd-2.29.4.2+17.10/overlord/assertstate/assertmgr.go 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/assertstate/assertmgr.go 2018-01-24 20:02:44.000000000 +0000 @@ -59,6 +59,10 @@ return &AssertManager{runner: runner}, nil } +func (m *AssertManager) KnownTaskKinds() []string { + return m.runner.KnownTaskKinds() +} + // Ensure implements StateManager.Ensure. func (m *AssertManager) Ensure() error { m.runner.Ensure() diff -Nru snapd-2.29.4.2+17.10/overlord/assertstate/assertstate.go snapd-2.31.1+17.10/overlord/assertstate/assertstate.go --- snapd-2.29.4.2+17.10/overlord/assertstate/assertstate.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/assertstate/assertstate.go 2017-12-01 15:51:55.000000000 +0000 @@ -175,7 +175,12 @@ return fmt.Sprintf("refresh control errors:%s", strings.Join(l, "\n - ")) } -// ValidateRefreshes validates the refresh candidate revisions represented by the snapInfos, looking for the needed refresh control validation assertions, it returns a validated subset in validated and a summary error if not all candidates validated. ignoreValidation is a set of snap-ids that should not be gated. +// ValidateRefreshes validates the refresh candidate revisions +// represented by the snapInfos, looking for the needed refresh +// control validation assertions, it returns a validated subset in +// validated and a summary error if not all candidates +// validated. ignoreValidation is a set of snap-ids that should not be +// gated. func ValidateRefreshes(s *state.State, snapInfos []*snap.Info, ignoreValidation map[string]bool, userID int) (validated []*snap.Info, err error) { // maps gated snap-ids to gating snap-ids controlled := make(map[string][]string) @@ -320,6 +325,19 @@ return a.(*asserts.Account), nil } +// Store returns the store assertion with the given name/id if it is +// present in the system assertion database. +func Store(s *state.State, store string) (*asserts.Store, error) { + db := DB(s) + a, err := db.Find(asserts.StoreType, map[string]string{ + "store": store, + }) + if err != nil { + return nil, err + } + return a.(*asserts.Store), nil +} + // AutoAliases returns the explicit automatic aliases alias=>app mapping for the given installed snap. func AutoAliases(s *state.State, info *snap.Info) (map[string]string, error) { if info.SnapID == "" { diff -Nru snapd-2.29.4.2+17.10/overlord/assertstate/assertstate_test.go snapd-2.31.1+17.10/overlord/assertstate/assertstate_test.go --- snapd-2.29.4.2+17.10/overlord/assertstate/assertstate_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/assertstate/assertstate_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -25,6 +25,7 @@ "fmt" "io/ioutil" "path/filepath" + "sort" "testing" "time" @@ -41,7 +42,6 @@ "github.com/snapcore/snapd/overlord/auth" "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" - "github.com/snapcore/snapd/overlord/storestate" "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap/snaptest" "github.com/snapcore/snapd/store/storetest" @@ -108,7 +108,7 @@ s.o.AddManager(s.mgr) s.state.Lock() - storestate.ReplaceStore(s.state, &fakeStore{ + snapstate.ReplaceStore(s.state, &fakeStore{ state: s.state, db: s.storeSigning, }) @@ -127,6 +127,12 @@ c.Check(db, FitsTypeOf, (*asserts.Database)(nil)) } +func (s *assertMgrSuite) TestKnownTaskKinds(c *C) { + kinds := s.mgr.KnownTaskKinds() + sort.Strings(kinds) + c.Assert(kinds, DeepEquals, []string{"validate-snap"}) +} + func (s *assertMgrSuite) TestAdd(c *C) { s.state.Lock() defer s.state.Unlock() @@ -1131,3 +1137,29 @@ c.Check(acct.AccountID(), Equals, s.dev1Acct.AccountID()) c.Check(acct.Username(), Equals, "developer1") } + +func (s *assertMgrSuite) TestStore(c *C) { + s.state.Lock() + defer s.state.Unlock() + + err := assertstate.Add(s.state, s.storeSigning.StoreAccountKey("")) + c.Assert(err, IsNil) + err = assertstate.Add(s.state, s.dev1Acct) + c.Assert(err, IsNil) + storeHeaders := map[string]interface{}{ + "store": "foo", + "operator-id": s.dev1Acct.AccountID(), + "timestamp": time.Now().Format(time.RFC3339), + } + fooStore, err := s.storeSigning.Sign(asserts.StoreType, storeHeaders, nil, "") + c.Assert(err, IsNil) + err = assertstate.Add(s.state, fooStore) + c.Assert(err, IsNil) + + _, err = assertstate.Store(s.state, "bar") + c.Check(asserts.IsNotFound(err), Equals, true) + + store, err := assertstate.Store(s.state, "foo") + c.Assert(err, IsNil) + c.Check(store.Store(), Equals, "foo") +} diff -Nru snapd-2.29.4.2+17.10/overlord/assertstate/helpers.go snapd-2.31.1+17.10/overlord/assertstate/helpers.go --- snapd-2.29.4.2+17.10/overlord/assertstate/helpers.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/assertstate/helpers.go 2017-12-01 15:51:55.000000000 +0000 @@ -26,8 +26,8 @@ "github.com/snapcore/snapd/asserts" "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/overlord/auth" + "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" - "github.com/snapcore/snapd/overlord/storestate" ) // TODO: snapstate also has this, move to auth, or change a bit the approach now that we have AuthContext in the store? @@ -104,7 +104,7 @@ return err } - sto := storestate.Store(s) + sto := snapstate.Store(s) retrieve := func(ref *asserts.Ref) (asserts.Assertion, error) { // TODO: ignore errors if already in db? diff -Nru snapd-2.29.4.2+17.10/overlord/auth/auth.go snapd-2.31.1+17.10/overlord/auth/auth.go --- snapd-2.29.4.2+17.10/overlord/auth/auth.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/auth/auth.go 2018-01-24 20:02:44.000000000 +0000 @@ -24,6 +24,7 @@ "encoding/base64" "errors" "fmt" + "net/url" "os" "sort" "strconv" @@ -157,11 +158,16 @@ return &authenticatedUser, nil } +var ErrInvalidUser = errors.New("invalid user") + // RemoveUser removes a user from the state given its ID func RemoveUser(st *state.State, userID int) error { var authStateData AuthState err := st.Get("auth", &authStateData) + if err == state.ErrNoState { + return ErrInvalidUser + } if err != nil { return err } @@ -178,7 +184,7 @@ } } - return fmt.Errorf("invalid user") + return ErrInvalidUser } func Users(st *state.State) ([]*UserState, error) { @@ -204,6 +210,9 @@ var authStateData AuthState err := st.Get("auth", &authStateData) + if err == state.ErrNoState { + return nil, ErrInvalidUser + } if err != nil { return nil, err } @@ -213,7 +222,7 @@ return &user, nil } } - return nil, fmt.Errorf("invalid user") + return nil, ErrInvalidUser } // UpdateUser updates user in state @@ -221,6 +230,9 @@ var authStateData AuthState err := st.Get("auth", &authStateData) + if err == state.ErrNoState { + return ErrInvalidUser + } if err != nil { return err } @@ -233,7 +245,7 @@ } } - return fmt.Errorf("invalid user") + return ErrInvalidUser } // Device returns the device details from the state. @@ -360,6 +372,8 @@ // DeviceSessionRequestParams produces a device-session-request with the given nonce, together with other required parameters, the device serial and model assertions. DeviceSessionRequestParams(nonce string) (*DeviceSessionRequestParams, error) + // ProxyStore returns the store assertion for the proxy store if one is set. + ProxyStore() (*asserts.Store, error) } var ( @@ -378,6 +392,7 @@ StoreID(fallback string) (string, error) DeviceSessionRequestParams(nonce string) (*DeviceSessionRequestParams, error) + ProxyStoreParams(defaultURL *url.URL) (proxyStoreID string, proxySroreURL *url.URL, err error) } // authContext helps keeping track of auth data in the state and exposing it. @@ -483,3 +498,19 @@ } return params, nil } + +// ProxyStoreParams returns the id and URL of the proxy store if one is set. Returns the defaultURL otherwise and id = "". +func (ac *authContext) ProxyStoreParams(defaultURL *url.URL) (proxyStoreID string, proxySroreURL *url.URL, err error) { + var sto *asserts.Store + if ac.deviceAsserts != nil { + var err error + sto, err = ac.deviceAsserts.ProxyStore() + if err != nil && err != state.ErrNoState { + return "", nil, err + } + } + if sto != nil { + return sto.Store(), sto.URL(), nil + } + return "", defaultURL, nil +} diff -Nru snapd-2.29.4.2+17.10/overlord/auth/auth_test.go snapd-2.31.1+17.10/overlord/auth/auth_test.go --- snapd-2.29.4.2+17.10/overlord/auth/auth_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/auth/auth_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -20,6 +20,7 @@ package auth_test import ( + "net/url" "os" "strings" "testing" @@ -39,10 +40,18 @@ type authSuite struct { state *state.State + + defURL *url.URL } var _ = Suite(&authSuite{}) +func (as *authSuite) SetupSuite(c *C) { + var err error + as.defURL, err = url.Parse("http://store") + c.Assert(err, IsNil) +} + func (as *authSuite) SetUpTest(c *C) { as.state = state.New(nil) } @@ -263,7 +272,7 @@ as.state.Lock() userFromState, err := auth.User(as.state, 42) as.state.Unlock() - c.Check(err, NotNil) + c.Check(err, Equals, auth.ErrInvalidUser) c.Check(userFromState, IsNil) } @@ -275,6 +284,7 @@ as.state.Lock() userFromState, err := auth.User(as.state, 42) + c.Check(err, Equals, auth.ErrInvalidUser) c.Check(err, ErrorMatches, "invalid user") c.Check(userFromState, IsNil) } @@ -326,7 +336,7 @@ as.state.Lock() err := auth.UpdateUser(as.state, user) as.state.Unlock() - c.Assert(err, ErrorMatches, "invalid user") + c.Assert(err, Equals, auth.ErrInvalidUser) } func (as *authSuite) TestRemove(c *C) { @@ -348,12 +358,12 @@ as.state.Lock() _, err = auth.User(as.state, user.ID) as.state.Unlock() - c.Check(err, ErrorMatches, "invalid user") + c.Check(err, Equals, auth.ErrInvalidUser) as.state.Lock() err = auth.RemoveUser(as.state, user.ID) as.state.Unlock() - c.Assert(err, ErrorMatches, "invalid user") + c.Assert(err, Equals, auth.ErrInvalidUser) } func (as *authSuite) TestSetDevice(c *C) { @@ -438,7 +448,7 @@ authContext := auth.NewAuthContext(as.state, nil) _, err := authContext.UpdateUserAuth(user, nil) - c.Assert(err, ErrorMatches, "invalid user") + c.Assert(err, Equals, auth.ErrInvalidUser) } func (as *authSuite) TestAuthContextDeviceForNonExistent(c *C) { @@ -509,12 +519,17 @@ }) } -func (as *authSuite) TestAuthContextStoreIDFallback(c *C) { +func (as *authSuite) TestAuthContextStoreParamsFallback(c *C) { authContext := auth.NewAuthContext(as.state, nil) storeID, err := authContext.StoreID("store-id") c.Assert(err, IsNil) c.Check(storeID, Equals, "store-id") + + proxyStoreID, proxyStoreURL, err := authContext.ProxyStoreParams(as.defURL) + c.Assert(err, IsNil) + c.Check(proxyStoreID, Equals, "") + c.Check(proxyStoreURL, Equals, as.defURL) } func (as *authSuite) TestAuthContextStoreIDFromEnv(c *C) { @@ -580,6 +595,16 @@ sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij AXNpZw=` + + exStore = `type: store +authority-id: canonical +store: foo +operator-id: foo-operator +url: http://foo.internal +timestamp: 2017-11-01T10:00:00Z +sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij + +AXNpZw=` ) type testDeviceAssertions struct { @@ -636,6 +661,17 @@ }, nil } +func (da *testDeviceAssertions) ProxyStore() (*asserts.Store, error) { + if da.nothing { + return nil, state.ErrNoState + } + a, err := asserts.Decode([]byte(exStore)) + if err != nil { + return nil, err + } + return a.(*asserts.Store), nil +} + func (as *authSuite) TestAuthContextMissingDeviceAssertions(c *C) { // no assertions in state authContext := auth.NewAuthContext(as.state, &testDeviceAssertions{nothing: true}) @@ -646,6 +682,11 @@ storeID, err := authContext.StoreID("fallback") c.Assert(err, IsNil) c.Check(storeID, Equals, "fallback") + + proxyStoreID, proxyStoreURL, err := authContext.ProxyStoreParams(as.defURL) + c.Assert(err, IsNil) + c.Check(proxyStoreID, Equals, "") + c.Check(proxyStoreURL, Equals, as.defURL) } func (as *authSuite) TestAuthContextWithDeviceAssertions(c *C) { @@ -673,6 +714,15 @@ storeID, err := authContext.StoreID("store-id") c.Assert(err, IsNil) c.Check(storeID, Equals, "my-brand-store-id") + + // proxy store + fooURL, err := url.Parse("http://foo.internal") + c.Assert(err, IsNil) + + proxyStoreID, proxyStoreURL, err := authContext.ProxyStoreParams(as.defURL) + c.Assert(err, IsNil) + c.Check(proxyStoreID, Equals, "foo") + c.Check(proxyStoreURL, DeepEquals, fooURL) } func (as *authSuite) TestAuthContextWithDeviceAssertionsGenericClassicModel(c *C) { diff -Nru snapd-2.29.4.2+17.10/overlord/cmdstate/cmdmgr.go snapd-2.31.1+17.10/overlord/cmdstate/cmdmgr.go --- snapd-2.29.4.2+17.10/overlord/cmdstate/cmdmgr.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/cmdstate/cmdmgr.go 2018-01-24 20:02:44.000000000 +0000 @@ -41,6 +41,10 @@ return &CommandManager{runner: runner} } +func (m *CommandManager) KnownTaskKinds() []string { + return m.runner.KnownTaskKinds() +} + // Ensure is part of the overlord.StateManager interface. func (m *CommandManager) Ensure() error { m.runner.Ensure() diff -Nru snapd-2.29.4.2+17.10/overlord/cmdstate/cmdstate_test.go snapd-2.31.1+17.10/overlord/cmdstate/cmdstate_test.go --- snapd-2.29.4.2+17.10/overlord/cmdstate/cmdstate_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/cmdstate/cmdstate_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -21,6 +21,7 @@ import ( "path/filepath" + "sort" "strings" "testing" "time" @@ -77,6 +78,12 @@ s.restore() } +func (s *cmdSuite) TestKnownTaskKinds(c *check.C) { + kinds := s.manager.KnownTaskKinds() + sort.Strings(kinds) + c.Assert(kinds, check.DeepEquals, []string{"exec-command"}) +} + func (s *cmdSuite) TestExecTask(c *check.C) { s.state.Lock() defer s.state.Unlock() diff -Nru snapd-2.29.4.2+17.10/overlord/configstate/config/transaction.go snapd-2.31.1+17.10/overlord/configstate/config/transaction.go --- snapd-2.29.4.2+17.10/overlord/configstate/config/transaction.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/configstate/config/transaction.go 2017-12-01 15:51:55.000000000 +0000 @@ -61,6 +61,11 @@ return transaction } +// State returns the system State +func (t *Transaction) State() *state.State { + return t.state +} + // Set sets the provided snap's configuration key to the given value. // The provided key may be formed as a dotted key path through nested maps. // For example, the "a.b.c" key describes the {a: {b: {c: value}}} map. diff -Nru snapd-2.29.4.2+17.10/overlord/configstate/config/transaction_test.go snapd-2.31.1+17.10/overlord/configstate/config/transaction_test.go --- snapd-2.29.4.2+17.10/overlord/configstate/config/transaction_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/configstate/config/transaction_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -318,3 +318,11 @@ c.Assert(config.IsNoOption(err), Equals, true) c.Assert(err, ErrorMatches, `snap "some-snap" has no configuration`) } + +func (s *transactionSuite) TestState(c *C) { + s.state.Lock() + defer s.state.Unlock() + + tr := config.NewTransaction(s.state) + c.Check(tr.State(), DeepEquals, s.state) +} diff -Nru snapd-2.29.4.2+17.10/overlord/configstate/configcore/corecfg.go snapd-2.31.1+17.10/overlord/configstate/configcore/corecfg.go --- snapd-2.29.4.2+17.10/overlord/configstate/configcore/corecfg.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/configstate/configcore/corecfg.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,87 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package configcore + +import ( + "fmt" + "os" + + "github.com/snapcore/snapd/overlord/configstate/config" + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/release" +) + +var ( + Stdout = os.Stdout + Stderr = os.Stderr +) + +type Conf interface { + Get(snapName, key string, result interface{}) error + State() *state.State +} + +func coreCfg(tr Conf, key string) (result string, err error) { + var v interface{} = "" + if err := tr.Get("core", key, &v); err != nil && !config.IsNoOption(err) { + return "", err + } + // TODO: we could have a fully typed approach but at the + // moment we also always use "" to mean unset as well, this is + // the smallest change + return fmt.Sprintf("%v", v), nil +} + +func Run(tr Conf) error { + if err := validateProxyStore(tr); err != nil { + return err + } + if err := validateRefreshSchedule(tr); err != nil { + return err + } + + // see if it makes sense to run at all + if release.OnClassic { + // nothing to do + return nil + } + // TODO: consider allowing some of these on classic too? + // consider erroring on core-only options on classic? + + // handle the various core config options: + // service.*.disable + if err := handleServiceDisableConfiguration(tr); err != nil { + return err + } + // system.power-key-action + if err := handlePowerButtonConfiguration(tr); err != nil { + return err + } + // pi-config.* + if err := handlePiConfiguration(tr); err != nil { + return err + } + // proxy.{http,https,ftp} + if err := handleProxyConfiguration(tr); err != nil { + return err + } + + return nil +} diff -Nru snapd-2.29.4.2+17.10/overlord/configstate/configcore/corecfg_test.go snapd-2.31.1+17.10/overlord/configstate/configcore/corecfg_test.go --- snapd-2.29.4.2+17.10/overlord/configstate/configcore/corecfg_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/configstate/configcore/corecfg_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,86 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package configcore_test + +import ( + "fmt" + "reflect" + "testing" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/systemd" +) + +func Test(t *testing.T) { TestingT(t) } + +type mockConf struct { + state *state.State + conf map[string]interface{} + err error +} + +func (cfg *mockConf) Get(snapName, key string, result interface{}) error { + if snapName != "core" { + return fmt.Errorf("mockConf only knows about core") + } + if cfg.conf[key] != nil { + v1 := reflect.ValueOf(result) + v2 := reflect.Indirect(v1) + v2.Set(reflect.ValueOf(cfg.conf[key])) + } + return cfg.err +} + +func (cfg *mockConf) State() *state.State { + return cfg.state +} + +// configcoreSuite is the base for all the configcore tests +type configcoreSuite struct { + state *state.State + + systemctlArgs [][]string + systemctlRestorer func() +} + +var _ = Suite(&configcoreSuite{}) + +func (s *configcoreSuite) SetUpSuite(c *C) { + s.systemctlRestorer = systemd.MockSystemctl(func(args ...string) ([]byte, error) { + s.systemctlArgs = append(s.systemctlArgs, args[:]) + output := []byte("ActiveState=inactive") + return output, nil + }) +} + +func (s *configcoreSuite) TearDownSuite(c *C) { + s.systemctlRestorer() +} + +func (s *configcoreSuite) SetUpTest(c *C) { + s.state = state.New(nil) +} + +// runCfgSuite tests configcore.Run() +type runCfgSuite struct { + configcoreSuite +} diff -Nru snapd-2.29.4.2+17.10/overlord/configstate/configcore/export_test.go snapd-2.31.1+17.10/overlord/configstate/configcore/export_test.go --- snapd-2.29.4.2+17.10/overlord/configstate/configcore/export_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/configstate/configcore/export_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,27 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package configcore + +var ( + UpdatePiConfig = updatePiConfig + SwitchHandlePowerKey = switchHandlePowerKey + SwitchDisableService = switchDisableService + UpdateKeyValueStream = updateKeyValueStream +) diff -Nru snapd-2.29.4.2+17.10/overlord/configstate/configcore/picfg.go snapd-2.31.1+17.10/overlord/configstate/configcore/picfg.go --- snapd-2.29.4.2+17.10/overlord/configstate/configcore/picfg.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/configstate/configcore/picfg.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,100 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package configcore + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" +) + +// valid pi config keys +var piConfigKeys = map[string]bool{ + "disable_overscan": true, + "framebuffer_width": true, + "framebuffer_height": true, + "framebuffer_depth": true, + "framebuffer_ignore_alpha": true, + "overscan_left": true, + "overscan_right": true, + "overscan_top": true, + "overscan_bottom": true, + "overscan_scale": true, + "display_rotate": true, + "hdmi_group": true, + "hdmi_mode": true, + "hdmi_drive": true, + "avoid_warnings": true, + "gpu_mem_256": true, + "gpu_mem_512": true, + "gpu_mem": true, + "sdtv_aspect": true, + "config_hdmi_boost": true, + "hdmi_force_hotplug": true, +} + +func updatePiConfig(path string, config map[string]string) error { + f, err := os.Open(path) + if err != nil { + return err + } + defer f.Close() + + toWrite, err := updateKeyValueStream(f, piConfigKeys, config) + if err != nil { + return err + } + + if toWrite != nil { + s := strings.Join(toWrite, "\n") + // ensure we have a final newline in the file + s += "\n" + return osutil.AtomicWriteFile(path, []byte(s), 0644, 0) + } + + return nil +} + +func piConfigFile() string { + return filepath.Join(dirs.GlobalRootDir, "/boot/uboot/config.txt") +} + +func handlePiConfiguration(tr Conf) error { + if osutil.FileExists(piConfigFile()) { + // snapctl can actually give us the whole dict in + // JSON, in a single call; use that instead of this. + config := map[string]string{} + for key := range piConfigKeys { + output, err := coreCfg(tr, fmt.Sprintf("pi-config.%s", strings.Replace(key, "_", "-", -1))) + if err != nil { + return err + } + config[key] = output + } + if err := updatePiConfig(piConfigFile(), config); err != nil { + return err + } + } + return nil +} diff -Nru snapd-2.29.4.2+17.10/overlord/configstate/configcore/picfg_test.go snapd-2.31.1+17.10/overlord/configstate/configcore/picfg_test.go --- snapd-2.29.4.2+17.10/overlord/configstate/configcore/picfg_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/configstate/configcore/picfg_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,155 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package configcore_test + +import ( + "io/ioutil" + "os" + "path/filepath" + "strings" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/overlord/configstate/configcore" + "github.com/snapcore/snapd/release" +) + +type piCfgSuite struct { + configcoreSuite + + mockConfigPath string +} + +var _ = Suite(&piCfgSuite{}) + +var mockConfigTxt = ` +# For more options and information see +# http://www.raspberrypi.org/documentation/configuration/config-txt.md +#hdmi_group=1 +# uncomment this if your display has a black border of unused pixels visible +# and your display can output without overscan +#disable_overscan=1 +unrelated_options=are-kept +` + +func (s *piCfgSuite) SetUpTest(c *C) { + dirs.SetRootDir(c.MkDir()) + c.Assert(os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "etc"), 0755), IsNil) + + s.mockConfigPath = filepath.Join(dirs.GlobalRootDir, "/boot/uboot/config.txt") + err := os.MkdirAll(filepath.Dir(s.mockConfigPath), 0755) + c.Assert(err, IsNil) + s.mockConfig(c, mockConfigTxt) +} + +func (s *piCfgSuite) TearDownTest(c *C) { + dirs.SetRootDir("/") +} + +func (s *piCfgSuite) mockConfig(c *C, txt string) { + err := ioutil.WriteFile(s.mockConfigPath, []byte(txt), 0644) + c.Assert(err, IsNil) +} + +func (s *piCfgSuite) checkMockConfig(c *C, expected string) { + newContent, err := ioutil.ReadFile(s.mockConfigPath) + c.Assert(err, IsNil) + c.Check(string(newContent), Equals, expected) +} + +func (s *piCfgSuite) TestConfigurePiConfigUncommentExisting(c *C) { + err := configcore.UpdatePiConfig(s.mockConfigPath, map[string]string{"disable_overscan": "1"}) + c.Assert(err, IsNil) + + expected := strings.Replace(mockConfigTxt, "#disable_overscan=1", "disable_overscan=1", -1) + s.checkMockConfig(c, expected) +} + +func (s *piCfgSuite) TestConfigurePiConfigCommentExisting(c *C) { + s.mockConfig(c, mockConfigTxt+"avoid_warnings=1\n") + + err := configcore.UpdatePiConfig(s.mockConfigPath, map[string]string{"avoid_warnings": ""}) + c.Assert(err, IsNil) + + expected := mockConfigTxt + "#avoid_warnings=1\n" + s.checkMockConfig(c, expected) +} + +func (s *piCfgSuite) TestConfigurePiConfigAddNewOption(c *C) { + err := configcore.UpdatePiConfig(s.mockConfigPath, map[string]string{"framebuffer_depth": "16"}) + c.Assert(err, IsNil) + + expected := mockConfigTxt + "framebuffer_depth=16\n" + s.checkMockConfig(c, expected) + + // add again, verify its not added twice but updated + err = configcore.UpdatePiConfig(s.mockConfigPath, map[string]string{"framebuffer_depth": "32"}) + c.Assert(err, IsNil) + expected = mockConfigTxt + "framebuffer_depth=32\n" + s.checkMockConfig(c, expected) +} + +func (s *piCfgSuite) TestConfigurePiConfigNoChangeUnset(c *C) { + // ensure we cannot write to the dir to test that we really + // do not update the file + err := os.Chmod(filepath.Dir(s.mockConfigPath), 0500) + c.Assert(err, IsNil) + defer os.Chmod(filepath.Dir(s.mockConfigPath), 0755) + + err = configcore.UpdatePiConfig(s.mockConfigPath, map[string]string{"hdmi_group": ""}) + c.Assert(err, IsNil) +} + +func (s *piCfgSuite) TestConfigurePiConfigNoChangeSet(c *C) { + // ensure we cannot write to the dir to test that we really + // do not update the file + err := os.Chmod(filepath.Dir(s.mockConfigPath), 0500) + c.Assert(err, IsNil) + defer os.Chmod(filepath.Dir(s.mockConfigPath), 0755) + + err = configcore.UpdatePiConfig(s.mockConfigPath, map[string]string{"unrelated_options": "cannot-be-set"}) + c.Assert(err, ErrorMatches, `cannot set unsupported configuration value "unrelated_options"`) +} + +func (s *piCfgSuite) TestConfigurePiConfigIntegration(c *C) { + restore := release.MockOnClassic(false) + defer restore() + + err := configcore.Run(&mockConf{ + conf: map[string]interface{}{ + "pi-config.disable-overscan": 1, + }, + }) + c.Assert(err, IsNil) + + expected := strings.Replace(mockConfigTxt, "#disable_overscan=1", "disable_overscan=1", -1) + s.checkMockConfig(c, expected) + + err = configcore.Run(&mockConf{ + conf: map[string]interface{}{ + "pi-config.disable-overscan": "", + }, + }) + c.Assert(err, IsNil) + + s.checkMockConfig(c, mockConfigTxt) + +} diff -Nru snapd-2.29.4.2+17.10/overlord/configstate/configcore/powerbtn.go snapd-2.31.1+17.10/overlord/configstate/configcore/powerbtn.go --- snapd-2.29.4.2+17.10/overlord/configstate/configcore/powerbtn.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/configstate/configcore/powerbtn.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,81 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package configcore + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" +) + +func powerBtnCfg() string { + return filepath.Join(dirs.GlobalRootDir, "/etc/systemd/logind.conf.d/00-snap-core.conf") +} + +// switchHandlePowerKey changes the behavior when the power key is pressed +func switchHandlePowerKey(action string) error { + validActions := map[string]bool{ + "ignore": true, + "poweroff": true, + "reboot": true, + "halt": true, + "kexec": true, + "suspend": true, + "hibernate": true, + "hybrid-sleep": true, + "lock": true, + } + + cfgDir := filepath.Dir(powerBtnCfg()) + if !osutil.IsDirectory(cfgDir) { + if err := os.MkdirAll(cfgDir, 0755); err != nil { + return err + } + } + if !validActions[action] { + return fmt.Errorf("invalid action %q supplied for system.power-key-action option", action) + } + + content := fmt.Sprintf(`[Login] +HandlePowerKey=%s +`, action) + return osutil.AtomicWriteFile(powerBtnCfg(), []byte(content), 0644, 0) +} + +func handlePowerButtonConfiguration(tr Conf) error { + output, err := coreCfg(tr, "system.power-key-action") + if err != nil { + return err + } + if output == "" { + if err := os.Remove(powerBtnCfg()); err != nil && !os.IsNotExist(err) { + return err + } + + } else { + if err := switchHandlePowerKey(output); err != nil { + return err + } + } + return nil +} diff -Nru snapd-2.29.4.2+17.10/overlord/configstate/configcore/powerbtn_test.go snapd-2.31.1+17.10/overlord/configstate/configcore/powerbtn_test.go --- snapd-2.29.4.2+17.10/overlord/configstate/configcore/powerbtn_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/configstate/configcore/powerbtn_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,79 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package configcore_test + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/overlord/configstate/configcore" + "github.com/snapcore/snapd/release" +) + +type powerbtnSuite struct { + configcoreSuite + + mockPowerBtnCfg string +} + +var _ = Suite(&powerbtnSuite{}) + +func (s *powerbtnSuite) SetUpTest(c *C) { + dirs.SetRootDir(c.MkDir()) + c.Assert(os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "etc"), 0755), IsNil) + + s.mockPowerBtnCfg = filepath.Join(dirs.GlobalRootDir, "/etc/systemd/logind.conf.d/00-snap-core.conf") +} + +func (s *powerbtnSuite) TearDownTest(c *C) { + dirs.SetRootDir("/") +} + +func (s *powerbtnSuite) TestConfigurePowerButtonInvalid(c *C) { + err := configcore.SwitchHandlePowerKey("invalid-action") + c.Check(err, ErrorMatches, `invalid action "invalid-action" supplied for system.power-key-action option`) +} + +func (s *powerbtnSuite) TestConfigurePowerIntegration(c *C) { + restore := release.MockOnClassic(false) + defer restore() + + for _, action := range []string{"ignore", "poweroff", "reboot", "halt", "kexec", "suspend", "hibernate", "hybrid-sleep", "lock"} { + + err := configcore.Run(&mockConf{ + conf: map[string]interface{}{ + "system.power-key-action": action, + }, + }) + c.Assert(err, IsNil) + + // ensure nothing gets enabled/disabled when an unsupported + // service is set for disable + content, err := ioutil.ReadFile(s.mockPowerBtnCfg) + c.Assert(err, IsNil) + c.Check(string(content), Equals, fmt.Sprintf("[Login]\nHandlePowerKey=%s\n", action)) + } + +} diff -Nru snapd-2.29.4.2+17.10/overlord/configstate/configcore/proxy.go snapd-2.31.1+17.10/overlord/configstate/configcore/proxy.go --- snapd-2.29.4.2+17.10/overlord/configstate/configcore/proxy.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/configstate/configcore/proxy.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,105 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package configcore + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/overlord/assertstate" +) + +var proxyConfigKeys = map[string]bool{ + "http_proxy": true, + "https_proxy": true, + "ftp_proxy": true, + "no_proxy": true, +} + +func etcEnvironment() string { + return filepath.Join(dirs.GlobalRootDir, "/etc/environment") +} + +func updateEtcEnvironmentConfig(path string, config map[string]string) error { + f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644) + if err != nil { + return nil + } + defer f.Close() + + toWrite, err := updateKeyValueStream(f, proxyConfigKeys, config) + if err != nil { + return err + } + if toWrite != nil { + return ioutil.WriteFile(path, []byte(strings.Join(toWrite, "\n")), 0644) + } + + return nil +} + +func handleProxyConfiguration(tr Conf) error { + config := map[string]string{} + // normal proxy settings + for _, key := range []string{"http", "https", "ftp"} { + output, err := coreCfg(tr, "proxy."+key) + if err != nil { + return err + } + config[key+"_proxy"] = output + } + // handle no_proxy + output, err := coreCfg(tr, "proxy.no-proxy") + if err != nil { + return err + } + config["no_proxy"] = output + + if err := updateEtcEnvironmentConfig(etcEnvironment(), config); err != nil { + return err + } + + return nil +} + +func validateProxyStore(tr Conf) error { + proxyStore, err := coreCfg(tr, "proxy.store") + if err != nil { + return err + } + + if proxyStore == "" { + return nil + } + + st := tr.State() + st.Lock() + defer st.Unlock() + _, err = assertstate.Store(st, proxyStore) + if asserts.IsNotFound(err) { + return fmt.Errorf("cannot set proxy.store to %q without a matching store assertion", proxyStore) + } + return err +} diff -Nru snapd-2.29.4.2+17.10/overlord/configstate/configcore/proxy_test.go snapd-2.31.1+17.10/overlord/configstate/configcore/proxy_test.go --- snapd-2.29.4.2+17.10/overlord/configstate/configcore/proxy_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/configstate/configcore/proxy_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,168 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package configcore_test + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "time" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/asserts/assertstest" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/overlord/assertstate" + "github.com/snapcore/snapd/overlord/configstate/configcore" + "github.com/snapcore/snapd/release" +) + +type proxySuite struct { + configcoreSuite + + mockEtcEnvironment string + + storeSigning *assertstest.StoreStack +} + +var _ = Suite(&proxySuite{}) + +func (s *proxySuite) SetUpTest(c *C) { + s.configcoreSuite.SetUpTest(c) + + dirs.SetRootDir(c.MkDir()) + err := os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "/etc/"), 0755) + c.Assert(err, IsNil) + s.mockEtcEnvironment = filepath.Join(dirs.GlobalRootDir, "/etc/environment") + + s.storeSigning = assertstest.NewStoreStack("canonical", nil) + + db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{ + Backstore: asserts.NewMemoryBackstore(), + Trusted: s.storeSigning.Trusted, + OtherPredefined: s.storeSigning.Generic, + }) + c.Assert(err, IsNil) + + s.state.Lock() + assertstate.ReplaceDB(s.state, db) + s.state.Unlock() + + err = db.Add(s.storeSigning.StoreAccountKey("")) + c.Assert(err, IsNil) +} + +func (s *proxySuite) TearDownTest(c *C) { + dirs.SetRootDir("/") +} + +func (s *proxySuite) makeMockEtcEnvironment(c *C) { + err := ioutil.WriteFile(s.mockEtcEnvironment, []byte(` +PATH="/usr/bin" +`), 0644) + c.Assert(err, IsNil) +} + +func (s *proxySuite) TestConfigureProxy(c *C) { + restore := release.MockOnClassic(false) + defer restore() + + for _, proto := range []string{"http", "https", "ftp"} { + // populate with content + s.makeMockEtcEnvironment(c) + + err := configcore.Run(&mockConf{ + conf: map[string]interface{}{ + fmt.Sprintf("proxy.%s", proto): fmt.Sprintf("%s://example.com", proto), + }, + }) + c.Assert(err, IsNil) + + content, err := ioutil.ReadFile(s.mockEtcEnvironment) + c.Assert(err, IsNil) + c.Check(string(content), Equals, fmt.Sprintf(` +PATH="/usr/bin" +%[1]s_proxy=%[1]s://example.com`, proto)) + } +} + +func (s *proxySuite) TestConfigureNoProxy(c *C) { + restore := release.MockOnClassic(false) + defer restore() + + // populate with content + s.makeMockEtcEnvironment(c) + err := configcore.Run(&mockConf{ + conf: map[string]interface{}{ + "proxy.no-proxy": "example.com,bar.com", + }, + }) + c.Assert(err, IsNil) + + content, err := ioutil.ReadFile(s.mockEtcEnvironment) + c.Assert(err, IsNil) + c.Check(string(content), Equals, ` +PATH="/usr/bin" +no_proxy=example.com,bar.com`) +} + +func (s *proxySuite) TestConfigureProxyStore(c *C) { + // set to "" + err := configcore.Run(&mockConf{ + conf: map[string]interface{}{ + "proxy.store": "", + }, + }) + c.Check(err, IsNil) + + // no assertion + conf := &mockConf{ + state: s.state, + conf: map[string]interface{}{ + "proxy.store": "foo", + }, + } + + err = configcore.Run(conf) + c.Check(err, ErrorMatches, `cannot set proxy.store to "foo" without a matching store assertion`) + + operatorAcct := assertstest.NewAccount(s.storeSigning, "foo-operator", nil, "") + s.state.Lock() + err = assertstate.Add(s.state, operatorAcct) + s.state.Unlock() + c.Assert(err, IsNil) + + // have a store assertion. + stoAs, err := s.storeSigning.Sign(asserts.StoreType, map[string]interface{}{ + "store": "foo", + "operator-id": operatorAcct.AccountID(), + "timestamp": time.Now().Format(time.RFC3339), + }, nil, "") + c.Assert(err, IsNil) + s.state.Lock() + err = assertstate.Add(s.state, stoAs) + s.state.Unlock() + c.Assert(err, IsNil) + + err = configcore.Run(conf) + c.Check(err, IsNil) +} diff -Nru snapd-2.29.4.2+17.10/overlord/configstate/configcore/refresh.go snapd-2.31.1+17.10/overlord/configstate/configcore/refresh.go --- snapd-2.29.4.2+17.10/overlord/configstate/configcore/refresh.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/configstate/configcore/refresh.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,63 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package configcore + +import ( + "fmt" + + "github.com/snapcore/snapd/overlord/devicestate" + "github.com/snapcore/snapd/timeutil" +) + +func validateRefreshSchedule(tr Conf) error { + refreshTimerStr, err := coreCfg(tr, "refresh.timer") + if err != nil { + return err + } + if refreshTimerStr != "" { + // try legacy refresh.schedule setting if new-style + // refresh.timer is not set + if _, err = timeutil.ParseSchedule(refreshTimerStr); err != nil { + return err + } + } + + refreshScheduleStr, err := coreCfg(tr, "refresh.schedule") + if err != nil { + return err + } + if refreshScheduleStr == "" { + return nil + } + + if refreshScheduleStr == "managed" { + st := tr.State() + st.Lock() + defer st.Unlock() + + if !devicestate.CanManageRefreshes(st) { + return fmt.Errorf("cannot set schedule to managed") + } + return nil + } + + _, err = timeutil.ParseLegacySchedule(refreshScheduleStr) + return err +} diff -Nru snapd-2.29.4.2+17.10/overlord/configstate/configcore/refresh_test.go snapd-2.31.1+17.10/overlord/configstate/configcore/refresh_test.go --- snapd-2.29.4.2+17.10/overlord/configstate/configcore/refresh_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/configstate/configcore/refresh_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,76 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package configcore_test + +import ( + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/overlord/configstate/configcore" +) + +type refreshSuite struct { + configcoreSuite +} + +var _ = Suite(&refreshSuite{}) + +func (s *refreshSuite) TestConfigureRefreshTimerHappy(c *C) { + err := configcore.Run(&mockConf{ + conf: map[string]interface{}{ + "refresh.timer": "8:00~12:00/2", + }, + }) + c.Assert(err, IsNil) +} + +func (s *refreshSuite) TestConfigureRefreshTimerRejected(c *C) { + err := configcore.Run(&mockConf{ + conf: map[string]interface{}{ + "refresh.timer": "invalid", + }, + }) + c.Assert(err, ErrorMatches, `cannot parse "invalid": "invalid" is not a valid weekday`) +} + +func (s *refreshSuite) TestConfigureLegacyRefreshScheduleHappy(c *C) { + err := configcore.Run(&mockConf{ + conf: map[string]interface{}{ + "refresh.schedule": "8:00-12:00", + }, + }) + c.Assert(err, IsNil) +} + +func (s *refreshSuite) TestConfigureLegacyRefreshScheduleRejected(c *C) { + err := configcore.Run(&mockConf{ + conf: map[string]interface{}{ + "refresh.schedule": "invalid", + }, + }) + c.Assert(err, ErrorMatches, `cannot parse "invalid": not a valid interval`) + + // check that refresh.schedule is verified against legacy parser + err = configcore.Run(&mockConf{ + conf: map[string]interface{}{ + "refresh.schedule": "8:00~12:00/2", + }, + }) + c.Assert(err, ErrorMatches, `cannot parse "8:00~12:00": not a valid interval`) +} diff -Nru snapd-2.29.4.2+17.10/overlord/configstate/configcore/services.go snapd-2.31.1+17.10/overlord/configstate/configcore/services.go --- snapd-2.29.4.2+17.10/overlord/configstate/configcore/services.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/configstate/configcore/services.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,81 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package configcore + +import ( + "fmt" + "time" + + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/systemd" +) + +type sysdLogger struct{} + +func (l *sysdLogger) Notify(status string) { + fmt.Fprintf(Stderr, "sysd: %s\n", status) +} + +// swtichDisableService switches a service in/out of disabled state +// where "true" means disabled and "false" means enabled. +func switchDisableService(service, value string) error { + sysd := systemd.New(dirs.GlobalRootDir, &sysdLogger{}) + serviceName := fmt.Sprintf("%s.service", service) + + switch value { + case "true": + if err := sysd.Disable(serviceName); err != nil { + return err + } + if err := sysd.Mask(serviceName); err != nil { + return err + } + return sysd.Stop(serviceName, 5*time.Minute) + case "false": + if err := sysd.Unmask(serviceName); err != nil { + return err + } + if err := sysd.Enable(serviceName); err != nil { + return err + } + return sysd.Start(serviceName) + default: + return fmt.Errorf("option %q has invalid value %q", serviceName, value) + } +} + +// services that can be disabled +var services = []string{"ssh", "rsyslog"} + +func handleServiceDisableConfiguration(tr Conf) error { + for _, service := range services { + output, err := coreCfg(tr, fmt.Sprintf("service.%s.disable", service)) + if err != nil { + return err + } + if output != "" { + if err := switchDisableService(service, output); err != nil { + return err + } + } + } + + return nil +} diff -Nru snapd-2.29.4.2+17.10/overlord/configstate/configcore/services_test.go snapd-2.31.1+17.10/overlord/configstate/configcore/services_test.go --- snapd-2.29.4.2+17.10/overlord/configstate/configcore/services_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/configstate/configcore/services_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,141 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package configcore_test + +import ( + "fmt" + "os" + "path/filepath" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/overlord/configstate/configcore" + "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/testutil" +) + +type servicesSuite struct { + configcoreSuite + testutil.BaseTest +} + +var _ = Suite(&servicesSuite{}) + +func (s *servicesSuite) SetUpTest(c *C) { + s.BaseTest.SetUpTest(c) + dirs.SetRootDir(c.MkDir()) + c.Assert(os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "etc"), 0755), IsNil) + s.systemctlArgs = nil + s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})) +} + +func (s *servicesSuite) TearDownTest(c *C) { + dirs.SetRootDir("/") + s.BaseTest.TearDownTest(c) +} + +func (s *servicesSuite) TestConfigureServiceInvalidValue(c *C) { + err := configcore.SwitchDisableService("ssh", "xxx") + c.Check(err, ErrorMatches, `option "ssh.service" has invalid value "xxx"`) +} + +func (s *servicesSuite) TestConfigureServiceNotDisabled(c *C) { + err := configcore.SwitchDisableService("ssh", "false") + c.Assert(err, IsNil) + c.Check(s.systemctlArgs, DeepEquals, [][]string{ + {"--root", dirs.GlobalRootDir, "unmask", "ssh.service"}, + {"--root", dirs.GlobalRootDir, "enable", "ssh.service"}, + {"start", "ssh.service"}, + }) +} + +func (s *servicesSuite) TestConfigureServiceDisabled(c *C) { + err := configcore.SwitchDisableService("ssh", "true") + c.Assert(err, IsNil) + c.Check(s.systemctlArgs, DeepEquals, [][]string{ + {"--root", dirs.GlobalRootDir, "disable", "ssh.service"}, + {"--root", dirs.GlobalRootDir, "mask", "ssh.service"}, + {"stop", "ssh.service"}, + {"show", "--property=ActiveState", "ssh.service"}, + }) +} + +func (s *servicesSuite) TestConfigureServiceDisabledIntegration(c *C) { + restore := release.MockOnClassic(false) + defer restore() + + for _, srvName := range []string{"ssh", "rsyslog"} { + s.systemctlArgs = nil + + err := configcore.Run(&mockConf{ + conf: map[string]interface{}{ + fmt.Sprintf("service.%s.disable", srvName): true, + }, + }) + c.Assert(err, IsNil) + srv := fmt.Sprintf("%s.service", srvName) + c.Check(s.systemctlArgs, DeepEquals, [][]string{ + {"--root", dirs.GlobalRootDir, "disable", srv}, + {"--root", dirs.GlobalRootDir, "mask", srv}, + {"stop", srv}, + {"show", "--property=ActiveState", srv}, + }) + } +} + +func (s *servicesSuite) TestConfigureServiceEnableIntegration(c *C) { + restore := release.MockOnClassic(false) + defer restore() + + for _, srvName := range []string{"ssh", "rsyslog"} { + s.systemctlArgs = nil + err := configcore.Run(&mockConf{ + conf: map[string]interface{}{ + fmt.Sprintf("service.%s.disable", srvName): false, + }, + }) + + c.Assert(err, IsNil) + srv := fmt.Sprintf("%s.service", srvName) + c.Check(s.systemctlArgs, DeepEquals, [][]string{ + {"--root", dirs.GlobalRootDir, "unmask", srv}, + {"--root", dirs.GlobalRootDir, "enable", srv}, + {"start", srv}, + }) + } +} + +func (s *servicesSuite) TestConfigureServiceUnsupportedService(c *C) { + restore := release.MockOnClassic(false) + defer restore() + + err := configcore.Run(&mockConf{ + conf: map[string]interface{}{ + "service.snapd.disable": true, + }, + }) + c.Assert(err, IsNil) + + // ensure nothing gets enabled/disabled when an unsupported + // service is set for disable + c.Check(s.systemctlArgs, IsNil) +} diff -Nru snapd-2.29.4.2+17.10/overlord/configstate/configcore/utils.go snapd-2.31.1+17.10/overlord/configstate/configcore/utils.go --- snapd-2.29.4.2+17.10/overlord/configstate/configcore/utils.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/configstate/configcore/utils.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,97 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package configcore + +import ( + "bufio" + "fmt" + "io" + "regexp" +) + +// first match is if it is comment, second is key, third value +var rx = regexp.MustCompile(`^[ \t]*(#?)[ \t#]*([a-z_]+)=(.*)$`) + +// updateKeyValueStream updates simple key=value files with comments. +// Example for such formats are: /etc/environment or /boot/uboot/config.txt +// +// An r io.Reader, map of supported config keys and a configuration +// "patch" is taken as input, the r is read line-by-line and any line +// and any required configuration change from the "config" input is +// applied. +// +// If changes need to be written a []string +// that contains the full file is returned. On error an error is returned. +func updateKeyValueStream(r io.Reader, supportedConfigKeys map[string]bool, newConfig map[string]string) (toWrite []string, err error) { + cfgKeys := make([]string, len(newConfig)) + i := 0 + for k := range newConfig { + if !supportedConfigKeys[k] { + return nil, fmt.Errorf("cannot set unsupported configuration value %q", k) + } + cfgKeys[i] = k + i++ + } + + // now go over the content + found := map[string]bool{} + needsWrite := false + + scanner := bufio.NewScanner(r) + for scanner.Scan() { + line := scanner.Text() + matches := rx.FindStringSubmatch(line) + if len(matches) > 0 && supportedConfigKeys[matches[2]] { + wasComment := (matches[1] == "#") + key := matches[2] + oldValue := matches[3] + found[key] = true + if newConfig[key] != "" { + if wasComment || oldValue != newConfig[key] { + line = fmt.Sprintf("%s=%s", key, newConfig[key]) + needsWrite = true + } + } else { + if !wasComment { + line = fmt.Sprintf("#%s=%s", key, oldValue) + needsWrite = true + } + } + } + toWrite = append(toWrite, line) + } + if err := scanner.Err(); err != nil { + return nil, err + } + + // write anything that is missing + for key := range newConfig { + if !found[key] && newConfig[key] != "" { + needsWrite = true + toWrite = append(toWrite, fmt.Sprintf("%s=%s", key, newConfig[key])) + } + } + + if needsWrite { + return toWrite, nil + } + + return nil, nil +} diff -Nru snapd-2.29.4.2+17.10/overlord/configstate/configcore/utils_test.go snapd-2.31.1+17.10/overlord/configstate/configcore/utils_test.go --- snapd-2.29.4.2+17.10/overlord/configstate/configcore/utils_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/configstate/configcore/utils_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,65 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package configcore_test + +import ( + "bytes" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/overlord/configstate/configcore" +) + +type utilsSuite struct{} + +var _ = Suite(&utilsSuite{}) + +func (s *utilsSuite) TestUpdateKeyValueStreamNoNewConfig(c *C) { + in := bytes.NewBufferString("foo=bar") + newConfig := map[string]string{} + supportedConfigKeys := map[string]bool{} + + toWrite, err := configcore.UpdateKeyValueStream(in, supportedConfigKeys, newConfig) + c.Check(err, IsNil) + c.Check(toWrite, IsNil) +} + +func (s *utilsSuite) TestUpdateKeyValueStreamConfigNotInAllConfig(c *C) { + in := bytes.NewBufferString("") + newConfig := map[string]string{"unsupported-options": "cannot be set"} + supportedConfigKeys := map[string]bool{ + "foo": true, + } + + _, err := configcore.UpdateKeyValueStream(in, supportedConfigKeys, newConfig) + c.Check(err, ErrorMatches, `cannot set unsupported configuration value "unsupported-options"`) +} + +func (s *utilsSuite) TestUpdateKeyValueStreamOneChange(c *C) { + in := bytes.NewBufferString("foo=bar") + newConfig := map[string]string{"foo": "baz"} + supportedConfigKeys := map[string]bool{ + "foo": true, + } + + toWrite, err := configcore.UpdateKeyValueStream(in, supportedConfigKeys, newConfig) + c.Check(err, IsNil) + c.Check(toWrite, DeepEquals, []string{"foo=baz"}) +} diff -Nru snapd-2.29.4.2+17.10/overlord/configstate/configmgr.go snapd-2.31.1+17.10/overlord/configstate/configmgr.go --- snapd-2.29.4.2+17.10/overlord/configstate/configmgr.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/configstate/configmgr.go 2018-01-24 20:02:44.000000000 +0000 @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2016 Canonical Ltd + * Copyright (C) 2016-2017 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 @@ -22,23 +22,31 @@ import ( "regexp" + "github.com/snapcore/snapd/overlord/configstate/configcore" "github.com/snapcore/snapd/overlord/hookstate" - "github.com/snapcore/snapd/overlord/state" ) -// ConfigManager is responsible for the maintenance of per-snap configuration in -// the system state. -type ConfigManager struct { - state *state.State -} +var configcoreRun = configcore.Run -// Manager returns a new ConfigManager. -func Manager(s *state.State, hookManager *hookstate.HookManager) (*ConfigManager, error) { - manager := &ConfigManager{ - state: s, +func MockConfigcoreRun(f func(conf configcore.Conf) error) (restore func()) { + origConfigcoreRun := configcoreRun + configcoreRun = f + return func() { + configcoreRun = origConfigcoreRun } +} +func Init(hookManager *hookstate.HookManager) { + // Most configuration is handled via the "configure" hook of the + // snaps. However some configuration is internally handled hookManager.Register(regexp.MustCompile("^configure$"), newConfigureHandler) - - return manager, nil + // Ensure that we run configure for the core snap internally. + // Note that we use the func() indirection so that mocking configcoreRun + // in tests works correctly. + hookManager.RegisterHijack("configure", "core", func(ctx *hookstate.Context) error { + ctx.Lock() + tr := ContextTransaction(ctx) + ctx.Unlock() + return configcoreRun(tr) + }) } diff -Nru snapd-2.29.4.2+17.10/overlord/configstate/configstate.go snapd-2.31.1+17.10/overlord/configstate/configstate.go --- snapd-2.29.4.2+17.10/overlord/configstate/configstate.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/configstate/configstate.go 2017-12-01 15:51:55.000000000 +0000 @@ -30,6 +30,7 @@ "github.com/snapcore/snapd/overlord/hookstate" "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/snap" ) func init() { @@ -46,8 +47,31 @@ return timeout } +// ConfigureInstalled returns a taskset to apply the given +// configuration patch for an installed snap. It returns +// snap.NotInstalledError if the snap is not installed. +func ConfigureInstalled(st *state.State, snapName string, patch map[string]interface{}, flags int) (*state.TaskSet, error) { + // core is handled internally and can be configured before + // being installed + if snapName != "core" { + var snapst snapstate.SnapState + err := snapstate.Get(st, snapName, &snapst) + if err != nil && err != state.ErrNoState { + return nil, err + } + if !snapst.IsInstalled() { + return nil, &snap.NotInstalledError{Snap: snapName} + } + } + + taskset := Configure(st, snapName, patch, flags) + return taskset, nil +} + // Configure returns a taskset to apply the given configuration patch. -func Configure(s *state.State, snapName string, patch map[string]interface{}, flags int) *state.TaskSet { +func Configure(st *state.State, snapName string, patch map[string]interface{}, flags int) *state.TaskSet { + summary := fmt.Sprintf(i18n.G("Run configure hook of %q snap"), snapName) + // regular configuration hook hooksup := &hookstate.HookSetup{ Snap: snapName, Hook: "configure", @@ -63,12 +87,11 @@ } else if len(patch) > 0 { contextData = map[string]interface{}{"patch": patch} } - var summary string + if hooksup.Optional { summary = fmt.Sprintf(i18n.G("Run configure hook of %q snap if present"), snapName) - } else { - summary = fmt.Sprintf(i18n.G("Run configure hook of %q snap"), snapName) } - task := hookstate.HookTask(s, summary, hooksup, contextData) + + task := hookstate.HookTask(st, summary, hooksup, contextData) return state.NewTaskSet(task) } diff -Nru snapd-2.29.4.2+17.10/overlord/configstate/configstate_test.go snapd-2.31.1+17.10/overlord/configstate/configstate_test.go --- snapd-2.29.4.2+17.10/overlord/configstate/configstate_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/configstate/configstate_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -24,7 +24,9 @@ . "gopkg.in/check.v1" + "github.com/snapcore/snapd/overlord" "github.com/snapcore/snapd/overlord/configstate" + "github.com/snapcore/snapd/overlord/configstate/configcore" "github.com/snapcore/snapd/overlord/hookstate" "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" @@ -36,6 +38,7 @@ } var _ = Suite(&tasksetsSuite{}) +var _ = Suite(&configcoreHijackSuite{}) func (s *tasksetsSuite) SetUpTest(c *C) { s.state = state.New(nil) @@ -69,7 +72,17 @@ useDefaults: true, }} -func (s *tasksetsSuite) TestConfigure(c *C) { +func (s *tasksetsSuite) TestConfigureInstalled(c *C) { + s.state.Lock() + snapstate.Set(s.state, "test-snap", &snapstate.SnapState{ + Sequence: []*snap.SideInfo{ + {RealName: "test-snap", Revision: snap.R(1)}, + }, + Current: snap.R(1), + Active: true, + }) + s.state.Unlock() + for _, test := range configureTests { var flags int if test.ignoreError { @@ -129,3 +142,59 @@ c.Check(useDefaults, Equals, test.useDefaults) } } + +func (s *tasksetsSuite) TestConfigureNotInstalled(c *C) { + patch := map[string]interface{}{"foo": "bar"} + s.state.Lock() + defer s.state.Unlock() + + _, err := configstate.ConfigureInstalled(s.state, "test-snap", patch, 0) + c.Check(err, ErrorMatches, `snap "test-snap" is not installed`) + + // core can be configure before being installed + _, err = configstate.ConfigureInstalled(s.state, "core", patch, 0) + c.Check(err, IsNil) +} + +type configcoreHijackSuite struct { + o *overlord.Overlord + state *state.State +} + +func (s *configcoreHijackSuite) SetUpTest(c *C) { + s.o = overlord.Mock() + s.state = s.o.State() + hookMgr, err := hookstate.Manager(s.state) + c.Assert(err, IsNil) + s.o.AddManager(hookMgr) + configstate.Init(hookMgr) +} + +func (s *configcoreHijackSuite) TestHijack(c *C) { + configcoreRan := false + witnessConfigcoreRun := func(conf configcore.Conf) error { + // called with no state lock! + conf.State().Lock() + defer conf.State().Unlock() + configcoreRan = true + return nil + } + r := configstate.MockConfigcoreRun(witnessConfigcoreRun) + defer r() + + s.state.Lock() + defer s.state.Unlock() + + ts := configstate.Configure(s.state, "core", nil, 0) + + chg := s.state.NewChange("configure-core", "configure core") + chg.AddAll(ts) + + s.state.Unlock() + err := s.o.Settle(5 * time.Second) + s.state.Lock() + c.Assert(err, IsNil) + + c.Check(chg.Err(), IsNil) + c.Check(configcoreRan, Equals, true) +} diff -Nru snapd-2.29.4.2+17.10/overlord/devicestate/devicemgr.go snapd-2.31.1+17.10/overlord/devicestate/devicemgr.go --- snapd-2.29.4.2+17.10/overlord/devicestate/devicemgr.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/devicestate/devicemgr.go 2018-01-24 20:02:44.000000000 +0000 @@ -357,6 +357,10 @@ return strings.Join(parts, "\n - ") } +func (m *DeviceManager) KnownTaskKinds() []string { + return m.runner.KnownTaskKinds() +} + // Ensure implements StateManager.Ensure. func (m *DeviceManager) Ensure() error { var errs []error @@ -466,3 +470,11 @@ }, err } + +// ProxyStore returns the store assertion for the proxy store if one is set. +func (m *DeviceManager) ProxyStore() (*asserts.Store, error) { + m.state.Lock() + defer m.state.Unlock() + + return ProxyStore(m.state) +} diff -Nru snapd-2.29.4.2+17.10/overlord/devicestate/devicestate.go snapd-2.31.1+17.10/overlord/devicestate/devicestate.go --- snapd-2.29.4.2+17.10/overlord/devicestate/devicestate.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/devicestate/devicestate.go 2017-12-01 15:51:55.000000000 +0000 @@ -29,6 +29,8 @@ "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/overlord/assertstate" "github.com/snapcore/snapd/overlord/auth" + "github.com/snapcore/snapd/overlord/configstate/config" + "github.com/snapcore/snapd/overlord/ifacestate/ifacerepo" "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" "github.com/snapcore/snapd/release" @@ -196,4 +198,72 @@ snapstate.AddCheckSnapCallback(checkGadgetOrKernel) }) snapstate.CanAutoRefresh = canAutoRefresh + snapstate.CanManageRefreshes = CanManageRefreshes +} + +// ProxyStore returns the store assertion for the proxy store if one is set. +func ProxyStore(st *state.State) (*asserts.Store, error) { + tr := config.NewTransaction(st) + var proxyStore string + err := tr.GetMaybe("core", "proxy.store", &proxyStore) + if err != nil { + return nil, err + } + if proxyStore == "" { + return nil, state.ErrNoState + } + + a, err := assertstate.DB(st).Find(asserts.StoreType, map[string]string{ + "store": proxyStore, + }) + if asserts.IsNotFound(err) { + return nil, state.ErrNoState + } + if err != nil { + return nil, err + } + + return a.(*asserts.Store), nil +} + +// interfaceConnected returns true if the given snap/interface names +// are connected +func interfaceConnected(st *state.State, snapName, ifName string) bool { + conns, err := ifacerepo.Get(st).Connected(snapName, ifName) + return err == nil && len(conns) > 0 +} + +// CanManageRefreshes returns true if the device can be +// switched to the "core.refresh.schedule=managed" mode. +func CanManageRefreshes(st *state.State) bool { + snapStates, err := snapstate.All(st) + if err != nil { + return false + } + for _, snapst := range snapStates { + if !snapst.Active { + continue + } + info, err := snapst.CurrentInfo() + if err != nil { + continue + } + // The snap must have a snap declaration (implies that + // its from the store) + if _, err := assertstate.SnapDeclaration(st, info.SideInfo.SnapID); err != nil { + continue + } + + for _, plugInfo := range info.Plugs { + if plugInfo.Interface == "snapd-control" && plugInfo.Attrs["refresh-schedule"] == "managed" { + snapName := info.Name() + plugName := plugInfo.Name + if interfaceConnected(st, snapName, plugName) { + return true + } + } + } + } + + return false } diff -Nru snapd-2.29.4.2+17.10/overlord/devicestate/devicestate_test.go snapd-2.31.1+17.10/overlord/devicestate/devicestate_test.go --- snapd-2.29.4.2+17.10/overlord/devicestate/devicestate_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/devicestate/devicestate_test.go 2018-02-12 14:19:57.000000000 +0000 @@ -28,6 +28,7 @@ "net/http" "net/http/httptest" "os" + "sort" "sync" "testing" "time" @@ -42,15 +43,18 @@ "github.com/snapcore/snapd/boot/boottest" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/httputil" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/builtin" "github.com/snapcore/snapd/overlord" "github.com/snapcore/snapd/overlord/assertstate" "github.com/snapcore/snapd/overlord/auth" + "github.com/snapcore/snapd/overlord/configstate/config" "github.com/snapcore/snapd/overlord/devicestate" "github.com/snapcore/snapd/overlord/hookstate" "github.com/snapcore/snapd/overlord/hookstate/ctlcmd" + "github.com/snapcore/snapd/overlord/ifacestate/ifacerepo" "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" - "github.com/snapcore/snapd/overlord/storestate" "github.com/snapcore/snapd/partition" "github.com/snapcore/snapd/release" "github.com/snapcore/snapd/snap" @@ -149,7 +153,7 @@ s.o.AddManager(s.mgr) s.state.Lock() - storestate.ReplaceStore(s.state, &fakeStore{ + snapstate.ReplaceStore(s.state, &fakeStore{ state: s.state, db: s.storeSigning, }) @@ -175,10 +179,8 @@ } const ( - // will become "/api/v1/snaps/auth/request-id" - requestIDURLPath = "/identity/api/v1/request-id" - // will become "/api/v1/snaps/auth/serial" - serialURLPath = "/identity/api/v1/devices" + requestIDURLPath = "/api/v1/snaps/auth/request-id" + serialURLPath = "/api/v1/snaps/auth/devices" ) // seeding avoids triggering a real full seeding, it simulates having it in process instead @@ -195,6 +197,10 @@ return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case requestIDURLPath, "/svc/request-id": + if s.reqID == "REQID-501" { + w.WriteHeader(501) + return + } w.WriteHeader(200) c.Check(r.Header.Get("User-Agent"), Equals, expectedUserAgent) io.WriteString(w, fmt.Sprintf(`{"request-id": "%s"}`, s.reqID)) @@ -315,6 +321,12 @@ return nil } +func (s *deviceMgrSuite) TestKnownTaskKinds(c *C) { + kinds := s.mgr.KnownTaskKinds() + sort.Strings(kinds) + c.Assert(kinds, DeepEquals, []string{"generate-device-key", "mark-seeded", "request-serial"}) +} + func (s *deviceMgrSuite) TestFullDeviceRegistrationHappy(c *C) { r1 := devicestate.MockKeyLength(testKeyLength) defer r1() @@ -758,6 +770,120 @@ c.Check(device.Serial, Equals, "9999") } +func (s *deviceMgrSuite) TestDoRequestSerialErrorsOnNoHost(c *C) { + if os.Getenv("http_proxy") != "" { + c.Skip("cannot run test when http proxy is in use, the error pattern is different") + } + + privKey, _ := assertstest.GenerateKey(testKeyLength) + + nowhere := "http://nowhere.nowhere.test" + + mockRequestIDURL := nowhere + requestIDURLPath + restore := devicestate.MockRequestIDURL(mockRequestIDURL) + defer restore() + + mockSerialRequestURL := nowhere + serialURLPath + restore = devicestate.MockSerialRequestURL(mockSerialRequestURL) + defer restore() + + // setup state as done by first-boot/Ensure/doGenerateDeviceKey + s.state.Lock() + defer s.state.Unlock() + + s.setupGadget(c, ` +name: gadget +type: gadget +version: gadget +`, "") + + auth.SetDevice(s.state, &auth.DeviceState{ + Brand: "canonical", + Model: "pc", + KeyID: privKey.PublicKey().ID(), + }) + s.mgr.KeypairManager().Put(privKey) + + t := s.state.NewTask("request-serial", "test") + chg := s.state.NewChange("become-operational", "...") + chg.AddTask(t) + + // avoid full seeding + s.seeding() + + s.state.Unlock() + s.mgr.Ensure() + s.mgr.Wait() + s.state.Lock() + + c.Check(chg.Status(), Equals, state.ErrorStatus) +} + +func (s *deviceMgrSuite) TestDoRequestSerialMaxTentatives(c *C) { + privKey, _ := assertstest.GenerateKey(testKeyLength) + + // immediate + r := devicestate.MockRetryInterval(0) + defer r() + + r = devicestate.MockMaxTentatives(2) + defer r() + + s.reqID = "REQID-501" + mockServer := s.mockServer(c) + defer mockServer.Close() + + mockRequestIDURL := mockServer.URL + requestIDURLPath + restore := devicestate.MockRequestIDURL(mockRequestIDURL) + defer restore() + + mockSerialRequestURL := mockServer.URL + serialURLPath + restore = devicestate.MockSerialRequestURL(mockSerialRequestURL) + defer restore() + + restore = devicestate.MockRepeatRequestSerial("after-add-serial") + defer restore() + + // setup state as done by first-boot/Ensure/doGenerateDeviceKey + s.state.Lock() + defer s.state.Unlock() + + s.setupGadget(c, ` +name: gadget +type: gadget +version: gadget +`, "") + + auth.SetDevice(s.state, &auth.DeviceState{ + Brand: "canonical", + Model: "pc", + KeyID: privKey.PublicKey().ID(), + }) + s.mgr.KeypairManager().Put(privKey) + + t := s.state.NewTask("request-serial", "test") + chg := s.state.NewChange("become-operational", "...") + chg.AddTask(t) + + // avoid full seeding + s.seeding() + + s.state.Unlock() + s.mgr.Ensure() + s.mgr.Wait() + s.state.Lock() + + c.Check(chg.Status(), Equals, state.DoingStatus) + + s.state.Unlock() + s.mgr.Ensure() + s.mgr.Wait() + s.state.Lock() + + c.Check(chg.Status(), Equals, state.ErrorStatus) + c.Check(chg.Err(), ErrorMatches, `(?s).*cannot retrieve request-id for making a request for a serial: unexpected status 501.*`) +} + func (s *deviceMgrSuite) TestFullDeviceRegistrationPollHappy(c *C) { r1 := devicestate.MockKeyLength(testKeyLength) defer r1() @@ -1258,6 +1384,55 @@ c.Check(sessReq.Nonce(), Equals, "NONCE-1") } +func (s *deviceMgrSuite) TestDeviceAssertionsProxyStore(c *C) { + // nothing in the state + s.state.Lock() + _, err := devicestate.ProxyStore(s.state) + s.state.Unlock() + c.Check(err, Equals, state.ErrNoState) + + _, err = s.mgr.ProxyStore() + c.Check(err, Equals, state.ErrNoState) + + // have a store referenced + s.state.Lock() + tr := config.NewTransaction(s.state) + err = tr.Set("core", "proxy.store", "foo") + tr.Commit() + s.state.Unlock() + c.Assert(err, IsNil) + _, err = s.mgr.ProxyStore() + c.Check(err, Equals, state.ErrNoState) + + operatorAcct := assertstest.NewAccount(s.storeSigning, "foo-operator", nil, "") + s.state.Lock() + err = assertstate.Add(s.state, operatorAcct) + s.state.Unlock() + c.Assert(err, IsNil) + + // have a store assertion. + stoAs, err := s.storeSigning.Sign(asserts.StoreType, map[string]interface{}{ + "store": "foo", + "operator-id": operatorAcct.AccountID(), + "timestamp": time.Now().Format(time.RFC3339), + }, nil, "") + c.Assert(err, IsNil) + s.state.Lock() + err = assertstate.Add(s.state, stoAs) + s.state.Unlock() + c.Assert(err, IsNil) + + sto, err := s.mgr.ProxyStore() + c.Assert(err, IsNil) + c.Assert(sto.Store(), Equals, "foo") + + s.state.Lock() + sto, err = devicestate.ProxyStore(s.state) + s.state.Unlock() + c.Assert(err, IsNil) + c.Assert(sto.Store(), Equals, "foo") +} + func (s *deviceMgrSuite) TestDeviceManagerEnsureSeedYamlAlreadySeeded(c *C) { s.state.Lock() s.state.Set("seeded", true) @@ -1882,3 +2057,127 @@ s.state.Set("seeded", false) c.Check(canAutoRefresh(), Equals, false) } + +func makeInstalledMockCoreSnapWithSnapdControl(c *C, st *state.State) *snap.Info { + sideInfoCore11 := &snap.SideInfo{RealName: "core", Revision: snap.R(11), SnapID: "core-id"} + snapstate.Set(st, "core", &snapstate.SnapState{ + Active: true, + Sequence: []*snap.SideInfo{sideInfoCore11}, + Current: sideInfoCore11.Revision, + SnapType: "os", + }) + core11 := snaptest.MockSnap(c, ` +name: core +version: 1.0 +slots: + snapd-control: +`, "", sideInfoCore11) + c.Assert(core11.Slots, HasLen, 1) + + return core11 +} + +var snapWithSnapdControlRefreshScheduleManagedYAML = ` +name: snap-with-snapd-control +version: 1.0 +plugs: + snapd-control: + refresh-schedule: managed +` + +var snapWithSnapdControlOnlyYAML = ` +name: snap-with-snapd-control +version: 1.0 +plugs: + snapd-control: +` + +func makeInstalledMockSnap(c *C, st *state.State, yml string) *snap.Info { + sideInfo11 := &snap.SideInfo{RealName: "snap-with-snapd-control", Revision: snap.R(11), SnapID: "snap-with-snapd-control-id"} + snapstate.Set(st, "snap-with-snapd-control", &snapstate.SnapState{ + Active: true, + Sequence: []*snap.SideInfo{sideInfo11}, + Current: sideInfo11.Revision, + SnapType: "app", + }) + info11 := snaptest.MockSnap(c, yml, "", sideInfo11) + c.Assert(info11.Plugs, HasLen, 1) + + return info11 +} + +func makeMockRepoWithConnectedSnaps(c *C, st *state.State, info11, core11 *snap.Info, ifname string) { + repo := interfaces.NewRepository() + for _, iface := range builtin.Interfaces() { + err := repo.AddInterface(iface) + c.Assert(err, IsNil) + } + err := repo.AddSnap(info11) + c.Assert(err, IsNil) + err = repo.AddSnap(core11) + c.Assert(err, IsNil) + err = repo.Connect(interfaces.ConnRef{ + PlugRef: interfaces.PlugRef{Snap: info11.Name(), Name: ifname}, + SlotRef: interfaces.SlotRef{Snap: core11.Name(), Name: ifname}, + }) + c.Assert(err, IsNil) + conns, err := repo.Connected("snap-with-snapd-control", "snapd-control") + c.Assert(err, IsNil) + c.Assert(conns, HasLen, 1) + ifacerepo.Replace(st, repo) +} + +func (s *deviceMgrSuite) makeSnapDeclaration(c *C, st *state.State, info *snap.Info) { + decl, err := s.storeSigning.Sign(asserts.SnapDeclarationType, map[string]interface{}{ + "series": "16", + "snap-name": info.Name(), + "snap-id": info.SideInfo.SnapID, + "publisher-id": "canonical", + "timestamp": time.Now().UTC().Format(time.RFC3339), + }, nil, "") + c.Assert(err, IsNil) + err = assertstate.Add(s.state, decl) + c.Assert(err, IsNil) +} + +func (s *deviceMgrSuite) TestCanManageRefreshes(c *C) { + st := s.state + st.Lock() + defer st.Unlock() + + // not possbile to manage by default + c.Check(devicestate.CanManageRefreshes(st), Equals, false) + + // not possible with just a snap with "snapd-control" plug with the + // right attribute + info11 := makeInstalledMockSnap(c, st, snapWithSnapdControlRefreshScheduleManagedYAML) + c.Check(devicestate.CanManageRefreshes(st), Equals, false) + + // not possible with a core snap with snapd control + core11 := makeInstalledMockCoreSnapWithSnapdControl(c, st) + c.Check(devicestate.CanManageRefreshes(st), Equals, false) + + // not possible even with connected interfaces + makeMockRepoWithConnectedSnaps(c, st, info11, core11, "snapd-control") + c.Check(devicestate.CanManageRefreshes(st), Equals, false) + + // if all of the above plus a snap declaration are in place we can + // manage schedules + s.makeSnapDeclaration(c, st, info11) + c.Check(devicestate.CanManageRefreshes(st), Equals, true) +} + +func (s *deviceMgrSuite) TestCanManageRefreshesNoRefreshScheduleManaged(c *C) { + st := s.state + st.Lock() + defer st.Unlock() + + // just having a connected "snapd-control" interface is not enough + // for setting refresh.schedule=managed + info11 := makeInstalledMockSnap(c, st, snapWithSnapdControlOnlyYAML) + core11 := makeInstalledMockCoreSnapWithSnapdControl(c, st) + makeMockRepoWithConnectedSnaps(c, st, info11, core11, "snapd-control") + s.makeSnapDeclaration(c, st, info11) + + c.Check(devicestate.CanManageRefreshes(st), Equals, false) +} diff -Nru snapd-2.29.4.2+17.10/overlord/devicestate/export_test.go snapd-2.31.1+17.10/overlord/devicestate/export_test.go --- snapd-2.29.4.2+17.10/overlord/devicestate/export_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/devicestate/export_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -62,6 +62,14 @@ } } +func MockMaxTentatives(max int) (restore func()) { + old := maxTentatives + maxTentatives = max + return func() { + maxTentatives = old + } +} + func (m *DeviceManager) KeypairManager() asserts.KeypairManager { return m.keypairMgr } diff -Nru snapd-2.29.4.2+17.10/overlord/devicestate/firstboot_test.go snapd-2.31.1+17.10/overlord/devicestate/firstboot_test.go --- snapd-2.29.4.2+17.10/overlord/devicestate/firstboot_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/devicestate/firstboot_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -40,7 +40,9 @@ "github.com/snapcore/snapd/overlord" "github.com/snapcore/snapd/overlord/assertstate" "github.com/snapcore/snapd/overlord/auth" + "github.com/snapcore/snapd/overlord/configstate" "github.com/snapcore/snapd/overlord/configstate/config" + "github.com/snapcore/snapd/overlord/configstate/configcore" "github.com/snapcore/snapd/overlord/devicestate" "github.com/snapcore/snapd/overlord/hookstate" "github.com/snapcore/snapd/overlord/ifacestate" @@ -743,6 +745,13 @@ rhk := hookstate.MockRunHook(hookInvoke) defer rhk() + // ensure we have something that captures the core config + restore := configstate.MockConfigcoreRun(func(configcore.Conf) error { + configured = append(configured, "configcore") + return nil + }) + defer restore() + // avoid device reg chg1 := st.NewChange("become-operational", "init device") chg1.SetStatus(state.DoingStatus) @@ -800,7 +809,7 @@ c.Assert(err, IsNil) c.Check(val, Equals, "foo.") - c.Check(configured, DeepEquals, []string{"core", "pc-kernel", "pc", "foo"}) + c.Check(configured, DeepEquals, []string{"configcore", "pc-kernel", "pc", "foo"}) // and ensure state is now considered seeded var seeded bool diff -Nru snapd-2.29.4.2+17.10/overlord/devicestate/handlers.go snapd-2.31.1+17.10/overlord/devicestate/handlers.go --- snapd-2.29.4.2+17.10/overlord/devicestate/handlers.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/devicestate/handlers.go 2017-12-01 15:51:55.000000000 +0000 @@ -23,6 +23,7 @@ "encoding/json" "errors" "fmt" + "net" "net/http" "net/url" "time" @@ -37,7 +38,6 @@ "github.com/snapcore/snapd/overlord/configstate/config" "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" - "github.com/snapcore/snapd/overlord/storestate" ) func (m *DeviceManager) doMarkSeeded(t *state.Task, _ *tomb.Tomb) error { @@ -55,14 +55,15 @@ func deviceAPIBaseURL() string { if useStaging() { - return "https://myapps.developer.staging.ubuntu.com/identity/api/v1/" + return "https://api.staging.snapcraft.io/api/v1/snaps/auth/" } - return "https://myapps.developer.ubuntu.com/identity/api/v1/" + return "https://api.snapcraft.io/api/v1/snaps/auth/" } var ( keyLength = 4096 retryInterval = 60 * time.Second + maxTentatives = 15 deviceAPIBase = deviceAPIBaseURL() requestIDURL = deviceAPIBase + "request-id" serialRequestURL = deviceAPIBase + "devices" @@ -114,9 +115,12 @@ RequestID string `json:"request-id"` } -func retryErr(t *state.Task, reason string, a ...interface{}) error { +func retryErr(t *state.Task, nTentatives int, reason string, a ...interface{}) error { t.State().Lock() defer t.State().Unlock() + if nTentatives >= maxTentatives { + return fmt.Errorf(reason, a...) + } t.Errorf(reason, a...) return &state.Retry{After: retryInterval} } @@ -126,10 +130,10 @@ Errors []*serverError `json:"error_list"` } -func retryBadStatus(t *state.Task, reason string, resp *http.Response) error { +func retryBadStatus(t *state.Task, nTentatives int, reason string, resp *http.Response) error { if resp.StatusCode > 500 { // likely temporary - return retryErr(t, "%s: unexpected status %d", reason, resp.StatusCode) + return retryErr(t, nTentatives, "%s: unexpected status %d", reason, resp.StatusCode) } if resp.Header.Get("Content-Type") == "application/json" { var srvErr serverError @@ -149,6 +153,16 @@ } func prepareSerialRequest(t *state.Task, privKey asserts.PrivateKey, device *auth.DeviceState, client *http.Client, cfg *serialRequestConfig) (string, error) { + // limit tentatives starting from scratch before going to + // slower full retries + var nTentatives int + err := t.Get("pre-poll-tentatives", &nTentatives) + if err != nil && err != state.ErrNoState { + return "", err + } + nTentatives++ + t.Set("pre-poll-tentatives", nTentatives) + st := t.State() st.Unlock() defer st.Lock() @@ -162,18 +176,23 @@ resp, err := client.Do(req) if err != nil { - return "", retryErr(t, "cannot retrieve request-id for making a request for a serial: %v", err) + if netErr, ok := err.(net.Error); ok && !netErr.Temporary() { + // a non temporary net error, like a DNS no + // host, error out and do full retries + return "", fmt.Errorf("cannot retrieve request-id for making a request for a serial: %v", err) + } + return "", retryErr(t, nTentatives, "cannot retrieve request-id for making a request for a serial: %v", err) } defer resp.Body.Close() if resp.StatusCode != 200 { - return "", retryBadStatus(t, "cannot retrieve request-id for making a request for a serial", resp) + return "", retryBadStatus(t, nTentatives, "cannot retrieve request-id for making a request for a serial", resp) } dec := json.NewDecoder(resp.Body) var requestID requestIDResp err = dec.Decode(&requestID) if err != nil { // assume broken i/o - return "", retryErr(t, "cannot read response with request-id for making a request for a serial: %v", err) + return "", retryErr(t, nTentatives, "cannot read response with request-id for making a request for a serial: %v", err) } encodedPubKey, err := asserts.EncodePublicKey(privKey.PublicKey()) @@ -217,7 +236,7 @@ resp, err := client.Do(req) if err != nil { - return nil, retryErr(t, "cannot deliver device serial request: %v", err) + return nil, retryErr(t, 0, "cannot deliver device serial request: %v", err) } defer resp.Body.Close() @@ -226,7 +245,7 @@ case 202: return nil, errPoll default: - return nil, retryBadStatus(t, "cannot deliver device serial request", resp) + return nil, retryBadStatus(t, 0, "cannot deliver device serial request", resp) } // TODO: support a stream of assertions instead of just the serial @@ -235,7 +254,7 @@ dec := asserts.NewDecoder(resp.Body) got, err := dec.Decode() if err != nil { // assume broken i/o - return nil, retryErr(t, "cannot read response to request for a serial: %v", err) + return nil, retryErr(t, 0, "cannot read response to request for a serial: %v", err) } serial, ok := got.(*asserts.Serial) @@ -491,7 +510,7 @@ var repeatRequestSerial string // for tests func fetchKeys(st *state.State, keyID string) (errAcctKey error, err error) { - sto := storestate.Store(st) + sto := snapstate.Store(st) db := assertstate.DB(st) for { _, err := db.FindPredefined(asserts.AccountKeyType, map[string]string{ diff -Nru snapd-2.29.4.2+17.10/overlord/export_test.go snapd-2.31.1+17.10/overlord/export_test.go --- snapd-2.29.4.2+17.10/overlord/export_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/export_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -23,8 +23,9 @@ "time" "github.com/snapcore/snapd/overlord/auth" - "github.com/snapcore/snapd/overlord/state" - "github.com/snapcore/snapd/overlord/storestate" + "github.com/snapcore/snapd/overlord/configstate" + "github.com/snapcore/snapd/overlord/hookstate" + "github.com/snapcore/snapd/store" ) // MockEnsureInterval sets the overlord ensure interval for tests. @@ -59,10 +60,17 @@ return o.stateEng } -// MockSetupStore mocks storestate.SetupStore as called by overlord.New. -func MockSetupStore(new func(*state.State, auth.AuthContext) error) (restore func()) { - setupStore = new +// MockStoreNew mocks store.New as called by overlord.New. +func MockStoreNew(new func(*store.Config, auth.AuthContext) *store.Store) (restore func()) { + storeNew = new return func() { - setupStore = storestate.SetupStore + storeNew = store.New + } +} + +func MockConfigstateInit(new func(hookmgr *hookstate.HookManager)) (restore func()) { + configstateInit = new + return func() { + configstateInit = configstate.Init } } diff -Nru snapd-2.29.4.2+17.10/overlord/hookstate/ctlcmd/export_test.go snapd-2.31.1+17.10/overlord/hookstate/ctlcmd/export_test.go --- snapd-2.29.4.2+17.10/overlord/hookstate/ctlcmd/export_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/hookstate/ctlcmd/export_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -21,15 +21,15 @@ import ( "fmt" + "github.com/snapcore/snapd/overlord/hookstate" "github.com/snapcore/snapd/overlord/servicestate" "github.com/snapcore/snapd/overlord/state" "github.com/snapcore/snapd/snap" ) var AttributesTask = attributesTask -var CopyAttributes = copyAttributes -func MockServicestateControlFunc(f func(*state.State, []*snap.AppInfo, *servicestate.Instruction) (*state.TaskSet, error)) (restore func()) { +func MockServicestateControlFunc(f func(*state.State, []*snap.AppInfo, *servicestate.Instruction, *hookstate.Context) (*state.TaskSet, error)) (restore func()) { old := servicestateControl servicestateControl = f return func() { servicestateControl = old } diff -Nru snapd-2.29.4.2+17.10/overlord/hookstate/ctlcmd/helpers.go snapd-2.31.1+17.10/overlord/hookstate/ctlcmd/helpers.go --- snapd-2.29.4.2+17.10/overlord/hookstate/ctlcmd/helpers.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/hookstate/ctlcmd/helpers.go 2017-12-01 15:51:55.000000000 +0000 @@ -69,20 +69,36 @@ var servicestateControl = servicestate.Control -func queueCommand(context *hookstate.Context, ts *state.TaskSet) { - // queue command task after all existing tasks of the hook's change +func queueCommand(context *hookstate.Context, ts *state.TaskSet) error { + hookTask, ok := context.Task() + if !ok { + return fmt.Errorf("attempted to queue command with ephemeral context") + } + st := context.State() st.Lock() defer st.Unlock() - task, ok := context.Task() - if !ok { - panic("attempted to queue command with ephemeral context") + change := hookTask.Change() + hookTaskLanes := hookTask.Lanes() + tasks := change.LaneTasks(hookTaskLanes...) + + // When installing or updating multiple snaps, there is one lane per snap. + // We want service command to join respective lane (it's the lane the hook belongs to). + // In case there are no lanes, only the default lane no. 0, there is no need to join it. + if len(hookTaskLanes) == 1 && hookTaskLanes[0] == 0 { + hookTaskLanes = nil + } + for _, l := range hookTaskLanes { + ts.JoinLane(l) } - change := task.Change() - tasks := change.Tasks() ts.WaitAll(state.NewTaskSet(tasks...)) change.AddAll(ts) + // As this can be run from what was originally the last task of a change, + // make sure the tasks added to the change are considered immediately. + st.EnsureBefore(0) + + return nil } func runServiceCommand(context *hookstate.Context, inst *servicestate.Instruction, serviceNames []string) error { @@ -96,14 +112,14 @@ return err } - ts, err := servicestateControl(st, appInfos, inst) + // passing context so we can ignore self-conflicts with the current change + ts, err := servicestateControl(st, appInfos, inst, context) if err != nil { return err } if !context.IsEphemeral() && context.HookName() == "configure" { - queueCommand(context, ts) - return nil + return queueCommand(context, ts) } st.Lock() diff -Nru snapd-2.29.4.2+17.10/overlord/hookstate/ctlcmd/restart.go snapd-2.31.1+17.10/overlord/hookstate/ctlcmd/restart.go --- snapd-2.29.4.2+17.10/overlord/hookstate/ctlcmd/restart.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/hookstate/ctlcmd/restart.go 2017-12-01 15:51:55.000000000 +0000 @@ -33,8 +33,7 @@ ) func init() { - // FIXME: uncomment once the feature is fixed to work on install/refresh - // addCommand("restart", shortRestartHelp, longRestartHelp, func() command { return &restartCommand{} }) + addCommand("restart", shortRestartHelp, longRestartHelp, func() command { return &restartCommand{} }) } type restartCommand struct { diff -Nru snapd-2.29.4.2+17.10/overlord/hookstate/ctlcmd/services_test.go snapd-2.31.1+17.10/overlord/hookstate/ctlcmd/services_test.go --- snapd-2.29.4.2+17.10/overlord/hookstate/ctlcmd/services_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/hookstate/ctlcmd/services_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -21,11 +21,13 @@ import ( "fmt" + "sort" + . "gopkg.in/check.v1" "github.com/snapcore/snapd/client" "github.com/snapcore/snapd/dirs" - "github.com/snapcore/snapd/overlord/configstate" + "github.com/snapcore/snapd/overlord/auth" "github.com/snapcore/snapd/overlord/hookstate" "github.com/snapcore/snapd/overlord/hookstate/ctlcmd" "github.com/snapcore/snapd/overlord/hookstate/hooktest" @@ -34,12 +36,49 @@ "github.com/snapcore/snapd/overlord/state" "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap/snaptest" + "github.com/snapcore/snapd/store" + "github.com/snapcore/snapd/store/storetest" "github.com/snapcore/snapd/testutil" ) +type fakeStore struct { + storetest.Store +} + +func (f *fakeStore) SnapInfo(spec store.SnapSpec, user *auth.UserState) (*snap.Info, error) { + return &snap.Info{ + SideInfo: snap.SideInfo{ + RealName: spec.Name, + Revision: snap.R(2), + }, + Publisher: "foo", + Architectures: []string{"all"}, + }, nil +} + +func (f *fakeStore) ListRefresh(cand []*store.RefreshCandidate, user *auth.UserState, opt *store.RefreshOptions) ([]*snap.Info, error) { + return []*snap.Info{{ + SideInfo: snap.SideInfo{ + RealName: "test-snap", + Revision: snap.R(2), + SnapID: "test-snap-id", + }, + Publisher: "foo", + Architectures: []string{"all"}, + }, {SideInfo: snap.SideInfo{ + RealName: "other-snap", + Revision: snap.R(2), + SnapID: "other-snap-id", + }, + Publisher: "foo", + Architectures: []string{"all"}, + }}, nil +} + type servicectlSuite struct { testutil.BaseTest st *state.State + fakeStore fakeStore mockContext *hookstate.Context mockHandler *hooktest.MockHandler } @@ -69,15 +108,13 @@ ` func mockServiceChangeFunc(testServiceControlInputs func(appInfos []*snap.AppInfo, inst *servicestate.Instruction)) func() { - return ctlcmd.MockServicestateControlFunc(func(st *state.State, appInfos []*snap.AppInfo, inst *servicestate.Instruction) (*state.TaskSet, error) { + return ctlcmd.MockServicestateControlFunc(func(st *state.State, appInfos []*snap.AppInfo, inst *servicestate.Instruction, context *hookstate.Context) (*state.TaskSet, error) { testServiceControlInputs(appInfos, inst) return nil, fmt.Errorf("forced error") }) } func (s *servicectlSuite) SetUpTest(c *C) { - c.Skip("disabled until snapctl start/stop/restart commands are restored") - s.BaseTest.SetUpTest(c) oldRoot := dirs.GlobalRootDir dirs.SetRootDir(c.MkDir()) @@ -95,6 +132,8 @@ s.st.Lock() defer s.st.Unlock() + snapstate.ReplaceStore(s.st, &s.fakeStore) + // mock installed snaps info1 := snaptest.MockSnap(c, string(testSnapYaml), "", &snap.SideInfo{ Revision: snap.R(1), @@ -228,14 +267,138 @@ c.Assert(serviceChangeFuncCalled, Equals, true) } +func (s *servicectlSuite) TestConflictingChange(c *C) { + s.st.Lock() + task := s.st.NewTask("link-snap", "conflicting task") + snapsup := snapstate.SnapSetup{ + SideInfo: &snap.SideInfo{ + RealName: "test-snap", + SnapID: "test-snap-id-1", + Revision: snap.R(1), + }, + } + task.Set("snap-setup", snapsup) + chg := s.st.NewChange("conflicting change", "install change") + chg.AddTask(task) + s.st.Unlock() + + _, _, err := ctlcmd.Run(s.mockContext, []string{"start", "test-snap.test-service"}) + c.Check(err, NotNil) + c.Check(err, ErrorMatches, `snap "test-snap" has "conflicting change" change in progress`) +} + func (s *servicectlSuite) TestQueuedCommands(c *C) { s.st.Lock() - ts := configstate.Configure(s.st, "test-snap", nil, 0) - chg := s.st.NewChange("configure change", "configure change") + + chg := s.st.NewChange("install change", "install change") + installed, tts, err := snapstate.InstallMany(s.st, []string{"one", "two"}, 0) + c.Assert(err, IsNil) + c.Check(installed, DeepEquals, []string{"one", "two"}) + c.Assert(tts, HasLen, 2) + c.Assert(tts[0].Tasks(), HasLen, 12) + c.Assert(tts[1].Tasks(), HasLen, 12) + chg.AddAll(tts[0]) + chg.AddAll(tts[1]) + + s.st.Unlock() + + for _, ts := range tts { + tsTasks := ts.Tasks() + // assumes configure task is last + task := tsTasks[len(tsTasks)-1] + c.Assert(task.Kind(), Equals, "run-hook") + setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "configure"} + context, err := hookstate.NewContext(task, task.State(), setup, s.mockHandler, "") + c.Assert(err, IsNil) + + _, _, err = ctlcmd.Run(context, []string{"stop", "test-snap.test-service"}) + c.Check(err, IsNil) + _, _, err = ctlcmd.Run(context, []string{"start", "test-snap.test-service"}) + c.Check(err, IsNil) + _, _, err = ctlcmd.Run(context, []string{"restart", "test-snap.test-service"}) + c.Check(err, IsNil) + } + + s.st.Lock() + defer s.st.Unlock() + + for i := 1; i <= 2; i++ { + laneTasks := chg.LaneTasks(i) + c.Assert(laneTasks, HasLen, 15) + c.Check(laneTasks[11].Summary(), Matches, `Run configure hook of .* snap if present`) + c.Check(laneTasks[12].Summary(), Equals, "stop of [test-snap.test-service]") + c.Check(laneTasks[13].Summary(), Equals, "start of [test-snap.test-service]") + c.Check(laneTasks[14].Summary(), Equals, "restart of [test-snap.test-service]") + } +} + +func (s *servicectlSuite) TestQueuedCommandsUpdateMany(c *C) { + oldAutoAliases := snapstate.AutoAliases + snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) { + return nil, nil + } + defer func() { snapstate.AutoAliases = oldAutoAliases }() + + s.st.Lock() + + chg := s.st.NewChange("update many change", "update change") + installed, tts, err := snapstate.UpdateMany(s.st, []string{"test-snap", "other-snap"}, 0) + c.Assert(err, IsNil) + sort.Strings(installed) + c.Check(installed, DeepEquals, []string{"other-snap", "test-snap"}) + c.Assert(tts, HasLen, 2) + c.Assert(tts[0].Tasks(), HasLen, 17) + c.Assert(tts[1].Tasks(), HasLen, 17) + chg.AddAll(tts[0]) + chg.AddAll(tts[1]) + + s.st.Unlock() + + for _, ts := range tts { + tsTasks := ts.Tasks() + // assumes configure task is last + task := tsTasks[len(tsTasks)-1] + c.Assert(task.Kind(), Equals, "run-hook") + setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "configure"} + context, err := hookstate.NewContext(task, task.State(), setup, s.mockHandler, "") + c.Assert(err, IsNil) + + _, _, err = ctlcmd.Run(context, []string{"stop", "test-snap.test-service"}) + c.Check(err, IsNil) + _, _, err = ctlcmd.Run(context, []string{"start", "test-snap.test-service"}) + c.Check(err, IsNil) + _, _, err = ctlcmd.Run(context, []string{"restart", "test-snap.test-service"}) + c.Check(err, IsNil) + } + + s.st.Lock() + defer s.st.Unlock() + + for i := 1; i <= 2; i++ { + laneTasks := chg.LaneTasks(i) + c.Assert(laneTasks, HasLen, 20) + c.Check(laneTasks[16].Summary(), Matches, `Run configure hook of .* snap if present`) + c.Check(laneTasks[17].Summary(), Equals, "stop of [test-snap.test-service]") + c.Check(laneTasks[18].Summary(), Equals, "start of [test-snap.test-service]") + c.Check(laneTasks[19].Summary(), Equals, "restart of [test-snap.test-service]") + } +} + +func (s *servicectlSuite) TestQueuedCommandsSingleLane(c *C) { + s.st.Lock() + + chg := s.st.NewChange("install change", "install change") + ts, err := snapstate.Install(s.st, "one", "", snap.R(1), 0, snapstate.Flags{}) + c.Assert(err, IsNil) + c.Assert(ts.Tasks(), HasLen, 12) chg.AddAll(ts) + s.st.Unlock() - task := ts.Tasks()[0] + tsTasks := ts.Tasks() + // assumes configure task is last + task := tsTasks[len(tsTasks)-1] + c.Assert(task.Kind(), Equals, "run-hook") setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "configure"} context, err := hookstate.NewContext(task, task.State(), setup, s.mockHandler, "") c.Assert(err, IsNil) @@ -250,10 +413,10 @@ s.st.Lock() defer s.st.Unlock() - allTasks := chg.Tasks() - c.Assert(allTasks, HasLen, 4) - c.Check(allTasks[0].Summary(), Equals, `Run configure hook of "test-snap" snap if present`) - c.Check(allTasks[1].Summary(), Equals, "stop of [test-snap.test-service]") - c.Check(allTasks[2].Summary(), Equals, "start of [test-snap.test-service]") - c.Check(allTasks[3].Summary(), Equals, "restart of [test-snap.test-service]") + laneTasks := chg.LaneTasks(0) + c.Assert(laneTasks, HasLen, 15) + c.Check(laneTasks[11].Summary(), Matches, `Run configure hook of .* snap if present`) + c.Check(laneTasks[12].Summary(), Equals, "stop of [test-snap.test-service]") + c.Check(laneTasks[13].Summary(), Equals, "start of [test-snap.test-service]") + c.Check(laneTasks[14].Summary(), Equals, "restart of [test-snap.test-service]") } diff -Nru snapd-2.29.4.2+17.10/overlord/hookstate/ctlcmd/set.go snapd-2.31.1+17.10/overlord/hookstate/ctlcmd/set.go --- snapd-2.29.4.2+17.10/overlord/hookstate/ctlcmd/set.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/hookstate/ctlcmd/set.go 2017-12-01 15:51:55.000000000 +0000 @@ -163,46 +163,3 @@ attrsTask.Set(which, attributes) return nil } - -func copyAttributes(value map[string]interface{}) (map[string]interface{}, error) { - cpy, err := copyRecursive(value) - if err != nil { - return nil, err - } - return cpy.(map[string]interface{}), err -} - -func copyRecursive(value interface{}) (interface{}, error) { - switch v := value.(type) { - case string: - return v, nil - case bool: - return v, nil - case int: - return int64(v), nil - case int64: - return v, nil - case []interface{}: - arr := make([]interface{}, len(v)) - for i, el := range v { - tmp, err := copyRecursive(el) - if err != nil { - return nil, err - } - arr[i] = tmp - } - return arr, nil - case map[string]interface{}: - mp := make(map[string]interface{}, len(v)) - for key, item := range v { - tmp, err := copyRecursive(item) - if err != nil { - return nil, err - } - mp[key] = tmp - } - return mp, nil - default: - return nil, fmt.Errorf("unsupported attribute type '%T', value '%v'", value, value) - } -} diff -Nru snapd-2.29.4.2+17.10/overlord/hookstate/ctlcmd/set_test.go snapd-2.31.1+17.10/overlord/hookstate/ctlcmd/set_test.go --- snapd-2.29.4.2+17.10/overlord/hookstate/ctlcmd/set_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/hookstate/ctlcmd/set_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -21,7 +21,6 @@ import ( "encoding/json" - "reflect" "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/overlord/configstate/config" @@ -280,35 +279,3 @@ c.Check(string(stdout), Equals, "") c.Check(string(stderr), Equals, "") } - -func (s *setAttrSuite) TestCopyAttributes(c *C) { - orig := map[string]interface{}{ - "a": "A", - "b": true, - "c": int(100), - "d": []interface{}{"x", "y", true}, - "e": map[string]interface{}{ - "e1": "E1", - }, - } - - cpy, err := ctlcmd.CopyAttributes(orig) - c.Assert(err, IsNil) - // verify that int is converted into int64 - c.Check(reflect.TypeOf(cpy["c"]).Kind(), Equals, reflect.Int64) - c.Check(reflect.TypeOf(orig["c"]).Kind(), Equals, reflect.Int) - // change the type of orig's value to int64 to make DeepEquals happy in the test - orig["c"] = int64(100) - c.Check(cpy, DeepEquals, orig) - - cpy["d"].([]interface{})[0] = 999 - c.Check(orig["d"].([]interface{})[0], Equals, "x") - cpy["e"].(map[string]interface{})["e1"] = "x" - c.Check(orig["e"].(map[string]interface{})["e1"], Equals, "E1") - - type unsupported struct{} - var x unsupported - _, err = ctlcmd.CopyAttributes(map[string]interface{}{"x": x}) - c.Assert(err, NotNil) - c.Check(err, ErrorMatches, "unsupported attribute type 'ctlcmd_test.unsupported', value '{}'") -} diff -Nru snapd-2.29.4.2+17.10/overlord/hookstate/ctlcmd/start.go snapd-2.31.1+17.10/overlord/hookstate/ctlcmd/start.go --- snapd-2.29.4.2+17.10/overlord/hookstate/ctlcmd/start.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/hookstate/ctlcmd/start.go 2017-12-01 15:51:55.000000000 +0000 @@ -33,8 +33,7 @@ ) func init() { - // FIXME: uncomment once the feature is fixed to work on install/refresh - // addCommand("start", shortStartHelp, longStartHelp, func() command { return &startCommand{} }) + addCommand("start", shortStartHelp, longStartHelp, func() command { return &startCommand{} }) } type startCommand struct { diff -Nru snapd-2.29.4.2+17.10/overlord/hookstate/ctlcmd/stop.go snapd-2.31.1+17.10/overlord/hookstate/ctlcmd/stop.go --- snapd-2.29.4.2+17.10/overlord/hookstate/ctlcmd/stop.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/hookstate/ctlcmd/stop.go 2017-12-01 15:51:55.000000000 +0000 @@ -41,8 +41,7 @@ ) func init() { - // FIXME: uncomment once the feature is fixed to work on install/refresh - // addCommand("stop", shortStopHelp, longStopHelp, func() command { return &stopCommand{} }) + addCommand("stop", shortStopHelp, longStopHelp, func() command { return &stopCommand{} }) } func (c *stopCommand) Execute(args []string) error { diff -Nru snapd-2.29.4.2+17.10/overlord/hookstate/hookmgr.go snapd-2.31.1+17.10/overlord/hookstate/hookmgr.go --- snapd-2.29.4.2+17.10/overlord/hookstate/hookmgr.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/hookstate/hookmgr.go 2018-01-24 20:02:44.000000000 +0000 @@ -39,6 +39,9 @@ "github.com/snapcore/snapd/snap" ) +type hijackFunc func(ctx *Context) error +type hijackKey struct{ hook, snap string } + // HookManager is responsible for the maintenance of hooks in the system state. // It runs hooks when they're requested, assuming they're present in the given // snap. Otherwise they're skipped with no error. @@ -49,6 +52,8 @@ contextsMutex sync.RWMutex contexts map[string]*Context + + hijackMap map[hijackKey]hijackFunc } // Handler is the interface a client must satify to handle hooks. @@ -111,9 +116,16 @@ runner: runner, repository: newRepository(), contexts: make(map[string]*Context), + hijackMap: make(map[hijackKey]hijackFunc), } runner.AddHandler("run-hook", manager.doRunHook, nil) + // Compatibility with snapd between 2.29 and 2.30 in edge only. + // We generated a configure-snapd task on core refreshes and + // for compatibility we need to handle those. + runner.AddHandler("configure-snapd", func(*state.Task, *tomb.Tomb) error { + return nil + }, nil) setupHooks(manager) @@ -126,6 +138,10 @@ m.repository.addHandlerGenerator(pattern, generator) } +func (m *HookManager) KnownTaskKinds() []string { + return m.runner.KnownTaskKinds() +} + // Ensure implements StateManager.Ensure. func (m *HookManager) Ensure() error { m.runner.Ensure() @@ -142,6 +158,17 @@ m.runner.Stop() } +func (m *HookManager) hijacked(hookName, snapName string) hijackFunc { + return m.hijackMap[hijackKey{hookName, snapName}] +} + +func (m *HookManager) RegisterHijack(hookName, snapName string, f hijackFunc) { + if _, ok := m.hijackMap[hijackKey{hookName, snapName}]; ok { + panic(fmt.Sprintf("hook %s for snap %s already hijacked", hookName, snapName)) + } + m.hijackMap[hijackKey{hookName, snapName}] = f +} + func (m *HookManager) ephemeralContext(cookieID string) (context *Context, err error) { var contexts map[string]string m.state.Lock() @@ -184,10 +211,7 @@ var snapst snapstate.SnapState err = snapstate.Get(task.State(), hooksup.Snap, &snapst) - if err == state.ErrNoState { - return nil, nil, fmt.Errorf("cannot find %q snap", hooksup.Snap) - } - if err != nil { + if err != nil && err != state.ErrNoState { return nil, nil, fmt.Errorf("cannot handle %q snap: %v", hooksup.Snap, err) } @@ -206,14 +230,23 @@ return err } - info, err := snapst.CurrentInfo() - if err != nil { - return fmt.Errorf("cannot read %q snap details: %v", hooksup.Snap, err) - } + mustHijack := m.hijacked(hooksup.Hook, hooksup.Snap) != nil + hookExists := false + if !mustHijack { + // not hijacked, snap must be installed + if !snapst.IsInstalled() { + return fmt.Errorf("cannot find %q snap", hooksup.Snap) + } + + info, err := snapst.CurrentInfo() + if err != nil { + return fmt.Errorf("cannot read %q snap details: %v", hooksup.Snap, err) + } - hookExists := info.Hooks[hooksup.Hook] != nil - if !hookExists && !hooksup.Optional { - return fmt.Errorf("snap %q has no %q hook", hooksup.Snap, hooksup.Hook) + hookExists = info.Hooks[hooksup.Hook] != nil + if !hookExists && !hooksup.Optional { + return fmt.Errorf("snap %q has no %q hook", hooksup.Snap, hooksup.Hook) + } } context, err := NewContext(task, task.State(), hooksup, nil, "") @@ -238,7 +271,6 @@ if handlersCount > 1 { return fmt.Errorf("internal error: %d handlers registered for hook %q, expected 1", handlersCount, hooksup.Hook) } - context.handler = handlers[0] contextID := context.ID() @@ -256,24 +288,28 @@ return err } - if hookExists { - output, err := runHook(context, tomb) - if err != nil { - if hooksup.TrackError { - trackHookError(context, output, err) + // some hooks get hijacked, e.g. the core configuration + var output []byte + if f := m.hijacked(hooksup.Hook, hooksup.Snap); f != nil { + err = f(context) + } else if hookExists { + output, err = runHook(context, tomb) + } + if err != nil { + if hooksup.TrackError { + trackHookError(context, output, err) + } + err = osutil.OutputErr(output, err) + if hooksup.IgnoreError { + task.State().Lock() + task.Errorf("ignoring failure in hook %q: %v", hooksup.Hook, err) + task.State().Unlock() + } else { + if handlerErr := context.Handler().Error(err); handlerErr != nil { + return handlerErr } - err = osutil.OutputErr(output, err) - if hooksup.IgnoreError { - task.State().Lock() - task.Errorf("ignoring failure in hook %q: %v", hooksup.Hook, err) - task.State().Unlock() - } else { - if handlerErr := context.Handler().Error(err); handlerErr != nil { - return handlerErr - } - return fmt.Errorf("run hook %q: %v", hooksup.Hook, err) - } + return fmt.Errorf("run hook %q: %v", hooksup.Hook, err) } } diff -Nru snapd-2.29.4.2+17.10/overlord/hookstate/hooks.go snapd-2.31.1+17.10/overlord/hookstate/hooks.go --- snapd-2.29.4.2+17.10/overlord/hookstate/hooks.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/hookstate/hooks.go 2017-12-01 15:51:55.000000000 +0000 @@ -28,6 +28,7 @@ func init() { snapstate.SetupInstallHook = SetupInstallHook + snapstate.SetupPreRefreshHook = SetupPreRefreshHook snapstate.SetupPostRefreshHook = SetupPostRefreshHook snapstate.SetupRemoveHook = SetupRemoveHook } @@ -53,6 +54,17 @@ } summary := fmt.Sprintf(i18n.G("Run post-refresh hook of %q snap if present"), hooksup.Snap) + return HookTask(st, summary, hooksup, nil) +} + +func SetupPreRefreshHook(st *state.State, snapName string) *state.Task { + hooksup := &HookSetup{ + Snap: snapName, + Hook: "pre-refresh", + Optional: true, + } + + summary := fmt.Sprintf(i18n.G("Run pre-refresh hook of %q snap if present"), hooksup.Snap) task := HookTask(st, summary, hooksup, nil) return task @@ -94,5 +106,6 @@ hookMgr.Register(regexp.MustCompile("^install$"), handlerGenerator) hookMgr.Register(regexp.MustCompile("^post-refresh$"), handlerGenerator) + hookMgr.Register(regexp.MustCompile("^pre-refresh$"), handlerGenerator) hookMgr.Register(regexp.MustCompile("^remove$"), handlerGenerator) } diff -Nru snapd-2.29.4.2+17.10/overlord/hookstate/hookstate_test.go snapd-2.31.1+17.10/overlord/hookstate/hookstate_test.go --- snapd-2.29.4.2+17.10/overlord/hookstate/hookstate_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/hookstate/hookstate_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -25,6 +25,7 @@ "os" "path/filepath" "regexp" + "sort" "sync/atomic" "testing" "time" @@ -153,6 +154,12 @@ s.manager.Wait() } +func (s *hookManagerSuite) TestKnownTaskKinds(c *C) { + kinds := s.manager.KnownTaskKinds() + sort.Strings(kinds) + c.Assert(kinds, DeepEquals, []string{"configure-snapd", "run-hook"}) +} + func (s *hookManagerSuite) TestHookSetupJsonMarshal(c *C) { hookSetup := &hookstate.HookSetup{Snap: "snap-name", Revision: snap.R(1), Hook: "hook-name"} out, err := json.Marshal(hookSetup) @@ -218,6 +225,90 @@ c.Check(s.change.Status(), Equals, state.DoneStatus) } +func (s *hookManagerSuite) TestHookSnapMissing(c *C) { + s.state.Lock() + snapstate.Set(s.state, "test-snap", nil) + s.state.Unlock() + + s.manager.Ensure() + s.manager.Wait() + + s.state.Lock() + defer s.state.Unlock() + + c.Check(s.change.Err(), ErrorMatches, `(?s).*cannot find "test-snap" snap.*`) +} + +func (s *hookManagerSuite) TestHookHijackingHappy(c *C) { + // this works even if test-snap is not present + s.state.Lock() + snapstate.Set(s.state, "test-snap", nil) + s.state.Unlock() + + var hijackedContext *hookstate.Context + s.manager.RegisterHijack("configure", "test-snap", func(ctx *hookstate.Context) error { + hijackedContext = ctx + return nil + }) + + s.manager.Ensure() + s.manager.Wait() + + s.state.Lock() + defer s.state.Unlock() + + c.Check(hijackedContext, DeepEquals, s.context) + c.Check(s.command.Calls(), HasLen, 0) + + c.Assert(s.context, NotNil) + c.Check(s.context.SnapName(), Equals, "test-snap") + c.Check(s.context.SnapRevision(), Equals, snap.R(1)) + c.Check(s.context.HookName(), Equals, "configure") + + c.Check(s.mockHandler.BeforeCalled, Equals, true) + c.Check(s.mockHandler.DoneCalled, Equals, true) + c.Check(s.mockHandler.ErrorCalled, Equals, false) + + c.Check(s.task.Kind(), Equals, "run-hook") + c.Check(s.task.Status(), Equals, state.DoneStatus) + c.Check(s.change.Status(), Equals, state.DoneStatus) +} + +func (s *hookManagerSuite) TestHookHijackingUnHappy(c *C) { + s.manager.RegisterHijack("configure", "test-snap", func(ctx *hookstate.Context) error { + return fmt.Errorf("not-happy-at-all") + }) + + s.manager.Ensure() + s.manager.Wait() + + s.state.Lock() + defer s.state.Unlock() + + c.Check(s.command.Calls(), HasLen, 0) + + c.Assert(s.context, NotNil) + c.Check(s.context.SnapName(), Equals, "test-snap") + c.Check(s.context.SnapRevision(), Equals, snap.R(1)) + c.Check(s.context.HookName(), Equals, "configure") + + c.Check(s.mockHandler.BeforeCalled, Equals, true) + c.Check(s.mockHandler.DoneCalled, Equals, false) + c.Check(s.mockHandler.ErrorCalled, Equals, true) + + c.Check(s.task.Kind(), Equals, "run-hook") + c.Check(s.task.Status(), Equals, state.ErrorStatus) + c.Check(s.change.Status(), Equals, state.ErrorStatus) +} + +func (s *hookManagerSuite) TestHookHijackingVeryUnHappy(c *C) { + f := func(ctx *hookstate.Context) error { + return nil + } + s.manager.RegisterHijack("configure", "test-snap", f) + c.Check(func() { s.manager.RegisterHijack("configure", "test-snap", f) }, PanicMatches, "hook configure for snap test-snap already hijacked") +} + func (s *hookManagerSuite) TestHookTaskInitializesContext(c *C) { s.manager.Ensure() s.manager.Wait() @@ -844,3 +935,22 @@ c.Assert(testSnap1HookCalls, Equals, 1) c.Assert(testSnap2HookCalls, Equals, 1) } + +func (s *hookManagerSuite) TestCompatForConfigureSnapd(c *C) { + st := s.state + + st.Lock() + defer st.Unlock() + + task := st.NewTask("configure-snapd", "Snapd between 2.29 and 2.30 in edge insertd those tasks") + chg := st.NewChange("configure", "configure snapd") + chg.AddTask(task) + + st.Unlock() + s.manager.Ensure() + s.manager.Wait() + st.Lock() + + c.Check(chg.Status(), Equals, state.DoneStatus) + c.Check(task.Status(), Equals, state.DoneStatus) +} diff -Nru snapd-2.29.4.2+17.10/overlord/ifacestate/export_test.go snapd-2.31.1+17.10/overlord/ifacestate/export_test.go --- snapd-2.29.4.2+17.10/overlord/ifacestate/export_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/ifacestate/export_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -29,7 +29,7 @@ AddImplicitSlots = addImplicitSlots ) -func MockConflictPredicate(pred func(string) bool) (restore func()) { +func MockConflictPredicate(pred func(*state.Task) bool) (restore func()) { old := noConflictOnConnectTasks noConflictOnConnectTasks = pred return func() { noConflictOnConnectTasks = old } diff -Nru snapd-2.29.4.2+17.10/overlord/ifacestate/handlers.go snapd-2.31.1+17.10/overlord/ifacestate/handlers.go --- snapd-2.29.4.2+17.10/overlord/ifacestate/handlers.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/ifacestate/handlers.go 2018-01-24 20:02:44.000000000 +0000 @@ -154,7 +154,8 @@ task.Logf("%s", snap.BadInterfacesSummary(snapInfo)) } - if err := m.reloadConnections(snapName); err != nil { + reconnectedSnaps, err := m.reloadConnections(snapName) + if err != nil { return err } // FIXME: here we should not reconnect auto-connect plug/slot @@ -170,6 +171,9 @@ for _, name := range disconnectedSnaps { affectedSet[name] = true } + for _, name := range reconnectedSnaps { + affectedSet[name] = true + } for _, name := range connectedSnaps { affectedSet[name] = true } @@ -382,9 +386,9 @@ // check the connection against the declarations' rules ic := policy.ConnectCandidate{ - Plug: plug.PlugInfo, + Plug: plug, PlugSnapDeclaration: plugDecl, - Slot: slot.SlotInfo, + Slot: slot, SlotSnapDeclaration: slotDecl, BaseDeclaration: baseDecl, } @@ -399,6 +403,7 @@ } } + // TODO: pass dynamic attributes from hooks err = m.repo.Connect(connRef) if err != nil { return err @@ -509,10 +514,10 @@ // on disk are rewriten. This is ok because core/ubuntu-core have // exactly the same profiles and nothing in the generated policies // has the slot-name encoded. - if err := m.reloadConnections(oldName); err != nil { + if _, err := m.reloadConnections(oldName); err != nil { return err } - if err := m.reloadConnections(newName); err != nil { + if _, err := m.reloadConnections(newName); err != nil { return err } diff -Nru snapd-2.29.4.2+17.10/overlord/ifacestate/helpers.go snapd-2.31.1+17.10/overlord/ifacestate/helpers.go --- snapd-2.29.4.2+17.10/overlord/ifacestate/helpers.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/ifacestate/helpers.go 2018-01-24 20:02:44.000000000 +0000 @@ -51,7 +51,7 @@ if err := m.renameCorePlugConnection(); err != nil { return err } - if err := m.reloadConnections(""); err != nil { + if _, err := m.reloadConnections(""); err != nil { return err } if err := m.regenerateAllSecurityProfiles(); err != nil { @@ -76,11 +76,17 @@ func (m *InterfaceManager) addBackends(extra []interfaces.SecurityBackend) error { for _, backend := range backends.All { + if err := backend.Initialize(); err != nil { + return err + } if err := m.repo.AddBackend(backend); err != nil { return err } } for _, backend := range extra { + if err := backend.Initialize(); err != nil { + return err + } if err := m.repo.AddBackend(backend); err != nil { return err } @@ -185,15 +191,18 @@ // reloadConnections reloads connections stored in the state in the repository. // Using non-empty snapName the operation can be scoped to connections // affecting a given snap. -func (m *InterfaceManager) reloadConnections(snapName string) error { +// +// The return value is the list of affected snap names. +func (m *InterfaceManager) reloadConnections(snapName string) ([]string, error) { conns, err := getConns(m.state) if err != nil { - return err + return nil, err } + affected := make(map[string]bool) for id := range conns { connRef, err := interfaces.ParseConnRef(id) if err != nil { - return err + return nil, err } if snapName != "" && connRef.PlugRef.Snap != snapName && connRef.SlotRef.Snap != snapName { continue @@ -201,8 +210,14 @@ if err := m.repo.Connect(connRef); err != nil { logger.Noticef("%s", err) } + affected[connRef.PlugRef.Snap] = true + affected[connRef.SlotRef.Snap] = true } - return nil + result := make([]string, 0, len(affected)) + for name := range affected { + result = append(result, name) + } + return result, nil } func (m *InterfaceManager) setupSnapSecurity(task *state.Task, snapInfo *snap.Info, opts interfaces.ConfinementOptions) error { @@ -347,20 +362,20 @@ if len(candidates) != 1 { crefs := make([]string, 0, len(candidates)) for _, candidate := range candidates { - crefs = append(crefs, candidate.Ref().String()) + crefs = append(crefs, candidate.String()) } - task.Logf("cannot auto connect %s (plug auto-connection), candidates found: %q", plug.Ref(), strings.Join(crefs, ", ")) + task.Logf("cannot auto connect %s (plug auto-connection), candidates found: %q", plug, strings.Join(crefs, ", ")) continue } slot := candidates[0] - connRef := interfaces.ConnRef{PlugRef: plug.Ref(), SlotRef: slot.Ref()} + connRef := interfaces.NewConnRef(plug, slot) key := connRef.ID() if _, ok := conns[key]; ok { // Suggested connection already exist so don't clobber it. // NOTE: we don't log anything here as this is a normal and common condition. continue } - if err := m.repo.Connect(connRef); err != nil { + if err := m.repo.Connect(*connRef); err != nil { task.Logf("cannot auto connect %s to %s: %s (plug auto-connection)", connRef.PlugRef, connRef.SlotRef, err) continue } @@ -384,23 +399,23 @@ // considering auto-connections from plug candSlots := m.repo.AutoConnectCandidateSlots(plug.Snap.Name(), plug.Name, autochecker.check) - if len(candSlots) != 1 || candSlots[0].Ref() != slot.Ref() { + if len(candSlots) != 1 || candSlots[0].String() != slot.String() { crefs := make([]string, 0, len(candSlots)) for _, candidate := range candSlots { - crefs = append(crefs, candidate.Ref().String()) + crefs = append(crefs, candidate.String()) } - task.Logf("cannot auto connect %s to %s (slot auto-connection), alternatives found: %q", slot.Ref(), plug.Ref(), strings.Join(crefs, ", ")) + task.Logf("cannot auto connect %s to %s (slot auto-connection), alternatives found: %q", slot, plug, strings.Join(crefs, ", ")) continue } - connRef := interfaces.ConnRef{PlugRef: plug.Ref(), SlotRef: slot.Ref()} + connRef := interfaces.NewConnRef(plug, slot) key := connRef.ID() if _, ok := conns[key]; ok { // Suggested connection already exist so don't clobber it. // NOTE: we don't log anything here as this is a normal and common condition. continue } - if err := m.repo.Connect(connRef); err != nil { + if err := m.repo.Connect(*connRef); err != nil { task.Logf("cannot auto connect %s to %s: %s (slot auto-connection)", connRef.PlugRef, connRef.SlotRef, err) continue } diff -Nru snapd-2.29.4.2+17.10/overlord/ifacestate/ifacemgr.go snapd-2.31.1+17.10/overlord/ifacestate/ifacemgr.go --- snapd-2.29.4.2+17.10/overlord/ifacestate/ifacemgr.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/ifacestate/ifacemgr.go 2018-01-24 20:02:44.000000000 +0000 @@ -23,6 +23,7 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/backends" "github.com/snapcore/snapd/overlord/hookstate" + "github.com/snapcore/snapd/overlord/ifacestate/ifacerepo" "github.com/snapcore/snapd/overlord/state" ) @@ -56,6 +57,10 @@ return nil, err } + s.Lock() + ifacerepo.Replace(s, m.repo) + s.Unlock() + // interface tasks might touch more than the immediate task target snap, serialize them runner.SetBlocked(func(_ *state.Task, running []*state.Task) bool { return len(running) != 0 @@ -73,6 +78,10 @@ return m, nil } +func (m *InterfaceManager) KnownTaskKinds() []string { + return m.runner.KnownTaskKinds() +} + // Ensure implements StateManager.Ensure. func (m *InterfaceManager) Ensure() error { m.runner.Ensure() @@ -87,7 +96,6 @@ // Stop implements StateManager.Stop. func (m *InterfaceManager) Stop() { m.runner.Stop() - } // Repository returns the interface repository used internally by the manager. diff -Nru snapd-2.29.4.2+17.10/overlord/ifacestate/ifacerepo/repo.go snapd-2.31.1+17.10/overlord/ifacestate/ifacerepo/repo.go --- snapd-2.29.4.2+17.10/overlord/ifacestate/ifacerepo/repo.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/ifacestate/ifacerepo/repo.go 2017-12-01 15:51:55.000000000 +0000 @@ -0,0 +1,41 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2016-2017 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 . + * + */ + +package ifacerepo + +import ( + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/overlord/state" +) + +type interfacesRepoKey struct{} + +// Replace replaces the interface repository used by the managers. +func Replace(st *state.State, repo *interfaces.Repository) { + st.Cache(interfacesRepoKey{}, repo) +} + +// Get returns the interface repository used by the managers. +func Get(st *state.State) *interfaces.Repository { + repo := st.Cached(interfacesRepoKey{}) + if repo == nil { + panic("internal error: cannot find cached interfaces repository, interface manager not initialized?") + } + return repo.(*interfaces.Repository) +} diff -Nru snapd-2.29.4.2+17.10/overlord/ifacestate/ifacerepo/repo_test.go snapd-2.31.1+17.10/overlord/ifacestate/ifacerepo/repo_test.go --- snapd-2.29.4.2+17.10/overlord/ifacestate/ifacerepo/repo_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/ifacestate/ifacerepo/repo_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -0,0 +1,63 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2016 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package ifacerepo_test + +import ( + "testing" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/overlord" + "github.com/snapcore/snapd/overlord/ifacestate/ifacerepo" +) + +func Test(t *testing.T) { TestingT(t) } + +type ifaceRepoSuite struct { + o *overlord.Overlord + repo *interfaces.Repository +} + +var _ = Suite(&ifaceRepoSuite{}) + +func (s *ifaceRepoSuite) SetUpTest(c *C) { + s.o = overlord.Mock() + s.repo = &interfaces.Repository{} +} + +func (s *ifaceRepoSuite) TestHappy(c *C) { + st := s.o.State() + st.Lock() + defer st.Unlock() + + ifacerepo.Replace(st, s.repo) + + repo := ifacerepo.Get(st) + c.Check(s.repo, DeepEquals, repo) +} + +func (s *ifaceRepoSuite) TestGetPanics(c *C) { + st := s.o.State() + st.Lock() + defer st.Unlock() + + c.Check(func() { ifacerepo.Get(st) }, PanicMatches, `internal error: cannot find cached interfaces repository, interface manager not initialized\?`) +} diff -Nru snapd-2.29.4.2+17.10/overlord/ifacestate/ifacestate.go snapd-2.31.1+17.10/overlord/ifacestate/ifacestate.go --- snapd-2.29.4.2+17.10/overlord/ifacestate/ifacestate.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/ifacestate/ifacestate.go 2017-12-01 15:51:55.000000000 +0000 @@ -35,8 +35,8 @@ "github.com/snapcore/snapd/snap" ) -var noConflictOnConnectTasks = func(kind string) bool { - return kind != "connect" && kind != "disconnect" +var noConflictOnConnectTasks = func(task *state.Task) bool { + return task.Kind() != "connect" && task.Kind() != "disconnect" } // Connect returns a set of tasks for connecting an interface. diff -Nru snapd-2.29.4.2+17.10/overlord/ifacestate/ifacestate_test.go snapd-2.31.1+17.10/overlord/ifacestate/ifacestate_test.go --- snapd-2.29.4.2+17.10/overlord/ifacestate/ifacestate_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/ifacestate/ifacestate_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -21,6 +21,7 @@ import ( "fmt" + "sort" "strings" "testing" "time" @@ -36,6 +37,7 @@ "github.com/snapcore/snapd/overlord/assertstate" "github.com/snapcore/snapd/overlord/hookstate" "github.com/snapcore/snapd/overlord/ifacestate" + "github.com/snapcore/snapd/overlord/ifacestate/ifacerepo" "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" "github.com/snapcore/snapd/snap" @@ -139,6 +141,28 @@ mgr.Wait() } +func (s *interfaceManagerSuite) TestKnownTaskKinds(c *C) { + mgr, err := ifacestate.Manager(s.state, s.hookManager(c), nil, nil) + c.Assert(err, IsNil) + kinds := mgr.KnownTaskKinds() + sort.Strings(kinds) + c.Assert(kinds, DeepEquals, []string{ + "connect", + "discard-conns", + "disconnect", + "remove-profiles", + "setup-profiles", + "transition-ubuntu-core"}) +} + +func (s *interfaceManagerSuite) TestRepoAvailable(c *C) { + _ = s.manager(c) + s.state.Lock() + defer s.state.Unlock() + repo := ifacerepo.Get(s.state) + c.Check(repo, FitsTypeOf, &interfaces.Repository{}) +} + func (s *interfaceManagerSuite) TestConnectTask(c *C) { s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"}) s.mockSnap(c, consumerYaml) @@ -213,7 +237,7 @@ chg.AddTask(t) _, err := f(s.state, "consumer", "plug", "producer", "slot") - c.Assert(err, ErrorMatches, fmt.Sprintf(`snap "%s" has changes in progress`, snapName)) + c.Assert(err, ErrorMatches, fmt.Sprintf(`snap "%s" has "other-chg" change in progress`, snapName)) } func (s *interfaceManagerSuite) TestConnectConflictsPugSnap(c *C) { @@ -258,11 +282,11 @@ _, err = ifacestate.Connect(s.state, "consumer", "plug", "producer", "slot") c.Assert(err, NotNil) - c.Assert(err, ErrorMatches, `snap "consumer" has changes in progress`) + c.Assert(err, ErrorMatches, `snap "consumer" has "other-connect" change in progress`) _, err = ifacestate.Disconnect(s.state, "consumer", "plug", "producer", "slot") c.Assert(err, NotNil) - c.Assert(err, ErrorMatches, `snap "consumer" has changes in progress`) + c.Assert(err, ErrorMatches, `snap "consumer" has "other-connect" change in progress`) } func (s *interfaceManagerSuite) TestEnsureProcessesConnectTask(c *C) { @@ -307,12 +331,9 @@ c.Check(change.Status(), Equals, state.DoneStatus) repo := s.manager(c).Repository() - plug := repo.Plug("consumer", "plug") - slot := repo.Slot("producer", "slot") - c.Assert(plug.Connections, HasLen, 1) - c.Assert(slot.Connections, HasLen, 1) - c.Check(plug.Connections[0], DeepEquals, interfaces.SlotRef{Snap: "producer", Name: "slot"}) - c.Check(slot.Connections[0], DeepEquals, interfaces.PlugRef{Snap: "consumer", Name: "plug"}) + ifaces := repo.Interfaces() + c.Assert(ifaces.Connections, HasLen, 1) + c.Check(ifaces.Connections, DeepEquals, []*interfaces.ConnRef{{interfaces.PlugRef{Snap: "consumer", Name: "plug"}, interfaces.SlotRef{Snap: "producer", Name: "slot"}}}) } func (s *interfaceManagerSuite) TestConnectTaskCheckInterfaceMismatch(c *C) { @@ -383,10 +404,8 @@ c.Check(change.Status(), Equals, state.ErrorStatus) repo := s.manager(c).Repository() - plug := repo.Plug("consumer", "plug") - slot := repo.Slot("producer", "slot") - c.Check(plug.Connections, HasLen, 0) - c.Check(slot.Connections, HasLen, 0) + ifaces := repo.Interfaces() + c.Check(ifaces.Connections, HasLen, 0) }) } @@ -399,11 +418,9 @@ c.Check(change.Status(), Equals, state.DoneStatus) repo := s.manager(c).Repository() - plug := repo.Plug("consumer", "plug") - slot := repo.Slot("producer", "slot") - c.Assert(plug.Connections, HasLen, 1) - c.Check(plug.Connections[0], DeepEquals, interfaces.SlotRef{Snap: "producer", Name: "slot"}) - c.Check(slot.Connections[0], DeepEquals, interfaces.PlugRef{Snap: "consumer", Name: "plug"}) + ifaces := repo.Interfaces() + c.Assert(ifaces.Connections, HasLen, 1) + c.Check(ifaces.Connections, DeepEquals, []*interfaces.ConnRef{{interfaces.PlugRef{Snap: "consumer", Name: "plug"}, interfaces.SlotRef{Snap: "producer", Name: "slot"}}}) }) } @@ -418,11 +435,9 @@ c.Check(change.Status(), Equals, state.DoneStatus) repo := s.manager(c).Repository() - plug := repo.Plug("consumer", "plug") - slot := repo.Slot("producer", "slot") - c.Assert(plug.Connections, HasLen, 1) - c.Check(plug.Connections[0], DeepEquals, interfaces.SlotRef{Snap: "producer", Name: "slot"}) - c.Check(slot.Connections[0], DeepEquals, interfaces.PlugRef{Snap: "consumer", Name: "plug"}) + ifaces := repo.Interfaces() + c.Assert(ifaces.Connections, HasLen, 1) + c.Check(ifaces.Connections, DeepEquals, []*interfaces.ConnRef{{interfaces.PlugRef{Snap: "consumer", Name: "plug"}, interfaces.SlotRef{Snap: "producer", Name: "slot"}}}) }) } @@ -544,10 +559,8 @@ // Ensure that the connection has been removed from the repository repo := mgr.Repository() - plug := repo.Plug("consumer", "plug") - slot := repo.Slot("producer", "slot") - c.Assert(plug.Connections, HasLen, 0) - c.Assert(slot.Connections, HasLen, 0) + ifaces := repo.Interfaces() + c.Assert(ifaces.Connections, HasLen, 0) // Ensure that the backend was used to setup security of both snaps c.Assert(s.secBackend.SetupCalls, HasLen, 2) @@ -799,7 +812,8 @@ repo := mgr.Repository() plug := repo.Plug("snap", "network") c.Assert(plug, Not(IsNil)) - c.Check(plug.Connections, HasLen, 0) + ifaces := repo.Interfaces() + c.Assert(ifaces.Connections, HasLen, 0) } // The setup-profiles task will auto-connect plugs with viable candidates. @@ -844,7 +858,8 @@ repo := mgr.Repository() plug := repo.Plug("snap", "network") c.Assert(plug, Not(IsNil)) - c.Check(plug.Connections, HasLen, 1) + ifaces := repo.Interfaces() + c.Assert(ifaces.Connections, HasLen, 1) //FIXME add deep eq } // The setup-profiles task will auto-connect slots with viable candidates. @@ -893,7 +908,9 @@ repo := mgr.Repository() slot := repo.Slot("producer", "slot") c.Assert(slot, Not(IsNil)) - c.Check(slot.Connections, HasLen, 1) + ifaces := repo.Interfaces() + c.Assert(ifaces.Connections, HasLen, 1) + c.Check(ifaces.Connections, DeepEquals, []*interfaces.ConnRef{{interfaces.PlugRef{Snap: "consumer", Name: "plug"}, interfaces.SlotRef{Snap: "producer", Name: "slot"}}}) } // The setup-profiles task will auto-connect slots with viable multiple candidates. @@ -947,7 +964,12 @@ repo := mgr.Repository() slot := repo.Slot("producer", "slot") c.Assert(slot, Not(IsNil)) - c.Check(slot.Connections, HasLen, 2) + ifaces := repo.Interfaces() + c.Assert(ifaces.Connections, HasLen, 2) + c.Check(ifaces.Connections, DeepEquals, []*interfaces.ConnRef{ + {interfaces.PlugRef{Snap: "consumer", Name: "plug"}, interfaces.SlotRef{Snap: "producer", Name: "slot"}}, + {interfaces.PlugRef{Snap: "consumer2", Name: "plug"}, interfaces.SlotRef{Snap: "producer", Name: "slot"}}, + }) } // The setup-profiles task will not auto-connect slots if viable alternative slots are present. @@ -994,26 +1016,26 @@ // The setup-profiles task will auto-connect plugs with viable candidates also condidering snap declarations. func (s *interfaceManagerSuite) TestDoSetupSnapSecurityAutoConnectsDeclBased(c *C) { - s.testDoSetupSnapSecurityAutoConnectsDeclBased(c, true, func(conns map[string]interface{}, plug *interfaces.Plug) { + s.testDoSetupSnapSecurityAutoConnectsDeclBased(c, true, func(conns map[string]interface{}, repoConns []*interfaces.ConnRef) { // Ensure that "test" plug is now saved in the state as auto-connected. c.Check(conns, DeepEquals, map[string]interface{}{ "consumer:plug producer:slot": map[string]interface{}{"auto": true, "interface": "test"}, }) // Ensure that "test" is really connected. - c.Check(plug.Connections, HasLen, 1) + c.Check(repoConns, HasLen, 1) }) } // The setup-profiles task will *not* auto-connect plugs with viable candidates when snap declarations are missing. func (s *interfaceManagerSuite) TestDoSetupSnapSecurityAutoConnectsDeclBasedWhenMissingDecl(c *C) { - s.testDoSetupSnapSecurityAutoConnectsDeclBased(c, false, func(conns map[string]interface{}, plug *interfaces.Plug) { + s.testDoSetupSnapSecurityAutoConnectsDeclBased(c, false, func(conns map[string]interface{}, repoConns []*interfaces.ConnRef) { // Ensure nothing is connected. c.Check(conns, HasLen, 0) - c.Check(plug.Connections, HasLen, 0) + c.Check(repoConns, HasLen, 0) }) } -func (s *interfaceManagerSuite) testDoSetupSnapSecurityAutoConnectsDeclBased(c *C, withDecl bool, check func(map[string]interface{}, *interfaces.Plug)) { +func (s *interfaceManagerSuite) testDoSetupSnapSecurityAutoConnectsDeclBased(c *C, withDecl bool, check func(map[string]interface{}, []*interfaces.ConnRef)) { restore := assertstest.MockBuiltinBaseDeclaration([]byte(` type: base-declaration authority-id: canonical @@ -1065,7 +1087,7 @@ plug := repo.Plug("consumer", "plug") c.Assert(plug, Not(IsNil)) - check(conns, plug) + check(conns, repo.Interfaces().Connections) } // The setup-profiles task will only touch connection state for the task it @@ -1161,6 +1183,15 @@ snapInfo := s.mockSnap(c, consumerYaml) s.mockSnap(c, producerYaml) s.testDoSetupSnapSecuirtyReloadsConnectionsWhenInvokedOn(c, snapInfo.Name(), snapInfo.Revision) + + // Ensure that the backend was used to setup security of both snaps + c.Assert(s.secBackend.SetupCalls, HasLen, 2) + c.Assert(s.secBackend.RemoveCalls, HasLen, 0) + c.Check(s.secBackend.SetupCalls[0].SnapInfo.Name(), Equals, "consumer") + c.Check(s.secBackend.SetupCalls[1].SnapInfo.Name(), Equals, "producer") + + c.Check(s.secBackend.SetupCalls[0].Options, Equals, interfaces.ConfinementOptions{}) + c.Check(s.secBackend.SetupCalls[1].Options, Equals, interfaces.ConfinementOptions{}) } func (s *interfaceManagerSuite) TestDoSetupSnapSecuirtyReloadsConnectionsWhenInvokedOnSlotSide(c *C) { @@ -1168,6 +1199,15 @@ s.mockSnap(c, consumerYaml) snapInfo := s.mockSnap(c, producerYaml) s.testDoSetupSnapSecuirtyReloadsConnectionsWhenInvokedOn(c, snapInfo.Name(), snapInfo.Revision) + + // Ensure that the backend was used to setup security of both snaps + c.Assert(s.secBackend.SetupCalls, HasLen, 2) + c.Assert(s.secBackend.RemoveCalls, HasLen, 0) + c.Check(s.secBackend.SetupCalls[0].SnapInfo.Name(), Equals, "producer") + c.Check(s.secBackend.SetupCalls[1].SnapInfo.Name(), Equals, "consumer") + + c.Check(s.secBackend.SetupCalls[0].Options, Equals, interfaces.ConfinementOptions{}) + c.Check(s.secBackend.SetupCalls[1].Options, Equals, interfaces.ConfinementOptions{}) } func (s *interfaceManagerSuite) testDoSetupSnapSecuirtyReloadsConnectionsWhenInvokedOn(c *C, snapName string, revision snap.Revision) { @@ -1198,12 +1238,9 @@ repo := mgr.Repository() // Repository shows the connection - plug := repo.Plug("consumer", "plug") - slot := repo.Slot("producer", "slot") - c.Assert(plug.Connections, HasLen, 1) - c.Assert(slot.Connections, HasLen, 1) - c.Check(plug.Connections[0], DeepEquals, interfaces.SlotRef{Snap: "producer", Name: "slot"}) - c.Check(slot.Connections[0], DeepEquals, interfaces.PlugRef{Snap: "consumer", Name: "plug"}) + ifaces := repo.Interfaces() + c.Assert(ifaces.Connections, HasLen, 1) + c.Check(ifaces.Connections, DeepEquals, []*interfaces.ConnRef{{interfaces.PlugRef{Snap: "consumer", Name: "plug"}, interfaces.SlotRef{Snap: "producer", Name: "slot"}}}) } // The setup-profiles task will honor snapstate.DevMode flag by storing it @@ -1663,12 +1700,9 @@ mgr := s.manager(c) repo := mgr.Repository() - plug := repo.Plug("consumer", "plug") - slot := repo.Slot("producer", "slot") - c.Assert(plug.Connections, HasLen, 1) - c.Assert(slot.Connections, HasLen, 1) - c.Check(plug.Connections[0], DeepEquals, interfaces.SlotRef{Snap: "producer", Name: "slot"}) - c.Check(slot.Connections[0], DeepEquals, interfaces.PlugRef{Snap: "consumer", Name: "plug"}) + ifaces := repo.Interfaces() + c.Assert(ifaces.Connections, HasLen, 1) + c.Check(ifaces.Connections, DeepEquals, []*interfaces.ConnRef{{interfaces.PlugRef{Snap: "consumer", Name: "plug"}, interfaces.SlotRef{Snap: "producer", Name: "slot"}}}) } func (s *interfaceManagerSuite) TestSetupProfilesDevModeMultiple(c *C) { @@ -1687,20 +1721,16 @@ }) c.Assert(err, IsNil) - err = repo.AddSlot(&interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: siC, - Name: "slot", - Interface: "test", - }, + err = repo.AddSlot(&snap.SlotInfo{ + Snap: siC, + Name: "slot", + Interface: "test", }) c.Assert(err, IsNil) - err = repo.AddPlug(&interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: siP, - Name: "plug", - Interface: "test", - }, + err = repo.AddPlug(&snap.PlugInfo{ + Snap: siP, + Name: "plug", + Interface: "test", }) c.Assert(err, IsNil) connRef := interfaces.ConnRef{ @@ -2085,5 +2115,7 @@ repo := mgr.Repository() plug := repo.Plug("snap", "network") c.Assert(plug, Not(IsNil)) - c.Check(plug.Connections, HasLen, 1) + ifaces := repo.Interfaces() + c.Assert(ifaces.Connections, HasLen, 1) + c.Check(ifaces.Connections, DeepEquals, []*interfaces.ConnRef{{interfaces.PlugRef{Snap: "snap", Name: "network"}, interfaces.SlotRef{Snap: "core", Name: "network"}}}) } diff -Nru snapd-2.29.4.2+17.10/overlord/managers_test.go snapd-2.31.1+17.10/overlord/managers_test.go --- snapd-2.29.4.2+17.10/overlord/managers_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/managers_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -46,10 +46,10 @@ "github.com/snapcore/snapd/overlord" "github.com/snapcore/snapd/overlord/assertstate" "github.com/snapcore/snapd/overlord/auth" + "github.com/snapcore/snapd/overlord/configstate/config" "github.com/snapcore/snapd/overlord/hookstate" "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" - "github.com/snapcore/snapd/overlord/storestate" "github.com/snapcore/snapd/partition" "github.com/snapcore/snapd/release" "github.com/snapcore/snapd/snap" @@ -211,7 +211,16 @@ var settleTimeout = 15 * time.Second func makeTestSnap(c *C, snapYamlContent string) string { - return snaptest.MakeTestSnapWithFiles(c, snapYamlContent, nil) + info, err := snap.InfoFromSnapYaml([]byte(snapYamlContent)) + c.Assert(err, IsNil) + + var files [][]string + for _, app := range info.Apps { + // files is a list of (filename, content) + files = append(files, []string{app.Command, ""}) + } + + return snaptest.MakeTestSnapWithFiles(c, snapYamlContent, files) } func (ms *mgrsSuite) TestHappyLocalInstall(c *C) { @@ -479,16 +488,14 @@ c.Assert(mockServer, NotNil) baseURL, _ = url.Parse(mockServer.URL) - assertionsBaseURL, _ := baseURL.Parse("api/v1/snaps") storeCfg := store.Config{ - StoreBaseURL: baseURL, - AssertionsBaseURL: assertionsBaseURL, + StoreBaseURL: baseURL, } mStore := store.New(&storeCfg, nil) st := ms.o.State() st.Lock() - storestate.ReplaceStore(ms.o.State(), mStore) + snapstate.ReplaceStore(ms.o.State(), mStore) st.Unlock() return mockServer @@ -1717,11 +1724,11 @@ err := os.MkdirAll(filepath.Dir(dirs.SnapStateFile), 0755) c.Assert(err, IsNil) - captureAuthContext := func(_ *state.State, ac auth.AuthContext) error { + captureAuthContext := func(_ *store.Config, ac auth.AuthContext) *store.Store { s.ac = ac - return nil + return store.New(nil, nil) } - r := overlord.MockSetupStore(captureAuthContext) + r := overlord.MockStoreNew(captureAuthContext) defer r() s.storeSigning = assertstest.NewStoreStack("can0nical", nil) @@ -1797,7 +1804,7 @@ c.Assert(err, IsNil) c.Check(storeID, Equals, "fallback") - // setup model in system state + // setup model in system statey auth.SetDevice(st, &auth.DeviceState{ Brand: s.serial.BrandID(), Model: s.serial.Model(), @@ -1848,3 +1855,47 @@ c.Check(params.EncodedModel(), DeepEquals, string(asserts.Encode(s.model))) } + +func (s *authContextSetupSuite) TestProxyStoreParams(c *C) { + st := s.o.State() + st.Lock() + defer st.Unlock() + + defURL, err := url.Parse("http://store") + c.Assert(err, IsNil) + + st.Unlock() + proxyStoreID, proxyStoreURL, err := s.ac.ProxyStoreParams(defURL) + st.Lock() + c.Assert(err, IsNil) + c.Check(proxyStoreID, Equals, "") + c.Check(proxyStoreURL, Equals, defURL) + + // setup proxy store reference and assertion + operatorAcct := assertstest.NewAccount(s.storeSigning, "foo-operator", nil, "") + err = assertstate.Add(st, operatorAcct) + c.Assert(err, IsNil) + stoAs, err := s.storeSigning.Sign(asserts.StoreType, map[string]interface{}{ + "store": "foo", + "operator-id": operatorAcct.AccountID(), + "url": "http://foo.internal", + "timestamp": time.Now().Format(time.RFC3339), + }, nil, "") + c.Assert(err, IsNil) + err = assertstate.Add(st, stoAs) + c.Assert(err, IsNil) + tr := config.NewTransaction(st) + err = tr.Set("core", "proxy.store", "foo") + c.Assert(err, IsNil) + tr.Commit() + + fooURL, err := url.Parse("http://foo.internal") + c.Assert(err, IsNil) + + st.Unlock() + proxyStoreID, proxyStoreURL, err = s.ac.ProxyStoreParams(defURL) + st.Lock() + c.Assert(err, IsNil) + c.Check(proxyStoreID, Equals, "foo") + c.Check(proxyStoreURL, DeepEquals, fooURL) +} diff -Nru snapd-2.29.4.2+17.10/overlord/overlord.go snapd-2.31.1+17.10/overlord/overlord.go --- snapd-2.29.4.2+17.10/overlord/overlord.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/overlord.go 2018-01-31 08:47:06.000000000 +0000 @@ -43,7 +43,7 @@ "github.com/snapcore/snapd/overlord/patch" "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" - "github.com/snapcore/snapd/overlord/storestate" + "github.com/snapcore/snapd/store" ) var ( @@ -53,6 +53,10 @@ abortWait = 24 * time.Hour * 7 pruneMaxChanges = 500 + + defaultCachedDownloads = 5 + + configstateInit = configstate.Init ) // Overlord is the central manager of a snappy system, keeping @@ -68,17 +72,17 @@ // restarts restartHandler func(t state.RestartType) // managers - inited bool - snapMgr *snapstate.SnapManager - assertMgr *assertstate.AssertManager - ifaceMgr *ifacestate.InterfaceManager - hookMgr *hookstate.HookManager - configMgr *configstate.ConfigManager - deviceMgr *devicestate.DeviceManager - cmdMgr *cmdstate.CommandManager + inited bool + snapMgr *snapstate.SnapManager + assertMgr *assertstate.AssertManager + ifaceMgr *ifacestate.InterfaceManager + hookMgr *hookstate.HookManager + deviceMgr *devicestate.DeviceManager + cmdMgr *cmdstate.CommandManager + unknownMgr *UnknownTaskManager } -var setupStore = storestate.SetupStore +var storeNew = store.New // New creates a new Overlord with all its state managers. func New() (*Overlord, error) { @@ -98,6 +102,8 @@ } o.stateEng = NewStateEngine(s) + o.unknownMgr = NewUnknownTaskManager(s) + o.stateEng.AddManager(o.unknownMgr) hookMgr, err := hookstate.Manager(s) if err != nil { @@ -123,13 +129,6 @@ } o.addManager(ifaceMgr) - // TODO: this is a bit weird, not actually a StateManager - configMgr, err := configstate.Manager(s, hookMgr) - if err != nil { - return nil, err - } - o.configMgr = configMgr - deviceMgr, err := devicestate.Manager(s, hookMgr) if err != nil { return nil, err @@ -138,17 +137,18 @@ o.addManager(cmdstate.Manager(s)) + configstateInit(hookMgr) + s.Lock() defer s.Unlock() - // setting up the store authContext := auth.NewAuthContext(s, o.deviceMgr) - err = setupStore(s, authContext) - if err != nil { - return nil, err - } + sto := storeNew(nil, authContext) + sto.SetCacheDownloads(defaultCachedDownloads) + + snapstate.ReplaceStore(s, sto) - if err := o.snapMgr.GenerateCookies(s); err != nil { + if err := o.snapMgr.SyncCookies(s); err != nil { return nil, fmt.Errorf("failed to generate cookies: %q", err) } @@ -171,6 +171,7 @@ o.cmdMgr = x } o.stateEng.AddManager(mgr) + o.unknownMgr.Ignore(mgr.KnownTaskKinds()) } func loadState(backend state.Backend) (*state.State, error) { @@ -368,22 +369,30 @@ return o.ifaceMgr } -// HookManager returns the hook manager responsible for running hooks under the -// overlord. +// HookManager returns the hook manager responsible for running hooks +// under the overlord. func (o *Overlord) HookManager() *hookstate.HookManager { return o.hookMgr } -// DeviceManager returns the device manager responsible for the device identity and policies +// DeviceManager returns the device manager responsible for the device +// identity and policies. func (o *Overlord) DeviceManager() *devicestate.DeviceManager { return o.deviceMgr } -// CommandManager returns the manager responsible for running odd jobs +// CommandManager returns the manager responsible for running odd +// jobs. func (o *Overlord) CommandManager() *cmdstate.CommandManager { return o.cmdMgr } +// UnknownTaskManager returns the manager responsible for handling of +// unknown tasks. +func (o *Overlord) UnknownTaskManager() *UnknownTaskManager { + return o.unknownMgr +} + // Mock creates an Overlord without any managers and with a backend // not using disk. Managers can be added with AddManager. For testing. func Mock() *Overlord { @@ -392,6 +401,9 @@ inited: false, } o.stateEng = NewStateEngine(state.New(mockBackend{o: o})) + o.unknownMgr = NewUnknownTaskManager(o.stateEng.State()) + o.stateEng.AddManager(o.unknownMgr) + return o } diff -Nru snapd-2.29.4.2+17.10/overlord/overlord_test.go snapd-2.31.1+17.10/overlord/overlord_test.go --- snapd-2.29.4.2+17.10/overlord/overlord_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/overlord_test.go 2018-01-31 08:47:06.000000000 +0000 @@ -21,7 +21,6 @@ import ( "encoding/json" - "errors" "fmt" "io/ioutil" "os" @@ -36,9 +35,11 @@ "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/overlord" "github.com/snapcore/snapd/overlord/auth" + "github.com/snapcore/snapd/overlord/hookstate" "github.com/snapcore/snapd/overlord/patch" "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/store" "github.com/snapcore/snapd/testutil" ) @@ -63,11 +64,10 @@ restore := patch.Mock(42, nil) defer restore() - var setupStoreAuthContext auth.AuthContext - defer overlord.MockSetupStore(func(_ *state.State, ac auth.AuthContext) error { - setupStoreAuthContext = ac - return nil - })() + var configstateInitCalled bool + overlord.MockConfigstateInit(func(*hookstate.HookManager) { + configstateInitCalled = true + }) o, err := overlord.New() c.Assert(err, IsNil) @@ -79,6 +79,7 @@ c.Check(o.HookManager(), NotNil) c.Check(o.DeviceManager(), NotNil) c.Check(o.CommandManager(), NotNil) + c.Check(configstateInitCalled, Equals, true) s := o.State() c.Check(s, NotNil) @@ -90,8 +91,10 @@ s.Get("patch-level", &patchLevel) c.Check(patchLevel, Equals, 42) - // store was setup with an auth context - c.Check(setupStoreAuthContext, NotNil) + // store is setup + sto := snapstate.Store(s) + c.Check(sto, FitsTypeOf, &store.Store{}) + c.Check(sto.(*store.Store).CacheDownloads(), Equals, 5) } func (ovs *overlordSuite) TestNewWithGoodState(c *C) { @@ -158,15 +161,6 @@ c.Check(b, Equals, true) } -func (ovs *overlordSuite) TestNewWithSetupStoreError(c *C) { - defer overlord.MockSetupStore(func(*state.State, auth.AuthContext) error { - return errors.New("fake error") - })() - - _, err := overlord.New() - c.Check(err, ErrorMatches, "fake error") -} - type witnessManager struct { state *state.State expectedEnsure int @@ -174,6 +168,10 @@ ensureCallback func(s *state.State) error } +func (m *witnessManager) KnownTaskKinds() []string { + return []string{"foo"} +} + func (wm *witnessManager) Ensure() error { if wm.expectedEnsure--; wm.expectedEnsure == 0 { close(wm.ensureCalled) @@ -426,6 +424,7 @@ restoreIntv := overlord.MockPruneInterval(100*time.Millisecond, 1000*time.Millisecond, 1*time.Hour) defer restoreIntv() o := overlord.Mock() + o.UnknownTaskManager().Ignore([]string{"foo"}) // create two changes, one that can be pruned now, one in progress st := o.State() @@ -541,6 +540,10 @@ return rm } +func (rm *runnerManager) KnownTaskKinds() []string { + return rm.runner.KnownTaskKinds() +} + func (rm *runnerManager) Ensure() error { if rm.ensureCallback != nil { rm.ensureCallback() diff -Nru snapd-2.29.4.2+17.10/overlord/servicestate/servicestate.go snapd-2.31.1+17.10/overlord/servicestate/servicestate.go --- snapd-2.29.4.2+17.10/overlord/servicestate/servicestate.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/servicestate/servicestate.go 2017-12-01 15:51:55.000000000 +0000 @@ -24,6 +24,7 @@ "github.com/snapcore/snapd/client" "github.com/snapcore/snapd/overlord/cmdstate" + "github.com/snapcore/snapd/overlord/hookstate" "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" "github.com/snapcore/snapd/snap" @@ -39,7 +40,11 @@ type ServiceActionConflictError struct{ error } -func Control(st *state.State, appInfos []*snap.AppInfo, inst *Instruction) (*state.TaskSet, error) { +// Control creates a taskset for starting/stopping/restarting services via systemctl. +// The appInfos and inst define the services and the command to execute. +// Context is used to determine change conflicts - we will not conflict with +// tasks from same change as that of context's. +func Control(st *state.State, appInfos []*snap.AppInfo, inst *Instruction, context *hookstate.Context) (*state.TaskSet, error) { // the argv to call systemctl will need at most one entry per appInfo, // plus one for "systemctl", one for the action, and sometimes one for // an option. That's a maximum of 3+len(appInfos). @@ -83,7 +88,22 @@ st.Lock() defer st.Unlock() - if err := snapstate.CheckChangeConflictMany(st, snapNames, nil); err != nil { + + var checkConflict func(otherTask *state.Task) bool + if context != nil && !context.IsEphemeral() { + if task, ok := context.Task(); ok { + chg := task.Change() + checkConflict = func(otherTask *state.Task) bool { + if chg != nil && otherTask.Change() != nil { + // if same change, then return false (no conflict) + return chg.ID() != otherTask.Change().ID() + } + return true + } + } + } + + if err := snapstate.CheckChangeConflictMany(st, snapNames, checkConflict); err != nil { return nil, &ServiceActionConflictError{err} } diff -Nru snapd-2.29.4.2+17.10/overlord/snapstate/aliasesv2.go snapd-2.31.1+17.10/overlord/snapstate/aliasesv2.go --- snapd-2.29.4.2+17.10/overlord/snapstate/aliasesv2.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/snapstate/aliasesv2.go 2017-12-01 15:51:55.000000000 +0000 @@ -537,7 +537,7 @@ var snapst SnapState err := Get(st, snapName, &snapst) if err == state.ErrNoState { - return nil, fmt.Errorf("cannot find snap %q", snapName) + return nil, &snap.NotInstalledError{Snap: snapName} } if err != nil { return nil, err @@ -592,7 +592,7 @@ var snapst SnapState err := Get(st, snapName, &snapst) if err == state.ErrNoState { - return nil, fmt.Errorf("cannot find snap %q", snapName) + return nil, &snap.NotInstalledError{Snap: snapName} } if err != nil { return nil, err @@ -675,7 +675,7 @@ var snapst SnapState err := Get(st, name, &snapst) if err == state.ErrNoState { - return nil, fmt.Errorf("cannot find snap %q", name) + return nil, &snap.NotInstalledError{Snap: name} } if err != nil { return nil, err diff -Nru snapd-2.29.4.2+17.10/overlord/snapstate/aliasesv2_test.go snapd-2.31.1+17.10/overlord/snapstate/aliasesv2_test.go --- snapd-2.29.4.2+17.10/overlord/snapstate/aliasesv2_test.go 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/snapstate/aliasesv2_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -712,7 +712,7 @@ s.state.NewChange("alias", "...").AddAll(ts) _, err = snapstate.Update(s.state, "some-snap", "some-channel", snap.R(0), s.user.ID, snapstate.Flags{}) - c.Assert(err, ErrorMatches, `snap "some-snap" has changes in progress`) + c.Assert(err, ErrorMatches, `snap "some-snap" has "alias" change in progress`) } func (s *snapmgrTestSuite) TestUpdateAliasChangeConflict(c *C) { @@ -732,7 +732,7 @@ s.state.NewChange("update", "...").AddAll(ts) _, err = snapstate.Alias(s.state, "some-snap", "cmd1", "alias1") - c.Assert(err, ErrorMatches, `snap "some-snap" has changes in progress`) + c.Assert(err, ErrorMatches, `snap "some-snap" has "update" change in progress`) } func (s *snapmgrTestSuite) TestAliasAliasConflict(c *C) { @@ -1010,7 +1010,7 @@ s.state.NewChange("update", "...").AddAll(ts) _, err = snapstate.DisableAllAliases(s.state, "some-snap") - c.Assert(err, ErrorMatches, `snap "some-snap" has changes in progress`) + c.Assert(err, ErrorMatches, `snap "some-snap" has "update" change in progress`) } func (s *snapmgrTestSuite) TestUpdateRemoveManualAliasChangeConflict(c *C) { @@ -1033,7 +1033,7 @@ s.state.NewChange("update", "...").AddAll(ts) _, _, err = snapstate.RemoveManualAlias(s.state, "alias1") - c.Assert(err, ErrorMatches, `snap "some-snap" has changes in progress`) + c.Assert(err, ErrorMatches, `snap "some-snap" has "update" change in progress`) } func (s *snapmgrTestSuite) TestDisableAllAliasesUpdateChangeConflict(c *C) { @@ -1053,7 +1053,7 @@ s.state.NewChange("alias", "...").AddAll(ts) _, err = snapstate.Update(s.state, "some-snap", "some-channel", snap.R(0), s.user.ID, snapstate.Flags{}) - c.Assert(err, ErrorMatches, `snap "some-snap" has changes in progress`) + c.Assert(err, ErrorMatches, `snap "some-snap" has "alias" change in progress`) } func (s *snapmgrTestSuite) TestRemoveManualAliasUpdateChangeConflict(c *C) { @@ -1076,7 +1076,7 @@ s.state.NewChange("unalias", "...").AddAll(ts) _, err = snapstate.Update(s.state, "some-snap", "some-channel", snap.R(0), s.user.ID, snapstate.Flags{}) - c.Assert(err, ErrorMatches, `snap "some-snap" has changes in progress`) + c.Assert(err, ErrorMatches, `snap "some-snap" has "unalias" change in progress`) } func (s *snapmgrTestSuite) TestPreferTasks(c *C) { @@ -1174,7 +1174,7 @@ s.state.NewChange("update", "...").AddAll(ts) _, err = snapstate.Prefer(s.state, "some-snap") - c.Assert(err, ErrorMatches, `snap "some-snap" has changes in progress`) + c.Assert(err, ErrorMatches, `snap "some-snap" has "update" change in progress`) } func (s *snapmgrTestSuite) TestPreferUpdateChangeConflict(c *C) { @@ -1198,5 +1198,5 @@ s.state.NewChange("prefer", "...").AddAll(ts) _, err = snapstate.Update(s.state, "some-snap", "some-channel", snap.R(0), s.user.ID, snapstate.Flags{}) - c.Assert(err, ErrorMatches, `snap "some-snap" has changes in progress`) + c.Assert(err, ErrorMatches, `snap "some-snap" has "prefer" change in progress`) } diff -Nru snapd-2.29.4.2+17.10/overlord/snapstate/autorefresh.go snapd-2.31.1+17.10/overlord/snapstate/autorefresh.go --- snapd-2.29.4.2+17.10/overlord/snapstate/autorefresh.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/snapstate/autorefresh.go 2018-02-20 15:48:42.000000000 +0000 @@ -0,0 +1,286 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package snapstate + +import ( + "fmt" + "time" + + "github.com/snapcore/snapd/i18n" + "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/overlord/configstate/config" + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/strutil" + "github.com/snapcore/snapd/timeutil" +) + +// the default refresh pattern +const defaultRefreshSchedule = "00:00~24:00/4" + +// hooks setup by devicestate +var ( + CanAutoRefresh func(st *state.State) (bool, error) + CanManageRefreshes func(st *state.State) bool +) + +// refreshRetryDelay specified the minimum time to retry failed refreshes +var refreshRetryDelay = 10 * time.Minute + +// autoRefresh will ensure that snaps are refreshed automatically +// according to the refresh schedule. +type autoRefresh struct { + state *state.State + + lastRefreshSchedule string + nextRefresh time.Time + lastRefreshAttempt time.Time +} + +func newAutoRefresh(st *state.State) *autoRefresh { + return &autoRefresh{ + state: st, + } +} + +// RefreshSchedule will return a user visible string with the current schedule +// for the automatic refreshes and a flag indicating whether the schedule is a +// legacy one. +func (m *autoRefresh) RefreshSchedule() (schedule string, legacy bool, err error) { + _, schedule, legacy, err = m.refreshScheduleWithDefaultsFallback() + return schedule, legacy, err +} + +// NextRefresh returns when the next automatic refresh will happen. +func (m *autoRefresh) NextRefresh() time.Time { + return m.nextRefresh +} + +// LastRefresh returns when the last refresh happened. +func (m *autoRefresh) LastRefresh() (time.Time, error) { + var lastRefresh time.Time + err := m.state.Get("last-refresh", &lastRefresh) + if err != nil && err != state.ErrNoState { + return time.Time{}, err + } + return lastRefresh, nil +} + +// Ensure ensures that we refresh all installed snaps periodically +func (m *autoRefresh) Ensure() error { + m.state.Lock() + defer m.state.Unlock() + + // see if it even makes sense to try to refresh + if CanAutoRefresh == nil { + return nil + } + if ok, err := CanAutoRefresh(m.state); err != nil || !ok { + return err + } + + // Check that we have reasonable delays between attempts. + // If the store is under stress we need to make sure we do not + // hammer it too often + if !m.lastRefreshAttempt.IsZero() && m.lastRefreshAttempt.Add(refreshRetryDelay).After(time.Now()) { + return nil + } + + // get lastRefresh and schedule + lastRefresh, err := m.LastRefresh() + if err != nil { + return err + } + + refreshSchedule, refreshScheduleStr, _, err := m.refreshScheduleWithDefaultsFallback() + if err != nil { + return err + } + if len(refreshSchedule) == 0 { + m.nextRefresh = time.Time{} + return nil + } + // we already have a refresh time, check if we got a new config + if !m.nextRefresh.IsZero() { + if m.lastRefreshSchedule != refreshScheduleStr { + // the refresh schedule has changed + logger.Debugf("Refresh timer changed.") + m.nextRefresh = time.Time{} + } + } + m.lastRefreshSchedule = refreshScheduleStr + + // ensure nothing is in flight already + if autoRefreshInFlight(m.state) { + return nil + } + + // compute next refresh attempt time (if needed) + if m.nextRefresh.IsZero() { + // store attempts in memory so that we can backoff + if !lastRefresh.IsZero() { + delta := timeutil.Next(refreshSchedule, lastRefresh) + m.nextRefresh = time.Now().Add(delta) + } else { + // immediate + m.nextRefresh = time.Now() + } + logger.Debugf("Next refresh scheduled for %s.", m.nextRefresh) + } + + // do refresh attempt (if needed) + if !m.nextRefresh.After(time.Now()) { + err = m.launchAutoRefresh() + // clear nextRefresh only if the refresh worked. There is + // still the lastRefreshAttempt rate limit so things will + // not go into a busy store loop + if err == nil { + m.nextRefresh = time.Time{} + } + } + + return err +} + +// refreshScheduleWithDefaultsFallback returns the current refresh schedule +// and refresh string. When an invalid refresh schedule is set by the user +// the refresh schedule is automatically reset to the default. +// +// TODO: we can remove the refreshSchedule reset because we have validation +// of the schedule now. +func (m *autoRefresh) refreshScheduleWithDefaultsFallback() (ts []*timeutil.Schedule, scheduleAsStr string, legacy bool, err error) { + if refreshScheduleManaged(m.state) { + if m.lastRefreshSchedule != "managed" { + logger.Noticef("refresh.schedule is managed via the snapd-control interface") + m.lastRefreshSchedule = "managed" + } + return nil, "managed", true, nil + } + + tr := config.NewTransaction(m.state) + + // try the new refresh.timer config option first + err = tr.Get("core", "refresh.timer", &scheduleAsStr) + if err != nil && !config.IsNoOption(err) { + return nil, "", false, err + } + if scheduleAsStr != "" { + ts, err = timeutil.ParseSchedule(scheduleAsStr) + if err != nil { + logger.Noticef("cannot use refresh.timer configuration: %s", err) + return refreshScheduleDefault() + } + return ts, scheduleAsStr, false, nil + } + + // fallback to legacy refresh.schedule setting when the new + // config option is not set + err = tr.Get("core", "refresh.schedule", &scheduleAsStr) + if err != nil && !config.IsNoOption(err) { + return nil, "", false, err + } + if scheduleAsStr != "" { + ts, err = timeutil.ParseLegacySchedule(scheduleAsStr) + if err != nil { + logger.Noticef("cannot use refresh.schedule configuration: %s", err) + return refreshScheduleDefault() + } + return ts, scheduleAsStr, true, nil + } + + return refreshScheduleDefault() +} + +// launchAutoRefresh creates the auto-refresh taskset and a change for it. +func (m *autoRefresh) launchAutoRefresh() error { + m.lastRefreshAttempt = time.Now() + updated, tasksets, err := AutoRefresh(m.state) + if err != nil { + logger.Noticef("Cannot prepare auto-refresh change: %s", err) + return err + } + + // Set last refresh time only if the store (in AutoRefresh) gave + // us no error. + m.state.Set("last-refresh", time.Now()) + + var msg string + switch len(updated) { + case 0: + logger.Noticef(i18n.G("auto-refresh: all snaps are up-to-date")) + return nil + case 1: + msg = fmt.Sprintf(i18n.G("Auto-refresh snap %q"), updated[0]) + case 2, 3: + quoted := strutil.Quoted(updated) + // TRANSLATORS: the %s is a comma-separated list of quoted snap names + msg = fmt.Sprintf(i18n.G("Auto-refresh snaps %s"), quoted) + default: + msg = fmt.Sprintf(i18n.G("Auto-refresh %d snaps"), len(updated)) + } + + chg := m.state.NewChange("auto-refresh", msg) + for _, ts := range tasksets { + chg.AddAll(ts) + } + chg.Set("snap-names", updated) + chg.Set("api-data", map[string]interface{}{"snap-names": updated}) + + return nil +} + +func refreshScheduleDefault() (ts []*timeutil.Schedule, scheduleStr string, legacy bool, err error) { + refreshSchedule, err := timeutil.ParseSchedule(defaultRefreshSchedule) + if err != nil { + panic(fmt.Sprintf("defaultRefreshSchedule cannot be parsed: %s", err)) + } + + return refreshSchedule, defaultRefreshSchedule, false, nil +} + +func autoRefreshInFlight(st *state.State) bool { + for _, chg := range st.Changes() { + if chg.Kind() == "auto-refresh" && !chg.Status().Ready() { + return true + } + } + return false +} + +// refreshScheduleManaged returns true if the refresh schedule of the +// device is managed by an external snap +func refreshScheduleManaged(st *state.State) bool { + var refreshScheduleStr string + + // this will only be "nil" if running in tests + if CanManageRefreshes == nil { + return false + } + + tr := config.NewTransaction(st) + err := tr.Get("core", "refresh.schedule", &refreshScheduleStr) + if err != nil { + return false + } + if refreshScheduleStr != "managed" { + return false + } + + return CanManageRefreshes(st) +} diff -Nru snapd-2.29.4.2+17.10/overlord/snapstate/autorefresh_test.go snapd-2.31.1+17.10/overlord/snapstate/autorefresh_test.go --- snapd-2.29.4.2+17.10/overlord/snapstate/autorefresh_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/snapstate/autorefresh_test.go 2018-01-31 08:47:06.000000000 +0000 @@ -0,0 +1,162 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package snapstate_test + +import ( + "fmt" + "time" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/overlord/auth" + "github.com/snapcore/snapd/overlord/configstate/config" + "github.com/snapcore/snapd/overlord/snapstate" + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/store" + "github.com/snapcore/snapd/store/storetest" +) + +type autoRefreshStore struct { + storetest.Store + + ops []string + + listRefreshErr error +} + +func (r *autoRefreshStore) ListRefresh(cands []*store.RefreshCandidate, _ *auth.UserState, flags *store.RefreshOptions) ([]*snap.Info, error) { + r.ops = append(r.ops, "list-refresh") + return nil, r.listRefreshErr +} + +type autoRefreshTestSuite struct { + state *state.State + + store *autoRefreshStore +} + +var _ = Suite(&autoRefreshTestSuite{}) + +func (s *autoRefreshTestSuite) SetUpTest(c *C) { + s.state = state.New(nil) + + s.store = &autoRefreshStore{} + + s.state.Lock() + defer s.state.Unlock() + snapstate.ReplaceStore(s.state, s.store) + + snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ + Active: true, + Sequence: []*snap.SideInfo{ + {RealName: "some-snap", Revision: snap.R(5), SnapID: "some-snap-id"}, + }, + Current: snap.R(5), + SnapType: "app", + UserID: 1, + }) + + snapstate.CanAutoRefresh = func(*state.State) (bool, error) { return true, nil } + snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) { + return nil, nil + } +} + +func (s *autoRefreshTestSuite) TearDownTest(c *C) { + snapstate.CanAutoRefresh = nil + snapstate.AutoAliases = nil +} + +func (s *autoRefreshTestSuite) TestLastRefresh(c *C) { + af := snapstate.NewAutoRefresh(s.state) + err := af.Ensure() + c.Check(err, IsNil) + c.Check(s.store.ops, DeepEquals, []string{"list-refresh"}) + + var lastRefresh time.Time + s.state.Lock() + s.state.Get("last-refresh", &lastRefresh) + s.state.Unlock() + c.Check(lastRefresh.Year(), Equals, time.Now().Year()) +} + +func (s *autoRefreshTestSuite) TestLastRefreshRefreshManaged(c *C) { + snapstate.CanManageRefreshes = func(st *state.State) bool { + return true + } + defer func() { snapstate.CanManageRefreshes = nil }() + + s.state.Lock() + defer s.state.Unlock() + + tr := config.NewTransaction(s.state) + tr.Set("core", "refresh.schedule", "managed") + tr.Commit() + + af := snapstate.NewAutoRefresh(s.state) + s.state.Unlock() + err := af.Ensure() + s.state.Lock() + c.Check(err, IsNil) + c.Check(s.store.ops, HasLen, 0) + + refreshScheduleStr, legacy, err := af.RefreshSchedule() + c.Check(refreshScheduleStr, Equals, "managed") + c.Check(legacy, Equals, true) + c.Check(err, IsNil) + + c.Check(af.NextRefresh(), DeepEquals, time.Time{}) +} + +func (s *autoRefreshTestSuite) TestLastRefreshNoRefreshNeeded(c *C) { + s.state.Lock() + s.state.Set("last-refresh", time.Now()) + s.state.Unlock() + + af := snapstate.NewAutoRefresh(s.state) + err := af.Ensure() + c.Check(err, IsNil) + c.Check(s.store.ops, HasLen, 0) +} + +func (s *autoRefreshTestSuite) TestRefreshBackoff(c *C) { + s.store.listRefreshErr = fmt.Errorf("random store error") + af := snapstate.NewAutoRefresh(s.state) + err := af.Ensure() + c.Check(err, ErrorMatches, "random store error") + c.Check(s.store.ops, HasLen, 1) + c.Check(s.store.ops, DeepEquals, []string{"list-refresh"}) + + // call ensure again, our back-off will prevent the store from + // being hit again + err = af.Ensure() + c.Check(err, IsNil) + c.Check(s.store.ops, HasLen, 1) + + // fake that the retryRefreshDelay is over + restore := snapstate.MockRefreshRetryDelay(1 * time.Millisecond) + defer restore() + time.Sleep(10 * time.Millisecond) + + err = af.Ensure() + c.Check(err, ErrorMatches, "random store error") + c.Check(s.store.ops, HasLen, 2) +} diff -Nru snapd-2.29.4.2+17.10/overlord/snapstate/backend/aliases.go snapd-2.31.1+17.10/overlord/snapstate/backend/aliases.go --- snapd-2.29.4.2+17.10/overlord/snapstate/backend/aliases.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/snapstate/backend/aliases.go 2017-12-01 15:51:55.000000000 +0000 @@ -24,9 +24,9 @@ "os" "path/filepath" "strings" + "syscall" "github.com/snapcore/snapd/dirs" - "github.com/snapcore/snapd/osutil" ) // Alias represents a command alias with a name and its application target. @@ -46,6 +46,12 @@ return fmt.Errorf("cannot remove alias symlink: %v", err) } removed[alias.Name] = true + completer := filepath.Join(dirs.CompletersDir, alias.Name) + target, err := os.Readlink(completer) + if err != nil || target != alias.Target { + continue + } + os.Remove(completer) } for _, alias := range add { @@ -61,13 +67,23 @@ if err != nil { return fmt.Errorf("cannot create alias symlink: %v", err) } + + target, err := os.Readlink(filepath.Join(dirs.CompletersDir, alias.Target)) + if err == nil && target == dirs.CompleteSh { + os.Symlink(alias.Target, filepath.Join(dirs.CompletersDir, alias.Name)) + } } return nil } // RemoveSnapAliases removes all the aliases targeting the given snap. func (b Backend) RemoveSnapAliases(snapName string) error { - cands, err := filepath.Glob(filepath.Join(dirs.SnapBinariesDir, "*")) + removeSymlinksTo(dirs.CompletersDir, snapName) + return removeSymlinksTo(dirs.SnapBinariesDir, snapName) +} + +func removeSymlinksTo(dir, snapName string) error { + cands, err := filepath.Glob(filepath.Join(dir, "*")) if err != nil { return err } @@ -75,19 +91,20 @@ var firstErr error // best effort for _, cand := range cands { - if osutil.IsSymlink(cand) { - target, err := os.Readlink(cand) - if err != nil { - if firstErr == nil { - firstErr = err - } - continue + target, err := os.Readlink(cand) + if err, ok := err.(*os.PathError); ok && err.Err == syscall.EINVAL { + continue + } + if err != nil { + if firstErr == nil { + firstErr = err } - if target == snapName || strings.HasPrefix(target, prefix) { - err := os.Remove(cand) - if err != nil && firstErr == nil { - firstErr = fmt.Errorf("cannot remove alias symlink: %v", err) - } + continue + } + if target == snapName || strings.HasPrefix(target, prefix) { + err := os.Remove(cand) + if err != nil && firstErr == nil { + firstErr = fmt.Errorf("cannot remove alias symlink: %v", err) } } } diff -Nru snapd-2.29.4.2+17.10/overlord/snapstate/backend/aliases_test.go snapd-2.31.1+17.10/overlord/snapstate/backend/aliases_test.go --- snapd-2.29.4.2+17.10/overlord/snapstate/backend/aliases_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/snapstate/backend/aliases_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -20,6 +20,7 @@ package backend_test import ( + "io/ioutil" "os" "path/filepath" @@ -40,57 +41,68 @@ dirs.SetRootDir(c.MkDir()) err := os.MkdirAll(dirs.SnapBinariesDir, 0755) c.Assert(err, IsNil) + c.Assert(os.MkdirAll(dirs.CompletersDir, 0755), IsNil) + c.Assert(os.MkdirAll(filepath.Dir(dirs.CompleteSh), 0755), IsNil) + c.Assert(ioutil.WriteFile(dirs.CompleteSh, nil, 0644), IsNil) } func (s *aliasesSuite) TearDownTest(c *C) { dirs.SetRootDir("") } -func missingAliases(aliases []*backend.Alias) ([]*backend.Alias, error) { - var res []*backend.Alias +func missingAliases(aliases []*backend.Alias) (missingAs, missingCs []*backend.Alias, err error) { for _, cand := range aliases { - _, err := os.Lstat(filepath.Join(dirs.SnapBinariesDir, cand.Name)) + _, err = os.Lstat(filepath.Join(dirs.SnapBinariesDir, cand.Name)) if err != nil { if !os.IsNotExist(err) { - return nil, err + return nil, nil, err } - res = append(res, cand) + missingAs = append(missingAs, cand) + } + + _, err = os.Lstat(filepath.Join(dirs.CompletersDir, cand.Name)) + if err != nil { + if !os.IsNotExist(err) { + return nil, nil, err + } + missingCs = append(missingCs, cand) } } - return res, nil + return missingAs, missingCs, nil } func (s *aliasesSuite) TestMissingAliases(c *C) { err := os.Symlink("x.foo", filepath.Join(dirs.SnapBinariesDir, "foo")) c.Assert(err, IsNil) + c.Assert(os.Symlink("x.a", filepath.Join(dirs.CompletersDir, "a")), IsNil) - aliases, err := missingAliases([]*backend.Alias{{"a", "a"}, {"foo", "foo"}}) + missingAs, missingCs, err := missingAliases([]*backend.Alias{{"a", "x.a"}, {"foo", "x.foo"}}) c.Assert(err, IsNil) - c.Check(aliases, DeepEquals, []*backend.Alias{{"a", "a"}}) + c.Check(missingAs, DeepEquals, []*backend.Alias{{"a", "x.a"}}) + c.Check(missingCs, DeepEquals, []*backend.Alias{{"foo", "x.foo"}}) } -func matchingAliases(aliases []*backend.Alias) ([]*backend.Alias, error) { - var res []*backend.Alias +func matchingAliases(aliases []*backend.Alias) (matchingAs, matchingCs []*backend.Alias, err error) { for _, cand := range aliases { - fn := filepath.Join(dirs.SnapBinariesDir, cand.Name) - fileInfo, err := os.Lstat(fn) - if err != nil { - if !os.IsNotExist(err) { - return nil, err + target, err := os.Readlink(filepath.Join(dirs.SnapBinariesDir, cand.Name)) + if err == nil { + if target == cand.Target { + matchingAs = append(matchingAs, cand) } - continue + } else if !os.IsNotExist(err) { + return nil, nil, err } - if (fileInfo.Mode() & os.ModeSymlink) != 0 { - target, err := os.Readlink(fn) - if err != nil { - return nil, err - } + + target, err = os.Readlink(filepath.Join(dirs.CompletersDir, cand.Name)) + if err == nil { if target == cand.Target { - res = append(res, cand) + matchingCs = append(matchingCs, cand) } + } else if !os.IsNotExist(err) { + return nil, nil, err } } - return res, nil + return matchingAs, matchingCs, nil } func (s *aliasesSuite) TestMatchingAliases(c *C) { @@ -98,10 +110,13 @@ c.Assert(err, IsNil) err = os.Symlink("y.bar", filepath.Join(dirs.SnapBinariesDir, "bar")) c.Assert(err, IsNil) + c.Assert(os.Symlink("y.foo", filepath.Join(dirs.CompletersDir, "foo")), IsNil) + c.Assert(os.Symlink("x.bar", filepath.Join(dirs.CompletersDir, "bar")), IsNil) - aliases, err := matchingAliases([]*backend.Alias{{"a", "a"}, {"foo", "x.foo"}, {"bar", "x.bar"}}) + matchingAs, matchingCs, err := matchingAliases([]*backend.Alias{{"a", "x.a"}, {"foo", "x.foo"}, {"bar", "x.bar"}}) c.Assert(err, IsNil) - c.Check(aliases, DeepEquals, []*backend.Alias{{"foo", "x.foo"}}) + c.Check(matchingAs, DeepEquals, []*backend.Alias{{"foo", "x.foo"}}) + c.Check(matchingCs, DeepEquals, []*backend.Alias{{"bar", "x.bar"}}) } func (s *aliasesSuite) TestUpdateAliasesAdd(c *C) { @@ -110,9 +125,29 @@ err := s.be.UpdateAliases(aliases, nil) c.Assert(err, IsNil) - match, err := matchingAliases(aliases) + matchingAs, matchingCs, err := matchingAliases(aliases) + c.Assert(err, IsNil) + c.Check(matchingAs, DeepEquals, aliases) + c.Check(matchingCs, HasLen, 0) +} + +func mkCompleters(c *C, apps ...string) { + for _, app := range apps { + c.Assert(os.Symlink(dirs.CompleteSh, filepath.Join(dirs.CompletersDir, app)), IsNil) + } +} + +func (s *aliasesSuite) TestUpdateAliasesAddWithCompleter(c *C) { + mkCompleters(c, "x.bar", "x.foo") + aliases := []*backend.Alias{{"foo", "x.foo"}, {"bar", "x.bar"}} + + err := s.be.UpdateAliases(aliases, nil) + c.Assert(err, IsNil) + + matchingAs, matchingCs, err := matchingAliases(aliases) c.Assert(err, IsNil) - c.Check(match, HasLen, len(aliases)) + c.Check(matchingAs, DeepEquals, aliases) + c.Check(matchingCs, DeepEquals, aliases) } func (s *aliasesSuite) TestUpdateAliasesAddIdempot(c *C) { @@ -124,9 +159,26 @@ err = s.be.UpdateAliases(aliases, nil) c.Assert(err, IsNil) - match, err := matchingAliases(aliases) + matchingAs, matchingCs, err := matchingAliases(aliases) c.Assert(err, IsNil) - c.Check(match, HasLen, len(aliases)) + c.Check(matchingAs, DeepEquals, aliases) + c.Check(matchingCs, HasLen, 0) +} + +func (s *aliasesSuite) TestUpdateAliasesAddWithCompleterIdempot(c *C) { + mkCompleters(c, "x.foo", "x.bar") + aliases := []*backend.Alias{{"foo", "x.foo"}, {"bar", "x.bar"}} + + err := s.be.UpdateAliases(aliases, nil) + c.Assert(err, IsNil) + + err = s.be.UpdateAliases(aliases, nil) + c.Assert(err, IsNil) + + matchingAs, matchingCs, err := matchingAliases(aliases) + c.Assert(err, IsNil) + c.Check(matchingAs, DeepEquals, aliases) + c.Check(matchingCs, DeepEquals, aliases) } func (s *aliasesSuite) TestUpdateAliasesRemove(c *C) { @@ -135,20 +187,49 @@ err := s.be.UpdateAliases(aliases, nil) c.Assert(err, IsNil) - match, err := matchingAliases(aliases) + matchingAs, matchingCs, err := matchingAliases(aliases) + c.Assert(err, IsNil) + c.Check(matchingAs, DeepEquals, aliases) + c.Check(matchingCs, HasLen, 0) + + err = s.be.UpdateAliases(nil, aliases) + c.Assert(err, IsNil) + + missingAs, missingCs, err := missingAliases(aliases) + c.Assert(err, IsNil) + c.Check(missingAs, DeepEquals, aliases) + c.Check(missingCs, DeepEquals, aliases) + + matchingAs, matchingCs, err = matchingAliases(aliases) + c.Assert(err, IsNil) + c.Check(matchingAs, HasLen, 0) + c.Check(matchingCs, HasLen, 0) +} + +func (s *aliasesSuite) TestUpdateAliasesWithCompleterRemove(c *C) { + mkCompleters(c, "x.foo", "x.bar") + aliases := []*backend.Alias{{"foo", "x.foo"}, {"bar", "x.bar"}} + + err := s.be.UpdateAliases(aliases, nil) + c.Assert(err, IsNil) + + matchingAs, matchingCs, err := matchingAliases(aliases) c.Assert(err, IsNil) - c.Check(match, HasLen, len(aliases)) + c.Check(matchingAs, HasLen, len(aliases)) + c.Check(matchingCs, HasLen, len(aliases)) err = s.be.UpdateAliases(nil, aliases) c.Assert(err, IsNil) - missing, err := missingAliases(aliases) + missingAs, missingCs, err := missingAliases(aliases) c.Assert(err, IsNil) - c.Check(missing, HasLen, len(aliases)) + c.Check(missingAs, DeepEquals, aliases) + c.Check(missingCs, DeepEquals, aliases) - match, err = matchingAliases(aliases) + matchingAs, matchingCs, err = matchingAliases(aliases) c.Assert(err, IsNil) - c.Check(match, HasLen, 0) + c.Check(matchingAs, HasLen, 0) + c.Check(matchingCs, HasLen, 0) } func (s *aliasesSuite) TestUpdateAliasesRemoveIdempot(c *C) { @@ -163,13 +244,39 @@ err = s.be.UpdateAliases(nil, aliases) c.Assert(err, IsNil) - missing, err := missingAliases(aliases) + missingAs, missingCs, err := missingAliases(aliases) + c.Assert(err, IsNil) + c.Check(missingAs, DeepEquals, aliases) + c.Check(missingCs, DeepEquals, aliases) + + matchingAs, matchingCs, err := matchingAliases(aliases) + c.Assert(err, IsNil) + c.Check(matchingAs, HasLen, 0) + c.Check(matchingCs, HasLen, 0) +} + +func (s *aliasesSuite) TestUpdateAliasesWithCompleterRemoveIdempot(c *C) { + mkCompleters(c, "x.foo", "x.bar") + aliases := []*backend.Alias{{"foo", "x.foo"}, {"bar", "x.bar"}} + + err := s.be.UpdateAliases(aliases, nil) + c.Assert(err, IsNil) + + err = s.be.UpdateAliases(nil, aliases) + c.Assert(err, IsNil) + + err = s.be.UpdateAliases(nil, aliases) + c.Assert(err, IsNil) + + missingAs, missingCs, err := missingAliases(aliases) c.Assert(err, IsNil) - c.Check(missing, HasLen, len(aliases)) + c.Check(missingAs, DeepEquals, aliases) + c.Check(missingCs, DeepEquals, aliases) - match, err := matchingAliases(aliases) + matchingAs, matchingCs, err := matchingAliases(aliases) c.Assert(err, IsNil) - c.Check(match, HasLen, 0) + c.Check(matchingAs, HasLen, 0) + c.Check(matchingCs, HasLen, 0) } func (s *aliasesSuite) TestUpdateAliasesAddRemoveOverlap(c *C) { @@ -182,16 +289,56 @@ err = s.be.UpdateAliases(after, before) c.Assert(err, IsNil) - match, err := matchingAliases(before) + matchingAs, matchingCs, err := matchingAliases(before) c.Assert(err, IsNil) - c.Check(match, HasLen, 0) - match, err = matchingAliases(after) + c.Check(matchingAs, HasLen, 0) + c.Check(matchingCs, HasLen, 0) + matchingAs, matchingCs, err = matchingAliases(after) c.Assert(err, IsNil) - c.Check(match, HasLen, len(after)) + c.Check(matchingAs, DeepEquals, after) + c.Check(matchingCs, HasLen, 0) +} + +func (s *aliasesSuite) TestUpdateAliasesWithCompleterAddRemoveOverlap(c *C) { + mkCompleters(c, "x.baz", "x.bar") + before := []*backend.Alias{{"bar", "x.bar"}} + after := []*backend.Alias{{"bar", "x.baz"}} + + err := s.be.UpdateAliases(before, nil) + c.Assert(err, IsNil) + + err = s.be.UpdateAliases(after, before) + c.Assert(err, IsNil) + + matchingAs, matchingCs, err := matchingAliases(before) + c.Assert(err, IsNil) + c.Check(matchingAs, HasLen, 0) + c.Check(matchingCs, HasLen, 0) + matchingAs, matchingCs, err = matchingAliases(after) + c.Assert(err, IsNil) + c.Check(matchingAs, DeepEquals, after) + c.Check(matchingCs, DeepEquals, after) } func (s *aliasesSuite) TestRemoveSnapAliases(c *C) { - aliases := []*backend.Alias{{"x", "x"}, {"bar", "x.bar"}, {"baz", "y.baz"}, {"y", "y"}} + aliases := []*backend.Alias{{"bar", "x.bar"}, {"baz", "y.baz"}} + + err := s.be.UpdateAliases(aliases, nil) + c.Assert(err, IsNil) + + err = s.be.RemoveSnapAliases("x") + c.Assert(err, IsNil) + + matchingAs, matchingCs, err := matchingAliases(aliases) + c.Assert(err, IsNil) + c.Check(matchingAs, DeepEquals, []*backend.Alias{{"baz", "y.baz"}}) + // no completion for the commands -> no completion for the aliases + c.Check(matchingCs, HasLen, 0) +} + +func (s *aliasesSuite) TestRemoveSnapAliasesWithCompleter(c *C) { + mkCompleters(c, "x", "x.bar", "y", "y.baz") + aliases := []*backend.Alias{{"xx", "x"}, {"bar", "x.bar"}, {"baz", "y.baz"}, {"yy", "y"}} err := s.be.UpdateAliases(aliases, nil) c.Assert(err, IsNil) @@ -199,7 +346,8 @@ err = s.be.RemoveSnapAliases("x") c.Assert(err, IsNil) - match, err := matchingAliases(aliases) + matchingAs, matchingCs, err := matchingAliases(aliases) c.Assert(err, IsNil) - c.Check(match, DeepEquals, []*backend.Alias{{"baz", "y.baz"}, {"y", "y"}}) + c.Check(matchingAs, DeepEquals, []*backend.Alias{{"baz", "y.baz"}, {"yy", "y"}}) + c.Check(matchingCs, DeepEquals, []*backend.Alias{{"baz", "y.baz"}, {"yy", "y"}}) } diff -Nru snapd-2.29.4.2+17.10/overlord/snapstate/backend/backend_test.go snapd-2.31.1+17.10/overlord/snapstate/backend/backend_test.go --- snapd-2.29.4.2+17.10/overlord/snapstate/backend/backend_test.go 2016-08-11 17:27:59.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/snapstate/backend/backend_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -34,7 +34,14 @@ func TestBackend(t *testing.T) { TestingT(t) } func makeTestSnap(c *C, snapYamlContent string) string { - return snaptest.MakeTestSnapWithFiles(c, snapYamlContent, nil) + info, err := snap.InfoFromSnapYaml([]byte(snapYamlContent)) + c.Assert(err, IsNil) + var files [][]string + for _, app := range info.Apps { + // files is a list of (filename, content) + files = append(files, []string{app.Command, ""}) + } + return snaptest.MakeTestSnapWithFiles(c, snapYamlContent, files) } type backendSuite struct{} diff -Nru snapd-2.29.4.2+17.10/overlord/snapstate/backend/mountunit_test.go snapd-2.31.1+17.10/overlord/snapstate/backend/mountunit_test.go --- snapd-2.29.4.2+17.10/overlord/snapstate/backend/mountunit_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/snapstate/backend/mountunit_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -86,7 +86,7 @@ What=/var/lib/snapd/snaps/foo_13.snap Where=%s/foo/13 Type=squashfs -Options=nodev,ro +Options=nodev,ro,x-gdu.hide [Install] WantedBy=multi-user.target diff -Nru snapd-2.29.4.2+17.10/overlord/snapstate/backend.go snapd-2.31.1+17.10/overlord/snapstate/backend.go --- snapd-2.29.4.2+17.10/overlord/snapstate/backend.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/snapstate/backend.go 2018-01-24 20:02:44.000000000 +0000 @@ -20,11 +20,35 @@ package snapstate import ( + "io" + + "golang.org/x/net/context" + + "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/overlord/auth" "github.com/snapcore/snapd/overlord/snapstate/backend" "github.com/snapcore/snapd/progress" "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/store" ) +// A StoreService can find, list available updates and download snaps. +type StoreService interface { + SnapInfo(spec store.SnapSpec, user *auth.UserState) (*snap.Info, error) + Find(search *store.Search, user *auth.UserState) ([]*snap.Info, error) + LookupRefresh(*store.RefreshCandidate, *auth.UserState) (*snap.Info, error) + ListRefresh([]*store.RefreshCandidate, *auth.UserState, *store.RefreshOptions) ([]*snap.Info, error) + Sections(user *auth.UserState) ([]string, error) + WriteCatalogs(names io.Writer, adder store.SnapAdder) error + Download(context.Context, string, string, *snap.DownloadInfo, progress.Meter, *auth.UserState) error + + Assertion(assertType *asserts.AssertionType, primaryKey []string, user *auth.UserState) (asserts.Assertion, error) + + SuggestedCurrency() string + Buy(options *store.BuyOptions, user *auth.UserState) (*store.BuyResult, error) + ReadyToBuy(*auth.UserState) error +} + type managerBackend interface { // install releated SetupSnap(snapFilePath string, si *snap.SideInfo, meter progress.Meter) error diff -Nru snapd-2.29.4.2+17.10/overlord/snapstate/backend_test.go snapd-2.31.1+17.10/overlord/snapstate/backend_test.go --- snapd-2.29.4.2+17.10/overlord/snapstate/backend_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/snapstate/backend_test.go 2018-01-31 08:47:06.000000000 +0000 @@ -23,6 +23,7 @@ "errors" "fmt" "io" + "path/filepath" "sort" "strings" @@ -52,6 +53,8 @@ aliases []*backend.Alias rmAliases []*backend.Alias + + userID int } type fakeOps []fakeOp @@ -119,12 +122,6 @@ } confinement := snap.StrictConfinement - switch spec.Channel { - case "channel-for-devmode": - confinement = snap.DevModeConfinement - case "channel-for-classic": - confinement = snap.ClassicConfinement - } typ := snap.TypeApp if spec.Name == "some-core" { @@ -136,7 +133,7 @@ SideInfo: snap.SideInfo{ RealName: spec.Name, Channel: spec.Channel, - SnapID: "snapIDsnapidsnapidsnapidsnapidsn", + SnapID: spec.Name + "-id", Revision: spec.Revision, }, Version: spec.Name, @@ -146,7 +143,23 @@ Confinement: confinement, Type: typ, } - f.fakeBackend.ops = append(f.fakeBackend.ops, fakeOp{op: "storesvc-snap", name: spec.Name, revno: spec.Revision}) + switch spec.Channel { + case "channel-for-devmode": + info.Confinement = snap.DevModeConfinement + case "channel-for-classic": + info.Confinement = snap.ClassicConfinement + case "channel-for-paid": + info.Prices = map[string]float64{"USD": 0.77} + info.SideInfo.Paid = true + case "channel-for-private": + info.SideInfo.Private = true + } + + userID := 0 + if user != nil { + userID = user.ID + } + f.fakeBackend.ops = append(f.fakeBackend.ops, fakeOp{op: "storesvc-snap", name: spec.Name, revno: spec.Revision, userID: userID}) return info, nil } @@ -173,6 +186,8 @@ name = "some-snap" case "core-snap-id": name = "core" + case "snap-with-snapd-control-id": + name = "snap-with-snapd-control" default: panic(fmt.Sprintf("ListRefresh: unknown snap-id: %s", cand.SnapID)) } @@ -217,8 +232,12 @@ } } + userID := 0 + if user != nil { + userID = user.ID + } // TODO: move this back to ListRefresh - f.fakeBackend.ops = append(f.fakeBackend.ops, fakeOp{op: "storesvc-list-refresh", cand: *cand, revno: hit}) + f.fakeBackend.ops = append(f.fakeBackend.ops, fakeOp{op: "storesvc-list-refresh", cand: *cand, revno: hit, userID: userID}) if !hit.Unset() { return info, nil @@ -227,19 +246,19 @@ return nil, store.ErrNoUpdateAvailable } -func (f *fakeStore) ListRefresh(cands []*store.RefreshCandidate, _ *auth.UserState) ([]*snap.Info, error) { +func (f *fakeStore) ListRefresh(cands []*store.RefreshCandidate, user *auth.UserState, flags *store.RefreshOptions) ([]*snap.Info, error) { f.pokeStateLock() if len(cands) == 0 { return nil, nil } - if len(cands) > 2 { - panic("ListRefresh unexpectedly called with more than two candidates") + if len(cands) > 3 { + panic("fake ListRefresh unexpectedly called with more than 3 candidates") } var res []*snap.Info for _, cand := range cands { - info, err := f.LookupRefresh(cand, nil) + info, err := f.LookupRefresh(cand, user) if err == store.ErrLocalSnap || err == store.ErrNoUpdateAvailable { continue } @@ -274,7 +293,7 @@ return nil } -func (f *fakeStore) WriteCatalogs(io.Writer) error { +func (f *fakeStore) WriteCatalogs(io.Writer, store.SnapAdder) error { f.pokeStateLock() f.fakeBackend.ops = append(f.fakeBackend.ops, fakeOp{ op: "x-commands", @@ -297,6 +316,7 @@ linkSnapFailTrigger string copySnapDataFailTrigger string + emptyContainer snap.Container } func (f *fakeSnappyBackend) OpenSnapFile(snapFilePath string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { @@ -309,8 +329,13 @@ op.sinfo = *si } + name := filepath.Base(snapFilePath) + if idx := strings.IndexByte(name, '_'); idx > -1 { + name = name[:idx] + } + f.ops = append(f.ops, op) - return &snap.Info{Architectures: []string{"all"}}, nil, nil + return &snap.Info{SuggestedName: name, Architectures: []string{"all"}}, f.emptyContainer, nil } func (f *fakeSnappyBackend) SetupSnap(snapFilePath string, si *snap.SideInfo, p progress.Meter) error { diff -Nru snapd-2.29.4.2+17.10/overlord/snapstate/catalogrefresh.go snapd-2.31.1+17.10/overlord/snapstate/catalogrefresh.go --- snapd-2.29.4.2+17.10/overlord/snapstate/catalogrefresh.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/snapstate/catalogrefresh.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,121 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package snapstate + +import ( + "fmt" + "os" + "sort" + "strings" + "time" + + "github.com/snapcore/snapd/advisor" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/overlord/state" +) + +var catalogRefreshDelay = 24 * time.Hour + +type catalogRefresh struct { + state *state.State + + nextCatalogRefresh time.Time +} + +func newCatalogRefresh(st *state.State) *catalogRefresh { + return &catalogRefresh{state: st} +} + +// Ensure will ensure that the catalog refresh happens +func (r *catalogRefresh) Ensure() error { + r.state.Lock() + defer r.state.Unlock() + + // sneakily don't do anything if in testing + if CanAutoRefresh == nil { + return nil + } + + theStore := Store(r.state) + now := time.Now() + needsRefresh := r.nextCatalogRefresh.IsZero() || r.nextCatalogRefresh.Before(now) + + if !needsRefresh { + return nil + } + + next := now.Add(catalogRefreshDelay) + // catalog refresh does not carry on trying on error + r.nextCatalogRefresh = next + + logger.Debugf("Catalog refresh starting now; next scheduled for %s.", next) + + return refreshCatalogs(r.state, theStore) +} + +var newCmdDB = advisor.Create + +func refreshCatalogs(st *state.State, theStore StoreService) error { + st.Unlock() + defer st.Lock() + + if err := os.MkdirAll(dirs.SnapCacheDir, 0755); err != nil { + return fmt.Errorf("cannot create directory %q: %v", dirs.SnapCacheDir, err) + } + + sections, err := theStore.Sections(nil) + if err != nil { + return err + } + + sort.Strings(sections) + if err := osutil.AtomicWriteFile(dirs.SnapSectionsFile, []byte(strings.Join(sections, "\n")), 0644, 0); err != nil { + return err + } + + namesFile, err := osutil.NewAtomicFile(dirs.SnapNamesFile, 0644, 0, osutil.NoChown, osutil.NoChown) + if err != nil { + return err + } + defer namesFile.Cancel() + + cmdDB, err := newCmdDB() + if err != nil { + return err + } + + // if all goes well we'll Commit() making this a NOP: + defer cmdDB.Rollback() + + if err := theStore.WriteCatalogs(namesFile, cmdDB); err != nil { + return err + } + + err1 := namesFile.Commit() + err2 := cmdDB.Commit() + + if err2 != nil { + return err2 + } + + return err1 +} diff -Nru snapd-2.29.4.2+17.10/overlord/snapstate/catalogrefresh_test.go snapd-2.31.1+17.10/overlord/snapstate/catalogrefresh_test.go --- snapd-2.29.4.2+17.10/overlord/snapstate/catalogrefresh_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/snapstate/catalogrefresh_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,119 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package snapstate_test + +import ( + "io" + "io/ioutil" + "time" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/advisor" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/overlord/auth" + "github.com/snapcore/snapd/overlord/snapstate" + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/store" + "github.com/snapcore/snapd/store/storetest" +) + +type catalogStore struct { + storetest.Store + + ops []string +} + +func (r *catalogStore) WriteCatalogs(w io.Writer, a store.SnapAdder) error { + r.ops = append(r.ops, "write-catalog") + w.Write([]byte("pkg1\npkg2")) + a.AddSnap("foo", []string{"foo", "meh"}) + a.AddSnap("bar", []string{"bar", "meh"}) + return nil +} + +func (r *catalogStore) Sections(*auth.UserState) ([]string, error) { + r.ops = append(r.ops, "sections") + return []string{"section1", "section2"}, nil +} + +type catalogRefreshTestSuite struct { + state *state.State + + store *catalogStore + tmpdir string +} + +var _ = Suite(&catalogRefreshTestSuite{}) + +func (s *catalogRefreshTestSuite) SetUpTest(c *C) { + s.tmpdir = c.MkDir() + dirs.SetRootDir(s.tmpdir) + s.state = state.New(nil) + + s.store = &catalogStore{} + s.state.Lock() + snapstate.ReplaceStore(s.state, s.store) + s.state.Unlock() + + snapstate.CanAutoRefresh = func(*state.State) (bool, error) { return true, nil } +} + +func (s *catalogRefreshTestSuite) TearDownTest(c *C) { + snapstate.CanAutoRefresh = nil +} + +func (s *catalogRefreshTestSuite) TestCatalogRefresh(c *C) { + cr7 := snapstate.NewCatalogRefresh(s.state) + err := cr7.Ensure() + c.Check(err, IsNil) + + c.Check(s.store.ops, DeepEquals, []string{"sections", "write-catalog"}) + + c.Check(osutil.FileExists(dirs.SnapSectionsFile), Equals, true) + content, err := ioutil.ReadFile(dirs.SnapSectionsFile) + c.Assert(err, IsNil) + c.Check(string(content), Equals, "section1\nsection2") + + c.Check(osutil.FileExists(dirs.SnapNamesFile), Equals, true) + content, err = ioutil.ReadFile(dirs.SnapNamesFile) + c.Assert(err, IsNil) + c.Check(string(content), Equals, "pkg1\npkg2") + + c.Check(osutil.FileExists(dirs.SnapCommandsDB), Equals, true) + dump, err := advisor.Dump() + c.Assert(err, IsNil) + c.Check(dump, DeepEquals, map[string][]string{ + "foo": {"foo"}, + "bar": {"bar"}, + "meh": {"foo", "bar"}, + }) +} + +func (s *catalogRefreshTestSuite) TestCatalogRefreshNotNeeded(c *C) { + cr7 := snapstate.NewCatalogRefresh(s.state) + snapstate.MockCatalogRefreshNextRefresh(cr7, time.Now().Add(1*time.Hour)) + err := cr7.Ensure() + c.Check(err, IsNil) + c.Check(s.store.ops, HasLen, 0) + c.Check(osutil.FileExists(dirs.SnapSectionsFile), Equals, false) + c.Check(osutil.FileExists(dirs.SnapNamesFile), Equals, false) +} diff -Nru snapd-2.29.4.2+17.10/overlord/snapstate/check_snap.go snapd-2.31.1+17.10/overlord/snapstate/check_snap.go --- snapd-2.29.4.2+17.10/overlord/snapstate/check_snap.go 2017-09-13 14:47:18.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/snapstate/check_snap.go 2018-01-24 20:02:44.000000000 +0000 @@ -27,6 +27,7 @@ "github.com/snapcore/snapd/arch" "github.com/snapcore/snapd/cmd" + "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/overlord/snapstate/backend" "github.com/snapcore/snapd/overlord/state" "github.com/snapcore/snapd/release" @@ -182,11 +183,19 @@ var openSnapFile = backend.OpenSnapFile +func validateContainer(c snap.Container, s *snap.Info, logf func(format string, v ...interface{})) error { + err := snap.ValidateContainer(c, s, logf) + if err == nil { + return nil + } + return fmt.Errorf("%v; contact developer", err) +} + // checkSnap ensures that the snap can be installed. func checkSnap(st *state.State, snapFilePath string, si *snap.SideInfo, curInfo *snap.Info, flags Flags) error { // This assumes that the snap was already verified or --dangerous was used. - s, _, err := openSnapFile(snapFilePath, si) + s, c, err := openSnapFile(snapFilePath, si) if err != nil { return err } @@ -195,6 +204,10 @@ return err } + if err := validateContainer(c, s, logger.Noticef); err != nil { + return err + } + st.Lock() defer st.Unlock() diff -Nru snapd-2.29.4.2+17.10/overlord/snapstate/check_snap_test.go snapd-2.31.1+17.10/overlord/snapstate/check_snap_test.go --- snapd-2.29.4.2+17.10/overlord/snapstate/check_snap_test.go 2017-10-23 06:17:27.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/snapstate/check_snap_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -22,6 +22,9 @@ import ( "errors" "fmt" + "io/ioutil" + "os" + "path/filepath" . "gopkg.in/check.v1" @@ -31,6 +34,7 @@ "github.com/snapcore/snapd/overlord/state" "github.com/snapcore/snapd/release" "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snapdir" "github.com/snapcore/snapd/snap/snaptest" "github.com/snapcore/snapd/testutil" @@ -69,7 +73,7 @@ var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { c.Check(path, Equals, "snap-path") c.Check(si, IsNil) - return info, nil, nil + return info, emptyContainer(c), nil } restore := snapstate.MockOpenSnapFile(openSnapFile) defer restore() @@ -160,7 +164,7 @@ c.Assert(err, IsNil) var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { - return info, nil, nil + return info, emptyContainer(c), nil } restore := snapstate.MockOpenSnapFile(openSnapFile) defer restore() @@ -183,7 +187,7 @@ var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { info := snaptest.MockInfo(c, yaml, si) - return info, nil, nil + return info, emptyContainer(c), nil } r1 := snapstate.MockOpenSnapFile(openSnapFile) defer r1() @@ -212,7 +216,7 @@ c.Assert(err, IsNil) var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { - return info, nil, nil + return info, emptyContainer(c), nil } restore := snapstate.MockOpenSnapFile(openSnapFile) defer restore() @@ -260,7 +264,7 @@ c.Assert(err, IsNil) var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { - return info, nil, nil + return info, emptyContainer(c), nil } restore := snapstate.MockOpenSnapFile(openSnapFile) defer restore() @@ -302,7 +306,7 @@ c.Assert(err, IsNil) var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { - return info, nil, nil + return info, emptyContainer(c), nil } restore := snapstate.MockOpenSnapFile(openSnapFile) defer restore() @@ -343,7 +347,7 @@ c.Assert(err, IsNil) var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { - return info, nil, nil + return info, emptyContainer(c), nil } restore := snapstate.MockOpenSnapFile(openSnapFile) defer restore() @@ -384,7 +388,7 @@ c.Assert(err, IsNil) var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { - return info, nil, nil + return info, emptyContainer(c), nil } restore := snapstate.MockOpenSnapFile(openSnapFile) defer restore() @@ -426,7 +430,7 @@ c.Assert(err, IsNil) var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { - return info, nil, nil + return info, emptyContainer(c), nil } restore := snapstate.MockOpenSnapFile(openSnapFile) defer restore() @@ -454,7 +458,7 @@ c.Assert(err, IsNil) var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { - return info, nil, nil + return info, emptyContainer(c), nil } restore := snapstate.MockOpenSnapFile(openSnapFile) defer restore() @@ -476,7 +480,7 @@ var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { c.Check(path, Equals, "snap-path") c.Check(si, IsNil) - return info, nil, nil + return info, emptyContainer(c), nil } restore := snapstate.MockOpenSnapFile(openSnapFile) defer restore() @@ -497,7 +501,7 @@ var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { c.Check(path, Equals, "snap-path") c.Check(si, IsNil) - return info, nil, nil + return info, emptyContainer(c), nil } restore := snapstate.MockOpenSnapFile(openSnapFile) defer restore() @@ -521,7 +525,7 @@ var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { c.Check(path, Equals, "snap-path") c.Check(si, IsNil) - return info, nil, nil + return info, emptyContainer(c), nil } restore := snapstate.MockOpenSnapFile(openSnapFile) defer restore() @@ -565,7 +569,7 @@ c.Assert(err, IsNil) var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { - return info, nil, nil + return info, emptyContainer(c), nil } restore := snapstate.MockOpenSnapFile(openSnapFile) defer restore() @@ -607,7 +611,7 @@ c.Assert(err, IsNil) var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { - return info, nil, nil + return info, emptyContainer(c), nil } restore := snapstate.MockOpenSnapFile(openSnapFile) defer restore() @@ -632,7 +636,7 @@ c.Assert(err, IsNil) var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { - return info, nil, nil + return info, emptyContainer(c), nil } restore := snapstate.MockOpenSnapFile(openSnapFile) defer restore() @@ -670,7 +674,7 @@ c.Assert(err, IsNil) var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { - return info, nil, nil + return info, emptyContainer(c), nil } restore := snapstate.MockOpenSnapFile(openSnapFile) defer restore() @@ -680,3 +684,14 @@ st.Lock() c.Check(err, IsNil) } + +// emptyContainer returns a minimal container that passes +// ValidateContainer: / and /meta exist and are 0755, and +// /meta/snap.yaml is a regular world-readable file. +func emptyContainer(c *C) *snapdir.SnapDir { + d := c.MkDir() + c.Assert(os.Chmod(d, 0755), IsNil) + c.Assert(os.Mkdir(filepath.Join(d, "meta"), 0755), IsNil) + c.Assert(ioutil.WriteFile(filepath.Join(d, "meta", "snap.yaml"), nil, 0444), IsNil) + return snapdir.New(d) +} diff -Nru snapd-2.29.4.2+17.10/overlord/snapstate/cookies.go snapd-2.31.1+17.10/overlord/snapstate/cookies.go --- snapd-2.29.4.2+17.10/overlord/snapstate/cookies.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/snapstate/cookies.go 2017-12-01 15:51:55.000000000 +0000 @@ -0,0 +1,168 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package snapstate + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/strutil" +) + +func cookies(st *state.State) (map[string]string, error) { + var snapCookies map[string]string + if err := st.Get("snap-cookies", &snapCookies); err != nil { + if err != state.ErrNoState { + return nil, fmt.Errorf("cannot get snap cookies: %v", err) + } + snapCookies = make(map[string]string) + } + return snapCookies, nil +} + +// SyncCookies creates snap cookies for snaps that are missing them (may be the case for snaps installed +// before the feature of running snapctl outside of hooks was introduced, leading to a warning +// from snap-confine). +// It is the caller's responsibility to lock state before calling this function. +func (m *SnapManager) SyncCookies(st *state.State) error { + var snapNames map[string]*json.RawMessage + if err := st.Get("snaps", &snapNames); err != nil && err != state.ErrNoState { + return err + } + + snapCookies, err := cookies(st) + if err != nil { + return err + } + + snapsWithCookies := make(map[string]bool) + for _, snap := range snapCookies { + // check if we have a cookie for non-installed snap or if we have a duplicated cookie + if _, ok := snapNames[snap]; !ok || snapsWithCookies[snap] { + // there is no point in checking all cookies if we found a bad one - recreate them all + snapCookies = make(map[string]string) + snapsWithCookies = make(map[string]bool) + break + } + snapsWithCookies[snap] = true + } + + var changed bool + + // make sure every snap has a cookie, generate one if necessary + for snap := range snapNames { + if _, ok := snapsWithCookies[snap]; !ok { + cookie := makeCookie() + snapCookies[cookie] = snap + changed = true + } + } + + content := make(map[string]*osutil.FileState) + for cookie, snap := range snapCookies { + content[fmt.Sprintf("snap.%s", snap)] = &osutil.FileState{ + Content: []byte(cookie), + Mode: 0600, + } + } + if _, _, err := osutil.EnsureDirState(dirs.SnapCookieDir, "snap.*", content); err != nil { + return fmt.Errorf("Failed to synchronize snap cookies: %s", err) + } + + if changed { + st.Set("snap-cookies", &snapCookies) + } + return nil +} + +func (m *SnapManager) createSnapCookie(st *state.State, snapName string) error { + snapCookies, err := cookies(st) + if err != nil { + return err + } + + // make sure we don't create cookie if it already exists + for _, snap := range snapCookies { + if snapName == snap { + return nil + } + } + + cookieID, err := createCookieFile(snapName) + if err != nil { + return err + } + + snapCookies[cookieID] = snapName + st.Set("snap-cookies", &snapCookies) + return nil +} + +func (m *SnapManager) removeSnapCookie(st *state.State, snapName string) error { + if err := removeCookieFile(snapName); err != nil { + return err + } + + var snapCookies map[string]string + err := st.Get("snap-cookies", &snapCookies) + if err != nil { + if err != state.ErrNoState { + return fmt.Errorf("cannot get snap cookies: %v", err) + } + // no cookies in the state + return nil + } + + for cookieID, snap := range snapCookies { + if snapName == snap { + delete(snapCookies, cookieID) + st.Set("snap-cookies", snapCookies) + return nil + } + } + return nil +} + +func makeCookie() string { + return strutil.MakeRandomString(44) +} + +func createCookieFile(snapName string) (cookieID string, err error) { + cookieID = makeCookie() + path := filepath.Join(dirs.SnapCookieDir, fmt.Sprintf("snap.%s", snapName)) + err = osutil.AtomicWriteFile(path, []byte(cookieID), 0600, 0) + if err != nil { + return "", fmt.Errorf("Failed to create cookie file %q: %s", path, err) + } + return cookieID, nil +} + +func removeCookieFile(snapName string) error { + path := filepath.Join(dirs.SnapCookieDir, fmt.Sprintf("snap.%s", snapName)) + if err := os.Remove(path); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("Failed to remove cookie file %q: %s", path, err) + } + return nil +} diff -Nru snapd-2.29.4.2+17.10/overlord/snapstate/cookies_test.go snapd-2.31.1+17.10/overlord/snapstate/cookies_test.go --- snapd-2.29.4.2+17.10/overlord/snapstate/cookies_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/snapstate/cookies_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -0,0 +1,158 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package snapstate + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "path/filepath" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/testutil" +) + +type cookiesSuite struct { + testutil.BaseTest + st *state.State + snapmgr *SnapManager +} + +var _ = Suite(&cookiesSuite{}) + +func (s *cookiesSuite) SetUpTest(c *C) { + s.BaseTest.SetUpTest(c) + dirs.SetRootDir(c.MkDir()) + s.st = state.New(nil) + s.snapmgr, _ = Manager(s.st) +} + +func (s *cookiesSuite) TearDownTest(c *C) { + s.BaseTest.TearDownTest(c) +} + +func checkCookie(c *C, st *state.State, snapName string) { + var cookies map[string]string + var found int + var cookieID string + + c.Assert(st.Get("snap-cookies", &cookies), IsNil) + + for cookie, snap := range cookies { + if snap == snapName { + found = found + 1 + cookieID = cookie + } + } + c.Assert(found, Equals, 1) + + data, err := ioutil.ReadFile(fmt.Sprintf("%s/snap.%s", dirs.SnapCookieDir, snapName)) + c.Assert(err, IsNil) + c.Assert(string(data), DeepEquals, cookieID) + c.Assert(cookieID, HasLen, 44) +} + +func (s *cookiesSuite) TestSyncCookies(c *C) { + s.st.Lock() + defer s.st.Unlock() + + // verify that SyncCookies creates a cookie for a snap that's missing it and removes stale/invalid cookies + s.st.Set("snaps", map[string]*json.RawMessage{ + "some-snap": nil, + "other-snap": nil}) + staleCookieFile := filepath.Join(dirs.SnapCookieDir, "snap.stale-cookie-snap") + c.Assert(ioutil.WriteFile(staleCookieFile, nil, 0644), IsNil) + c.Assert(osutil.FileExists(staleCookieFile), Equals, true) + + // some-snap doesn't have cookie + cookies := map[string]string{ + "123456": "other-snap", + "809809": "other-snap", + "999999": "unknown-snap", + "199989": "unknown-snap", + } + s.st.Set("snap-cookies", cookies) + + for i := 0; i < 2; i++ { + s.snapmgr.SyncCookies(s.st) + + c.Assert(osutil.FileExists(staleCookieFile), Equals, false) + + var newCookies map[string]string + err := s.st.Get("snap-cookies", &newCookies) + c.Assert(err, IsNil) + c.Assert(newCookies, HasLen, 2) + + cookieFile := filepath.Join(dirs.SnapCookieDir, "snap.some-snap") + c.Assert(osutil.FileExists(cookieFile), Equals, true) + data, err := ioutil.ReadFile(cookieFile) + c.Assert(err, IsNil) + c.Assert(newCookies[string(data)], NotNil) + c.Assert(newCookies[string(data)], Equals, "some-snap") + + cookieFile = filepath.Join(dirs.SnapCookieDir, "snap.other-snap") + c.Assert(osutil.FileExists(cookieFile), Equals, true) + data, err = ioutil.ReadFile(cookieFile) + c.Assert(err, IsNil) + c.Assert(newCookies[string(data)], NotNil) + c.Assert(newCookies[string(data)], Equals, "other-snap") + } +} + +func (s *cookiesSuite) TestCreateSnapCookie(c *C) { + s.st.Lock() + defer s.st.Unlock() + + c.Assert(s.snapmgr.createSnapCookie(s.st, "foo"), IsNil) + checkCookie(c, s.st, "foo") + c.Assert(s.snapmgr.createSnapCookie(s.st, "foo"), IsNil) + checkCookie(c, s.st, "foo") +} + +func (s *cookiesSuite) TestRemoveSnapCookie(c *C) { + s.st.Lock() + defer s.st.Unlock() + + cookieFile := filepath.Join(dirs.SnapCookieDir, "snap.bar") + + c.Assert(ioutil.WriteFile(cookieFile, nil, 0644), IsNil) + + // remove should not fail if cookie is not there + c.Assert(s.snapmgr.removeSnapCookie(s.st, "bar"), IsNil) + c.Assert(osutil.FileExists(cookieFile), Equals, false) + + c.Assert(s.snapmgr.createSnapCookie(s.st, "foo"), IsNil) + c.Assert(s.snapmgr.createSnapCookie(s.st, "bar"), IsNil) + c.Assert(osutil.FileExists(cookieFile), Equals, true) + + c.Assert(s.snapmgr.removeSnapCookie(s.st, "bar"), IsNil) + c.Assert(osutil.FileExists(cookieFile), Equals, false) + + var cookies map[string]string + c.Assert(s.st.Get("snap-cookies", &cookies), IsNil) + c.Assert(cookies, HasLen, 1) + + // cookie for snap "foo" remains untouched + checkCookie(c, s.st, "foo") +} diff -Nru snapd-2.29.4.2+17.10/overlord/snapstate/export_test.go snapd-2.31.1+17.10/overlord/snapstate/export_test.go --- snapd-2.29.4.2+17.10/overlord/snapstate/export_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/snapstate/export_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -71,6 +71,10 @@ m.runner.AddHandler("run-hook", func(task *state.Task, _ *tomb.Tomb) error { return nil }, nil) + m.runner.AddHandler("configure-snapd", func(t *state.Task, _ *tomb.Tomb) error { + return nil + }, nil) + } // AddAdhocTaskHandlers registers handlers for ad hoc test handler @@ -106,8 +110,11 @@ CheckSnap = checkSnap CanRemove = canRemove CanDisable = canDisable + CachedStore = cachedStore DefaultRefreshSchedule = defaultRefreshSchedule NameAndRevnoFromSnap = nameAndRevnoFromSnap + DoInstall = doInstall + UserFromUserID = userFromUserID ) func PreviousSideInfo(snapst *SnapState) *snap.SideInfo { @@ -122,3 +129,28 @@ CheckAliasesConflicts = checkAliasesConflicts DisableAliases = disableAliases ) + +// readme files +var ( + WriteSnapReadme = writeSnapReadme + SnapReadme = snapReadme +) + +// refreshes +var ( + NewAutoRefresh = newAutoRefresh + NewRefreshHints = newRefreshHints + NewCatalogRefresh = newCatalogRefresh +) + +func MockCatalogRefreshNextRefresh(cr *catalogRefresh, when time.Time) { + cr.nextCatalogRefresh = when +} + +func MockRefreshRetryDelay(d time.Duration) func() { + origRefreshRetryDelay := refreshRetryDelay + refreshRetryDelay = d + return func() { + refreshRetryDelay = origRefreshRetryDelay + } +} diff -Nru snapd-2.29.4.2+17.10/overlord/snapstate/flags.go snapd-2.31.1+17.10/overlord/snapstate/flags.go --- snapd-2.29.4.2+17.10/overlord/snapstate/flags.go 2017-11-30 07:12:26.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/snapstate/flags.go 2018-01-31 08:47:06.000000000 +0000 @@ -53,6 +53,10 @@ // Unaliased is set to request that no automatic aliases are created // installing the snap. Unaliased bool `json:"unaliased,omitempty"` + + // Amend allows refreshing out of a snap unknown to the store + // and into one that is known. + Amend bool `json:"amend,omitempty"` } // DevModeAllowed returns whether a snap can be installed with devmode confinement (either set or overridden) diff -Nru snapd-2.29.4.2+17.10/overlord/snapstate/handlers_download_test.go snapd-2.31.1+17.10/overlord/snapstate/handlers_download_test.go --- snapd-2.29.4.2+17.10/overlord/snapstate/handlers_download_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/snapstate/handlers_download_test.go 2018-01-31 08:47:06.000000000 +0000 @@ -24,7 +24,6 @@ "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" - "github.com/snapcore/snapd/overlord/storestate" "github.com/snapcore/snapd/snap" ) @@ -50,7 +49,7 @@ state: s.state, fakeBackend: s.fakeBackend, } - storestate.ReplaceStore(s.state, s.fakeStore) + snapstate.ReplaceStore(s.state, s.fakeStore) var err error s.snapmgr, err = snapstate.Manager(s.state) @@ -108,7 +107,7 @@ t.Get("snap-setup", &snapsup) c.Check(snapsup.SideInfo, DeepEquals, &snap.SideInfo{ RealName: "foo", - SnapID: "snapIDsnapidsnapidsnapidsnapidsn", + SnapID: "foo-id", Revision: snap.R(11), Channel: "some-channel", }) diff -Nru snapd-2.29.4.2+17.10/overlord/snapstate/handlers.go snapd-2.31.1+17.10/overlord/snapstate/handlers.go --- snapd-2.29.4.2+17.10/overlord/snapstate/handlers.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/snapstate/handlers.go 2018-01-24 20:02:44.000000000 +0000 @@ -22,7 +22,6 @@ import ( "fmt" "os" - "path/filepath" "strconv" "strings" "time" @@ -30,17 +29,14 @@ "gopkg.in/tomb.v2" "github.com/snapcore/snapd/boot" - "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/logger" - "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/overlord/auth" "github.com/snapcore/snapd/overlord/configstate/config" "github.com/snapcore/snapd/overlord/snapstate/backend" "github.com/snapcore/snapd/overlord/state" - "github.com/snapcore/snapd/overlord/storestate" "github.com/snapcore/snapd/release" "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/store" - "github.com/snapcore/snapd/strutil" ) // TaskSnapSetup returns the SnapSetup with task params hold by or referred to by the the task. @@ -325,7 +321,7 @@ } st.Lock() - theStore := storestate.Store(st) + theStore := Store(st) user, err := userFromUserID(st, snapsup.UserID) st.Unlock() if err != nil { @@ -482,10 +478,6 @@ return err } - if err := m.createSnapCookie(st, snapsup.Name()); err != nil { - return fmt.Errorf("cannot create snap cookie: %v", err) - } - snapst.Active = true err = m.backend.LinkSnap(oldInfo) if err != nil { @@ -571,49 +563,6 @@ return nil } -func (m *SnapManager) createSnapCookie(st *state.State, snapName string) error { - cookieID := strutil.MakeRandomString(44) - path := filepath.Join(dirs.SnapCookieDir, fmt.Sprintf("snap.%s", snapName)) - err := osutil.AtomicWriteFile(path, []byte(cookieID), 0600, 0) - if err != nil { - return err - } - - var contexts map[string]string - err = st.Get("snap-cookies", &contexts) - if err != nil { - if err != state.ErrNoState { - return fmt.Errorf("cannot get snap cookies: %v", err) - } - contexts = make(map[string]string) - } - contexts[cookieID] = snapName - st.Set("snap-cookies", &contexts) - return nil -} - -func (m *SnapManager) removeSnapCookie(st *state.State, snapName string) error { - var contexts map[string]string - err := st.Get("snap-cookies", &contexts) - if err != nil { - if err != state.ErrNoState { - return fmt.Errorf("cannot get snap cookies: %v", err) - } - contexts = make(map[string]string) - } - - for cookieID, snap := range contexts { - if snapName == snap { - delete(contexts, cookieID) - st.Set("snap-cookies", contexts) - // error on removing the context file is not fatal - _ = os.Remove(filepath.Join(dirs.SnapCookieDir, fmt.Sprintf("snap.%s", snapName))) - break - } - } - return nil -} - func (m *SnapManager) doLinkSnap(t *state.Task, _ *tomb.Tomb) error { st := t.State() st.Lock() @@ -657,6 +606,23 @@ if snapsup.Required { // set only on install and left alone on refresh snapst.Required = true } + // only set userID if unset or logged out in snapst and if we + // actually have an associated user + if snapsup.UserID > 0 { + var user *auth.UserState + if snapst.UserID != 0 { + user, err = auth.User(st, snapst.UserID) + if err != nil && err != auth.ErrInvalidUser { + return err + } + } + if user == nil { + // if the original user installing the snap is + // no longer available transfer to user who + // triggered this change + snapst.UserID = snapsup.UserID + } + } newInfo, err := readInfo(snapsup.Name(), cand) if err != nil { @@ -779,7 +745,7 @@ if len(snapst.Sequence) == 1 { if err := m.removeSnapCookie(st, snapsup.Name()); err != nil { - return fmt.Errorf("cannot remove snap context: %v", err) + return fmt.Errorf("cannot remove snap cookie: %v", err) } } @@ -863,6 +829,38 @@ return nil } +func (m *SnapManager) doSwitchSnap(t *state.Task, _ *tomb.Tomb) error { + st := t.State() + st.Lock() + defer st.Unlock() + + snapsup, snapst, err := snapSetupAndState(t) + if err != nil { + return err + } + snapst.Channel = snapsup.Channel + + Set(st, snapsup.Name(), snapst) + return nil +} + +func (m *SnapManager) doToggleSnapFlags(t *state.Task, _ *tomb.Tomb) error { + st := t.State() + st.Lock() + defer st.Unlock() + + snapsup, snapst, err := snapSetupAndState(t) + if err != nil { + return err + } + + // for now we support toggling only ignore-validation + snapst.IgnoreValidation = snapsup.IgnoreValidation + + Set(st, snapsup.Name(), snapst) + return nil +} + func (m *SnapManager) startSnapServices(t *state.Task, _ *tomb.Tomb) error { st := t.State() st.Lock() @@ -1027,7 +1025,7 @@ return &state.Retry{After: 3 * time.Minute} } if err := m.removeSnapCookie(st, snapsup.Name()); err != nil { - return fmt.Errorf("cannot remove snap context: %v", err) + return fmt.Errorf("cannot remove snap cookie: %v", err) } } if err = config.DiscardRevisionConfig(st, snapsup.Name(), snapsup.Revision()); err != nil { diff -Nru snapd-2.29.4.2+17.10/overlord/snapstate/handlers_link_test.go snapd-2.31.1+17.10/overlord/snapstate/handlers_link_test.go --- snapd-2.29.4.2+17.10/overlord/snapstate/handlers_link_test.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/snapstate/handlers_link_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -20,6 +20,7 @@ package snapstate_test import ( + "encoding/json" "fmt" "path/filepath" "time" @@ -27,10 +28,12 @@ . "gopkg.in/check.v1" "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/overlord/auth" "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" "github.com/snapcore/snapd/release" "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/testutil" ) type linkSnapSuite struct { @@ -108,6 +111,7 @@ Revision: snap.R(33), }, Channel: "beta", + UserID: 2, }) s.state.NewChange("dummy", "...").AddTask(t) @@ -132,10 +136,115 @@ c.Check(snapst.Sequence, HasLen, 1) c.Check(snapst.Current, Equals, snap.R(33)) c.Check(snapst.Channel, Equals, "beta") + c.Check(snapst.UserID, Equals, 2) c.Check(t.Status(), Equals, state.DoneStatus) c.Check(s.stateBackend.restartRequested, HasLen, 0) } +func (s *linkSnapSuite) TestDoLinkSnapSuccessNoUserID(c *C) { + s.state.Lock() + t := s.state.NewTask("link-snap", "test") + t.Set("snap-setup", &snapstate.SnapSetup{ + SideInfo: &snap.SideInfo{ + RealName: "foo", + Revision: snap.R(33), + }, + Channel: "beta", + }) + s.state.NewChange("dummy", "...").AddTask(t) + + s.state.Unlock() + s.snapmgr.Ensure() + s.snapmgr.Wait() + s.state.Lock() + defer s.state.Unlock() + + // check that snapst.UserID does not get set + var snapst snapstate.SnapState + err := snapstate.Get(s.state, "foo", &snapst) + c.Assert(err, IsNil) + c.Check(snapst.UserID, Equals, 0) + + var snaps map[string]*json.RawMessage + err = s.state.Get("snaps", &snaps) + c.Assert(err, IsNil) + raw := []byte(*snaps["foo"]) + c.Check(string(raw), Not(testutil.Contains), "user-id") +} + +func (s *linkSnapSuite) TestDoLinkSnapSuccessUserIDAlreadySet(c *C) { + s.state.Lock() + snapstate.Set(s.state, "foo", &snapstate.SnapState{ + Sequence: []*snap.SideInfo{ + {RealName: "foo", Revision: snap.R(1)}, + }, + Current: snap.R(1), + UserID: 1, + }) + // the user + user, err := auth.NewUser(s.state, "username", "email@test.com", "macaroon", []string{"discharge"}) + c.Assert(err, IsNil) + c.Assert(user.ID, Equals, 1) + + t := s.state.NewTask("link-snap", "test") + t.Set("snap-setup", &snapstate.SnapSetup{ + SideInfo: &snap.SideInfo{ + RealName: "foo", + Revision: snap.R(33), + }, + Channel: "beta", + UserID: 2, + }) + s.state.NewChange("dummy", "...").AddTask(t) + + s.state.Unlock() + s.snapmgr.Ensure() + s.snapmgr.Wait() + s.state.Lock() + defer s.state.Unlock() + + // check that snapst.UserID was not "transferred" + var snapst snapstate.SnapState + err = snapstate.Get(s.state, "foo", &snapst) + c.Assert(err, IsNil) + c.Check(snapst.UserID, Equals, 1) +} + +func (s *linkSnapSuite) TestDoLinkSnapSuccessUserLoggedOut(c *C) { + s.state.Lock() + snapstate.Set(s.state, "foo", &snapstate.SnapState{ + Sequence: []*snap.SideInfo{ + {RealName: "foo", Revision: snap.R(1)}, + }, + Current: snap.R(1), + UserID: 1, + }) + + t := s.state.NewTask("link-snap", "test") + t.Set("snap-setup", &snapstate.SnapSetup{ + SideInfo: &snap.SideInfo{ + RealName: "foo", + Revision: snap.R(33), + }, + Channel: "beta", + UserID: 2, + }) + s.state.NewChange("dummy", "...").AddTask(t) + + s.state.Unlock() + s.snapmgr.Ensure() + s.snapmgr.Wait() + s.state.Lock() + defer s.state.Unlock() + + // check that snapst.UserID was transferred + // given that user 1 doesn't exist anymore + var snapst snapstate.SnapState + err := snapstate.Get(s.state, "foo", &snapst) + c.Assert(err, IsNil) + c.Check(snapst.UserID, Equals, 2) +} + func (s *linkSnapSuite) TestDoUndoLinkSnap(c *C) { s.state.Lock() defer s.state.Unlock() diff -Nru snapd-2.29.4.2+17.10/overlord/snapstate/readme.go snapd-2.31.1+17.10/overlord/snapstate/readme.go --- snapd-2.29.4.2+17.10/overlord/snapstate/readme.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/snapstate/readme.go 2017-12-01 15:51:55.000000000 +0000 @@ -0,0 +1,65 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package snapstate + +import ( + "os" + "strings" + + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" +) + +const snapREADME = ` +This directory presents installed snap packages. + +It has the following structure: + +@SNAP_MOUNT_DIR@/bin - Symlinks to snap applications. +@SNAP_MOUNT_DIR@// - Mountpoint for snap content. +@SNAP_MOUNT_DIR@//current - Symlink to current revision, if enabled. + +DISK SPACE USAGE + +The disk space consumed by the content under this directory is +minimal as the real snap content never leaves the .snap file. +Snaps are *mounted* rather than unpacked. + +For further details please visit +https://forum.snapcraft.io/t/the-snap-directory/2817 +` + +func snapReadme() string { + return strings.Replace(snapREADME, "@SNAP_MOUNT_DIR@", dirs.SnapMountDir, -1) +} + +func writeSnapReadme() error { + const fname = "README" + content := map[string]*osutil.FileState{ + fname: {Content: []byte(snapReadme()), Mode: 0444}, + } + if err := os.MkdirAll(dirs.SnapMountDir, 0755); err != nil { + return err + } + // NOTE: We are using EnsureDirState to not unconditionally write to flash + // and thus prolong life of the device. + _, _, err := osutil.EnsureDirState(dirs.SnapMountDir, fname, content) + return err +} diff -Nru snapd-2.29.4.2+17.10/overlord/snapstate/readme_test.go snapd-2.31.1+17.10/overlord/snapstate/readme_test.go --- snapd-2.29.4.2+17.10/overlord/snapstate/readme_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/snapstate/readme_test.go 2017-12-01 15:51:55.000000000 +0000 @@ -0,0 +1,80 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package snapstate_test + +import ( + "io/ioutil" + "os" + "path/filepath" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/overlord/snapstate" + "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/testutil" +) + +type readmeSuite struct{} + +var _ = Suite(&readmeSuite{}) + +func (s *readmeSuite) TestSnapReadmeFedora(c *C) { + restore := release.MockReleaseInfo(&release.OS{ID: "fedora"}) + defer restore() + + dirs.SetRootDir("/") + defer dirs.SetRootDir("/") + + c.Assert(snapstate.SnapReadme(), testutil.Contains, "/var/lib/snapd/snap/bin - Symlinks to snap applications.\n") +} + +func (s *readmeSuite) TestSnapReadmeUbuntu(c *C) { + restore := release.MockReleaseInfo(&release.OS{ID: "ubuntu"}) + defer restore() + + dirs.SetRootDir("/") + defer dirs.SetRootDir("/") + + c.Assert(snapstate.SnapReadme(), testutil.Contains, "/snap/bin - Symlinks to snap applications.\n") +} + +func (s *readmeSuite) TestWriteSnapREADME(c *C) { + dirs.SetRootDir(c.MkDir()) + defer dirs.SetRootDir("") + + f := filepath.Join(dirs.SnapMountDir, "README") + + // Missing file is created. + c.Assert(snapstate.WriteSnapReadme(), IsNil) + data, err := ioutil.ReadFile(f) + c.Assert(err, IsNil) + c.Check(string(data), testutil.Contains, "https://forum.snapcraft.io/t/the-snap-directory/2817") + + // Corrupted file is cured. + err = os.Remove(f) + c.Assert(err, IsNil) + err = ioutil.WriteFile(f, []byte("corrupted"), 0644) + c.Assert(err, IsNil) + c.Assert(snapstate.WriteSnapReadme(), IsNil) + data, err = ioutil.ReadFile(f) + c.Assert(err, IsNil) + c.Check(string(data), testutil.Contains, "https://forum.snapcraft.io/t/the-snap-directory/2817") +} diff -Nru snapd-2.29.4.2+17.10/overlord/snapstate/refreshhints.go snapd-2.31.1+17.10/overlord/snapstate/refreshhints.go --- snapd-2.29.4.2+17.10/overlord/snapstate/refreshhints.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/snapstate/refreshhints.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,103 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package snapstate + +import ( + "time" + + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/store" +) + +var refreshHintsDelay = time.Duration(24 * time.Hour) + +// refreshHints will ensure that we get regular data about refreshes +// so that we can potentially warn the user about important missing +// refreshes. +type refreshHints struct { + state *state.State +} + +func newRefreshHints(st *state.State) *refreshHints { + return &refreshHints{state: st} +} + +func (r *refreshHints) lastRefresh(timestampKey string) (time.Time, error) { + var lastRefresh time.Time + if err := r.state.Get(timestampKey, &lastRefresh); err != nil && err != state.ErrNoState { + return time.Time{}, err + } + return lastRefresh, nil +} + +func (r *refreshHints) needsUpdate() (bool, error) { + tFull, err := r.lastRefresh("last-refresh") + if err != nil { + return false, err + } + tHints, err := r.lastRefresh("last-refresh-hints") + if err != nil { + return false, err + } + + recentEnough := time.Now().Add(-refreshHintsDelay) + if tFull.After(recentEnough) || tFull.Equal(recentEnough) { + return false, nil + } + return tHints.Before(recentEnough), nil +} + +func (r *refreshHints) refresh() error { + var refreshManaged bool + refreshManaged = refreshScheduleManaged(r.state) + + _, _, _, err := refreshCandidates(r.state, nil, nil, &store.RefreshOptions{RefreshManaged: refreshManaged}) + // TODO: we currently set last-refresh-hints even when there was an + // error. In the future we may retry with a backoff. + r.state.Set("last-refresh-hints", time.Now()) + return err +} + +// Ensure will ensure that refresh hints are available on a regular +// interval. +func (r *refreshHints) Ensure() error { + r.state.Lock() + defer r.state.Unlock() + + // CanAutoRefresh is a hook that is set by the devicestate + // code to ensure that we only AutoRefersh if the device has + // bootstraped itself enough. This is only nil when snapstate + // is used in isolation (like in tests). + if CanAutoRefresh == nil { + return nil + } + if ok, err := CanAutoRefresh(r.state); err != nil || !ok { + return err + } + + needsUpdate, err := r.needsUpdate() + if err != nil { + return err + } + if !needsUpdate { + return nil + } + return r.refresh() +} diff -Nru snapd-2.29.4.2+17.10/overlord/snapstate/refreshhints_test.go snapd-2.31.1+17.10/overlord/snapstate/refreshhints_test.go --- snapd-2.29.4.2+17.10/overlord/snapstate/refreshhints_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/snapstate/refreshhints_test.go 2018-01-24 20:02:44.000000000 +0000 @@ -0,0 +1,114 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2017 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 . + * + */ + +package snapstate_test + +import ( + "time" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/overlord/auth" + "github.com/snapcore/snapd/overlord/snapstate" + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/store" + "github.com/snapcore/snapd/store/storetest" +) + +type recordingStore struct { + storetest.Store + + ops []string +} + +func (r *recordingStore) ListRefresh(cands []*store.RefreshCandidate, _ *auth.UserState, flags *store.RefreshOptions) ([]*snap.Info, error) { + r.ops = append(r.ops, "list-refresh") + return nil, nil +} + +type refreshHintsTestSuite struct { + state *state.State + + store *recordingStore +} + +var _ = Suite(&refreshHintsTestSuite{}) + +func (s *refreshHintsTestSuite) SetUpTest(c *C) { + s.state = state.New(nil) + + s.store = &recordingStore{} + s.state.Lock() + defer s.state.Unlock() + snapstate.ReplaceStore(s.state, s.store) + + snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ + Active: true, + Sequence: []*snap.SideInfo{ + {RealName: "some-snap", Revision: snap.R(5), SnapID: "some-snap-id"}, + }, + Current: snap.R(5), + SnapType: "app", + UserID: 1, + }) + + snapstate.CanAutoRefresh = func(*state.State) (bool, error) { return true, nil } + snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) { + return nil, nil + } +} + +func (s *refreshHintsTestSuite) TearDownTest(c *C) { + snapstate.CanAutoRefresh = nil + snapstate.AutoAliases = nil +} + +func (s *refreshHintsTestSuite) TestLastRefresh(c *C) { + rh := snapstate.NewRefreshHints(s.state) + err := rh.Ensure() + c.Check(err, IsNil) + c.Check(s.store.ops, DeepEquals, []string{"list-refresh"}) +} + +func (s *refreshHintsTestSuite) TestLastRefreshNoRefreshNeeded(c *C) { + s.state.Lock() + s.state.Set("last-refresh-hints", time.Now().Add(-23*time.Hour)) + s.state.Unlock() + + rh := snapstate.NewRefreshHints(s.state) + err := rh.Ensure() + c.Check(err, IsNil) + c.Check(s.store.ops, HasLen, 0) +} + +func (s *refreshHintsTestSuite) TestLastRefreshNoRefreshNeededBecauseOfFullAutoRefresh(c *C) { + s.state.Lock() + s.state.Set("last-refresh-hints", time.Now().Add(-48*time.Hour)) + s.state.Unlock() + + s.state.Lock() + s.state.Set("last-refresh", time.Now().Add(-23*time.Hour)) + s.state.Unlock() + + rh := snapstate.NewRefreshHints(s.state) + err := rh.Ensure() + c.Check(err, IsNil) + c.Check(s.store.ops, HasLen, 0) +} diff -Nru snapd-2.29.4.2+17.10/overlord/snapstate/snapmgr.go snapd-2.31.1+17.10/overlord/snapstate/snapmgr.go --- snapd-2.29.4.2+17.10/overlord/snapstate/snapmgr.go 2017-11-30 15:02:11.000000000 +0000 +++ snapd-2.31.1+17.10/overlord/snapstate/snapmgr.go 2018-01-31 08:47:06.000000000 +0000 @@ -20,7 +20,6 @@ package snapstate import ( - "encoding/json" "errors" "fmt" "os" @@ -31,56 +30,24 @@ "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/errtracker" "github.com/snapcore/snapd/i18n" - "github.com/snapcore/snapd/logger" - "github.com/snapcore/snapd/overlord/configstate/config" "github.com/snapcore/snapd/overlord/snapstate/backend" "github.com/snapcore/snapd/overlord/state" - "github.com/snapcore/snapd/overlord/storestate" "github.com/snapcore/snapd/release" "github.com/snapcore/snapd/snap" - "github.com/snapcore/snapd/strutil" - "github.com/snapcore/snapd/timeutil" + "github.com/snapcore/snapd/store" ) -// FIXME: what we actually want is a more flexible schedule spec that is -// user configurable like: -// """ -// tue -// tue,thu -// tue-thu -// 9:00 -// 9:00,15:00 -// 9:00-15:00 -// tue,thu@9:00-15:00 -// tue@9:00;thu@15:00 -// mon,wed-fri@9:00-11:00,13:00-15:00 -// """ -// where 9:00 is implicitly taken as 9:00-10:00 -// and tue is implicitly taken as tue@ -// -// it is controlled via: -// $ snap refresh --schedule=