diff -Nru snapd-2.27.5/apparmor/probe.go snapd-2.28.5/apparmor/probe.go --- snapd-2.27.5/apparmor/probe.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/apparmor/probe.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,151 @@ +// -*- 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 apparmor + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strings" +) + +// FeatureLevel encodes the kind of support for apparmor found on this system. +type FeatureLevel int + +const ( + // None indicates that apparmor is not enabled. + None FeatureLevel = iota + // Partial indicates that apparmor is enabled but some features are missing. + Partial + // Full indicates that all features are supported. + Full +) + +var ( + // featureSysPath points to the sysfs directory where apparmor features are listed. + featuresSysPath = "/sys/kernel/security/apparmor/features" + // requiredFeatures are the apparmor features needed for strict confinement. + requiredFeatures = []string{ + "caps", + "dbus", + "domain", + "file", + "mount", + "namespaces", + "network", + "ptrace", + "rlimit", + "signal", + } +) + +// KernelSupport describes apparmor features supported by the kernel. +type KernelSupport struct { + enabled bool + features map[string]bool +} + +// ProbeKernel checks which apparmor features are available. +func ProbeKernel() *KernelSupport { + entries, err := ioutil.ReadDir(featuresSysPath) + if err != nil { + return nil + } + ks := &KernelSupport{ + enabled: err == nil, + features: make(map[string]bool, len(entries)), + } + for _, entry := range entries { + // Each sub-directory represents a speicfic feature. Some have more + // details as additional sub-directories or files therein but we are + // not inspecting that at the moment. + if entry.IsDir() { + ks.features[entry.Name()] = true + } + } + return ks +} + +// IsEnabled returns true if apparmor is enabled. +func (ks *KernelSupport) IsEnabled() bool { + return ks != nil && ks.enabled +} + +// SupportsFeature returns true if a given apparmor feature is supported. +func (ks *KernelSupport) SupportsFeature(feature string) bool { + return ks != nil && ks.features[feature] +} + +// Evaluate checks if the apparmor module is enabled and if all the required features are available. +func (ks *KernelSupport) Evaluate() (level FeatureLevel, summary string) { + if !ks.IsEnabled() { + return None, fmt.Sprintf("apparmor is not enabled") + } + var missing []string + for _, feature := range requiredFeatures { + if !ks.SupportsFeature(feature) { + missing = append(missing, feature) + } + } + if len(missing) > 0 { + sort.Strings(missing) + return Partial, fmt.Sprintf("apparmor is enabled but some features are missing: %s", strings.Join(missing, ", ")) + } + return Full, "apparmor is enabled and all features are available" +} + +// MockFeatureLevel fakes the desired apparmor feature level. +func MockFeatureLevel(level FeatureLevel) (restore func()) { + oldFeaturesSysPath := featuresSysPath + + temp, err := ioutil.TempDir("", "mock-apparmor-feature-level") + if err != nil { + panic(err) + } + featuresSysPath = filepath.Join(temp, "features") + + switch level { + case None: + // create no directory at all (apparmor not available). + case Partial: + // create several feature directories, matching vanilla 4.12 kernel. + for _, feature := range []string{"caps", "domain", "file", "network", "policy", "rlimit"} { + if err := os.MkdirAll(filepath.Join(featuresSysPath, feature), 0755); err != nil { + panic(err) + } + } + case Full: + // create all the feature directories, matching Ubuntu kernels. + for _, feature := range requiredFeatures { + if err := os.MkdirAll(filepath.Join(featuresSysPath, feature), 0755); err != nil { + panic(err) + } + } + } + + return func() { + if err := os.RemoveAll(temp); err != nil { + panic(err) + } + featuresSysPath = oldFeaturesSysPath + } +} diff -Nru snapd-2.27.5/apparmor/probe_test.go snapd-2.28.5/apparmor/probe_test.go --- snapd-2.27.5/apparmor/probe_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/apparmor/probe_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,77 @@ +// -*- 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 apparmor_test + +import ( + . "gopkg.in/check.v1" + "testing" + + "github.com/snapcore/snapd/apparmor" +) + +func Test(t *testing.T) { + TestingT(t) +} + +type probeSuite struct{} + +var _ = Suite(&probeSuite{}) + +func (s *probeSuite) TestMockProbeNone(c *C) { + restore := apparmor.MockFeatureLevel(apparmor.None) + defer restore() + + ks := apparmor.ProbeKernel() + c.Assert(ks.IsEnabled(), Equals, false) + c.Assert(ks.SupportsFeature("dbus"), Equals, false) + c.Assert(ks.SupportsFeature("file"), Equals, false) + + level, summary := ks.Evaluate() + c.Assert(level, Equals, apparmor.None) + c.Assert(summary, Equals, "apparmor is not enabled") +} + +func (s *probeSuite) TestMockProbePartial(c *C) { + restore := apparmor.MockFeatureLevel(apparmor.Partial) + defer restore() + + ks := apparmor.ProbeKernel() + c.Assert(ks.IsEnabled(), Equals, true) + c.Assert(ks.SupportsFeature("dbus"), Equals, false) + c.Assert(ks.SupportsFeature("file"), Equals, true) + + level, summary := ks.Evaluate() + c.Assert(level, Equals, apparmor.Partial) + c.Assert(summary, Equals, "apparmor is enabled but some features are missing: dbus, mount, namespaces, ptrace, signal") +} + +func (s *probeSuite) TestMockProbeFull(c *C) { + restore := apparmor.MockFeatureLevel(apparmor.Full) + defer restore() + + ks := apparmor.ProbeKernel() + c.Assert(ks.IsEnabled(), Equals, true) + c.Assert(ks.SupportsFeature("dbus"), Equals, true) + c.Assert(ks.SupportsFeature("file"), Equals, true) + + level, summary := ks.Evaluate() + c.Assert(level, Equals, apparmor.Full) + c.Assert(summary, Equals, "apparmor is enabled and all features are available") +} diff -Nru snapd-2.27.5/arch/arch.go snapd-2.28.5/arch/arch.go --- snapd-2.27.5/arch/arch.go 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/arch/arch.go 2017-09-13 14:47:18.000000000 +0000 @@ -23,6 +23,8 @@ "log" "runtime" "syscall" + + "github.com/snapcore/snapd/release" ) // ArchitectureType is the type for a supported snappy architecture @@ -70,6 +72,18 @@ "ppc64": "ppc64", } + // If we are running on an ARM platform we need to have a + // closer look if we are on armhf or armel. If we're not + // on a armv6 platform we can continue to use the Go + // arch mapping. The Go arch sadly doesn't map this out + // for us so we have to fallback to uname here. + if goarch == "arm" { + machineName := release.Machine() + if machineName == "armv6l" { + return "armel" + } + } + ubuntuArch := goArchMapping[goarch] if ubuntuArch == "" { log.Panicf("unknown goarch %q", goarch) diff -Nru snapd-2.27.5/asserts/account_test.go snapd-2.28.5/asserts/account_test.go --- snapd-2.27.5/asserts/account_test.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/asserts/account_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -158,6 +158,8 @@ otherDB := setup3rdPartySigning(c, "other", storeDB, db) headers := ex.Headers() + // default to signing db's authority + delete(headers, "authority-id") headers["timestamp"] = time.Now().Format(time.RFC3339) account, err := otherDB.Sign(asserts.AccountType, headers, nil, "") c.Assert(err, IsNil) diff -Nru snapd-2.27.5/asserts/asserts.go snapd-2.28.5/asserts/asserts.go --- snapd-2.27.5/asserts/asserts.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/asserts/asserts.go 2017-09-13 14:47:18.000000000 +0000 @@ -68,6 +68,7 @@ SnapDeveloperType = &AssertionType{"snap-developer", []string{"snap-id", "publisher-id"}, assembleSnapDeveloper, 0} SystemUserType = &AssertionType{"system-user", []string{"brand-id", "email"}, assembleSystemUser, 0} ValidationType = &AssertionType{"validation", []string{"series", "snap-id", "approved-snap-id", "approved-snap-revision"}, assembleValidation, 0} + StoreType = &AssertionType{"store", []string{"store"}, assembleStore, 0} // ... ) @@ -92,6 +93,7 @@ SystemUserType.Name: SystemUserType, ValidationType.Name: ValidationType, RepairType.Name: RepairType, + StoreType.Name: StoreType, // no authority DeviceSessionRequestType.Name: DeviceSessionRequestType, SerialRequestType.Name: SerialRequestType, @@ -103,6 +105,18 @@ return typeRegistry[name] } +// TypeNames returns a sorted list of known assertion type names. +func TypeNames() []string { + names := make([]string, 0, len(typeRegistry)) + for k := range typeRegistry { + names = append(names, k) + } + + sort.Strings(names) + + return names +} + var maxSupportedFormat = map[string]int{} func init() { diff -Nru snapd-2.27.5/asserts/assertstest/assertstest.go snapd-2.28.5/asserts/assertstest/assertstest.go --- snapd-2.27.5/asserts/assertstest/assertstest.go 2016-10-27 13:22:15.000000000 +0000 +++ snapd-2.28.5/asserts/assertstest/assertstest.go 2017-09-13 14:47:18.000000000 +0000 @@ -237,7 +237,15 @@ } func (db *SigningDB) Sign(assertType *asserts.AssertionType, headers map[string]interface{}, body []byte, keyID string) (asserts.Assertion, error) { - headers["authority-id"] = db.AuthorityID + if _, ok := headers["authority-id"]; !ok { + // copy before modifying + headers2 := make(map[string]interface{}, len(headers)+1) + for h, v := range headers { + headers2[h] = v + } + headers = headers2 + headers["authority-id"] = db.AuthorityID + } if keyID == "" { keyID = db.KeyID } @@ -258,6 +266,13 @@ TrustedKey *asserts.AccountKey Trusted []asserts.Assertion + // Generic authority assertions. + GenericAccount *asserts.Account + GenericKey *asserts.AccountKey + GenericModelsKey *asserts.AccountKey + Generic []asserts.Assertion + GenericClassicModel *asserts.Model + // Signing assertion db that signs with the root private key. RootSigning *SigningDB @@ -265,9 +280,36 @@ *SigningDB } +// StoreKeys holds a set of store private keys. +type StoreKeys struct { + Root asserts.PrivateKey + Store asserts.PrivateKey + Generic asserts.PrivateKey + GenericModels asserts.PrivateKey +} + +var ( + rootPrivKey, _ = GenerateKey(1024) + storePrivKey, _ = GenerateKey(752) + genericPrivKey, _ = GenerateKey(752) + genericModelsPrivKey, _ = GenerateKey(752) + + pregenKeys = StoreKeys{ + Root: rootPrivKey, + Store: storePrivKey, + Generic: genericPrivKey, + GenericModels: genericModelsPrivKey, + } +) + // NewStoreStack creates a new store assertion stack. It panics on error. -func NewStoreStack(authorityID string, rootPrivKey, storePrivKey asserts.PrivateKey) *StoreStack { - rootSigning := NewSigningDB(authorityID, rootPrivKey) +// Optional keys specify private keys to use for the various roles. +func NewStoreStack(authorityID string, keys *StoreKeys) *StoreStack { + if keys == nil { + keys = &pregenKeys + } + + rootSigning := NewSigningDB(authorityID, keys.Root) ts := time.Now().Format(time.RFC3339) trustedAcct := NewAccount(rootSigning, authorityID, map[string]interface{}{ "account-id": authorityID, @@ -277,33 +319,82 @@ trustedKey := NewAccountKey(rootSigning, trustedAcct, map[string]interface{}{ "name": "root", "since": ts, - }, rootPrivKey.PublicKey(), "") + }, keys.Root.PublicKey(), "") trusted := []asserts.Assertion{trustedAcct, trustedKey} + genericAcct := NewAccount(rootSigning, "generic", map[string]interface{}{ + "account-id": "generic", + "validation": "certified", + "timestamp": ts, + }, "") + + err := rootSigning.ImportKey(keys.GenericModels) + if err != nil { + panic(err) + } + genericModelsKey := NewAccountKey(rootSigning, genericAcct, map[string]interface{}{ + "name": "models", + "since": ts, + }, genericModelsPrivKey.PublicKey(), "") + generic := []asserts.Assertion{genericAcct, genericModelsKey} + db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{ - Backstore: asserts.NewMemoryBackstore(), - Trusted: trusted, + Backstore: asserts.NewMemoryBackstore(), + Trusted: trusted, + OtherPredefined: generic, }) if err != nil { panic(err) } - err = db.ImportKey(storePrivKey) + err = db.ImportKey(keys.Store) if err != nil { panic(err) } storeKey := NewAccountKey(rootSigning, trustedAcct, map[string]interface{}{ "name": "store", - }, storePrivKey.PublicKey(), "") + }, keys.Store.PublicKey(), "") err = db.Add(storeKey) if err != nil { panic(err) } + err = db.ImportKey(keys.Generic) + if err != nil { + panic(err) + } + genericKey := NewAccountKey(rootSigning, genericAcct, map[string]interface{}{ + "name": "serials", + "since": ts, + }, keys.Generic.PublicKey(), "") + err = db.Add(genericKey) + if err != nil { + panic(err) + } + + a, err := rootSigning.Sign(asserts.ModelType, map[string]interface{}{ + "authority-id": "generic", + "series": "16", + "brand-id": "generic", + "model": "generic-classic", + "classic": "true", + "timestamp": ts, + }, nil, genericModelsKey.PublicKeyID()) + if err != nil { + panic(err) + } + genericClassicMod := a.(*asserts.Model) + return &StoreStack{ TrustedAccount: trustedAcct, TrustedKey: trustedKey, Trusted: trusted, + GenericAccount: genericAcct, + GenericKey: genericKey, + GenericModelsKey: genericModelsKey, + Generic: generic, + GenericClassicModel: genericClassicMod, + RootSigning: rootSigning, SigningDB: &SigningDB{ diff -Nru snapd-2.27.5/asserts/assertstest/assertstest_test.go snapd-2.28.5/asserts/assertstest/assertstest_test.go --- snapd-2.27.5/asserts/assertstest/assertstest_test.go 2016-09-15 18:55:10.000000000 +0000 +++ snapd-2.28.5/asserts/assertstest/assertstest_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -75,10 +75,7 @@ } func (s *helperSuite) TestStoreStack(c *C) { - rootPrivKey, _ := assertstest.GenerateKey(1024) - storePrivKey, _ := assertstest.GenerateKey(752) - - store := assertstest.NewStoreStack("super", rootPrivKey, storePrivKey) + store := assertstest.NewStoreStack("super", nil) c.Check(store.TrustedAccount.AccountID(), Equals, "super") c.Check(store.TrustedAccount.IsCertified(), Equals, true) @@ -86,9 +83,13 @@ c.Check(store.TrustedKey.AccountID(), Equals, "super") c.Check(store.TrustedKey.Name(), Equals, "root") + c.Check(store.GenericAccount.AccountID(), Equals, "generic") + c.Check(store.GenericAccount.IsCertified(), Equals, true) + db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{ - Backstore: asserts.NewMemoryBackstore(), - Trusted: store.Trusted, + Backstore: asserts.NewMemoryBackstore(), + Trusted: store.Trusted, + OtherPredefined: store.Generic, }) c.Assert(err, IsNil) @@ -100,6 +101,30 @@ c.Check(storeAccKey.PublicKeyID(), Equals, store.KeyID) c.Check(storeAccKey.Name(), Equals, "store") + c.Check(store.GenericKey.AccountID(), Equals, "generic") + c.Check(store.GenericKey.Name(), Equals, "serials") + + c.Check(store.GenericModelsKey.AccountID(), Equals, "generic") + c.Check(store.GenericModelsKey.Name(), Equals, "models") + + g, err := store.Find(asserts.AccountType, map[string]string{ + "account-id": "generic", + }) + c.Assert(err, IsNil) + c.Assert(g.Headers(), DeepEquals, store.GenericAccount.Headers()) + + g, err = store.Find(asserts.AccountKeyType, map[string]string{ + "public-key-sha3-384": store.GenericKey.PublicKeyID(), + }) + c.Assert(err, IsNil) + c.Assert(g.Headers(), DeepEquals, store.GenericKey.Headers()) + + g, err = store.Find(asserts.AccountKeyType, map[string]string{ + "public-key-sha3-384": store.GenericModelsKey.PublicKeyID(), + }) + c.Assert(err, IsNil) + c.Assert(g.Headers(), DeepEquals, store.GenericModelsKey.Headers()) + acct := assertstest.NewAccount(store, "devel1", nil, "") c.Check(acct.Username(), Equals, "devel1") c.Check(acct.AccountID(), HasLen, 32) @@ -119,4 +144,20 @@ c.Assert(err, IsNil) c.Check(acctKey.Name(), Equals, "default") + + a, err := db.Find(asserts.AccountType, map[string]string{ + "account-id": "generic", + }) + c.Assert(err, IsNil) + c.Assert(a.Headers(), DeepEquals, store.GenericAccount.Headers()) + + c.Check(store.GenericClassicModel.AuthorityID(), Equals, "generic") + c.Check(store.GenericClassicModel.BrandID(), Equals, "generic") + c.Check(store.GenericClassicModel.Model(), Equals, "generic-classic") + c.Check(store.GenericClassicModel.Classic(), Equals, true) + err = db.Check(store.GenericClassicModel) + c.Assert(err, IsNil) + + err = db.Add(store.GenericKey) + c.Assert(err, IsNil) } diff -Nru snapd-2.27.5/asserts/asserts_test.go snapd-2.28.5/asserts/asserts_test.go --- snapd-2.27.5/asserts/asserts_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/asserts/asserts_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -46,6 +46,31 @@ c.Check(asserts.Type("test-only").MaxSupportedFormat(), Equals, 1) } +func (as *assertsSuite) TestTypeNames(c *C) { + c.Check(asserts.TypeNames(), DeepEquals, []string{ + "account", + "account-key", + "account-key-request", + "base-declaration", + "device-session-request", + "model", + "repair", + "serial", + "serial-request", + "snap-build", + "snap-declaration", + "snap-developer", + "snap-revision", + "store", + "system-user", + "test-only", + "test-only-2", + "test-only-no-authority", + "test-only-no-authority-pk", + "validation", + }) +} + func (as *assertsSuite) TestSuggestFormat(c *C) { fmtnum, err := asserts.SuggestFormat(asserts.Type("test-only-2"), nil, nil) c.Assert(err, IsNil) @@ -820,6 +845,7 @@ "account", "account-key", "base-declaration", + "store", "snap-declaration", "snap-build", "snap-revision", diff -Nru snapd-2.27.5/asserts/database.go snapd-2.28.5/asserts/database.go --- snapd-2.27.5/asserts/database.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/asserts/database.go 2017-09-13 14:47:18.000000000 +0000 @@ -72,8 +72,11 @@ // DatabaseConfig for an assertion database. type DatabaseConfig struct { - // trusted set of assertions (account and account-key supported) + // trusted set of assertions (account and account-key supported), + // used to establish root keys and trusted authorities Trusted []Assertion + // predefined assertions but that do not establish foundational trust + OtherPredefined []Assertion // backstore for assertions, left unset storing assertions will error Backstore Backstore // manager/backstore for keypairs, defaults to in-memory implementation @@ -143,17 +146,24 @@ // Provided headers must contain the primary key for the assertion type. // It returns ErrNotFound if the assertion cannot be found. Find(assertionType *AssertionType, headers map[string]string) (Assertion, error) - // FindTrusted finds an assertion in the trusted set based on arbitrary headers. - // Provided headers must contain the primary key for the assertion type. - // It returns ErrNotFound if the assertion cannot be found. + // FindPredefined finds an assertion in the predefined sets + // (trusted or not) based on arbitrary headers. Provided + // headers must contain the primary key for the assertion + // type. It returns ErrNotFound if the assertion cannot be + // found. + FindPredefined(assertionType *AssertionType, headers map[string]string) (Assertion, error) + // FindTrusted finds an assertion in the trusted set based on + // arbitrary headers. Provided headers must contain the + // primary key for the assertion type. It returns ErrNotFound + // if the assertion cannot be found. FindTrusted(assertionType *AssertionType, headers map[string]string) (Assertion, error) // FindMany finds assertions based on arbitrary headers. // It returns ErrNotFound if no assertion can be found. FindMany(assertionType *AssertionType, headers map[string]string) ([]Assertion, error) - // FindManyTrusted finds assertions in the trusted set based - // on arbitrary headers. It returns ErrNotFound if no - // assertion can be found. - FindManyTrusted(assertionType *AssertionType, headers map[string]string) ([]Assertion, error) + // FindManyPredefined finds assertions in the predefined sets + // (trusted or not) based on arbitrary headers. It returns + // ErrNotFound if no assertion can be found. + FindManyPredefined(assertionType *AssertionType, headers map[string]string) ([]Assertion, error) // Check tests whether the assertion is properly signed and consistent with all the stored knowledge. Check(assert Assertion) error } @@ -169,6 +179,7 @@ bs Backstore keypairMgr KeypairManager trusted Backstore + predefined Backstore backstores []Backstore checkers []Checker } @@ -193,17 +204,26 @@ accKey := accepted err := trustedBackstore.Put(AccountKeyType, accKey) if err != nil { - return nil, fmt.Errorf("error loading for use trusted account key %q for %q: %v", accKey.PublicKeyID(), accKey.AccountID(), err) + return nil, fmt.Errorf("cannot predefine trusted account key %q for %q: %v", accKey.PublicKeyID(), accKey.AccountID(), err) } case *Account: acct := accepted err := trustedBackstore.Put(AccountType, acct) if err != nil { - return nil, fmt.Errorf("error loading for use trusted account %q: %v", acct.DisplayName(), err) + return nil, fmt.Errorf("cannot predefine trusted account %q: %v", acct.DisplayName(), err) } default: - return nil, fmt.Errorf("cannot load trusted assertions that are not account-key or account: %s", a.Type().Name) + return nil, fmt.Errorf("cannot predefine trusted assertions that are not account-key or account: %s", a.Type().Name) + } + } + + otherPredefinedBackstore := NewMemoryBackstore() + + for _, a := range cfg.OtherPredefined { + err := otherPredefinedBackstore.Put(a.Type(), a) + if err != nil { + return nil, fmt.Errorf("cannot predefine assertion %v: %v", a.Ref(), err) } } @@ -218,10 +238,11 @@ bs: bs, keypairMgr: keypairMgr, trusted: trustedBackstore, + predefined: otherPredefinedBackstore, // order here is relevant, Find* precedence and // findAccountKey depend on it, trusted should win over the // general backstore! - backstores: []Backstore{trustedBackstore, bs}, + backstores: []Backstore{trustedBackstore, otherPredefinedBackstore, bs}, checkers: dbCheckers, }, nil } @@ -365,6 +386,11 @@ return fmt.Errorf("cannot add %q assertion with primary key clashing with a trusted assertion: %v", ref.Type.Name, ref.PrimaryKey) } + _, err = db.predefined.Get(ref.Type, ref.PrimaryKey, ref.Type.MaxSupportedFormat()) + if err != ErrNotFound { + return fmt.Errorf("cannot add %q assertion with primary key clashing with a predefined assertion: %v", ref.Type.Name, ref.PrimaryKey) + } + return db.bs.Put(ref.Type, assert) } @@ -433,6 +459,14 @@ return find(db.backstores, assertionType, headers, maxFormat) } +// FindPredefined finds an assertion in the predefined sets (trusted +// or not) based on arbitrary headers. Provided headers must contain +// the primary key for the assertion type. It returns ErrNotFound if +// the assertion cannot be found. +func (db *Database) FindPredefined(assertionType *AssertionType, headers map[string]string) (Assertion, error) { + return find([]Backstore{db.trusted, db.predefined}, assertionType, headers, -1) +} + // FindTrusted finds an assertion in the trusted set based on arbitrary headers. // Provided headers must contain the primary key for the assertion type. // It returns ErrNotFound if the assertion cannot be found. @@ -472,11 +506,11 @@ return db.findMany(db.backstores, assertionType, headers) } -// FindManyTrusted finds assertions in the trusted set based on -// arbitrary headers. It returns ErrNotFound if no assertion can be -// found. -func (db *Database) FindManyTrusted(assertionType *AssertionType, headers map[string]string) ([]Assertion, error) { - return db.findMany([]Backstore{db.trusted}, assertionType, headers) +// FindManyPrefined finds assertions in the predefined sets (trusted +// or not) based on arbitrary headers. It returns ErrNotFound if no +// assertion can be found. +func (db *Database) FindManyPredefined(assertionType *AssertionType, headers map[string]string) ([]Assertion, error) { + return db.findMany([]Backstore{db.trusted, db.predefined}, assertionType, headers) } // assertion checkers diff -Nru snapd-2.27.5/asserts/database_test.go snapd-2.28.5/asserts/database_test.go --- snapd-2.27.5/asserts/database_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/asserts/database_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -101,7 +101,7 @@ } _, err = asserts.OpenDatabase(cfg) - c.Assert(err, ErrorMatches, "cannot load trusted assertions that are not account-key or account: test-only") + c.Assert(err, ErrorMatches, "cannot predefine trusted assertions that are not account-key or account: test-only") } type databaseSuite struct { @@ -335,6 +335,17 @@ bs, err := asserts.OpenFSBackstore(topDir) c.Assert(err, IsNil) + headers := map[string]interface{}{ + "type": "account", + "authority-id": "canonical", + "account-id": "predefined", + "validation": "certified", + "display-name": "Predef", + "timestamp": time.Now().Format(time.RFC3339), + } + predefAcct, err := safs.signingDB.Sign(asserts.AccountType, headers, nil, safs.signingKeyID) + c.Assert(err, IsNil) + trustedKey := testPrivKey0 cfg := &asserts.DatabaseConfig{ Backstore: bs, @@ -342,6 +353,9 @@ asserts.BootstrapAccountForTest("canonical"), asserts.BootstrapAccountKeyForTest("canonical", trustedKey.PublicKey()), }, + OtherPredefined: []asserts.Assertion{ + predefAcct, + }, } db, err := asserts.OpenDatabase(cfg) c.Assert(err, IsNil) @@ -720,7 +734,7 @@ c.Check(err, Equals, asserts.ErrNotFound) } -func (safs *signAddFindSuite) TestFindFindsTrustedAccountKeys(c *C) { +func (safs *signAddFindSuite) TestFindFindsPredefined(c *C) { pk1 := testPrivKey1 acct1 := assertstest.NewAccount(safs.signingDB, "acc-id1", map[string]interface{}{ @@ -745,10 +759,22 @@ c.Assert(tKey.(*asserts.AccountKey).AccountID(), Equals, "canonical") c.Assert(tKey.(*asserts.AccountKey).PublicKeyID(), Equals, safs.signingKeyID) + // find predefined account as well + predefAcct, err := safs.db.Find(asserts.AccountType, map[string]string{ + "account-id": "predefined", + }) + c.Assert(err, IsNil) + c.Assert(predefAcct.(*asserts.Account).AccountID(), Equals, "predefined") + c.Assert(predefAcct.(*asserts.Account).DisplayName(), Equals, "Predef") + // find trusted and indirectly trusted accKeys, err := safs.db.FindMany(asserts.AccountKeyType, nil) c.Assert(err, IsNil) c.Check(accKeys, HasLen, 2) + + accts, err := safs.db.FindMany(asserts.AccountType, nil) + c.Assert(err, IsNil) + c.Check(accts, HasLen, 3) } func (safs *signAddFindSuite) TestFindTrusted(c *C) { @@ -794,9 +820,78 @@ "public-key-sha3-384": acct1Key.PublicKeyID(), }) c.Check(err, Equals, asserts.ErrNotFound) + + _, err = safs.db.FindTrusted(asserts.AccountType, map[string]string{ + "account-id": "predefined", + }) + c.Check(err, Equals, asserts.ErrNotFound) +} + +func (safs *signAddFindSuite) TestFindPredefined(c *C) { + pk1 := testPrivKey1 + + acct1 := assertstest.NewAccount(safs.signingDB, "acc-id1", map[string]interface{}{ + "authority-id": "canonical", + }, safs.signingKeyID) + + acct1Key := assertstest.NewAccountKey(safs.signingDB, acct1, map[string]interface{}{ + "authority-id": "canonical", + }, pk1.PublicKey(), safs.signingKeyID) + + err := safs.db.Add(acct1) + c.Assert(err, IsNil) + err = safs.db.Add(acct1Key) + c.Assert(err, IsNil) + + // find the trusted account + tAcct, err := safs.db.FindPredefined(asserts.AccountType, map[string]string{ + "account-id": "canonical", + }) + c.Assert(err, IsNil) + c.Assert(tAcct.(*asserts.Account).AccountID(), Equals, "canonical") + + // find the trusted key + tKey, err := safs.db.FindPredefined(asserts.AccountKeyType, map[string]string{ + "account-id": "canonical", + "public-key-sha3-384": safs.signingKeyID, + }) + c.Assert(err, IsNil) + c.Assert(tKey.(*asserts.AccountKey).AccountID(), Equals, "canonical") + c.Assert(tKey.(*asserts.AccountKey).PublicKeyID(), Equals, safs.signingKeyID) + + // find predefined account as well + predefAcct, err := safs.db.FindPredefined(asserts.AccountType, map[string]string{ + "account-id": "predefined", + }) + c.Assert(err, IsNil) + c.Assert(predefAcct.(*asserts.Account).AccountID(), Equals, "predefined") + c.Assert(predefAcct.(*asserts.Account).DisplayName(), Equals, "Predef") + + // doesn't find not trusted or predefined assertions + _, err = safs.db.FindPredefined(asserts.AccountType, map[string]string{ + "account-id": acct1.AccountID(), + }) + c.Check(err, Equals, asserts.ErrNotFound) + + _, err = safs.db.FindPredefined(asserts.AccountKeyType, map[string]string{ + "account-id": acct1.AccountID(), + "public-key-sha3-384": acct1Key.PublicKeyID(), + }) + c.Check(err, Equals, asserts.ErrNotFound) } -func (safs *signAddFindSuite) TestFindManyTrusted(c *C) { +func (safs *signAddFindSuite) TestFindManyPredefined(c *C) { + headers := map[string]interface{}{ + "type": "account", + "authority-id": "canonical", + "account-id": "predefined", + "validation": "certified", + "display-name": "Predef", + "timestamp": time.Now().Format(time.RFC3339), + } + predefAcct, err := safs.signingDB.Sign(asserts.AccountType, headers, nil, safs.signingKeyID) + c.Assert(err, IsNil) + trustedKey0 := testPrivKey0 trustedKey1 := testPrivKey1 cfg := &asserts.DatabaseConfig{ @@ -806,6 +901,9 @@ asserts.BootstrapAccountKeyForTest("canonical", trustedKey0.PublicKey()), asserts.BootstrapAccountKeyForTest("canonical", trustedKey1.PublicKey()), }, + OtherPredefined: []asserts.Assertion{ + predefAcct, + }, } db, err := asserts.OpenDatabase(cfg) c.Assert(err, IsNil) @@ -826,15 +924,23 @@ c.Assert(err, IsNil) // find the trusted account - tAccts, err := db.FindManyTrusted(asserts.AccountType, map[string]string{ + tAccts, err := db.FindManyPredefined(asserts.AccountType, map[string]string{ "account-id": "canonical", }) c.Assert(err, IsNil) c.Assert(tAccts, HasLen, 1) c.Assert(tAccts[0].(*asserts.Account).AccountID(), Equals, "canonical") + // find the predefined account + pAccts, err := db.FindManyPredefined(asserts.AccountType, map[string]string{ + "account-id": "predefined", + }) + c.Assert(err, IsNil) + c.Assert(pAccts, HasLen, 1) + c.Assert(pAccts[0].(*asserts.Account).AccountID(), Equals, "predefined") + // find the multiple trusted keys - tKeys, err := db.FindManyTrusted(asserts.AccountKeyType, map[string]string{ + tKeys, err := db.FindManyPredefined(asserts.AccountKeyType, map[string]string{ "account-id": "canonical", }) c.Assert(err, IsNil) @@ -849,13 +955,13 @@ trustedKey1.PublicKey().ID(): "canonical", }) - // doesn't find not trusted assertions - _, err = db.FindManyTrusted(asserts.AccountType, map[string]string{ + // doesn't find not predefined assertions + _, err = db.FindManyPredefined(asserts.AccountType, map[string]string{ "account-id": acct1.AccountID(), }) c.Check(err, Equals, asserts.ErrNotFound) - _, err = db.FindManyTrusted(asserts.AccountKeyType, map[string]string{ + _, err = db.FindManyPredefined(asserts.AccountKeyType, map[string]string{ "account-id": acct1.AccountID(), "public-key-sha3-384": acct1Key.PublicKeyID(), }) @@ -885,6 +991,22 @@ c.Check(err, ErrorMatches, `cannot add "account-key" assertion with primary key clashing with a trusted assertion: .*`) } +func (safs *signAddFindSuite) TestDontLetAddConfusinglyAssertionClashingWithPredefinedOnes(c *C) { + headers := map[string]interface{}{ + "type": "account", + "authority-id": "canonical", + "account-id": "predefined", + "validation": "certified", + "display-name": "Predef", + "timestamp": time.Now().Format(time.RFC3339), + } + predefAcct, err := safs.signingDB.Sign(asserts.AccountType, headers, nil, safs.signingKeyID) + c.Assert(err, IsNil) + + err = safs.db.Add(predefAcct) + c.Check(err, ErrorMatches, `cannot add "account" assertion with primary key clashing with a predefined assertion: .*`) +} + func (safs *signAddFindSuite) TestFindAndRefResolve(c *C) { headers := map[string]interface{}{ "authority-id": "canonical", diff -Nru snapd-2.27.5/asserts/device_asserts_test.go snapd-2.28.5/asserts/device_asserts_test.go --- snapd-2.27.5/asserts/device_asserts_test.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/asserts/device_asserts_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -26,6 +26,7 @@ . "gopkg.in/check.v1" "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/asserts/assertstest" ) type modelSuite struct { @@ -209,7 +210,7 @@ c.Assert(err, IsNil) storeDB, db := makeStoreAndCheckDB(c) - brandDB := setup3rdPartySigning(c, "brand1", storeDB, db) + brandDB := setup3rdPartySigning(c, "brand-id1", storeDB, db) headers := ex.Headers() headers["brand-id"] = brandDB.AuthorityID @@ -226,7 +227,7 @@ c.Assert(err, IsNil) storeDB, db := makeStoreAndCheckDB(c) - brandDB := setup3rdPartySigning(c, "brand1", storeDB, db) + brandDB := setup3rdPartySigning(c, "brand-id1", storeDB, db) headers := ex.Headers() headers["brand-id"] = brandDB.AuthorityID @@ -350,6 +351,7 @@ {"authority-id: brand-id1\n", "authority-id: random\n", `authority-id and brand-id must match, serial assertions are expected to be signed by the brand: "random" != "brand-id1"`}, {"model: baz-3000\n", "", `"model" header is mandatory`}, {"model: baz-3000\n", "model: \n", `"model" header should not be empty`}, + {"model: baz-3000\n", "model: _what\n", `"model" header contains invalid characters: "_what"`}, {"serial: 2700\n", "", `"serial" header is mandatory`}, {"serial: 2700\n", "serial: \n", `"serial" header should not be empty`}, {ss.tsLine, "", `"timestamp" header is mandatory`}, @@ -379,6 +381,42 @@ c.Check(err, ErrorMatches, serialErrPrefix+"device key does not match provided key id") } +func (ss *serialSuite) TestSerialCheck(c *C) { + encoded := strings.Replace(serialExample, "TSLINE", ss.tsLine, 1) + encoded = strings.Replace(encoded, "DEVICEKEY", strings.Replace(ss.encodedDevKey, "\n", "\n ", -1), 1) + encoded = strings.Replace(encoded, "KEYID", ss.deviceKey.PublicKey().ID(), 1) + ex, err := asserts.Decode([]byte(encoded)) + c.Assert(err, IsNil) + + storeDB, db := makeStoreAndCheckDB(c) + brandDB := setup3rdPartySigning(c, "brand1", storeDB, db) + + tests := []struct { + signDB assertstest.SignerDB + brandID string + authID string + keyID string + }{ + {brandDB, brandDB.AuthorityID, "", brandDB.KeyID}, + } + + for _, test := range tests { + headers := ex.Headers() + headers["brand-id"] = test.brandID + if test.authID != "" { + headers["authority-id"] = test.authID + } else { + headers["authority-id"] = test.brandID + } + headers["timestamp"] = time.Now().Format(time.RFC3339) + serial, err := test.signDB.Sign(asserts.SerialType, headers, nil, test.keyID) + c.Assert(err, IsNil) + + err = db.Check(serial) + c.Check(err, IsNil) + } +} + func (ss *serialSuite) TestSerialRequestHappy(c *C) { sreq, err := asserts.SignWithoutAuthority(asserts.SerialRequestType, map[string]interface{}{ diff -Nru snapd-2.27.5/asserts/fetcher.go snapd-2.28.5/asserts/fetcher.go --- snapd-2.27.5/asserts/fetcher.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/asserts/fetcher.go 2017-09-13 14:47:18.000000000 +0000 @@ -60,9 +60,9 @@ } func (f *fetcher) chase(ref *Ref, a Assertion) error { - // check if ref points to a trusted assertion, in which case + // check if ref points to predefined assertion, in which case // there is nothing to do - _, err := ref.Resolve(f.db.FindTrusted) + _, err := ref.Resolve(f.db.FindPredefined) if err == nil { return nil } diff -Nru snapd-2.27.5/asserts/fetcher_test.go snapd-2.28.5/asserts/fetcher_test.go --- snapd-2.27.5/asserts/fetcher_test.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/asserts/fetcher_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -39,9 +39,7 @@ var _ = Suite(&fetcherSuite{}) func (s *fetcherSuite) SetUpTest(c *C) { - rootPrivKey, _ := assertstest.GenerateKey(1024) - storePrivKey, _ := assertstest.GenerateKey(752) - s.storeSigning = assertstest.NewStoreStack("can0nical", rootPrivKey, storePrivKey) + s.storeSigning = assertstest.NewStoreStack("can0nical", nil) } func fakeSnap(rev int) []byte { diff -Nru snapd-2.27.5/asserts/gpgkeypairmgr_test.go snapd-2.28.5/asserts/gpgkeypairmgr_test.go --- snapd-2.27.5/asserts/gpgkeypairmgr_test.go 2016-08-29 15:03:59.000000000 +0000 +++ snapd-2.28.5/asserts/gpgkeypairmgr_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -80,7 +80,7 @@ } func (gkms *gpgKeypairMgrSuite) TestUseInSigning(c *C) { - store := assertstest.NewStoreStack("trusted", testPrivKey0, testPrivKey1) + store := assertstest.NewStoreStack("trusted", nil) devKey, err := gkms.keypairMgr.Get(assertstest.DevKeyID) c.Assert(err, IsNil) diff -Nru snapd-2.27.5/asserts/snapasserts/snapasserts_test.go snapd-2.28.5/asserts/snapasserts/snapasserts_test.go --- snapd-2.27.5/asserts/snapasserts/snapasserts_test.go 2017-01-12 09:32:51.000000000 +0000 +++ snapd-2.28.5/asserts/snapasserts/snapasserts_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -49,9 +49,7 @@ var _ = Suite(&snapassertsSuite{}) func (s *snapassertsSuite) SetUpTest(c *C) { - rootPrivKey, _ := assertstest.GenerateKey(1024) - storePrivKey, _ := assertstest.GenerateKey(752) - s.storeSigning = assertstest.NewStoreStack("can0nical", rootPrivKey, storePrivKey) + s.storeSigning = assertstest.NewStoreStack("can0nical", nil) s.dev1Acct = assertstest.NewAccount(s.storeSigning, "developer1", nil, "") diff -Nru snapd-2.27.5/asserts/snap_asserts_test.go snapd-2.28.5/asserts/snap_asserts_test.go --- snapd-2.27.5/asserts/snap_asserts_test.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/asserts/snap_asserts_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -595,14 +595,12 @@ } } -func makeStoreAndCheckDB(c *C) (storeDB *assertstest.SigningDB, checkDB *asserts.Database) { - trustedPrivKey := testPrivKey0 - storePrivKey := testPrivKey1 - - store := assertstest.NewStoreStack("canonical", trustedPrivKey, storePrivKey) +func makeStoreAndCheckDB(c *C) (store *assertstest.StoreStack, checkDB *asserts.Database) { + store = assertstest.NewStoreStack("canonical", nil) cfg := &asserts.DatabaseConfig{ - Backstore: asserts.NewMemoryBackstore(), - Trusted: store.Trusted, + Backstore: asserts.NewMemoryBackstore(), + Trusted: store.Trusted, + OtherPredefined: store.Generic, } checkDB, err := asserts.OpenDatabase(cfg) c.Assert(err, IsNil) @@ -610,11 +608,14 @@ // add store key err = checkDB.Add(store.StoreAccountKey("")) c.Assert(err, IsNil) + // add generic key + err = checkDB.Add(store.GenericKey) + c.Assert(err, IsNil) - return store.SigningDB, checkDB + return store, checkDB } -func setup3rdPartySigning(c *C, username string, storeDB *assertstest.SigningDB, checkDB *asserts.Database) (signingDB *assertstest.SigningDB) { +func setup3rdPartySigning(c *C, username string, storeDB assertstest.SignerDB, checkDB *asserts.Database) (signingDB *assertstest.SigningDB) { privKey := testPrivKey2 acct := assertstest.NewAccount(storeDB, username, map[string]interface{}{ @@ -809,7 +810,9 @@ otherDB := setup3rdPartySigning(c, "other", storeDB, db) - headers := srs.makeHeaders(nil) + headers := srs.makeHeaders(map[string]interface{}{ + "authority-id": "other", + }) snapRev, err := otherDB.Sign(asserts.SnapRevisionType, headers, nil, "") c.Assert(err, IsNil) @@ -997,7 +1000,9 @@ prereqSnapDecl(c, storeDB, db) prereqSnapDecl2(c, storeDB, db) - headers := vs.makeHeaders(nil) + headers := vs.makeHeaders(map[string]interface{}{ + "authority-id": "canonical", // not the publisher + }) validation, err := storeDB.Sign(asserts.ValidationType, headers, nil, "") c.Assert(err, IsNil) @@ -1062,11 +1067,10 @@ func (vs *validationSuite) TestMissingGatedSnapDeclaration(c *C) { storeDB, db := makeStoreAndCheckDB(c) - - prereqDevAccount(c, storeDB, db) + devDB := setup3rdPartySigning(c, "dev-id1", storeDB, db) headers := vs.makeHeaders(nil) - a, err := storeDB.Sign(asserts.ValidationType, headers, nil, "") + a, err := devDB.Sign(asserts.ValidationType, headers, nil, "") c.Assert(err, IsNil) err = db.Check(a) @@ -1075,12 +1079,12 @@ func (vs *validationSuite) TestMissingGatingSnapDeclaration(c *C) { storeDB, db := makeStoreAndCheckDB(c) + devDB := setup3rdPartySigning(c, "dev-id1", storeDB, db) - prereqDevAccount(c, storeDB, db) prereqSnapDecl2(c, storeDB, db) headers := vs.makeHeaders(nil) - a, err := storeDB.Sign(asserts.ValidationType, headers, nil, "") + a, err := devDB.Sign(asserts.ValidationType, headers, nil, "") c.Assert(err, IsNil) err = db.Check(a) diff -Nru snapd-2.27.5/asserts/store_asserts.go snapd-2.28.5/asserts/store_asserts.go --- snapd-2.27.5/asserts/store_asserts.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/asserts/store_asserts.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,112 @@ +package asserts + +import ( + "fmt" + "net/url" +) + +// Store holds a store assertion, defining the configuration needed to connect +// a device to the store. +type Store struct { + assertionBase + url *url.URL +} + +// Store returns the identifying name of the operator's store. +func (store *Store) Store() string { + return store.HeaderString("store") +} + +// OperatorID returns the account id of the store's operator. +func (store *Store) OperatorID() string { + return store.HeaderString("operator-id") +} + +// URL returns the URL of the store's API. +func (store *Store) URL() *url.URL { + return store.url +} + +// Location returns a summary of the store's location/purpose. +func (store *Store) Location() string { + return store.HeaderString("location") +} + +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()) { + return fmt.Errorf("store assertion %q is not signed by a directly trusted authority: %s", + store.Store(), store.AuthorityID()) + } + + _, err := db.Find(AccountType, map[string]string{"account-id": store.OperatorID()}) + if err != nil { + if err == ErrNotFound { + return fmt.Errorf( + "store assertion %q does not have a matching account assertion for the operator %q", + store.Store(), store.OperatorID()) + } + return err + } + + return nil +} + +// Prerequisites returns references to this store's prerequisite assertions. +func (store *Store) Prerequisites() []*Ref { + return []*Ref{ + {AccountType, []string{store.OperatorID()}}, + } +} + +// checkStoreURL validates the "url" header and returns a full URL or nil. +func checkStoreURL(headers map[string]interface{}) (*url.URL, error) { + s, err := checkOptionalString(headers, "url") + if err != nil { + return nil, err + } + + if s == "" { + return nil, nil + } + + errWhat := `"url" header` + + u, err := url.Parse(s) + if err != nil { + return nil, fmt.Errorf("%s must be a valid URL: %s", errWhat, s) + } + if u.Scheme != "http" && u.Scheme != "https" { + return nil, fmt.Errorf(`%s scheme must be "https" or "http": %s`, errWhat, s) + } + if u.Host == "" { + return nil, fmt.Errorf(`%s must have a host: %s`, errWhat, s) + } + if u.RawQuery != "" { + return nil, fmt.Errorf(`%s must not have a query: %s`, errWhat, s) + } + if u.Fragment != "" { + return nil, fmt.Errorf(`%s must not have a fragment: %s`, errWhat, s) + } + + return u, nil +} + +func assembleStore(assert assertionBase) (Assertion, error) { + _, err := checkNotEmptyString(assert.headers, "operator-id") + if err != nil { + return nil, err + } + + url, err := checkStoreURL(assert.headers) + if err != nil { + return nil, err + } + + _, err = checkOptionalString(assert.headers, "location") + if err != nil { + return nil, err + } + + return &Store{assertionBase: assert, url: url}, nil +} diff -Nru snapd-2.27.5/asserts/store_asserts_test.go snapd-2.28.5/asserts/store_asserts_test.go --- snapd-2.27.5/asserts/store_asserts_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/asserts/store_asserts_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,188 @@ +package asserts_test + +import ( + "fmt" + "strings" + + "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/asserts/assertstest" + . "gopkg.in/check.v1" +) + +var _ = Suite(&storeSuite{}) + +type storeSuite struct { + validExample string +} + +func (s *storeSuite) SetUpSuite(c *C) { + 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" + + "sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij\n" + + "\n" + + "AXNpZw==" +} + +func (s *storeSuite) TestDecodeOK(c *C) { + a, err := asserts.Decode([]byte(s.validExample)) + c.Assert(err, IsNil) + c.Check(a.Type(), Equals, asserts.StoreType) + store := a.(*asserts.Store) + + c.Check(store.OperatorID(), Equals, "op-id1") + c.Check(store.Store(), Equals, "store1") + c.Check(store.URL().String(), Equals, "https://store.example.com") + c.Check(store.Location(), Equals, "upstairs") +} + +var storeErrPrefix = "assertion store: " + +func (s *storeSuite) TestDecodeInvalidHeaders(c *C) { + tests := []struct{ original, invalid, expectedErr string }{ + {"store: store1\n", "", `"store" header is mandatory`}, + {"store: store1\n", "store: \n", `"store" header should not be empty`}, + {"operator-id: op-id1\n", "", `"operator-id" header is mandatory`}, + {"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`}, + } + + for _, test := range tests { + invalid := strings.Replace(s.validExample, test.original, test.invalid, 1) + _, err := asserts.Decode([]byte(invalid)) + c.Check(err, ErrorMatches, storeErrPrefix+test.expectedErr) + } +} + +func (s *storeSuite) TestURLOptional(c *C) { + tests := []string{"", "url: \n"} + for _, test := range tests { + encoded := strings.Replace(s.validExample, "url: https://store.example.com\n", test, 1) + assert, err := asserts.Decode([]byte(encoded)) + c.Assert(err, IsNil) + store := assert.(*asserts.Store) + c.Check(store.URL(), IsNil) + } +} + +func (s *storeSuite) TestURL(c *C) { + tests := []struct { + url string + err string + }{ + // Valid URLs. + {"http://example.com/", ""}, + {"https://example.com/", ""}, + {"https://example.com/some/path/", ""}, + {"https://example.com:443/", ""}, + {"https://example.com:1234/", ""}, + {"https://user:pass@example.com/", ""}, + {"https://token@example.com/", ""}, + + // Invalid URLs. + {"://example.com", `"url" header must be a valid URL`}, + {"example.com", `"url" header scheme must be "https" or "http"`}, + {"//example.com", `"url" header scheme must be "https" or "http"`}, + {"ftp://example.com", `"url" header scheme must be "https" or "http"`}, + {"mailto:someone@example.com", `"url" header scheme must be "https" or "http"`}, + {"https://", `"url" header must have a host`}, + {"https:///", `"url" header must have a host`}, + {"https:///some/path", `"url" header must have a host`}, + {"https://example.com/?foo=bar", `"url" header must not have a query`}, + {"https://example.com/#fragment", `"url" header must not have a fragment`}, + } + + for _, test := range tests { + encoded := strings.Replace( + s.validExample, "url: https://store.example.com\n", + fmt.Sprintf("url: %s\n", test.url), 1) + assert, err := asserts.Decode([]byte(encoded)) + if test.err != "" { + c.Assert(err, NotNil) + c.Check(err.Error(), Equals, storeErrPrefix+test.err+": "+test.url) + } else { + c.Assert(err, IsNil) + c.Check(assert.(*asserts.Store).URL().String(), Equals, test.url) + } + } +} + +func (s *storeSuite) TestLocationOptional(c *C) { + encoded := strings.Replace(s.validExample, "location: upstairs\n", "", 1) + _, err := asserts.Decode([]byte(encoded)) + c.Check(err, IsNil) +} + +func (s *storeSuite) TestLocation(c *C) { + for _, test := range []string{"foo", "bar", ""} { + encoded := strings.Replace( + s.validExample, "location: upstairs\n", + fmt.Sprintf("location: %s\n", test), 1) + assert, err := asserts.Decode([]byte(encoded)) + c.Assert(err, IsNil) + store := assert.(*asserts.Store) + c.Check(store.Location(), Equals, test) + } +} + +func (s *storeSuite) TestCheckAuthority(c *C) { + storeDB, db := makeStoreAndCheckDB(c) + + // Add account for operator. + operator := assertstest.NewAccount(storeDB, "op-id1", nil, "") + err := db.Add(operator) + c.Assert(err, IsNil) + + storeHeaders := map[string]interface{}{ + "store": "store1", + "operator-id": operator.HeaderString("account-id"), + } + + // store signed by some other account fails. + otherDB := setup3rdPartySigning(c, "other", storeDB, db) + store, err := otherDB.Sign(asserts.StoreType, storeHeaders, nil, "") + c.Assert(err, IsNil) + err = db.Check(store) + c.Assert(err, ErrorMatches, `store assertion "store1" is not signed by a directly trusted authority: other`) + + // but succeeds when signed by a trusted authority. + store, err = storeDB.Sign(asserts.StoreType, storeHeaders, nil, "") + c.Assert(err, IsNil) + err = db.Check(store) + c.Assert(err, IsNil) +} + +func (s *storeSuite) TestCheckOperatorAccount(c *C) { + storeDB, db := makeStoreAndCheckDB(c) + + store, err := storeDB.Sign(asserts.StoreType, map[string]interface{}{ + "store": "store1", + "operator-id": "op-id1", + }, nil, "") + c.Assert(err, IsNil) + + // No account for operator op-id1 yet, so Check fails. + err = db.Check(store) + c.Assert(err, ErrorMatches, `store assertion "store1" does not have a matching account assertion for the operator "op-id1"`) + + // Add the op-id1 account. + operator := assertstest.NewAccount(storeDB, "op-id1", map[string]interface{}{"account-id": "op-id1"}, "") + err = db.Add(operator) + c.Assert(err, IsNil) + + // Now the operator exists so Check succeeds. + err = db.Check(store) + c.Assert(err, IsNil) +} + +func (s *storeSuite) TestPrerequisites(c *C) { + assert, err := asserts.Decode([]byte(s.validExample)) + c.Assert(err, IsNil) + c.Assert(assert.Prerequisites(), DeepEquals, []*asserts.Ref{ + {Type: asserts.AccountType, PrimaryKey: []string{"op-id1"}}, + }) +} diff -Nru snapd-2.27.5/asserts/sysdb/generic.go snapd-2.28.5/asserts/sysdb/generic.go --- snapd-2.27.5/asserts/sysdb/generic.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/asserts/sysdb/generic.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,196 @@ +// -*- 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 sysdb + +import ( + "fmt" + + "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/osutil" +) + +const ( + encodedGenericAccount = `type: account +authority-id: canonical +account-id: generic +display-name: Generic +timestamp: 2017-07-27T00:00:00.0Z +username: generic +validation: certified +sign-key-sha3-384: -CvQKAwRQ5h3Ffn10FILJoEZUXOv6km9FwA80-Rcj-f-6jadQ89VRswHNiEB9Lxk + +AcLDXAQAAQoABgUCWYuVIgAKCRDUpVvql9g3II66IACcoxSoX8+PQLa9TNuNBUs3bdTW6V5ZOdE8 +vnziIg+yqu3qYfWHcRf1qu7K9Igv5lH3uM5jh2AHlndaoX4Qg1Rm9rOZCkRr1dDUmdRDBXN2pdTA +oydd0Ivpeai4ATbSZs11h50/vN/mxBwM6TzdGHqRNt6lvygAPe7VtfchSW/J0NsSIHr9SUeuIHkJ +C79DV27B+9/m8pnpKJo/Fv8nKGs4sMduKVjrj9Po3UhpZEQWf3I3SeDI5IE4TgoDe+O7neGUtT6W +D9wnMWLphC+rHbJguxXG/fmnUYiM2U8o4WVrs/fjF0zDRH7rY3tbLPbFXf2OD4qfOvS//VLQWeCK +KAgKhwz0d5CqaHyKSplywSvwO/dxlrqOjt39k3EjYxVuNS5UQk/BzPoDZD5maisCFm9JZwqBlWHP +6XTj8rhHSkNAPXezs2ZpVSsdtNYmpLLzWIFsAviuoMjYYDyL6jZrD4RBNrNOvSNQGLezB+eyI5DW +9vr2ppCw8zr49epPvJ4uqj/AILgr52zworl7v/27X67BOSoRMmE4AOnvjSJ8cN6Yt83AuEI4aZbP +DlF2Znqp8o/srtmJ3ZMpsjIsAqVhCeTU6eWXbYfNUlIMSmC6CDwQQzsukU4M6NEwUQbWddiM3iNL +FdeFsBscXg4Qm/0Y3PULriDoct+VpBUhzwVXG+Lj6rjtcX7n1C/7u9i/+WIBJ7jU4FBjwOdgpSCQ +DSCb0PgTM2PfbScFpn3KVYs0kT/Jc40Lpw6CUG9iUIdz5qlJzhbRiuhU8yjEg9q/5lWizAuxcP+P +anNhmNXsme46IJh7WnlzPAVMsToz8bWY01LC3t33pPGlRJo109PMbNK7reMIb4KFiL4Hy7gVmTj9 +uydReVBUTZuMLRq1ShAJNScZ+HTpWruLoiC87Rf1++1KakahmtWYCdlJv/JSOyjSh8D9h0GEmqON +lKmzrNgQS8QhLh5uBcITN2Kt1UFGu2o9I8l0TgD5Uh9fG/R/A536fpcvIzOA/lhVttO6P9POwUVv +RIBZ3TpVOSzQ+ADpDexRUouPLPkgUwVBSctcgafMaj/w8DsULvlOYd3Sqq9a+zg6bZr9oPBKutUn +YkIUWmLW1OsdQ2eBS9dFqzJTAOELxNOUq37UGnIrMbk3Vn8hLK+S/+W9XL6WVxzmL1PT9FJZZ41p +KdaFV+mvrTfyoxuzXxkWbMwQkc56Ifn+IojbDwMI4FcTcl4dOeUrlnqwBJmTTwEhLVkYDvzYsVV9 +4joFUWhp10JMm3lO+3596m0kYWMhyvGfYnH7QcQ3GtMAz82yRHc1X+seeWyD/aIjlHYNYfaJ5Ogs +VC76lXi7swMtA9jV5FJIGmQufLo9f93NSYxqwpa8 +` + + encodedGenericModelsAccountKey = `type: account-key +authority-id: canonical +public-key-sha3-384: d-JcZF9nD9eBw7bwMnH61x-bklnQOhQud1Is6o_cn2wTj8EYDi9musrIT9z2MdAa +account-id: generic +name: models +since: 2017-07-27T00:00:00.0Z +body-length: 717 +sign-key-sha3-384: -CvQKAwRQ5h3Ffn10FILJoEZUXOv6km9FwA80-Rcj-f-6jadQ89VRswHNiEB9Lxk + +AcbBTQRWhcGAARAAoRakbLAMMoPeMz5MLCzAR6ALu/xxP9PuCdkknHH5lJrKE2adFj22DMwjWKj6 +0pZU1Ushv4r7eb1NmFfl7a6Pz5ert+O5Qt53feK30+yiZF+Pgsx46SVTGy8QvicxhDhChdJ7ugW2 +Vbz8dXDT9gv1E5hLl2BiuxxZHtMMTitO3bCtQcM/YwUeFljZZYd1FwxtgolnA5IUcHomIEQ5Xw6X +dCYGNkVjenb8aLBfi/ZZ84LHQjSbo3b87KP7syeEH2uuFJ2W8ZwGfUCll84gF+lYiLO6BQk8psIR +aRqnPfdjeuYg0ZLhdNV2Gu6GTNYMSrGLJ4vafAoIoMOifeIfK/DjN0XpfUIYwrM3UIvssEaLyE0L +i30PN5bpmmyfj5EDkJj9DqHzBly1kc20ciEtVCwOUijhQr4UjjfPiJFyed1/yndY1z/L85iATcsb +mwAw/wOyHKge/mlVztXV2H8DywcLV8Kbo5/ZZzcdKhDgL9URosQ5bMeYDPWwPsS02exHFl150dpR +p6MmeSCFPiQQjDrM3nWXLv/uemBE1IgX5q2eW6kJbSvRn519O3OrFEs2NBMEgvE3mIvewNlxFbDj +96Oj54Zh3rVtYu/g9yo2Bb2uf9gpOGS6TxrqN3aP5FigZzxkMCGFG8UOOFI7k2eQjMd8va5V8JTZ +ijWZgBjDB1YuQ1MAEQEAAQ== + +AcLDXAQAAQoABgUCWYuUigAKCRDUpVvql9g3IOobH/wLm7sfLu3A/QWrdrMB1xRe6JOKuOQoNEt0 +Vhg8q4MgOt1mxPzBUMGBJCcq9EiTYaUT4eDXSJL1OKFgh42oK5uY+GLsPWamxBY1Rg6QoESjJPcS +2niwTOjjTdpIrZ5M3pKRmxTxT+Wsq9j+1t4jvy/baI6+uO6KQh0UIMyOEhG+uJ8aJ2OcF3uV5gtF +fL1Y4Jr1Ir/4B2K7s8OhlrO1Yw3woB+YIkOjJ6oAOfQx5B/p1vK4uXOCIZarcfYX4XOhNgvPGaeL +O+NHk3GwTmEBngs49E8zq8ii8OoqIT6YzUd4taqHvZD4inTlw6MKGld7myCbZVZ3b0NXosplwYXa +jVL9ZBWTJukcIs4jEJ0XkTEuwvOpiGbtXdmDDlOSYkhZQdmQn3CIveGLRFa6pCi9a/jstyB+4sgk +MnwmJxEg8L3i1OvjgUM8uexCfg4cBVP9fCKuaC26uAXUiiHz7mIZhVSlLXHgUgMn5jekluPndgRZ +D2mGG0WscTMTb9uOpbLo6BWCwM7rGaZQgVSZsIj1cise05fjGpOozeqDhG25obcUXxhIUStztc9t +Z9MwSz9xdsUqV8XztEhkgfc7dh2fPWluiE9hLrdzyoU1xE6syujm8HE+bIJnDFYoE/Kw6WqIEm/3 +mWhnOmi9uZsMBErKZKO4sqcLfR/zIn2Lx0ivg/yZzHHnDY5hwdrhQtn+AHCb+QJ9AJVte9hI+kt+ +Fv8neohiMTCY8XxjrdB3QBPGesVsIMI5zAd14X4MqNKBYb4Ucg8YCIj7WLkQHbHO1GQwhPY8Tl9u +QqysZo/WnLVuvaruEBsBBGUJ7Ju5GtFKdWMdoH3YQmYHdxxxK37NPqBY70OrTSFJU5QT6PGFSvif +aMDg0X/aRj2uE3vgTI5hdqI4JYv1Mt1gYOPv4AMx/o/2q9dVENFYMTXcYBITMScUVV8NzmH8SNge +w7AWUPlQvWGZbTz62lYXHuUX1cdzz37B0LrEjh1ZC1V8emzfkLzEFYP/qUk1c4NjKsTjj5d463Gq +cn31Mr83tt5l7HWwP8bvTMIj98bOIJapsncGOzPYhs8cjZeOy0Q7EcvHjGRrj26CGWZacT3f0A0e +kb66ocAxV4nH1FDsfn8KdLKFgmSmW6SXkD2nqY94/pommJzUBF6s54DijZMXqHRwIRyPA8ymrCGt +t4shJh7dobC8Tg6RA84Bf9HkeqI97PQYFYMuNX0U59x2s0IQsOAYjH53NIf/jSPC4GDvLs7k+O76 +R2PJK1VN6/ckJZAb3Rum5Ak5sbLTpRAVHIAVU1NAjHc5lYUHhxXJmJsbw6Jawb9Xb3T96s+WdD3Y +062upMY95pr0ZPf1tVGgzpcVCEw7yAOw+SkMksx+ +` + + encodedGenericClassicModel = `type: model +authority-id: generic +series: 16 +brand-id: generic +model: generic-classic +classic: true +timestamp: 2017-07-27T00:00:00.0Z +sign-key-sha3-384: d-JcZF9nD9eBw7bwMnH61x-bklnQOhQud1Is6o_cn2wTj8EYDi9musrIT9z2MdAa + +AcLBXAQAAQoABgUCWYuXiAAKCRAdLQyY+/mCiST0D/0XGQauzV2bbTEy6DkrR1jlNbI6x8vfIdS8 +KvEWYvzOWNhNlVSfwNOkFjs3uMHgCO6/fCg03wGXTyV9D7ZgrMeUzWrYp6EmXk8/LQSaBnff86XO +4/vYyfyvEYavhF0kQ6QGg8Cqr0EaMyw0x9/zWEO/Ll9fH/8nv9qcQq8N4AbebNvNxtGsCmJuXpSe +2rxl3Dw8XarYBmqgcBQhXxRNpa6/AgaTNBpPOTqgNA8ZtmbZwYLuaFjpZP410aJSs+evSKepy/ce ++zTA7RB3384YQVeZDdTudX2fGtuCnBZBAJ+NYlk0t8VFXxyOhyMSXeylSpNSx4pCqmUZRyaf5SDS +g1XxJet4IP0stZH1SfPOwc9oE81/bJlKsb9QIQKQRewvtUCLfe9a6Vy/CYd2elvcWOmeANVrJK0m +nRaz6VBm09RJTuwUT6vNugXSOCeF7W3WN1RHJuex0zw+nP3eCehxFSr33YrVniaA7zGfjXvS8tKx +AINNQB4g2fpfet4na6lPPMYM41WHIHPCMTz/fJQ6dZBSEg6UUZ/GiQhGEfWPBteK7yd9pQ8qB3fj +ER4UvKnR7hcVI26e3NGNkXP5kp0SFCkV5NQs8rzXzokpB7p/V5Pnqp3Km6wu45cU6UiTZFhR2IMT +l+6AMtrS4gDGHktOhwfmOMWqmhvR/INF+TjaWbsB6g== +` +) + +var ( + genericAssertions []asserts.Assertion + genericStagingAssertions []asserts.Assertion + genericExtraAssertions []asserts.Assertion + + genericClassicModel *asserts.Model + genericStagingClassicModel *asserts.Model + genericClassicModelOverride *asserts.Model +) + +func init() { + genericAccount, err := asserts.Decode([]byte(encodedGenericAccount)) + if err != nil { + panic(fmt.Sprintf(`cannot decode "generic"'s account: %v`, err)) + } + genericModelsAccountKey, err := asserts.Decode([]byte(encodedGenericModelsAccountKey)) + if err != nil { + panic(fmt.Sprintf(`cannot decode "generic"'s "models" account-key: %v`, err)) + } + + genericAssertions = []asserts.Assertion{genericAccount, genericModelsAccountKey} + + a, err := asserts.Decode([]byte(encodedGenericClassicModel)) + if err != nil { + panic(fmt.Sprintf(`cannot decode "generic"'s "generic-classic" model: %v`, err)) + } + genericClassicModel = a.(*asserts.Model) +} + +// Generic returns a copy of the current set of predefined assertions for the 'generic' authority as used by Open. +func Generic() []asserts.Assertion { + generic := []asserts.Assertion(nil) + if !osutil.GetenvBool("SNAPPY_USE_STAGING_STORE") { + generic = append(generic, genericAssertions...) + } else { + generic = append(generic, genericStagingAssertions...) + } + generic = append(generic, genericExtraAssertions...) + return generic +} + +// InjectGeneric injects further predefined assertions into the set used Open. +// Returns a restore function to reinstate the previous set. Useful +// for tests or called globally without worrying about restoring. +func InjectGeneric(extra []asserts.Assertion) (restore func()) { + prev := genericExtraAssertions + genericExtraAssertions = make([]asserts.Assertion, len(prev)+len(extra)) + copy(genericExtraAssertions, prev) + copy(genericExtraAssertions[len(prev):], extra) + return func() { + genericExtraAssertions = prev + } +} + +// GenericClassicModel returns the model assertion for the "generic"'s "generic-classic" fallback model. +func GenericClassicModel() *asserts.Model { + if genericClassicModelOverride != nil { + return genericClassicModelOverride + } + if !osutil.GetenvBool("SNAPPY_USE_STAGING_STORE") { + return genericClassicModel + } else { + return genericStagingClassicModel + } +} + +// MockGenericClassicModel mocks the predefined generic-classic model returned by GenericClassicModel. +func MockGenericClassicModel(mod *asserts.Model) (restore func()) { + prevOverride := genericClassicModelOverride + genericClassicModelOverride = mod + return func() { + genericClassicModelOverride = prevOverride + } +} diff -Nru snapd-2.27.5/asserts/sysdb/staging.go snapd-2.28.5/asserts/sysdb/staging.go --- snapd-2.27.5/asserts/sysdb/staging.go 2016-09-09 06:45:37.000000000 +0000 +++ snapd-2.28.5/asserts/sysdb/staging.go 2017-09-13 14:47:18.000000000 +0000 @@ -79,6 +79,78 @@ Fy5UJIEKB0j0R2qnCz6HZkyQrUsz5HiIIlks18FfOZwuIc4GGPbwwQBoXW7a6KQg0aa62BPj5Iww 3w60rtTSUsjINkZ/GXLodfzPglOl6VLF7bWx2hGesg== ` + encodedStagingGenericAccount = `type: account +authority-id: canonical +account-id: generic +display-name: Generic +timestamp: 2017-07-27T00:00:00.0Z +username: generic +validation: certified +sign-key-sha3-384: e2r8on4LgdxQSaW5T8mBD5oC2fSTktTVxOYa5w3kIP4_nNF6L7mt6fOJShdOGkKu + +AcLBXAQAAQoABgUCWXmmFAAKCRAHKljtl9kuLkAWD/98LgECwAN8S09o4aEFpdGXgWpx8z58wl6T +5mZVDyYpCV9ugC2DqBqGQxp4X1P7Wn9+weXw8nmL7IywVn/hCVHJOmBLJSr3wLjpVBY9RrIHYoXi +k9W7IFo4ggw1j1FRLg2tKk81MnK0fK/Qws9OXzilDir5R2bQ/E0sodGW3NpbwtbpkY/BtP6YPoJ/ +1+205KG5m6oG8y6mf74bjMGfJ+iFFpIDayIpXl+YTkJ25BOVGcuC66cIrmdc63rBIHL2tU/3GUMB +xZGiyG9Fuli1uV4ALhN9j43hxAtVwXOn/qgOiN8TGQz3OvlVUXTuFVmkdvCdfT2XHrJjFmEs9SlL +u2EEmvaNFJ61lQG/VrN6O0BswenTlIO0tTFe126o/cTmKg8/ga4v2WjMlcOCzfu+cIZIzTTnn4Le +iXdQ6+c3QN+Co4SI0UvgJ4nGWQ9W+4q4xVJTliKTzK2BZ40vHUi51rMC/puqsMpnAbHSn4iy8vpf +CyJh7jyuITPEzfpurNMb+VD+1Brd2DJCVnlwQq+rzNerXd5xcHCdZsfX+ATukHgYTZWa467ZEFhI +Bk1xUWAYs8r2JDFb5YPtZuW7Vt1UUpFdx6DroL6OODvZ6mDUtsOa8nm7G1l4uRJtqunplPyCDjnL +aQhlAouLMltWeGITO+5jePHJKTnYQAFEvo0WIgEYpA== +` + encodedStagingGenericModelsAccountKey = `type: account-key +authority-id: canonical +public-key-sha3-384: 93jDrIGOXymDg9BPCLES5mAr6aGXU7e0wwXeJlIYIWbUzM_kB81CiqX7cTlB9Y1z +account-id: generic +name: models +since: 2017-07-27T00:00:00.0Z +body-length: 717 +sign-key-sha3-384: e2r8on4LgdxQSaW5T8mBD5oC2fSTktTVxOYa5w3kIP4_nNF6L7mt6fOJShdOGkKu + +AcbBTQRWhcGAARAAxcyFC13COEmIwWwLsjp4AAILhWSp8/dQ6cOzY3T7tqqoSn9iKyidpJTfrtml +DKHZe0zC10fog2Mvp1AO7dNqK9kHUdCQE+YatHmkm1a3QoZqwJsj77w09Q+l1uvDjrfrF0S/KcYa +0hfDZQ+51T1msbatWN2qU42dX280IMV+zo1GpKK8z6br4glY2tki4CJokVAAt+bl4bBqDZ4EoBYe +9CsACmNhw2d/fOAlis2jwG3tWXMORX9FcGRx/COasvRb7rjA0DJfKxOnTw7uC0UjDUB6bU6O0smS +Q5oK3V5fJIAcMBXNe5MkdTKGLY61hTFLiw4F6MkrM3O8dnXtexCojV+QtROTIM4R2dJTOv7r2s/9 +KT4wIQmOcjMEWxyq1H1rqjCHBjGnKa6GC1j/4NwqlxUEiqZMYs12px9ypeEqjL3tKURCanPOlwXO +p2E1i+V53XznnS3RA7I6Aa37w/9clTJk5vzVT8G6+k6xsB9zwKYOsipG0zHjyuW9Qtkd15bA0Iv9 +MrZGE4U7RwEnt4jBa98rcLs1sCkJEau0hEU4MiPyqi8XL2b/TtPnCwN8rQRVQvakzGw83Ol/B8ZI +2OGu0aB6HAWbdy81yXIUES9ZtH7nK5X7dSdJu92wXBMOyel9cryHzlYFjSlPKyqRx2lsYk4K6Hiq +VRY3L12yjXkcHcsAEQEAAQ== + +AcLBXAQAAQoABgUCWXmevAAKCRAHKljtl9kuLltVEADAfpBlY4b4oImKPq8Pp6UKFjgcMVjJLcSI +EOfAMygIaZwzNSuOh2wPRBAMMZlcFlBEFLfGbh7R2RG1/R7PSR4q+gMZZ3qJ5QUjUUuGnkSfCLhK +jVtPlX8qdPWTdEgUeTEKNzHogP0MiIChHdeuv/iQ9fSgdw/lBZsblKAdrHv00ZQHup9XGWdZ4Fnb +cSiK9tZ3/nZAG18PErEh7wJntwygqcjScS0jTSa5BecQoy8O6wxKafQgxuixdHw+dt6sa34qzwel +ROb1VmNcmMGsv2YuPsRcqjgvL7drDzXRRcYhmiSCUFhGPx3RY0UWO9G9Pzok64l/1D7o6Xah3h4D +oxkepM5JTAiy165kfQzEFGMtvlv0d3mOCLMWqJzjzhhn/bPcoh5MO/PhpSR1y71tjjWtKR4SD1K/ +feR+KE73gEgqmHLss3TF/O2RxvbV15W0paxmiffUyeE/uQ1p5ddmBwke/gM/OOgUA4G3g9vgTeaQ +YVFCD0h75mX9GylVXUMmxlYSRVO59JsxCpWSqXWh4xkigbBZSAOPn6vkM/4nxZLf/ufVyNqnwo2r +Si/lWgk/lJoHaTVsqm9n0DxiR8lH54eFghprkN+KgGFMKlY127n50CEwqG1j4gKfjxmRycgKbx5O +a0IGmGjVnVmGOdX9wpg6fBHbhczXZtId02Q7yzF87A== +` + + encodedStagingGenericClassicModel = `type: model +authority-id: generic +series: 16 +brand-id: generic +model: generic-classic +classic: true +timestamp: 2017-07-27T00:00:00.0Z +sign-key-sha3-384: 93jDrIGOXymDg9BPCLES5mAr6aGXU7e0wwXeJlIYIWbUzM_kB81CiqX7cTlB9Y1z + +AcLBXAQAAQoABgUCWXnYbAAKCRDqhmvwxUsbelvOD/4qxiDs4blJoRSXmzvsKTyM/Z2QuLw9bqUj +QXKoCSB78ATFwr01kvqJMzwJ1eT4zKOajUERKPN9fN1af0w07DoYG5bt/Pb7s/UFDmwIQg244wLI +lQ/NPCAm4SEvN1GEe0OxdCpMuPe+x++FvFtnF7CXJPmLdHln6A1eMhwyxGX+el1QxhiR+mWLCCNp +B4ndjh154H5SXRw1lmUiYdE/kCsOqGeZ5ljTni+Rh8xDYxmVCthrLCUVtHhMVrKeylDwwS7Sf/HV +GY9r/C9r07xRom06bBN/vQwdoLzGuU3SS7UsN0Ud95tJhAUtP5jW1dN8otviMcOAdtj7jTwSX4FY +pdgmkldjaCRaHxBA923cjGgl98LCjbdG5KmmKoT6DTb3AyFOT2XwlRl/MaRJBK2Tp1nVNZDjLY4j +VfRETt17ZCONt3yn/OhQk8bV6EsdJvT2/nMlNejXgnMtLfbH8v6xWLKrLOVOjILVF5zgK8+z4+d2 +ILIZupGooMouhddmcHem76lSnS+y75NMQXg5lBrUU2xAQRloWTw0oF+Hr5vcZkX5f4R/yH8Zz1Dt ++zRs2zqOK5hjdejhU5x/N3KSBLy+TUMk7JsdVv0nhdpJUKrFyGWn+YzBNE2GgEfPfXnkaU91/AD2 +SWyt8kWVPmT3DCzs7u5IXYIVxcq4FjkmeU9sTrn88g== +` ) func init() { @@ -91,4 +163,21 @@ panic(fmt.Sprintf("cannot decode trusted assertion: %v", err)) } trustedStagingAssertions = []asserts.Assertion{stagingTrustedAccount, stagingRootAccountKey} + + genericAccount, err := asserts.Decode([]byte(encodedStagingGenericAccount)) + if err != nil { + panic(fmt.Sprintf(`cannot decode "generic"'s account: %v`, err)) + } + genericModelsAccountKey, err := asserts.Decode([]byte(encodedStagingGenericModelsAccountKey)) + if err != nil { + panic(fmt.Sprintf(`cannot decode "generic"'s "models" account-key: %v`, err)) + } + + genericStagingAssertions = []asserts.Assertion{genericAccount, genericModelsAccountKey} + + a, err := asserts.Decode([]byte(encodedStagingGenericClassicModel)) + if err != nil { + panic(fmt.Sprintf(`cannot decode "generic"'s "generic-classic" model: %v`, err)) + } + genericStagingClassicModel = a.(*asserts.Model) } diff -Nru snapd-2.27.5/asserts/sysdb/sysdb.go snapd-2.28.5/asserts/sysdb/sysdb.go --- snapd-2.27.5/asserts/sysdb/sysdb.go 2016-08-11 17:27:49.000000000 +0000 +++ snapd-2.28.5/asserts/sysdb/sysdb.go 2017-09-13 14:47:18.000000000 +0000 @@ -42,7 +42,8 @@ // Open opens the system-wide assertion database with the trusted assertions set configured. func Open() (*asserts.Database, error) { cfg := &asserts.DatabaseConfig{ - Trusted: Trusted(), + Trusted: Trusted(), + OtherPredefined: Generic(), } return openDatabaseAt(dirs.SnapAssertsDBDir, cfg) } diff -Nru snapd-2.27.5/asserts/sysdb/sysdb_test.go snapd-2.28.5/asserts/sysdb/sysdb_test.go --- snapd-2.27.5/asserts/sysdb/sysdb_test.go 2016-08-23 18:49:28.000000000 +0000 +++ snapd-2.28.5/asserts/sysdb/sysdb_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -38,6 +38,8 @@ type sysDBSuite struct { extraTrusted []asserts.Assertion + extraGeneric []asserts.Assertion + otherModel *asserts.Model probeAssert asserts.Assertion } @@ -64,8 +66,27 @@ sdbs.extraTrusted = []asserts.Assertion{trustedAcct, trustedAccKey} + otherAcct := assertstest.NewAccount(signingDB, "gener1c", map[string]interface{}{ + "account-id": "gener1c", + "validation": "certified", + "timestamp": "2015-11-20T15:04:00Z", + }, "") + + sdbs.extraGeneric = []asserts.Assertion{otherAcct} + + a, err := signingDB.Sign(asserts.ModelType, map[string]interface{}{ + "series": "16", + "brand-id": "can0nical", + "model": "other-model", + "classic": "true", + "timestamp": "2015-11-20T15:04:00Z", + }, nil, "") + c.Assert(err, IsNil) + sdbs.otherModel = a.(*asserts.Model) + fakeRoot := filepath.Join(tmpdir, "root") - err := os.Mkdir(fakeRoot, os.ModePerm) + + err = os.Mkdir(fakeRoot, os.ModePerm) c.Assert(err, IsNil) dirs.SetRootDir(fakeRoot) @@ -87,6 +108,33 @@ c.Check(trustedEx, HasLen, 4) } +func (sdbs *sysDBSuite) TestGeneric(c *C) { + generic := sysdb.Generic() + c.Check(generic, HasLen, 2) + + restore := sysdb.InjectGeneric(sdbs.extraGeneric) + defer restore() + + genericEx := sysdb.Generic() + c.Check(genericEx, HasLen, 3) +} + +func (sdbs *sysDBSuite) TestGenericClassicModel(c *C) { + m := sysdb.GenericClassicModel() + c.Assert(m, NotNil) + + c.Check(m.AuthorityID(), Equals, "generic") + c.Check(m.BrandID(), Equals, "generic") + c.Check(m.Model(), Equals, "generic-classic") + c.Check(m.Classic(), Equals, true) + + r := sysdb.MockGenericClassicModel(sdbs.otherModel) + defer r() + + m = sysdb.GenericClassicModel() + c.Check(m, Equals, sdbs.otherModel) +} + func (sdbs *sysDBSuite) TestOpenSysDatabase(c *C) { db, err := sysdb.Open() c.Assert(err, IsNil) @@ -107,6 +155,23 @@ err = db.Check(trustedAcc) c.Check(err, IsNil) + // check generic + genericAcc, err := db.Find(asserts.AccountType, map[string]string{ + "account-id": "generic", + }) + c.Assert(err, IsNil) + _, err = db.FindMany(asserts.AccountKeyType, map[string]string{ + "account-id": "generic", + "name": "models", + }) + c.Assert(err, IsNil) + + err = db.Check(genericAcc) + c.Check(err, IsNil) + + err = db.Check(sysdb.GenericClassicModel()) + c.Check(err, IsNil) + // extraneous err = db.Check(sdbs.probeAssert) c.Check(err, ErrorMatches, "no matching public key.*") diff -Nru snapd-2.27.5/client/apps.go snapd-2.28.5/client/apps.go --- snapd-2.27.5/client/apps.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/client/apps.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,242 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2015-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 client + +import ( + "bufio" + "bytes" + "encoding/json" + "errors" + "fmt" + "net/url" + "strconv" + "strings" + "time" +) + +// AppInfo describes a single snap application. +type AppInfo struct { + Snap string `json:"snap,omitempty"` + Name string `json:"name"` + DesktopFile string `json:"desktop-file,omitempty"` + Daemon string `json:"daemon,omitempty"` + Enabled bool `json:"enabled,omitempty"` + Active bool `json:"active,omitempty"` +} + +// IsService returns true if the application is a background daemon. +func (a *AppInfo) IsService() bool { + if a == nil { + return false + } + if a.Daemon == "" { + return false + } + + return true +} + +// AppOptions represent the options of the Apps call. +type AppOptions struct { + // If Service is true, only return apps that are services + // (app.IsService() is true); otherwise, return all. + Service bool +} + +// Apps returns information about all matching apps. Each name can be +// either a snap or a snap.app. If names is empty, list all (that +// satisfy opts). +func (client *Client) Apps(names []string, opts AppOptions) ([]*AppInfo, error) { + q := make(url.Values) + if len(names) > 0 { + q.Add("names", strings.Join(names, ",")) + } + if opts.Service { + q.Add("select", "service") + } + + var appInfos []*AppInfo + _, err := client.doSync("GET", "/v2/apps", q, nil, nil, &appInfos) + + return appInfos, err +} + +// LogOptions represent the options of the Logs call. +type LogOptions struct { + N int // The maximum number of log lines to retrieve initially. If <0, no limit. + Follow bool // Whether to continue returning new lines as they appear +} + +// A Log holds the information of a single syslog entry +type Log struct { + Timestamp time.Time `json:"timestamp"` // Timestamp of the event, in RFC3339 format to µs precision. + Message string `json:"message"` // The log message itself + SID string `json:"sid"` // The syslog identifier + PID string `json:"pid"` // The process identifier +} + +func (l Log) String() string { + return fmt.Sprintf("%s %s[%s]: %s", l.Timestamp.Format(time.RFC3339), l.SID, l.PID, l.Message) +} + +// Logs asks for the logs of a series of services, by name. +func (client *Client) Logs(names []string, opts LogOptions) (<-chan Log, error) { + query := url.Values{} + if len(names) > 0 { + query.Set("names", strings.Join(names, ",")) + } + query.Set("n", strconv.Itoa(opts.N)) + if opts.Follow { + query.Set("follow", strconv.FormatBool(opts.Follow)) + } + + rsp, err := client.raw("GET", "/v2/logs", query, nil, nil) + if err != nil { + return nil, err + } + + ch := make(chan Log, 20) + go func() { + // logs come in application/json-seq, described in RFC7464: it's + // a series of . Decoders are + // expected to skip invalid or truncated or empty records. + scanner := bufio.NewScanner(rsp.Body) + for scanner.Scan() { + buf := scanner.Bytes() // the scanner prunes the ending LF + if len(buf) < 1 { + // truncated record? skip + continue + } + idx := bytes.IndexByte(buf, 0x1E) // find the initial RS + if idx < 0 { + // no RS? skip + continue + } + buf = buf[idx+1:] // drop the initial RS + var log Log + if err := json.Unmarshal(buf, &log); err != nil { + // truncated/corrupted/binary record? skip + continue + } + ch <- log + } + close(ch) + rsp.Body.Close() + }() + + return ch, nil +} + +// ErrNoNames is returned by Start, Stop, or Restart, when the given +// list of things on which to operate is empty. +var ErrNoNames = errors.New(`"names" must not be empty`) + +type appInstruction struct { + Action string `json:"action"` + Names []string `json:"names"` + StartOptions + StopOptions + RestartOptions +} + +// StartOptions represent the different options of the Start call. +type StartOptions struct { + // Enable, as well as starting, the listed services. A + // disabled service does not start on boot. + Enable bool `json:"enable,omitempty"` +} + +// Start services. +// +// It takes a list of names that can be snaps, of which all their +// services are started, or snap.service which are individual +// services to start; it shouldn't be empty. +func (client *Client) Start(names []string, opts StartOptions) (changeID string, err error) { + if len(names) == 0 { + return "", ErrNoNames + } + + buf, err := json.Marshal(appInstruction{ + Action: "start", + Names: names, + StartOptions: opts, + }) + if err != nil { + return "", err + } + return client.doAsync("POST", "/v2/apps", nil, nil, bytes.NewReader(buf)) +} + +// StopOptions represent the different options of the Stop call. +type StopOptions struct { + // Disable, as well as stopping, the listed services. A + // service that is not disabled starts on boot. + Disable bool `json:"disable,omitempty"` +} + +// Stop services. +// +// It takes a list of names that can be snaps, of which all their +// services are stopped, or snap.service which are individual +// services to stop; it shouldn't be empty. +func (client *Client) Stop(names []string, opts StopOptions) (changeID string, err error) { + if len(names) == 0 { + return "", ErrNoNames + } + + buf, err := json.Marshal(appInstruction{ + Action: "stop", + Names: names, + StopOptions: opts, + }) + if err != nil { + return "", err + } + return client.doAsync("POST", "/v2/apps", nil, nil, bytes.NewReader(buf)) +} + +// RestartOptions represent the different options of the Restart call. +type RestartOptions struct { + // Reload the services, if possible (i.e. if the App has a + // ReloadCommand, invoque it), instead of restarting. + Reload bool `json:"reload,omitempty"` +} + +// Restart services. +// +// It takes a list of names that can be snaps, of which all their +// services are restarted, or snap.service which are individual +// services to restart; it shouldn't be empty. If the service is not +// running, starts it. +func (client *Client) Restart(names []string, opts RestartOptions) (changeID string, err error) { + if len(names) == 0 { + return "", ErrNoNames + } + + buf, err := json.Marshal(appInstruction{ + Action: "restart", + Names: names, + RestartOptions: opts, + }) + if err != nil { + return "", err + } + return client.doAsync("POST", "/v2/apps", nil, nil, bytes.NewReader(buf)) +} diff -Nru snapd-2.27.5/client/apps_test.go snapd-2.28.5/client/apps_test.go --- snapd-2.27.5/client/apps_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/client/apps_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,372 @@ +// -*- 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 client_test + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + + "gopkg.in/check.v1" + + "github.com/snapcore/snapd/client" +) + +func mksvc(snap, app string) *client.AppInfo { + return &client.AppInfo{ + Snap: snap, + Name: app, + Daemon: "simple", + Active: true, + Enabled: true, + } + +} + +func testClientApps(cs *clientSuite, c *check.C) ([]*client.AppInfo, error) { + services, err := cs.cli.Apps([]string{"foo", "bar"}, client.AppOptions{}) + c.Check(cs.req.URL.Path, check.Equals, "/v2/apps") + c.Check(cs.req.Method, check.Equals, "GET") + query := cs.req.URL.Query() + c.Check(query, check.HasLen, 1) + c.Check(query.Get("names"), check.Equals, "foo,bar") + + return services, err +} + +func testClientAppsService(cs *clientSuite, c *check.C) ([]*client.AppInfo, error) { + services, err := cs.cli.Apps([]string{"foo", "bar"}, client.AppOptions{Service: true}) + c.Check(cs.req.URL.Path, check.Equals, "/v2/apps") + c.Check(cs.req.Method, check.Equals, "GET") + query := cs.req.URL.Query() + c.Check(query, check.HasLen, 2) + c.Check(query.Get("names"), check.Equals, "foo,bar") + c.Check(query.Get("select"), check.Equals, "service") + + return services, err +} + +var appcheckers = []func(*clientSuite, *check.C) ([]*client.AppInfo, error){testClientApps, testClientAppsService} + +func (cs *clientSuite) TestClientServiceGetHappy(c *check.C) { + expected := []*client.AppInfo{mksvc("foo", "foo"), mksvc("bar", "bar1")} + buf, err := json.Marshal(expected) + c.Assert(err, check.IsNil) + cs.rsp = fmt.Sprintf(`{"type": "sync", "result": %s}`, buf) + for _, chkr := range appcheckers { + actual, err := chkr(cs, c) + c.Assert(err, check.IsNil) + c.Check(actual, check.DeepEquals, expected) + } +} + +func (cs *clientSuite) TestClientServiceGetSad(c *check.C) { + cs.err = fmt.Errorf("xyzzy") + for _, chkr := range appcheckers { + actual, err := chkr(cs, c) + c.Assert(err, check.ErrorMatches, ".* xyzzy") + c.Check(actual, check.HasLen, 0) + } +} + +func testClientLogs(cs *clientSuite, c *check.C) ([]client.Log, error) { + ch, err := cs.cli.Logs([]string{"foo", "bar"}, client.LogOptions{N: -1, Follow: false}) + c.Check(cs.req.URL.Path, check.Equals, "/v2/logs") + c.Check(cs.req.Method, check.Equals, "GET") + query := cs.req.URL.Query() + c.Check(query, check.HasLen, 2) + c.Check(query.Get("names"), check.Equals, "foo,bar") + c.Check(query.Get("n"), check.Equals, "-1") + + var logs []client.Log + if ch != nil { + for log := range ch { + logs = append(logs, log) + } + } + + return logs, err +} + +func (cs *clientSuite) TestClientLogsHappy(c *check.C) { + cs.rsp = ` +{"message":"hello"} +{"message":"bye"} +`[1:] // remove the first \n + + logs, err := testClientLogs(cs, c) + c.Assert(err, check.IsNil) + c.Check(logs, check.DeepEquals, []client.Log{{Message: "hello"}, {Message: "bye"}}) +} + +func (cs *clientSuite) TestClientLogsDealsWithIt(c *check.C) { + cs.rsp = `this is a line with no RS on it +this is a line with a RS after some junk{"message": "hello"} +{"message": "bye"} +and that was a regular line. The next one is empty, despite having a RS (and the one after is entirely empty): + + +` + logs, err := testClientLogs(cs, c) + c.Assert(err, check.IsNil) + c.Check(logs, check.DeepEquals, []client.Log{{Message: "hello"}, {Message: "bye"}}) +} + +func (cs *clientSuite) TestClientLogsSad(c *check.C) { + cs.err = fmt.Errorf("xyzzy") + actual, err := testClientLogs(cs, c) + c.Assert(err, check.ErrorMatches, ".* xyzzy") + c.Check(actual, check.HasLen, 0) +} + +func (cs *clientSuite) TestClientLogsOpts(c *check.C) { + const ( + maxint = int((^uint(0)) >> 1) + minint = -maxint - 1 + ) + for _, names := range [][]string{nil, {}, {"foo"}, {"foo", "bar"}} { + for _, n := range []int{-1, 0, 1, minint, maxint} { + for _, follow := range []bool{true, false} { + iterdesc := check.Commentf("names: %v, n: %v, follow: %v", names, n, follow) + + ch, err := cs.cli.Logs(names, client.LogOptions{N: n, Follow: follow}) + c.Check(err, check.IsNil, iterdesc) + c.Check(cs.req.URL.Path, check.Equals, "/v2/logs", iterdesc) + c.Check(cs.req.Method, check.Equals, "GET", iterdesc) + query := cs.req.URL.Query() + numQ := 0 + + var namesout []string + if ns := query.Get("names"); ns != "" { + namesout = strings.Split(ns, ",") + } + + c.Check(len(namesout), check.Equals, len(names), iterdesc) + if len(names) != 0 { + c.Check(namesout, check.DeepEquals, names, iterdesc) + numQ++ + } + + nout, nerr := strconv.Atoi(query.Get("n")) + c.Check(nerr, check.IsNil, iterdesc) + c.Check(nout, check.Equals, n, iterdesc) + numQ++ + + if follow { + fout, ferr := strconv.ParseBool(query.Get("follow")) + c.Check(fout, check.Equals, true, iterdesc) + c.Check(ferr, check.IsNil, iterdesc) + numQ++ + } + + c.Check(query, check.HasLen, numQ, iterdesc) + + for x := range ch { + c.Logf("expecting empty channel, got %v during %s", x, iterdesc) + c.Fail() + } + } + } + } +} + +func (cs *clientSuite) TestClientServiceStart(c *check.C) { + cs.rsp = `{"type": "async", "status-code": 202, "change": "24"}` + + type scenario struct { + names []string + opts client.StartOptions + comment check.CommentInterface + } + + var scenarios []scenario + + for _, names := range [][]string{ + nil, {}, + {"foo"}, + {"foo", "bar", "baz"}, + } { + for _, opts := range []client.StartOptions{ + {Enable: true}, + {Enable: false}, + } { + scenarios = append(scenarios, scenario{ + names: names, + opts: opts, + comment: check.Commentf("{%q; %#v}", names, opts), + }) + } + } + + for _, sc := range scenarios { + id, err := cs.cli.Start(sc.names, sc.opts) + if len(sc.names) == 0 { + c.Check(id, check.Equals, "", sc.comment) + c.Check(err, check.Equals, client.ErrNoNames, sc.comment) + c.Check(cs.req, check.IsNil, sc.comment) // i.e. the request was never done + } else { + c.Assert(err, check.IsNil, sc.comment) + c.Check(id, check.Equals, "24", sc.comment) + c.Check(cs.req.URL.Path, check.Equals, "/v2/apps", sc.comment) + c.Check(cs.req.Method, check.Equals, "POST", sc.comment) + c.Check(cs.req.URL.Query(), check.HasLen, 0, sc.comment) + + inames := make([]interface{}, len(sc.names)) + for i, name := range sc.names { + inames[i] = interface{}(name) + } + + var reqOp map[string]interface{} + c.Assert(json.NewDecoder(cs.req.Body).Decode(&reqOp), check.IsNil, sc.comment) + if sc.opts.Enable { + c.Check(len(reqOp), check.Equals, 3, sc.comment) + c.Check(reqOp["enable"], check.Equals, true, sc.comment) + } else { + c.Check(len(reqOp), check.Equals, 2, sc.comment) + c.Check(reqOp["enable"], check.IsNil, sc.comment) + } + c.Check(reqOp["action"], check.Equals, "start", sc.comment) + c.Check(reqOp["names"], check.DeepEquals, inames, sc.comment) + } + } +} + +func (cs *clientSuite) TestClientServiceStop(c *check.C) { + cs.rsp = `{"type": "async", "status-code": 202, "change": "24"}` + + type tT struct { + names []string + opts client.StopOptions + comment check.CommentInterface + } + + var scs []tT + + for _, names := range [][]string{ + nil, {}, + {"foo"}, + {"foo", "bar", "baz"}, + } { + for _, opts := range []client.StopOptions{ + {Disable: true}, + {Disable: false}, + } { + scs = append(scs, tT{ + names: names, + opts: opts, + comment: check.Commentf("{%q; %#v}", names, opts), + }) + } + } + + for _, sc := range scs { + id, err := cs.cli.Stop(sc.names, sc.opts) + if len(sc.names) == 0 { + c.Check(id, check.Equals, "", sc.comment) + c.Check(err, check.Equals, client.ErrNoNames, sc.comment) + c.Check(cs.req, check.IsNil, sc.comment) // i.e. the request was never done + } else { + c.Assert(err, check.IsNil, sc.comment) + c.Check(id, check.Equals, "24", sc.comment) + c.Check(cs.req.URL.Path, check.Equals, "/v2/apps", sc.comment) + c.Check(cs.req.Method, check.Equals, "POST", sc.comment) + c.Check(cs.req.URL.Query(), check.HasLen, 0, sc.comment) + + inames := make([]interface{}, len(sc.names)) + for i, name := range sc.names { + inames[i] = interface{}(name) + } + + var reqOp map[string]interface{} + c.Assert(json.NewDecoder(cs.req.Body).Decode(&reqOp), check.IsNil, sc.comment) + if sc.opts.Disable { + c.Check(len(reqOp), check.Equals, 3, sc.comment) + c.Check(reqOp["disable"], check.Equals, true, sc.comment) + } else { + c.Check(len(reqOp), check.Equals, 2, sc.comment) + c.Check(reqOp["disable"], check.IsNil, sc.comment) + } + c.Check(reqOp["action"], check.Equals, "stop", sc.comment) + c.Check(reqOp["names"], check.DeepEquals, inames, sc.comment) + } + } +} + +func (cs *clientSuite) TestClientServiceRestart(c *check.C) { + cs.rsp = `{"type": "async", "status-code": 202, "change": "24"}` + + type tT struct { + names []string + opts client.RestartOptions + comment check.CommentInterface + } + + var scs []tT + + for _, names := range [][]string{ + nil, {}, + {"foo"}, + {"foo", "bar", "baz"}, + } { + for _, opts := range []client.RestartOptions{ + {Reload: true}, + {Reload: false}, + } { + scs = append(scs, tT{ + names: names, + opts: opts, + comment: check.Commentf("{%q; %#v}", names, opts), + }) + } + } + + for _, sc := range scs { + id, err := cs.cli.Restart(sc.names, sc.opts) + if len(sc.names) == 0 { + c.Check(id, check.Equals, "", sc.comment) + c.Check(err, check.Equals, client.ErrNoNames, sc.comment) + c.Check(cs.req, check.IsNil, sc.comment) // i.e. the request was never done + } else { + c.Assert(err, check.IsNil, sc.comment) + c.Check(id, check.Equals, "24", sc.comment) + c.Check(cs.req.URL.Path, check.Equals, "/v2/apps", sc.comment) + c.Check(cs.req.Method, check.Equals, "POST", sc.comment) + c.Check(cs.req.URL.Query(), check.HasLen, 0, sc.comment) + + inames := make([]interface{}, len(sc.names)) + for i, name := range sc.names { + inames[i] = interface{}(name) + } + + var reqOp map[string]interface{} + c.Assert(json.NewDecoder(cs.req.Body).Decode(&reqOp), check.IsNil, sc.comment) + if sc.opts.Reload { + c.Check(len(reqOp), check.Equals, 3, sc.comment) + c.Check(reqOp["reload"], check.Equals, true, sc.comment) + } else { + c.Check(len(reqOp), check.Equals, 2, sc.comment) + c.Check(reqOp["reload"], check.IsNil, sc.comment) + } + c.Check(reqOp["action"], check.Equals, "restart", sc.comment) + c.Check(reqOp["names"], check.DeepEquals, inames, sc.comment) + } + } +} diff -Nru snapd-2.27.5/client/asserts.go snapd-2.28.5/client/asserts.go --- snapd-2.27.5/client/asserts.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/client/asserts.go 2017-09-13 14:47:18.000000000 +0000 @@ -42,6 +42,19 @@ return nil } +// AssertionTypes returns a list of assertion type names. +func (client *Client) AssertionTypes() ([]string, error) { + var types struct { + Types []string `json:"types"` + } + _, err := client.doSync("GET", "/v2/assertions", nil, nil, nil, &types) + if err != nil { + return nil, fmt.Errorf("cannot get assertion type names: %v", err) + } + + return types.Types, nil +} + // Known queries assertions with type assertTypeName and matching assertion headers. func (client *Client) Known(assertTypeName string, headers map[string]string) ([]asserts.Assertion, error) { path := fmt.Sprintf("/v2/assertions/%s", assertTypeName) diff -Nru snapd-2.27.5/client/asserts_test.go snapd-2.28.5/client/asserts_test.go --- snapd-2.27.5/client/asserts_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/client/asserts_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -45,6 +45,20 @@ c.Check(cs.req.URL.Path, Equals, "/v2/assertions") } +func (cs *clientSuite) TestClientAssertsTypes(c *C) { + cs.rsp = `{ + "result": { + "types": ["one", "two"] + }, + "status": "OK", + "status-code": 200, + "type": "sync" +}` + typs, err := cs.cli.AssertionTypes() + c.Assert(err, IsNil) + c.Check(typs, DeepEquals, []string{"one", "two"}) +} + func (cs *clientSuite) TestClientAssertsCallsEndpoint(c *C) { _, _ = cs.cli.Known("snap-revision", nil) c.Check(cs.req.Method, Equals, "GET") diff -Nru snapd-2.27.5/client/client.go snapd-2.28.5/client/client.go --- snapd-2.27.5/client/client.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/client/client.go 2017-09-13 14:47:18.000000000 +0000 @@ -33,6 +33,7 @@ "time" "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/jsonutil" ) func unixDialer(socketPath string) func(string, string) (net.Conn, error) { @@ -58,6 +59,11 @@ // Authorization header from reading the auth.json data. DisableAuth bool + // Interactive controls whether the client runs in interactive mode. + // At present, this only affects whether interactive polkit + // authorisation is requested. + Interactive bool + // Socket is the path to the unix socket to use Socket string } @@ -68,6 +74,7 @@ doer doer disableAuth bool + interactive bool } // New returns a new instance of Client @@ -87,6 +94,7 @@ Transport: &http.Transport{Dial: unixDialer(config.Socket)}, }, disableAuth: config.DisableAuth, + interactive: config.Interactive, } } @@ -98,6 +106,7 @@ baseURL: *baseURL, doer: &http.Client{}, disableAuth: config.DisableAuth, + interactive: config.Interactive, } } @@ -149,6 +158,10 @@ return fmt.Sprintf("cannot communicate with server: %v", e.error) } +// AllowInteractionHeader is the HTTP request header used to indicate +// that the client is willing to allow interaction. +const AllowInteractionHeader = "X-Allow-Interaction" + // raw performs a request and returns the resulting http.Response and // error you usually only need to call this directly if you expect the // response to not be JSON, otherwise you'd call Do(...) instead. @@ -174,6 +187,10 @@ } } + if client.interactive { + req.Header.Set(AllowInteractionHeader, "true") + } + rsp, err := client.doer.Do(req) if err != nil { return nil, ConnectionError{err} @@ -256,7 +273,7 @@ } if v != nil { - if err := json.Unmarshal(rsp.Result, v); err != nil { + if err := jsonutil.DecodeWithNumber(bytes.NewReader(rsp.Result), v); err != nil { return nil, fmt.Errorf("cannot unmarshal: %v", err) } } diff -Nru snapd-2.27.5/client/client_test.go snapd-2.28.5/client/client_test.go --- snapd-2.27.5/client/client_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/client/client_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -170,6 +170,21 @@ c.Check(authorization, Equals, "") } +func (cs *clientSuite) TestClientHonorsInteractive(c *C) { + var v string + cli := client.New(&client.Config{Interactive: false}) + cli.SetDoer(cs) + _ = cli.Do("GET", "/this", nil, nil, &v) + interactive := cs.req.Header.Get(client.AllowInteractionHeader) + c.Check(interactive, Equals, "") + + cli = client.New(&client.Config{Interactive: true}) + cli.SetDoer(cs) + _ = cli.Do("GET", "/this", nil, nil, &v) + interactive = cs.req.Header.Get(client.AllowInteractionHeader) + c.Check(interactive, Equals, "true") +} + func (cs *clientSuite) TestClientWhoAmINobody(c *C) { email, err := cs.cli.WhoAmI() c.Assert(err, IsNil) diff -Nru snapd-2.27.5/client/conf_test.go snapd-2.28.5/client/conf_test.go --- snapd-2.27.5/client/conf_test.go 2016-09-09 06:45:37.000000000 +0000 +++ snapd-2.28.5/client/conf_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -75,6 +75,17 @@ c.Check(value, check.DeepEquals, map[string]interface{}{"test-key": "test-value"}) } +func (cs *clientSuite) TestClientGetConfBigInt(c *check.C) { + cs.rsp = `{ + "type": "sync", + "status-code": 200, + "result": {"test-key": 1234567890} + }` + value, err := cs.cli.Conf("snap-name", []string{"test-key"}) + c.Assert(err, check.IsNil) + c.Check(value, check.DeepEquals, map[string]interface{}{"test-key": json.Number("1234567890")}) +} + func (cs *clientSuite) TestClientGetConfMultipleKeys(c *check.C) { cs.rsp = `{ "type": "sync", diff -Nru snapd-2.27.5/client/interfaces.go snapd-2.28.5/client/interfaces.go --- snapd-2.27.5/client/interfaces.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/client/interfaces.go 2017-09-13 14:47:18.000000000 +0000 @@ -22,6 +22,8 @@ import ( "bytes" "encoding/json" + "net/url" + "strings" ) // Plug represents the potential of a given snap to connect to a slot. @@ -58,15 +60,19 @@ Name string `json:"slot"` } -// Interfaces contains information about all plugs, slots and their connections -type Interfaces struct { +// Connections contains information about all plugs, slots and their connections +type Connections struct { Plugs []Plug `json:"plugs"` Slots []Slot `json:"slots"` } -// InterfaceMetaData contains meta-data about a given interface type. -type InterfaceMetaData struct { - Description string `json:"description,omitempty"` +// Interface holds information about a given interface and its instances. +type Interface struct { + Name string `json:"name,omitempty"` + Summary string `json:"summary,omitempty"` + DocURL string `json:"doc-url,omitempty"` + Plugs []Plug `json:"plugs,omitempty"` + Slots []Slot `json:"slots,omitempty"` } // InterfaceAction represents an action performed on the interface system. @@ -76,9 +82,45 @@ Slots []Slot `json:"slots,omitempty"` } -// Interfaces returns all plugs, slots and their connections. -func (client *Client) Interfaces() (interfaces Interfaces, err error) { - _, err = client.doSync("GET", "/v2/interfaces", nil, nil, nil, &interfaces) +// Connections returns all plugs, slots and their connections. +func (client *Client) Connections() (Connections, error) { + var conns Connections + _, err := client.doSync("GET", "/v2/interfaces", nil, nil, nil, &conns) + return conns, err +} + +// InterfaceOptions represents opt-in elements include in responses. +type InterfaceOptions struct { + Names []string + Doc bool + Plugs bool + Slots bool + Connected bool +} + +func (client *Client) Interfaces(opts *InterfaceOptions) (interfaces []*Interface, err error) { + query := url.Values{} + if opts != nil && len(opts.Names) > 0 { + query.Set("names", strings.Join(opts.Names, ",")) // Return just those specific interfaces. + } + if opts != nil { + if opts.Doc { + query.Set("doc", "true") // Return documentation of each selected interface. + } + if opts.Plugs { + query.Set("plugs", "true") // Return plugs of each selected interface. + } + if opts.Slots { + query.Set("slots", "true") // Return slots of each selected interface. + } + } + // NOTE: Presence of "select" triggers the use of the new response format. + if opts != nil && opts.Connected { + query.Set("select", "connected") // Return just the connected interfaces. + } else { + query.Set("select", "all") // Return all interfaces. + } + _, err = client.doSync("GET", "/v2/interfaces", query, nil, nil, &interfaces) return } diff -Nru snapd-2.27.5/client/interfaces_test.go snapd-2.28.5/client/interfaces_test.go --- snapd-2.27.5/client/interfaces_test.go 2016-06-01 20:08:33.000000000 +0000 +++ snapd-2.28.5/client/interfaces_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -27,13 +27,142 @@ "github.com/snapcore/snapd/client" ) -func (cs *clientSuite) TestClientInterfacesCallsEndpoint(c *check.C) { - _, _ = cs.cli.Interfaces() +func (cs *clientSuite) TestClientInterfacesOptionEncoding(c *check.C) { + // Choose some options + _, _ = cs.cli.Interfaces(&client.InterfaceOptions{ + Names: []string{"a", "b"}, + Doc: true, + Plugs: true, + Slots: true, + Connected: true, + }) c.Check(cs.req.Method, check.Equals, "GET") c.Check(cs.req.URL.Path, check.Equals, "/v2/interfaces") + c.Check(cs.req.URL.RawQuery, check.Equals, + "doc=true&names=a%2Cb&plugs=true&select=connected&slots=true") } -func (cs *clientSuite) TestClientInterfaces(c *check.C) { +func (cs *clientSuite) TestClientInterfacesAll(c *check.C) { + // Ask for a summary of all interfaces. + cs.rsp = `{ + "type": "sync", + "result": [ + {"name": "iface-a", "summary": "the A iface"}, + {"name": "iface-b", "summary": "the B iface"}, + {"name": "iface-c", "summary": "the C iface"} + ] + }` + ifaces, err := cs.cli.Interfaces(nil) + c.Check(cs.req.Method, check.Equals, "GET") + c.Check(cs.req.URL.Path, check.Equals, "/v2/interfaces") + // This uses the select=all query option to indicate that new response + // format should be used. The same API endpoint is used by the Interfaces + // and by the Connections functions an the absence or presence of the + // select query option decides what kind of result should be returned + // (legacy or modern). + c.Check(cs.req.URL.RawQuery, check.Equals, "select=all") + c.Assert(err, check.IsNil) + c.Check(ifaces, check.DeepEquals, []*client.Interface{ + {Name: "iface-a", Summary: "the A iface"}, + {Name: "iface-b", Summary: "the B iface"}, + {Name: "iface-c", Summary: "the C iface"}, + }) +} + +func (cs *clientSuite) TestClientInterfacesConnected(c *check.C) { + // Ask for for a summary of connected interfaces. + cs.rsp = `{ + "type": "sync", + "result": [ + {"name": "iface-a", "summary": "the A iface"}, + {"name": "iface-c", "summary": "the C iface"} + ] + }` + ifaces, err := cs.cli.Interfaces(&client.InterfaceOptions{ + Connected: true, + }) + c.Check(cs.req.URL.Path, check.Equals, "/v2/interfaces") + // This uses select=connected to ignore interfaces that just sit on some + // snap but are not connected to anything. + c.Check(cs.req.URL.RawQuery, check.Equals, "select=connected") + c.Assert(err, check.IsNil) + c.Check(ifaces, check.DeepEquals, []*client.Interface{ + {Name: "iface-a", Summary: "the A iface"}, + // interface b was not connected so it doesn't get listed. + {Name: "iface-c", Summary: "the C iface"}, + }) +} + +func (cs *clientSuite) TestClientInterfacesSelectedDetails(c *check.C) { + // Ask for single element and request docs, plugs and slots. + cs.rsp = `{ + "type": "sync", + "result": [ + { + "name": "iface-a", + "summary": "the A iface", + "doc-url": "http://example.org/ifaces/a", + "plugs": [{ + "snap": "consumer", + "plug": "plug", + "interface": "iface-a" + }], + "slots": [{ + "snap": "producer", + "slot": "slot", + "interface": "iface-a" + }] + } + ] + }` + opts := &client.InterfaceOptions{Names: []string{"iface-a"}, Doc: true, Plugs: true, Slots: true} + ifaces, err := cs.cli.Interfaces(opts) + c.Check(cs.req.Method, check.Equals, "GET") + c.Check(cs.req.URL.Path, check.Equals, "/v2/interfaces") + // This enables documentation, plugs, slots, chooses a specific interface + // (iface-a), and uses select=all to indicate that new response is desired. + c.Check(cs.req.URL.RawQuery, check.Equals, + "doc=true&names=iface-a&plugs=true&select=all&slots=true") + c.Assert(err, check.IsNil) + c.Check(ifaces, check.DeepEquals, []*client.Interface{ + { + Name: "iface-a", + Summary: "the A iface", + DocURL: "http://example.org/ifaces/a", + Plugs: []client.Plug{{Snap: "consumer", Name: "plug", Interface: "iface-a"}}, + Slots: []client.Slot{{Snap: "producer", Name: "slot", Interface: "iface-a"}}, + }, + }) +} + +func (cs *clientSuite) TestClientInterfacesMultiple(c *check.C) { + // Ask for multiple interfaces. + cs.rsp = `{ + "type": "sync", + "result": [ + {"name": "iface-a", "summary": "the A iface"}, + {"name": "iface-b", "summary": "the B iface"} + ] + }` + ifaces, err := cs.cli.Interfaces(&client.InterfaceOptions{Names: []string{"iface-a", "iface-b"}}) + c.Check(cs.req.Method, check.Equals, "GET") + c.Check(cs.req.URL.Path, check.Equals, "/v2/interfaces") + // This chooses a specific interfaces (iface-a, iface-b) + c.Check(cs.req.URL.RawQuery, check.Equals, "names=iface-a%2Ciface-b&select=all") + c.Assert(err, check.IsNil) + c.Check(ifaces, check.DeepEquals, []*client.Interface{ + {Name: "iface-a", Summary: "the A iface"}, + {Name: "iface-b", Summary: "the B iface"}, + }) +} + +func (cs *clientSuite) TestClientConnectionsCallsEndpoint(c *check.C) { + _, _ = cs.cli.Connections() + c.Check(cs.req.Method, check.Equals, "GET") + c.Check(cs.req.URL.Path, check.Equals, "/v2/interfaces") +} + +func (cs *clientSuite) TestClientConnections(c *check.C) { cs.rsp = `{ "type": "sync", "result": { @@ -61,9 +190,9 @@ ] } }` - interfaces, err := cs.cli.Interfaces() + conns, err := cs.cli.Connections() c.Assert(err, check.IsNil) - c.Check(interfaces, check.DeepEquals, client.Interfaces{ + c.Check(conns, check.DeepEquals, client.Connections{ Plugs: []client.Plug{ { Snap: "canonical-pi2", diff -Nru snapd-2.27.5/client/packages.go snapd-2.28.5/client/packages.go --- snapd-2.27.5/client/packages.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/client/packages.go 2017-09-13 14:47:18.000000000 +0000 @@ -55,6 +55,7 @@ Apps []AppInfo `json:"apps"` Broken string `json:"broken"` Contact string `json:"contact"` + License string `json:"license,omitempty"` Prices map[string]float64 `json:"prices"` Screenshots []Screenshot `json:"screenshots"` @@ -66,11 +67,6 @@ Tracks []string } -type AppInfo struct { - Name string `json:"name"` - Daemon string `json:"daemon"` -} - type Screenshot struct { URL string `json:"url"` Width int64 `json:"width,omitempty"` diff -Nru snapd-2.27.5/client/packages_test.go snapd-2.28.5/client/packages_test.go --- snapd-2.27.5/client/packages_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/client/packages_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,6 +20,7 @@ package client_test import ( + "encoding/json" "fmt" "net/url" "time" @@ -112,6 +113,7 @@ "download-size": 22212, "icon": "https://myapps.developer.ubuntu.com/site_media/appmedia/2015/03/hello.svg_NZLfWbh.png", "installed-size": -1, + "license": "GPL-3.0", "name": "hello-world", "developer": "canonical", "resource": "/v2/snaps/hello-world.canonical", @@ -133,6 +135,7 @@ DownloadSize: 22212, Icon: "https://myapps.developer.ubuntu.com/site_media/appmedia/2015/03/hello.svg_NZLfWbh.png", InstalledSize: -1, + License: "GPL-3.0", Name: "hello-world", Developer: "canonical", Status: client.StatusAvailable, @@ -180,6 +183,7 @@ "icon": "/v2/icons/chatroom.ogra/icon", "installed-size": 18976651, "install-date": "2016-01-02T15:04:05Z", + "license": "GPL-3.0", "name": "chatroom", "developer": "ogra", "resource": "/v2/snaps/chatroom.ogra", @@ -209,6 +213,7 @@ Icon: "/v2/icons/chatroom.ogra/icon", InstalledSize: 18976651, InstallDate: time.Date(2016, 1, 2, 15, 4, 5, 0, time.UTC), + License: "GPL-3.0", Name: "chatroom", Developer: "ogra", Status: client.StatusActive, @@ -224,3 +229,55 @@ }, }) } + +func (cs *clientSuite) TestAppInfoNoServiceNoDaemon(c *check.C) { + buf, err := json.MarshalIndent(client.AppInfo{Name: "hello"}, "\t", "\t") + c.Assert(err, check.IsNil) + c.Check(string(buf), check.Equals, `{ + "name": "hello" + }`) +} + +func (cs *clientSuite) TestAppInfoServiceDaemon(c *check.C) { + buf, err := json.MarshalIndent(client.AppInfo{ + Snap: "foo", + Name: "hello", + Daemon: "daemon", + Enabled: true, + Active: false, + }, "\t", "\t") + c.Assert(err, check.IsNil) + c.Check(string(buf), check.Equals, `{ + "snap": "foo", + "name": "hello", + "daemon": "daemon", + "enabled": true + }`) +} + +func (cs *clientSuite) TestAppInfoNilNotService(c *check.C) { + var app *client.AppInfo + c.Check(app.IsService(), check.Equals, false) +} + +func (cs *clientSuite) TestAppInfoNoDaemonNotService(c *check.C) { + var app *client.AppInfo + c.Assert(json.Unmarshal([]byte(`{"name": "hello"}`), &app), check.IsNil) + c.Check(app.Name, check.Equals, "hello") + c.Check(app.IsService(), check.Equals, false) +} + +func (cs *clientSuite) TestAppInfoEmptyDaemonNotService(c *check.C) { + var app *client.AppInfo + c.Assert(json.Unmarshal([]byte(`{"name": "hello", "daemon": ""}`), &app), check.IsNil) + c.Check(app.Name, check.Equals, "hello") + c.Check(app.IsService(), check.Equals, false) +} + +func (cs *clientSuite) TestAppInfoDaemonIsService(c *check.C) { + var app *client.AppInfo + + c.Assert(json.Unmarshal([]byte(`{"name": "hello", "daemon": "x"}`), &app), check.IsNil) + c.Check(app.Name, check.Equals, "hello") + c.Check(app.IsService(), check.Equals, true) +} diff -Nru snapd-2.27.5/client/snap_op.go snapd-2.28.5/client/snap_op.go --- snapd-2.27.5/client/snap_op.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/client/snap_op.go 2017-09-13 14:47:18.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 @@ -117,6 +117,11 @@ return client.doSnapAction("revert", name, options) } +// Switch moves the snap to a different channel without a refresh +func (client *Client) Switch(name string, options *SnapOptions) (changeID string, err error) { + return client.doSnapAction("switch", name, options) +} + var ErrDangerousNotApplicable = fmt.Errorf("dangerous option only meaningful when installing from a local file") func (client *Client) doSnapAction(actionName string, snapName string, options *SnapOptions) (changeID string, err error) { diff -Nru snapd-2.27.5/client/snap_op_test.go snapd-2.28.5/client/snap_op_test.go --- snapd-2.27.5/client/snap_op_test.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/client/snap_op_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -46,6 +46,7 @@ {(*client.Client).Revert, "revert"}, {(*client.Client).Enable, "enable"}, {(*client.Client).Disable, "disable"}, + {(*client.Client).Switch, "switch"}, } var multiOps = []struct { diff -Nru snapd-2.27.5/cmd/cmd.go snapd-2.28.5/cmd/cmd.go --- snapd-2.27.5/cmd/cmd.go 2017-08-24 06:52:40.000000000 +0000 +++ snapd-2.28.5/cmd/cmd.go 2017-10-10 16:16:09.000000000 +0000 @@ -20,8 +20,8 @@ package cmd import ( - "fmt" "io/ioutil" + "log" "os" "path/filepath" "regexp" @@ -66,8 +66,7 @@ if !release.OnClassic { return false } - switch release.ReleaseInfo.ID { - case "fedora", "centos", "rhel", "opensuse", "suse", "poky", "arch": + if !release.DistroLike("debian", "ubuntu") { logger.Debugf("re-exec not supported on distro %q yet", release.ReleaseInfo.ID) return false } @@ -116,12 +115,6 @@ func InternalToolPath(tool string) string { distroTool := filepath.Join(dirs.DistroLibExecDir, tool) - // mostly useful for tests - if !osutil.GetenvBool(reExecKey, true) { - logger.Debugf("re-exec disabled by user") - return distroTool - } - // find the internal path relative to the running snapd, this // ensure we don't rely on the state of the system (like // having a valid "current" symlink). @@ -132,8 +125,8 @@ } // ensure we never use this helper from anything but - if !strings.HasSuffix(exe, "/snapd") { - panic("InternalToolPath can only be used from snapd") + if !strings.HasSuffix(exe, "/snapd") && !strings.HasSuffix(exe, ".test") { + log.Panicf("InternalToolPath can only be used from snapd, got: %s", exe) } if !strings.HasPrefix(exe, dirs.SnapMountDir) { @@ -146,10 +139,11 @@ return filepath.Join(filepath.Dir(exe), tool) } -// mustUnsetenv will os.Unsetenv the for or panic if it cannot do that +// mustUnsetenv will unset the given environment key or panic if it +// cannot do that func mustUnsetenv(key string) { if err := os.Unsetenv(key); err != nil { - panic(fmt.Sprintf("cannot unset %s: %s", key, err)) + log.Panicf("cannot unset %s: %s", key, err) } } @@ -159,6 +153,7 @@ // Which executable are we? exe, err := os.Readlink(selfExe) if err != nil { + logger.Noticef("cannot read /proc/self/exe: %v", err) return } @@ -180,8 +175,7 @@ } // Did we already re-exec? - if osutil.GetenvBool("SNAP_DID_REEXEC") { - mustUnsetenv("SNAP_DID_REEXEC") + if strings.HasPrefix(exe, dirs.SnapMountDir) { return } @@ -207,6 +201,7 @@ } 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)) } diff -Nru snapd-2.27.5/cmd/cmd_test.go snapd-2.28.5/cmd/cmd_test.go --- snapd-2.27.5/cmd/cmd_test.go 2017-08-24 06:52:40.000000000 +0000 +++ snapd-2.28.5/cmd/cmd_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -217,7 +217,12 @@ } func (s *cmdSuite) TestInternalToolPathFromIncorrectHelper(c *C) { - c.Check(func() { cmd.InternalToolPath("potato") }, PanicMatches, "InternalToolPath can only be used from snapd") + restore := cmd.MockOsReadlink(func(string) (string, error) { + return "/usr/bin/potato", nil + }) + defer restore() + + c.Check(func() { cmd.InternalToolPath("potato") }, PanicMatches, "InternalToolPath can only be used from snapd, got: /usr/bin/potato") } func (s *cmdSuite) TestExecInCoreSnap(c *C) { @@ -281,10 +286,9 @@ } func (s *cmdSuite) TestExecInCoreSnapNoDouble(c *C) { - defer s.mockReExecFor(c, s.newCore, "potato")() - - 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.ExecInCoreSnap() c.Check(s.execCalled, Equals, 0) diff -Nru snapd-2.27.5/cmd/libsnap-confine-private/snap.c snapd-2.28.5/cmd/libsnap-confine-private/snap.c --- snapd-2.27.5/cmd/libsnap-confine-private/snap.c 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/cmd/libsnap-confine-private/snap.c 2017-10-13 20:09:56.000000000 +0000 @@ -30,7 +30,7 @@ bool verify_security_tag(const char *security_tag, const char *snap_name) { const char *whitelist_re = - "^snap\\.([a-z](-?[a-z0-9])*)\\.([a-zA-Z0-9](-?[a-zA-Z0-9])*|hook\\.[a-z](-?[a-z])*)$"; + "^snap\\.([a-z0-9](-?[a-z0-9])*)\\.([a-zA-Z0-9](-?[a-zA-Z0-9])*|hook\\.[a-z](-?[a-z])*)$"; regex_t re; if (regcomp(&re, whitelist_re, REG_EXTENDED) != 0) die("can not compile regex %s", whitelist_re); diff -Nru snapd-2.27.5/cmd/libsnap-confine-private/snap-test.c snapd-2.28.5/cmd/libsnap-confine-private/snap-test.c --- snapd-2.27.5/cmd/libsnap-confine-private/snap-test.c 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/cmd/libsnap-confine-private/snap-test.c 2017-10-13 20:09:56.000000000 +0000 @@ -54,7 +54,8 @@ g_assert_false(verify_security_tag("snap.n@me.app", "n@me")); g_assert_false(verify_security_tag("SNAP.name.app", "name")); g_assert_false(verify_security_tag("snap.Name.app", "Name")); - g_assert_false(verify_security_tag("snap.0name.app", "0name")); + // This used to be false but it's now allowed. + g_assert_true(verify_security_tag("snap.0name.app", "0name")); g_assert_false(verify_security_tag("snap.-name.app", "-name")); g_assert_false(verify_security_tag("snap.name.@app", "name")); g_assert_false(verify_security_tag(".name.app", "name")); @@ -67,6 +68,13 @@ g_assert_false(verify_security_tag("snap.foo.hook.bar", "fooo")); g_assert_false(verify_security_tag("snap.foo.hook.bar", "snap")); g_assert_false(verify_security_tag("snap.foo.hook.bar", "bar")); + + // Regression test 12to8 + g_assert_true(verify_security_tag("snap.12to8.128to8", "12to8")); + g_assert_true(verify_security_tag("snap.123test.123test", "123test")); + g_assert_true(verify_security_tag + ("snap.123test.hook.configure", "123test")); + } static void test_sc_snap_name_validate() @@ -166,6 +174,12 @@ (err, SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME)); sc_error_free(err); } + // Regression test: 12to8 and 123test + sc_snap_name_validate("12to8", &err); + g_assert_null(err); + sc_snap_name_validate("123test", &err); + g_assert_null(err); + } static void test_sc_snap_name_validate__respects_error_protocol() diff -Nru snapd-2.27.5/cmd/Makefile.am snapd-2.28.5/cmd/Makefile.am --- snapd-2.27.5/cmd/Makefile.am 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/cmd/Makefile.am 2017-09-13 14:47:18.000000000 +0000 @@ -43,10 +43,15 @@ # The hack target helps devlopers work on snap-confine on their live system by # installing a fresh copy of snap confine and the appropriate apparmor profile. .PHONY: hack -hack: snap-confine/snap-confine snap-confine/snap-confine.apparmor +hack: snap-confine/snap-confine snap-confine/snap-confine.apparmor snap-update-ns/snap-update-ns sudo install -D -m 4755 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 sudo apparmor_parser -r snap-confine/snap-confine.apparmor + sudo install -m 755 snap-update-ns/snap-update-ns $(DESTDIR)$(libexecdir)/snap-update-ns + +# for the hack target also: +snap-update-ns/snap-update-ns: snap-update-ns/*.go snap-update-ns/*.[ch] + cd snap-update-ns && GOPATH=$(or $(GOPATH),$(realpath $(srcdir)/../../../../..)) go build -i -v ## ## libsnap-confine-private.a @@ -179,7 +184,7 @@ snap-confine/user-support.c \ snap-confine/user-support.h -snap_confine_snap_confine_CFLAGS = -Wall -Werror $(AM_CFLAGS) +snap_confine_snap_confine_CFLAGS = -Wall -Werror $(AM_CFLAGS) -DLIBEXECDIR=\"$(libexecdir)\" snap_confine_snap_confine_LDFLAGS = $(AM_LDFLAGS) snap_confine_snap_confine_LDADD = libsnap-confine-private.a snap_confine_snap_confine_CFLAGS += $(LIBUDEV_CFLAGS) diff -Nru snapd-2.27.5/cmd/snap/cmd_auto_import_test.go snapd-2.28.5/cmd/snap/cmd_auto_import_test.go --- snapd-2.27.5/cmd/snap/cmd_auto_import_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/cmd/snap/cmd_auto_import_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -187,9 +187,6 @@ restore := release.MockOnClassic(false) defer restore() - dirs.SetRootDir(c.MkDir()) - defer dirs.SetRootDir("") - l, err := logger.New(s.stderr, 0) c.Assert(err, IsNil) logger.SetLogger(l) @@ -257,9 +254,6 @@ }) - dirs.SetRootDir(c.MkDir()) - defer dirs.SetRootDir("") - fakeAssertsFn := filepath.Join(dirs.SnapAssertsSpoolDir, "1234343") err := os.MkdirAll(filepath.Dir(fakeAssertsFn), 0755) c.Assert(err, IsNil) @@ -287,9 +281,6 @@ restore := release.MockOnClassic(false) defer restore() - dirs.SetRootDir(c.MkDir()) - defer dirs.SetRootDir("") - l, err := logger.New(s.stderr, 0) c.Assert(err, IsNil) logger.SetLogger(l) diff -Nru snapd-2.27.5/cmd/snap/cmd_connect_test.go snapd-2.28.5/cmd/snap/cmd_connect_test.go --- snapd-2.27.5/cmd/snap/cmd_connect_test.go 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/cmd/snap/cmd_connect_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -196,7 +196,7 @@ c.Assert(rest, DeepEquals, []string{}) } -var fortestingInterfaceList = client.Interfaces{ +var fortestingConnectionList = client.Connections{ Slots: []client.Slot{ { Snap: "core", @@ -284,7 +284,7 @@ c.Assert(r.Method, Equals, "GET") EncodeResponseBody(c, w, map[string]interface{}{ "type": "sync", - "result": fortestingInterfaceList, + "result": fortestingConnectionList, }) default: c.Fatalf("unexpected path %q", r.URL.Path) diff -Nru snapd-2.27.5/cmd/snap/cmd_disconnect_test.go snapd-2.28.5/cmd/snap/cmd_disconnect_test.go --- snapd-2.27.5/cmd/snap/cmd_disconnect_test.go 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/cmd/snap/cmd_disconnect_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -180,7 +180,7 @@ c.Assert(r.Method, Equals, "GET") EncodeResponseBody(c, w, map[string]interface{}{ "type": "sync", - "result": fortestingInterfaceList, + "result": fortestingConnectionList, }) default: c.Fatalf("unexpected path %q", r.URL.Path) diff -Nru snapd-2.27.5/cmd/snap/cmd_export_key_test.go snapd-2.28.5/cmd/snap/cmd_export_key_test.go --- snapd-2.27.5/cmd/snap/cmd_export_key_test.go 2016-09-09 06:45:37.000000000 +0000 +++ snapd-2.28.5/cmd/snap/cmd_export_key_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -58,9 +58,7 @@ } func (s *SnapKeysSuite) TestExportKeyAccount(c *C) { - rootPrivKey, _ := assertstest.GenerateKey(1024) - storePrivKey, _ := assertstest.GenerateKey(752) - storeSigning := assertstest.NewStoreStack("canonical", rootPrivKey, storePrivKey) + storeSigning := assertstest.NewStoreStack("canonical", nil) manager := asserts.NewGPGKeypairManager() assertstest.NewAccount(storeSigning, "developer1", nil, "") rest, err := snap.Parser().ParseArgs([]string{"export-key", "another", "--account=developer1"}) diff -Nru snapd-2.27.5/cmd/snap/cmd_info.go snapd-2.28.5/cmd/snap/cmd_info.go --- snapd-2.27.5/cmd/snap/cmd_info.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/cmd/snap/cmd_info.go 2017-09-13 14:47:18.000000000 +0000 @@ -27,6 +27,8 @@ "strings" "text/tabwriter" + "gopkg.in/yaml.v2" + "github.com/jessevdk/go-flags" "github.com/snapcore/snapd/asserts" @@ -121,7 +123,7 @@ } fmt.Fprintf(w, "path:\t%q\n", path) fmt.Fprintf(w, "name:\t%s\n", info.Name()) - fmt.Fprintf(w, "summary:\t%q\n", info.Summary()) + fmt.Fprintf(w, "summary:\t%s\n", formatSummary(info.Summary())) var notes *Notes if verbose { @@ -158,12 +160,13 @@ // in a user friendly way. // // The rules are (intentionally) very simple: +// - trim whitespace // - word wrap at "max" chars // - keep \n intact and break here // - ignore \r func formatDescr(descr string, max int) string { out := bytes.NewBuffer(nil) - for _, line := range strings.Split(descr, "\n") { + for _, line := range strings.Split(strings.TrimSpace(descr), "\n") { if len(line) > max { for _, chunk := range strutil.WordWrap(line, max) { fmt.Fprintf(out, " %s\n", chunk) @@ -183,7 +186,7 @@ commands := make([]string, 0, len(allApps)) for _, app := range allApps { - if app.Daemon != "" { + if app.IsService() { continue } @@ -200,13 +203,47 @@ } } +func maybePrintServices(w io.Writer, snapName string, allApps []client.AppInfo, n int) { + if len(allApps) == 0 { + return + } + + services := make([]string, 0, len(allApps)) + for _, app := range allApps { + if !app.IsService() { + continue + } + + var active, enabled string + if app.Active { + active = "active" + } else { + active = "inactive" + } + if app.Enabled { + enabled = "enabled" + } else { + enabled = "disabled" + } + services = append(services, fmt.Sprintf(" %s:\t%s, %s, %s", snap.JoinSnapApp(snapName, app.Name), app.Daemon, enabled, active)) + } + if len(services) == 0 { + return + } + + fmt.Fprintf(w, "services:\n") + for _, svc := range services { + fmt.Fprintln(w, svc) + } +} + // displayChannels displays channels and tracks in the right order func displayChannels(w io.Writer, remote *client.Snap) { // \t\t\t so we get "installed" lined up with "channels" fmt.Fprintf(w, "channels:\t\t\t\n") // order by tracks - for i, tr := range remote.Tracks { + for _, tr := range remote.Tracks { trackHasOpenChannel := false for _, risk := range []string{"stable", "candidate", "beta", "edge"} { chName := fmt.Sprintf("%s/%s", tr, risk) @@ -230,13 +267,17 @@ } fmt.Fprintf(w, " %s:\t%s\t%s\t%s\t%s\n", chName, version, revision, size, notes) } - // add separator between tracks - if i < len(remote.Tracks)-1 { - fmt.Fprintf(w, " \t\t\t\t\n") - } } } +func formatSummary(raw string) string { + s, err := yaml.Marshal(raw) + if err != nil { + return fmt.Sprintf("cannot marshal summary: %s", err) + } + return strings.TrimSpace(string(s)) +} + func (x *infoCmd) Execute([]string) error { cli := Client() @@ -265,7 +306,7 @@ noneOK = false fmt.Fprintf(w, "name:\t%s\n", both.Name) - fmt.Fprintf(w, "summary:\t%q\n", both.Summary) + fmt.Fprintf(w, "summary:\t%s\n", formatSummary(both.Summary)) // TODO: have publisher; use publisher here, // and additionally print developer if publisher != developer fmt.Fprintf(w, "publisher:\t%s\n", both.Developer) @@ -279,6 +320,7 @@ maybePrintType(w, both.Type) maybePrintID(w, both) maybePrintCommands(w, snapName, both.Apps, termWidth) + maybePrintServices(w, snapName, both.Apps, termWidth) if x.Verbose { fmt.Fprintln(w, "notes:\t") diff -Nru snapd-2.27.5/cmd/snap/cmd_info_test.go snapd-2.28.5/cmd/snap/cmd_info_test.go --- snapd-2.27.5/cmd/snap/cmd_info_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/cmd/snap/cmd_info_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,14 +20,76 @@ package main_test import ( + "bytes" "fmt" "net/http" "gopkg.in/check.v1" + "github.com/snapcore/snapd/client" snap "github.com/snapcore/snapd/cmd/snap" ) +var cmdAppInfos = []client.AppInfo{{Name: "app1"}, {Name: "app2"}} +var svcAppInfos = []client.AppInfo{ + { + Name: "svc1", + Daemon: "simple", + Enabled: false, + Active: true, + }, + { + Name: "svc2", + Daemon: "simple", + Enabled: true, + Active: false, + }, +} + +var mixedAppInfos = append(append([]client.AppInfo(nil), cmdAppInfos...), svcAppInfos...) + +func (s *SnapSuite) TestMaybePrintServices(c *check.C) { + for _, infos := range [][]client.AppInfo{svcAppInfos, mixedAppInfos} { + var buf bytes.Buffer + snap.MaybePrintServices(&buf, "foo", infos, -1) + + c.Check(buf.String(), check.Equals, `services: + foo.svc1: simple, disabled, active + foo.svc2: simple, enabled, inactive +`) + } +} + +func (s *SnapSuite) TestMaybePrintServicesNoServices(c *check.C) { + for _, infos := range [][]client.AppInfo{cmdAppInfos, nil} { + var buf bytes.Buffer + snap.MaybePrintServices(&buf, "foo", infos, -1) + + c.Check(buf.String(), check.Equals, "") + } +} + +func (s *SnapSuite) TestMaybePrintCommands(c *check.C) { + for _, infos := range [][]client.AppInfo{cmdAppInfos, mixedAppInfos} { + var buf bytes.Buffer + snap.MaybePrintCommands(&buf, "foo", infos, -1) + + c.Check(buf.String(), check.Equals, `commands: + - foo.app1 + - foo.app2 +`) + } +} + +func (s *SnapSuite) TestMaybePrintCommandsNoCommands(c *check.C) { + for _, infos := range [][]client.AppInfo{svcAppInfos, nil} { + var buf bytes.Buffer + snap.MaybePrintCommands(&buf, "foo", infos, -1) + + c.Check(buf.String(), check.Equals, "") + } +} + func (s *SnapSuite) TestInfoPriced(c *check.C) { n := 0 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { @@ -49,13 +111,76 @@ rest, err := snap.Parser().ParseArgs([]string{"info", "hello"}) c.Assert(err, check.IsNil) c.Assert(rest, check.DeepEquals, []string{}) - c.Check(s.Stdout(), check.Matches, `name: hello -summary: "GNU Hello, the \"hello world\" snap" + c.Check(s.Stdout(), check.Equals, `name: hello +summary: GNU Hello, the "hello world" snap publisher: canonical price: 1.99GBP description: | GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/ +snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 +`) + c.Check(s.Stderr(), check.Equals, "") +} + +const mockInfoJSON = ` +{ + "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", + "download-size": 65536, + "icon": "", + "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6", + "name": "hello", + "private": false, + "resource": "/v2/snaps/hello", + "revision": "1", + "status": "available", + "summary": "The GNU Hello snap", + "type": "app", + "version": "2.10" + } + ], + "sources": [ + "store" + ], + "suggested-currency": "GBP" +} +` + +func (s *SnapSuite) TestInfoUnquoted(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, "{}") + 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 +description: | + GNU hello prints a friendly greeting. This is part of the snapcraft tour at + https://snapcraft.io/ snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6 `) c.Check(s.Stderr(), check.Equals, "") diff -Nru snapd-2.27.5/cmd/snap/cmd_interface.go snapd-2.28.5/cmd/snap/cmd_interface.go --- snapd-2.27.5/cmd/snap/cmd_interface.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/cmd/snap/cmd_interface.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,184 @@ +// -*- 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" + "sort" + "text/tabwriter" + + "github.com/snapcore/snapd/client" + "github.com/snapcore/snapd/i18n" + + "github.com/jessevdk/go-flags" +) + +type cmdInterface struct { + ShowAttrs bool `long:"attrs"` + ShowAll bool `long:"all"` + Positionals struct { + Interface interfaceName `skip-help:"true"` + } `positional-args:"true"` +} + +var shortInterfaceHelp = i18n.G("Lists snap interfaces") +var longInterfaceHelp = i18n.G(` +The interface command shows details of snap interfaces. + +If no interface name is provided, a list of interface names with at least +one connection is shown, or a list of all interfaces if --all is provided. +`) + +func init() { + addCommand("interface", shortInterfaceHelp, longInterfaceHelp, func() flags.Commander { + return &cmdInterface{} + }, map[string]string{ + "attrs": i18n.G("Show interface attributes"), + "all": i18n.G("Include unused interfaces"), + }, []argDesc{{ + name: i18n.G(""), + desc: i18n.G("Show details of a specific interface"), + }}) +} + +func (x *cmdInterface) Execute(args []string) error { + if len(args) > 0 { + return ErrExtraArgs + } + + if x.Positionals.Interface != "" { + // Show one interface in detail. + name := string(x.Positionals.Interface) + ifaces, err := Client().Interfaces(&client.InterfaceOptions{ + Names: []string{name}, + Doc: true, + Plugs: true, + Slots: true, + }) + if err != nil { + return err + } + if len(ifaces) == 0 { + return fmt.Errorf(i18n.G("no such interface")) + } + x.showOneInterface(ifaces[0]) + } else { + // Show an overview of available interfaces. + ifaces, err := Client().Interfaces(&client.InterfaceOptions{ + Connected: !x.ShowAll, + }) + if err != nil { + return err + } + if len(ifaces) == 0 { + if x.ShowAll { + return fmt.Errorf(i18n.G("no interfaces found")) + } + return fmt.Errorf(i18n.G("no interfaces currently connected")) + } + x.showManyInterfaces(ifaces) + } + return nil +} + +func (x *cmdInterface) showOneInterface(iface *client.Interface) { + w := tabwriter.NewWriter(Stdout, 2, 2, 1, ' ', 0) + defer w.Flush() + + fmt.Fprintf(w, "name:\t%s\n", iface.Name) + if iface.Summary != "" { + fmt.Fprintf(w, "summary:\t%s\n", iface.Summary) + } + if iface.DocURL != "" { + fmt.Fprintf(w, "documentation:\t%s\n", iface.DocURL) + } + if len(iface.Plugs) > 0 { + fmt.Fprintf(w, "plugs:\n") + for _, plug := range iface.Plugs { + var labelPart string + if plug.Label != "" { + labelPart = fmt.Sprintf(" (%s)", plug.Label) + } + if plug.Name == iface.Name { + fmt.Fprintf(w, " - %s%s", plug.Snap, labelPart) + } else { + fmt.Fprintf(w, ` - %s:%s%s`, plug.Snap, plug.Name, labelPart) + } + // Print a colon which will make the snap:plug element a key-value + // yaml object so that we can write the attributes. + if len(plug.Attrs) > 0 && x.ShowAttrs { + fmt.Fprintf(w, ":\n") + x.showAttrs(w, plug.Attrs, " ") + } else { + fmt.Fprintf(w, "\n") + } + } + } + if len(iface.Slots) > 0 { + fmt.Fprintf(w, "slots:\n") + for _, slot := range iface.Slots { + var labelPart string + if slot.Label != "" { + labelPart = fmt.Sprintf(" (%s)", slot.Label) + } + if slot.Name == iface.Name { + fmt.Fprintf(w, " - %s%s", slot.Snap, labelPart) + } else { + fmt.Fprintf(w, ` - %s:%s%s`, slot.Snap, slot.Name, labelPart) + } + // Print a colon which will make the snap:slot element a key-value + // yaml object so that we can write the attributes. + if len(slot.Attrs) > 0 && x.ShowAttrs { + fmt.Fprintf(w, ":\n") + x.showAttrs(w, slot.Attrs, " ") + } else { + fmt.Fprintf(w, "\n") + } + } + } +} + +func (x *cmdInterface) showManyInterfaces(infos []*client.Interface) { + w := tabWriter() + defer w.Flush() + fmt.Fprintln(w, i18n.G("Name\tSummary")) + for _, iface := range infos { + fmt.Fprintf(w, "%s\t%s\n", iface.Name, iface.Summary) + } +} + +func (x *cmdInterface) showAttrs(w io.Writer, attrs map[string]interface{}, indent string) { + if len(attrs) == 0 { + return + } + names := make([]string, 0, len(attrs)) + for name := range attrs { + names = append(names, name) + } + sort.Strings(names) + for _, name := range names { + value := attrs[name] + switch value.(type) { + case string, int, bool: + fmt.Fprintf(w, "%s %s:\t%v\n", indent, name, value) + } + } +} diff -Nru snapd-2.27.5/cmd/snap/cmd_interfaces.go snapd-2.28.5/cmd/snap/cmd_interfaces.go --- snapd-2.27.5/cmd/snap/cmd_interfaces.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/cmd/snap/cmd_interfaces.go 2017-09-13 14:47:18.000000000 +0000 @@ -69,7 +69,7 @@ return ErrExtraArgs } - ifaces, err := Client().Interfaces() + ifaces, err := Client().Connections() if err != nil { return err } diff -Nru snapd-2.27.5/cmd/snap/cmd_interfaces_test.go snapd-2.28.5/cmd/snap/cmd_interfaces_test.go --- snapd-2.27.5/cmd/snap/cmd_interfaces_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/cmd/snap/cmd_interfaces_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -31,7 +31,7 @@ . "github.com/snapcore/snapd/cmd/snap" ) -func (s *SnapSuite) TestInterfacesZeroSlotsOnePlug(c *C) { +func (s *SnapSuite) TestConnectionsZeroSlotsOnePlug(c *C) { s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { c.Check(r.Method, Equals, "GET") c.Check(r.URL.Path, Equals, "/v2/interfaces") @@ -40,7 +40,7 @@ c.Check(body, DeepEquals, []byte{}) EncodeResponseBody(c, w, map[string]interface{}{ "type": "sync", - "result": client.Interfaces{ + "result": client.Connections{ Plugs: []client.Plug{ { Snap: "keyboard-lights", @@ -60,7 +60,7 @@ c.Assert(s.Stderr(), Equals, "") } -func (s *SnapSuite) TestInterfacesZeroPlugsOneSlot(c *C) { +func (s *SnapSuite) TestConnectionsZeroPlugsOneSlot(c *C) { s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { c.Check(r.Method, Equals, "GET") c.Check(r.URL.Path, Equals, "/v2/interfaces") @@ -69,7 +69,7 @@ c.Check(body, DeepEquals, []byte{}) EncodeResponseBody(c, w, map[string]interface{}{ "type": "sync", - "result": client.Interfaces{ + "result": client.Connections{ Slots: []client.Slot{ { Snap: "canonical-pi2", @@ -91,7 +91,7 @@ c.Assert(s.Stderr(), Equals, "") } -func (s *SnapSuite) TestInterfacesOneSlotOnePlug(c *C) { +func (s *SnapSuite) TestConnectionsOneSlotOnePlug(c *C) { s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { c.Check(r.Method, Equals, "GET") c.Check(r.URL.Path, Equals, "/v2/interfaces") @@ -100,7 +100,7 @@ c.Check(body, DeepEquals, []byte{}) EncodeResponseBody(c, w, map[string]interface{}{ "type": "sync", - "result": client.Interfaces{ + "result": client.Connections{ Slots: []client.Slot{ { Snap: "canonical-pi2", @@ -158,7 +158,7 @@ c.Assert(s.Stderr(), Equals, "") } -func (s *SnapSuite) TestInterfacesTwoPlugs(c *C) { +func (s *SnapSuite) TestConnectionsTwoPlugs(c *C) { s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { c.Check(r.Method, Equals, "GET") c.Check(r.URL.Path, Equals, "/v2/interfaces") @@ -167,7 +167,7 @@ c.Check(body, DeepEquals, []byte{}) EncodeResponseBody(c, w, map[string]interface{}{ "type": "sync", - "result": client.Interfaces{ + "result": client.Connections{ Slots: []client.Slot{ { Snap: "canonical-pi2", @@ -199,7 +199,7 @@ c.Assert(s.Stderr(), Equals, "") } -func (s *SnapSuite) TestInterfacesPlugsWithCommonName(c *C) { +func (s *SnapSuite) TestConnectionsPlugsWithCommonName(c *C) { s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { c.Check(r.Method, Equals, "GET") c.Check(r.URL.Path, Equals, "/v2/interfaces") @@ -208,7 +208,7 @@ c.Check(body, DeepEquals, []byte{}) EncodeResponseBody(c, w, map[string]interface{}{ "type": "sync", - "result": client.Interfaces{ + "result": client.Connections{ Slots: []client.Slot{ { Snap: "canonical-pi2", @@ -266,7 +266,7 @@ c.Assert(s.Stderr(), Equals, "") } -func (s *SnapSuite) TestInterfacesOsSnapSlots(c *C) { +func (s *SnapSuite) TestConnectionsOsSnapSlots(c *C) { s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { c.Check(r.Method, Equals, "GET") c.Check(r.URL.Path, Equals, "/v2/interfaces") @@ -275,7 +275,7 @@ c.Check(body, DeepEquals, []byte{}) EncodeResponseBody(c, w, map[string]interface{}{ "type": "sync", - "result": client.Interfaces{ + "result": client.Connections{ Slots: []client.Slot{ { Snap: "core", @@ -333,7 +333,7 @@ c.Assert(s.Stderr(), Equals, "") } -func (s *SnapSuite) TestInterfacesTwoSlotsAndFiltering(c *C) { +func (s *SnapSuite) TestConnectionsTwoSlotsAndFiltering(c *C) { s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { c.Check(r.Method, Equals, "GET") c.Check(r.URL.Path, Equals, "/v2/interfaces") @@ -342,7 +342,7 @@ c.Check(body, DeepEquals, []byte{}) EncodeResponseBody(c, w, map[string]interface{}{ "type": "sync", - "result": client.Interfaces{ + "result": client.Connections{ Slots: []client.Slot{ { Snap: "canonical-pi2", @@ -382,7 +382,7 @@ c.Assert(s.Stderr(), Equals, "") } -func (s *SnapSuite) TestInterfacesOfSpecificSnap(c *C) { +func (s *SnapSuite) TestConnectionsOfSpecificSnap(c *C) { s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { c.Check(r.Method, Equals, "GET") c.Check(r.URL.Path, Equals, "/v2/interfaces") @@ -391,7 +391,7 @@ c.Check(body, DeepEquals, []byte{}) EncodeResponseBody(c, w, map[string]interface{}{ "type": "sync", - "result": client.Interfaces{ + "result": client.Connections{ Slots: []client.Slot{ { Snap: "cheese", @@ -426,7 +426,7 @@ c.Assert(s.Stderr(), Equals, "") } -func (s *SnapSuite) TestInterfacesOfSpecificSnapAndSlot(c *C) { +func (s *SnapSuite) TestConnectionsOfSpecificSnapAndSlot(c *C) { s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { c.Check(r.Method, Equals, "GET") c.Check(r.URL.Path, Equals, "/v2/interfaces") @@ -435,7 +435,7 @@ c.Check(body, DeepEquals, []byte{}) EncodeResponseBody(c, w, map[string]interface{}{ "type": "sync", - "result": client.Interfaces{ + "result": client.Connections{ Slots: []client.Slot{ { Snap: "cheese", @@ -469,7 +469,7 @@ c.Assert(s.Stderr(), Equals, "") } -func (s *SnapSuite) TestInterfacesNothingAtAll(c *C) { +func (s *SnapSuite) TestConnectionsNothingAtAll(c *C) { s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { c.Check(r.Method, Equals, "GET") c.Check(r.URL.Path, Equals, "/v2/interfaces") @@ -478,7 +478,7 @@ c.Check(body, DeepEquals, []byte{}) EncodeResponseBody(c, w, map[string]interface{}{ "type": "sync", - "result": client.Interfaces{}, + "result": client.Connections{}, }) }) rest, err := Parser().ParseArgs([]string{"interfaces"}) @@ -490,7 +490,7 @@ c.Assert(s.Stderr(), Equals, "") } -func (s *SnapSuite) TestInterfacesOfSpecificType(c *C) { +func (s *SnapSuite) TestConnectionsOfSpecificType(c *C) { s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { c.Check(r.Method, Equals, "GET") c.Check(r.URL.Path, Equals, "/v2/interfaces") @@ -499,7 +499,7 @@ c.Check(body, DeepEquals, []byte{}) EncodeResponseBody(c, w, map[string]interface{}{ "type": "sync", - "result": client.Interfaces{ + "result": client.Connections{ Slots: []client.Slot{ { Snap: "cheese", @@ -535,14 +535,14 @@ c.Assert(s.Stderr(), Equals, "") } -func (s *SnapSuite) TestInterfacesCompletion(c *C) { +func (s *SnapSuite) TestConnectionsCompletion(c *C) { s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/v2/interfaces": c.Assert(r.Method, Equals, "GET") EncodeResponseBody(c, w, map[string]interface{}{ "type": "sync", - "result": fortestingInterfaceList, + "result": fortestingConnectionList, }) default: c.Fatalf("unexpected path %q", r.URL.Path) diff -Nru snapd-2.27.5/cmd/snap/cmd_interface_test.go snapd-2.28.5/cmd/snap/cmd_interface_test.go --- snapd-2.27.5/cmd/snap/cmd_interface_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/cmd/snap/cmd_interface_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,293 @@ +// -*- 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_test + +import ( + "io/ioutil" + "net/http" + "os" + + "github.com/jessevdk/go-flags" + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/client" + . "github.com/snapcore/snapd/cmd/snap" +) + +func (s *SnapSuite) TestInterfaceHelp(c *C) { + msg := `Usage: + snap.test [OPTIONS] interface [interface-OPTIONS] [] + +The interface command shows details of snap interfaces. + +If no interface name is provided, a list of interface names with at least +one connection is shown, or a list of all interfaces if --all is provided. + +Application Options: + --version Print the version and exit + +Help Options: + -h, --help Show this help message + +[interface command options] + --attrs Show interface attributes + --all Include unused interfaces + +[interface command arguments] + : Show details of a specific interface +` + rest, err := Parser().ParseArgs([]string{"interface", "--help"}) + c.Assert(err.Error(), Equals, msg) + c.Assert(rest, DeepEquals, []string{}) +} + +func (s *SnapSuite) TestInterfaceListEmpty(c *C) { + s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { + c.Check(r.Method, Equals, "GET") + c.Check(r.URL.Path, Equals, "/v2/interfaces") + c.Check(r.URL.RawQuery, Equals, "select=connected") + body, err := ioutil.ReadAll(r.Body) + c.Check(err, IsNil) + c.Check(body, DeepEquals, []byte{}) + EncodeResponseBody(c, w, map[string]interface{}{ + "type": "sync", + "result": []*client.Interface{}, + }) + }) + rest, err := Parser().ParseArgs([]string{"interface"}) + c.Assert(err, ErrorMatches, "no interfaces currently connected") + c.Assert(rest, DeepEquals, []string{"interface"}) + c.Assert(s.Stdout(), Equals, "") + c.Assert(s.Stderr(), Equals, "") +} + +func (s *SnapSuite) TestInterfaceListAllEmpty(c *C) { + s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { + c.Check(r.Method, Equals, "GET") + c.Check(r.URL.Path, Equals, "/v2/interfaces") + c.Check(r.URL.RawQuery, Equals, "select=all") + body, err := ioutil.ReadAll(r.Body) + c.Check(err, IsNil) + c.Check(body, DeepEquals, []byte{}) + EncodeResponseBody(c, w, map[string]interface{}{ + "type": "sync", + "result": []*client.Interface{}, + }) + }) + rest, err := Parser().ParseArgs([]string{"interface", "--all"}) + c.Assert(err, ErrorMatches, "no interfaces found") + c.Assert(rest, DeepEquals, []string{"--all"}) // XXX: feels like a bug in go-flags. + c.Assert(s.Stdout(), Equals, "") + c.Assert(s.Stderr(), Equals, "") +} + +func (s *SnapSuite) TestInterfaceList(c *C) { + s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { + c.Check(r.Method, Equals, "GET") + c.Check(r.URL.Path, Equals, "/v2/interfaces") + c.Check(r.URL.RawQuery, Equals, "select=connected") + body, err := ioutil.ReadAll(r.Body) + c.Check(err, IsNil) + c.Check(body, DeepEquals, []byte{}) + EncodeResponseBody(c, w, map[string]interface{}{ + "type": "sync", + "result": []*client.Interface{{ + Name: "network", + Summary: "allows access to the network", + }, { + Name: "network-bind", + Summary: "allows providing services on the network", + }}, + }) + }) + rest, err := Parser().ParseArgs([]string{"interface"}) + c.Assert(err, IsNil) + c.Assert(rest, DeepEquals, []string{}) + expectedStdout := "" + + "Name Summary\n" + + "network allows access to the network\n" + + "network-bind allows providing services on the network\n" + c.Assert(s.Stdout(), Equals, expectedStdout) + c.Assert(s.Stderr(), Equals, "") +} + +func (s *SnapSuite) TestInterfaceListAll(c *C) { + s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { + c.Check(r.Method, Equals, "GET") + c.Check(r.URL.Path, Equals, "/v2/interfaces") + c.Check(r.URL.RawQuery, Equals, "select=all") + body, err := ioutil.ReadAll(r.Body) + c.Check(err, IsNil) + c.Check(body, DeepEquals, []byte{}) + EncodeResponseBody(c, w, map[string]interface{}{ + "type": "sync", + "result": []*client.Interface{{ + Name: "network", + Summary: "allows access to the network", + }, { + Name: "network-bind", + Summary: "allows providing services on the network", + }, { + Name: "unused", + Summary: "just an unused interface, nothing to see here", + }}, + }) + }) + rest, err := Parser().ParseArgs([]string{"interface", "--all"}) + c.Assert(err, IsNil) + c.Assert(rest, DeepEquals, []string{}) + expectedStdout := "" + + "Name Summary\n" + + "network allows access to the network\n" + + "network-bind allows providing services on the network\n" + + "unused just an unused interface, nothing to see here\n" + c.Assert(s.Stdout(), Equals, expectedStdout) + c.Assert(s.Stderr(), Equals, "") +} + +func (s *SnapSuite) TestInterfaceDetails(c *C) { + s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { + c.Check(r.Method, Equals, "GET") + c.Check(r.URL.Path, Equals, "/v2/interfaces") + c.Check(r.URL.RawQuery, Equals, "doc=true&names=network&plugs=true&select=all&slots=true") + body, err := ioutil.ReadAll(r.Body) + c.Check(err, IsNil) + c.Check(body, DeepEquals, []byte{}) + EncodeResponseBody(c, w, map[string]interface{}{ + "type": "sync", + "result": []*client.Interface{{ + Name: "network", + Summary: "allows access to the network", + DocURL: "http://example.org/about-the-network-interface", + Plugs: []client.Plug{ + {Snap: "deepin-music", Name: "network"}, + {Snap: "http", Name: "network"}, + }, + Slots: []client.Slot{{Snap: "core", Name: "network"}}, + }}, + }) + }) + rest, err := Parser().ParseArgs([]string{"interface", "network"}) + c.Assert(err, IsNil) + c.Assert(rest, DeepEquals, []string{}) + expectedStdout := "" + + "name: network\n" + + "summary: allows access to the network\n" + + "documentation: http://example.org/about-the-network-interface\n" + + "plugs:\n" + + " - deepin-music\n" + + " - http\n" + + "slots:\n" + + " - core\n" + c.Assert(s.Stdout(), Equals, expectedStdout) + c.Assert(s.Stderr(), Equals, "") +} + +func (s *SnapSuite) TestInterfaceDetailsAndAttrs(c *C) { + s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { + c.Check(r.Method, Equals, "GET") + c.Check(r.URL.Path, Equals, "/v2/interfaces") + c.Check(r.URL.RawQuery, Equals, "doc=true&names=serial-port&plugs=true&select=all&slots=true") + body, err := ioutil.ReadAll(r.Body) + c.Check(err, IsNil) + c.Check(body, DeepEquals, []byte{}) + EncodeResponseBody(c, w, map[string]interface{}{ + "type": "sync", + "result": []*client.Interface{{ + Name: "serial-port", + Summary: "allows providing or using a specific serial port", + Plugs: []client.Plug{ + {Snap: "minicom", Name: "serial-port"}, + }, + Slots: []client.Slot{{ + Snap: "gizmo-gadget", + Name: "debug-serial-port", + Label: "serial port for debugging", + Attrs: map[string]interface{}{ + "header": "pin-array", + "location": "internal", + "path": "/dev/ttyS0", + }, + }}, + }}, + }) + }) + rest, err := Parser().ParseArgs([]string{"interface", "--attrs", "serial-port"}) + c.Assert(err, IsNil) + c.Assert(rest, DeepEquals, []string{}) + expectedStdout := "" + + "name: serial-port\n" + + "summary: allows providing or using a specific serial port\n" + + "plugs:\n" + + " - minicom\n" + + "slots:\n" + + " - gizmo-gadget:debug-serial-port (serial port for debugging):\n" + + " header: pin-array\n" + + " location: internal\n" + + " path: /dev/ttyS0\n" + c.Assert(s.Stdout(), Equals, expectedStdout) + c.Assert(s.Stderr(), Equals, "") +} + +func (s *SnapSuite) TestInterfaceCompletion(c *C) { + s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { + c.Assert(r.Method, Equals, "GET") + c.Check(r.URL.Path, Equals, "/v2/interfaces") + c.Check(r.URL.RawQuery, Equals, "select=all") + EncodeResponseBody(c, w, map[string]interface{}{ + "type": "sync", + "result": []*client.Interface{{ + Name: "network", + Summary: "allows access to the network", + }, { + Name: "network-bind", + Summary: "allows providing services on the network", + }}, + }) + }) + os.Setenv("GO_FLAGS_COMPLETION", "verbose") + defer os.Unsetenv("GO_FLAGS_COMPLETION") + + expected := []flags.Completion{} + parser := Parser() + parser.CompletionHandler = func(obtained []flags.Completion) { + c.Check(obtained, DeepEquals, expected) + } + + expected = []flags.Completion{ + {Item: "network", Description: "allows access to the network"}, + {Item: "network-bind", Description: "allows providing services on the network"}, + } + _, err := parser.ParseArgs([]string{"interface", ""}) + c.Assert(err, IsNil) + + expected = []flags.Completion{ + {Item: "network-bind", Description: "allows providing services on the network"}, + } + _, err = parser.ParseArgs([]string{"interface", "network-"}) + c.Assert(err, IsNil) + + expected = []flags.Completion{} + _, err = parser.ParseArgs([]string{"interface", "bogus"}) + c.Assert(err, IsNil) + + c.Assert(s.Stdout(), Equals, "") + c.Assert(s.Stderr(), Equals, "") +} diff -Nru snapd-2.27.5/cmd/snap/cmd_known.go snapd-2.28.5/cmd/snap/cmd_known.go --- snapd-2.27.5/cmd/snap/cmd_known.go 2016-12-08 15:14:07.000000000 +0000 +++ snapd-2.28.5/cmd/snap/cmd_known.go 2017-09-13 14:47:18.000000000 +0000 @@ -34,8 +34,8 @@ type cmdKnown struct { KnownOptions struct { // XXX: how to get a list of assert types for completion? - AssertTypeName string `required:"true"` - HeaderFilters []string `required:"0"` + AssertTypeName assertTypeName `required:"true"` + HeaderFilters []string `required:"0"` } `positional-args:"true" required:"true"` Remote bool `long:"remote"` @@ -112,9 +112,9 @@ var assertions []asserts.Assertion var err error if x.Remote { - assertions, err = downloadAssertion(x.KnownOptions.AssertTypeName, headers) + assertions, err = downloadAssertion(string(x.KnownOptions.AssertTypeName), headers) } else { - assertions, err = Client().Known(x.KnownOptions.AssertTypeName, headers) + assertions, err = Client().Known(string(x.KnownOptions.AssertTypeName), headers) } if err != nil { return err diff -Nru snapd-2.27.5/cmd/snap/cmd_known_test.go snapd-2.28.5/cmd/snap/cmd_known_test.go --- snapd-2.27.5/cmd/snap/cmd_known_test.go 2016-09-28 09:07:27.000000000 +0000 +++ snapd-2.28.5/cmd/snap/cmd_known_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -25,6 +25,7 @@ "net/http/httptest" "net/url" + "github.com/jessevdk/go-flags" "gopkg.in/check.v1" "github.com/snapcore/snapd/overlord/auth" @@ -88,3 +89,21 @@ _, err := snap.Parser().ParseArgs([]string{"known", "--remote", "model", "series=16", "brand-id=canonical"}) c.Assert(err, check.ErrorMatches, `missing primary header "model" to query remote assertion`) } + +func (s *SnapSuite) TestAssertTypeNameCompletion(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/assertions") + fmt.Fprintln(w, `{"type": "sync", "result": { "types": [ "account", "... more stuff ...", "validation" ] } }`) + default: + c.Fatalf("expected to get 1 requests, now on %d", n+1) + } + + n++ + }) + + c.Check(snap.AssertTypeNameCompletion("v"), check.DeepEquals, []flags.Completion{{Item: "validation"}}) +} diff -Nru snapd-2.27.5/cmd/snap/cmd_run.go snapd-2.28.5/cmd/snap/cmd_run.go --- snapd-2.27.5/cmd/snap/cmd_run.go 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/cmd/snap/cmd_run.go 2017-09-13 14:47:18.000000000 +0000 @@ -395,6 +395,9 @@ if info.NeedsClassic() { cmd = append(cmd, "--classic") } + if info.Base != "" { + cmd = append(cmd, "--base", info.Base) + } cmd = append(cmd, securityTag) cmd = append(cmd, filepath.Join(dirs.CoreLibExecDir, "snap-exec")) diff -Nru snapd-2.27.5/cmd/snap/cmd_run_test.go snapd-2.28.5/cmd/snap/cmd_run_test.go --- snapd-2.27.5/cmd/snap/cmd_run_test.go 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/cmd/snap/cmd_run_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -65,8 +65,6 @@ } func (s *SnapSuite) TestSnapRunWhenMissingConfine(c *check.C) { - dirs.SetRootDir(c.MkDir()) - // mock installed snap si := snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{ Revision: snap.R("x2"), @@ -95,11 +93,9 @@ } func (s *SnapSuite) TestSnapRunAppIntegration(c *check.C) { - // mock installed snap - dirs.SetRootDir(c.MkDir()) - defer func() { dirs.SetRootDir("/") }() defer mockSnapConfine(dirs.DistroLibExecDir)() + // mock installed snap si := snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{ Revision: snap.R("x2"), }) @@ -132,11 +128,9 @@ } func (s *SnapSuite) TestSnapRunClassicAppIntegration(c *check.C) { - // mock installed snap - dirs.SetRootDir(c.MkDir()) - defer func() { dirs.SetRootDir("/") }() defer mockSnapConfine(dirs.DistroLibExecDir)() + // mock installed snap si := snaptest.MockSnap(c, string(mockYaml)+"confinement: classic\n", string(mockContents), &snap.SideInfo{ Revision: snap.R("x2"), }) @@ -169,11 +163,9 @@ } func (s *SnapSuite) TestSnapRunAppWithCommandIntegration(c *check.C) { - // mock installed snap - dirs.SetRootDir(c.MkDir()) - defer func() { dirs.SetRootDir("/") }() defer mockSnapConfine(dirs.DistroLibExecDir)() + // mock installed snap si := snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{ Revision: snap.R(42), }) @@ -222,11 +214,9 @@ } func (s *SnapSuite) TestSnapRunHookIntegration(c *check.C) { - // mock installed snap - dirs.SetRootDir(c.MkDir()) - defer func() { dirs.SetRootDir("/") }() defer mockSnapConfine(dirs.DistroLibExecDir)() + // mock installed snap si := snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{ Revision: snap.R(42), }) @@ -258,11 +248,9 @@ } func (s *SnapSuite) TestSnapRunHookUnsetRevisionIntegration(c *check.C) { - // mock installed snap - dirs.SetRootDir(c.MkDir()) - defer func() { dirs.SetRootDir("/") }() defer mockSnapConfine(dirs.DistroLibExecDir)() + // mock installed snap si := snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{ Revision: snap.R(42), }) @@ -294,11 +282,9 @@ } func (s *SnapSuite) TestSnapRunHookSpecificRevisionIntegration(c *check.C) { - // mock installed snap - dirs.SetRootDir(c.MkDir()) - defer func() { dirs.SetRootDir("/") }() defer mockSnapConfine(dirs.DistroLibExecDir)() + // mock installed snap // Create both revisions 41 and 42 snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{ Revision: snap.R(41), @@ -332,10 +318,6 @@ } func (s *SnapSuite) TestSnapRunHookMissingRevisionIntegration(c *check.C) { - // mock installed snap - dirs.SetRootDir(c.MkDir()) - defer func() { dirs.SetRootDir("/") }() - // Only create revision 42 si := snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{ Revision: snap.R(42), @@ -362,10 +344,6 @@ } func (s *SnapSuite) TestSnapRunHookMissingHookIntegration(c *check.C) { - // mock installed snap - dirs.SetRootDir(c.MkDir()) - defer func() { dirs.SetRootDir("/") }() - // Only create revision 42 si := snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{ Revision: snap.R(42), @@ -402,11 +380,9 @@ } func (s *SnapSuite) TestSnapRunSaneEnvironmentHandling(c *check.C) { - // mock installed snap - dirs.SetRootDir(c.MkDir()) - defer func() { dirs.SetRootDir("/") }() defer mockSnapConfine(dirs.DistroLibExecDir)() + // mock installed snap si := snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{ Revision: snap.R(42), }) @@ -460,11 +436,9 @@ } func (s *SnapSuite) TestSnapRunAppIntegrationFromCore(c *check.C) { - // mock installed snap - dirs.SetRootDir(c.MkDir()) - defer func() { dirs.SetRootDir("/") }() defer mockSnapConfine(filepath.Join(dirs.SnapMountDir, "core", "current", dirs.CoreLibExecDir))() + // mock installed snap si := snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{ Revision: snap.R("x2"), }) @@ -503,10 +477,6 @@ } func (s *SnapSuite) TestSnapRunXauthorityMigration(c *check.C) { - // mock installed snap; happily this also gives us a directory - // below /tmp which the Xauthority migration expects. - dirs.SetRootDir(c.MkDir()) - defer func() { dirs.SetRootDir("/") }() defer mockSnapConfine(dirs.DistroLibExecDir)() u, err := user.Current() @@ -516,6 +486,8 @@ err = os.MkdirAll(filepath.Join(dirs.XdgRuntimeDirBase, u.Uid), 0700) c.Assert(err, check.IsNil) + // mock installed snap; happily this also gives us a directory + // below /tmp which the Xauthority migration expects. si := snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{ Revision: snap.R("x2"), }) diff -Nru snapd-2.27.5/cmd/snap/cmd_services.go snapd-2.28.5/cmd/snap/cmd_services.go --- snapd-2.27.5/cmd/snap/cmd_services.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/cmd/snap/cmd_services.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,215 @@ +// -*- 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 main + +import ( + "fmt" + "strconv" + + "github.com/jessevdk/go-flags" + + "github.com/snapcore/snapd/client" + "github.com/snapcore/snapd/i18n" +) + +type svcStatus struct { + Positional struct { + ServiceNames []serviceName `positional-arg-name:""` + } `positional-args:"yes"` +} + +type svcLogs struct { + N string `short:"n" default:"10"` + Follow bool `short:"f"` + Positional struct { + ServiceNames []serviceName `positional-arg-name:"" required:"1"` + } `positional-args:"yes" required:"yes"` +} + +var ( + shortServicesHelp = i18n.G("Query the status of services") + shortLogsHelp = i18n.G("Retrieve logs of services") + shortStartHelp = i18n.G("Start services") + shortStopHelp = i18n.G("Stop services") + shortRestartHelp = i18n.G("Restart services") +) + +func init() { + addCommand("services", shortServicesHelp, "", func() flags.Commander { return &svcStatus{} }, nil, nil) + addCommand("logs", shortLogsHelp, "", func() flags.Commander { return &svcLogs{} }, nil, nil) + + addCommand("start", shortStartHelp, "", func() flags.Commander { return &svcStart{} }, nil, nil) + addCommand("stop", shortStopHelp, "", func() flags.Commander { return &svcStop{} }, nil, nil) + addCommand("restart", shortRestartHelp, "", func() flags.Commander { return &svcRestart{} }, nil, nil) +} + +func svcNames(s []serviceName) []string { + svcNames := make([]string, len(s)) + for i, svcName := range s { + svcNames[i] = string(svcName) + } + return svcNames +} + +func (s *svcStatus) Execute(args []string) error { + if len(args) > 0 { + return ErrExtraArgs + } + + services, err := Client().Apps(svcNames(s.Positional.ServiceNames), client.AppOptions{Service: true}) + if err != nil { + return err + } + + w := tabWriter() + defer w.Flush() + + fmt.Fprintln(w, i18n.G("Snap\tService\tStartup\tCurrent")) + + for _, svc := range services { + startup := i18n.G("disabled") + if svc.Enabled { + startup = i18n.G("enabled") + } + current := i18n.G("inactive") + if svc.Active { + current = i18n.G("active") + } + fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", svc.Snap, svc.Name, startup, current) + } + + return nil +} + +func (s *svcLogs) Execute(args []string) error { + if len(args) > 0 { + return ErrExtraArgs + } + + sN := -1 + if s.N != "all" { + n, err := strconv.ParseInt(s.N, 0, 32) + if n < 0 || err != nil { + return fmt.Errorf(i18n.G("invalid argument for flag ‘-n’: expected a non-negative integer argument, or “all”.")) + } + sN = int(n) + } + + logs, err := Client().Logs(svcNames(s.Positional.ServiceNames), client.LogOptions{N: sN, Follow: s.Follow}) + if err != nil { + return err + } + + for log := range logs { + fmt.Fprintln(Stdout, log) + } + + return nil +} + +type svcStart struct { + waitMixin + Positional struct { + ServiceNames []serviceName `positional-arg-name:"" required:"1"` + } `positional-args:"yes" required:"yes"` + Enable bool `long:"enable"` +} + +func (s *svcStart) Execute(args []string) error { + if len(args) > 0 { + return ErrExtraArgs + } + cli := Client() + names := svcNames(s.Positional.ServiceNames) + changeID, err := cli.Start(names, client.StartOptions{Enable: s.Enable}) + if err != nil { + return err + } + if _, err := s.wait(cli, changeID); err != nil { + if err == noWait { + return nil + } + return err + } + + fmt.Fprintf(Stdout, i18n.G("Started.\n")) + + return nil +} + +type svcStop struct { + waitMixin + Positional struct { + ServiceNames []serviceName `positional-arg-name:"" required:"1"` + } `positional-args:"yes" required:"yes"` + Disable bool `long:"disable"` +} + +func (s *svcStop) Execute(args []string) error { + if len(args) > 0 { + return ErrExtraArgs + } + cli := Client() + names := svcNames(s.Positional.ServiceNames) + changeID, err := cli.Stop(names, client.StopOptions{Disable: s.Disable}) + if err != nil { + return err + } + if _, err := s.wait(cli, changeID); err != nil { + if err == noWait { + return nil + } + return err + } + + fmt.Fprintf(Stdout, i18n.G("Stopped.\n")) + + return nil +} + +type svcRestart struct { + waitMixin + Positional struct { + ServiceNames []serviceName `positional-arg-name:"" required:"1"` + } `positional-args:"yes" required:"yes"` + Reload bool `long:"reload"` +} + +func (s *svcRestart) Execute(args []string) error { + if len(args) > 0 { + return ErrExtraArgs + } + cli := Client() + names := svcNames(s.Positional.ServiceNames) + changeID, err := cli.Restart(names, client.RestartOptions{Reload: s.Reload}) + if err != nil { + return err + } + if _, err := s.wait(cli, changeID); err != nil { + if err == noWait { + return nil + } + return err + } + + fmt.Fprintf(Stdout, i18n.G("Restarted.\n")) + + return nil +} diff -Nru snapd-2.27.5/cmd/snap/cmd_services_test.go snapd-2.28.5/cmd/snap/cmd_services_test.go --- snapd-2.27.5/cmd/snap/cmd_services_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/cmd/snap/cmd_services_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,173 @@ +// -*- 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 main_test + +import ( + "fmt" + "net/http" + "time" + + "gopkg.in/check.v1" + + "github.com/snapcore/snapd/client" + snap "github.com/snapcore/snapd/cmd/snap" +) + +type appOpSuite struct { + BaseSnapSuite + + restoreAll func() +} + +var _ = check.Suite(&appOpSuite{}) + +func (s *appOpSuite) SetUpTest(c *check.C) { + s.BaseSnapSuite.SetUpTest(c) + + restoreClientRetry := client.MockDoRetry(time.Millisecond, 10*time.Millisecond) + restorePollTime := snap.MockPollTime(time.Millisecond) + s.restoreAll = func() { + restoreClientRetry() + restorePollTime() + } +} + +func (s *appOpSuite) TearDownTest(c *check.C) { + s.restoreAll() + s.BaseSnapSuite.TearDownTest(c) +} + +func (s *appOpSuite) expectedBody(op string, names []string, extra []string) map[string]interface{} { + inames := make([]interface{}, len(names)) + for i, name := range names { + inames[i] = name + } + expectedBody := map[string]interface{}{ + "action": op, + "names": inames, + } + for _, x := range extra { + expectedBody[x] = true + } + return expectedBody +} + +func (s *appOpSuite) args(op string, names []string, extra []string, noWait bool) []string { + args := []string{op} + if noWait { + args = append(args, "--no-wait") + } + for _, x := range extra { + args = append(args, "--"+x) + } + for _, name := range names { + args = append(args, name) + } + return args +} + +func (s *appOpSuite) testOpNoArgs(c *check.C, op string) { + s.RedirectClientToTestServer(nil) + _, err := snap.Parser().ParseArgs([]string{op}) + c.Assert(err, check.ErrorMatches, `.* required argument .* not provided`) +} + +func (s *appOpSuite) testOpErrorResponse(c *check.C, op string, names []string, extra []string, noWait bool) { + n := 0 + s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { + switch n { + case 0: + c.Check(r.Method, check.Equals, "POST") + c.Check(r.URL.Path, check.Equals, "/v2/apps") + c.Check(r.URL.Query(), check.HasLen, 0) + c.Check(DecodedRequestBody(c, r), check.DeepEquals, s.expectedBody(op, names, extra)) + w.WriteHeader(400) + fmt.Fprintln(w, `{"type": "error", "result": {"message": "error"}, "status-code": 400}`) + default: + c.Fatalf("expected to get 1 requests, now on %d", n+1) + } + + n++ + }) + + _, err := snap.Parser().ParseArgs(s.args(op, names, extra, noWait)) + c.Assert(err, check.ErrorMatches, "error") + c.Check(n, check.Equals, 1) +} + +func (s *appOpSuite) testOp(c *check.C, op, summary string, names []string, extra []string, noWait bool) { + n := 0 + s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { + switch n { + case 0: + c.Check(r.URL.Path, check.Equals, "/v2/apps") + c.Check(r.URL.Query(), check.HasLen, 0) + c.Check(DecodedRequestBody(c, r), check.DeepEquals, s.expectedBody(op, names, extra)) + c.Check(r.Method, check.Equals, "POST") + w.WriteHeader(202) + fmt.Fprintln(w, `{"type":"async", "change": "42", "status-code": 202}`) + case 1: + c.Check(r.Method, check.Equals, "GET") + c.Check(r.URL.Path, check.Equals, "/v2/changes/42") + fmt.Fprintln(w, `{"type": "sync", "result": {"status": "Doing"}}`) + case 2: + c.Check(r.Method, check.Equals, "GET") + c.Check(r.URL.Path, check.Equals, "/v2/changes/42") + fmt.Fprintln(w, `{"type": "sync", "result": {"ready": true, "status": "Done"}}`) + default: + c.Fatalf("expected to get 2 requests, now on %d", n+1) + } + + n++ + }) + rest, err := snap.Parser().ParseArgs(s.args(op, names, extra, noWait)) + c.Assert(err, check.IsNil) + c.Assert(rest, check.HasLen, 0) + c.Check(s.Stderr(), check.Equals, "") + expectedN := 3 + if noWait { + summary = "42" + expectedN = 1 + } + c.Check(s.Stdout(), check.Equals, summary+"\n") + // ensure that the fake server api was actually hit + c.Check(n, check.Equals, expectedN) +} + +func (s *appOpSuite) TestAppOps(c *check.C) { + extras := []string{"enable", "disable", "reload"} + summaries := []string{"Started.", "Stopped.", "Restarted."} + for i, op := range []string{"start", "stop", "restart"} { + s.testOpNoArgs(c, op) + for _, extra := range [][]string{nil, {extras[i]}} { + for _, noWait := range []bool{false, true} { + for _, names := range [][]string{ + {"foo"}, + {"foo", "bar"}, + {"foo", "bar.baz"}, + } { + s.testOpErrorResponse(c, op, names, extra, noWait) + s.testOp(c, op, summaries[i], names, extra, noWait) + s.stdout.Reset() + } + } + } + } +} diff -Nru snapd-2.27.5/cmd/snap/cmd_set.go snapd-2.28.5/cmd/snap/cmd_set.go --- snapd-2.27.5/cmd/snap/cmd_set.go 2016-12-08 15:14:07.000000000 +0000 +++ snapd-2.28.5/cmd/snap/cmd_set.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,13 +20,13 @@ package main import ( - "encoding/json" "fmt" "strings" "github.com/jessevdk/go-flags" "github.com/snapcore/snapd/i18n" + "github.com/snapcore/snapd/jsonutil" ) var shortSetHelp = i18n.G("Changes configuration options") @@ -70,12 +70,11 @@ return fmt.Errorf(i18n.G("invalid configuration: %q (want key=value)"), patchValue) } var value interface{} - err := json.Unmarshal([]byte(parts[1]), &value) - if err == nil { - patchValues[parts[0]] = value - } else { + if err := jsonutil.DecodeWithNumber(strings.NewReader(parts[1]), &value); err != nil { // Not valid JSON-- just save the string as-is. patchValues[parts[0]] = parts[1] + } else { + patchValues[parts[0]] = value } } diff -Nru snapd-2.27.5/cmd/snap/cmd_set_test.go snapd-2.28.5/cmd/snap/cmd_set_test.go --- snapd-2.27.5/cmd/snap/cmd_set_test.go 2016-11-24 09:36:03.000000000 +0000 +++ snapd-2.28.5/cmd/snap/cmd_set_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,13 +20,13 @@ package main_test import ( + "encoding/json" "fmt" "net/http" "gopkg.in/check.v1" snapset "github.com/snapcore/snapd/cmd/snap" - "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap/snaptest" ) @@ -46,9 +46,6 @@ func (s *SnapSuite) TestSnapSetIntegrationString(c *check.C) { // mock installed snap - dirs.SetRootDir(c.MkDir()) - defer func() { dirs.SetRootDir("/") }() - snaptest.MockSnap(c, string(validApplyYaml), string(validApplyContents), &snap.SideInfo{ Revision: snap.R(42), }) @@ -63,26 +60,33 @@ func (s *SnapSuite) TestSnapSetIntegrationNumber(c *check.C) { // mock installed snap - dirs.SetRootDir(c.MkDir()) - defer func() { dirs.SetRootDir("/") }() - snaptest.MockSnap(c, string(validApplyYaml), string(validApplyContents), &snap.SideInfo{ Revision: snap.R(42), }) // and mock the server - s.mockSetConfigServer(c, 1.2) + s.mockSetConfigServer(c, json.Number("1.2")) // Set a config value for the active snap _, err := snapset.Parser().ParseArgs([]string{"set", "snapname", "key=1.2"}) c.Assert(err, check.IsNil) } +func (s *SnapSuite) TestSnapSetIntegrationBigInt(c *check.C) { + snaptest.MockSnap(c, string(validApplyYaml), string(validApplyContents), &snap.SideInfo{ + Revision: snap.R(42), + }) + + // and mock the server + s.mockSetConfigServer(c, json.Number("1234567890")) + + // Set a config value for the active snap + _, err := snapset.Parser().ParseArgs([]string{"set", "snapname", "key=1234567890"}) + c.Assert(err, check.IsNil) +} + func (s *SnapSuite) TestSnapSetIntegrationJson(c *check.C) { // mock installed snap - dirs.SetRootDir(c.MkDir()) - defer func() { dirs.SetRootDir("/") }() - snaptest.MockSnap(c, string(validApplyYaml), string(validApplyContents), &snap.SideInfo{ Revision: snap.R(42), }) diff -Nru snapd-2.27.5/cmd/snap/cmd_snap_op.go snapd-2.28.5/cmd/snap/cmd_snap_op.go --- snapd-2.27.5/cmd/snap/cmd_snap_op.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/cmd/snap/cmd_snap_op.go 2017-09-13 14:47:18.000000000 +0000 @@ -948,6 +948,47 @@ return nil } +var shortSwitchHelp = i18n.G("Switches snap to a different channel") +var longSwitchHelp = i18n.G(` +The switch command switches the given snap to a different channel without +doing a refresh. +`) + +type cmdSwitch struct { + channelMixin + + Positional struct { + Snap installedSnapName `positional-arg-name:"" required:"1"` + } `positional-args:"yes" required:"yes"` +} + +func (x cmdSwitch) Execute(args []string) error { + if err := x.setChannelFromCommandline(); err != nil { + return err + } + if x.Channel == "" { + return fmt.Errorf("missing --channel= parameter") + } + + cli := Client() + name := string(x.Positional.Snap) + channel := string(x.Channel) + opts := &client.SnapOptions{ + Channel: channel, + } + changeID, err := cli.Switch(name, opts) + if err != nil { + return err + } + + if _, err = wait(cli, changeID); err != nil { + return err + } + + fmt.Fprintf(Stdout, i18n.G("%q switched to the %q channel\n"), name, channel) + return nil +} + func init() { addCommand("remove", shortRemoveHelp, longRemoveHelp, func() flags.Commander { return &cmdRemove{} }, waitDescs.also(map[string]string{"revision": i18n.G("Remove only the given revision")}), nil) @@ -971,4 +1012,6 @@ addCommand("revert", shortRevertHelp, longRevertHelp, func() flags.Commander { return &cmdRevert{} }, waitDescs.also(modeDescs).also(map[string]string{ "revision": "Revert to the given revision", }), nil) + addCommand("switch", shortSwitchHelp, longSwitchHelp, func() flags.Commander { return &cmdSwitch{} }, nil, nil) + } diff -Nru snapd-2.27.5/cmd/snap/cmd_snap_op_test.go snapd-2.28.5/cmd/snap/cmd_snap_op_test.go --- snapd-2.27.5/cmd/snap/cmd_snap_op_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/cmd/snap/cmd_snap_op_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -1009,3 +1009,33 @@ s.stdout.Reset() } } + +func (s *SnapOpSuite) TestSwitchHappy(c *check.C) { + s.srv.total = 3 + 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": "switch", + "channel": "beta", + }) + } + + s.RedirectClientToTestServer(s.srv.handle) + rest, err := snap.Parser().ParseArgs([]string{"switch", "--beta", "foo"}) + c.Assert(err, check.IsNil) + c.Assert(rest, check.DeepEquals, []string{}) + c.Check(s.Stdout(), check.Matches, `(?sm).*"foo" switched to the "beta" channel`) + 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) TestSwitchUnhappy(c *check.C) { + _, err := snap.Parser().ParseArgs([]string{"switch"}) + c.Assert(err, check.ErrorMatches, "the required argument `` was not provided") +} + +func (s *SnapOpSuite) TestSwitchAlsoUnhappy(c *check.C) { + _, err := snap.Parser().ParseArgs([]string{"switch", "foo"}) + c.Assert(err, check.ErrorMatches, `missing --channel= parameter`) +} diff -Nru snapd-2.27.5/cmd/snap/cmd_userd.go snapd-2.28.5/cmd/snap/cmd_userd.go --- snapd-2.27.5/cmd/snap/cmd_userd.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/cmd/snap/cmd_userd.go 2017-09-13 14:47:18.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 ( + "fmt" + "os" + "os/signal" + "syscall" + + "github.com/jessevdk/go-flags" + + "github.com/snapcore/snapd/i18n" + "github.com/snapcore/snapd/userd" +) + +type cmdUserd struct { + userd userd.Userd +} + +var shortUserdHelp = i18n.G("Start the userd service") +var longUserdHelp = i18n.G("The userd command starts the snap user session service.") + +func init() { + cmd := addCommand("userd", + shortAbortHelp, + longAbortHelp, + func() flags.Commander { + return &cmdUserd{} + }, + nil, + []argDesc{}, + ) + cmd.hidden = true +} + +func (x *cmdUserd) Execute(args []string) error { + if len(args) > 0 { + return ErrExtraArgs + } + + if err := x.userd.Init(); err != nil { + return err + } + x.userd.Start() + + ch := make(chan os.Signal) + signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1) + select { + case sig := <-ch: + fmt.Fprintf(Stdout, "Exiting on %s.\n", sig) + case <-x.userd.Dying(): + // something called Stop() + } + + return x.userd.Stop() +} diff -Nru snapd-2.27.5/cmd/snap/cmd_userd_test.go snapd-2.28.5/cmd/snap/cmd_userd_test.go --- snapd-2.27.5/cmd/snap/cmd_userd_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/cmd/snap/cmd_userd_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,70 @@ +// -*- 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_test + +import ( + "os" + "syscall" + "time" + + . "gopkg.in/check.v1" + + snap "github.com/snapcore/snapd/cmd/snap" + "github.com/snapcore/snapd/testutil" +) + +type userdSuite struct { + BaseSnapSuite + testutil.DBusTest +} + +var _ = Suite(&userdSuite{}) + +func (s *userdSuite) TestUserdBadCommandline(c *C) { + _, err := snap.Parser().ParseArgs([]string{"userd", "extra-arg"}) + c.Assert(err, ErrorMatches, "too many arguments for command") +} + +func (s *userdSuite) TestUserd(c *C) { + go func() { + defer func() { + me, err := os.FindProcess(os.Getpid()) + c.Assert(err, IsNil) + me.Signal(syscall.SIGUSR1) + }() + + needle := "io.snapcraft.Launcher" + for i := 0; i < 10; i++ { + for _, objName := range s.SessionBus.Names() { + if objName == needle { + return + } + time.Sleep(1 * time.Second) + } + + } + c.Fatalf("%s does not appeared on the bus", needle) + }() + + rest, err := snap.Parser().ParseArgs([]string{"userd"}) + c.Assert(err, IsNil) + c.Check(rest, DeepEquals, []string{}) + c.Check(s.Stdout(), Equals, "Exiting on user defined signal 1.\n") +} diff -Nru snapd-2.27.5/cmd/snap/cmd_watch_test.go snapd-2.28.5/cmd/snap/cmd_watch_test.go --- snapd-2.27.5/cmd/snap/cmd_watch_test.go 2016-11-24 10:38:34.000000000 +0000 +++ snapd-2.28.5/cmd/snap/cmd_watch_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -81,5 +81,5 @@ buf, err := ioutil.ReadFile(stdout.Name()) c.Assert(err, IsNil) - c.Check(string(buf), testutil.Contains, "\rmy-snap 50.00 KB / 100.00 KB") + c.Check(string(buf), testutil.Contains, "\rmy-snap 0 B / 100.00 KB") } diff -Nru snapd-2.27.5/cmd/snap/complete.go snapd-2.28.5/cmd/snap/complete.go --- snapd-2.27.5/cmd/snap/complete.go 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/cmd/snap/complete.go 2017-09-13 14:47:18.000000000 +0000 @@ -13,8 +13,7 @@ type installedSnapName string func (s installedSnapName) Complete(match string) []flags.Completion { - cli := Client() - snaps, err := cli.List(nil, nil) + snaps, err := Client().List(nil, nil) if err != nil { return nil } @@ -35,8 +34,7 @@ if len(match) < 3 { return nil } - cli := Client() - snaps, _, err := cli.Find(&client.FindOptions{ + snaps, _, err := Client().Find(&client.FindOptions{ Prefix: true, Query: match, }) @@ -71,8 +69,7 @@ type changeID string func (s changeID) Complete(match string) []flags.Completion { - cli := Client() - changes, err := cli.Changes(&client.ChangesOptions{Selector: client.ChangesAll}) + changes, err := Client().Changes(&client.ChangesOptions{Selector: client.ChangesAll}) if err != nil { return nil } @@ -87,6 +84,24 @@ return ret } +type assertTypeName string + +func (n assertTypeName) Complete(match string) []flags.Completion { + cli := Client() + names, err := cli.AssertionTypes() + if err != nil { + return nil + } + ret := make([]flags.Completion, 0, len(names)) + for _, name := range names { + if strings.HasPrefix(name, match) { + ret = append(ret, flags.Completion{Item: name}) + } + } + + return ret +} + type keyName string func (s keyName) Complete(match string) []flags.Completion { @@ -202,8 +217,7 @@ parts := strings.SplitN(match, ":", 2) // Ask snapd about available interfaces. - cli := Client() - ifaces, err := cli.Interfaces() + ifaces, err := Client().Connections() if err != nil { return nil } @@ -289,4 +303,47 @@ } return ret +} + +type interfaceName string + +func (s interfaceName) Complete(match string) []flags.Completion { + ifaces, err := Client().Interfaces(nil) + if err != nil { + return nil + } + + ret := make([]flags.Completion, 0, len(ifaces)) + for _, iface := range ifaces { + if strings.HasPrefix(iface.Name, match) { + ret = append(ret, flags.Completion{Item: iface.Name, Description: iface.Summary}) + } + } + + return ret +} + +type serviceName string + +func (s serviceName) Complete(match string) []flags.Completion { + cli := Client() + apps, err := cli.Apps(nil, client.AppOptions{Service: true}) + if err != nil { + return nil + } + + snaps := map[string]bool{} + var ret []flags.Completion + for _, app := range apps { + if !app.IsService() { + continue + } + if !snaps[app.Snap] { + snaps[app.Snap] = true + ret = append(ret, flags.Completion{Item: app.Snap}) + } + ret = append(ret, flags.Completion{Item: app.Snap + "." + app.Name}) + } + + return ret } diff -Nru snapd-2.27.5/cmd/snap/error.go snapd-2.28.5/cmd/snap/error.go --- snapd-2.27.5/cmd/snap/error.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/cmd/snap/error.go 2017-09-13 14:47:18.000000000 +0000 @@ -100,6 +100,12 @@ // arch/channel/revision. Surface that here somehow! msg = i18n.G("snap %q not found") + if snapName == "" { + errValStr, ok := err.Value.(string) + if ok && errValStr != "" { + snapName = errValStr + } + } if opts != nil { if opts.Revision != "" { // TRANSLATORS: %%q will become a %q for the snap name; %q is whatever foo the user used for --revision=foo diff -Nru snapd-2.27.5/cmd/snap/export_test.go snapd-2.28.5/cmd/snap/export_test.go --- snapd-2.27.5/cmd/snap/export_test.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/cmd/snap/export_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -23,6 +23,8 @@ "os/user" "time" + "github.com/jessevdk/go-flags" + "github.com/snapcore/snapd/overlord/auth" "github.com/snapcore/snapd/store" ) @@ -36,6 +38,8 @@ Wait = wait ResolveApp = resolveApp IsReexeced = isReexeced + MaybePrintServices = maybePrintServices + MaybePrintCommands = maybePrintCommands ) func MockPollTime(d time.Duration) (restore func()) { @@ -119,3 +123,7 @@ } return x.Less(0, 1) } + +func AssertTypeNameCompletion(match string) []flags.Completion { + return assertTypeName("").Complete(match) +} diff -Nru snapd-2.27.5/cmd/snap/main.go snapd-2.28.5/cmd/snap/main.go --- snapd-2.27.5/cmd/snap/main.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/cmd/snap/main.go 2017-09-13 14:47:18.000000000 +0000 @@ -229,6 +229,8 @@ var ClientConfig = client.Config{ // we need the powerful snapd socket Socket: dirs.SnapdSocket, + // Allow interactivity if we have a terminal + Interactive: terminal.IsTerminal(0), } // Client returns a new client using ClientConfig as configuration. diff -Nru snapd-2.27.5/cmd/snap/main_test.go snapd-2.28.5/cmd/snap/main_test.go --- snapd-2.27.5/cmd/snap/main_test.go 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/cmd/snap/main_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -61,6 +61,8 @@ func (s *BaseSnapSuite) SetUpTest(c *C) { s.BaseTest.SetUpTest(c) + dirs.SetRootDir(c.MkDir()) + s.stdin = bytes.NewBuffer(nil) s.stdout = bytes.NewBuffer(nil) s.stderr = bytes.NewBuffer(nil) @@ -83,6 +85,7 @@ c.Assert(s.AuthFile == "", Equals, false) err := os.Unsetenv(TestAuthFileEnvKey) c.Assert(err, IsNil) + dirs.SetRootDir("/") s.BaseTest.TearDownTest(c) } @@ -128,6 +131,7 @@ func DecodedRequestBody(c *C, r *http.Request) map[string]interface{} { var body map[string]interface{} decoder := json.NewDecoder(r.Body) + decoder.UseNumber() err := decoder.Decode(&body) c.Assert(err, IsNil) return body @@ -240,9 +244,6 @@ } func (s *SnapSuite) TestResolveApp(c *C) { - dirs.SetRootDir(c.MkDir()) - defer dirs.SetRootDir("/") - err := os.MkdirAll(dirs.SnapBinariesDir, 0755) c.Assert(err, IsNil) diff -Nru snapd-2.27.5/cmd/snap-confine/mount-support.c snapd-2.28.5/cmd/snap-confine/mount-support.c --- snapd-2.27.5/cmd/snap-confine/mount-support.c 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/cmd/snap-confine/mount-support.c 2017-10-13 20:09:56.000000000 +0000 @@ -30,6 +30,7 @@ #include #include #include +#include #include "../libsnap-confine-private/classic.h" #include "../libsnap-confine-private/cleanup-funcs.h" @@ -217,6 +218,7 @@ // The struct is terminated with an entry with NULL path. const struct sc_mount *mounts; bool on_classic_distro; + bool uses_base_snap; }; /** @@ -330,16 +332,67 @@ }; for (const char **dirs = dirs_from_core; *dirs != NULL; dirs++) { const char *dir = *dirs; + struct stat buf; if (access(dir, F_OK) == 0) { sc_must_snprintf(src, sizeof src, "%s%s", config->rootfs_dir, dir); sc_must_snprintf(dst, sizeof dst, "%s%s", scratch_dir, dir); - sc_do_mount(src, dst, NULL, MS_BIND, NULL); - sc_do_mount("none", dst, NULL, MS_SLAVE, NULL); + if (lstat(src, &buf) == 0 + && lstat(dst, &buf) == 0) { + sc_do_mount(src, dst, NULL, MS_BIND, + NULL); + sc_do_mount("none", dst, NULL, MS_SLAVE, + NULL); + } } } } + if (config->uses_base_snap) { + // when bases are used we need to bind-mount the libexecdir + // (that contains snap-exec) into /usr/lib/snapd of the + // base snap so that snap-exec is available for the snaps + // (base snaps do not ship snapd) + + // dst is always /usr/lib/snapd as this is where snapd + // assumes to find snap-exec + sc_must_snprintf(dst, sizeof dst, "%s/usr/lib/snapd", + scratch_dir); + + // bind mount the current $ROOT/usr/lib/snapd path, + // where $ROOT is either "/" or the "/snap/core/current" + // that we are re-execing from + char *src = NULL; + char self[PATH_MAX + 1] = { 0, }; + if (readlink("/proc/self/exe", self, sizeof(self) - 1) < 0) { + die("cannot read /proc/self/exe"); + } + // this cannot happen except when the kernel is buggy + if (strstr(self, "/snap-confine") == NULL) { + die("cannot use result from readlink: %s", src); + } + src = dirname(self); + // dirname(path) might return '.' depending on path. + // /proc/self/exe should always point + // to an absolute path, but let's guarantee that. + if (src[0] != '/') { + die("cannot use the result of dirname(): %s", src); + } + + sc_do_mount(src, dst, NULL, MS_BIND | MS_RDONLY, NULL); + sc_do_mount("none", dst, NULL, MS_SLAVE, NULL); + + // FIXME: snapctl tool - our apparmor policy wants it in + // /usr/bin/snapctl, we will need an empty file + // here from the base snap or we need to move it + // into a different location and just symlink it + // (/usr/lib/snapd/snapctl -> /usr/bin/snapctl) + // and in the base snap case adjust PATH + //src = "/usr/bin/snapctl"; + //sc_must_snprintf(dst, sizeof dst, "%s%s", scratch_dir, src); + //sc_do_mount(src, dst, NULL, MS_REC | MS_BIND, NULL); + //sc_do_mount("none", dst, NULL, MS_REC | MS_SLAVE, NULL); + } // Bind mount the directory where all snaps are mounted. The location of // the this directory on the host filesystem may not match the location in // the desired root filesystem. In the "core" and "ubuntu-core" snaps the @@ -512,9 +565,10 @@ if (vanilla_cwd == NULL) { die("cannot get the current working directory"); } - // Remember if we are on classic, some things behave differently there. bool on_classic_distro = is_running_on_classic_distribution(); - if (on_classic_distro) { + // on classic or with alternative base snaps we need to setup + // a different confinement + if (on_classic_distro || !sc_streq(base_snap_name, "core")) { const struct sc_mount mounts[] = { {"/dev"}, // because it contains devices on host OS {"/etc"}, // because that's where /etc/resolv.conf lives, perhaps a bad idea @@ -557,6 +611,7 @@ .rootfs_dir = rootfs_dir, .mounts = mounts, .on_classic_distro = true, + .uses_base_snap = !sc_streq(base_snap_name, "core"), }; sc_bootstrap_mount_namespace(&classic_config); } else { @@ -573,6 +628,7 @@ struct sc_mount_config all_snap_config = { .rootfs_dir = "/", .mounts = mounts, + .uses_base_snap = !sc_streq(base_snap_name, "core"), }; sc_bootstrap_mount_namespace(&all_snap_config); } diff -Nru snapd-2.27.5/cmd/snap-confine/snap-confine.apparmor.in snapd-2.28.5/cmd/snap-confine/snap-confine.apparmor.in --- snapd-2.27.5/cmd/snap-confine/snap-confine.apparmor.in 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/cmd/snap-confine/snap-confine.apparmor.in 2017-10-11 17:40:25.000000000 +0000 @@ -5,22 +5,25 @@ # We run privileged, so be fanatical about what we include and don't use # any abstractions /etc/ld.so.cache r, - /lib/@{multiarch}/ld-*.so mr, + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}ld-*.so mrix, # libc, you are funny - /lib/@{multiarch}/libc{,-[0-9]*}.so* mr, - /lib/@{multiarch}/libpthread{,-[0-9]*}.so* mr, - /lib/@{multiarch}/librt{,-[0-9]*}.so* mr, - /lib/@{multiarch}/libgcc_s.so* mr, + /{,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}/}librt{,-[0-9]*}.so* mr, + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libgcc_s.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, # normal libs in order - /lib/@{multiarch}/libapparmor.so* mr, - /lib/@{multiarch}/libcgmanager.so* mr, - /lib/@{multiarch}/libdl-[0-9]*.so* mr, - /lib/@{multiarch}/libnih.so* mr, - /lib/@{multiarch}/libnih-dbus.so* mr, - /lib/@{multiarch}/libdbus-1.so* mr, - /lib/@{multiarch}/libudev.so* mr, - /usr/lib/@{multiarch}/libseccomp.so* mr, - /lib/@{multiarch}/libseccomp.so* mr, + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libapparmor.so* mr, + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libcgmanager.so* mr, + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libdl{,-[0-9]*}.so* mr, + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libnih.so* mr, + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libnih-dbus.so* mr, + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libdbus-1.so* mr, + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libudev.so* mr, + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libseccomp.so* mr, + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libcap.so* mr, @LIBEXECDIR@/snap-confine mr, @@ -57,6 +60,8 @@ @{PROC}/[0-9]*/attr/exec w, # Reading current profile @{PROC}/[0-9]*/attr/current r, + # Reading available filesystems + @{PROC}/filesystems r, # To find where apparmor is mounted @{PROC}/[0-9]*/mounts r, @@ -153,18 +158,29 @@ 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_*/lib/modules/, - mount options=(rw rslave) -> /tmp/snap.rootfs_*/lib/modules/, + 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) /var/log/ -> /tmp/snap.rootfs_*/var/log/, mount options=(rw rslave) -> /tmp/snap.rootfs_*/var/log/, mount options=(rw rbind) /usr/src/ -> /tmp/snap.rootfs_*/usr/src/, mount options=(rw rslave) -> /tmp/snap.rootfs_*/usr/src/, + + # allow making host snap-exec available inside base snaps + mount options=(rw bind) @LIBEXECDIR@/ -> /tmp/snap.rootfs_*/usr/lib/snapd/, + mount options=(rw slave) -> /tmp/snap.rootfs_*/usr/lib/snapd/, + + # allow making re-execed host snap-exec available inside base snaps + mount options=(ro bind) @SNAP_MOUNT_DIR@/core/*/usr/lib/snapd/ -> /tmp/snap.rootfs_*/usr/lib/snapd/, + + mount options=(rw bind) /usr/bin/snapctl -> /tmp/snap.rootfs_*/usr/bin/snapctl, + mount options=(rw slave) -> /tmp/snap.rootfs_*/usr/bin/snapctl, + # /etc/alternatives (classic) mount options=(rw bind) @SNAP_MOUNT_DIR@/{,ubuntu-}core/*/etc/alternatives/ -> /tmp/snap.rootfs_*/etc/alternatives/, - mount options=(rw bind) @SNAP_MOUNT_DIR@/{,ubuntu-}core/*/etc/ssl/ -> /tmp/snap.rootfs_*/etc/ssl/, - mount options=(rw bind) @SNAP_MOUNT_DIR@/{,ubuntu-}core/*/etc/nsswitch.conf -> /tmp/snap.rootfs_*/etc/nsswitch.conf, + mount options=(rw bind) @SNAP_MOUNT_DIR@/*/*/etc/ssl/ -> /tmp/snap.rootfs_*/etc/ssl/, + mount options=(rw bind) @SNAP_MOUNT_DIR@/*/*/etc/nsswitch.conf -> /tmp/snap.rootfs_*/etc/nsswitch.conf, # /etc/alternatives (core) mount options=(rw bind) /etc/alternatives/ -> /tmp/snap.rootfs_*/etc/alternatives/, mount options=(rw slave) -> /tmp/snap.rootfs_*/etc/alternatives/, @@ -233,6 +249,12 @@ # nvidia handling, glob needs /usr/** and the launcher must be # able to bind mount the nvidia dir /sys/module/nvidia/version r, + /sys/**/drivers/nvidia{,_*}/* r, + /sys/**/nvidia*/uevent r, + /sys/module/nvidia{,_*}/* r, + /dev/nvidia[0-9]* r, + /dev/nvidiactl r, + /dev/nvidia-uvm r, /usr/** r, mount options=(rw bind) /usr/lib/nvidia-*/ -> /{tmp/snap.rootfs_*/,}var/lib/snapd/lib/gl/, @@ -331,21 +353,25 @@ # We run privileged, so be fanatical about what we include and don't use # any abstractions /etc/ld.so.cache r, - /lib/@{multiarch}/ld-*.so mr, + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}ld-*.so mrix, # libc, you are funny - /lib/@{multiarch}/libc{,-[0-9]*}.so* mr, - /lib/@{multiarch}/libpthread{,-[0-9]*}.so* mr, - /lib/@{multiarch}/librt{,-[0-9]*}.so* mr, - /lib/@{multiarch}/libgcc_s.so* mr, + /{,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}/}librt{,-[0-9]*}.so* mr, + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libgcc_s.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, # normal libs in order - /lib/@{multiarch}/libapparmor.so* mr, - /lib/@{multiarch}/libcgmanager.so* mr, - /lib/@{multiarch}/libnih.so* mr, - /lib/@{multiarch}/libnih-dbus.so* mr, - /lib/@{multiarch}/libdbus-1.so* mr, - /lib/@{multiarch}/libudev.so* mr, - /usr/lib/@{multiarch}/libseccomp.so* mr, - /lib/@{multiarch}/libseccomp.so* mr, + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libapparmor.so* mr, + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libcgmanager.so* mr, + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libdl{,-[0-9]*}.so* mr, + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libnih.so* mr, + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libnih-dbus.so* mr, + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libdbus-1.so* mr, + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libudev.so* mr, + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libseccomp.so* mr, + /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libcap.so* mr, @LIBEXECDIR@/snap-confine mr, diff -Nru snapd-2.27.5/cmd/snap-confine/snap-confine.c snapd-2.28.5/cmd/snap-confine/snap-confine.c --- snapd-2.27.5/cmd/snap-confine/snap-confine.c 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/cmd/snap-confine/snap-confine.c 2017-10-13 21:25:17.000000000 +0000 @@ -18,6 +18,7 @@ #include "config.h" #endif +#include #include #include #include @@ -62,6 +63,34 @@ } } +// sc_maybe_fixup_udev will remove incorrectly created udev tags +// 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() +{ + glob_t glob_res __attribute__ ((cleanup(globfree))) = { + .gl_pathv = NULL,.gl_pathc = 0,.gl_offs = 0,}; + const char *glob_pattern = "/run/udev/tags/snap_*/*nvidia*"; + int err = glob(glob_pattern, 0, NULL, &glob_res); + if (err == GLOB_NOMATCH) { + return; + } + if (err != 0) { + die("cannot search using glob pattern %s: %d", + glob_pattern, err); + } + // kill bogus udev tags for nvidia. They confuse udev, this + // undoes the damage from github.com/snapcore/snapd/pull/3671. + // + // The udev tagging of nvidia got reverted in: + // https://github.com/snapcore/snapd/pull/4022 + // but leftover files need to get removed or apps won't start + for (size_t i = 0; i < glob_res.gl_pathc; ++i) { + unlink(glob_res.gl_pathv[i]); + } +} + int main(int argc, char **argv) { // Use our super-defensive parser to figure out what we've been asked to do. @@ -190,6 +219,7 @@ // for systems that had their NS created with an // old version sc_maybe_fixup_permissions(); + sc_maybe_fixup_udev(); sc_unlock(snap_name, snap_lock_fd); // Reset path as we cannot rely on the path from the host OS to diff -Nru snapd-2.27.5/cmd/snap-confine/udev-support.c snapd-2.28.5/cmd/snap-confine/udev-support.c --- snapd-2.27.5/cmd/snap-confine/udev-support.c 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/cmd/snap-confine/udev-support.c 2017-10-13 20:09:56.000000000 +0000 @@ -32,27 +32,10 @@ #include "../libsnap-confine-private/utils.h" #include "udev-support.h" -void run_snappy_app_dev_add(struct snappy_udev *udev_s, const char *path) +void +_run_snappy_app_dev_add_majmin(struct snappy_udev *udev_s, + const char *path, unsigned major, unsigned minor) { - if (udev_s == NULL) - die("snappy_udev is NULL"); - if (udev_s->udev == NULL) - die("snappy_udev->udev is NULL"); - if (udev_s->tagname_len == 0 - || udev_s->tagname_len >= MAX_BUF - || strnlen(udev_s->tagname, MAX_BUF) != udev_s->tagname_len - || udev_s->tagname[udev_s->tagname_len] != '\0') - die("snappy_udev->tagname has invalid length"); - - debug("%s: %s %s", __func__, path, udev_s->tagname); - - struct udev_device *d = - udev_device_new_from_syspath(udev_s->udev, path); - if (d == NULL) - die("can not find %s", path); - dev_t devnum = udev_device_get_devnum(d); - udev_device_unref(d); - int status = 0; pid_t pid = fork(); if (pid < 0) { @@ -72,9 +55,9 @@ // user-controlled environment can't be used to subvert // snappy-add-dev char *env[] = { NULL }; - unsigned major = MAJOR(devnum); - unsigned minor = MINOR(devnum); sc_must_snprintf(buf, sizeof(buf), "%u:%u", major, minor); + debug("running snappy-app-dev add %s %s %s", udev_s->tagname, + path, buf); execle("/lib/udev/snappy-app-dev", "/lib/udev/snappy-app-dev", "add", udev_s->tagname, path, buf, NULL, env); die("execl failed"); @@ -87,6 +70,32 @@ die("child died with signal %i", WTERMSIG(status)); } +void run_snappy_app_dev_add(struct snappy_udev *udev_s, const char *path) +{ + if (udev_s == NULL) + die("snappy_udev is NULL"); + if (udev_s->udev == NULL) + die("snappy_udev->udev is NULL"); + if (udev_s->tagname_len == 0 + || udev_s->tagname_len >= MAX_BUF + || strnlen(udev_s->tagname, MAX_BUF) != udev_s->tagname_len + || udev_s->tagname[udev_s->tagname_len] != '\0') + die("snappy_udev->tagname has invalid length"); + + debug("%s: %s %s", __func__, path, udev_s->tagname); + + struct udev_device *d = + udev_device_new_from_syspath(udev_s->udev, path); + if (d == NULL) + die("can not find %s", path); + dev_t devnum = udev_device_get_devnum(d); + udev_device_unref(d); + + unsigned major = MAJOR(devnum); + unsigned minor = MINOR(devnum); + _run_snappy_app_dev_add_majmin(udev_s, path, major, minor); +} + /* * snappy_udev_init() - setup the snappy_udev structure. Return 0 if devices * are assigned, else return -1. Callers should use snappy_udev_cleanup() to @@ -197,6 +206,56 @@ for (int i = 0; static_devices[i] != NULL; i++) run_snappy_app_dev_add(udev_s, static_devices[i]); + // nvidia modules are proprietary and therefore aren't in sysfs and + // can't be udev tagged. For now, just add existing nvidia devices to + // the cgroup unconditionally (AppArmor will still mediate the access). + // We'll want to rethink this if snapd needs to mediate access to other + // proprietary devices. + // + // Device major and minor numbers are described in (though nvidia-uvm + // currently isn't listed): + // https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/devices.txt + char nv_path[15] = { 0 }; // /dev/nvidiaXXX + const char *nvctl_path = "/dev/nvidiactl"; + const char *nvuvm_path = "/dev/nvidia-uvm"; + const char *nvidia_modeset_path = "/dev/nvidia-modeset"; + + struct stat sbuf; + + // /dev/nvidia0 through /dev/nvidia254 + for (unsigned nv_minor = 0; nv_minor < 255; nv_minor++) { + sc_must_snprintf(nv_path, sizeof(nv_path), "/dev/nvidia%u", + nv_minor); + + // Stop trying to find devices after one is not found. In this + // manner, we'll add /dev/nvidia0 and /dev/nvidia1 but stop + // trying to find nvidia3 - nvidia254 if nvidia2 is not found. + if (stat(nv_path, &sbuf) != 0) { + break; + } + _run_snappy_app_dev_add_majmin(udev_s, nv_path, + MAJOR(sbuf.st_rdev), + MINOR(sbuf.st_rdev)); + } + + // /dev/nvidiactl + if (stat(nvctl_path, &sbuf) == 0) { + _run_snappy_app_dev_add_majmin(udev_s, nvctl_path, + MAJOR(sbuf.st_rdev), + MINOR(sbuf.st_rdev)); + } + // /dev/nvidia-uvm + if (stat(nvuvm_path, &sbuf) == 0) { + _run_snappy_app_dev_add_majmin(udev_s, nvuvm_path, + MAJOR(sbuf.st_rdev), + MINOR(sbuf.st_rdev)); + } + // /dev/nvidia-modeset + if (stat(nvidia_modeset_path, &sbuf) == 0) { + _run_snappy_app_dev_add_majmin(udev_s, nvidia_modeset_path, + MAJOR(sbuf.st_rdev), + MINOR(sbuf.st_rdev)); + } // add the assigned devices while (udev_s->assigned != NULL) { const char *path = udev_list_entry_get_name(udev_s->assigned); diff -Nru snapd-2.27.5/cmd/snapctl/main.go snapd-2.28.5/cmd/snapctl/main.go --- snapd-2.27.5/cmd/snapctl/main.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/cmd/snapctl/main.go 2017-09-13 14:47:18.000000000 +0000 @@ -24,6 +24,7 @@ "os" "github.com/snapcore/snapd/client" + "github.com/snapcore/snapd/corecfg" "github.com/snapcore/snapd/dirs" ) @@ -38,6 +39,19 @@ } func main() { + // check for internal commands + 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) + } + } + + // no internal command, route via snapd stdout, stderr, err := run() if err != nil { fmt.Fprintf(os.Stderr, "error: %s\n", err) diff -Nru snapd-2.27.5/cmd/snap-exec/main.go snapd-2.28.5/cmd/snap-exec/main.go --- snapd-2.27.5/cmd/snap-exec/main.go 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/cmd/snap-exec/main.go 2017-09-20 07:05:01.000000000 +0000 @@ -193,7 +193,7 @@ } // build the environment - env := append(os.Environ(), hook.Env()...) + env := append(os.Environ(), osutil.SubstituteEnv(hook.Env())...) // run the hook hookPath := filepath.Join(hook.Snap.HooksDir(), hook.Name) diff -Nru snapd-2.27.5/cmd/snap-repair/export_test.go snapd-2.28.5/cmd/snap-repair/export_test.go --- snapd-2.27.5/cmd/snap-repair/export_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/cmd/snap-repair/export_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -19,8 +19,87 @@ package main +import ( + "time" + + "gopkg.in/retry.v1" + + "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/httputil" +) + var ( Parser = parser ParseArgs = parseArgs Run = run ) + +func MockFetchRetryStrategy(strategy retry.Strategy) (restore func()) { + originalFetchRetryStrategy := fetchRetryStrategy + fetchRetryStrategy = strategy + return func() { + fetchRetryStrategy = originalFetchRetryStrategy + } +} + +func MockPeekRetryStrategy(strategy retry.Strategy) (restore func()) { + originalPeekRetryStrategy := peekRetryStrategy + peekRetryStrategy = strategy + return func() { + peekRetryStrategy = originalPeekRetryStrategy + } +} + +func MockMaxRepairScriptSize(maxSize int) (restore func()) { + originalMaxSize := maxRepairScriptSize + maxRepairScriptSize = maxSize + return func() { + maxRepairScriptSize = originalMaxSize + } +} + +func MockTrustedRepairRootKeys(keys []*asserts.AccountKey) (restore func()) { + original := trustedRepairRootKeys + trustedRepairRootKeys = keys + return func() { + trustedRepairRootKeys = original + } +} + +func (run *Runner) BrandModel() (brand, model string) { + return run.state.Device.Brand, run.state.Device.Model +} + +func (run *Runner) SetStateModified(modified bool) { + run.stateModified = modified +} + +func (run *Runner) SetBrandModel(brand, model string) { + run.state.Device.Brand = brand + run.state.Device.Model = model +} + +func (run *Runner) TimeLowerBound() time.Time { + return run.state.TimeLowerBound +} + +func (run *Runner) TLSTime() time.Time { + return httputil.BaseTransport(run.cli).TLSClientConfig.Time() +} + +func (run *Runner) Sequence(brand string) []*RepairState { + return run.state.Sequences[brand] +} + +func (run *Runner) SetSequence(brand string, sequence []*RepairState) { + if run.state.Sequences == nil { + run.state.Sequences = make(map[string][]*RepairState) + } + run.state.Sequences[brand] = sequence +} + +func MockTimeNow(f func() time.Time) (restore func()) { + origTimeNow := timeNow + timeNow = f + return func() { timeNow = origTimeNow } +} diff -Nru snapd-2.27.5/cmd/snap-repair/runner.go snapd-2.28.5/cmd/snap-repair/runner.go --- snapd-2.27.5/cmd/snap-repair/runner.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/cmd/snap-repair/runner.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,799 @@ +// -*- 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" + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "os" + "path/filepath" + "strconv" + "strings" + "time" + + "gopkg.in/retry.v1" + + "github.com/snapcore/snapd/arch" + "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/asserts/sysdb" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/httputil" + "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/strutil" +) + +// Repair is a runnable repair. +type Repair struct { + *asserts.Repair + + run *Runner + sequence int +} + +// SetStatus sets the status of the repair in the state and saves the latter. +func (r *Repair) SetStatus(status RepairStatus) { + brandID := r.BrandID() + cur := *r.run.state.Sequences[brandID][r.sequence-1] + cur.Status = status + r.run.setRepairState(brandID, cur) + r.run.SaveState() +} + +// Run executes the repair script leaving execution trail files on disk. +func (r *Repair) Run() error { + // XXX initial skeleton... + // write the script to disk + rundir := filepath.Join(dirs.SnapRepairRunDir, r.BrandID(), r.RepairID()) + err := os.MkdirAll(rundir, 0775) + if err != nil { + return err + } + script := filepath.Join(rundir, fmt.Sprintf("script.r%d", r.Revision())) + err = osutil.AtomicWriteFile(script, r.Body(), 0600, 0) + if err != nil { + return err + } + + // XXX actually run things and captures output etc + + return nil +} + +// Runner implements fetching, tracking and running repairs. +type Runner struct { + BaseURL *url.URL + cli *http.Client + + state state + stateModified bool + + // sequenceNext keeps track of the next integer id in a brand sequence to considered in this run, see Next. + sequenceNext map[string]int +} + +// NewRunner returns a Runner. +func NewRunner() *Runner { + run := &Runner{ + sequenceNext: make(map[string]int), + } + opts := httputil.ClientOpts{ + MayLogBody: false, + TLSConfig: &tls.Config{ + Time: run.now, + }, + } + run.cli = httputil.NewHTTPClient(&opts) + return run +} + +var ( + fetchRetryStrategy = retry.LimitCount(7, retry.LimitTime(90*time.Second, + retry.Exponential{ + Initial: 500 * time.Millisecond, + Factor: 2.5, + }, + )) + + peekRetryStrategy = retry.LimitCount(5, retry.LimitTime(44*time.Second, + retry.Exponential{ + Initial: 300 * time.Millisecond, + Factor: 2.5, + }, + )) +) + +var ( + ErrRepairNotFound = errors.New("repair not found") + ErrRepairNotModified = errors.New("repair was not modified") +) + +var ( + maxRepairScriptSize = 24 * 1024 * 1024 +) + +// Fetch retrieves a stream with the repair with the given ids and any +// auxiliary assertions. If revision>=0 the request will include an +// If-None-Match header with an ETag for the revision, and +// ErrRepairNotModified is returned if the revision is still current. +func (run *Runner) Fetch(brandID, repairID string, revision int) (repair *asserts.Repair, aux []asserts.Assertion, err error) { + u, err := run.BaseURL.Parse(fmt.Sprintf("repairs/%s/%s", brandID, repairID)) + if err != nil { + return nil, nil, err + } + + var r []asserts.Assertion + resp, err := httputil.RetryRequest(u.String(), func() (*http.Response, error) { + req, err := http.NewRequest("GET", u.String(), nil) + if err != nil { + return nil, err + } + req.Header.Set("Accept", "application/x.ubuntu.assertion") + if revision >= 0 { + req.Header.Set("If-None-Match", fmt.Sprintf(`"%d"`, revision)) + } + return run.cli.Do(req) + }, func(resp *http.Response) error { + if resp.StatusCode == 200 { + // decode assertions + dec := asserts.NewDecoderWithTypeMaxBodySize(resp.Body, map[*asserts.AssertionType]int{ + asserts.RepairType: maxRepairScriptSize, + }) + for { + a, err := dec.Decode() + if err == io.EOF { + break + } + if err != nil { + return err + } + r = append(r, a) + } + if len(r) == 0 { + return io.ErrUnexpectedEOF + } + } + return nil + }, fetchRetryStrategy) + + if err != nil { + return nil, nil, err + } + + moveTimeLowerBound := true + defer func() { + if moveTimeLowerBound { + t, _ := http.ParseTime(resp.Header.Get("Date")) + run.moveTimeLowerBound(t) + } + }() + + switch resp.StatusCode { + case 200: + // ok + case 304: + // not modified + return nil, nil, ErrRepairNotModified + case 404: + return nil, nil, ErrRepairNotFound + default: + moveTimeLowerBound = false + return nil, nil, fmt.Errorf("cannot fetch repair, unexpected status %d", resp.StatusCode) + } + + repair, aux, err = checkStream(brandID, repairID, r) + if err != nil { + return nil, nil, fmt.Errorf("cannot fetch repair, %v", err) + } + + if repair.Revision() <= revision { + // this shouldn't happen but if it does we behave like + // all the rest of assertion infrastructure and ignore + // the now superseded revision + return nil, nil, ErrRepairNotModified + } + + return +} + +func checkStream(brandID, repairID string, r []asserts.Assertion) (repair *asserts.Repair, aux []asserts.Assertion, err error) { + if len(r) == 0 { + return nil, nil, fmt.Errorf("empty repair assertions stream") + } + var ok bool + repair, ok = r[0].(*asserts.Repair) + if !ok { + return nil, nil, fmt.Errorf("unexpected first assertion %q", r[0].Type().Name) + } + + if repair.BrandID() != brandID || repair.RepairID() != repairID { + return nil, nil, fmt.Errorf("repair id mismatch %s/%s != %s/%s", repair.BrandID(), repair.RepairID(), brandID, repairID) + } + + return repair, r[1:], nil +} + +type peekResp struct { + Headers map[string]interface{} `json:"headers"` +} + +// Peek retrieves the headers for the repair with the given ids. +func (run *Runner) Peek(brandID, repairID string) (headers map[string]interface{}, err error) { + u, err := run.BaseURL.Parse(fmt.Sprintf("repairs/%s/%s", brandID, repairID)) + if err != nil { + return nil, err + } + + var rsp peekResp + + resp, err := httputil.RetryRequest(u.String(), func() (*http.Response, error) { + req, err := http.NewRequest("GET", u.String(), nil) + if err != nil { + return nil, err + } + req.Header.Set("Accept", "application/json") + return run.cli.Do(req) + }, func(resp *http.Response) error { + rsp.Headers = nil + if resp.StatusCode == 200 { + dec := json.NewDecoder(resp.Body) + return dec.Decode(&rsp) + } + return nil + }, peekRetryStrategy) + + if err != nil { + return nil, err + } + + moveTimeLowerBound := true + defer func() { + if moveTimeLowerBound { + t, _ := http.ParseTime(resp.Header.Get("Date")) + run.moveTimeLowerBound(t) + } + }() + + switch resp.StatusCode { + case 200: + // ok + case 404: + return nil, ErrRepairNotFound + default: + moveTimeLowerBound = false + return nil, fmt.Errorf("cannot peek repair headers, unexpected status %d", resp.StatusCode) + } + + headers = rsp.Headers + if headers["brand-id"] != brandID || headers["repair-id"] != repairID { + return nil, fmt.Errorf("cannot peek repair headers, repair id mismatch %s/%s != %s/%s", headers["brand-id"], headers["repair-id"], brandID, repairID) + } + + return headers, nil +} + +// deviceInfo captures information about the device. +type deviceInfo struct { + Brand string `json:"brand"` + Model string `json:"model"` +} + +// RepairStatus represents the possible statuses of a repair. +type RepairStatus int + +const ( + RetryStatus RepairStatus = iota + SkipStatus + DoneStatus +) + +// RepairState holds the current revision and status of a repair in a sequence of repairs. +type RepairState struct { + Sequence int `json:"sequence"` + Revision int `json:"revision"` + Status RepairStatus `json:"status"` +} + +// state holds the atomically updated control state of the runner with sequences of repairs and their states. +type state struct { + Device deviceInfo `json:"device"` + Sequences map[string][]*RepairState `json:"sequences,omitempty"` + TimeLowerBound time.Time `json:"time-lower-bound"` +} + +func (run *Runner) setRepairState(brandID string, state RepairState) { + if run.state.Sequences == nil { + run.state.Sequences = make(map[string][]*RepairState) + } + sequence := run.state.Sequences[brandID] + if state.Sequence > len(sequence) { + run.stateModified = true + run.state.Sequences[brandID] = append(sequence, &state) + } else if *sequence[state.Sequence-1] != state { + run.stateModified = true + sequence[state.Sequence-1] = &state + } +} + +func (run *Runner) readState() error { + r, err := os.Open(dirs.SnapRepairStateFile) + if err != nil { + return err + } + defer r.Close() + dec := json.NewDecoder(r) + return dec.Decode(&run.state) +} + +func (run *Runner) moveTimeLowerBound(t time.Time) { + if t.After(run.state.TimeLowerBound) { + run.stateModified = true + run.state.TimeLowerBound = t.UTC() + } +} + +var timeNow = time.Now + +func (run *Runner) now() time.Time { + now := timeNow().UTC() + if now.Before(run.state.TimeLowerBound) { + return run.state.TimeLowerBound + } + return now +} + +func (run *Runner) initState() error { + if err := os.MkdirAll(dirs.SnapRepairDir, 0775); err != nil { + return fmt.Errorf("cannot create repair state directory: %v", err) + } + // best-effort remove old + os.Remove(dirs.SnapRepairStateFile) + run.state = state{} + // initialize time lower bound with image built time/seed.yaml time + info, err := os.Stat(filepath.Join(dirs.SnapSeedDir, "seed.yaml")) + if err != nil { + return err + } + run.moveTimeLowerBound(info.ModTime()) + // initialize device info + if err := run.initDeviceInfo(); err != nil { + return err + } + run.stateModified = true + return run.SaveState() +} + +func trustedBackstore(trusted []asserts.Assertion) asserts.Backstore { + trustedBS := asserts.NewMemoryBackstore() + for _, t := range trusted { + trustedBS.Put(t.Type(), t) + } + return trustedBS +} + +func checkAuthorityID(a asserts.Assertion, trusted asserts.Backstore) error { + assertType := a.Type() + if assertType != asserts.AccountKeyType && assertType != asserts.AccountType { + return nil + } + // check that account and account-key assertions are signed by + // a trusted authority + acctID := a.AuthorityID() + _, err := trusted.Get(asserts.AccountType, []string{acctID}, asserts.AccountType.MaxSupportedFormat()) + if err != nil && err != asserts.ErrNotFound { + return err + } + if err == asserts.ErrNotFound { + return fmt.Errorf("%v not signed by trusted authority: %s", a.Ref(), acctID) + } + return nil +} + +func verifySignatures(a asserts.Assertion, workBS asserts.Backstore, trusted asserts.Backstore) error { + if err := checkAuthorityID(a, trusted); err != nil { + return err + } + acctKeyMaxSuppFormat := asserts.AccountKeyType.MaxSupportedFormat() + + seen := make(map[string]bool) + bottom := false + for !bottom { + u := a.Ref().Unique() + if seen[u] { + return fmt.Errorf("circular assertions") + } + seen[u] = true + signKey := []string{a.SignKeyID()} + key, err := trusted.Get(asserts.AccountKeyType, signKey, acctKeyMaxSuppFormat) + if err != nil && err != asserts.ErrNotFound { + return err + } + if err == nil { + bottom = true + } else { + key, err = workBS.Get(asserts.AccountKeyType, signKey, acctKeyMaxSuppFormat) + if err != nil && err != asserts.ErrNotFound { + return err + } + if err == asserts.ErrNotFound { + return fmt.Errorf("cannot find public key %q", signKey[0]) + } + if err := checkAuthorityID(key, trusted); err != nil { + return err + } + } + if err := asserts.CheckSignature(a, key.(*asserts.AccountKey), nil, time.Time{}); err != nil { + return err + } + a = key + } + return nil +} + +func (run *Runner) initDeviceInfo() error { + const errPrefix = "cannot set device information: " + + workBS := asserts.NewMemoryBackstore() + assertSeedDir := filepath.Join(dirs.SnapSeedDir, "assertions") + dc, err := ioutil.ReadDir(assertSeedDir) + if err != nil { + return err + } + var model *asserts.Model + for _, fi := range dc { + fn := filepath.Join(assertSeedDir, fi.Name()) + f, err := os.Open(fn) + if err != nil { + // best effort + continue + } + dec := asserts.NewDecoder(f) + for { + a, err := dec.Decode() + if err != nil { + // best effort + break + } + switch a.Type() { + case asserts.ModelType: + if model != nil { + return fmt.Errorf(errPrefix + "multiple models in seed assertions") + } + model = a.(*asserts.Model) + case asserts.AccountType, asserts.AccountKeyType: + workBS.Put(a.Type(), a) + } + } + } + if model == nil { + return fmt.Errorf(errPrefix + "no model assertion in seed data") + } + trustedBS := trustedBackstore(sysdb.Trusted()) + if err := verifySignatures(model, workBS, trustedBS); err != nil { + return fmt.Errorf(errPrefix+"%v", err) + } + acctPK := []string{model.BrandID()} + acctMaxSupFormat := asserts.AccountType.MaxSupportedFormat() + acct, err := trustedBS.Get(asserts.AccountType, acctPK, acctMaxSupFormat) + if err != nil { + var err error + acct, err = workBS.Get(asserts.AccountType, acctPK, acctMaxSupFormat) + if err != nil { + return fmt.Errorf(errPrefix + "no brand account assertion in seed data") + } + } + if err := verifySignatures(acct, workBS, trustedBS); err != nil { + return fmt.Errorf(errPrefix+"%v", err) + } + run.state.Device.Brand = model.BrandID() + run.state.Device.Model = model.Model() + return nil +} + +// LoadState loads the repairs' state from disk, and (re)initializes it if it's missing or corrupted. +func (run *Runner) LoadState() error { + err := run.readState() + if err == nil { + return nil + } + // error => initialize from scratch + if !os.IsNotExist(err) { + logger.Noticef("cannor read repair state: %v", err) + } + return run.initState() +} + +// SaveState saves the repairs' state to disk. +func (run *Runner) SaveState() error { + if !run.stateModified { + return nil + } + m, err := json.Marshal(&run.state) + if err != nil { + return fmt.Errorf("cannot marshal repair state: %v", err) + } + err = osutil.AtomicWriteFile(dirs.SnapRepairStateFile, m, 0600, 0) + if err != nil { + return fmt.Errorf("cannot save repair state: %v", err) + } + run.stateModified = false + return nil +} + +func stringList(headers map[string]interface{}, name string) ([]string, error) { + v, ok := headers[name] + if !ok { + return nil, nil + } + l, ok := v.([]interface{}) + if !ok { + return nil, fmt.Errorf("header %q is not a list", name) + } + r := make([]string, len(l)) + for i, v := range l { + s, ok := v.(string) + if !ok { + return nil, fmt.Errorf("header %q contains non-string elements", name) + } + r[i] = s + } + return r, nil +} + +// Applicable returns whether a repair with the given headers is applicable to the device. +func (run *Runner) Applicable(headers map[string]interface{}) bool { + series, err := stringList(headers, "series") + if err != nil { + return false + } + if len(series) != 0 && !strutil.ListContains(series, release.Series) { + return false + } + archs, err := stringList(headers, "architectures") + if err != nil { + return false + } + if len(archs) != 0 && !strutil.ListContains(archs, arch.UbuntuArchitecture()) { + return false + } + brandModel := fmt.Sprintf("%s/%s", run.state.Device.Brand, run.state.Device.Model) + models, err := stringList(headers, "models") + if err != nil { + return false + } + if len(models) != 0 && !strutil.ListContains(models, brandModel) { + // model prefix matching: brand/prefix* + hit := false + for _, patt := range models { + if strings.HasSuffix(patt, "*") && strings.ContainsRune(patt, '/') { + if strings.HasPrefix(brandModel, strings.TrimSuffix(patt, "*")) { + hit = true + break + } + } + } + if !hit { + return false + } + } + return true +} + +var errSkip = errors.New("repair unnecessary on this system") + +func (run *Runner) fetch(brandID string, seq int) (repair *asserts.Repair, aux []asserts.Assertion, err error) { + repairID := strconv.Itoa(seq) + headers, err := run.Peek(brandID, repairID) + if err != nil { + return nil, nil, err + } + if !run.Applicable(headers) { + return nil, nil, errSkip + } + return run.Fetch(brandID, repairID, -1) +} + +func (run *Runner) refetch(brandID string, seq, revision int) (repair *asserts.Repair, aux []asserts.Assertion, err error) { + repairID := strconv.Itoa(seq) + return run.Fetch(brandID, repairID, revision) +} + +func (run *Runner) saveStream(brandID string, seq int, repair *asserts.Repair, aux []asserts.Assertion) error { + repairID := strconv.Itoa(seq) + d := filepath.Join(dirs.SnapRepairAssertsDir, brandID, repairID) + err := os.MkdirAll(d, 0775) + if err != nil { + return err + } + buf := &bytes.Buffer{} + enc := asserts.NewEncoder(buf) + r := append([]asserts.Assertion{repair}, aux...) + for _, a := range r { + if err := enc.Encode(a); err != nil { + return fmt.Errorf("cannot encode repair assertions %s-%s for saving: %v", brandID, repairID, err) + } + } + p := filepath.Join(d, fmt.Sprintf("repair.r%d", r[0].Revision())) + return osutil.AtomicWriteFile(p, buf.Bytes(), 0600, 0) +} + +func (run *Runner) readSavedStream(brandID string, seq, revision int) (repair *asserts.Repair, aux []asserts.Assertion, err error) { + repairID := strconv.Itoa(seq) + d := filepath.Join(dirs.SnapRepairAssertsDir, brandID, repairID) + p := filepath.Join(d, fmt.Sprintf("repair.r%d", revision)) + f, err := os.Open(p) + if err != nil { + return nil, nil, err + } + defer f.Close() + + dec := asserts.NewDecoder(f) + var r []asserts.Assertion + for { + a, err := dec.Decode() + if err == io.EOF { + break + } + if err != nil { + return nil, nil, fmt.Errorf("cannot decode repair assertions %s-%s from disk: %v", brandID, repairID, err) + } + r = append(r, a) + } + return checkStream(brandID, repairID, r) +} + +func (run *Runner) makeReady(brandID string, sequenceNext int) (repair *asserts.Repair, err error) { + sequence := run.state.Sequences[brandID] + var aux []asserts.Assertion + var state RepairState + if sequenceNext <= len(sequence) { + // consider retries + state = *sequence[sequenceNext-1] + if state.Status != RetryStatus { + return nil, errSkip + } + var err error + repair, aux, err = run.refetch(brandID, state.Sequence, state.Revision) + if err != nil { + if err != ErrRepairNotModified { + logger.Noticef("cannot refetch repair %s-%d, will retry what is on disk: %v", brandID, sequenceNext, err) + } + // try to use what we have already on disk + repair, aux, err = run.readSavedStream(brandID, state.Sequence, state.Revision) + if err != nil { + return nil, err + } + } + } else { + // fetch the next repair in the sequence + // assumes no gaps, each repair id is present so far, + // possibly skipped + var err error + repair, aux, err = run.fetch(brandID, sequenceNext) + if err != nil && err != errSkip { + return nil, err + } + state = RepairState{ + Sequence: sequenceNext, + } + if err == errSkip { + // TODO: store headers to justify decision + state.Status = SkipStatus + run.setRepairState(brandID, state) + return nil, errSkip + } + } + // verify with signatures + if err := run.Verify(repair, aux); err != nil { + return nil, fmt.Errorf("cannot verify repair %s-%d: %v", brandID, state.Sequence, err) + } + if err := run.saveStream(brandID, state.Sequence, repair, aux); err != nil { + return nil, err + } + state.Revision = repair.Revision() + if !run.Applicable(repair.Headers()) { + state.Status = SkipStatus + run.setRepairState(brandID, state) + return nil, errSkip + } + run.setRepairState(brandID, state) + return repair, nil +} + +// Next returns the next repair for the brand id sequence to run/retry or ErrRepairNotFound if there is none atm. It updates the state as required. +func (run *Runner) Next(brandID string) (*Repair, error) { + sequenceNext := run.sequenceNext[brandID] + if sequenceNext == 0 { + sequenceNext = 1 + } + for { + repair, err := run.makeReady(brandID, sequenceNext) + // SaveState is a no-op unless makeReady modified the state + stateErr := run.SaveState() + if err != nil && err != errSkip && err != ErrRepairNotFound { + // err is a non trivial error, just log the SaveState error and report err + if stateErr != nil { + logger.Noticef("%v", stateErr) + } + return nil, err + } + if stateErr != nil { + return nil, stateErr + } + if err == ErrRepairNotFound { + return nil, ErrRepairNotFound + } + + sequenceNext += 1 + run.sequenceNext[brandID] = sequenceNext + if err == errSkip { + continue + } + + return &Repair{ + Repair: repair, + run: run, + sequence: sequenceNext - 1, + }, nil + } +} + +// Limit trust to specific keys while there's no delegation or limited +// keys support. The obtained assertion stream may also include +// account keys that are directly or indirectly signed by a trusted +// key. +var ( + trustedRepairRootKeys []*asserts.AccountKey +) + +// Verify verifies that the repair is properly signed by the specific +// trusted root keys or by account keys in the stream (passed via aux) +// directly or indirectly signed by a trusted key. +func (run *Runner) Verify(repair *asserts.Repair, aux []asserts.Assertion) error { + workBS := asserts.NewMemoryBackstore() + for _, a := range aux { + if a.Type() != asserts.AccountKeyType { + continue + } + err := workBS.Put(asserts.AccountKeyType, a) + if err != nil { + return err + } + } + trustedBS := asserts.NewMemoryBackstore() + for _, t := range trustedRepairRootKeys { + trustedBS.Put(asserts.AccountKeyType, t) + } + for _, t := range sysdb.Trusted() { + if t.Type() == asserts.AccountType { + trustedBS.Put(asserts.AccountType, t) + } + } + + return verifySignatures(repair, workBS, trustedBS) +} diff -Nru snapd-2.27.5/cmd/snap-repair/runner_test.go snapd-2.28.5/cmd/snap-repair/runner_test.go --- snapd-2.27.5/cmd/snap-repair/runner_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/cmd/snap-repair/runner_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,1393 @@ +// -*- 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" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "strconv" + "strings" + "time" + + . "gopkg.in/check.v1" + "gopkg.in/retry.v1" + + "github.com/snapcore/snapd/arch" + "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/asserts/assertstest" + "github.com/snapcore/snapd/asserts/sysdb" + repair "github.com/snapcore/snapd/cmd/snap-repair" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" +) + +type runnerSuite struct { + tmpdir string + + seedTime time.Time + t0 time.Time + + storeSigning *assertstest.StoreStack + + brandSigning *assertstest.SigningDB + brandAcct *asserts.Account + brandAcctKey *asserts.AccountKey + + modelAs *asserts.Model + + seedAssertsDir string + + repairRootAcctKey *asserts.AccountKey + repairsAcctKey *asserts.AccountKey + + repairsSigning *assertstest.SigningDB +} + +var _ = Suite(&runnerSuite{}) + +func (s *runnerSuite) SetUpSuite(c *C) { + s.storeSigning = assertstest.NewStoreStack("canonical", nil) + + brandPrivKey, _ := assertstest.GenerateKey(752) + + s.brandAcct = assertstest.NewAccount(s.storeSigning, "my-brand", map[string]interface{}{ + "account-id": "my-brand", + }, "") + s.brandAcctKey = assertstest.NewAccountKey(s.storeSigning, s.brandAcct, nil, brandPrivKey.PublicKey(), "") + s.brandSigning = assertstest.NewSigningDB("my-brand", brandPrivKey) + + modelAs, err := s.brandSigning.Sign(asserts.ModelType, map[string]interface{}{ + "series": "16", + "brand-id": "my-brand", + "model": "my-model-2", + "architecture": "armhf", + "gadget": "gadget", + "kernel": "kernel", + "timestamp": time.Now().UTC().Format(time.RFC3339), + }, nil, "") + c.Assert(err, IsNil) + s.modelAs = modelAs.(*asserts.Model) + + repairRootKey, _ := assertstest.GenerateKey(1024) + + s.repairRootAcctKey = assertstest.NewAccountKey(s.storeSigning.RootSigning, s.storeSigning.TrustedAccount, nil, repairRootKey.PublicKey(), "") + + repairsKey, _ := assertstest.GenerateKey(752) + + repairRootSigning := assertstest.NewSigningDB("canonical", repairRootKey) + + s.repairsAcctKey = assertstest.NewAccountKey(repairRootSigning, s.storeSigning.TrustedAccount, nil, repairsKey.PublicKey(), "") + + s.repairsSigning = assertstest.NewSigningDB("canonical", repairsKey) +} + +func (s *runnerSuite) SetUpTest(c *C) { + s.tmpdir = c.MkDir() + dirs.SetRootDir(s.tmpdir) + + s.seedAssertsDir = filepath.Join(dirs.SnapSeedDir, "assertions") + + // dummy seed yaml + err := os.MkdirAll(dirs.SnapSeedDir, 0755) + c.Assert(err, IsNil) + seedYamlFn := filepath.Join(dirs.SnapSeedDir, "seed.yaml") + err = ioutil.WriteFile(seedYamlFn, nil, 0644) + c.Assert(err, IsNil) + seedTime, err := time.Parse(time.RFC3339, "2017-08-11T15:49:49Z") + c.Assert(err, IsNil) + err = os.Chtimes(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), seedTime, seedTime) + c.Assert(err, IsNil) + s.seedTime = seedTime + + s.t0 = time.Now().UTC().Truncate(time.Minute) +} + +func (s *runnerSuite) TearDownTest(c *C) { + dirs.SetRootDir("/") +} + +var ( + testKey = `type: account-key +authority-id: canonical +account-id: canonical +name: repair +public-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj +since: 2015-11-16T15:04:00Z +body-length: 149 +sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij + +AcZrBFaFwYABAvCX5A8dTcdLdhdiuy2YRHO5CAfM5InQefkKOhNMUq2yfi3Sk6trUHxskhZkPnm4 +NKx2yRr332q7AJXQHLX+DrZ29ycyoQ2NQGO3eAfQ0hjAAQFYBF8SSh5SutPu5XCVABEBAAE= + +AXNpZw== +` + + testRepair = `type: repair +authority-id: canonical +brand-id: canonical +repair-id: 2 +architectures: + - amd64 + - arm64 +series: + - 16 +models: + - xyz/frobinator +timestamp: 2017-03-30T12:22:16Z +body-length: 7 +sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj + +script + + +AXNpZw== +` + testHeadersResp = `{"headers": +{"architectures":["amd64","arm64"],"authority-id":"canonical","body-length":"7","brand-id":"canonical","models":["xyz/frobinator"],"repair-id":"2","series":["16"],"sign-key-sha3-384":"KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj","timestamp":"2017-03-30T12:22:16Z","type":"repair"}}` +) + +func mustParseURL(s string) *url.URL { + u, err := url.Parse(s) + if err != nil { + panic(err) + } + return u +} + +func (s *runnerSuite) mockBrokenTimeNowSetToEpoch(c *C, runner *repair.Runner) (restore func()) { + epoch := time.Unix(0, 0) + r := repair.MockTimeNow(func() time.Time { + return epoch + }) + c.Check(runner.TLSTime().Equal(epoch), Equals, true) + return r +} + +func (s *runnerSuite) checkBrokenTimeNowMitigated(c *C, runner *repair.Runner) { + c.Check(runner.TLSTime().Before(s.t0), Equals, false) +} + +func (s *runnerSuite) TestFetchJustRepair(c *C) { + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion") + c.Check(r.URL.Path, Equals, "/repairs/canonical/2") + io.WriteString(w, testRepair) + })) + + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + + r := s.mockBrokenTimeNowSetToEpoch(c, runner) + defer r() + + repair, aux, err := runner.Fetch("canonical", "2", -1) + c.Assert(err, IsNil) + c.Check(repair, NotNil) + c.Check(aux, HasLen, 0) + c.Check(repair.BrandID(), Equals, "canonical") + c.Check(repair.RepairID(), Equals, "2") + c.Check(repair.Body(), DeepEquals, []byte("script\n")) + + s.checkBrokenTimeNowMitigated(c, runner) +} + +func (s *runnerSuite) TestFetchScriptTooBig(c *C) { + restore := repair.MockMaxRepairScriptSize(4) + defer restore() + + n := 0 + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + n++ + c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion") + c.Check(r.URL.Path, Equals, "/repairs/canonical/2") + io.WriteString(w, testRepair) + })) + + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + + _, _, err := runner.Fetch("canonical", "2", -1) + c.Assert(err, ErrorMatches, `assertion body length 7 exceeds maximum body size 4 for "repair".*`) + c.Assert(n, Equals, 1) +} + +var ( + testRetryStrategy = retry.LimitCount(5, retry.LimitTime(1*time.Second, + retry.Exponential{ + Initial: 1 * time.Millisecond, + Factor: 1, + }, + )) +) + +func (s *runnerSuite) TestFetch500(c *C) { + restore := repair.MockFetchRetryStrategy(testRetryStrategy) + defer restore() + + n := 0 + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + n++ + w.WriteHeader(500) + })) + + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + + _, _, err := runner.Fetch("canonical", "2", -1) + c.Assert(err, ErrorMatches, "cannot fetch repair, unexpected status 500") + c.Assert(n, Equals, 5) +} + +func (s *runnerSuite) TestFetchEmpty(c *C) { + restore := repair.MockFetchRetryStrategy(testRetryStrategy) + defer restore() + + n := 0 + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + n++ + w.WriteHeader(200) + })) + + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + + _, _, err := runner.Fetch("canonical", "2", -1) + c.Assert(err, Equals, io.ErrUnexpectedEOF) + c.Assert(n, Equals, 5) +} + +func (s *runnerSuite) TestFetchBroken(c *C) { + restore := repair.MockFetchRetryStrategy(testRetryStrategy) + defer restore() + + n := 0 + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + n++ + w.WriteHeader(200) + io.WriteString(w, "xyz:") + })) + + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + + _, _, err := runner.Fetch("canonical", "2", -1) + c.Assert(err, Equals, io.ErrUnexpectedEOF) + c.Assert(n, Equals, 5) +} + +func (s *runnerSuite) TestFetchNotFound(c *C) { + restore := repair.MockFetchRetryStrategy(testRetryStrategy) + defer restore() + + n := 0 + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + n++ + w.WriteHeader(404) + })) + + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + + r := s.mockBrokenTimeNowSetToEpoch(c, runner) + defer r() + + _, _, err := runner.Fetch("canonical", "2", -1) + c.Assert(err, Equals, repair.ErrRepairNotFound) + c.Assert(n, Equals, 1) + + s.checkBrokenTimeNowMitigated(c, runner) +} + +func (s *runnerSuite) TestFetchIfNoneMatchNotModified(c *C) { + restore := repair.MockFetchRetryStrategy(testRetryStrategy) + defer restore() + + n := 0 + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + n++ + c.Check(r.Header.Get("If-None-Match"), Equals, `"0"`) + w.WriteHeader(304) + })) + + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + + r := s.mockBrokenTimeNowSetToEpoch(c, runner) + defer r() + + _, _, err := runner.Fetch("canonical", "2", 0) + c.Assert(err, Equals, repair.ErrRepairNotModified) + c.Assert(n, Equals, 1) + + s.checkBrokenTimeNowMitigated(c, runner) +} + +func (s *runnerSuite) TestFetchIgnoreSupersededRevision(c *C) { + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, testRepair) + })) + + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + + _, _, err := runner.Fetch("canonical", "2", 2) + c.Assert(err, Equals, repair.ErrRepairNotModified) +} + +func (s *runnerSuite) TestFetchIdMismatch(c *C) { + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion") + io.WriteString(w, testRepair) + })) + + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + + _, _, err := runner.Fetch("canonical", "4", -1) + c.Assert(err, ErrorMatches, `cannot fetch repair, repair id mismatch canonical/2 != canonical/4`) +} + +func (s *runnerSuite) TestFetchWrongFirstType(c *C) { + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion") + c.Check(r.URL.Path, Equals, "/repairs/canonical/2") + io.WriteString(w, testKey) + })) + + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + + _, _, err := runner.Fetch("canonical", "2", -1) + c.Assert(err, ErrorMatches, `cannot fetch repair, unexpected first assertion "account-key"`) +} + +func (s *runnerSuite) TestFetchRepairPlusKey(c *C) { + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion") + c.Check(r.URL.Path, Equals, "/repairs/canonical/2") + io.WriteString(w, testRepair) + io.WriteString(w, "\n") + io.WriteString(w, testKey) + })) + + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + + repair, aux, err := runner.Fetch("canonical", "2", -1) + c.Assert(err, IsNil) + c.Check(repair, NotNil) + c.Check(aux, HasLen, 1) + _, ok := aux[0].(*asserts.AccountKey) + c.Check(ok, Equals, true) +} + +func (s *runnerSuite) TestPeek(c *C) { + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + c.Check(r.Header.Get("Accept"), Equals, "application/json") + c.Check(r.URL.Path, Equals, "/repairs/canonical/2") + io.WriteString(w, testHeadersResp) + })) + + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + + r := s.mockBrokenTimeNowSetToEpoch(c, runner) + defer r() + + h, err := runner.Peek("canonical", "2") + c.Assert(err, IsNil) + c.Check(h["series"], DeepEquals, []interface{}{"16"}) + c.Check(h["architectures"], DeepEquals, []interface{}{"amd64", "arm64"}) + c.Check(h["models"], DeepEquals, []interface{}{"xyz/frobinator"}) + + s.checkBrokenTimeNowMitigated(c, runner) +} + +func (s *runnerSuite) TestPeek500(c *C) { + restore := repair.MockPeekRetryStrategy(testRetryStrategy) + defer restore() + + n := 0 + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + n++ + w.WriteHeader(500) + })) + + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + + _, err := runner.Peek("canonical", "2") + c.Assert(err, ErrorMatches, "cannot peek repair headers, unexpected status 500") + c.Assert(n, Equals, 5) +} + +func (s *runnerSuite) TestPeekInvalid(c *C) { + restore := repair.MockPeekRetryStrategy(testRetryStrategy) + defer restore() + + n := 0 + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + n++ + w.WriteHeader(200) + io.WriteString(w, "{") + })) + + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + + _, err := runner.Peek("canonical", "2") + c.Assert(err, Equals, io.ErrUnexpectedEOF) + c.Assert(n, Equals, 5) +} + +func (s *runnerSuite) TestPeekNotFound(c *C) { + n := 0 + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + n++ + w.WriteHeader(404) + })) + + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + + r := s.mockBrokenTimeNowSetToEpoch(c, runner) + defer r() + + _, err := runner.Peek("canonical", "2") + c.Assert(err, Equals, repair.ErrRepairNotFound) + c.Assert(n, Equals, 1) + + s.checkBrokenTimeNowMitigated(c, runner) +} + +func (s *runnerSuite) TestPeekIdMismatch(c *C) { + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + c.Check(r.Header.Get("Accept"), Equals, "application/json") + io.WriteString(w, testHeadersResp) + })) + + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + + _, err := runner.Peek("canonical", "4") + c.Assert(err, ErrorMatches, `cannot peek repair headers, repair id mismatch canonical/2 != canonical/4`) +} + +const freshStateJSON = `{"device":{"brand":"my-brand","model":"my-model"},"time-lower-bound":"2017-08-11T15:49:49Z"}` + +func (s *runnerSuite) freshState(c *C) { + err := os.MkdirAll(dirs.SnapRepairDir, 0775) + c.Assert(err, IsNil) + err = ioutil.WriteFile(dirs.SnapRepairStateFile, []byte(freshStateJSON), 0600) + c.Assert(err, IsNil) +} + +func (s *runnerSuite) TestLoadState(c *C) { + s.freshState(c) + + runner := repair.NewRunner() + err := runner.LoadState() + c.Assert(err, IsNil) + brand, model := runner.BrandModel() + c.Check(brand, Equals, "my-brand") + c.Check(model, Equals, "my-model") +} + +func (s *runnerSuite) initSeed(c *C) { + err := os.MkdirAll(s.seedAssertsDir, 0775) + c.Assert(err, IsNil) +} + +func (s *runnerSuite) writeSeedAssert(c *C, fname string, a asserts.Assertion) { + err := ioutil.WriteFile(filepath.Join(s.seedAssertsDir, fname), asserts.Encode(a), 0644) + c.Assert(err, IsNil) +} + +func (s *runnerSuite) rmSeedAssert(c *C, fname string) { + err := os.Remove(filepath.Join(s.seedAssertsDir, fname)) + c.Assert(err, IsNil) +} + +func (s *runnerSuite) TestLoadStateInitState(c *C) { + // sanity + c.Check(osutil.IsDirectory(dirs.SnapRepairDir), Equals, false) + c.Check(osutil.FileExists(dirs.SnapRepairStateFile), Equals, false) + // setup realistic seed/assertions + r := sysdb.InjectTrusted(s.storeSigning.Trusted) + defer r() + s.initSeed(c) + s.writeSeedAssert(c, "store.account-key", s.storeSigning.StoreAccountKey("")) + s.writeSeedAssert(c, "brand.account", s.brandAcct) + s.writeSeedAssert(c, "brand.account-key", s.brandAcctKey) + s.writeSeedAssert(c, "model", s.modelAs) + + runner := repair.NewRunner() + err := runner.LoadState() + c.Assert(err, IsNil) + c.Check(osutil.FileExists(dirs.SnapRepairStateFile), Equals, true) + + brand, model := runner.BrandModel() + c.Check(brand, Equals, "my-brand") + c.Check(model, Equals, "my-model-2") + + c.Check(runner.TimeLowerBound().Equal(s.seedTime), Equals, true) +} + +func (s *runnerSuite) TestLoadStateInitDeviceInfoFail(c *C) { + // sanity + c.Check(osutil.IsDirectory(dirs.SnapRepairDir), Equals, false) + c.Check(osutil.FileExists(dirs.SnapRepairStateFile), Equals, false) + // setup realistic seed/assertions + r := sysdb.InjectTrusted(s.storeSigning.Trusted) + defer r() + s.initSeed(c) + + const errPrefix = "cannot set device information: " + tests := []struct { + breakFunc func() + expectedErr string + }{ + {func() { s.rmSeedAssert(c, "model") }, errPrefix + "no model assertion in seed data"}, + {func() { s.rmSeedAssert(c, "brand.account") }, errPrefix + "no brand account assertion in seed data"}, + {func() { s.rmSeedAssert(c, "brand.account-key") }, errPrefix + `cannot find public key.*`}, + {func() { + // broken signature + blob := asserts.Encode(s.brandAcct) + err := ioutil.WriteFile(filepath.Join(s.seedAssertsDir, "brand.account"), blob[:len(blob)-3], 0644) + c.Assert(err, IsNil) + }, errPrefix + "cannot decode signature:.*"}, + {func() { s.writeSeedAssert(c, "model2", s.modelAs) }, errPrefix + "multiple models in seed assertions"}, + } + + for _, test := range tests { + s.writeSeedAssert(c, "store.account-key", s.storeSigning.StoreAccountKey("")) + s.writeSeedAssert(c, "brand.account", s.brandAcct) + s.writeSeedAssert(c, "brand.account-key", s.brandAcctKey) + s.writeSeedAssert(c, "model", s.modelAs) + + test.breakFunc() + + runner := repair.NewRunner() + err := runner.LoadState() + c.Check(err, ErrorMatches, test.expectedErr) + } +} + +func (s *runnerSuite) TestTLSTime(c *C) { + s.freshState(c) + runner := repair.NewRunner() + err := runner.LoadState() + c.Assert(err, IsNil) + epoch := time.Unix(0, 0) + r := repair.MockTimeNow(func() time.Time { + return epoch + }) + defer r() + c.Check(runner.TLSTime().Equal(s.seedTime), Equals, true) +} + +func (s *runnerSuite) TestLoadStateInitStateFail(c *C) { + par := filepath.Dir(dirs.SnapSeedDir) + err := os.Chmod(par, 0555) + c.Assert(err, IsNil) + defer os.Chmod(par, 0775) + + runner := repair.NewRunner() + err = runner.LoadState() + c.Check(err, ErrorMatches, `cannot create repair state directory:.*`) +} + +func (s *runnerSuite) TestSaveStateFail(c *C) { + s.freshState(c) + + runner := repair.NewRunner() + err := runner.LoadState() + c.Assert(err, IsNil) + + err = os.Chmod(dirs.SnapRepairDir, 0555) + c.Assert(err, IsNil) + defer os.Chmod(dirs.SnapRepairDir, 0775) + + // no error because this is a no-op + err = runner.SaveState() + c.Check(err, IsNil) + + // mark as modified + runner.SetStateModified(true) + + err = runner.SaveState() + c.Check(err, ErrorMatches, `cannot save repair state:.*`) +} + +func (s *runnerSuite) TestSaveState(c *C) { + s.freshState(c) + + runner := repair.NewRunner() + err := runner.LoadState() + c.Assert(err, IsNil) + + runner.SetSequence("canonical", []*repair.RepairState{ + {Sequence: 1, Revision: 3}, + }) + // mark as modified + runner.SetStateModified(true) + + err = runner.SaveState() + c.Assert(err, IsNil) + + data, err := ioutil.ReadFile(dirs.SnapRepairStateFile) + c.Assert(err, IsNil) + c.Check(string(data), Equals, `{"device":{"brand":"my-brand","model":"my-model"},"sequences":{"canonical":[{"sequence":1,"revision":3,"status":0}]},"time-lower-bound":"2017-08-11T15:49:49Z"}`) +} + +func (s *runnerSuite) TestApplicable(c *C) { + s.freshState(c) + runner := repair.NewRunner() + err := runner.LoadState() + c.Assert(err, IsNil) + + scenarios := []struct { + headers map[string]interface{} + applicable bool + }{ + {nil, true}, + {map[string]interface{}{"series": []interface{}{"18"}}, false}, + {map[string]interface{}{"series": []interface{}{"18", "16"}}, true}, + {map[string]interface{}{"series": "18"}, false}, + {map[string]interface{}{"series": []interface{}{18}}, false}, + {map[string]interface{}{"architectures": []interface{}{arch.UbuntuArchitecture()}}, true}, + {map[string]interface{}{"architectures": []interface{}{"other-arch"}}, false}, + {map[string]interface{}{"architectures": []interface{}{"other-arch", arch.UbuntuArchitecture()}}, true}, + {map[string]interface{}{"architectures": arch.UbuntuArchitecture()}, false}, + {map[string]interface{}{"models": []interface{}{"my-brand/my-model"}}, true}, + {map[string]interface{}{"models": []interface{}{"other-brand/other-model"}}, false}, + {map[string]interface{}{"models": []interface{}{"other-brand/other-model", "my-brand/my-model"}}, true}, + {map[string]interface{}{"models": "my-brand/my-model"}, false}, + // model prefix matches + {map[string]interface{}{"models": []interface{}{"my-brand/*"}}, true}, + {map[string]interface{}{"models": []interface{}{"my-brand/my-mod*"}}, true}, + {map[string]interface{}{"models": []interface{}{"my-brand/xxx*"}}, false}, + {map[string]interface{}{"models": []interface{}{"my-brand/my-mod*", "my-brand/xxx*"}}, true}, + {map[string]interface{}{"models": []interface{}{"my*"}}, false}, + } + + for _, scen := range scenarios { + ok := runner.Applicable(scen.headers) + c.Check(ok, Equals, scen.applicable, Commentf("%v", scen)) + } +} + +var ( + nextRepairs = []string{`type: repair +authority-id: canonical +brand-id: canonical +repair-id: 1 +timestamp: 2017-07-01T12:00:00Z +body-length: 8 +sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj + +scriptA + + +AXNpZw==`, + `type: repair +authority-id: canonical +brand-id: canonical +repair-id: 2 +series: + - 33 +timestamp: 2017-07-02T12:00:00Z +body-length: 8 +sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj + +scriptB + + +AXNpZw==`, + `type: repair +revision: 2 +authority-id: canonical +brand-id: canonical +repair-id: 3 +series: + - 16 +timestamp: 2017-07-03T12:00:00Z +body-length: 8 +sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj + +scriptC + + +AXNpZw== +`} + + repair3Rev4 = `type: repair +revision: 4 +authority-id: canonical +brand-id: canonical +repair-id: 3 +series: + - 16 +timestamp: 2017-07-03T12:00:00Z +body-length: 9 +sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj + +scriptC2 + + +AXNpZw== +` + + repair4 = `type: repair +authority-id: canonical +brand-id: canonical +repair-id: 4 +timestamp: 2017-07-03T12:00:00Z +body-length: 8 +sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj + +scriptD + + +AXNpZw== +` +) + +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) { + urlPath := r.URL.Path + if redirectFirst && r.Header.Get("Accept") == asserts.MediaType { + if !strings.HasPrefix(urlPath, "/final/") { + // redirect + finalURL := mockServer.URL + "/final" + r.URL.Path + w.Header().Set("Location", finalURL) + w.WriteHeader(302) + return + } + urlPath = strings.TrimPrefix(urlPath, "/final") + } + + c.Check(strings.HasPrefix(urlPath, "/repairs/canonical/"), Equals, true) + + seq, err := strconv.Atoi(strings.TrimPrefix(urlPath, "/repairs/canonical/")) + c.Assert(err, IsNil) + + if seq > len(*seqRepairs) { + w.WriteHeader(404) + return + } + + rpr := []byte((*seqRepairs)[seq-1]) + dec := asserts.NewDecoder(bytes.NewBuffer(rpr)) + repair, err := dec.Decode() + c.Assert(err, IsNil) + + switch r.Header.Get("Accept") { + case "application/json": + b, err := json.Marshal(map[string]interface{}{ + "headers": repair.Headers(), + }) + c.Assert(err, IsNil) + w.Write(b) + case asserts.MediaType: + etag := fmt.Sprintf(`"%d"`, repair.Revision()) + if strings.Contains(r.Header.Get("If-None-Match"), etag) { + w.WriteHeader(304) + return + } + w.Write(rpr) + } + })) + + c.Assert(mockServer, NotNil) + + return mockServer +} + +func (s *runnerSuite) TestVerify(c *C) { + r1 := sysdb.InjectTrusted(s.storeSigning.Trusted) + defer r1() + r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey}) + defer r2() + + runner := repair.NewRunner() + + a, err := s.repairsSigning.Sign(asserts.RepairType, map[string]interface{}{ + "brand-id": "canonical", + "repair-id": "2", + "timestamp": time.Now().UTC().Format(time.RFC3339), + }, []byte("#script"), "") + c.Assert(err, IsNil) + rpr := a.(*asserts.Repair) + + err = runner.Verify(rpr, []asserts.Assertion{s.repairsAcctKey}) + c.Check(err, IsNil) +} + +func (s *runnerSuite) signSeqRepairs(c *C, repairs []string) []string { + var seq []string + for _, rpr := range repairs { + decoded, err := asserts.Decode([]byte(rpr)) + c.Assert(err, IsNil) + signed, err := s.repairsSigning.Sign(asserts.RepairType, decoded.Headers(), decoded.Body(), "") + c.Assert(err, IsNil) + buf := &bytes.Buffer{} + enc := asserts.NewEncoder(buf) + enc.Encode(signed) + enc.Encode(s.repairsAcctKey) + seq = append(seq, buf.String()) + } + return seq +} + +func (s *runnerSuite) loadSequences(c *C) map[string][]*repair.RepairState { + data, err := ioutil.ReadFile(dirs.SnapRepairStateFile) + c.Assert(err, IsNil) + var x struct { + Sequences map[string][]*repair.RepairState `json:"sequences"` + } + err = json.Unmarshal(data, &x) + c.Assert(err, IsNil) + return x.Sequences +} + +func (s *runnerSuite) testNext(c *C, redirectFirst bool) { + r1 := sysdb.InjectTrusted(s.storeSigning.Trusted) + defer r1() + r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey}) + defer r2() + + seqRepairs := s.signSeqRepairs(c, nextRepairs) + + mockServer := makeMockServer(c, &seqRepairs, redirectFirst) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + runner.LoadState() + + rpr, err := runner.Next("canonical") + c.Assert(err, IsNil) + c.Check(rpr.RepairID(), Equals, "1") + c.Check(osutil.FileExists(filepath.Join(dirs.SnapRepairAssertsDir, "canonical", "1", "repair.r0")), Equals, true) + + rpr, err = runner.Next("canonical") + c.Assert(err, IsNil) + c.Check(rpr.RepairID(), Equals, "3") + strm, err := ioutil.ReadFile(filepath.Join(dirs.SnapRepairAssertsDir, "canonical", "3", "repair.r2")) + c.Assert(err, IsNil) + c.Check(string(strm), Equals, seqRepairs[2]) + + // no more + rpr, err = runner.Next("canonical") + c.Check(err, Equals, repair.ErrRepairNotFound) + + expectedSeq := []*repair.RepairState{ + {Sequence: 1}, + {Sequence: 2, Status: repair.SkipStatus}, + {Sequence: 3, Revision: 2}, + } + c.Check(runner.Sequence("canonical"), DeepEquals, expectedSeq) + // on disk + seqs := s.loadSequences(c) + c.Check(seqs["canonical"], DeepEquals, expectedSeq) + + // start fresh run with new runner + // will refetch repair 3 + signed := s.signSeqRepairs(c, []string{repair3Rev4, repair4}) + seqRepairs[2] = signed[0] + seqRepairs = append(seqRepairs, signed[1]) + + runner = repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + runner.LoadState() + + rpr, err = runner.Next("canonical") + c.Assert(err, IsNil) + c.Check(rpr.RepairID(), Equals, "1") + + rpr, err = runner.Next("canonical") + c.Assert(err, IsNil) + c.Check(rpr.RepairID(), Equals, "3") + // refetched new revision! + c.Check(rpr.Revision(), Equals, 4) + c.Check(rpr.Body(), DeepEquals, []byte("scriptC2\n")) + + // new repair + rpr, err = runner.Next("canonical") + c.Assert(err, IsNil) + c.Check(rpr.RepairID(), Equals, "4") + c.Check(rpr.Body(), DeepEquals, []byte("scriptD\n")) + + // no more + rpr, err = runner.Next("canonical") + c.Check(err, Equals, repair.ErrRepairNotFound) + + c.Check(runner.Sequence("canonical"), DeepEquals, []*repair.RepairState{ + {Sequence: 1}, + {Sequence: 2, Status: repair.SkipStatus}, + {Sequence: 3, Revision: 4}, + {Sequence: 4}, + }) +} + +func (s *runnerSuite) TestNext(c *C) { + redirectFirst := false + s.testNext(c, redirectFirst) +} + +func (s *runnerSuite) TestNextRedirect(c *C) { + redirectFirst := true + s.testNext(c, redirectFirst) +} + +func (s *runnerSuite) TestNextImmediateSkip(c *C) { + seqRepairs := []string{`type: repair +authority-id: canonical +brand-id: canonical +repair-id: 1 +series: + - 33 +timestamp: 2017-07-02T12:00:00Z +body-length: 8 +sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj + +scriptB + + +AXNpZw==`} + + mockServer := makeMockServer(c, &seqRepairs, false) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + runner.LoadState() + + // not applicable => not returned + _, err := runner.Next("canonical") + c.Check(err, Equals, repair.ErrRepairNotFound) + + expectedSeq := []*repair.RepairState{ + {Sequence: 1, Status: repair.SkipStatus}, + } + c.Check(runner.Sequence("canonical"), DeepEquals, expectedSeq) + // on disk + seqs := s.loadSequences(c) + c.Check(seqs["canonical"], DeepEquals, expectedSeq) +} + +func (s *runnerSuite) TestNextRefetchSkip(c *C) { + seqRepairs := []string{`type: repair +authority-id: canonical +brand-id: canonical +repair-id: 1 +series: + - 16 +timestamp: 2017-07-02T12:00:00Z +body-length: 8 +sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj + +scriptB + + +AXNpZw==`} + + r1 := sysdb.InjectTrusted(s.storeSigning.Trusted) + defer r1() + r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey}) + defer r2() + + seqRepairs = s.signSeqRepairs(c, seqRepairs) + + mockServer := makeMockServer(c, &seqRepairs, false) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + runner.LoadState() + + _, err := runner.Next("canonical") + c.Assert(err, IsNil) + + expectedSeq := []*repair.RepairState{ + {Sequence: 1}, + } + c.Check(runner.Sequence("canonical"), DeepEquals, expectedSeq) + // on disk + seqs := s.loadSequences(c) + c.Check(seqs["canonical"], DeepEquals, expectedSeq) + + // new fresh run, repair becomes now unapplicable + seqRepairs[0] = `type: repair +authority-id: canonical +revision: 1 +brand-id: canonical +repair-id: 1 +series: + - 33 +timestamp: 2017-07-02T12:00:00Z +body-length: 7 +sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj + +scriptX + +AXNpZw==` + + seqRepairs = s.signSeqRepairs(c, seqRepairs) + + runner = repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + runner.LoadState() + + _, err = runner.Next("canonical") + c.Check(err, Equals, repair.ErrRepairNotFound) + + expectedSeq = []*repair.RepairState{ + {Sequence: 1, Revision: 1, Status: repair.SkipStatus}, + } + c.Check(runner.Sequence("canonical"), DeepEquals, expectedSeq) + // on disk + seqs = s.loadSequences(c) + c.Check(seqs["canonical"], DeepEquals, expectedSeq) +} + +func (s *runnerSuite) TestNext500(c *C) { + restore := repair.MockPeekRetryStrategy(testRetryStrategy) + defer restore() + + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(500) + })) + + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + runner.LoadState() + + _, err := runner.Next("canonical") + c.Assert(err, ErrorMatches, "cannot peek repair headers, unexpected status 500") +} + +func (s *runnerSuite) TestNextNotFound(c *C) { + s.freshState(c) + + restore := repair.MockPeekRetryStrategy(testRetryStrategy) + defer restore() + + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(404) + })) + + c.Assert(mockServer, NotNil) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + runner.LoadState() + + // sanity + data, err := ioutil.ReadFile(dirs.SnapRepairStateFile) + c.Assert(err, IsNil) + c.Check(string(data), Equals, freshStateJSON) + + _, err = runner.Next("canonical") + c.Assert(err, Equals, repair.ErrRepairNotFound) + + // we saved new time lower bound + t1 := runner.TimeLowerBound() + expected := strings.Replace(freshStateJSON, "2017-08-11T15:49:49Z", t1.Format(time.RFC3339), 1) + c.Check(expected, Not(Equals), freshStateJSON) + data, err = ioutil.ReadFile(dirs.SnapRepairStateFile) + c.Assert(err, IsNil) + c.Check(string(data), Equals, expected) +} + +func (s *runnerSuite) TestNextSaveStateError(c *C) { + seqRepairs := []string{`type: repair +authority-id: canonical +brand-id: canonical +repair-id: 1 +series: + - 33 +timestamp: 2017-07-02T12:00:00Z +body-length: 8 +sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj + +scriptB + + +AXNpZw==`} + + mockServer := makeMockServer(c, &seqRepairs, false) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + runner.LoadState() + + // break SaveState + err := os.Chmod(dirs.SnapRepairDir, 0555) + c.Assert(err, IsNil) + defer os.Chmod(dirs.SnapRepairDir, 0775) + + _, err = runner.Next("canonical") + c.Check(err, ErrorMatches, `cannot save repair state:.*`) +} + +func (s *runnerSuite) TestNextVerifyNoKey(c *C) { + seqRepairs := []string{`type: repair +authority-id: canonical +brand-id: canonical +repair-id: 1 +timestamp: 2017-07-02T12:00:00Z +body-length: 8 +sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj + +scriptB + + +AXNpZw==`} + + mockServer := makeMockServer(c, &seqRepairs, false) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + runner.LoadState() + + _, err := runner.Next("canonical") + c.Check(err, ErrorMatches, `cannot verify repair canonical-1: cannot find public key.*`) + + c.Check(runner.Sequence("canonical"), HasLen, 0) +} + +func (s *runnerSuite) TestNextVerifySelfSigned(c *C) { + randoKey, _ := assertstest.GenerateKey(752) + + randomSigning := assertstest.NewSigningDB("canonical", randoKey) + randoKeyEncoded, err := asserts.EncodePublicKey(randoKey.PublicKey()) + c.Assert(err, IsNil) + acctKey, err := randomSigning.Sign(asserts.AccountKeyType, map[string]interface{}{ + "authority-id": "canonical", + "account-id": "canonical", + "public-key-sha3-384": randoKey.PublicKey().ID(), + "name": "repairs", + "since": time.Now().UTC().Format(time.RFC3339), + }, randoKeyEncoded, "") + c.Assert(err, IsNil) + + rpr, err := randomSigning.Sign(asserts.RepairType, map[string]interface{}{ + "brand-id": "canonical", + "repair-id": "1", + "timestamp": time.Now().UTC().Format(time.RFC3339), + }, []byte("scriptB\n"), "") + c.Assert(err, IsNil) + + buf := &bytes.Buffer{} + enc := asserts.NewEncoder(buf) + enc.Encode(rpr) + enc.Encode(acctKey) + seqRepairs := []string{buf.String()} + + mockServer := makeMockServer(c, &seqRepairs, false) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + runner.LoadState() + + _, err = runner.Next("canonical") + c.Check(err, ErrorMatches, `cannot verify repair canonical-1: circular assertions`) + + c.Check(runner.Sequence("canonical"), HasLen, 0) +} + +func (s *runnerSuite) TestNextVerifyAllKeysOK(c *C) { + r1 := sysdb.InjectTrusted(s.storeSigning.Trusted) + defer r1() + r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey}) + defer r2() + + decoded, err := asserts.Decode([]byte(nextRepairs[0])) + c.Assert(err, IsNil) + signed, err := s.repairsSigning.Sign(asserts.RepairType, decoded.Headers(), decoded.Body(), "") + c.Assert(err, IsNil) + + // stream with all keys (any order) works as well + buf := &bytes.Buffer{} + enc := asserts.NewEncoder(buf) + enc.Encode(signed) + enc.Encode(s.storeSigning.TrustedKey) + enc.Encode(s.repairRootAcctKey) + enc.Encode(s.repairsAcctKey) + seqRepairs := []string{buf.String()} + + mockServer := makeMockServer(c, &seqRepairs, false) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + runner.LoadState() + + rpr, err := runner.Next("canonical") + c.Assert(err, IsNil) + c.Check(rpr.RepairID(), Equals, "1") +} + +func (s *runnerSuite) TestRepairSetStatus(c *C) { + seqRepairs := []string{`type: repair +authority-id: canonical +brand-id: canonical +repair-id: 1 +timestamp: 2017-07-02T12:00:00Z +body-length: 8 +sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj + +scriptB + + +AXNpZw==`} + + r1 := sysdb.InjectTrusted(s.storeSigning.Trusted) + defer r1() + r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey}) + defer r2() + + seqRepairs = s.signSeqRepairs(c, seqRepairs) + + mockServer := makeMockServer(c, &seqRepairs, false) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + runner.LoadState() + + rpr, err := runner.Next("canonical") + c.Assert(err, IsNil) + + rpr.SetStatus(repair.DoneStatus) + + expectedSeq := []*repair.RepairState{ + {Sequence: 1, Status: repair.DoneStatus}, + } + c.Check(runner.Sequence("canonical"), DeepEquals, expectedSeq) + // on disk + seqs := s.loadSequences(c) + c.Check(seqs["canonical"], DeepEquals, expectedSeq) +} + +func (s *runnerSuite) TestRepairBasicRun(c *C) { + seqRepairs := []string{`type: repair +authority-id: canonical +brand-id: canonical +repair-id: 1 +series: + - 16 +timestamp: 2017-07-02T12:00:00Z +body-length: 7 +sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj + +exit 0 + + +AXNpZw==`} + + r1 := sysdb.InjectTrusted(s.storeSigning.Trusted) + defer r1() + r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey}) + defer r2() + + seqRepairs = s.signSeqRepairs(c, seqRepairs) + + mockServer := makeMockServer(c, &seqRepairs, false) + defer mockServer.Close() + + runner := repair.NewRunner() + runner.BaseURL = mustParseURL(mockServer.URL) + runner.LoadState() + + rpr, err := runner.Next("canonical") + c.Assert(err, IsNil) + + rpr.Run() + scrpt, err := ioutil.ReadFile(filepath.Join(dirs.SnapRepairRunDir, "canonical", "1", "script.r0")) + c.Assert(err, IsNil) + c.Check(string(scrpt), Equals, "exit 0\n") +} diff -Nru snapd-2.27.5/cmd/snap-seccomp/export_test.go snapd-2.28.5/cmd/snap-seccomp/export_test.go --- snapd-2.27.5/cmd/snap-seccomp/export_test.go 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/cmd/snap-seccomp/export_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -22,6 +22,7 @@ var ( Compile = compile SeccompResolver = seccompResolver + FindGid = findGid ) func MockArchUbuntuArchitecture(f func() string) (restore func()) { diff -Nru snapd-2.27.5/cmd/snap-seccomp/main.go snapd-2.28.5/cmd/snap-seccomp/main.go --- snapd-2.27.5/cmd/snap-seccomp/main.go 2017-08-24 08:11:14.000000000 +0000 +++ snapd-2.28.5/cmd/snap-seccomp/main.go 2017-09-13 14:47:18.000000000 +0000 @@ -28,6 +28,7 @@ //#include //#include //#include +//#include //#include //#include //#include @@ -135,6 +136,11 @@ // return htobe64(val); //} // +//static int mygetgrnam_r(const char *name, struct group *grp,char *buf, +// size_t buflen, struct group **result) { +// return getgrnam_r(name, grp, buf, buflen, result); +//} +// import "C" import ( @@ -143,10 +149,13 @@ "fmt" "io/ioutil" "os" + "os/user" "path/filepath" + "regexp" "strconv" "strings" "syscall" + "unsafe" // FIXME: we want github.com/seccomp/libseccomp-golang but that // will not work with trusty because libseccomp-golang checks @@ -458,6 +467,137 @@ return strconv.ParseUint(token, 10, 64) } +// Be very strict so usernames and groups specified in policy are widely +// compatible. From NAME_REGEX in /etc/adduser.conf +var userGroupNamePattern = regexp.MustCompile("^[a-z][-a-z0-9_]*$") + +func findUid(username string) (uint64, error) { + if !userGroupNamePattern.MatchString(username) { + return 0, fmt.Errorf("%q must be a valid username", username) + } + user, err := user.Lookup(username) + if err != nil { + return 0, err + } + + return strconv.ParseUint(user.Uid, 10, 64) +} + +// hrm, user.LookupGroup() doesn't exist yet: +// https://github.com/golang/go/issues/2617 +// +// Use implementation from upcoming releases: +// https://golang.org/src/os/user/lookup_unix.go +func lookupGroup(groupname string) (string, error) { + var grp C.struct_group + var result *C.struct_group + + buf := alloc(groupBuffer) + defer buf.free() + cname := C.CString(groupname) + defer C.free(unsafe.Pointer(cname)) + + err := retryWithBuffer(buf, func() syscall.Errno { + return syscall.Errno(C.mygetgrnam_r(cname, + &grp, + (*C.char)(buf.ptr), + C.size_t(buf.size), + &result)) + }) + if err != nil { + return "", fmt.Errorf("group: lookup groupname %s: %v", groupname, err) + } + if result == nil { + return "", fmt.Errorf("group: unknown group %s", groupname) + } + return strconv.Itoa(int(grp.gr_gid)), nil +} + +type bufferKind C.int + +const ( + groupBuffer = bufferKind(C._SC_GETGR_R_SIZE_MAX) +) + +func (k bufferKind) initialSize() C.size_t { + sz := C.sysconf(C.int(k)) + if sz == -1 { + // DragonFly and FreeBSD do not have _SC_GETPW_R_SIZE_MAX. + // Additionally, not all Linux systems have it, either. For + // example, the musl libc returns -1. + return 1024 + } + if !isSizeReasonable(int64(sz)) { + // Truncate. If this truly isn't enough, retryWithBuffer will error on the first run. + return maxBufferSize + } + return C.size_t(sz) +} + +type memBuffer struct { + ptr unsafe.Pointer + size C.size_t +} + +func alloc(kind bufferKind) *memBuffer { + sz := kind.initialSize() + return &memBuffer{ + ptr: C.malloc(sz), + size: sz, + } +} + +func (mb *memBuffer) resize(newSize C.size_t) { + mb.ptr = C.realloc(mb.ptr, newSize) + mb.size = newSize +} + +func (mb *memBuffer) free() { + C.free(mb.ptr) +} + +// retryWithBuffer repeatedly calls f(), increasing the size of the +// buffer each time, until f succeeds, fails with a non-ERANGE error, +// or the buffer exceeds a reasonable limit. +func retryWithBuffer(buf *memBuffer, f func() syscall.Errno) error { + for { + errno := f() + if errno == 0 { + return nil + } else if errno != syscall.ERANGE { + return errno + } + newSize := buf.size * 2 + if !isSizeReasonable(int64(newSize)) { + return fmt.Errorf("internal buffer exceeds %d bytes", maxBufferSize) + } + buf.resize(newSize) + } +} + +const maxBufferSize = 1 << 20 + +func isSizeReasonable(sz int64) bool { + return sz > 0 && sz <= maxBufferSize +} + +// end code from https://golang.org/src/os/user/lookup_unix.go + +func findGid(group string) (uint64, error) { + if !userGroupNamePattern.MatchString(group) { + return 0, fmt.Errorf("%q must be a valid group name", group) + } + + //group, err := user.LookupGroup(group) + group, err := lookupGroup(group) + if err != nil { + return 0, err + } + + //return strconv.ParseUint(group.Gid, 10, 64) + return strconv.ParseUint(group, 10, 64) +} + func parseLine(line string, secFilter *seccomp.ScmpFilter) error { // ignore comments and empty lines if strings.HasPrefix(line, "#") || line == "" { @@ -508,6 +648,18 @@ } else if strings.HasPrefix(arg, "|") { cmpOp = seccomp.CompareMaskedEqual value, err = readNumber(arg[1:]) + } else if strings.HasPrefix(arg, "u:") { + cmpOp = seccomp.CompareEqual + value, err = findUid(arg[2:]) + if err != nil { + return fmt.Errorf("cannot parse token %q (line %q): %v", arg, line, err) + } + } else if strings.HasPrefix(arg, "g:") { + cmpOp = seccomp.CompareEqual + value, err = findGid(arg[2:]) + if err != nil { + return fmt.Errorf("cannot parse token %q (line %q): %v", arg, line, err) + } } else { cmpOp = seccomp.CompareEqual value, err = readNumber(arg) diff -Nru snapd-2.27.5/cmd/snap-seccomp/main_ppc64le.go snapd-2.28.5/cmd/snap-seccomp/main_ppc64le.go --- snapd-2.27.5/cmd/snap-seccomp/main_ppc64le.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/cmd/snap-seccomp/main_ppc64le.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,31 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +// +build ppc64le,go1.7,!go1.8 + +/* + * 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 + +/* +#cgo LDFLAGS: -no-pie + +// we need "-no-pie" for ppc64le,go1.7 to work around build failure on +// ppc64el with go1.7, see +// https://forum.snapcraft.io/t/snapd-master-fails-on-zesty-ppc64el-with-r-ppc64-addr16-ha-for-symbol-out-of-range/ +*/ +import "C" diff -Nru snapd-2.27.5/cmd/snap-seccomp/main_test.go snapd-2.28.5/cmd/snap-seccomp/main_test.go --- snapd-2.27.5/cmd/snap-seccomp/main_test.go 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/cmd/snap-seccomp/main_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,189 +20,256 @@ package main_test import ( - "encoding/binary" "fmt" - "io" + "io/ioutil" "math/rand" "os" + "os/exec" "path/filepath" "strconv" "strings" "testing" - "unsafe" . "gopkg.in/check.v1" "github.com/mvo5/libseccomp-golang" - // forked from "golang.org/x/net/bpf" - // until https://github.com/golang/go/issues/20556 - "github.com/mvo5/net/bpf" - "github.com/snapcore/snapd/arch" main "github.com/snapcore/snapd/cmd/snap-seccomp" - "github.com/snapcore/snapd/release" ) // Hook up check.v1 into the "go test" runner func Test(t *testing.T) { TestingT(t) } -type snapSeccompSuite struct{} +type snapSeccompSuite struct { + seccompBpfLoader string + seccompSyscallRunner string +} var _ = Suite(&snapSeccompSuite{}) -func decodeBpfFromFile(p string) ([]bpf.Instruction, error) { - var ops []bpf.Instruction - var rawOp bpf.RawInstruction +var seccompBpfLoaderContent = []byte(` +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define MAX_BPF_SIZE 32 * 1024 + +int sc_apply_seccomp_bpf(const char* profile_path) +{ + unsigned char bpf[MAX_BPF_SIZE + 1]; // account for EOF + FILE* fp; + fp = fopen(profile_path, "rb"); + if (fp == NULL) { + fprintf(stderr, "cannot read %s\n", profile_path); + return -1; + } + + // set 'size' to 1; to get bytes transferred + size_t num_read = fread(bpf, 1, sizeof(bpf), fp); + + if (ferror(fp) != 0) { + perror("fread()"); + return -1; + } else if (feof(fp) == 0) { + fprintf(stderr, "file too big\n"); + return -1; + } + fclose(fp); + + struct sock_fprog prog = { + .len = num_read / sizeof(struct sock_filter), + .filter = (struct sock_filter*)bpf, + }; + + // Set NNP to allow loading seccomp policy into the kernel without + // root + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { + perror("prctl(PR_NO_NEW_PRIVS, 1, 0, 0, 0)"); + return -1; + } + + if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) { + perror("prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, ...) failed"); + return -1; + } + return 0; +} + +int main(int argc, char* argv[]) +{ + int rc = 0; + if (argc < 2) { + fprintf(stderr, "Usage: %s [prog ...]\n", argv[0]); + return 1; + } + + rc = sc_apply_seccomp_bpf(argv[1]); + if (rc != 0) + return -rc; + + execv(argv[2], (char* const*)&argv[2]); + perror("execv failed"); + return 1; +} +`) + +var seccompSyscallRunnerContent = []byte(` +#define _GNU_SOURCE +#include +#include +#include +int main(int argc, char** argv) +{ + int l[7]; + for (int i = 0; i < 7; i++) + l[i] = atoi(argv[i + 1]); + // There might be architecture-specific requirements. see "man syscall" + // for details. + syscall(l[0], l[1], l[2], l[3], l[4], l[5], l[6]); + syscall(SYS_exit, 0, 0, 0, 0, 0, 0); + return 0; +} +`) - r, err := os.Open(p) +func lastKmsg() string { + output, err := exec.Command("dmesg").CombinedOutput() if err != nil { - return nil, err + return err.Error() } - defer r.Close() + l := strings.Split(string(output), "\n") + return fmt.Sprintf("Showing last 10 lines of dmesg:\n%s", strings.Join(l[len(l)-10:], "\n")) +} - for { - err = binary.Read(r, nativeEndian(), &rawOp) - if err == io.EOF { - break - } - if err != nil { - return nil, err - } - ops = append(ops, rawOp.Disassemble()) - } +func (s *snapSeccompSuite) SetUpSuite(c *C) { + // build seccomp-load helper + s.seccompBpfLoader = filepath.Join(c.MkDir(), "seccomp_bpf_loader") + err := ioutil.WriteFile(s.seccompBpfLoader+".c", seccompBpfLoaderContent, 0644) + c.Assert(err, IsNil) + cmd := exec.Command("gcc", "-Werror", "-Wall", s.seccompBpfLoader+".c", "-o", s.seccompBpfLoader) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err = cmd.Run() + c.Assert(err, IsNil) - return ops, nil + // build syscall-runner helper + s.seccompSyscallRunner = filepath.Join(c.MkDir(), "seccomp_syscall_runner") + err = ioutil.WriteFile(s.seccompSyscallRunner+".c", seccompSyscallRunnerContent, 0644) + c.Assert(err, IsNil) + cmd = exec.Command("gcc", "-std=c99", "-Werror", "-Wall", "-static", s.seccompSyscallRunner+".c", "-o", s.seccompSyscallRunner, "-Wl,-static", "-static-libgcc") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err = cmd.Run() + c.Assert(err, IsNil) } -func parseBpfInput(s string) (*main.SeccompData, error) { - // syscall;arch;arg1,arg2... - l := strings.Split(s, ";") +// Runs the policy through the kernel: +// 1. runs main.Compile() +// 2. the program in seccompBpfLoaderContent with the output file as an +// argument +// 3. the program in seccompBpfLoaderContent loads the output file BPF into +// the kernel and executes the program in seccompBpfRunnerContent with the +// syscall and arguments specified by the test +// +// In this manner, in addition to verifying policy syntax we are able to +// unit test the resulting bpf in several ways. +// +// Full testing of applied policy is done elsewhere via spread tests. +// +// Note that we skip testing prctl(PR_SET_ENDIAN) - it causes havoc when +// it is run. We will also need to skip: fadvise64_64, +// ftruncate64, posix_fadvise, pread64, pwrite64, readahead, +// sync_file_range, and truncate64. +// Once we start using those. See `man syscall` +func (s *snapSeccompSuite) runBpf(c *C, seccompWhitelist, bpfInput string, expected int) { + outPath := filepath.Join(c.MkDir(), "bpf") + err := main.Compile([]byte(seccompWhitelist), outPath) + c.Assert(err, IsNil) - var scmpArch seccomp.ScmpArch - if len(l) < 2 || l[1] == "native" { - scmpArch = main.UbuntuArchToScmpArch(arch.UbuntuArchitecture()) - } else { - scmpArch = main.UbuntuArchToScmpArch(l[1]) - } + // Common syscalls we need to allow for a minimal statically linked + // c program. + // + // If we compile a test program for each test we can get away with + // a even smaller set of syscalls: execve,exit essentially. But it + // means a much longer test run (30s vs 2s). Commit d288d89 contains + // the code for this. + common := ` +execve +uname +brk +arch_prctl +readlink +access +sysinfo +exit +# i386 +set_thread_area +# armhf +set_tls +# arm64 +readlinkat +faccessat +` + bpfPath := filepath.Join(c.MkDir(), "bpf") + err = main.Compile([]byte(common+seccompWhitelist), bpfPath) + c.Assert(err, IsNil) - sc, err := seccomp.GetSyscallFromNameByArch(l[0], scmpArch) - if err != nil { - return nil, err + // syscallName;arch;arg1,arg2... + l := strings.Split(bpfInput, ";") + if len(l) > 1 && l[1] != "native" { + c.Logf("cannot use non-native in runBpfInKernel") + return } - // libseccomp may return negative numbers here for syscalls that - // are "special" for some reason. there is no "official" way to - // resolve them using the API to the real number. this is why - // we workaround there. - if sc < 0 { - /* -101 is __PNR_socket */ - if sc == -101 && scmpArch == seccomp.ArchX86 { - sc = 359 /* see src/arch-x86.c socket */ - } else if sc == -101 && scmpArch == seccomp.ArchS390X { - sc = 359 /* see src/arch-s390x.c socket */ - } else if sc == -10165 && scmpArch == seccomp.ArchARM64 { - // -10165 is mknod on aarch64 and it is translated - // to mknodat. for our simulation -10165 is fine - // though - } else { - panic(fmt.Sprintf("cannot resolve syscall %v for arch %v, got %v", l[0], l[1], sc)) - } + + var syscallRunnerArgs [7]string + syscallNr, err := seccomp.GetSyscallFromName(l[0]) + c.Assert(err, IsNil) + if syscallNr < 0 { + c.Skip(fmt.Sprintf("skipping %v because it resolves to negative %v", l[0], syscallNr)) + return } - var syscallArgs [6]uint64 + syscallRunnerArgs[0] = strconv.FormatInt(int64(syscallNr), 10) if len(l) > 2 { args := strings.Split(l[2], ",") for i := range args { // init with random number argument - syscallArgs[i] = (uint64)(rand.Uint32()) + syscallArg := (uint64)(rand.Uint32()) // override if the test specifies a specific number if nr, err := strconv.ParseUint(args[i], 10, 64); err == nil { - syscallArgs[i] = nr + syscallArg = nr } else if nr, ok := main.SeccompResolver[args[i]]; ok { - syscallArgs[i] = nr + syscallArg = nr } + syscallRunnerArgs[i+1] = strconv.FormatUint(syscallArg, 10) } } - sd := &main.SeccompData{} - sd.SetArch(main.ScmpArchToSeccompNativeArch(scmpArch)) - sd.SetNr(sc) - sd.SetArgs(syscallArgs) - return sd, nil -} - -// Endianness detection. -func nativeEndian() binary.ByteOrder { - // Credit matt kane, taken from his gosndfile project. - // https://groups.google.com/forum/#!msg/golang-nuts/3GEzwKfRRQw/D1bMbFP-ClAJ - // https://github.com/mkb218/gosndfile - var i int32 = 0x01020304 - u := unsafe.Pointer(&i) - pb := (*byte)(u) - b := *pb - isLittleEndian := b == 0x04 - - if isLittleEndian { - return binary.LittleEndian - } else { - return binary.BigEndian - } -} - -// simulateBpf: -// 1. runs main.Compile() which will catch syntax errors and output to a file -// 2. takes the output file from main.Compile and loads it via -// decodeBpfFromFile -// 3. parses the decoded bpf using the seccomp library and various -// snapd functions -// 4. runs the parsed bpf through a bpf VM -// -// In this manner, in addition to verifying policy syntax we are able to -// unit test the resulting bpf in several ways approximating the kernels -// behaviour (approximating because this parser is not the kernel's seccomp -// parser). -// -// Full testing of applied policy is done elsewhere via spread tests. -func simulateBpf(c *C, seccompWhitelist, bpfInput string, expected int) { - outPath := filepath.Join(c.MkDir(), "bpf") - err := main.Compile([]byte(seccompWhitelist), outPath) - c.Assert(err, IsNil) - - ops, err := decodeBpfFromFile(outPath) - c.Assert(err, IsNil) - - vm, err := bpf.NewVM(ops) - c.Assert(err, IsNil) - - bpfSeccompInput, err := parseBpfInput(bpfInput) - c.Assert(err, IsNil) - - buf2 := (*[64]byte)(unsafe.Pointer(bpfSeccompInput)) - out, err := vm.Run(buf2[:]) - c.Assert(err, IsNil) - c.Check(out, Equals, expected, Commentf("unexpected result for %q (input %q), got %v expected %v", seccompWhitelist, bpfInput, out, expected)) -} - -func (s *snapSeccompSuite) SetUpSuite(c *C) { - // FIXME: we currently use a fork of x/net/bpf because of: - // https://github.com/golang/go/issues/20556 - // switch to x/net/bpf once we can simulate seccomp bpf there - bpf.VmEndianness = nativeEndian() -} - -func systemUsesSocketcall() bool { - // We need to skip the tests on trusty/i386 and trusty/s390x as - // those are using the socketcall syscall instead of the real - // socket syscall. - // - // See also: - // https://bugs.launchpad.net/ubuntu/+source/glibc/+bug/1576066 - if release.ReleaseInfo.VersionID == "14.04" { - if arch.UbuntuArchitecture() == "i386" || arch.UbuntuArchitecture() == "s390x" { - return true + cmd := exec.Command(s.seccompBpfLoader, bpfPath, s.seccompSyscallRunner, syscallRunnerArgs[0], syscallRunnerArgs[1], syscallRunnerArgs[2], syscallRunnerArgs[3], syscallRunnerArgs[4], syscallRunnerArgs[5], syscallRunnerArgs[6]) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err = cmd.Run() + switch expected { + case main.SeccompRetAllow: + if err != nil { + c.Fatalf("unexpected error for %q (failed to run %q): %s", seccompWhitelist, lastKmsg(), err) + } + case main.SeccompRetKill: + if err == nil { + c.Fatalf("unexpected success for %q %q (ran but should have failed %s)", seccompWhitelist, bpfInput, lastKmsg()) } + default: + c.Fatalf("unknown expected result %v", expected) } - return false } // TestCompile iterates over a range of textual seccomp whitelist rules and @@ -219,6 +286,10 @@ // {"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 := main.FindGid("shadow") + c.Assert(err, IsNil) + for _, t := range []struct { seccompWhitelist string bpfInput string @@ -233,7 +304,7 @@ {"read\nwrite\nexecve\n", "write", main.SeccompRetAllow}, // trivial denial - {"read", "execve", main.SeccompRetKill}, + {"read", "ioctl", main.SeccompRetKill}, // test argument filtering syntax, we currently support: // >=, <=, !, <, >, | @@ -309,23 +380,41 @@ {"quotactl Q_GETQUOTA", "quotactl;native;Q_GETQUOTA", main.SeccompRetAllow}, {"quotactl Q_GETQUOTA", "quotactl;native;99", main.SeccompRetKill}, + // test_bad_seccomp_filter_args_termios + {"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}, + } { + s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected) + } +} + +// TestCompileSocket runs in a separate tests so that only this part +// can be skipped when "socketcall()" is used instead of "socket()". +// +// Some architectures (i386, s390x) use the "socketcall" syscall instead +// of "socket". This is the case on Ubuntu 14.04, 17.04, 17.10 +func (s *snapSeccompSuite) TestCompileSocket(c *C) { + for _, t := range []struct { + seccompWhitelist string + bpfInput string + expected int + }{ + // test_bad_seccomp_filter_args_socket {"socket AF_UNIX", "socket;native;AF_UNIX", main.SeccompRetAllow}, {"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}, - - // test_bad_seccomp_filter_args_termios - {"ioctl - TIOCSTI", "ioctl;native;-,TIOCSTI", main.SeccompRetAllow}, - {"ioctl - TIOCSTI", "ioctl;native;-,99", main.SeccompRetKill}, } { - // skip socket tests if the system uses socketcall instead - // of socket - if strings.Contains(t.seccompWhitelist, "socket") && systemUsesSocketcall() { - continue - } - simulateBpf(c, t.seccompWhitelist, t.bpfInput, t.expected) + s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected) } + } func (s *snapSeccompSuite) TestCompileBadInput(c *C) { @@ -398,6 +487,22 @@ {"setpriority <=", `cannot parse line: cannot parse token "<=" .*`}, {"setpriority |", `cannot parse line: cannot parse token "|" .*`}, {"setpriority !", `cannot parse line: cannot parse token "!" .*`}, + + // u: + {"setuid :root", `cannot parse line: cannot parse token ":root" .*`}, + {"setuid u:", `cannot parse line: cannot parse token "u:" \(line "setuid u:"\): "" must be a valid username`}, + {"setuid u:0", `cannot parse line: cannot parse token "u:0" \(line "setuid u:0"\): "0" must be a valid username`}, + {"setuid u:b@d|npu+", `cannot parse line: cannot parse token "u:b@d|npu+" \(line "setuid u:b@d|npu+"\): "b@d|npu+" must be a valid username`}, + {"setuid u:snap.bad", `cannot parse line: cannot parse token "u:snap.bad" \(line "setuid u:snap.bad"\): "snap.bad" must be a valid username`}, + {"setuid U:root", `cannot parse line: cannot parse token "U:root" .*`}, + {"setuid u:nonexistent", `cannot parse line: cannot parse token "u:nonexistent" \(line "setuid u:nonexistent"\): user: unknown user nonexistent`}, + // g: + {"setgid g:", `cannot parse line: cannot parse token "g:" \(line "setgid g:"\): "" must be a valid group name`}, + {"setgid g:0", `cannot parse line: cannot parse token "g:0" \(line "setgid g:0"\): "0" must be a valid group name`}, + {"setgid g:b@d|npu+", `cannot parse line: cannot parse token "g:b@d|npu+" \(line "setgid g:b@d|npu+"\): "b@d|npu+" must be a valid group name`}, + {"setgid g:snap.bad", `cannot parse line: cannot parse token "g:snap.bad" \(line "setgid g:snap.bad"\): "snap.bad" must be a valid group name`}, + {"setgid G:root", `cannot parse line: cannot parse token "G:root" .*`}, + {"setgid g:nonexistent", `cannot parse line: cannot parse token "g:nonexistent" \(line "setgid g:nonexistent"\): group: unknown group nonexistent`}, } { outPath := filepath.Join(c.MkDir(), "bpf") err := main.Compile([]byte(t.inp), outPath) @@ -407,27 +512,20 @@ // ported from test_restrictions_working_args_socket func (s *snapSeccompSuite) TestRestrictionsWorkingArgsSocket(c *C) { - // skip socket tests if the system uses socketcall instead - // of socket - if systemUsesSocketcall() { - c.Skip("cannot run when socketcall() is used") - return - } - for _, pre := range []string{"AF", "PF"} { for _, i := range []string{"UNIX", "LOCAL", "INET", "INET6", "IPX", "NETLINK", "X25", "AX25", "ATMPVC", "APPLETALK", "PACKET", "ALG", "CAN", "BRIDGE", "NETROM", "ROSE", "NETBEUI", "SECURITY", "KEY", "ASH", "ECONET", "SNA", "IRDA", "PPPOX", "WANPIPE", "BLUETOOTH", "RDS", "LLC", "TIPC", "IUCV", "RXRPC", "ISDN", "PHONET", "IEEE802154", "CAIF", "NFC", "VSOCK", "MPLS", "IB"} { seccompWhitelist := fmt.Sprintf("socket %s_%s", pre, i) bpfInputGood := fmt.Sprintf("socket;native;%s_%s", pre, i) bpfInputBad := "socket;native;99999" - simulateBpf(c, seccompWhitelist, bpfInputGood, main.SeccompRetAllow) - simulateBpf(c, seccompWhitelist, bpfInputBad, main.SeccompRetKill) + s.runBpf(c, seccompWhitelist, bpfInputGood, main.SeccompRetAllow) + s.runBpf(c, seccompWhitelist, bpfInputBad, main.SeccompRetKill) for _, j := range []string{"SOCK_STREAM", "SOCK_DGRAM", "SOCK_SEQPACKET", "SOCK_RAW", "SOCK_RDM", "SOCK_PACKET"} { seccompWhitelist := fmt.Sprintf("socket %s_%s %s", pre, i, j) bpfInputGood := fmt.Sprintf("socket;native;%s_%s,%s", pre, i, j) bpfInputBad := fmt.Sprintf("socket;native;%s_%s,9999", pre, i) - simulateBpf(c, seccompWhitelist, bpfInputGood, main.SeccompRetAllow) - simulateBpf(c, seccompWhitelist, bpfInputBad, main.SeccompRetKill) + s.runBpf(c, seccompWhitelist, bpfInputGood, main.SeccompRetAllow) + s.runBpf(c, seccompWhitelist, bpfInputBad, main.SeccompRetKill) } } } @@ -437,8 +535,8 @@ seccompWhitelist := fmt.Sprintf("socket %s - %s", j, i) bpfInputGood := fmt.Sprintf("socket;native;%s,0,%s", j, i) bpfInputBad := fmt.Sprintf("socket;native;%s,0,99", j) - simulateBpf(c, seccompWhitelist, bpfInputGood, main.SeccompRetAllow) - simulateBpf(c, seccompWhitelist, bpfInputBad, main.SeccompRetKill) + s.runBpf(c, seccompWhitelist, bpfInputGood, main.SeccompRetAllow) + s.runBpf(c, seccompWhitelist, bpfInputBad, main.SeccompRetKill) } } } @@ -449,38 +547,36 @@ // good input seccompWhitelist := fmt.Sprintf("quotactl %s", arg) bpfInputGood := fmt.Sprintf("quotactl;native;%s", arg) - simulateBpf(c, seccompWhitelist, bpfInputGood, main.SeccompRetAllow) + s.runBpf(c, seccompWhitelist, bpfInputGood, main.SeccompRetAllow) // bad input for _, bad := range []string{"quotactl;native;99999", "read;native;"} { - simulateBpf(c, seccompWhitelist, bad, main.SeccompRetKill) + s.runBpf(c, seccompWhitelist, bad, main.SeccompRetKill) } } } // ported from test_restrictions_working_args_prctl func (s *snapSeccompSuite) TestRestrictionsWorkingArgsPrctl(c *C) { - bpf.VmEndianness = nativeEndian() - - for _, arg := range []string{"PR_CAP_AMBIENT", "PR_CAP_AMBIENT_RAISE", "PR_CAP_AMBIENT_LOWER", "PR_CAP_AMBIENT_IS_SET", "PR_CAP_AMBIENT_CLEAR_ALL", "PR_CAPBSET_READ", "PR_CAPBSET_DROP", "PR_SET_CHILD_SUBREAPER", "PR_GET_CHILD_SUBREAPER", "PR_SET_DUMPABLE", "PR_GET_DUMPABLE", "PR_SET_ENDIAN", "PR_GET_ENDIAN", "PR_SET_FPEMU", "PR_GET_FPEMU", "PR_SET_FPEXC", "PR_GET_FPEXC", "PR_SET_KEEPCAPS", "PR_GET_KEEPCAPS", "PR_MCE_KILL", "PR_MCE_KILL_GET", "PR_SET_MM", "PR_SET_MM_START_CODE", "PR_SET_MM_END_CODE", "PR_SET_MM_START_DATA", "PR_SET_MM_END_DATA", "PR_SET_MM_START_STACK", "PR_SET_MM_START_BRK", "PR_SET_MM_BRK", "PR_SET_MM_ARG_START", "PR_SET_MM_ARG_END", "PR_SET_MM_ENV_START", "PR_SET_MM_ENV_END", "PR_SET_MM_AUXV", "PR_SET_MM_EXE_FILE", "PR_MPX_ENABLE_MANAGEMENT", "PR_MPX_DISABLE_MANAGEMENT", "PR_SET_NAME", "PR_GET_NAME", "PR_SET_NO_NEW_PRIVS", "PR_GET_NO_NEW_PRIVS", "PR_SET_PDEATHSIG", "PR_GET_PDEATHSIG", "PR_SET_PTRACER", "PR_SET_SECCOMP", "PR_GET_SECCOMP", "PR_SET_SECUREBITS", "PR_GET_SECUREBITS", "PR_SET_THP_DISABLE", "PR_TASK_PERF_EVENTS_DISABLE", "PR_TASK_PERF_EVENTS_ENABLE", "PR_GET_THP_DISABLE", "PR_GET_TID_ADDRESS", "PR_SET_TIMERSLACK", "PR_GET_TIMERSLACK", "PR_SET_TIMING", "PR_GET_TIMING", "PR_SET_TSC", "PR_GET_TSC", "PR_SET_UNALIGN", "PR_GET_UNALIGN"} { + for _, arg := range []string{"PR_CAP_AMBIENT", "PR_CAP_AMBIENT_RAISE", "PR_CAP_AMBIENT_LOWER", "PR_CAP_AMBIENT_IS_SET", "PR_CAP_AMBIENT_CLEAR_ALL", "PR_CAPBSET_READ", "PR_CAPBSET_DROP", "PR_SET_CHILD_SUBREAPER", "PR_GET_CHILD_SUBREAPER", "PR_SET_DUMPABLE", "PR_GET_DUMPABLE", "PR_GET_ENDIAN", "PR_SET_FPEMU", "PR_GET_FPEMU", "PR_SET_FPEXC", "PR_GET_FPEXC", "PR_SET_KEEPCAPS", "PR_GET_KEEPCAPS", "PR_MCE_KILL", "PR_MCE_KILL_GET", "PR_SET_MM", "PR_SET_MM_START_CODE", "PR_SET_MM_END_CODE", "PR_SET_MM_START_DATA", "PR_SET_MM_END_DATA", "PR_SET_MM_START_STACK", "PR_SET_MM_START_BRK", "PR_SET_MM_BRK", "PR_SET_MM_ARG_START", "PR_SET_MM_ARG_END", "PR_SET_MM_ENV_START", "PR_SET_MM_ENV_END", "PR_SET_MM_AUXV", "PR_SET_MM_EXE_FILE", "PR_MPX_ENABLE_MANAGEMENT", "PR_MPX_DISABLE_MANAGEMENT", "PR_SET_NAME", "PR_GET_NAME", "PR_SET_NO_NEW_PRIVS", "PR_GET_NO_NEW_PRIVS", "PR_SET_PDEATHSIG", "PR_GET_PDEATHSIG", "PR_SET_PTRACER", "PR_SET_SECCOMP", "PR_GET_SECCOMP", "PR_SET_SECUREBITS", "PR_GET_SECUREBITS", "PR_SET_THP_DISABLE", "PR_TASK_PERF_EVENTS_DISABLE", "PR_TASK_PERF_EVENTS_ENABLE", "PR_GET_THP_DISABLE", "PR_GET_TID_ADDRESS", "PR_SET_TIMERSLACK", "PR_GET_TIMERSLACK", "PR_SET_TIMING", "PR_GET_TIMING", "PR_SET_TSC", "PR_GET_TSC", "PR_SET_UNALIGN", "PR_GET_UNALIGN"} { // good input seccompWhitelist := fmt.Sprintf("prctl %s", arg) bpfInputGood := fmt.Sprintf("prctl;native;%s", arg) - simulateBpf(c, seccompWhitelist, bpfInputGood, main.SeccompRetAllow) + s.runBpf(c, seccompWhitelist, bpfInputGood, main.SeccompRetAllow) // bad input - for _, bad := range []string{"prctl;native;99999", "read;native;"} { - simulateBpf(c, seccompWhitelist, bad, main.SeccompRetKill) + for _, bad := range []string{"prctl;native;99999", "setpriority;native;"} { + s.runBpf(c, seccompWhitelist, bad, main.SeccompRetKill) } if arg == "PR_CAP_AMBIENT" { for _, j := range []string{"PR_CAP_AMBIENT_RAISE", "PR_CAP_AMBIENT_LOWER", "PR_CAP_AMBIENT_IS_SET", "PR_CAP_AMBIENT_CLEAR_ALL"} { seccompWhitelist := fmt.Sprintf("prctl %s %s", arg, j) bpfInputGood := fmt.Sprintf("prctl;native;%s,%s", arg, j) - simulateBpf(c, seccompWhitelist, bpfInputGood, main.SeccompRetAllow) + s.runBpf(c, seccompWhitelist, bpfInputGood, main.SeccompRetAllow) for _, bad := range []string{ fmt.Sprintf("prctl;native;%s,99999", arg), - "read;native;", + "setpriority;native;", } { - simulateBpf(c, seccompWhitelist, bad, main.SeccompRetKill) + s.runBpf(c, seccompWhitelist, bad, main.SeccompRetKill) } } } @@ -509,7 +605,7 @@ {"setns - CLONE_NEWUSER", "setns;native;-,99", main.SeccompRetKill}, {"setns - CLONE_NEWUTS", "setns;native;-,99", main.SeccompRetKill}, } { - simulateBpf(c, t.seccompWhitelist, t.bpfInput, t.expected) + s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected) } } @@ -533,7 +629,7 @@ {"mknod - S_IFIFO", "mknod;native;-,999", main.SeccompRetKill}, {"mknod - S_IFSOCK", "mknod;native;-,999", main.SeccompRetKill}, } { - simulateBpf(c, t.seccompWhitelist, t.bpfInput, t.expected) + s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected) } } @@ -553,7 +649,7 @@ {"setpriority PRIO_PGRP", "setpriority;native;99", main.SeccompRetKill}, {"setpriority PRIO_USER", "setpriority;native;99", main.SeccompRetKill}, } { - simulateBpf(c, t.seccompWhitelist, t.bpfInput, t.expected) + s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected) } } @@ -569,7 +665,29 @@ // bad input {"ioctl - TIOCSTI", "quotactl;native;-,99", main.SeccompRetKill}, } { - simulateBpf(c, t.seccompWhitelist, t.bpfInput, t.expected) + s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected) + } +} + +func (s *snapSeccompSuite) TestRestrictionsWorkingArgsUidGid(c *C) { + for _, t := range []struct { + seccompWhitelist string + bpfInput string + expected int + }{ + // good input. 'root' and 'daemon' are guaranteed to be '0' and + // '1' respectively + {"setuid u:root", "setuid;native;0", main.SeccompRetAllow}, + {"setuid u:daemon", "setuid;native;1", main.SeccompRetAllow}, + {"setgid g:root", "setgid;native;0", main.SeccompRetAllow}, + {"setgid g:daemon", "setgid;native;1", main.SeccompRetAllow}, + // bad input + {"setuid u:root", "setuid;native;99", main.SeccompRetKill}, + {"setuid u:daemon", "setuid;native;99", main.SeccompRetKill}, + {"setgid g:root", "setgid;native;99", main.SeccompRetKill}, + {"setgid g:daemon", "setgid;native;99", main.SeccompRetKill}, + } { + s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected) } } @@ -601,7 +719,7 @@ // here because on endian mismatch the arch will *not* be // added if arch.UbuntuArchitecture() == t.arch { - simulateBpf(c, t.seccompWhitelist, t.bpfInput, t.expected) + s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected) } } } diff -Nru snapd-2.27.5/cmd/snap-update-ns/bootstrap_ppc64le.go snapd-2.28.5/cmd/snap-update-ns/bootstrap_ppc64le.go --- snapd-2.27.5/cmd/snap-update-ns/bootstrap_ppc64le.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/cmd/snap-update-ns/bootstrap_ppc64le.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,31 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +// +build ppc64le,go1.7,!go1.8 + +/* + * 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 + +/* +#cgo LDFLAGS: -no-pie + +// we need "-no-pie" for ppc64le,go1.7 to work around build failure on +// ppc64el with go1.7, see +// https://forum.snapcraft.io/t/snapd-master-fails-on-zesty-ppc64el-with-r-ppc64-addr16-ha-for-symbol-out-of-range/ +*/ +import "C" diff -Nru snapd-2.27.5/corecfg/corecfg.go snapd-2.28.5/corecfg/corecfg.go --- snapd-2.27.5/corecfg/corecfg.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/corecfg/corecfg.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,83 @@ +// -*- 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 { + _, err := systemd.SystemctlCmd("--version") + return err +} + +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.27.5/corecfg/corecfg_test.go snapd-2.28.5/corecfg/corecfg_test.go --- snapd-2.27.5/corecfg/corecfg_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/corecfg/corecfg_test.go 2017-09-13 14:47:18.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 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 +} + +var _ = Suite(&coreCfgSuite{}) + +func (s *coreCfgSuite) SetUpSuite(c *C) { + systemd.SystemctlCmd = func(args ...string) ([]byte, error) { + s.systemctlArgs = append(s.systemctlArgs, args[:]) + output := []byte("ActiveState=inactive") + return output, nil + } +} + +// 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() + + oldSystemdSystemctlCmd := systemd.SystemctlCmd + systemd.SystemctlCmd = func(args ...string) ([]byte, error) { + return nil, fmt.Errorf("simulate missing core-support") + } + defer func() { + systemd.SystemctlCmd = oldSystemdSystemctlCmd + }() + + err := corecfg.Run() + c.Check(err, ErrorMatches, `(?m)cannot run systemctl - core-support interface seems disconnected: simulate missing core-support`) +} diff -Nru snapd-2.27.5/corecfg/export_test.go snapd-2.28.5/corecfg/export_test.go --- snapd-2.27.5/corecfg/export_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/corecfg/export_test.go 2017-09-13 14:47:18.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 corecfg + +var ( + UpdatePiConfig = updatePiConfig + SwitchHandlePowerKey = switchHandlePowerKey + SwitchDisableService = switchDisableService + UpdateKeyValueStream = updateKeyValueStream +) diff -Nru snapd-2.27.5/corecfg/picfg.go snapd-2.28.5/corecfg/picfg.go --- snapd-2.27.5/corecfg/picfg.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/corecfg/picfg.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,98 @@ +// -*- 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.27.5/corecfg/picfg_test.go snapd-2.28.5/corecfg/picfg_test.go --- snapd-2.27.5/corecfg/picfg_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/corecfg/picfg_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,165 @@ +// -*- 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.27.5/corecfg/powerbtn.go snapd-2.28.5/corecfg/powerbtn.go --- snapd-2.27.5/corecfg/powerbtn.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/corecfg/powerbtn.go 2017-09-13 14:47:18.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 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.27.5/corecfg/powerbtn_test.go snapd-2.28.5/corecfg/powerbtn_test.go --- snapd-2.27.5/corecfg/powerbtn_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/corecfg/powerbtn_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,84 @@ +// -*- 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.27.5/corecfg/proxy.go snapd-2.28.5/corecfg/proxy.go --- snapd-2.27.5/corecfg/proxy.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/corecfg/proxy.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,73 @@ +// -*- 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.27.5/corecfg/proxy_test.go snapd-2.28.5/corecfg/proxy_test.go --- snapd-2.27.5/corecfg/proxy_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/corecfg/proxy_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,82 @@ +// -*- 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.27.5/corecfg/services.go snapd-2.28.5/corecfg/services.go --- snapd-2.27.5/corecfg/services.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/corecfg/services.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,75 @@ +// -*- 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.27.5/corecfg/services_test.go snapd-2.28.5/corecfg/services_test.go --- snapd-2.27.5/corecfg/services_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/corecfg/services_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,149 @@ +// -*- 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/testutil" +) + +type servicesSuite struct { + coreCfgSuite +} + +var _ = Suite(&servicesSuite{}) + +func (s *servicesSuite) SetUpTest(c *C) { + dirs.SetRootDir(c.MkDir()) + c.Assert(os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "etc"), 0755), IsNil) + s.systemctlArgs = nil +} + +func (s *servicesSuite) TearDownTest(c *C) { + dirs.SetRootDir("/") +} + +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.27.5/corecfg/utils.go snapd-2.28.5/corecfg/utils.go --- snapd-2.27.5/corecfg/utils.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/corecfg/utils.go 2017-09-13 14:47:18.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 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.27.5/corecfg/utils_test.go snapd-2.28.5/corecfg/utils_test.go --- snapd-2.27.5/corecfg/utils_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/corecfg/utils_test.go 2017-09-13 14:47:18.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 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.27.5/daemon/api.go snapd-2.28.5/daemon/api.go --- snapd-2.27.5/daemon/api.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/daemon/api.go 2017-09-13 14:47:18.000000000 +0000 @@ -46,10 +46,12 @@ "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/i18n/dumb" "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/jsonutil" "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/osutil" "github.com/snapcore/snapd/overlord/assertstate" "github.com/snapcore/snapd/overlord/auth" + "github.com/snapcore/snapd/overlord/cmdstate" "github.com/snapcore/snapd/overlord/configstate" "github.com/snapcore/snapd/overlord/configstate/config" "github.com/snapcore/snapd/overlord/devicestate" @@ -62,6 +64,7 @@ "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/store" "github.com/snapcore/snapd/strutil" + "github.com/snapcore/snapd/systemd" ) var api = []*Command{ @@ -86,6 +89,8 @@ usersCmd, sectionsCmd, aliasesCmd, + appsCmd, + logsCmd, debugCmd, } @@ -103,8 +108,9 @@ } loginCmd = &Command{ - Path: "/v2/login", - POST: loginUser, + Path: "/v2/login", + POST: loginUser, + PolkitOK: "io.snapcraft.snapd.login", } logoutCmd = &Command{ @@ -126,17 +132,32 @@ } snapsCmd = &Command{ - Path: "/v2/snaps", - UserOK: true, - GET: getSnapsInfo, - POST: postSnaps, + Path: "/v2/snaps", + UserOK: true, + PolkitOK: "io.snapcraft.snapd.manage", + GET: getSnapsInfo, + POST: postSnaps, } snapCmd = &Command{ - Path: "/v2/snaps/{name}", + Path: "/v2/snaps/{name}", + UserOK: true, + PolkitOK: "io.snapcraft.snapd.manage", + GET: getSnapInfo, + POST: postSnap, + } + + appsCmd = &Command{ + Path: "/v2/apps", + UserOK: true, + GET: getAppsInfo, + POST: postApps, + } + + logsCmd = &Command{ + Path: "/v2/logs", UserOK: true, - GET: getSnapInfo, - POST: postSnap, + GET: getLogs, } snapConfCmd = &Command{ @@ -148,14 +169,16 @@ interfacesCmd = &Command{ Path: "/v2/interfaces", UserOK: true, - GET: getInterfaces, + GET: interfacesConnectionsMultiplexer, POST: changeInterfaces, } // TODO: allow to post assertions for UserOK? they are verified anyway assertsCmd = &Command{ - Path: "/v2/assertions", - POST: doAssert, + Path: "/v2/assertions", + UserOK: true, + GET: getAssertTypeNames, + POST: doAssert, } assertsFindManyCmd = &Command{ @@ -461,7 +484,7 @@ about, err := localSnapInfo(c.d.overlord.State(), name) if err != nil { if err == errNoSnap { - return SnapNotFound(err) + return SnapNotFound(name, err) } return InternalError("%v", err) @@ -613,7 +636,7 @@ snapInfo, err := theStore.SnapInfo(spec, user) if err != nil { if err == store.ErrSnapNotFound { - return SnapNotFound(err) + return SnapNotFound(name, err) } return InternalError("%v", err) } @@ -822,6 +845,7 @@ snapstateRemoveMany = snapstate.RemoveMany snapstateRevert = snapstate.Revert snapstateRevertToRevision = snapstate.RevertToRevision + snapstateSwitch = snapstate.Switch assertstateRefreshSnapDeclarations = assertstate.RefreshSnapDeclarations ) @@ -1088,6 +1112,19 @@ return msg, []*state.TaskSet{ts}, nil } +func snapSwitch(inst *snapInstruction, st *state.State) (string, []*state.TaskSet, error) { + if !inst.Revision.Unset() { + return "", nil, errors.New("switch takes no revision") + } + ts, err := snapstate.Switch(st, inst.Snaps[0], inst.Channel) + if err != nil { + return "", nil, err + } + + msg := fmt.Sprintf(i18n.G("Switch %q snap to %s"), inst.Snaps[0], inst.Channel) + return msg, []*state.TaskSet{ts}, nil +} + type snapActionFunc func(*snapInstruction, *state.State) (string, []*state.TaskSet, error) var snapInstructionDispTable = map[string]snapActionFunc{ @@ -1097,6 +1134,7 @@ "revert": snapRevert, "enable": snapEnable, "disable": snapDisable, + "switch": snapSwitch, } func (inst *snapInstruction) dispatch() snapActionFunc { @@ -1111,7 +1149,7 @@ switch err { case store.ErrSnapNotFound: - return SnapNotFound(err) + return SnapNotFound(inst.Snaps[0], err) case store.ErrNoUpdateAvailable: kind = errorKindSnapNoUpdateAvailable case store.ErrLocalSnap: @@ -1491,7 +1529,7 @@ about, err := localSnapInfo(st, name) if err != nil { if err == errNoSnap { - return SnapNotFound(err) + return SnapNotFound(name, err) } return InternalError("%v", err) } @@ -1561,8 +1599,7 @@ snapName := vars["name"] var patchValues map[string]interface{} - decoder := json.NewDecoder(r.Body) - if err := decoder.Decode(&patchValues); err != nil { + if err := jsonutil.DecodeWithNumber(r.Body, &patchValues); err != nil { return BadRequest("cannot decode request body into patch values: %v", err) } @@ -1573,7 +1610,7 @@ var snapst snapstate.SnapState if err := snapstate.Get(st, snapName, &snapst); err != nil { if err == state.ErrNoState { - return SnapNotFound(err) + return SnapNotFound(snapName, err) } else { return InternalError("%v", err) } @@ -1589,8 +1626,41 @@ return AsyncResponse(nil, &Meta{Change: change.ID()}) } -// getInterfaces returns all plugs and slots. +// interfacesConnectionsMultiplexer multiplexes to either legacy (connection) or modern behavior (interfaces). +func interfacesConnectionsMultiplexer(c *Command, r *http.Request, user *auth.UserState) Response { + query := r.URL.Query() + qselect := query.Get("select") + if qselect == "" { + return getLegacyConnections(c, r, user) + } else { + return getInterfaces(c, r, user) + } +} + func getInterfaces(c *Command, r *http.Request, user *auth.UserState) Response { + q := r.URL.Query() + pselect := q.Get("select") + if pselect != "all" && pselect != "connected" { + return BadRequest("unsupported select qualifier") + } + var names []string + namesStr := q.Get("names") + if namesStr != "" { + names = strings.Split(namesStr, ",") + } + + opts := &interfaces.InfoOptions{ + Names: names, + Doc: q.Get("doc") == "true", + Plugs: q.Get("plugs") == "true", + Slots: q.Get("slots") == "true", + Connected: pselect == "connected", + } + repo := c.d.overlord.InterfaceManager().Repository() + return SyncResponse(repo.Info(opts), nil) +} + +func getLegacyConnections(c *Command, r *http.Request, user *auth.UserState) Response { repo := c.d.overlord.InterfaceManager().Repository() return SyncResponse(repo.Interfaces(), nil) } @@ -1715,6 +1785,12 @@ return AsyncResponse(nil, &Meta{Change: change.ID()}) } +func getAssertTypeNames(c *Command, r *http.Request, user *auth.UserState) Response { + return SyncResponse(map[string][]string{ + "types": asserts.TypeNames(), + }, nil) +} + func doAssert(c *Command, r *http.Request, user *auth.UserState) Response { batch := assertstate.NewBatch() _, err := batch.AddStream(r.Body) @@ -1953,9 +2029,9 @@ } var ( - postCreateUserUcrednetGetUID = ucrednetGetUID - storeUserInfo = store.UserInfo - osutilAddUser = osutil.AddUser + postCreateUserUcrednetGet = ucrednetGet + storeUserInfo = store.UserInfo + osutilAddUser = osutil.AddUser ) func getUserDetailsFromStore(email string) (string, *osutil.AddUserOptions, error) { @@ -2131,7 +2207,7 @@ } func postCreateUser(c *Command, r *http.Request, user *auth.UserState) Response { - uid, err := postCreateUserUcrednetGetUID(r.RemoteAddr) + _, uid, err := postCreateUserUcrednetGet(r.RemoteAddr) if err != nil { return BadRequest("cannot get ucrednet uid: %v", err) } @@ -2313,8 +2389,7 @@ func runSnapctl(c *Command, r *http.Request, user *auth.UserState) Response { var snapctlOptions client.SnapCtlOptions - decoder := json.NewDecoder(r.Body) - if err := decoder.Decode(&snapctlOptions); err != nil { + if err := jsonutil.DecodeWithNumber(r.Body, &snapctlOptions); err != nil { return BadRequest("cannot decode snapctl request: %s", err) } @@ -2322,12 +2397,9 @@ return BadRequest("snapctl cannot run without args") } - // Right now snapctl is only used for hooks. If at some point it grows - // beyond that, this probably shouldn't go straight to the HookManager. - context, err := c.d.overlord.HookManager().Context(snapctlOptions.ContextID) - if err != nil { - return BadRequest("error running snapctl: %s", err) - } + // 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) if err != nil { if e, ok := err.(*flags.Error); ok && e.Type == flags.ErrHelp { @@ -2337,7 +2409,7 @@ } } - if context.IsEphemeral() { + if context != nil && context.IsEphemeral() { context.Lock() defer context.Unlock() if err := context.Done(); err != nil { @@ -2354,7 +2426,7 @@ } func getUsers(c *Command, r *http.Request, user *auth.UserState) Response { - uid, err := postCreateUserUcrednetGetUID(r.RemoteAddr) + _, uid, err := postCreateUserUcrednetGet(r.RemoteAddr) if err != nil { return BadRequest("cannot get ucrednet uid: %v", err) } @@ -2513,3 +2585,155 @@ return SyncResponse(res, nil) } + +func getAppsInfo(c *Command, r *http.Request, user *auth.UserState) Response { + query := r.URL.Query() + + opts := appInfoOptions{} + switch sel := query.Get("select"); sel { + case "": + // nothing to do + case "service": + opts.service = true + default: + return BadRequest("invalid select parameter: %q", sel) + } + + appInfos, rsp := appInfosFor(c.d.overlord.State(), splitQS(query.Get("names")), opts) + if rsp != nil { + return rsp + } + + return SyncResponse(clientAppInfosFromSnapAppInfos(appInfos), nil) +} + +func getLogs(c *Command, r *http.Request, user *auth.UserState) Response { + query := r.URL.Query() + n := "10" + if s := query.Get("n"); s != "" { + m, err := strconv.ParseInt(s, 0, 32) + if err != nil { + return BadRequest(`invalid value for n: %q: %v`, s, err) + } + if m < 0 { + n = "all" + } else { + n = s + } + } + follow := false + if s := query.Get("follow"); s != "" { + f, err := strconv.ParseBool(s) + if err != nil { + return BadRequest(`invalid value for follow: %q: %v`, s, err) + } + follow = f + } + + // only services have logs for now + opts := appInfoOptions{service: true} + appInfos, rsp := appInfosFor(c.d.overlord.State(), splitQS(query.Get("names")), opts) + if rsp != nil { + return rsp + } + + serviceNames := make([]string, len(appInfos)) + for i, appInfo := range appInfos { + serviceNames[i] = appInfo.ServiceName() + } + + sysd := systemd.New(dirs.GlobalRootDir, &progress.NullProgress{}) + reader, err := sysd.LogReader(serviceNames, n, follow) + if err != nil { + return InternalError("cannot get logs: %v", err) + } + + return &journalLineReaderSeqResponse{ + ReadCloser: reader, + follow: follow, + } +} + +type appInstruction struct { + Action string `json:"action"` + Names []string `json:"names"` + client.StartOptions + client.StopOptions + client.RestartOptions +} + +func postApps(c *Command, r *http.Request, user *auth.UserState) Response { + var inst appInstruction + decoder := json.NewDecoder(r.Body) + if err := decoder.Decode(&inst); err != nil { + return BadRequest("cannot decode request body into service operation: %v", err) + } + if len(inst.Names) == 0 { + // on POST, don't allow empty to mean all + return BadRequest("cannot perform operation on services without a list of services to operate on") + } + + st := c.d.overlord.State() + appInfos, rsp := appInfosFor(st, inst.Names, appInfoOptions{service: true}) + if rsp != nil { + return rsp + } + if len(appInfos) == 0 { + // can't happen: appInfosFor with a non-empty list of services + // shouldn't ever return an empty appInfos with no error response + return InternalError("no services found") + } + + // 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). + argv := make([]string, 2, 3+len(appInfos)) + argv[0] = "systemctl" + + argv[1] = inst.Action + switch inst.Action { + case "start": + if inst.Enable { + argv[1] = "enable" + argv = append(argv, "--now") + } + case "stop": + if inst.Disable { + argv[1] = "disable" + argv = append(argv, "--now") + } + case "restart": + if inst.Reload { + argv[1] = "reload-or-restart" + } + default: + return BadRequest("unknown action %q", inst.Action) + } + + snapNames := make([]string, 0, len(appInfos)) + lastName := "" + names := make([]string, len(appInfos)) + for i, svc := range appInfos { + argv = append(argv, svc.ServiceName()) + snapName := svc.Snap.Name() + names[i] = snapName + "." + svc.Name + if snapName != lastName { + snapNames = append(snapNames, snapName) + lastName = snapName + } + } + + desc := fmt.Sprintf("%s of %v", inst.Action, names) + + st.Lock() + defer st.Unlock() + if err := snapstate.CheckChangeConflictMany(st, snapNames, nil); err != nil { + return InternalError(err.Error()) + } + + ts := cmdstate.Exec(st, desc, argv) + chg := st.NewChange("service-control", desc) + chg.AddAll(ts) + st.EnsureBefore(0) + return AsyncResponse(nil, &Meta{Change: chg.ID()}) +} diff -Nru snapd-2.27.5/daemon/api_test.go snapd-2.28.5/daemon/api_test.go --- snapd-2.27.5/daemon/api_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/daemon/api_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -30,6 +30,7 @@ "go/token" "io" "io/ioutil" + "math" "mime/multipart" "net/http" "net/http/httptest" @@ -38,17 +39,18 @@ "os/user" "path/filepath" "sort" + "strconv" "strings" "time" "golang.org/x/crypto/sha3" - "golang.org/x/net/context" "gopkg.in/check.v1" "gopkg.in/macaroon.v1" "github.com/snapcore/snapd/asserts" "github.com/snapcore/snapd/asserts/assertstest" "github.com/snapcore/snapd/asserts/sysdb" + "github.com/snapcore/snapd/client" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/ifacetest" @@ -59,15 +61,18 @@ "github.com/snapcore/snapd/overlord/ifacestate" "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" - "github.com/snapcore/snapd/progress" "github.com/snapcore/snapd/release" "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/systemd" "github.com/snapcore/snapd/testutil" ) type apiBaseSuite struct { + storetest.Store + rsnaps []*snap.Info err error vars map[string]string @@ -82,6 +87,18 @@ storeSigning *assertstest.StoreStack restoreRelease func() trustedRestorer func() + + origSysctlCmd func(...string) ([]byte, error) + sysctlArgses [][]string + sysctlBufs [][]byte + sysctlErrs []error + + origJctlCmd func([]string, string, bool) (io.ReadCloser, error) + jctlSvcses [][]string + jctlNs []string + jctlFollows []bool + jctlRCs []io.ReadCloser + jctlErrs []error } func (s *apiBaseSuite) SnapInfo(spec store.SnapSpec, user *auth.UserState) (*snap.Info, error) { @@ -120,10 +137,6 @@ return s.suggestedCurrency } -func (s *apiBaseSuite) Download(context.Context, string, string, *snap.DownloadInfo, progress.Meter, *auth.UserState) error { - panic("Download not expected to be called") -} - func (s *apiBaseSuite) Buy(options *store.BuyOptions, user *auth.UserState) (*store.BuyResult, error) { s.buyOptions = options s.user = user @@ -135,14 +148,6 @@ return s.err } -func (s *apiBaseSuite) Assertion(*asserts.AssertionType, []string, *auth.UserState) (asserts.Assertion, error) { - panic("Assertion not expected to be called") -} - -func (s *apiBaseSuite) Sections(*auth.UserState) ([]string, error) { - panic("Sections not expected to be called") -} - func (s *apiBaseSuite) muxVars(*http.Request) map[string]string { return s.vars } @@ -152,19 +157,60 @@ s.restoreRelease = release.MockForcedDevmode(false) snapstate.CanAutoRefresh = nil + s.origSysctlCmd = systemd.SystemctlCmd + s.origJctlCmd = systemd.JournalctlCmd + systemd.SystemctlCmd = s.systemctl + systemd.JournalctlCmd = s.journalctl } func (s *apiBaseSuite) TearDownSuite(c *check.C) { muxVars = nil s.restoreRelease() + systemd.SystemctlCmd = s.origSysctlCmd } -var ( - rootPrivKey, _ = assertstest.GenerateKey(1024) - storePrivKey, _ = assertstest.GenerateKey(752) -) +func (s *apiBaseSuite) systemctl(args ...string) (buf []byte, err error) { + s.sysctlArgses = append(s.sysctlArgses, args) + + if args[0] != "show" { + panic(fmt.Sprintf("unexpected systemctl call: %v", args)) + } + + if len(s.sysctlErrs) > 0 { + err, s.sysctlErrs = s.sysctlErrs[0], s.sysctlErrs[1:] + } + if len(s.sysctlBufs) > 0 { + buf, s.sysctlBufs = s.sysctlBufs[0], s.sysctlBufs[1:] + } + + return buf, err +} + +func (s *apiBaseSuite) journalctl(svcs []string, n string, follow bool) (rc io.ReadCloser, err error) { + s.jctlSvcses = append(s.jctlSvcses, svcs) + s.jctlNs = append(s.jctlNs, n) + s.jctlFollows = append(s.jctlFollows, follow) + + if len(s.jctlErrs) > 0 { + err, s.jctlErrs = s.jctlErrs[0], s.jctlErrs[1:] + } + if len(s.jctlRCs) > 0 { + rc, s.jctlRCs = s.jctlRCs[0], s.jctlRCs[1:] + } + + return rc, err +} func (s *apiBaseSuite) SetUpTest(c *check.C) { + s.sysctlArgses = nil + s.sysctlBufs = nil + s.sysctlErrs = nil + s.jctlSvcses = nil + s.jctlNs = nil + s.jctlFollows = nil + s.jctlRCs = nil + s.jctlErrs = nil + dirs.SetRootDir(c.MkDir()) err := os.MkdirAll(filepath.Dir(dirs.SnapStateFile), 0755) c.Assert(err, check.IsNil) @@ -184,7 +230,7 @@ s.buyOptions = nil s.buyResult = nil - s.storeSigning = assertstest.NewStoreStack("can0nical", rootPrivKey, storePrivKey) + s.storeSigning = assertstest.NewStoreStack("can0nical", nil) s.trustedRestorer = sysdb.InjectTrusted(s.storeSigning.Trusted) assertstateRefreshSnapDeclarations = nil @@ -237,6 +283,12 @@ snapstate.ReplaceStore(st, s) // mark as already seeded st.Set("seeded", true) + // registered + auth.SetDevice(st, &auth.DeviceState{ + Brand: "canonical", + Model: "pc", + Serial: "serialserial", + }) s.d = d return d @@ -364,8 +416,51 @@ // we have v0 [r5] installed s.mkInstalledInState(c, d, "foo", "bar", "v0", snap.R(5), false, "") // and v1 [r10] is current - s.mkInstalledInState(c, d, "foo", "bar", "v1", snap.R(10), true, "title: title\ndescription: description\nsummary: summary\napps:\n cmd:\n command: some.cmd\n cmd2:\n command: other.cmd\n") + s.mkInstalledInState(c, d, "foo", "bar", "v1", snap.R(10), true, `title: title +description: description +summary: summary +license: GPL-3.0 +apps: + cmd: + command: some.cmd + cmd2: + command: other.cmd + svc1: + command: somed1 + daemon: simple + svc2: + command: somed2 + daemon: forking + svc3: + command: somed3 + daemon: oneshot + svc4: + command: somed4 + daemon: notify +`) df := s.mkInstalledDesktopFile(c, "foo_cmd.desktop", "[Desktop]\nExec=foo.cmd %U") + s.sysctlBufs = [][]byte{ + []byte(`Type=simple +Id=snap.foo.svc1.service +ActiveState=fumbling +UnitFileState=enabled +`), + []byte(`Type=forking +Id=snap.foo.svc2.service +ActiveState=active +UnitFileState=disabled +`), + []byte(`Type=oneshot +Id=snap.foo.svc3.service +ActiveState=reloading +UnitFileState=static +`), + []byte(`Type=notify +Id=snap.foo.svc4.service +ActiveState=inactive +UnitFileState=potatoes +`), + } req, err := http.NewRequest("GET", "/v2/snaps/foo", nil) c.Assert(err, check.IsNil) @@ -407,13 +502,40 @@ "jailmode": false, "confinement": snap.StrictConfinement, "trymode": false, - "apps": []appJSON{ - {Name: "cmd", DesktopFile: df}, - // no desktop file - {Name: "cmd2"}, + "apps": []*client.AppInfo{ + { + Snap: "foo", Name: "cmd", + DesktopFile: df, + }, { + // no desktop file + Snap: "foo", Name: "cmd2", + }, { + // services + Snap: "foo", Name: "svc1", + Daemon: "simple", + Enabled: true, + Active: false, + }, { + Snap: "foo", Name: "svc2", + Daemon: "forking", + Enabled: false, + Active: true, + }, { + Snap: "foo", Name: "svc3", + Daemon: "oneshot", + Enabled: true, + Active: true, + }, + { + Snap: "foo", Name: "svc4", + Daemon: "notify", + Enabled: false, + Active: false, + }, }, "broken": "", "contact": "", + "license": "GPL-3.0", }, Meta: meta, } @@ -519,12 +641,13 @@ "snapstateRefreshCandidates", "snapstateRevert", "snapstateRevertToRevision", + "snapstateSwitch", "assertstateRefreshSnapDeclarations", "unsafeReadSnapInfo", "osutilAddUser", "setupLocalUser", "storeUserInfo", - "postCreateUserUcrednetGetUID", + "postCreateUserUcrednetGet", "ensureStateSoon", } c.Check(found, check.Equals, len(api)+len(exceptions), @@ -1698,6 +1821,7 @@ st := d.overlord.State() st.Lock() + defer st.Unlock() chg := st.Change(rsp.Change) c.Assert(chg, check.NotNil) c.Check(chg.Summary(), check.Equals, "foooo") @@ -1705,7 +1829,6 @@ err = chg.Get("snap-names", &names) c.Assert(err, check.IsNil) c.Check(names, check.DeepEquals, []string{"foo"}) - st.Unlock() c.Check(soon, check.Equals, 1) } @@ -1755,10 +1878,10 @@ st := d.overlord.State() st.Lock() + defer st.Unlock() chg := st.Change(rsp.Change) c.Assert(chg, check.NotNil) c.Check(chg.Summary(), check.Equals, "") - st.Unlock() } func (s *apiSuite) TestPostSnapDispatch(c *check.C) { @@ -1776,6 +1899,7 @@ {"revert", snapRevert}, {"enable", snapEnable}, {"disable", snapDisable}, + {"switch", snapSwitch}, {"xyzzy", nil}, } @@ -1786,8 +1910,8 @@ } } -func (s *apiSuite) TestPostSnapEnableDisableRevision(c *check.C) { - for _, action := range []string{"enable", "disable"} { +func (s *apiSuite) TestPostSnapEnableDisableSwitchRevision(c *check.C) { + for _, action := range []string{"enable", "disable", "switch"} { buf := bytes.NewBufferString(`{"action": "` + action + `", "revision": "42"}`) req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf) c.Assert(err, check.IsNil) @@ -2106,6 +2230,10 @@ return req } + st := d.overlord.State() + st.Lock() + defer st.Unlock() + for _, t := range []struct { coreInfoErr error nTasks int @@ -2153,12 +2281,12 @@ } // try the snap (without an installed core) + st.Unlock() rsp := postSnaps(snapsCmd, reqForFlags(t.flags), nil).(*resp) + st.Lock() c.Assert(rsp.Type, check.Equals, ResponseTypeAsync, check.Commentf(t.desc)) c.Assert(tryWasCalled, check.Equals, true, check.Commentf(t.desc)) - st := d.overlord.State() - st.Lock() chg := st.Change(rsp.Change) c.Assert(chg, check.NotNil, check.Commentf(t.desc)) @@ -2183,7 +2311,6 @@ }, check.Commentf(t.desc)) c.Check(soon, check.Equals, 1, check.Commentf(t.desc)) - st.Unlock() } } @@ -2394,6 +2521,51 @@ }}) } +func (s *apiSuite) TestSetConfNumber(c *check.C) { + d := s.daemon(c) + s.mockSnap(c, configYaml) + + // Mock the hook runner + hookRunner := testutil.MockCommand(c, "snap", "") + defer hookRunner.Restore() + + d.overlord.Loop() + defer d.overlord.Stop() + + text, err := json.Marshal(map[string]interface{}{"key": 1234567890}) + c.Assert(err, check.IsNil) + + buffer := bytes.NewBuffer(text) + req, err := http.NewRequest("PUT", "/v2/snaps/config-snap/conf", buffer) + c.Assert(err, check.IsNil) + + s.vars = map[string]string{"name": "config-snap"} + + rec := httptest.NewRecorder() + snapConfCmd.PUT(snapConfCmd, req, nil).ServeHTTP(rec, req) + c.Check(rec.Code, check.Equals, 202) + + var body map[string]interface{} + err = json.Unmarshal(rec.Body.Bytes(), &body) + c.Assert(err, check.IsNil) + id := body["change"].(string) + + st := d.overlord.State() + st.Lock() + chg := st.Change(id) + st.Unlock() + c.Assert(chg, check.NotNil) + + <-chg.Ready() + + st.Lock() + defer st.Unlock() + tr := config.NewTransaction(d.overlord.State()) + var result interface{} + c.Assert(tr.Get("config-snap", "key", &result), check.IsNil) + c.Assert(result, check.DeepEquals, json.Number("1234567890")) +} + func (s *apiSuite) TestSetConfBadSnap(c *check.C) { d := s.daemon(c) @@ -2425,7 +2597,9 @@ "status": "Not Found", "result": map[string]interface{}{ "message": "no state entry for key", - "kind": "snap-not-found"}, + "kind": "snap-not-found", + "value": "config-snap", + }, "type": "error"}) } @@ -3304,6 +3478,125 @@ }) } +/** +// Tests for GET /v2/interface (note: singular!) + +func (s *apiSuite) TestInterfaceIndex(c *check.C) { + d := s.daemon(c) + + s.mockIface(c, &ifacetest.TestInterface{ + InterfaceName: "test", + InterfaceStaticInfo: interfaces.StaticInfo{ + Summary: "summary", + }, + }) + s.mockSnap(c, consumerYaml) + s.mockSnap(c, producerYaml) + + repo := d.overlord.InterfaceManager().Repository() + connRef := interfaces.ConnRef{ + PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"}, + SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"}, + } + c.Assert(repo.Connect(connRef), check.IsNil) + + req, err := http.NewRequest("GET", "/v2/interface", nil) + c.Assert(err, check.IsNil) + rec := httptest.NewRecorder() + interfaceIndexCmd.GET(interfaceIndexCmd, req, nil).ServeHTTP(rec, req) + c.Check(rec.Code, check.Equals, 200) + var body map[string]interface{} + err = json.Unmarshal(rec.Body.Bytes(), &body) + c.Check(err, check.IsNil) + // The body contains large number of interface names, ensure that just the + // test one, added above, exists. + c.Check(body["result"], testutil.DeepContains, map[string]interface{}{ + "name": "test", + "summary": "summary", + "used": true, + }) + c.Check(body["status"], check.Equals, "OK") + c.Check(body["status-code"], check.Equals, 200.0) + c.Check(body["type"], check.Equals, "sync") +} + +// Tests for GET /v2/interface/test + +func (s *apiSuite) TestInterfaceDetail(c *check.C) { + _ = s.daemon(c) + + s.mockIface(c, &ifacetest.TestInterface{ + InterfaceName: "test", + InterfaceStaticInfo: interfaces.StaticInfo{ + Summary: "summary", + }, + }) + s.mockSnap(c, consumerYaml) + s.mockSnap(c, producerYaml) + + // NOTE: this is confusing, we must set s.vars manually, + s.vars = map[string]string{"name": "test"} + req, err := http.NewRequest("GET", "/v2/interface/test", nil) + c.Assert(err, check.IsNil) + rec := httptest.NewRecorder() + interfaceDetailCmd.GET(interfaceDetailCmd, req, nil).ServeHTTP(rec, req) + c.Check(rec.Code, check.Equals, 200) + var body map[string]interface{} + err = json.Unmarshal(rec.Body.Bytes(), &body) + c.Check(err, check.IsNil) + c.Check(body, check.DeepEquals, map[string]interface{}{ + "result": map[string]interface{}{ + "name": "test", + "summary": "summary", + "plugs": []interface{}{ + map[string]interface{}{ + "snap": "consumer", + "plug": "plug", + "label": "label", + "attrs": map[string]interface{}{"key": "value"}, + }, + }, + "slots": []interface{}{ + map[string]interface{}{ + "snap": "producer", + "slot": "slot", + "label": "label", + "attrs": map[string]interface{}{"key": "value"}, + }, + }, + "used": true, + }, + "status": "OK", + "status-code": 200.0, + "type": "sync", + }) +} + +func (s *apiSuite) TestInterfaceDetail404(c *check.C) { + _ = s.daemon(c) + + // NOTE: this is confusing, we must set s.vars manually, + s.vars = map[string]string{"name": "test"} + req, err := http.NewRequest("GET", "/v2/interface/test", nil) + c.Assert(err, check.IsNil) + rec := httptest.NewRecorder() + interfaceDetailCmd.GET(interfaceDetailCmd, req, nil).ServeHTTP(rec, req) + c.Check(rec.Code, check.Equals, 404) + var body map[string]interface{} + err = json.Unmarshal(rec.Body.Bytes(), &body) + c.Check(err, check.IsNil) + c.Check(body, check.DeepEquals, map[string]interface{}{ + "result": map[string]interface{}{ + "message": `cannot find interface named "test"`, + }, + "status": "Not Found", + "status-code": 404.0, + "type": "error", + }) +} + +**/ + // Test for POST /v2/interfaces func (s *apiSuite) TestConnectPlugSuccess(c *check.C) { @@ -3725,6 +4018,14 @@ }) } +func (s *apiSuite) TestGetAsserts(c *check.C) { + s.daemon(c) + resp := assertsCmd.GET(assertsCmd, nil, nil).(*resp) + c.Check(resp.Status, check.Equals, 200) + c.Check(resp.Type, check.Equals, ResponseTypeSync) + c.Check(resp.Result, check.DeepEquals, map[string][]string{"types": asserts.TypeNames()}) +} + func assertAdd(st *state.State, a asserts.Assertion) { st.Lock() defer st.Unlock() @@ -3837,7 +4138,7 @@ // Verify c.Check(rec.Code, check.Equals, 200, check.Commentf("body %q", rec.Body)) c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/x.ubuntu.assertion; bundle=y") - c.Check(rec.HeaderMap.Get("X-Ubuntu-Assertions-Count"), check.Equals, "3") + c.Check(rec.HeaderMap.Get("X-Ubuntu-Assertions-Count"), check.Equals, "4") dec := asserts.NewDecoder(rec.Body) a1, err := dec.Decode() c.Assert(err, check.IsNil) @@ -3849,12 +4150,15 @@ a3, err := dec.Decode() c.Assert(err, check.IsNil) + a4, err := dec.Decode() + c.Assert(err, check.IsNil) + _, err = dec.Decode() c.Assert(err, check.Equals, io.EOF) - ids := []string{a1.(*asserts.Account).AccountID(), a2.(*asserts.Account).AccountID(), a3.(*asserts.Account).AccountID()} + ids := []string{a1.(*asserts.Account).AccountID(), a2.(*asserts.Account).AccountID(), a3.(*asserts.Account).AccountID(), a4.(*asserts.Account).AccountID()} sort.Strings(ids) - c.Check(ids, check.DeepEquals, []string{"can0nical", "canonical", "developer1-id"}) + c.Check(ids, check.DeepEquals, []string{"can0nical", "canonical", "developer1-id", "generic"}) } func (s *apiSuite) TestAssertsFindManyFilter(c *check.C) { @@ -4453,8 +4757,8 @@ s.apiBaseSuite.SetUpTest(c) s.daemon(c) - postCreateUserUcrednetGetUID = func(string) (uint32, error) { - return 0, nil + postCreateUserUcrednetGet = func(string) (uint32, uint32, error) { + return 100, 0, nil } s.mockUserHome = c.MkDir() userLookup = mkUserLookup(s.mockUserHome) @@ -4463,7 +4767,7 @@ func (s *postCreateUserSuite) TearDownTest(c *check.C) { s.apiBaseSuite.TearDownTest(c) - postCreateUserUcrednetGetUID = ucrednetGetUID + postCreateUserUcrednetGet = ucrednetGet userLookup = user.Lookup osutilAddUser = osutil.AddUser storeUserInfo = store.UserInfo @@ -4631,8 +4935,9 @@ // create fake device st.Lock() err = auth.SetDevice(st, &auth.DeviceState{ - Brand: "my-brand", - Model: "my-model", + Brand: "my-brand", + Model: "my-model", + Serial: "serialserial", }) st.Unlock() c.Assert(err, check.IsNil) @@ -4813,11 +5118,11 @@ s.makeSystemUsers(c, []map[string]interface{}{goodUser}) - postCreateUserUcrednetGetUID = func(string) (uint32, error) { - return 0, nil + postCreateUserUcrednetGet = func(string) (uint32, uint32, error) { + return 100, 0, nil } defer func() { - postCreateUserUcrednetGetUID = ucrednetGetUID + postCreateUserUcrednetGet = ucrednetGet }() // do it! @@ -5514,3 +5819,569 @@ c.Check(rsp.Result.(map[string]interface{})["base-declaration"], testutil.Contains, "type: base-declaration") } + +type appSuite struct { + apiBaseSuite + cmd *testutil.MockCmd + + infoA, infoB, infoC, infoD *snap.Info +} + +var _ = check.Suite(&appSuite{}) + +func (s *appSuite) SetUpTest(c *check.C) { + s.apiBaseSuite.SetUpTest(c) + s.cmd = testutil.MockCommand(c, "systemctl", "").Also("journalctl", "") + s.daemon(c) + s.infoA = s.mkInstalledInState(c, s.d, "snap-a", "dev", "v1", snap.R(1), true, "apps: {svc1: {daemon: simple}, svc2: {daemon: simple, reload-command: x}}") + s.infoB = s.mkInstalledInState(c, s.d, "snap-b", "dev", "v1", snap.R(1), true, "apps: {svc3: {daemon: simple}, cmd1: {}}") + s.infoC = s.mkInstalledInState(c, s.d, "snap-c", "dev", "v1", snap.R(1), true, "") + s.infoD = s.mkInstalledInState(c, s.d, "snap-d", "dev", "v1", snap.R(1), true, "apps: {cmd2: {}, cmd3: {}}") + s.d.overlord.Loop() +} + +func (s *appSuite) TearDownTest(c *check.C) { + s.d.overlord.Stop() + s.cmd.Restore() + s.apiBaseSuite.TearDownTest(c) +} + +func (s *appSuite) TestSplitAppName(c *check.C) { + type T struct { + name string + snap string + app string + } + + for _, x := range []T{ + {name: "foo.bar", snap: "foo", app: "bar"}, + {name: "foo", snap: "foo", app: ""}, + {name: "foo.bar.baz", snap: "foo", app: "bar.baz"}, + {name: ".", snap: "", app: ""}, // SISO + } { + snap, app := splitAppName(x.name) + c.Check(x.snap, check.Equals, snap, check.Commentf(x.name)) + c.Check(x.app, check.Equals, app, check.Commentf(x.name)) + } +} + +func (s *appSuite) TestGetAppsInfo(c *check.C) { + svcNames := []string{"snap-a.svc1", "snap-a.svc2", "snap-b.svc3"} + for _, name := range svcNames { + s.sysctlBufs = append(s.sysctlBufs, []byte(fmt.Sprintf(` +Id=snap.%s.service +Type=simple +ActiveState=active +UnitFileState=enabled +`[1:], name))) + } + + req, err := http.NewRequest("GET", "/v2/apps", nil) + c.Assert(err, check.IsNil) + + rsp := getAppsInfo(appsCmd, req, nil).(*resp) + c.Assert(rsp.Status, check.Equals, 200) + c.Assert(rsp.Type, check.Equals, ResponseTypeSync) + c.Assert(rsp.Result, check.FitsTypeOf, []*client.AppInfo{}) + apps := rsp.Result.([]*client.AppInfo) + c.Assert(apps, check.HasLen, 6) + + for _, name := range svcNames { + snap, app := splitAppName(name) + c.Check(apps, testutil.DeepContains, &client.AppInfo{ + Snap: snap, + Name: app, + Daemon: "simple", + Active: true, + Enabled: true, + }) + } + + for _, name := range []string{"snap-b.cmd1", "snap-d.cmd2", "snap-d.cmd3"} { + snap, app := splitAppName(name) + c.Check(apps, testutil.DeepContains, &client.AppInfo{ + Snap: snap, + Name: app, + }) + } + + appNames := make([]string, len(apps)) + for i, app := range apps { + appNames[i] = app.Snap + "." + app.Name + } + c.Check(sort.StringsAreSorted(appNames), check.Equals, true) +} + +func (s *appSuite) TestGetAppsInfoNames(c *check.C) { + + req, err := http.NewRequest("GET", "/v2/apps?names=snap-d", nil) + c.Assert(err, check.IsNil) + + rsp := getAppsInfo(appsCmd, req, nil).(*resp) + c.Assert(rsp.Status, check.Equals, 200) + c.Assert(rsp.Type, check.Equals, ResponseTypeSync) + c.Assert(rsp.Result, check.FitsTypeOf, []*client.AppInfo{}) + apps := rsp.Result.([]*client.AppInfo) + c.Assert(apps, check.HasLen, 2) + + for _, name := range []string{"snap-d.cmd2", "snap-d.cmd3"} { + snap, app := splitAppName(name) + c.Check(apps, testutil.DeepContains, &client.AppInfo{ + Snap: snap, + Name: app, + }) + } + + appNames := make([]string, len(apps)) + for i, app := range apps { + appNames[i] = app.Snap + "." + app.Name + } + c.Check(sort.StringsAreSorted(appNames), check.Equals, true) +} + +func (s *appSuite) TestGetAppsInfoServices(c *check.C) { + svcNames := []string{"snap-a.svc1", "snap-a.svc2", "snap-b.svc3"} + for _, name := range svcNames { + s.sysctlBufs = append(s.sysctlBufs, []byte(fmt.Sprintf(` +Id=snap.%s.service +Type=simple +ActiveState=active +UnitFileState=enabled +`[1:], name))) + } + + req, err := http.NewRequest("GET", "/v2/apps?select=service", nil) + c.Assert(err, check.IsNil) + + rsp := getAppsInfo(appsCmd, req, nil).(*resp) + c.Assert(rsp.Status, check.Equals, 200) + c.Assert(rsp.Type, check.Equals, ResponseTypeSync) + c.Assert(rsp.Result, check.FitsTypeOf, []*client.AppInfo{}) + svcs := rsp.Result.([]*client.AppInfo) + c.Assert(svcs, check.HasLen, 3) + + for _, name := range svcNames { + snap, app := splitAppName(name) + c.Check(svcs, testutil.DeepContains, &client.AppInfo{ + Snap: snap, + Name: app, + Daemon: "simple", + Active: true, + Enabled: true, + }) + } + + appNames := make([]string, len(svcs)) + for i, svc := range svcs { + appNames[i] = svc.Snap + "." + svc.Name + } + c.Check(sort.StringsAreSorted(appNames), check.Equals, true) +} + +func (s *appSuite) TestGetAppsInfoBadSelect(c *check.C) { + req, err := http.NewRequest("GET", "/v2/apps?select=potato", nil) + c.Assert(err, check.IsNil) + + rsp := getAppsInfo(appsCmd, req, nil).(*resp) + c.Assert(rsp.Status, check.Equals, 400) + c.Assert(rsp.Type, check.Equals, ResponseTypeError) +} + +func (s *appSuite) TestGetAppsInfoBadName(c *check.C) { + req, err := http.NewRequest("GET", "/v2/apps?names=potato", nil) + c.Assert(err, check.IsNil) + + rsp := getAppsInfo(appsCmd, req, nil).(*resp) + c.Assert(rsp.Status, check.Equals, 404) + c.Assert(rsp.Type, check.Equals, ResponseTypeError) +} + +func (s *appSuite) TestAppInfosForOne(c *check.C) { + st := s.d.overlord.State() + appInfos, rsp := appInfosFor(st, []string{"snap-a.svc1"}, appInfoOptions{service: true}) + c.Assert(rsp, check.IsNil) + c.Assert(appInfos, check.HasLen, 1) + c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA) + c.Check(appInfos[0].Name, check.Equals, "svc1") +} + +func (s *appSuite) TestAppInfosForAll(c *check.C) { + type T struct { + opts appInfoOptions + snaps []*snap.Info + names []string + } + + for _, t := range []T{ + { + opts: appInfoOptions{service: true}, + names: []string{"svc1", "svc2", "svc3"}, + snaps: []*snap.Info{s.infoA, s.infoA, s.infoB}, + }, + { + opts: appInfoOptions{}, + names: []string{"svc1", "svc2", "cmd1", "svc3", "cmd2", "cmd3"}, + snaps: []*snap.Info{s.infoA, s.infoA, s.infoB, s.infoB, s.infoD, s.infoD}, + }, + } { + c.Assert(len(t.names), check.Equals, len(t.snaps), check.Commentf("%s", t.opts)) + + st := s.d.overlord.State() + appInfos, rsp := appInfosFor(st, nil, t.opts) + c.Assert(rsp, check.IsNil, check.Commentf("%s", t.opts)) + names := make([]string, len(appInfos)) + for i, appInfo := range appInfos { + names[i] = appInfo.Name + } + c.Assert(names, check.DeepEquals, t.names, check.Commentf("%s", t.opts)) + + for i := range appInfos { + c.Check(appInfos[i].Snap, check.DeepEquals, t.snaps[i], check.Commentf("%s: %s", t.opts, t.names[i])) + } + } +} + +func (s *appSuite) TestAppInfosForOneSnap(c *check.C) { + st := s.d.overlord.State() + appInfos, rsp := appInfosFor(st, []string{"snap-a"}, appInfoOptions{service: true}) + c.Assert(rsp, check.IsNil) + c.Assert(appInfos, check.HasLen, 2) + sort.Sort(bySnapApp(appInfos)) + + c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA) + c.Check(appInfos[0].Name, check.Equals, "svc1") + c.Check(appInfos[1].Snap, check.DeepEquals, s.infoA) + c.Check(appInfos[1].Name, check.Equals, "svc2") +} + +func (s *appSuite) TestAppInfosForMixedArgs(c *check.C) { + st := s.d.overlord.State() + appInfos, rsp := appInfosFor(st, []string{"snap-a", "snap-a.svc1"}, appInfoOptions{service: true}) + c.Assert(rsp, check.IsNil) + c.Assert(appInfos, check.HasLen, 2) + sort.Sort(bySnapApp(appInfos)) + + c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA) + c.Check(appInfos[0].Name, check.Equals, "svc1") + c.Check(appInfos[1].Snap, check.DeepEquals, s.infoA) + c.Check(appInfos[1].Name, check.Equals, "svc2") +} + +func (s *appSuite) TestAppInfosCleanupAndSorted(c *check.C) { + st := s.d.overlord.State() + appInfos, rsp := appInfosFor(st, []string{ + "snap-b.svc3", + "snap-a.svc2", + "snap-a.svc1", + "snap-a.svc2", + "snap-b.svc3", + "snap-a.svc1", + "snap-b", + "snap-a", + }, appInfoOptions{service: true}) + c.Assert(rsp, check.IsNil) + c.Assert(appInfos, check.HasLen, 3) + sort.Sort(bySnapApp(appInfos)) + + c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA) + c.Check(appInfos[0].Name, check.Equals, "svc1") + c.Check(appInfos[1].Snap, check.DeepEquals, s.infoA) + c.Check(appInfos[1].Name, check.Equals, "svc2") + c.Check(appInfos[2].Snap, check.DeepEquals, s.infoB) + c.Check(appInfos[2].Name, check.Equals, "svc3") +} + +func (s *appSuite) TestAppInfosForAppless(c *check.C) { + st := s.d.overlord.State() + appInfos, rsp := appInfosFor(st, []string{"snap-c"}, appInfoOptions{service: true}) + c.Assert(rsp, check.FitsTypeOf, &resp{}) + c.Check(rsp.(*resp).Status, check.Equals, 404) + c.Check(rsp.(*resp).Result.(*errorResult).Kind, check.Equals, errorKindAppNotFound) + c.Assert(appInfos, check.IsNil) +} + +func (s *appSuite) TestAppInfosForMissingApp(c *check.C) { + st := s.d.overlord.State() + appInfos, rsp := appInfosFor(st, []string{"snap-c.whatever"}, appInfoOptions{service: true}) + c.Assert(rsp, check.FitsTypeOf, &resp{}) + c.Check(rsp.(*resp).Status, check.Equals, 404) + c.Check(rsp.(*resp).Result.(*errorResult).Kind, check.Equals, errorKindAppNotFound) + c.Assert(appInfos, check.IsNil) +} + +func (s *appSuite) TestAppInfosForMissingSnap(c *check.C) { + st := s.d.overlord.State() + appInfos, rsp := appInfosFor(st, []string{"snap-x"}, appInfoOptions{service: true}) + c.Assert(rsp, check.FitsTypeOf, &resp{}) + c.Check(rsp.(*resp).Status, check.Equals, 404) + c.Check(rsp.(*resp).Result.(*errorResult).Kind, check.Equals, errorKindSnapNotFound) + c.Assert(appInfos, check.IsNil) +} + +func (s *appSuite) TestLogs(c *check.C) { + s.jctlRCs = []io.ReadCloser{ioutil.NopCloser(strings.NewReader(` +{"MESSAGE": "hello1", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "42"} +{"MESSAGE": "hello2", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "44"} +{"MESSAGE": "hello3", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "46"} +{"MESSAGE": "hello4", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "48"} +{"MESSAGE": "hello5", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "50"} + `))} + + req, err := http.NewRequest("GET", "/v2/logs?names=snap-a.svc2&n=42&follow=false", nil) + c.Assert(err, check.IsNil) + + rec := httptest.NewRecorder() + getLogs(logsCmd, req, nil).ServeHTTP(rec, req) + + c.Check(s.jctlSvcses, check.DeepEquals, [][]string{{"snap.snap-a.svc2.service"}}) + c.Check(s.jctlNs, check.DeepEquals, []string{"42"}) + c.Check(s.jctlFollows, check.DeepEquals, []bool{false}) + + c.Check(rec.Code, check.Equals, 200) + c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json-seq") + c.Check(rec.Body.String(), check.Equals, ` +{"timestamp":"1970-01-01T00:00:00.000042Z","message":"hello1","sid":"xyzzy","pid":"42"} +{"timestamp":"1970-01-01T00:00:00.000044Z","message":"hello2","sid":"xyzzy","pid":"42"} +{"timestamp":"1970-01-01T00:00:00.000046Z","message":"hello3","sid":"xyzzy","pid":"42"} +{"timestamp":"1970-01-01T00:00:00.000048Z","message":"hello4","sid":"xyzzy","pid":"42"} +{"timestamp":"1970-01-01T00:00:00.00005Z","message":"hello5","sid":"xyzzy","pid":"42"} +`[1:]) +} + +func (s *appSuite) TestLogsN(c *check.C) { + type T struct { + in string + out string + } + + for _, t := range []T{ + {in: "", out: "10"}, + {in: "0", out: "0"}, + {in: "-1", out: "all"}, + {in: strconv.Itoa(math.MinInt32), out: "all"}, + {in: strconv.Itoa(math.MaxInt32), out: strconv.Itoa(math.MaxInt32)}, + } { + + s.jctlRCs = []io.ReadCloser{ioutil.NopCloser(strings.NewReader(""))} + s.jctlNs = nil + + req, err := http.NewRequest("GET", "/v2/logs?n="+t.in, nil) + c.Assert(err, check.IsNil) + + rec := httptest.NewRecorder() + getLogs(logsCmd, req, nil).ServeHTTP(rec, req) + + c.Check(s.jctlNs, check.DeepEquals, []string{t.out}) + } +} + +func (s *appSuite) TestLogsBadN(c *check.C) { + req, err := http.NewRequest("GET", "/v2/logs?n=hello", nil) + c.Assert(err, check.IsNil) + + rsp := getLogs(logsCmd, req, nil).(*resp) + c.Assert(rsp.Status, check.Equals, 400) + c.Assert(rsp.Type, check.Equals, ResponseTypeError) +} + +func (s *appSuite) TestLogsFollow(c *check.C) { + s.jctlRCs = []io.ReadCloser{ + ioutil.NopCloser(strings.NewReader("")), + ioutil.NopCloser(strings.NewReader("")), + ioutil.NopCloser(strings.NewReader("")), + } + + reqT, err := http.NewRequest("GET", "/v2/logs?follow=true", nil) + c.Assert(err, check.IsNil) + reqF, err := http.NewRequest("GET", "/v2/logs?follow=false", nil) + c.Assert(err, check.IsNil) + reqN, err := http.NewRequest("GET", "/v2/logs", nil) + c.Assert(err, check.IsNil) + + rec := httptest.NewRecorder() + getLogs(logsCmd, reqT, nil).ServeHTTP(rec, reqT) + getLogs(logsCmd, reqF, nil).ServeHTTP(rec, reqF) + getLogs(logsCmd, reqN, nil).ServeHTTP(rec, reqN) + + c.Check(s.jctlFollows, check.DeepEquals, []bool{true, false, false}) +} + +func (s *appSuite) TestLogsBadFollow(c *check.C) { + req, err := http.NewRequest("GET", "/v2/logs?follow=hello", nil) + c.Assert(err, check.IsNil) + + rsp := getLogs(logsCmd, req, nil).(*resp) + c.Assert(rsp.Status, check.Equals, 400) + c.Assert(rsp.Type, check.Equals, ResponseTypeError) +} + +func (s *appSuite) TestLogsBadName(c *check.C) { + req, err := http.NewRequest("GET", "/v2/logs?names=hello", nil) + c.Assert(err, check.IsNil) + + rsp := getLogs(logsCmd, req, nil).(*resp) + c.Assert(rsp.Status, check.Equals, 404) + c.Assert(rsp.Type, check.Equals, ResponseTypeError) +} + +func (s *appSuite) TestLogsSad(c *check.C) { + s.jctlErrs = []error{errors.New("potato")} + req, err := http.NewRequest("GET", "/v2/logs", nil) + c.Assert(err, check.IsNil) + + rsp := getLogs(logsCmd, req, nil).(*resp) + c.Assert(rsp.Status, check.Equals, 500) + c.Assert(rsp.Type, check.Equals, ResponseTypeError) +} + +func (s *appSuite) testPostApps(c *check.C, inst appInstruction, systemctlCall []string) *state.Change { + postBody, err := json.Marshal(inst) + c.Assert(err, check.IsNil) + + req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBuffer(postBody)) + c.Assert(err, check.IsNil) + + rsp := postApps(appsCmd, req, nil).(*resp) + c.Assert(rsp.Status, check.Equals, 202) + c.Assert(rsp.Type, check.Equals, ResponseTypeAsync) + c.Check(rsp.Change, check.Matches, `[0-9]+`) + + st := s.d.overlord.State() + st.Lock() + defer st.Unlock() + chg := st.Change(rsp.Change) + c.Assert(chg, check.NotNil) + c.Check(chg.Tasks(), check.HasLen, 1) + + st.Unlock() + <-chg.Ready() + st.Lock() + + c.Check(s.cmd.Calls(), check.DeepEquals, [][]string{systemctlCall}) + return chg +} + +func (s *appSuite) TestPostAppsStartOne(c *check.C) { + inst := appInstruction{Action: "start", Names: []string{"snap-a.svc2"}} + expected := []string{"systemctl", "start", "snap.snap-a.svc2.service"} + s.testPostApps(c, inst, expected) +} + +func (s *appSuite) TestPostAppsStartTwo(c *check.C) { + inst := appInstruction{Action: "start", Names: []string{"snap-a"}} + expected := []string{"systemctl", "start", "snap.snap-a.svc1.service", "snap.snap-a.svc2.service"} + chg := s.testPostApps(c, inst, expected) + // check the summary expands the snap into actual apps + c.Check(chg.Summary(), check.Equals, "start of [snap-a.svc1 snap-a.svc2]") +} + +func (s *appSuite) TestPostAppsStartThree(c *check.C) { + inst := appInstruction{Action: "start", Names: []string{"snap-a", "snap-b"}} + expected := []string{"systemctl", "start", "snap.snap-a.svc1.service", "snap.snap-a.svc2.service", "snap.snap-b.svc3.service"} + chg := s.testPostApps(c, inst, expected) + // check the summary expands the snap into actual apps + c.Check(chg.Summary(), check.Equals, "start of [snap-a.svc1 snap-a.svc2 snap-b.svc3]") +} + +func (s *appSuite) TestPosetAppsStop(c *check.C) { + inst := appInstruction{Action: "stop", Names: []string{"snap-a.svc2"}} + expected := []string{"systemctl", "stop", "snap.snap-a.svc2.service"} + s.testPostApps(c, inst, expected) +} + +func (s *appSuite) TestPosetAppsRestart(c *check.C) { + inst := appInstruction{Action: "restart", Names: []string{"snap-a.svc2"}} + expected := []string{"systemctl", "restart", "snap.snap-a.svc2.service"} + s.testPostApps(c, inst, expected) +} + +func (s *appSuite) TestPosetAppsReload(c *check.C) { + inst := appInstruction{Action: "restart", Names: []string{"snap-a.svc2"}} + inst.Reload = true + expected := []string{"systemctl", "reload-or-restart", "snap.snap-a.svc2.service"} + s.testPostApps(c, inst, expected) +} + +func (s *appSuite) TestPosetAppsEnableNow(c *check.C) { + inst := appInstruction{Action: "start", Names: []string{"snap-a.svc2"}} + inst.Enable = true + expected := []string{"systemctl", "enable", "--now", "snap.snap-a.svc2.service"} + s.testPostApps(c, inst, expected) +} + +func (s *appSuite) TestPosetAppsDisableNow(c *check.C) { + inst := appInstruction{Action: "stop", Names: []string{"snap-a.svc2"}} + inst.Disable = true + expected := []string{"systemctl", "disable", "--now", "snap.snap-a.svc2.service"} + s.testPostApps(c, inst, expected) +} + +func (s *appSuite) TestPostAppsBadJSON(c *check.C) { + req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`'junk`)) + c.Assert(err, check.IsNil) + 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.Matches, ".*cannot decode request body.*") +} + +func (s *appSuite) TestPostAppsBadOp(c *check.C) { + req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"random": "json"}`)) + c.Assert(err, check.IsNil) + 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.Matches, ".*cannot perform operation on services without a list of services.*") +} + +func (s *appSuite) TestPostAppsBadSnap(c *check.C) { + req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "stop", "names": ["snap-c"]}`)) + c.Assert(err, check.IsNil) + rsp := postApps(appsCmd, req, nil).(*resp) + c.Check(rsp.Status, check.Equals, 404) + c.Check(rsp.Type, check.Equals, ResponseTypeError) + c.Check(rsp.Result.(*errorResult).Message, check.Equals, `snap "snap-c" has no services`) +} + +func (s *appSuite) TestPostAppsBadApp(c *check.C) { + req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "stop", "names": ["snap-a.what"]}`)) + c.Assert(err, check.IsNil) + rsp := postApps(appsCmd, req, nil).(*resp) + c.Check(rsp.Status, check.Equals, 404) + c.Check(rsp.Type, check.Equals, ResponseTypeError) + c.Check(rsp.Result.(*errorResult).Message, check.Equals, `snap "snap-a" has no service "what"`) +} + +func (s *appSuite) TestPostAppsBadAction(c *check.C) { + req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "discombobulate", "names": ["snap-a.svc1"]}`)) + c.Assert(err, check.IsNil) + 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, `unknown action "discombobulate"`) +} + +func (s *appSuite) TestPostAppsConflict(c *check.C) { + st := s.d.overlord.State() + st.Lock() + locked := true + defer func() { + if locked { + st.Unlock() + } + }() + + ts, err := snapstate.Remove(st, "snap-a", snap.R(0)) + c.Assert(err, check.IsNil) + // need a change to make the tasks visible + st.NewChange("enable", "...").AddAll(ts) + st.Unlock() + locked = false + + req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "start", "names": ["snap-a.svc1"]}`)) + c.Assert(err, check.IsNil) + rsp := postApps(appsCmd, req, nil).(*resp) + c.Check(rsp.Status, check.Equals, 500) + c.Check(rsp.Type, check.Equals, ResponseTypeError) + c.Check(rsp.Result.(*errorResult).Message, check.Equals, `snap "snap-a" has changes in progress`) +} diff -Nru snapd-2.27.5/daemon/daemon.go snapd-2.28.5/daemon/daemon.go --- snapd-2.27.5/daemon/daemon.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/daemon/daemon.go 2017-09-13 14:47:18.000000000 +0000 @@ -26,6 +26,7 @@ "os" "os/exec" "runtime" + "strconv" "strings" "sync" unix "syscall" @@ -35,6 +36,7 @@ "github.com/gorilla/mux" "gopkg.in/tomb.v2" + "github.com/snapcore/snapd/client" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/httputil" "github.com/snapcore/snapd/i18n/dumb" @@ -43,6 +45,7 @@ "github.com/snapcore/snapd/overlord" "github.com/snapcore/snapd/overlord/auth" "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/polkit" ) // A Daemon listens for requests and routes them to the right command @@ -77,9 +80,14 @@ // is this path accessible on the snapd-snap socket? SnapOK bool + // can polkit grant access? set to polkit action ID if so + PolkitOK string + d *Daemon } +var polkitCheckAuthorizationForPid = polkit.CheckAuthorizationForPid + func (c *Command) canAccess(r *http.Request, user *auth.UserState) bool { if user != nil { // Authenticated users do anything for now. @@ -87,31 +95,55 @@ } isUser := false - uid, err := ucrednetGetUID(r.RemoteAddr) + pid, uid, err := ucrednetGet(r.RemoteAddr) if err == nil { - if uid == 0 { - // Superuser does anything. - return true - } - isUser = true - } else if err != errNoUID { + } else if err != errNoID { logger.Noticef("unexpected error when attempting to get UID: %s", err) return false } else if c.SnapOK { return true } - if r.Method != "GET" { + if r.Method == "GET" { + // Guest and user access restricted to GET requests + if c.GuestOK { + return true + } + + if isUser && c.UserOK { + return true + } + } + + // Remaining admin checks rely on identifying peer uid + if !isUser { return false } - if isUser && c.UserOK { + if uid == 0 { + // Superuser does anything. return true } - if c.GuestOK { - return true + if c.PolkitOK != "" { + var flags polkit.CheckFlags + allowHeader := r.Header.Get(client.AllowInteractionHeader) + if allowHeader != "" { + if allow, err := strconv.ParseBool(allowHeader); err != nil { + logger.Noticef("error parsing %s header: %s", client.AllowInteractionHeader, err) + } else if allow { + flags |= polkit.CheckAllowInteraction + } + } + if authorized, err := polkitCheckAuthorizationForPid(pid, c.PolkitOK, nil, flags); err == nil { + if authorized { + // polkit says user is authorised + return true + } + } else if err != polkit.ErrDismissed { + logger.Noticef("polkit error: %s", err) + } } return false @@ -168,6 +200,12 @@ w.s = s } +func (w *wrappedWriter) Flush() { + if f, ok := w.w.(http.Flusher); ok { + f.Flush() + } +} + func logit(handler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ww := &wrappedWriter{w: w} diff -Nru snapd-2.27.5/daemon/daemon_test.go snapd-2.28.5/daemon/daemon_test.go --- snapd-2.27.5/daemon/daemon_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/daemon/daemon_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -22,6 +22,8 @@ import ( "fmt" + "bytes" + "errors" "io/ioutil" "net" "net/http" @@ -35,19 +37,32 @@ "github.com/gorilla/mux" "gopkg.in/check.v1" + "github.com/snapcore/snapd/client" "github.com/snapcore/snapd/dirs" + "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/polkit" + "github.com/snapcore/snapd/testutil" ) // Hook up check.v1 into the "go test" runner func Test(t *testing.T) { check.TestingT(t) } -type daemonSuite struct{} +type daemonSuite struct { + authorized bool + err error + lastPolkitFlags polkit.CheckFlags +} var _ = check.Suite(&daemonSuite{}) +func (s *daemonSuite) checkAuthorizationForPid(pid uint32, actionId string, details map[string]string, flags polkit.CheckFlags) (bool, error) { + s.lastPolkitFlags = flags + return s.authorized, s.err +} + func (s *daemonSuite) SetUpSuite(c *check.C) { snapstate.CanAutoRefresh = nil } @@ -56,10 +71,18 @@ dirs.SetRootDir(c.MkDir()) err := os.MkdirAll(filepath.Dir(dirs.SnapStateFile), 0755) c.Assert(err, check.IsNil) + polkitCheckAuthorizationForPid = s.checkAuthorizationForPid } func (s *daemonSuite) TearDownTest(c *check.C) { dirs.SetRootDir("") + s.authorized = false + s.err = nil + logger.SetLogger(logger.NullLogger) +} + +func (s *daemonSuite) TearDownSuite(c *check.C) { + polkitCheckAuthorizationForPid = polkit.CheckAuthorizationForPid } // build a new daemon, with only a little of Init(), suitable for the tests @@ -106,7 +129,7 @@ c.Check(rec.Code, check.Equals, 401, check.Commentf(method)) rec = httptest.NewRecorder() - req.RemoteAddr = "uid=0;" + req.RemoteAddr + req.RemoteAddr = "pid=100;uid=0;" + req.RemoteAddr cmd.ServeHTTP(rec, req) c.Check(mck.lastMethod, check.Equals, method) @@ -115,7 +138,7 @@ req, err := http.NewRequest("POTATO", "", nil) c.Assert(err, check.IsNil) - req.RemoteAddr = "uid=0;" + req.RemoteAddr + req.RemoteAddr = "pid=100;uid=0;" + req.RemoteAddr rec := httptest.NewRecorder() cmd.ServeHTTP(rec, req) @@ -157,8 +180,8 @@ } func (s *daemonSuite) TestUserAccess(c *check.C) { - get := &http.Request{Method: "GET", RemoteAddr: "uid=42;"} - put := &http.Request{Method: "PUT", RemoteAddr: "uid=42;"} + get := &http.Request{Method: "GET", RemoteAddr: "pid=100;uid=42;"} + put := &http.Request{Method: "PUT", RemoteAddr: "pid=100;uid=42;"} cmd := &Command{d: newTestDaemon(c)} c.Check(cmd.canAccess(get, nil), check.Equals, false) @@ -181,8 +204,8 @@ } func (s *daemonSuite) TestSuperAccess(c *check.C) { - get := &http.Request{Method: "GET", RemoteAddr: "uid=0;"} - put := &http.Request{Method: "PUT", RemoteAddr: "uid=0;"} + get := &http.Request{Method: "GET", RemoteAddr: "pid=100;uid=0;"} + put := &http.Request{Method: "PUT", RemoteAddr: "pid=100;uid=0;"} cmd := &Command{d: newTestDaemon(c)} c.Check(cmd.canAccess(get, nil), check.Equals, true) @@ -201,6 +224,65 @@ c.Check(cmd.canAccess(put, nil), check.Equals, true) } +func (s *daemonSuite) TestPolkitAccess(c *check.C) { + put := &http.Request{Method: "PUT", RemoteAddr: "pid=100;uid=42;"} + cmd := &Command{d: newTestDaemon(c), PolkitOK: "polkit.action"} + + // polkit says user is not authorised + s.authorized = false + c.Check(cmd.canAccess(put, nil), check.Equals, false) + + // polkit grants authorisation + s.authorized = true + c.Check(cmd.canAccess(put, nil), check.Equals, true) + + // an error occurs communicating with polkit + s.err = errors.New("error") + c.Check(cmd.canAccess(put, nil), check.Equals, false) +} + +func (s *daemonSuite) TestPolkitAccessForGet(c *check.C) { + get := &http.Request{Method: "GET", RemoteAddr: "pid=100;uid=42;"} + cmd := &Command{d: newTestDaemon(c), PolkitOK: "polkit.action"} + + // polkit can grant authorisation for GET requests + s.authorized = true + c.Check(cmd.canAccess(get, nil), check.Equals, true) + + // for UserOK commands, polkit is not consulted + cmd.UserOK = true + polkitCheckAuthorizationForPid = func(pid uint32, actionId string, details map[string]string, flags polkit.CheckFlags) (bool, error) { + panic("polkit.CheckAuthorizationForPid called") + } + c.Check(cmd.canAccess(get, nil), check.Equals, true) +} + +func (s *daemonSuite) TestPolkitInteractivity(c *check.C) { + put := &http.Request{Method: "PUT", RemoteAddr: "pid=100;uid=42;", Header: make(http.Header)} + cmd := &Command{d: newTestDaemon(c), PolkitOK: "polkit.action"} + s.authorized = true + + var logbuf bytes.Buffer + log, err := logger.New(&logbuf, logger.DefaultFlags) + c.Assert(err, check.IsNil) + logger.SetLogger(log) + + c.Check(cmd.canAccess(put, nil), check.Equals, true) + c.Check(s.lastPolkitFlags, check.Equals, polkit.CheckNone) + c.Check(logbuf.String(), check.Equals, "") + + put.Header.Set(client.AllowInteractionHeader, "true") + c.Check(cmd.canAccess(put, nil), check.Equals, true) + c.Check(s.lastPolkitFlags, check.Equals, polkit.CheckAllowInteraction) + c.Check(logbuf.String(), check.Equals, "") + + // bad values are logged and treated as false + put.Header.Set(client.AllowInteractionHeader, "garbage") + c.Check(cmd.canAccess(put, nil), check.Equals, true) + c.Check(s.lastPolkitFlags, check.Equals, polkit.CheckNone) + c.Check(logbuf.String(), testutil.Contains, "error parsing X-Allow-Interaction header:") +} + func (s *daemonSuite) TestAddRoutes(c *check.C) { d := newTestDaemon(c) @@ -254,13 +336,22 @@ return err } -func (s *daemonSuite) TestStartStop(c *check.C) { - d := newTestDaemon(c) +func (s *daemonSuite) markSeeded(d *Daemon) { st := d.overlord.State() - // mark as already seeded st.Lock() st.Set("seeded", true) + auth.SetDevice(st, &auth.DeviceState{ + Brand: "canonical", + Model: "pc", + Serial: "serialserial", + }) st.Unlock() +} + +func (s *daemonSuite) TestStartStop(c *check.C) { + d := newTestDaemon(c) + // mark as already seeded + s.markSeeded(d) l, err := net.Listen("tcp", "127.0.0.1:0") c.Assert(err, check.IsNil) @@ -303,10 +394,7 @@ func (s *daemonSuite) TestRestartWiring(c *check.C) { d := newTestDaemon(c) // mark as already seeded - st := d.overlord.State() - st.Lock() - st.Set("seeded", true) - st.Unlock() + s.markSeeded(d) l, err := net.Listen("tcp", "127.0.0.1:0") c.Assert(err, check.IsNil) @@ -368,11 +456,8 @@ return }) - st := d.overlord.State() // mark as already seeded - st.Lock() - st.Set("seeded", true) - st.Unlock() + s.markSeeded(d) snapdL, err := net.Listen("tcp", "127.0.0.1:0") c.Assert(err, check.IsNil) diff -Nru snapd-2.27.5/daemon/response.go snapd-2.28.5/daemon/response.go --- snapd-2.27.5/daemon/response.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/daemon/response.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,15 +20,19 @@ package daemon import ( + "bufio" "encoding/json" "fmt" + "io" "mime" "net/http" "path/filepath" "strconv" "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/client" "github.com/snapcore/snapd/logger" + "github.com/snapcore/snapd/systemd" ) // ResponseType is the response type @@ -129,6 +133,7 @@ errorKindSnapAlreadyInstalled = errorKind("snap-already-installed") errorKindSnapNotInstalled = errorKind("snap-not-installed") errorKindSnapNotFound = errorKind("snap-not-found") + errorKindAppNotFound = errorKind("app-not-found") errorKindSnapLocal = errorKind("snap-local") errorKindSnapNoUpdateAvailable = errorKind("snap-no-update-available") @@ -202,6 +207,68 @@ http.ServeFile(w, r, string(f)) } +// A journalLineReaderSeqResponse's ServeHTTP method reads lines (presumed to +// be, each one on its own, a JSON dump of a systemd.Log, as output by +// journalctl -o json) from an io.ReadCloser, loads that into a client.Log, and +// outputs the json dump of that, padded with RS and LF to make it a valid +// json-seq response. +// +// The reader is always closed when done (this is important for +// osutil.WatingStdoutPipe). +// +// Tip: “jq” knows how to read this; “jq --seq” both reads and writes this. +type journalLineReaderSeqResponse struct { + io.ReadCloser + follow bool +} + +func (rr *journalLineReaderSeqResponse) ServeHTTP(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json-seq") + + flusher, hasFlusher := w.(http.Flusher) + + var err error + dec := json.NewDecoder(rr) + writer := bufio.NewWriter(w) + enc := json.NewEncoder(writer) + for { + var log systemd.Log + if err = dec.Decode(&log); err != nil { + break + } + + writer.WriteByte(0x1E) // RS -- see ascii(7), and RFC7464 + + // ignore the error... + t, _ := log.Time() + if err = enc.Encode(client.Log{ + Timestamp: t, + Message: log.Message(), + SID: log.SID(), + PID: log.PID(), + }); err != nil { + break + } + + if rr.follow { + if e := writer.Flush(); e != nil { + break + } + if hasFlusher { + flusher.Flush() + } + } + } + if err != nil && err != io.EOF { + fmt.Fprintf(writer, `\x1E{"error": %q}\n`, err) + logger.Noticef("cannot stream response; problem reading: %v", err) + } + if err := writer.Flush(); err != nil { + logger.Noticef("cannot stream response; problem writing: %v", err) + } + rr.Close() +} + type assertResponse struct { assertions []asserts.Assertion bundle bool @@ -250,13 +317,30 @@ Conflict = makeErrorResponder(409) ) -func SnapNotFound(err error) Response { +// SnapNotFound is an error responder used when an operation is +// requested on a snap that doesn't exist. +func SnapNotFound(snapName string, err error) Response { return &resp{ Type: ResponseTypeError, Result: &errorResult{ Message: err.Error(), Kind: errorKindSnapNotFound, + Value: snapName, }, Status: 404, } } + +// AppNotFound is an error responder used when an operation is +// requested on a app that doesn't exist. +func AppNotFound(format string, v ...interface{}) Response { + res := &errorResult{ + Message: fmt.Sprintf(format, v...), + Kind: errorKindAppNotFound, + } + return &resp{ + Type: ResponseTypeError, + Result: res, + Status: 404, + } +} diff -Nru snapd-2.27.5/daemon/snap.go snapd-2.28.5/daemon/snap.go --- snapd-2.27.5/daemon/snap.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/daemon/snap.go 2017-09-13 14:47:18.000000000 +0000 @@ -25,13 +25,19 @@ "os" "path/filepath" "sort" + "strings" "time" + "github.com/snapcore/snapd/client" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/osutil" "github.com/snapcore/snapd/overlord/assertstate" "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/progress" "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/systemd" ) var errNoSnap = errors.New("snap not installed") @@ -157,13 +163,6 @@ return about, firstErr } -// appJSON contains the json for snap.AppInfo -type appJSON struct { - Name string `json:"name"` - Daemon string `json:"daemon"` - DesktopFile string `json:"desktop-file,omitempty"` -} - // screenshotJSON contains the json for snap.ScreenshotInfo type screenshotJSON struct { URL string `json:"url"` @@ -171,6 +170,150 @@ Height int64 `json:"height,omitempty"` } +type bySnapApp []*snap.AppInfo + +func (a bySnapApp) Len() int { return len(a) } +func (a bySnapApp) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a bySnapApp) Less(i, j int) bool { + iName := a[i].Snap.Name() + jName := a[j].Snap.Name() + if iName == jName { + return a[i].Name < a[j].Name + } + return iName < jName +} + +// this differs from snap.SplitSnapApp in the handling of the +// snap-only case: +// snap.SplitSnapApp("foo") is ("foo", "foo"), +// splitAppName("foo") is ("foo", ""). +func splitAppName(s string) (snap, app string) { + if idx := strings.IndexByte(s, '.'); idx > -1 { + return s[:idx], s[idx+1:] + } + + return s, "" +} + +type appInfoOptions struct { + service bool +} + +func (opts appInfoOptions) String() string { + if opts.service { + return "service" + } + + return "app" +} + +// appInfosFor returns a sorted list apps described by names. +// +// * If names is empty, returns all apps of the wanted kinds (which +// could be an empty list). +// * An element of names can be a snap name, in which case all apps +// from the snap of the wanted kind are included in the result (and +// it's an error if the snap has no apps of the wanted kind). +// * An element of names can instead be snap.app, in which case that app is +// included in the result (and it's an error if the snap and app don't +// both exist, or if the app is not a wanted kind) +// On error an appropriate error Response is returned; a nil Response means +// no error. +// +// It's a programming error to call this with wanted having neither +// services nor commands set. +func appInfosFor(st *state.State, names []string, opts appInfoOptions) ([]*snap.AppInfo, Response) { + snapNames := make(map[string]bool) + requested := make(map[string]bool) + for _, name := range names { + requested[name] = true + name, _ = splitAppName(name) + snapNames[name] = true + } + + snaps, err := allLocalSnapInfos(st, false, snapNames) + if err != nil { + return nil, InternalError("cannot list local snaps! %v", err) + } + + found := make(map[string]bool) + appInfos := make([]*snap.AppInfo, 0, len(requested)) + for _, snp := range snaps { + snapName := snp.info.Name() + apps := make([]*snap.AppInfo, 0, len(snp.info.Apps)) + for _, app := range snp.info.Apps { + if !opts.service || app.IsService() { + apps = append(apps, app) + } + } + + if len(apps) == 0 && requested[snapName] { + return nil, AppNotFound("snap %q has no %ss", snapName, opts) + } + + includeAll := len(requested) == 0 || requested[snapName] + if includeAll { + // want all services in a snap + found[snapName] = true + } + + for _, app := range apps { + appName := snapName + "." + app.Name + if includeAll || requested[appName] { + appInfos = append(appInfos, app) + found[appName] = true + } + } + } + + for k := range requested { + if !found[k] { + if snapNames[k] { + return nil, SnapNotFound(k, fmt.Errorf("snap %q not found", k)) + } else { + snap, app := splitAppName(k) + return nil, AppNotFound("snap %q has no %s %q", snap, opts, app) + } + } + } + + sort.Sort(bySnapApp(appInfos)) + + return appInfos, nil +} + +func clientAppInfosFromSnapAppInfos(apps []*snap.AppInfo) []*client.AppInfo { + // TODO: pass in an actual notifier here instead of null + // (Status doesn't _need_ it, but benefits from it) + sysd := systemd.New(dirs.GlobalRootDir, &progress.NullProgress{}) + + out := make([]*client.AppInfo, len(apps)) + for i, app := range apps { + out[i] = &client.AppInfo{ + Snap: app.Snap.Name(), + Name: app.Name, + } + if fn := app.DesktopFile(); osutil.FileExists(fn) { + out[i].DesktopFile = fn + } + + if app.IsService() { + // TODO: look into making a single call to Status for all services + if sts, err := sysd.Status(app.ServiceName()); err != nil { + logger.Noticef("cannot get status of service %q: %v", app.Name, err) + } else if len(sts) != 1 { + logger.Noticef("cannot get status of service %q: expected 1 result, got %d", app.Name, len(sts)) + } else { + out[i].Daemon = sts[0].Daemon + out[i].Enabled = sts[0].Enabled + out[i].Active = sts[0].Active + } + } + } + + return out +} + func mapLocal(about aboutSnap) map[string]interface{} { localSnap, snapst := about.info, about.snapst status := "installed" @@ -178,25 +321,13 @@ status = "active" } - appNames := make([]string, 0, len(localSnap.Apps)) - for appName := range localSnap.Apps { - appNames = append(appNames, appName) - } - sort.Strings(appNames) - apps := make([]appJSON, 0, len(localSnap.Apps)) - for _, appName := range appNames { - app := localSnap.Apps[appName] - var installedDesktopFile string - if osutil.FileExists(app.DesktopFile()) { - installedDesktopFile = app.DesktopFile() - } - - apps = append(apps, appJSON{ - Name: app.Name, - Daemon: app.Daemon, - DesktopFile: installedDesktopFile, - }) + snapapps := make([]*snap.AppInfo, 0, len(localSnap.Apps)) + for _, app := range localSnap.Apps { + snapapps = append(snapapps, app) } + sort.Sort(bySnapApp(snapapps)) + + apps := clientAppInfosFromSnapAppInfos(snapapps) // TODO: expose aliases information and state? @@ -229,6 +360,10 @@ result["title"] = localSnap.Title() } + if localSnap.License != "" { + result["license"] = localSnap.License + } + return result } @@ -274,6 +409,10 @@ result["title"] = remoteSnap.Title() } + if remoteSnap.License != "" { + result["license"] = remoteSnap.License + } + if len(screenshots) > 0 { result["screenshots"] = screenshots } diff -Nru snapd-2.27.5/daemon/ucrednet.go snapd-2.28.5/daemon/ucrednet.go --- snapd-2.27.5/daemon/ucrednet.go 2016-12-08 15:14:07.000000000 +0000 +++ snapd-2.28.5/daemon/ucrednet.go 2017-09-13 14:47:18.000000000 +0000 @@ -28,40 +28,56 @@ sys "syscall" ) -var errNoUID = errors.New("no uid found") +var errNoID = errors.New("no pid/uid found") -const ucrednetNobody = uint32((1 << 32) - 1) +const ( + ucrednetNoProcess = uint32(0) + ucrednetNobody = uint32((1 << 32) - 1) +) -func ucrednetGetUID(remoteAddr string) (uint32, error) { - idx := strings.IndexByte(remoteAddr, ';') - if !strings.HasPrefix(remoteAddr, "uid=") || idx < 5 { - return ucrednetNobody, errNoUID +func ucrednetGet(remoteAddr string) (pid uint32, uid uint32, err error) { + pid = ucrednetNoProcess + uid = ucrednetNobody + for _, token := range strings.Split(remoteAddr, ";") { + var v uint64 + if strings.HasPrefix(token, "pid=") { + if v, err = strconv.ParseUint(token[4:], 10, 32); err == nil { + pid = uint32(v) + } else { + break + } + } else if strings.HasPrefix(token, "uid=") { + if v, err = strconv.ParseUint(token[4:], 10, 32); err == nil { + uid = uint32(v) + } else { + break + } + } } - - uid, err := strconv.ParseUint(remoteAddr[4:idx], 10, 32) - if err != nil { - return ucrednetNobody, err + if pid == ucrednetNoProcess || uid == ucrednetNobody { + err = errNoID } - - return uint32(uid), nil + return } type ucrednetAddr struct { net.Addr + pid string uid string } func (wa *ucrednetAddr) String() string { - return fmt.Sprintf("uid=%s;%s", wa.uid, wa.Addr) + return fmt.Sprintf("pid=%s;uid=%s;%s", wa.pid, wa.uid, wa.Addr) } type ucrednetConn struct { net.Conn + pid string uid string } func (wc *ucrednetConn) RemoteAddr() net.Addr { - return &ucrednetAddr{wc.Conn.RemoteAddr(), wc.uid} + return &ucrednetAddr{wc.Conn.RemoteAddr(), wc.pid, wc.uid} } type ucrednetListener struct{ net.Listener } @@ -74,7 +90,7 @@ return nil, err } - uid := "" + var pid, uid string if ucon, ok := con.(*net.UnixConn); ok { f, err := ucon.File() if err != nil { @@ -88,8 +104,9 @@ return nil, err } + pid = strconv.FormatUint(uint64(ucred.Pid), 10) uid = strconv.FormatUint(uint64(ucred.Uid), 10) } - return &ucrednetConn{con, uid}, err + return &ucrednetConn{con, pid, uid}, err } diff -Nru snapd-2.27.5/daemon/ucrednet_test.go snapd-2.28.5/daemon/ucrednet_test.go --- snapd-2.27.5/daemon/ucrednet_test.go 2015-11-30 07:39:34.000000000 +0000 +++ snapd-2.28.5/daemon/ucrednet_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -52,7 +52,7 @@ } func (s *ucrednetSuite) TestAcceptConnRemoteAddrString(c *check.C) { - s.ucred = &sys.Ucred{Uid: 42} + s.ucred = &sys.Ucred{Pid: 100, Uid: 42} d := c.MkDir() sock := filepath.Join(d, "sock") @@ -73,8 +73,9 @@ defer conn.Close() remoteAddr := conn.RemoteAddr().String() - c.Check(remoteAddr, check.Matches, "uid=42;.*") - uid, err := ucrednetGetUID(remoteAddr) + c.Check(remoteAddr, check.Matches, "pid=100;uid=42;.*") + pid, uid, err := ucrednetGet(remoteAddr) + c.Check(pid, check.Equals, uint32(100)) c.Check(uid, check.Equals, uint32(42)) c.Check(err, check.IsNil) } @@ -99,14 +100,15 @@ defer conn.Close() remoteAddr := conn.RemoteAddr().String() - c.Check(remoteAddr, check.Matches, "uid=;.*") - uid, err := ucrednetGetUID(remoteAddr) + c.Check(remoteAddr, check.Matches, "pid=;uid=;.*") + pid, uid, err := ucrednetGet(remoteAddr) + c.Check(pid, check.Equals, ucrednetNoProcess) c.Check(uid, check.Equals, ucrednetNobody) - c.Check(err, check.Equals, errNoUID) + c.Check(err, check.Equals, errNoID) } func (s *ucrednetSuite) TestAcceptErrors(c *check.C) { - s.ucred = &sys.Ucred{Uid: 42} + s.ucred = &sys.Ucred{Pid: 100, Uid: 42} d := c.MkDir() sock := filepath.Join(d, "sock") @@ -142,31 +144,36 @@ } func (s *ucrednetSuite) TestGetNoUid(c *check.C) { - uid, err := ucrednetGetUID("uid=;") - c.Check(err, check.Equals, errNoUID) + 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) { - uid, err := ucrednetGetUID("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) { - uid, err := ucrednetGetUID("hello") - c.Check(err, check.Equals, errNoUID) + 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) { - uid, err := ucrednetGetUID("") - c.Check(err, check.Equals, errNoUID) + 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) { - uid, err := ucrednetGetUID("uid=42;") + pid, uid, err := ucrednetGet("pid=100;uid=42;") c.Check(err, check.IsNil) + c.Check(pid, check.Equals, uint32(100)) c.Check(uid, check.Equals, uint32(42)) } diff -Nru snapd-2.27.5/data/completion/complete.sh snapd-2.28.5/data/completion/complete.sh --- snapd-2.27.5/data/completion/complete.sh 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/data/completion/complete.sh 2017-09-13 14:47:18.000000000 +0000 @@ -36,6 +36,7 @@ # from 'etelpmoc.sh', validates the results and puts the validated results # into the bash completion environment variables # 8. bash displays the results to the user +type -t _complete_from_snap > /dev/null || _complete_from_snap() { { # De-serialize the output of 'snap run --command=complete ...' into the format @@ -100,17 +101,29 @@ } -# _complete_from_snap_maybe calls _complete_from_snap if the command is in -# bin/snap, and otherwise does bash-completion's _completion_loader (which is -# what -D would've done before). -_complete_from_snap_maybe() { - # catch /snap/bin and /var/lib/snapd/snap/bin - if [[ "$(which "$1")" =~ /snap/bin/ && ( -e /var/lib/snapd/snap/core/current/usr/lib/snapd/etelpmoc.sh || -e /snap/core/current/usr/lib/snapd/etelpmoc.sh ) ]]; then - _complete_from_snap "$1" - return $? - fi - # fallback to the old -D - _completion_loader "$1" -} +# this file can be sourced directly as e.g. /usr/lib/snapd/complete.sh, or via +# a symlink from /usr/share/bash-completion/completions/. In the first case we +# want to load the default loader; in the second, the specific one. +# +if [[ "${BASH_SOURCE[0]}" =~ ^/usr/share/bash-completion/completions/ ]]; then + complete -F _complete_from_snap "$1" +else + + # _complete_from_snap_maybe calls _complete_from_snap if the command is in + # bin/snap, and otherwise does bash-completion's _completion_loader (which is + # what -D would've done before). + type -t _complete_from_snap_maybe > /dev/null || + _complete_from_snap_maybe() { + local etel=snap/core/current/usr/lib/snapd/etelpmoc.sh + # catch /snap/bin and /var/lib/snapd/snap/bin + if [[ "$(which "$1")" =~ /snap/bin/ && ( -e "/var/lib/snapd/$etel" || -e "/$etel" ) ]]; then + complete -F _complete_from_snap "$1" + return 124 + fi + # fallback to the old -D + _completion_loader "$1" + } + + complete -D -F _complete_from_snap_maybe +fi -complete -D -F _complete_from_snap_maybe diff -Nru snapd-2.27.5/data/completion/snap snapd-2.28.5/data/completion/snap --- snapd-2.27.5/data/completion/snap 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/data/completion/snap 2017-09-13 14:47:18.000000000 +0000 @@ -14,7 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -_complete() { +_complete_snap() { # TODO: add support for sourcing this function from the core snap. local cur prev words cword _init_completion -n : || return @@ -62,4 +62,4 @@ return 0 } -complete -F _complete snap +complete -F _complete_snap snap diff -Nru snapd-2.27.5/data/dbus/io.snapcraft.Launcher.service.in snapd-2.28.5/data/dbus/io.snapcraft.Launcher.service.in --- snapd-2.27.5/data/dbus/io.snapcraft.Launcher.service.in 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/data/dbus/io.snapcraft.Launcher.service.in 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=io.snapcraft.Launcher +Exec=@bindir@/snap userd diff -Nru snapd-2.27.5/data/dbus/Makefile snapd-2.28.5/data/dbus/Makefile --- snapd-2.27.5/data/dbus/Makefile 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/data/dbus/Makefile 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,33 @@ +# +# 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 . + +BINDIR := /usr/bin +DBUSSERVICESDIR := /usr/share/dbus-1/services + +SERVICES_GENERATED := $(patsubst %.service.in,%.service,$(wildcard *.service.in)) +SERVICES := ${SERVICES_GENERATED} + +%.service: %.service.in + cat $< | sed 's:@bindir@:${BINDIR}:g' | cat > $@ + +all: ${SERVICES} + +install: ${SERVICES} + # NOTE: old (e.g. 14.04) GNU coreutils doesn't -D with -t + install -d -m 0755 ${DESTDIR}/${DBUSSERVICESDIR} + install -m 0644 -t ${DESTDIR}/${DBUSSERVICESDIR} $^ + +clean: + rm -f ${SERVICES_GENERATED} diff -Nru snapd-2.27.5/data/env/Makefile snapd-2.28.5/data/env/Makefile --- snapd-2.27.5/data/env/Makefile 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/data/env/Makefile 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,37 @@ +# +# 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 . + +SNAP_MOUNT_DIR := /snap +ENVD := /etc/profile.d + +%.sh: %.sh.in + sed < $< > $@ \ + s:@SNAP_MOUNT_DIR@:${SNAP_MOUNT_DIR}:g + +GENERATED = snapd.sh + + +all: ${GENERATED} +.PHONY: all + +install: ${GENERATED} + # NOTE: old (e.g. 14.04) GNU coreutils doesn't -D with -t + install -d -m 0755 ${DESTDIR}/${ENVD} + install -m 0644 -t ${DESTDIR}/${ENVD} $^ +.PHONY: install + +clean: + $(RM) ${GENERATED} +.PHONY: clean diff -Nru snapd-2.27.5/data/env/snapd.sh.in snapd-2.28.5/data/env/snapd.sh.in --- snapd-2.27.5/data/env/snapd.sh.in 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/data/env/snapd.sh.in 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,14 @@ +#!/bin/sh --this-shebang-is-just-here-to-inform-shellcheck-- + +# Expand $PATH to include the directory where snappy applications go. +if [ "${PATH#*/snap/bin}" = "${PATH}" ]; then + export PATH=$PATH:@SNAP_MOUNT_DIR@/bin +fi + +# desktop files (used by desktop environments within both X11 and Wayland) are +# looked for in XDG_DATA_DIRS; make sure it includes the relevant directory for +# snappy applications' desktop files. +if [ "${XDG_DATA_DIRS#*/snapd/desktop}" = "${XDG_DATA_DIRS}" ]; then + export XDG_DATA_DIRS="${XDG_DATA_DIRS:-/usr/local/share:/usr/share}:/var/lib/snapd/desktop" +fi + diff -Nru snapd-2.27.5/data/Makefile snapd-2.28.5/data/Makefile --- snapd-2.27.5/data/Makefile 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/data/Makefile 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,4 @@ +all install clean: + $(MAKE) -C systemd $@ + $(MAKE) -C dbus $@ + $(MAKE) -C env $@ diff -Nru snapd-2.27.5/data/polkit/io.snapcraft.snapd.policy snapd-2.28.5/data/polkit/io.snapcraft.snapd.policy --- snapd-2.27.5/data/polkit/io.snapcraft.snapd.policy 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/data/polkit/io.snapcraft.snapd.policy 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,29 @@ + + + + + Snapcraft + http://snapcraft.io + + + Authenticate on snap daemon + Authorization is required to authenticate on the snap daemon + + auth_admin + auth_admin + auth_admin_keep + + + + + Install, update, or remove packages + Authentication is required to install, update, or remove packages + + auth_admin + auth_admin + auth_admin_keep + + + diff -Nru snapd-2.27.5/data/systemd/Makefile snapd-2.28.5/data/systemd/Makefile --- snapd-2.27.5/data/systemd/Makefile 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/data/systemd/Makefile 2017-09-13 14:47:18.000000000 +0000 @@ -33,8 +33,10 @@ all: ${SYSTEMD_UNITS} install: $(SYSTEMD_UNITS) - install -D -m 0644 -t ${DESTDIR}/${SYSTEMDSYSTEMUNITDIR} $^ - install -D -m 0755 -t ${DESTDIR}/${LIBEXECDIR}/snapd snapd.core-fixup.sh + # 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 clean: rm -f ${SYSTEMD_UNITS_GENERATED} diff -Nru snapd-2.27.5/data/systemd/snapd.autoimport.service.in snapd-2.28.5/data/systemd/snapd.autoimport.service.in --- snapd-2.27.5/data/systemd/snapd.autoimport.service.in 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/data/systemd/snapd.autoimport.service.in 2017-09-13 14:47:18.000000000 +0000 @@ -1,6 +1,8 @@ [Unit] Description=Auto import assertions from block devices After=snapd.service snapd.socket +# don't run on classic +ConditionKernelCommandLine=snap_core [Service] Type=oneshot diff -Nru snapd-2.27.5/data/systemd/snapd.core-fixup.service.in snapd-2.28.5/data/systemd/snapd.core-fixup.service.in --- snapd-2.27.5/data/systemd/snapd.core-fixup.service.in 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/data/systemd/snapd.core-fixup.service.in 2017-09-13 14:47:18.000000000 +0000 @@ -1,7 +1,8 @@ [Unit] Description=Automatically repair incorrect owner/permissions on core devices Before=snapd.service -ConditionPathExists=/writable/system-data +# don't run on classic +ConditionKernelCommandLine=snap_core ConditionPathExists=!/var/lib/snapd/device/ownership-change.after Documentation=man:snap(1) diff -Nru snapd-2.27.5/data/systemd/snapd.snap-repair.service.in snapd-2.28.5/data/systemd/snapd.snap-repair.service.in --- snapd-2.27.5/data/systemd/snapd.snap-repair.service.in 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/data/systemd/snapd.snap-repair.service.in 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,10 @@ +[Unit] +Description=Automatically fetch and run repair assertions +Documentation=man:snap(1) +# don't run on classic +ConditionKernelCommandLine=snap_core + +[Service] +Type=oneshot +ExecStart=@libexecdir@/snapd/snap-repair run +Environment=SNAP_REAPIR_FROM_TIMER=1 diff -Nru snapd-2.27.5/data/systemd/snapd.snap-repair.timer snapd-2.28.5/data/systemd/snapd.snap-repair.timer --- snapd-2.27.5/data/systemd/snapd.snap-repair.timer 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/data/systemd/snapd.snap-repair.timer 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,14 @@ +[Unit] +Description=Timer to automatically fetch and run repair assertions +# don't run on classic +ConditionKernelCommandLine=snap_core + +[Timer] +OnCalendar=*-*-* 5,11,17,23:00 +RandomizedDelaySec=2h +AccuracySec=10min +Persistent=true +OnStartupSec=15m + +[Install] +WantedBy=timers.target diff -Nru snapd-2.27.5/data/systemd/snap-repair.service.in snapd-2.28.5/data/systemd/snap-repair.service.in --- snapd-2.27.5/data/systemd/snap-repair.service.in 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/data/systemd/snap-repair.service.in 1970-01-01 00:00:00.000000000 +0000 @@ -1,8 +0,0 @@ -[Unit] -Description=Automatically fetch and run repair assertions -Documentation=man:snap(1) - -[Service] -Type=oneshot -ExecStart=@libexecdir@/snapd/snap-repair run -Environment=SNAP_REAPIR_FROM_TIMER=1 diff -Nru snapd-2.27.5/data/systemd/snap-repair.timer snapd-2.28.5/data/systemd/snap-repair.timer --- snapd-2.27.5/data/systemd/snap-repair.timer 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/data/systemd/snap-repair.timer 1970-01-01 00:00:00.000000000 +0000 @@ -1,12 +0,0 @@ -[Unit] -Description=Timer to automatically fetch and run repair assertions - -[Timer] -OnCalendar=*-*-* 5,11,17,23:00 -RandomizedDelaySec=2h -AccuracySec=10min -Persistent=true -OnStartupSec=15m - -[Install] -WantedBy=timers.target diff -Nru snapd-2.27.5/debian/changelog snapd-2.28.5/debian/changelog --- snapd-2.27.5/debian/changelog 2017-08-30 05:32:20.000000000 +0000 +++ snapd-2.28.5/debian/changelog 2017-10-13 21:25:46.000000000 +0000 @@ -1,3 +1,331 @@ +snapd (2.28.5) xenial; urgency=medium + + * New upstream release, LP: #1714984 + - snap-confine: cleanup broken nvidia udev tags + - cmd/snap-confine: update valid security tag regexp + - overlord/ifacestate: refresh udev backend on startup + - dbus: ensure io.snapcraft.Launcher.service is created on re- + exec + - snap-confine: add support for handling /dev/nvidia-modeset + - interfaces/network-control: remove incorrect rules for tun + + -- Michael Vogt Fri, 13 Oct 2017 23:25:46 +0200 + +snapd (2.28.4) xenial; urgency=medium + + * New upstream release, LP: #1714984 + - interfaces/opengl: don't udev tag nvidia devices and use snap- + confine instead + - debian: fix replaces/breaks for snap-xdg-open (thanks to apw!) + + -- Michael Vogt Wed, 11 Oct 2017 19:40:57 +0200 + +snapd (2.28.3) xenial; urgency=medium + + * New upstream release, LP: #1714984 + - interfaces/lxd: lxd slot implementation can also be an app + snap + + -- Michael Vogt Wed, 11 Oct 2017 08:20:26 +0200 + +snapd (2.28.2) xenial; urgency=medium + + * New upstream release, LP: #1714984 + - interfaces: fix udev rules for tun + - release,cmd,dirs: Redo the distro checks to take into account + distribution families + + -- Michael Vogt Tue, 10 Oct 2017 18:39:58 +0200 + +snapd (2.28.1) xenial; urgency=medium + + * New upstream release, LP: #1714984 + - snap-confine: update apparmor rules for fedora based basesnaps + - snapstate: rename refresh hook to post-refresh for consistency + + -- Michael Vogt Wed, 27 Sep 2017 17:59:49 -0400 + +snapd (2.28) xenial; urgency=medium + + * New upstream release, LP: #1714984 + - hooks: rename refresh to after-refresh + - snap-confine: bind mount /usr/lib/snapd relative to snap-confine + - cmd,dirs: treat "liri" the same way as "arch" + - snap-confine: fix base snaps on core + - hooks: substitute env vars when executing hooks + - interfaces: updates for default, browser-support, desktop, opengl, + upower and stub-resolv.conf + - cmd,dirs: treat manjaro the same as arch + - systemd: do not run auto-import and repair services on classic + - packaging/fedora: Ensure vendor/ is empty for builds and fix spec + to build current master + - many: fix TestSetConfNumber missing an Unlock and other fragility + improvements + - osutil: adjust StreamCommand tests for golang 1.9 + - daemon: allow polkit authorisation to install/remove snaps + - tests: make TestCmdWatch more robust + - debian: improve package description + - interfaces: add netlink kobject uevent to hardware observe + - debian: update trusted account-keys check on 14.04 packaging + - interfaces/network-{control,observe}: allow receiving + kobject_uevent() messages + - tests: fix lxd test for external backend + - snap-confine,snap-update-ns: add -no-pie to fix FTBFS on + go1.7,ppc64 + - corecfg: mock "systemctl" in all corecfg tests + - tests: fix unit tests on Ubuntu 14.04 + - debian: add missing flags when building static snap-exec + - many: end-to-end support for the bare base snap + - overlord/snapstate: SetRootDir from SetUpTest, not in just some + tests + - store: have an ad-hoc method on cfg to get its list of uris for + tests + - daemon: let client decide whether to allow interactive auth via + polkit + - client,daemon,snap,store: add license field + - overlord/snapstate: rename HasCurrent to IsInstalled, remove + superfluous/misleading check from All + - cmd/snap: SetRootDir from SetUpTest, not in just some individual + tests. + - systemd: rename snap-repair.{service,timer} to snapd.snap- + repair.{service,timer} + - snap-seccomp: remove use of x/net/bpf from tests + - httputil: more naive per go version way to recreate a default + transport for tls reconfig + - cmd/snap-seccomp/main_test.go: add one more syscall for arm64 + - interfaces/opengl: use == to compare, not = + - cmd/snap-seccomp/main_test.go: add syscalls for armhf and arm64 + - cmd/snap-repair: track and use a lower bound for the time for + TLS checks + - interfaces: expose bluez interface on classic OS + - snap-seccomp: add in-kernel bpf tests + - overlord: always try to get a serial, lazily on classic + - tests: add nmcli regression test + - tests: deal with __PNR_chown on aarch64 to fix FTBFS on arm64 + - tests: add autopilot-introspection interface test + - vendor: fix artifact from manually editing vendor/vendor.json + - tests: rename complexion to test-snapd-complexion + - interfaces: add desktop and desktop-legacy + interfaces/desktop: add new 'desktop' interface for modern DEs + interfaces/builtin/desktop_test.go: use modern testing techniques + interfaces/wayland: allow read on /etc/drirc for Plasma desktop + interfaces/desktop-legacy: add new 'legacy' interface (currently + for a11y and input) + - tests: fix race in snap userd test + - devices/iio: add read/write for missing sysfs entries + - spread: don't set HTTPS?_PROXY for linode + - cmd/snap-repair: check signatures of repairs from Next + - env: set XDG_DATA_DIRS for wayland et.al. + - interfaces/{default,account-control}: Use username/group instead + of uid/gid + - interfaces/builtin: use udev tagging more broadly + - tests: add basic lxd test + - wrappers: ensure bash completion snaps install on core + - vendor: use old golang.org/x/crypto/ssh/terminal to build on + powerpc again + - docs: add PULL_REQUEST_TEMPLATE.md + - interfaces: fix network-manager plug + - hooks: do not error out when hook is optional and no hook handler + is registered + - cmd/snap: add userd command to replace snapd-xdg-open + - tests: new regex used to validate the core version on extra snaps + ass... + - snap: add new `snap switch` command + - tests: wait more and more debug info about fakestore start issues + - apparmor,release: add better apparmor detection/mocking code + - interfaces/i2c: adjust sysfs rule for alternate paths + - interfaces/apparmor: add missing call to dirs.SetRootDir + - cmd: "make hack" now also installs snap-update-ns + - tests: copy files with less verbosity + - cmd/snap-confine: allow using additional libraries required by + openSUSE + - packaging/fedora: Merge changes from Fedora Dist-Git + - snapstate: improve the error message when classic confinement is + not supported + - tests: add test to ensure amd64 can run i386 syscall binaries + - tests: adding extra info for fakestore when fails to start + - tests: install most important snaps + - cmd/snap-repair: more test coverage of filtering + - squashfs: remove runCommand/runCommandWithOutput as we do not need + it + - cmd/snap-repair: ignore superseded revisions, filter on arch and + models + - hooks: support for refresh hook + - Partial revert "overlord/devicestate, store: update device auth + endpoints URLs" + - cmd/snap-confine: allow reading /proc/filesystems + - cmd/snap-confine: genearlize apparmor profile for various lib + layout + - corecfg: fix proxy.* writing and add integration test + - corecfg: deal with system.power-key-action="" correctly + - vendor: update vendor.json after (presumed) manual edits + - cmd/snap: in `snap info`, don't print a newline between tracks + - daemon: add polkit support to /v2/login + - snapd,snapctl: decode json using Number + - client: fix go vet 1.7 errors + - tests: make 17.04 shellcheck clean + - tests: remove TestInterfacesHelp as it breaks when go-flags + changes + - snapstate: undo a daemon restart on classic if needed + - cmd/snap-repair: recover brand/model from + /var/lib/snapd/seed/assertions checking signatures and brand + account + - spread: opt into unsafe IO during spread tests + - snap-repair: update snap-repair/runner_test.go for API change in + makeMockServer + - cmd/snap-repair: skeleton code around actually running a repair + - tests: wait until the port is listening after start the fake store + - corecfg: fix typo in tests + - cmd/snap-repair: test that redirects works during fetching + - osutil: honor SNAPD_UNSAFE_IO for testing + - vendor: explode and make more precise our golang.go/x/crypto deps, + use same version as Debian unstable + - many: sanitize NewStoreStack signature, have shared default store + test private keys + - systemd: disable `Nice=-5` to fix error when running inside lxd + - spread.yaml: update delta ref to 2.27 + - cmd/snap-repair: use E-Tags when refetching a repair to retry + - interfaces/many: updates based on chromium and mrrescue denials + - cmd/snap-repair: implement most logic to get the next repair to + run/retry in a brand sequence + - asserts/assertstest: copy headers in SigningDB.Sign + - interfaces: convert uhid to common interface and test cases + improvement for time_control and opengl + - many tests: move all panicing fake store methods to a common place + - asserts: add store assertion type + - interfaces: don't crash if content slot has no attributes + - debian: do not build with -buildmode=pie on i386 + - wrappers: symlink completion snippets when symlinking binaries + - tests: adding more debug information for the interfaces-cups- + control … + - apparmor: pass --quiet to parser on load unless SNAPD_DEBUG is set + - many: allow and support serials signed by the 'generic' authority + instead of the brand + - corecfg: add proxy configuration via `snap set core + proxy.{http,https,ftp}=...` + - interfaces: a bunch of interfaces test improvement + - tests: enable regression and completion suites for opensuse + - tests: installing snapd for nested test suite + - interfaces: convert lxd_support to common iface + - interfaces: add missing test for camera interface. + - snap: add support for parsing snap layout section + - cmd/snap-repair: like for downloads we cannot have a timeout (at + least for now), less aggressive retry strategies + - overlord: rely on more conservative ensure interval + - overlord,store: no piles of return args for methods gathering + device session request params + - overlord,store: send model assertion when setting up device + sessions + - interfaces/misc: updates for unity7/x11, browser- + support, network-control and mount-observe + interfaces/unity7,x11: update for NETLINK_KOBJECT_UEVENT + interfaces/browser-support: update sysfs reads for + newer browser versions, interfaces/network-control: rw for + ieee80211 advanced wireless interfaces/mount-observe: allow read + on sysfs entries for block devices + - tests: use dnf --refresh install to avert stale cache + - osutil: ensure TestLockUnlockWorks uses supported flock + - interfaces: convert lxd to common iface + - tests: restart snapd to ensure re-exec settings are applied + - tests: fix interfaces-cups-control test + - interfaces: improve and tweak bunch of interfaces test cases. + - tests: adding extra worker for fedora + - asserts,overlord/devicestate: support predefined assertions that + don't establish foundational trust + - interfaces: convert two hardware_random interfaces to common iface + - interfaces: convert io_ports_control to common iface + - tests: fix for upgrade test on fedora + - daemon, client, cmd/snap: implement snap start/stop/restart + - cmd/snap-confine: set _FILE_OFFSET_BITS to 64 + - interfaces: covert framebuffer to commonInterface + - interfaces: convert joystick to common iface + - interfaces/builtin: add the spi interface + - wrappers, overlord/snapstate/backend: make link-snap clean up on + failure. + - interfaces/wayland: add wayland interface + - interfaces: convert kvm to common iface + - tests: extend upower-observe test to cover snaps providing slots + - tests: enable main suite for opensuse + - interfaces: convert physical_memory_observe to common iface + - interfaces: add missing test for optical_drive interface. + - interfaces: convert physical_memory_control to common iface + - interfaces: convert ppp to common iface + - interfaces: convert time-control to common iface + - tests: fix failover test + - interfaces/builtin: rework for avahi interface + - interfaces: convert broadcom-asic-control to common iface + - snap/snapenv: document the use of CoreSnapMountDir for SNAP + - packaging/arch: drop patches merged into master + - cmd: fix mustUnsetenv docstring (thanks to Chipaca) + - release: remove default from VERSION_ID + - tests: enable regression, upgrade and completion test suites for + fedora + - tests: restore interfaces-account-control properly + - overlord/devicestate, store: update device auth endpoints URLs + - tests: fix install-hook test failure + - tests: download core and ubuntu-core at most once + - interfaces: add common support for udev + - overlord/devicestate: fix, don't assume that the serial is backed + by a 1-key chain + - cmd/snap-confine: don't share /etc/nsswitch from host + - store: do not resume a download when we already have the whole + thing + - many: implement "snap logs" + - store: don't call useDeltas() twice in quick succession + - interfaces/builtin: add kvm interface + - snap/snapenv: always expect /snap for $SNAP + - cmd: mark arch as non-reexecing distro + - cmd: fix tests that assume /snap mount + - gitignore: ignore more build artefacts + - packaging: add current arch packaging + - interfaces/unity7: allow receiving media key events in (at least) + gnome-shell + - interfaces/many, cmd/snap-confine: miscellaneous policy updates + - interfaces/builtin: implement broadcom-asic-control interface + - interfaces/builtin: reduce duplication and remove cruft in + Sanitize{Plug,Slot} + - tests: apply underscore convention for SNAPMOUNTDIR variable + - interfaces/greengrass-support: adjust accesses now that have + working snap + - daemon, client, cmd/snap: implement "snap services" + - tests: fix refresh tests not stopping fake store for fedora + - many: add the interface command + - overlord/snapstate/backend: some copydata improvements + - many: support querying and completing assertion type names + - interfaces/builtin: discard empty Validate{Plug,Slot} + - cmd/snap-repair: start of Runner, implement first pass of Peek + and Fetch + - tests: enable main suite on fedora + - snap: do not always quote the snap info summary + - vendor: update go-flags to address crash in "snap debug" + - interfaces: opengl support pci device and vendor + - many: start implenting "base" snap type on the snapd side + - arch,release: map armv6 correctly + - many: expose service status in 'snap info' + - tests: add browser-support interface test + - tests: disable snapd-notify for the external backend + - interfaces: Add /run/uuid/request to openvswitch + - interfaces: add password-manager-service implicit classic + interface + - cmd: rework reexec detection + - cmd: fix re-exec bug when starting from snapd 2.21 + - tests: dependency packages installed during prepare-project + - tests: remove unneeded check for re-exec in InternalToolPath() + - cmd,tests: fix classic confinement confusing re-execution code + - store: configurable base api + - tests: fix how package lists are updated for opensuse and fedora + + -- Michael Vogt Mon, 25 Sep 2017 12:07:34 -0400 + +snapd (2.27.6) xenial; urgency=medium + + * New upstream release, LP: #1703798: + - interfaces: add udev netlink support to hardware-observe + - interfaces/network-{control,observe}: allow receiving + kobject_uevent() messages + + -- Michael Vogt Thu, 07 Sep 2017 10:22:18 +0200 + snapd (2.27.5) xenial; urgency=medium * New upstream release, LP: #1703798: @@ -322,6 +650,29 @@ -- Michael Vogt Thu, 10 Aug 2017 12:43:16 +0200 +snapd (2.26.14) xenial; urgency=medium + + * New upstream release, LP: #1690083 + - cmd: fix incorrect re-exec when starting from snapd 2.21 + + -- Michael Vogt Thu, 20 Jul 2017 13:52:05 +0200 + +snapd (2.26.13) xenial; urgency=medium + + * New upstream release, LP: #1690083 + - cmd,tests: fix classic confinement confusing re-execution code + - cmd: fix incorrect check check for re-exec in InternalToolPath() + - snap-seccomp: add secondary arch for unrestricted snaps as well + + -- Michael Vogt Tue, 18 Jul 2017 20:34:33 +0200 + +snapd (2.26.10) xenial; urgency=medium + + * New upstream release, LP: #1690083 + - Fix snap-seccomp tests in artful/trusty on i386/s390x/aarch64 + + -- Michael Vogt Mon, 17 Jul 2017 11:58:22 +0200 + snapd (2.26.9) xenial; urgency=medium * New upstream release, LP: #1690083 diff -Nru snapd-2.27.5/debian/control snapd-2.28.5/debian/control --- snapd-2.27.5/debian/control 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/debian/control 2017-10-11 17:40:25.000000000 +0000 @@ -7,6 +7,7 @@ autotools-dev, bash-completion, debhelper (>= 9), + dbus, dh-apparmor, dh-autoreconf, dh-golang (>=1.7), @@ -64,11 +65,11 @@ systemd, ${misc:Depends}, ${shlibs:Depends} -Replaces: ubuntu-snappy (<< 1.9), ubuntu-snappy-cli (<< 1.9), snap-confine (<< 2.23), ubuntu-core-launcher (<< 2.22) -Breaks: ubuntu-snappy (<< 1.9), ubuntu-snappy-cli (<< 1.9), snap-confine (<< 2.23), ubuntu-core-launcher (<< 2.22) +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) Conflicts: snap (<< 2013-11-29-1ubuntu1) Built-Using: ${Built-Using} ${misc:Built-Using} -Description: Tool to interact with Ubuntu Core Snappy. +Description: Daemon and tooling that enable snap packages Install, configure, refresh and remove snap packages. Snaps are 'universal' packages that work across many different Linux systems, enabling secure distribution of the latest apps and utilities for @@ -112,3 +113,11 @@ Pre-Depends: dpkg (>= 1.15.7.2) Description: Transitional package for snapd This is a transitional dummy package. It can safely be removed. + +Package: snapd-xdg-open +Architecture: any +Depends: snapd (= ${binary:Version}), ${misc:Depends} +Section: oldlibs +Pre-Depends: dpkg (>= 1.15.7.2) +Description: Transitional package for snapd-xdg-open + This is a transitional dummy package. It can safely be removed. diff -Nru snapd-2.27.5/debian/rules snapd-2.28.5/debian/rules --- snapd-2.27.5/debian/rules 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/debian/rules 2017-09-13 14:47:18.000000000 +0000 @@ -1,5 +1,13 @@ #!/usr/bin/make -f # -*- makefile -*- +# +# These rules should work for any debian-ish distro that uses systemd +# as init. That does _not_ include Ubuntu 14.04 ("trusty"); look for +# its own special rule file. +# +# Please keep the diff between that and this relatively small, even if +# it means having suboptimal code; these need to be kept in sync by +# sentient bags of meat. #export DH_VERBOSE=1 export DH_OPTIONS @@ -24,8 +32,10 @@ # Disable -buildmode=pie mode on i386 as can panics in spectacular # ways (LP: #1711052). # See also https://forum.snapcraft.io/t/artful-i386-panics/ +# Note while the panic is only on artful, that's because artful +# detects it; the issue potentially there on older things. BUILDFLAGS:=-pkgdir=$(CURDIR)/_build/std -ifneq ($(shell dpkg-architecture -qDEB_TARGET_ARCH),i386) +ifneq ($(shell dpkg-architecture -qDEB_HOST_ARCH),i386) BUILDFLAGS+= -buildmode=pie endif @@ -53,7 +63,7 @@ # because derivatives may have different kernels that don't support all the # required confinement features and we don't to mislead anyone about the # security of the system. Discuss a proper approach to this for downstreams -# if and when they approach us +# if and when they approach us. ifeq ($(shell dpkg-vendor --query Vendor),Ubuntu) # On Ubuntu 16.04 we need to produce a build that can be used on wide # variety of systems. As such we prefer static linking over dynamic linking @@ -103,9 +113,9 @@ ) endif dh_clean + $(MAKE) -C data clean # XXX: hacky $(MAKE) -C cmd distclean || true - $(MAKE) -C data/systemd clean override_dh_auto_build: # usually done via `go generate` but that is not supported on powerpc @@ -114,21 +124,29 @@ mkdir -p _build/src/$(DH_GOPKG)/cmd/snap/test-data cp -a cmd/snap/test-data/*.gpg _build/src/$(DH_GOPKG)/cmd/snap/test-data/ dh_auto_build -- $(BUILDFLAGS) $(TAGS) $(GCCGOFLAGS) + # Generate static snap-exec - it somehow includes CGO so we must + # force a static build here. We need a static snap-exec inside + # the core snap because not all bases will have a libc + (cd _build/bin && GOPATH=$$(pwd)/.. CGO_ENABLED=0 go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snap-exec) + # ensure we generated a static build + $(shell if ldd _build/bin/snap-exec; then false "need static build"; fi) + # Build C bits, sadly manually cd cmd && ( autoreconf -i -f ) cd cmd && ( ./configure --prefix=/usr --libexecdir=/usr/lib/snapd $(VENDOR_ARGS)) $(MAKE) -C cmd all - # Generate the real systemd units out of the available templates - $(MAKE) -C data/systemd all + # Generate the real systemd/dbus/env config files + $(MAKE) -C data all override_dh_auto_test: dh_auto_test -- $(GCCGOFLAGS) # a tested default (production) build should have no test keys ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))) - # check that only the main trusted account-key is included - [ $$(strings _build/bin/snapd|grep -c -E "public-key-sha3-384: [a-zA-Z0-9_-]{64}") -eq 1 ] + # check that only the main trusted account-keys are included + [ $$(strings _build/bin/snapd|grep -c -E "public-key-sha3-384: [a-zA-Z0-9_-]{64}") -eq 2 ] strings _build/bin/snapd|grep -c "^public-key-sha3-384: -CvQKAwRQ5h3Ffn10FILJoEZUXOv6km9FwA80-Rcj-f-6jadQ89VRswHNiEB9Lxk$$" + strings _build/bin/snapd|grep -c "^public-key-sha3-384: d-JcZF9nD9eBw7bwMnH61x-bklnQOhQud1Is6o_cn2wTj8EYDi9musrIT9z2MdAa$$" endif ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))) # run the snap-confine tests @@ -152,12 +170,12 @@ # we want the repair timer enabled by default dh_systemd_enable \ -psnapd \ - data/systemd/snap-repair.timer + data/systemd/snapd.snap-repair.timer # but the repair service disabled dh_systemd_enable \ --no-enable \ -psnapd \ - data/systemd/snap-repair.service + data/systemd/snapd.snap-repair.service # enable snapd dh_systemd_enable \ -psnapd \ @@ -210,10 +228,14 @@ cp -R share/locale debian/snapd/usr/share; \ fi - # install snapd's systemd units, done here instead of - # debian/snapd.install because the ubuntu/14.04 release - # branch adds/changes bits here - $(MAKE) -C data/systemd install DESTDIR=$(CURDIR)/debian/snapd/ SYSTEMDSYSTEMUNITDIR=$(SYSTEMD_UNITS_DESTDIR) + # install snapd's systemd units / upstart jobs, done + # here instead of debian/snapd.install because the + # ubuntu/14.04 release branch adds/changes bits here + $(MAKE) -C data install DESTDIR=$(CURDIR)/debian/snapd/ \ + SYSTEMDSYSTEMUNITDIR=$(SYSTEMD_UNITS_DESTDIR) + # we called this apps-bin-path.sh instead of snapd.sh, and + # it's a conf file so we're stuck with it + mv debian/snapd/etc/profile.d/snapd.sh debian/snapd/etc/profile.d/apps-bin-path.sh $(MAKE) -C cmd install DESTDIR=$(CURDIR)/debian/tmp @@ -227,6 +249,7 @@ install -d $(CURDIR)/debian/tmp/etc/apparmor.d touch $(CURDIR)/debian/tmp/etc/apparmor.d/usr.lib.snapd.snap-confine.real endif + dh_install override_dh_auto_install: snap.8 diff -Nru snapd-2.27.5/debian/snapd.install snapd-2.28.5/debian/snapd.install --- snapd-2.27.5/debian/snapd.install 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/debian/snapd.install 2017-09-13 14:47:18.000000000 +0000 @@ -7,10 +7,6 @@ usr/bin/snapd /usr/lib/snapd/ usr/bin/snap-seccomp /usr/lib/snapd/ -# etc/profile.d contains the PATH extension for snap packages -etc/profile.d -# etc/X11/Xsession.d will add to XDG_DATA_DIRS so that we have .desktop support -etc/X11 # bash completion data/completion/snap /usr/share/bash-completion/completions data/completion/complete.sh /usr/lib/snapd/ @@ -19,6 +15,8 @@ data/udev/rules.d/66-snapd-autoimport.rules /lib/udev/rules.d # snap/snapd version information data/info /usr/lib/snapd/ +# polkit actions +data/polkit/io.snapcraft.snapd.policy /usr/share/polkit-1/actions/ # snap-confine stuff etc/apparmor.d/usr.lib.snapd.snap-confine.real diff -Nru snapd-2.27.5/debian/tests/control snapd-2.28.5/debian/tests/control --- snapd-2.27.5/debian/tests/control 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/debian/tests/control 2017-09-13 14:47:18.000000000 +0000 @@ -1,5 +1,5 @@ Tests: integrationtests -Restrictions: allow-stderr, isolation-container, rw-build-tree, needs-root, breaks-testbed +Restrictions: allow-stderr, rw-build-tree, needs-root, breaks-testbed, isolation-machine Depends: @builddeps@, bzr, ca-certificates, diff -Nru snapd-2.27.5/dirs/dirs.go snapd-2.28.5/dirs/dirs.go --- snapd-2.27.5/dirs/dirs.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/dirs/dirs.go 2017-10-10 16:16:09.000000000 +0000 @@ -64,6 +64,11 @@ SnapStateFile string + SnapRepairDir string + SnapRepairStateFile string + SnapRepairRunDir string + SnapRepairAssertsDir string + SnapBinariesDir string SnapServicesDir string SnapDesktopFilesDir string @@ -80,6 +85,8 @@ XdgRuntimeDirGlob string CompletionHelper string + CompletersDir string + CompleteSh string ) const ( @@ -121,7 +128,24 @@ // SupportsClassicConfinement returns true if the current directory layout supports classic confinement. func SupportsClassicConfinement() bool { - return SnapMountDir == defaultSnapMountDir + if SnapMountDir == defaultSnapMountDir { + return true + } + + // distros with a non-default /snap location may still be good + // if there is a symlink in place that links from the + // defaultSnapMountDir (/snap) to the distro specific mount dir + smd := filepath.Join(GlobalRootDir, defaultSnapMountDir) + fi, err := os.Lstat(smd) + if err == nil && fi.Mode()&os.ModeSymlink != 0 { + if target, err := filepath.EvalSymlinks(smd); err == nil { + if target == SnapMountDir { + return true + } + } + } + + return false } // SetRootDir allows settings a new global root directory, this is useful @@ -132,10 +156,9 @@ } GlobalRootDir = rootdir - switch release.ReleaseInfo.ID { - case "fedora", "centos", "rhel", "arch": + if release.DistroLike("fedora", "arch") { SnapMountDir = filepath.Join(rootdir, "/var/lib/snapd/snap") - default: + } else { SnapMountDir = filepath.Join(rootdir, defaultSnapMountDir) } @@ -166,6 +189,11 @@ SnapSeedDir = filepath.Join(rootdir, snappyDir, "seed") SnapDeviceDir = filepath.Join(rootdir, snappyDir, "device") + SnapRepairDir = filepath.Join(rootdir, snappyDir, "repair") + SnapRepairStateFile = filepath.Join(SnapRepairDir, "repair.json") + SnapRepairRunDir = filepath.Join(SnapRepairDir, "run") + SnapRepairAssertsDir = filepath.Join(SnapRepairDir, "assertions") + SnapBinariesDir = filepath.Join(SnapMountDir, "bin") SnapServicesDir = filepath.Join(rootdir, "/etc/systemd/system") SnapBusPolicyDir = filepath.Join(rootdir, "/etc/dbus-1/system.d") @@ -193,4 +221,6 @@ XdgRuntimeDirGlob = filepath.Join(rootdir, XdgRuntimeDirBase, "*/") CompletionHelper = filepath.Join(CoreLibExecDir, "etelpmoc.sh") + CompletersDir = filepath.Join(rootdir, "/usr/share/bash-completion/completions/") + CompleteSh = filepath.Join(SnapMountDir, "core/current/usr/lib/snapd/complete.sh") } diff -Nru snapd-2.27.5/dirs/dirs_test.go snapd-2.28.5/dirs/dirs_test.go --- snapd-2.27.5/dirs/dirs_test.go 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/dirs/dirs_test.go 2017-10-10 16:16:09.000000000 +0000 @@ -20,6 +20,8 @@ package dirs_test import ( + "os" + "path/filepath" "testing" . "gopkg.in/check.v1" @@ -54,26 +56,44 @@ reset := release.MockReleaseInfo(&release.OS{ID: "ubuntu"}) defer reset() dirs.SetRootDir("/") - c.Assert(dirs.SupportsClassicConfinement(), Equals, true) + c.Check(dirs.SupportsClassicConfinement(), Equals, true) + dirs.SetRootDir("/alt") - c.Assert(dirs.SupportsClassicConfinement(), Equals, false) + defer dirs.SetRootDir("/") + c.Check(dirs.SupportsClassicConfinement(), Equals, false) +} + +func (s *DirsTestSuite) TestClassicConfinementSymlinkWorkaround(c *C) { + restore := release.MockReleaseInfo(&release.OS{ID: "fedora"}) + defer restore() + + altRoot := c.MkDir() + dirs.SetRootDir(altRoot) + defer dirs.SetRootDir("/") + c.Check(dirs.SupportsClassicConfinement(), Equals, false) + d := filepath.Join(altRoot, "/var/lib/snapd/snap") + os.MkdirAll(d, 0755) + os.Symlink(d, filepath.Join(altRoot, "snap")) + c.Check(dirs.SupportsClassicConfinement(), Equals, true) } func (s *DirsTestSuite) TestClassicConfinementSupportOnSpecificDistributions(c *C) { - for _, current := range []struct { - Name string + for _, t := range []struct { + ID string + IDLike []string Expected bool }{ - {"fedora", false}, - {"rhel", false}, - {"centos", false}, - {"ubuntu", true}, - {"debian", true}, - {"suse", true}, - {"yocto", true}} { - reset := release.MockReleaseInfo(&release.OS{ID: current.Name}) + {"fedora", nil, false}, + {"rhel", []string{"fedora"}, false}, + {"centos", []string{"fedora"}, false}, + {"ubuntu", []string{"debian"}, true}, + {"debian", nil, true}, + {"suse", nil, true}, + {"yocto", nil, true}, + } { + reset := release.MockReleaseInfo(&release.OS{ID: t.ID, IDLike: t.IDLike}) defer reset() dirs.SetRootDir("/") - c.Assert(dirs.SupportsClassicConfinement(), Equals, current.Expected) + c.Check(dirs.SupportsClassicConfinement(), Equals, t.Expected, Commentf("unexpected result for %v", t.ID)) } } diff -Nru snapd-2.27.5/etc/profile.d/apps-bin-path.sh snapd-2.28.5/etc/profile.d/apps-bin-path.sh --- snapd-2.27.5/etc/profile.d/apps-bin-path.sh 2016-04-28 08:04:19.000000000 +0000 +++ snapd-2.28.5/etc/profile.d/apps-bin-path.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -# Expand the $PATH to include /snap/bin which is what snappy applications -# use -PATH=$PATH:/snap/bin diff -Nru snapd-2.27.5/etc/X11/Xsession.d/65snappy snapd-2.28.5/etc/X11/Xsession.d/65snappy --- snapd-2.27.5/etc/X11/Xsession.d/65snappy 2016-11-24 09:36:04.000000000 +0000 +++ snapd-2.28.5/etc/X11/Xsession.d/65snappy 1970-01-01 00:00:00.000000000 +0000 @@ -1,12 +0,0 @@ -# This file is sourced by Xsession(5), not executed. -# Add the additional snappy desktop path - -if [ -z "$XDG_DATA_DIRS" ]; then - # 60x11-common_xdg_path does not always set XDG_DATA_DIRS - # so we ensure we have sensible defaults here (LP: #1575014) - # as a workaround - XDG_DATA_DIRS=/usr/local/share/:/usr/share/:/var/lib/snapd/desktop -else - XDG_DATA_DIRS="$XDG_DATA_DIRS":/var/lib/snapd/desktop -fi -export XDG_DATA_DIRS diff -Nru snapd-2.27.5/httputil/logger.go snapd-2.28.5/httputil/logger.go --- snapd-2.27.5/httputil/logger.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/httputil/logger.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,6 +20,7 @@ package httputil import ( + "crypto/tls" "errors" "net/http" "net/http/httputil" @@ -89,6 +90,7 @@ type ClientOpts struct { Timeout time.Duration + TLSConfig *tls.Config MayLogBody bool } @@ -99,9 +101,12 @@ opts = &ClientOpts{} } + transport := newDefaultTransport() + transport.TLSClientConfig = opts.TLSConfig + return &http.Client{ Transport: &LoggedTransport{ - Transport: http.DefaultTransport, + Transport: transport, Key: "SNAPD_DEBUG_HTTP", body: opts.MayLogBody, }, @@ -119,3 +124,12 @@ return nil } + +// BaseTransport returns the underlying http.Transport of a client created with NewHTTPClient. It panics if that's not the case. For tests. +func BaseTransport(cli *http.Client) *http.Transport { + tr, ok := cli.Transport.(*LoggedTransport) + if !ok { + panic("client must have been created with httputil.NewHTTPClient") + } + return tr.Transport.(*http.Transport) +} diff -Nru snapd-2.27.5/httputil/transport16.go snapd-2.28.5/httputil/transport16.go --- snapd-2.27.5/httputil/transport16.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/httputil/transport16.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,41 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +// +build !go1.7 + +/* + * 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 httputil + +import ( + "net/http" + "time" +) + +var origDefaultTransport *http.Transport = http.DefaultTransport.(*http.Transport) + +// newDefaultTransport makes a fresh modifiable instance of Transport +// with the same parameters as http.DefaultTransport. +func newDefaultTransport() *http.Transport { + // based on https://github.com/golang/go/blob/release-branch.go1.6/src/net/http/transport.go#L33 + return &http.Transport{ + Proxy: http.ProxyFromEnvironment, + Dial: origDefaultTransport.Dial, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } +} diff -Nru snapd-2.27.5/httputil/transport17.go snapd-2.28.5/httputil/transport17.go --- snapd-2.27.5/httputil/transport17.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/httputil/transport17.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,43 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +// +build go1.7 + +/* + * 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 httputil + +import ( + "net/http" + "time" +) + +var origDefaultTransport *http.Transport = http.DefaultTransport.(*http.Transport) + +// newDefaultTransport makes a fresh modifiable instance of Transport +// with the same parameters as http.DefaultTransport. +func newDefaultTransport() *http.Transport { + // based on https://github.com/golang/go/blob/release-branch.go1.7/src/net/http/transport.go#L38 + return &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: origDefaultTransport.DialContext, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } +} diff -Nru snapd-2.27.5/image/image_test.go snapd-2.28.5/image/image_test.go --- snapd-2.27.5/image/image_test.go 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/image/image_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -95,9 +95,7 @@ s.storeSnapInfo = make(map[string]*snap.Info) s.tsto = image.MockToolingStore(s) - rootPrivKey, _ := assertstest.GenerateKey(1024) - storePrivKey, _ := assertstest.GenerateKey(752) - s.storeSigning = assertstest.NewStoreStack("canonical", rootPrivKey, storePrivKey) + s.storeSigning = assertstest.NewStoreStack("canonical", nil) brandPrivKey, _ := assertstest.GenerateKey(752) s.brandSigning = assertstest.NewSigningDB("my-brand", brandPrivKey) diff -Nru snapd-2.27.5/interfaces/apparmor/apparmor.go snapd-2.28.5/interfaces/apparmor/apparmor.go --- snapd-2.27.5/interfaces/apparmor/apparmor.go 2016-06-01 20:08:33.000000000 +0000 +++ snapd-2.28.5/interfaces/apparmor/apparmor.go 2017-09-13 14:47:18.000000000 +0000 @@ -34,6 +34,7 @@ "strings" "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" ) // LoadProfile loads an apparmor profile from the given file. @@ -42,10 +43,14 @@ // If there was a profile with the same name before, that profile is replaced. func LoadProfile(fname string) error { // Use no-expr-simplify since expr-simplify is actually slower on armhf (LP: #1383858) - output, err := exec.Command( - "apparmor_parser", "--replace", "--write-cache", "-O", - "no-expr-simplify", fmt.Sprintf("--cache-loc=%s", dirs.AppArmorCacheDir), - fname).CombinedOutput() + args := []string{"--replace", "--write-cache", "-O", "no-expr-simplify", + fmt.Sprintf("--cache-loc=%s", dirs.AppArmorCacheDir)} + if !osutil.GetenvBool("SNAPD_DEBUG") { + args = append(args, "--quiet") + } + args = append(args, fname) + + output, err := exec.Command("apparmor_parser", args...).CombinedOutput() if err != nil { return fmt.Errorf("cannot load apparmor profile: %s\napparmor_parser output:\n%s", err, string(output)) } diff -Nru snapd-2.27.5/interfaces/apparmor/apparmor_test.go snapd-2.28.5/interfaces/apparmor/apparmor_test.go --- snapd-2.27.5/interfaces/apparmor/apparmor_test.go 2016-06-15 13:35:09.000000000 +0000 +++ snapd-2.28.5/interfaces/apparmor/apparmor_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -59,7 +59,7 @@ err := apparmor.LoadProfile("/path/to/snap.samba.smbd") c.Assert(err, IsNil) c.Assert(cmd.Calls(), DeepEquals, [][]string{ - {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", "--cache-loc=/var/cache/apparmor", "/path/to/snap.samba.smbd"}, + {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", "--cache-loc=/var/cache/apparmor", "--quiet", "/path/to/snap.samba.smbd"}, }) } @@ -71,6 +71,18 @@ apparmor_parser output: `) c.Assert(cmd.Calls(), DeepEquals, [][]string{ + {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", "--cache-loc=/var/cache/apparmor", "--quiet", "/path/to/snap.samba.smbd"}, + }) +} + +func (s *appArmorSuite) TestLoadProfileRunsAppArmorParserReplaceWithSnapdDebug(c *C) { + os.Setenv("SNAPD_DEBUG", "1") + defer os.Unsetenv("SNAPD_DEBUG") + cmd := testutil.MockCommand(c, "apparmor_parser", "") + defer cmd.Restore() + err := apparmor.LoadProfile("/path/to/snap.samba.smbd") + c.Assert(err, IsNil) + c.Assert(cmd.Calls(), DeepEquals, [][]string{ {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", "--cache-loc=/var/cache/apparmor", "/path/to/snap.samba.smbd"}, }) } @@ -78,6 +90,8 @@ // Tests for Profile.Unload() func (s *appArmorSuite) TestUnloadProfileRunsAppArmorParserRemove(c *C) { + dirs.SetRootDir(c.MkDir()) + defer dirs.SetRootDir("") cmd := testutil.MockCommand(c, "apparmor_parser", "") defer cmd.Restore() err := apparmor.UnloadProfile("snap.samba.smbd") diff -Nru snapd-2.27.5/interfaces/apparmor/backend_test.go snapd-2.28.5/interfaces/apparmor/backend_test.go --- snapd-2.27.5/interfaces/apparmor/backend_test.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/interfaces/apparmor/backend_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -120,7 +120,7 @@ c.Check(err, IsNil) // apparmor_parser was used to load that file c.Check(s.parserCmd.Calls(), DeepEquals, [][]string{ - {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), profile}, + {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), "--quiet", profile}, }) } @@ -133,7 +133,7 @@ c.Check(err, IsNil) // apparmor_parser was used to load that file c.Check(s.parserCmd.Calls(), DeepEquals, [][]string{ - {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), profile}, + {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), "--quiet", profile}, }) } @@ -145,7 +145,7 @@ c.Assert(err, IsNil) profile := filepath.Join(dirs.SnapAppArmorDir, "snap.samba.smbd") c.Check(s.parserCmd.Calls(), DeepEquals, [][]string{ - {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), profile}, + {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), "--quiet", profile}, }) s.RemoveSnap(c, snapInfo) } @@ -200,7 +200,7 @@ // apparmor_parser was used to reload the profile because snap revision // is inside the generated policy. c.Check(s.parserCmd.Calls(), DeepEquals, [][]string{ - {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), profile}, + {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), "--quiet", profile}, }) s.RemoveSnap(c, snapInfo) } @@ -219,8 +219,8 @@ c.Check(err, IsNil) // apparmor_parser was used to load the both profiles c.Check(s.parserCmd.Calls(), DeepEquals, [][]string{ - {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), nmbdProfile}, - {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), smbdProfile}, + {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), "--quiet", nmbdProfile}, + {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), "--quiet", smbdProfile}, }) s.RemoveSnap(c, snapInfo) } @@ -241,9 +241,9 @@ c.Check(err, IsNil) // apparmor_parser was used to load the both profiles c.Check(s.parserCmd.Calls(), DeepEquals, [][]string{ - {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), hookProfile}, - {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), nmbdProfile}, - {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), smbdProfile}, + {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), "--quiet", hookProfile}, + {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), "--quiet", nmbdProfile}, + {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), "--quiet", smbdProfile}, }) s.RemoveSnap(c, snapInfo) } @@ -262,7 +262,7 @@ c.Check(os.IsNotExist(err), Equals, true) // apparmor_parser was used to remove the unused profile c.Check(s.parserCmd.Calls(), DeepEquals, [][]string{ - {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), smbdProfile}, + {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), "--quiet", smbdProfile}, {"apparmor_parser", "--remove", "snap.samba.nmbd"}, }) s.RemoveSnap(c, snapInfo) @@ -284,8 +284,8 @@ c.Check(os.IsNotExist(err), Equals, true) // apparmor_parser was used to remove the unused profile c.Check(s.parserCmd.Calls(), DeepEquals, [][]string{ - {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), nmbdProfile}, - {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), smbdProfile}, + {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), "--quiet", nmbdProfile}, + {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", s.RootDir), "--quiet", smbdProfile}, {"apparmor_parser", "--remove", "snap.samba.hook.configure"}, }) s.RemoveSnap(c, snapInfo) diff -Nru snapd-2.27.5/interfaces/apparmor/template.go snapd-2.28.5/interfaces/apparmor/template.go --- snapd-2.27.5/interfaces/apparmor/template.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/apparmor/template.go 2017-09-15 15:05:07.000000000 +0000 @@ -153,6 +153,7 @@ /{,usr/}bin/more ixr, /{,usr/}bin/mv ixr, /{,usr/}bin/nice ixr, + /{,usr/}bin/nohup ixr, /{,usr/}bin/openssl ixr, # may cause harmless capability block_suspend denial /{,usr/}bin/pgrep ixr, /{,usr/}bin/printenv ixr, diff -Nru snapd-2.27.5/interfaces/builtin/account_control.go snapd-2.28.5/interfaces/builtin/account_control.go --- snapd-2.27.5/interfaces/builtin/account_control.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/account_control.go 2017-09-13 14:47:18.000000000 +0000 @@ -21,13 +21,6 @@ const accountControlSummary = `allows managing non-system user accounts` -const accountControlDescription = ` -The account-control interface allows connected plugs to create, modify and -delete non-system users as well as to change account passwords. - -The core snap provides the slot that is shared by all the snaps. -` - const accountControlBaseDeclarationSlots = ` account-control: allow-installation: @@ -66,9 +59,8 @@ // Needed because useradd uses a netlink socket const accountControlConnectedPlugSecComp = ` # useradd requires chowning to 'shadow' -# TODO: dynamically determine the shadow gid to support alternate cores -fchown - 0 42 -fchown32 - 0 42 +fchown - u:root g:shadow +fchown32 - u:root g:shadow # from libaudit1 bind @@ -79,7 +71,6 @@ registerIface(&commonInterface{ name: "account-control", summary: accountControlSummary, - description: accountControlDescription, implicitOnCore: true, implicitOnClassic: true, baseDeclarationSlots: accountControlBaseDeclarationSlots, diff -Nru snapd-2.27.5/interfaces/builtin/account_control_test.go snapd-2.28.5/interfaces/builtin/account_control_test.go --- snapd-2.27.5/interfaces/builtin/account_control_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/account_control_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -73,26 +73,18 @@ } func (s *AccountControlSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "account-control", Interface: "account-control", - }}) - c.Assert(err, ErrorMatches, "account-control slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "account-control slots are reserved for the core snap") } func (s *AccountControlSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *AccountControlSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "account-control"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "account-control"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *AccountControlSuite) TestUsedSecuritySystems(c *C) { @@ -107,7 +99,7 @@ err = seccompSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) - c.Check(seccompSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "\nfchown - 0 42\n") + c.Check(seccompSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "\nfchown - u:root g:shadow\n") } func (s *AccountControlSuite) TestInterfaces(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/alsa.go snapd-2.28.5/interfaces/builtin/alsa.go --- snapd-2.27.5/interfaces/builtin/alsa.go 2017-08-24 08:12:57.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/alsa.go 2017-09-13 14:47:18.000000000 +0000 @@ -21,12 +21,6 @@ const alsaSummary = `allows access to raw ALSA devices` -const alsaDescription = ` -The alsa interface allows connected plugs to access raw ALSA devices. - -The core snap provides the slot that is shared by all the snaps. -` - const alsaBaseDeclarationSlots = ` alsa: allow-installation: @@ -52,15 +46,25 @@ @{PROC}/asound/** rw, ` +const alsaConnectedPlugUDev = ` +KERNEL=="controlC[0-9]*", TAG+="###CONNECTED_SECURITY_TAGS###" +KERNEL=="hwC[0-9]*D[0-9]*", TAG+="###CONNECTED_SECURITY_TAGS###" +KERNEL=="pcmC[0-9]*D[0-9]*[cp]", TAG+="###CONNECTED_SECURITY_TAGS###" +KERNEL=="midiC[0-9]*D[0-9]*", TAG+="###CONNECTED_SECURITY_TAGS###" +KERNEL=="timer", TAG+="###CONNECTED_SECURITY_TAGS###" +KERNEL=="seq", TAG+="###CONNECTED_SECURITY_TAGS###" +SUBSYSTEM=="sound", KERNEL=="card[0-9]*", TAG+="###CONNECTED_SECURITY_TAGS###" +` + func init() { registerIface(&commonInterface{ name: "alsa", summary: alsaSummary, - description: alsaDescription, implicitOnCore: true, implicitOnClassic: true, baseDeclarationSlots: alsaBaseDeclarationSlots, connectedPlugAppArmor: alsaConnectedPlugAppArmor, + connectedPlugUDev: alsaConnectedPlugUDev, reservedForOS: true, }) } diff -Nru snapd-2.27.5/interfaces/builtin/alsa_test.go snapd-2.28.5/interfaces/builtin/alsa_test.go --- snapd-2.27.5/interfaces/builtin/alsa_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/alsa_test.go 2017-09-13 14:47:18.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 @@ -25,8 +25,8 @@ "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/snap/snaptest" "github.com/snapcore/snapd/testutil" ) @@ -40,24 +40,21 @@ iface: builtin.MustInterface("alsa"), }) -func (s *AlsaInterfaceSuite) SetUpTest(c *C) { - var mockPlugSnapInfoYaml = `name: other -version: 1.0 +const alsaConsumerYaml = `name: consumer apps: app: - command: foo plugs: [alsa] ` - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "alsa", - Interface: "alsa", - }, - } - snapInfo := snaptest.MockInfo(c, mockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: snapInfo.Plugs["alsa"]} - c.Assert(s.iface, NotNil) + +const alsaCoreYaml = `name: core +type: os +slots: + alsa: +` + +func (s *AlsaInterfaceSuite) SetUpTest(c *C) { + s.plug = MockPlug(c, alsaConsumerYaml, nil, "alsa") + s.slot = MockSlot(c, alsaCoreYaml, nil, "alsa") } func (s *AlsaInterfaceSuite) TestName(c *C) { @@ -65,35 +62,44 @@ } func (s *AlsaInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "alsa", Interface: "alsa", - }}) - c.Assert(err, ErrorMatches, "alsa slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "alsa slots are reserved for the core snap") } func (s *AlsaInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) + c.Assert(s.plug.Sanitize(s.iface), 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.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, 1) + c.Assert(spec.Snippets()[0], testutil.Contains, `KERNEL=="pcmC[0-9]*D[0-9]*[cp]", TAG+="snap_consumer_app"`) +} + +func (s *AlsaInterfaceSuite) TestStaticInfo(c *C) { + si := interfaces.StaticInfoOf(s.iface) + c.Assert(si.ImplicitOnCore, Equals, true) + c.Assert(si.ImplicitOnClassic, Equals, true) + c.Assert(si.Summary, Equals, `allows access to raw ALSA devices`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "alsa") } -func (s *AlsaInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "alsa"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "alsa"`) -} - -func (s *AlsaInterfaceSuite) 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) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) - c.Check(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, "/dev/snd/* rw,") +func (s *AlsaInterfaceSuite) TestAutoConnect(c *C) { + c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) } func (s *AlsaInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/autopilot_test.go snapd-2.28.5/interfaces/builtin/autopilot_test.go --- snapd-2.27.5/interfaces/builtin/autopilot_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/autopilot_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -66,26 +66,18 @@ } func (s *AutopilotInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "autopilot-introspection", Interface: "autopilot-introspection", - }}) - c.Assert(err, ErrorMatches, "autopilot-introspection slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "autopilot-introspection slots are reserved for the core snap") } func (s *AutopilotInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *AutopilotInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "autopilot-introspection"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "autopilot-introspection"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *AutopilotInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/avahi_control.go snapd-2.28.5/interfaces/builtin/avahi_control.go --- snapd-2.27.5/interfaces/builtin/avahi_control.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/avahi_control.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,174 @@ +// -*- 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 + +import ( + "strings" + + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/apparmor" + "github.com/snapcore/snapd/interfaces/dbus" + "github.com/snapcore/snapd/release" +) + +const avahiControlBaseDeclarationSlots = ` + avahi-control: + allow-installation: + slot-snap-type: + - app + - core + deny-auto-connection: true + deny-connection: + on-classic: false +` + +const avahiControlSummary = `allows control over service discovery on a local network via the mDNS/DNS-SD protocol suite` + +const avahiControlConnectedSlotAppArmor = ` +# Description: allows configuration of service discovery via mDNS/DNS-SD +# EntryGroup +dbus (receive) + bus=system + path=/Client*/EntryGroup* + interface=org.freedesktop.Avahi.EntryGroup + peer=(label=###PLUG_SECURITY_TAGS###), + +dbus (send) + bus=system + interface=org.freedesktop.Avahi.EntryGroup + member=StateChanged + peer=(name=org.freedesktop.Avahi, label=###PLUG_SECURITY_TAGS###), +` + +const avahiControlConnectedPlugAppArmor = ` +dbus (send) + bus=system + path=/ + interface=org.freedesktop.Avahi.Server + member=Set* + peer=(name=org.freedesktop.Avahi,label=###SLOT_SECURITY_TAGS###), + +# EntryGroup +dbus (send) + bus=system + path=/ + interface=org.freedesktop.Avahi.Server + member=EntryGroupNew + peer=(name=org.freedesktop.Avahi, label=###SLOT_SECURITY_TAGS###), + +dbus (send) + bus=system + path=/Client*/EntryGroup* + interface=org.freedesktop.Avahi.EntryGroup + member={Free,Commit,Reset} + peer=(name=org.freedesktop.Avahi, label=###SLOT_SECURITY_TAGS###), + +dbus (send) + bus=system + path=/Client*/EntryGroup* + interface=org.freedesktop.Avahi.EntryGroup + member={GetState,IsEmpty,UpdateServiceTxt} + peer=(name=org.freedesktop.Avahi, label=###SLOT_SECURITY_TAGS###), + +dbus (send) + bus=system + path=/Client*/EntryGroup* + interface=org.freedesktop.Avahi.EntryGroup + member=Add{Service,ServiceSubtype,Address,Record} + peer=(name=org.freedesktop.Avahi, label=###SLOT_SECURITY_TAGS###), + +dbus (receive) + bus=system + path=/Client*/EntryGroup* + interface=org.freedesktop.Avahi.EntryGroup + peer=(label=###SLOT_SECURITY_TAGS###), +` + +type avahiControlInterface struct{} + +func (iface *avahiControlInterface) Name() string { + return "avahi-control" +} + +func (iface *avahiControlInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ + Summary: avahiControlSummary, + ImplicitOnClassic: true, + BaseDeclarationSlots: avahiControlBaseDeclarationSlots, + } +} + +func (iface *avahiControlInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { + old := "###SLOT_SECURITY_TAGS###" + var new string + if release.OnClassic { + // If we're running on classic Avahi will be part + // of the OS snap and will run unconfined. + new = "unconfined" + } else { + new = slotAppLabelExpr(slot) + } + // avahi-control implies avahi-observe, so add snippets for both here + snippet := strings.Replace(avahiObserveConnectedPlugAppArmor, old, new, -1) + spec.AddSnippet(snippet) + snippet = strings.Replace(avahiControlConnectedPlugAppArmor, old, new, -1) + spec.AddSnippet(snippet) + return nil +} + +func (iface *avahiControlInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { + if !release.OnClassic { + // NOTE: this is using avahi-observe permanent slot as it contains + // base declarations for running as the avahi service. + spec.AddSnippet(avahiObservePermanentSlotAppArmor) + } + return nil +} + +func (iface *avahiControlInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { + if !release.OnClassic { + old := "###PLUG_SECURITY_TAGS###" + new := plugAppLabelExpr(plug) + // avahi-control implies avahi-observe, so add snippets for both here + snippet := strings.Replace(avahiObserveConnectedSlotAppArmor, old, new, -1) + spec.AddSnippet(snippet) + snippet = strings.Replace(avahiControlConnectedSlotAppArmor, old, new, -1) + spec.AddSnippet(snippet) + } + return nil +} + +func (iface *avahiControlInterface) DBusPermanentSlot(spec *dbus.Specification, slot *interfaces.Slot) error { + if !release.OnClassic { + // NOTE: this is using avahi-observe permanent slot as it contains + // base declarations for running as the avahi service. + spec.AddSnippet(avahiObservePermanentSlotDBus) + } + return nil +} + +func (iface *avahiControlInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { + // allow what declarations allowed + return true +} + +func init() { + registerIface(&avahiControlInterface{}) +} diff -Nru snapd-2.27.5/interfaces/builtin/avahi_control_test.go snapd-2.28.5/interfaces/builtin/avahi_control_test.go --- snapd-2.27.5/interfaces/builtin/avahi_control_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/avahi_control_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,195 @@ +// -*- 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/dbus" + "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/testutil" +) + +type AvahiControlInterfaceSuite struct { + iface interfaces.Interface + plug *interfaces.Plug + appSlot *interfaces.Slot + coreSlot *interfaces.Slot +} + +var _ = Suite(&AvahiControlInterfaceSuite{ + iface: builtin.MustInterface("avahi-control"), +}) + +const avahiControlConsumerYaml = `name: consumer +apps: + app: + plugs: [avahi-control] +` + +const avahiControlProducerYaml = `name: producer +apps: + app: + slots: [avahi-control] +` + +const avahiControlCoreYaml = `name: core +slots: + avahi-control: +` + +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") +} + +func (s *AvahiControlInterfaceSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "avahi-control") +} + +func (s *AvahiControlInterfaceSuite) TestSanitizeSlot(c *C) { + c.Assert(s.coreSlot.Sanitize(s.iface), IsNil) + c.Assert(s.appSlot.Sanitize(s.iface), IsNil) + // avahi-control slot can now be used on snap other than core. + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "some-snap"}, + Name: "avahi-control", + Interface: "avahi-control", + }} + c.Assert(slot.Sanitize(s.iface), IsNil) +} + +func (s *AvahiControlInterfaceSuite) TestSanitizePlug(c *C) { + c.Assert(s.plug.Sanitize(s.iface), IsNil) +} + +func (s *AvahiControlInterfaceSuite) TestAppArmorSpec(c *C) { + // on a core system with avahi slot coming from a regular app snap. + restore := release.MockOnClassic(false) + defer restore() + + // connected plug to app slot + spec := &apparmor.Specification{} + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.appSlot, nil), 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"),`) + // make sure control includes also observe capabilities + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `interface=org.freedesktop.Avahi.AddressResolver`) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `interface=org.freedesktop.Avahi.HostNameResolver`) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `interface=org.freedesktop.Avahi.ServiceResolver`) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `interface=org.freedesktop.Avahi.RecordBrowser`) + // control capabilities + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `member=Set*`) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `member=EntryGroupNew`) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `interface=org.freedesktop.Avahi.EntryGroup`) + + // connected app slot to plug + spec = &apparmor.Specification{} + c.Assert(spec.AddConnectedSlot(s.iface, s.plug, nil, s.appSlot, nil), 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"),`) + // make sure control includes also observe capabilities + c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, `interface=org.freedesktop.Avahi.AddressResolver`) + c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, `interface=org.freedesktop.Avahi.HostNameResolver`) + c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, `interface=org.freedesktop.Avahi.ServiceResolver`) + c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, `interface=org.freedesktop.Avahi.RecordBrowser`) + // control capabilities + c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, `interface=org.freedesktop.Avahi.EntryGroup`) + + // permanent app slot + spec = &apparmor.Specification{} + c.Assert(spec.AddPermanentSlot(s.iface, s.appSlot), IsNil) + c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.producer.app"}) + c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, `dbus (bind) + bus=system + name="org.freedesktop.Avahi",`) + + // on a classic system with avahi slot coming from the core snap. + restore = release.MockOnClassic(true) + 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.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),") + // make sure control includes also observe capabilities + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `interface=org.freedesktop.Avahi.AddressResolver`) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `interface=org.freedesktop.Avahi.HostNameResolver`) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `interface=org.freedesktop.Avahi.ServiceResolver`) + // control capabilities + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `member=Set*`) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `member=EntryGroupNew`) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `interface=org.freedesktop.Avahi.EntryGroup`) + + // connected core slot to plug + spec = &apparmor.Specification{} + c.Assert(spec.AddConnectedSlot(s.iface, s.plug, nil, s.coreSlot, nil), 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.SecurityTags(), HasLen, 0) +} + +func (s *AvahiControlInterfaceSuite) TestDBusSpec(c *C) { + // on a core system with avahi slot coming from a regular app snap. + restore := release.MockOnClassic(false) + defer restore() + + spec := &dbus.Specification{} + c.Assert(spec.AddPermanentSlot(s.iface, s.appSlot), IsNil) + c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.producer.app"}) + c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, ``) + + // on a classic system with avahi slot coming from the core snap. + restore = release.MockOnClassic(true) + defer restore() + + spec = &dbus.Specification{} + c.Assert(spec.AddPermanentSlot(s.iface, s.coreSlot), IsNil) + c.Assert(spec.SecurityTags(), HasLen, 0) +} + +func (s *AvahiControlInterfaceSuite) TestStaticInfo(c *C) { + si := interfaces.StaticInfoOf(s.iface) + c.Assert(si.ImplicitOnCore, Equals, false) + c.Assert(si.ImplicitOnClassic, Equals, true) + c.Assert(si.Summary, Equals, `allows control over service discovery on a local network via the mDNS/DNS-SD protocol suite`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "avahi-control") +} + +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) +} + +func (s *AvahiControlInterfaceSuite) TestInterfaces(c *C) { + c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) +} diff -Nru snapd-2.27.5/interfaces/builtin/avahi_observe.go snapd-2.28.5/interfaces/builtin/avahi_observe.go --- snapd-2.27.5/interfaces/builtin/avahi_observe.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/avahi_observe.go 2017-09-13 14:47:18.000000000 +0000 @@ -19,18 +19,217 @@ package builtin -const avahiObserveSummary = `allows discovering local domains, hostnames and services` +import ( + "strings" + + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/apparmor" + "github.com/snapcore/snapd/interfaces/dbus" + "github.com/snapcore/snapd/release" +) + +const avahiObserveSummary = `allows discovery on a local network via the mDNS/DNS-SD protocol suite` const avahiObserveBaseDeclarationSlots = ` avahi-observe: allow-installation: slot-snap-type: + - app - core deny-auto-connection: true + deny-connection: + on-classic: false +` + +const avahiObservePermanentSlotAppArmor = ` +network netlink, + +# Description: Allow operating as the avahi service. This gives +# privileged access to the system. +#include + +dbus (send) + bus=system + path=/org/freedesktop/DBus + interface=org.freedesktop.DBus + member={Request,Release}Name + peer=(name=org.freedesktop.DBus, label=unconfined), + +dbus (receive, send) + bus=system + path=/org/freedesktop/DBus + interface=org.freedesktop.DBus + member=GetConnectionUnixProcessID + peer=(label=unconfined), + +dbus (receive, send) + bus=system + path=/org/freedesktop/DBus + interface=org.freedesktop.DBus + member=GetConnectionUnixUser + peer=(label=unconfined), + +# Allow binding the service to the requested connection name +dbus (bind) + bus=system + name="org.freedesktop.Avahi", + +# Allow traffic to/from our path and interface with any method for unconfined +# clients to talk to our service. +dbus (receive, send) + bus=system + path=/org/freedesktop/Avahi{,/**} + interface=org.freedesktop.Avahi* + peer=(label=unconfined), + +# Allow traffic to/from org.freedesktop.DBus for Avahi service +dbus (receive, send) + bus=system + path=/org/freedesktop/Avahi{,/**} + interface=org.freedesktop.DBus.* + peer=(label=unconfined), +` + +// Note: avahiObserveConnectedSlotAppArmor is also used by avahi-control in AppArmorConnectedSlot +const avahiObserveConnectedSlotAppArmor = ` +# Description: Allow operating as the avahi service. This gives +# privileged access to the system. +#include + +# Allow all access to Avahi service + +dbus (receive) + bus=system + path=/ + interface=org.freedesktop.DBus.Peer + member=Ping + peer=(label=###PLUG_SECURITY_TAGS###), + +dbus (receive) + bus=system + path=/ + interface=org.freedesktop.Avahi.Server + peer=(label=###PLUG_SECURITY_TAGS###), + +dbus (send) + bus=system + interface=org.freedesktop.Avahi.Server + member=StateChanged + peer=(name=org.freedesktop.Avahi, label=###PLUG_SECURITY_TAGS###), + +# address resolving +dbus (receive) + bus=system + path=/Client*/AddressResolver* + interface=org.freedesktop.Avahi.AddressResolver + peer=(label=###PLUG_SECURITY_TAGS###), + +dbus (send) + bus=system + interface=org.freedesktop.Avahi.AddressResolver + peer=(name=org.freedesktop.Avahi, label=###PLUG_SECURITY_TAGS###), + +# host name resolving +dbus (receive) + bus=system + path=/Client*/HostNameResolver* + interface=org.freedesktop.Avahi.HostNameResolver + peer=(label=###PLUG_SECURITY_TAGS###), + +dbus (send) + bus=system + interface=org.freedesktop.Avahi.HostNameResolver + peer=(name=org.freedesktop.Avahi, label=###PLUG_SECURITY_TAGS###), + +# service resolving +dbus (receive) + bus=system + path=/Client*/ServiceResolver* + interface=org.freedesktop.Avahi.ServiceResolver + peer=(label=###PLUG_SECURITY_TAGS###), + +dbus (send) + bus=system + interface=org.freedesktop.Avahi.ServiceResolver + peer=(name=org.freedesktop.Avahi, label=###PLUG_SECURITY_TAGS###), + +# domain browsing +dbus (receive) + bus=system + path=/Client*/DomainBrowser* + interface=org.freedesktop.Avahi.DomainBrowser + peer=(label=###PLUG_SECURITY_TAGS###), + +dbus (send) + bus=system + interface=org.freedesktop.Avahi.DomainBrowser + peer=(name=org.freedesktop.Avahi, label=###PLUG_SECURITY_TAGS###), + +# record browsing +dbus (receive) + bus=system + path=/Client*/RecordBrowser* + interface=org.freedesktop.Avahi.RecordBrowser + peer=(label=###PLUG_SECURITY_TAGS###), + +dbus (send) + bus=system + interface=org.freedesktop.Avahi.RecordBrowser + peer=(name=org.freedesktop.Avahi, label=###PLUG_SECURITY_TAGS###), + +# service browsing +dbus (receive) + bus=system + path=/Client*/ServiceBrowser* + interface=org.freedesktop.Avahi.ServiceBrowser + peer=(label=###PLUG_SECURITY_TAGS###), + +dbus (send) + bus=system + interface=org.freedesktop.Avahi.ServiceBrowser + peer=(name=org.freedesktop.Avahi, label=###PLUG_SECURITY_TAGS###), + +# service type browsing +dbus (receive) + bus=system + path=/Client*/ServiceTypeBrowser* + interface=org.freedesktop.Avahi.ServiceTypeBrowser + peer=(label=###PLUG_SECURITY_TAGS###), + +dbus (send) + bus=system + interface=org.freedesktop.Avahi.ServiceTypeBrowser + peer=(name=org.freedesktop.Avahi, label=###PLUG_SECURITY_TAGS###), ` +// Note: avahiObservePermanentSlotDBus is used by avahi-control in DBusPermanentSlot +const avahiObservePermanentSlotDBus = ` + + + + + + + + + + + + + + + + + + + +` + +// Note: avahiObserveConnectedPlugAppArmor is also used by avahi-control in AppArmorConnectedPlug const avahiObserveConnectedPlugAppArmor = ` -# Description: allows domain browsing, service browsing and service resolving +# Description: allows domain, record, service, and service type browsing +# as well as address, host and service resolving #include dbus (send) @@ -38,14 +237,21 @@ path=/ interface=org.freedesktop.DBus.Peer member=Ping - peer=(name=org.freedesktop.Avahi,label=unconfined), + peer=(name=org.freedesktop.Avahi,label=###SLOT_SECURITY_TAGS###), +# Allow accessing DBus properties and resolving dbus (send) bus=system path=/ interface=org.freedesktop.Avahi.Server - member=Get* - peer=(name=org.freedesktop.Avahi,label=unconfined), + member={Get*,Resolve*,IsNSSSupportAvailable} + peer=(name=org.freedesktop.Avahi,label=###SLOT_SECURITY_TAGS###), + +# Allow receiving anything from the slot server +dbus (receive) + bus=system + interface=org.freedesktop.Avahi.Server + peer=(label=###SLOT_SECURITY_TAGS###), # Don't allow introspection since it reveals too much (path is not service # specific for unconfined) @@ -59,25 +265,45 @@ # These allows tampering with other snap's browsers, so don't autoconnect for # now. -# service browsing +# address resolving dbus (send) bus=system path=/ interface=org.freedesktop.Avahi.Server - member=ServiceBrowserNew - peer=(name=org.freedesktop.Avahi,label=unconfined), + member=AddressResolverNew + peer=(name=org.freedesktop.Avahi, label=###SLOT_SECURITY_TAGS###), dbus (send) bus=system - path=/Client*/ServiceBrowser* - interface=org.freedesktop.Avahi.ServiceBrowser + path=/Client*/AddressResolver* + interface=org.freedesktop.Avahi.AddressResolver member=Free - peer=(name=org.freedesktop.Avahi,label=unconfined), + peer=(name=org.freedesktop.Avahi, label=###SLOT_SECURITY_TAGS###), dbus (receive) bus=system - interface=org.freedesktop.Avahi.ServiceBrowser - peer=(label=unconfined), + interface=org.freedesktop.Avahi.AddressResolver + peer=(label=###SLOT_SECURITY_TAGS###), + +# host name resolving +dbus (send) + bus=system + path=/ + interface=org.freedesktop.Avahi.Server + member=HostNameResolverNew + peer=(name=org.freedesktop.Avahi, label=###SLOT_SECURITY_TAGS###), + +dbus (send) + bus=system + path=/Client*/HostNameResolver* + interface=org.freedesktop.Avahi.HostNameResolver + member=Free + peer=(name=org.freedesktop.Avahi, label=###SLOT_SECURITY_TAGS###), + +dbus (receive) + bus=system + interface=org.freedesktop.Avahi.HostNameResolver + peer=(label=###SLOT_SECURITY_TAGS###), # service resolving dbus (send) @@ -85,19 +311,19 @@ path=/ interface=org.freedesktop.Avahi.Server member=ServiceResolverNew - peer=(name=org.freedesktop.Avahi,label=unconfined), + peer=(name=org.freedesktop.Avahi, label=###SLOT_SECURITY_TAGS###), dbus (send) bus=system path=/Client*/ServiceResolver* interface=org.freedesktop.Avahi.ServiceResolver member=Free - peer=(name=org.freedesktop.Avahi,label=unconfined), + peer=(name=org.freedesktop.Avahi, label=###SLOT_SECURITY_TAGS###), dbus (receive) bus=system interface=org.freedesktop.Avahi.ServiceResolver - peer=(label=unconfined), + peer=(label=###SLOT_SECURITY_TAGS###), # domain browsing dbus (send) @@ -105,29 +331,139 @@ path=/ interface=org.freedesktop.Avahi.Server member=DomainBrowserNew - peer=(name=org.freedesktop.Avahi,label=unconfined), + peer=(name=org.freedesktop.Avahi, label=###SLOT_SECURITY_TAGS###), dbus (send) bus=system path=/Client*/DomainBrowser* interface=org.freedesktop.Avahi.DomainBrowser member=Free - peer=(name=org.freedesktop.Avahi,label=unconfined), + peer=(name=org.freedesktop.Avahi, label=###SLOT_SECURITY_TAGS###), dbus (receive) bus=system - path=/Client*/DomainBrowser* interface=org.freedesktop.Avahi.DomainBrowser - peer=(label=unconfined), + peer=(label=###SLOT_SECURITY_TAGS###), + +# record browsing +dbus (send) + bus=system + path=/ + interface=org.freedesktop.Avahi.Server + member=RecordBrowserNew + peer=(name=org.freedesktop.Avahi, label=###SLOT_SECURITY_TAGS###), + +dbus (send) + bus=system + path=/Client*/RecordBrowser* + interface=org.freedesktop.Avahi.RecordBrowser + member=Free + peer=(name=org.freedesktop.Avahi, label=###SLOT_SECURITY_TAGS###), + +dbus (receive) + bus=system + interface=org.freedesktop.Avahi.RecordBrowser + peer=(label=###SLOT_SECURITY_TAGS###), + +# service browsing +dbus (send) + bus=system + path=/ + interface=org.freedesktop.Avahi.Server + member=ServiceBrowserNew + peer=(name=org.freedesktop.Avahi, label=###SLOT_SECURITY_TAGS###), + +dbus (send) + bus=system + path=/Client*/ServiceBrowser* + interface=org.freedesktop.Avahi.ServiceBrowser + member=Free + peer=(name=org.freedesktop.Avahi, label=###SLOT_SECURITY_TAGS###), + +dbus (receive) + bus=system + interface=org.freedesktop.Avahi.ServiceBrowser + peer=(label=###SLOT_SECURITY_TAGS###), + +# Service type browsing +dbus (send) + bus=system + path=/ + interface=org.freedesktop.Avahi.Server + member=ServiceTypeBrowserNew + peer=(name=org.freedesktop.Avahi, label=###SLOT_SECURITY_TAGS###), + +dbus (send) + bus=system + path=/Client*/ServiceTypeBrowser* + interface=org.freedesktop.Avahi.ServiceTypeBrowser + member=Free + peer=(name=org.freedesktop.Avahi, label=###SLOT_SECURITY_TAGS###), + +dbus (receive) + bus=system + interface=org.freedesktop.Avahi.ServiceTypeBrowser + peer=(label=###SLOT_SECURITY_TAGS###), ` +type avahiObserveInterface struct{} + +func (iface *avahiObserveInterface) Name() string { + return "avahi-observe" +} + +func (iface *avahiObserveInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ + Summary: avahiObserveSummary, + ImplicitOnClassic: true, + BaseDeclarationSlots: avahiObserveBaseDeclarationSlots, + } +} + +func (iface *avahiObserveInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { + old := "###SLOT_SECURITY_TAGS###" + var new string + if release.OnClassic { + // If we're running on classic Avahi will be part + // of the OS snap and will run unconfined. + new = "unconfined" + } else { + new = slotAppLabelExpr(slot) + } + snippet := strings.Replace(avahiObserveConnectedPlugAppArmor, old, new, -1) + spec.AddSnippet(snippet) + return nil +} + +func (iface *avahiObserveInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) 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 { + if !release.OnClassic { + old := "###PLUG_SECURITY_TAGS###" + new := plugAppLabelExpr(plug) + snippet := strings.Replace(avahiObserveConnectedSlotAppArmor, old, new, -1) + spec.AddSnippet(snippet) + } + return nil +} + +func (iface *avahiObserveInterface) DBusPermanentSlot(spec *dbus.Specification, slot *interfaces.Slot) error { + if !release.OnClassic { + spec.AddSnippet(avahiObservePermanentSlotDBus) + } + return nil +} + +func (iface *avahiObserveInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { + // allow what declarations allowed + return true +} + func init() { - registerIface(&commonInterface{ - name: "avahi-observe", - summary: avahiObserveSummary, - implicitOnClassic: true, - baseDeclarationSlots: avahiObserveBaseDeclarationSlots, - connectedPlugAppArmor: avahiObserveConnectedPlugAppArmor, - reservedForOS: true, - }) + registerIface(&avahiObserveInterface{}) } diff -Nru snapd-2.27.5/interfaces/builtin/avahi_observe_test.go snapd-2.28.5/interfaces/builtin/avahi_observe_test.go --- snapd-2.27.5/interfaces/builtin/avahi_observe_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/avahi_observe_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -25,38 +25,44 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/interfaces/dbus" + "github.com/snapcore/snapd/release" "github.com/snapcore/snapd/snap" - "github.com/snapcore/snapd/snap/snaptest" "github.com/snapcore/snapd/testutil" ) type AvahiObserveInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + plug *interfaces.Plug + appSlot *interfaces.Slot + coreSlot *interfaces.Slot } var _ = Suite(&AvahiObserveInterfaceSuite{ iface: builtin.MustInterface("avahi-observe"), }) -func (s *AvahiObserveInterfaceSuite) SetUpTest(c *C) { - var mockPlugSnapInfoYaml = `name: other -version: 1.0 +const avahiObserveConsumerYaml = `name: consumer apps: app: - command: foo plugs: [avahi-observe] ` - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "avahi-observe", - Interface: "avahi-observe", - }, - } - snapInfo := snaptest.MockInfo(c, mockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: snapInfo.Plugs["avahi-observe"]} + +const avahiObserveProducerYaml = `name: producer +apps: + app: + slots: [avahi-observe] +` + +const avahiObserveCoreYaml = `name: core +slots: + avahi-observe: +` + +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") } func (s *AvahiObserveInterfaceSuite) TestName(c *C) { @@ -64,35 +70,124 @@ } func (s *AvahiObserveInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.coreSlot.Sanitize(s.iface), IsNil) + c.Assert(s.appSlot.Sanitize(s.iface), IsNil) + // avahi-observe slot can now be used on snap other than core. + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "avahi-observe", Interface: "avahi-observe", - }}) - c.Assert(err, ErrorMatches, "avahi-observe slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), IsNil) } func (s *AvahiObserveInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } -func (s *AvahiObserveInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "avahi-observe"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "avahi-observe"`) -} - -func (s *AvahiObserveInterfaceSuite) 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) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) - c.Check(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, "name=org.freedesktop.Avahi") +func (s *AvahiObserveInterfaceSuite) TestAppArmorSpec(c *C) { + // on a core system with avahi slot coming from a regular app snap. + restore := release.MockOnClassic(false) + defer restore() + + // connected plug to app slot + spec := &apparmor.Specification{} + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.appSlot, nil), 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"),`) + // make sure observe does have observe but not control capabilities + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `interface=org.freedesktop.Avahi.AddressResolver`) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `interface=org.freedesktop.Avahi.HostNameResolver`) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `interface=org.freedesktop.Avahi.ServiceResolver`) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `interface=org.freedesktop.Avahi.RecordBrowser`) + // control capabilities + c.Assert(spec.SnippetForTag("snap.consumer.app"), Not(testutil.Contains), `member=Set*`) + c.Assert(spec.SnippetForTag("snap.consumer.app"), Not(testutil.Contains), `member=EntryGroupNew`) + c.Assert(spec.SnippetForTag("snap.consumer.app"), Not(testutil.Contains), `interface=org.freedesktop.Avahi.EntryGroup`) + + // connected app slot to plug + spec = &apparmor.Specification{} + c.Assert(spec.AddConnectedSlot(s.iface, s.plug, nil, s.appSlot, nil), 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"),`) + // make sure observe does have observe but not control capabilities + c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, `interface=org.freedesktop.Avahi.AddressResolver`) + c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, `interface=org.freedesktop.Avahi.HostNameResolver`) + c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, `interface=org.freedesktop.Avahi.ServiceResolver`) + c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, `interface=org.freedesktop.Avahi.RecordBrowser`) + // control capabilities + c.Assert(spec.SnippetForTag("snap.producer.app"), Not(testutil.Contains), `interface=org.freedesktop.Avahi.EntryGroup`) + + // permanent app slot + spec = &apparmor.Specification{} + c.Assert(spec.AddPermanentSlot(s.iface, s.appSlot), IsNil) + c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.producer.app"}) + c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, `dbus (bind) + bus=system + name="org.freedesktop.Avahi",`) + + // on a classic system with avahi slot coming from the core snap. + restore = release.MockOnClassic(true) + 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.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),") + // make sure observe does have observe but not control capabilities + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `interface=org.freedesktop.Avahi.AddressResolver`) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `interface=org.freedesktop.Avahi.HostNameResolver`) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `interface=org.freedesktop.Avahi.ServiceResolver`) + // control capabilities + c.Assert(spec.SnippetForTag("snap.consumer.app"), Not(testutil.Contains), `member=Set*`) + c.Assert(spec.SnippetForTag("snap.consumer.app"), Not(testutil.Contains), `member=EntryGroupNew`) + c.Assert(spec.SnippetForTag("snap.consumer.app"), Not(testutil.Contains), `interface=org.freedesktop.Avahi.EntryGroup`) + + // connected core slot to plug + spec = &apparmor.Specification{} + c.Assert(spec.AddConnectedSlot(s.iface, s.plug, nil, s.coreSlot, nil), 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.SecurityTags(), HasLen, 0) +} + +func (s *AvahiObserveInterfaceSuite) TestDBusSpec(c *C) { + // on a core system with avahi slot coming from a regular app snap. + restore := release.MockOnClassic(false) + defer restore() + + spec := &dbus.Specification{} + c.Assert(spec.AddPermanentSlot(s.iface, s.appSlot), IsNil) + c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.producer.app"}) + c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, ``) + + // on a classic system with avahi slot coming from the core snap. + restore = release.MockOnClassic(true) + defer restore() + + spec = &dbus.Specification{} + c.Assert(spec.AddPermanentSlot(s.iface, s.coreSlot), IsNil) + c.Assert(spec.SecurityTags(), HasLen, 0) +} + +func (s *AvahiObserveInterfaceSuite) TestStaticInfo(c *C) { + si := interfaces.StaticInfoOf(s.iface) + c.Assert(si.ImplicitOnCore, Equals, false) + c.Assert(si.ImplicitOnClassic, Equals, true) + c.Assert(si.Summary, Equals, `allows discovery on a local network via the mDNS/DNS-SD protocol suite`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "avahi-observe") +} + +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) } func (s *AvahiObserveInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/bluetooth_control.go snapd-2.28.5/interfaces/builtin/bluetooth_control.go --- snapd-2.27.5/interfaces/builtin/bluetooth_control.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/bluetooth_control.go 2017-09-13 14:47:18.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 @@ -56,6 +56,8 @@ bind ` +const bluetoothControlConnectedPlugUDev = `SUBSYSTEM=="bluetooth", TAG+="###CONNECTED_SECURITY_TAGS###"` + func init() { registerIface(&commonInterface{ name: "bluetooth-control", @@ -65,6 +67,7 @@ baseDeclarationSlots: bluetoothControlBaseDeclarationSlots, connectedPlugAppArmor: bluetoothControlConnectedPlugAppArmor, connectedPlugSecComp: bluetoothControlConnectedPlugSecComp, + connectedPlugUDev: bluetoothControlConnectedPlugUDev, reservedForOS: true, }) } diff -Nru snapd-2.27.5/interfaces/builtin/bluetooth_control_test.go snapd-2.28.5/interfaces/builtin/bluetooth_control_test.go --- snapd-2.27.5/interfaces/builtin/bluetooth_control_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/bluetooth_control_test.go 2017-09-13 14:47:18.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 @@ -26,6 +26,7 @@ "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/snap/snaptest" "github.com/snapcore/snapd/testutil" @@ -72,42 +73,39 @@ } func (s *BluetoothControlInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "bluetooth-control", Interface: "bluetooth-control", - }}) - c.Assert(err, ErrorMatches, "bluetooth-control slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "bluetooth-control slots are reserved for the core snap") } func (s *BluetoothControlInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } -func (s *BluetoothControlInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "bluetooth-control"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "bluetooth-control"`) -} - -func (s *BluetoothControlInterfaceSuite) 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) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) - c.Assert(apparmorSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "capability net_admin") - - // 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.app2"}) - c.Check(seccompSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "\nbind\n") +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.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.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"`) } func (s *BluetoothControlInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/bluez.go snapd-2.28.5/interfaces/builtin/bluez.go --- snapd-2.27.5/interfaces/builtin/bluez.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/bluez.go 2017-09-13 14:47:18.000000000 +0000 @@ -26,6 +26,8 @@ "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/dbus" "github.com/snapcore/snapd/interfaces/seccomp" + "github.com/snapcore/snapd/interfaces/udev" + "github.com/snapcore/snapd/release" ) const bluezSummary = `allows operating as the bluez service` @@ -35,8 +37,10 @@ allow-installation: slot-snap-type: - app - deny-connection: true + - core deny-auto-connection: true + deny-connection: + on-classic: false ` const bluezPermanentSlotAppArmor = ` @@ -192,55 +196,73 @@ ` +const bluezConnectedPlugUDev = `KERNEL=="rfkill", TAG+="###CONNECTED_SECURITY_TAGS###"` + type bluezInterface struct{} func (iface *bluezInterface) Name() string { return "bluez" } -func (iface *bluezInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *bluezInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: bluezSummary, + ImplicitOnClassic: true, BaseDeclarationSlots: bluezBaseDeclarationSlots, } } func (iface *bluezInterface) DBusPermanentSlot(spec *dbus.Specification, slot *interfaces.Slot) error { - spec.AddSnippet(bluezPermanentSlotDBus) + 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 { old := "###SLOT_SECURITY_TAGS###" - new := slotAppLabelExpr(slot) + var new string + if release.OnClassic { + new = "unconfined" + } else { + new = slotAppLabelExpr(slot) + } snippet := strings.Replace(bluezConnectedPlugAppArmor, old, new, -1) spec.AddSnippet(snippet) return nil } func (iface *bluezInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - old := "###PLUG_SECURITY_TAGS###" - new := plugAppLabelExpr(plug) - snippet := strings.Replace(bluezConnectedSlotAppArmor, old, new, -1) - spec.AddSnippet(snippet) - return nil -} - -func (iface *bluezInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { - spec.AddSnippet(bluezPermanentSlotAppArmor) + if !release.OnClassic { + old := "###PLUG_SECURITY_TAGS###" + new := plugAppLabelExpr(plug) + snippet := strings.Replace(bluezConnectedSlotAppArmor, old, new, -1) + spec.AddSnippet(snippet) + } return nil } -func (iface *bluezInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *interfaces.Slot) error { - spec.AddSnippet(bluezPermanentSlotSecComp) +func (iface *bluezInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { + old := "###CONNECTED_SECURITY_TAGS###" + for appName := range plug.Apps { + tag := udevSnapSecurityName(plug.Snap.Name(), appName) + snippet := strings.Replace(bluezConnectedPlugUDev, old, tag, -1) + spec.AddSnippet(snippet) + } return nil } -func (iface *bluezInterface) SanitizePlug(plug *interfaces.Plug) error { +func (iface *bluezInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { + if !release.OnClassic { + spec.AddSnippet(bluezPermanentSlotAppArmor) + } return nil } -func (iface *bluezInterface) SanitizeSlot(slot *interfaces.Slot) error { +func (iface *bluezInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *interfaces.Slot) error { + if !release.OnClassic { + spec.AddSnippet(bluezPermanentSlotSecComp) + } return nil } diff -Nru snapd-2.27.5/interfaces/builtin/bluez_test.go snapd-2.28.5/interfaces/builtin/bluez_test.go --- snapd-2.27.5/interfaces/builtin/bluez_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/bluez_test.go 2017-09-13 14:47:18.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 @@ -27,143 +27,232 @@ "github.com/snapcore/snapd/interfaces/builtin" "github.com/snapcore/snapd/interfaces/dbus" "github.com/snapcore/snapd/interfaces/seccomp" - "github.com/snapcore/snapd/snap" - "github.com/snapcore/snapd/snap/snaptest" + "github.com/snapcore/snapd/interfaces/udev" + "github.com/snapcore/snapd/release" "github.com/snapcore/snapd/testutil" ) type BluezInterfaceSuite struct { - iface interfaces.Interface - slot *interfaces.Slot - plug *interfaces.Plug + iface interfaces.Interface + appSlot *interfaces.Slot + coreSlot *interfaces.Slot + plug *interfaces.Plug } var _ = Suite(&BluezInterfaceSuite{ iface: builtin.MustInterface("bluez"), }) -const bluezMockPlugSnapInfoYaml = `name: other -version: 1.0 +const bluezConsumerYaml = `name: consumer apps: + app: + plugs: [bluez] +` + +const bluezConsumerTwoAppsYaml = `name: consumer +apps: + app1: + plugs: [bluez] app2: - command: foo plugs: [bluez] ` -const bluezMockSlotSnapInfoYaml = `name: bluez -version: 1.0 + +const bluezConsumerThreeAppsYaml = `name: consumer +apps: + app1: + plugs: [bluez] + app2: + plugs: [bluez] + app3: +` + +const bluezProducerYaml = `name: producer +apps: + app: + slots: [bluez] +` + +const bluezProducerTwoAppsYaml = `name: producer apps: app1: - command: foo + slots: [bluez] + app2: slots: [bluez] ` +const bluezProducerThreeAppsYaml = `name: producer +apps: + app1: + slots: [bluez] + app2: + app3: + slots: [bluez] +` + +const bluezCoreYaml = `name: core +slots: + bluez: +` + func (s *BluezInterfaceSuite) SetUpTest(c *C) { - slotSnap := snaptest.MockInfo(c, bluezMockSlotSnapInfoYaml, nil) - s.slot = &interfaces.Slot{SlotInfo: slotSnap.Slots["bluez"]} - plugSnap := snaptest.MockInfo(c, bluezMockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["bluez"]} + s.plug = MockPlug(c, bluezConsumerYaml, nil, "bluez") + s.appSlot = MockSlot(c, bluezProducerYaml, nil, "bluez") + s.coreSlot = MockSlot(c, bluezCoreYaml, nil, "bluez") } func (s *BluezInterfaceSuite) TestName(c *C) { c.Assert(s.iface.Name(), Equals, "bluez") } -// The label glob when all apps are bound to the bluez slot -func (s *BluezInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelAll(c *C) { - app1 := &snap.AppInfo{Name: "app1"} - app2 := &snap.AppInfo{Name: "app2"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "bluez", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - Name: "bluez", - Interface: "bluez", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - } - - apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) - c.Assert(apparmorSpec.SnippetForTag("snap.other.app2"), testutil.Contains, `peer=(label="snap.bluez.*"),`) -} - -// The label uses alternation when some, but not all, apps is bound to the bluez slot -func (s *BluezInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelSome(c *C) { - 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: "bluez", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, - }, - Name: "bluez", - Interface: "bluez", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - } - - apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) - c.Assert(apparmorSpec.SnippetForTag("snap.other.app2"), testutil.Contains, `peer=(label="snap.bluez.{app1,app2}"),`) -} - -// The label uses short form when exactly one app is bound to the bluez slot -func (s *BluezInterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelOne(c *C) { - app := &snap.AppInfo{Name: "app"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "bluez", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - Name: "bluez", - Interface: "bluez", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - } - - apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) - c.Assert(apparmorSpec.SnippetForTag("snap.other.app2"), testutil.Contains, `peer=(label="snap.bluez.app"),`) -} - -func (s *BluezInterfaceSuite) TestConnectedSlotSnippetAppArmor(c *C) { - apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedSlot(s.iface, s.plug, nil, s.slot, nil) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.bluez.app1"}) - c.Assert(apparmorSpec.SnippetForTag("snap.bluez.app1"), testutil.Contains, `peer=(label="snap.other.app2")`) -} - -func (s *BluezInterfaceSuite) TestUsedSecuritySystems(c *C) { - apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) - c.Assert(err, IsNil) - err = apparmorSpec.AddPermanentSlot(s.iface, s.slot) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), HasLen, 2) - - dbusSpec := &dbus.Specification{} - err = dbusSpec.AddPermanentSlot(s.iface, s.slot) - c.Assert(err, IsNil) - c.Assert(dbusSpec.SecurityTags(), DeepEquals, []string{"snap.bluez.app1"}) - c.Assert(dbusSpec.SnippetForTag("snap.bluez.app1"), testutil.Contains, ``) - - seccompSpec := &seccomp.Specification{} - err = seccompSpec.AddPermanentSlot(s.iface, s.slot) - c.Assert(err, IsNil) - c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.bluez.app1"}) - c.Check(seccompSpec.SnippetForTag("snap.bluez.app1"), testutil.Contains, "listen\n") +func (s *BluezInterfaceSuite) TestAppArmorSpec(c *C) { + // on a core system with bluez slot coming from a regular app snap. + restore := release.MockOnClassic(false) + defer restore() + + // 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.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") + spec = &apparmor.Specification{} + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil), 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") + spec = &apparmor.Specification{} + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil), 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.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") + spec = &apparmor.Specification{} + c.Assert(spec.AddConnectedSlot(s.iface, plug, nil, s.appSlot, nil), 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") + spec = &apparmor.Specification{} + c.Assert(spec.AddConnectedSlot(s.iface, plug, nil, s.appSlot, nil), 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.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),`) + + // on a classic system with bluez slot coming from the core snap. + restore = release.MockOnClassic(true) + 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.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)") + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "peer=(label=unconfined),") + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `interface=org.freedesktop.DBus.ObjectManager`) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `interface=org.freedesktop.DBus.*`) + + // connected core slot to plug + spec = &apparmor.Specification{} + c.Assert(spec.AddConnectedSlot(s.iface, s.plug, nil, s.coreSlot, nil), 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.SecurityTags(), HasLen, 0) +} + +func (s *BluezInterfaceSuite) TestDBusSpec(c *C) { + // on a core system with bluez slot coming from a regular app snap. + restore := release.MockOnClassic(false) + defer restore() + + spec := &dbus.Specification{} + c.Assert(spec.AddPermanentSlot(s.iface, s.appSlot), IsNil) + c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.producer.app"}) + c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, ``) + + // on a classic system with bluez slot coming from the core snap. + restore = release.MockOnClassic(true) + defer restore() + + spec = &dbus.Specification{} + c.Assert(spec.AddPermanentSlot(s.iface, s.coreSlot), IsNil) + c.Assert(spec.SecurityTags(), HasLen, 0) +} + +func (s *BluezInterfaceSuite) TestSecCompSpec(c *C) { + // on a core system with bluez slot coming from a regular app snap. + restore := release.MockOnClassic(false) + defer restore() + + spec := &seccomp.Specification{} + c.Assert(spec.AddPermanentSlot(s.iface, s.appSlot), IsNil) + c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.producer.app"}) + c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, "listen\n") + + // on a classic system with bluez slot coming from the core snap. + restore = release.MockOnClassic(true) + defer restore() + + spec = &seccomp.Specification{} + c.Assert(spec.AddPermanentSlot(s.iface, s.coreSlot), IsNil) + c.Assert(spec.SecurityTags(), HasLen, 0) + +} + +func (s *BluezInterfaceSuite) TestUDevSpec(c *C) { + // on a core system with bluez slot coming from a regular app snap. + restore := release.MockOnClassic(false) + 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"`) + + // 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.Snippets()[0], testutil.Contains, `KERNEL=="rfkill", TAG+="snap_consumer_app"`) + +} + +func (s *BluezInterfaceSuite) TestStaticInfo(c *C) { + si := interfaces.StaticInfoOf(s.iface) + c.Assert(si.ImplicitOnCore, Equals, false) + c.Assert(si.ImplicitOnClassic, Equals, true) + c.Assert(si.Summary, Equals, `allows operating as the bluez service`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "bluez") +} + +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) } func (s *BluezInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/bool_file.go snapd-2.28.5/interfaces/builtin/bool_file.go --- snapd-2.27.5/interfaces/builtin/bool_file.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/bool_file.go 2017-09-13 14:47:18.000000000 +0000 @@ -52,8 +52,8 @@ return "bool-file" } -func (iface *boolFileInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *boolFileInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: boolFileSummary, BaseDeclarationSlots: boolFileBaseDeclarationSlots, } @@ -71,9 +71,6 @@ // SanitizeSlot checks and possibly modifies a slot. // Valid "bool-file" slots must contain the attribute "path". func (iface *boolFileInterface) SanitizeSlot(slot *interfaces.Slot) error { - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface)) - } path, ok := slot.Attrs["path"].(string) if !ok || path == "" { return fmt.Errorf("bool-file must contain the path attribute") @@ -87,15 +84,6 @@ return fmt.Errorf("bool-file can only point at LED brightness or GPIO value") } -// SanitizePlug checks and possibly modifies a plug. -func (iface *boolFileInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface)) - } - // NOTE: currently we don't check anything on the plug side. - return nil -} - func (iface *boolFileInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { gpioSnippet := ` /sys/class/gpio/export rw, @@ -151,14 +139,6 @@ return true } -func (iface *boolFileInterface) ValidatePlug(plug *interfaces.Plug, attrs map[string]interface{}) error { - return nil -} - -func (iface *boolFileInterface) ValidateSlot(slot *interfaces.Slot, attrs map[string]interface{}) error { - return nil -} - func init() { registerIface(&boolFileInterface{}) } diff -Nru snapd-2.27.5/interfaces/builtin/bool_file_test.go snapd-2.28.5/interfaces/builtin/bool_file_test.go --- snapd-2.27.5/interfaces/builtin/bool_file_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/bool_file_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -105,33 +105,21 @@ func (s *BoolFileInterfaceSuite) TestSanitizeSlot(c *C) { // Both LED and GPIO slots are accepted - err := s.iface.SanitizeSlot(s.ledSlot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(s.gpioSlot) - c.Assert(err, IsNil) + c.Assert(s.ledSlot.Sanitize(s.iface), IsNil) + c.Assert(s.gpioSlot.Sanitize(s.iface), IsNil) // Slots without the "path" attribute are rejected. - err = s.iface.SanitizeSlot(s.missingPathSlot) - c.Assert(err, ErrorMatches, + c.Assert(s.missingPathSlot.Sanitize(s.iface), ErrorMatches, "bool-file must contain the path attribute") // Slots without the "path" attribute are rejected. - err = s.iface.SanitizeSlot(s.parentDirPathSlot) - c.Assert(err, ErrorMatches, + c.Assert(s.parentDirPathSlot.Sanitize(s.iface), ErrorMatches, "bool-file can only point at LED brightness or GPIO value") // Slots with incorrect value of the "path" attribute are rejected. - err = s.iface.SanitizeSlot(s.badPathSlot) - c.Assert(err, ErrorMatches, + c.Assert(s.badPathSlot.Sanitize(s.iface), ErrorMatches, "bool-file can only point at LED brightness or GPIO value") - // It is impossible to use "bool-file" interface to sanitize slots with other interfaces. - c.Assert(func() { s.iface.SanitizeSlot(s.badInterfaceSlot) }, PanicMatches, - `slot is not of interface "bool-file"`) } func (s *BoolFileInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) - // It is impossible to use "bool-file" interface to sanitize plugs of different interface. - c.Assert(func() { s.iface.SanitizePlug(s.badInterfacePlug) }, PanicMatches, - `plug is not of interface "bool-file"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *BoolFileInterfaceSuite) TestPlugSnippetHandlesSymlinkErrors(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/broadcom_asic_control.go snapd-2.28.5/interfaces/builtin/broadcom_asic_control.go --- snapd-2.27.5/interfaces/builtin/broadcom_asic_control.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/broadcom_asic_control.go 2017-09-13 14:47:18.000000000 +0000 @@ -19,17 +19,6 @@ package builtin -import ( - "fmt" - "strings" - - "github.com/snapcore/snapd/interfaces" - "github.com/snapcore/snapd/interfaces/apparmor" - "github.com/snapcore/snapd/interfaces/kmod" - "github.com/snapcore/snapd/interfaces/udev" - "github.com/snapcore/snapd/snap" -) - const broadcomAsicControlSummary = `allows using the broadcom-asic kernel module` const broadcomAsicControlBaseDeclarationSlots = ` @@ -49,12 +38,20 @@ /dev/linux-user-bde rw, /dev/linux-kernel-bde rw, /dev/linux-bcm-knet rw, + +# 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/bus/pci/devices/ r, +/run/udev/data/+pci:[0-9]* r, ` const broadcomAsicControlConnectedPlugUDev = ` -KERNEL=="linux-user-bde", TAG+="###SLOT_SECURITY_TAGS###" -KERNEL=="linux-kernel-bde", TAG+="###SLOT_SECURITY_TAGS###" -KERNEL=="linux-bcm-knet", TAG+="###SLOT_SECURITY_TAGS###" +SUBSYSTEM=="pci", DRIVER=="linux-kernel-bde", TAG+="###CONNECTED_SECURITY_TAGS###" +SUBSYSTEM=="net", KERNEL=="bcm[0-9]*", TAG+="###CONNECTED_SECURITY_TAGS###" ` // The upstream linux kernel doesn't come with support for the @@ -67,68 +64,16 @@ "linux-bcm-knet", } -type broadcomAsicControlInterface struct{} - -func (iface *broadcomAsicControlInterface) Name() string { - return "broadcom-asic-control" -} - -// MetaData returns various meta-data about this interface. -func (iface *broadcomAsicControlInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ - Summary: broadcomAsicControlSummary, - ImplicitOnCore: true, - ImplicitOnClassic: true, - BaseDeclarationSlots: broadcomAsicControlBaseDeclarationSlots, - } -} - -func (iface *broadcomAsicControlInterface) SanitizeSlot(slot *interfaces.Slot) error { - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface.Name())) - } - if slot.Snap.Type != snap.TypeOS { - return fmt.Errorf("%s slots are reserved for the core snap", iface.Name()) - } - return nil -} - -func (iface *broadcomAsicControlInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface.Name())) - } - // NOTE: currently we don't check anything on the plug side. - return nil -} - -func (iface *broadcomAsicControlInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - spec.AddSnippet(broadcomAsicControlConnectedPlugAppArmor) - return nil -} - -func (iface *broadcomAsicControlInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { - return true -} - -func (iface *broadcomAsicControlInterface) KModConnectedPlug(spec *kmod.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - for _, m := range broadcomAsicControlConnectedPlugKMod { - if err := spec.AddModule(m); err != nil { - return err - } - } - return nil -} - -func (iface *broadcomAsicControlInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - old := "###SLOT_SECURITY_TAGS###" - for appName := range plug.Apps { - tag := udevSnapSecurityName(plug.Snap.Name(), appName) - snippet := strings.Replace(broadcomAsicControlConnectedPlugUDev, old, tag, -1) - spec.AddSnippet(snippet) - } - return nil -} - func init() { - registerIface(&broadcomAsicControlInterface{}) + registerIface(&commonInterface{ + name: "broadcom-asic-control", + summary: broadcomAsicControlSummary, + implicitOnCore: true, + implicitOnClassic: true, + reservedForOS: true, + baseDeclarationSlots: broadcomAsicControlBaseDeclarationSlots, + connectedPlugAppArmor: broadcomAsicControlConnectedPlugAppArmor, + connectedPlugKModModules: broadcomAsicControlConnectedPlugKMod, + connectedPlugUDev: broadcomAsicControlConnectedPlugUDev, + }) } diff -Nru snapd-2.27.5/interfaces/builtin/broadcom_asic_control_test.go snapd-2.28.5/interfaces/builtin/broadcom_asic_control_test.go --- snapd-2.27.5/interfaces/builtin/broadcom_asic_control_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/broadcom_asic_control_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -65,18 +65,18 @@ } func (s *BroadcomAsicControlSuite) TestSanitizeSlot(c *C) { - c.Assert(s.iface.SanitizeSlot(s.slot), IsNil) + c.Assert(s.slot.Sanitize(s.iface), IsNil) slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "broadcom-asic-control", Interface: "broadcom-asic-control", }} - c.Assert(s.iface.SanitizeSlot(slot), ErrorMatches, + c.Assert(slot.Sanitize(s.iface), ErrorMatches, "broadcom-asic-control slots are reserved for the core snap") } func (s *BroadcomAsicControlSuite) TestSanitizePlug(c *C) { - c.Assert(s.iface.SanitizePlug(s.plug), IsNil) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *BroadcomAsicControlSuite) TestAppArmorSpec(c *C) { @@ -89,8 +89,8 @@ func (s *BroadcomAsicControlSuite) TestUDevSpec(c *C) { spec := &udev.Specification{} c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(len(spec.Snippets()), Equals, 1) - c.Assert(spec.Snippets()[0], testutil.Contains, `KERNEL=="linux-user-bde", TAG+="snap_consumer_app"`) + c.Assert(spec.Snippets(), HasLen, 1) + c.Assert(spec.Snippets()[0], testutil.Contains, `SUBSYSTEM=="net", KERNEL=="bcm[0-9]*", TAG+="snap_consumer_app"`) } func (s *BroadcomAsicControlSuite) TestKModSpec(c *C) { @@ -103,12 +103,12 @@ }) } -func (s *BroadcomAsicControlSuite) TestMetaData(c *C) { - mi := interfaces.IfaceMetaData(s.iface) - c.Assert(mi.ImplicitOnCore, Equals, true) - c.Assert(mi.ImplicitOnClassic, Equals, true) - c.Assert(mi.Summary, Equals, "allows using the broadcom-asic kernel module") - c.Assert(mi.BaseDeclarationSlots, testutil.Contains, "broadcom-asic-control") +func (s *BroadcomAsicControlSuite) 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 using the broadcom-asic kernel module") + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "broadcom-asic-control") } func (s *BroadcomAsicControlSuite) TestAutoConnect(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/browser_support.go snapd-2.28.5/interfaces/builtin/browser_support.go --- snapd-2.27.5/interfaces/builtin/browser_support.go 2017-08-24 08:12:46.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/browser_support.go 2017-09-15 15:05:07.000000000 +0000 @@ -140,6 +140,7 @@ /sys/devices/**/product r, /sys/devices/**/revision r, /sys/devices/**/serial r, +/sys/devices/**/vendor r, /sys/devices/system/node/node[0-9]*/meminfo r, # Chromium content api tries to read these. It is an information disclosure @@ -165,16 +166,8 @@ /run/udev/data/b252:[0-9]* r, /run/udev/data/b253:[0-9]* r, /run/udev/data/b259:[0-9]* r, -/run/udev/data/c242:[0-9]* r, -/run/udev/data/c243:[0-9]* r, -/run/udev/data/c245:[0-9]* r, -/run/udev/data/c246:[0-9]* r, -/run/udev/data/c247:[0-9]* r, -/run/udev/data/c248:[0-9]* r, -/run/udev/data/c249:[0-9]* r, -/run/udev/data/c250:[0-9]* r, -/run/udev/data/c251:[0-9]* r, -/run/udev/data/c254:[0-9]* r, +/run/udev/data/c24[2-9]:[0-9]* r, +/run/udev/data/c25[0-4]:[0-9]* r, /sys/bus/**/devices/ r, @@ -262,8 +255,8 @@ return "browser-support" } -func (iface *browserSupportInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *browserSupportInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: browserSupportSummary, ImplicitOnCore: true, ImplicitOnClassic: true, @@ -271,15 +264,7 @@ } } -func (iface *browserSupportInterface) SanitizeSlot(slot *interfaces.Slot) error { - return nil -} - func (iface *browserSupportInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface.Name())) - } - // 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 { @@ -316,14 +301,6 @@ return true } -func (iface *browserSupportInterface) ValidatePlug(plug *interfaces.Plug, attrs map[string]interface{}) error { - return nil -} - -func (iface *browserSupportInterface) ValidateSlot(slot *interfaces.Slot, attrs map[string]interface{}) error { - return nil -} - func init() { registerIface(&browserSupportInterface{}) } diff -Nru snapd-2.27.5/interfaces/builtin/browser_support_test.go snapd-2.28.5/interfaces/builtin/browser_support_test.go --- snapd-2.27.5/interfaces/builtin/browser_support_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/browser_support_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -66,13 +66,11 @@ } func (s *BrowserSupportInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) + c.Assert(s.slot.Sanitize(s.iface), IsNil) } func (s *BrowserSupportInterfaceSuite) TestSanitizePlugNoAttrib(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *BrowserSupportInterfaceSuite) TestSanitizePlugWithAttrib(c *C) { @@ -83,10 +81,8 @@ allow-sandbox: true ` info := snaptest.MockInfo(c, mockSnapYaml, nil) - plug := &interfaces.Plug{PlugInfo: info.Plugs["browser-support"]} - err := s.iface.SanitizePlug(plug) - c.Assert(err, IsNil) + c.Assert(plug.Sanitize(s.iface), IsNil) } func (s *BrowserSupportInterfaceSuite) TestSanitizePlugWithBadAttrib(c *C) { @@ -97,11 +93,9 @@ allow-sandbox: bad ` info := snaptest.MockInfo(c, mockSnapYaml, nil) - plug := &interfaces.Plug{PlugInfo: info.Plugs["browser-support"]} - err := s.iface.SanitizePlug(plug) - c.Assert(err, Not(IsNil)) - c.Assert(err, ErrorMatches, "browser-support plug requires bool with 'allow-sandbox'") + c.Assert(plug.Sanitize(s.iface), ErrorMatches, + "browser-support plug requires bool with 'allow-sandbox'") } func (s *BrowserSupportInterfaceSuite) TestConnectedPlugSnippetWithoutAttrib(c *C) { @@ -188,11 +182,6 @@ c.Assert(secCompSnippet, testutil.Contains, `chroot`) } -func (s *BrowserSupportInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "browser-support"`) -} - func (s *BrowserSupportInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected plugs have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} diff -Nru snapd-2.27.5/interfaces/builtin/camera.go snapd-2.28.5/interfaces/builtin/camera.go --- snapd-2.27.5/interfaces/builtin/camera.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/camera.go 2017-09-13 14:47:18.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 @@ -42,6 +42,8 @@ /sys/devices/pci**/usb*/**/video4linux/** r, ` +const cameraConnectedPlugUDev = `KERNEL=="video[0-9]*", TAG+="###CONNECTED_SECURITY_TAGS###"` + func init() { registerIface(&commonInterface{ name: "camera", @@ -50,6 +52,7 @@ implicitOnClassic: true, baseDeclarationSlots: cameraBaseDeclarationSlots, connectedPlugAppArmor: cameraConnectedPlugAppArmor, + connectedPlugUDev: cameraConnectedPlugUDev, reservedForOS: true, }) } diff -Nru snapd-2.27.5/interfaces/builtin/camera_test.go snapd-2.28.5/interfaces/builtin/camera_test.go --- snapd-2.27.5/interfaces/builtin/camera_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/camera_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,107 @@ +// -*- 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 CameraInterfaceSuite struct { + iface interfaces.Interface + slot *interfaces.Slot + plug *interfaces.Plug +} + +var _ = Suite(&CameraInterfaceSuite{ + iface: builtin.MustInterface("camera"), +}) + +const cameraConsumerYaml = `name: consumer +apps: + app: + plugs: [camera] +` + +const cameraCoreYaml = `name: core +type: os +slots: + camera: +` + +func (s *CameraInterfaceSuite) SetUpTest(c *C) { + s.plug = MockPlug(c, cameraConsumerYaml, nil, "camera") + s.slot = MockSlot(c, cameraCoreYaml, nil, "camera") +} + +func (s *CameraInterfaceSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "camera") +} + +func (s *CameraInterfaceSuite) TestSanitizeSlot(c *C) { + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "some-snap"}, + Name: "camera", + Interface: "camera", + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "camera slots are reserved for the core snap") +} + +func (s *CameraInterfaceSuite) TestSanitizePlug(c *C) { + c.Assert(s.plug.Sanitize(s.iface), 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.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"`) +} + +func (s *CameraInterfaceSuite) TestStaticInfo(c *C) { + si := interfaces.StaticInfoOf(s.iface) + c.Assert(si.ImplicitOnCore, Equals, true) + c.Assert(si.ImplicitOnClassic, Equals, true) + c.Assert(si.Summary, Equals, `allows access to all cameras`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "camera") +} + +func (s *CameraInterfaceSuite) TestAutoConnect(c *C) { + c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) +} + +func (s *CameraInterfaceSuite) TestInterfaces(c *C) { + c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) +} diff -Nru snapd-2.27.5/interfaces/builtin/classic_support_test.go snapd-2.28.5/interfaces/builtin/classic_support_test.go --- snapd-2.27.5/interfaces/builtin/classic_support_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/classic_support_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -66,20 +66,11 @@ } func (s *ClassicSupportInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) + c.Assert(s.slot.Sanitize(s.iface), IsNil) } func (s *ClassicSupportInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *ClassicSupportInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "classic-support"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "classic-support"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *ClassicSupportInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/common.go snapd-2.28.5/interfaces/builtin/common.go --- snapd-2.27.5/interfaces/builtin/common.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/common.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,14 +20,14 @@ package builtin import ( - "fmt" "path/filepath" + "strings" "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/kmod" "github.com/snapcore/snapd/interfaces/seccomp" - "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/interfaces/udev" ) type evalSymlinksFn func(string) (string, error) @@ -37,10 +37,9 @@ var evalSymlinks = filepath.EvalSymlinks type commonInterface struct { - name string - summary string - description string - documentationURL string + name string + summary string + docURL string implicitOnCore bool implicitOnClassic bool @@ -50,6 +49,7 @@ connectedPlugAppArmor string connectedPlugSecComp string + connectedPlugUDev string reservedForOS bool rejectAutoConnectPairs bool @@ -64,12 +64,11 @@ return iface.name } -// MetaData returns various meta-data about this interface. -func (iface *commonInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +// StaticInfo returns various meta-data about this interface. +func (iface *commonInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: iface.summary, - Description: iface.description, - DocumentationURL: iface.documentationURL, + DocURL: iface.docURL, ImplicitOnCore: iface.implicitOnCore, ImplicitOnClassic: iface.implicitOnClassic, BaseDeclarationPlugs: iface.baseDeclarationPlugs, @@ -82,21 +81,9 @@ // If the reservedForOS flag is set then only slots on core snap // are allowed. func (iface *commonInterface) SanitizeSlot(slot *interfaces.Slot) error { - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface.Name())) + if iface.reservedForOS { + return sanitizeSlotReservedForOS(iface, slot) } - if iface.reservedForOS && slot.Snap.Type != snap.TypeOS { - return fmt.Errorf("%s slots are reserved for the operating system snap", iface.name) - } - return nil -} - -// SanitizePlug checks and possibly modifies a plug. -func (iface *commonInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface.Name())) - } - // NOTE: currently we don't check anything on the plug side. return nil } @@ -158,3 +145,15 @@ } return nil } + +func (iface *commonInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { + old := "###CONNECTED_SECURITY_TAGS###" + if iface.connectedPlugUDev != "" { + for appName := range plug.Apps { + tag := udevSnapSecurityName(plug.Snap.Name(), appName) + snippet := strings.Replace(iface.connectedPlugUDev, old, tag, -1) + spec.AddSnippet(snippet) + } + } + return nil +} diff -Nru snapd-2.27.5/interfaces/builtin/common_test.go snapd-2.28.5/interfaces/builtin/common_test.go --- snapd-2.27.5/interfaces/builtin/common_test.go 2016-06-01 20:08:33.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/common_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,9 +20,54 @@ package builtin import ( + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/interfaces/udev" "github.com/snapcore/snapd/testutil" ) +type commonIfaceSuite struct{} + +var _ = Suite(&commonIfaceSuite{}) + +func (s *commonIfaceSuite) TestUDevSpec(c *C) { + plug := MockPlug(c, ` +name: consumer +apps: + app-a: + plugs: [common] + app-b: + app-c: + plugs: [common] +`, nil, "common") + slot := MockSlot(c, ` +name: producer +slots: + common: +`, nil, "common") + + // common interface can define connected plug udev rules + iface := &commonInterface{ + name: "common", + connectedPlugUDev: `KERNEL="foo", TAG+="###CONNECTED_SECURITY_TAGS###"`, + } + spec := &udev.Specification{} + c.Assert(spec.AddConnectedPlug(iface, plug, nil, slot, nil), IsNil) + c.Assert(spec.Snippets(), DeepEquals, []string{ + `KERNEL="foo", TAG+="snap_consumer_app-a"`, + // NOTE: app-b is unaffected as it doesn't have a plug reference. + `KERNEL="foo", TAG+="snap_consumer_app-c"`, + }) + + // connected plug udev rules are optional + iface = &commonInterface{ + name: "common", + } + spec = &udev.Specification{} + c.Assert(spec.AddConnectedPlug(iface, plug, nil, slot, nil), IsNil) + c.Assert(spec.Snippets(), HasLen, 0) +} + // MockEvalSymlinks replaces the path/filepath.EvalSymlinks function used inside the caps package. func MockEvalSymlinks(test *testutil.BaseTest, fn func(string) (string, error)) { orig := evalSymlinks diff -Nru snapd-2.27.5/interfaces/builtin/content.go snapd-2.28.5/interfaces/builtin/content.go --- snapd-2.27.5/interfaces/builtin/content.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/content.go 2017-09-13 14:47:18.000000000 +0000 @@ -57,8 +57,8 @@ return "content" } -func (iface *contentInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *contentInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: contentSummary, BaseDeclarationSlots: contentBaseDeclarationSlots, } @@ -69,9 +69,6 @@ } func (iface *contentInterface) SanitizeSlot(slot *interfaces.Slot) error { - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface)) - } content, ok := slot.Attrs["content"].(string) if !ok || len(content) == 0 { if slot.Attrs == nil { @@ -101,9 +98,6 @@ } func (iface *contentInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface)) - } content, ok := plug.Attrs["content"].(string) if !ok || len(content) == 0 { if plug.Attrs == nil { @@ -235,14 +229,6 @@ return nil } -func (iface *contentInterface) ValidatePlug(plug *interfaces.Plug, attrs map[string]interface{}) error { - return nil -} - -func (iface *contentInterface) ValidateSlot(slot *interfaces.Slot, attrs map[string]interface{}) error { - return nil -} - func init() { registerIface(&contentInterface{}) } diff -Nru snapd-2.27.5/interfaces/builtin/content_test.go snapd-2.28.5/interfaces/builtin/content_test.go --- snapd-2.27.5/interfaces/builtin/content_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/content_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -59,8 +59,7 @@ ` info := snaptest.MockInfo(c, mockSnapYaml, nil) slot := &interfaces.Slot{SlotInfo: info.Slots["content-slot"]} - err := s.iface.SanitizeSlot(slot) - c.Assert(err, IsNil) + c.Assert(slot.Sanitize(s.iface), IsNil) } func (s *ContentSuite) TestSanitizeSlotContentLabelDefault(c *C) { @@ -74,8 +73,7 @@ ` info := snaptest.MockInfo(c, mockSnapYaml, nil) slot := &interfaces.Slot{SlotInfo: info.Slots["content-slot"]} - err := s.iface.SanitizeSlot(slot) - c.Assert(err, IsNil) + c.Assert(slot.Sanitize(s.iface), IsNil) c.Assert(slot.Attrs["content"], Equals, slot.Name) } @@ -89,8 +87,7 @@ ` info := snaptest.MockInfo(c, mockSnapYaml, nil) slot := &interfaces.Slot{SlotInfo: info.Slots["content-slot"]} - err := s.iface.SanitizeSlot(slot) - c.Assert(err, ErrorMatches, "read or write path must be set") + c.Assert(slot.Sanitize(s.iface), ErrorMatches, "read or write path must be set") } func (s *ContentSuite) TestSanitizeSlotEmptyPaths(c *C) { @@ -105,8 +102,7 @@ ` info := snaptest.MockInfo(c, mockSnapYaml, nil) slot := &interfaces.Slot{SlotInfo: info.Slots["content-slot"]} - err := s.iface.SanitizeSlot(slot) - c.Assert(err, ErrorMatches, "read or write path must be set") + c.Assert(slot.Sanitize(s.iface), ErrorMatches, "read or write path must be set") } func (s *ContentSuite) TestSanitizeSlotHasRelativePath(c *C) { @@ -120,8 +116,7 @@ for _, rw := range []string{"read: [../foo]", "write: [../bar]"} { info := snaptest.MockInfo(c, mockSnapYaml+" "+rw, nil) slot := &interfaces.Slot{SlotInfo: info.Slots["content-slot"]} - err := s.iface.SanitizeSlot(slot) - c.Assert(err, ErrorMatches, "content interface path is not clean:.*") + c.Assert(slot.Sanitize(s.iface), ErrorMatches, "content interface path is not clean:.*") } } @@ -136,8 +131,7 @@ ` info := snaptest.MockInfo(c, mockSnapYaml, nil) plug := &interfaces.Plug{PlugInfo: info.Plugs["content-plug"]} - err := s.iface.SanitizePlug(plug) - c.Assert(err, IsNil) + c.Assert(plug.Sanitize(s.iface), IsNil) } func (s *ContentSuite) TestSanitizePlugContentLabelDefault(c *C) { @@ -150,8 +144,7 @@ ` info := snaptest.MockInfo(c, mockSnapYaml, nil) plug := &interfaces.Plug{PlugInfo: info.Plugs["content-plug"]} - err := s.iface.SanitizePlug(plug) - c.Assert(err, IsNil) + c.Assert(plug.Sanitize(s.iface), IsNil) c.Assert(plug.Attrs["content"], Equals, plug.Name) } @@ -165,8 +158,7 @@ ` info := snaptest.MockInfo(c, mockSnapYaml, nil) plug := &interfaces.Plug{PlugInfo: info.Plugs["content-plug"]} - err := s.iface.SanitizePlug(plug) - c.Assert(err, ErrorMatches, "content plug must contain target path") + c.Assert(plug.Sanitize(s.iface), ErrorMatches, "content plug must contain target path") } func (s *ContentSuite) TestSanitizePlugSimpleTargetRelative(c *C) { @@ -180,8 +172,7 @@ ` info := snaptest.MockInfo(c, mockSnapYaml, nil) plug := &interfaces.Plug{PlugInfo: info.Plugs["content-plug"]} - err := s.iface.SanitizePlug(plug) - c.Assert(err, ErrorMatches, "content interface target path is not clean:.*") + c.Assert(plug.Sanitize(s.iface), ErrorMatches, "content interface target path is not clean:.*") } func (s *ContentSuite) TestSanitizePlugNilAttrMap(c *C) { @@ -194,8 +185,7 @@ ` info := snaptest.MockInfo(c, mockSnapYaml, nil) plug := &interfaces.Plug{PlugInfo: info.Plugs["content"]} - err := s.iface.SanitizePlug(plug) - c.Assert(err, ErrorMatches, "content plug must contain target path") + c.Assert(plug.Sanitize(s.iface), ErrorMatches, "content plug must contain target path") } func (s *ContentSuite) TestSanitizeSlotNilAttrMap(c *C) { @@ -208,8 +198,7 @@ ` info := snaptest.MockInfo(c, mockSnapYaml, nil) slot := &interfaces.Slot{SlotInfo: info.Slots["content"]} - err := s.iface.SanitizeSlot(slot) - c.Assert(err, ErrorMatches, "read or write path must be set") + c.Assert(slot.Sanitize(s.iface), ErrorMatches, "read or write path must be set") } func (s *ContentSuite) TestResolveSpecialVariable(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/core_support.go snapd-2.28.5/interfaces/builtin/core_support.go --- snapd-2.27.5/interfaces/builtin/core_support.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/core_support.go 2017-09-13 14:47:18.000000000 +0000 @@ -45,6 +45,8 @@ /bin/systemctl Uxr, +/usr/bin/snapctl ixr, + # Allow modifying rsyslog configuration for such things as remote logging. For # now, only allow modifying NN-snap*.conf and snap*.conf files. /etc/rsyslog.d/{,*} r, @@ -69,10 +71,10 @@ # in /etc/systemd/logind.conf.d. Also allow creating the logind.conf.d # directory as it may not be there for existing installs (wirtable-path # magic oddness). -/etc/systemd/logind.conf r, -/etc/systemd/logind.conf.d/ rw, -/etc/systemd/logind.conf.d/{,*} r, -/etc/systemd/logind.conf.d/{,[0-9][0-9]-}snap*.conf w, +/etc/systemd/logind.conf r, +/etc/systemd/logind.conf.d/ rw, +/etc/systemd/logind.conf.d/{,*} r, +/etc/systemd/logind.conf.d/{,[0-9][0-9]-}snap*.conf* w, # Allow managing the hostname with a core config option /etc/hostname rw, @@ -85,10 +87,18 @@ # the core snap, general mgmt of the service is handled via systemctl /etc/default/swapfile rw, -# Allow read/write access to the pi2 boot config.txt. WARNING: improperly -# editing this file may render the system unbootable. +# Allow read/write access to the pi2 boot config.txt and the directory +# so that it can dirsync it. +# WARNING: improperly editing this file may render the system unbootable. +owner /boot/uboot/ r, owner /boot/uboot/config.txt rwk, -owner /boot/uboot/config.txt.tmp rwk, +owner /boot/uboot/config.txt.* rwk, + +# Allow read/write /etc/environment so that proxy configuration can +# be written +owner /etc/ r, +owner /etc/environment rwk, +owner /etc/environment.* rwk, ` func init() { diff -Nru snapd-2.27.5/interfaces/builtin/core_support_test.go snapd-2.28.5/interfaces/builtin/core_support_test.go --- snapd-2.27.5/interfaces/builtin/core_support_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/core_support_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -64,26 +64,18 @@ } func (s *CoreSupportInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "core-support", Interface: "core-support", - }}) - c.Assert(err, ErrorMatches, "core-support slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "core-support slots are reserved for the core snap") } func (s *CoreSupportInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *CoreSupportInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "core-support"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "core-support"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *CoreSupportInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/dbus.go snapd-2.28.5/interfaces/builtin/dbus.go --- snapd-2.27.5/interfaces/builtin/dbus.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/dbus.go 2017-09-13 14:47:18.000000000 +0000 @@ -204,8 +204,8 @@ return "dbus" } -func (iface *dbusInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *dbusInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: dbusSummary, BaseDeclarationSlots: dbusBaseDeclarationSlots, } @@ -404,19 +404,11 @@ } func (iface *dbusInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface)) - } - _, _, err := iface.getAttribs(plug.Attrs) return err } func (iface *dbusInterface) SanitizeSlot(slot *interfaces.Slot) error { - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface)) - } - _, _, err := iface.getAttribs(slot.Attrs) return err } @@ -426,14 +418,6 @@ return true } -func (iface *dbusInterface) ValidatePlug(plug *interfaces.Plug, attrs map[string]interface{}) error { - return nil -} - -func (iface *dbusInterface) ValidateSlot(slot *interfaces.Slot, attrs map[string]interface{}) error { - return nil -} - func init() { registerIface(&dbusInterface{}) } diff -Nru snapd-2.27.5/interfaces/builtin/dbus_test.go snapd-2.28.5/interfaces/builtin/dbus_test.go --- snapd-2.27.5/interfaces/builtin/dbus_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/dbus_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -138,8 +138,7 @@ c.Assert(err, IsNil) slot := &interfaces.Slot{SlotInfo: info.Slots["dbus-slot"]} - err = s.iface.SanitizeSlot(slot) - c.Assert(err, IsNil) + c.Assert(slot.Sanitize(s.iface), IsNil) } func (s *DbusInterfaceSuite) TestValidSystemBusName(c *C) { @@ -156,8 +155,7 @@ c.Assert(err, IsNil) slot := &interfaces.Slot{SlotInfo: info.Slots["dbus-slot"]} - err = s.iface.SanitizeSlot(slot) - c.Assert(err, IsNil) + c.Assert(slot.Sanitize(s.iface), IsNil) } func (s *DbusInterfaceSuite) TestValidFullBusName(c *C) { @@ -174,8 +172,7 @@ c.Assert(err, IsNil) slot := &interfaces.Slot{SlotInfo: info.Slots["dbus-slot"]} - err = s.iface.SanitizeSlot(slot) - c.Assert(err, IsNil) + c.Assert(slot.Sanitize(s.iface), IsNil) } func (s *DbusInterfaceSuite) TestNonexistentBusName(c *C) { @@ -192,9 +189,7 @@ c.Assert(err, IsNil) slot := &interfaces.Slot{SlotInfo: info.Slots["dbus-slot"]} - err = s.iface.SanitizeSlot(slot) - c.Assert(err, Not(IsNil)) - c.Assert(err, ErrorMatches, "bus 'nonexistent' must be one of 'session' or 'system'") + c.Assert(slot.Sanitize(s.iface), 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 @@ -213,9 +208,7 @@ c.Assert(err, IsNil) slot := &interfaces.Slot{SlotInfo: info.Slots["dbus-slot"]} - err = s.iface.SanitizeSlot(slot) - c.Assert(err, Not(IsNil)) - c.Assert(err, ErrorMatches, "DBus bus name must not end with -NUMBER") + c.Assert(slot.Sanitize(s.iface), ErrorMatches, "DBus bus name must not end with -NUMBER") } func (s *DbusInterfaceSuite) TestSanitizeSlotSystem(c *C) { @@ -232,8 +225,7 @@ c.Assert(err, IsNil) slot := &interfaces.Slot{SlotInfo: info.Slots["dbus-slot"]} - err = s.iface.SanitizeSlot(slot) - c.Assert(err, IsNil) + c.Assert(slot.Sanitize(s.iface), IsNil) } func (s *DbusInterfaceSuite) TestSanitizeSlotSession(c *C) { @@ -250,8 +242,7 @@ c.Assert(err, IsNil) slot := &interfaces.Slot{SlotInfo: info.Slots["dbus-slot"]} - err = s.iface.SanitizeSlot(slot) - c.Assert(err, IsNil) + c.Assert(slot.Sanitize(s.iface), IsNil) } func (s *DbusInterfaceSuite) TestSanitizePlugSystem(c *C) { @@ -268,8 +259,7 @@ c.Assert(err, IsNil) plug := &interfaces.Plug{PlugInfo: info.Plugs["dbus-plug"]} - err = s.iface.SanitizePlug(plug) - c.Assert(err, IsNil) + c.Assert(plug.Sanitize(s.iface), IsNil) } func (s *DbusInterfaceSuite) TestSanitizePlugSession(c *C) { @@ -286,8 +276,7 @@ c.Assert(err, IsNil) plug := &interfaces.Plug{PlugInfo: info.Plugs["dbus-plug"]} - err = s.iface.SanitizePlug(plug) - c.Assert(err, IsNil) + c.Assert(plug.Sanitize(s.iface), IsNil) } func (s *DbusInterfaceSuite) TestPermanentSlotAppArmorSession(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/dcdbas_control_test.go snapd-2.28.5/interfaces/builtin/dcdbas_control_test.go --- snapd-2.27.5/interfaces/builtin/dcdbas_control_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/dcdbas_control_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -63,26 +63,18 @@ } func (s *DcdbasControlInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "dcdbas-control", Interface: "dcdbas-control", - }}) - c.Assert(err, ErrorMatches, "dcdbas-control slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "dcdbas-control slots are reserved for the core snap") } func (s *DcdbasControlInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *DcdbasControlInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "dcdbas-control"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "dcdbas-control"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *DcdbasControlInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/desktop.go snapd-2.28.5/interfaces/builtin/desktop.go --- snapd-2.27.5/interfaces/builtin/desktop.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/desktop.go 2017-09-15 15:05:07.000000000 +0000 @@ -0,0 +1,137 @@ +// -*- 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 desktopSummary = `allows access to basic graphical desktop resources` + +const desktopBaseDeclarationSlots = ` + desktop: + allow-installation: + slot-snap-type: + - core +` + +const desktopConnectedPlugAppArmor = ` +# Description: Can access basic graphical desktop resources. To be used with +# other interfaces (eg, wayland). + +#include +#include + +#include +/var/cache/fontconfig/ r, +/var/cache/fontconfig/** mr, + +# subset of gnome abstraction +/etc/gtk-3.0/settings.ini r, +owner @{HOME}/.config/gtk-3.0/settings.ini r, +# Note: this leaks directory names that wouldn't otherwise be known to the snap +owner @{HOME}/.config/gtk-3.0/bookmarks r, + +/usr/share/icons/ r, +/usr/share/icons/** r, +/usr/share/icons/*/index.theme rk, +/usr/share/pixmaps/ r, +/usr/share/pixmaps/** r, +/usr/share/unity/icons/** r, +/usr/share/thumbnailer/icons/** r, +/usr/share/themes/** r, + +# The snapcraft desktop part may look for schema files in various locations, so +# allow reading system installed schemas. +/usr/share/glib*/schemas/{,*} r, +/usr/share/gnome/glib*/schemas/{,*} r, +/usr/share/ubuntu/glib*/schemas/{,*} r, + +# subset of freedesktop.org +owner @{HOME}/.local/share/mime/** r, +owner @{HOME}/.config/user-dirs.dirs r, + +# gmenu +dbus (send) + bus=session + interface=org.gtk.Actions + member=Changed + peer=(name=org.freedesktop.DBus, label=unconfined), + +# notifications +dbus (send) + bus=session + path=/org/freedesktop/Notifications + interface=org.freedesktop.Notifications + member="{GetCapabilities,GetServerInformation,Notify}" + peer=(label=unconfined), + +dbus (receive) + bus=session + path=/org/freedesktop/Notifications + interface=org.freedesktop.Notifications + member=NotificationClosed + peer=(label=unconfined), + +# Allow requesting interest in receiving media key events. This tells Gnome +# settings that our application should be notified when key events we are +# interested in are pressed, and allows us to receive those events. +dbus (receive, send) + bus=session + interface=org.gnome.SettingsDaemon.MediaKeys + path=/org/gnome/SettingsDaemon/MediaKeys + peer=(label=unconfined), +dbus (send) + bus=session + interface=org.freedesktop.DBus.Properties + path=/org/gnome/SettingsDaemon/MediaKeys + member="Get{,All}" + peer=(label=unconfined), + +# Allow use of snapd's internal 'xdg-open' +/usr/bin/xdg-open ixr, +/usr/share/applications/{,*} r, +/usr/bin/dbus-send ixr, +dbus (send) + bus=session + path=/ + interface=com.canonical.SafeLauncher + member=OpenURL + peer=(label=unconfined), +# ... and this allows access to the new xdg-open service which +# is now part of snapd itself. +dbus (send) + bus=session + path=/io/snapcraft/Launcher + interface=io.snapcraft.Launcher + member=OpenURL + peer=(label=unconfined), + +# Lttng tracing is very noisy and should not be allowed by confined apps. Can +# safely deny. LP: #1260491 +deny /{dev,run,var/run}/shm/lttng-ust-* rw, +` + +func init() { + registerIface(&commonInterface{ + name: "desktop", + summary: desktopSummary, + implicitOnClassic: true, + baseDeclarationSlots: desktopBaseDeclarationSlots, + connectedPlugAppArmor: desktopConnectedPlugAppArmor, + reservedForOS: true, + }) +} diff -Nru snapd-2.27.5/interfaces/builtin/desktop_legacy.go snapd-2.28.5/interfaces/builtin/desktop_legacy.go --- snapd-2.27.5/interfaces/builtin/desktop_legacy.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/desktop_legacy.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,236 @@ +// -*- 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 desktopLegacySummary = `allows privileged access to desktop legacy methods` + +// While this gives privileged access to legacy methods we should auto-connect +// this transitional interface since most desktop applications will need it. +// When safe alternative methods are added to the desktop interface by default, +// we can consider making this manually connected. +const desktopLegacyBaseDeclarationSlots = ` + desktop-legacy: + allow-installation: + slot-snap-type: + - core +` + +const desktopLegacyConnectedPlugAppArmor = ` +# Description: Can access common desktop legacy methods. This gives privileged +# access to the user's input. + +# accessibility (a11y) +#include +dbus (send) + bus=session + path=/org/a11y/bus + interface=org.a11y.Bus + member=GetAddress + peer=(label=unconfined), + +#include + +# Allow the accessibility services in the user session to send us any events +dbus (receive) + bus=accessibility + peer=(label=unconfined), + +# Allow querying for capabilities and registering +dbus (send) + bus=accessibility + path="/org/a11y/atspi/accessible/root" + interface="org.a11y.atspi.Socket" + member="Embed" + peer=(name=org.a11y.atspi.Registry, label=unconfined), +dbus (send) + bus=accessibility + path="/org/a11y/atspi/registry" + interface="org.a11y.atspi.Registry" + member="GetRegisteredEvents" + peer=(name=org.a11y.atspi.Registry, label=unconfined), +dbus (send) + bus=accessibility + path="/org/a11y/atspi/registry/deviceeventcontroller" + interface="org.a11y.atspi.DeviceEventController" + member="Get{DeviceEvent,Keystroke}Listeners" + peer=(name=org.a11y.atspi.Registry, label=unconfined), +dbus (send) + bus=accessibility + path="/org/a11y/atspi/registry/deviceeventcontroller" + interface="org.a11y.atspi.DeviceEventController" + member="NotifyListenersSync" + peer=(name=org.a11y.atspi.Registry, label=unconfined), + +# org.a11y.atspi is not designed for application isolation and these rules +# can be used to send change events for other processes. +dbus (send) + bus=accessibility + path="/org/a11y/atspi/accessible/root" + interface="org.a11y.atspi.Event.Object" + member="ChildrenChanged" + peer=(name=org.freedesktop.DBus, label=unconfined), +dbus (send) + bus=accessibility + path="/org/a11y/atspi/accessible/root" + interface="org.a11y.atspi.Accessible" + member="Get*" + peer=(label=unconfined), +dbus (send) + bus=accessibility + path="/org/a11y/atspi/accessible/[0-9]*" + interface="org.a11y.atspi.Event.Object" + member="{ChildrenChanged,PropertyChange,StateChanged,TextCaretMoved}" + peer=(name=org.freedesktop.DBus, label=unconfined), +dbus (send) + bus=accessibility + path="/org/a11y/atspi/accessible/[0-9]*" + interface="org.freedesktop.DBus.Properties" + member="Get{,All}" + peer=(label=unconfined), + +dbus (send) + bus=accessibility + path="/org/a11y/atspi/cache" + interface="org.a11y.atspi.Cache" + member="{Add,Remove}Accessible" + peer=(name=org.freedesktop.DBus, label=unconfined), + + +# ibus +# subset of ibus abstraction +/usr/lib/@{multiarch}/gtk-2.0/[0-9]*/immodules/im-ibus.so mr, +owner @{HOME}/.config/ibus/ r, +owner @{HOME}/.config/ibus/bus/ r, +owner @{HOME}/.config/ibus/bus/* r, + +# allow communicating with ibus-daemon (this allows sniffing key events) +unix (connect, receive, send) + type=stream + peer=(addr="@/tmp/ibus/dbus-*"), + + +# mozc +# allow communicating with mozc server +unix (connect, receive, send) + type=stream + peer=(addr="@tmp/.mozc.*"), + + +# fcitx +# allow communicating with fcitx dbus service +dbus send + bus=fcitx + path=/org/freedesktop/DBus + interface=org.freedesktop.DBus + member={Hello,AddMatch,RemoveMatch,GetNameOwner,NameHasOwner,StartServiceByName} + peer=(name=org.freedesktop.DBus), + +owner @{HOME}/.config/fcitx/dbus/* r, + +# allow creating an input context +dbus send + bus={fcitx,session} + path=/inputmethod + interface=org.fcitx.Fcitx.InputMethod + member=CreateIC* + peer=(label=unconfined), + +# allow setting up and tearing down the input context +dbus send + bus={fcitx,session} + path=/inputcontext_[0-9]* + interface=org.fcitx.Fcitx.InputContext + member="{Close,Destroy,Enable}IC" + peer=(label=unconfined), + +dbus send + bus={fcitx,session} + path=/inputcontext_[0-9]* + interface=org.fcitx.Fcitx.InputContext + member=Reset + peer=(label=unconfined), + +# allow service to send us signals +dbus receive + bus=fcitx + peer=(label=unconfined), + +dbus receive + bus=session + interface=org.fcitx.Fcitx.* + peer=(label=unconfined), + +# use the input context +dbus send + bus={fcitx,session} + path=/inputcontext_[0-9]* + interface=org.fcitx.Fcitx.InputContext + member="Focus{In,Out}" + peer=(label=unconfined), + +dbus send + bus={fcitx,session} + path=/inputcontext_[0-9]* + interface=org.fcitx.Fcitx.InputContext + member="{CommitPreedit,Set*}" + peer=(label=unconfined), + +# this is an information leak and allows key and mouse sniffing. If the input +# context path were tied to the process' security label, this would not be an +# issue. +dbus send + bus={fcitx,session} + path=/inputcontext_[0-9]* + interface=org.fcitx.Fcitx.InputContext + member="{MouseEvent,ProcessKeyEvent}" + peer=(label=unconfined), + +# this method does not exist with the sunpinyin backend (at least), so allow +# it for other input methods. This may consitute an information leak (which, +# again, could be avoided if the path were tied to the process' security +# label). +dbus send + bus={fcitx,session} + path=/inputcontext_[0-9]* + interface=org.freedesktop.DBus.Properties + member=GetAll + peer=(label=unconfined), +` + +const desktopLegacyConnectedPlugSecComp = ` +# Description: Can access common desktop legacy methods. This gives privileged +# access to the user's input. + +listen +accept +accept4 +` + +func init() { + registerIface(&commonInterface{ + name: "desktop-legacy", + summary: desktopLegacySummary, + implicitOnClassic: true, + baseDeclarationSlots: desktopLegacyBaseDeclarationSlots, + connectedPlugAppArmor: desktopLegacyConnectedPlugAppArmor, + connectedPlugSecComp: desktopLegacyConnectedPlugSecComp, + reservedForOS: true, + }) +} diff -Nru snapd-2.27.5/interfaces/builtin/desktop_legacy_test.go snapd-2.28.5/interfaces/builtin/desktop_legacy_test.go --- snapd-2.27.5/interfaces/builtin/desktop_legacy_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/desktop_legacy_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,104 @@ +// -*- 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 DesktopLegacyInterfaceSuite struct { + iface interfaces.Interface + coreSlot *interfaces.Slot + plug *interfaces.Plug +} + +var _ = Suite(&DesktopLegacyInterfaceSuite{ + iface: builtin.MustInterface("desktop-legacy"), +}) + +const desktopLegacyConsumerYaml = `name: consumer +apps: + app: + plugs: [desktop-legacy] +` + +const desktopLegacyCoreYaml = `name: core +type: os +slots: + desktop-legacy: +` + +func (s *DesktopLegacyInterfaceSuite) SetUpTest(c *C) { + s.plug = MockPlug(c, desktopLegacyConsumerYaml, nil, "desktop-legacy") + s.coreSlot = MockSlot(c, desktopLegacyCoreYaml, nil, "desktop-legacy") +} + +func (s *DesktopLegacyInterfaceSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "desktop-legacy") +} + +func (s *DesktopLegacyInterfaceSuite) TestSanitizeSlot(c *C) { + c.Assert(s.coreSlot.Sanitize(s.iface), IsNil) + // desktop-legacy slot currently only used with core + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "some-snap"}, + Name: "desktop-legacy", + Interface: "desktop-legacy", + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "desktop-legacy slots are reserved for the core snap") +} + +func (s *DesktopLegacyInterfaceSuite) TestSanitizePlug(c *C) { + c.Assert(s.plug.Sanitize(s.iface), 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.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 ") + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `peer=(addr="@/tmp/ibus/dbus-*"),`) + + // connected plug to core slot + spec = &apparmor.Specification{} + c.Assert(spec.AddConnectedSlot(s.iface, s.plug, nil, s.coreSlot, nil), IsNil) + c.Assert(spec.SecurityTags(), HasLen, 0) +} + +func (s *DesktopLegacyInterfaceSuite) TestStaticInfo(c *C) { + si := interfaces.StaticInfoOf(s.iface) + c.Assert(si.ImplicitOnCore, Equals, false) + c.Assert(si.ImplicitOnClassic, Equals, true) + c.Assert(si.Summary, Equals, `allows privileged access to desktop legacy methods`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "desktop-legacy") +} + +func (s *DesktopLegacyInterfaceSuite) TestInterfaces(c *C) { + c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) +} diff -Nru snapd-2.27.5/interfaces/builtin/desktop_test.go snapd-2.28.5/interfaces/builtin/desktop_test.go --- snapd-2.27.5/interfaces/builtin/desktop_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/desktop_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,104 @@ +// -*- 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 DesktopInterfaceSuite struct { + iface interfaces.Interface + coreSlot *interfaces.Slot + plug *interfaces.Plug +} + +var _ = Suite(&DesktopInterfaceSuite{ + iface: builtin.MustInterface("desktop"), +}) + +const desktopConsumerYaml = `name: consumer +apps: + app: + plugs: [desktop] +` + +const desktopCoreYaml = `name: core +type: os +slots: + desktop: +` + +func (s *DesktopInterfaceSuite) SetUpTest(c *C) { + s.plug = MockPlug(c, desktopConsumerYaml, nil, "desktop") + s.coreSlot = MockSlot(c, desktopCoreYaml, nil, "desktop") +} + +func (s *DesktopInterfaceSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "desktop") +} + +func (s *DesktopInterfaceSuite) TestSanitizeSlot(c *C) { + c.Assert(s.coreSlot.Sanitize(s.iface), IsNil) + // desktop slot currently only used with core + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "some-snap"}, + Name: "desktop", + Interface: "desktop", + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "desktop slots are reserved for the core snap") +} + +func (s *DesktopInterfaceSuite) TestSanitizePlug(c *C) { + c.Assert(s.plug.Sanitize(s.iface), 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.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 ") + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "/etc/gtk-3.0/settings.ini r,") + + // connected plug to core slot + spec = &apparmor.Specification{} + c.Assert(spec.AddConnectedSlot(s.iface, s.plug, nil, s.coreSlot, nil), IsNil) + c.Assert(spec.SecurityTags(), HasLen, 0) +} + +func (s *DesktopInterfaceSuite) TestStaticInfo(c *C) { + si := interfaces.StaticInfoOf(s.iface) + c.Assert(si.ImplicitOnCore, Equals, false) + c.Assert(si.ImplicitOnClassic, Equals, true) + c.Assert(si.Summary, Equals, `allows access to basic graphical desktop resources`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "desktop") +} + +func (s *DesktopInterfaceSuite) TestInterfaces(c *C) { + c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) +} diff -Nru snapd-2.27.5/interfaces/builtin/docker.go snapd-2.28.5/interfaces/builtin/docker.go --- snapd-2.27.5/interfaces/builtin/docker.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/docker.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,8 +20,6 @@ package builtin import ( - "fmt" - "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/seccomp" @@ -58,8 +56,8 @@ return "docker" } -func (iface *dockerInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *dockerInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: dockerSummary, BaseDeclarationSlots: dockerBaseDeclarationSlots, } @@ -75,20 +73,6 @@ return nil } -func (iface *dockerInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface.Name())) - } - return nil -} - -func (iface *dockerInterface) SanitizeSlot(slot *interfaces.Slot) error { - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface.Name())) - } - return nil -} - func (iface *dockerInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff -Nru snapd-2.27.5/interfaces/builtin/docker_support.go snapd-2.28.5/interfaces/builtin/docker_support.go --- snapd-2.27.5/interfaces/builtin/docker_support.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/docker_support.go 2017-09-13 14:47:18.000000000 +0000 @@ -542,8 +542,8 @@ return "docker-support" } -func (iface *dockerSupportInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *dockerSupportInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: dockerSupportSummary, ImplicitOnCore: true, ImplicitOnClassic: true, @@ -571,17 +571,7 @@ return nil } -func (iface *dockerSupportInterface) SanitizeSlot(slot *interfaces.Slot) error { - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface.Name())) - } - return nil -} - func (iface *dockerSupportInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface.Name())) - } if v, ok := plug.Attrs["privileged-containers"]; ok { if _, ok = v.(bool); !ok { return fmt.Errorf("docker-support plug requires bool with 'privileged-containers'") @@ -595,10 +585,6 @@ return true } -func (iface *dockerSupportInterface) ValidatePlug(plug *interfaces.Plug, attrs map[string]interface{}) error { - return nil -} - func init() { registerIface(&dockerSupportInterface{}) } diff -Nru snapd-2.27.5/interfaces/builtin/docker_support_test.go snapd-2.28.5/interfaces/builtin/docker_support_test.go --- snapd-2.27.5/interfaces/builtin/docker_support_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/docker_support_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -96,13 +96,11 @@ } func (s *DockerSupportInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) + c.Assert(s.slot.Sanitize(s.iface), IsNil) } func (s *DockerSupportInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *DockerSupportInterfaceSuite) TestSanitizePlugWithPrivilegedTrue(c *C) { @@ -123,8 +121,7 @@ c.Assert(err, IsNil) plug := &interfaces.Plug{PlugInfo: info.Plugs["privileged"]} - err = s.iface.SanitizePlug(plug) - c.Assert(err, IsNil) + c.Assert(plug.Sanitize(s.iface), IsNil) apparmorSpec := &apparmor.Specification{} err = apparmorSpec.AddConnectedPlug(s.iface, plug, nil, s.slot, nil) @@ -157,8 +154,7 @@ c.Assert(err, IsNil) plug := &interfaces.Plug{PlugInfo: info.Plugs["privileged"]} - err = s.iface.SanitizePlug(plug) - c.Assert(err, IsNil) + c.Assert(plug.Sanitize(s.iface), IsNil) apparmorSpec := &apparmor.Specification{} err = apparmorSpec.AddConnectedPlug(s.iface, plug, nil, s.slot, nil) @@ -186,9 +182,7 @@ c.Assert(err, IsNil) plug := &interfaces.Plug{PlugInfo: info.Plugs["privileged"]} - err = s.iface.SanitizePlug(plug) - c.Assert(err, Not(IsNil)) - c.Assert(err, ErrorMatches, "docker-support plug requires bool with 'privileged-containers'") + c.Assert(plug.Sanitize(s.iface), ErrorMatches, "docker-support plug requires bool with 'privileged-containers'") } func (s *DockerSupportInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/docker_test.go snapd-2.28.5/interfaces/builtin/docker_test.go --- snapd-2.27.5/interfaces/builtin/docker_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/docker_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -82,13 +82,11 @@ } func (s *DockerInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) + c.Assert(s.slot.Sanitize(s.iface), IsNil) } func (s *DockerInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *DockerInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/export_test.go snapd-2.28.5/interfaces/builtin/export_test.go --- snapd-2.27.5/interfaces/builtin/export_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/export_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -22,12 +22,19 @@ import ( "fmt" + . "gopkg.in/check.v1" + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snaptest" ) var ( - RegisterIface = registerIface - ResolveSpecialVariable = resolveSpecialVariable + RegisterIface = registerIface + ResolveSpecialVariable = resolveSpecialVariable + SanitizeSlotReservedForOS = sanitizeSlotReservedForOS + SanitizeSlotReservedForOSOrGadget = sanitizeSlotReservedForOSOrGadget + SanitizeSlotReservedForOSOrApp = sanitizeSlotReservedForOSOrApp ) func MprisGetName(iface interfaces.Interface, attribs map[string]interface{}) (string, error) { @@ -53,3 +60,19 @@ } panic(fmt.Errorf("cannot find interface with name %q", name)) } + +func MockPlug(c *C, yaml string, si *snap.SideInfo, plugName string) *interfaces.Plug { + info := snaptest.MockInfo(c, yaml, si) + if plugInfo, ok := info.Plugs[plugName]; ok { + return &interfaces.Plug{PlugInfo: 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 { + info := snaptest.MockInfo(c, yaml, si) + if slotInfo, ok := info.Slots[slotName]; ok { + return &interfaces.Slot{SlotInfo: slotInfo} + } + panic(fmt.Sprintf("cannot find slot %q in snap %q", slotName, info.Name())) +} diff -Nru snapd-2.27.5/interfaces/builtin/firewall_control.go snapd-2.28.5/interfaces/builtin/firewall_control.go --- snapd-2.27.5/interfaces/builtin/firewall_control.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/firewall_control.go 2017-09-15 15:05:07.000000000 +0000 @@ -35,6 +35,7 @@ # privileged access to networking and should only be used with trusted apps. #include +/run/systemd/resolve/stub-resolv.conf r, # systemd-resolved (not yet included in nameservice abstraction) # diff -Nru snapd-2.27.5/interfaces/builtin/firewall_control_test.go snapd-2.28.5/interfaces/builtin/firewall_control_test.go --- snapd-2.27.5/interfaces/builtin/firewall_control_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/firewall_control_test.go 2017-09-13 14:47:18.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 @@ -28,7 +28,6 @@ "github.com/snapcore/snapd/interfaces/kmod" "github.com/snapcore/snapd/interfaces/seccomp" "github.com/snapcore/snapd/snap" - "github.com/snapcore/snapd/snap/snaptest" "github.com/snapcore/snapd/testutil" ) @@ -38,28 +37,25 @@ plug *interfaces.Plug } -const firewallControlMockPlugSnapInfoYaml = `name: other -version: 1.0 +const firewallControlConsumerYaml = `name: consumer apps: - app2: - command: foo + app: plugs: [firewall-control] ` +const firewallControlCoreYaml = `name: core +type: os +slots: + firewall-control: +` + var _ = Suite(&FirewallControlInterfaceSuite{ iface: builtin.MustInterface("firewall-control"), }) func (s *FirewallControlInterfaceSuite) SetUpTest(c *C) { - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "firewall-control", - Interface: "firewall-control", - }, - } - plugSnap := snaptest.MockInfo(c, firewallControlMockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["firewall-control"]} + s.plug = MockPlug(c, firewallControlConsumerYaml, nil, "firewall-control") + s.slot = MockSlot(c, firewallControlCoreYaml, nil, "firewall-control") } func (s *FirewallControlInterfaceSuite) TestName(c *C) { @@ -67,45 +63,37 @@ } func (s *FirewallControlInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "firewall-control", Interface: "firewall-control", - }}) - c.Assert(err, ErrorMatches, "firewall-control slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "firewall-control slots are reserved for the core snap") } func (s *FirewallControlInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } -func (s *FirewallControlInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "firewall-control"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "firewall-control"`) -} - -func (s *FirewallControlInterfaceSuite) 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) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) - c.Assert(apparmorSpec.SnippetForTag("snap.other.app2"), testutil.Contains, `capability 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) - c.Assert(err, IsNil) - c.Assert(seccompSpec.Snippets(), HasLen, 1) +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.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.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{} - err = spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) - c.Assert(err, IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) c.Assert(spec.Modules(), DeepEquals, map[string]bool{ "arp_tables": true, "br_netfilter": true, @@ -114,6 +102,18 @@ }) } +func (s *FirewallControlInterfaceSuite) 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 control over network firewall") + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "firewall-control") +} + +func (s *FirewallControlInterfaceSuite) TestAutoConnect(c *C) { + c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) +} + func (s *FirewallControlInterfaceSuite) TestInterfaces(c *C) { c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) } diff -Nru snapd-2.27.5/interfaces/builtin/framebuffer.go snapd-2.28.5/interfaces/builtin/framebuffer.go --- snapd-2.27.5/interfaces/builtin/framebuffer.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/framebuffer.go 2017-09-13 14:47:18.000000000 +0000 @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2017 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 @@ -19,14 +19,6 @@ package builtin -import ( - "fmt" - - "github.com/snapcore/snapd/interfaces" - "github.com/snapcore/snapd/interfaces/apparmor" - "github.com/snapcore/snapd/interfaces/udev" -) - const framebufferSummary = `allows access to universal framebuffer devices` const framebufferBaseDeclarationSlots = ` @@ -45,75 +37,17 @@ /run/udev/data/c29:[0-9]* r, ` -// The type for physical-memory-control interface -type framebufferInterface struct{} - -// Getter for the name of the physical-memory-control interface -func (iface *framebufferInterface) Name() string { - return "framebuffer" -} - -func (iface *framebufferInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ - Summary: framebufferSummary, - ImplicitOnCore: true, - ImplicitOnClassic: true, - BaseDeclarationSlots: framebufferBaseDeclarationSlots, - } -} - -func (iface *framebufferInterface) String() string { - return iface.Name() -} - -// Check validity of the defined slot -func (iface *framebufferInterface) SanitizeSlot(slot *interfaces.Slot) error { - // Does it have right type? - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface)) - } - - // Creation of the slot of this type - // is allowed only by a gadget or os snap - if !(slot.Snap.Type == "os") { - return fmt.Errorf("%s slots only allowed on core snap", iface.Name()) - } - return nil -} - -// Checks and possibly modifies a plug -func (iface *framebufferInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface)) - } - // Currently nothing is checked on the plug side - return nil -} - -func (iface *framebufferInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - spec.AddSnippet(framebufferConnectedPlugAppArmor) - return nil -} - -func (iface *framebufferInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - // This will fix access denied of opengl interface when it's used with - // framebuffer interface in the same snap. - // https://bugs.launchpad.net/snapd/+bug/1675738 - // TODO: we are not doing this due to the bug and we'll be reintroducing - // the udev tagging soon. - //const udevRule = `KERNEL=="fb[0-9]*", TAG+="%s"` - //for appName := range plug.Apps { - // tag := udevSnapSecurityName(plug.Snap.Name(), appName) - // spec.AddSnippet(fmt.Sprintf(udevRule, tag)) - //} - return nil -} - -func (iface *framebufferInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { - // Allow what is allowed in the declarations - return true -} +const framebufferConnectedPlugUDev = `KERNEL=="fb[0-9]*", TAG+="###CONNECTED_SECURITY_TAGS###"` func init() { - registerIface(&framebufferInterface{}) + registerIface(&commonInterface{ + name: "framebuffer", + summary: framebufferSummary, + implicitOnCore: true, + implicitOnClassic: true, + baseDeclarationSlots: framebufferBaseDeclarationSlots, + connectedPlugAppArmor: framebufferConnectedPlugAppArmor, + connectedPlugUDev: framebufferConnectedPlugUDev, + reservedForOS: true, + }) } diff -Nru snapd-2.27.5/interfaces/builtin/framebuffer_test.go snapd-2.28.5/interfaces/builtin/framebuffer_test.go --- snapd-2.27.5/interfaces/builtin/framebuffer_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/framebuffer_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -25,8 +25,8 @@ "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/snap/snaptest" "github.com/snapcore/snapd/testutil" ) @@ -36,30 +36,27 @@ plug *interfaces.Plug } +const framebufferConsumerYaml = ` +name: consumer +apps: + app: + plugs: [framebuffer] +` + +const framebufferOsYaml = ` +name: core +type: os +slots: + framebuffer: +` + var _ = Suite(&FramebufferInterfaceSuite{ iface: builtin.MustInterface("framebuffer"), }) func (s *FramebufferInterfaceSuite) SetUpTest(c *C) { - // Mock for OS Snap - osSnapInfo := snaptest.MockInfo(c, ` -name: ubuntu-core -type: os -slots: - test-framebuffer: - interface: framebuffer -`, nil) - s.slot = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["test-framebuffer"]} - - // Snap Consumers - consumingSnapInfo := snaptest.MockInfo(c, ` -name: client-snap -apps: - app-accessing-framebuffer: - command: foo - plugs: [framebuffer] -`, nil) - s.plug = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["framebuffer"]} + s.plug = MockPlug(c, framebufferConsumerYaml, nil, "framebuffer") + s.slot = MockSlot(c, framebufferOsYaml, nil, "framebuffer") } func (s *FramebufferInterfaceSuite) TestName(c *C) { @@ -67,43 +64,44 @@ } func (s *FramebufferInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "framebuffer", Interface: "framebuffer", - }}) - c.Assert(err, ErrorMatches, "framebuffer slots only allowed on core snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "framebuffer slots are reserved for the core snap") } func (s *FramebufferInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } -func (s *FramebufferInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "framebuffer"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "framebuffer"`) +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.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `/dev/fb[0-9]* rw,`) } -func (s *FramebufferInterfaceSuite) TestUsedSecuritySystems(c *C) { - expectedSnippet1 := ` -# Description: Allow reading and writing to the universal framebuffer (/dev/fb*) which -# gives privileged access to the console framebuffer. +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"`) +} -/dev/fb[0-9]* rw, -/run/udev/data/c29:[0-9]* r, -` - // connected plugs have a non-nil security snippet for apparmor - apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.client-snap.app-accessing-framebuffer"}) - aasnippet := apparmorSpec.SnippetForTag("snap.client-snap.app-accessing-framebuffer") - c.Assert(aasnippet, Equals, expectedSnippet1, Commentf("\nexpected:\n%s\nfound:\n%s", expectedSnippet1, aasnippet)) +func (s *FramebufferInterfaceSuite) TestStaticInfo(c *C) { + si := interfaces.StaticInfoOf(s.iface) + c.Assert(si.ImplicitOnCore, Equals, true) + c.Assert(si.ImplicitOnClassic, Equals, true) + c.Assert(si.Summary, Equals, `allows access to universal framebuffer devices`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "framebuffer") +} + +func (s *FramebufferInterfaceSuite) TestAutoConnect(c *C) { + c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) } func (s *FramebufferInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/fuse_support.go snapd-2.28.5/interfaces/builtin/fuse_support.go --- snapd-2.27.5/interfaces/builtin/fuse_support.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/fuse_support.go 2017-09-13 14:47:18.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 @@ -83,8 +83,9 @@ #/{,usr/}bin/fusermount ixr, ` +const fuseSupportConnectedPlugUDev = `KERNEL=="fuse", TAG+="###CONNECTED_SECURITY_TAGS###"` + func init() { - // Ubuntu 14.04 does not support the fuse-support interface. registerIface(&commonInterface{ name: "fuse-support", summary: fuseSupportSummary, @@ -94,5 +95,6 @@ reservedForOS: true, connectedPlugAppArmor: fuseSupportConnectedPlugAppArmor, connectedPlugSecComp: fuseSupportConnectedPlugSecComp, + connectedPlugUDev: fuseSupportConnectedPlugUDev, }) } diff -Nru snapd-2.27.5/interfaces/builtin/fuse_support_test.go snapd-2.28.5/interfaces/builtin/fuse_support_test.go --- snapd-2.27.5/interfaces/builtin/fuse_support_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/fuse_support_test.go 2017-09-13 14:47:18.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 @@ -26,8 +26,9 @@ "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/snap/snaptest" "github.com/snapcore/snapd/testutil" ) @@ -37,28 +38,25 @@ plug *interfaces.Plug } -const fuseSupportMockPlugSnapInfoYaml = `name: other -version: 1.0 +const fuseSupportConsumerYaml = `name: consumer apps: - app2: - command: foo + app: plugs: [fuse-support] ` +const fuseSupportCoreYaml = `name: core +type: os +slots: + fuse-support: +` + var _ = Suite(&FuseSupportInterfaceSuite{ iface: builtin.MustInterface("fuse-support"), }) func (s *FuseSupportInterfaceSuite) SetUpTest(c *C) { - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "fuse-support", - Interface: "fuse-support", - }, - } - plugSnap := snaptest.MockInfo(c, fuseSupportMockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["fuse-support"]} + s.plug = MockPlug(c, fuseSupportConsumerYaml, nil, "fuse-support") + s.slot = MockSlot(c, fuseSupportCoreYaml, nil, "fuse-support") } func (s *FuseSupportInterfaceSuite) TestName(c *C) { @@ -66,42 +64,51 @@ } func (s *FuseSupportInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "fuse-support", Interface: "fuse-support", - }}) - c.Assert(err, ErrorMatches, "fuse-support slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "fuse-support slots are reserved for the core snap") } func (s *FuseSupportInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) + c.Assert(s.plug.Sanitize(s.iface), 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.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.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"`) +} + +func (s *FuseSupportInterfaceSuite) TestStaticInfo(c *C) { + si := interfaces.StaticInfoOf(s.iface) + c.Assert(si.ImplicitOnCore, Equals, true) + c.Assert(si.ImplicitOnClassic, Equals, !(release.ReleaseInfo.ID == "ubuntu" && release.ReleaseInfo.VersionID == "14.04")) + c.Assert(si.Summary, Equals, `allows access to the FUSE file system`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "fuse-support") } -func (s *FuseSupportInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "fuse-support"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "fuse-support"`) -} - -func (s *FuseSupportInterfaceSuite) 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) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) - c.Assert(apparmorSpec.SnippetForTag("snap.other.app2"), testutil.Contains, `/dev/fuse`) - - // 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.app2"}) - c.Check(seccompSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "mount\n") +func (s *FuseSupportInterfaceSuite) TestAutoConnect(c *C) { + c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) } func (s *FuseSupportInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/fwupd.go snapd-2.28.5/interfaces/builtin/fwupd.go --- snapd-2.27.5/interfaces/builtin/fwupd.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/fwupd.go 2017-09-15 15:05:07.000000000 +0000 @@ -106,6 +106,7 @@ #Can access the network #include #include + /run/systemd/resolve/stub-resolv.conf r, # DBus accesses #include @@ -218,8 +219,8 @@ return "fwupd" } -func (iface *fwupdInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *fwupdInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: fwupdSummary, BaseDeclarationSlots: fwupdBaseDeclarationSlots, } @@ -262,16 +263,6 @@ return nil } -// SanitizePlug checks the plug definition is valid -func (iface *fwupdInterface) SanitizePlug(plug *interfaces.Plug) error { - return nil -} - -// SanitizeSlot checks the slot definition is valid -func (iface *fwupdInterface) SanitizeSlot(slot *interfaces.Slot) error { - return nil -} - func (iface *fwupdInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff -Nru snapd-2.27.5/interfaces/builtin/gpio.go snapd-2.28.5/interfaces/builtin/gpio.go --- snapd-2.27.5/interfaces/builtin/gpio.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/gpio.go 2017-09-13 14:47:18.000000000 +0000 @@ -54,8 +54,8 @@ return "gpio" } -func (iface *gpioInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *gpioInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: gpioSummary, BaseDeclarationSlots: gpioBaseDeclarationSlots, } @@ -63,14 +63,8 @@ // SanitizeSlot checks the slot definition is valid func (iface *gpioInterface) SanitizeSlot(slot *interfaces.Slot) error { - // Paranoid check this right interface type - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface)) - } - - // We will only allow creation of this type of slot by a gadget or OS snap - if !(slot.Snap.Type == "gadget" || slot.Snap.Type == "os") { - return fmt.Errorf("gpio slots only allowed on gadget or core snaps") + if err := sanitizeSlotReservedForOSOrGadget(iface, slot); err != nil { + return err } // Must have a GPIO number @@ -88,17 +82,6 @@ return nil } -// SanitizePlug checks the plug definition is valid -func (iface *gpioInterface) SanitizePlug(plug *interfaces.Plug) error { - // Make sure right interface type - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface)) - } - - // Plug is good - 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"]) // Entries in /sys/class/gpio for single GPIO's are just symlinks @@ -134,10 +117,6 @@ return true } -func (iface *gpioInterface) ValidateSlot(slot *interfaces.Slot, attrs map[string]interface{}) error { - return nil -} - func init() { registerIface(&gpioInterface{}) } diff -Nru snapd-2.27.5/interfaces/builtin/gpio_test.go snapd-2.28.5/interfaces/builtin/gpio_test.go --- snapd-2.27.5/interfaces/builtin/gpio_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/gpio_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -98,39 +98,30 @@ func (s *GpioInterfaceSuite) TestSanitizeSlotGadgetSnap(c *C) { // gpio slot on gadget accepeted - err := s.iface.SanitizeSlot(s.gadgetGpioSlot) - c.Assert(err, IsNil) + c.Assert(s.gadgetGpioSlot.Sanitize(s.iface), IsNil) // slots without number attribute are rejected - err = s.iface.SanitizeSlot(s.gadgetMissingNumberSlot) - c.Assert(err, ErrorMatches, "gpio slot must have a number attribute") + c.Assert(s.gadgetMissingNumberSlot.Sanitize(s.iface), ErrorMatches, + "gpio slot must have a number attribute") // slots with number attribute that isnt a number - err = s.iface.SanitizeSlot(s.gadgetBadNumberSlot) - c.Assert(err, ErrorMatches, "gpio slot number attribute must be an int") - - // Must be right interface type - c.Assert(func() { s.iface.SanitizeSlot(s.gadgetBadInterfaceSlot) }, PanicMatches, `slot is not of interface "gpio"`) + c.Assert(s.gadgetBadNumberSlot.Sanitize(s.iface), ErrorMatches, + "gpio slot number attribute must be an int") } func (s *GpioInterfaceSuite) TestSanitizeSlotOsSnap(c *C) { // gpio slot on OS accepeted - err := s.iface.SanitizeSlot(s.osGpioSlot) - c.Assert(err, IsNil) + c.Assert(s.osGpioSlot.Sanitize(s.iface), IsNil) } func (s *GpioInterfaceSuite) TestSanitizeSlotAppSnap(c *C) { // gpio slot not accepted on app snap - err := s.iface.SanitizeSlot(s.appGpioSlot) - c.Assert(err, ErrorMatches, "gpio slots only allowed on gadget or core snaps") + c.Assert(s.appGpioSlot.Sanitize(s.iface), ErrorMatches, + "gpio slots are reserved for the core and gadget snaps") } func (s *GpioInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.gadgetPlug) - c.Assert(err, IsNil) - - // It is impossible to use "bool-file" interface to sanitize plugs of different interface. - c.Assert(func() { s.iface.SanitizePlug(s.gadgetBadInterfacePlug) }, PanicMatches, `plug is not of interface "gpio"`) + c.Assert(s.gadgetPlug.Sanitize(s.iface), IsNil) } func (s *GpioInterfaceSuite) TestSystemdConnectedSlot(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/greengrass_support.go snapd-2.28.5/interfaces/builtin/greengrass_support.go --- snapd-2.27.5/interfaces/builtin/greengrass_support.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/greengrass_support.go 2017-09-13 14:47:18.000000000 +0000 @@ -58,7 +58,7 @@ owner /sys/fs/cgroup/cpuset/{,system.slice/}cpuset.cpus rw, owner /sys/fs/cgroup/cpuset/{,system.slice/}cpuset.mems rw, owner /sys/fs/cgroup/*/system.slice/@{profile_name}.service/{,**} rw, -# for running under snap run --shell +# for running just after a reboot owner /sys/fs/cgroup/*/user.slice/ rw, owner /sys/fs/cgroup/cpuset/user.slice/cpuset.cpus rw, owner /sys/fs/cgroup/cpuset/user.slice/cpuset.mems rw, diff -Nru snapd-2.27.5/interfaces/builtin/greengrass_support_test.go snapd-2.28.5/interfaces/builtin/greengrass_support_test.go --- snapd-2.27.5/interfaces/builtin/greengrass_support_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/greengrass_support_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -66,26 +66,18 @@ } func (s *GreengrassSupportInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "greengrass-support", Interface: "greengrass-support", - }}) - c.Assert(err, ErrorMatches, "greengrass-support slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "greengrass-support slots are reserved for the core snap") } func (s *GreengrassSupportInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *GreengrassSupportInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "greengrass-support"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "greengrass-support"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *GreengrassSupportInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/gsettings_test.go snapd-2.28.5/interfaces/builtin/gsettings_test.go --- snapd-2.27.5/interfaces/builtin/gsettings_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/gsettings_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -66,26 +66,18 @@ } func (s *GsettingsInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "gsettings", Interface: "gsettings", - }}) - c.Assert(err, ErrorMatches, "gsettings slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "gsettings slots are reserved for the core snap") } func (s *GsettingsInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *GsettingsInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "gsettings"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "gsettings"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *GsettingsInterfaceSuite) TestConnectedPlugSnippet(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/hardware_observe.go snapd-2.28.5/interfaces/builtin/hardware_observe.go --- snapd-2.27.5/interfaces/builtin/hardware_observe.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/hardware_observe.go 2017-09-07 12:33:50.000000000 +0000 @@ -56,6 +56,7 @@ # Needed for udevadm /run/udev/data/** r, +network netlink raw, # util-linux /{,usr/}bin/lscpu ixr, @@ -87,6 +88,10 @@ # multicast statistics socket AF_NETLINK - NETLINK_GENERIC + +# kernel uevents +socket AF_NETLINK - NETLINK_KOBJECT_UEVENT +bind ` func init() { diff -Nru snapd-2.27.5/interfaces/builtin/hardware_observe_test.go snapd-2.28.5/interfaces/builtin/hardware_observe_test.go --- snapd-2.27.5/interfaces/builtin/hardware_observe_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/hardware_observe_test.go 2017-10-11 07:09:14.000000000 +0000 @@ -66,26 +66,18 @@ } func (s *HardwareObserveInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "hardware-observe", Interface: "hardware-observe", - }}) - c.Assert(err, ErrorMatches, "hardware-observe slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "hardware-observe slots are reserved for the core snap") } func (s *HardwareObserveInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *HardwareObserveInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "hardware-observe"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "hardware-observe"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *HardwareObserveInterfaceSuite) TestUsedSecuritySystems(c *C) { @@ -95,6 +87,7 @@ 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") + c.Assert(apparmorSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "network netlink raw,\n") // connected plugs have a non-nil security snippet for seccomp seccompSpec := &seccomp.Specification{} @@ -102,6 +95,7 @@ c.Assert(err, IsNil) c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) c.Check(seccompSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "iopl\n") + c.Check(seccompSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "socket AF_NETLINK - NETLINK_KOBJECT_UEVENT\n") } func (s *HardwareObserveInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/hardware_random_control.go snapd-2.28.5/interfaces/builtin/hardware_random_control.go --- snapd-2.27.5/interfaces/builtin/hardware_random_control.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/hardware_random_control.go 2017-09-13 14:47:18.000000000 +0000 @@ -19,15 +19,6 @@ package builtin -import ( - "fmt" - - "github.com/snapcore/snapd/interfaces" - "github.com/snapcore/snapd/interfaces/apparmor" - "github.com/snapcore/snapd/interfaces/udev" - "github.com/snapcore/snapd/snap" -) - const hardwareRandomControlSummary = `allows control over the hardware random number generator` const hardwareRandomControlBaseDeclarationSlots = ` @@ -54,63 +45,17 @@ /sys/devices/virtual/misc/hw_random/rng_current w, ` -// The type for physical-memory-control interface -type hardwareRandomControlInterface struct{} - -// Getter for the name of the physical-memory-control interface -func (iface *hardwareRandomControlInterface) Name() string { - return "hardware-random-control" -} - -func (iface *hardwareRandomControlInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ - Summary: hardwareRandomControlSummary, - ImplicitOnCore: true, - ImplicitOnClassic: true, - BaseDeclarationSlots: hardwareRandomControlBaseDeclarationSlots, - } -} - -// Check validity of the defined slot -func (iface *hardwareRandomControlInterface) SanitizeSlot(slot *interfaces.Slot) error { - // Does it have right type? - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface.Name())) - } - if slot.Snap.Type != snap.TypeOS { - return fmt.Errorf("%s slots are reserved for the operating system snap", iface.Name()) - } - return nil -} - -// Checks and possibly modifies a plug -func (iface *hardwareRandomControlInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface.Name())) - } - // Currently nothing is checked on the plug side - return nil -} - -func (iface *hardwareRandomControlInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - spec.AddSnippet(hardwareRandomControlConnectedPlugAppArmor) - return nil -} - -func (iface *hardwareRandomControlInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - const udevRule = `KERNEL=="hwrng", TAG+="%s"` - for appName := range plug.Apps { - tag := udevSnapSecurityName(plug.Snap.Name(), appName) - spec.AddSnippet(fmt.Sprintf(udevRule, tag)) - } - return nil -} - -func (iface *hardwareRandomControlInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { - // Allow what is allowed in the declarations - return true -} +const hardwareRandomControlConnectedPlugUDev = `KERNEL=="hwrng", TAG+="###CONNECTED_SECURITY_TAGS###"` func init() { - registerIface(&hardwareRandomControlInterface{}) + registerIface(&commonInterface{ + name: "hardware-random-control", + summary: hardwareRandomControlSummary, + implicitOnCore: true, + implicitOnClassic: true, + baseDeclarationSlots: hardwareRandomControlBaseDeclarationSlots, + connectedPlugAppArmor: hardwareRandomControlConnectedPlugAppArmor, + connectedPlugUDev: hardwareRandomControlConnectedPlugUDev, + reservedForOS: true, + }) } diff -Nru snapd-2.27.5/interfaces/builtin/hardware_random_control_test.go snapd-2.28.5/interfaces/builtin/hardware_random_control_test.go --- snapd-2.27.5/interfaces/builtin/hardware_random_control_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/hardware_random_control_test.go 2017-09-13 14:47:18.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 @@ -27,7 +27,6 @@ "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" ) @@ -41,25 +40,21 @@ iface: builtin.MustInterface("hardware-random-control"), }) -func (s *HardwareRandomControlInterfaceSuite) SetUpTest(c *C) { - // Mock for OS Snap - osSnapInfo := snaptest.MockInfo(c, ` -name: core +const hardwareRandomControlConsumerYaml = `name: consumer +apps: + app: + plugs: [hardware-random-control] +` + +const hardwareRandomControlCoreYaml = `name: core type: os slots: hardware-random-control: -`, nil) - s.slot = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["hardware-random-control"]} +` - // Snap Consumers - consumingSnapInfo := snaptest.MockInfo(c, ` -name: snap -apps: - app: - command: foo - plugs: [hardware-random-control] -`, nil) - s.plug = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["hardware-random-control"]} +func (s *HardwareRandomControlInterfaceSuite) SetUpTest(c *C) { + s.plug = MockPlug(c, hardwareRandomControlConsumerYaml, nil, "hardware-random-control") + s.slot = MockSlot(c, hardwareRandomControlCoreYaml, nil, "hardware-random-control") } func (s *HardwareRandomControlInterfaceSuite) TestName(c *C) { @@ -67,40 +62,44 @@ } func (s *HardwareRandomControlInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "hardware-random-control", Interface: "hardware-random-control", - }}) - c.Assert(err, ErrorMatches, "hardware-random-control slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "hardware-random-control slots are reserved for the core snap") } func (s *HardwareRandomControlInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *HardwareRandomControlInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "hardware-random-control"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "hardware-random-control"`) + c.Assert(s.plug.Sanitize(s.iface), 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.SecurityTags(), DeepEquals, []string{"snap.snap.app"}) - c.Assert(spec.SnippetForTag("snap.snap.app"), testutil.Contains, "hw_random/rng_current w,") + 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) - expected := []string{`KERNEL=="hwrng", TAG+="snap_snap_app"`} - c.Assert(spec.Snippets(), DeepEquals, expected) + c.Assert(spec.Snippets(), HasLen, 1) + c.Assert(spec.Snippets()[0], Equals, `KERNEL=="hwrng", TAG+="snap_consumer_app"`) +} + +func (s *HardwareRandomControlInterfaceSuite) 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 control over the hardware random number generator`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "hardware-random-control") +} + +func (s *HardwareRandomControlInterfaceSuite) TestAutoConnect(c *C) { + c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) } func (s *HardwareRandomControlInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/hardware_random_observe.go snapd-2.28.5/interfaces/builtin/hardware_random_observe.go --- snapd-2.27.5/interfaces/builtin/hardware_random_observe.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/hardware_random_observe.go 2017-09-13 14:47:18.000000000 +0000 @@ -19,15 +19,6 @@ package builtin -import ( - "fmt" - - "github.com/snapcore/snapd/interfaces" - "github.com/snapcore/snapd/interfaces/apparmor" - "github.com/snapcore/snapd/interfaces/udev" - "github.com/snapcore/snapd/snap" -) - const hardwareRandomObserveSummary = `allows reading from hardware random number generator` const hardwareRandomObserveBaseDeclarationSlots = ` @@ -49,62 +40,17 @@ /sys/devices/virtual/misc/hw_random/rng_{available,current} r, ` -// The type for physical-memory-control interface -type hardwareRandomObserveInterface struct{} - -// Getter for the name of the physical-memory-control interface -func (iface *hardwareRandomObserveInterface) Name() string { - return "hardware-random-observe" -} - -func (iface *hardwareRandomObserveInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ - Summary: hardwareRandomObserveSummary, - ImplicitOnCore: true, - ImplicitOnClassic: true, - BaseDeclarationSlots: hardwareRandomObserveBaseDeclarationSlots, - } -} - -// Check validity of the defined slot -func (iface *hardwareRandomObserveInterface) SanitizeSlot(slot *interfaces.Slot) error { - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface.Name())) - } - if slot.Snap.Type != snap.TypeOS { - return fmt.Errorf("%s slots are reserved for the operating system snap", iface.Name()) - } - return nil -} - -// Checks and possibly modifies a plug -func (iface *hardwareRandomObserveInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface.Name())) - } - // Currently nothing is checked on the plug side - return nil -} - -func (iface *hardwareRandomObserveInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - spec.AddSnippet(hardwareRandomObserveConnectedPlugAppArmor) - return nil -} - -func (iface *hardwareRandomObserveInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - const udevRule = `KERNEL=="hwrng", TAG+="%s"` - for appName := range plug.Apps { - tag := udevSnapSecurityName(plug.Snap.Name(), appName) - spec.AddSnippet(fmt.Sprintf(udevRule, tag)) - } - return nil -} - -func (iface *hardwareRandomObserveInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { - // Allow what is allowed in the declarations - return true -} +const hardwareRandomObserveConnectedPlugUDev = `KERNEL=="hwrng", TAG+="###CONNECTED_SECURITY_TAGS###"` func init() { - registerIface(&hardwareRandomObserveInterface{}) + registerIface(&commonInterface{ + name: "hardware-random-observe", + summary: hardwareRandomObserveSummary, + implicitOnCore: true, + implicitOnClassic: true, + baseDeclarationSlots: hardwareRandomObserveBaseDeclarationSlots, + connectedPlugAppArmor: hardwareRandomObserveConnectedPlugAppArmor, + connectedPlugUDev: hardwareRandomObserveConnectedPlugUDev, + reservedForOS: true, + }) } diff -Nru snapd-2.27.5/interfaces/builtin/hardware_random_observe_test.go snapd-2.28.5/interfaces/builtin/hardware_random_observe_test.go --- snapd-2.27.5/interfaces/builtin/hardware_random_observe_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/hardware_random_observe_test.go 2017-09-13 14:47:18.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 @@ -27,7 +27,6 @@ "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" ) @@ -41,25 +40,21 @@ iface: builtin.MustInterface("hardware-random-observe"), }) -func (s *HardwareRandomObserveInterfaceSuite) SetUpTest(c *C) { - // Mock for OS Snap - osSnapInfo := snaptest.MockInfo(c, ` -name: core +const hardwareRandomObserveConsumerYaml = `name: consumer +apps: + app: + plugs: [hardware-random-observe] +` + +const hardwareRandomObserveCoreYaml = `name: core type: os slots: hardware-random-observe: -`, nil) - s.slot = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["hardware-random-observe"]} +` - // Snap Consumers - consumingSnapInfo := snaptest.MockInfo(c, ` -name: snap -apps: - app: - command: foo - plugs: [hardware-random-observe] -`, nil) - s.plug = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["hardware-random-observe"]} +func (s *HardwareRandomObserveInterfaceSuite) SetUpTest(c *C) { + s.plug = MockPlug(c, hardwareRandomObserveConsumerYaml, nil, "hardware-random-observe") + s.slot = MockSlot(c, hardwareRandomObserveCoreYaml, nil, "hardware-random-observe") } func (s *HardwareRandomObserveInterfaceSuite) TestName(c *C) { @@ -67,40 +62,44 @@ } func (s *HardwareRandomObserveInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "hardware-random-observe", Interface: "hardware-random-observe", - }}) - c.Assert(err, ErrorMatches, "hardware-random-observe slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "hardware-random-observe slots are reserved for the core snap") } func (s *HardwareRandomObserveInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *HardwareRandomObserveInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "hardware-random-observe"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "hardware-random-observe"`) + c.Assert(s.plug.Sanitize(s.iface), 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.SecurityTags(), DeepEquals, []string{"snap.snap.app"}) - c.Assert(spec.SnippetForTag("snap.snap.app"), testutil.Contains, "hw_random/rng_{available,current} r,") + 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) - expected := []string{`KERNEL=="hwrng", TAG+="snap_snap_app"`} - c.Assert(spec.Snippets(), DeepEquals, expected) + c.Assert(spec.Snippets(), HasLen, 1) + c.Assert(spec.Snippets()[0], Equals, `KERNEL=="hwrng", TAG+="snap_consumer_app"`) +} + +func (s *HardwareRandomObserveInterfaceSuite) 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 from hardware random number generator`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "hardware-random-observe") +} + +func (s *HardwareRandomObserveInterfaceSuite) TestAutoConnect(c *C) { + c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) } func (s *HardwareRandomObserveInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/hidraw.go snapd-2.28.5/interfaces/builtin/hidraw.go --- snapd-2.27.5/interfaces/builtin/hidraw.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/hidraw.go 2017-09-13 14:47:18.000000000 +0000 @@ -49,8 +49,8 @@ return "hidraw" } -func (iface *hidrawInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *hidrawInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: hidrawSummary, BaseDeclarationSlots: hidrawBaseDeclarationSlots, } @@ -71,14 +71,8 @@ // SanitizeSlot checks validity of the defined slot func (iface *hidrawInterface) SanitizeSlot(slot *interfaces.Slot) error { - // Check slot is of right type - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface)) - } - - // We will only allow creation of this type of slot by a gadget or OS snap - if !(slot.Snap.Type == "gadget" || slot.Snap.Type == "os") { - return fmt.Errorf("hidraw slots only allowed on gadget or core snaps") + if err := sanitizeSlotReservedForOSOrGadget(iface, slot); err != nil { + return err } // Check slot has a path attribute identify hidraw device @@ -122,15 +116,6 @@ return nil } -// SanitizePlug checks and possibly modifies a plug. -func (iface *hidrawInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface)) - } - // NOTE: currently we don't check anything on the plug side. - return nil -} - func (iface *hidrawInterface) UDevPermanentSlot(spec *udev.Specification, slot *interfaces.Slot) error { usbVendor, vOk := slot.Attrs["usb-vendor"].(int64) if !vOk { @@ -198,10 +183,6 @@ return false } -func (iface *hidrawInterface) ValidateSlot(slot *interfaces.Slot, attrs map[string]interface{}) error { - return nil -} - func init() { registerIface(&hidrawInterface{}) } diff -Nru snapd-2.27.5/interfaces/builtin/hidraw_test.go snapd-2.28.5/interfaces/builtin/hidraw_test.go --- snapd-2.27.5/interfaces/builtin/hidraw_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/hidraw_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -152,43 +152,29 @@ func (s *HidrawInterfaceSuite) TestSanitizeCoreSnapSlots(c *C) { for _, slot := range []*interfaces.Slot{s.testSlot1, s.testSlot2} { - err := s.iface.SanitizeSlot(slot) - c.Assert(err, IsNil) + c.Assert(slot.Sanitize(s.iface), IsNil) } } func (s *HidrawInterfaceSuite) TestSanitizeBadCoreSnapSlots(c *C) { // Slots without the "path" attribute are rejected. - err := s.iface.SanitizeSlot(s.missingPathSlot) - c.Assert(err, ErrorMatches, `hidraw slots must have a path attribute`) + c.Assert(s.missingPathSlot.Sanitize(s.iface), 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} { - err := s.iface.SanitizeSlot(slot) - c.Assert(err, ErrorMatches, "hidraw path attribute must be a valid device node") + c.Assert(slot.Sanitize(s.iface), ErrorMatches, "hidraw path attribute must be a valid device node") } - - // It is impossible to use "bool-file" interface to sanitize slots with other interfaces. - c.Assert(func() { s.iface.SanitizeSlot(s.badInterfaceSlot) }, PanicMatches, `slot is not of interface "hidraw"`) } func (s *HidrawInterfaceSuite) TestSanitizeGadgetSnapSlots(c *C) { - err := s.iface.SanitizeSlot(s.testUDev1) - c.Assert(err, IsNil) - - err = s.iface.SanitizeSlot(s.testUDev2) - c.Assert(err, IsNil) + c.Assert(s.testUDev1.Sanitize(s.iface), IsNil) + c.Assert(s.testUDev2.Sanitize(s.iface), IsNil) } func (s *HidrawInterfaceSuite) TestSanitizeBadGadgetSnapSlots(c *C) { - err := s.iface.SanitizeSlot(s.testUDevBadValue1) - c.Assert(err, ErrorMatches, "hidraw usb-vendor attribute not valid: -1") - - err = s.iface.SanitizeSlot(s.testUDevBadValue2) - c.Assert(err, ErrorMatches, "hidraw usb-product attribute not valid: 65536") - - err = s.iface.SanitizeSlot(s.testUDevBadValue3) - c.Assert(err, ErrorMatches, "hidraw path attribute specifies invalid symlink location") + 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") } func (s *HidrawInterfaceSuite) TestPermanentSlotUDevSnippets(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/home_test.go snapd-2.28.5/interfaces/builtin/home_test.go --- snapd-2.27.5/interfaces/builtin/home_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/home_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -64,26 +64,18 @@ } func (s *HomeInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "home", Interface: "home", - }}) - c.Assert(err, ErrorMatches, "home slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "home slots are reserved for the core snap") } func (s *HomeInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *HomeInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "home"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "home"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *HomeInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/i2c.go snapd-2.28.5/interfaces/builtin/i2c.go --- snapd-2.27.5/interfaces/builtin/i2c.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/i2c.go 2017-09-13 14:47:18.000000000 +0000 @@ -41,6 +41,15 @@ deny-auto-connection: true ` +const i2cConnectedPlugAppArmor = ` +# Description: Can access I2C controller + +%s rw, +/sys/devices/platform/{*,**.i2c}/%s/** rw, +` + +const i2cConnectedPlugUDev = `KERNEL=="%s", TAG+="%s"` + // The type for i2c interface type i2cInterface struct{} @@ -49,8 +58,8 @@ return "i2c" } -func (iface *i2cInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *i2cInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: i2cSummary, BaseDeclarationSlots: i2cBaseDeclarationSlots, } @@ -67,15 +76,8 @@ // Check validity of the defined slot func (iface *i2cInterface) SanitizeSlot(slot *interfaces.Slot) error { - // Does it have right type? - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface)) - } - - // Creation of the slot of this type - // is allowed only by a gadget snap - if !(slot.Snap.Type == "gadget" || slot.Snap.Type == "os") { - return fmt.Errorf("%s slots only allowed on gadget or core snaps", iface.Name()) + if err := sanitizeSlotReservedForOSOrGadget(iface, slot); err != nil { + return err } // Validate the path @@ -93,15 +95,6 @@ return nil } -// Checks and possibly modifies a plug -func (iface *i2cInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface)) - } - // Currently nothing is checked on the plug side - 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 { @@ -109,8 +102,7 @@ } cleanedPath := filepath.Clean(path) - spec.AddSnippet(fmt.Sprintf("%s rw,", cleanedPath)) - spec.AddSnippet(fmt.Sprintf("/sys/devices/platform/**.i2c/%s/** rw,", strings.TrimPrefix(path, "/dev/"))) + spec.AddSnippet(fmt.Sprintf(i2cConnectedPlugAppArmor, cleanedPath, strings.TrimPrefix(path, "/dev/"))) return nil } @@ -120,10 +112,9 @@ return nil } const pathPrefix = "/dev/" - const udevRule string = `KERNEL=="%s", TAG+="%s"` for appName := range plug.Apps { tag := udevSnapSecurityName(plug.Snap.Name(), appName) - spec.AddSnippet(fmt.Sprintf(udevRule, strings.TrimPrefix(path, pathPrefix), tag)) + spec.AddSnippet(fmt.Sprintf(i2cConnectedPlugUDev, strings.TrimPrefix(path, pathPrefix), tag)) } return nil } @@ -133,10 +124,6 @@ return true } -func (iface *i2cInterface) ValidateSlot(slot *interfaces.Slot, attrs map[string]interface{}) error { - return nil -} - func init() { registerIface(&i2cInterface{}) } diff -Nru snapd-2.27.5/interfaces/builtin/i2c_test.go snapd-2.28.5/interfaces/builtin/i2c_test.go --- snapd-2.27.5/interfaces/builtin/i2c_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/i2c_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -138,67 +138,38 @@ } func (s *I2cInterfaceSuite) TestSanitizeCoreSnapSlot(c *C) { - err := s.iface.SanitizeSlot(s.testSlot1) - c.Assert(err, IsNil) + c.Assert(s.testSlot1.Sanitize(s.iface), IsNil) } func (s *I2cInterfaceSuite) TestSanitizeGadgetSnapSlot(c *C) { - - err := s.iface.SanitizeSlot(s.testUDev1) - c.Assert(err, IsNil) - - err = s.iface.SanitizeSlot(s.testUDev2) - c.Assert(err, IsNil) - - err = s.iface.SanitizeSlot(s.testUDev3) - c.Assert(err, IsNil) + c.Assert(s.testUDev1.Sanitize(s.iface), IsNil) + c.Assert(s.testUDev2.Sanitize(s.iface), IsNil) + c.Assert(s.testUDev3.Sanitize(s.iface), IsNil) } func (s *I2cInterfaceSuite) TestSanitizeBadGadgetSnapSlot(c *C) { - - err := s.iface.SanitizeSlot(s.testUDevBadValue1) - c.Assert(err, ErrorMatches, "i2c path attribute must be a valid device node") - - err = s.iface.SanitizeSlot(s.testUDevBadValue2) - c.Assert(err, ErrorMatches, "i2c path attribute must be a valid device node") - - err = s.iface.SanitizeSlot(s.testUDevBadValue3) - c.Assert(err, ErrorMatches, "i2c path attribute must be a valid device node") - - err = s.iface.SanitizeSlot(s.testUDevBadValue4) - c.Assert(err, ErrorMatches, "i2c path attribute must be a valid device node") - - err = s.iface.SanitizeSlot(s.testUDevBadValue5) - c.Assert(err, ErrorMatches, "i2c path attribute must be a valid device node") - - err = s.iface.SanitizeSlot(s.testUDevBadValue6) - c.Assert(err, ErrorMatches, "i2c slot must have a path attribute") - - err = s.iface.SanitizeSlot(s.testUDevBadValue7) - c.Assert(err, ErrorMatches, "i2c slot must have a path attribute") - - c.Assert(func() { s.iface.SanitizeSlot(s.testUDevBadInterface1) }, PanicMatches, `slot is not of interface "i2c"`) + 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") } -func (s *I2cInterfaceSuite) TestConnectedPlugUDevSnippets(c *C) { - expectedSnippet1 := `KERNEL=="i2c-1", TAG+="snap_client-snap_app-accessing-1-port"` - +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) - snippet := spec.Snippets()[0] - c.Assert(snippet, Equals, expectedSnippet1) + c.Assert(spec.Snippets()[0], testutil.Contains, `KERNEL=="i2c-1", TAG+="snap_client-snap_app-accessing-1-port"`) } -func (s *I2cInterfaceSuite) TestConnectedPlugAppArmorSnippets(c *C) { - expectedSnippet1 := `/dev/i2c-1 rw, -/sys/devices/platform/**.i2c/i2c-1/** rw,` - apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.testPlugPort1, nil, s.testUDev1, nil) - 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)) +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.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,`) } func (s *I2cInterfaceSuite) TestAutoConnect(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/iio.go snapd-2.28.5/interfaces/builtin/iio.go --- snapd-2.27.5/interfaces/builtin/iio.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/iio.go 2017-09-13 14:47:18.000000000 +0000 @@ -47,8 +47,12 @@ ###IIO_DEVICE_PATH### rw, /sys/bus/iio/devices/###IIO_DEVICE_NAME###/ r, /sys/bus/iio/devices/###IIO_DEVICE_NAME###/** rwk, +/sys/devices/**/###IIO_DEVICE_NAME###/ r, +/sys/devices/**/###IIO_DEVICE_NAME###/** rwk, ` +const iioConnectedPlugUDev = `KERNEL=="%s", TAG+="%s"` + // The type for iio interface type iioInterface struct{} @@ -57,8 +61,8 @@ return "iio" } -func (iface *iioInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *iioInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: iioSummary, BaseDeclarationSlots: iioBaseDeclarationSlots, } @@ -75,15 +79,8 @@ // Check validity of the defined slot func (iface *iioInterface) SanitizeSlot(slot *interfaces.Slot) error { - // Does it have right type? - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface)) - } - - // Creation of the slot of this type - // is allowed only by a gadget or os snap - if !(slot.Snap.Type == "gadget" || slot.Snap.Type == "os") { - return fmt.Errorf("%s slots only allowed on gadget or core snaps", iface.Name()) + if err := sanitizeSlotReservedForOSOrGadget(iface, slot); err != nil { + return err } // Validate the path @@ -101,15 +98,6 @@ return nil } -// Checks and possibly modifies a plug -func (iface *iioInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface)) - } - // Currently nothing is checked on the plug side - 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 { @@ -136,10 +124,9 @@ return nil } const pathPrefix = "/dev/" - const udevRule = `KERNEL=="%s", TAG+="%s"` for appName := range plug.Apps { tag := udevSnapSecurityName(plug.Snap.Name(), appName) - spec.AddSnippet(fmt.Sprintf(udevRule, strings.TrimPrefix(path, pathPrefix), tag)) + spec.AddSnippet(fmt.Sprintf(iioConnectedPlugUDev, strings.TrimPrefix(path, pathPrefix), tag)) } return nil } @@ -149,10 +136,6 @@ return true } -func (iface *iioInterface) ValidateSlot(slot *interfaces.Slot, attrs map[string]interface{}) error { - return nil -} - func init() { registerIface(&iioInterface{}) } diff -Nru snapd-2.27.5/interfaces/builtin/iio_test.go snapd-2.28.5/interfaces/builtin/iio_test.go --- snapd-2.27.5/interfaces/builtin/iio_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/iio_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -143,42 +143,21 @@ } func (s *IioInterfaceSuite) TestSanitizeBadGadgetSnapSlot(c *C) { - - err := s.iface.SanitizeSlot(s.testUDevBadValue1) - c.Assert(err, ErrorMatches, "iio path attribute must be a valid device node") - - err = s.iface.SanitizeSlot(s.testUDevBadValue2) - c.Assert(err, ErrorMatches, "iio path attribute must be a valid device node") - - err = s.iface.SanitizeSlot(s.testUDevBadValue3) - c.Assert(err, ErrorMatches, "iio path attribute must be a valid device node") - - err = s.iface.SanitizeSlot(s.testUDevBadValue4) - c.Assert(err, ErrorMatches, "iio path attribute must be a valid device node") - - err = s.iface.SanitizeSlot(s.testUDevBadValue5) - c.Assert(err, ErrorMatches, "iio path attribute must be a valid device node") - - err = s.iface.SanitizeSlot(s.testUDevBadValue6) - c.Assert(err, ErrorMatches, "iio path attribute must be a valid device node") - - err = s.iface.SanitizeSlot(s.testUDevBadValue7) - c.Assert(err, ErrorMatches, "iio slot must have a path attribute") - - err = s.iface.SanitizeSlot(s.testUDevBadValue8) - c.Assert(err, ErrorMatches, "iio slot must have a path attribute") - - c.Assert(func() { s.iface.SanitizeSlot(s.testUDevBadInterface1) }, PanicMatches, `slot is not of interface "iio"`) + 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") } func (s *IioInterfaceSuite) TestConnectedPlugUDevSnippets(c *C) { - expectedSnippet1 := `KERNEL=="iio:device1", TAG+="snap_client-snap_app-accessing-1-port"` - spec := &udev.Specification{} c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPort1, nil, s.testUDev1, nil), IsNil) c.Assert(spec.Snippets(), HasLen, 1) - snippet := spec.Snippets()[0] - c.Assert(snippet, Equals, expectedSnippet1) + c.Assert(spec.Snippets()[0], Equals, `KERNEL=="iio:device1", TAG+="snap_client-snap_app-accessing-1-port"`) } func (s *IioInterfaceSuite) TestConnectedPlugAppArmorSnippets(c *C) { @@ -188,6 +167,8 @@ /dev/iio:device1 rw, /sys/bus/iio/devices/iio:device1/ r, /sys/bus/iio/devices/iio:device1/** rwk, +/sys/devices/**/iio:device1/ r, +/sys/devices/**/iio:device1/** rwk, ` apparmorSpec := &apparmor.Specification{} err := apparmorSpec.AddConnectedPlug(s.iface, s.testPlugPort1, nil, s.testUDev1, nil) diff -Nru snapd-2.27.5/interfaces/builtin/io_ports_control.go snapd-2.28.5/interfaces/builtin/io_ports_control.go --- snapd-2.27.5/interfaces/builtin/io_ports_control.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/io_ports_control.go 2017-09-13 14:47:18.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 @@ -19,15 +19,6 @@ package builtin -import ( - "fmt" - - "github.com/snapcore/snapd/interfaces" - "github.com/snapcore/snapd/interfaces/apparmor" - "github.com/snapcore/snapd/interfaces/seccomp" - "github.com/snapcore/snapd/interfaces/udev" -) - const ioPortsControlSummary = `allows access to all I/O ports` const ioPortsControlBaseDeclarationSlots = ` @@ -56,76 +47,18 @@ ioperm iopl ` - -// The type for io-ports-control interface -type ioPortsControlInterface struct{} - -// Getter for the name of the io-ports-control interface -func (iface *ioPortsControlInterface) Name() string { - return "io-ports-control" -} - -func (iface *ioPortsControlInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ - Summary: ioPortsControlSummary, - ImplicitOnCore: true, - ImplicitOnClassic: true, - BaseDeclarationSlots: ioPortsControlBaseDeclarationSlots, - } -} - -func (iface *ioPortsControlInterface) String() string { - return iface.Name() -} - -// Check validity of the defined slot -func (iface *ioPortsControlInterface) SanitizeSlot(slot *interfaces.Slot) error { - // Does it have right type? - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface)) - } - - // Creation of the slot of this type - // is allowed only by a gadget or os snap - if !(slot.Snap.Type == "os") { - return fmt.Errorf("%s slots only allowed on core snap", iface.Name()) - } - return nil -} - -// Checks and possibly modifies a plug -func (iface *ioPortsControlInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface)) - } - // Currently nothing is checked on the plug side - return nil -} - -func (iface *ioPortsControlInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - spec.AddSnippet(ioPortsControlConnectedPlugAppArmor) - return nil -} - -func (iface *ioPortsControlInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - const udevRule = `KERNEL=="port", TAG+="%s"` - for appName := range plug.Apps { - tag := udevSnapSecurityName(plug.Snap.Name(), appName) - spec.AddSnippet(fmt.Sprintf(udevRule, tag)) - } - return nil -} - -func (iface *ioPortsControlInterface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - spec.AddSnippet(ioPortsControlConnectedPlugSecComp) - return nil -} - -func (iface *ioPortsControlInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { - // Allow what is allowed in the declarations - return true -} +const ioPortsControlConnectedPlugUDev = `KERNEL=="port", TAG+="###CONNECTED_SECURITY_TAGS###"` func init() { - registerIface(&ioPortsControlInterface{}) + registerIface(&commonInterface{ + name: "io-ports-control", + summary: ioPortsControlSummary, + implicitOnCore: true, + implicitOnClassic: true, + baseDeclarationSlots: ioPortsControlBaseDeclarationSlots, + connectedPlugAppArmor: ioPortsControlConnectedPlugAppArmor, + connectedPlugSecComp: ioPortsControlConnectedPlugSecComp, + connectedPlugUDev: ioPortsControlConnectedPlugUDev, + reservedForOS: true, + }) } diff -Nru snapd-2.27.5/interfaces/builtin/io_ports_control_test.go snapd-2.28.5/interfaces/builtin/io_ports_control_test.go --- snapd-2.27.5/interfaces/builtin/io_ports_control_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/io_ports_control_test.go 2017-09-13 14:47:18.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 @@ -28,7 +28,6 @@ "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" ) @@ -42,26 +41,21 @@ iface: builtin.MustInterface("io-ports-control"), }) -func (s *ioPortsControlInterfaceSuite) SetUpTest(c *C) { - // Mock for OS Snap - osSnapInfo := snaptest.MockInfo(c, ` -name: ubuntu-core +const ioPortsControlConsumerYaml = `name: consumer +apps: + app: + plugs: [io-ports-control] +` + +const ioPortsControlCoreYaml = `name: core type: os slots: - test-io-ports: - interface: io-ports-control -`, nil) - s.slot = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["test-io-ports"]} - - // Snap Consumers - consumingSnapInfo := snaptest.MockInfo(c, ` -name: client-snap -apps: - app-accessing-io-ports: - command: foo - plugs: [io-ports-control] -`, nil) - s.plug = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["io-ports-control"]} + io-ports-control: +` + +func (s *ioPortsControlInterfaceSuite) SetUpTest(c *C) { + s.plug = MockPlug(c, ioPortsControlConsumerYaml, nil, "io-ports-control") + s.slot = MockSlot(c, ioPortsControlCoreYaml, nil, "io-ports-control") } func (s *ioPortsControlInterfaceSuite) TestName(c *C) { @@ -69,69 +63,51 @@ } func (s *ioPortsControlInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "io-ports-control", Interface: "io-ports-control", - }}) - c.Assert(err, ErrorMatches, "io-ports-control slots only allowed on core snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "io-ports-control slots are reserved for the core snap") } func (s *ioPortsControlInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } -func (s *ioPortsControlInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "io-ports-control"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "io-ports-control"`) +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.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "/dev/port rw,") } -func (s *ioPortsControlInterfaceSuite) TestUsedSecuritySystems(c *C) { - expectedSnippet1 := ` -# Description: Allow write access to all I/O ports. -# See 'man 4 mem' for details. - -capability sys_rawio, # required by iopl - -/dev/port rw, -` - expectedSnippet3 := `KERNEL=="port", TAG+="snap_client-snap_app-accessing-io-ports"` - // connected plugs have a non-nil security snippet for apparmor - apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.client-snap.app-accessing-io-ports"}) - aasnippet := apparmorSpec.SnippetForTag("snap.client-snap.app-accessing-io-ports") - c.Assert(aasnippet, Equals, expectedSnippet1, Commentf("\nexpected:\n%s\nfound:\n%s", expectedSnippet1, aasnippet)) +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.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) - snippet := udevSpec.Snippets()[0] - c.Assert(snippet, Equals, expectedSnippet3) + c.Assert(udevSpec.Snippets()[0], Equals, `KERNEL=="port", TAG+="snap_consumer_app"`) } -func (s *ioPortsControlInterfaceSuite) TestConnectedPlugPolicySecComp(c *C) { - expectedSnippet2 := ` -# Description: Allow changes to the I/O port permissions and -# privilege level of the calling process. In addition to granting -# unrestricted I/O port access, running at a higher I/O privilege -# level also allows the process to disable interrupts. This will -# probably crash the system, and is not recommended. -ioperm -iopl +func (s *ioPortsControlInterfaceSuite) TestStaticInfo(c *C) { + si := interfaces.StaticInfoOf(s.iface) + c.Assert(si.ImplicitOnCore, Equals, true) + c.Assert(si.ImplicitOnClassic, Equals, true) + c.Assert(si.Summary, Equals, `allows access to all I/O ports`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "io-ports-control") +} -` - 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.client-snap.app-accessing-io-ports"}) - c.Check(seccompSpec.SnippetForTag("snap.client-snap.app-accessing-io-ports"), Equals, expectedSnippet2) +func (s *ioPortsControlInterfaceSuite) TestAutoConnect(c *C) { + c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) } func (s *ioPortsControlInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/joystick.go snapd-2.28.5/interfaces/builtin/joystick.go --- snapd-2.27.5/interfaces/builtin/joystick.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/joystick.go 2017-09-13 14:47:18.000000000 +0000 @@ -19,14 +19,6 @@ package builtin -import ( - "fmt" - - "github.com/snapcore/snapd/interfaces" - "github.com/snapcore/snapd/interfaces/apparmor" - "github.com/snapcore/snapd/snap" -) - const joystickSummary = `allows access to joystick devices` const joystickBaseDeclarationSlots = ` @@ -46,75 +38,17 @@ /run/udev/data/c13:{[0-9],[12][0-9],3[01]} r, ` -// joystickInterface is the type for joystick interface -type joystickInterface struct{} - -// Name returns the name of the joystick interface. -func (iface *joystickInterface) Name() string { - return "joystick" -} - -func (iface *joystickInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ - Summary: joystickSummary, - ImplicitOnCore: true, - ImplicitOnClassic: true, - BaseDeclarationSlots: joystickBaseDeclarationSlots, - } -} - -// String returns the name of the joystick interface. -func (iface *joystickInterface) String() string { - return iface.Name() -} - -// SanitizeSlot checks the validity of the defined slot. -func (iface *joystickInterface) SanitizeSlot(slot *interfaces.Slot) error { - // Does it have right type? - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface)) - } - - // The snap implementing this slot must be an os snap. - if !(slot.Snap.Type == snap.TypeOS) { - return fmt.Errorf("%s slots only allowed on core snap", iface.Name()) - } - - return nil -} - -// SanitizePlug checks and possibly modifies a plug. -func (iface *joystickInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface)) - } - - // Currently nothing is checked on the plug side - return nil -} - -// AppArmorConnectedPlug adds the necessary appamor snippet to the spec that -// allows access to joystick devices. -func (iface *joystickInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - spec.AddSnippet(joystickConnectedPlugAppArmor) - return nil -} - -// TODO: This interface needs to use udev tagging, see LP: #1675738. -// func (iface *joystickInterface) UdevConnectedPlug(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { -// const udevRule = `KERNEL=="js[0-9]*", TAG+="%s"` -// for appName := range plug.Apps { -// tag := udevSnapSecurityName(plug.Snap.Name(), appName) -// spec.AddSnippet(fmt.Sprintf(udevRule, tag)) -// } -// return nil -// } - -// AutoConnect returns true in order to allow what's in the declarations. -func (iface *joystickInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { - return true -} +const joystickConnectedPlugUDev = `KERNEL=="js[0-9]*", TAG+="###CONNECTED_SECURITY_TAGS###"` func init() { - registerIface(&joystickInterface{}) + registerIface(&commonInterface{ + name: "joystick", + summary: joystickSummary, + implicitOnCore: true, + implicitOnClassic: true, + baseDeclarationSlots: joystickBaseDeclarationSlots, + connectedPlugAppArmor: joystickConnectedPlugAppArmor, + connectedPlugUDev: joystickConnectedPlugUDev, + reservedForOS: true, + }) } diff -Nru snapd-2.27.5/interfaces/builtin/joystick_test.go snapd-2.28.5/interfaces/builtin/joystick_test.go --- snapd-2.27.5/interfaces/builtin/joystick_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/joystick_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -25,8 +25,8 @@ "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/snap/snaptest" "github.com/snapcore/snapd/testutil" ) @@ -40,26 +40,21 @@ iface: builtin.MustInterface("joystick"), }) -func (s *JoystickInterfaceSuite) SetUpTest(c *C) { - // Mock for OS Snap - osSnapInfo := snaptest.MockInfo(c, ` -name: ubuntu-core +const joystickConsumerYaml = `name: consumer +apps: + app: + plugs: [joystick] +` + +const joystickCoreYaml = `name: core type: os slots: - test-joystick: - interface: joystick -`, nil) - s.slot = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["test-joystick"]} - - // Snap Consumers - consumingSnapInfo := snaptest.MockInfo(c, ` -name: client-snap -apps: - app-accessing-joystick: - command: foo - plugs: [joystick] -`, nil) - s.plug = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["joystick"]} + joystick: +` + +func (s *JoystickInterfaceSuite) SetUpTest(c *C) { + s.plug = MockPlug(c, joystickConsumerYaml, nil, "joystick") + s.slot = MockSlot(c, joystickCoreYaml, nil, "joystick") } func (s *JoystickInterfaceSuite) TestName(c *C) { @@ -67,45 +62,44 @@ } func (s *JoystickInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "joystick", Interface: "joystick", - }}) - c.Assert(err, ErrorMatches, "joystick slots only allowed on core snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "joystick slots are reserved for the core snap") } func (s *JoystickInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } -func (s *JoystickInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "joystick"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "joystick"`) +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.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) TestUsedSecuritySystems(c *C) { - expectedSnippet := ` -# Description: Allow reading and writing to joystick devices (/dev/input/js*). +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"`) +} -# Per https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/devices.txt -# 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, -` +func (s *JoystickInterfaceSuite) TestStaticInfo(c *C) { + si := interfaces.StaticInfoOf(s.iface) + c.Assert(si.ImplicitOnCore, Equals, true) + c.Assert(si.ImplicitOnClassic, Equals, true) + c.Assert(si.Summary, Equals, `allows access to joystick devices`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "joystick") +} - // connected plugs have a non-nil security snippet for apparmor - apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.client-snap.app-accessing-joystick"}) - aasnippet := apparmorSpec.SnippetForTag("snap.client-snap.app-accessing-joystick") - c.Assert(aasnippet, Equals, expectedSnippet) +func (s *JoystickInterfaceSuite) TestAutoConnect(c *C) { + c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) } func (s *JoystickInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/kernel_module_control.go snapd-2.28.5/interfaces/builtin/kernel_module_control.go --- snapd-2.27.5/interfaces/builtin/kernel_module_control.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/kernel_module_control.go 2017-09-13 14:47:18.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 @@ -60,6 +60,7 @@ finit_module delete_module ` +const kernelModuleControlConnectedPlugUDev = `KERNEL=="mem", TAG+="###CONNECTED_SECURITY_TAGS###"` func init() { registerIface(&commonInterface{ @@ -71,6 +72,7 @@ baseDeclarationSlots: kernelModuleControlBaseDeclarationSlots, connectedPlugAppArmor: kernelModuleControlConnectedPlugAppArmor, connectedPlugSecComp: kernelModuleControlConnectedPlugSecComp, + connectedPlugUDev: kernelModuleControlConnectedPlugUDev, reservedForOS: true, }) } diff -Nru snapd-2.27.5/interfaces/builtin/kernel_module_control_test.go snapd-2.28.5/interfaces/builtin/kernel_module_control_test.go --- snapd-2.27.5/interfaces/builtin/kernel_module_control_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/kernel_module_control_test.go 2017-09-13 14:47:18.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 @@ -26,8 +26,8 @@ "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/snap/snaptest" "github.com/snapcore/snapd/testutil" ) @@ -37,28 +37,25 @@ plug *interfaces.Plug } -const kernelmodctlMockPlugSnapInfoYaml = `name: other -version: 1.0 +var _ = Suite(&KernelModuleControlInterfaceSuite{ + iface: builtin.MustInterface("kernel-module-control"), +}) + +const kernelmodctlConsumerYaml = `name: consumer apps: - app2: - command: foo + app: plugs: [kernel-module-control] ` -var _ = Suite(&KernelModuleControlInterfaceSuite{ - iface: builtin.MustInterface("kernel-module-control"), -}) +const kernelmodctlCoreYaml = `name: core +type: os +slots: + kernel-module-control: +` func (s *KernelModuleControlInterfaceSuite) SetUpTest(c *C) { - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "kernel-module-control", - Interface: "kernel-module-control", - }, - } - plugSnap := snaptest.MockInfo(c, kernelmodctlMockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["kernel-module-control"]} + s.plug = MockPlug(c, kernelmodctlConsumerYaml, nil, "kernel-module-control") + s.slot = MockSlot(c, kernelmodctlCoreYaml, nil, "kernel-module-control") } func (s *KernelModuleControlInterfaceSuite) TestName(c *C) { @@ -66,42 +63,51 @@ } func (s *KernelModuleControlInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "kernel-module-control", Interface: "kernel-module-control", - }}) - c.Assert(err, ErrorMatches, "kernel-module-control slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "kernel-module-control slots are reserved for the core snap") } func (s *KernelModuleControlInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) + c.Assert(s.plug.Sanitize(s.iface), 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.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.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"`) +} + +func (s *KernelModuleControlInterfaceSuite) 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 insertion, removal and querying of kernel modules`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "kernel-module-control") } -func (s *KernelModuleControlInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "kernel-module-control"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "kernel-module-control"`) -} - -func (s *KernelModuleControlInterfaceSuite) 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) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) - c.Check(apparmorSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "capability sys_module,") - - // 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.app2"}) - c.Check(seccompSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "finit_module\n") +func (s *KernelModuleControlInterfaceSuite) TestAutoConnect(c *C) { + c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) } func (s *KernelModuleControlInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/kubernetes_support_test.go snapd-2.28.5/interfaces/builtin/kubernetes_support_test.go --- snapd-2.27.5/interfaces/builtin/kubernetes_support_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/kubernetes_support_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -66,26 +66,18 @@ } func (s *KubernetesSupportInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "kubernetes-support", Interface: "kubernetes-support", - }}) - c.Assert(err, ErrorMatches, "kubernetes-support slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "kubernetes-support slots are reserved for the core snap") } func (s *KubernetesSupportInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *KubernetesSupportInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "kubernetes-support"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "kubernetes-support"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *KubernetesSupportInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/kvm.go snapd-2.28.5/interfaces/builtin/kvm.go --- snapd-2.27.5/interfaces/builtin/kvm.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/kvm.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,52 @@ +// -*- 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 kvmSummary = `allows access to the kvm device` + +const kvmBaseDeclarationSlots = ` + kvm: + allow-installation: + slot-snap-type: + - core + deny-auto-connection: true +` + +const kvmConnectedPlugAppArmor = ` +# Description: Allow write access to kvm. +# See 'man kvm' for details. + +/dev/kvm rw, +` + +const kvmConnectedPlugUDev = `KERNEL=="kvm", TAG+="###CONNECTED_SECURITY_TAGS###"` + +func init() { + registerIface(&commonInterface{ + name: "kvm", + summary: kvmSummary, + implicitOnCore: true, + implicitOnClassic: true, + baseDeclarationSlots: kvmBaseDeclarationSlots, + connectedPlugAppArmor: kvmConnectedPlugAppArmor, + connectedPlugUDev: kvmConnectedPlugUDev, + reservedForOS: true, + }) +} diff -Nru snapd-2.27.5/interfaces/builtin/kvm_test.go snapd-2.28.5/interfaces/builtin/kvm_test.go --- snapd-2.27.5/interfaces/builtin/kvm_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/kvm_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,112 @@ +// -*- 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 kvmInterfaceSuite struct { + iface interfaces.Interface + slot *interfaces.Slot + plug *interfaces.Plug +} + +var _ = Suite(&kvmInterfaceSuite{ + iface: builtin.MustInterface("kvm"), +}) + +const kvmConsumerYaml = `name: consumer +apps: + app: + plugs: [kvm] +` + +const kvmCoreYaml = `name: core +type: os +slots: + kvm: +` + +func (s *kvmInterfaceSuite) SetUpTest(c *C) { + s.plug = MockPlug(c, kvmConsumerYaml, nil, "kvm") + s.slot = MockSlot(c, kvmCoreYaml, nil, "kvm") +} + +func (s *kvmInterfaceSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "kvm") +} + +func (s *kvmInterfaceSuite) TestSanitizeSlot(c *C) { + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "some-snap"}, + Name: "kvm", + Interface: "kvm", + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "kvm slots are reserved for the core snap") +} + +func (s *kvmInterfaceSuite) TestSanitizePlug(c *C) { + c.Assert(s.plug.Sanitize(s.iface), 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.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) + c.Assert(spec.SnippetForTag("snap.consumer.app"), Equals, ` +# Description: Allow write access to kvm. +# See 'man kvm' for details. + +/dev/kvm rw, +`) +} + +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"`) +} + +func (s *kvmInterfaceSuite) TestStaticInfo(c *C) { + si := interfaces.StaticInfoOf(s.iface) + c.Assert(si.ImplicitOnCore, Equals, true) + c.Assert(si.ImplicitOnClassic, Equals, true) + c.Assert(si.Summary, Equals, `allows access to the kvm device`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "kvm") +} + +func (s *kvmInterfaceSuite) TestAutoConnect(c *C) { + c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) +} + +func (s *kvmInterfaceSuite) TestInterfaces(c *C) { + c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) +} diff -Nru snapd-2.27.5/interfaces/builtin/libvirt_test.go snapd-2.28.5/interfaces/builtin/libvirt_test.go --- snapd-2.27.5/interfaces/builtin/libvirt_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/libvirt_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -57,13 +57,11 @@ } func (s *LibvirtInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, ErrorMatches, ".*libvirt slots are reserved for the operating system snap.*") + c.Assert(s.slot.Sanitize(s.iface), ErrorMatches, ".*libvirt slots are reserved for the core snap.*") } func (s *LibvirtInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *LibvirtInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/locale_control_test.go snapd-2.28.5/interfaces/builtin/locale_control_test.go --- snapd-2.27.5/interfaces/builtin/locale_control_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/locale_control_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -64,26 +64,18 @@ } func (s *LocaleControlInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "locale-control", Interface: "locale-control", - }}) - c.Assert(err, ErrorMatches, "locale-control slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "locale-control slots are reserved for the core snap") } func (s *LocaleControlInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *LocaleControlInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "locale-control"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "locale-control"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *LocaleControlInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/location_control.go snapd-2.28.5/interfaces/builtin/location_control.go --- snapd-2.27.5/interfaces/builtin/location_control.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/location_control.go 2017-09-13 14:47:18.000000000 +0000 @@ -207,8 +207,8 @@ return "location-control" } -func (iface *locationControlInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *locationControlInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: locationControlSummary, BaseDeclarationSlots: locationControlBaseDeclarationSlots, } @@ -245,14 +245,6 @@ return nil } -func (iface *locationControlInterface) SanitizePlug(plug *interfaces.Plug) error { - return nil -} - -func (iface *locationControlInterface) SanitizeSlot(slot *interfaces.Slot) error { - return nil -} - func (iface *locationControlInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff -Nru snapd-2.27.5/interfaces/builtin/location_observe.go snapd-2.28.5/interfaces/builtin/location_observe.go --- snapd-2.27.5/interfaces/builtin/location_observe.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/location_observe.go 2017-09-13 14:47:18.000000000 +0000 @@ -261,8 +261,8 @@ return "location-observe" } -func (iface *locationObserveInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *locationObserveInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: locationObserveSummary, BaseDeclarationSlots: locationObserveBaseDeclarationSlots, } @@ -299,14 +299,6 @@ return nil } -func (iface *locationObserveInterface) SanitizePlug(plug *interfaces.Plug) error { - return nil -} - -func (iface *locationObserveInterface) SanitizeSlot(slot *interfaces.Slot) error { - return nil -} - func (iface *locationObserveInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff -Nru snapd-2.27.5/interfaces/builtin/log_observe_test.go snapd-2.28.5/interfaces/builtin/log_observe_test.go --- snapd-2.27.5/interfaces/builtin/log_observe_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/log_observe_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -64,26 +64,18 @@ } func (s *LogObserveInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "log-observe", Interface: "log-observe", - }}) - c.Assert(err, ErrorMatches, "log-observe slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "log-observe slots are reserved for the core snap") } func (s *LogObserveInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *LogObserveInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "log-observe"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "log-observe"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *LogObserveInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/lxd.go snapd-2.28.5/interfaces/builtin/lxd.go --- snapd-2.27.5/interfaces/builtin/lxd.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/lxd.go 2017-10-11 06:11:17.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 @@ -19,14 +19,6 @@ package builtin -import ( - "fmt" - - "github.com/snapcore/snapd/interfaces" - "github.com/snapcore/snapd/interfaces/apparmor" - "github.com/snapcore/snapd/interfaces/seccomp" -) - const lxdSummary = `allows access to the LXD socket` const lxdBaseDeclarationSlots = ` @@ -51,48 +43,12 @@ socket AF_NETLINK - NETLINK_GENERIC ` -type lxdInterface struct{} - -func (iface *lxdInterface) Name() string { - return "lxd" -} - -func (iface *lxdInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ - Summary: lxdSummary, - BaseDeclarationSlots: lxdBaseDeclarationSlots, - } -} - -func (iface *lxdInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - spec.AddSnippet(lxdConnectedPlugAppArmor) - return nil -} - -func (iface *lxdInterface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - spec.AddSnippet(lxdConnectedPlugSecComp) - return nil -} - -func (iface *lxdInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface.Name())) - } - return nil -} - -func (iface *lxdInterface) SanitizeSlot(slot *interfaces.Slot) error { - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface.Name())) - } - return nil -} - -func (iface *lxdInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { - // allow what declarations allowed - return true -} - func init() { - registerIface(&lxdInterface{}) + registerIface(&commonInterface{ + name: "lxd", + summary: lxdSummary, + baseDeclarationSlots: lxdBaseDeclarationSlots, + connectedPlugAppArmor: lxdConnectedPlugAppArmor, + connectedPlugSecComp: lxdConnectedPlugSecComp, + }) } diff -Nru snapd-2.27.5/interfaces/builtin/lxd_support.go snapd-2.28.5/interfaces/builtin/lxd_support.go --- snapd-2.27.5/interfaces/builtin/lxd_support.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/lxd_support.go 2017-09-13 14:47:18.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 @@ -19,12 +19,6 @@ package builtin -import ( - "github.com/snapcore/snapd/interfaces" - "github.com/snapcore/snapd/interfaces/apparmor" - "github.com/snapcore/snapd/interfaces/seccomp" -) - const lxdSupportSummary = `allows operating as the LXD service` const lxdSupportBaseDeclarationPlugs = ` @@ -55,45 +49,16 @@ @unrestricted ` -type lxdSupportInterface struct{} - -func (iface *lxdSupportInterface) Name() string { - return "lxd-support" -} - -func (iface *lxdSupportInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ - Summary: lxdSupportSummary, - ImplicitOnCore: true, - ImplicitOnClassic: true, - BaseDeclarationPlugs: lxdSupportBaseDeclarationPlugs, - BaseDeclarationSlots: lxdSupportBaseDeclarationSlots, - } -} - -func (iface *lxdSupportInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - spec.AddSnippet(lxdSupportConnectedPlugAppArmor) - return nil -} - -func (iface *lxdSupportInterface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - spec.AddSnippet(lxdSupportConnectedPlugSecComp) - return nil -} - -func (iface *lxdSupportInterface) SanitizePlug(plug *interfaces.Plug) error { - return nil -} - -func (iface *lxdSupportInterface) SanitizeSlot(slot *interfaces.Slot) error { - return nil -} - -func (iface *lxdSupportInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { - // allow what declarations allowed - return true -} - func init() { - registerIface(&lxdSupportInterface{}) + registerIface(&commonInterface{ + name: "lxd-support", + summary: lxdSupportSummary, + implicitOnCore: true, + implicitOnClassic: true, + baseDeclarationSlots: lxdSupportBaseDeclarationSlots, + baseDeclarationPlugs: lxdSupportBaseDeclarationPlugs, + connectedPlugAppArmor: lxdSupportConnectedPlugAppArmor, + connectedPlugSecComp: lxdSupportConnectedPlugSecComp, + reservedForOS: true, + }) } diff -Nru snapd-2.27.5/interfaces/builtin/lxd_support_test.go snapd-2.28.5/interfaces/builtin/lxd_support_test.go --- snapd-2.27.5/interfaces/builtin/lxd_support_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/lxd_support_test.go 2017-09-13 14:47:18.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 @@ -27,7 +27,6 @@ "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" ) @@ -37,28 +36,25 @@ plug *interfaces.Plug } -const lxdsupportMockPlugSnapInfoYaml = `name: lxd -version: 1.0 +var _ = Suite(&LxdSupportInterfaceSuite{ + iface: builtin.MustInterface("lxd-support"), +}) + +const lxdSupportConsumerYaml = `name: consumer apps: app: - command: foo plugs: [lxd-support] ` -var _ = Suite(&LxdSupportInterfaceSuite{ - iface: builtin.MustInterface("lxd-support"), -}) +const lxdSupportCoreYaml = `name: core +type: os +slots: + lxd-support: +` func (s *LxdSupportInterfaceSuite) SetUpTest(c *C) { - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "lxd-support", - Interface: "lxd-support", - }, - } - plugSnap := snaptest.MockInfo(c, lxdsupportMockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["lxd-support"]} + s.plug = MockPlug(c, lxdSupportConsumerYaml, nil, "lxd-support") + s.slot = MockSlot(c, lxdSupportCoreYaml, nil, "lxd-support") } func (s *LxdSupportInterfaceSuite) TestName(c *C) { @@ -66,37 +62,42 @@ } func (s *LxdSupportInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "some-snap"}, + Name: "lxd-support", + Interface: "lxd-support", + }} + + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "lxd-support slots are reserved for the core snap") } func (s *LxdSupportInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } -func (s *LxdSupportInterfaceSuite) 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) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), HasLen, 1) -} - -func (s *LxdSupportInterfaceSuite) TestPermanentSlotPolicyAppArmor(c *C) { - apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.lxd.app"}) - c.Assert(apparmorSpec.SnippetForTag("snap.lxd.app"), testutil.Contains, "/usr/sbin/aa-exec ux,\n") -} - -func (s *LxdSupportInterfaceSuite) TestConnectedPlugPolicySecComp(c *C) { - 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.lxd.app"}) - c.Check(seccompSpec.SnippetForTag("snap.lxd.app"), testutil.Contains, "@unrestricted\n") +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.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.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "@unrestricted\n") +} + +func (s *LxdSupportInterfaceSuite) 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 operating as the LXD service`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "lxd-support") + c.Assert(si.BaseDeclarationPlugs, testutil.Contains, "lxd-support") } func (s *LxdSupportInterfaceSuite) TestAutoConnect(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/lxd_test.go snapd-2.28.5/interfaces/builtin/lxd_test.go --- snapd-2.27.5/interfaces/builtin/lxd_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/lxd_test.go 2017-10-11 06:11:17.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 @@ -38,63 +38,67 @@ var _ = Suite(&LxdInterfaceSuite{ iface: builtin.MustInterface("lxd"), - slot: &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "lxd", - Interface: "lxd", - }, - }, - - plug: &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{ - SuggestedName: "lxd", - }, - Name: "lxd", - Interface: "lxd", - Apps: map[string]*snap.AppInfo{ - "app": { - Snap: &snap.Info{ - SuggestedName: "lxd", - }, - Name: "app"}}, - }, - }, }) +const lxdConsumerYaml = `name: consumer +apps: + app: + plugs: [lxd] +` + +const lxdCoreYaml = `name: core +type: os +slots: + lxd: +` + +func (s *LxdInterfaceSuite) SetUpTest(c *C) { + s.plug = MockPlug(c, lxdConsumerYaml, nil, "lxd") + s.slot = MockSlot(c, lxdCoreYaml, nil, "lxd") +} + func (s *LxdInterfaceSuite) TestName(c *C) { c.Assert(s.iface.Name(), Equals, "lxd") } func (s *LxdInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "some-snap"}, + Name: "lxd", + Interface: "lxd", + }} + + c.Assert(slot.Sanitize(s.iface), IsNil) } func (s *LxdInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) + c.Assert(s.plug.Sanitize(s.iface), 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.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.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) + c.Check(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "shutdown\n") } -func (s *LxdInterfaceSuite) TestConnectedPlugSnippetAppArmor(c *C) { - apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.lxd.app"}) - c.Assert(apparmorSpec.SnippetForTag("snap.lxd.app"), testutil.Contains, "/var/snap/lxd/common/lxd/unix.socket rw,\n") -} - -func (s *LxdInterfaceSuite) TestConnectedPlugSnippetSecComp(c *C) { - 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.lxd.app"}) - c.Check(seccompSpec.SnippetForTag("snap.lxd.app"), testutil.Contains, "shutdown\n") +func (s *LxdInterfaceSuite) TestStaticInfo(c *C) { + si := interfaces.StaticInfoOf(s.iface) + c.Assert(si.ImplicitOnCore, Equals, false) + c.Assert(si.ImplicitOnClassic, Equals, false) + c.Assert(si.Summary, Equals, `allows access to the LXD socket`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "lxd") } func (s *LxdInterfaceSuite) TestAutoConnect(c *C) { - // allow what declarations allowed c.Check(s.iface.AutoConnect(nil, nil), Equals, true) } diff -Nru snapd-2.27.5/interfaces/builtin/maliit.go snapd-2.28.5/interfaces/builtin/maliit.go --- snapd-2.27.5/interfaces/builtin/maliit.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/maliit.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,7 +20,6 @@ package builtin import ( - "fmt" "strings" "github.com/snapcore/snapd/interfaces" @@ -130,8 +129,8 @@ return "maliit" } -func (iface *maliitInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *maliitInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: maliitSummary, BaseDeclarationSlots: maliitBaseDeclarationSlots, } @@ -163,20 +162,6 @@ return nil } -func (iface *maliitInterface) SanitizePlug(slot *interfaces.Plug) error { - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface)) - } - return nil -} - -func (iface *maliitInterface) SanitizeSlot(slot *interfaces.Slot) error { - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface)) - } - return nil -} - func (iface *maliitInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff -Nru snapd-2.27.5/interfaces/builtin/media_hub.go snapd-2.28.5/interfaces/builtin/media_hub.go --- snapd-2.27.5/interfaces/builtin/media_hub.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/media_hub.go 2017-09-13 14:47:18.000000000 +0000 @@ -162,8 +162,8 @@ return "media-hub" } -func (iface *mediaHubInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *mediaHubInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: mediaHubSummary, BaseDeclarationSlots: mediaHubBaseDeclarationSlots, } @@ -193,14 +193,6 @@ return nil } -func (iface *mediaHubInterface) SanitizePlug(plug *interfaces.Plug) error { - return nil -} - -func (iface *mediaHubInterface) SanitizeSlot(slot *interfaces.Slot) error { - return nil -} - func (iface *mediaHubInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff -Nru snapd-2.27.5/interfaces/builtin/mir.go snapd-2.28.5/interfaces/builtin/mir.go --- snapd-2.27.5/interfaces/builtin/mir.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/mir.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,11 +20,13 @@ package builtin import ( + "fmt" "strings" "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/seccomp" + "github.com/snapcore/snapd/interfaces/udev" ) const mirSummary = `allows operating as the Mir server` @@ -91,14 +93,24 @@ deny /{dev,run,var/run}/shm/lttng-ust-* rw, ` +const mirPermanentSlotUdev = ` +KERNEL=="tty[0-9]*", TAG+="%[1]s" + +# input devices +KERNEL=="mice", TAG+="%[1]s" +KERNEL=="mouse[0-9]*", TAG+="%[1]s" +KERNEL=="event[0-9]*", TAG+="%[1]s" +KERNEL=="ts[0-9]*", TAG+="%[1]s" +` + type mirInterface struct{} func (iface *mirInterface) Name() string { return "mir" } -func (iface *mirInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *mirInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: mirSummary, BaseDeclarationSlots: mirBaseDeclarationSlots, } @@ -130,11 +142,11 @@ return nil } -func (iface *mirInterface) SanitizePlug(plug *interfaces.Plug) error { - return nil -} - -func (iface *mirInterface) SanitizeSlot(slot *interfaces.Slot) error { +func (iface *mirInterface) UDevPermanentSlot(spec *udev.Specification, slot *interfaces.Slot) error { + for appName := range slot.Apps { + tag := udevSnapSecurityName(slot.Snap.Name(), appName) + spec.AddSnippet(fmt.Sprintf(mirPermanentSlotUdev, tag)) + } return nil } diff -Nru snapd-2.27.5/interfaces/builtin/mir_test.go snapd-2.28.5/interfaces/builtin/mir_test.go --- snapd-2.27.5/interfaces/builtin/mir_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/mir_test.go 2017-09-13 14:47:18.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 @@ -26,6 +26,7 @@ "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/snaptest" "github.com/snapcore/snapd/testutil" ) @@ -135,6 +136,13 @@ c.Assert(len(snippets), Equals, 0) } +func (s *MirInterfaceSuite) TestUDevSpec(c *C) { + udevSpec := &udev.Specification{} + c.Assert(udevSpec.AddPermanentSlot(s.iface, s.coreSlot), IsNil) + c.Assert(udevSpec.Snippets(), HasLen, 1) + c.Assert(udevSpec.Snippets()[0], testutil.Contains, `KERNEL=="event[0-9]*", TAG+="snap_mir-server_mir"`) +} + func (s *MirInterfaceSuite) TestInterfaces(c *C) { c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) } diff -Nru snapd-2.27.5/interfaces/builtin/modem_manager.go snapd-2.28.5/interfaces/builtin/modem_manager.go --- snapd-2.27.5/interfaces/builtin/modem_manager.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/modem_manager.go 2017-09-15 15:05:07.000000000 +0000 @@ -69,6 +69,7 @@ /sys/devices/**/usb**/descriptors r, include +/run/systemd/resolve/stub-resolv.conf r, # DBus accesses include @@ -1187,14 +1188,18 @@ LABEL="mm_candidate_end" ` +const modemManagerPermanentSlotUDevTag = ` +KERNEL=="tty[A-Z]*[0-9]*|cdc-wdm[0-9]*", TAG+="###CONNECTED_SECURITY_TAGS###" +` + type modemManagerInterface struct{} func (iface *modemManagerInterface) Name() string { return "modem-manager" } -func (iface *modemManagerInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *modemManagerInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: modemManagerSummary, ImplicitOnClassic: true, BaseDeclarationSlots: modemManagerBaseDeclarationSlots, @@ -1228,7 +1233,13 @@ } func (iface *modemManagerInterface) UDevPermanentSlot(spec *udev.Specification, slot *interfaces.Slot) error { - spec.AddSnippet(modemManagerPermanentSlotUDev) + old := "###CONNECTED_SECURITY_TAGS###" + udevRule := modemManagerPermanentSlotUDev + for appName := range slot.Apps { + tag := udevSnapSecurityName(slot.Snap.Name(), appName) + udevRule += strings.Replace(modemManagerPermanentSlotUDevTag, old, tag, -1) + } + spec.AddSnippet(udevRule) return nil } @@ -1245,14 +1256,6 @@ return nil } -func (iface *modemManagerInterface) SanitizePlug(plug *interfaces.Plug) error { - return nil -} - -func (iface *modemManagerInterface) SanitizeSlot(slot *interfaces.Slot) error { - return nil -} - func (iface *modemManagerInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff -Nru snapd-2.27.5/interfaces/builtin/modem_manager_test.go snapd-2.28.5/interfaces/builtin/modem_manager_test.go --- snapd-2.27.5/interfaces/builtin/modem_manager_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/modem_manager_test.go 2017-09-13 14:47:18.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 @@ -209,6 +209,7 @@ c.Assert(udevSpec.AddPermanentSlot(s.iface, s.slot), IsNil) c.Assert(udevSpec.Snippets(), HasLen, 1) c.Assert(udevSpec.Snippets()[0], testutil.Contains, `SUBSYSTEMS=="usb"`) + c.Assert(udevSpec.Snippets()[0], testutil.Contains, `KERNEL=="tty[A-Z]*[0-9]*|cdc-wdm[0-9]*", TAG+="snap_modem-manager_mm"`) } func (s *ModemManagerInterfaceSuite) TestPermanentSlotDBus(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/mount_observe_test.go snapd-2.28.5/interfaces/builtin/mount_observe_test.go --- snapd-2.27.5/interfaces/builtin/mount_observe_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/mount_observe_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -64,26 +64,17 @@ } func (s *MountObserveInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "mount-observe", Interface: "mount-observe", - }}) - c.Assert(err, ErrorMatches, "mount-observe slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, "mount-observe slots are reserved for the core snap") } func (s *MountObserveInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *MountObserveInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "mount-observe"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "mount-observe"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *MountObserveInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/mpris.go snapd-2.28.5/interfaces/builtin/mpris.go --- snapd-2.27.5/interfaces/builtin/mpris.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/mpris.go 2017-09-13 14:47:18.000000000 +0000 @@ -157,8 +157,8 @@ return "mpris" } -func (iface *mprisInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *mprisInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: mprisSummary, BaseDeclarationSlots: mprisBaseDeclarationSlots, } @@ -220,15 +220,7 @@ return mprisName, nil } -func (iface *mprisInterface) SanitizePlug(slot *interfaces.Plug) error { - return nil -} - func (iface *mprisInterface) SanitizeSlot(slot *interfaces.Slot) error { - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface)) - } - _, err := iface.getName(slot.Attrs) return err } @@ -238,14 +230,6 @@ return true } -func (iface *mprisInterface) ValidatePlug(plug *interfaces.Plug, attrs map[string]interface{}) error { - return nil -} - -func (iface *mprisInterface) ValidateSlot(slot *interfaces.Slot, attrs map[string]interface{}) error { - return nil -} - func init() { registerIface(&mprisInterface{}) } diff -Nru snapd-2.27.5/interfaces/builtin/netlink_audit_test.go snapd-2.28.5/interfaces/builtin/netlink_audit_test.go --- snapd-2.27.5/interfaces/builtin/netlink_audit_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/netlink_audit_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -65,26 +65,18 @@ } func (s *NetlinkAuditInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "netlink-audit", Interface: "netlink-audit", - }}) - c.Assert(err, ErrorMatches, "netlink-audit slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "netlink-audit slots are reserved for the core snap") } func (s *NetlinkAuditInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *NetlinkAuditInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "netlink-audit"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "netlink-audit"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *NetlinkAuditInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/netlink_connector_test.go snapd-2.28.5/interfaces/builtin/netlink_connector_test.go --- snapd-2.27.5/interfaces/builtin/netlink_connector_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/netlink_connector_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -65,26 +65,18 @@ } func (s *NetlinkConnectorInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "netlink-connector", Interface: "netlink-connector", - }}) - c.Assert(err, ErrorMatches, "netlink-connector slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "netlink-connector slots are reserved for the core snap") } func (s *NetlinkConnectorInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *NetlinkConnectorInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "netlink-connector"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "netlink-connector"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *NetlinkConnectorInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/network_bind.go snapd-2.28.5/interfaces/builtin/network_bind.go --- snapd-2.27.5/interfaces/builtin/network_bind.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/network_bind.go 2017-09-15 15:05:07.000000000 +0000 @@ -32,6 +32,7 @@ const networkBindConnectedPlugAppArmor = ` # Description: Can access the network as a server. #include +/run/systemd/resolve/stub-resolv.conf r, # systemd-resolved (not yet included in nameservice abstraction) # diff -Nru snapd-2.27.5/interfaces/builtin/network_bind_test.go snapd-2.28.5/interfaces/builtin/network_bind_test.go --- snapd-2.27.5/interfaces/builtin/network_bind_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/network_bind_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -65,26 +65,18 @@ } func (s *NetworkBindInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "network-bind", Interface: "network-bind", - }}) - c.Assert(err, ErrorMatches, "network-bind slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "network-bind slots are reserved for the core snap") } func (s *NetworkBindInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *NetworkBindInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "network-bind"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "network-bind"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *NetworkBindInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/network_control.go snapd-2.28.5/interfaces/builtin/network_control.go --- snapd-2.27.5/interfaces/builtin/network_control.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/network_control.go 2017-10-12 16:34:56.000000000 +0000 @@ -36,6 +36,7 @@ # trusted apps. #include +/run/systemd/resolve/stub-resolv.conf r, # systemd-resolved (not yet included in nameservice abstraction) # @@ -164,11 +165,11 @@ /etc/rpc r, -# TUN/TAP +# TUN/TAP - https://www.kernel.org/doc/Documentation/networking/tuntap.txt +# +# We only need to tag /dev/net/tun since the tap[0-9]* and tun[0-9]* devices +# are virtual and don't show up in /dev /dev/net/tun rw, -# These are dynamically created via ioctl() on /dev/net/tun -/dev/tun[0-9]{,[0-9]*} rw, -/dev/tap[0-9]{,[0-9]*} rw, # access to bridge sysfs interfaces for bridge settings /sys/devices/virtual/net/*/bridge/* rw, @@ -244,6 +245,19 @@ socket AF_NETLINK - NETLINK_ISCSI socket AF_NETLINK - NETLINK_RDMA socket AF_NETLINK - NETLINK_GENERIC + +# for receiving kobject_uevent() net messages from the kernel +socket AF_NETLINK - NETLINK_KOBJECT_UEVENT +` + +/* https://www.kernel.org/doc/Documentation/networking/tuntap.txt + * + * We only need to tag /dev/net/tun since the tap[0-9]* and tun[0-9]* devices + * are virtual and don't show up in /dev + */ +const networkControlConnectedPlugUDev = ` +KERNEL=="rfkill", TAG+="###CONNECTED_SECURITY_TAGS###" +KERNEL=="tun", TAG+="###CONNECTED_SECURITY_TAGS###" ` func init() { @@ -255,6 +269,8 @@ baseDeclarationSlots: networkControlBaseDeclarationSlots, connectedPlugAppArmor: networkControlConnectedPlugAppArmor, connectedPlugSecComp: networkControlConnectedPlugSecComp, + connectedPlugUDev: networkControlConnectedPlugUDev, reservedForOS: true, }) + } diff -Nru snapd-2.27.5/interfaces/builtin/network_control_test.go snapd-2.28.5/interfaces/builtin/network_control_test.go --- snapd-2.27.5/interfaces/builtin/network_control_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/network_control_test.go 2017-10-12 16:34:56.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 @@ -26,8 +26,8 @@ "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/snap/snaptest" "github.com/snapcore/snapd/testutil" ) @@ -37,28 +37,25 @@ plug *interfaces.Plug } -const netctlMockPlugSnapInfoYaml = `name: other -version: 1.0 +var _ = Suite(&NetworkControlInterfaceSuite{ + iface: builtin.MustInterface("network-control"), +}) + +const networkControlConsumerYaml = `name: consumer apps: - app2: - command: foo + app: plugs: [network-control] ` -var _ = Suite(&NetworkControlInterfaceSuite{ - iface: builtin.MustInterface("network-control"), -}) +const networkControlCoreYaml = `name: core +type: os +slots: + network-control: +` func (s *NetworkControlInterfaceSuite) SetUpTest(c *C) { - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "network-control", - Interface: "network-control", - }, - } - plugSnap := snaptest.MockInfo(c, netctlMockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["network-control"]} + s.plug = MockPlug(c, networkControlConsumerYaml, nil, "network-control") + s.slot = MockSlot(c, networkControlCoreYaml, nil, "network-control") } func (s *NetworkControlInterfaceSuite) TestName(c *C) { @@ -66,44 +63,52 @@ } func (s *NetworkControlInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "network-control", Interface: "network-control", - }}) - c.Assert(err, ErrorMatches, "network-control slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "network-control slots are reserved for the core snap") } func (s *NetworkControlInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } -func (s *NetworkControlInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "network-control"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "network-control"`) -} - -func (s *NetworkControlInterfaceSuite) 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) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) - c.Assert(apparmorSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "/run/netns/* rw,\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) - c.Assert(err, IsNil) - c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"}) - c.Check(seccompSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "setns - CLONE_NEWNET\n") +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.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.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, 1) + c.Assert(spec.Snippets()[0], testutil.Contains, `KERNEL=="tun", TAG+="snap_consumer_app"`) +} + +func (s *NetworkControlInterfaceSuite) TestStaticInfo(c *C) { + si := interfaces.StaticInfoOf(s.iface) + c.Assert(si.ImplicitOnCore, Equals, true) + c.Assert(si.ImplicitOnClassic, Equals, true) + c.Assert(si.Summary, Equals, `allows configuring networking and network namespaces`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "network-control") +} + +func (s *NetworkControlInterfaceSuite) TestAutoConnect(c *C) { + c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) +} func (s *NetworkControlInterfaceSuite) TestInterfaces(c *C) { c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) } diff -Nru snapd-2.27.5/interfaces/builtin/network.go snapd-2.28.5/interfaces/builtin/network.go --- snapd-2.27.5/interfaces/builtin/network.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/network.go 2017-09-15 15:05:07.000000000 +0000 @@ -28,16 +28,11 @@ - core ` -const networkDescription = ` -The network interface allows connected plugs to access the network as a client. - -The core snap provides the slot that is shared by all the snaps. -` - // http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/apparmor/policygroups/ubuntu-core/16.04/network const networkConnectedPlugAppArmor = ` # Description: Can access the network as a client. #include +/run/systemd/resolve/stub-resolv.conf r, # systemd-resolved (not yet included in nameservice abstraction) # @@ -81,7 +76,6 @@ registerIface(&commonInterface{ name: "network", summary: networkSummary, - description: networkDescription, implicitOnCore: true, implicitOnClassic: true, baseDeclarationSlots: networkBaseDeclarationSlots, diff -Nru snapd-2.27.5/interfaces/builtin/network_manager.go snapd-2.28.5/interfaces/builtin/network_manager.go --- snapd-2.27.5/interfaces/builtin/network_manager.go 2017-08-29 13:52:46.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/network_manager.go 2017-09-15 15:05:07.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/interfaces/udev" "github.com/snapcore/snapd/release" ) @@ -113,6 +114,7 @@ /etc/resolvconf/update.d/* ix, #include +/run/systemd/resolve/stub-resolv.conf r, # Explicitly deny plugging snaps from ptracing the slot to silence noisy # denials. Neither the NetworkManager service nor nmcli require ptrace @@ -412,14 +414,16 @@ 2048 ` +const networkManagerPermanentSlotUdev = `KERNEL=="rfkill", TAG+="###CONNECTED_SECURITY_TAGS###"` + type networkManagerInterface struct{} func (iface *networkManagerInterface) Name() string { return "network-manager" } -func (iface *networkManagerInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *networkManagerInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: networkManagerSummary, ImplicitOnClassic: true, BaseDeclarationSlots: networkManagerBaseDeclarationSlots, @@ -464,11 +468,13 @@ return nil } -func (iface *networkManagerInterface) SanitizePlug(plug *interfaces.Plug) error { - return nil -} - -func (iface *networkManagerInterface) SanitizeSlot(slot *interfaces.Slot) error { +func (iface *networkManagerInterface) UDevPermanentSlot(spec *udev.Specification, slot *interfaces.Slot) error { + old := "###CONNECTED_SECURITY_TAGS###" + for appName := range slot.Apps { + tag := udevSnapSecurityName(slot.Snap.Name(), appName) + udevRule := strings.Replace(networkManagerPermanentSlotUdev, old, tag, -1) + spec.AddSnippet(udevRule) + } return nil } diff -Nru snapd-2.27.5/interfaces/builtin/network_manager_test.go snapd-2.28.5/interfaces/builtin/network_manager_test.go --- snapd-2.27.5/interfaces/builtin/network_manager_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/network_manager_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -27,6 +27,7 @@ "github.com/snapcore/snapd/interfaces/builtin" "github.com/snapcore/snapd/interfaces/dbus" "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/snap/snaptest" @@ -191,6 +192,13 @@ c.Check(seccompSpec.SnippetForTag("snap.network-manager.nm"), testutil.Contains, "listen\n") } +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"`) +} + func (s *NetworkManagerInterfaceSuite) TestInterfaces(c *C) { c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) } diff -Nru snapd-2.27.5/interfaces/builtin/network_observe.go snapd-2.28.5/interfaces/builtin/network_observe.go --- snapd-2.27.5/interfaces/builtin/network_observe.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/network_observe.go 2017-10-11 07:09:14.000000000 +0000 @@ -41,6 +41,7 @@ #capability net_admin, #include +/run/systemd/resolve/stub-resolv.conf r, # systemd-resolved (not yet included in nameservice abstraction) # @@ -117,6 +118,9 @@ # network devices /sys/devices/**/net/** r, + +# for receiving kobject_uevent() net messages from the kernel +network netlink raw, ` // http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/seccomp/policygroups/ubuntu-core/16.04/network-observe @@ -139,6 +143,9 @@ # multicast statistics socket AF_NETLINK - NETLINK_GENERIC + +# for receiving kobject_uevent() net messages from the kernel +socket AF_NETLINK - NETLINK_KOBJECT_UEVENT ` func init() { diff -Nru snapd-2.27.5/interfaces/builtin/network_observe_test.go snapd-2.28.5/interfaces/builtin/network_observe_test.go --- snapd-2.27.5/interfaces/builtin/network_observe_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/network_observe_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -66,26 +66,18 @@ } func (s *NetworkObserveInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "network-observe", Interface: "network-observe", - }}) - c.Assert(err, ErrorMatches, "network-observe slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "network-observe slots are reserved for the core snap") } func (s *NetworkObserveInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *NetworkObserveInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "network-observe"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "network-observe"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *NetworkObserveInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/network_setup_control_test.go snapd-2.28.5/interfaces/builtin/network_setup_control_test.go --- snapd-2.27.5/interfaces/builtin/network_setup_control_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/network_setup_control_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -63,26 +63,18 @@ } func (s *NetworkSetupControlInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "network-setup-control", Interface: "network-setup-control", - }}) - c.Assert(err, ErrorMatches, "network-setup-control slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "network-setup-control slots are reserved for the core snap") } func (s *NetworkSetupControlInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *NetworkSetupControlInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "network-setup-control"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "network-setup-control"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *NetworkSetupControlInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/network_setup_observe_test.go snapd-2.28.5/interfaces/builtin/network_setup_observe_test.go --- snapd-2.27.5/interfaces/builtin/network_setup_observe_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/network_setup_observe_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -64,26 +64,18 @@ } func (s *NetworkSetupObserveInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "network-setup-observe", Interface: "network-setup-observe", - }}) - c.Assert(err, ErrorMatches, "network-setup-observe slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "network-setup-observe slots are reserved for the core snap") } func (s *NetworkSetupObserveInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *NetworkSetupObserveInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "network-setup-observe"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "network-setup-observe"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *NetworkSetupObserveInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/network_status.go snapd-2.28.5/interfaces/builtin/network_status.go --- snapd-2.27.5/interfaces/builtin/network_status.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/network_status.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,7 +20,6 @@ package builtin import ( - "fmt" "strings" "github.com/snapcore/snapd/interfaces" @@ -106,8 +105,8 @@ return "network-status" } -func (iface *networkStatusInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *networkStatusInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: networkStatusSummary, BaseDeclarationSlots: networkStatusBaseDeclarationSlots, } @@ -137,20 +136,6 @@ return nil } -func (iface *networkStatusInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface.Name())) - } - return nil -} - -func (iface *networkStatusInterface) SanitizeSlot(slot *interfaces.Slot) error { - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface.Name())) - } - return nil -} - func (iface *networkStatusInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff -Nru snapd-2.27.5/interfaces/builtin/network_status_test.go snapd-2.28.5/interfaces/builtin/network_status_test.go --- snapd-2.27.5/interfaces/builtin/network_status_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/network_status_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -26,7 +26,6 @@ "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" ) @@ -67,13 +66,6 @@ c.Check(s.iface.Name(), Equals, "network-status") } -func (s *NetworkStatusSuite) TestSanitizeIncorrectInterface(c *C) { - c.Check(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "network-status"`) - c.Check(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "network-status"`) -} - func (s *NetworkStatusSuite) TestAppArmorConnectedPlug(c *C) { spec := &apparmor.Specification{} c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) diff -Nru snapd-2.27.5/interfaces/builtin/network_test.go snapd-2.28.5/interfaces/builtin/network_test.go --- snapd-2.27.5/interfaces/builtin/network_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/network_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -66,26 +66,18 @@ } func (s *NetworkInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "network", Interface: "network", - }}) - c.Assert(err, ErrorMatches, "network slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "network slots are reserved for the core snap") } func (s *NetworkInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *NetworkInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "network"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "network"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *NetworkInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/ofono.go snapd-2.28.5/interfaces/builtin/ofono.go --- snapd-2.27.5/interfaces/builtin/ofono.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/ofono.go 2017-09-15 15:05:07.000000000 +0000 @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2016 Canonical Ltd + * 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 @@ -88,6 +88,7 @@ network bluetooth, include +/run/systemd/resolve/stub-resolv.conf r, # DBus accesses include @@ -288,14 +289,28 @@ LABEL="ofono_speedup_end" ` +/* + 1.Linux modem drivers set up the modem device /dev/modem as a symbolic link + to the actual device to /dev/ttyS* + 2./dev/socket/rild is just a socket, not device node created by rild daemon. + Similar case for chnlat*. + So we intetionally skipped modem, rild and chnlat. +*/ +const ofonoPermanentSlotUDevTag = ` +KERNEL=="tty[A-Z]*[0-9]*|cdc-wdm[0-9]*", TAG+="###CONNECTED_SECURITY_TAGS###" +KERNEL=="tun", TAG+="###CONNECTED_SECURITY_TAGS###" +KERNEL=="tun[0-9]*", TAG+="###CONNECTED_SECURITY_TAGS###" +KERNEL=="dsp", TAG+="###CONNECTED_SECURITY_TAGS###" +` + type ofonoInterface struct{} func (iface *ofonoInterface) Name() string { return "ofono" } -func (iface *ofonoInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *ofonoInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: ofonoSummary, ImplicitOnClassic: true, BaseDeclarationSlots: ofonoBaseDeclarationSlots, @@ -325,7 +340,14 @@ } func (iface *ofonoInterface) UDevPermanentSlot(spec *udev.Specification, slot *interfaces.Slot) error { - spec.AddSnippet(ofonoPermanentSlotUDev) + old := "###CONNECTED_SECURITY_TAGS###" + udevRule := ofonoPermanentSlotUDev + for appName := range slot.Apps { + tag := udevSnapSecurityName(slot.Snap.Name(), appName) + udevRule += strings.Replace(ofonoPermanentSlotUDevTag, old, tag, -1) + spec.AddSnippet(udevRule) + } + spec.AddSnippet(udevRule) return nil } @@ -341,14 +363,6 @@ return nil } -func (iface *ofonoInterface) SanitizePlug(plug *interfaces.Plug) error { - return nil -} - -func (iface *ofonoInterface) SanitizeSlot(slot *interfaces.Slot) error { - return nil -} - func (iface *ofonoInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff -Nru snapd-2.27.5/interfaces/builtin/ofono_test.go snapd-2.28.5/interfaces/builtin/ofono_test.go --- snapd-2.27.5/interfaces/builtin/ofono_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/ofono_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2016 Canonical Ltd + * 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 @@ -26,6 +26,7 @@ "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/snap/snaptest" @@ -196,6 +197,14 @@ c.Assert(seccompSpec.SnippetForTag("snap.ofono.app"), testutil.Contains, "listen\n") } +func (s *OfonoInterfaceSuite) TestPermanentSlotSnippetUDev(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], testutil.Contains, `LABEL="ofono_isi_end"`) + c.Assert(spec.Snippets()[0], testutil.Contains, `KERNEL=="tty[A-Z]*[0-9]*|cdc-wdm[0-9]*", TAG+="snap_ofono_app"`) +} + func (s *OfonoInterfaceSuite) TestInterfaces(c *C) { c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) } diff -Nru snapd-2.27.5/interfaces/builtin/online_accounts_service.go snapd-2.28.5/interfaces/builtin/online_accounts_service.go --- snapd-2.27.5/interfaces/builtin/online_accounts_service.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/online_accounts_service.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,7 +20,6 @@ package builtin import ( - "fmt" "strings" "github.com/snapcore/snapd/interfaces" @@ -104,8 +103,8 @@ return "online-accounts-service" } -func (iface *onlineAccountsServiceInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *onlineAccountsServiceInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: onlineAccountsServiceSummary, BaseDeclarationSlots: onlineAccountsServiceBaseDeclarationSlots, } @@ -135,20 +134,6 @@ return nil } -func (iface *onlineAccountsServiceInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface.Name())) - } - return nil -} - -func (iface *onlineAccountsServiceInterface) SanitizeSlot(slot *interfaces.Slot) error { - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface.Name())) - } - return nil -} - func (iface *onlineAccountsServiceInterface) AutoConnect(plug *interfaces.Plug, slot *interfaces.Slot) bool { return true } diff -Nru snapd-2.27.5/interfaces/builtin/online_accounts_service_test.go snapd-2.28.5/interfaces/builtin/online_accounts_service_test.go --- snapd-2.27.5/interfaces/builtin/online_accounts_service_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/online_accounts_service_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -26,7 +26,6 @@ "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" ) @@ -71,15 +70,8 @@ } func (s *OnlineAccountsServiceInterfaceSuite) TestSanitize(c *C) { - c.Assert(s.iface.SanitizePlug(s.plug), IsNil) - c.Assert(s.iface.SanitizeSlot(s.slot), IsNil) -} - -func (s *OnlineAccountsServiceInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "online-accounts-service"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "online-accounts-service"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) + c.Assert(s.slot.Sanitize(s.iface), IsNil) } func (s *OnlineAccountsServiceInterfaceSuite) TestAppArmorConnectedPlug(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/opengl.go snapd-2.28.5/interfaces/builtin/opengl.go --- snapd-2.27.5/interfaces/builtin/opengl.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/opengl.go 2017-10-11 17:40:25.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 @@ -40,20 +40,25 @@ # nvidia @{PROC}/driver/nvidia/params r, @{PROC}/modules r, - /dev/nvidiactl rw, - /dev/nvidia-modeset rw, /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/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: @@ -63,6 +68,13 @@ /run/udev/data/c226:[0-9]* r, # 226 drm ` +// The nvidia modules don't use sysfs (therefore they can't be udev tagged) and +// will be added by snap-confine. +const openglConnectedPlugUDev = ` +SUBSYSTEM=="drm", KERNEL=="card[0-9]*", TAG+="###CONNECTED_SECURITY_TAGS###" +KERNEL=="vchiq", TAG+="###CONNECTED_SECURITY_TAGS###" +` + func init() { registerIface(&commonInterface{ name: "opengl", @@ -71,6 +83,7 @@ implicitOnClassic: true, baseDeclarationSlots: openglBaseDeclarationSlots, connectedPlugAppArmor: openglConnectedPlugAppArmor, + connectedPlugUDev: openglConnectedPlugUDev, reservedForOS: true, }) } diff -Nru snapd-2.27.5/interfaces/builtin/opengl_test.go snapd-2.28.5/interfaces/builtin/opengl_test.go --- snapd-2.27.5/interfaces/builtin/opengl_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/opengl_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,107 @@ +// -*- 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 OpenglInterfaceSuite struct { + iface interfaces.Interface + slot *interfaces.Slot + plug *interfaces.Plug +} + +var _ = Suite(&OpenglInterfaceSuite{ + iface: builtin.MustInterface("opengl"), +}) + +const openglConsumerYaml = `name: consumer +apps: + app: + plugs: [opengl] +` + +const openglCoreYaml = `name: core +type: os +slots: + opengl: +` + +func (s *OpenglInterfaceSuite) SetUpTest(c *C) { + s.plug = MockPlug(c, openglConsumerYaml, nil, "opengl") + s.slot = MockSlot(c, openglCoreYaml, nil, "opengl") +} + +func (s *OpenglInterfaceSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "opengl") +} + +func (s *OpenglInterfaceSuite) TestSanitizeSlot(c *C) { + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "some-snap"}, + Name: "opengl", + Interface: "opengl", + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "opengl slots are reserved for the core snap") +} + +func (s *OpenglInterfaceSuite) TestSanitizePlug(c *C) { + c.Assert(s.plug.Sanitize(s.iface), 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.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, 1) + c.Assert(spec.Snippets()[0], testutil.Contains, `SUBSYSTEM=="drm", KERNEL=="card[0-9]*", TAG+="snap_consumer_app"`) +} + +func (s *OpenglInterfaceSuite) TestStaticInfo(c *C) { + si := interfaces.StaticInfoOf(s.iface) + c.Assert(si.ImplicitOnCore, Equals, true) + c.Assert(si.ImplicitOnClassic, Equals, true) + c.Assert(si.Summary, Equals, `allows access to OpenGL stack`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "opengl") +} + +func (s *OpenglInterfaceSuite) TestAutoConnect(c *C) { + c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) +} + +func (s *OpenglInterfaceSuite) TestInterfaces(c *C) { + c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) +} diff -Nru snapd-2.27.5/interfaces/builtin/openvswitch_support.go snapd-2.28.5/interfaces/builtin/openvswitch_support.go --- snapd-2.27.5/interfaces/builtin/openvswitch_support.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/openvswitch_support.go 2017-09-13 14:47:18.000000000 +0000 @@ -29,16 +29,21 @@ deny-auto-connection: true ` +const openvswitchSupportConnectedPlugAppArmor = ` +/run/uuidd/request rw, +` + var openvswitchSupportConnectedPlugKmod = []string{`openvswitch`} func init() { registerIface(&commonInterface{ name: "openvswitch-support", - summary: openvswitchSummary, + summary: openvswitchSupportSummary, implicitOnCore: true, implicitOnClassic: true, baseDeclarationSlots: openvswitchSupportBaseDeclarationSlots, connectedPlugKModModules: openvswitchSupportConnectedPlugKmod, + connectedPlugAppArmor: openvswitchSupportConnectedPlugAppArmor, reservedForOS: true, }) } diff -Nru snapd-2.27.5/interfaces/builtin/openvswitch_support_test.go snapd-2.28.5/interfaces/builtin/openvswitch_support_test.go --- snapd-2.27.5/interfaces/builtin/openvswitch_support_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/openvswitch_support_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -23,9 +23,11 @@ . "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/kmod" "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snaptest" "github.com/snapcore/snapd/testutil" ) @@ -53,31 +55,42 @@ }, }) +func (s *OpenvSwitchSupportInterfaceSuite) SetUpTest(c *C) { + var mockPlugSnapInfoYaml = `name: other +version: 1.0 +apps: + app: + 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", + }, + } + snapInfo := snaptest.MockInfo(c, mockPlugSnapInfoYaml, nil) + s.plug = &interfaces.Plug{PlugInfo: snapInfo.Plugs["openvswitch-support"]} +} + func (s *OpenvSwitchSupportInterfaceSuite) TestName(c *C) { c.Assert(s.iface.Name(), Equals, "openvswitch-support") } func (s *OpenvSwitchSupportInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "openvswitch-support", Interface: "openvswitch-support", - }}) - c.Assert(err, ErrorMatches, "openvswitch-support slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "openvswitch-support slots are reserved for the core snap") } func (s *OpenvSwitchSupportInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *OpenvSwitchSupportInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "openvswitch-support"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "openvswitch-support"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *OpenvSwitchSupportInterfaceSuite) TestUsedSecuritySystems(c *C) { @@ -87,6 +100,12 @@ c.Assert(spec.Modules(), DeepEquals, map[string]bool{ "openvswitch": true, }) + + apparmorSpec := &apparmor.Specification{} + err = apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) + 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") } func (s *OpenvSwitchSupportInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/openvswitch_test.go snapd-2.28.5/interfaces/builtin/openvswitch_test.go --- snapd-2.27.5/interfaces/builtin/openvswitch_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/openvswitch_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -64,26 +64,18 @@ } func (s *OpenvSwitchInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "openvswitch", Interface: "openvswitch", - }}) - c.Assert(err, ErrorMatches, "openvswitch slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "openvswitch slots are reserved for the core snap") } func (s *OpenvSwitchInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *OpenvSwitchInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "openvswitch"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "openvswitch"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *OpenvSwitchInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/optical_drive.go snapd-2.28.5/interfaces/builtin/optical_drive.go --- snapd-2.27.5/interfaces/builtin/optical_drive.go 2017-08-24 08:12:52.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/optical_drive.go 2017-09-13 14:47:18.000000000 +0000 @@ -36,6 +36,11 @@ /run/udev/data/b11:[0-9]* r, ` +const opticalDriveConnectedPlugUDev = ` +KERNEL=="sr[0-9]*", TAG+="###CONNECTED_SECURITY_TAGS###" +KERNEL=="scd[0-9]*", TAG+="###CONNECTED_SECURITY_TAGS###" +` + func init() { registerIface(&commonInterface{ name: "optical-drive", @@ -43,6 +48,7 @@ implicitOnClassic: true, baseDeclarationSlots: opticalDriveBaseDeclarationSlots, connectedPlugAppArmor: opticalDriveConnectedPlugAppArmor, + connectedPlugUDev: opticalDriveConnectedPlugUDev, reservedForOS: true, }) } diff -Nru snapd-2.27.5/interfaces/builtin/optical_drive_test.go snapd-2.28.5/interfaces/builtin/optical_drive_test.go --- snapd-2.27.5/interfaces/builtin/optical_drive_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/optical_drive_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,107 @@ +// -*- 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 OpticalDriveInterfaceSuite struct { + iface interfaces.Interface + slot *interfaces.Slot + plug *interfaces.Plug +} + +var _ = Suite(&OpticalDriveInterfaceSuite{ + iface: builtin.MustInterface("optical-drive"), +}) + +const opticalDriveConsumerYaml = `name: consumer +apps: + app: + plugs: [optical-drive] +` + +const opticalDriveCoreYaml = `name: core +type: os +slots: + optical-drive: +` + +func (s *OpticalDriveInterfaceSuite) SetUpTest(c *C) { + s.plug = MockPlug(c, opticalDriveConsumerYaml, nil, "optical-drive") + s.slot = MockSlot(c, opticalDriveCoreYaml, nil, "optical-drive") +} + +func (s *OpticalDriveInterfaceSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "optical-drive") +} + +func (s *OpticalDriveInterfaceSuite) TestSanitizeSlot(c *C) { + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "some-snap"}, + Name: "optical-drive", + Interface: "optical-drive", + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "optical-drive slots are reserved for the core snap") +} + +func (s *OpticalDriveInterfaceSuite) TestSanitizePlug(c *C) { + c.Assert(s.plug.Sanitize(s.iface), 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.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, 1) + c.Assert(spec.Snippets()[0], testutil.Contains, `KERNEL=="sr[0-9]*", TAG+="snap_consumer_app"`) +} + +func (s *OpticalDriveInterfaceSuite) TestStaticInfo(c *C) { + si := interfaces.StaticInfoOf(s.iface) + c.Assert(si.ImplicitOnCore, Equals, false) + c.Assert(si.ImplicitOnClassic, Equals, true) + c.Assert(si.Summary, Equals, `allows read access to optical drives`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "optical-drive") +} + +func (s *OpticalDriveInterfaceSuite) TestAutoConnect(c *C) { + c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) +} + +func (s *OpticalDriveInterfaceSuite) TestInterfaces(c *C) { + c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) +} diff -Nru snapd-2.27.5/interfaces/builtin/password_manager_service.go snapd-2.28.5/interfaces/builtin/password_manager_service.go --- snapd-2.27.5/interfaces/builtin/password_manager_service.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/password_manager_service.go 2017-09-13 14:47:18.000000000 +0000 @@ -21,16 +21,6 @@ const passwordManagerServiceSummary = `allow access to common password manager services` -const passwordManagerServiceDescription = ` -The password-manager-service interface allows connected plugs full access to -common Desktop Environment password services (eg, currently secret-service and -kwallet). - -The core snap provides the slot that is shared by all snaps on a classic -system. This interface gives access to sensitive information in the user's -session. -` - const passwordManagerBaseDeclarationSlots = ` password-manager-service: allow-installation: @@ -95,7 +85,6 @@ registerIface(&commonInterface{ name: "password-manager-service", summary: passwordManagerServiceSummary, - description: passwordManagerServiceDescription, implicitOnClassic: true, baseDeclarationSlots: passwordManagerBaseDeclarationSlots, connectedPlugAppArmor: passwordManagerServiceConnectedPlugAppArmor, diff -Nru snapd-2.27.5/interfaces/builtin/password_manager_service_test.go snapd-2.28.5/interfaces/builtin/password_manager_service_test.go --- snapd-2.27.5/interfaces/builtin/password_manager_service_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/password_manager_service_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -64,26 +64,18 @@ } func (s *passwordManagerServiceInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "password-manager-service", Interface: "password-manager-service", - }}) - c.Assert(err, ErrorMatches, "password-manager-service slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "password-manager-service slots are reserved for the core snap") } func (s *passwordManagerServiceInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *passwordManagerServiceInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "password-manager-service"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "password-manager-service"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *passwordManagerServiceInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/physical_memory_control.go snapd-2.28.5/interfaces/builtin/physical_memory_control.go --- snapd-2.27.5/interfaces/builtin/physical_memory_control.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/physical_memory_control.go 2017-09-13 14:47:18.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 @@ -19,14 +19,6 @@ package builtin -import ( - "fmt" - - "github.com/snapcore/snapd/interfaces" - "github.com/snapcore/snapd/interfaces/apparmor" - "github.com/snapcore/snapd/interfaces/udev" -) - const physicalMemoryControlSummary = `allows write access to all physical memory` const physicalMemoryControlBaseDeclarationSlots = ` @@ -49,70 +41,17 @@ /dev/mem rw, ` -// The type for physical-memory-control interface -type physicalMemoryControlInterface struct{} - -// Getter for the name of the physical-memory-control interface -func (iface *physicalMemoryControlInterface) Name() string { - return "physical-memory-control" -} - -func (iface *physicalMemoryControlInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ - Summary: physicalMemoryControlSummary, - ImplicitOnCore: true, - ImplicitOnClassic: true, - BaseDeclarationSlots: physicalMemoryControlBaseDeclarationSlots, - } -} - -func (iface *physicalMemoryControlInterface) String() string { - return iface.Name() -} - -// Check validity of the defined slot -func (iface *physicalMemoryControlInterface) SanitizeSlot(slot *interfaces.Slot) error { - // Does it have right type? - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface)) - } - - // Creation of the slot of this type - // is allowed only by a gadget or os snap - if !(slot.Snap.Type == "os") { - return fmt.Errorf("%s slots only allowed on core snap", iface.Name()) - } - return nil -} - -// Checks and possibly modifies a plug -func (iface *physicalMemoryControlInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface)) - } - // Currently nothing is checked on the plug side - return nil -} - -func (iface *physicalMemoryControlInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - spec.AddSnippet(physicalMemoryControlConnectedPlugAppArmor) - return nil -} - -func (iface *physicalMemoryControlInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - const udevRule = `KERNEL=="mem", TAG+="%s"` - for appName := range plug.Apps { - tag := udevSnapSecurityName(plug.Snap.Name(), appName) - spec.AddSnippet(fmt.Sprintf(udevRule, tag)) - } - return nil -} - -func (iface *physicalMemoryControlInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { - // Allow what is allowed in the declarations - return true -} +const physicalMemoryControlConnectedPlugUDev = `KERNEL=="mem", TAG+="###CONNECTED_SECURITY_TAGS###"` func init() { - registerIface(&physicalMemoryControlInterface{}) + registerIface(&commonInterface{ + name: "physical-memory-control", + summary: physicalMemoryControlSummary, + implicitOnCore: true, + implicitOnClassic: true, + baseDeclarationSlots: physicalMemoryControlBaseDeclarationSlots, + connectedPlugAppArmor: physicalMemoryControlConnectedPlugAppArmor, + connectedPlugUDev: physicalMemoryControlConnectedPlugUDev, + reservedForOS: true, + }) } diff -Nru snapd-2.27.5/interfaces/builtin/physical_memory_control_test.go snapd-2.28.5/interfaces/builtin/physical_memory_control_test.go --- snapd-2.27.5/interfaces/builtin/physical_memory_control_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/physical_memory_control_test.go 2017-09-13 14:47:18.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 @@ -27,7 +27,6 @@ "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" ) @@ -41,26 +40,21 @@ iface: builtin.MustInterface("physical-memory-control"), }) -func (s *PhysicalMemoryControlInterfaceSuite) SetUpTest(c *C) { - // Mock for OS Snap - osSnapInfo := snaptest.MockInfo(c, ` -name: ubuntu-core +const physicalMemoryControlConsumerYaml = `name: consumer +apps: + app: + plugs: [physical-memory-control] +` + +const physicalMemoryControlCoreYaml = `name: core type: os slots: - test-physical-memory: - interface: physical-memory-control -`, nil) - s.slot = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["test-physical-memory"]} - - // Snap Consumers - consumingSnapInfo := snaptest.MockInfo(c, ` -name: client-snap -apps: - app-accessing-physical-memory: - command: foo - plugs: [physical-memory-control] -`, nil) - s.plug = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["physical-memory-control"]} + physical-memory-control: +` + +func (s *PhysicalMemoryControlInterfaceSuite) SetUpTest(c *C) { + s.plug = MockPlug(c, physicalMemoryControlConsumerYaml, nil, "physical-memory-control") + s.slot = MockSlot(c, physicalMemoryControlCoreYaml, nil, "physical-memory-control") } func (s *PhysicalMemoryControlInterfaceSuite) TestName(c *C) { @@ -68,55 +62,44 @@ } func (s *PhysicalMemoryControlInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "physical-memory-control", Interface: "physical-memory-control", - }}) - c.Assert(err, ErrorMatches, "physical-memory-control slots only allowed on core snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "physical-memory-control slots are reserved for the core snap") } func (s *PhysicalMemoryControlInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } -func (s *PhysicalMemoryControlInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "physical-memory-control"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "physical-memory-control"`) -} - -func (s *PhysicalMemoryControlInterfaceSuite) TestUsedSecuritySystems(c *C) { - expectedSnippet1 := ` -# Description: With kernels with STRICT_DEVMEM=n, write access to all physical -# memory. -# -# With STRICT_DEVMEM=y, allow writing to /dev/mem to access -# architecture-specific subset of the physical address (eg, PCI space, -# BIOS code and data regions on x86, etc) for all common uses of /dev/mem -# (eg, X without KMS, dosemu, etc). -capability sys_rawio, -/dev/mem rw, -` - expectedSnippet2 := `KERNEL=="mem", TAG+="snap_client-snap_app-accessing-physical-memory"` +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.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"`) +} + +func (s *PhysicalMemoryControlInterfaceSuite) 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 physical memory`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "physical-memory-control") +} - // connected plugs have a non-nil security snippet for apparmor - apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.client-snap.app-accessing-physical-memory"}) - aasnippet := apparmorSpec.SnippetForTag("snap.client-snap.app-accessing-physical-memory") - c.Assert(aasnippet, Equals, expectedSnippet1, Commentf("\nexpected:\n%s\nfound:\n%s", expectedSnippet1, aasnippet)) - - udevSpec := &udev.Specification{} - c.Assert(udevSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) - c.Assert(udevSpec.Snippets(), HasLen, 1) - snippet := udevSpec.Snippets()[0] - c.Assert(snippet, DeepEquals, expectedSnippet2) +func (s *PhysicalMemoryControlInterfaceSuite) TestAutoConnect(c *C) { + c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) } func (s *PhysicalMemoryControlInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/physical_memory_observe.go snapd-2.28.5/interfaces/builtin/physical_memory_observe.go --- snapd-2.27.5/interfaces/builtin/physical_memory_observe.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/physical_memory_observe.go 2017-09-13 14:47:18.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 @@ -19,14 +19,6 @@ package builtin -import ( - "fmt" - - "github.com/snapcore/snapd/interfaces" - "github.com/snapcore/snapd/interfaces/apparmor" - "github.com/snapcore/snapd/interfaces/udev" -) - const physicalMemoryObserveSummary = `allows read access to all physical memory` const physicalMemoryObserveBaseDeclarationSlots = ` @@ -45,70 +37,17 @@ /dev/mem r, ` -// The type for physical-memory-observe interface -type physicalMemoryObserveInterface struct{} - -// Getter for the name of the physical-memory-observe interface -func (iface *physicalMemoryObserveInterface) Name() string { - return "physical-memory-observe" -} - -func (iface *physicalMemoryObserveInterface) String() string { - return iface.Name() -} - -func (iface *physicalMemoryObserveInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ - Summary: physicalMemoryObserveSummary, - ImplicitOnCore: true, - ImplicitOnClassic: true, - BaseDeclarationSlots: physicalMemoryObserveBaseDeclarationSlots, - } -} - -// Check validity of the defined slot -func (iface *physicalMemoryObserveInterface) SanitizeSlot(slot *interfaces.Slot) error { - // Does it have right type? - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface)) - } - - // Creation of the slot of this type - // is allowed only by a gadget or os snap - if !(slot.Snap.Type == "os") { - return fmt.Errorf("%s slots only allowed on core snap", iface.Name()) - } - return nil -} - -// Checks and possibly modifies a plug -func (iface *physicalMemoryObserveInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface)) - } - // Currently nothing is checked on the plug side - return nil -} - -func (iface *physicalMemoryObserveInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - spec.AddSnippet(physicalMemoryObserveConnectedPlugAppArmor) - return nil -} - -func (iface *physicalMemoryObserveInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - const udevRule = `KERNEL=="mem", TAG+="%s"` - for appName := range plug.Apps { - tag := udevSnapSecurityName(plug.Snap.Name(), appName) - spec.AddSnippet(fmt.Sprintf(udevRule, tag)) - } - return nil -} - -func (iface *physicalMemoryObserveInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { - // Allow what is allowed in the declarations - return true -} +const physicalMemoryObserveConnectedPlugUDev = `KERNEL=="mem", TAG+="###CONNECTED_SECURITY_TAGS###"` func init() { - registerIface(&physicalMemoryObserveInterface{}) + registerIface(&commonInterface{ + name: "physical-memory-observe", + summary: physicalMemoryObserveSummary, + implicitOnCore: true, + implicitOnClassic: true, + baseDeclarationSlots: physicalMemoryObserveBaseDeclarationSlots, + connectedPlugAppArmor: physicalMemoryObserveConnectedPlugAppArmor, + connectedPlugUDev: physicalMemoryObserveConnectedPlugUDev, + reservedForOS: true, + }) } diff -Nru snapd-2.27.5/interfaces/builtin/physical_memory_observe_test.go snapd-2.28.5/interfaces/builtin/physical_memory_observe_test.go --- snapd-2.27.5/interfaces/builtin/physical_memory_observe_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/physical_memory_observe_test.go 2017-09-13 14:47:18.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 @@ -28,7 +28,6 @@ "github.com/snapcore/snapd/interfaces/builtin" "github.com/snapcore/snapd/snap" - "github.com/snapcore/snapd/snap/snaptest" "github.com/snapcore/snapd/testutil" ) @@ -42,26 +41,21 @@ iface: builtin.MustInterface("physical-memory-observe"), }) -func (s *PhysicalMemoryObserveInterfaceSuite) SetUpTest(c *C) { - // Mock for OS Snap - osSnapInfo := snaptest.MockInfo(c, ` -name: ubuntu-core +const physicalMemoryObserveConsumerYaml = `name: consumer +apps: + app: + plugs: [physical-memory-observe] +` + +const physicalMemoryObserveCoreYaml = `name: core type: os slots: - test-physical-memory: - interface: physical-memory-observe -`, nil) - s.slot = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["test-physical-memory"]} - - // Snap Consumers - consumingSnapInfo := snaptest.MockInfo(c, ` -name: client-snap -apps: - app-accessing-physical-memory: - command: foo - plugs: [physical-memory-observe] -`, nil) - s.plug = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["physical-memory-observe"]} + physical-memory-observe: +` + +func (s *PhysicalMemoryObserveInterfaceSuite) SetUpTest(c *C) { + s.plug = MockPlug(c, physicalMemoryObserveConsumerYaml, nil, "physical-memory-observe") + s.slot = MockSlot(c, physicalMemoryObserveCoreYaml, nil, "physical-memory-observe") } func (s *PhysicalMemoryObserveInterfaceSuite) TestName(c *C) { @@ -69,51 +63,44 @@ } func (s *PhysicalMemoryObserveInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "physical-memory-observe", Interface: "physical-memory-observe", - }}) - c.Assert(err, ErrorMatches, "physical-memory-observe slots only allowed on core snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "physical-memory-observe slots are reserved for the core snap") } func (s *PhysicalMemoryObserveInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } -func (s *PhysicalMemoryObserveInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "physical-memory-observe"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "physical-memory-observe"`) -} - -func (s *PhysicalMemoryObserveInterfaceSuite) TestUsedSecuritySystems(c *C) { - expectedSnippet1 := ` -# Description: With kernels with STRICT_DEVMEM=n, read-only access to all physical -# memory. With STRICT_DEVMEM=y, allow reading /dev/mem for read-only -# access to architecture-specific subset of the physical address (eg, PCI, -# space, BIOS code and data regions on x86, etc). -/dev/mem r, -` - expectedSnippet2 := `KERNEL=="mem", TAG+="snap_client-snap_app-accessing-physical-memory"` - - // connected plugs have a non-nil security snippet for apparmor - apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.client-snap.app-accessing-physical-memory"}) - aasnippet := apparmorSpec.SnippetForTag("snap.client-snap.app-accessing-physical-memory") - c.Assert(aasnippet, Equals, expectedSnippet1, Commentf("\nexpected:\n%s\nfound:\n%s", expectedSnippet1, aasnippet)) +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.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) - snippet := spec.Snippets()[0] - c.Assert(snippet, DeepEquals, expectedSnippet2) + c.Assert(spec.Snippets()[0], DeepEquals, `KERNEL=="mem", TAG+="snap_consumer_app"`) +} + +func (s *PhysicalMemoryObserveInterfaceSuite) 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 read access to all physical memory`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "physical-memory-observe") +} + +func (s *PhysicalMemoryObserveInterfaceSuite) TestAutoConnect(c *C) { + c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) } func (s *PhysicalMemoryObserveInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/ppp.go snapd-2.28.5/interfaces/builtin/ppp.go --- snapd-2.27.5/interfaces/builtin/ppp.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/ppp.go 2017-09-13 14:47:18.000000000 +0000 @@ -19,12 +19,6 @@ package builtin -import ( - "github.com/snapcore/snapd/interfaces" - "github.com/snapcore/snapd/interfaces/apparmor" - "github.com/snapcore/snapd/interfaces/kmod" -) - const pppSummary = `allows operating as the ppp service` const pppBaseDeclarationSlots = ` @@ -57,45 +51,25 @@ // ppp_generic creates /dev/ppp. Other ppp modules will be automatically loaded // by the kernel on different ioctl calls for this device. Note also that // in many cases ppp_generic is statically linked into the kernel (CONFIG_PPP=y) -const pppConnectedPlugKmod = "ppp_generic" - -type pppInterface struct{} - -func (iface *pppInterface) Name() string { - return "ppp" -} - -func (iface *pppInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ - Summary: pppSummary, - ImplicitOnCore: true, - ImplicitOnClassic: true, - BaseDeclarationSlots: pppBaseDeclarationSlots, - } -} - -func (iface *pppInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - spec.AddSnippet(pppConnectedPlugAppArmor) - return nil +var pppConnectedPlugKmod = []string{ + "ppp_generic", } -func (iface *pppInterface) KModConnectedPlug(spec *kmod.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - return spec.AddModule(pppConnectedPlugKmod) -} - -func (iface *pppInterface) SanitizePlug(plug *interfaces.Plug) error { - return nil -} - -func (iface *pppInterface) SanitizeSlot(slot *interfaces.Slot) error { - return nil -} - -func (iface *pppInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { - // allow what declarations allowed - return true -} +const pppConnectedPlugUDev = ` +KERNEL=="ppp", TAG+="###CONNECTED_SECURITY_TAGS###" +KERNEL=="tty[A-Z]*[0-9]*", TAG+="###CONNECTED_SECURITY_TAGS###" +` func init() { - registerIface(&pppInterface{}) + registerIface(&commonInterface{ + name: "ppp", + summary: pppSummary, + implicitOnCore: true, + implicitOnClassic: true, + baseDeclarationSlots: pppBaseDeclarationSlots, + connectedPlugAppArmor: pppConnectedPlugAppArmor, + connectedPlugKModModules: pppConnectedPlugKmod, + connectedPlugUDev: pppConnectedPlugUDev, + reservedForOS: true, + }) } diff -Nru snapd-2.27.5/interfaces/builtin/ppp_test.go snapd-2.28.5/interfaces/builtin/ppp_test.go --- snapd-2.27.5/interfaces/builtin/ppp_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/ppp_test.go 2017-09-13 14:47:18.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 @@ -25,10 +25,9 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/builtin" - "github.com/snapcore/snapd/interfaces/dbus" "github.com/snapcore/snapd/interfaces/kmod" "github.com/snapcore/snapd/interfaces/udev" - "github.com/snapcore/snapd/snap/snaptest" + "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/testutil" ) @@ -42,66 +41,77 @@ iface: builtin.MustInterface("ppp"), }) -func (s *PppInterfaceSuite) SetUpTest(c *C) { - const mockPlugSnapInfo = `name: other -version: 1.0 +const pppConsumerYaml = `name: consumer apps: app: - command: foo plugs: [ppp] ` - const mockSlotSnapInfo = `name: ppp -version: 1.0 -apps: - app: - command: foo - slots: [ppp] +const pppCoreYaml = `name: core +type: os +slots: + ppp: ` - slotSnap := snaptest.MockInfo(c, mockPlugSnapInfo, nil) - s.slot = &interfaces.Slot{SlotInfo: slotSnap.Slots["ppp"]} - plugSnap := snaptest.MockInfo(c, mockPlugSnapInfo, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["ppp"]} +func (s *PppInterfaceSuite) SetUpTest(c *C) { + s.plug = MockPlug(c, pppConsumerYaml, nil, "ppp") + s.slot = MockSlot(c, pppCoreYaml, nil, "ppp") } func (s *PppInterfaceSuite) TestName(c *C) { c.Assert(s.iface.Name(), Equals, "ppp") } -func (s *PppInterfaceSuite) TestUsedSecuritySystems(c *C) { - apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) - c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `/usr/sbin/pppd ix,`) - - apparmorSpec = &apparmor.Specification{} - err = apparmorSpec.AddPermanentSlot(s.iface, s.slot) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), HasLen, 0) - - dbusSpec := &dbus.Specification{} - err = dbusSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) - c.Assert(err, IsNil) - c.Assert(dbusSpec.SecurityTags(), HasLen, 0) - dbusSpec = &dbus.Specification{} - err = dbusSpec.AddPermanentSlot(s.iface, s.slot) - c.Assert(err, IsNil) - c.Assert(dbusSpec.SecurityTags(), HasLen, 0) - - udevSpec := &udev.Specification{} - c.Assert(udevSpec.AddPermanentSlot(s.iface, s.slot), IsNil) - c.Assert(udevSpec.Snippets(), HasLen, 0) +func (s *PppInterfaceSuite) TestSanitizeSlot(c *C) { + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "some-snap"}, + Name: "ppp", + Interface: "ppp", + }} + + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "ppp slots are reserved for the core snap") +} + +func (s *PppInterfaceSuite) TestSanitizePlug(c *C) { + c.Assert(s.plug.Sanitize(s.iface), 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.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{} - err = spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) - c.Assert(err, IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) c.Assert(spec.Modules(), DeepEquals, map[string]bool{ "ppp_generic": true, }) } +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, 1) + c.Assert(spec.Snippets()[0], testutil.Contains, `KERNEL=="ppp", TAG+="snap_consumer_app"`) +} + +func (s *PppInterfaceSuite) 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 operating as the ppp service`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "ppp") +} + +func (s *PppInterfaceSuite) TestAutoConnect(c *C) { + c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) +} + func (s *PppInterfaceSuite) TestInterfaces(c *C) { c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) } diff -Nru snapd-2.27.5/interfaces/builtin/process_control_test.go snapd-2.28.5/interfaces/builtin/process_control_test.go --- snapd-2.27.5/interfaces/builtin/process_control_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/process_control_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -66,26 +66,18 @@ } func (s *ProcessControlInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "process-control", Interface: "process-control", - }}) - c.Assert(err, ErrorMatches, "process-control slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "process-control slots are reserved for the core snap") } func (s *ProcessControlInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *ProcessControlInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "process-control"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "process-control"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *ProcessControlInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/pulseaudio.go snapd-2.28.5/interfaces/builtin/pulseaudio.go --- snapd-2.27.5/interfaces/builtin/pulseaudio.go 2017-08-24 08:12:57.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/pulseaudio.go 2017-09-13 14:47:18.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 @@ -20,9 +20,12 @@ package builtin import ( + "strings" + "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" ) @@ -126,14 +129,20 @@ socket AF_NETLINK - NETLINK_KOBJECT_UEVENT ` +const pulseaudioPermanentSlotUdev = ` +KERNEL=="controlC[0-9]*", TAG+="###CONNECTED_SECURITY_TAGS###" +KERNEL=="pcmC[0-9]*D[0-9]*[cp]", TAG+="###CONNECTED_SECURITY_TAGS###" +KERNEL=="timer", TAG+="###CONNECTED_SECURITY_TAGS###" +` + type pulseAudioInterface struct{} func (iface *pulseAudioInterface) Name() string { return "pulseaudio" } -func (iface *pulseAudioInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *pulseAudioInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: pulseaudioSummary, ImplicitOnClassic: true, BaseDeclarationSlots: pulseaudioBaseDeclarationSlots, @@ -148,6 +157,16 @@ return nil } +func (iface *pulseAudioInterface) UDevPermanentSlot(spec *udev.Specification, slot *interfaces.Slot) error { + old := "###CONNECTED_SECURITY_TAGS###" + for appName := range slot.Apps { + tag := udevSnapSecurityName(slot.Snap.Name(), appName) + udevRule := strings.Replace(pulseaudioPermanentSlotUdev, old, tag, -1) + spec.AddSnippet(udevRule) + } + return nil +} + func (iface *pulseAudioInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *interfaces.Slot) error { spec.AddSnippet(pulseaudioPermanentSlotAppArmor) return nil @@ -163,14 +182,6 @@ return nil } -func (iface *pulseAudioInterface) SanitizePlug(slot *interfaces.Plug) error { - return nil -} - -func (iface *pulseAudioInterface) SanitizeSlot(slot *interfaces.Slot) error { - return nil -} - func (iface *pulseAudioInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { return true } diff -Nru snapd-2.27.5/interfaces/builtin/pulseaudio_test.go snapd-2.28.5/interfaces/builtin/pulseaudio_test.go --- snapd-2.27.5/interfaces/builtin/pulseaudio_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/pulseaudio_test.go 2017-09-13 14:47:18.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 @@ -25,6 +25,7 @@ "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/builtin" "github.com/snapcore/snapd/interfaces/seccomp" + "github.com/snapcore/snapd/interfaces/udev" "github.com/snapcore/snapd/snap/snaptest" "github.com/snapcore/snapd/testutil" ) @@ -82,12 +83,12 @@ } func (s *PulseAudioInterfaceSuite) TestSanitizeSlot(c *C) { - c.Assert(s.iface.SanitizeSlot(s.coreSlot), IsNil) - c.Assert(s.iface.SanitizeSlot(s.classicSlot), IsNil) + c.Assert(s.coreSlot.Sanitize(s.iface), IsNil) + c.Assert(s.classicSlot.Sanitize(s.iface), IsNil) } func (s *PulseAudioInterfaceSuite) TestSanitizePlug(c *C) { - c.Assert(s.iface.SanitizePlug(s.plug), IsNil) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *PulseAudioInterfaceSuite) TestSecCompOnClassic(c *C) { @@ -111,6 +112,13 @@ c.Assert(seccompSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "shmctl\n") } +func (s *PulseAudioInterfaceSuite) TestUDev(c *C) { + spec := &udev.Specification{} + c.Assert(spec.AddPermanentSlot(s.iface, s.coreSlot), IsNil) + c.Assert(spec.Snippets(), HasLen, 1) + c.Assert(spec.Snippets()[0], testutil.Contains, `KERNEL=="pcmC[0-9]*D[0-9]*[cp]", TAG+="snap_pulseaudio_app1"`) +} + func (s *PulseAudioInterfaceSuite) TestInterfaces(c *C) { c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) } diff -Nru snapd-2.27.5/interfaces/builtin/raw_usb.go snapd-2.28.5/interfaces/builtin/raw_usb.go --- snapd-2.27.5/interfaces/builtin/raw_usb.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/raw_usb.go 2017-09-13 14:47:18.000000000 +0000 @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2016 Canonical Ltd + * 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 @@ -45,6 +45,8 @@ /run/udev/data/+usb:* r, ` +const rawusbConnectedPlugUDev = `SUBSYSTEMS=="usb", TAG+="###CONNECTED_SECURITY_TAGS###"` + func init() { registerIface(&commonInterface{ name: "raw-usb", @@ -53,6 +55,7 @@ implicitOnClassic: true, baseDeclarationSlots: rawusbBaseDeclarationSlots, connectedPlugAppArmor: rawusbConnectedPlugAppArmor, + connectedPlugUDev: rawusbConnectedPlugUDev, reservedForOS: true, }) } diff -Nru snapd-2.27.5/interfaces/builtin/raw_usb_test.go snapd-2.28.5/interfaces/builtin/raw_usb_test.go --- snapd-2.27.5/interfaces/builtin/raw_usb_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/raw_usb_test.go 2017-09-13 14:47:18.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 @@ -25,76 +25,83 @@ "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/snap/snaptest" "github.com/snapcore/snapd/testutil" ) -type RawUsbSuite struct { +type RawUsbInterfaceSuite struct { iface interfaces.Interface slot *interfaces.Slot plug *interfaces.Plug } -var _ = Suite(&RawUsbSuite{ +var _ = Suite(&RawUsbInterfaceSuite{ iface: builtin.MustInterface("raw-usb"), }) -func (s *RawUsbSuite) SetUpTest(c *C) { - const mockPlugSnapInfo = `name: other -version: 1.0 +const rawusbConsumerYaml = `name: consumer apps: app: - command: foo plugs: [raw-usb] ` - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "raw-usb", - Interface: "raw-usb", - }, - } - plugSnap := snaptest.MockInfo(c, mockPlugSnapInfo, nil) - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["raw-usb"]} + +const rawusbCoreYaml = `name: core +type: os +slots: + raw-usb: +` + +func (s *RawUsbInterfaceSuite) SetUpTest(c *C) { + s.plug = MockPlug(c, rawusbConsumerYaml, nil, "raw-usb") + s.slot = MockSlot(c, rawusbCoreYaml, nil, "raw-usb") } -func (s *RawUsbSuite) TestName(c *C) { +func (s *RawUsbInterfaceSuite) TestName(c *C) { c.Assert(s.iface.Name(), Equals, "raw-usb") } -func (s *RawUsbSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ +func (s *RawUsbInterfaceSuite) TestSanitizeSlot(c *C) { + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "raw-usb", Interface: "raw-usb", - }}) - c.Assert(err, ErrorMatches, "raw-usb slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "raw-usb slots are reserved for the core snap") +} + +func (s *RawUsbInterfaceSuite) TestSanitizePlug(c *C) { + c.Assert(s.plug.Sanitize(s.iface), 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.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `/sys/bus/usb/devices/`) } -func (s *RawUsbSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) +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, `SUBSYSTEMS=="usb", TAG+="snap_consumer_app"`) } -func (s *RawUsbSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "raw-usb"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "raw-usb"`) +func (s *RawUsbInterfaceSuite) 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 raw access to all USB devices`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "raw-usb") } -func (s *RawUsbSuite) 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) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) - c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `/sys/bus/usb/devices/`) +func (s *RawUsbInterfaceSuite) TestAutoConnect(c *C) { + c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) } -func (s *RawUsbSuite) TestInterfaces(c *C) { +func (s *RawUsbInterfaceSuite) TestInterfaces(c *C) { c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) } diff -Nru snapd-2.27.5/interfaces/builtin/removable_media_test.go snapd-2.28.5/interfaces/builtin/removable_media_test.go --- snapd-2.27.5/interfaces/builtin/removable_media_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/removable_media_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -63,26 +63,18 @@ } func (s *RemovableMediaInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "removable-media", Interface: "removable-media", - }}) - c.Assert(err, ErrorMatches, "removable-media slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "removable-media slots are reserved for the core snap") } func (s *RemovableMediaInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *RemovableMediaInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "removable-media"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "removable-media"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *RemovableMediaInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/screen_inhibit_control_test.go snapd-2.28.5/interfaces/builtin/screen_inhibit_control_test.go --- snapd-2.27.5/interfaces/builtin/screen_inhibit_control_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/screen_inhibit_control_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -64,26 +64,18 @@ } func (s *ScreenInhibitControlInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "screen-inhibit-control", Interface: "screen-inhibit-control", - }}) - c.Assert(err, ErrorMatches, "screen-inhibit-control slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "screen-inhibit-control slots are reserved for the core snap") } func (s *ScreenInhibitControlInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *ScreenInhibitControlInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "screen-inhibit-control"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "screen-inhibit-control"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *ScreenInhibitControlInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/serial_port.go snapd-2.28.5/interfaces/builtin/serial_port.go --- snapd-2.27.5/interfaces/builtin/serial_port.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/serial_port.go 2017-09-13 14:47:18.000000000 +0000 @@ -49,8 +49,8 @@ return "serial-port" } -func (iface *serialPortInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *serialPortInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: serialPortSummary, BaseDeclarationSlots: serialPortBaseDeclarationSlots, } @@ -77,14 +77,8 @@ // SanitizeSlot checks validity of the defined slot func (iface *serialPortInterface) SanitizeSlot(slot *interfaces.Slot) error { - // Check slot is of right type - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface)) - } - - // We will only allow creation of this type of slot by a gadget or OS snap - if !(slot.Snap.Type == "gadget" || slot.Snap.Type == "os") { - return fmt.Errorf("serial-port slots only allowed on gadget or core snaps") + if err := sanitizeSlotReservedForOSOrGadget(iface, slot); err != nil { + return err } // Check slot has a path attribute identify serial device @@ -128,15 +122,6 @@ return nil } -// SanitizePlug checks and possibly modifies a plug. -func (iface *serialPortInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface)) - } - // NOTE: currently we don't check anything on the plug side. - return nil -} - func (iface *serialPortInterface) UDevPermanentSlot(spec *udev.Specification, slot *interfaces.Slot) error { usbVendor, vOk := slot.Attrs["usb-vendor"].(int64) if !vOk { @@ -204,10 +189,6 @@ return false } -func (iface *serialPortInterface) ValidateSlot(slot *interfaces.Slot, attrs map[string]interface{}) error { - return nil -} - func init() { registerIface(&serialPortInterface{}) } diff -Nru snapd-2.27.5/interfaces/builtin/serial_port_test.go snapd-2.28.5/interfaces/builtin/serial_port_test.go --- snapd-2.27.5/interfaces/builtin/serial_port_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/serial_port_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -212,43 +212,29 @@ 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} { - err := s.iface.SanitizeSlot(slot) - c.Assert(err, IsNil) + c.Assert(slot.Sanitize(s.iface), IsNil) } } func (s *SerialPortInterfaceSuite) TestSanitizeBadCoreSnapSlots(c *C) { // Slots without the "path" attribute are rejected. - err := s.iface.SanitizeSlot(s.missingPathSlot) - c.Assert(err, ErrorMatches, `serial-port slot must have a path attribute`) + c.Assert(s.missingPathSlot.Sanitize(s.iface), 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} { - err := s.iface.SanitizeSlot(slot) - c.Assert(err, ErrorMatches, "serial-port path attribute must be a valid device node") + c.Assert(slot.Sanitize(s.iface), ErrorMatches, "serial-port path attribute must be a valid device node") } - - // It is impossible to use "bool-file" interface to sanitize slots with other interfaces. - c.Assert(func() { s.iface.SanitizeSlot(s.badInterfaceSlot) }, PanicMatches, `slot is not of interface "serial-port"`) } func (s *SerialPortInterfaceSuite) TestSanitizeGadgetSnapSlots(c *C) { - err := s.iface.SanitizeSlot(s.testUDev1) - c.Assert(err, IsNil) - - err = s.iface.SanitizeSlot(s.testUDev2) - c.Assert(err, IsNil) + c.Assert(s.testUDev1.Sanitize(s.iface), IsNil) + c.Assert(s.testUDev2.Sanitize(s.iface), IsNil) } func (s *SerialPortInterfaceSuite) TestSanitizeBadGadgetSnapSlots(c *C) { - err := s.iface.SanitizeSlot(s.testUDevBadValue1) - c.Assert(err, ErrorMatches, "serial-port usb-vendor attribute not valid: -1") - - err = s.iface.SanitizeSlot(s.testUDevBadValue2) - c.Assert(err, ErrorMatches, "serial-port usb-product attribute not valid: 65536") - - err = s.iface.SanitizeSlot(s.testUDevBadValue3) - c.Assert(err, ErrorMatches, "serial-port path attribute specifies invalid symlink location") + 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") } func (s *SerialPortInterfaceSuite) TestPermanentSlotUDevSnippets(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/shutdown_test.go snapd-2.28.5/interfaces/builtin/shutdown_test.go --- snapd-2.27.5/interfaces/builtin/shutdown_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/shutdown_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -63,26 +63,17 @@ } func (s *ShutdownInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "shutdown", Interface: "shutdown", - }}) - c.Assert(err, ErrorMatches, "shutdown slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, "shutdown slots are reserved for the core snap") } func (s *ShutdownInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *ShutdownInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "shutdown"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "shutdown"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *ShutdownInterfaceSuite) TestConnectedPlugSnippet(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/snapd_control_test.go snapd-2.28.5/interfaces/builtin/snapd_control_test.go --- snapd-2.27.5/interfaces/builtin/snapd_control_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/snapd_control_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -63,26 +63,18 @@ } func (s *SnapdControlInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "snapd-control", Interface: "snapd-control", - }}) - c.Assert(err, ErrorMatches, "snapd-control slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "snapd-control slots are reserved for the core snap") } func (s *SnapdControlInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *SnapdControlInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "snapd-control"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "snapd-control"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *SnapdControlInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/spi.go snapd-2.28.5/interfaces/builtin/spi.go --- snapd-2.27.5/interfaces/builtin/spi.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/spi.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,108 @@ +// -*- 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 + +import ( + "fmt" + "path/filepath" + "regexp" + "strings" + + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/apparmor" + "github.com/snapcore/snapd/interfaces/udev" +) + +const spiSummary = `allows access to specific spi controller` + +const spiBaseDeclarationSlots = ` + spi: + allow-installation: + slot-snap-type: + - core + - gadget + deny-auto-connection: true +` + +type spiInterface struct{} + +func (iface *spiInterface) Name() string { + return "spi" +} + +func (iface *spiInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ + Summary: spiSummary, + BaseDeclarationSlots: spiBaseDeclarationSlots, + } +} + +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()) + } + path = filepath.Clean(path) + if !spiDevPattern.MatchString(path) { + return "", fmt.Errorf("%q is not a valid SPI device", path) + } + return path, nil +} + +func (iface *spiInterface) SanitizeSlot(slot *interfaces.Slot) error { + if err := sanitizeSlotReservedForOSOrGadget(iface, slot); err != nil { + return err + } + _, err := iface.path(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) + if err != nil { + return nil + } + spec.AddSnippet(fmt.Sprintf("%s rw,", path)) + spec.AddSnippet(fmt.Sprintf("/sys/devices/platform/**/**.spi/**/%s/** rw,", strings.TrimPrefix(path, "/dev/"))) + 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) + if err != nil { + return nil + } + for appName := range plug.Apps { + tag := udevSnapSecurityName(plug.Snap.Name(), appName) + spec.AddSnippet(fmt.Sprintf(`KERNEL=="%s", TAG+="%s"`, strings.TrimPrefix(path, "/dev/"), tag)) + } + return nil +} + +func (iface *spiInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { + // Allow what is allowed in the declarations + return true +} + +func init() { + registerIface(&spiInterface{}) +} diff -Nru snapd-2.27.5/interfaces/builtin/spi_test.go snapd-2.28.5/interfaces/builtin/spi_test.go --- snapd-2.27.5/interfaces/builtin/spi_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/spi_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,188 @@ +// -*- 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/snap/snaptest" + "github.com/snapcore/snapd/testutil" +) + +type spiInterfaceSuite struct { + 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 +} + +var _ = Suite(&spiInterfaceSuite{ + iface: builtin.MustInterface("spi"), +}) + +func (s *spiInterfaceSuite) SetUpTest(c *C) { + info := snaptest.MockInfo(c, ` +name: core +type: os +slots: + spi-1: + interface: spi + path: /dev/spidev0.0 + spi-2: + 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"]} + + info = snaptest.MockInfo(c, ` +name: gadget +type: gadget +slots: + spi-1: + interface: spi + path: /dev/spidev0.0 + spi-2: + interface: spi + path: /dev/spidev0.1 + bad-spi-1: + interface: spi + path: /dev/spev0.0 + bad-spi-2: + interface: spi + path: /dev/sidv0.0 + bad-spi-3: + interface: spi + path: /dev/slpiv0.3 + bad-spi-4: + interface: spi + path: /dev/sdev-00 + bad-spi-5: + interface: spi + path: /dev/spi-foo + 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"]} + + info = snaptest.MockInfo(c, ` +name: consumer +plugs: + spi-1: + interface: spi + path: /dev/spidev.0.0 + spi-2: + interface: spi + path: /dev/spidev0.1 +apps: + app: + command: foo + plugs: [spi-1] +`, nil) + s.plug1 = &interfaces.Plug{PlugInfo: info.Plugs["spi-1"]} + s.plug2 = &interfaces.Plug{PlugInfo: info.Plugs["spi-2"]} +} + +func (s *spiInterfaceSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "spi") +} + +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(err, ErrorMatches, `"/dev/spev0.0" is not a valid SPI device`) + err = s.slotGadgetBad2.Sanitize(s.iface) + c.Assert(err, ErrorMatches, `"/dev/sidv0.0" is not a valid SPI device`) + err = s.slotGadgetBad3.Sanitize(s.iface) + c.Assert(err, ErrorMatches, `"/dev/slpiv0.3" is not a valid SPI device`) + err = s.slotGadgetBad4.Sanitize(s.iface) + c.Assert(err, ErrorMatches, `"/dev/sdev-00" is not a valid SPI device`) + err = s.slotGadgetBad5.Sanitize(s.iface) + c.Assert(err, ErrorMatches, `"/dev/spi-foo" is not a valid SPI device`) + err = s.slotGadgetBad6.Sanitize(s.iface) + c.Assert(err, ErrorMatches, `slot "gadget:bad-spi-6" must have a path attribute`) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "some-snap"}, + Name: "spi", + Interface: "spi", + }} + c.Assert(slot.Sanitize(s.iface), 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"`) +} + +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.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) + c.Assert(spec.SnippetForTag("snap.consumer.app"), Equals, ""+ + "/dev/spidev0.0 rw,\n"+ + "/sys/devices/platform/**/**.spi/**/spidev0.0/** rw,") +} + +func (s *spiInterfaceSuite) TestStaticInfo(c *C) { + si := interfaces.StaticInfoOf(s.iface) + c.Assert(si.ImplicitOnCore, Equals, false) + c.Assert(si.ImplicitOnClassic, Equals, false) + c.Assert(si.Summary, Equals, "allows access to specific spi controller") + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "spi") +} + +func (s *spiInterfaceSuite) TestAutoConnect(c *C) { + c.Check(s.iface.AutoConnect(nil, nil), Equals, true) +} + +func (s *spiInterfaceSuite) TestInterfaces(c *C) { + c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) +} diff -Nru snapd-2.27.5/interfaces/builtin/storage_framework_service.go snapd-2.28.5/interfaces/builtin/storage_framework_service.go --- snapd-2.27.5/interfaces/builtin/storage_framework_service.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/storage_framework_service.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,7 +20,6 @@ package builtin import ( - "fmt" "strings" "github.com/snapcore/snapd/interfaces" @@ -118,8 +117,8 @@ return "storage-framework-service" } -func (iface *storageFrameworkServiceInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *storageFrameworkServiceInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: storageFrameworkServiceSummary, BaseDeclarationSlots: storageFrameworkServiceBaseDeclarationSlots, } @@ -153,20 +152,6 @@ return nil } -func (iface *storageFrameworkServiceInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface.Name())) - } - return nil -} - -func (iface *storageFrameworkServiceInterface) SanitizeSlot(slot *interfaces.Slot) error { - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface.Name())) - } - return nil -} - func (iface *storageFrameworkServiceInterface) AutoConnect(plug *interfaces.Plug, slot *interfaces.Slot) bool { return true } diff -Nru snapd-2.27.5/interfaces/builtin/storage_framework_service_test.go snapd-2.28.5/interfaces/builtin/storage_framework_service_test.go --- snapd-2.27.5/interfaces/builtin/storage_framework_service_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/storage_framework_service_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -26,7 +26,6 @@ "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" ) @@ -68,13 +67,6 @@ c.Check(s.iface.Name(), Equals, "storage-framework-service") } -func (s *StorageFrameworkServiceInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Check(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "storage-framework-service"`) - c.Check(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "storage-framework-service"`) -} - func (s *StorageFrameworkServiceInterfaceSuite) TestAppArmorConnectedPlug(c *C) { spec := &apparmor.Specification{} c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) diff -Nru snapd-2.27.5/interfaces/builtin/system_observe_test.go snapd-2.28.5/interfaces/builtin/system_observe_test.go --- snapd-2.27.5/interfaces/builtin/system_observe_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/system_observe_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -66,26 +66,18 @@ } func (s *SystemObserveInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "system-observe", Interface: "system-observe", - }}) - c.Assert(err, ErrorMatches, "system-observe slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "system-observe slots are reserved for the core snap") } func (s *SystemObserveInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *SystemObserveInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "system-observe"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "system-observe"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *SystemObserveInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/system_trace_test.go snapd-2.28.5/interfaces/builtin/system_trace_test.go --- snapd-2.27.5/interfaces/builtin/system_trace_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/system_trace_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -64,26 +64,17 @@ } func (s *SystemTraceInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "system-trace", Interface: "system-trace", - }}) - c.Assert(err, ErrorMatches, "system-trace slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, "system-trace slots are reserved for the core snap") } func (s *SystemTraceInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *SystemTraceInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "system-trace"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "system-trace"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *SystemTraceInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/thumbnailer_service.go snapd-2.28.5/interfaces/builtin/thumbnailer_service.go --- snapd-2.27.5/interfaces/builtin/thumbnailer_service.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/thumbnailer_service.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,7 +20,6 @@ package builtin import ( - "fmt" "strings" "github.com/snapcore/snapd/interfaces" @@ -103,8 +102,8 @@ return "thumbnailer-service" } -func (iface *thumbnailerServiceInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *thumbnailerServiceInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: thumbnailerServiceSummary, BaseDeclarationSlots: thumbnailerServiceBaseDeclarationSlots, } @@ -137,20 +136,6 @@ return nil } -func (iface *thumbnailerServiceInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface.Name())) - } - return nil -} - -func (iface *thumbnailerServiceInterface) SanitizeSlot(slot *interfaces.Slot) error { - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface.Name())) - } - return nil -} - func (iface *thumbnailerServiceInterface) AutoConnect(plug *interfaces.Plug, slot *interfaces.Slot) bool { return true } diff -Nru snapd-2.27.5/interfaces/builtin/thumbnailer_service_test.go snapd-2.28.5/interfaces/builtin/thumbnailer_service_test.go --- snapd-2.27.5/interfaces/builtin/thumbnailer_service_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/thumbnailer_service_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -25,7 +25,6 @@ "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" ) @@ -79,13 +78,6 @@ c.Check(s.iface.Name(), Equals, "thumbnailer-service") } -func (s *ThumbnailerServiceInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Check(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "thumbnailer-service"`) - c.Check(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "thumbnailer-service"`) -} - func (s *ThumbnailerServiceInterfaceSuite) TestUsedSecuritySystems(c *C) { // connected slots have a non-nil security snippet for apparmor apparmorSpec := &apparmor.Specification{} diff -Nru snapd-2.27.5/interfaces/builtin/time_control.go snapd-2.28.5/interfaces/builtin/time_control.go --- snapd-2.27.5/interfaces/builtin/time_control.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/time_control.go 2017-09-13 14:47:18.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 @@ -19,14 +19,6 @@ package builtin -import ( - "fmt" - - "github.com/snapcore/snapd/interfaces" - "github.com/snapcore/snapd/interfaces/apparmor" - "github.com/snapcore/snapd/interfaces/udev" -) - const timeControlSummary = `allows setting system date and time` const timeControlBaseDeclarationSlots = ` @@ -100,70 +92,17 @@ /sbin/hwclock ixr, ` -// The type for the rtc interface -type timeControlInterface struct{} - -// Getter for the name of the rtc interface -func (iface *timeControlInterface) Name() string { - return "time-control" -} - -func (iface *timeControlInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ - Summary: timeControlSummary, - ImplicitOnCore: true, - ImplicitOnClassic: true, - BaseDeclarationSlots: timeControlBaseDeclarationSlots, - } -} - -func (iface *timeControlInterface) String() string { - return iface.Name() -} - -// Check validity of the defined slot -func (iface *timeControlInterface) SanitizeSlot(slot *interfaces.Slot) error { - // Does it have right type? - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface)) - } - - // Creation of the slot of this type - // is allowed only by a gadget or os snap - if !(slot.Snap.Type == "os") { - return fmt.Errorf("%s slots are reserved for the operating system snap", iface.Name()) - } - return nil -} - -// Checks and possibly modifies a plug -func (iface *timeControlInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface)) - } - // Currently nothing is checked on the plug side - return nil -} - -func (iface *timeControlInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - spec.AddSnippet(timeControlConnectedPlugAppArmor) - return nil -} - -func (iface *timeControlInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - const udevRule = `KERNEL=="/dev/rtc0", TAG+="%s"` - for appName := range plug.Apps { - tag := udevSnapSecurityName(plug.Snap.Name(), appName) - spec.AddSnippet(fmt.Sprintf(udevRule, tag)) - } - return nil -} - -func (iface *timeControlInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { - // Allow what is allowed in the declarations - return true -} +const timeControlConnectedPlugUDev = `SUBSYSTEM=="rtc", TAG+="###CONNECTED_SECURITY_TAGS###"` func init() { - registerIface(&timeControlInterface{}) + registerIface(&commonInterface{ + name: "time-control", + summary: timeControlSummary, + implicitOnCore: true, + implicitOnClassic: true, + baseDeclarationSlots: timeControlBaseDeclarationSlots, + connectedPlugAppArmor: timeControlConnectedPlugAppArmor, + connectedPlugUDev: timeControlConnectedPlugUDev, + reservedForOS: true, + }) } diff -Nru snapd-2.27.5/interfaces/builtin/time_control_test.go snapd-2.28.5/interfaces/builtin/time_control_test.go --- snapd-2.27.5/interfaces/builtin/time_control_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/time_control_test.go 2017-09-13 14:47:18.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 @@ -27,17 +27,16 @@ "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" ) -type TimeControlTestInterfaceSuite struct { +type TimeControlInterfaceSuite struct { iface interfaces.Interface slot *interfaces.Slot plug *interfaces.Plug } -var _ = Suite(&TimeControlTestInterfaceSuite{ +var _ = Suite(&TimeControlInterfaceSuite{ iface: builtin.MustInterface("time-control"), slot: &interfaces.Slot{ SlotInfo: &snap.SlotInfo{ @@ -49,63 +48,68 @@ plug: nil, }) -func (s *TimeControlTestInterfaceSuite) SetUpTest(c *C) { - consumingSnapInfo := snaptest.MockInfo(c, ` -name: client-snap +const timectlConsumerYaml = `name: consumer apps: - app-accessing-time-control: - command: foo - plugs: [time-control] -`, nil) - s.plug = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["time-control"]} + app: + plugs: [time-control] +` + +const timectlCoreYaml = `name: core +type: os +slots: + time-control: +` + +func (s *TimeControlInterfaceSuite) SetUpTest(c *C) { + s.plug = MockPlug(c, timectlConsumerYaml, nil, "time-control") + s.slot = MockSlot(c, timectlCoreYaml, nil, "time-control") } -func (s *TimeControlTestInterfaceSuite) TestName(c *C) { +func (s *TimeControlInterfaceSuite) TestName(c *C) { c.Assert(s.iface.Name(), Equals, "time-control") } -func (s *TimeControlTestInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ +func (s *TimeControlInterfaceSuite) TestSanitizeSlot(c *C) { + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "time-control", Interface: "time-control", - }}) - c.Assert(err, ErrorMatches, "time-control slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "time-control slots are reserved for the core snap") } -func (s *TimeControlTestInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) +func (s *TimeControlInterfaceSuite) TestSanitizePlug(c *C) { + c.Assert(s.plug.Sanitize(s.iface), IsNil) } -func (s *TimeControlTestInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "time-control"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "time-control"`) +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.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) + c.Check(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "org/freedesktop/timedate1") } -func (s *TimeControlTestInterfaceSuite) TestUsedSecuritySystems(c *C) { - expectedUDevSnippet := `KERNEL=="/dev/rtc0", TAG+="snap_client-snap_app-accessing-time-control"` - - // connected plugs have a non-nil security snippet for apparmor - apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.client-snap.app-accessing-time-control"}) - c.Check(apparmorSpec.SnippetForTag("snap.client-snap.app-accessing-time-control"), testutil.Contains, "org/freedesktop/timedate1") - - // connected plugs have a non-nil security snippet for udev +func (s *TimeControlInterfaceSuite) TestUDevSpec(c *C) { spec := &udev.Specification{} - spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) - c.Assert(err, IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) c.Assert(spec.Snippets(), HasLen, 1) - snippet := spec.Snippets()[0] - c.Assert(snippet, DeepEquals, expectedUDevSnippet) + c.Assert(spec.Snippets()[0], testutil.Contains, `SUBSYSTEM=="rtc", TAG+="snap_consumer_app"`) +} + +func (s *TimeControlInterfaceSuite) 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 setting system date and time`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "time-control") +} + +func (s *TimeControlInterfaceSuite) TestAutoConnect(c *C) { + c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) } -func (s *TimeControlTestInterfaceSuite) TestInterfaces(c *C) { +func (s *TimeControlInterfaceSuite) TestInterfaces(c *C) { c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) } diff -Nru snapd-2.27.5/interfaces/builtin/timeserver_control_test.go snapd-2.28.5/interfaces/builtin/timeserver_control_test.go --- snapd-2.27.5/interfaces/builtin/timeserver_control_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/timeserver_control_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -64,26 +64,18 @@ } func (s *TimeserverControlInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "timeserver-control", Interface: "timeserver-control", - }}) - c.Assert(err, ErrorMatches, "timeserver-control slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "timeserver-control slots are reserved for the core snap") } func (s *TimeserverControlInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *TimeserverControlInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "timeserver-control"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "timeserver-control"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *TimeserverControlInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/timezone_control_test.go snapd-2.28.5/interfaces/builtin/timezone_control_test.go --- snapd-2.27.5/interfaces/builtin/timezone_control_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/timezone_control_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -64,26 +64,18 @@ } func (s *TimezoneControlInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "timezone-control", Interface: "timezone-control", - }}) - c.Assert(err, ErrorMatches, "timezone-control slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "timezone-control slots are reserved for the core snap") } func (s *TimezoneControlInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *TimezoneControlInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "timezone-control"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "timezone-control"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *TimezoneControlInterfaceSuite) TestConnectedPlug(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/tpm.go snapd-2.28.5/interfaces/builtin/tpm.go --- snapd-2.27.5/interfaces/builtin/tpm.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/tpm.go 2017-09-13 14:47:18.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 @@ -35,6 +35,8 @@ /dev/tpm0 rw, ` +const tpmConnectedPlugUDev = `KERNEL=="tpm[0-9]*", TAG+="###CONNECTED_SECURITY_TAGS###"` + func init() { registerIface(&commonInterface{ name: "tpm", @@ -43,6 +45,7 @@ implicitOnClassic: true, baseDeclarationSlots: tpmBaseDeclarationSlots, connectedPlugAppArmor: tpmConnectedPlugAppArmor, + connectedPlugUDev: tpmConnectedPlugUDev, reservedForOS: true, }) } diff -Nru snapd-2.27.5/interfaces/builtin/tpm_test.go snapd-2.28.5/interfaces/builtin/tpm_test.go --- snapd-2.27.5/interfaces/builtin/tpm_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/tpm_test.go 2017-09-13 14:47:18.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 @@ -25,8 +25,8 @@ "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/snap/snaptest" "github.com/snapcore/snapd/testutil" ) @@ -40,23 +40,21 @@ iface: builtin.MustInterface("tpm"), }) -func (s *TpmInterfaceSuite) SetUpTest(c *C) { - var mockPlugSnapInfoYaml = `name: other -version: 1.0 +const tpmConsumerYaml = `name: consumer apps: app: - command: foo plugs: [tpm] ` - s.slot = &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS}, - Name: "tpm", - Interface: "tpm", - }, - } - snapInfo := snaptest.MockInfo(c, mockPlugSnapInfoYaml, nil) - s.plug = &interfaces.Plug{PlugInfo: snapInfo.Plugs["tpm"]} + +const tpmCoreYaml = `name: core +type: os +slots: + tpm: +` + +func (s *TpmInterfaceSuite) SetUpTest(c *C) { + s.plug = MockPlug(c, tpmConsumerYaml, nil, "tpm") + s.slot = MockSlot(c, tpmCoreYaml, nil, "tpm") } func (s *TpmInterfaceSuite) TestName(c *C) { @@ -64,35 +62,44 @@ } func (s *TpmInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "tpm", Interface: "tpm", - }}) - c.Assert(err, ErrorMatches, "tpm slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "tpm slots are reserved for the core snap") } func (s *TpmInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) + c.Assert(s.plug.Sanitize(s.iface), 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.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"`) +} + +func (s *TpmInterfaceSuite) TestStaticInfo(c *C) { + si := interfaces.StaticInfoOf(s.iface) + c.Assert(si.ImplicitOnCore, Equals, true) + c.Assert(si.ImplicitOnClassic, Equals, true) + c.Assert(si.Summary, Equals, `allows access to the Trusted Platform Module device`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "tpm") } -func (s *TpmInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "tpm"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "tpm"`) -} - -func (s *TpmInterfaceSuite) 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) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) - c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, "/dev/tpm0") +func (s *TpmInterfaceSuite) TestAutoConnect(c *C) { + c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) } func (s *TpmInterfaceSuite) TestInterfaces(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/ubuntu_download_manager.go snapd-2.28.5/interfaces/builtin/ubuntu_download_manager.go --- snapd-2.27.5/interfaces/builtin/ubuntu_download_manager.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/ubuntu_download_manager.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,7 +20,6 @@ package builtin import ( - "fmt" "strings" "github.com/snapcore/snapd/interfaces" @@ -201,8 +200,8 @@ return "ubuntu-download-manager" } -func (iface *ubuntuDownloadManagerInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *ubuntuDownloadManagerInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: ubuntuDownloadManagerSummary, BaseDeclarationSlots: ubuntuDownloadManagerBaseDeclarationSlots, } @@ -236,20 +235,6 @@ return nil } -func (iface *ubuntuDownloadManagerInterface) SanitizePlug(slot *interfaces.Plug) error { - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface)) - } - return nil -} - -func (iface *ubuntuDownloadManagerInterface) SanitizeSlot(slot *interfaces.Slot) error { - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface)) - } - return nil -} - func (iface *ubuntuDownloadManagerInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff -Nru snapd-2.27.5/interfaces/builtin/ubuntu_download_manager_test.go snapd-2.28.5/interfaces/builtin/ubuntu_download_manager_test.go --- snapd-2.27.5/interfaces/builtin/ubuntu_download_manager_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/ubuntu_download_manager_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -64,26 +64,17 @@ } func (s *UbuntuDownloadManagerInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *UbuntuDownloadManagerInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "ubuntu-download-manager", Interface: "ubuntu-download-manager", - }}) - c.Assert(err, IsNil) -} - -func (s *UbuntuDownloadManagerInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "ubuntu-download-manager"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "ubuntu-download-manager"`) + }} + c.Assert(slot.Sanitize(s.iface), IsNil) } func (s *UbuntuDownloadManagerInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/udisks2.go snapd-2.28.5/interfaces/builtin/udisks2.go --- snapd-2.27.5/interfaces/builtin/udisks2.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/udisks2.go 2017-09-13 14:47:18.000000000 +0000 @@ -102,6 +102,7 @@ # give raw read access to the system disks and therefore the entire system. /dev/sd* r, /dev/mmcblk* r, +/dev/vd* r, # Needed for probing raw devices capability sys_rawio, @@ -352,14 +353,20 @@ ENV{ID_PART_TABLE_TYPE}=="dos", ENV{ID_PART_ENTRY_TYPE}=="0x0", ENV{ID_PART_ENTRY_NUMBER}=="1", ENV{ID_FS_TYPE}=="iso9660|udf", ENV{UDISKS_IGNORE}="0" ` +const udisks2PermanentSlotUDevTag = ` +SUBSYSTEM=="block", TAG+="###CONNECTED_SECURITY_TAGS###" +# This tags all USB devices, so we'll use AppArmor to mediate specific access (eg, /dev/sd* and /dev/mmcblk*) +SUBSYSTEM=="usb", TAG+="###CONNECTED_SECURITY_TAGS###" +` + type udisks2Interface struct{} func (iface *udisks2Interface) Name() string { return "udisks2" } -func (iface *udisks2Interface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *udisks2Interface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: udisks2Summary, BaseDeclarationSlots: udisks2BaseDeclarationSlots, } @@ -389,7 +396,13 @@ } func (iface *udisks2Interface) UDevPermanentSlot(spec *udev.Specification, slot *interfaces.Slot) error { - spec.AddSnippet(udisks2PermanentSlotUDev) + old := "###CONNECTED_SECURITY_TAGS###" + udevRule := udisks2PermanentSlotUDev + for appName := range slot.Apps { + tag := udevSnapSecurityName(slot.Snap.Name(), appName) + udevRule += strings.Replace(udisks2PermanentSlotUDevTag, old, tag, -1) + } + spec.AddSnippet(udevRule) return nil } @@ -406,14 +419,6 @@ return nil } -func (iface *udisks2Interface) SanitizePlug(slot *interfaces.Plug) error { - return nil -} - -func (iface *udisks2Interface) SanitizeSlot(slot *interfaces.Slot) error { - return nil -} - func (iface *udisks2Interface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff -Nru snapd-2.27.5/interfaces/builtin/udisks2_test.go snapd-2.28.5/interfaces/builtin/udisks2_test.go --- snapd-2.27.5/interfaces/builtin/udisks2_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/udisks2_test.go 2017-09-13 14:47:18.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 @@ -28,8 +28,6 @@ "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/snap/snaptest" "github.com/snapcore/snapd/testutil" ) @@ -39,31 +37,59 @@ plug *interfaces.Plug } -const udisks2mockPlugSnapInfoYaml = `name: udisks2 -version: 1.0 +var _ = Suite(&UDisks2InterfaceSuite{ + iface: builtin.MustInterface("udisks2"), +}) + +const udisks2ConsumerYaml = `name: consumer apps: app: - command: foo plugs: [udisks2] ` -const udisks2mockSlotSnapInfoYaml = `name: udisks2 -version: 1.0 +const udisks2ConsumerTwoAppsYaml = `name: consumer +apps: + app1: + plugs: [udisks2] + app2: + plugs: [udisks2] +` + +const udisks2ConsumerThreeAppsYaml = `name: consumer +apps: + app1: + plugs: [udisks2] + app2: + plugs: [udisks2] + app3: +` + +const udisks2ProducerYaml = `name: producer +apps: + app: + slots: [udisks2] +` + +const udisks2ProducerTwoAppsYaml = `name: producer apps: app1: - command: foo + slots: [udisks2] + app2: slots: [udisks2] ` -var _ = Suite(&UDisks2InterfaceSuite{ - iface: builtin.MustInterface("udisks2"), -}) +const udisks2ProducerThreeAppsYaml = `name: producer +apps: + app1: + slots: [udisks2] + app2: + app3: + slots: [udisks2] +` func (s *UDisks2InterfaceSuite) SetUpTest(c *C) { - slotSnap := snaptest.MockInfo(c, udisks2mockSlotSnapInfoYaml, nil) - plugSnap := snaptest.MockInfo(c, udisks2mockPlugSnapInfoYaml, nil) - s.slot = &interfaces.Slot{SlotInfo: slotSnap.Slots["udisks2"]} - s.plug = &interfaces.Plug{PlugInfo: plugSnap.Plugs["udisks2"]} + s.plug = MockPlug(c, udisks2ConsumerYaml, nil, "udisks2") + s.slot = MockSlot(c, udisks2ProducerYaml, nil, "udisks2") } func (s *UDisks2InterfaceSuite) TestName(c *C) { @@ -71,191 +97,98 @@ } func (s *UDisks2InterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) + c.Assert(s.slot.Sanitize(s.iface), 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.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") + spec = &apparmor.Specification{} + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil), 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") + spec = &apparmor.Specification{} + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil), 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.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") + spec = &apparmor.Specification{} + c.Assert(spec.AddConnectedSlot(s.iface, plug, nil, s.slot, nil), 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") + spec = &apparmor.Specification{} + c.Assert(spec.AddConnectedSlot(s.iface, plug, nil, s.slot, nil), 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.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),`) +} + +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.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.SecurityTags(), DeepEquals, []string{"snap.producer.app"}) + c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, ``) +} + +func (s *UDisks2InterfaceSuite) TestUDevSpec(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], testutil.Contains, `LABEL="udisks_probe_end"`) + c.Assert(spec.Snippets()[0], testutil.Contains, `SUBSYSTEM=="usb", TAG+="snap_producer_app"`) +} + +func (s *UDisks2InterfaceSuite) TestSecCompSpec(c *C) { + spec := &seccomp.Specification{} + c.Assert(spec.AddPermanentSlot(s.iface, s.slot), IsNil) + c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.producer.app"}) + c.Assert(spec.SnippetForTag("snap.producer.app"), testutil.Contains, "mount\n") +} + +func (s *UDisks2InterfaceSuite) TestStaticInfo(c *C) { + si := interfaces.StaticInfoOf(s.iface) + c.Assert(si.ImplicitOnCore, Equals, false) + c.Assert(si.ImplicitOnClassic, Equals, false) + c.Assert(si.Summary, Equals, `allows operating as or interacting with the UDisks2 service`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "udisks2") } -// The label glob when all apps are bound to the udisks2 slot -func (s *UDisks2InterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelAll(c *C) { - app1 := &snap.AppInfo{Name: "app1"} - app2 := &snap.AppInfo{Name: "app2"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "udisks2prod", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - Name: "udisks2", - Interface: "udisks2", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - } - - apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.udisks2.app"}) - c.Assert(apparmorSpec.SnippetForTag("snap.udisks2.app"), testutil.Contains, `peer=(label="snap.udisks2prod.*"),`) -} - -// The label uses alternation when some, but not all, apps is bound to the udisks2 slot -func (s *UDisks2InterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelSome(c *C) { - 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: "udisks2prod", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, - }, - Name: "udisks2", - Interface: "udisks2", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - } - - apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.udisks2.app"}) - c.Assert(apparmorSpec.SnippetForTag("snap.udisks2.app"), testutil.Contains, `peer=(label="snap.udisks2prod.{app1,app2}"),`) -} - -// The label uses short form when exactly one app is bound to the udisks2 slot -func (s *UDisks2InterfaceSuite) TestConnectedPlugSnippetUsesSlotLabelOne(c *C) { - app := &snap.AppInfo{Name: "app"} - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{ - SuggestedName: "udisks2", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - Name: "udisks2", - Interface: "udisks2", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - } - - apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, slot, nil) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.udisks2.app"}) - c.Assert(apparmorSpec.SnippetForTag("snap.udisks2.app"), testutil.Contains, `peer=(label="snap.udisks2.app"),`) -} - -// The label glob when all apps are bound to the udisks2 plug -func (s *UDisks2InterfaceSuite) TestConnectedSlotSnippetUsesPlugLabelAll(c *C) { - app1 := &snap.AppInfo{Name: "app1"} - app2 := &snap.AppInfo{Name: "app2"} - plug := &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{ - SuggestedName: "udisks2client", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - Name: "udisks2", - Interface: "udisks2", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - } - - apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedSlot(s.iface, plug, nil, s.slot, nil) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.udisks2.app1"}) - c.Assert(apparmorSpec.SnippetForTag("snap.udisks2.app1"), testutil.Contains, `peer=(label="snap.udisks2client.*"),`) -} - -// The label uses alternation when some, but not all, apps is bound to the udisks2 plug -func (s *UDisks2InterfaceSuite) TestConnectedSlotSnippetUsesPlugLabelSome(c *C) { - 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: "udisks2", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2, "app3": app3}, - }, - Name: "udisks2", - Interface: "udisks2", - Apps: map[string]*snap.AppInfo{"app1": app1, "app2": app2}, - }, - } - - apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedSlot(s.iface, plug, nil, s.slot, nil) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.udisks2.app1"}) - c.Assert(apparmorSpec.SnippetForTag("snap.udisks2.app1"), testutil.Contains, `peer=(label="snap.udisks2.{app1,app2}"),`) -} - -// The label uses short form when exactly one app is bound to the udisks2 plug -func (s *UDisks2InterfaceSuite) TestConnectedSlotSnippetUsesPlugLabelOne(c *C) { - app := &snap.AppInfo{Name: "app"} - plug := &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{ - SuggestedName: "udisks2", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - Name: "udisks2", - Interface: "udisks2", - Apps: map[string]*snap.AppInfo{"app": app}, - }, - } - - apparmorSpec := &apparmor.Specification{} - err := apparmorSpec.AddConnectedSlot(s.iface, plug, nil, s.slot, nil) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.udisks2.app1"}) - c.Assert(apparmorSpec.SnippetForTag("snap.udisks2.app1"), testutil.Contains, `peer=(label="snap.udisks2.app"),`) -} - -func (s *UDisks2InterfaceSuite) TestUsedSecuritySystems(c *C) { - dbusSpec := &dbus.Specification{} - err := dbusSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) - c.Assert(err, IsNil) - err = dbusSpec.AddPermanentSlot(s.iface, s.slot) - c.Assert(err, IsNil) - c.Assert(dbusSpec.SecurityTags(), HasLen, 2) - - apparmorSpec := &apparmor.Specification{} - err = apparmorSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) - c.Assert(err, IsNil) - err = apparmorSpec.AddPermanentSlot(s.iface, s.slot) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), HasLen, 2) - - udevSpec := &udev.Specification{} - c.Assert(udevSpec.AddPermanentSlot(s.iface, s.slot), IsNil) - c.Assert(udevSpec.Snippets(), HasLen, 1) - c.Check(udevSpec.Snippets()[0], testutil.Contains, `LABEL="udisks_probe_end"`) - - seccompSpec := &seccomp.Specification{} - err = seccompSpec.AddPermanentSlot(s.iface, s.slot) - c.Assert(err, IsNil) - c.Assert(seccompSpec.SecurityTags(), DeepEquals, []string{"snap.udisks2.app1"}) - c.Check(seccompSpec.SnippetForTag("snap.udisks2.app1"), testutil.Contains, "mount\n") -} - -func (s *UDisks2InterfaceSuite) TestDBusConnectedPlug(c *C) { - dbusSpec := &dbus.Specification{} - err := dbusSpec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) - c.Assert(err, IsNil) - c.Assert(dbusSpec.SecurityTags(), DeepEquals, []string{"snap.udisks2.app"}) - c.Check(dbusSpec.SnippetForTag("snap.udisks2.app"), testutil.Contains, ``) -} - -func (s *UDisks2InterfaceSuite) TestDBusPermanentSlot(c *C) { - dbusSpec := &dbus.Specification{} - err := dbusSpec.AddPermanentSlot(s.iface, s.slot) - c.Assert(err, IsNil) - c.Assert(dbusSpec.SecurityTags(), DeepEquals, []string{"snap.udisks2.app1"}) - c.Check(dbusSpec.SnippetForTag("snap.udisks2.app1"), testutil.Contains, ``) +func (s *UDisks2InterfaceSuite) TestAutoConnect(c *C) { + c.Assert(s.iface.AutoConnect(s.plug, s.slot), Equals, true) } func (s *UDisks2InterfaceSuite) TestInterfaces(c *C) { - c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) + c.Assert(builtin.Interfaces(), testutil.DeepContains, s.iface) } diff -Nru snapd-2.27.5/interfaces/builtin/uhid.go snapd-2.28.5/interfaces/builtin/uhid.go --- snapd-2.27.5/interfaces/builtin/uhid.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/uhid.go 2017-09-13 14:47:18.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 @@ -19,14 +19,6 @@ package builtin -import ( - "fmt" - - "github.com/snapcore/snapd/interfaces" - "github.com/snapcore/snapd/interfaces/apparmor" - "github.com/snapcore/snapd/interfaces/udev" -) - const uhidSummary = `allows control over UHID devices` const uhidBaseDeclarationSlots = ` @@ -45,63 +37,17 @@ /dev/uhid rw, ` -type uhidInterface struct{} - -func (iface *uhidInterface) Name() string { - return "uhid" -} - -func (iface *uhidInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ - Summary: uhidSummary, - ImplicitOnCore: true, - ImplicitOnClassic: true, - BaseDeclarationSlots: uhidBaseDeclarationSlots, - } -} - -func (iface *uhidInterface) String() string { - return iface.Name() -} - -// Check the validity of the slot -func (iface *uhidInterface) SanitizeSlot(slot *interfaces.Slot) error { - // First check the type - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface)) - } - - return nil -} - -// Check and possibly modify a plug -func (iface *uhidInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface)) - } - // Currently nothing is checked on the plug side - return nil -} - -func (iface *uhidInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - spec.AddSnippet(uhidConnectedPlugAppArmor) - return nil -} - -func (iface *uhidInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error { - const udevRule = `KERNEL=="uhid", TAG+="%s"` - for appName := range plug.Apps { - tag := udevSnapSecurityName(plug.Snap.Name(), appName) - spec.AddSnippet(fmt.Sprintf(udevRule, tag)) - } - return nil -} - -func (iface *uhidInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { - // Allow what is allowed in the declaration - return true -} +const uhidConnectedPlugUDev = `KERNEL=="uhid", TAG+="###CONNECTED_SECURITY_TAGS###"` func init() { - registerIface(&uhidInterface{}) + registerIface(&commonInterface{ + name: "uhid", + summary: uhidSummary, + implicitOnCore: true, + implicitOnClassic: true, + baseDeclarationSlots: uhidBaseDeclarationSlots, + connectedPlugAppArmor: uhidConnectedPlugAppArmor, + connectedPlugUDev: uhidConnectedPlugUDev, + reservedForOS: true, + }) } diff -Nru snapd-2.27.5/interfaces/builtin/uhid_test.go snapd-2.28.5/interfaces/builtin/uhid_test.go --- snapd-2.27.5/interfaces/builtin/uhid_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/uhid_test.go 2017-09-13 14:47:18.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 @@ -26,7 +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/snaptest" + "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/testutil" ) @@ -40,27 +40,21 @@ iface: builtin.MustInterface("uhid"), }) -func (s *UhidInterfaceSuite) SetUpTest(c *C) { - // Mocking - osSnapInfo := snaptest.MockInfo(c, ` -name: ubuntu-core +const uhidConsumerYaml = `name: consumer +apps: + app: + plugs: [uhid] +` + +const uhidCoreYaml = `name: core type: os slots: - test-slot-1: - interface: uhid - path: /dev/uhid -`, nil) - s.slot = &interfaces.Slot{SlotInfo: osSnapInfo.Slots["test-slot-1"]} - - // Snap Consumers - consumingSnapInfo := snaptest.MockInfo(c, ` -name: client-snap -apps: - app-accessing-slot-1: - command: foo - plugs: [uhid] -`, nil) - s.plug = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["uhid"]} + uhid: +` + +func (s *UhidInterfaceSuite) SetUpTest(c *C) { + s.plug = MockPlug(c, uhidConsumerYaml, nil, "uhid") + s.slot = MockSlot(c, uhidCoreYaml, nil, "uhid") } func (s *UhidInterfaceSuite) TestName(c *C) { @@ -68,32 +62,41 @@ } func (s *UhidInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "some-snap"}, + Name: "uhid", + Interface: "uhid", + }} + + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "uhid slots are reserved for the core snap") } func (s *UhidInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } -func (s *UhidInterfaceSuite) TestConnectedPlugAppArmorSnippets(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) - c.Assert(err, IsNil) - c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.client-snap.app-accessing-slot-1"}) - c.Assert(apparmorSpec.SnippetForTag("snap.client-snap.app-accessing-slot-1"), testutil.Contains, "/dev/uhid rw,\n") +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.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "/dev/uhid rw,\n") } -func (s *UhidInterfaceSuite) TestConnectedPlugUDevSnippets(c *C) { - expectedSnippet1 := `KERNEL=="uhid", TAG+="snap_client-snap_app-accessing-slot-1"` +func (s *UhidInterfaceSuite) TestUDevSpec(c *C) { spec := &udev.Specification{} - err := spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil) - c.Assert(err, IsNil) + c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.slot, nil), IsNil) c.Assert(spec.Snippets(), HasLen, 1) - snippet := spec.Snippets()[0] - c.Assert(snippet, Equals, expectedSnippet1) + c.Assert(spec.Snippets()[0], Equals, `KERNEL=="uhid", TAG+="snap_consumer_app"`) +} + +func (s *UhidInterfaceSuite) 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 control over UHID devices`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "uhid") } func (s *UhidInterfaceSuite) TestAutoConnect(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/unity7.go snapd-2.28.5/interfaces/builtin/unity7.go --- snapd-2.27.5/interfaces/builtin/unity7.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/unity7.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,13 +20,11 @@ package builtin import ( - "fmt" "strings" "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` @@ -88,12 +86,23 @@ /usr/bin/xdg-open ixr, /usr/share/applications/{,*} r, /usr/bin/dbus-send ixr, + +# This allow access to the first version of the snapd-xdg-open +# version which was shipped outside of snapd dbus (send) bus=session path=/ interface=com.canonical.SafeLauncher member=OpenURL peer=(label=unconfined), +# ... and this allows access to the new xdg-open service which +# is now part of snapd itself. +dbus (send) + bus=session + path=/io/snapcraft/Launcher + interface=io.snapcraft.Launcher + member=OpenURL + peer=(label=unconfined), # input methods (ibus) # subset of ibus abstraction @@ -305,6 +314,11 @@ bus=session interface=com.canonical.SafeLauncher.OpenURL peer=(label=unconfined), +# new url helper (part of snap userd) +dbus (send) + bus=session + interface=io.snapcraft.Launcher.OpenURL + peer=(label=unconfined), # dbusmenu dbus (send) @@ -554,8 +568,8 @@ return "unity7" } -func (iface *unity7Interface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *unity7Interface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: unity7Summary, ImplicitOnClassic: true, BaseDeclarationSlots: unity7BaseDeclarationSlots, @@ -579,25 +593,8 @@ return nil } -func (iface *unity7Interface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface.Name())) - } - - return nil -} - func (iface *unity7Interface) SanitizeSlot(slot *interfaces.Slot) error { - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface.Name())) - } - - // Creation of the slot of this type is allowed only by the os snap - if !(slot.Snap.Type == snap.TypeOS) { - return fmt.Errorf("%s slots are reserved for the operating system snap", iface.Name()) - } - - return nil + return sanitizeSlotReservedForOS(iface, slot) } func (iface *unity7Interface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { diff -Nru snapd-2.27.5/interfaces/builtin/unity7_test.go snapd-2.28.5/interfaces/builtin/unity7_test.go --- snapd-2.27.5/interfaces/builtin/unity7_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/unity7_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -66,26 +66,18 @@ } func (s *Unity7InterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "unity7", Interface: "unity7", - }}) - c.Assert(err, ErrorMatches, "unity7 slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "unity7 slots are reserved for the core snap") } func (s *Unity7InterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *Unity7InterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other-snap"}}) }, - PanicMatches, `slot is not of interface "unity7"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other-snap"}}) }, - PanicMatches, `plug is not of interface "unity7"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *Unity7InterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/unity8_calendar_test.go snapd-2.28.5/interfaces/builtin/unity8_calendar_test.go --- snapd-2.27.5/interfaces/builtin/unity8_calendar_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/unity8_calendar_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -78,15 +78,7 @@ } func (s *Unity8CalendarInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *Unity8CalendarInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "unity8-calendar"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "unity8-calendar"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *Unity8CalendarInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/unity8_contacts_test.go snapd-2.28.5/interfaces/builtin/unity8_contacts_test.go --- snapd-2.27.5/interfaces/builtin/unity8_contacts_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/unity8_contacts_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -79,15 +79,7 @@ } func (s *Unity8ContactsInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *Unity8ContactsInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "unity8-contacts"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "unity8-contacts"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *Unity8ContactsInterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/builtin/unity8.go snapd-2.28.5/interfaces/builtin/unity8.go --- snapd-2.27.5/interfaces/builtin/unity8.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/unity8.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,7 +20,6 @@ package builtin import ( - "fmt" "strings" "github.com/snapcore/snapd/interfaces" @@ -103,8 +102,8 @@ return "unity8" } -func (iface *unity8Interface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *unity8Interface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: unity8Summary, BaseDeclarationPlugs: unity8BaseDeclarationPlugs, BaseDeclarationSlots: unity8BaseDeclarationSlots, @@ -128,20 +127,6 @@ return nil } -func (iface *unity8Interface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface)) - } - return nil -} - -func (iface *unity8Interface) SanitizeSlot(slot *interfaces.Slot) error { - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface)) - } - return nil -} - func (iface *unity8Interface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff -Nru snapd-2.27.5/interfaces/builtin/unity8_pim_common.go snapd-2.28.5/interfaces/builtin/unity8_pim_common.go --- snapd-2.27.5/interfaces/builtin/unity8_pim_common.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/unity8_pim_common.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,7 +20,6 @@ package builtin import ( - "fmt" "strings" "github.com/snapcore/snapd/interfaces" @@ -116,8 +115,8 @@ return iface.name } -func (iface *unity8PimCommonInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *unity8PimCommonInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: iface.summary, BaseDeclarationSlots: iface.baseDeclarationSlots, } @@ -165,21 +164,6 @@ return nil } -func (iface *unity8PimCommonInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface \"%s\"", iface.Name())) - } - - return nil -} - -func (iface *unity8PimCommonInterface) SanitizeSlot(slot *interfaces.Slot) error { - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface \"%s\"", iface.Name())) - } - return nil -} - func (iface *unity8PimCommonInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { // allow what declarations allowed return true diff -Nru snapd-2.27.5/interfaces/builtin/upower_observe.go snapd-2.28.5/interfaces/builtin/upower_observe.go --- snapd-2.27.5/interfaces/builtin/upower_observe.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/upower_observe.go 2017-09-15 15:05:07.000000000 +0000 @@ -20,7 +20,6 @@ package builtin import ( - "fmt" "strings" "github.com/snapcore/snapd/interfaces" @@ -28,7 +27,6 @@ "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` @@ -193,6 +191,13 @@ dbus (send) bus=system + path=/org/freedesktop/UPower + interface=org.freedesktop.UPower + member=GetDisplayDevice + peer=(label=###SLOT_SECURITY_TAGS###), + +dbus (send) + bus=system path=/org/freedesktop/UPower/devices/** interface=org.freedesktop.UPower.Device member=GetHistory @@ -221,8 +226,8 @@ return "upower-observe" } -func (iface *upowerObserveInterface) MetaData() interfaces.MetaData { - return interfaces.MetaData{ +func (iface *upowerObserveInterface) StaticInfo() interfaces.StaticInfo { + return interfaces.StaticInfo{ Summary: upowerObserveSummary, ImplicitOnClassic: true, BaseDeclarationSlots: upowerObserveBaseDeclarationSlots, @@ -264,21 +269,8 @@ return nil } -func (iface *upowerObserveInterface) SanitizePlug(plug *interfaces.Plug) error { - if iface.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", iface.Name())) - } - return nil -} - func (iface *upowerObserveInterface) SanitizeSlot(slot *interfaces.Slot) error { - if iface.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", iface.Name())) - } - if slot.Snap.Type != snap.TypeApp && slot.Snap.Type != snap.TypeOS { - return fmt.Errorf("%s slots are reserved for the operating system or application snaps", iface.Name()) - } - return nil + return sanitizeSlotReservedForOSOrApp(iface, slot) } func (iface *upowerObserveInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool { diff -Nru snapd-2.27.5/interfaces/builtin/upower_observe_test.go snapd-2.28.5/interfaces/builtin/upower_observe_test.go --- snapd-2.27.5/interfaces/builtin/upower_observe_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/upower_observe_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -83,26 +83,18 @@ } func (s *UPowerObserveInterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.coreSlot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.coreSlot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "upower-observe", Interface: "upower-observe", - }}) - c.Assert(err, ErrorMatches, "upower-observe slots are reserved for the operating system or application snaps") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "upower-observe slots are reserved for the core and app snaps") } func (s *UPowerObserveInterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *UPowerObserveInterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "upower-observe"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "upower-observe"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } // The label glob when all apps are bound to the ofono slot diff -Nru snapd-2.27.5/interfaces/builtin/utils.go snapd-2.28.5/interfaces/builtin/utils.go --- snapd-2.27.5/interfaces/builtin/utils.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/utils.go 2017-09-13 14:47:18.000000000 +0000 @@ -91,3 +91,27 @@ func udevSnapSecurityName(snapName string, appName string) string { return fmt.Sprintf(`snap_%s_%s`, snapName, appName) } + +// sanitizeSlotReservedForOS checks if slot is of type os. +func sanitizeSlotReservedForOS(iface interfaces.Interface, slot *interfaces.Slot) error { + if slot.Snap.Type != snap.TypeOS { + return fmt.Errorf("%s slots are reserved for the core snap", iface.Name()) + } + return nil +} + +// sanitizeSlotReservedForOSOrGadget checks if the slot is of type os or gadget. +func sanitizeSlotReservedForOSOrGadget(iface interfaces.Interface, slot *interfaces.Slot) 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()) + } + return nil +} + +// sanitizeSlotReservedForOSOrApp checks if the slot is of type os or app. +func sanitizeSlotReservedForOSOrApp(iface interfaces.Interface, slot *interfaces.Slot) 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()) + } + return nil +} diff -Nru snapd-2.27.5/interfaces/builtin/utils_test.go snapd-2.28.5/interfaces/builtin/utils_test.go --- snapd-2.27.5/interfaces/builtin/utils_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/utils_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,83 @@ +// -*- 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 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 +} + +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}}}, +}) + +func (s *utilsSuite) TestSanitizeSlotReservedForOS(c *C) { + errmsg := "iface slots are reserved for the core snap" + c.Assert(builtin.SanitizeSlotReservedForOS(s.iface, s.slotOS), IsNil) + c.Assert(builtin.SanitizeSlotReservedForOS(s.iface, s.slotApp), ErrorMatches, errmsg) + c.Assert(builtin.SanitizeSlotReservedForOS(s.iface, s.slotGadget), ErrorMatches, errmsg) +} + +func (s *utilsSuite) TestSanitizeSlotReservedForOSOrGadget(c *C) { + errmsg := "iface slots are reserved for the core and gadget snaps" + c.Assert(builtin.SanitizeSlotReservedForOSOrGadget(s.iface, s.slotOS), IsNil) + c.Assert(builtin.SanitizeSlotReservedForOSOrGadget(s.iface, s.slotApp), ErrorMatches, errmsg) + c.Assert(builtin.SanitizeSlotReservedForOSOrGadget(s.iface, s.slotGadget), IsNil) +} + +func (s *utilsSuite) TestSanitizeSlotReservedForOSOrApp(c *C) { + errmsg := "iface slots are reserved for the core and app snaps" + c.Assert(builtin.SanitizeSlotReservedForOSOrApp(s.iface, s.slotOS), IsNil) + c.Assert(builtin.SanitizeSlotReservedForOSOrApp(s.iface, s.slotApp), IsNil) + c.Assert(builtin.SanitizeSlotReservedForOSOrApp(s.iface, s.slotGadget), ErrorMatches, errmsg) +} + +func MockPlug(c *C, yaml string, si *snap.SideInfo, plugName string) *interfaces.Plug { + info := snaptest.MockInfo(c, yaml, si) + if plugInfo, ok := info.Plugs[plugName]; ok { + return &interfaces.Plug{PlugInfo: 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 { + info := snaptest.MockInfo(c, yaml, si) + if slotInfo, ok := info.Slots[slotName]; ok { + return &interfaces.Slot{SlotInfo: slotInfo} + } + panic(fmt.Sprintf("cannot find slot %q in snap %q", slotName, info.Name())) +} diff -Nru snapd-2.27.5/interfaces/builtin/wayland.go snapd-2.28.5/interfaces/builtin/wayland.go --- snapd-2.27.5/interfaces/builtin/wayland.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/wayland.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,50 @@ +// -*- 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 waylandSummary = `allows access to compositors supporting wayland protocol` + +const waylandBaseDeclarationSlots = ` + wayland: + allow-installation: + slot-snap-type: + - core +` + +const waylandConnectedPlugAppArmor = ` +# Description: Can access compositors supporting the wayland protocol + +# Allow access to the wayland compsitor server socket +owner /run/user/*/wayland-[0-9]* rw, + +# Needed when using QT_QPA_PLATFORM=wayland-egl +/etc/drirc r, +` + +func init() { + registerIface(&commonInterface{ + name: "wayland", + summary: waylandSummary, + implicitOnClassic: true, + baseDeclarationSlots: waylandBaseDeclarationSlots, + connectedPlugAppArmor: waylandConnectedPlugAppArmor, + reservedForOS: true, + }) +} diff -Nru snapd-2.27.5/interfaces/builtin/wayland_test.go snapd-2.28.5/interfaces/builtin/wayland_test.go --- snapd-2.27.5/interfaces/builtin/wayland_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/wayland_test.go 2017-09-13 14:47:18.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 WaylandInterfaceSuite struct { + iface interfaces.Interface + coreSlot *interfaces.Slot + plug *interfaces.Plug +} + +var _ = Suite(&WaylandInterfaceSuite{ + iface: builtin.MustInterface("wayland"), +}) + +const waylandConsumerYaml = `name: consumer +apps: + app: + plugs: [wayland] +` + +const waylandCoreYaml = `name: core +type: os +slots: + wayland: +` + +func (s *WaylandInterfaceSuite) SetUpTest(c *C) { + s.plug = MockPlug(c, waylandConsumerYaml, nil, "wayland") + s.coreSlot = MockSlot(c, waylandCoreYaml, nil, "wayland") +} + +func (s *WaylandInterfaceSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "wayland") +} + +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") +} + +func (s *WaylandInterfaceSuite) TestSanitizePlug(c *C) { + c.Assert(s.plug.Sanitize(s.iface), IsNil) +} + +func (s *WaylandInterfaceSuite) 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.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "owner /run/user/*/wayland-[0-9]* rw,") + + // connected plug to core slot + spec = &apparmor.Specification{} + c.Assert(spec.AddConnectedSlot(s.iface, s.plug, nil, s.coreSlot, nil), IsNil) + c.Assert(spec.SecurityTags(), HasLen, 0) +} + +func (s *WaylandInterfaceSuite) TestStaticInfo(c *C) { + si := interfaces.StaticInfoOf(s.iface) + c.Assert(si.ImplicitOnCore, Equals, false) + c.Assert(si.ImplicitOnClassic, Equals, true) + c.Assert(si.Summary, Equals, `allows access to compositors supporting wayland protocol`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "wayland") +} + +func (s *WaylandInterfaceSuite) TestInterfaces(c *C) { + c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) +} diff -Nru snapd-2.27.5/interfaces/builtin/x11_test.go snapd-2.28.5/interfaces/builtin/x11_test.go --- snapd-2.27.5/interfaces/builtin/x11_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/builtin/x11_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -66,26 +66,18 @@ } func (s *X11InterfaceSuite) TestSanitizeSlot(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) - err = s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{ + c.Assert(s.slot.Sanitize(s.iface), IsNil) + slot := &interfaces.Slot{SlotInfo: &snap.SlotInfo{ Snap: &snap.Info{SuggestedName: "some-snap"}, Name: "x11", Interface: "x11", - }}) - c.Assert(err, ErrorMatches, "x11 slots are reserved for the operating system snap") + }} + c.Assert(slot.Sanitize(s.iface), ErrorMatches, + "x11 slots are reserved for the core snap") } func (s *X11InterfaceSuite) TestSanitizePlug(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) -} - -func (s *X11InterfaceSuite) TestSanitizeIncorrectInterface(c *C) { - c.Assert(func() { s.iface.SanitizeSlot(&interfaces.Slot{SlotInfo: &snap.SlotInfo{Interface: "other"}}) }, - PanicMatches, `slot is not of interface "x11"`) - c.Assert(func() { s.iface.SanitizePlug(&interfaces.Plug{PlugInfo: &snap.PlugInfo{Interface: "other"}}) }, - PanicMatches, `plug is not of interface "x11"`) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } func (s *X11InterfaceSuite) TestUsedSecuritySystems(c *C) { diff -Nru snapd-2.27.5/interfaces/core.go snapd-2.28.5/interfaces/core.go --- snapd-2.27.5/interfaces/core.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/core.go 2017-09-13 14:47:18.000000000 +0000 @@ -38,6 +38,19 @@ return PlugRef{Snap: plug.Snap.Name(), Name: plug.Name} } +// Sanitize plug with a given snapd interface. +func (plug *Plug) Sanitize(iface Interface) error { + if iface.Name() != plug.Interface { + return fmt.Errorf("cannot sanitize plug %q (interface %q) using interface %q", + plug.Ref(), plug.Interface, iface.Name()) + } + var err error + if iface, ok := iface.(PlugSanitizer); ok { + err = iface.SanitizePlug(plug) + } + return err +} + // PlugRef is a reference to a plug. type PlugRef struct { Snap string `json:"snap"` @@ -60,6 +73,19 @@ return SlotRef{Snap: slot.Snap.Name(), Name: slot.Name} } +// Sanitize slot with a given snapd interface. +func (slot *Slot) Sanitize(iface Interface) error { + if iface.Name() != slot.Interface { + return fmt.Errorf("cannot sanitize slot %q (interface %q) using interface %q", + slot.Ref(), slot.Interface, iface.Name()) + } + var err error + if iface, ok := iface.(SlotSanitizer); ok { + err = iface.SanitizeSlot(slot) + } + return err +} + // SlotRef is a reference to a slot. type SlotRef struct { Snap string `json:"snap"` @@ -71,12 +97,21 @@ return fmt.Sprintf("%s:%s", ref.Snap, ref.Name) } -// Interfaces holds information about a list of plugs and slots, their connections and interface meta-data. +// Interfaces holds information about a list of plugs, slots and their connections. type Interfaces struct { Plugs []*Plug `json:"plugs"` Slots []*Slot `json:"slots"` } +// Info holds information about a given interface and its instances. +type Info struct { + Name string + Summary string + DocURL string + Plugs []*snap.PlugInfo + Slots []*snap.SlotInfo +} + // ConnRef holds information about plug and slot reference that form a particular connection. type ConnRef struct { PlugRef PlugRef @@ -114,12 +149,6 @@ // Unique and public name of this interface. Name() string - // SanitizePlug checks if a plug is correct, altering if necessary. - SanitizePlug(plug *Plug) error - - // SanitizeSlot checks if a slot is correct, altering if necessary. - SanitizeSlot(slot *Slot) error - // AutoConnect returns whether plug and slot should be // implicitly auto-connected assuming they will be an // unambiguous connection candidate and declaration-based checks @@ -127,16 +156,24 @@ AutoConnect(plug *Plug, slot *Slot) bool } -// MetaData describes various meta-data of a given interface. +// PlugSanitizer can be implemented by Interfaces that have reasons to sanitize their plugs. +type PlugSanitizer interface { + SanitizePlug(plug *Plug) error +} + +// SlotSanitizer can be implemented by Interfaces that have reasons to sanitize their slots. +type SlotSanitizer interface { + SanitizeSlot(slot *Slot) error +} + +// StaticInfo describes various static-info of a given interface. // // The Summary must be a one-line string of length suitable for listing views. -// The Description must describe the purpose of the interface in non-technical -// terms. The DocumentationURL can point to website (e.g. a forum thread) that -// goes into more depth and documents the interface in detail. -type MetaData struct { - Summary string `json:"summary,omitempty"` - Description string `json:"description,omitempty"` - DocumentationURL string `json:"documentation-url,omitempty"` +// The DocsURL can point to website (e.g. a forum thread) that goes into more +// depth and documents the interface in detail. +type StaticInfo struct { + Summary string `json:"summary,omitempty"` + DocURL string `json:"doc-url,omitempty"` // ImplicitOnCore controls if a slot is automatically added to core (non-classic) systems. ImplicitOnCore bool `json:"implicit-on-core,omitempty"` @@ -149,15 +186,15 @@ BaseDeclarationSlots string } -// IfaceMetaData returns the meta-data of the given interface. -func IfaceMetaData(iface Interface) (md MetaData) { +// StaticInfoOf returns the static-info of the given interface. +func StaticInfoOf(iface Interface) (si StaticInfo) { type metaDataProvider interface { - MetaData() MetaData + StaticInfo() StaticInfo } if iface, ok := iface.(metaDataProvider); ok { - md = iface.MetaData() + si = iface.StaticInfo() } - return md + return si } // Specification describes interactions between backends and interfaces. diff -Nru snapd-2.27.5/interfaces/core_test.go snapd-2.28.5/interfaces/core_test.go --- snapd-2.27.5/interfaces/core_test.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/interfaces/core_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,12 +20,15 @@ package interfaces_test import ( + "fmt" "testing" . "gopkg.in/check.v1" . "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/ifacetest" "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snaptest" ) func Test(t *testing.T) { @@ -177,3 +180,43 @@ _, err = ParseConnRef("snap:plug snap:slot:garbage") c.Assert(err, ErrorMatches, `malformed connection identifier: ".*"`) } + +func (s *CoreSuite) TestSanitizePlug(c *C) { + info := snaptest.MockInfo(c, ` +name: snap +plugs: + plug: + interface: iface +`, nil) + plug := &Plug{PlugInfo: info.Plugs["plug"]} + c.Assert(plug.Sanitize(&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{ + InterfaceName: "other", + }), ErrorMatches, `cannot sanitize plug "snap:plug" \(interface "iface"\) using interface "other"`) +} + +func (s *CoreSuite) TestSanitizeSlot(c *C) { + info := snaptest.MockInfo(c, ` +name: snap +slots: + slot: + interface: iface +`, nil) + slot := &Slot{SlotInfo: info.Slots["slot"]} + c.Assert(slot.Sanitize(&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{ + InterfaceName: "other", + }), ErrorMatches, `cannot sanitize slot "snap:slot" \(interface "iface"\) using interface "other"`) +} diff -Nru snapd-2.27.5/interfaces/dbus/backend.go snapd-2.28.5/interfaces/dbus/backend.go --- snapd-2.27.5/interfaces/dbus/backend.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/interfaces/dbus/backend.go 2017-10-13 07:55:10.000000000 +0000 @@ -31,10 +31,13 @@ "bytes" "fmt" "os" + "path/filepath" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/release" "github.com/snapcore/snapd/snap" ) @@ -46,6 +49,17 @@ return "dbus" } +// setupDbusServiceForUserd will setup the service file for the new +// `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 + } + return osutil.CopyFile(filepath.Join(coreRoot, dst), dst, osutil.CopyFlagPreserveAll) +} + // Setup creates dbus configuration files specific to a given snap. // // DBus has no concept of a complain mode so confinment type is ignored. @@ -57,6 +71,13 @@ return fmt.Errorf("cannot obtain dbus specification for snap %q: %s", snapName, err) } + // core on classic is special + if snapName == "core" && release.OnClassic { + if err := setupDbusServiceForUserd(snapInfo); err != nil { + logger.Noticef("cannot create host `snap userd` dbus service file: %s", err) + } + } + // Get the files that this snap should have content, err := b.deriveContent(spec.(*Specification), snapInfo) if err != nil { diff -Nru snapd-2.27.5/interfaces/export_test.go snapd-2.28.5/interfaces/export_test.go --- snapd-2.27.5/interfaces/export_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/interfaces/export_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,68 @@ +// -*- 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 + +type BySlotRef bySlotRef + +func (c BySlotRef) Len() int { return bySlotRef(c).Len() } +func (c BySlotRef) Swap(i, j int) { bySlotRef(c).Swap(i, j) } +func (c BySlotRef) Less(i, j int) bool { return bySlotRef(c).Less(i, j) } + +type ByPlugRef byPlugRef + +func (c ByPlugRef) Len() int { return byPlugRef(c).Len() } +func (c ByPlugRef) Swap(i, j int) { byPlugRef(c).Swap(i, j) } +func (c ByPlugRef) Less(i, j int) bool { return byPlugRef(c).Less(i, j) } + +type ByPlugSnapAndName byPlugSnapAndName + +func (c ByPlugSnapAndName) Len() int { return byPlugSnapAndName(c).Len() } +func (c ByPlugSnapAndName) Swap(i, j int) { byPlugSnapAndName(c).Swap(i, j) } +func (c ByPlugSnapAndName) Less(i, j int) bool { return byPlugSnapAndName(c).Less(i, j) } + +type BySlotSnapAndName bySlotSnapAndName + +func (c BySlotSnapAndName) Len() int { return bySlotSnapAndName(c).Len() } +func (c BySlotSnapAndName) Swap(i, j int) { bySlotSnapAndName(c).Swap(i, j) } +func (c BySlotSnapAndName) Less(i, j int) bool { return bySlotSnapAndName(c).Less(i, j) } + +type ByBackendName byBackendName + +func (c ByBackendName) Len() int { return byBackendName(c).Len() } +func (c ByBackendName) Swap(i, j int) { byBackendName(c).Swap(i, j) } +func (c ByBackendName) Less(i, j int) bool { return byBackendName(c).Less(i, j) } + +type ByInterfaceName byInterfaceName + +func (c ByInterfaceName) Len() int { return byInterfaceName(c).Len() } +func (c ByInterfaceName) Swap(i, j int) { byInterfaceName(c).Swap(i, j) } +func (c ByInterfaceName) Less(i, j int) bool { return byInterfaceName(c).Less(i, j) } + +type ByPlugInfo byPlugInfo + +func (c ByPlugInfo) Len() int { return byPlugInfo(c).Len() } +func (c ByPlugInfo) Swap(i, j int) { byPlugInfo(c).Swap(i, j) } +func (c ByPlugInfo) Less(i, j int) bool { return byPlugInfo(c).Less(i, j) } + +type BySlotInfo bySlotInfo + +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) } diff -Nru snapd-2.27.5/interfaces/ifacetest/testiface.go snapd-2.28.5/interfaces/ifacetest/testiface.go --- snapd-2.27.5/interfaces/ifacetest/testiface.go 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/interfaces/ifacetest/testiface.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,8 +20,6 @@ package ifacetest import ( - "fmt" - "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/dbus" @@ -36,7 +34,8 @@ // It is public so that it can be consumed from other packages. type TestInterface struct { // InterfaceName is the name of this interface - InterfaceName string + InterfaceName string + InterfaceStaticInfo interfaces.StaticInfo // AutoConnectCallback is the callback invoked inside AutoConnect AutoConnectCallback func(*interfaces.Plug, *interfaces.Slot) bool // SanitizePlugCallback is the callback invoked inside SanitizePlug() @@ -114,11 +113,12 @@ return t.InterfaceName } +func (t *TestInterface) StaticInfo() interfaces.StaticInfo { + return t.InterfaceStaticInfo +} + // SanitizePlug checks and possibly modifies a plug. func (t *TestInterface) SanitizePlug(plug *interfaces.Plug) error { - if t.Name() != plug.Interface { - panic(fmt.Sprintf("plug is not of interface %q", t)) - } if t.SanitizePlugCallback != nil { return t.SanitizePlugCallback(plug) } @@ -127,9 +127,6 @@ // SanitizeSlot checks and possibly modifies a slot. func (t *TestInterface) SanitizeSlot(slot *interfaces.Slot) error { - if t.Name() != slot.Interface { - panic(fmt.Sprintf("slot is not of interface %q", t)) - } if t.SanitizeSlotCallback != nil { return t.SanitizeSlotCallback(slot) } diff -Nru snapd-2.27.5/interfaces/ifacetest/testiface_test.go snapd-2.28.5/interfaces/ifacetest/testiface_test.go --- snapd-2.27.5/interfaces/ifacetest/testiface_test.go 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/interfaces/ifacetest/testiface_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -39,7 +39,12 @@ } var _ = Suite(&TestInterfaceSuite{ - iface: &ifacetest.TestInterface{InterfaceName: "test"}, + iface: &ifacetest.TestInterface{ + InterfaceName: "test", + InterfaceStaticInfo: interfaces.StaticInfo{ + Summary: "summary", + }, + }, plug: &interfaces.Plug{ PlugInfo: &snap.PlugInfo{ Snap: &snap.Info{SuggestedName: "snap"}, @@ -61,6 +66,12 @@ c.Assert(s.iface.Name(), Equals, "test") } +func (s *TestInterfaceSuite) TestStaticInfo(c *C) { + c.Assert(interfaces.StaticInfoOf(s.iface), Equals, interfaces.StaticInfo{ + Summary: "summary", + }) +} + // TestInterface has provisions to customize validation func (s *TestInterfaceSuite) TestValidatePlugError(c *C) { iface := &ifacetest.TestInterface{ @@ -86,8 +97,7 @@ // TestInterface doesn't do any sanitization by default func (s *TestInterfaceSuite) TestSanitizePlugOK(c *C) { - err := s.iface.SanitizePlug(s.plug) - c.Assert(err, IsNil) + c.Assert(s.plug.Sanitize(s.iface), IsNil) } // TestInterface has provisions to customize sanitization @@ -98,26 +108,12 @@ return fmt.Errorf("sanitize plug failed") }, } - err := iface.SanitizePlug(s.plug) - c.Assert(err, ErrorMatches, "sanitize plug failed") -} - -// TestInterface sanitization still checks for interface identity -func (s *TestInterfaceSuite) TestSanitizePlugWrongInterface(c *C) { - plug := &interfaces.Plug{ - PlugInfo: &snap.PlugInfo{ - Snap: &snap.Info{SuggestedName: "snap"}, - Name: "name", - Interface: "other-interface", - }, - } - c.Assert(func() { s.iface.SanitizePlug(plug) }, Panics, "plug is not of interface \"test\"") + c.Assert(s.plug.Sanitize(iface), ErrorMatches, "sanitize plug failed") } // TestInterface doesn't do any sanitization by default func (s *TestInterfaceSuite) TestSanitizeSlotOK(c *C) { - err := s.iface.SanitizeSlot(s.slot) - c.Assert(err, IsNil) + c.Assert(s.slot.Sanitize(s.iface), IsNil) } // TestInterface has provisions to customize sanitization @@ -128,20 +124,7 @@ return fmt.Errorf("sanitize slot failed") }, } - err := iface.SanitizeSlot(s.slot) - c.Assert(err, ErrorMatches, "sanitize slot failed") -} - -// TestInterface sanitization still checks for interface identity -func (s *TestInterfaceSuite) TestSanitizeSlotWrongInterface(c *C) { - slot := &interfaces.Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "snap"}, - Name: "name", - Interface: "interface", - }, - } - c.Assert(func() { s.iface.SanitizeSlot(slot) }, Panics, "slot is not of interface \"test\"") + c.Assert(s.slot.Sanitize(iface), ErrorMatches, "sanitize slot failed") } // TestInterface hands out empty plug security snippets diff -Nru snapd-2.27.5/interfaces/json.go snapd-2.28.5/interfaces/json.go --- snapd-2.27.5/interfaces/json.go 2016-04-08 21:26:25.000000000 +0000 +++ snapd-2.28.5/interfaces/json.go 2017-09-13 14:47:18.000000000 +0000 @@ -27,10 +27,10 @@ type plugJSON struct { Snap string `json:"snap"` Name string `json:"plug"` - Interface string `json:"interface"` + Interface string `json:"interface,omitempty"` Attrs map[string]interface{} `json:"attrs,omitempty"` Apps []string `json:"apps,omitempty"` - Label string `json:"label"` + Label string `json:"label,omitempty"` Connections []SlotRef `json:"connections,omitempty"` } @@ -55,10 +55,10 @@ type slotJSON struct { Snap string `json:"snap"` Name string `json:"slot"` - Interface string `json:"interface"` + Interface string `json:"interface,omitempty"` Attrs map[string]interface{} `json:"attrs,omitempty"` Apps []string `json:"apps,omitempty"` - Label string `json:"label"` + Label string `json:"label,omitempty"` Connections []PlugRef `json:"connections,omitempty"` } @@ -78,3 +78,41 @@ Connections: slot.Connections, }) } + +// interfaceInfoJSON aids in marshaling Info into JSON. +type interfaceInfoJSON struct { + Name string `json:"name,omitempty"` + Summary string `json:"summary,omitempty"` + DocURL string `json:"doc-url,omitempty"` + Plugs []*plugJSON `json:"plugs,omitempty"` + Slots []*slotJSON `json:"slots,omitempty"` +} + +// MarshalJSON returns the JSON encoding of Info. +func (info *Info) MarshalJSON() ([]byte, error) { + plugs := make([]*plugJSON, 0, len(info.Plugs)) + for _, plug := range info.Plugs { + plugs = append(plugs, &plugJSON{ + Snap: plug.Snap.Name(), + Name: plug.Name, + Attrs: plug.Attrs, + Label: plug.Label, + }) + } + slots := make([]*slotJSON, 0, len(info.Slots)) + for _, slot := range info.Slots { + slots = append(slots, &slotJSON{ + Snap: slot.Snap.Name(), + Name: slot.Name, + Attrs: slot.Attrs, + Label: slot.Label, + }) + } + return json.Marshal(&interfaceInfoJSON{ + Name: info.Name, + Summary: info.Summary, + DocURL: info.DocURL, + Plugs: plugs, + Slots: slots, + }) +} diff -Nru snapd-2.27.5/interfaces/json_test.go snapd-2.28.5/interfaces/json_test.go --- snapd-2.27.5/interfaces/json_test.go 2016-06-01 20:08:33.000000000 +0000 +++ snapd-2.28.5/interfaces/json_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -28,12 +28,13 @@ "github.com/snapcore/snapd/snap" ) -type JSONSuite struct{} - -var _ = Suite(&JSONSuite{}) +type JSONSuite struct { + plug *Plug + slot *Slot +} -func (s *JSONSuite) TestPlugMarshalJSON(c *C) { - plug := &Plug{ +var _ = Suite(&JSONSuite{ + plug: &Plug{ PlugInfo: &snap.PlugInfo{ Snap: &snap.Info{SuggestedName: "snap-name"}, Name: "plug-name", @@ -50,8 +51,29 @@ Snap: "other-snap-name", Name: "slot-name", }}, - } - data, err := json.Marshal(plug) + }, + slot: &Slot{ + SlotInfo: &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "snap-name"}, + Name: "slot-name", + Interface: "interface", + Attrs: map[string]interface{}{"key": "value"}, + Apps: map[string]*snap.AppInfo{ + "app-name": { + Name: "app-name", + }, + }, + Label: "label", + }, + Connections: []PlugRef{{ + Snap: "other-snap-name", + Name: "plug-name", + }}, + }, +}) + +func (s *JSONSuite) TestPlugMarshalJSON(c *C) { + data, err := json.Marshal(s.plug) c.Assert(err, IsNil) var repr map[string]interface{} err = json.Unmarshal(data, &repr) @@ -70,25 +92,7 @@ } func (s *JSONSuite) TestSlotMarshalJSON(c *C) { - slot := &Slot{ - SlotInfo: &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "snap-name"}, - Name: "slot-name", - Interface: "interface", - Attrs: map[string]interface{}{"key": "value"}, - Apps: map[string]*snap.AppInfo{ - "app-name": { - Name: "app-name", - }, - }, - Label: "label", - }, - Connections: []PlugRef{{ - Snap: "other-snap-name", - Name: "plug-name", - }}, - } - data, err := json.Marshal(slot) + data, err := json.Marshal(s.slot) c.Assert(err, IsNil) var repr map[string]interface{} err = json.Unmarshal(data, &repr) @@ -105,3 +109,39 @@ }, }) } + +func (s *JSONSuite) TestInfoMarshalJSON(c *C) { + ifaceInfo := &Info{ + Name: "iface", + Summary: "interface summary", + DocURL: "http://example.org/", + Plugs: []*snap.PlugInfo{s.plug.PlugInfo}, + Slots: []*snap.SlotInfo{s.slot.SlotInfo}, + } + data, err := json.Marshal(ifaceInfo) + c.Assert(err, IsNil) + var repr map[string]interface{} + err = json.Unmarshal(data, &repr) + c.Assert(err, IsNil) + c.Check(repr, DeepEquals, map[string]interface{}{ + "name": "iface", + "summary": "interface summary", + "doc-url": "http://example.org/", + "plugs": []interface{}{ + map[string]interface{}{ + "snap": "snap-name", + "plug": "plug-name", + "attrs": map[string]interface{}{"key": "value"}, + "label": "label", + }, + }, + "slots": []interface{}{ + map[string]interface{}{ + "snap": "snap-name", + "slot": "slot-name", + "attrs": map[string]interface{}{"key": "value"}, + "label": "label", + }, + }, + }) +} diff -Nru snapd-2.27.5/interfaces/mount/ns_test.go snapd-2.28.5/interfaces/mount/ns_test.go --- snapd-2.27.5/interfaces/mount/ns_test.go 2017-08-24 06:52:40.000000000 +0000 +++ snapd-2.28.5/interfaces/mount/ns_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -45,9 +45,6 @@ s.AddCleanup(release.MockOnClassic(true)) // Anything that just gives us no-reexec. s.AddCleanup(release.MockReleaseInfo(&release.OS{ID: "fedora"})) - - os.Setenv("SNAP_REEXEC", "0") - s.AddCleanup(func() { os.Unsetenv("SNAP_REEXEC") }) } func (s *nsSuite) TestDiscardNamespaceMnt(c *C) { diff -Nru snapd-2.27.5/interfaces/naming_test.go snapd-2.28.5/interfaces/naming_test.go --- snapd-2.27.5/interfaces/naming_test.go 2016-06-01 20:08:33.000000000 +0000 +++ snapd-2.28.5/interfaces/naming_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -32,3 +32,7 @@ func (s *NamingSuite) TestSecurityTagGlob(c *C) { c.Check(SecurityTagGlob("http"), Equals, "snap.http.*") } + +func (s *NamingSuite) TestInterfaceServiceName(c *C) { + c.Check(InterfaceServiceName("http", "helper"), Equals, "snap.http.interface.helper.service") +} diff -Nru snapd-2.27.5/interfaces/policy/basedeclaration.go snapd-2.28.5/interfaces/policy/basedeclaration.go --- snapd-2.27.5/interfaces/policy/basedeclaration.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/policy/basedeclaration.go 2017-09-13 14:47:18.000000000 +0000 @@ -168,7 +168,7 @@ return nil, err } for _, iface := range ifaces { - plugPolicy := interfaces.IfaceMetaData(iface).BaseDeclarationPlugs + plugPolicy := interfaces.StaticInfoOf(iface).BaseDeclarationPlugs if _, err := buf.WriteString(trimTrailingNewline(plugPolicy)); err != nil { return nil, err } @@ -177,7 +177,7 @@ return nil, err } for _, iface := range ifaces { - slotPolicy := interfaces.IfaceMetaData(iface).BaseDeclarationSlots + slotPolicy := interfaces.StaticInfoOf(iface).BaseDeclarationSlots if _, err := buf.WriteString(trimTrailingNewline(slotPolicy)); err != nil { return nil, err } diff -Nru snapd-2.27.5/interfaces/policy/basedeclaration_test.go snapd-2.28.5/interfaces/policy/basedeclaration_test.go --- snapd-2.27.5/interfaces/policy/basedeclaration_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/policy/basedeclaration_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -140,6 +140,8 @@ // these simply auto-connect, anything else doesn't autoconnect := map[string]bool{ "browser-support": true, + "desktop": true, + "desktop-legacy": true, "gsettings": true, "media-hub": true, "mir": true, @@ -155,6 +157,7 @@ "unity7": true, "unity8": true, "upower-observe": true, + "wayland": true, "x11": true, } @@ -456,7 +459,9 @@ slotInstallation = map[string][]string{ // other "autopilot-introspection": {"core"}, - "bluez": {"app"}, + "avahi-control": {"app", "core"}, + "avahi-observe": {"app", "core"}, + "bluez": {"app", "core"}, "bool-file": {"core", "gadget"}, "browser-support": {"core"}, "content": {"app", "gadget"}, @@ -482,9 +487,10 @@ "network-status": {"app"}, "ofono": {"app", "core"}, "online-accounts-service": {"app"}, - "ppp": {"core"}, - "pulseaudio": {"app", "core"}, - "serial-port": {"core", "gadget"}, + "ppp": {"core"}, + "pulseaudio": {"app", "core"}, + "serial-port": {"core", "gadget"}, + "spi": {"core", "gadget"}, "storage-framework-service": {"app"}, "thumbnailer-service": {"app"}, "ubuntu-download-manager": {"app"}, @@ -539,7 +545,8 @@ c.Check(err, NotNil, comm) } if compareWithSanitize { - sanitizeErr := iface.SanitizeSlot(&interfaces.Slot{SlotInfo: slotInfo}) + slot := &interfaces.Slot{SlotInfo: slotInfo} + sanitizeErr := slot.Sanitize(iface) if err == nil { c.Check(sanitizeErr, IsNil, comm) } else { @@ -612,7 +619,6 @@ // connecting with these interfaces needs to be allowed on // case-by-case basis noconnect := map[string]bool{ - "bluez": true, "content": true, "docker": true, "fwupd": true, diff -Nru snapd-2.27.5/interfaces/repo.go snapd-2.28.5/interfaces/repo.go --- snapd-2.27.5/interfaces/repo.go 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/interfaces/repo.go 2017-09-13 14:47:18.000000000 +0000 @@ -80,6 +80,115 @@ return nil } +// InfoOptions describes options for Info. +// +// Names: return just this subset if non-empty. +// Doc: return documentation. +// Plugs: return information about plugs. +// Slots: return information about slots. +// Connected: only consider interfaces with at least one connection. +type InfoOptions struct { + Names []string + Doc bool + Plugs bool + Slots bool + Connected bool +} + +func (r *Repository) interfaceInfo(iface Interface, opts *InfoOptions) *Info { + // NOTE: InfoOptions.Connected is handled by Info + si := StaticInfoOf(iface) + ifaceName := iface.Name() + ii := &Info{ + Name: ifaceName, + Summary: si.Summary, + } + if opts != nil && opts.Doc { + // Collect documentation URL + ii.DocURL = si.DocURL + } + if opts != nil && opts.Plugs { + // 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 + if plugInfo.Interface == ifaceName { + ii.Plugs = append(ii.Plugs, plugInfo) + } + } + } + } + if opts != nil && opts.Slots { + // 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 + if slotInfo.Interface == ifaceName { + ii.Slots = append(ii.Slots, slotInfo) + } + } + } + } + return ii +} + +// Info returns information about interfaces in the system. +// +// If names is empty then all interfaces are considered. Query options decide +// which data to return but can also skip interfaces without connections. See +// the documentation of InfoOptions for details. +func (r *Repository) Info(opts *InfoOptions) []*Info { + r.m.Lock() + defer r.m.Unlock() + + // If necessary compute the set of interfaces with any connections. + var connected map[string]bool + if opts != nil && opts.Connected { + connected = make(map[string]bool) + for _, plugMap := range r.slotPlugs { + for plug, ok := range plugMap { + if ok { + connected[plug.Interface] = true + } + } + } + for _, slotMap := range r.plugSlots { + for slot, ok := range slotMap { + if ok { + connected[slot.Interface] = true + } + } + } + } + + // If weren't asked about specific interfaces then query every interface. + var names []string + if opts == nil || len(opts.Names) == 0 { + for _, iface := range r.ifaces { + name := iface.Name() + if connected == nil || connected[name] { + // Optionally filter out interfaces without connections. + names = append(names, name) + } + } + } else { + names = make([]string, len(opts.Names)) + copy(names, opts.Names) + } + sort.Strings(names) + + // Query each interface we are interested in. + infos := make([]*Info, 0, len(names)) + for _, name := range names { + if iface, ok := r.ifaces[name]; ok { + if connected == nil || connected[name] { + infos = append(infos, r.interfaceInfo(iface, opts)) + } + } + } + return infos +} + // AddBackend adds the provided security backend to the repository. func (r *Repository) AddBackend(backend SecurityBackend) error { r.m.Lock() @@ -154,7 +263,7 @@ return fmt.Errorf("cannot add plug, interface %q is not known", plug.Interface) } // Reject plug that don't pass interface-specific sanitization - if err := i.SanitizePlug(plug); err != nil { + if err := plug.Sanitize(i); err != nil { return fmt.Errorf("cannot add plug: %v", err) } if _, ok := r.plugs[snapName][plug.Name]; ok { @@ -253,7 +362,7 @@ if i == nil { return fmt.Errorf("cannot add slot, interface %q is not known", slot.Interface) } - if err := i.SanitizeSlot(slot); err != nil { + if err := slot.Sanitize(i); err != nil { return fmt.Errorf("cannot add slot: %v", err) } if _, ok := r.slots[snapName][slot.Name]; ok { @@ -783,7 +892,7 @@ continue } plug := &Plug{PlugInfo: plugInfo} - if err := iface.SanitizePlug(plug); err != nil { + if err := plug.Sanitize(iface); err != nil { bad.issues[plugName] = err.Error() continue } @@ -805,7 +914,7 @@ continue } slot := &Slot{SlotInfo: slotInfo} - if err := iface.SanitizeSlot(slot); err != nil { + if err := slot.Sanitize(iface); err != nil { bad.issues[slotName] = err.Error() continue } diff -Nru snapd-2.27.5/interfaces/repo_test.go snapd-2.28.5/interfaces/repo_test.go --- snapd-2.27.5/interfaces/repo_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/repo_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -1845,3 +1845,83 @@ candidatePlugs := repo.AutoConnectCandidatePlugs("content-slot-snap", "exported-content", contentPolicyCheck) c.Assert(candidatePlugs, HasLen, 0) } + +func (s *RepositorySuite) TestInfo(c *C) { + r := s.emptyRepo + + // Add some test interfaces. + i1 := &ifacetest.TestInterface{InterfaceName: "i1", InterfaceStaticInfo: StaticInfo{Summary: "i1 summary", DocURL: "http://example.com/i1"}} + i2 := &ifacetest.TestInterface{InterfaceName: "i2", InterfaceStaticInfo: StaticInfo{Summary: "i2 summary", DocURL: "http://example.com/i2"}} + i3 := &ifacetest.TestInterface{InterfaceName: "i3", InterfaceStaticInfo: StaticInfo{Summary: "i3 summary", DocURL: "http://example.com/i3"}} + c.Assert(r.AddInterface(i1), IsNil) + c.Assert(r.AddInterface(i2), IsNil) + c.Assert(r.AddInterface(i3), IsNil) + + // Add some test snaps. + s1 := snaptest.MockInfo(c, fmt.Sprintf(` +name: s1 +apps: + s1: + plugs: [i1, i2] +`), nil) + c.Assert(r.AddSnap(s1), IsNil) + + s2 := snaptest.MockInfo(c, fmt.Sprintf(` +name: s2 +apps: + s2: + slots: [i1, i3] +`), nil) + c.Assert(r.AddSnap(s2), IsNil) + + s3 := snaptest.MockInfo(c, fmt.Sprintf(` +name: s3 +type: os +slots: + i2: +`), nil) + c.Assert(r.AddSnap(s3), IsNil) + + // Connect a few things for the tests below. + c.Assert(r.Connect(ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "i1"}, SlotRef: SlotRef{Snap: "s2", Name: "i1"}}), IsNil) + c.Assert(r.Connect(ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "i2"}, SlotRef: SlotRef{Snap: "s3", Name: "i2"}}), IsNil) + + // Without any names or options we get the summary of all the interfaces. + infos := r.Info(nil) + c.Assert(infos, DeepEquals, []*Info{ + {Name: "i1", Summary: "i1 summary"}, + {Name: "i2", Summary: "i2 summary"}, + {Name: "i3", Summary: "i3 summary"}, + }) + + // We can choose specific interfaces, unknown names are just skipped. + infos = r.Info(&InfoOptions{Names: []string{"i2", "i4"}}) + c.Assert(infos, DeepEquals, []*Info{ + {Name: "i2", Summary: "i2 summary"}, + }) + + // We can ask for documentation. + infos = r.Info(&InfoOptions{Names: []string{"i2"}, Doc: true}) + c.Assert(infos, DeepEquals, []*Info{ + {Name: "i2", Summary: "i2 summary", DocURL: "http://example.com/i2"}, + }) + + // We can ask for a list of plugs. + infos = r.Info(&InfoOptions{Names: []string{"i2"}, Plugs: true}) + c.Assert(infos, DeepEquals, []*Info{ + {Name: "i2", Summary: "i2 summary", Plugs: []*snap.PlugInfo{s1.Plugs["i2"]}}, + }) + + // We can ask for a list of slots too. + infos = r.Info(&InfoOptions{Names: []string{"i2"}, Slots: true}) + c.Assert(infos, DeepEquals, []*Info{ + {Name: "i2", Summary: "i2 summary", Slots: []*snap.SlotInfo{s3.Slots["i2"]}}, + }) + + // We can also ask for only those interfaces that have connected plugs or slots. + infos = r.Info(&InfoOptions{Connected: true}) + c.Assert(infos, DeepEquals, []*Info{ + {Name: "i1", Summary: "i1 summary"}, + {Name: "i2", Summary: "i2 summary"}, + }) +} diff -Nru snapd-2.27.5/interfaces/seccomp/template.go snapd-2.28.5/interfaces/seccomp/template.go --- snapd-2.27.5/interfaces/seccomp/template.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/interfaces/seccomp/template.go 2017-09-13 14:47:18.000000000 +0000 @@ -69,12 +69,12 @@ # snappy doesn't currently support per-app UID/GIDs. All daemons run as 'root' # so allow chown to 'root'. DAC will prevent non-root from chowning to root. -chown - 0 0 -chown32 - 0 0 -fchown - 0 0 -fchown32 - 0 0 -lchown - 0 0 -lchown32 - 0 0 +chown - u:root g:root +chown32 - u:root g:root +fchown - u:root g:root +fchown32 - u:root g:root +lchown - u:root g:root +lchown32 - u:root g:root clock_getres clock_gettime diff -Nru snapd-2.27.5/interfaces/sorting.go snapd-2.28.5/interfaces/sorting.go --- snapd-2.27.5/interfaces/sorting.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/interfaces/sorting.go 2017-09-13 14:47:18.000000000 +0000 @@ -19,6 +19,12 @@ package interfaces +import ( + "sort" + + "github.com/snapcore/snapd/snap" +) + type bySlotRef []SlotRef func (c bySlotRef) Len() int { return len(c) } @@ -68,8 +74,71 @@ func (c byBackendName) Len() int { return len(c) } func (c byBackendName) Swap(i, j int) { c[i], c[j] = c[j], c[i] } func (c byBackendName) Less(i, j int) bool { - if c[i].Name() != c[j].Name() { - return c[i].Name() < c[j].Name() + return c[i].Name() < c[j].Name() +} + +func sortedSnapNamesWithPlugs(m map[string]map[string]*Plug) []string { + keys := make([]string, 0, len(m)) + for key := range m { + keys = append(keys, key) + } + sort.Strings(keys) + return keys +} + +func sortedPlugNames(m map[string]*Plug) []string { + keys := make([]string, 0, len(m)) + for key := range m { + keys = append(keys, key) } + sort.Strings(keys) + return keys +} + +func sortedSnapNamesWithSlots(m map[string]map[string]*Slot) []string { + keys := make([]string, 0, len(m)) + for key := range m { + keys = append(keys, key) + } + sort.Strings(keys) + return keys +} + +func sortedSlotNames(m map[string]*Slot) []string { + keys := make([]string, 0, len(m)) + for key := range m { + keys = append(keys, key) + } + sort.Strings(keys) + return keys +} + +type byInterfaceName []Interface + +func (c byInterfaceName) Len() int { return len(c) } +func (c byInterfaceName) Swap(i, j int) { c[i], c[j] = c[j], c[i] } +func (c byInterfaceName) Less(i, j int) bool { return c[i].Name() < c[j].Name() } + +type byPlugInfo []*snap.PlugInfo + +func (c byPlugInfo) Len() int { return len(c) } +func (c byPlugInfo) Swap(i, j int) { c[i], c[j] = c[j], c[i] } +func (c byPlugInfo) Less(i, j int) bool { + if c[i].Snap.Name() != c[j].Snap.Name() { + return c[i].Snap.Name() < c[j].Snap.Name() + } + return c[i].Name < c[j].Name +} + +type bySlotInfo []*snap.SlotInfo + +func (c bySlotInfo) Len() int { return len(c) } +func (c bySlotInfo) Swap(i, j int) { c[i], c[j] = c[j], c[i] } +func (c bySlotInfo) Less(i, j int) bool { + if c[i].Snap.Name() != c[j].Snap.Name() { + return c[i].Snap.Name() < c[j].Snap.Name() + } + return c[i].Name < c[j].Name +} diff -Nru snapd-2.27.5/interfaces/sorting_test.go snapd-2.28.5/interfaces/sorting_test.go --- snapd-2.27.5/interfaces/sorting_test.go 2016-03-03 10:00:09.000000000 +0000 +++ snapd-2.28.5/interfaces/sorting_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -17,12 +17,16 @@ * */ -package interfaces +package interfaces_test import ( "sort" . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/ifacetest" + "github.com/snapcore/snapd/snap" ) type SortingSuite struct{} @@ -30,7 +34,7 @@ var _ = Suite(&SortingSuite{}) func (s *SortingSuite) TestSortBySlotRef(c *C) { - list := []SlotRef{ + list := []interfaces.SlotRef{ { Snap: "snap-2", Name: "name-2", @@ -44,8 +48,8 @@ Name: "name-1", }, } - sort.Sort(bySlotRef(list)) - c.Assert(list, DeepEquals, []SlotRef{ + sort.Sort(interfaces.BySlotRef(list)) + c.Assert(list, DeepEquals, []interfaces.SlotRef{ { Snap: "snap-1", Name: "name-1", @@ -62,7 +66,7 @@ } func (s *SortingSuite) TestSortByPlugRef(c *C) { - list := []PlugRef{ + list := []interfaces.PlugRef{ { Snap: "snap-2", Name: "name-2", @@ -76,8 +80,8 @@ Name: "name-1", }, } - sort.Sort(byPlugRef(list)) - c.Assert(list, DeepEquals, []PlugRef{ + sort.Sort(interfaces.ByPlugRef(list)) + c.Assert(list, DeepEquals, []interfaces.PlugRef{ { Snap: "snap-1", Name: "name-1", @@ -92,3 +96,59 @@ }, }) } + +func (s *SortingSuite) TestByBackendName(c *C) { + list := []interfaces.SecurityBackend{ + &ifacetest.TestSecurityBackend{BackendName: "backend-2"}, + &ifacetest.TestSecurityBackend{BackendName: "backend-1"}, + } + sort.Sort(interfaces.ByBackendName(list)) + c.Assert(list, DeepEquals, []interfaces.SecurityBackend{ + &ifacetest.TestSecurityBackend{BackendName: "backend-1"}, + &ifacetest.TestSecurityBackend{BackendName: "backend-2"}, + }) +} + +func (s *SortingSuite) TestByInterfaceName(c *C) { + list := []interfaces.Interface{ + &ifacetest.TestInterface{InterfaceName: "iface-2"}, + &ifacetest.TestInterface{InterfaceName: "iface-1"}, + } + sort.Sort(interfaces.ByInterfaceName(list)) + c.Assert(list, DeepEquals, []interfaces.Interface{ + &ifacetest.TestInterface{InterfaceName: "iface-1"}, + &ifacetest.TestInterface{InterfaceName: "iface-2"}, + }) +} + +func (s *SortingSuite) TestByPlugInfo(c *C) { + list := []*snap.PlugInfo{ + {Snap: &snap.Info{SuggestedName: "name-2"}, Name: "plug-2"}, + {Snap: &snap.Info{SuggestedName: "name-2"}, Name: "plug-1"}, + {Snap: &snap.Info{SuggestedName: "name-1"}, Name: "plug-2"}, + {Snap: &snap.Info{SuggestedName: "name-1"}, Name: "plug-1"}, + } + sort.Sort(interfaces.ByPlugInfo(list)) + c.Assert(list, DeepEquals, []*snap.PlugInfo{ + {Snap: &snap.Info{SuggestedName: "name-1"}, Name: "plug-1"}, + {Snap: &snap.Info{SuggestedName: "name-1"}, Name: "plug-2"}, + {Snap: &snap.Info{SuggestedName: "name-2"}, Name: "plug-1"}, + {Snap: &snap.Info{SuggestedName: "name-2"}, Name: "plug-2"}, + }) +} + +func (s *SortingSuite) TestBySlotInfo(c *C) { + list := []*snap.SlotInfo{ + {Snap: &snap.Info{SuggestedName: "name-2"}, Name: "plug-2"}, + {Snap: &snap.Info{SuggestedName: "name-2"}, Name: "plug-1"}, + {Snap: &snap.Info{SuggestedName: "name-1"}, Name: "plug-2"}, + {Snap: &snap.Info{SuggestedName: "name-1"}, Name: "plug-1"}, + } + sort.Sort(interfaces.BySlotInfo(list)) + c.Assert(list, DeepEquals, []*snap.SlotInfo{ + {Snap: &snap.Info{SuggestedName: "name-1"}, Name: "plug-1"}, + {Snap: &snap.Info{SuggestedName: "name-1"}, Name: "plug-2"}, + {Snap: &snap.Info{SuggestedName: "name-2"}, Name: "plug-1"}, + {Snap: &snap.Info{SuggestedName: "name-2"}, Name: "plug-2"}, + }) +} diff -Nru snapd-2.27.5/jsonutil/json.go snapd-2.28.5/jsonutil/json.go --- snapd-2.27.5/jsonutil/json.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/jsonutil/json.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,40 @@ +// -*- 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 jsonutil + +import ( + "encoding/json" + "fmt" + "io" +) + +// DecodeWithNumber decodes input data using json.Decoder, ensuring numbers are preserved +// via json.Number data type. It errors out on invalid json or any excess input. +func DecodeWithNumber(r io.Reader, value interface{}) error { + dec := json.NewDecoder(r) + dec.UseNumber() + if err := dec.Decode(&value); err != nil { + return err + } + if dec.More() { + return fmt.Errorf("cannot parse json value") + } + return nil +} diff -Nru snapd-2.27.5/jsonutil/json_test.go snapd-2.28.5/jsonutil/json_test.go --- snapd-2.27.5/jsonutil/json_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/jsonutil/json_test.go 2017-09-13 14:47:18.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 jsonutil_test + +import ( + "encoding/json" + "strings" + "testing" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/jsonutil" +) + +func Test(t *testing.T) { TestingT(t) } + +type utilSuite struct{} + +var _ = Suite(&utilSuite{}) + +func (s *utilSuite) TestDecodeError(c *C) { + input := "{]" + var output interface{} + err := jsonutil.DecodeWithNumber(strings.NewReader(input), &output) + c.Assert(err, NotNil) + c.Assert(err, ErrorMatches, `invalid character ']' looking for beginning of object key string`) +} + +func (s *utilSuite) TestDecodeErrorOnExcessData(c *C) { + input := "1000000000[1,2]" + var output interface{} + err := jsonutil.DecodeWithNumber(strings.NewReader(input), &output) + c.Assert(err, NotNil) + c.Assert(err, ErrorMatches, `cannot parse json value`) +} + +func (s *utilSuite) TestDecodeSuccess(c *C) { + input := `{"a":1000000000, "b": 1.2, "c": "foo", "d":null}` + var output interface{} + err := jsonutil.DecodeWithNumber(strings.NewReader(input), &output) + c.Assert(err, IsNil) + c.Assert(output, DeepEquals, map[string]interface{}{ + "a": json.Number("1000000000"), + "b": json.Number("1.2"), + "c": "foo", + "d": nil, + }) +} diff -Nru snapd-2.27.5/osutil/exec_test.go snapd-2.28.5/osutil/exec_test.go --- snapd-2.27.5/osutil/exec_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/osutil/exec_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -207,8 +207,9 @@ wrf, wrc := osutil.WaitingReaderGuts(stdout) c.Assert(wrf, FitsTypeOf, &os.File{}) - c.Check(wrf.(*os.File).Close(), Equals, syscall.EINVAL) // i.e. already closed - c.Check(wrc.ProcessState, NotNil) // i.e. already waited for + // Depending on golang version the error is one of the two. + c.Check(wrf.(*os.File).Close(), ErrorMatches, "invalid argument|file already closed") + c.Check(wrc.ProcessState, NotNil) // i.e. already waited for } func (s *execSuite) TestStreamCommandSad(c *C) { @@ -221,6 +222,7 @@ wrf, wrc := osutil.WaitingReaderGuts(stdout) c.Assert(wrf, FitsTypeOf, &os.File{}) - c.Check(wrf.(*os.File).Close(), Equals, syscall.EINVAL) // i.e. already closed - c.Check(wrc.ProcessState, NotNil) // i.e. already waited for + // Depending on golang version the error is one of the two. + c.Check(wrf.(*os.File).Close(), ErrorMatches, "invalid argument|file already closed") + c.Check(wrc.ProcessState, NotNil) // i.e. already waited for } diff -Nru snapd-2.27.5/osutil/io.go snapd-2.28.5/osutil/io.go --- snapd-2.27.5/osutil/io.go 2016-08-11 17:27:59.000000000 +0000 +++ snapd-2.28.5/osutil/io.go 2017-09-13 14:47:18.000000000 +0000 @@ -23,6 +23,7 @@ "errors" "os" "path/filepath" + "strings" "github.com/snapcore/snapd/strutil" ) @@ -35,6 +36,11 @@ AtomicWriteFollow AtomicWriteFlags = 1 << iota ) +// Allow disabling sync for testing. This brings massive improvements on +// certain filesystems (like btrfs) and very much noticeable improvements in +// all unit tests in genreal. +var snapdUnsafeIO bool = len(os.Args) > 0 && strings.HasSuffix(os.Args[0], ".test") && GetenvBool("SNAPD_UNSAFE_IO") + // AtomicWriteFile updates the filename atomically and works otherwise // like io/ioutil.WriteFile() // @@ -91,13 +97,18 @@ return errors.New("internal error: AtomicWriteFileChown needs none or both of uid and gid set") } - if err := fd.Sync(); err != nil { - return err + if !snapdUnsafeIO { + if err := fd.Sync(); err != nil { + return err + } } if err := os.Rename(tmp, filename); err != nil { return err } - return dir.Sync() + if !snapdUnsafeIO { + return dir.Sync() + } + return nil } diff -Nru snapd-2.27.5/osutil/stat.go snapd-2.28.5/osutil/stat.go --- snapd-2.27.5/osutil/stat.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/osutil/stat.go 2017-09-13 14:47:18.000000000 +0000 @@ -22,6 +22,7 @@ import ( "os" "os/exec" + "syscall" ) // FileExists return true if given path can be stat()ed by us. Note that @@ -76,3 +77,13 @@ } return p } + +// IsWritable checks if the given file/directory can be written by +// the current user +func IsWritable(path string) bool { + // from "fcntl.h" + const W_OK = 2 + + err := syscall.Access(path, W_OK) + return err == nil +} diff -Nru snapd-2.27.5/osutil/stat_test.go snapd-2.28.5/osutil/stat_test.go --- snapd-2.27.5/osutil/stat_test.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/osutil/stat_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -24,6 +24,7 @@ "io/ioutil" "os" "path/filepath" + "strings" . "gopkg.in/check.v1" ) @@ -100,3 +101,45 @@ lookPath = func(name string) (string, error) { return "", fmt.Errorf("Not found") } c.Assert(LookPathDefault("bar", "/bin/bla"), Equals, "/bin/bla") } + +func makeTestPath(c *C, path string, mode os.FileMode) string { + path = filepath.Join(c.MkDir(), path) + + switch { + // request for directory + case strings.HasSuffix(path, "/"): + err := os.MkdirAll(path, os.FileMode(mode)) + c.Assert(err, IsNil) + default: + // request for a file + err := ioutil.WriteFile(path, nil, os.FileMode(mode)) + c.Assert(err, IsNil) + } + + return path +} + +func (s *StatTestSuite) TestIsWritableDir(c *C) { + for _, t := range []struct { + path string + mode os.FileMode + isWritable bool + }{ + {"dir/", 0755, true}, + {"dir/", 0555, false}, + {"dir/", 0750, true}, + {"dir/", 0550, false}, + {"dir/", 0700, true}, + {"dir/", 0500, false}, + + {"file", 0644, true}, + {"file", 0444, false}, + {"file", 0640, true}, + {"file", 0440, false}, + {"file", 0600, true}, + {"file", 0400, false}, + } { + writable := IsWritable(makeTestPath(c, t.path, t.mode)) + c.Check(writable, Equals, t.isWritable, Commentf("incorrect result for %q (%s), got %v, expected %v", t.path, t.mode, writable, t.isWritable)) + } +} diff -Nru snapd-2.27.5/overlord/assertstate/assertstate_test.go snapd-2.28.5/overlord/assertstate/assertstate_test.go --- snapd-2.27.5/overlord/assertstate/assertstate_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/overlord/assertstate/assertstate_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -29,7 +29,6 @@ "time" "golang.org/x/crypto/sha3" - "golang.org/x/net/context" . "gopkg.in/check.v1" @@ -41,10 +40,10 @@ "github.com/snapcore/snapd/overlord/auth" "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" - "github.com/snapcore/snapd/progress" "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap/snaptest" "github.com/snapcore/snapd/store" + "github.com/snapcore/snapd/store/storetest" ) func TestAssertManager(t *testing.T) { TestingT(t) } @@ -63,6 +62,7 @@ var _ = Suite(&assertMgrSuite{}) type fakeStore struct { + storetest.Store state *state.State db asserts.RODatabase } @@ -84,48 +84,10 @@ return a, nil } -func (*fakeStore) SnapInfo(store.SnapSpec, *auth.UserState) (*snap.Info, error) { - panic("fakeStore.SnapInfo not expected") -} - -func (sto *fakeStore) Find(*store.Search, *auth.UserState) ([]*snap.Info, error) { - panic("fakeStore.Find not expected") -} - -func (sto *fakeStore) LookupRefresh(*store.RefreshCandidate, *auth.UserState) (*snap.Info, error) { - panic("fakeStore.LookupRefresh not expected") -} - -func (sto *fakeStore) ListRefresh([]*store.RefreshCandidate, *auth.UserState) ([]*snap.Info, error) { - panic("fakeStore.ListRefresh not expected") -} - -func (sto *fakeStore) Download(context.Context, string, string, *snap.DownloadInfo, progress.Meter, *auth.UserState) error { - panic("fakeStore.Download not expected") -} - -func (sto *fakeStore) SuggestedCurrency() string { - panic("fakeStore.SuggestedCurrency not expected") -} - -func (sto *fakeStore) Buy(*store.BuyOptions, *auth.UserState) (*store.BuyResult, error) { - panic("fakeStore.Buy not expected") -} - -func (sto *fakeStore) ReadyToBuy(*auth.UserState) error { - panic("fakeStore.ReadyToBuy not expected") -} - -func (sto *fakeStore) Sections(*auth.UserState) ([]string, error) { - panic("fakeStore.Sections not expected") -} - func (s *assertMgrSuite) SetUpTest(c *C) { dirs.SetRootDir(c.MkDir()) - rootPrivKey, _ := assertstest.GenerateKey(1024) - storePrivKey, _ := assertstest.GenerateKey(752) - s.storeSigning = assertstest.NewStoreStack("can0nical", rootPrivKey, storePrivKey) + s.storeSigning = assertstest.NewStoreStack("can0nical", nil) s.restore = sysdb.InjectTrusted(s.storeSigning.Trusted) dev1PrivKey, _ := assertstest.GenerateKey(752) diff -Nru snapd-2.27.5/overlord/auth/auth.go snapd-2.28.5/overlord/auth/auth.go --- snapd-2.27.5/overlord/auth/auth.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/overlord/auth/auth.go 2017-10-12 19:26:13.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 @@ -31,6 +31,7 @@ "gopkg.in/macaroon.v1" "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/asserts/sysdb" "github.com/snapcore/snapd/overlord/state" ) @@ -329,17 +330,36 @@ return nil, ErrInvalidAuth } +// DeviceSessionRequestParams gathers the assertions and information to be sent to request a device session. +type DeviceSessionRequestParams struct { + Request *asserts.DeviceSessionRequest + Serial *asserts.Serial + Model *asserts.Model +} + +func (p *DeviceSessionRequestParams) EncodedRequest() string { + return string(asserts.Encode(p.Request)) +} + +func (p *DeviceSessionRequestParams) EncodedSerial() string { + return string(asserts.Encode(p.Serial)) +} + +func (p *DeviceSessionRequestParams) EncodedModel() string { + return string(asserts.Encode(p.Model)) +} + // DeviceAssertions helps exposing the assertions about device identity. // All methods should return state.ErrNoState if the underlying needed // information is not (yet) available. type DeviceAssertions interface { // Model returns the device model assertion. Model() (*asserts.Model, error) - // Serial returns the device model assertion. + // Serial returns the device serial assertion. Serial() (*asserts.Serial, error) - // DeviceSessionRequest produces a device-session-request with the given nonce, it also returns the device serial assertion. - DeviceSessionRequest(nonce string) (*asserts.DeviceSessionRequest, *asserts.Serial, error) + // 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) } var ( @@ -357,7 +377,7 @@ StoreID(fallback string) (string, error) - DeviceSessionRequest(nonce string) (devSessionRequest []byte, serial []byte, err error) + DeviceSessionRequestParams(nonce string) (*DeviceSessionRequestParams, error) } // authContext helps keeping track of auth data in the state and exposing it. @@ -421,10 +441,11 @@ return cur, nil } -// StoreID returns the store set in the model assertion, if mod != nil, -// or the override from the UBUNTU_STORE_ID envvar. +// StoreID returns the store set in the model assertion, if mod != nil +// and it's not the generic classic model, or the override from the +// UBUNTU_STORE_ID envvar. func StoreID(mod *asserts.Model) string { - if mod != nil { + if mod != nil && mod.Ref().Unique() != sysdb.GenericClassicModel().Ref().Unique() { return mod.Store() } return os.Getenv("UBUNTU_STORE_ID") @@ -448,17 +469,17 @@ return fallback, nil } -// DeviceSessionRequest produces a device-session-request with the given nonce, it also returns the encoded device serial assertion. It returns ErrNoSerial if the device serial is not yet initialized. -func (ac *authContext) DeviceSessionRequest(nonce string) (deviceSessionRequest []byte, serial []byte, err error) { +// DeviceSessionRequestParams produces a device-session-request with the given nonce, together with other required parameters, the device serial and model assertions. It returns ErrNoSerial if the device serial is not yet initialized. +func (ac *authContext) DeviceSessionRequestParams(nonce string) (*DeviceSessionRequestParams, error) { if ac.deviceAsserts == nil { - return nil, nil, ErrNoSerial + return nil, ErrNoSerial } - req, ser, err := ac.deviceAsserts.DeviceSessionRequest(nonce) + params, err := ac.deviceAsserts.DeviceSessionRequestParams(nonce) if err == state.ErrNoState { - return nil, nil, ErrNoSerial + return nil, ErrNoSerial } if err != nil { - return nil, nil, err + return nil, err } - return asserts.Encode(req), asserts.Encode(ser), nil + return params, nil } diff -Nru snapd-2.27.5/overlord/auth/auth_test.go snapd-2.28.5/overlord/auth/auth_test.go --- snapd-2.27.5/overlord/auth/auth_test.go 2016-11-24 09:36:04.000000000 +0000 +++ snapd-2.28.5/overlord/auth/auth_test.go 2017-10-12 19:26:13.000000000 +0000 @@ -29,6 +29,7 @@ "gopkg.in/macaroon.v1" "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/asserts/sysdb" "github.com/snapcore/snapd/overlord/auth" "github.com/snapcore/snapd/overlord/state" ) @@ -525,10 +526,11 @@ c.Assert(err, IsNil) c.Check(storeID, Equals, "env-store-id") } -func (as *authSuite) TestAuthContextDeviceSessionRequestNilDeviceAssertions(c *C) { + +func (as *authSuite) TestAuthContextDeviceSessionRequestParamsNilDeviceAssertions(c *C) { authContext := auth.NewAuthContext(as.state, nil) - _, _, err := authContext.DeviceSessionRequest("NONCE") + _, err := authContext.DeviceSessionRequestParams("NONCE") c.Check(err, Equals, auth.ErrNoSerial) } @@ -606,29 +608,39 @@ return a.(*asserts.Serial), nil } -func (da *testDeviceAssertions) DeviceSessionRequest(nonce string) (*asserts.DeviceSessionRequest, *asserts.Serial, error) { +func (da *testDeviceAssertions) DeviceSessionRequestParams(nonce string) (*auth.DeviceSessionRequestParams, error) { if da.nothing { - return nil, nil, state.ErrNoState + return nil, state.ErrNoState } ex := strings.Replace(exDeviceSessionRequest, "@NONCE@", nonce, 1) ex = strings.Replace(ex, "@TS@", time.Now().Format(time.RFC3339), 1) - a1, err := asserts.Decode([]byte(ex)) + aReq, err := asserts.Decode([]byte(ex)) + if err != nil { + return nil, err + } + + aSer, err := asserts.Decode([]byte(exSerial)) if err != nil { - return nil, nil, err + return nil, err } - a2, err := asserts.Decode([]byte(exSerial)) + aMod, err := asserts.Decode([]byte(exModel)) if err != nil { - return nil, nil, err + return nil, err } - return a1.(*asserts.DeviceSessionRequest), a2.(*asserts.Serial), nil + + return &auth.DeviceSessionRequestParams{ + Request: aReq.(*asserts.DeviceSessionRequest), + Serial: aSer.(*asserts.Serial), + Model: aMod.(*asserts.Model), + }, nil } func (as *authSuite) TestAuthContextMissingDeviceAssertions(c *C) { // no assertions in state authContext := auth.NewAuthContext(as.state, &testDeviceAssertions{nothing: true}) - _, _, err := authContext.DeviceSessionRequest("NONCE") + _, err := authContext.DeviceSessionRequestParams("NONCE") c.Check(err, Equals, auth.ErrNoSerial) storeID, err := authContext.StoreID("fallback") @@ -640,17 +652,46 @@ // having assertions in state authContext := auth.NewAuthContext(as.state, &testDeviceAssertions{}) - req, serial, err := authContext.DeviceSessionRequest("NONCE-1") + params, err := authContext.DeviceSessionRequestParams("NONCE-1") c.Assert(err, IsNil) - c.Check(strings.Contains(string(req), "nonce: NONCE-1\n"), Equals, true) - c.Check(strings.Contains(string(req), "serial: 9999\n"), Equals, true) - c.Check(strings.Contains(string(serial), "serial: 9999\n"), Equals, true) + req := params.EncodedRequest() + serial := params.EncodedSerial() + model := params.EncodedModel() + + c.Check(strings.Contains(req, "nonce: NONCE-1\n"), Equals, true) + c.Check(strings.Contains(req, "serial: 9999\n"), Equals, true) + + c.Check(strings.Contains(serial, "model: baz-3000\n"), Equals, true) + c.Check(strings.Contains(serial, "serial: 9999\n"), Equals, true) + c.Check(strings.Contains(model, "model: baz-3000\n"), Equals, true) + c.Check(strings.Contains(model, "serial:\n"), Equals, false) + + // going to be ignored + os.Setenv("UBUNTU_STORE_ID", "env-store-id") + defer os.Unsetenv("UBUNTU_STORE_ID") storeID, err := authContext.StoreID("store-id") c.Assert(err, IsNil) c.Check(storeID, Equals, "my-brand-store-id") } +func (as *authSuite) TestAuthContextWithDeviceAssertionsGenericClassicModel(c *C) { + model, err := asserts.Decode([]byte(exModel)) + c.Assert(err, IsNil) + // (ab)use the example as the generic classic model + r := sysdb.MockGenericClassicModel(model.(*asserts.Model)) + defer r() + // having assertions in state + authContext := auth.NewAuthContext(as.state, &testDeviceAssertions{}) + + // for the generic classic model we continue to consider the env var + os.Setenv("UBUNTU_STORE_ID", "env-store-id") + defer os.Unsetenv("UBUNTU_STORE_ID") + storeID, err := authContext.StoreID("store-id") + c.Assert(err, IsNil) + c.Check(storeID, Equals, "env-store-id") +} + func (as *authSuite) TestUsers(c *C) { as.state.Lock() user1, err1 := auth.NewUser(as.state, "user1", "email1@test.com", "macaroon", []string{"discharge"}) diff -Nru snapd-2.27.5/overlord/configstate/config/helpers.go snapd-2.28.5/overlord/configstate/config/helpers.go --- snapd-2.27.5/overlord/configstate/config/helpers.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/overlord/configstate/config/helpers.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,11 +20,13 @@ package config import ( + "bytes" "encoding/json" "fmt" "regexp" "strings" + "github.com/snapcore/snapd/jsonutil" "github.com/snapcore/snapd/overlord/state" "github.com/snapcore/snapd/snap" ) @@ -56,11 +58,11 @@ case *json.RawMessage: // Raw replaces pristine on commit. Unpack, update, and repack. var configm map[string]interface{} - err := json.Unmarshal([]byte(*config), &configm) - if err != nil { + + if err := jsonutil.DecodeWithNumber(bytes.NewReader(*config), &configm); err != nil { return nil, fmt.Errorf("snap %q option %q is not a map", snapName, strings.Join(subkeys[:pos], ".")) } - _, err = PatchConfig(snapName, subkeys, pos, configm, value) + _, err := PatchConfig(snapName, subkeys, pos, configm, value) if err != nil { return nil, err } @@ -98,8 +100,7 @@ if !ok { raw = jsonRaw(value) } - err := json.Unmarshal([]byte(*raw), result) - if err != nil { + if err := jsonutil.DecodeWithNumber(bytes.NewReader(*raw), &result); err != nil { key := strings.Join(subkeys, ".") return fmt.Errorf("internal error: cannot unmarshal snap %q option %q into %T: %s, json: %s", snapName, key, result, err, *raw) } @@ -112,8 +113,7 @@ if !ok { raw = jsonRaw(value) } - err := json.Unmarshal([]byte(*raw), &configm) - if err != nil { + if err := jsonutil.DecodeWithNumber(bytes.NewReader(*raw), &configm); err != nil { return fmt.Errorf("snap %q option %q is not a map", snapName, strings.Join(subkeys[:pos+1], ".")) } } diff -Nru snapd-2.27.5/overlord/configstate/config/transaction.go snapd-2.28.5/overlord/configstate/config/transaction.go --- snapd-2.27.5/overlord/configstate/config/transaction.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/overlord/configstate/config/transaction.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,11 +20,13 @@ package config import ( + "bytes" "encoding/json" "fmt" "strings" "sync" + "github.com/snapcore/snapd/jsonutil" "github.com/snapcore/snapd/overlord/state" ) @@ -124,6 +126,7 @@ if IsNoOption(err) { err = getFromPristine(snapName, subkeys, 0, t.pristine[snapName], result) } + return err } @@ -153,8 +156,7 @@ } if pos+1 == len(subkeys) { - err := json.Unmarshal([]byte(*raw), result) - if err != nil { + if err := jsonutil.DecodeWithNumber(bytes.NewReader(*raw), &result); err != nil { key := strings.Join(subkeys, ".") return fmt.Errorf("internal error: cannot unmarshal snap %q option %q into %T: %s, json: %s", snapName, key, result, err, *raw) } @@ -162,8 +164,7 @@ } var configm map[string]*json.RawMessage - err := json.Unmarshal([]byte(*raw), &configm) - if err != nil { + if err := jsonutil.DecodeWithNumber(bytes.NewReader(*raw), &configm); err != nil { return fmt.Errorf("snap %q option %q is not a map", snapName, strings.Join(subkeys[:pos+1], ".")) } return getFromPristine(snapName, subkeys, pos+1, configm, result) @@ -225,7 +226,7 @@ return jsonRaw(change) } var pristinem map[string]*json.RawMessage - if err := json.Unmarshal([]byte(*pristine), &pristinem); err != nil { + if err := jsonutil.DecodeWithNumber(bytes.NewReader(*pristine), &pristinem); err != nil { // Not a map. Overwrite with the change. return jsonRaw(change) } diff -Nru snapd-2.27.5/overlord/configstate/config/transaction_test.go snapd-2.28.5/overlord/configstate/config/transaction_test.go --- snapd-2.27.5/overlord/configstate/config/transaction_test.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/overlord/configstate/config/transaction_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,15 +20,17 @@ package config_test import ( + "bytes" "encoding/json" "fmt" + "strings" "testing" . "gopkg.in/check.v1" + "github.com/snapcore/snapd/jsonutil" "github.com/snapcore/snapd/overlord/configstate/config" "github.com/snapcore/snapd/overlord/state" - "strings" ) func TestT(t *testing.T) { TestingT(t) } @@ -62,8 +64,7 @@ } kv := strings.SplitN(pair, "=", 2) var v interface{} - err := json.Unmarshal([]byte(kv[1]), &v) - if err != nil { + if err := jsonutil.DecodeWithNumber(strings.NewReader(kv[1]), &v); err != nil { v = kv[1] } m[kv[0]] = v @@ -85,14 +86,15 @@ var setGetTests = [][]setGetOp{{ // Basics. `set one=1 two=2`, - `setunder three=3`, - `get one=1 two=2 three=-`, - `getunder one=- two=- three=3`, + `set big=1234567890`, + `setunder three=3 big=9876543210`, + `get one=1 big=1234567890 two=2 three=-`, + `getunder one=- two=- three=3 big=9876543210`, `commit`, `getunder one=1 two=2 three=3`, `get one=1 two=2 three=3`, - `set two=22 four=4`, - `get one=1 two=22 three=3 four=4`, + `set two=22 four=4 big=1234567890`, + `get one=1 two=22 three=3 four=4 big=1234567890`, `getunder one=1 two=2 three=3 four=-`, `commit`, `getunder one=1 two=22 three=3 four=4`, @@ -232,7 +234,7 @@ s.state.Set("config", config) case "getunder": - var config map[string]map[string]interface{} + var config map[string]map[string]*json.RawMessage s.state.Get("config", &config) for k, expected := range op.args() { obtained, ok := config[snap][k] @@ -242,7 +244,9 @@ } continue } - c.Assert(obtained, DeepEquals, expected) + var cfg interface{} + c.Assert(jsonutil.DecodeWithNumber(bytes.NewReader(*obtained), &cfg), IsNil) + c.Assert(cfg, DeepEquals, expected) } default: diff -Nru snapd-2.27.5/overlord/devicestate/devicemgr.go snapd-2.28.5/overlord/devicestate/devicemgr.go --- snapd-2.27.5/overlord/devicestate/devicemgr.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/overlord/devicestate/devicemgr.go 2017-09-13 14:47:18.000000000 +0000 @@ -26,14 +26,17 @@ "time" "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/asserts/sysdb" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/i18n/dumb" + "github.com/snapcore/snapd/overlord/assertstate" "github.com/snapcore/snapd/overlord/auth" "github.com/snapcore/snapd/overlord/hookstate" "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" "github.com/snapcore/snapd/partition" "github.com/snapcore/snapd/release" + "github.com/snapcore/snapd/snap" ) // DeviceManager is responsible for managing the device identity and device @@ -148,31 +151,84 @@ return nil } + // conditions to trigger device registration + // + // * have a model assertion with a gadget (core and + // device-like classic) in which case we need also to wait + // for the gadget to have been installed though + // TODO: consider a way to support lazy registration on classic + // even with a gadget and some preseeded snaps + // + // * classic with a model assertion with a non-default store specified + // * lazy classic case (might have a model with no gadget nor store + // or no model): we wait to have some snaps installed or be + // in the process to install some + + var seeded bool + err = m.state.Get("seeded", &seeded) + if err != nil && err != state.ErrNoState { + return err + } + if device.Brand == "" || device.Model == "" { - // cannot proceed until seeding has loaded the model - // assertion and set the brand and model, that is - // optional on classic - // TODO: later we can check if - // "seeded" was set and we still don't have a brand/model - // and use a fallback assertion - return nil + if !release.OnClassic || !seeded { + return nil + } + // we are on classic and seeded but there is no model: + // use a fallback model! + err = assertstate.Add(m.state, sysdb.GenericClassicModel()) + if err != nil && !asserts.IsUnaccceptedUpdate(err) { + return fmt.Errorf(`cannot install "generic-classic" fallback model assertion: %v`, err) + } + device.Brand = "generic" + device.Model = "generic-classic" + if err := auth.SetDevice(m.state, device); err != nil { + return err + } } if m.changeInFlight("become-operational") { return nil } - // TODO: make presence of gadget optional on classic? that is - // sensible only for devices that the store can give directly - // serials to and when we will have a general fallback - gadgetInfo, err := snapstate.GadgetInfo(m.state) - if err == state.ErrNoState { - // no gadget installed yet, cannot proceed - return nil - } - if err != nil { + var storeID, gadget string + model, err := Model(m.state) + if err != nil && err != state.ErrNoState { return err } + if err == nil { + gadget = model.Gadget() + storeID = model.Store() + } else { + return fmt.Errorf("internal error: core device brand and model are set but there is no model assertion") + } + + if gadget == "" && storeID == "" { + // classic: if we have no gadget and no non-default store + // wait to have snaps or snap installation + + n, err := snapstate.NumSnaps(m.state) + if err != nil { + return err + } + if n == 0 && !snapstate.Installing(m.state) { + return nil + } + } + + // if there's a gadget specified wait for it + var gadgetInfo *snap.Info + if gadget != "" { + var err error + gadgetInfo, err = snapstate.GadgetInfo(m.state) + if err == state.ErrNoState { + // no gadget installed yet, cannot proceed + return nil + } + if err != nil { + return err + } + } // have some backoff between full retries if m.ensureOperationalShouldBackoff(time.Now()) { @@ -188,7 +244,7 @@ tasks := []*state.Task{} var prepareDevice *state.Task - if gadgetInfo.Hooks["prepare-device"] != nil { + if gadgetInfo != nil && gadgetInfo.Hooks["prepare-device"] != nil { summary := i18n.G("Run prepare-device hook") hooksup := &hookstate.HookSetup{ Snap: gadgetInfo.Name(), @@ -367,19 +423,24 @@ return Serial(m.state) } -// DeviceSessionRequest produces a device-session-request with the given nonce, it also returns the device serial assertion. -func (m *DeviceManager) DeviceSessionRequest(nonce string) (*asserts.DeviceSessionRequest, *asserts.Serial, error) { +// DeviceSessionRequestParams produces a device-session-request with the given nonce, together with other required parameters, the device serial and model assertions. +func (m *DeviceManager) DeviceSessionRequestParams(nonce string) (*auth.DeviceSessionRequestParams, error) { m.state.Lock() defer m.state.Unlock() + model, err := Model(m.state) + if err != nil { + return nil, err + } + serial, err := Serial(m.state) if err != nil { - return nil, nil, err + return nil, err } privKey, err := m.keyPair() if err != nil { - return nil, nil, err + return nil, err } a, err := asserts.SignWithoutAuthority(asserts.DeviceSessionRequestType, map[string]interface{}{ @@ -390,9 +451,13 @@ "timestamp": time.Now().UTC().Format(time.RFC3339), }, nil, privKey) if err != nil { - return nil, nil, err + return nil, err } - return a.(*asserts.DeviceSessionRequest), serial, err + return &auth.DeviceSessionRequestParams{ + Request: a.(*asserts.DeviceSessionRequest), + Serial: serial, + Model: model, + }, err } diff -Nru snapd-2.27.5/overlord/devicestate/devicestate.go snapd-2.28.5/overlord/devicestate/devicestate.go --- snapd-2.27.5/overlord/devicestate/devicestate.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/overlord/devicestate/devicestate.go 2017-09-13 14:47:18.000000000 +0000 @@ -95,23 +95,23 @@ return false, nil } + // Either we have a serial or we try anyway if we attempted + // for a while to get a serial, this would allow us to at + // least upgrade core if that can help. + if ensureOperationalAttempts(st) >= 3 { + return true, nil + } + + // Check model exists, for sanity. We always have a model, either + // seeded or a generic one that ships with snapd. _, err := Model(st) if err == state.ErrNoState { - // no model, no need to wait for a serial - // can happen only on classic - return true, nil + return false, nil } if err != nil { return false, err } - // either we have a serial or we try anyway if we attempted - // for a while to get a serial, this would allow us to at - // least upgrade core if that can help - if ensureOperationalAttempts(st) >= 3 { - return true, nil - } - _, err = Serial(st) if err == state.ErrNoState { return false, nil diff -Nru snapd-2.27.5/overlord/devicestate/devicestate_test.go snapd-2.28.5/overlord/devicestate/devicestate_test.go --- snapd-2.27.5/overlord/devicestate/devicestate_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/overlord/devicestate/devicestate_test.go 2017-09-13 14:47:18.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 @@ -20,6 +20,7 @@ package devicestate_test import ( + "bytes" "encoding/json" "fmt" "io" @@ -31,13 +32,13 @@ "testing" "time" - "golang.org/x/net/context" . "gopkg.in/check.v1" "gopkg.in/tomb.v2" "gopkg.in/yaml.v2" "github.com/snapcore/snapd/asserts" "github.com/snapcore/snapd/asserts/assertstest" + "github.com/snapcore/snapd/asserts/sysdb" "github.com/snapcore/snapd/boot/boottest" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/httputil" @@ -49,11 +50,12 @@ "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" "github.com/snapcore/snapd/partition" - "github.com/snapcore/snapd/progress" "github.com/snapcore/snapd/release" "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/strutil" ) func TestDeviceManager(t *testing.T) { TestingT(t) } @@ -69,13 +71,16 @@ reqID string - restoreOnClassic func() + restoreOnClassic func() + restoreGenericClassicMod func() } var _ = Suite(&deviceMgrSuite{}) var testKeyLength = 1024 type fakeStore struct { + storetest.Store + state *state.State db asserts.RODatabase } @@ -97,59 +102,24 @@ return a, nil } -func (*fakeStore) SnapInfo(store.SnapSpec, *auth.UserState) (*snap.Info, error) { - panic("fakeStore.SnapInfo not expected") -} - -func (sto *fakeStore) Find(*store.Search, *auth.UserState) ([]*snap.Info, error) { - panic("fakeStore.Find not expected") -} - -func (sto *fakeStore) LookupRefresh(*store.RefreshCandidate, *auth.UserState) (*snap.Info, error) { - panic("fakeStore.LookupRefresh not expected") -} - -func (sto *fakeStore) ListRefresh([]*store.RefreshCandidate, *auth.UserState) ([]*snap.Info, error) { - panic("fakeStore.ListRefresh not expected") -} - -func (sto *fakeStore) Download(context.Context, string, string, *snap.DownloadInfo, progress.Meter, *auth.UserState) error { - panic("fakeStore.Download not expected") -} - -func (sto *fakeStore) SuggestedCurrency() string { - panic("fakeStore.SuggestedCurrency not expected") -} - -func (sto *fakeStore) Buy(*store.BuyOptions, *auth.UserState) (*store.BuyResult, error) { - panic("fakeStore.Buy not expected") -} - -func (sto *fakeStore) ReadyToBuy(*auth.UserState) error { - panic("fakeStore.ReadyToBuy not expected") -} - -func (sto *fakeStore) Sections(*auth.UserState) ([]string, error) { - panic("fakeStore.Sections not expected") -} - func (s *deviceMgrSuite) SetUpTest(c *C) { dirs.SetRootDir(c.MkDir()) os.MkdirAll(dirs.SnapRunDir, 0755) s.restoreOnClassic = release.MockOnClassic(false) - rootPrivKey, _ := assertstest.GenerateKey(testKeyLength) - storePrivKey, _ := assertstest.GenerateKey(752) - s.storeSigning = assertstest.NewStoreStack("canonical", rootPrivKey, storePrivKey) + s.storeSigning = assertstest.NewStoreStack("canonical", nil) s.state = state.New(nil) + s.restoreGenericClassicMod = sysdb.MockGenericClassicModel(s.storeSigning.GenericClassicModel) + brandPrivKey, _ := assertstest.GenerateKey(752) s.brandSigning = assertstest.NewSigningDB("my-brand", brandPrivKey) db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{ - Backstore: asserts.NewMemoryBackstore(), - Trusted: s.storeSigning.Trusted, + Backstore: asserts.NewMemoryBackstore(), + Trusted: s.storeSigning.Trusted, + OtherPredefined: s.storeSigning.Generic, }) c.Assert(err, IsNil) @@ -182,6 +152,7 @@ assertstate.ReplaceDB(s.state, nil) s.state.Unlock() dirs.SetRootDir("") + s.restoreGenericClassicMod() s.restoreOnClassic() } @@ -194,6 +165,13 @@ } } +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" +) + // seeding avoids triggering a real full seeding, it simulates having it in process instead func (s *deviceMgrSuite) seeding() { chg := s.state.NewChange("seed", "Seed system") @@ -207,15 +185,15 @@ count := 0 return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { - case "/identity/api/v1/request-id": + case requestIDURLPath, "/svc/request-id": w.WriteHeader(200) c.Check(r.Header.Get("User-Agent"), Equals, expectedUserAgent) io.WriteString(w, fmt.Sprintf(`{"request-id": "%s"}`, s.reqID)) - case "/identity/api/v1/serial": + case "/svc/serial": c.Check(r.Header.Get("X-Extra-Header"), Equals, "extra") fallthrough - case "/identity/api/v1/devices": + case serialURLPath: c.Check(r.Header.Get("User-Agent"), Equals, expectedUserAgent) mu.Lock() @@ -231,8 +209,26 @@ c.Assert(ok, Equals, true) err = asserts.SignatureCheck(serialReq, serialReq.DeviceKey()) c.Assert(err, IsNil) - c.Check(serialReq.BrandID(), Equals, "canonical") - c.Check(serialReq.Model(), Equals, "pc") + brandID := serialReq.BrandID() + model := serialReq.Model() + authID := "canonical" + keyID := "" + switch model { + case "pc", "pc2": + case "classic-alt-store": + c.Check(brandID, Equals, "canonical") + case "generic-classic": + c.Check(brandID, Equals, "generic") + authID = "generic" + keyID = s.storeSigning.GenericKey.PublicKeyID() + /*case "my-model": + c.Check(brandID, Equals, "my-brand") + authID = "generic" + keyID = s.storeSigning.GenericKey.PublicKeyID() + */ + default: + c.Fatal("unknown model") + } reqID := serialReq.RequestID() if reqID == "REQID-BADREQ" { w.Header().Set("Content-Type", "application/json") @@ -252,17 +248,23 @@ serialStr = serialReq.Serial() } serial, err := s.storeSigning.Sign(asserts.SerialType, map[string]interface{}{ - "brand-id": "canonical", - "model": "pc", + "authority-id": authID, + "brand-id": brandID, + "model": model, "serial": serialStr, "device-key": serialReq.HeaderString("device-key"), "device-key-sha3-384": serialReq.SignKeyID(), "timestamp": time.Now().Format(time.RFC3339), - }, serialReq.Body(), "") + }, serialReq.Body(), keyID) c.Assert(err, IsNil) w.Header().Set("Content-Type", asserts.MediaType) w.WriteHeader(200) - w.Write(asserts.Encode(serial)) + encoded := asserts.Encode(serial) + switch reqID { + case "REQID-SERIAL-W-BAD-MODEL": + encoded = bytes.Replace(encoded, []byte("model: pc"), []byte("model: foo"), 1) + } + w.Write(encoded) } })) } @@ -295,6 +297,15 @@ }) } +func (s *deviceMgrSuite) findBecomeOperationalChange(skipIDs ...string) *state.Change { + for _, chg := range s.state.Changes() { + if chg.Kind() == "become-operational" && !strutil.ListContains(skipIDs, chg.ID()) { + return chg + } + } + return nil +} + func (s *deviceMgrSuite) TestFullDeviceRegistrationHappy(c *C) { r1 := devicestate.MockKeyLength(testKeyLength) defer r1() @@ -303,11 +314,11 @@ mockServer := s.mockServer(c) defer mockServer.Close() - mockRequestIDURL := mockServer.URL + "/identity/api/v1/request-id" + mockRequestIDURL := mockServer.URL + requestIDURLPath r2 := devicestate.MockRequestIDURL(mockRequestIDURL) defer r2() - mockSerialRequestURL := mockServer.URL + "/identity/api/v1/devices" + mockSerialRequestURL := mockServer.URL + serialURLPath r3 := devicestate.MockSerialRequestURL(mockSerialRequestURL) defer r3() @@ -315,15 +326,97 @@ s.state.Lock() defer s.state.Unlock() + s.makeModelAssertionInState(c, "canonical", "pc", map[string]string{ + "architecture": "amd64", + "kernel": "pc-kernel", + "gadget": "pc", + }) + + auth.SetDevice(s.state, &auth.DeviceState{ + Brand: "canonical", + Model: "pc", + }) + + // avoid full seeding + s.seeding() + + // not started without gadget + s.state.Unlock() + s.mgr.Ensure() + s.state.Lock() + + becomeOperational := s.findBecomeOperationalChange() + c.Check(becomeOperational, IsNil) + s.setupGadget(c, ` -name: gadget +name: pc type: gadget version: gadget `, "") + // runs the whole device registration process + s.state.Unlock() + s.settle() + s.state.Lock() + + becomeOperational = s.findBecomeOperationalChange() + c.Assert(becomeOperational, NotNil) + + c.Check(becomeOperational.Status().Ready(), Equals, true) + c.Check(becomeOperational.Err(), IsNil) + + device, err := auth.Device(s.state) + c.Assert(err, IsNil) + c.Check(device.Brand, Equals, "canonical") + c.Check(device.Model, Equals, "pc") + c.Check(device.Serial, Equals, "9999") + + a, err := s.db.Find(asserts.SerialType, map[string]string{ + "brand-id": "canonical", + "model": "pc", + "serial": "9999", + }) + c.Assert(err, IsNil) + serial := a.(*asserts.Serial) + + privKey, err := s.mgr.KeypairManager().Get(serial.DeviceKey().ID()) + c.Assert(err, IsNil) + c.Check(privKey, NotNil) + + c.Check(device.KeyID, Equals, privKey.PublicKey().ID()) +} + +func (s *deviceMgrSuite) TestFullDeviceRegistrationHappyClassicNoGadget(c *C) { + restore := release.MockOnClassic(true) + defer restore() + + r1 := devicestate.MockKeyLength(testKeyLength) + defer r1() + + s.reqID = "REQID-1" + mockServer := s.mockServer(c) + defer mockServer.Close() + + mockRequestIDURL := mockServer.URL + requestIDURLPath + r2 := devicestate.MockRequestIDURL(mockRequestIDURL) + defer r2() + + mockSerialRequestURL := mockServer.URL + serialURLPath + r3 := devicestate.MockSerialRequestURL(mockSerialRequestURL) + defer r3() + + // setup state as will be done by first-boot + s.state.Lock() + defer s.state.Unlock() + + s.makeModelAssertionInState(c, "canonical", "classic-alt-store", map[string]string{ + "classic": "true", + "store": "alt-store", + }) + auth.SetDevice(s.state, &auth.DeviceState{ Brand: "canonical", - Model: "pc", + Model: "classic-alt-store", }) // avoid full seeding @@ -334,13 +427,7 @@ s.settle() s.state.Lock() - var becomeOperational *state.Change - for _, chg := range s.state.Changes() { - if chg.Kind() == "become-operational" { - becomeOperational = chg - break - } - } + becomeOperational := s.findBecomeOperationalChange() c.Assert(becomeOperational, NotNil) c.Check(becomeOperational.Status().Ready(), Equals, true) @@ -349,12 +436,169 @@ device, err := auth.Device(s.state) c.Assert(err, IsNil) c.Check(device.Brand, Equals, "canonical") - c.Check(device.Model, Equals, "pc") + c.Check(device.Model, Equals, "classic-alt-store") c.Check(device.Serial, Equals, "9999") a, err := s.db.Find(asserts.SerialType, map[string]string{ "brand-id": "canonical", - "model": "pc", + "model": "classic-alt-store", + "serial": "9999", + }) + c.Assert(err, IsNil) + serial := a.(*asserts.Serial) + + privKey, err := s.mgr.KeypairManager().Get(serial.DeviceKey().ID()) + c.Assert(err, IsNil) + c.Check(privKey, NotNil) + + c.Check(device.KeyID, Equals, privKey.PublicKey().ID()) +} + +func (s *deviceMgrSuite) TestFullDeviceRegistrationHappyClassicFallback(c *C) { + restore := release.MockOnClassic(true) + defer restore() + + r1 := devicestate.MockKeyLength(testKeyLength) + defer r1() + + s.reqID = "REQID-1" + mockServer := s.mockServer(c) + defer mockServer.Close() + + mockRequestIDURL := mockServer.URL + requestIDURLPath + r2 := devicestate.MockRequestIDURL(mockRequestIDURL) + defer r2() + + mockSerialRequestURL := mockServer.URL + serialURLPath + r3 := devicestate.MockSerialRequestURL(mockSerialRequestURL) + defer r3() + + // setup state as will be done by first-boot + s.state.Lock() + defer s.state.Unlock() + + // in this case is just marked seeded without snaps + s.state.Set("seeded", true) + + // not started without some installation happening or happened + s.state.Unlock() + s.mgr.Ensure() + s.state.Lock() + + becomeOperational := s.findBecomeOperationalChange() + c.Check(becomeOperational, IsNil) + + // have a in-progress installation + inst := s.state.NewChange("install", "...") + task := s.state.NewTask("mount-snap", "...") + inst.AddTask(task) + + // runs the whole device registration process + s.state.Unlock() + s.settle() + s.state.Lock() + + becomeOperational = s.findBecomeOperationalChange() + c.Assert(becomeOperational, NotNil) + + c.Check(becomeOperational.Status().Ready(), Equals, true) + c.Check(becomeOperational.Err(), IsNil) + + device, err := auth.Device(s.state) + c.Assert(err, IsNil) + c.Check(device.Brand, Equals, "generic") + c.Check(device.Model, Equals, "generic-classic") + c.Check(device.Serial, Equals, "9999") + + // model was installed + _, err = s.db.Find(asserts.ModelType, map[string]string{ + "series": "16", + "brand-id": "generic", + "model": "generic-classic", + "classic": "true", + }) + c.Assert(err, IsNil) + + a, err := s.db.Find(asserts.SerialType, map[string]string{ + "brand-id": "generic", + "model": "generic-classic", + "serial": "9999", + }) + c.Assert(err, IsNil) + serial := a.(*asserts.Serial) + + privKey, err := s.mgr.KeypairManager().Get(serial.DeviceKey().ID()) + c.Assert(err, IsNil) + c.Check(privKey, NotNil) + + c.Check(device.KeyID, Equals, privKey.PublicKey().ID()) + + // auto-refreshes are possible + ok, err := devicestate.CanAutoRefresh(s.state) + c.Assert(err, IsNil) + c.Check(ok, Equals, true) +} + +func (s *deviceMgrSuite) TestFullDeviceRegistrationAltBrandHappy(c *C) { + c.Skip("not yet supported") + r1 := devicestate.MockKeyLength(testKeyLength) + defer r1() + + s.reqID = "REQID-1" + mockServer := s.mockServer(c) + defer mockServer.Close() + + mockRequestIDURL := mockServer.URL + requestIDURLPath + r2 := devicestate.MockRequestIDURL(mockRequestIDURL) + defer r2() + + mockSerialRequestURL := mockServer.URL + serialURLPath + r3 := devicestate.MockSerialRequestURL(mockSerialRequestURL) + defer r3() + + // setup state as will be done by first-boot + s.state.Lock() + defer s.state.Unlock() + + s.makeModelAssertionInState(c, "my-brand", "my-model", map[string]string{ + "classic": "true", + "store": "alt-store", + }) + + s.setupGadget(c, ` +name: gadget +type: gadget +version: gadget +`, "") + + auth.SetDevice(s.state, &auth.DeviceState{ + Brand: "my-brand", + Model: "my-model", + }) + + // avoid full seeding + s.seeding() + + // runs the whole device registration process + s.state.Unlock() + s.settle() + s.state.Lock() + + becomeOperational := s.findBecomeOperationalChange() + c.Assert(becomeOperational, NotNil) + + c.Check(becomeOperational.Status().Ready(), Equals, true) + c.Check(becomeOperational.Err(), IsNil) + + device, err := auth.Device(s.state) + c.Assert(err, IsNil) + c.Check(device.Brand, Equals, "my-brand") + c.Check(device.Model, Equals, "my-model") + c.Check(device.Serial, Equals, "9999") + + a, err := s.db.Find(asserts.SerialType, map[string]string{ + "brand-id": "my-brand", + "model": "my-model", "serial": "9999", }) c.Assert(err, IsNil) @@ -374,11 +618,11 @@ mockServer := s.mockServer(c) defer mockServer.Close() - mockRequestIDURL := mockServer.URL + "/identity/api/v1/request-id" + mockRequestIDURL := mockServer.URL + requestIDURLPath restore := devicestate.MockRequestIDURL(mockRequestIDURL) defer restore() - mockSerialRequestURL := mockServer.URL + "/identity/api/v1/devices" + mockSerialRequestURL := mockServer.URL + serialURLPath restore = devicestate.MockSerialRequestURL(mockSerialRequestURL) defer restore() @@ -443,11 +687,11 @@ mockServer := s.mockServer(c) defer mockServer.Close() - mockRequestIDURL := mockServer.URL + "/identity/api/v1/request-id" + mockRequestIDURL := mockServer.URL + requestIDURLPath restore := devicestate.MockRequestIDURL(mockRequestIDURL) defer restore() - mockSerialRequestURL := mockServer.URL + "/identity/api/v1/devices" + mockSerialRequestURL := mockServer.URL + serialURLPath restore = devicestate.MockSerialRequestURL(mockSerialRequestURL) defer restore() @@ -513,11 +757,11 @@ mockServer := s.mockServer(c) defer mockServer.Close() - mockRequestIDURL := mockServer.URL + "/identity/api/v1/request-id" + mockRequestIDURL := mockServer.URL + requestIDURLPath r2 := devicestate.MockRequestIDURL(mockRequestIDURL) defer r2() - mockSerialRequestURL := mockServer.URL + "/identity/api/v1/devices" + mockSerialRequestURL := mockServer.URL + serialURLPath r3 := devicestate.MockSerialRequestURL(mockSerialRequestURL) defer r3() @@ -529,17 +773,23 @@ s.state.Lock() defer s.state.Unlock() - s.setupGadget(c, ` -name: gadget -type: gadget -version: gadget -`, "") + s.makeModelAssertionInState(c, "canonical", "pc", map[string]string{ + "architecture": "amd64", + "kernel": "pc-kernel", + "gadget": "pc", + }) auth.SetDevice(s.state, &auth.DeviceState{ Brand: "canonical", Model: "pc", }) + s.setupGadget(c, ` +name: pc +type: gadget +version: gadget +`, "") + // avoid full seeding s.seeding() @@ -548,13 +798,7 @@ s.settle() s.state.Lock() - var becomeOperational *state.Change - for _, chg := range s.state.Changes() { - if chg.Kind() == "become-operational" { - becomeOperational = chg - break - } - } + becomeOperational := s.findBecomeOperationalChange() c.Assert(becomeOperational, NotNil) c.Check(becomeOperational.Status().Ready(), Equals, true) @@ -593,7 +837,7 @@ c.Assert(ctx.HookName(), Equals, "prepare-device") // snapctl set the registration params - _, _, err := ctlcmd.Run(ctx, []string{"set", fmt.Sprintf("device-service.url=%q", mockServer.URL+"/identity/api/v1/")}) + _, _, err := ctlcmd.Run(ctx, []string{"set", fmt.Sprintf("device-service.url=%q", mockServer.URL+"/svc/")}) c.Assert(err, IsNil) h, err := json.Marshal(map[string]string{ @@ -622,6 +866,12 @@ s.state.Lock() defer s.state.Unlock() + s.makeModelAssertionInState(c, "canonical", "pc2", map[string]string{ + "architecture": "amd64", + "kernel": "pc-kernel", + "gadget": "gadget", + }) + s.setupGadget(c, ` name: gadget type: gadget @@ -629,9 +879,10 @@ hooks: prepare-device: `, "") + auth.SetDevice(s.state, &auth.DeviceState{ Brand: "canonical", - Model: "pc", + Model: "pc2", }) // avoid full seeding @@ -642,13 +893,7 @@ s.settle() s.state.Lock() - var becomeOperational *state.Change - for _, chg := range s.state.Changes() { - if chg.Kind() == "become-operational" { - becomeOperational = chg - break - } - } + becomeOperational := s.findBecomeOperationalChange() c.Assert(becomeOperational, NotNil) c.Check(becomeOperational.Status().Ready(), Equals, true) @@ -657,12 +902,12 @@ device, err := auth.Device(s.state) c.Assert(err, IsNil) c.Check(device.Brand, Equals, "canonical") - c.Check(device.Model, Equals, "pc") + c.Check(device.Model, Equals, "pc2") c.Check(device.Serial, Equals, "Y9999") a, err := s.db.Find(asserts.SerialType, map[string]string{ "brand-id": "canonical", - "model": "pc", + "model": "pc2", "serial": "Y9999", }) c.Assert(err, IsNil) @@ -691,11 +936,11 @@ mockServer := s.mockServer(c) defer mockServer.Close() - mockRequestIDURL := mockServer.URL + "/identity/api/v1/request-id" + mockRequestIDURL := mockServer.URL + requestIDURLPath r2 := devicestate.MockRequestIDURL(mockRequestIDURL) defer r2() - mockSerialRequestURL := mockServer.URL + "/identity/api/v1/devices" + mockSerialRequestURL := mockServer.URL + serialURLPath r3 := devicestate.MockSerialRequestURL(mockSerialRequestURL) defer r3() @@ -706,17 +951,23 @@ // sanity c.Check(devicestate.EnsureOperationalAttempts(s.state), Equals, 0) - s.setupGadget(c, ` -name: gadget -type: gadget -version: gadget -`, "") + s.makeModelAssertionInState(c, "canonical", "pc", map[string]string{ + "architecture": "amd64", + "kernel": "pc-kernel", + "gadget": "pc", + }) auth.SetDevice(s.state, &auth.DeviceState{ Brand: "canonical", Model: "pc", }) + s.setupGadget(c, ` +name: pc +type: gadget +version: gadget +`, "") + // avoid full seeding s.seeding() @@ -725,13 +976,7 @@ s.settle() s.state.Lock() - var becomeOperational *state.Change - for _, chg := range s.state.Changes() { - if chg.Kind() == "become-operational" { - becomeOperational = chg - break - } - } + becomeOperational := s.findBecomeOperationalChange() c.Assert(becomeOperational, NotNil) firstTryID := becomeOperational.ID() @@ -754,13 +999,7 @@ s.settle() s.state.Lock() - becomeOperational = nil - for _, chg := range s.state.Changes() { - if chg.Kind() == "become-operational" && chg.ID() != firstTryID { - becomeOperational = chg - break - } - } + becomeOperational = s.findBecomeOperationalChange(firstTryID) c.Assert(becomeOperational, NotNil) c.Check(becomeOperational.Status().Ready(), Equals, true) @@ -794,6 +1033,61 @@ } } +func (s *deviceMgrSuite) TestFullDeviceRegistrationMismatchedSerial(c *C) { + r1 := devicestate.MockKeyLength(testKeyLength) + defer r1() + + s.reqID = "REQID-SERIAL-W-BAD-MODEL" + mockServer := s.mockServer(c) + defer mockServer.Close() + + mockRequestIDURL := mockServer.URL + requestIDURLPath + r2 := devicestate.MockRequestIDURL(mockRequestIDURL) + defer r2() + + mockSerialRequestURL := mockServer.URL + serialURLPath + r3 := devicestate.MockSerialRequestURL(mockSerialRequestURL) + defer r3() + + // setup state as will be done by first-boot + s.state.Lock() + defer s.state.Unlock() + + // sanity + c.Check(devicestate.EnsureOperationalAttempts(s.state), Equals, 0) + + s.setupGadget(c, ` +name: gadget +type: gadget +version: gadget +`, "") + + s.makeModelAssertionInState(c, "canonical", "pc", map[string]string{ + "architecture": "amd64", + "kernel": "pc-kernel", + "gadget": "pc", + }) + + auth.SetDevice(s.state, &auth.DeviceState{ + Brand: "canonical", + Model: "pc", + }) + + // avoid full seeding + s.seeding() + + // try the whole device registration process + s.state.Unlock() + s.settle() + s.state.Lock() + + becomeOperational := s.findBecomeOperationalChange() + c.Assert(becomeOperational, NotNil) + + c.Check(becomeOperational.Status().Ready(), Equals, true) + c.Check(becomeOperational.Err(), ErrorMatches, `(?s).*obtained serial assertion does not match provided device identity information.*`) +} + func (s *deviceMgrSuite) TestDeviceAssertionsModelAndSerial(c *C) { // nothing in the state s.state.Lock() @@ -882,11 +1176,27 @@ c.Check(ser.Serial(), Equals, "8989") } -func (s *deviceMgrSuite) TestDeviceAssertionsDeviceSessionRequest(c *C) { +func (s *deviceMgrSuite) TestDeviceAssertionsDeviceSessionRequestParams(c *C) { // nothing there - _, _, err := s.mgr.DeviceSessionRequest("NONCE-1") + _, err := s.mgr.DeviceSessionRequestParams("NONCE-1") c.Check(err, Equals, state.ErrNoState) + // have a model assertion + modela, err := s.storeSigning.Sign(asserts.ModelType, map[string]interface{}{ + "series": "16", + "brand-id": "canonical", + "model": "pc", + "gadget": "pc", + "kernel": "kernel", + "architecture": "amd64", + "timestamp": time.Now().Format(time.RFC3339), + }, nil, "") + c.Assert(err, IsNil) + s.state.Lock() + err = assertstate.Add(s.state, modela) + s.state.Unlock() + c.Assert(err, IsNil) + // setup state as done by device initialisation s.state.Lock() devKey, _ := assertstest.GenerateKey(testKeyLength) @@ -913,11 +1223,15 @@ s.mgr.KeypairManager().Put(devKey) s.state.Unlock() - sessReq, serial, err := s.mgr.DeviceSessionRequest("NONCE-1") + params, err := s.mgr.DeviceSessionRequestParams("NONCE-1") c.Assert(err, IsNil) - c.Check(serial.Serial(), Equals, "8989") + c.Check(params.Model.Model(), Equals, "pc") + + c.Check(params.Serial.Model(), Equals, "pc") + c.Check(params.Serial.Serial(), Equals, "8989") + sessReq := params.Request // correctly signed with device key err = asserts.SignatureCheck(sessReq, devKey.PublicKey()) c.Check(err, IsNil) @@ -1416,7 +1730,15 @@ for k, v := range extras { headers[k] = v } - modelAs, err := s.storeSigning.Sign(asserts.ModelType, headers, nil, "") + var signer assertstest.SignerDB + switch brandID { + case "canonical": + signer = s.storeSigning.RootSigning + case "my-brand": + s.setupBrands(c) + signer = s.brandSigning + } + modelAs, err := signer.Sign(asserts.ModelType, headers, nil, "") c.Assert(err, IsNil) err = assertstate.Add(s.state, modelAs) c.Assert(err, IsNil) @@ -1532,7 +1854,7 @@ // seeded, no model -> auto-refresh s.state.Set("seeded", true) - c.Check(canAutoRefresh(), Equals, true) + c.Check(canAutoRefresh(), Equals, false) // seeded, model, no serial -> no auto-refresh auth.SetDevice(s.state, &auth.DeviceState{ diff -Nru snapd-2.27.5/overlord/devicestate/firstboot_test.go snapd-2.28.5/overlord/devicestate/firstboot_test.go --- snapd-2.27.5/overlord/devicestate/firstboot_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/overlord/devicestate/firstboot_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -97,9 +97,7 @@ err = ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), nil, 0644) c.Assert(err, IsNil) - rootPrivKey, _ := assertstest.GenerateKey(1024) - storePrivKey, _ := assertstest.GenerateKey(752) - s.storeSigning = assertstest.NewStoreStack("can0nical", rootPrivKey, storePrivKey) + s.storeSigning = assertstest.NewStoreStack("can0nical", nil) s.restore = sysdb.InjectTrusted(s.storeSigning.Trusted) s.brandPrivKey, _ = assertstest.GenerateKey(752) diff -Nru snapd-2.27.5/overlord/devicestate/handlers.go snapd-2.28.5/overlord/devicestate/handlers.go --- snapd-2.27.5/overlord/devicestate/handlers.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/overlord/devicestate/handlers.go 2017-09-13 14:47:18.000000000 +0000 @@ -228,6 +228,8 @@ return nil, retryBadStatus(t, "cannot deliver device serial request", resp) } + // TODO: support a stream of assertions instead of just the serial + // decode body with serial assertion dec := asserts.NewDecoder(resp.Body) got, err := dec.Decode() @@ -318,69 +320,83 @@ } func getSerialRequestConfig(t *state.Task) (*serialRequestConfig, error) { - gadgetInfo, err := snapstate.GadgetInfo(t.State()) - if err != nil { - return nil, fmt.Errorf("cannot find gadget snap and its name: %v", err) - } - gadgetName := gadgetInfo.Name() - - tr := config.NewTransaction(t.State()) var svcURL string - err = tr.GetMaybe(gadgetName, "device-service.url", &svcURL) - if err != nil { + + // gadget is optional on classic + model, err := Model(t.State()) + if err != nil && err != state.ErrNoState { return nil, err } - if svcURL != "" { - baseURL, err := url.Parse(svcURL) + var gadgetName string + var tr *config.Transaction + if model != nil && model.Gadget() != "" { + // model specifies a gadget + gadgetInfo, err := snapstate.GadgetInfo(t.State()) if err != nil { - return nil, fmt.Errorf("cannot parse device registration base URL %q: %v", svcURL, err) + return nil, fmt.Errorf("cannot find gadget snap and its name: %v", err) } + gadgetName = gadgetInfo.Name() - var headers map[string]string - err = tr.GetMaybe(gadgetName, "device-service.headers", &headers) + tr = config.NewTransaction(t.State()) + err = tr.GetMaybe(gadgetName, "device-service.url", &svcURL) if err != nil { return nil, err } + } - cfg := serialRequestConfig{ - headers: headers, - } + if svcURL == "" { + // no gadget or no device-service.url, use the fallback + // service in the store + return &serialRequestConfig{ + requestIDURL: requestIDURL, + serialRequestURL: serialRequestURL, + }, nil + } - reqIDURL, err := baseURL.Parse("request-id") - if err != nil { - return nil, fmt.Errorf("cannot build /request-id URL from %v: %v", baseURL, err) - } - cfg.requestIDURL = reqIDURL.String() + baseURL, err := url.Parse(svcURL) + if err != nil { + return nil, fmt.Errorf("cannot parse device registration base URL %q: %v", svcURL, err) + } - var bodyStr string - err = tr.GetMaybe(gadgetName, "registration.body", &bodyStr) - if err != nil { - return nil, err - } + var headers map[string]string + err = tr.GetMaybe(gadgetName, "device-service.headers", &headers) + if err != nil { + return nil, err + } - cfg.body = []byte(bodyStr) + cfg := serialRequestConfig{ + headers: headers, + } - serialURL, err := baseURL.Parse("serial") - if err != nil { - return nil, fmt.Errorf("cannot build /serial URL from %v: %v", baseURL, err) - } - cfg.serialRequestURL = serialURL.String() + reqIDURL, err := baseURL.Parse("request-id") + if err != nil { + return nil, fmt.Errorf("cannot build /request-id URL from %v: %v", baseURL, err) + } + cfg.requestIDURL = reqIDURL.String() - var proposedSerial string - err = tr.GetMaybe(gadgetName, "registration.proposed-serial", &proposedSerial) - if err != nil { - return nil, err - } - cfg.proposedSerial = proposedSerial + var bodyStr string + err = tr.GetMaybe(gadgetName, "registration.body", &bodyStr) + if err != nil { + return nil, err + } + + cfg.body = []byte(bodyStr) + + serialURL, err := baseURL.Parse("serial") + if err != nil { + return nil, fmt.Errorf("cannot build /serial URL from %v: %v", baseURL, err) + } + cfg.serialRequestURL = serialURL.String() - return &cfg, nil + var proposedSerial string + err = tr.GetMaybe(gadgetName, "registration.proposed-serial", &proposedSerial) + if err != nil { + return nil, err } + cfg.proposedSerial = proposedSerial - return &serialRequestConfig{ - requestIDURL: requestIDURL, - serialRequestURL: serialRequestURL, - }, nil + return &cfg, nil } func (m *DeviceManager) doRequestSerial(t *state.Task, _ *tomb.Tomb) error { @@ -440,18 +456,10 @@ return err } - sto := snapstate.Store(st) - // try to fetch the signing key of the serial - st.Unlock() - a, errAcctKey := sto.Assertion(asserts.AccountKeyType, []string{serial.SignKeyID()}, nil) - st.Lock() - if errAcctKey == nil { - err := assertstate.Add(st, a) - if err != nil { - if !asserts.IsUnaccceptedUpdate(err) { - return err - } - } + // try to fetch the signing key chain of the serial + errAcctKey, err := fetchKeys(st, serial.SignKeyID()) + if err != nil { + return err } // add the serial assertion to the system assertion db @@ -480,3 +488,32 @@ } var repeatRequestSerial string // for tests + +func fetchKeys(st *state.State, keyID string) (errAcctKey error, err error) { + sto := snapstate.Store(st) + db := assertstate.DB(st) + for { + _, err := db.FindPredefined(asserts.AccountKeyType, map[string]string{ + "public-key-sha3-384": keyID, + }) + if err == nil { + return nil, nil + } + if err != asserts.ErrNotFound { + return nil, err + } + st.Unlock() + a, errAcctKey := sto.Assertion(asserts.AccountKeyType, []string{keyID}, nil) + st.Lock() + if errAcctKey != nil { + return errAcctKey, nil + } + err = assertstate.Add(st, a) + if err != nil { + if !asserts.IsUnaccceptedUpdate(err) { + return nil, err + } + } + keyID = a.SignKeyID() + } +} diff -Nru snapd-2.27.5/overlord/hookstate/context.go snapd-2.28.5/overlord/hookstate/context.go --- snapd-2.27.5/overlord/hookstate/context.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/overlord/hookstate/context.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,12 +20,14 @@ package hookstate import ( + "bytes" "encoding/json" "fmt" "sync" "sync/atomic" "time" + "github.com/snapcore/snapd/jsonutil" "github.com/snapcore/snapd/overlord/state" "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/strutil" @@ -180,7 +182,7 @@ return state.ErrNoState } - err := json.Unmarshal([]byte(*raw), &value) + err := jsonutil.DecodeWithNumber(bytes.NewReader(*raw), &value) if err != nil { return fmt.Errorf("cannot unmarshal context value for %q: %s", key, err) } diff -Nru snapd-2.27.5/overlord/hookstate/context_test.go snapd-2.28.5/overlord/hookstate/context_test.go --- snapd-2.27.5/overlord/hookstate/context_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/overlord/hookstate/context_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -22,6 +22,8 @@ import ( . "gopkg.in/check.v1" + "encoding/json" + "github.com/snapcore/snapd/overlord/state" "github.com/snapcore/snapd/snap" ) @@ -67,6 +69,17 @@ c.Check(s.context.Get("baz", &output), NotNil) } +func (s *contextSuite) TestSetAndGetNumber(c *C) { + s.context.Lock() + defer s.context.Unlock() + + s.context.Set("num", 1234567890) + + var output interface{} + c.Check(s.context.Get("num", &output), IsNil) + c.Assert(output, Equals, json.Number("1234567890")) +} + func (s *contextSuite) TestSetPersistence(c *C) { s.context.Lock() s.context.Set("foo", "bar") diff -Nru snapd-2.27.5/overlord/hookstate/ctlcmd/set.go snapd-2.28.5/overlord/hookstate/ctlcmd/set.go --- snapd-2.27.5/overlord/hookstate/ctlcmd/set.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/overlord/hookstate/ctlcmd/set.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,11 +20,11 @@ package ctlcmd import ( - "encoding/json" "fmt" "strings" "github.com/snapcore/snapd/i18n/dumb" + "github.com/snapcore/snapd/jsonutil" "github.com/snapcore/snapd/overlord/configstate" "github.com/snapcore/snapd/overlord/hookstate" ) @@ -102,8 +102,7 @@ } key := parts[0] var value interface{} - err := json.Unmarshal([]byte(parts[1]), &value) - if err != nil { + if err := jsonutil.DecodeWithNumber(strings.NewReader(parts[1]), &value); err != nil { // Not valid JSON-- just save the string as-is. value = parts[1] } @@ -154,8 +153,7 @@ } var value interface{} - err := json.Unmarshal([]byte(parts[1]), &value) - if err != nil { + if err := jsonutil.DecodeWithNumber(strings.NewReader(parts[1]), &value); err != nil { // Not valid JSON, save the string as-is value = parts[1] } diff -Nru snapd-2.27.5/overlord/hookstate/ctlcmd/set_test.go snapd-2.28.5/overlord/hookstate/ctlcmd/set_test.go --- snapd-2.27.5/overlord/hookstate/ctlcmd/set_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/overlord/hookstate/ctlcmd/set_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,6 +20,7 @@ package ctlcmd_test import ( + "encoding/json" "reflect" "github.com/snapcore/snapd/interfaces" @@ -116,6 +117,27 @@ c.Check(value, Equals, "192.168.0.1:5555") } +func (s *setSuite) TestSetNumbers(c *C) { + stdout, stderr, err := ctlcmd.Run(s.mockContext, []string{"set", "foo=1234567890", "bar=123456.7890"}) + c.Check(err, IsNil) + c.Check(string(stdout), Equals, "") + c.Check(string(stderr), Equals, "") + + // Notify the context that we're done. This should save the config. + s.mockContext.Lock() + defer s.mockContext.Unlock() + c.Check(s.mockContext.Done(), IsNil) + + // Verify that the global config has been updated. + var value interface{} + tr := config.NewTransaction(s.mockContext.State()) + c.Check(tr.Get("test-snap", "foo", &value), IsNil) + c.Check(value, Equals, json.Number("1234567890")) + + c.Check(tr.Get("test-snap", "bar", &value), IsNil) + c.Check(value, Equals, json.Number("123456.7890")) +} + func (s *setSuite) TestCommandSavesDeltasOnly(c *C) { // Setup an initial configuration s.mockContext.State().Lock() diff -Nru snapd-2.27.5/overlord/hookstate/hookmgr.go snapd-2.28.5/overlord/hookstate/hookmgr.go --- snapd-2.27.5/overlord/hookstate/hookmgr.go 2017-08-29 05:30:10.000000000 +0000 +++ snapd-2.28.5/overlord/hookstate/hookmgr.go 2017-10-04 14:43:22.000000000 +0000 @@ -227,6 +227,9 @@ handlers := m.repository.generateHandlers(context) handlersCount := len(handlers) if handlersCount == 0 { + // Do not report error if hook handler doesn't exist as long as the hook is optional. + // This is to avoid issues when downgrading to an old core snap that doesn't know about + // particular hook type and a task for it exists (e.g. "post-refresh" hook). if hooksup.Optional { return nil } diff -Nru snapd-2.27.5/overlord/hookstate/hooks.go snapd-2.28.5/overlord/hookstate/hooks.go --- snapd-2.27.5/overlord/hookstate/hooks.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/overlord/hookstate/hooks.go 2017-10-04 14:43:22.000000000 +0000 @@ -28,6 +28,7 @@ func init() { snapstate.SetupInstallHook = SetupInstallHook + snapstate.SetupPostRefreshHook = SetupPostRefreshHook snapstate.SetupRemoveHook = SetupRemoveHook } @@ -44,6 +45,19 @@ return task } +func SetupPostRefreshHook(st *state.State, snapName string) *state.Task { + hooksup := &HookSetup{ + Snap: snapName, + Hook: "post-refresh", + Optional: true, + } + + summary := fmt.Sprintf(i18n.G("Run post-refresh hook of %q snap if present"), hooksup.Snap) + task := HookTask(st, summary, hooksup, nil) + + return task +} + type snapHookHandler struct { } @@ -79,5 +93,6 @@ } hookMgr.Register(regexp.MustCompile("^install$"), handlerGenerator) + hookMgr.Register(regexp.MustCompile("^post-refresh$"), handlerGenerator) hookMgr.Register(regexp.MustCompile("^remove$"), handlerGenerator) } diff -Nru snapd-2.27.5/overlord/ifacestate/helpers.go snapd-2.28.5/overlord/ifacestate/helpers.go --- snapd-2.27.5/overlord/ifacestate/helpers.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/overlord/ifacestate/helpers.go 2017-10-13 18:02:58.000000000 +0000 @@ -141,7 +141,7 @@ // The issue this is attempting to fix is only // affecting seccomp/apparmor so limit the work just to // this backend. - shouldRefresh := (backend.Name() == interfaces.SecuritySecComp || backend.Name() == interfaces.SecurityAppArmor) + shouldRefresh := (backend.Name() == interfaces.SecuritySecComp || backend.Name() == interfaces.SecurityAppArmor || backend.Name() == interfaces.SecurityUDev) if !shouldRefresh { continue } diff -Nru snapd-2.27.5/overlord/ifacestate/ifacestate_test.go snapd-2.28.5/overlord/ifacestate/ifacestate_test.go --- snapd-2.27.5/overlord/ifacestate/ifacestate_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/overlord/ifacestate/ifacestate_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -44,11 +44,6 @@ func TestInterfaceManager(t *testing.T) { TestingT(t) } -var ( - rootKey, _ = assertstest.GenerateKey(752) - storeKey, _ = assertstest.GenerateKey(752) -) - type interfaceManagerSuite struct { state *state.State db *asserts.Database @@ -65,7 +60,7 @@ var _ = Suite(&interfaceManagerSuite{}) func (s *interfaceManagerSuite) SetUpTest(c *C) { - s.storeSigning = assertstest.NewStoreStack("canonical", rootKey, storeKey) + s.storeSigning = assertstest.NewStoreStack("canonical", nil) s.mockSnapCmd = testutil.MockCommand(c, "snap", "") diff -Nru snapd-2.27.5/overlord/ifacestate/implicit.go snapd-2.28.5/overlord/ifacestate/implicit.go --- snapd-2.27.5/overlord/ifacestate/implicit.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/overlord/ifacestate/implicit.go 2017-09-13 14:47:18.000000000 +0000 @@ -38,8 +38,8 @@ } // Ask each interface if it wants to be implcitly added. for _, iface := range builtin.Interfaces() { - md := interfaces.IfaceMetaData(iface) - if (release.OnClassic && md.ImplicitOnClassic) || (!release.OnClassic && md.ImplicitOnCore) { + si := interfaces.StaticInfoOf(iface) + if (release.OnClassic && si.ImplicitOnClassic) || (!release.OnClassic && si.ImplicitOnCore) { ifaceName := iface.Name() if _, ok := snapInfo.Slots[ifaceName]; !ok { snapInfo.Slots[ifaceName] = makeImplicitSlot(snapInfo, ifaceName) diff -Nru snapd-2.27.5/overlord/managers_test.go snapd-2.28.5/overlord/managers_test.go --- snapd-2.27.5/overlord/managers_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/overlord/managers_test.go 2017-09-13 14:47:18.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,7 +22,6 @@ // test the various managers and their operation together through overlord import ( - "bytes" "encoding/json" "fmt" "io" @@ -93,9 +92,6 @@ ) var ( - rootPrivKey, _ = assertstest.GenerateKey(1024) - storePrivKey, _ = assertstest.GenerateKey(752) - brandPrivKey, _ = assertstest.GenerateKey(752) develPrivKey, _ = assertstest.GenerateKey(752) @@ -135,7 +131,7 @@ ms.snapDiscardNs = testutil.MockCommand(c, "snap-discard-ns", "") dirs.DistroLibExecDir = ms.snapDiscardNs.BinDir() - ms.storeSigning = assertstest.NewStoreStack("can0nical", rootPrivKey, storePrivKey) + ms.storeSigning = assertstest.NewStoreStack("can0nical", nil) ms.restoreTrusted = sysdb.InjectTrusted(ms.storeSigning.Trusted) ms.devAcct = assertstest.NewAccount(ms.storeSigning, "devdevdev", map[string]interface{}{ @@ -149,7 +145,14 @@ ms.o = o st := ms.o.State() st.Lock() + // seeded st.Set("seeded", true) + // registered + auth.SetDevice(st, &auth.DeviceState{ + Brand: "generic", + Model: "generic-classic", + Serial: "serialserial", + }) st.Unlock() ms.serveIDtoName = make(map[string]string) @@ -1699,7 +1702,7 @@ r := overlord.MockStoreNew(captureAuthContext) defer r() - s.storeSigning = assertstest.NewStoreStack("can0nical", rootPrivKey, storePrivKey) + s.storeSigning = assertstest.NewStoreStack("can0nical", nil) s.restoreTrusted = sysdb.InjectTrusted(s.storeSigning.Trusted) s.brandSigning = assertstest.NewSigningDB("my-brand", brandPrivKey) @@ -1788,17 +1791,19 @@ c.Check(storeID, Equals, "my-brand-store-id") } -func (s *authContextSetupSuite) TestDeviceSessionRequest(c *C) { +func (s *authContextSetupSuite) TestDeviceSessionRequestParams(c *C) { st := s.o.State() st.Lock() defer st.Unlock() st.Unlock() - _, _, err := s.ac.DeviceSessionRequest("NONCE") + _, err := s.ac.DeviceSessionRequestParams("NONCE") st.Lock() c.Check(err, Equals, auth.ErrNoSerial) - // setup serial and key in system state + // setup model, serial and key in system state + err = assertstate.Add(st, s.model) + c.Assert(err, IsNil) err = assertstate.Add(st, s.serial) c.Assert(err, IsNil) kpMgr, err := asserts.OpenFSKeypairManager(dirs.SnapDeviceDir) @@ -1813,9 +1818,11 @@ }) st.Unlock() - req, encSerial, err := s.ac.DeviceSessionRequest("NONCE") + params, err := s.ac.DeviceSessionRequestParams("NONCE") st.Lock() c.Assert(err, IsNil) - c.Check(bytes.HasPrefix(req, []byte("type: device-session-request\n")), Equals, true) - c.Check(encSerial, DeepEquals, asserts.Encode(s.serial)) + c.Check(strings.HasPrefix(params.EncodedRequest(), "type: device-session-request\n"), Equals, true) + c.Check(params.EncodedSerial(), DeepEquals, string(asserts.Encode(s.serial))) + c.Check(params.EncodedModel(), DeepEquals, string(asserts.Encode(s.model))) + } diff -Nru snapd-2.27.5/overlord/overlord_test.go snapd-2.28.5/overlord/overlord_test.go --- snapd-2.27.5/overlord/overlord_test.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/overlord/overlord_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -34,6 +34,7 @@ "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/overlord" + "github.com/snapcore/snapd/overlord/auth" "github.com/snapcore/snapd/overlord/patch" "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" @@ -179,6 +180,11 @@ st := o.State() st.Lock() st.Set("seeded", true) + auth.SetDevice(st, &auth.DeviceState{ + Brand: "canonical", + Model: "pc", + Serial: "serialserial", + }) st.Unlock() } @@ -358,7 +364,7 @@ } func (ovs *overlordSuite) TestEnsureLoopPrune(c *C) { - restoreIntv := overlord.MockPruneInterval(20*time.Millisecond, 100*time.Millisecond, 100*time.Millisecond) + restoreIntv := overlord.MockPruneInterval(200*time.Millisecond, 1000*time.Millisecond, 1000*time.Millisecond) defer restoreIntv() o, err := overlord.New() c.Assert(err, IsNil) @@ -378,7 +384,7 @@ cycles := -1 waitForPrune := func(_ *state.State) error { if cycles == -1 { - if time.Since(t0) > 100*time.Millisecond { + if time.Since(t0) > 1000*time.Millisecond { cycles = 2 // wait a couple more loop cycles } return nil @@ -419,7 +425,7 @@ } func (ovs *overlordSuite) TestEnsureLoopPruneRunsMultipleTimes(c *C) { - restoreIntv := overlord.MockPruneInterval(10*time.Millisecond, 100*time.Millisecond, 1*time.Hour) + restoreIntv := overlord.MockPruneInterval(100*time.Millisecond, 1000*time.Millisecond, 1*time.Hour) defer restoreIntv() o, err := overlord.New() c.Assert(err, IsNil) @@ -443,7 +449,7 @@ o.Loop() // ensure the first change is pruned - time.Sleep(150 * time.Millisecond) + time.Sleep(1500 * time.Millisecond) st.Lock() c.Check(st.Changes(), HasLen, 1) st.Unlock() @@ -452,7 +458,7 @@ st.Lock() chg2.SetStatus(state.DoneStatus) st.Unlock() - time.Sleep(150 * time.Millisecond) + time.Sleep(1500 * time.Millisecond) st.Lock() c.Check(st.Changes(), HasLen, 0) st.Unlock() diff -Nru snapd-2.27.5/overlord/snapstate/backend/copydata.go snapd-2.28.5/overlord/snapstate/backend/copydata.go --- snapd-2.27.5/overlord/snapstate/backend/copydata.go 2016-09-15 18:55:10.000000000 +0000 +++ snapd-2.28.5/overlord/snapstate/backend/copydata.go 2017-09-13 14:47:18.000000000 +0000 @@ -40,6 +40,9 @@ if oldSnap == nil { return os.MkdirAll(newSnap.DataDir(), 0755) + } else if oldSnap.Revision == newSnap.Revision { + // nothing to do + return nil } return copySnapData(oldSnap, newSnap) @@ -47,6 +50,10 @@ // UndoCopySnapData removes the copy that may have been done for newInfo snap of oldInfo snap data and also the data directories that may have been created for newInfo snap. func (b Backend) UndoCopySnapData(newInfo *snap.Info, oldInfo *snap.Info, meter progress.Meter) error { + if oldInfo != nil && oldInfo.Revision == newInfo.Revision { + // nothing to do + return nil + } err1 := b.RemoveSnapData(newInfo) if err1 != nil { logger.Noticef("Cannot remove data directories for %q: %v", newInfo.Name(), err1) diff -Nru snapd-2.27.5/overlord/snapstate/backend/copydata_test.go snapd-2.28.5/overlord/snapstate/backend/copydata_test.go --- snapd-2.27.5/overlord/snapstate/backend/copydata_test.go 2016-11-24 09:36:04.000000000 +0000 +++ snapd-2.28.5/overlord/snapstate/backend/copydata_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -30,6 +30,7 @@ . "gopkg.in/check.v1" "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/osutil" "github.com/snapcore/snapd/progress" "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap/snaptest" @@ -445,3 +446,104 @@ err := s.be.CopySnapData(v2, v1, &s.nullProgress) c.Assert(err, ErrorMatches, fmt.Sprintf(`cannot copy %s to %s: .*: "cp: boom" \(3\)`, q(v1.DataDir()), q(v2.DataDir()))) } + +func (s *copydataSuite) TestCopyDataPartialFailure(c *C) { + v1 := snaptest.MockSnap(c, helloYaml1, helloContents, &snap.SideInfo{Revision: snap.R(10)}) + + s.populateData(c, snap.R(10)) + homedir1 := s.populateHomeData(c, "user1", snap.R(10)) + homedir2 := s.populateHomeData(c, "user2", snap.R(10)) + + // pretend we install a new version + v2 := snaptest.MockSnap(c, helloYaml2, helloContents, &snap.SideInfo{Revision: snap.R(20)}) + + // sanity check: the 20 dirs don't exist yet (but 10 do) + for _, dir := range []string{dirs.SnapDataDir, homedir1, homedir2} { + c.Assert(osutil.FileExists(filepath.Join(dir, "hello", "20")), Equals, false, Commentf(dir)) + c.Assert(osutil.FileExists(filepath.Join(dir, "hello", "10")), Equals, true, Commentf(dir)) + } + + c.Assert(os.Chmod(filepath.Join(homedir2, "hello", "10", "canary.home"), 0), IsNil) + + // try to copy data + err := s.be.CopySnapData(v2, v1, &s.nullProgress) + c.Assert(err, NotNil) + + // the copy data failed, so check it cleaned up after itself (but not too much!) + for _, dir := range []string{dirs.SnapDataDir, homedir1, homedir2} { + c.Check(osutil.FileExists(filepath.Join(dir, "hello", "20")), Equals, false, Commentf(dir)) + c.Check(osutil.FileExists(filepath.Join(dir, "hello", "10")), Equals, true, Commentf(dir)) + } +} + +func (s *copydataSuite) TestCopyDataSameRevision(c *C) { + v1 := snaptest.MockSnap(c, helloYaml1, helloContents, &snap.SideInfo{Revision: snap.R(10)}) + + homedir1 := s.populateHomeData(c, "user1", snap.R(10)) + homedir2 := s.populateHomeData(c, "user2", snap.R(10)) + c.Assert(os.MkdirAll(v1.DataDir(), 0755), IsNil) + c.Assert(os.MkdirAll(v1.CommonDataDir(), 0755), IsNil) + c.Assert(ioutil.WriteFile(filepath.Join(v1.DataDir(), "canary.txt"), nil, 0644), IsNil) + c.Assert(ioutil.WriteFile(filepath.Join(v1.CommonDataDir(), "canary.common"), nil, 0644), IsNil) + + // the data is there + for _, fn := range []string{ + filepath.Join(v1.DataDir(), "canary.txt"), + filepath.Join(v1.CommonDataDir(), "canary.common"), + filepath.Join(homedir1, "hello", "10", "canary.home"), + filepath.Join(homedir2, "hello", "10", "canary.home"), + } { + c.Assert(osutil.FileExists(fn), Equals, true, Commentf(fn)) + } + + // copy data works + err := s.be.CopySnapData(v1, v1, &s.nullProgress) + c.Assert(err, IsNil) + + // the data is still there :-) + for _, fn := range []string{ + filepath.Join(v1.DataDir(), "canary.txt"), + filepath.Join(v1.CommonDataDir(), "canary.common"), + filepath.Join(homedir1, "hello", "10", "canary.home"), + filepath.Join(homedir2, "hello", "10", "canary.home"), + } { + c.Check(osutil.FileExists(fn), Equals, true, Commentf(fn)) + } + +} + +func (s *copydataSuite) TestUndoCopyDataSameRevision(c *C) { + v1 := snaptest.MockSnap(c, helloYaml1, helloContents, &snap.SideInfo{Revision: snap.R(10)}) + + homedir1 := s.populateHomeData(c, "user1", snap.R(10)) + homedir2 := s.populateHomeData(c, "user2", snap.R(10)) + c.Assert(os.MkdirAll(v1.DataDir(), 0755), IsNil) + c.Assert(os.MkdirAll(v1.CommonDataDir(), 0755), IsNil) + c.Assert(ioutil.WriteFile(filepath.Join(v1.DataDir(), "canary.txt"), nil, 0644), IsNil) + c.Assert(ioutil.WriteFile(filepath.Join(v1.CommonDataDir(), "canary.common"), nil, 0644), IsNil) + + // the data is there + for _, fn := range []string{ + filepath.Join(v1.DataDir(), "canary.txt"), + filepath.Join(v1.CommonDataDir(), "canary.common"), + filepath.Join(homedir1, "hello", "10", "canary.home"), + filepath.Join(homedir2, "hello", "10", "canary.home"), + } { + c.Assert(osutil.FileExists(fn), Equals, true, Commentf(fn)) + } + + // undo copy data works + err := s.be.UndoCopySnapData(v1, v1, &s.nullProgress) + c.Assert(err, IsNil) + + // the data is still there :-) + for _, fn := range []string{ + filepath.Join(v1.DataDir(), "canary.txt"), + filepath.Join(v1.CommonDataDir(), "canary.common"), + filepath.Join(homedir1, "hello", "10", "canary.home"), + filepath.Join(homedir2, "hello", "10", "canary.home"), + } { + c.Check(osutil.FileExists(fn), Equals, true, Commentf(fn)) + } + +} diff -Nru snapd-2.27.5/overlord/snapstate/backend/link.go snapd-2.28.5/overlord/snapstate/backend/link.go --- snapd-2.27.5/overlord/snapstate/backend/link.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/overlord/snapstate/backend/link.go 2017-09-13 14:47:18.000000000 +0000 @@ -89,10 +89,13 @@ } // add the daemons from the snap.yaml if err := wrappers.AddSnapServices(s, &progress.NullProgress{}); err != nil { + wrappers.RemoveSnapBinaries(s) return err } // add the desktop files if err := wrappers.AddSnapDesktopFiles(s); err != nil { + wrappers.RemoveSnapServices(s, &progress.NullProgress{}) + wrappers.RemoveSnapBinaries(s) return err } diff -Nru snapd-2.27.5/overlord/snapstate/backend/link_test.go snapd-2.28.5/overlord/snapstate/backend/link_test.go --- snapd-2.27.5/overlord/snapstate/backend/link_test.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/overlord/snapstate/backend/link_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,6 +20,9 @@ package backend_test import ( + "errors" + "io/ioutil" + "os" "path/filepath" . "gopkg.in/check.v1" @@ -218,3 +221,95 @@ err := s.be.LinkSnap(info) c.Assert(err, ErrorMatches, `cannot link snap "foo" with unset revision`) } + +type linkCleanupSuite struct { + linkSuite + info *snap.Info +} + +var _ = Suite(&linkCleanupSuite{}) + +func (s *linkCleanupSuite) SetUpTest(c *C) { + s.linkSuite.SetUpTest(c) + + const yaml = `name: hello +version: 1.0 +environment: + KEY: value + +apps: + foo: + command: foo + bar: + command: bar + svc: + command: svc + daemon: simple +` + s.info = snaptest.MockSnap(c, yaml, "", &snap.SideInfo{Revision: snap.R(11)}) + + guiDir := filepath.Join(s.info.MountDir(), "meta", "gui") + c.Assert(os.MkdirAll(guiDir, 0755), IsNil) + c.Assert(ioutil.WriteFile(filepath.Join(guiDir, "bin.desktop"), []byte(` +[Desktop Entry] +Name=bin +Icon=${SNAP}/bin.png +Exec=bin +`), 0644), IsNil) + + systemd.SystemctlCmd = func(...string) ([]byte, error) { + return nil, nil + } + + // sanity checks + for _, d := range []string{dirs.SnapBinariesDir, dirs.SnapDesktopFilesDir, dirs.SnapServicesDir} { + os.MkdirAll(d, 0755) + l, err := filepath.Glob(filepath.Join(d, "*")) + c.Assert(err, IsNil, Commentf(d)) + c.Assert(l, HasLen, 0, Commentf(d)) + } +} + +func (s *linkCleanupSuite) testLinkCleanupDirOnFail(c *C, dir string) { + c.Assert(os.Chmod(dir, 0), IsNil) + defer os.Chmod(dir, 0755) + + err := s.be.LinkSnap(s.info) + c.Assert(err, NotNil) + c.Assert(err, FitsTypeOf, &os.PathError{}) + + for _, d := range []string{dirs.SnapBinariesDir, dirs.SnapDesktopFilesDir, dirs.SnapServicesDir} { + l, err := filepath.Glob(filepath.Join(d, "*")) + c.Check(err, IsNil, Commentf(d)) + c.Check(l, HasLen, 0, Commentf(d)) + } +} + +func (s *linkCleanupSuite) TestLinkCleanupOnDesktopFail(c *C) { + s.testLinkCleanupDirOnFail(c, dirs.SnapDesktopFilesDir) +} + +func (s *linkCleanupSuite) TestLinkCleanupOnBinariesFail(c *C) { + // this one is the trivial case _as the code stands today_, + // but nothing guarantees that ordering. + s.testLinkCleanupDirOnFail(c, dirs.SnapBinariesDir) +} + +func (s *linkCleanupSuite) TestLinkCleanupOnServicesFail(c *C) { + s.testLinkCleanupDirOnFail(c, dirs.SnapServicesDir) +} + +func (s *linkCleanupSuite) TestLinkCleanupOnSystemctlFail(c *C) { + systemd.SystemctlCmd = func(...string) ([]byte, error) { + return nil, errors.New("ouchie") + } + err := s.be.LinkSnap(s.info) + c.Assert(err, ErrorMatches, "ouchie") + + for _, d := range []string{dirs.SnapBinariesDir, dirs.SnapDesktopFilesDir, dirs.SnapServicesDir} { + l, err := filepath.Glob(filepath.Join(d, "*")) + c.Check(err, IsNil, Commentf(d)) + c.Check(l, HasLen, 0, Commentf(d)) + } + +} diff -Nru snapd-2.27.5/overlord/snapstate/backend/snapdata.go snapd-2.28.5/overlord/snapstate/backend/snapdata.go --- snapd-2.27.5/overlord/snapstate/backend/snapdata.go 2016-11-24 09:36:04.000000000 +0000 +++ snapd-2.28.5/overlord/snapstate/backend/snapdata.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,11 +20,13 @@ package backend import ( + "errors" "fmt" "os" "path/filepath" unix "syscall" + "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/osutil" "github.com/snapcore/snapd/snap" ) @@ -118,6 +120,21 @@ if err != nil { return err } + done := make([]string, 0, len(oldDataDirs)) + defer func() { + if err == nil { + return + } + // something went wrong, but we'd already written stuff. Fix that. + for _, newDir := range done { + if err := os.RemoveAll(newDir); err != nil { + logger.Noticef("while undoing creation of new data directory %q: %v", newDir, err) + } + if err := untrash(newDir); err != nil { + logger.Noticef("while restoring the old version of data directory %q: %v", newDir, err) + } + } + }() newSuffix := filepath.Base(newSnap.DataDir()) for _, oldDir := range oldDataDirs { @@ -126,6 +143,7 @@ if err := copySnapDataDirectory(oldDir, newDir); err != nil { return err } + done = append(done, newDir) } return nil @@ -199,7 +217,20 @@ if _, err := os.Stat(newPath); err != nil { if err := osutil.CopyFile(oldPath, newPath, osutil.CopyFlagPreserveAll|osutil.CopyFlagSync); err != nil { - return fmt.Errorf("cannot copy %q to %q: %v", oldPath, newPath, err) + msg := fmt.Sprintf("cannot copy %q to %q: %v", oldPath, newPath, err) + // remove the directory, in case it was a partial success + if e := os.RemoveAll(newPath); e != nil && !os.IsNotExist(e) { + msg += fmt.Sprintf("; and when trying to remove the partially-copied new data directory: %v", e) + } + // something went wrong but we already trashed what was there + // try to fix that; hope for the best + if e := untrash(newPath); e != nil { + // oh noes + // TODO: issue a warning to the user that data was lost + msg += fmt.Sprintf("; and when trying to restore the old data directory: %v", e) + } + + return errors.New(msg) } } } else if !os.IsNotExist(err) { diff -Nru snapd-2.27.5/overlord/snapstate/backend_test.go snapd-2.28.5/overlord/snapstate/backend_test.go --- snapd-2.27.5/overlord/snapstate/backend_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/overlord/snapstate/backend_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -27,7 +27,6 @@ "golang.org/x/net/context" - "github.com/snapcore/snapd/asserts" "github.com/snapcore/snapd/overlord/auth" "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/snapstate/backend" @@ -35,6 +34,7 @@ "github.com/snapcore/snapd/progress" "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/store" + "github.com/snapcore/snapd/store/storetest" ) type fakeOp struct { @@ -90,6 +90,8 @@ } type fakeStore struct { + storetest.Store + downloads []fakeDownload fakeBackend *fakeSnappyBackend fakeCurrentProgress int @@ -147,10 +149,6 @@ return info, nil } -func (f *fakeStore) Find(search *store.Search, user *auth.UserState) ([]*snap.Info, error) { - panic("Find called") -} - func (f *fakeStore) LookupRefresh(cand *store.RefreshCandidate, user *auth.UserState) (*snap.Info, error) { f.pokeStateLock() @@ -271,22 +269,6 @@ return nil } -func (f *fakeStore) Buy(options *store.BuyOptions, user *auth.UserState) (*store.BuyResult, error) { - panic("Never expected fakeStore.Buy to be called") -} - -func (f *fakeStore) ReadyToBuy(user *auth.UserState) error { - panic("Never expected fakeStore.ReadyToBuy to be called") -} - -func (f *fakeStore) Assertion(*asserts.AssertionType, []string, *auth.UserState) (asserts.Assertion, error) { - panic("Never expected fakeStore.Assertion to be called") -} - -func (f *fakeStore) Sections(user *auth.UserState) ([]string, error) { - panic("Sections called") -} - type fakeSnappyBackend struct { ops fakeOps diff -Nru snapd-2.27.5/overlord/snapstate/check_snap.go snapd-2.28.5/overlord/snapstate/check_snap.go --- snapd-2.27.5/overlord/snapstate/check_snap.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/overlord/snapstate/check_snap.go 2017-09-13 14:47:18.000000000 +0000 @@ -306,7 +306,34 @@ return nil } +func checkBases(st *state.State, snapInfo, curInfo *snap.Info, flags Flags) error { + // check if this is relevant + if snapInfo.Type != snap.TypeApp && snapInfo.Type != snap.TypeGadget { + return nil + } + if snapInfo.Base == "" { + return nil + } + + snapStates, err := All(st) + if err != nil { + return err + } + for otherSnap, snapst := range snapStates { + typ, err := snapst.Type() + if err != nil { + return err + } + if typ == snap.TypeBase && otherSnap == snapInfo.Base { + return nil + } + } + + return fmt.Errorf("cannot find required base %q", snapInfo.Base) +} + func init() { AddCheckSnapCallback(checkCoreName) AddCheckSnapCallback(checkGadgetOrKernel) + AddCheckSnapCallback(checkBases) } diff -Nru snapd-2.27.5/overlord/snapstate/check_snap_test.go snapd-2.28.5/overlord/snapstate/check_snap_test.go --- snapd-2.27.5/overlord/snapstate/check_snap_test.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/overlord/snapstate/check_snap_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -612,3 +612,66 @@ st.Lock() c.Check(err, ErrorMatches, "cannot replace kernel snap with a different one") } + +func (s *checkSnapSuite) TestCheckSnapBasesErrorsIfMissing(c *C) { + st := state.New(nil) + st.Lock() + defer st.Unlock() + + const yaml = `name: requires-base +version: 1 +base: some-base +` + + info, err := snap.InfoFromSnapYaml([]byte(yaml)) + c.Assert(err, IsNil) + + var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { + return info, nil, nil + } + restore := snapstate.MockOpenSnapFile(openSnapFile) + defer restore() + + st.Unlock() + err = snapstate.CheckSnap(st, "snap-path", nil, nil, snapstate.Flags{}) + st.Lock() + c.Check(err, ErrorMatches, "cannot find required base \"some-base\"") +} + +func (s *checkSnapSuite) TestCheckSnapBasesHappy(c *C) { + st := state.New(nil) + st.Lock() + defer st.Unlock() + + si := &snap.SideInfo{RealName: "some-base", Revision: snap.R(1), SnapID: "some-base-id"} + snaptest.MockSnap(c, ` +name: some-base +type: base +version: 1 +`, "", si) + snapstate.Set(st, "some-base", &snapstate.SnapState{ + SnapType: "base", + Active: true, + Sequence: []*snap.SideInfo{si}, + Current: si.Revision, + }) + + const yaml = `name: requires-base +version: 1 +base: some-base +` + + info, err := snap.InfoFromSnapYaml([]byte(yaml)) + c.Assert(err, IsNil) + + var openSnapFile = func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error) { + return info, nil, nil + } + restore := snapstate.MockOpenSnapFile(openSnapFile) + defer restore() + + st.Unlock() + err = snapstate.CheckSnap(st, "snap-path", nil, nil, snapstate.Flags{}) + st.Lock() + c.Check(err, IsNil) +} diff -Nru snapd-2.27.5/overlord/snapstate/handlers.go snapd-2.28.5/overlord/snapstate/handlers.go --- snapd-2.27.5/overlord/snapstate/handlers.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/overlord/snapstate/handlers.go 2017-09-13 14:47:18.000000000 +0000 @@ -715,6 +715,16 @@ Set(st, snapsup.Name(), snapst) // Make sure if state commits and snapst is mutated we won't be rerun t.SetStatus(state.UndoneStatus) + + // If we are on classic and have no previous version of core + // we may have restarted from a distro package into the core + // snap. We need to undo that restart here. Instead of in + // doUnlinkCurrentSnap() like we usually do when going from + // core snap -> next core snap + if release.OnClassic && newInfo.Type == snap.TypeOS && oldCurrent.Unset() { + t.Logf("Requested daemon restart (undo classic initial core install)") + st.RequestRestart(state.RestartDaemon) + } return nil } diff -Nru snapd-2.27.5/overlord/snapstate/handlers_link_test.go snapd-2.28.5/overlord/snapstate/handlers_link_test.go --- snapd-2.27.5/overlord/snapstate/handlers_link_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/overlord/snapstate/handlers_link_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -47,7 +47,7 @@ var _ = Suite(&linkSnapSuite{}) type witnessRestartReqStateBackend struct { - restartRequested state.RestartType + restartRequested []state.RestartType } func (b *witnessRestartReqStateBackend) Checkpoint([]byte) error { @@ -55,13 +55,13 @@ } func (b *witnessRestartReqStateBackend) RequestRestart(t state.RestartType) { - b.restartRequested = t + b.restartRequested = append(b.restartRequested, t) } func (b *witnessRestartReqStateBackend) EnsureBefore(time.Duration) {} func (s *linkSnapSuite) SetUpTest(c *C) { - dirs.SnapCookieDir = c.MkDir() + dirs.SetRootDir(c.MkDir()) s.stateBackend = &witnessRestartReqStateBackend{} s.fakeBackend = &fakeSnappyBackend{} @@ -133,7 +133,7 @@ c.Check(snapst.Current, Equals, snap.R(33)) c.Check(snapst.Channel, Equals, "beta") c.Check(t.Status(), Equals, state.DoneStatus) - c.Check(s.stateBackend.restartRequested, Equals, state.RestartUnset) + c.Check(s.stateBackend.restartRequested, HasLen, 0) } func (s *linkSnapSuite) TestDoUndoLinkSnap(c *C) { @@ -182,7 +182,7 @@ Channel: "beta", }) - s.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "foo/35") + s.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.SnapMountDir, "foo/35") s.state.NewChange("dummy", "...").AddTask(t) s.state.Unlock() @@ -204,11 +204,11 @@ }, { op: "link-snap.failed", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "foo/35"), + name: filepath.Join(dirs.SnapMountDir, "foo/35"), }, { op: "unlink-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "foo/35"), + name: filepath.Join(dirs.SnapMountDir, "foo/35"), }, }) } @@ -245,7 +245,7 @@ c.Check(typ, Equals, snap.TypeOS) c.Check(t.Status(), Equals, state.DoneStatus) - c.Check(s.stateBackend.restartRequested, Equals, state.RestartDaemon) + c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon}) c.Check(t.Log(), HasLen, 1) c.Check(t.Log()[0], Matches, `.*INFO Requested daemon restart\.`) } @@ -385,5 +385,47 @@ c.Check(snapst.Current, Equals, snap.R(1)) c.Check(t.Status(), Equals, state.UndoneStatus) - c.Check(s.stateBackend.restartRequested, Equals, state.RestartDaemon) + c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon}) +} + +func (s *linkSnapSuite) TestDoUndoLinkSnapCoreClassic(c *C) { + restore := release.MockOnClassic(true) + defer restore() + + s.state.Lock() + defer s.state.Unlock() + + // no previous core snap and an error on link, in this + // case we need to restart on classic back into the distro + // package version + si1 := &snap.SideInfo{ + RealName: "core", + Revision: snap.R(1), + } + t := s.state.NewTask("link-snap", "test") + t.Set("snap-setup", &snapstate.SnapSetup{ + SideInfo: si1, + }) + chg := s.state.NewChange("dummy", "...") + chg.AddTask(t) + + terr := s.state.NewTask("error-trigger", "provoking total undo") + terr.WaitFor(t) + chg.AddTask(terr) + + s.state.Unlock() + + for i := 0; i < 3; i++ { + s.snapmgr.Ensure() + s.snapmgr.Wait() + } + + s.state.Lock() + var snapst snapstate.SnapState + err := snapstate.Get(s.state, "core", &snapst) + c.Assert(err, Equals, state.ErrNoState) + c.Check(t.Status(), Equals, state.UndoneStatus) + + c.Check(s.stateBackend.restartRequested, DeepEquals, []state.RestartType{state.RestartDaemon, state.RestartDaemon}) + } diff -Nru snapd-2.27.5/overlord/snapstate/handlers_mount_test.go snapd-2.28.5/overlord/snapstate/handlers_mount_test.go --- snapd-2.27.5/overlord/snapstate/handlers_mount_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/overlord/snapstate/handlers_mount_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -44,8 +44,8 @@ var _ = Suite(&mountSnapSuite{}) func (s *mountSnapSuite) SetUpTest(c *C) { - oldDir := dirs.SnapCookieDir - dirs.SnapCookieDir = c.MkDir() + oldDir := dirs.GlobalRootDir + dirs.SetRootDir(c.MkDir()) s.fakeBackend = &fakeSnappyBackend{} s.state = state.New(nil) @@ -60,7 +60,7 @@ reset1 := snapstate.MockReadInfo(s.fakeBackend.ReadInfo) s.reset = func() { reset1() - dirs.SnapCookieDir = oldDir + dirs.SetRootDir(oldDir) } } @@ -138,7 +138,7 @@ c.Check(s.fakeBackend.ops, DeepEquals, fakeOps{ { op: "current", - old: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "core/1"), + old: filepath.Join(dirs.SnapMountDir, "core/1"), }, { op: "setup-snap", @@ -147,7 +147,7 @@ }, { op: "undo-setup-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "core/2"), + name: filepath.Join(dirs.SnapMountDir, "core/2"), stype: "os", }, }) diff -Nru snapd-2.27.5/overlord/snapstate/handlers_prepare_test.go snapd-2.28.5/overlord/snapstate/handlers_prepare_test.go --- snapd-2.27.5/overlord/snapstate/handlers_prepare_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/overlord/snapstate/handlers_prepare_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -40,7 +40,7 @@ var _ = Suite(&prepareSnapSuite{}) func (s *prepareSnapSuite) SetUpTest(c *C) { - dirs.SnapCookieDir = c.MkDir() + dirs.SetRootDir(c.MkDir()) s.fakeBackend = &fakeSnappyBackend{} s.state = state.New(nil) diff -Nru snapd-2.27.5/overlord/snapstate/snapmgr.go snapd-2.28.5/overlord/snapstate/snapmgr.go --- snapd-2.27.5/overlord/snapstate/snapmgr.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/overlord/snapstate/snapmgr.go 2017-09-13 14:47:18.000000000 +0000 @@ -152,8 +152,8 @@ snapst.SnapType = string(typ) } -// HasCurrent returns whether snapst.Current is set. -func (snapst *SnapState) HasCurrent() bool { +// IsInstalled returns whether the snap is installed, i.e. snapst represents an installed snap with Current revision set. +func (snapst *SnapState) IsInstalled() bool { if snapst.Current.Unset() { if len(snapst.Sequence) > 0 { panic(fmt.Sprintf("snapst.Current and snapst.Sequence out of sync: %#v %#v", snapst.Current, snapst.Sequence)) @@ -176,11 +176,9 @@ return local } -// TODO: unexport CurrentSideInfo and HasCurrent? - // CurrentSideInfo returns the side info for the revision indicated by snapst.Current in the snap revision sequence if there is one. func (snapst *SnapState) CurrentSideInfo() *snap.SideInfo { - if !snapst.HasCurrent() { + if !snapst.IsInstalled() { return nil } if idx := snapst.LastIndex(snapst.Current); idx >= 0 { @@ -347,6 +345,9 @@ runner.AddHandler("disable-aliases", m.doDisableAliases, m.undoRefreshAliases) runner.AddHandler("prefer-aliases", m.doPreferAliases, m.undoRefreshAliases) + // misc + runner.AddHandler("switch-snap", m.doSwitchSnap, nil) + // control serialisation runner.SetBlocked(m.blockedTask) @@ -634,6 +635,21 @@ 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 +} + // GenerateCookies 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). diff -Nru snapd-2.27.5/overlord/snapstate/snapstate.go snapd-2.28.5/overlord/snapstate/snapstate.go --- snapd-2.27.5/overlord/snapstate/snapstate.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/overlord/snapstate/snapstate.go 2017-10-04 14:43:22.000000000 +0000 @@ -64,10 +64,10 @@ if !release.OnClassic { return nil, fmt.Errorf("classic confinement is only supported on classic systems") } else if !dirs.SupportsClassicConfinement() { - return nil, fmt.Errorf("classic confinement is not yet supported on your distribution") + return nil, fmt.Errorf(i18n.G("classic confinement requires snaps under /snap or symlink from /snap to %s"), dirs.SnapMountDir) } } - if !snapst.HasCurrent() { // install? + if !snapst.IsInstalled() { // install? // check that the snap command namespace doesn't conflict with an enabled alias if err := checkSnapAliasConflict(st, snapsup.Name()); err != nil { return nil, err @@ -169,8 +169,15 @@ addTask(setupAliases) prev = setupAliases + // run refresh hook when updating existing snap, otherwise run install hook + if snapst.IsInstalled() && !snapsup.Flags.Revert { + refreshHook := SetupPostRefreshHook(st, snapsup.Name()) + addTask(refreshHook) + prev = refreshHook + } + // only run install hook if installing the snap for the first time - if !snapst.HasCurrent() { + if !snapst.IsInstalled() { installHook := SetupInstallHook(st, snapsup.Name()) addTask(installHook) prev = installHook @@ -182,7 +189,7 @@ prev = startSnapServices // Do not do that if we are reverting to a local revision - if snapst.HasCurrent() && !snapsup.Flags.Revert { + if snapst.IsInstalled() && !snapsup.Flags.Revert { seq := snapst.Sequence currentIndex := snapst.LastIndex(snapst.Current) @@ -235,7 +242,7 @@ } var confFlags int - if !snapst.HasCurrent() && snapsup.SideInfo != nil && snapsup.SideInfo.SnapID != "" { + if !snapst.IsInstalled() && snapsup.SideInfo != nil && snapsup.SideInfo.SnapID != "" { // installation, run configure using the gadget defaults // if available confFlags |= UseConfigDefaults @@ -268,6 +275,10 @@ panic("internal error: snapstate.SetupInstallHook is unset") } +var SetupPostRefreshHook = func(st *state.State, snapName string) *state.Task { + panic("internal error: snapstate.SetupPostRefreshHook is unset") +} + var SetupRemoveHook = func(st *state.State, snapName string) *state.Task { panic("internal error: snapstate.SetupRemoveHook is unset") } @@ -299,11 +310,18 @@ return &plugRef, &slotRef, nil } -// CheckChangeConflict ensures that for the given snapName no other -// changes that alters the snap (like remove, install, refresh) are in -// progress. It also ensures that snapst (if not nil) did not get -// modified. If a conflict is detected an error is returned. -func CheckChangeConflict(st *state.State, snapName string, checkConflictPredicate func(taskKind string) bool, snapst *SnapState) error { +// CheckChangeConflictMany ensures that for the given snapNames no other +// changes that alters the snaps (like remove, install, refresh) are in +// progress. If a conflict is detected an error is returned. +// +// It's like CheckChangeConflict, but for multiple snaps, and does not +// check snapst. +func CheckChangeConflictMany(st *state.State, snapNames []string, checkConflictPredicate func(taskKind string) bool) error { + snapMap := make(map[string]bool, len(snapNames)) + for _, k := range snapNames { + snapMap[k] = true + } + for _, chg := range st.Changes() { if chg.Status().Ready() { continue @@ -322,7 +340,13 @@ if err != nil { return fmt.Errorf("internal error: cannot obtain plug/slot data from task: %s", task.Summary()) } - if (plugRef.Snap == snapName || slotRef.Snap == snapName) && (checkConflictPredicate == nil || checkConflictPredicate(k)) { + if (snapMap[plugRef.Snap] || snapMap[slotRef.Snap]) && (checkConflictPredicate == nil || checkConflictPredicate(k)) { + var snapName string + if snapMap[plugRef.Snap] { + snapName = plugRef.Snap + } else { + snapName = slotRef.Snap + } return fmt.Errorf("snap %q has changes in progress", snapName) } } else { @@ -330,13 +354,26 @@ if err != nil { return fmt.Errorf("internal error: cannot obtain snap setup from task: %s", task.Summary()) } - if (snapsup.Name() == snapName) && (checkConflictPredicate == nil || checkConflictPredicate(k)) { + snapName := snapsup.Name() + if (snapMap[snapName]) && (checkConflictPredicate == nil || checkConflictPredicate(k)) { return fmt.Errorf("snap %q has changes in progress", snapName) } } } } + return nil +} + +// CheckChangeConflict ensures that for the given snapName no other +// changes that alters the snap (like remove, install, refresh) are in +// progress. It also ensures that snapst (if not nil) did not get +// modified. If a conflict is detected an error is returned. +func CheckChangeConflict(st *state.State, snapName string, checkConflictPredicate func(taskKind string) bool, snapst *SnapState) error { + if err := CheckChangeConflictMany(st, []string{snapName}, checkConflictPredicate); err != nil { + return err + } + if snapst != nil { // caller wants us to also make sure the SnapState in state // matches the one they provided. Necessary because we need to @@ -418,7 +455,7 @@ if err != nil && err != state.ErrNoState { return nil, err } - if snapst.HasCurrent() { + if snapst.IsInstalled() { return nil, &snap.AlreadyInstalledError{Snap: name} } @@ -782,6 +819,28 @@ return changed, mustPrune, transferTargets, nil } +// Switch switches a snap to a new channel +func Switch(st *state.State, name, channel string) (*state.TaskSet, error) { + var snapst SnapState + err := Get(st, name, &snapst) + if err != nil && err != state.ErrNoState { + return nil, err + } + if !snapst.IsInstalled() { + return nil, fmt.Errorf("cannot find snap %q", name) + } + + snapsup := &SnapSetup{ + SideInfo: snapst.CurrentSideInfo(), + Channel: channel, + } + + switchSnap := st.NewTask("switch-snap", fmt.Sprintf(i18n.G("Switch snap %q to %s"), snapsup.Name(), snapsup.Channel)) + switchSnap.Set("snap-setup", &snapsup) + + return state.NewTaskSet(switchSnap), nil +} + // Update initiates a change updating a snap. // Note that the state must be locked by the caller. func Update(st *state.State, name, channel string, revision snap.Revision, userID int, flags Flags) (*state.TaskSet, error) { @@ -790,7 +849,7 @@ if err != nil && err != state.ErrNoState { return nil, err } - if !snapst.HasCurrent() { + if !snapst.IsInstalled() { return nil, fmt.Errorf("cannot find snap %q", name) } @@ -1082,7 +1141,7 @@ return nil, err } - if !snapst.HasCurrent() { + if !snapst.IsInstalled() { return nil, &snap.NotInstalledError{Snap: name, Rev: snap.R(0)} } @@ -1305,7 +1364,7 @@ if err != nil && err != state.ErrNoState { return nil, err } - if !oldSnapst.HasCurrent() { + if !oldSnapst.IsInstalled() { return nil, fmt.Errorf("cannot transition snap %q: not installed", oldName) } @@ -1321,7 +1380,7 @@ if err != nil && err != state.ErrNoState { return nil, err } - if !newSnapst.HasCurrent() { + if !newSnapst.IsInstalled() { // start by instaling the new snap tsInst, err := doInstall(st, &newSnapst, &SnapSetup{ Channel: oldSnapst.Channel, @@ -1364,6 +1423,18 @@ // State/info accessors +// Installing returns whether there's an in-progress installation. +func Installing(st *state.State) bool { + for _, task := range st.Tasks() { + k := task.Kind() + chg := task.Change() + if k == "mount-snap" && chg != nil && !chg.Status().Ready() { + return true + } + } + return false +} + // Info returns the information about the snap with given name and revision. // Works also for a mounted candidate snap in the process of being installed. func Info(st *state.State, name string, revision snap.Revision) (*snap.Info, error) { @@ -1427,13 +1498,20 @@ } curStates := make(map[string]*SnapState, len(stateMap)) for snapName, snapst := range stateMap { - if snapst.HasCurrent() { - curStates[snapName] = snapst - } + curStates[snapName] = snapst } return curStates, nil } +// NumSnaps returns the number of installed snaps. +func NumSnaps(st *state.State) (int, error) { + var snaps map[string]*json.RawMessage + if err := st.Get("snaps", &snaps); err != nil && err != state.ErrNoState { + return -1, err + } + return len(snaps), nil +} + // Set sets the SnapState of the given snap, overwriting any earlier state. func Set(st *state.State, name string, snapst *SnapState) { var snaps map[string]*json.RawMessage @@ -1486,7 +1564,7 @@ var res []*snap.Info for _, snapst := range stateMap { - if !snapst.HasCurrent() { + if !snapst.IsInstalled() { continue } typ, err := snapst.Type() diff -Nru snapd-2.27.5/overlord/snapstate/snapstate_test.go snapd-2.28.5/overlord/snapstate/snapstate_test.go --- snapd-2.27.5/overlord/snapstate/snapstate_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/overlord/snapstate/snapstate_test.go 2017-10-04 14:43:22.000000000 +0000 @@ -79,7 +79,7 @@ var _ = Suite(&snapmgrTestSuite{}) func (s *snapmgrTestSuite) SetUpTest(c *C) { - dirs.SnapCookieDir = c.MkDir() + dirs.SetRootDir(c.MkDir()) s.fakeBackend = &fakeSnappyBackend{} s.state = state.New(nil) @@ -91,8 +91,10 @@ } oldSetupInstallHook := snapstate.SetupInstallHook + oldSetupPostRefreshHook := snapstate.SetupPostRefreshHook oldSetupRemoveHook := snapstate.SetupRemoveHook snapstate.SetupInstallHook = hookstate.SetupInstallHook + snapstate.SetupPostRefreshHook = hookstate.SetupPostRefreshHook snapstate.SetupRemoveHook = hookstate.SetupRemoveHook var err error @@ -107,7 +109,9 @@ s.reset = func() { snapstate.SetupInstallHook = oldSetupInstallHook + snapstate.SetupPostRefreshHook = oldSetupPostRefreshHook snapstate.SetupRemoveHook = oldSetupRemoveHook + restore2() restore1() dirs.SetRootDir("/") @@ -154,7 +158,15 @@ func taskKinds(tasks []*state.Task) []string { kinds := make([]string, len(tasks)) for i, task := range tasks { - kinds[i] = task.Kind() + k := task.Kind() + if k == "run-hook" { + var hooksup hookstate.HookSetup + if err := task.Get("hook-setup", &hooksup); err != nil { + panic(err) + } + k = fmt.Sprintf("%s[%s]", k, hooksup.Hook) + } + kinds[i] = k } return kinds } @@ -185,7 +197,7 @@ expected = append(expected, "set-auto-aliases", "setup-aliases", - "run-hook", + "run-hook[install]", "start-snap-services") for i := 0; i < discards; i++ { expected = append(expected, @@ -199,7 +211,7 @@ ) } expected = append(expected, - "run-hook", + "run-hook[configure]", ) c.Assert(kinds, DeepEquals, expected) @@ -231,7 +243,10 @@ expected = append(expected, "set-auto-aliases", "setup-aliases", + "run-hook[post-refresh]", "start-snap-services") + + c.Assert(ts.Tasks()[len(expected)-2].Summary(), Matches, `Run post-refresh hook of .*`) for i := 0; i < discards; i++ { expected = append(expected, "clear-snap", @@ -244,7 +259,7 @@ ) } expected = append(expected, - "run-hook", + "run-hook[configure]", ) c.Assert(kinds, DeepEquals, expected) @@ -253,7 +268,7 @@ func verifyRemoveTasks(c *C, ts *state.TaskSet) { c.Assert(taskKinds(ts.Tasks()), DeepEquals, []string{ "stop-snap-services", - "run-hook", + "run-hook[remove]", "remove-aliases", "unlink-snap", "remove-profiles", @@ -318,7 +333,7 @@ func (s *snapmgrTestSuite) TestInstallClassicConfinementFiltering(c *C) { if !dirs.SupportsClassicConfinement() { - return + c.Skip("no support for classic") } s.state.Lock() @@ -344,13 +359,10 @@ reset := release.MockReleaseInfo(&release.OS{ ID: "fedora", }) - defer func() { reset(); dirs.SetRootDir("/") }() - - dirs.SetRootDir("/") + defer reset() _, err := snapstate.Install(s.state, "some-snap", "channel-for-classic", snap.R(0), s.user.ID, snapstate.Flags{Classic: true}) - c.Assert(err, Not(IsNil)) - c.Assert(err, DeepEquals, fmt.Errorf("classic confinement is not yet supported on your distribution")) + c.Assert(err, ErrorMatches, "classic confinement requires snaps under /snap or symlink from /snap to "+dirs.SnapMountDir) } func (s *snapmgrTestSuite) TestInstallTasks(c *C) { @@ -362,12 +374,6 @@ verifyInstallTasks(c, 0, 0, ts, s.state) c.Assert(s.state.TaskCount(), Equals, len(ts.Tasks())) - - runHooks := tasksWithKind(ts, "run-hook") - // one hook task for install, one for configure hook - c.Assert(runHooks, HasLen, 2) - c.Assert(runHooks[0].Summary(), Equals, `Run install hook of "some-snap" snap if present`) - c.Assert(runHooks[1].Summary(), Equals, `Run configure hook of "some-snap" snap if present`) } func (s *snapmgrTestSuite) TestInstallHookNotRunForInstalledSnap(c *C) { @@ -389,9 +395,10 @@ c.Assert(err, IsNil) runHooks := tasksWithKind(ts, "run-hook") - // no hook task for install, only for configure hook - c.Assert(runHooks, HasLen, 1) - c.Assert(runHooks[0].Summary(), Equals, `Run configure hook of "some-snap" snap if present`) + // hook tasks for refresh and for configure hook only; no install hook + c.Assert(runHooks, HasLen, 2) + c.Assert(runHooks[0].Summary(), Equals, `Run post-refresh hook of "some-snap" snap if present`) + c.Assert(runHooks[1].Summary(), Equals, `Run configure hook of "some-snap" snap if present`) } func (s *snapmgrTestSuite) TestCoreInstallTasks(c *C) { @@ -444,7 +451,7 @@ "set-auto-aliases", "setup-aliases", "start-snap-services", - "run-hook", + "run-hook[configure]", }) chg := s.state.NewChange("revert", "revert snap") @@ -753,7 +760,7 @@ "set-auto-aliases", "setup-aliases", "start-snap-services", - "run-hook", + "run-hook[configure]", }) } @@ -782,6 +789,33 @@ }) } +func (s *snapmgrTestSuite) TestSwitchTasks(c *C) { + s.state.Lock() + defer s.state.Unlock() + + snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ + Sequence: []*snap.SideInfo{ + {RealName: "some-snap", Revision: snap.R(11)}, + }, + Current: snap.R(11), + Active: false, + }) + + ts, err := snapstate.Switch(s.state, "some-snap", "some-channel") + c.Assert(err, IsNil) + + c.Assert(s.state.TaskCount(), Equals, len(ts.Tasks())) + c.Assert(taskKinds(ts.Tasks()), DeepEquals, []string{"switch-snap"}) +} + +func (s *snapmgrTestSuite) TestSwitchUnhappy(c *C) { + s.state.Lock() + defer s.state.Unlock() + + _, err := snapstate.Switch(s.state, "non-existing-snap", "some-channel") + c.Assert(err, ErrorMatches, `cannot find snap "non-existing-snap"`) +} + func (s *snapmgrTestSuite) TestDisableTasks(c *C) { s.state.Lock() defer s.state.Unlock() @@ -1009,11 +1043,6 @@ verifyUpdateTasks(c, unlinkBefore|cleanupAfter, 0, ts, s.state) c.Assert(s.state.TaskCount(), Equals, len(ts.Tasks())) - runHooks := tasksWithKind(ts, "run-hook") - // no 'install' hook task, only configure hook - c.Assert(runHooks, HasLen, 1) - c.Assert(runHooks[0].Summary(), Equals, `Run configure hook of "some-snap" snap if present`) - c.Check(validateCalled, Equals, true) var snapsup snapstate.SnapSetup @@ -1312,11 +1341,6 @@ c.Assert(s.state.TaskCount(), Equals, len(ts.Tasks())) verifyRemoveTasks(c, ts) - - runHooks := tasksWithKind(ts, "run-hook") - // hook task for 'remove' - c.Assert(runHooks, HasLen, 1) - c.Assert(runHooks[0].Summary(), Equals, `Run remove hook of "foo" snap if present`) } func (s *snapmgrTestSuite) TestRemoveHookNotExecutedIfNotLastRevison(c *C) { @@ -1376,6 +1400,7 @@ // ensure all our tasks ran c.Assert(chg.Err(), IsNil) c.Assert(chg.IsReady(), Equals, true) + c.Check(snapstate.Installing(s.state), Equals, false) c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{{ macaroon: s.user.StoreMacaroon, name: "some-snap", @@ -1401,7 +1426,7 @@ }, { op: "open-snap-file", - name: "/var/lib/snapd/snaps/some-snap_42.snap", + name: filepath.Join(dirs.SnapBlobDir, "some-snap_42.snap"), sinfo: snap.SideInfo{ RealName: "some-snap", Channel: "some-channel", @@ -1411,12 +1436,12 @@ }, { op: "setup-snap", - name: "/var/lib/snapd/snaps/some-snap_42.snap", + name: filepath.Join(dirs.SnapBlobDir, "some-snap_42.snap"), revno: snap.R(42), }, { op: "copy-data", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/42"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/42"), old: "", }, { @@ -1435,7 +1460,7 @@ }, { op: "link-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/42"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/42"), }, { op: "update-aliases", @@ -1471,7 +1496,7 @@ c.Assert(snapsup, DeepEquals, snapstate.SnapSetup{ Channel: "some-channel", UserID: s.user.ID, - SnapPath: "/var/lib/snapd/snaps/some-snap_42.snap", + SnapPath: filepath.Join(dirs.SnapBlobDir, "some-snap_42.snap"), DownloadInfo: &snap.DownloadInfo{ DownloadURL: "https://some-server.com/some/path.snap", }, @@ -1501,6 +1526,20 @@ c.Assert(snapst.Required, Equals, false) } +func (s *snapmgrTestSuite) TestInstalling(c *C) { + s.state.Lock() + defer s.state.Unlock() + + c.Check(snapstate.Installing(s.state), Equals, false) + + chg := s.state.NewChange("install", "install a snap") + ts, err := snapstate.Install(s.state, "some-snap", "some-channel", snap.R(42), 0, snapstate.Flags{}) + c.Assert(err, IsNil) + chg.AddAll(ts) + + c.Check(snapstate.Installing(s.state), Equals, true) +} + func (s *snapmgrTestSuite) TestUpdateRunThrough(c *C) { // use services-snap here to make sure services would be stopped/started appropriately si := snap.SideInfo{ @@ -1551,11 +1590,11 @@ }, { op: "current", - old: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "services-snap/7"), + old: filepath.Join(dirs.SnapMountDir, "services-snap/7"), }, { op: "open-snap-file", - name: "/var/lib/snapd/snaps/services-snap_11.snap", + name: filepath.Join(dirs.SnapBlobDir, "services-snap_11.snap"), sinfo: snap.SideInfo{ RealName: "services-snap", SnapID: "services-snap-id", @@ -1565,12 +1604,12 @@ }, { op: "setup-snap", - name: "/var/lib/snapd/snaps/services-snap_11.snap", + name: filepath.Join(dirs.SnapBlobDir, "services-snap_11.snap"), revno: snap.R(11), }, { op: "stop-snap-services", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "services-snap/7"), + name: filepath.Join(dirs.SnapMountDir, "services-snap/7"), }, { op: "remove-snap-aliases", @@ -1578,12 +1617,12 @@ }, { op: "unlink-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "services-snap/7"), + name: filepath.Join(dirs.SnapMountDir, "services-snap/7"), }, { op: "copy-data", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "services-snap/11"), - old: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "services-snap/7"), + name: filepath.Join(dirs.SnapMountDir, "services-snap/11"), + old: filepath.Join(dirs.SnapMountDir, "services-snap/7"), }, { op: "setup-profiles:Doing", @@ -1601,14 +1640,14 @@ }, { op: "link-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "services-snap/11"), + name: filepath.Join(dirs.SnapMountDir, "services-snap/11"), }, { op: "update-aliases", }, { op: "start-snap-services", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "services-snap/11"), + name: filepath.Join(dirs.SnapMountDir, "services-snap/11"), }, { op: "cleanup-trash", @@ -1640,7 +1679,7 @@ Channel: "some-channel", UserID: s.user.ID, - SnapPath: "/var/lib/snapd/snaps/services-snap_11.snap", + SnapPath: filepath.Join(dirs.SnapBlobDir, "services-snap_11.snap"), DownloadInfo: &snap.DownloadInfo{ DownloadURL: "https://some-server.com/some/path.snap", }, @@ -1653,6 +1692,11 @@ SnapID: "services-snap-id", }) + // check post-refresh hook + task = ts.Tasks()[11] + c.Assert(task.Kind(), Equals, "run-hook") + c.Assert(task.Summary(), Matches, `Run post-refresh hook of "services-snap" snap if present`) + // verify snaps in the system state var snapst snapstate.SnapState err = snapstate.Get(s.state, "services-snap", &snapst) @@ -1696,7 +1740,7 @@ c.Assert(err, IsNil) chg.AddAll(ts) - s.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "/some-snap/11") + s.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.SnapMountDir, "/some-snap/11") s.state.Unlock() defer s.snapmgr.Stop() @@ -1725,11 +1769,11 @@ }, { op: "current", - old: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/7"), + old: filepath.Join(dirs.SnapMountDir, "some-snap/7"), }, { op: "open-snap-file", - name: "/var/lib/snapd/snaps/some-snap_11.snap", + name: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"), sinfo: snap.SideInfo{ RealName: "some-snap", SnapID: "some-snap-id", @@ -1739,7 +1783,7 @@ }, { op: "setup-snap", - name: "/var/lib/snapd/snaps/some-snap_11.snap", + name: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"), revno: snap.R(11), }, { @@ -1748,12 +1792,12 @@ }, { op: "unlink-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/7"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/7"), }, { op: "copy-data", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/11"), - old: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/7"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/11"), + old: filepath.Join(dirs.SnapMountDir, "some-snap/7"), }, { op: "setup-profiles:Doing", @@ -1771,11 +1815,11 @@ }, { op: "link-snap.failed", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/11"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/11"), }, { op: "unlink-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/11"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/11"), }, { op: "setup-profiles:Undoing", @@ -1784,19 +1828,19 @@ }, { op: "undo-copy-snap-data", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/11"), - old: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/7"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/11"), + old: filepath.Join(dirs.SnapMountDir, "some-snap/7"), }, { op: "link-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/7"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/7"), }, { op: "update-aliases", }, { op: "undo-setup-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/11"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/11"), stype: "app", }, } @@ -1885,11 +1929,11 @@ }, { op: "current", - old: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/7"), + old: filepath.Join(dirs.SnapMountDir, "some-snap/7"), }, { op: "open-snap-file", - name: "/var/lib/snapd/snaps/some-snap_11.snap", + name: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"), sinfo: snap.SideInfo{ RealName: "some-snap", SnapID: "some-snap-id", @@ -1899,7 +1943,7 @@ }, { op: "setup-snap", - name: "/var/lib/snapd/snaps/some-snap_11.snap", + name: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"), revno: snap.R(11), }, { @@ -1908,12 +1952,12 @@ }, { op: "unlink-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/7"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/7"), }, { op: "copy-data", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/11"), - old: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/7"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/11"), + old: filepath.Join(dirs.SnapMountDir, "some-snap/7"), }, { op: "setup-profiles:Doing", @@ -1931,7 +1975,7 @@ }, { op: "link-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/11"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/11"), }, { op: "update-aliases", @@ -1943,7 +1987,7 @@ }, { op: "unlink-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/11"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/11"), }, { op: "setup-profiles:Undoing", @@ -1952,19 +1996,19 @@ }, { op: "undo-copy-snap-data", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/11"), - old: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/7"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/11"), + old: filepath.Join(dirs.SnapMountDir, "some-snap/7"), }, { op: "link-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/7"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/7"), }, { op: "update-aliases", }, { op: "undo-setup-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/11"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/11"), stype: "app", }, } @@ -2559,7 +2603,7 @@ } if scenario.update { first := tasks[j] - j += 14 + j += 15 c.Check(first.Kind(), Equals, "download-snap") wait := false if expectedPruned["other-snap"]["aliasA"] { @@ -2589,7 +2633,7 @@ c.Check(aliasTask.WaitTasks(), HasLen, 0) } } - c.Assert(j, Equals, len(tasks), Commentf("%#v", scenario)) + c.Assert(len(tasks), Equals, j, Commentf("%#v", scenario)) // conflict checks are triggered chg := s.state.NewChange("update", "...") @@ -2676,7 +2720,7 @@ }, { op: "copy-data", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "mock/x1"), + name: filepath.Join(dirs.SnapMountDir, "mock/x1"), old: "", }, { @@ -2693,7 +2737,7 @@ }, { op: "link-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "mock/x1"), + name: filepath.Join(dirs.SnapMountDir, "mock/x1"), }, { op: "setup-profiles:Doing", // core phase 2 @@ -2772,7 +2816,7 @@ // ensure only local install was run, i.e. first action is pseudo-action current c.Assert(ops.Ops(), HasLen, 11) c.Check(ops[0].op, Equals, "current") - c.Check(ops[0].old, Equals, filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "mock/x2")) + c.Check(ops[0].old, Equals, filepath.Join(dirs.SnapMountDir, "mock/x2")) // and setup-snap c.Check(ops[1].op, Equals, "setup-snap") c.Check(ops[1].name, Matches, `.*/mock_1.0_all.snap`) @@ -2785,11 +2829,11 @@ }) c.Check(ops[3].op, Equals, "unlink-snap") - c.Check(ops[3].name, Equals, filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "mock/x2")) + c.Check(ops[3].name, Equals, filepath.Join(dirs.SnapMountDir, "mock/x2")) c.Check(ops[4].op, Equals, "copy-data") - c.Check(ops[4].name, Equals, filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "mock/x3")) - c.Check(ops[4].old, Equals, filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "mock/x2")) + c.Check(ops[4].name, Equals, filepath.Join(dirs.SnapMountDir, "mock/x3")) + c.Check(ops[4].old, Equals, filepath.Join(dirs.SnapMountDir, "mock/x2")) c.Check(ops[5].op, Equals, "setup-profiles:Doing") c.Check(ops[5].name, Equals, "mock") @@ -2801,7 +2845,7 @@ Revision: snap.R(-3), }) c.Check(ops[7].op, Equals, "link-snap") - c.Check(ops[7].name, Equals, filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "mock/x3")) + c.Check(ops[7].name, Equals, filepath.Join(dirs.SnapMountDir, "mock/x3")) c.Check(ops[8].op, Equals, "setup-profiles:Doing") // core phase 2 // verify snapSetup info @@ -2864,7 +2908,7 @@ { // ensure only local install was run, i.e. first action is pseudo-action current op: "current", - old: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "mock/100001"), + old: filepath.Join(dirs.SnapMountDir, "mock/100001"), }, { // and setup-snap @@ -2878,12 +2922,12 @@ }, { op: "unlink-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "mock/100001"), + name: filepath.Join(dirs.SnapMountDir, "mock/100001"), }, { op: "copy-data", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "mock/x1"), - old: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "mock/100001"), + name: filepath.Join(dirs.SnapMountDir, "mock/x1"), + old: filepath.Join(dirs.SnapMountDir, "mock/100001"), }, { op: "setup-profiles:Doing", @@ -2899,7 +2943,7 @@ }, { op: "link-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "mock/x1"), + name: filepath.Join(dirs.SnapMountDir, "mock/x1"), }, { op: "setup-profiles:Doing", @@ -2970,7 +3014,7 @@ c.Check(s.fakeBackend.ops[4].op, Equals, "candidate") c.Check(s.fakeBackend.ops[4].sinfo, DeepEquals, *si) c.Check(s.fakeBackend.ops[5].op, Equals, "link-snap") - c.Check(s.fakeBackend.ops[5].name, Equals, filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/42")) + c.Check(s.fakeBackend.ops[5].name, Equals, filepath.Join(dirs.SnapMountDir, "some-snap/42")) // verify snapSetup info var snapsup snapstate.SnapSetup @@ -3031,7 +3075,7 @@ }, { op: "unlink-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/7"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/7"), }, { op: "remove-profiles:Doing", @@ -3040,15 +3084,15 @@ }, { op: "remove-snap-data", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/7"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/7"), }, { op: "remove-snap-common-data", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/7"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/7"), }, { op: "remove-snap-files", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/7"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/7"), stype: "app", }, { @@ -3141,7 +3185,7 @@ }, { op: "unlink-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/7"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/7"), }, { op: "remove-profiles:Doing", @@ -3150,33 +3194,33 @@ }, { op: "remove-snap-data", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/7"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/7"), }, { op: "remove-snap-files", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/7"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/7"), stype: "app", }, { op: "remove-snap-data", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/3"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/3"), }, { op: "remove-snap-files", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/3"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/3"), stype: "app", }, { op: "remove-snap-data", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/5"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/5"), }, { op: "remove-snap-common-data", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/5"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/5"), }, { op: "remove-snap-files", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/5"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/5"), stype: "app", }, { @@ -3272,11 +3316,11 @@ expected := fakeOps{ { op: "remove-snap-data", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/3"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/3"), }, { op: "remove-snap-files", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/3"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/3"), stype: "app", }, } @@ -3337,15 +3381,15 @@ expected := fakeOps{ { op: "remove-snap-data", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/2"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/2"), }, { op: "remove-snap-common-data", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/2"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/2"), }, { op: "remove-snap-files", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/2"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/2"), stype: "app", }, { @@ -3726,27 +3770,27 @@ expectedTail := fakeOps{ { op: "link-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/11"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/11"), }, { op: "update-aliases", }, { op: "remove-snap-data", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/1"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/1"), }, { op: "remove-snap-files", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/1"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/1"), stype: "app", }, { op: "remove-snap-data", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/2"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/2"), }, { op: "remove-snap-files", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/2"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/2"), stype: "app", }, { @@ -3891,7 +3935,7 @@ }, { op: "unlink-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/7"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/7"), }, { op: "setup-profiles:Doing", @@ -3907,7 +3951,7 @@ }, { op: "link-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/2"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/2"), }, { op: "update-aliases", @@ -4019,7 +4063,7 @@ }, { op: "unlink-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/2"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/2"), }, { op: "setup-profiles:Doing", @@ -4032,7 +4076,7 @@ }, { op: "link-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/7"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/7"), }, { op: "update-aliases", @@ -4100,7 +4144,7 @@ }, { op: "unlink-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/2"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/2"), }, { op: "setup-profiles:Doing", @@ -4116,7 +4160,7 @@ }, { op: "link-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/1"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/1"), }, { op: "update-aliases", @@ -4128,7 +4172,7 @@ }, { op: "unlink-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/1"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/1"), }, { op: "setup-profiles:Undoing", @@ -4137,7 +4181,7 @@ }, { op: "link-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/2"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/2"), }, { op: "update-aliases", @@ -4182,7 +4226,7 @@ c.Assert(err, IsNil) chg.AddAll(ts) - s.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/1") + s.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.SnapMountDir, "some-snap/1") s.state.Unlock() defer s.snapmgr.Stop() @@ -4196,7 +4240,7 @@ }, { op: "unlink-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/2"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/2"), }, { op: "setup-profiles:Doing", @@ -4212,12 +4256,12 @@ }, { op: "link-snap.failed", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/1"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/1"), }, // undo stuff here { op: "unlink-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/1"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/1"), }, { op: "setup-profiles:Undoing", @@ -4226,7 +4270,7 @@ }, { op: "link-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/2"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/2"), }, { op: "update-aliases", @@ -4318,7 +4362,7 @@ }, { op: "link-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/7"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/7"), }, { op: "update-aliases", @@ -4375,7 +4419,7 @@ }, { op: "unlink-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/7"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/7"), }, { op: "remove-profiles:Doing", @@ -4395,6 +4439,48 @@ c.Assert(snapst.AliasesPending, Equals, true) } +func (s *snapmgrTestSuite) TestSwitchRunThrough(c *C) { + si := snap.SideInfo{ + RealName: "some-snap", + Revision: snap.R(7), + Channel: "edge", + SnapID: "foo", + } + + s.state.Lock() + defer s.state.Unlock() + + snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ + Sequence: []*snap.SideInfo{&si}, + Current: si.Revision, + Channel: "edge", + }) + + chg := s.state.NewChange("switch-snap", "switch snap to some-channel") + ts, err := snapstate.Switch(s.state, "some-snap", "some-channel") + c.Assert(err, IsNil) + chg.AddAll(ts) + + s.state.Unlock() + defer s.snapmgr.Stop() + s.settle() + s.state.Lock() + + // switch is not really really doing anything backend related + c.Assert(s.fakeBackend.ops, HasLen, 0) + + // ensure the desired channel has changed + var snapst snapstate.SnapState + err = snapstate.Get(s.state, "some-snap", &snapst) + c.Assert(err, IsNil) + c.Assert(snapst.Channel, Equals, "some-channel") + + // ensure the current info has not changed + info, err := snapst.CurrentInfo() + c.Assert(err, IsNil) + c.Assert(info.Channel, Equals, "edge") +} + func (s *snapmgrTestSuite) TestDisableDoesNotEnableAgain(c *C) { si := snap.SideInfo{ RealName: "some-snap", @@ -4424,7 +4510,7 @@ c.Assert(err, IsNil) chg.AddAll(ts) - s.fakeBackend.copySnapDataFailTrigger = filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/11") + s.fakeBackend.copySnapDataFailTrigger = filepath.Join(dirs.SnapMountDir, "some-snap/11") s.state.Unlock() defer s.snapmgr.Stop() @@ -4452,7 +4538,7 @@ }, { op: "open-snap-file", - name: "/var/lib/snapd/snaps/some-snap_11.snap", + name: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"), sinfo: snap.SideInfo{ RealName: "some-snap", SnapID: "snapIDsnapidsnapidsnapidsnapidsn", @@ -4462,17 +4548,17 @@ }, { op: "setup-snap", - name: "/var/lib/snapd/snaps/some-snap_11.snap", + name: filepath.Join(dirs.SnapBlobDir, "some-snap_11.snap"), revno: snap.R(11), }, { op: "copy-data.failed", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/11"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/11"), old: "", }, { op: "undo-setup-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/11"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/11"), stype: "app", }, } @@ -4517,7 +4603,7 @@ c.Assert(err, IsNil) chg.AddAll(ts) - s.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/11") + s.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.SnapMountDir, "some-snap/11") s.state.Unlock() defer s.snapmgr.Stop() @@ -4542,6 +4628,7 @@ ERROR fail set-auto-aliases: Hold setup-aliases: Hold +run-hook: Hold start-snap-services: Hold cleanup: Hold run-hook: Hold`) @@ -4555,6 +4642,7 @@ ERROR fail set-auto-aliases: Hold setup-aliases: Hold +run-hook: Hold start-snap-services: Hold cleanup: Hold run-hook: Hold`) @@ -5115,6 +5203,10 @@ c.Assert(err, IsNil) c.Assert(snapStates, HasLen, 1) + n, err := snapstate.NumSnaps(st) + c.Assert(err, IsNil) + c.Check(n, Equals, 1) + snapst := snapStates["name1"] c.Assert(snapst, NotNil) @@ -5147,17 +5239,29 @@ c.Assert(err, IsNil) c.Check(snapStates, HasLen, 0) + n, err := snapstate.NumSnaps(st) + c.Assert(err, IsNil) + c.Check(n, Equals, 0) + snapstate.Set(st, "foo", nil) snapStates, err = snapstate.All(st) c.Assert(err, IsNil) c.Check(snapStates, HasLen, 0) + n, err = snapstate.NumSnaps(st) + c.Assert(err, IsNil) + c.Check(n, Equals, 0) + snapstate.Set(st, "foo", &snapstate.SnapState{}) snapStates, err = snapstate.All(st) c.Assert(err, IsNil) c.Check(snapStates, HasLen, 0) + + n, err = snapstate.NumSnaps(st) + c.Assert(err, IsNil) + c.Check(n, Equals, 0) } func (s *snapmgrTestSuite) TestTrySetsTryMode(c *C) { @@ -5216,9 +5320,9 @@ "setup-profiles", "set-auto-aliases", "setup-aliases", - "run-hook", + "run-hook[install]", "start-snap-services", - "run-hook", + "run-hook[configure]", }) } @@ -5501,8 +5605,8 @@ c.Check(s.fakeBackend.ops.Count("copy-data"), Equals, 1) c.Check(s.fakeBackend.ops.First("copy-data"), DeepEquals, &fakeOp{ op: "copy-data", - name: fmt.Sprintf(filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/%d"), opts.via), - old: fmt.Sprintf(filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/%d"), opts.current), + name: fmt.Sprintf(filepath.Join(dirs.SnapMountDir, "some-snap/%d"), opts.via), + old: fmt.Sprintf(filepath.Join(dirs.SnapMountDir, "some-snap/%d"), opts.current), }) return ts @@ -5511,7 +5615,7 @@ func (s *snapmgrTestSuite) testUpdateFailureSequence(c *C, opts *opSeqOpts) *state.TaskSet { opts.revert = false opts.after = opts.before - s.fakeBackend.linkSnapFailTrigger = fmt.Sprintf(filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/%d"), opts.via) + s.fakeBackend.linkSnapFailTrigger = fmt.Sprintf(filepath.Join(dirs.SnapMountDir, "some-snap/%d"), opts.via) snapst, ts := s.testOpSequence(c, opts) // a failed update will always end with current unchanged c.Check(snapst.Current.N, Equals, opts.current) @@ -5565,7 +5669,7 @@ func (s *snapmgrTestSuite) testRevertFailureSequence(c *C, opts *opSeqOpts) *state.TaskSet { opts.revert = true opts.after = opts.before - s.fakeBackend.linkSnapFailTrigger = fmt.Sprintf(filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/%d"), opts.via) + s.fakeBackend.linkSnapFailTrigger = fmt.Sprintf(filepath.Join(dirs.SnapMountDir, "some-snap/%d"), opts.via) snapst, ts := s.testOpSequence(c, opts) // a failed revert will always end with current unchanged c.Check(snapst.Current.N, Equals, opts.current) @@ -5793,12 +5897,12 @@ }, { op: "unlink-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/11"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/11"), }, { op: "copy-data", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/7"), - old: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/11"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/7"), + old: filepath.Join(dirs.SnapMountDir, "some-snap/11"), }, { op: "setup-profiles:Doing", @@ -5816,7 +5920,7 @@ }, { op: "link-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "some-snap/7"), + name: filepath.Join(dirs.SnapMountDir, "some-snap/7"), }, { op: "update-aliases", @@ -5902,7 +6006,7 @@ for _, ts := range tts { c.Assert(taskKinds(ts.Tasks()), DeepEquals, []string{ "stop-snap-services", - "run-hook", + "run-hook[remove]", "remove-aliases", "unlink-snap", "remove-profiles", @@ -5955,8 +6059,6 @@ func (s *snapmgrTestSuite) TestConfigDefaults(c *C) { r := release.MockOnClassic(false) defer r() - dirs.SetRootDir(c.MkDir()) - defer dirs.SetRootDir("") // using MockSnap, we want to read the bits on disk snapstate.MockReadInfo(snap.ReadInfo) @@ -6008,10 +6110,6 @@ bootloader: grub `) - // change root dir for this test, this can't be done in SetUpTest as it affects all other tests. - dirs.SetRootDir(c.MkDir()) - defer dirs.SetRootDir("/") - info := snaptest.MockSnap(c, mockGadgetSnapYaml, "SNAP", &snap.SideInfo{Revision: snap.R(2)}) err := ioutil.WriteFile(filepath.Join(info.MountDir(), "meta", "gadget.yaml"), mockGadgetYaml, 0644) c.Assert(err, IsNil) @@ -6040,8 +6138,6 @@ func (s *snapmgrTestSuite) TestGadgetDefaults(c *C) { r := release.MockOnClassic(false) defer r() - dirs.SetRootDir(c.MkDir()) - defer dirs.SetRootDir("") // using MockSnap, we want to read the bits on disk snapstate.MockReadInfo(snap.ReadInfo) @@ -6070,8 +6166,6 @@ func (s *snapmgrTestSuite) TestInstallPathSkipConfigure(c *C) { r := release.MockOnClassic(false) defer r() - dirs.SetRootDir(c.MkDir()) - defer dirs.SetRootDir("") // using MockSnap, we want to read the bits on disk snapstate.MockReadInfo(snap.ReadInfo) @@ -6094,9 +6188,6 @@ } func (s *snapmgrTestSuite) TestGadgetDefaultsInstalled(c *C) { - dirs.SetRootDir(c.MkDir()) - defer dirs.SetRootDir("") - // using MockSnap, we want to read the bits on disk snapstate.MockReadInfo(snap.ReadInfo) @@ -6258,7 +6349,7 @@ }, { op: "open-snap-file", - name: "/var/lib/snapd/snaps/core_11.snap", + name: filepath.Join(dirs.SnapBlobDir, "core_11.snap"), sinfo: snap.SideInfo{ RealName: "core", SnapID: "snapIDsnapidsnapidsnapidsnapidsn", @@ -6267,12 +6358,12 @@ }, { op: "setup-snap", - name: "/var/lib/snapd/snaps/core_11.snap", + name: filepath.Join(dirs.SnapBlobDir, "core_11.snap"), revno: snap.R(11), }, { op: "copy-data", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "core/11"), + name: filepath.Join(dirs.SnapMountDir, "core/11"), old: "", }, { @@ -6290,7 +6381,7 @@ }, { op: "link-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "core/11"), + name: filepath.Join(dirs.SnapMountDir, "core/11"), }, { op: "setup-profiles:Doing", @@ -6310,7 +6401,7 @@ }, { op: "unlink-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "ubuntu-core/1"), + name: filepath.Join(dirs.SnapMountDir, "ubuntu-core/1"), }, { op: "remove-profiles:Doing", @@ -6319,15 +6410,15 @@ }, { op: "remove-snap-data", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "ubuntu-core/1"), + name: filepath.Join(dirs.SnapMountDir, "ubuntu-core/1"), }, { op: "remove-snap-common-data", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "ubuntu-core/1"), + name: filepath.Join(dirs.SnapMountDir, "ubuntu-core/1"), }, { op: "remove-snap-files", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "ubuntu-core/1"), + name: filepath.Join(dirs.SnapMountDir, "ubuntu-core/1"), stype: "os", }, { @@ -6397,7 +6488,7 @@ }, { op: "unlink-snap", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "ubuntu-core/1"), + name: filepath.Join(dirs.SnapMountDir, "ubuntu-core/1"), }, { op: "remove-profiles:Doing", @@ -6406,15 +6497,15 @@ }, { op: "remove-snap-data", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "ubuntu-core/1"), + name: filepath.Join(dirs.SnapMountDir, "ubuntu-core/1"), }, { op: "remove-snap-common-data", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "ubuntu-core/1"), + name: filepath.Join(dirs.SnapMountDir, "ubuntu-core/1"), }, { op: "remove-snap-files", - name: filepath.Join(dirs.StripRootDir(dirs.SnapMountDir), "ubuntu-core/1"), + name: filepath.Join(dirs.SnapMountDir, "ubuntu-core/1"), stype: "os", }, { @@ -6788,6 +6879,45 @@ c.Check(t.Status(), Equals, state.ErrorStatus) } +func (s *snapmgrTestSuite) TestConflictMany(c *C) { + s.state.Lock() + defer s.state.Unlock() + + for _, snapName := range []string{"a-snap", "b-snap"} { + snapstate.Set(s.state, snapName, &snapstate.SnapState{ + Sequence: []*snap.SideInfo{ + {RealName: snapName, Revision: snap.R(11)}, + }, + Current: snap.R(11), + Active: false, + }) + + ts, err := snapstate.Enable(s.state, snapName) + c.Assert(err, IsNil) + // need a change to make the tasks visible + s.state.NewChange("enable", "...").AddAll(ts) + } + + // things that should be ok: + for _, m := range [][]string{ + {}, //nothing + {"c-snap"}, + {"c-snap", "d-snap", "e-snap", "f-snap"}, + } { + c.Check(snapstate.CheckChangeConflictMany(s.state, m, nil), IsNil) + } + + // things that should not be ok: + for _, m := range [][]string{ + {"a-snap"}, + {"a-snap", "b-snap"}, + {"a-snap", "c-snap"}, + {"b-snap", "c-snap"}, + } { + c.Check(snapstate.CheckChangeConflictMany(s.state, m, nil), ErrorMatches, `snap "[^"]*" has changes in progress`) + } +} + type canDisableSuite struct{} var _ = Suite(&canDisableSuite{}) diff -Nru snapd-2.27.5/packaging/arch/PKGBUILD snapd-2.28.5/packaging/arch/PKGBUILD --- snapd-2.27.5/packaging/arch/PKGBUILD 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/packaging/arch/PKGBUILD 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,130 @@ +# $Id$ +# Maintainer: Timothy Redaelli +# Contributor: Zygmunt Krynicki + +pkgbase=snapd +pkgname=snapd +pkgver=2.26.14.r1445.g3df03f98b +pkgrel=1 +arch=('i686' 'x86_64') +url="https://github.com/snapcore/snapd" +license=('GPL3') +makedepends=('git' 'go' 'go-tools' 'libseccomp' 'libcap' 'python-docutils' 'systemd' 'xfsprogs' 'libseccomp') +checkdepends=('python' 'squashfs-tools' 'indent' 'shellcheck') + +options=('!strip' 'emptydirs') +install=snapd.install +source=("git+https://github.com/snapcore/$pkgname.git") +md5sums=('SKIP') + +_gourl=github.com/snapcore/snapd + + +pkgver() { + cd "$srcdir/snapd" + git describe --tag | sed -r 's/([^-]*-g)/r\1/; s/-/./g' +} + +prepare() { + cd "$pkgname" + + # Use $srcdir/go as our GOPATH + export GOPATH="$srcdir/go" + mkdir -p "$GOPATH" + # Have snapd checkout appear in a place suitable for subsequent GOPATH This + # way we don't have to go get it again and it is exactly what the tag/hash + # above describes. + mkdir -p "$(dirname "$GOPATH/src/${_gourl}")" + ln --no-target-directory -fs "$srcdir/$pkgname" "$GOPATH/src/${_gourl}" + # Patch snap-seccomp build flags not to link libseccomp statically. + sed -i -e 's/-Wl,-Bstatic -lseccomp -Wl,-Bdynamic/-lseccomp/' "$srcdir/$pkgname/cmd/snap-seccomp/main.go" +} + +build() { + export GOPATH="$srcdir/go" + # Use get-deps.sh provided by upstream to fetch go dependencies using the + # godeps tool and dependencies.tsv (maintained upstream). + cd "$GOPATH/src/${_gourl}" + # Generate version + ./mkversion.sh $pkgver-$pkgrel + + # Get golang dependencies + XDG_CONFIG_HOME="$srcdir" ./get-deps.sh + + # Generate the real systemd units out of the available templates + make -C data/systemd all + + # Build/install snap and snapd + go install "${_gourl}/cmd/snap" + go install "${_gourl}/cmd/snapctl" + go install "${_gourl}/cmd/snapd" + go install "${_gourl}/cmd/snap-update-ns" + go install "${_gourl}/cmd/snap-seccomp" + + # Build snap-confine + cd cmd + autoreconf -i -f + ./configure \ + --prefix=/usr \ + --libexecdir=/usr/lib/snapd \ + --with-snap-mount-dir=/var/lib/snapd/snap \ + --disable-apparmor \ + --enable-nvidia-arch \ + --enable-merged-usr + make +} + +check() { + export GOPATH="$srcdir/go" + cd "$GOPATH/src/${_gourl}" + + # XXX: Those files are unknown to gitignore but are checked by run-checks + # --static. Before gitignore is updated we just remove the junk one and move + # the valuable one aside. + rm -f cmd/snap-confine/snap-confine-debug + mv data/info $srcdir/xxx-info + + ./run-checks --unit + # XXX: Static checks choke on autotools generated cruft. Let's not run them + # here as they are designed to pass on a clean tree, before anything else is + # done, not after building the tree. + # ./run-checks --static + make -C cmd -k check + + mv $srcdir/xxx-info data/info +} + +package() { + pkgdesc="Service and tools for management of snap packages." + depends=('snap-confine' 'squashfs-tools' 'libseccomp' 'libsystemd') + replaces=('snap-confine') + provides=('snap-confine') + + export GOPATH="$srcdir/go" + # Ensure that we have /var/lib/snapd/{hostfs,lib/gl}/ as they are required by snap-confine + # for constructing some bind mounts around. + install -d -m 755 "$pkgdir/var/lib/snapd/hostfs/" "$pkgdir/var/lib/snapd/lib/gl/" + # Install the refresh timer and service for updating snaps + install -d -m 755 "$pkgdir/usr/lib/systemd/system/" + install -m 644 "$GOPATH/src/${_gourl}/data/systemd/snapd.refresh.service" "$pkgdir/usr/lib/systemd/system" + install -m 644 "$GOPATH/src/${_gourl}/data/systemd/snapd.refresh.timer" "$pkgdir/usr/lib/systemd/system" + # Install the snapd socket and service for the main daemon + install -m 644 "$GOPATH/src/${_gourl}/data/systemd/snapd.service" "$pkgdir/usr/lib/systemd/system" + install -m 644 "$GOPATH/src/${_gourl}/data/systemd/snapd.socket" "$pkgdir/usr/lib/systemd/system" + # Install snap, snapctl, snap-update-ns, snap-seccomp and snapd executables + install -d -m 755 "$pkgdir/usr/bin/" + install -m 755 "$GOPATH/bin/snap" "$pkgdir/usr/bin/" + install -m 755 "$GOPATH/bin/snapctl" "$pkgdir/usr/bin/" + install -d -m 755 "$pkgdir/usr/lib/snapd" + install -m 755 "$GOPATH/bin/snap-update-ns" "$pkgdir/usr/lib/snapd/" + install -m 755 "$GOPATH/bin/snap-seccomp" "$pkgdir/usr/lib/snapd/" + install -m 755 "$GOPATH/bin/snapd" "$pkgdir/usr/lib/snapd/" + # Install snap-confine + make -C "$srcdir/$pkgbase/cmd" install DESTDIR="$pkgdir/" + # Install script to export binaries paths of snaps and XDG_DATA_DIRS for their .desktop files + make -C "$srcdir/$pkgbase/data/env" install DESTDIR="$pkgdir" + # Install the bash tab completion files + install -Dm 755 "$GOPATH/src/${_gourl}/data/completion/snap" "$pkgdir/usr/share/bash-completion/completions/snap" + install -Dm 755 "$GOPATH/src/${_gourl}/data/completion/complete.sh" "$pkgdir/usr/lib/snapd/complete.sh" + install -Dm 755 "$GOPATH/src/${_gourl}/data/completion/etelpmoc.sh" "$pkgdir/usr/lib/snapd/etelpmoc.sh" +} diff -Nru snapd-2.27.5/packaging/arch/snapd.install snapd-2.28.5/packaging/arch/snapd.install --- snapd-2.27.5/packaging/arch/snapd.install 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/packaging/arch/snapd.install 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,15 @@ +## arg 1: the new package version +post_install() { + echo + echo 'To use snapd start/enable the snapd.socket' + echo + echo 'If you want your apps to be automatically updated' + echo 'from the store start/enable the snapd.refresh.timer' + echo + echo 'NOTE: Desktop entries show up after logging in again' + echo ' or rebooting after snapd installation' + echo + echo 'For more informations, see https://wiki.archlinux.org/index.php/Snapd' +} + +# vim:set ts=2 sw=2 et: diff -Nru snapd-2.27.5/packaging/fedora/snapd.spec snapd-2.28.5/packaging/fedora/snapd.spec --- snapd-2.27.5/packaging/fedora/snapd.spec 2017-08-30 05:32:20.000000000 +0000 +++ snapd-2.28.5/packaging/fedora/snapd.spec 2017-10-13 21:25:46.000000000 +0000 @@ -48,7 +48,7 @@ %global snappy_svcs snapd.service snapd.socket snapd.autoimport.service snapd.refresh.timer snapd.refresh.service Name: snapd -Version: 2.27.5 +Version: 2.28.5 Release: 0%{?dist} Summary: A transactional software package manager Group: System Environment/Base @@ -71,7 +71,6 @@ # If go_compiler is not set to 1, there is no virtual provide. Use golang instead. BuildRequires: %{?go_compiler:compiler(go-compiler)}%{!?go_compiler:golang} BuildRequires: systemd -BuildRequires: libseccomp-static %{?systemd_requires} Requires: snap-confine%{?_isa} = %{version}-%{release} @@ -87,6 +86,8 @@ %if ! 0%{?with_bundled} BuildRequires: golang(github.com/cheggaaa/pb) BuildRequires: golang(github.com/coreos/go-systemd/activation) +BuildRequires: golang(github.com/godbus/dbus) +BuildRequires: golang(github.com/godbus/dbus/introspect) BuildRequires: golang(github.com/gorilla/mux) BuildRequires: golang(github.com/jessevdk/go-flags) BuildRequires: golang(github.com/mvo5/uboot-go/uenv) @@ -129,6 +130,7 @@ BuildRequires: pkgconfig(udev) BuildRequires: xfsprogs-devel BuildRequires: glibc-static +BuildRequires: libseccomp-static BuildRequires: valgrind BuildRequires: %{_bindir}/rst2man %if 0%{?fedora} >= 25 @@ -171,6 +173,8 @@ %if ! 0%{?with_bundled} Requires: golang(github.com/cheggaaa/pb) Requires: golang(github.com/coreos/go-systemd/activation) +Requires: golang(github.com/godbus/dbus) +Requires: golang(github.com/godbus/dbus/introspect) Requires: golang(github.com/gorilla/mux) Requires: golang(github.com/jessevdk/go-flags) Requires: golang(github.com/mvo5/uboot-go/uenv) @@ -194,6 +198,8 @@ # *sigh*... I hate golang... Provides: bundled(golang(github.com/cheggaaa/pb)) Provides: bundled(golang(github.com/coreos/go-systemd/activation)) +Provides: bundled(golang(github.com/godbus/dbus)) +Provides: bundled(golang(github.com/godbus/dbus/introspect)) Provides: bundled(golang(github.com/gorilla/mux)) Provides: bundled(golang(github.com/jessevdk/go-flags)) Provides: bundled(golang(github.com/mvo5/uboot-go/uenv)) @@ -249,6 +255,7 @@ Provides: golang(%{import_path}/overlord) = %{version}-%{release} Provides: golang(%{import_path}/overlord/assertstate) = %{version}-%{release} Provides: golang(%{import_path}/overlord/auth) = %{version}-%{release} +Provides: golang(%{import_path}/overlord/cmdstate) = %{version}-%{release} Provides: golang(%{import_path}/overlord/configstate) = %{version}-%{release} Provides: golang(%{import_path}/overlord/configstate/config) = %{version}-%{release} Provides: golang(%{import_path}/overlord/devicestate) = %{version}-%{release} @@ -261,9 +268,10 @@ Provides: golang(%{import_path}/overlord/snapstate/backend) = %{version}-%{release} Provides: golang(%{import_path}/overlord/state) = %{version}-%{release} Provides: golang(%{import_path}/partition) = %{version}-%{release} +Provides: golang(%{import_path}/partition/androidbootenv) = %{version}-%{release} Provides: golang(%{import_path}/partition/grubenv) = %{version}-%{release} +Provides: golang(%{import_path}/partition/ubootenv) = %{version}-%{release} Provides: golang(%{import_path}/progress) = %{version}-%{release} -Provides: golang(%{import_path}/provisioning) = %{version}-%{release} Provides: golang(%{import_path}/release) = %{version}-%{release} Provides: golang(%{import_path}/snap) = %{version}-%{release} Provides: golang(%{import_path}/snap/snapdir) = %{version}-%{release} @@ -277,7 +285,9 @@ Provides: golang(%{import_path}/tests/lib/fakestore/store) = %{version}-%{release} Provides: golang(%{import_path}/testutil) = %{version}-%{release} Provides: golang(%{import_path}/timeout) = %{version}-%{release} +Provides: golang(%{import_path}/timeutil) = %{version}-%{release} Provides: golang(%{import_path}/wrappers) = %{version}-%{release} +Provides: golang(%{import_path}/x11) = %{version}-%{release} %description devel @@ -320,6 +330,16 @@ %prep %setup -q +%if ! 0%{?with_bundled} +# Ensure there's no bundled stuff accidentally leaking in... +rm -rf vendor/* + +# XXX: HACK: Fake that we have the right import path because bad testing +# did not verify that this path was actually valid on all supported systems. +mkdir -p vendor/gopkg.in/cheggaaa +ln -s %{gopath}/src/github.com/cheggaaa/pb vendor/gopkg.in/cheggaaa/pb.v1 + +%endif %build # Generate version files @@ -345,13 +365,14 @@ # set tags. %gobuild -o bin/snapd $GOFLAGS %{import_path}/cmd/snapd %gobuild -o bin/snap $GOFLAGS %{import_path}/cmd/snap -%gobuild -o bin/snap-exec $GOFLAGS %{import_path}/cmd/snap-exec %gobuild -o bin/snapctl $GOFLAGS %{import_path}/cmd/snapctl %gobuild -o bin/snap-update-ns $GOFLAGS %{import_path}/cmd/snap-update-ns +# build snap-exec completely static for base snaps +CGO_ENABLED=0 %gobuild -o bin/snap-exec $GOFLAGS %{import_path}/cmd/snap-exec # We don't need mvo5 fork for seccomp, as we have seccomp 2.3.x sed -e "s:github.com/mvo5/libseccomp-golang:github.com/seccomp/libseccomp-golang:g" -i cmd/snap-seccomp/*.go -%gobuild -o bin/snap-seccomp %{import_path}/cmd/snap-seccomp +%gobuild -o bin/snap-seccomp $GOFLAGS %{import_path}/cmd/snap-seccomp # Build SELinux module pushd ./data/selinux @@ -380,13 +401,16 @@ popd # Build systemd units -pushd ./data/systemd +pushd ./data/ make BINDIR="%{_bindir}" LIBEXECDIR="%{_libexecdir}" \ SYSTEMDSYSTEMUNITDIR="%{_unitdir}" \ SNAP_MOUNT_DIR="%{_sharedstatedir}/snapd/snap" \ SNAPD_ENVIRONMENT_FILE="%{_sysconfdir}/sysconfig/snapd" popd +# Build environ-tweaking snippet +make -C data/env SNAP_MOUNT_DIR="%{_sharedstatedir}/snapd/snap" + %install install -d -p %{buildroot}%{_bindir} install -d -p %{buildroot}%{_libexecdir}/snapd @@ -426,6 +450,8 @@ # Install bash completion for "snap" install -m 644 -D data/completion/snap %{buildroot}%{_datadir}/bash-completion/completions/snap +install -m 644 -D data/completion/complete.sh %{buildroot}%{_libexecdir}/snapd +install -m 644 -D data/completion/etelpmoc.sh %{buildroot}%{_libexecdir}/snapd # Install snap-confine pushd ./cmd @@ -439,28 +465,21 @@ popd # Install all systemd units -pushd ./data/systemd +pushd ./data/ %make_install SYSTEMDSYSTEMUNITDIR="%{_unitdir}" BINDIR="%{_bindir}" LIBEXECDIR="%{_libexecdir}" # Remove snappy core specific units rm -fv %{buildroot}%{_unitdir}/snapd.system-shutdown.service -rm -fv %{buildroot}%{_unitdir}/snap-repair.* +rm -fv %{buildroot}%{_unitdir}/snapd.snap-repair.* rm -fv %{buildroot}%{_unitdir}/snapd.core-fixup.* popd # Remove snappy core specific scripts rm %{buildroot}%{_libexecdir}/snapd/snapd.core-fixup.sh -# Put /var/lib/snapd/snap/bin on PATH -# Put /var/lib/snapd/desktop on XDG_DATA_DIRS -cat << __SNAPD_SH__ > %{buildroot}%{_sysconfdir}/profile.d/snapd.sh -PATH=\$PATH:/var/lib/snapd/snap/bin -if [ -z "\$XDG_DATA_DIRS" ]; then - XDG_DATA_DIRS=/usr/share/:/usr/local/share/:/var/lib/snapd/desktop -else - XDG_DATA_DIRS="\$XDG_DATA_DIRS":/var/lib/snapd/desktop -fi -export XDG_DATA_DIRS -__SNAPD_SH__ +# Install environ-tweaking snippet +pushd ./data/env +%make_install +popd # Disable re-exec by default echo 'SNAP_REEXEC=0' > %{buildroot}%{_sysconfdir}/sysconfig/snapd @@ -535,6 +554,8 @@ %{_libexecdir}/snapd/snap-mgmt %{_mandir}/man1/snap.1* %{_datadir}/bash-completion/completions/snap +%{_libexecdir}/snapd/complete.sh +%{_libexecdir}/snapd/etelpmoc.sh %{_sysconfdir}/profile.d/snapd.sh %{_unitdir}/snapd.socket %{_unitdir}/snapd.service @@ -556,6 +577,7 @@ %ghost %dir %{_sharedstatedir}/snapd/snap/bin %dir %{_localstatedir}/snap %ghost %{_sharedstatedir}/snapd/state.json +%{_datadir}/dbus-1/services/io.snapcraft.Launcher.service %files -n snap-confine %doc cmd/snap-confine/PORTING @@ -636,6 +658,313 @@ %changelog +* Fri Oct 13 2017 Michael Vogt +- New upstream release 2.28.5 + - snap-confine: cleanup broken nvidia udev tags + - cmd/snap-confine: update valid security tag regexp + - overlord/ifacestate: refresh udev backend on startup + - dbus: ensure io.snapcraft.Launcher.service is created on re- + exec + - snap-confine: add support for handling /dev/nvidia-modeset + - interfaces/network-control: remove incorrect rules for tun + +* Wed Oct 11 2017 Michael Vogt +- New upstream release 2.28.4 + - interfaces/opengl: don't udev tag nvidia devices and use snap- + confine instead + - debian: fix replaces/breaks for snap-xdg-open (thanks to apw!) + +* Wed Oct 11 2017 Michael Vogt +- New upstream release 2.28.3 + - interfaces/lxd: lxd slot implementation can also be an app + snap + +* Tue Oct 10 2017 Michael Vogt +- New upstream release 2.28.2 + - interfaces: fix udev rules for tun + - release,cmd,dirs: Redo the distro checks to take into account + distribution families + +* Wed Sep 27 2017 Michael Vogt +- New upstream release 2.28.1 + - snap-confine: update apparmor rules for fedora based basesnaps + - snapstate: rename refresh hook to post-refresh for consistency + +* Mon Sep 25 2017 Michael Vogt +- New upstream release 2.28 + - hooks: rename refresh to after-refresh + - snap-confine: bind mount /usr/lib/snapd relative to snap-confine + - cmd,dirs: treat "liri" the same way as "arch" + - snap-confine: fix base snaps on core + - hooks: substitute env vars when executing hooks + - interfaces: updates for default, browser-support, desktop, opengl, + upower and stub-resolv.conf + - cmd,dirs: treat manjaro the same as arch + - systemd: do not run auto-import and repair services on classic + - packaging/fedora: Ensure vendor/ is empty for builds and fix spec + to build current master + - many: fix TestSetConfNumber missing an Unlock and other fragility + improvements + - osutil: adjust StreamCommand tests for golang 1.9 + - daemon: allow polkit authorisation to install/remove snaps + - tests: make TestCmdWatch more robust + - debian: improve package description + - interfaces: add netlink kobject uevent to hardware observe + - debian: update trusted account-keys check on 14.04 packaging + - interfaces/network-{control,observe}: allow receiving + kobject_uevent() messages + - tests: fix lxd test for external backend + - snap-confine,snap-update-ns: add -no-pie to fix FTBFS on + go1.7,ppc64 + - corecfg: mock "systemctl" in all corecfg tests + - tests: fix unit tests on Ubuntu 14.04 + - debian: add missing flags when building static snap-exec + - many: end-to-end support for the bare base snap + - overlord/snapstate: SetRootDir from SetUpTest, not in just some + tests + - store: have an ad-hoc method on cfg to get its list of uris for + tests + - daemon: let client decide whether to allow interactive auth via + polkit + - client,daemon,snap,store: add license field + - overlord/snapstate: rename HasCurrent to IsInstalled, remove + superfluous/misleading check from All + - cmd/snap: SetRootDir from SetUpTest, not in just some individual + tests. + - systemd: rename snap-repair.{service,timer} to snapd.snap- + repair.{service,timer} + - snap-seccomp: remove use of x/net/bpf from tests + - httputil: more naive per go version way to recreate a default + transport for tls reconfig + - cmd/snap-seccomp/main_test.go: add one more syscall for arm64 + - interfaces/opengl: use == to compare, not = + - cmd/snap-seccomp/main_test.go: add syscalls for armhf and arm64 + - cmd/snap-repair: track and use a lower bound for the time for + TLS checks + - interfaces: expose bluez interface on classic OS + - snap-seccomp: add in-kernel bpf tests + - overlord: always try to get a serial, lazily on classic + - tests: add nmcli regression test + - tests: deal with __PNR_chown on aarch64 to fix FTBFS on arm64 + - tests: add autopilot-introspection interface test + - vendor: fix artifact from manually editing vendor/vendor.json + - tests: rename complexion to test-snapd-complexion + - interfaces: add desktop and desktop-legacy + interfaces/desktop: add new 'desktop' interface for modern DEs* + interfaces/builtin/desktop_test.go: use modern testing techniques* + interfaces/wayland: allow read on /etc/drirc for Plasma desktop* + interfaces/desktop-legacy: add new 'legacy' interface (currently + for a11y and input) + - tests: fix race in snap userd test + - devices/iio: add read/write for missing sysfs entries + - spread: don't set HTTPS?_PROXY for linode + - cmd/snap-repair: check signatures of repairs from Next + - env: set XDG_DATA_DIRS for wayland et.al. + - interfaces/{default,account-control}: Use username/group instead + of uid/gid + - interfaces/builtin: use udev tagging more broadly + - tests: add basic lxd test + - wrappers: ensure bash completion snaps install on core + - vendor: use old golang.org/x/crypto/ssh/terminal to build on + powerpc again + - docs: add PULL_REQUEST_TEMPLATE.md + - interfaces: fix network-manager plug + - hooks: do not error out when hook is optional and no hook handler + is registered + - cmd/snap: add userd command to replace snapd-xdg-open + - tests: new regex used to validate the core version on extra snaps + ass... + - snap: add new `snap switch` command + - tests: wait more and more debug info about fakestore start issues + - apparmor,release: add better apparmor detection/mocking code + - interfaces/i2c: adjust sysfs rule for alternate paths + - interfaces/apparmor: add missing call to dirs.SetRootDir + - cmd: "make hack" now also installs snap-update-ns + - tests: copy files with less verbosity + - cmd/snap-confine: allow using additional libraries required by + openSUSE + - packaging/fedora: Merge changes from Fedora Dist-Git + - snapstate: improve the error message when classic confinement is + not supported + - tests: add test to ensure amd64 can run i386 syscall binaries + - tests: adding extra info for fakestore when fails to start + - tests: install most important snaps + - cmd/snap-repair: more test coverage of filtering + - squashfs: remove runCommand/runCommandWithOutput as we do not need + it + - cmd/snap-repair: ignore superseded revisions, filter on arch and + models + - hooks: support for refresh hook + - Partial revert "overlord/devicestate, store: update device auth + endpoints URLs" + - cmd/snap-confine: allow reading /proc/filesystems + - cmd/snap-confine: genearlize apparmor profile for various lib + layout + - corecfg: fix proxy.* writing and add integration test + - corecfg: deal with system.power-key-action="" correctly + - vendor: update vendor.json after (presumed) manual edits + - cmd/snap: in `snap info`, don't print a newline between tracks + - daemon: add polkit support to /v2/login + - snapd,snapctl: decode json using Number + - client: fix go vet 1.7 errors + - tests: make 17.04 shellcheck clean + - tests: remove TestInterfacesHelp as it breaks when go-flags + changes + - snapstate: undo a daemon restart on classic if needed + - cmd/snap-repair: recover brand/model from + /var/lib/snapd/seed/assertions checking signatures and brand + account + - spread: opt into unsafe IO during spread tests + - snap-repair: update snap-repair/runner_test.go for API change in + makeMockServer + - cmd/snap-repair: skeleton code around actually running a repair + - tests: wait until the port is listening after start the fake store + - corecfg: fix typo in tests + - cmd/snap-repair: test that redirects works during fetching + - osutil: honor SNAPD_UNSAFE_IO for testing + - vendor: explode and make more precise our golang.go/x/crypto deps, + use same version as Debian unstable + - many: sanitize NewStoreStack signature, have shared default store + test private keys + - systemd: disable `Nice=-5` to fix error when running inside lxd + - spread.yaml: update delta ref to 2.27 + - cmd/snap-repair: use E-Tags when refetching a repair to retry + - interfaces/many: updates based on chromium and mrrescue denials + - cmd/snap-repair: implement most logic to get the next repair to + run/retry in a brand sequence + - asserts/assertstest: copy headers in SigningDB.Sign + - interfaces: convert uhid to common interface and test cases + improvement for time_control and opengl + - many tests: move all panicing fake store methods to a common place + - asserts: add store assertion type + - interfaces: don't crash if content slot has no attributes + - debian: do not build with -buildmode=pie on i386 + - wrappers: symlink completion snippets when symlinking binaries + - tests: adding more debug information for the interfaces-cups- + control … + - apparmor: pass --quiet to parser on load unless SNAPD_DEBUG is set + - many: allow and support serials signed by the 'generic' authority + instead of the brand + - corecfg: add proxy configuration via `snap set core + proxy.{http,https,ftp}=...` + - interfaces: a bunch of interfaces test improvement + - tests: enable regression and completion suites for opensuse + - tests: installing snapd for nested test suite + - interfaces: convert lxd_support to common iface + - interfaces: add missing test for camera interface. + - snap: add support for parsing snap layout section + - cmd/snap-repair: like for downloads we cannot have a timeout (at + least for now), less aggressive retry strategies + - overlord: rely on more conservative ensure interval + - overlord,store: no piles of return args for methods gathering + device session request params + - overlord,store: send model assertion when setting up device + sessions + - interfaces/misc: updates for unity7/x11, browser- + support, network-control and mount-observe + interfaces/unity7,x11: update for NETLINK_KOBJECT_UEVENT + interfaces/browser-support: update sysfs reads for + newer browser versions, interfaces/network-control: rw for + ieee80211 advanced wireless interfaces/mount-observe: allow read + on sysfs entries for block devices + - tests: use dnf --refresh install to avert stale cache + - osutil: ensure TestLockUnlockWorks uses supported flock + - interfaces: convert lxd to common iface + - tests: restart snapd to ensure re-exec settings are applied + - tests: fix interfaces-cups-control test + - interfaces: improve and tweak bunch of interfaces test cases. + - tests: adding extra worker for fedora + - asserts,overlord/devicestate: support predefined assertions that + don't establish foundational trust + - interfaces: convert two hardware_random interfaces to common iface + - interfaces: convert io_ports_control to common iface + - tests: fix for upgrade test on fedora + - daemon, client, cmd/snap: implement snap start/stop/restart + - cmd/snap-confine: set _FILE_OFFSET_BITS to 64 + - interfaces: covert framebuffer to commonInterface + - interfaces: convert joystick to common iface + - interfaces/builtin: add the spi interface + - wrappers, overlord/snapstate/backend: make link-snap clean up on + failure. + - interfaces/wayland: add wayland interface + - interfaces: convert kvm to common iface + - tests: extend upower-observe test to cover snaps providing slots + - tests: enable main suite for opensuse + - interfaces: convert physical_memory_observe to common iface + - interfaces: add missing test for optical_drive interface. + - interfaces: convert physical_memory_control to common iface + - interfaces: convert ppp to common iface + - interfaces: convert time-control to common iface + - tests: fix failover test + - interfaces/builtin: rework for avahi interface + - interfaces: convert broadcom-asic-control to common iface + - snap/snapenv: document the use of CoreSnapMountDir for SNAP + - packaging/arch: drop patches merged into master + - cmd: fix mustUnsetenv docstring (thanks to Chipaca) + - release: remove default from VERSION_ID + - tests: enable regression, upgrade and completion test suites for + fedora + - tests: restore interfaces-account-control properly + - overlord/devicestate, store: update device auth endpoints URLs + - tests: fix install-hook test failure + - tests: download core and ubuntu-core at most once + - interfaces: add common support for udev + - overlord/devicestate: fix, don't assume that the serial is backed + by a 1-key chain + - cmd/snap-confine: don't share /etc/nsswitch from host + - store: do not resume a download when we already have the whole + thing + - many: implement "snap logs" + - store: don't call useDeltas() twice in quick succession + - interfaces/builtin: add kvm interface + - snap/snapenv: always expect /snap for $SNAP + - cmd: mark arch as non-reexecing distro + - cmd: fix tests that assume /snap mount + - gitignore: ignore more build artefacts + - packaging: add current arch packaging + - interfaces/unity7: allow receiving media key events in (at least) + gnome-shell + - interfaces/many, cmd/snap-confine: miscellaneous policy updates + - interfaces/builtin: implement broadcom-asic-control interface + - interfaces/builtin: reduce duplication and remove cruft in + Sanitize{Plug,Slot} + - tests: apply underscore convention for SNAPMOUNTDIR variable + - interfaces/greengrass-support: adjust accesses now that have + working snap + - daemon, client, cmd/snap: implement "snap services" + - tests: fix refresh tests not stopping fake store for fedora + - many: add the interface command + - overlord/snapstate/backend: some copydata improvements + - many: support querying and completing assertion type names + - interfaces/builtin: discard empty Validate{Plug,Slot} + - cmd/snap-repair: start of Runner, implement first pass of Peek + and Fetch + - tests: enable main suite on fedora + - snap: do not always quote the snap info summary + - vendor: update go-flags to address crash in "snap debug" + - interfaces: opengl support pci device and vendor + - many: start implenting "base" snap type on the snapd side + - arch,release: map armv6 correctly + - many: expose service status in 'snap info' + - tests: add browser-support interface test + - tests: disable snapd-notify for the external backend + - interfaces: Add /run/uuid/request to openvswitch + - interfaces: add password-manager-service implicit classic + interface + - cmd: rework reexec detection + - cmd: fix re-exec bug when starting from snapd 2.21 + - tests: dependency packages installed during prepare-project + - tests: remove unneeded check for re-exec in InternalToolPath() + - cmd,tests: fix classic confinement confusing re-execution code + - store: configurable base api + - tests: fix how package lists are updated for opensuse and fedora + +* Thu Sep 07 2017 Michael Vogt +- New upstream release 2.27.6 + - interfaces: add udev netlink support to hardware-observe + - interfaces/network-{control,observe}: allow receiving + kobject_uevent() messages + * Wed Aug 30 2017 Michael Vogt - New upstream release 2.27.5 - interfaces: fix network-manager plug regression @@ -654,6 +983,12 @@ - systemd: disable `Nice=-5` to fix error when running inside lxdSee https://bugs.launchpad.net/snapd/+bug/1709536 +* Wed Aug 16 2017 Neal Gompa - 2.27.2-2 +- Bump to rebuild for F27 and Rawhide + +* Wed Aug 16 2017 Neal Gompa - 2.27.2-1 +- Release 2.27.2 to Fedora (RH#1482173) + * Wed Aug 16 2017 Michael Vogt - New upstream release 2.27.2 - tests: remove TestInterfacesHelp as it breaks when go-flags @@ -665,6 +1000,9 @@ - store: do not resume a download when we already have the whole thing +* Mon Aug 14 2017 Neal Gompa - 2.27.1-1 +- Release 2.27.1 to Fedora (RH#1481247) + * Mon Aug 14 2017 Michael Vogt - New upstream release 2.27.1 - tests: use dnf --refresh install to avert stale cache @@ -679,6 +1017,9 @@ - interfaces/mount-observe: allow read on sysfs entries for block devices +* Thu Aug 10 2017 Neal Gompa - 2.27-1 +- Release 2.27 to Fedora (RH#1458086) + * Thu Aug 10 2017 Michael Vogt - New upstream release 2.27 - fix build failure on 32bit fedora @@ -942,6 +1283,12 @@ - api, ifacestate: resolve disconnect early - interfaces/builtin: ensure we don't register interfaces twice +* Thu Aug 03 2017 Fedora Release Engineering - 2.26.3-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Binutils_Mass_Rebuild + +* Thu Jul 27 2017 Fedora Release Engineering - 2.26.3-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild + * Thu May 25 2017 Neal Gompa - 2.26.3-3 - Cover even more stuff for proper erasure on final uninstall (RH#1444422) diff -Nru snapd-2.27.5/packaging/fedora/snap-mgmt.sh snapd-2.28.5/packaging/fedora/snap-mgmt.sh --- snapd-2.27.5/packaging/fedora/snap-mgmt.sh 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/packaging/fedora/snap-mgmt.sh 2017-09-13 14:47:18.000000000 +0000 @@ -66,7 +66,7 @@ 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 -rf "${SNAP_MOUNT_DIR:?}/$snap/$rev" rm -f "${SNAP_MOUNT_DIR}/$snap/current" # snap data dir rm -rf "/var/snap/$snap/$rev" @@ -97,8 +97,8 @@ rm -rf /var/lib/snapd/snaps/* echo "Final directory cleanup" - rm -rf "${SNAP_MOUNT_DIR}"/* - rm -rf /var/snap/* + rm -rf "${SNAP_MOUNT_DIR}" + rm -rf /var/snap echo "Removing leftover snap shared state data" rm -rf /var/lib/snapd/desktop/applications/* @@ -115,7 +115,7 @@ ;; --snap-mount-dir=*) SNAP_MOUNT_DIR=${1#*=} - SNAP_UNIT_PREFIX="$(systemd-escape -p $SNAP_MOUNT_DIR)" + SNAP_UNIT_PREFIX=$(systemd-escape -p "$SNAP_MOUNT_DIR") shift ;; --purge) diff -Nru snapd-2.27.5/packaging/fedora-25/snapd.spec snapd-2.28.5/packaging/fedora-25/snapd.spec --- snapd-2.27.5/packaging/fedora-25/snapd.spec 2017-08-30 05:32:20.000000000 +0000 +++ snapd-2.28.5/packaging/fedora-25/snapd.spec 2017-10-13 21:25:46.000000000 +0000 @@ -48,7 +48,7 @@ %global snappy_svcs snapd.service snapd.socket snapd.autoimport.service snapd.refresh.timer snapd.refresh.service Name: snapd -Version: 2.27.5 +Version: 2.28.5 Release: 0%{?dist} Summary: A transactional software package manager Group: System Environment/Base @@ -71,7 +71,6 @@ # If go_compiler is not set to 1, there is no virtual provide. Use golang instead. BuildRequires: %{?go_compiler:compiler(go-compiler)}%{!?go_compiler:golang} BuildRequires: systemd -BuildRequires: libseccomp-static %{?systemd_requires} Requires: snap-confine%{?_isa} = %{version}-%{release} @@ -87,6 +86,8 @@ %if ! 0%{?with_bundled} BuildRequires: golang(github.com/cheggaaa/pb) BuildRequires: golang(github.com/coreos/go-systemd/activation) +BuildRequires: golang(github.com/godbus/dbus) +BuildRequires: golang(github.com/godbus/dbus/introspect) BuildRequires: golang(github.com/gorilla/mux) BuildRequires: golang(github.com/jessevdk/go-flags) BuildRequires: golang(github.com/mvo5/uboot-go/uenv) @@ -129,6 +130,7 @@ BuildRequires: pkgconfig(udev) BuildRequires: xfsprogs-devel BuildRequires: glibc-static +BuildRequires: libseccomp-static BuildRequires: valgrind BuildRequires: %{_bindir}/rst2man %if 0%{?fedora} >= 25 @@ -171,6 +173,8 @@ %if ! 0%{?with_bundled} Requires: golang(github.com/cheggaaa/pb) Requires: golang(github.com/coreos/go-systemd/activation) +Requires: golang(github.com/godbus/dbus) +Requires: golang(github.com/godbus/dbus/introspect) Requires: golang(github.com/gorilla/mux) Requires: golang(github.com/jessevdk/go-flags) Requires: golang(github.com/mvo5/uboot-go/uenv) @@ -194,6 +198,8 @@ # *sigh*... I hate golang... Provides: bundled(golang(github.com/cheggaaa/pb)) Provides: bundled(golang(github.com/coreos/go-systemd/activation)) +Provides: bundled(golang(github.com/godbus/dbus)) +Provides: bundled(golang(github.com/godbus/dbus/introspect)) Provides: bundled(golang(github.com/gorilla/mux)) Provides: bundled(golang(github.com/jessevdk/go-flags)) Provides: bundled(golang(github.com/mvo5/uboot-go/uenv)) @@ -249,6 +255,7 @@ Provides: golang(%{import_path}/overlord) = %{version}-%{release} Provides: golang(%{import_path}/overlord/assertstate) = %{version}-%{release} Provides: golang(%{import_path}/overlord/auth) = %{version}-%{release} +Provides: golang(%{import_path}/overlord/cmdstate) = %{version}-%{release} Provides: golang(%{import_path}/overlord/configstate) = %{version}-%{release} Provides: golang(%{import_path}/overlord/configstate/config) = %{version}-%{release} Provides: golang(%{import_path}/overlord/devicestate) = %{version}-%{release} @@ -261,9 +268,10 @@ Provides: golang(%{import_path}/overlord/snapstate/backend) = %{version}-%{release} Provides: golang(%{import_path}/overlord/state) = %{version}-%{release} Provides: golang(%{import_path}/partition) = %{version}-%{release} +Provides: golang(%{import_path}/partition/androidbootenv) = %{version}-%{release} Provides: golang(%{import_path}/partition/grubenv) = %{version}-%{release} +Provides: golang(%{import_path}/partition/ubootenv) = %{version}-%{release} Provides: golang(%{import_path}/progress) = %{version}-%{release} -Provides: golang(%{import_path}/provisioning) = %{version}-%{release} Provides: golang(%{import_path}/release) = %{version}-%{release} Provides: golang(%{import_path}/snap) = %{version}-%{release} Provides: golang(%{import_path}/snap/snapdir) = %{version}-%{release} @@ -277,7 +285,9 @@ Provides: golang(%{import_path}/tests/lib/fakestore/store) = %{version}-%{release} Provides: golang(%{import_path}/testutil) = %{version}-%{release} Provides: golang(%{import_path}/timeout) = %{version}-%{release} +Provides: golang(%{import_path}/timeutil) = %{version}-%{release} Provides: golang(%{import_path}/wrappers) = %{version}-%{release} +Provides: golang(%{import_path}/x11) = %{version}-%{release} %description devel @@ -320,6 +330,16 @@ %prep %setup -q +%if ! 0%{?with_bundled} +# Ensure there's no bundled stuff accidentally leaking in... +rm -rf vendor/* + +# XXX: HACK: Fake that we have the right import path because bad testing +# did not verify that this path was actually valid on all supported systems. +mkdir -p vendor/gopkg.in/cheggaaa +ln -s %{gopath}/src/github.com/cheggaaa/pb vendor/gopkg.in/cheggaaa/pb.v1 + +%endif %build # Generate version files @@ -345,13 +365,14 @@ # set tags. %gobuild -o bin/snapd $GOFLAGS %{import_path}/cmd/snapd %gobuild -o bin/snap $GOFLAGS %{import_path}/cmd/snap -%gobuild -o bin/snap-exec $GOFLAGS %{import_path}/cmd/snap-exec %gobuild -o bin/snapctl $GOFLAGS %{import_path}/cmd/snapctl %gobuild -o bin/snap-update-ns $GOFLAGS %{import_path}/cmd/snap-update-ns +# build snap-exec completely static for base snaps +CGO_ENABLED=0 %gobuild -o bin/snap-exec $GOFLAGS %{import_path}/cmd/snap-exec # We don't need mvo5 fork for seccomp, as we have seccomp 2.3.x sed -e "s:github.com/mvo5/libseccomp-golang:github.com/seccomp/libseccomp-golang:g" -i cmd/snap-seccomp/*.go -%gobuild -o bin/snap-seccomp %{import_path}/cmd/snap-seccomp +%gobuild -o bin/snap-seccomp $GOFLAGS %{import_path}/cmd/snap-seccomp # Build SELinux module pushd ./data/selinux @@ -380,13 +401,16 @@ popd # Build systemd units -pushd ./data/systemd +pushd ./data/ make BINDIR="%{_bindir}" LIBEXECDIR="%{_libexecdir}" \ SYSTEMDSYSTEMUNITDIR="%{_unitdir}" \ SNAP_MOUNT_DIR="%{_sharedstatedir}/snapd/snap" \ SNAPD_ENVIRONMENT_FILE="%{_sysconfdir}/sysconfig/snapd" popd +# Build environ-tweaking snippet +make -C data/env SNAP_MOUNT_DIR="%{_sharedstatedir}/snapd/snap" + %install install -d -p %{buildroot}%{_bindir} install -d -p %{buildroot}%{_libexecdir}/snapd @@ -426,6 +450,8 @@ # Install bash completion for "snap" install -m 644 -D data/completion/snap %{buildroot}%{_datadir}/bash-completion/completions/snap +install -m 644 -D data/completion/complete.sh %{buildroot}%{_libexecdir}/snapd +install -m 644 -D data/completion/etelpmoc.sh %{buildroot}%{_libexecdir}/snapd # Install snap-confine pushd ./cmd @@ -439,28 +465,21 @@ popd # Install all systemd units -pushd ./data/systemd +pushd ./data/ %make_install SYSTEMDSYSTEMUNITDIR="%{_unitdir}" BINDIR="%{_bindir}" LIBEXECDIR="%{_libexecdir}" # Remove snappy core specific units rm -fv %{buildroot}%{_unitdir}/snapd.system-shutdown.service -rm -fv %{buildroot}%{_unitdir}/snap-repair.* +rm -fv %{buildroot}%{_unitdir}/snapd.snap-repair.* rm -fv %{buildroot}%{_unitdir}/snapd.core-fixup.* popd # Remove snappy core specific scripts rm %{buildroot}%{_libexecdir}/snapd/snapd.core-fixup.sh -# Put /var/lib/snapd/snap/bin on PATH -# Put /var/lib/snapd/desktop on XDG_DATA_DIRS -cat << __SNAPD_SH__ > %{buildroot}%{_sysconfdir}/profile.d/snapd.sh -PATH=\$PATH:/var/lib/snapd/snap/bin -if [ -z "\$XDG_DATA_DIRS" ]; then - XDG_DATA_DIRS=/usr/share/:/usr/local/share/:/var/lib/snapd/desktop -else - XDG_DATA_DIRS="\$XDG_DATA_DIRS":/var/lib/snapd/desktop -fi -export XDG_DATA_DIRS -__SNAPD_SH__ +# Install environ-tweaking snippet +pushd ./data/env +%make_install +popd # Disable re-exec by default echo 'SNAP_REEXEC=0' > %{buildroot}%{_sysconfdir}/sysconfig/snapd @@ -535,6 +554,8 @@ %{_libexecdir}/snapd/snap-mgmt %{_mandir}/man1/snap.1* %{_datadir}/bash-completion/completions/snap +%{_libexecdir}/snapd/complete.sh +%{_libexecdir}/snapd/etelpmoc.sh %{_sysconfdir}/profile.d/snapd.sh %{_unitdir}/snapd.socket %{_unitdir}/snapd.service @@ -556,6 +577,7 @@ %ghost %dir %{_sharedstatedir}/snapd/snap/bin %dir %{_localstatedir}/snap %ghost %{_sharedstatedir}/snapd/state.json +%{_datadir}/dbus-1/services/io.snapcraft.Launcher.service %files -n snap-confine %doc cmd/snap-confine/PORTING @@ -636,6 +658,313 @@ %changelog +* Fri Oct 13 2017 Michael Vogt +- New upstream release 2.28.5 + - snap-confine: cleanup broken nvidia udev tags + - cmd/snap-confine: update valid security tag regexp + - overlord/ifacestate: refresh udev backend on startup + - dbus: ensure io.snapcraft.Launcher.service is created on re- + exec + - snap-confine: add support for handling /dev/nvidia-modeset + - interfaces/network-control: remove incorrect rules for tun + +* Wed Oct 11 2017 Michael Vogt +- New upstream release 2.28.4 + - interfaces/opengl: don't udev tag nvidia devices and use snap- + confine instead + - debian: fix replaces/breaks for snap-xdg-open (thanks to apw!) + +* Wed Oct 11 2017 Michael Vogt +- New upstream release 2.28.3 + - interfaces/lxd: lxd slot implementation can also be an app + snap + +* Tue Oct 10 2017 Michael Vogt +- New upstream release 2.28.2 + - interfaces: fix udev rules for tun + - release,cmd,dirs: Redo the distro checks to take into account + distribution families + +* Wed Sep 27 2017 Michael Vogt +- New upstream release 2.28.1 + - snap-confine: update apparmor rules for fedora based basesnaps + - snapstate: rename refresh hook to post-refresh for consistency + +* Mon Sep 25 2017 Michael Vogt +- New upstream release 2.28 + - hooks: rename refresh to after-refresh + - snap-confine: bind mount /usr/lib/snapd relative to snap-confine + - cmd,dirs: treat "liri" the same way as "arch" + - snap-confine: fix base snaps on core + - hooks: substitute env vars when executing hooks + - interfaces: updates for default, browser-support, desktop, opengl, + upower and stub-resolv.conf + - cmd,dirs: treat manjaro the same as arch + - systemd: do not run auto-import and repair services on classic + - packaging/fedora: Ensure vendor/ is empty for builds and fix spec + to build current master + - many: fix TestSetConfNumber missing an Unlock and other fragility + improvements + - osutil: adjust StreamCommand tests for golang 1.9 + - daemon: allow polkit authorisation to install/remove snaps + - tests: make TestCmdWatch more robust + - debian: improve package description + - interfaces: add netlink kobject uevent to hardware observe + - debian: update trusted account-keys check on 14.04 packaging + - interfaces/network-{control,observe}: allow receiving + kobject_uevent() messages + - tests: fix lxd test for external backend + - snap-confine,snap-update-ns: add -no-pie to fix FTBFS on + go1.7,ppc64 + - corecfg: mock "systemctl" in all corecfg tests + - tests: fix unit tests on Ubuntu 14.04 + - debian: add missing flags when building static snap-exec + - many: end-to-end support for the bare base snap + - overlord/snapstate: SetRootDir from SetUpTest, not in just some + tests + - store: have an ad-hoc method on cfg to get its list of uris for + tests + - daemon: let client decide whether to allow interactive auth via + polkit + - client,daemon,snap,store: add license field + - overlord/snapstate: rename HasCurrent to IsInstalled, remove + superfluous/misleading check from All + - cmd/snap: SetRootDir from SetUpTest, not in just some individual + tests. + - systemd: rename snap-repair.{service,timer} to snapd.snap- + repair.{service,timer} + - snap-seccomp: remove use of x/net/bpf from tests + - httputil: more naive per go version way to recreate a default + transport for tls reconfig + - cmd/snap-seccomp/main_test.go: add one more syscall for arm64 + - interfaces/opengl: use == to compare, not = + - cmd/snap-seccomp/main_test.go: add syscalls for armhf and arm64 + - cmd/snap-repair: track and use a lower bound for the time for + TLS checks + - interfaces: expose bluez interface on classic OS + - snap-seccomp: add in-kernel bpf tests + - overlord: always try to get a serial, lazily on classic + - tests: add nmcli regression test + - tests: deal with __PNR_chown on aarch64 to fix FTBFS on arm64 + - tests: add autopilot-introspection interface test + - vendor: fix artifact from manually editing vendor/vendor.json + - tests: rename complexion to test-snapd-complexion + - interfaces: add desktop and desktop-legacy + interfaces/desktop: add new 'desktop' interface for modern DEs* + interfaces/builtin/desktop_test.go: use modern testing techniques* + interfaces/wayland: allow read on /etc/drirc for Plasma desktop* + interfaces/desktop-legacy: add new 'legacy' interface (currently + for a11y and input) + - tests: fix race in snap userd test + - devices/iio: add read/write for missing sysfs entries + - spread: don't set HTTPS?_PROXY for linode + - cmd/snap-repair: check signatures of repairs from Next + - env: set XDG_DATA_DIRS for wayland et.al. + - interfaces/{default,account-control}: Use username/group instead + of uid/gid + - interfaces/builtin: use udev tagging more broadly + - tests: add basic lxd test + - wrappers: ensure bash completion snaps install on core + - vendor: use old golang.org/x/crypto/ssh/terminal to build on + powerpc again + - docs: add PULL_REQUEST_TEMPLATE.md + - interfaces: fix network-manager plug + - hooks: do not error out when hook is optional and no hook handler + is registered + - cmd/snap: add userd command to replace snapd-xdg-open + - tests: new regex used to validate the core version on extra snaps + ass... + - snap: add new `snap switch` command + - tests: wait more and more debug info about fakestore start issues + - apparmor,release: add better apparmor detection/mocking code + - interfaces/i2c: adjust sysfs rule for alternate paths + - interfaces/apparmor: add missing call to dirs.SetRootDir + - cmd: "make hack" now also installs snap-update-ns + - tests: copy files with less verbosity + - cmd/snap-confine: allow using additional libraries required by + openSUSE + - packaging/fedora: Merge changes from Fedora Dist-Git + - snapstate: improve the error message when classic confinement is + not supported + - tests: add test to ensure amd64 can run i386 syscall binaries + - tests: adding extra info for fakestore when fails to start + - tests: install most important snaps + - cmd/snap-repair: more test coverage of filtering + - squashfs: remove runCommand/runCommandWithOutput as we do not need + it + - cmd/snap-repair: ignore superseded revisions, filter on arch and + models + - hooks: support for refresh hook + - Partial revert "overlord/devicestate, store: update device auth + endpoints URLs" + - cmd/snap-confine: allow reading /proc/filesystems + - cmd/snap-confine: genearlize apparmor profile for various lib + layout + - corecfg: fix proxy.* writing and add integration test + - corecfg: deal with system.power-key-action="" correctly + - vendor: update vendor.json after (presumed) manual edits + - cmd/snap: in `snap info`, don't print a newline between tracks + - daemon: add polkit support to /v2/login + - snapd,snapctl: decode json using Number + - client: fix go vet 1.7 errors + - tests: make 17.04 shellcheck clean + - tests: remove TestInterfacesHelp as it breaks when go-flags + changes + - snapstate: undo a daemon restart on classic if needed + - cmd/snap-repair: recover brand/model from + /var/lib/snapd/seed/assertions checking signatures and brand + account + - spread: opt into unsafe IO during spread tests + - snap-repair: update snap-repair/runner_test.go for API change in + makeMockServer + - cmd/snap-repair: skeleton code around actually running a repair + - tests: wait until the port is listening after start the fake store + - corecfg: fix typo in tests + - cmd/snap-repair: test that redirects works during fetching + - osutil: honor SNAPD_UNSAFE_IO for testing + - vendor: explode and make more precise our golang.go/x/crypto deps, + use same version as Debian unstable + - many: sanitize NewStoreStack signature, have shared default store + test private keys + - systemd: disable `Nice=-5` to fix error when running inside lxd + - spread.yaml: update delta ref to 2.27 + - cmd/snap-repair: use E-Tags when refetching a repair to retry + - interfaces/many: updates based on chromium and mrrescue denials + - cmd/snap-repair: implement most logic to get the next repair to + run/retry in a brand sequence + - asserts/assertstest: copy headers in SigningDB.Sign + - interfaces: convert uhid to common interface and test cases + improvement for time_control and opengl + - many tests: move all panicing fake store methods to a common place + - asserts: add store assertion type + - interfaces: don't crash if content slot has no attributes + - debian: do not build with -buildmode=pie on i386 + - wrappers: symlink completion snippets when symlinking binaries + - tests: adding more debug information for the interfaces-cups- + control … + - apparmor: pass --quiet to parser on load unless SNAPD_DEBUG is set + - many: allow and support serials signed by the 'generic' authority + instead of the brand + - corecfg: add proxy configuration via `snap set core + proxy.{http,https,ftp}=...` + - interfaces: a bunch of interfaces test improvement + - tests: enable regression and completion suites for opensuse + - tests: installing snapd for nested test suite + - interfaces: convert lxd_support to common iface + - interfaces: add missing test for camera interface. + - snap: add support for parsing snap layout section + - cmd/snap-repair: like for downloads we cannot have a timeout (at + least for now), less aggressive retry strategies + - overlord: rely on more conservative ensure interval + - overlord,store: no piles of return args for methods gathering + device session request params + - overlord,store: send model assertion when setting up device + sessions + - interfaces/misc: updates for unity7/x11, browser- + support, network-control and mount-observe + interfaces/unity7,x11: update for NETLINK_KOBJECT_UEVENT + interfaces/browser-support: update sysfs reads for + newer browser versions, interfaces/network-control: rw for + ieee80211 advanced wireless interfaces/mount-observe: allow read + on sysfs entries for block devices + - tests: use dnf --refresh install to avert stale cache + - osutil: ensure TestLockUnlockWorks uses supported flock + - interfaces: convert lxd to common iface + - tests: restart snapd to ensure re-exec settings are applied + - tests: fix interfaces-cups-control test + - interfaces: improve and tweak bunch of interfaces test cases. + - tests: adding extra worker for fedora + - asserts,overlord/devicestate: support predefined assertions that + don't establish foundational trust + - interfaces: convert two hardware_random interfaces to common iface + - interfaces: convert io_ports_control to common iface + - tests: fix for upgrade test on fedora + - daemon, client, cmd/snap: implement snap start/stop/restart + - cmd/snap-confine: set _FILE_OFFSET_BITS to 64 + - interfaces: covert framebuffer to commonInterface + - interfaces: convert joystick to common iface + - interfaces/builtin: add the spi interface + - wrappers, overlord/snapstate/backend: make link-snap clean up on + failure. + - interfaces/wayland: add wayland interface + - interfaces: convert kvm to common iface + - tests: extend upower-observe test to cover snaps providing slots + - tests: enable main suite for opensuse + - interfaces: convert physical_memory_observe to common iface + - interfaces: add missing test for optical_drive interface. + - interfaces: convert physical_memory_control to common iface + - interfaces: convert ppp to common iface + - interfaces: convert time-control to common iface + - tests: fix failover test + - interfaces/builtin: rework for avahi interface + - interfaces: convert broadcom-asic-control to common iface + - snap/snapenv: document the use of CoreSnapMountDir for SNAP + - packaging/arch: drop patches merged into master + - cmd: fix mustUnsetenv docstring (thanks to Chipaca) + - release: remove default from VERSION_ID + - tests: enable regression, upgrade and completion test suites for + fedora + - tests: restore interfaces-account-control properly + - overlord/devicestate, store: update device auth endpoints URLs + - tests: fix install-hook test failure + - tests: download core and ubuntu-core at most once + - interfaces: add common support for udev + - overlord/devicestate: fix, don't assume that the serial is backed + by a 1-key chain + - cmd/snap-confine: don't share /etc/nsswitch from host + - store: do not resume a download when we already have the whole + thing + - many: implement "snap logs" + - store: don't call useDeltas() twice in quick succession + - interfaces/builtin: add kvm interface + - snap/snapenv: always expect /snap for $SNAP + - cmd: mark arch as non-reexecing distro + - cmd: fix tests that assume /snap mount + - gitignore: ignore more build artefacts + - packaging: add current arch packaging + - interfaces/unity7: allow receiving media key events in (at least) + gnome-shell + - interfaces/many, cmd/snap-confine: miscellaneous policy updates + - interfaces/builtin: implement broadcom-asic-control interface + - interfaces/builtin: reduce duplication and remove cruft in + Sanitize{Plug,Slot} + - tests: apply underscore convention for SNAPMOUNTDIR variable + - interfaces/greengrass-support: adjust accesses now that have + working snap + - daemon, client, cmd/snap: implement "snap services" + - tests: fix refresh tests not stopping fake store for fedora + - many: add the interface command + - overlord/snapstate/backend: some copydata improvements + - many: support querying and completing assertion type names + - interfaces/builtin: discard empty Validate{Plug,Slot} + - cmd/snap-repair: start of Runner, implement first pass of Peek + and Fetch + - tests: enable main suite on fedora + - snap: do not always quote the snap info summary + - vendor: update go-flags to address crash in "snap debug" + - interfaces: opengl support pci device and vendor + - many: start implenting "base" snap type on the snapd side + - arch,release: map armv6 correctly + - many: expose service status in 'snap info' + - tests: add browser-support interface test + - tests: disable snapd-notify for the external backend + - interfaces: Add /run/uuid/request to openvswitch + - interfaces: add password-manager-service implicit classic + interface + - cmd: rework reexec detection + - cmd: fix re-exec bug when starting from snapd 2.21 + - tests: dependency packages installed during prepare-project + - tests: remove unneeded check for re-exec in InternalToolPath() + - cmd,tests: fix classic confinement confusing re-execution code + - store: configurable base api + - tests: fix how package lists are updated for opensuse and fedora + +* Thu Sep 07 2017 Michael Vogt +- New upstream release 2.27.6 + - interfaces: add udev netlink support to hardware-observe + - interfaces/network-{control,observe}: allow receiving + kobject_uevent() messages + * Wed Aug 30 2017 Michael Vogt - New upstream release 2.27.5 - interfaces: fix network-manager plug regression @@ -654,6 +983,12 @@ - systemd: disable `Nice=-5` to fix error when running inside lxdSee https://bugs.launchpad.net/snapd/+bug/1709536 +* Wed Aug 16 2017 Neal Gompa - 2.27.2-2 +- Bump to rebuild for F27 and Rawhide + +* Wed Aug 16 2017 Neal Gompa - 2.27.2-1 +- Release 2.27.2 to Fedora (RH#1482173) + * Wed Aug 16 2017 Michael Vogt - New upstream release 2.27.2 - tests: remove TestInterfacesHelp as it breaks when go-flags @@ -665,6 +1000,9 @@ - store: do not resume a download when we already have the whole thing +* Mon Aug 14 2017 Neal Gompa - 2.27.1-1 +- Release 2.27.1 to Fedora (RH#1481247) + * Mon Aug 14 2017 Michael Vogt - New upstream release 2.27.1 - tests: use dnf --refresh install to avert stale cache @@ -679,6 +1017,9 @@ - interfaces/mount-observe: allow read on sysfs entries for block devices +* Thu Aug 10 2017 Neal Gompa - 2.27-1 +- Release 2.27 to Fedora (RH#1458086) + * Thu Aug 10 2017 Michael Vogt - New upstream release 2.27 - fix build failure on 32bit fedora @@ -942,6 +1283,12 @@ - api, ifacestate: resolve disconnect early - interfaces/builtin: ensure we don't register interfaces twice +* Thu Aug 03 2017 Fedora Release Engineering - 2.26.3-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Binutils_Mass_Rebuild + +* Thu Jul 27 2017 Fedora Release Engineering - 2.26.3-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild + * Thu May 25 2017 Neal Gompa - 2.26.3-3 - Cover even more stuff for proper erasure on final uninstall (RH#1444422) diff -Nru snapd-2.27.5/packaging/fedora-25/snap-mgmt.sh snapd-2.28.5/packaging/fedora-25/snap-mgmt.sh --- snapd-2.27.5/packaging/fedora-25/snap-mgmt.sh 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/packaging/fedora-25/snap-mgmt.sh 2017-09-13 14:47:18.000000000 +0000 @@ -66,7 +66,7 @@ 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 -rf "${SNAP_MOUNT_DIR:?}/$snap/$rev" rm -f "${SNAP_MOUNT_DIR}/$snap/current" # snap data dir rm -rf "/var/snap/$snap/$rev" @@ -97,8 +97,8 @@ rm -rf /var/lib/snapd/snaps/* echo "Final directory cleanup" - rm -rf "${SNAP_MOUNT_DIR}"/* - rm -rf /var/snap/* + rm -rf "${SNAP_MOUNT_DIR}" + rm -rf /var/snap echo "Removing leftover snap shared state data" rm -rf /var/lib/snapd/desktop/applications/* @@ -115,7 +115,7 @@ ;; --snap-mount-dir=*) SNAP_MOUNT_DIR=${1#*=} - SNAP_UNIT_PREFIX="$(systemd-escape -p $SNAP_MOUNT_DIR)" + SNAP_UNIT_PREFIX=$(systemd-escape -p "$SNAP_MOUNT_DIR") shift ;; --purge) diff -Nru snapd-2.27.5/packaging/opensuse-42.1/snapd.changes snapd-2.28.5/packaging/opensuse-42.1/snapd.changes --- snapd-2.27.5/packaging/opensuse-42.1/snapd.changes 2017-08-30 05:32:20.000000000 +0000 +++ snapd-2.28.5/packaging/opensuse-42.1/snapd.changes 2017-10-13 21:25:46.000000000 +0000 @@ -1,4 +1,39 @@ ------------------------------------------------------------------- +Fri Oct 13 19:46:37 UTC 2017 - mvo@fastmail.fm + +- Update to upstream release 2.28.4 + +------------------------------------------------------------------- +Wed Oct 11 19:46:37 UTC 2017 - mvo@fastmail.fm + +- Update to upstream release 2.28.4 + +------------------------------------------------------------------- +Wed Oct 11 08:23:47 UTC 2017 - mvo@fastmail.fm + +- Update to upstream release 2.28.3 + +------------------------------------------------------------------- +Tue Oct 10 18:42:45 UTC 2017 - mvo@fastmail.fm + +- Update to upstream release 2.28.2 + +------------------------------------------------------------------- +Mon Sep 27 22:04:59 UTC 2017 - mvo@fastmail.fm + +- Update to upstream release 2.28.1 + +------------------------------------------------------------------- +Mon Sep 25 16:09:15 UTC 2017 - mvo@fastmail.fm + +- Update to upstream release 2.28 + +------------------------------------------------------------------- +Thu Sep 07 10:32:21 UTC 2017 - mvo@fastmail.fm + +- Update to upstream release 2.27.6 + +------------------------------------------------------------------- Wed Aug 30 07:45:01 UTC 2017 - mvo@fastmail.fm - Update to upstream release 2.27.5 diff -Nru snapd-2.27.5/packaging/opensuse-42.1/snapd.spec snapd-2.28.5/packaging/opensuse-42.1/snapd.spec --- snapd-2.27.5/packaging/opensuse-42.1/snapd.spec 2017-08-30 05:32:20.000000000 +0000 +++ snapd-2.28.5/packaging/opensuse-42.1/snapd.spec 2017-10-13 21:25:46.000000000 +0000 @@ -32,8 +32,8 @@ %define systemd_services_list snapd.refresh.timer snapd.refresh.service snapd.socket snapd.service snapd.autoimport.service snapd.system-shutdown.service Name: snapd -Version: 2.27.5 -Release: 1 +Version: 2.28.5 +Release: 0 Summary: Tools enabling systems to work with .snap files License: GPL-3.0 Group: System/Packages @@ -53,7 +53,6 @@ BuildRequires: libseccomp-devel BuildRequires: libtool BuildRequires: libudev-devel -BuildRequires: libudev-devel BuildRequires: libuuid-devel BuildRequires: make BuildRequires: pkg-config @@ -122,7 +121,7 @@ # apparmor kernel available in SUSE and Debian. The generated apparmor profiles # cannot be loaded into a vanilla kernel. As a temporary measure we just switch # it all off. -%configure --disable-apparmor --disable-seccomp --libexecdir=/usr/lib/snapd +%configure --disable-apparmor --libexecdir=%{_libexecdir}/snapd %build # Build golang executables @@ -145,9 +144,15 @@ %endif %gobuild cmd/snap -%gobuild cmd/snap-exec %gobuild cmd/snapctl %gobuild cmd/snap-update-ns +# build snap-exec completely static for base snaps +CGO_ENABLED=0 %gobuild cmd/snap-exec + +# This is ok because snap-seccomp only requires static linking when it runs from the core-snap via re-exec. +sed -e "s/-Bstatic -lseccomp/-Bstatic/g" -i %{_builddir}/go/src/%{provider_prefix}/cmd/snap-seccomp/main.go +# build snap-seccomp +%gobuild cmd/snap-seccomp # Build C executables make %{?_smp_mflags} -C cmd @@ -160,17 +165,18 @@ # Install all the go stuff %goinstall # TODO: instead of removing it move this to a dedicated golang package -rm -rf %{buildroot}/usr/lib64/go -rm -rf %{buildroot}/usr/lib/go +rm -rf %{buildroot}%{_libexecdir}64/go +rm -rf %{buildroot}%{_libexecdir}/go find %{buildroot} -# Move snapd, snap-exec and snap-update-ns into /usr/lib/snapd -install -m 755 -d %{buildroot}/usr/lib/snapd -mv %{buildroot}/usr/bin/snapd %{buildroot}/usr/lib/snapd/snapd -mv %{buildroot}/usr/bin/snap-exec %{buildroot}/usr/lib/snapd/snap-exec -mv %{buildroot}/usr/bin/snap-update-ns %{buildroot}/usr/lib/snapd/snap-update-ns +# Move snapd, snap-exec, snap-seccomp and snap-update-ns into %{_libexecdir}/snapd +install -m 755 -d %{buildroot}%{_libexecdir}/snapd +mv %{buildroot}/usr/bin/snapd %{buildroot}%{_libexecdir}/snapd/snapd +mv %{buildroot}/usr/bin/snap-exec %{buildroot}%{_libexecdir}/snapd/snap-exec +mv %{buildroot}/usr/bin/snap-update-ns %{buildroot}%{_libexecdir}/snapd/snap-update-ns +mv %{buildroot}/usr/bin/snap-seccomp %{buildroot}%{_libexecdir}/snapd/snap-seccomp # Install profile.d-based PATH integration for /snap/bin -install -m 755 -d %{buildroot}/etc/profile.d/ -install -m 644 etc/profile.d/apps-bin-path.sh %{buildroot}/etc/profile.d/snapd.sh +# and XDG_DATA_DIRS for /var/lib/snapd/desktop +make -C data/env install DESTDIR=%{buildroot} # Generate and install man page for snap command install -m 755 -d %{buildroot}%{_mandir}/man1 @@ -190,7 +196,7 @@ # NOTE: we don't want to ship system-shutdown helper, it is just a helper on # ubuntu-core systems that exclusively use snaps. It is used during the # shutdown process and thus can be left out of the distribution package. -rm -f %{?buildroot}/usr/lib/snapd/system-shutdown +rm -f %{?buildroot}%{_libexecdir}/snapd/system-shutdown # Install the directories that snapd creates by itself so that they can be a part of the package install -d %buildroot/var/lib/snapd/{assertions,desktop/applications,device,hostfs,mount,apparmor/profiles,seccomp/bpf,snaps} install -d %buildroot/snap/bin @@ -201,27 +207,32 @@ install -m 644 -D packaging/opensuse-42.2/permissions %buildroot/%{_sysconfdir}/permissions.d/snapd install -m 644 -D packaging/opensuse-42.2/permissions.paranoid %buildroot/%{_sysconfdir}/permissions.d/snapd.paranoid # Install the systemd units -make -C data/systemd install DESTDIR=%{buildroot} SYSTEMDSYSTEMUNITDIR=%{_unitdir} -for s in snapd.autoimport.service snapd.system-shutdown.service snap-repair.timer snap-repair.service; do - rm %buildroot/%{_unitdir}/$s +make -C data install DESTDIR=%{buildroot} SYSTEMDSYSTEMUNITDIR=%{_unitdir} +for s in snapd.autoimport.service snapd.system-shutdown.service snapd.snap-repair.timer snapd.snap-repair.service snapd.core-fixup.service; do + rm -f %buildroot/%{_unitdir}/$s done +# Remove snappy core specific scripts +rm -f %buildroot%{_libexecdir}/snapd/snapd.core-fixup.sh + # See https://en.opensuse.org/openSUSE:Packaging_checks#suse-missing-rclink for details install -d %{buildroot}/usr/sbin ln -sf %{_sbindir}/service %{buildroot}/%{_sbindir}/rcsnapd ln -sf %{_sbindir}/service %{buildroot}/%{_sbindir}/rcsnapd.refresh # Install the "info" data file with snapd version -install -m 644 -D data/info %{buildroot}/usr/lib/snapd/info +install -m 644 -D data/info %{buildroot}%{_libexecdir}/snapd/info # Install bash completion for "snap" install -m 644 -D data/completion/snap %{buildroot}/usr/share/bash-completion/completions/snap +install -m 644 -D data/completion/complete.sh %{buildroot}%{_libexecdir}/snapd +install -m 644 -D data/completion/etelpmoc.sh %{buildroot}%{_libexecdir}/snapd %verifyscript -%verify_permissions -e /usr/lib/snapd/snap-confine +%verify_permissions -e %{_libexecdir}/snapd/snap-confine %pre %service_add_pre %{systemd_services_list} %post -%set_permissions /usr/lib/snapd/snap-confine +%set_permissions %{_libexecdir}/snapd/snap-confine %service_add_post %{systemd_services_list} case ":$PATH:" in *:/snap/bin:*) @@ -245,7 +256,7 @@ %dir %attr(0000,root,root) /var/lib/snapd/void %dir /snap %dir /snap/bin -%dir /usr/lib/snapd +%dir %{_libexecdir}/snapd %dir /var/lib/snapd %dir /var/lib/snapd/apparmor %dir /var/lib/snapd/apparmor/profiles @@ -258,7 +269,7 @@ %dir /var/lib/snapd/seccomp %dir /var/lib/snapd/seccomp/bpf %dir /var/lib/snapd/snaps -%verify(not user group mode) %attr(04755,root,root) /usr/lib/snapd/snap-confine +%verify(not user group mode) %attr(04755,root,root) %{_libexecdir}/snapd/snap-confine %{_mandir}/man5/snap-confine.5.gz %{_mandir}/man5/snap-discard-ns.5.gz %{_udevrulesdir}/80-snappy-assign.rules @@ -270,15 +281,18 @@ /usr/bin/snapctl /usr/sbin/rcsnapd /usr/sbin/rcsnapd.refresh -/usr/lib/snapd/info -/usr/lib/snapd/snap-discard-ns -/usr/lib/snapd/snap-update-ns -/usr/lib/snapd/snap-exec -/usr/lib/snapd/snap-seccomp -/usr/lib/snapd/snapd -/usr/lib/udev/snappy-app-dev +%{_libexecdir}/snapd/info +%{_libexecdir}/snapd/snap-discard-ns +%{_libexecdir}/snapd/snap-update-ns +%{_libexecdir}/snapd/snap-exec +%{_libexecdir}/snapd/snap-seccomp +%{_libexecdir}/snapd/snapd +%{_libexecdir}/udev/snappy-app-dev /usr/share/bash-completion/completions/snap +%{_libexecdir}/snapd/complete.sh +%{_libexecdir}/snapd/etelpmoc.sh %{_mandir}/man1/snap.1.gz +/usr/share/dbus-1/services/io.snapcraft.Launcher.service %changelog diff -Nru snapd-2.27.5/packaging/opensuse-42.2/snapd.changes snapd-2.28.5/packaging/opensuse-42.2/snapd.changes --- snapd-2.27.5/packaging/opensuse-42.2/snapd.changes 2017-08-30 05:32:20.000000000 +0000 +++ snapd-2.28.5/packaging/opensuse-42.2/snapd.changes 2017-10-13 21:25:46.000000000 +0000 @@ -1,4 +1,39 @@ ------------------------------------------------------------------- +Fri Oct 13 19:46:37 UTC 2017 - mvo@fastmail.fm + +- Update to upstream release 2.28.4 + +------------------------------------------------------------------- +Wed Oct 11 19:46:37 UTC 2017 - mvo@fastmail.fm + +- Update to upstream release 2.28.4 + +------------------------------------------------------------------- +Wed Oct 11 08:23:47 UTC 2017 - mvo@fastmail.fm + +- Update to upstream release 2.28.3 + +------------------------------------------------------------------- +Tue Oct 10 18:42:45 UTC 2017 - mvo@fastmail.fm + +- Update to upstream release 2.28.2 + +------------------------------------------------------------------- +Mon Sep 27 22:04:59 UTC 2017 - mvo@fastmail.fm + +- Update to upstream release 2.28.1 + +------------------------------------------------------------------- +Mon Sep 25 16:09:15 UTC 2017 - mvo@fastmail.fm + +- Update to upstream release 2.28 + +------------------------------------------------------------------- +Thu Sep 07 10:32:21 UTC 2017 - mvo@fastmail.fm + +- Update to upstream release 2.27.6 + +------------------------------------------------------------------- Wed Aug 30 07:45:01 UTC 2017 - mvo@fastmail.fm - Update to upstream release 2.27.5 diff -Nru snapd-2.27.5/packaging/opensuse-42.2/snapd.spec snapd-2.28.5/packaging/opensuse-42.2/snapd.spec --- snapd-2.27.5/packaging/opensuse-42.2/snapd.spec 2017-08-30 05:32:20.000000000 +0000 +++ snapd-2.28.5/packaging/opensuse-42.2/snapd.spec 2017-10-13 21:25:46.000000000 +0000 @@ -32,8 +32,8 @@ %define systemd_services_list snapd.refresh.timer snapd.refresh.service snapd.socket snapd.service snapd.autoimport.service snapd.system-shutdown.service Name: snapd -Version: 2.27.5 -Release: 1 +Version: 2.28.5 +Release: 0 Summary: Tools enabling systems to work with .snap files License: GPL-3.0 Group: System/Packages @@ -53,7 +53,6 @@ BuildRequires: libseccomp-devel BuildRequires: libtool BuildRequires: libudev-devel -BuildRequires: libudev-devel BuildRequires: libuuid-devel BuildRequires: make BuildRequires: pkg-config @@ -122,7 +121,7 @@ # apparmor kernel available in SUSE and Debian. The generated apparmor profiles # cannot be loaded into a vanilla kernel. As a temporary measure we just switch # it all off. -%configure --disable-apparmor --disable-seccomp --libexecdir=/usr/lib/snapd +%configure --disable-apparmor --libexecdir=%{_libexecdir}/snapd %build # Build golang executables @@ -145,9 +144,15 @@ %endif %gobuild cmd/snap -%gobuild cmd/snap-exec %gobuild cmd/snapctl %gobuild cmd/snap-update-ns +# build snap-exec completely static for base snaps +CGO_ENABLED=0 %gobuild cmd/snap-exec + +# This is ok because snap-seccomp only requires static linking when it runs from the core-snap via re-exec. +sed -e "s/-Bstatic -lseccomp/-Bstatic/g" -i %{_builddir}/go/src/%{provider_prefix}/cmd/snap-seccomp/main.go +# build snap-seccomp +%gobuild cmd/snap-seccomp # Build C executables make %{?_smp_mflags} -C cmd @@ -160,17 +165,18 @@ # Install all the go stuff %goinstall # TODO: instead of removing it move this to a dedicated golang package -rm -rf %{buildroot}/usr/lib64/go -rm -rf %{buildroot}/usr/lib/go +rm -rf %{buildroot}%{_libexecdir}64/go +rm -rf %{buildroot}%{_libexecdir}/go find %{buildroot} -# Move snapd, snap-exec and snap-update-ns into /usr/lib/snapd -install -m 755 -d %{buildroot}/usr/lib/snapd -mv %{buildroot}/usr/bin/snapd %{buildroot}/usr/lib/snapd/snapd -mv %{buildroot}/usr/bin/snap-exec %{buildroot}/usr/lib/snapd/snap-exec -mv %{buildroot}/usr/bin/snap-update-ns %{buildroot}/usr/lib/snapd/snap-update-ns +# Move snapd, snap-exec, snap-seccomp and snap-update-ns into %{_libexecdir}/snapd +install -m 755 -d %{buildroot}%{_libexecdir}/snapd +mv %{buildroot}/usr/bin/snapd %{buildroot}%{_libexecdir}/snapd/snapd +mv %{buildroot}/usr/bin/snap-exec %{buildroot}%{_libexecdir}/snapd/snap-exec +mv %{buildroot}/usr/bin/snap-update-ns %{buildroot}%{_libexecdir}/snapd/snap-update-ns +mv %{buildroot}/usr/bin/snap-seccomp %{buildroot}%{_libexecdir}/snapd/snap-seccomp # Install profile.d-based PATH integration for /snap/bin -install -m 755 -d %{buildroot}/etc/profile.d/ -install -m 644 etc/profile.d/apps-bin-path.sh %{buildroot}/etc/profile.d/snapd.sh +# and XDG_DATA_DIRS for /var/lib/snapd/desktop +make -C data/env install DESTDIR=%{buildroot} # Generate and install man page for snap command install -m 755 -d %{buildroot}%{_mandir}/man1 @@ -190,7 +196,7 @@ # NOTE: we don't want to ship system-shutdown helper, it is just a helper on # ubuntu-core systems that exclusively use snaps. It is used during the # shutdown process and thus can be left out of the distribution package. -rm -f %{?buildroot}/usr/lib/snapd/system-shutdown +rm -f %{?buildroot}%{_libexecdir}/snapd/system-shutdown # Install the directories that snapd creates by itself so that they can be a part of the package install -d %buildroot/var/lib/snapd/{assertions,desktop/applications,device,hostfs,mount,apparmor/profiles,seccomp/bpf,snaps} install -d %buildroot/snap/bin @@ -201,27 +207,32 @@ install -m 644 -D packaging/opensuse-42.2/permissions %buildroot/%{_sysconfdir}/permissions.d/snapd install -m 644 -D packaging/opensuse-42.2/permissions.paranoid %buildroot/%{_sysconfdir}/permissions.d/snapd.paranoid # Install the systemd units -make -C data/systemd install DESTDIR=%{buildroot} SYSTEMDSYSTEMUNITDIR=%{_unitdir} -for s in snapd.autoimport.service snapd.system-shutdown.service snap-repair.timer snap-repair.service; do - rm %buildroot/%{_unitdir}/$s +make -C data install DESTDIR=%{buildroot} SYSTEMDSYSTEMUNITDIR=%{_unitdir} +for s in snapd.autoimport.service snapd.system-shutdown.service snapd.snap-repair.timer snapd.snap-repair.service snapd.core-fixup.service; do + rm -f %buildroot/%{_unitdir}/$s done +# Remove snappy core specific scripts +rm -f %buildroot%{_libexecdir}/snapd/snapd.core-fixup.sh + # See https://en.opensuse.org/openSUSE:Packaging_checks#suse-missing-rclink for details install -d %{buildroot}/usr/sbin ln -sf %{_sbindir}/service %{buildroot}/%{_sbindir}/rcsnapd ln -sf %{_sbindir}/service %{buildroot}/%{_sbindir}/rcsnapd.refresh # Install the "info" data file with snapd version -install -m 644 -D data/info %{buildroot}/usr/lib/snapd/info +install -m 644 -D data/info %{buildroot}%{_libexecdir}/snapd/info # Install bash completion for "snap" install -m 644 -D data/completion/snap %{buildroot}/usr/share/bash-completion/completions/snap +install -m 644 -D data/completion/complete.sh %{buildroot}%{_libexecdir}/snapd +install -m 644 -D data/completion/etelpmoc.sh %{buildroot}%{_libexecdir}/snapd %verifyscript -%verify_permissions -e /usr/lib/snapd/snap-confine +%verify_permissions -e %{_libexecdir}/snapd/snap-confine %pre %service_add_pre %{systemd_services_list} %post -%set_permissions /usr/lib/snapd/snap-confine +%set_permissions %{_libexecdir}/snapd/snap-confine %service_add_post %{systemd_services_list} case ":$PATH:" in *:/snap/bin:*) @@ -245,7 +256,7 @@ %dir %attr(0000,root,root) /var/lib/snapd/void %dir /snap %dir /snap/bin -%dir /usr/lib/snapd +%dir %{_libexecdir}/snapd %dir /var/lib/snapd %dir /var/lib/snapd/apparmor %dir /var/lib/snapd/apparmor/profiles @@ -258,7 +269,7 @@ %dir /var/lib/snapd/seccomp %dir /var/lib/snapd/seccomp/bpf %dir /var/lib/snapd/snaps -%verify(not user group mode) %attr(04755,root,root) /usr/lib/snapd/snap-confine +%verify(not user group mode) %attr(04755,root,root) %{_libexecdir}/snapd/snap-confine %{_mandir}/man5/snap-confine.5.gz %{_mandir}/man5/snap-discard-ns.5.gz %{_udevrulesdir}/80-snappy-assign.rules @@ -270,15 +281,18 @@ /usr/bin/snapctl /usr/sbin/rcsnapd /usr/sbin/rcsnapd.refresh -/usr/lib/snapd/info -/usr/lib/snapd/snap-discard-ns -/usr/lib/snapd/snap-update-ns -/usr/lib/snapd/snap-exec -/usr/lib/snapd/snap-seccomp -/usr/lib/snapd/snapd -/usr/lib/udev/snappy-app-dev +%{_libexecdir}/snapd/info +%{_libexecdir}/snapd/snap-discard-ns +%{_libexecdir}/snapd/snap-update-ns +%{_libexecdir}/snapd/snap-exec +%{_libexecdir}/snapd/snap-seccomp +%{_libexecdir}/snapd/snapd +%{_libexecdir}/udev/snappy-app-dev /usr/share/bash-completion/completions/snap +%{_libexecdir}/snapd/complete.sh +%{_libexecdir}/snapd/etelpmoc.sh %{_mandir}/man1/snap.1.gz +/usr/share/dbus-1/services/io.snapcraft.Launcher.service %changelog diff -Nru snapd-2.27.5/packaging/ubuntu-14.04/changelog snapd-2.28.5/packaging/ubuntu-14.04/changelog --- snapd-2.27.5/packaging/ubuntu-14.04/changelog 2017-08-30 05:32:20.000000000 +0000 +++ snapd-2.28.5/packaging/ubuntu-14.04/changelog 2017-10-13 21:25:46.000000000 +0000 @@ -1,3 +1,329 @@ +snapd (2.28.5~14.04) trusty; urgency=medium + + * New upstream release, LP: #1714984 + - snap-confine: cleanup broken nvidia udev tags + - cmd/snap-confine: update valid security tag regexp + - overlord/ifacestate: refresh udev backend on startup + - dbus: ensure io.snapcraft.Launcher.service is created on re- + exec + - snap-confine: add support for handling /dev/nvidia-modeset + - interfaces/network-control: remove incorrect rules for tun + + -- Michael Vogt Fri, 13 Oct 2017 23:25:46 +0200 + +snapd (2.28.4~14.04) trusty; urgency=medium + + * New upstream release, LP: #1714984 + - interfaces/opengl: don't udev tag nvidia devices and use snap- + confine instead + - debian: fix replaces/breaks for snap-xdg-open (thanks to apw!) + + -- Michael Vogt Wed, 11 Oct 2017 19:40:57 +0200 + +snapd (2.28.3~14.04) trusty; urgency=medium + + * New upstream release, LP: #1714984 + - interfaces/lxd: lxd slot implementation can also be an app + snap + + -- Michael Vogt Wed, 11 Oct 2017 08:20:26 +0200 + +snapd (2.28.2~14.04) trusty; urgency=medium + + * New upstream release, LP: #1714984 + - interfaces: fix udev rules for tun + - release,cmd,dirs: Redo the distro checks to take into account + distribution families + + -- Michael Vogt Tue, 10 Oct 2017 18:39:58 +0200 + +snapd (2.28.1~14.04) trusty; urgency=medium + + * New upstream release, LP: #1714984 + - snap-confine: update apparmor rules for fedora based basesnaps + - snapstate: rename refresh hook to post-refresh for consistency + + -- Michael Vogt Wed, 27 Sep 2017 18:01:48 -0400 + +snapd (2.28~14.04) trusty; urgency=medium + + * New upstream release, LP: #1714984 + - hooks: rename refresh to after-refresh + - snap-confine: bind mount /usr/lib/snapd relative to snap-confine + - cmd,dirs: treat "liri" the same way as "arch" + - snap-confine: fix base snaps on core + - hooks: substitute env vars when executing hooks + - interfaces: updates for default, browser-support, desktop, opengl, + upower and stub-resolv.conf + - cmd,dirs: treat manjaro the same as arch + - systemd: do not run auto-import and repair services on classic + - packaging/fedora: Ensure vendor/ is empty for builds and fix spec + to build current master + - many: fix TestSetConfNumber missing an Unlock and other fragility + improvements + - osutil: adjust StreamCommand tests for golang 1.9 + - debian: improve package description + - interfaces: add netlink kobject uevent to hardware observe + - debian: update trusted account-keys check on 14.04 packaging + - interfaces/network-{control,observe}: allow receiving + kobject_uevent() messages + - tests: fix lxd test for external backend + - snap-confine,snap-update-ns: add -no-pie to fix FTBFS on + go1.7,ppc64 + - corecfg: mock "systemctl" in all corecfg tests + - tests: fix unit tests on Ubuntu 14.04 + - debian: add missing flags when building static snap-exec + - many: end-to-end support for the bare base snap + - overlord/snapstate: SetRootDir from SetUpTest, not in just some + tests + - store: have an ad-hoc method on cfg to get its list of uris for + tests + - daemon: let client decide whether to allow interactive auth via + polkit + - client,daemon,snap,store: add license field + - overlord/snapstate: rename HasCurrent to IsInstalled, remove + superfluous/misleading check from All + - cmd/snap: SetRootDir from SetUpTest, not in just some individual + tests. + - systemd: rename snap-repair.{service,timer} to snapd.snap- + repair.{service,timer} + - snap-seccomp: remove use of x/net/bpf from tests + - httputil: more naive per go version way to recreate a default + transport for tls reconfig + - cmd/snap-seccomp/main_test.go: add one more syscall for arm64 + - interfaces/opengl: use == to compare, not = + - cmd/snap-seccomp/main_test.go: add syscalls for armhf and arm64 + - cmd/snap-repair: track and use a lower bound for the time for + TLS checks + - interfaces: expose bluez interface on classic OS + - snap-seccomp: add in-kernel bpf tests + - overlord: always try to get a serial, lazily on classic + - tests: add nmcli regression test + - tests: deal with __PNR_chown on aarch64 to fix FTBFS on arm64 + - tests: add autopilot-introspection interface test + - vendor: fix artifact from manually editing vendor/vendor.json + - tests: rename complexion to test-snapd-complexion + - interfaces: add desktop and desktop-legacy + interfaces/desktop: add new 'desktop' interface for modern DEs + interfaces/builtin/desktop_test.go: use modern testing techniques + interfaces/wayland: allow read on /etc/drirc for Plasma desktop + interfaces/desktop-legacy: add new 'legacy' interface (currently + for a11y and input) + - tests: fix race in snap userd test + - devices/iio: add read/write for missing sysfs entries + - spread: don't set HTTPS?_PROXY for linode + - cmd/snap-repair: check signatures of repairs from Next + - env: set XDG_DATA_DIRS for wayland et.al. + - interfaces/{default,account-control}: Use username/group instead + of uid/gid + - interfaces/builtin: use udev tagging more broadly + - tests: add basic lxd test + - wrappers: ensure bash completion snaps install on core + - vendor: use old golang.org/x/crypto/ssh/terminal to build on + powerpc again + - docs: add PULL_REQUEST_TEMPLATE.md + - interfaces: fix network-manager plug + - hooks: do not error out when hook is optional and no hook handler + is registered + - cmd/snap: add userd command to replace snapd-xdg-open + - tests: new regex used to validate the core version on extra snaps + ass... + - snap: add new `snap switch` command + - tests: wait more and more debug info about fakestore start issues + - apparmor,release: add better apparmor detection/mocking code + - interfaces/i2c: adjust sysfs rule for alternate paths + - interfaces/apparmor: add missing call to dirs.SetRootDir + - cmd: "make hack" now also installs snap-update-ns + - tests: copy files with less verbosity + - cmd/snap-confine: allow using additional libraries required by + openSUSE + - packaging/fedora: Merge changes from Fedora Dist-Git + - snapstate: improve the error message when classic confinement is + not supported + - tests: add test to ensure amd64 can run i386 syscall binaries + - tests: adding extra info for fakestore when fails to start + - tests: install most important snaps + - cmd/snap-repair: more test coverage of filtering + - squashfs: remove runCommand/runCommandWithOutput as we do not need + it + - cmd/snap-repair: ignore superseded revisions, filter on arch and + models + - hooks: support for refresh hook + - Partial revert "overlord/devicestate, store: update device auth + endpoints URLs" + - cmd/snap-confine: allow reading /proc/filesystems + - cmd/snap-confine: genearlize apparmor profile for various lib + layout + - corecfg: fix proxy.* writing and add integration test + - corecfg: deal with system.power-key-action="" correctly + - vendor: update vendor.json after (presumed) manual edits + - cmd/snap: in `snap info`, don't print a newline between tracks + - daemon: add polkit support to /v2/login + - snapd,snapctl: decode json using Number + - client: fix go vet 1.7 errors + - tests: make 17.04 shellcheck clean + - tests: remove TestInterfacesHelp as it breaks when go-flags + changes + - snapstate: undo a daemon restart on classic if needed + - cmd/snap-repair: recover brand/model from + /var/lib/snapd/seed/assertions checking signatures and brand + account + - spread: opt into unsafe IO during spread tests + - snap-repair: update snap-repair/runner_test.go for API change in + makeMockServer + - cmd/snap-repair: skeleton code around actually running a repair + - tests: wait until the port is listening after start the fake store + - corecfg: fix typo in tests + - cmd/snap-repair: test that redirects works during fetching + - osutil: honor SNAPD_UNSAFE_IO for testing + - vendor: explode and make more precise our golang.go/x/crypto deps, + use same version as Debian unstable + - many: sanitize NewStoreStack signature, have shared default store + test private keys + - systemd: disable `Nice=-5` to fix error when running inside lxd + - spread.yaml: update delta ref to 2.27 + - cmd/snap-repair: use E-Tags when refetching a repair to retry + - interfaces/many: updates based on chromium and mrrescue denials + - cmd/snap-repair: implement most logic to get the next repair to + run/retry in a brand sequence + - asserts/assertstest: copy headers in SigningDB.Sign + - interfaces: convert uhid to common interface and test cases + improvement for time_control and opengl + - many tests: move all panicing fake store methods to a common place + - asserts: add store assertion type + - interfaces: don't crash if content slot has no attributes + - debian: do not build with -buildmode=pie on i386 + - wrappers: symlink completion snippets when symlinking binaries + - tests: adding more debug information for the interfaces-cups- + control … + - apparmor: pass --quiet to parser on load unless SNAPD_DEBUG is set + - many: allow and support serials signed by the 'generic' authority + instead of the brand + - corecfg: add proxy configuration via `snap set core + proxy.{http,https,ftp}=...` + - interfaces: a bunch of interfaces test improvement + - tests: enable regression and completion suites for opensuse + - tests: installing snapd for nested test suite + - interfaces: convert lxd_support to common iface + - interfaces: add missing test for camera interface. + - snap: add support for parsing snap layout section + - cmd/snap-repair: like for downloads we cannot have a timeout (at + least for now), less aggressive retry strategies + - overlord: rely on more conservative ensure interval + - overlord,store: no piles of return args for methods gathering + device session request params + - overlord,store: send model assertion when setting up device + sessions + - interfaces/misc: updates for unity7/x11, browser- + support, network-control and mount-observe + interfaces/unity7,x11: update for NETLINK_KOBJECT_UEVENT + interfaces/browser-support: update sysfs reads for + newer browser versions, interfaces/network-control: rw for + ieee80211 advanced wireless interfaces/mount-observe: allow read + on sysfs entries for block devices + - tests: use dnf --refresh install to avert stale cache + - osutil: ensure TestLockUnlockWorks uses supported flock + - interfaces: convert lxd to common iface + - tests: restart snapd to ensure re-exec settings are applied + - tests: fix interfaces-cups-control test + - interfaces: improve and tweak bunch of interfaces test cases. + - tests: adding extra worker for fedora + - asserts,overlord/devicestate: support predefined assertions that + don't establish foundational trust + - interfaces: convert two hardware_random interfaces to common iface + - interfaces: convert io_ports_control to common iface + - tests: fix for upgrade test on fedora + - daemon, client, cmd/snap: implement snap start/stop/restart + - cmd/snap-confine: set _FILE_OFFSET_BITS to 64 + - interfaces: covert framebuffer to commonInterface + - interfaces: convert joystick to common iface + - interfaces/builtin: add the spi interface + - wrappers, overlord/snapstate/backend: make link-snap clean up on + failure. + - interfaces/wayland: add wayland interface + - interfaces: convert kvm to common iface + - tests: extend upower-observe test to cover snaps providing slots + - tests: enable main suite for opensuse + - interfaces: convert physical_memory_observe to common iface + - interfaces: add missing test for optical_drive interface. + - interfaces: convert physical_memory_control to common iface + - interfaces: convert ppp to common iface + - interfaces: convert time-control to common iface + - tests: fix failover test + - interfaces/builtin: rework for avahi interface + - interfaces: convert broadcom-asic-control to common iface + - snap/snapenv: document the use of CoreSnapMountDir for SNAP + - packaging/arch: drop patches merged into master + - cmd: fix mustUnsetenv docstring (thanks to Chipaca) + - release: remove default from VERSION_ID + - tests: enable regression, upgrade and completion test suites for + fedora + - tests: restore interfaces-account-control properly + - overlord/devicestate, store: update device auth endpoints URLs + - tests: fix install-hook test failure + - tests: download core and ubuntu-core at most once + - interfaces: add common support for udev + - overlord/devicestate: fix, don't assume that the serial is backed + by a 1-key chain + - cmd/snap-confine: don't share /etc/nsswitch from host + - store: do not resume a download when we already have the whole + thing + - many: implement "snap logs" + - store: don't call useDeltas() twice in quick succession + - interfaces/builtin: add kvm interface + - snap/snapenv: always expect /snap for $SNAP + - cmd: mark arch as non-reexecing distro + - cmd: fix tests that assume /snap mount + - gitignore: ignore more build artefacts + - packaging: add current arch packaging + - interfaces/unity7: allow receiving media key events in (at least) + gnome-shell + - interfaces/many, cmd/snap-confine: miscellaneous policy updates + - interfaces/builtin: implement broadcom-asic-control interface + - interfaces/builtin: reduce duplication and remove cruft in + Sanitize{Plug,Slot} + - tests: apply underscore convention for SNAPMOUNTDIR variable + - interfaces/greengrass-support: adjust accesses now that have + working snap + - daemon, client, cmd/snap: implement "snap services" + - tests: fix refresh tests not stopping fake store for fedora + - many: add the interface command + - overlord/snapstate/backend: some copydata improvements + - many: support querying and completing assertion type names + - interfaces/builtin: discard empty Validate{Plug,Slot} + - cmd/snap-repair: start of Runner, implement first pass of Peek + and Fetch + - tests: enable main suite on fedora + - snap: do not always quote the snap info summary + - vendor: update go-flags to address crash in "snap debug" + - interfaces: opengl support pci device and vendor + - many: start implenting "base" snap type on the snapd side + - arch,release: map armv6 correctly + - many: expose service status in 'snap info' + - tests: add browser-support interface test + - tests: disable snapd-notify for the external backend + - interfaces: Add /run/uuid/request to openvswitch + - interfaces: add password-manager-service implicit classic + interface + - cmd: rework reexec detection + - cmd: fix re-exec bug when starting from snapd 2.21 + - tests: dependency packages installed during prepare-project + - tests: remove unneeded check for re-exec in InternalToolPath() + - cmd,tests: fix classic confinement confusing re-execution code + - store: configurable base api + - tests: fix how package lists are updated for opensuse and fedora + + -- Michael Vogt Mon, 25 Sep 2017 12:08:10 -0400 + +snapd (2.27.6~14.04) trusty; urgency=medium + + * New upstream release, LP: #1703798: + - interfaces: add udev netlink support to hardware-observe + - interfaces/network-{control,observe}: allow receiving + kobject_uevent() messages + + -- Michael Vogt Thu, 07 Sep 2017 10:22:18 +0200 + snapd (2.27.5~14.04) trusty; urgency=medium * New upstream release, LP: #1703798: @@ -322,6 +648,29 @@ -- Michael Vogt Thu, 10 Aug 2017 12:43:23 +0200 +snapd (2.26.14~14.04) trusty; urgency=medium + + * New upstream release, LP: #1690083 + - cmd: fix incorrect re-exec when starting from snapd 2.21 + + -- Michael Vogt Thu, 20 Jul 2017 13:52:05 +0200 + +snapd (2.26.13~14.04) trusty; urgency=medium + + * New upstream release, LP: #1690083 + - cmd,tests: fix classic confinement confusing re-execution code + - cmd: fix incorrect check check for re-exec in InternalToolPath() + - snap-seccomp: add secondary arch for unrestricted snaps as well + + -- Michael Vogt Tue, 18 Jul 2017 20:34:33 +0200 + +snapd (2.26.10~14.04) trusty; urgency=medium + + * New upstream release, LP: #1690083 + - Fix snap-seccomp tests in artful/trusty on i386/s390x/aarch64 + + -- Michael Vogt Mon, 17 Jul 2017 11:58:26 +0200 + snapd (2.26.9~14.04) trusty; urgency=medium * New upstream release, LP: #1690083 diff -Nru snapd-2.27.5/packaging/ubuntu-14.04/control snapd-2.28.5/packaging/ubuntu-14.04/control --- snapd-2.27.5/packaging/ubuntu-14.04/control 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/packaging/ubuntu-14.04/control 2017-09-13 14:47:18.000000000 +0000 @@ -7,6 +7,7 @@ autotools-dev, bash-completion, debhelper (>= 9), + dbus, dh-apparmor, dh-autoreconf, dh-golang (>=1.7), @@ -17,8 +18,8 @@ gnupg2, golang-any (>=2:1.6) | golang-1.6, indent, - libcap-dev, init-system-helpers, + libcap-dev, libapparmor-dev, libglib2.0-dev, libseccomp-dev (>= 2.1.1-1ubuntu1~trusty4), @@ -70,11 +71,11 @@ util-linux (>=2.20.1-5.1ubuntu20.9), ${misc:Depends}, ${shlibs:Depends} -Replaces: ubuntu-snappy (<< 1.9), ubuntu-snappy-cli (<< 1.9), snap-confine (<< 2.23~14.04) -Breaks: ubuntu-snappy (<< 1.9), ubuntu-snappy-cli (<< 1.9), snap-confine (<< 2.23~14.04) +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) Conflicts: snap (<< 2013-11-29-1ubuntu1) Built-Using: ${Built-Using} ${misc:Built-Using} -Description: Tool to interact with Ubuntu Core Snappy. +Description: Daemon and tooling that enable snap packages Install, configure, refresh and remove snap packages. Snaps are 'universal' packages that work across many different Linux systems, enabling secure distribution of the latest apps and utilities for @@ -113,8 +114,16 @@ Package: ubuntu-core-launcher Architecture: any -Depends: snap-confine (= ${binary:Version}), ${misc:Depends} -Section: oldlibs +Depends: snapd (= ${binary:Version}), ${misc:Depends} +Section: oldlibs +Pre-Depends: dpkg (>= 1.15.7.2) +Description: Transitional package for snapd + This is a transitional dummy package. It can safely be removed. + +Package: snapd-xdg-open +Architecture: any +Depends: snapd (= ${binary:Version}), ${misc:Depends} +Section: oldlibs Pre-Depends: dpkg (>= 1.15.7.2) -Description: Transitional package for snap-confine +Description: Transitional package for snapd-xdg-open This is a transitional dummy package. It can safely be removed. diff -Nru snapd-2.27.5/packaging/ubuntu-14.04/rules snapd-2.28.5/packaging/ubuntu-14.04/rules --- snapd-2.27.5/packaging/ubuntu-14.04/rules 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/packaging/ubuntu-14.04/rules 2017-09-13 14:47:18.000000000 +0000 @@ -1,5 +1,15 @@ #!/usr/bin/make -f # -*- makefile -*- +# +# These rules should work for any debian-ish distro that is *not* +# systemd based but can use a tweaked, deputy systemd. This includes +# just Ubuntu 14.04 ("trusty"), to our knowledge (let us know if +# you're using this elsewhere!). The more general, systemd-based one +# is the 16.04 rule. +# +# Please keep the diff between that and this relatively small, even if +# it means having suboptimal code; these need to be kept in sync by +# sentient bags of meat. #export DH_VERBOSE=1 export DH_OPTIONS @@ -14,13 +24,13 @@ include /etc/os-release -# We are relying on a deputy systemd setup for trusty, -# in which systemd does not run as PID 1. To solve the -# problem of services shipping systemd units and upstart jobs -# being started twice, we altered systemd on trusty to ignore -# /lib/systemd/system and instead consider only selected units from -# /lib/systemd/upstart. +# On trusty we are relying on a deputy systemd, which does not run as +# PID 1. To solve the problem of services shipping systemd units and +# upstart jobs being started twice, we altered systemd on trusty to +# ignore /lib/systemd/system and instead consider only selected units +# from /lib/systemd/upstart. SYSTEMD_UNITS_DESTDIR="lib/systemd/upstart/" + # make sure that trusty's golang-1.6 is picked up correctly. export PATH:=/usr/lib/go-1.6/bin:${PATH} @@ -28,7 +38,16 @@ # work around that by constructing the appropriate -I flag by hand. GCCGO := $(shell go tool dist env > /dev/null 2>&1 && echo no || echo yes) -BUILDFLAGS:=-buildmode=pie -pkgdir=$(CURDIR)/_build/std +# Disable -buildmode=pie mode on i386 as can panics in spectacular +# ways (LP: #1711052). +# See also https://forum.snapcraft.io/t/artful-i386-panics/ +# Note while the panic is only on artful, that's because artful +# detects it; the issue potentially there on older things. +BUILDFLAGS:=-pkgdir=$(CURDIR)/_build/std +ifneq ($(shell dpkg-architecture -qDEB_HOST_ARCH),i386) +BUILDFLAGS+= -buildmode=pie +endif + GCCGOFLAGS= ifeq ($(GCCGO),yes) GOARCH := $(shell go env GOARCH) @@ -53,7 +72,8 @@ # because derivatives may have different kernels that don't support all the # required confinement features and we don't to mislead anyone about the # security of the system. Discuss a proper approach to this for downstreams -# if and when they approach us +# if and when they approach us. +# NOTE this could be simpler for trusty, but this way keeps the diff down. ifeq ($(shell dpkg-vendor --query Vendor),Ubuntu) # On Ubuntu 16.04 we need to produce a build that can be used on wide # variety of systems. As such we prefer static linking over dynamic linking @@ -72,31 +92,14 @@ endif BUILT_USING=$(shell dpkg-query -f '$${source:Package} (= $${source:Version}), ' -W $(BUILT_USING_PACKAGES)) -# export DEB_BUILD_MAINT_OPTIONS = hardening=+all -# DPKG_EXPORT_BUILDFLAGS = 1 -# include /usr/share/dpkg/buildflags.mk - -# Currently, we enable confinement for Ubuntu only, not for derivatives, -# because derivatives may have different kernels that don't support all the -# required confinement features and we don't to mislead anyone about the -# security of the system. Discuss a proper approach to this for downstreams -# if and when they approach us -ifeq ($(shell dpkg-vendor --query Vendor),Ubuntu) - # On Ubuntu 14.04 snapd cannot add the libcap dependency because of a - # feature/bug in apt where this would stop updates for some people (apt - # tries not to install new packages unless `dist-upgrade' command is used). - # As a work-around we link to libcap statically. - VENDOR_ARGS=--enable-nvidia-ubuntu --enable-static-libcap -else - VENDOR_ARGS=--disable-apparmor -endif - %: dh $@ --buildsystem=golang --with=golang --fail-missing --builddirectory=_build override_dh_fixperms: dh_fixperms -Xusr/lib/snapd/snap-confine + +# trusty doesn't need the .real workaround override_dh_installdeb: dh_apparmor --profile-name=usr.lib.snapd.snap-confine -psnapd dh_installdeb @@ -114,9 +117,9 @@ ) endif dh_clean + $(MAKE) -C data clean # XXX: hacky $(MAKE) -C cmd distclean || true - $(MAKE) -C data/systemd clean override_dh_auto_build: # usually done via `go generate` but that is not supported on powerpc @@ -125,24 +128,37 @@ mkdir -p _build/src/$(DH_GOPKG)/cmd/snap/test-data cp -a cmd/snap/test-data/*.gpg _build/src/$(DH_GOPKG)/cmd/snap/test-data/ dh_auto_build -- $(BUILDFLAGS) $(TAGS) $(GCCGOFLAGS) + # Generate static snap-exec - it somehow includes CGO so we must + # force a static build here. We need a static snap-exec inside + # the core snap because not all bases will have a libc + (cd _build/bin && GOPATH=$$(pwd)/.. CGO_ENABLED=0 go build $(DH_GOPKG)/cmd/snap-exec) + # ensure we generated a static build + $(shell if ldd _build/bin/snap-exec; then false "need static build"; fi) + # Build C bits, sadly manually cd cmd && ( autoreconf -i -f ) cd cmd && ( ./configure --prefix=/usr --libexecdir=/usr/lib/snapd $(VENDOR_ARGS)) $(MAKE) -C cmd all + # Generate the real systemd/dbus/env config files + $(MAKE) -C data all + override_dh_auto_test: dh_auto_test -- $(GCCGOFLAGS) # a tested default (production) build should have no test keys ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))) - # check that only the main trusted account-key is included - [ $$(strings _build/bin/snapd|grep -c -E "public-key-sha3-384: [a-zA-Z0-9_-]{64}") -eq 1 ] + # check that only the main trusted account-keys are included + [ $$(strings _build/bin/snapd|grep -c -E "public-key-sha3-384: [a-zA-Z0-9_-]{64}") -eq 2 ] strings _build/bin/snapd|grep -c "^public-key-sha3-384: -CvQKAwRQ5h3Ffn10FILJoEZUXOv6km9FwA80-Rcj-f-6jadQ89VRswHNiEB9Lxk$$" + strings _build/bin/snapd|grep -c "^public-key-sha3-384: d-JcZF9nD9eBw7bwMnH61x-bklnQOhQud1Is6o_cn2wTj8EYDi9musrIT9z2MdAa$$" endif ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))) # run the snap-confine tests $(MAKE) -C cmd check endif +# no dh_systemd in trusty + override_dh_install: # we do not need this in the package, its just needed during build rm -rf ${CURDIR}/debian/tmp/usr/bin/xgettext-go @@ -155,17 +171,22 @@ if [ -d share/locale ]; then \ cp -R share/locale debian/snapd/usr/share; \ fi - # we install snapd's systemd units - mkdir -p debian/snapd/$(SYSTEMD_UNITS_DESTDIR) - install --mode=0644 debian/snapd.refresh.timer debian/snapd/$(SYSTEMD_UNITS_DESTDIR) - install --mode=0644 debian/snapd.refresh.service debian/snapd/$(SYSTEMD_UNITS_DESTDIR) - install --mode=0644 debian/snapd.autoimport.service debian/snapd/$(SYSTEMD_UNITS_DESTDIR) - install --mode=0644 debian/*.socket debian/snapd/$(SYSTEMD_UNITS_DESTDIR) - install --mode=0644 debian/snapd.service debian/snapd/$(SYSTEMD_UNITS_DESTDIR) + + # install snapd's systemd units / upstart jobs, done + # here instead of debian/snapd.install because the + # ubuntu/14.04 release branch adds/changes bits here + $(MAKE) -C data install DESTDIR=$(CURDIR)/debian/snapd/ \ + SYSTEMDSYSTEMUNITDIR=$(SYSTEMD_UNITS_DESTDIR) + # we called this apps-bin-path.sh instead of snapd.sh, and + # it's a conf file so we're stuck with it + mv debian/snapd/etc/profile.d/snapd.sh debian/snapd/etc/profile.d/apps-bin-path.sh + install --mode=0644 debian/snap.mount.service debian/snapd/$(SYSTEMD_UNITS_DESTDIR) - # and now the normal install rules - install --mode=0644 debian/snapd.system-shutdown.service debian/snapd/$(SYSTEMD_UNITS_DESTDIR) + $(MAKE) -C cmd install DESTDIR=$(CURDIR)/debian/tmp + + # trusty doesn't need the .real workaround + dh_install override_dh_auto_install: snap.8 diff -Nru snapd-2.27.5/packaging/ubuntu-14.04/snapd.autoimport.service snapd-2.28.5/packaging/ubuntu-14.04/snapd.autoimport.service --- snapd-2.27.5/packaging/ubuntu-14.04/snapd.autoimport.service 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/packaging/ubuntu-14.04/snapd.autoimport.service 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -[Unit] -Description=Auto import assertions from block devices -After=snapd.service snapd.socket - -[Service] -Type=oneshot -ExecStart=/usr/bin/snap auto-import - -[Install] -WantedBy=multi-user.target diff -Nru snapd-2.27.5/packaging/ubuntu-14.04/snapd.install snapd-2.28.5/packaging/ubuntu-14.04/snapd.install --- snapd-2.27.5/packaging/ubuntu-14.04/snapd.install 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/packaging/ubuntu-14.04/snapd.install 2017-09-13 14:47:18.000000000 +0000 @@ -7,10 +7,6 @@ usr/bin/snapd /usr/lib/snapd/ usr/bin/snap-seccomp /usr/lib/snapd/ -# etc/profile.d contains the PATH extension for snap packages -etc/profile.d -# etc/X11/Xsession.d will add to XDG_DATA_DIRS so that we have .desktop support -etc/X11 # bash completion data/completion/snap /usr/share/bash-completion/completions data/completion/complete.sh /usr/lib/snapd/ @@ -19,6 +15,8 @@ data/udev/rules.d/66-snapd-autoimport.rules /lib/udev/rules.d # snap/snapd version information data/info /usr/lib/snapd/ +# polkit actions +data/polkit/io.snapcraft.snapd.policy /usr/share/polkit-1/actions/ # snap-confine stuff etc/apparmor.d/usr.lib.snapd.snap-confine diff -Nru snapd-2.27.5/packaging/ubuntu-14.04/snapd.refresh.service snapd-2.28.5/packaging/ubuntu-14.04/snapd.refresh.service --- snapd-2.27.5/packaging/ubuntu-14.04/snapd.refresh.service 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/packaging/ubuntu-14.04/snapd.refresh.service 1970-01-01 00:00:00.000000000 +0000 @@ -1,11 +0,0 @@ -[Unit] -Description=Automatically refresh installed snaps -After=network-online.target snapd.socket -Requires=snapd.socket -ConditionPathExistsGlob=/snap/*/current -Documentation=man:snap(1) - -[Service] -Type=oneshot -ExecStart=/usr/bin/snap refresh -Environment=SNAP_REFRESH_FROM_TIMER=1 diff -Nru snapd-2.27.5/packaging/ubuntu-14.04/snapd.refresh.timer snapd-2.28.5/packaging/ubuntu-14.04/snapd.refresh.timer --- snapd-2.27.5/packaging/ubuntu-14.04/snapd.refresh.timer 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/packaging/ubuntu-14.04/snapd.refresh.timer 1970-01-01 00:00:00.000000000 +0000 @@ -1,14 +0,0 @@ -[Unit] -Description=Timer to automatically refresh installed snaps - -[Timer] -# spread the requests gently -# https://bugs.launchpad.net/snappy/+bug/1537793 -OnCalendar=23,05,11,17:00 -RandomizedDelaySec=6h -AccuracySec=10min -Persistent=true -OnStartupSec=15m - -[Install] -WantedBy=timers.target diff -Nru snapd-2.27.5/packaging/ubuntu-14.04/snapd.service snapd-2.28.5/packaging/ubuntu-14.04/snapd.service --- snapd-2.27.5/packaging/ubuntu-14.04/snapd.service 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/packaging/ubuntu-14.04/snapd.service 1970-01-01 00:00:00.000000000 +0000 @@ -1,12 +0,0 @@ -[Unit] -Description=Snappy daemon -Requires=snapd.socket - -[Service] -ExecStart=/usr/lib/snapd/snapd -EnvironmentFile=/etc/environment -Restart=always -Type=notify - -[Install] -WantedBy=multi-user.target diff -Nru snapd-2.27.5/packaging/ubuntu-14.04/snapd.socket snapd-2.28.5/packaging/ubuntu-14.04/snapd.socket --- snapd-2.27.5/packaging/ubuntu-14.04/snapd.socket 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/packaging/ubuntu-14.04/snapd.socket 1970-01-01 00:00:00.000000000 +0000 @@ -1,13 +0,0 @@ -[Unit] -Description=Socket activation for snappy daemon - -[Socket] -ListenStream=/run/snapd.socket -ListenStream=/run/snapd-snap.socket -SocketMode=0666 -# these are the defaults, but can't hurt to specify them anyway: -SocketUser=root -SocketGroup=root - -[Install] -WantedBy=sockets.target diff -Nru snapd-2.27.5/packaging/ubuntu-14.04/snapd.system-shutdown.service snapd-2.28.5/packaging/ubuntu-14.04/snapd.system-shutdown.service --- snapd-2.27.5/packaging/ubuntu-14.04/snapd.system-shutdown.service 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/packaging/ubuntu-14.04/snapd.system-shutdown.service 1970-01-01 00:00:00.000000000 +0000 @@ -1,15 +0,0 @@ -[Unit] -Description=Ubuntu core (all-snaps) system shutdown helper setup service -Before=umount.target -DefaultDependencies=false -# don't run on classic -ConditionKernelCommandLine=snap_core -# don't run if system-shutdown isn't there -ConditionPathExists=/usr/lib/snapd/system-shutdown - -[Service] -Type=oneshot -ExecStart=/bin/sh -euc 'mount /run -o remount,exec; mkdir -p /run/initramfs; cp /usr/lib/snapd/system-shutdown /run/initramfs/shutdown' - -[Install] -WantedBy=final.target diff -Nru snapd-2.27.5/packaging/ubuntu-16.04/changelog snapd-2.28.5/packaging/ubuntu-16.04/changelog --- snapd-2.27.5/packaging/ubuntu-16.04/changelog 2017-08-30 05:32:20.000000000 +0000 +++ snapd-2.28.5/packaging/ubuntu-16.04/changelog 2017-10-13 21:25:46.000000000 +0000 @@ -1,3 +1,331 @@ +snapd (2.28.5) xenial; urgency=medium + + * New upstream release, LP: #1714984 + - snap-confine: cleanup broken nvidia udev tags + - cmd/snap-confine: update valid security tag regexp + - overlord/ifacestate: refresh udev backend on startup + - dbus: ensure io.snapcraft.Launcher.service is created on re- + exec + - snap-confine: add support for handling /dev/nvidia-modeset + - interfaces/network-control: remove incorrect rules for tun + + -- Michael Vogt Fri, 13 Oct 2017 23:25:46 +0200 + +snapd (2.28.4) xenial; urgency=medium + + * New upstream release, LP: #1714984 + - interfaces/opengl: don't udev tag nvidia devices and use snap- + confine instead + - debian: fix replaces/breaks for snap-xdg-open (thanks to apw!) + + -- Michael Vogt Wed, 11 Oct 2017 19:40:57 +0200 + +snapd (2.28.3) xenial; urgency=medium + + * New upstream release, LP: #1714984 + - interfaces/lxd: lxd slot implementation can also be an app + snap + + -- Michael Vogt Wed, 11 Oct 2017 08:20:26 +0200 + +snapd (2.28.2) xenial; urgency=medium + + * New upstream release, LP: #1714984 + - interfaces: fix udev rules for tun + - release,cmd,dirs: Redo the distro checks to take into account + distribution families + + -- Michael Vogt Tue, 10 Oct 2017 18:39:58 +0200 + +snapd (2.28.1) xenial; urgency=medium + + * New upstream release, LP: #1714984 + - snap-confine: update apparmor rules for fedora based basesnaps + - snapstate: rename refresh hook to post-refresh for consistency + + -- Michael Vogt Wed, 27 Sep 2017 17:59:49 -0400 + +snapd (2.28) xenial; urgency=medium + + * New upstream release, LP: #1714984 + - hooks: rename refresh to after-refresh + - snap-confine: bind mount /usr/lib/snapd relative to snap-confine + - cmd,dirs: treat "liri" the same way as "arch" + - snap-confine: fix base snaps on core + - hooks: substitute env vars when executing hooks + - interfaces: updates for default, browser-support, desktop, opengl, + upower and stub-resolv.conf + - cmd,dirs: treat manjaro the same as arch + - systemd: do not run auto-import and repair services on classic + - packaging/fedora: Ensure vendor/ is empty for builds and fix spec + to build current master + - many: fix TestSetConfNumber missing an Unlock and other fragility + improvements + - osutil: adjust StreamCommand tests for golang 1.9 + - daemon: allow polkit authorisation to install/remove snaps + - tests: make TestCmdWatch more robust + - debian: improve package description + - interfaces: add netlink kobject uevent to hardware observe + - debian: update trusted account-keys check on 14.04 packaging + - interfaces/network-{control,observe}: allow receiving + kobject_uevent() messages + - tests: fix lxd test for external backend + - snap-confine,snap-update-ns: add -no-pie to fix FTBFS on + go1.7,ppc64 + - corecfg: mock "systemctl" in all corecfg tests + - tests: fix unit tests on Ubuntu 14.04 + - debian: add missing flags when building static snap-exec + - many: end-to-end support for the bare base snap + - overlord/snapstate: SetRootDir from SetUpTest, not in just some + tests + - store: have an ad-hoc method on cfg to get its list of uris for + tests + - daemon: let client decide whether to allow interactive auth via + polkit + - client,daemon,snap,store: add license field + - overlord/snapstate: rename HasCurrent to IsInstalled, remove + superfluous/misleading check from All + - cmd/snap: SetRootDir from SetUpTest, not in just some individual + tests. + - systemd: rename snap-repair.{service,timer} to snapd.snap- + repair.{service,timer} + - snap-seccomp: remove use of x/net/bpf from tests + - httputil: more naive per go version way to recreate a default + transport for tls reconfig + - cmd/snap-seccomp/main_test.go: add one more syscall for arm64 + - interfaces/opengl: use == to compare, not = + - cmd/snap-seccomp/main_test.go: add syscalls for armhf and arm64 + - cmd/snap-repair: track and use a lower bound for the time for + TLS checks + - interfaces: expose bluez interface on classic OS + - snap-seccomp: add in-kernel bpf tests + - overlord: always try to get a serial, lazily on classic + - tests: add nmcli regression test + - tests: deal with __PNR_chown on aarch64 to fix FTBFS on arm64 + - tests: add autopilot-introspection interface test + - vendor: fix artifact from manually editing vendor/vendor.json + - tests: rename complexion to test-snapd-complexion + - interfaces: add desktop and desktop-legacy + interfaces/desktop: add new 'desktop' interface for modern DEs + interfaces/builtin/desktop_test.go: use modern testing techniques + interfaces/wayland: allow read on /etc/drirc for Plasma desktop + interfaces/desktop-legacy: add new 'legacy' interface (currently + for a11y and input) + - tests: fix race in snap userd test + - devices/iio: add read/write for missing sysfs entries + - spread: don't set HTTPS?_PROXY for linode + - cmd/snap-repair: check signatures of repairs from Next + - env: set XDG_DATA_DIRS for wayland et.al. + - interfaces/{default,account-control}: Use username/group instead + of uid/gid + - interfaces/builtin: use udev tagging more broadly + - tests: add basic lxd test + - wrappers: ensure bash completion snaps install on core + - vendor: use old golang.org/x/crypto/ssh/terminal to build on + powerpc again + - docs: add PULL_REQUEST_TEMPLATE.md + - interfaces: fix network-manager plug + - hooks: do not error out when hook is optional and no hook handler + is registered + - cmd/snap: add userd command to replace snapd-xdg-open + - tests: new regex used to validate the core version on extra snaps + ass... + - snap: add new `snap switch` command + - tests: wait more and more debug info about fakestore start issues + - apparmor,release: add better apparmor detection/mocking code + - interfaces/i2c: adjust sysfs rule for alternate paths + - interfaces/apparmor: add missing call to dirs.SetRootDir + - cmd: "make hack" now also installs snap-update-ns + - tests: copy files with less verbosity + - cmd/snap-confine: allow using additional libraries required by + openSUSE + - packaging/fedora: Merge changes from Fedora Dist-Git + - snapstate: improve the error message when classic confinement is + not supported + - tests: add test to ensure amd64 can run i386 syscall binaries + - tests: adding extra info for fakestore when fails to start + - tests: install most important snaps + - cmd/snap-repair: more test coverage of filtering + - squashfs: remove runCommand/runCommandWithOutput as we do not need + it + - cmd/snap-repair: ignore superseded revisions, filter on arch and + models + - hooks: support for refresh hook + - Partial revert "overlord/devicestate, store: update device auth + endpoints URLs" + - cmd/snap-confine: allow reading /proc/filesystems + - cmd/snap-confine: genearlize apparmor profile for various lib + layout + - corecfg: fix proxy.* writing and add integration test + - corecfg: deal with system.power-key-action="" correctly + - vendor: update vendor.json after (presumed) manual edits + - cmd/snap: in `snap info`, don't print a newline between tracks + - daemon: add polkit support to /v2/login + - snapd,snapctl: decode json using Number + - client: fix go vet 1.7 errors + - tests: make 17.04 shellcheck clean + - tests: remove TestInterfacesHelp as it breaks when go-flags + changes + - snapstate: undo a daemon restart on classic if needed + - cmd/snap-repair: recover brand/model from + /var/lib/snapd/seed/assertions checking signatures and brand + account + - spread: opt into unsafe IO during spread tests + - snap-repair: update snap-repair/runner_test.go for API change in + makeMockServer + - cmd/snap-repair: skeleton code around actually running a repair + - tests: wait until the port is listening after start the fake store + - corecfg: fix typo in tests + - cmd/snap-repair: test that redirects works during fetching + - osutil: honor SNAPD_UNSAFE_IO for testing + - vendor: explode and make more precise our golang.go/x/crypto deps, + use same version as Debian unstable + - many: sanitize NewStoreStack signature, have shared default store + test private keys + - systemd: disable `Nice=-5` to fix error when running inside lxd + - spread.yaml: update delta ref to 2.27 + - cmd/snap-repair: use E-Tags when refetching a repair to retry + - interfaces/many: updates based on chromium and mrrescue denials + - cmd/snap-repair: implement most logic to get the next repair to + run/retry in a brand sequence + - asserts/assertstest: copy headers in SigningDB.Sign + - interfaces: convert uhid to common interface and test cases + improvement for time_control and opengl + - many tests: move all panicing fake store methods to a common place + - asserts: add store assertion type + - interfaces: don't crash if content slot has no attributes + - debian: do not build with -buildmode=pie on i386 + - wrappers: symlink completion snippets when symlinking binaries + - tests: adding more debug information for the interfaces-cups- + control … + - apparmor: pass --quiet to parser on load unless SNAPD_DEBUG is set + - many: allow and support serials signed by the 'generic' authority + instead of the brand + - corecfg: add proxy configuration via `snap set core + proxy.{http,https,ftp}=...` + - interfaces: a bunch of interfaces test improvement + - tests: enable regression and completion suites for opensuse + - tests: installing snapd for nested test suite + - interfaces: convert lxd_support to common iface + - interfaces: add missing test for camera interface. + - snap: add support for parsing snap layout section + - cmd/snap-repair: like for downloads we cannot have a timeout (at + least for now), less aggressive retry strategies + - overlord: rely on more conservative ensure interval + - overlord,store: no piles of return args for methods gathering + device session request params + - overlord,store: send model assertion when setting up device + sessions + - interfaces/misc: updates for unity7/x11, browser- + support, network-control and mount-observe + interfaces/unity7,x11: update for NETLINK_KOBJECT_UEVENT + interfaces/browser-support: update sysfs reads for + newer browser versions, interfaces/network-control: rw for + ieee80211 advanced wireless interfaces/mount-observe: allow read + on sysfs entries for block devices + - tests: use dnf --refresh install to avert stale cache + - osutil: ensure TestLockUnlockWorks uses supported flock + - interfaces: convert lxd to common iface + - tests: restart snapd to ensure re-exec settings are applied + - tests: fix interfaces-cups-control test + - interfaces: improve and tweak bunch of interfaces test cases. + - tests: adding extra worker for fedora + - asserts,overlord/devicestate: support predefined assertions that + don't establish foundational trust + - interfaces: convert two hardware_random interfaces to common iface + - interfaces: convert io_ports_control to common iface + - tests: fix for upgrade test on fedora + - daemon, client, cmd/snap: implement snap start/stop/restart + - cmd/snap-confine: set _FILE_OFFSET_BITS to 64 + - interfaces: covert framebuffer to commonInterface + - interfaces: convert joystick to common iface + - interfaces/builtin: add the spi interface + - wrappers, overlord/snapstate/backend: make link-snap clean up on + failure. + - interfaces/wayland: add wayland interface + - interfaces: convert kvm to common iface + - tests: extend upower-observe test to cover snaps providing slots + - tests: enable main suite for opensuse + - interfaces: convert physical_memory_observe to common iface + - interfaces: add missing test for optical_drive interface. + - interfaces: convert physical_memory_control to common iface + - interfaces: convert ppp to common iface + - interfaces: convert time-control to common iface + - tests: fix failover test + - interfaces/builtin: rework for avahi interface + - interfaces: convert broadcom-asic-control to common iface + - snap/snapenv: document the use of CoreSnapMountDir for SNAP + - packaging/arch: drop patches merged into master + - cmd: fix mustUnsetenv docstring (thanks to Chipaca) + - release: remove default from VERSION_ID + - tests: enable regression, upgrade and completion test suites for + fedora + - tests: restore interfaces-account-control properly + - overlord/devicestate, store: update device auth endpoints URLs + - tests: fix install-hook test failure + - tests: download core and ubuntu-core at most once + - interfaces: add common support for udev + - overlord/devicestate: fix, don't assume that the serial is backed + by a 1-key chain + - cmd/snap-confine: don't share /etc/nsswitch from host + - store: do not resume a download when we already have the whole + thing + - many: implement "snap logs" + - store: don't call useDeltas() twice in quick succession + - interfaces/builtin: add kvm interface + - snap/snapenv: always expect /snap for $SNAP + - cmd: mark arch as non-reexecing distro + - cmd: fix tests that assume /snap mount + - gitignore: ignore more build artefacts + - packaging: add current arch packaging + - interfaces/unity7: allow receiving media key events in (at least) + gnome-shell + - interfaces/many, cmd/snap-confine: miscellaneous policy updates + - interfaces/builtin: implement broadcom-asic-control interface + - interfaces/builtin: reduce duplication and remove cruft in + Sanitize{Plug,Slot} + - tests: apply underscore convention for SNAPMOUNTDIR variable + - interfaces/greengrass-support: adjust accesses now that have + working snap + - daemon, client, cmd/snap: implement "snap services" + - tests: fix refresh tests not stopping fake store for fedora + - many: add the interface command + - overlord/snapstate/backend: some copydata improvements + - many: support querying and completing assertion type names + - interfaces/builtin: discard empty Validate{Plug,Slot} + - cmd/snap-repair: start of Runner, implement first pass of Peek + and Fetch + - tests: enable main suite on fedora + - snap: do not always quote the snap info summary + - vendor: update go-flags to address crash in "snap debug" + - interfaces: opengl support pci device and vendor + - many: start implenting "base" snap type on the snapd side + - arch,release: map armv6 correctly + - many: expose service status in 'snap info' + - tests: add browser-support interface test + - tests: disable snapd-notify for the external backend + - interfaces: Add /run/uuid/request to openvswitch + - interfaces: add password-manager-service implicit classic + interface + - cmd: rework reexec detection + - cmd: fix re-exec bug when starting from snapd 2.21 + - tests: dependency packages installed during prepare-project + - tests: remove unneeded check for re-exec in InternalToolPath() + - cmd,tests: fix classic confinement confusing re-execution code + - store: configurable base api + - tests: fix how package lists are updated for opensuse and fedora + + -- Michael Vogt Mon, 25 Sep 2017 12:07:34 -0400 + +snapd (2.27.6) xenial; urgency=medium + + * New upstream release, LP: #1703798: + - interfaces: add udev netlink support to hardware-observe + - interfaces/network-{control,observe}: allow receiving + kobject_uevent() messages + + -- Michael Vogt Thu, 07 Sep 2017 10:22:18 +0200 + snapd (2.27.5) xenial; urgency=medium * New upstream release, LP: #1703798: @@ -322,6 +650,29 @@ -- Michael Vogt Thu, 10 Aug 2017 12:43:16 +0200 +snapd (2.26.14) xenial; urgency=medium + + * New upstream release, LP: #1690083 + - cmd: fix incorrect re-exec when starting from snapd 2.21 + + -- Michael Vogt Thu, 20 Jul 2017 13:52:05 +0200 + +snapd (2.26.13) xenial; urgency=medium + + * New upstream release, LP: #1690083 + - cmd,tests: fix classic confinement confusing re-execution code + - cmd: fix incorrect check check for re-exec in InternalToolPath() + - snap-seccomp: add secondary arch for unrestricted snaps as well + + -- Michael Vogt Tue, 18 Jul 2017 20:34:33 +0200 + +snapd (2.26.10) xenial; urgency=medium + + * New upstream release, LP: #1690083 + - Fix snap-seccomp tests in artful/trusty on i386/s390x/aarch64 + + -- Michael Vogt Mon, 17 Jul 2017 11:58:22 +0200 + snapd (2.26.9) xenial; urgency=medium * New upstream release, LP: #1690083 diff -Nru snapd-2.27.5/packaging/ubuntu-16.04/control snapd-2.28.5/packaging/ubuntu-16.04/control --- snapd-2.27.5/packaging/ubuntu-16.04/control 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/packaging/ubuntu-16.04/control 2017-10-11 17:40:25.000000000 +0000 @@ -7,6 +7,7 @@ autotools-dev, bash-completion, debhelper (>= 9), + dbus, dh-apparmor, dh-autoreconf, dh-golang (>=1.7), @@ -64,11 +65,11 @@ systemd, ${misc:Depends}, ${shlibs:Depends} -Replaces: ubuntu-snappy (<< 1.9), ubuntu-snappy-cli (<< 1.9), snap-confine (<< 2.23), ubuntu-core-launcher (<< 2.22) -Breaks: ubuntu-snappy (<< 1.9), ubuntu-snappy-cli (<< 1.9), snap-confine (<< 2.23), ubuntu-core-launcher (<< 2.22) +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) Conflicts: snap (<< 2013-11-29-1ubuntu1) Built-Using: ${Built-Using} ${misc:Built-Using} -Description: Tool to interact with Ubuntu Core Snappy. +Description: Daemon and tooling that enable snap packages Install, configure, refresh and remove snap packages. Snaps are 'universal' packages that work across many different Linux systems, enabling secure distribution of the latest apps and utilities for @@ -112,3 +113,11 @@ Pre-Depends: dpkg (>= 1.15.7.2) Description: Transitional package for snapd This is a transitional dummy package. It can safely be removed. + +Package: snapd-xdg-open +Architecture: any +Depends: snapd (= ${binary:Version}), ${misc:Depends} +Section: oldlibs +Pre-Depends: dpkg (>= 1.15.7.2) +Description: Transitional package for snapd-xdg-open + This is a transitional dummy package. It can safely be removed. diff -Nru snapd-2.27.5/packaging/ubuntu-16.04/rules snapd-2.28.5/packaging/ubuntu-16.04/rules --- snapd-2.27.5/packaging/ubuntu-16.04/rules 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/packaging/ubuntu-16.04/rules 2017-09-13 14:47:18.000000000 +0000 @@ -1,5 +1,13 @@ #!/usr/bin/make -f # -*- makefile -*- +# +# These rules should work for any debian-ish distro that uses systemd +# as init. That does _not_ include Ubuntu 14.04 ("trusty"); look for +# its own special rule file. +# +# Please keep the diff between that and this relatively small, even if +# it means having suboptimal code; these need to be kept in sync by +# sentient bags of meat. #export DH_VERBOSE=1 export DH_OPTIONS @@ -24,8 +32,10 @@ # Disable -buildmode=pie mode on i386 as can panics in spectacular # ways (LP: #1711052). # See also https://forum.snapcraft.io/t/artful-i386-panics/ +# Note while the panic is only on artful, that's because artful +# detects it; the issue potentially there on older things. BUILDFLAGS:=-pkgdir=$(CURDIR)/_build/std -ifneq ($(shell dpkg-architecture -qDEB_TARGET_ARCH),i386) +ifneq ($(shell dpkg-architecture -qDEB_HOST_ARCH),i386) BUILDFLAGS+= -buildmode=pie endif @@ -53,7 +63,7 @@ # because derivatives may have different kernels that don't support all the # required confinement features and we don't to mislead anyone about the # security of the system. Discuss a proper approach to this for downstreams -# if and when they approach us +# if and when they approach us. ifeq ($(shell dpkg-vendor --query Vendor),Ubuntu) # On Ubuntu 16.04 we need to produce a build that can be used on wide # variety of systems. As such we prefer static linking over dynamic linking @@ -103,9 +113,9 @@ ) endif dh_clean + $(MAKE) -C data clean # XXX: hacky $(MAKE) -C cmd distclean || true - $(MAKE) -C data/systemd clean override_dh_auto_build: # usually done via `go generate` but that is not supported on powerpc @@ -114,21 +124,29 @@ mkdir -p _build/src/$(DH_GOPKG)/cmd/snap/test-data cp -a cmd/snap/test-data/*.gpg _build/src/$(DH_GOPKG)/cmd/snap/test-data/ dh_auto_build -- $(BUILDFLAGS) $(TAGS) $(GCCGOFLAGS) + # Generate static snap-exec - it somehow includes CGO so we must + # force a static build here. We need a static snap-exec inside + # the core snap because not all bases will have a libc + (cd _build/bin && GOPATH=$$(pwd)/.. CGO_ENABLED=0 go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snap-exec) + # ensure we generated a static build + $(shell if ldd _build/bin/snap-exec; then false "need static build"; fi) + # Build C bits, sadly manually cd cmd && ( autoreconf -i -f ) cd cmd && ( ./configure --prefix=/usr --libexecdir=/usr/lib/snapd $(VENDOR_ARGS)) $(MAKE) -C cmd all - # Generate the real systemd units out of the available templates - $(MAKE) -C data/systemd all + # Generate the real systemd/dbus/env config files + $(MAKE) -C data all override_dh_auto_test: dh_auto_test -- $(GCCGOFLAGS) # a tested default (production) build should have no test keys ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))) - # check that only the main trusted account-key is included - [ $$(strings _build/bin/snapd|grep -c -E "public-key-sha3-384: [a-zA-Z0-9_-]{64}") -eq 1 ] + # check that only the main trusted account-keys are included + [ $$(strings _build/bin/snapd|grep -c -E "public-key-sha3-384: [a-zA-Z0-9_-]{64}") -eq 2 ] strings _build/bin/snapd|grep -c "^public-key-sha3-384: -CvQKAwRQ5h3Ffn10FILJoEZUXOv6km9FwA80-Rcj-f-6jadQ89VRswHNiEB9Lxk$$" + strings _build/bin/snapd|grep -c "^public-key-sha3-384: d-JcZF9nD9eBw7bwMnH61x-bklnQOhQud1Is6o_cn2wTj8EYDi9musrIT9z2MdAa$$" endif ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))) # run the snap-confine tests @@ -152,12 +170,12 @@ # we want the repair timer enabled by default dh_systemd_enable \ -psnapd \ - data/systemd/snap-repair.timer + data/systemd/snapd.snap-repair.timer # but the repair service disabled dh_systemd_enable \ --no-enable \ -psnapd \ - data/systemd/snap-repair.service + data/systemd/snapd.snap-repair.service # enable snapd dh_systemd_enable \ -psnapd \ @@ -210,10 +228,14 @@ cp -R share/locale debian/snapd/usr/share; \ fi - # install snapd's systemd units, done here instead of - # debian/snapd.install because the ubuntu/14.04 release - # branch adds/changes bits here - $(MAKE) -C data/systemd install DESTDIR=$(CURDIR)/debian/snapd/ SYSTEMDSYSTEMUNITDIR=$(SYSTEMD_UNITS_DESTDIR) + # install snapd's systemd units / upstart jobs, done + # here instead of debian/snapd.install because the + # ubuntu/14.04 release branch adds/changes bits here + $(MAKE) -C data install DESTDIR=$(CURDIR)/debian/snapd/ \ + SYSTEMDSYSTEMUNITDIR=$(SYSTEMD_UNITS_DESTDIR) + # we called this apps-bin-path.sh instead of snapd.sh, and + # it's a conf file so we're stuck with it + mv debian/snapd/etc/profile.d/snapd.sh debian/snapd/etc/profile.d/apps-bin-path.sh $(MAKE) -C cmd install DESTDIR=$(CURDIR)/debian/tmp @@ -227,6 +249,7 @@ install -d $(CURDIR)/debian/tmp/etc/apparmor.d touch $(CURDIR)/debian/tmp/etc/apparmor.d/usr.lib.snapd.snap-confine.real endif + dh_install override_dh_auto_install: snap.8 diff -Nru snapd-2.27.5/packaging/ubuntu-16.04/snapd.install snapd-2.28.5/packaging/ubuntu-16.04/snapd.install --- snapd-2.27.5/packaging/ubuntu-16.04/snapd.install 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/packaging/ubuntu-16.04/snapd.install 2017-09-13 14:47:18.000000000 +0000 @@ -7,10 +7,6 @@ usr/bin/snapd /usr/lib/snapd/ usr/bin/snap-seccomp /usr/lib/snapd/ -# etc/profile.d contains the PATH extension for snap packages -etc/profile.d -# etc/X11/Xsession.d will add to XDG_DATA_DIRS so that we have .desktop support -etc/X11 # bash completion data/completion/snap /usr/share/bash-completion/completions data/completion/complete.sh /usr/lib/snapd/ @@ -19,6 +15,8 @@ data/udev/rules.d/66-snapd-autoimport.rules /lib/udev/rules.d # snap/snapd version information data/info /usr/lib/snapd/ +# polkit actions +data/polkit/io.snapcraft.snapd.policy /usr/share/polkit-1/actions/ # snap-confine stuff etc/apparmor.d/usr.lib.snapd.snap-confine.real diff -Nru snapd-2.27.5/packaging/ubuntu-16.04/tests/control snapd-2.28.5/packaging/ubuntu-16.04/tests/control --- snapd-2.27.5/packaging/ubuntu-16.04/tests/control 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/packaging/ubuntu-16.04/tests/control 2017-09-13 14:47:18.000000000 +0000 @@ -1,5 +1,5 @@ Tests: integrationtests -Restrictions: allow-stderr, isolation-container, rw-build-tree, needs-root, breaks-testbed +Restrictions: allow-stderr, rw-build-tree, needs-root, breaks-testbed, isolation-machine Depends: @builddeps@, bzr, ca-certificates, diff -Nru snapd-2.27.5/packaging/ubuntu-16.10/changelog snapd-2.28.5/packaging/ubuntu-16.10/changelog --- snapd-2.27.5/packaging/ubuntu-16.10/changelog 2017-08-30 05:32:20.000000000 +0000 +++ snapd-2.28.5/packaging/ubuntu-16.10/changelog 2017-10-13 21:25:46.000000000 +0000 @@ -1,3 +1,331 @@ +snapd (2.28.5) xenial; urgency=medium + + * New upstream release, LP: #1714984 + - snap-confine: cleanup broken nvidia udev tags + - cmd/snap-confine: update valid security tag regexp + - overlord/ifacestate: refresh udev backend on startup + - dbus: ensure io.snapcraft.Launcher.service is created on re- + exec + - snap-confine: add support for handling /dev/nvidia-modeset + - interfaces/network-control: remove incorrect rules for tun + + -- Michael Vogt Fri, 13 Oct 2017 23:25:46 +0200 + +snapd (2.28.4) xenial; urgency=medium + + * New upstream release, LP: #1714984 + - interfaces/opengl: don't udev tag nvidia devices and use snap- + confine instead + - debian: fix replaces/breaks for snap-xdg-open (thanks to apw!) + + -- Michael Vogt Wed, 11 Oct 2017 19:40:57 +0200 + +snapd (2.28.3) xenial; urgency=medium + + * New upstream release, LP: #1714984 + - interfaces/lxd: lxd slot implementation can also be an app + snap + + -- Michael Vogt Wed, 11 Oct 2017 08:20:26 +0200 + +snapd (2.28.2) xenial; urgency=medium + + * New upstream release, LP: #1714984 + - interfaces: fix udev rules for tun + - release,cmd,dirs: Redo the distro checks to take into account + distribution families + + -- Michael Vogt Tue, 10 Oct 2017 18:39:58 +0200 + +snapd (2.28.1) xenial; urgency=medium + + * New upstream release, LP: #1714984 + - snap-confine: update apparmor rules for fedora based basesnaps + - snapstate: rename refresh hook to post-refresh for consistency + + -- Michael Vogt Wed, 27 Sep 2017 17:59:49 -0400 + +snapd (2.28) xenial; urgency=medium + + * New upstream release, LP: #1714984 + - hooks: rename refresh to after-refresh + - snap-confine: bind mount /usr/lib/snapd relative to snap-confine + - cmd,dirs: treat "liri" the same way as "arch" + - snap-confine: fix base snaps on core + - hooks: substitute env vars when executing hooks + - interfaces: updates for default, browser-support, desktop, opengl, + upower and stub-resolv.conf + - cmd,dirs: treat manjaro the same as arch + - systemd: do not run auto-import and repair services on classic + - packaging/fedora: Ensure vendor/ is empty for builds and fix spec + to build current master + - many: fix TestSetConfNumber missing an Unlock and other fragility + improvements + - osutil: adjust StreamCommand tests for golang 1.9 + - daemon: allow polkit authorisation to install/remove snaps + - tests: make TestCmdWatch more robust + - debian: improve package description + - interfaces: add netlink kobject uevent to hardware observe + - debian: update trusted account-keys check on 14.04 packaging + - interfaces/network-{control,observe}: allow receiving + kobject_uevent() messages + - tests: fix lxd test for external backend + - snap-confine,snap-update-ns: add -no-pie to fix FTBFS on + go1.7,ppc64 + - corecfg: mock "systemctl" in all corecfg tests + - tests: fix unit tests on Ubuntu 14.04 + - debian: add missing flags when building static snap-exec + - many: end-to-end support for the bare base snap + - overlord/snapstate: SetRootDir from SetUpTest, not in just some + tests + - store: have an ad-hoc method on cfg to get its list of uris for + tests + - daemon: let client decide whether to allow interactive auth via + polkit + - client,daemon,snap,store: add license field + - overlord/snapstate: rename HasCurrent to IsInstalled, remove + superfluous/misleading check from All + - cmd/snap: SetRootDir from SetUpTest, not in just some individual + tests. + - systemd: rename snap-repair.{service,timer} to snapd.snap- + repair.{service,timer} + - snap-seccomp: remove use of x/net/bpf from tests + - httputil: more naive per go version way to recreate a default + transport for tls reconfig + - cmd/snap-seccomp/main_test.go: add one more syscall for arm64 + - interfaces/opengl: use == to compare, not = + - cmd/snap-seccomp/main_test.go: add syscalls for armhf and arm64 + - cmd/snap-repair: track and use a lower bound for the time for + TLS checks + - interfaces: expose bluez interface on classic OS + - snap-seccomp: add in-kernel bpf tests + - overlord: always try to get a serial, lazily on classic + - tests: add nmcli regression test + - tests: deal with __PNR_chown on aarch64 to fix FTBFS on arm64 + - tests: add autopilot-introspection interface test + - vendor: fix artifact from manually editing vendor/vendor.json + - tests: rename complexion to test-snapd-complexion + - interfaces: add desktop and desktop-legacy + interfaces/desktop: add new 'desktop' interface for modern DEs + interfaces/builtin/desktop_test.go: use modern testing techniques + interfaces/wayland: allow read on /etc/drirc for Plasma desktop + interfaces/desktop-legacy: add new 'legacy' interface (currently + for a11y and input) + - tests: fix race in snap userd test + - devices/iio: add read/write for missing sysfs entries + - spread: don't set HTTPS?_PROXY for linode + - cmd/snap-repair: check signatures of repairs from Next + - env: set XDG_DATA_DIRS for wayland et.al. + - interfaces/{default,account-control}: Use username/group instead + of uid/gid + - interfaces/builtin: use udev tagging more broadly + - tests: add basic lxd test + - wrappers: ensure bash completion snaps install on core + - vendor: use old golang.org/x/crypto/ssh/terminal to build on + powerpc again + - docs: add PULL_REQUEST_TEMPLATE.md + - interfaces: fix network-manager plug + - hooks: do not error out when hook is optional and no hook handler + is registered + - cmd/snap: add userd command to replace snapd-xdg-open + - tests: new regex used to validate the core version on extra snaps + ass... + - snap: add new `snap switch` command + - tests: wait more and more debug info about fakestore start issues + - apparmor,release: add better apparmor detection/mocking code + - interfaces/i2c: adjust sysfs rule for alternate paths + - interfaces/apparmor: add missing call to dirs.SetRootDir + - cmd: "make hack" now also installs snap-update-ns + - tests: copy files with less verbosity + - cmd/snap-confine: allow using additional libraries required by + openSUSE + - packaging/fedora: Merge changes from Fedora Dist-Git + - snapstate: improve the error message when classic confinement is + not supported + - tests: add test to ensure amd64 can run i386 syscall binaries + - tests: adding extra info for fakestore when fails to start + - tests: install most important snaps + - cmd/snap-repair: more test coverage of filtering + - squashfs: remove runCommand/runCommandWithOutput as we do not need + it + - cmd/snap-repair: ignore superseded revisions, filter on arch and + models + - hooks: support for refresh hook + - Partial revert "overlord/devicestate, store: update device auth + endpoints URLs" + - cmd/snap-confine: allow reading /proc/filesystems + - cmd/snap-confine: genearlize apparmor profile for various lib + layout + - corecfg: fix proxy.* writing and add integration test + - corecfg: deal with system.power-key-action="" correctly + - vendor: update vendor.json after (presumed) manual edits + - cmd/snap: in `snap info`, don't print a newline between tracks + - daemon: add polkit support to /v2/login + - snapd,snapctl: decode json using Number + - client: fix go vet 1.7 errors + - tests: make 17.04 shellcheck clean + - tests: remove TestInterfacesHelp as it breaks when go-flags + changes + - snapstate: undo a daemon restart on classic if needed + - cmd/snap-repair: recover brand/model from + /var/lib/snapd/seed/assertions checking signatures and brand + account + - spread: opt into unsafe IO during spread tests + - snap-repair: update snap-repair/runner_test.go for API change in + makeMockServer + - cmd/snap-repair: skeleton code around actually running a repair + - tests: wait until the port is listening after start the fake store + - corecfg: fix typo in tests + - cmd/snap-repair: test that redirects works during fetching + - osutil: honor SNAPD_UNSAFE_IO for testing + - vendor: explode and make more precise our golang.go/x/crypto deps, + use same version as Debian unstable + - many: sanitize NewStoreStack signature, have shared default store + test private keys + - systemd: disable `Nice=-5` to fix error when running inside lxd + - spread.yaml: update delta ref to 2.27 + - cmd/snap-repair: use E-Tags when refetching a repair to retry + - interfaces/many: updates based on chromium and mrrescue denials + - cmd/snap-repair: implement most logic to get the next repair to + run/retry in a brand sequence + - asserts/assertstest: copy headers in SigningDB.Sign + - interfaces: convert uhid to common interface and test cases + improvement for time_control and opengl + - many tests: move all panicing fake store methods to a common place + - asserts: add store assertion type + - interfaces: don't crash if content slot has no attributes + - debian: do not build with -buildmode=pie on i386 + - wrappers: symlink completion snippets when symlinking binaries + - tests: adding more debug information for the interfaces-cups- + control … + - apparmor: pass --quiet to parser on load unless SNAPD_DEBUG is set + - many: allow and support serials signed by the 'generic' authority + instead of the brand + - corecfg: add proxy configuration via `snap set core + proxy.{http,https,ftp}=...` + - interfaces: a bunch of interfaces test improvement + - tests: enable regression and completion suites for opensuse + - tests: installing snapd for nested test suite + - interfaces: convert lxd_support to common iface + - interfaces: add missing test for camera interface. + - snap: add support for parsing snap layout section + - cmd/snap-repair: like for downloads we cannot have a timeout (at + least for now), less aggressive retry strategies + - overlord: rely on more conservative ensure interval + - overlord,store: no piles of return args for methods gathering + device session request params + - overlord,store: send model assertion when setting up device + sessions + - interfaces/misc: updates for unity7/x11, browser- + support, network-control and mount-observe + interfaces/unity7,x11: update for NETLINK_KOBJECT_UEVENT + interfaces/browser-support: update sysfs reads for + newer browser versions, interfaces/network-control: rw for + ieee80211 advanced wireless interfaces/mount-observe: allow read + on sysfs entries for block devices + - tests: use dnf --refresh install to avert stale cache + - osutil: ensure TestLockUnlockWorks uses supported flock + - interfaces: convert lxd to common iface + - tests: restart snapd to ensure re-exec settings are applied + - tests: fix interfaces-cups-control test + - interfaces: improve and tweak bunch of interfaces test cases. + - tests: adding extra worker for fedora + - asserts,overlord/devicestate: support predefined assertions that + don't establish foundational trust + - interfaces: convert two hardware_random interfaces to common iface + - interfaces: convert io_ports_control to common iface + - tests: fix for upgrade test on fedora + - daemon, client, cmd/snap: implement snap start/stop/restart + - cmd/snap-confine: set _FILE_OFFSET_BITS to 64 + - interfaces: covert framebuffer to commonInterface + - interfaces: convert joystick to common iface + - interfaces/builtin: add the spi interface + - wrappers, overlord/snapstate/backend: make link-snap clean up on + failure. + - interfaces/wayland: add wayland interface + - interfaces: convert kvm to common iface + - tests: extend upower-observe test to cover snaps providing slots + - tests: enable main suite for opensuse + - interfaces: convert physical_memory_observe to common iface + - interfaces: add missing test for optical_drive interface. + - interfaces: convert physical_memory_control to common iface + - interfaces: convert ppp to common iface + - interfaces: convert time-control to common iface + - tests: fix failover test + - interfaces/builtin: rework for avahi interface + - interfaces: convert broadcom-asic-control to common iface + - snap/snapenv: document the use of CoreSnapMountDir for SNAP + - packaging/arch: drop patches merged into master + - cmd: fix mustUnsetenv docstring (thanks to Chipaca) + - release: remove default from VERSION_ID + - tests: enable regression, upgrade and completion test suites for + fedora + - tests: restore interfaces-account-control properly + - overlord/devicestate, store: update device auth endpoints URLs + - tests: fix install-hook test failure + - tests: download core and ubuntu-core at most once + - interfaces: add common support for udev + - overlord/devicestate: fix, don't assume that the serial is backed + by a 1-key chain + - cmd/snap-confine: don't share /etc/nsswitch from host + - store: do not resume a download when we already have the whole + thing + - many: implement "snap logs" + - store: don't call useDeltas() twice in quick succession + - interfaces/builtin: add kvm interface + - snap/snapenv: always expect /snap for $SNAP + - cmd: mark arch as non-reexecing distro + - cmd: fix tests that assume /snap mount + - gitignore: ignore more build artefacts + - packaging: add current arch packaging + - interfaces/unity7: allow receiving media key events in (at least) + gnome-shell + - interfaces/many, cmd/snap-confine: miscellaneous policy updates + - interfaces/builtin: implement broadcom-asic-control interface + - interfaces/builtin: reduce duplication and remove cruft in + Sanitize{Plug,Slot} + - tests: apply underscore convention for SNAPMOUNTDIR variable + - interfaces/greengrass-support: adjust accesses now that have + working snap + - daemon, client, cmd/snap: implement "snap services" + - tests: fix refresh tests not stopping fake store for fedora + - many: add the interface command + - overlord/snapstate/backend: some copydata improvements + - many: support querying and completing assertion type names + - interfaces/builtin: discard empty Validate{Plug,Slot} + - cmd/snap-repair: start of Runner, implement first pass of Peek + and Fetch + - tests: enable main suite on fedora + - snap: do not always quote the snap info summary + - vendor: update go-flags to address crash in "snap debug" + - interfaces: opengl support pci device and vendor + - many: start implenting "base" snap type on the snapd side + - arch,release: map armv6 correctly + - many: expose service status in 'snap info' + - tests: add browser-support interface test + - tests: disable snapd-notify for the external backend + - interfaces: Add /run/uuid/request to openvswitch + - interfaces: add password-manager-service implicit classic + interface + - cmd: rework reexec detection + - cmd: fix re-exec bug when starting from snapd 2.21 + - tests: dependency packages installed during prepare-project + - tests: remove unneeded check for re-exec in InternalToolPath() + - cmd,tests: fix classic confinement confusing re-execution code + - store: configurable base api + - tests: fix how package lists are updated for opensuse and fedora + + -- Michael Vogt Mon, 25 Sep 2017 12:07:34 -0400 + +snapd (2.27.6) xenial; urgency=medium + + * New upstream release, LP: #1703798: + - interfaces: add udev netlink support to hardware-observe + - interfaces/network-{control,observe}: allow receiving + kobject_uevent() messages + + -- Michael Vogt Thu, 07 Sep 2017 10:22:18 +0200 + snapd (2.27.5) xenial; urgency=medium * New upstream release, LP: #1703798: @@ -322,6 +650,29 @@ -- Michael Vogt Thu, 10 Aug 2017 12:43:16 +0200 +snapd (2.26.14) xenial; urgency=medium + + * New upstream release, LP: #1690083 + - cmd: fix incorrect re-exec when starting from snapd 2.21 + + -- Michael Vogt Thu, 20 Jul 2017 13:52:05 +0200 + +snapd (2.26.13) xenial; urgency=medium + + * New upstream release, LP: #1690083 + - cmd,tests: fix classic confinement confusing re-execution code + - cmd: fix incorrect check check for re-exec in InternalToolPath() + - snap-seccomp: add secondary arch for unrestricted snaps as well + + -- Michael Vogt Tue, 18 Jul 2017 20:34:33 +0200 + +snapd (2.26.10) xenial; urgency=medium + + * New upstream release, LP: #1690083 + - Fix snap-seccomp tests in artful/trusty on i386/s390x/aarch64 + + -- Michael Vogt Mon, 17 Jul 2017 11:58:22 +0200 + snapd (2.26.9) xenial; urgency=medium * New upstream release, LP: #1690083 diff -Nru snapd-2.27.5/packaging/ubuntu-16.10/control snapd-2.28.5/packaging/ubuntu-16.10/control --- snapd-2.27.5/packaging/ubuntu-16.10/control 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/packaging/ubuntu-16.10/control 2017-10-11 17:40:25.000000000 +0000 @@ -7,6 +7,7 @@ autotools-dev, bash-completion, debhelper (>= 9), + dbus, dh-apparmor, dh-autoreconf, dh-golang (>=1.7), @@ -64,11 +65,11 @@ systemd, ${misc:Depends}, ${shlibs:Depends} -Replaces: ubuntu-snappy (<< 1.9), ubuntu-snappy-cli (<< 1.9), snap-confine (<< 2.23), ubuntu-core-launcher (<< 2.22) -Breaks: ubuntu-snappy (<< 1.9), ubuntu-snappy-cli (<< 1.9), snap-confine (<< 2.23), ubuntu-core-launcher (<< 2.22) +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) Conflicts: snap (<< 2013-11-29-1ubuntu1) Built-Using: ${Built-Using} ${misc:Built-Using} -Description: Tool to interact with Ubuntu Core Snappy. +Description: Daemon and tooling that enable snap packages Install, configure, refresh and remove snap packages. Snaps are 'universal' packages that work across many different Linux systems, enabling secure distribution of the latest apps and utilities for @@ -112,3 +113,11 @@ Pre-Depends: dpkg (>= 1.15.7.2) Description: Transitional package for snapd This is a transitional dummy package. It can safely be removed. + +Package: snapd-xdg-open +Architecture: any +Depends: snapd (= ${binary:Version}), ${misc:Depends} +Section: oldlibs +Pre-Depends: dpkg (>= 1.15.7.2) +Description: Transitional package for snapd-xdg-open + This is a transitional dummy package. It can safely be removed. diff -Nru snapd-2.27.5/packaging/ubuntu-16.10/rules snapd-2.28.5/packaging/ubuntu-16.10/rules --- snapd-2.27.5/packaging/ubuntu-16.10/rules 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/packaging/ubuntu-16.10/rules 2017-09-13 14:47:18.000000000 +0000 @@ -1,5 +1,13 @@ #!/usr/bin/make -f # -*- makefile -*- +# +# These rules should work for any debian-ish distro that uses systemd +# as init. That does _not_ include Ubuntu 14.04 ("trusty"); look for +# its own special rule file. +# +# Please keep the diff between that and this relatively small, even if +# it means having suboptimal code; these need to be kept in sync by +# sentient bags of meat. #export DH_VERBOSE=1 export DH_OPTIONS @@ -24,8 +32,10 @@ # Disable -buildmode=pie mode on i386 as can panics in spectacular # ways (LP: #1711052). # See also https://forum.snapcraft.io/t/artful-i386-panics/ +# Note while the panic is only on artful, that's because artful +# detects it; the issue potentially there on older things. BUILDFLAGS:=-pkgdir=$(CURDIR)/_build/std -ifneq ($(shell dpkg-architecture -qDEB_TARGET_ARCH),i386) +ifneq ($(shell dpkg-architecture -qDEB_HOST_ARCH),i386) BUILDFLAGS+= -buildmode=pie endif @@ -53,7 +63,7 @@ # because derivatives may have different kernels that don't support all the # required confinement features and we don't to mislead anyone about the # security of the system. Discuss a proper approach to this for downstreams -# if and when they approach us +# if and when they approach us. ifeq ($(shell dpkg-vendor --query Vendor),Ubuntu) # On Ubuntu 16.04 we need to produce a build that can be used on wide # variety of systems. As such we prefer static linking over dynamic linking @@ -103,9 +113,9 @@ ) endif dh_clean + $(MAKE) -C data clean # XXX: hacky $(MAKE) -C cmd distclean || true - $(MAKE) -C data/systemd clean override_dh_auto_build: # usually done via `go generate` but that is not supported on powerpc @@ -114,21 +124,29 @@ mkdir -p _build/src/$(DH_GOPKG)/cmd/snap/test-data cp -a cmd/snap/test-data/*.gpg _build/src/$(DH_GOPKG)/cmd/snap/test-data/ dh_auto_build -- $(BUILDFLAGS) $(TAGS) $(GCCGOFLAGS) + # Generate static snap-exec - it somehow includes CGO so we must + # force a static build here. We need a static snap-exec inside + # the core snap because not all bases will have a libc + (cd _build/bin && GOPATH=$$(pwd)/.. CGO_ENABLED=0 go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snap-exec) + # ensure we generated a static build + $(shell if ldd _build/bin/snap-exec; then false "need static build"; fi) + # Build C bits, sadly manually cd cmd && ( autoreconf -i -f ) cd cmd && ( ./configure --prefix=/usr --libexecdir=/usr/lib/snapd $(VENDOR_ARGS)) $(MAKE) -C cmd all - # Generate the real systemd units out of the available templates - $(MAKE) -C data/systemd all + # Generate the real systemd/dbus/env config files + $(MAKE) -C data all override_dh_auto_test: dh_auto_test -- $(GCCGOFLAGS) # a tested default (production) build should have no test keys ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))) - # check that only the main trusted account-key is included - [ $$(strings _build/bin/snapd|grep -c -E "public-key-sha3-384: [a-zA-Z0-9_-]{64}") -eq 1 ] + # check that only the main trusted account-keys are included + [ $$(strings _build/bin/snapd|grep -c -E "public-key-sha3-384: [a-zA-Z0-9_-]{64}") -eq 2 ] strings _build/bin/snapd|grep -c "^public-key-sha3-384: -CvQKAwRQ5h3Ffn10FILJoEZUXOv6km9FwA80-Rcj-f-6jadQ89VRswHNiEB9Lxk$$" + strings _build/bin/snapd|grep -c "^public-key-sha3-384: d-JcZF9nD9eBw7bwMnH61x-bklnQOhQud1Is6o_cn2wTj8EYDi9musrIT9z2MdAa$$" endif ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))) # run the snap-confine tests @@ -152,12 +170,12 @@ # we want the repair timer enabled by default dh_systemd_enable \ -psnapd \ - data/systemd/snap-repair.timer + data/systemd/snapd.snap-repair.timer # but the repair service disabled dh_systemd_enable \ --no-enable \ -psnapd \ - data/systemd/snap-repair.service + data/systemd/snapd.snap-repair.service # enable snapd dh_systemd_enable \ -psnapd \ @@ -210,10 +228,14 @@ cp -R share/locale debian/snapd/usr/share; \ fi - # install snapd's systemd units, done here instead of - # debian/snapd.install because the ubuntu/14.04 release - # branch adds/changes bits here - $(MAKE) -C data/systemd install DESTDIR=$(CURDIR)/debian/snapd/ SYSTEMDSYSTEMUNITDIR=$(SYSTEMD_UNITS_DESTDIR) + # install snapd's systemd units / upstart jobs, done + # here instead of debian/snapd.install because the + # ubuntu/14.04 release branch adds/changes bits here + $(MAKE) -C data install DESTDIR=$(CURDIR)/debian/snapd/ \ + SYSTEMDSYSTEMUNITDIR=$(SYSTEMD_UNITS_DESTDIR) + # we called this apps-bin-path.sh instead of snapd.sh, and + # it's a conf file so we're stuck with it + mv debian/snapd/etc/profile.d/snapd.sh debian/snapd/etc/profile.d/apps-bin-path.sh $(MAKE) -C cmd install DESTDIR=$(CURDIR)/debian/tmp @@ -227,6 +249,7 @@ install -d $(CURDIR)/debian/tmp/etc/apparmor.d touch $(CURDIR)/debian/tmp/etc/apparmor.d/usr.lib.snapd.snap-confine.real endif + dh_install override_dh_auto_install: snap.8 diff -Nru snapd-2.27.5/packaging/ubuntu-16.10/snapd.install snapd-2.28.5/packaging/ubuntu-16.10/snapd.install --- snapd-2.27.5/packaging/ubuntu-16.10/snapd.install 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/packaging/ubuntu-16.10/snapd.install 2017-09-13 14:47:18.000000000 +0000 @@ -7,10 +7,6 @@ usr/bin/snapd /usr/lib/snapd/ usr/bin/snap-seccomp /usr/lib/snapd/ -# etc/profile.d contains the PATH extension for snap packages -etc/profile.d -# etc/X11/Xsession.d will add to XDG_DATA_DIRS so that we have .desktop support -etc/X11 # bash completion data/completion/snap /usr/share/bash-completion/completions data/completion/complete.sh /usr/lib/snapd/ @@ -19,6 +15,8 @@ data/udev/rules.d/66-snapd-autoimport.rules /lib/udev/rules.d # snap/snapd version information data/info /usr/lib/snapd/ +# polkit actions +data/polkit/io.snapcraft.snapd.policy /usr/share/polkit-1/actions/ # snap-confine stuff etc/apparmor.d/usr.lib.snapd.snap-confine.real diff -Nru snapd-2.27.5/packaging/ubuntu-16.10/tests/control snapd-2.28.5/packaging/ubuntu-16.10/tests/control --- snapd-2.27.5/packaging/ubuntu-16.10/tests/control 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/packaging/ubuntu-16.10/tests/control 2017-09-13 14:47:18.000000000 +0000 @@ -1,5 +1,5 @@ Tests: integrationtests -Restrictions: allow-stderr, isolation-container, rw-build-tree, needs-root, breaks-testbed +Restrictions: allow-stderr, rw-build-tree, needs-root, breaks-testbed, isolation-machine Depends: @builddeps@, bzr, ca-certificates, diff -Nru snapd-2.27.5/packaging/ubuntu-17.04/changelog snapd-2.28.5/packaging/ubuntu-17.04/changelog --- snapd-2.27.5/packaging/ubuntu-17.04/changelog 2017-08-30 05:32:20.000000000 +0000 +++ snapd-2.28.5/packaging/ubuntu-17.04/changelog 2017-10-13 21:25:46.000000000 +0000 @@ -1,3 +1,331 @@ +snapd (2.28.5) xenial; urgency=medium + + * New upstream release, LP: #1714984 + - snap-confine: cleanup broken nvidia udev tags + - cmd/snap-confine: update valid security tag regexp + - overlord/ifacestate: refresh udev backend on startup + - dbus: ensure io.snapcraft.Launcher.service is created on re- + exec + - snap-confine: add support for handling /dev/nvidia-modeset + - interfaces/network-control: remove incorrect rules for tun + + -- Michael Vogt Fri, 13 Oct 2017 23:25:46 +0200 + +snapd (2.28.4) xenial; urgency=medium + + * New upstream release, LP: #1714984 + - interfaces/opengl: don't udev tag nvidia devices and use snap- + confine instead + - debian: fix replaces/breaks for snap-xdg-open (thanks to apw!) + + -- Michael Vogt Wed, 11 Oct 2017 19:40:57 +0200 + +snapd (2.28.3) xenial; urgency=medium + + * New upstream release, LP: #1714984 + - interfaces/lxd: lxd slot implementation can also be an app + snap + + -- Michael Vogt Wed, 11 Oct 2017 08:20:26 +0200 + +snapd (2.28.2) xenial; urgency=medium + + * New upstream release, LP: #1714984 + - interfaces: fix udev rules for tun + - release,cmd,dirs: Redo the distro checks to take into account + distribution families + + -- Michael Vogt Tue, 10 Oct 2017 18:39:58 +0200 + +snapd (2.28.1) xenial; urgency=medium + + * New upstream release, LP: #1714984 + - snap-confine: update apparmor rules for fedora based basesnaps + - snapstate: rename refresh hook to post-refresh for consistency + + -- Michael Vogt Wed, 27 Sep 2017 17:59:49 -0400 + +snapd (2.28) xenial; urgency=medium + + * New upstream release, LP: #1714984 + - hooks: rename refresh to after-refresh + - snap-confine: bind mount /usr/lib/snapd relative to snap-confine + - cmd,dirs: treat "liri" the same way as "arch" + - snap-confine: fix base snaps on core + - hooks: substitute env vars when executing hooks + - interfaces: updates for default, browser-support, desktop, opengl, + upower and stub-resolv.conf + - cmd,dirs: treat manjaro the same as arch + - systemd: do not run auto-import and repair services on classic + - packaging/fedora: Ensure vendor/ is empty for builds and fix spec + to build current master + - many: fix TestSetConfNumber missing an Unlock and other fragility + improvements + - osutil: adjust StreamCommand tests for golang 1.9 + - daemon: allow polkit authorisation to install/remove snaps + - tests: make TestCmdWatch more robust + - debian: improve package description + - interfaces: add netlink kobject uevent to hardware observe + - debian: update trusted account-keys check on 14.04 packaging + - interfaces/network-{control,observe}: allow receiving + kobject_uevent() messages + - tests: fix lxd test for external backend + - snap-confine,snap-update-ns: add -no-pie to fix FTBFS on + go1.7,ppc64 + - corecfg: mock "systemctl" in all corecfg tests + - tests: fix unit tests on Ubuntu 14.04 + - debian: add missing flags when building static snap-exec + - many: end-to-end support for the bare base snap + - overlord/snapstate: SetRootDir from SetUpTest, not in just some + tests + - store: have an ad-hoc method on cfg to get its list of uris for + tests + - daemon: let client decide whether to allow interactive auth via + polkit + - client,daemon,snap,store: add license field + - overlord/snapstate: rename HasCurrent to IsInstalled, remove + superfluous/misleading check from All + - cmd/snap: SetRootDir from SetUpTest, not in just some individual + tests. + - systemd: rename snap-repair.{service,timer} to snapd.snap- + repair.{service,timer} + - snap-seccomp: remove use of x/net/bpf from tests + - httputil: more naive per go version way to recreate a default + transport for tls reconfig + - cmd/snap-seccomp/main_test.go: add one more syscall for arm64 + - interfaces/opengl: use == to compare, not = + - cmd/snap-seccomp/main_test.go: add syscalls for armhf and arm64 + - cmd/snap-repair: track and use a lower bound for the time for + TLS checks + - interfaces: expose bluez interface on classic OS + - snap-seccomp: add in-kernel bpf tests + - overlord: always try to get a serial, lazily on classic + - tests: add nmcli regression test + - tests: deal with __PNR_chown on aarch64 to fix FTBFS on arm64 + - tests: add autopilot-introspection interface test + - vendor: fix artifact from manually editing vendor/vendor.json + - tests: rename complexion to test-snapd-complexion + - interfaces: add desktop and desktop-legacy + interfaces/desktop: add new 'desktop' interface for modern DEs + interfaces/builtin/desktop_test.go: use modern testing techniques + interfaces/wayland: allow read on /etc/drirc for Plasma desktop + interfaces/desktop-legacy: add new 'legacy' interface (currently + for a11y and input) + - tests: fix race in snap userd test + - devices/iio: add read/write for missing sysfs entries + - spread: don't set HTTPS?_PROXY for linode + - cmd/snap-repair: check signatures of repairs from Next + - env: set XDG_DATA_DIRS for wayland et.al. + - interfaces/{default,account-control}: Use username/group instead + of uid/gid + - interfaces/builtin: use udev tagging more broadly + - tests: add basic lxd test + - wrappers: ensure bash completion snaps install on core + - vendor: use old golang.org/x/crypto/ssh/terminal to build on + powerpc again + - docs: add PULL_REQUEST_TEMPLATE.md + - interfaces: fix network-manager plug + - hooks: do not error out when hook is optional and no hook handler + is registered + - cmd/snap: add userd command to replace snapd-xdg-open + - tests: new regex used to validate the core version on extra snaps + ass... + - snap: add new `snap switch` command + - tests: wait more and more debug info about fakestore start issues + - apparmor,release: add better apparmor detection/mocking code + - interfaces/i2c: adjust sysfs rule for alternate paths + - interfaces/apparmor: add missing call to dirs.SetRootDir + - cmd: "make hack" now also installs snap-update-ns + - tests: copy files with less verbosity + - cmd/snap-confine: allow using additional libraries required by + openSUSE + - packaging/fedora: Merge changes from Fedora Dist-Git + - snapstate: improve the error message when classic confinement is + not supported + - tests: add test to ensure amd64 can run i386 syscall binaries + - tests: adding extra info for fakestore when fails to start + - tests: install most important snaps + - cmd/snap-repair: more test coverage of filtering + - squashfs: remove runCommand/runCommandWithOutput as we do not need + it + - cmd/snap-repair: ignore superseded revisions, filter on arch and + models + - hooks: support for refresh hook + - Partial revert "overlord/devicestate, store: update device auth + endpoints URLs" + - cmd/snap-confine: allow reading /proc/filesystems + - cmd/snap-confine: genearlize apparmor profile for various lib + layout + - corecfg: fix proxy.* writing and add integration test + - corecfg: deal with system.power-key-action="" correctly + - vendor: update vendor.json after (presumed) manual edits + - cmd/snap: in `snap info`, don't print a newline between tracks + - daemon: add polkit support to /v2/login + - snapd,snapctl: decode json using Number + - client: fix go vet 1.7 errors + - tests: make 17.04 shellcheck clean + - tests: remove TestInterfacesHelp as it breaks when go-flags + changes + - snapstate: undo a daemon restart on classic if needed + - cmd/snap-repair: recover brand/model from + /var/lib/snapd/seed/assertions checking signatures and brand + account + - spread: opt into unsafe IO during spread tests + - snap-repair: update snap-repair/runner_test.go for API change in + makeMockServer + - cmd/snap-repair: skeleton code around actually running a repair + - tests: wait until the port is listening after start the fake store + - corecfg: fix typo in tests + - cmd/snap-repair: test that redirects works during fetching + - osutil: honor SNAPD_UNSAFE_IO for testing + - vendor: explode and make more precise our golang.go/x/crypto deps, + use same version as Debian unstable + - many: sanitize NewStoreStack signature, have shared default store + test private keys + - systemd: disable `Nice=-5` to fix error when running inside lxd + - spread.yaml: update delta ref to 2.27 + - cmd/snap-repair: use E-Tags when refetching a repair to retry + - interfaces/many: updates based on chromium and mrrescue denials + - cmd/snap-repair: implement most logic to get the next repair to + run/retry in a brand sequence + - asserts/assertstest: copy headers in SigningDB.Sign + - interfaces: convert uhid to common interface and test cases + improvement for time_control and opengl + - many tests: move all panicing fake store methods to a common place + - asserts: add store assertion type + - interfaces: don't crash if content slot has no attributes + - debian: do not build with -buildmode=pie on i386 + - wrappers: symlink completion snippets when symlinking binaries + - tests: adding more debug information for the interfaces-cups- + control … + - apparmor: pass --quiet to parser on load unless SNAPD_DEBUG is set + - many: allow and support serials signed by the 'generic' authority + instead of the brand + - corecfg: add proxy configuration via `snap set core + proxy.{http,https,ftp}=...` + - interfaces: a bunch of interfaces test improvement + - tests: enable regression and completion suites for opensuse + - tests: installing snapd for nested test suite + - interfaces: convert lxd_support to common iface + - interfaces: add missing test for camera interface. + - snap: add support for parsing snap layout section + - cmd/snap-repair: like for downloads we cannot have a timeout (at + least for now), less aggressive retry strategies + - overlord: rely on more conservative ensure interval + - overlord,store: no piles of return args for methods gathering + device session request params + - overlord,store: send model assertion when setting up device + sessions + - interfaces/misc: updates for unity7/x11, browser- + support, network-control and mount-observe + interfaces/unity7,x11: update for NETLINK_KOBJECT_UEVENT + interfaces/browser-support: update sysfs reads for + newer browser versions, interfaces/network-control: rw for + ieee80211 advanced wireless interfaces/mount-observe: allow read + on sysfs entries for block devices + - tests: use dnf --refresh install to avert stale cache + - osutil: ensure TestLockUnlockWorks uses supported flock + - interfaces: convert lxd to common iface + - tests: restart snapd to ensure re-exec settings are applied + - tests: fix interfaces-cups-control test + - interfaces: improve and tweak bunch of interfaces test cases. + - tests: adding extra worker for fedora + - asserts,overlord/devicestate: support predefined assertions that + don't establish foundational trust + - interfaces: convert two hardware_random interfaces to common iface + - interfaces: convert io_ports_control to common iface + - tests: fix for upgrade test on fedora + - daemon, client, cmd/snap: implement snap start/stop/restart + - cmd/snap-confine: set _FILE_OFFSET_BITS to 64 + - interfaces: covert framebuffer to commonInterface + - interfaces: convert joystick to common iface + - interfaces/builtin: add the spi interface + - wrappers, overlord/snapstate/backend: make link-snap clean up on + failure. + - interfaces/wayland: add wayland interface + - interfaces: convert kvm to common iface + - tests: extend upower-observe test to cover snaps providing slots + - tests: enable main suite for opensuse + - interfaces: convert physical_memory_observe to common iface + - interfaces: add missing test for optical_drive interface. + - interfaces: convert physical_memory_control to common iface + - interfaces: convert ppp to common iface + - interfaces: convert time-control to common iface + - tests: fix failover test + - interfaces/builtin: rework for avahi interface + - interfaces: convert broadcom-asic-control to common iface + - snap/snapenv: document the use of CoreSnapMountDir for SNAP + - packaging/arch: drop patches merged into master + - cmd: fix mustUnsetenv docstring (thanks to Chipaca) + - release: remove default from VERSION_ID + - tests: enable regression, upgrade and completion test suites for + fedora + - tests: restore interfaces-account-control properly + - overlord/devicestate, store: update device auth endpoints URLs + - tests: fix install-hook test failure + - tests: download core and ubuntu-core at most once + - interfaces: add common support for udev + - overlord/devicestate: fix, don't assume that the serial is backed + by a 1-key chain + - cmd/snap-confine: don't share /etc/nsswitch from host + - store: do not resume a download when we already have the whole + thing + - many: implement "snap logs" + - store: don't call useDeltas() twice in quick succession + - interfaces/builtin: add kvm interface + - snap/snapenv: always expect /snap for $SNAP + - cmd: mark arch as non-reexecing distro + - cmd: fix tests that assume /snap mount + - gitignore: ignore more build artefacts + - packaging: add current arch packaging + - interfaces/unity7: allow receiving media key events in (at least) + gnome-shell + - interfaces/many, cmd/snap-confine: miscellaneous policy updates + - interfaces/builtin: implement broadcom-asic-control interface + - interfaces/builtin: reduce duplication and remove cruft in + Sanitize{Plug,Slot} + - tests: apply underscore convention for SNAPMOUNTDIR variable + - interfaces/greengrass-support: adjust accesses now that have + working snap + - daemon, client, cmd/snap: implement "snap services" + - tests: fix refresh tests not stopping fake store for fedora + - many: add the interface command + - overlord/snapstate/backend: some copydata improvements + - many: support querying and completing assertion type names + - interfaces/builtin: discard empty Validate{Plug,Slot} + - cmd/snap-repair: start of Runner, implement first pass of Peek + and Fetch + - tests: enable main suite on fedora + - snap: do not always quote the snap info summary + - vendor: update go-flags to address crash in "snap debug" + - interfaces: opengl support pci device and vendor + - many: start implenting "base" snap type on the snapd side + - arch,release: map armv6 correctly + - many: expose service status in 'snap info' + - tests: add browser-support interface test + - tests: disable snapd-notify for the external backend + - interfaces: Add /run/uuid/request to openvswitch + - interfaces: add password-manager-service implicit classic + interface + - cmd: rework reexec detection + - cmd: fix re-exec bug when starting from snapd 2.21 + - tests: dependency packages installed during prepare-project + - tests: remove unneeded check for re-exec in InternalToolPath() + - cmd,tests: fix classic confinement confusing re-execution code + - store: configurable base api + - tests: fix how package lists are updated for opensuse and fedora + + -- Michael Vogt Mon, 25 Sep 2017 12:07:34 -0400 + +snapd (2.27.6) xenial; urgency=medium + + * New upstream release, LP: #1703798: + - interfaces: add udev netlink support to hardware-observe + - interfaces/network-{control,observe}: allow receiving + kobject_uevent() messages + + -- Michael Vogt Thu, 07 Sep 2017 10:22:18 +0200 + snapd (2.27.5) xenial; urgency=medium * New upstream release, LP: #1703798: @@ -322,6 +650,29 @@ -- Michael Vogt Thu, 10 Aug 2017 12:43:16 +0200 +snapd (2.26.14) xenial; urgency=medium + + * New upstream release, LP: #1690083 + - cmd: fix incorrect re-exec when starting from snapd 2.21 + + -- Michael Vogt Thu, 20 Jul 2017 13:52:05 +0200 + +snapd (2.26.13) xenial; urgency=medium + + * New upstream release, LP: #1690083 + - cmd,tests: fix classic confinement confusing re-execution code + - cmd: fix incorrect check check for re-exec in InternalToolPath() + - snap-seccomp: add secondary arch for unrestricted snaps as well + + -- Michael Vogt Tue, 18 Jul 2017 20:34:33 +0200 + +snapd (2.26.10) xenial; urgency=medium + + * New upstream release, LP: #1690083 + - Fix snap-seccomp tests in artful/trusty on i386/s390x/aarch64 + + -- Michael Vogt Mon, 17 Jul 2017 11:58:22 +0200 + snapd (2.26.9) xenial; urgency=medium * New upstream release, LP: #1690083 diff -Nru snapd-2.27.5/packaging/ubuntu-17.04/control snapd-2.28.5/packaging/ubuntu-17.04/control --- snapd-2.27.5/packaging/ubuntu-17.04/control 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/packaging/ubuntu-17.04/control 2017-10-11 17:40:25.000000000 +0000 @@ -7,6 +7,7 @@ autotools-dev, bash-completion, debhelper (>= 9), + dbus, dh-apparmor, dh-autoreconf, dh-golang (>=1.7), @@ -64,11 +65,11 @@ systemd, ${misc:Depends}, ${shlibs:Depends} -Replaces: ubuntu-snappy (<< 1.9), ubuntu-snappy-cli (<< 1.9), snap-confine (<< 2.23), ubuntu-core-launcher (<< 2.22) -Breaks: ubuntu-snappy (<< 1.9), ubuntu-snappy-cli (<< 1.9), snap-confine (<< 2.23), ubuntu-core-launcher (<< 2.22) +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) Conflicts: snap (<< 2013-11-29-1ubuntu1) Built-Using: ${Built-Using} ${misc:Built-Using} -Description: Tool to interact with Ubuntu Core Snappy. +Description: Daemon and tooling that enable snap packages Install, configure, refresh and remove snap packages. Snaps are 'universal' packages that work across many different Linux systems, enabling secure distribution of the latest apps and utilities for @@ -112,3 +113,11 @@ Pre-Depends: dpkg (>= 1.15.7.2) Description: Transitional package for snapd This is a transitional dummy package. It can safely be removed. + +Package: snapd-xdg-open +Architecture: any +Depends: snapd (= ${binary:Version}), ${misc:Depends} +Section: oldlibs +Pre-Depends: dpkg (>= 1.15.7.2) +Description: Transitional package for snapd-xdg-open + This is a transitional dummy package. It can safely be removed. diff -Nru snapd-2.27.5/packaging/ubuntu-17.04/rules snapd-2.28.5/packaging/ubuntu-17.04/rules --- snapd-2.27.5/packaging/ubuntu-17.04/rules 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/packaging/ubuntu-17.04/rules 2017-09-13 14:47:18.000000000 +0000 @@ -1,5 +1,13 @@ #!/usr/bin/make -f # -*- makefile -*- +# +# These rules should work for any debian-ish distro that uses systemd +# as init. That does _not_ include Ubuntu 14.04 ("trusty"); look for +# its own special rule file. +# +# Please keep the diff between that and this relatively small, even if +# it means having suboptimal code; these need to be kept in sync by +# sentient bags of meat. #export DH_VERBOSE=1 export DH_OPTIONS @@ -24,8 +32,10 @@ # Disable -buildmode=pie mode on i386 as can panics in spectacular # ways (LP: #1711052). # See also https://forum.snapcraft.io/t/artful-i386-panics/ +# Note while the panic is only on artful, that's because artful +# detects it; the issue potentially there on older things. BUILDFLAGS:=-pkgdir=$(CURDIR)/_build/std -ifneq ($(shell dpkg-architecture -qDEB_TARGET_ARCH),i386) +ifneq ($(shell dpkg-architecture -qDEB_HOST_ARCH),i386) BUILDFLAGS+= -buildmode=pie endif @@ -53,7 +63,7 @@ # because derivatives may have different kernels that don't support all the # required confinement features and we don't to mislead anyone about the # security of the system. Discuss a proper approach to this for downstreams -# if and when they approach us +# if and when they approach us. ifeq ($(shell dpkg-vendor --query Vendor),Ubuntu) # On Ubuntu 16.04 we need to produce a build that can be used on wide # variety of systems. As such we prefer static linking over dynamic linking @@ -103,9 +113,9 @@ ) endif dh_clean + $(MAKE) -C data clean # XXX: hacky $(MAKE) -C cmd distclean || true - $(MAKE) -C data/systemd clean override_dh_auto_build: # usually done via `go generate` but that is not supported on powerpc @@ -114,21 +124,29 @@ mkdir -p _build/src/$(DH_GOPKG)/cmd/snap/test-data cp -a cmd/snap/test-data/*.gpg _build/src/$(DH_GOPKG)/cmd/snap/test-data/ dh_auto_build -- $(BUILDFLAGS) $(TAGS) $(GCCGOFLAGS) + # Generate static snap-exec - it somehow includes CGO so we must + # force a static build here. We need a static snap-exec inside + # the core snap because not all bases will have a libc + (cd _build/bin && GOPATH=$$(pwd)/.. CGO_ENABLED=0 go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snap-exec) + # ensure we generated a static build + $(shell if ldd _build/bin/snap-exec; then false "need static build"; fi) + # Build C bits, sadly manually cd cmd && ( autoreconf -i -f ) cd cmd && ( ./configure --prefix=/usr --libexecdir=/usr/lib/snapd $(VENDOR_ARGS)) $(MAKE) -C cmd all - # Generate the real systemd units out of the available templates - $(MAKE) -C data/systemd all + # Generate the real systemd/dbus/env config files + $(MAKE) -C data all override_dh_auto_test: dh_auto_test -- $(GCCGOFLAGS) # a tested default (production) build should have no test keys ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))) - # check that only the main trusted account-key is included - [ $$(strings _build/bin/snapd|grep -c -E "public-key-sha3-384: [a-zA-Z0-9_-]{64}") -eq 1 ] + # check that only the main trusted account-keys are included + [ $$(strings _build/bin/snapd|grep -c -E "public-key-sha3-384: [a-zA-Z0-9_-]{64}") -eq 2 ] strings _build/bin/snapd|grep -c "^public-key-sha3-384: -CvQKAwRQ5h3Ffn10FILJoEZUXOv6km9FwA80-Rcj-f-6jadQ89VRswHNiEB9Lxk$$" + strings _build/bin/snapd|grep -c "^public-key-sha3-384: d-JcZF9nD9eBw7bwMnH61x-bklnQOhQud1Is6o_cn2wTj8EYDi9musrIT9z2MdAa$$" endif ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))) # run the snap-confine tests @@ -152,12 +170,12 @@ # we want the repair timer enabled by default dh_systemd_enable \ -psnapd \ - data/systemd/snap-repair.timer + data/systemd/snapd.snap-repair.timer # but the repair service disabled dh_systemd_enable \ --no-enable \ -psnapd \ - data/systemd/snap-repair.service + data/systemd/snapd.snap-repair.service # enable snapd dh_systemd_enable \ -psnapd \ @@ -210,10 +228,14 @@ cp -R share/locale debian/snapd/usr/share; \ fi - # install snapd's systemd units, done here instead of - # debian/snapd.install because the ubuntu/14.04 release - # branch adds/changes bits here - $(MAKE) -C data/systemd install DESTDIR=$(CURDIR)/debian/snapd/ SYSTEMDSYSTEMUNITDIR=$(SYSTEMD_UNITS_DESTDIR) + # install snapd's systemd units / upstart jobs, done + # here instead of debian/snapd.install because the + # ubuntu/14.04 release branch adds/changes bits here + $(MAKE) -C data install DESTDIR=$(CURDIR)/debian/snapd/ \ + SYSTEMDSYSTEMUNITDIR=$(SYSTEMD_UNITS_DESTDIR) + # we called this apps-bin-path.sh instead of snapd.sh, and + # it's a conf file so we're stuck with it + mv debian/snapd/etc/profile.d/snapd.sh debian/snapd/etc/profile.d/apps-bin-path.sh $(MAKE) -C cmd install DESTDIR=$(CURDIR)/debian/tmp @@ -227,6 +249,7 @@ install -d $(CURDIR)/debian/tmp/etc/apparmor.d touch $(CURDIR)/debian/tmp/etc/apparmor.d/usr.lib.snapd.snap-confine.real endif + dh_install override_dh_auto_install: snap.8 diff -Nru snapd-2.27.5/packaging/ubuntu-17.04/snapd.install snapd-2.28.5/packaging/ubuntu-17.04/snapd.install --- snapd-2.27.5/packaging/ubuntu-17.04/snapd.install 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/packaging/ubuntu-17.04/snapd.install 2017-09-13 14:47:18.000000000 +0000 @@ -7,10 +7,6 @@ usr/bin/snapd /usr/lib/snapd/ usr/bin/snap-seccomp /usr/lib/snapd/ -# etc/profile.d contains the PATH extension for snap packages -etc/profile.d -# etc/X11/Xsession.d will add to XDG_DATA_DIRS so that we have .desktop support -etc/X11 # bash completion data/completion/snap /usr/share/bash-completion/completions data/completion/complete.sh /usr/lib/snapd/ @@ -19,6 +15,8 @@ data/udev/rules.d/66-snapd-autoimport.rules /lib/udev/rules.d # snap/snapd version information data/info /usr/lib/snapd/ +# polkit actions +data/polkit/io.snapcraft.snapd.policy /usr/share/polkit-1/actions/ # snap-confine stuff etc/apparmor.d/usr.lib.snapd.snap-confine.real diff -Nru snapd-2.27.5/packaging/ubuntu-17.04/tests/control snapd-2.28.5/packaging/ubuntu-17.04/tests/control --- snapd-2.27.5/packaging/ubuntu-17.04/tests/control 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/packaging/ubuntu-17.04/tests/control 2017-09-13 14:47:18.000000000 +0000 @@ -1,5 +1,5 @@ Tests: integrationtests -Restrictions: allow-stderr, isolation-container, rw-build-tree, needs-root, breaks-testbed +Restrictions: allow-stderr, rw-build-tree, needs-root, breaks-testbed, isolation-machine Depends: @builddeps@, bzr, ca-certificates, diff -Nru snapd-2.27.5/polkit/authority.go snapd-2.28.5/polkit/authority.go --- snapd-2.27.5/polkit/authority.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/polkit/authority.go 2017-09-13 14:47:18.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 polkit + +import ( + "errors" + + "github.com/godbus/dbus" +) + +type CheckFlags uint32 + +const ( + CheckNone CheckFlags = 0x00 + CheckAllowInteraction CheckFlags = 0x01 +) + +var ( + ErrDismissed = errors.New("Authorization request dismissed") + ErrInteraction = errors.New("Authorization requires interaction") +) + +func checkAuthorization(subject authSubject, actionId string, details map[string]string, flags CheckFlags) (bool, error) { + bus, err := dbus.SystemBus() + if err != nil { + return false, err + } + authority := bus.Object("org.freedesktop.PolicyKit1", + "/org/freedesktop/PolicyKit1/Authority") + + var result authResult + err = authority.Call( + "org.freedesktop.PolicyKit1.Authority.CheckAuthorization", 0, + subject, actionId, details, flags, "").Store(&result) + if err != nil && !result.IsAuthorized { + if result.IsChallenge { + err = ErrInteraction + } else if result.Details["polkit.dismissed"] != "" { + err = ErrDismissed + } + } + return result.IsAuthorized, err +} + +// CheckAuthorizationForPid queries polkit to determine whether a process is +// authorized to perform an action. +func CheckAuthorizationForPid(pid uint32, actionId string, details map[string]string, flags CheckFlags) (bool, error) { + subject := authSubject{ + Kind: "unix-process", + Details: make(map[string]dbus.Variant), + } + subject.Details["pid"] = dbus.MakeVariant(pid) + startTime, err := getStartTimeForPid(pid) + if err != nil { + return false, err + } + subject.Details["start-time"] = dbus.MakeVariant(startTime) + return checkAuthorization(subject, actionId, details, flags) +} + +type authSubject struct { + Kind string + Details map[string]dbus.Variant +} + +type authResult struct { + IsAuthorized bool + IsChallenge bool + Details map[string]string +} diff -Nru snapd-2.27.5/polkit/pid_start_time.go snapd-2.28.5/polkit/pid_start_time.go --- snapd-2.27.5/polkit/pid_start_time.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/polkit/pid_start_time.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,68 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +build linux + +/* + * 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 polkit + +import ( + "fmt" + "io/ioutil" + "strconv" + "strings" +) + +// getStartTimeForPid determines the start time for a given process ID +func getStartTimeForPid(pid uint32) (uint64, error) { + filename := fmt.Sprintf("/proc/%d/stat", pid) + return getStartTimeForProcStatFile(filename) +} + +// getStartTimeForProcStatFile determines the start time from a process stat file +// +// The implementation is intended to be compatible with polkit: +// https://cgit.freedesktop.org/polkit/tree/src/polkit/polkitunixprocess.c +func getStartTimeForProcStatFile(filename string) (uint64, error) { + data, err := ioutil.ReadFile(filename) + if err != nil { + return 0, err + } + contents := string(data) + + // start time is the token at index 19 after the '(process + // name)' entry - since only this field can contain the ')' + // character, search backwards for this to avoid malicious + // processes trying to fool us + // + // See proc(5) man page for a description of the + // /proc/[pid]/stat file format and the meaning of the + // starttime field. + idx := strings.IndexByte(contents, ')') + if idx < 0 { + return 0, fmt.Errorf("cannot parse %s", filename) + } + idx += 2 // skip ") " + if idx > len(contents) { + return 0, fmt.Errorf("cannot parse %s", filename) + } + tokens := strings.Split(contents[idx:], " ") + if len(tokens) < 20 { + return 0, fmt.Errorf("cannot parse %s", filename) + } + return strconv.ParseUint(tokens[19], 10, 64) +} diff -Nru snapd-2.27.5/polkit/pid_start_time_test.go snapd-2.28.5/polkit/pid_start_time_test.go --- snapd-2.27.5/polkit/pid_start_time_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/polkit/pid_start_time_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,71 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- +// +build linux + +/* + * 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 polkit + +import ( + "io/ioutil" + "os" + "path/filepath" + "syscall" + "testing" + + "gopkg.in/check.v1" +) + +func Test(t *testing.T) { check.TestingT(t) } + +type polkitSuite struct{} + +var _ = check.Suite(&polkitSuite{}) + +func (s *polkitSuite) TestGetStartTime(c *check.C) { + pid := os.Getpid() + + startTime, err := getStartTimeForPid(uint32(pid)) + c.Assert(err, check.IsNil) + c.Check(startTime, check.Not(check.Equals), uint64(0)) +} + +func (s *polkitSuite) TestGetStartTimeBadPid(c *check.C) { + // Find an unused process ID by checking for errors from Kill. + pid := 2 + for { + if err := syscall.Kill(pid, 0); err == syscall.ESRCH { + break + } + pid += 1 + } + + startTime, err := getStartTimeForPid(uint32(pid)) + c.Assert(err, check.ErrorMatches, "open .*: no such file or directory") + c.Check(startTime, check.Equals, uint64(0)) +} + +func (s *polkitSuite) TestProcStatParsing(c *check.C) { + filename := filepath.Join(c.MkDir(), "stat") + contents := []byte("18433 (cat) R 9732 18433 9732 34818 18433 4194304 96 0 1 0 0 0 0 0 20 0 1 0 123104764 7602176 182 18446744073709551615 94902526107648 94902526138492 140734457666896 0 0 0 0 0 0 0 0 0 17 5 0 0 0 0 0 94902528236168 94902528237760 94902542680064 140734457672267 140734457672287 140734457672287 140734457675759 0") + err := ioutil.WriteFile(filename, contents, 0644) + c.Assert(err, check.IsNil) + + startTime, err := getStartTimeForProcStatFile(filename) + c.Assert(err, check.IsNil) + c.Check(startTime, check.Equals, uint64(123104764)) +} diff -Nru snapd-2.27.5/progress/progress.go snapd-2.28.5/progress/progress.go --- snapd-2.27.5/progress/progress.go 2016-12-08 15:14:07.000000000 +0000 +++ snapd-2.28.5/progress/progress.go 2017-09-13 14:47:18.000000000 +0000 @@ -25,8 +25,8 @@ "os" "unicode" - "github.com/cheggaaa/pb" "golang.org/x/crypto/ssh/terminal" + "gopkg.in/cheggaaa/pb.v1" ) // Meter is an interface to show progress to the user diff -Nru snapd-2.27.5/PULL_REQUEST_TEMPLATE.md snapd-2.28.5/PULL_REQUEST_TEMPLATE.md --- snapd-2.27.5/PULL_REQUEST_TEMPLATE.md 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/PULL_REQUEST_TEMPLATE.md 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,2 @@ +Thanks for helping us make a better snapd! +Have you signed the [license agreement](https://www.ubuntu.com/legal/contributors) and read the [contribution guide](CONTRIBUTING.md)? diff -Nru snapd-2.27.5/README.md snapd-2.28.5/README.md --- snapd-2.27.5/README.md 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/README.md 2017-09-13 14:47:18.000000000 +0000 @@ -24,8 +24,10 @@ ## Get in touch -We're friendly! Talk to us on [IRC](https://webchat.freenode.net/?channels=snappy) -or on [our mailing list](https://lists.snapcraft.io/mailman/listinfo/snapcraft). +We're friendly! Talk to us on +[IRC](https://webchat.freenode.net/?channels=snappy), +[Rocket Chat](https://rocket.ubuntu.com/channel/snappy), +or on [our forums](https://forum.snapcraft.io/). Get news and stay up to date on [Twitter](https://twitter.com/snapcraftio), [Google+](https://plus.google.com/+SnapcraftIo) or @@ -43,4 +45,4 @@ [coveralls-url]: https://coveralls.io/github/snapcore/snapd?branch=master [codecov-url]: https://codecov.io/gh/snapcore/snapd -[codecov-image]: https://codecov.io/gh/snapcore/snapd/branch/master/graph/badge.svg \ No newline at end of file +[codecov-image]: https://codecov.io/gh/snapcore/snapd/branch/master/graph/badge.svg diff -Nru snapd-2.27.5/release/export_linux_test.go snapd-2.28.5/release/export_linux_test.go --- snapd-2.27.5/release/export_linux_test.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/release/export_linux_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -21,4 +21,5 @@ var ( GetKernelRelease = getKernelRelease + GetMachineName = getMachineName ) diff -Nru snapd-2.27.5/release/release.go snapd-2.28.5/release/release.go --- snapd-2.27.5/release/release.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/release/release.go 2017-10-10 16:16:09.000000000 +0000 @@ -21,11 +21,12 @@ import ( "bufio" - "io/ioutil" "os" - "path/filepath" "strings" "unicode" + + "github.com/snapcore/snapd/apparmor" + "github.com/snapcore/snapd/strutil" ) // Series holds the Ubuntu Core series for snapd to use. @@ -33,8 +34,9 @@ // OS contains information about the system extracted from /etc/os-release. type OS struct { - ID string `json:"id"` - VersionID string `json:"version-id,omitempty"` + ID string `json:"id"` + IDLike []string `json:"-"` + VersionID string `json:"version-id,omitempty"` } var ( @@ -55,15 +57,16 @@ // ForceDevMode returns true if the distribution doesn't implement required // security features for confinement and devmode is forced. func (o *OS) ForceDevMode() bool { - for _, req := range requiredApparmorFeatures { - // Also ensure appamor is enabled (cannot use - // osutil.FileExists() here because of cyclic imports) - p := filepath.Join(apparmorFeaturesSysPath, req) - if _, err := os.Stat(p); err != nil { + level, _ := apparmor.ProbeKernel().Evaluate() + return level != apparmor.Full +} + +func DistroLike(distros ...string) bool { + for _, distro := range distros { + if ReleaseInfo.ID == distro || strutil.ListContains(ReleaseInfo.IDLike, distro) { return true } } - return false } @@ -76,7 +79,6 @@ func readOSRelease() OS { // TODO: separate this out into its own thing maybe (if made more general) osRelease := OS{ - VersionID: "unknown", // from os-release(5): If not set, defaults to "ID=linux". ID: "linux", } @@ -112,6 +114,9 @@ // not being too good at reading comprehension. // Works around e.g. lp:1602317 osRelease.ID = strings.Fields(strings.ToLower(v))[0] + case "ID_LIKE": + // This is like ID, except it's a space separated list... hooray? + osRelease.IDLike = strings.Fields(strings.ToLower(v)) case "VERSION_ID": osRelease.VersionID = v } @@ -152,26 +157,9 @@ // MockForcedDevmode fake the system to believe its in a distro // that is in ForcedDevmode func MockForcedDevmode(isDevmode bool) (restore func()) { - oldApparmorFeaturesSysPath := apparmorFeaturesSysPath - - temp, err := ioutil.TempDir("", "mock-forced-devmode") - if err != nil { - panic(err) - } - fakeApparmorFeaturesSysPath := filepath.Join(temp, "apparmor") - if !isDevmode { - for _, req := range requiredApparmorFeatures { - if err := os.MkdirAll(filepath.Join(fakeApparmorFeaturesSysPath, req), 0755); err != nil { - panic(err) - } - } - } - apparmorFeaturesSysPath = fakeApparmorFeaturesSysPath - - return func() { - if err := os.RemoveAll(temp); err != nil { - panic(err) - } - apparmorFeaturesSysPath = oldApparmorFeaturesSysPath + level := apparmor.Full + if isDevmode { + level = apparmor.None } + return apparmor.MockFeatureLevel(level) } diff -Nru snapd-2.27.5/release/release_test.go snapd-2.28.5/release/release_test.go --- snapd-2.27.5/release/release_test.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/release/release_test.go 2017-10-10 16:16:09.000000000 +0000 @@ -93,12 +93,41 @@ c.Check(os.VersionID, Equals, "0.4") } +func (s *ReleaseTestSuite) TestFamilyOSRelease(c *C) { + mockOSRelease := filepath.Join(c.MkDir(), "mock-os-release") + dump := `NAME="CentOS Linux" +VERSION="7 (Core)" +ID="centos" +ID_LIKE="rhel fedora" +VERSION_ID="7" +PRETTY_NAME="CentOS Linux 7 (Core)" +ANSI_COLOR="0;31" +CPE_NAME="cpe:/o:centos:centos:7" +HOME_URL="https://www.centos.org/" +BUG_REPORT_URL="https://bugs.centos.org/" + +CENTOS_MANTISBT_PROJECT="CentOS-7" +CENTOS_MANTISBT_PROJECT_VERSION="7" +REDHAT_SUPPORT_PRODUCT="centos" +REDHAT_SUPPORT_PRODUCT_VERSION="7"` + err := ioutil.WriteFile(mockOSRelease, []byte(dump), 0644) + c.Assert(err, IsNil) + + reset := release.MockOSReleasePath(mockOSRelease) + defer reset() + + os := release.ReadOSRelease() + c.Check(os.ID, Equals, "centos") + c.Check(os.VersionID, Equals, "7") + c.Check(os.IDLike, DeepEquals, []string{"rhel", "fedora"}) +} + func (s *ReleaseTestSuite) TestReadOSReleaseNotFound(c *C) { reset := release.MockOSReleasePath("not-there") defer reset() os := release.ReadOSRelease() - c.Assert(os, DeepEquals, release.OS{ID: "linux", VersionID: "unknown"}) + c.Assert(os, DeepEquals, release.OS{ID: "linux"}) } func (s *ReleaseTestSuite) TestOnClassic(c *C) { diff -Nru snapd-2.27.5/release/uname_linux.go snapd-2.28.5/release/uname_linux.go --- snapd-2.27.5/release/uname_linux.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/release/uname_linux.go 2017-09-13 14:47:18.000000000 +0000 @@ -23,11 +23,36 @@ "syscall" ) +// We have to implement separate functions for the kernel version and the +// machine name at the moment as the utsname struct is either using int8 +// or uint8 depending on the architecture the code is built for. As there +// is no easy way to generlize this implements the same code twice. The +// way to get this solved is by using []byte inside the utsname struct +// instead of []int8/[]uint8. See https://github.com/golang/go/issues/20753 +// for details. + func getKernelRelease(buf *syscall.Utsname) string { + // The Utsname structures uses [65]int8 or [65]uint8, depending on + // architecture, to represent various fields. We need to convert them to + // strings. input := buf.Release[:] + output := make([]byte, 0, len(input)) + for _, c := range input { + // The input buffer has fixed size but we want to break at the first + // zero we encounter. + if c == 0 { + break + } + output = append(output, byte(c)) + } + return string(output) +} + +func getMachineName(buf *syscall.Utsname) string { // The Utsname structures uses [65]int8 or [65]uint8, depending on - // architecture, to represent various fields. We need to conver them to + // architecture, to represent various fields. We need to convert them to // strings. + input := buf.Machine[:] output := make([]byte, 0, len(input)) for _, c := range input { // The input buffer has fixed size but we want to break at the first @@ -50,3 +75,13 @@ // Release is more informative than Version. return getKernelRelease(&buf) } + +// Machine returns the name of the machine or the string "unknown" if one cannot be determined. +func Machine() string { + var buf syscall.Utsname + err := syscall.Uname(&buf) + if err != nil { + return "unknown" + } + return getMachineName(&buf) +} diff -Nru snapd-2.27.5/release/uname_linux_test.go snapd-2.28.5/release/uname_linux_test.go --- snapd-2.27.5/release/uname_linux_test.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/release/uname_linux_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -33,7 +33,7 @@ c.Check(ver, Not(Equals), "") } -func (s *ReleaseTestSuite) TestGetKenrelRelease(c *C) { +func (s *ReleaseTestSuite) TestGetKernelRelease(c *C) { var buf syscall.Utsname c.Check(release.GetKernelRelease(&buf), Equals, "") @@ -50,3 +50,24 @@ c.Check(release.GetKernelRelease(&buf), Equals, "foo") } + +func (s *ReleaseTestSuite) TestGetKernelMachine(c *C) { + var buf syscall.Utsname + c.Check(release.GetMachineName(&buf), Equals, "") + + buf.Machine[0] = 'a' + buf.Machine[1] = 'r' + buf.Machine[2] = 'm' + buf.Machine[3] = 'v' + buf.Machine[4] = '7' + buf.Machine[5] = 'a' + buf.Machine[6] = 0 + buf.Machine[7] = 'u' + buf.Machine[8] = 'n' + buf.Machine[9] = 'u' + buf.Machine[10] = 's' + buf.Machine[11] = 'e' + buf.Machine[12] = 'd' + + c.Check(release.GetMachineName(&buf), Equals, "armv7a") +} diff -Nru snapd-2.27.5/snap/hooktypes.go snapd-2.28.5/snap/hooktypes.go --- snapd-2.27.5/snap/hooktypes.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/snap/hooktypes.go 2017-10-04 14:43:22.000000000 +0000 @@ -27,6 +27,7 @@ newHookType(regexp.MustCompile("^prepare-device$")), newHookType(regexp.MustCompile("^configure$")), newHookType(regexp.MustCompile("^install$")), + newHookType(regexp.MustCompile("^post-refresh$")), newHookType(regexp.MustCompile("^remove$")), newHookType(regexp.MustCompile("^prepare-(?:plug|slot)-[-a-z0-9]+$")), newHookType(regexp.MustCompile("^connect-(?:plug|slot)-[-a-z0-9]+$")), diff -Nru snapd-2.27.5/snap/info.go snapd-2.28.5/snap/info.go --- snapd-2.27.5/snap/info.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/snap/info.go 2017-09-13 14:47:18.000000000 +0000 @@ -29,7 +29,6 @@ "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/strutil" - "github.com/snapcore/snapd/systemd" "github.com/snapcore/snapd/timeout" ) @@ -156,7 +155,9 @@ LicenseAgreement string LicenseVersion string + License string Epoch string + Base string Confinement ConfinementType Apps map[string]*AppInfo LegacyAliases map[string]*AppInfo // FIXME: eventually drop this @@ -187,6 +188,21 @@ // The ordered list of tracks that contain channels Tracks []string + + Layout map[string]*Layout +} + +// Layout describes a single element of the layout section. +type Layout struct { + Snap *Info + + Path string `json:"path"` + Bind string `json:"bind,omitempty"` + Type string `json:"type,omitempty"` + User string `json:"user,omitempty"` + Group string `json:"group,omitempty"` + Mode os.FileMode `json:"mode,omitempty"` + Symlink string `json:"symlink,omitempty"` } // ChannelSnapInfo is the minimum information that can be used to clearly @@ -406,7 +422,7 @@ StopCommand string ReloadCommand string PostStopCommand string - RestartCond systemd.RestartCondition + RestartCond RestartCondition Completer string // TODO: this should go away once we have more plumbing and can change @@ -459,6 +475,18 @@ return filepath.Join(dirs.SnapBinariesDir, binName) } +// CompleterPath returns the path to the completer snippet for the app binary. +func (app *AppInfo) CompleterPath() string { + var binName string + if app.Name == app.Snap.Name() { + binName = filepath.Base(app.Name) + } else { + binName = fmt.Sprintf("%s.%s", app.Snap.Name(), filepath.Base(app.Name)) + } + + return filepath.Join(dirs.CompletersDir, binName) +} + func (app *AppInfo) launcherCommand(command string) string { if command != "" { command = " " + command diff -Nru snapd-2.27.5/snap/info_snap_yaml.go snapd-2.28.5/snap/info_snap_yaml.go --- snapd-2.27.5/snap/info_snap_yaml.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/snap/info_snap_yaml.go 2017-09-13 14:47:18.000000000 +0000 @@ -21,13 +21,14 @@ import ( "fmt" + "os" "sort" + "strconv" "strings" "gopkg.in/yaml.v2" "github.com/snapcore/snapd/strutil" - "github.com/snapcore/snapd/systemd" "github.com/snapcore/snapd/timeout" ) @@ -40,15 +41,18 @@ Title string `yaml:"title"` Description string `yaml:"description"` Summary string `yaml:"summary"` + License string `yaml:"license,omitempty"` LicenseAgreement string `yaml:"license-agreement,omitempty"` LicenseVersion string `yaml:"license-version,omitempty"` Epoch string `yaml:"epoch,omitempty"` + Base string `yaml:"base,omitempty"` Confinement ConfinementType `yaml:"confinement,omitempty"` Environment strutil.OrderedMap `yaml:"environment,omitempty"` Plugs map[string]interface{} `yaml:"plugs,omitempty"` Slots map[string]interface{} `yaml:"slots,omitempty"` Apps map[string]appYaml `yaml:"apps,omitempty"` Hooks map[string]hookYaml `yaml:"hooks,omitempty"` + Layout map[string]layoutYaml `yaml:"layout,omitempty"` } type appYaml struct { @@ -64,9 +68,9 @@ StopTimeout timeout.Timeout `yaml:"stop-timeout,omitempty"` Completer string `yaml:"completer,omitempty"` - RestartCond systemd.RestartCondition `yaml:"restart-condition,omitempty"` - SlotNames []string `yaml:"slots,omitempty"` - PlugNames []string `yaml:"plugs,omitempty"` + RestartCond RestartCondition `yaml:"restart-condition,omitempty"` + SlotNames []string `yaml:"slots,omitempty"` + PlugNames []string `yaml:"plugs,omitempty"` BusName string `yaml:"bus-name,omitempty"` @@ -77,6 +81,15 @@ PlugNames []string `yaml:"plugs,omitempty"` } +type layoutYaml struct { + Bind string `yaml:"bind,omitempty"` + Type string `yaml:"type,omitempty"` + User string `yaml:"user,omitempty"` + Group string `yaml:"group,omitempty"` + Mode string `yaml:"mode,omitempty"` + Symlink string `yaml:"symlink,omitempty"` +} + // InfoFromSnapYaml creates a new info based on the given snap.yaml data func InfoFromSnapYaml(yamlData []byte) (*Info, error) { var y snapYaml @@ -120,6 +133,34 @@ // Bind unbound slots to all apps bindUnboundSlots(globalSlotNames, snap) + // Collect layout elements. + if y.Layout != nil { + snap.Layout = make(map[string]*Layout, len(y.Layout)) + for path, l := range y.Layout { + var mode os.FileMode = 0755 + if l.Mode != "" { + m, err := strconv.ParseUint(l.Mode, 8, 32) + if err != nil { + return nil, err + } + mode = os.FileMode(m) + } + user := "root" + if l.User != "" { + user = l.User + } + group := "root" + if l.Group != "" { + group = l.Group + } + snap.Layout[path] = &Layout{ + Snap: snap, Path: path, + Bind: l.Bind, Type: l.Type, Symlink: l.Symlink, + User: user, Group: group, Mode: mode, + } + } + } + // Rename specific plugs on the core snap. snap.renameClashingCorePlugs() @@ -158,10 +199,12 @@ OriginalTitle: y.Title, OriginalDescription: y.Description, OriginalSummary: y.Summary, + License: y.License, LicenseAgreement: y.LicenseAgreement, LicenseVersion: y.LicenseVersion, Epoch: epoch, Confinement: confinement, + Base: y.Base, Apps: make(map[string]*AppInfo), LegacyAliases: make(map[string]*AppInfo), Hooks: make(map[string]*HookInfo), diff -Nru snapd-2.27.5/snap/info_snap_yaml_test.go snapd-2.28.5/snap/info_snap_yaml_test.go --- snapd-2.27.5/snap/info_snap_yaml_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/snap/info_snap_yaml_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -28,7 +28,6 @@ "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/strutil" - "github.com/snapcore/snapd/systemd" "github.com/snapcore/snapd/timeout" ) @@ -1058,6 +1057,7 @@ type: app epoch: 1* confinement: devmode +license: GPL-3.0 description: | Foo provides useful services apps: @@ -1101,6 +1101,7 @@ c.Check(info.Publisher, Equals, "") c.Check(info.PublisherID, Equals, "") c.Check(info.Channel, Equals, "") + c.Check(info.License, Equals, "GPL-3.0") app1 := info.Apps["daemon"] app2 := info.Apps["foo"] @@ -1344,7 +1345,7 @@ Name: "svc", Command: "svc1", Daemon: "forking", - RestartCond: systemd.RestartOnAbnormal, + RestartCond: snap.RestartOnAbnormal, StopTimeout: timeout.Timeout(25 * time.Second), StopCommand: "stop-cmd", PostStopCommand: "post-stop-cmd", diff -Nru snapd-2.27.5/snap/info_test.go snapd-2.28.5/snap/info_test.go --- snapd-2.27.5/snap/info_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/snap/info_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -110,6 +110,18 @@ c.Check(info.Apps["foo"].WrapperPath(), Equals, filepath.Join(dirs.SnapBinariesDir, "foo")) } +func (s *infoSuite) TestAppInfoCompleterPath(c *C) { + info, err := snap.InfoFromSnapYaml([]byte(`name: foo +apps: + foo: + bar: +`)) + c.Assert(err, IsNil) + + c.Check(info.Apps["bar"].CompleterPath(), Equals, filepath.Join(dirs.CompletersDir, "foo.bar")) + c.Check(info.Apps["foo"].CompleterPath(), Equals, filepath.Join(dirs.CompletersDir, "foo")) +} + func (s *infoSuite) TestAppInfoLauncherCommand(c *C) { dirs.SetRootDir("") @@ -710,3 +722,46 @@ c.Check(info.Apps["app1"].IsService(), Equals, false) c.Check(info.Apps["app1"].IsService(), Equals, false) } + +func (s *infoSuite) TestLayoutParsing(c *C) { + info, err := snap.InfoFromSnapYaml([]byte(`name: layout-demo +layout: + /usr: + bind: $SNAP/usr + /mytmp: + type: tmpfs + user: nobody + group: nobody + mode: 1777 + /mylink: + symlink: /link/target +`)) + c.Assert(err, IsNil) + + layout := info.Layout + c.Assert(layout, NotNil) + c.Check(layout["/usr"], DeepEquals, &snap.Layout{ + Snap: info, + Path: "/usr", + User: "root", + Group: "root", + Mode: 0755, + Bind: "$SNAP/usr", + }) + c.Check(layout["/mytmp"], DeepEquals, &snap.Layout{ + Snap: info, + Path: "/mytmp", + Type: "tmpfs", + User: "nobody", + Group: "nobody", + Mode: 01777, + }) + c.Check(layout["/mylink"], DeepEquals, &snap.Layout{ + Snap: info, + Path: "/mylink", + User: "root", + Group: "root", + Mode: 0755, + Symlink: "/link/target", + }) +} diff -Nru snapd-2.27.5/snap/restartcond.go snapd-2.28.5/snap/restartcond.go --- snapd-2.27.5/snap/restartcond.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/snap/restartcond.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,75 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2014-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 snap + +import ( + "errors" +) + +// RestartCondition encapsulates the different systemd 'restart' options +type RestartCondition string + +// These are the supported restart conditions +const ( + RestartNever RestartCondition = "never" + RestartOnSuccess RestartCondition = "on-success" + RestartOnFailure RestartCondition = "on-failure" + RestartOnAbnormal RestartCondition = "on-abnormal" + RestartOnAbort RestartCondition = "on-abort" + RestartAlways RestartCondition = "always" +) + +var RestartMap = map[string]RestartCondition{ + "no": RestartNever, + "never": RestartNever, + "on-success": RestartOnSuccess, + "on-failure": RestartOnFailure, + "on-abnormal": RestartOnAbnormal, + "on-abort": RestartOnAbort, + "always": RestartAlways, +} + +// ErrUnknownRestartCondition is returned when trying to unmarshal an unknown restart condition +var ErrUnknownRestartCondition = errors.New("invalid restart condition") + +func (rc RestartCondition) String() string { + if rc == "never" { + return "no" + } + return string(rc) +} + +// UnmarshalYAML so RestartCondition implements yaml's Unmarshaler interface +func (rc *RestartCondition) UnmarshalYAML(unmarshal func(interface{}) error) error { + var v string + + if err := unmarshal(&v); err != nil { + return err + } + + nrc, ok := RestartMap[v] + if !ok { + return ErrUnknownRestartCondition + } + + *rc = nrc + + return nil +} diff -Nru snapd-2.27.5/snap/restartcond_test.go snapd-2.28.5/snap/restartcond_test.go --- snapd-2.27.5/snap/restartcond_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/snap/restartcond_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,51 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2014-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 snap_test + +import ( + . "gopkg.in/check.v1" + "gopkg.in/yaml.v2" + + "github.com/snapcore/snapd/snap" +) + +type restartcondSuite struct{} + +var _ = Suite(&restartcondSuite{}) + +func (*restartcondSuite) TestRestartCondUnmarshal(c *C) { + for name, cond := range snap.RestartMap { + bs := []byte(name) + var rc snap.RestartCondition + + c.Check(yaml.Unmarshal(bs, &rc), IsNil) + c.Check(rc, Equals, cond, Commentf(name)) + } +} + +func (restartcondSuite) TestRestartCondString(c *C) { + for name, cond := range snap.RestartMap { + if name == "never" { + c.Check(cond.String(), Equals, "no") + } else { + c.Check(cond.String(), Equals, name, Commentf(name)) + } + } +} diff -Nru snapd-2.27.5/snap/snapenv/snapenv.go snapd-2.28.5/snap/snapenv/snapenv.go --- snapd-2.27.5/snap/snapenv/snapenv.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/snap/snapenv/snapenv.go 2017-09-13 14:47:18.000000000 +0000 @@ -23,9 +23,11 @@ "fmt" "os" "os/user" + "path/filepath" "strings" "github.com/snapcore/snapd/arch" + "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/snap" ) @@ -76,7 +78,12 @@ // somewhere more reasonable like the snappy module. func basicEnv(info *snap.Info) map[string]string { return map[string]string{ - "SNAP": info.MountDir(), + // This uses CoreSnapMountDir because the computed environment + // variables are conveyed to the started application process which + // shall *either* execute with the new mount namespace where snaps are + // always mounted on /snap OR it is a classically confined snap where + // /snap is a part of the distribution package. + "SNAP": filepath.Join(dirs.CoreSnapMountDir, info.Name(), info.Revision.String()), "SNAP_COMMON": info.CommonDataDir(), "SNAP_DATA": info.DataDir(), "SNAP_NAME": info.Name(), diff -Nru snapd-2.27.5/snap/snapenv/snapenv_test.go snapd-2.28.5/snap/snapenv/snapenv_test.go --- snapd-2.27.5/snap/snapenv/snapenv_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/snap/snapenv/snapenv_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -67,7 +67,7 @@ env := basicEnv(mockSnapInfo) c.Assert(env, DeepEquals, map[string]string{ - "SNAP": fmt.Sprintf("%s/foo/17", dirs.SnapMountDir), + "SNAP": fmt.Sprintf("%s/foo/17", dirs.CoreSnapMountDir), "SNAP_ARCH": arch.UbuntuArchitecture(), "SNAP_COMMON": "/var/snap/foo/common", "SNAP_DATA": "/var/snap/foo/17", diff -Nru snapd-2.27.5/snap/squashfs/squashfs.go snapd-2.28.5/snap/squashfs/squashfs.go --- snapd-2.27.5/snap/squashfs/squashfs.go 2016-11-24 09:36:04.000000000 +0000 +++ snapd-2.28.5/snap/squashfs/squashfs.go 2017-09-13 14:47:18.000000000 +0000 @@ -27,7 +27,6 @@ "path" "path/filepath" "regexp" - "strings" "github.com/snapcore/snapd/osutil" ) @@ -88,23 +87,8 @@ return osutil.CopyFile(s.path, targetPath, osutil.CopyFlagPreserveAll|osutil.CopyFlagSync) } -var runCommandWithOutput = func(args ...string) ([]byte, error) { - cmd := exec.Command(args[0], args[1:]...) - output, err := cmd.CombinedOutput() - if err != nil { - return nil, fmt.Errorf("cmd: %q failed: %v (%q)", strings.Join(args, " "), err, output) - } - - return output, nil -} - -var runCommand = func(args ...string) error { - _, err := runCommandWithOutput(args...) - return err -} - func (s *Snap) Unpack(src, dstDir string) error { - return runCommand("unsquashfs", "-f", "-i", "-d", dstDir, s.path, src) + return exec.Command("unsquashfs", "-f", "-i", "-d", dstDir, s.path, src).Run() } // Size returns the size of a squashfs snap. @@ -126,7 +110,7 @@ defer os.RemoveAll(tmpdir) unpackDir := filepath.Join(tmpdir, "unpack") - if err := runCommand("unsquashfs", "-i", "-d", unpackDir, s.path, filePath); err != nil { + if err := exec.Command("unsquashfs", "-i", "-d", unpackDir, s.path, filePath).Run(); err != nil { return nil, err } @@ -135,10 +119,10 @@ // ListDir returns the content of a single directory inside a squashfs snap. func (s *Snap) ListDir(dirPath string) ([]string, error) { - output, err := runCommandWithOutput( - "unsquashfs", "-no-progress", "-dest", "_", "-l", s.path, dirPath) + output, err := exec.Command( + "unsquashfs", "-no-progress", "-dest", "_", "-l", s.path, dirPath).CombinedOutput() if err != nil { - return nil, err + return nil, osutil.OutputErr(output, err) } prefixPath := path.Join("_", dirPath) @@ -165,12 +149,12 @@ } return osutil.ChDir(buildDir, func() error { - return runCommand( + return exec.Command( "mksquashfs", ".", fullSnapPath, "-noappend", "-comp", "xz", "-no-xattrs", - ) + ).Run() }) } diff -Nru snapd-2.27.5/snap/squashfs/squashfs_test.go snapd-2.28.5/snap/squashfs/squashfs_test.go --- snapd-2.27.5/snap/squashfs/squashfs_test.go 2016-08-23 18:49:28.000000000 +0000 +++ snapd-2.28.5/snap/squashfs/squashfs_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -24,7 +24,6 @@ "os" "os/exec" "path/filepath" - "regexp" "strings" "testing" @@ -169,18 +168,3 @@ squashfs-root/random/dir `) } - -func (s *SquashfsTestSuite) TestRunCommandGood(c *C) { - err := runCommand("true") - c.Assert(err, IsNil) -} - -func (s *SquashfsTestSuite) TestRunCommandBad(c *C) { - err := runCommand("false") - c.Assert(err, ErrorMatches, regexp.QuoteMeta(`cmd: "false" failed: exit status 1 ("")`)) -} - -func (s *SquashfsTestSuite) TestRunCommandUgly(c *C) { - err := runCommand("cat", "/no/such/file") - c.Assert(err, ErrorMatches, regexp.QuoteMeta(`cmd: "cat /no/such/file" failed: exit status 1 ("cat: /no/such/file: No such file or directory\n")`)) -} diff -Nru snapd-2.27.5/snap/types.go snapd-2.28.5/snap/types.go --- snapd-2.27.5/snap/types.go 2016-11-24 09:36:04.000000000 +0000 +++ snapd-2.28.5/snap/types.go 2017-09-13 14:47:18.000000000 +0000 @@ -31,8 +31,11 @@ const ( TypeApp Type = "app" TypeGadget Type = "gadget" - TypeOS Type = "os" TypeKernel Type = "kernel" + TypeBase Type = "base" + + // FIXME: this really should be TypeCore + TypeOS Type = "os" ) // UnmarshalJSON sets *m to a copy of data. @@ -65,7 +68,7 @@ t = TypeApp } - if t != TypeApp && t != TypeGadget && t != TypeOS && t != TypeKernel { + if t != TypeApp && t != TypeGadget && t != TypeOS && t != TypeKernel && t != TypeBase { return fmt.Errorf("invalid snap type: %q", str) } diff -Nru snapd-2.27.5/snap/types_test.go snapd-2.28.5/snap/types_test.go --- snapd-2.27.5/snap/types_test.go 2016-11-24 09:36:04.000000000 +0000 +++ snapd-2.28.5/snap/types_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -53,6 +53,10 @@ out, err = json.Marshal(TypeKernel) c.Assert(err, IsNil) c.Check(string(out), Equals, "\"kernel\"") + + out, err = json.Marshal(TypeBase) + c.Assert(err, IsNil) + c.Check(string(out), Equals, "\"base\"") } func (s *typeSuite) TestJsonUnmarshalTypes(c *C) { @@ -77,6 +81,10 @@ err = json.Unmarshal([]byte("\"kernel\""), &st) c.Assert(err, IsNil) c.Check(st, Equals, TypeKernel) + + err = json.Unmarshal([]byte("\"base\""), &st) + c.Assert(err, IsNil) + c.Check(st, Equals, TypeBase) } func (s *typeSuite) TestJsonUnmarshalInvalidTypes(c *C) { @@ -104,6 +112,10 @@ out, err = yaml.Marshal(TypeKernel) c.Assert(err, IsNil) c.Check(string(out), Equals, "kernel\n") + + out, err = yaml.Marshal(TypeBase) + c.Assert(err, IsNil) + c.Check(string(out), Equals, "base\n") } func (s *typeSuite) TestYamlUnmarshalTypes(c *C) { @@ -128,6 +140,10 @@ err = yaml.Unmarshal([]byte("kernel"), &st) c.Assert(err, IsNil) c.Check(st, Equals, TypeKernel) + + err = yaml.Unmarshal([]byte("base"), &st) + c.Assert(err, IsNil) + c.Check(st, Equals, TypeBase) } func (s *typeSuite) TestYamlUnmarshalInvalidTypes(c *C) { diff -Nru snapd-2.27.5/snap/validate.go snapd-2.28.5/snap/validate.go --- snapd-2.27.5/snap/validate.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/snap/validate.go 2017-09-13 14:47:18.000000000 +0000 @@ -21,7 +21,11 @@ import ( "fmt" + "os" "regexp" + "strings" + + "github.com/snapcore/snapd/spdx" ) // Regular expression describing correct identifiers. @@ -47,6 +51,14 @@ return nil } +// ValidateLicense checks if a string is a valid SPDX expression. +func ValidateLicense(license string) error { + if err := spdx.ValidateLicense(license); err != nil { + return fmt.Errorf("cannot validate license %q: %s", license, err) + } + return nil +} + // ValidateHook validates the content of the given HookInfo func ValidateHook(hook *HookInfo) error { valid := validHookName.MatchString(hook.Name) @@ -87,6 +99,14 @@ return err } + license := info.License + if license != "" { + err := ValidateLicense(license) + if err != nil { + return err + } + } + // validate app entries for _, app := range info.Apps { err := ValidateApp(app) @@ -114,6 +134,12 @@ if err := plugsSlotsUniqueNames(info); err != nil { return err } + + for _, layout := range info.Layout { + if err := ValidateLayout(layout); err != nil { + return err + } + } return nil } @@ -170,3 +196,74 @@ } return nil } + +// ValidatePathVariables ensures that given path contains only $SNAP, $SNAP_DATA or $SNAP_COMMON. +func ValidatePathVariables(path string) error { + for path != "" { + start := strings.IndexRune(path, '$') + if start < 0 { + break + } + path = path[start+1:] + end := strings.IndexFunc(path, func(c rune) bool { + return (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && c != '_' + }) + if end < 0 { + end = len(path) + } + v := path[:end] + if v != "SNAP" && v != "SNAP_DATA" && v != "SNAP_COMMON" { + return fmt.Errorf("reference to unknown variable %q", "$"+v) + } + path = path[end:] + } + return nil +} + +// ValidateLayout ensures that the given layout contains only valid subset of constructs. +func ValidateLayout(li *Layout) error { + // The path is used to identify the layout below so validate it first. + if li.Path == "" { + return fmt.Errorf("cannot accept layout with empty path") + } else { + if err := ValidatePathVariables(li.Path); err != nil { + return fmt.Errorf("cannot accept layout of %q: %s", li.Path, err) + } + } + // Presence of the Bind, Type and Symlink fields implies kind of layout. + if li.Bind == "" && li.Type == "" && li.Symlink == "" { + return fmt.Errorf("cannot determine layout for %q", li.Path) + } + if (li.Bind != "" && li.Type != "") || + (li.Bind != "" && li.Symlink != "") || + (li.Type != "" && li.Symlink != "") { + return fmt.Errorf("cannot accept conflicting layout for %q", li.Path) + } + if li.Bind != "" { + if err := ValidatePathVariables(li.Bind); err != nil { + return fmt.Errorf("cannot accept layout of %q: %s", li.Path, err) + } + } + // Only the "tmpfs" filesystem is allowed. + if li.Type != "" && li.Type != "tmpfs" { + return fmt.Errorf("cannot accept filesystem %q for %q", li.Type, li.Path) + } + if li.Symlink != "" { + if err := ValidatePathVariables(li.Symlink); err != nil { + return fmt.Errorf("cannot accept layout of %q: %s", li.Path, err) + } + } + // Only certain users and groups are allowed. + // TODO: allow declared snap user and group names. + if li.User != "" && li.User != "root" && li.User != "nobody" { + return fmt.Errorf("cannot accept user %q for %q", li.User, li.Path) + } + if li.Group != "" && li.Group != "root" && li.Group != "nobody" { + return fmt.Errorf("cannot accept group %q for %q", li.Group, li.Path) + } + // "at most" 0777 permissions are allowed. + if li.Mode&^os.FileMode(0777) != 0 { + return fmt.Errorf("cannot accept mode %#0o for %q", li.Mode, li.Path) + } + return nil +} diff -Nru snapd-2.27.5/snap/validate_test.go snapd-2.28.5/snap/validate_test.go --- snapd-2.27.5/snap/validate_test.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/snap/validate_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -82,6 +82,23 @@ } } +func (s *ValidateSuite) TestValidateLicense(c *C) { + validLicenses := []string{ + "GPL-3.0", "(GPL-3.0)", "GPL-3.0+", "GPL-3.0 AND GPL-2.0", "GPL-3.0 OR GPL-2.0", "MIT OR (GPL-3.0 AND GPL-2.0)", "MIT OR(GPL-3.0 AND GPL-2.0)", + } + for _, epoch := range validLicenses { + err := ValidateLicense(epoch) + c.Assert(err, IsNil) + } + invalidLicenses := []string{ + "GPL~3.0", "3.0-GPL", "(GPL-3.0", "(GPL-3.0))", "GPL-3.0++", "+GPL-3.0", "GPL-3.0 GPL-2.0", + } + for _, epoch := range invalidLicenses { + err := ValidateLicense(epoch) + c.Assert(err, NotNil) + } +} + func (s *ValidateSuite) TestValidateHook(c *C) { validHooks := []*HookInfo{ {Name: "a"}, @@ -244,6 +261,25 @@ c.Assert(Validate(info), IsNil) } +func (s *ValidateSuite) TestIllegalSnapLicense(c *C) { + info, err := InfoFromSnapYaml([]byte(`name: foo +version: 1.0 +license: GPL~3.0 +`)) + c.Assert(err, IsNil) + + err = Validate(info) + c.Check(err, ErrorMatches, `cannot validate license "GPL~3.0": unknown license: GPL~3.0`) +} + +func (s *ValidateSuite) TestMissingSnapLicenseIsOkay(c *C) { + info, err := InfoFromSnapYaml([]byte(`name: foo +version: 1.0 +`)) + c.Assert(err, IsNil) + c.Assert(Validate(info), IsNil) +} + func (s *ValidateSuite) TestIllegalHookName(c *C) { hookType := NewHookType(regexp.MustCompile(".*")) restore := MockSupportedHookTypes([]*HookType{hookType}) @@ -311,3 +347,46 @@ c.Assert(err, ErrorMatches, `invalid alias name: ".*"`) } } + +func (s *ValidateSuite) TestValidateLayout(c *C) { + // Several invalid layouts. + c.Check(ValidateLayout(&Layout{}), + ErrorMatches, "cannot accept layout with empty path") + c.Check(ValidateLayout(&Layout{Path: "/foo"}), + ErrorMatches, `cannot determine layout for "/foo"`) + c.Check(ValidateLayout(&Layout{Path: "/foo", Bind: "/bar", Type: "tmpfs"}), + ErrorMatches, `cannot accept conflicting layout for "/foo"`) + c.Check(ValidateLayout(&Layout{Path: "/foo", Bind: "/bar", Symlink: "/froz"}), + ErrorMatches, `cannot accept conflicting layout for "/foo"`) + c.Check(ValidateLayout(&Layout{Path: "/foo", Type: "tmpfs", Symlink: "/froz"}), + ErrorMatches, `cannot accept conflicting layout for "/foo"`) + c.Check(ValidateLayout(&Layout{Path: "/foo", Type: "ext4"}), + ErrorMatches, `cannot accept filesystem "ext4" for "/foo"`) + c.Check(ValidateLayout(&Layout{Path: "/foo/bar", Type: "tmpfs", User: "foo"}), + ErrorMatches, `cannot accept user "foo" for "/foo/bar"`) + c.Check(ValidateLayout(&Layout{Path: "/foo/bar", Type: "tmpfs", Group: "foo"}), + ErrorMatches, `cannot accept group "foo" for "/foo/bar"`) + c.Check(ValidateLayout(&Layout{Path: "/foo", Type: "tmpfs", Mode: 01755}), + ErrorMatches, `cannot accept mode 01755 for "/foo"`) + c.Check(ValidateLayout(&Layout{Path: "$FOO", Type: "tmpfs"}), + ErrorMatches, `cannot accept layout of "\$FOO": reference to unknown variable "\$FOO"`) + c.Check(ValidateLayout(&Layout{Path: "/foo", Bind: "$BAR"}), + ErrorMatches, `cannot accept layout of "/foo": reference to unknown variable "\$BAR"`) + c.Check(ValidateLayout(&Layout{Path: "/foo", Symlink: "$BAR"}), + ErrorMatches, `cannot accept layout of "/foo": reference to unknown variable "\$BAR"`) + // Several valid layouts. + c.Check(ValidateLayout(&Layout{Path: "/tmp", Type: "tmpfs"}), IsNil) + c.Check(ValidateLayout(&Layout{Path: "/usr", Bind: "$SNAP/usr"}), IsNil) + c.Check(ValidateLayout(&Layout{Path: "/var", Bind: "$SNAP_DATA/var"}), IsNil) + c.Check(ValidateLayout(&Layout{Path: "/var", Bind: "$SNAP_COMMON/var"}), IsNil) + c.Check(ValidateLayout(&Layout{Path: "/etc/foo.conf", Symlink: "$SNAP_DATA/etc/foo.conf"}), IsNil) + c.Check(ValidateLayout(&Layout{Path: "/a/b", Type: "tmpfs", User: "nobody"}), IsNil) + c.Check(ValidateLayout(&Layout{Path: "/a/b", Type: "tmpfs", User: "root"}), IsNil) + c.Check(ValidateLayout(&Layout{Path: "/a/b", Type: "tmpfs", Group: "nobody"}), IsNil) + c.Check(ValidateLayout(&Layout{Path: "/a/b", Type: "tmpfs", Group: "root"}), IsNil) + c.Check(ValidateLayout(&Layout{Path: "/a/b", Type: "tmpfs", Mode: 0655}), IsNil) + c.Check(ValidateLayout(&Layout{Path: "/usr", Symlink: "$SNAP/usr"}), IsNil) + c.Check(ValidateLayout(&Layout{Path: "/var", Symlink: "$SNAP_DATA/var"}), IsNil) + c.Check(ValidateLayout(&Layout{Path: "/var", Symlink: "$SNAP_COMMON/var"}), IsNil) + c.Check(ValidateLayout(&Layout{Path: "$SNAP/data", Symlink: "$SNAP_DATA"}), IsNil) +} diff -Nru snapd-2.27.5/spdx/licenses.go snapd-2.28.5/spdx/licenses.go --- snapd-2.27.5/spdx/licenses.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/spdx/licenses.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,501 @@ +// -*- 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 spdx + +// from https://spdx.org/licenses/ +var osi = []string{ + "AFL-1.1", + "AFL-1.2", + "AFL-2.0", + "AFL-2.1", + "AFL-3.0", + "APL-1.0", + "Apache-1.1", + "Apache-2.0", + "APSL-1.0", + "APSL-1.1", + "APSL-1.2", + "APSL-2.0", + "Artistic-1.0", + "Artistic-1.0-Perl", + "Artistic-1.0-cl8", + "Artistic-2.0", + "AAL", + "BSL-1.0", + "BSD-2-Clause", + "BSD-3-Clause", + "0BSD", + "CECILL-2.1", + "CNRI-Python", + "CDDL-1.0", + "CPAL-1.0", + "CPL-1.0", + "CATOSL-1.1", + "CUA-OPL-1.0", + "EPL-1.0", + "ECL-1.0", + "ECL-2.0", + "EFL-1.0", + "EFL-2.0", + "Entessa", + "EUDatagrid", + "EUPL-1.1", + "Fair", + "Frameworx-1.0", + "AGPL-3.0", + "GPL-2.0", + "GPL-3.0", + "LGPL-2.1", + "LGPL-3.0", + "LGPL-2.0", + "HPND", + "IPL-1.0", + "Intel", + "IPA", + "ISC", + "LPPL-1.3c", + "LiLiQ-P-1.1", + "LiLiQ-Rplus-1.1", + "LiLiQ-R-1.1", + "LPL-1.02", + "LPL-1.0", + "MS-PL", + "MS-RL", + "MirOS", + "MIT", + "Motosoto", + "MPL-1.0", + "MPL-1.1", + "MPL-2.0", + "MPL-2.0-no-copyleft-exception", + "Multics", + "NASA-1.3", + "Naumen", + "NGPL", + "Nokia", + "NPOSL-3.0", + "NTP", + "OCLC-2.0", + "OGTSL", + "OSL-1.0", + "OSL-2.0", + "OSL-2.1", + "OSL-3.0", + "OSET-PL-2.1", + "PHP-3.0", + "PostgreSQL", + "Python-2.0", + "QPL-1.0", + "RPSL-1.0", + "RPL-1.1", + "RPL-1.5", + "RSCPL", + "OFL-1.1", + "SimPL-2.0", + "Sleepycat", + "SISSL", + "SPL-1.0", + "Watcom-1.0", + "UPL-1.0", + "NCSA", + "VSL-1.0", + "W3C", + "Xnet", + "Zlib", + "ZPL-2.0", +} + +var allLicenses = []string{ + "Glide", + "Abstyles", + "AFL-1.1", + "AFL-1.2", + "AFL-2.0", + "AFL-2.1", + "AFL-3.0", + "AMPAS", + "APL-1.0", + "Adobe-Glyph", + "APAFML", + "Adobe-2006", + "AGPL-1.0", + "Afmparse", + "Aladdin", + "ADSL", + "AMDPLPA", + "ANTLR-PD", + "Apache-1.0", + "Apache-1.1", + "Apache-2.0", + "AML", + "APSL-1.0", + "APSL-1.1", + "APSL-1.2", + "APSL-2.0", + "Artistic-1.0", + "Artistic-1.0-Perl", + "Artistic-1.0-cl8", + "Artistic-2.0", + "AAL", + "Bahyph", + "Barr", + "Beerware", + "BitTorrent-1.0", + "BitTorrent-1.1", + "BSL-1.0", + "Borceux", + "BSD-2-Clause", + "BSD-2-Clause-FreeBSD", + "BSD-2-Clause-NetBSD", + "BSD-3-Clause", + "BSD-3-Clause-Clear", + "BSD-3-Clause-No-Nuclear-License", + "BSD-3-Clause-No-Nuclear-License-2014", + "BSD-3-Clause-No-Nuclear-Warranty", + "BSD-4-Clause", + "BSD-Protection", + "BSD-Source-Code", + "BSD-3-Clause-Attribution", + "0BSD", + "BSD-4-Clause-UC", + "bzip2-1.0.5", + "bzip2-1.0.6", + "Caldera", + "CECILL-1.0", + "CECILL-1.1", + "CECILL-2.0", + "CECILL-2.1", + "CECILL-B", + "CECILL-C", + "ClArtistic", + "MIT-CMU", + "CNRI-Jython", + "CNRI-Python", + "CNRI-Python-GPL-Compatible", + "CPOL-1.02", + "CDDL-1.0", + "CDDL-1.1", + "CPAL-1.0", + "CPL-1.0", + "CATOSL-1.1", + "Condor-1.1", + "CC-BY-1.0", + "CC-BY-2.0", + "CC-BY-2.5", + "CC-BY-3.0", + "CC-BY-4.0", + "CC-BY-ND-1.0", + "CC-BY-ND-2.0", + "CC-BY-ND-2.5", + "CC-BY-ND-3.0", + "CC-BY-ND-4.0", + "CC-BY-NC-1.0", + "CC-BY-NC-2.0", + "CC-BY-NC-2.5", + "CC-BY-NC-3.0", + "CC-BY-NC-4.0", + "CC-BY-NC-ND-1.0", + "CC-BY-NC-ND-2.0", + "CC-BY-NC-ND-2.5", + "CC-BY-NC-ND-3.0", + "CC-BY-NC-ND-4.0", + "CC-BY-NC-SA-1.0", + "CC-BY-NC-SA-2.0", + "CC-BY-NC-SA-2.5", + "CC-BY-NC-SA-3.0", + "CC-BY-NC-SA-4.0", + "CC-BY-SA-1.0", + "CC-BY-SA-2.0", + "CC-BY-SA-2.5", + "CC-BY-SA-3.0", + "CC-BY-SA-4.0", + "CC0-1.0", + "Crossword", + "CrystalStacker", + "CUA-OPL-1.0", + "Cube", + "curl", + "D-FSL-1.0", + "diffmark", + "WTFPL", + "DOC", + "Dotseqn", + "DSDP", + "dvipdfm", + "EPL-1.0", + "ECL-1.0", + "ECL-2.0", + "eGenix", + "EFL-1.0", + "EFL-2.0", + "MIT-advertising", + "MIT-enna", + "Entessa", + "ErlPL-1.1", + "EUDatagrid", + "EUPL-1.0", + "EUPL-1.1", + "Eurosym", + "Fair", + "MIT-feh", + "Frameworx-1.0", + "FreeImage", + "FTL", + "FSFAP", + "FSFUL", + "FSFULLR", + "Giftware", + "GL2PS", + "Glulxe", + "AGPL-3.0", + "GFDL-1.1", + "GFDL-1.2", + "GFDL-1.3", + "GPL-1.0", + "GPL-2.0", + "GPL-3.0", + "LGPL-2.1", + "LGPL-3.0", + "LGPL-2.0", + "gnuplot", + "gSOAP-1.3b", + "HaskellReport", + "HPND", + "IBM-pibs", + "IPL-1.0", + "ICU", + "ImageMagick", + "iMatix", + "Imlib2", + "IJG", + "Info-ZIP", + "Intel-ACPI", + "Intel", + "Interbase-1.0", + "IPA", + "ISC", + "JasPer-2.0", + "JSON", + "LPPL-1.0", + "LPPL-1.1", + "LPPL-1.2", + "LPPL-1.3a", + "LPPL-1.3c", + "Latex2e", + "BSD-3-Clause-LBNL", + "Leptonica", + "LGPLLR", + "Libpng", + "libtiff", + "LAL-1.2", + "LAL-1.3", + "LiLiQ-P-1.1", + "LiLiQ-Rplus-1.1", + "LiLiQ-R-1.1", + "LPL-1.02", + "LPL-1.0", + "MakeIndex", + "MTLL", + "MS-PL", + "MS-RL", + "MirOS", + "MITNFA", + "MIT", + "Motosoto", + "MPL-1.0", + "MPL-1.1", + "MPL-2.0", + "MPL-2.0-no-copyleft-exception", + "mpich2", + "Multics", + "Mup", + "NASA-1.3", + "Naumen", + "NBPL-1.0", + "Net-SNMP", + "NetCDF", + "NGPL", + "NOSL", + "NPL-1.0", + "NPL-1.1", + "Newsletr", + "NLPL", + "Nokia", + + "NPOSL-3.0", + + "NLOD-1.0", + "Noweb", + "NRL", + "NTP", + + "Nunit", + "OCLC-2.0", + + "ODbL-1.0", + "PDDL-1.0", + "OCCT-PL", + "OGTSL", + + "OLDAP-2.2.2", + "OLDAP-1.1", + "OLDAP-1.2", + "OLDAP-1.3", + "OLDAP-1.4", + "OLDAP-2.0", + "OLDAP-2.0.1", + "OLDAP-2.1", + "OLDAP-2.2", + "OLDAP-2.2.1", + "OLDAP-2.3", + "OLDAP-2.4", + "OLDAP-2.5", + "OLDAP-2.6", + "OLDAP-2.7", + "OLDAP-2.8", + "OML", + "OPL-1.0", + "OSL-1.0", + + "OSL-1.1", + "OSL-2.0", + + "OSL-2.1", + + "OSL-3.0", + + "OpenSSL", + "OSET-PL-2.1", + + "PHP-3.0", + + "PHP-3.01", + "Plexus", + "PostgreSQL", + + "psfrag", + "psutils", + "Python-2.0", + + "QPL-1.0", + + "Qhull", + "Rdisc", + "RPSL-1.0", + + "RPL-1.1", + + "RPL-1.5", + + "RHeCos-1.1", + "RSCPL", + + "RSA-MD", + "Ruby", + "SAX-PD", + "Saxpath", + "SCEA", + "SWL", + "SMPPL", + "Sendmail", + "SGI-B-1.0", + "SGI-B-1.1", + "SGI-B-2.0", + "OFL-1.0", + "OFL-1.1", + + "SimPL-2.0", + + "Sleepycat", + + "SNIA", + "Spencer-86", + "Spencer-94", + "Spencer-99", + "SMLNJ", + "SugarCRM-1.1.3", + "SISSL", + + "SISSL-1.2", + "SPL-1.0", + + "Watcom-1.0", + + "TCL", + "TCP-wrappers", + "Unlicense", + "TMate", + "TORQUE-1.1", + "TOSL", + "Unicode-DFS-2015", + "Unicode-DFS-2016", + "Unicode-TOU", + "UPL-1.0", + + "NCSA", + + "Vim", + "VOSTROM", + "VSL-1.0", + + "W3C-20150513", + "W3C-19980720", + "W3C", + + "Wsuipa", + "Xnet", + + "X11", + "Xerox", + "XFree86-1.1", + "xinetd", + "xpp", + "XSkat", + "YPL-1.0", + "YPL-1.1", + "Zed", + "Zend-2.0", + "Zimbra-1.3", + "Zimbra-1.4", + "Zlib", + + "zlib-acknowledgement", + "ZPL-1.1", + "ZPL-2.0", + + "ZPL-2.1", + + // FIXME: non SPDX licenses that the snapstore uses + "Proprietary", + "Other Open Source", +} + +// from https://www.google.com/url?q=https://docs.google.com/a/s.sfusd.edu/document/d/1wE_zvLU4c291ACi9wIJmQoE4ltKRW4rzM1TYiIvEVOs/edit?pli%3D1%23heading%3Dh.ruv3yl8g6czd&sa=D&ust=1473291615601000&usg=AFQjCNFyLcPLdEarX1TOesGWxg9Afb57mA +var licenseExceptions = []string{ + "Autoconf-exception-2.0", + "Autoconf-exception-3.0", + "Bison-exception-2.2", + "Classpath-exception-2.0", + "eCos-exception-2.0", + "Font-exception-2.0", + "GCC-exception-2.0", + "GCC-exception-3.1", + "WxWindows-exception-3.1", +} diff -Nru snapd-2.27.5/spdx/parser.go snapd-2.28.5/spdx/parser.go --- snapd-2.27.5/spdx/parser.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/spdx/parser.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,154 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2016 Canonical Ltd + * + * This program is free software: you can redistribuLicenseidte 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 spdx + +import ( + "fmt" + "io" + "strings" +) + +type operator string + +const ( + opUNSET operator = "" + opAND = "AND" + opOR = "OR" + opWITH = "WITH" +) + +func isOperator(tok string) bool { + return tok == opAND || tok == opOR || tok == opWITH +} + +type licenseID string + +func newLicenseID(s string) (licenseID, error) { + needle := s + if strings.HasSuffix(s, "+") { + needle = s[:len(s)-1] + } + for _, known := range allLicenses { + if needle == known { + return licenseID(s), nil + } + } + return "", fmt.Errorf("unknown license: %s", s) +} + +type licenseExceptionID string + +func newLicenseExceptionID(s string) (licenseExceptionID, error) { + for _, known := range licenseExceptions { + if s == known { + return licenseExceptionID(s), nil + } + } + return "", fmt.Errorf("unknown license exception: %s", s) +} + +type parser struct { + s *Scanner +} + +func newParser(r io.Reader) *parser { + return &parser{s: NewScanner(r)} +} + +func (p *parser) Validate() error { + return p.validate(0) +} + +func (p *parser) advance(id string) error { + if p.s.Text() != id { + return fmt.Errorf("expected %q got %q", id, p.s.Text()) + } + return nil +} + +func (p *parser) validate(depth int) error { + last := "" + + for p.s.Scan() { + tok := p.s.Text() + + switch { + case tok == "(": + if last == opWITH { + return fmt.Errorf("%q not allowed after WITH", tok) + } + if err := p.validate(depth + 1); err != nil { + return err + } + if p.s.Text() != ")" { + return fmt.Errorf(`expected ")" got %q`, p.s.Text()) + } + case tok == ")": + if depth == 0 { + return fmt.Errorf(`unexpected ")"`) + } + if last == "" { + return fmt.Errorf("empty expression") + } + return nil + case isOperator(tok): + if last == "" { + return fmt.Errorf("missing license before %s", tok) + } + if last == opAND || last == opOR { + return fmt.Errorf("expected license name, got %q", tok) + } + if last == opWITH { + return fmt.Errorf("expected exception name, got %q", tok) + } + default: + switch { + case last == opWITH: + if _, err := newLicenseExceptionID(tok); err != nil { + return err + } + case last == "", last == opAND, last == opOR: + if _, err := newLicenseID(tok); err != nil { + return err + } + default: + if _, err := newLicenseID(last); err == nil { + if _, err := newLicenseID(tok); err == nil { + return fmt.Errorf("missing AND or OR between %q and %q", last, tok) + } + } + return fmt.Errorf("unexpected string: %q", tok) + } + + } + last = tok + } + if err := p.s.Err(); err != nil { + return err + } + if isOperator(last) { + return fmt.Errorf("missing license after %s", last) + } + if last == "" { + return fmt.Errorf("empty expression") + } + + return nil +} diff -Nru snapd-2.27.5/spdx/parser_test.go snapd-2.28.5/spdx/parser_test.go --- snapd-2.27.5/spdx/parser_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/spdx/parser_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,76 @@ +// -*- 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 spdx_test + +import ( + "testing" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/spdx" +) + +func Test(t *testing.T) { TestingT(t) } + +type spdxSuite struct{} + +var _ = Suite(&spdxSuite{}) + +func (s *spdxSuite) TestParseHappy(c *C) { + for _, t := range []string{ + "GPL-2.0", + "GPL-2.0+", + "GPL-2.0 AND BSD-2-Clause", + "GPL-2.0 OR BSD-2-Clause", + "GPL-2.0 WITH GCC-exception-3.1", + "(GPL-2.0 AND BSD-2-Clause)", + "GPL-2.0 AND (BSD-2-Clause OR 0BSD)", + "GPL-2.0 AND (BSD-2-Clause OR 0BSD) WITH GCC-exception-3.1", + "((GPL-2.0 AND (BSD-2-Clause OR 0BSD)) OR GPL-3.0) ", + } { + err := spdx.ValidateLicense(t) + c.Check(err, IsNil, Commentf("input: %q", t)) + } +} + +func (s *spdxSuite) TestParseError(c *C) { + for _, t := range []struct { + inp string + errStr string + }{ + {"", "empty expression"}, + {"GPL-3.0 AND ()", "empty expression"}, + {"()", "empty expression"}, + + {"FOO", `unknown license: FOO`}, + {"GPL-3.0 xxx", `unexpected string: "xxx"`}, + {"GPL-2.0 GPL-3.0", `missing AND or OR between "GPL-2.0" and "GPL-3.0"`}, + {"(GPL-2.0))", `unexpected "\)"`}, + {"(GPL-2.0", `expected "\)" got ""`}, + {"OR", "missing license before OR"}, + {"OR GPL-2.0", "missing license before OR"}, + {"GPL-2.0 OR", "missing license after OR"}, + {"GPL-2.0 WITH BAR", "unknown license exception: BAR"}, + {"GPL-2.0 WITH (foo)", `"\(" not allowed after WITH`}, + } { + err := spdx.ValidateLicense(t.inp) + c.Check(err, ErrorMatches, t.errStr, Commentf("input: %q", t.inp)) + } +} diff -Nru snapd-2.27.5/spdx/scanner.go snapd-2.28.5/spdx/scanner.go --- snapd-2.27.5/spdx/scanner.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/spdx/scanner.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,69 @@ +// -*- 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 spdx + +import ( + "bufio" + "io" +) + +type Scanner struct { + *bufio.Scanner +} + +func spdxSplit(data []byte, atEOF bool) (advance int, token []byte, err error) { + // skip WS + start := 0 + for ; start < len(data); start++ { + if data[start] != ' ' && data[start] != '\n' { + break + } + } + if start == len(data) { + return start, nil, nil + } + + switch data[start] { + // found ( or ) + case '(', ')': + return start + 1, data[start : start+1], nil + } + + for i := start; i < len(data); i++ { + switch data[i] { + // token finished + case ' ', '\n': + return i + 1, data[start:i], nil + // found ( or ) - we need to rescan it + case '(', ')': + return i, data[start:i], nil + } + } + if atEOF && len(data) > start { + return len(data), data[start:], nil + } + return start, nil, nil +} + +func NewScanner(r io.Reader) *Scanner { + scanner := bufio.NewScanner(r) + scanner.Split(spdxSplit) + return &Scanner{scanner} +} diff -Nru snapd-2.27.5/spdx/scanner_test.go snapd-2.28.5/spdx/scanner_test.go --- snapd-2.27.5/spdx/scanner_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/spdx/scanner_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,49 @@ +// -*- 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 spdx_test + +import ( + "bytes" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/spdx" +) + +func (s *spdxSuite) TestScannerHappy(c *C) { + for _, t := range []struct { + inp string + tokens []string + }{ + {"0BSD", []string{"0BSD"}}, + {"0BSD OR GPL-2.0", []string{"0BSD", "OR", "GPL-2.0"}}, + {"(0BSD OR GPL-2.0)", []string{"(", "0BSD", "OR", "GPL-2.0", ")"}}, + {"(A (B C))", []string{"(", "A", "(", "B", "C", ")", ")"}}, + } { + i := 0 + scanner := spdx.NewScanner(bytes.NewBufferString(t.inp)) + for scanner.Scan() { + c.Check(scanner.Text(), Equals, t.tokens[i]) + i++ + } + c.Check(len(t.tokens), Equals, i) + c.Check(scanner.Err(), IsNil) + } +} diff -Nru snapd-2.27.5/spdx/validate.go snapd-2.28.5/spdx/validate.go --- snapd-2.27.5/spdx/validate.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/spdx/validate.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,34 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2016 Canonical Ltd + * + * This program is free software: you can redistribuLicenseidte 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 spdx + +import "bytes" + +// ValidateLicense implements license validation for SPDX 2.1 License +// Expressions as described in Appendix IV of +// https://spdx.org/spdx-specification-21-web-version +// +// An error is returned if the license string is not conforming this +// spec. +// +// Note that the "license-ref" part of the spec is not supported +func ValidateLicense(license string) error { + return newParser(bytes.NewBufferString(license)).Validate() +} diff -Nru snapd-2.27.5/spread.yaml snapd-2.28.5/spread.yaml --- snapd-2.27.5/spread.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/spread.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -30,7 +30,7 @@ GADGET_CHANNEL: "$(HOST: echo ${SPREAD_GADGET_CHANNEL:-edge})" REMOTE_STORE: "$(HOST: echo ${SPREAD_REMOTE_STORE:-production})" SNAPPY_USE_STAGING_STORE: "$(HOST: if [ $SPREAD_REMOTE_STORE = staging ]; then echo 1; else echo 0; fi)" - DELTA_REF: 2.17 + DELTA_REF: 2.27 DELTA_PREFIX: snapd-$DELTA_REF/ SNAPD_PUBLISHED_VERSION: "$(HOST: echo $SPREAD_SNAPD_PUBLISHED_VERSION)" HTTP_PROXY: "$(HOST: echo $SPREAD_HTTP_PROXY)" @@ -38,11 +38,21 @@ NO_PROXY: "127.0.0.1" NEW_CORE_CHANNEL: "$(HOST: echo $SPREAD_NEW_CORE_CHANNEL)" SRU_VALIDATION: "$(HOST: echo ${SPREAD_SRU_VALIDATION:-0})" + PRE_CACHE_SNAPS: core ubuntu-core test-snapd-tools + # This skips various sync calls which can greatly speed up test execution. + SNAPD_UNSAFE_IO: 1 backends: linode: key: "$(HOST: echo $SPREAD_LINODE_KEY)" halt-timeout: 2h + environment: + # Using proxy can help to accelerate testing in local conditions + # but it is unlikely anyone has a proxy that is addressable from + # Linode network. As such, don't honor host's SPREAD_HTTP_PROXY + # that was set globally above. + HTTP_PROXY: null + HTTPS_PROXY: null systems: - ubuntu-14.04-64: kernel: GRUB 2 @@ -61,9 +71,10 @@ kernel: GRUB 2 workers: 4 - fedora-25-64: - workers: 1 + workers: 3 - opensuse-42.2-64: - workers: 1 + manual: true + workers: 2 qemu: systems: - ubuntu-14.04-32: @@ -346,10 +357,6 @@ suites: tests/main/: summary: Full-system tests for snapd - # Test cases are not yet ported to Fedora/openSUSE that is why - # we keep them disabled. A later PR will enable most tests and - # drop this blacklist. - systems: [-fedora-*, -opensuse-*] prepare: | . $TESTSLIB/prepare.sh if [[ "$SPREAD_SYSTEM" == ubuntu-core-16-* ]]; then @@ -376,13 +383,8 @@ tests/completion/: summary: completion tests - # ppc64el disabled because of https://bugs.launchpad.net/snappy/+bug/1655594 - # Test cases are not yet ported to Fedora/openSUSE that is why - # we keep them disabled. A later PR will enable most tests and - # drop this blacklist. - systems: [-ubuntu-core-*, -ubuntu-*-ppc64el, -fedora-*, -opensuse-*] - + systems: [-ubuntu-core-*, -ubuntu-*-ppc64el] prepare: | . $TESTSLIB/prepare.sh prepare_classic @@ -392,7 +394,8 @@ prepare_each_classic restore: | $TESTSLIB/reset.sh --store - apt-get purge -y snapd + . $TESTSLIB/pkgdb.sh + distro_purge_package snapd environment: _/plain: _ @@ -411,10 +414,6 @@ tests/regression/: summary: Regression tests for snapd - # Test cases are not yet ported to Fedora/openSUSE that is why - # we keep them disabled. A later PR will enable most tests and - # drop this blacklist. - systems: [-fedora-*, -opensuse-*] prepare: | . $TESTSLIB/prepare.sh if [[ "$SPREAD_SYSTEM" == ubuntu-core-16-* ]]; then @@ -427,15 +426,16 @@ restore: | $TESTSLIB/reset.sh if [[ "$SPREAD_SYSTEM" != ubuntu-core-16-* ]]; then - apt-get purge -y snapd + . $TESTSLIB/pkgdb.sh + distro_purge_package snapd fi tests/upgrade/: summary: Tests for snapd upgrade - # Test cases are not yet ported to Fedora/openSUSE that is why - # we keep them disabled. A later PR will enable most tests and + # Test cases are not yet ported to openSUSE that is why we keep + # it disabled. A later PR will enable most tests and # drop this blacklist. - systems: [-ubuntu-core-16-*, -fedora-*, -opensuse-*] + systems: [-ubuntu-core-16-*, -opensuse-*] restore: | if [ "$REMOTE_STORE" = staging ]; then echo "skip upgrade tests while talking to the staging store" @@ -446,8 +446,9 @@ echo "skip upgrade tests while talking to the staging store" exit 0 fi - $TESTSLIB/reset.sh - apt-get purge -y snapd + . $TESTSLIB/pkgdb.sh + distro_purge_package snapd + distro_purge_package snapd-xdg-open || true tests/unit/: summary: Suite to run unit tests (non-go and different go runtimes) @@ -475,7 +476,8 @@ prepare_each_classic restore: | $TESTSLIB/reset.sh --store - apt-get purge -y snapd snap-confine ubuntu-core-launcher + . $TESTSLIB/pkgdb.sh + distro_purge_package snapd snap-confine ubuntu-core-launcher tests/nightly/: summary: Suite for nightly, expensive, tests @@ -496,7 +498,8 @@ restore: | $TESTSLIB/reset.sh if [[ "$SPREAD_SYSTEM" != ubuntu-core-16-* ]]; then - apt-get purge -y snapd + . $TESTSLIB/pkgdb.sh + distro_purge_package snapd fi tests/nested/: @@ -512,11 +515,13 @@ kill-timeout: 2h manual: true prepare: | - apt update && apt install -y snapd qemu genisoimage sshpass - + . $TESTSLIB/pkgdb.sh + distro_update_package_db + distro_install_package snapd qemu genisoimage sshpass snap install --classic --beta ubuntu-image restore: | - apt remove -y qemu genisoimage sshpass + . $TESTSLIB/pkgdb.sh + distro_purge_package qemu genisoimage sshpass snap remove ubuntu-image # vim:ts=4:sw=4:et diff -Nru snapd-2.27.5/store/auth.go snapd-2.28.5/store/auth.go --- snapd-2.27.5/store/auth.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/store/auth.go 2017-09-13 14:47:18.000000000 +0000 @@ -33,14 +33,15 @@ ) var ( - myappsAPIBase = myappsURL() + baseAPIURL = apiURL() + // DeviceNonceAPI points to endpoint to get a nonce + DeviceNonceAPI = baseAPIURL.String() + "api/v1/snaps/auth/nonces" + // DeviceSessionAPI points to endpoint to get a device session + DeviceSessionAPI = baseAPIURL.String() + "api/v1/snaps/auth/sessions" + myappsAPIBase = myappsURL() // MyAppsMacaroonACLAPI points to MyApps endpoint to get a ACL macaroon MyAppsMacaroonACLAPI = myappsAPIBase + "dev/api/acl/" - // MyAppsDeviceNonceAPI points to MyApps endpoint to get a nonce - MyAppsDeviceNonceAPI = myappsAPIBase + "identity/api/v1/nonces" - // MyAppsDeviceSessionAPI points to MyApps endpoint to get a device session - MyAppsDeviceSessionAPI = myappsAPIBase + "identity/api/v1/sessions" - ubuntuoneAPIBase = authURL() + ubuntuoneAPIBase = authURL() // UbuntuoneLocation is the Ubuntuone location as defined in the store macaroon UbuntuoneLocation = authLocation() // UbuntuoneDischargeAPI points to SSO endpoint to discharge a macaroon @@ -252,7 +253,7 @@ "User-Agent": httputil.UserAgent(), "Accept": "application/json", } - resp, err := retryPostRequestDecodeJSON(MyAppsDeviceNonceAPI, headers, nil, &responseData, nil) + resp, err := retryPostRequestDecodeJSON(DeviceNonceAPI, headers, nil, &responseData, nil) if err != nil { return "", fmt.Errorf(errorPrefix+"%v", err) } @@ -268,13 +269,20 @@ return responseData.Nonce, nil } +type deviceSessionRequestParamsEncoder interface { + EncodedRequest() string + EncodedSerial() string + EncodedModel() string +} + // requestDeviceSession requests a device session macaroon from the store. -func requestDeviceSession(serialAssertion, sessionRequest, previousSession string) (string, error) { +func requestDeviceSession(paramsEncoder deviceSessionRequestParamsEncoder, previousSession string) (string, error) { const errorPrefix = "cannot get device session from store: " data := map[string]string{ - "serial-assertion": serialAssertion, - "device-session-request": sessionRequest, + "device-session-request": paramsEncoder.EncodedRequest(), + "serial-assertion": paramsEncoder.EncodedSerial(), + "model-assertion": paramsEncoder.EncodedModel(), } var err error deviceJSONData, err := json.Marshal(data) @@ -295,7 +303,7 @@ headers["X-Device-Authorization"] = fmt.Sprintf(`Macaroon root="%s"`, previousSession) } - _, err = retryPostRequest(MyAppsDeviceSessionAPI, headers, deviceJSONData, func(resp *http.Response) error { + _, err = retryPostRequest(DeviceSessionAPI, headers, deviceJSONData, func(resp *http.Response) error { if resp.StatusCode == 200 || resp.StatusCode == 202 { return json.NewDecoder(resp.Body).Decode(&responseData) } diff -Nru snapd-2.27.5/store/auth_test.go snapd-2.28.5/store/auth_test.go --- snapd-2.27.5/store/auth_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/store/auth_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2014-2016 Canonical Ltd + * Copyright (C) 2014-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 @@ -283,7 +283,7 @@ io.WriteString(w, mockStoreReturnNonce) })) defer mockServer.Close() - MyAppsDeviceNonceAPI = mockServer.URL + "/identity/api/v1/nonces" + DeviceNonceAPI = mockServer.URL + "/api/v1/snaps/auth/nonces" nonce, err := requestStoreDeviceNonce() c.Assert(err, IsNil) @@ -301,7 +301,7 @@ } })) defer mockServer.Close() - MyAppsDeviceNonceAPI = mockServer.URL + "/identity/api/v1/nonces" + DeviceNonceAPI = mockServer.URL + "/api/v1/snaps/auth/nonces" nonce, err := requestStoreDeviceNonce() c.Assert(err, IsNil) @@ -316,7 +316,7 @@ w.WriteHeader(500) })) defer mockServer.Close() - MyAppsDeviceNonceAPI = mockServer.URL + "/identity/api/v1/nonces" + DeviceNonceAPI = mockServer.URL + "/api/v1/snaps/auth/nonces" _, err := requestStoreDeviceNonce() c.Assert(err, NotNil) @@ -325,7 +325,7 @@ } func (s *authTestSuite) TestRequestStoreDeviceNonceFailureOnDNS(c *C) { - MyAppsDeviceNonceAPI = "http://nonexistingserver121321.com/identity/api/v1/nonces" + DeviceNonceAPI = "http://nonexistingserver121321.com/api/v1/snaps/auth/nonces" _, err := requestStoreDeviceNonce() c.Assert(err, NotNil) c.Assert(err, ErrorMatches, `cannot get nonce from store.*`) @@ -336,7 +336,7 @@ io.WriteString(w, mockStoreReturnNoNonce) })) defer mockServer.Close() - MyAppsDeviceNonceAPI = mockServer.URL + "/identity/api/v1/nonces" + DeviceNonceAPI = mockServer.URL + "/api/v1/snaps/auth/nonces" nonce, err := requestStoreDeviceNonce() c.Assert(err, ErrorMatches, "cannot get nonce from store: empty nonce returned") @@ -350,7 +350,7 @@ n++ })) defer mockServer.Close() - MyAppsDeviceNonceAPI = mockServer.URL + "/identity/api/v1/nonces" + DeviceNonceAPI = mockServer.URL + "/api/v1/snaps/auth/nonces" nonce, err := requestStoreDeviceNonce() c.Assert(err, ErrorMatches, "cannot get nonce from store: store server returned status 500") @@ -358,19 +358,33 @@ c.Assert(nonce, Equals, "") } +type testDeviceSessionRequestParamsEncoder struct{} + +func (pe *testDeviceSessionRequestParamsEncoder) EncodedRequest() string { + return "session-request" +} + +func (pe *testDeviceSessionRequestParamsEncoder) EncodedSerial() string { + return "serial-assertion" +} + +func (pe *testDeviceSessionRequestParamsEncoder) EncodedModel() string { + return "model-assertion" +} + func (s *authTestSuite) TestRequestDeviceSession(c *C) { mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { jsonReq, err := ioutil.ReadAll(r.Body) c.Assert(err, IsNil) - c.Check(string(jsonReq), Equals, `{"device-session-request":"session-request","serial-assertion":"serial-assertion"}`) + c.Check(string(jsonReq), Equals, `{"device-session-request":"session-request","model-assertion":"model-assertion","serial-assertion":"serial-assertion"}`) c.Check(r.Header.Get("X-Device-Authorization"), Equals, "") io.WriteString(w, mockStoreReturnMacaroon) })) defer mockServer.Close() - MyAppsDeviceSessionAPI = mockServer.URL + "/identity/api/v1/sessions" + DeviceSessionAPI = mockServer.URL + "/api/v1/snaps/auth/sessions" - macaroon, err := requestDeviceSession("serial-assertion", "session-request", "") + macaroon, err := requestDeviceSession(&testDeviceSessionRequestParamsEncoder{}, "") c.Assert(err, IsNil) c.Assert(macaroon, Equals, "the-root-macaroon-serialized-data") } @@ -379,15 +393,15 @@ mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { jsonReq, err := ioutil.ReadAll(r.Body) c.Assert(err, IsNil) - c.Check(string(jsonReq), Equals, `{"device-session-request":"session-request","serial-assertion":"serial-assertion"}`) + c.Check(string(jsonReq), Equals, `{"device-session-request":"session-request","model-assertion":"model-assertion","serial-assertion":"serial-assertion"}`) c.Check(r.Header.Get("X-Device-Authorization"), Equals, `Macaroon root="previous-session"`) io.WriteString(w, mockStoreReturnMacaroon) })) defer mockServer.Close() - MyAppsDeviceSessionAPI = mockServer.URL + "/identity/api/v1/sessions" + DeviceSessionAPI = mockServer.URL + "/api/v1/snaps/auth/sessions" - macaroon, err := requestDeviceSession("serial-assertion", "session-request", "previous-session") + macaroon, err := requestDeviceSession(&testDeviceSessionRequestParamsEncoder{}, "previous-session") c.Assert(err, IsNil) c.Assert(macaroon, Equals, "the-root-macaroon-serialized-data") } @@ -397,9 +411,9 @@ io.WriteString(w, mockStoreReturnNoMacaroon) })) defer mockServer.Close() - MyAppsDeviceSessionAPI = mockServer.URL + "/identity/api/v1/sessions" + DeviceSessionAPI = mockServer.URL + "/api/v1/snaps/auth/sessions" - macaroon, err := requestDeviceSession("serial-assertion", "session-request", "") + macaroon, err := requestDeviceSession(&testDeviceSessionRequestParamsEncoder{}, "") c.Assert(err, ErrorMatches, "cannot get device session from store: empty session returned") c.Assert(macaroon, Equals, "") } @@ -412,9 +426,9 @@ n++ })) defer mockServer.Close() - MyAppsDeviceSessionAPI = mockServer.URL + "/identity/api/v1/sessions" + DeviceSessionAPI = mockServer.URL + "/api/v1/snaps/auth/sessions" - macaroon, err := requestDeviceSession("serial-assertion", "session-request", "") + macaroon, err := requestDeviceSession(&testDeviceSessionRequestParamsEncoder{}, "") c.Assert(err, ErrorMatches, `cannot get device session from store: store server returned status 500 and body "error body"`) c.Assert(n, Equals, 5) c.Assert(macaroon, Equals, "") diff -Nru snapd-2.27.5/store/details.go snapd-2.28.5/store/details.go --- snapd-2.27.5/store/details.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/store/details.go 2017-09-13 14:47:18.000000000 +0000 @@ -44,6 +44,7 @@ Revision int `json:"revision"` // store revisions are ints starting at 1 ScreenshotURLs []string `json:"screenshot_urls,omitempty"` SnapID string `json:"snap_id"` + License string `json:"license,omitempty"` // FIXME: the store should send "contact" here, once it does we // can remove support_url diff -Nru snapd-2.27.5/store/export_test.go snapd-2.28.5/store/export_test.go --- snapd-2.27.5/store/export_test.go 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/store/export_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -20,6 +20,9 @@ package store import ( + "net/url" + "reflect" + "github.com/snapcore/snapd/testutil" "gopkg.in/retry.v1" @@ -33,3 +36,19 @@ defaultRetryStrategy = originalDefaultRetryStrategy }) } + +func (cfg *Config) apiURIs() map[string]*url.URL { + urls := map[string]*url.URL{} + + v := reflect.ValueOf(*cfg) + t := reflect.TypeOf(*cfg) + n := v.NumField() + for i := 0; i < n; i++ { + vf := v.Field(i) + if u, ok := vf.Interface().(*url.URL); ok { + urls[t.Field(i).Name] = u + } + } + + return urls +} diff -Nru snapd-2.27.5/store/store.go snapd-2.28.5/store/store.go --- snapd-2.27.5/store/store.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/store/store.go 2017-09-13 14:47:18.000000000 +0000 @@ -102,6 +102,7 @@ info.Private = d.Private info.Confinement = snap.ConfinementType(d.Confinement) info.Contact = d.Contact + info.License = d.License deltas := make([]snap.DeltaInfo, len(d.Deltas)) for i, d := range d.Deltas { @@ -183,6 +184,36 @@ DeltaFormat string } +// SetAPI updates API URLs in the Config. Must not be used to change active config. +func (cfg *Config) SetAPI(api *url.URL) error { + storeBaseURI, err := storeURL(api) + if err != nil { + return err + } + assertsBaseURI, err := assertsURL(storeBaseURI) + if err != nil { + return err + } + + // XXX: Repeating "api/" here is cumbersome, but the next generation + // of store APIs will probably drop that prefix (since it now + // duplicates the hostname), and we may want to switch to v2 APIs + // one at a time; so it's better to consider that as part of + // individual endpoint paths. + cfg.SearchURI = urlJoin(storeBaseURI, "api/v1/snaps/search") + // slash at the end because snap name is appended to this with .Parse(snapName) + cfg.DetailsURI = urlJoin(storeBaseURI, "api/v1/snaps/details/") + cfg.BulkURI = urlJoin(storeBaseURI, "api/v1/snaps/metadata") + cfg.SectionsURI = urlJoin(storeBaseURI, "api/v1/snaps/sections") + cfg.OrdersURI = urlJoin(storeBaseURI, "api/v1/snaps/purchases/orders") + cfg.BuyURI = urlJoin(storeBaseURI, "api/v1/snaps/purchases/buy") + cfg.CustomersMeURI = urlJoin(storeBaseURI, "api/v1/snaps/purchases/customers/me") + + cfg.AssertionsURI = urlJoin(assertsBaseURI, "assertions/") + + return nil +} + // Store represents the ubuntu snap store type Store struct { searchURI *url.URL @@ -270,22 +301,39 @@ return &url } -func apiURL() string { - // FIXME: this will become a store-url assertion +// apiURL returns the system default base API URL. +func apiURL() *url.URL { + s := "https://api.snapcraft.io/" + if useStaging() { + s = "https://api.staging.snapcraft.io/" + } + u, _ := url.Parse(s) + return u +} + +// storeURL returns the base store URL, derived from either the given API URL +// or an env var override. +func storeURL(api *url.URL) (*url.URL, error) { + var override string + var overrideName string // XXX: Deprecated but present for backward-compatibility: this used // to be "Click Package Index". Remove this once people have got // used to SNAPPY_FORCE_API_URL instead. - if u := os.Getenv("SNAPPY_FORCE_CPI_URL"); u != "" && strings.HasSuffix(u, "api/v1/") { - return strings.TrimSuffix(u, "api/v1/") + if s := os.Getenv("SNAPPY_FORCE_CPI_URL"); s != "" && strings.HasSuffix(s, "api/v1/") { + overrideName = "SNAPPY_FORCE_CPI_URL" + override = strings.TrimSuffix(s, "api/v1/") + } else if s := os.Getenv("SNAPPY_FORCE_API_URL"); s != "" { + overrideName = "SNAPPY_FORCE_API_URL" + override = s } - if u := os.Getenv("SNAPPY_FORCE_API_URL"); u != "" { - return u - } - if useStaging() { - return "https://api.staging.snapcraft.io/" + if override != "" { + u, err := url.Parse(override) + if err != nil { + return nil, fmt.Errorf("invalid %s: %s", overrideName, err) + } + return u, nil } - - return "https://api.snapcraft.io/" + return api, nil } func authLocation() string { @@ -302,13 +350,17 @@ return "https://" + authLocation() + "/api/v2" } -func assertsURL(storeBaseURI *url.URL) string { - if u := os.Getenv("SNAPPY_FORCE_SAS_URL"); u != "" { - return u +func assertsURL(storeBaseURI *url.URL) (*url.URL, error) { + if s := os.Getenv("SNAPPY_FORCE_SAS_URL"); s != "" { + u, err := url.Parse(s) + if err != nil { + return nil, fmt.Errorf("invalid SNAPPY_FORCE_SAS_URL: %s", err) + } + return u, nil } // XXX: This will eventually become urlJoin(storeBaseURI, "v2/") // once new bulk-friendly APIs are designed and implemented. - return urlJoin(storeBaseURI, "api/v1/snaps/").String() + return urlJoin(storeBaseURI, "api/v1/snaps/"), nil } func myappsURL() string { @@ -327,34 +379,17 @@ } func init() { - storeBaseURI, err := url.Parse(apiURL()) + storeBaseURI, err := storeURL(apiURL()) if err != nil { panic(err) } if storeBaseURI.RawQuery != "" { panic("store API URL may not contain query string") } - - assertsBaseURI, err := url.Parse(assertsURL(storeBaseURI)) + err = defaultConfig.SetAPI(storeBaseURI) if err != nil { panic(err) } - - // XXX: Repeating "api/" here is cumbersome, but the next generation - // of store APIs will probably drop that prefix (since it now - // duplicates the hostname), and we may want to switch to v2 APIs - // one at a time; so it's better to consider that as part of - // individual endpoint paths. - defaultConfig.SearchURI = urlJoin(storeBaseURI, "api/v1/snaps/search") - // slash at the end because snap name is appended to this with .Parse(snapName) - defaultConfig.DetailsURI = urlJoin(storeBaseURI, "api/v1/snaps/details/") - defaultConfig.BulkURI = urlJoin(storeBaseURI, "api/v1/snaps/metadata") - defaultConfig.OrdersURI = urlJoin(storeBaseURI, "api/v1/snaps/purchases/orders") - defaultConfig.BuyURI = urlJoin(storeBaseURI, "api/v1/snaps/purchases/buy") - defaultConfig.CustomersMeURI = urlJoin(storeBaseURI, "api/v1/snaps/purchases/customers/me") - defaultConfig.SectionsURI = urlJoin(storeBaseURI, "api/v1/snaps/sections") - - defaultConfig.AssertionsURI = urlJoin(assertsBaseURI, "assertions/") } type searchResults struct { @@ -586,12 +621,12 @@ return err } - sessionRequest, serialAssertion, err := s.authContext.DeviceSessionRequest(nonce) + devSessReqParams, err := s.authContext.DeviceSessionRequestParams(nonce) if err != nil { return err } - session, err := requestDeviceSession(string(serialAssertion), string(sessionRequest), device.SessionMacaroon) + session, err := requestDeviceSession(devSessReqParams, device.SessionMacaroon) if err != nil { return err } @@ -1287,14 +1322,15 @@ } if useDeltas() { logger.Debugf("Available deltas returned by store: %v", downloadInfo.Deltas) - } - if useDeltas() && len(downloadInfo.Deltas) == 1 { - err := s.downloadAndApplyDelta(name, targetPath, downloadInfo, pbar, user) - if err == nil { - return nil + + if len(downloadInfo.Deltas) == 1 { + err := s.downloadAndApplyDelta(name, targetPath, downloadInfo, pbar, user) + if err == nil { + return nil + } + // We revert to normal downloads if there is any error. + logger.Noticef("Cannot download or apply deltas for %s: %v", name, err) } - // We revert to normal downloads if there is any error. - logger.Noticef("Cannot download or apply deltas for %s: %v", name, err) } partialPath := targetPath + ".partial" diff -Nru snapd-2.27.5/store/storetest/storetest.go snapd-2.28.5/store/storetest/storetest.go --- snapd-2.27.5/store/storetest/storetest.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/store/storetest/storetest.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,84 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2014-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 storetest + +import ( + "golang.org/x/net/context" + + "github.com/snapcore/snapd/asserts" + "github.com/snapcore/snapd/overlord/auth" + "github.com/snapcore/snapd/overlord/snapstate" + "github.com/snapcore/snapd/progress" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/store" +) + +// Store implements a snapstate.StoreService where every single method panics. +// +// Embed in your own fakeStore to avoid having to keep up with that interface's +// evolution when it's unrelated to your code. +type Store struct{} + +// ensure we conform +var _ snapstate.StoreService = Store{} + +func (Store) SnapInfo(store.SnapSpec, *auth.UserState) (*snap.Info, error) { + panic("Store.SnapInfo not expected") +} + +func (Store) Find(*store.Search, *auth.UserState) ([]*snap.Info, error) { + panic("Store.Find not expected") +} + +func (Store) LookupRefresh(*store.RefreshCandidate, *auth.UserState) (*snap.Info, error) { + panic("Store.LookupRefresh not expected") +} + +func (Store) ListRefresh([]*store.RefreshCandidate, *auth.UserState) ([]*snap.Info, error) { + panic("Store.ListRefresh not expected") +} + +func (Store) Download(context.Context, string, string, *snap.DownloadInfo, progress.Meter, *auth.UserState) error { + panic("Store.Download not expected") +} + +func (Store) SuggestedCurrency() string { + panic("Store.SuggestedCurrency not expected") +} + +func (Store) Buy(*store.BuyOptions, *auth.UserState) (*store.BuyResult, error) { + panic("Store.Buy not expected") +} + +func (Store) ReadyToBuy(*auth.UserState) error { + panic("Store.ReadyToBuy not expected") +} + +func (Store) Sections(*auth.UserState) ([]string, error) { + panic("Store.Sections not expected") +} + +func (Store) Assertion(*asserts.AssertionType, []string, *auth.UserState) (asserts.Assertion, error) { + panic("Store.Assertion not expected") +} + +func (Store) SnapCommands() (map[string][]string, error) { + panic("fakeStore.SnapCommands not expected") +} diff -Nru snapd-2.27.5/store/store_test.go snapd-2.28.5/store/store_test.go --- snapd-2.27.5/store/store_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/store/store_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2014-2016 Canonical Ltd + * Copyright (C) 2014-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 @@ -55,6 +55,74 @@ "github.com/snapcore/snapd/testutil" ) +func TestStore(t *testing.T) { TestingT(t) } + +type configTestSuite struct{} + +var _ = Suite(&configTestSuite{}) + +func (suite *configTestSuite) TestSetAPI(c *C) { + // Sanity check to prove at least one URI changes. + cfg := DefaultConfig() + c.Assert(cfg.SectionsURI.Scheme, Equals, "https") + c.Assert(cfg.SectionsURI.Host, Equals, "api.snapcraft.io") + c.Assert(cfg.SectionsURI.Path, Matches, "/api/v1/snaps/.*") + + api, err := url.Parse("http://example.com/path/prefix/") + c.Assert(err, IsNil) + err = cfg.SetAPI(api) + c.Assert(err, IsNil) + + for _, uri := range cfg.apiURIs() { + c.Assert(uri, NotNil) + c.Check(uri.String(), Matches, "http://example.com/path/prefix/api/v1/snaps/.*") + } +} + +func (suite *configTestSuite) TestSetAPIStoreOverrides(c *C) { + cfg := DefaultConfig() + c.Assert(cfg.SetAPI(apiURL()), IsNil) + c.Check(cfg.SearchURI, Matches, apiURL().String()+".*") + + c.Assert(os.Setenv("SNAPPY_FORCE_API_URL", "https://force-api.local/"), IsNil) + defer os.Setenv("SNAPPY_FORCE_API_URL", "") + cfg = DefaultConfig() + c.Assert(cfg.SetAPI(apiURL()), IsNil) + for _, u := range cfg.apiURIs() { + c.Check(u.String(), Matches, "https://force-api.local/.*") + } +} + +func (suite *configTestSuite) TestSetAPIStoreURLBadEnviron(c *C) { + c.Assert(os.Setenv("SNAPPY_FORCE_API_URL", "://example.com"), IsNil) + defer os.Setenv("SNAPPY_FORCE_API_URL", "") + + cfg := DefaultConfig() + err := cfg.SetAPI(apiURL()) + c.Check(err, ErrorMatches, "invalid SNAPPY_FORCE_API_URL: parse ://example.com: missing protocol scheme") +} + +func (suite *configTestSuite) TestSetAPIAssertsOverrides(c *C) { + cfg := DefaultConfig() + c.Assert(cfg.SetAPI(apiURL()), IsNil) + c.Check(cfg.SearchURI, Matches, apiURL().String()+".*") + + c.Assert(os.Setenv("SNAPPY_FORCE_SAS_URL", "https://force-sas.local/"), IsNil) + defer os.Setenv("SNAPPY_FORCE_SAS_URL", "") + cfg = DefaultConfig() + c.Assert(cfg.SetAPI(apiURL()), IsNil) + c.Check(cfg.AssertionsURI, Matches, "https://force-sas.local/.*") +} + +func (suite *configTestSuite) TestSetAPIAssertsURLBadEnviron(c *C) { + c.Assert(os.Setenv("SNAPPY_FORCE_SAS_URL", "://example.com"), IsNil) + defer os.Setenv("SNAPPY_FORCE_SAS_URL", "") + + cfg := DefaultConfig() + err := cfg.SetAPI(apiURL()) + c.Check(err, ErrorMatches, "invalid SNAPPY_FORCE_SAS_URL: parse ://example.com: missing protocol scheme") +} + type remoteRepoTestSuite struct { testutil.BaseTest store *Store @@ -67,11 +135,23 @@ mockXDelta *testutil.MockCmd } -func TestStore(t *testing.T) { TestingT(t) } - var _ = Suite(&remoteRepoTestSuite{}) const ( + exModel = `type: model +authority-id: my-brand +series: 16 +brand-id: my-brand +model: baz-3000 +architecture: armhf +gadget: gadget +kernel: kernel +store: my-brand-store-id +timestamp: 2016-08-20T13:00:00Z +sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij + +AXNpZw=` + exSerial = `type: serial authority-id: my-brand brand-id: my-brand @@ -143,18 +223,27 @@ return fallback, nil } -func (ac *testAuthContext) DeviceSessionRequest(nonce string) ([]byte, []byte, error) { +func (ac *testAuthContext) DeviceSessionRequestParams(nonce string) (*auth.DeviceSessionRequestParams, error) { + model, err := asserts.Decode([]byte(exModel)) + if err != nil { + return nil, err + } + serial, err := asserts.Decode([]byte(exSerial)) if err != nil { - return nil, nil, err + return nil, err } sessReq, err := asserts.Decode([]byte(strings.Replace(exDeviceSessionRequest, "@NONCE@", nonce, 1))) if err != nil { - return nil, nil, err + return nil, err } - return asserts.Encode(sessReq.(*asserts.DeviceSessionRequest)), asserts.Encode(serial.(*asserts.Serial)), nil + return &auth.DeviceSessionRequestParams{ + Request: sessReq.(*asserts.DeviceSessionRequest), + Serial: serial.(*asserts.Serial), + Model: model.(*asserts.Model), + }, nil } func makeTestMacaroon() (*macaroon.Macaroon, error) { @@ -1447,9 +1536,19 @@ c.Check(authorization, Equals, `Macaroon root="refreshed-session-macaroon"`) io.WriteString(w, "response-data") } - case "/identity/api/v1/nonces": + case "/api/v1/auth/nonces": io.WriteString(w, `{"nonce": "1234567890:9876543210"}`) - case "/identity/api/v1/sessions": + case "/api/v1/auth/sessions": + // sanity of request + jsonReq, err := ioutil.ReadAll(r.Body) + c.Assert(err, IsNil) + var req map[string]string + err = json.Unmarshal(jsonReq, &req) + c.Assert(err, IsNil) + c.Check(strings.HasPrefix(req["device-session-request"], "type: device-session-request\n"), Equals, true) + c.Check(strings.HasPrefix(req["serial-assertion"], "type: serial\n"), Equals, true) + c.Check(strings.HasPrefix(req["model-assertion"], "type: model\n"), Equals, true) + authorization := r.Header.Get("X-Device-Authorization") if authorization == "" { io.WriteString(w, `{"macaroon": "expired-session-macaroon"}`) @@ -1466,8 +1565,8 @@ c.Assert(mockServer, NotNil) defer mockServer.Close() - MyAppsDeviceNonceAPI = mockServer.URL + "/identity/api/v1/nonces" - MyAppsDeviceSessionAPI = mockServer.URL + "/identity/api/v1/sessions" + DeviceNonceAPI = mockServer.URL + "/api/v1/auth/nonces" + DeviceSessionAPI = mockServer.URL + "/api/v1/auth/sessions" // make sure device session is not set t.device.SessionMacaroon = "" @@ -1612,7 +1711,7 @@ /* acquired via -http --pretty=format --print b https://api.snapcraft.io/api/v1/snaps/details/hello-world X-Ubuntu-Series:16 fields==anon_download_url,architecture,channel,download_sha3_384,summary,description,binary_filesize,download_url,icon_url,last_updated,package_name,prices,publisher,ratings_average,revision,screenshot_urls,snap_id,support_url,title,content,version,origin,developer_id,private,confinement channel==edge | xsel -b +http --pretty=format --print b https://api.snapcraft.io/api/v1/snaps/details/hello-world X-Ubuntu-Series:16 fields==anon_download_url,architecture,channel,download_sha3_384,summary,description,binary_filesize,download_url,icon_url,last_updated,license,package_name,prices,publisher,ratings_average,revision,screenshot_urls,snap_id,support_url,title,content,version,origin,developer_id,private,confinement channel==edge | xsel -b on 2016-07-03. Then, by hand: * set prices to {"EUR": 0.99, "USD": 1.23}. @@ -1625,7 +1724,7 @@ const MockDetailsJSON = `{ "_links": { "self": { - "href": "https://api.snapcraft.io/api/v1/snaps/details/hello-world?fields=anon_download_url%2Carchitecture%2Cchannel%2Cdownload_sha3_384%2Csummary%2Cdescription%2Cbinary_filesize%2Cdownload_url%2Cicon_url%2Clast_updated%2Cpackage_name%2Cprices%2Cpublisher%2Cratings_average%2Crevision%2Cscreenshot_urls%2Csnap_id%2Csupport_url%2Ctitle%2Ccontent%2Cversion%2Corigin%2Cdeveloper_id%2Cprivate%2Cconfinement&channel=edge" + "href": "https://api.snapcraft.io/api/v1/snaps/details/hello-world?fields=anon_download_url%2Carchitecture%2Cchannel%2Cdownload_sha3_384%2Csummary%2Cdescription%2Cbinary_filesize%2Cdownload_url%2Cicon_url%2Clast_updated%2Clicense%2Cpackage_name%2Cprices%2Cpublisher%2Cratings_average%2Crevision%2Cscreenshot_urls%2Csnap_id%2Csupport_url%2Ctitle%2Ccontent%2Cversion%2Corigin%2Cdeveloper_id%2Cprivate%2Cconfinement&channel=edge" } }, "anon_download_url": "https://public.apps.ubuntu.com/anon/download-snap/buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ_27.snap", @@ -1642,6 +1741,7 @@ "download_url": "https://public.apps.ubuntu.com/download-snap/buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ_27.snap", "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2015/03/hello.svg_NZLfWbh.png", "last_updated": "2016-07-12T16:37:23.960632Z", + "license": "GPL-3.0", "origin": "canonical", "package_name": "hello-world", "prices": {"EUR": 0.99, "USD": 1.23}, @@ -1704,7 +1804,7 @@ const MockDetailsJSONnoChannelMapList = `{ "_links": { "self": { - "href": "https://api.snapcraft.io/api/v1/snaps/details/hello-world?fields=anon_download_url%2Carchitecture%2Cchannel%2Cdownload_sha3_384%2Csummary%2Cdescription%2Cbinary_filesize%2Cdownload_url%2Cicon_url%2Clast_updated%2Cpackage_name%2Cprices%2Cpublisher%2Cratings_average%2Crevision%2Cscreenshot_urls%2Csnap_id%2Csupport_url%2Ctitle%2Ccontent%2Cversion%2Corigin%2Cdeveloper_id%2Cprivate%2Cconfinement&channel=edge" + "href": "https://api.snapcraft.io/api/v1/snaps/details/hello-world?fields=anon_download_url%2Carchitecture%2Cchannel%2Cdownload_sha3_384%2Csummary%2Cdescription%2Cbinary_filesize%2Cdownload_url%2Cicon_url%2Clast_updated%2Clicense%2Cpackage_name%2Cprices%2Cpublisher%2Cratings_average%2Crevision%2Cscreenshot_urls%2Csnap_id%2Csupport_url%2Ctitle%2Ccontent%2Cversion%2Corigin%2Cdeveloper_id%2Cprivate%2Cconfinement&channel=edge" } }, "anon_download_url": "https://public.apps.ubuntu.com/anon/download-snap/buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ_27.snap", @@ -1721,6 +1821,7 @@ "download_url": "https://public.apps.ubuntu.com/download-snap/buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ_27.snap", "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2015/03/hello.svg_NZLfWbh.png", "last_updated": "2016-07-12T16:37:23.960632Z", + "license": "GPL-3.0", "origin": "canonical", "package_name": "hello-world", "prices": {"EUR": 0.99, "USD": 1.23}, @@ -1842,6 +1943,7 @@ c.Check(result.Description(), Equals, "This is a simple hello world example.") c.Check(result.Summary(), Equals, "The 'hello-world' of snaps") c.Check(result.Title(), Equals, "Hello World") + c.Check(result.License, Equals, "GPL-3.0") c.Assert(result.Prices, DeepEquals, map[string]float64{"EUR": 0.99, "USD": 1.23}) c.Assert(result.Screenshots, DeepEquals, []snap.ScreenshotInfo{ { @@ -2211,7 +2313,7 @@ /* acquired via -http --pretty=format --print b https://api.snapcraft.io/api/v1/snaps/details/no:such:package X-Ubuntu-Series:16 fields==anon_download_url,architecture,channel,download_sha512,summary,description,binary_filesize,download_url,icon_url,last_updated,package_name,prices,publisher,ratings_average,revision,snap_id,support_url,title,content,version,origin,developer_id,private,confinement channel==edge | xsel -b +http --pretty=format --print b https://api.snapcraft.io/api/v1/snaps/details/no:such:package X-Ubuntu-Series:16 fields==anon_download_url,architecture,channel,download_sha512,summary,description,binary_filesize,download_url,icon_url,last_updated,license,package_name,prices,publisher,ratings_average,revision,snap_id,support_url,title,content,version,origin,developer_id,private,confinement channel==edge | xsel -b on 2016-07-03 @@ -2267,7 +2369,7 @@ } /* acquired via: -curl -s -H "accept: application/hal+json" -H "X-Ubuntu-Release: 16" -H "X-Ubuntu-Device-Channel: edge" -H "X-Ubuntu-Wire-Protocol: 1" -H "X-Ubuntu-Architecture: amd64" 'https://api.snapcraft.io/api/v1/snaps/search?fields=anon_download_url%2Carchitecture%2Cchannel%2Cdownload_sha512%2Csummary%2Cdescription%2Cbinary_filesize%2Cdownload_url%2Cicon_url%2Clast_updated%2Cpackage_name%2Cprices%2Cpublisher%2Cratings_average%2Crevision%2Cscreenshot_urls%2Csnap_id%2Csupport_url%2Ctitle%2Ccontent%2Cversion%2Corigin&q=hello' | python -m json.tool | xsel -b +curl -s -H "accept: application/hal+json" -H "X-Ubuntu-Release: 16" -H "X-Ubuntu-Device-Channel: edge" -H "X-Ubuntu-Wire-Protocol: 1" -H "X-Ubuntu-Architecture: amd64" 'https://api.snapcraft.io/api/v1/snaps/search?fields=anon_download_url%2Carchitecture%2Cchannel%2Cdownload_sha512%2Csummary%2Cdescription%2Cbinary_filesize%2Cdownload_url%2Cicon_url%2Clast_updated%2Clicense%2Cpackage_name%2Cprices%2Cpublisher%2Cratings_average%2Crevision%2Cscreenshot_urls%2Csnap_id%2Csupport_url%2Ctitle%2Ccontent%2Cversion%2Corigin&q=hello' | python -m json.tool | xsel -b Screenshot URLS set manually. */ const MockSearchJSON = `{ @@ -2286,6 +2388,7 @@ "download_url": "https://public.apps.ubuntu.com/download-snap/buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ_25.snap", "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2015/03/hello.svg_NZLfWbh.png", "last_updated": "2016-04-19T19:50:50.435291Z", + "license": "GPL-3.0", "origin": "canonical", "package_name": "hello-world", "prices": {"EUR": 2.99, "USD": 3.49}, @@ -2303,13 +2406,13 @@ }, "_links": { "first": { - "href": "https://api.snapcraft.io/api/v1/snaps/search?q=hello&fields=anon_download_url%2Carchitecture%2Cchannel%2Cdownload_sha512%2Csummary%2Cdescription%2Cbinary_filesize%2Cdownload_url%2Cicon_url%2Clast_updated%2Cpackage_name%2Cprices%2Cpublisher%2Cratings_average%2Crevision%2Cscreenshot_urls%2Csnap_id%2Csupport_url%2Ctitle%2Ccontent%2Cversion%2Corigin&page=1" + "href": "https://api.snapcraft.io/api/v1/snaps/search?q=hello&fields=anon_download_url%2Carchitecture%2Cchannel%2Cdownload_sha512%2Csummary%2Cdescription%2Cbinary_filesize%2Cdownload_url%2Cicon_url%2Clast_updated%2Clicense%2Cpackage_name%2Cprices%2Cpublisher%2Cratings_average%2Crevision%2Cscreenshot_urls%2Csnap_id%2Csupport_url%2Ctitle%2Ccontent%2Cversion%2Corigin&page=1" }, "last": { - "href": "https://api.snapcraft.io/api/v1/snaps/search?q=hello&fields=anon_download_url%2Carchitecture%2Cchannel%2Cdownload_sha512%2Csummary%2Cdescription%2Cbinary_filesize%2Cdownload_url%2Cicon_url%2Clast_updated%2Cpackage_name%2Cprices%2Cpublisher%2Cratings_average%2Crevision%2Cscreenshot_urls%2Csnap_id%2Csupport_url%2Ctitle%2Ccontent%2Cversion%2Corigin&page=1" + "href": "https://api.snapcraft.io/api/v1/snaps/search?q=hello&fields=anon_download_url%2Carchitecture%2Cchannel%2Cdownload_sha512%2Csummary%2Cdescription%2Cbinary_filesize%2Cdownload_url%2Cicon_url%2Clast_updated%2Clicense%2Cpackage_name%2Cprices%2Cpublisher%2Cratings_average%2Crevision%2Cscreenshot_urls%2Csnap_id%2Csupport_url%2Ctitle%2Ccontent%2Cversion%2Corigin&page=1" }, "self": { - "href": "https://api.snapcraft.io/api/v1/snaps/search?q=hello&fields=anon_download_url%2Carchitecture%2Cchannel%2Cdownload_sha512%2Csummary%2Cdescription%2Cbinary_filesize%2Cdownload_url%2Cicon_url%2Clast_updated%2Cpackage_name%2Cprices%2Cpublisher%2Cratings_average%2Crevision%2Cscreenshot_urls%2Csnap_id%2Csupport_url%2Ctitle%2Ccontent%2Cversion%2Corigin&page=1" + "href": "https://api.snapcraft.io/api/v1/snaps/search?q=hello&fields=anon_download_url%2Carchitecture%2Cchannel%2Cdownload_sha512%2Csummary%2Cdescription%2Cbinary_filesize%2Cdownload_url%2Cicon_url%2Clast_updated%2Clicense%2Cpackage_name%2Cprices%2Cpublisher%2Cratings_average%2Crevision%2Cscreenshot_urls%2Csnap_id%2Csupport_url%2Ctitle%2Ccontent%2Cversion%2Corigin&page=1" } } } @@ -2725,7 +2828,7 @@ /* acquired via: (against production "hello-world") -$ curl -s --data-binary '{"snaps":[{"snap_id":"buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ","channel":"stable","revision":25,"epoch":"0","confinement":"strict"}],"fields":["anon_download_url","architecture","channel","download_sha512","summary","description","binary_filesize","download_url","icon_url","last_updated","package_name","prices","publisher","ratings_average","revision","snap_id","support_url","title","content","version","origin","developer_id","private","confinement"]}' -H 'content-type: application/json' -H 'X-Ubuntu-Release: 16' -H 'X-Ubuntu-Wire-Protocol: 1' -H "accept: application/hal+json" https://api.snapcraft.io/api/v1/snaps/metadata | python3 -m json.tool --sort-keys | xsel -b +$ curl -s --data-binary '{"snaps":[{"snap_id":"buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ","channel":"stable","revision":25,"epoch":"0","confinement":"strict"}],"fields":["anon_download_url","architecture","channel","download_sha512","summary","description","binary_filesize","download_url","icon_url","last_updated","license","package_name","prices","publisher","ratings_average","revision","snap_id","support_url","title","content","version","origin","developer_id","private","confinement"]}' -H 'content-type: application/json' -H 'X-Ubuntu-Release: 16' -H 'X-Ubuntu-Wire-Protocol: 1' -H "accept: application/hal+json" https://api.snapcraft.io/api/v1/snaps/metadata | python3 -m json.tool --sort-keys | xsel -b */ var MockUpdatesJSON = ` { @@ -2746,6 +2849,7 @@ "download_url": "https://public.apps.ubuntu.com/download-snap/buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ_26.snap", "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2015/03/hello.svg_NZLfWbh.png", "last_updated": "2016-05-31T07:02:32.586839Z", + "license": "GPL-3.0", "origin": "canonical", "package_name": "hello-world", "prices": {}, @@ -3457,6 +3561,7 @@ "download_url": "https://public.apps.ubuntu.com/download-snap/buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ_26.snap", "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2015/03/hello.svg_NZLfWbh.png", "last_updated": "2016-05-31T07:02:32.586839Z", + "license": "GPL-3.0", "origin": "canonical", "package_name": "hello-world", "prices": {}, @@ -3680,53 +3785,104 @@ c.Assert(getStructFields(s{}), DeepEquals, []string{"hello"}) } -func (t *remoteRepoTestSuite) TestApiURLDependsOnEnviron(c *C) { +func (t *remoteRepoTestSuite) TestAuthLocationDependsOnEnviron(c *C) { c.Assert(os.Setenv("SNAPPY_USE_STAGING_STORE", ""), IsNil) - before := apiURL() + before := authLocation() c.Assert(os.Setenv("SNAPPY_USE_STAGING_STORE", "1"), IsNil) defer os.Setenv("SNAPPY_USE_STAGING_STORE", "") - after := apiURL() + after := authLocation() c.Check(before, Not(Equals), after) } -func (t *remoteRepoTestSuite) TestAuthLocationDependsOnEnviron(c *C) { +func (t *remoteRepoTestSuite) TestAuthURLDependsOnEnviron(c *C) { c.Assert(os.Setenv("SNAPPY_USE_STAGING_STORE", ""), IsNil) - before := authLocation() + before := authURL() c.Assert(os.Setenv("SNAPPY_USE_STAGING_STORE", "1"), IsNil) defer os.Setenv("SNAPPY_USE_STAGING_STORE", "") - after := authLocation() + after := authURL() c.Check(before, Not(Equals), after) } -func (t *remoteRepoTestSuite) TestAuthURLDependsOnEnviron(c *C) { +func (t *remoteRepoTestSuite) TestApiURLDependsOnEnviron(c *C) { c.Assert(os.Setenv("SNAPPY_USE_STAGING_STORE", ""), IsNil) - before := authURL() + before := apiURL() c.Assert(os.Setenv("SNAPPY_USE_STAGING_STORE", "1"), IsNil) defer os.Setenv("SNAPPY_USE_STAGING_STORE", "") - after := authURL() + after := apiURL() c.Check(before, Not(Equals), after) } +func (t *remoteRepoTestSuite) TestStoreURLDependsOnEnviron(c *C) { + // This also depends on the API URL, but that's tested separately (see + // TestApiURLDependsOnEnviron). + api := apiURL() + + c.Assert(os.Setenv("SNAPPY_FORCE_CPI_URL", ""), IsNil) + c.Assert(os.Setenv("SNAPPY_FORCE_API_URL", ""), IsNil) + + // Test in order of precedence (low first) leaving env vars set as we go ... + + u, err := storeURL(api) + c.Assert(err, IsNil) + c.Check(u.String(), Matches, api.String()+".*") + + c.Assert(os.Setenv("SNAPPY_FORCE_API_URL", "https://force-api.local/"), IsNil) + defer os.Setenv("SNAPPY_FORCE_API_URL", "") + u, err = storeURL(api) + c.Assert(err, IsNil) + c.Check(u.String(), Matches, "https://force-api.local/.*") + + c.Assert(os.Setenv("SNAPPY_FORCE_CPI_URL", "https://force-cpi.local/api/v1/"), IsNil) + defer os.Setenv("SNAPPY_FORCE_CPI_URL", "") + u, err = storeURL(api) + c.Assert(err, IsNil) + c.Check(u.String(), Matches, "https://force-cpi.local/.*") +} + +func (t *remoteRepoTestSuite) TestStoreURLBadEnvironAPI(c *C) { + c.Assert(os.Setenv("SNAPPY_FORCE_API_URL", "://force-api.local/"), IsNil) + defer os.Setenv("SNAPPY_FORCE_API_URL", "") + _, err := storeURL(apiURL()) + c.Check(err, ErrorMatches, "invalid SNAPPY_FORCE_API_URL: parse ://force-api.local/: missing protocol scheme") +} + +func (t *remoteRepoTestSuite) TestStoreURLBadEnvironCPI(c *C) { + c.Assert(os.Setenv("SNAPPY_FORCE_CPI_URL", "://force-cpi.local/api/v1/"), IsNil) + defer os.Setenv("SNAPPY_FORCE_CPI_URL", "") + _, err := storeURL(apiURL()) + c.Check(err, ErrorMatches, "invalid SNAPPY_FORCE_CPI_URL: parse ://force-cpi.local/: missing protocol scheme") +} + func (t *remoteRepoTestSuite) TestAssertsURLDependsOnEnviron(c *C) { - // This also depends on SNAPPY_USE_STAGING_STORE, but that's tested - // separately (see TestApiURLDependsOnEnviron). - storeBaseURI, _ := url.Parse(apiURL()) + // This also depends on the store API, but that's tested separately (see + // TestStoreURLDependsOnEnviron). + apiBaseURI := apiURL() c.Assert(os.Setenv("SNAPPY_FORCE_SAS_URL", ""), IsNil) - before := assertsURL(storeBaseURI) + before, err := assertsURL(apiBaseURI) + c.Assert(err, IsNil) c.Assert(os.Setenv("SNAPPY_FORCE_SAS_URL", "https://assertions.example.org/v1/"), IsNil) defer os.Setenv("SNAPPY_FORCE_SAS_URL", "") - after := assertsURL(storeBaseURI) + after, err := assertsURL(apiBaseURI) + c.Assert(err, IsNil) - c.Check(before, Not(Equals), after) - c.Check(after, Equals, "https://assertions.example.org/v1/") + c.Check(before.String(), Not(Equals), after.String()) + c.Check(after.String(), Equals, "https://assertions.example.org/v1/") +} + +func (t *remoteRepoTestSuite) TestAssertsURLBadEnviron(c *C) { + c.Assert(os.Setenv("SNAPPY_FORCE_SAS_URL", "://example.com"), IsNil) + defer os.Setenv("SNAPPY_FORCE_SAS_URL", "") + + _, err := assertsURL(apiURL()) + c.Check(err, ErrorMatches, "invalid SNAPPY_FORCE_SAS_URL: parse ://example.com: missing protocol scheme") } func (t *remoteRepoTestSuite) TestMyAppsURLDependsOnEnviron(c *C) { diff -Nru snapd-2.27.5/systemd/systemd.go snapd-2.28.5/systemd/systemd.go --- snapd-2.27.5/systemd/systemd.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/systemd/systemd.go 2017-09-13 14:47:18.000000000 +0000 @@ -91,8 +91,7 @@ Stop(service string, timeout time.Duration) error Kill(service, signal string) error Restart(service string, timeout time.Duration) error - Status(service string) (string, error) - ServiceStatus(service string) (*ServiceStatus, error) + Status(services ...string) ([]*ServiceStatus, error) LogReader(services []string, n string, follow bool) (io.ReadCloser, error) WriteMountUnitFile(name, what, where, fstype string) (string, error) } @@ -100,53 +99,6 @@ // A Log is a single entry in the systemd journal type Log map[string]string -// RestartCondition encapsulates the different systemd 'restart' options -type RestartCondition string - -// These are the supported restart conditions -const ( - RestartNever RestartCondition = "never" - RestartOnSuccess RestartCondition = "on-success" - RestartOnFailure RestartCondition = "on-failure" - RestartOnAbnormal RestartCondition = "on-abnormal" - RestartOnAbort RestartCondition = "on-abort" - RestartAlways RestartCondition = "always" -) - -var RestartMap = map[string]RestartCondition{ - "never": RestartNever, - "on-success": RestartOnSuccess, - "on-failure": RestartOnFailure, - "on-abnormal": RestartOnAbnormal, - "on-abort": RestartOnAbort, - "always": RestartAlways, -} - -// ErrUnknownRestartCondition is returned when trying to unmarshal an unknown restart condition -var ErrUnknownRestartCondition = errors.New("invalid restart condition") - -func (rc RestartCondition) String() string { - return string(rc) -} - -// UnmarshalYAML so RestartCondition implements yaml's Unmarshaler interface -func (rc *RestartCondition) UnmarshalYAML(unmarshal func(interface{}) error) error { - var v string - - if err := unmarshal(&v); err != nil { - return err - } - - nrc, ok := RestartMap[v] - if !ok { - return ErrUnknownRestartCondition - } - - *rc = nrc - - return nil -} - const ( // the default target for systemd units that we generate ServicesTarget = "multi-user.target" @@ -201,56 +153,91 @@ return JournalctlCmd(serviceNames, n, follow) } -var statusregex = regexp.MustCompile(`(?m)^(?:(.*?)=(.*))?$`) - -func (s *systemd) Status(serviceName string) (string, error) { - status, err := s.ServiceStatus(serviceName) - if err != nil { - return "", err - } - - return status.StatusString(), nil -} +var statusregex = regexp.MustCompile(`(?m)^(?:(.+?)=(.*)|(.*))?$`) -// A ServiceStatus holds structured service status information. type ServiceStatus struct { - ServiceFileName string `json:"service-file-name"` - LoadState string `json:"load-state"` - ActiveState string `json:"active-state"` - SubState string `json:"sub-state"` - UnitFileState string `json:"unit-file-state"` + Daemon string + ServiceFileName string + Enabled bool + Active bool } -func (status *ServiceStatus) StatusString() string { - return fmt.Sprintf("%s; %s; %s (%s)", status.UnitFileState, status.LoadState, status.ActiveState, status.SubState) -} - -func (s *systemd) ServiceStatus(serviceName string) (*ServiceStatus, error) { - bs, err := SystemctlCmd("show", "--property=Id,LoadState,ActiveState,SubState,UnitFileState", serviceName) +func (s *systemd) Status(serviceNames ...string) ([]*ServiceStatus, error) { + expected := []string{"Id", "Type", "ActiveState", "UnitFileState"} + cmd := make([]string, len(serviceNames)+2) + cmd[0] = "show" + cmd[1] = "--property=" + strings.Join(expected, ",") + copy(cmd[2:], serviceNames) + bs, err := SystemctlCmd(cmd...) if err != nil { return nil, err } - status := &ServiceStatus{ServiceFileName: serviceName} + sts := make([]*ServiceStatus, 0, len(serviceNames)) + cur := &ServiceStatus{} + seen := map[string]bool{} for _, bs := range statusregex.FindAllSubmatch(bs, -1) { - if len(bs[0]) > 0 { - k := string(bs[1]) - v := string(bs[2]) - switch k { - case "LoadState": - status.LoadState = v - case "ActiveState": - status.ActiveState = v - case "SubState": - status.SubState = v - case "UnitFileState": - status.UnitFileState = v + if len(bs[0]) == 0 { + // systemctl separates data pertaining to particular services by an empty line + missing := make([]string, 0, len(expected)) + for _, k := range expected { + if !seen[k] { + missing = append(missing, k) + } + } + if len(missing) > 0 { + return nil, fmt.Errorf("cannot get service status: missing %s in ‘systemctl show’ output", strings.Join(missing, ", ")) + + } + sts = append(sts, cur) + if len(sts) > len(serviceNames) { + break // wut + } + if cur.ServiceFileName != serviceNames[len(sts)-1] { + return nil, fmt.Errorf("cannot get service status: queried status of %q but got status of %q", serviceNames[len(sts)-1], cur.ServiceFileName) } + + cur = &ServiceStatus{} + seen = map[string]bool{} + continue + } + if len(bs[3]) > 0 { + return nil, fmt.Errorf("cannot get service status: bad line %q in ‘systemctl show’ output", bs[3]) + } + k := string(bs[1]) + v := string(bs[2]) + + if v == "" { + return nil, fmt.Errorf("cannot get service status: empty field %q in ‘systemctl show’ output", k) + } + + switch k { + case "Id": + cur.ServiceFileName = v + case "Type": + cur.Daemon = v + case "ActiveState": + // made to match “systemctl is-active” behaviour, at least at systemd 229 + cur.Active = v == "active" || v == "reloading" + case "UnitFileState": + // "static" means it can't be disabled + cur.Enabled = v == "enabled" || v == "static" + default: + return nil, fmt.Errorf("cannot get service status: unexpected field %q in ‘systemctl show’ output", k) } + + if seen[k] { + return nil, fmt.Errorf("cannot get service status: duplicate field %q in ‘systemctl show’ output", k) + } + seen[k] = true + } + + if len(sts) != len(serviceNames) { + return nil, fmt.Errorf("cannot get service status: expected %d results, got %d", len(serviceNames), len(sts)) } - return status, nil + return sts, nil } // Stop the given service, and wait until it has stopped. @@ -335,25 +322,19 @@ return isTimeout } -const myFmt = "2006-01-02T15:04:05.000000Z07:00" - -// Timestamp of the Log, formatted like RFC3339 to µs precision. -// -// If no timestamp, the string "-(no timestamp!)-" -- and something is -// wrong with your system. Some other "impossible" error conditions -// also result in "-(errror message)-" timestamps. -func (l Log) Timestamp() string { - t := "-(no timestamp!)-" - if sus, ok := l["__REALTIME_TIMESTAMP"]; ok { - // according to systemd.journal-fields(7) it's microseconds as a decimal string - if us, err := strconv.ParseInt(sus, 10, 64); err == nil { - t = time.Unix(us/1000000, 1000*(us%1000000)).UTC().Format(myFmt) - } else { - t = fmt.Sprintf("-(timestamp not a decimal number: %#v)-", sus) - } +// Time returns the time the Log was received by the journal. +func (l Log) Time() (time.Time, error) { + sus, ok := l["__REALTIME_TIMESTAMP"] + if !ok { + return time.Time{}, errors.New("no timestamp") + } + // according to systemd.journal-fields(7) it's microseconds as a decimal string + us, err := strconv.ParseInt(sus, 10, 64) + if err != nil { + return time.Time{}, fmt.Errorf("timestamp not a decimal number: %#v", sus) } - return t + return time.Unix(us/1000000, 1000*(us%1000000)).UTC(), nil } // Message of the Log, if any; otherwise, "-". @@ -386,10 +367,6 @@ return "-" } -func (l Log) String() string { - return fmt.Sprintf("%s %s[%s]: %s", l.Timestamp(), l.SID(), l.PID(), l.Message()) -} - // useFuse detects if we should be using squashfuse instead func useFuse() bool { if !osutil.FileExists("/dev/fuse") { diff -Nru snapd-2.27.5/systemd/systemd_test.go snapd-2.28.5/systemd/systemd_test.go --- snapd-2.27.5/systemd/systemd_test.go 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/systemd/systemd_test.go 2017-09-13 14:47:18.000000000 +0000 @@ -30,7 +30,6 @@ "time" . "gopkg.in/check.v1" - "gopkg.in/yaml.v2" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/osutil" @@ -167,28 +166,159 @@ func (s *SystemdTestSuite) TestStatus(c *C) { s.outs = [][]byte{ - []byte("Id=Thing\nLoadState=LoadState\nActiveState=ActiveState\nSubState=SubState\nUnitFileState=UnitFileState\n"), + []byte(` +Type=simple +Id=foo.service +ActiveState=active +UnitFileState=enabled + +Type=simple +Id=bar.service +ActiveState=reloading +UnitFileState=static + +Type=potato +Id=baz.service +ActiveState=inactive +UnitFileState=disabled +`[1:]), } s.errors = []error{nil} - out, err := New("", s.rep).Status("foo") + out, err := New("", s.rep).Status("foo.service", "bar.service", "baz.service") c.Assert(err, IsNil) - c.Check(out, Equals, "UnitFileState; LoadState; ActiveState (SubState)") + c.Check(out, DeepEquals, []*ServiceStatus{ + { + Daemon: "simple", + ServiceFileName: "foo.service", + Active: true, + Enabled: true, + }, { + Daemon: "simple", + ServiceFileName: "bar.service", + Active: true, + Enabled: true, + }, { + Daemon: "potato", + ServiceFileName: "baz.service", + Active: false, + Enabled: false, + }, + }) + c.Check(s.rep.msgs, IsNil) + c.Assert(s.argses, DeepEquals, [][]string{{"show", "--property=Id,Type,ActiveState,UnitFileState", "foo.service", "bar.service", "baz.service"}}) } -func (s *SystemdTestSuite) TestStatusObj(c *C) { +func (s *SystemdTestSuite) TestStatusBadNumberOfValues(c *C) { s.outs = [][]byte{ - []byte("Id=Thing\nLoadState=LoadState\nActiveState=ActiveState\nSubState=SubState\nUnitFileState=UnitFileState\n"), + []byte(` +Type=simple +Id=foo.service +ActiveState=active +UnitFileState=enabled + +Type=simple +Id=foo.service +ActiveState=active +UnitFileState=enabled +`[1:]), } s.errors = []error{nil} - out, err := New("", s.rep).ServiceStatus("foo") - c.Assert(err, IsNil) - c.Check(out, DeepEquals, &ServiceStatus{ - ServiceFileName: "foo", - LoadState: "LoadState", - ActiveState: "ActiveState", - SubState: "SubState", - UnitFileState: "UnitFileState", - }) + out, err := New("", s.rep).Status("foo.service") + c.Check(err, ErrorMatches, "cannot get service status: expected 1 results, got 2") + c.Check(out, IsNil) + c.Check(s.rep.msgs, IsNil) +} + +func (s *SystemdTestSuite) TestStatusBadLine(c *C) { + s.outs = [][]byte{ + []byte(` +Type=simple +Id=foo.service +ActiveState=active +UnitFileState=enabled +Potatoes +`[1:]), + } + s.errors = []error{nil} + out, err := New("", s.rep).Status("foo.service") + c.Assert(err, ErrorMatches, `.* bad line "Potatoes" .*`) + c.Check(out, IsNil) +} + +func (s *SystemdTestSuite) TestStatusBadId(c *C) { + s.outs = [][]byte{ + []byte(` +Type=simple +Id=bar.service +ActiveState=active +UnitFileState=enabled +`[1:]), + } + s.errors = []error{nil} + out, err := New("", s.rep).Status("foo.service") + c.Assert(err, ErrorMatches, `.* queried status of "foo.service" but got status of "bar.service"`) + c.Check(out, IsNil) +} + +func (s *SystemdTestSuite) TestStatusBadField(c *C) { + s.outs = [][]byte{ + []byte(` +Type=simple +Id=foo.service +ActiveState=active +UnitFileState=enabled +Potatoes=false +`[1:]), + } + s.errors = []error{nil} + out, err := New("", s.rep).Status("foo.service") + c.Assert(err, ErrorMatches, `.* unexpected field "Potatoes" .*`) + c.Check(out, IsNil) +} + +func (s *SystemdTestSuite) TestStatusMissingField(c *C) { + s.outs = [][]byte{ + []byte(` +Type=simple +Id=foo.service +ActiveState=active +`[1:]), + } + s.errors = []error{nil} + out, err := New("", s.rep).Status("foo.service") + c.Assert(err, ErrorMatches, `.* missing UnitFileState .*`) + c.Check(out, IsNil) +} + +func (s *SystemdTestSuite) TestStatusDupeField(c *C) { + s.outs = [][]byte{ + []byte(` +Type=simple +Id=foo.service +ActiveState=active +ActiveState=active +UnitFileState=enabled +`[1:]), + } + s.errors = []error{nil} + out, err := New("", s.rep).Status("foo.service") + c.Assert(err, ErrorMatches, `.* duplicate field "ActiveState" .*`) + c.Check(out, IsNil) +} + +func (s *SystemdTestSuite) TestStatusEmptyField(c *C) { + s.outs = [][]byte{ + []byte(` +Type=simple +Id= +ActiveState=active +UnitFileState=enabled +`[1:]), + } + s.errors = []error{nil} + out, err := New("", s.rep).Status("foo.service") + c.Assert(err, ErrorMatches, `.* empty field "Id" .*`) + c.Check(out, IsNil) } func (s *SystemdTestSuite) TestStopTimeout(c *C) { @@ -277,33 +407,22 @@ c.Check(Log{"_PID": "42", "SYSLOG_PID": "99"}.PID(), Equals, "42") } -func (s *SystemdTestSuite) TestTimestamp(c *C) { - c.Check(Log{}.Timestamp(), Equals, "-(no timestamp!)-") - c.Check(Log{"__REALTIME_TIMESTAMP": "what"}.Timestamp(), Equals, `-(timestamp not a decimal number: "what")-`) - c.Check(Log{"__REALTIME_TIMESTAMP": "0"}.Timestamp(), Equals, "1970-01-01T00:00:00.000000Z") - c.Check(Log{"__REALTIME_TIMESTAMP": "42"}.Timestamp(), Equals, "1970-01-01T00:00:00.000042Z") -} - -func (s *SystemdTestSuite) TestLogString(c *C) { - c.Check(Log{}.String(), Equals, "-(no timestamp!)- -[-]: -") - c.Check(Log{ - "__REALTIME_TIMESTAMP": "0", - }.String(), Equals, "1970-01-01T00:00:00.000000Z -[-]: -") - c.Check(Log{ - "__REALTIME_TIMESTAMP": "0", - "MESSAGE": "hi", - }.String(), Equals, "1970-01-01T00:00:00.000000Z -[-]: hi") - c.Check(Log{ - "__REALTIME_TIMESTAMP": "42", - "MESSAGE": "hi", - "SYSLOG_IDENTIFIER": "me", - }.String(), Equals, "1970-01-01T00:00:00.000042Z me[-]: hi") - c.Check(Log{ - "__REALTIME_TIMESTAMP": "42", - "MESSAGE": "hi", - "SYSLOG_IDENTIFIER": "me", - "_PID": "99", - }.String(), Equals, "1970-01-01T00:00:00.000042Z me[99]: hi") +func (s *SystemdTestSuite) TestTime(c *C) { + t, err := Log{}.Time() + c.Check(t.IsZero(), Equals, true) + c.Check(err, ErrorMatches, "no timestamp") + + t, err = Log{"__REALTIME_TIMESTAMP": "what"}.Time() + c.Check(t.IsZero(), Equals, true) + c.Check(err, ErrorMatches, `timestamp not a decimal number: "what"`) + + t, err = Log{"__REALTIME_TIMESTAMP": "0"}.Time() + c.Check(err, IsNil) + c.Check(t.String(), Equals, "1970-01-01 00:00:00 +0000 UTC") + + t, err = Log{"__REALTIME_TIMESTAMP": "42"}.Time() + c.Check(err, IsNil) + c.Check(t.String(), Equals, "1970-01-01 00:00:00.000042 +0000 UTC") } func (s *SystemdTestSuite) TestMountUnitPath(c *C) { @@ -360,22 +479,6 @@ `, snapDir)) } -func (s *SystemdTestSuite) TestRestartCondUnmarshal(c *C) { - for cond := range RestartMap { - bs := []byte(cond) - var rc RestartCondition - - c.Check(yaml.Unmarshal(bs, &rc), IsNil) - c.Check(rc, Equals, RestartMap[cond], Commentf(cond)) - } -} - -func (s *SystemdTestSuite) TestRestartCondString(c *C) { - for name, cond := range RestartMap { - c.Check(cond.String(), Equals, name, Commentf(name)) - } -} - func (s *SystemdTestSuite) TestFuseInContainer(c *C) { if !osutil.FileExists("/dev/fuse") { c.Skip("No /dev/fuse on the system") diff -Nru snapd-2.27.5/tests/completion/dirs.complete snapd-2.28.5/tests/completion/dirs.complete --- snapd-2.27.5/tests/completion/dirs.complete 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/tests/completion/dirs.complete 2017-09-13 14:47:18.000000000 +0000 @@ -1,4 +1,4 @@ # -*- sh -*- -complete -A directory -o dirnames complexion +complete -A directory -o dirnames test-snapd-complexion diff -Nru snapd-2.27.5/tests/completion/files.complete snapd-2.28.5/tests/completion/files.complete --- snapd-2.27.5/tests/completion/files.complete 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/tests/completion/files.complete 2017-09-13 14:47:18.000000000 +0000 @@ -1,3 +1,3 @@ # -*- sh -*- -complete -A file -o filenames complexion +complete -A file -o filenames test-snapd-complexion diff -Nru snapd-2.27.5/tests/completion/func.complete snapd-2.28.5/tests/completion/func.complete --- snapd-2.27.5/tests/completion/func.complete 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/tests/completion/func.complete 2017-09-13 14:47:18.000000000 +0000 @@ -4,4 +4,4 @@ COMPREPLY=($( compgen -W "won too tree" "${COMP_WORDS[$COMP_CWORD]}" )) } -complete -F _complete complexion +complete -F _complete test-snapd-complexion diff -Nru snapd-2.27.5/tests/completion/funky.complete snapd-2.28.5/tests/completion/funky.complete --- snapd-2.27.5/tests/completion/funky.complete 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/tests/completion/funky.complete 2017-09-13 14:47:18.000000000 +0000 @@ -1,3 +1,3 @@ # -*- sh -*- -complete -W "this 'that one' 'which one'" complexion +complete -W "this 'that one' 'which one'" test-snapd-complexion diff -Nru snapd-2.27.5/tests/completion/funkyfunc.complete snapd-2.28.5/tests/completion/funkyfunc.complete --- snapd-2.27.5/tests/completion/funkyfunc.complete 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/tests/completion/funkyfunc.complete 2017-09-13 14:47:18.000000000 +0000 @@ -7,5 +7,5 @@ which one" "${COMP_WORDS[$COMP_CWORD]}" )) } -complete -F _complete complexion +complete -F _complete test-snapd-complexion diff -Nru snapd-2.27.5/tests/completion/hosts.complete snapd-2.28.5/tests/completion/hosts.complete --- snapd-2.27.5/tests/completion/hosts.complete 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/tests/completion/hosts.complete 2017-09-13 14:47:18.000000000 +0000 @@ -1,3 +1,3 @@ # -*- sh -*- -complete -A hostname complexion +complete -A hostname test-snapd-complexion diff -Nru snapd-2.27.5/tests/completion/hosts_n_dirs.complete snapd-2.28.5/tests/completion/hosts_n_dirs.complete --- snapd-2.27.5/tests/completion/hosts_n_dirs.complete 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/tests/completion/hosts_n_dirs.complete 2017-09-13 14:47:18.000000000 +0000 @@ -1,3 +1,3 @@ # -*- sh -*- -complete -A hostname -o plusdirs complexion +complete -A hostname -o plusdirs test-snapd-complexion diff -Nru snapd-2.27.5/tests/completion/indirect/task.exp snapd-2.28.5/tests/completion/indirect/task.exp --- snapd-2.27.5/tests/completion/indirect/task.exp 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/tests/completion/indirect/task.exp 2017-09-13 14:47:18.000000000 +0000 @@ -3,16 +3,17 @@ next send "source /usr/share/bash-completion/bash_completion\n" next +# because this sources complete.sh, it won't be using the snippets send "source $::env(SPREAD_PATH)/data/completion/complete.sh\n" next -chat "complexion \t\t" $::env(_OUT0) +chat "test-snapd-complexion \t\t" $::env(_OUT0) cancel # completion when the cursor is not at the end of the line: set back1 [string repeat "\b" [string length $::env(_KEY1)]] -chat "complexion $::env(_KEY1)$back1\t\t" $::env(_OUT0) +chat "test-snapd-complexion $::env(_KEY1)$back1\t\t" $::env(_OUT0) cancel -chat "complexion $::env(_KEY1)\t" $::env(_OUT1) +chat "test-snapd-complexion $::env(_KEY1)\t" $::env(_OUT1) cancel -chat "complexion $::env(_KEY2)\t\t" $::env(_OUT2) +chat "test-snapd-complexion $::env(_KEY2)\t\t" $::env(_OUT2) cancel brexit diff -Nru snapd-2.27.5/tests/completion/indirect/task.yaml snapd-2.28.5/tests/completion/indirect/task.yaml --- snapd-2.27.5/tests/completion/indirect/task.yaml 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/tests/completion/indirect/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -2,17 +2,20 @@ prepare: | ( - cd ../../lib/snaps/complexion + cd ../../lib/snaps/test-snapd-complexion snap try - mv complexion.bash-completer complexion.bash-completer.orig - cp "${SPREAD_PATH}/${SPREAD_SUITE}/${SPREAD_VARIANT}.complete" complexion.bash-completer + mv test-snapd-complexion.bash-completer test-snapd-complexion.bash-completer.orig + cp "${SPREAD_PATH}/${SPREAD_SUITE}/${SPREAD_VARIANT}.complete" test-snapd-complexion.bash-completer + # current ordering should ensure this anyway, but just in case, make sure we're + # using complete.sh and not the generated snippet by removing the snippet + rm /usr/share/bash-completion/completions/test-snapd-complexion ) restore: | ( - cd ../../lib/snaps/complexion - mv complexion.bash-completer.orig complexion.bash-completer - snap remove complexion + cd ../../lib/snaps/test-snapd-complexion + mv test-snapd-complexion.bash-completer.orig test-snapd-complexion.bash-completer + snap remove test-snapd-complexion ) execute: | diff -Nru snapd-2.27.5/tests/completion/plain.complete snapd-2.28.5/tests/completion/plain.complete --- snapd-2.27.5/tests/completion/plain.complete 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/tests/completion/plain.complete 2017-09-13 14:47:18.000000000 +0000 @@ -1,3 +1,3 @@ # -*- sh -*- -complete -W 'won too tree' complexion +complete -W 'won too tree' test-snapd-complexion diff -Nru snapd-2.27.5/tests/completion/plain_plusdirs.complete snapd-2.28.5/tests/completion/plain_plusdirs.complete --- snapd-2.27.5/tests/completion/plain_plusdirs.complete 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/tests/completion/plain_plusdirs.complete 2017-09-13 14:47:18.000000000 +0000 @@ -1,3 +1,3 @@ # -*- sh -*- -complete -W "alpha beta chi" -o plusdirs complexion +complete -W "alpha beta chi" -o plusdirs test-snapd-complexion diff -Nru snapd-2.27.5/tests/completion/simple/task.exp snapd-2.28.5/tests/completion/simple/task.exp --- snapd-2.27.5/tests/completion/simple/task.exp 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/tests/completion/simple/task.exp 2017-09-13 14:47:18.000000000 +0000 @@ -3,14 +3,14 @@ next send -s "source $::env(SPREAD_PATH)/$::env(SPREAD_SUITE)/$::env(SPREAD_VARIANT).complete\n" next -chat "complexion \t\t" $::env(_OUT0) +chat "test-snapd-complexion \t\t" $::env(_OUT0) cancel # completion when the cursor is not at the end of the line: set back1 [string repeat "\b" [string length $::env(_KEY1)]] -chat "complexion $::env(_KEY1)$back1\t\t" $::env(_OUT0) +chat "test-snapd-complexion $::env(_KEY1)$back1\t\t" $::env(_OUT0) cancel -chat "complexion $::env(_KEY1)\t" $::env(_OUT1) +chat "test-snapd-complexion $::env(_KEY1)\t" $::env(_OUT1) cancel -chat "complexion $::env(_KEY2)\t\t" $::env(_OUT2) +chat "test-snapd-complexion $::env(_KEY2)\t\t" $::env(_OUT2) cancel brexit diff -Nru snapd-2.27.5/tests/completion/snippets/task.exp snapd-2.28.5/tests/completion/snippets/task.exp --- snapd-2.27.5/tests/completion/snippets/task.exp 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/completion/snippets/task.exp 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,17 @@ +source "$::env(SPREAD_PATH)/$::env(SPREAD_SUITE)/lib.exp0" +send "source $::env(SPREAD_PATH)/$::env(SPREAD_SUITE)/$::env(SPREAD_VARIANT).sh\n" +next +send "source /usr/share/bash-completion/bash_completion\n" +next +# compared to indirect/, this one does not source complete.sh +chat "test-snapd-complexion \t\t" $::env(_OUT0) +cancel +# completion when the cursor is not at the end of the line: +set back1 [string repeat "\b" [string length $::env(_KEY1)]] +chat "test-snapd-complexion $::env(_KEY1)$back1\t\t" $::env(_OUT0) +cancel +chat "test-snapd-complexion $::env(_KEY1)\t" $::env(_OUT1) +cancel +chat "test-snapd-complexion $::env(_KEY2)\t\t" $::env(_OUT2) +cancel +brexit diff -Nru snapd-2.27.5/tests/completion/snippets/task.yaml snapd-2.28.5/tests/completion/snippets/task.yaml --- snapd-2.27.5/tests/completion/snippets/task.yaml 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/completion/snippets/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,22 @@ +summary: indirect completion + +prepare: | + ( + cd ../../lib/snaps/test-snapd-complexion + snap try + mv test-snapd-complexion.bash-completer test-snapd-complexion.bash-completer.orig + cp "${SPREAD_PATH}/${SPREAD_SUITE}/${SPREAD_VARIANT}.complete" test-snapd-complexion.bash-completer + ) + +restore: | + ( + cd ../../lib/snaps/test-snapd-complexion + mv test-snapd-complexion.bash-completer.orig test-snapd-complexion.bash-completer + snap remove test-snapd-complexion + ) + +execute: | + d="$PWD" + source "${SPREAD_PATH}/${SPREAD_SUITE}/${SPREAD_VARIANT}.vars" + export _OUT0 _OUT1 _OUT2 _KEY1 _KEY2 _COMP + sudo PATH=$PATH -E -u test expect -d -f "$d"/task.exp diff -Nru snapd-2.27.5/tests/completion/twisted.complete snapd-2.28.5/tests/completion/twisted.complete --- snapd-2.27.5/tests/completion/twisted.complete 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/tests/completion/twisted.complete 2017-09-13 14:47:18.000000000 +0000 @@ -1,3 +1,3 @@ # -*- sh -*- -complete -A directory -o filenames complexion +complete -A directory -o filenames test-snapd-complexion diff -Nru snapd-2.27.5/tests/lib/dbus.sh snapd-2.28.5/tests/lib/dbus.sh --- snapd-2.27.5/tests/lib/dbus.sh 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/lib/dbus.sh 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,34 @@ +#!/bin/bash + +start_dbus_unit(){ + local executable="$1" + + dbus-launch > dbus.env + export $(cat dbus.env) + if [[ "$SPREAD_SYSTEM" == ubuntu-14.04-* ]]; then + cat < /etc/init/dbus-provider.conf +env DBUS_SESSION_BUS_ADDRESS="$DBUS_SESSION_BUS_ADDRESS" +env DBUS_SESSION_BUS_PID="$DBUS_SESSION_BUS_PID" +script + $executable +end script +EOF + initctl reload-configuration + start dbus-provider + else + systemd-run --unit dbus-provider \ + --setenv=DBUS_SESSION_BUS_ADDRESS=$DBUS_SESSION_BUS_ADDRESS \ + --setenv=DBUS_SESSION_BUS_PID=$DBUS_SESSION_BUS_PID \ + $executable + fi +} + +stop_dbus_unit(){ + rm -f dbus.env + if [[ "$SPREAD_SYSTEM" == ubuntu-14.04-* ]]; then + stop dbus-provider + rm -f /etc/init/dbus-provider.conf + else + systemctl stop dbus-provider + fi +} diff -Nru snapd-2.27.5/tests/lib/dirs.sh snapd-2.28.5/tests/lib/dirs.sh --- snapd-2.27.5/tests/lib/dirs.sh 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/lib/dirs.sh 2017-09-13 14:47:18.000000000 +0000 @@ -1,11 +1,11 @@ #!/bin/sh -export SNAPMOUNTDIR=/snap +export SNAP_MOUNT_DIR=/snap export LIBEXECDIR=/usr/lib case "$SPREAD_SYSTEM" in fedora-*) - export SNAPMOUNTDIR=/var/lib/snapd/snap + export SNAP_MOUNT_DIR=/var/lib/snapd/snap export LIBEXECDIR=/usr/libexec ;; *) diff -Nru snapd-2.27.5/tests/lib/files.sh snapd-2.28.5/tests/lib/files.sh --- snapd-2.27.5/tests/lib/files.sh 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/lib/files.sh 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,15 @@ +#!/bin/bash + +wait_for_file() { + the_file="$1" + iters="$2" + sleep_time="$3" + + for _ in $(seq "$iters"); do + if [ -f "$the_file" ]; then + return 0 + fi + sleep "$sleep_time" + done + return 1 +} diff -Nru snapd-2.27.5/tests/lib/mkpinentry.sh snapd-2.28.5/tests/lib/mkpinentry.sh --- snapd-2.27.5/tests/lib/mkpinentry.sh 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/lib/mkpinentry.sh 2017-09-13 14:47:18.000000000 +0000 @@ -1,6 +1,18 @@ #!/bin/sh -echo "setup fake gpg pinentry environment" -mkdir -p ~/.snap/gnupg/ -echo pinentry-program "$TESTSLIB/pinentry-fake.sh" > ~/.snap/gnupg/gpg-agent.conf -chmod -R go-rwx ~/.snap +if gpg --version | MATCH "gpg \(GnuPG\) 1."; then + echo "fake gpg pinentry not used for gpg v1" +else + echo "setup fake gpg pinentry environment for gpg v2 or higher" + case "$SPREAD_SYSTEM" in + opensuse-*) + mkdir -p ~/.gnupg + echo pinentry-program "$TESTSLIB/pinentry-fake.sh" > ~/.gnupg/gpg-agent.conf + ;; + *) + mkdir -p ~/.snap/gnupg/ + echo pinentry-program "$TESTSLIB/pinentry-fake.sh" > ~/.snap/gnupg/gpg-agent.conf + chmod -R go-rwx ~/.snap + ;; + esac +fi diff -Nru snapd-2.27.5/tests/lib/pkgdb.sh snapd-2.28.5/tests/lib/pkgdb.sh --- snapd-2.27.5/tests/lib/pkgdb.sh 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/lib/pkgdb.sh 2017-09-13 14:47:18.000000000 +0000 @@ -144,6 +144,16 @@ esac done + # ensure systemd is up-to-date, if there is a mismatch libudev-dev + # will fail to install because the poor apt resolver does not get it + case "$SPREAD_SYSTEM" in + ubuntu-*|debian-*) + if [[ "$@" =~ "libudev-dev" ]]; then + apt-get install -y --only-upgrade systemd + fi + ;; + esac + for pkg in "$@" ; do package_name=$(distro_name_package "$pkg") # When we could not find a different package name for the distribution @@ -154,12 +164,15 @@ case "$SPREAD_SYSTEM" in ubuntu-*|debian-*) + # shellcheck disable=SC2086 quiet apt-get install $APT_FLAGS -y "$package_name" ;; fedora-*) + # shellcheck disable=SC2086 dnf -q -y --refresh install $DNF_FLAGS "$package_name" ;; opensuse-*) + # shellcheck disable=SC2086 zypper -q install -y $ZYPPER_FLAGS "$package_name" ;; *) @@ -185,6 +198,7 @@ ;; fedora-*) dnf -y -q remove "$package_name" + dnf -q clean all ;; opensuse-*) zypper -q remove -y "$package_name" @@ -203,10 +217,10 @@ quiet apt-get update ;; fedora-*) - dnf -y -q upgrade + dnf -q makecache ;; opensuse-*) - zypper -q update -y + zypper -q refresh ;; *) echo "ERROR: Unsupported distribution $SPREAD_SYSTEM" @@ -279,12 +293,15 @@ packages= case "$SPREAD_SYSTEM" in ubuntu-*|debian-*) + # shellcheck disable=SC2125 packages="${GOHOME}"/snapd_*.deb ;; fedora-*) + # shellcheck disable=SC2125 packages="${GOHOME}"/snap-confine*.rpm\ "${GOPATH}"/snapd*.rpm ;; opensuse-*) + # shellcheck disable=SC2125 packages="${GOHOME}"/snapd*.rpm ;; *) @@ -292,6 +309,7 @@ ;; esac + # shellcheck disable=SC2086 distro_install_local_package $packages # On some distributions the snapd.socket is not yet automatically @@ -305,19 +323,151 @@ fi } -# Specify necessary packages which need to be installed on a -# system to provide a basic build environment for snapd. -export DISTRO_BUILD_DEPS=() -case "$SPREAD_SYSTEM" in - debian-*|ubuntu-*) - DISTRO_BUILD_DEPS=(build-essential curl devscripts expect gdebi-core jq rng-tools git netcat-openbsd) - ;; - fedora-*) - DISTRO_BUILD_DEPS=(mock git expect curl golang rpm-build redhat-lsb-core) - ;; - opensuse-*) - DISTRO_BUILD_DEPS=(osc git expect curl golang-packaging lsb-release netcat-openbsd jq rng-tools) - ;; - *) - ;; -esac +distro_get_package_extension() { + case "$SPREAD_SYSTEM" in + ubuntu-*|debian-*) + echo "deb" + ;; + fedora-*|opensuse-*) + echo "rpm" + ;; + esac +} + +pkg_dependencies_ubuntu_generic(){ + echo " + autoconf + automake + autotools-dev + build-essential + curl + devscripts + expect + gdebi-core + git + indent + jq + libapparmor-dev + libglib2.0-dev + libseccomp-dev + libudev-dev + netcat-openbsd + pkg-config + python3-docutils + rng-tools + udev + " +} + +pkg_dependencies_ubuntu_classic(){ + echo " + cups + dbus-x11 + gnome-keyring + jq + man + printer-driver-cups-pdf + python3-yaml + upower + weston + xdg-utils + " + + case "$SPREAD_SYSTEM" in + ubuntu-14.04-*) + echo " + linux-image-extra-$(uname -r) + pollinate + " + ;; + ubuntu-16.04-32) + echo " + linux-image-extra-$(uname -r) + pollinate + " + ;; + ubuntu-16.04-64) + echo " + gccgo-6 + kpartx + libvirt-bin + linux-image-extra-$(uname -r) + pollinate + qemu + x11-utils + xvfb + " + ;; + ubuntu-*) + echo " + linux-image-extra-$(uname -r) + pollinate + " + ;; + debian-*) + ;; + esac +} + +pkg_dependencies_ubuntu_core(){ + echo " + linux-image-extra-$(uname -r) + pollinate + " +} + +pkg_dependencies_fedora(){ + echo " + curl + dbus-x11 + expect + git + golang + jq + mock + redhat-lsb-core + rpm-build + " +} + +pkg_dependencies_opensuse(){ + echo " + curl + expect + git + golang-packaging + jq + lsb-release + netcat-openbsd + osc + rng-tools + xdg-utils + " +} + +pkg_dependencies(){ + case "$SPREAD_SYSTEM" in + ubuntu-core-16-*) + pkg_dependencies_ubuntu_generic + pkg_dependencies_ubuntu_core + ;; + ubuntu-*|debian-*) + pkg_dependencies_ubuntu_generic + pkg_dependencies_ubuntu_classic + ;; + fedora-*) + pkg_dependencies_fedora + ;; + opensuse-*) + pkg_dependencies_opensuse + ;; + *) + ;; + esac +} + +install_pkg_dependencies(){ + pkgs=$(pkg_dependencies) + # shellcheck disable=SC2086 + distro_install_package $pkgs +} diff -Nru snapd-2.27.5/tests/lib/prepare-project.sh snapd-2.28.5/tests/lib/prepare-project.sh --- snapd-2.27.5/tests/lib/prepare-project.sh 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/lib/prepare-project.sh 2017-09-13 14:47:18.000000000 +0000 @@ -74,8 +74,9 @@ # Create a source tarball for the current snapd sources mkdir -p "/tmp/pkg/snapd-$version" - cp -rav -- * "/tmp/pkg/snapd-$version/" + cp -ra -- * "/tmp/pkg/snapd-$version/" mkdir -p "$rpm_dir/SOURCES" + # shellcheck disable=SC2086 (cd /tmp/pkg && tar c${archive_compression}f "$rpm_dir/SOURCES/$archive_name" "snapd-$version" $extra_tar_args) cp "$packaging_path"/* "$rpm_dir/SOURCES/" @@ -158,11 +159,11 @@ if [ ! -f "$GOHOME/bin/snapbuild" ]; then mkdir -p "$GOHOME/bin" snap install --edge test-snapd-snapbuild - cp "$SNAPMOUNTDIR/test-snapd-snapbuild/current/bin/snapbuild" "$GOHOME/bin/snapbuild" + cp "$SNAP_MOUNT_DIR/test-snapd-snapbuild/current/bin/snapbuild" "$GOHOME/bin/snapbuild" snap remove test-snapd-snapbuild fi # stop and disable autorefresh - if [ -e "$SNAPMOUNTDIR/core/current/meta/hooks/configure" ]; then + if [ -e "$SNAP_MOUNT_DIR/core/current/meta/hooks/configure" ]; then systemctl disable --now snapd.refresh.timer snap set core refresh.disabled=true fi @@ -218,15 +219,12 @@ fi distro_purge_package snapd || true -distro_install_package ${DISTRO_BUILD_DEPS[@]} +install_pkg_dependencies # We take a special case for Debian/Ubuntu where we install additional build deps # base on the packaging. In Fedora/Suse this is handled via mock/osc case "$SPREAD_SYSTEM" in debian-*|ubuntu-*) - # ensure systemd is up-to-date, if there is a mismatch libudev-dev - # will fail to install because the poor apt resolver does not get it - apt-get install -y --only-upgrade systemd # in 16.04: apt build-dep -y ./ gdebi --quiet --apt-line ./debian/control | quiet xargs -r apt-get install -y ;; diff -Nru snapd-2.27.5/tests/lib/prepare.sh snapd-2.28.5/tests/lib/prepare.sh --- snapd-2.27.5/tests/lib/prepare.sh 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/lib/prepare.sh 2017-09-13 14:47:18.000000000 +0000 @@ -32,7 +32,7 @@ # shove the new snap-exec and snapctl in there, and repack it. # First of all, unmount the core - core="$(readlink -f "$SNAPMOUNTDIR/core/current" || readlink -f "$SNAPMOUNTDIR/ubuntu-core/current")" + core="$(readlink -f "$SNAP_MOUNT_DIR/core/current" || readlink -f "$SNAP_MOUNT_DIR/ubuntu-core/current")" snap="$(mount | grep " $core" | awk '{print $1}')" umount --verbose "$core" @@ -91,6 +91,11 @@ Environment=SNAP_REEXEC=$SNAP_REEXEC EOF fi + # the re-exec setting may have changed in the service so we need + # to ensure snapd is reloaded + systemctl daemon-reload + systemctl restart snapd + if [ ! -f /etc/systemd/system/snapd.service.d/local.conf ]; then echo "/etc/systemd/system/snapd.service.d/local.conf vanished!" exit 1 @@ -152,6 +157,26 @@ # Snapshot the state including core. if [ ! -f "$SPREAD_PATH/snapd-state.tar.gz" ]; then + # Pre-cache a few heavy snaps so that they can be installed by tests + # quickly. This relies on a behavior of snapd where .partial files are + # used for resuming downloads. + ( + set -x + cd /tmp + for snap_name in ${PRE_CACHE_SNAPS:-}; do + snap download "$snap_name" + done + for snap_file in *.snap; do + mv "$snap_file" "$snap_file.partial" + # There is a bug in snapd where partial file must be a proper + # prefix of the full file or we make a wrong request to the + # store. + truncate --size=-1 "$snap_file.partial" + mv "$snap_file.partial" /var/lib/snapd/snaps/ + done + set +x + ) + ! snap list | grep core || exit 1 # use parameterized core channel (defaults to edge) instead # of a fixed one and close to stable in order to detect defects @@ -186,15 +211,16 @@ systemctl stop snapd.{service,socket} systemctl daemon-reload - escaped_snap_mount_dir="$(systemd-escape --path "$SNAPMOUNTDIR")" + escaped_snap_mount_dir="$(systemd-escape --path "$SNAP_MOUNT_DIR")" units="$(systemctl list-unit-files --full | grep -e "^$escaped_snap_mount_dir[-.].*\.mount" -e "^$escaped_snap_mount_dir[-.].*\.service" | cut -f1 -d ' ')" for unit in $units; do systemctl stop "$unit" done snapd_env="/etc/environment /etc/systemd/system/snapd.service.d /etc/systemd/system/snapd.socket.d" - tar czf "$SPREAD_PATH"/snapd-state.tar.gz /var/lib/snapd "$SNAPMOUNTDIR" /etc/systemd/system/"$escaped_snap_mount_dir"-*core*.mount $snapd_env + # shellcheck disable=SC2086 + tar czf "$SPREAD_PATH"/snapd-state.tar.gz /var/lib/snapd "$SNAP_MOUNT_DIR" /etc/systemd/system/"$escaped_snap_mount_dir"-*core*.mount $snapd_env systemctl daemon-reload # Workaround for http://paste.ubuntu.com/17735820/ - core="$(readlink -f "$SNAPMOUNTDIR/core/current")" + core="$(readlink -f "$SNAP_MOUNT_DIR/core/current")" # on 14.04 it is possible that the core snap is still mounted at this point, unmount # to prevent errors starting the mount unit if [[ "$SPREAD_SYSTEM" = ubuntu-14.04-* ]] && mount | grep -q "$core"; then @@ -208,13 +234,11 @@ if [[ "$SPREAD_SYSTEM" == debian-* || "$SPREAD_SYSTEM" == ubuntu-* ]]; then if [[ "$SPREAD_SYSTEM" == ubuntu-* ]]; then - quiet apt install -y -q pollinate pollinate fi # Improve entropy for the whole system quite a lot to get fast # key generation during our test cycles - apt-get install -y -q rng-tools echo "HRNGDEVICE=/dev/urandom" > /etc/default/rng-tools /etc/init.d/rng-tools restart diff -Nru snapd-2.27.5/tests/lib/reset.sh snapd-2.28.5/tests/lib/reset.sh --- snapd-2.27.5/tests/lib/reset.sh 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/lib/reset.sh 2017-09-13 14:47:18.000000000 +0000 @@ -18,7 +18,7 @@ ;; fedora-*|opensuse-*) sh -x "${SPREAD_PATH}/packaging/fedora/snap-mgmt.sh" \ - --snap-mount-dir="$SNAPMOUNTDIR" \ + --snap-mount-dir="$SNAP_MOUNT_DIR" \ --purge # The script above doesn't remove the snapd directory as this # is normally done by the rpm packaging system. @@ -29,11 +29,11 @@ ;; esac # extra purge - rm -rvf /var/snap "${SNAPMOUNTDIR:?}/bin" - mkdir -p "$SNAPMOUNTDIR" /var/snap /var/lib/snapd - if [ "$(find "$SNAPMOUNTDIR" /var/snap -mindepth 1 -print -quit)" ]; then + rm -rvf /var/snap "${SNAP_MOUNT_DIR:?}/bin" + mkdir -p "$SNAP_MOUNT_DIR" /var/snap /var/lib/snapd + if [ "$(find "$SNAP_MOUNT_DIR" /var/snap -mindepth 1 -print -quit)" ]; then echo "postinst purge failed" - ls -lR "$SNAPMOUNTDIR"/ /var/snap/ + ls -lR "$SNAP_MOUNT_DIR"/ /var/snap/ exit 1 fi @@ -51,7 +51,7 @@ # Restore snapd state and start systemd service units tar -C/ -xzf "$SPREAD_PATH/snapd-state.tar.gz" - escaped_snap_mount_dir="$(systemd-escape --path "$SNAPMOUNTDIR")" + escaped_snap_mount_dir="$(systemd-escape --path "$SNAP_MOUNT_DIR")" mounts="$(systemctl list-unit-files --full | grep "^$escaped_snap_mount_dir[-.].*\.mount" | cut -f1 -d ' ')" services="$(systemctl list-unit-files --full | grep "^$escaped_snap_mount_dir[-.].*\.service" | cut -f1 -d ' ')" systemctl daemon-reload # Workaround for http://paste.ubuntu.com/17735820/ @@ -77,7 +77,7 @@ # shellcheck source=tests/lib/names.sh . "$TESTSLIB/names.sh" - for snap in "$SNAPMOUNTDIR"/*; do + for snap in "$SNAP_MOUNT_DIR"/*; do snap="${snap:6}" case "$snap" in "bin" | "$gadget_name" | "$kernel_name" | core ) diff -Nru snapd-2.27.5/tests/lib/snaps/basic-hooks/meta/hooks/configure snapd-2.28.5/tests/lib/snaps/basic-hooks/meta/hooks/configure --- snapd-2.27.5/tests/lib/snaps/basic-hooks/meta/hooks/configure 2016-09-28 09:07:27.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/basic-hooks/meta/hooks/configure 2017-09-20 07:05:01.000000000 +0000 @@ -1,3 +1,7 @@ #!/bin/sh echo "configure hook" +command=$(snapctl get command) +if [ "$command" = "dump-env" ]; then + env > $SNAP_DATA/hooks-env +fi diff -Nru snapd-2.27.5/tests/lib/snaps/basic-hooks/meta/snap.yaml snapd-2.28.5/tests/lib/snaps/basic-hooks/meta/snap.yaml --- snapd-2.27.5/tests/lib/snaps/basic-hooks/meta/snap.yaml 2016-06-27 15:33:55.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/basic-hooks/meta/snap.yaml 2017-09-20 07:05:01.000000000 +0000 @@ -1,2 +1,6 @@ name: basic-hooks version: 1.0 +environment: + TEST_SNAP: $SNAP + TEST_COMMON: $SNAP_COMMON + TEST_DATA: $SNAP_DATA diff -Nru snapd-2.27.5/tests/lib/snaps/browser-support-consumer/bin/cmd snapd-2.28.5/tests/lib/snaps/browser-support-consumer/bin/cmd --- snapd-2.27.5/tests/lib/snaps/browser-support-consumer/bin/cmd 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/browser-support-consumer/bin/cmd 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,2 @@ +#!/bin/sh +"$@" diff -Nru snapd-2.27.5/tests/lib/snaps/browser-support-consumer/meta/snap.yaml.in snapd-2.28.5/tests/lib/snaps/browser-support-consumer/meta/snap.yaml.in --- snapd-2.27.5/tests/lib/snaps/browser-support-consumer/meta/snap.yaml.in 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/browser-support-consumer/meta/snap.yaml.in 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,10 @@ +name: browser-support-consumer +version: 1.0 +apps: + cmd: + command: bin/cmd + plugs: [browser-support] + +plugs: + browser-support: + allow-sandbox: @ALLOW_SANDBOX@ diff -Nru snapd-2.27.5/tests/lib/snaps/complexion/bin/complexion snapd-2.28.5/tests/lib/snaps/complexion/bin/complexion --- snapd-2.27.5/tests/lib/snaps/complexion/bin/complexion 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/complexion/bin/complexion 1970-01-01 00:00:00.000000000 +0000 @@ -1,7 +0,0 @@ -#!/bin/bash -echo "Greetings from inside ${SNAP:?}." -if [ "$#" -gt "0" ]; then - echo "Arguments given:" - printf "> %q\n" "$@" -fi -echo "That is all. Have a nice day." diff -Nru snapd-2.27.5/tests/lib/snaps/complexion/complexion.bash-completer snapd-2.28.5/tests/lib/snaps/complexion/complexion.bash-completer --- snapd-2.27.5/tests/lib/snaps/complexion/complexion.bash-completer 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/complexion/complexion.bash-completer 1970-01-01 00:00:00.000000000 +0000 @@ -1,30 +0,0 @@ -# -*- shell-script -*- - -_complete_complexion() { - local cur prev words cword - _init_completion || return - - case "$prev" in - -f) - _filedir - return - ;; - -h) - _known_hosts_real "$cur" - return - ;; - -b) - COMPREPLY=( $( compgen -W 'counterrevolutionary electroencephalogram uncharacteristically' -- "$cur" ) ) - return - ;; - esac - - if [[ "$cur" == -* ]]; then - COMPREPLY=( $( compgen -W "-f -h -b" -- "$cur" ) ) - return - fi - - _filedir -} - -complete -F _complete_complexion complexion diff -Nru snapd-2.27.5/tests/lib/snaps/complexion/meta/snap.yaml snapd-2.28.5/tests/lib/snaps/complexion/meta/snap.yaml --- snapd-2.27.5/tests/lib/snaps/complexion/meta/snap.yaml 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/complexion/meta/snap.yaml 1970-01-01 00:00:00.000000000 +0000 @@ -1,7 +0,0 @@ -name: complexion -version: 1.0 -apps: - complexion: - command: bin/complexion - completer: complexion.bash-completer - plugs: [home] diff -Nru snapd-2.27.5/tests/lib/snaps/snapctl-from-snap/bin/snapctl-get snapd-2.28.5/tests/lib/snaps/snapctl-from-snap/bin/snapctl-get --- snapd-2.27.5/tests/lib/snaps/snapctl-from-snap/bin/snapctl-get 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/snapctl-from-snap/bin/snapctl-get 2017-09-13 14:47:18.000000000 +0000 @@ -1,2 +1,2 @@ #!/bin/sh -snapctl get $@ +snapctl get "$@" diff -Nru snapd-2.27.5/tests/lib/snaps/snapctl-from-snap/bin/snapctl-set snapd-2.28.5/tests/lib/snaps/snapctl-from-snap/bin/snapctl-set --- snapd-2.27.5/tests/lib/snaps/snapctl-from-snap/bin/snapctl-set 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/snapctl-from-snap/bin/snapctl-set 2017-09-13 14:47:18.000000000 +0000 @@ -1,2 +1,2 @@ #!/bin/sh -snapctl set $@ +snapctl set "$@" diff -Nru snapd-2.27.5/tests/lib/snaps/snapctl-hooks/meta/hooks/configure snapd-2.28.5/tests/lib/snaps/snapctl-hooks/meta/hooks/configure --- snapd-2.27.5/tests/lib/snaps/snapctl-hooks/meta/hooks/configure 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/snapctl-hooks/meta/hooks/configure 2017-09-13 14:47:18.000000000 +0000 @@ -30,6 +30,28 @@ fi } +test_get_int_number() { + echo "Getting int number" + if ! output="$(snapctl get intnumber)"; then + echo "Expected snapctl get to be able to retrieve value just set" + exit 1 + fi + expected_output="1234567890" + if [ "$output" != "$expected_output" ]; then + echo "Expected output to be '$expected_output', but it was '$output'" + exit 1 + fi + if ! output="$(snapctl get intnumber2)"; then + echo "Expected snapctl get to be able to retrieve value just set" + exit 1 + fi + expected_output="\"a\": 9876543210" + if ! echo "$output" | grep -q -e "$expected_output"; then + echo "Expected output to be '$expected_output', but it was '$output'" + exit 1 + fi +} + test_snapctl_get_foo_null() { echo "Getting foo" if ! output="$(snapctl get foo)"; then @@ -68,6 +90,9 @@ "test-exit-one") test_exit_one ;; + "test-get-int") + test_get_int_number + ;; *) echo "Invalid command: '$command'" exit 1 diff -Nru snapd-2.27.5/tests/lib/snaps/snap-hooks/meta/hooks/post-refresh snapd-2.28.5/tests/lib/snaps/snap-hooks/meta/hooks/post-refresh --- snapd-2.27.5/tests/lib/snaps/snap-hooks/meta/hooks/post-refresh 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/snap-hooks/meta/hooks/post-refresh 2017-10-04 14:43:22.000000000 +0000 @@ -0,0 +1,3 @@ +#!/bin/sh + +snapctl set refreshed=1 diff -Nru snapd-2.27.5/tests/lib/snaps/snap-hooks/meta/hooks/remove snapd-2.28.5/tests/lib/snaps/snap-hooks/meta/hooks/remove --- snapd-2.27.5/tests/lib/snaps/snap-hooks/meta/hooks/remove 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/snap-hooks/meta/hooks/remove 2017-09-13 14:47:18.000000000 +0000 @@ -2,4 +2,4 @@ RETVAL=$(snapctl get exitcode) echo "$RETVAL" > /root/remove-hook-executed -exit $RETVAL +exit "$RETVAL" diff -Nru snapd-2.27.5/tests/lib/snaps/test-snapd-autopilot-consumer/consumer snapd-2.28.5/tests/lib/snaps/test-snapd-autopilot-consumer/consumer --- snapd-2.27.5/tests/lib/snaps/test-snapd-autopilot-consumer/consumer 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/test-snapd-autopilot-consumer/consumer 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +import dbus +import os +import sys + +def _get_obj(): + return dbus.SessionBus().get_object("com.canonical.Autopilot.Introspection", "/com/canonical/Autopilot/Introspection") + +def get_version(): + obj = _get_obj() + print(obj.GetVersion(dbus_interface="com.canonical.Autopilot.Introspection")) + +def get_state(): + obj = _get_obj() + print(obj.GetState(dbus_interface="com.canonical.Autopilot.Introspection")) + +if __name__ == "__main__": + if len(sys.argv) > 1 and sys.argv[1] == "GetState": + sys.exit(get_state()) + else: + sys.exit(get_version()) diff -Nru snapd-2.27.5/tests/lib/snaps/test-snapd-autopilot-consumer/provider.py snapd-2.28.5/tests/lib/snaps/test-snapd-autopilot-consumer/provider.py --- snapd-2.27.5/tests/lib/snaps/test-snapd-autopilot-consumer/provider.py 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/test-snapd-autopilot-consumer/provider.py 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 + +from gi.repository import GLib +import dbus +import dbus.service + +from dbus.mainloop.glib import DBusGMainLoop + +DBusGMainLoop(set_as_default=True) + +class DBusProvider(dbus.service.Object): + def __init__(self): + bus = dbus.SessionBus() + bus_name = dbus.service.BusName("com.canonical.Autopilot.Introspection", bus=bus) + dbus.service.Object.__init__(self, bus_name, "/com/canonical/Autopilot/Introspection") + + @dbus.service.method(dbus_interface="com.canonical.Autopilot.Introspection", + out_signature="s") + def GetVersion(self): + return "my-ap-version" + + @dbus.service.method(dbus_interface="com.canonical.Autopilot.Introspection", + out_signature="s") + def GetState(self): + return "my-ap-state" + +if __name__ == "__main__": + DBusProvider() + loop = GLib.MainLoop() + loop.run() diff -Nru snapd-2.27.5/tests/lib/snaps/test-snapd-autopilot-consumer/snapcraft.yaml snapd-2.28.5/tests/lib/snaps/test-snapd-autopilot-consumer/snapcraft.yaml --- snapd-2.27.5/tests/lib/snaps/test-snapd-autopilot-consumer/snapcraft.yaml 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/test-snapd-autopilot-consumer/snapcraft.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,28 @@ +name: test-snapd-autopilot-consumer +version: 1.0 +summary: Basic autopilot consumer snap +description: A basic snap declaring an autopilot plug +confinement: strict +grade: stable + +apps: + provider: + command: wrapper + slots: [autopilot-test] + consumer: + command: consumer + plugs: [autopilot-introspection] + +slots: + autopilot-test: + interface: dbus + bus: session + name: com.canonical.Autopilot.Introspection + +parts: + deps: + plugin: python + stage-packages: [python3-gi, python3-dbus, gir1.2-glib-2.0] + copy: + plugin: dump + source: . diff -Nru snapd-2.27.5/tests/lib/snaps/test-snapd-autopilot-consumer/wrapper snapd-2.28.5/tests/lib/snaps/test-snapd-autopilot-consumer/wrapper --- snapd-2.27.5/tests/lib/snaps/test-snapd-autopilot-consumer/wrapper 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/test-snapd-autopilot-consumer/wrapper 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,3 @@ +export GI_TYPELIB_PATH=$SNAP/usr/lib/girepository-1.0:$(ls -d $SNAP/usr/lib/*/girepository-1.0) + +$SNAP/usr/bin/python3 $SNAP/provider.py diff -Nru snapd-2.27.5/tests/lib/snaps/test-snapd-base/meta/snap.yaml snapd-2.28.5/tests/lib/snaps/test-snapd-base/meta/snap.yaml --- snapd-2.27.5/tests/lib/snaps/test-snapd-base/meta/snap.yaml 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/test-snapd-base/meta/snap.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,4 @@ +name: test-snapd-base +version: 1.0 +type: base +summary: A base snap for testing diff -Nru snapd-2.27.5/tests/lib/snaps/test-snapd-base/random-file snapd-2.28.5/tests/lib/snaps/test-snapd-base/random-file --- snapd-2.27.5/tests/lib/snaps/test-snapd-base/random-file 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/test-snapd-base/random-file 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1 @@ +some random file diff -Nru snapd-2.27.5/tests/lib/snaps/test-snapd-base-bare/Makefile snapd-2.28.5/tests/lib/snaps/test-snapd-base-bare/Makefile --- snapd-2.27.5/tests/lib/snaps/test-snapd-base-bare/Makefile 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/test-snapd-base-bare/Makefile 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,13 @@ +#!/usr/bin/make -f + +DIRS := dev etc home lib/modules media proc root \ + run/media run/netns \ + snap sys tmp \ + usr/bin usr/lib/snapd usr/src \ + var/lib/snapd var/log var/snap var/tmp + +install: + mkdir -p $(DIRS) + +clean: + rm -rf $(DIRS) diff -Nru snapd-2.27.5/tests/lib/snaps/test-snapd-base-bare/snapcraft.yaml snapd-2.28.5/tests/lib/snaps/test-snapd-base-bare/snapcraft.yaml --- snapd-2.27.5/tests/lib/snaps/test-snapd-base-bare/snapcraft.yaml 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/test-snapd-base-bare/snapcraft.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,12 @@ +name: test-snapd-base-bare +version: 1.0 +type: base +summary: Empty base snap for snapd testing +description: | + An example base snap that contains nothing except the directories + required as mount points. + +parts: + dir-layout: + plugin: make + source: . diff -Nru snapd-2.27.5/tests/lib/snaps/test-snapd-busybox-static/snapcraft.yaml snapd-2.28.5/tests/lib/snaps/test-snapd-busybox-static/snapcraft.yaml --- snapd-2.27.5/tests/lib/snaps/test-snapd-busybox-static/snapcraft.yaml 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/test-snapd-busybox-static/snapcraft.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,17 @@ +name: test-snapd-busybox-static +version: 1.0 +summary: Statically linked busybox for test +description: | + Statically linked busybox for test useful for e.g. bare base testing +base: test-snapd-base-bare +apps: + busybox-static: + command: bin/busybox + wrapper: none + +parts: + busybox-static: + plugin: dump + stage-packages: [busybox-static] + snap: + - bin/busybox diff -Nru snapd-2.27.5/tests/lib/snaps/test-snapd-classic-confinement/bin/recurse snapd-2.28.5/tests/lib/snaps/test-snapd-classic-confinement/bin/recurse --- snapd-2.27.5/tests/lib/snaps/test-snapd-classic-confinement/bin/recurse 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/test-snapd-classic-confinement/bin/recurse 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,6 @@ +#!/bin/sh +N="${1:-0}" +echo "recurse: $N" +if [ "$N" -gt 0 ]; then + exec /snap/bin/test-snapd-classic-confinement.recurse $(( N - 1 )) +fi diff -Nru snapd-2.27.5/tests/lib/snaps/test-snapd-classic-confinement/meta/snap.yaml snapd-2.28.5/tests/lib/snaps/test-snapd-classic-confinement/meta/snap.yaml --- snapd-2.27.5/tests/lib/snaps/test-snapd-classic-confinement/meta/snap.yaml 2017-08-24 06:52:40.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/test-snapd-classic-confinement/meta/snap.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -4,3 +4,5 @@ apps: test-snapd-classic-confinement: command: bin/classic-confinement + recurse: + command: bin/recurse diff -Nru snapd-2.27.5/tests/lib/snaps/test-snapd-complexion/bin/test-snapd-complexion snapd-2.28.5/tests/lib/snaps/test-snapd-complexion/bin/test-snapd-complexion --- snapd-2.27.5/tests/lib/snaps/test-snapd-complexion/bin/test-snapd-complexion 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/test-snapd-complexion/bin/test-snapd-complexion 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,7 @@ +#!/bin/bash +echo "Greetings from inside ${SNAP:?}." +if [ "$#" -gt "0" ]; then + echo "Arguments given:" + printf "> %q\n" "$@" +fi +echo "That is all. Have a nice day." diff -Nru snapd-2.27.5/tests/lib/snaps/test-snapd-complexion/meta/snap.yaml snapd-2.28.5/tests/lib/snaps/test-snapd-complexion/meta/snap.yaml --- snapd-2.27.5/tests/lib/snaps/test-snapd-complexion/meta/snap.yaml 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/test-snapd-complexion/meta/snap.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,7 @@ +name: test-snapd-complexion +version: 1.0 +apps: + test-snapd-complexion: + command: bin/test-snapd-complexion + completer: test-snapd-complexion.bash-completer + plugs: [home] diff -Nru snapd-2.27.5/tests/lib/snaps/test-snapd-complexion/test-snapd-complexion.bash-completer snapd-2.28.5/tests/lib/snaps/test-snapd-complexion/test-snapd-complexion.bash-completer --- snapd-2.27.5/tests/lib/snaps/test-snapd-complexion/test-snapd-complexion.bash-completer 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/test-snapd-complexion/test-snapd-complexion.bash-completer 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,30 @@ +# -*- shell-script -*- + +_complete_test-snapd-complexion() { + local cur prev words cword + _init_completion || return + + case "$prev" in + -f) + _filedir + return + ;; + -h) + _known_hosts_real "$cur" + return + ;; + -b) + COMPREPLY=( $( compgen -W 'counterrevolutionary electroencephalogram uncharacteristically' -- "$cur" ) ) + return + ;; + esac + + if [[ "$cur" == -* ]]; then + COMPREPLY=( $( compgen -W "-f -h -b" -- "$cur" ) ) + return + fi + + _filedir +} + +complete -F _complete_test-snapd-complexion test-snapd-complexion diff -Nru snapd-2.27.5/tests/lib/snaps/test-snapd-requires-base/meta/snap.yaml snapd-2.28.5/tests/lib/snaps/test-snapd-requires-base/meta/snap.yaml --- snapd-2.27.5/tests/lib/snaps/test-snapd-requires-base/meta/snap.yaml 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps/test-snapd-requires-base/meta/snap.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,4 @@ +name: test-snapd-requires-base +base: test-snapd-base +version: 1.0 +summary: A test snap that requires a specific base snap diff -Nru snapd-2.27.5/tests/lib/snaps.sh snapd-2.28.5/tests/lib/snaps.sh --- snapd-2.27.5/tests/lib/snaps.sh 2017-08-24 06:52:10.000000000 +0000 +++ snapd-2.28.5/tests/lib/snaps.sh 2017-09-13 14:47:18.000000000 +0000 @@ -43,3 +43,23 @@ snap install --dangerous generic-consumer/*.snap rm -rf generic-consumer } + +is_classic_confinement_supported() { + case "$SPREAD_SYSTEM" in + ubuntu-core-16-*) + return 1 + ;; + ubuntu-*|debian-*) + return 0 + ;; + fedora-*) + return 1 + ;; + opensuse-*) + return 0 + ;; + *) + return 0 + ;; + esac +} diff -Nru snapd-2.27.5/tests/lib/store.sh snapd-2.28.5/tests/lib/store.sh --- snapd-2.27.5/tests/lib/store.sh 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/lib/store.sh 2017-09-13 14:47:18.000000000 +0000 @@ -51,6 +51,20 @@ echo "And snapd is configured to use the controlled store" _configure_store_backends "SNAPPY_FORCE_API_URL=http://localhost:11028" "SNAPPY_USE_STAGING_STORE=$SNAPPY_USE_STAGING_STORE" + + echo "Wait until fake store is ready" + for _ in $(seq 15); do + if netstat -ntlp | MATCH "127.0.0.1:11028*.*LISTEN"; then + return 0 + fi + sleep 1 + done + + echo "fakestore service not started properly" + netstat -ntlp | grep "127.0.0.1:11028" || true + journalctl -u fakestore || true + systemctl status fakestore || true + exit 1 } teardown_fake_store(){ diff -Nru snapd-2.27.5/tests/lib/strings.sh snapd-2.28.5/tests/lib/strings.sh --- snapd-2.27.5/tests/lib/strings.sh 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/lib/strings.sh 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,5 @@ +#!/bin/sh + +str_to_one_line(){ + echo "$1" | tr '\r\n' ' ' | tr -s ' ' +} diff -Nru snapd-2.27.5/tests/lib/systemd.sh snapd-2.28.5/tests/lib/systemd.sh --- snapd-2.27.5/tests/lib/systemd.sh 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/lib/systemd.sh 2017-09-14 13:53:11.000000000 +0000 @@ -12,9 +12,27 @@ # Use like systemd_stop_and_destroy_unit(fakestore) systemd_stop_and_destroy_unit() { - if systemctl status "$1"; then + if systemctl is-active "$1"; then systemctl stop "$1" fi rm -f "/run/systemd/system/$1.service" systemctl daemon-reload } + +wait_for_service() { + local service_name="$1" + local state="${2:-active}" + for i in $(seq 300); do + if systemctl show -p ActiveState "$service_name" | grep -q "ActiveState=$state"; then + return + fi + # show debug output every 1min + if [ $(( i % 60 )) = 0 ]; then + systemctl status "$service_name" || true; + fi + sleep 1; + done + + echo "service $service_name did not start" + exit 1 +} diff -Nru snapd-2.27.5/tests/main/abort/task.yaml snapd-2.28.5/tests/main/abort/task.yaml --- snapd-2.27.5/tests/main/abort/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/abort/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -15,7 +15,7 @@ echo "====================================" echo "Abort with valid id - error" - subdirPath="$SNAPMOUNTDIR/$SNAP_NAME/current/foo" + subdirPath="$SNAP_MOUNT_DIR/$SNAP_NAME/current/foo" mkdir -p "$subdirPath" . "$TESTSLIB/snaps.sh" if install_local "$SNAP_NAME"; then diff -Nru snapd-2.27.5/tests/main/alias/task.yaml snapd-2.28.5/tests/main/alias/task.yaml --- snapd-2.27.5/tests/main/alias/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/alias/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -16,8 +16,8 @@ snap alias aliases.cmd2 alias2 echo "Test the aliases" - test -h "$SNAPMOUNTDIR/bin/alias1" - test -h "$SNAPMOUNTDIR/bin/alias2" + test -h "$SNAP_MOUNT_DIR/bin/alias1" + test -h "$SNAP_MOUNT_DIR/bin/alias2" alias1|MATCH "ok command 1" alias2|MATCH "ok command 2" @@ -30,7 +30,7 @@ echo "One still works, one is not there" alias1|MATCH "ok command 1" - test ! -e "$SNAPMOUNTDIR/bin/alias2" + test ! -e "$SNAP_MOUNT_DIR/bin/alias2" alias2 2>&1|MATCH "alias2: command not found" echo "Check listing again" @@ -41,7 +41,7 @@ snap unalias aliases|MATCH ".*- aliases.cmd1 as alias1*" echo "Alias is gone" - test ! -e "$SNAPMOUNTDIR/bin/alias1" + test ! -e "$SNAP_MOUNT_DIR/bin/alias1" alias1 2>&1|MATCH "alias1: command not found" ! snap aliases|MATCH "aliases.cmd1 +alias1" @@ -51,5 +51,5 @@ echo "Removing the snap should remove the aliases" snap remove aliases - test ! -e "$SNAPMOUNTDIR/bin/alias1" - test ! -e "$SNAPMOUNTDIR/bin/alias2" + test ! -e "$SNAP_MOUNT_DIR/bin/alias1" + test ! -e "$SNAP_MOUNT_DIR/bin/alias2" diff -Nru snapd-2.27.5/tests/main/auto-aliases/task.yaml snapd-2.28.5/tests/main/auto-aliases/task.yaml --- snapd-2.27.5/tests/main/auto-aliases/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/auto-aliases/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -6,8 +6,8 @@ snap install test-snapd-auto-aliases echo "Test the auto-aliases" - test -h "$SNAPMOUNTDIR/bin/test_snapd_wellknown1" - test -h "$SNAPMOUNTDIR/bin/test_snapd_wellknown2" + test -h "$SNAP_MOUNT_DIR/bin/test_snapd_wellknown1" + test -h "$SNAP_MOUNT_DIR/bin/test_snapd_wellknown2" test_snapd_wellknown1|MATCH "ok wellknown 1" test_snapd_wellknown2|MATCH "ok wellknown 2" @@ -17,21 +17,21 @@ echo "Removing the snap should remove the aliases" snap remove test-snapd-auto-aliases - test ! -e "$SNAPMOUNTDIR/bin/test_snapd_wellknown1" - test ! -e "$SNAPMOUNTDIR/bin/test_snapd_wellknown2" + test ! -e "$SNAP_MOUNT_DIR/bin/test_snapd_wellknown1" + test ! -e "$SNAP_MOUNT_DIR/bin/test_snapd_wellknown2" ! snap aliases|MATCH "test-snapd-auto-aliases.wellknown1 +test_snapd_wellknown1" ! snap aliases|MATCH "test-snapd-auto-aliases.wellknown2 +test_snapd_wellknown2" echo "Installing the snap with --unaliased doesn't create the aliases" snap install --unaliased test-snapd-auto-aliases - test ! -e "$SNAPMOUNTDIR/bin/test_snapd_wellknown1" - test ! -e "$SNAPMOUNTDIR/bin/test_snapd_wellknown2" + test ! -e "$SNAP_MOUNT_DIR/bin/test_snapd_wellknown1" + test ! -e "$SNAP_MOUNT_DIR/bin/test_snapd_wellknown2" snap aliases|MATCH "test-snapd-auto-aliases.wellknown1 +test_snapd_wellknown1 +disabled" snap aliases|MATCH "test-snapd-auto-aliases.wellknown2 +test_snapd_wellknown2 +disabled" echo "snap prefer will enable them after the fact" snap prefer test-snapd-auto-aliases - test -h "$SNAPMOUNTDIR/bin/test_snapd_wellknown1" - test -h "$SNAPMOUNTDIR/bin/test_snapd_wellknown2" + test -h "$SNAP_MOUNT_DIR/bin/test_snapd_wellknown1" + test -h "$SNAP_MOUNT_DIR/bin/test_snapd_wellknown2" snap aliases|MATCH "test-snapd-auto-aliases.wellknown1 +test_snapd_wellknown1 +-" snap aliases|MATCH "test-snapd-auto-aliases.wellknown2 +test_snapd_wellknown2 +-" diff -Nru snapd-2.27.5/tests/main/auto-refresh/task.yaml snapd-2.28.5/tests/main/auto-refresh/task.yaml --- snapd-2.27.5/tests/main/auto-refresh/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/auto-refresh/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -5,7 +5,7 @@ restore: | . "$TESTSLIB/dirs.sh" - if [ -e "$SNAPMOUNTDIR/core/current/meta/hooks/configure" ]; then + if [ -e "$SNAP_MOUNT_DIR/core/current/meta/hooks/configure" ]; then snap set core refresh.schedule="$(date +%a --date=2days)@12:00-14:00" snap set core refresh.disabled=true fi @@ -22,7 +22,7 @@ echo "Install a snap from stable" snap install test-snapd-tools snap list | MATCH 'test-snapd-tools +[0-9]+\.[0-9]+' - if [ -e "$SNAPMOUNTDIR/core/current/meta/hooks/configure" ]; then + if [ -e "$SNAP_MOUNT_DIR/core/current/meta/hooks/configure" ]; then snap set core refresh.schedule="0:00-23:59" snap set core refresh.disabled=false fi diff -Nru snapd-2.27.5/tests/main/base-snaps/task.yaml snapd-2.28.5/tests/main/base-snaps/task.yaml --- snapd-2.27.5/tests/main/base-snaps/task.yaml 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/main/base-snaps/task.yaml 2017-09-22 07:00:26.000000000 +0000 @@ -0,0 +1,47 @@ +summary: Check that base snaps work + +execute: | + . $TESTSLIB/snaps.sh + + echo "Ensure a snap that requires a unavailable base snap can not be installed" + snapbuild $TESTSLIB/snaps/test-snapd-requires-base . + if install_local test-snapd-requires-base; then + echo "ERROR: test-snapd-requires-base should not be installable without test-snapd-base" + exit 1 + fi + + echo "Ensure a base snap can be installed" + snapbuild $TESTSLIB/snaps/test-snapd-base . + install_local test-snapd-base + snap list | MATCH test-snapd-base + + echo "With test-snapd-base installed we now can install test-snapd-requires-base" + install_local test-snapd-requires-base + snap list | MATCH test-snapd-requires-base + + + echo "Ensure the bare base works" + + if [ "$(uname -m)" != "x86_64" ]; then + echo "This test can only run on amd64 right now because snapcraft " + echo "cannot current generate binaries without wrapper scripts." + echo "Check: https://github.com/snapcore/snapcraft/pull/1420" + echo "and: https://code.launchpad.net/~snappy-dev/snappy-hub/test-snapd-busybox-static" + exit 0 + fi + + # FIXME: we need to pull in test-snapd-base-bare explicitly here for now + # once snapd can do that on its own we can remove this + # This will be fixed via: + # https://github.com/mvo5/snappy/tree/ensure-core-in-snapstate-with-bases + # + snap install --edge test-snapd-base-bare + snap install --edge --devmode test-snapd-busybox-static + echo "Ensure we can run a statically linked binary from an empty base" + test-snapd-busybox-static.busybox-static echo hello | MATCH hello + + if test-snapd-busybox-static.busybox-static ls /bin/dd; then + echo "bare should be empty but it is not:" + test-snapd-busybox-static.busybox-static ls /bin + exit 1 + fi \ No newline at end of file diff -Nru snapd-2.27.5/tests/main/classic-confinement/task.yaml snapd-2.28.5/tests/main/classic-confinement/task.yaml --- snapd-2.27.5/tests/main/classic-confinement/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/classic-confinement/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -3,8 +3,11 @@ environment: CLASSIC_SNAP: test-snapd-classic-confinement +# Classic confinement isn't working yet on Fedora +systems: [-ubuntu-core-*, -fedora-*] + prepare: | - . "$TESTSLIB/snaps.sh" + . $TESTSLIB/snaps.sh snapbuild "$TESTSLIB/snaps/$CLASSIC_SNAP/" . execute: | @@ -19,14 +22,6 @@ exit 1 fi - if [[ "$SPREAD_SYSTEM" =~ core ]]; then - echo "Check that the classic snap is not installable even with --classic" - ( snap install --dangerous --classic "${CLASSIC_SNAP}_1.0_all.snap" 2>&1 && exit 1 || true ) | MATCH $'cannot install snap file: classic confinement is only supported on classic\n *systems' - echo "Not from the store either" - ( snap install --classic "$CLASSIC_SNAP" 2>&1 && exit 1 || true ) | MATCH "requires classic confinement" - - exit 0 - fi echo "Check that the classic snap works (it skips the entire sandbox)" snap install --dangerous --classic "${CLASSIC_SNAP}_1.0_all.snap" touch /tmp/lala diff -Nru snapd-2.27.5/tests/main/classic-confinement-not-supported/task.yaml snapd-2.28.5/tests/main/classic-confinement-not-supported/task.yaml --- snapd-2.27.5/tests/main/classic-confinement-not-supported/task.yaml 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/main/classic-confinement-not-supported/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,38 @@ +summary: Ensure that classic confinement works + +environment: + CLASSIC_SNAP: test-snapd-classic-confinement + +systems: [ubuntu-core-*, fedora-*] + +prepare: | + . $TESTSLIB/snaps.sh + snapbuild "$TESTSLIB/snaps/$CLASSIC_SNAP/" . + +execute: | + . $TESTSLIB/strings.sh + + echo "Check that classic snaps work only with --classic" + if snap install --dangerous "${CLASSIC_SNAP}_1.0_all.snap"; then + echo "snap install needs --classic to install local snaps with classic confinment" + exit 1 + fi + + if snap install $CLASSIC_SNAP; then + echo "snap install needs --classic to install remote snaps with classic confinment" + exit 1 + fi + + echo "Check that the classic snap is not installable even with --classic" + EXPECTED_TEXT="cannot install snap file: classic confinement is only supported on classic systems" + if [[ "$SPREAD_SYSTEM" = fedora-* ]]; then + EXPECTED_TEXT="classic confinement requires snaps under /snap or symlink from /snap to /var/lib/snapd/snap" + fi + str_to_one_line "$( snap install --dangerous --classic "${CLASSIC_SNAP}_1.0_all.snap" 2>&1 && exit 1 || true )" | MATCH "$EXPECTED_TEXT" + + echo "Not from the store either" + EXPECTED_TEXT="snap \"$CLASSIC_SNAP\" requires classic confinement which is only available on classic systems" + if [[ "$SPREAD_SYSTEM" = fedora-* ]]; then + EXPECTED_TEXT="cannot install \"$CLASSIC_SNAP\": classic confinement requires snaps under /snap or symlink from /snap to /var/lib/snapd/snap" + fi + str_to_one_line "$( snap install --classic "$CLASSIC_SNAP" 2>&1 && exit 1 || true )" | MATCH "$EXPECTED_TEXT" diff -Nru snapd-2.27.5/tests/main/classic-ubuntu-core-transition/task.yaml snapd-2.28.5/tests/main/classic-ubuntu-core-transition/task.yaml --- snapd-2.27.5/tests/main/classic-ubuntu-core-transition/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/classic-ubuntu-core-transition/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -2,7 +2,9 @@ # we never test on core because the transition can only happen on "classic" # we disable on ppc64el because the downloads are very slow there -systems: [-ubuntu-core-16-*, -ubuntu-*-ppc64el] +# Both Fedora and openSUSE are disabled at the moment as there is something +# fishy going on and the snapd service gets terminated during the process. +systems: [-ubuntu-core-16-*, -ubuntu-*-ppc64el, -fedora-*, -opensuse-*] warn-timeout: 1m kill-timeout: 5m @@ -10,16 +12,13 @@ restore: | rm -f state.json.new +debug: | + snap changes + . "$TESTSLIB/changes.sh" + snap change "$(change_id 'Transition ubuntu-core to core')" || true execute: | . "$TESTSLIB/pkgdb.sh" - - wait_for_service() { - local service_name="$1" - local state="${2:-active}" - while ! systemctl show -p ActiveState "$service_name" | grep -q "ActiveState=$state"; do - systemctl status "$service_name" || true; sleep 1; - done - } + . $TESTSLIB/systemd.sh curl() { local url="$1" # sadly systemd active means not that its really ready so we wait @@ -64,14 +63,34 @@ systemctl start snapd.{service,socket} echo "Ensure transition is triggered" + # wait for steady state or ensure-state-soon will be pointless + ok=0 + for i in $(seq 40); do + if ! snap changes|grep -q ".*.Doing.*" ; then + ok=1 + break + fi + sleep .5 + done + if [ $ok -ne 1 ] ; then + echo "Did not reach steady state" + exit 1 + fi snap debug ensure-state-soon - . "$TESTSLIB/changes.sh" - while ! snap changes|grep ".*Done.*Transition ubuntu-core to core"; do - snap changes - snap change "$(change_id 'Transition ubuntu-core to core')" || true + # wait for transition + ok=0 + for i in $(seq 240); do + if snap changes|grep -q ".*Done.*Transition ubuntu-core to core" ; then + ok=1 + break + fi sleep 1 done + if [ $ok -ne 1 ] ; then + echo "Transition did not start or finish" + exit 1 + fi if snap list|grep ubuntu-core; then echo "ubuntu-core still installed, transition failed" diff -Nru snapd-2.27.5/tests/main/classic-ubuntu-core-transition-auth/task.yaml snapd-2.28.5/tests/main/classic-ubuntu-core-transition-auth/task.yaml --- snapd-2.27.5/tests/main/classic-ubuntu-core-transition-auth/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/classic-ubuntu-core-transition-auth/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -2,10 +2,16 @@ # we never test on core because the transition can only happen on "classic" # we disable on ppc64el because the downloads are very slow there -systems: [-ubuntu-core-16-*, -ubuntu-*-ppc64el] +# Both Fedora and openSUSE are disabled at the moment as there is something +# fishy going on and the snapd service gets terminated during the process. +systems: [-ubuntu-core-16-*, -ubuntu-*-ppc64el, -fedora-*, -opensuse-*] warn-timeout: 1m kill-timeout: 5m +debug: | + snap changes + . "$TESTSLIB/changes.sh" + snap change "$(change_id 'Transition ubuntu-core to core')" || true execute: | . "$TESTSLIB/pkgdb.sh" echo "Ensure core is gone and we have ubuntu-core instead" @@ -22,12 +28,35 @@ echo '{}' > /home/test/.snap/auth.json echo "Ensure transition is triggered" + # wait for steady state or ensure-state-soon will be pointless + ok=0 + for i in $(seq 40); do + if ! snap changes|grep -q ".*.Doing.*" ; then + ok=1 + break + fi + sleep .5 + done + if [ $ok -ne 1 ] ; then + echo "Did not reach steady state" + exit 1 + fi snap debug ensure-state-soon - while ! snap changes|grep ".*Done.*Transition ubuntu-core to core"; do - snap changes + + # wait for transition + ok=0 + for i in $(seq 240); do + if snap changes|grep -q ".*Done.*Transition ubuntu-core to core" ; then + ok=1 + break + fi sleep 1 done + if [ $ok -ne 1 ] ; then + echo "Transition did not start or finish" + exit 1 + fi if snap list|grep ubuntu-core; then echo "ubuntu-core still installed, transition failed" diff -Nru snapd-2.27.5/tests/main/classic-ubuntu-core-transition-two-cores/task.yaml snapd-2.28.5/tests/main/classic-ubuntu-core-transition-two-cores/task.yaml --- snapd-2.27.5/tests/main/classic-ubuntu-core-transition-two-cores/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/classic-ubuntu-core-transition-two-cores/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -6,11 +6,11 @@ warn-timeout: 1m kill-timeout: 5m +debug: | + snap changes + . $TESTSLIB/changes.sh + snap change "$(change_id 'Transition ubuntu-core to core')" || true execute: | - . "$TESTSLIB/pkgdb.sh" - echo "Ensure we have two cores" - distro_install_package jq - echo "install a snap" snap install test-snapd-python-webserver snap interfaces |MATCH ":network.*test-snapd-python-webserver" @@ -33,14 +33,34 @@ snap list | MATCH "core " echo "Ensure transition is triggered" + # wait for steady state or ensure-state-soon will be pointless + ok=0 + for i in $(seq 40); do + if ! snap changes|grep -q ".*.Doing.*" ; then + ok=1 + break + fi + sleep .5 + done + if [ $ok -ne 1 ] ; then + echo "Did not reach steady state" + exit 1 + fi snap debug ensure-state-soon - . $TESTSLIB/changes.sh - while ! snap changes|grep ".*Done.*Transition ubuntu-core to core"; do - snap changes - snap change $(change_id "Transition ubuntu-core to core")||true + # wait for transition + ok=0 + for i in $(seq 240); do + if snap changes|grep -q ".*Done.*Transition ubuntu-core to core" ; then + ok=1 + break + fi sleep 1 done + if [ $ok -ne 1 ] ; then + echo "Transition did not start or finish" + exit 1 + fi if ! snap list|MATCH -v ubuntu-core; then echo "ubuntu-core still installed, transition failed" diff -Nru snapd-2.27.5/tests/main/confinement-classic/task.yaml snapd-2.28.5/tests/main/confinement-classic/task.yaml --- snapd-2.27.5/tests/main/confinement-classic/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/confinement-classic/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -1,10 +1,14 @@ summary: trivial snap with classic confinement runs correctly + +# Classic confinement isn't working yet on Fedora +systems: [-ubuntu-core-16-*, -fedora-*] + details: | This test checks that a very much trivial "hello-world"-like snap using classic confinement can be executed correctly. There are two variants of this test (classic and jailmode) and the snap (this particular one) should function correctly in both cases. -systems: [-ubuntu-core-16-*] + execute: | . $TESTSLIB/dirs.sh @@ -15,7 +19,7 @@ } run_install --classic - $SNAPMOUNTDIR/bin/test-snapd-hello-classic | MATCH 'Hello Classic!' + $SNAP_MOUNT_DIR/bin/test-snapd-hello-classic | MATCH 'Hello Classic!' if [ "$(snap debug confinement)" = partial ]; then exit 0 @@ -23,7 +27,7 @@ # Installing again will increase revision and put the snap into jailmode run_install --classic --jailmode - $SNAPMOUNTDIR/bin/test-snapd-hello-classic | MATCH 'Hello Classic!' + $SNAP_MOUNT_DIR/bin/test-snapd-hello-classic | MATCH 'Hello Classic!' restore: | make -C test-snapd-hello-classic clean diff -Nru snapd-2.27.5/tests/main/confinement-classic/test-snapd-hello-classic/Makefile snapd-2.28.5/tests/main/confinement-classic/test-snapd-hello-classic/Makefile --- snapd-2.27.5/tests/main/confinement-classic/test-snapd-hello-classic/Makefile 2017-01-12 09:32:51.000000000 +0000 +++ snapd-2.28.5/tests/main/confinement-classic/test-snapd-hello-classic/Makefile 2017-09-13 14:47:18.000000000 +0000 @@ -7,6 +7,11 @@ ifeq ($(arch),x86_64-linux-gnu) snap_arch = amd64 dynamic_linker=ld-linux-x86-64.so.2 +else ifeq ($(arch),x86_64-suse-linux) +# NOTE: arch needs to be x86_64-linux-gnu, not x86_64-suse-linux +arch := x86_64-linux-gnu +snap_arch = amd64 +dynamic_linker=ld-linux-x86-64.so.2 else ifeq ($(arch),i686-linux-gnu) # NOTE: arch needs to be i386-linux-gnu, not i686-linux-gnu arch := i386-linux-gnu diff -Nru snapd-2.27.5/tests/main/create-user/task.yaml snapd-2.28.5/tests/main/create-user/task.yaml --- snapd-2.27.5/tests/main/create-user/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/create-user/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -1,6 +1,8 @@ summary: Ensure create-user functionality -systems: [-ubuntu-core-16-*] +# Disabled for Fedora, openSUSE as both have not all options for add user +# available the `snap create-user` command requires. Needs code rework. +systems: [-ubuntu-core-16-*, -fedora-*, -opensuse-*] environment: USER_EMAIL: mvo@ubuntu.com diff -Nru snapd-2.27.5/tests/main/debs-have-built-using/task.yaml snapd-2.28.5/tests/main/debs-have-built-using/task.yaml --- snapd-2.27.5/tests/main/debs-have-built-using/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/debs-have-built-using/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -1,5 +1,7 @@ summary: Ensure that our debs have the "built-using" header -systems: [-ubuntu-core-*] + +systems: [-ubuntu-core-*, -fedora-*, -opensuse-*] + execute: | out=$(dpkg -I $GOHOME/snapd_*.deb) if [[ "$SPREAD_SYSTEM" = ubuntu-* ]]; then diff -Nru snapd-2.27.5/tests/main/dirs-not-shared-with-host/task.yaml snapd-2.28.5/tests/main/dirs-not-shared-with-host/task.yaml --- snapd-2.27.5/tests/main/dirs-not-shared-with-host/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/dirs-not-shared-with-host/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -19,10 +19,10 @@ . "$TESTSLIB/dirs.sh" echo "We can check the inode number of $DIRECTORY" host_inode="$(stat -c '%i' $DIRECTORY)" - if [ -e $SNAPMOUNTDIR/core/current ]; then - core_inode="$(stat -c '%i' $SNAPMOUNTDIR/core/current/$DIRECTORY)" + if [ -e $SNAP_MOUNT_DIR/core/current ]; then + core_inode="$(stat -c '%i' $SNAP_MOUNT_DIR/core/current/$DIRECTORY)" else - core_inode="$(stat -c '%i' $SNAPMOUNTDIR/ubuntu-core/current/$DIRECTORY)" + core_inode="$(stat -c '%i' $SNAP_MOUNT_DIR/ubuntu-core/current/$DIRECTORY)" fi effective_inode="$(test-snapd-tools.cmd stat -c '%i' $DIRECTORY)" echo "The inode number as seen from a confined snap should be that of the $DIRECTORY from the core snap" diff -Nru snapd-2.27.5/tests/main/econnreset/task.yaml snapd-2.28.5/tests/main/econnreset/task.yaml --- snapd-2.27.5/tests/main/econnreset/task.yaml 2017-08-24 06:52:40.000000000 +0000 +++ snapd-2.28.5/tests/main/econnreset/task.yaml 2017-10-13 07:54:42.000000000 +0000 @@ -10,7 +10,7 @@ su -c "/usr/bin/env SNAPD_DEBUG=1 snap download --edge test-snapd-huge 2>snap-download.log" test & echo "Wait until the download started and downloaded more than 1 MB" - for i in $(seq 20); do + for i in $(seq 40); do if partial=$(ls test-snapd-huge_*.snap.partial | head -1); then if [ $(stat -c%s "$partial") -gt $(( 1024 * 1024 )) ]; then break @@ -21,6 +21,7 @@ if [ ! -f "$partial" ] || [ $(stat -c%s "$partial") -eq 0 ]; then echo "Partial file $partial did not start downloading, test broken" + kill -9 $(pidof snap) exit 1 fi diff -Nru snapd-2.27.5/tests/main/enable-disable/task.yaml snapd-2.28.5/tests/main/enable-disable/task.yaml --- snapd-2.27.5/tests/main/enable-disable/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/enable-disable/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -10,7 +10,7 @@ snap disable test-snapd-tools|MATCH disabled echo "Ensure the test-snapd-tools command is no longer there" - if ls $SNAPMOUNTDIR/bin/test-snapd-tools*; then + if ls $SNAP_MOUNT_DIR/bin/test-snapd-tools*; then echo "test-snapd-tools binaries are not disabled" exit 1 fi diff -Nru snapd-2.27.5/tests/main/failover/task.yaml snapd-2.28.5/tests/main/failover/task.yaml --- snapd-2.27.5/tests/main/failover/task.yaml 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/tests/main/failover/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -21,10 +21,14 @@ TARGET_SNAP/rclocalcrash: core TARGET_SNAP/emptysystemd: core #TARGET_SNAP/emptyinitrd: kernel + BUILD_DIR: /home/tmp + +prepare: | + mkdir -p $BUILD_DIR restore: | rm -f failing.snap failBoot currentBoot prevBoot - rm -rf /tmp/unpack + rm -rf $BUILD_DIR # FIXME: remove the unset when we reset properly snap_try_{core,kernel} on rollback . $TESTSLIB/boot.sh @@ -39,19 +43,19 @@ execute: | inject_rclocalcrash_failure(){ - chmod a+x /tmp/unpack/etc/rc.local - cat < /tmp/unpack/etc/rc.local + chmod a+x $BUILD_DIR/unpack/etc/rc.local + cat < $BUILD_DIR/unpack/etc/rc.local #!bin/sh printf c > /proc/sysrq-trigger EOF } inject_emptysystemd_failure(){ - truncate -s 0 /tmp/unpack/lib/systemd/systemd + truncate -s 0 $BUILD_DIR/unpack/lib/systemd/systemd } inject_emptyinitrd_failure(){ - truncate -s 0 /tmp/unpack/initrd.img + truncate -s 0 $BUILD_DIR/unpack/initrd.img } . $TESTSLIB/names.sh @@ -67,13 +71,13 @@ snap list | awk "/^${TARGET_SNAP_NAME} / {print(\$3)}" > prevBoot # unpack current target snap - unsquashfs -d /tmp/unpack /var/lib/snapd/snaps/${TARGET_SNAP_NAME}_$(cat prevBoot).snap + unsquashfs -d $BUILD_DIR/unpack /var/lib/snapd/snaps/${TARGET_SNAP_NAME}_$(cat prevBoot).snap # set failure condition eval ${INJECT_FAILURE} # repack new target snap - snapbuild /tmp/unpack . && mv ${TARGET_SNAP_NAME}_*.snap failing.snap + snapbuild $BUILD_DIR/unpack . && mv ${TARGET_SNAP_NAME}_*.snap failing.snap # install new target snap chg_id=$(snap install --dangerous failing.snap --no-wait) diff -Nru snapd-2.27.5/tests/main/generic-classic-reg/task.yaml snapd-2.28.5/tests/main/generic-classic-reg/task.yaml --- snapd-2.27.5/tests/main/generic-classic-reg/task.yaml 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/main/generic-classic-reg/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,26 @@ +summary: | + Ensure device initialisation registration works with the fallback + generic/generi-classic model and we have a serial and can acquire + a session macaroon +systems: [-ubuntu-core-16-*] +execute: | + echo "Wait for device initialisation to have been done" + # this was in fact triggered around when core was installed + while ! snap changes | grep -q "Done.*Initialize device"; do sleep 1; done + + echo "We have a model assertion" + snap known model|MATCH "series: 16" + + if ! snap known model|grep "brand-id: generic" ; then + echo "Not a generic model. Skipping." + exit 0 + fi + + echo "Check we have a serial" + snap known serial|MATCH "authority-id: generic" + snap known serial|MATCH "brand-id: generic" + snap known serial|MATCH "model: generic-classic" + + echo "Make sure we could acquire a session macaroon" + snap find pc + MATCH '"session-macaroon":"[^"]' < /var/lib/snapd/state.json diff -Nru snapd-2.27.5/tests/main/install-hook/task.yaml snapd-2.28.5/tests/main/install-hook/task.yaml --- snapd-2.27.5/tests/main/install-hook/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/install-hook/task.yaml 2017-10-04 14:43:22.000000000 +0000 @@ -14,11 +14,20 @@ snap get snap-hooks installed | MATCH 1 snap get snap-hooks foo | MATCH bar + echo "Verify that post-refresh hook was not executed" + if snap get snap-install-hooks refreshed; then + echo "'refreshed' config value not expected on first install" + exit 1 + fi + echo "Verify that install hook is run only once" snap set snap-hooks installed=2 install_local snap-hooks snap get snap-hooks installed | MATCH 2 + echo "Verify that post-refresh hook was executed" + snap get snap-hooks refreshed | MATCH 1 + snap connect snap-hooks:home echo "Verify that remove hook is not executed when removing single revision" diff -Nru snapd-2.27.5/tests/main/install-sideload/task.yaml snapd-2.28.5/tests/main/install-sideload/task.yaml --- snapd-2.27.5/tests/main/install-sideload/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/install-sideload/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -40,7 +40,7 @@ expected="\[Desktop Entry\]\n\ Name=Echo\n\ Comment=It echos stuff\n\ - Exec=env BAMF_DESKTOP_FILE_HINT=/var/lib/snapd/desktop/applications/basic-desktop_echo.desktop $SNAPMOUNTDIR/bin/basic-desktop.echo\n" + Exec=env BAMF_DESKTOP_FILE_HINT=/var/lib/snapd/desktop/applications/basic-desktop_echo.desktop $SNAP_MOUNT_DIR/bin/basic-desktop.echo\n" cat /var/lib/snapd/desktop/applications/basic-desktop_echo.desktop | grep -Pzq "$expected" echo "Sideload devmode snap fails without flags" @@ -74,4 +74,4 @@ echo "Remove --revision works" snap remove --revision x1 test-snapd-tools test-snapd-tools.success - test ! -d $SNAPMOUNTDIR/test-snapd-tools/x1 + test ! -d $SNAP_MOUNT_DIR/test-snapd-tools/x1 diff -Nru snapd-2.27.5/tests/main/install-snaps/task.yaml snapd-2.28.5/tests/main/install-snaps/task.yaml --- snapd-2.27.5/tests/main/install-snaps/task.yaml 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/main/install-snaps/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,99 @@ +summary: Check install popular snaps + +details: | + This test is intended to install some popular snaps from + different channels. The idea is to detect any problem + installing that are currently published and some of them + with many revisions. + The execution of this test is made on the nightly build. + +manual: true + +environment: + # High Profile + SNAP/azurecli: azure-cli + SNAP/awscli: aws-cli + SNAP/heroku: heroku + SNAP/hiri: hiri + SNAP/kubectl: kubectl + SNAP/rocketchatserver: rocketchat-server + # Selected from recent insights posts + SNAP/corebird: corebird + SNAP/gitterdesktop: gitter-desktop + SNAP/helm: helm + SNAP/mattermostdesktop: mattermost-desktop + SNAP/mentaplexmediaserver: menta-plexmediaserver + SNAP/openspades: openspades + SNAP/pintown: pin-town + SNAP/postgresql10: postgresql10 + SNAP/storjshare: storjshare + SNAP/slackterm: slack-term + SNAP/vectr: vectr + SNAP/wekan: wekan + SNAP/wormhole: wormhole + # Featured snaps in Ubuntu Software + SNAP/anboxinstaller: anbox-installer + SNAP/lxd: lxd + # Top non canonical snaps + SNAP/atom: atom + SNAP/discord: discord + SNAP/docker: docker + SNAP/etcd: etcd + SNAP/geocoder: geocoder + SNAP/gimp: gimp + SNAP/huggle: huggle + SNAP/hugo: hugo + SNAP/ia: ia + SNAP/kurly: kurly + SNAP/micro: micro + SNAP/nikola: nikola + SNAP/parity: parity + SNAP/paritybitcoin: parity-bitcoin + SNAP/remmina: remmina + SNAP/telegramsergiusens: telegram-sergiusens + SNAP/zeronet: zeronet + SNAP/zeronetjs: zeronet-js + # Top canonical snaps + SNAP/bare: bare + SNAP/bluez: bluez + SNAP/conjureup: conjure-up + SNAP/gedit: gedit + SNAP/go: go + SNAP/juju: juju + SNAP/neutron: neutron + SNAP/nova: nova + SNAP/snapcraft: snapcraft + SNAP/solc: solc + SNAP/vault: vault + +execute: | + . "$TESTSLIB/snaps.sh" + + CHANNELS="stable candidate beta edge" + for CHANNEL in $CHANNELS; do + if ! CHANNEL_INFO="$(snap info $SNAP | grep " $CHANNEL: ")"; then + echo "Snap $SNAP not found" + exit + fi + if echo $CHANNEL_INFO | MATCH "$CHANNEL:.*–"; then + continue + fi + + if echo $CHANNEL_INFO | MATCH "$CHANNEL:.*classic"; then + if is_classic_confinement_supported; then + snap install $SNAP --$CHANNEL --classic + else + echo "The snap $SNAP requires classic confinement which is not supported yet" + exit + fi + elif echo $CHANNEL_INFO | MATCH "$CHANNEL:.*jailmode"; then + snap install $SNAP --$CHANNEL --jailmode + elif echo $CHANNEL_INFO | MATCH "$CHANNEL:.*devmode"; then + snap install $SNAP --$CHANNEL --devmode + else + snap install $SNAP --$CHANNEL + fi + break + done + + snap list | MATCH $SNAP diff -Nru snapd-2.27.5/tests/main/install-store/task.yaml snapd-2.28.5/tests/main/install-store/task.yaml --- snapd-2.27.5/tests/main/install-store/task.yaml 2017-08-29 13:51:54.000000000 +0000 +++ snapd-2.28.5/tests/main/install-store/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -1,6 +1,6 @@ summary: Checks for special cases of snap install from the store -systems: [ubuntu-core-16-*] +systems: [ubuntu-*] environment: SNAP_NAME: test-snapd-tools @@ -37,6 +37,5 @@ actual=$(snap install --channel beta --devmode $DEVMODE_SNAP) echo "$actual" | grep -Pzq "$expected" - echo "Install network-manager and do basic smoke test" - snap install network-manager - network-manager.nmcli d show + echo "Install a snap that contains bash-completion scripts" + snap install --edge test-snapd-complexion diff -Nru snapd-2.27.5/tests/main/interfaces-account-control/task.yaml snapd-2.28.5/tests/main/interfaces-account-control/task.yaml --- snapd-2.27.5/tests/main/interfaces-account-control/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/interfaces-account-control/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -23,8 +23,8 @@ execute: | . $TESTSLIB/dirs.sh - $SNAPMOUNTDIR/bin/account-control-consumer.useradd --extrausers alice - echo alice:password | $SNAPMOUNTDIR/bin/account-control-consumer.chpasswd + $SNAP_MOUNT_DIR/bin/account-control-consumer.useradd --extrausers alice + echo alice:password | $SNAP_MOUNT_DIR/bin/account-control-consumer.chpasswd # User deletion is unsupported yet on Core: https://bugs.launchpad.net/ubuntu/+source/shadow/+bug/1659534 - # $SNAPMOUNTDIR/bin/account-control-consumer.userdel --extrausers alice + # $SNAP_MOUNT_DIR/bin/account-control-consumer.userdel --extrausers alice diff -Nru snapd-2.27.5/tests/main/interfaces-alsa/task.yaml snapd-2.28.5/tests/main/interfaces-alsa/task.yaml --- snapd-2.27.5/tests/main/interfaces-alsa/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/interfaces-alsa/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -1,5 +1,8 @@ summary: Ensure that the alsa interface works. +# Spread system for Fedora, openSUSE doesn't seem to provide any /dev/snd entries +systems: [-fedora-*, -opensuse-*] + details: | The alsa interface allows connected plugs to access raw ALSA devices. diff -Nru snapd-2.27.5/tests/main/interfaces-autopilot-introspection/task.yaml snapd-2.28.5/tests/main/interfaces-autopilot-introspection/task.yaml --- snapd-2.27.5/tests/main/interfaces-autopilot-introspection/task.yaml 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/main/interfaces-autopilot-introspection/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,85 @@ +summary: Ensure that the autopilot-introspection interface works + +details: | + The autopilot-intrspection interface allows an application to be introspected + and export its ui status over DBus. + + The test uses an snap that declares a plug on autopilot-intrsopection, it + needs to request a dbus name on start so that its state can be queried. + +systems: [-ubuntu-core-16-*] + +prepare: | + . "$TESTSLIB/dirs.sh" + + echo "Given a snap declaring an autopilot-intrspection plug in installed" + snap install --edge test-snapd-autopilot-consumer + + echo "And the provider dbus loop is started" + . "$TESTSLIB/dbus.sh" + start_dbus_unit $SNAP_MOUNT_DIR/bin/test-snapd-autopilot-consumer.provider + +restore: | + rm -f *.error + . "$TESTSLIB/dbus.sh" + stop_dbus_unit + +execute: | + . "$TESTSLIB/dirs.sh" + + dbus_send(){ + local method="$1" + echo $(dbus-send --print-reply --dest=com.canonical.Autopilot.Introspection /com/canonical/Autopilot/Introspection com.canonical.Autopilot.Introspection.${method}) + } + + CONNECTED_PATTERN=":autopilot-introspection +test-snapd-autopilot-consumer" + DISCONNECTED_PATTERN="^\- +test-snapd-autopilot-consumer:autopilot-introspection" + + export $(cat dbus.env) + + echo "Then the plug is disconnected by default" + snap interfaces | MATCH "$DISCONNECTED_PATTERN" + + echo "When the plug is connected" + snap connect test-snapd-autopilot-consumer:autopilot-introspection + snap interfaces | MATCH "$CONNECTED_PATTERN" + + echo "Then the dbus name is properly reserved and the snap app version can be introspected" + + for i in $(seq 10); do + if ! dbus_send GetVersion | MATCH "my-ap-version"; then + sleep 1 + else + break + fi + done + $SNAP_MOUNT_DIR/bin/test-snapd-autopilot-consumer.consumer GetVersion | MATCH "my-ap-version" + + echo "And the snap app state can be intrsopected" + $SNAP_MOUNT_DIR/bin/test-snapd-autopilot-consumer.consumer GetState | MATCH "my-ap-state" + + if [ "$(snap debug confinement)" = none ]; then + exit 0 + fi + + echo "=================================" + + if [ "$(snap debug confinement)" = strict ] ; then + echo "When the plug is disconnected" + snap disconnect test-snapd-autopilot-consumer:autopilot-introspection + snap interfaces | MATCH "$DISCONNECTED_PATTERN" + + echo "Then the snap version is not introspectable" + if $SNAP_MOUNT_DIR/bin/test-snapd-autopilot-consumer.consumer GetVersion 2>${PWD}/getversion.error ; then + echo "Expected permission error trying to introspect version with disconnected plug" + exit 1 + fi + MATCH "Permission denied" < getversion.error + + echo "And the snap state is not introspectable" + if $SNAP_MOUNT_DIR/bin/test-snapd-autopilot-consumer.consumer GetState 2>${PWD}/getstate.error; then + echo "Expected permission error trying to introspect state with disconnected plug" + exit 1 + fi + MATCH "Permission denied" < getstate.error + fi diff -Nru snapd-2.27.5/tests/main/interfaces-avahi-observe/task.yaml snapd-2.28.5/tests/main/interfaces-avahi-observe/task.yaml --- snapd-2.27.5/tests/main/interfaces-avahi-observe/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/interfaces-avahi-observe/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -1,6 +1,6 @@ summary: check that avahi-observe interface works -systems: [-ubuntu-core-*] +systems: [-ubuntu-core-*, -fedora-*, -opensuse-*] prepare: | echo "Given a snap with an avahi-observe interface plug is installed" @@ -40,7 +40,7 @@ echo "Expected error with disconnected plug didn't happen" exit 1 fi - cat avahi.error | MATCH "org.freedesktop.DBus.Error.AccessDenied" + MATCH "org.freedesktop.DBus.Error.AccessDenied" < avahi.error fi echo "When the plug is connected" diff -Nru snapd-2.27.5/tests/main/interfaces-browser-support/task.yaml snapd-2.28.5/tests/main/interfaces-browser-support/task.yaml --- snapd-2.27.5/tests/main/interfaces-browser-support/task.yaml 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/main/interfaces-browser-support/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,154 @@ +summary: Check that the browser-support interface works + +environment: + ALLOW_SANDBOX/allow: true + ALLOW_SANDBOX/disallow: false + OWNED_FILES: "/var/tmp/etilqs_test" + READABLE_FILES: + /run/udev/data/+platform:test + /etc/opt/chrome/test + READABLE_WITH_SANDBOX_FILES: + /run/udev/data/c1:1-test + /run/udev/data/c10:1-test + /run/udev/data/c13:1-test + /run/udev/data/c180:1-test + /run/udev/data/c4:1-test + /run/udev/data/c5:1-test + /run/udev/data/c7:1-test + /run/udev/data/+hid:test + /run/udev/data/+input:input1-test + /run/udev/data/c29:1-test + /run/udev/data/+backlight:test + /run/udev/data/+leds:test + /run/udev/data/c116:1-test + /run/udev/data/+sound:card1-test + /run/udev/data/c108:1-test + /run/udev/data/c189:1-test + /run/udev/data/c89:1-test + /run/udev/data/c81:1-test + /run/udev/data/+acpi:test + /run/udev/data/+hwmon:hwmon1-test + /run/udev/data/+i2c:test + +prepare: | + echo "Given a snap declaring a plug on browser-support with allow-sandbox set to $ALLOW_SANDBOX is installed" + cp -ar "$TESTSLIB/snaps/browser-support-consumer" . + sed "s/@ALLOW_SANDBOX@/$ALLOW_SANDBOX/" browser-support-consumer/meta/snap.yaml.in > browser-support-consumer/meta/snap.yaml + snapbuild browser-support-consumer browser-support-consumer + snap install --dangerous browser-support-consumer/*.snap + rm -rf browser-support-consumer + +restore: | + rm -f *.error /var/tmp/test + for file in $OWNED_FILES $READABLE_FILES $READABLE_WITH_SANDBOX_FILES; do + rm -f $file + done + + for dir in $(cat created_dirs); do + rm -rf $dir + done + rm -f created_dirs + +execute: | + . "$TESTSLIB/dirs.sh" + + CONNECTED_PATTERN=":browser-support +browser-support-consumer" + DISCONNECTED_PATTERN="^\- +browser-support-consumer:browser-support" + + echo "Then the plug is connected by default" + snap interfaces | MATCH "$CONNECTED_PATTERN" + + echo "And the snap is able to access tmp" + echo "test" > /var/tmp/test + su -l -c "$SNAP_MOUNT_DIR/bin/browser-support-consumer.cmd ls /var/tmp/" test | MATCH test + + echo "And the snap is able to access owned files" + for owned_file in $OWNED_FILES; do + echo "test" > "$owned_file" + chown test:12345 "$owned_file" + su -l -c "$SNAP_MOUNT_DIR/bin/browser-support-consumer.cmd cat $owned_file" test | MATCH test + su -l -c "$SNAP_MOUNT_DIR/bin/browser-support-consumer.cmd touch $owned_file" test + done + + echo "And the snap is able to access readable files" + for readable_file in $READABLE_FILES; do + parent_dir=$(dirname $readable_file) + if [ ! -d $parent_dir ]; then + if mkdir -p $parent_dir; then + echo "$parent_dir" >> created_dirs + else + echo "$parent_dir couldn't be created, write-only partition?" + continue + fi + fi + echo "test" > "$readable_file" + su -l -c "$SNAP_MOUNT_DIR/bin/browser-support-consumer.cmd cat $readable_file" test | MATCH test + done + + for readable_file in $READABLE_WITH_SANDBOX_FILES; do + parent_dir=$(dirname $readable_file) + if [ ! -d $parent_dir ]; then + mkdir -p $parent_dir + echo "$parent_dir" >> created_dirs + fi + echo "test" > "$readable_file" + done + + if [ "$ALLOW_SANDBOX" = "true" ]; then + for readable_file in $READABLE_WITH_SANDBOX_FILES; do + su -l -c "$SNAP_MOUNT_DIR/bin/browser-support-consumer.cmd cat $readable_file" test | MATCH test + done + fi + + if [ "$(snap debug confinement)" = strict ] ; then + echo "And the resources available with sandbox are not reachable without it" + if [ "$ALLOW_SANDBOX" = "false" ]; then + for readable_file in $READABLE_WITH_SANDBOX_FILES; do + if su -l -c "$SNAP_MOUNT_DIR/bin/browser-support-consumer.cmd cat $readable_file 2>${PWD}/readable-without-sandbox-read.err" test; then + echo "Expected error without sandbox didn't happen" + exit 1 + fi + MATCH "Permission denied" < readable-without-sandbox-read.err + done + fi + + echo "When the plug is disconnected" + snap disconnect browser-support-consumer:browser-support + snap interfaces | MATCH "$DISCONNECTED_PATTERN" + + echo "Then the snap is not able to access tmp" + if su -l -c "$SNAP_MOUNT_DIR/bin/browser-support-consumer.cmd ls /var/tmp/ 2>${PWD}/tmpdir-access.err" test; then + echo "Expected error with disconnected plug didn't happen" + exit 1 + fi + MATCH "Permission denied" < tmpdir-access.err + if su -l -c "$SNAP_MOUNT_DIR/bin/browser-support-consumer.cmd cat /var/tmp/etilqs_test 2>${PWD}/tmpfile-read.err" test; then + echo "Expected error with disconnected plug didn't happen" + exit 1 + fi + MATCH "Permission denied" < tmpfile-read.err + + for owned_file in $OWNED_FILES; do + if su -l -c "$SNAP_MOUNT_DIR/bin/browser-support-consumer.cmd cat $owned_file 2>${PWD}/owned-read.err" test; then + echo "Expected error with disconnected plug didn't happen" + exit 1 + fi + MATCH "Permission denied" < owned-read.err + done + for readable_file in $READABLE_FILES; do + if [ -f "$readable_file" ] && su -l -c "$SNAP_MOUNT_DIR/bin/browser-support-consumer.cmd cat $readable_file 2>${PWD}/readable-read.err" test; then + echo "Expected error with disconnected plug didn't happen" + exit 1 + fi + MATCH "Permission denied" < readable-read.err + done + if [ "$ALLOW_SANDBOX" = "true" ]; then + for readable_file in $READABLE_WITH_SANDBOX_FILES; do + if su -l -c "$SNAP_MOUNT_DIR/bin/browser-support-consumer.cmd cat $readable_file 2>${PWD}/readable-with-sandbox-read.err" test; then + echo "Expected error with disconnected plug didn't happen" + exit 1 + fi + MATCH "Permission denied" < readable-with-sandbox-read.err + done + fi + fi diff -Nru snapd-2.27.5/tests/main/interfaces-cups-control/task.yaml snapd-2.28.5/tests/main/interfaces-cups-control/task.yaml --- snapd-2.27.5/tests/main/interfaces-cups-control/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/interfaces-cups-control/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -1,7 +1,8 @@ summary: Ensure that the cups interface works. -systems: - - -ubuntu-core-16-* +# Default cups/cups-pdf configuration on these distributions isn't +# working yet without further tweaks. +systems: [-ubuntu-core-16-*, -opensuse-*, -fedora-*] details: | The cups-control interface allows a snap to access the locale configuration. @@ -23,25 +24,27 @@ echo "Given a snap declaring a cups plug is installed" snap install test-snapd-cups-control-consumer - echo "And the pdf printer is available" - . "$TESTSLIB/pkgdb.sh" - distro_install_package --no-install-recommends cups printer-driver-cups-pdf - if [[ "$SPREAD_SYSTEM" != ubuntu-14.04-* ]]; then # Not all distributions are starting the cups service directly after # the package was installed. + echo "Enabling cups service in case it is not enabled" if ! systemctl is-enabled cups ; then # We can't use --now as this isn't supported by all distributions systemctl enable cups + fi + echo "Starting cups service in case it is not active" + if ! systemctl is-active cups; then systemctl start cups fi fi restore: | - . "$TESTSLIB/pkgdb.sh" - distro_purge_package cups printer-driver-cups-pdf rm -rf $HOME/PDF $TEST_FILE print.error +debug: | + systemctl status cups || true + journalctl -u cpus || true + execute: | CONNECTED_PATTERN=":cups-control +test-snapd-cups-control-consumer" DISCONNECTED_PATTERN="(?s).*?\n- +test-snapd-cups-control-consumer:cups-control" diff -Nru snapd-2.27.5/tests/main/interfaces-dbus/task.yaml snapd-2.28.5/tests/main/interfaces-dbus/task.yaml --- snapd-2.27.5/tests/main/interfaces-dbus/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/interfaces-dbus/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -15,54 +15,27 @@ prepare: | . "$TESTSLIB/dirs.sh" - . "$TESTSLIB/pkgdb.sh" echo "Give a snap declaring a dbus slot in installed" snap install --edge test-snapd-dbus-provider - echo "And the D-bus X11 dependencies are installed" - distro_install_package dbus-x11 - echo "And a snap declaring a matching dbus plug is installed" snap install --edge test-snapd-dbus-consumer echo "And the provider dbus loop is started" - dbus-launch > dbus.env - export $(cat dbus.env | xargs) - if [[ "$SPREAD_SYSTEM" == ubuntu-14.04-* ]]; then - cat < /etc/init/dbus-provider.conf - env DISPLAY="$DISPLAY" - env DBUS_SESSION_BUS_ADDRESS="$DBUS_SESSION_BUS_ADDRESS" - env DBUS_SESSION_BUS_PID="$DBUS_SESSION_BUS_PID" - script - $SNAPMOUNTDIR/bin/test-snapd-dbus-provider.provider - end script - EOF - initctl reload-configuration - start dbus-provider - else - systemd-run --unit dbus-provider \ - --setenv=DISPLAY=$DISPLAY \ - --setenv=DBUS_SESSION_BUS_ADDRESS=$DBUS_SESSION_BUS_ADDRESS \ - --setenv=DBUS_SESSION_BUS_PID=$DBUS_SESSION_BUS_PID \ - $SNAPMOUNTDIR/bin/test-snapd-dbus-provider.provider - fi + . "$TESTSLIB/dbus.sh" + start_dbus_unit $SNAP_MOUNT_DIR/bin/test-snapd-dbus-provider.provider restore: | - . "$TESTSLIB/pkgdb.sh" - rm -f call.error dbus.env - distro_purge_package dbus-x11 - if [[ "$SPREAD_SYSTEM" == ubuntu-14.04-* ]]; then - stop dbus-provider - rm -f /etc/init/dbus-provider.conf - else - systemctl stop dbus-provider - fi + rm -f call.error + . "$TESTSLIB/dbus.sh" + stop_dbus_unit execute: | CONNECTED_PATTERN="test-snapd-dbus-provider:dbus-test +test-snapd-dbus-consumer" DISCONNECTED_PATTERN="^\- +test-snapd-dbus-consumer:dbus-test" - export $(cat dbus.env | xargs) + + export $(cat dbus.env) echo "Then the dbus name is properly reserved by the provider and the method is accessible" while ! dbus-send --print-reply --dest=com.dbustest.HelloWorld /com/dbustest/HelloWorld com.dbustest.HelloWorld.SayHello | MATCH "hello world"; do diff -Nru snapd-2.27.5/tests/main/interfaces-firewall-control/task.yaml snapd-2.28.5/tests/main/interfaces-firewall-control/task.yaml --- snapd-2.27.5/tests/main/interfaces-firewall-control/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/interfaces-firewall-control/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -1,5 +1,7 @@ summary: Ensure that the firewall-control interface works. +systems: [-fedora-*, -opensuse-*] + details: | The firewall-control interface allows a snap to configure the firewall. @@ -26,7 +28,7 @@ snap install --dangerous firewall-control-consumer_1.0_all.snap echo "And a service is listening" - printf "#!/bin/sh -e\nwhile true; do echo \"HTTP/1.1 200 OK\n\nok\n\" | nc -l -p $PORT -q 1; done" > $SERVICE_FILE + printf "#!/bin/sh -e\nwhile true; do echo \"HTTP/1.1 200 OK\n\nok\n\" | nc -l -p $PORT -w 1; done" > $SERVICE_FILE chmod a+x $SERVICE_FILE . "$TESTSLIB/systemd.sh" systemd_create_and_start_unit $SERVICE_NAME "$(readlink -f $SERVICE_FILE)" diff -Nru snapd-2.27.5/tests/main/interfaces-iio/task.yaml snapd-2.28.5/tests/main/interfaces-iio/task.yaml --- snapd-2.27.5/tests/main/interfaces-iio/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/interfaces-iio/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -42,7 +42,7 @@ echo "This test needs test keys to be trusted" exit fi - test "`$SNAPMOUNTDIR/bin/iio-consumer.read`" = "iio-0" + test "`$SNAP_MOUNT_DIR/bin/iio-consumer.read`" = "iio-0" - $SNAPMOUNTDIR/bin/iio-consumer.write "hello" - test "`$SNAPMOUNTDIR/bin/iio-consumer.read`" = "hello" + $SNAP_MOUNT_DIR/bin/iio-consumer.write "hello" + test "`$SNAP_MOUNT_DIR/bin/iio-consumer.read`" = "hello" diff -Nru snapd-2.27.5/tests/main/interfaces-kernel-module-control/task.yaml snapd-2.28.5/tests/main/interfaces-kernel-module-control/task.yaml --- snapd-2.27.5/tests/main/interfaces-kernel-module-control/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/interfaces-kernel-module-control/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -1,5 +1,11 @@ summary: Ensure that the kernel-module-control interface works. +systems: [-fedora-*, -opensuse-*] + +environment: + MODULE: minix + MODULE_PATH: /lib/modules/$(uname -r)/kernel/fs/$MODULE/$MODULE.ko + details: | The kernel-module-control interface allows insertion, removal and querying of modules. @@ -9,7 +15,7 @@ usual, must be able to be reconnected. A snap declaring a plug on this interface must be able to list the modules - loaded, insert and remove a module. For the test we use the binfmt_misc module. + loaded, insert and remove a module. For the test we use the $MODULE module. prepare: | echo "Given a snap declaring a plug on the kernel-module-control interface is installed" @@ -19,18 +25,16 @@ restore: | rm -f *.error - if lsmod | MATCH binfmt_misc && ! -f module_present; then - rmmod binfmt_misc + if lsmod | MATCH $MODULE && ! -f module_present; then + rmmod $MODULE elif [ -f module_present ]; then - insmod /lib/modules/$(uname -r)/kernel/fs/binfmt_misc.ko - fi - if [ -f package_present ]; then - apt install -y binfmt-support + insmod $MODULE_PATH fi - rm -f module_present package_present + rm -f module_present debug: | lsmod + ls -R /lib/modules/$(uname -r)/kernel/fs execute: | CONNECTED_PATTERN=":kernel-module-control +.*test-snapd-kernel-module-consumer" @@ -51,22 +55,15 @@ [ $(su -l -c "test-snapd-kernel-module-consumer.lsmod" test | wc -l) -gt 2 ] echo "And the snap is able to insert a module" - if lsmod | MATCH binfmt_misc; then + if lsmod | MATCH $MODULE; then touch module_present - if ! rmmod binfmt_misc; then - # the module is being used - if apt list --installed binfmt-support | MATCH binfmt-support; then - touch package_present - apt remove -y binfmt-support - rmmod binfmt_misc - fi - fi + rmmod minix fi - lsmod | MATCH -v binfmt_misc - test-snapd-kernel-module-consumer.insmod /lib/modules/$(uname -r)/kernel/fs/binfmt_misc.ko + lsmod | MATCH -v $MODULE + test-snapd-kernel-module-consumer.insmod $MODULE_PATH echo "And the snap is able to read /sys/module" - generic-consumer.cmd ls /sys/module | MATCH binfmt_misc + generic-consumer.cmd ls /sys/module | MATCH $MODULE echo "And the snap is not able to write to /sys/module" if su -l -c "generic-consumer.cmd touch /sys/module/test 2>${PWD}/touch.error" test; then @@ -76,8 +73,8 @@ cat touch.error | MATCH "Permission denied" echo "And the snap is able to remove a module" - test-snapd-kernel-module-consumer.rmmod binfmt_misc - lsmod | MATCH -v binfmt_misc + test-snapd-kernel-module-consumer.rmmod $MODULE + lsmod | MATCH -v $MODULE if [ "$(snap debug confinement)" = partial ] ; then exit 0 @@ -98,17 +95,17 @@ cat list.error | MATCH "Permission denied" echo "And the snap is not able to insert a module" - if test-snapd-kernel-module-consumer.insmod /lib/modules/$(uname -r)/kernel/fs/binfmt_misc.ko; then + if test-snapd-kernel-module-consumer.insmod $MODULE_PATH; then echo "Expected permission error inserting module with disconnected plug" exit 1 fi echo "And the snap is not able to remove a module" # first we need to insert the module - lsmod | MATCH -v binfmt_misc - insmod /lib/modules/$(uname -r)/kernel/fs/binfmt_misc.ko - lsmod | MATCH binfmt_misc - if test-snapd-kernel-module-consumer.rmmod binfmt_misc 2>${PWD}/remove.error; then + lsmod | MATCH -v $MODULE + insmod $MODULE_PATH + lsmod | MATCH $MODULE + if test-snapd-kernel-module-consumer.rmmod $MODULE 2>${PWD}/remove.error; then echo "Expected permission error removing module with disconnected plug" exit 1 fi diff -Nru snapd-2.27.5/tests/main/interfaces-libvirt/task.yaml snapd-2.28.5/tests/main/interfaces-libvirt/task.yaml --- snapd-2.27.5/tests/main/interfaces-libvirt/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/interfaces-libvirt/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -15,9 +15,7 @@ respond to ping. Once the domain is created, the test checks connectivity to the unikernel. prepare: | - echo "Given libvirt and qemu are installed" - apt install -y libvirt-bin qemu - # add test user to the libvirtd group + # Given test user is added to the libvirtd group adduser test libvirtd echo "And libvirt is configured to manage /dev/net/tun" @@ -37,8 +35,6 @@ ip link set dev tap100 up restore: | - apt autoremove -y --purge libvirt-bin qemu - ip link delete tap100 # remove test user from the libvirtd group diff -Nru snapd-2.27.5/tests/main/interfaces-locale-control/task.yaml snapd-2.28.5/tests/main/interfaces-locale-control/task.yaml --- snapd-2.27.5/tests/main/interfaces-locale-control/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/interfaces-locale-control/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -1,5 +1,7 @@ summary: Ensure that the locale-control interface works. +systems: [-fedora-*, -opensuse-*] + summary: | The locale-control interface allows a snap to access the locale configuration. @@ -8,7 +10,7 @@ reconnected. A snap declaring a plug on this interface must be able to access the /etc/default/locale - file both for reading and writing. + file both for reading and writing. This path doesn't exist on the excluded distributions. prepare: | if [[ "$SPREAD_SYSTEM" = ubuntu-core-* ]]; then diff -Nru snapd-2.27.5/tests/main/interfaces-mount-observe/task.yaml snapd-2.28.5/tests/main/interfaces-mount-observe/task.yaml --- snapd-2.27.5/tests/main/interfaces-mount-observe/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/interfaces-mount-observe/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -35,7 +35,7 @@ snap interfaces | grep -Pzq "$CONNECTED_PATTERN" echo "Then the mount info is reachable" - expected="$SNAPMOUNTDIR/mount-observe-consumer" + expected="$SNAP_MOUNT_DIR/mount-observe-consumer" su -l -c "mount-observe-consumer" test | grep -Pq "$expected" if [ "$(snap debug confinement)" = strict ] ; then @@ -63,5 +63,5 @@ install_local test-snapd-tools echo "Then the new mount info is reachable" - expected="$SNAPMOUNTDIR/test-snapd-tools" + expected="$SNAP_MOUNT_DIR/test-snapd-tools" su -l -c "mount-observe-consumer" test | grep -Pq "$expected" diff -Nru snapd-2.27.5/tests/main/interfaces-network/task.yaml snapd-2.28.5/tests/main/interfaces-network/task.yaml --- snapd-2.27.5/tests/main/interfaces-network/task.yaml 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/tests/main/interfaces-network/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -1,5 +1,7 @@ summary: Ensure network interface works. +systems: [-fedora-*, -opensuse-*] + details: | The network interface allows a snap to access the network as a client. diff -Nru snapd-2.27.5/tests/main/interfaces-network-bind/task.yaml snapd-2.28.5/tests/main/interfaces-network-bind/task.yaml --- snapd-2.27.5/tests/main/interfaces-network-bind/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/interfaces-network-bind/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -60,7 +60,7 @@ snap interfaces | grep -Pzq "$CONNECTED_PATTERN" echo "Then the service is accessible by a client" - nc -w 2 -q 2 localhost "$PORT" < $REQUEST_FILE | grep -Pqz "ok\n" + nc -w 2 localhost "$PORT" < $REQUEST_FILE | grep -Pqz "ok\n" if [ "$(snap debug confinement)" = partial ] ; then exit 0 @@ -73,5 +73,5 @@ snap interfaces | grep -Pzq "$DISCONNECTED_PATTERN" echo "Then the service is not accessible by a client" - response=$(nc -w 2 -q 2 localhost "$PORT" < $REQUEST_FILE) + response=$(nc -w 2 localhost "$PORT" < $REQUEST_FILE) [ "$response" = "" ] diff -Nru snapd-2.27.5/tests/main/interfaces-network-control/task.yaml snapd-2.28.5/tests/main/interfaces-network-control/task.yaml --- snapd-2.27.5/tests/main/interfaces-network-control/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/interfaces-network-control/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -1,5 +1,7 @@ summary: Ensure that the network-control interface works. +systems: [-fedora-*, -opensuse-*] + details: | The network-control interface allows a snap to configure networking. @@ -25,7 +27,7 @@ snap install --dangerous network-control-consumer_1.0_all.snap echo "And a network service is up" - printf "#!/bin/sh -e\nwhile true; do echo \"HTTP/1.1 200 OK\n\nok\n\" | nc -l -p $PORT -q 1; done" > $SERVICE_FILE + printf "#!/bin/sh -e\nwhile true; do echo \"HTTP/1.1 200 OK\n\nok\n\" | nc -l -p $PORT; done" > $SERVICE_FILE chmod a+x $SERVICE_FILE systemd_create_and_start_unit $SERVICE_NAME "$(readlink -f $SERVICE_FILE)" diff -Nru snapd-2.27.5/tests/main/interfaces-network-observe/task.yaml snapd-2.28.5/tests/main/interfaces-network-observe/task.yaml --- snapd-2.27.5/tests/main/interfaces-network-observe/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/interfaces-network-observe/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -1,5 +1,7 @@ summary: Ensure that the network-observe interface works +systems: [-fedora-*, -opensuse-*] + details: | The network-observe interface allows a snap to query the network status information. @@ -23,7 +25,7 @@ snap install --dangerous network-observe-consumer_1.0_all.snap echo "And a network service is up" - printf "#!/bin/sh -e\nwhile true; do echo \"HTTP/1.1 200 OK\n\nok\n\" | nc -l -p $PORT -q 1; done" > $SERVICE_FILE + printf "#!/bin/sh -e\nwhile true; do echo \"HTTP/1.1 200 OK\n\nok\n\" | nc -l -p $PORT; done" > $SERVICE_FILE chmod a+x $SERVICE_FILE systemd_create_and_start_unit $SERVICE_NAME "$(readlink -f $SERVICE_FILE)" diff -Nru snapd-2.27.5/tests/main/interfaces-password-manager-service/task.yaml snapd-2.28.5/tests/main/interfaces-password-manager-service/task.yaml --- snapd-2.27.5/tests/main/interfaces-password-manager-service/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/interfaces-password-manager-service/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -6,12 +6,10 @@ prepare: | . $TESTSLIB/pkgdb.sh echo "Ensure we have a working gnome-keyring" - distro_install_package gnome-keyring dbus-x11 snap install --edge test-snapd-password-manager-consumer restore: | . $TESTSLIB/pkgdb.sh - distro_auto_remove_packages gnome-keyring dbus-x11 kill $(cat dbus-launch.pid) rm -f dbus-launch.pid diff -Nru snapd-2.27.5/tests/main/interfaces-time-control/task.yaml snapd-2.28.5/tests/main/interfaces-time-control/task.yaml --- snapd-2.27.5/tests/main/interfaces-time-control/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/interfaces-time-control/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -30,9 +30,9 @@ . $TESTSLIB/dirs.sh # Read/write access should be possible - test -n "`$SNAPMOUNTDIR/bin/time-control-consumer.read`" - $SNAPMOUNTDIR/bin/time-control-consumer.write + test -n "`$SNAP_MOUNT_DIR/bin/time-control-consumer.read`" + $SNAP_MOUNT_DIR/bin/time-control-consumer.write # Read/write access should be possible - test -n "`$SNAPMOUNTDIR/bin/time-control-consumer.timedatectl status`" - $SNAPMOUNTDIR/bin/time-control-consumer.timedatectl set-local-rtc no + test -n "`$SNAP_MOUNT_DIR/bin/time-control-consumer.timedatectl status`" + $SNAP_MOUNT_DIR/bin/time-control-consumer.timedatectl set-local-rtc no diff -Nru snapd-2.27.5/tests/main/interfaces-upower-observe/task.yaml snapd-2.28.5/tests/main/interfaces-upower-observe/task.yaml --- snapd-2.27.5/tests/main/interfaces-upower-observe/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/interfaces-upower-observe/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -1,11 +1,12 @@ summary: Ensure that the upower-observe interface works. systems: - - -ubuntu-core-16-* # ppc64el disabled because of https://github.com/snapcore/snapd/issues/2504 - -ubuntu-*-ppc64el + - -fedora-* + - -opensuse-* -summary: | +details: | The upower-observe interface allows a snap to query UPower for power devices, history and statistics. @@ -19,44 +20,55 @@ echo "Given a snap declaring a plug on the upower-observe interface is installed" snap install --edge test-snapd-upower-observe-consumer - . "$TESTSLIB/pkgdb.sh" - distro_install_package upower + if [[ "$SPREAD_SYSTEM" = ubuntu-core-* ]]; then + echo "And a snap providing a upower-observe slot is installed" + snap install upower + else + . "$TESTSLIB/pkgdb.sh" + distro_install_package upower + fi restore: | rm -f upower.error - . "$TESTSLIB/pkgdb.sh" - distro_purge_package upower + if [[ "$SPREAD_SYSTEM" != ubuntu-core-* ]]; then + . "$TESTSLIB/pkgdb.sh" + distro_purge_package upower + fi execute: | - CONNECTED_PATTERN=":upower-observe +test-snapd-upower-observe-consumer" - DISCONNECTED_PATTERN="(?s).*?\n- +test-snapd-upower-observe-consumer:upower-observe" + SLOT_PROVIDER= + SLOT_NAME=upower-observe + if [[ "$SPREAD_SYSTEM" = ubuntu-core-* ]]; then + SLOT_PROVIDER=upower + SLOT_NAME=service + fi - echo "Then it is connected by default" - snap interfaces | grep -Pzq "$CONNECTED_PATTERN" + CONNECTED_PATTERN="$SLOT_PROVIDER:$SLOT_NAME +test-snapd-upower-observe-consumer" + DISCONNECTED_PATTERN="\- +test-snapd-upower-observe-consumer:upower-observe" - echo "===================================" + echo "The snap is connected by default" + snap interfaces | MATCH "$CONNECTED_PATTERN" + + echo "When the plug is connected the snap is able to dump info about the upower devices" + expected="/org/freedesktop/UPower/devices/DisplayDevice.*" + for i in $(seq 20); do + if ! test-snapd-upower-observe-consumer.upower --dump | MATCH "$expected"; then + sleep 1 + fi + done + test-snapd-upower-observe-consumer.upower --dump | MATCH "$expected" - echo "When the plug is disconnected" - snap disconnect test-snapd-upower-observe-consumer:upower-observe - snap interfaces | grep -Pzq "$DISCONNECTED_PATTERN" + echo "===================================" if [ "$(snap debug confinement)" = strict ] ; then + echo "When the plug is disconnected" + snap disconnect test-snapd-upower-observe-consumer:upower-observe + snap interfaces | MATCH "$DISCONNECTED_PATTERN" + echo "Then the snap is not able to dump info about the upower devices" - if su -l -c "test-snapd-upower-observe-consumer.upower --dump 2>${PWD}/upower.error" test; then + if test-snapd-upower-observe-consumer.upower --dump 2>${PWD}/upower.error; then echo "Expected permission error accessing upower info with disconnected plug" exit 1 fi - grep -q "Permission denied" upower.error - - echo "===================================" + cat upower.error | MATCH "Permission denied" fi - - echo "When the plug is connected" - snap connect test-snapd-upower-observe-consumer:upower-observe - snap interfaces | grep -Pzq "$CONNECTED_PATTERN" - - echo "Then the snap is able to dump info about the upower devices" - expected="(?s)Device: +/org/freedesktop/UPower/devices/DisplayDevice.*Daemon:.*" - # debug - su -l -c 'test-snapd-upower-observe-consumer.upower --dump' test || true - su -l -c 'test-snapd-upower-observe-consumer.upower --dump' test | grep -Pqz "$expected" diff -Nru snapd-2.27.5/tests/main/interfaces-wayland/task.yaml snapd-2.28.5/tests/main/interfaces-wayland/task.yaml --- snapd-2.27.5/tests/main/interfaces-wayland/task.yaml 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/main/interfaces-wayland/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,65 @@ +summary: Ensure that the wayland interface works + +# Only test on classic Ubuntu amd64 systems that have wayland +systems: [ ubuntu-1*-*64 ] + +prepare: | + . $TESTSLIB/pkgdb.sh + snap install --edge test-snapd-wayland + +restore: | + echo "Stop weston compositor" + /usr/bin/killall -9 /usr/bin/weston || true + +execute: | + CONNECTED_PATTERN=":wayland +test-snapd-wayland" + DISCONNECTED_PATTERN="\- +test-snapd-wayland:wayland" + + echo "When the plug is connected" + snap connect test-snapd-wayland:wayland + snap interfaces | MATCH "$CONNECTED_PATTERN" + + if [ "$(snap debug confinement)" = "partial" ] ; then + exit 0 + fi + + echo "====================================" + + echo "Create XDG_RUNTIME_DIR=/run/user/12345" + mkdir -p /run/user/12345 || true + chmod 700 /run/user/12345 + chown test:test /run/user/12345 + + echo "Start weston compositor under test user" + XDG_RUNTIME_DIR=/run/user/12345 su -p -c "weston --backend=headless-backend.so" test & + + echo "Then wait for the socket to show up" + count=0 + while sleep 1 && [ ! -S /run/user/12345/wayland-0 ]; do + echo $count + count=$((count+1)) + if [ "$count" -gt 10 ]; then + echo "Could not find wayland socket" + exit 1 + fi + done + + echo "Then the snap command under the test user is able connect to the wayland socket" + XDG_RUNTIME_DIR=/run/user/12345 su -p -l -c test-snapd-wayland test | MATCH wl_compositor + + echo "====================================" + + echo "When the plug is disconnected" + snap disconnect test-snapd-wayland:wayland + snap interfaces | MATCH "$DISCONNECTED_PATTERN" + + echo "Then the snap command is not able to connect to the wayland socket" + if XDG_RUNTIME_DIR=/run/user/12345 su -p -l -c test-snapd-wayland test; then + echo "Expected error with plug disconnected" + exit 1 + fi + + # If this is in 'restore', execute doesn't exit and spread must timeout + # the test. + echo "Stop weston compositor" + /usr/bin/killall -9 /usr/bin/weston || true diff -Nru snapd-2.27.5/tests/main/listing/task.yaml snapd-2.28.5/tests/main/listing/task.yaml --- snapd-2.27.5/tests/main/listing/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/listing/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -11,10 +11,13 @@ # but edge will have a timestamp in there, "16.2+201701010932", so add an optional \+[0-9]+ to the end # *current* edge also has .git. and a hash snippet, so add an optional .git.[0-9a-f]+ to the already optional timestamp if [ "$SPREAD_BACKEND" = "linode" -o "$SPREAD_BACKEND" == "qemu" ] && [ "$SPREAD_SYSTEM" = "ubuntu-core-16-64" ]; then - echo "With customized images the ubuntu-core snap is sideloaded" - expected='^core .* [0-9]{2}-[0-9.]+(\+git[0-9]+\.[0-9a-f]+)? +x[0-9]+ +core *$' + echo "With customized images the core snap is sideloaded" + expected='^core .* [0-9]{2}-[0-9.]+(~[a-z0-9]+)?(\+git[0-9]+\.[0-9a-f]+)? +x[0-9]+ +core *$' + elif [ "$SRU_VALIDATION" = "1" ]; then + echo "When sru validation is done the core snap is installed from the store" + expected='^core .* [0-9]{2}-[0-9.]+(~[a-z0-9]+)?(\+[0-9]+\.[0-9a-f]+)? +[0-9]+ +canonical +core *$' else - expected='^core .* [0-9]{2}-[0-9.]+(\+git[0-9]+\.[0-9a-f]+)? +[0-9]+ +canonical +core *$' + expected='^core .* [0-9]{2}-[0-9.]+(~[a-z0-9]+)?(\+git[0-9]+\.[0-9a-f]+)? +[0-9]+ +canonical +core *$' fi snap list | MATCH "$expected" diff -Nru snapd-2.27.5/tests/main/lxd/task.yaml snapd-2.28.5/tests/main/lxd/task.yaml --- snapd-2.27.5/tests/main/lxd/task.yaml 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/main/lxd/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,82 @@ +summary: Ensure that lxd works + +# only run this on ubuntu 16+, lxd will not work on !ubuntu systems +# currently nor on ubuntu 14.04 +systems: [ubuntu-16*, ubuntu-core-*] + +restore: | + if [[ $(ls -1 "$GOHOME"/snapd_*.deb | wc -l || echo 0) -eq 0 ]]; then + exit + fi + + lxd.lxc stop my-ubuntu + lxd.lxc delete my-ubuntu + +debug: | + # debug output from lxd + journalctl -u snap.lxd.daemon.service + +execute: | + if [[ $(ls -1 "$GOHOME"/snapd_*.deb | wc -l || echo 0) -eq 0 ]]; then + echo "No run lxd test when there are not .deb files built" + exit + fi + + wait_for_lxd(){ + while ! printf "GET / HTTP/1.0\n\n" | nc -U /var/snap/lxd/common/lxd/unix.socket | MATCH "200 OK"; do sleep 1; done + } + + echo "Install lxd" + snap install lxd + + echo "Create a trivial container using the lxd snap" + wait_for_lxd + lxd init --auto + + echo "Setting up proxy for lxc" + if [ -n "${http_proxy:-}" ]; then + lxd.lxc config set core.proxy_http $http_proxy + fi + if [ -n "${https_proxy:-}" ]; then + lxd.lxc config set core.proxy_https $http_proxy + fi + + lxd.lxc launch ubuntu:16.04 my-ubuntu + + echo "Ensure we can run things inside" + lxd.lxc exec my-ubuntu echo hello | MATCH hello + + echo "Ensure we can get network" + lxd.lxc network create testbr0 + lxd.lxc network attach testbr0 my-ubuntu eth0 + lxd.lxc exec my-ubuntu dhclient eth0 + + echo "Cleanup container" + lxd.lxc exec my-ubuntu -- apt autoremove --purge -y snapd ubuntu-core-launcher + + echo "Install snapd" + lxd.lxc exec my-ubuntu -- mkdir -p "$GOHOME" + lxd.lxc file push "$GOHOME"/snapd_*.deb my-ubuntu/$GOPATH/ + lxd.lxc exec my-ubuntu -- dpkg -i "$GOHOME"/snapd_*.deb + + echo "Setting up proxy *inside* the container" + if [ -n "${http_proxy:-}" ]; then + lxd.lxc exec my-ubuntu -- sh -c "echo http_proxy=$http_proxy >> /etc/environment" + fi + if [ -n "${https_proxy:-}" ]; then + lxd.lxc exec my-ubuntu -- sh -c "echo https_proxy=$https_proxy >> /etc/environment" + fi + lxd.lxc exec my-ubuntu -- systemctl daemon-reload + lxd.lxc exec my-ubuntu -- systemctl restart snapd.service + lxd.lxc exec my-ubuntu -- cat /etc/environment + + # FIXME: workaround for missing squashfuse + lxd.lxc exec my-ubuntu apt update + lxd.lxc exec my-ubuntu -- apt install -y squashfuse + + # FIXME: ensure that the kernel running is recent enough, this + # will only work with an up-to-date xenial kernel (4.4.0-78+) + + echo "Ensure we can use snapd inside lxd" + lxd.lxc exec my-ubuntu snap install test-snapd-tools + lxd.lxc exec my-ubuntu test-snapd-tools.echo from-the-inside | MATCH from-the-inside diff -Nru snapd-2.27.5/tests/main/op-install-failed-undone/task.yaml snapd-2.28.5/tests/main/op-install-failed-undone/task.yaml --- snapd-2.27.5/tests/main/op-install-failed-undone/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/op-install-failed-undone/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -4,7 +4,7 @@ restore: | . $TESTSLIB/dirs.sh - rm -rf $SNAPMOUNTDIR/test-snapd-tools + rm -rf $SNAP_MOUNT_DIR/test-snapd-tools execute: | check_empty_glob(){ @@ -16,7 +16,7 @@ . $TESTSLIB/dirs.sh echo "Given we make a snap uninstallable" - mkdir -p $SNAPMOUNTDIR/test-snapd-tools/current/foo + mkdir -p $SNAP_MOUNT_DIR/test-snapd-tools/current/foo echo "And we try to install it" . $TESTSLIB/snaps.sh @@ -37,7 +37,7 @@ echo "And the Mount subtask is actually undone" snap change $failed_task_id | grep -Pq "Undone +.*?Mount snap \"test-snapd-tools\"" - check_empty_glob $SNAPMOUNTDIR/test-snapd-tools [0-9]+ + check_empty_glob $SNAP_MOUNT_DIR/test-snapd-tools [0-9]+ check_empty_glob /var/lib/snapd/snaps test-snapd-tools_[0-9]+.snap echo "And the Data Copy subtask is actually undone" diff -Nru snapd-2.27.5/tests/main/op-remove/task.yaml snapd-2.28.5/tests/main/op-remove/task.yaml --- snapd-2.27.5/tests/main/op-remove/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/op-remove/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -9,7 +9,7 @@ snap_revisions(){ local snap_name=$1 - echo -n $(find $SNAPMOUNTDIR/"$snap_name"/ -maxdepth 1 -type d -name "x*" | wc -l) + echo -n $(find $SNAP_MOUNT_DIR/"$snap_name"/ -maxdepth 1 -type d -name "x*" | wc -l) } echo "Given two revisions of a snap have been installed" diff -Nru snapd-2.27.5/tests/main/op-remove-retry/task.yaml snapd-2.28.5/tests/main/op-remove-retry/task.yaml --- snapd-2.27.5/tests/main/op-remove-retry/task.yaml 2016-09-28 09:07:27.000000000 +0000 +++ snapd-2.28.5/tests/main/op-remove-retry/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -4,11 +4,6 @@ kill %1 || true execute: | - wait_for_service(){ - local service_name=$1 - local state=$2 - while ! systemctl show -p ActiveState $service_name | grep -q "ActiveState=$state"; do systemctl status $service_name || true; sleep 1; done - } wait_for_remove_state(){ local state=$1 local expected="(?s)$state.*?Remove \"test-snapd-tools\" snap" diff -Nru snapd-2.27.5/tests/main/postrm-purge/task.yaml snapd-2.28.5/tests/main/postrm-purge/task.yaml --- snapd-2.27.5/tests/main/postrm-purge/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/postrm-purge/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -9,17 +9,26 @@ snap install test-snapd-control-consumer snap install test-snapd-auto-aliases + . $TESTSLIB/dirs.sh + + # For now we use the Fedora specific snap-mgmt script but as soon + # as we have a generic one we can use cross-distro we need to + # change this. echo "And snapd is purged" - # only available on trusty - if [ -x ${SPREAD_PATH}/debian/snapd.prerm ]; then - sh -x ${SPREAD_PATH}/debian/snapd.prerm + if [[ "$SPREAD_SYSTEM" = fedora-* ]] ; then + sh ${SPREAD_PATH}/packaging/fedora/snap-mgmt.sh \ + --snap-mount-dir=$SNAP_MOUNT_DIR \ + --purge + else + # only available on trusty + if [ -x ${SPREAD_PATH}/debian/snapd.prerm ]; then + sh -x ${SPREAD_PATH}/debian/snapd.prerm + fi + sh -x ${SPREAD_PATH}/debian/snapd.postrm purge fi - sh -x ${SPREAD_PATH}/debian/snapd.postrm purge - - . $TESTSLIB/dirs.sh echo "Nothing is left" - for d in $SNAPMOUNTDIR /var/snap; do + for d in $SNAP_MOUNT_DIR /var/snap; do if [ -d "$d" ]; then echo "$d is not removed" ls -lR $d diff -Nru snapd-2.27.5/tests/main/prefer/task.yaml snapd-2.28.5/tests/main/prefer/task.yaml --- snapd-2.27.5/tests/main/prefer/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/prefer/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -6,15 +6,15 @@ snap install test-snapd-auto-aliases echo "Sanity check" - test -h $SNAPMOUNTDIR/bin/test_snapd_wellknown1 - test -h $SNAPMOUNTDIR/bin/test_snapd_wellknown2 + test -h $SNAP_MOUNT_DIR/bin/test_snapd_wellknown1 + test -h $SNAP_MOUNT_DIR/bin/test_snapd_wellknown2 echo "Disable the auto-aliases" snap unalias test-snapd-auto-aliases echo "Auto-aliases are gone" - test ! -e $SNAPMOUNTDIR/bin/test_snapd_wellknown1 - test ! -e $SNAPMOUNTDIR/bin/test_snapd_wellknown2 + test ! -e $SNAP_MOUNT_DIR/bin/test_snapd_wellknown1 + test ! -e $SNAP_MOUNT_DIR/bin/test_snapd_wellknown2 echo "Check listing" snap aliases|MATCH "test-snapd-auto-aliases.wellknown1 +test_snapd_wellknown1 +disabled" @@ -24,8 +24,8 @@ snap prefer test-snapd-auto-aliases|MATCH ".*- test-snapd-auto-aliases.wellknown1 as test_snapd_wellknown1.*" echo "Test that the auto-aliases are back" - test -h $SNAPMOUNTDIR/bin/test_snapd_wellknown1 - test -h $SNAPMOUNTDIR/bin/test_snapd_wellknown2 + test -h $SNAP_MOUNT_DIR/bin/test_snapd_wellknown1 + test -h $SNAP_MOUNT_DIR/bin/test_snapd_wellknown2 test_snapd_wellknown1|MATCH "ok wellknown 1" test_snapd_wellknown2|MATCH "ok wellknown 2" diff -Nru snapd-2.27.5/tests/main/prepare-image-grub/task.yaml snapd-2.28.5/tests/main/prepare-image-grub/task.yaml --- snapd-2.27.5/tests/main/prepare-image-grub/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/prepare-image-grub/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -1,6 +1,9 @@ summary: Check that prepare-image works for grub-systems -systems: [-ubuntu-core-16-*] + +systems: [-ubuntu-core-16-*, -fedora-*, -opensuse-*] + backends: [-autopkgtest] + # TODO: use the real stores with proper assertions fully as well once possible environment: ROOT: /tmp/root @@ -9,6 +12,7 @@ STORE_DIR: $(pwd)/fake-store-blobdir STORE_ADDR: localhost:11028 UBUNTU_IMAGE_SKIP_COPY_UNVERIFIED_SNAPS: 1 + prepare: | if [ "$TRUST_TEST_KEYS" = "false" ]; then echo "This test needs test keys to be trusted" @@ -17,6 +21,7 @@ . $TESTSLIB/store.sh setup_fake_store $STORE_DIR + restore: | if [ "$TRUST_TEST_KEYS" = "false" ]; then echo "This test needs test keys to be trusted" @@ -26,6 +31,7 @@ . $TESTSLIB/store.sh teardown_fake_store $STORE_DIR rm -rf $ROOT + execute: | if [ "$TRUST_TEST_KEYS" = "false" ]; then echo "This test needs test keys to be trusted" diff -Nru snapd-2.27.5/tests/main/refresh/task.yaml snapd-2.28.5/tests/main/refresh/task.yaml --- snapd-2.27.5/tests/main/refresh/task.yaml 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/tests/main/refresh/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -29,9 +29,11 @@ flags= if [[ $SNAP_NAME =~ classic ]]; then - if [[ "$SPREAD_SYSTEM" == ubuntu-core-* ]]; then - exit - fi + case "$SPREAD_SYSTEM" in + ubuntu-core-*|fedora-*) + exit + ;; + esac flags=--classic fi @@ -72,8 +74,12 @@ fi fi - if [[ $SNAP_NAME =~ classic && "$SPREAD_SYSTEM" == ubuntu-core-* ]]; then - exit + if [[ $SNAP_NAME =~ classic ]]; then + case "$SPREAD_SYSTEM" in + ubuntu-core-*|fedora-*) + exit + ;; + esac fi # FIXME: currently the --list from channel doesn't work diff -Nru snapd-2.27.5/tests/main/refresh-all/task.yaml snapd-2.28.5/tests/main/refresh-all/task.yaml --- snapd-2.27.5/tests/main/refresh-all/task.yaml 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/tests/main/refresh-all/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -34,6 +34,7 @@ . $TESTSLIB/store.sh teardown_fake_store $BLOB_DIR + rm -rf $BLOB_DIR execute: | if [ "$TRUST_TEST_KEYS" = "false" ]; then @@ -41,9 +42,16 @@ exit fi + echo "Sanity check for the fake store" + snap refresh 2>&1 | MATCH "All snaps up to date." + echo "When the store is configured to make them refreshable" + . $TESTSLIB/files.sh . $TESTSLIB/store.sh - init_fake_refreshes test-snapd-tools,test-snapd-python-webserver $BLOB_DIR + init_fake_refreshes test-snapd-tools $BLOB_DIR + wait_for_file $BLOB_DIR/test-snapd-tools*fake1*.snap 4 .5 + init_fake_refreshes test-snapd-python-webserver $BLOB_DIR + wait_for_file $BLOB_DIR/test-snapd-python-webserver*fake1*.snap 4 .5 echo "And a refresh is performed" snap refresh diff -Nru snapd-2.27.5/tests/main/refresh-all-undo/task.yaml snapd-2.28.5/tests/main/refresh-all-undo/task.yaml --- snapd-2.27.5/tests/main/refresh-all-undo/task.yaml 2017-08-18 10:22:56.000000000 +0000 +++ snapd-2.28.5/tests/main/refresh-all-undo/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -31,6 +31,7 @@ . $TESTSLIB/store.sh teardown_fake_store $BLOB_DIR + rm -rf $BLOB_DIR execute: | if [ "$TRUST_TEST_KEYS" = "false" ]; then @@ -38,9 +39,16 @@ exit fi + echo "Sanity check for the fake store" + snap refresh 2>&1 | MATCH "All snaps up to date" + echo "When the store is configured to make them refreshable" + . $TESTSLIB/files.sh . $TESTSLIB/store.sh - init_fake_refreshes $GOOD_SNAP,$BAD_SNAP $BLOB_DIR + init_fake_refreshes "$GOOD_SNAP" "$BLOB_DIR" + wait_for_file "$BLOB_DIR"/"${GOOD_SNAP}"*fake1*.snap 4 .5 + init_fake_refreshes "$BAD_SNAP" "$BLOB_DIR" + wait_for_file "$BLOB_DIR"/"${BAD_SNAP}"*fake1*.snap 4 .5 echo "When a snap is broken" echo "i-am-broken-now" >> $BLOB_DIR/${BAD_SNAP}*fake1*.snap diff -Nru snapd-2.27.5/tests/main/regression-home-snap-root-owned/task.yaml snapd-2.28.5/tests/main/regression-home-snap-root-owned/task.yaml --- snapd-2.27.5/tests/main/regression-home-snap-root-owned/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/regression-home-snap-root-owned/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -11,7 +11,7 @@ . $TESTSLIB/dirs.sh # run a snap command via sudo - output=$(su -l -c "sudo $SNAPMOUNTDIR/bin/test-snapd-tools.env" test) + output=$(su -l -c "sudo $SNAP_MOUNT_DIR/bin/test-snapd-tools.env" test) # ensure SNAP_USER_DATA points to the right place echo $output | MATCH SNAP_USER_DATA=/root/snap/test-snapd-tools/x[0-9]+ @@ -21,7 +21,7 @@ echo "Verify that the /root/snap directory created and root owned" if [ $(stat -c '%U' /root/snap) != "root" ]; then echo "The /root/snap directory is not owned by root" - ls -ld $SNAPMOUNTDIR/snap + ls -ld $SNAP_MOUNT_DIR/snap exit 1 fi diff -Nru snapd-2.27.5/tests/main/revert/task.yaml snapd-2.28.5/tests/main/revert/task.yaml --- snapd-2.27.5/tests/main/revert/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/revert/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -73,7 +73,7 @@ snap list | MATCH "test-snapd-tools +[0-9]+\.[0-9]+ " echo "And the data directories are present" - ls $SNAPMOUNTDIR/test-snapd-tools | MATCH current + ls $SNAP_MOUNT_DIR/test-snapd-tools | MATCH current ls /var/snap/test-snapd-tools | MATCH current echo "And the snap runs confined" diff -Nru snapd-2.27.5/tests/main/revert-devmode/task.yaml snapd-2.28.5/tests/main/revert-devmode/task.yaml --- snapd-2.27.5/tests/main/revert-devmode/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/revert-devmode/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -59,7 +59,7 @@ echo "Then the new version is installed" snap list | MATCH 'test-snapd-tools +[0-9]+\.[0-9]+\+fake1' - LATEST=$(readlink $SNAPMOUNTDIR/test-snapd-tools/current) + LATEST=$(readlink $SNAP_MOUNT_DIR/test-snapd-tools/current) echo "When a revert is made without --devmode flag" snap revert test-snapd-tools diff -Nru snapd-2.27.5/tests/main/security-device-cgroups/task.yaml snapd-2.28.5/tests/main/security-device-cgroups/task.yaml --- snapd-2.27.5/tests/main/security-device-cgroups/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/security-device-cgroups/task.yaml 2017-10-11 17:40:25.000000000 +0000 @@ -1,5 +1,9 @@ summary: Ensure that the security rules related to device cgroups work. +# We don't run the native kernel on these distributions yet so we can't +# load kernel modules coming from distribution packages yet. +systems: [-fedora-*, -opensuse-*] + environment: DEVICE_NAME/kmsg: kmsg UDEVADM_PATH/kmsg: /sys/devices/virtual/mem/kmsg @@ -19,8 +23,37 @@ if [ ! -e /sys/devices/virtual/misc/uinput ]; then modprobe uinput fi + # create nvidia devices if they don't exist + if [ ! -e /dev/nvidia0 ]; then + mknod /dev/nvidia0 c 195 0 + touch /dev/nvidia0.spread + fi + if [ ! -e /dev/nvidiactl ]; then + mknod /dev/nvidiactl c 195 255 + touch /dev/nvidiactl.spread + fi + if [ ! -e /dev/nvidia-uvm ]; then + mknod /dev/nvidia-uvm c 247 0 + touch /dev/nvidia-uvm.spread + fi + # move aside an existing nvidia device + if [ -e /dev/nvidia254 ]; then + mv /dev/nvidia254 /dev/nvidia254.spread + fi restore: | + if [ -e /dev/nvidia0.spread ]; then + rm -f /dev/nvidia0 /dev/nvidia0.spread + fi + if [ -e /dev/nvidiactl.spread ]; then + rm -f /dev/nvidiactl /dev/nvidiactl.spread + fi + if [ -e /dev/nvidia-uvm.spread ]; then + rm -f /dev/nvidia-uvm /dev/nvidia-uvm.spread + fi + if [ -e /dev/nvidia254.spread ]; then + mv /dev/nvidia254.spread /dev/nvidia254 + fi rm -f /etc/udev/rules.d/70-snap.test-snapd-tools.rules udevadm control --reload-rules udevadm trigger @@ -66,4 +99,12 @@ echo "And other devices are not shown in the snap device list" MATCH -v "$OTHER_DEVICE_ID" < /sys/fs/cgroup/devices/snap.test-snapd-tools.env/devices.list + echo "But existing nvidia devices are in the snap's device cgroup" + MATCH "c 195:0 rwm" < /sys/fs/cgroup/devices/snap.test-snapd-tools.env/devices.list + MATCH "c 195:255 rwm" < /sys/fs/cgroup/devices/snap.test-snapd-tools.env/devices.list + MATCH "c 247:0 rwm" < /sys/fs/cgroup/devices/snap.test-snapd-tools.env/devices.list + + echo "But nonexisting nvidia devices are not" + MATCH -v "c 195:254 rwm" < /sys/fs/cgroup/devices/snap.test-snapd-tools.env/devices.list + # TODO: check device unassociated after removing the udev file and rebooting diff -Nru snapd-2.27.5/tests/main/security-devpts/pts.exp snapd-2.28.5/tests/main/security-devpts/pts.exp --- snapd-2.27.5/tests/main/security-devpts/pts.exp 2016-11-24 09:36:04.000000000 +0000 +++ snapd-2.28.5/tests/main/security-devpts/pts.exp 2017-09-13 14:47:18.000000000 +0000 @@ -7,7 +7,7 @@ expect "\[0-9]*ptmx" {} timeout { exit 1 } # Launch app and checks contents of /dev/pts, should have only ptmx -spawn su -l -c "/snap/bin/test-snapd-tools.sh" test +spawn su -l -c "$env(SNAP_MOUNT_DIR)/bin/test-snapd-tools.sh" test expect "bash-4.3\\$" {} timeout { exit 1 } send "ls /dev/pts\n" expect { diff -Nru snapd-2.27.5/tests/main/security-devpts/task.yaml snapd-2.28.5/tests/main/security-devpts/task.yaml --- snapd-2.27.5/tests/main/security-devpts/task.yaml 2017-08-18 13:48:10.000000000 +0000 +++ snapd-2.28.5/tests/main/security-devpts/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -9,5 +9,10 @@ install_local test-snapd-tools execute: | + if [ "$(snap debug confinement)" = none ] ; then + exit 0 + fi + echo "Then the pts device follows confinement rules" + . $TESTSLIB/dirs.sh expect -d -f pts.exp diff -Nru snapd-2.27.5/tests/main/security-private-tmp/task.yaml snapd-2.28.5/tests/main/security-private-tmp/task.yaml --- snapd-2.27.5/tests/main/security-private-tmp/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/security-private-tmp/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -19,14 +19,15 @@ snap install --dangerous not-test-snapd-tools_1.0_all.snap restore: | - rm -rf not-test-snapd-tools_1.0_all.snap \ - $SNAP_INSTALL_DIR /tmp/foo *stat.error + rm -rf not-test-snapd-tools_1.0_all.snap "$SNAP_INSTALL_DIR" /tmp/foo *stat.error execute: | + . $TESTSLIB/dirs.sh + echo "When a temporary file is created by one snap" expect -d -f tmp-create.exp - if [ -e /usr/lib/snapd/snap-discard-ns ]; then + if [ -e "$LIBEXECDIR/snapd/snap-discard-ns" ]; then echo "Then that file is accessible from other calls of commands from the same snap" if ! test-snapd-tools.cmd stat /tmp/foo 2>same-stat.error; then echo "Expected the file to be present" diff -Nru snapd-2.27.5/tests/main/security-private-tmp/tmp-create.exp snapd-2.28.5/tests/main/security-private-tmp/tmp-create.exp --- snapd-2.27.5/tests/main/security-private-tmp/tmp-create.exp 2016-11-24 09:36:04.000000000 +0000 +++ snapd-2.28.5/tests/main/security-private-tmp/tmp-create.exp 2017-09-13 14:47:18.000000000 +0000 @@ -5,7 +5,7 @@ spawn bash # Test private /tmp, allowed access -spawn su -l -c "/snap/bin/test-snapd-tools.sh" test +spawn su -l -c "$env(SNAP_MOUNT_DIR)/bin/test-snapd-tools.sh" test expect "bash-4.3\\$" {} timeout { exit 1 } send "touch /tmp/foo\n" send "stat /tmp/foo\n" diff -Nru snapd-2.27.5/tests/main/security-setuid-root/task.yaml snapd-2.28.5/tests/main/security-setuid-root/task.yaml --- snapd-2.27.5/tests/main/security-setuid-root/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/security-setuid-root/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -29,13 +29,13 @@ # NOTE: This has to run as the test user because the protection is only # active if user gains elevated permissions as a result of using setuid # root snap-confine. - if su test -c "sh -c \"SNAP_NAME=test-snapd-tools $SNAPMOUNTDIR/core/current/usr/lib/snapd/snap-confine snap.test-snapd-tools.cmd /bin/true 2>/dev/null\""; then + if su test -c "sh -c \"SNAP_NAME=test-snapd-tools $SNAP_MOUNT_DIR/core/current/usr/lib/snapd/snap-confine snap.test-snapd-tools.cmd /bin/true 2>/dev/null\""; then echo "snap-confine didn't refuse to run!" exit 1 fi - su test -c "sh -c \"SNAP_NAME=test-snapd-tools $SNAPMOUNTDIR/core/current/usr/lib/snapd/snap-confine snap.test-snapd-tools.cmd /bin/true 2>&1\"" | MATCH "Refusing to continue to avoid permission escalation attacks" + su test -c "sh -c \"SNAP_NAME=test-snapd-tools $SNAP_MOUNT_DIR/core/current/usr/lib/snapd/snap-confine snap.test-snapd-tools.cmd /bin/true 2>&1\"" | MATCH "Refusing to continue to avoid permission escalation attacks" debug: | - ls -ld $SNAPMOUNTDIR/core/current/usr/lib/snapd/snap-confine || true - ls -ld $SNAPMOUNTDIR/ubuntu-core/current/usr/lib/snapd/snap-confine || true + ls -ld $SNAP_MOUNT_DIR/core/current/usr/lib/snapd/snap-confine || true + ls -ld $SNAP_MOUNT_DIR/ubuntu-core/current/usr/lib/snapd/snap-confine || true ls -ld /usr/lib/snapd/snap-confine || true snap list diff -Nru snapd-2.27.5/tests/main/server-snap/task.yaml snapd-2.28.5/tests/main/server-snap/task.yaml --- snapd-2.27.5/tests/main/server-snap/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/server-snap/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -1,5 +1,7 @@ summary: Check snap web servers +systems: [-fedora-*, -opensuse-*] + environment: SNAP_NAME/pythonServer: test-snapd-python-webserver IP_VERSION/pythonServer: 4 @@ -27,7 +29,7 @@ rm -f request.txt execute: | - response=$(nc -q 5 -"$IP_VERSION" "$LOCALHOST" "$PORT" < request.txt) + response=$(nc -w 5 -"$IP_VERSION" "$LOCALHOST" "$PORT" < request.txt) statusPattern="(?s)HTTP\/1\.0 200 OK\n*" echo "$response" | grep -Pzq "$statusPattern" diff -Nru snapd-2.27.5/tests/main/snap-confine/task.yaml snapd-2.28.5/tests/main/snap-confine/task.yaml --- snapd-2.27.5/tests/main/snap-confine/task.yaml 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/main/snap-confine/task.yaml 2017-10-13 21:25:17.000000000 +0000 @@ -0,0 +1,32 @@ +summary: Test that snap-confine errors in the right way + +# the error message can only happen on classic systems +systems: [-ubuntu-core-16-*] + +prepare: | + echo "Install test snap" + snap install test-snapd-tools + +restore: | + . $TESTSLIB/dirs.sh + + echo "Restore current symlink" + mv $SNAP_MOUNT_DIR/core/current.renamed $SNAP_MOUNT_DIR/core/current || true + rm -f snap-confine.stderr + +execute: | + . $TESTSLIB/dirs.sh + + echo "Test nvidia device fix" + # For https://github.com/snapcore/snapd/pull/4042 + echo "Simulate nvidia device tags" + mkdir -p /run/udev/tags/snap_test-snapd-tools_echo + for f in c226:0 +module:nvidia +module:nvidia_modeset; do + touch /run/udev/tags/snap_test-snapd-tools_echo/$f + done + test-snapd-tools.echo hello | MATCH hello + echo "Non nvidia files are still there" + test -f /run/udev/tags/snap_test-snapd-tools_echo/c226:0 + echo "But nvidia files are gone" + ! test -f /run/udev/tags/snap_test-snapd-tools_echo/+module:nvidia + ! test -f /run/udev/tags/snap_test-snapd-tools_echo/+module:nvidia_modeset diff -Nru snapd-2.27.5/tests/main/snap-confine-from-core/task.yaml snapd-2.28.5/tests/main/snap-confine-from-core/task.yaml --- snapd-2.27.5/tests/main/snap-confine-from-core/task.yaml 2017-08-08 06:31:43.000000000 +0000 +++ snapd-2.28.5/tests/main/snap-confine-from-core/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -1,6 +1,7 @@ summary: Test that snap-confine is run from core on re-exec -systems: [-ubuntu-core-16-*] +# Disable for Fedora, openSUSE as re-exec is not support there yet +systems: [-ubuntu-core-16-*, -fedora-*, -opensuse-*] prepare: | echo "Installing test-snapd-tools" diff -Nru snapd-2.27.5/tests/main/snapctl/task.yaml snapd-2.28.5/tests/main/snapctl/task.yaml --- snapd-2.27.5/tests/main/snapctl/task.yaml 2016-10-27 13:22:15.000000000 +0000 +++ snapd-2.28.5/tests/main/snapctl/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -11,16 +11,17 @@ echo "Verify that snapctl -h runs without a context" if ! snapctl -h; then echo "Expected snapctl -h to be successful" + exit 1 fi echo "Verify that the snapd API is only available via the snapd socket" - if ! printf "GET /v2/snaps HTTP/1.0\r\n\r\n" | nc -U -q 1 /run/snapd.socket | grep "200 OK"; then + if ! printf "GET /v2/snaps HTTP/1.0\r\n\r\n" | nc -U -w 1 /run/snapd.socket | grep "200 OK"; then echo "Expected snapd API to be available on the snapd socket" echo "Got: $(curl -s --unix-socket /run/snapd.socket http:/v2/snaps)" exit 1 fi - if ! printf "GET /v2/snaps HTTP/1.0\r\n\r\n" | nc -U -q 1 /run/snapd-snap.socket | grep "401 Unauthorized"; then + if ! printf "GET /v2/snaps HTTP/1.0\r\n\r\n" | nc -U -w 1 /run/snapd-snap.socket | grep "401 Unauthorized"; then echo "Expected snapd API to be unauthorized on the snap socket" exit 1 fi diff -Nru snapd-2.27.5/tests/main/snapctl-configure-core/task.yaml snapd-2.28.5/tests/main/snapctl-configure-core/task.yaml --- snapd-2.27.5/tests/main/snapctl-configure-core/task.yaml 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/main/snapctl-configure-core/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,67 @@ +summary: Ensure "snapctl core-configure" works + +# the test is only meaningful on core devices +systems: [ubuntu-core-*] + +prepare: | + cat > new-configure-core </tmp/old-info - mount --bind /tmp/old-info $SNAPMOUNTDIR/core/current/usr/lib/snapd/info + mount --bind /tmp/old-info $SNAP_MOUNT_DIR/core/current/usr/lib/snapd/info systemctl start snapd.service snapd.socket snap list journalctl | MATCH "core snap \(at .*\) is older \(.*\) than distribution package" echo "Revert back to normal" systemctl stop snapd.service snapd.socket - umount $SNAPMOUNTDIR/core/current/usr/lib/snapd/info + umount $SNAP_MOUNT_DIR/core/current/usr/lib/snapd/info echo "Ensure SNAP_REEXEC=0 is honored for snapd" cat > /etc/systemd/system/snapd.service.d/reexec.conf < out.yaml + diff -u out.yaml snap-interface-core-support.yaml +restore: | + rm -f out.yaml + diff -Nru snapd-2.27.5/tests/main/snap-on-non-shared-root/task.yaml snapd-2.28.5/tests/main/snap-on-non-shared-root/task.yaml --- snapd-2.27.5/tests/main/snap-on-non-shared-root/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/snap-on-non-shared-root/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -5,11 +5,11 @@ . $TESTSLIB/dirs.sh # simulate a system with a non-shared / mount --make-private / - mount --make-private $(readlink -f $SNAPMOUNTDIR/core/current) + mount --make-private $(readlink -f $SNAP_MOUNT_DIR/core/current) restore: | . $TESTSLIB/dirs.sh mount --make-rshared / - mount --make-rshared $(readlink -f $SNAPMOUNTDIR/core/current) + mount --make-rshared $(readlink -f $SNAP_MOUNT_DIR/core/current) execute: | . $TESTSLIB/dirs.sh @@ -21,14 +21,14 @@ snap refresh --edge test-snapd-tools test-snapd-tools.echo hello - echo "Ensure we have a shared mount of $SNAPMOUNTDIR" - cat /proc/self/mountinfo |MATCH "$SNAPMOUNTDIR $SNAPMOUNTDIR.*shared:[0-9]" + echo "Ensure we have a shared mount of $SNAP_MOUNT_DIR" + cat /proc/self/mountinfo |MATCH "$SNAP_MOUNT_DIR $SNAP_MOUNT_DIR.*shared:[0-9]" echo "Run it again for good measure" test-snapd-tools.echo hello - echo "... and ensure we do not mount $SNAPMOUNTDIR again" - n=$(cat /proc/self/mountinfo |grep "$SNAPMOUNTDIR $SNAPMOUNTDIR.*shared:[0-9]"|wc -l) + echo "... and ensure we do not mount $SNAP_MOUNT_DIR again" + n=$(cat /proc/self/mountinfo |grep "$SNAP_MOUNT_DIR $SNAP_MOUNT_DIR.*shared:[0-9]"|wc -l) if [ "$n" -ne 1 ]; then - echo "Incorrect extra $SNAPMOUNTDIR bind mounts created" + echo "Incorrect extra $SNAP_MOUNT_DIR bind mounts created" exit 1 fi \ No newline at end of file diff -Nru snapd-2.27.5/tests/main/snap-remove-not-mounted/task.yaml snapd-2.28.5/tests/main/snap-remove-not-mounted/task.yaml --- snapd-2.27.5/tests/main/snap-remove-not-mounted/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/snap-remove-not-mounted/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -9,7 +9,7 @@ # simulate what happens if someone "snap try /tmp/something" and then # reboots: the dir is gone and nothing can be mounted anymore rm -rf /tmp/test-snapd-tools - umount $SNAPMOUNTDIR/test-snapd-tools/x1 + umount $SNAP_MOUNT_DIR/test-snapd-tools/x1 # ensure removal still works snap remove test-snapd-tools \ No newline at end of file diff -Nru snapd-2.27.5/tests/main/snap-repair/task.yaml snapd-2.28.5/tests/main/snap-repair/task.yaml --- snapd-2.27.5/tests/main/snap-repair/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/snap-repair/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -1,5 +1,7 @@ summary: Ensure that snap-repair is available +systems: [-fedora-*, -opensuse-*] + execute: | . $TESTSLIB/dirs.sh @@ -12,7 +14,7 @@ # All the tests below are only relevant on an ubuntu-core system echo "Check that the snap-repair timer is active" - systemctl list-timers | MATCH snap-repair.timer + systemctl list-timers | MATCH snapd.snap-repair.timer echo "Check that snap-repair can be run" "${LIBEXECDIR}"/snapd/snap-repair run | MATCH "run is not implemented yet" diff -Nru snapd-2.27.5/tests/main/snap-run-alias/task.yaml snapd-2.28.5/tests/main/snap-run-alias/task.yaml --- snapd-2.27.5/tests/main/snap-run-alias/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/snap-run-alias/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -11,8 +11,8 @@ restore: | . $TESTSLIB/dirs.sh - rm -f $SNAPMOUNTDIR/bin/test_echo - rm -f $SNAPMOUNTDIR/bin/test_cat + rm -f $SNAP_MOUNT_DIR/bin/test_echo + rm -f $SNAP_MOUNT_DIR/bin/test_cat rm -f orig.txt rm -f new.txt @@ -25,13 +25,13 @@ execute: | . $TESTSLIB/dirs.sh - SNAP=$SNAPMOUNTDIR/test-snapd-tools/current + SNAP=$SNAP_MOUNT_DIR/test-snapd-tools/current echo Testing that creating an alias symlinks works $APP $SNAP/bin/cat $APP $SNAP/bin/cat > orig.txt 2>&1 - ln -s $APP $SNAPMOUNTDIR/bin/$ALIAS + ln -s $APP $SNAP_MOUNT_DIR/bin/$ALIAS $ALIAS $SNAP/bin/cat $ALIAS $SNAP/bin/cat > new.txt 2>&1 diff -Nru snapd-2.27.5/tests/main/snap-run-hook/task.yaml snapd-2.28.5/tests/main/snap-run-hook/task.yaml --- snapd-2.27.5/tests/main/snap-run-hook/task.yaml 2017-01-16 06:45:53.000000000 +0000 +++ snapd-2.28.5/tests/main/snap-run-hook/task.yaml 2017-09-20 07:05:01.000000000 +0000 @@ -5,6 +5,7 @@ # correctly SNAP_REEXEC/reexec0: 0 SNAP_REEXEC/reexec1: 1 + ENVDUMP: /var/snap/basic-hooks/current/hooks-env prepare: | echo "Build test hooks package" @@ -46,3 +47,9 @@ echo "Expected invalid-hook output to be '$expected_output', but it was '$output'" exit 1 fi + + snap set basic-hooks command=dump-env + echo "Test that environment variables were interpolated" + cat $ENVDUMP | MATCH "^TEST_COMMON=/var/snap/basic-hooks/common$" + cat $ENVDUMP | MATCH "^TEST_DATA=/var/snap/basic-hooks/.*$" + cat $ENVDUMP | MATCH "^TEST_SNAP=/snap/basic-hooks/.*$" diff -Nru snapd-2.27.5/tests/main/snap-run-symlink/task.yaml snapd-2.28.5/tests/main/snap-run-symlink/task.yaml --- snapd-2.27.5/tests/main/snap-run-symlink/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/snap-run-symlink/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -15,14 +15,14 @@ execute: | . $TESTSLIB/dirs.sh - SNAP=$SNAPMOUNTDIR/test-snapd-tools/current + SNAP=$SNAP_MOUNT_DIR/test-snapd-tools/current echo Testing that replacing the wrapper with a symlink works $APP $SNAP/bin/cat $APP $SNAP/bin/cat > orig.txt 2>&1 - rm $SNAPMOUNTDIR/bin/$APP - ln -s /usr/bin/snap $SNAPMOUNTDIR/bin/$APP + rm $SNAP_MOUNT_DIR/bin/$APP + ln -s /usr/bin/snap $SNAP_MOUNT_DIR/bin/$APP $APP $SNAP/bin/cat $APP $SNAP/bin/cat > new.txt 2>&1 diff -Nru snapd-2.27.5/tests/main/snap-run-symlink-error/task.yaml snapd-2.28.5/tests/main/snap-run-symlink-error/task.yaml --- snapd-2.27.5/tests/main/snap-run-symlink-error/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/snap-run-symlink-error/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -1,16 +1,16 @@ summary: Check error handling in symlinks to /usr/bin/snap restore: | . $TESTSLIB/dirs.sh - rm -f $SNAPMOUNTDIR/bin/xxx - rmdir $SNAPMOUNTDIR/bin + rm -f $SNAP_MOUNT_DIR/bin/xxx + rmdir $SNAP_MOUNT_DIR/bin execute: | . $TESTSLIB/dirs.sh echo Setting up incorrect symlink for snap run - mkdir -p $SNAPMOUNTDIR/bin - ln -s /usr/bin/snap $SNAPMOUNTDIR/bin/xxx + mkdir -p $SNAP_MOUNT_DIR/bin + ln -s /usr/bin/snap $SNAP_MOUNT_DIR/bin/xxx echo Running unknown command - expected="internal error, please report: running \"xxx\" failed: cannot find current revision for snap xxx: readlink $SNAPMOUNTDIR/xxx/current: no such file or directory" - output="$($SNAPMOUNTDIR/bin/xxx 2>&1 )" && exit 1 + expected="internal error, please report: running \"xxx\" failed: cannot find current revision for snap xxx: readlink $SNAP_MOUNT_DIR/xxx/current: no such file or directory" + output="$($SNAP_MOUNT_DIR/bin/xxx 2>&1 )" && exit 1 echo $output err=$? echo Verifying error message diff -Nru snapd-2.27.5/tests/main/snap-run-userdata-current/task.yaml snapd-2.28.5/tests/main/snap-run-userdata-current/task.yaml --- snapd-2.27.5/tests/main/snap-run-userdata-current/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/snap-run-userdata-current/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -9,13 +9,13 @@ execute: | . $TESTSLIB/dirs.sh echo "Test that 'current' symlink is created in user data dir" - CURRENT=$(readlink $SNAPMOUNTDIR/test-snapd-tools/current) + CURRENT=$(readlink $SNAP_MOUNT_DIR/test-snapd-tools/current) if [ -z "$CURRENT" ]; then echo "Could not determine current version of $SNAP" exit 1 fi - $SNAPMOUNTDIR/bin/test-snapd-tools.echo -n + $SNAP_MOUNT_DIR/bin/test-snapd-tools.echo -n UDATA_CURRENT=$(readlink $HOME/snap/test-snapd-tools/current) if [ "$CURRENT" != "$UDATA_CURRENT" ]; then echo "Invalid 'current' symlink in user-data directory, expected $CURRENT, got $UDATA_CURRENT" @@ -24,7 +24,7 @@ echo "Test that 'current' symlink is recreated" rm -rf $HOME/snap/test-snapd-tools/current - $SNAPMOUNTDIR/bin/test-snapd-tools.echo -n + $SNAP_MOUNT_DIR/bin/test-snapd-tools.echo -n if [ ! -L $HOME/snap/test-snapd-tools/current ]; then echo "The 'current' symlink not present in user-data directory" exit 1 @@ -32,7 +32,7 @@ echo "Test that 'current' symlink is updated if incorrect" ln -fs $HOME/snap/test-snapd-tools/wrong $HOME/snap/test-snapd-tools/current - $SNAPMOUNTDIR/bin/test-snapd-tools.echo -n + $SNAP_MOUNT_DIR/bin/test-snapd-tools.echo -n UDATA_CURRENT=$(readlink $HOME/snap/test-snapd-tools/current) if [ "$CURRENT" != "$UDATA_CURRENT" ]; then echo "Invalid 'current' symlink in user-data directory, expected $CURRENT, got $UDATA_CURRENT" diff -Nru snapd-2.27.5/tests/main/snap-seccomp/task.yaml snapd-2.28.5/tests/main/snap-seccomp/task.yaml --- snapd-2.27.5/tests/main/snap-seccomp/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/snap-seccomp/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -125,3 +125,14 @@ ( (sleep 3; $SNAP_SECCOMP compile ${PROFILE}.src ${PROFILE}.bin) &) echo "Ensure the code still runs" test-snapd-tools.echo hello | MATCH hello + + if [ "$(uname -p)" = "x86_64" ]; then + echo "Ensure secondary arch works for amd64 with i386 binaries" + snap install --edge test-snapd-hello-multi-arch + test-snapd-hello-multi-arch.hello-i386 + + echo "Ensure secondary arch works in @complain mode too" + snap remove test-snapd-hello-multi-arch + snap install --devmode --edge test-snapd-hello-multi-arch + test-snapd-hello-multi-arch.hello-i386 + fi diff -Nru snapd-2.27.5/tests/main/snap-sign/task.yaml snapd-2.28.5/tests/main/snap-sign/task.yaml --- snapd-2.27.5/tests/main/snap-sign/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/snap-sign/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -1,7 +1,7 @@ summary: Run snap sign to sign a model assertion # ppc64el disabled because of https://bugs.launchpad.net/snappy/+bug/1655594 -systems: [-ubuntu-core-16-*, -ubuntu-*-ppc64el] +systems: [-ubuntu-core-16-*, -ubuntu-*-ppc64el, -fedora-*, -opensuse-*] prepare: | "$TESTSLIB"/mkpinentry.sh diff -Nru snapd-2.27.5/tests/main/snap-switch/task.yaml snapd-2.28.5/tests/main/snap-switch/task.yaml --- snapd-2.27.5/tests/main/snap-switch/task.yaml 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/main/snap-switch/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,8 @@ +summary: Ensure that the snap switch command works + +execute: | + snap install test-snapd-tools + snap info test-snapd-tools|MATCH "tracking: +stable" + + snap switch --edge test-snapd-tools + snap info test-snapd-tools|MATCH "tracking: +edge" diff -Nru snapd-2.27.5/tests/main/snap-userd/task.yaml snapd-2.28.5/tests/main/snap-userd/task.yaml --- snapd-2.27.5/tests/main/snap-userd/task.yaml 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/main/snap-userd/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,76 @@ +summary: Ensure snap userd allows opening a URL via xdg-open + +systems: + # Not supposed to work on Ubuntu Core systems as we don't have + # a user session environment there + - -ubuntu-core-* + +environment: + DISPLAY: :0 + +restore: | + . "$TESTSLIB/dirs.sh" + . "$TESTSLIB/pkgdb.sh" + rm -f dbus.env + umount -f /usr/bin/xdg-open || true + umount -f $SNAP_MOUNT_DIR/core/current/usr/bin/xdg-open || true + +execute: | + . "$TESTSLIB/pkgdb.sh" + . "$TESTSLIB/dirs.sh" + + dbus-launch > dbus.env + export $(cat dbus.env | xargs) + + # wait for session to be ready + ping_launcher() { + dbus-send --session \ + --dest=io.snapcraft.Launcher \ + --type=method_call \ + --print-reply \ + / \ + org.freedesktop.DBus.Peer.Ping + } + while ! ping_launcher ; do + sleep .5 + done + + # Create a small helper which will tell us if snap passes + # the URL correctly to the right handler + cat << 'EOF' > /tmp/xdg-open + #!/bin/sh + echo "$@" > /tmp/xdg-open-output + EOF + chmod +x /tmp/xdg-open + mount --bind /tmp/xdg-open /usr/bin/xdg-open + + # Until necessary changes landed in the core snap we need + # to create a custom one which points to the new service + # name io.snapcraft.Launcher where the current core + # snap still uses com.canonical.Launcher. + cat << 'EOF' > /tmp/xdg-open-core + #!/bin/sh + dbus-send --print-reply --session --dest=io.snapcraft.Launcher /io/snapcraft/Launcher io.snapcraft.Launcher.OpenURL string:"$1" + EOF + chmod +x /tmp/xdg-open-core + mount --bind /tmp/xdg-open-core $SNAP_MOUNT_DIR/core/current/usr/bin/xdg-open + + ensure_xdg_open_output() { + rm -f /tmp/xdg-open-output + export DBUS_SESSION_BUS_ADDRESS=$DBUS_SESSION_BUS_ADDRESS + $SNAP_MOUNT_DIR/core/current/usr/bin/xdg-open $1 + test -e /tmp/xdg-open-output + test "$(cat /tmp/xdg-open-output)" = $1 + } + + # Ensure http, https and mailto work + ensure_xdg_open_output "https://snapcraft.io" + ensure_xdg_open_output "http://snapcraft.io" + ensure_xdg_open_output "mailto:talk@snapcraft.io" + + # Ensure other schemes are not passed through + rm /tmp/xdg-open-output + ! $SNAP_MOUNT_DIR/core/current/usr/bin/xdg-open ftp://snapcraft.io + test ! -e /tmp/xdg-open-output + ! $SNAP_MOUNT_DIR/core/current/usr/bin/xdg-open aabbcc + test ! -e /tmp/xdg-open-output diff -Nru snapd-2.27.5/tests/main/snap-userd-reexec/task.yaml snapd-2.28.5/tests/main/snap-userd-reexec/task.yaml --- snapd-2.27.5/tests/main/snap-userd-reexec/task.yaml 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/main/snap-userd-reexec/task.yaml 2017-10-13 07:00:37.000000000 +0000 @@ -0,0 +1,17 @@ +summary: Check that core refresh will create the userd dbus serivce file + +# only run on systems that re-exec +systems: [ubuntu-16*, ubuntu-17*] + +execute: | + snap list | awk "/^core / {print(\$3)}" > prevBoot + + echo "Ensure service file is created if missing (e.g. on re-exec)" + mv /usr/share/dbus-1/services/io.snapcraft.Launcher.service /usr/share/dbus-1/services/io.snapcraft.Launcher.service.orig + + echo "Install new core" + snap install --dangerous /var/lib/snapd/snaps/core_$(cat prevBoot).snap + + echo "Ensure the dbus service file got created" + test -f /usr/share/dbus-1/services/io.snapcraft.Launcher.service + diff -u /usr/share/dbus-1/services/io.snapcraft.Launcher.service.orig /usr/share/dbus-1/services/io.snapcraft.Launcher.service \ No newline at end of file diff -Nru snapd-2.27.5/tests/main/try/task.yaml snapd-2.28.5/tests/main/try/task.yaml --- snapd-2.27.5/tests/main/try/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/try/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -1,6 +1,7 @@ summary: Check that try command works -systems: - - -ubuntu-core-16-* + +systems: [-ubuntu-core-16-*, -fedora-*, -opensuse-*] + environment: PORT: 8081 SERVICE_FILE: "./service.sh" @@ -10,7 +11,7 @@ prepare: | . $TESTSLIB/systemd.sh echo "Given a service listening on a port" - printf "#!/bin/sh -e\nwhile true; do printf \"HTTP/1.1 200 OK\n\nok\n\" | nc -l -p $PORT -q 1; done" > $SERVICE_FILE + printf "#!/bin/sh -e\nwhile true; do printf \"HTTP/1.1 200 OK\n\nok\n\" | nc -l -p $PORT -w 1; done" > $SERVICE_FILE chmod a+x $SERVICE_FILE systemd_create_and_start_unit $SERVICE_NAME "$(readlink -f $SERVICE_FILE)" diff -Nru snapd-2.27.5/tests/main/try-snap-goes-away/task.yaml snapd-2.28.5/tests/main/try-snap-goes-away/task.yaml --- snapd-2.27.5/tests/main/try-snap-goes-away/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/main/try-snap-goes-away/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -30,7 +30,7 @@ . $TESTSLIB/dirs.sh echo And all its binaries - N="$(ls $SNAPMOUNTDIR/bin/$SNAP_NAME*|wc -l)" + N="$(ls $SNAP_MOUNT_DIR/bin/$SNAP_NAME*|wc -l)" if [ "$N" -ne 0 ]; then echo "Some binaries are not cleaned" exit 1 diff -Nru snapd-2.27.5/tests/nested/core-revert/task.yaml snapd-2.28.5/tests/nested/core-revert/task.yaml --- snapd-2.27.5/tests/nested/core-revert/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/nested/core-revert/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -3,14 +3,10 @@ systems: [ubuntu-16.04-64] prepare: | - apt install -y --no-install-recommends kpartx - . "$TESTSLIB/nested.sh" create_nested_core_vm restore: | - apt autoremove -y --purge kpartx - . "$TESTSLIB/nested.sh" destroy_nested_core_vm diff -Nru snapd-2.27.5/tests/nested/extra-snaps-assertions/task.yaml snapd-2.28.5/tests/nested/extra-snaps-assertions/task.yaml --- snapd-2.27.5/tests/nested/extra-snaps-assertions/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/nested/extra-snaps-assertions/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -64,4 +64,4 @@ execute_remote "snap known model" | MATCH "series: 16" echo "Make sure core has an actual revision" - execute_remote "snap list" | MATCH "^core +[0-9]+\-[0-9]+ +[0-9]+ +canonical +\-" + execute_remote "snap list" | MATCH "^core +[0-9]+\-[0-9.]+ +[0-9]+ +canonical +\-" diff -Nru snapd-2.27.5/tests/nightly/docker/task.yaml snapd-2.28.5/tests/nightly/docker/task.yaml --- snapd-2.27.5/tests/nightly/docker/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/nightly/docker/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -2,18 +2,12 @@ systems: [ubuntu-16.04-*, ubuntu-core-16-*, ubuntu-14.04-64] prepare: | - if apt show "linux-image-extra-$(uname -r)"; then - apt install -y "linux-image-extra-$(uname -r)" - fi modprobe aufs # we don't distort the download statistics, we run with SNAPPY_TESTING=1 # which will add "testing" to the user-agent header and the store does # not count this. snap install docker -restore: | - apt remove -y "linux-image-extra-$(uname -r)-generic" || true - debug: | cat /var/log/syslog dmesg diff -Nru snapd-2.27.5/tests/nightly/unity/task.yaml snapd-2.28.5/tests/nightly/unity/task.yaml --- snapd-2.27.5/tests/nightly/unity/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/nightly/unity/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -11,11 +11,11 @@ # and doesn't install cleanly if that file is in place mv /etc/init/tty1.conf /etc/init/tty1.conf.back - apt install -y --no-install-recommends x11-utils xvfb unity + apt install -y --no-install-recommends unity disabled_restore: | systemctl stop unity-app - apt autoremove -y --purge x11-utils xvfb unity + apt autoremove -y --purge unity mv /etc/init/tty1.conf.back /etc/init/tty1.conf diff -Nru snapd-2.27.5/tests/regression/lp-1595444/task.yaml snapd-2.28.5/tests/regression/lp-1595444/task.yaml --- snapd-2.27.5/tests/regression/lp-1595444/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/regression/lp-1595444/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -1,18 +1,24 @@ summary: Regression check for https://bugs.launchpad.net/snap-confine/+bug/1595444 + details: | This task checks the behavior of snap-confine when it is started from a directory that doesn't exist in the execution environment (chroot). + systems: # This test only applies to classic systems - -ubuntu-core-16-* # No confinement (AppArmor, Seccomp) available on these systems - -debian-* + - -fedora-* + - -opensuse-* + prepare: | echo "Having installed the test snap" . "$TESTSLIB/snaps.sh" install_local test-snapd-tools echo "Hanving created a directory not present in the core snap" mkdir -p "/foo" + execute: | echo "We can go to a location that is available in all snaps (/tmp)" echo "We can run the 'pwd' tool and it reports /tmp" @@ -22,6 +28,7 @@ [ "$(cd /foo && test-snapd-tools.cmd pwd)" = "/var/lib/snapd/void" ] echo "And that directory is not readable or writable" [ "$(cd /foo && test-snapd-tools.cmd ls 2>&1)" = "ls: cannot open directory '.': Permission denied" ]; + restore: | rm -f -d /foo # NOTE: the snap is blocked by apparmor from reading /var/lib/snapd/void diff -Nru snapd-2.27.5/tests/regression/lp-1597839/task.yaml snapd-2.28.5/tests/regression/lp-1597839/task.yaml --- snapd-2.27.5/tests/regression/lp-1597839/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/regression/lp-1597839/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -1,6 +1,6 @@ summary: Regression check for https://bugs.launchpad.net/snap-confine/+bug/1597839 # This test only applies to classic systems -systems: [-ubuntu-core-16-*] +systems: [-ubuntu-core-16-*, -fedora-*] details: | The snappy execution environment should contain the /lib/modules directory from the host filesystem when running on a classic distribution diff -Nru snapd-2.27.5/tests/regression/lp-1597842/task.yaml snapd-2.28.5/tests/regression/lp-1597842/task.yaml --- snapd-2.27.5/tests/regression/lp-1597842/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/regression/lp-1597842/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -4,7 +4,7 @@ directory from the classic distribution. Certain snaps may use that to access kernel sources. # This test only applies to classic systems -systems: [-ubuntu-core-16-*] +systems: [-ubuntu-core-16-*, -fedora-*] prepare: | echo "Having installed the test snap" . "$TESTSLIB/snaps.sh" diff -Nru snapd-2.27.5/tests/regression/lp-1599891/task.yaml snapd-2.28.5/tests/regression/lp-1599891/task.yaml --- snapd-2.27.5/tests/regression/lp-1599891/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/regression/lp-1599891/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -2,6 +2,8 @@ systems: # No confinement (AppArmor, Seccomp) available on these systems - -debian-* + - -fedora-* + - -opensuse-* execute: | snap_confine=/usr/lib/snapd/snap-confine echo "Seeing that snap-confine is in $snap_confine" diff -Nru snapd-2.27.5/tests/regression/lp-1613845/task.yaml snapd-2.28.5/tests/regression/lp-1613845/task.yaml --- snapd-2.27.5/tests/regression/lp-1613845/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/regression/lp-1613845/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -5,7 +5,7 @@ the host filesystem. While this would never work in an all-snap image it is still important to ensure that it works in classic devmode environment. # This test only applies to classic systems -systems: [-ubuntu-core-16-*] +systems: [-ubuntu-core-16-*, -fedora-*] prepare: | echo "Having installed the test snap in devmode" . "$TESTSLIB/snaps.sh" diff -Nru snapd-2.27.5/tests/regression/lp-1630479/task.yaml snapd-2.28.5/tests/regression/lp-1630479/task.yaml --- snapd-2.27.5/tests/regression/lp-1630479/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/regression/lp-1630479/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -12,7 +12,7 @@ execute: | . "$TESTSLIB/dirs.sh" echo "We can observe the value of PATH twice" - revision=$(readlink "$SNAPMOUNTDIR/test-snapd-tools/current") + revision=$(readlink "$SNAP_MOUNT_DIR/test-snapd-tools/current") one="$(test-snapd-tools.cmd sh -c 'echo $PATH')" two="$(test-snapd-tools.cmd sh -c 'echo $PATH')" echo "We can see that PATH is stable across calls" @@ -20,8 +20,8 @@ echo "We can make sure that it has the right value" [ "$one" = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games" ] # NOTE: the following parts are notably absent from PATH - # - $SNAPMOUNTDIR/test-snapd-tools/$revision/bin - # - $SNAPMOUNTDIR/test-snapd-tools/$revision/usr/bin + # - $SNAP_MOUNT_DIR/test-snapd-tools/$revision/bin + # - $SNAP_MOUNT_DIR/test-snapd-tools/$revision/usr/bin # # This is because our test snap is not built with snapcraft and those path # elements are only added by the wrappers generated by snapcraft. diff -Nru snapd-2.27.5/tests/regression/lp-1641885/task.yaml snapd-2.28.5/tests/regression/lp-1641885/task.yaml --- snapd-2.27.5/tests/regression/lp-1641885/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/regression/lp-1641885/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -2,6 +2,8 @@ systems: # No confinement (AppArmor, Seccomp) available on these systems - -debian-* + - -fedora-* + - -opensuse-* details: | Users found that a snap that uses "confinement: devmode", even when installed with "snap install --jailmode" is effectively in devmode (the diff -Nru snapd-2.27.5/tests/regression/lp-1644439/task.yaml snapd-2.28.5/tests/regression/lp-1644439/task.yaml --- snapd-2.27.5/tests/regression/lp-1644439/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/regression/lp-1644439/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -38,11 +38,11 @@ . "$TESTSLIB/dirs.sh" echo "We can now run a snap command from the namespace of a snap command and see it work" test-snapd-tools.cmd /bin/true - test-snapd-tools.cmd /bin/sh -c "SNAP_CONFINE_DEBUG=yes $SNAPMOUNTDIR/bin/test-snapd-tools.cmd /bin/true" + test-snapd-tools.cmd /bin/sh -c "SNAP_CONFINE_DEBUG=yes $SNAP_MOUNT_DIR/bin/test-snapd-tools.cmd /bin/true" echo "We can now discard the namespace and repeat the test as a non-root user" /usr/lib/snapd/snap-discard-ns test-snapd-tools su -l -c 'test-snapd-tools.cmd /bin/true' test - su -l -c "test-snapd-tools.cmd /bin/sh -c \"SNAP_CONFINE_DEBUG=yes $SNAPMOUNTDIR/bin/test-snapd-tools.cmd /bin/true\"" test + su -l -c "test-snapd-tools.cmd /bin/sh -c \"SNAP_CONFINE_DEBUG=yes $SNAP_MOUNT_DIR/bin/test-snapd-tools.cmd /bin/true\"" test debug: | # Kernel version is an important input in understing failures of this test uname -a diff -Nru snapd-2.27.5/tests/regression/nmcli/task.yaml snapd-2.28.5/tests/regression/nmcli/task.yaml --- snapd-2.27.5/tests/regression/nmcli/task.yaml 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/regression/nmcli/task.yaml 2017-09-14 13:53:11.000000000 +0000 @@ -0,0 +1,24 @@ +summary: Checks basic network-manager/nmcli functionality + +description: | + Test regression caused by seccomp argument filtering getting + stricter but the network-manager plug side was not updated + to include "socket AF_NETLINK - NETLINK_KOBJECT_UEVENT" which + is vital for nmcli to work. + +systems: [ubuntu-core-*] + +execute: | + echo "Install network-manager and do basic smoke test" + snap install network-manager + + # using wait_for_service is not enough, systemd considers + # the service active even when it is not (yet) listening to + # dbus + for i in $(seq 300); do + if network-manager.nmcli general; then + break + fi + sleep 1 + done + network-manager.nmcli d show diff -Nru snapd-2.27.5/tests/unit/c-unit-tests/task.yaml snapd-2.28.5/tests/unit/c-unit-tests/task.yaml --- snapd-2.27.5/tests/unit/c-unit-tests/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/unit/c-unit-tests/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -1,12 +1,9 @@ summary: Run the test suite for C code -environment: - EXTRA_PKGS: autoconf automake autotools-dev indent libapparmor-dev libglib2.0-dev libseccomp-dev libudev-dev pkg-config python3-docutils udev prepare: | # Sanity check, the core snap is installed snap info core | MATCH "installed:" # Install build dependencies for the test dpkg --get-selections > pkg-list - apt-get install --yes $EXTRA_PKGS # Remove any autogarbage from sent by developer rm -rf "$SPREAD_PATH/cmd/"{autom4te.cache,configure,test-driver,config.status,config.guess,config.sub,config.h.in,compile,install-sh,depcomp,build,missing,aclocal.m4,Makefile,Makefile.in} make -C "$SPREAD_PATH/cmd" distclean || true diff -Nru snapd-2.27.5/tests/unit/gccgo/task.yaml snapd-2.28.5/tests/unit/gccgo/task.yaml --- snapd-2.27.5/tests/unit/gccgo/task.yaml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/tests/unit/gccgo/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -3,11 +3,9 @@ systems: [ubuntu-16.04-64] prepare: | echo Installing gccgo-6 and pretending it is the default go - apt install -y gccgo-6 ln -s /usr/bin/go-6 /usr/local/bin/go restore: | rm -f /usr/local/bin/go - apt-get autoremove -y gccgo-6 execute: | echo Ensure we really build with gccgo go version|MATCH gccgo diff -Nru snapd-2.27.5/tests/upgrade/basic/task.yaml snapd-2.28.5/tests/upgrade/basic/task.yaml --- snapd-2.27.5/tests/upgrade/basic/task.yaml 2017-08-24 06:52:40.000000000 +0000 +++ snapd-2.28.5/tests/upgrade/basic/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -11,43 +11,52 @@ exit 0 fi . "$TESTSLIB/pkgdb.sh" + . "$TESTSLIB/snaps.sh" - echo Install previous version... - dpkg -l snapd snap-confine || true - apt-get install -y snapd + echo "Remove snapd and snap-confine" + distro_purge_package snapd snapd-selinux snap-confine || true + + echo "Install previous snapd version from the store" + distro_install_package snap-confine snapd prevsnapdver=$(snap --version|grep "snapd ") if [[ "$SPREAD_SYSTEM" = debian-* ]] ; then - # For debian we install the latest core snap from beta instead - # from stable as stable and candidate are broken at the moment. - # FIXME: drop this again once 2.25 landed in stable. - snap install --beta core + # For debian we install the latest core snap independently until + # the bug fix is on stable once 2.27 landed + snap install core fi - echo Install sanity check snaps with it + echo "Install sanity check snaps with it" snap install test-snapd-tools snap install test-snapd-auto-aliases + if is_classic_confinement_supported; then + install_local_classic test-snapd-classic-confinement + fi - echo Sanity check installs + echo "Sanity check installs" test-snapd-tools.echo Hello | grep Hello test-snapd-tools.env | grep SNAP_NAME=test-snapd-tools test_snapd_wellknown1|MATCH "ok wellknown 1" test_snapd_wellknown2|MATCH "ok wellknown 2" - echo Do upgrade + echo "Do upgrade" # allow-downgrades prevents errors when new versions hit the archive, for instance, # trying to install 2.11ubuntu1 over 2.11+0.16.04 - distro_install_local_package --allow-downgrades "$GOHOME"/snapd*.deb + pkg_extension="$(distro_get_package_extension)" + distro_install_local_package --allow-downgrades "$GOHOME"/snap*."$pkg_extension" snapdver=$(snap --version|grep "snapd ") [ "$snapdver" != "$prevsnapdver" ] - echo Sanity check already installed snaps after upgrade + echo "Sanity check already installed snaps after upgrade" snap list | grep core snap list | grep test-snapd-tools test-snapd-tools.echo Hello | grep Hello test-snapd-tools.env | grep SNAP_NAME=test-snapd-tools + if is_classic_confinement_supported; then + test-snapd-classic-confinement.recurse 5 + fi # only test if confinement works and we actually have apparmor available # FIXME: this will be converted to a better check once we added the @@ -65,7 +74,7 @@ snap aliases|MATCH "test-snapd-auto-aliases.wellknown1 +test_snapd_wellknown1 +-" snap aliases|MATCH "test-snapd-auto-aliases.wellknown2 +test_snapd_wellknown2 +-" - echo Check migrating to types in state + echo "Check migrating to types in state" coreType=$(jq -r '.data.snaps["core"].type' /var/lib/snapd/state.json) testSnapType=$(jq -r '.data.snaps["test-snapd-tools"].type' /var/lib/snapd/state.json) [ "$coreType" = "os" ] diff -Nru snapd-2.27.5/tests/upgrade/snapd-xdg-open/task.yaml snapd-2.28.5/tests/upgrade/snapd-xdg-open/task.yaml --- snapd-2.27.5/tests/upgrade/snapd-xdg-open/task.yaml 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/tests/upgrade/snapd-xdg-open/task.yaml 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,38 @@ +summary: Verify snapd-xdg-open package is properly replaced with the snapd one +description: | + snapd-xdg-open was formerly provided by the snapd-xdg-open package + and is now part of the snapd package. This test case verifies that + the snapd-xdg-open package from the archive is properly replaced. +# Test case only applies to Ubuntu as that is the only distribution where +# we had a snapd-xdg-open package ever. +systems: [-ubuntu-core-*, -debian-*, -ubuntu-14.04-*, -fedora-*] +restore: | + . "$TESTSLIB/pkgdb.sh" + if [ "$REMOTE_STORE" = staging ]; then + echo "skip upgrade tests while talking to the staging store" + exit 0 + fi + distro_purge_package snapd-xdg-open || true +execute: | + . "$TESTSLIB/pkgdb.sh" + + # Original version of snapd-xdg-open in 16.04 which was not + # part of the snapd source package. + ver=0.0.0~16.04 + if ! distro_install_package snapd-xdg-open=$ver; then + # version of snapd-xdg-open in 17.04,17.10 + ver=0.0.0 + if ! distro_install_package snapd-xdg-open=$ver; then + echo "SKIP: cannot find snapd-xdg-open, skipping test" + exit 0 + fi + fi + + prevsnapdxdgver=$(dpkg-query --showformat='${Version}' --show snapd-xdg-open) + + # allow-downgrades prevents errors when new versions hit the archive, for instance, + # trying to install 2.11ubuntu1 over 2.11+0.16.04 + distro_install_local_package --allow-downgrades $GOHOME/snapd*.deb + + snapdxdgver=$(dpkg-query --showformat='${Version}' --show snapd-xdg-open) + [ "$snapdxdgver" != "$prevsnapdxdgver" ] diff -Nru snapd-2.27.5/testutil/dbustest.go snapd-2.28.5/testutil/dbustest.go --- snapd-2.27.5/testutil/dbustest.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/testutil/dbustest.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,69 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2015 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 testutil + +import ( + "fmt" + "os" + "os/exec" + + "github.com/godbus/dbus" + + . "gopkg.in/check.v1" +) + +// DBusTest provides a separate dbus session bus for running tests +type DBusTest struct { + tmpdir string + dbusDaemon *exec.Cmd + oldSessionBusEnv string + + // the dbus.Conn to the session bus that tests can use + SessionBus *dbus.Conn +} + +func (s *DBusTest) SetUpSuite(c *C) { + if _, err := exec.LookPath("dbus-daemon"); err != nil { + c.Skip(fmt.Sprintf("cannot run test without dbus-daemon: %s", err)) + return + } + if _, err := exec.LookPath("dbus-launch"); err != nil { + c.Skip(fmt.Sprintf("cannot run test without dbus-launch: %s", err)) + return + } + + s.tmpdir = c.MkDir() + s.dbusDaemon = exec.Command("dbus-daemon", "--session", fmt.Sprintf("--address=unix:%s/user_bus_socket", s.tmpdir)) + err := s.dbusDaemon.Start() + c.Assert(err, IsNil) + s.oldSessionBusEnv = os.Getenv("DBUS_SESSION_BUS_ADDRESS") + + s.SessionBus, err = dbus.SessionBus() + c.Assert(err, IsNil) +} + +func (s *DBusTest) TearDownSuite(c *C) { + os.Setenv("DBUS_SESSION_BUS_ADDRESS", s.oldSessionBusEnv) + if s.dbusDaemon != nil && s.dbusDaemon.Process != nil { + err := s.dbusDaemon.Process.Kill() + c.Assert(err, IsNil) + } + +} diff -Nru snapd-2.27.5/.travis.yml snapd-2.28.5/.travis.yml --- snapd-2.27.5/.travis.yml 2017-08-24 06:46:40.000000000 +0000 +++ snapd-2.28.5/.travis.yml 2017-10-10 18:11:24.000000000 +0000 @@ -13,7 +13,6 @@ quiet: true install: - - curl -s -o - http://canihazip.com/s - sudo apt-get update -qq - sudo apt-get install -qq squashfs-tools xdelta3 - sudo apt-get install -qq gnupg1 || sudo apt-get install -qq gnupg diff -Nru snapd-2.27.5/userd/launcher.go snapd-2.28.5/userd/launcher.go --- snapd-2.27.5/userd/launcher.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/userd/launcher.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,88 @@ +// -*- 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 userd + +import ( + "fmt" + "net/url" + "os/exec" + + "github.com/godbus/dbus" + "github.com/snapcore/snapd/strutil" +) + +const launcherIntrospectionXML = ` + + + + + + + + + + + +` + +var ( + allowedURLSchemes = []string{"http", "https", "mailto"} +) + +// Launcher implements the 'io.snapcraft.Launcher' DBus interface. +type Launcher struct{} + +// Name returns the name of the interface this object implements +func (s *Launcher) Name() string { + return "io.snapcraft.Launcher" +} + +// IntrospectionData gives the XML formatted introspection description +// of the DBus service. +func (s *Launcher) IntrospectionData() string { + return launcherIntrospectionXML +} + +func makeAccessDeniedError(err error) *dbus.Error { + return &dbus.Error{ + Name: "org.freedesktop.DBus.Error.AccessDenied", + Body: []interface{}{err.Error()}, + } +} + +// OpenURL implements the 'OpenURL' method of the 'com.canonical.Launcher' +// DBus interface. Before the provided url is passed to xdg-open the scheme is +// validated against a list of allowed schemes. All other schemes are denied. +func (s *Launcher) OpenURL(addr string) *dbus.Error { + u, err := url.Parse(addr) + if err != nil { + return &dbus.ErrMsgInvalidArg + } + + if !strutil.ListContains(allowedURLSchemes, u.Scheme) { + return makeAccessDeniedError(fmt.Errorf("Supplied URL scheme %q is not allowed", u.Scheme)) + } + + if err = exec.Command("xdg-open", addr).Run(); err != nil { + return dbus.MakeFailedError(fmt.Errorf("cannot open supplied URL")) + } + + return nil +} diff -Nru snapd-2.27.5/userd/launcher_test.go snapd-2.28.5/userd/launcher_test.go --- snapd-2.27.5/userd/launcher_test.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/userd/launcher_test.go 2017-09-13 14:47:18.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 userd_test + +import ( + "github.com/godbus/dbus" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/testutil" + "github.com/snapcore/snapd/userd" +) + +type launcherSuite struct { + launcher *userd.Launcher + + mockXdgOpen *testutil.MockCmd +} + +var _ = Suite(&launcherSuite{}) + +func (s *launcherSuite) SetUpTest(c *C) { + s.launcher = &userd.Launcher{} + s.mockXdgOpen = testutil.MockCommand(c, "xdg-open", "") +} + +func (s *launcherSuite) TearDownTest(c *C) { + s.mockXdgOpen.Restore() +} + +func (s *launcherSuite) TestOpenURLWithNotAllowedScheme(c *C) { + for _, t := range []struct { + url string + errMatcher string + }{ + {"tel://049112233445566", "Supplied URL scheme \"tel\" is not allowed"}, + {"aabbccdd0011", "Supplied URL scheme \"\" is not allowed"}, + {"invälid:%url", dbus.ErrMsgInvalidArg.Error()}, + } { + err := s.launcher.OpenURL(t.url) + c.Assert(err, ErrorMatches, t.errMatcher) + c.Assert(s.mockXdgOpen.Calls(), IsNil) + } +} + +func (s *launcherSuite) TestOpenURLWithAllowedSchemeHappy(c *C) { + for _, schema := range []string{"http", "https", "mailto"} { + err := s.launcher.OpenURL(schema + "://snapcraft.io") + c.Assert(err, IsNil) + c.Assert(s.mockXdgOpen.Calls(), DeepEquals, [][]string{ + {"xdg-open", schema + "://snapcraft.io"}, + }) + s.mockXdgOpen.ForgetCalls() + } +} + +func (s *launcherSuite) TestOpenURLWithFailingXdgOpen(c *C) { + cmd := testutil.MockCommand(c, "xdg-open", "false") + defer cmd.Restore() + + err := s.launcher.OpenURL("https://snapcraft.io") + c.Assert(err, NotNil) + c.Assert(err, ErrorMatches, "cannot open supplied URL") +} diff -Nru snapd-2.27.5/userd/userd.go snapd-2.28.5/userd/userd.go --- snapd-2.27.5/userd/userd.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/userd/userd.go 2017-09-13 14:47:18.000000000 +0000 @@ -0,0 +1,113 @@ +// -*- 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 userd + +import ( + "bytes" + "fmt" + + "github.com/godbus/dbus" + "github.com/godbus/dbus/introspect" + "gopkg.in/tomb.v2" + + "github.com/snapcore/snapd/logger" +) + +const ( + busName = "io.snapcraft.Launcher" + basePath = "/io/snapcraft/Launcher" +) + +type dbusInterface interface { + Name() string + IntrospectionData() string +} + +type Userd struct { + tomb tomb.Tomb + conn *dbus.Conn + dbusIfaces []dbusInterface +} + +func (ud *Userd) createAndExportInterfaces() { + ud.dbusIfaces = []dbusInterface{&Launcher{}} + + var buffer bytes.Buffer + buffer.WriteString("") + + for _, iface := range ud.dbusIfaces { + ud.conn.Export(iface, basePath, iface.Name()) + buffer.WriteString(iface.IntrospectionData()) + } + + buffer.WriteString(introspect.IntrospectDataString) + buffer.WriteString("") + + ud.conn.Export(introspect.Introspectable(buffer.String()), basePath, "org.freedesktop.DBus.Introspectable") +} + +func (ud *Userd) Init() error { + var err error + + ud.conn, err = dbus.SessionBus() + if err != nil { + return err + } + + reply, err := ud.conn.RequestName(busName, dbus.NameFlagDoNotQueue) + if err != nil { + return err + } + + if reply != dbus.RequestNameReplyPrimaryOwner { + err = fmt.Errorf("cannot obtain bus name '%s'", busName) + return err + } + + ud.createAndExportInterfaces() + return nil +} + +func (ud *Userd) Start() { + logger.Noticef("Starting snap userd") + + ud.tomb.Go(func() error { + // Listen to keep our thread up and running. All DBus bits + // are running in the background + select { + case <-ud.tomb.Dying(): + ud.conn.Close() + } + err := ud.tomb.Err() + if err != nil && err != tomb.ErrStillAlive { + return err + } + return nil + }) +} + +func (ud *Userd) Stop() error { + ud.tomb.Kill(nil) + return ud.tomb.Wait() +} + +func (ud *Userd) Dying() <-chan struct{} { + return ud.tomb.Dying() +} diff -Nru snapd-2.27.5/vendor/github.com/cheggaaa/pb/format.go snapd-2.28.5/vendor/github.com/cheggaaa/pb/format.go --- snapd-2.27.5/vendor/github.com/cheggaaa/pb/format.go 2016-08-30 11:53:30.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/cheggaaa/pb/format.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,87 +0,0 @@ -package pb - -import ( - "fmt" - "strings" - "time" -) - -type Units int - -const ( - // U_NO are default units, they represent a simple value and are not formatted at all. - U_NO Units = iota - // U_BYTES units are formatted in a human readable way (b, Bb, Mb, ...) - U_BYTES - // U_DURATION units are formatted in a human readable way (3h14m15s) - U_DURATION -) - -func Format(i int64) *formatter { - return &formatter{n: i} -} - -type formatter struct { - n int64 - unit Units - width int - perSec bool -} - -func (f *formatter) Value(n int64) *formatter { - f.n = n - return f -} - -func (f *formatter) To(unit Units) *formatter { - f.unit = unit - return f -} - -func (f *formatter) Width(width int) *formatter { - f.width = width - return f -} - -func (f *formatter) PerSec() *formatter { - f.perSec = true - return f -} - -func (f *formatter) String() (out string) { - switch f.unit { - case U_BYTES: - out = formatBytes(f.n) - case U_DURATION: - d := time.Duration(f.n) - if d > time.Hour*24 { - out = fmt.Sprintf("%dd", d/24/time.Hour) - d -= (d / time.Hour / 24) * (time.Hour * 24) - } - out = fmt.Sprintf("%s%v", out, d) - default: - out = fmt.Sprintf(fmt.Sprintf("%%%dd", f.width), f.n) - } - if f.perSec { - out += "/s" - } - return -} - -// Convert bytes to human readable string. Like a 2 MB, 64.2 KB, 52 B -func formatBytes(i int64) (result string) { - switch { - case i > (1024 * 1024 * 1024 * 1024): - result = fmt.Sprintf("%.02f TB", float64(i)/1024/1024/1024/1024) - case i > (1024 * 1024 * 1024): - result = fmt.Sprintf("%.02f GB", float64(i)/1024/1024/1024) - case i > (1024 * 1024): - result = fmt.Sprintf("%.02f MB", float64(i)/1024/1024) - case i > 1024: - result = fmt.Sprintf("%.02f KB", float64(i)/1024) - default: - result = fmt.Sprintf("%d B", i) - } - result = strings.Trim(result, " ") - return -} diff -Nru snapd-2.27.5/vendor/github.com/cheggaaa/pb/LICENSE snapd-2.28.5/vendor/github.com/cheggaaa/pb/LICENSE --- snapd-2.27.5/vendor/github.com/cheggaaa/pb/LICENSE 2016-08-30 11:53:30.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/cheggaaa/pb/LICENSE 1970-01-01 00:00:00.000000000 +0000 @@ -1,12 +0,0 @@ -Copyright (c) 2012-2015, Sergey Cherepanov -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff -Nru snapd-2.27.5/vendor/github.com/cheggaaa/pb/pb_appengine.go snapd-2.28.5/vendor/github.com/cheggaaa/pb/pb_appengine.go --- snapd-2.27.5/vendor/github.com/cheggaaa/pb/pb_appengine.go 2016-08-30 11:53:30.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/cheggaaa/pb/pb_appengine.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,11 +0,0 @@ -// +build appengine - -package pb - -import "errors" - -// terminalWidth returns width of the terminal, which is not supported -// and should always failed on appengine classic which is a sandboxed PaaS. -func terminalWidth() (int, error) { - return 0, errors.New("Not supported") -} diff -Nru snapd-2.27.5/vendor/github.com/cheggaaa/pb/pb.go snapd-2.28.5/vendor/github.com/cheggaaa/pb/pb.go --- snapd-2.27.5/vendor/github.com/cheggaaa/pb/pb.go 2016-08-30 11:53:30.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/cheggaaa/pb/pb.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,441 +0,0 @@ -// Simple console progress bars -package pb - -import ( - "fmt" - "io" - "math" - "strings" - "sync" - "sync/atomic" - "time" - "unicode/utf8" -) - -// Current version -const Version = "1.0.5" - -const ( - // Default refresh rate - 200ms - DEFAULT_REFRESH_RATE = time.Millisecond * 200 - FORMAT = "[=>-]" -) - -// DEPRECATED -// variables for backward compatibility, from now do not work -// use pb.Format and pb.SetRefreshRate -var ( - DefaultRefreshRate = DEFAULT_REFRESH_RATE - BarStart, BarEnd, Empty, Current, CurrentN string -) - -// Create new progress bar object -func New(total int) *ProgressBar { - return New64(int64(total)) -} - -// Create new progress bar object using int64 as total -func New64(total int64) *ProgressBar { - pb := &ProgressBar{ - Total: total, - RefreshRate: DEFAULT_REFRESH_RATE, - ShowPercent: true, - ShowCounters: true, - ShowBar: true, - ShowTimeLeft: true, - ShowFinalTime: true, - Units: U_NO, - ManualUpdate: false, - finish: make(chan struct{}), - currentValue: -1, - mu: new(sync.Mutex), - } - return pb.Format(FORMAT) -} - -// Create new object and start -func StartNew(total int) *ProgressBar { - return New(total).Start() -} - -// Callback for custom output -// For example: -// bar.Callback = func(s string) { -// mySuperPrint(s) -// } -// -type Callback func(out string) - -type ProgressBar struct { - current int64 // current must be first member of struct (https://code.google.com/p/go/issues/detail?id=5278) - - Total int64 - RefreshRate time.Duration - ShowPercent, ShowCounters bool - ShowSpeed, ShowTimeLeft, ShowBar bool - ShowFinalTime bool - Output io.Writer - Callback Callback - NotPrint bool - Units Units - Width int - ForceWidth bool - ManualUpdate bool - AutoStat bool - - // Default width for the time box. - UnitsWidth int - TimeBoxWidth int - - finishOnce sync.Once //Guards isFinish - finish chan struct{} - isFinish bool - - startTime time.Time - startValue int64 - currentValue int64 - - prefix, postfix string - - mu *sync.Mutex - lastPrint string - - BarStart string - BarEnd string - Empty string - Current string - CurrentN string - - AlwaysUpdate bool -} - -// Start print -func (pb *ProgressBar) Start() *ProgressBar { - pb.startTime = time.Now() - pb.startValue = pb.current - if pb.Total == 0 { - pb.ShowTimeLeft = false - pb.ShowPercent = false - pb.AutoStat = false - } - if !pb.ManualUpdate { - pb.Update() // Initial printing of the bar before running the bar refresher. - go pb.refresher() - } - return pb -} - -// Increment current value -func (pb *ProgressBar) Increment() int { - return pb.Add(1) -} - -// Get current value -func (pb *ProgressBar) Get() int64 { - c := atomic.LoadInt64(&pb.current) - return c -} - -// Set current value -func (pb *ProgressBar) Set(current int) *ProgressBar { - return pb.Set64(int64(current)) -} - -// Set64 sets the current value as int64 -func (pb *ProgressBar) Set64(current int64) *ProgressBar { - atomic.StoreInt64(&pb.current, current) - return pb -} - -// Add to current value -func (pb *ProgressBar) Add(add int) int { - return int(pb.Add64(int64(add))) -} - -func (pb *ProgressBar) Add64(add int64) int64 { - return atomic.AddInt64(&pb.current, add) -} - -// Set prefix string -func (pb *ProgressBar) Prefix(prefix string) *ProgressBar { - pb.prefix = prefix - return pb -} - -// Set postfix string -func (pb *ProgressBar) Postfix(postfix string) *ProgressBar { - pb.postfix = postfix - return pb -} - -// Set custom format for bar -// Example: bar.Format("[=>_]") -// Example: bar.Format("[\x00=\x00>\x00-\x00]") // \x00 is the delimiter -func (pb *ProgressBar) Format(format string) *ProgressBar { - var formatEntries []string - if len(format) == 5 { - formatEntries = strings.Split(format, "") - } else { - formatEntries = strings.Split(format, "\x00") - } - if len(formatEntries) == 5 { - pb.BarStart = formatEntries[0] - pb.BarEnd = formatEntries[4] - pb.Empty = formatEntries[3] - pb.Current = formatEntries[1] - pb.CurrentN = formatEntries[2] - } - return pb -} - -// Set bar refresh rate -func (pb *ProgressBar) SetRefreshRate(rate time.Duration) *ProgressBar { - pb.RefreshRate = rate - return pb -} - -// Set units -// bar.SetUnits(U_NO) - by default -// bar.SetUnits(U_BYTES) - for Mb, Kb, etc -func (pb *ProgressBar) SetUnits(units Units) *ProgressBar { - pb.Units = units - return pb -} - -// Set max width, if width is bigger than terminal width, will be ignored -func (pb *ProgressBar) SetMaxWidth(width int) *ProgressBar { - pb.Width = width - pb.ForceWidth = false - return pb -} - -// Set bar width -func (pb *ProgressBar) SetWidth(width int) *ProgressBar { - pb.Width = width - pb.ForceWidth = true - return pb -} - -// End print -func (pb *ProgressBar) Finish() { - //Protect multiple calls - pb.finishOnce.Do(func() { - close(pb.finish) - pb.write(atomic.LoadInt64(&pb.current)) - if !pb.NotPrint { - fmt.Println() - } - pb.isFinish = true - }) -} - -// End print and write string 'str' -func (pb *ProgressBar) FinishPrint(str string) { - pb.Finish() - fmt.Println(str) -} - -// implement io.Writer -func (pb *ProgressBar) Write(p []byte) (n int, err error) { - n = len(p) - pb.Add(n) - return -} - -// implement io.Reader -func (pb *ProgressBar) Read(p []byte) (n int, err error) { - n = len(p) - pb.Add(n) - return -} - -// Create new proxy reader over bar -// Takes io.Reader or io.ReadCloser -func (pb *ProgressBar) NewProxyReader(r io.Reader) *Reader { - return &Reader{r, pb} -} - -func (pb *ProgressBar) write(current int64) { - width := pb.GetWidth() - - var percentBox, countersBox, timeLeftBox, speedBox, barBox, end, out string - - // percents - if pb.ShowPercent { - var percent float64 - if pb.Total > 0 { - percent = float64(current) / (float64(pb.Total) / float64(100)) - } else { - percent = float64(current) / float64(100) - } - percentBox = fmt.Sprintf(" %6.02f%%", percent) - } - - // counters - if pb.ShowCounters { - current := Format(current).To(pb.Units).Width(pb.UnitsWidth) - if pb.Total > 0 { - total := Format(pb.Total).To(pb.Units).Width(pb.UnitsWidth) - countersBox = fmt.Sprintf(" %s / %s ", current, total) - } else { - countersBox = fmt.Sprintf(" %s / ? ", current) - } - } - - // time left - fromStart := time.Now().Sub(pb.startTime) - currentFromStart := current - pb.startValue - select { - case <-pb.finish: - if pb.ShowFinalTime { - var left time.Duration - if pb.Total > 0 { - left = (fromStart / time.Second) * time.Second - } else { - left = (time.Duration(currentFromStart) / time.Second) * time.Second - } - timeLeftBox = fmt.Sprintf(" %s", left.String()) - } - default: - if pb.ShowTimeLeft && currentFromStart > 0 { - perEntry := fromStart / time.Duration(currentFromStart) - var left time.Duration - if pb.Total > 0 { - left = time.Duration(pb.Total-currentFromStart) * perEntry - left = (left / time.Second) * time.Second - } else { - left = time.Duration(currentFromStart) * perEntry - left = (left / time.Second) * time.Second - } - timeLeft := Format(int64(left)).To(U_DURATION).String() - timeLeftBox = fmt.Sprintf(" %s", timeLeft) - } - } - - if len(timeLeftBox) < pb.TimeBoxWidth { - timeLeftBox = fmt.Sprintf("%s%s", strings.Repeat(" ", pb.TimeBoxWidth-len(timeLeftBox)), timeLeftBox) - } - - // speed - if pb.ShowSpeed && currentFromStart > 0 { - fromStart := time.Now().Sub(pb.startTime) - speed := float64(currentFromStart) / (float64(fromStart) / float64(time.Second)) - speedBox = " " + Format(int64(speed)).To(pb.Units).Width(pb.UnitsWidth).PerSec().String() - } - - barWidth := escapeAwareRuneCountInString(countersBox + pb.BarStart + pb.BarEnd + percentBox + timeLeftBox + speedBox + pb.prefix + pb.postfix) - // bar - if pb.ShowBar { - size := width - barWidth - if size > 0 { - if pb.Total > 0 { - curCount := int(math.Ceil((float64(current) / float64(pb.Total)) * float64(size))) - emptCount := size - curCount - barBox = pb.BarStart - if emptCount < 0 { - emptCount = 0 - } - if curCount > size { - curCount = size - } - if emptCount <= 0 { - barBox += strings.Repeat(pb.Current, curCount) - } else if curCount > 0 { - barBox += strings.Repeat(pb.Current, curCount-1) + pb.CurrentN - } - barBox += strings.Repeat(pb.Empty, emptCount) + pb.BarEnd - } else { - barBox = pb.BarStart - pos := size - int(current)%int(size) - if pos-1 > 0 { - barBox += strings.Repeat(pb.Empty, pos-1) - } - barBox += pb.Current - if size-pos-1 > 0 { - barBox += strings.Repeat(pb.Empty, size-pos-1) - } - barBox += pb.BarEnd - } - } - } - - // check len - out = pb.prefix + countersBox + barBox + percentBox + speedBox + timeLeftBox + pb.postfix - if escapeAwareRuneCountInString(out) < width { - end = strings.Repeat(" ", width-utf8.RuneCountInString(out)) - } - - // and print! - pb.mu.Lock() - pb.lastPrint = out + end - pb.mu.Unlock() - switch { - case pb.isFinish: - return - case pb.Output != nil: - fmt.Fprint(pb.Output, "\r"+out+end) - case pb.Callback != nil: - pb.Callback(out + end) - case !pb.NotPrint: - fmt.Print("\r" + out + end) - } -} - -// GetTerminalWidth - returns terminal width for all platforms. -func GetTerminalWidth() (int, error) { - return terminalWidth() -} - -func (pb *ProgressBar) GetWidth() int { - if pb.ForceWidth { - return pb.Width - } - - width := pb.Width - termWidth, _ := terminalWidth() - if width == 0 || termWidth <= width { - width = termWidth - } - - return width -} - -// Write the current state of the progressbar -func (pb *ProgressBar) Update() { - c := atomic.LoadInt64(&pb.current) - if pb.AlwaysUpdate || c != pb.currentValue { - pb.write(c) - pb.currentValue = c - } - if pb.AutoStat { - if c == 0 { - pb.startTime = time.Now() - pb.startValue = 0 - } else if c >= pb.Total && pb.isFinish != true { - pb.Finish() - } - } -} - -func (pb *ProgressBar) String() string { - return pb.lastPrint -} - -// Internal loop for refreshing the progressbar -func (pb *ProgressBar) refresher() { - for { - select { - case <-pb.finish: - return - case <-time.After(pb.RefreshRate): - pb.Update() - } - } -} - -type window struct { - Row uint16 - Col uint16 - Xpixel uint16 - Ypixel uint16 -} diff -Nru snapd-2.27.5/vendor/github.com/cheggaaa/pb/pb_nix.go snapd-2.28.5/vendor/github.com/cheggaaa/pb/pb_nix.go --- snapd-2.27.5/vendor/github.com/cheggaaa/pb/pb_nix.go 2016-08-30 11:53:30.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/cheggaaa/pb/pb_nix.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,8 +0,0 @@ -// +build linux darwin freebsd netbsd openbsd dragonfly -// +build !appengine - -package pb - -import "syscall" - -const sysIoctl = syscall.SYS_IOCTL diff -Nru snapd-2.27.5/vendor/github.com/cheggaaa/pb/pb_solaris.go snapd-2.28.5/vendor/github.com/cheggaaa/pb/pb_solaris.go --- snapd-2.27.5/vendor/github.com/cheggaaa/pb/pb_solaris.go 2016-08-30 11:53:30.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/cheggaaa/pb/pb_solaris.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,6 +0,0 @@ -// +build solaris -// +build !appengine - -package pb - -const sysIoctl = 54 diff -Nru snapd-2.27.5/vendor/github.com/cheggaaa/pb/pb_win.go snapd-2.28.5/vendor/github.com/cheggaaa/pb/pb_win.go --- snapd-2.27.5/vendor/github.com/cheggaaa/pb/pb_win.go 2016-08-30 11:53:30.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/cheggaaa/pb/pb_win.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,141 +0,0 @@ -// +build windows - -package pb - -import ( - "errors" - "fmt" - "os" - "sync" - "syscall" - "unsafe" -) - -var tty = os.Stdin - -var ( - kernel32 = syscall.NewLazyDLL("kernel32.dll") - - // GetConsoleScreenBufferInfo retrieves information about the - // specified console screen buffer. - // http://msdn.microsoft.com/en-us/library/windows/desktop/ms683171(v=vs.85).aspx - procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") - - // GetConsoleMode retrieves the current input mode of a console's - // input buffer or the current output mode of a console screen buffer. - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx - getConsoleMode = kernel32.NewProc("GetConsoleMode") - - // SetConsoleMode sets the input mode of a console's input buffer - // or the output mode of a console screen buffer. - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx - setConsoleMode = kernel32.NewProc("SetConsoleMode") - - // SetConsoleCursorPosition sets the cursor position in the - // specified console screen buffer. - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx - setConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") -) - -type ( - // Defines the coordinates of the upper left and lower right corners - // of a rectangle. - // See - // http://msdn.microsoft.com/en-us/library/windows/desktop/ms686311(v=vs.85).aspx - smallRect struct { - Left, Top, Right, Bottom int16 - } - - // Defines the coordinates of a character cell in a console screen - // buffer. The origin of the coordinate system (0,0) is at the top, left cell - // of the buffer. - // See - // http://msdn.microsoft.com/en-us/library/windows/desktop/ms682119(v=vs.85).aspx - coordinates struct { - X, Y int16 - } - - word int16 - - // Contains information about a console screen buffer. - // http://msdn.microsoft.com/en-us/library/windows/desktop/ms682093(v=vs.85).aspx - consoleScreenBufferInfo struct { - dwSize coordinates - dwCursorPosition coordinates - wAttributes word - srWindow smallRect - dwMaximumWindowSize coordinates - } -) - -// terminalWidth returns width of the terminal. -func terminalWidth() (width int, err error) { - var info consoleScreenBufferInfo - _, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(syscall.Stdout), uintptr(unsafe.Pointer(&info)), 0) - if e != 0 { - return 0, error(e) - } - return int(info.dwSize.X) - 1, nil -} - -func getCursorPos() (pos coordinates, err error) { - var info consoleScreenBufferInfo - _, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(syscall.Stdout), uintptr(unsafe.Pointer(&info)), 0) - if e != 0 { - return info.dwCursorPosition, error(e) - } - return info.dwCursorPosition, nil -} - -func setCursorPos(pos coordinates) error { - _, _, e := syscall.Syscall(setConsoleCursorPosition.Addr(), 2, uintptr(syscall.Stdout), uintptr(uint32(uint16(pos.Y))<<16|uint32(uint16(pos.X))), 0) - if e != 0 { - return error(e) - } - return nil -} - -var ErrPoolWasStarted = errors.New("Bar pool was started") - -var echoLocked bool -var echoLockMutex sync.Mutex - -var oldState word - -func lockEcho() (quit chan int, err error) { - echoLockMutex.Lock() - defer echoLockMutex.Unlock() - if echoLocked { - err = ErrPoolWasStarted - return - } - echoLocked = true - - if _, _, e := syscall.Syscall(getConsoleMode.Addr(), 2, uintptr(syscall.Stdout), uintptr(unsafe.Pointer(&oldState)), 0); e != 0 { - err = fmt.Errorf("Can't get terminal settings: %v", e) - return - } - - newState := oldState - const ENABLE_ECHO_INPUT = 0x0004 - const ENABLE_LINE_INPUT = 0x0002 - newState = newState & (^(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT)) - if _, _, e := syscall.Syscall(setConsoleMode.Addr(), 2, uintptr(syscall.Stdout), uintptr(newState), 0); e != 0 { - err = fmt.Errorf("Can't set terminal settings: %v", e) - return - } - return -} - -func unlockEcho() (err error) { - echoLockMutex.Lock() - defer echoLockMutex.Unlock() - if !echoLocked { - return - } - echoLocked = false - if _, _, e := syscall.Syscall(setConsoleMode.Addr(), 2, uintptr(syscall.Stdout), uintptr(oldState), 0); e != 0 { - err = fmt.Errorf("Can't set terminal settings") - } - return -} diff -Nru snapd-2.27.5/vendor/github.com/cheggaaa/pb/pb_x.go snapd-2.28.5/vendor/github.com/cheggaaa/pb/pb_x.go --- snapd-2.27.5/vendor/github.com/cheggaaa/pb/pb_x.go 2016-08-30 11:53:30.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/cheggaaa/pb/pb_x.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,110 +0,0 @@ -// +build linux darwin freebsd netbsd openbsd solaris dragonfly -// +build !appengine - -package pb - -import ( - "errors" - "fmt" - "os" - "os/signal" - "runtime" - "sync" - "syscall" - "unsafe" -) - -const ( - TIOCGWINSZ = 0x5413 - TIOCGWINSZ_OSX = 1074295912 -) - -var tty *os.File - -var ErrPoolWasStarted = errors.New("Bar pool was started") - -var echoLocked bool -var echoLockMutex sync.Mutex - -func init() { - var err error - tty, err = os.Open("/dev/tty") - if err != nil { - tty = os.Stdin - } -} - -// terminalWidth returns width of the terminal. -func terminalWidth() (int, error) { - w := new(window) - tio := syscall.TIOCGWINSZ - if runtime.GOOS == "darwin" { - tio = TIOCGWINSZ_OSX - } - res, _, err := syscall.Syscall(sysIoctl, - tty.Fd(), - uintptr(tio), - uintptr(unsafe.Pointer(w)), - ) - if int(res) == -1 { - return 0, err - } - return int(w.Col), nil -} - -var oldState syscall.Termios - -func lockEcho() (quit chan int, err error) { - echoLockMutex.Lock() - defer echoLockMutex.Unlock() - if echoLocked { - err = ErrPoolWasStarted - return - } - echoLocked = true - - fd := tty.Fd() - if _, _, e := syscall.Syscall6(sysIoctl, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0); e != 0 { - err = fmt.Errorf("Can't get terminal settings: %v", e) - return - } - - newState := oldState - newState.Lflag &^= syscall.ECHO - newState.Lflag |= syscall.ICANON | syscall.ISIG - newState.Iflag |= syscall.ICRNL - if _, _, e := syscall.Syscall6(sysIoctl, fd, ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); e != 0 { - err = fmt.Errorf("Can't set terminal settings: %v", e) - return - } - quit = make(chan int, 1) - go catchTerminate(quit) - return -} - -func unlockEcho() (err error) { - echoLockMutex.Lock() - defer echoLockMutex.Unlock() - if !echoLocked { - return - } - echoLocked = false - fd := tty.Fd() - if _, _, e := syscall.Syscall6(sysIoctl, fd, ioctlWriteTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0); e != 0 { - err = fmt.Errorf("Can't set terminal settings") - } - return -} - -// listen exit signals and restore terminal state -func catchTerminate(quit chan int) { - sig := make(chan os.Signal, 1) - signal.Notify(sig, os.Interrupt, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGKILL) - defer signal.Stop(sig) - select { - case <-quit: - unlockEcho() - case <-sig: - unlockEcho() - } -} diff -Nru snapd-2.27.5/vendor/github.com/cheggaaa/pb/pool.go snapd-2.28.5/vendor/github.com/cheggaaa/pb/pool.go --- snapd-2.27.5/vendor/github.com/cheggaaa/pb/pool.go 2016-08-30 11:53:30.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/cheggaaa/pb/pool.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,76 +0,0 @@ -// +build linux darwin freebsd netbsd openbsd solaris dragonfly windows - -package pb - -import ( - "sync" - "time" -) - -// Create and start new pool with given bars -// You need call pool.Stop() after work -func StartPool(pbs ...*ProgressBar) (pool *Pool, err error) { - pool = new(Pool) - if err = pool.start(); err != nil { - return - } - pool.Add(pbs...) - return -} - -type Pool struct { - RefreshRate time.Duration - bars []*ProgressBar - quit chan int - finishOnce sync.Once -} - -// Add progress bars. -func (p *Pool) Add(pbs ...*ProgressBar) { - for _, bar := range pbs { - bar.ManualUpdate = true - bar.NotPrint = true - bar.Start() - p.bars = append(p.bars, bar) - } -} - -func (p *Pool) start() (err error) { - p.RefreshRate = DefaultRefreshRate - quit, err := lockEcho() - if err != nil { - return - } - p.quit = make(chan int) - go p.writer(quit) - return -} - -func (p *Pool) writer(finish chan int) { - var first = true - for { - select { - case <-time.After(p.RefreshRate): - if p.print(first) { - p.print(false) - finish <- 1 - return - } - first = false - case <-p.quit: - finish <- 1 - return - } - } -} - -// Restore terminal state and close pool -func (p *Pool) Stop() error { - // Wait until one final refresh has passed. - time.Sleep(p.RefreshRate) - - p.finishOnce.Do(func() { - close(p.quit) - }) - return unlockEcho() -} diff -Nru snapd-2.27.5/vendor/github.com/cheggaaa/pb/pool_win.go snapd-2.28.5/vendor/github.com/cheggaaa/pb/pool_win.go --- snapd-2.27.5/vendor/github.com/cheggaaa/pb/pool_win.go 2016-08-30 11:53:30.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/cheggaaa/pb/pool_win.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,35 +0,0 @@ -// +build windows - -package pb - -import ( - "fmt" - "log" -) - -func (p *Pool) print(first bool) bool { - var out string - if !first { - coords, err := getCursorPos() - if err != nil { - log.Panic(err) - } - coords.Y -= int16(len(p.bars)) - coords.X = 0 - - err = setCursorPos(coords) - if err != nil { - log.Panic(err) - } - } - isFinished := true - for _, bar := range p.bars { - if !bar.isFinish { - isFinished = false - } - bar.Update() - out += fmt.Sprintf("\r%s\n", bar.String()) - } - fmt.Print(out) - return isFinished -} diff -Nru snapd-2.27.5/vendor/github.com/cheggaaa/pb/pool_x.go snapd-2.28.5/vendor/github.com/cheggaaa/pb/pool_x.go --- snapd-2.27.5/vendor/github.com/cheggaaa/pb/pool_x.go 2016-08-30 11:53:30.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/cheggaaa/pb/pool_x.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,22 +0,0 @@ -// +build linux darwin freebsd netbsd openbsd solaris dragonfly - -package pb - -import "fmt" - -func (p *Pool) print(first bool) bool { - var out string - if !first { - out = fmt.Sprintf("\033[%dA", len(p.bars)) - } - isFinished := true - for _, bar := range p.bars { - if !bar.isFinish { - isFinished = false - } - bar.Update() - out += fmt.Sprintf("\r%s\n", bar.String()) - } - fmt.Print(out) - return isFinished -} diff -Nru snapd-2.27.5/vendor/github.com/cheggaaa/pb/reader.go snapd-2.28.5/vendor/github.com/cheggaaa/pb/reader.go --- snapd-2.27.5/vendor/github.com/cheggaaa/pb/reader.go 2016-08-30 11:53:30.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/cheggaaa/pb/reader.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,25 +0,0 @@ -package pb - -import ( - "io" -) - -// It's proxy reader, implement io.Reader -type Reader struct { - io.Reader - bar *ProgressBar -} - -func (r *Reader) Read(p []byte) (n int, err error) { - n, err = r.Reader.Read(p) - r.bar.Add(n) - return -} - -// Close the reader when it implements io.Closer -func (r *Reader) Close() (err error) { - if closer, ok := r.Reader.(io.Closer); ok { - return closer.Close() - } - return -} diff -Nru snapd-2.27.5/vendor/github.com/cheggaaa/pb/README.md snapd-2.28.5/vendor/github.com/cheggaaa/pb/README.md --- snapd-2.27.5/vendor/github.com/cheggaaa/pb/README.md 2016-08-30 11:53:30.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/cheggaaa/pb/README.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,176 +0,0 @@ -# Terminal progress bar for Go - -Simple progress bar for console programs. - - -## Installation - -``` -go get gopkg.in/cheggaaa/pb.v1 -``` - -## Usage - -```Go -package main - -import ( - "gopkg.in/cheggaaa/pb.v1" - "time" -) - -func main() { - count := 100000 - bar := pb.StartNew(count) - for i := 0; i < count; i++ { - bar.Increment() - time.Sleep(time.Millisecond) - } - bar.FinishPrint("The End!") -} - -``` - -Result will be like this: - -``` -> go run test.go -37158 / 100000 [================>_______________________________] 37.16% 1m11s -``` - -## Customization - -```Go -// create bar -bar := pb.New(count) - -// refresh info every second (default 200ms) -bar.SetRefreshRate(time.Second) - -// show percents (by default already true) -bar.ShowPercent = true - -// show bar (by default already true) -bar.ShowBar = true - -// no counters -bar.ShowCounters = false - -// show "time left" -bar.ShowTimeLeft = true - -// show average speed -bar.ShowSpeed = true - -// sets the width of the progress bar -bar.SetWidth(80) - -// sets the width of the progress bar, but if terminal size smaller will be ignored -bar.SetMaxWidth(80) - -// convert output to readable format (like KB, MB) -bar.SetUnits(pb.U_BYTES) - -// and start -bar.Start() -``` - -## Progress bar for IO Operations - -```go -// create and start bar -bar := pb.New(myDataLen).SetUnits(pb.U_BYTES) -bar.Start() - -// my io.Reader -r := myReader - -// my io.Writer -w := myWriter - -// create proxy reader -reader := bar.NewProxyReader(r) - -// and copy from pb reader -io.Copy(w, reader) - -``` - -```go -// create and start bar -bar := pb.New(myDataLen).SetUnits(pb.U_BYTES) -bar.Start() - -// my io.Reader -r := myReader - -// my io.Writer -w := myWriter - -// create multi writer -writer := io.MultiWriter(w, bar) - -// and copy -io.Copy(writer, r) - -bar.Finish() -``` - -## Custom Progress Bar Look-and-feel - -```go -bar.Format("<.- >") -``` - -## Multiple Progress Bars (experimental and unstable) - -Do not print to terminal while pool is active. - -```go -package main - -import ( - "math/rand" - "sync" - "time" - - "gopkg.in/cheggaaa/pb.v1" -) - -func main() { - // create bars - first := pb.New(200).Prefix("First ") - second := pb.New(200).Prefix("Second ") - third := pb.New(200).Prefix("Third ") - // start pool - pool, err := pb.StartPool(first, second, third) - if err != nil { - panic(err) - } - // update bars - wg := new(sync.WaitGroup) - for _, bar := range []*pb.ProgressBar{first, second, third} { - wg.Add(1) - go func(cb *pb.ProgressBar) { - for n := 0; n < 200; n++ { - cb.Increment() - time.Sleep(time.Millisecond * time.Duration(rand.Intn(100))) - } - cb.Finish() - wg.Done() - }(bar) - } - wg.Wait() - // close pool - pool.Stop() -} -``` - -The result will be as follows: - -``` -$ go run example/multiple.go -First 141 / 1000 [===============>---------------------------------------] 14.10 % 44s -Second 139 / 1000 [==============>---------------------------------------] 13.90 % 44s -Third 152 / 1000 [================>--------------------------------------] 15.20 % 40s -``` diff -Nru snapd-2.27.5/vendor/github.com/cheggaaa/pb/runecount.go snapd-2.28.5/vendor/github.com/cheggaaa/pb/runecount.go --- snapd-2.27.5/vendor/github.com/cheggaaa/pb/runecount.go 2016-08-30 11:53:30.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/cheggaaa/pb/runecount.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,17 +0,0 @@ -package pb - -import ( - "regexp" - "unicode/utf8" -) - -// Finds the control character sequences (like colors) -var ctrlFinder = regexp.MustCompile("\x1b\x5b[0-9]+\x6d") - -func escapeAwareRuneCountInString(s string) int { - n := utf8.RuneCountInString(s) - for _, sm := range ctrlFinder.FindAllString(s, -1) { - n -= len(sm) - } - return n -} diff -Nru snapd-2.27.5/vendor/github.com/cheggaaa/pb/termios_bsd.go snapd-2.28.5/vendor/github.com/cheggaaa/pb/termios_bsd.go --- snapd-2.27.5/vendor/github.com/cheggaaa/pb/termios_bsd.go 2016-08-30 11:53:30.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/cheggaaa/pb/termios_bsd.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,9 +0,0 @@ -// +build darwin freebsd netbsd openbsd dragonfly -// +build !appengine - -package pb - -import "syscall" - -const ioctlReadTermios = syscall.TIOCGETA -const ioctlWriteTermios = syscall.TIOCSETA diff -Nru snapd-2.27.5/vendor/github.com/cheggaaa/pb/termios_nix.go snapd-2.28.5/vendor/github.com/cheggaaa/pb/termios_nix.go --- snapd-2.27.5/vendor/github.com/cheggaaa/pb/termios_nix.go 2016-08-30 11:53:30.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/cheggaaa/pb/termios_nix.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,7 +0,0 @@ -// +build linux solaris -// +build !appengine - -package pb - -const ioctlReadTermios = 0x5401 // syscall.TCGETS -const ioctlWriteTermios = 0x5402 // syscall.TCSETS diff -Nru snapd-2.27.5/vendor/github.com/coreos/go-systemd/activation/files.go snapd-2.28.5/vendor/github.com/coreos/go-systemd/activation/files.go --- snapd-2.27.5/vendor/github.com/coreos/go-systemd/activation/files.go 2017-08-14 06:13:40.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/coreos/go-systemd/activation/files.go 2017-09-07 12:49:04.000000000 +0000 @@ -1,18 +1,16 @@ -/* -Copyright 2013 CoreOS Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. // Package activation implements primitives for systemd socket activation. package activation @@ -30,10 +28,8 @@ func Files(unsetEnv bool) []*os.File { if unsetEnv { - // there is no way to unset env in golang os package for now - // https://code.google.com/p/go/issues/detail?id=6423 - defer os.Setenv("LISTEN_PID", "") - defer os.Setenv("LISTEN_FDS", "") + defer os.Unsetenv("LISTEN_PID") + defer os.Unsetenv("LISTEN_FDS") } pid, err := strconv.Atoi(os.Getenv("LISTEN_PID")) @@ -46,7 +42,7 @@ return nil } - var files []*os.File + files := make([]*os.File, 0, nfds) for fd := listenFdsStart; fd < listenFdsStart+nfds; fd++ { syscall.CloseOnExec(fd) files = append(files, os.NewFile(uintptr(fd), "LISTEN_FD_"+strconv.Itoa(fd))) diff -Nru snapd-2.27.5/vendor/github.com/coreos/go-systemd/activation/listeners.go snapd-2.28.5/vendor/github.com/coreos/go-systemd/activation/listeners.go --- snapd-2.27.5/vendor/github.com/coreos/go-systemd/activation/listeners.go 2017-08-14 06:13:40.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/coreos/go-systemd/activation/listeners.go 2017-09-07 12:49:04.000000000 +0000 @@ -1,38 +1,60 @@ -/* -Copyright 2014 CoreOS Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package activation import ( - "fmt" + "crypto/tls" "net" ) -// Listeners returns net.Listeners for all socket activated fds passed to this process. +// Listeners returns a slice containing a net.Listener for each matching socket type +// passed to this process. +// +// The order of the file descriptors is preserved in the returned slice. +// Nil values are used to fill any gaps. For example if systemd were to return file descriptors +// corresponding with "udp, tcp, tcp", then the slice would contain {nil, net.Listener, net.Listener} func Listeners(unsetEnv bool) ([]net.Listener, error) { files := Files(unsetEnv) listeners := make([]net.Listener, len(files)) for i, f := range files { - var err error - listeners[i], err = net.FileListener(f) - if err != nil { - return nil, fmt.Errorf("Error setting up FileListener for fd %d: %s", f.Fd(), err.Error()) + if pc, err := net.FileListener(f); err == nil { + listeners[i] = pc } } - return listeners, nil } + +// TLSListeners returns a slice containing a net.listener for each matching TCP socket type +// passed to this process. +// It uses default Listeners func and forces TCP sockets handlers to use TLS based on tlsConfig. +func TLSListeners(unsetEnv bool, tlsConfig *tls.Config) ([]net.Listener, error) { + listeners, err := Listeners(unsetEnv) + + if listeners == nil || err != nil { + return nil, err + } + + if tlsConfig != nil && err == nil { + for i, l := range listeners { + // Activate TLS only for TCP sockets + if l.Addr().Network() == "tcp" { + listeners[i] = tls.NewListener(l, tlsConfig) + } + } + } + + return listeners, err +} diff -Nru snapd-2.27.5/vendor/github.com/coreos/go-systemd/activation/packetconns.go snapd-2.28.5/vendor/github.com/coreos/go-systemd/activation/packetconns.go --- snapd-2.27.5/vendor/github.com/coreos/go-systemd/activation/packetconns.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/coreos/go-systemd/activation/packetconns.go 2017-09-07 12:49:04.000000000 +0000 @@ -0,0 +1,37 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package activation + +import ( + "net" +) + +// PacketConns returns a slice containing a net.PacketConn for each matching socket type +// passed to this process. +// +// The order of the file descriptors is preserved in the returned slice. +// Nil values are used to fill any gaps. For example if systemd were to return file descriptors +// corresponding with "udp, tcp, udp", then the slice would contain {net.PacketConn, nil, net.PacketConn} +func PacketConns(unsetEnv bool) ([]net.PacketConn, error) { + files := Files(unsetEnv) + conns := make([]net.PacketConn, len(files)) + + for i, f := range files { + if pc, err := net.FilePacketConn(f); err == nil { + conns[i] = pc + } + } + return conns, nil +} diff -Nru snapd-2.27.5/vendor/github.com/godbus/dbus/auth_external.go snapd-2.28.5/vendor/github.com/godbus/dbus/auth_external.go --- snapd-2.27.5/vendor/github.com/godbus/dbus/auth_external.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/godbus/dbus/auth_external.go 2017-08-23 09:04:17.000000000 +0000 @@ -0,0 +1,26 @@ +package dbus + +import ( + "encoding/hex" +) + +// AuthExternal returns an Auth that authenticates as the given user with the +// EXTERNAL mechanism. +func AuthExternal(user string) Auth { + return authExternal{user} +} + +// AuthExternal implements the EXTERNAL authentication mechanism. +type authExternal struct { + user string +} + +func (a authExternal) FirstData() ([]byte, []byte, AuthStatus) { + b := make([]byte, 2*len(a.user)) + hex.Encode(b, []byte(a.user)) + return []byte("EXTERNAL"), b, AuthOk +} + +func (a authExternal) HandleData(b []byte) ([]byte, AuthStatus) { + return nil, AuthError +} diff -Nru snapd-2.27.5/vendor/github.com/godbus/dbus/auth.go snapd-2.28.5/vendor/github.com/godbus/dbus/auth.go --- snapd-2.27.5/vendor/github.com/godbus/dbus/auth.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/godbus/dbus/auth.go 2017-08-23 09:04:17.000000000 +0000 @@ -0,0 +1,253 @@ +package dbus + +import ( + "bufio" + "bytes" + "errors" + "io" + "os" + "strconv" +) + +// AuthStatus represents the Status of an authentication mechanism. +type AuthStatus byte + +const ( + // AuthOk signals that authentication is finished; the next command + // from the server should be an OK. + AuthOk AuthStatus = iota + + // AuthContinue signals that additional data is needed; the next command + // from the server should be a DATA. + AuthContinue + + // AuthError signals an error; the server sent invalid data or some + // other unexpected thing happened and the current authentication + // process should be aborted. + AuthError +) + +type authState byte + +const ( + waitingForData authState = iota + waitingForOk + waitingForReject +) + +// Auth defines the behaviour of an authentication mechanism. +type Auth interface { + // Return the name of the mechnism, the argument to the first AUTH command + // and the next status. + FirstData() (name, resp []byte, status AuthStatus) + + // Process the given DATA command, and return the argument to the DATA + // command and the next status. If len(resp) == 0, no DATA command is sent. + HandleData(data []byte) (resp []byte, status AuthStatus) +} + +// Auth authenticates the connection, trying the given list of authentication +// mechanisms (in that order). If nil is passed, the EXTERNAL and +// DBUS_COOKIE_SHA1 mechanisms are tried for the current user. For private +// connections, this method must be called before sending any messages to the +// bus. Auth must not be called on shared connections. +func (conn *Conn) Auth(methods []Auth) error { + if methods == nil { + uid := strconv.Itoa(os.Getuid()) + methods = []Auth{AuthExternal(uid), AuthCookieSha1(uid, getHomeDir())} + } + in := bufio.NewReader(conn.transport) + err := conn.transport.SendNullByte() + if err != nil { + return err + } + err = authWriteLine(conn.transport, []byte("AUTH")) + if err != nil { + return err + } + s, err := authReadLine(in) + if err != nil { + return err + } + if len(s) < 2 || !bytes.Equal(s[0], []byte("REJECTED")) { + return errors.New("dbus: authentication protocol error") + } + s = s[1:] + for _, v := range s { + for _, m := range methods { + if name, data, status := m.FirstData(); bytes.Equal(v, name) { + var ok bool + err = authWriteLine(conn.transport, []byte("AUTH"), []byte(v), data) + if err != nil { + return err + } + switch status { + case AuthOk: + err, ok = conn.tryAuth(m, waitingForOk, in) + case AuthContinue: + err, ok = conn.tryAuth(m, waitingForData, in) + default: + panic("dbus: invalid authentication status") + } + if err != nil { + return err + } + if ok { + if conn.transport.SupportsUnixFDs() { + err = authWriteLine(conn, []byte("NEGOTIATE_UNIX_FD")) + if err != nil { + return err + } + line, err := authReadLine(in) + if err != nil { + return err + } + switch { + case bytes.Equal(line[0], []byte("AGREE_UNIX_FD")): + conn.EnableUnixFDs() + conn.unixFD = true + case bytes.Equal(line[0], []byte("ERROR")): + default: + return errors.New("dbus: authentication protocol error") + } + } + err = authWriteLine(conn.transport, []byte("BEGIN")) + if err != nil { + return err + } + go conn.inWorker() + go conn.outWorker() + return nil + } + } + } + } + return errors.New("dbus: authentication failed") +} + +// tryAuth tries to authenticate with m as the mechanism, using state as the +// initial authState and in for reading input. It returns (nil, true) on +// success, (nil, false) on a REJECTED and (someErr, false) if some other +// error occured. +func (conn *Conn) tryAuth(m Auth, state authState, in *bufio.Reader) (error, bool) { + for { + s, err := authReadLine(in) + if err != nil { + return err, false + } + switch { + case state == waitingForData && string(s[0]) == "DATA": + if len(s) != 2 { + err = authWriteLine(conn.transport, []byte("ERROR")) + if err != nil { + return err, false + } + continue + } + data, status := m.HandleData(s[1]) + switch status { + case AuthOk, AuthContinue: + if len(data) != 0 { + err = authWriteLine(conn.transport, []byte("DATA"), data) + if err != nil { + return err, false + } + } + if status == AuthOk { + state = waitingForOk + } + case AuthError: + err = authWriteLine(conn.transport, []byte("ERROR")) + if err != nil { + return err, false + } + } + case state == waitingForData && string(s[0]) == "REJECTED": + return nil, false + case state == waitingForData && string(s[0]) == "ERROR": + err = authWriteLine(conn.transport, []byte("CANCEL")) + if err != nil { + return err, false + } + state = waitingForReject + case state == waitingForData && string(s[0]) == "OK": + if len(s) != 2 { + err = authWriteLine(conn.transport, []byte("CANCEL")) + if err != nil { + return err, false + } + state = waitingForReject + } + conn.uuid = string(s[1]) + return nil, true + case state == waitingForData: + err = authWriteLine(conn.transport, []byte("ERROR")) + if err != nil { + return err, false + } + case state == waitingForOk && string(s[0]) == "OK": + if len(s) != 2 { + err = authWriteLine(conn.transport, []byte("CANCEL")) + if err != nil { + return err, false + } + state = waitingForReject + } + conn.uuid = string(s[1]) + return nil, true + case state == waitingForOk && string(s[0]) == "REJECTED": + return nil, false + case state == waitingForOk && (string(s[0]) == "DATA" || + string(s[0]) == "ERROR"): + + err = authWriteLine(conn.transport, []byte("CANCEL")) + if err != nil { + return err, false + } + state = waitingForReject + case state == waitingForOk: + err = authWriteLine(conn.transport, []byte("ERROR")) + if err != nil { + return err, false + } + case state == waitingForReject && string(s[0]) == "REJECTED": + return nil, false + case state == waitingForReject: + return errors.New("dbus: authentication protocol error"), false + default: + panic("dbus: invalid auth state") + } + } +} + +// authReadLine reads a line and separates it into its fields. +func authReadLine(in *bufio.Reader) ([][]byte, error) { + data, err := in.ReadBytes('\n') + if err != nil { + return nil, err + } + data = bytes.TrimSuffix(data, []byte("\r\n")) + return bytes.Split(data, []byte{' '}), nil +} + +// authWriteLine writes the given line in the authentication protocol format +// (elements of data separated by a " " and terminated by "\r\n"). +func authWriteLine(out io.Writer, data ...[]byte) error { + buf := make([]byte, 0) + for i, v := range data { + buf = append(buf, v...) + if i != len(data)-1 { + buf = append(buf, ' ') + } + } + buf = append(buf, '\r') + buf = append(buf, '\n') + n, err := out.Write(buf) + if err != nil { + return err + } + if n != len(buf) { + return io.ErrUnexpectedEOF + } + return nil +} diff -Nru snapd-2.27.5/vendor/github.com/godbus/dbus/auth_sha1.go snapd-2.28.5/vendor/github.com/godbus/dbus/auth_sha1.go --- snapd-2.27.5/vendor/github.com/godbus/dbus/auth_sha1.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/godbus/dbus/auth_sha1.go 2017-08-23 09:04:17.000000000 +0000 @@ -0,0 +1,102 @@ +package dbus + +import ( + "bufio" + "bytes" + "crypto/rand" + "crypto/sha1" + "encoding/hex" + "os" +) + +// AuthCookieSha1 returns an Auth that authenticates as the given user with the +// DBUS_COOKIE_SHA1 mechanism. The home parameter should specify the home +// directory of the user. +func AuthCookieSha1(user, home string) Auth { + return authCookieSha1{user, home} +} + +type authCookieSha1 struct { + user, home string +} + +func (a authCookieSha1) FirstData() ([]byte, []byte, AuthStatus) { + b := make([]byte, 2*len(a.user)) + hex.Encode(b, []byte(a.user)) + return []byte("DBUS_COOKIE_SHA1"), b, AuthContinue +} + +func (a authCookieSha1) HandleData(data []byte) ([]byte, AuthStatus) { + challenge := make([]byte, len(data)/2) + _, err := hex.Decode(challenge, data) + if err != nil { + return nil, AuthError + } + b := bytes.Split(challenge, []byte{' '}) + if len(b) != 3 { + return nil, AuthError + } + context := b[0] + id := b[1] + svchallenge := b[2] + cookie := a.getCookie(context, id) + if cookie == nil { + return nil, AuthError + } + clchallenge := a.generateChallenge() + if clchallenge == nil { + return nil, AuthError + } + hash := sha1.New() + hash.Write(bytes.Join([][]byte{svchallenge, clchallenge, cookie}, []byte{':'})) + hexhash := make([]byte, 2*hash.Size()) + hex.Encode(hexhash, hash.Sum(nil)) + data = append(clchallenge, ' ') + data = append(data, hexhash...) + resp := make([]byte, 2*len(data)) + hex.Encode(resp, data) + return resp, AuthOk +} + +// getCookie searches for the cookie identified by id in context and returns +// the cookie content or nil. (Since HandleData can't return a specific error, +// but only whether an error occured, this function also doesn't bother to +// return an error.) +func (a authCookieSha1) getCookie(context, id []byte) []byte { + file, err := os.Open(a.home + "/.dbus-keyrings/" + string(context)) + if err != nil { + return nil + } + defer file.Close() + rd := bufio.NewReader(file) + for { + line, err := rd.ReadBytes('\n') + if err != nil { + return nil + } + line = line[:len(line)-1] + b := bytes.Split(line, []byte{' '}) + if len(b) != 3 { + return nil + } + if bytes.Equal(b[0], id) { + return b[2] + } + } +} + +// generateChallenge returns a random, hex-encoded challenge, or nil on error +// (see above). +func (a authCookieSha1) generateChallenge() []byte { + b := make([]byte, 16) + n, err := rand.Read(b) + if err != nil { + return nil + } + if n != 16 { + return nil + } + enc := make([]byte, 32) + hex.Encode(enc, b) + return enc +} diff -Nru snapd-2.27.5/vendor/github.com/godbus/dbus/call.go snapd-2.28.5/vendor/github.com/godbus/dbus/call.go --- snapd-2.27.5/vendor/github.com/godbus/dbus/call.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/godbus/dbus/call.go 2017-08-23 09:04:17.000000000 +0000 @@ -0,0 +1,36 @@ +package dbus + +import ( + "errors" +) + +// Call represents a pending or completed method call. +type Call struct { + Destination string + Path ObjectPath + Method string + Args []interface{} + + // Strobes when the call is complete. + Done chan *Call + + // After completion, the error status. If this is non-nil, it may be an + // error message from the peer (with Error as its type) or some other error. + Err error + + // Holds the response once the call is done. + Body []interface{} +} + +var errSignature = errors.New("dbus: mismatched signature") + +// Store stores the body of the reply into the provided pointers. It returns +// an error if the signatures of the body and retvalues don't match, or if +// the error status is not nil. +func (c *Call) Store(retvalues ...interface{}) error { + if c.Err != nil { + return c.Err + } + + return Store(c.Body, retvalues...) +} diff -Nru snapd-2.27.5/vendor/github.com/godbus/dbus/conn_darwin.go snapd-2.28.5/vendor/github.com/godbus/dbus/conn_darwin.go --- snapd-2.27.5/vendor/github.com/godbus/dbus/conn_darwin.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/godbus/dbus/conn_darwin.go 2017-08-23 09:04:17.000000000 +0000 @@ -0,0 +1,33 @@ +package dbus + +import ( + "errors" + "fmt" + "os" + "os/exec" +) + +const defaultSystemBusAddress = "unix:path=/opt/local/var/run/dbus/system_bus_socket" + +func getSessionBusPlatformAddress() (string, error) { + cmd := exec.Command("launchctl", "getenv", "DBUS_LAUNCHD_SESSION_BUS_SOCKET") + b, err := cmd.CombinedOutput() + + if err != nil { + return "", err + } + + if len(b) == 0 { + return "", errors.New("dbus: couldn't determine address of session bus") + } + + return "unix:path=" + string(b[:len(b)-1]), nil +} + +func getSystemBusPlatformAddress() string { + address := os.Getenv("DBUS_LAUNCHD_SESSION_BUS_SOCKET") + if address != "" { + return fmt.Sprintf("unix:path=%s", address) + } + return defaultSystemBusAddress +} diff -Nru snapd-2.27.5/vendor/github.com/godbus/dbus/conn.go snapd-2.28.5/vendor/github.com/godbus/dbus/conn.go --- snapd-2.27.5/vendor/github.com/godbus/dbus/conn.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/godbus/dbus/conn.go 2017-08-23 09:04:17.000000000 +0000 @@ -0,0 +1,683 @@ +package dbus + +import ( + "errors" + "io" + "os" + "reflect" + "strings" + "sync" +) + +var ( + systemBus *Conn + systemBusLck sync.Mutex + sessionBus *Conn + sessionBusLck sync.Mutex + sessionEnvLck sync.Mutex +) + +// ErrClosed is the error returned by calls on a closed connection. +var ErrClosed = errors.New("dbus: connection closed by user") + +// Conn represents a connection to a message bus (usually, the system or +// session bus). +// +// Connections are either shared or private. Shared connections +// are shared between calls to the functions that return them. As a result, +// the methods Close, Auth and Hello must not be called on them. +// +// Multiple goroutines may invoke methods on a connection simultaneously. +type Conn struct { + transport + + busObj BusObject + unixFD bool + uuid string + + names []string + namesLck sync.RWMutex + + serialLck sync.Mutex + nextSerial uint32 + serialUsed map[uint32]bool + + calls map[uint32]*Call + callsLck sync.RWMutex + + handler Handler + + out chan *Message + closed bool + outLck sync.RWMutex + + signalHandler SignalHandler + + eavesdropped chan<- *Message + eavesdroppedLck sync.Mutex +} + +// SessionBus returns a shared connection to the session bus, connecting to it +// if not already done. +func SessionBus() (conn *Conn, err error) { + sessionBusLck.Lock() + defer sessionBusLck.Unlock() + if sessionBus != nil { + return sessionBus, nil + } + defer func() { + if conn != nil { + sessionBus = conn + } + }() + conn, err = SessionBusPrivate() + if err != nil { + return + } + if err = conn.Auth(nil); err != nil { + conn.Close() + conn = nil + return + } + if err = conn.Hello(); err != nil { + conn.Close() + conn = nil + } + return +} + +func getSessionBusAddress() (string, error) { + sessionEnvLck.Lock() + defer sessionEnvLck.Unlock() + address := os.Getenv("DBUS_SESSION_BUS_ADDRESS") + if address != "" && address != "autolaunch:" { + return address, nil + } + return getSessionBusPlatformAddress() +} + +// SessionBusPrivate returns a new private connection to the session bus. +func SessionBusPrivate() (*Conn, error) { + address, err := getSessionBusAddress() + if err != nil { + return nil, err + } + + return Dial(address) +} + +// SessionBusPrivate returns a new private connection to the session bus. +func SessionBusPrivateHandler(handler Handler, signalHandler SignalHandler) (*Conn, error) { + address, err := getSessionBusAddress() + if err != nil { + return nil, err + } + return DialHandler(address, handler, signalHandler) +} + +// SystemBus returns a shared connection to the system bus, connecting to it if +// not already done. +func SystemBus() (conn *Conn, err error) { + systemBusLck.Lock() + defer systemBusLck.Unlock() + if systemBus != nil { + return systemBus, nil + } + defer func() { + if conn != nil { + systemBus = conn + } + }() + conn, err = SystemBusPrivate() + if err != nil { + return + } + if err = conn.Auth(nil); err != nil { + conn.Close() + conn = nil + return + } + if err = conn.Hello(); err != nil { + conn.Close() + conn = nil + } + return +} + +// SystemBusPrivate returns a new private connection to the system bus. +func SystemBusPrivate() (*Conn, error) { + return Dial(getSystemBusPlatformAddress()) +} + +// SystemBusPrivateHandler returns a new private connection to the system bus, using the provided handlers. +func SystemBusPrivateHandler(handler Handler, signalHandler SignalHandler) (*Conn, error) { + return DialHandler(getSystemBusPlatformAddress(), handler, signalHandler) +} + +// Dial establishes a new private connection to the message bus specified by address. +func Dial(address string) (*Conn, error) { + tr, err := getTransport(address) + if err != nil { + return nil, err + } + return newConn(tr, newDefaultHandler(), newDefaultSignalHandler()) +} + +// DialHandler establishes a new private connection to the message bus specified by address, using the supplied handlers. +func DialHandler(address string, handler Handler, signalHandler SignalHandler) (*Conn, error) { + tr, err := getTransport(address) + if err != nil { + return nil, err + } + return newConn(tr, handler, signalHandler) +} + +// NewConn creates a new private *Conn from an already established connection. +func NewConn(conn io.ReadWriteCloser) (*Conn, error) { + return NewConnHandler(conn, newDefaultHandler(), newDefaultSignalHandler()) +} + +// NewConnHandler creates a new private *Conn from an already established connection, using the supplied handlers. +func NewConnHandler(conn io.ReadWriteCloser, handler Handler, signalHandler SignalHandler) (*Conn, error) { + return newConn(genericTransport{conn}, handler, signalHandler) +} + +// newConn creates a new *Conn from a transport. +func newConn(tr transport, handler Handler, signalHandler SignalHandler) (*Conn, error) { + conn := new(Conn) + conn.transport = tr + conn.calls = make(map[uint32]*Call) + conn.out = make(chan *Message, 10) + conn.handler = handler + conn.signalHandler = signalHandler + conn.nextSerial = 1 + conn.serialUsed = map[uint32]bool{0: true} + conn.busObj = conn.Object("org.freedesktop.DBus", "/org/freedesktop/DBus") + return conn, nil +} + +// BusObject returns the object owned by the bus daemon which handles +// administrative requests. +func (conn *Conn) BusObject() BusObject { + return conn.busObj +} + +// Close closes the connection. Any blocked operations will return with errors +// and the channels passed to Eavesdrop and Signal are closed. This method must +// not be called on shared connections. +func (conn *Conn) Close() error { + conn.outLck.Lock() + if conn.closed { + // inWorker calls Close on read error, the read error may + // be caused by another caller calling Close to shutdown the + // dbus connection, a double-close scenario we prevent here. + conn.outLck.Unlock() + return nil + } + close(conn.out) + conn.closed = true + conn.outLck.Unlock() + + if term, ok := conn.signalHandler.(Terminator); ok { + term.Terminate() + } + + if term, ok := conn.handler.(Terminator); ok { + term.Terminate() + } + + conn.eavesdroppedLck.Lock() + if conn.eavesdropped != nil { + close(conn.eavesdropped) + } + conn.eavesdroppedLck.Unlock() + + return conn.transport.Close() +} + +// Eavesdrop causes conn to send all incoming messages to the given channel +// without further processing. Method replies, errors and signals will not be +// sent to the appropiate channels and method calls will not be handled. If nil +// is passed, the normal behaviour is restored. +// +// The caller has to make sure that ch is sufficiently buffered; +// if a message arrives when a write to ch is not possible, the message is +// discarded. +func (conn *Conn) Eavesdrop(ch chan<- *Message) { + conn.eavesdroppedLck.Lock() + conn.eavesdropped = ch + conn.eavesdroppedLck.Unlock() +} + +// getSerial returns an unused serial. +func (conn *Conn) getSerial() uint32 { + conn.serialLck.Lock() + defer conn.serialLck.Unlock() + n := conn.nextSerial + for conn.serialUsed[n] { + n++ + } + conn.serialUsed[n] = true + conn.nextSerial = n + 1 + return n +} + +// Hello sends the initial org.freedesktop.DBus.Hello call. This method must be +// called after authentication, but before sending any other messages to the +// bus. Hello must not be called for shared connections. +func (conn *Conn) Hello() error { + var s string + err := conn.busObj.Call("org.freedesktop.DBus.Hello", 0).Store(&s) + if err != nil { + return err + } + conn.namesLck.Lock() + conn.names = make([]string, 1) + conn.names[0] = s + conn.namesLck.Unlock() + return nil +} + +// inWorker runs in an own goroutine, reading incoming messages from the +// transport and dispatching them appropiately. +func (conn *Conn) inWorker() { + for { + msg, err := conn.ReadMessage() + if err == nil { + conn.eavesdroppedLck.Lock() + if conn.eavesdropped != nil { + select { + case conn.eavesdropped <- msg: + default: + } + conn.eavesdroppedLck.Unlock() + continue + } + conn.eavesdroppedLck.Unlock() + dest, _ := msg.Headers[FieldDestination].value.(string) + found := false + if dest == "" { + found = true + } else { + conn.namesLck.RLock() + if len(conn.names) == 0 { + found = true + } + for _, v := range conn.names { + if dest == v { + found = true + break + } + } + conn.namesLck.RUnlock() + } + if !found { + // Eavesdropped a message, but no channel for it is registered. + // Ignore it. + continue + } + switch msg.Type { + case TypeMethodReply, TypeError: + serial := msg.Headers[FieldReplySerial].value.(uint32) + conn.callsLck.Lock() + if c, ok := conn.calls[serial]; ok { + if msg.Type == TypeError { + name, _ := msg.Headers[FieldErrorName].value.(string) + c.Err = Error{name, msg.Body} + } else { + c.Body = msg.Body + } + c.Done <- c + conn.serialLck.Lock() + delete(conn.serialUsed, serial) + conn.serialLck.Unlock() + delete(conn.calls, serial) + } + conn.callsLck.Unlock() + case TypeSignal: + iface := msg.Headers[FieldInterface].value.(string) + member := msg.Headers[FieldMember].value.(string) + // as per http://dbus.freedesktop.org/doc/dbus-specification.html , + // sender is optional for signals. + sender, _ := msg.Headers[FieldSender].value.(string) + if iface == "org.freedesktop.DBus" && sender == "org.freedesktop.DBus" { + if member == "NameLost" { + // If we lost the name on the bus, remove it from our + // tracking list. + name, ok := msg.Body[0].(string) + if !ok { + panic("Unable to read the lost name") + } + conn.namesLck.Lock() + for i, v := range conn.names { + if v == name { + conn.names = append(conn.names[:i], + conn.names[i+1:]...) + } + } + conn.namesLck.Unlock() + } else if member == "NameAcquired" { + // If we acquired the name on the bus, add it to our + // tracking list. + name, ok := msg.Body[0].(string) + if !ok { + panic("Unable to read the acquired name") + } + conn.namesLck.Lock() + conn.names = append(conn.names, name) + conn.namesLck.Unlock() + } + } + go conn.handleSignal(msg) + case TypeMethodCall: + go conn.handleCall(msg) + } + } else if _, ok := err.(InvalidMessageError); !ok { + // Some read error occured (usually EOF); we can't really do + // anything but to shut down all stuff and returns errors to all + // pending replies. + conn.Close() + conn.callsLck.RLock() + for _, v := range conn.calls { + v.Err = err + v.Done <- v + } + conn.callsLck.RUnlock() + return + } + // invalid messages are ignored + } +} + +func (conn *Conn) handleSignal(msg *Message) { + iface := msg.Headers[FieldInterface].value.(string) + member := msg.Headers[FieldMember].value.(string) + // as per http://dbus.freedesktop.org/doc/dbus-specification.html , + // sender is optional for signals. + sender, _ := msg.Headers[FieldSender].value.(string) + signal := &Signal{ + Sender: sender, + Path: msg.Headers[FieldPath].value.(ObjectPath), + Name: iface + "." + member, + Body: msg.Body, + } + conn.signalHandler.DeliverSignal(iface, member, signal) +} + +// Names returns the list of all names that are currently owned by this +// connection. The slice is always at least one element long, the first element +// being the unique name of the connection. +func (conn *Conn) Names() []string { + conn.namesLck.RLock() + // copy the slice so it can't be modified + s := make([]string, len(conn.names)) + copy(s, conn.names) + conn.namesLck.RUnlock() + return s +} + +// Object returns the object identified by the given destination name and path. +func (conn *Conn) Object(dest string, path ObjectPath) BusObject { + return &Object{conn, dest, path} +} + +// outWorker runs in an own goroutine, encoding and sending messages that are +// sent to conn.out. +func (conn *Conn) outWorker() { + for msg := range conn.out { + err := conn.SendMessage(msg) + conn.callsLck.RLock() + if err != nil { + if c := conn.calls[msg.serial]; c != nil { + c.Err = err + c.Done <- c + } + conn.serialLck.Lock() + delete(conn.serialUsed, msg.serial) + conn.serialLck.Unlock() + } else if msg.Type != TypeMethodCall { + conn.serialLck.Lock() + delete(conn.serialUsed, msg.serial) + conn.serialLck.Unlock() + } + conn.callsLck.RUnlock() + } +} + +// Send sends the given message to the message bus. You usually don't need to +// use this; use the higher-level equivalents (Call / Go, Emit and Export) +// instead. If msg is a method call and NoReplyExpected is not set, a non-nil +// call is returned and the same value is sent to ch (which must be buffered) +// once the call is complete. Otherwise, ch is ignored and a Call structure is +// returned of which only the Err member is valid. +func (conn *Conn) Send(msg *Message, ch chan *Call) *Call { + var call *Call + + msg.serial = conn.getSerial() + if msg.Type == TypeMethodCall && msg.Flags&FlagNoReplyExpected == 0 { + if ch == nil { + ch = make(chan *Call, 5) + } else if cap(ch) == 0 { + panic("dbus: unbuffered channel passed to (*Conn).Send") + } + call = new(Call) + call.Destination, _ = msg.Headers[FieldDestination].value.(string) + call.Path, _ = msg.Headers[FieldPath].value.(ObjectPath) + iface, _ := msg.Headers[FieldInterface].value.(string) + member, _ := msg.Headers[FieldMember].value.(string) + call.Method = iface + "." + member + call.Args = msg.Body + call.Done = ch + conn.callsLck.Lock() + conn.calls[msg.serial] = call + conn.callsLck.Unlock() + conn.outLck.RLock() + if conn.closed { + call.Err = ErrClosed + call.Done <- call + } else { + conn.out <- msg + } + conn.outLck.RUnlock() + } else { + conn.outLck.RLock() + if conn.closed { + call = &Call{Err: ErrClosed} + } else { + conn.out <- msg + call = &Call{Err: nil} + } + conn.outLck.RUnlock() + } + return call +} + +// sendError creates an error message corresponding to the parameters and sends +// it to conn.out. +func (conn *Conn) sendError(err error, dest string, serial uint32) { + var e *Error + switch em := err.(type) { + case Error: + e = &em + case *Error: + e = em + case DBusError: + name, body := em.DBusError() + e = NewError(name, body) + default: + e = MakeFailedError(err) + } + msg := new(Message) + msg.Type = TypeError + msg.serial = conn.getSerial() + msg.Headers = make(map[HeaderField]Variant) + if dest != "" { + msg.Headers[FieldDestination] = MakeVariant(dest) + } + msg.Headers[FieldErrorName] = MakeVariant(e.Name) + msg.Headers[FieldReplySerial] = MakeVariant(serial) + msg.Body = e.Body + if len(e.Body) > 0 { + msg.Headers[FieldSignature] = MakeVariant(SignatureOf(e.Body...)) + } + conn.outLck.RLock() + if !conn.closed { + conn.out <- msg + } + conn.outLck.RUnlock() +} + +// sendReply creates a method reply message corresponding to the parameters and +// sends it to conn.out. +func (conn *Conn) sendReply(dest string, serial uint32, values ...interface{}) { + msg := new(Message) + msg.Type = TypeMethodReply + msg.serial = conn.getSerial() + msg.Headers = make(map[HeaderField]Variant) + if dest != "" { + msg.Headers[FieldDestination] = MakeVariant(dest) + } + msg.Headers[FieldReplySerial] = MakeVariant(serial) + msg.Body = values + if len(values) > 0 { + msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...)) + } + conn.outLck.RLock() + if !conn.closed { + conn.out <- msg + } + conn.outLck.RUnlock() +} + +func (conn *Conn) defaultSignalAction(fn func(h *defaultSignalHandler, ch chan<- *Signal), ch chan<- *Signal) { + if !isDefaultSignalHandler(conn.signalHandler) { + return + } + handler := conn.signalHandler.(*defaultSignalHandler) + fn(handler, ch) +} + +// Signal registers the given channel to be passed all received signal messages. +// The caller has to make sure that ch is sufficiently buffered; if a message +// arrives when a write to c is not possible, it is discarded. +// +// Multiple of these channels can be registered at the same time. +// +// These channels are "overwritten" by Eavesdrop; i.e., if there currently is a +// channel for eavesdropped messages, this channel receives all signals, and +// none of the channels passed to Signal will receive any signals. +func (conn *Conn) Signal(ch chan<- *Signal) { + conn.defaultSignalAction((*defaultSignalHandler).addSignal, ch) +} + +// RemoveSignal removes the given channel from the list of the registered channels. +func (conn *Conn) RemoveSignal(ch chan<- *Signal) { + conn.defaultSignalAction((*defaultSignalHandler).removeSignal, ch) +} + +// SupportsUnixFDs returns whether the underlying transport supports passing of +// unix file descriptors. If this is false, method calls containing unix file +// descriptors will return an error and emitted signals containing them will +// not be sent. +func (conn *Conn) SupportsUnixFDs() bool { + return conn.unixFD +} + +// Error represents a D-Bus message of type Error. +type Error struct { + Name string + Body []interface{} +} + +func NewError(name string, body []interface{}) *Error { + return &Error{name, body} +} + +func (e Error) Error() string { + if len(e.Body) >= 1 { + s, ok := e.Body[0].(string) + if ok { + return s + } + } + return e.Name +} + +// Signal represents a D-Bus message of type Signal. The name member is given in +// "interface.member" notation, e.g. org.freedesktop.D-Bus.NameLost. +type Signal struct { + Sender string + Path ObjectPath + Name string + Body []interface{} +} + +// transport is a D-Bus transport. +type transport interface { + // Read and Write raw data (for example, for the authentication protocol). + io.ReadWriteCloser + + // Send the initial null byte used for the EXTERNAL mechanism. + SendNullByte() error + + // Returns whether this transport supports passing Unix FDs. + SupportsUnixFDs() bool + + // Signal the transport that Unix FD passing is enabled for this connection. + EnableUnixFDs() + + // Read / send a message, handling things like Unix FDs. + ReadMessage() (*Message, error) + SendMessage(*Message) error +} + +var ( + transports = make(map[string]func(string) (transport, error)) +) + +func getTransport(address string) (transport, error) { + var err error + var t transport + + addresses := strings.Split(address, ";") + for _, v := range addresses { + i := strings.IndexRune(v, ':') + if i == -1 { + err = errors.New("dbus: invalid bus address (no transport)") + continue + } + f := transports[v[:i]] + if f == nil { + err = errors.New("dbus: invalid bus address (invalid or unsupported transport)") + continue + } + t, err = f(v[i+1:]) + if err == nil { + return t, nil + } + } + return nil, err +} + +// dereferenceAll returns a slice that, assuming that vs is a slice of pointers +// of arbitrary types, containes the values that are obtained from dereferencing +// all elements in vs. +func dereferenceAll(vs []interface{}) []interface{} { + for i := range vs { + v := reflect.ValueOf(vs[i]) + v = v.Elem() + vs[i] = v.Interface() + } + return vs +} + +// getKey gets a key from a the list of keys. Returns "" on error / not found... +func getKey(s, key string) string { + for _, keyEqualsValue := range strings.Split(s, ",") { + keyValue := strings.SplitN(keyEqualsValue, "=", 2) + if len(keyValue) == 2 && keyValue[0] == key { + return keyValue[1] + } + } + return "" +} diff -Nru snapd-2.27.5/vendor/github.com/godbus/dbus/conn_other.go snapd-2.28.5/vendor/github.com/godbus/dbus/conn_other.go --- snapd-2.27.5/vendor/github.com/godbus/dbus/conn_other.go 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/godbus/dbus/conn_other.go 2017-08-23 09:04:17.000000000 +0000 @@ -0,0 +1,42 @@ +// +build !darwin + +package dbus + +import ( + "bytes" + "errors" + "fmt" + "os" + "os/exec" +) + +const defaultSystemBusAddress = "unix:path=/var/run/dbus/system_bus_socket" + +func getSessionBusPlatformAddress() (string, error) { + cmd := exec.Command("dbus-launch") + b, err := cmd.CombinedOutput() + + if err != nil { + return "", err + } + + i := bytes.IndexByte(b, '=') + j := bytes.IndexByte(b, '\n') + + if i == -1 || j == -1 { + return "", errors.New("dbus: couldn't determine address of session bus") + } + + env, addr := string(b[0:i]), string(b[i+1:j]) + os.Setenv(env, addr) + + return addr, nil +} + +func getSystemBusPlatformAddress() string { + address := os.Getenv("DBUS_SYSTEM_BUS_ADDRESS") + if address != "" { + return fmt.Sprintf("unix:path=%s", address) + } + return defaultSystemBusAddress +} diff -Nru snapd-2.27.5/vendor/github.com/godbus/dbus/CONTRIBUTING.md snapd-2.28.5/vendor/github.com/godbus/dbus/CONTRIBUTING.md --- snapd-2.27.5/vendor/github.com/godbus/dbus/CONTRIBUTING.md 1970-01-01 00:00:00.000000000 +0000 +++ snapd-2.28.5/vendor/github.com/godbus/dbus/CONTRIBUTING.md 2017-08-23 09:04:17.000000000 +0000 @@ -0,0 +1,50 @@ +# How to Contribute + +## Getting Started + +- Fork the repository on GitHub +- Read the [README](README.markdown) for build and test instructions +- Play with the project, submit bugs, submit patches! + +## Contribution Flow + +This is a rough outline of what a contributor's workflow looks like: + +- Create a topic branch from where you want to base your work (usually master). +- Make commits of logical units. +- Make sure your commit messages are in the proper format (see below). +- Push your changes to a topic branch in your fork of the repository. +- Make sure the tests pass, and add any new tests as appropriate. +- Submit a pull request to the original repository. + +Thanks for your contributions! + +### Format of the Commit Message + +We follow a rough convention for commit messages that is designed to answer two +questions: what changed and why. The subject line should feature the what and +the body of the commit should describe the why. + +``` +scripts: add the test-cluster command + +this uses tmux to setup a test cluster that you can easily kill and +start for debugging. + +Fixes #38 +``` + +The format can be described more formally as follows: + +``` +: + + + +