diff -Nru snapd-2.28.5/apparmor/probe.go snapd-2.29.3/apparmor/probe.go
--- snapd-2.28.5/apparmor/probe.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/apparmor/probe.go 1970-01-01 00:00:00.000000000 +0000
@@ -1,151 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2017 Canonical Ltd
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-
-package 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.28.5/apparmor/probe_test.go snapd-2.29.3/apparmor/probe_test.go
--- snapd-2.28.5/apparmor/probe_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/apparmor/probe_test.go 1970-01-01 00:00:00.000000000 +0000
@@ -1,77 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2017 Canonical Ltd
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-
-package 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.28.5/asserts/account_key.go snapd-2.29.3/asserts/account_key.go
--- snapd-2.28.5/asserts/account_key.go 2016-11-24 09:36:03.000000000 +0000
+++ snapd-2.29.3/asserts/account_key.go 2017-10-23 06:17:27.000000000 +0000
@@ -102,7 +102,7 @@
_, err := db.Find(AccountType, map[string]string{
"account-id": ak.AccountID(),
})
- if err == ErrNotFound {
+ if IsNotFound(err) {
return fmt.Errorf("account-key assertion for %q does not have a matching account assertion", ak.AccountID())
}
if err != nil {
@@ -119,7 +119,7 @@
"account-id": ak.AccountID(),
"name": ak.Name(),
})
- if err != nil && err != ErrNotFound {
+ if err != nil && !IsNotFound(err) {
return err
}
for _, assertion := range assertions {
@@ -227,7 +227,7 @@
_, err := db.Find(AccountType, map[string]string{
"account-id": akr.AccountID(),
})
- if err == ErrNotFound {
+ if IsNotFound(err) {
return fmt.Errorf("account-key-request assertion for %q does not have a matching account assertion", akr.AccountID())
}
if err != nil {
diff -Nru snapd-2.28.5/asserts/asserts.go snapd-2.29.3/asserts/asserts.go
--- snapd-2.28.5/asserts/asserts.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/asserts/asserts.go 2017-10-23 06:17:27.000000000 +0000
@@ -153,6 +153,39 @@
return formatnum, nil
}
+// HeadersFromPrimaryKey constructs a headers mapping from the
+// primaryKey values and the assertion type, it errors if primaryKey
+// has the wrong length.
+func HeadersFromPrimaryKey(assertType *AssertionType, primaryKey []string) (headers map[string]string, err error) {
+ if len(primaryKey) != len(assertType.PrimaryKey) {
+ return nil, fmt.Errorf("primary key has wrong length for %q assertion", assertType.Name)
+ }
+ headers = make(map[string]string, len(assertType.PrimaryKey))
+ for i, name := range assertType.PrimaryKey {
+ keyVal := primaryKey[i]
+ if keyVal == "" {
+ return nil, fmt.Errorf("primary key %q header cannot be empty", name)
+ }
+ headers[name] = keyVal
+ }
+ return headers, nil
+}
+
+// PrimaryKeyFromHeaders extracts the tuple of values from headers
+// corresponding to a primary key under the assertion type, it errors
+// if there are missing primary key headers.
+func PrimaryKeyFromHeaders(assertType *AssertionType, headers map[string]string) (primaryKey []string, err error) {
+ primaryKey = make([]string, len(assertType.PrimaryKey))
+ for i, k := range assertType.PrimaryKey {
+ keyVal := headers[k]
+ if keyVal == "" {
+ return nil, fmt.Errorf("must provide primary key: %v", k)
+ }
+ primaryKey[i] = keyVal
+ }
+ return primaryKey, nil
+}
+
// Ref expresses a reference to an assertion.
type Ref struct {
Type *AssertionType
@@ -184,13 +217,10 @@
// Resolve resolves the reference using the given find function.
func (ref *Ref) Resolve(find func(assertType *AssertionType, headers map[string]string) (Assertion, error)) (Assertion, error) {
- if len(ref.PrimaryKey) != len(ref.Type.PrimaryKey) {
+ headers, err := HeadersFromPrimaryKey(ref.Type, ref.PrimaryKey)
+ if err != nil {
return nil, fmt.Errorf("%q assertion reference primary key has the wrong length (expected %v): %v", ref.Type.Name, ref.Type.PrimaryKey, ref.PrimaryKey)
}
- headers := make(map[string]string, len(ref.PrimaryKey))
- for i, name := range ref.Type.PrimaryKey {
- headers[name] = ref.PrimaryKey[i]
- }
return find(ref.Type, headers)
}
diff -Nru snapd-2.28.5/asserts/assertstest/assertstest.go snapd-2.29.3/asserts/assertstest/assertstest.go
--- snapd-2.28.5/asserts/assertstest/assertstest.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/asserts/assertstest/assertstest.go 2017-10-23 06:17:27.000000000 +0000
@@ -415,7 +415,7 @@
"account-id": ss.AuthorityID,
"public-key-sha3-384": keyID,
})
- if err == asserts.ErrNotFound {
+ if asserts.IsNotFound(err) {
return nil
}
if err != nil {
diff -Nru snapd-2.28.5/asserts/asserts_test.go snapd-2.29.3/asserts/asserts_test.go
--- snapd-2.28.5/asserts/asserts_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/asserts/asserts_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -77,6 +77,40 @@
c.Check(fmtnum, Equals, 0)
}
+func (as *assertsSuite) TestPrimaryKeyHelpers(c *C) {
+ headers, err := asserts.HeadersFromPrimaryKey(asserts.TestOnlyType, []string{"one"})
+ c.Assert(err, IsNil)
+ c.Check(headers, DeepEquals, map[string]string{
+ "primary-key": "one",
+ })
+
+ headers, err = asserts.HeadersFromPrimaryKey(asserts.TestOnly2Type, []string{"bar", "baz"})
+ c.Assert(err, IsNil)
+ c.Check(headers, DeepEquals, map[string]string{
+ "pk1": "bar",
+ "pk2": "baz",
+ })
+
+ _, err = asserts.HeadersFromPrimaryKey(asserts.TestOnly2Type, []string{"bar"})
+ c.Check(err, ErrorMatches, `primary key has wrong length for "test-only-2" assertion`)
+
+ _, err = asserts.HeadersFromPrimaryKey(asserts.TestOnly2Type, []string{"", "baz"})
+ c.Check(err, ErrorMatches, `primary key "pk1" header cannot be empty`)
+
+ pk, err := asserts.PrimaryKeyFromHeaders(asserts.TestOnly2Type, headers)
+ c.Assert(err, IsNil)
+ c.Check(pk, DeepEquals, []string{"bar", "baz"})
+
+ headers["other"] = "foo"
+ pk1, err := asserts.PrimaryKeyFromHeaders(asserts.TestOnly2Type, headers)
+ c.Assert(err, IsNil)
+ c.Check(pk1, DeepEquals, pk)
+
+ delete(headers, "pk2")
+ _, err = asserts.PrimaryKeyFromHeaders(asserts.TestOnly2Type, headers)
+ c.Check(err, ErrorMatches, `must provide primary key: pk2`)
+}
+
func (as *assertsSuite) TestRef(c *C) {
ref := &asserts.Ref{
Type: asserts.TestOnly2Type,
diff -Nru snapd-2.28.5/asserts/database.go snapd-2.29.3/asserts/database.go
--- snapd-2.28.5/asserts/database.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/asserts/database.go 2017-10-23 06:17:27.000000000 +0000
@@ -22,12 +22,33 @@
package asserts
import (
- "errors"
"fmt"
"regexp"
"time"
)
+// NotFoundError is returned when an assertion can not be found.
+type NotFoundError struct {
+ Type *AssertionType
+ Headers map[string]string
+}
+
+func (e *NotFoundError) Error() string {
+ pk, err := PrimaryKeyFromHeaders(e.Type, e.Headers)
+ if err != nil || len(e.Headers) != len(pk) {
+ // TODO: worth conveying more information?
+ return fmt.Sprintf("%s assertion not found", e.Type.Name)
+ }
+
+ return fmt.Sprintf("%v not found", &Ref{Type: e.Type, PrimaryKey: pk})
+}
+
+// IsNotFound returns whether err is an assertion not found error.
+func IsNotFound(err error) bool {
+ _, ok := err.(*NotFoundError)
+ return ok
+}
+
// A Backstore stores assertions. It can store and retrieve assertions
// by type under unique primary key headers (whose names are available
// from assertType.PrimaryKey). Plus it supports searching by headers.
@@ -37,8 +58,9 @@
// It is responsible for checking that assert is newer than a
// previously stored revision with the same primary key headers.
Put(assertType *AssertionType, assert Assertion) error
- // Get returns the assertion with the given unique key for its primary key headers.
- // If none is present it returns ErrNotFound.
+ // Get returns the assertion with the given unique key for its
+ // primary key headers. If none is present it returns a
+ // NotFoundError, usually with omitted Headers.
Get(assertType *AssertionType, key []string, maxFormat int) (Assertion, error)
// Search returns assertions matching the given headers.
// It invokes foundCb for each found assertion.
@@ -52,7 +74,7 @@
}
func (nbs nullBackstore) Get(t *AssertionType, k []string, maxFormat int) (Assertion, error) {
- return nil, ErrNotFound
+ return nil, &NotFoundError{Type: t}
}
func (nbs nullBackstore) Search(t *AssertionType, h map[string]string, f func(Assertion), maxFormat int) error {
@@ -85,11 +107,6 @@
Checkers []Checker
}
-// Well-known errors
-var (
- ErrNotFound = errors.New("assertion not found")
-)
-
// RevisionError indicates a revision improperly used for an operation.
type RevisionError struct {
Used, Current int
@@ -144,25 +161,25 @@
IsTrustedAccount(accountID string) bool
// Find an assertion based on arbitrary headers.
// Provided headers must contain the primary key for the assertion type.
- // It returns ErrNotFound if the assertion cannot be found.
+ // It returns a NotFoundError if the assertion cannot be found.
Find(assertionType *AssertionType, headers map[string]string) (Assertion, error)
// 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.
+ // type. It returns a NotFoundError 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.
+ // primary key for the assertion type. It returns a
+ // NotFoundError 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.
+ // It returns a NotFoundError if no assertion can be found.
FindMany(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.
+ // (trusted or not) based on arbitrary headers. It returns a
+ // NotFoundError 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
@@ -299,11 +316,11 @@
}
return hit, nil
}
- if err != ErrNotFound {
+ if !IsNotFound(err) {
return nil, err
}
}
- return nil, ErrNotFound
+ return nil, &NotFoundError{Type: AccountKeyType}
}
// IsTrustedAccount returns whether the account is part of the trusted set.
@@ -329,7 +346,7 @@
if typ.flags&noAuthority == 0 {
// TODO: later may need to consider type of assert to find candidate keys
accKey, err = db.findAccountKey(assert.AuthorityID(), assert.SignKeyID())
- if err == ErrNotFound {
+ if IsNotFound(err) {
return fmt.Errorf("no matching public key %q for signature by %q", assert.SignKeyID(), assert.AuthorityID())
}
if err != nil {
@@ -364,7 +381,7 @@
if err != nil {
if ufe, ok := err.(*UnsupportedFormatError); ok {
_, err := ref.Resolve(db.Find)
- if err != nil && err != ErrNotFound {
+ if err != nil && !IsNotFound(err) {
return err
}
return &UnsupportedFormatError{Ref: ufe.Ref, Format: ufe.Format, Update: err == nil}
@@ -382,12 +399,12 @@
// through the os snap this seems the safest policy until we
// know more/better
_, err = db.trusted.Get(ref.Type, ref.PrimaryKey, ref.Type.MaxSupportedFormat())
- if err != ErrNotFound {
+ if !IsNotFound(err) {
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 {
+ if !IsNotFound(err) {
return fmt.Errorf("cannot add %q assertion with primary key clashing with a predefined assertion: %v", ref.Type.Name, ref.PrimaryKey)
}
@@ -417,13 +434,10 @@
return nil, fmt.Errorf("cannot find %q assertions for format %d higher than supported format %d", assertionType.Name, maxFormat, maxSupp)
}
}
- keyValues := make([]string, len(assertionType.PrimaryKey))
- for i, k := range assertionType.PrimaryKey {
- keyVal := headers[k]
- if keyVal == "" {
- return nil, fmt.Errorf("must provide primary key: %v", k)
- }
- keyValues[i] = keyVal
+
+ keyValues, err := PrimaryKeyFromHeaders(assertionType, headers)
+ if err != nil {
+ return nil, err
}
var assert Assertion
@@ -433,13 +447,13 @@
assert = a
break
}
- if err != ErrNotFound {
+ if !IsNotFound(err) {
return nil, err
}
}
if assert == nil || !searchMatch(assert, headers) {
- return nil, ErrNotFound
+ return nil, &NotFoundError{Type: assertionType, Headers: headers}
}
return assert, nil
@@ -447,29 +461,29 @@
// Find an assertion based on arbitrary headers.
// Provided headers must contain the primary key for the assertion type.
-// It returns ErrNotFound if the assertion cannot be found.
+// It returns a NotFoundError if the assertion cannot be found.
func (db *Database) Find(assertionType *AssertionType, headers map[string]string) (Assertion, error) {
return find(db.backstores, assertionType, headers, -1)
}
// FindMaxFormat finds an assertion like Find but such that its
// format is <= maxFormat by passing maxFormat along to the backend.
-// It returns ErrNotFound if such an assertion cannot be found.
+// It returns a NotFoundError if such an assertion cannot be found.
func (db *Database) FindMaxFormat(assertionType *AssertionType, headers map[string]string, maxFormat int) (Assertion, error) {
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.
+// the primary key for the assertion type. It returns a NotFoundError
+// 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.
+// It returns a NotFoundError if the assertion cannot be found.
func (db *Database) FindTrusted(assertionType *AssertionType, headers map[string]string) (Assertion, error) {
return find([]Backstore{db.trusted}, assertionType, headers, -1)
}
@@ -495,20 +509,20 @@
}
if len(res) == 0 {
- return nil, ErrNotFound
+ return nil, &NotFoundError{Type: assertionType, Headers: headers}
}
return res, nil
}
// FindMany finds assertions based on arbitrary headers.
-// It returns ErrNotFound if no assertion can be found.
+// It returns a NotFoundError if no assertion can be found.
func (db *Database) FindMany(assertionType *AssertionType, headers map[string]string) ([]Assertion, error) {
return db.findMany(db.backstores, 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.
+// or not) based on arbitrary headers. It returns a NotFoundError 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)
}
diff -Nru snapd-2.28.5/asserts/database_test.go snapd-2.29.3/asserts/database_test.go
--- snapd-2.28.5/asserts/database_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/asserts/database_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -636,6 +636,24 @@
c.Check(asserts.IsUnaccceptedUpdate(err), Equals, true)
}
+func (safs *signAddFindSuite) TestNotFoundError(c *C) {
+ err1 := &asserts.NotFoundError{
+ Type: asserts.SnapDeclarationType,
+ Headers: map[string]string{
+ "series": "16",
+ "snap-id": "snap-id",
+ },
+ }
+ c.Check(asserts.IsNotFound(err1), Equals, true)
+ c.Check(err1.Error(), Equals, "snap-declaration (snap-id; series:16) not found")
+
+ err2 := &asserts.NotFoundError{
+ Type: asserts.SnapRevisionType,
+ }
+ c.Check(asserts.IsNotFound(err1), Equals, true)
+ c.Check(err2.Error(), Equals, "snap-revision assertion not found")
+}
+
func (safs *signAddFindSuite) TestFindNotFound(c *C) {
headers := map[string]interface{}{
"authority-id": "canonical",
@@ -647,18 +665,26 @@
err = safs.db.Add(a1)
c.Assert(err, IsNil)
- retrieved1, err := safs.db.Find(asserts.TestOnlyType, map[string]string{
+ hdrs := map[string]string{
"primary-key": "b",
+ }
+ retrieved1, err := safs.db.Find(asserts.TestOnlyType, hdrs)
+ c.Assert(err, DeepEquals, &asserts.NotFoundError{
+ Type: asserts.TestOnlyType,
+ Headers: hdrs,
})
- c.Assert(err, Equals, asserts.ErrNotFound)
c.Check(retrieved1, IsNil)
// checking also extra headers
- retrieved1, err = safs.db.Find(asserts.TestOnlyType, map[string]string{
+ hdrs = map[string]string{
"primary-key": "a",
"authority-id": "other-auth-id",
+ }
+ retrieved1, err = safs.db.Find(asserts.TestOnlyType, hdrs)
+ c.Assert(err, DeepEquals, &asserts.NotFoundError{
+ Type: asserts.TestOnlyType,
+ Headers: hdrs,
})
- c.Assert(err, Equals, asserts.ErrNotFound)
c.Check(retrieved1, IsNil)
}
@@ -726,12 +752,16 @@
c.Assert(err, IsNil)
c.Assert(res, HasLen, 1)
- res, err = safs.db.FindMany(asserts.TestOnlyType, map[string]string{
+ hdrs := map[string]string{
"primary-key": "b",
"other": "other-x",
- })
+ }
+ res, err = safs.db.FindMany(asserts.TestOnlyType, hdrs)
c.Assert(res, HasLen, 0)
- c.Check(err, Equals, asserts.ErrNotFound)
+ c.Check(err, DeepEquals, &asserts.NotFoundError{
+ Type: asserts.TestOnlyType,
+ Headers: hdrs,
+ })
}
func (safs *signAddFindSuite) TestFindFindsPredefined(c *C) {
@@ -810,21 +840,29 @@
c.Assert(tKey.(*asserts.AccountKey).PublicKeyID(), Equals, safs.signingKeyID)
// doesn't find not trusted assertions
- _, err = safs.db.FindTrusted(asserts.AccountType, map[string]string{
+ hdrs := map[string]string{
"account-id": acct1.AccountID(),
+ }
+ _, err = safs.db.FindTrusted(asserts.AccountType, hdrs)
+ c.Check(err, DeepEquals, &asserts.NotFoundError{
+ Type: asserts.AccountType,
+ Headers: hdrs,
})
- c.Check(err, Equals, asserts.ErrNotFound)
- _, err = safs.db.FindTrusted(asserts.AccountKeyType, map[string]string{
+ hdrs = map[string]string{
"account-id": acct1.AccountID(),
"public-key-sha3-384": acct1Key.PublicKeyID(),
+ }
+ _, err = safs.db.FindTrusted(asserts.AccountKeyType, hdrs)
+ c.Check(err, DeepEquals, &asserts.NotFoundError{
+ Type: asserts.AccountKeyType,
+ Headers: hdrs,
})
- c.Check(err, Equals, asserts.ErrNotFound)
_, err = safs.db.FindTrusted(asserts.AccountType, map[string]string{
"account-id": "predefined",
})
- c.Check(err, Equals, asserts.ErrNotFound)
+ c.Check(asserts.IsNotFound(err), Equals, true)
}
func (safs *signAddFindSuite) TestFindPredefined(c *C) {
@@ -868,16 +906,24 @@
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{
+ hdrs := map[string]string{
"account-id": acct1.AccountID(),
+ }
+ _, err = safs.db.FindPredefined(asserts.AccountType, hdrs)
+ c.Check(err, DeepEquals, &asserts.NotFoundError{
+ Type: asserts.AccountType,
+ Headers: hdrs,
})
- c.Check(err, Equals, asserts.ErrNotFound)
- _, err = safs.db.FindPredefined(asserts.AccountKeyType, map[string]string{
+ hdrs = map[string]string{
"account-id": acct1.AccountID(),
"public-key-sha3-384": acct1Key.PublicKeyID(),
+ }
+ _, err = safs.db.FindPredefined(asserts.AccountKeyType, hdrs)
+ c.Check(err, DeepEquals, &asserts.NotFoundError{
+ Type: asserts.AccountKeyType,
+ Headers: hdrs,
})
- c.Check(err, Equals, asserts.ErrNotFound)
}
func (safs *signAddFindSuite) TestFindManyPredefined(c *C) {
@@ -956,16 +1002,20 @@
})
// doesn't find not predefined assertions
- _, err = db.FindManyPredefined(asserts.AccountType, map[string]string{
+ hdrs := map[string]string{
"account-id": acct1.AccountID(),
+ }
+ _, err = db.FindManyPredefined(asserts.AccountType, hdrs)
+ c.Check(err, DeepEquals, &asserts.NotFoundError{
+ Type: asserts.AccountType,
+ Headers: hdrs,
})
- c.Check(err, Equals, asserts.ErrNotFound)
_, err = db.FindManyPredefined(asserts.AccountKeyType, map[string]string{
"account-id": acct1.AccountID(),
"public-key-sha3-384": acct1Key.PublicKeyID(),
})
- c.Check(err, Equals, asserts.ErrNotFound)
+ c.Check(asserts.IsNotFound(err), Equals, true)
}
func (safs *signAddFindSuite) TestDontLetAddConfusinglyAssertionClashingWithTrustedOnes(c *C) {
@@ -1039,7 +1089,13 @@
PrimaryKey: []string{"kb", "ka"},
}
_, err = ref.Resolve(safs.db.Find)
- c.Assert(err, Equals, asserts.ErrNotFound)
+ c.Assert(err, DeepEquals, &asserts.NotFoundError{
+ Type: ref.Type,
+ Headers: map[string]string{
+ "pk1": "kb",
+ "pk2": "ka",
+ },
+ })
}
func (safs *signAddFindSuite) TestFindMaxFormat(c *C) {
@@ -1125,7 +1181,7 @@
{&asserts.RevisionError{Used: 1, Current: 5}, true},
{&asserts.RevisionError{Used: 3, Current: 1}, false},
{errors.New("other error"), false},
- {asserts.ErrNotFound, false},
+ {&asserts.NotFoundError{Type: asserts.TestOnlyType}, false},
}
for _, t := range tests {
diff -Nru snapd-2.28.5/asserts/fetcher.go snapd-2.29.3/asserts/fetcher.go
--- snapd-2.28.5/asserts/fetcher.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/asserts/fetcher.go 2017-10-23 06:17:27.000000000 +0000
@@ -66,7 +66,7 @@
if err == nil {
return nil
}
- if err != ErrNotFound {
+ if !IsNotFound(err) {
return err
}
u := ref.Unique()
diff -Nru snapd-2.28.5/asserts/fsbackstore.go snapd-2.29.3/asserts/fsbackstore.go
--- snapd-2.28.5/asserts/fsbackstore.go 2016-10-27 13:22:15.000000000 +0000
+++ snapd-2.29.3/asserts/fsbackstore.go 2017-10-23 06:17:27.000000000 +0000
@@ -55,7 +55,7 @@
func (fsbs *filesystemBackstore) readAssertion(assertType *AssertionType, diskPrimaryPath string) (Assertion, error) {
encoded, err := readEntry(fsbs.top, assertType.Name, diskPrimaryPath)
if os.IsNotExist(err) {
- return nil, ErrNotFound
+ return nil, errNotFound
}
if err != nil {
return nil, fmt.Errorf("broken assertion storage, cannot read assertion: %v", err)
@@ -94,7 +94,7 @@
}
}
if a == nil {
- return nil, ErrNotFound
+ return nil, errNotFound
}
return a, nil
}
@@ -115,7 +115,7 @@
namesCb := func(relpaths []string) error {
var err error
a, err = fsbs.pickLatestAssertion(assertType, relpaths, maxFormat)
- if err == ErrNotFound {
+ if err == errNotFound {
return nil
}
return err
@@ -129,7 +129,7 @@
}
if a == nil {
- return nil, ErrNotFound
+ return nil, errNotFound
}
return a, nil
@@ -139,10 +139,7 @@
fsbs.mu.Lock()
defer fsbs.mu.Unlock()
- primaryPath := make([]string, len(assertType.PrimaryKey))
- for i, k := range assertType.PrimaryKey {
- primaryPath[i] = assert.HeaderString(k)
- }
+ primaryPath := assert.Ref().PrimaryKey
curAssert, err := fsbs.currentAssertion(assertType, primaryPath, assertType.MaxSupportedFormat())
if err == nil {
@@ -151,7 +148,7 @@
if curRev >= rev {
return &RevisionError{Current: curRev, Used: rev}
}
- } else if err != ErrNotFound {
+ } else if err != errNotFound {
return err
}
@@ -172,14 +169,18 @@
fsbs.mu.RLock()
defer fsbs.mu.RUnlock()
- return fsbs.currentAssertion(assertType, key, maxFormat)
+ a, err := fsbs.currentAssertion(assertType, key, maxFormat)
+ if err == errNotFound {
+ return nil, &NotFoundError{Type: assertType}
+ }
+ return a, err
}
func (fsbs *filesystemBackstore) search(assertType *AssertionType, diskPattern []string, foundCb func(Assertion), maxFormat int) error {
assertTypeTop := filepath.Join(fsbs.top, assertType.Name)
candCb := func(diskPrimaryPaths []string) error {
a, err := fsbs.pickLatestAssertion(assertType, diskPrimaryPaths, maxFormat)
- if err == ErrNotFound {
+ if err == errNotFound {
return nil
}
if err != nil {
diff -Nru snapd-2.28.5/asserts/fsbackstore_test.go snapd-2.29.3/asserts/fsbackstore_test.go
--- snapd-2.28.5/asserts/fsbackstore_test.go 2016-10-27 13:22:15.000000000 +0000
+++ snapd-2.29.3/asserts/fsbackstore_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -150,13 +150,20 @@
c.Check(a.Revision(), Equals, 0)
a, err = bs.Get(asserts.TestOnlyType, []string{"zoo"}, 0)
- c.Assert(err, Equals, asserts.ErrNotFound)
+ c.Assert(err, DeepEquals, &asserts.NotFoundError{
+ Type: asserts.TestOnlyType,
+ // Headers can be omitted by Backstores
+ })
+ c.Check(a, IsNil)
err = bs.Put(asserts.TestOnlyType, af2)
c.Assert(err, IsNil)
a, err = bs.Get(asserts.TestOnlyType, []string{"zoo"}, 1)
- c.Assert(err, Equals, asserts.ErrNotFound)
+ c.Assert(err, DeepEquals, &asserts.NotFoundError{
+ Type: asserts.TestOnlyType,
+ })
+ c.Check(a, IsNil)
a, err = bs.Get(asserts.TestOnlyType, []string{"zoo"}, 2)
c.Assert(err, IsNil)
diff -Nru snapd-2.28.5/asserts/membackstore.go snapd-2.29.3/asserts/membackstore.go
--- snapd-2.28.5/asserts/membackstore.go 2016-11-24 09:36:03.000000000 +0000
+++ snapd-2.29.3/asserts/membackstore.go 2017-10-23 06:17:27.000000000 +0000
@@ -20,6 +20,7 @@
package asserts
import (
+ "errors"
"sync"
)
@@ -80,11 +81,15 @@
return nil
}
+// errNotFound is used internally by backends, it is converted to the richer
+// NotFoundError only at their public interface boundary
+var errNotFound = errors.New("assertion not found")
+
func (br memBSBranch) get(key []string, maxFormat int) (Assertion, error) {
key0 := key[0]
down := br[key0]
if down == nil {
- return nil, ErrNotFound
+ return nil, errNotFound
}
return down.get(key[1:], maxFormat)
}
@@ -93,7 +98,7 @@
key0 := key[0]
cur := leaf.cur(key0, maxFormat)
if cur == nil {
- return nil, ErrNotFound
+ return nil, errNotFound
}
return cur, nil
}
@@ -142,11 +147,9 @@
mbs.mu.Lock()
defer mbs.mu.Unlock()
- internalKey := make([]string, 1+len(assertType.PrimaryKey))
+ internalKey := make([]string, 1, 1+len(assertType.PrimaryKey))
internalKey[0] = assertType.Name
- for i, name := range assertType.PrimaryKey {
- internalKey[1+i] = assert.HeaderString(name)
- }
+ internalKey = append(internalKey, assert.Ref().PrimaryKey...)
err := mbs.top.put(assertType, internalKey, assert)
return err
@@ -160,7 +163,11 @@
internalKey[0] = assertType.Name
copy(internalKey[1:], key)
- return mbs.top.get(internalKey, maxFormat)
+ a, err := mbs.top.get(internalKey, maxFormat)
+ if err == errNotFound {
+ return nil, &NotFoundError{Type: assertType}
+ }
+ return a, err
}
func (mbs *memoryBackstore) Search(assertType *AssertionType, headers map[string]string, foundCb func(Assertion), maxFormat int) error {
diff -Nru snapd-2.28.5/asserts/membackstore_test.go snapd-2.29.3/asserts/membackstore_test.go
--- snapd-2.28.5/asserts/membackstore_test.go 2016-10-27 13:22:15.000000000 +0000
+++ snapd-2.29.3/asserts/membackstore_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -58,14 +58,19 @@
func (mbss *memBackstoreSuite) TestGetNotFound(c *C) {
a, err := mbss.bs.Get(asserts.TestOnlyType, []string{"foo"}, 0)
- c.Assert(err, Equals, asserts.ErrNotFound)
+ c.Assert(err, DeepEquals, &asserts.NotFoundError{
+ Type: asserts.TestOnlyType,
+ // Headers can be omitted by Backstores
+ })
c.Check(a, IsNil)
err = mbss.bs.Put(asserts.TestOnlyType, mbss.a)
c.Assert(err, IsNil)
a, err = mbss.bs.Get(asserts.TestOnlyType, []string{"bar"}, 0)
- c.Assert(err, Equals, asserts.ErrNotFound)
+ c.Assert(err, DeepEquals, &asserts.NotFoundError{
+ Type: asserts.TestOnlyType,
+ })
c.Check(a, IsNil)
}
@@ -247,13 +252,13 @@
c.Check(a.Revision(), Equals, 0)
a, err = bs.Get(asserts.TestOnlyType, []string{"zoo"}, 0)
- c.Assert(err, Equals, asserts.ErrNotFound)
+ c.Assert(err, FitsTypeOf, &asserts.NotFoundError{})
err = bs.Put(asserts.TestOnlyType, af2)
c.Assert(err, IsNil)
a, err = bs.Get(asserts.TestOnlyType, []string{"zoo"}, 1)
- c.Assert(err, Equals, asserts.ErrNotFound)
+ c.Assert(err, FitsTypeOf, &asserts.NotFoundError{})
a, err = bs.Get(asserts.TestOnlyType, []string{"zoo"}, 2)
c.Assert(err, IsNil)
diff -Nru snapd-2.28.5/asserts/repair.go snapd-2.29.3/asserts/repair.go
--- snapd-2.28.5/asserts/repair.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/asserts/repair.go 2017-10-23 06:17:27.000000000 +0000
@@ -20,7 +20,10 @@
package asserts
import (
+ "fmt"
"regexp"
+ "strconv"
+ "strings"
"time"
)
@@ -33,6 +36,8 @@
architectures []string
models []string
+ id int
+
disabled bool
timestamp time.Time
}
@@ -42,12 +47,17 @@
return r.HeaderString("brand-id")
}
-// RepairID returns the "id" of the repair. It should be a short string
-// that follows a convention like "REPAIR-123". Similar to a CVE there
-// should be a public place to look up details about the repair-id
+// RepairID returns the sequential id of the repair. There
+// should be a public place to look up details about the repair
+// by brand-id and repair-id.
// (e.g. the snapcraft forum).
-func (r *Repair) RepairID() string {
- return r.HeaderString("repair-id")
+func (r *Repair) RepairID() int {
+ return r.id
+}
+
+// Summary returns the mandatory summary description of the repair.
+func (r *Repair) Summary() string {
+ return r.HeaderString("summary")
}
// Architectures returns the architectures that this assertions applies to.
@@ -96,9 +106,23 @@
return nil, err
}
- if _, err = checkStringMatchesWhat(assert.headers, "repair-id", "header", validRepairID); err != nil {
+ repairID, err := checkStringMatches(assert.headers, "repair-id", validRepairID)
+ if err != nil {
return nil, err
}
+ id, err := strconv.Atoi(repairID)
+ if err != nil {
+ // given it matched it can likely only be too large
+ return nil, fmt.Errorf("repair-id too large: %s", repairID)
+ }
+
+ summary, err := checkNotEmptyString(assert.headers, "summary")
+ if err != nil {
+ return nil, err
+ }
+ if strings.ContainsAny(summary, "\n\r") {
+ return nil, fmt.Errorf(`"summary" header cannot have newlines`)
+ }
series, err := checkStringList(assert.headers, "series")
if err != nil {
@@ -128,6 +152,7 @@
series: series,
architectures: architectures,
models: models,
+ id: id,
disabled: disabled,
timestamp: timestamp,
}, nil
diff -Nru snapd-2.28.5/asserts/repair_test.go snapd-2.29.3/asserts/repair_test.go
--- snapd-2.28.5/asserts/repair_test.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/asserts/repair_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -67,6 +67,7 @@
var repairExample = fmt.Sprintf("type: repair\n"+
"authority-id: acme\n"+
"brand-id: acme\n"+
+ "summary: example repair\n"+
"architectures:\n"+
" - amd64\n"+
" - arm64\n"+
@@ -97,7 +98,8 @@
repair := a.(*asserts.Repair)
c.Check(repair.Timestamp(), Equals, s.ts)
c.Check(repair.BrandID(), Equals, "acme")
- c.Check(repair.RepairID(), Equals, "42")
+ c.Check(repair.RepairID(), Equals, 42)
+ c.Check(repair.Summary(), Equals, "example repair")
c.Check(repair.Series(), DeepEquals, []string{"16"})
c.Check(repair.Architectures(), DeepEquals, []string{"amd64", "arm64"})
c.Check(repair.Models(), DeepEquals, []string{"acme/frobinator"})
@@ -143,7 +145,11 @@
{"repair-id: 42\n", "repair-id: no-number\n", `"repair-id" header contains invalid characters: "no-number"`},
{"repair-id: 42\n", "repair-id: 0\n", `"repair-id" header contains invalid characters: "0"`},
{"repair-id: 42\n", "repair-id: 01\n", `"repair-id" header contains invalid characters: "01"`},
+ {"repair-id: 42\n", "repair-id: 99999999999999999999\n", `repair-id too large:.*`},
{"brand-id: acme\n", "brand-id: brand-id-not-eq-authority-id\n", `authority-id and brand-id must match, repair assertions are expected to be signed by the brand: "acme" != "brand-id-not-eq-authority-id"`},
+ {"summary: example repair\n", "", `"summary" header is mandatory`},
+ {"summary: example repair\n", "summary: \n", `"summary" header should not be empty`},
+ {"summary: example repair\n", "summary:\n multi\n line\n", `"summary" header cannot have newlines`},
{s.tsLine, "", `"timestamp" header is mandatory`},
{s.tsLine, "timestamp: \n", `"timestamp" header should not be empty`},
{s.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`},
diff -Nru snapd-2.28.5/asserts/snapasserts/snapasserts.go snapd-2.29.3/asserts/snapasserts/snapasserts.go
--- snapd-2.28.5/asserts/snapasserts/snapasserts.go 2017-08-18 13:48:10.000000000 +0000
+++ snapd-2.29.3/asserts/snapasserts/snapasserts.go 2017-10-23 06:17:27.000000000 +0000
@@ -29,9 +29,10 @@
)
type Finder interface {
- // Find an assertion based on arbitrary headers.
- // Provided headers must contain the primary key for the assertion type.
- // It returns ErrNotFound if the assertion cannot be found.
+ // Find an assertion based on arbitrary headers. Provided
+ // headers must contain the primary key for the assertion
+ // type. It returns a asserts.NotFoundError if the assertion
+ // cannot be found.
Find(assertionType *asserts.AssertionType, headers map[string]string) (asserts.Assertion, error)
}
@@ -85,7 +86,7 @@
return nil
}
-// DeriveSideInfo tries to construct a SideInfo for the given snap using its digest to find the relevant snap assertions with the information in the given database. It will fail with asserts.ErrNotFound if it cannot find them.
+// DeriveSideInfo tries to construct a SideInfo for the given snap using its digest to find the relevant snap assertions with the information in the given database. It will fail with an asserts.NotFoundError if it cannot find them.
func DeriveSideInfo(snapPath string, db Finder) (*snap.SideInfo, error) {
snapSHA3_384, snapSize, err := asserts.SnapFileSHA3_384(snapPath)
if err != nil {
diff -Nru snapd-2.28.5/asserts/snapasserts/snapasserts_test.go snapd-2.29.3/asserts/snapasserts/snapasserts_test.go
--- snapd-2.28.5/asserts/snapasserts/snapasserts_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/asserts/snapasserts/snapasserts_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -247,7 +247,7 @@
_, err = snapasserts.DeriveSideInfo(snapPath, s.localDB)
// cannot find signatures with metadata for snap
- c.Assert(err, Equals, asserts.ErrNotFound)
+ c.Assert(asserts.IsNotFound(err), Equals, true)
}
func (s *snapassertsSuite) TestDeriveSideInfoSizeMismatch(c *C) {
diff -Nru snapd-2.28.5/asserts/snap_asserts.go snapd-2.29.3/asserts/snap_asserts.go
--- snapd-2.28.5/asserts/snap_asserts.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/asserts/snap_asserts.go 2017-10-23 06:17:27.000000000 +0000
@@ -104,7 +104,7 @@
_, err := db.Find(AccountType, map[string]string{
"account-id": snapdcl.PublisherID(),
})
- if err == ErrNotFound {
+ if IsNotFound(err) {
return fmt.Errorf("snap-declaration assertion for %q (id %q) does not have a matching account assertion for the publisher %q", snapdcl.SnapName(), snapdcl.SnapID(), snapdcl.PublisherID())
}
if err != nil {
@@ -436,7 +436,7 @@
_, err := db.Find(AccountType, map[string]string{
"account-id": snaprev.DeveloperID(),
})
- if err == ErrNotFound {
+ if IsNotFound(err) {
return fmt.Errorf("snap-revision assertion for snap id %q does not have a matching account assertion for the developer %q", snaprev.SnapID(), snaprev.DeveloperID())
}
if err != nil {
@@ -447,7 +447,7 @@
"series": release.Series,
"snap-id": snaprev.SnapID(),
})
- if err == ErrNotFound {
+ if IsNotFound(err) {
return fmt.Errorf("snap-revision assertion for snap id %q does not have a matching snap-declaration assertion", snaprev.SnapID())
}
if err != nil {
@@ -557,7 +557,7 @@
"series": validation.Series(),
"snap-id": validation.ApprovedSnapID(),
})
- if err == ErrNotFound {
+ if IsNotFound(err) {
return fmt.Errorf("validation assertion by snap-id %q does not have a matching snap-declaration assertion for approved-snap-id %q", validation.SnapID(), validation.ApprovedSnapID())
}
if err != nil {
@@ -567,7 +567,7 @@
"series": validation.Series(),
"snap-id": validation.SnapID(),
})
- if err == ErrNotFound {
+ if IsNotFound(err) {
return fmt.Errorf("validation assertion by snap-id %q does not have a matching snap-declaration assertion", validation.SnapID())
}
if err != nil {
@@ -803,7 +803,7 @@
"snap-id": snapdev.SnapID(),
})
if err != nil {
- if err == ErrNotFound {
+ if IsNotFound(err) {
return fmt.Errorf("snap-developer assertion for snap id %q does not have a matching snap-declaration assertion", snapdev.SnapID())
}
return err
@@ -812,7 +812,7 @@
// check there's an account for the publisher-id
_, err = db.Find(AccountType, map[string]string{"account-id": publisherID})
if err != nil {
- if err == ErrNotFound {
+ if IsNotFound(err) {
return fmt.Errorf("snap-developer assertion for snap-id %q does not have a matching account assertion for the publisher %q", snapdev.SnapID(), publisherID)
}
return err
@@ -825,7 +825,7 @@
}
_, err = db.Find(AccountType, map[string]string{"account-id": developerID})
if err != nil {
- if err == ErrNotFound {
+ if IsNotFound(err) {
return fmt.Errorf("snap-developer assertion for snap-id %q does not have a matching account assertion for the developer %q", snapdev.SnapID(), developerID)
}
return err
diff -Nru snapd-2.28.5/asserts/store_asserts.go snapd-2.29.3/asserts/store_asserts.go
--- snapd-2.28.5/asserts/store_asserts.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/asserts/store_asserts.go 2017-10-23 06:17:27.000000000 +0000
@@ -1,3 +1,22 @@
+// -*- 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 asserts
import (
@@ -41,7 +60,7 @@
_, err := db.Find(AccountType, map[string]string{"account-id": store.OperatorID()})
if err != nil {
- if err == ErrNotFound {
+ if IsNotFound(err) {
return fmt.Errorf(
"store assertion %q does not have a matching account assertion for the operator %q",
store.Store(), store.OperatorID())
diff -Nru snapd-2.28.5/asserts/store_asserts_test.go snapd-2.29.3/asserts/store_asserts_test.go
--- snapd-2.28.5/asserts/store_asserts_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/asserts/store_asserts_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -1,3 +1,22 @@
+// -*- 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 asserts_test
import (
diff -Nru snapd-2.28.5/client/interfaces.go snapd-2.29.3/client/interfaces.go
--- snapd-2.28.5/client/interfaces.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/client/interfaces.go 2017-10-23 06:17:27.000000000 +0000
@@ -98,7 +98,7 @@
Connected bool
}
-func (client *Client) Interfaces(opts *InterfaceOptions) (interfaces []*Interface, err error) {
+func (client *Client) Interfaces(opts *InterfaceOptions) ([]*Interface, error) {
query := url.Values{}
if opts != nil && len(opts.Names) > 0 {
query.Set("names", strings.Join(opts.Names, ",")) // Return just those specific interfaces.
@@ -120,8 +120,10 @@
} else {
query.Set("select", "all") // Return all interfaces.
}
- _, err = client.doSync("GET", "/v2/interfaces", query, nil, nil, &interfaces)
- return
+ var interfaces []*Interface
+ _, err := client.doSync("GET", "/v2/interfaces", query, nil, nil, &interfaces)
+
+ return interfaces, err
}
// performInterfaceAction performs a single action on the interface system.
diff -Nru snapd-2.28.5/cmd/autogen.sh snapd-2.29.3/cmd/autogen.sh
--- snapd-2.28.5/cmd/autogen.sh 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/cmd/autogen.sh 2017-10-23 06:17:27.000000000 +0000
@@ -20,7 +20,7 @@
. /etc/os-release
case "$ID" in
arch)
- extra_opts="--libexecdir=/usr/lib/snapd --with-snap-mount-dir=/var/lib/snapd/snap --disable-apparmor --enable-nvidia-arch --enable-merged-usr"
+ extra_opts="--libexecdir=/usr/lib/snapd --with-snap-mount-dir=/var/lib/snapd/snap --disable-apparmor --enable-nvidia-biarch --enable-merged-usr"
;;
debian)
extra_opts="--libexecdir=/usr/lib/snapd"
@@ -28,10 +28,10 @@
ubuntu)
case "$VERSION_ID" in
16.04)
- extra_opts="--libexecdir=/usr/lib/snapd --enable-nvidia-ubuntu --enable-static-libcap --enable-static-libapparmor --enable-static-libseccomp"
+ extra_opts="--libexecdir=/usr/lib/snapd --enable-nvidia-multiarch --enable-static-libcap --enable-static-libapparmor --enable-static-libseccomp"
;;
*)
- extra_opts="--libexecdir=/usr/lib/snapd --enable-nvidia-ubuntu --enable-static-libcap"
+ extra_opts="--libexecdir=/usr/lib/snapd --enable-nvidia-multiarch --enable-static-libcap"
;;
esac
;;
@@ -39,11 +39,10 @@
extra_opts="--libexecdir=/usr/libexec/snapd --with-snap-mount-dir=/var/lib/snapd/snap --enable-merged-usr --disable-apparmor"
;;
opensuse)
- # NOTE: we need to disable apparmor as the version on OpenSUSE
- # is too old to confine snap-confine and installed snaps
- # themselves. This should be changed once all the kernel
- # patches find their way into the distribution.
- extra_opts="--libexecdir=/usr/lib/snapd --disable-apparmor"
+ extra_opts="--libexecdir=/usr/lib/snapd"
+ ;;
+ solus)
+ extra_opts="--enable-nvidia-biarch"
;;
esac
diff -Nru snapd-2.28.5/cmd/cmd.go snapd-2.29.3/cmd/cmd.go
--- snapd-2.28.5/cmd/cmd.go 2017-10-10 16:16:09.000000000 +0000
+++ snapd-2.29.3/cmd/cmd.go 2017-11-09 11:33:41.000000000 +0000
@@ -176,6 +176,15 @@
// Did we already re-exec?
if strings.HasPrefix(exe, dirs.SnapMountDir) {
+ // Older version of snapd (before 2.28) did use this env
+ // to check if they should re-exec or not. We still need
+ // to unset it because the host snap tool may be old and
+ // using this key. So if e.g. the host has snapd 2.27 and
+ // snapd re-execs to 2.29 then `snap run --shell classic-snap`
+ // will go into an environment where the snapd 2.27 sees
+ // this key and stops re-execing - which is not what we
+ // want. C.f. https://forum.snapcraft.io/t/seccomp-error-calling-snap-from-another-classic-snap-on-core-candidate/2736/7
+ mustUnsetenv("SNAP_DID_REEXEC")
return
}
diff -Nru snapd-2.28.5/cmd/cmd_test.go snapd-2.29.3/cmd/cmd_test.go
--- snapd-2.28.5/cmd/cmd_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/cmd/cmd_test.go 2017-11-09 11:33:41.000000000 +0000
@@ -30,6 +30,7 @@
"github.com/snapcore/snapd/cmd"
"github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/testutil"
)
@@ -38,6 +39,7 @@
type cmdSuite struct {
restoreExec func()
+ restoreLogger func()
execCalled int
lastExecArgv0 string
lastExecArgv []string
@@ -51,6 +53,7 @@
func (s *cmdSuite) SetUpTest(c *C) {
s.restoreExec = cmd.MockSyscallExec(s.syscallExec)
+ _, s.restoreLogger = logger.MockLogger()
s.execCalled = 0
s.lastExecArgv0 = ""
s.lastExecArgv = nil
@@ -64,6 +67,7 @@
func (s *cmdSuite) TearDownTest(c *C) {
s.restoreExec()
+ s.restoreLogger()
}
func (s *cmdSuite) syscallExec(argv0 string, argv []string, envv []string) (err error) {
@@ -289,6 +293,7 @@
selfExe := filepath.Join(s.fakeroot, "proc/self/exe")
err := os.Symlink(filepath.Join(s.fakeroot, "/snap/core/42/usr/lib/snapd"), selfExe)
c.Assert(err, IsNil)
+ cmd.MockSelfExe(selfExe)
cmd.ExecInCoreSnap()
c.Check(s.execCalled, Equals, 0)
@@ -303,3 +308,17 @@
cmd.ExecInCoreSnap()
c.Check(s.execCalled, Equals, 0)
}
+
+func (s *cmdSuite) TestExecInCoreSnapUnsetsDidReexec(c *C) {
+ os.Setenv("SNAP_DID_REEXEC", "1")
+ defer os.Unsetenv("SNAP_DID_REEXEC")
+
+ selfExe := filepath.Join(s.fakeroot, "proc/self/exe")
+ err := os.Symlink(filepath.Join(s.fakeroot, "/snap/core/42/usr/lib/snapd"), selfExe)
+ c.Assert(err, IsNil)
+ cmd.MockSelfExe(selfExe)
+
+ cmd.ExecInCoreSnap()
+ c.Check(s.execCalled, Equals, 0)
+ c.Check(os.Getenv("SNAP_DID_REEXEC"), Equals, "")
+}
diff -Nru snapd-2.28.5/cmd/configure.ac snapd-2.29.3/cmd/configure.ac
--- snapd-2.28.5/cmd/configure.ac 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/cmd/configure.ac 2017-10-23 06:17:27.000000000 +0000
@@ -114,32 +114,32 @@
# PKG_CHECK_MODULES([LIBCAP], [libcap])
# Enable special support for hosts with proprietary nvidia drivers on Ubuntu.
-AC_ARG_ENABLE([nvidia-ubuntu],
- AS_HELP_STRING([--enable-nvidia-ubuntu], [Support for proprietary nvidia drivers (Ubuntu)]),
+AC_ARG_ENABLE([nvidia-multiarch],
+ AS_HELP_STRING([--enable-nvidia-multiarch], [Support for proprietary nvidia drivers (Ubuntu/Debian)]),
[case "${enableval}" in
- yes) enable_nvidia_ubuntu=yes ;;
- no) enable_nvidia_ubuntu=no ;;
- *) AC_MSG_ERROR([bad value ${enableval} for --enable-nvidia-ubuntu])
- esac], [enable_nvidia_ubuntu=no])
-AM_CONDITIONAL([NVIDIA_UBUNTU], [test "x$enable_nvidia_ubuntu" = "xyes"])
+ yes) enable_nvidia_multiarch=yes ;;
+ no) enable_nvidia_multiarch=no ;;
+ *) AC_MSG_ERROR([bad value ${enableval} for --enable-nvidia-multiarch])
+ esac], [enable_nvidia_multiarch=no])
+AM_CONDITIONAL([NVIDIA_MULTIARCH], [test "x$enable_nvidia_multiarch" = "xyes"])
-AS_IF([test "x$enable_nvidia_ubuntu" = "xyes"], [
- AC_DEFINE([NVIDIA_UBUNTU], [1],
- [Support for proprietary nvidia drivers (Ubuntu)])])
+AS_IF([test "x$enable_nvidia_multiarch" = "xyes"], [
+ AC_DEFINE([NVIDIA_MULTIARCH], [1],
+ [Support for proprietary nvidia drivers (Ubuntu/Debian)])])
# Enable special support for hosts with proprietary nvidia drivers on Arch.
-AC_ARG_ENABLE([nvidia-arch],
- AS_HELP_STRING([--enable-nvidia-arch], [Support for proprietary nvidia drivers (Arch)]),
+AC_ARG_ENABLE([nvidia-biarch],
+ AS_HELP_STRING([--enable-nvidia-biarch], [Support for proprietary nvidia drivers (bi-arch distributions)]),
[case "${enableval}" in
- yes) enable_nvidia_arch=yes ;;
- no) enable_nvidia_arch=no ;;
- *) AC_MSG_ERROR([bad value ${enableval} for --enable-nvidia-arch])
- esac], [enable_nvidia_arch=no])
-AM_CONDITIONAL([NVIDIA_ARCH], [test "x$enable_nvidia_arch" = "xyes"])
+ yes) enable_nvidia_biarch=yes ;;
+ no) enable_nvidia_biarch=no ;;
+ *) AC_MSG_ERROR([bad value ${enableval} for --enable-nvidia-biarch])
+ esac], [enable_nvidia_biarch=no])
+AM_CONDITIONAL([NVIDIA_BIARCH], [test "x$enable_nvidia_biarch" = "xyes"])
-AS_IF([test "x$enable_nvidia_arch" = "xyes"], [
- AC_DEFINE([NVIDIA_ARCH], [1],
- [Support for proprietary nvidia drivers (Arch)])])
+AS_IF([test "x$enable_nvidia_biarch" = "xyes"], [
+ AC_DEFINE([NVIDIA_BIARCH], [1],
+ [Support for proprietary nvidia drivers (bi-arch distributions)])])
AC_ARG_ENABLE([merged-usr],
AS_HELP_STRING([--enable-merged-usr], [Enable support for merged /usr directory]),
diff -Nru snapd-2.28.5/cmd/decode-mount-opts/decode-mount-opts.c snapd-2.29.3/cmd/decode-mount-opts/decode-mount-opts.c
--- snapd-2.28.5/cmd/decode-mount-opts/decode-mount-opts.c 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/cmd/decode-mount-opts/decode-mount-opts.c 2017-10-23 06:17:27.000000000 +0000
@@ -32,7 +32,7 @@
fprintf(stderr, "cannot parse given argument as a number\n");
return 1;
}
- char buf[1000];
+ char buf[1000] = {0};
printf("%#lx is %s\n", mountflags, sc_mount_opt2str(buf, sizeof buf, mountflags));
return 0;
}
diff -Nru snapd-2.28.5/cmd/libsnap-confine-private/cgroup-freezer-support.c snapd-2.29.3/cmd/libsnap-confine-private/cgroup-freezer-support.c
--- snapd-2.28.5/cmd/libsnap-confine-private/cgroup-freezer-support.c 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/cmd/libsnap-confine-private/cgroup-freezer-support.c 2017-10-23 06:17:27.000000000 +0000
@@ -0,0 +1,67 @@
+// For AT_EMPTY_PATH and O_PATH
+#define _GNU_SOURCE
+
+#include "cgroup-freezer-support.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "cleanup-funcs.h"
+#include "string-utils.h"
+#include "utils.h"
+
+static const char *freezer_cgroup_dir = "/sys/fs/cgroup/freezer";
+
+void sc_cgroup_freezer_join(const char *snap_name, pid_t pid)
+{
+ // Format the name of the cgroup hierarchy.
+ char buf[PATH_MAX] = { 0 };
+ sc_must_snprintf(buf, sizeof buf, "snap.%s", snap_name);
+
+ // Open the freezer cgroup directory.
+ int cgroup_fd SC_CLEANUP(sc_cleanup_close) = -1;
+ cgroup_fd = open(freezer_cgroup_dir,
+ O_PATH | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
+ if (cgroup_fd < 0) {
+ die("cannot open freezer cgroup (%s)", freezer_cgroup_dir);
+ }
+ // Create the freezer hierarchy for the given snap.
+ if (mkdirat(cgroup_fd, buf, 0755) < 0 && errno != EEXIST) {
+ die("cannot create freezer cgroup hierarchy for snap %s",
+ snap_name);
+ }
+ // Open the hierarchy directory for the given snap.
+ int hierarchy_fd SC_CLEANUP(sc_cleanup_close) = -1;
+ hierarchy_fd = openat(cgroup_fd, buf,
+ O_PATH | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
+ if (hierarchy_fd < 0) {
+ die("cannot open freezer cgroup hierarchy for snap %s",
+ snap_name);
+ }
+ // Since we may be running from a setuid but not setgid executable, ensure
+ // that the group and owner of the hierarchy directory is root.root.
+ if (fchownat(hierarchy_fd, "", 0, 0, AT_EMPTY_PATH) < 0) {
+ die("cannot change owner of freezer cgroup hierarchy for snap %s to root.root", snap_name);
+ }
+ // Open the tasks file.
+ int tasks_fd SC_CLEANUP(sc_cleanup_close) = -1;
+ tasks_fd = openat(hierarchy_fd, "tasks",
+ O_WRONLY | O_NOFOLLOW | O_CLOEXEC);
+ if (tasks_fd < 0) {
+ die("cannot open tasks file for freezer cgroup hierarchy for snap %s", snap_name);
+ }
+ // Write the process (task) number to the tasks file. Linux task IDs are
+ // limited to 2^29 so a long int is enough to represent it.
+ // See include/linux/threads.h in the kernel source tree for details.
+ int n = sc_must_snprintf(buf, sizeof buf, "%ld", (long)pid);
+ if (write(tasks_fd, buf, n) < n) {
+ die("cannot move process %ld to freezer cgroup hierarchy for snap %s", (long)pid, snap_name);
+ }
+ debug("moved process %ld to freezer cgroup hierarchy for snap %s",
+ (long)pid, snap_name);
+}
diff -Nru snapd-2.28.5/cmd/libsnap-confine-private/cgroup-freezer-support.h snapd-2.29.3/cmd/libsnap-confine-private/cgroup-freezer-support.h
--- snapd-2.28.5/cmd/libsnap-confine-private/cgroup-freezer-support.h 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/cmd/libsnap-confine-private/cgroup-freezer-support.h 2017-10-23 06:17:27.000000000 +0000
@@ -0,0 +1,26 @@
+#ifndef SC_CGROUP_FREEZER_SUPPORT_H
+#define SC_CGROUP_FREEZER_SUPPORT_H
+
+#include
+#include "error.h"
+
+/**
+ * Join the freezer cgroup for the given snap.
+ *
+ * This function adds the specified task to the freezer cgroup specific to the
+ * given snap. The name of the cgroup is "snap.$snap_name".
+ *
+ * Interestingly we don't need to actually freeze the processes. The group
+ * allows us to track processes belonging to a given snap. This makes the
+ * measurement "are any processes of this snap still alive" very simple.
+ *
+ * The "tasks" file belonging to the cgroup contains the set of all the
+ * processes that originate from the given snap. Examining that file one can
+ * reliably determine if the set is empty or not.
+ *
+ * For more details please review:
+ * https://www.kernel.org/doc/Documentation/cgroup-v1/freezer-subsystem.txt
+**/
+void sc_cgroup_freezer_join(const char *snap_name, pid_t pid);
+
+#endif
diff -Nru snapd-2.28.5/cmd/libsnap-confine-private/classic.c snapd-2.29.3/cmd/libsnap-confine-private/classic.c
--- snapd-2.28.5/cmd/libsnap-confine-private/classic.c 2017-10-13 20:09:56.000000000 +0000
+++ snapd-2.29.3/cmd/libsnap-confine-private/classic.c 2017-10-23 06:17:27.000000000 +0000
@@ -1,15 +1,25 @@
#include "config.h"
#include "classic.h"
+#include "../libsnap-confine-private/cleanup-funcs.h"
+#include
+#include
#include
+char *os_release = "/etc/os-release";
+
bool is_running_on_classic_distribution()
{
- // NOTE: keep this list sorted please
- return false
- || access("/var/lib/dpkg/status", F_OK) == 0
- || access("/var/lib/pacman", F_OK) == 0
- || access("/var/lib/portage", F_OK) == 0
- || access("/var/lib/rpm", F_OK) == 0
- || access("/sbin/procd", F_OK) == 0;
+ FILE *f SC_CLEANUP(sc_cleanup_file) = fopen(os_release, "r");
+ if (f == NULL) {
+ return true;
+ }
+
+ char buf[255] = { 0 };
+ while (fgets(buf, sizeof buf, f) != NULL) {
+ if (strcmp(buf, "ID=ubuntu-core\n") == 0) {
+ return false;
+ }
+ }
+ return true;
}
diff -Nru snapd-2.28.5/cmd/libsnap-confine-private/classic-test.c snapd-2.29.3/cmd/libsnap-confine-private/classic-test.c
--- snapd-2.28.5/cmd/libsnap-confine-private/classic-test.c 2017-10-13 20:09:56.000000000 +0000
+++ snapd-2.29.3/cmd/libsnap-confine-private/classic-test.c 2017-10-23 06:17:27.000000000 +0000
@@ -20,4 +20,52 @@
#include
-// TODO: write some tests
+const char *os_release_classic = ""
+ "NAME=\"Ubuntu\"\n"
+ "VERSION=\"17.04 (Zesty Zapus)\"\n" "ID=ubuntu\n" "ID_LIKE=debian\n";
+
+static void test_is_on_classic()
+{
+ g_file_set_contents("os-release.classic", os_release_classic,
+ strlen(os_release_classic), NULL);
+ os_release = "os-release.classic";
+ g_assert_true(is_running_on_classic_distribution());
+ unlink("os-release.classic");
+}
+
+const char *os_release_core = ""
+ "NAME=\"Ubuntu Core\"\n" "VERSION=\"16\"\n" "ID=ubuntu-core\n";
+
+static void test_is_on_core()
+{
+ g_file_set_contents("os-release.core", os_release_core,
+ strlen(os_release_core), NULL);
+ os_release = "os-release.core";
+ g_assert_false(is_running_on_classic_distribution());
+ unlink("os-release.core");
+}
+
+const char *os_release_classic_with_long_line = ""
+ "NAME=\"Ubuntu\"\n"
+ "VERSION=\"17.04 (Zesty Zapus)\"\n"
+ "ID=ubuntu\n"
+ "ID_LIKE=debian\n"
+ "LONG=line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.line.";
+
+static void test_is_on_classic_with_long_line()
+{
+ g_file_set_contents("os-release.classic-with-long-line",
+ os_release_classic, strlen(os_release_classic),
+ NULL);
+ os_release = "os-release.classic-with-long-line";
+ g_assert_true(is_running_on_classic_distribution());
+ unlink("os-release.classic-with-long-line");
+}
+
+static void __attribute__ ((constructor)) init()
+{
+ g_test_add_func("/classic/on-classic", test_is_on_classic);
+ g_test_add_func("/classic/on-classic-with-long-line",
+ test_is_on_classic_with_long_line);
+ g_test_add_func("/classic/on-core", test_is_on_core);
+}
diff -Nru snapd-2.28.5/cmd/libsnap-confine-private/cleanup-funcs.h snapd-2.29.3/cmd/libsnap-confine-private/cleanup-funcs.h
--- snapd-2.28.5/cmd/libsnap-confine-private/cleanup-funcs.h 2017-10-13 20:09:56.000000000 +0000
+++ snapd-2.29.3/cmd/libsnap-confine-private/cleanup-funcs.h 2017-10-23 06:17:27.000000000 +0000
@@ -27,6 +27,10 @@
#include
#include
+// SC_CLEANUP will run the given cleanup function when the variable next
+// to it goes out of scope.
+#define SC_CLEANUP(n) __attribute__((cleanup(n)))
+
/**
* Free a dynamically allocated string.
*
diff -Nru snapd-2.28.5/cmd/libsnap-confine-private/cleanup-funcs-test.c snapd-2.29.3/cmd/libsnap-confine-private/cleanup-funcs-test.c
--- snapd-2.28.5/cmd/libsnap-confine-private/cleanup-funcs-test.c 2017-10-13 20:09:56.000000000 +0000
+++ snapd-2.29.3/cmd/libsnap-confine-private/cleanup-funcs-test.c 2017-10-23 06:17:27.000000000 +0000
@@ -28,7 +28,7 @@
called = 1;
}
{
- int test __attribute__ ((cleanup(fn)));
+ int test SC_CLEANUP(fn);
test = 0;
test++;
}
diff -Nru snapd-2.28.5/cmd/libsnap-confine-private/locking.c snapd-2.29.3/cmd/libsnap-confine-private/locking.c
--- snapd-2.28.5/cmd/libsnap-confine-private/locking.c 2017-10-13 20:09:56.000000000 +0000
+++ snapd-2.29.3/cmd/libsnap-confine-private/locking.c 2017-10-27 12:23:38.000000000 +0000
@@ -60,7 +60,7 @@
if (sigaction(SIGALRM, &act, NULL) < 0) {
die("cannot install signal handler for SIGALRM");
}
- alarm(3);
+ alarm(6);
debug("sanity timeout initialized and set for three seconds");
}
@@ -92,14 +92,14 @@
die("cannot create lock directory %s", sc_lock_dir);
}
debug("opening lock directory %s", sc_lock_dir);
- int dir_fd __attribute__ ((cleanup(sc_cleanup_close))) = -1;
+ int dir_fd SC_CLEANUP(sc_cleanup_close) = -1;
dir_fd =
open(sc_lock_dir, O_DIRECTORY | O_PATH | O_CLOEXEC | O_NOFOLLOW);
if (dir_fd < 0) {
die("cannot open lock directory");
}
// Construct the name of the lock file.
- char lock_fname[PATH_MAX];
+ char lock_fname[PATH_MAX] = { 0 };
sc_must_snprintf(lock_fname, sizeof lock_fname, "%s/%s.lock",
sc_lock_dir, scope ? : "");
diff -Nru snapd-2.28.5/cmd/libsnap-confine-private/locking-test.c snapd-2.29.3/cmd/libsnap-confine-private/locking-test.c
--- snapd-2.28.5/cmd/libsnap-confine-private/locking-test.c 2017-10-13 20:09:56.000000000 +0000
+++ snapd-2.29.3/cmd/libsnap-confine-private/locking-test.c 2017-10-27 12:23:38.000000000 +0000
@@ -65,12 +65,12 @@
const char *lock_dir = sc_test_use_fake_lock_dir();
int fd = sc_lock("foo");
// Construct the name of the lock file
- char *lock_file __attribute__ ((cleanup(sc_cleanup_string))) = NULL;
+ char *lock_file SC_CLEANUP(sc_cleanup_string) = NULL;
lock_file = g_strdup_printf("%s/foo.lock", lock_dir);
// Open the lock file again to obtain a separate file descriptor.
// According to flock(2) locks are associated with an open file table entry
// so this descriptor will be separate and can compete for the same lock.
- int lock_fd __attribute__ ((cleanup(sc_cleanup_close))) = -1;
+ int lock_fd SC_CLEANUP(sc_cleanup_close) = -1;
lock_fd = open(lock_file, O_RDWR | O_CLOEXEC | O_NOFOLLOW);
g_assert_cmpint(lock_fd, !=, -1);
// The non-blocking lock operation should fail with EWOULDBLOCK as the lock
@@ -91,7 +91,7 @@
if (g_test_subprocess()) {
sc_enable_sanity_timeout();
debug("waiting...");
- usleep(4 * G_USEC_PER_SEC);
+ usleep(7 * G_USEC_PER_SEC);
debug("woke up");
sc_disable_sanity_timeout();
return;
diff -Nru snapd-2.28.5/cmd/libsnap-confine-private/mountinfo.c snapd-2.29.3/cmd/libsnap-confine-private/mountinfo.c
--- snapd-2.28.5/cmd/libsnap-confine-private/mountinfo.c 2017-10-13 20:09:56.000000000 +0000
+++ snapd-2.29.3/cmd/libsnap-confine-private/mountinfo.c 2017-10-23 06:17:27.000000000 +0000
@@ -78,13 +78,13 @@
if (fname == NULL) {
fname = "/proc/self/mountinfo";
}
- FILE *f __attribute__ ((cleanup(sc_cleanup_file))) = NULL;
+ FILE *f SC_CLEANUP(sc_cleanup_file) = NULL;
f = fopen(fname, "rt");
if (f == NULL) {
free(info);
return NULL;
}
- char *line __attribute__ ((cleanup(sc_cleanup_string))) = NULL;
+ char *line SC_CLEANUP(sc_cleanup_string) = NULL;
size_t line_size = 0;
struct sc_mountinfo_entry *entry, *last = NULL;
for (;;) {
diff -Nru snapd-2.28.5/cmd/libsnap-confine-private/mount-opt.c snapd-2.29.3/cmd/libsnap-confine-private/mount-opt.c
--- snapd-2.28.5/cmd/libsnap-confine-private/mount-opt.c 2017-10-13 20:09:56.000000000 +0000
+++ snapd-2.29.3/cmd/libsnap-confine-private/mount-opt.c 2017-10-23 06:17:27.000000000 +0000
@@ -109,7 +109,7 @@
#undef F
// Render any flags that are unaccounted for.
if (flags) {
- char of[128];
+ char of[128] = { 0 };
sc_must_snprintf(of, sizeof of, "%#lx", flags);
sc_string_append(buf, buf_size, of);
}
@@ -197,7 +197,7 @@
}
// If regular option syntax exists then use it.
if (mountflags & ~used_special_flags) {
- char opts_buf[1000];
+ char opts_buf[1000] = { 0 };
sc_mount_opt2str(opts_buf, sizeof opts_buf, mountflags &
~used_special_flags);
sc_string_append(buf, buf_size, " -o ");
@@ -249,7 +249,7 @@
const char *fs_type, unsigned long mountflags,
const void *data)
{
- char buf[10000];
+ char buf[10000] = { 0 };
const char *mount_cmd = NULL;
void ensure_mount_cmd() {
@@ -288,7 +288,7 @@
void sc_do_umount(const char *target, int flags)
{
- char buf[10000];
+ char buf[10000] = { 0 };
const char *umount_cmd = NULL;
void ensure_umount_cmd() {
diff -Nru snapd-2.28.5/cmd/libsnap-confine-private/mount-opt-test.c snapd-2.29.3/cmd/libsnap-confine-private/mount-opt-test.c
--- snapd-2.28.5/cmd/libsnap-confine-private/mount-opt-test.c 2017-10-13 20:09:56.000000000 +0000
+++ snapd-2.29.3/cmd/libsnap-confine-private/mount-opt-test.c 2017-10-23 06:17:27.000000000 +0000
@@ -25,7 +25,7 @@
static void test_sc_mount_opt2str()
{
- char buf[1000];
+ char buf[1000] = { 0 };
g_assert_cmpstr(sc_mount_opt2str(buf, sizeof buf, 0), ==, "");
g_assert_cmpstr(sc_mount_opt2str(buf, sizeof buf, MS_RDONLY), ==, "ro");
g_assert_cmpstr(sc_mount_opt2str(buf, sizeof buf, MS_NOSUID), ==,
@@ -93,7 +93,7 @@
static void test_sc_mount_cmd()
{
- char cmd[10000];
+ char cmd[10000] = { 0 };
// Typical mount
sc_mount_cmd(cmd, sizeof cmd, "/dev/sda3", "/mnt", "ext4", MS_RDONLY,
@@ -146,8 +146,8 @@
g_assert_cmpstr(cmd, ==, "mount --move /from /to");
// Monster (invalid but let's format it)
- char from[PATH_MAX];
- char to[PATH_MAX];
+ char from[PATH_MAX] = { 0 };
+ char to[PATH_MAX] = { 0 };
for (int i = 1; i < PATH_MAX - 1; ++i) {
from[i] = 'a';
to[i] = 'b';
@@ -176,7 +176,7 @@
static void test_sc_umount_cmd()
{
- char cmd[1000];
+ char cmd[1000] = { 0 };
// Typical umount
sc_umount_cmd(cmd, sizeof cmd, "/mnt/foo", 0);
diff -Nru snapd-2.28.5/cmd/libsnap-confine-private/snap.c snapd-2.29.3/cmd/libsnap-confine-private/snap.c
--- snapd-2.28.5/cmd/libsnap-confine-private/snap.c 2017-10-13 20:09:56.000000000 +0000
+++ snapd-2.29.3/cmd/libsnap-confine-private/snap.c 2017-10-23 06:17:27.000000000 +0000
@@ -99,6 +99,8 @@
void sc_snap_name_validate(const char *snap_name, struct sc_error **errorp)
{
+ // NOTE: This function should be synchronized with the two other
+ // implementations: validate_snap_name and snap.ValidateName.
struct sc_error *err = NULL;
// Ensure that name is not NULL
diff -Nru snapd-2.28.5/cmd/libsnap-confine-private/string-utils-test.c snapd-2.29.3/cmd/libsnap-confine-private/string-utils-test.c
--- snapd-2.28.5/cmd/libsnap-confine-private/string-utils-test.c 2017-10-13 20:09:56.000000000 +0000
+++ snapd-2.29.3/cmd/libsnap-confine-private/string-utils-test.c 2017-10-23 06:17:27.000000000 +0000
@@ -55,7 +55,7 @@
static void test_sc_must_snprintf()
{
- char buf[5];
+ char buf[5] = { 0 };
sc_must_snprintf(buf, sizeof buf, "1234");
g_assert_cmpstr(buf, ==, "1234");
}
@@ -140,7 +140,7 @@
static void test_sc_string_append__overflow()
{
if (g_test_subprocess()) {
- char buf[4] = { 0, };
+ char buf[4] = { 0 };
// Try to append a string that's one character too long.
sc_string_append(buf, sizeof buf, "1234");
@@ -829,5 +829,8 @@
test_sc_string_append_char_pair__normal);
g_test_add_func("/string-utils/sc_string_quote__NULL_buf",
test_sc_string_quote_NULL_str);
+ g_test_add_func
+ ("/string-utils/sc_string_append_char_pair__uninitialized_buf",
+ test_sc_string_append_char_pair__uninitialized_buf);
g_test_add_func("/string-utils/sc_string_quote", test_sc_string_quote);
}
diff -Nru snapd-2.28.5/cmd/libsnap-confine-private/utils.c snapd-2.29.3/cmd/libsnap-confine-private/utils.c
--- snapd-2.28.5/cmd/libsnap-confine-private/utils.c 2017-10-13 20:09:56.000000000 +0000
+++ snapd-2.29.3/cmd/libsnap-confine-private/utils.c 2017-10-23 06:17:27.000000000 +0000
@@ -66,20 +66,22 @@
};
/**
- * Convert string to a boolean value.
+ * Convert string to a boolean value, with a default.
*
* The return value is 0 in case of success or -1 when the string cannot be
* converted correctly. In such case errno is set to indicate the problem and
* the value is not written back to the caller-supplied pointer.
+ *
+ * If the text cannot be recognized, the default value is used.
**/
-static int str2bool(const char *text, bool * value)
+static int parse_bool(const char *text, bool * value, bool default_value)
{
if (value == NULL) {
errno = EFAULT;
return -1;
}
if (text == NULL) {
- *value = false;
+ *value = default_value;
return 0;
}
for (int i = 0; i < sizeof sc_bool_names / sizeof *sc_bool_names; ++i) {
@@ -95,15 +97,16 @@
/**
* Get an environment variable and convert it to a boolean.
*
- * Supported values are those of str2bool(), namely "yes", "no" as well as "1"
+ * Supported values are those of parse_bool(), namely "yes", "no" as well as "1"
* and "0". All other values are treated as false and a diagnostic message is
- * printed to stderr.
+ * printed to stderr. If the environment variable is unset, set value to the
+ * default_value as if the environment variable was set to default_value.
**/
-static bool getenv_bool(const char *name)
+static bool getenv_bool(const char *name, bool default_value)
{
const char *str_value = getenv(name);
- bool value;
- if (str2bool(str_value, &value) < 0) {
+ bool value = default_value;
+ if (parse_bool(str_value, &value, default_value) < 0) {
if (errno == EINVAL) {
fprintf(stderr,
"WARNING: unrecognized value of environment variable %s (expected yes/no or 1/0)\n",
@@ -118,7 +121,12 @@
bool sc_is_debug_enabled()
{
- return getenv_bool("SNAP_CONFINE_DEBUG");
+ return getenv_bool("SNAP_CONFINE_DEBUG", false);
+}
+
+bool sc_is_reexec_enabled()
+{
+ return getenv_bool("SNAP_REEXEC", true);
}
void debug(const char *msg, ...)
@@ -155,7 +163,7 @@
}
// We're going to use strtok_r, which needs to modify the path, so we'll
// make a copy of it.
- char *path_copy __attribute__ ((cleanup(sc_cleanup_string))) = NULL;
+ char *path_copy SC_CLEANUP(sc_cleanup_string) = NULL;
path_copy = strdup(path);
if (path_copy == NULL) {
return -1;
@@ -170,7 +178,7 @@
// of mkdir calls, to avoid following symlinks and placing the user data
// directory somewhere we never intended for it to go. The first step is to
// get an initial file descriptor.
- int fd __attribute__ ((cleanup(sc_cleanup_close))) = AT_FDCWD;
+ int fd SC_CLEANUP(sc_cleanup_close) = AT_FDCWD;
if (path_copy[0] == '/') {
fd = open("/", open_flags);
if (fd < 0) {
diff -Nru snapd-2.28.5/cmd/libsnap-confine-private/utils.h snapd-2.29.3/cmd/libsnap-confine-private/utils.h
--- snapd-2.28.5/cmd/libsnap-confine-private/utils.h 2017-10-13 20:09:56.000000000 +0000
+++ snapd-2.29.3/cmd/libsnap-confine-private/utils.h 2017-10-23 06:17:27.000000000 +0000
@@ -37,6 +37,11 @@
**/
bool sc_is_debug_enabled();
+/**
+ * Return true if re-execution is enabled.
+ **/
+bool sc_is_reexec_enabled();
+
void write_string_to_file(const char *filepath, const char *buf);
/**
diff -Nru snapd-2.28.5/cmd/libsnap-confine-private/utils-test.c snapd-2.29.3/cmd/libsnap-confine-private/utils-test.c
--- snapd-2.28.5/cmd/libsnap-confine-private/utils-test.c 2017-10-13 20:09:56.000000000 +0000
+++ snapd-2.29.3/cmd/libsnap-confine-private/utils-test.c 2017-10-23 06:17:27.000000000 +0000
@@ -20,40 +20,53 @@
#include
-static void test_str2bool()
+static void test_parse_bool()
{
int err;
bool value;
- err = str2bool("yes", &value);
+ value = false;
+ err = parse_bool("yes", &value, false);
g_assert_cmpint(err, ==, 0);
g_assert_true(value);
- err = str2bool("1", &value);
+ value = false;
+ err = parse_bool("1", &value, false);
g_assert_cmpint(err, ==, 0);
g_assert_true(value);
- err = str2bool("no", &value);
+ value = true;
+ err = parse_bool("no", &value, false);
g_assert_cmpint(err, ==, 0);
g_assert_false(value);
- err = str2bool("0", &value);
+ value = true;
+ err = parse_bool("0", &value, false);
g_assert_cmpint(err, ==, 0);
g_assert_false(value);
- err = str2bool("", &value);
+ value = true;
+ err = parse_bool("", &value, false);
g_assert_cmpint(err, ==, 0);
g_assert_false(value);
- err = str2bool(NULL, &value);
+ value = true;
+ err = parse_bool(NULL, &value, false);
g_assert_cmpint(err, ==, 0);
g_assert_false(value);
- err = str2bool("flower", &value);
+ value = false;
+ err = parse_bool(NULL, &value, true);
+ g_assert_cmpint(err, ==, 0);
+ g_assert_true(value);
+
+ value = true;
+ err = parse_bool("flower", &value, false);
g_assert_cmpint(err, ==, -1);
g_assert_cmpint(errno, ==, EINVAL);
+ g_assert_true(value);
- err = str2bool("yes", NULL);
+ err = parse_bool("yes", NULL, false);
g_assert_cmpint(err, ==, -1);
g_assert_cmpint(errno, ==, EFAULT);
}
@@ -164,7 +177,7 @@
static void __attribute__ ((constructor)) init()
{
- g_test_add_func("/utils/str2bool", test_str2bool);
+ g_test_add_func("/utils/parse_bool", test_parse_bool);
g_test_add_func("/utils/die", test_die);
g_test_add_func("/utils/die_with_errno", test_die_with_errno);
g_test_add_func("/utils/sc_nonfatal_mkpath/relative",
diff -Nru snapd-2.28.5/cmd/Makefile.am snapd-2.29.3/cmd/Makefile.am
--- snapd-2.28.5/cmd/Makefile.am 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/cmd/Makefile.am 2017-10-23 06:17:27.000000000 +0000
@@ -43,15 +43,19 @@
# 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 snap-update-ns/snap-update-ns
+hack: snap-confine/snap-confine snap-confine/snap-confine.apparmor snap-update-ns/snap-update-ns snap-seccomp/snap-seccomp
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 install -m 644 snap-confine/snap-confine.apparmor $(DESTDIR)/etc/apparmor.d/$(patsubst .%,%,$(subst /,.,$(libexecdir))).snap-confine.real
+ sudo install -d -m 755 $(DESTDIR)/var/lib/snapd/apparmor/snap-confine.d/
sudo apparmor_parser -r snap-confine/snap-confine.apparmor
sudo install -m 755 snap-update-ns/snap-update-ns $(DESTDIR)$(libexecdir)/snap-update-ns
+ sudo install -m 755 snap-seccomp/snap-seccomp $(DESTDIR)$(libexecdir)/snap-seccomp
# 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
+snap-seccomp/snap-seccomp: snap-seccomp/*.go
+ cd snap-seccomp && GOPATH=$(or $(GOPATH),$(realpath $(srcdir)/../../../../..)) go build -i -v
##
## libsnap-confine-private.a
@@ -60,6 +64,8 @@
noinst_LIBRARIES += libsnap-confine-private.a
libsnap_confine_private_a_SOURCES = \
+ libsnap-confine-private/cgroup-freezer-support.c \
+ libsnap-confine-private/cgroup-freezer-support.h \
libsnap-confine-private/classic.c \
libsnap-confine-private/classic.h \
libsnap-confine-private/cleanup-funcs.c \
@@ -157,8 +163,8 @@
libexec_PROGRAMS += snap-confine/snap-confine
if HAVE_RST2MAN
-dist_man_MANS += snap-confine/snap-confine.5
-CLEANFILES += snap-confine/snap-confine.5
+dist_man_MANS += snap-confine/snap-confine.1
+CLEANFILES += snap-confine/snap-confine.1
endif
EXTRA_DIST += snap-confine/snap-confine.rst
EXTRA_DIST += snap-confine/snap-confine.apparmor.in
@@ -277,7 +283,7 @@
endif # WITH_UNIT_TESTS
if HAVE_RST2MAN
-snap-confine/%.5: snap-confine/%.rst
+snap-confine/%.1: snap-confine/%.rst
mkdir -p snap-confine
$(HAVE_RST2MAN) $^ > $@
endif
@@ -295,6 +301,7 @@
install -d -m 755 $(DESTDIR)/etc/apparmor.d/
install -m 644 snap-confine/snap-confine.apparmor $(DESTDIR)/etc/apparmor.d/$(patsubst .%,%,$(subst /,.,$(libexecdir))).snap-confine
endif
+ install -d -m 755 $(DESTDIR)/var/lib/snapd/apparmor/snap-confine.d/
# NOTE: The 'void' directory *has to* be chmod 000
install-data-local::
diff -Nru snapd-2.28.5/cmd/snap/cmd_ack.go snapd-2.29.3/cmd/snap/cmd_ack.go
--- snapd-2.28.5/cmd/snap/cmd_ack.go 2016-12-08 15:14:07.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_ack.go 2017-10-27 12:23:38.000000000 +0000
@@ -50,7 +50,9 @@
addCommand("ack", shortAckHelp, longAckHelp, func() flags.Commander {
return &cmdAck{}
}, nil, []argDesc{{
+ // TRANSLATORS: This needs to be wrapped in <>s.
name: i18n.G(""),
+ // TRANSLATORS: This should probably not start with a lowercase letter.
desc: i18n.G("Assertion file"),
}})
}
diff -Nru snapd-2.28.5/cmd/snap/cmd_alias.go snapd-2.29.3/cmd/snap/cmd_alias.go
--- snapd-2.28.5/cmd/snap/cmd_alias.go 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_alias.go 2017-10-27 12:23:38.000000000 +0000
@@ -33,8 +33,8 @@
type cmdAlias struct {
Positionals struct {
- SnapApp string `required:"yes"`
- Alias string `required:"yes"`
+ SnapApp appName `required:"yes"`
+ Alias string `required:"yes"`
} `positional-args:"true"`
}
@@ -52,6 +52,7 @@
return &cmdAlias{}
}, nil, []argDesc{
{name: ""},
+ // TRANSLATORS: This needs to be wrapped in <>s.
{name: i18n.G("")},
})
}
@@ -61,7 +62,7 @@
return ErrExtraArgs
}
- snapName, appName := snap.SplitSnapApp(x.Positionals.SnapApp)
+ snapName, appName := snap.SplitSnapApp(string(x.Positionals.SnapApp))
alias := x.Positionals.Alias
cli := Client()
diff -Nru snapd-2.28.5/cmd/snap/cmd_auto_import_test.go snapd-2.29.3/cmd/snap/cmd_auto_import_test.go
--- snapd-2.28.5/cmd/snap/cmd_auto_import_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_auto_import_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -85,9 +85,8 @@
restore = snap.MockMountInfoPath(makeMockMountInfo(c, content))
defer restore()
- l, err := logger.New(s.stderr, 0)
- c.Assert(err, IsNil)
- logger.SetLogger(l)
+ logbuf, restore := logger.MockLogger()
+ defer restore()
rest, err := snap.Parser().ParseArgs([]string{"auto-import"})
c.Assert(err, IsNil)
@@ -96,7 +95,7 @@
// matches because we may get a:
// "WARNING: cannot create syslog logger\n"
// in the output
- c.Check(s.Stderr(), Matches, fmt.Sprintf("(?ms).*imported %s\n", fakeAssertsFn))
+ c.Check(logbuf.String(), Matches, fmt.Sprintf("(?ms).*imported %s\n", fakeAssertsFn))
c.Check(n, Equals, total)
}
@@ -187,9 +186,8 @@
restore := release.MockOnClassic(false)
defer restore()
- l, err := logger.New(s.stderr, 0)
- c.Assert(err, IsNil)
- logger.SetLogger(l)
+ logbuf, restore := logger.MockLogger()
+ defer restore()
fakeAssertData := []byte("good-assertion")
@@ -197,7 +195,7 @@
snap.ClientConfig.BaseURL = "can-not-connect-to-this-url"
fakeAssertsFn := filepath.Join(c.MkDir(), "auto-import.assert")
- err = ioutil.WriteFile(fakeAssertsFn, fakeAssertData, 0644)
+ err := ioutil.WriteFile(fakeAssertsFn, fakeAssertData, 0644)
c.Assert(err, IsNil)
mockMountInfoFmt := `
@@ -213,7 +211,7 @@
// matches because we may get a:
// "WARNING: cannot create syslog logger\n"
// in the output
- c.Check(s.Stderr(), Matches, "(?ms).*queuing for later.*\n")
+ c.Check(logbuf.String(), Matches, "(?ms).*queuing for later.*\n")
files, err := ioutil.ReadDir(dirs.SnapAssertsSpoolDir)
c.Assert(err, IsNil)
@@ -260,9 +258,8 @@
err = ioutil.WriteFile(fakeAssertsFn, fakeAssertData, 0644)
c.Assert(err, IsNil)
- l, err := logger.New(s.stderr, 0)
- c.Assert(err, IsNil)
- logger.SetLogger(l)
+ logbuf, restore := logger.MockLogger()
+ defer restore()
rest, err := snap.Parser().ParseArgs([]string{"auto-import"})
c.Assert(err, IsNil)
@@ -271,7 +268,7 @@
// matches because we may get a:
// "WARNING: cannot create syslog logger\n"
// in the output
- c.Check(s.Stderr(), Matches, fmt.Sprintf("(?ms).*imported %s\n", fakeAssertsFn))
+ c.Check(logbuf.String(), Matches, fmt.Sprintf("(?ms).*imported %s\n", fakeAssertsFn))
c.Check(n, Equals, total)
c.Check(osutil.FileExists(fakeAssertsFn), Equals, false)
@@ -281,9 +278,8 @@
restore := release.MockOnClassic(false)
defer restore()
- l, err := logger.New(s.stderr, 0)
- c.Assert(err, IsNil)
- logger.SetLogger(l)
+ _, restoreLogger := logger.MockLogger()
+ defer restoreLogger()
// fake data is bigger than the default assertion limit
fakeAssertData := make([]byte, 641*1024)
@@ -292,7 +288,7 @@
snap.ClientConfig.BaseURL = "can-not-connect-to-this-url"
fakeAssertsFn := filepath.Join(c.MkDir(), "auto-import.assert")
- err = ioutil.WriteFile(fakeAssertsFn, fakeAssertData, 0644)
+ err := ioutil.WriteFile(fakeAssertsFn, fakeAssertData, 0644)
c.Assert(err, IsNil)
mockMountInfoFmt := `
diff -Nru snapd-2.28.5/cmd/snap/cmd_buy.go snapd-2.29.3/cmd/snap/cmd_buy.go
--- snapd-2.28.5/cmd/snap/cmd_buy.go 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_buy.go 2017-10-27 12:23:38.000000000 +0000
@@ -46,6 +46,7 @@
return &cmdBuy{}
}, map[string]string{}, []argDesc{{
name: "",
+ // TRANSLATORS: This should probably not start with a lowercase letter.
desc: i18n.G("Snap name"),
}})
}
diff -Nru snapd-2.28.5/cmd/snap/cmd_connect.go snapd-2.29.3/cmd/snap/cmd_connect.go
--- snapd-2.28.5/cmd/snap/cmd_connect.go 2017-08-18 13:48:10.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_connect.go 2017-10-27 12:23:38.000000000 +0000
@@ -57,7 +57,9 @@
addCommand("connect", shortConnectHelp, longConnectHelp, func() flags.Commander {
return &cmdConnect{}
}, nil, []argDesc{
+ // TRANSLATORS: This needs to be wrapped in <>s.
{name: i18n.G(":")},
+ // TRANSLATORS: This needs to be wrapped in <>s.
{name: i18n.G(":")},
})
}
diff -Nru snapd-2.28.5/cmd/snap/cmd_create_key.go snapd-2.29.3/cmd/snap/cmd_create_key.go
--- snapd-2.28.5/cmd/snap/cmd_create_key.go 2016-09-15 18:55:10.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_create_key.go 2017-10-27 12:23:38.000000000 +0000
@@ -43,7 +43,9 @@
func() flags.Commander {
return &cmdCreateKey{}
}, nil, []argDesc{{
+ // TRANSLATORS: This needs to be wrapped in <>s.
name: i18n.G(""),
+ // TRANSLATORS: This should probably not start with a lowercase letter.
desc: i18n.G("Name of key to create; defaults to 'default'"),
}})
cmd.hidden = true
diff -Nru snapd-2.28.5/cmd/snap/cmd_create_user.go snapd-2.29.3/cmd/snap/cmd_create_user.go
--- snapd-2.28.5/cmd/snap/cmd_create_user.go 2016-10-27 13:22:15.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_create_user.go 2017-10-27 12:23:38.000000000 +0000
@@ -56,9 +56,9 @@
"known": i18n.G("Use known assertions for user creation"),
"force-managed": i18n.G("Force adding the user, even if the device is already managed"),
}, []argDesc{{
- // TRANSLATORS: noun
+ // TRANSLATORS: This is a noun, and it needs to be wrapped in <>s.
name: i18n.G(""),
- // TRANSLATORS: note users on login.ubuntu.com can have multiple email addresses
+ // TRANSLATORS: This should probably not start with a lowercase letter. Also, note users on login.ubuntu.com can have multiple email addresses.
desc: i18n.G("An email of a user on login.ubuntu.com"),
}})
cmd.hidden = true
diff -Nru snapd-2.28.5/cmd/snap/cmd_delete_key.go snapd-2.29.3/cmd/snap/cmd_delete_key.go
--- snapd-2.28.5/cmd/snap/cmd_delete_key.go 2016-12-08 15:14:07.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_delete_key.go 2017-10-27 12:23:38.000000000 +0000
@@ -39,7 +39,9 @@
func() flags.Commander {
return &cmdDeleteKey{}
}, nil, []argDesc{{
+ // TRANSLATORS: This needs to be wrapped in <>s.
name: i18n.G(""),
+ // TRANSLATORS: This should probably not start with a lowercase letter.
desc: i18n.G("Name of key to delete"),
}})
cmd.hidden = true
diff -Nru snapd-2.28.5/cmd/snap/cmd_disconnect.go snapd-2.29.3/cmd/snap/cmd_disconnect.go
--- snapd-2.28.5/cmd/snap/cmd_disconnect.go 2017-08-18 13:48:10.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_disconnect.go 2017-10-27 12:23:38.000000000 +0000
@@ -53,7 +53,9 @@
addCommand("disconnect", shortDisconnectHelp, longDisconnectHelp, func() flags.Commander {
return &cmdDisconnect{}
}, nil, []argDesc{
+ // TRANSLATORS: This needs to be wrapped in <>s.
{name: i18n.G(":")},
+ // TRANSLATORS: This needs to be wrapped in <>s.
{name: i18n.G(":")},
})
}
diff -Nru snapd-2.28.5/cmd/snap/cmd_download.go snapd-2.29.3/cmd/snap/cmd_download.go
--- snapd-2.28.5/cmd/snap/cmd_download.go 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_download.go 2017-10-27 12:23:38.000000000 +0000
@@ -56,6 +56,7 @@
"revision": i18n.G("Download the given revision of a snap, to which you must have developer access"),
}), []argDesc{{
name: "",
+ // TRANSLATORS: This should probably not start with a lowercase letter.
desc: i18n.G("Snap name"),
}})
}
diff -Nru snapd-2.28.5/cmd/snap/cmd_export_key.go snapd-2.29.3/cmd/snap/cmd_export_key.go
--- snapd-2.28.5/cmd/snap/cmd_export_key.go 2016-12-08 15:14:07.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_export_key.go 2017-10-27 12:23:38.000000000 +0000
@@ -45,7 +45,9 @@
}, map[string]string{
"account": i18n.G("Format public key material as a request for an account-key for this account-id"),
}, []argDesc{{
+ // TRANSLATORS: This needs to be wrapped in <>s.
name: i18n.G(""),
+ // TRANSLATORS: This should probably not start with a lowercase letter.
desc: i18n.G("Name of key to export"),
}})
cmd.hidden = true
diff -Nru snapd-2.28.5/cmd/snap/cmd_find.go snapd-2.29.3/cmd/snap/cmd_find.go
--- snapd-2.28.5/cmd/snap/cmd_find.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_find.go 2017-10-27 12:23:38.000000000 +0000
@@ -27,6 +27,7 @@
"github.com/jessevdk/go-flags"
"github.com/snapcore/snapd/client"
+ "github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/i18n"
)
@@ -68,6 +69,10 @@
type SectionName string
func (s SectionName) Complete(match string) []flags.Completion {
+ if ret, err := completeFromSortedFile(dirs.SnapSectionsFile, match); err == nil {
+ return ret
+ }
+
cli := Client()
sections, err := cli.Sections()
if err != nil {
@@ -96,7 +101,10 @@
}, map[string]string{
"private": i18n.G("Search private snaps"),
"section": i18n.G("Restrict the search to a given section"),
- }, []argDesc{{name: i18n.G("")}}).alias = "search"
+ }, []argDesc{{
+ // TRANSLATORS: This needs to be wrapped in <>s.
+ name: i18n.G(""),
+ }}).alias = "search"
}
func (x *cmdFind) Execute(args []string) error {
diff -Nru snapd-2.28.5/cmd/snap/cmd_get.go snapd-2.29.3/cmd/snap/cmd_get.go
--- snapd-2.28.5/cmd/snap/cmd_get.go 2016-12-08 15:14:07.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_get.go 2017-10-27 12:23:38.000000000 +0000
@@ -22,11 +22,14 @@
import (
"encoding/json"
"fmt"
+ "os"
+ "sort"
"strings"
"github.com/jessevdk/go-flags"
"github.com/snapcore/snapd/i18n"
+ "golang.org/x/crypto/ssh/terminal"
)
var shortGetHelp = i18n.G("Prints configuration options")
@@ -52,58 +55,163 @@
type cmdGet struct {
Positional struct {
- Snap installedSnapName
+ Snap installedSnapName `required:"yes"`
Keys []string
- } `positional-args:"yes" required:"yes"`
+ } `positional-args:"yes"`
Typed bool `short:"t"`
Document bool `short:"d"`
+ List bool `short:"l"`
}
func init() {
addCommand("get", shortGetHelp, longGetHelp, func() flags.Commander { return &cmdGet{} },
map[string]string{
"d": i18n.G("Always return document, even with single key"),
+ "l": i18n.G("Always return list, even with single key"),
"t": i18n.G("Strict typing with nulls and quoted strings"),
}, []argDesc{
{
name: "",
+ // TRANSLATORS: This should probably not start with a lowercase letter.
desc: i18n.G("The snap whose conf is being requested"),
},
{
+ // TRANSLATORS: This needs to be wrapped in <>s.
name: i18n.G(""),
+ // TRANSLATORS: This should probably not start with a lowercase letter.
desc: i18n.G("Key of interest within the configuration"),
},
})
}
-func (x *cmdGet) Execute(args []string) error {
- if len(args) > 0 {
- // TRANSLATORS: the %s is the list of extra arguments
- return fmt.Errorf(i18n.G("too many arguments: %s"), strings.Join(args, " "))
+type ConfigValue struct {
+ Path string
+ Value interface{}
+}
+
+type byConfigPath []ConfigValue
+
+func (s byConfigPath) Len() int { return len(s) }
+func (s byConfigPath) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+func (s byConfigPath) Less(i, j int) bool {
+ other := s[j].Path
+ for k, c := range s[i].Path {
+ if len(other) <= k {
+ return false
+ }
+
+ switch {
+ case c == rune(other[k]):
+ continue
+ case c == '.':
+ return true
+ case other[k] == '.' || c > rune(other[k]):
+ return false
+ }
+ return true
}
+ return true
+}
- if x.Document && x.Typed {
- return fmt.Errorf("cannot use -d and -t together")
+func sortByPath(config []ConfigValue) {
+ sort.Sort(byConfigPath(config))
+}
+
+func flattenConfig(cfg map[string]interface{}, root bool) (values []ConfigValue) {
+ const docstr = "{...}"
+ for k, v := range cfg {
+ if input, ok := v.(map[string]interface{}); ok {
+ if root {
+ values = append(values, ConfigValue{k, docstr})
+ } else {
+ for kk, vv := range input {
+ p := k + "." + kk
+ if _, ok := vv.(map[string]interface{}); ok {
+ values = append(values, ConfigValue{p, docstr})
+ } else {
+ values = append(values, ConfigValue{p, vv})
+ }
+ }
+ }
+ } else {
+ values = append(values, ConfigValue{k, v})
+ }
}
+ sortByPath(values)
+ return values
+}
- snapName := string(x.Positional.Snap)
- confKeys := x.Positional.Keys
+func rootRequested(confKeys []string) bool {
+ return len(confKeys) == 0
+}
- cli := Client()
- conf, err := cli.Conf(snapName, confKeys)
+// outputJson will be used when the user requested "document" output via
+// the "-d" commandline switch.
+func (c *cmdGet) outputJson(conf interface{}) error {
+ bytes, err := json.MarshalIndent(conf, "", "\t")
if err != nil {
return err
}
+ fmt.Fprintln(Stdout, string(bytes))
+ return nil
+}
+
+// outputList will be used when the user requested list output via the
+// "-l" commandline switch.
+func (x *cmdGet) outputList(conf map[string]interface{}) error {
+ if rootRequested(x.Positional.Keys) && len(conf) == 0 {
+ return fmt.Errorf("snap %q has no configuration", x.Positional.Snap)
+ }
+
+ w := tabWriter()
+ defer w.Flush()
+
+ fmt.Fprintf(w, "Key\tValue\n")
+ values := flattenConfig(conf, rootRequested(x.Positional.Keys))
+ for _, v := range values {
+ fmt.Fprintf(w, "%s\t%v\n", v.Path, v.Value)
+ }
+ return nil
+}
+
+var isTerminal = func() bool {
+ return terminal.IsTerminal(int(os.Stdin.Fd()))
+}
+
+// outputDefault will be used when no commandline switch to override the
+// output where used. The output follows the following rules:
+// - a single key with a string value is printed directly
+// - multiple keys are printed as a list to the terminal (if there is one)
+// or as json if there is no terminal
+// - the option "typed" is honored
+func (x *cmdGet) outputDefault(conf map[string]interface{}, snapName string, confKeys []string) error {
+ if rootRequested(confKeys) && len(conf) == 0 {
+ return fmt.Errorf("snap %q has no configuration", snapName)
+ }
+
var confToPrint interface{} = conf
- if !x.Document && len(confKeys) == 1 {
- confToPrint = conf[confKeys[0]]
+
+ if len(confKeys) == 1 {
+ // if single key was requested, then just output the
+ // value unless it's a map, in which case it will be
+ // printed as a list below.
+ if _, ok := conf[confKeys[0]].(map[string]interface{}); !ok {
+ confToPrint = conf[confKeys[0]]
+ }
}
- if x.Typed && confToPrint == nil {
- fmt.Fprintln(Stdout, "null")
- return nil
+ // conf looks like a map
+ if cfg, ok := confToPrint.(map[string]interface{}); ok {
+ if isTerminal() {
+ return x.outputList(cfg)
+ }
+
+ // TODO: remove this conditional and the warning below
+ // after a transition period.
+ fmt.Fprintf(Stderr, i18n.G(`WARNING: The output of "snap get" will become a list with columns - use -d or -l to force the output format.\n`))
+ return x.outputJson(confToPrint)
}
if s, ok := confToPrint.(string); ok && !x.Typed {
@@ -111,14 +219,44 @@
return nil
}
- var bytes []byte
- if confToPrint != nil {
- bytes, err = json.MarshalIndent(confToPrint, "", "\t")
- if err != nil {
- return err
- }
+ if confToPrint != nil || x.Typed {
+ return x.outputJson(confToPrint)
}
- fmt.Fprintln(Stdout, string(bytes))
+ fmt.Fprintln(Stdout, "")
return nil
+
+}
+
+func (x *cmdGet) Execute(args []string) error {
+ if len(args) > 0 {
+ // TRANSLATORS: the %s is the list of extra arguments
+ return fmt.Errorf(i18n.G("too many arguments: %s"), strings.Join(args, " "))
+ }
+
+ if x.Document && x.Typed {
+ return fmt.Errorf("cannot use -d and -t together")
+ }
+
+ if x.Document && x.List {
+ return fmt.Errorf("cannot use -d and -l together")
+ }
+
+ snapName := string(x.Positional.Snap)
+ confKeys := x.Positional.Keys
+
+ cli := Client()
+ conf, err := cli.Conf(snapName, confKeys)
+ if err != nil {
+ return err
+ }
+
+ switch {
+ case x.Document:
+ return x.outputJson(conf)
+ case x.List:
+ return x.outputList(conf)
+ default:
+ return x.outputDefault(conf, snapName, confKeys)
+ }
}
diff -Nru snapd-2.28.5/cmd/snap/cmd_get_test.go snapd-2.29.3/cmd/snap/cmd_get_test.go
--- snapd-2.28.5/cmd/snap/cmd_get_test.go 2016-10-27 13:22:15.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_get_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -29,9 +29,12 @@
snapset "github.com/snapcore/snapd/cmd/snap"
)
-var getTests = []struct {
- args, stdout, error string
-}{{
+type getCmdArgs struct {
+ args, stdout, stderr, error string
+ isTerminal bool
+}
+
+var getTests = []getCmdArgs{{
args: "get snap-name --foo",
error: ".*unknown flag.*foo.*",
}, {
@@ -56,29 +59,128 @@
args: "get -d snapname test-key1",
stdout: "{\n\t\"test-key1\": \"test-value1\"\n}\n",
}, {
- args: "get snapname test-key1 test-key2",
+ args: "get -l snapname test-key1",
+ stdout: "Key Value\ntest-key1 test-value1\n",
+}, {
+ args: "get snapname -l test-key1 test-key2",
+ stdout: "Key Value\ntest-key1 test-value1\ntest-key2 2\n",
+}, {
+ args: "get snapname document",
+ stderr: `WARNING: The output of "snap get" will become a list with columns - use -d or -l to force the output format.\n`,
+ stdout: "{\n\t\"document\": {\n\t\t\"key1\": \"value1\",\n\t\t\"key2\": \"value2\"\n\t}\n}\n",
+}, {
+ isTerminal: true,
+ args: "get snapname document",
+ stdout: "Key Value\ndocument.key1 value1\ndocument.key2 value2\n",
+}, {
+ args: "get snapname -d test-key1 test-key2",
stdout: "{\n\t\"test-key1\": \"test-value1\",\n\t\"test-key2\": 2\n}\n",
-}}
-
-func (s *SnapSuite) TestSnapGetTests(c *C) {
- s.mockGetConfigServer(c)
+}, {
+ args: "get snapname -l document",
+ stdout: "Key Value\ndocument.key1 value1\ndocument.key2 value2\n",
+}, {
+ args: "get -d snapname document",
+ stdout: "{\n\t\"document\": {\n\t\t\"key1\": \"value1\",\n\t\t\"key2\": \"value2\"\n\t}\n}\n",
+}, {
+ args: "get -l snapname",
+ stdout: "Key Value\nbar 100\nfoo {...}\n",
+}, {
+ args: "get snapname -l test-key3 test-key4",
+ stdout: "Key Value\ntest-key3.a 1\ntest-key3.b 2\ntest-key3-a 9\ntest-key4.a 3\ntest-key4.b 4\n",
+}, {
+ args: "get -d snapname",
+ stdout: "{\n\t\"bar\": 100,\n\t\"foo\": {\n\t\t\"key1\": \"value1\",\n\t\t\"key2\": \"value2\"\n\t}\n}\n",
+}, {
+ isTerminal: true,
+ args: "get snapname test-key1 test-key2",
+ stdout: "Key Value\ntest-key1 test-value1\ntest-key2 2\n",
+}, {
+ isTerminal: false,
+ args: "get snapname test-key1 test-key2",
+ stdout: "{\n\t\"test-key1\": \"test-value1\",\n\t\"test-key2\": 2\n}\n",
+ stderr: `WARNING: The output of "snap get" will become a list with columns - use -d or -l to force the output format.\n`,
+},
+}
- for _, test := range getTests {
+func (s *SnapSuite) runTests(cmds []getCmdArgs, c *C) {
+ for _, test := range cmds {
s.stdout.Truncate(0)
s.stderr.Truncate(0)
c.Logf("Test: %s", test.args)
+ restore := snapset.MockIsTerminal(test.isTerminal)
+ defer restore()
+
_, err := snapset.Parser().ParseArgs(strings.Fields(test.args))
if test.error != "" {
c.Check(err, ErrorMatches, test.error)
} else {
c.Check(err, IsNil)
+ c.Check(s.Stderr(), Equals, test.stderr)
c.Check(s.Stdout(), Equals, test.stdout)
}
}
}
+func (s *SnapSuite) TestSnapGetTests(c *C) {
+ s.mockGetConfigServer(c)
+ s.runTests(getTests, c)
+}
+
+var getNoConfigTests = []getCmdArgs{{
+ args: "get -l snapname",
+ error: `snap "snapname" has no configuration`,
+}, {
+ args: "get snapname",
+ error: `snap "snapname" has no configuration`,
+}, {
+ args: "get -d snapname",
+ stdout: "{}\n",
+}}
+
+func (s *SnapSuite) TestSnapGetNoConfiguration(c *C) {
+ s.mockGetEmptyConfigServer(c)
+ s.runTests(getNoConfigTests, c)
+}
+
+func (s *SnapSuite) TestSortByPath(c *C) {
+ values := []snapset.ConfigValue{
+ {Path: "test-key3.b"},
+ {Path: "a"},
+ {Path: "test-key3.a"},
+ {Path: "a.b.c"},
+ {Path: "test-key4.a"},
+ {Path: "test-key4.b"},
+ {Path: "a-b"},
+ {Path: "zzz"},
+ {Path: "aa"},
+ {Path: "test-key3-a"},
+ {Path: "a.b"},
+ }
+ snapset.SortByPath(values)
+
+ expected := []string{
+ "a",
+ "a.b",
+ "a.b.c",
+ "a-b",
+ "aa",
+ "test-key3.a",
+ "test-key3.b",
+ "test-key3-a",
+ "test-key4.a",
+ "test-key4.b",
+ "zzz",
+ }
+
+ c.Assert(values, HasLen, len(expected))
+
+ for i, e := range expected {
+ c.Assert(values[i].Path, Equals, e)
+ }
+}
+
func (s *SnapSuite) mockGetConfigServer(c *C) {
s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v2/snaps/snapname/conf" {
@@ -96,10 +198,29 @@
fmt.Fprintln(w, `{"type":"sync", "status-code": 200, "result": {"test-key2":2}}`)
case "test-key1,test-key2":
fmt.Fprintln(w, `{"type":"sync", "status-code": 200, "result": {"test-key1":"test-value1","test-key2":2}}`)
+ case "test-key3,test-key4":
+ fmt.Fprintln(w, `{"type":"sync", "status-code": 200, "result": {"test-key3":{"a":1,"b":2},"test-key3-a":9,"test-key4":{"a":3,"b":4}}}`)
case "missing-key":
fmt.Fprintln(w, `{"type":"sync", "status-code": 200, "result": {}}`)
+ case "document":
+ fmt.Fprintln(w, `{"type":"sync", "status-code": 200, "result": {"document":{"key1":"value1","key2":"value2"}}}`)
+ case "":
+ fmt.Fprintln(w, `{"type":"sync", "status-code": 200, "result": {"foo":{"key1":"value1","key2":"value2"},"bar":100}}`)
default:
c.Errorf("unexpected keys %q", query.Get("keys"))
}
})
}
+
+func (s *SnapSuite) mockGetEmptyConfigServer(c *C) {
+ s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path != "/v2/snaps/snapname/conf" {
+ c.Errorf("unexpected path %q", r.URL.Path)
+ return
+ }
+
+ c.Check(r.Method, Equals, "GET")
+
+ fmt.Fprintln(w, `{"type":"sync", "status-code": 200, "result": {}}`)
+ })
+}
diff -Nru snapd-2.28.5/cmd/snap/cmd_interface.go snapd-2.29.3/cmd/snap/cmd_interface.go
--- snapd-2.28.5/cmd/snap/cmd_interface.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_interface.go 2017-10-27 12:23:38.000000000 +0000
@@ -54,7 +54,9 @@
"attrs": i18n.G("Show interface attributes"),
"all": i18n.G("Include unused interfaces"),
}, []argDesc{{
+ // TRANSLATORS: This needs to be wrapped in <>s.
name: i18n.G(""),
+ // TRANSLATORS: This should probably not start with a lowercase letter.
desc: i18n.G("Show details of a specific interface"),
}})
}
diff -Nru snapd-2.28.5/cmd/snap/cmd_interfaces.go snapd-2.29.3/cmd/snap/cmd_interfaces.go
--- snapd-2.28.5/cmd/snap/cmd_interfaces.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_interfaces.go 2017-10-27 12:23:38.000000000 +0000
@@ -59,7 +59,9 @@
}, map[string]string{
"i": i18n.G("Constrain listing to specific interfaces"),
}, []argDesc{{
+ // TRANSLATORS: This needs to be wrapped in <>s.
name: i18n.G(":"),
+ // TRANSLATORS: This should probably not start with a lowercase letter.
desc: i18n.G("Constrain listing to a specific snap or snap:name"),
}})
}
diff -Nru snapd-2.28.5/cmd/snap/cmd_known.go snapd-2.29.3/cmd/snap/cmd_known.go
--- snapd-2.28.5/cmd/snap/cmd_known.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_known.go 2017-10-27 12:23:38.000000000 +0000
@@ -53,10 +53,14 @@
return &cmdKnown{}
}, nil, []argDesc{
{
+ // TRANSLATORS: This needs to be wrapped in <>s.
name: i18n.G(""),
+ // TRANSLATORS: This should probably not start with a lowercase letter.
desc: i18n.G("Assertion type name"),
}, {
+ // TRANSLATORS: This needs to be wrapped in <>s.
name: i18n.G(""),
+ // TRANSLATORS: This should probably not start with a lowercase letter.
desc: i18n.G("Constrain listing to those matching header=value"),
},
})
@@ -76,13 +80,9 @@
if at == nil {
return nil, fmt.Errorf("cannot find assertion type %q", typeName)
}
- primaryKeys := make([]string, len(at.PrimaryKey))
- for i, k := range at.PrimaryKey {
- pk, ok := headers[k]
- if !ok {
- return nil, fmt.Errorf("missing primary header %q to query remote assertion", k)
- }
- primaryKeys[i] = pk
+ primaryKeys, err := asserts.PrimaryKeyFromHeaders(at, headers)
+ if err != nil {
+ return nil, fmt.Errorf("cannot query remote assertion: %v", err)
}
sto := storeNew(nil, authContext)
diff -Nru snapd-2.28.5/cmd/snap/cmd_known_test.go snapd-2.29.3/cmd/snap/cmd_known_test.go
--- snapd-2.28.5/cmd/snap/cmd_known_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_known_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -57,15 +57,15 @@
if cfg == nil {
cfg = store.DefaultConfig()
}
- serverURL, err := url.Parse(server.URL + "/assertions/")
- c.Assert(err, check.IsNil)
- cfg.AssertionsURI = serverURL
+ serverURL, _ := url.Parse(server.URL)
+ cfg.AssertionsBaseURL = serverURL
return store.New(cfg, auth)
})
defer restorer()
n := 0
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ c.Assert(r.URL.Path, check.Matches, ".*/assertions/.*") // sanity check request
switch n {
case 0:
c.Check(r.Method, check.Equals, "GET")
@@ -87,7 +87,7 @@
func (s *SnapSuite) TestKnownRemoteMissingPrimaryKey(c *check.C) {
_, 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`)
+ c.Assert(err, check.ErrorMatches, `cannot query remote assertion: must provide primary key: model`)
}
func (s *SnapSuite) TestAssertTypeNameCompletion(c *check.C) {
diff -Nru snapd-2.28.5/cmd/snap/cmd_login.go snapd-2.29.3/cmd/snap/cmd_login.go
--- snapd-2.28.5/cmd/snap/cmd_login.go 2016-12-15 08:17:53.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_login.go 2017-10-27 12:23:38.000000000 +0000
@@ -55,8 +55,9 @@
func() flags.Commander {
return &cmdLogin{}
}, nil, []argDesc{{
- // TRANSLATORS: noun
+ // TRANSLATORS: This is a noun, and it needs to be wrapped in <>s.
name: i18n.G(""),
+ // TRANSLATORS: This should probably not start with a lowercase letter.
desc: i18n.G("The login.ubuntu.com email to login as"),
}})
}
diff -Nru snapd-2.28.5/cmd/snap/cmd_pack.go snapd-2.29.3/cmd/snap/cmd_pack.go
--- snapd-2.28.5/cmd/snap/cmd_pack.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_pack.go 2017-10-23 06:17:27.000000000 +0000
@@ -0,0 +1,66 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2016 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package main
+
+import (
+ "fmt"
+
+ "github.com/jessevdk/go-flags"
+
+ "github.com/snapcore/snapd/i18n"
+ "github.com/snapcore/snapd/snap/pack"
+)
+
+type packCmd struct {
+ Positional struct {
+ SnapDir string `positional-arg-name:""`
+ TargetDir string `positional-arg-name:""`
+ } `positional-args:"yes"`
+}
+
+var shortPackHelp = i18n.G("pack the given target dir as a snap")
+var longPackHelp = i18n.G(`
+The pack command packs the given snap-dir as a snap.`)
+
+func init() {
+ addCommand("pack",
+ shortPackHelp,
+ longPackHelp,
+ func() flags.Commander {
+ return &packCmd{}
+ }, nil, nil)
+}
+
+func (x *packCmd) Execute([]string) error {
+ if x.Positional.SnapDir == "" {
+ x.Positional.SnapDir = "."
+ }
+ if x.Positional.TargetDir == "" {
+ x.Positional.TargetDir = "."
+ }
+
+ snapPath, err := pack.Snap(x.Positional.SnapDir, x.Positional.TargetDir)
+ if err != nil {
+ return fmt.Errorf("cannot pack %q: %v", x.Positional.SnapDir, err)
+
+ }
+ fmt.Fprintf(Stdout, "built: %s\n", snapPath)
+ return nil
+}
diff -Nru snapd-2.28.5/cmd/snap/cmd_prepare_image.go snapd-2.29.3/cmd/snap/cmd_prepare_image.go
--- snapd-2.28.5/cmd/snap/cmd_prepare_image.go 2017-08-18 13:48:10.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_prepare_image.go 2017-10-27 12:23:38.000000000 +0000
@@ -49,10 +49,14 @@
"channel": "The channel to use",
}, []argDesc{
{
+ // TRANSLATORS: This needs to be wrapped in <>s.
name: i18n.G(""),
+ // TRANSLATORS: This should probably not start with a lowercase letter.
desc: i18n.G("The model assertion name"),
}, {
+ // TRANSLATORS: This needs to be wrapped in <>s.
name: i18n.G(""),
+ // TRANSLATORS: This should probably not start with a lowercase letter.
desc: i18n.G("The output directory"),
},
})
diff -Nru snapd-2.28.5/cmd/snap/cmd_repair_repairs.go snapd-2.29.3/cmd/snap/cmd_repair_repairs.go
--- snapd-2.28.5/cmd/snap/cmd_repair_repairs.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_repair_repairs.go 2017-10-23 06:17:27.000000000 +0000
@@ -0,0 +1,93 @@
+// -*- 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/exec"
+ "path/filepath"
+
+ "github.com/jessevdk/go-flags"
+
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/i18n"
+ "github.com/snapcore/snapd/release"
+)
+
+func runSnapRepair(cmdStr string, args []string) error {
+ // do not even try to run snap-repair on classic, some distros
+ // may not even package it
+ if release.OnClassic {
+ return fmt.Errorf(i18n.G("repairs are not available on a classic system"))
+ }
+
+ snapRepairPath := filepath.Join(dirs.GlobalRootDir, dirs.CoreLibExecDir, "snap-repair")
+ args = append([]string{cmdStr}, args...)
+ cmd := exec.Command(snapRepairPath, args...)
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ return cmd.Run()
+}
+
+type cmdShowRepair struct {
+ Positional struct {
+ Repair []string `positional-arg-name:""`
+ } `positional-args:"yes"`
+}
+
+var shortRepairHelp = i18n.G("Shows specific repairs")
+var longRepairHelp = i18n.G(`
+The repair command shows the details about one or multiple repairs.
+`)
+
+func init() {
+ cmd := addCommand("repair", shortRepairHelp, longRepairHelp, func() flags.Commander {
+ return &cmdShowRepair{}
+ }, nil, nil)
+ if release.OnClassic {
+ cmd.hidden = true
+ }
+}
+
+func (x *cmdShowRepair) Execute(args []string) error {
+ return runSnapRepair("show", x.Positional.Repair)
+}
+
+type cmdListRepairs struct{}
+
+var shortRepairsHelp = i18n.G("Lists all repairs")
+var longRepairsHelp = i18n.G(`
+The repairs command lists all processed repairs for this device.
+`)
+
+func init() {
+ cmd := addCommand("repairs", shortRepairsHelp, longRepairsHelp, func() flags.Commander {
+ return &cmdListRepairs{}
+ }, nil, nil)
+ if release.OnClassic {
+ cmd.hidden = true
+ }
+}
+
+func (x *cmdListRepairs) Execute(args []string) error {
+ return runSnapRepair("list", args)
+}
diff -Nru snapd-2.28.5/cmd/snap/cmd_repair_repairs_test.go snapd-2.29.3/cmd/snap/cmd_repair_repairs_test.go
--- snapd-2.28.5/cmd/snap/cmd_repair_repairs_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_repair_repairs_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -0,0 +1,67 @@
+// -*- 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 (
+ "os"
+ "path/filepath"
+
+ . "gopkg.in/check.v1"
+
+ snap "github.com/snapcore/snapd/cmd/snap"
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/release"
+ "github.com/snapcore/snapd/testutil"
+)
+
+func mockSnapRepair(c *C) *testutil.MockCmd {
+ coreLibExecDir := filepath.Join(dirs.GlobalRootDir, dirs.CoreLibExecDir)
+ err := os.MkdirAll(coreLibExecDir, 0755)
+ c.Assert(err, IsNil)
+ return testutil.MockCommand(c, filepath.Join(coreLibExecDir, "snap-repair"), "")
+}
+
+func (s *SnapSuite) TestSnapShowRepair(c *C) {
+ restore := release.MockOnClassic(false)
+ defer restore()
+
+ mockSnapRepair := mockSnapRepair(c)
+ defer mockSnapRepair.Restore()
+
+ _, err := snap.Parser().ParseArgs([]string{"repair", "canonical-1"})
+ c.Assert(err, IsNil)
+ c.Check(mockSnapRepair.Calls(), DeepEquals, [][]string{
+ {"snap-repair", "show", "canonical-1"},
+ })
+}
+
+func (s *SnapSuite) TestSnapListRepairs(c *C) {
+ restore := release.MockOnClassic(false)
+ defer restore()
+
+ mockSnapRepair := mockSnapRepair(c)
+ defer mockSnapRepair.Restore()
+
+ _, err := snap.Parser().ParseArgs([]string{"repairs"})
+ c.Assert(err, IsNil)
+ c.Check(mockSnapRepair.Calls(), DeepEquals, [][]string{
+ {"snap-repair", "list"},
+ })
+}
diff -Nru snapd-2.28.5/cmd/snap/cmd_set.go snapd-2.29.3/cmd/snap/cmd_set.go
--- snapd-2.28.5/cmd/snap/cmd_set.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_set.go 2017-10-27 12:23:38.000000000 +0000
@@ -54,9 +54,12 @@
addCommand("set", shortSetHelp, longSetHelp, func() flags.Commander { return &cmdSet{} }, nil, []argDesc{
{
name: "",
+ // TRANSLATORS: This should probably not start with a lowercase letter.
desc: i18n.G("The snap to configure (e.g. hello-world)"),
}, {
+ // TRANSLATORS: This needs to be wrapped in <>s.
name: i18n.G(""),
+ // TRANSLATORS: This should probably not start with a lowercase letter.
desc: i18n.G("Configuration value (key=value)"),
},
})
diff -Nru snapd-2.28.5/cmd/snap/cmd_shell.go snapd-2.29.3/cmd/snap/cmd_shell.go
--- snapd-2.28.5/cmd/snap/cmd_shell.go 2017-01-16 06:45:53.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_shell.go 2017-10-27 12:23:38.000000000 +0000
@@ -45,7 +45,9 @@
func() flags.Commander {
return &cmdShell{}
}, nil, []argDesc{{
+ // TRANSLATORS: This needs to be wrapped in <>s.
name: i18n.G(""),
+ // TRANSLATORS: This should probably not start with a lowercase letter.
desc: i18n.G("The type of shell you want"),
}})
}
diff -Nru snapd-2.28.5/cmd/snap/cmd_sign_build.go snapd-2.29.3/cmd/snap/cmd_sign_build.go
--- snapd-2.28.5/cmd/snap/cmd_sign_build.go 2016-12-08 15:14:07.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_sign_build.go 2017-10-27 12:23:38.000000000 +0000
@@ -58,7 +58,9 @@
"k": i18n.G("Name of the GnuPG key to use (defaults to 'default' as key name)"),
"grade": i18n.G("Grade states the build quality of the snap (defaults to 'stable')"),
}, []argDesc{{
+ // TRANSLATORS: This needs to be wrapped in <>s.
name: i18n.G(""),
+ // TRANSLATORS: This should probably not start with a lowercase letter.
desc: i18n.G("Filename of the snap you want to assert a build for"),
}})
cmd.hidden = true
diff -Nru snapd-2.28.5/cmd/snap/cmd_snap_op.go snapd-2.29.3/cmd/snap/cmd_snap_op.go
--- snapd-2.28.5/cmd/snap/cmd_snap_op.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_snap_op.go 2017-10-23 06:17:27.000000000 +0000
@@ -71,7 +71,7 @@
}
func wait(cli *client.Client, id string) (*client.Change, error) {
- pb := progress.NewTextProgress()
+ pb := progress.MakeProgressBar()
defer func() {
pb.Finished()
}()
@@ -122,7 +122,7 @@
case t.ID == lastID:
pb.Set(float64(t.Progress.Done))
default:
- pb.Start(t.Progress.Label, float64(t.Progress.Total))
+ pb.Start(t.Summary, float64(t.Progress.Total))
lastID = t.ID
}
break
diff -Nru snapd-2.28.5/cmd/snap/cmd_snap_op_test.go snapd-2.29.3/cmd/snap/cmd_snap_op_test.go
--- snapd-2.28.5/cmd/snap/cmd_snap_op_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_snap_op_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -26,7 +26,6 @@
"mime/multipart"
"net/http"
"net/http/httptest"
- "os"
"path/filepath"
"regexp"
"time"
@@ -35,6 +34,9 @@
"github.com/snapcore/snapd/client"
snap "github.com/snapcore/snapd/cmd/snap"
+ "github.com/snapcore/snapd/progress"
+ "github.com/snapcore/snapd/progress/progresstest"
+ "github.com/snapcore/snapd/testutil"
)
type snapOpTestServer struct {
@@ -103,6 +105,8 @@
}
func (s *SnapOpSuite) TestWait(c *check.C) {
+ meter := &progresstest.Meter{}
+ defer progress.MockMeter(meter)()
restore := snap.MockMaxGoneTime(time.Millisecond)
defer restore()
@@ -111,27 +115,16 @@
snap.ClientConfig.BaseURL = server.URL
server.Close()
- d := c.MkDir()
- oldStdout := os.Stdout
- stdout, err := ioutil.TempFile(d, "stdout")
- c.Assert(err, check.IsNil)
- defer func() {
- os.Stdout = oldStdout
- stdout.Close()
- os.Remove(stdout.Name())
- }()
- os.Stdout = stdout
-
cli := snap.Client()
chg, err := snap.Wait(cli, "x")
c.Assert(chg, check.IsNil)
c.Assert(err, check.NotNil)
- buf, err := ioutil.ReadFile(stdout.Name())
- c.Assert(err, check.IsNil)
- c.Check(string(buf), check.Matches, "(?ms).*Waiting for server to restart.*")
+ c.Check(meter.Labels, testutil.Contains, "Waiting for server to restart")
}
func (s *SnapOpSuite) TestWaitRecovers(c *check.C) {
+ meter := &progresstest.Meter{}
+ defer progress.MockMeter(meter)()
restore := snap.MockMaxGoneTime(time.Millisecond)
defer restore()
@@ -144,27 +137,14 @@
fmt.Fprintln(w, `{"type": "sync", "result": {"ready": true, "status": "Done"}}`)
})
- d := c.MkDir()
- oldStdout := os.Stdout
- stdout, err := ioutil.TempFile(d, "stdout")
- c.Assert(err, check.IsNil)
- defer func() {
- os.Stdout = oldStdout
- stdout.Close()
- os.Remove(stdout.Name())
- }()
- os.Stdout = stdout
-
cli := snap.Client()
chg, err := snap.Wait(cli, "x")
// we got the change
c.Assert(chg, check.NotNil)
c.Assert(err, check.IsNil)
- buf, err := ioutil.ReadFile(stdout.Name())
- c.Assert(err, check.IsNil)
// but only after recovering
- c.Check(string(buf), check.Matches, "(?ms).*Waiting for server to restart.*")
+ c.Check(meter.Labels, testutil.Contains, "Waiting for server to restart")
}
func (s *SnapOpSuite) TestInstall(c *check.C) {
diff -Nru snapd-2.28.5/cmd/snap/cmd_unalias.go snapd-2.29.3/cmd/snap/cmd_unalias.go
--- snapd-2.28.5/cmd/snap/cmd_unalias.go 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_unalias.go 2017-10-27 12:23:38.000000000 +0000
@@ -27,7 +27,7 @@
type cmdUnalias struct {
Positionals struct {
- AliasOrSnap string `required:"yes"`
+ AliasOrSnap aliasOrSnap `required:"yes"`
} `positional-args:"true"`
}
@@ -40,6 +40,7 @@
addCommand("unalias", shortUnaliasHelp, longUnaliasHelp, func() flags.Commander {
return &cmdUnalias{}
}, nil, []argDesc{
+ // TRANSLATORS: This needs to be wrapped in <>s.
{name: i18n.G("")},
})
}
@@ -50,7 +51,7 @@
}
cli := Client()
- id, err := cli.Unalias(x.Positionals.AliasOrSnap)
+ id, err := cli.Unalias(string(x.Positionals.AliasOrSnap))
if err != nil {
return err
}
diff -Nru snapd-2.28.5/cmd/snap/cmd_userd_test.go snapd-2.29.3/cmd/snap/cmd_userd_test.go
--- snapd-2.28.5/cmd/snap/cmd_userd_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_userd_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -27,16 +27,33 @@
. "gopkg.in/check.v1"
snap "github.com/snapcore/snapd/cmd/snap"
+ "github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/testutil"
)
type userdSuite struct {
BaseSnapSuite
testutil.DBusTest
+
+ restoreLogger func()
}
var _ = Suite(&userdSuite{})
+func (s *userdSuite) SetUpTest(c *C) {
+ s.BaseSnapSuite.SetUpTest(c)
+ s.DBusTest.SetUpTest(c)
+
+ _, s.restoreLogger = logger.MockLogger()
+}
+
+func (s *userdSuite) TearDownTest(c *C) {
+ s.BaseSnapSuite.TearDownTest(c)
+ s.DBusTest.TearDownTest(c)
+
+ s.restoreLogger()
+}
+
func (s *userdSuite) TestUserdBadCommandline(c *C) {
_, err := snap.Parser().ParseArgs([]string{"userd", "extra-arg"})
c.Assert(err, ErrorMatches, "too many arguments for command")
diff -Nru snapd-2.28.5/cmd/snap/cmd_watch_test.go snapd-2.29.3/cmd/snap/cmd_watch_test.go
--- snapd-2.28.5/cmd/snap/cmd_watch_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/cmd/snap/cmd_watch_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -21,15 +21,14 @@
import (
"fmt"
- "io/ioutil"
"net/http"
- "os"
"time"
. "gopkg.in/check.v1"
snap "github.com/snapcore/snapd/cmd/snap"
- "github.com/snapcore/snapd/testutil"
+ "github.com/snapcore/snapd/progress"
+ "github.com/snapcore/snapd/progress/progresstest"
)
var fmtWatchChangeJSON = `{"type": "sync", "result": {
@@ -42,6 +41,8 @@
}}`
func (s *SnapSuite) TestCmdWatch(c *C) {
+ meter := &progresstest.Meter{}
+ defer progress.MockMeter(meter)()
restore := snap.MockMaxGoneTime(time.Millisecond)
defer restore()
@@ -64,22 +65,9 @@
n++
})
- oldStdout := os.Stdout
- stdout, err := ioutil.TempFile("", "stdout")
- c.Assert(err, IsNil)
- defer func() {
- os.Stdout = oldStdout
- stdout.Close()
- os.Remove(stdout.Name())
- }()
- os.Stdout = stdout
-
- _, err = snap.Parser().ParseArgs([]string{"watch", "42"})
- os.Stdout = oldStdout
+ _, err := snap.Parser().ParseArgs([]string{"watch", "42"})
c.Assert(err, IsNil)
c.Check(n, Equals, 3)
- buf, err := ioutil.ReadFile(stdout.Name())
- c.Assert(err, IsNil)
- c.Check(string(buf), testutil.Contains, "\rmy-snap 0 B / 100.00 KB")
+ c.Check(meter.Values, DeepEquals, []float64{51200})
}
diff -Nru snapd-2.28.5/cmd/snap/complete.go snapd-2.29.3/cmd/snap/complete.go
--- snapd-2.28.5/cmd/snap/complete.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/cmd/snap/complete.go 2017-10-23 06:17:27.000000000 +0000
@@ -1,13 +1,36 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2016 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
package main
import (
+ "bufio"
"fmt"
+ "os"
"strings"
"github.com/jessevdk/go-flags"
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/client"
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/snap"
)
type installedSnapName string
@@ -28,9 +51,46 @@
return ret
}
+func completeFromSortedFile(filename, match string) ([]flags.Completion, error) {
+ file, err := os.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+
+ var ret []flags.Completion
+
+ // TODO: look into implementing binary search
+ // e.g. https://github.com/pts/pts-line-bisect/
+ scanner := bufio.NewScanner(file)
+ for scanner.Scan() {
+ line := scanner.Text()
+ if line < match {
+ continue
+ }
+ if !strings.HasPrefix(line, match) {
+ break
+ }
+ ret = append(ret, flags.Completion{Item: line})
+ if len(ret) > 10000 {
+ // too many matches; slow machines could take too long to process this
+ // e.g. the bbb takes ~1s to process ~2M entries (i.e. to reach the
+ // point of asking the user if they actually want to see that many
+ // results). 10k ought to be enough for anybody.
+ break
+ }
+ }
+
+ return ret, nil
+}
+
type remoteSnapName string
func (s remoteSnapName) Complete(match string) []flags.Completion {
+ if ret, err := completeFromSortedFile(dirs.SnapNamesFile, match); err == nil {
+ return ret
+ }
+
if len(match) < 3 {
return nil
}
@@ -323,6 +383,30 @@
return ret
}
+type appName string
+
+func (s appName) Complete(match string) []flags.Completion {
+ cli := Client()
+ apps, err := cli.Apps(nil, client.AppOptions{})
+ if err != nil {
+ return nil
+ }
+
+ var ret []flags.Completion
+ for _, app := range apps {
+ if app.IsService() {
+ continue
+ }
+ name := snap.JoinSnapApp(app.Snap, app.Name)
+ if !strings.HasPrefix(name, match) {
+ continue
+ }
+ ret = append(ret, flags.Completion{Item: name})
+ }
+
+ return ret
+}
+
type serviceName string
func (s serviceName) Complete(match string) []flags.Completion {
@@ -347,3 +431,27 @@
return ret
}
+
+type aliasOrSnap string
+
+func (s aliasOrSnap) Complete(match string) []flags.Completion {
+ aliases, err := Client().Aliases()
+ if err != nil {
+ return nil
+ }
+ var ret []flags.Completion
+ for snap, aliases := range aliases {
+ if strings.HasPrefix(snap, match) {
+ ret = append(ret, flags.Completion{Item: snap})
+ }
+ for alias, status := range aliases {
+ if status.Status == "disabled" {
+ continue
+ }
+ if strings.HasPrefix(alias, match) {
+ ret = append(ret, flags.Completion{Item: alias})
+ }
+ }
+ }
+ return ret
+}
diff -Nru snapd-2.28.5/cmd/snap/export_test.go snapd-2.29.3/cmd/snap/export_test.go
--- snapd-2.28.5/cmd/snap/export_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/cmd/snap/export_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -40,6 +40,7 @@
IsReexeced = isReexeced
MaybePrintServices = maybePrintServices
MaybePrintCommands = maybePrintCommands
+ SortByPath = sortByPath
)
func MockPollTime(d time.Duration) (restore func()) {
@@ -127,3 +128,11 @@
func AssertTypeNameCompletion(match string) []flags.Completion {
return assertTypeName("").Complete(match)
}
+
+func MockIsTerminal(t bool) (restore func()) {
+ oldIsTerminal := isTerminal
+ isTerminal = func() bool { return t }
+ return func() {
+ isTerminal = oldIsTerminal
+ }
+}
diff -Nru snapd-2.28.5/cmd/snap/last.go snapd-2.29.3/cmd/snap/last.go
--- snapd-2.28.5/cmd/snap/last.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/cmd/snap/last.go 2017-10-27 12:23:38.000000000 +0000
@@ -38,7 +38,9 @@
}
var changeIDMixinArgDesc = []argDesc{{
+ // TRANSLATORS: This needs to be wrapped in <>s.
name: i18n.G(""),
+ // TRANSLATORS: This should probably not start with a lowercase letter.
desc: i18n.G("Change ID"),
}}
diff -Nru snapd-2.28.5/cmd/snap/main.go snapd-2.29.3/cmd/snap/main.go
--- snapd-2.28.5/cmd/snap/main.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/cmd/snap/main.go 2017-10-27 12:23:38.000000000 +0000
@@ -26,6 +26,7 @@
"path/filepath"
"strings"
"unicode"
+ "unicode/utf8"
"github.com/jessevdk/go-flags"
@@ -38,19 +39,31 @@
"github.com/snapcore/snapd/i18n"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/snap"
)
func init() {
// set User-Agent for when 'snap' talks to the store directly (snap download etc...)
httputil.SetUserAgentFromVersion(cmd.Version, "snap")
+
+ // plug/slot sanitization not used nor possible from snap command, make it no-op
+ snap.SanitizePlugsSlots = func(snapInfo *snap.Info) {}
+
+ if osutil.GetenvBool("SNAPD_DEBUG") || osutil.GetenvBool("SNAPPY_TESTING") {
+ // in tests or when debugging, enforce the "tidy" lint checks
+ noticef = logger.Panicf
+ }
}
-// Standard streams, redirected for testing.
var (
- Stdin io.Reader = os.Stdin
- Stdout io.Writer = os.Stdout
- Stderr io.Writer = os.Stderr
- ReadPassword = terminal.ReadPassword
+ // Standard streams, redirected for testing.
+ Stdin io.Reader = os.Stdin
+ Stdout io.Writer = os.Stdout
+ Stderr io.Writer = os.Stderr
+ // overridden for testing
+ ReadPassword = terminal.ReadPassword
+ // set to logger.Panicf in testing
+ noticef = logger.Noticef
)
type options struct {
@@ -124,8 +137,15 @@
logger.Panicf("description of %s's %q of %q set from tag (=> no i18n)", cmdName, optName, origDesc)
}
if len(desc) > 0 {
- if !unicode.IsUpper(([]rune)(desc)[0]) {
- logger.Panicf("description of %s's %q not uppercase: %q", cmdName, optName, desc)
+ // decode the first rune instead of converting all of desc into []rune
+ r, _ := utf8.DecodeRuneInString(desc)
+ // note IsLower != !IsUpper for runes with no upper/lower.
+ // Also note that login.u.c. is the only exception we're allowing for
+ // now, but the list of exceptions could grow -- if it does, we might
+ // want to change it to check for urlish things instead of just
+ // login.u.c.
+ if unicode.IsLower(r) && !strings.HasPrefix(desc, "login.ubuntu.com") {
+ noticef("description of %s's %q is lowercase: %q", cmdName, optName, desc)
}
}
}
@@ -133,7 +153,7 @@
func lintArg(cmdName, optName, desc, origDesc string) {
lintDesc(cmdName, optName, desc, origDesc)
if optName[0] != '<' || optName[len(optName)-1] != '>' {
- logger.Panicf("argument %q's %q should have <>s", cmdName, optName)
+ noticef("argument %q's %q should be wrapped in <>s", cmdName, optName)
}
}
diff -Nru snapd-2.28.5/cmd/snap/main_test.go snapd-2.29.3/cmd/snap/main_test.go
--- snapd-2.28.5/cmd/snap/main_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/cmd/snap/main_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -37,6 +37,7 @@
"github.com/snapcore/snapd/cmd"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/osutil"
+ snapdsnap "github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/testutil"
snap "github.com/snapcore/snapd/cmd/snap"
@@ -74,6 +75,8 @@
snap.ReadPassword = s.readPassword
s.AuthFile = filepath.Join(c.MkDir(), "json")
os.Setenv(TestAuthFileEnvKey, s.AuthFile)
+
+ snapdsnap.MockSanitizePlugsSlots(func(snapInfo *snapdsnap.Info) {})
}
func (s *BaseSnapSuite) TearDownTest(c *C) {
diff -Nru snapd-2.28.5/cmd/snap-confine/apparmor-support.c snapd-2.29.3/cmd/snap-confine/apparmor-support.c
--- snapd-2.28.5/cmd/snap-confine/apparmor-support.c 2017-10-13 20:09:56.000000000 +0000
+++ snapd-2.29.3/cmd/snap-confine/apparmor-support.c 2017-10-23 06:17:27.000000000 +0000
@@ -73,7 +73,7 @@
// Use aa_getcon() to check the label of the current process and
// confinement type. Note that the returned label must be released with
// free() but the mode is a constant string that must not be freed.
- char *label __attribute__ ((cleanup(sc_cleanup_string))) = NULL;
+ char *label SC_CLEANUP(sc_cleanup_string) = NULL;
char *mode = NULL;
if (aa_getcon(&label, &mode) < 0) {
die("cannot query current apparmor profile");
diff -Nru snapd-2.28.5/cmd/snap-confine/cookie-support.c snapd-2.29.3/cmd/snap-confine/cookie-support.c
--- snapd-2.28.5/cmd/snap-confine/cookie-support.c 2017-10-13 20:09:56.000000000 +0000
+++ snapd-2.29.3/cmd/snap-confine/cookie-support.c 2017-10-23 06:17:27.000000000 +0000
@@ -39,13 +39,13 @@
char *sc_cookie_get_from_snapd(const char *snap_name, struct sc_error **errorp)
{
- char context_path[PATH_MAX];
+ char context_path[PATH_MAX] = { 0 };
struct sc_error *err = NULL;
char *context = NULL;
sc_must_snprintf(context_path, sizeof(context_path), "%s/snap.%s",
sc_cookie_dir, snap_name);
- int fd __attribute__ ((cleanup(sc_cleanup_close))) = -1;
+ int fd SC_CLEANUP(sc_cleanup_close) = -1;
fd = open(context_path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
if (fd < 0) {
err =
@@ -55,7 +55,7 @@
goto out;
}
// large enough buffer for opaque cookie string
- char context_val[255];
+ char context_val[255] = { 0 };
ssize_t n = read(fd, context_val, sizeof(context_val) - 1);
if (n < 0) {
err =
diff -Nru snapd-2.28.5/cmd/snap-confine/cookie-support-test.c snapd-2.29.3/cmd/snap-confine/cookie-support-test.c
--- snapd-2.28.5/cmd/snap-confine/cookie-support-test.c 2017-10-13 20:09:56.000000000 +0000
+++ snapd-2.29.3/cmd/snap-confine/cookie-support-test.c 2017-10-23 06:17:27.000000000 +0000
@@ -47,7 +47,7 @@
static void create_dumy_cookie_file(const char *snap_name,
const char *dummy_cookie)
{
- char path[PATH_MAX];
+ char path[PATH_MAX] = { 0 };
FILE *f;
int n;
diff -Nru snapd-2.28.5/cmd/snap-confine/mount-support.c snapd-2.29.3/cmd/snap-confine/mount-support.c
--- snapd-2.28.5/cmd/snap-confine/mount-support.c 2017-10-13 20:09:56.000000000 +0000
+++ snapd-2.29.3/cmd/snap-confine/mount-support.c 2017-10-23 06:17:27.000000000 +0000
@@ -19,6 +19,7 @@
#include
#include
+#include
#include
#include
#include
@@ -29,6 +30,8 @@
#include
#include
#include
+#include
+#include
#include
#include
@@ -136,76 +139,50 @@
sc_do_mount("/dev/pts/ptmx", "/dev/ptmx", "none", MS_BIND, 0);
}
-/*
- * Setup mount profiles as described by snapd.
- *
- * This function reads /var/lib/snapd/mount/$security_tag.fstab as a fstab(5) file
- * and executes the mount requests described there.
- *
- * Currently only bind mounts are allowed. All bind mounts are read only by
- * default though the `rw` flag can be used.
+/**
+ * Setup mount profiles by running snap-update-ns.
*
- * This function is called with the rootfs being "consistent" so that it is
- * either the core snap on an all-snap system or the core snap + punched holes
- * on a classic system.
+ * The first argument is an open file descriptor (though opened with O_PATH, so
+ * not as powerful), to a copy of snap-update-ns. The program is opened before
+ * the root filesystem is pivoted so that it is easier to pick the right copy.
**/
-static void sc_setup_mount_profiles(const char *snap_name)
+static void sc_setup_mount_profiles(int snap_update_ns_fd,
+ const char *snap_name)
{
- debug("%s: %s", __FUNCTION__, snap_name);
-
- FILE *desired __attribute__ ((cleanup(sc_cleanup_endmntent))) = NULL;
- FILE *current __attribute__ ((cleanup(sc_cleanup_endmntent))) = NULL;
- char profile_path[PATH_MAX];
-
- sc_must_snprintf(profile_path, sizeof(profile_path),
- "/run/snapd/ns/snap.%s.fstab", snap_name);
- debug("opening current mount profile %s", profile_path);
- current = setmntent(profile_path, "w");
- if (current == NULL) {
- die("cannot open current mount profile: %s", profile_path);
- }
-
- sc_must_snprintf(profile_path, sizeof(profile_path),
- "/var/lib/snapd/mount/snap.%s.fstab", snap_name);
- debug("opening desired mount profile %s", profile_path);
- desired = setmntent(profile_path, "r");
- if (desired == NULL && errno == ENOENT) {
- // It is ok for the desired profile to not exist. Note that in this
- // case we also "update" the current profile as we already opened and
- // truncated it above.
- return;
- }
- if (desired == NULL) {
- die("cannot open desired mount profile: %s", profile_path);
- }
-
- struct mntent *m = NULL;
- while ((m = getmntent(desired)) != NULL) {
- debug("read mount entry\n"
- "\tmnt_fsname: %s\n"
- "\tmnt_dir: %s\n"
- "\tmnt_type: %s\n"
- "\tmnt_opts: %s\n"
- "\tmnt_freq: %d\n"
- "\tmnt_passno: %d",
- m->mnt_fsname, m->mnt_dir, m->mnt_type,
- m->mnt_opts, m->mnt_freq, m->mnt_passno);
- int flags = MS_BIND | MS_RDONLY | MS_NODEV | MS_NOSUID;
- debug("initial flags are: bind,ro,nodev,nosuid");
- if (strcmp(m->mnt_type, "none") != 0) {
- die("cannot honor mount profile, only 'none' filesystem type is supported");
- }
- if (hasmntopt(m, "bind") == NULL) {
- die("cannot honor mount profile, the bind mount flag is mandatory");
- }
- if (hasmntopt(m, "rw") != NULL) {
- flags &= ~MS_RDONLY;
- }
- sc_do_mount(m->mnt_fsname, m->mnt_dir, NULL, flags, NULL);
- if (addmntent(current, m) != 0) { // NOTE: returns 1 on error.
- die("cannot append entry to the current mount profile");
+ debug("calling snap-update-ns to initialize mount namespace");
+ pid_t child = fork();
+ if (child < 0) {
+ die("cannot fork to run snap-update-ns");
+ }
+ if (child == 0) {
+ // We are the child, execute snap-update-ns
+ char *snap_name_copy SC_CLEANUP(sc_cleanup_string) = NULL;
+ snap_name_copy = strdup(snap_name);
+ if (snap_name_copy == NULL) {
+ die("cannot copy snap name");
}
+ char *argv[] = {
+ "snap-update-ns", "--from-snap-confine", snap_name_copy,
+ NULL
+ };
+ char *envp[] = { NULL };
+ debug("fexecv(%d (snap-update-ns), %s %s %s,)",
+ snap_update_ns_fd, argv[0], argv[1], argv[2]);
+ fexecve(snap_update_ns_fd, argv, envp);
+ die("cannot execute snap-update-ns");
+ }
+ // We are the parent, so wait for snap-update-ns to finish.
+ int status = 0;
+ debug("waiting for snap-update-ns to finish...");
+ if (waitpid(child, &status, 0) < 0) {
+ die("waitpid() failed for snap-update-ns process");
+ }
+ if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
+ die("snap-update-ns failed with code %i", WEXITSTATUS(status));
+ } else if (WIFSIGNALED(status)) {
+ die("snap-update-ns killed by signal %i", WTERMSIG(status));
}
+ debug("snap-update-ns finished successfully");
}
struct sc_mount {
@@ -259,8 +236,8 @@
static void sc_bootstrap_mount_namespace(const struct sc_mount_config *config)
{
char scratch_dir[] = "/tmp/snap.rootfs_XXXXXX";
- char src[PATH_MAX];
- char dst[PATH_MAX];
+ char src[PATH_MAX] = { 0 };
+ char dst[PATH_MAX] = { 0 };
if (mkdtemp(scratch_dir) == NULL) {
die("cannot create temporary directory for the root file system");
}
@@ -363,7 +340,7 @@
// where $ROOT is either "/" or the "/snap/core/current"
// that we are re-execing from
char *src = NULL;
- char self[PATH_MAX + 1] = { 0, };
+ char self[PATH_MAX + 1] = { 0 };
if (readlink("/proc/self/exe", self, sizeof(self) - 1) < 0) {
die("cannot read /proc/self/exe");
}
@@ -555,16 +532,49 @@
return false;
}
+static int sc_open_snap_update_ns()
+{
+ // +1 is for the case where the link is exactly PATH_MAX long but we also
+ // want to store the terminating '\0'. The readlink system call doesn't add
+ // terminating null, but our initialization of buf handles this for us.
+ char buf[PATH_MAX + 1] = { 0 };
+ if (readlink("/proc/self/exe", buf, sizeof buf) < 0) {
+ die("cannot readlink /proc/self/exe");
+ }
+ if (buf[0] != '/') { // this shouldn't happen, but make sure have absolute path
+ die("readlink /proc/self/exe returned relative path");
+ }
+ char *bufcopy SC_CLEANUP(sc_cleanup_string) = NULL;
+ bufcopy = strdup(buf);
+ if (bufcopy == NULL) {
+ die("cannot copy buffer");
+ }
+ char *dname = dirname(bufcopy);
+ sc_must_snprintf(buf, sizeof buf, "%s/%s", dname, "snap-update-ns");
+ debug("snap-update-ns executable: %s", buf);
+ int fd = open(buf, O_PATH | O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
+ if (fd < 0) {
+ die("cannot open snap-update-ns executable");
+ }
+ debug("opened snap-update-ns executable as file descriptor %d", fd);
+ return fd;
+}
+
void sc_populate_mount_ns(const char *base_snap_name, const char *snap_name)
{
// Get the current working directory before we start fiddling with
// mounts and possibly pivot_root. At the end of the whole process, we
// will try to re-locate to the same directory (if possible).
- char *vanilla_cwd __attribute__ ((cleanup(sc_cleanup_string))) = NULL;
+ char *vanilla_cwd SC_CLEANUP(sc_cleanup_string) = NULL;
vanilla_cwd = get_current_dir_name();
if (vanilla_cwd == NULL) {
die("cannot get the current working directory");
}
+ // Find and open snap-update-ns from the same path as where we
+ // (snap-confine) were called.
+ int snap_update_ns_fd SC_CLEANUP(sc_cleanup_close) = -1;
+ snap_update_ns_fd = sc_open_snap_update_ns();
+
bool on_classic_distro = is_running_on_classic_distribution();
// on classic or with alternative base snaps we need to setup
// a different confinement
@@ -592,18 +602,24 @@
{"/run/netns", true}, // access to the 'ip netns' network namespaces
{},
};
- char rootfs_dir[PATH_MAX];
- again:
+ char rootfs_dir[PATH_MAX] = { 0 };
sc_must_snprintf(rootfs_dir, sizeof rootfs_dir,
"%s/%s/current/", SNAP_MOUNT_DIR,
base_snap_name);
if (access(rootfs_dir, F_OK) != 0) {
if (sc_streq(base_snap_name, "core")) {
- // As a special fallback, allow the base snap to degrade from
- // "core" to "ubuntu-core". This is needed for the migration
- // tests.
+ // As a special fallback, allow the
+ // base snap to degrade from "core" to
+ // "ubuntu-core". This is needed for
+ // the migration tests.
base_snap_name = "ubuntu-core";
- goto again;
+ sc_must_snprintf(rootfs_dir, sizeof rootfs_dir,
+ "%s/%s/current/",
+ SNAP_MOUNT_DIR,
+ base_snap_name);
+ if (access(rootfs_dir, F_OK) != 0) {
+ die("cannot locate the core or legacy core snap (current symlink missing?)");
+ }
}
die("cannot locate the base snap: %s", base_snap_name);
}
@@ -646,7 +662,7 @@
sc_setup_quirks();
}
// setup the security backend bind mounts
- sc_setup_mount_profiles(snap_name);
+ sc_setup_mount_profiles(snap_update_ns_fd, snap_name);
// Try to re-locate back to vanilla working directory. This can fail
// because that directory is no longer present.
@@ -665,8 +681,7 @@
static bool is_mounted_with_shared_option(const char *dir)
{
- struct sc_mountinfo *sm
- __attribute__ ((cleanup(sc_cleanup_mountinfo))) = NULL;
+ struct sc_mountinfo *sm SC_CLEANUP(sc_cleanup_mountinfo) = NULL;
sm = sc_parse_mountinfo(NULL);
if (sm == NULL) {
die("cannot parse /proc/self/mountinfo");
diff -Nru snapd-2.28.5/cmd/snap-confine/mount-support-nvidia.c snapd-2.29.3/cmd/snap-confine/mount-support-nvidia.c
--- snapd-2.28.5/cmd/snap-confine/mount-support-nvidia.c 2017-10-13 20:09:56.000000000 +0000
+++ snapd-2.29.3/cmd/snap-confine/mount-support-nvidia.c 2017-10-23 06:17:27.000000000 +0000
@@ -33,7 +33,9 @@
#include "../libsnap-confine-private/string-utils.h"
#include "../libsnap-confine-private/utils.h"
-#ifdef NVIDIA_ARCH
+#define SC_NVIDIA_DRIVER_VERSION_FILE "/sys/module/nvidia/version"
+
+#ifdef NVIDIA_BIARCH
// List of globs that describe nvidia userspace libraries.
// This list was compiled from the following packages.
@@ -93,7 +95,7 @@
const char *glob_list[],
size_t glob_list_len)
{
- glob_t glob_res __attribute__ ((__cleanup__(globfree))) = {
+ glob_t glob_res SC_CLEANUP(globfree) = {
.gl_pathv = NULL};
// Find all the entries matching the list of globs
for (size_t i = 0; i < glob_list_len; ++i) {
@@ -109,12 +111,11 @@
}
// Symlink each file found
for (size_t i = 0; i < glob_res.gl_pathc; ++i) {
- char symlink_name[512];
- char symlink_target[512];
+ char symlink_name[512] = { 0 };
+ char symlink_target[512] = { 0 };
const char *pathname = glob_res.gl_pathv[i];
char *pathname_copy
- __attribute__ ((cleanup(sc_cleanup_string))) =
- strdup(pathname);
+ SC_CLEANUP(sc_cleanup_string) = strdup(pathname);
char *filename = basename(pathname_copy);
struct stat stat_buf;
int err = lstat(pathname, &stat_buf);
@@ -166,10 +167,10 @@
}
}
-static void sc_mount_nvidia_driver_arch(const char *rootfs_dir)
+static void sc_mount_nvidia_driver_biarch(const char *rootfs_dir)
{
// Bind mount a tmpfs on $rootfs_dir/var/lib/snapd/lib/gl
- char buf[512];
+ char buf[512] = { 0 };
sc_must_snprintf(buf, sizeof(buf), "%s%s", rootfs_dir,
"/var/lib/snapd/lib/gl");
const char *libgl_dir = buf;
@@ -187,21 +188,20 @@
}
}
-#endif // ifdef NVIDIA_ARCH
+#endif // ifdef NVIDIA_BIARCH
-#ifdef NVIDIA_UBUNTU
+#ifdef NVIDIA_MULTIARCH
struct sc_nvidia_driver {
int major_version;
int minor_version;
};
-#define SC_NVIDIA_DRIVER_VERSION_FILE "/sys/module/nvidia/version"
#define SC_LIBGL_DIR "/var/lib/snapd/lib/gl"
static void sc_probe_nvidia_driver(struct sc_nvidia_driver *driver)
{
- FILE *file __attribute__ ((cleanup(sc_cleanup_file))) = NULL;
+ FILE *file SC_CLEANUP(sc_cleanup_file) = NULL;
debug("opening file describing nvidia driver version");
file = fopen(SC_NVIDIA_DRIVER_VERSION_FILE, "rt");
if (file == NULL) {
@@ -224,7 +224,7 @@
driver->minor_version);
}
-static void sc_mount_nvidia_driver_ubuntu(const char *rootfs_dir)
+static void sc_mount_nvidia_driver_multiarch(const char *rootfs_dir)
{
struct sc_nvidia_driver driver;
@@ -237,7 +237,8 @@
}
// Construct the paths for the driver userspace libraries
// and for the gl directory.
- char src[PATH_MAX], dst[PATH_MAX];
+ char src[PATH_MAX] = { 0 };
+ char dst[PATH_MAX] = { 0 };
sc_must_snprintf(src, sizeof src, "/usr/lib/nvidia-%d",
driver.major_version);
sc_must_snprintf(dst, sizeof dst, "%s%s", rootfs_dir, SC_LIBGL_DIR);
@@ -256,14 +257,18 @@
die("cannot bind mount nvidia driver %s -> %s", src, dst);
}
}
-#endif // ifdef NVIDIA_UBUNTU
+#endif // ifdef NVIDIA_MULTIARCH
void sc_mount_nvidia_driver(const char *rootfs_dir)
{
-#ifdef NVIDIA_UBUNTU
- sc_mount_nvidia_driver_ubuntu(rootfs_dir);
-#endif // ifdef NVIDIA_UBUNTU
-#ifdef NVIDIA_ARCH
- sc_mount_nvidia_driver_arch(rootfs_dir);
-#endif // ifdef NVIDIA_ARCH
+ /* If NVIDIA module isn't loaded, don't attempt to mount the drivers */
+ if (access(SC_NVIDIA_DRIVER_VERSION_FILE, F_OK) != 0) {
+ return;
+ }
+#ifdef NVIDIA_MULTIARCH
+ sc_mount_nvidia_driver_multiarch(rootfs_dir);
+#endif // ifdef NVIDIA_MULTIARCH
+#ifdef NVIDIA_BIARCH
+ sc_mount_nvidia_driver_biarch(rootfs_dir);
+#endif // ifdef NVIDIA_BIARCH
}
diff -Nru snapd-2.28.5/cmd/snap-confine/ns-support.c snapd-2.29.3/cmd/snap-confine/ns-support.c
--- snapd-2.28.5/cmd/snap-confine/ns-support.c 2017-10-13 20:09:56.000000000 +0000
+++ snapd-2.29.3/cmd/snap-confine/ns-support.c 2017-10-23 06:17:27.000000000 +0000
@@ -78,8 +78,7 @@
**/
static bool sc_is_ns_group_dir_private()
{
- struct sc_mountinfo *info
- __attribute__ ((cleanup(sc_cleanup_mountinfo))) = NULL;
+ struct sc_mountinfo *info SC_CLEANUP(sc_cleanup_mountinfo) = NULL;
info = sc_parse_mountinfo(NULL);
if (info == NULL) {
die("cannot parse /proc/self/mountinfo");
@@ -101,8 +100,8 @@
void sc_reassociate_with_pid1_mount_ns()
{
- int init_mnt_fd __attribute__ ((cleanup(sc_cleanup_close))) = -1;
- int self_mnt_fd __attribute__ ((cleanup(sc_cleanup_close))) = -1;
+ int init_mnt_fd SC_CLEANUP(sc_cleanup_close) = -1;
+ int self_mnt_fd SC_CLEANUP(sc_cleanup_close) = -1;
debug("checking if the current process shares mount namespace"
" with the init process");
@@ -117,7 +116,8 @@
if (self_mnt_fd < 0) {
die("cannot open mount namespace of the current process (O_PATH)");
}
- char init_buf[128], self_buf[128];
+ char init_buf[128] = { 0 };
+ char self_buf[128] = { 0 };
memset(init_buf, 0, sizeof init_buf);
if (readlinkat(init_mnt_fd, "", init_buf, sizeof init_buf) < 0) {
if (errno == ENOENT) {
@@ -141,8 +141,7 @@
"the init process, re-association required");
// NOTE: we cannot use O_NOFOLLOW here because that file will always be a
// symbolic link. We actually want to open it this way.
- int init_mnt_fd_real
- __attribute__ ((cleanup(sc_cleanup_close))) = -1;
+ int init_mnt_fd_real SC_CLEANUP(sc_cleanup_close) = -1;
init_mnt_fd_real = open("/proc/1/ns/mnt", O_RDONLY | O_CLOEXEC);
if (init_mnt_fd_real < 0) {
die("cannot open mount namespace of the init process");
@@ -244,10 +243,10 @@
struct sc_apparmor *apparmor)
{
// Open the mount namespace file.
- char mnt_fname[PATH_MAX];
+ char mnt_fname[PATH_MAX] = { 0 };
sc_must_snprintf(mnt_fname, sizeof mnt_fname, "%s%s", group->name,
SC_NS_MNT_FILE);
- int mnt_fd __attribute__ ((cleanup(sc_cleanup_close))) = -1;
+ int mnt_fd SC_CLEANUP(sc_cleanup_close) = -1;
// NOTE: There is no O_EXCL here because the file can be around but
// doesn't have to be a mounted namespace.
//
@@ -276,8 +275,7 @@
#define NSFS_MAGIC 0x6e736673
#endif
if (buf.f_type == NSFS_MAGIC || buf.f_type == PROC_SUPER_MAGIC) {
- char *vanilla_cwd __attribute__ ((cleanup(sc_cleanup_string))) =
- NULL;
+ char *vanilla_cwd SC_CLEANUP(sc_cleanup_string) = NULL;
vanilla_cwd = get_current_dir_name();
if (vanilla_cwd == NULL) {
die("cannot get the current working directory");
@@ -377,8 +375,8 @@
debug
("capturing mount namespace of process %d in namespace group %s",
(int)parent, group->name);
- char src[PATH_MAX];
- char dst[PATH_MAX];
+ char src[PATH_MAX] = { 0 };
+ char dst[PATH_MAX] = { 0 };
sc_must_snprintf(src, sizeof src, "/proc/%d/ns/mnt",
(int)parent);
sc_must_snprintf(dst, sizeof dst, "%s%s", group->name,
@@ -438,7 +436,7 @@
void sc_discard_preserved_ns_group(struct sc_ns_group *group)
{
// Remember the current working directory
- int old_dir_fd __attribute__ ((cleanup(sc_cleanup_close))) = -1;
+ int old_dir_fd SC_CLEANUP(sc_cleanup_close) = -1;
old_dir_fd = open(".", O_PATH | O_DIRECTORY | O_CLOEXEC);
if (old_dir_fd < 0) {
die("cannot open current directory");
@@ -448,7 +446,7 @@
die("cannot move to namespace group directory");
}
// Unmount ${group_name}.mnt which holds the preserved namespace
- char mnt_fname[PATH_MAX];
+ char mnt_fname[PATH_MAX] = { 0 };
sc_must_snprintf(mnt_fname, sizeof mnt_fname, "%s%s", group->name,
SC_NS_MNT_FILE);
debug("unmounting preserved mount namespace file %s", mnt_fname);
diff -Nru snapd-2.28.5/cmd/snap-confine/quirks.c snapd-2.29.3/cmd/snap-confine/quirks.c
--- snapd-2.28.5/cmd/snap-confine/quirks.c 2017-10-13 20:09:56.000000000 +0000
+++ snapd-2.29.3/cmd/snap-confine/quirks.c 2017-10-23 06:17:27.000000000 +0000
@@ -90,7 +90,7 @@
if (sc_nonfatal_mkpath(dest_dir, 0755) < 0) {
die("cannot create empty directory at %s", dest_dir);
}
- char buf[1000];
+ char buf[1000] = { 0 };
const char *flags_str = sc_mount_opt2str(buf, sizeof buf, flags);
debug("performing operation: mount %s %s -o %s", src_dir, dest_dir,
flags_str);
@@ -130,15 +130,15 @@
}
debug("bind-mounting all the files from the reference directory");
- DIR *dirp __attribute__ ((cleanup(sc_cleanup_closedir))) = NULL;
+ DIR *dirp SC_CLEANUP(sc_cleanup_closedir) = NULL;
dirp = opendir(ref_dir);
if (dirp == NULL) {
die("cannot open reference directory %s", ref_dir);
}
struct dirent *entryp = NULL;
do {
- char src_name[PATH_MAX * 2];
- char dest_name[PATH_MAX * 2];
+ char src_name[PATH_MAX * 2] = { 0 };
+ char dest_name[PATH_MAX * 2] = { 0 };
// Set errno to zero, if readdir fails it will not only return null but
// set errno to a non-zero value. This is how we can differentiate
// end-of-directory from an actual error.
@@ -203,7 +203,7 @@
"/var/lib/snapd", snapd_tmp);
}
// now let's make /var/lib the vanilla /var/lib from the core snap
- char buf[PATH_MAX];
+ char buf[PATH_MAX] = { 0 };
sc_must_snprintf(buf, sizeof buf, "%s/var/lib",
sc_get_inner_core_mount_point());
sc_quirk_create_writable_mimic("/var/lib", buf,
diff -Nru snapd-2.28.5/cmd/snap-confine/seccomp-support.c snapd-2.29.3/cmd/snap-confine/seccomp-support.c
--- snapd-2.28.5/cmd/snap-confine/seccomp-support.c 2017-10-13 20:09:56.000000000 +0000
+++ snapd-2.29.3/cmd/snap-confine/seccomp-support.c 2017-10-27 12:31:06.000000000 +0000
@@ -38,7 +38,7 @@
static const char *filter_profile_dir = "/var/lib/snapd/seccomp/bpf/";
// MAX_BPF_SIZE is an arbitrary limit.
-const int MAX_BPF_SIZE = 32 * 1024;
+#define MAX_BPF_SIZE (32 * 1024)
typedef struct sock_filter bpf_instr;
@@ -66,7 +66,7 @@
die("valid_bpfpath_is_safe needs an absolute path as input");
}
// strtok_r() modifies its first argument, so work on a copy
- char *tokenized __attribute__ ((cleanup(sc_cleanup_string))) = NULL;
+ char *tokenized SC_CLEANUP(sc_cleanup_string) = NULL;
tokenized = strdup(path);
if (tokenized == NULL) {
die("cannot allocate memory for copy of path");
@@ -74,7 +74,7 @@
// allocate a string large enough to hold path, and initialize it to
// '/'
size_t checked_path_size = strlen(path) + 1;
- char *checked_path __attribute__ ((cleanup(sc_cleanup_string))) = NULL;
+ char *checked_path SC_CLEANUP(sc_cleanup_string) = NULL;
checked_path = calloc(checked_path_size, 1);
if (checked_path == NULL) {
die("cannot allocate memory for checked_path");
@@ -93,7 +93,7 @@
// reconstruct the path from '/' down to profile_name
char *buf_token = strtok_r(tokenized, "/", &buf_saveptr);
while (buf_token != NULL) {
- char *prev __attribute__ ((cleanup(sc_cleanup_string))) = NULL;
+ char *prev SC_CLEANUP(sc_cleanup_string) = NULL;
prev = strdup(checked_path); // needed by vsnprintf in sc_must_snprintf
if (prev == NULL) {
die("cannot allocate memory for copy of checked_path");
@@ -116,7 +116,7 @@
{
debug("loading bpf program for security tag %s", filter_profile);
- char profile_path[PATH_MAX];
+ char profile_path[PATH_MAX] = { 0 };
sc_must_snprintf(profile_path, sizeof(profile_path), "%s/%s.bin",
filter_profile_dir, filter_profile);
@@ -154,7 +154,7 @@
validate_bpfpath_is_safe(profile_path);
// load bpf
- unsigned char bpf[MAX_BPF_SIZE + 1]; // account for EOF
+ char bpf[MAX_BPF_SIZE + 1] = { 0 }; // account for EOF
FILE *fp = fopen(profile_path, "rb");
if (fp == NULL) {
die("cannot read %s", profile_path);
@@ -170,6 +170,10 @@
fclose(fp);
debug("read %zu bytes from %s", num_read, profile_path);
+ if (sc_streq(bpf, "@unrestricted\n")) {
+ return 0;
+ }
+
uid_t real_uid, effective_uid, saved_uid;
if (getresuid(&real_uid, &effective_uid, &saved_uid) < 0) {
die("cannot call getresuid");
diff -Nru snapd-2.28.5/cmd/snap-confine/snap-confine.apparmor.in snapd-2.29.3/cmd/snap-confine/snap-confine.apparmor.in
--- snapd-2.28.5/cmd/snap-confine/snap-confine.apparmor.in 2017-10-11 17:40:25.000000000 +0000
+++ snapd-2.29.3/cmd/snap-confine/snap-confine.apparmor.in 2017-11-09 12:09:26.000000000 +0000
@@ -2,6 +2,14 @@
#include
@LIBEXECDIR@/snap-confine (attach_disconnected) {
+ # Include any additional files that snapd chose to generate.
+ # - for $HOME on NFS
+ # - for $HOME on encrypted media
+ #
+ # Those are discussed on https://forum.snapcraft.io/t/snapd-vs-upstream-kernel-vs-apparmor
+ # and https://forum.snapcraft.io/t/snaps-and-nfs-home/
+ include "/var/lib/snapd/apparmor/snap-confine.d"
+
# We run privileged, so be fanatical about what we include and don't use
# any abstractions
/etc/ld.so.cache r,
@@ -35,16 +43,24 @@
/dev/pts/[0-9]* rw,
/dev/tty rw,
- # cgroups
+ # cgroup: devices
capability sys_admin,
capability dac_override,
/sys/fs/cgroup/devices/snap{,py}.*/ w,
/sys/fs/cgroup/devices/snap{,py}.*/tasks w,
/sys/fs/cgroup/devices/snap{,py}.*/devices.{allow,deny} w,
+ # cgroup: freezer
+ # Allow creating per-snap cgroup freezers and adding snap command (task)
+ # invocations to the freezer. This allows for reliably enumerating all
+ # running tasks for the snap.
+ /sys/fs/cgroup/freezer/ r,
+ /sys/fs/cgroup/freezer/snap.*/ w,
+ /sys/fs/cgroup/freezer/snap.*/tasks w,
+
# querying udev
/etc/udev/udev.conf r,
- /sys/devices/**/uevent r,
+ /sys/**/uevent r,
/lib/udev/snappy-app-dev ixr, # drop
/run/udev/** rw,
/{,usr/}bin/tr ixr,
@@ -96,9 +112,6 @@
# reading seccomp filters
/{tmp/snap.rootfs_*/,}var/lib/snapd/seccomp/bpf/*.bin r,
- # reading mount profiles
- /{tmp/snap.rootfs_*/,}var/lib/snapd/mount/*.fstab r,
-
# ensuring correct permissions in sc_quirk_create_writable_mimic
/{tmp/snap.rootfs_*/,}var/lib/ rw,
@@ -200,6 +213,9 @@
umount /var/lib/snapd/hostfs/proc/,
mount options=(rw rslave) -> /var/lib/snapd/hostfs/,
+ # Allow reading the os-release file (possibly a symlink to /usr/lib).
+ /{etc/,usr/lib/}os-release r,
+
# set up snap-specific private /tmp dir
capability chown,
/tmp/ w,
@@ -224,28 +240,6 @@
# NOTE: at this stage the /snap directory is stable as we have called
# pivot_root already.
- # Support mount profiles via the content interface. This should correspond
- # to permutations of $SNAP -> $SNAP for reading and $SNAP_{DATA,COMMON} ->
- # $SNAP_{DATA,COMMON} for both reading and writing.
- #
- # Note that:
- # /snap/*/*/**
- # is meant to mean:
- # /snap/$SNAP_NAME/$SNAP_REVISION/and-any-subdirectory
- # but:
- # /var/snap/*/**
- # is meant to mean:
- # /var/snap/$SNAP_NAME/$SNAP_REVISION/
- mount options=(ro bind) /snap/*/** -> /snap/*/*/**,
- mount options=(ro bind) /snap/*/** -> /var/snap/*/**,
- mount options=(rw bind) /var/snap/*/** -> /var/snap/*/**,
- mount options=(ro bind) /var/snap/*/** -> /var/snap/*/**,
- # But we don't want anyone to touch /snap/bin
- audit deny mount /snap/bin/** -> /**,
- audit deny mount /** -> /snap/bin/**,
- # Allow the content interface to bind fonts from the host filesystem
- mount options=(ro bind) /var/lib/snapd/hostfs/usr/share/fonts/ -> /snap/*/*/**,
-
# nvidia handling, glob needs /usr/** and the launcher must be
# able to bind mount the nvidia dir
/sys/module/nvidia/version r,
@@ -336,7 +330,6 @@
/run/snapd/ns/ rw,
/run/snapd/ns/*.lock rwk,
/run/snapd/ns/*.mnt rw,
- /run/snapd/ns/*.fstab rw,
ptrace (read, readby, tracedby) peer=@LIBEXECDIR@/snap-confine//mount-namespace-capture-helper,
@{PROC}/*/mountinfo r,
capability sys_chroot,
@@ -413,4 +406,120 @@
# Allow snap-confine to be killed
signal (receive) peer=unconfined,
+
+ # Allow executing snap-update-ns when...
+
+ # ...snap-confine is, conceptually, re-executing and uses snap-update-ns
+ # from the distribution package. This is also the location used when using
+ # the core/base snap on all-snap systems. The variants here represent
+ # various locations of libexecdir across distributions.
+ /usr/lib{,exec,64}/snapd/snap-update-ns Cxr -> snap_update_ns,
+
+ # ...snap-confine is not, conceptually, re-executing and uses
+ # snap-update-ns from the distribution package but we are already inside
+ # the constructed mount namespace so we must traverse "hostfs". The
+ # variants here represent various locations of libexecdir across
+ # distributions.
+ /var/lib/snapd/hostfs/usr/lib{,exec,64}/snapd/snap-update-ns Cxr -> snap_update_ns,
+
+ # ..snap-confine is, conceptually, re-executing and uses snap-update-ns
+ # from the core snap. Note that the location of the core snap varies from
+ # distribution to distribution. The variants here represent different
+ # locations of snap mount directory across distributions.
+ /{,var/lib/snapd/}snap/core/*/usr/lib/snapd/snap-update-ns Cxr -> snap_update_ns,
+
+ # ...snap-confine is, conceptually, re-executing and uses snap-update-ns
+ # from the core snap but we are already inside the constructed mount
+ # namespace. Here the apparmor kernel module re-constructs the path to
+ # snap-update-ns using the "hostfs" mount entry rather than the more
+ # "natural" /snap mount entry but we have no control over that. This is
+ # reported as (LP: #1716339). The variants here represent different
+ # locations of snap mount directory across distributions.
+ /var/lib/snapd/hostfs/{,var/lib/snapd/}snap/core/*/usr/lib/snapd/snap-update-ns Cxr -> snap_update_ns,
+
+ profile snap_update_ns (attach_disconnected) {
+ # The next four rules mirror those above. We want to be able to read
+ # and map snap-update-ns into memory but it may come from a variety of places.
+ /usr/lib{,exec,64}/snapd/snap-update-ns mr,
+ /var/lib/snapd/hostfs/usr/lib{,exec,64}/snapd/snap-update-ns mr,
+ /{,var/lib/snapd/}snap/core/*/usr/lib/snapd/snap-update-ns mr,
+ /var/lib/snapd/hostfs/{,var/lib/snapd/}snap/core/*/usr/lib/snapd/snap-update-ns mr,
+
+ # Allow reading the dynamic linker cache.
+ /etc/ld.so.cache r,
+ # Allow reading, mapping and executing the dynamic linker.
+ /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}ld-*.so mrix,
+ # Allow reading and mapping various parts of the standard library and
+ # dynamically loaded nss modules and what not.
+ /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libc{,-[0-9]*}.so* mr,
+ /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libpthread{,-[0-9]*}.so* mr,
+
+ # Allow reading the command line (snap-update-ns uses it in pre-Go bootstrap code).
+ @{PROC}/@{pid}/cmdline r,
+
+ # Allow reading the os-release file (possibly a symlink to /usr/lib).
+ /{etc/,usr/lib/}os-release r,
+
+ # Allow creating/grabbing various snapd lock files.
+ /run/snapd/lock/*.lock rwk,
+
+ # Allow reading stored mount namespaces,
+ /run/snapd/ns/ r,
+ /run/snapd/ns/*.mnt r,
+
+ # Allow reading per-snap desired mount profiles. Those are written by
+ # snapd and represent the desired layout and content connections.
+ /var/lib/snapd/mount/snap.*.fstab r,
+
+ # Allow reading and writing actual per-snap mount profiles. Note that
+ # the second rule is generic to allow our tmpfile-rename approach to
+ # writing them. Those are written by snap-update-ns and represent the
+ # actual layout at a given moment.
+ /run/snapd/ns/*.fstab rw,
+ /run/snapd/ns/*.fstab.* rw,
+
+ # NOTE: at this stage the /snap directory is stable as we have called
+ # pivot_root already.
+
+ # Needed to perform mount/unmounts.
+ capability sys_admin,
+
+ # Support mount profiles via the content interface. This should correspond
+ # to permutations of $SNAP -> $SNAP for reading and $SNAP_{DATA,COMMON} ->
+ # $SNAP_{DATA,COMMON} for both reading and writing.
+ #
+ # Note that:
+ # /snap/*/*/**
+ # is meant to mean:
+ # /snap/$SNAP_NAME/$SNAP_REVISION/and-any-subdirectory
+ # but:
+ # /var/snap/*/**
+ # is meant to mean:
+ # /var/snap/$SNAP_NAME/$SNAP_REVISION/
+ mount options=(ro bind) /snap/*/** -> /snap/*/*/**,
+ mount options=(ro bind) /snap/*/** -> /var/snap/*/**,
+ mount options=(rw bind) /var/snap/*/** -> /var/snap/*/**,
+ mount options=(ro bind) /var/snap/*/** -> /var/snap/*/**,
+
+ # Allow the content interface to bind fonts from the host filesystem
+ mount options=(ro bind) /var/lib/snapd/hostfs/usr/share/fonts/ -> /snap/*/*/**,
+ # Allow the desktop interface to bind fonts from the host filesystem
+ mount options=(ro bind) /var/lib/snapd/hostfs/usr/share/fonts/ -> /usr/share/fonts/,
+ mount options=(ro bind) /var/lib/snapd/hostfs/usr/local/share/fonts/ -> /usr/local/share/fonts/,
+ mount options=(ro bind) /var/lib/snapd/hostfs/var/cache/fontconfig/ -> /var/cache/fontconfig/,
+
+ # Allow unmounts matching possible mounts listed above.
+ umount /snap/*/*/**,
+ umount /var/snap/*/**,
+ umount /usr/share/fonts,
+ umount /usr/local/share/fonts,
+ umount /var/cache/fontconfig,
+
+ # But we don't want anyone to touch /snap/bin
+ audit deny mount /snap/bin/** -> /**,
+ audit deny mount /** -> /snap/bin/**,
+
+ # Allow the content interface to bind fonts from the host filesystem
+ mount options=(ro bind) /var/lib/snapd/hostfs/usr/share/fonts/ -> /snap/*/*/**,
+ }
}
diff -Nru snapd-2.28.5/cmd/snap-confine/snap-confine-args.h snapd-2.29.3/cmd/snap-confine/snap-confine-args.h
--- snapd-2.28.5/cmd/snap-confine/snap-confine-args.h 2017-10-13 20:09:56.000000000 +0000
+++ snapd-2.29.3/cmd/snap-confine/snap-confine-args.h 2017-10-23 06:17:27.000000000 +0000
@@ -77,7 +77,7 @@
* Cleanup an error with sc_args_free()
*
* This function is designed to be used with
- * __attribute__((cleanup(sc_cleanup_args))).
+ * SC_CLEANUP(sc_cleanup_args).
**/
void sc_cleanup_args(struct sc_args **ptr);
diff -Nru snapd-2.28.5/cmd/snap-confine/snap-confine-args-test.c snapd-2.29.3/cmd/snap-confine/snap-confine-args-test.c
--- snapd-2.28.5/cmd/snap-confine/snap-confine-args-test.c 2017-10-13 20:09:56.000000000 +0000
+++ snapd-2.29.3/cmd/snap-confine/snap-confine-args-test.c 2017-10-23 06:17:27.000000000 +0000
@@ -17,6 +17,7 @@
#include "snap-confine-args.h"
#include "snap-confine-args.c"
+#include "../libsnap-confine-private/cleanup-funcs.h"
#include
@@ -76,8 +77,8 @@
static void test_sc_nonfatal_parse_args__typical()
{
// Test that typical invocation of snap-confine is parsed correctly.
- struct sc_error *err __attribute__ ((cleanup(sc_cleanup_error))) = NULL;
- struct sc_args *args __attribute__ ((cleanup(sc_cleanup_args))) = NULL;
+ struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL;
+ struct sc_args *args SC_CLEANUP(sc_cleanup_args) = NULL;
int argc;
char **argv;
@@ -109,7 +110,7 @@
static void test_sc_cleanup_args()
{
// Check that NULL argument parser can be cleaned up
- struct sc_error *err __attribute__ ((cleanup(sc_cleanup_error))) = NULL;
+ struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL;
struct sc_args *args = NULL;
sc_cleanup_args(&args);
@@ -130,8 +131,8 @@
static void test_sc_nonfatal_parse_args__typical_classic()
{
// Test that typical invocation of snap-confine is parsed correctly.
- struct sc_error *err __attribute__ ((cleanup(sc_cleanup_error))) = NULL;
- struct sc_args *args __attribute__ ((cleanup(sc_cleanup_args))) = NULL;
+ struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL;
+ struct sc_args *args SC_CLEANUP(sc_cleanup_args) = NULL;
int argc;
char **argv;
@@ -165,8 +166,8 @@
// Test that typical legacy invocation of snap-confine via the
// ubuntu-core-launcher symlink, with duplicated security tag, is parsed
// correctly.
- struct sc_error *err __attribute__ ((cleanup(sc_cleanup_error))) = NULL;
- struct sc_args *args __attribute__ ((cleanup(sc_cleanup_args))) = NULL;
+ struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL;
+ struct sc_args *args SC_CLEANUP(sc_cleanup_args) = NULL;
int argc;
char **argv;
@@ -198,8 +199,8 @@
static void test_sc_nonfatal_parse_args__version()
{
// Test that snap-confine --version is detected.
- struct sc_error *err __attribute__ ((cleanup(sc_cleanup_error))) = NULL;
- struct sc_args *args __attribute__ ((cleanup(sc_cleanup_args))) = NULL;
+ struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL;
+ struct sc_args *args SC_CLEANUP(sc_cleanup_args) = NULL;
int argc;
char **argv;
@@ -228,8 +229,8 @@
static void test_sc_nonfatal_parse_args__evil_input()
{
// Check that calling without any arguments is reported as error.
- struct sc_error *err __attribute__ ((cleanup(sc_cleanup_error))) = NULL;
- struct sc_args *args __attribute__ ((cleanup(sc_cleanup_args))) = NULL;
+ struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL;
+ struct sc_args *args SC_CLEANUP(sc_cleanup_args) = NULL;
// NULL argcp/argvp attack
args = sc_nonfatal_parse_args(NULL, NULL, &err);
@@ -268,8 +269,8 @@
static void test_sc_nonfatal_parse_args__nothing_to_parse()
{
// Check that calling without any arguments is reported as error.
- struct sc_error *err __attribute__ ((cleanup(sc_cleanup_error))) = NULL;
- struct sc_args *args __attribute__ ((cleanup(sc_cleanup_args))) = NULL;
+ struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL;
+ struct sc_args *args SC_CLEANUP(sc_cleanup_args) = NULL;
int argc;
char **argv;
@@ -287,8 +288,8 @@
static void test_sc_nonfatal_parse_args__no_security_tag()
{
// Check that lack of security tag is reported as error.
- struct sc_error *err __attribute__ ((cleanup(sc_cleanup_error))) = NULL;
- struct sc_args *args __attribute__ ((cleanup(sc_cleanup_args))) = NULL;
+ struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL;
+ struct sc_args *args SC_CLEANUP(sc_cleanup_args) = NULL;
int argc;
char **argv;
@@ -309,8 +310,8 @@
static void test_sc_nonfatal_parse_args__no_executable()
{
// Check that lack of security tag is reported as error.
- struct sc_error *err __attribute__ ((cleanup(sc_cleanup_error))) = NULL;
- struct sc_args *args __attribute__ ((cleanup(sc_cleanup_args))) = NULL;
+ struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL;
+ struct sc_args *args SC_CLEANUP(sc_cleanup_args) = NULL;
int argc;
char **argv;
@@ -331,8 +332,8 @@
static void test_sc_nonfatal_parse_args__unknown_option()
{
// Check that unrecognized option switch is reported as error.
- struct sc_error *err __attribute__ ((cleanup(sc_cleanup_error))) = NULL;
- struct sc_args *args __attribute__ ((cleanup(sc_cleanup_args))) = NULL;
+ struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL;
+ struct sc_args *args SC_CLEANUP(sc_cleanup_args) = NULL;
int argc;
char **argv;
@@ -360,8 +361,7 @@
"--frozbonicator", NULL);
// Call sc_nonfatal_parse_args() without an error handle
- struct sc_args *args
- __attribute__ ((cleanup(sc_cleanup_args))) = NULL;
+ struct sc_args *args SC_CLEANUP(sc_cleanup_args) = NULL;
args = sc_nonfatal_parse_args(&argc, &argv, NULL);
(void)args;
@@ -379,8 +379,8 @@
static void test_sc_nonfatal_parse_args__base_snap()
{
// Check that --base specifies the name of the base snap.
- struct sc_error *err __attribute__ ((cleanup(sc_cleanup_error))) = NULL;
- struct sc_args *args __attribute__ ((cleanup(sc_cleanup_args))) = NULL;
+ struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL;
+ struct sc_args *args SC_CLEANUP(sc_cleanup_args) = NULL;
int argc;
char **argv;
@@ -407,8 +407,8 @@
static void test_sc_nonfatal_parse_args__base_snap__missing_arg()
{
// Check that --base specifies the name of the base snap.
- struct sc_error *err __attribute__ ((cleanup(sc_cleanup_error))) = NULL;
- struct sc_args *args __attribute__ ((cleanup(sc_cleanup_args))) = NULL;
+ struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL;
+ struct sc_args *args SC_CLEANUP(sc_cleanup_args) = NULL;
int argc;
char **argv;
@@ -429,8 +429,8 @@
static void test_sc_nonfatal_parse_args__base_snap__twice()
{
// Check that --base specifies the name of the base snap.
- struct sc_error *err __attribute__ ((cleanup(sc_cleanup_error))) = NULL;
- struct sc_args *args __attribute__ ((cleanup(sc_cleanup_args))) = NULL;
+ struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL;
+ struct sc_args *args SC_CLEANUP(sc_cleanup_args) = NULL;
int argc;
char **argv;
diff -Nru snapd-2.28.5/cmd/snap-confine/snap-confine.c snapd-2.29.3/cmd/snap-confine/snap-confine.c
--- snapd-2.28.5/cmd/snap-confine/snap-confine.c 2017-10-13 21:25:17.000000000 +0000
+++ snapd-2.29.3/cmd/snap-confine/snap-confine.c 2017-10-23 06:17:27.000000000 +0000
@@ -26,6 +26,7 @@
#include
#include
+#include "../libsnap-confine-private/cgroup-freezer-support.h"
#include "../libsnap-confine-private/classic.h"
#include "../libsnap-confine-private/cleanup-funcs.h"
#include "../libsnap-confine-private/locking.h"
@@ -69,7 +70,7 @@
// https://forum.snapcraft.io/t/weird-udev-enumerate-error/2360/17
void sc_maybe_fixup_udev()
{
- glob_t glob_res __attribute__ ((cleanup(globfree))) = {
+ glob_t glob_res SC_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);
@@ -95,7 +96,7 @@
{
// Use our super-defensive parser to figure out what we've been asked to do.
struct sc_error *err = NULL;
- struct sc_args *args __attribute__ ((cleanup(sc_cleanup_args))) = NULL;
+ struct sc_args *args SC_CLEANUP(sc_cleanup_args) = NULL;
args = sc_nonfatal_parse_args(&argc, &argv, &err);
sc_die_on_error(err);
@@ -141,11 +142,10 @@
}
#endif
- char *snap_context __attribute__ ((cleanup(sc_cleanup_string))) = NULL;
+ char *snap_context SC_CLEANUP(sc_cleanup_string) = NULL;
// Do no get snap context value if running a hook (we don't want to overwrite hook's SNAP_COOKIE)
if (!sc_is_hook_security_tag(security_tag)) {
- struct sc_error *err
- __attribute__ ((cleanup(sc_cleanup_error))) = NULL;
+ struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL;
snap_context = sc_cookie_get_from_snapd(snap_name, &err);
if (err != NULL) {
error("%s\n", sc_error_msg(err));
@@ -220,6 +220,12 @@
// old version
sc_maybe_fixup_permissions();
sc_maybe_fixup_udev();
+
+ // Associate each snap process with a dedicated snap freezer
+ // control group. This simplifies testing if any processes
+ // belonging to a given snap are still alive.
+ // See the documentation of the function for details.
+ sc_cgroup_freezer_join(snap_name, getpid());
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.28.5/cmd/snap-confine/snap-confine.rst snapd-2.29.3/cmd/snap-confine/snap-confine.rst
--- snapd-2.28.5/cmd/snap-confine/snap-confine.rst 2017-08-18 13:48:10.000000000 +0000
+++ snapd-2.29.3/cmd/snap-confine/snap-confine.rst 2017-10-23 06:17:27.000000000 +0000
@@ -7,27 +7,37 @@
-----------------------------------------------
:Author: zygmunt.krynicki@canonical.com
-:Date: 2016-10-05
+:Date: 2017-09-18
:Copyright: Canonical Ltd.
-:Version: 1.0.43
-:Manual section: 5
+:Version: 2.28
+:Manual section: 1
:Manual group: snappy
SYNOPSIS
========
- snap-confine SECURITY_TAG COMMAND [...ARGUMENTS]
+ snap-confine [--classic] [--base BASE] SECURITY_TAG COMMAND [...ARGUMENTS]
DESCRIPTION
===========
-The `snap-confine` is a program used internally by `snapd` to construct a
-confined execution environment for snap applications.
+The `snap-confine` is a program used internally by `snapd` to construct the
+execution environment for snap applications.
OPTIONS
=======
-The `snap-confine` program does not support any options.
+The `snap-confine` program accepts two options:
+
+ `--classic` requests the so-called _classic_ _confinement_ in which
+ applications are not confined at all (like in classic systems, hence the
+ name). This disables the use of a dedicated, per-snap mount namespace. The
+ `snapd` service generates permissive apparmor and seccomp profiles that
+ allow everything.
+
+ `--base BASE` directs snap-confine to use the given base snap as the root
+ filesystem. If omitted it defaults to the `core` snap. This is derived from
+ snap meta-data by `snapd` when starting the application process.
FEATURES
========
@@ -38,11 +48,12 @@
`snap-confine` switches to the apparmor profile `$SECURITY_TAG`. The profile is
**mandatory** and `snap-confine` will refuse to run without it.
-has to be loaded into the kernel prior to using `snap-confine`. Typically this
-is arranged for by `snapd`. The profile contains rich description of what the
-application process is allowed to do, this includes system calls, file paths,
-access patterns, linux capabilities, etc. The apparmor profile can also do
-extensive dbus mediation. Refer to apparmor documentation for more details.
+The profile has to be loaded into the kernel prior to using `snap-confine`.
+Typically this is arranged for by `snapd`. The profile contains rich
+description of what the application process is allowed to do, this includes
+system calls, file paths, access patterns, linux capabilities, etc. The
+apparmor profile can also do extensive dbus mediation. Refer to apparmor
+documentation for more details.
Seccomp profiles
----------------
@@ -53,11 +64,10 @@
file contains the seccomp bpf binary program that is loaded into the
kernel by snap-confine.
-The file is generated with the /usr/lib/snapd/snap-seccomp compiler
-from the `$SECURITY_TAG.src` file that uses a custom syntax that
-describes the set of allowed system calls and optionally their
-arguments. The profile is then used to confine the started
-application.
+The file is generated with the `/usr/lib/snapd/snap-seccomp` compiler from the
+`$SECURITY_TAG.src` file that uses a custom syntax that describes the set of
+allowed system calls and optionally their arguments. The profile is then used
+to confine the started application.
As a security precaution disallowed system calls cause the started application
executable to be killed by the kernel. In the future this restriction may be
@@ -66,15 +76,20 @@
Mount profiles
--------------
-`snap-confine` looks for the `/var/lib/snapd/mount/$SECURITY_TAG.fstab` file.
-If present it is read, parsed and treated like a typical `fstab(5)` file.
-The mount directives listed there are executed in order. All directives must
-succeed as any failure will abort execution.
+`snap-confine` uses a helper process, `snap-update-ns`, to apply the mount
+namespace profile to freshly constructed mount namespace. That tool looks for
+the `/var/lib/snapd/mount/snap.$SNAP_NAME.fstab` file. If present it is read,
+parsed and treated like a mostly-typical `fstab(5)` file. The mount directives
+listed there are executed in order. All directives must succeed as any failure
+will abort execution.
By default all mount entries start with the following flags: `bind`, `ro`,
`nodev`, `nosuid`. Some of those flags can be reversed by an appropriate
option (e.g. `rw` can cause the mount point to be writable).
+Certain additional features are enabled and conveyed through the use of mount
+options prefixed with `x-snapd-`.
+
As a security precaution only `bind` mounts are supported at this time.
Quirks
@@ -85,7 +100,7 @@
useful and important) have grown to rely on. This section documents the list of
quirks:
-- The /var/lib/lxd directory, if it exists on the host, is made available in
+- The `/var/lib/lxd` directory, if it exists on the host, is made available in
the execution environment. This allows various snaps, while running in
devmode, to access the LXD socket. LP: #1613845
@@ -127,9 +142,9 @@
FILES
=====
-`snap-confine` uses the following files:
+`snap-confine` and `snap-update-ns` use the following files:
-`/var/lib/snapd/mount/*.fstab`:
+`/var/lib/snapd/mount/snap.*.fstab`:
Description of the mount profile.
diff -Nru snapd-2.28.5/cmd/snap-confine/udev-support.c snapd-2.29.3/cmd/snap-confine/udev-support.c
--- snapd-2.28.5/cmd/snap-confine/udev-support.c 2017-10-13 20:09:56.000000000 +0000
+++ snapd-2.29.3/cmd/snap-confine/udev-support.c 2017-11-09 09:07:17.000000000 +0000
@@ -50,12 +50,17 @@
if (real_uid != 0 && effective_uid == 0)
if (setuid(0) != 0)
die("setuid failed");
- char buf[64];
+ char buf[64] = { 0 };
// pass snappy-add-dev an empty environment so the
// user-controlled environment can't be used to subvert
// snappy-add-dev
char *env[] = { NULL };
- sc_must_snprintf(buf, sizeof(buf), "%u:%u", major, minor);
+ if (minor == UINT_MAX) {
+ sc_must_snprintf(buf, sizeof(buf), "%u:*", major);
+ } else {
+ 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",
@@ -177,7 +182,7 @@
die("snappy_udev->tagname has invalid length");
// create devices cgroup controller
- char cgroup_dir[PATH_MAX];
+ char cgroup_dir[PATH_MAX] = { 0 };
sc_must_snprintf(cgroup_dir, sizeof(cgroup_dir),
"/sys/fs/cgroup/devices/%s/", security_tag);
@@ -186,11 +191,11 @@
die("mkdir failed");
// move ourselves into it
- char cgroup_file[PATH_MAX];
+ char cgroup_file[PATH_MAX] = { 0 };
sc_must_snprintf(cgroup_file, sizeof(cgroup_file), "%s%s", cgroup_dir,
"tasks");
- char buf[128];
+ char buf[128] = { 0 };
sc_must_snprintf(buf, sizeof(buf), "%i", getpid());
write_string_to_file(cgroup_file, buf);
@@ -206,6 +211,19 @@
for (int i = 0; static_devices[i] != NULL; i++)
run_snappy_app_dev_add(udev_s, static_devices[i]);
+ // add glob for current and future PTY slaves. We unconditionally add
+ // them since we use a devpts newinstance. Unix98 PTY slaves major
+ // are 136-143.
+ // https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/devices.txt
+ for (unsigned pty_major = 136; pty_major <= 143; pty_major++) {
+ // '/dev/pts/slaves' is only used for debugging and by
+ // /lib/udev/snappy-app-dev to determine if it is a block
+ // device, so just use something to indicate what the
+ // addition is for
+ _run_snappy_app_dev_add_majmin(udev_s, "/dev/pts/slaves",
+ pty_major, UINT_MAX);
+ }
+
// 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).
@@ -256,6 +274,13 @@
MAJOR(sbuf.st_rdev),
MINOR(sbuf.st_rdev));
}
+ // /dev/uhid isn't represented in sysfs, so add it to the device cgroup
+ // if it exists and let AppArmor handle the mediation
+ if (stat("/dev/uhid", &sbuf) == 0) {
+ _run_snappy_app_dev_add_majmin(udev_s, "/dev/uhid",
+ 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.28.5/cmd/snap-discard-ns/snap-discard-ns.c snapd-2.29.3/cmd/snap-discard-ns/snap-discard-ns.c
--- snapd-2.28.5/cmd/snap-discard-ns/snap-discard-ns.c 2017-10-13 20:09:56.000000000 +0000
+++ snapd-2.29.3/cmd/snap-discard-ns/snap-discard-ns.c 2017-10-23 06:17:27.000000000 +0000
@@ -39,7 +39,7 @@
sc_close_ns_group(group);
}
// Unlink the current mount profile, if any.
- char profile_path[PATH_MAX];
+ char profile_path[PATH_MAX] = { 0 };
sc_must_snprintf(profile_path, sizeof(profile_path),
"/run/snapd/ns/snap.%s.fstab", snap_name);
if (unlink(profile_path) < 0) {
diff -Nru snapd-2.28.5/cmd/snap-exec/export_test.go snapd-2.29.3/cmd/snap-exec/export_test.go
--- snapd-2.28.5/cmd/snap-exec/export_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/cmd/snap-exec/export_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -0,0 +1,51 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2014-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 main
+
+var (
+ ExpandEnvCmdArgs = expandEnvCmdArgs
+ FindCommand = findCommand
+ ParseArgs = parseArgs
+ Run = run
+ ExecApp = execApp
+ ExecHook = execHook
+)
+
+func MockSyscallExec(f func(argv0 string, argv []string, envv []string) (err error)) func() {
+ origSyscallExec := syscallExec
+ syscallExec = f
+ return func() {
+ syscallExec = origSyscallExec
+ }
+}
+
+func SetOptsCommand(s string) {
+ opts.Command = s
+}
+func GetOptsCommand() string {
+ return opts.Command
+}
+
+func SetOptsHook(s string) {
+ opts.Hook = s
+}
+func GetOptsHook() string {
+ return opts.Hook
+}
diff -Nru snapd-2.28.5/cmd/snap-exec/main.go snapd-2.29.3/cmd/snap-exec/main.go
--- snapd-2.28.5/cmd/snap-exec/main.go 2017-09-20 07:05:01.000000000 +0000
+++ snapd-2.29.3/cmd/snap-exec/main.go 2017-10-23 06:17:27.000000000 +0000
@@ -31,6 +31,7 @@
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/snapenv"
)
// for the tests
@@ -42,6 +43,11 @@
Hook string `long:"hook" description:"hook to run" hidden:"yes"`
}
+func init() {
+ // plug/slot sanitization not used nor possible from snap-exec, make it no-op
+ snap.SanitizePlugsSlots = func(snapInfo *snap.Info) {}
+}
+
func main() {
if err := run(); err != nil {
fmt.Fprintf(os.Stderr, "cannot snap-exec: %s\n", err)
@@ -83,10 +89,10 @@
// Now actually handle the dispatching
if opts.Hook != "" {
- return snapExecHook(snapApp, revision, opts.Hook)
+ return execHook(snapApp, revision, opts.Hook)
}
- return snapExecApp(snapApp, revision, opts.Command, extraArgs)
+ return execApp(snapApp, revision, opts.Command, extraArgs)
}
const defaultShell = "/bin/bash"
@@ -118,7 +124,22 @@
return cmd, nil
}
-func snapExecApp(snapApp, revision, command string, args []string) error {
+// expandEnvCmdArgs takes the string list of commandline arguments
+// and expands any $VAR with the given var from the env argument.
+func expandEnvCmdArgs(args []string, env map[string]string) []string {
+ cmdArgs := make([]string, 0, len(args))
+ for _, arg := range args {
+ maybeExpanded := os.Expand(arg, func(k string) string {
+ return env[k]
+ })
+ if maybeExpanded != "" {
+ cmdArgs = append(cmdArgs, maybeExpanded)
+ }
+ }
+ return cmdArgs
+}
+
+func execApp(snapApp, revision, command string, args []string) error {
rev, err := snap.ParseRevision(revision)
if err != nil {
return fmt.Errorf("cannot parse revision %q: %s", revision, err)
@@ -141,15 +162,25 @@
if err != nil {
return err
}
+
+ // build the environment from the yaml, translating TMPDIR and
+ // similar variables back from where they were hidden when
+ // invoking the setuid snap-confine.
+ env := []string{}
+ for _, kv := range os.Environ() {
+ if strings.HasPrefix(kv, snapenv.PreservedUnsafePrefix) {
+ kv = kv[len(snapenv.PreservedUnsafePrefix):]
+ }
+ env = append(env, kv)
+ }
+ env = append(env, osutil.SubstituteEnv(app.Env())...)
+
// strings.Split() is ok here because we validate all app fields
// and the whitelist is pretty strict (see
// snap/validate.go:appContentWhitelist)
- cmdArgv := strings.Split(cmdAndArgs, " ")
- cmd := cmdArgv[0]
- cmdArgs := cmdArgv[1:]
-
- // build the environment from the yaml
- env := append(os.Environ(), osutil.SubstituteEnv(app.Env())...)
+ tmpArgv := strings.Split(cmdAndArgs, " ")
+ cmd := tmpArgv[0]
+ cmdArgs := expandEnvCmdArgs(tmpArgv[1:], osutil.EnvMap(env))
// run the command
fullCmd := filepath.Join(app.Snap.MountDir(), cmd)
@@ -174,7 +205,7 @@
return nil
}
-func snapExecHook(snapName, revision, hookName string) error {
+func execHook(snapName, revision, hookName string) error {
rev, err := snap.ParseRevision(revision)
if err != nil {
return err
diff -Nru snapd-2.28.5/cmd/snap-exec/main_test.go snapd-2.29.3/cmd/snap-exec/main_test.go
--- snapd-2.28.5/cmd/snap-exec/main_test.go 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/cmd/snap-exec/main_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -17,7 +17,7 @@
*
*/
-package main
+package main_test
import (
"fmt"
@@ -25,7 +25,6 @@
"os"
"os/exec"
"path/filepath"
- "syscall"
"testing"
. "gopkg.in/check.v1"
@@ -34,6 +33,8 @@
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snaptest"
"github.com/snapcore/snapd/testutil"
+
+ snapExec "github.com/snapcore/snapd/cmd/snap-exec"
)
// Hook up check.v1 into the "go test" runner
@@ -45,12 +46,11 @@
func (s *snapExecSuite) SetUpTest(c *C) {
// clean previous parse runs
- opts.Command = ""
- opts.Hook = ""
+ snapExec.SetOptsCommand("")
+ snapExec.SetOptsHook("")
}
func (s *snapExecSuite) TearDown(c *C) {
- syscallExec = syscall.Exec
dirs.SetRootDir("/")
}
@@ -58,7 +58,7 @@
version: 1.0
apps:
app:
- command: run-app cmd-arg1
+ command: run-app cmd-arg1 $SNAP_DATA
stop-command: stop-app
post-stop-command: post-stop-app
environment:
@@ -86,13 +86,13 @@
func (s *snapExecSuite) TestInvalidCombinedParameters(c *C) {
invalidParameters := []string{"--hook=hook-name", "--command=command-name", "snap-name"}
- _, _, err := parseArgs(invalidParameters)
+ _, _, err := snapExec.ParseArgs(invalidParameters)
c.Check(err, ErrorMatches, ".*cannot use --hook and --command together.*")
}
func (s *snapExecSuite) TestInvalidExtraParameters(c *C) {
invalidParameters := []string{"--hook=hook-name", "snap-name", "foo", "bar"}
- _, _, err := parseArgs(invalidParameters)
+ _, _, err := snapExec.ParseArgs(invalidParameters)
c.Check(err, ErrorMatches, ".*too many arguments for hook \"hook-name\": snap-name foo bar.*")
}
@@ -104,11 +104,11 @@
cmd string
expected string
}{
- {cmd: "", expected: `run-app cmd-arg1`},
+ {cmd: "", expected: `run-app cmd-arg1 $SNAP_DATA`},
{cmd: "stop", expected: "stop-app"},
{cmd: "post-stop", expected: "post-stop-app"},
} {
- cmd, err := findCommand(info.Apps["app"], t.cmd)
+ cmd, err := snapExec.FindCommand(info.Apps["app"], t.cmd)
c.Check(err, IsNil)
c.Check(cmd, Equals, t.expected)
}
@@ -118,7 +118,7 @@
info, err := snap.InfoFromSnapYaml(mockYaml)
c.Assert(err, IsNil)
- _, err = findCommand(info.Apps["app"], "xxx")
+ _, err = snapExec.FindCommand(info.Apps["app"], "xxx")
c.Check(err, ErrorMatches, `cannot use "xxx" command`)
}
@@ -126,7 +126,7 @@
info, err := snap.InfoFromSnapYaml(mockYaml)
c.Assert(err, IsNil)
- _, err = findCommand(info.Apps["nostop"], "stop")
+ _, err = snapExec.FindCommand(info.Apps["nostop"], "stop")
c.Check(err, ErrorMatches, `no "stop" command found for "nostop"`)
}
@@ -139,15 +139,16 @@
execArgv0 := ""
execArgs := []string{}
execEnv := []string{}
- syscallExec = func(argv0 string, argv []string, env []string) error {
+ restore := snapExec.MockSyscallExec(func(argv0 string, argv []string, env []string) error {
execArgv0 = argv0
execArgs = argv
execEnv = env
return nil
- }
+ })
+ defer restore()
// launch and verify its run the right way
- err := snapExecApp("snapname.app", "42", "stop", []string{"arg1", "arg2"})
+ err := snapExec.ExecApp("snapname.app", "42", "stop", []string{"arg1", "arg2"})
c.Assert(err, IsNil)
c.Check(execArgv0, Equals, fmt.Sprintf("%s/snapname/42/stop-app", dirs.SnapMountDir))
c.Check(execArgs, DeepEquals, []string{execArgv0, "arg1", "arg2"})
@@ -164,14 +165,15 @@
execArgv0 := ""
execArgs := []string{}
- syscallExec = func(argv0 string, argv []string, env []string) error {
+ restore := snapExec.MockSyscallExec(func(argv0 string, argv []string, env []string) error {
execArgv0 = argv0
execArgs = argv
return nil
- }
+ })
+ defer restore()
// launch and verify it ran correctly
- err := snapExecHook("snapname", "42", "configure")
+ err := snapExec.ExecHook("snapname", "42", "configure")
c.Assert(err, IsNil)
c.Check(execArgv0, Equals, fmt.Sprintf("%s/snapname/42/meta/hooks/configure", dirs.SnapMountDir))
c.Check(execArgs, DeepEquals, []string{execArgv0})
@@ -183,26 +185,26 @@
Revision: snap.R("42"),
})
- err := snapExecHook("snapname", "42", "missing-hook")
+ err := snapExec.ExecHook("snapname", "42", "missing-hook")
c.Assert(err, NotNil)
c.Assert(err, ErrorMatches, "cannot find hook \"missing-hook\" in \"snapname\"")
}
func (s *snapExecSuite) TestSnapExecIgnoresUnknownArgs(c *C) {
- snapApp, rest, err := parseArgs([]string{"--command=shell", "snapname.app", "--arg1", "arg2"})
+ snapApp, rest, err := snapExec.ParseArgs([]string{"--command=shell", "snapname.app", "--arg1", "arg2"})
c.Assert(err, IsNil)
- c.Assert(opts.Command, Equals, "shell")
+ c.Assert(snapExec.GetOptsCommand(), Equals, "shell")
c.Assert(snapApp, DeepEquals, "snapname.app")
c.Assert(rest, DeepEquals, []string{"--arg1", "arg2"})
}
func (s *snapExecSuite) TestSnapExecErrorsOnUnknown(c *C) {
- _, _, err := parseArgs([]string{"--command=shell", "--unknown", "snapname.app", "--arg1", "arg2"})
+ _, _, err := snapExec.ParseArgs([]string{"--command=shell", "--unknown", "snapname.app", "--arg1", "arg2"})
c.Check(err, ErrorMatches, "unknown flag `unknown'")
}
func (s *snapExecSuite) TestSnapExecErrorsOnMissingSnapApp(c *C) {
- _, _, err := parseArgs([]string{"--command=shell"})
+ _, _, err := snapExec.ParseArgs([]string{"--command=shell"})
c.Check(err, ErrorMatches, "need the application to run as argument")
}
@@ -227,11 +229,12 @@
// we can not use the real syscall.execv here because it would
// replace the entire test :)
- syscallExec = actuallyExec
+ restore := snapExec.MockSyscallExec(actuallyExec)
+ defer restore()
// run it
os.Args = []string{"snap-exec", "snapname.app", "foo", "--bar=baz", "foobar"}
- err = run()
+ err = snapExec.Run()
c.Assert(err, IsNil)
output, err := ioutil.ReadFile(canaryFile)
@@ -268,11 +271,12 @@
// we can not use the real syscall.execv here because it would
// replace the entire test :)
- syscallExec = actuallyExec
+ restore := snapExec.MockSyscallExec(actuallyExec)
+ defer restore()
// run it
os.Args = []string{"snap-exec", "--hook=configure", "snapname"}
- err := run()
+ err := snapExec.Run()
c.Assert(err, IsNil)
output, err := ioutil.ReadFile(canaryFile)
@@ -299,17 +303,81 @@
execArgv0 := ""
execArgs := []string{}
execEnv := []string{}
- syscallExec = func(argv0 string, argv []string, env []string) error {
+ restore := snapExec.MockSyscallExec(func(argv0 string, argv []string, env []string) error {
execArgv0 = argv0
execArgs = argv
execEnv = env
return nil
- }
+ })
+ defer restore()
// launch and verify its run the right way
- err := snapExecApp("snapname.app", "42", "shell", []string{"-c", "echo foo"})
+ err := snapExec.ExecApp("snapname.app", "42", "shell", []string{"-c", "echo foo"})
c.Assert(err, IsNil)
c.Check(execArgv0, Equals, "/bin/bash")
c.Check(execArgs, DeepEquals, []string{execArgv0, "-c", "echo foo"})
c.Check(execEnv, testutil.Contains, "LD_LIBRARY_PATH=/some/path/lib")
}
+
+func (s *snapExecSuite) TestSnapExecAppIntegrationWithVars(c *C) {
+ dirs.SetRootDir(c.MkDir())
+ snaptest.MockSnap(c, string(mockYaml), string(mockContents), &snap.SideInfo{
+ Revision: snap.R("42"),
+ })
+
+ execArgv0 := ""
+ execArgs := []string{}
+ execEnv := []string{}
+ restore := snapExec.MockSyscallExec(func(argv0 string, argv []string, env []string) error {
+ execArgv0 = argv0
+ execArgs = argv
+ execEnv = env
+ return nil
+ })
+ defer restore()
+
+ // setup env
+ os.Setenv("SNAP_DATA", "/var/snap/snapname/42")
+ defer os.Unsetenv("SNAP_DATA")
+
+ // launch and verify its run the right way
+ err := snapExec.ExecApp("snapname.app", "42", "", []string{"user-arg1"})
+ c.Assert(err, IsNil)
+ c.Check(execArgv0, Equals, fmt.Sprintf("%s/snapname/42/run-app", dirs.SnapMountDir))
+ c.Check(execArgs, DeepEquals, []string{execArgv0, "cmd-arg1", "/var/snap/snapname/42", "user-arg1"})
+ c.Check(execEnv, testutil.Contains, "BASE_PATH=/some/path")
+ c.Check(execEnv, testutil.Contains, "LD_LIBRARY_PATH=/some/path/lib")
+ c.Check(execEnv, testutil.Contains, fmt.Sprintf("MY_PATH=%s", os.Getenv("PATH")))
+}
+
+func (s *snapExecSuite) TestSnapExecExpandEnvCmdArgs(c *C) {
+ for _, t := range []struct {
+ args []string
+ env map[string]string
+ expected []string
+ }{
+ {
+ args: []string{"foo"},
+ env: nil,
+ expected: []string{"foo"},
+ },
+ {
+ args: []string{"$var"},
+ env: map[string]string{"var": "value"},
+ expected: []string{"value"},
+ },
+ {
+ args: []string{"foo", "$not_existing"},
+ env: nil,
+ expected: []string{"foo"},
+ },
+ {
+ args: []string{"foo", "$var", "baz"},
+ env: map[string]string{"var": "bar", "unrelated": "env"},
+ expected: []string{"foo", "bar", "baz"},
+ },
+ } {
+ c.Check(snapExec.ExpandEnvCmdArgs(t.args, t.env), DeepEquals, t.expected)
+
+ }
+}
diff -Nru snapd-2.28.5/cmd/snap-repair/cmd_done_retry_skip.go snapd-2.29.3/cmd/snap-repair/cmd_done_retry_skip.go
--- snapd-2.28.5/cmd/snap-repair/cmd_done_retry_skip.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/cmd/snap-repair/cmd_done_retry_skip.go 2017-10-23 06:17:27.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 main
+
+import (
+ "fmt"
+ "os"
+ "strconv"
+)
+
+func init() {
+ cmd, err := parser.AddCommand("done", "Signal repair is done", "", &cmdDone{})
+ if err != nil {
+
+ panic(err)
+ }
+ cmd.Hidden = true
+
+ cmd, err = parser.AddCommand("skip", "Signal repair should be skipped", "", &cmdSkip{})
+ if err != nil {
+ panic(err)
+ }
+ cmd.Hidden = true
+
+ cmd, err = parser.AddCommand("retry", "Signal repair must be retried next time", "", &cmdRetry{})
+ if err != nil {
+ panic(err)
+ }
+ cmd.Hidden = true
+}
+
+func writeToStatusFD(msg string) error {
+ statusFdStr := os.Getenv("SNAP_REPAIR_STATUS_FD")
+ if statusFdStr == "" {
+ return fmt.Errorf("cannot find SNAP_REPAIR_STATUS_FD environment")
+ }
+ fd, err := strconv.Atoi(statusFdStr)
+ if err != nil {
+ return fmt.Errorf("cannot parse SNAP_REPAIR_STATUS_FD environment: %s", err)
+ }
+ f := os.NewFile(uintptr(fd), "")
+ defer f.Close()
+ if _, err := f.Write([]byte(msg + "\n")); err != nil {
+ return err
+ }
+ return nil
+}
+
+type cmdDone struct{}
+
+func (c *cmdDone) Execute(args []string) error {
+ return writeToStatusFD("done")
+}
+
+type cmdSkip struct{}
+
+func (c *cmdSkip) Execute([]string) error {
+ return writeToStatusFD("skip")
+}
+
+type cmdRetry struct{}
+
+func (c *cmdRetry) Execute([]string) error {
+ return writeToStatusFD("retry")
+}
diff -Nru snapd-2.28.5/cmd/snap-repair/cmd_done_retry_skip_test.go snapd-2.29.3/cmd/snap-repair/cmd_done_retry_skip_test.go
--- snapd-2.28.5/cmd/snap-repair/cmd_done_retry_skip_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/cmd/snap-repair/cmd_done_retry_skip_test.go 2017-10-23 06:17:27.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 main_test
+
+import (
+ "io/ioutil"
+ "os"
+ "strconv"
+ "syscall"
+
+ . "gopkg.in/check.v1"
+
+ repair "github.com/snapcore/snapd/cmd/snap-repair"
+)
+
+func (r *repairSuite) TestStatusNoStatusFdEnv(c *C) {
+ for _, s := range []string{"done", "skip", "retry"} {
+ err := repair.ParseArgs([]string{s})
+ c.Check(err, ErrorMatches, "cannot find SNAP_REPAIR_STATUS_FD environment")
+ }
+}
+
+func (r *repairSuite) TestStatusBadStatusFD(c *C) {
+ for _, s := range []string{"done", "skip", "retry"} {
+ os.Setenv("SNAP_REPAIR_STATUS_FD", "123456789")
+ defer os.Unsetenv("SNAP_REPAIR_STATUS_FD")
+
+ err := repair.ParseArgs([]string{s})
+ c.Check(err, ErrorMatches, `write : bad file descriptor`)
+ }
+}
+
+func (r *repairSuite) TestStatusUnparsableStatusFD(c *C) {
+ for _, s := range []string{"done", "skip", "retry"} {
+ os.Setenv("SNAP_REPAIR_STATUS_FD", "xxx")
+ defer os.Unsetenv("SNAP_REPAIR_STATUS_FD")
+
+ err := repair.ParseArgs([]string{s})
+ c.Check(err, ErrorMatches, `cannot parse SNAP_REPAIR_STATUS_FD environment: strconv.*: parsing "xxx": invalid syntax`)
+ }
+}
+
+func (r *repairSuite) TestStatusHappy(c *C) {
+ for _, s := range []string{"done", "skip", "retry"} {
+ rp, wp, err := os.Pipe()
+ c.Assert(err, IsNil)
+ defer rp.Close()
+ defer wp.Close()
+
+ fd, e := syscall.Dup(int(wp.Fd()))
+ c.Assert(e, IsNil)
+ wp.Close()
+
+ os.Setenv("SNAP_REPAIR_STATUS_FD", strconv.Itoa(fd))
+ defer os.Unsetenv("SNAP_REPAIR_STATUS_FD")
+
+ err = repair.ParseArgs([]string{s})
+ c.Check(err, IsNil)
+
+ status, err := ioutil.ReadAll(rp)
+ c.Assert(err, IsNil)
+ c.Check(string(status), Equals, s+"\n")
+ }
+}
diff -Nru snapd-2.28.5/cmd/snap-repair/cmd_list.go snapd-2.29.3/cmd/snap-repair/cmd_list.go
--- snapd-2.28.5/cmd/snap-repair/cmd_list.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/cmd/snap-repair/cmd_list.go 2017-10-23 06:17:27.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"
+ "text/tabwriter"
+)
+
+func init() {
+ const (
+ short = "Lists repairs run on this device"
+ long = ""
+ )
+
+ if _, err := parser.AddCommand("list", short, long, &cmdList{}); err != nil {
+ panic(err)
+ }
+
+}
+
+type cmdList struct{}
+
+func (c *cmdList) Execute([]string) error {
+ w := tabwriter.NewWriter(Stdout, 5, 3, 2, ' ', 0)
+ defer w.Flush()
+
+ // FIXME: this will not currently list the repairs that are
+ // skipped because of e.g. wrong architecture
+
+ // directory structure is:
+ // var/lib/snapd/run/repairs/
+ // canonical/
+ // 1/
+ // r0.retry
+ // r0.script
+ // r1.done
+ // r1.script
+ // 2/
+ // r3.done
+ // r3.script
+ repairTraces, err := newRepairTraces("*", "*")
+ if err != nil {
+ return err
+ }
+ if len(repairTraces) == 0 {
+ fmt.Fprintf(Stderr, "no repairs yet\n")
+ return nil
+ }
+
+ fmt.Fprintf(w, "Repair\tRev\tStatus\tSummary\n")
+ for _, t := range repairTraces {
+ fmt.Fprintf(w, "%s\t%v\t%s\t%s\n", t.Repair(), t.Revision(), t.Status(), t.Summary())
+ }
+
+ return nil
+}
diff -Nru snapd-2.28.5/cmd/snap-repair/cmd_list_test.go snapd-2.29.3/cmd/snap-repair/cmd_list_test.go
--- snapd-2.28.5/cmd/snap-repair/cmd_list_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/cmd/snap-repair/cmd_list_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -0,0 +1,47 @@
+// -*- 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 (
+ . "gopkg.in/check.v1"
+
+ repair "github.com/snapcore/snapd/cmd/snap-repair"
+)
+
+func (r *repairSuite) TestListNoRepairsYet(c *C) {
+ err := repair.ParseArgs([]string{"list"})
+ c.Check(err, IsNil)
+ c.Check(r.Stdout(), Equals, "")
+ c.Check(r.Stderr(), Equals, "no repairs yet\n")
+}
+
+func (r *repairSuite) TestListRepairsSimple(c *C) {
+ makeMockRepairState(c)
+
+ err := repair.ParseArgs([]string{"list"})
+ c.Check(err, IsNil)
+ c.Check(r.Stdout(), Equals, `Repair Rev Status Summary
+canonical-1 3 retry repair one
+my-brand-1 1 done my-brand repair one
+my-brand-2 2 skip my-brand repair two
+my-brand-3 0 running my-brand repair three
+`)
+ c.Check(r.Stderr(), Equals, "")
+}
diff -Nru snapd-2.28.5/cmd/snap-repair/cmd_run.go snapd-2.29.3/cmd/snap-repair/cmd_run.go
--- snapd-2.28.5/cmd/snap-repair/cmd_run.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/cmd/snap-repair/cmd_run.go 2017-10-23 06:17:27.000000000 +0000
@@ -21,6 +21,12 @@
import (
"fmt"
+ "net/url"
+ "os"
+ "path/filepath"
+
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/osutil"
)
func init() {
@@ -37,7 +43,60 @@
type cmdRun struct{}
+var baseURL *url.URL
+
+func init() {
+ var baseurl string
+ if osutil.GetenvBool("SNAPPY_USE_STAGING_STORE") {
+ baseurl = "https://api.staging.snapcraft.io/v2/"
+ } else {
+ baseurl = "https://api.snapcraft.io/v2/"
+ }
+
+ var err error
+ baseURL, err = url.Parse(baseurl)
+ if err != nil {
+ panic(fmt.Sprintf("cannot setup base url: %v", err))
+ }
+}
+
func (c *cmdRun) Execute(args []string) error {
- fmt.Fprintf(Stdout, "run is not implemented yet\n")
+ if err := os.MkdirAll(dirs.SnapRunRepairDir, 0755); err != nil {
+ return err
+ }
+ flock, err := osutil.NewFileLock(filepath.Join(dirs.SnapRunRepairDir, "lock"))
+ if err != nil {
+ return err
+ }
+ err = flock.TryLock()
+ if err == osutil.ErrAlreadyLocked {
+ return fmt.Errorf("cannot run, another snap-repair run already executing")
+ }
+ if err != nil {
+ return err
+ }
+ defer flock.Unlock()
+
+ run := NewRunner()
+ run.BaseURL = baseURL
+ err = run.LoadState()
+ if err != nil {
+ return err
+ }
+
+ for {
+ repair, err := run.Next("canonical")
+ if err == ErrRepairNotFound {
+ // no more repairs
+ break
+ }
+ if err != nil {
+ return err
+ }
+
+ if err := repair.Run(); err != nil {
+ return err
+ }
+ }
return nil
}
diff -Nru snapd-2.28.5/cmd/snap-repair/cmd_run_test.go snapd-2.29.3/cmd/snap-repair/cmd_run_test.go
--- snapd-2.28.5/cmd/snap-repair/cmd_run_test.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/cmd/snap-repair/cmd_run_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -20,14 +20,53 @@
package main_test
import (
+ "os"
+ "path/filepath"
+
. "gopkg.in/check.v1"
+ "github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/asserts/sysdb"
repair "github.com/snapcore/snapd/cmd/snap-repair"
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/osutil"
)
func (r *repairSuite) TestRun(c *C) {
+ r1 := sysdb.InjectTrusted(r.storeSigning.Trusted)
+ defer r1()
+ r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{r.repairRootAcctKey})
+ defer r2()
+
+ r.freshState(c)
+
+ const script = `#!/bin/sh
+echo "happy output"
+echo "done" >&$SNAP_REPAIR_STATUS_FD
+exit 0
+`
+ seqRepairs := r.signSeqRepairs(c, []string{makeMockRepair(script)})
+ mockServer := makeMockServer(c, &seqRepairs, false)
+ defer mockServer.Close()
+
+ repair.MockBaseURL(mockServer.URL)
+
err := repair.ParseArgs([]string{"run"})
c.Check(err, IsNil)
- c.Check(r.Stdout(), Equals, "run is not implemented yet\n")
+ c.Check(r.Stdout(), HasLen, 0)
+
+ c.Check(osutil.FileExists(filepath.Join(dirs.SnapRepairRunDir, "canonical", "1", "r0.done")), Equals, true)
+}
+
+func (r *repairSuite) TestRunAlreadyLocked(c *C) {
+ err := os.MkdirAll(dirs.SnapRunRepairDir, 0700)
+ c.Assert(err, IsNil)
+ flock, err := osutil.NewFileLock(filepath.Join(dirs.SnapRunRepairDir, "lock"))
+ c.Assert(err, IsNil)
+ err = flock.Lock()
+ c.Assert(err, IsNil)
+ defer flock.Unlock()
+ err = repair.ParseArgs([]string{"run"})
+ c.Check(err, ErrorMatches, `cannot run, another snap-repair run already executing`)
}
diff -Nru snapd-2.28.5/cmd/snap-repair/cmd_show.go snapd-2.29.3/cmd/snap-repair/cmd_show.go
--- snapd-2.28.5/cmd/snap-repair/cmd_show.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/cmd/snap-repair/cmd_show.go 2017-10-23 06:17:27.000000000 +0000
@@ -0,0 +1,91 @@
+// -*- 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"
+ "strings"
+)
+
+func init() {
+ const (
+ short = "Shows specific repairs run on this device"
+ long = ""
+ )
+
+ if _, err := parser.AddCommand("show", short, long, &cmdShow{}); err != nil {
+ panic(err)
+ }
+
+}
+
+type cmdShow struct {
+ Positional struct {
+ Repair []string `positional-arg-name:""`
+ } `positional-args:"yes"`
+}
+
+func showRepairDetails(w io.Writer, repair string) error {
+ i := strings.LastIndex(repair, "-")
+ if i < 0 {
+ return fmt.Errorf("cannot parse repair %q", repair)
+ }
+ brand := repair[:i]
+ seq := repair[i+1:]
+
+ repairTraces, err := newRepairTraces(brand, seq)
+ if err != nil {
+ return err
+ }
+ if len(repairTraces) == 0 {
+ return fmt.Errorf("cannot find repair \"%s-%s\"", brand, seq)
+ }
+
+ for _, trace := range repairTraces {
+ fmt.Fprintf(w, "repair: %s\n", trace.Repair())
+ fmt.Fprintf(w, "revision: %s\n", trace.Revision())
+ fmt.Fprintf(w, "status: %s\n", trace.Status())
+ fmt.Fprintf(w, "summary: %s\n", trace.Summary())
+
+ fmt.Fprintf(w, "script:\n")
+ if err := trace.WriteScriptIndented(w, 2); err != nil {
+ fmt.Fprintf(w, "%serror: %s\n", indentPrefix(2), err)
+ }
+
+ fmt.Fprintf(w, "output:\n")
+ if err := trace.WriteOutputIndented(w, 2); err != nil {
+ fmt.Fprintf(w, "%serror: %s\n", indentPrefix(2), err)
+ }
+ }
+
+ return nil
+}
+
+func (c *cmdShow) Execute([]string) error {
+ for _, repair := range c.Positional.Repair {
+ if err := showRepairDetails(Stdout, repair); err != nil {
+ return err
+ }
+ fmt.Fprintf(Stdout, "\n")
+ }
+
+ return nil
+}
diff -Nru snapd-2.28.5/cmd/snap-repair/cmd_show_test.go snapd-2.29.3/cmd/snap-repair/cmd_show_test.go
--- snapd-2.28.5/cmd/snap-repair/cmd_show_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/cmd/snap-repair/cmd_show_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -0,0 +1,142 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2017 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package main_test
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+
+ . "gopkg.in/check.v1"
+
+ repair "github.com/snapcore/snapd/cmd/snap-repair"
+ "github.com/snapcore/snapd/dirs"
+)
+
+func (r *repairSuite) TestShowRepairSingle(c *C) {
+ makeMockRepairState(c)
+
+ err := repair.ParseArgs([]string{"show", "canonical-1"})
+ c.Check(err, IsNil)
+ c.Check(r.Stdout(), Equals, `repair: canonical-1
+revision: 3
+status: retry
+summary: repair one
+script:
+ #!/bin/sh
+ echo retry output
+output:
+ retry output
+
+`)
+
+}
+
+func (r *repairSuite) TestShowRepairMultiple(c *C) {
+ makeMockRepairState(c)
+
+ // repair.ParseArgs() always appends to its internal slice:
+ // cmdShow.Positional.Repair. To workaround this we create a
+ // new cmdShow here
+ err := repair.NewCmdShow("canonical-1", "my-brand-1", "my-brand-2").Execute(nil)
+ c.Check(err, IsNil)
+ c.Check(r.Stdout(), Equals, `repair: canonical-1
+revision: 3
+status: retry
+summary: repair one
+script:
+ #!/bin/sh
+ echo retry output
+output:
+ retry output
+
+repair: my-brand-1
+revision: 1
+status: done
+summary: my-brand repair one
+script:
+ #!/bin/sh
+ echo done output
+output:
+ done output
+
+repair: my-brand-2
+revision: 2
+status: skip
+summary: my-brand repair two
+script:
+ #!/bin/sh
+ echo skip output
+output:
+ skip output
+
+`)
+}
+
+func (r *repairSuite) TestShowRepairErrorNoRepairDir(c *C) {
+ dirs.SetRootDir(c.MkDir())
+
+ err := repair.NewCmdShow("canonical-1").Execute(nil)
+ c.Check(err, ErrorMatches, `cannot find repair "canonical-1"`)
+}
+
+func (r *repairSuite) TestShowRepairSingleWithoutScript(c *C) {
+ makeMockRepairState(c)
+ scriptPath := filepath.Join(dirs.SnapRepairRunDir, "canonical/1", "r3.script")
+ err := os.Remove(scriptPath)
+ c.Assert(err, IsNil)
+
+ err = repair.NewCmdShow("canonical-1").Execute(nil)
+ c.Check(err, IsNil)
+ c.Check(r.Stdout(), Equals, fmt.Sprintf(`repair: canonical-1
+revision: 3
+status: retry
+summary: repair one
+script:
+ error: open %s: no such file or directory
+output:
+ retry output
+
+`, scriptPath))
+
+}
+
+func (r *repairSuite) TestShowRepairSingleUnreadableOutput(c *C) {
+ makeMockRepairState(c)
+ scriptPath := filepath.Join(dirs.SnapRepairRunDir, "canonical/1", "r3.retry")
+ err := os.Chmod(scriptPath, 0000)
+ c.Assert(err, IsNil)
+ defer os.Chmod(scriptPath, 0644)
+
+ err = repair.NewCmdShow("canonical-1").Execute(nil)
+ c.Check(err, IsNil)
+ c.Check(r.Stdout(), Equals, fmt.Sprintf(`repair: canonical-1
+revision: 3
+status: retry
+summary: -
+script:
+ #!/bin/sh
+ echo retry output
+output:
+ error: open %s: permission denied
+
+`, scriptPath))
+
+}
diff -Nru snapd-2.28.5/cmd/snap-repair/export_test.go snapd-2.29.3/cmd/snap-repair/export_test.go
--- snapd-2.28.5/cmd/snap-repair/export_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/cmd/snap-repair/export_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -20,6 +20,7 @@
package main
import (
+ "net/url"
"time"
"gopkg.in/retry.v1"
@@ -34,6 +35,18 @@
Run = run
)
+func MockBaseURL(baseurl string) (restore func()) {
+ orig := baseURL
+ u, err := url.Parse(baseurl)
+ if err != nil {
+ panic(err)
+ }
+ baseURL = u
+ return func() {
+ baseURL = orig
+ }
+}
+
func MockFetchRetryStrategy(strategy retry.Strategy) (restore func()) {
originalFetchRetryStrategy := fetchRetryStrategy
fetchRetryStrategy = strategy
@@ -66,6 +79,10 @@
}
}
+func TrustedRepairRootKeys() []*asserts.AccountKey {
+ return trustedRepairRootKeys
+}
+
func (run *Runner) BrandModel() (brand, model string) {
return run.state.Device.Brand, run.state.Device.Model
}
@@ -98,8 +115,28 @@
run.state.Sequences[brand] = sequence
}
+func MockDefaultRepairTimeout(d time.Duration) (restore func()) {
+ orig := defaultRepairTimeout
+ defaultRepairTimeout = d
+ return func() {
+ defaultRepairTimeout = orig
+ }
+}
+
+func MockErrtrackerReportRepair(mock func(string, string, string, map[string]string) (string, error)) (restore func()) {
+ prev := errtrackerReportRepair
+ errtrackerReportRepair = mock
+ return func() { errtrackerReportRepair = prev }
+}
+
func MockTimeNow(f func() time.Time) (restore func()) {
origTimeNow := timeNow
timeNow = f
return func() { timeNow = origTimeNow }
}
+
+func NewCmdShow(args ...string) *cmdShow {
+ cmdShow := &cmdShow{}
+ cmdShow.Positional.Repair = args
+ return cmdShow
+}
diff -Nru snapd-2.28.5/cmd/snap-repair/main.go snapd-2.29.3/cmd/snap-repair/main.go
--- snapd-2.28.5/cmd/snap-repair/main.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/cmd/snap-repair/main.go 2017-10-23 06:17:27.000000000 +0000
@@ -27,6 +27,9 @@
// TODO: consider not using go-flags at all
"github.com/jessevdk/go-flags"
+ "github.com/snapcore/snapd/cmd"
+ "github.com/snapcore/snapd/httputil"
+ "github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/release"
)
@@ -46,6 +49,13 @@
`
)
+func init() {
+ err := logger.SimpleSetup()
+ if err != nil {
+ fmt.Fprintf(Stderr, "WARNING: failed to activate logging: %v\n", err)
+ }
+}
+
var errOnClassic = fmt.Errorf("cannot use snap-repair on a classic system")
func main() {
@@ -61,6 +71,7 @@
if release.OnClassic {
return errOnClassic
}
+ httputil.SetUserAgentFromVersion(cmd.Version, "snap-repair")
if err := parseArgs(os.Args[1:]); err != nil {
return err
diff -Nru snapd-2.28.5/cmd/snap-repair/main_test.go snapd-2.29.3/cmd/snap-repair/main_test.go
--- snapd-2.28.5/cmd/snap-repair/main_test.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/cmd/snap-repair/main_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -26,6 +26,7 @@
. "gopkg.in/check.v1"
repair "github.com/snapcore/snapd/cmd/snap-repair"
+ "github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/testutil"
)
@@ -35,6 +36,9 @@
type repairSuite struct {
testutil.BaseTest
+ baseRunnerSuite
+
+ rootdir string
stdout *bytes.Buffer
stderr *bytes.Buffer
@@ -42,6 +46,7 @@
func (r *repairSuite) SetUpTest(c *C) {
r.BaseTest.SetUpTest(c)
+ r.baseRunnerSuite.SetUpTest(c)
r.stdout = bytes.NewBuffer(nil)
r.stderr = bytes.NewBuffer(nil)
@@ -53,6 +58,10 @@
oldStderr := repair.Stderr
r.AddCleanup(func() { repair.Stderr = oldStderr })
repair.Stderr = r.stderr
+
+ r.rootdir = c.MkDir()
+ dirs.SetRootDir(r.rootdir)
+ r.AddCleanup(func() { dirs.SetRootDir("/") })
}
func (r *repairSuite) Stdout() string {
@@ -67,7 +76,7 @@
func (r *repairSuite) TestUnknownArg(c *C) {
err := repair.ParseArgs([]string{})
- c.Check(err, ErrorMatches, "Please specify the run command")
+ c.Check(err, ErrorMatches, "Please specify one command of: list, run or show")
}
func (r *repairSuite) TestRunOnClassic(c *C) {
diff -Nru snapd-2.28.5/cmd/snap-repair/runner.go snapd-2.29.3/cmd/snap-repair/runner.go
--- snapd-2.28.5/cmd/snap-repair/runner.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/cmd/snap-repair/runner.go 2017-10-23 06:17:27.000000000 +0000
@@ -20,6 +20,7 @@
package main
import (
+ "bufio"
"bytes"
"crypto/tls"
"encoding/json"
@@ -30,9 +31,11 @@
"net/http"
"net/url"
"os"
+ "os/exec"
"path/filepath"
"strconv"
"strings"
+ "syscall"
"time"
"gopkg.in/retry.v1"
@@ -41,6 +44,7 @@
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/asserts/sysdb"
"github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/errtracker"
"github.com/snapcore/snapd/httputil"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
@@ -48,6 +52,13 @@
"github.com/snapcore/snapd/strutil"
)
+var (
+ // TODO: move inside the repairs themselves?
+ defaultRepairTimeout = 30 * time.Minute
+)
+
+var errtrackerReportRepair = errtracker.ReportRepair
+
// Repair is a runnable repair.
type Repair struct {
*asserts.Repair
@@ -56,6 +67,14 @@
sequence int
}
+func (r *Repair) RunDir() string {
+ return filepath.Join(dirs.SnapRepairRunDir, r.BrandID(), strconv.Itoa(r.RepairID()))
+}
+
+func (r *Repair) String() string {
+ return fmt.Sprintf("%s-%v", r.BrandID(), r.RepairID())
+}
+
// SetStatus sets the status of the repair in the state and saves the latter.
func (r *Repair) SetStatus(status RepairStatus) {
brandID := r.BrandID()
@@ -65,26 +84,186 @@
r.run.SaveState()
}
+// makeRepairSymlink ensures $dir/repair exists and is a symlink to
+// /usr/lib/snapd/snap-repair
+func makeRepairSymlink(dir string) (err error) {
+ // make "repair" binary available to the repair scripts via symlink
+ // to the real snap-repair
+ if err = os.MkdirAll(dir, 0755); err != nil {
+ return err
+ }
+
+ old := filepath.Join(dirs.CoreLibExecDir, "snap-repair")
+ new := filepath.Join(dir, "repair")
+ if err := os.Symlink(old, new); err != nil && !os.IsExist(err) {
+ return err
+ }
+
+ return nil
+}
+
// 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())
+ rundir := r.RunDir()
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)
+
+ // ensure the script can use "repair done"
+ repairToolsDir := filepath.Join(dirs.SnapRunRepairDir, "tools")
+ if err := makeRepairSymlink(repairToolsDir); err != nil {
+ return err
+ }
+
+ baseName := fmt.Sprintf("r%d", r.Revision())
+ script := filepath.Join(rundir, baseName+".script")
+ err = osutil.AtomicWriteFile(script, r.Body(), 0700, 0)
+ if err != nil {
+ return err
+ }
+
+ logPath := filepath.Join(rundir, baseName+".running")
+ logf, err := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
+ if err != nil {
+ return err
+ }
+ defer logf.Close()
+
+ fmt.Fprintf(logf, "repair: %s\n", r)
+ fmt.Fprintf(logf, "revision: %d\n", r.Revision())
+ fmt.Fprintf(logf, "summary: %s\n", r.Summary())
+ fmt.Fprintf(logf, "output:\n")
+
+ statusR, statusW, err := os.Pipe()
if err != nil {
return err
}
+ defer statusR.Close()
+ defer statusW.Close()
+
+ logger.Debugf("executing %s", script)
+
+ // run the script
+ env := os.Environ()
+ // we need to hardcode FD=3 because this is the FD after
+ // exec.Command() forked. there is no way in go currently
+ // to run something right after fork() in the child to
+ // know the fd. However because go will close all fds
+ // except the ones in "cmd.ExtraFiles" we are safe to set "3"
+ env = append(env, "SNAP_REPAIR_STATUS_FD=3")
+ env = append(env, "SNAP_REPAIR_RUN_DIR="+rundir)
+ // inject repairToolDir into PATH so that the script can use
+ // `repair {done,skip,retry}`
+ var havePath bool
+ for i, envStr := range env {
+ if strings.HasPrefix(envStr, "PATH=") {
+ newEnv := fmt.Sprintf("%s:%s", strings.TrimSuffix(envStr, ":"), repairToolsDir)
+ env[i] = newEnv
+ havePath = true
+ }
+ }
+ if !havePath {
+ env = append(env, "PATH=/usr/sbin:/usr/bin:/sbin:/bin:"+repairToolsDir)
+ }
+
+ workdir := filepath.Join(rundir, "work")
+ if err := os.MkdirAll(workdir, 0700); err != nil {
+ return err
+ }
+
+ cmd := exec.Command(script)
+ cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
+ cmd.Env = env
+ cmd.Dir = workdir
+ cmd.ExtraFiles = []*os.File{statusW}
+ cmd.Stdout = logf
+ cmd.Stderr = logf
+ if err = cmd.Start(); err != nil {
+ return err
+ }
+ statusW.Close()
- // XXX actually run things and captures output etc
+ // wait for repair to finish or timeout
+ var scriptErr error
+ killTimerCh := time.After(defaultRepairTimeout)
+ doneCh := make(chan error)
+ go func() {
+ doneCh <- cmd.Wait()
+ close(doneCh)
+ }()
+ select {
+ case scriptErr = <-doneCh:
+ // done
+ case <-killTimerCh:
+ if err := osutil.KillProcessGroup(cmd); err != nil {
+ logger.Noticef("cannot kill timed out repair %s: %s", r, err)
+ }
+ scriptErr = fmt.Errorf("repair did not finish within %s", defaultRepairTimeout)
+ }
+ // read repair status pipe, use the last value
+ status := readStatus(statusR)
+ statusPath := filepath.Join(rundir, baseName+"."+status.String())
+
+ // if the script had an error exit status still honor what we
+ // read from the status-pipe, however report the error
+ if scriptErr != nil {
+ scriptErr = fmt.Errorf("repair %s revision %d failed: %s", r, r.Revision(), scriptErr)
+ if err := r.errtrackerReport(scriptErr, status, logPath); err != nil {
+ logger.Noticef("cannot report error to errtracker: %s", err)
+ }
+ // ensure the error is present in the output log
+ fmt.Fprintf(logf, "\n%s", scriptErr)
+ }
+ if err := os.Rename(logPath, statusPath); err != nil {
+ return err
+ }
+ r.SetStatus(status)
return nil
}
+func readStatus(r io.Reader) RepairStatus {
+ var status RepairStatus
+ scanner := bufio.NewScanner(r)
+ for scanner.Scan() {
+ switch strings.TrimSpace(scanner.Text()) {
+ case "done":
+ status = DoneStatus
+ // TODO: support having a script skip over many and up to a given repair-id #
+ case "skip":
+ status = SkipStatus
+ }
+ }
+ if scanner.Err() != nil {
+ return RetryStatus
+ }
+ return status
+}
+
+// errtrackerReport reports an repairErr with the given logPath to the
+// snap error tracker.
+func (r *Repair) errtrackerReport(repairErr error, status RepairStatus, logPath string) error {
+ errMsg := repairErr.Error()
+
+ scriptOutput, err := ioutil.ReadFile(logPath)
+ if err != nil {
+ logger.Noticef("cannot read %s", logPath)
+ }
+ s := fmt.Sprintf("%s/%d", r.BrandID(), r.RepairID())
+
+ dupSig := fmt.Sprintf("%s\n%s\noutput:\n%s", s, errMsg, scriptOutput)
+ extra := map[string]string{
+ "Revision": strconv.Itoa(r.Revision()),
+ "BrandID": r.BrandID(),
+ "RepairID": strconv.Itoa(r.RepairID()),
+ "Status": status.String(),
+ }
+ _, err = errtrackerReportRepair(s, errMsg, dupSig, extra)
+ return err
+}
+
// Runner implements fetching, tracking and running repairs.
type Runner struct {
BaseURL *url.URL
@@ -141,8 +320,8 @@
// 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))
+func (run *Runner) Fetch(brandID string, repairID int, revision int) (*asserts.Repair, []asserts.Assertion, error) {
+ u, err := run.BaseURL.Parse(fmt.Sprintf("repairs/%s/%d", brandID, repairID))
if err != nil {
return nil, nil, err
}
@@ -160,6 +339,7 @@
return run.cli.Do(req)
}, func(resp *http.Response) error {
if resp.StatusCode == 200 {
+ logger.Debugf("fetching repair %s-%d", brandID, repairID)
// decode assertions
dec := asserts.NewDecoderWithTypeMaxBodySize(resp.Body, map[*asserts.AssertionType]int{
asserts.RepairType: maxRepairScriptSize,
@@ -206,7 +386,7 @@
return nil, nil, fmt.Errorf("cannot fetch repair, unexpected status %d", resp.StatusCode)
}
- repair, aux, err = checkStream(brandID, repairID, r)
+ repair, aux, err := checkStream(brandID, repairID, r)
if err != nil {
return nil, nil, fmt.Errorf("cannot fetch repair, %v", err)
}
@@ -218,10 +398,10 @@
return nil, nil, ErrRepairNotModified
}
- return
+ return repair, aux, err
}
-func checkStream(brandID, repairID string, r []asserts.Assertion) (repair *asserts.Repair, aux []asserts.Assertion, err error) {
+func checkStream(brandID string, repairID int, r []asserts.Assertion) (repair *asserts.Repair, aux []asserts.Assertion, err error) {
if len(r) == 0 {
return nil, nil, fmt.Errorf("empty repair assertions stream")
}
@@ -232,7 +412,7 @@
}
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 nil, nil, fmt.Errorf("repair id mismatch %s/%d != %s/%d", repair.BrandID(), repair.RepairID(), brandID, repairID)
}
return repair, r[1:], nil
@@ -243,8 +423,8 @@
}
// 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))
+func (run *Runner) Peek(brandID string, repairID int) (headers map[string]interface{}, err error) {
+ u, err := run.BaseURL.Parse(fmt.Sprintf("repairs/%s/%d", brandID, repairID))
if err != nil {
return nil, err
}
@@ -290,8 +470,8 @@
}
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)
+ if headers["brand-id"] != brandID || headers["repair-id"] != strconv.Itoa(repairID) {
+ return nil, fmt.Errorf("cannot peek repair headers, repair id mismatch %s/%s != %s/%d", headers["brand-id"], headers["repair-id"], brandID, repairID)
}
return headers, nil
@@ -312,6 +492,19 @@
DoneStatus
)
+func (rs RepairStatus) String() string {
+ switch rs {
+ case RetryStatus:
+ return "retry"
+ case SkipStatus:
+ return "skip"
+ case DoneStatus:
+ return "done"
+ default:
+ return "unknown"
+ }
+}
+
// RepairState holds the current revision and status of a repair in a sequence of repairs.
type RepairState struct {
Sequence int `json:"sequence"`
@@ -405,10 +598,10 @@
// a trusted authority
acctID := a.AuthorityID()
_, err := trusted.Get(asserts.AccountType, []string{acctID}, asserts.AccountType.MaxSupportedFormat())
- if err != nil && err != asserts.ErrNotFound {
+ if err != nil && !asserts.IsNotFound(err) {
return err
}
- if err == asserts.ErrNotFound {
+ if asserts.IsNotFound(err) {
return fmt.Errorf("%v not signed by trusted authority: %s", a.Ref(), acctID)
}
return nil
@@ -430,17 +623,17 @@
seen[u] = true
signKey := []string{a.SignKeyID()}
key, err := trusted.Get(asserts.AccountKeyType, signKey, acctKeyMaxSuppFormat)
- if err != nil && err != asserts.ErrNotFound {
+ if err != nil && !asserts.IsNotFound(err) {
return err
}
if err == nil {
bottom = true
} else {
key, err = workBS.Get(asserts.AccountKeyType, signKey, acctKeyMaxSuppFormat)
- if err != nil && err != asserts.ErrNotFound {
+ if err != nil && !asserts.IsNotFound(err) {
return err
}
- if err == asserts.ErrNotFound {
+ if asserts.IsNotFound(err) {
return fmt.Errorf("cannot find public key %q", signKey[0])
}
if err := checkAuthorityID(key, trusted); err != nil {
@@ -567,6 +760,9 @@
// Applicable returns whether a repair with the given headers is applicable to the device.
func (run *Runner) Applicable(headers map[string]interface{}) bool {
+ if headers["disabled"] == "true" {
+ return false
+ }
series, err := stringList(headers, "series")
if err != nil {
return false
@@ -606,8 +802,7 @@
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)
+func (run *Runner) fetch(brandID string, repairID int) (repair *asserts.Repair, aux []asserts.Assertion, err error) {
headers, err := run.Peek(brandID, repairID)
if err != nil {
return nil, nil, err
@@ -618,14 +813,12 @@
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)
+func (run *Runner) refetch(brandID string, repairID, revision int) (repair *asserts.Repair, aux []asserts.Assertion, err error) {
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)
+func (run *Runner) saveStream(brandID string, repairID int, repair *asserts.Repair, aux []asserts.Assertion) error {
+ d := filepath.Join(dirs.SnapRepairAssertsDir, brandID, strconv.Itoa(repairID))
err := os.MkdirAll(d, 0775)
if err != nil {
return err
@@ -635,17 +828,16 @@
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)
+ return fmt.Errorf("cannot encode repair assertions %s-%d for saving: %v", brandID, repairID, err)
}
}
- p := filepath.Join(d, fmt.Sprintf("repair.r%d", r[0].Revision()))
+ p := filepath.Join(d, fmt.Sprintf("r%d.repair", 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))
+func (run *Runner) readSavedStream(brandID string, repairID, revision int) (repair *asserts.Repair, aux []asserts.Assertion, err error) {
+ d := filepath.Join(dirs.SnapRepairAssertsDir, brandID, strconv.Itoa(repairID))
+ p := filepath.Join(d, fmt.Sprintf("r%d.repair", revision))
f, err := os.Open(p)
if err != nil {
return nil, nil, err
@@ -660,7 +852,7 @@
break
}
if err != nil {
- return nil, nil, fmt.Errorf("cannot decode repair assertions %s-%s from disk: %v", brandID, repairID, err)
+ return nil, nil, fmt.Errorf("cannot decode repair assertions %s-%d from disk: %v", brandID, repairID, err)
}
r = append(r, a)
}
diff -Nru snapd-2.28.5/cmd/snap-repair/runner_test.go snapd-2.29.3/cmd/snap-repair/runner_test.go
--- snapd-2.28.5/cmd/snap-repair/runner_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/cmd/snap-repair/runner_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -43,10 +43,11 @@
"github.com/snapcore/snapd/asserts/sysdb"
repair "github.com/snapcore/snapd/cmd/snap-repair"
"github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
)
-type runnerSuite struct {
+type baseRunnerSuite struct {
tmpdir string
seedTime time.Time
@@ -66,11 +67,11 @@
repairsAcctKey *asserts.AccountKey
repairsSigning *assertstest.SigningDB
-}
-var _ = Suite(&runnerSuite{})
+ restoreLogger func()
+}
-func (s *runnerSuite) SetUpSuite(c *C) {
+func (s *baseRunnerSuite) SetUpSuite(c *C) {
s.storeSigning = assertstest.NewStoreStack("canonical", nil)
brandPrivKey, _ := assertstest.GenerateKey(752)
@@ -106,7 +107,9 @@
s.repairsSigning = assertstest.NewSigningDB("canonical", repairsKey)
}
-func (s *runnerSuite) SetUpTest(c *C) {
+func (s *baseRunnerSuite) SetUpTest(c *C) {
+ _, s.restoreLogger = logger.MockLogger()
+
s.tmpdir = c.MkDir()
dirs.SetRootDir(s.tmpdir)
@@ -127,10 +130,42 @@
s.t0 = time.Now().UTC().Truncate(time.Minute)
}
-func (s *runnerSuite) TearDownTest(c *C) {
+func (s *baseRunnerSuite) TearDownTest(c *C) {
dirs.SetRootDir("/")
+ s.restoreLogger()
+}
+
+func (s *baseRunnerSuite) 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
}
+const freshStateJSON = `{"device":{"brand":"my-brand","model":"my-model"},"time-lower-bound":"2017-08-11T15:49:49Z"}`
+
+func (s *baseRunnerSuite) 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)
+}
+
+type runnerSuite struct {
+ baseRunnerSuite
+}
+
+var _ = Suite(&runnerSuite{})
+
var (
testKey = `type: account-key
authority-id: canonical
@@ -151,6 +186,7 @@
authority-id: canonical
brand-id: canonical
repair-id: 2
+summary: repair two
architectures:
- amd64
- arm64
@@ -208,12 +244,12 @@
r := s.mockBrokenTimeNowSetToEpoch(c, runner)
defer r()
- repair, aux, err := runner.Fetch("canonical", "2", -1)
+ 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.RepairID(), Equals, 2)
c.Check(repair.Body(), DeepEquals, []byte("script\n"))
s.checkBrokenTimeNowMitigated(c, runner)
@@ -237,7 +273,7 @@
runner := repair.NewRunner()
runner.BaseURL = mustParseURL(mockServer.URL)
- _, _, err := runner.Fetch("canonical", "2", -1)
+ _, _, 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)
}
@@ -267,7 +303,7 @@
runner := repair.NewRunner()
runner.BaseURL = mustParseURL(mockServer.URL)
- _, _, err := runner.Fetch("canonical", "2", -1)
+ _, _, err := runner.Fetch("canonical", 2, -1)
c.Assert(err, ErrorMatches, "cannot fetch repair, unexpected status 500")
c.Assert(n, Equals, 5)
}
@@ -288,7 +324,7 @@
runner := repair.NewRunner()
runner.BaseURL = mustParseURL(mockServer.URL)
- _, _, err := runner.Fetch("canonical", "2", -1)
+ _, _, err := runner.Fetch("canonical", 2, -1)
c.Assert(err, Equals, io.ErrUnexpectedEOF)
c.Assert(n, Equals, 5)
}
@@ -310,7 +346,7 @@
runner := repair.NewRunner()
runner.BaseURL = mustParseURL(mockServer.URL)
- _, _, err := runner.Fetch("canonical", "2", -1)
+ _, _, err := runner.Fetch("canonical", 2, -1)
c.Assert(err, Equals, io.ErrUnexpectedEOF)
c.Assert(n, Equals, 5)
}
@@ -334,7 +370,7 @@
r := s.mockBrokenTimeNowSetToEpoch(c, runner)
defer r()
- _, _, err := runner.Fetch("canonical", "2", -1)
+ _, _, err := runner.Fetch("canonical", 2, -1)
c.Assert(err, Equals, repair.ErrRepairNotFound)
c.Assert(n, Equals, 1)
@@ -361,7 +397,7 @@
r := s.mockBrokenTimeNowSetToEpoch(c, runner)
defer r()
- _, _, err := runner.Fetch("canonical", "2", 0)
+ _, _, err := runner.Fetch("canonical", 2, 0)
c.Assert(err, Equals, repair.ErrRepairNotModified)
c.Assert(n, Equals, 1)
@@ -379,7 +415,7 @@
runner := repair.NewRunner()
runner.BaseURL = mustParseURL(mockServer.URL)
- _, _, err := runner.Fetch("canonical", "2", 2)
+ _, _, err := runner.Fetch("canonical", 2, 2)
c.Assert(err, Equals, repair.ErrRepairNotModified)
}
@@ -395,7 +431,7 @@
runner := repair.NewRunner()
runner.BaseURL = mustParseURL(mockServer.URL)
- _, _, err := runner.Fetch("canonical", "4", -1)
+ _, _, err := runner.Fetch("canonical", 4, -1)
c.Assert(err, ErrorMatches, `cannot fetch repair, repair id mismatch canonical/2 != canonical/4`)
}
@@ -412,7 +448,7 @@
runner := repair.NewRunner()
runner.BaseURL = mustParseURL(mockServer.URL)
- _, _, err := runner.Fetch("canonical", "2", -1)
+ _, _, err := runner.Fetch("canonical", 2, -1)
c.Assert(err, ErrorMatches, `cannot fetch repair, unexpected first assertion "account-key"`)
}
@@ -431,7 +467,7 @@
runner := repair.NewRunner()
runner.BaseURL = mustParseURL(mockServer.URL)
- repair, aux, err := runner.Fetch("canonical", "2", -1)
+ repair, aux, err := runner.Fetch("canonical", 2, -1)
c.Assert(err, IsNil)
c.Check(repair, NotNil)
c.Check(aux, HasLen, 1)
@@ -455,7 +491,7 @@
r := s.mockBrokenTimeNowSetToEpoch(c, runner)
defer r()
- h, err := runner.Peek("canonical", "2")
+ 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"})
@@ -480,7 +516,7 @@
runner := repair.NewRunner()
runner.BaseURL = mustParseURL(mockServer.URL)
- _, err := runner.Peek("canonical", "2")
+ _, err := runner.Peek("canonical", 2)
c.Assert(err, ErrorMatches, "cannot peek repair headers, unexpected status 500")
c.Assert(n, Equals, 5)
}
@@ -502,7 +538,7 @@
runner := repair.NewRunner()
runner.BaseURL = mustParseURL(mockServer.URL)
- _, err := runner.Peek("canonical", "2")
+ _, err := runner.Peek("canonical", 2)
c.Assert(err, Equals, io.ErrUnexpectedEOF)
c.Assert(n, Equals, 5)
}
@@ -523,7 +559,7 @@
r := s.mockBrokenTimeNowSetToEpoch(c, runner)
defer r()
- _, err := runner.Peek("canonical", "2")
+ _, err := runner.Peek("canonical", 2)
c.Assert(err, Equals, repair.ErrRepairNotFound)
c.Assert(n, Equals, 1)
@@ -542,19 +578,10 @@
runner := repair.NewRunner()
runner.BaseURL = mustParseURL(mockServer.URL)
- _, err := runner.Peek("canonical", "4")
+ _, 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)
@@ -659,14 +686,27 @@
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)
+func makeReadOnly(c *C, dir string) (restore func()) {
+ // skip tests that need this because uid==0 does not honor
+ // write permissions in directories (yay, unix)
+ if os.Getuid() == 0 {
+ // FIXME: we could use osutil.Chattr() here
+ c.Skip("too lazy to make path readonly as root")
+ }
+ err := os.Chmod(dir, 0555)
c.Assert(err, IsNil)
- defer os.Chmod(par, 0775)
+ return func() {
+ err := os.Chmod(dir, 0755)
+ c.Assert(err, IsNil)
+ }
+}
+
+func (s *runnerSuite) TestLoadStateInitStateFail(c *C) {
+ restore := makeReadOnly(c, filepath.Dir(dirs.SnapSeedDir))
+ defer restore()
runner := repair.NewRunner()
- err = runner.LoadState()
+ err := runner.LoadState()
c.Check(err, ErrorMatches, `cannot create repair state directory:.*`)
}
@@ -677,9 +717,8 @@
err := runner.LoadState()
c.Assert(err, IsNil)
- err = os.Chmod(dirs.SnapRepairDir, 0555)
- c.Assert(err, IsNil)
- defer os.Chmod(dirs.SnapRepairDir, 0775)
+ restore := makeReadOnly(c, dirs.SnapRepairDir)
+ defer restore()
// no error because this is a no-op
err = runner.SaveState()
@@ -742,6 +781,8 @@
{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},
+ {map[string]interface{}{"disabled": "true"}, false},
+ {map[string]interface{}{"disabled": "false"}, true},
}
for _, scen := range scenarios {
@@ -755,6 +796,7 @@
authority-id: canonical
brand-id: canonical
repair-id: 1
+summary: repair one
timestamp: 2017-07-01T12:00:00Z
body-length: 8
sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
@@ -767,6 +809,7 @@
authority-id: canonical
brand-id: canonical
repair-id: 2
+summary: repair two
series:
- 33
timestamp: 2017-07-02T12:00:00Z
@@ -782,6 +825,7 @@
authority-id: canonical
brand-id: canonical
repair-id: 3
+summary: repair three rev2
series:
- 16
timestamp: 2017-07-03T12:00:00Z
@@ -799,6 +843,7 @@
authority-id: canonical
brand-id: canonical
repair-id: 3
+summary: repair three rev4
series:
- 16
timestamp: 2017-07-03T12:00:00Z
@@ -815,6 +860,7 @@
authority-id: canonical
brand-id: canonical
repair-id: 4
+summary: repair four
timestamp: 2017-07-03T12:00:00Z
body-length: 8
sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
@@ -878,6 +924,13 @@
return mockServer
}
+func (s *runnerSuite) TestTrustedRepairRootKeys(c *C) {
+ acctKeys := repair.TrustedRepairRootKeys()
+ c.Check(acctKeys, HasLen, 1)
+ c.Check(acctKeys[0].AccountID(), Equals, "canonical")
+ c.Check(acctKeys[0].PublicKeyID(), Equals, "nttW6NfBXI_E-00u38W-KH6eiksfQNXuI7IiumoV49_zkbhM0sYTzSnFlwZC-W4t")
+}
+
func (s *runnerSuite) TestVerify(c *C) {
r1 := sysdb.InjectTrusted(s.storeSigning.Trusted)
defer r1()
@@ -889,6 +942,7 @@
a, err := s.repairsSigning.Sign(asserts.RepairType, map[string]interface{}{
"brand-id": "canonical",
"repair-id": "2",
+ "summary": "repair two",
"timestamp": time.Now().UTC().Format(time.RFC3339),
}, []byte("#script"), "")
c.Assert(err, IsNil)
@@ -942,13 +996,13 @@
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)
+ c.Check(rpr.RepairID(), Equals, 1)
+ c.Check(osutil.FileExists(filepath.Join(dirs.SnapRepairAssertsDir, "canonical", "1", "r0.repair")), 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.Check(rpr.RepairID(), Equals, 3)
+ strm, err := ioutil.ReadFile(filepath.Join(dirs.SnapRepairAssertsDir, "canonical", "3", "r2.repair"))
c.Assert(err, IsNil)
c.Check(string(strm), Equals, seqRepairs[2])
@@ -978,11 +1032,11 @@
rpr, err = runner.Next("canonical")
c.Assert(err, IsNil)
- c.Check(rpr.RepairID(), Equals, "1")
+ c.Check(rpr.RepairID(), Equals, 1)
rpr, err = runner.Next("canonical")
c.Assert(err, IsNil)
- c.Check(rpr.RepairID(), Equals, "3")
+ c.Check(rpr.RepairID(), Equals, 3)
// refetched new revision!
c.Check(rpr.Revision(), Equals, 4)
c.Check(rpr.Body(), DeepEquals, []byte("scriptC2\n"))
@@ -990,7 +1044,7 @@
// new repair
rpr, err = runner.Next("canonical")
c.Assert(err, IsNil)
- c.Check(rpr.RepairID(), Equals, "4")
+ c.Check(rpr.RepairID(), Equals, 4)
c.Check(rpr.Body(), DeepEquals, []byte("scriptD\n"))
// no more
@@ -1020,6 +1074,7 @@
authority-id: canonical
brand-id: canonical
repair-id: 1
+summary: repair one
series:
- 33
timestamp: 2017-07-02T12:00:00Z
@@ -1056,6 +1111,7 @@
authority-id: canonical
brand-id: canonical
repair-id: 1
+summary: repair one
series:
- 16
timestamp: 2017-07-02T12:00:00Z
@@ -1098,8 +1154,10 @@
revision: 1
brand-id: canonical
repair-id: 1
+summary: repair one rev1
series:
- - 33
+ - 16
+disabled: true
timestamp: 2017-07-02T12:00:00Z
body-length: 7
sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
@@ -1184,6 +1242,7 @@
authority-id: canonical
brand-id: canonical
repair-id: 1
+summary: repair one
series:
- 33
timestamp: 2017-07-02T12:00:00Z
@@ -1203,11 +1262,10 @@
runner.LoadState()
// break SaveState
- err := os.Chmod(dirs.SnapRepairDir, 0555)
- c.Assert(err, IsNil)
- defer os.Chmod(dirs.SnapRepairDir, 0775)
+ restore := makeReadOnly(c, dirs.SnapRepairDir)
+ defer restore()
- _, err = runner.Next("canonical")
+ _, err := runner.Next("canonical")
c.Check(err, ErrorMatches, `cannot save repair state:.*`)
}
@@ -1216,6 +1274,7 @@
authority-id: canonical
brand-id: canonical
repair-id: 1
+summary: repair one
timestamp: 2017-07-02T12:00:00Z
body-length: 8
sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
@@ -1256,6 +1315,7 @@
rpr, err := randomSigning.Sign(asserts.RepairType, map[string]interface{}{
"brand-id": "canonical",
"repair-id": "1",
+ "summary": "repair one",
"timestamp": time.Now().UTC().Format(time.RFC3339),
}, []byte("scriptB\n"), "")
c.Assert(err, IsNil)
@@ -1308,7 +1368,7 @@
rpr, err := runner.Next("canonical")
c.Assert(err, IsNil)
- c.Check(rpr.RepairID(), Equals, "1")
+ c.Check(rpr.RepairID(), Equals, 1)
}
func (s *runnerSuite) TestRepairSetStatus(c *C) {
@@ -1316,6 +1376,7 @@
authority-id: canonical
brand-id: canonical
repair-id: 1
+summary: repair one
timestamp: 2017-07-02T12:00:00Z
body-length: 8
sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
@@ -1358,6 +1419,7 @@
authority-id: canonical
brand-id: canonical
repair-id: 1
+summary: repair one
series:
- 16
timestamp: 2017-07-02T12:00:00Z
@@ -1387,7 +1449,328 @@
c.Assert(err, IsNil)
rpr.Run()
- scrpt, err := ioutil.ReadFile(filepath.Join(dirs.SnapRepairRunDir, "canonical", "1", "script.r0"))
+ scrpt, err := ioutil.ReadFile(filepath.Join(dirs.SnapRepairRunDir, "canonical", "1", "r0.script"))
c.Assert(err, IsNil)
c.Check(string(scrpt), Equals, "exit 0\n")
}
+
+func makeMockRepair(script string) string {
+ return fmt.Sprintf(`type: repair
+authority-id: canonical
+brand-id: canonical
+repair-id: 1
+summary: repair one
+series:
+ - 16
+timestamp: 2017-07-02T12:00:00Z
+body-length: %d
+sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
+
+%s
+
+AXNpZw==`, len(script), script)
+}
+
+func verifyRepairStatus(c *C, status repair.RepairStatus) {
+ data, err := ioutil.ReadFile(dirs.SnapRepairStateFile)
+ c.Assert(err, IsNil)
+ c.Check(string(data), Matches, fmt.Sprintf(`{"device":{"brand":"","model":""},"sequences":{"canonical":\[{"sequence":1,"revision":0,"status":%d}.*`, status))
+}
+
+// tests related to correct execution of script
+type runScriptSuite struct {
+ baseRunnerSuite
+
+ seqRepairs []string
+
+ mockServer *httptest.Server
+ runner *repair.Runner
+
+ runDir string
+
+ restoreErrTrackerReportRepair func()
+ errReport struct {
+ repair string
+ errMsg string
+ dupSig string
+ extra map[string]string
+ }
+}
+
+var _ = Suite(&runScriptSuite{})
+
+func (s *runScriptSuite) SetUpTest(c *C) {
+ s.baseRunnerSuite.SetUpTest(c)
+
+ s.mockServer = makeMockServer(c, &s.seqRepairs, false)
+
+ s.runner = repair.NewRunner()
+ s.runner.BaseURL = mustParseURL(s.mockServer.URL)
+ s.runner.LoadState()
+
+ s.runDir = filepath.Join(dirs.SnapRepairRunDir, "canonical", "1")
+
+ s.restoreErrTrackerReportRepair = repair.MockErrtrackerReportRepair(s.errtrackerReportRepair)
+}
+
+func (s *runScriptSuite) TearDownTest(c *C) {
+ s.baseRunnerSuite.TearDownTest(c)
+
+ s.restoreErrTrackerReportRepair()
+ s.mockServer.Close()
+}
+
+func (s *runScriptSuite) errtrackerReportRepair(repair, errMsg, dupSig string, extra map[string]string) (string, error) {
+ s.errReport.repair = repair
+ s.errReport.errMsg = errMsg
+ s.errReport.dupSig = dupSig
+ s.errReport.extra = extra
+
+ return "some-oops-id", nil
+}
+
+func (s *runScriptSuite) testScriptRun(c *C, mockScript string) *repair.Repair {
+ r1 := sysdb.InjectTrusted(s.storeSigning.Trusted)
+ defer r1()
+ r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey})
+ defer r2()
+
+ s.seqRepairs = s.signSeqRepairs(c, s.seqRepairs)
+
+ rpr, err := s.runner.Next("canonical")
+ c.Assert(err, IsNil)
+
+ err = rpr.Run()
+ c.Assert(err, IsNil)
+
+ scrpt, err := ioutil.ReadFile(filepath.Join(s.runDir, "r0.script"))
+ c.Assert(err, IsNil)
+ c.Check(string(scrpt), Equals, mockScript)
+
+ return rpr
+}
+
+func (s *runScriptSuite) verifyRundir(c *C, names []string) {
+ dirents, err := ioutil.ReadDir(s.runDir)
+ c.Assert(err, IsNil)
+ c.Assert(dirents, HasLen, len(names))
+ for i := range dirents {
+ c.Check(dirents[i].Name(), Matches, names[i])
+ }
+}
+
+type byMtime []os.FileInfo
+
+func (m byMtime) Len() int { return len(m) }
+func (m byMtime) Less(i, j int) bool { return m[i].ModTime().Before(m[j].ModTime()) }
+func (m byMtime) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
+
+func (s *runScriptSuite) verifyOutput(c *C, name, expectedOutput string) {
+ output, err := ioutil.ReadFile(filepath.Join(s.runDir, name))
+ c.Assert(err, IsNil)
+ c.Check(string(output), Equals, expectedOutput)
+ // ensure correct permissions
+ fi, err := os.Stat(filepath.Join(s.runDir, name))
+ c.Assert(err, IsNil)
+ c.Check(fi.Mode(), Equals, os.FileMode(0600))
+}
+
+func (s *runScriptSuite) TestRepairBasicRunHappy(c *C) {
+ script := `#!/bin/sh
+echo "happy output"
+echo "done" >&$SNAP_REPAIR_STATUS_FD
+exit 0
+`
+ s.seqRepairs = []string{makeMockRepair(script)}
+ s.testScriptRun(c, script)
+ // verify
+ s.verifyRundir(c, []string{
+ `^r0.done$`,
+ `^r0.script$`,
+ `^work$`,
+ })
+ s.verifyOutput(c, "r0.done", `repair: canonical-1
+revision: 0
+summary: repair one
+output:
+happy output
+`)
+ verifyRepairStatus(c, repair.DoneStatus)
+}
+
+func (s *runScriptSuite) TestRepairBasicRunUnhappy(c *C) {
+ script := `#!/bin/sh
+echo "unhappy output"
+exit 1
+`
+ s.seqRepairs = []string{makeMockRepair(script)}
+ s.testScriptRun(c, script)
+ // verify
+ s.verifyRundir(c, []string{
+ `^r0.retry$`,
+ `^r0.script$`,
+ `^work$`,
+ })
+ s.verifyOutput(c, "r0.retry", `repair: canonical-1
+revision: 0
+summary: repair one
+output:
+unhappy output
+
+repair canonical-1 revision 0 failed: exit status 1`)
+ verifyRepairStatus(c, repair.RetryStatus)
+
+ c.Check(s.errReport.repair, Equals, "canonical/1")
+ c.Check(s.errReport.errMsg, Equals, `repair canonical-1 revision 0 failed: exit status 1`)
+ c.Check(s.errReport.dupSig, Equals, `canonical/1
+repair canonical-1 revision 0 failed: exit status 1
+output:
+repair: canonical-1
+revision: 0
+summary: repair one
+output:
+unhappy output
+`)
+ c.Check(s.errReport.extra, DeepEquals, map[string]string{
+ "Revision": "0",
+ "RepairID": "1",
+ "BrandID": "canonical",
+ "Status": "retry",
+ })
+}
+
+func (s *runScriptSuite) TestRepairBasicSkip(c *C) {
+ script := `#!/bin/sh
+echo "other output"
+echo "skip" >&$SNAP_REPAIR_STATUS_FD
+exit 0
+`
+ s.seqRepairs = []string{makeMockRepair(script)}
+ s.testScriptRun(c, script)
+ // verify
+ s.verifyRundir(c, []string{
+ `^r0.script$`,
+ `^r0.skip$`,
+ `^work$`,
+ })
+ s.verifyOutput(c, "r0.skip", `repair: canonical-1
+revision: 0
+summary: repair one
+output:
+other output
+`)
+ verifyRepairStatus(c, repair.SkipStatus)
+}
+
+func (s *runScriptSuite) TestRepairBasicRunUnhappyThenHappy(c *C) {
+ script := `#!/bin/sh
+if [ -f zzz-ran-once ]; then
+ echo "happy now"
+ echo "done" >&$SNAP_REPAIR_STATUS_FD
+ exit 0
+fi
+echo "unhappy output"
+touch zzz-ran-once
+exit 1
+`
+ s.seqRepairs = []string{makeMockRepair(script)}
+ rpr := s.testScriptRun(c, script)
+ s.verifyRundir(c, []string{
+ `^r0.retry$`,
+ `^r0.script$`,
+ `^work$`,
+ })
+ s.verifyOutput(c, "r0.retry", `repair: canonical-1
+revision: 0
+summary: repair one
+output:
+unhappy output
+
+repair canonical-1 revision 0 failed: exit status 1`)
+ verifyRepairStatus(c, repair.RetryStatus)
+
+ // run again, it will be happy this time
+ err := rpr.Run()
+ c.Assert(err, IsNil)
+
+ s.verifyRundir(c, []string{
+ `^r0.done$`,
+ `^r0.retry$`,
+ `^r0.script$`,
+ `^work$`,
+ })
+ s.verifyOutput(c, "r0.done", `repair: canonical-1
+revision: 0
+summary: repair one
+output:
+happy now
+`)
+ verifyRepairStatus(c, repair.DoneStatus)
+}
+
+func (s *runScriptSuite) TestRepairHitsTimeout(c *C) {
+ r1 := sysdb.InjectTrusted(s.storeSigning.Trusted)
+ defer r1()
+ r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey})
+ defer r2()
+
+ restore := repair.MockDefaultRepairTimeout(100 * time.Millisecond)
+ defer restore()
+
+ script := `#!/bin/sh
+echo "output before timeout"
+sleep 100
+`
+ s.seqRepairs = []string{makeMockRepair(script)}
+ s.seqRepairs = s.signSeqRepairs(c, s.seqRepairs)
+
+ rpr, err := s.runner.Next("canonical")
+ c.Assert(err, IsNil)
+
+ err = rpr.Run()
+ c.Assert(err, IsNil)
+
+ s.verifyRundir(c, []string{
+ `^r0.retry$`,
+ `^r0.script$`,
+ `^work$`,
+ })
+ s.verifyOutput(c, "r0.retry", `repair: canonical-1
+revision: 0
+summary: repair one
+output:
+output before timeout
+
+repair canonical-1 revision 0 failed: repair did not finish within 100ms`)
+ verifyRepairStatus(c, repair.RetryStatus)
+}
+
+func (s *runScriptSuite) TestRepairHasCorrectPath(c *C) {
+ r1 := sysdb.InjectTrusted(s.storeSigning.Trusted)
+ defer r1()
+ r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey})
+ defer r2()
+
+ script := `#!/bin/sh
+echo PATH=$PATH
+ls -l ${PATH##*:}/repair
+`
+ s.seqRepairs = []string{makeMockRepair(script)}
+ s.seqRepairs = s.signSeqRepairs(c, s.seqRepairs)
+
+ rpr, err := s.runner.Next("canonical")
+ c.Assert(err, IsNil)
+
+ err = rpr.Run()
+ c.Assert(err, IsNil)
+
+ output, err := ioutil.ReadFile(filepath.Join(s.runDir, "r0.retry"))
+ c.Assert(err, IsNil)
+ c.Check(string(output), Matches, fmt.Sprintf(`(?ms).*^PATH=.*:.*/run/snapd/repair/tools.*`))
+ c.Check(string(output), Matches, `(?ms).*/repair -> /usr/lib/snapd/snap-repair`)
+
+ // run again and ensure no error happens
+ err = rpr.Run()
+ c.Assert(err, IsNil)
+
+}
diff -Nru snapd-2.28.5/cmd/snap-repair/staging.go snapd-2.29.3/cmd/snap-repair/staging.go
--- snapd-2.28.5/cmd/snap-repair/staging.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/cmd/snap-repair/staging.go 2017-10-23 06:17:27.000000000 +0000
@@ -0,0 +1,81 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+// +build withtestkeys withstagingkeys
+
+/*
+ * 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"
+
+ "github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/osutil"
+)
+
+const (
+ encodedStagingRepairRootAccountKey = `type: account-key
+authority-id: canonical
+public-key-sha3-384: 0GgXgD-RtfU0HJFBmaaiUQaNUFATl1oOlzJ44Bi4MbQAwcu8ektQLGBkKQ4JuA_O
+account-id: canonical
+name: repair-root
+since: 2017-07-07T00:00:00.0Z
+body-length: 1406
+sign-key-sha3-384: e2r8on4LgdxQSaW5T8mBD5oC2fSTktTVxOYa5w3kIP4_nNF6L7mt6fOJShdOGkKu
+
+AcbDTQRWhcGAASAA0D+AqdqBtgiaABSZ++NdDKcvFtpmEphDfpEAAo4MAWucmE5yVN9mHFRXvwU0
+ZkLQwPuiF5Vp5EP/kHyKgmmF9nKUBXnuZXfuH4vzH9ZuEfdxGc0On+XK4KwyPUzOXj2n1Rsxj0P5
+06wJ6QFghi6nORx3Hz4pxZH7SRANgZudwWE53+whbkJyU/psv4RXfxPnu3YGo0qPk/wGCfV8kkkH
+UFmeqJJEk14EGI+Kv9WlIVAqctryqf9mSXkgnhp4lzdGpsRCmRcw7boVjdOieFCJv+gVs7i52Yxu
+YyiA09XF8j85DSMl4s/TyN4bm9649beBCpTJNyb3Ep6lydGiZck8ChJRLDXGP2XZtRKXsBMNIfwP
+5qnLtX1/Sm1znag0grGbd3AUqL6ySAr42x8QIxZfqzk5DvQbF3xOiu2xzxTt1eB3069MlnFw99ui
+fLwlec7imbCiX3bryutCRhKgkJz4MbNsyHiW51k1l1IDbABey+gfVHpeBq/2aHK5qSy98dRBSYSj
+Ki++j8zR1ODequWy+OrF4cu6IQ35eunHQ2mRsJIE0xFGAjG3vCPJwzVoNS5m5R0ffncIUYdKxt/R
+W2mLo43qX0fquW5LvHyI14d3B3LYfKz05FmASJaE4A+/GQhM7kMCnmykro0MM6MU0sd2OruOZVVo
+z6GQ37Hyo/TGToyCr7qD/+tSq3dKYGyezl/Y4I589eqnc1DaMHL2ssiXDsbpSRHpnNqMUq6UNg4M
+NsUiDLaGsNJj1ft6A7jz+yoqJ74m3hlaQK1Rot8FXBkuJGoRKBahHbh/bfkGWChDvzY9ZpoUExY2
+rp7tNYEP/LEAI/RQd03sBnqd3V8YhggT2n6QCC4ikLKvUTE3RY0qn5aAa7KC1wVi+7SfdeVl3RBF
+Jyb9GCYfRUv/bfFH/TCZ9WVN1v/GIMcjBFGJf7H3cz/deela53XSaecYHuvFRpVfzmx28UcR8UY4
+5WqHfxnVQlY6DPv+kjzMzIEJGwgSAFc0d4wlSwS/Y1T0ednFRUyjMAxEUvE8tOLibtXw4q/srIFt
+OIgpd/xErcyi5Ddgt7EQoYo+rtVZ8x5EwR0+i7VAV+a3bnGSJW2LFEjt2RZUiMjohVZ4oOVuoDd2
+VQzMFv41flbyqjgHhtJSCIOKDg9uI2FHbQ5vrX9qBooS68YkBALwCq+P7nSxDxFuS0CgrzSH35FX
+VneOl68U74pxRgdlPJ0HI92oilrbTH8Ft0m5SzNsy+9ZZZtIDFQW+lx/ApixyifARFnZ3C3Gdx59
+FlFNbE75+X28joGtul2mPjJ1eI1dCwiFCF3R/rwfRmw3Wpv76re+EzVR1MJVCcTgC1lUoCJpKl1J
+n3PQLcR8J0iqswARAQAB
+
+AcLBXAQAAQoABgUCWYM7bQAKCRAHKljtl9kuLtCFD/4miBm0HyLE8GdboeUtWw+oOlH0AgabRqYi
+a1TpEJeYQIjnwDuCCPYtJxL1Rc+5dSNnbY9L+34NuaSyYMJY/FMuSS5iaNomGnj7YiAOds1+1/6h
+Z1bTm3ttZnphg5DxckYZLaKoYgRaOzAbiRM8l+2bDbXlq3KRxZ7o7D1V/xpPis8SWK57gQ7VppHI
+fcw5jnzWokWSowaKShimjJNCXMaeGdGJBLU1wcJC/XRf3tXSZecwMfL9CN/G8b17HvIFN/Pe3oS9
+QxYMQ0p3J3PF3F19Iow0VHi78hPKtVmJb5igwzBlGYFW7zZ3R35nJ7Iv6VW58G2HDDGMdBfZp930
+FbLb3mj8Yw3S5fcMZ09vpT7PK0tjFoVJtDFBOkrjvxVMEPRa0IJNcfl/hgPdp1/IFXWpZhfvk8a8
+qgzffxN+Ro/J4Jt9QrHM4sNwiEOjVvHY4cQ9GOfns9UqocmxYPDxElBNraCFOCSudZgXiyF7zUYF
+OnYqTDR4ChiZtmUqIiZr6rXgZTm1raGlqR7nsbDlkJtru7tzkgMRw8xFRolaQIKiyAwTewF7vLho
+imwYTRuYRMzft1q5EeRWR4XwtlIuqsXg3FCGTNIG4HiAFKrrNV7AOvVjIUSgpOcWv2leSiRQjgpY
+I9oD82ii+5rKvebnGIa0o+sWhYNFoviP/49DnDNJWA==
+`
+)
+
+func init() {
+ repairRootAccountKey, err := asserts.Decode([]byte(encodedStagingRepairRootAccountKey))
+ if err != nil {
+ panic(fmt.Sprintf("cannot decode trusted account-key: %v", err))
+ }
+ if osutil.GetenvBool("SNAPPY_USE_STAGING_STORE") {
+ trustedRepairRootKeys = append(trustedRepairRootKeys, repairRootAccountKey.(*asserts.AccountKey))
+ }
+}
diff -Nru snapd-2.28.5/cmd/snap-repair/trace.go snapd-2.29.3/cmd/snap-repair/trace.go
--- snapd-2.28.5/cmd/snap-repair/trace.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/cmd/snap-repair/trace.go 2017-10-23 06:17:27.000000000 +0000
@@ -0,0 +1,176 @@
+// -*- 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 (
+ "bufio"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "regexp"
+ "strconv"
+ "strings"
+
+ "github.com/snapcore/snapd/dirs"
+)
+
+// newRepairTraces returns all repairTrace about the given "brand" and "seq"
+// that can be found. brand, seq can be filepath.Glob expressions.
+func newRepairTraces(brand, seq string) ([]*repairTrace, error) {
+ matches, err := filepath.Glob(filepath.Join(dirs.SnapRepairRunDir, brand, seq, "*"))
+ if err != nil {
+ return nil, err
+ }
+
+ var repairTraces []*repairTrace
+ for _, match := range matches {
+ if trace := newRepairTraceFromPath(match); trace != nil {
+ repairTraces = append(repairTraces, trace)
+ }
+ }
+
+ return repairTraces, nil
+}
+
+// repairTrace holds information about a repair that was run.
+type repairTrace struct {
+ path string
+}
+
+// validRepairTraceName checks that the given name looks like a valid repair
+// trace
+var validRepairTraceName = regexp.MustCompile(`^r[0-9]+\.(done|skip|retry|running)$`)
+
+// newRepairTraceFromPath takes a repair log path like
+// the path /var/lib/snapd/repair/run/my-brand/1/r2.done
+// and contructs a repair log from that.
+func newRepairTraceFromPath(path string) *repairTrace {
+ rt := &repairTrace{path: path}
+ if !validRepairTraceName.MatchString(filepath.Base(path)) {
+ return nil
+ }
+ return rt
+}
+
+// Repair returns the repair human readable string in the form $brand-$id
+func (rt *repairTrace) Repair() string {
+ seq := filepath.Base(filepath.Dir(rt.path))
+ brand := filepath.Base(filepath.Dir(filepath.Dir(rt.path)))
+
+ return fmt.Sprintf("%s-%s", brand, seq)
+}
+
+// Revision returns the revision of the repair
+func (rt *repairTrace) Revision() string {
+ rev, err := revFromFilepath(rt.path)
+ if err != nil {
+ // this can never happen because we check that path starts
+ // with the right prefix. However handle the case just in
+ // case.
+ return "-"
+ }
+ return rev
+}
+
+// Summary returns the summary of the repair that was run
+func (rt *repairTrace) Summary() string {
+ f, err := os.Open(rt.path)
+ if err != nil {
+ return "-"
+ }
+ defer f.Close()
+
+ needle := "summary: "
+ scanner := bufio.NewScanner(f)
+ for scanner.Scan() {
+ s := scanner.Text()
+ if strings.HasPrefix(s, needle) {
+ return s[len(needle):]
+ }
+ }
+
+ return "-"
+}
+
+// Status returns the status of the given repair {done,skip,retry,running}
+func (rt *repairTrace) Status() string {
+ return filepath.Ext(rt.path)[1:]
+}
+
+func indentPrefix(level int) string {
+ return strings.Repeat(" ", level)
+}
+
+// WriteScriptIndented outputs the script that produced this repair output
+// to the given writer w with the indent level given by indent.
+func (rt *repairTrace) WriteScriptIndented(w io.Writer, indent int) error {
+ scriptPath := rt.path[:strings.LastIndex(rt.path, ".")] + ".script"
+ f, err := os.Open(scriptPath)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ scanner := bufio.NewScanner(f)
+ for scanner.Scan() {
+ fmt.Fprintf(w, "%s%s\n", indentPrefix(indent), scanner.Text())
+ }
+ if scanner.Err() != nil {
+ return scanner.Err()
+ }
+ return nil
+}
+
+// WriteOutputIndented outputs the repair output to the given writer w
+// with the indent level given by indent.
+func (rt *repairTrace) WriteOutputIndented(w io.Writer, indent int) error {
+ f, err := os.Open(rt.path)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ scanner := bufio.NewScanner(f)
+ // move forward in the log to where the actual script output starts
+ for scanner.Scan() {
+ if scanner.Text() == "output:" {
+ break
+ }
+ }
+ // write the script output to w
+ for scanner.Scan() {
+ fmt.Fprintf(w, "%s%s\n", indentPrefix(indent), scanner.Text())
+ }
+ if scanner.Err() != nil {
+ return scanner.Err()
+ }
+ return nil
+}
+
+// revFromFilepath is a helper that extracts the revision number from the
+// filename of the repairTrace
+func revFromFilepath(name string) (string, error) {
+ var rev int
+ if _, err := fmt.Sscanf(filepath.Base(name), "r%d.", &rev); err == nil {
+ return strconv.Itoa(rev), nil
+ }
+ return "", fmt.Errorf("cannot find revision in %q", name)
+}
diff -Nru snapd-2.28.5/cmd/snap-repair/trace_test.go snapd-2.29.3/cmd/snap-repair/trace_test.go
--- snapd-2.28.5/cmd/snap-repair/trace_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/cmd/snap-repair/trace_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -0,0 +1,66 @@
+// -*- 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 (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/dirs"
+)
+
+func makeMockRepairState(c *C) {
+ // the canonical script dir content
+ basedir := filepath.Join(dirs.SnapRepairRunDir, "canonical/1")
+ err := os.MkdirAll(basedir, 0700)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(filepath.Join(basedir, "r3.retry"), []byte("repair: canonical-1\nsummary: repair one\noutput:\nretry output"), 0600)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(filepath.Join(basedir, "r3.script"), []byte("#!/bin/sh\necho retry output"), 0700)
+ c.Assert(err, IsNil)
+
+ // my-brand
+ basedir = filepath.Join(dirs.SnapRepairRunDir, "my-brand/1")
+ err = os.MkdirAll(basedir, 0700)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(filepath.Join(basedir, "r1.done"), []byte("repair: my-brand-1\nsummary: my-brand repair one\noutput:\ndone output"), 0600)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(filepath.Join(basedir, "r1.script"), []byte("#!/bin/sh\necho done output"), 0700)
+ c.Assert(err, IsNil)
+
+ basedir = filepath.Join(dirs.SnapRepairRunDir, "my-brand/2")
+ err = os.MkdirAll(basedir, 0700)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(filepath.Join(basedir, "r2.skip"), []byte("repair: my-brand-2\nsummary: my-brand repair two\noutput:\nskip output"), 0600)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(filepath.Join(basedir, "r2.script"), []byte("#!/bin/sh\necho skip output"), 0700)
+ c.Assert(err, IsNil)
+
+ basedir = filepath.Join(dirs.SnapRepairRunDir, "my-brand/3")
+ err = os.MkdirAll(basedir, 0700)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(filepath.Join(basedir, "r0.running"), []byte("repair: my-brand-3\nsummary: my-brand repair three\noutput:\nrunning output"), 0600)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(filepath.Join(basedir, "r0.script"), []byte("#!/bin/sh\necho running output"), 0700)
+ c.Assert(err, IsNil)
+}
diff -Nru snapd-2.28.5/cmd/snap-repair/trusted.go snapd-2.29.3/cmd/snap-repair/trusted.go
--- snapd-2.28.5/cmd/snap-repair/trusted.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/cmd/snap-repair/trusted.go 2017-10-23 06:17:27.000000000 +0000
@@ -0,0 +1,89 @@
+// -*- 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"
+
+ "github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/osutil"
+)
+
+const (
+ encodedRepairRootAccountKey = `type: account-key
+authority-id: canonical
+public-key-sha3-384: nttW6NfBXI_E-00u38W-KH6eiksfQNXuI7IiumoV49_zkbhM0sYTzSnFlwZC-W4t
+account-id: canonical
+name: repair-root
+since: 2017-07-07T00:00:00.0Z
+body-length: 1406
+sign-key-sha3-384: -CvQKAwRQ5h3Ffn10FILJoEZUXOv6km9FwA80-Rcj-f-6jadQ89VRswHNiEB9Lxk
+
+AcbDTQRWhcGAASAAtlCIEilQCQh9Ffjn9IxD+5FtWfdKdJHrQdtFPy/2Q1kOvC++ef/3bG1xMwao
+tue9K0HCMtv3apHS1C32Y8JBMD8oykRpAd5H05+sgzZr3kCHIvgogKFsXfdd5+W5Q1+59Vy/81UH
+tJCs99wBwboNh/pMCXBGI3jDRN1f7hOxcHUIW+KTaHCVZnrXXmCn6Oe6brR9qiUXgEB2I6rBT/Fe
+cumdfvFN/zSsJ3Vvv9IbTfHYAZD82NrSqz4UZ3WJarIaxlykgLJaZN4bqSQYPYsc8lLlwjQeGloW
++r8dIypKOzPnUYurzWcNzcCnNCT1zhpY/IK2rFcZbN5/mP2/5PtjFlbX88aPGPbOTqYANmxfboCx
+wo4D4aS7PD6gLC7XM8bgh8BACpmG3BnskL7F/9IHMl85SUHFIya2fDu7A7HqNUn7cpENGbHojj7G
+J2s2965FSRuIvp69wEmknYD/kahjT1+Vy94D2rVB7mjtTruPueF2KTpo2jRXFM+ABq+T9ybjXD6f
+UuSXu5xeg0Cv1sxOh4O4b45uaCXb8B74chEUW+cb3cV0NGE/QgBJUBeS68vUI8lqQFmPInci6Md4
+oiKFVbloL0ZmOGj73Xv2uAcexAK9bEiI+adVS2x9r4eFwtkST3XG0t/kw7eLgAVjtRcpmD6EuZ0Q
+ulAJHEsl7Sazm8GRU4GtZWaCajVb4n5TS1lin2nqUXwqxRUA3smLqkQoYXQni9vmhez4dEP4MVvq
+/0IdI50UGME5Fzc8hUhYzvbNS8g+VOeAK/qj3dzcdF9+n940AQI16Fcxi1/xFk8A4dw3AaDl4XnJ
+piyStE+xi95ne5HJW8r/f/JQos8I6QR5w7pe2URbgUdVPgQLv3r/4dS/X3aP+oakrPR7JuAVdP62
+vsjF4jK8Bl69mcF434xpshnbnW/f7XHomPY4gp8y7kD2/DdEs5hvaTHIPp25DEYhqjt3gfmMuUXi
+Mb5oy9KZp3ff8Squ+XNWSGQSyhX14xcQwM8QjNQnAisNg2hYwSM2n8q5IDWiwJQkFSriP5tMsa8E
+DMGI3LXUZKRJll9dQBjs6VzApT4/Ee0ZvSni0d2cWm3wkqFQudRpqz3JSwQ7jal0W5e0UhNeHh/W
+7nACD5hvcwF7UgUz0r8adlOy+nyfvWte65nbcRrIH7GS1xdgS0e9eW4znsplp7s/Z3gMhi8CN5TY
+0nZW82TTl69Wvn13SGJye0yJSjiy4KS0iRE6BwAt7dGAMs5c62IlBsWEHLmCW1/lixWA9YXT9iji
+G7DKSoofnsvqVP2wIQZxxt4xHMjUGXecyx8QX4BznwsV1vbzHOIG4a3Z9A1X1L3yh3ZbazFVeEE9
+7Dhz9hGYfd3PvwARAQAB
+
+AcLDXAQAAQoABgUCWbuO2gAKCRDUpVvql9g3IOPcIADZWObdYMKh2SblWXxUchnc3S4LDRtL8v+Q
+HdnXO5+dJmsj5LWhdsB7lz373aaylFTwHpNDWcDdAu7ulP0vr7zJURo1jGOo7VojSEeuAAu3YhwL
+2pR0p5Me0wuxl/pCX0x0nfDSeeTw11kproyN0GwJaErKEmyQyfOgVr2jN5sl1gBqQtKgG5gqZzC3
+oFH1HYGPl2kfAorxFw7MoPy4aRFaxUJfx4x6bEktgkkFT7AWGmawVwcpiiUbbpe9CPLEsn6yqJI9
+5XmQ3dJjp/6Y5D7x04LRH3Q5fajRcpdBrC0tDsT9UDbSRtIyo0KDNVHwQalLa2Sv51DV+Fy4eneM
+Lgu+oCUOnBecXIWnX+k0uyDW8aLHYapx8etpW3pln/hMRd8JxYVYAqDn7G2AYeSGS/4lzCJzysW2
+2/4RhH9Ql8ea0nSWVTJr3pmXKlPSH/OOy9IADEDUuEdvyMcq3YOXA9E4L3g9vR31JH+++swcTQPz
+rnGx0mE+TCQRWok/NZ1QNv4eNZlnLXdNS1DoV/kRqU04cblYYUoSO34mkjPEJ8ti+VzKh/PTA6/7
+1feFX276Zam/6b2rBLWCWYdblDM9oLAR4PfzntBZW4LzzOIb95IwiK4JoDoBr3x4+RxvxgVQLvHt
+8990uQ0se9+8BtLVFtd07NbldHXRBlZkq22a8CeVYrU3YZEtuEBvlGDpkdegw/vcvgHUUK1f8dXJ
+0+9oW2yQOLAguuPZ67GFSgdTnvR5dQYZZR2EbQJlPMOFA3loKeUxHSR9w7E3SFqXGqN1v6APDK0V
+lpVFq+rYlprvbf4GB0oR8yQOGtlxf+Ag3Nnx+90nlmtTK4k7tQpLzuAXGGREDCtn0y/YvWvGt6kN
+EV5Q/mAVe2/CtAUvfyX7V3xlzYCrJT9DBcCBMaUUekFrwvZi13WYJIn7YE2Qmam7ZsXdb991PoFv
++c6Pmeg6w3y7D+Vj4Yfi8IrjPrc6765DaaZxFyMia9GEQKHChZDkiEiAM6RfwlC5YXGzCroaZi0Y
+Knf/UkUWoa/jKZgQNiqrZ9oGmbURLeXkkHzpcFitwjzWr6tNScCzNIqs/uxTxbFM8fJu1gSmauEY
+TE1rn62SiuHNRKJqfLcCHucStK10knHkHTAJ3avS7rBz0Dy8UOa77bOjyei5n2rkyXztL2YjjGYh
+8jEt00xcvwJGePBfH10gCgTFWdfhfcP9/muKgiOSErQlHPypnr4vqO0PU9XDp106FFWyyNPd95kC
+l5IF9WMfl7YHpT0Ph7kBYwg9sKF/7oCVdbT5CoImxkE5DTkWB8xX6W/BhuMrp1rzTHFFGVd1ppb7
+EMUll4dd78OWonMlIgsMRuTSn93awb4X8xSJhRi9
+`
+)
+
+func init() {
+ repairRootAccountKey, err := asserts.Decode([]byte(encodedRepairRootAccountKey))
+ if err != nil {
+ panic(fmt.Sprintf("cannot decode trusted account-key: %v", err))
+ }
+ if !osutil.GetenvBool("SNAPPY_USE_STAGING_STORE") {
+ trustedRepairRootKeys = append(trustedRepairRootKeys, repairRootAccountKey.(*asserts.AccountKey))
+ }
+}
diff -Nru snapd-2.28.5/cmd/snap-seccomp/export_test.go snapd-2.29.3/cmd/snap-seccomp/export_test.go
--- snapd-2.28.5/cmd/snap-seccomp/export_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/cmd/snap-seccomp/export_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -22,7 +22,6 @@
var (
Compile = compile
SeccompResolver = seccompResolver
- FindGid = findGid
)
func MockArchUbuntuArchitecture(f func() string) (restore func()) {
diff -Nru snapd-2.28.5/cmd/snap-seccomp/main.go snapd-2.29.3/cmd/snap-seccomp/main.go
--- snapd-2.28.5/cmd/snap-seccomp/main.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/cmd/snap-seccomp/main.go 2017-10-27 12:31:06.000000000 +0000
@@ -28,7 +28,6 @@
//#include
//#include
//#include
-//#include
//#include
//#include
//#include
@@ -136,11 +135,6 @@
// 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 (
@@ -149,13 +143,10 @@
"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
@@ -467,137 +458,6 @@
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 == "" {
@@ -759,13 +619,14 @@
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
- // FIXME: right now complain mode is the equivalent to
- // unrestricted. We'll want to change this once we
- // seccomp logging is in order.
- //
- // special case: unrestricted means we switch to an allow-all
- // filter and are done
- if line == "@unrestricted" || line == "@complain" {
+ // special case: unrestricted means we stop early, we just
+ // write this special tag and evalulate in snap-confine
+ if line == "@unrestricted" {
+ return osutil.AtomicWrite(out, bytes.NewBufferString(line+"\n"), 0644, 0)
+ }
+ // complain mode is a "allow-all" filter for now until
+ // we can land https://github.com/snapcore/snapd/pull/3998
+ if line == "@complain" {
secFilter, err = seccomp.NewFilter(seccomp.ActAllow)
if err != nil {
return fmt.Errorf("cannot create seccomp filter: %s", err)
@@ -790,27 +651,36 @@
}
// write atomically
- dir, err := os.Open(filepath.Dir(out))
- if err != nil {
- return err
- }
- defer dir.Close()
-
- fout, err := os.Create(out + ".tmp")
+ fout, err := osutil.NewAtomicFile(out, 0644, 0, -1, -1)
if err != nil {
return err
}
defer fout.Close()
- if err := secFilter.ExportBPF(fout); err != nil {
+
+ if err := secFilter.ExportBPF(fout.File); err != nil {
return err
}
- if err := fout.Sync(); err != nil {
- return err
+ return fout.Commit()
+}
+
+// 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_]*$")
+
+// findUid returns the identifier of the given UNIX user name.
+func findUid(username string) (uint64, error) {
+ if !userGroupNamePattern.MatchString(username) {
+ return 0, fmt.Errorf("%q must be a valid username", username)
}
- if err := os.Rename(out+".tmp", out); err != nil {
- return err
+ return osutil.FindUid(username)
+}
+
+// findGid returns the identifier of the given UNIX group name.
+func findGid(group string) (uint64, error) {
+ if !userGroupNamePattern.MatchString(group) {
+ return 0, fmt.Errorf("%q must be a valid group name", group)
}
- return dir.Sync()
+ return osutil.FindGid(group)
}
func showSeccompLibraryVersion() error {
diff -Nru snapd-2.28.5/cmd/snap-seccomp/main_test.go snapd-2.29.3/cmd/snap-seccomp/main_test.go
--- snapd-2.28.5/cmd/snap-seccomp/main_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/cmd/snap-seccomp/main_test.go 2017-10-30 18:11:24.000000000 +0000
@@ -36,6 +36,8 @@
"github.com/snapcore/snapd/arch"
main "github.com/snapcore/snapd/cmd/snap-seccomp"
+ "github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/release"
)
// Hook up check.v1 into the "go test" runner
@@ -164,11 +166,28 @@
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)
+
+ // Build 32bit runner on amd64 to test non-native syscall handling.
+ // Ideally we would build for ppc64el->powerpc and arm64->armhf but
+ // it seems tricky to find the right gcc-multilib for this.
+ if arch.UbuntuArchitecture() == "amd64" {
+ cmd = exec.Command(cmd.Args[0], cmd.Args[1:]...)
+ cmd.Args = append(cmd.Args, "-m32")
+ for i, k := range cmd.Args {
+ if k == s.seccompSyscallRunner {
+ cmd.Args[i] = s.seccompSyscallRunner + ".m32"
+ }
+ }
+ if output, err := cmd.CombinedOutput(); err != nil {
+ fmt.Printf("cannot build multi-lib syscall runner: %v\n%s", err, output)
+ }
+ }
}
// Runs the policy through the kernel:
@@ -190,10 +209,6 @@
// 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)
-
// Common syscalls we need to allow for a minimal statically linked
// c program.
//
@@ -217,26 +232,63 @@
# arm64
readlinkat
faccessat
+# i386 from amd64
+restart_syscall
`
bpfPath := filepath.Join(c.MkDir(), "bpf")
- err = main.Compile([]byte(common+seccompWhitelist), bpfPath)
+ err := main.Compile([]byte(common+seccompWhitelist), bpfPath)
c.Assert(err, IsNil)
+ // default syscall runner
+ syscallRunner := s.seccompSyscallRunner
+
// syscallName;arch;arg1,arg2...
l := strings.Split(bpfInput, ";")
- if len(l) > 1 && l[1] != "native" {
- c.Logf("cannot use non-native in runBpfInKernel")
- return
+ syscallName := l[0]
+ syscallArch := "native"
+ if len(l) > 1 {
+ syscallArch = l[1]
}
- var syscallRunnerArgs [7]string
- syscallNr, err := seccomp.GetSyscallFromName(l[0])
+ syscallNr, err := seccomp.GetSyscallFromName(syscallName)
c.Assert(err, IsNil)
- if syscallNr < 0 {
- c.Skip(fmt.Sprintf("skipping %v because it resolves to negative %v", l[0], syscallNr))
+
+ // Check if we want to test non-native architecture
+ // handling. Doing this via the in-kernel tests is tricky as
+ // we need a kernel that can run the architecture and a
+ // compiler that can produce the required binaries. Currently
+ // we only test amd64 running i386 here.
+ if syscallArch != "native" {
+ syscallNr, err = seccomp.GetSyscallFromNameByArch(syscallName, main.UbuntuArchToScmpArch(syscallArch))
+ c.Assert(err, IsNil)
+
+ switch syscallArch {
+ case "amd64":
+ // default syscallRunner
+ case "i386":
+ syscallRunner = s.seccompSyscallRunner + ".m32"
+ default:
+ c.Errorf("unexpected non-native arch: %s", syscallArch)
+ }
+ }
+ switch {
+ case syscallNr == -101:
+ // "socket"
+ // see libseccomp: _s390x_sock_demux(), _x86_sock_demux()
+ // the -101 is translated to 359 (socket)
+ syscallNr = 359
+ case syscallNr == -10165:
+ // "mknod" on arm64 is not available at all on arm64
+ // only "mknodat" but libseccomp will not generate a
+ // "mknodat" whitelist, it geneates a whitelist with
+ // syscall -10165 (!?!) so we cannot test this.
+ c.Skip("skipping mknod tests on arm64")
+ case syscallNr < 0:
+ c.Errorf("failed to resolve %v: %v", l[0], syscallNr)
return
}
+ var syscallRunnerArgs [7]string
syscallRunnerArgs[0] = strconv.FormatInt(int64(syscallNr), 10)
if len(l) > 2 {
args := strings.Split(l[2], ",")
@@ -253,7 +305,7 @@
}
}
- cmd := exec.Command(s.seccompBpfLoader, bpfPath, s.seccompSyscallRunner, syscallRunnerArgs[0], syscallRunnerArgs[1], syscallRunnerArgs[2], syscallRunnerArgs[3], syscallRunnerArgs[4], syscallRunnerArgs[5], syscallRunnerArgs[6])
+ cmd := exec.Command(s.seccompBpfLoader, bpfPath, syscallRunner, 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
@@ -272,6 +324,18 @@
}
}
+func (s *snapSeccompSuite) TestUnrestricted(c *C) {
+ inp := "@unrestricted\n"
+ outPath := filepath.Join(c.MkDir(), "bpf")
+ err := main.Compile([]byte(inp), outPath)
+ c.Assert(err, IsNil)
+
+ content, err := ioutil.ReadFile(outPath)
+ c.Assert(err, IsNil)
+ c.Check(content, DeepEquals, []byte(inp))
+
+}
+
// TestCompile iterates over a range of textual seccomp whitelist rules and
// mocked kernel syscall input. For each rule, the test consists of compiling
// the rule into a bpf program and then running that program on a virtual bpf
@@ -287,7 +351,7 @@
// {"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")
+ shadowGid, err := osutil.FindGid("shadow")
c.Assert(err, IsNil)
for _, t := range []struct {
@@ -296,7 +360,6 @@
expected int
}{
// special
- {"@unrestricted", "execve", main.SeccompRetAllow},
{"@complain", "execve", main.SeccompRetAllow},
// trivial allow
@@ -400,6 +463,10 @@
// 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) {
+ if release.ReleaseInfo.ID == "ubuntu" && release.ReleaseInfo.VersionID == "14.04" {
+ c.Skip("14.04/i386 uses socketcall which cannot be tested here")
+ }
+
for _, t := range []struct {
seccompWhitelist string
bpfInput string
@@ -512,6 +579,10 @@
// ported from test_restrictions_working_args_socket
func (s *snapSeccompSuite) TestRestrictionsWorkingArgsSocket(c *C) {
+ if release.ReleaseInfo.ID == "ubuntu" && release.ReleaseInfo.VersionID == "14.04" {
+ c.Skip("14.04/i386 uses socketcall which cannot be tested here")
+ }
+
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)
@@ -701,12 +772,6 @@
// on amd64 we add compat i386
{"amd64", "read", "read;i386", main.SeccompRetAllow},
{"amd64", "read", "read;amd64", main.SeccompRetAllow},
- // on arm64 we add compat armhf
- {"arm64", "read", "read;armhf", main.SeccompRetAllow},
- {"arm64", "read", "read;arm64", main.SeccompRetAllow},
- // on ppc64 we add compat powerpc
- {"ppc64", "read", "read;powerpc", main.SeccompRetAllow},
- {"ppc64", "read", "read;ppc64", main.SeccompRetAllow},
} {
// It is tricky to mock the architecture here because
// seccomp is always adding the native arch to the seccomp
diff -Nru snapd-2.28.5/cmd/snap-update-ns/bootstrap.c snapd-2.29.3/cmd/snap-update-ns/bootstrap.c
--- snapd-2.28.5/cmd/snap-update-ns/bootstrap.c 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/cmd/snap-update-ns/bootstrap.c 2017-10-23 06:17:27.000000000 +0000
@@ -15,12 +15,20 @@
*
*/
+// IMPORTANT: all the code in this file may be run with elevated privileges
+// when invoking snap-update-ns from the setuid snap-confine.
+//
+// This file is a preprocessor for snap-update-ns' main() function. It will
+// perform input validation and clear the environment so that snap-update-ns'
+// go code runs with safe inputs when called by the setuid() snap-confine.
+
#include "bootstrap.h"
#include
#include
#include
#include
+#include
#include
#include
#include
@@ -43,12 +51,21 @@
bootstrap_msg = "cannot open /proc/self/cmdline";
return -1;
}
- memset(buf, 0, buf_size);
+ // Ensure buf is initialized to all NULLs since our parsing of cmdline with
+ // its embedded NULLs depends on this. Use for loop instead of memset() to
+ // guarantee this initialization won't be optimized away.
+ size_t i;
+ for (i = 0; i < buf_size; ++i) {
+ buf[i] = '\0';
+ }
ssize_t num_read = read(fd, buf, buf_size);
if (num_read < 0) {
bootstrap_errno = errno;
bootstrap_msg = "cannot read /proc/self/cmdline";
- } else if (num_read == buf_size) {
+ } else if (num_read == 0) {
+ bootstrap_errno = errno;
+ bootstrap_msg = "unexpectedly /proc/self/cmdline is empty";
+ } else if (num_read == buf_size && buf_size > 0 && buf[buf_size - 1] != '\0') {
bootstrap_errno = 0;
bootstrap_msg = "cannot fit all of /proc/self/cmdline, buffer too small";
num_read = -1;
@@ -57,36 +74,35 @@
return num_read;
}
-// find_argv0 scans the command line buffer and looks for the 0st argument.
+// find_snap_name scans the command line buffer and looks for the 1st argument.
+// if the 1st argument exists but is empty NULL is returned.
const char*
-find_argv0(char* buf, size_t num_read)
+find_snap_name(const char* buf)
{
- // cmdline is an array of NUL ('\0') separated strings.
- size_t argv0_len = strnlen(buf, num_read);
- if (argv0_len == num_read) {
- // ensure that the buffer is properly terminated.
+ // Skip the zeroth argument as well as any options. We know buf is NUL
+ // padded and terminated from read_cmdline().
+ do {
+ size_t arg_len = strlen(buf);
+ buf += arg_len + 1;
+ } while (buf[0] == '-');
+
+ const char* snap_name = buf;
+ if (strlen(snap_name) == 0) {
return NULL;
}
- return buf;
+ return snap_name;
}
-// find_snap_name scans the command line buffer and looks for the 1st argument.
-// if the 1st argument exists but is empty NULL is returned.
const char*
-find_snap_name(char* buf, size_t num_read)
+find_1st_option(const char* buf)
{
- // cmdline is an array of NUL ('\0') separated strings. We can skip over
- // the first entry (program name) and look at the second entry, in our case
- // it should be the snap name.
- size_t argv0_len = strnlen(buf, num_read);
- if (argv0_len + 1 >= num_read) {
- return NULL;
- }
- char* snap_name = &buf[argv0_len + 1];
- if (*snap_name == '\0') {
- return NULL;
+ // Skip the zeroth argument and find the first command line option.
+ // We know buf is NUL padded and terminated from read_cmdline().
+ size_t pos = strlen(buf) + 1;
+ if (buf[pos] == '-') {
+ return &buf[pos];
}
- return snap_name;
+ return NULL;
}
// setns_into_snap switches mount namespace into that of a given snap.
@@ -94,17 +110,18 @@
setns_into_snap(const char* snap_name)
{
// Construct the name of the .mnt file to open.
- char buf[PATH_MAX];
+ char buf[PATH_MAX] = {
+ 0,
+ };
int n = snprintf(buf, sizeof buf, "/run/snapd/ns/%s.mnt", snap_name);
if (n >= sizeof buf || n < 0) {
- bootstrap_errno = errno;
+ bootstrap_errno = 0;
bootstrap_msg = "cannot format mount namespace file name";
return -1;
}
- // Open the mount namespace file, note that we don't specify O_NOFOLLOW as
- // that file is always a special, broken symbolic link.
- int fd = open(buf, O_RDONLY | O_CLOEXEC);
+ // Open the mount namespace file.
+ int fd = open(buf, O_RDONLY | O_CLOEXEC | O_NOFOLLOW);
if (fd < 0) {
bootstrap_errno = errno;
bootstrap_msg = "cannot open mount namespace file";
@@ -122,37 +139,105 @@
return err;
}
-// partially_validate_snap_name performs partial validation of the given name.
-// The goal is to ensure that there are no / or .. in the name.
-int partially_validate_snap_name(const char* snap_name)
+// TODO: reuse the code from snap-confine, if possible.
+static int skip_lowercase_letters(const char** p)
{
- // NOTE: neither set bootstrap_{msg,errno} but the return value means that
- // bootstrap does nothing. The name is re-validated by golang.
- if (strstr(snap_name, "..") != NULL) {
- return -1;
+ int skipped = 0;
+ const char* c;
+ for (c = *p; *c >= 'a' && *c <= 'z'; ++c) {
+ skipped += 1;
}
- if (strchr(snap_name, '/') != NULL) {
- return -1;
+ *p = (*p) + skipped;
+ return skipped;
+}
+
+// TODO: reuse the code from snap-confine, if possible.
+static int skip_digits(const char** p)
+{
+ int skipped = 0;
+ const char* c;
+ for (c = *p; *c >= '0' && *c <= '9'; ++c) {
+ skipped += 1;
}
+ *p = (*p) + skipped;
+ return skipped;
}
-// bootstrap prepares snap-update-ns to work in the namespace of the snap given
-// on command line.
-void bootstrap(void)
+// TODO: reuse the code from snap-confine, if possible.
+static int skip_one_char(const char** p, char c)
{
- // We don't have argc/argv so let's imitate that by reading cmdline
- char cmdline[1024];
- memset(cmdline, 0, sizeof cmdline);
- ssize_t num_read;
- if ((num_read = read_cmdline(cmdline, sizeof cmdline)) < 0) {
- return;
+ if (**p == c) {
+ *p += 1;
+ return 1;
+ }
+ return 0;
+}
+
+// validate_snap_name performs full validation of the given name.
+int validate_snap_name(const char* snap_name)
+{
+ // NOTE: This function should be synchronized with the two other
+ // implementations: sc_snap_name_validate and snap.ValidateName.
+
+ // Ensure that name is not NULL
+ if (snap_name == NULL) {
+ bootstrap_msg = "snap name cannot be NULL";
+ return -1;
+ }
+ // This is a regexp-free routine hand-codes the following pattern:
+ //
+ // "^([a-z0-9]+-?)*[a-z](-?[a-z0-9])*$"
+ //
+ // The only motivation for not using regular expressions is so that we
+ // don't run untrusted input against a potentially complex regular
+ // expression engine.
+ const char* p = snap_name;
+ if (skip_one_char(&p, '-')) {
+ bootstrap_msg = "snap name cannot start with a dash";
+ return -1;
+ }
+ bool got_letter = false;
+ for (; *p != '\0';) {
+ if (skip_lowercase_letters(&p) > 0) {
+ got_letter = true;
+ continue;
+ }
+ if (skip_digits(&p) > 0) {
+ continue;
+ }
+ if (skip_one_char(&p, '-') > 0) {
+ if (*p == '\0') {
+ bootstrap_msg = "snap name cannot end with a dash";
+ return -1;
+ }
+ if (skip_one_char(&p, '-') > 0) {
+ bootstrap_msg = "snap name cannot contain two consecutive dashes";
+ return -1;
+ }
+ continue;
+ }
+ bootstrap_msg = "snap name must use lower case letters, digits or dashes";
+ return -1;
+ }
+ if (!got_letter) {
+ bootstrap_msg = "snap name must contain at least one letter";
+ return -1;
}
- // Find the name of the called program. If it is ending with "-test" then do nothing.
+ bootstrap_msg = NULL;
+ return 0;
+}
+
+// process_arguments parses given cmdline which must be list of strings separated with NUL bytes.
+// cmdline is an array of NUL ('\0') separated strings and guaranteed to be
+// NUL-terminated via read_cmdline().
+void process_arguments(const char* cmdline, const char** snap_name_out, bool* should_setns_out)
+{
+ // Find the name of the called program. If it is ending with ".test" then do nothing.
// NOTE: This lets us use cgo/go to write tests without running the bulk
- // of the code automatically. In snapd we can just set the required
- // environment variable.
- const char* argv0 = find_argv0(cmdline, (size_t)num_read);
+ // of the code automatically.
+ //
+ const char* argv0 = cmdline;
if (argv0 == NULL) {
bootstrap_errno = 0;
bootstrap_msg = "argv0 is corrupted";
@@ -165,23 +250,78 @@
return;
}
+ // When we are running under "--from-snap-confine" option skip the setns
+ // call as snap-confine has already placed us in the right namespace.
+ const char* option = find_1st_option(cmdline);
+ bool should_setns = true;
+ if (option != NULL) {
+ if (strncmp(option, "--from-snap-confine", strlen("--from-snap-confine")) == 0) {
+ should_setns = false;
+ } else {
+ bootstrap_errno = 0;
+ bootstrap_msg = "unsupported option";
+ return;
+ }
+ }
+
// Find the name of the snap by scanning the cmdline. If there's no snap
// name given, just bail out. The go parts will scan this too.
- const char* snap_name = find_snap_name(cmdline, (size_t)num_read);
+ const char* snap_name = find_snap_name(cmdline);
if (snap_name == NULL) {
+ bootstrap_errno = 0;
+ bootstrap_msg = "snap name not provided";
return;
}
- // Look for known offenders in the snap name (.. and /) and do nothing if
- // those are found. The golang code will validate snap name and print a
- // proper error message but this just ensures we don't try to open / setns
- // anything unusual.
- if (partially_validate_snap_name(snap_name) < 0) {
+ // Ensure that the snap name is valid so that we don't blindly setns into
+ // something that is controlled by a potential attacker.
+ if (validate_snap_name(snap_name) < 0) {
+ bootstrap_errno = 0;
+ // bootstap_msg is set by validate_snap_name;
return;
}
+ // We have a valid snap name now so let's store it.
+ if (snap_name_out != NULL) {
+ *snap_name_out = snap_name;
+ }
+ if (should_setns_out != NULL) {
+ *should_setns_out = should_setns;
+ }
+ bootstrap_errno = 0;
+ bootstrap_msg = NULL;
+}
- // Switch the mount namespace to that of the snap given on command line.
- if (setns_into_snap(snap_name) < 0) {
+// bootstrap prepares snap-update-ns to work in the namespace of the snap given
+// on command line.
+void bootstrap(void)
+{
+ // We may have been started via a setuid-root snap-confine. In order to
+ // prevent environment-based attacks we start by erasing all environment
+ // variables.
+ if (clearenv() != 0) {
+ bootstrap_errno = 0;
+ bootstrap_msg = "bootstrap could not clear the environment";
return;
}
+ // We don't have argc/argv so let's imitate that by reading cmdline
+ // NOTE: use explicit initialization to avoid optimizing-out memset.
+ char cmdline[1024] = {
+ 0,
+ };
+ ssize_t num_read;
+ if ((num_read = read_cmdline(cmdline, sizeof cmdline)) < 0) {
+ // read_cmdline sets bootstrap_{errno,msg}
+ return;
+ }
+
+ // Analyze the read process cmdline to find the snap name and decide if we
+ // should use setns to jump into the mount namespace of a particular snap.
+ // This is spread out for easier testability.
+ const char* snap_name = NULL;
+ bool should_setns = false;
+ process_arguments(cmdline, &snap_name, &should_setns);
+ if (snap_name != NULL && should_setns) {
+ setns_into_snap(snap_name);
+ // setns_into_snap sets bootstrap_{errno,msg}
+ }
}
diff -Nru snapd-2.28.5/cmd/snap-update-ns/bootstrap.go snapd-2.29.3/cmd/snap-update-ns/bootstrap.go
--- snapd-2.28.5/cmd/snap-update-ns/bootstrap.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/cmd/snap-update-ns/bootstrap.go 2017-10-23 06:17:27.000000000 +0000
@@ -48,6 +48,9 @@
ErrNoNamespace = errors.New("cannot update mount namespace that was not created yet")
)
+// IMPORTANT: all the code in this section may be run with elevated privileges
+// when invoking snap-update-ns from the setuid snap-confine.
+
// BootstrapError returns error (if any) encountered in pre-main C code.
func BootstrapError() error {
if C.bootstrap_msg == nil {
@@ -64,32 +67,53 @@
return fmt.Errorf("%s", C.GoString(C.bootstrap_msg))
}
+// END IMPORTANT
+
// readCmdline is a wrapper around the C function read_cmdline.
func readCmdline(buf []byte) C.ssize_t {
return C.read_cmdline((*C.char)(unsafe.Pointer(&buf[0])), C.size_t(cap(buf)))
}
-// findArgv0 parses the argv-like array and finds the 0st argument.
-func findArgv0(buf []byte) *string {
- if ptr := C.find_argv0((*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf))); ptr != nil {
+// findSnapName parses the argv-like array and finds the 1st argument.
+func findSnapName(buf []byte) *string {
+ if ptr := C.find_snap_name((*C.char)(unsafe.Pointer(&buf[0]))); ptr != nil {
str := C.GoString(ptr)
return &str
}
return nil
}
-// findSnapName parses the argv-like array and finds the 1st argument.
-func findSnapName(buf []byte) *string {
- if ptr := C.find_snap_name((*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf))); ptr != nil {
+// findFirstOption returns the first "-option" string in argv-like array.
+func findFirstOption(buf []byte) *string {
+ if ptr := C.find_1st_option((*C.char)(unsafe.Pointer(&buf[0]))); ptr != nil {
str := C.GoString(ptr)
return &str
}
return nil
}
-// partiallyValidateSnapName checks if snap name is seemingly valid.
-// The real part of the validation happens on the go side.
-func partiallyValidateSnapName(snapName string) int {
+// validateSnapName checks if snap name is valid.
+// This also sets bootstrap_msg on failure.
+func validateSnapName(snapName string) int {
cStr := C.CString(snapName)
- return int(C.partially_validate_snap_name(cStr))
+ return int(C.validate_snap_name(cStr))
+}
+
+// processArguments parses commnad line arguments.
+// The argument cmdline is a string with embedded
+// NUL bytes, separating particular arguments.
+func processArguments(cmdline []byte) (snapName string, shouldSetNs bool) {
+ var snapNameOut *C.char
+ var shouldSetNsOut C.bool
+ var buf *C.char
+ if len(cmdline) > 0 {
+ buf = (*C.char)(unsafe.Pointer(&cmdline[0]))
+ }
+ C.process_arguments(buf, &snapNameOut, &shouldSetNsOut)
+ if snapNameOut != nil {
+ snapName = C.GoString(snapNameOut)
+ }
+ shouldSetNs = bool(shouldSetNsOut)
+
+ return snapName, shouldSetNs
}
diff -Nru snapd-2.28.5/cmd/snap-update-ns/bootstrap.h snapd-2.29.3/cmd/snap-update-ns/bootstrap.h
--- snapd-2.28.5/cmd/snap-update-ns/bootstrap.h 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/cmd/snap-update-ns/bootstrap.h 2017-10-23 06:17:27.000000000 +0000
@@ -20,15 +20,18 @@
#define _GNU_SOURCE
+#include
#include
extern int bootstrap_errno;
extern const char* bootstrap_msg;
void bootstrap(void);
+void process_arguments(const char* cmdline, const char** snap_name_out, bool* should_setns_out);
ssize_t read_cmdline(char* buf, size_t buf_size);
-const char* find_argv0(char* buf, size_t num_read);
-const char* find_snap_name(char* buf, size_t num_read);
-int partially_validate_snap_name(const char* snap_name);
+const char* find_argv0(const char* buf);
+const char* find_snap_name(const char* buf);
+const char* find_1st_option(const char* buf);
+int validate_snap_name(const char* snap_name);
#endif
diff -Nru snapd-2.28.5/cmd/snap-update-ns/bootstrap_test.go snapd-2.29.3/cmd/snap-update-ns/bootstrap_test.go
--- snapd-2.28.5/cmd/snap-update-ns/bootstrap_test.go 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/cmd/snap-update-ns/bootstrap_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -80,29 +80,94 @@
c.Assert(result, Equals, (*string)(nil))
}
-// Check that if argv0 is returned as expected
-func (s *bootstrapSuite) TestFindArgv0(c *C) {
- buf := []byte("arg0\x00argv1\x00")
- result := update.FindArgv0(buf)
+// Check that if the 1st argument is an option then it is skipped.
+func (s *bootstrapSuite) TestFindSnapName6(c *C) {
+ buf := []byte("arg0\x00--option\x00snap\x00\x00")
+ result := update.FindSnapName(buf)
c.Assert(result, NotNil)
- c.Assert(*result, Equals, "arg0")
+ c.Assert(*result, Equals, "snap")
}
-// Check that if argv0 is unterminated we return NULL.
-func (s *bootstrapSuite) TestFindArgv0Unterminated(c *C) {
- buf := []byte("arg0")
- result := update.FindArgv0(buf)
- c.Assert(result, Equals, (*string)(nil))
+// Check that if many options are skipped.
+func (s *bootstrapSuite) TestFindSnapName7(c *C) {
+ buf := []byte("arg0\x00--option\x00--another\x00snap\x00\x00")
+ result := update.FindSnapName(buf)
+ c.Assert(result, NotNil)
+ c.Assert(*result, Equals, "snap")
+}
+
+// Check that if there are no options we just return nil.
+func (s *bootstrapSuite) TestFindFirstOption1(c *C) {
+ for _, str := range []string{"\x00", "arg0\x00", "arg0\x00arg1\x00"} {
+ result := update.FindFirstOption([]byte(str))
+ c.Assert(result, Equals, (*string)(nil))
+ }
+}
+
+// Check that if there are are options we return the first one.
+func (s *bootstrapSuite) TestFindFirstOption2(c *C) {
+ expected := "-o1"
+ for _, str := range []string{
+ "\x00-o1\x00",
+ "arg0\x00-o1\x00",
+ "arg0\x00-o1\x00arg1\x00",
+ "arg0\x00-o1\x00-o2\x00",
+ "arg0\x00-o1\x00-o2\x00arg1\x00",
+ } {
+ result := update.FindFirstOption([]byte(str))
+ c.Assert(result, DeepEquals, &expected, Commentf("got %q", *result))
+ }
}
-// Check that PartiallyValidateSnapName rejects "/" and "..".
-func (s *bootstrapSuite) TestPartiallyValidateSnapName(c *C) {
- c.Assert(update.PartiallyValidateSnapName("hello-world"), Equals, 0)
- c.Assert(update.PartiallyValidateSnapName("hello/world"), Equals, -1)
- c.Assert(update.PartiallyValidateSnapName("hello..world"), Equals, -1)
+// Check that ValidateSnapName rejects "/" and "..".
+func (s *bootstrapSuite) TestValidateSnapName(c *C) {
+ c.Assert(update.ValidateSnapName("hello-world"), Equals, 0)
+ c.Assert(update.ValidateSnapName("hello/world"), Equals, -1)
+ c.Assert(update.ValidateSnapName("hello..world"), Equals, -1)
+ c.Assert(update.ValidateSnapName("INVALID"), Equals, -1)
+ c.Assert(update.ValidateSnapName("-invalid"), Equals, -1)
}
-// Check that pre-go bootstrap code is disabled while testing.
-func (s *bootstrapSuite) TestBootstrapDisabled(c *C) {
- c.Assert(update.BootstrapError(), ErrorMatches, "bootstrap is not enabled while testing")
+// Test various cases of command line handling.
+func (s *bootstrapSuite) TestProcessArguments(c *C) {
+ cases := []struct {
+ cmdline string
+ snapName string
+ shouldSetNs bool
+ errPattern string
+ }{
+ // Corrupted buffer is dealt with.
+ {"", "", false, "argv0 is corrupted"},
+ // When testing real bootstrap is identified and disabled.
+ {"argv0.test\x00", "", false, "bootstrap is not enabled while testing"},
+ // Snap name is mandatory.
+ {"argv0\x00", "", false, "snap name not provided"},
+ {"argv0\x00\x00", "", false, "snap name not provided"},
+ // Snap name is parsed correctly.
+ {"argv0\x00snapname\x00", "snapname", true, ""},
+ // Snap name is validated correctly.
+ {"argv0\x00in--valid\x00", "", false, "snap name cannot contain two consecutive dashes"},
+ {"argv0\x00invalid-\x00", "", false, "snap name cannot end with a dash"},
+ {"argv0\x00@invalid\x00", "", false, "snap name must use lower case letters, digits or dashes"},
+ {"argv0\x00INVALID\x00", "", false, "snap name must use lower case letters, digits or dashes"},
+ // The option --from-snap-confine disables setns.
+ {"argv0\x00--from-snap-confine\x00snapname\x00", "snapname", false, ""},
+ // Unknown options are reported.
+ {"argv0\x00-invalid\x00", "", false, "unsupported option"},
+ {"argv0\x00--option\x00", "", false, "unsupported option"},
+ }
+ for _, tc := range cases {
+ buf := []byte(tc.cmdline)
+ snapName, shouldSetNs := update.ProcessArguments(buf)
+ err := update.BootstrapError()
+ comment := Commentf("failed with cmdline %q, expected error pattern %q, actual error %q",
+ tc.cmdline, tc.errPattern, err)
+ if tc.errPattern != "" {
+ c.Assert(err, ErrorMatches, tc.errPattern, comment)
+ } else {
+ c.Assert(err, IsNil, comment)
+ }
+ c.Check(snapName, Equals, tc.snapName)
+ c.Check(shouldSetNs, Equals, tc.shouldSetNs)
+ }
}
diff -Nru snapd-2.28.5/cmd/snap-update-ns/change.go snapd-2.29.3/cmd/snap-update-ns/change.go
--- snapd-2.28.5/cmd/snap-update-ns/change.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/cmd/snap-update-ns/change.go 2017-11-03 16:15:11.000000000 +0000
@@ -0,0 +1,153 @@
+// -*- 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"
+ "path"
+ "sort"
+ "strings"
+ "syscall"
+
+ "github.com/snapcore/snapd/interfaces/mount"
+)
+
+// Action represents a mount action (mount, remount, unmount, etc).
+type Action string
+
+const (
+ // Keep indicates that a given mount entry should be kept as-is.
+ Keep Action = "keep"
+ // Mount represents an action that results in mounting something somewhere.
+ Mount Action = "mount"
+ // Unmount represents an action that results in unmounting something from somewhere.
+ Unmount Action = "unmount"
+ // Remount when needed
+)
+
+// Change describes a change to the mount table (action and the entry to act on).
+type Change struct {
+ Entry mount.Entry
+ Action Action
+}
+
+// String formats mount change to a human-readable line.
+func (c Change) String() string {
+ return fmt.Sprintf("%s (%s)", c.Action, c.Entry)
+}
+
+var (
+ sysMount = syscall.Mount
+ sysUnmount = syscall.Unmount
+)
+
+const unmountNoFollow = 8
+
+// Perform executes the desired mount or unmount change using system calls.
+// Filesystems that depend on helper programs or multiple independent calls to
+// the kernel (--make-shared, for example) are unsupported.
+func (c *Change) Perform() error {
+ switch c.Action {
+ case Mount:
+ flags, err := mount.OptsToFlags(c.Entry.Options)
+ if err != nil {
+ return err
+ }
+ return sysMount(c.Entry.Name, c.Entry.Dir, c.Entry.Type, uintptr(flags), "")
+ case Unmount:
+ return sysUnmount(c.Entry.Dir, unmountNoFollow)
+ }
+ return fmt.Errorf("cannot process mount change, unknown action: %q", c.Action)
+}
+
+// NeededChanges computes the changes required to change current to desired mount entries.
+//
+// The current and desired profiles is a fstab like list of mount entries. The
+// lists are processed and a "diff" of mount changes is produced. The mount
+// changes, when applied in order, transform the current profile into the
+// desired profile.
+func NeededChanges(currentProfile, desiredProfile *mount.Profile) []*Change {
+ // Copy both profiles as we will want to mutate them.
+ current := make([]mount.Entry, len(currentProfile.Entries))
+ copy(current, currentProfile.Entries)
+ desired := make([]mount.Entry, len(desiredProfile.Entries))
+ copy(desired, desiredProfile.Entries)
+
+ // Clean the directory part of both profiles. This is done so that we can
+ // easily test if a given directory is a subdirectory with
+ // strings.HasPrefix coupled with an extra slash character.
+ for i := range current {
+ current[i].Dir = path.Clean(current[i].Dir)
+ }
+ for i := range desired {
+ desired[i].Dir = path.Clean(desired[i].Dir)
+ }
+
+ // Sort both lists by directory name with implicit trailing slash.
+ sort.Sort(byMagicDir(current))
+ sort.Sort(byMagicDir(desired))
+
+ // Construct a desired directory map.
+ desiredMap := make(map[string]*mount.Entry)
+ for i := range desired {
+ desiredMap[desired[i].Dir] = &desired[i]
+ }
+
+ // Compute reusable entries: those which are equal in current and desired and which
+ // are not prefixed by another entry that changed.
+ var reuse map[string]bool
+ var skipDir string
+ for i := range current {
+ dir := current[i].Dir
+ if skipDir != "" && strings.HasPrefix(dir, skipDir) {
+ continue
+ }
+ skipDir = "" // reset skip prefix as it no longer applies
+ if entry, ok := desiredMap[dir]; ok && current[i].Equal(entry) {
+ if reuse == nil {
+ reuse = make(map[string]bool)
+ }
+ reuse[dir] = true
+ continue
+ }
+ skipDir = strings.TrimSuffix(dir, "/") + "/"
+ }
+
+ // We are now ready to compute the necessary mount changes.
+ var changes []*Change
+
+ // Unmount entries not reused in reverse to handle children before their parent.
+ for i := len(current) - 1; i >= 0; i-- {
+ if reuse[current[i].Dir] {
+ changes = append(changes, &Change{Action: Keep, Entry: current[i]})
+ } else {
+ changes = append(changes, &Change{Action: Unmount, Entry: current[i]})
+ }
+ }
+
+ // Mount desired entries not reused.
+ for i := range desired {
+ if !reuse[desired[i].Dir] {
+ changes = append(changes, &Change{Action: Mount, Entry: desired[i]})
+ }
+ }
+
+ return changes
+}
diff -Nru snapd-2.28.5/cmd/snap-update-ns/change_test.go snapd-2.29.3/cmd/snap-update-ns/change_test.go
--- snapd-2.28.5/cmd/snap-update-ns/change_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/cmd/snap-update-ns/change_test.go 2017-11-03 16:15:11.000000000 +0000
@@ -0,0 +1,239 @@
+// -*- 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 (
+ "errors"
+
+ . "gopkg.in/check.v1"
+
+ update "github.com/snapcore/snapd/cmd/snap-update-ns"
+ "github.com/snapcore/snapd/interfaces/mount"
+ "github.com/snapcore/snapd/testutil"
+)
+
+type changeSuite struct {
+ testutil.BaseTest
+ sys *update.SyscallRecorder
+}
+
+var (
+ errTesting = errors.New("testing")
+)
+
+var _ = Suite(&changeSuite{})
+
+func (s *changeSuite) SetUpTest(c *C) {
+ s.BaseTest.SetUpTest(c)
+ // Mock and record system interactions.
+ s.sys = &update.SyscallRecorder{}
+ s.BaseTest.AddCleanup(update.MockSystemCalls(s.sys))
+}
+
+func (s *changeSuite) TestString(c *C) {
+ change := update.Change{
+ Entry: mount.Entry{Dir: "/a/b", Name: "/dev/sda1"},
+ Action: update.Mount,
+ }
+ c.Assert(change.String(), Equals, "mount (/dev/sda1 /a/b none defaults 0 0)")
+}
+
+// When there are no profiles we don't do anything.
+func (s *changeSuite) TestNeededChangesNoProfiles(c *C) {
+ current := &mount.Profile{}
+ desired := &mount.Profile{}
+ changes := update.NeededChanges(current, desired)
+ c.Assert(changes, IsNil)
+}
+
+// When the profiles are the same we don't do anything.
+func (s *changeSuite) TestNeededChangesNoChange(c *C) {
+ current := &mount.Profile{Entries: []mount.Entry{{Dir: "/common/stuf"}}}
+ desired := &mount.Profile{Entries: []mount.Entry{{Dir: "/common/stuf"}}}
+ changes := update.NeededChanges(current, desired)
+ c.Assert(changes, DeepEquals, []*update.Change{
+ {Entry: mount.Entry{Dir: "/common/stuf"}, Action: update.Keep},
+ })
+}
+
+// When the content interface is connected we should mount the new entry.
+func (s *changeSuite) TestNeededChangesTrivialMount(c *C) {
+ current := &mount.Profile{}
+ desired := &mount.Profile{Entries: []mount.Entry{{Dir: "/common/stuf"}}}
+ changes := update.NeededChanges(current, desired)
+ c.Assert(changes, DeepEquals, []*update.Change{
+ {Entry: desired.Entries[0], Action: update.Mount},
+ })
+}
+
+// When the content interface is disconnected we should unmount the mounted entry.
+func (s *changeSuite) TestNeededChangesTrivialUnmount(c *C) {
+ current := &mount.Profile{Entries: []mount.Entry{{Dir: "/common/stuf"}}}
+ desired := &mount.Profile{}
+ changes := update.NeededChanges(current, desired)
+ c.Assert(changes, DeepEquals, []*update.Change{
+ {Entry: current.Entries[0], Action: update.Unmount},
+ })
+}
+
+// When umounting we unmount children before parents.
+func (s *changeSuite) TestNeededChangesUnmountOrder(c *C) {
+ current := &mount.Profile{Entries: []mount.Entry{
+ {Dir: "/common/stuf/extra"},
+ {Dir: "/common/stuf"},
+ }}
+ desired := &mount.Profile{}
+ changes := update.NeededChanges(current, desired)
+ c.Assert(changes, DeepEquals, []*update.Change{
+ {Entry: mount.Entry{Dir: "/common/stuf/extra"}, Action: update.Unmount},
+ {Entry: mount.Entry{Dir: "/common/stuf"}, Action: update.Unmount},
+ })
+}
+
+// When mounting we mount the parents before the children.
+func (s *changeSuite) TestNeededChangesMountOrder(c *C) {
+ current := &mount.Profile{}
+ desired := &mount.Profile{Entries: []mount.Entry{
+ {Dir: "/common/stuf/extra"},
+ {Dir: "/common/stuf"},
+ }}
+ changes := update.NeededChanges(current, desired)
+ c.Assert(changes, DeepEquals, []*update.Change{
+ {Entry: mount.Entry{Dir: "/common/stuf"}, Action: update.Mount},
+ {Entry: mount.Entry{Dir: "/common/stuf/extra"}, Action: update.Mount},
+ })
+}
+
+// When parent changes we don't reuse its children
+func (s *changeSuite) TestNeededChangesChangedParentSameChild(c *C) {
+ current := &mount.Profile{Entries: []mount.Entry{
+ {Dir: "/common/stuf", Name: "/dev/sda1"},
+ {Dir: "/common/stuf/extra"},
+ {Dir: "/common/unrelated"},
+ }}
+ desired := &mount.Profile{Entries: []mount.Entry{
+ {Dir: "/common/stuf", Name: "/dev/sda2"},
+ {Dir: "/common/stuf/extra"},
+ {Dir: "/common/unrelated"},
+ }}
+ changes := update.NeededChanges(current, desired)
+ c.Assert(changes, DeepEquals, []*update.Change{
+ {Entry: mount.Entry{Dir: "/common/unrelated"}, Action: update.Keep},
+ {Entry: mount.Entry{Dir: "/common/stuf/extra"}, Action: update.Unmount},
+ {Entry: mount.Entry{Dir: "/common/stuf", Name: "/dev/sda1"}, Action: update.Unmount},
+ {Entry: mount.Entry{Dir: "/common/stuf", Name: "/dev/sda2"}, Action: update.Mount},
+ {Entry: mount.Entry{Dir: "/common/stuf/extra"}, Action: update.Mount},
+ })
+}
+
+// When child changes we don't touch the unchanged parent
+func (s *changeSuite) TestNeededChangesSameParentChangedChild(c *C) {
+ current := &mount.Profile{Entries: []mount.Entry{
+ {Dir: "/common/stuf"},
+ {Dir: "/common/stuf/extra", Name: "/dev/sda1"},
+ {Dir: "/common/unrelated"},
+ }}
+ desired := &mount.Profile{Entries: []mount.Entry{
+ {Dir: "/common/stuf"},
+ {Dir: "/common/stuf/extra", Name: "/dev/sda2"},
+ {Dir: "/common/unrelated"},
+ }}
+ changes := update.NeededChanges(current, desired)
+ c.Assert(changes, DeepEquals, []*update.Change{
+ {Entry: mount.Entry{Dir: "/common/unrelated"}, Action: update.Keep},
+ {Entry: mount.Entry{Dir: "/common/stuf/extra", Name: "/dev/sda1"}, Action: update.Unmount},
+ {Entry: mount.Entry{Dir: "/common/stuf"}, Action: update.Keep},
+ {Entry: mount.Entry{Dir: "/common/stuf/extra", Name: "/dev/sda2"}, Action: update.Mount},
+ })
+}
+
+// cur = ['/a/b', '/a/b-1', '/a/b-1/3', '/a/b/c']
+// des = ['/a/b', '/a/b-1', '/a/b/c'
+//
+// We are smart about comparing entries as directories. Here even though "/a/b"
+// is a prefix of "/a/b-1" it is correctly reused.
+func (s *changeSuite) TestNeededChangesSmartEntryComparison(c *C) {
+ current := &mount.Profile{Entries: []mount.Entry{
+ {Dir: "/a/b", Name: "/dev/sda1"},
+ {Dir: "/a/b-1"},
+ {Dir: "/a/b-1/3"},
+ {Dir: "/a/b/c"},
+ }}
+ desired := &mount.Profile{Entries: []mount.Entry{
+ {Dir: "/a/b", Name: "/dev/sda2"},
+ {Dir: "/a/b-1"},
+ {Dir: "/a/b/c"},
+ }}
+ changes := update.NeededChanges(current, desired)
+ c.Assert(changes, DeepEquals, []*update.Change{
+ {Entry: mount.Entry{Dir: "/a/b/c"}, Action: update.Unmount},
+ {Entry: mount.Entry{Dir: "/a/b", Name: "/dev/sda1"}, Action: update.Unmount},
+ {Entry: mount.Entry{Dir: "/a/b-1/3"}, Action: update.Unmount},
+ {Entry: mount.Entry{Dir: "/a/b-1"}, Action: update.Keep},
+
+ {Entry: mount.Entry{Dir: "/a/b", Name: "/dev/sda2"}, Action: update.Mount},
+ {Entry: mount.Entry{Dir: "/a/b/c"}, Action: update.Mount},
+ })
+}
+
+// Change.Perform calls the mount system call.
+func (s *changeSuite) TestPerformMount(c *C) {
+ chg := &update.Change{Action: update.Mount, Entry: mount.Entry{Name: "source", Dir: "target", Type: "type"}}
+ c.Assert(chg.Perform(), IsNil)
+ c.Assert(s.sys.Calls(), DeepEquals, []string{`mount "source" "target" "type" 0 ""`})
+}
+
+// Change.Perform returns errors from mount system call
+func (s *changeSuite) TestPerformMountError(c *C) {
+ s.sys.InsertFault(`mount "source" "target" "type" 0 ""`, errTesting)
+ chg := &update.Change{Action: update.Mount, Entry: mount.Entry{Name: "source", Dir: "target", Type: "type"}}
+ c.Assert(chg.Perform(), Equals, errTesting)
+ c.Assert(s.sys.Calls(), DeepEquals, []string{`mount "source" "target" "type" 0 ""`})
+}
+
+// Change.Perform returns errors from bad flags
+func (s *changeSuite) TestPerformMountOptionError(c *C) {
+ chg := &update.Change{Action: update.Mount, Entry: mount.Entry{Name: "source", Dir: "target", Type: "type", Options: []string{"bogus"}}}
+ c.Assert(chg.Perform(), ErrorMatches, `unsupported mount option: "bogus"`)
+ c.Assert(s.sys.Calls(), HasLen, 0)
+}
+
+// Change.Perform calls the unmount system call.
+func (s *changeSuite) TestPerformUnmount(c *C) {
+ chg := &update.Change{Action: update.Unmount, Entry: mount.Entry{Name: "source", Dir: "target", Type: "type"}}
+ c.Assert(chg.Perform(), IsNil)
+ // The flag 8 is UMOUNT_NOFOLLOW
+ c.Assert(s.sys.Calls(), DeepEquals, []string{`unmount "target" UMOUNT_NOFOLLOW`})
+}
+
+// Change.Perform returns errors from unmount system call
+func (s *changeSuite) TestPerformUnountError(c *C) {
+ s.sys.InsertFault(`unmount "target" UMOUNT_NOFOLLOW`, errTesting)
+ chg := &update.Change{Action: update.Unmount, Entry: mount.Entry{Name: "source", Dir: "target", Type: "type"}}
+ c.Assert(chg.Perform(), Equals, errTesting)
+ c.Assert(s.sys.Calls(), DeepEquals, []string{`unmount "target" UMOUNT_NOFOLLOW`})
+}
+
+// Change.Perform handles unknown actions.
+func (s *changeSuite) TestPerformUnknownAction(c *C) {
+ chg := &update.Change{Action: update.Action(42)}
+ c.Assert(chg.Perform(), ErrorMatches, `cannot process mount change, unknown action: .*`)
+ c.Assert(s.sys.Calls(), HasLen, 0)
+}
diff -Nru snapd-2.28.5/cmd/snap-update-ns/export_test.go snapd-2.29.3/cmd/snap-update-ns/export_test.go
--- snapd-2.28.5/cmd/snap-update-ns/export_test.go 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/cmd/snap-update-ns/export_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -19,9 +19,70 @@
package main
+import (
+ "fmt"
+)
+
var (
- ReadCmdline = readCmdline
- FindArgv0 = findArgv0
- FindSnapName = findSnapName
- PartiallyValidateSnapName = partiallyValidateSnapName
+ ReadCmdline = readCmdline
+ FindSnapName = findSnapName
+ FindFirstOption = findFirstOption
+ ValidateSnapName = validateSnapName
+ ProcessArguments = processArguments
)
+
+// SystemCalls encapsulates various system interactions performed by this module.
+type SystemCalls interface {
+ Mount(source string, target string, fstype string, flags uintptr, data string) (err error)
+ Unmount(target string, flags int) (err error)
+}
+
+// SyscallRecorder stores which system calls were invoked.
+type SyscallRecorder struct {
+ calls []string
+ errors map[string]error
+}
+
+// InsertFault makes given subsequent call to return the specified error.
+func (sys *SyscallRecorder) InsertFault(call string, err error) {
+ if sys.errors == nil {
+ sys.errors = make(map[string]error)
+ }
+ sys.errors[call] = err
+}
+
+// Calls returns the sequence of mocked calls that have been made.
+func (sys *SyscallRecorder) Calls() []string {
+ return sys.calls
+}
+
+// call remembers that a given call has occurred and returns a pre-arranged error, if any
+func (sys *SyscallRecorder) call(call string) error {
+ sys.calls = append(sys.calls, call)
+ return sys.errors[call]
+}
+
+func (sys *SyscallRecorder) Mount(source string, target string, fstype string, flags uintptr, data string) (err error) {
+ return sys.call(fmt.Sprintf("mount %q %q %q %d %q", source, target, fstype, flags, data))
+}
+
+func (sys *SyscallRecorder) Unmount(target string, flags int) (err error) {
+ if flags == unmountNoFollow {
+ return sys.call(fmt.Sprintf("unmount %q %s", target, "UMOUNT_NOFOLLOW"))
+ }
+ return sys.call(fmt.Sprintf("unmount %q %d", target, flags))
+}
+
+// MockSystemCalls replaces real system calls with those of the argument.
+func MockSystemCalls(sc SystemCalls) (restore func()) {
+ oldSysMount := sysMount
+ oldSysUnmount := sysUnmount
+
+ sysMount = sc.Mount
+ sysUnmount = sc.Unmount
+
+ return func() {
+ sysMount = oldSysMount
+ sysUnmount = oldSysUnmount
+ }
+}
diff -Nru snapd-2.28.5/cmd/snap-update-ns/main.go snapd-2.29.3/cmd/snap-update-ns/main.go
--- snapd-2.28.5/cmd/snap-update-ns/main.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/cmd/snap-update-ns/main.go 2017-11-03 16:15:11.000000000 +0000
@@ -28,35 +28,40 @@
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/interfaces/mount"
"github.com/snapcore/snapd/logger"
- "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/osutil"
)
var opts struct {
- Positionals struct {
+ FromSnapConfine bool `long:"from-snap-confine"`
+ Positionals struct {
SnapName string `positional-arg-name:"SNAP_NAME" required:"yes"`
} `positional-args:"true"`
}
+// IMPORTANT: all the code in main() until bootstrap is finished may be run
+// with elevated privileges when invoking snap-update-ns from the setuid
+// snap-confine.
+
func main() {
+ logger.SimpleSetup()
if err := run(); err != nil {
fmt.Printf("cannot update snap namespace: %s\n", err)
os.Exit(1)
}
+ // END IMPORTANT
}
func parseArgs(args []string) error {
parser := flags.NewParser(&opts, flags.HelpFlag|flags.PassDoubleDash|flags.PassAfterNonOption)
- if _, err := parser.ParseArgs(args); err != nil {
- return err
- }
- return snap.ValidateName(opts.Positionals.SnapName)
+ _, err := parser.ParseArgs(args)
+ return err
}
-func run() error {
- if err := parseArgs(os.Args[1:]); err != nil {
- return err
- }
+// IMPORTANT: all the code in run() until BootStrapError() is finished may
+// be run with elevated privileges when invoking snap-update-ns from
+// the setuid snap-confine.
+func run() error {
// There is some C code that runs before main() is started.
// That code always runs and sets an error condition if it fails.
// Here we just check for the error.
@@ -68,6 +73,12 @@
}
return err
}
+ // END IMPORTANT
+
+ if err := parseArgs(os.Args[1:]); err != nil {
+ return err
+ }
+
snapName := opts.Positionals.SnapName
// Lock the mount namespace so that any concurrently attempted invocations
@@ -77,8 +88,18 @@
return fmt.Errorf("cannot open lock file for mount namespace of snap %q: %s", snapName, err)
}
defer lock.Close()
- if err := lock.Lock(); err != nil {
- return fmt.Errorf("cannot lock mount namespace of snap %q: %s", snapName, err)
+
+ if opts.FromSnapConfine {
+ // When --from-snap-conifne is passed then we just ensure that the
+ // namespace is locked. This is used by snap-confine to use
+ // snap-update-ns to apply mount profiles.
+ if err := lock.TryLock(); err != osutil.ErrAlreadyLocked {
+ return fmt.Errorf("mount namespace of snap %q is not locked but --from-snap-confine was used", snapName)
+ }
+ } else {
+ if err := lock.Lock(); err != nil {
+ return fmt.Errorf("cannot lock mount namespace of snap %q: %s", snapName, err)
+ }
}
// Read the desired and current mount profiles. Note that missing files
@@ -98,10 +119,10 @@
// Compute the needed changes and perform each change if needed, collecting
// those that we managed to perform or that were performed already.
- changesNeeded := mount.NeededChanges(currentBefore, desired)
- var changesMade []mount.Change
+ changesNeeded := NeededChanges(currentBefore, desired)
+ var changesMade []*Change
for _, change := range changesNeeded {
- if change.Action == mount.Keep {
+ if change.Action == Keep {
changesMade = append(changesMade, change)
continue
}
@@ -116,7 +137,7 @@
// and save it back for next runs.
var currentAfter mount.Profile
for _, change := range changesMade {
- if change.Action == mount.Mount || change.Action == mount.Keep {
+ if change.Action == Mount || change.Action == Keep {
currentAfter.Entries = append(currentAfter.Entries, change.Entry)
}
}
diff -Nru snapd-2.28.5/cmd/snap-update-ns/sorting.go snapd-2.29.3/cmd/snap-update-ns/sorting.go
--- snapd-2.28.5/cmd/snap-update-ns/sorting.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/cmd/snap-update-ns/sorting.go 2017-10-23 06:17:27.000000000 +0000
@@ -0,0 +1,44 @@
+// -*- 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 (
+ "strings"
+
+ "github.com/snapcore/snapd/interfaces/mount"
+)
+
+// byMagicDir allows sorting an array of entries that automagically assumes
+// each entry ends with a trailing slash.
+type byMagicDir []mount.Entry
+
+func (c byMagicDir) Len() int { return len(c) }
+func (c byMagicDir) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
+func (c byMagicDir) Less(i, j int) bool {
+ iDir := c[i].Dir
+ jDir := c[j].Dir
+ if !strings.HasSuffix(iDir, "/") {
+ iDir = iDir + "/"
+ }
+ if !strings.HasSuffix(jDir, "/") {
+ jDir = jDir + "/"
+ }
+ return iDir < jDir
+}
diff -Nru snapd-2.28.5/cmd/snap-update-ns/sorting_test.go snapd-2.29.3/cmd/snap-update-ns/sorting_test.go
--- snapd-2.28.5/cmd/snap-update-ns/sorting_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/cmd/snap-update-ns/sorting_test.go 2017-10-23 06:17:27.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 main
+
+import (
+ "sort"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/interfaces/mount"
+)
+
+type sortSuite struct{}
+
+var _ = Suite(&sortSuite{})
+
+func (s *sortSuite) TestTrailingSlashesComparison(c *C) {
+ // Naively sorted entries.
+ entries := []mount.Entry{
+ {Dir: "/a/b"},
+ {Dir: "/a/b-1"},
+ {Dir: "/a/b-1/3"},
+ {Dir: "/a/b/c"},
+ }
+ sort.Sort(byMagicDir(entries))
+ // Entries sorted as if they had a trailing slash.
+ c.Assert(entries, DeepEquals, []mount.Entry{
+ {Dir: "/a/b-1"},
+ {Dir: "/a/b-1/3"},
+ {Dir: "/a/b"},
+ {Dir: "/a/b/c"},
+ })
+}
diff -Nru snapd-2.28.5/corecfg/corecfg.go snapd-2.29.3/corecfg/corecfg.go
--- snapd-2.28.5/corecfg/corecfg.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/corecfg/corecfg.go 2017-10-23 06:17:27.000000000 +0000
@@ -38,8 +38,7 @@
// 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
+ return systemd.Available()
}
func snapctlGet(key string) (string, error) {
diff -Nru snapd-2.28.5/corecfg/corecfg_test.go snapd-2.29.3/corecfg/corecfg_test.go
--- snapd-2.28.5/corecfg/corecfg_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/corecfg/corecfg_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -34,17 +34,22 @@
// coreCfgSuite is the base for all the corecfg tests
type coreCfgSuite struct {
- systemctlArgs [][]string
+ systemctlArgs [][]string
+ systemctlRestorer func()
}
var _ = Suite(&coreCfgSuite{})
func (s *coreCfgSuite) SetUpSuite(c *C) {
- systemd.SystemctlCmd = func(args ...string) ([]byte, error) {
+ s.systemctlRestorer = systemd.MockSystemctl(func(args ...string) ([]byte, error) {
s.systemctlArgs = append(s.systemctlArgs, args[:])
output := []byte("ActiveState=inactive")
return output, nil
- }
+ })
+}
+
+func (s *coreCfgSuite) TearDownSuite(c *C) {
+ s.systemctlRestorer()
}
// runCfgSuite tests corecfg.Run()
@@ -66,13 +71,10 @@
restore := release.MockOnClassic(false)
defer restore()
- oldSystemdSystemctlCmd := systemd.SystemctlCmd
- systemd.SystemctlCmd = func(args ...string) ([]byte, error) {
+ r := systemd.MockSystemctl(func(args ...string) ([]byte, error) {
return nil, fmt.Errorf("simulate missing core-support")
- }
- defer func() {
- systemd.SystemctlCmd = oldSystemdSystemctlCmd
- }()
+ })
+ defer r()
err := corecfg.Run()
c.Check(err, ErrorMatches, `(?m)cannot run systemctl - core-support interface seems disconnected: simulate missing core-support`)
diff -Nru snapd-2.28.5/corecfg/picfg_test.go snapd-2.29.3/corecfg/picfg_test.go
--- snapd-2.28.5/corecfg/picfg_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/corecfg/picfg_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -59,7 +59,6 @@
err := os.MkdirAll(filepath.Dir(s.mockConfigPath), 0755)
c.Assert(err, IsNil)
s.mockConfig(c, mockConfigTxt)
-
}
func (s *piCfgSuite) TearDownTest(c *C) {
diff -Nru snapd-2.28.5/corecfg/services_test.go snapd-2.29.3/corecfg/services_test.go
--- snapd-2.28.5/corecfg/services_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/corecfg/services_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -29,23 +29,28 @@
"github.com/snapcore/snapd/corecfg"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/release"
+ "github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/testutil"
)
type servicesSuite struct {
coreCfgSuite
+ testutil.BaseTest
}
var _ = Suite(&servicesSuite{})
func (s *servicesSuite) SetUpTest(c *C) {
+ s.BaseTest.SetUpTest(c)
dirs.SetRootDir(c.MkDir())
c.Assert(os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "etc"), 0755), IsNil)
s.systemctlArgs = nil
+ s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
}
func (s *servicesSuite) TearDownTest(c *C) {
dirs.SetRootDir("/")
+ s.BaseTest.TearDownTest(c)
}
func (s *servicesSuite) TestConfigureServiceInvalidValue(c *C) {
diff -Nru snapd-2.28.5/daemon/api.go snapd-2.29.3/daemon/api.go
--- snapd-2.28.5/daemon/api.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/daemon/api.go 2017-11-09 18:15:21.000000000 +0000
@@ -44,21 +44,22 @@
"github.com/snapcore/snapd/asserts/snapasserts"
"github.com/snapcore/snapd/client"
"github.com/snapcore/snapd/dirs"
- "github.com/snapcore/snapd/i18n/dumb"
+ "github.com/snapcore/snapd/i18n"
"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"
"github.com/snapcore/snapd/overlord/hookstate/ctlcmd"
"github.com/snapcore/snapd/overlord/ifacestate"
+ "github.com/snapcore/snapd/overlord/servicestate"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/overlord/storestate"
"github.com/snapcore/snapd/progress"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
@@ -155,9 +156,9 @@
}
logsCmd = &Command{
- Path: "/v2/logs",
- UserOK: true,
- GET: getLogs,
+ Path: "/v2/logs",
+ PolkitOK: "io.snapcraft.snapd.manage",
+ GET: getLogs,
}
snapConfCmd = &Command{
@@ -188,10 +189,11 @@
}
stateChangeCmd = &Command{
- Path: "/v2/changes/{id}",
- UserOK: true,
- GET: getChange,
- POST: abortChange,
+ Path: "/v2/changes/{id}",
+ UserOK: true,
+ PolkitOK: "io.snapcraft.snapd.manage",
+ GET: getChange,
+ POST: abortChange,
}
stateChangesCmd = &Command{
@@ -505,33 +507,29 @@
return SyncResponse(result, nil)
}
-func webify(result map[string]interface{}, resource string) map[string]interface{} {
- result["resource"] = resource
-
- icon, ok := result["icon"].(string)
- if !ok || icon == "" || strings.HasPrefix(icon, "http") {
+func webify(result *client.Snap, resource string) *client.Snap {
+ if result.Icon == "" || strings.HasPrefix(result.Icon, "http") {
return result
}
- result["icon"] = ""
+ result.Icon = ""
route := appIconCmd.d.router.Get(appIconCmd.Path)
if route != nil {
- name, _ := result["name"].(string)
- url, err := route.URL("name", name)
+ url, err := route.URL("name", result.Name)
if err == nil {
- result["icon"] = url.String()
+ result.Icon = url.String()
}
}
return result
}
-func getStore(c *Command) snapstate.StoreService {
+func getStore(c *Command) storestate.StoreService {
st := c.d.overlord.State()
st.Lock()
defer st.Unlock()
- return snapstate.Store(st)
+ return storestate.Store(st)
}
func getSections(c *Command, r *http.Request, user *auth.UserState) Response {
@@ -548,7 +546,7 @@
// pass
case store.ErrEmptyQuery, store.ErrBadQuery:
return BadRequest("%v", err)
- case store.ErrUnauthenticated:
+ case store.ErrUnauthenticated, store.ErrInvalidCredentials:
return Unauthorized("%v", err)
default:
return InternalError("%v", err)
@@ -609,7 +607,7 @@
// pass
case store.ErrEmptyQuery, store.ErrBadQuery:
return BadRequest("%v", err)
- case store.ErrUnauthenticated:
+ case store.ErrUnauthenticated, store.ErrInvalidCredentials:
return Unauthorized(err.Error())
default:
return InternalError("%v", err)
@@ -634,10 +632,14 @@
AnyChannel: true,
}
snapInfo, err := theStore.SnapInfo(spec, user)
- if err != nil {
- if err == store.ErrSnapNotFound {
- return SnapNotFound(name, err)
- }
+ switch err {
+ case nil:
+ // pass
+ case store.ErrInvalidCredentials:
+ return Unauthorized("%v", err)
+ case store.ErrSnapNotFound:
+ return SnapNotFound(name, err)
+ default:
return InternalError("%v", err)
}
@@ -799,7 +801,7 @@
}
type snapInstruction struct {
- progress.NullProgress
+ progress.NullMeter
Action string `json:"action"`
Channel string `json:"channel"`
Revision snap.Revision `json:"revision"`
@@ -834,7 +836,6 @@
}
var (
- snapstateCoreInfo = snapstate.CoreInfo
snapstateInstall = snapstate.Install
snapstateInstallPath = snapstate.InstallPath
snapstateRefreshCandidates = snapstate.RefreshCandidates
@@ -858,42 +859,6 @@
var errNothingToInstall = errors.New("nothing to install")
-const oldDefaultSnapCoreName = "ubuntu-core"
-const defaultCoreSnapName = "core"
-
-func ensureCore(st *state.State, targetSnap string, userID int) (*state.TaskSet, error) {
- if targetSnap == defaultCoreSnapName || targetSnap == oldDefaultSnapCoreName {
- return nil, errNothingToInstall
- }
-
- _, err := snapstateCoreInfo(st)
- if err != state.ErrNoState {
- return nil, err
- }
-
- return snapstateInstall(st, defaultCoreSnapName, "stable", snap.R(0), userID, snapstate.Flags{})
-}
-
-func withEnsureCore(st *state.State, targetSnap string, userID int, install func() (*state.TaskSet, error)) ([]*state.TaskSet, error) {
- ubuCoreTs, err := ensureCore(st, targetSnap, userID)
- if err != nil && err != errNothingToInstall {
- return nil, err
- }
-
- ts, err := install()
- if err != nil {
- return nil, err
- }
-
- // ensure main install waits on ubuntu core install
- if ubuCoreTs != nil {
- ts.WaitAll(ubuCoreTs)
- return []*state.TaskSet{ubuCoreTs, ts}, nil
- }
-
- return []*state.TaskSet{ts}, nil
-}
-
var errDevJailModeConflict = errors.New("cannot use devmode and jailmode flags together")
var errClassicDevmodeConflict = errors.New("cannot use classic and devmode flags together")
var errNoJailMode = errors.New("this system cannot honour the jailmode flag")
@@ -991,11 +956,7 @@
logger.Noticef("Installing snap %q revision %s", inst.Snaps[0], inst.Revision)
- tsets, err := withEnsureCore(st, inst.Snaps[0], inst.userID,
- func() (*state.TaskSet, error) {
- return snapstateInstall(st, inst.Snaps[0], inst.Channel, inst.Revision, inst.userID, flags)
- },
- )
+ tset, err := snapstateInstall(st, inst.Snaps[0], inst.Channel, inst.Revision, inst.userID, flags)
if err != nil {
return "", nil, err
}
@@ -1004,7 +965,7 @@
if inst.Channel != "stable" && inst.Channel != "" {
msg = fmt.Sprintf(i18n.G("Install %q snap from %q channel"), inst.Snaps[0], inst.Channel)
}
- return msg, tsets, nil
+ return msg, []*state.TaskSet{tset}, nil
}
func snapUpdate(inst *snapInstruction, st *state.State) (string, []*state.TaskSet, error) {
@@ -1263,21 +1224,13 @@
return BadRequest("cannot read snap info for %s: %s", trydir, err)
}
- var userID int
- if user != nil {
- userID = user.ID
- }
- tsets, err := withEnsureCore(st, info.Name(), userID,
- func() (*state.TaskSet, error) {
- return snapstateTryPath(st, info.Name(), trydir, flags)
- },
- )
+ tset, err := snapstateTryPath(st, info.Name(), trydir, flags)
if err != nil {
return BadRequest("cannot try %s: %s", trydir, err)
}
msg := fmt.Sprintf(i18n.G("Try %q snap from %s"), info.Name(), trydir)
- chg := newChange(st, "try-snap", msg, tsets, []string{info.Name()})
+ chg := newChange(st, "try-snap", msg, []*state.TaskSet{tset}, []string{info.Name()})
chg.Set("api-data", map[string]string{"snap-name": info.Name()})
ensureStateSoon(st)
@@ -1453,11 +1406,11 @@
if !dangerousOK {
si, err := snapasserts.DeriveSideInfo(tempPath, assertstate.DB(st))
- switch err {
- case nil:
+ switch {
+ case err == nil:
snapName = si.RealName
sideInfo = si
- case asserts.ErrNotFound:
+ case asserts.IsNotFound(err):
// with devmode we try to find assertions but it's ok
// if they are not there (implies --dangerous)
if !isTrue(form, "devmode") {
@@ -1488,21 +1441,12 @@
msg = fmt.Sprintf(i18n.G("Install %q snap from file %q"), snapName, origPath)
}
- var userID int
- if user != nil {
- userID = user.ID
- }
-
- tsets, err := withEnsureCore(st, snapName, userID,
- func() (*state.TaskSet, error) {
- return snapstateInstallPath(st, sideInfo, tempPath, "", flags)
- },
- )
+ tset, err := snapstateInstallPath(st, sideInfo, tempPath, "", flags)
if err != nil {
return InternalError("cannot install snap file: %v", err)
}
- chg := newChange(st, "install-snap", msg, tsets, []string{snapName})
+ chg := newChange(st, "install-snap", msg, []*state.TaskSet{tset}, []string{snapName})
chg.Set("api-data", map[string]string{"snap-name": snapName})
ensureStateSoon(st)
@@ -1568,9 +1512,6 @@
snapName := vars["name"]
keys := splitQS(r.URL.Query().Get("keys"))
- if len(keys) == 0 {
- return BadRequest("cannot obtain configuration: no keys supplied")
- }
s := c.d.overlord.State()
s.Lock()
@@ -1578,15 +1519,30 @@
s.Unlock()
currentConfValues := make(map[string]interface{})
+ // Special case - return root document
+ if len(keys) == 0 {
+ keys = []string{""}
+ }
for _, key := range keys {
var value interface{}
if err := tr.Get(snapName, key, &value); err != nil {
if config.IsNoOption(err) {
+ if key == "" {
+ // no configuration - return empty document
+ currentConfValues = make(map[string]interface{})
+ break
+ }
return BadRequest("%v", err)
} else {
return InternalError("%v", err)
}
}
+ if key == "" {
+ if len(keys) > 1 {
+ return BadRequest("keys contains zero-length string")
+ }
+ return SyncResponse(value, nil)
+ }
currentConfValues[key] = value
}
@@ -1830,7 +1786,7 @@
state.Unlock()
assertions, err := db.FindMany(assertType, headers)
- if err == asserts.ErrNotFound {
+ if asserts.IsNotFound(err) {
return AssertResponse(nil, true)
} else if err != nil {
return InternalError("searching assertions failed: %v", err)
@@ -2068,7 +2024,7 @@
st.Lock()
assertions, err := db.FindMany(asserts.SystemUserType, headers)
st.Unlock()
- if err != nil && err != asserts.ErrNotFound {
+ if err != nil && !asserts.IsNotFound(err) {
return BadRequest("cannot find system-user assertion: %s", err)
}
@@ -2636,13 +2592,16 @@
if rsp != nil {
return rsp
}
+ if len(appInfos) == 0 {
+ return AppNotFound("no matching services")
+ }
serviceNames := make([]string, len(appInfos))
for i, appInfo := range appInfos {
serviceNames[i] = appInfo.ServiceName()
}
- sysd := systemd.New(dirs.GlobalRootDir, &progress.NullProgress{})
+ sysd := systemd.New(dirs.GlobalRootDir, progress.Null)
reader, err := sysd.LogReader(serviceNames, n, follow)
if err != nil {
return InternalError("cannot get logs: %v", err)
@@ -2654,16 +2613,8 @@
}
}
-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
+ var inst servicestate.Instruction
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&inst); err != nil {
return BadRequest("cannot decode request body into service operation: %v", err)
@@ -2684,56 +2635,16 @@
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
+ ts, err := servicestate.Control(st, appInfos, &inst)
+ if err != nil {
+ if _, ok := err.(servicestate.ServiceActionConflictError); ok {
+ return Conflict(err.Error())
}
+ return BadRequest(err.Error())
}
-
- 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)
+ chg := newChange(st, "service-control", fmt.Sprintf("Running service command"), []*state.TaskSet{ts}, inst.Names)
st.EnsureBefore(0)
return AsyncResponse(nil, &Meta{Change: chg.ID()})
}
diff -Nru snapd-2.28.5/daemon/api_test.go snapd-2.29.3/daemon/api_test.go
--- snapd-2.28.5/daemon/api_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/daemon/api_test.go 2017-11-09 18:14:36.000000000 +0000
@@ -46,6 +46,7 @@
"golang.org/x/crypto/sha3"
"gopkg.in/check.v1"
"gopkg.in/macaroon.v1"
+ "gopkg.in/tomb.v2"
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/asserts/assertstest"
@@ -55,12 +56,15 @@
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/interfaces/ifacetest"
"github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/overlord"
"github.com/snapcore/snapd/overlord/assertstate"
"github.com/snapcore/snapd/overlord/auth"
"github.com/snapcore/snapd/overlord/configstate/config"
"github.com/snapcore/snapd/overlord/ifacestate"
+ "github.com/snapcore/snapd/overlord/servicestate"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/overlord/storestate"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snaptest"
@@ -88,17 +92,17 @@
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
+ systemctlRestorer func()
+ sysctlArgses [][]string
+ sysctlBufs [][]byte
+ sysctlErrs []error
+
+ journalctlRestorer func()
+ jctlSvcses [][]string
+ jctlNs []string
+ jctlFollows []bool
+ jctlRCs []io.ReadCloser
+ jctlErrs []error
}
func (s *apiBaseSuite) SnapInfo(spec store.SnapSpec, user *auth.UserState) (*snap.Info, error) {
@@ -155,18 +159,15 @@
func (s *apiBaseSuite) SetUpSuite(c *check.C) {
muxVars = s.muxVars
s.restoreRelease = release.MockForcedDevmode(false)
-
- snapstate.CanAutoRefresh = nil
- s.origSysctlCmd = systemd.SystemctlCmd
- s.origJctlCmd = systemd.JournalctlCmd
- systemd.SystemctlCmd = s.systemctl
- systemd.JournalctlCmd = s.journalctl
+ s.systemctlRestorer = systemd.MockSystemctl(s.systemctl)
+ s.journalctlRestorer = systemd.MockJournalctl(s.journalctl)
}
func (s *apiBaseSuite) TearDownSuite(c *check.C) {
muxVars = nil
s.restoreRelease()
- systemd.SystemctlCmd = s.origSysctlCmd
+ s.systemctlRestorer()
+ s.journalctlRestorer()
}
func (s *apiBaseSuite) systemctl(args ...string) (buf []byte, err error) {
@@ -234,7 +235,6 @@
s.trustedRestorer = sysdb.InjectTrusted(s.storeSigning.Trusted)
assertstateRefreshSnapDeclarations = nil
- snapstateCoreInfo = nil
snapstateInstall = nil
snapstateInstallMany = nil
snapstateInstallPath = nil
@@ -256,7 +256,6 @@
dirs.SetRootDir("")
assertstateRefreshSnapDeclarations = assertstate.RefreshSnapDeclarations
- snapstateCoreInfo = snapstate.CoreInfo
snapstateInstall = snapstate.Install
snapstateInstallMany = snapstate.InstallMany
snapstateInstallPath = snapstate.InstallPath
@@ -280,7 +279,7 @@
st := d.overlord.State()
st.Lock()
defer st.Unlock()
- snapstate.ReplaceStore(st, s)
+ storestate.ReplaceStore(st, s)
// mark as already seeded
st.Set("seeded", true)
// registered
@@ -290,10 +289,83 @@
Serial: "serialserial",
})
+ // don't actually try to talk to the store on snapstate.Ensure
+ // needs doing after the call to devicestate.Manager (which
+ // happens in daemon.New via overlord.New)
+ snapstate.CanAutoRefresh = nil
+
+ s.d = d
+ return d
+}
+
+func (s *apiBaseSuite) daemonWithOverlordMock(c *check.C) *Daemon {
+ if s.d != nil {
+ panic("called daemon() twice")
+ }
+ d, err := New()
+ c.Assert(err, check.IsNil)
+ d.addRoutes()
+
+ o := overlord.Mock()
+ d.overlord = o
+
+ st := d.overlord.State()
+ // adds an assertion db
+ assertstate.Manager(st)
+ st.Lock()
+ defer st.Unlock()
+ storestate.ReplaceStore(st, s)
+
s.d = d
return d
}
+type fakeSnapManager struct {
+ runner *state.TaskRunner
+}
+
+func newFakeSnapManager(st *state.State) *fakeSnapManager {
+ runner := state.NewTaskRunner(st)
+
+ runner.AddHandler("fake-install-snap", func(t *state.Task, _ *tomb.Tomb) error {
+ return nil
+ }, nil)
+ runner.AddHandler("fake-install-snap-error", func(t *state.Task, _ *tomb.Tomb) error {
+ return fmt.Errorf("fake-install-snap-error errored")
+ }, nil)
+
+ return &fakeSnapManager{runner: runner}
+}
+
+func (m *fakeSnapManager) Ensure() error {
+ m.runner.Ensure()
+ return nil
+}
+
+func (m *fakeSnapManager) Wait() {
+ m.runner.Wait()
+}
+
+func (m *fakeSnapManager) Stop() {
+ m.runner.Stop()
+}
+
+// sanity
+var _ overlord.StateManager = (*fakeSnapManager)(nil)
+
+func (s *apiBaseSuite) daemonWithFakeSnapManager(c *check.C) *Daemon {
+ d := s.daemonWithOverlordMock(c)
+ st := d.overlord.State()
+ d.overlord.AddManager(newFakeSnapManager(st))
+ return d
+}
+
+func (s *apiBaseSuite) waitTrivialChange(c *check.C, chg *state.Change) {
+ err := s.d.overlord.Settle(5 * time.Second)
+ c.Assert(err, check.IsNil)
+ c.Assert(chg.IsReady(), check.Equals, true)
+}
+
func (s *apiBaseSuite) mkInstalled(c *check.C, name, developer, version string, revision snap.Revision, active bool, extraYaml string) *snap.Info {
return s.mkInstalledInState(c, nil, name, developer, version, revision, active, extraYaml)
}
@@ -468,41 +540,40 @@
c.Assert(ok, check.Equals, true)
c.Assert(rsp, check.NotNil)
- c.Assert(rsp.Result, check.FitsTypeOf, map[string]interface{}{})
- m := rsp.Result.(map[string]interface{})
+ c.Assert(rsp.Result, check.FitsTypeOf, &client.Snap{})
+ m := rsp.Result.(*client.Snap)
// installed-size depends on vagaries of the filesystem, just check type
- c.Check(m["installed-size"], check.FitsTypeOf, int64(0))
- delete(m, "installed-size")
+ c.Check(m.InstalledSize, check.FitsTypeOf, int64(0))
+ m.InstalledSize = 0
// ditto install-date
- c.Check(m["install-date"], check.FitsTypeOf, time.Time{})
- delete(m, "install-date")
+ c.Check(m.InstallDate, check.FitsTypeOf, time.Time{})
+ m.InstallDate = time.Time{}
meta := &Meta{}
expected := &resp{
Type: ResponseTypeSync,
Status: 200,
- Result: map[string]interface{}{
- "id": "foo-id",
- "name": "foo",
- "revision": snap.R(10),
- "version": "v1",
- "channel": "stable",
- "tracking-channel": "beta",
- "title": "title",
- "summary": "summary",
- "description": "description",
- "developer": "bar",
- "status": "active",
- "icon": "/v2/icons/foo/icon",
- "type": string(snap.TypeApp),
- "resource": "/v2/snaps/foo",
- "private": false,
- "devmode": false,
- "jailmode": false,
- "confinement": snap.StrictConfinement,
- "trymode": false,
- "apps": []*client.AppInfo{
+ Result: &client.Snap{
+ ID: "foo-id",
+ Name: "foo",
+ Revision: snap.R(10),
+ Version: "v1",
+ Channel: "stable",
+ TrackingChannel: "beta",
+ Title: "title",
+ Summary: "summary",
+ Description: "description",
+ Developer: "bar",
+ Status: "active",
+ Icon: "/v2/icons/foo/icon",
+ Type: string(snap.TypeApp),
+ Private: false,
+ DevMode: false,
+ JailMode: false,
+ Confinement: string(snap.StrictConfinement),
+ TryMode: false,
+ Apps: []client.AppInfo{
{
Snap: "foo", Name: "cmd",
DesktopFile: df,
@@ -533,9 +604,9 @@
Active: false,
},
},
- "broken": "",
- "contact": "",
- "license": "GPL-3.0",
+ Broken: "",
+ Contact: "",
+ License: "GPL-3.0",
},
Meta: meta,
}
@@ -623,8 +694,6 @@
"maxReadBuflen",
"muxVars",
"errNothingToInstall",
- "defaultCoreSnapName",
- "oldDefaultSnapCoreName",
"errDevJailModeConflict",
"errNoJailMode",
"errClassicDevmodeConflict",
@@ -634,7 +703,6 @@
"snapstateUpdate",
"snapstateInstallPath",
"snapstateTryPath",
- "snapstateCoreInfo",
"snapstateUpdateMany",
"snapstateInstallMany",
"snapstateRemoveMany",
@@ -1098,7 +1166,7 @@
c.Check(rsp.Type, check.Equals, ResponseTypeError)
c.Check(rsp.Status, check.Equals, 401)
- c.Check(rsp.Result.(*errorResult).Message, testutil.Contains, "cannot authenticate to snap store")
+ c.Check(rsp.Result.(*errorResult).Message, check.Equals, "invalid credentials")
}
func (s *apiSuite) TestUserFromRequestNoHeader(c *check.C) {
@@ -1336,7 +1404,7 @@
c.Assert(snaps, check.HasLen, 1)
c.Assert(snaps[0]["name"], check.Equals, "store")
c.Check(snaps[0]["prices"], check.IsNil)
- c.Check(snaps[0]["screenshots"], check.IsNil)
+ c.Check(snaps[0]["screenshots"], check.HasLen, 0)
c.Check(snaps[0]["channels"], check.IsNil)
c.Check(rsp.SuggestedCurrency, check.Equals, "EUR")
@@ -1792,9 +1860,7 @@
}
func (s *apiSuite) TestPostSnap(c *check.C) {
- d := s.daemon(c)
- d.overlord.Loop()
- defer d.overlord.Stop()
+ d := s.daemonWithOverlordMock(c)
soon := 0
ensureStateSoon = func(st *state.State) {
@@ -1834,9 +1900,7 @@
}
func (s *apiSuite) TestPostSnapVerfySnapInstruction(c *check.C) {
- d := s.daemon(c)
- d.overlord.Loop()
- defer d.overlord.Stop()
+ s.daemonWithOverlordMock(c)
buf := bytes.NewBufferString(`{"action": "install"}`)
req, err := http.NewRequest("POST", "/v2/snaps/ubuntu-core", buf)
@@ -1943,7 +2007,7 @@
// try a multipart/form-data upload
body := sideLoadBodyWithoutDevMode
head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"}
- chgSummary := s.sideloadCheck(c, body, head, snapstate.Flags{RemoveSnapPath: true}, false)
+ chgSummary := s.sideloadCheck(c, body, head, snapstate.Flags{RemoveSnapPath: true})
c.Check(chgSummary, check.Equals, `Install "local" snap from file "a/b/local.snap"`)
}
@@ -1954,7 +2018,7 @@
restore := release.MockForcedDevmode(true)
defer restore()
flags := snapstate.Flags{RemoveSnapPath: true}
- chgSummary := s.sideloadCheck(c, body, head, flags, false)
+ chgSummary := s.sideloadCheck(c, body, head, flags)
c.Check(chgSummary, check.Equals, `Install "local" snap from file "a/b/local.snap"`)
}
@@ -1973,7 +2037,7 @@
// try a multipart/form-data upload
flags := snapstate.Flags{RemoveSnapPath: true}
flags.DevMode = true
- chgSummary := s.sideloadCheck(c, body, head, flags, true)
+ chgSummary := s.sideloadCheck(c, body, head, flags)
c.Check(chgSummary, check.Equals, `Install "local" snap from file "x"`)
}
@@ -1995,7 +2059,7 @@
head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"}
// try a multipart/form-data upload
flags := snapstate.Flags{JailMode: true, RemoveSnapPath: true}
- chgSummary := s.sideloadCheck(c, body, head, flags, true)
+ chgSummary := s.sideloadCheck(c, body, head, flags)
c.Check(chgSummary, check.Equals, `Install "local" snap from file "x"`)
}
@@ -2014,9 +2078,7 @@
"\r\n" +
"true\r\n" +
"----hello--\r\n"
- d := s.daemon(c)
- d.overlord.Loop()
- defer d.overlord.Stop()
+ s.daemonWithOverlordMock(c)
req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body))
c.Assert(err, check.IsNil)
@@ -2038,9 +2100,7 @@
"\r\n" +
"true\r\n" +
"----hello--\r\n"
- d := s.daemon(c)
- d.overlord.Loop()
- defer d.overlord.Stop()
+ s.daemonWithOverlordMock(c)
req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body))
c.Assert(err, check.IsNil)
@@ -2055,9 +2115,7 @@
}
func (s *apiSuite) TestLocalInstallSnapDeriveSideInfo(c *check.C) {
- d := s.daemon(c)
- d.overlord.Loop()
- defer d.overlord.Stop()
+ d := s.daemonWithOverlordMock(c)
// add the assertions first
st := d.overlord.State()
assertAdd(st, s.storeSigning.StoreAccountKey(""))
@@ -2096,9 +2154,6 @@
c.Assert(err, check.IsNil)
req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--")
- snapstateCoreInfo = func(s *state.State) (*snap.Info, error) {
- return nil, nil
- }
snapstateInstallPath = func(s *state.State, si *snap.SideInfo, path, channel string, flags snapstate.Flags) (*state.TaskSet, error) {
c.Check(flags, check.Equals, snapstate.Flags{RemoveSnapPath: true})
c.Check(si, check.DeepEquals, &snap.SideInfo{
@@ -2137,9 +2192,7 @@
"\r\n" +
"xyzzy\r\n" +
"----hello--\r\n"
- d := s.daemon(c)
- d.overlord.Loop()
- defer d.overlord.Stop()
+ s.daemonWithOverlordMock(c)
req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body))
c.Assert(err, check.IsNil)
@@ -2180,9 +2233,7 @@
}
func (s *apiSuite) TestTrySnap(c *check.C) {
- d := s.daemon(c)
- d.overlord.Loop()
- defer d.overlord.Stop()
+ d := s.daemonWithFakeSnapManager(c)
var err error
@@ -2235,22 +2286,13 @@
defer st.Unlock()
for _, t := range []struct {
- coreInfoErr error
- nTasks int
- installSnap string
- flags snapstate.Flags
- desc string
+ flags snapstate.Flags
+ desc string
}{
- // core installed
- {nil, 1, "", snapstate.Flags{}, "core; -"},
- {nil, 1, "", snapstate.Flags{DevMode: true}, "core; devmode"},
- {nil, 1, "", snapstate.Flags{JailMode: true}, "core; jailmode"},
- {nil, 1, "", snapstate.Flags{Classic: true}, "core; classic"},
- // no-core-installed
- {state.ErrNoState, 2, "core", snapstate.Flags{}, "no core; -"},
- {state.ErrNoState, 2, "core", snapstate.Flags{DevMode: true}, "no core; devmode"},
- {state.ErrNoState, 2, "core", snapstate.Flags{JailMode: true}, "no core; jailmode"},
- {state.ErrNoState, 2, "core", snapstate.Flags{Classic: true}, "no core; classic"},
+ {snapstate.Flags{}, "core; -"},
+ {snapstate.Flags{DevMode: true}, "core; devmode"},
+ {snapstate.Flags{JailMode: true}, "core; jailmode"},
+ {snapstate.Flags{Classic: true}, "core; classic"},
} {
soon := 0
ensureStateSoon = func(st *state.State) {
@@ -2266,20 +2308,14 @@
return state.NewTaskSet(t), nil
}
- installSnap := ""
snapstateInstall = func(s *state.State, name, channel string, revision snap.Revision, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
if name != "core" {
c.Check(flags, check.DeepEquals, t.flags, check.Commentf(t.desc))
}
- installSnap = name
t := s.NewTask("fake-install-snap", "Doing a fake install")
return state.NewTaskSet(t), nil
}
- snapstateCoreInfo = func(s *state.State) (*snap.Info, error) {
- return nil, t.coreInfoErr
- }
-
// try the snap (without an installed core)
st.Unlock()
rsp := postSnaps(snapsCmd, reqForFlags(t.flags), nil).(*resp)
@@ -2290,11 +2326,10 @@
chg := st.Change(rsp.Change)
c.Assert(chg, check.NotNil, check.Commentf(t.desc))
- c.Assert(chg.Tasks(), check.HasLen, t.nTasks, check.Commentf(t.desc))
- c.Check(installSnap, check.Equals, t.installSnap, check.Commentf(t.desc))
+ c.Assert(chg.Tasks(), check.HasLen, 1, check.Commentf(t.desc))
st.Unlock()
- <-chg.Ready()
+ s.waitTrivialChange(c, chg)
st.Lock()
c.Check(chg.Kind(), check.Equals, "try-snap", check.Commentf(t.desc))
@@ -2332,10 +2367,8 @@
c.Check(rsp.Result.(*errorResult).Message, testutil.Contains, "not a snap directory")
}
-func (s *apiSuite) sideloadCheck(c *check.C, content string, head map[string]string, expectedFlags snapstate.Flags, hasCoreSnap bool) string {
- d := s.daemon(c)
- d.overlord.Loop()
- defer d.overlord.Stop()
+func (s *apiSuite) sideloadCheck(c *check.C, content string, head map[string]string, expectedFlags snapstate.Flags) string {
+ d := s.daemonWithFakeSnapManager(c)
soon := 0
ensureStateSoon = func(st *state.State) {
@@ -2349,13 +2382,6 @@
return &snap.Info{SuggestedName: "local"}, nil
}
- snapstateCoreInfo = func(s *state.State) (*snap.Info, error) {
- if hasCoreSnap {
- return nil, nil
- }
- // pretend we do not have a state for ubuntu-core
- return nil, state.ErrNoState
- }
snapstateInstall = func(s *state.State, name, channel string, revision snap.Revision, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
// NOTE: ubuntu-core is not installed in developer mode
c.Check(flags, check.Equals, snapstate.Flags{})
@@ -2387,13 +2413,7 @@
rsp := postSnaps(snapsCmd, req, nil).(*resp)
c.Assert(rsp.Type, check.Equals, ResponseTypeAsync)
n := 1
- if !hasCoreSnap {
- n++
- }
c.Assert(installQueue, check.HasLen, n)
- if !hasCoreSnap {
- c.Check(installQueue[0], check.Equals, defaultCoreSnapName)
- }
c.Check(installQueue[n-1], check.Matches, "local::.*/snapd-sideload-pkg-.*")
st := d.overlord.State()
@@ -2407,7 +2427,7 @@
c.Assert(chg.Tasks(), check.HasLen, n)
st.Unlock()
- <-chg.Ready()
+ s.waitTrivialChange(c, chg)
st.Lock()
c.Check(chg.Kind(), check.Equals, "install-snap")
@@ -2462,9 +2482,17 @@
c.Check(result, check.DeepEquals, map[string]interface{}{"message": `snap "test-snap" has no "test-key2" configuration option`})
}
-func (s *apiSuite) TestGetConfNoKey(c *check.C) {
- result := s.runGetConf(c, nil, 400)
- c.Check(result, check.DeepEquals, map[string]interface{}{"message": "cannot obtain configuration: no keys supplied"})
+func (s *apiSuite) TestGetRootDocument(c *check.C) {
+ d := s.daemon(c)
+ d.overlord.State().Lock()
+ tr := config.NewTransaction(d.overlord.State())
+ tr.Set("test-snap", "test-key1", "test-value1")
+ tr.Set("test-snap", "test-key2", "test-value2")
+ tr.Commit()
+ d.overlord.State().Unlock()
+
+ result := s.runGetConf(c, nil, 200)
+ c.Check(result, check.DeepEquals, map[string]interface{}{"test-key1": "test-value1", "test-key2": "test-value2"})
}
func (s *apiSuite) TestGetConfBadKey(c *check.C) {
@@ -2567,14 +2595,7 @@
}
func (s *apiSuite) TestSetConfBadSnap(c *check.C) {
- d := s.daemon(c)
-
- // Mock the hook runner
- hookRunner := testutil.MockCommand(c, "snap", "")
- defer hookRunner.Restore()
-
- d.overlord.Loop()
- defer d.overlord.Stop()
+ s.daemonWithOverlordMock(c)
text, err := json.Marshal(map[string]interface{}{"key": "value"})
c.Assert(err, check.IsNil)
@@ -2702,10 +2723,6 @@
restore := release.MockForcedDevmode(forcedDevmode)
defer restore()
- snapstateCoreInfo = func(s *state.State) (*snap.Info, error) {
- // we have core
- return nil, nil
- }
snapstateInstall = func(s *state.State, name, channel string, revno snap.Revision, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
calledFlags = flags
installQueue = append(installQueue, name)
@@ -2716,14 +2733,10 @@
}
defer func() {
- snapstateCoreInfo = nil
snapstateInstall = nil
}()
- d := s.daemon(c)
-
- d.overlord.Loop()
- defer d.overlord.Stop()
+ d := s.daemonWithFakeSnapManager(c)
var buf bytes.Buffer
if revision.Unset() {
@@ -2748,7 +2761,7 @@
c.Check(chg.Tasks(), check.HasLen, 1)
st.Unlock()
- <-chg.Ready()
+ s.waitTrivialChange(c, chg)
st.Lock()
c.Check(chg.Status(), check.Equals, state.DoneStatus)
@@ -2765,10 +2778,6 @@
installQueue := []string{}
assertstateCalledUserID := 0
- snapstateCoreInfo = func(s *state.State) (*snap.Info, error) {
- // we have core
- return nil, nil
- }
snapstateUpdate = func(s *state.State, name, channel string, revision snap.Revision, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
calledFlags = flags
calledUserID = userID
@@ -2808,10 +2817,6 @@
calledUserID := 0
installQueue := []string{}
- snapstateCoreInfo = func(s *state.State) (*snap.Info, error) {
- // we have core
- return nil, nil
- }
snapstateUpdate = func(s *state.State, name, channel string, revision snap.Revision, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
calledFlags = flags
calledUserID = userID
@@ -2850,10 +2855,6 @@
func (s *apiSuite) TestRefreshClassic(c *check.C) {
var calledFlags snapstate.Flags
- snapstateCoreInfo = func(s *state.State) (*snap.Info, error) {
- // we have ubuntu-core
- return nil, nil
- }
snapstateUpdate = func(s *state.State, name, channel string, revision snap.Revision, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
calledFlags = flags
return nil, nil
@@ -2884,10 +2885,6 @@
calledUserID := 0
installQueue := []string{}
- snapstateCoreInfo = func(s *state.State) (*snap.Info, error) {
- // we have ubuntu-core
- return nil, nil
- }
snapstateUpdate = func(s *state.State, name, channel string, revision snap.Revision, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
calledFlags = flags
calledUserID = userID
@@ -2932,9 +2929,7 @@
return []string{"fake1", "fake2"}, []*state.TaskSet{state.NewTaskSet(t)}, nil
}
- d := s.daemon(c)
- d.overlord.Loop()
- defer d.overlord.Stop()
+ d := s.daemonWithOverlordMock(c)
buf := bytes.NewBufferString(`{"action": "refresh"}`)
req, err := http.NewRequest("POST", "/v2/login", buf)
@@ -3099,103 +3094,13 @@
c.Check(removes, check.DeepEquals, inst.Snaps)
}
-func (s *apiSuite) TestInstallMissingCoreSnap(c *check.C) {
- installQueue := []*state.Task{}
-
- snapstateCoreInfo = func(s *state.State) (*snap.Info, error) {
- // pretend we do not have a core
- return nil, state.ErrNoState
- }
- snapstateInstall = func(s *state.State, name, channel string, revision snap.Revision, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
- t1 := s.NewTask("fake-install-snap", name)
- t2 := s.NewTask("fake-install-snap", "second task is just here so that we can check that the wait is correctly added to all tasks")
- installQueue = append(installQueue, t1, t2)
- return state.NewTaskSet(t1, t2), nil
- }
-
- d := s.daemon(c)
-
- d.overlord.Loop()
- defer d.overlord.Stop()
-
- buf := bytes.NewBufferString(`{"action": "install"}`)
- req, err := http.NewRequest("POST", "/v2/snaps/some-snap", buf)
- c.Assert(err, check.IsNil)
-
- s.vars = map[string]string{"name": "some-snap"}
- rsp := postSnap(snapCmd, req, nil).(*resp)
-
- c.Assert(rsp.Type, check.Equals, ResponseTypeAsync)
-
- st := d.overlord.State()
- st.Lock()
- defer st.Unlock()
- chg := st.Change(rsp.Change)
- c.Assert(chg, check.NotNil)
-
- c.Check(chg.Tasks(), check.HasLen, 4)
-
- c.Check(installQueue, check.HasLen, 4)
- // the two OS snap install tasks
- c.Check(installQueue[0].Summary(), check.Equals, defaultCoreSnapName)
- c.Check(installQueue[0].WaitTasks(), check.HasLen, 0)
- c.Check(installQueue[1].WaitTasks(), check.HasLen, 0)
- // the two "some-snap" install tasks
- c.Check(installQueue[2].Summary(), check.Equals, "some-snap")
- c.Check(installQueue[2].WaitTasks(), check.HasLen, 2)
- c.Check(installQueue[3].WaitTasks(), check.HasLen, 2)
-}
-
-// Installing ubuntu-core when not having ubuntu-core doesn't misbehave and try
-// to install ubuntu-core twice.
-func (s *apiSuite) TestInstallCoreSnapWhenMissing(c *check.C) {
- installQueue := []*state.Task{}
-
- snapstateCoreInfo = func(s *state.State) (*snap.Info, error) {
- // pretend we do not have a core
- return nil, state.ErrNoState
- }
- snapstateInstall = func(s *state.State, name, channel string, revision snap.Revision, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
- t1 := s.NewTask("fake-install-snap", name)
- t2 := s.NewTask("fake-install-snap", "second task is just here so that we can check that the wait is correctly added to all tasks")
- installQueue = append(installQueue, t1, t2)
- return state.NewTaskSet(t1, t2), nil
- }
-
- d := s.daemon(c)
- inst := &snapInstruction{
- Action: "install",
- Snaps: []string{defaultCoreSnapName},
- }
-
- st := d.overlord.State()
- st.Lock()
- defer st.Unlock()
- _, _, err := inst.dispatch()(inst, st)
- c.Check(err, check.IsNil)
-
- c.Check(installQueue, check.HasLen, 2)
- // the only OS snap install tasks
- c.Check(installQueue[0].Summary(), check.Equals, defaultCoreSnapName)
- c.Check(installQueue[0].WaitTasks(), check.HasLen, 0)
- c.Check(installQueue[1].WaitTasks(), check.HasLen, 0)
-}
-
func (s *apiSuite) TestInstallFails(c *check.C) {
- snapstateCoreInfo = func(s *state.State) (*snap.Info, error) {
- // we have core
- return nil, nil
- }
-
snapstateInstall = func(s *state.State, name, channel string, revision snap.Revision, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
t := s.NewTask("fake-install-snap-error", "Install task")
return state.NewTaskSet(t), nil
}
- d := s.daemon(c)
-
- d.overlord.Loop()
- defer d.overlord.Stop()
+ d := s.daemonWithFakeSnapManager(c)
buf := bytes.NewBufferString(`{"action": "install"}`)
req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf)
@@ -3214,7 +3119,7 @@
c.Check(chg.Tasks(), check.HasLen, 1)
st.Unlock()
- <-chg.Ready()
+ s.waitTrivialChange(c, chg)
st.Lock()
c.Check(chg.Err(), check.ErrorMatches, `(?sm).*Install task \(fake-install-snap-error errored\)`)
@@ -3250,10 +3155,6 @@
func (s *apiSuite) TestInstallDevMode(c *check.C) {
var calledFlags snapstate.Flags
- snapstateCoreInfo = func(s *state.State) (*snap.Info, error) {
- return nil, nil
- }
-
snapstateInstall = func(s *state.State, name, channel string, revision snap.Revision, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
calledFlags = flags
@@ -3281,10 +3182,6 @@
func (s *apiSuite) TestInstallJailMode(c *check.C) {
var calledFlags snapstate.Flags
- snapstateCoreInfo = func(s *state.State) (*snap.Info, error) {
- return nil, nil
- }
-
snapstateInstall = func(s *state.State, name, channel string, revision snap.Revision, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
calledFlags = flags
@@ -3657,9 +3554,6 @@
s.mockSnap(c, consumerYaml)
s.mockSnap(c, differentProducerYaml)
- d.overlord.Loop()
- defer d.overlord.Stop()
-
action := &interfaceAction{
Action: "connect",
Plugs: []plugJSON{{Snap: "consumer", Name: "plug"}},
@@ -3699,9 +3593,6 @@
s.mockSnap(c, producerYaml)
s.mockSnap(c, consumerYaml)
- d.overlord.Loop()
- defer d.overlord.Stop()
-
action := &interfaceAction{
Action: "connect",
Plugs: []plugJSON{{Snap: "consumer", Name: "missingplug"}},
@@ -3741,9 +3632,6 @@
s.mockSnap(c, producerYaml)
// there is no producer, no slot defined
- d.overlord.Loop()
- defer d.overlord.Stop()
-
action := &interfaceAction{
Action: "connect",
Plugs: []plugJSON{{Snap: "consumer", Name: "plug"}},
@@ -3842,15 +3730,12 @@
}
func (s *apiSuite) TestDisconnectPlugFailureNoSuchPlug(c *check.C) {
- d := s.daemon(c)
+ s.daemon(c)
s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
// there is no consumer, no plug defined
s.mockSnap(c, producerYaml)
- d.overlord.Loop()
- defer d.overlord.Stop()
-
action := &interfaceAction{
Action: "disconnect",
Plugs: []plugJSON{{Snap: "consumer", Name: "plug"}},
@@ -3878,15 +3763,12 @@
}
func (s *apiSuite) TestDisconnectPlugFailureNoSuchSlot(c *check.C) {
- d := s.daemon(c)
+ s.daemon(c)
s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
s.mockSnap(c, consumerYaml)
// there is no producer, no slot defined
- d.overlord.Loop()
- defer d.overlord.Stop()
-
action := &interfaceAction{
Action: "disconnect",
Plugs: []plugJSON{{Snap: "consumer", Name: "plug"}},
@@ -3915,15 +3797,12 @@
}
func (s *apiSuite) TestDisconnectPlugFailureNotConnected(c *check.C) {
- d := s.daemon(c)
+ s.daemon(c)
s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
s.mockSnap(c, consumerYaml)
s.mockSnap(c, producerYaml)
- d.overlord.Loop()
- defer d.overlord.Stop()
-
action := &interfaceAction{
Action: "disconnect",
Plugs: []plugJSON{{Snap: "consumer", Name: "plug"}},
@@ -5742,10 +5621,6 @@
func (s *apiSuite) TestInstallUnaliased(c *check.C) {
var calledFlags snapstate.Flags
- snapstateCoreInfo = func(s *state.State) (*snap.Info, error) {
- return nil, nil
- }
-
snapstateInstall = func(s *state.State, name, channel string, revision snap.Revision, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
calledFlags = flags
@@ -5785,9 +5660,7 @@
}
func (s *postDebugSuite) TestPostDebugEnsureStateSoon(c *check.C) {
- d := s.daemon(c)
- d.overlord.Loop()
- defer d.overlord.Stop()
+ s.daemonWithOverlordMock(c)
soon := 0
ensureStateSoon = func(st *state.State) {
@@ -5882,13 +5755,13 @@
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(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{
+ c.Check(apps, testutil.DeepContains, client.AppInfo{
Snap: snap,
Name: app,
Daemon: "simple",
@@ -5899,7 +5772,7 @@
for _, name := range []string{"snap-b.cmd1", "snap-d.cmd2", "snap-d.cmd3"} {
snap, app := splitAppName(name)
- c.Check(apps, testutil.DeepContains, &client.AppInfo{
+ c.Check(apps, testutil.DeepContains, client.AppInfo{
Snap: snap,
Name: app,
})
@@ -5920,13 +5793,13 @@
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(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{
+ c.Check(apps, testutil.DeepContains, client.AppInfo{
Snap: snap,
Name: app,
})
@@ -5956,13 +5829,13 @@
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(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{
+ c.Check(svcs, testutil.DeepContains, client.AppInfo{
Snap: snap,
Name: app,
Daemon: "simple",
@@ -6118,6 +5991,24 @@
c.Assert(appInfos, check.IsNil)
}
+func (s *apiSuite) TestLogsNoServices(c *check.C) {
+ // NOTE this is *apiSuite, not *appSuite, so there are no
+ // installed snaps with services
+
+ cmd := testutil.MockCommand(c, "systemctl", "").Also("journalctl", "")
+ defer cmd.Restore()
+ s.daemon(c)
+ s.d.overlord.Loop()
+ defer s.d.overlord.Stop()
+
+ 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, 404)
+ c.Assert(rsp.Type, check.Equals, ResponseTypeError)
+}
+
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"}
@@ -6234,7 +6125,7 @@
c.Assert(rsp.Type, check.Equals, ResponseTypeError)
}
-func (s *appSuite) testPostApps(c *check.C, inst appInstruction, systemctlCall []string) *state.Change {
+func (s *appSuite) testPostApps(c *check.C, inst servicestate.Instruction, systemctlCall []string) *state.Change {
postBody, err := json.Marshal(inst)
c.Assert(err, check.IsNil)
@@ -6262,55 +6153,61 @@
}
func (s *appSuite) TestPostAppsStartOne(c *check.C) {
- inst := appInstruction{Action: "start", Names: []string{"snap-a.svc2"}}
+ inst := servicestate.Instruction{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"}}
+ inst := servicestate.Instruction{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)
+ chg.State().Lock()
+ defer chg.State().Unlock()
// check the summary expands the snap into actual apps
- c.Check(chg.Summary(), check.Equals, "start of [snap-a.svc1 snap-a.svc2]")
+ c.Check(chg.Summary(), check.Equals, "Running service command")
+ c.Check(chg.Tasks()[0].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"}}
+ inst := servicestate.Instruction{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]")
+ c.Check(chg.Summary(), check.Equals, "Running service command")
+ chg.State().Lock()
+ defer chg.State().Unlock()
+ c.Check(chg.Tasks()[0].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"}}
+ inst := servicestate.Instruction{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"}}
+ inst := servicestate.Instruction{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 := servicestate.Instruction{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 := servicestate.Instruction{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 := servicestate.Instruction{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)
@@ -6381,7 +6278,7 @@
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.Status, check.Equals, 400)
c.Check(rsp.Type, check.Equals, ResponseTypeError)
c.Check(rsp.Result.(*errorResult).Message, check.Equals, `snap "snap-a" has changes in progress`)
}
diff -Nru snapd-2.28.5/daemon/daemon.go snapd-2.29.3/daemon/daemon.go
--- snapd-2.28.5/daemon/daemon.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/daemon/daemon.go 2017-10-27 06:08:37.000000000 +0000
@@ -39,7 +39,7 @@
"github.com/snapcore/snapd/client"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/httputil"
- "github.com/snapcore/snapd/i18n/dumb"
+ "github.com/snapcore/snapd/i18n"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/overlord"
@@ -86,12 +86,20 @@
d *Daemon
}
+type accessResult int
+
+const (
+ accessOK accessResult = iota
+ accessUnauthorized
+ accessForbidden
+)
+
var polkitCheckAuthorizationForPid = polkit.CheckAuthorizationForPid
-func (c *Command) canAccess(r *http.Request, user *auth.UserState) bool {
+func (c *Command) canAccess(r *http.Request, user *auth.UserState) accessResult {
if user != nil {
// Authenticated users do anything for now.
- return true
+ return accessOK
}
isUser := false
@@ -100,30 +108,30 @@
isUser = true
} else if err != errNoID {
logger.Noticef("unexpected error when attempting to get UID: %s", err)
- return false
+ return accessForbidden
} else if c.SnapOK {
- return true
+ return accessOK
}
if r.Method == "GET" {
// Guest and user access restricted to GET requests
if c.GuestOK {
- return true
+ return accessOK
}
if isUser && c.UserOK {
- return true
+ return accessOK
}
}
// Remaining admin checks rely on identifying peer uid
if !isUser {
- return false
+ return accessUnauthorized
}
if uid == 0 {
// Superuser does anything.
- return true
+ return accessOK
}
if c.PolkitOK != "" {
@@ -139,14 +147,16 @@
if authorized, err := polkitCheckAuthorizationForPid(pid, c.PolkitOK, nil, flags); err == nil {
if authorized {
// polkit says user is authorised
- return true
+ return accessOK
}
- } else if err != polkit.ErrDismissed {
+ } else if err == polkit.ErrDismissed {
+ return accessForbidden
+ } else {
logger.Noticef("polkit error: %s", err)
}
}
- return false
+ return accessUnauthorized
}
func (c *Command) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@@ -156,9 +166,15 @@
user, _ := UserFromRequest(state, r)
state.Unlock()
- if !c.canAccess(r, user) {
+ switch c.canAccess(r, user) {
+ case accessOK:
+ // nothing
+ case accessUnauthorized:
Unauthorized("access denied").ServeHTTP(w, r)
return
+ case accessForbidden:
+ Forbidden("forbidden").ServeHTTP(w, r)
+ return
}
var rspf ResponseFunc
diff -Nru snapd-2.28.5/daemon/daemon_test.go snapd-2.29.3/daemon/daemon_test.go
--- snapd-2.28.5/daemon/daemon_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/daemon/daemon_test.go 2017-10-27 06:08:37.000000000 +0000
@@ -41,7 +41,6 @@
"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"
@@ -63,10 +62,6 @@
return s.authorized, s.err
}
-func (s *daemonSuite) SetUpSuite(c *check.C) {
- snapstate.CanAutoRefresh = nil
-}
-
func (s *daemonSuite) SetUpTest(c *check.C) {
dirs.SetRootDir(c.MkDir())
err := os.MkdirAll(filepath.Dir(dirs.SnapStateFile), 0755)
@@ -152,31 +147,31 @@
del := &http.Request{Method: "DELETE"}
cmd := &Command{d: newTestDaemon(c)}
- c.Check(cmd.canAccess(get, nil), check.Equals, false)
- c.Check(cmd.canAccess(put, nil), check.Equals, false)
- c.Check(cmd.canAccess(pst, nil), check.Equals, false)
- c.Check(cmd.canAccess(del, nil), check.Equals, false)
+ c.Check(cmd.canAccess(get, nil), check.Equals, accessUnauthorized)
+ c.Check(cmd.canAccess(put, nil), check.Equals, accessUnauthorized)
+ c.Check(cmd.canAccess(pst, nil), check.Equals, accessUnauthorized)
+ c.Check(cmd.canAccess(del, nil), check.Equals, accessUnauthorized)
cmd = &Command{d: newTestDaemon(c), UserOK: true}
- c.Check(cmd.canAccess(get, nil), check.Equals, false)
- c.Check(cmd.canAccess(put, nil), check.Equals, false)
- c.Check(cmd.canAccess(pst, nil), check.Equals, false)
- c.Check(cmd.canAccess(del, nil), check.Equals, false)
+ c.Check(cmd.canAccess(get, nil), check.Equals, accessUnauthorized)
+ c.Check(cmd.canAccess(put, nil), check.Equals, accessUnauthorized)
+ c.Check(cmd.canAccess(pst, nil), check.Equals, accessUnauthorized)
+ c.Check(cmd.canAccess(del, nil), check.Equals, accessUnauthorized)
cmd = &Command{d: newTestDaemon(c), GuestOK: true}
- c.Check(cmd.canAccess(get, nil), check.Equals, true)
- c.Check(cmd.canAccess(put, nil), check.Equals, false)
- c.Check(cmd.canAccess(pst, nil), check.Equals, false)
- c.Check(cmd.canAccess(del, nil), check.Equals, false)
+ c.Check(cmd.canAccess(get, nil), check.Equals, accessOK)
+ c.Check(cmd.canAccess(put, nil), check.Equals, accessUnauthorized)
+ c.Check(cmd.canAccess(pst, nil), check.Equals, accessUnauthorized)
+ c.Check(cmd.canAccess(del, nil), check.Equals, accessUnauthorized)
// Since this request has no RemoteAddr, it must be coming from the snap
// socket instead of the snapd one. In that case, if SnapOK is true, this
// command should be wide open for all HTTP methods.
cmd = &Command{d: newTestDaemon(c), SnapOK: true}
- c.Check(cmd.canAccess(get, nil), check.Equals, true)
- c.Check(cmd.canAccess(put, nil), check.Equals, true)
- c.Check(cmd.canAccess(pst, nil), check.Equals, true)
- c.Check(cmd.canAccess(del, nil), check.Equals, true)
+ c.Check(cmd.canAccess(get, nil), check.Equals, accessOK)
+ c.Check(cmd.canAccess(put, nil), check.Equals, accessOK)
+ c.Check(cmd.canAccess(pst, nil), check.Equals, accessOK)
+ c.Check(cmd.canAccess(del, nil), check.Equals, accessOK)
}
func (s *daemonSuite) TestUserAccess(c *check.C) {
@@ -184,23 +179,23 @@
put := &http.Request{Method: "PUT", RemoteAddr: "pid=100;uid=42;"}
cmd := &Command{d: newTestDaemon(c)}
- c.Check(cmd.canAccess(get, nil), check.Equals, false)
- c.Check(cmd.canAccess(put, nil), check.Equals, false)
+ c.Check(cmd.canAccess(get, nil), check.Equals, accessUnauthorized)
+ c.Check(cmd.canAccess(put, nil), check.Equals, accessUnauthorized)
cmd = &Command{d: newTestDaemon(c), UserOK: true}
- c.Check(cmd.canAccess(get, nil), check.Equals, true)
- c.Check(cmd.canAccess(put, nil), check.Equals, false)
+ c.Check(cmd.canAccess(get, nil), check.Equals, accessOK)
+ c.Check(cmd.canAccess(put, nil), check.Equals, accessUnauthorized)
cmd = &Command{d: newTestDaemon(c), GuestOK: true}
- c.Check(cmd.canAccess(get, nil), check.Equals, true)
- c.Check(cmd.canAccess(put, nil), check.Equals, false)
+ c.Check(cmd.canAccess(get, nil), check.Equals, accessOK)
+ c.Check(cmd.canAccess(put, nil), check.Equals, accessUnauthorized)
// Since this request has a RemoteAddr, it must be coming from the snapd
// socket instead of the snap one. In that case, SnapOK should have no
// bearing on the default behavior, which is to deny access.
cmd = &Command{d: newTestDaemon(c), SnapOK: true}
- c.Check(cmd.canAccess(get, nil), check.Equals, false)
- c.Check(cmd.canAccess(put, nil), check.Equals, false)
+ c.Check(cmd.canAccess(get, nil), check.Equals, accessUnauthorized)
+ c.Check(cmd.canAccess(put, nil), check.Equals, accessUnauthorized)
}
func (s *daemonSuite) TestSuperAccess(c *check.C) {
@@ -208,20 +203,20 @@
put := &http.Request{Method: "PUT", RemoteAddr: "pid=100;uid=0;"}
cmd := &Command{d: newTestDaemon(c)}
- c.Check(cmd.canAccess(get, nil), check.Equals, true)
- c.Check(cmd.canAccess(put, nil), check.Equals, true)
+ c.Check(cmd.canAccess(get, nil), check.Equals, accessOK)
+ c.Check(cmd.canAccess(put, nil), check.Equals, accessOK)
cmd = &Command{d: newTestDaemon(c), UserOK: true}
- c.Check(cmd.canAccess(get, nil), check.Equals, true)
- c.Check(cmd.canAccess(put, nil), check.Equals, true)
+ c.Check(cmd.canAccess(get, nil), check.Equals, accessOK)
+ c.Check(cmd.canAccess(put, nil), check.Equals, accessOK)
cmd = &Command{d: newTestDaemon(c), GuestOK: true}
- c.Check(cmd.canAccess(get, nil), check.Equals, true)
- c.Check(cmd.canAccess(put, nil), check.Equals, true)
+ c.Check(cmd.canAccess(get, nil), check.Equals, accessOK)
+ c.Check(cmd.canAccess(put, nil), check.Equals, accessOK)
cmd = &Command{d: newTestDaemon(c), SnapOK: true}
- c.Check(cmd.canAccess(get, nil), check.Equals, true)
- c.Check(cmd.canAccess(put, nil), check.Equals, true)
+ c.Check(cmd.canAccess(get, nil), check.Equals, accessOK)
+ c.Check(cmd.canAccess(put, nil), check.Equals, accessOK)
}
func (s *daemonSuite) TestPolkitAccess(c *check.C) {
@@ -230,15 +225,19 @@
// polkit says user is not authorised
s.authorized = false
- c.Check(cmd.canAccess(put, nil), check.Equals, false)
+ c.Check(cmd.canAccess(put, nil), check.Equals, accessUnauthorized)
// polkit grants authorisation
s.authorized = true
- c.Check(cmd.canAccess(put, nil), check.Equals, true)
+ c.Check(cmd.canAccess(put, nil), check.Equals, accessOK)
// an error occurs communicating with polkit
s.err = errors.New("error")
- c.Check(cmd.canAccess(put, nil), check.Equals, false)
+ c.Check(cmd.canAccess(put, nil), check.Equals, accessUnauthorized)
+
+ // if the user dismisses the auth request, forbid access
+ s.err = polkit.ErrDismissed
+ c.Check(cmd.canAccess(put, nil), check.Equals, accessForbidden)
}
func (s *daemonSuite) TestPolkitAccessForGet(c *check.C) {
@@ -247,14 +246,14 @@
// polkit can grant authorisation for GET requests
s.authorized = true
- c.Check(cmd.canAccess(get, nil), check.Equals, true)
+ c.Check(cmd.canAccess(get, nil), check.Equals, accessOK)
// 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)
+ c.Check(cmd.canAccess(get, nil), check.Equals, accessOK)
}
func (s *daemonSuite) TestPolkitInteractivity(c *check.C) {
@@ -267,18 +266,18 @@
c.Assert(err, check.IsNil)
logger.SetLogger(log)
- c.Check(cmd.canAccess(put, nil), check.Equals, true)
+ c.Check(cmd.canAccess(put, nil), check.Equals, accessOK)
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(cmd.canAccess(put, nil), check.Equals, accessOK)
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(cmd.canAccess(put, nil), check.Equals, accessOK)
c.Check(s.lastPolkitFlags, check.Equals, polkit.CheckNone)
c.Check(logbuf.String(), testutil.Contains, "error parsing X-Allow-Interaction header:")
}
diff -Nru snapd-2.28.5/daemon/snap.go snapd-2.29.3/daemon/snap.go
--- snapd-2.28.5/daemon/snap.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/daemon/snap.go 2017-11-02 15:44:20.000000000 +0000
@@ -163,13 +163,6 @@
return about, firstErr
}
-// screenshotJSON contains the json for snap.ScreenshotInfo
-type screenshotJSON struct {
- URL string `json:"url"`
- Width int64 `json:"width,omitempty"`
- Height int64 `json:"height,omitempty"`
-}
-
type bySnapApp []*snap.AppInfo
func (a bySnapApp) Len() int { return len(a) }
@@ -282,14 +275,14 @@
return appInfos, nil
}
-func clientAppInfosFromSnapAppInfos(apps []*snap.AppInfo) []*client.AppInfo {
+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{})
+ sysd := systemd.New(dirs.GlobalRootDir, progress.Null)
- out := make([]*client.AppInfo, len(apps))
+ out := make([]client.AppInfo, len(apps))
for i, app := range apps {
- out[i] = &client.AppInfo{
+ out[i] = client.AppInfo{
Snap: app.Snap.Name(),
Name: app.Name,
}
@@ -314,7 +307,7 @@
return out
}
-func mapLocal(about aboutSnap) map[string]interface{} {
+func mapLocal(about aboutSnap) *client.Snap {
localSnap, snapst := about.info, about.snapst
status := "installed"
if snapst.Active && localSnap.Revision == snapst.Current {
@@ -331,43 +324,38 @@
// TODO: expose aliases information and state?
- result := map[string]interface{}{
- "description": localSnap.Description(),
- "developer": about.publisher,
- "icon": snapIcon(localSnap),
- "id": localSnap.SnapID,
- "install-date": snapDate(localSnap),
- "installed-size": localSnap.Size,
- "name": localSnap.Name(),
- "revision": localSnap.Revision,
- "status": status,
- "summary": localSnap.Summary(),
- "type": string(localSnap.Type),
- "version": localSnap.Version,
- "channel": localSnap.Channel,
- "tracking-channel": snapst.Channel,
- "confinement": localSnap.Confinement,
- "devmode": snapst.DevMode,
- "trymode": snapst.TryMode,
- "jailmode": snapst.JailMode,
- "private": localSnap.Private,
- "apps": apps,
- "broken": localSnap.Broken,
- "contact": localSnap.Contact,
- }
-
- if localSnap.Title() != "" {
- result["title"] = localSnap.Title()
- }
-
- if localSnap.License != "" {
- result["license"] = localSnap.License
+ result := &client.Snap{
+ Description: localSnap.Description(),
+ Developer: about.publisher,
+ Icon: snapIcon(localSnap),
+ ID: localSnap.SnapID,
+ InstallDate: snapDate(localSnap),
+ InstalledSize: localSnap.Size,
+ Name: localSnap.Name(),
+ Revision: localSnap.Revision,
+ Status: status,
+ Summary: localSnap.Summary(),
+ Type: string(localSnap.Type),
+ Version: localSnap.Version,
+ Channel: localSnap.Channel,
+ TrackingChannel: snapst.Channel,
+ // TODO: send ignore-validation
+ Confinement: string(localSnap.Confinement),
+ DevMode: snapst.DevMode,
+ TryMode: snapst.TryMode,
+ JailMode: snapst.JailMode,
+ Private: localSnap.Private,
+ Apps: apps,
+ Broken: localSnap.Broken,
+ Contact: localSnap.Contact,
+ Title: localSnap.Title(),
+ License: localSnap.License,
}
return result
}
-func mapRemote(remoteSnap *snap.Info) map[string]interface{} {
+func mapRemote(remoteSnap *snap.Info) *client.Snap {
status := "available"
if remoteSnap.MustBuy {
status = "priced"
@@ -378,55 +366,37 @@
confinement = snap.StrictConfinement
}
- screenshots := make([]screenshotJSON, len(remoteSnap.Screenshots))
+ screenshots := make([]client.Screenshot, len(remoteSnap.Screenshots))
for i, screenshot := range remoteSnap.Screenshots {
- screenshots[i] = screenshotJSON{
+ screenshots[i] = client.Screenshot{
URL: screenshot.URL,
Width: screenshot.Width,
Height: screenshot.Height,
}
}
- result := map[string]interface{}{
- "description": remoteSnap.Description(),
- "developer": remoteSnap.Publisher,
- "download-size": remoteSnap.Size,
- "icon": snapIcon(remoteSnap),
- "id": remoteSnap.SnapID,
- "name": remoteSnap.Name(),
- "revision": remoteSnap.Revision,
- "status": status,
- "summary": remoteSnap.Summary(),
- "type": string(remoteSnap.Type),
- "version": remoteSnap.Version,
- "channel": remoteSnap.Channel,
- "private": remoteSnap.Private,
- "confinement": confinement,
- "contact": remoteSnap.Contact,
- }
-
- if remoteSnap.Title() != "" {
- result["title"] = remoteSnap.Title()
- }
-
- if remoteSnap.License != "" {
- result["license"] = remoteSnap.License
- }
-
- if len(screenshots) > 0 {
- result["screenshots"] = screenshots
- }
-
- if len(remoteSnap.Prices) > 0 {
- result["prices"] = remoteSnap.Prices
- }
-
- if len(remoteSnap.Channels) > 0 {
- result["channels"] = remoteSnap.Channels
- }
-
- if len(remoteSnap.Tracks) > 0 {
- result["tracks"] = remoteSnap.Tracks
+ result := &client.Snap{
+ Description: remoteSnap.Description(),
+ Developer: remoteSnap.Publisher,
+ DownloadSize: remoteSnap.Size,
+ Icon: snapIcon(remoteSnap),
+ ID: remoteSnap.SnapID,
+ Name: remoteSnap.Name(),
+ Revision: remoteSnap.Revision,
+ Status: status,
+ Summary: remoteSnap.Summary(),
+ Type: string(remoteSnap.Type),
+ Version: remoteSnap.Version,
+ Channel: remoteSnap.Channel,
+ Private: remoteSnap.Private,
+ Confinement: string(confinement),
+ Contact: remoteSnap.Contact,
+ Title: remoteSnap.Title(),
+ License: remoteSnap.License,
+ Screenshots: screenshots,
+ Prices: remoteSnap.Prices,
+ Channels: remoteSnap.Channels,
+ Tracks: remoteSnap.Tracks,
}
return result
diff -Nru snapd-2.28.5/daemon/ucrednet.go snapd-2.29.3/daemon/ucrednet.go
--- snapd-2.28.5/daemon/ucrednet.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/daemon/ucrednet.go 2017-10-23 06:17:27.000000000 +0000
@@ -57,7 +57,8 @@
if pid == ucrednetNoProcess || uid == ucrednetNobody {
err = errNoID
}
- return
+
+ return pid, uid, err
}
type ucrednetAddr struct {
diff -Nru snapd-2.28.5/data/completion/snap snapd-2.29.3/data/completion/snap
--- snapd-2.28.5/data/completion/snap 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/data/completion/snap 2017-10-23 06:17:27.000000000 +0000
@@ -19,24 +19,34 @@
local cur prev words cword
_init_completion -n : || return
+ if [[ ${#words[@]} -le 2 ]]; then
+ # we're completing on the first word
+ COMPREPLY=($(GO_FLAGS_COMPLETION=1 "${words[@]}"))
+ return 0
+ fi
+
local command
- if [[ ${#words[@]} -gt 2 ]]; then
- if [[ ${words[1]} =~ ^-- ]]; then
- # global options take no args
- return 0
- fi
- if [[ ${words[-2]} = "--help" ]]; then
- # help takes no args
+ if [[ ${words[1]} =~ ^- ]]; then
+ # global options take no args
+ return 0
+ fi
+
+ for w in "${words[@]:1}"; do
+ if [[ "$w" == "-h" || "$w" == "--help" ]]; then
+ # completing on help gets confusing
return 0
fi
+ done
- command=${words[-2]}
- fi
+ command=${words[1]}
# Only split on newlines
local IFS=$'\n'
- COMPREPLY=($(GO_FLAGS_COMPLETION=1 "${words[@]}"))
+ # now we pass _just the bit that's being completed_ of the command
+ # to snap for it to figure it out. go-flags isn't smart enough to
+ # look at COMP_WORDS etc. itself.
+ COMPREPLY=($(GO_FLAGS_COMPLETION=1 snap "$command" "$cur"))
case $command in
install|info|sign-build)
diff -Nru snapd-2.28.5/debian/changelog snapd-2.29.3/debian/changelog
--- snapd-2.28.5/debian/changelog 2017-10-13 21:25:46.000000000 +0000
+++ snapd-2.29.3/debian/changelog 2017-11-09 18:16:29.000000000 +0000
@@ -1,3 +1,241 @@
+snapd (2.29.3) xenial; urgency=medium
+
+ * New upstream release, LP: #1726258
+ - daemon: cherry-picked /v2/logs fixes
+ - cmd/snap-confine: Respect biarch nature of libdirs
+ - cmd/snap-confine: Ensure snap-confine is allowed to access os-
+ release
+ - interfaces: fix udev tagging for hooks
+ - cmd: fix re-exec bug with classic confinement for host snapd
+ - tests: disable xdg-open-compat test
+ - cmd/snap-confine: add slave PTYs and let devpts newinstance
+ perform mediation
+ - interfaces/many: misc policy updates for browser-support, cups-
+ control and network-status
+ - interfaces/raw-usb: match on SUBSYSTEM, not SUBSYSTEMS
+ - tests: fix security-device-cgroup* tests on devices with
+ framebuffer
+
+ -- Michael Vogt Thu, 09 Nov 2017 19:16:29 +0100
+
+snapd (2.29.2) xenial; urgency=medium
+
+ * New upstream release, LP: #1726258
+ - snapctl: disable stop/start/restart (2.29)
+ - cmd/snap-update-ns: fix collection of changes made
+
+ -- Michael Vogt Fri, 03 Nov 2017 17:17:14 +0100
+
+snapd (2.29.1) xenial; urgency=medium
+
+ * New upstream release, LP: #1726258
+ - interfaces: fix incorrect signature of ofono DBusPermanentSlot
+ - interfaces/serial-port: udev tag plugged slots that have just
+ 'path' via KERNEL
+ - interfaces/hidraw: udev tag plugged slots that have just 'path'
+ via KERNEL
+ - interfaces/uhid: unconditionally add existing uhid device to the
+ device cgroup
+ - cmd/snap-update-ns: fix mount rules for font sharing
+ - tests: disable refresh-undo test on trusty for now
+ - tests: use `snap change --last=install` in snapd-reexec test
+ - Revert " wrappers: fail install if exec-line cannot be re-written
+ - interfaces: don't udev tag devmode or classic snaps
+ - many: make ignore-validation sticky and send the flag with refresh
+ requests
+
+ -- Michael Vogt Fri, 03 Nov 2017 07:25:17 +0100
+
+snapd (2.29) xenial; urgency=medium
+
+ * New upstream release, LP: #1726258
+ - interfaces/many: miscellaneous updates based on feedback from the
+ field
+ - snap-confine: allow reading uevents from any where in /sys
+ - spread: add bionic beaver
+ - debian: make packaging/ubuntu-14.04/copyright a real file again
+ - tests: cherry pick the fix for services test into 2.29
+ - cmd/snap-update-ns: initialize logger
+ - hooks/configure: queue service restarts
+ - snap-{confine,seccomp}: make @unrestricted fully unrestricted
+ - interfaces: clean system apparmor cache on core device
+ - debian: do not build static snap-exec on powerpc
+ - snap-confine: increase sanity_timeout to 6s
+ - snapctl: cherry pick service commands changes
+ - cmd/snap: tell translators about arg names and descs req's
+ - systemd: run all mount units before snapd.service to avoid race
+ - store: add a test to show auth failures are forwarded by doRequest
+ - daemon: convert ErrInvalidCredentials to a 401 Unauthorized error.
+ - store: forward on INVALID_CREDENTIALS error as
+ ErrInvalidCredentials
+ - daemon: generate a forbidden response message if polkit dialog is
+ dismissed
+ - daemon: Allow Polkit authorization to cancel changes.
+ - travis: switch to container based test runs
+ - interfaces: reduce duplicated code in interface tests mocks
+ - tests: improve revert related testing
+ - interfaces: sanitize plugs and slots early in ReadInfo
+ - store: add download caching
+ - preserve TMPDIR and HOSTALIASES across snap-confine invocation
+ - snap-confine: init all arrays with `= {0,}`
+ - tests: adding test for network-manager interface
+ - interfaces/mount: don't generate legacy per-hook/per-app mount
+ profiles
+ - snap: introduce structured epochs
+ - tests: fix interfaces-cups-control test for cups-2.2.5
+ - snap-confine: cleanup incorrectly created nvidia udev tags
+ - cmd/snap-confine: update valid security tag regexp
+ - cmd/libsnap: enable two stranded tests
+ - cmd,packaging: enable apparmor on openSUSE
+ - overlord/ifacestate: refresh all security backends on startup
+ - interfaces/dbus: drop unneeded check for
+ release.ReleaseInfo.ForceDevMode
+ - dbus: ensure io.snapcraft.Launcher.service is created on re-
+ exec
+ - overlord/auth: continue for now supporting UBUNTU_STORE_ID if the
+ model is generic-classic
+ - snap-confine: add support for handling /dev/nvidia-modeset
+ - interfaces/network-control: remove incorrect rules for tun
+ - spread: allow setting SPREAD_DEBUG_EACH=0 to disable debug-each
+ section
+ - packaging: remove .mnt files on removal
+ - tests: fix econnreset scenario when the iptables rule was not
+ created
+ - tests: add test for lxd interface
+ - run-checks: use nakedret static checker to check for naked
+ returns on long functions
+ - progress: be more flexible in testing ansimeter
+ - interfaces: fix udev rules for tun
+ - many: implement our own ANSI-escape-using progress indicator
+ - snap-exec: update tests to follow main_test pattern
+ - snap: support "command: foo $ENV_STRING"
+ - packaging: update nvidia configure options
+ - snap: add new `snap pack` and use in tests
+ - cmd: correctly name the "Ubuntu" and "Arch" NVIDIA methods
+ - cmd: add autogen case for solus
+ - tests: do not use http://canihazip.com/ which appears to be down
+ - hooks: commands for controlling own services from snapctl
+ - snap: refactor cmdGet.Execute()
+ - interfaces/mount: make Change.Perform testable and test it
+ - interfaces/mount,cmd/snap-update-ns: move change code
+ - snap-confine: is_running_on_classic_distribution() looks into os-
+ release
+ - interfaces: misc updates for default, browser-support, home and
+ system-observe
+ - interfaces: deny lttng by default
+ - interfaces/lxd: lxd slot implementation can also be an app snap
+ - release,cmd,dirs: Redo the distro checks to take into account
+ distribution families
+ - cmd/snap: completion for alias and unalias
+ - snap-confine: add new SC_CLEANUP and use it
+ - snap: refrain from running filepath.Base on random strings
+ - cmd/snap-confine: put processes into freezer hierarchy
+ - wrappers: fail install if exec-line cannot be re-written
+ - cmd/snap-seccomp,osutil: make user/group lookup functions public
+ - snapstate: deal with snap user data in the /root/ directory
+ - interfaces: Enhance full-confinement support for biarch
+ distributions
+ - snap-confine: Only attempt to copy/mount NVIDIA libs when NVIDIA
+ is used
+ - packaging/fedora: Add Fedora 26, 27, and Rawhide symlinks
+ - overlord/snapstate: prefer a smaller corner case for doing the
+ wrong thing
+ - cmd/snap-repair: set user agent for snap-repair http requests
+ - packaging: bring down the delta between 14.04 and 16.04
+ - snap-confine: Ensure lib64 biarch directory is respected
+ - snap-confine: update apparmor rules for fedora based base snaps
+ - tests: Increase SNAPD_CONFIGURE_HOOK_TIMEOUT to 3 minutes to
+ install real snaps
+ - daemon: use client.Snap instead of map[string]interface{} for
+ snaps.
+ - hooks: rename refresh hook to post-refresh
+ - git: make the .gitingore file a bit more targeted
+ - interfaces/opengl: don't udev tag nvidia devices and use snap-
+ confine instead
+ - cmd/snap-{confine,update-ns}: apply mount profiles using snap-
+ update-ns
+ - cmd: update "make hack"
+ - interfaces/system-observe: allow clients to enumerate DBus
+ connection names
+ - snap-repair: implement `snap-repair {list,show}`
+ - dirs,interfaces: create snap-confine.d on demand when re-executing
+ - snap-confine: fix base snaps on core
+ - cmd/snap-repair: fix tests when running as root
+ - interfaces: add Connection type
+ - cmd/snap-repair: skip disabled repairs
+ - cmd/snap-repair: prefer leaking unmanaged fds on test failure over
+ closing random ones
+ - snap-repair: make `repair` binary available for repair scripts
+ - snap-repair: fix missing Close() in TestStatusHappy
+ - cmd/snap-confine,packaging: import snapd-generated policy
+ - cmd/snap: return empty document if snap has no configuration
+ - snap-seccomp: run secondary-arch tests via gcc-multilib
+ - snap: implement `snap {repair,repairs}` and pass-through to snap-
+ repair
+ - interfaces/builtin: allow receiving dbus messages
+ - snap-repair: implement `snap-repair {done,skip,retry}`
+ - data/completion: small tweak to snap completion snippet
+ - dirs: fix classic support detection
+ - cmd/snap-repair: integrate root public keys for repairs
+ - tests: fix ubuntu core services
+ - tests: add new test that checks that the compat snapd-xdg-open
+ works
+ - snap-confine: improve error message if core/u-core cannot be found
+ - tests: only run tests/regression/nmcli on amd64
+ - interfaces: mount host system fonts in desktop interface
+ - interfaces: enable partial apparmor support
+ - snapstate: auto-install missing base snaps
+ - spread: work around temporary packaging issue in debian sid
+ - asserts,cmd/snap-repair: introduce a mandatory summary for repairs
+ - asserts,cmd/snap-repair: represent RepairID internally as an int
+ - tests: test the real "xdg-open" from the core snap
+ - many: implement fetching sections and package names periodically.
+ - interfaces/network: allow using netcat as client
+ - snap-seccomp, osutil: use osutil.AtomicFile in snap-seccomp
+ - snap-seccomp: skip mknod syscall on arm64
+ - tests: add trivial canonical-livepatch test
+ - tests: add test that ensures that all core services are working
+ - many: add logger.MockLogger() and use it in the tests
+ - snap-repair: fix test failure in TestRepairHitsTimeout
+ - asserts: add empty values check in HeadersFromPrimaryKey
+ - daemon: remove unused installSnap var in test
+ - daemon: reach for Overlord.Loop less thanks to overlord.Mock
+ - snap-seccomp: manually resolve socket() call in tests
+ - tests: change regex used to validate installed ubuntu core snap
+ - cmd/snapctl: allow snapctl -h without a context (regression fix).
+ - many: use snapcore/snapd/i18n instead of i18n/dumb
+ - many: introduce asserts.NotFoundError replacing both ErrNotFound
+ and store.AssertionNotFoundError
+ - packaging: don't include any marcos in comments
+ - overlord: use overlord.Mock in more tests, make sure we check the
+ outcome of Settle
+ - tests: try to fix staging tests
+ - store: simplify api base url config
+ - systemd: add systemd.MockJournalctl()
+ - many: provide systemd.MockSystemctl() helper
+ - tests: improve the listing test to not fail for e.g. 2.28~rc2
+ - snapstate: give snapmgrTestSuite.settle() more time to settle
+ - tests: fix regex to check core version on snap list
+ - debian: update trusted account-keys check on 14.04 packaging
+ - interfaces: add udev netlink support to hardware-observe
+ - overlord: introduce Mock which enables to use Overlord.Settle for
+ settle in many more places
+ - snap-repair: execute the repair and capture logs/status
+ - tests: run the tests/unit/go everywhere
+ - daemon, snapstate: move ensureCore from daemon/api.go into
+ snapstate.go
+ - cmd/snap: get keys or root document
+ - spread.yaml: turn suse to manual given that it's breaking master
+ - many: configure store from state, reconfigure store at runtime
+ - osutil: AtomicWriter (an io.Writer), and io.Reader versions of
+ AtomicWrite*
+ - tests: check for negative syscalls in runBpf() and skip those
+ tests
+ - docs: use abolute path in PULL_REQUEST_TEMPLATE.md
+ - store: move device auth endpoint uris to config (#3831)
+
+ -- Michael Vogt Mon, 30 Oct 2017 16:22:31 +0100
+
snapd (2.28.5) xenial; urgency=medium
* New upstream release, LP: #1714984
diff -Nru snapd-2.28.5/debian/control snapd-2.29.3/debian/control
--- snapd-2.28.5/debian/control 2017-10-11 17:40:25.000000000 +0000
+++ snapd-2.29.3/debian/control 2017-10-23 06:17:27.000000000 +0000
@@ -13,6 +13,7 @@
dh-golang (>=1.7),
dh-systemd,
fakeroot,
+ gcc-multilib [amd64],
gettext,
grub-common,
gnupg2,
diff -Nru snapd-2.28.5/debian/rules snapd-2.29.3/debian/rules
--- snapd-2.28.5/debian/rules 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/debian/rules 2017-10-27 12:24:38.000000000 +0000
@@ -70,7 +70,7 @@
# for stability, predicability and easy of deployment. We need to link some
# things dynamically though: udev has no stable IPC protocol between
# libudev and udevd so we need to link with it dynamically.
- VENDOR_ARGS=--enable-nvidia-ubuntu --enable-static-libcap --enable-static-libapparmor --enable-static-libseccomp
+ VENDOR_ARGS=--enable-nvidia-multiarch --enable-static-libcap --enable-static-libapparmor --enable-static-libseccomp
BUILT_USING_PACKAGES=libcap-dev libapparmor-dev libseccomp-dev
else
ifeq ($(shell dpkg-vendor --query Vendor),Debian)
@@ -124,12 +124,19 @@
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
+
+ # (static linking on powerpc with cgo is broken)
+ifneq ($(shell dpkg-architecture -qDEB_HOST_ARCH),powerpc)
+ # Generate static snap-exec and snap-update-ns - it somehow includes CGO so
+ # we must force a static build here. We need a static snap-{exec,update-ns}
+ # 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)
+ (cd _build/bin && GOPATH=$$(pwd)/.. go build --ldflags '-extldflags "-static"' $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snap-update-ns)
+
# ensure we generated a static build
$(shell if ldd _build/bin/snap-exec; then false "need static build"; fi)
+ $(shell if ldd _build/bin/snap-update-ns; then false "need static build"; fi)
+endif
# Build C bits, sadly manually
cd cmd && ( autoreconf -i -f )
@@ -147,74 +154,19 @@
[ $$(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$$"
+ # same for snap-repair
+ [ $$(strings _build/bin/snap-repair|grep -c -E "public-key-sha3-384: [a-zA-Z0-9_-]{64}") -eq 3 ]
+ # common with snapd
+ strings _build/bin/snap-repair|grep -c "^public-key-sha3-384: -CvQKAwRQ5h3Ffn10FILJoEZUXOv6km9FwA80-Rcj-f-6jadQ89VRswHNiEB9Lxk$$"
+ strings _build/bin/snap-repair|grep -c "^public-key-sha3-384: d-JcZF9nD9eBw7bwMnH61x-bklnQOhQud1Is6o_cn2wTj8EYDi9musrIT9z2MdAa$$"
+ # repair-root
+ strings _build/bin/snap-repair|grep -c "^public-key-sha3-384: nttW6NfBXI_E-00u38W-KH6eiksfQNXuI7IiumoV49_zkbhM0sYTzSnFlwZC-W4t$$"
endif
ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS)))
# run the snap-confine tests
$(MAKE) -C cmd check
endif
-override_dh_systemd_enable:
- # enable auto-import
- dh_systemd_enable \
- -psnapd \
- data/systemd/snapd.autoimport.service
- # we want the auto-update timer enabled by default
- dh_systemd_enable \
- -psnapd \
- data/systemd/snapd.refresh.timer
- # but the auto-update service disabled
- dh_systemd_enable \
- --no-enable \
- -psnapd \
- data/systemd/snapd.refresh.service
- # we want the repair timer enabled by default
- dh_systemd_enable \
- -psnapd \
- data/systemd/snapd.snap-repair.timer
- # but the repair service disabled
- dh_systemd_enable \
- --no-enable \
- -psnapd \
- data/systemd/snapd.snap-repair.service
- # enable snapd
- dh_systemd_enable \
- -psnapd \
- data/systemd/snapd.socket
- dh_systemd_enable \
- -psnapd \
- data/systemd/snapd.service
- dh_systemd_enable \
- -psnapd \
- data/systemd/snapd.system-shutdown.service
- dh_systemd_enable \
- -psnapd \
- data/systemd/snapd.core-fixup.service
-
-override_dh_systemd_start:
- # we want to start the auto-update timer
- dh_systemd_start \
- -psnapd \
- data/systemd/snapd.refresh.timer
- # but not start the service
- dh_systemd_start \
- --no-start \
- -psnapd \
- data/systemd/snapd.refresh.service
- # start snapd
- dh_systemd_start \
- -psnapd \
- data/systemd/snapd.socket
- dh_systemd_start \
- -psnapd \
- data/systemd/snapd.service
- # start autoimport
- dh_systemd_start \
- -psnapd \
- data/systemd/snapd.autoimport.service
- dh_systemd_start \
- -psnapd \
- data/systemd/snapd.core-fixup.service
-
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
diff -Nru snapd-2.28.5/debian/snapd.dirs snapd-2.29.3/debian/snapd.dirs
--- snapd-2.28.5/debian/snapd.dirs 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/debian/snapd.dirs 2017-10-23 06:17:27.000000000 +0000
@@ -1,5 +1,7 @@
snap
+var/cache/snapd
usr/lib/snapd
+var/lib/snapd/apparmor/snap-confine.d
var/lib/snapd/auto-import
var/lib/snapd/desktop
var/lib/snapd/environment
diff -Nru snapd-2.28.5/debian/snapd.install snapd-2.29.3/debian/snapd.install
--- snapd-2.28.5/debian/snapd.install 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/debian/snapd.install 2017-10-23 06:17:27.000000000 +0000
@@ -24,7 +24,7 @@
lib/udev/snappy-app-dev
usr/lib/snapd/snap-confine
usr/lib/snapd/snap-discard-ns
-usr/share/man/man5/snap-confine.5
+usr/share/man/man1/snap-confine.1
usr/share/man/man5/snap-discard-ns.5
# for compatibility with ancient snap installs that wrote the shell based
# wrapper scripts instead of the modern symlinks
diff -Nru snapd-2.28.5/debian/snapd.postrm snapd-2.29.3/debian/snapd.postrm
--- snapd-2.28.5/debian/snapd.postrm 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/debian/snapd.postrm 2017-10-23 06:17:27.000000000 +0000
@@ -4,22 +4,30 @@
systemctl_stop() {
unit="$1"
- if systemctl is-active -q "$unit"; then
- echo "Stoping $unit"
+ for i in $(seq 10); do
+ if ! systemctl is-active -q "$unit"; then
+ echo "$unit is stopped."
+ break
+ fi
+ echo "Stoping $unit [attempt $i]"
systemctl stop -q "$unit" || true
- fi
+ sleep .1
+ done
}
if [ "$1" = "purge" ]; then
# undo any bind mount to /snap that resulted from LP:#1668659
+ # (that bug can't happen in trusty -- and doing this would mess up snap.mount.service there)
if grep -q "/snap /snap" /proc/self/mountinfo; then
umount -l /snap || true
fi
- mounts=$(systemctl list-unit-files --full | grep '^snap[-.].*\.mount' | cut -f1 -d ' ')
- services=$(systemctl list-unit-files --full | grep '^snap[-.].*\.service' | cut -f1 -d ' ')
+ units=$(systemctl list-unit-files --full | grep '^snap[-.]' | cut -f1 -d ' ' | grep -vF snap.mount.service || true)
+ mounts=$(echo "$units" | grep '^snap[-.].*\.mount$' || true)
+ services=$(echo "$units" | grep '^snap[-.].*\.service$' || true)
+
for unit in $services $mounts; do
- # ensure its really a snapp mount unit or systemd unit
+ # ensure its really a snap mount unit or systemd unit
if ! grep -q 'What=/var/lib/snapd/snaps/' "/etc/systemd/system/$unit" && ! grep -q 'X-Snappy=yes' "/etc/systemd/system/$unit"; then
echo "Skipping non-snapd systemd unit $unit"
continue
@@ -43,7 +51,8 @@
rm -f "/snap/bin/$snap"
rm -f "/snap/bin/$snap".*
# snap mount dir
- umount -l "/snap/$snap/$rev" 2> /dev/null || true
+ # we pass -d (clean up loopback devices) for trusty compatibility
+ umount -d -l "/snap/$snap/$rev" 2> /dev/null || true
rm -rf "/snap/$snap/$rev"
rm -f "/snap/$snap/current"
# snap data dir
@@ -53,7 +62,7 @@
# opportunistic remove (may fail if there are still revisions left
for d in "/snap/$snap" "/var/snap/$snap"; do
if [ -d "$d" ]; then
- rmdir --ignore-fail-on-non-empty "$d"
+ rmdir --ignore-fail-on-non-empty "$d" || true
fi
done
fi
@@ -74,12 +83,19 @@
# opportunistic as those might not be actually mounted
for mnt in /run/snapd/ns/*.mnt; do
umount -l "$mnt" || true
+ rm -f "$mnt"
+ done
+ for fstab in /run/snapd/ns/*.fstab; do
+ rm -f "$fstab"
done
umount -l /run/snapd/ns/ || true
echo "Removing extra snap-confine apparmor rules"
rm -f /etc/apparmor.d/snap.core.*.usr.lib.snapd.snap-confine
+ echo "Removing snapd cache"
+ rm -f /var/cache/snapd/*
+
echo "Removing snapd state"
rm -rf /var/lib/snapd
fi
diff -Nru snapd-2.28.5/dirs/dirs.go snapd-2.29.3/dirs/dirs.go
--- snapd-2.28.5/dirs/dirs.go 2017-10-10 16:16:09.000000000 +0000
+++ snapd-2.29.3/dirs/dirs.go 2017-10-23 06:17:27.000000000 +0000
@@ -39,9 +39,11 @@
SnapBlobDir string
SnapDataDir string
SnapDataHomeGlob string
+ SnapDownloadCacheDir string
SnapAppArmorDir string
AppArmorCacheDir string
SnapAppArmorAdditionalDir string
+ SnapAppArmorConfineDir string
SnapSeccompDir string
SnapMountPolicyDir string
SnapUdevRulesDir string
@@ -68,6 +70,11 @@
SnapRepairStateFile string
SnapRepairRunDir string
SnapRepairAssertsDir string
+ SnapRunRepairDir string
+
+ SnapCacheDir string
+ SnapNamesFile string
+ SnapSectionsFile string
SnapBinariesDir string
SnapServicesDir string
@@ -87,6 +94,10 @@
CompletionHelper string
CompletersDir string
CompleteSh string
+
+ SystemFontsDir string
+ SystemLocalFontsDir string
+ SystemFontconfigCacheDir string
)
const (
@@ -128,14 +139,14 @@
// SupportsClassicConfinement returns true if the current directory layout supports classic confinement.
func SupportsClassicConfinement() bool {
- if SnapMountDir == defaultSnapMountDir {
+ smd := filepath.Join(GlobalRootDir, defaultSnapMountDir)
+ if SnapMountDir == smd {
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 {
@@ -167,6 +178,8 @@
SnapAppArmorDir = filepath.Join(rootdir, snappyDir, "apparmor", "profiles")
AppArmorCacheDir = filepath.Join(rootdir, "/var/cache/apparmor")
SnapAppArmorAdditionalDir = filepath.Join(rootdir, snappyDir, "apparmor", "additional")
+ SnapAppArmorConfineDir = filepath.Join(rootdir, snappyDir, "apparmor", "snap-confine.d")
+ SnapDownloadCacheDir = filepath.Join(rootdir, snappyDir, "cache")
SnapSeccompDir = filepath.Join(rootdir, snappyDir, "seccomp", "bpf")
SnapMountPolicyDir = filepath.Join(rootdir, snappyDir, "mount")
SnapMetaDir = filepath.Join(rootdir, snappyDir, "meta")
@@ -186,6 +199,10 @@
SnapStateFile = filepath.Join(rootdir, snappyDir, "state.json")
+ SnapCacheDir = filepath.Join(rootdir, "/var/cache/snapd")
+ SnapNamesFile = filepath.Join(SnapCacheDir, "names")
+ SnapSectionsFile = filepath.Join(SnapCacheDir, "sections")
+
SnapSeedDir = filepath.Join(rootdir, snappyDir, "seed")
SnapDeviceDir = filepath.Join(rootdir, snappyDir, "device")
@@ -193,6 +210,7 @@
SnapRepairStateFile = filepath.Join(SnapRepairDir, "repair.json")
SnapRepairRunDir = filepath.Join(SnapRepairDir, "run")
SnapRepairAssertsDir = filepath.Join(SnapRepairDir, "assertions")
+ SnapRunRepairDir = filepath.Join(SnapRunDir, "repair")
SnapBinariesDir = filepath.Join(SnapMountDir, "bin")
SnapServicesDir = filepath.Join(rootdir, "/etc/systemd/system")
@@ -223,4 +241,8 @@
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")
+
+ SystemFontsDir = filepath.Join(rootdir, "/usr/share/fonts")
+ SystemLocalFontsDir = filepath.Join(rootdir, "/usr/local/share/fonts")
+ SystemFontconfigCacheDir = filepath.Join(rootdir, "/var/cache/fontconfig")
}
diff -Nru snapd-2.28.5/dirs/dirs_test.go snapd-2.29.3/dirs/dirs_test.go
--- snapd-2.28.5/dirs/dirs_test.go 2017-10-10 16:16:09.000000000 +0000
+++ snapd-2.29.3/dirs/dirs_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -58,7 +58,7 @@
dirs.SetRootDir("/")
c.Check(dirs.SupportsClassicConfinement(), Equals, true)
- dirs.SetRootDir("/alt")
+ dirs.SnapMountDir = "/alt"
defer dirs.SetRootDir("/")
c.Check(dirs.SupportsClassicConfinement(), Equals, false)
}
diff -Nru snapd-2.28.5/errtracker/errtracker.go snapd-2.29.3/errtracker/errtracker.go
--- snapd-2.28.5/errtracker/errtracker.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/errtracker/errtracker.go 2017-10-23 06:17:27.000000000 +0000
@@ -83,7 +83,7 @@
}
func snapConfineProfileDigest(suffix string) string {
- profileText, err := ioutil.ReadFile(snapConfineProfile + suffix)
+ profileText, err := ioutil.ReadFile(filepath.Join(dirs.GlobalRootDir, snapConfineProfile+suffix))
if err != nil {
return ""
}
@@ -98,10 +98,36 @@
return "no"
}
+// Report reports an error with the given snap to the error tracker
func Report(snap, errMsg, dupSig string, extra map[string]string) (string, error) {
+ if extra == nil {
+ extra = make(map[string]string)
+ }
+ extra["ProblemType"] = "Snap"
+ extra["Snap"] = snap
+
+ return report(errMsg, dupSig, extra)
+}
+
+// ReportRepair reports an error with the given repair assertion script
+// to the error tracker
+func ReportRepair(repair, errMsg, dupSig string, extra map[string]string) (string, error) {
+ if extra == nil {
+ extra = make(map[string]string)
+ }
+ extra["ProblemType"] = "Repair"
+ extra["Repair"] = repair
+
+ return report(errMsg, dupSig, extra)
+}
+
+func report(errMsg, dupSig string, extra map[string]string) (string, error) {
if CrashDbURLBase == "" {
return "", nil
}
+ if extra == nil || extra["ProblemType"] == "" {
+ return "", fmt.Errorf(`key "ProblemType" not set in %v`, extra)
+ }
machineID, err := readMachineID()
if err != nil {
@@ -130,14 +156,12 @@
}
report := map[string]string{
- "ProblemType": "Snap",
"Architecture": arch.UbuntuArchitecture(),
"SnapdVersion": SnapdVersion,
"DistroRelease": distroRelease(),
"HostSnapdBuildID": hostBuildID,
"CoreSnapdBuildID": coreBuildID,
"Date": timeNow().Format(time.ANSIC),
- "Snap": snap,
"KernelVersion": release.KernelVersion(),
"ErrorMessage": errMsg,
"DuplicateSignature": dupSig,
diff -Nru snapd-2.28.5/errtracker/errtracker_test.go snapd-2.29.3/errtracker/errtracker_test.go
--- snapd-2.28.5/errtracker/errtracker_test.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/errtracker/errtracker_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -36,6 +36,7 @@
. "gopkg.in/check.v1"
"github.com/snapcore/snapd/arch"
+ "github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/errtracker"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/release"
@@ -48,7 +49,11 @@
type ErrtrackerTestSuite struct {
testutil.BaseTest
- snapConfineProfile string
+ tmpdir string
+
+ hostBuildID string
+ coreBuildID string
+ distroRelease string
}
var _ = Suite(&ErrtrackerTestSuite{})
@@ -59,8 +64,10 @@
func (s *ErrtrackerTestSuite) SetUpTest(c *C) {
s.BaseTest.SetUpTest(c)
- d := c.MkDir()
- p := filepath.Join(d, "machine-id")
+ s.tmpdir = c.MkDir()
+ dirs.SetRootDir(s.tmpdir)
+
+ p := filepath.Join(s.tmpdir, "machine-id")
err := ioutil.WriteFile(p, []byte("bbb1a6a5bcdb418380056a2d759c3f7c"), 0644)
c.Assert(err, IsNil)
s.AddCleanup(errtracker.MockMachineIDPaths([]string{p}))
@@ -68,26 +75,32 @@
s.AddCleanup(errtracker.MockCoreSnapd(falsePath))
s.AddCleanup(errtracker.MockReExec(true))
- p = filepath.Join(d, "usr.lib.snapd.snap-confine")
- err = ioutil.WriteFile(p, []byte("# fake profile of snap-confine"), 0644)
+ s.hostBuildID, err = osutil.ReadBuildID(truePath)
+ c.Assert(err, IsNil)
+ s.coreBuildID, err = osutil.ReadBuildID(falsePath)
c.Assert(err, IsNil)
- s.AddCleanup(errtracker.MockSnapConfineApparmorProfile(p))
- s.snapConfineProfile = p
+ if release.ReleaseInfo.ID == "ubuntu" {
+ s.distroRelease = fmt.Sprintf("%s %s", strings.Title(release.ReleaseInfo.ID), release.ReleaseInfo.VersionID)
+ } else {
+ s.distroRelease = fmt.Sprintf("%s %s", release.ReleaseInfo.ID, release.ReleaseInfo.VersionID)
+ }
}
func (s *ErrtrackerTestSuite) TestReport(c *C) {
n := 0
identifier := ""
- hostBuildID, err := osutil.ReadBuildID(truePath)
+
+ snapConfineProfile := filepath.Join(s.tmpdir, "/etc/apparmor.d/usr.lib.snapd.snap-confine")
+ err := os.MkdirAll(filepath.Dir(snapConfineProfile), 0755)
c.Assert(err, IsNil)
- coreBuildID, err := osutil.ReadBuildID(falsePath)
+ err = ioutil.WriteFile(snapConfineProfile, []byte("# fake profile of snap-confine"), 0644)
c.Assert(err, IsNil)
- err = ioutil.WriteFile(s.snapConfineProfile+".dpkg-new", []byte{0}, 0644)
+ err = ioutil.WriteFile(snapConfineProfile+".dpkg-new", []byte{0}, 0644)
c.Assert(err, IsNil)
- err = ioutil.WriteFile(s.snapConfineProfile+".real", []byte{0}, 0644)
+ err = ioutil.WriteFile(snapConfineProfile+".real", []byte{0}, 0644)
c.Assert(err, IsNil)
- err = ioutil.WriteFile(s.snapConfineProfile+".real.dpkg-new", []byte{0}, 0644)
+ err = ioutil.WriteFile(snapConfineProfile+".real.dpkg-new", []byte{0}, 0644)
c.Assert(err, IsNil)
prev := errtracker.SnapdVersion
@@ -106,32 +119,26 @@
var data map[string]string
err = bson.Unmarshal(b, &data)
c.Assert(err, IsNil)
- var distroRelease string
- if release.ReleaseInfo.ID == "ubuntu" {
- distroRelease = fmt.Sprintf("%s %s", strings.Title(release.ReleaseInfo.ID), release.ReleaseInfo.VersionID)
- } else {
- distroRelease = fmt.Sprintf("%s %s", release.ReleaseInfo.ID, release.ReleaseInfo.VersionID)
- }
c.Check(data, DeepEquals, map[string]string{
- "ProblemType": "Snap",
- "DistroRelease": distroRelease,
- "HostSnapdBuildID": hostBuildID,
- "CoreSnapdBuildID": coreBuildID,
+ "DistroRelease": s.distroRelease,
+ "HostSnapdBuildID": s.hostBuildID,
+ "CoreSnapdBuildID": s.coreBuildID,
"SnapdVersion": "some-snapd-version",
- "Snap": "some-snap",
"Date": "Fri Feb 17 09:51:00 2017",
- "Channel": "beta",
"KernelVersion": release.KernelVersion(),
"ErrorMessage": "failed to do stuff",
"DuplicateSignature": "[failed to do stuff]",
"Architecture": arch.UbuntuArchitecture(),
+ "DidSnapdReExec": "yes",
+
+ "ProblemType": "Snap",
+ "Snap": "some-snap",
+ "Channel": "beta",
"MD5SumSnapConfineAppArmorProfile": "7a7aa5f21063170c1991b84eb8d86de1",
"MD5SumSnapConfineAppArmorProfileDpkgNew": "93b885adfe0da089cdf634904fd59f71",
"MD5SumSnapConfineAppArmorProfileReal": "93b885adfe0da089cdf634904fd59f71",
"MD5SumSnapConfineAppArmorProfileRealDpkgNew": "93b885adfe0da089cdf634904fd59f71",
-
- "DidSnapdReExec": "yes",
})
fmt.Fprintf(w, "c14388aa-f78d-11e6-8df0-fa163eaf9b83 OOPSID")
case 1:
@@ -222,3 +229,59 @@
c.Check(n, Equals, 1)
c.Check(identifiers, DeepEquals, []string{fmt.Sprintf("/%x", sha512.Sum512(machineID))})
}
+
+func (s *ErrtrackerTestSuite) TestReportRepair(c *C) {
+ n := 0
+ prev := errtracker.SnapdVersion
+ defer func() { errtracker.SnapdVersion = prev }()
+ errtracker.SnapdVersion = "some-snapd-version"
+
+ handler := func(w http.ResponseWriter, r *http.Request) {
+ switch n {
+ case 0:
+ c.Check(r.Method, Equals, "POST")
+ c.Check(r.URL.Path, Matches, "/[a-z0-9]+")
+ b, err := ioutil.ReadAll(r.Body)
+ c.Assert(err, IsNil)
+
+ var data map[string]string
+ err = bson.Unmarshal(b, &data)
+ c.Assert(err, IsNil)
+ c.Check(data, DeepEquals, map[string]string{
+ "DistroRelease": s.distroRelease,
+ "HostSnapdBuildID": s.hostBuildID,
+ "CoreSnapdBuildID": s.coreBuildID,
+ "SnapdVersion": "some-snapd-version",
+ "Date": "Fri Feb 17 09:51:00 2017",
+ "KernelVersion": release.KernelVersion(),
+ "Architecture": arch.UbuntuArchitecture(),
+ "DidSnapdReExec": "yes",
+
+ "ProblemType": "Repair",
+ "Repair": `"repair (1; brand-id:canonical)"`,
+ "ErrorMessage": "failure in script",
+ "DuplicateSignature": "[dupSig]",
+ "BrandID": "canonical",
+ })
+ fmt.Fprintf(w, "c14388aa-f78d-11e6-8df0-fa163eaf9b83 OOPSID")
+ default:
+ c.Fatalf("expected one request, got %d", n+1)
+ }
+
+ n++
+ }
+
+ server := httptest.NewServer(http.HandlerFunc(handler))
+ defer server.Close()
+ restorer := errtracker.MockCrashDbURL(server.URL)
+ defer restorer()
+ restorer = errtracker.MockTimeNow(func() time.Time { return time.Date(2017, 2, 17, 9, 51, 0, 0, time.UTC) })
+ defer restorer()
+
+ id, err := errtracker.ReportRepair(`"repair (1; brand-id:canonical)"`, "failure in script", "[dupSig]", map[string]string{
+ "BrandID": "canonical",
+ })
+ c.Check(err, IsNil)
+ c.Check(id, Equals, "c14388aa-f78d-11e6-8df0-fa163eaf9b83 OOPSID")
+ c.Check(n, Equals, 1)
+}
diff -Nru snapd-2.28.5/errtracker/export_test.go snapd-2.29.3/errtracker/export_test.go
--- snapd-2.28.5/errtracker/export_test.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/errtracker/export_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -66,14 +66,6 @@
}
}
-func MockSnapConfineApparmorProfile(path string) (restorer func()) {
- old := snapConfineProfile
- snapConfineProfile = path
- return func() {
- snapConfineProfile = old
- }
-}
-
func MockReExec(didReExec bool) (restorer func()) {
old := osutil.GetenvBool("SNAP_DID_REEXEC")
if didReExec {
diff -Nru snapd-2.28.5/HACKING.md snapd-2.29.3/HACKING.md
--- snapd-2.28.5/HACKING.md 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/HACKING.md 2017-10-23 06:17:27.000000000 +0000
@@ -171,6 +171,11 @@
To debug interaction with the snap store, you can set `SNAP_DEBUG_HTTP`.
It is a bitfield: dump requests: 1, dump responses: 2, dump bodies: 4.
+(make hack: In case you get some security profiles errors when trying to install or refresh a snap,
+maybe you need to replace system installed snap-seccomp with the one aligned to the snapd that
+you are testing. To do this, simply backup /usr/lib/snapd/snap-seccomp and overwrite it with
+the testing one. Don't forget to rollback to the original when finish testing)
+
# Quick intro to hacking on snap-confine
Hey, welcome to the nice, low-level world of snap-confine
diff -Nru snapd-2.28.5/httputil/logger_test.go snapd-2.29.3/httputil/logger_test.go
--- snapd-2.28.5/httputil/logger_test.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/httputil/logger_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -39,21 +39,21 @@
func TestHTTPUtil(t *testing.T) { check.TestingT(t) }
type loggerSuite struct {
- logbuf *bytes.Buffer
+ logbuf *bytes.Buffer
+ restoreLogger func()
}
var _ = check.Suite(&loggerSuite{})
-func (loggerSuite) TearDownTest(c *check.C) {
+func (s *loggerSuite) TearDownTest(c *check.C) {
os.Unsetenv("SNAPD_DEBUG")
+ s.restoreLogger()
}
func (s *loggerSuite) SetUpTest(c *check.C) {
os.Setenv("SNAPD_DEBUG", "true")
s.logbuf = bytes.NewBuffer(nil)
- l, err := logger.New(s.logbuf, logger.DefaultFlags)
- c.Assert(err, check.IsNil)
- logger.SetLogger(l)
+ s.logbuf, s.restoreLogger = logger.MockLogger()
}
func (loggerSuite) TestFlags(c *check.C) {
diff -Nru snapd-2.28.5/i18n/dumb/dumb.go snapd-2.29.3/i18n/dumb/dumb.go
--- snapd-2.28.5/i18n/dumb/dumb.go 2016-12-08 15:14:07.000000000 +0000
+++ snapd-2.29.3/i18n/dumb/dumb.go 1970-01-01 00:00:00.000000000 +0000
@@ -1,24 +0,0 @@
-// -*- 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 i18n // import "github.com/snapcore/snapd/i18n/dumb"
-
-func G(s string) string {
- return s
-}
diff -Nru snapd-2.28.5/image/helpers.go snapd-2.29.3/image/helpers.go
--- snapd-2.28.5/image/helpers.go 2017-08-18 13:48:10.000000000 +0000
+++ snapd-2.29.3/image/helpers.go 2017-10-23 06:17:27.000000000 +0000
@@ -25,7 +25,9 @@
"encoding/json"
"fmt"
"os"
+ "os/signal"
"path/filepath"
+ "syscall"
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/asserts/snapasserts"
@@ -142,11 +144,24 @@
baseName := filepath.Base(snap.MountFile())
targetFn = filepath.Join(targetDir, baseName)
- pb := progress.NewTextProgress()
+ pb := progress.MakeProgressBar()
+ defer pb.Finished()
+
+ // Intercept sigint
+ c := make(chan os.Signal, 3)
+ signal.Notify(c, syscall.SIGINT)
+ go func() {
+ <-c
+ pb.Finished()
+ os.Exit(1)
+ }()
+
if err = sto.Download(context.TODO(), name, targetFn, &snap.DownloadInfo, pb, tsto.user); err != nil {
return "", nil, err
}
+ signal.Reset(syscall.SIGINT)
+
return targetFn, snap, nil
}
@@ -198,18 +213,9 @@
// Find provides the snapsserts.Finder interface for snapasserts.DerviceSideInfo
func (tsto *ToolingStore) Find(at *asserts.AssertionType, headers map[string]string) (asserts.Assertion, error) {
- pk := make([]string, len(at.PrimaryKey))
- for i, k := range at.PrimaryKey {
- pk[i] = headers[k]
- }
- as, err := tsto.sto.Assertion(at, pk, tsto.user)
+ pk, err := asserts.PrimaryKeyFromHeaders(at, headers)
if err != nil {
- // convert store error to something that the asserts would
- // return
- if _, ok := err.(*store.AssertionNotFoundError); ok {
- return nil, asserts.ErrNotFound
- }
return nil, err
}
- return as, nil
+ return tsto.sto.Assertion(at, pk, tsto.user)
}
diff -Nru snapd-2.28.5/image/image.go snapd-2.29.3/image/image.go
--- snapd-2.28.5/image/image.go 2017-08-18 13:48:10.000000000 +0000
+++ snapd-2.29.3/image/image.go 2017-10-23 06:17:27.000000000 +0000
@@ -105,7 +105,7 @@
local[snapName] = info
si, err := snapasserts.DeriveSideInfo(snapName, tsto)
- if err != nil && err != asserts.ErrNotFound {
+ if err != nil && !asserts.IsNotFound(err) {
return nil, err
}
if err == nil {
@@ -244,9 +244,6 @@
if osutil.FileExists(cloudConfig) {
dst := filepath.Join(cloudDir, "cloud.cfg")
err = osutil.CopyFile(cloudConfig, dst, osutil.CopyFlagOverwrite)
- } else {
- dst := filepath.Join(cloudDir, "cloud-init.disabled")
- err = osutil.AtomicWriteFile(dst, nil, 0644, 0)
}
return err
}
diff -Nru snapd-2.28.5/image/image_test.go snapd-2.29.3/image/image_test.go
--- snapd-2.28.5/image/image_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/image/image_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -58,7 +58,7 @@
}
func (s *emptyStore) Assertion(assertType *asserts.AssertionType, primaryKey []string, user *auth.UserState) (asserts.Assertion, error) {
- return nil, &store.AssertionNotFoundError{Ref: &asserts.Ref{Type: assertType, PrimaryKey: primaryKey}}
+ return nil, &asserts.NotFoundError{Type: assertType}
}
func Test(t *testing.T) { TestingT(t) }
@@ -564,9 +564,6 @@
c.Assert(err, IsNil)
c.Check(m["snap_core"], Equals, "core_x1.snap")
- // check that cloud-init is setup correctly
- c.Check(osutil.FileExists(filepath.Join(rootdir, "etc/cloud/cloud-init.disabled")), Equals, true)
-
c.Check(s.stderr.String(), Equals, "WARNING: \"core\", \"required-snap1\" were installed from local snaps disconnected from a store and cannot be refreshed subsequently!\n")
}
@@ -675,7 +672,7 @@
dirs.SetRootDir(targetDir)
err := image.InstallCloudConfig(emptyGadgetDir)
c.Assert(err, IsNil)
- c.Check(osutil.FileExists(filepath.Join(targetDir, "etc/cloud/cloud-init.disabled")), Equals, true)
+ c.Check(osutil.FileExists(filepath.Join(targetDir, "etc/cloud")), Equals, true)
}
func (s *imageSuite) TestInstallCloudConfigWithCloudConfig(c *C) {
@@ -827,8 +824,5 @@
c.Assert(err, IsNil)
c.Check(m["snap_core"], Equals, "core_3.snap")
- // check that cloud-init is setup correctly
- c.Check(osutil.FileExists(filepath.Join(rootdir, "etc/cloud/cloud-init.disabled")), Equals, true)
-
c.Check(s.stderr.String(), Equals, "")
}
diff -Nru snapd-2.28.5/interfaces/apparmor/backend.go snapd-2.29.3/interfaces/apparmor/backend.go
--- snapd-2.28.5/interfaces/apparmor/backend.go 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/interfaces/apparmor/backend.go 2017-10-27 12:31:06.000000000 +0000
@@ -118,6 +118,13 @@
return err
}
+ // create for policy extensions for snap-confine. This is required for the
+ // profiles to compile but distribution package may not yet contain this
+ // directory.
+ if err := os.MkdirAll(dirs.SnapAppArmorConfineDir, 0755); err != nil {
+ return err
+ }
+
// not using apparmor.LoadProfile() because it uses a different cachedir
if output, err := exec.Command("apparmor_parser", "--replace", "--write-cache", apparmorProfilePath, "--cache-loc", dirs.SystemApparmorCacheDir).CombinedOutput(); err != nil {
return fmt.Errorf("cannot replace snap-confine apparmor profile: %v", osutil.OutputErr(output, err))
@@ -145,6 +152,21 @@
logger.Noticef("cannot create host snap-confine apparmor configuration: %s", err)
}
}
+ // core on core devices is also special, the apparmor cache gets
+ // confused too easy, especially at rollbacks, so we delete the cache.
+ // See LP:#1460152 and
+ // https://forum.snapcraft.io/t/core-snap-revert-issues-on-core-devices/
+ if snapName == "core" && !release.OnClassic {
+ if li, err := filepath.Glob(filepath.Join(dirs.SystemApparmorCacheDir, "*")); err == nil {
+ for _, p := range li {
+ if st, err := os.Stat(p); err == nil && st.Mode().IsRegular() {
+ if err := os.Remove(p); err != nil {
+ logger.Noticef("cannot remove %q: %s", p, err)
+ }
+ }
+ }
+ }
+ }
// Get the files that this snap should have
content, err := b.deriveContent(spec.(*Specification), snapInfo, opts)
@@ -216,7 +238,11 @@
func addContent(securityTag string, snapInfo *snap.Info, opts interfaces.ConfinementOptions, snippetForTag string, content map[string]*osutil.FileState) {
var policy string
- if opts.Classic && !opts.JailMode {
+ // When partial AppArmor is detected, use the classic template for now. We could
+ // use devmode, but that could generate confusing log entries for users running
+ // snaps on systems with partial AppArmor support.
+ level := release.AppArmorLevel()
+ if level == release.PartialAppArmor || (opts.Classic && !opts.JailMode) {
policy = classicTemplate
} else {
policy = defaultTemplate
@@ -232,13 +258,12 @@
return fmt.Sprintf("profile \"%s\"", securityTag)
case "###SNIPPETS###":
var tagSnippets string
-
if opts.Classic && opts.JailMode {
// Add a special internal snippet for snaps using classic confinement
// and jailmode together. This snippet provides access to the core snap
// so that the dynamic linker and shared libraries can be used.
tagSnippets = classicJailmodeSnippet + "\n" + snippetForTag
- } else if opts.Classic && !opts.JailMode {
+ } else if level == release.PartialAppArmor || (opts.Classic && !opts.JailMode) {
// When classic confinement (without jailmode) is in effect we
// are ignoring all apparmor snippets as they may conflict with
// the super-broad template we are starting with.
diff -Nru snapd-2.28.5/interfaces/apparmor/backend_test.go snapd-2.29.3/interfaces/apparmor/backend_test.go
--- snapd-2.28.5/interfaces/apparmor/backend_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/apparmor/backend_test.go 2017-10-27 12:31:06.000000000 +0000
@@ -293,6 +293,9 @@
}
func (s *backendSuite) TestRealDefaultTemplateIsNormallyUsed(c *C) {
+ restore := release.MockAppArmorLevel(release.FullAppArmor)
+ defer restore()
+
snapInfo := snaptest.MockInfo(c, ifacetest.SambaYamlV1, nil)
// NOTE: we don't call apparmor.MockTemplate()
err := s.Backend.Setup(snapInfo, interfaces.ConfinementOptions{}, s.Repo)
@@ -366,6 +369,9 @@
}}
func (s *backendSuite) TestCombineSnippets(c *C) {
+ restore := release.MockAppArmorLevel(release.FullAppArmor)
+ defer restore()
+
// NOTE: replace the real template with a shorter variant
restoreTemplate := apparmor.MockTemplate("\n" +
"###VAR###\n" +
@@ -485,4 +491,36 @@
{"apparmor_parser", "--replace", "--write-cache", newAA[0], "--cache-loc", dirs.SystemApparmorCacheDir},
})
+ // snap-confine.d was created
+ _, err = os.Stat(dirs.SnapAppArmorConfineDir)
+ c.Check(err, IsNil)
+
+}
+
+func (s *backendSuite) TestCoreOnCoreCleansApparmorCache(c *C) {
+ restorer := release.MockOnClassic(false)
+ defer restorer()
+
+ err := os.MkdirAll(dirs.SystemApparmorCacheDir, 0755)
+ c.Assert(err, IsNil)
+ // the canary file in the cache will be removed
+ canaryPath := filepath.Join(dirs.SystemApparmorCacheDir, "meep")
+ err = ioutil.WriteFile(canaryPath, nil, 0644)
+ c.Assert(err, IsNil)
+ // but non-regular entries in the cache dir are kept
+ dirsAreKept := filepath.Join(dirs.SystemApparmorCacheDir, "dir")
+ err = os.MkdirAll(dirsAreKept, 0755)
+ c.Assert(err, IsNil)
+ symlinksAreKept := filepath.Join(dirs.SystemApparmorCacheDir, "symlink")
+ err = os.Symlink("some-sylink-target", symlinksAreKept)
+ c.Assert(err, IsNil)
+
+ // install the new core snap on classic triggers a new snap-confine
+ // for this snap-confine on core
+ s.InstallSnap(c, interfaces.ConfinementOptions{}, coreYaml, 111)
+
+ l, err := filepath.Glob(filepath.Join(dirs.SystemApparmorCacheDir, "*"))
+ c.Assert(err, IsNil)
+ // canary is gone, extra stuff is kept
+ c.Check(l, DeepEquals, []string{dirsAreKept, symlinksAreKept})
}
diff -Nru snapd-2.28.5/interfaces/apparmor/template.go snapd-2.29.3/interfaces/apparmor/template.go
--- snapd-2.28.5/interfaces/apparmor/template.go 2017-09-15 15:05:07.000000000 +0000
+++ snapd-2.29.3/interfaces/apparmor/template.go 2017-10-30 13:27:31.000000000 +0000
@@ -43,6 +43,12 @@
# for series 16 and cross-distro
/etc/ld.so.preload r,
+ # The base abstraction doesn't yet have this
+ /lib/terminfo/** rk,
+ /usr/share/terminfo/** k,
+ /usr/share/zoneinfo/** k,
+ owner @{PROC}/@{pid}/maps k,
+
# for python apps/services
#include
/usr/bin/python{,2,2.[0-9]*,3,3.[0-9]*} ixr,
@@ -226,8 +232,10 @@
/usr/share/distro-info/*.csv r,
# Allow reading /etc/os-release. On Ubuntu 16.04+ it is a symlink to /usr/lib
- # but on 14.04 it is an actual file so it doens't fall under other rules.
- /etc/os-release r,
+ # which is allowed by the base abstraction, but on 14.04 it is an actual file
+ # so need to add it here. Also allow read locks on the file.
+ /etc/os-release rk,
+ /usr/lib/os-release k,
# systemd native journal API (see sd_journal_print(4)). This should be in
# AppArmor's base abstraction, but until it is, include here.
@@ -278,6 +286,7 @@
/etc/{,writable/}localtime r,
/etc/{,writable/}mailname r,
/etc/{,writable/}timezone r,
+ owner @{PROC}/@{pid}/cgroup r,
@{PROC}/@{pid}/io r,
owner @{PROC}/@{pid}/limits r,
owner @{PROC}/@{pid}/loginuid r,
@@ -292,6 +301,7 @@
@{PROC}/@{pid}/task/[0-9]*/status r,
@{PROC}/sys/kernel/hostname r,
@{PROC}/sys/kernel/osrelease r,
+ @{PROC}/sys/kernel/ostype r,
@{PROC}/sys/kernel/yama/ptrace_scope r,
@{PROC}/sys/kernel/shmmax r,
@{PROC}/sys/fs/file-max r,
@@ -299,6 +309,8 @@
@{PROC}/sys/kernel/random/uuid r,
@{PROC}/sys/kernel/random/boot_id r,
/sys/devices/virtual/tty/{console,tty*}/active r,
+ /sys/fs/cgroup/memory/memory.limit_in_bytes r,
+ /sys/fs/cgroup/memory/snap.@{SNAP_NAME}{,.*}/memory.limit_in_bytes r,
/{,usr/}lib/ r,
# Reads of oom_adj and oom_score_adj are safe
@@ -409,6 +421,19 @@
capability sys_chroot,
/{,usr/}sbin/chroot ixr,
+ # Lttng tracing is very noisy and should not be allowed by confined apps. Can
+ # safely deny for the normal case (LP: #1260491). If/when an lttng-trace
+ # interface is needed, we can rework this.
+ deny /{dev,run,var/run}/shm/lttng-ust-* rw,
+
+ # Allow read-access on /home/ for navigating to other parts of the
+ # filesystem. While this allows enumerating users, this is already allowed
+ # via /etc/passwd and getent.
+ @{HOMEDIRS}/ r,
+
+ # Allow read-access to / for navigating to other parts of the filesystem.
+ / r,
+
###SNIPPETS###
}
`
diff -Nru snapd-2.28.5/interfaces/backends/backends.go snapd-2.29.3/interfaces/backends/backends.go
--- snapd-2.28.5/interfaces/backends/backends.go 2016-10-27 13:22:15.000000000 +0000
+++ snapd-2.29.3/interfaces/backends/backends.go 2017-10-23 06:17:27.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,8 @@
package backends
import (
+ "fmt"
+
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/interfaces/apparmor"
"github.com/snapcore/snapd/interfaces/dbus"
@@ -31,18 +33,40 @@
"github.com/snapcore/snapd/release"
)
-// append when a new security backend is added
-var All = []interfaces.SecurityBackend{
- &systemd.Backend{},
- &seccomp.Backend{},
- &dbus.Backend{},
- &udev.Backend{},
- &mount.Backend{},
- &kmod.Backend{},
-}
+var All []interfaces.SecurityBackend = backends()
+
+func backends() []interfaces.SecurityBackend {
+ all := []interfaces.SecurityBackend{
+ &systemd.Backend{},
+ &seccomp.Backend{},
+ &dbus.Backend{},
+ &udev.Backend{},
+ &mount.Backend{},
+ &kmod.Backend{},
+ }
+
+ // This should be logger.Noticef but due to ordering of initialization
+ // calls, the logger is not ready at this point yet and the message goes
+ // nowhere. Per advice from other snapd developers, we just print it
+ // directly.
+ //
+ // TODO: on this should become a user-visible message via the user-warning
+ // framework, so that users are aware that we have non-strict confinement.
+ // By printing this directly we ensure it will end up the journal for the
+ // snapd.service. This aspect should be retained even after the switch to
+ // user-warning.
+ fmt.Printf("AppArmor status: %s\n", release.AppArmorSummary())
-func init() {
- if !release.ReleaseInfo.ForceDevMode() {
- All = append(All, &apparmor.Backend{})
+ // Enable apparmor backend if there is any level of apparmor support,
+ // including partial feature set. This will allow snap-confine to always
+ // link to apparmor and check if it is enabled on boot, knowing that there
+ // is always *some* profile to apply to each snap process.
+ //
+ // When some features are missing the backend will generate more permissive
+ // profiles that keep applications operational, in forced-devmode.
+ switch release.AppArmorLevel() {
+ case release.FullAppArmor, release.PartialAppArmor:
+ all = append(all, &apparmor.Backend{})
}
+ return all
}
diff -Nru snapd-2.28.5/interfaces/backends/backends_test.go snapd-2.29.3/interfaces/backends/backends_test.go
--- snapd-2.28.5/interfaces/backends/backends_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/interfaces/backends/backends_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -0,0 +1,55 @@
+/*
+ * 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 backends_test
+
+import (
+ "testing"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/interfaces/backends"
+ "github.com/snapcore/snapd/release"
+ "github.com/snapcore/snapd/testutil"
+)
+
+func Test(t *testing.T) {
+ TestingT(t)
+}
+
+type backendsSuite struct{}
+
+var _ = Suite(&backendsSuite{})
+
+func (s *backendsSuite) TestIsAppArmorEnabled(c *C) {
+ for _, level := range []release.AppArmorLevelType{release.NoAppArmor, release.PartialAppArmor, release.FullAppArmor} {
+ restore := release.MockAppArmorLevel(level)
+ defer restore()
+
+ all := backends.Backends()
+ names := make([]string, len(all))
+ for i, backend := range all {
+ names[i] = string(backend.Name())
+ }
+
+ if level == release.NoAppArmor {
+ c.Assert(names, Not(testutil.Contains), "apparmor")
+ } else {
+ c.Assert(names, testutil.Contains, "apparmor")
+ }
+ }
+}
diff -Nru snapd-2.28.5/interfaces/backends/export_test.go snapd-2.29.3/interfaces/backends/export_test.go
--- snapd-2.28.5/interfaces/backends/export_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/interfaces/backends/export_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -0,0 +1,24 @@
+// -*- 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 backends
+
+var (
+ Backends = backends
+)
diff -Nru snapd-2.28.5/interfaces/builtin/all.go snapd-2.29.3/interfaces/builtin/all.go
--- snapd-2.28.5/interfaces/builtin/all.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/all.go 2017-10-23 06:17:27.000000000 +0000
@@ -24,8 +24,13 @@
"sort"
"github.com/snapcore/snapd/interfaces"
+ "github.com/snapcore/snapd/snap"
)
+func init() {
+ snap.SanitizePlugsSlots = SanitizePlugsSlots
+}
+
var (
allInterfaces map[string]interfaces.Interface
)
@@ -51,6 +56,44 @@
allInterfaces[iface.Name()] = iface
}
+func SanitizePlugsSlots(snapInfo *snap.Info) {
+ for plugName, plugInfo := range snapInfo.Plugs {
+ iface, ok := allInterfaces[plugInfo.Interface]
+ if !ok {
+ snapInfo.BadInterfaces[plugName] = fmt.Sprintf("unknown interface %q", plugInfo.Interface)
+ continue
+ }
+ // Reject plug with invalid name
+ if err := interfaces.ValidateName(plugName); err != nil {
+ snapInfo.BadInterfaces[plugName] = err.Error()
+ continue
+ }
+ plug := &interfaces.Plug{PlugInfo: plugInfo}
+ if err := plug.Sanitize(iface); err != nil {
+ snapInfo.BadInterfaces[plugName] = err.Error()
+ continue
+ }
+ }
+
+ for slotName, slotInfo := range snapInfo.Slots {
+ iface, ok := allInterfaces[slotInfo.Interface]
+ if !ok {
+ snapInfo.BadInterfaces[slotName] = fmt.Sprintf("unknown interface %q", slotInfo.Interface)
+ continue
+ }
+ // Reject slot with invalid name
+ if err := interfaces.ValidateName(slotName); err != nil {
+ snapInfo.BadInterfaces[slotName] = err.Error()
+ continue
+ }
+ slot := &interfaces.Slot{SlotInfo: slotInfo}
+ if err := slot.Sanitize(iface); err != nil {
+ snapInfo.BadInterfaces[slotName] = err.Error()
+ continue
+ }
+ }
+}
+
type byIfaceName []interfaces.Interface
func (c byIfaceName) Len() int { return len(c) }
diff -Nru snapd-2.28.5/interfaces/builtin/all_test.go snapd-2.29.3/interfaces/builtin/all_test.go
--- snapd-2.28.5/interfaces/builtin/all_test.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/all_test.go 2017-11-03 05:49:30.000000000 +0000
@@ -20,7 +20,9 @@
package builtin_test
import (
+ "fmt"
"reflect"
+ "strings"
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/interfaces/apparmor"
@@ -32,6 +34,8 @@
"github.com/snapcore/snapd/interfaces/seccomp"
"github.com/snapcore/snapd/interfaces/systemd"
"github.com/snapcore/snapd/interfaces/udev"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/snaptest"
. "gopkg.in/check.v1"
)
@@ -307,3 +311,117 @@
// Duplicates are detected.
c.Assert(func() { builtin.RegisterIface(iface) }, PanicMatches, `cannot register duplicate interface "foo"`)
}
+
+const testConsumerInvalidSlotNameYaml = `
+name: consumer
+slots:
+ ttyS5:
+ interface: iface
+apps:
+ app:
+ slots: [iface]
+`
+
+const testConsumerInvalidPlugNameYaml = `
+name: consumer
+plugs:
+ ttyS3:
+ interface: iface
+apps:
+ app:
+ plugs: [iface]
+`
+
+func (s *AllSuite) TestSanitizeErrorsOnInvalidSlotNames(c *C) {
+ restore := builtin.MockInterfaces(map[string]interfaces.Interface{
+ "iface": &ifacetest.TestInterface{InterfaceName: "iface"},
+ })
+ defer restore()
+
+ snapInfo := snaptest.MockInfo(c, testConsumerInvalidSlotNameYaml, nil)
+ snap.SanitizePlugsSlots(snapInfo)
+ c.Assert(snapInfo.BadInterfaces, HasLen, 1)
+ c.Check(snap.BadInterfacesSummary(snapInfo), Matches, `snap "consumer" has bad plugs or slots: ttyS5 \(invalid interface name: "ttyS5"\)`)
+}
+
+func (s *AllSuite) TestSanitizeErrorsOnInvalidPlugNames(c *C) {
+ restore := builtin.MockInterfaces(map[string]interfaces.Interface{
+ "iface": &ifacetest.TestInterface{InterfaceName: "iface"},
+ })
+ defer restore()
+
+ snapInfo := snaptest.MockInfo(c, testConsumerInvalidPlugNameYaml, nil)
+ snap.SanitizePlugsSlots(snapInfo)
+ c.Assert(snapInfo.BadInterfaces, HasLen, 1)
+ c.Check(snap.BadInterfacesSummary(snapInfo), Matches, `snap "consumer" has bad plugs or slots: ttyS3 \(invalid interface name: "ttyS3"\)`)
+}
+
+func (s *AllSuite) TestUnexpectedSpecSignatures(c *C) {
+ type funcSig struct {
+ name string
+ in []string
+ out []string
+ }
+ var sigs []funcSig
+
+ // All the valid signatures from all the specification definers from all the backends.
+ for _, backend := range []string{"AppArmor", "SecComp", "UDev", "DBus", "Systemd", "KMod"} {
+ backendLower := strings.ToLower(backend)
+ sigs = append(sigs, []funcSig{{
+ name: fmt.Sprintf("%sPermanentPlug", backend),
+ in: []string{
+ fmt.Sprintf("*%s.Specification", backendLower),
+ "*interfaces.Plug",
+ },
+ out: []string{"error"},
+ }, {
+ name: fmt.Sprintf("%sPermanentSlot", backend),
+ in: []string{
+ fmt.Sprintf("*%s.Specification", backendLower),
+ "*interfaces.Slot",
+ },
+ out: []string{"error"},
+ }, {
+ name: fmt.Sprintf("%sConnectedPlug", backend),
+ in: []string{
+ fmt.Sprintf("*%s.Specification", backendLower),
+ "*interfaces.Plug",
+ "map[string]interface {}",
+ "*interfaces.Slot",
+ "map[string]interface {}",
+ },
+ out: []string{"error"},
+ }, {
+ name: fmt.Sprintf("%sConnectedSlot", backend),
+ in: []string{
+ fmt.Sprintf("*%s.Specification", backendLower),
+ "*interfaces.Plug",
+ "map[string]interface {}",
+ "*interfaces.Slot",
+ "map[string]interface {}",
+ },
+ out: []string{"error"},
+ }}...)
+ }
+ for _, iface := range builtin.Interfaces() {
+ ifaceVal := reflect.ValueOf(iface)
+ ifaceType := ifaceVal.Type()
+ for _, sig := range sigs {
+ meth, ok := ifaceType.MethodByName(sig.name)
+ if !ok {
+ // all specificiation methods are optional.
+ continue
+ }
+ methType := meth.Type
+ // Check that the signature matches our expectation. The -1 and +1 below is for the receiver type.
+ c.Assert(methType.NumIn()-1, Equals, len(sig.in), Commentf("expected %s's %s method to take %d arguments", ifaceType, meth.Name, len(sig.in)))
+ for i, expected := range sig.in {
+ c.Assert(methType.In(i+1).String(), Equals, expected, Commentf("expected %s's %s method %dth argument type to be different", ifaceType, meth.Name, i))
+ }
+ c.Assert(methType.NumOut(), Equals, len(sig.out), Commentf("expected %s's %s method to return %d values", ifaceType, meth.Name, len(sig.out)))
+ for i, expected := range sig.out {
+ c.Assert(methType.Out(i).String(), Equals, expected, Commentf("expected %s's %s method %dth return value type to be different", ifaceType, meth.Name, i))
+ }
+ }
+ }
+}
diff -Nru snapd-2.28.5/interfaces/builtin/alsa.go snapd-2.29.3/interfaces/builtin/alsa.go
--- snapd-2.28.5/interfaces/builtin/alsa.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/alsa.go 2017-11-09 12:08:52.000000000 +0000
@@ -46,15 +46,15 @@
@{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###"
-`
+var alsaConnectedPlugUDev = []string{
+ `KERNEL=="controlC[0-9]*"`,
+ `KERNEL=="hwC[0-9]*D[0-9]*"`,
+ `KERNEL=="pcmC[0-9]*D[0-9]*[cp]"`,
+ `KERNEL=="midiC[0-9]*D[0-9]*"`,
+ `KERNEL=="timer"`,
+ `KERNEL=="seq"`,
+ `SUBSYSTEM=="sound", KERNEL=="card[0-9]*"`,
+}
func init() {
registerIface(&commonInterface{
diff -Nru snapd-2.28.5/interfaces/builtin/alsa_test.go snapd-2.29.3/interfaces/builtin/alsa_test.go
--- snapd-2.28.5/interfaces/builtin/alsa_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/alsa_test.go 2017-11-09 12:08:52.000000000 +0000
@@ -86,8 +86,8 @@
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"`)
+ c.Assert(spec.Snippets(), HasLen, 7)
+ c.Assert(spec.Snippets(), testutil.Contains, `KERNEL=="pcmC[0-9]*D[0-9]*[cp]", TAG+="snap_consumer_app"`)
}
func (s *AlsaInterfaceSuite) TestStaticInfo(c *C) {
diff -Nru snapd-2.28.5/interfaces/builtin/bluetooth_control.go snapd-2.29.3/interfaces/builtin/bluetooth_control.go
--- snapd-2.28.5/interfaces/builtin/bluetooth_control.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/bluetooth_control.go 2017-11-09 12:08:52.000000000 +0000
@@ -56,7 +56,7 @@
bind
`
-const bluetoothControlConnectedPlugUDev = `SUBSYSTEM=="bluetooth", TAG+="###CONNECTED_SECURITY_TAGS###"`
+var bluetoothControlConnectedPlugUDev = []string{`SUBSYSTEM=="bluetooth"`}
func init() {
registerIface(&commonInterface{
diff -Nru snapd-2.28.5/interfaces/builtin/bluez.go snapd-2.29.3/interfaces/builtin/bluez.go
--- snapd-2.28.5/interfaces/builtin/bluez.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/bluez.go 2017-11-09 12:08:52.000000000 +0000
@@ -91,11 +91,12 @@
bus=system
name="org.bluez.obex",
- # Allow traffic to/from our path and interface with any method for unconfined
- # cliens to talk to our bluez services.
+ # Allow traffic to/from our interface with any method for unconfined clients
+ # to talk to our bluez services. For the org.bluez interface we don't specify
+ # an Object Path since according to the bluez specification these can be
+ # anything (https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc).
dbus (receive, send)
bus=system
- path=/org/bluez{,/**}
interface=org.bluez.*
peer=(label=unconfined),
dbus (receive, send)
@@ -196,8 +197,6 @@
`
-const bluezConnectedPlugUDev = `KERNEL=="rfkill", TAG+="###CONNECTED_SECURITY_TAGS###"`
-
type bluezInterface struct{}
func (iface *bluezInterface) Name() string {
@@ -243,12 +242,7 @@
}
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)
- }
+ spec.TagDevice(`KERNEL=="rfkill"`)
return nil
}
diff -Nru snapd-2.28.5/interfaces/builtin/broadcom_asic_control.go snapd-2.29.3/interfaces/builtin/broadcom_asic_control.go
--- snapd-2.28.5/interfaces/builtin/broadcom_asic_control.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/broadcom_asic_control.go 2017-11-09 12:08:52.000000000 +0000
@@ -49,10 +49,10 @@
/run/udev/data/+pci:[0-9]* r,
`
-const broadcomAsicControlConnectedPlugUDev = `
-SUBSYSTEM=="pci", DRIVER=="linux-kernel-bde", TAG+="###CONNECTED_SECURITY_TAGS###"
-SUBSYSTEM=="net", KERNEL=="bcm[0-9]*", TAG+="###CONNECTED_SECURITY_TAGS###"
-`
+var broadcomAsicControlConnectedPlugUDev = []string{
+ `SUBSYSTEM=="pci", DRIVER=="linux-kernel-bde"`,
+ `SUBSYSTEM=="net", KERNEL=="bcm[0-9]*"`,
+}
// The upstream linux kernel doesn't come with support for the
// necessary kernel modules we need to drive a Broadcom ASIC.
diff -Nru snapd-2.28.5/interfaces/builtin/broadcom_asic_control_test.go snapd-2.29.3/interfaces/builtin/broadcom_asic_control_test.go
--- snapd-2.28.5/interfaces/builtin/broadcom_asic_control_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/broadcom_asic_control_test.go 2017-11-09 12:08:52.000000000 +0000
@@ -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(spec.Snippets(), HasLen, 1)
- c.Assert(spec.Snippets()[0], testutil.Contains, `SUBSYSTEM=="net", KERNEL=="bcm[0-9]*", TAG+="snap_consumer_app"`)
+ c.Assert(spec.Snippets(), HasLen, 2)
+ c.Assert(spec.Snippets(), testutil.Contains, `SUBSYSTEM=="net", KERNEL=="bcm[0-9]*", TAG+="snap_consumer_app"`)
}
func (s *BroadcomAsicControlSuite) TestKModSpec(c *C) {
diff -Nru snapd-2.28.5/interfaces/builtin/browser_support.go snapd-2.29.3/interfaces/builtin/browser_support.go
--- snapd-2.28.5/interfaces/builtin/browser_support.go 2017-09-15 15:05:07.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/browser_support.go 2017-11-09 07:19:00.000000000 +0000
@@ -55,9 +55,15 @@
# Chrome/Chromium should be modified to use snap.$SNAP_NAME.* or the snap
# packaging adjusted to use LD_PRELOAD technique from LP: #1577514
-owner /{dev,run}/shm/{,.}org.chromium.Chromium.* mrw,
+owner /{dev,run}/shm/{,.}org.chromium.* mrw,
owner /{dev,run}/shm/{,.}com.google.Chrome.* mrw,
+# Chrome's Singleton API sometimes causes an ouid/fsuid mismatch denial, so
+# for now, allow non-owner read on the singleton socket (LP: #1731012). See
+# https://forum.snapcraft.io/t/electron-snap-killed-when-using-app-makesingleinstance-api/2667/20
+/run/user/[0-9]*/snap.@{SNAP_NAME}/{,.}org.chromium.*/SS r,
+/run/user/[0-9]*/snap.@{SNAP_NAME}/{,.}com.google.Chrome.*/SS r,
+
# Allow reading platform files
/run/udev/data/+platform:* r,
@@ -66,6 +72,7 @@
# Chromium content api in gnome-shell reads this
/etc/opt/chrome/{,**} r,
+/etc/chromium/{,**} r,
# Chrome/Chromium should be adjusted to not use gconf. It is only used with
# legacy systems that don't have snapd
@@ -73,13 +80,15 @@
bus=session
interface="org.gnome.GConf.Server",
-# 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,
-
# webbrowser-app/webapp-container tries to read this file to determine if it is
# confined or not, so explicitly deny to avoid noise in the logs.
deny @{PROC}/@{pid}/attr/current r,
+
+# This is an information leak but disallowing it leads to developer confusion
+# when using the chromium content api file chooser due to a (harmless) glib
+# warning and the noisy AppArmor denial.
+owner @{PROC}/@{pid}/mounts r,
+owner @{PROC}/@{pid}/mountinfo r,
`
const browserSupportConnectedPlugAppArmorWithoutSandbox = `
@@ -131,6 +140,7 @@
/run/udev/data/c89:[0-9]* r, # /dev/i2c-*
/run/udev/data/c81:[0-9]* r, # video4linux (/dev/video*, etc)
/run/udev/data/c202:[0-9]* r, # /dev/cpu/*/msr
+/run/udev/data/c203:[0-9]* r, # /dev/cuse
/run/udev/data/+acpi:* r,
/run/udev/data/+hwmon:hwmon[0-9]* r,
/run/udev/data/+i2c:* r,
@@ -153,6 +163,7 @@
/run/udev/data/n[0-9]* r,
/run/udev/data/+bluetooth:hci[0-9]* r,
/run/udev/data/+rfkill:rfkill[0-9]* r,
+/run/udev/data/c241:[0-9]* r, # /dev/vhost-vsock
# storage
/run/udev/data/b1:[0-9]* r, # /dev/ram*
diff -Nru snapd-2.28.5/interfaces/builtin/camera.go snapd-2.29.3/interfaces/builtin/camera.go
--- snapd-2.28.5/interfaces/builtin/camera.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/camera.go 2017-11-09 12:08:52.000000000 +0000
@@ -42,7 +42,7 @@
/sys/devices/pci**/usb*/**/video4linux/** r,
`
-const cameraConnectedPlugUDev = `KERNEL=="video[0-9]*", TAG+="###CONNECTED_SECURITY_TAGS###"`
+var cameraConnectedPlugUDev = []string{`KERNEL=="video[0-9]*"`}
func init() {
registerIface(&commonInterface{
diff -Nru snapd-2.28.5/interfaces/builtin/common.go snapd-2.29.3/interfaces/builtin/common.go
--- snapd-2.28.5/interfaces/builtin/common.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/common.go 2017-11-09 12:08:52.000000000 +0000
@@ -21,7 +21,6 @@
import (
"path/filepath"
- "strings"
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/interfaces/apparmor"
@@ -49,7 +48,7 @@
connectedPlugAppArmor string
connectedPlugSecComp string
- connectedPlugUDev string
+ connectedPlugUDev []string
reservedForOS bool
rejectAutoConnectPairs bool
@@ -147,13 +146,8 @@
}
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)
- }
+ for _, rule := range iface.connectedPlugUDev {
+ spec.TagDevice(rule)
}
return nil
}
diff -Nru snapd-2.28.5/interfaces/builtin/common_test.go snapd-2.29.3/interfaces/builtin/common_test.go
--- snapd-2.28.5/interfaces/builtin/common_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/common_test.go 2017-11-09 12:08:52.000000000 +0000
@@ -49,14 +49,14 @@
// common interface can define connected plug udev rules
iface := &commonInterface{
name: "common",
- connectedPlugUDev: `KERNEL="foo", TAG+="###CONNECTED_SECURITY_TAGS###"`,
+ connectedPlugUDev: []string{`KERNEL=="foo"`},
}
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"`,
+ `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"`,
+ `KERNEL=="foo", TAG+="snap_consumer_app-c"`,
})
// connected plug udev rules are optional
diff -Nru snapd-2.28.5/interfaces/builtin/cups_control.go snapd-2.29.3/interfaces/builtin/cups_control.go
--- snapd-2.28.5/interfaces/builtin/cups_control.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/cups_control.go 2017-11-09 07:19:00.000000000 +0000
@@ -34,6 +34,7 @@
# privileged access to configure printing.
#include
+/run/cups/printcap r,
`
func init() {
diff -Nru snapd-2.28.5/interfaces/builtin/desktop.go snapd-2.29.3/interfaces/builtin/desktop.go
--- snapd-2.28.5/interfaces/builtin/desktop.go 2017-09-15 15:05:07.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/desktop.go 2017-10-30 13:27:31.000000000 +0000
@@ -19,6 +19,15 @@
package builtin
+import (
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/interfaces"
+ "github.com/snapcore/snapd/interfaces/apparmor"
+ "github.com/snapcore/snapd/interfaces/mount"
+ "github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/release"
+)
+
const desktopSummary = `allows access to basic graphical desktop resources`
const desktopBaseDeclarationSlots = `
@@ -62,7 +71,10 @@
# subset of freedesktop.org
owner @{HOME}/.local/share/mime/** r,
-owner @{HOME}/.config/user-dirs.dirs r,
+owner @{HOME}/.config/user-dirs.* r,
+
+/etc/xdg/user-dirs.conf r,
+/etc/xdg/user-dirs.defaults r,
# gmenu
dbus (send)
@@ -119,19 +131,61 @@
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,
`
+type desktopInterface struct{}
+
+func (iface *desktopInterface) Name() string {
+ return "desktop"
+}
+
+func (iface *desktopInterface) StaticInfo() interfaces.StaticInfo {
+ return interfaces.StaticInfo{
+ Summary: desktopSummary,
+ ImplicitOnClassic: true,
+ BaseDeclarationSlots: desktopBaseDeclarationSlots,
+ }
+}
+
+func (iface *desktopInterface) SanitizeSlot(slot *interfaces.Slot) error {
+ return sanitizeSlotReservedForOS(iface, slot)
+}
+
+func (iface *desktopInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool {
+ // allow what declarations allowed
+ return true
+}
+
+func (iface *desktopInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error {
+ spec.AddSnippet(desktopConnectedPlugAppArmor)
+ return nil
+}
+
+func (iface *desktopInterface) MountConnectedPlug(spec *mount.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error {
+ if !release.OnClassic {
+ // There is nothing to expose on an all-snaps system
+ return nil
+ }
+
+ fontconfigDirs := []string{
+ dirs.SystemFontsDir,
+ dirs.SystemLocalFontsDir,
+ dirs.SystemFontconfigCacheDir,
+ }
+
+ for _, dir := range fontconfigDirs {
+ if !osutil.IsDirectory(dir) {
+ continue
+ }
+ spec.AddMountEntry(mount.Entry{
+ Name: "/var/lib/snapd/hostfs" + dir,
+ Dir: dirs.StripRootDir(dir),
+ Options: []string{"bind", "ro"},
+ })
+ }
+ return nil
+}
+
func init() {
- registerIface(&commonInterface{
- name: "desktop",
- summary: desktopSummary,
- implicitOnClassic: true,
- baseDeclarationSlots: desktopBaseDeclarationSlots,
- connectedPlugAppArmor: desktopConnectedPlugAppArmor,
- reservedForOS: true,
- })
+ registerIface(&desktopInterface{})
}
diff -Nru snapd-2.28.5/interfaces/builtin/desktop_test.go snapd-2.29.3/interfaces/builtin/desktop_test.go
--- snapd-2.28.5/interfaces/builtin/desktop_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/desktop_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -20,11 +20,17 @@
package builtin_test
import (
+ "os"
+ "path/filepath"
+
. "gopkg.in/check.v1"
+ "github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/interfaces/apparmor"
"github.com/snapcore/snapd/interfaces/builtin"
+ "github.com/snapcore/snapd/interfaces/mount"
+ "github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/testutil"
)
@@ -56,6 +62,10 @@
s.coreSlot = MockSlot(c, desktopCoreYaml, nil, "desktop")
}
+func (s *DesktopInterfaceSuite) TearDownTest(c *C) {
+ dirs.SetRootDir("/")
+}
+
func (s *DesktopInterfaceSuite) TestName(c *C) {
c.Assert(s.iface.Name(), Equals, "desktop")
}
@@ -91,6 +101,45 @@
c.Assert(spec.SecurityTags(), HasLen, 0)
}
+func (s *DesktopInterfaceSuite) TestMountSpec(c *C) {
+ tmpdir := c.MkDir()
+ dirs.SetRootDir(tmpdir)
+ c.Assert(os.MkdirAll(filepath.Join(tmpdir, "/usr/share/fonts"), 0777), IsNil)
+ c.Assert(os.MkdirAll(filepath.Join(tmpdir, "/usr/local/share/fonts"), 0777), IsNil)
+ c.Assert(os.MkdirAll(filepath.Join(tmpdir, "/var/cache/fontconfig"), 0777), IsNil)
+
+ restore := release.MockOnClassic(false)
+ defer restore()
+
+ // On all-snaps systems, no mount entries are added
+ spec := &mount.Specification{}
+ c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.coreSlot, nil), IsNil)
+ c.Check(spec.MountEntries(), HasLen, 0)
+
+ // On classic systems, a number of font related directories
+ // are bind mounted from the host system if they exist.
+ restore = release.MockOnClassic(true)
+ defer restore()
+ spec = &mount.Specification{}
+ c.Assert(spec.AddConnectedPlug(s.iface, s.plug, nil, s.coreSlot, nil), IsNil)
+
+ entries := spec.MountEntries()
+ c.Assert(entries, HasLen, 3)
+
+ const hostfs = "/var/lib/snapd/hostfs"
+ c.Check(entries[0].Name, Equals, hostfs+dirs.SystemFontsDir)
+ c.Check(entries[0].Dir, Equals, "/usr/share/fonts")
+ c.Check(entries[0].Options, DeepEquals, []string{"bind", "ro"})
+
+ c.Check(entries[1].Name, Equals, hostfs+dirs.SystemLocalFontsDir)
+ c.Check(entries[1].Dir, Equals, "/usr/local/share/fonts")
+ c.Check(entries[1].Options, DeepEquals, []string{"bind", "ro"})
+
+ c.Check(entries[2].Name, Equals, hostfs+dirs.SystemFontconfigCacheDir)
+ c.Check(entries[2].Dir, Equals, "/var/cache/fontconfig")
+ c.Check(entries[2].Options, DeepEquals, []string{"bind", "ro"})
+}
+
func (s *DesktopInterfaceSuite) TestStaticInfo(c *C) {
si := interfaces.StaticInfoOf(s.iface)
c.Assert(si.ImplicitOnCore, Equals, false)
diff -Nru snapd-2.28.5/interfaces/builtin/framebuffer.go snapd-2.29.3/interfaces/builtin/framebuffer.go
--- snapd-2.28.5/interfaces/builtin/framebuffer.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/framebuffer.go 2017-11-09 12:08:52.000000000 +0000
@@ -37,7 +37,7 @@
/run/udev/data/c29:[0-9]* r,
`
-const framebufferConnectedPlugUDev = `KERNEL=="fb[0-9]*", TAG+="###CONNECTED_SECURITY_TAGS###"`
+var framebufferConnectedPlugUDev = []string{`KERNEL=="fb[0-9]*"`}
func init() {
registerIface(&commonInterface{
diff -Nru snapd-2.28.5/interfaces/builtin/fuse_support.go snapd-2.29.3/interfaces/builtin/fuse_support.go
--- snapd-2.28.5/interfaces/builtin/fuse_support.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/fuse_support.go 2017-11-09 12:08:52.000000000 +0000
@@ -83,7 +83,7 @@
#/{,usr/}bin/fusermount ixr,
`
-const fuseSupportConnectedPlugUDev = `KERNEL=="fuse", TAG+="###CONNECTED_SECURITY_TAGS###"`
+var fuseSupportConnectedPlugUDev = []string{`KERNEL=="fuse"`}
func init() {
registerIface(&commonInterface{
diff -Nru snapd-2.28.5/interfaces/builtin/hardware_observe.go snapd-2.29.3/interfaces/builtin/hardware_observe.go
--- snapd-2.28.5/interfaces/builtin/hardware_observe.go 2017-09-07 12:33:50.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/hardware_observe.go 2017-10-30 13:27:31.000000000 +0000
@@ -40,6 +40,7 @@
# used by lspci
capability sys_admin,
/etc/modprobe.d/{,*} r,
+/lib/modprobe.d/{,*} r,
# files in /sys pertaining to hardware (eg, 'lspci -A linux-sysfs')
/sys/{block,bus,class,devices,firmware}/{,**} r,
diff -Nru snapd-2.28.5/interfaces/builtin/hardware_random_control.go snapd-2.29.3/interfaces/builtin/hardware_random_control.go
--- snapd-2.28.5/interfaces/builtin/hardware_random_control.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/hardware_random_control.go 2017-11-09 12:08:52.000000000 +0000
@@ -45,7 +45,7 @@
/sys/devices/virtual/misc/hw_random/rng_current w,
`
-const hardwareRandomControlConnectedPlugUDev = `KERNEL=="hwrng", TAG+="###CONNECTED_SECURITY_TAGS###"`
+var hardwareRandomControlConnectedPlugUDev = []string{`KERNEL=="hwrng"`}
func init() {
registerIface(&commonInterface{
diff -Nru snapd-2.28.5/interfaces/builtin/hardware_random_observe.go snapd-2.29.3/interfaces/builtin/hardware_random_observe.go
--- snapd-2.28.5/interfaces/builtin/hardware_random_observe.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/hardware_random_observe.go 2017-11-09 12:08:52.000000000 +0000
@@ -40,7 +40,7 @@
/sys/devices/virtual/misc/hw_random/rng_{available,current} r,
`
-const hardwareRandomObserveConnectedPlugUDev = `KERNEL=="hwrng", TAG+="###CONNECTED_SECURITY_TAGS###"`
+var hardwareRandomObserveConnectedPlugUDev = []string{`KERNEL=="hwrng"`}
func init() {
registerIface(&commonInterface{
diff -Nru snapd-2.28.5/interfaces/builtin/hidraw.go snapd-2.29.3/interfaces/builtin/hidraw.go
--- snapd-2.28.5/interfaces/builtin/hidraw.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/hidraw.go 2017-11-09 12:08:52.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
@@ -141,7 +141,7 @@
return nil
}
- // Path to fixed device node (no udev tagging)
+ // Path to fixed device node
path, pathOk := slot.Attrs["path"].(string)
if !pathOk {
return nil
@@ -153,17 +153,30 @@
}
func (iface *hidrawInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error {
+ hasOnlyPath := true
+ if iface.hasUsbAttrs(slot) {
+ hasOnlyPath = false
+ }
+
usbVendor, vOk := slot.Attrs["usb-vendor"].(int64)
- if !vOk {
+ if !vOk && !hasOnlyPath {
return nil
}
usbProduct, pOk := slot.Attrs["usb-product"].(int64)
- if !pOk {
+ if !pOk && !hasOnlyPath {
return nil
}
- for appName := range plug.Apps {
- tag := udevSnapSecurityName(plug.Snap.Name(), appName)
- spec.AddSnippet(udevUsbDeviceSnippet("hidraw", usbVendor, usbProduct, "TAG", tag))
+
+ path, pathOk := slot.Attrs["path"].(string)
+ if !pathOk && hasOnlyPath {
+ return nil
+ }
+
+ if hasOnlyPath {
+ spec.TagDevice(fmt.Sprintf(`SUBSYSTEM=="hidraw", KERNEL=="%s"`, strings.TrimPrefix(path, "/dev/")))
+ } else {
+ spec.TagDevice(fmt.Sprintf(`IMPORT{builtin}="usb_id"
+SUBSYSTEM=="hidraw", SUBSYSTEMS=="usb", ATTRS{idVendor}=="%04x", ATTRS{idProduct}=="%04x"`, usbVendor, usbProduct))
}
return nil
}
diff -Nru snapd-2.28.5/interfaces/builtin/hidraw_test.go snapd-2.29.3/interfaces/builtin/hidraw_test.go
--- snapd-2.28.5/interfaces/builtin/hidraw_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/hidraw_test.go 2017-11-03 05:48:53.000000000 +0000
@@ -53,6 +53,7 @@
// Consuming Snap
testPlugPort1 *interfaces.Plug
testPlugPort2 *interfaces.Plug
+ testPlugPort3 *interfaces.Plug
}
var _ = Suite(&HidrawInterfaceSuite{
@@ -133,6 +134,8 @@
interface: hidraw
plug-for-device-2:
interface: hidraw
+ plug-for-device-3:
+ interface: hidraw
apps:
app-accessing-1-device:
@@ -141,9 +144,13 @@
app-accessing-2-devices:
command: bar
plugs: [plug-for-device-1, plug-for-device-2]
+ app-accessing-3rd-device:
+ command: baz
+ plugs: [plug-for-device-3]
`, nil)
s.testPlugPort1 = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["plug-for-device-1"]}
s.testPlugPort2 = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["plug-for-device-2"]}
+ s.testPlugPort3 = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["plug-for-device-3"]}
}
func (s *HidrawInterfaceSuite) TestName(c *C) {
@@ -201,24 +208,31 @@
}
func (s *HidrawInterfaceSuite) TestConnectedPlugUDevSnippets(c *C) {
+ // add the plug for the slot with just path
spec := &udev.Specification{}
c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPort1, nil, s.testSlot1, nil), IsNil)
- c.Assert(spec.Snippets(), HasLen, 0)
-
- expectedSnippet1 := `IMPORT{builtin}="usb_id"
-SUBSYSTEM=="hidraw", SUBSYSTEMS=="usb", ATTRS{idVendor}=="0001", ATTRS{idProduct}=="0001", TAG+="snap_client-snap_app-accessing-2-devices"`
- c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPort1, nil, s.testUDev1, nil), IsNil)
c.Assert(spec.Snippets(), HasLen, 1)
snippet := spec.Snippets()[0]
+ expectedSnippet1 := `SUBSYSTEM=="hidraw", KERNEL=="hidraw0", TAG+="snap_client-snap_app-accessing-2-devices"`
c.Assert(snippet, Equals, expectedSnippet1)
+ // add the plug for the first slot with vendor and product ids
+ spec = &udev.Specification{}
+ c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPort1, nil, s.testUDev1, nil), IsNil)
+ c.Assert(spec.Snippets(), HasLen, 1)
+ snippet = spec.Snippets()[0]
expectedSnippet2 := `IMPORT{builtin}="usb_id"
-SUBSYSTEM=="hidraw", SUBSYSTEMS=="usb", ATTRS{idVendor}=="ffff", ATTRS{idProduct}=="ffff", TAG+="snap_client-snap_app-accessing-2-devices"`
+SUBSYSTEM=="hidraw", SUBSYSTEMS=="usb", ATTRS{idVendor}=="0001", ATTRS{idProduct}=="0001", TAG+="snap_client-snap_app-accessing-2-devices"`
+ c.Assert(snippet, Equals, expectedSnippet2)
+
+ // add the plug for the second slot with vendor and product ids
spec = &udev.Specification{}
c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPort2, nil, s.testUDev2, nil), IsNil)
c.Assert(spec.Snippets(), HasLen, 1)
snippet = spec.Snippets()[0]
- c.Assert(snippet, Equals, expectedSnippet2)
+ expectedSnippet3 := `IMPORT{builtin}="usb_id"
+SUBSYSTEM=="hidraw", SUBSYSTEMS=="usb", ATTRS{idVendor}=="ffff", ATTRS{idProduct}=="ffff", TAG+="snap_client-snap_app-accessing-2-devices"`
+ c.Assert(snippet, Equals, expectedSnippet3)
}
func (s *HidrawInterfaceSuite) TestConnectedPlugAppArmorSnippets(c *C) {
@@ -247,6 +261,34 @@
c.Assert(snippet, DeepEquals, expectedSnippet3, Commentf("\nexpected:\n%s\nfound:\n%s", expectedSnippet3, snippet))
}
+func (s *HidrawInterfaceSuite) TestConnectedPlugUDevSnippetsForPath(c *C) {
+ expectedSnippet1 := `SUBSYSTEM=="hidraw", KERNEL=="hidraw0", TAG+="snap_client-snap_app-accessing-2-devices"`
+ udevSpec := &udev.Specification{}
+ err := udevSpec.AddConnectedPlug(s.iface, s.testPlugPort1, nil, s.testSlot1, nil)
+ c.Assert(err, IsNil)
+ c.Assert(udevSpec.Snippets(), HasLen, 1)
+ snippet := udevSpec.Snippets()[0]
+ c.Assert(snippet, DeepEquals, expectedSnippet1, Commentf("\nexpected:\n%s\nfound:\n%s", expectedSnippet1, snippet))
+
+ expectedSnippet2 := `IMPORT{builtin}="usb_id"
+SUBSYSTEM=="hidraw", SUBSYSTEMS=="usb", ATTRS{idVendor}=="0001", ATTRS{idProduct}=="0001", TAG+="snap_client-snap_app-accessing-2-devices"`
+ udevSpec = &udev.Specification{}
+ err = udevSpec.AddConnectedPlug(s.iface, s.testPlugPort1, nil, s.testUDev1, nil)
+ c.Assert(err, IsNil)
+ c.Assert(udevSpec.Snippets(), HasLen, 1)
+ snippet = udevSpec.Snippets()[0]
+ c.Assert(snippet, DeepEquals, expectedSnippet2, Commentf("\nexpected:\n%s\nfound:\n%s", expectedSnippet2, snippet))
+
+ expectedSnippet3 := `IMPORT{builtin}="usb_id"
+SUBSYSTEM=="hidraw", SUBSYSTEMS=="usb", ATTRS{idVendor}=="ffff", ATTRS{idProduct}=="ffff", TAG+="snap_client-snap_app-accessing-2-devices"`
+ udevSpec = &udev.Specification{}
+ err = udevSpec.AddConnectedPlug(s.iface, s.testPlugPort2, nil, s.testUDev2, nil)
+ c.Assert(err, IsNil)
+ c.Assert(udevSpec.Snippets(), HasLen, 1)
+ snippet = udevSpec.Snippets()[0]
+ c.Assert(snippet, DeepEquals, expectedSnippet3, Commentf("\nexpected:\n%s\nfound:\n%s", expectedSnippet3, snippet))
+}
+
func (s *HidrawInterfaceSuite) TestInterfaces(c *C) {
c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface)
}
diff -Nru snapd-2.28.5/interfaces/builtin/home.go snapd-2.29.3/interfaces/builtin/home.go
--- snapd-2.28.5/interfaces/builtin/home.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/home.go 2017-10-30 13:27:31.000000000 +0000
@@ -42,12 +42,13 @@
# Allow read/write access to all files in @{HOME}, except snap application
# data in @{HOME}/snaps and toplevel hidden directories in @{HOME}.
-owner @{HOME}/[^s.]** rwk,
-owner @{HOME}/s[^n]** rwk,
-owner @{HOME}/sn[^a]** rwk,
-owner @{HOME}/sna[^p]** rwk,
+owner @{HOME}/[^s.]** rwklix,
+owner @{HOME}/s[^n]** rwklix,
+owner @{HOME}/sn[^a]** rwklix,
+owner @{HOME}/sna[^p]** rwklix,
+owner @{HOME}/snap[^/]** rwklix,
# Allow creating a few files not caught above
-owner @{HOME}/{s,sn,sna}{,/} rwk,
+owner @{HOME}/{s,sn,sna}{,/} rwklix,
# Allow access to gvfs mounts for files owned by the user (including hidden
# files; only allow writes to files, not the mount point).
diff -Nru snapd-2.28.5/interfaces/builtin/i2c.go snapd-2.29.3/interfaces/builtin/i2c.go
--- snapd-2.28.5/interfaces/builtin/i2c.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/i2c.go 2017-11-09 12:08:52.000000000 +0000
@@ -48,8 +48,6 @@
/sys/devices/platform/{*,**.i2c}/%s/** rw,
`
-const i2cConnectedPlugUDev = `KERNEL=="%s", TAG+="%s"`
-
// The type for i2c interface
type i2cInterface struct{}
@@ -111,11 +109,7 @@
if !pathOk {
return nil
}
- const pathPrefix = "/dev/"
- for appName := range plug.Apps {
- tag := udevSnapSecurityName(plug.Snap.Name(), appName)
- spec.AddSnippet(fmt.Sprintf(i2cConnectedPlugUDev, strings.TrimPrefix(path, pathPrefix), tag))
- }
+ spec.TagDevice(fmt.Sprintf(`KERNEL=="%s"`, strings.TrimPrefix(path, "/dev/")))
return nil
}
diff -Nru snapd-2.28.5/interfaces/builtin/iio.go snapd-2.29.3/interfaces/builtin/iio.go
--- snapd-2.28.5/interfaces/builtin/iio.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/iio.go 2017-11-09 12:08:52.000000000 +0000
@@ -51,8 +51,6 @@
/sys/devices/**/###IIO_DEVICE_NAME###/** rwk,
`
-const iioConnectedPlugUDev = `KERNEL=="%s", TAG+="%s"`
-
// The type for iio interface
type iioInterface struct{}
@@ -123,11 +121,7 @@
if !pathOk {
return nil
}
- const pathPrefix = "/dev/"
- for appName := range plug.Apps {
- tag := udevSnapSecurityName(plug.Snap.Name(), appName)
- spec.AddSnippet(fmt.Sprintf(iioConnectedPlugUDev, strings.TrimPrefix(path, pathPrefix), tag))
- }
+ spec.TagDevice(fmt.Sprintf(`KERNEL=="%s"`, strings.TrimPrefix(path, "/dev/")))
return nil
}
diff -Nru snapd-2.28.5/interfaces/builtin/io_ports_control.go snapd-2.29.3/interfaces/builtin/io_ports_control.go
--- snapd-2.28.5/interfaces/builtin/io_ports_control.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/io_ports_control.go 2017-11-09 12:08:52.000000000 +0000
@@ -47,7 +47,8 @@
ioperm
iopl
`
-const ioPortsControlConnectedPlugUDev = `KERNEL=="port", TAG+="###CONNECTED_SECURITY_TAGS###"`
+
+var ioPortsControlConnectedPlugUDev = []string{`KERNEL=="port"`}
func init() {
registerIface(&commonInterface{
diff -Nru snapd-2.28.5/interfaces/builtin/joystick.go snapd-2.29.3/interfaces/builtin/joystick.go
--- snapd-2.28.5/interfaces/builtin/joystick.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/joystick.go 2017-11-09 12:08:52.000000000 +0000
@@ -38,7 +38,7 @@
/run/udev/data/c13:{[0-9],[12][0-9],3[01]} r,
`
-const joystickConnectedPlugUDev = `KERNEL=="js[0-9]*", TAG+="###CONNECTED_SECURITY_TAGS###"`
+var joystickConnectedPlugUDev = []string{`KERNEL=="js[0-9]*"`}
func init() {
registerIface(&commonInterface{
diff -Nru snapd-2.28.5/interfaces/builtin/kernel_module_control.go snapd-2.29.3/interfaces/builtin/kernel_module_control.go
--- snapd-2.28.5/interfaces/builtin/kernel_module_control.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/kernel_module_control.go 2017-11-09 12:08:52.000000000 +0000
@@ -60,7 +60,8 @@
finit_module
delete_module
`
-const kernelModuleControlConnectedPlugUDev = `KERNEL=="mem", TAG+="###CONNECTED_SECURITY_TAGS###"`
+
+var kernelModuleControlConnectedPlugUDev = []string{`KERNEL=="mem"`}
func init() {
registerIface(&commonInterface{
diff -Nru snapd-2.28.5/interfaces/builtin/kvm.go snapd-2.29.3/interfaces/builtin/kvm.go
--- snapd-2.28.5/interfaces/builtin/kvm.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/kvm.go 2017-11-09 12:08:52.000000000 +0000
@@ -36,7 +36,7 @@
/dev/kvm rw,
`
-const kvmConnectedPlugUDev = `KERNEL=="kvm", TAG+="###CONNECTED_SECURITY_TAGS###"`
+var kvmConnectedPlugUDev = []string{`KERNEL=="kvm"`}
func init() {
registerIface(&commonInterface{
diff -Nru snapd-2.28.5/interfaces/builtin/mir.go snapd-2.29.3/interfaces/builtin/mir.go
--- snapd-2.28.5/interfaces/builtin/mir.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/mir.go 2017-11-09 12:08:52.000000000 +0000
@@ -20,7 +20,6 @@
package builtin
import (
- "fmt"
"strings"
"github.com/snapcore/snapd/interfaces"
@@ -87,20 +86,6 @@
unix (receive, send) type=seqpacket addr=none peer=(label=###SLOT_SECURITY_TAGS###),
/run/mir_socket rw,
/run/user/[0-9]*/mir_socket rw,
-
-# 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,
-`
-
-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{}
@@ -143,10 +128,11 @@
}
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))
- }
+ spec.TagDevice(`KERNEL=="tty[0-9]*"`)
+ spec.TagDevice(`KERNEL=="mice"`)
+ spec.TagDevice(`KERNEL=="mouse[0-9]*"`)
+ spec.TagDevice(`KERNEL=="event[0-9]*"`)
+ spec.TagDevice(`KERNEL=="ts[0-9]*"`)
return nil
}
diff -Nru snapd-2.28.5/interfaces/builtin/mir_test.go snapd-2.29.3/interfaces/builtin/mir_test.go
--- snapd-2.28.5/interfaces/builtin/mir_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/mir_test.go 2017-11-09 12:08:52.000000000 +0000
@@ -139,8 +139,8 @@
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"`)
+ c.Assert(udevSpec.Snippets(), HasLen, 5)
+ c.Assert(udevSpec.Snippets(), testutil.Contains, `KERNEL=="event[0-9]*", TAG+="snap_mir-server_mir"`)
}
func (s *MirInterfaceSuite) TestInterfaces(c *C) {
diff -Nru snapd-2.28.5/interfaces/builtin/modem_manager.go snapd-2.29.3/interfaces/builtin/modem_manager.go
--- snapd-2.28.5/interfaces/builtin/modem_manager.go 2017-09-15 15:05:07.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/modem_manager.go 2017-11-09 12:08:52.000000000 +0000
@@ -1188,10 +1188,6 @@
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 {
@@ -1233,13 +1229,8 @@
}
func (iface *modemManagerInterface) UDevPermanentSlot(spec *udev.Specification, slot *interfaces.Slot) error {
- 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)
+ spec.AddSnippet(modemManagerPermanentSlotUDev)
+ spec.TagDevice(`KERNEL=="tty[A-Z]*[0-9]*|cdc-wdm[0-9]*"`)
return nil
}
diff -Nru snapd-2.28.5/interfaces/builtin/modem_manager_test.go snapd-2.29.3/interfaces/builtin/modem_manager_test.go
--- snapd-2.28.5/interfaces/builtin/modem_manager_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/modem_manager_test.go 2017-11-09 12:08:52.000000000 +0000
@@ -207,9 +207,9 @@
udevSpec := &udev.Specification{}
c.Assert(udevSpec.AddPermanentSlot(s.iface, s.slot), IsNil)
- c.Assert(udevSpec.Snippets(), HasLen, 1)
+ c.Assert(udevSpec.Snippets(), HasLen, 2)
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"`)
+ c.Assert(udevSpec.Snippets(), 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.28.5/interfaces/builtin/network_control.go snapd-2.29.3/interfaces/builtin/network_control.go
--- snapd-2.28.5/interfaces/builtin/network_control.go 2017-10-12 16:34:56.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/network_control.go 2017-11-09 12:08:52.000000000 +0000
@@ -255,10 +255,10 @@
* 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###"
-`
+var networkControlConnectedPlugUDev = []string{
+ `KERNEL=="rfkill"`,
+ `KERNEL=="tun"`,
+}
func init() {
registerIface(&commonInterface{
diff -Nru snapd-2.28.5/interfaces/builtin/network_control_test.go snapd-2.29.3/interfaces/builtin/network_control_test.go
--- snapd-2.28.5/interfaces/builtin/network_control_test.go 2017-10-12 16:34:56.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/network_control_test.go 2017-11-09 12:08:52.000000000 +0000
@@ -94,8 +94,8 @@
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"`)
+ c.Assert(spec.Snippets(), HasLen, 2)
+ c.Assert(spec.Snippets(), testutil.Contains, `KERNEL=="tun", TAG+="snap_consumer_app"`)
}
func (s *NetworkControlInterfaceSuite) TestStaticInfo(c *C) {
diff -Nru snapd-2.28.5/interfaces/builtin/network.go snapd-2.29.3/interfaces/builtin/network.go
--- snapd-2.28.5/interfaces/builtin/network.go 2017-09-15 15:05:07.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/network.go 2017-10-23 06:17:27.000000000 +0000
@@ -57,6 +57,9 @@
@{PROC}/sys/net/core/somaxconn r,
@{PROC}/sys/net/ipv4/tcp_fastopen r,
+
+# Allow using netcat as client
+/{,usr/}bin/nc{,.openbsd} ixr,
`
// http://bazaar.launchpad.net/~ubuntu-security/ubuntu-core-security/trunk/view/head:/data/seccomp/policygroups/ubuntu-core/16.04/network
diff -Nru snapd-2.28.5/interfaces/builtin/network_manager.go snapd-2.29.3/interfaces/builtin/network_manager.go
--- snapd-2.28.5/interfaces/builtin/network_manager.go 2017-09-15 15:05:07.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/network_manager.go 2017-11-09 12:08:52.000000000 +0000
@@ -414,8 +414,6 @@
2048
`
-const networkManagerPermanentSlotUdev = `KERNEL=="rfkill", TAG+="###CONNECTED_SECURITY_TAGS###"`
-
type networkManagerInterface struct{}
func (iface *networkManagerInterface) Name() string {
@@ -469,12 +467,7 @@
}
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)
- }
+ spec.TagDevice(`KERNEL=="rfkill"`)
return nil
}
diff -Nru snapd-2.28.5/interfaces/builtin/network_status.go snapd-2.29.3/interfaces/builtin/network_status.go
--- snapd-2.28.5/interfaces/builtin/network_status.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/network_status.go 2017-11-09 07:19:00.000000000 +0000
@@ -53,6 +53,13 @@
dbus (bind)
bus=system
name="com.ubuntu.connectivity1.NetworkingStatus",
+
+# allow queries from unconfined
+dbus (receive)
+ bus=system
+ path=/com/ubuntu/connectivity1/NetworkingStatus{,/**}
+ interface=org.freedesktop.DBus.*
+ peer=(label=unconfined),
`
const networkStatusConnectedSlotAppArmor = `
diff -Nru snapd-2.28.5/interfaces/builtin/ofono.go snapd-2.29.3/interfaces/builtin/ofono.go
--- snapd-2.28.5/interfaces/builtin/ofono.go 2017-09-15 15:05:07.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/ofono.go 2017-11-09 12:08:52.000000000 +0000
@@ -289,20 +289,6 @@
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 {
@@ -334,20 +320,24 @@
return nil
}
-func (iface *ofonoInterface) DBusPermanentSlot(spec *dbus.Specification, plug *interfaces.Plug, slot *interfaces.Slot) error {
+func (iface *ofonoInterface) DBusPermanentSlot(spec *dbus.Specification, slot *interfaces.Slot) error {
spec.AddSnippet(ofonoPermanentSlotDBus)
return nil
}
func (iface *ofonoInterface) UDevPermanentSlot(spec *udev.Specification, slot *interfaces.Slot) error {
- 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)
+ spec.AddSnippet(ofonoPermanentSlotUDev)
+ /*
+ 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.
+ */
+ spec.TagDevice(`KERNEL=="tty[A-Z]*[0-9]*|cdc-wdm[0-9]*"`)
+ spec.TagDevice(`KERNEL=="tun"`)
+ spec.TagDevice(`KERNEL=="tun[0-9]*"`)
+ spec.TagDevice(`KERNEL=="dsp"`)
return nil
}
diff -Nru snapd-2.28.5/interfaces/builtin/ofono_test.go snapd-2.29.3/interfaces/builtin/ofono_test.go
--- snapd-2.28.5/interfaces/builtin/ofono_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/ofono_test.go 2017-11-09 12:08:52.000000000 +0000
@@ -200,9 +200,9 @@
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(), HasLen, 5)
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"`)
+ c.Assert(spec.Snippets(), testutil.Contains, `KERNEL=="tty[A-Z]*[0-9]*|cdc-wdm[0-9]*", TAG+="snap_ofono_app"`)
}
func (s *OfonoInterfaceSuite) TestInterfaces(c *C) {
diff -Nru snapd-2.28.5/interfaces/builtin/opengl.go snapd-2.29.3/interfaces/builtin/opengl.go
--- snapd-2.28.5/interfaces/builtin/opengl.go 2017-10-11 17:40:25.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/opengl.go 2017-11-09 12:08:52.000000000 +0000
@@ -35,6 +35,19 @@
/var/lib/snapd/lib/gl/ r,
/var/lib/snapd/lib/gl/** rm,
+ # Supports linux-driver-management from Solus (staged symlink trees into libdirs)
+ /var/lib/snapd/hostfs/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}glx-provider/**.so{,.*} rm,
+
+ # Bi-arch distribution nvidia support
+ /var/lib/snapd/hostfs/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libcuda*.so{,.*} rm,
+ /var/lib/snapd/hostfs/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libnvidia*.so{,.*} rm,
+ /var/lib/snapd/hostfs/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libnvcuvid.so{,.*} rm,
+ /var/lib/snapd/hostfs/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}lib{GL,EGL}*nvidia.so{,.*} rm,
+ /var/lib/snapd/hostfs/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libGLdispatch.so{,.*} rm,
+
+ # Main bi-arch GL libraries
+ /var/lib/snapd/hostfs/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}lib{GL,EGL}.so{,.*} rm,
+
/dev/dri/ r,
/dev/dri/card0 rw,
# nvidia
@@ -70,10 +83,10 @@
// 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###"
-`
+var openglConnectedPlugUDev = []string{
+ `SUBSYSTEM=="drm", KERNEL=="card[0-9]*"`,
+ `KERNEL=="vchiq"`,
+}
func init() {
registerIface(&commonInterface{
diff -Nru snapd-2.28.5/interfaces/builtin/opengl_test.go snapd-2.29.3/interfaces/builtin/opengl_test.go
--- snapd-2.28.5/interfaces/builtin/opengl_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/opengl_test.go 2017-11-09 12:08:52.000000000 +0000
@@ -86,8 +86,8 @@
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"`)
+ c.Assert(spec.Snippets(), HasLen, 2)
+ c.Assert(spec.Snippets(), testutil.Contains, `SUBSYSTEM=="drm", KERNEL=="card[0-9]*", TAG+="snap_consumer_app"`)
}
func (s *OpenglInterfaceSuite) TestStaticInfo(c *C) {
diff -Nru snapd-2.28.5/interfaces/builtin/optical_drive.go snapd-2.29.3/interfaces/builtin/optical_drive.go
--- snapd-2.28.5/interfaces/builtin/optical_drive.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/optical_drive.go 2017-11-09 12:08:52.000000000 +0000
@@ -36,10 +36,10 @@
/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###"
-`
+var opticalDriveConnectedPlugUDev = []string{
+ `KERNEL=="sr[0-9]*"`,
+ `KERNEL=="scd[0-9]*"`,
+}
func init() {
registerIface(&commonInterface{
diff -Nru snapd-2.28.5/interfaces/builtin/optical_drive_test.go snapd-2.29.3/interfaces/builtin/optical_drive_test.go
--- snapd-2.28.5/interfaces/builtin/optical_drive_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/optical_drive_test.go 2017-11-09 12:08:52.000000000 +0000
@@ -86,8 +86,8 @@
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"`)
+ c.Assert(spec.Snippets(), HasLen, 2)
+ c.Assert(spec.Snippets(), testutil.Contains, `KERNEL=="sr[0-9]*", TAG+="snap_consumer_app"`)
}
func (s *OpticalDriveInterfaceSuite) TestStaticInfo(c *C) {
diff -Nru snapd-2.28.5/interfaces/builtin/physical_memory_control.go snapd-2.29.3/interfaces/builtin/physical_memory_control.go
--- snapd-2.28.5/interfaces/builtin/physical_memory_control.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/physical_memory_control.go 2017-11-09 12:08:52.000000000 +0000
@@ -41,7 +41,7 @@
/dev/mem rw,
`
-const physicalMemoryControlConnectedPlugUDev = `KERNEL=="mem", TAG+="###CONNECTED_SECURITY_TAGS###"`
+var physicalMemoryControlConnectedPlugUDev = []string{`KERNEL=="mem"`}
func init() {
registerIface(&commonInterface{
diff -Nru snapd-2.28.5/interfaces/builtin/physical_memory_observe.go snapd-2.29.3/interfaces/builtin/physical_memory_observe.go
--- snapd-2.28.5/interfaces/builtin/physical_memory_observe.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/physical_memory_observe.go 2017-11-09 12:08:52.000000000 +0000
@@ -37,7 +37,7 @@
/dev/mem r,
`
-const physicalMemoryObserveConnectedPlugUDev = `KERNEL=="mem", TAG+="###CONNECTED_SECURITY_TAGS###"`
+var physicalMemoryObserveConnectedPlugUDev = []string{`KERNEL=="mem"`}
func init() {
registerIface(&commonInterface{
diff -Nru snapd-2.28.5/interfaces/builtin/ppp.go snapd-2.29.3/interfaces/builtin/ppp.go
--- snapd-2.28.5/interfaces/builtin/ppp.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/ppp.go 2017-11-09 12:08:52.000000000 +0000
@@ -55,10 +55,10 @@
"ppp_generic",
}
-const pppConnectedPlugUDev = `
-KERNEL=="ppp", TAG+="###CONNECTED_SECURITY_TAGS###"
-KERNEL=="tty[A-Z]*[0-9]*", TAG+="###CONNECTED_SECURITY_TAGS###"
-`
+var pppConnectedPlugUDev = []string{
+ `KERNEL=="ppp"`,
+ `KERNEL=="tty[A-Z]*[0-9]*"`,
+}
func init() {
registerIface(&commonInterface{
diff -Nru snapd-2.28.5/interfaces/builtin/ppp_test.go snapd-2.29.3/interfaces/builtin/ppp_test.go
--- snapd-2.28.5/interfaces/builtin/ppp_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/ppp_test.go 2017-11-09 12:08:52.000000000 +0000
@@ -96,8 +96,8 @@
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"`)
+ c.Assert(spec.Snippets(), HasLen, 2)
+ c.Assert(spec.Snippets(), testutil.Contains, `KERNEL=="ppp", TAG+="snap_consumer_app"`)
}
func (s *PppInterfaceSuite) TestStaticInfo(c *C) {
diff -Nru snapd-2.28.5/interfaces/builtin/pulseaudio.go snapd-2.29.3/interfaces/builtin/pulseaudio.go
--- snapd-2.28.5/interfaces/builtin/pulseaudio.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/pulseaudio.go 2017-11-09 12:08:52.000000000 +0000
@@ -20,8 +20,6 @@
package builtin
import (
- "strings"
-
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/interfaces/apparmor"
"github.com/snapcore/snapd/interfaces/seccomp"
@@ -129,12 +127,6 @@
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 {
@@ -158,12 +150,9 @@
}
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)
- }
+ spec.TagDevice(`KERNEL=="controlC[0-9]*"`)
+ spec.TagDevice(`KERNEL=="pcmC[0-9]*D[0-9]*[cp]"`)
+ spec.TagDevice(`KERNEL=="timer"`)
return nil
}
diff -Nru snapd-2.28.5/interfaces/builtin/pulseaudio_test.go snapd-2.29.3/interfaces/builtin/pulseaudio_test.go
--- snapd-2.28.5/interfaces/builtin/pulseaudio_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/pulseaudio_test.go 2017-11-09 12:08:52.000000000 +0000
@@ -115,8 +115,8 @@
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"`)
+ c.Assert(spec.Snippets(), HasLen, 3)
+ c.Assert(spec.Snippets(), testutil.Contains, `KERNEL=="pcmC[0-9]*D[0-9]*[cp]", TAG+="snap_pulseaudio_app1"`)
}
func (s *PulseAudioInterfaceSuite) TestInterfaces(c *C) {
diff -Nru snapd-2.28.5/interfaces/builtin/raw_usb.go snapd-2.29.3/interfaces/builtin/raw_usb.go
--- snapd-2.28.5/interfaces/builtin/raw_usb.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/raw_usb.go 2017-11-09 12:08:52.000000000 +0000
@@ -45,7 +45,7 @@
/run/udev/data/+usb:* r,
`
-const rawusbConnectedPlugUDev = `SUBSYSTEMS=="usb", TAG+="###CONNECTED_SECURITY_TAGS###"`
+var rawusbConnectedPlugUDev = []string{`SUBSYSTEM=="usb"`}
func init() {
registerIface(&commonInterface{
diff -Nru snapd-2.28.5/interfaces/builtin/raw_usb_test.go snapd-2.29.3/interfaces/builtin/raw_usb_test.go
--- snapd-2.28.5/interfaces/builtin/raw_usb_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/raw_usb_test.go 2017-11-09 07:19:00.000000000 +0000
@@ -87,7 +87,7 @@
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"`)
+ c.Assert(spec.Snippets()[0], testutil.Contains, `SUBSYSTEM=="usb", TAG+="snap_consumer_app"`)
}
func (s *RawUsbInterfaceSuite) TestStaticInfo(c *C) {
diff -Nru snapd-2.28.5/interfaces/builtin/removable_media.go snapd-2.29.3/interfaces/builtin/removable_media.go
--- snapd-2.28.5/interfaces/builtin/removable_media.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/removable_media.go 2017-10-30 13:27:31.000000000 +0000
@@ -32,6 +32,13 @@
const removableMediaConnectedPlugAppArmor = `
# Description: Can access removable storage filesystems
+# Allow read-access to /run/ for navigating to removable media.
+/run/ r,
+
+# Allow read on /run/media/ for navigating to the mount points. While this
+# allows enumerating users, this is already allowed via /etc/passwd and getent.
+/{,run/}media/ r,
+
# Mount points could be in /run/media//* or /media//*
/{,run/}media/*/ r,
/{,run/}media/*/** rw,
diff -Nru snapd-2.28.5/interfaces/builtin/serial_port.go snapd-2.29.3/interfaces/builtin/serial_port.go
--- snapd-2.28.5/interfaces/builtin/serial_port.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/serial_port.go 2017-11-09 12:08:52.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
@@ -148,7 +148,7 @@
return nil
}
- // Path to fixed device node (no udev tagging)
+ // Path to fixed device node
path, pathOk := slot.Attrs["path"].(string)
if !pathOk {
return nil
@@ -159,17 +159,32 @@
}
func (iface *serialPortInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error {
+ // For connected plugs, we use vendor and product ids if available,
+ // otherwise add the kernel device
+ hasOnlyPath := true
+ if iface.hasUsbAttrs(slot) {
+ hasOnlyPath = false
+ }
+
usbVendor, vOk := slot.Attrs["usb-vendor"].(int64)
- if !vOk {
+ if !vOk && !hasOnlyPath {
return nil
}
usbProduct, pOk := slot.Attrs["usb-product"].(int64)
- if !pOk {
+ if !pOk && !hasOnlyPath {
return nil
}
- for appName := range plug.Apps {
- tag := udevSnapSecurityName(plug.Snap.Name(), appName)
- spec.AddSnippet(udevUsbDeviceSnippet("tty", usbVendor, usbProduct, "TAG", tag))
+
+ path, pathOk := slot.Attrs["path"].(string)
+ if !pathOk && hasOnlyPath {
+ return nil
+ }
+
+ if hasOnlyPath {
+ spec.TagDevice(fmt.Sprintf(`SUBSYSTEM=="tty", KERNEL=="%s"`, strings.TrimPrefix(path, "/dev/")))
+ } else {
+ spec.TagDevice(fmt.Sprintf(`IMPORT{builtin}="usb_id"
+SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="%04x", ATTRS{idProduct}=="%04x"`, usbVendor, usbProduct))
}
return nil
}
diff -Nru snapd-2.28.5/interfaces/builtin/serial_port_test.go snapd-2.29.3/interfaces/builtin/serial_port_test.go
--- snapd-2.28.5/interfaces/builtin/serial_port_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/serial_port_test.go 2017-11-03 05:48:53.000000000 +0000
@@ -65,6 +65,7 @@
// Consuming Snap
testPlugPort1 *interfaces.Plug
testPlugPort2 *interfaces.Plug
+ testPlugPort3 *interfaces.Plug
}
var _ = Suite(&SerialPortInterfaceSuite{
@@ -193,6 +194,8 @@
interface: serial-port
plug-for-port-2:
interface: serial-port
+ plug-for-port-3:
+ interface: serial-port
apps:
app-accessing-1-port:
@@ -201,9 +204,13 @@
app-accessing-2-ports:
command: bar
plugs: [plug-for-port-1, plug-for-port-2]
+ app-accessing-3rd-port:
+ command: foo
+ plugs: [plug-for-port-3]
`, nil)
s.testPlugPort1 = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["plug-for-port-1"]}
s.testPlugPort2 = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["plug-for-port-2"]}
+ s.testPlugPort3 = &interfaces.Plug{PlugInfo: consumingSnapInfo.Plugs["plug-for-port-3"]}
}
func (s *SerialPortInterfaceSuite) TestName(c *C) {
@@ -264,27 +271,34 @@
}
func (s *SerialPortInterfaceSuite) TestConnectedPlugUDevSnippets(c *C) {
+ // add the plug for the slot with just path
spec := &udev.Specification{}
err := spec.AddConnectedPlug(s.iface, s.testPlugPort1, nil, s.testSlot1, nil)
c.Assert(err, IsNil)
- c.Assert(spec.Snippets(), HasLen, 0)
+ c.Assert(spec.Snippets(), HasLen, 1)
+ snippet := spec.Snippets()[0]
+ expectedSnippet1 := `SUBSYSTEM=="tty", KERNEL=="ttyS0", TAG+="snap_client-snap_app-accessing-2-ports"`
+ c.Assert(snippet, Equals, expectedSnippet1)
- expectedSnippet1 := `IMPORT{builtin}="usb_id"
-SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="0001", ATTRS{idProduct}=="0001", TAG+="snap_client-snap_app-accessing-2-ports"`
+ // add plug for the first slot with product and vendor ids
+ spec = &udev.Specification{}
err = spec.AddConnectedPlug(s.iface, s.testPlugPort1, nil, s.testUDev1, nil)
c.Assert(err, IsNil)
c.Assert(spec.Snippets(), HasLen, 1)
- snippet := spec.Snippets()[0]
- c.Assert(snippet, Equals, expectedSnippet1)
+ snippet = spec.Snippets()[0]
+ expectedSnippet2 := `IMPORT{builtin}="usb_id"
+SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="0001", ATTRS{idProduct}=="0001", TAG+="snap_client-snap_app-accessing-2-ports"`
+ c.Assert(snippet, Equals, expectedSnippet2)
+ // add plug for the first slot with product and vendor ids
spec = &udev.Specification{}
err = spec.AddConnectedPlug(s.iface, s.testPlugPort2, nil, s.testUDev2, nil)
c.Assert(err, IsNil)
c.Assert(spec.Snippets(), HasLen, 1)
snippet = spec.Snippets()[0]
- expectedSnippet2 := `IMPORT{builtin}="usb_id"
+ expectedSnippet3 := `IMPORT{builtin}="usb_id"
SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="ffff", ATTRS{idProduct}=="ffff", TAG+="snap_client-snap_app-accessing-2-ports"`
- c.Assert(snippet, Equals, expectedSnippet2)
+ c.Assert(snippet, Equals, expectedSnippet3)
}
func (s *SerialPortInterfaceSuite) TestConnectedPlugAppArmorSnippets(c *C) {
@@ -325,6 +339,49 @@
checkConnectedPlugSnippet(s.testPlugPort2, s.testUDev2, expectedSnippet9)
}
+func (s *SerialPortInterfaceSuite) TestConnectedPlugUDevSnippetsForPath(c *C) {
+ checkConnectedPlugSnippet := func(plug *interfaces.Plug, slot *interfaces.Slot, expectedSnippet string) {
+ udevSpec := &udev.Specification{}
+ err := udevSpec.AddConnectedPlug(s.iface, plug, nil, slot, nil)
+ c.Assert(err, IsNil)
+
+ c.Assert(udevSpec.Snippets(), HasLen, 1)
+ snippet := udevSpec.Snippets()[0]
+ c.Assert(snippet, DeepEquals, expectedSnippet, Commentf("\nexpected:\n%s\nfound:\n%s", expectedSnippet, snippet))
+ }
+
+ // these have only path
+ expectedSnippet1 := `SUBSYSTEM=="tty", KERNEL=="ttyS0", TAG+="snap_client-snap_app-accessing-3rd-port"`
+ checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot1, expectedSnippet1)
+
+ expectedSnippet2 := `SUBSYSTEM=="tty", KERNEL=="ttyUSB927", TAG+="snap_client-snap_app-accessing-3rd-port"`
+ checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot2, expectedSnippet2)
+
+ expectedSnippet3 := `SUBSYSTEM=="tty", KERNEL=="ttyS42", TAG+="snap_client-snap_app-accessing-3rd-port"`
+ checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot3, expectedSnippet3)
+
+ expectedSnippet4 := `SUBSYSTEM=="tty", KERNEL=="ttyO0", TAG+="snap_client-snap_app-accessing-3rd-port"`
+ checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot4, expectedSnippet4)
+
+ expectedSnippet5 := `SUBSYSTEM=="tty", KERNEL=="ttyACM0", TAG+="snap_client-snap_app-accessing-3rd-port"`
+ checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot5, expectedSnippet5)
+
+ expectedSnippet6 := `SUBSYSTEM=="tty", KERNEL=="ttyAMA0", TAG+="snap_client-snap_app-accessing-3rd-port"`
+ checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot6, expectedSnippet6)
+
+ expectedSnippet7 := `SUBSYSTEM=="tty", KERNEL=="ttyXRUSB0", TAG+="snap_client-snap_app-accessing-3rd-port"`
+ checkConnectedPlugSnippet(s.testPlugPort3, s.testSlot7, expectedSnippet7)
+
+ // these have product and vendor ids
+ expectedSnippet8 := `IMPORT{builtin}="usb_id"
+SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="0001", ATTRS{idProduct}=="0001", TAG+="snap_client-snap_app-accessing-3rd-port"`
+ checkConnectedPlugSnippet(s.testPlugPort3, s.testUDev1, expectedSnippet8)
+
+ expectedSnippet9 := `IMPORT{builtin}="usb_id"
+SUBSYSTEM=="tty", SUBSYSTEMS=="usb", ATTRS{idVendor}=="ffff", ATTRS{idProduct}=="ffff", TAG+="snap_client-snap_app-accessing-3rd-port"`
+ checkConnectedPlugSnippet(s.testPlugPort3, s.testUDev2, expectedSnippet9)
+}
+
func (s *SerialPortInterfaceSuite) TestInterfaces(c *C) {
c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface)
}
diff -Nru snapd-2.28.5/interfaces/builtin/spi.go snapd-2.29.3/interfaces/builtin/spi.go
--- snapd-2.28.5/interfaces/builtin/spi.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/spi.go 2017-11-09 12:08:52.000000000 +0000
@@ -91,10 +91,7 @@
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))
- }
+ spec.TagDevice(fmt.Sprintf(`KERNEL=="%s"`, strings.TrimPrefix(path, "/dev/")))
return nil
}
diff -Nru snapd-2.28.5/interfaces/builtin/system_observe.go snapd-2.29.3/interfaces/builtin/system_observe.go
--- snapd-2.28.5/interfaces/builtin/system_observe.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/system_observe.go 2017-10-23 06:17:27.000000000 +0000
@@ -66,6 +66,10 @@
@{PROC}/*/{,task/*/}statm r,
@{PROC}/*/{,task/*/}status r,
+# Allow discovering the os-release of the host
+/var/lib/snapd/hostfs/etc/os-release rk,
+/var/lib/snapd/hostfs/usr/lib/os-release rk,
+
#include
dbus (send)
@@ -82,6 +86,14 @@
interface=org.freedesktop.DBus.Introspectable
member=Introspect
peer=(label=unconfined),
+
+# Allow clients to enumerate DBus connection names on common buses
+dbus (send)
+ bus={session,system}
+ path=/org/freedesktop/DBus
+ interface=org.freedesktop.DBus
+ member=ListNames
+ peer=(label=unconfined),
`
const systemObserveConnectedPlugSecComp = `
diff -Nru snapd-2.28.5/interfaces/builtin/time_control.go snapd-2.29.3/interfaces/builtin/time_control.go
--- snapd-2.28.5/interfaces/builtin/time_control.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/time_control.go 2017-11-09 12:08:52.000000000 +0000
@@ -92,7 +92,7 @@
/sbin/hwclock ixr,
`
-const timeControlConnectedPlugUDev = `SUBSYSTEM=="rtc", TAG+="###CONNECTED_SECURITY_TAGS###"`
+var timeControlConnectedPlugUDev = []string{`SUBSYSTEM=="rtc"`}
func init() {
registerIface(&commonInterface{
diff -Nru snapd-2.28.5/interfaces/builtin/tpm.go snapd-2.29.3/interfaces/builtin/tpm.go
--- snapd-2.28.5/interfaces/builtin/tpm.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/tpm.go 2017-11-09 12:08:52.000000000 +0000
@@ -35,7 +35,7 @@
/dev/tpm0 rw,
`
-const tpmConnectedPlugUDev = `KERNEL=="tpm[0-9]*", TAG+="###CONNECTED_SECURITY_TAGS###"`
+var tpmConnectedPlugUDev = []string{`KERNEL=="tpm[0-9]*"`}
func init() {
registerIface(&commonInterface{
diff -Nru snapd-2.28.5/interfaces/builtin/udisks2.go snapd-2.29.3/interfaces/builtin/udisks2.go
--- snapd-2.28.5/interfaces/builtin/udisks2.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/udisks2.go 2017-11-09 12:08:52.000000000 +0000
@@ -112,7 +112,7 @@
# Allow connected clients to interact with the service. This gives privileged
# access to the system.
-dbus (send)
+dbus (receive, send)
bus=system
path=/org/freedesktop/UDisks2/**
interface=org.freedesktop.DBus.Properties
@@ -353,12 +353,6 @@
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 {
@@ -396,13 +390,10 @@
}
func (iface *udisks2Interface) UDevPermanentSlot(spec *udev.Specification, slot *interfaces.Slot) error {
- 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)
+ spec.AddSnippet(udisks2PermanentSlotUDev)
+ spec.TagDevice(`SUBSYSTEM=="block"`)
+ // # This tags all USB devices, so we'll use AppArmor to mediate specific access (eg, /dev/sd* and /dev/mmcblk*)
+ spec.TagDevice(`SUBSYSTEM=="usb"`)
return nil
}
diff -Nru snapd-2.28.5/interfaces/builtin/udisks2_test.go snapd-2.29.3/interfaces/builtin/udisks2_test.go
--- snapd-2.28.5/interfaces/builtin/udisks2_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/udisks2_test.go 2017-11-09 12:08:52.000000000 +0000
@@ -165,9 +165,9 @@
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(), HasLen, 3)
c.Assert(spec.Snippets()[0], testutil.Contains, `LABEL="udisks_probe_end"`)
- c.Assert(spec.Snippets()[0], testutil.Contains, `SUBSYSTEM=="usb", TAG+="snap_producer_app"`)
+ c.Assert(spec.Snippets(), testutil.Contains, `SUBSYSTEM=="usb", TAG+="snap_producer_app"`)
}
func (s *UDisks2InterfaceSuite) TestSecCompSpec(c *C) {
diff -Nru snapd-2.28.5/interfaces/builtin/uhid.go snapd-2.29.3/interfaces/builtin/uhid.go
--- snapd-2.28.5/interfaces/builtin/uhid.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/uhid.go 2017-11-03 05:48:53.000000000 +0000
@@ -37,7 +37,7 @@
/dev/uhid rw,
`
-const uhidConnectedPlugUDev = `KERNEL=="uhid", TAG+="###CONNECTED_SECURITY_TAGS###"`
+// Note: uhid is not represented in sysfs so it cannot be udev tagged
func init() {
registerIface(&commonInterface{
@@ -47,7 +47,6 @@
implicitOnClassic: true,
baseDeclarationSlots: uhidBaseDeclarationSlots,
connectedPlugAppArmor: uhidConnectedPlugAppArmor,
- connectedPlugUDev: uhidConnectedPlugUDev,
reservedForOS: true,
})
}
diff -Nru snapd-2.28.5/interfaces/builtin/uhid_test.go snapd-2.29.3/interfaces/builtin/uhid_test.go
--- snapd-2.28.5/interfaces/builtin/uhid_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/uhid_test.go 2017-11-03 05:48:53.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/interfaces/udev"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/testutil"
)
@@ -84,13 +83,6 @@
c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "/dev/uhid rw,\n")
}
-func (s *UhidInterfaceSuite) 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=="uhid", TAG+="snap_consumer_app"`)
-}
-
func (s *UhidInterfaceSuite) TestStaticInfo(c *C) {
si := interfaces.StaticInfoOf(s.iface)
c.Assert(si.ImplicitOnCore, Equals, true)
diff -Nru snapd-2.28.5/interfaces/builtin/unity7.go snapd-2.29.3/interfaces/builtin/unity7.go
--- snapd-2.28.5/interfaces/builtin/unity7.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/unity7.go 2017-10-30 13:27:31.000000000 +0000
@@ -214,7 +214,10 @@
# subset of freedesktop.org
/usr/share/mime/** r,
owner @{HOME}/.local/share/mime/** r,
-owner @{HOME}/.config/user-dirs.dirs r,
+owner @{HOME}/.config/user-dirs.* r,
+
+/etc/xdg/user-dirs.conf r,
+/etc/xdg/user-dirs.defaults r,
# gtk settings (subset of gnome abstraction)
owner @{HOME}/.config/gtk-2.0/gtkfilechooser.ini r,
@@ -543,10 +546,6 @@
path=/org/gnome/SettingsDaemon/MediaKeys
member="Get{,All}"
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,
`
const unity7ConnectedPlugSeccomp = `
diff -Nru snapd-2.28.5/interfaces/builtin/unity8.go snapd-2.29.3/interfaces/builtin/unity8.go
--- snapd-2.28.5/interfaces/builtin/unity8.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/unity8.go 2017-10-23 06:17:27.000000000 +0000
@@ -86,10 +86,6 @@
path=/
member={PasteboardChanged,PasteFormatsChanged,PasteSelected,PasteSelectionCancelled}
peer=(name=com.ubuntu.content.dbus.Service,label=###SLOT_SECURITY_TAGS###),
-
-# 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,
`
const unity8ConnectedPlugSecComp = `
diff -Nru snapd-2.28.5/interfaces/builtin/utils_test.go snapd-2.29.3/interfaces/builtin/utils_test.go
--- snapd-2.28.5/interfaces/builtin/utils_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/builtin/utils_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -20,15 +20,12 @@
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 {
@@ -67,17 +64,9 @@
}
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()))
+ return builtin.MockPlug(c, yaml, si, plugName)
}
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()))
+ return builtin.MockSlot(c, yaml, si, slotName)
}
diff -Nru snapd-2.28.5/interfaces/core.go snapd-2.29.3/interfaces/core.go
--- snapd-2.28.5/interfaces/core.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/core.go 2017-10-23 06:17:27.000000000 +0000
@@ -118,6 +118,15 @@
SlotRef SlotRef
}
+type Connection struct {
+ plugInfo *snap.PlugInfo
+ slotInfo *snap.SlotInfo
+}
+
+func (conn *Connection) Interface() string {
+ return conn.plugInfo.Interface
+}
+
// ID returns a string identifying a given connection.
func (conn *ConnRef) ID() string {
return fmt.Sprintf("%s:%s %s:%s", conn.PlugRef.Snap, conn.PlugRef.Name, conn.SlotRef.Snap, conn.SlotRef.Name)
diff -Nru snapd-2.28.5/interfaces/mount/backend.go snapd-2.29.3/interfaces/mount/backend.go
--- snapd-2.28.5/interfaces/mount/backend.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/interfaces/mount/backend.go 2017-10-23 06:17:27.000000000 +0000
@@ -98,17 +98,8 @@
}
fstate := &osutil.FileState{Content: buffer.Bytes(), Mode: 0644}
content := make(map[string]*osutil.FileState)
- // Add the new per-snap fstab file. This file will be read by snap-confine.
+ // Add the per-snap fstab file. This file is read by snap-update-ns.
content[fmt.Sprintf("snap.%s.fstab", snapInfo.Name())] = fstate
- // Add legacy per-app/per-hook fstab files. Those are identical but
- // snap-confine doesn't yet load it from a per-snap location. This can be
- // safely removed once snap-confine is updated.
- for _, appInfo := range snapInfo.Apps {
- content[fmt.Sprintf("%s.fstab", appInfo.SecurityTag())] = fstate
- }
- for _, hookInfo := range snapInfo.Hooks {
- content[fmt.Sprintf("%s.fstab", hookInfo.SecurityTag())] = fstate
- }
return content
}
diff -Nru snapd-2.28.5/interfaces/mount/backend_test.go snapd-2.29.3/interfaces/mount/backend_test.go
--- snapd-2.28.5/interfaces/mount/backend_test.go 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/interfaces/mount/backend_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -150,15 +150,6 @@
got := strings.Split(string(content), "\n")
sort.Strings(got)
c.Check(got, DeepEquals, expected)
- // and that we have the legacy, per app/hook files as well.
- for _, binary := range []string{"app1", "app2", "hook.configure"} {
- fn := filepath.Join(dirs.SnapMountPolicyDir, fmt.Sprintf("snap.snap-name.%s.fstab", binary))
- content, err := ioutil.ReadFile(fn)
- c.Assert(err, IsNil, Commentf("Expected mount profile for %q", binary))
- got := strings.Split(string(content), "\n")
- sort.Strings(got)
- c.Check(got, DeepEquals, expected)
- }
}
func (s *backendSuite) TestSetupSetsupWithoutDir(c *C) {
@@ -169,9 +160,4 @@
// Ensure that backend.Setup() creates the required dir on demand
os.Remove(dirs.SnapMountPolicyDir)
s.InstallSnap(c, interfaces.ConfinementOptions{}, mockSnapYaml, 0)
-
- for _, binary := range []string{"app1", "app2", "hook.configure"} {
- fn := filepath.Join(dirs.SnapMountPolicyDir, fmt.Sprintf("snap.snap-name.%s.fstab", binary))
- c.Assert(osutil.FileExists(fn), Equals, true, Commentf("Expected mount file for %q", binary))
- }
}
diff -Nru snapd-2.28.5/interfaces/mount/change.go snapd-2.29.3/interfaces/mount/change.go
--- snapd-2.28.5/interfaces/mount/change.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/interfaces/mount/change.go 1970-01-01 00:00:00.000000000 +0000
@@ -1,145 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2017 Canonical Ltd
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-
-package mount
-
-import (
- "fmt"
- "path"
- "sort"
- "strings"
- "syscall"
-)
-
-// Action represents a mount action (mount, remount, unmount, etc).
-type Action string
-
-const (
- // Keep indicates that a given mount entry should be kept as-is.
- Keep Action = "keep"
- // Mount represents an action that results in mounting something somewhere.
- Mount Action = "mount"
- // Unmount represents an action that results in unmounting something from somewhere.
- Unmount Action = "unmount"
- // Remount when needed
-)
-
-// Change describes a change to the mount table (action and the entry to act on).
-type Change struct {
- Entry Entry
- Action Action
-}
-
-// String formats mount change to a human-readable line.
-func (c Change) String() string {
- return fmt.Sprintf("%s (%s)", c.Action, c.Entry)
-}
-
-// Perform executes the desired mount or unmount change using system calls.
-// Filesystems that depend on helper programs or multiple independent calls to
-// the kernel (--make-shared, for example) are unsupported.
-func (c *Change) Perform() error {
- switch c.Action {
- case Mount:
- flags, err := OptsToFlags(c.Entry.Options)
- if err != nil {
- return err
- }
- return syscall.Mount(c.Entry.Name, c.Entry.Dir, c.Entry.Type, uintptr(flags), "")
- case Unmount:
- const UMOUNT_NOFOLLOW = 8
- return syscall.Unmount(c.Entry.Dir, UMOUNT_NOFOLLOW)
- }
- return fmt.Errorf("cannot process mount change, unknown action: %q", c.Action)
-}
-
-// NeededChanges computes the changes required to change current to desired mount entries.
-//
-// The current and desired profiles is a fstab like list of mount entries. The
-// lists are processed and a "diff" of mount changes is produced. The mount
-// changes, when applied in order, transform the current profile into the
-// desired profile.
-func NeededChanges(currentProfile, desiredProfile *Profile) []Change {
- // Copy both profiles as we will want to mutate them.
- current := make([]Entry, len(currentProfile.Entries))
- copy(current, currentProfile.Entries)
- desired := make([]Entry, len(desiredProfile.Entries))
- copy(desired, desiredProfile.Entries)
-
- // Clean the directory part of both profiles. This is done so that we can
- // easily test if a given directory is a subdirectory with
- // strings.HasPrefix coupled with an extra slash character.
- for i := range current {
- current[i].Dir = path.Clean(current[i].Dir)
- }
- for i := range desired {
- desired[i].Dir = path.Clean(desired[i].Dir)
- }
-
- // Sort both lists by directory name with implicit trailing slash.
- sort.Sort(byMagicDir(current))
- sort.Sort(byMagicDir(desired))
-
- // Construct a desired directory map.
- desiredMap := make(map[string]*Entry)
- for i := range desired {
- desiredMap[desired[i].Dir] = &desired[i]
- }
-
- // Compute reusable entries: those which are equal in current and desired and which
- // are not prefixed by another entry that changed.
- var reuse map[string]bool
- var skipDir string
- for i := range current {
- dir := current[i].Dir
- if skipDir != "" && strings.HasPrefix(dir, skipDir) {
- continue
- }
- skipDir = "" // reset skip prefix as it no longer applies
- if entry, ok := desiredMap[dir]; ok && current[i].Equal(entry) {
- if reuse == nil {
- reuse = make(map[string]bool)
- }
- reuse[dir] = true
- continue
- }
- skipDir = strings.TrimSuffix(dir, "/") + "/"
- }
-
- // We are now ready to compute the necessary mount changes.
- var changes []Change
-
- // Unmount entries not reused in reverse to handle children before their parent.
- for i := len(current) - 1; i >= 0; i-- {
- if reuse[current[i].Dir] {
- changes = append(changes, Change{Action: Keep, Entry: current[i]})
- } else {
- changes = append(changes, Change{Action: Unmount, Entry: current[i]})
- }
- }
-
- // Mount desired entries not reused.
- for i := range desired {
- if !reuse[desired[i].Dir] {
- changes = append(changes, Change{Action: Mount, Entry: desired[i]})
- }
- }
-
- return changes
-}
diff -Nru snapd-2.28.5/interfaces/mount/change_test.go snapd-2.29.3/interfaces/mount/change_test.go
--- snapd-2.28.5/interfaces/mount/change_test.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/interfaces/mount/change_test.go 1970-01-01 00:00:00.000000000 +0000
@@ -1,176 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2017 Canonical Ltd
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-
-package mount_test
-
-import (
- . "gopkg.in/check.v1"
-
- "github.com/snapcore/snapd/interfaces/mount"
-)
-
-type changeSuite struct{}
-
-var _ = Suite(&changeSuite{})
-
-func (s *changeSuite) TestString(c *C) {
- change := mount.Change{
- Entry: mount.Entry{Dir: "/a/b", Name: "/dev/sda1"},
- Action: mount.Mount,
- }
- c.Assert(change.String(), Equals, "mount (/dev/sda1 /a/b none defaults 0 0)")
-}
-
-// When there are no profiles we don't do anything.
-func (s *changeSuite) TestNeededChangesNoProfiles(c *C) {
- current := &mount.Profile{}
- desired := &mount.Profile{}
- changes := mount.NeededChanges(current, desired)
- c.Assert(changes, IsNil)
-}
-
-// When the profiles are the same we don't do anything.
-func (s *changeSuite) TestNeededChangesNoChange(c *C) {
- current := &mount.Profile{Entries: []mount.Entry{{Dir: "/common/stuf"}}}
- desired := &mount.Profile{Entries: []mount.Entry{{Dir: "/common/stuf"}}}
- changes := mount.NeededChanges(current, desired)
- c.Assert(changes, DeepEquals, []mount.Change{
- {Entry: mount.Entry{Dir: "/common/stuf"}, Action: mount.Keep},
- })
-}
-
-// When the content interface is connected we should mount the new entry.
-func (s *changeSuite) TestNeededChangesTrivialMount(c *C) {
- current := &mount.Profile{}
- desired := &mount.Profile{Entries: []mount.Entry{{Dir: "/common/stuf"}}}
- changes := mount.NeededChanges(current, desired)
- c.Assert(changes, DeepEquals, []mount.Change{
- {Entry: desired.Entries[0], Action: mount.Mount},
- })
-}
-
-// When the content interface is disconnected we should unmount the mounted entry.
-func (s *changeSuite) TestNeededChangesTrivialUnmount(c *C) {
- current := &mount.Profile{Entries: []mount.Entry{{Dir: "/common/stuf"}}}
- desired := &mount.Profile{}
- changes := mount.NeededChanges(current, desired)
- c.Assert(changes, DeepEquals, []mount.Change{
- {Entry: current.Entries[0], Action: mount.Unmount},
- })
-}
-
-// When umounting we unmount children before parents.
-func (s *changeSuite) TestNeededChangesUnmountOrder(c *C) {
- current := &mount.Profile{Entries: []mount.Entry{
- {Dir: "/common/stuf/extra"},
- {Dir: "/common/stuf"},
- }}
- desired := &mount.Profile{}
- changes := mount.NeededChanges(current, desired)
- c.Assert(changes, DeepEquals, []mount.Change{
- {Entry: mount.Entry{Dir: "/common/stuf/extra"}, Action: mount.Unmount},
- {Entry: mount.Entry{Dir: "/common/stuf"}, Action: mount.Unmount},
- })
-}
-
-// When mounting we mount the parents before the children.
-func (s *changeSuite) TestNeededChangesMountOrder(c *C) {
- current := &mount.Profile{}
- desired := &mount.Profile{Entries: []mount.Entry{
- {Dir: "/common/stuf/extra"},
- {Dir: "/common/stuf"},
- }}
- changes := mount.NeededChanges(current, desired)
- c.Assert(changes, DeepEquals, []mount.Change{
- {Entry: mount.Entry{Dir: "/common/stuf"}, Action: mount.Mount},
- {Entry: mount.Entry{Dir: "/common/stuf/extra"}, Action: mount.Mount},
- })
-}
-
-// When parent changes we don't reuse its children
-func (s *changeSuite) TestNeededChangesChangedParentSameChild(c *C) {
- current := &mount.Profile{Entries: []mount.Entry{
- {Dir: "/common/stuf", Name: "/dev/sda1"},
- {Dir: "/common/stuf/extra"},
- {Dir: "/common/unrelated"},
- }}
- desired := &mount.Profile{Entries: []mount.Entry{
- {Dir: "/common/stuf", Name: "/dev/sda2"},
- {Dir: "/common/stuf/extra"},
- {Dir: "/common/unrelated"},
- }}
- changes := mount.NeededChanges(current, desired)
- c.Assert(changes, DeepEquals, []mount.Change{
- {Entry: mount.Entry{Dir: "/common/unrelated"}, Action: mount.Keep},
- {Entry: mount.Entry{Dir: "/common/stuf/extra"}, Action: mount.Unmount},
- {Entry: mount.Entry{Dir: "/common/stuf", Name: "/dev/sda1"}, Action: mount.Unmount},
- {Entry: mount.Entry{Dir: "/common/stuf", Name: "/dev/sda2"}, Action: mount.Mount},
- {Entry: mount.Entry{Dir: "/common/stuf/extra"}, Action: mount.Mount},
- })
-}
-
-// When child changes we don't touch the unchanged parent
-func (s *changeSuite) TestNeededChangesSameParentChangedChild(c *C) {
- current := &mount.Profile{Entries: []mount.Entry{
- {Dir: "/common/stuf"},
- {Dir: "/common/stuf/extra", Name: "/dev/sda1"},
- {Dir: "/common/unrelated"},
- }}
- desired := &mount.Profile{Entries: []mount.Entry{
- {Dir: "/common/stuf"},
- {Dir: "/common/stuf/extra", Name: "/dev/sda2"},
- {Dir: "/common/unrelated"},
- }}
- changes := mount.NeededChanges(current, desired)
- c.Assert(changes, DeepEquals, []mount.Change{
- {Entry: mount.Entry{Dir: "/common/unrelated"}, Action: mount.Keep},
- {Entry: mount.Entry{Dir: "/common/stuf/extra", Name: "/dev/sda1"}, Action: mount.Unmount},
- {Entry: mount.Entry{Dir: "/common/stuf"}, Action: mount.Keep},
- {Entry: mount.Entry{Dir: "/common/stuf/extra", Name: "/dev/sda2"}, Action: mount.Mount},
- })
-}
-
-// cur = ['/a/b', '/a/b-1', '/a/b-1/3', '/a/b/c']
-// des = ['/a/b', '/a/b-1', '/a/b/c'
-//
-// We are smart about comparing entries as directories. Here even though "/a/b"
-// is a prefix of "/a/b-1" it is correctly reused.
-func (s *changeSuite) TestNeededChangesSmartEntryComparison(c *C) {
- current := &mount.Profile{Entries: []mount.Entry{
- {Dir: "/a/b", Name: "/dev/sda1"},
- {Dir: "/a/b-1"},
- {Dir: "/a/b-1/3"},
- {Dir: "/a/b/c"},
- }}
- desired := &mount.Profile{Entries: []mount.Entry{
- {Dir: "/a/b", Name: "/dev/sda2"},
- {Dir: "/a/b-1"},
- {Dir: "/a/b/c"},
- }}
- changes := mount.NeededChanges(current, desired)
- c.Assert(changes, DeepEquals, []mount.Change{
- {Entry: mount.Entry{Dir: "/a/b/c"}, Action: mount.Unmount},
- {Entry: mount.Entry{Dir: "/a/b", Name: "/dev/sda1"}, Action: mount.Unmount},
- {Entry: mount.Entry{Dir: "/a/b-1/3"}, Action: mount.Unmount},
- {Entry: mount.Entry{Dir: "/a/b-1"}, Action: mount.Keep},
-
- {Entry: mount.Entry{Dir: "/a/b", Name: "/dev/sda2"}, Action: mount.Mount},
- {Entry: mount.Entry{Dir: "/a/b/c"}, Action: mount.Mount},
- })
-}
diff -Nru snapd-2.28.5/interfaces/mount/sorting.go snapd-2.29.3/interfaces/mount/sorting.go
--- snapd-2.28.5/interfaces/mount/sorting.go 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/interfaces/mount/sorting.go 1970-01-01 00:00:00.000000000 +0000
@@ -1,42 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2017 Canonical Ltd
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-
-package mount
-
-import (
- "strings"
-)
-
-// byMagicDir allows sorting an array of entries that automagically assumes
-// each entry ends with a trailing slash.
-type byMagicDir []Entry
-
-func (c byMagicDir) Len() int { return len(c) }
-func (c byMagicDir) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
-func (c byMagicDir) Less(i, j int) bool {
- iDir := c[i].Dir
- jDir := c[j].Dir
- if !strings.HasSuffix(iDir, "/") {
- iDir = iDir + "/"
- }
- if !strings.HasSuffix(jDir, "/") {
- jDir = jDir + "/"
- }
- return iDir < jDir
-}
diff -Nru snapd-2.28.5/interfaces/mount/sorting_test.go snapd-2.29.3/interfaces/mount/sorting_test.go
--- snapd-2.28.5/interfaces/mount/sorting_test.go 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/interfaces/mount/sorting_test.go 1970-01-01 00:00:00.000000000 +0000
@@ -1,48 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2017 Canonical Ltd
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-
-package mount
-
-import (
- "sort"
-
- . "gopkg.in/check.v1"
-)
-
-type sortSuite struct{}
-
-var _ = Suite(&sortSuite{})
-
-func (s *sortSuite) TestTrailingSlashesComparison(c *C) {
- // Naively sorted entries.
- entries := []Entry{
- {Dir: "/a/b"},
- {Dir: "/a/b-1"},
- {Dir: "/a/b-1/3"},
- {Dir: "/a/b/c"},
- }
- sort.Sort(byMagicDir(entries))
- // Entries sorted as if they had a trailing slash.
- c.Assert(entries, DeepEquals, []Entry{
- {Dir: "/a/b-1"},
- {Dir: "/a/b-1/3"},
- {Dir: "/a/b"},
- {Dir: "/a/b/c"},
- })
-}
diff -Nru snapd-2.28.5/interfaces/repo.go snapd-2.29.3/interfaces/repo.go
--- snapd-2.28.5/interfaces/repo.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/repo.go 2017-10-23 06:17:27.000000000 +0000
@@ -20,7 +20,6 @@
package interfaces
import (
- "bytes"
"fmt"
"sort"
"strings"
@@ -38,22 +37,24 @@
plugs map[string]map[string]*Plug
slots map[string]map[string]*Slot
// given a slot and a plug, are they connected?
- slotPlugs map[*Slot]map[*Plug]bool
+ slotPlugs map[*Slot]map[*Plug]*Connection
// given a plug and a slot, are they connected?
- plugSlots map[*Plug]map[*Slot]bool
+ plugSlots map[*Plug]map[*Slot]*Connection
backends map[SecuritySystem]SecurityBackend
}
// NewRepository creates an empty plug repository.
func NewRepository() *Repository {
- return &Repository{
+ repo := &Repository{
ifaces: make(map[string]Interface),
plugs: make(map[string]map[string]*Plug),
slots: make(map[string]map[string]*Slot),
- slotPlugs: make(map[*Slot]map[*Plug]bool),
- plugSlots: make(map[*Plug]map[*Slot]bool),
+ slotPlugs: make(map[*Slot]map[*Plug]*Connection),
+ plugSlots: make(map[*Plug]map[*Slot]*Connection),
backends: make(map[SecuritySystem]SecurityBackend),
}
+
+ return repo
}
// Interface returns an interface with a given name.
@@ -146,15 +147,15 @@
if opts != nil && opts.Connected {
connected = make(map[string]bool)
for _, plugMap := range r.slotPlugs {
- for plug, ok := range plugMap {
- if ok {
+ for plug, conn := range plugMap {
+ if conn != nil {
connected[plug.Interface] = true
}
}
}
for _, slotMap := range r.plugSlots {
- for slot, ok := range slotMap {
- if ok {
+ for slot, conn := range slotMap {
+ if conn != nil {
connected[slot.Interface] = true
}
}
@@ -519,7 +520,7 @@
return nil, fmt.Errorf("snap %q has no slot named %q", slotSnapName, slotName)
}
// Ensure that slot and plug are connected
- if !r.slotPlugs[slot][plug] {
+ if r.slotPlugs[slot][plug] == nil {
return nil, fmt.Errorf("cannot disconnect %s:%s from %s:%s, it is not connected",
plugSnapName, plugName, slotSnapName, slotName)
}
@@ -572,19 +573,20 @@
plugSnapName, plugName, plug.Interface, slotSnapName, slotName, slot.Interface)
}
// Ensure that slot and plug are not connected yet
- if r.slotPlugs[slot][plug] {
+ if r.slotPlugs[slot][plug] != nil {
// But if they are don't treat this as an error.
return nil
}
// Connect the plug
if r.slotPlugs[slot] == nil {
- r.slotPlugs[slot] = make(map[*Plug]bool)
+ r.slotPlugs[slot] = make(map[*Plug]*Connection)
}
if r.plugSlots[plug] == nil {
- r.plugSlots[plug] = make(map[*Slot]bool)
+ r.plugSlots[plug] = make(map[*Slot]*Connection)
}
- r.slotPlugs[slot][plug] = true
- r.plugSlots[plug][slot] = true
+ conn := &Connection{plugInfo: plug.PlugInfo, slotInfo: slot.SlotInfo}
+ r.slotPlugs[slot][plug] = conn
+ r.plugSlots[plug][slot] = conn
slot.Connections = append(slot.Connections, PlugRef{plug.Snap.Name(), plug.Name})
plug.Connections = append(plug.Connections, SlotRef{slot.Snap.Name(), slot.Name})
return nil
@@ -624,7 +626,7 @@
return fmt.Errorf("snap %q has no slot named %q", slotSnapName, slotName)
}
// Ensure that slot and plug are connected
- if !r.slotPlugs[slot][plug] {
+ if r.slotPlugs[slot][plug] == nil {
return fmt.Errorf("cannot disconnect %s:%s from %s:%s, it is not connected",
plugSnapName, plugName, slotSnapName, slotName)
}
@@ -814,39 +816,6 @@
return spec, nil
}
-// BadInterfacesError is returned when some snap interfaces could not be registered.
-// Those interfaces not mentioned in the error were successfully registered.
-type BadInterfacesError struct {
- snap string
- issues map[string]string // slot or plug name => message
-}
-
-func (e *BadInterfacesError) Error() string {
- inverted := make(map[string][]string)
- for name, reason := range e.issues {
- inverted[reason] = append(inverted[reason], name)
- }
- var buf bytes.Buffer
- fmt.Fprintf(&buf, "snap %q has bad plugs or slots: ", e.snap)
- reasons := make([]string, 0, len(inverted))
- for reason := range inverted {
- reasons = append(reasons, reason)
- }
- sort.Strings(reasons)
- for _, reason := range reasons {
- names := inverted[reason]
- sort.Strings(names)
- for i, name := range names {
- if i > 0 {
- buf.WriteString(", ")
- }
- buf.WriteString(name)
- }
- fmt.Fprintf(&buf, " (%s); ", reason)
- }
- return strings.TrimSuffix(buf.String(), "; ")
-}
-
// AddSnap adds plugs and slots declared by the given snap to the repository.
//
// This function can be used to implement snap install or, when used along with
@@ -875,58 +844,21 @@
return fmt.Errorf("cannot register interfaces for snap %q more than once", snapName)
}
- bad := BadInterfacesError{
- snap: snapName,
- issues: make(map[string]string),
- }
-
for plugName, plugInfo := range snapInfo.Plugs {
- iface, ok := r.ifaces[plugInfo.Interface]
- if !ok {
- bad.issues[plugName] = "unknown interface"
- continue
- }
- // Reject plug with invalid name
- if err := ValidateName(plugName); err != nil {
- bad.issues[plugName] = err.Error()
- continue
- }
- plug := &Plug{PlugInfo: plugInfo}
- if err := plug.Sanitize(iface); err != nil {
- bad.issues[plugName] = err.Error()
- continue
- }
if r.plugs[snapName] == nil {
r.plugs[snapName] = make(map[string]*Plug)
}
+ plug := &Plug{PlugInfo: plugInfo}
r.plugs[snapName][plugName] = plug
}
for slotName, slotInfo := range snapInfo.Slots {
- iface, ok := r.ifaces[slotInfo.Interface]
- if !ok {
- bad.issues[slotName] = "unknown interface"
- continue
- }
- // Reject slot with invalid name
- if err := ValidateName(slotName); err != nil {
- bad.issues[slotName] = err.Error()
- continue
- }
- slot := &Slot{SlotInfo: slotInfo}
- if err := slot.Sanitize(iface); err != nil {
- bad.issues[slotName] = err.Error()
- continue
- }
if r.slots[snapName] == nil {
r.slots[snapName] = make(map[string]*Slot)
}
+ slot := &Slot{SlotInfo: slotInfo}
r.slots[snapName][slotName] = slot
}
-
- if len(bad.issues) > 0 {
- return &bad
- }
return nil
}
diff -Nru snapd-2.28.5/interfaces/repo_test.go snapd-2.29.3/interfaces/repo_test.go
--- snapd-2.28.5/interfaces/repo_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/interfaces/repo_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -1539,38 +1539,6 @@
c.Assert(err, IsNil)
}
-func (s *AddRemoveSuite) TestAddSnapComplexErrorHandling(c *C) {
- err := s.repo.AddInterface(&ifacetest.TestInterface{
- InterfaceName: "invalid-plug-iface",
- SanitizePlugCallback: func(plug *Plug) error { return fmt.Errorf("plug is invalid") },
- SanitizeSlotCallback: func(slot *Slot) error { return fmt.Errorf("slot is invalid") },
- })
- c.Assert(err, IsNil)
- err = s.repo.AddInterface(&ifacetest.TestInterface{
- InterfaceName: "invalid-slot-iface",
- SanitizePlugCallback: func(plug *Plug) error { return fmt.Errorf("plug is invalid") },
- SanitizeSlotCallback: func(slot *Slot) error { return fmt.Errorf("slot is invalid") },
- })
- c.Assert(err, IsNil)
- snapInfo := snaptest.MockInfo(c, `
-name: complex
-plugs:
- invalid-plug-iface:
- unknown-plug-iface:
-slots:
- invalid-slot-iface:
- unknown-slot-iface:
-`, nil)
- err = s.repo.AddSnap(snapInfo)
- c.Check(err, ErrorMatches,
- `snap "complex" has bad plugs or slots: invalid-plug-iface \(plug is invalid\); invalid-slot-iface \(slot is invalid\); unknown-plug-iface, unknown-slot-iface \(unknown interface\)`)
- // Nothing was added
- c.Check(s.repo.Plug("complex", "invalid-plug-iface"), IsNil)
- c.Check(s.repo.Plug("complex", "unknown-plug-iface"), IsNil)
- c.Check(s.repo.Slot("complex", "invalid-slot-iface"), IsNil)
- c.Check(s.repo.Slot("complex", "unknown-slot-iface"), IsNil)
-}
-
const testConsumerYaml = `
name: consumer
apps:
@@ -1616,18 +1584,6 @@
c.Assert(s.repo.Plug("consumer", "iface"), Not(IsNil))
}
-func (s *AddRemoveSuite) TestAddSnapErrorsOnInvalidSlotNames(c *C) {
- _, err := s.addSnap(c, testConsumerInvalidSlotNameYaml)
- c.Assert(err, NotNil)
- c.Check(err, ErrorMatches, `snap "consumer" has bad plugs or slots: ttyS5 \(invalid interface name: "ttyS5"\)`)
-}
-
-func (s *AddRemoveSuite) TestAddSnapErrorsOnInvalidPlugNames(c *C) {
- _, err := s.addSnap(c, testConsumerInvalidPlugNameYaml)
- c.Assert(err, NotNil)
- c.Check(err, ErrorMatches, `snap "consumer" has bad plugs or slots: ttyS3 \(invalid interface name: "ttyS3"\)`)
-}
-
func (s *AddRemoveSuite) TestAddSnapErrorsOnExistingSnapPlugs(c *C) {
_, err := s.addSnap(c, testConsumerYaml)
c.Assert(err, IsNil)
diff -Nru snapd-2.28.5/interfaces/systemd/backend_test.go snapd-2.29.3/interfaces/systemd/backend_test.go
--- snapd-2.28.5/interfaces/systemd/backend_test.go 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/interfaces/systemd/backend_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -29,14 +29,15 @@
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/interfaces/ifacetest"
"github.com/snapcore/snapd/interfaces/systemd"
- "github.com/snapcore/snapd/testutil"
sysd "github.com/snapcore/snapd/systemd"
)
type backendSuite struct {
ifacetest.BackendSuite
- systemctlCmd *testutil.MockCmd
+
+ systemctlArgs [][]string
+ systemctlRestorer func()
}
var _ = Suite(&backendSuite{})
@@ -52,11 +53,14 @@
s.Backend = &systemd.Backend{}
s.BackendSuite.SetUpTest(c)
c.Assert(s.Repo.AddBackend(s.Backend), IsNil)
- s.systemctlCmd = testutil.MockCommand(c, "systemctl", "echo ActiveState=inactive")
+ s.systemctlRestorer = sysd.MockSystemctl(func(args ...string) ([]byte, error) {
+ s.systemctlArgs = append(s.systemctlArgs, append([]string{"systemctl"}, args...))
+ return []byte("ActiveState=inactive"), nil
+ })
}
func (s *backendSuite) TearDownTest(c *C) {
- s.systemctlCmd.Restore()
+ s.systemctlRestorer()
s.BackendSuite.TearDownTest(c)
}
@@ -65,17 +69,17 @@
}
func (s *backendSuite) TestInstallingSnapWritesStartsServices(c *C) {
- prevctlCmd := sysd.SystemctlCmd
- defer func() { sysd.SystemctlCmd = prevctlCmd }()
-
var sysdLog [][]string
- sysd.SystemctlCmd = func(cmd ...string) ([]byte, error) {
+
+ r := sysd.MockSystemctl(func(cmd ...string) ([]byte, error) {
sysdLog = append(sysdLog, cmd)
if cmd[0] == "show" {
return []byte("ActiveState=inactive\n"), nil
}
return []byte{}, nil
- }
+ })
+ defer r()
+
s.Iface.SystemdPermanentSlotCallback = func(spec *systemd.Specification, slot *interfaces.Slot) error {
return spec.AddService("snap.samba.interface.foo.service", &systemd.Service{ExecStart: "/bin/true"})
}
@@ -100,14 +104,14 @@
}
for _, opts := range testedConfinementOpts {
snapInfo := s.InstallSnap(c, opts, ifacetest.SambaYamlV1, 1)
- s.systemctlCmd.ForgetCalls()
+ s.systemctlArgs = nil
s.RemoveSnap(c, snapInfo)
service := filepath.Join(dirs.SnapServicesDir, "snap.samba.interface.foo.service")
// the service file was removed
_, err := os.Stat(service)
c.Check(os.IsNotExist(err), Equals, true)
// the service was stopped
- c.Check(s.systemctlCmd.Calls(), DeepEquals, [][]string{
+ c.Check(s.systemctlArgs, DeepEquals, [][]string{
{"systemctl", "--root", dirs.GlobalRootDir, "disable", "snap.samba.interface.foo.service"},
{"systemctl", "stop", "snap.samba.interface.foo.service"},
{"systemctl", "show", "--property=ActiveState", "snap.samba.interface.foo.service"},
@@ -125,7 +129,7 @@
return spec.AddService("snap.samba.interface.bar.service", &systemd.Service{ExecStart: "/bin/false"})
}
snapInfo := s.InstallSnap(c, interfaces.ConfinementOptions{}, ifacetest.SambaYamlV1, 1)
- s.systemctlCmd.ForgetCalls()
+ s.systemctlArgs = nil
serviceFoo := filepath.Join(dirs.SnapServicesDir, "snap.samba.interface.foo.service")
serviceBar := filepath.Join(dirs.SnapServicesDir, "snap.samba.interface.bar.service")
// the services were created
@@ -141,7 +145,7 @@
// Update over to the same snap to regenerate security
s.UpdateSnap(c, snapInfo, interfaces.ConfinementOptions{}, ifacetest.SambaYamlV1, 0)
// The bar service should have been stopped
- c.Check(s.systemctlCmd.Calls(), DeepEquals, [][]string{
+ c.Check(s.systemctlArgs, DeepEquals, [][]string{
{"systemctl", "--root", dirs.GlobalRootDir, "disable", "snap.samba.interface.bar.service"},
{"systemctl", "stop", "snap.samba.interface.bar.service"},
{"systemctl", "show", "--property=ActiveState", "snap.samba.interface.bar.service"},
diff -Nru snapd-2.28.5/interfaces/udev/backend.go snapd-2.29.3/interfaces/udev/backend.go
--- snapd-2.28.5/interfaces/udev/backend.go 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/interfaces/udev/backend.go 2017-11-02 19:49:21.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
@@ -17,8 +17,8 @@
*
*/
-// Package udev implements integration between snappy, udev and
-// ubuntu-core-laucher around tagging character and block devices so that they
+// Package udev implements integration between snapd, udev and
+// snap-confine around tagging character and block devices so that they
// can be accessed by applications.
//
// TODO: Document this better
@@ -29,6 +29,7 @@
"fmt"
"os"
"path/filepath"
+ "strings"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/interfaces"
@@ -85,7 +86,14 @@
var buffer bytes.Buffer
buffer.WriteString("# This file is automatically generated.\n")
+ if (opts.DevMode || opts.Classic) && !opts.JailMode {
+ buffer.WriteString("# udev tagging/device cgroups disabled with non-strict mode snaps\n")
+ }
for _, snippet := range content {
+ if (opts.DevMode || opts.Classic) && !opts.JailMode {
+ buffer.WriteRune('#')
+ snippet = strings.Replace(snippet, "\n", "\n#", -1)
+ }
buffer.WriteString(snippet)
buffer.WriteByte('\n')
}
diff -Nru snapd-2.28.5/interfaces/udev/backend_test.go snapd-2.29.3/interfaces/udev/backend_test.go
--- snapd-2.28.5/interfaces/udev/backend_test.go 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/interfaces/udev/backend_test.go 2017-11-02 19:49:21.000000000 +0000
@@ -285,7 +285,11 @@
fname := filepath.Join(dirs.SnapUdevRulesDir, "70-snap.samba.rules")
data, err := ioutil.ReadFile(fname)
c.Assert(err, IsNil)
- c.Check(string(data), Equals, "# This file is automatically generated.\ndummy\n")
+ if opts.DevMode || opts.Classic {
+ c.Check(string(data), Equals, "# This file is automatically generated.\n# udev tagging/device cgroups disabled with non-strict mode snaps\n#dummy\n")
+ } else {
+ c.Check(string(data), Equals, "# This file is automatically generated.\ndummy\n")
+ }
stat, err := os.Stat(fname)
c.Assert(err, IsNil)
c.Check(stat.Mode(), Equals, os.FileMode(0644))
@@ -293,6 +297,28 @@
}
}
+func (s *backendSuite) TestCombineSnippetsWithActualSnippetsWithNewline(c *C) {
+ // NOTE: Hand out a permanent snippet so that .rules file is generated.
+ s.Iface.UDevPermanentSlotCallback = func(spec *udev.Specification, slot *interfaces.Slot) error {
+ spec.AddSnippet("dummy1\ndummy2")
+ return nil
+ }
+ for _, opts := range testedConfinementOpts {
+ snapInfo := s.InstallSnap(c, opts, ifacetest.SambaYamlV1, 0)
+ fname := filepath.Join(dirs.SnapUdevRulesDir, "70-snap.samba.rules")
+ data, err := ioutil.ReadFile(fname)
+ c.Assert(err, IsNil)
+ if opts.DevMode || opts.Classic {
+ c.Check(string(data), Equals, "# This file is automatically generated.\n# udev tagging/device cgroups disabled with non-strict mode snaps\n#dummy1\n#dummy2\n")
+ } else {
+ c.Check(string(data), Equals, "# This file is automatically generated.\ndummy1\ndummy2\n")
+ }
+ stat, err := os.Stat(fname)
+ c.Assert(err, IsNil)
+ c.Check(stat.Mode(), Equals, os.FileMode(0644))
+ s.RemoveSnap(c, snapInfo)
+ }
+}
func (s *backendSuite) TestCombineSnippetsWithActualSnippetsWhenPlugNoApps(c *C) {
// NOTE: Hand out a permanent snippet so that .rules file is generated.
s.Iface.UDevPermanentPlugCallback = func(spec *udev.Specification, slot *interfaces.Plug) error {
@@ -304,7 +330,11 @@
fname := filepath.Join(dirs.SnapUdevRulesDir, "70-snap.foo.rules")
data, err := ioutil.ReadFile(fname)
c.Assert(err, IsNil)
- c.Check(string(data), Equals, "# This file is automatically generated.\ndummy\n")
+ if opts.DevMode || opts.Classic {
+ c.Check(string(data), Equals, "# This file is automatically generated.\n# udev tagging/device cgroups disabled with non-strict mode snaps\n#dummy\n")
+ } else {
+ c.Check(string(data), Equals, "# This file is automatically generated.\ndummy\n")
+ }
stat, err := os.Stat(fname)
c.Assert(err, IsNil)
c.Check(stat.Mode(), Equals, os.FileMode(0644))
@@ -323,7 +353,11 @@
fname := filepath.Join(dirs.SnapUdevRulesDir, "70-snap.foo.rules")
data, err := ioutil.ReadFile(fname)
c.Assert(err, IsNil)
- c.Check(string(data), Equals, "# This file is automatically generated.\ndummy\n")
+ if opts.DevMode || opts.Classic {
+ c.Check(string(data), Equals, "# This file is automatically generated.\n# udev tagging/device cgroups disabled with non-strict mode snaps\n#dummy\n")
+ } else {
+ c.Check(string(data), Equals, "# This file is automatically generated.\ndummy\n")
+ }
stat, err := os.Stat(fname)
c.Assert(err, IsNil)
c.Check(stat.Mode(), Equals, os.FileMode(0644))
diff -Nru snapd-2.28.5/interfaces/udev/spec.go snapd-2.29.3/interfaces/udev/spec.go
--- snapd-2.28.5/interfaces/udev/spec.go 2017-08-18 13:48:10.000000000 +0000
+++ snapd-2.29.3/interfaces/udev/spec.go 2017-11-09 12:08:52.000000000 +0000
@@ -20,7 +20,9 @@
package udev
import (
+ "fmt"
"sort"
+ "strings"
"github.com/snapcore/snapd/interfaces"
)
@@ -28,7 +30,8 @@
// Specification assists in collecting udev snippets associated with an interface.
type Specification struct {
// Snippets are stored in a map for de-duplication
- snippets map[string]bool
+ snippets map[string]bool
+ securityTags []string
}
// AddSnippet adds a new udev snippet.
@@ -39,6 +42,17 @@
spec.snippets[snippet] = true
}
+func udevTag(securityTag string) string {
+ return strings.Replace(securityTag, ".", "_", -1)
+}
+
+// TagDevice adds an app/hook specific udev tag to devices described by the snippet.
+func (spec *Specification) TagDevice(snippet string) {
+ for _, securityTag := range spec.securityTags {
+ spec.AddSnippet(fmt.Sprintf(`%s, TAG+="%s"`, snippet, udevTag(securityTag)))
+ }
+}
+
// Snippets returns a copy of all the snippets added so far.
func (spec *Specification) Snippets() (result []string) {
for k := range spec.snippets {
@@ -56,6 +70,8 @@
UDevConnectedPlug(spec *Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error
}
if iface, ok := iface.(definer); ok {
+ spec.securityTags = plug.SecurityTags()
+ defer func() { spec.securityTags = nil }()
return iface.UDevConnectedPlug(spec, plug, plugAttrs, slot, slotAttrs)
}
return nil
@@ -67,6 +83,8 @@
UDevConnectedSlot(spec *Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error
}
if iface, ok := iface.(definer); ok {
+ spec.securityTags = slot.SecurityTags()
+ defer func() { spec.securityTags = nil }()
return iface.UDevConnectedSlot(spec, plug, plugAttrs, slot, slotAttrs)
}
return nil
@@ -78,6 +96,8 @@
UDevPermanentPlug(spec *Specification, plug *interfaces.Plug) error
}
if iface, ok := iface.(definer); ok {
+ spec.securityTags = plug.SecurityTags()
+ defer func() { spec.securityTags = nil }()
return iface.UDevPermanentPlug(spec, plug)
}
return nil
@@ -89,6 +109,8 @@
UDevPermanentSlot(spec *Specification, slot *interfaces.Slot) error
}
if iface, ok := iface.(definer); ok {
+ spec.securityTags = slot.SecurityTags()
+ defer func() { spec.securityTags = nil }()
return iface.UDevPermanentSlot(spec, slot)
}
return nil
diff -Nru snapd-2.28.5/interfaces/udev/spec_test.go snapd-2.29.3/interfaces/udev/spec_test.go
--- snapd-2.28.5/interfaces/udev/spec_test.go 2017-08-18 13:48:10.000000000 +0000
+++ snapd-2.29.3/interfaces/udev/spec_test.go 2017-11-09 12:08:52.000000000 +0000
@@ -25,7 +25,7 @@
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/interfaces/ifacetest"
"github.com/snapcore/snapd/interfaces/udev"
- "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/snaptest"
)
type specSuite struct {
@@ -55,22 +55,28 @@
return nil
},
},
- plug: &interfaces.Plug{
- PlugInfo: &snap.PlugInfo{
- Snap: &snap.Info{SuggestedName: "snap1"},
- Name: "name",
- Interface: "test",
- },
- },
- slot: &interfaces.Slot{
- SlotInfo: &snap.SlotInfo{
- Snap: &snap.Info{SuggestedName: "snap2"},
- Name: "name",
- Interface: "test",
- },
- },
})
+func (s *specSuite) SetUpSuite(c *C) {
+ info1 := snaptest.MockInfo(c, `name: snap1
+plugs:
+ name:
+ interface: test
+apps:
+ foo:
+ command: bin/foo
+hooks:
+ configure:
+`, nil)
+ info2 := snaptest.MockInfo(c, `name: snap2
+slots:
+ name:
+ interface: test
+`, nil)
+ s.plug = &interfaces.Plug{PlugInfo: info1.Plugs["name"]}
+ s.slot = &interfaces.Slot{SlotInfo: info2.Slots["name"]}
+}
+
func (s *specSuite) SetUpTest(c *C) {
s.spec = &udev.Specification{}
}
@@ -80,6 +86,24 @@
c.Assert(s.spec.Snippets(), DeepEquals, []string{"foo"})
}
+func (s *specSuite) TestTagDevice(c *C) {
+ // TagDevice acts in the scope of the plug/slot (as appropriate) and
+ // affects all of the apps and hooks related to the given plug or slot
+ // (with the exception that slots cannot have hooks).
+ iface := &ifacetest.TestInterface{
+ InterfaceName: "test",
+ UDevConnectedPlugCallback: func(spec *udev.Specification, plug *interfaces.Plug, plugAttrs map[string]interface{}, slot *interfaces.Slot, slotAttrs map[string]interface{}) error {
+ spec.TagDevice(`kernel="voodoo"`)
+ return nil
+ },
+ }
+ c.Assert(s.spec.AddConnectedPlug(iface, s.plug, nil, s.slot, nil), IsNil)
+ c.Assert(s.spec.Snippets(), DeepEquals, []string{
+ `kernel="voodoo", TAG+="snap_snap1_foo"`,
+ `kernel="voodoo", TAG+="snap_snap1_hook_configure"`,
+ })
+}
+
// The spec.Specification can be used through the interfaces.Specification interface
func (s *specSuite) TestSpecificationIface(c *C) {
var r interfaces.Specification = s.spec
diff -Nru snapd-2.28.5/logger/logger.go snapd-2.29.3/logger/logger.go
--- snapd-2.28.5/logger/logger.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/logger/logger.go 2017-10-23 06:17:27.000000000 +0000
@@ -20,6 +20,7 @@
package logger
import (
+ "bytes"
"fmt"
"io"
"log"
@@ -86,6 +87,21 @@
logger.Debug(msg)
}
+// MockLogger replaces the exiting logger with a buffer and returns
+// the log buffer and a restore function.
+func MockLogger() (buf *bytes.Buffer, restore func()) {
+ buf = &bytes.Buffer{}
+ oldLogger := logger
+ l, err := New(buf, DefaultFlags)
+ if err != nil {
+ panic(err)
+ }
+ SetLogger(l)
+ return buf, func() {
+ SetLogger(oldLogger)
+ }
+}
+
// SetLogger sets the global logger to the given one
func SetLogger(l Logger) {
lock.Lock()
diff -Nru snapd-2.28.5/logger/logger_test.go snapd-2.29.3/logger/logger_test.go
--- snapd-2.28.5/logger/logger_test.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/logger/logger_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -36,16 +36,16 @@
var _ = Suite(&LogSuite{})
type LogSuite struct {
- oldLogger logger.Logger
+ logbuf *bytes.Buffer
+ restoreLogger func()
}
func (s *LogSuite) SetUpTest(c *C) {
- s.oldLogger = logger.GetLogger()
- logger.SetLogger(logger.NullLogger)
+ s.logbuf, s.restoreLogger = logger.MockLogger()
}
func (s *LogSuite) TearDownTest(c *C) {
- logger.SetLogger(s.oldLogger)
+ s.restoreLogger()
}
func (s *LogSuite) TestDefault(c *C) {
@@ -67,48 +67,24 @@
}
func (s *LogSuite) TestDebugf(c *C) {
- var logbuf bytes.Buffer
- l, err := logger.New(&logbuf, logger.DefaultFlags)
- c.Assert(err, IsNil)
-
- logger.SetLogger(l)
-
logger.Debugf("xyzzy")
- c.Check(logbuf.String(), Equals, "")
+ c.Check(s.logbuf.String(), Equals, "")
}
func (s *LogSuite) TestDebugfEnv(c *C) {
- var logbuf bytes.Buffer
- l, err := logger.New(&logbuf, logger.DefaultFlags)
- c.Assert(err, IsNil)
-
- logger.SetLogger(l)
-
os.Setenv("SNAPD_DEBUG", "1")
defer os.Unsetenv("SNAPD_DEBUG")
logger.Debugf("xyzzy")
- c.Check(logbuf.String(), testutil.Contains, `DEBUG: xyzzy`)
+ c.Check(s.logbuf.String(), testutil.Contains, `DEBUG: xyzzy`)
}
func (s *LogSuite) TestNoticef(c *C) {
- var logbuf bytes.Buffer
- l, err := logger.New(&logbuf, logger.DefaultFlags)
- c.Assert(err, IsNil)
-
- logger.SetLogger(l)
-
logger.Noticef("xyzzy")
- c.Check(logbuf.String(), Matches, `(?m).*logger_test\.go:\d+: xyzzy`)
+ c.Check(s.logbuf.String(), Matches, `(?m).*logger_test\.go:\d+: xyzzy`)
}
func (s *LogSuite) TestPanicf(c *C) {
- var logbuf bytes.Buffer
- l, err := logger.New(&logbuf, logger.DefaultFlags)
- c.Assert(err, IsNil)
-
- logger.SetLogger(l)
-
c.Check(func() { logger.Panicf("xyzzy") }, Panics, "xyzzy")
- c.Check(logbuf.String(), Matches, `(?m).*logger_test\.go:\d+: PANIC xyzzy`)
+ c.Check(s.logbuf.String(), Matches, `(?m).*logger_test\.go:\d+: PANIC xyzzy`)
}
diff -Nru snapd-2.28.5/osutil/cp_test.go snapd-2.29.3/osutil/cp_test.go
--- snapd-2.28.5/osutil/cp_test.go 2016-09-09 06:45:37.000000000 +0000
+++ snapd-2.29.3/osutil/cp_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -63,7 +63,7 @@
}
}
- return
+ return err
}
func (s *cpSuite) SetUpTest(c *C) {
diff -Nru snapd-2.28.5/osutil/env.go snapd-2.29.3/osutil/env.go
--- snapd-2.28.5/osutil/env.go 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/osutil/env.go 2017-10-23 06:17:27.000000000 +0000
@@ -65,6 +65,19 @@
return 0
}
+// EnvMap takes a list of "key=value" strings and transforms them into
+// a map.
+func EnvMap(env []string) map[string]string {
+ out := make(map[string]string, len(env))
+ for _, kv := range env {
+ l := strings.SplitN(kv, "=", 2)
+ if len(l) == 2 {
+ out[l[0]] = l[1]
+ }
+ }
+ return out
+}
+
// SubstituteEnv takes a list of environment strings like:
// - K1=BAR
// - K2=$K1
diff -Nru snapd-2.28.5/osutil/env_test.go snapd-2.29.3/osutil/env_test.go
--- snapd-2.28.5/osutil/env_test.go 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/osutil/env_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -132,3 +132,31 @@
c.Check(strings.Join(env, ","), check.DeepEquals, t.expected, check.Commentf("invalid result for %q, got %q expected %q", t.env, env, t.expected))
}
}
+
+func (s *envSuite) TestEnvMap(c *check.C) {
+ for _, t := range []struct {
+ env []string
+ expected map[string]string
+ }{
+ {
+ []string{"K=V"},
+ map[string]string{"K": "V"},
+ },
+ {
+ []string{"K=V=V=V"},
+ map[string]string{"K": "V=V=V"},
+ },
+ {
+ []string{"K1=V1", "K2=V2"},
+ map[string]string{"K1": "V1", "K2": "V2"},
+ },
+ {
+ // invalid input is handled gracefully
+ []string{"KEY_ONLY"},
+ map[string]string{},
+ },
+ } {
+ m := osutil.EnvMap(t.env)
+ c.Check(m, check.DeepEquals, t.expected, check.Commentf("invalid result for %q, got %q expected %q", t.env, m, t.expected))
+ }
+}
diff -Nru snapd-2.28.5/osutil/exec.go snapd-2.29.3/osutil/exec.go
--- snapd-2.28.5/osutil/exec.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/osutil/exec.go 2017-10-23 06:17:27.000000000 +0000
@@ -147,11 +147,11 @@
cmdWaitTimeout = 5 * time.Second
)
-// killProcessGroup kills the process group associated with the given command.
+// KillProcessGroup kills the process group associated with the given command.
//
// If the command hasn't had Setpgid set in its SysProcAttr, you'll probably end
// up killing yourself.
-func killProcessGroup(cmd *exec.Cmd) error {
+func KillProcessGroup(cmd *exec.Cmd) error {
pgid, err := syscallGetpgid(cmd.Process.Pid)
if err != nil {
return err
@@ -220,7 +220,7 @@
// select above exited which means that aborted or killTimeout
// was reached. Kill the command and wait for command.Wait()
// to clean it up (but limit the wait with the cmdWaitTimer)
- if err := killProcessGroup(command); err != nil {
+ if err := KillProcessGroup(command); err != nil {
return nil, fmt.Errorf("cannot abort: %s", err)
}
select {
diff -Nru snapd-2.28.5/osutil/export_test.go snapd-2.29.3/osutil/export_test.go
--- snapd-2.28.5/osutil/export_test.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/osutil/export_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -21,6 +21,7 @@
import (
"io"
+ "os"
"os/exec"
"os/user"
"syscall"
@@ -79,9 +80,27 @@
}
}
-var KillProcessGroup = killProcessGroup
-
func WaitingReaderGuts(r io.Reader) (io.Reader, *exec.Cmd) {
wr := r.(*waitingReader)
return wr.reader, wr.cmd
}
+
+func MockChown(f func(*os.File, int, int) error) func() {
+ oldChown := chown
+ chown = f
+ return func() {
+ chown = oldChown
+ }
+}
+
+func SetAtomicFileRenamed(aw *AtomicFile, renamed bool) {
+ aw.renamed = renamed
+}
+
+func SetUnsafeIO(b bool) func() {
+ oldSnapdUnsafeIO := snapdUnsafeIO
+ snapdUnsafeIO = b
+ return func() {
+ snapdUnsafeIO = oldSnapdUnsafeIO
+ }
+}
diff -Nru snapd-2.28.5/osutil/group.go snapd-2.29.3/osutil/group.go
--- snapd-2.28.5/osutil/group.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/osutil/group.go 2017-10-23 06:17:27.000000000 +0000
@@ -0,0 +1,158 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2017 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package osutil
+
+// #include
+// #include
+// #include
+// #include
+import "C"
+
+import (
+ "fmt"
+ "os/user"
+ "strconv"
+ "syscall"
+ "unsafe"
+)
+
+// 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.getgrnam_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
+
+// FindUid returns the identifier of the given UNIX user name.
+func FindUid(username string) (uint64, error) {
+ user, err := user.Lookup(username)
+ if err != nil {
+ return 0, err
+ }
+
+ return strconv.ParseUint(user.Uid, 10, 64)
+}
+
+// FindGid returns the identifier of the given UNIX group name.
+func FindGid(group string) (uint64, error) {
+ // In golang 1.8 we can use the built-in function like this:
+ //group, err := user.LookupGroup(group)
+ group, err := lookupGroup(group)
+ if err != nil {
+ return 0, err
+ }
+
+ // In golang 1.8 we can parse the group.Gid string instead.
+ //return strconv.ParseUint(group.Gid, 10, 64)
+ return strconv.ParseUint(group, 10, 64)
+}
diff -Nru snapd-2.28.5/osutil/io.go snapd-2.29.3/osutil/io.go
--- snapd-2.28.5/osutil/io.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/osutil/io.go 2017-10-23 06:17:27.000000000 +0000
@@ -20,7 +20,9 @@
package osutil
import (
+ "bytes"
"errors"
+ "io"
"os"
"path/filepath"
"strings"
@@ -39,18 +41,45 @@
// 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")
+var snapdUnsafeIO bool = len(os.Args) > 0 && strings.HasSuffix(os.Args[0], ".test") && GetenvBool("SNAPD_UNSAFE_IO", true)
-// AtomicWriteFile updates the filename atomically and works otherwise
-// like io/ioutil.WriteFile()
-//
-// Note that it won't follow symlinks and will replace existing symlinks
-// with the real file
-func AtomicWriteFile(filename string, data []byte, perm os.FileMode, flags AtomicWriteFlags) (err error) {
- return AtomicWriteFileChown(filename, data, perm, flags, -1, -1)
+// An AtomicFile is similar to an os.File but it has an additional
+// Commit() method that does whatever needs to be done so the
+// modification is "atomic": an AtomicFile will do its best to leave
+// either the previous content or the new content in permanent
+// storage. It also has a Cancel() method to abort and clean up.
+type AtomicFile struct {
+ *os.File
+
+ target string
+ tmpname string
+ uid int
+ gid int
+ closed bool
+ renamed bool
}
-func AtomicWriteFileChown(filename string, data []byte, perm os.FileMode, flags AtomicWriteFlags, uid, gid int) (err error) {
+// NewAtomicFile builds an AtomicFile backed by an *os.File that will have
+// the given filename, permissions and uid/gid when Committed.
+//
+// It _might_ be implemented using O_TMPFILE (see open(2)).
+//
+// Note that it won't follow symlinks and will replace existing symlinks with
+// the real file, unless the AtomicWriteFollow flag is specified.
+//
+// It is the caller's responsibility to clean up on error, by calling Cancel().
+//
+// It is also the caller's responsibility to coordinate access to this, if it
+// is used from different goroutines.
+//
+// Also note that there are a number of scenarios where Commit fails and then
+// Cancel also fails. In all these scenarios your filesystem was probably in a
+// rather poor state. Good luck.
+func NewAtomicFile(filename string, perm os.FileMode, flags AtomicWriteFlags, uid, gid int) (aw *AtomicFile, err error) {
+ if (uid < 0) != (gid < 0) {
+ return nil, errors.New("internal error: AtomicFile needs none or both of uid and gid set")
+ }
+
if flags&AtomicWriteFollow != 0 {
if fn, err := os.Readlink(filename); err == nil || (fn != "" && os.IsNotExist(err)) {
if filepath.IsAbs(fn) {
@@ -62,53 +91,132 @@
}
tmp := filename + "." + strutil.MakeRandomString(12)
- // XXX: if go switches to use aio_fsync, we need to open the dir for writing
- dir, err := os.Open(filepath.Dir(filename))
- if err != nil {
- return err
- }
- defer dir.Close()
-
fd, err := os.OpenFile(tmp, os.O_WRONLY|os.O_CREATE|os.O_TRUNC|os.O_EXCL, perm)
if err != nil {
- return err
+ return nil, err
}
- defer func() {
- e := fd.Close()
- if err == nil {
- err = e
- }
- if err != nil {
- os.Remove(tmp)
- }
- }()
- // according to the docs, Write returns a non-nil error when n !=
- // len(b), so don't worry about short writes.
- if _, err := fd.Write(data); err != nil {
- return err
+ return &AtomicFile{
+ File: fd,
+ target: filename,
+ tmpname: tmp,
+ uid: uid,
+ gid: gid,
+ }, nil
+}
+
+// ErrCannotCancel means the Commit operation failed at the last step, and
+// your luck has run out.
+var ErrCannotCancel = errors.New("cannot cancel: file has already been renamed")
+
+func (aw *AtomicFile) Close() error {
+ aw.closed = true
+ return aw.File.Close()
+}
+
+// Cancel closes the AtomicWriter, and cleans up any artifacts. Cancel
+// can fail if Commit() was (even partially) successful, but calling
+// Cancel after a successful Commit does nothing beyond returning
+// error--so it's always safe to defer a Cancel().
+func (aw *AtomicFile) Cancel() error {
+ if aw.renamed {
+ return ErrCannotCancel
+ }
+
+ var e1, e2 error
+ if aw.tmpname != "" {
+ e1 = os.Remove(aw.tmpname)
+ }
+ if !aw.closed {
+ e2 = aw.Close()
+ }
+ if e1 != nil {
+ return e1
}
+ return e2
+}
+
+var chown = (*os.File).Chown
- if uid > -1 && gid > -1 {
- if err := fd.Chown(uid, gid); err != nil {
+// Commit the modification; make it permanent.
+//
+// If Commit succeeds, the writer is closed and further attempts to
+// write will fail. If Commit fails, the writer _might_ be closed;
+// Cancel() needs to be called to clean up.
+func (aw *AtomicFile) Commit() error {
+ if aw.uid > -1 && aw.gid > -1 {
+ if err := chown(aw.File, aw.uid, aw.gid); err != nil {
return err
}
- } else if uid > -1 || gid > -1 {
- return errors.New("internal error: AtomicWriteFileChown needs none or both of uid and gid set")
}
+ var dir *os.File
if !snapdUnsafeIO {
- if err := fd.Sync(); err != nil {
+ // XXX: if go switches to use aio_fsync, we need to open the dir for writing
+ d, err := os.Open(filepath.Dir(aw.target))
+ if err != nil {
+ return err
+ }
+ dir = d
+ defer dir.Close()
+
+ if err := aw.Sync(); err != nil {
return err
}
}
- if err := os.Rename(tmp, filename); err != nil {
+ if err := aw.Close(); err != nil {
+ return err
+ }
+
+ if err := os.Rename(aw.tmpname, aw.target); err != nil {
return err
}
+ aw.renamed = true // it is now too late to Cancel()
if !snapdUnsafeIO {
return dir.Sync()
}
+
return nil
}
+
+// The AtomicWrite* family of functions work like ioutil.WriteFile(), but the
+// file created is an AtomicWriter, which is Committed before returning.
+//
+// AtomicWriteChown and AtomicWriteFileChown take an uid and a gid that can be
+// used to specify the ownership of the created file. They must be both
+// non-negative (in which case chown is called), or both negative (in which
+// case it isn't).
+//
+// AtomicWriteFile and AtomicWriteFileChown take the content to be written as a
+// []byte, and so work exactly like io.WriteFile(); AtomicWrite and
+// AtomicWriteChown take an io.Reader which is copied into the file instead,
+// and so are more amenable to streaming.
+func AtomicWrite(filename string, reader io.Reader, perm os.FileMode, flags AtomicWriteFlags) (err error) {
+ return AtomicWriteChown(filename, reader, perm, flags, -1, -1)
+}
+
+func AtomicWriteFile(filename string, data []byte, perm os.FileMode, flags AtomicWriteFlags) (err error) {
+ return AtomicWriteChown(filename, bytes.NewReader(data), perm, flags, -1, -1)
+}
+
+func AtomicWriteFileChown(filename string, data []byte, perm os.FileMode, flags AtomicWriteFlags, uid, gid int) (err error) {
+ return AtomicWriteChown(filename, bytes.NewReader(data), perm, flags, uid, gid)
+}
+
+func AtomicWriteChown(filename string, reader io.Reader, perm os.FileMode, flags AtomicWriteFlags, uid, gid int) (err error) {
+ aw, err := NewAtomicFile(filename, perm, flags, uid, gid)
+ if err != nil {
+ return err
+ }
+
+ // Cancel once Committed is a NOP :-)
+ defer aw.Cancel()
+
+ if _, err := io.Copy(aw, reader); err != nil {
+ return err
+ }
+
+ return aw.Commit()
+}
diff -Nru snapd-2.28.5/osutil/io_test.go snapd-2.29.3/osutil/io_test.go
--- snapd-2.28.5/osutil/io_test.go 2017-09-11 07:15:11.000000000 +0000
+++ snapd-2.29.3/osutil/io_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -17,14 +17,16 @@
*
*/
-package osutil
+package osutil_test
import (
+ "errors"
"io/ioutil"
"math/rand"
"os"
"path/filepath"
+ "github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/strutil"
. "gopkg.in/check.v1"
@@ -38,7 +40,7 @@
tmpdir := c.MkDir()
p := filepath.Join(tmpdir, "foo")
- err := AtomicWriteFile(p, []byte("canary"), 0644, 0)
+ err := osutil.AtomicWriteFile(p, []byte("canary"), 0644, 0)
c.Assert(err, IsNil)
content, err := ioutil.ReadFile(p)
@@ -55,7 +57,7 @@
tmpdir := c.MkDir()
p := filepath.Join(tmpdir, "foo")
- err := AtomicWriteFile(p, []byte(""), 0600, 0)
+ err := osutil.AtomicWriteFile(p, []byte(""), 0600, 0)
c.Assert(err, IsNil)
st, err := os.Stat(p)
@@ -67,7 +69,7 @@
tmpdir := c.MkDir()
p := filepath.Join(tmpdir, "foo")
c.Assert(ioutil.WriteFile(p, []byte("hello"), 0644), IsNil)
- c.Assert(AtomicWriteFile(p, []byte("hi"), 0600, 0), IsNil)
+ c.Assert(osutil.AtomicWriteFile(p, []byte("hi"), 0600, 0), IsNil)
content, err := ioutil.ReadFile(p)
c.Assert(err, IsNil)
@@ -84,7 +86,7 @@
c.Assert(os.Chmod(rodir, 0500), IsNil)
defer os.Chmod(rodir, 0700)
- err := AtomicWriteFile(p, []byte("hi"), 0600, 0)
+ err := osutil.AtomicWriteFile(p, []byte("hi"), 0600, 0)
c.Assert(err, NotNil)
}
@@ -98,7 +100,7 @@
c.Assert(os.Chmod(rodir, 0500), IsNil)
defer os.Chmod(rodir, 0700)
- err := AtomicWriteFile(p, []byte("hi"), 0600, AtomicWriteFollow)
+ err := osutil.AtomicWriteFile(p, []byte("hi"), 0600, osutil.AtomicWriteFollow)
c.Assert(err, IsNil)
content, err := ioutil.ReadFile(p)
@@ -117,7 +119,7 @@
defer os.Chmod(rodir, 0700)
c.Assert(ioutil.WriteFile(s, []byte("hello"), 0644), IsNil)
- c.Assert(AtomicWriteFile(p, []byte("hi"), 0600, AtomicWriteFollow), IsNil)
+ c.Assert(osutil.AtomicWriteFile(p, []byte("hi"), 0600, osutil.AtomicWriteFollow), IsNil)
content, err := ioutil.ReadFile(p)
c.Assert(err, IsNil)
@@ -133,7 +135,7 @@
c.Assert(os.Chmod(rodir, 0500), IsNil)
defer os.Chmod(rodir, 0700)
- err := AtomicWriteFile(p, []byte("hi"), 0600, AtomicWriteFollow)
+ err := osutil.AtomicWriteFile(p, []byte("hi"), 0600, osutil.AtomicWriteFollow)
c.Assert(err, IsNil)
content, err := ioutil.ReadFile(p)
@@ -152,7 +154,7 @@
defer os.Chmod(rodir, 0700)
c.Assert(ioutil.WriteFile(s, []byte("hello"), 0644), IsNil)
- c.Assert(AtomicWriteFile(p, []byte("hi"), 0600, AtomicWriteFollow), IsNil)
+ c.Assert(osutil.AtomicWriteFile(p, []byte("hi"), 0600, osutil.AtomicWriteFollow), IsNil)
content, err := ioutil.ReadFile(p)
c.Assert(err, IsNil)
@@ -171,6 +173,100 @@
err := ioutil.WriteFile(p+"."+expectedRandomness, []byte(""), 0644)
c.Assert(err, IsNil)
- err = AtomicWriteFile(p, []byte(""), 0600, 0)
+ err = osutil.AtomicWriteFile(p, []byte(""), 0600, 0)
c.Assert(err, ErrorMatches, "open .*: file exists")
}
+
+func (ts *AtomicWriteTestSuite) TestAtomicFileUidGidError(c *C) {
+ d := c.MkDir()
+ p := filepath.Join(d, "foo")
+
+ _, err := osutil.NewAtomicFile(p, 0644, 0, -1, 1)
+ c.Check(err, ErrorMatches, ".*needs none or both of uid and gid set")
+}
+
+func (ts *AtomicWriteTestSuite) TestAtomicFileChownError(c *C) {
+ eUid := 42
+ eGid := 74
+ eErr := errors.New("this didn't work")
+ defer osutil.MockChown(func(fd *os.File, uid int, gid int) error {
+ c.Check(uid, Equals, eUid)
+ c.Check(gid, Equals, eGid)
+ return eErr
+ })()
+
+ d := c.MkDir()
+ p := filepath.Join(d, "foo")
+
+ aw, err := osutil.NewAtomicFile(p, 0644, 0, eUid, eGid)
+ c.Assert(err, IsNil)
+ defer aw.Cancel()
+
+ _, err = aw.Write([]byte("hello"))
+ c.Assert(err, IsNil)
+
+ c.Check(aw.Commit(), Equals, eErr)
+}
+
+func (ts *AtomicWriteTestSuite) TestAtomicFileCancelError(c *C) {
+ d := c.MkDir()
+ p := filepath.Join(d, "foo")
+ aw, err := osutil.NewAtomicFile(p, 0644, 0, -1, -1)
+ c.Assert(err, IsNil)
+
+ c.Assert(aw.File.Close(), IsNil)
+ // Depending on golang version the error is one of the two.
+ c.Check(aw.Cancel(), ErrorMatches, "invalid argument|file already closed")
+}
+
+func (ts *AtomicWriteTestSuite) TestAtomicFileCancelBadError(c *C) {
+ d := c.MkDir()
+ p := filepath.Join(d, "foo")
+ aw, err := osutil.NewAtomicFile(p, 0644, 0, -1, -1)
+ c.Assert(err, IsNil)
+ defer aw.Close()
+
+ osutil.SetAtomicFileRenamed(aw, true)
+
+ c.Check(aw.Cancel(), Equals, osutil.ErrCannotCancel)
+}
+
+func (ts *AtomicWriteTestSuite) TestAtomicFileCancelNoClose(c *C) {
+ d := c.MkDir()
+ p := filepath.Join(d, "foo")
+ aw, err := osutil.NewAtomicFile(p, 0644, 0, -1, -1)
+ c.Assert(err, IsNil)
+ c.Assert(aw.Close(), IsNil)
+
+ c.Check(aw.Cancel(), IsNil)
+}
+
+func (ts *AtomicWriteTestSuite) TestAtomicFileCancel(c *C) {
+ d := c.MkDir()
+ p := filepath.Join(d, "foo")
+
+ aw, err := osutil.NewAtomicFile(p, 0644, 0, -1, -1)
+ c.Assert(err, IsNil)
+ fn := aw.File.Name()
+ c.Check(osutil.FileExists(fn), Equals, true)
+ c.Check(aw.Cancel(), IsNil)
+ c.Check(osutil.FileExists(fn), Equals, false)
+}
+
+// SafeIoAtomicWriteTestSuite runs all AtomicWrite with safe
+// io enabled
+type SafeIoAtomicWriteTestSuite struct {
+ AtomicWriteTestSuite
+
+ restoreUnsafeIO func()
+}
+
+var _ = Suite(&SafeIoAtomicWriteTestSuite{})
+
+func (s *SafeIoAtomicWriteTestSuite) SetUpSuite(c *C) {
+ s.restoreUnsafeIO = osutil.SetUnsafeIO(false)
+}
+
+func (s *SafeIoAtomicWriteTestSuite) TearDownSuite(c *C) {
+ s.restoreUnsafeIO()
+}
diff -Nru snapd-2.28.5/osutil/osutil_test.go snapd-2.29.3/osutil/osutil_test.go
--- snapd-2.28.5/osutil/osutil_test.go 2016-08-11 17:27:59.000000000 +0000
+++ snapd-2.29.3/osutil/osutil_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -1,3 +1,22 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2017 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
package osutil_test
import (
diff -Nru snapd-2.28.5/overlord/assertstate/assertmgr.go snapd-2.29.3/overlord/assertstate/assertmgr.go
--- snapd-2.28.5/overlord/assertstate/assertmgr.go 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/overlord/assertstate/assertmgr.go 2017-10-23 06:17:27.000000000 +0000
@@ -29,7 +29,6 @@
"github.com/snapcore/snapd/asserts/sysdb"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
- "github.com/snapcore/snapd/store"
)
// AssertManager is responsible for the enforcement of assertions in
@@ -42,6 +41,8 @@
// Manager returns a new assertion manager.
func Manager(s *state.State) (*AssertManager, error) {
+ delayedCrossMgrInit()
+
runner := state.NewTaskRunner(s)
runner.AddHandler("validate-snap", doValidateSnap, nil)
@@ -112,8 +113,8 @@
err = doFetch(t.State(), snapsup.UserID, func(f asserts.Fetcher) error {
return snapasserts.FetchSnapAssertions(f, sha3_384)
})
- if notFound, ok := err.(*store.AssertionNotFoundError); ok {
- if notFound.Ref.Type == asserts.SnapRevisionType {
+ if notFound, ok := err.(*asserts.NotFoundError); ok {
+ if notFound.Type == asserts.SnapRevisionType {
return fmt.Errorf("cannot verify snap %q, no matching signatures found", snapsup.Name())
} else {
return fmt.Errorf("cannot find supported signatures to verify snap %q and its hash (%v)", snapsup.Name(), notFound)
diff -Nru snapd-2.28.5/overlord/assertstate/assertstate.go snapd-2.29.3/overlord/assertstate/assertstate.go
--- snapd-2.28.5/overlord/assertstate/assertstate.go 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/overlord/assertstate/assertstate.go 2017-11-02 15:44:20.000000000 +0000
@@ -33,7 +33,6 @@
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
- "github.com/snapcore/snapd/store"
)
// Add the given assertion to the system assertion database.
@@ -105,12 +104,12 @@
db := cachedDB(st)
retrieve := func(ref *asserts.Ref) (asserts.Assertion, error) {
a, err := b.bs.Get(ref.Type, ref.PrimaryKey, ref.Type.MaxSupportedFormat())
- if err == asserts.ErrNotFound {
+ if asserts.IsNotFound(err) {
// fallback to pre-existing assertions
a, err = ref.Resolve(db.Find)
}
if err != nil {
- return nil, fmt.Errorf("cannot find %s: %s", ref, err)
+ return nil, findError("cannot find %s", ref, err)
}
return a, nil
}
@@ -129,6 +128,14 @@
return f.commit()
}
+func findError(format string, ref *asserts.Ref, err error) error {
+ if asserts.IsNotFound(err) {
+ return fmt.Errorf(format, ref)
+ } else {
+ return fmt.Errorf(format+": %v", ref, err)
+ }
+}
+
// RefreshSnapDeclarations refetches all the current snap declarations and their prerequisites.
func RefreshSnapDeclarations(s *state.State, userID int) error {
snapStates, err := snapstate.All(s)
@@ -168,8 +175,8 @@
return fmt.Sprintf("refresh control errors:%s", strings.Join(l, "\n - "))
}
-// ValidateRefreshes validates the refresh candidate revisions represented by the snapInfos, looking for the needed refresh control validation assertions, it returns a validated subset in validated and a summary error if not all candidates validated.
-func ValidateRefreshes(s *state.State, snapInfos []*snap.Info, userID int) (validated []*snap.Info, err error) {
+// ValidateRefreshes validates the refresh candidate revisions represented by the snapInfos, looking for the needed refresh control validation assertions, it returns a validated subset in validated and a summary error if not all candidates validated. ignoreValidation is a set of snap-ids that should not be gated.
+func ValidateRefreshes(s *state.State, snapInfos []*snap.Info, ignoreValidation map[string]bool, userID int) (validated []*snap.Info, err error) {
// maps gated snap-ids to gating snap-ids
controlled := make(map[string][]string)
// maps gating snap-ids to their snap names
@@ -194,7 +201,7 @@
"snap-id": gatingID,
})
if err != nil {
- return nil, fmt.Errorf("internal error: cannot find snap declaration for installed snap %q (id %q): err", snapName, gatingID)
+ return nil, fmt.Errorf("internal error: cannot find snap declaration for installed snap %q: %v", snapName, err)
}
decl := a.(*asserts.SnapDeclaration)
control := decl.RefreshControl()
@@ -203,7 +210,9 @@
}
gatingNames[gatingID] = decl.SnapName()
for _, gatedID := range control {
- controlled[gatedID] = append(controlled[gatedID], gatingID)
+ if !ignoreValidation[gatedID] {
+ controlled[gatedID] = append(controlled[gatedID], gatingID)
+ }
}
}
@@ -225,7 +234,7 @@
PrimaryKey: []string{release.Series, gatingID, gatedID, candInfo.Revision.String()},
}
err := f.Fetch(valref)
- if notFound, ok := err.(*store.AssertionNotFoundError); ok && notFound.Ref.Type == asserts.ValidationType {
+ if notFound, ok := err.(*asserts.NotFoundError); ok && notFound.Type == asserts.ValidationType {
return fmt.Errorf("no validation by %q", gatingNames[gatingID])
}
if err != nil {
@@ -245,7 +254,7 @@
for _, valref := range validationRefs {
a, err := valref.Resolve(db.Find)
if err != nil {
- return nil, fmt.Errorf("internal error: cannot find just fetched %v: %v", valref, err)
+ return nil, findError("internal error: cannot find just fetched %v", valref, err)
}
if val := a.(*asserts.Validation); val.Revoked() {
revoked = val
@@ -267,20 +276,13 @@
return validated, nil
}
-func init() {
- // hook validation of refreshes into snapstate logic
- snapstate.ValidateRefreshes = ValidateRefreshes
- // hook auto refresh of assertions into snapstate
- snapstate.AutoRefreshAssertions = AutoRefreshAssertions
-}
-
// BaseDeclaration returns the base-declaration assertion with policies governing all snaps.
func BaseDeclaration(s *state.State) (*asserts.BaseDeclaration, error) {
// TODO: switch keeping this in the DB and have it revisioned/updated
// via the store
baseDecl := asserts.BuiltinBaseDeclaration()
if baseDecl == nil {
- return nil, asserts.ErrNotFound
+ return nil, &asserts.NotFoundError{Type: asserts.BaseDeclarationType}
}
return baseDecl, nil
}
@@ -351,7 +353,11 @@
return res, nil
}
-func init() {
+func delayedCrossMgrInit() {
+ // hook validation of refreshes into snapstate logic
+ snapstate.ValidateRefreshes = ValidateRefreshes
+ // hook auto refresh of assertions into snapstate
+ snapstate.AutoRefreshAssertions = AutoRefreshAssertions
// hook retrieving auto-aliases into snapstate logic
snapstate.AutoAliases = AutoAliases
}
diff -Nru snapd-2.28.5/overlord/assertstate/assertstate_test.go snapd-2.29.3/overlord/assertstate/assertstate_test.go
--- snapd-2.28.5/overlord/assertstate/assertstate_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/overlord/assertstate/assertstate_test.go 2017-11-02 15:44:20.000000000 +0000
@@ -36,19 +36,21 @@
"github.com/snapcore/snapd/asserts/assertstest"
"github.com/snapcore/snapd/asserts/sysdb"
"github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/overlord"
"github.com/snapcore/snapd/overlord/assertstate"
"github.com/snapcore/snapd/overlord/auth"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/overlord/storestate"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snaptest"
- "github.com/snapcore/snapd/store"
"github.com/snapcore/snapd/store/storetest"
)
func TestAssertManager(t *testing.T) { TestingT(t) }
type assertMgrSuite struct {
+ o *overlord.Overlord
state *state.State
mgr *assertstate.AssertManager
@@ -77,11 +79,7 @@
func (sto *fakeStore) Assertion(assertType *asserts.AssertionType, key []string, _ *auth.UserState) (asserts.Assertion, error) {
sto.pokeStateLock()
ref := &asserts.Ref{Type: assertType, PrimaryKey: key}
- a, err := ref.Resolve(sto.db.Find)
- if err != nil {
- return nil, &store.AssertionNotFoundError{Ref: ref}
- }
- return a, nil
+ return ref.Resolve(sto.db.Find)
}
func (s *assertMgrSuite) SetUpTest(c *C) {
@@ -102,13 +100,15 @@
s.dev1Signing = assertstest.NewSigningDB(s.dev1Acct.AccountID(), dev1PrivKey)
- s.state = state.New(nil)
+ s.o = overlord.Mock()
+ s.state = s.o.State()
mgr, err := assertstate.Manager(s.state)
c.Assert(err, IsNil)
s.mgr = mgr
+ s.o.AddManager(s.mgr)
s.state.Lock()
- snapstate.ReplaceStore(s.state, &fakeStore{
+ storestate.ReplaceStore(s.state, &fakeStore{
state: s.state,
db: s.storeSigning,
})
@@ -262,6 +262,7 @@
"authority-id": "can0nical",
"brand-id": "can0nical",
"repair-id": "2",
+ "summary": "repair two",
"timestamp": time.Now().UTC().Format(time.RFC3339),
}
repair, err := aSignDB.Sign(asserts.RepairType, headers, []byte("#script"), "")
@@ -406,12 +407,9 @@
c.Check(snapRev.(*asserts.SnapRevision).SnapRevision(), Equals, 11)
}
-func (s *assertMgrSuite) settle() {
- // XXX: would like to use Overlord.Settle but not enough control there
- for i := 0; i < 50; i++ {
- s.mgr.Ensure()
- s.mgr.Wait()
- }
+func (s *assertMgrSuite) settle(c *C) {
+ err := s.o.Settle(5 * time.Second)
+ c.Assert(err, IsNil)
}
func (s *assertMgrSuite) TestValidateSnap(c *C) {
@@ -441,7 +439,7 @@
s.state.Unlock()
defer s.mgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
c.Assert(chg.Err(), IsNil)
@@ -479,7 +477,7 @@
s.state.Unlock()
defer s.mgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
c.Assert(chg.Err(), ErrorMatches, `(?s).*cannot verify snap "foo", no matching signatures found.*`)
@@ -512,7 +510,7 @@
s.state.Unlock()
defer s.mgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
c.Assert(chg.Err(), ErrorMatches, `(?s).*cannot install snap "f" that is undergoing a rename to "foo".*`)
@@ -566,7 +564,7 @@
s.state.Unlock()
defer s.mgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
c.Assert(chg.Err(), ErrorMatches, `(?s).*proposed "snap-declaration" assertion has format 999 but 0 is latest supported.*`)
@@ -711,7 +709,7 @@
s.state.Lock()
defer s.state.Unlock()
- validated, err := assertstate.ValidateRefreshes(s.state, nil, 0)
+ validated, err := assertstate.ValidateRefreshes(s.state, nil, nil, 0)
c.Assert(err, IsNil)
c.Check(validated, HasLen, 0)
}
@@ -738,7 +736,7 @@
SideInfo: snap.SideInfo{RealName: "foo", SnapID: "foo-id", Revision: snap.R(9)},
}
- validated, err := assertstate.ValidateRefreshes(s.state, []*snap.Info{fooRefresh}, 0)
+ validated, err := assertstate.ValidateRefreshes(s.state, []*snap.Info{fooRefresh}, nil, 0)
c.Assert(err, IsNil)
c.Check(validated, DeepEquals, []*snap.Info{fooRefresh})
}
@@ -767,11 +765,40 @@
SideInfo: snap.SideInfo{RealName: "foo", SnapID: "foo-id", Revision: snap.R(9)},
}
- validated, err := assertstate.ValidateRefreshes(s.state, []*snap.Info{fooRefresh}, 0)
+ validated, err := assertstate.ValidateRefreshes(s.state, []*snap.Info{fooRefresh}, nil, 0)
c.Assert(err, ErrorMatches, `cannot refresh "foo" to revision 9: no validation by "bar"`)
c.Check(validated, HasLen, 0)
}
+func (s *assertMgrSuite) TestValidateRefreshesMissingValidationButIgnore(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapDeclFoo := s.snapDecl(c, "foo", nil)
+ snapDeclBar := s.snapDecl(c, "bar", map[string]interface{}{
+ "refresh-control": []interface{}{"foo-id"},
+ })
+ s.stateFromDecl(snapDeclFoo, snap.R(7))
+ s.stateFromDecl(snapDeclBar, snap.R(3))
+
+ err := assertstate.Add(s.state, s.storeSigning.StoreAccountKey(""))
+ c.Assert(err, IsNil)
+ err = assertstate.Add(s.state, s.dev1Acct)
+ c.Assert(err, IsNil)
+ err = assertstate.Add(s.state, snapDeclFoo)
+ c.Assert(err, IsNil)
+ err = assertstate.Add(s.state, snapDeclBar)
+ c.Assert(err, IsNil)
+
+ fooRefresh := &snap.Info{
+ SideInfo: snap.SideInfo{RealName: "foo", SnapID: "foo-id", Revision: snap.R(9)},
+ }
+
+ validated, err := assertstate.ValidateRefreshes(s.state, []*snap.Info{fooRefresh}, map[string]bool{"foo-id": true}, 0)
+ c.Assert(err, IsNil)
+ c.Check(validated, DeepEquals, []*snap.Info{fooRefresh})
+}
+
func (s *assertMgrSuite) TestValidateRefreshesValidationOK(c *C) {
s.state.Lock()
defer s.state.Unlock()
@@ -835,7 +862,7 @@
SideInfo: snap.SideInfo{RealName: "foo", SnapID: "foo-id", Revision: snap.R(9)},
}
- validated, err := assertstate.ValidateRefreshes(s.state, []*snap.Info{fooRefresh}, 0)
+ validated, err := assertstate.ValidateRefreshes(s.state, []*snap.Info{fooRefresh}, nil, 0)
c.Assert(err, IsNil)
c.Check(validated, DeepEquals, []*snap.Info{fooRefresh})
}
@@ -904,7 +931,7 @@
SideInfo: snap.SideInfo{RealName: "foo", SnapID: "foo-id", Revision: snap.R(9)},
}
- validated, err := assertstate.ValidateRefreshes(s.state, []*snap.Info{fooRefresh}, 0)
+ validated, err := assertstate.ValidateRefreshes(s.state, []*snap.Info{fooRefresh}, nil, 0)
c.Assert(err, ErrorMatches, `(?s).*cannot refresh "foo" to revision 9: validation by "baz" \(id "baz-id"\) revoked.*`)
c.Check(validated, HasLen, 0)
}
@@ -917,7 +944,7 @@
defer r1()
baseDecl, err := assertstate.BaseDeclaration(s.state)
- c.Assert(err, Equals, asserts.ErrNotFound)
+ c.Assert(asserts.IsNotFound(err), Equals, true)
c.Check(baseDecl, IsNil)
r2 := assertstest.MockBuiltinBaseDeclaration([]byte(`
@@ -949,7 +976,7 @@
c.Assert(err, IsNil)
_, err = assertstate.SnapDeclaration(s.state, "snap-id-other")
- c.Check(err, Equals, asserts.ErrNotFound)
+ c.Check(asserts.IsNotFound(err), Equals, true)
snapDecl, err := assertstate.SnapDeclaration(s.state, "foo-id")
c.Assert(err, IsNil)
@@ -978,7 +1005,7 @@
SnapID: "baz-id",
},
})
- c.Check(err, ErrorMatches, `internal error: cannot find snap-declaration for installed snap "baz": assertion not found`)
+ c.Check(err, ErrorMatches, `internal error: cannot find snap-declaration for installed snap "baz": snap-declaration \(baz-id; series:16\) not found`)
info := snaptest.MockInfo(c, `
name: foo
@@ -1038,7 +1065,7 @@
SnapID: "baz-id",
},
})
- c.Check(err, ErrorMatches, `internal error: cannot find snap-declaration for installed snap "baz": assertion not found`)
+ c.Check(err, ErrorMatches, `internal error: cannot find snap-declaration for installed snap "baz": snap-declaration \(baz-id; series:16\) not found`)
// empty list
// have a declaration in the system db
@@ -1097,7 +1124,7 @@
c.Assert(err, IsNil)
_, err = assertstate.SnapDeclaration(s.state, "snap-id-other")
- c.Check(err, Equals, asserts.ErrNotFound)
+ c.Check(asserts.IsNotFound(err), Equals, true)
acct, err := assertstate.Publisher(s.state, "foo-id")
c.Assert(err, IsNil)
diff -Nru snapd-2.28.5/overlord/assertstate/helpers.go snapd-2.29.3/overlord/assertstate/helpers.go
--- snapd-2.28.5/overlord/assertstate/helpers.go 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/overlord/assertstate/helpers.go 2017-10-23 06:17:27.000000000 +0000
@@ -26,8 +26,8 @@
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/overlord/auth"
- "github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/overlord/storestate"
)
// TODO: snapstate also has this, move to auth, or change a bit the approach now that we have AuthContext in the store?
@@ -104,7 +104,7 @@
return err
}
- sto := snapstate.Store(s)
+ sto := storestate.Store(s)
retrieve := func(ref *asserts.Ref) (asserts.Assertion, error) {
// TODO: ignore errors if already in db?
diff -Nru snapd-2.28.5/overlord/auth/auth_test.go snapd-2.29.3/overlord/auth/auth_test.go
--- snapd-2.28.5/overlord/auth/auth_test.go 2017-10-12 19:26:13.000000000 +0000
+++ snapd-2.29.3/overlord/auth/auth_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -692,6 +692,23 @@
c.Check(storeID, Equals, "env-store-id")
}
+func (as *authSuite) TestAuthContextWithDeviceAssertionsGenericClassicModelNoEnvVar(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
+ // but when the env var is unset we don't do anything wrong.
+ os.Unsetenv("UBUNTU_STORE_ID")
+ storeID, err := authContext.StoreID("store-id")
+ c.Assert(err, IsNil)
+ c.Check(storeID, Equals, "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.28.5/overlord/configstate/config/helpers.go snapd-2.29.3/overlord/configstate/config/helpers.go
--- snapd-2.28.5/overlord/configstate/config/helpers.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/overlord/configstate/config/helpers.go 2017-10-23 06:17:27.000000000 +0000
@@ -34,6 +34,9 @@
var validKey = regexp.MustCompile("^(?:[a-z0-9]+-?)*[a-z](?:-?[a-z0-9])*$")
func ParseKey(key string) (subkeys []string, err error) {
+ if key == "" {
+ return []string{}, nil
+ }
subkeys = strings.Split(key, ".")
for _, subkey := range subkeys {
if !validKey.MatchString(subkey) {
@@ -90,6 +93,18 @@
// The provided key may be formed as a dotted key path through nested maps.
// For example, the "a.b.c" key describes the {a: {b: {c: value}}} map.
func GetFromChange(snapName string, subkeys []string, pos int, config map[string]interface{}, result interface{}) error {
+ // special case - get root document
+ if len(subkeys) == 0 {
+ if config == nil {
+ return &NoOptionError{SnapName: snapName, Key: ""}
+ }
+ raw := jsonRaw(config)
+
+ if err := jsonutil.DecodeWithNumber(bytes.NewReader(*raw), &result); err != nil {
+ return fmt.Errorf("internal error: cannot unmarshal snap %q root document: %s", snapName, err)
+ }
+ return nil
+ }
value, ok := config[subkeys[pos]]
if !ok {
return &NoOptionError{SnapName: snapName, Key: strings.Join(subkeys[:pos+1], ".")}
diff -Nru snapd-2.28.5/overlord/configstate/config/transaction.go snapd-2.29.3/overlord/configstate/config/transaction.go
--- snapd-2.28.5/overlord/configstate/config/transaction.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/overlord/configstate/config/transaction.go 2017-10-23 06:17:27.000000000 +0000
@@ -143,6 +143,18 @@
}
func getFromPristine(snapName string, subkeys []string, pos int, config map[string]*json.RawMessage, result interface{}) error {
+ // special case - get root document
+ if len(subkeys) == 0 {
+ if len(config) == 0 {
+ return &NoOptionError{SnapName: snapName}
+ }
+ raw := jsonRaw(config)
+ if err := jsonutil.DecodeWithNumber(bytes.NewReader(*raw), &result); err != nil {
+ return fmt.Errorf("internal error: cannot unmarshal snap %q root document: %s", snapName, err)
+ }
+ return nil
+ }
+
raw, ok := config[subkeys[pos]]
if !ok {
return &NoOptionError{SnapName: snapName, Key: strings.Join(subkeys[:pos+1], ".")}
@@ -251,5 +263,8 @@
}
func (e *NoOptionError) Error() string {
+ if e.Key == "" {
+ return fmt.Sprintf("snap %q has no configuration", e.SnapName)
+ }
return fmt.Sprintf("snap %q has no %q configuration option", e.SnapName, e.Key)
}
diff -Nru snapd-2.28.5/overlord/configstate/config/transaction_test.go snapd-2.29.3/overlord/configstate/config/transaction_test.go
--- snapd-2.28.5/overlord/configstate/config/transaction_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/overlord/configstate/config/transaction_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -103,6 +103,13 @@
`set doc={"one":1,"two":2}`,
`get doc={"one":1,"two":2}`,
}, {
+ // Root doc
+ `set doc={"one":1,"two":2}`,
+ `getroot ={"doc":{"one":1,"two":2}}`,
+ `commit`,
+ `getroot ={"doc":{"one":1,"two":2}}`,
+ `getrootunder ={"doc":{"one":1,"two":2}}`,
+}, {
// Nested mutations.
`set one.two.three=3`,
`set one.five=5`,
@@ -248,7 +255,20 @@
c.Assert(jsonutil.DecodeWithNumber(bytes.NewReader(*obtained), &cfg), IsNil)
c.Assert(cfg, DeepEquals, expected)
}
-
+ case "getroot":
+ var obtained interface{}
+ c.Assert(t.Get(snap, "", &obtained), IsNil)
+ c.Assert(obtained, DeepEquals, op.args()[""])
+ case "getrootunder":
+ var config map[string]*json.RawMessage
+ s.state.Get("config", &config)
+ for _, expected := range op.args() {
+ obtained, ok := config[snap]
+ c.Assert(ok, Equals, true)
+ var cfg interface{}
+ c.Assert(jsonutil.DecodeWithNumber(bytes.NewReader(*obtained), &cfg), IsNil)
+ c.Assert(cfg, DeepEquals, expected)
+ }
default:
panic("unknown test op kind: " + op.kind())
}
@@ -286,3 +306,15 @@
err = tr.Get("test-snap", "foo", &broken)
c.Assert(err, ErrorMatches, ".*BAM!.*")
}
+
+func (s *transactionSuite) TestNoConfiguration(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ var res interface{}
+ tr := config.NewTransaction(s.state)
+ err := tr.Get("some-snap", "", &res)
+ c.Assert(err, NotNil)
+ c.Assert(config.IsNoOption(err), Equals, true)
+ c.Assert(err, ErrorMatches, `snap "some-snap" has no configuration`)
+}
diff -Nru snapd-2.28.5/overlord/configstate/configstate.go snapd-2.29.3/overlord/configstate/configstate.go
--- snapd-2.28.5/overlord/configstate/configstate.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/overlord/configstate/configstate.go 2017-10-23 06:17:27.000000000 +0000
@@ -26,7 +26,7 @@
"os"
"time"
- "github.com/snapcore/snapd/i18n/dumb"
+ "github.com/snapcore/snapd/i18n"
"github.com/snapcore/snapd/overlord/hookstate"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
@@ -36,7 +36,7 @@
snapstate.Configure = Configure
}
-func configureHookTimeout() time.Duration {
+func ConfigureHookTimeout() time.Duration {
timeout := 5 * time.Minute
if s := os.Getenv("SNAPD_CONFIGURE_HOOK_TIMEOUT"); s != "" {
if to, err := time.ParseDuration(s); err == nil {
@@ -55,7 +55,7 @@
IgnoreError: flags&snapstate.IgnoreHookError != 0,
TrackError: flags&snapstate.TrackHookError != 0,
// all configure hooks must finish within this timeout
- Timeout: configureHookTimeout(),
+ Timeout: ConfigureHookTimeout(),
}
var contextData map[string]interface{}
if flags&snapstate.UseConfigDefaults != 0 {
diff -Nru snapd-2.28.5/overlord/configstate/handler_test.go snapd-2.29.3/overlord/configstate/handler_test.go
--- snapd-2.28.5/overlord/configstate/handler_test.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/overlord/configstate/handler_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -43,6 +43,7 @@
state *state.State
context *hookstate.Context
handler hookstate.Handler
+ restore func()
}
var _ = Suite(&configureHandlerSuite{})
@@ -52,6 +53,8 @@
s.state.Lock()
defer s.state.Unlock()
+ s.restore = snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})
+
task := s.state.NewTask("test-task", "my test task")
setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "test-hook"}
@@ -62,6 +65,10 @@
s.handler = configstate.NewConfigureHandler(s.context)
}
+func (s *configureHandlerSuite) TearDownTest(c *C) {
+ s.restore()
+}
+
func (s *configureHandlerSuite) TestBeforeInitializesTransaction(c *C) {
// Initialize context
s.context.Lock()
diff -Nru snapd-2.28.5/overlord/devicestate/devicemgr.go snapd-2.29.3/overlord/devicestate/devicemgr.go
--- snapd-2.28.5/overlord/devicestate/devicemgr.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/overlord/devicestate/devicemgr.go 2017-10-23 06:17:27.000000000 +0000
@@ -28,7 +28,7 @@
"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/i18n"
"github.com/snapcore/snapd/overlord/assertstate"
"github.com/snapcore/snapd/overlord/auth"
"github.com/snapcore/snapd/overlord/hookstate"
@@ -55,6 +55,8 @@
// Manager returns a new device manager.
func Manager(s *state.State, hookManager *hookstate.HookManager) (*DeviceManager, error) {
+ delayedCrossMgrInit()
+
runner := state.NewTaskRunner(s)
keypairMgr, err := asserts.OpenFSKeypairManager(dirs.SnapDeviceDir)
@@ -252,6 +254,9 @@
}
prepareDevice = hookstate.HookTask(m.state, summary, hooksup, nil)
tasks = append(tasks, prepareDevice)
+ // hooks are under a different manager, make sure we consider
+ // it immediately
+ m.state.EnsureBefore(0)
}
genKey := m.state.NewTask("generate-device-key", i18n.G("Generate device key"))
diff -Nru snapd-2.28.5/overlord/devicestate/devicestate.go snapd-2.29.3/overlord/devicestate/devicestate.go
--- snapd-2.28.5/overlord/devicestate/devicestate.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/overlord/devicestate/devicestate.go 2017-10-23 06:17:27.000000000 +0000
@@ -23,6 +23,7 @@
import (
"fmt"
+ "sync"
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/logger"
@@ -50,7 +51,7 @@
"brand-id": device.Brand,
"model": device.Model,
})
- if err == asserts.ErrNotFound {
+ if asserts.IsNotFound(err) {
return nil, state.ErrNoState
}
if err != nil {
@@ -76,7 +77,7 @@
"model": device.Model,
"serial": device.Serial,
})
- if err == asserts.ErrNotFound {
+ if asserts.IsNotFound(err) {
return nil, state.ErrNoState
}
if err != nil {
@@ -188,7 +189,11 @@
return nil
}
-func init() {
- snapstate.AddCheckSnapCallback(checkGadgetOrKernel)
+var once sync.Once
+
+func delayedCrossMgrInit() {
+ once.Do(func() {
+ snapstate.AddCheckSnapCallback(checkGadgetOrKernel)
+ })
snapstate.CanAutoRefresh = canAutoRefresh
}
diff -Nru snapd-2.28.5/overlord/devicestate/devicestate_test.go snapd-2.29.3/overlord/devicestate/devicestate_test.go
--- snapd-2.28.5/overlord/devicestate/devicestate_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/overlord/devicestate/devicestate_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -42,6 +42,7 @@
"github.com/snapcore/snapd/boot/boottest"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/httputil"
+ "github.com/snapcore/snapd/overlord"
"github.com/snapcore/snapd/overlord/assertstate"
"github.com/snapcore/snapd/overlord/auth"
"github.com/snapcore/snapd/overlord/devicestate"
@@ -49,11 +50,11 @@
"github.com/snapcore/snapd/overlord/hookstate/ctlcmd"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/overlord/storestate"
"github.com/snapcore/snapd/partition"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snaptest"
- "github.com/snapcore/snapd/store"
"github.com/snapcore/snapd/store/storetest"
"github.com/snapcore/snapd/strutil"
)
@@ -61,11 +62,14 @@
func TestDeviceManager(t *testing.T) { TestingT(t) }
type deviceMgrSuite struct {
+ o *overlord.Overlord
state *state.State
hookMgr *hookstate.HookManager
mgr *devicestate.DeviceManager
db *asserts.Database
+ bootloader *boottest.MockBootloader
+
storeSigning *assertstest.StoreStack
brandSigning *assertstest.SigningDB
@@ -73,6 +77,7 @@
restoreOnClassic func()
restoreGenericClassicMod func()
+ restoreSanitize func()
}
var _ = Suite(&deviceMgrSuite{})
@@ -95,21 +100,23 @@
func (sto *fakeStore) Assertion(assertType *asserts.AssertionType, key []string, _ *auth.UserState) (asserts.Assertion, error) {
sto.pokeStateLock()
ref := &asserts.Ref{Type: assertType, PrimaryKey: key}
- a, err := ref.Resolve(sto.db.Find)
- if err != nil {
- return nil, &store.AssertionNotFoundError{Ref: ref}
- }
- return a, nil
+ return ref.Resolve(sto.db.Find)
}
func (s *deviceMgrSuite) SetUpTest(c *C) {
dirs.SetRootDir(c.MkDir())
os.MkdirAll(dirs.SnapRunDir, 0755)
+ s.restoreSanitize = snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})
+
+ s.bootloader = boottest.NewMockBootloader("mock", c.MkDir())
+ partition.ForceBootloader(s.bootloader)
+
s.restoreOnClassic = release.MockOnClassic(false)
s.storeSigning = assertstest.NewStoreStack("canonical", nil)
- s.state = state.New(nil)
+ s.o = overlord.Mock()
+ s.state = s.o.State()
s.restoreGenericClassicMod = sysdb.MockGenericClassicModel(s.storeSigning.GenericClassicModel)
@@ -137,10 +144,12 @@
s.db = db
s.hookMgr = hookMgr
+ s.o.AddManager(s.hookMgr)
s.mgr = mgr
+ s.o.AddManager(s.mgr)
s.state.Lock()
- snapstate.ReplaceStore(s.state, &fakeStore{
+ storestate.ReplaceStore(s.state, &fakeStore{
state: s.state,
db: s.storeSigning,
})
@@ -151,18 +160,18 @@
s.state.Lock()
assertstate.ReplaceDB(s.state, nil)
s.state.Unlock()
+ partition.ForceBootloader(nil)
dirs.SetRootDir("")
s.restoreGenericClassicMod()
s.restoreOnClassic()
+ s.restoreSanitize()
}
-func (s *deviceMgrSuite) settle() {
- for i := 0; i < 50; i++ {
- s.hookMgr.Ensure()
- s.mgr.Ensure()
- s.hookMgr.Wait()
- s.mgr.Wait()
- }
+var settleTimeout = 15 * time.Second
+
+func (s *deviceMgrSuite) settle(c *C) {
+ err := s.o.Settle(settleTimeout)
+ c.Assert(err, IsNil)
}
const (
@@ -356,7 +365,7 @@
// runs the whole device registration process
s.state.Unlock()
- s.settle()
+ s.settle(c)
s.state.Lock()
becomeOperational = s.findBecomeOperationalChange()
@@ -424,7 +433,7 @@
// runs the whole device registration process
s.state.Unlock()
- s.settle()
+ s.settle(c)
s.state.Lock()
becomeOperational := s.findBecomeOperationalChange()
@@ -495,7 +504,7 @@
// runs the whole device registration process
s.state.Unlock()
- s.settle()
+ s.settle(c)
s.state.Lock()
becomeOperational = s.findBecomeOperationalChange()
@@ -581,7 +590,7 @@
// runs the whole device registration process
s.state.Unlock()
- s.settle()
+ s.settle(c)
s.state.Lock()
becomeOperational := s.findBecomeOperationalChange()
@@ -735,7 +744,7 @@
"model": "pc",
"serial": "9999",
})
- c.Assert(err, Equals, asserts.ErrNotFound)
+ c.Assert(asserts.IsNotFound(err), Equals, true)
s.state.Unlock()
s.mgr.Ensure()
@@ -795,12 +804,19 @@
// runs the whole device registration process with polling
s.state.Unlock()
- s.settle()
+ s.settle(c)
s.state.Lock()
becomeOperational := s.findBecomeOperationalChange()
c.Assert(becomeOperational, NotNil)
+ // needs 3 more Retry passes of polling
+ for i := 0; i < 3; i++ {
+ s.state.Unlock()
+ s.settle(c)
+ s.state.Lock()
+ }
+
c.Check(becomeOperational.Status().Ready(), Equals, true)
c.Check(becomeOperational.Err(), IsNil)
@@ -890,7 +906,7 @@
// runs the whole device registration process
s.state.Unlock()
- s.settle()
+ s.settle(c)
s.state.Lock()
becomeOperational := s.findBecomeOperationalChange()
@@ -973,7 +989,7 @@
// try the whole device registration process
s.state.Unlock()
- s.settle()
+ s.settle(c)
s.state.Lock()
becomeOperational := s.findBecomeOperationalChange()
@@ -996,7 +1012,7 @@
s.reqID = "REQID-1"
s.mgr.SetLastBecomeOperationalAttempt(time.Now().Add(-15 * time.Minute))
s.state.Unlock()
- s.settle()
+ s.settle(c)
s.state.Lock()
becomeOperational = s.findBecomeOperationalChange(firstTryID)
@@ -1078,7 +1094,7 @@
// try the whole device registration process
s.state.Unlock()
- s.settle()
+ s.settle(c)
s.state.Lock()
becomeOperational := s.findBecomeOperationalChange()
@@ -1317,10 +1333,7 @@
}
func (s *deviceMgrSuite) TestDeviceManagerEnsureBootOkBootloaderHappy(c *C) {
- bootloader := boottest.NewMockBootloader("mock", c.MkDir())
- partition.ForceBootloader(bootloader)
- defer partition.ForceBootloader(nil)
- bootloader.SetBootVars(map[string]string{
+ s.bootloader.SetBootVars(map[string]string{
"snap_mode": "trying",
"snap_try_core": "core_1.snap",
})
@@ -1340,18 +1353,14 @@
s.state.Lock()
c.Assert(err, IsNil)
- m, err := bootloader.GetBootVars("snap_mode")
+ m, err := s.bootloader.GetBootVars("snap_mode")
c.Assert(err, IsNil)
c.Assert(m, DeepEquals, map[string]string{"snap_mode": ""})
}
func (s *deviceMgrSuite) TestDeviceManagerEnsureBootOkUpdateBootRevisionsHappy(c *C) {
- bootloader := boottest.NewMockBootloader("mock", c.MkDir())
- partition.ForceBootloader(bootloader)
- defer partition.ForceBootloader(nil)
-
// simulate that we have a new core_2, tried to boot it but that failed
- bootloader.SetBootVars(map[string]string{
+ s.bootloader.SetBootVars(map[string]string{
"snap_mode": "",
"snap_try_core": "core_2.snap",
"snap_core": "core_1.snap",
@@ -1386,14 +1395,11 @@
}
func (s *deviceMgrSuite) TestDeviceManagerEnsureBootOkNotRunAgain(c *C) {
- bootloader := boottest.NewMockBootloader("mock", c.MkDir())
- bootloader.SetBootVars(map[string]string{
+ s.bootloader.SetBootVars(map[string]string{
"snap_mode": "trying",
"snap_try_core": "core_1.snap",
})
- bootloader.SetErr = fmt.Errorf("ensure bootloader is not used")
- partition.ForceBootloader(bootloader)
- defer partition.ForceBootloader(nil)
+ s.bootloader.SetErr = fmt.Errorf("ensure bootloader is not used")
s.mgr.SetBootOkRan(true)
@@ -1413,10 +1419,7 @@
})
s.state.Unlock()
- bootloader := boottest.NewMockBootloader("mock", c.MkDir())
- bootloader.GetErr = fmt.Errorf("bootloader err")
- partition.ForceBootloader(bootloader)
- defer partition.ForceBootloader(nil)
+ s.bootloader.GetErr = fmt.Errorf("bootloader err")
s.mgr.SetBootOkRan(false)
diff -Nru snapd-2.28.5/overlord/devicestate/firstboot.go snapd-2.29.3/overlord/devicestate/firstboot.go
--- snapd-2.28.5/overlord/devicestate/firstboot.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/overlord/devicestate/firstboot.go 2017-10-23 06:17:27.000000000 +0000
@@ -29,7 +29,7 @@
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/asserts/snapasserts"
"github.com/snapcore/snapd/dirs"
- "github.com/snapcore/snapd/i18n/dumb"
+ "github.com/snapcore/snapd/i18n"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/overlord/assertstate"
"github.com/snapcore/snapd/overlord/auth"
@@ -56,7 +56,7 @@
sideInfo.RealName = sn.Name
} else {
si, err := snapasserts.DeriveSideInfo(path, assertstate.DB(st))
- if err == asserts.ErrNotFound {
+ if asserts.IsNotFound(err) {
return nil, fmt.Errorf("cannot find signatures with metadata for snap %q (%q)", sn.Name, path)
}
if err != nil {
diff -Nru snapd-2.28.5/overlord/devicestate/firstboot_test.go snapd-2.29.3/overlord/devicestate/firstboot_test.go
--- snapd-2.28.5/overlord/devicestate/firstboot_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/overlord/devicestate/firstboot_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -43,6 +43,7 @@
"github.com/snapcore/snapd/overlord/configstate/config"
"github.com/snapcore/snapd/overlord/devicestate"
"github.com/snapcore/snapd/overlord/hookstate"
+ "github.com/snapcore/snapd/overlord/ifacestate"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/partition"
@@ -106,6 +107,10 @@
ovld, err := overlord.New()
c.Assert(err, IsNil)
s.overlord = ovld
+
+ // don't actually try to talk to the store on snapstate.Ensure
+ // needs doing after the call to devicestate.Manager (which happens in overlord.New)
+ snapstate.CanAutoRefresh = nil
}
func (s *FirstBootTestSuite) TearDownTest(c *C) {
@@ -275,7 +280,7 @@
return coreFname, kernelFname, gadgetFname
}
-func (s *FirstBootTestSuite) makeBecomeOpertionalChange(c *C) *state.Change {
+func (s *FirstBootTestSuite) makeBecomeOperationalChange(c *C, st *state.State) *state.Change {
coreFname, kernelFname, gadgetFname := s.makeCoreSnaps(c, false)
devAcct := assertstest.NewAccount(s.storeSigning, "developer", map[string]interface{}{
@@ -335,7 +340,6 @@
c.Assert(err, IsNil)
// run the firstboot stuff
- st := s.overlord.State()
st.Lock()
defer st.Unlock()
tsAll, err := devicestate.PopulateStateFromSeedImpl(st)
@@ -361,11 +365,6 @@
chg1 := st.NewChange("become-operational", "init device")
chg1.SetStatus(state.DoingStatus)
- st.Unlock()
- s.overlord.Settle()
- st.Lock()
- // unlocked by defer
-
return chg
}
@@ -378,8 +377,11 @@
"snap_kernel": "pc-kernel_1.snap",
})
- chg := s.makeBecomeOpertionalChange(c)
st := s.overlord.State()
+ chg := s.makeBecomeOperationalChange(c, st)
+ err := s.overlord.Settle(settleTimeout)
+ c.Assert(err, IsNil)
+
st.Lock()
defer st.Unlock()
@@ -441,11 +443,39 @@
}
func (s *FirstBootTestSuite) TestPopulateFromSeedMissingBootloader(c *C) {
- chg := s.makeBecomeOpertionalChange(c)
- st := s.overlord.State()
+ st0 := s.overlord.State()
+ st0.Lock()
+ db := assertstate.DB(st0)
+ st0.Unlock()
+
+ // we run only with the relevant managers to produce the error
+ // situation
+ o := overlord.Mock()
+ st := o.State()
+ snapmgr, err := snapstate.Manager(st)
+ c.Assert(err, IsNil)
+ o.AddManager(snapmgr)
+
+ ifacemgr, err := ifacestate.Manager(st, nil, nil, nil)
+ c.Assert(err, IsNil)
+ o.AddManager(ifacemgr)
st.Lock()
- defer st.Unlock()
+ assertstate.ReplaceDB(st, db.(*asserts.Database))
+ st.Unlock()
+
+ chg := s.makeBecomeOperationalChange(c, st)
+
+ // we cannot use Settle because the Change will not become Clean
+ // under the subset of managers
+ for i := 0; i < 25 && !chg.IsReady(); i++ {
+ snapmgr.Ensure()
+ ifacemgr.Ensure()
+ snapmgr.Wait()
+ ifacemgr.Wait()
+ }
+ st.Lock()
+ defer st.Unlock()
c.Assert(chg.Err(), ErrorMatches, `(?s).* cannot determine bootloader.*`)
}
@@ -534,9 +564,10 @@
chg1.SetStatus(state.DoingStatus)
st.Unlock()
- s.overlord.Settle()
+ err = s.overlord.Settle(settleTimeout)
st.Lock()
c.Assert(chg.Err(), IsNil)
+ c.Assert(err, IsNil)
// and check the snap got correctly installed
c.Check(osutil.FileExists(filepath.Join(dirs.SnapMountDir, "foo", "128", "meta", "snap.yaml")), Equals, true)
@@ -717,9 +748,10 @@
chg1.SetStatus(state.DoingStatus)
st.Unlock()
- s.overlord.Settle()
+ err = s.overlord.Settle(settleTimeout)
st.Lock()
c.Assert(chg.Err(), IsNil)
+ c.Assert(err, IsNil)
// and check the snap got correctly installed
c.Check(osutil.FileExists(filepath.Join(dirs.SnapMountDir, "foo", "128", "meta", "snap.yaml")), Equals, true)
@@ -881,7 +913,7 @@
// try import and verify that its rejects because other assertions are
// missing
_, err := devicestate.ImportAssertionsFromSeed(st)
- c.Assert(err, ErrorMatches, "cannot find account-key .*: assertion not found")
+ c.Assert(err, ErrorMatches, "cannot find account-key .*")
}
func (s *FirstBootTestSuite) TestImportAssertionsFromSeedTwoModelAsserts(c *C) {
diff -Nru snapd-2.28.5/overlord/devicestate/handlers.go snapd-2.29.3/overlord/devicestate/handlers.go
--- snapd-2.28.5/overlord/devicestate/handlers.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/overlord/devicestate/handlers.go 2017-10-23 06:17:27.000000000 +0000
@@ -37,6 +37,7 @@
"github.com/snapcore/snapd/overlord/configstate/config"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/overlord/storestate"
)
func (m *DeviceManager) doMarkSeeded(t *state.Task, _ *tomb.Tomb) error {
@@ -429,7 +430,7 @@
"model": device.Model,
"device-key-sha3-384": privKey.PublicKey().ID(),
})
- if err != nil && err != asserts.ErrNotFound {
+ if err != nil && !asserts.IsNotFound(err) {
return err
}
@@ -490,7 +491,7 @@
var repeatRequestSerial string // for tests
func fetchKeys(st *state.State, keyID string) (errAcctKey error, err error) {
- sto := snapstate.Store(st)
+ sto := storestate.Store(st)
db := assertstate.DB(st)
for {
_, err := db.FindPredefined(asserts.AccountKeyType, map[string]string{
@@ -499,7 +500,7 @@
if err == nil {
return nil, nil
}
- if err != asserts.ErrNotFound {
+ if !asserts.IsNotFound(err) {
return nil, err
}
st.Unlock()
diff -Nru snapd-2.28.5/overlord/export_test.go snapd-2.29.3/overlord/export_test.go
--- snapd-2.28.5/overlord/export_test.go 2016-08-29 15:03:59.000000000 +0000
+++ snapd-2.29.3/overlord/export_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -23,7 +23,8 @@
"time"
"github.com/snapcore/snapd/overlord/auth"
- "github.com/snapcore/snapd/store"
+ "github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/overlord/storestate"
)
// MockEnsureInterval sets the overlord ensure interval for tests.
@@ -58,10 +59,10 @@
return o.stateEng
}
-// MockStoreNew mocks store.New as called by overlord.New.
-func MockStoreNew(new func(*store.Config, auth.AuthContext) *store.Store) (restore func()) {
- storeNew = new
+// MockSetupStore mocks storestate.SetupStore as called by overlord.New.
+func MockSetupStore(new func(*state.State, auth.AuthContext) error) (restore func()) {
+ setupStore = new
return func() {
- storeNew = store.New
+ setupStore = storestate.SetupStore
}
}
diff -Nru snapd-2.28.5/overlord/hookstate/context.go snapd-2.29.3/overlord/hookstate/context.go
--- snapd-2.28.5/overlord/hookstate/context.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/overlord/hookstate/context.go 2017-10-27 12:23:38.000000000 +0000
@@ -80,6 +80,12 @@
return c.setup.Revision
}
+// Task returns the task associated with the hook or (nil, false) if the context is ephemeral
+// and task is not available.
+func (c *Context) Task() (*state.Task, bool) {
+ return c.task, c.task != nil
+}
+
// HookName returns the name of the hook in this context.
func (c *Context) HookName() string {
return c.setup.Hook
diff -Nru snapd-2.28.5/overlord/hookstate/ctlcmd/export_test.go snapd-2.29.3/overlord/hookstate/ctlcmd/export_test.go
--- snapd-2.28.5/overlord/hookstate/ctlcmd/export_test.go 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/overlord/hookstate/ctlcmd/export_test.go 2017-10-27 12:23:38.000000000 +0000
@@ -19,11 +19,22 @@
package ctlcmd
-import "fmt"
+import (
+ "fmt"
+ "github.com/snapcore/snapd/overlord/servicestate"
+ "github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/snap"
+)
var AttributesTask = attributesTask
var CopyAttributes = copyAttributes
+func MockServicestateControlFunc(f func(*state.State, []*snap.AppInfo, *servicestate.Instruction) (*state.TaskSet, error)) (restore func()) {
+ old := servicestateControl
+ servicestateControl = f
+ return func() { servicestateControl = old }
+}
+
func AddMockCommand(name string) *MockCommand {
mockCommand := NewMockCommand()
addCommand(name, "", "", func() command { return mockCommand })
diff -Nru snapd-2.28.5/overlord/hookstate/ctlcmd/get.go snapd-2.29.3/overlord/hookstate/ctlcmd/get.go
--- snapd-2.28.5/overlord/hookstate/ctlcmd/get.go 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/overlord/hookstate/ctlcmd/get.go 2017-10-23 06:17:27.000000000 +0000
@@ -24,7 +24,7 @@
"fmt"
"strings"
- "github.com/snapcore/snapd/i18n/dumb"
+ "github.com/snapcore/snapd/i18n"
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/overlord/configstate"
"github.com/snapcore/snapd/overlord/configstate/config"
diff -Nru snapd-2.28.5/overlord/hookstate/ctlcmd/helpers.go snapd-2.29.3/overlord/hookstate/ctlcmd/helpers.go
--- snapd-2.28.5/overlord/hookstate/ctlcmd/helpers.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/overlord/hookstate/ctlcmd/helpers.go 2017-10-27 12:23:38.000000000 +0000
@@ -0,0 +1,123 @@
+// -*- 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 ctlcmd
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/snapcore/snapd/i18n"
+ "github.com/snapcore/snapd/overlord/configstate"
+ "github.com/snapcore/snapd/overlord/hookstate"
+ "github.com/snapcore/snapd/overlord/servicestate"
+ "github.com/snapcore/snapd/overlord/snapstate"
+ "github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/snap"
+)
+
+func getServiceInfos(st *state.State, snapName string, serviceNames []string) ([]*snap.AppInfo, error) {
+ st.Lock()
+ defer st.Unlock()
+
+ var snapst snapstate.SnapState
+ if err := snapstate.Get(st, snapName, &snapst); err != nil {
+ return nil, err
+ }
+
+ info, err := snapst.CurrentInfo()
+ if err != nil {
+ return nil, err
+ }
+
+ var svcs []*snap.AppInfo
+ for _, svcName := range serviceNames {
+ if svcName == snapName {
+ // all the services
+ return info.Services(), nil
+ }
+ if !strings.HasPrefix(svcName, snapName+".") {
+ return nil, fmt.Errorf(i18n.G("unknown service: %q"), svcName)
+ }
+ // this doesn't support service aliases
+ app, ok := info.Apps[svcName[1+len(snapName):]]
+ if !(ok && app.IsService()) {
+ return nil, fmt.Errorf(i18n.G("unknown service: %q"), svcName)
+ }
+ svcs = append(svcs, app)
+ }
+
+ return svcs, nil
+}
+
+var servicestateControl = servicestate.Control
+
+func queueCommand(context *hookstate.Context, ts *state.TaskSet) {
+ // queue command task after all existing tasks of the hook's change
+ st := context.State()
+ st.Lock()
+ defer st.Unlock()
+
+ task, ok := context.Task()
+ if !ok {
+ panic("attempted to queue command with ephemeral context")
+ }
+ change := task.Change()
+ tasks := change.Tasks()
+ ts.WaitAll(state.NewTaskSet(tasks...))
+ change.AddAll(ts)
+}
+
+func runServiceCommand(context *hookstate.Context, inst *servicestate.Instruction, serviceNames []string) error {
+ if context == nil {
+ return fmt.Errorf(i18n.G("cannot %s without a context"), inst.Action)
+ }
+
+ st := context.State()
+ appInfos, err := getServiceInfos(st, context.SnapName(), serviceNames)
+ if err != nil {
+ return err
+ }
+
+ ts, err := servicestateControl(st, appInfos, inst)
+ if err != nil {
+ return err
+ }
+
+ if !context.IsEphemeral() && context.HookName() == "configure" {
+ queueCommand(context, ts)
+ return nil
+ }
+
+ st.Lock()
+ chg := st.NewChange("service-control", fmt.Sprintf("Running service command for snap %q", context.SnapName()))
+ chg.AddAll(ts)
+ st.EnsureBefore(0)
+ st.Unlock()
+
+ select {
+ case <-chg.Ready():
+ st.Lock()
+ defer st.Unlock()
+ return chg.Err()
+ case <-time.After(configstate.ConfigureHookTimeout() / 2):
+ return fmt.Errorf("%s command is taking too long", inst.Action)
+ }
+}
diff -Nru snapd-2.28.5/overlord/hookstate/ctlcmd/restart.go snapd-2.29.3/overlord/hookstate/ctlcmd/restart.go
--- snapd-2.28.5/overlord/hookstate/ctlcmd/restart.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/overlord/hookstate/ctlcmd/restart.go 2017-11-03 16:15:11.000000000 +0000
@@ -0,0 +1,57 @@
+// -*- 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 ctlcmd
+
+import (
+ "github.com/snapcore/snapd/client"
+ "github.com/snapcore/snapd/i18n"
+ "github.com/snapcore/snapd/overlord/servicestate"
+)
+
+var (
+ shortRestartHelp = i18n.G("Restart services")
+ longRestartHelp = i18n.G(`
+The restart command restarts the given services of the snap. If executed from the
+"configure" hook, the services will be restarted after the hook finishes.`)
+)
+
+func init() {
+ // FIXME: uncomment once the feature is fixed to work on install/refresh
+ // addCommand("restart", shortRestartHelp, longRestartHelp, func() command { return &restartCommand{} })
+}
+
+type restartCommand struct {
+ baseCommand
+ Positional struct {
+ ServiceNames []string `positional-arg-name:"" required:"yes"`
+ } `positional-args:"yes" required:"yes"`
+ Reload bool `long:"reload" description:"Reload the given services if they support it (see man systemctl for details)"`
+}
+
+func (c *restartCommand) Execute(args []string) error {
+ inst := servicestate.Instruction{
+ Action: "restart",
+ Names: c.Positional.ServiceNames,
+ RestartOptions: client.RestartOptions{
+ Reload: c.Reload,
+ },
+ }
+ return runServiceCommand(c.context(), &inst, c.Positional.ServiceNames)
+}
diff -Nru snapd-2.28.5/overlord/hookstate/ctlcmd/services_test.go snapd-2.29.3/overlord/hookstate/ctlcmd/services_test.go
--- snapd-2.28.5/overlord/hookstate/ctlcmd/services_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/overlord/hookstate/ctlcmd/services_test.go 2017-11-03 16:15:11.000000000 +0000
@@ -0,0 +1,259 @@
+// -*- 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 ctlcmd_test
+
+import (
+ "fmt"
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/client"
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/overlord/configstate"
+ "github.com/snapcore/snapd/overlord/hookstate"
+ "github.com/snapcore/snapd/overlord/hookstate/ctlcmd"
+ "github.com/snapcore/snapd/overlord/hookstate/hooktest"
+ "github.com/snapcore/snapd/overlord/servicestate"
+ "github.com/snapcore/snapd/overlord/snapstate"
+ "github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/snaptest"
+ "github.com/snapcore/snapd/testutil"
+)
+
+type servicectlSuite struct {
+ testutil.BaseTest
+ st *state.State
+ mockContext *hookstate.Context
+ mockHandler *hooktest.MockHandler
+}
+
+var _ = Suite(&servicectlSuite{})
+
+const testSnapYaml = `name: test-snap
+version: 1.0
+summary: test-snap
+apps:
+ normal-app:
+ command: bin/dummy
+ test-service:
+ command: bin/service
+ daemon: simple
+ reload-command: bin/reload
+`
+
+const otherSnapYaml = `name: other-snap
+version: 1.0
+summary: other-snap
+apps:
+ test-service:
+ command: bin/service
+ daemon: simple
+ reload-command: bin/reload
+`
+
+func mockServiceChangeFunc(testServiceControlInputs func(appInfos []*snap.AppInfo, inst *servicestate.Instruction)) func() {
+ return ctlcmd.MockServicestateControlFunc(func(st *state.State, appInfos []*snap.AppInfo, inst *servicestate.Instruction) (*state.TaskSet, error) {
+ testServiceControlInputs(appInfos, inst)
+ return nil, fmt.Errorf("forced error")
+ })
+}
+
+func (s *servicectlSuite) SetUpTest(c *C) {
+ c.Skip("disabled until snapctl start/stop/restart commands are restored")
+
+ s.BaseTest.SetUpTest(c)
+ oldRoot := dirs.GlobalRootDir
+ dirs.SetRootDir(c.MkDir())
+
+ testutil.MockCommand(c, "systemctl", "")
+
+ s.BaseTest.AddCleanup(func() {
+ dirs.SetRootDir(oldRoot)
+ })
+ s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
+
+ s.mockHandler = hooktest.NewMockHandler()
+
+ s.st = state.New(nil)
+ s.st.Lock()
+ defer s.st.Unlock()
+
+ // mock installed snaps
+ info1 := snaptest.MockSnap(c, string(testSnapYaml), "", &snap.SideInfo{
+ Revision: snap.R(1),
+ })
+ info2 := snaptest.MockSnap(c, string(otherSnapYaml), "", &snap.SideInfo{
+ Revision: snap.R(1),
+ })
+ snapstate.Set(s.st, info1.Name(), &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{
+ {
+ RealName: info1.Name(),
+ Revision: info1.Revision,
+ SnapID: "test-snap-id",
+ },
+ },
+ Current: info1.Revision,
+ })
+ snapstate.Set(s.st, info2.Name(), &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{
+ {
+ RealName: info2.Name(),
+ Revision: info2.Revision,
+ SnapID: "other-snap-id",
+ },
+ },
+ Current: info2.Revision,
+ })
+
+ task := s.st.NewTask("test-task", "my test task")
+ setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "test-hook"}
+
+ var err error
+ s.mockContext, err = hookstate.NewContext(task, task.State(), setup, s.mockHandler, "")
+ c.Assert(err, IsNil)
+}
+
+func (s *servicectlSuite) TearDownTest(c *C) {
+ s.BaseTest.TearDownTest(c)
+}
+
+func (s *servicectlSuite) TestStopCommand(c *C) {
+ var serviceChangeFuncCalled bool
+ restore := mockServiceChangeFunc(func(appInfos []*snap.AppInfo, inst *servicestate.Instruction) {
+ serviceChangeFuncCalled = true
+ c.Assert(appInfos, HasLen, 1)
+ c.Assert(appInfos[0].Name, Equals, "test-service")
+ c.Assert(inst, DeepEquals, &servicestate.Instruction{
+ Action: "stop",
+ Names: []string{"test-snap.test-service"},
+ StopOptions: client.StopOptions{
+ Disable: false,
+ },
+ },
+ )
+ })
+ defer restore()
+ _, _, err := ctlcmd.Run(s.mockContext, []string{"stop", "test-snap.test-service"})
+ c.Assert(err, NotNil)
+ c.Check(err, ErrorMatches, "forced error")
+ c.Assert(serviceChangeFuncCalled, Equals, true)
+}
+
+func (s *servicectlSuite) TestStopCommandUnknownService(c *C) {
+ var serviceChangeFuncCalled bool
+ restore := mockServiceChangeFunc(func(appInfos []*snap.AppInfo, inst *servicestate.Instruction) {
+ serviceChangeFuncCalled = true
+ })
+ defer restore()
+ _, _, err := ctlcmd.Run(s.mockContext, []string{"stop", "test-snap.fooservice"})
+ c.Assert(err, NotNil)
+ c.Assert(err, ErrorMatches, `unknown service: "test-snap.fooservice"`)
+ c.Assert(serviceChangeFuncCalled, Equals, false)
+}
+
+func (s *servicectlSuite) TestStopCommandFailsOnOtherSnap(c *C) {
+ var serviceChangeFuncCalled bool
+ restore := mockServiceChangeFunc(func(appInfos []*snap.AppInfo, inst *servicestate.Instruction) {
+ serviceChangeFuncCalled = true
+ })
+ defer restore()
+ // verify that snapctl is not allowed to control services of other snaps (only the one of its hook)
+ _, _, err := ctlcmd.Run(s.mockContext, []string{"stop", "other-snap.test-service"})
+ c.Check(err, NotNil)
+ c.Assert(err, ErrorMatches, `unknown service: "other-snap.test-service"`)
+ c.Assert(serviceChangeFuncCalled, Equals, false)
+}
+
+func (s *servicectlSuite) TestStartCommand(c *C) {
+ var serviceChangeFuncCalled bool
+ restore := mockServiceChangeFunc(func(appInfos []*snap.AppInfo, inst *servicestate.Instruction) {
+ serviceChangeFuncCalled = true
+ c.Assert(appInfos, HasLen, 1)
+ c.Assert(appInfos[0].Name, Equals, "test-service")
+ c.Assert(inst, DeepEquals, &servicestate.Instruction{
+ Action: "start",
+ Names: []string{"test-snap.test-service"},
+ StartOptions: client.StartOptions{
+ Enable: false,
+ },
+ },
+ )
+ })
+ defer restore()
+ _, _, err := ctlcmd.Run(s.mockContext, []string{"start", "test-snap.test-service"})
+ c.Check(err, NotNil)
+ c.Check(err, ErrorMatches, "forced error")
+ c.Assert(serviceChangeFuncCalled, Equals, true)
+}
+
+func (s *servicectlSuite) TestRestartCommand(c *C) {
+ var serviceChangeFuncCalled bool
+ restore := mockServiceChangeFunc(func(appInfos []*snap.AppInfo, inst *servicestate.Instruction) {
+ serviceChangeFuncCalled = true
+ c.Assert(appInfos, HasLen, 1)
+ c.Assert(appInfos[0].Name, Equals, "test-service")
+ c.Assert(inst, DeepEquals, &servicestate.Instruction{
+ Action: "restart",
+ Names: []string{"test-snap.test-service"},
+ RestartOptions: client.RestartOptions{
+ Reload: false,
+ },
+ },
+ )
+ })
+ defer restore()
+ _, _, err := ctlcmd.Run(s.mockContext, []string{"restart", "test-snap.test-service"})
+ c.Check(err, NotNil)
+ c.Check(err, ErrorMatches, "forced error")
+ c.Assert(serviceChangeFuncCalled, Equals, true)
+}
+
+func (s *servicectlSuite) TestQueuedCommands(c *C) {
+ s.st.Lock()
+ ts := configstate.Configure(s.st, "test-snap", nil, 0)
+ chg := s.st.NewChange("configure change", "configure change")
+ chg.AddAll(ts)
+ s.st.Unlock()
+
+ task := ts.Tasks()[0]
+ setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "configure"}
+ context, err := hookstate.NewContext(task, task.State(), setup, s.mockHandler, "")
+ c.Assert(err, IsNil)
+
+ _, _, err = ctlcmd.Run(context, []string{"stop", "test-snap.test-service"})
+ c.Check(err, IsNil)
+ _, _, err = ctlcmd.Run(context, []string{"start", "test-snap.test-service"})
+ c.Check(err, IsNil)
+ _, _, err = ctlcmd.Run(context, []string{"restart", "test-snap.test-service"})
+ c.Check(err, IsNil)
+
+ s.st.Lock()
+ defer s.st.Unlock()
+
+ allTasks := chg.Tasks()
+ c.Assert(allTasks, HasLen, 4)
+ c.Check(allTasks[0].Summary(), Equals, `Run configure hook of "test-snap" snap if present`)
+ c.Check(allTasks[1].Summary(), Equals, "stop of [test-snap.test-service]")
+ c.Check(allTasks[2].Summary(), Equals, "start of [test-snap.test-service]")
+ c.Check(allTasks[3].Summary(), Equals, "restart of [test-snap.test-service]")
+}
diff -Nru snapd-2.28.5/overlord/hookstate/ctlcmd/set.go snapd-2.29.3/overlord/hookstate/ctlcmd/set.go
--- snapd-2.28.5/overlord/hookstate/ctlcmd/set.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/overlord/hookstate/ctlcmd/set.go 2017-10-23 06:17:27.000000000 +0000
@@ -23,7 +23,7 @@
"fmt"
"strings"
- "github.com/snapcore/snapd/i18n/dumb"
+ "github.com/snapcore/snapd/i18n"
"github.com/snapcore/snapd/jsonutil"
"github.com/snapcore/snapd/overlord/configstate"
"github.com/snapcore/snapd/overlord/hookstate"
diff -Nru snapd-2.28.5/overlord/hookstate/ctlcmd/start.go snapd-2.29.3/overlord/hookstate/ctlcmd/start.go
--- snapd-2.28.5/overlord/hookstate/ctlcmd/start.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/overlord/hookstate/ctlcmd/start.go 2017-11-03 16:15:11.000000000 +0000
@@ -0,0 +1,57 @@
+// -*- 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 ctlcmd
+
+import (
+ "github.com/snapcore/snapd/client"
+ "github.com/snapcore/snapd/i18n"
+ "github.com/snapcore/snapd/overlord/servicestate"
+)
+
+var (
+ shortStartHelp = i18n.G("Start services")
+ longStartHelp = i18n.G(`
+The start command starts the given services of the snap. If executed from the
+"configure" hook, the services will be started after the hook finishes.`)
+)
+
+func init() {
+ // FIXME: uncomment once the feature is fixed to work on install/refresh
+ // addCommand("start", shortStartHelp, longStartHelp, func() command { return &startCommand{} })
+}
+
+type startCommand struct {
+ baseCommand
+ Positional struct {
+ ServiceNames []string `positional-arg-name:"" required:"yes"`
+ } `positional-args:"yes" required:"yes"`
+ Enable bool `long:"enable" description:"Enable the specified services (see man systemctl for details)"`
+}
+
+func (c *startCommand) Execute(args []string) error {
+ inst := servicestate.Instruction{
+ Action: "start",
+ Names: c.Positional.ServiceNames,
+ StartOptions: client.StartOptions{
+ Enable: c.Enable,
+ },
+ }
+ return runServiceCommand(c.context(), &inst, c.Positional.ServiceNames)
+}
diff -Nru snapd-2.28.5/overlord/hookstate/ctlcmd/stop.go snapd-2.29.3/overlord/hookstate/ctlcmd/stop.go
--- snapd-2.28.5/overlord/hookstate/ctlcmd/stop.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/overlord/hookstate/ctlcmd/stop.go 2017-11-03 16:15:11.000000000 +0000
@@ -0,0 +1,57 @@
+// -*- 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 ctlcmd
+
+import (
+ "github.com/snapcore/snapd/client"
+ "github.com/snapcore/snapd/i18n"
+ "github.com/snapcore/snapd/overlord/servicestate"
+)
+
+type stopCommand struct {
+ baseCommand
+ Positional struct {
+ ServiceNames []string `positional-arg-name:"" required:"yes"`
+ } `positional-args:"yes" required:"yes"`
+ Disable bool `long:"disable" description:"Disable the specified services (see man systemctl for details)"`
+}
+
+var (
+ shortStopHelp = i18n.G("Stop services")
+ longStopHelp = i18n.G(`
+The stop command stops the given services of the snap. If executed from the
+"configure" hook, the services will be stopped after the hook finishes.`)
+)
+
+func init() {
+ // FIXME: uncomment once the feature is fixed to work on install/refresh
+ // addCommand("stop", shortStopHelp, longStopHelp, func() command { return &stopCommand{} })
+}
+
+func (c *stopCommand) Execute(args []string) error {
+ inst := servicestate.Instruction{
+ Action: "stop",
+ Names: c.Positional.ServiceNames,
+ StopOptions: client.StopOptions{
+ Disable: c.Disable,
+ },
+ }
+ return runServiceCommand(c.context(), &inst, c.Positional.ServiceNames)
+}
diff -Nru snapd-2.28.5/overlord/hookstate/hooks.go snapd-2.29.3/overlord/hookstate/hooks.go
--- snapd-2.28.5/overlord/hookstate/hooks.go 2017-10-04 14:43:22.000000000 +0000
+++ snapd-2.29.3/overlord/hookstate/hooks.go 2017-10-23 06:17:27.000000000 +0000
@@ -21,7 +21,7 @@
"fmt"
"regexp"
- "github.com/snapcore/snapd/i18n/dumb"
+ "github.com/snapcore/snapd/i18n"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
)
diff -Nru snapd-2.28.5/overlord/hookstate/hookstate_test.go snapd-2.29.3/overlord/hookstate/hookstate_test.go
--- snapd-2.28.5/overlord/hookstate/hookstate_test.go 2017-08-30 09:16:36.000000000 +0000
+++ snapd-2.29.3/overlord/hookstate/hookstate_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -32,6 +32,7 @@
. "gopkg.in/check.v1"
"github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/overlord"
"github.com/snapcore/snapd/overlord/hookstate"
"github.com/snapcore/snapd/overlord/hookstate/hooktest"
"github.com/snapcore/snapd/overlord/snapstate"
@@ -46,6 +47,7 @@
type hookManagerSuite struct {
testutil.BaseTest
+ o *overlord.Overlord
state *state.State
manager *hookstate.HookManager
context *hookstate.Context
@@ -85,10 +87,14 @@
s.BaseTest.SetUpTest(c)
dirs.SetRootDir(c.MkDir())
- s.state = state.New(nil)
+ s.o = overlord.Mock()
+ s.state = s.o.State()
manager, err := hookstate.Manager(s.state)
c.Assert(err, IsNil)
s.manager = manager
+ s.o.AddManager(s.manager)
+
+ s.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
hooksup := &hookstate.HookSetup{
Snap: "test-snap",
@@ -137,11 +143,9 @@
dirs.SetRootDir("")
}
-func (s *hookManagerSuite) settle() {
- for i := 0; i < 50; i++ {
- s.manager.Ensure()
- s.manager.Wait()
- }
+func (s *hookManagerSuite) settle(c *C) {
+ err := s.o.Settle(5 * time.Second)
+ c.Assert(err, IsNil)
}
func (s *hookManagerSuite) TestSmoke(c *C) {
@@ -726,7 +730,7 @@
}
s.state.Unlock()
- s.settle()
+ s.settle(c)
s.state.Lock()
defer s.state.Unlock()
@@ -828,7 +832,7 @@
s.state.Unlock()
- s.settle()
+ s.settle(c)
s.state.Lock()
defer s.state.Unlock()
diff -Nru snapd-2.28.5/overlord/ifacestate/handlers.go snapd-2.29.3/overlord/ifacestate/handlers.go
--- snapd-2.28.5/overlord/ifacestate/handlers.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/overlord/ifacestate/handlers.go 2017-10-23 06:17:27.000000000 +0000
@@ -148,12 +148,12 @@
return err
}
if err := m.repo.AddSnap(snapInfo); err != nil {
- if _, ok := err.(*interfaces.BadInterfacesError); ok {
- task.Logf("%s", err)
- } else {
- return err
- }
+ return err
+ }
+ if len(snapInfo.BadInterfaces) > 0 {
+ task.Logf("%s", snap.BadInterfacesSummary(snapInfo))
}
+
if err := m.reloadConnections(snapName); err != nil {
return err
}
diff -Nru snapd-2.28.5/overlord/ifacestate/helpers.go snapd-2.29.3/overlord/ifacestate/helpers.go
--- snapd-2.28.5/overlord/ifacestate/helpers.go 2017-10-13 18:02:58.000000000 +0000
+++ snapd-2.29.3/overlord/ifacestate/helpers.go 2017-10-23 06:17:27.000000000 +0000
@@ -138,12 +138,8 @@
// For each backend:
for _, backend := range securityBackends {
- // 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 || backend.Name() == interfaces.SecurityUDev)
- if !shouldRefresh {
- continue
+ if backend.Name() == "" {
+ continue // Test backends have no name, skip them to simplify testing.
}
// Refresh security of this snap and backend
if err := backend.Setup(snapInfo, opts, m.repo); err != nil {
diff -Nru snapd-2.28.5/overlord/ifacestate/ifacemgr.go snapd-2.29.3/overlord/ifacestate/ifacemgr.go
--- snapd-2.28.5/overlord/ifacestate/ifacemgr.go 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/overlord/ifacestate/ifacemgr.go 2017-10-23 06:17:27.000000000 +0000
@@ -38,6 +38,8 @@
// Manager returns a new InterfaceManager.
// Extra interfaces can be provided for testing.
func Manager(s *state.State, hookManager *hookstate.HookManager, extraInterfaces []interfaces.Interface, extraBackends []interfaces.SecurityBackend) (*InterfaceManager, error) {
+ delayedCrossMgrInit()
+
// NOTE: hookManager is nil only when testing.
if hookManager != nil {
setupHooks(hookManager)
@@ -49,6 +51,7 @@
runner: runner,
repo: interfaces.NewRepository(),
}
+
if err := m.initialize(extraInterfaces, extraBackends); err != nil {
return nil, err
}
diff -Nru snapd-2.28.5/overlord/ifacestate/ifacestate.go snapd-2.29.3/overlord/ifacestate/ifacestate.go
--- snapd-2.28.5/overlord/ifacestate/ifacestate.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/overlord/ifacestate/ifacestate.go 2017-10-23 06:17:27.000000000 +0000
@@ -23,8 +23,9 @@
import (
"fmt"
+ "sync"
- "github.com/snapcore/snapd/i18n/dumb"
+ "github.com/snapcore/snapd/i18n"
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/interfaces/policy"
"github.com/snapcore/snapd/overlord/assertstate"
@@ -201,9 +202,13 @@
return ic.Check()
}
-func init() {
+var once sync.Once
+
+func delayedCrossMgrInit() {
// hook interface checks into snapstate installation logic
- snapstate.AddCheckSnapCallback(func(st *state.State, snapInfo, _ *snap.Info, _ snapstate.Flags) error {
- return CheckInterfaces(st, snapInfo)
+ once.Do(func() {
+ snapstate.AddCheckSnapCallback(func(st *state.State, snapInfo, _ *snap.Info, _ snapstate.Flags) error {
+ return CheckInterfaces(st, snapInfo)
+ })
})
}
diff -Nru snapd-2.28.5/overlord/ifacestate/ifacestate_test.go snapd-2.29.3/overlord/ifacestate/ifacestate_test.go
--- snapd-2.28.5/overlord/ifacestate/ifacestate_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/overlord/ifacestate/ifacestate_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -32,6 +32,7 @@
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/interfaces/ifacetest"
+ "github.com/snapcore/snapd/overlord"
"github.com/snapcore/snapd/overlord/assertstate"
"github.com/snapcore/snapd/overlord/hookstate"
"github.com/snapcore/snapd/overlord/ifacestate"
@@ -45,28 +46,30 @@
func TestInterfaceManager(t *testing.T) { TestingT(t) }
type interfaceManagerSuite struct {
- state *state.State
- db *asserts.Database
- privateMgr *ifacestate.InterfaceManager
- privateHookMgr *hookstate.HookManager
- extraIfaces []interfaces.Interface
- extraBackends []interfaces.SecurityBackend
- secBackend *ifacetest.TestSecurityBackend
- restoreBackends func()
- mockSnapCmd *testutil.MockCmd
- storeSigning *assertstest.StoreStack
+ testutil.BaseTest
+ o *overlord.Overlord
+ state *state.State
+ db *asserts.Database
+ privateMgr *ifacestate.InterfaceManager
+ privateHookMgr *hookstate.HookManager
+ extraIfaces []interfaces.Interface
+ extraBackends []interfaces.SecurityBackend
+ secBackend *ifacetest.TestSecurityBackend
+ mockSnapCmd *testutil.MockCmd
+ storeSigning *assertstest.StoreStack
}
var _ = Suite(&interfaceManagerSuite{})
func (s *interfaceManagerSuite) SetUpTest(c *C) {
+ s.BaseTest.SetUpTest(c)
s.storeSigning = assertstest.NewStoreStack("canonical", nil)
s.mockSnapCmd = testutil.MockCommand(c, "snap", "")
dirs.SetRootDir(c.MkDir())
- state := state.New(nil)
- s.state = state
+ s.o = overlord.Mock()
+ s.state = s.o.State()
db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{
Backstore: asserts.NewMemoryBackstore(),
Trusted: s.storeSigning.Trusted,
@@ -76,8 +79,10 @@
err = db.Add(s.storeSigning.StoreAccountKey(""))
c.Assert(err, IsNil)
+ s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
+
s.state.Lock()
- assertstate.ReplaceDB(state, s.db)
+ assertstate.ReplaceDB(s.state, s.db)
s.state.Unlock()
s.privateHookMgr = nil
@@ -88,17 +93,18 @@
// TODO: transition this so that we don't load real backends and instead
// just load the test backend here and this is nicely integrated with
// extraBackends above.
- s.restoreBackends = ifacestate.MockSecurityBackends([]interfaces.SecurityBackend{s.secBackend})
+ s.BaseTest.AddCleanup(ifacestate.MockSecurityBackends([]interfaces.SecurityBackend{s.secBackend}))
}
func (s *interfaceManagerSuite) TearDownTest(c *C) {
+ s.BaseTest.TearDownTest(c)
+
s.mockSnapCmd.Restore()
if s.privateMgr != nil {
s.privateMgr.Stop()
}
dirs.SetRootDir("")
- s.restoreBackends()
}
func (s *interfaceManagerSuite) manager(c *C) *ifacestate.InterfaceManager {
@@ -107,6 +113,7 @@
c.Assert(err, IsNil)
mgr.AddForeignTaskHandlers()
s.privateMgr = mgr
+ s.o.AddManager(mgr)
}
return s.privateMgr
}
@@ -116,17 +123,14 @@
mgr, err := hookstate.Manager(s.state)
c.Assert(err, IsNil)
s.privateHookMgr = mgr
+ s.o.AddManager(mgr)
}
return s.privateHookMgr
}
func (s *interfaceManagerSuite) settle(c *C) {
- for i := 0; i < 50; i++ {
- s.hookManager(c).Ensure()
- s.manager(c).Ensure()
- s.hookManager(c).Wait()
- s.manager(c).Wait()
- }
+ err := s.o.Settle(5 * time.Second)
+ c.Assert(err, IsNil)
}
func (s *interfaceManagerSuite) TestSmoke(c *C) {
@@ -136,7 +140,7 @@
}
func (s *interfaceManagerSuite) TestConnectTask(c *C) {
- s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
+ s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
s.mockSnap(c, consumerYaml)
s.mockSnap(c, producerYaml)
_ = s.manager(c)
@@ -262,7 +266,7 @@
}
func (s *interfaceManagerSuite) TestEnsureProcessesConnectTask(c *C) {
- s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
+ s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
s.mockSnap(c, consumerYaml)
s.mockSnap(c, producerYaml)
_ = s.manager(c)
@@ -312,8 +316,7 @@
}
func (s *interfaceManagerSuite) TestConnectTaskCheckInterfaceMismatch(c *C) {
- s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
- s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test2"})
+ s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
s.mockSnap(c, consumerYaml)
s.mockSnap(c, producerYaml)
_ = s.manager(c)
@@ -346,7 +349,7 @@
}
func (s *interfaceManagerSuite) TestConnectTaskNoSuchSlot(c *C) {
- s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
+ s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
s.mockSnap(c, consumerYaml)
s.mockSnap(c, producerYaml)
_ = s.manager(c)
@@ -358,7 +361,7 @@
}
func (s *interfaceManagerSuite) TestConnectTaskNoSuchPlug(c *C) {
- s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
+ s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
s.mockSnap(c, consumerYaml)
s.mockSnap(c, producerYaml)
_ = s.manager(c)
@@ -435,7 +438,7 @@
- $SLOT_PUBLISHER_ID
`))
defer restore()
- s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
+ s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
setup()
_ = s.manager(c)
@@ -491,7 +494,7 @@
func (s *interfaceManagerSuite) testDisconnect(c *C, plugSnap, plugName, slotSnap, slotName string) {
// Put two snaps in place They consumer has an plug that can be connected
// to slot on the producer.
- s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
+ s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
s.mockSnap(c, consumerYaml)
s.mockSnap(c, producerYaml)
@@ -560,11 +563,15 @@
s.extraIfaces = append(s.extraIfaces, iface)
}
+func (s *interfaceManagerSuite) mockIfaces(c *C, ifaces ...interfaces.Interface) {
+ s.extraIfaces = append(s.extraIfaces, ifaces...)
+}
+
func (s *interfaceManagerSuite) mockSnapDecl(c *C, name, publisher string, extraHeaders map[string]interface{}) {
_, err := s.db.Find(asserts.AccountType, map[string]string{
"account-id": publisher,
})
- if err == asserts.ErrNotFound {
+ if asserts.IsNotFound(err) {
acct := assertstest.NewAccount(s.storeSigning, publisher, map[string]interface{}{
"account-id": publisher,
}, "")
@@ -604,7 +611,7 @@
decl := a[0].(*asserts.SnapDeclaration)
snapInfo.SnapID = decl.SnapID()
sideInfo.SnapID = decl.SnapID()
- } else if err == asserts.ErrNotFound {
+ } else if asserts.IsNotFound(err) {
err = nil
}
c.Assert(err, IsNil)
@@ -843,7 +850,7 @@
// The setup-profiles task will auto-connect slots with viable candidates.
func (s *interfaceManagerSuite) TestDoSetupSnapSecurityAutoConnectsSlots(c *C) {
// Mock the interface that will be used by the test
- s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
+ s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
// Add an OS snap.
s.mockSnap(c, ubuntuCoreSnapYaml)
// Add a consumer snap with unconnect plug (interface "test")
@@ -892,7 +899,7 @@
// The setup-profiles task will auto-connect slots with viable multiple candidates.
func (s *interfaceManagerSuite) TestDoSetupSnapSecurityAutoConnectsSlotsMultiplePlugs(c *C) {
// Mock the interface that will be used by the test
- s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
+ s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
// Add an OS snap.
s.mockSnap(c, ubuntuCoreSnapYaml)
// Add a consumer snap with unconnect plug (interface "test")
@@ -1019,7 +1026,7 @@
`))
defer restore()
// Add the producer snap
- s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
+ s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
s.mockSnapDecl(c, "producer", "one-publisher", nil)
s.mockSnap(c, producerYaml)
@@ -1150,20 +1157,20 @@
}
func (s *interfaceManagerSuite) TestDoSetupSnapSecuirtyReloadsConnectionsWhenInvokedOnPlugSide(c *C) {
+ s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
snapInfo := s.mockSnap(c, consumerYaml)
s.mockSnap(c, producerYaml)
s.testDoSetupSnapSecuirtyReloadsConnectionsWhenInvokedOn(c, snapInfo.Name(), snapInfo.Revision)
}
func (s *interfaceManagerSuite) TestDoSetupSnapSecuirtyReloadsConnectionsWhenInvokedOnSlotSide(c *C) {
+ s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
s.mockSnap(c, consumerYaml)
snapInfo := s.mockSnap(c, producerYaml)
s.testDoSetupSnapSecuirtyReloadsConnectionsWhenInvokedOn(c, snapInfo.Name(), snapInfo.Revision)
}
func (s *interfaceManagerSuite) testDoSetupSnapSecuirtyReloadsConnectionsWhenInvokedOn(c *C, snapName string, revision snap.Revision) {
- s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
-
s.state.Lock()
s.state.Set("conns", map[string]interface{}{
"consumer:plug producer:slot": map[string]interface{}{"interface": "test"},
@@ -1437,7 +1444,7 @@
}
func (s *interfaceManagerSuite) TestDoRemove(c *C) {
- s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
+ s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
s.mockSnap(c, consumerYaml)
s.mockSnap(c, producerYaml)
@@ -1482,7 +1489,7 @@
}
func (s *interfaceManagerSuite) TestConnectTracksConnectionsInState(c *C) {
- s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
+ s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
s.mockSnap(c, consumerYaml)
s.mockSnap(c, producerYaml)
@@ -1521,10 +1528,10 @@
}
func (s *interfaceManagerSuite) TestConnectSetsUpSecurity(c *C) {
- s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
+ s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
+
s.mockSnap(c, consumerYaml)
s.mockSnap(c, producerYaml)
-
_ = s.manager(c)
s.state.Lock()
@@ -1558,7 +1565,7 @@
}
func (s *interfaceManagerSuite) TestDisconnectSetsUpSecurity(c *C) {
- s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
+ s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
s.mockSnap(c, consumerYaml)
s.mockSnap(c, producerYaml)
@@ -1603,7 +1610,7 @@
}
func (s *interfaceManagerSuite) TestDisconnectTracksConnectionsInState(c *C) {
- s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
+ s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
s.mockSnap(c, consumerYaml)
s.mockSnap(c, producerYaml)
s.state.Lock()
@@ -1643,7 +1650,7 @@
}
func (s *interfaceManagerSuite) TestManagerReloadsConnections(c *C) {
- s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
+ s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
s.mockSnap(c, consumerYaml)
s.mockSnap(c, producerYaml)
@@ -1675,6 +1682,11 @@
InterfaceName: "test",
})
c.Assert(err, IsNil)
+ err = repo.AddInterface(&ifacetest.TestInterface{
+ InterfaceName: "test2",
+ })
+ c.Assert(err, IsNil)
+
err = repo.AddSlot(&interfaces.Slot{
SlotInfo: &snap.SlotInfo{
Snap: siC,
diff -Nru snapd-2.28.5/overlord/managers_test.go snapd-2.29.3/overlord/managers_test.go
--- snapd-2.28.5/overlord/managers_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/overlord/managers_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -49,6 +49,7 @@
"github.com/snapcore/snapd/overlord/hookstate"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/overlord/storestate"
"github.com/snapcore/snapd/partition"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
@@ -61,29 +62,28 @@
type mgrsSuite struct {
tempdir string
- aa *testutil.MockCmd
- udev *testutil.MockCmd
- umount *testutil.MockCmd
+ restore func()
- snapDiscardNs *testutil.MockCmd
+ aa *testutil.MockCmd
+ udev *testutil.MockCmd
+ umount *testutil.MockCmd
+ restoreSystemctl func()
- prevctlCmd func(...string) ([]byte, error)
+ snapDiscardNs *testutil.MockCmd
+ snapSeccomp *testutil.MockCmd
storeSigning *assertstest.StoreStack
restoreTrusted func()
- restore func()
devAcct *asserts.Account
- o *overlord.Overlord
-
serveIDtoName map[string]string
serveSnapPath map[string]string
serveRevision map[string]string
hijackServeSnap func(http.ResponseWriter)
- snapSeccomp *testutil.MockCmd
+ o *overlord.Overlord
}
var (
@@ -116,15 +116,14 @@
}
os.Setenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS", "1")
- snapstate.CanAutoRefresh = nil
// create a fake systemd environment
os.MkdirAll(filepath.Join(dirs.SnapServicesDir, "multi-user.target.wants"), 0755)
- ms.prevctlCmd = systemd.SystemctlCmd
- systemd.SystemctlCmd = func(cmd ...string) ([]byte, error) {
+ ms.restoreSystemctl = systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
return []byte("ActiveState=inactive\n"), nil
- }
+ })
+
ms.aa = testutil.MockCommand(c, "apparmor_parser", "")
ms.udev = testutil.MockCommand(c, "udevadm", "")
ms.umount = testutil.MockCommand(c, "umount", "")
@@ -140,12 +139,21 @@
err = ms.storeSigning.Add(ms.devAcct)
c.Assert(err, IsNil)
+ ms.serveIDtoName = make(map[string]string)
+ ms.serveSnapPath = make(map[string]string)
+ ms.serveRevision = make(map[string]string)
+
+ snapSeccompPath := filepath.Join(dirs.DistroLibExecDir, "snap-seccomp")
+ err = os.MkdirAll(filepath.Dir(snapSeccompPath), 0755)
+ c.Assert(err, IsNil)
+ ms.snapSeccomp = testutil.MockCommand(c, snapSeccompPath, "")
+
o, err := overlord.New()
c.Assert(err, IsNil)
ms.o = o
st := ms.o.State()
st.Lock()
- // seeded
+ defer st.Unlock()
st.Set("seeded", true)
// registered
auth.SetDevice(st, &auth.DeviceState{
@@ -153,24 +161,46 @@
Model: "generic-classic",
Serial: "serialserial",
})
- st.Unlock()
- ms.serveIDtoName = make(map[string]string)
- ms.serveSnapPath = make(map[string]string)
- ms.serveRevision = make(map[string]string)
-
- snapSeccompPath := filepath.Join(dirs.DistroLibExecDir, "snap-seccomp")
- err = os.MkdirAll(filepath.Dir(snapSeccompPath), 0755)
+ // add "core" snap declaration
+ headers := map[string]interface{}{
+ "series": "16",
+ "snap-name": "core",
+ "publisher-id": "can0nical",
+ "timestamp": time.Now().Format(time.RFC3339),
+ }
+ headers["snap-id"] = fakeSnapID(headers["snap-name"].(string))
+ err = assertstate.Add(st, ms.storeSigning.StoreAccountKey(""))
c.Assert(err, IsNil)
- ms.snapSeccomp = testutil.MockCommand(c, snapSeccompPath, "")
+ a, err := ms.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "")
+ c.Assert(err, IsNil)
+ err = assertstate.Add(st, a)
+ c.Assert(err, IsNil)
+ ms.serveRevision["core"] = "1"
+ ms.serveIDtoName[fakeSnapID("core")] = "core"
+ err = ms.storeSigning.Add(a)
+ c.Assert(err, IsNil)
+
+ // add core itself
+ snapstate.Set(st, "core", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{
+ {RealName: "core", SnapID: fakeSnapID("core"), Revision: snap.R(1)},
+ },
+ Current: snap.R(1),
+ SnapType: "os",
+ })
+ // don't actually try to talk to the store on snapstate.Ensure
+ // needs doing after the call to devicestate.Manager (which happens in overlord.New)
+ snapstate.CanAutoRefresh = nil
}
func (ms *mgrsSuite) TearDownTest(c *C) {
dirs.SetRootDir("")
ms.restoreTrusted()
ms.restore()
+ ms.restoreSystemctl()
os.Unsetenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS")
- systemd.SystemctlCmd = ms.prevctlCmd
ms.udev.Restore()
ms.aa.Restore()
ms.umount.Restore()
@@ -178,6 +208,8 @@
ms.snapSeccomp.Restore()
}
+var settleTimeout = 15 * time.Second
+
func makeTestSnap(c *C, snapYamlContent string) string {
return snaptest.MakeTestSnapWithFiles(c, snapYamlContent, nil)
}
@@ -200,7 +232,7 @@
chg.AddAll(ts)
st.Unlock()
- err = ms.o.Settle()
+ err = ms.o.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)
@@ -250,7 +282,7 @@
chg.AddAll(ts)
st.Unlock()
- err = ms.o.Settle()
+ err = ms.o.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)
@@ -349,7 +381,7 @@
}
func (ms *mgrsSuite) mockStore(c *C) *httptest.Server {
- var baseURL string
+ var baseURL *url.URL
fillHit := func(name string) string {
snapf, err := snap.Open(ms.serveSnapPath[name])
if err != nil {
@@ -359,29 +391,32 @@
if err != nil {
panic(err)
}
- hit := strings.Replace(searchHit, "@URL@", baseURL+"/snap/"+name, -1)
+ hit := strings.Replace(searchHit, "@URL@", baseURL.String()+"/api/v1/snaps/download/"+name, -1)
hit = strings.Replace(hit, "@NAME@", name, -1)
hit = strings.Replace(hit, "@SNAPID@", fakeSnapID(name), -1)
- hit = strings.Replace(hit, "@ICON@", baseURL+"/icon", -1)
+ hit = strings.Replace(hit, "@ICON@", baseURL.String()+"/icon", -1)
hit = strings.Replace(hit, "@VERSION@", info.Version, -1)
hit = strings.Replace(hit, "@REVISION@", ms.serveRevision[name], -1)
return hit
}
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // all URLS are /api/v1/snaps/... so check the url is sane and discard
+ // the common prefix to simplify indexing into the comps slice.
comps := strings.Split(r.URL.Path, "/")
- if len(comps) == 0 {
+ if len(comps) <= 4 {
panic("unexpected url path: " + r.URL.Path)
-
}
- switch comps[1] {
+ comps = comps[4:]
+
+ switch comps[0] {
case "assertions":
ref := &asserts.Ref{
- Type: asserts.Type(comps[2]),
- PrimaryKey: comps[3:],
+ Type: asserts.Type(comps[1]),
+ PrimaryKey: comps[2:],
}
a, err := ref.Resolve(ms.storeSigning.Find)
- if err == asserts.ErrNotFound {
+ if asserts.IsNotFound(err) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(404)
w.Write([]byte(`{"status": 404}`))
@@ -396,7 +431,7 @@
return
case "details":
w.WriteHeader(200)
- io.WriteString(w, fillHit(comps[2]))
+ io.WriteString(w, fillHit(comps[1]))
case "metadata":
dec := json.NewDecoder(r.Body)
var input struct {
@@ -427,12 +462,12 @@
panic(err)
}
w.Write(output)
- case "snap":
+ case "download":
if ms.hijackServeSnap != nil {
ms.hijackServeSnap(w)
return
}
- snapR, err := os.Open(ms.serveSnapPath[comps[2]])
+ snapR, err := os.Open(ms.serveSnapPath[comps[1]])
if err != nil {
panic(err)
}
@@ -443,24 +478,17 @@
}))
c.Assert(mockServer, NotNil)
- baseURL = mockServer.URL
-
- detailsURL, err := url.Parse(baseURL + "/details/")
- c.Assert(err, IsNil)
- bulkURL, err := url.Parse(baseURL + "/metadata")
- c.Assert(err, IsNil)
- assertionsURL, err := url.Parse(baseURL + "/assertions/")
- c.Assert(err, IsNil)
+ baseURL, _ = url.Parse(mockServer.URL)
+ assertionsBaseURL, _ := baseURL.Parse("api/v1/snaps")
storeCfg := store.Config{
- DetailsURI: detailsURL,
- BulkURI: bulkURL,
- AssertionsURI: assertionsURL,
+ StoreBaseURL: baseURL,
+ AssertionsBaseURL: assertionsBaseURL,
}
mStore := store.New(&storeCfg, nil)
st := ms.o.State()
st.Lock()
- snapstate.ReplaceStore(ms.o.State(), mStore)
+ storestate.ReplaceStore(ms.o.State(), mStore)
st.Unlock()
return mockServer
@@ -516,7 +544,7 @@
chg.AddAll(ts)
st.Unlock()
- err = ms.o.Settle()
+ err = ms.o.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)
@@ -565,7 +593,7 @@
chg.AddAll(ts)
st.Unlock()
- err = ms.o.Settle()
+ err = ms.o.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)
@@ -618,9 +646,7 @@
defer st.Unlock()
// have the snap-declaration in the system db
- err := assertstate.Add(st, ms.storeSigning.StoreAccountKey(""))
- c.Assert(err, IsNil)
- err = assertstate.Add(st, ms.devAcct)
+ err := assertstate.Add(st, ms.devAcct)
c.Assert(err, IsNil)
err = assertstate.Add(st, snapDecl)
c.Assert(err, IsNil)
@@ -631,7 +657,7 @@
chg.AddAll(ts)
st.Unlock()
- err = ms.o.Settle()
+ err = ms.o.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)
@@ -688,9 +714,7 @@
defer st.Unlock()
// have the snap-declaration in the system db
- err := assertstate.Add(st, ms.storeSigning.StoreAccountKey(""))
- c.Assert(err, IsNil)
- err = assertstate.Add(st, ms.devAcct)
+ err := assertstate.Add(st, ms.devAcct)
c.Assert(err, IsNil)
err = assertstate.Add(st, snapDecl)
c.Assert(err, IsNil)
@@ -701,7 +725,7 @@
chg.AddAll(ts)
st.Unlock()
- err = ms.o.Settle()
+ err = ms.o.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)
@@ -738,7 +762,7 @@
chg.AddAll(ts)
st.Unlock()
- err = ms.o.Settle()
+ err = ms.o.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)
@@ -816,7 +840,7 @@
chg.AddAll(tss[0])
st.Unlock()
- err = ms.o.Settle()
+ err = ms.o.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)
@@ -856,7 +880,7 @@
chg.AddAll(ts)
st.Unlock()
- err = ms.o.Settle()
+ err = ms.o.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)
@@ -876,7 +900,7 @@
bootloader.BootVars["snap_core"] = "core_x1.snap"
st.Unlock()
- err = ms.o.Settle()
+ err = ms.o.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)
@@ -929,8 +953,6 @@
defer st.Unlock()
// setup model assertion
- err = assertstate.Add(st, ms.storeSigning.StoreAccountKey(""))
- c.Assert(err, IsNil)
err = assertstate.Add(st, brandAcct)
c.Assert(err, IsNil)
err = assertstate.Add(st, brandAccKey)
@@ -948,7 +970,7 @@
chg.AddAll(ts)
st.Unlock()
- err = ms.o.Settle()
+ err = ms.o.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)
@@ -980,7 +1002,7 @@
chg.AddAll(ts)
st.Unlock()
- err = ms.o.Settle()
+ err = ms.o.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)
@@ -999,7 +1021,7 @@
chg.AddAll(ts)
st.Unlock()
- err = ms.o.Settle()
+ err = ms.o.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)
@@ -1044,7 +1066,7 @@
chg.AddAll(ts)
st.Unlock()
- err = ms.o.Settle()
+ err = ms.o.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)
@@ -1083,7 +1105,7 @@
chg.AddAll(ts)
st.Unlock()
- err = ms.o.Settle()
+ err = ms.o.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)
@@ -1129,7 +1151,7 @@
chg.AddAll(ts)
st.Unlock()
- err = ms.o.Settle()
+ err = ms.o.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)
@@ -1149,7 +1171,7 @@
chg.AddAll(ts)
st.Unlock()
- err = ms.o.Settle()
+ err = ms.o.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)
@@ -1202,7 +1224,7 @@
chg.AddAll(ts)
st.Unlock()
- err = ms.o.Settle()
+ err = ms.o.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)
@@ -1262,7 +1284,7 @@
chg.AddAll(ts)
st.Unlock()
- err = ms.o.Settle()
+ err = ms.o.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)
@@ -1307,7 +1329,7 @@
chg.AddAll(tss[0])
st.Unlock()
- err = ms.o.Settle()
+ err = ms.o.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)
@@ -1367,7 +1389,7 @@
chg.AddAll(ts)
st.Unlock()
- err = ms.o.Settle()
+ err = ms.o.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)
@@ -1408,7 +1430,7 @@
chg.AddAll(ts)
st.Unlock()
- err = ms.o.Settle()
+ err = ms.o.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)
@@ -1480,7 +1502,7 @@
chg.AddAll(ts)
st.Unlock()
- err = ms.o.Settle()
+ err = ms.o.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)
@@ -1494,7 +1516,7 @@
chg.AddAll(ts)
st.Unlock()
- err = ms.o.Settle()
+ err = ms.o.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)
@@ -1556,7 +1578,7 @@
chg.AddAll(tss[2])
st.Unlock()
- err = ms.o.Settle()
+ err = ms.o.Settle(settleTimeout)
st.Lock()
c.Assert(err, IsNil)
@@ -1695,11 +1717,11 @@
err := os.MkdirAll(filepath.Dir(dirs.SnapStateFile), 0755)
c.Assert(err, IsNil)
- captureAuthContext := func(_ *store.Config, ac auth.AuthContext) *store.Store {
+ captureAuthContext := func(_ *state.State, ac auth.AuthContext) error {
s.ac = ac
return nil
}
- r := overlord.MockStoreNew(captureAuthContext)
+ r := overlord.MockSetupStore(captureAuthContext)
defer r()
s.storeSigning = assertstest.NewStoreStack("can0nical", nil)
diff -Nru snapd-2.28.5/overlord/overlord.go snapd-2.29.3/overlord/overlord.go
--- snapd-2.28.5/overlord/overlord.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/overlord/overlord.go 2017-10-23 06:17:27.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
@@ -43,7 +43,7 @@
"github.com/snapcore/snapd/overlord/patch"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
- "github.com/snapcore/snapd/store"
+ "github.com/snapcore/snapd/overlord/storestate"
)
var (
@@ -68,6 +68,7 @@
// restarts
restartHandler func(t state.RestartType)
// managers
+ inited bool
snapMgr *snapstate.SnapManager
assertMgr *assertstate.AssertManager
ifaceMgr *ifacestate.InterfaceManager
@@ -77,12 +78,13 @@
cmdMgr *cmdstate.CommandManager
}
-var storeNew = store.New
+var setupStore = storestate.SetupStore
// New creates a new Overlord with all its state managers.
func New() (*Overlord, error) {
o := &Overlord{
loopTomb: new(tomb.Tomb),
+ inited: true,
}
backend := &overlordStateBackend{
@@ -101,30 +103,27 @@
if err != nil {
return nil, err
}
- o.hookMgr = hookMgr
- o.stateEng.AddManager(o.hookMgr)
+ o.addManager(hookMgr)
snapMgr, err := snapstate.Manager(s)
if err != nil {
return nil, err
}
- o.snapMgr = snapMgr
- o.stateEng.AddManager(o.snapMgr)
+ o.addManager(snapMgr)
assertMgr, err := assertstate.Manager(s)
if err != nil {
return nil, err
}
- o.assertMgr = assertMgr
- o.stateEng.AddManager(o.assertMgr)
+ o.addManager(assertMgr)
ifaceMgr, err := ifacestate.Manager(s, hookMgr, nil, nil)
if err != nil {
return nil, err
}
- o.ifaceMgr = ifaceMgr
- o.stateEng.AddManager(o.ifaceMgr)
+ o.addManager(ifaceMgr)
+ // TODO: this is a bit weird, not actually a StateManager
configMgr, err := configstate.Manager(s, hookMgr)
if err != nil {
return nil, err
@@ -135,25 +134,45 @@
if err != nil {
return nil, err
}
- o.deviceMgr = deviceMgr
- o.stateEng.AddManager(o.deviceMgr)
+ o.addManager(deviceMgr)
- o.cmdMgr = cmdstate.Manager(s)
- o.stateEng.AddManager(o.cmdMgr)
+ o.addManager(cmdstate.Manager(s))
+
+ s.Lock()
+ defer s.Unlock()
// setting up the store
authContext := auth.NewAuthContext(s, o.deviceMgr)
- sto := storeNew(nil, authContext)
- s.Lock()
- snapstate.ReplaceStore(s, sto)
+ err = setupStore(s, authContext)
+ if err != nil {
+ return nil, err
+ }
+
if err := o.snapMgr.GenerateCookies(s); err != nil {
return nil, fmt.Errorf("failed to generate cookies: %q", err)
}
- s.Unlock()
return o, nil
}
+func (o *Overlord) addManager(mgr StateManager) {
+ switch x := mgr.(type) {
+ case *hookstate.HookManager:
+ o.hookMgr = x
+ case *snapstate.SnapManager:
+ o.snapMgr = x
+ case *assertstate.AssertManager:
+ o.assertMgr = x
+ case *ifacestate.InterfaceManager:
+ o.ifaceMgr = x
+ case *devicestate.DeviceManager:
+ o.deviceMgr = x
+ case *cmdstate.CommandManager:
+ o.cmdMgr = x
+ }
+ o.stateEng.AddManager(mgr)
+}
+
func loadState(backend state.Backend) (*state.State, error) {
if !osutil.FileExists(dirs.SnapStateFile) {
// fail fast, mostly interesting for tests, this dir is setup
@@ -263,9 +282,10 @@
// Settle runs first a state engine Ensure and then wait for activities to settle.
// That's done by waiting for all managers activities to settle while
-// making sure no immediate further Ensure is scheduled. Chiefly for tests.
-// Cannot be used in conjunction with Loop.
-func (o *Overlord) Settle() error {
+// making sure no immediate further Ensure is scheduled. Chiefly for
+// tests. Cannot be used in conjunction with Loop. If timeout is
+// non-zero and settling takes longer than timeout, returns an error.
+func (o *Overlord) Settle(timeout time.Duration) error {
func() {
o.ensureLock.Lock()
defer o.ensureLock.Unlock()
@@ -282,9 +302,17 @@
o.ensureTimer = nil
}()
+ t0 := time.Now()
done := false
var errs []error
for !done {
+ if timeout > 0 && time.Since(t0) > timeout {
+ err := fmt.Errorf("Settle is not converging")
+ if len(errs) != 0 {
+ return &ensureError{append(errs, err)}
+ }
+ return err
+ }
next := o.ensureTimerReset()
err := o.stateEng.Ensure()
switch ee := err.(type) {
@@ -298,6 +326,18 @@
o.ensureLock.Lock()
done = o.ensureNext.Equal(next)
o.ensureLock.Unlock()
+ if done {
+ // we should wait also for cleanup handlers
+ st := o.State()
+ st.Lock()
+ for _, chg := range st.Changes() {
+ if chg.IsReady() && !chg.IsClean() {
+ done = false
+ break
+ }
+ }
+ st.Unlock()
+ }
}
if len(errs) != 0 {
return &ensureError{errs}
@@ -343,3 +383,46 @@
func (o *Overlord) CommandManager() *cmdstate.CommandManager {
return o.cmdMgr
}
+
+// Mock creates an Overlord without any managers and with a backend
+// not using disk. Managers can be added with AddManager. For testing.
+func Mock() *Overlord {
+ o := &Overlord{
+ loopTomb: new(tomb.Tomb),
+ inited: false,
+ }
+ o.stateEng = NewStateEngine(state.New(mockBackend{o: o}))
+ return o
+}
+
+// AddManager adds a manager to the overlord created with Mock. For
+// testing.
+func (o *Overlord) AddManager(mgr StateManager) {
+ if o.inited {
+ panic("internal error: cannot add managers to a fully initialized Overlord")
+ }
+ o.addManager(mgr)
+}
+
+type mockBackend struct {
+ o *Overlord
+}
+
+func (mb mockBackend) Checkpoint(data []byte) error {
+ return nil
+}
+
+func (mb mockBackend) EnsureBefore(d time.Duration) {
+ mb.o.ensureLock.Lock()
+ timer := mb.o.ensureTimer
+ mb.o.ensureLock.Unlock()
+ if timer == nil {
+ return
+ }
+
+ mb.o.ensureBefore(d)
+}
+
+func (mb mockBackend) RequestRestart(t state.RestartType) {
+ mb.o.requestRestart(t)
+}
diff -Nru snapd-2.28.5/overlord/overlord_test.go snapd-2.29.3/overlord/overlord_test.go
--- snapd-2.28.5/overlord/overlord_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/overlord/overlord_test.go 2017-10-23 06:17:27.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
@@ -21,6 +21,7 @@
import (
"encoding/json"
+ "errors"
"fmt"
"io/ioutil"
"os"
@@ -38,7 +39,6 @@
"github.com/snapcore/snapd/overlord/patch"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
- "github.com/snapcore/snapd/store"
"github.com/snapcore/snapd/testutil"
)
@@ -63,6 +63,12 @@
restore := patch.Mock(42, nil)
defer restore()
+ var setupStoreAuthContext auth.AuthContext
+ defer overlord.MockSetupStore(func(_ *state.State, ac auth.AuthContext) error {
+ setupStoreAuthContext = ac
+ return nil
+ })()
+
o, err := overlord.New()
c.Assert(err, IsNil)
c.Check(o, NotNil)
@@ -70,7 +76,9 @@
c.Check(o.SnapManager(), NotNil)
c.Check(o.AssertManager(), NotNil)
c.Check(o.InterfaceManager(), NotNil)
+ c.Check(o.HookManager(), NotNil)
c.Check(o.DeviceManager(), NotNil)
+ c.Check(o.CommandManager(), NotNil)
s := o.State()
c.Check(s, NotNil)
@@ -82,9 +90,8 @@
s.Get("patch-level", &patchLevel)
c.Check(patchLevel, Equals, 42)
- // store is setup
- sto := snapstate.Store(s)
- c.Check(sto, FitsTypeOf, &store.Store{})
+ // store was setup with an auth context
+ c.Check(setupStoreAuthContext, NotNil)
}
func (ovs *overlordSuite) TestNewWithGoodState(c *C) {
@@ -151,6 +158,15 @@
c.Check(b, Equals, true)
}
+func (ovs *overlordSuite) TestNewWithSetupStoreError(c *C) {
+ defer overlord.MockSetupStore(func(*state.State, auth.AuthContext) error {
+ return errors.New("fake error")
+ })()
+
+ _, err := overlord.New()
+ c.Check(err, ErrorMatches, "fake error")
+}
+
type witnessManager struct {
state *state.State
expectedEnsure int
@@ -202,17 +218,15 @@
func (ovs *overlordSuite) TestEnsureLoopRunAndStop(c *C) {
restoreIntv := overlord.MockEnsureInterval(10 * time.Millisecond)
defer restoreIntv()
- o, err := overlord.New()
- c.Assert(err, IsNil)
+ o := overlord.Mock()
witness := &witnessManager{
state: o.State(),
expectedEnsure: 3,
ensureCalled: make(chan struct{}),
}
- o.Engine().AddManager(witness)
+ o.AddManager(witness)
- markSeeded(o)
o.Loop()
defer o.Stop()
@@ -224,15 +238,14 @@
}
c.Check(time.Since(t0) >= 10*time.Millisecond, Equals, true)
- err = o.Stop()
+ err := o.Stop()
c.Assert(err, IsNil)
}
func (ovs *overlordSuite) TestEnsureLoopMediatedEnsureBeforeImmediate(c *C) {
restoreIntv := overlord.MockEnsureInterval(10 * time.Minute)
defer restoreIntv()
- o, err := overlord.New()
- c.Assert(err, IsNil)
+ o := overlord.Mock()
ensure := func(s *state.State) error {
s.EnsureBefore(0)
@@ -245,10 +258,8 @@
ensureCalled: make(chan struct{}),
ensureCallback: ensure,
}
- se := o.Engine()
- se.AddManager(witness)
+ o.AddManager(witness)
- markSeeded(o)
o.Loop()
defer o.Stop()
@@ -262,8 +273,7 @@
func (ovs *overlordSuite) TestEnsureLoopMediatedEnsureBefore(c *C) {
restoreIntv := overlord.MockEnsureInterval(10 * time.Minute)
defer restoreIntv()
- o, err := overlord.New()
- c.Assert(err, IsNil)
+ o := overlord.Mock()
ensure := func(s *state.State) error {
s.EnsureBefore(10 * time.Millisecond)
@@ -276,10 +286,8 @@
ensureCalled: make(chan struct{}),
ensureCallback: ensure,
}
- se := o.Engine()
- se.AddManager(witness)
+ o.AddManager(witness)
- markSeeded(o)
o.Loop()
defer o.Stop()
@@ -293,9 +301,7 @@
func (ovs *overlordSuite) TestEnsureBeforeSleepy(c *C) {
restoreIntv := overlord.MockEnsureInterval(10 * time.Minute)
defer restoreIntv()
-
- o, err := overlord.New()
- c.Assert(err, IsNil)
+ o := overlord.Mock()
ensure := func(s *state.State) error {
overlord.MockEnsureNext(o, time.Now().Add(-10*time.Hour))
@@ -309,10 +315,8 @@
ensureCalled: make(chan struct{}),
ensureCallback: ensure,
}
- se := o.Engine()
- se.AddManager(witness)
+ o.AddManager(witness)
- markSeeded(o)
o.Loop()
defer o.Stop()
@@ -326,8 +330,7 @@
func (ovs *overlordSuite) TestEnsureLoopMediatedEnsureBeforeOutsideEnsure(c *C) {
restoreIntv := overlord.MockEnsureInterval(10 * time.Minute)
defer restoreIntv()
- o, err := overlord.New()
- c.Assert(err, IsNil)
+ o := overlord.Mock()
ch := make(chan struct{})
ensure := func(s *state.State) error {
@@ -341,10 +344,8 @@
ensureCalled: make(chan struct{}),
ensureCallback: ensure,
}
- se := o.Engine()
- se.AddManager(witness)
+ o.AddManager(witness)
- markSeeded(o)
o.Loop()
defer o.Stop()
@@ -354,7 +355,7 @@
c.Fatal("Ensure calls not happening")
}
- se.State().EnsureBefore(0)
+ o.State().EnsureBefore(0)
select {
case <-witness.ensureCalled:
@@ -366,8 +367,7 @@
func (ovs *overlordSuite) TestEnsureLoopPrune(c *C) {
restoreIntv := overlord.MockPruneInterval(200*time.Millisecond, 1000*time.Millisecond, 1000*time.Millisecond)
defer restoreIntv()
- o, err := overlord.New()
- c.Assert(err, IsNil)
+ o := overlord.Mock()
st := o.State()
st.Lock()
@@ -400,10 +400,8 @@
witness := &witnessManager{
ensureCallback: waitForPrune,
}
- se := o.Engine()
- se.AddManager(witness)
+ o.AddManager(witness)
- markSeeded(o)
o.Loop()
select {
@@ -412,7 +410,7 @@
c.Fatal("Pruning should have happened by now")
}
- err = o.Stop()
+ err := o.Stop()
c.Assert(err, IsNil)
st.Lock()
@@ -427,8 +425,7 @@
func (ovs *overlordSuite) TestEnsureLoopPruneRunsMultipleTimes(c *C) {
restoreIntv := overlord.MockPruneInterval(100*time.Millisecond, 1000*time.Millisecond, 1*time.Hour)
defer restoreIntv()
- o, err := overlord.New()
- c.Assert(err, IsNil)
+ o := overlord.Mock()
// create two changes, one that can be pruned now, one in progress
st := o.State()
@@ -444,7 +441,6 @@
c.Check(st.Changes(), HasLen, 2)
st.Unlock()
- markSeeded(o)
// start the loop that runs the prune ticker
o.Loop()
@@ -464,7 +460,7 @@
st.Unlock()
// cleanup loop ticker
- err = o.Stop()
+ err := o.Stop()
c.Assert(err, IsNil)
}
@@ -520,6 +516,27 @@
s.EnsureBefore(20 * time.Millisecond)
return nil
}, nil)
+ rm.runner.AddHandler("runMgrForever", func(t *state.Task, _ *tomb.Tomb) error {
+ s := t.State()
+ s.Lock()
+ defer s.Unlock()
+ s.EnsureBefore(20 * time.Millisecond)
+ return &state.Retry{}
+ }, nil)
+ rm.runner.AddHandler("runMgrWCleanup", func(t *state.Task, _ *tomb.Tomb) error {
+ s := t.State()
+ s.Lock()
+ defer s.Unlock()
+ s.Set("runMgrWCleanupMark", 1)
+ return nil
+ }, nil)
+ rm.runner.AddCleanup("runMgrWCleanup", func(t *state.Task, _ *tomb.Tomb) error {
+ s := t.State()
+ s.Lock()
+ defer s.Unlock()
+ s.Set("runMgrWCleanupCleanedUp", 1)
+ return nil
+ })
return rm
}
@@ -543,13 +560,11 @@
func (ovs *overlordSuite) TestTrivialSettle(c *C) {
restoreIntv := overlord.MockEnsureInterval(1 * time.Minute)
defer restoreIntv()
- o, err := overlord.New()
- c.Assert(err, IsNil)
+ o := overlord.Mock()
- se := o.Engine()
- s := se.State()
+ s := o.State()
rm1 := newRunnerManager(s)
- se.AddManager(rm1)
+ o.AddManager(rm1)
defer o.Engine().Stop()
@@ -561,28 +576,49 @@
chg.AddTask(t1)
s.Unlock()
-
- markSeeded(o)
- o.Settle()
-
+ o.Settle(5 * time.Second)
s.Lock()
c.Check(t1.Status(), Equals, state.DoneStatus)
var v int
- err = s.Get("runMgr1Mark", &v)
+ err := s.Get("runMgr1Mark", &v)
c.Check(err, IsNil)
}
+func (ovs *overlordSuite) TestSettleNotConverging(c *C) {
+ restoreIntv := overlord.MockEnsureInterval(1 * time.Minute)
+ defer restoreIntv()
+ o := overlord.Mock()
+
+ s := o.State()
+ rm1 := newRunnerManager(s)
+ o.AddManager(rm1)
+
+ defer o.Engine().Stop()
+
+ s.Lock()
+ defer s.Unlock()
+
+ chg := s.NewChange("chg", "...")
+ t1 := s.NewTask("runMgrForever", "1...")
+ chg.AddTask(t1)
+
+ s.Unlock()
+ err := o.Settle(250 * time.Millisecond)
+ s.Lock()
+
+ c.Check(err, ErrorMatches, `Settle is not converging`)
+
+}
+
func (ovs *overlordSuite) TestSettleChain(c *C) {
restoreIntv := overlord.MockEnsureInterval(1 * time.Minute)
defer restoreIntv()
- o, err := overlord.New()
- c.Assert(err, IsNil)
+ o := overlord.Mock()
- se := o.Engine()
- s := se.State()
+ s := o.State()
rm1 := newRunnerManager(s)
- se.AddManager(rm1)
+ o.AddManager(rm1)
defer o.Engine().Stop()
@@ -596,29 +632,60 @@
chg.AddAll(state.NewTaskSet(t1, t2))
s.Unlock()
+ o.Settle(5 * time.Second)
+ s.Lock()
+ c.Check(t1.Status(), Equals, state.DoneStatus)
+ c.Check(t2.Status(), Equals, state.DoneStatus)
- markSeeded(o)
- o.Settle()
+ var v int
+ err := s.Get("runMgr1Mark", &v)
+ c.Check(err, IsNil)
+ err = s.Get("runMgr2Mark", &v)
+ c.Check(err, IsNil)
+}
+
+func (ovs *overlordSuite) TestSettleChainWCleanup(c *C) {
+ restoreIntv := overlord.MockEnsureInterval(1 * time.Minute)
+ defer restoreIntv()
+ o := overlord.Mock()
+
+ s := o.State()
+ rm1 := newRunnerManager(s)
+ o.AddManager(rm1)
+
+ defer o.Engine().Stop()
s.Lock()
+ defer s.Unlock()
+
+ chg := s.NewChange("chg", "...")
+ t1 := s.NewTask("runMgrWCleanup", "1...")
+ t2 := s.NewTask("runMgr2", "2...")
+ t2.WaitFor(t1)
+ chg.AddAll(state.NewTaskSet(t1, t2))
+
+ s.Unlock()
+ o.Settle(5 * time.Second)
+ s.Lock()
c.Check(t1.Status(), Equals, state.DoneStatus)
c.Check(t2.Status(), Equals, state.DoneStatus)
var v int
- err = s.Get("runMgr1Mark", &v)
+ err := s.Get("runMgrWCleanupMark", &v)
c.Check(err, IsNil)
err = s.Get("runMgr2Mark", &v)
c.Check(err, IsNil)
+
+ err = s.Get("runMgrWCleanupCleanedUp", &v)
+ c.Check(err, IsNil)
}
func (ovs *overlordSuite) TestSettleExplicitEnsureBefore(c *C) {
restoreIntv := overlord.MockEnsureInterval(1 * time.Minute)
defer restoreIntv()
- o, err := overlord.New()
- c.Assert(err, IsNil)
+ o := overlord.Mock()
- se := o.Engine()
- s := se.State()
+ s := o.State()
rm1 := newRunnerManager(s)
rm1.ensureCallback = func() {
s.Lock()
@@ -628,7 +695,7 @@
s.Set("ensureCount", v+1)
}
- se.AddManager(rm1)
+ o.AddManager(rm1)
defer o.Engine().Stop()
@@ -638,16 +705,14 @@
chg := s.NewChange("chg", "...")
t := s.NewTask("runMgrEnsureBefore", "...")
chg.AddTask(t)
- s.Unlock()
-
- markSeeded(o)
- o.Settle()
+ s.Unlock()
+ o.Settle(5 * time.Second)
s.Lock()
c.Check(t.Status(), Equals, state.DoneStatus)
var v int
- err = s.Get("ensureCount", &v)
+ err := s.Get("ensureCount", &v)
c.Check(err, IsNil)
c.Check(v, Equals, 2)
}
diff -Nru snapd-2.28.5/overlord/patch/export_test.go snapd-2.29.3/overlord/patch/export_test.go
--- snapd-2.28.5/overlord/patch/export_test.go 2016-11-24 09:36:04.000000000 +0000
+++ snapd-2.29.3/overlord/patch/export_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -1,3 +1,22 @@
+// -*- 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 patch
import (
diff -Nru snapd-2.28.5/overlord/patch/patch3.go snapd-2.29.3/overlord/patch/patch3.go
--- snapd-2.28.5/overlord/patch/patch3.go 2016-12-08 15:14:07.000000000 +0000
+++ snapd-2.29.3/overlord/patch/patch3.go 2017-10-23 06:17:27.000000000 +0000
@@ -22,7 +22,7 @@
import (
"fmt"
- "github.com/snapcore/snapd/i18n/dumb"
+ "github.com/snapcore/snapd/i18n"
"github.com/snapcore/snapd/overlord/state"
)
diff -Nru snapd-2.28.5/overlord/servicestate/servicestate.go snapd-2.29.3/overlord/servicestate/servicestate.go
--- snapd-2.28.5/overlord/servicestate/servicestate.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/overlord/servicestate/servicestate.go 2017-10-27 12:23:38.000000000 +0000
@@ -0,0 +1,91 @@
+// -*- 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 servicestate
+
+import (
+ "fmt"
+
+ "github.com/snapcore/snapd/client"
+ "github.com/snapcore/snapd/overlord/cmdstate"
+ "github.com/snapcore/snapd/overlord/snapstate"
+ "github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/snap"
+)
+
+type Instruction struct {
+ Action string `json:"action"`
+ Names []string `json:"names"`
+ client.StartOptions
+ client.StopOptions
+ client.RestartOptions
+}
+
+type ServiceActionConflictError struct{ error }
+
+func Control(st *state.State, appInfos []*snap.AppInfo, inst *Instruction) (*state.TaskSet, error) {
+ // the argv to call systemctl will need at most one entry per appInfo,
+ // plus one for "systemctl", one for the action, and sometimes one for
+ // an option. That's a maximum of 3+len(appInfos).
+ 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 nil, fmt.Errorf("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 nil, &ServiceActionConflictError{err}
+ }
+
+ return cmdstate.Exec(st, desc, argv), nil
+}
diff -Nru snapd-2.28.5/overlord/snapstate/aliasesv2.go snapd-2.29.3/overlord/snapstate/aliasesv2.go
--- snapd-2.28.5/overlord/snapstate/aliasesv2.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/overlord/snapstate/aliasesv2.go 2017-10-23 06:17:27.000000000 +0000
@@ -24,7 +24,7 @@
"fmt"
"strings"
- "github.com/snapcore/snapd/i18n/dumb"
+ "github.com/snapcore/snapd/i18n"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/overlord/snapstate/backend"
"github.com/snapcore/snapd/overlord/state"
diff -Nru snapd-2.28.5/overlord/snapstate/aliasesv2_test.go snapd-2.29.3/overlord/snapstate/aliasesv2_test.go
--- snapd-2.28.5/overlord/snapstate/aliasesv2_test.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/overlord/snapstate/aliasesv2_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -229,6 +229,7 @@
c.Check(dropped, HasLen, 0)
c.Check(seen, DeepEquals, map[string]bool{
+ "core": true,
"alias-snap": true,
"other-snap": true,
})
@@ -552,7 +553,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("%v", chg.Err()))
@@ -667,7 +668,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("%v", chg.Err()))
@@ -815,7 +816,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("%v", chg.Err()))
@@ -903,7 +904,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("%v", chg.Err()))
@@ -955,7 +956,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("%v", chg.Err()))
@@ -1123,7 +1124,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("%v", chg.Err()))
diff -Nru snapd-2.28.5/overlord/snapstate/backend/copydata_test.go snapd-2.29.3/overlord/snapstate/backend/copydata_test.go
--- snapd-2.28.5/overlord/snapstate/backend/copydata_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/overlord/snapstate/backend/copydata_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -40,9 +40,8 @@
)
type copydataSuite struct {
- be backend.Backend
- nullProgress progress.NullProgress
- tempdir string
+ be backend.Backend
+ tempdir string
}
var _ = Suite(©dataSuite{})
@@ -79,7 +78,7 @@
v1 := snaptest.MockSnap(c, helloYaml1, helloContents, &snap.SideInfo{Revision: snap.R(10)})
// just creates data dirs in this case
- err = s.be.CopySnapData(v1, nil, &s.nullProgress)
+ err = s.be.CopySnapData(v1, nil, progress.Null)
c.Assert(err, IsNil)
canaryDataFile := filepath.Join(v1.DataDir(), "canary.txt")
@@ -94,7 +93,7 @@
c.Assert(err, IsNil)
v2 := snaptest.MockSnap(c, helloYaml2, helloContents, &snap.SideInfo{Revision: snap.R(20)})
- err = s.be.CopySnapData(v2, v1, &s.nullProgress)
+ err = s.be.CopySnapData(v2, v1, progress.Null)
c.Assert(err, IsNil)
newCanaryDataFile := filepath.Join(dirs.SnapDataDir, "hello/20", "canary.txt")
@@ -125,11 +124,11 @@
defer func() { dirs.SnapDataHomeGlob = oldSnapDataHomeGlob }()
v1 := snaptest.MockSnap(c, helloYaml1, helloContents, &snap.SideInfo{Revision: snap.R(10)})
- c.Assert(s.be.CopySnapData(v1, nil, &s.nullProgress), IsNil)
+ c.Assert(s.be.CopySnapData(v1, nil, progress.Null), IsNil)
c.Assert(os.Chmod(v1.DataDir(), 0), IsNil)
v2 := snaptest.MockSnap(c, helloYaml2, helloContents, &snap.SideInfo{Revision: snap.R(20)})
- err := s.be.CopySnapData(v2, v1, &s.nullProgress)
+ err := s.be.CopySnapData(v2, v1, progress.Null)
c.Check(err, ErrorMatches, "cannot copy .*")
}
@@ -142,7 +141,7 @@
dirs.SnapDataHomeGlob = filepath.Join(s.tempdir, "no-such-home", "*", "snap")
v1 := snaptest.MockSnap(c, helloYaml1, helloContents, &snap.SideInfo{Revision: snap.R(10)})
- err := s.be.CopySnapData(v1, nil, &s.nullProgress)
+ err := s.be.CopySnapData(v1, nil, progress.Null)
c.Assert(err, IsNil)
canaryDataFile := filepath.Join(v1.DataDir(), "canary.txt")
@@ -153,7 +152,7 @@
c.Assert(err, IsNil)
v2 := snaptest.MockSnap(c, helloYaml2, helloContents, &snap.SideInfo{Revision: snap.R(20)})
- err = s.be.CopySnapData(v2, v1, &s.nullProgress)
+ err = s.be.CopySnapData(v2, v1, progress.Null)
c.Assert(err, IsNil)
_, err = os.Stat(filepath.Join(v2.DataDir(), "canary.txt"))
@@ -193,7 +192,8 @@
c.Assert(err, IsNil)
err = ioutil.WriteFile(filepath.Join(homeData, "canary.home"), []byte(fmt.Sprintln(revision)), 0644)
c.Assert(err, IsNil)
- return
+
+ return homedir
}
func (s *copydataSuite) TestCopyDataDoUndo(c *C) {
@@ -205,7 +205,7 @@
v2 := snaptest.MockSnap(c, helloYaml2, helloContents, &snap.SideInfo{Revision: snap.R(20)})
// copy data
- err := s.be.CopySnapData(v2, v1, &s.nullProgress)
+ err := s.be.CopySnapData(v2, v1, progress.Null)
c.Assert(err, IsNil)
v2data := filepath.Join(dirs.SnapDataDir, "hello/20")
l, err := filepath.Glob(filepath.Join(v2data, "*"))
@@ -216,7 +216,7 @@
c.Assert(err, IsNil)
c.Assert(l, HasLen, 1)
- err = s.be.UndoCopySnapData(v2, v1, &s.nullProgress)
+ err = s.be.UndoCopySnapData(v2, v1, progress.Null)
c.Assert(err, IsNil)
// now removed
@@ -239,14 +239,14 @@
v2 := snaptest.MockSnap(c, helloYaml2, helloContents, &snap.SideInfo{Revision: snap.R(20)})
// copy data
- err := s.be.CopySnapData(v2, v1, &s.nullProgress)
+ err := s.be.CopySnapData(v2, v1, progress.Null)
c.Assert(err, IsNil)
v2data := filepath.Join(dirs.SnapDataDir, "hello/20")
l, err := filepath.Glob(filepath.Join(v2data, "*"))
c.Assert(err, IsNil)
c.Assert(l, HasLen, 1)
- err = s.be.UndoCopySnapData(v2, v1, &s.nullProgress)
+ err = s.be.UndoCopySnapData(v2, v1, progress.Null)
c.Assert(err, IsNil)
// now removed
@@ -258,14 +258,14 @@
v1 := snaptest.MockSnap(c, helloYaml1, helloContents, &snap.SideInfo{Revision: snap.R(10)})
// first install
- err := s.be.CopySnapData(v1, nil, &s.nullProgress)
+ err := s.be.CopySnapData(v1, nil, progress.Null)
c.Assert(err, IsNil)
_, err = os.Stat(v1.DataDir())
c.Assert(err, IsNil)
_, err = os.Stat(v1.CommonDataDir())
c.Assert(err, IsNil)
- err = s.be.UndoCopySnapData(v1, nil, &s.nullProgress)
+ err = s.be.UndoCopySnapData(v1, nil, progress.Null)
c.Assert(err, IsNil)
_, err = os.Stat(v1.DataDir())
c.Check(os.IsNotExist(err), Equals, true)
@@ -285,7 +285,7 @@
c.Check(s.populatedData("20"), Equals, "20\n")
// and now we pretend to refresh back to v1 (r10)
- c.Check(s.be.CopySnapData(v1, v2, &s.nullProgress), IsNil)
+ c.Check(s.be.CopySnapData(v1, v2, progress.Null), IsNil)
// so 10 now has 20's data
c.Check(s.populatedData("10"), Equals, "20\n")
@@ -310,14 +310,14 @@
c.Check(s.populatedData("20"), Equals, "20\n")
// and now we pretend to refresh back to v1 (r10)
- c.Check(s.be.CopySnapData(v1, v2, &s.nullProgress), IsNil)
+ c.Check(s.be.CopySnapData(v1, v2, progress.Null), IsNil)
// so v1 (r10) now has v2 (r20)'s data and we have trash
c.Check(s.populatedData("10"), Equals, "20\n")
c.Check(s.populatedData("10.old"), Equals, "10\n")
// but oh no! we have to undo it!
- c.Check(s.be.UndoCopySnapData(v1, v2, &s.nullProgress), IsNil)
+ c.Check(s.be.UndoCopySnapData(v1, v2, progress.Null), IsNil)
// so now v1 (r10) has v1 (r10)'s data and v2 (r20) has v2 (r20)'s data and we have no trash
c.Check(s.populatedData("10"), Equals, "10\n")
@@ -337,10 +337,10 @@
v2 := snaptest.MockSnap(c, helloYaml2, helloContents, &snap.SideInfo{Revision: snap.R(20)})
// copy data
- err := s.be.CopySnapData(v2, v1, &s.nullProgress)
+ err := s.be.CopySnapData(v2, v1, progress.Null)
c.Assert(err, IsNil)
- err = s.be.CopySnapData(v2, v1, &s.nullProgress)
+ err = s.be.CopySnapData(v2, v1, progress.Null)
c.Assert(err, IsNil)
v2data := filepath.Join(dirs.SnapDataDir, "hello/20")
@@ -364,15 +364,15 @@
v2 := snaptest.MockSnap(c, helloYaml2, helloContents, &snap.SideInfo{Revision: snap.R(20)})
// copy data
- err := s.be.CopySnapData(v2, v1, &s.nullProgress)
+ err := s.be.CopySnapData(v2, v1, progress.Null)
c.Assert(err, IsNil)
v2data := filepath.Join(dirs.SnapDataDir, "hello/20")
- err = s.be.UndoCopySnapData(v2, v1, &s.nullProgress)
+ err = s.be.UndoCopySnapData(v2, v1, progress.Null)
c.Assert(err, IsNil)
- err = s.be.UndoCopySnapData(v2, v1, &s.nullProgress)
+ err = s.be.UndoCopySnapData(v2, v1, progress.Null)
c.Assert(err, IsNil)
// now removed
@@ -387,10 +387,10 @@
v1 := snaptest.MockSnap(c, helloYaml1, helloContents, &snap.SideInfo{Revision: snap.R(10)})
// first install
- err := s.be.CopySnapData(v1, nil, &s.nullProgress)
+ err := s.be.CopySnapData(v1, nil, progress.Null)
c.Assert(err, IsNil)
- err = s.be.CopySnapData(v1, nil, &s.nullProgress)
+ err = s.be.CopySnapData(v1, nil, progress.Null)
c.Assert(err, IsNil)
_, err = os.Stat(v1.DataDir())
@@ -398,7 +398,7 @@
_, err = os.Stat(v1.CommonDataDir())
c.Assert(err, IsNil)
- err = s.be.UndoCopySnapData(v1, nil, &s.nullProgress)
+ err = s.be.UndoCopySnapData(v1, nil, progress.Null)
c.Assert(err, IsNil)
_, err = os.Stat(v1.DataDir())
c.Check(os.IsNotExist(err), Equals, true)
@@ -410,17 +410,17 @@
v1 := snaptest.MockSnap(c, helloYaml1, helloContents, &snap.SideInfo{Revision: snap.R(10)})
// first install
- err := s.be.CopySnapData(v1, nil, &s.nullProgress)
+ err := s.be.CopySnapData(v1, nil, progress.Null)
c.Assert(err, IsNil)
_, err = os.Stat(v1.DataDir())
c.Assert(err, IsNil)
_, err = os.Stat(v1.CommonDataDir())
c.Assert(err, IsNil)
- err = s.be.UndoCopySnapData(v1, nil, &s.nullProgress)
+ err = s.be.UndoCopySnapData(v1, nil, progress.Null)
c.Assert(err, IsNil)
- err = s.be.UndoCopySnapData(v1, nil, &s.nullProgress)
+ err = s.be.UndoCopySnapData(v1, nil, progress.Null)
c.Assert(err, IsNil)
_, err = os.Stat(v1.DataDir())
@@ -443,7 +443,7 @@
}
// copy data will fail
- err := s.be.CopySnapData(v2, v1, &s.nullProgress)
+ err := s.be.CopySnapData(v2, v1, progress.Null)
c.Assert(err, ErrorMatches, fmt.Sprintf(`cannot copy %s to %s: .*: "cp: boom" \(3\)`, q(v1.DataDir()), q(v2.DataDir())))
}
@@ -466,7 +466,7 @@
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)
+ err := s.be.CopySnapData(v2, v1, progress.Null)
c.Assert(err, NotNil)
// the copy data failed, so check it cleaned up after itself (but not too much!)
@@ -497,7 +497,7 @@
}
// copy data works
- err := s.be.CopySnapData(v1, v1, &s.nullProgress)
+ err := s.be.CopySnapData(v1, v1, progress.Null)
c.Assert(err, IsNil)
// the data is still there :-)
@@ -533,7 +533,7 @@
}
// undo copy data works
- err := s.be.UndoCopySnapData(v1, v1, &s.nullProgress)
+ err := s.be.UndoCopySnapData(v1, v1, progress.Null)
c.Assert(err, IsNil)
// the data is still there :-)
diff -Nru snapd-2.28.5/overlord/snapstate/backend/link.go snapd-2.29.3/overlord/snapstate/backend/link.go
--- snapd-2.28.5/overlord/snapstate/backend/link.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/overlord/snapstate/backend/link.go 2017-10-23 06:17:27.000000000 +0000
@@ -88,13 +88,13 @@
return err
}
// add the daemons from the snap.yaml
- if err := wrappers.AddSnapServices(s, &progress.NullProgress{}); err != nil {
+ if err := wrappers.AddSnapServices(s, progress.Null); err != nil {
wrappers.RemoveSnapBinaries(s)
return err
}
// add the desktop files
if err := wrappers.AddSnapDesktopFiles(s); err != nil {
- wrappers.RemoveSnapServices(s, &progress.NullProgress{})
+ wrappers.RemoveSnapServices(s, progress.Null)
wrappers.RemoveSnapBinaries(s)
return err
}
diff -Nru snapd-2.28.5/overlord/snapstate/backend/link_test.go snapd-2.29.3/overlord/snapstate/backend/link_test.go
--- snapd-2.28.5/overlord/snapstate/backend/link_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/overlord/snapstate/backend/link_test.go 2017-11-02 19:49:21.000000000 +0000
@@ -38,9 +38,9 @@
)
type linkSuite struct {
- be backend.Backend
- nullProgress progress.NullProgress
- prevctlCmd func(...string) ([]byte, error)
+ be backend.Backend
+
+ systemctlRestorer func()
}
var _ = Suite(&linkSuite{})
@@ -48,15 +48,14 @@
func (s *linkSuite) SetUpTest(c *C) {
dirs.SetRootDir(c.MkDir())
- s.prevctlCmd = systemd.SystemctlCmd
- systemd.SystemctlCmd = func(cmd ...string) ([]byte, error) {
+ s.systemctlRestorer = systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
return []byte("ActiveState=inactive\n"), nil
- }
+ })
}
func (s *linkSuite) TearDownTest(c *C) {
dirs.SetRootDir("")
- systemd.SystemctlCmd = s.prevctlCmd
+ s.systemctlRestorer()
}
func (s *linkSuite) TestLinkDoUndoGenerateWrappers(c *C) {
@@ -87,7 +86,7 @@
c.Assert(l, HasLen, 1)
// undo will remove
- err = s.be.UnlinkSnap(info, &s.nullProgress)
+ err = s.be.UnlinkSnap(info, progress.Null)
c.Assert(err, IsNil)
l, err = filepath.Glob(filepath.Join(dirs.SnapBinariesDir, "*"))
@@ -122,7 +121,7 @@
c.Assert(currentDataDir, Equals, dataDir)
// undo will remove the symlinks
- err = s.be.UnlinkSnap(info, &s.nullProgress)
+ err = s.be.UnlinkSnap(info, progress.Null)
c.Assert(err, IsNil)
c.Check(osutil.FileExists(currentActiveSymlink), Equals, false)
@@ -193,10 +192,10 @@
err := s.be.LinkSnap(info)
c.Assert(err, IsNil)
- err = s.be.UnlinkSnap(info, &s.nullProgress)
+ err = s.be.UnlinkSnap(info, progress.Null)
c.Assert(err, IsNil)
- err = s.be.UnlinkSnap(info, &s.nullProgress)
+ err = s.be.UnlinkSnap(info, progress.Null)
c.Assert(err, IsNil)
// no wrappers
@@ -257,9 +256,10 @@
Exec=bin
`), 0644), IsNil)
- systemd.SystemctlCmd = func(...string) ([]byte, error) {
+ r := systemd.MockSystemctl(func(...string) ([]byte, error) {
return nil, nil
- }
+ })
+ defer r()
// sanity checks
for _, d := range []string{dirs.SnapBinariesDir, dirs.SnapDesktopFilesDir, dirs.SnapServicesDir} {
@@ -300,9 +300,11 @@
}
func (s *linkCleanupSuite) TestLinkCleanupOnSystemctlFail(c *C) {
- systemd.SystemctlCmd = func(...string) ([]byte, error) {
+ r := systemd.MockSystemctl(func(...string) ([]byte, error) {
return nil, errors.New("ouchie")
- }
+ })
+ defer r()
+
err := s.be.LinkSnap(s.info)
c.Assert(err, ErrorMatches, "ouchie")
diff -Nru snapd-2.28.5/overlord/snapstate/backend/mountunit_test.go snapd-2.29.3/overlord/snapstate/backend/mountunit_test.go
--- snapd-2.28.5/overlord/snapstate/backend/mountunit_test.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/overlord/snapstate/backend/mountunit_test.go 2017-10-27 06:23:41.000000000 +0000
@@ -37,9 +37,9 @@
)
type mountunitSuite struct {
- nullProgress progress.NullProgress
- prevctlCmd func(...string) ([]byte, error)
- umount *testutil.MockCmd
+ umount *testutil.MockCmd
+
+ systemctlRestorer func()
}
var _ = Suite(&mountunitSuite{})
@@ -50,17 +50,16 @@
err := os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "etc", "systemd", "system", "multi-user.target.wants"), 0755)
c.Assert(err, IsNil)
- s.prevctlCmd = systemd.SystemctlCmd
- systemd.SystemctlCmd = func(cmd ...string) ([]byte, error) {
+ s.systemctlRestorer = systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
return []byte("ActiveState=inactive\n"), nil
- }
+ })
s.umount = testutil.MockCommand(c, "umount", "")
}
func (s *mountunitSuite) TearDownTest(c *C) {
dirs.SetRootDir("")
- systemd.SystemctlCmd = s.prevctlCmd
s.umount.Restore()
+ s.systemctlRestorer()
}
func (s *mountunitSuite) TestAddMountUnit(c *C) {
@@ -72,7 +71,7 @@
Version: "1.1",
Architectures: []string{"all"},
}
- err := backend.AddMountUnit(info, &s.nullProgress)
+ err := backend.AddMountUnit(info, progress.Null)
c.Assert(err, IsNil)
// ensure correct mount unit
@@ -81,6 +80,7 @@
c.Assert(err, IsNil)
c.Assert(string(mount), Equals, fmt.Sprintf(`[Unit]
Description=Mount unit for foo
+Before=snapd.service
[Mount]
What=/var/lib/snapd/snaps/foo_13.snap
@@ -104,7 +104,7 @@
Architectures: []string{"all"},
}
- err := backend.AddMountUnit(info, &s.nullProgress)
+ err := backend.AddMountUnit(info, progress.Null)
c.Assert(err, IsNil)
// ensure we have the files
@@ -113,7 +113,7 @@
c.Assert(osutil.FileExists(p), Equals, true)
// now call remove and ensure they are gone
- err = backend.RemoveMountUnit(info.MountDir(), &s.nullProgress)
+ err = backend.RemoveMountUnit(info.MountDir(), progress.Null)
c.Assert(err, IsNil)
p = filepath.Join(dirs.SnapServicesDir, un)
c.Assert(osutil.FileExists(p), Equals, false)
diff -Nru snapd-2.28.5/overlord/snapstate/backend/setup_test.go snapd-2.29.3/overlord/snapstate/backend/setup_test.go
--- snapd-2.28.5/overlord/snapstate/backend/setup_test.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/overlord/snapstate/backend/setup_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -40,10 +40,9 @@
)
type setupSuite struct {
- be backend.Backend
- nullProgress progress.NullProgress
- prevctlCmd func(...string) ([]byte, error)
- umount *testutil.MockCmd
+ be backend.Backend
+ umount *testutil.MockCmd
+ systemctlRestorer func()
}
var _ = Suite(&setupSuite{})
@@ -54,18 +53,18 @@
err := os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "etc", "systemd", "system", "multi-user.target.wants"), 0755)
c.Assert(err, IsNil)
- s.prevctlCmd = systemd.SystemctlCmd
- systemd.SystemctlCmd = func(cmd ...string) ([]byte, error) {
+ s.systemctlRestorer = systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {
return []byte("ActiveState=inactive\n"), nil
- }
+ })
+
s.umount = testutil.MockCommand(c, "umount", "")
}
func (s *setupSuite) TearDownTest(c *C) {
dirs.SetRootDir("")
partition.ForceBootloader(nil)
- systemd.SystemctlCmd = s.prevctlCmd
s.umount.Restore()
+ s.systemctlRestorer()
}
func (s *setupSuite) TestSetupDoUndoSimple(c *C) {
@@ -76,7 +75,7 @@
Revision: snap.R(14),
}
- err := s.be.SetupSnap(snapPath, &si, &s.nullProgress)
+ err := s.be.SetupSnap(snapPath, &si, progress.Null)
c.Assert(err, IsNil)
// after setup the snap file is in the right dir
@@ -94,7 +93,7 @@
c.Assert(osutil.FileExists(minInfo.MountDir()), Equals, true)
// undo undoes the mount unit and the instdir creation
- err = s.be.UndoSetupSnap(minInfo, "app", &s.nullProgress)
+ err = s.be.UndoSetupSnap(minInfo, "app", progress.Null)
c.Assert(err, IsNil)
l, _ := filepath.Glob(filepath.Join(dirs.SnapServicesDir, "*.mount"))
@@ -129,7 +128,7 @@
Revision: snap.R(140),
}
- err := s.be.SetupSnap(snapPath, &si, &s.nullProgress)
+ err := s.be.SetupSnap(snapPath, &si, progress.Null)
c.Assert(err, IsNil)
l, _ := filepath.Glob(filepath.Join(bootloader.Dir(), "*"))
c.Assert(l, HasLen, 1)
@@ -137,7 +136,7 @@
minInfo := snap.MinimalPlaceInfo("kernel", snap.R(140))
// undo deletes the kernel assets again
- err = s.be.UndoSetupSnap(minInfo, "kernel", &s.nullProgress)
+ err = s.be.UndoSetupSnap(minInfo, "kernel", progress.Null)
c.Assert(err, IsNil)
l, _ = filepath.Glob(filepath.Join(bootloader.Dir(), "*"))
@@ -173,11 +172,11 @@
Revision: snap.R(140),
}
- err := s.be.SetupSnap(snapPath, &si, &s.nullProgress)
+ err := s.be.SetupSnap(snapPath, &si, progress.Null)
c.Assert(err, IsNil)
// retry run
- err = s.be.SetupSnap(snapPath, &si, &s.nullProgress)
+ err = s.be.SetupSnap(snapPath, &si, progress.Null)
c.Assert(err, IsNil)
minInfo := snap.MinimalPlaceInfo("kernel", snap.R(140))
@@ -222,16 +221,16 @@
Revision: snap.R(140),
}
- err := s.be.SetupSnap(snapPath, &si, &s.nullProgress)
+ err := s.be.SetupSnap(snapPath, &si, progress.Null)
c.Assert(err, IsNil)
minInfo := snap.MinimalPlaceInfo("kernel", snap.R(140))
- err = s.be.UndoSetupSnap(minInfo, "kernel", &s.nullProgress)
+ err = s.be.UndoSetupSnap(minInfo, "kernel", progress.Null)
c.Assert(err, IsNil)
// retry run
- err = s.be.UndoSetupSnap(minInfo, "kernel", &s.nullProgress)
+ err = s.be.UndoSetupSnap(minInfo, "kernel", progress.Null)
c.Assert(err, IsNil)
// sanity checks
diff -Nru snapd-2.28.5/overlord/snapstate/backend/snapdata.go snapd-2.29.3/overlord/snapstate/backend/snapdata.go
--- snapd-2.28.5/overlord/snapstate/backend/snapdata.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/overlord/snapstate/backend/snapdata.go 2017-10-23 06:17:27.000000000 +0000
@@ -26,6 +26,7 @@
"path/filepath"
unix "syscall"
+ "github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/snap"
@@ -86,6 +87,8 @@
if err != nil {
return nil, err
}
+ // then the /root user (including GlobalRootDir for tests)
+ found = append(found, snap.UserDataDir(filepath.Join(dirs.GlobalRootDir, "/root/")))
// then system data
found = append(found, snap.DataDir())
diff -Nru snapd-2.28.5/overlord/snapstate/backend.go snapd-2.29.3/overlord/snapstate/backend.go
--- snapd-2.28.5/overlord/snapstate/backend.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/overlord/snapstate/backend.go 2017-10-23 06:17:27.000000000 +0000
@@ -20,32 +20,11 @@
package snapstate
import (
- "github.com/snapcore/snapd/asserts"
- "github.com/snapcore/snapd/overlord/auth"
"github.com/snapcore/snapd/overlord/snapstate/backend"
"github.com/snapcore/snapd/progress"
"github.com/snapcore/snapd/snap"
- "github.com/snapcore/snapd/store"
-
- "golang.org/x/net/context"
)
-// A StoreService can find, list available updates and download snaps.
-type StoreService interface {
- SnapInfo(spec store.SnapSpec, user *auth.UserState) (*snap.Info, error)
- Find(search *store.Search, user *auth.UserState) ([]*snap.Info, error)
- LookupRefresh(*store.RefreshCandidate, *auth.UserState) (*snap.Info, error)
- ListRefresh([]*store.RefreshCandidate, *auth.UserState) ([]*snap.Info, error)
- Sections(user *auth.UserState) ([]string, error)
- Download(context.Context, string, string, *snap.DownloadInfo, progress.Meter, *auth.UserState) error
-
- Assertion(assertType *asserts.AssertionType, primaryKey []string, user *auth.UserState) (asserts.Assertion, error)
-
- SuggestedCurrency() string
- Buy(options *store.BuyOptions, user *auth.UserState) (*store.BuyResult, error)
- ReadyToBuy(*auth.UserState) error
-}
-
type managerBackend interface {
// install releated
SetupSnap(snapFilePath string, si *snap.SideInfo, meter progress.Meter) error
diff -Nru snapd-2.28.5/overlord/snapstate/backend_test.go snapd-2.29.3/overlord/snapstate/backend_test.go
--- snapd-2.28.5/overlord/snapstate/backend_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/overlord/snapstate/backend_test.go 2017-11-02 15:44:20.000000000 +0000
@@ -22,6 +22,7 @@
import (
"errors"
"fmt"
+ "io"
"sort"
"strings"
@@ -93,6 +94,7 @@
storetest.Store
downloads []fakeDownload
+ refreshRevnos map[string]snap.Revision
fakeBackend *fakeSnappyBackend
fakeCurrentProgress int
fakeTotalProgress int
@@ -176,6 +178,9 @@
}
revno := snap.R(11)
+ if r := f.refreshRevnos[cand.SnapID]; !r.Unset() {
+ revno = r
+ }
confinement := snap.StrictConfinement
switch cand.Channel {
case "channel-for-7":
@@ -269,6 +274,24 @@
return nil
}
+func (f *fakeStore) WriteCatalogs(io.Writer) error {
+ f.pokeStateLock()
+ f.fakeBackend.ops = append(f.fakeBackend.ops, fakeOp{
+ op: "x-commands",
+ })
+
+ return nil
+}
+
+func (f *fakeStore) Sections(*auth.UserState) ([]string, error) {
+ f.pokeStateLock()
+ f.fakeBackend.ops = append(f.fakeBackend.ops, fakeOp{
+ op: "x-sections",
+ })
+
+ return nil, nil
+}
+
type fakeSnappyBackend struct {
ops fakeOps
diff -Nru snapd-2.28.5/overlord/snapstate/booted_test.go snapd-2.29.3/overlord/snapstate/booted_test.go
--- snapd-2.28.5/overlord/snapstate/booted_test.go 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/overlord/snapstate/booted_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -24,34 +24,44 @@
import (
"os"
"path/filepath"
+ "time"
. "gopkg.in/check.v1"
"github.com/snapcore/snapd/boot/boottest"
"github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/overlord"
"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"
"github.com/snapcore/snapd/snap/snaptest"
+ "github.com/snapcore/snapd/testutil"
)
type bootedSuite struct {
+ testutil.BaseTest
bootloader *boottest.MockBootloader
+ o *overlord.Overlord
state *state.State
snapmgr *snapstate.SnapManager
fakeBackend *fakeSnappyBackend
+ restore func()
}
var _ = Suite(&bootedSuite{})
func (bs *bootedSuite) SetUpTest(c *C) {
+ bs.BaseTest.SetUpTest(c)
+
dirs.SetRootDir(c.MkDir())
err := os.MkdirAll(filepath.Dir(dirs.SnapStateFile), 0755)
c.Assert(err, IsNil)
+ bs.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
+
// booted is not running on classic
release.MockOnClassic(false)
@@ -61,11 +71,14 @@
partition.ForceBootloader(bs.bootloader)
bs.fakeBackend = &fakeSnappyBackend{}
- bs.state = state.New(nil)
+ bs.o = overlord.Mock()
+ bs.state = bs.o.State()
bs.snapmgr, err = snapstate.Manager(bs.state)
c.Assert(err, IsNil)
bs.snapmgr.AddForeignTaskHandlers(bs.fakeBackend)
+ bs.o.AddManager(bs.snapmgr)
+
snapstate.SetSnapManagerBackend(bs.snapmgr, bs.fakeBackend)
snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) {
return nil, nil
@@ -73,6 +86,7 @@
}
func (bs *bootedSuite) TearDownTest(c *C) {
+ bs.BaseTest.TearDownTest(c)
snapstate.AutoAliases = nil
release.MockOnClassic(true)
dirs.SetRootDir("")
@@ -85,10 +99,7 @@
var kernelSI2 = &snap.SideInfo{RealName: "canonical-pc-linux", Revision: snap.R(2)}
func (bs *bootedSuite) settle() {
- for i := 0; i < 50; i++ {
- bs.snapmgr.Ensure()
- bs.snapmgr.Wait()
- }
+ bs.o.Settle(5 * time.Second)
}
func (bs *bootedSuite) makeInstalledKernelOS(c *C, st *state.State) {
diff -Nru snapd-2.28.5/overlord/snapstate/check_snap_test.go snapd-2.29.3/overlord/snapstate/check_snap_test.go
--- snapd-2.28.5/overlord/snapstate/check_snap_test.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/overlord/snapstate/check_snap_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -32,22 +32,27 @@
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snaptest"
+ "github.com/snapcore/snapd/testutil"
"github.com/snapcore/snapd/overlord/snapstate"
)
type checkSnapSuite struct {
+ testutil.BaseTest
st *state.State
}
var _ = Suite(&checkSnapSuite{})
func (s *checkSnapSuite) SetUpTest(c *C) {
+ s.BaseTest.SetUpTest(c)
dirs.SetRootDir(c.MkDir())
s.st = state.New(nil)
+ s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
}
func (s *checkSnapSuite) TearDownTest(c *C) {
+ s.BaseTest.TearDownTest(c)
dirs.SetRootDir("")
}
diff -Nru snapd-2.28.5/overlord/snapstate/export_test.go snapd-2.29.3/overlord/snapstate/export_test.go
--- snapd-2.28.5/overlord/snapstate/export_test.go 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/overlord/snapstate/export_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -21,6 +21,7 @@
import (
"errors"
+ "time"
"gopkg.in/tomb.v2"
@@ -95,11 +96,16 @@
return func() { errtrackerReport = prev }
}
+func MockPrerequisitesRetryTimeout(d time.Duration) (restore func()) {
+ old := prerequisitesRetryTimeout
+ prerequisitesRetryTimeout = d
+ return func() { prerequisitesRetryTimeout = old }
+}
+
var (
CheckSnap = checkSnap
CanRemove = canRemove
CanDisable = canDisable
- CachedStore = cachedStore
DefaultRefreshSchedule = defaultRefreshSchedule
NameAndRevnoFromSnap = nameAndRevnoFromSnap
)
diff -Nru snapd-2.28.5/overlord/snapstate/flags.go snapd-2.29.3/overlord/snapstate/flags.go
--- snapd-2.28.5/overlord/snapstate/flags.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/overlord/snapstate/flags.go 2017-11-02 15:44:20.000000000 +0000
@@ -62,7 +62,6 @@
// ForSnapSetup returns a copy of the Flags with the flags that we don't need in SnapSetup set to false (so they're not serialized)
func (f Flags) ForSnapSetup() Flags {
- f.IgnoreValidation = false
f.SkipConfigure = false
return f
}
diff -Nru snapd-2.28.5/overlord/snapstate/handlers_download_test.go snapd-2.29.3/overlord/snapstate/handlers_download_test.go
--- snapd-2.28.5/overlord/snapstate/handlers_download_test.go 2017-08-08 06:31:43.000000000 +0000
+++ snapd-2.29.3/overlord/snapstate/handlers_download_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -24,6 +24,7 @@
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/overlord/storestate"
"github.com/snapcore/snapd/snap"
)
@@ -49,7 +50,7 @@
state: s.state,
fakeBackend: s.fakeBackend,
}
- snapstate.ReplaceStore(s.state, s.fakeStore)
+ storestate.ReplaceStore(s.state, s.fakeStore)
var err error
s.snapmgr, err = snapstate.Manager(s.state)
diff -Nru snapd-2.28.5/overlord/snapstate/handlers.go snapd-2.29.3/overlord/snapstate/handlers.go
--- snapd-2.28.5/overlord/snapstate/handlers.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/overlord/snapstate/handlers.go 2017-11-02 15:44:20.000000000 +0000
@@ -36,6 +36,7 @@
"github.com/snapcore/snapd/overlord/configstate/config"
"github.com/snapcore/snapd/overlord/snapstate/backend"
"github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/overlord/storestate"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/store"
@@ -115,6 +116,107 @@
*/
+const defaultCoreSnapName = "core"
+const defaultBaseSnapsChannel = "stable"
+
+func changeInFlight(st *state.State, snapName string) (bool, error) {
+ for _, chg := range st.Changes() {
+ if chg.Status().Ready() {
+ continue
+ }
+ for _, tc := range chg.Tasks() {
+ if tc.Kind() == "link-snap" || tc.Kind() == "discard-snap" {
+ snapsup, err := TaskSnapSetup(tc)
+ if err != nil {
+ return false, err
+ }
+ // some other change aleady inflight
+ if snapsup.Name() == snapName {
+ return true, nil
+ }
+ }
+ }
+ }
+
+ return false, nil
+}
+
+// timeout for tasks to check if the prerequisites are ready
+var prerequisitesRetryTimeout = 30 * time.Second
+
+func (m *SnapManager) doPrerequisites(t *state.Task, _ *tomb.Tomb) error {
+ st := t.State()
+ st.Lock()
+ defer st.Unlock()
+
+ // check if we need to inject tasks to install core
+ snapsup, _, err := snapSetupAndState(t)
+ if err != nil {
+ return err
+ }
+
+ // core/ubuntu-core can not have prerequisites
+ snapName := snapsup.Name()
+ if snapName == defaultCoreSnapName || snapName == "ubuntu-core" {
+ return nil
+ }
+
+ // check prereqs
+ prereqName := defaultCoreSnapName
+ if snapsup.Base != "" {
+ prereqName = snapsup.Base
+ }
+
+ var prereqState SnapState
+ err = Get(st, prereqName, &prereqState)
+ // we have the prereq already
+ if err == nil {
+ return nil
+ }
+ // if it is a real error, report
+ if err != state.ErrNoState {
+ return err
+ }
+
+ // check that there is no task that installs the prereq already
+ prereqPending, err := changeInFlight(st, prereqName)
+ if err != nil {
+ return err
+ }
+ if prereqPending {
+ // if something else installs core already we need to
+ // wait for that to either finish successfully or fail
+ return &state.Retry{After: prerequisitesRetryTimeout}
+ }
+
+ // not installed, nor queued for install -> install it
+ ts, err := Install(st, prereqName, defaultBaseSnapsChannel, snap.R(0), snapsup.UserID, Flags{})
+ // something might have triggered an explicit install of core while
+ // the state was unlocked -> deal with that here
+ if _, ok := err.(changeDuringInstallError); ok {
+ return &state.Retry{After: prerequisitesRetryTimeout}
+ }
+ if _, ok := err.(changeConflictError); ok {
+ return &state.Retry{After: prerequisitesRetryTimeout}
+ }
+ if err != nil {
+ return err
+ }
+ ts.JoinLane(st.NewLane())
+
+ // inject install for core into this change
+ chg := t.Change()
+ for _, t := range chg.Tasks() {
+ t.WaitAll(ts)
+ }
+ chg.AddAll(ts)
+ // make sure that the new change is committed to the state
+ // together with marking this task done
+ t.SetStatus(state.DoneStatus)
+
+ return nil
+}
+
func (m *SnapManager) doPrepareSnap(t *state.Task, _ *tomb.Tomb) error {
st := t.State()
st.Lock()
@@ -223,7 +325,7 @@
}
st.Lock()
- theStore := Store(st)
+ theStore := storestate.Store(st)
user, err := userFromUserID(st, snapsup.UserID)
st.Unlock()
if err != nil {
@@ -295,6 +397,7 @@
if err != nil {
return err
}
+
t.State().Lock()
t.Set("snap-type", newInfo.Type)
t.State().Unlock()
@@ -541,6 +644,8 @@
if snapsup.Channel != "" {
snapst.Channel = snapsup.Channel
}
+ oldIgnoreValidation := snapst.IgnoreValidation
+ snapst.IgnoreValidation = snapsup.IgnoreValidation
oldTryMode := snapst.TryMode
snapst.TryMode = snapsup.TryMode
oldDevMode := snapst.DevMode
@@ -592,6 +697,7 @@
t.Set("old-devmode", oldDevMode)
t.Set("old-jailmode", oldJailMode)
t.Set("old-classic", oldClassic)
+ t.Set("old-ignore-validation", oldIgnoreValidation)
t.Set("old-channel", oldChannel)
t.Set("old-current", oldCurrent)
t.Set("old-candidate-index", oldCandidateIndex)
@@ -636,6 +742,11 @@
if err != nil {
return err
}
+ var oldIgnoreValidation bool
+ err = t.Get("old-ignore-validation", &oldIgnoreValidation)
+ if err != nil && err != state.ErrNoState {
+ return err
+ }
var oldTryMode bool
err = t.Get("old-trymode", &oldTryMode)
if err != nil {
@@ -690,6 +801,7 @@
snapst.Current = oldCurrent
snapst.Active = false
snapst.Channel = oldChannel
+ snapst.IgnoreValidation = oldIgnoreValidation
snapst.TryMode = oldTryMode
snapst.DevMode = oldDevMode
snapst.JailMode = oldJailMode
diff -Nru snapd-2.28.5/overlord/snapstate/snapmgr.go snapd-2.29.3/overlord/snapstate/snapmgr.go
--- snapd-2.28.5/overlord/snapstate/snapmgr.go 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/overlord/snapstate/snapmgr.go 2017-10-23 06:17:27.000000000 +0000
@@ -35,9 +35,9 @@
"github.com/snapcore/snapd/overlord/configstate/config"
"github.com/snapcore/snapd/overlord/snapstate/backend"
"github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/overlord/storestate"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
- "github.com/snapcore/snapd/store"
"github.com/snapcore/snapd/strutil"
"github.com/snapcore/snapd/timeutil"
)
@@ -69,6 +69,7 @@
// overridden in the tests
var errtrackerReport = errtracker.Report
+var catalogRefreshDelay = 24 * time.Hour
// SnapManager is responsible for the installation and removal of snaps.
type SnapManager struct {
@@ -79,6 +80,8 @@
nextRefresh time.Time
lastRefreshAttempt time.Time
+ nextCatalogRefresh time.Time
+
lastUbuntuCoreTransitionAttempt time.Time
runner *state.TaskRunner
@@ -89,6 +92,7 @@
// FIXME: rename to RequestedChannel to convey the meaning better
Channel string `json:"channel,omitempty"`
UserID int `json:"user-id,omitempty"`
+ Base string `json:"base,omitempty"`
Flags
@@ -266,32 +270,6 @@
return false
}
-type cachedStoreKey struct{}
-
-// ReplaceStore replaces the store used by the manager.
-func ReplaceStore(state *state.State, store StoreService) {
- state.Cache(cachedStoreKey{}, store)
-}
-
-func cachedStore(st *state.State) StoreService {
- ubuntuStore := st.Cached(cachedStoreKey{})
- if ubuntuStore == nil {
- return nil
- }
- return ubuntuStore.(StoreService)
-}
-
-// the store implementation has the interface consumed here
-var _ StoreService = (*store.Store)(nil)
-
-// Store returns the store service used by the snapstate package.
-func Store(st *state.State) StoreService {
- if cachedStore := cachedStore(st); cachedStore != nil {
- return cachedStore
- }
- panic("internal error: needing the store before managers have initialized it")
-}
-
// Manager returns a new snap manager.
func Manager(st *state.State) (*SnapManager, error) {
runner := state.NewTaskRunner(st)
@@ -312,6 +290,10 @@
}, nil)
// install/update related
+
+ // TODO: no undo handler here, we may use the GC for this and just
+ // remove anything that is not referenced anymore
+ runner.AddHandler("prerequisites", m.doPrerequisites, nil)
runner.AddHandler("prepare-snap", m.doPrepareSnap, m.undoPrepareSnap)
runner.AddHandler("download-snap", m.doDownloadSnap, m.undoPrepareSnap)
runner.AddHandler("mount-snap", m.doMountSnap, m.undoMountSnap)
@@ -351,18 +333,21 @@
// control serialisation
runner.SetBlocked(m.blockedTask)
- // test handlers
- runner.AddHandler("fake-install-snap", func(t *state.Task, _ *tomb.Tomb) error {
- return nil
- }, nil)
- runner.AddHandler("fake-install-snap-error", func(t *state.Task, _ *tomb.Tomb) error {
- return fmt.Errorf("fake-install-snap-error errored")
- }, nil)
-
return m, nil
}
func (m *SnapManager) blockedTask(cand *state.Task, running []*state.Task) bool {
+ // Serialize "prerequisites", the state lock is not enough as
+ // Install() inside doPrerequisites() will unlock to talk to
+ // the store.
+ if cand.Kind() == "prerequisites" {
+ for _, t := range running {
+ if t.Kind() == "prerequisites" {
+ return true
+ }
+ }
+ }
+
return false
}
@@ -468,14 +453,26 @@
return lastRefresh, nil
}
+// NextRefresh returns the time the next update of the system's snaps
+// will be attempted.
+// The caller should be holding the state lock.
func (m *SnapManager) NextRefresh() time.Time {
return m.nextRefresh
}
+// RefreshSchedule returns the current refresh schedule.
+// The caller should be holding the state lock.
func (m *SnapManager) RefreshSchedule() string {
return m.currentRefreshSchedule
}
+// NextCatalogRefresh returns the time the next update of catalog
+// data will be attempted.
+// The caller should be holding the state lock.
+func (m *SnapManager) NextCatalogRefresh() time.Time {
+ return m.nextCatalogRefresh
+}
+
// ensureRefreshes ensures that we refresh all installed snaps periodically
func (m *SnapManager) ensureRefreshes() error {
m.state.Lock()
@@ -538,6 +535,33 @@
return err
}
+// ensureCatalogRefresh ensures that we refresh the catalog
+// data periodically
+func (m *SnapManager) ensureCatalogRefresh() error {
+ // sneakily don't do anything if in testing
+ if CanAutoRefresh == nil {
+ return nil
+ }
+ m.state.Lock()
+ defer m.state.Unlock()
+
+ theStore := storestate.Store(m.state)
+ now := time.Now()
+ needsRefresh := m.nextCatalogRefresh.IsZero() || m.nextCatalogRefresh.Before(now)
+
+ if !needsRefresh {
+ return nil
+ }
+
+ next := now.Add(catalogRefreshDelay)
+ // catalog refresh does not carry on trying on error
+ m.nextCatalogRefresh = next
+
+ logger.Debugf("Catalog refresh starting now; next scheduled for %s.", next)
+
+ return refreshCatalogs(m.state, theStore)
+}
+
// ensureForceDevmodeDropsDevmodeFromState undoes the froced devmode
// in snapstate for forced devmode distros.
func (m *SnapManager) ensureForceDevmodeDropsDevmodeFromState() error {
@@ -687,6 +711,7 @@
m.ensureForceDevmodeDropsDevmodeFromState(),
m.ensureUbuntuCoreTransition(),
m.ensureRefreshes(),
+ m.ensureCatalogRefresh(),
}
m.runner.Ensure()
diff -Nru snapd-2.28.5/overlord/snapstate/snapstate.go snapd-2.29.3/overlord/snapstate/snapstate.go
--- snapd-2.28.5/overlord/snapstate/snapstate.go 2017-10-04 14:43:22.000000000 +0000
+++ snapd-2.29.3/overlord/snapstate/snapstate.go 2017-11-02 15:44:20.000000000 +0000
@@ -23,16 +23,21 @@
import (
"encoding/json"
"fmt"
+ "os"
"reflect"
"sort"
+ "strings"
"github.com/snapcore/snapd/boot"
"github.com/snapcore/snapd/dirs"
- "github.com/snapcore/snapd/i18n/dumb"
+ "github.com/snapcore/snapd/i18n"
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/logger"
+ "github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/overlord/auth"
+ "github.com/snapcore/snapd/overlord/snapstate/backend"
"github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/overlord/storestate"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/store"
@@ -78,6 +83,10 @@
return nil, err
}
+ // ensure core gets installed. if it is already installed return
+ // an empty task set
+ ts := state.NewTaskSet()
+
targetRevision := snapsup.Revision()
revisionStr := ""
if snapsup.SideInfo != nil {
@@ -87,6 +96,9 @@
// check if we already have the revision locally (alters tasks)
revisionIsLocal := snapst.LastIndex(targetRevision) >= 0
+ prereq := st.NewTask("prerequisites", fmt.Sprintf(i18n.G("Ensure prerequisites for %q are available"), snapsup.Name()))
+ prereq.Set("snap-setup", snapsup)
+
var prepare, prev *state.Task
fromStore := false
// if we have a local revision here we go back to that
@@ -97,8 +109,9 @@
prepare = st.NewTask("download-snap", fmt.Sprintf(i18n.G("Download snap %q%s from channel %q"), snapsup.Name(), revisionStr, snapsup.Channel))
}
prepare.Set("snap-setup", snapsup)
+ prepare.WaitFor(prereq)
- tasks := []*state.Task{prepare}
+ tasks := []*state.Task{prereq, prepare}
addTask := func(t *state.Task) {
t.Set("snap-setup-task", prepare.ID())
t.WaitFor(prev)
@@ -236,6 +249,8 @@
}
installSet := state.NewTaskSet(tasks...)
+ installSet.WaitAll(ts)
+ ts.AddAll(installSet)
if flags&skipConfigure != 0 {
return installSet, nil
@@ -249,10 +264,10 @@
}
configSet := ConfigureSnap(st, snapsup.Name(), confFlags)
- configSet.WaitAll(installSet)
- installSet.AddAll(configSet)
+ configSet.WaitAll(ts)
+ ts.AddAll(configSet)
- return installSet, nil
+ return ts, nil
}
// ConfigureSnap returns a set of tasks to configure snapName as done during installation/refresh.
@@ -260,7 +275,7 @@
// This is slightly ugly, ideally we would check the type instead
// of hardcoding the name here. Unfortunately we do not have the
// type until we actually run the change.
- if snapName == "core" {
+ if snapName == defaultCoreSnapName {
confFlags |= IgnoreHookError
confFlags |= TrackHookError
}
@@ -310,6 +325,14 @@
return &plugRef, &slotRef, nil
}
+type changeConflictError struct {
+ snapName string
+}
+
+func (e changeConflictError) Error() string {
+ return fmt.Sprintf("snap %q has changes in progress", e.snapName)
+}
+
// 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.
@@ -347,7 +370,7 @@
} else {
snapName = slotRef.Snap
}
- return fmt.Errorf("snap %q has changes in progress", snapName)
+ return changeConflictError{snapName}
}
} else {
snapsup, err := TaskSnapSetup(task)
@@ -356,7 +379,7 @@
}
snapName := snapsup.Name()
if (snapMap[snapName]) && (checkConflictPredicate == nil || checkConflictPredicate(k)) {
- return fmt.Errorf("snap %q has changes in progress", snapName)
+ return changeConflictError{snapName}
}
}
}
@@ -365,6 +388,14 @@
return nil
}
+type changeDuringInstallError struct {
+ snapName string
+}
+
+func (c changeDuringInstallError) Error() string {
+ return fmt.Sprintf("snap %q state changed during install preparations", c.snapName)
+}
+
// 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
@@ -388,7 +419,7 @@
// TODO: implement the rather-boring-but-more-performant SnapState.Equals
if !reflect.DeepEqual(snapst, &cursnapst) {
- return fmt.Errorf("snap %q state changed during install preparations", snapName)
+ return changeDuringInstallError{snapName: snapName}
}
}
@@ -425,7 +456,15 @@
instFlags |= skipConfigure
}
+ // It is ok do open the snap file here because we either
+ // have side info or the user passed --dangerous
+ info, _, err := backend.OpenSnapFile(path, si)
+ if err != nil {
+ return nil, err
+ }
+
snapsup := &SnapSetup{
+ Base: info.Base,
SideInfo: si,
SnapPath: path,
Channel: channel,
@@ -470,6 +509,7 @@
snapsup := &SnapSetup{
Channel: channel,
+ Base: info.Base,
UserID: userID,
Flags: flags.ForSnapSetup(),
DownloadInfo: &info.DownloadInfo,
@@ -503,20 +543,21 @@
// RefreshCandidates gets a list of candidates for update
// Note that the state must be locked by the caller.
func RefreshCandidates(st *state.State, user *auth.UserState) ([]*snap.Info, error) {
- updates, _, err := refreshCandidates(st, nil, user)
+ updates, _, _, err := refreshCandidates(st, nil, user)
return updates, err
}
-func refreshCandidates(st *state.State, names []string, user *auth.UserState) ([]*snap.Info, map[string]*SnapState, error) {
+func refreshCandidates(st *state.State, names []string, user *auth.UserState) ([]*snap.Info, map[string]*SnapState, map[string]bool, error) {
snapStates, err := All(st)
if err != nil {
- return nil, nil, err
+ return nil, nil, nil, err
}
sort.Strings(names)
stateByID := make(map[string]*SnapState, len(snapStates))
candidatesInfo := make([]*store.RefreshCandidate, 0, len(snapStates))
+ ignoreValidation := make(map[string]bool)
for _, snapst := range snapStates {
if len(names) == 0 && (snapst.TryMode || snapst.DevMode) {
// no auto-refresh for trymode nor devmode
@@ -549,10 +590,11 @@
// get confinement preference from the snapstate
candidateInfo := &store.RefreshCandidate{
// the desired channel (not info.Channel!)
- Channel: snapst.Channel,
- SnapID: snapInfo.SnapID,
- Revision: snapInfo.Revision,
- Epoch: snapInfo.Epoch,
+ Channel: snapst.Channel,
+ SnapID: snapInfo.SnapID,
+ Revision: snapInfo.Revision,
+ Epoch: snapInfo.Epoch,
+ IgnoreValidation: snapst.IgnoreValidation,
}
if len(names) == 0 {
@@ -560,22 +602,25 @@
}
candidatesInfo = append(candidatesInfo, candidateInfo)
+ if snapst.IgnoreValidation {
+ ignoreValidation[snapInfo.SnapID] = true
+ }
}
- theStore := Store(st)
+ theStore := storestate.Store(st)
st.Unlock()
updates, err := theStore.ListRefresh(candidatesInfo, user)
st.Lock()
if err != nil {
- return nil, nil, err
+ return nil, nil, nil, err
}
- return updates, stateByID, nil
+ return updates, stateByID, ignoreValidation, nil
}
// ValidateRefreshes allows to hook validation into the handling of refresh candidates.
-var ValidateRefreshes func(st *state.State, refreshes []*snap.Info, userID int) (validated []*snap.Info, err error)
+var ValidateRefreshes func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int) (validated []*snap.Info, err error)
// UpdateMany updates everything from the given list of names that the
// store says is updateable. If the list is empty, update everything.
@@ -586,13 +631,13 @@
return nil, nil, err
}
- updates, stateByID, err := refreshCandidates(st, names, user)
+ updates, stateByID, ignoreValidation, err := refreshCandidates(st, names, user)
if err != nil {
return nil, nil, err
}
if ValidateRefreshes != nil && len(updates) != 0 {
- updates, err = ValidateRefreshes(st, updates, userID)
+ updates, err = ValidateRefreshes(st, updates, ignoreValidation, userID)
if err != nil {
// not doing "refresh all" report the error
if len(names) != 0 {
@@ -863,6 +908,8 @@
channel = snapst.Channel
}
+ // TODO: make flags be per revision to avoid this logic (that
+ // leaves corner cases all over the place)
if !(flags.JailMode || flags.DevMode) {
flags.Classic = flags.Classic || snapst.Flags.Classic
}
@@ -889,6 +936,7 @@
// see if we need to update the channel
if infoErr == store.ErrNoUpdateAvailable && snapst.Channel != channel {
+ // TODO: do we want to treat ignore-validation similarly?
snapsup := &SnapSetup{
SideInfo: snapst.CurrentSideInfo(),
// update the tracked channel
@@ -922,7 +970,7 @@
func infoForUpdate(st *state.State, snapst *SnapState, name, channel string, revision snap.Revision, userID int, flags Flags) (*snap.Info, error) {
if revision.Unset() {
// good ol' refresh
- info, err := updateInfo(st, snapst, channel, userID)
+ info, err := updateInfo(st, snapst, channel, flags.IgnoreValidation, userID)
if err != nil {
return nil, err
}
@@ -930,7 +978,7 @@
return nil, err
}
if ValidateRefreshes != nil && !flags.IgnoreValidation {
- _, err := ValidateRefreshes(st, []*snap.Info{info}, userID)
+ _, err := ValidateRefreshes(st, []*snap.Info{info}, nil, userID)
if err != nil {
return nil, err
}
@@ -1342,6 +1390,19 @@
return nil, err
}
flags.Revert = true
+ // TODO: make flags be per revision to avoid this logic (that
+ // leaves corner cases all over the place)
+ if !(flags.JailMode || flags.DevMode || flags.Classic) {
+ if snapst.Flags.DevMode {
+ flags.DevMode = true
+ }
+ if snapst.Flags.JailMode {
+ flags.JailMode = true
+ }
+ if snapst.Flags.Classic {
+ flags.Classic = true
+ }
+ }
snapsup := &SnapSetup{
SideInfo: snapst.Sequence[i],
Flags: flags.ForSnapSetup(),
@@ -1628,10 +1689,10 @@
// some systems have two cores: ubuntu-core/core
// we always return "core" in this case
if len(res) == 2 {
- if res[0].Name() == "core" && res[1].Name() == "ubuntu-core" {
+ if res[0].Name() == defaultCoreSnapName && res[1].Name() == "ubuntu-core" {
return res[0], nil
}
- if res[0].Name() == "ubuntu-core" && res[1].Name() == "core" {
+ if res[0].Name() == "ubuntu-core" && res[1].Name() == defaultCoreSnapName {
return res[1], nil
}
return nil, fmt.Errorf("unexpected cores %q and %q", res[0].Name(), res[1].Name())
@@ -1669,3 +1730,33 @@
return defaults, nil
}
+
+func refreshCatalogs(st *state.State, theStore storestate.StoreService) error {
+ st.Unlock()
+ defer st.Lock()
+
+ if err := os.MkdirAll(dirs.SnapCacheDir, 0755); err != nil {
+ return fmt.Errorf("cannot create directory %q: %v", dirs.SnapCacheDir, err)
+ }
+
+ sections, err := theStore.Sections(nil)
+ if err != nil {
+ return err
+ }
+
+ sort.Strings(sections)
+ if err := osutil.AtomicWriteFile(dirs.SnapSectionsFile, []byte(strings.Join(sections, "\n")), 0644, 0); err != nil {
+ return err
+ }
+
+ namesFile, err := osutil.NewAtomicFile(dirs.SnapNamesFile, 0644, 0, -1, -1)
+ if err != nil {
+ return err
+ }
+ defer namesFile.Cancel()
+ if err := theStore.WriteCatalogs(namesFile); err != nil {
+ return err
+ }
+
+ return namesFile.Commit()
+}
diff -Nru snapd-2.28.5/overlord/snapstate/snapstate_test.go snapd-2.29.3/overlord/snapstate/snapstate_test.go
--- snapd-2.28.5/overlord/snapstate/snapstate_test.go 2017-10-04 14:43:22.000000000 +0000
+++ snapd-2.29.3/overlord/snapstate/snapstate_test.go 2017-11-02 15:44:20.000000000 +0000
@@ -20,7 +20,6 @@
package snapstate_test
import (
- "bytes"
"encoding/json"
"errors"
"fmt"
@@ -37,12 +36,14 @@
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/overlord"
"github.com/snapcore/snapd/overlord/auth"
"github.com/snapcore/snapd/overlord/configstate/config"
"github.com/snapcore/snapd/overlord/hookstate"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/snapstate/backend"
"github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/overlord/storestate"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snaptest"
@@ -57,6 +58,8 @@
func TestSnapManager(t *testing.T) { TestingT(t) }
type snapmgrTestSuite struct {
+ testutil.BaseTest
+ o *overlord.Overlord
state *state.State
snapmgr *snapstate.SnapManager
@@ -64,25 +67,25 @@
fakeStore *fakeStore
user *auth.UserState
-
- reset func()
}
-func (s *snapmgrTestSuite) settle() {
- // FIXME: use the real settle here
- for i := 0; i < 50; i++ {
- s.snapmgr.Ensure()
- s.snapmgr.Wait()
- }
+func (s *snapmgrTestSuite) settle(c *C) {
+ err := s.o.Settle(5 * time.Second)
+ c.Assert(err, IsNil)
}
var _ = Suite(&snapmgrTestSuite{})
func (s *snapmgrTestSuite) SetUpTest(c *C) {
+ s.BaseTest.SetUpTest(c)
dirs.SetRootDir(c.MkDir())
+ s.o = overlord.Mock()
+ s.state = s.o.State()
+
+ s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
+
s.fakeBackend = &fakeSnappyBackend{}
- s.state = state.New(nil)
s.fakeStore = &fakeStore{
fakeCurrentProgress: 75,
fakeTotalProgress: 100,
@@ -104,23 +107,32 @@
snapstate.SetSnapManagerBackend(s.snapmgr, s.fakeBackend)
- restore1 := snapstate.MockReadInfo(s.fakeBackend.ReadInfo)
- restore2 := snapstate.MockOpenSnapFile(s.fakeBackend.OpenSnapFile)
+ s.o.AddManager(s.snapmgr)
+
+ s.BaseTest.AddCleanup(snapstate.MockReadInfo(s.fakeBackend.ReadInfo))
+ s.BaseTest.AddCleanup(snapstate.MockOpenSnapFile(s.fakeBackend.OpenSnapFile))
- s.reset = func() {
+ s.BaseTest.AddCleanup(func() {
snapstate.SetupInstallHook = oldSetupInstallHook
snapstate.SetupPostRefreshHook = oldSetupPostRefreshHook
snapstate.SetupRemoveHook = oldSetupRemoveHook
- restore2()
- restore1()
dirs.SetRootDir("/")
- }
+ })
s.state.Lock()
- snapstate.ReplaceStore(s.state, s.fakeStore)
+ storestate.ReplaceStore(s.state, s.fakeStore)
s.user, err = auth.NewUser(s.state, "username", "email@test.com", "macaroon", []string{"discharge"})
c.Assert(err, IsNil)
+
+ snapstate.Set(s.state, "core", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{
+ {RealName: "core", Revision: snap.R(1)},
+ },
+ Current: snap.R(1),
+ SnapType: "os",
+ })
s.state.Unlock()
snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) {
@@ -129,24 +141,10 @@
}
func (s *snapmgrTestSuite) TearDownTest(c *C) {
+ s.BaseTest.TearDownTest(c)
snapstate.ValidateRefreshes = nil
snapstate.AutoAliases = nil
snapstate.CanAutoRefresh = nil
- s.reset()
-}
-
-func (s *snapmgrTestSuite) TestStore(c *C) {
- s.state.Lock()
- defer s.state.Unlock()
-
- sto := &store.Store{}
- snapstate.ReplaceStore(s.state, sto)
- store1 := snapstate.Store(s.state)
- c.Check(store1, Equals, sto)
-
- // cached
- store2 := snapstate.Store(s.state)
- c.Check(store2, Equals, sto)
}
const (
@@ -175,6 +173,7 @@
kinds := taskKinds(ts.Tasks())
expected := []string{
+ "prerequisites",
"download-snap",
"validate-snap",
"mount-snap",
@@ -221,6 +220,7 @@
kinds := taskKinds(ts.Tasks())
expected := []string{
+ "prerequisites",
"download-snap",
"validate-snap",
"mount-snap",
@@ -361,6 +361,9 @@
})
defer reset()
+ // this needs doing because dirs depends on the release info
+ dirs.SetRootDir(dirs.GlobalRootDir)
+
_, err := snapstate.Install(s.state, "some-snap", "channel-for-classic", snap.R(0), s.user.ID, snapstate.Flags{Classic: true})
c.Assert(err, ErrorMatches, "classic confinement requires snaps under /snap or symlink from /snap to "+dirs.SnapMountDir)
}
@@ -423,7 +426,9 @@
c.Check(flag, Equals, true)
}
-func (s *snapmgrTestSuite) testRevertTasks(flags snapstate.Flags, c *C) {
+type fullFlags struct{ before, change, after, setup snapstate.Flags }
+
+func (s *snapmgrTestSuite) testRevertTasksFullFlags(flags fullFlags, c *C) {
s.state.Lock()
defer s.state.Unlock()
@@ -433,15 +438,18 @@
{RealName: "some-snap", Revision: snap.R(7)},
{RealName: "some-snap", Revision: snap.R(11)},
},
+ Flags: flags.before,
Current: snap.R(11),
SnapType: "app",
})
- ts, err := snapstate.Revert(s.state, "some-snap", flags)
+ ts, err := snapstate.Revert(s.state, "some-snap", flags.change)
c.Assert(err, IsNil)
- c.Assert(s.state.TaskCount(), Equals, len(ts.Tasks()))
- c.Assert(taskKinds(ts.Tasks()), DeepEquals, []string{
+ tasks := ts.Tasks()
+ c.Assert(s.state.TaskCount(), Equals, len(tasks))
+ c.Assert(taskKinds(tasks), DeepEquals, []string{
+ "prerequisites",
"prepare-snap",
"stop-snap-services",
"remove-aliases",
@@ -454,24 +462,67 @@
"run-hook[configure]",
})
+ snapsup, err := snapstate.TaskSnapSetup(tasks[0])
+ c.Assert(err, IsNil)
+ flags.setup.Revert = true
+ c.Check(snapsup.Flags, Equals, flags.setup)
+
chg := s.state.NewChange("revert", "revert snap")
chg.AddAll(ts)
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
var snapst snapstate.SnapState
err = snapstate.Get(s.state, "some-snap", &snapst)
c.Assert(err, IsNil)
- c.Check(snapst.Flags, Equals, flags)
+ c.Check(snapst.Flags, Equals, flags.after)
+}
+
+func (s *snapmgrTestSuite) testRevertTasks(flags snapstate.Flags, c *C) {
+ s.testRevertTasksFullFlags(fullFlags{before: flags, change: flags, after: flags, setup: flags}, c)
}
func (s *snapmgrTestSuite) TestRevertTasks(c *C) {
s.testRevertTasks(snapstate.Flags{}, c)
}
+func (s *snapmgrTestSuite) TestRevertTasksFromDevMode(c *C) {
+ // the snap is installed in devmode, but the request to revert does not specify devmode
+ s.testRevertTasksFullFlags(fullFlags{
+ before: snapstate.Flags{DevMode: true}, // the snap is installed in devmode
+ change: snapstate.Flags{}, // the request to revert does not specify devmode
+ after: snapstate.Flags{DevMode: true}, // the reverted snap is installed in devmode
+ setup: snapstate.Flags{DevMode: true}, // because setup said so
+ }, c)
+}
+
+func (s *snapmgrTestSuite) TestRevertTasksFromJailMode(c *C) {
+ // the snap is installed in jailmode, but the request to revert does not specify jailmode
+ s.testRevertTasksFullFlags(fullFlags{
+ before: snapstate.Flags{JailMode: true}, // the snap is installed in jailmode
+ change: snapstate.Flags{}, // the request to revert does not specify jailmode
+ after: snapstate.Flags{JailMode: true}, // the reverted snap is installed in jailmode
+ setup: snapstate.Flags{JailMode: true}, // because setup said so
+ }, c)
+}
+
+func (s *snapmgrTestSuite) TestRevertTasksFromClassic(c *C) {
+ if !dirs.SupportsClassicConfinement() {
+ c.Skip("no support for classic")
+ }
+
+ // the snap is installed in classic, but the request to revert does not specify classic
+ s.testRevertTasksFullFlags(fullFlags{
+ before: snapstate.Flags{Classic: true}, // the snap is installed in classic
+ change: snapstate.Flags{}, // the request to revert does not specify classic
+ after: snapstate.Flags{Classic: true}, // the reverted snap is installed in classic
+ setup: snapstate.Flags{Classic: true}, // because setup said so
+ }, c)
+}
+
func (s *snapmgrTestSuite) TestRevertTasksDevMode(c *C) {
s.testRevertTasks(snapstate.Flags{DevMode: true}, c)
}
@@ -482,7 +533,7 @@
func (s *snapmgrTestSuite) TestRevertTasksClassic(c *C) {
if !dirs.SupportsClassicConfinement() {
- return
+ c.Skip("no support for classic")
}
s.testRevertTasks(snapstate.Flags{Classic: true}, c)
}
@@ -579,7 +630,7 @@
func (s *snapmgrTestSuite) TestUpdateManyClassicConfinementFiltering(c *C) {
if !dirs.SupportsClassicConfinement() {
- return
+ c.Skip("no support for classic")
}
s.state.Lock()
@@ -601,7 +652,7 @@
func (s *snapmgrTestSuite) TestUpdateManyClassic(c *C) {
if !dirs.SupportsClassicConfinement() {
- return
+ c.Skip("no support for classic")
}
s.state.Lock()
@@ -674,11 +725,12 @@
})
validateCalled := false
- validateRefreshes := func(st *state.State, refreshes []*snap.Info, userID int) ([]*snap.Info, error) {
+ validateRefreshes := func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int) ([]*snap.Info, error) {
validateCalled = true
c.Check(refreshes, HasLen, 1)
c.Check(refreshes[0].SnapID, Equals, "some-snap-id")
c.Check(refreshes[0].Revision, Equals, snap.R(11))
+ c.Check(ignoreValidation, HasLen, 0)
return refreshes, nil
}
// hook it up
@@ -706,10 +758,11 @@
})
validateErr := errors.New("refresh control error")
- validateRefreshes := func(st *state.State, refreshes []*snap.Info, userID int) ([]*snap.Info, error) {
+ validateRefreshes := func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int) ([]*snap.Info, error) {
c.Check(refreshes, HasLen, 1)
c.Check(refreshes[0].SnapID, Equals, "some-snap-id")
c.Check(refreshes[0].Revision, Equals, snap.R(11))
+ c.Check(ignoreValidation, HasLen, 0)
return nil, validateErr
}
// hook it up
@@ -751,6 +804,7 @@
// ensure that we do not run any form of garbage-collection
c.Assert(s.state.TaskCount(), Equals, len(ts.Tasks()))
c.Assert(taskKinds(ts.Tasks()), DeepEquals, []string{
+ "prerequisites",
"prepare-snap",
"stop-snap-services",
"remove-aliases",
@@ -936,6 +990,7 @@
Aliases: map[string]*snapstate.AliasTarget{
"foo.bar": {Manual: "bar"},
},
+ SnapType: "app",
})
_, err := snapstate.Install(s.state, "foo", "some-channel", snap.R(0), 0, snapstate.Flags{})
@@ -955,17 +1010,17 @@
Channel: "edge",
Sequence: []*snap.SideInfo{{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)}},
Current: snap.R(1),
+ SnapType: "app",
})
s.state.Unlock()
return s.fakeStore.SnapInfo(spec, user)
}
func (s *snapmgrTestSuite) TestInstallStateConflict(c *C) {
-
s.state.Lock()
defer s.state.Unlock()
- snapstate.ReplaceStore(s.state, sneakyStore{fakeStore: s.fakeStore, state: s.state})
+ storestate.ReplaceStore(s.state, sneakyStore{fakeStore: s.fakeStore, state: s.state})
_, err := snapstate.Install(s.state, "some-snap", "some-channel", snap.R(0), 0, snapstate.Flags{})
c.Assert(err, ErrorMatches, `snap "some-snap" state changed during install preparations`)
@@ -1031,7 +1086,7 @@
})
validateCalled := false
- happyValidateRefreshes := func(st *state.State, refreshes []*snap.Info, userID int) ([]*snap.Info, error) {
+ happyValidateRefreshes := func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int) ([]*snap.Info, error) {
validateCalled = true
return refreshes, nil
}
@@ -1082,7 +1137,7 @@
func (s *snapmgrTestSuite) TestUpdateDevModeConfinementFiltering(c *C) {
if !dirs.SupportsClassicConfinement() {
- return
+ c.Skip("no support for classic")
}
s.state.Lock()
@@ -1108,7 +1163,7 @@
func (s *snapmgrTestSuite) TestUpdateClassicConfinementFiltering(c *C) {
if !dirs.SupportsClassicConfinement() {
- return
+ c.Skip("no support for classic")
}
s.state.Lock()
@@ -1136,7 +1191,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
// verify snap is in classic
@@ -1148,7 +1203,7 @@
func (s *snapmgrTestSuite) TestUpdateClassicFromClassic(c *C) {
if !dirs.SupportsClassicConfinement() {
- return
+ c.Skip("no support for classic")
}
s.state.Lock()
@@ -1208,7 +1263,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
// verify snap is in classic
@@ -1220,7 +1275,7 @@
func (s *snapmgrTestSuite) TestUpdateStrictFromClassic(c *C) {
if !dirs.SupportsClassicConfinement() {
- return
+ c.Skip("no support for classic")
}
s.state.Lock()
@@ -1394,7 +1449,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
// ensure all our tasks ran
@@ -1477,7 +1532,7 @@
// check progress
ta := ts.Tasks()
- task := ta[0]
+ task := ta[1]
_, cur, total := task.Progress()
c.Assert(cur, Equals, s.fakeStore.fakeCurrentProgress)
c.Assert(total, Equals, s.fakeStore.fakeTotalProgress)
@@ -1565,7 +1620,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
expected := fakeOps{
@@ -1575,7 +1630,6 @@
Channel: "some-channel",
SnapID: "services-snap-id",
Revision: snap.R(7),
- Epoch: "0",
},
revno: snap.R(11),
},
@@ -1666,7 +1720,7 @@
c.Assert(s.fakeBackend.ops, DeepEquals, expected)
// check progress
- task := ts.Tasks()[0]
+ task := ts.Tasks()[1]
_, cur, total := task.Progress()
c.Assert(cur, Equals, s.fakeStore.fakeCurrentProgress)
c.Assert(total, Equals, s.fakeStore.fakeTotalProgress)
@@ -1693,7 +1747,7 @@
})
// check post-refresh hook
- task = ts.Tasks()[11]
+ task = ts.Tasks()[12]
c.Assert(task.Kind(), Equals, "run-hook")
c.Assert(task.Summary(), Matches, `Run post-refresh hook of "services-snap" snap if present`)
@@ -1744,7 +1798,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
expected := fakeOps{
@@ -1754,7 +1808,6 @@
Channel: "some-channel",
SnapID: "some-snap-id",
Revision: snap.R(7),
- Epoch: "",
},
revno: snap.R(11),
},
@@ -1904,7 +1957,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
expected := fakeOps{
@@ -1914,7 +1967,6 @@
Channel: "some-channel",
SnapID: "some-snap-id",
Revision: snap.R(7),
- Epoch: "",
},
revno: snap.R(11),
},
@@ -2107,7 +2159,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
expected := fakeOps{
@@ -2120,7 +2172,6 @@
Channel: "channel-for-7",
SnapID: "some-snap-id",
Revision: snap.R(7),
- Epoch: "",
},
},
}
@@ -2177,10 +2228,11 @@
})
validateErr := errors.New("refresh control error")
- validateRefreshes := func(st *state.State, refreshes []*snap.Info, userID int) ([]*snap.Info, error) {
+ validateRefreshes := func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int) ([]*snap.Info, error) {
c.Check(refreshes, HasLen, 1)
c.Check(refreshes[0].SnapID, Equals, "some-snap-id")
c.Check(refreshes[0].Revision, Equals, snap.R(11))
+ c.Check(ignoreValidation, HasLen, 0)
return nil, validateErr
}
// hook it up
@@ -2208,7 +2260,7 @@
})
validateErr := errors.New("refresh control error")
- validateRefreshes := func(st *state.State, refreshes []*snap.Info, userID int) ([]*snap.Info, error) {
+ validateRefreshes := func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int) ([]*snap.Info, error) {
return nil, validateErr
}
// hook it up
@@ -2224,6 +2276,140 @@
c.Check(snapsup.Flags, DeepEquals, flags.ForSnapSetup())
}
+func (s *snapmgrTestSuite) TestUpdateIgnoreValidationSticky(c *C) {
+ si := snap.SideInfo{
+ RealName: "some-snap",
+ SnapID: "some-snap-id",
+ Revision: snap.R(7),
+ }
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{&si},
+ Current: si.Revision,
+ SnapType: "app",
+ })
+
+ validateErr := errors.New("refresh control error")
+ validateRefreshesFail := func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int) ([]*snap.Info, error) {
+ c.Check(refreshes, HasLen, 1)
+ if len(ignoreValidation) == 0 {
+ return nil, validateErr
+ }
+ c.Check(ignoreValidation, DeepEquals, map[string]bool{
+ "some-snap-id": true,
+ })
+ return refreshes, nil
+ }
+ // hook it up
+ snapstate.ValidateRefreshes = validateRefreshesFail
+
+ flags := snapstate.Flags{IgnoreValidation: true}
+ ts, err := snapstate.Update(s.state, "some-snap", "stable", snap.R(0), s.user.ID, flags)
+ c.Assert(err, IsNil)
+
+ c.Check(s.fakeBackend.ops[0], DeepEquals, fakeOp{
+ op: "storesvc-list-refresh",
+ revno: snap.R(11),
+ cand: store.RefreshCandidate{
+ SnapID: "some-snap-id",
+ Revision: snap.R(7),
+ Channel: "stable",
+ IgnoreValidation: true,
+ },
+ })
+
+ chg := s.state.NewChange("refresh", "refresh snap")
+ chg.AddAll(ts)
+
+ s.state.Unlock()
+ defer s.snapmgr.Stop()
+ s.settle(c)
+ s.state.Lock()
+
+ // verify snap has IgnoreValidation set
+ var snapst snapstate.SnapState
+ err = snapstate.Get(s.state, "some-snap", &snapst)
+ c.Assert(err, IsNil)
+ c.Check(snapst.IgnoreValidation, Equals, true)
+ c.Check(snapst.Current, Equals, snap.R(11))
+
+ s.fakeBackend.ops = nil
+ s.fakeStore.refreshRevnos = map[string]snap.Revision{
+ "some-snap-id": snap.R(12),
+ }
+ _, tts, err := snapstate.UpdateMany(s.state, []string{"some-snap"}, s.user.ID)
+ c.Assert(err, IsNil)
+ c.Check(tts, HasLen, 1)
+
+ c.Check(s.fakeBackend.ops[0], DeepEquals, fakeOp{
+ op: "storesvc-list-refresh",
+ revno: snap.R(12),
+ cand: store.RefreshCandidate{
+ SnapID: "some-snap-id",
+ Revision: snap.R(11),
+ Channel: "stable",
+ IgnoreValidation: true,
+ },
+ })
+
+ chg = s.state.NewChange("refresh", "refresh snaps")
+ chg.AddAll(tts[0])
+
+ s.state.Unlock()
+ defer s.snapmgr.Stop()
+ s.settle(c)
+ s.state.Lock()
+
+ snapst = snapstate.SnapState{}
+ err = snapstate.Get(s.state, "some-snap", &snapst)
+ c.Assert(err, IsNil)
+ c.Check(snapst.IgnoreValidation, Equals, true)
+ c.Check(snapst.Current, Equals, snap.R(12))
+
+ // reset ignore validation
+ s.fakeBackend.ops = nil
+ s.fakeStore.refreshRevnos = map[string]snap.Revision{
+ "some-snap-id": snap.R(11),
+ }
+ validateRefreshes := func(st *state.State, refreshes []*snap.Info, ignoreValidation map[string]bool, userID int) ([]*snap.Info, error) {
+ return refreshes, nil
+ }
+ // hook it up
+ snapstate.ValidateRefreshes = validateRefreshes
+ flags = snapstate.Flags{}
+ ts, err = snapstate.Update(s.state, "some-snap", "stable", snap.R(0), s.user.ID, flags)
+ c.Assert(err, IsNil)
+
+ c.Check(s.fakeBackend.ops[0], DeepEquals, fakeOp{
+ op: "storesvc-list-refresh",
+ revno: snap.R(11),
+ cand: store.RefreshCandidate{
+ SnapID: "some-snap-id",
+ Revision: snap.R(12),
+ Channel: "stable",
+ IgnoreValidation: false,
+ },
+ })
+
+ chg = s.state.NewChange("refresh", "refresh snap")
+ chg.AddAll(ts)
+
+ s.state.Unlock()
+ defer s.snapmgr.Stop()
+ s.settle(c)
+ s.state.Lock()
+
+ snapst = snapstate.SnapState{}
+ err = snapstate.Get(s.state, "some-snap", &snapst)
+ c.Assert(err, IsNil)
+ c.Check(snapst.IgnoreValidation, Equals, false)
+ c.Check(snapst.Current, Equals, snap.R(11))
+}
+
func (s *snapmgrTestSuite) TestSingleUpdateBlockedRevision(c *C) {
// single updates should *not* set the block list
si7 := snap.SideInfo{
@@ -2244,6 +2430,7 @@
Active: true,
Sequence: []*snap.SideInfo{&si7, &si11},
Current: si7.Revision,
+ SnapType: "app",
})
_, err := snapstate.Update(s.state, "some-snap", "some-channel", snap.R(0), s.user.ID, snapstate.Flags{})
@@ -2256,7 +2443,6 @@
cand: store.RefreshCandidate{
SnapID: "some-snap-id",
Revision: snap.R(7),
- Epoch: "",
Channel: "some-channel",
},
})
@@ -2283,6 +2469,7 @@
Active: true,
Sequence: []*snap.SideInfo{&si7, &si11},
Current: si7.Revision,
+ SnapType: "app",
})
updates, _, err := snapstate.UpdateMany(s.state, []string{"some-snap"}, s.user.ID)
@@ -2462,7 +2649,7 @@
j++
expectedUpdatesSet["some-snap"] = true
first := updateTs.Tasks()[0]
- c.Check(first.Kind(), Equals, "download-snap")
+ c.Check(first.Kind(), Equals, "prerequisites")
wait := false
if expectedPruned["other-snap"]["aliasA"] {
wait = true
@@ -2603,8 +2790,8 @@
}
if scenario.update {
first := tasks[j]
- j += 15
- c.Check(first.Kind(), Equals, "download-snap")
+ j += 16
+ c.Check(first.Kind(), Equals, "prerequisites")
wait := false
if expectedPruned["other-snap"]["aliasA"] {
wait = true
@@ -2703,7 +2890,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
expected := fakeOps{
@@ -2759,7 +2946,7 @@
// verify snapSetup info
var snapsup snapstate.SnapSetup
- task := ts.Tasks()[0]
+ task := ts.Tasks()[1]
err = task.Get("snap-setup", &snapsup)
c.Assert(err, IsNil)
c.Assert(snapsup, DeepEquals, snapstate.SnapSetup{
@@ -2797,7 +2984,8 @@
Sequence: []*snap.SideInfo{
{RealName: "mock", Revision: snap.R(-2)},
},
- Current: snap.R(-2),
+ Current: snap.R(-2),
+ SnapType: "app",
})
mockSnap := makeTestSnap(c, `name: mock
@@ -2809,7 +2997,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
ops := s.fakeBackend.ops
@@ -2850,7 +3038,7 @@
// verify snapSetup info
var snapsup snapstate.SnapSetup
- task := ts.Tasks()[0]
+ task := ts.Tasks()[1]
err = task.Get("snap-setup", &snapsup)
c.Assert(err, IsNil)
c.Assert(snapsup, DeepEquals, snapstate.SnapSetup{
@@ -2889,7 +3077,8 @@
Sequence: []*snap.SideInfo{
{RealName: "mock", Revision: snap.R(100001)},
},
- Current: snap.R(100001),
+ Current: snap.R(100001),
+ SnapType: "app",
})
mockSnap := makeTestSnap(c, `name: mock
@@ -2901,7 +3090,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
expected := fakeOps{
@@ -2999,7 +3188,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
// ensure only local install was run, i.e. first actions are pseudo-action current
@@ -3065,7 +3254,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
expected := fakeOps{
@@ -3175,7 +3364,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
expected := fakeOps{
@@ -3309,7 +3498,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
c.Check(len(s.fakeBackend.ops), Equals, 2)
@@ -3374,7 +3563,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
c.Check(len(s.fakeBackend.ops), Equals, 5)
@@ -3581,7 +3770,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
// verify snaps in the system state
@@ -3634,7 +3823,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
// verify snaps in the system state
@@ -3675,7 +3864,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
cfgs = nil
@@ -3722,7 +3911,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
// config snapshot of rev. 2 has been made by 'revert'
@@ -3763,7 +3952,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
// ensure garbage collection runs as the last tasks
@@ -3925,7 +4114,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
expected := fakeOps{
@@ -4009,7 +4198,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
c.Assert(s.fakeBackend.ops.Ops(), HasLen, 6)
@@ -4053,7 +4242,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
expected := fakeOps{
@@ -4134,7 +4323,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
expected := fakeOps{
@@ -4230,7 +4419,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
expected := fakeOps{
@@ -4347,7 +4536,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
expected := fakeOps{
@@ -4409,7 +4598,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
expected := fakeOps{
@@ -4463,7 +4652,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
// switch is not really really doing anything backend related
@@ -4514,7 +4703,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
expected := fakeOps{
@@ -4607,7 +4796,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
// verify we generated a failure report
@@ -4619,8 +4808,9 @@
"Revision": "11",
})
c.Check(errMsg, Matches, `(?sm)change "install": "install a snap"
-download-snap: Undoing
+prerequisites: Done
snap-setup: "some-snap" \(11\) "some-channel"
+download-snap: Undoing
validate-snap: Done
.*
link-snap: Error
@@ -4633,8 +4823,9 @@
cleanup: Hold
run-hook: Hold`)
c.Check(errSig, Matches, `(?sm)snap-install:
-download-snap: Undoing
+prerequisites: Done
snap-setup: "some-snap"
+download-snap: Undoing
validate-snap: Done
.*
link-snap: Error
@@ -4655,7 +4846,7 @@
chg.AddAll(ts)
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
// verify that we excluded this field from the bugreport
c.Check(n, Equals, 2)
@@ -4687,11 +4878,8 @@
defer s.state.Unlock()
snapstate.CanAutoRefresh = func(*state.State) (bool, error) { return true, nil }
- logbuf := bytes.NewBuffer(nil)
- l, err := logger.New(logbuf, logger.DefaultFlags)
- c.Assert(err, IsNil)
- logger.SetLogger(l)
- defer logger.SetLogger(logger.NullLogger)
+ logbuf, restore := logger.MockLogger()
+ defer restore()
s.state.Set("last-refresh", time.Date(2009, 8, 13, 8, 0, 5, 0, time.UTC))
tr := config.NewTransaction(s.state)
@@ -4861,7 +5049,7 @@
// run the changes
s.state.Unlock()
- s.settle()
+ s.settle(c)
s.state.Lock()
s.verifyRefreshLast(c)
@@ -4936,7 +5124,8 @@
}
type snapmgrQuerySuite struct {
- st *state.State
+ st *state.State
+ restore func()
}
var _ = Suite(&snapmgrQuerySuite{})
@@ -4946,6 +5135,11 @@
st.Lock()
defer st.Unlock()
+ restoreSanitize := snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})
+ s.restore = func() {
+ restoreSanitize()
+ }
+
s.st = st
dirs.SetRootDir(c.MkDir())
@@ -4980,6 +5174,7 @@
func (s *snapmgrQuerySuite) TearDownTest(c *C) {
dirs.SetRootDir("")
+ s.restore()
}
func (s *snapmgrQuerySuite) TestInfo(c *C) {
@@ -5276,7 +5471,7 @@
}
func (s *snapmgrTestSuite) TestTrySetsTryModeClassic(c *C) {
if !dirs.SupportsClassicConfinement() {
- return
+ c.Skip("no support for classic")
}
s.testTrySetsTryMode(snapstate.Flags{Classic: true}, c)
}
@@ -5299,7 +5494,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
// verify snap is in TryMode
@@ -5312,6 +5507,7 @@
c.Check(s.state.TaskCount(), Equals, len(ts.Tasks()))
c.Check(taskKinds(ts.Tasks()), DeepEquals, []string{
+ "prerequisites",
"prepare-snap",
"mount-snap",
"copy-snap-data",
@@ -5329,7 +5525,7 @@
func (s *snapmgrTestSuite) TestTryUndoRemovesTryFlag(c *C) {
if !dirs.SupportsClassicConfinement() {
- return
+ c.Skip("no support for classic")
}
s.testTrySetsTryMode(snapstate.Flags{}, c)
}
@@ -5342,7 +5538,7 @@
}
func (s *snapmgrTestSuite) TestTryUndoRemovesTryFlagLeavesClassic(c *C) {
if !dirs.SupportsClassicConfinement() {
- return
+ c.Skip("no support for classic")
}
s.testTrySetsTryMode(snapstate.Flags{Classic: true}, c)
}
@@ -5376,7 +5572,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
// verify snap is not in try mode, the state got undone
@@ -5584,7 +5780,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
var snapst snapstate.SnapState
@@ -5888,7 +6084,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
expected := fakeOps{
{
@@ -6135,10 +6331,23 @@
c.Assert(hookstate.HookTask(s.state, "", hooksup, contextData), NotNil)
}
+func makeInstalledMockCoreSnap(c *C) {
+ coreSnapYaml := `name: core
+version: 1.0
+type: os
+`
+ snaptest.MockSnap(c, coreSnapYaml, "", &snap.SideInfo{
+ RealName: "core",
+ Revision: snap.R(1),
+ })
+}
+
func (s *snapmgrTestSuite) TestGadgetDefaults(c *C) {
r := release.MockOnClassic(false)
defer r()
+ makeInstalledMockCoreSnap(c)
+
// using MockSnap, we want to read the bits on disk
snapstate.MockReadInfo(snap.ReadInfo)
@@ -6167,6 +6376,8 @@
r := release.MockOnClassic(false)
defer r()
+ makeInstalledMockCoreSnap(c)
+
// using MockSnap, we want to read the bits on disk
snapstate.MockReadInfo(snap.ReadInfo)
@@ -6188,6 +6399,8 @@
}
func (s *snapmgrTestSuite) TestGadgetDefaultsInstalled(c *C) {
+ makeInstalledMockCoreSnap(c)
+
// using MockSnap, we want to read the bits on disk
snapstate.MockReadInfo(snap.ReadInfo)
@@ -6251,6 +6464,7 @@
s.state.Lock()
defer s.state.Unlock()
+ snapstate.Set(s.state, "core", nil)
snapstate.Set(s.state, "ubuntu-core", &snapstate.SnapState{
Active: true,
Sequence: []*snap.SideInfo{{RealName: "ubuntu-core", SnapID: "ubuntu-core-snap-id", Revision: snap.R(1)}},
@@ -6301,6 +6515,7 @@
s.state.Lock()
defer s.state.Unlock()
+ snapstate.Set(s.state, "core", nil)
snapstate.Set(s.state, "ubuntu-core", &snapstate.SnapState{
Active: true,
Sequence: []*snap.SideInfo{{RealName: "ubuntu-core", SnapID: "ubuntu-core-snap-id", Revision: snap.R(1)}},
@@ -6317,7 +6532,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
// ensure all our tasks ran
@@ -6465,7 +6680,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
// ensure all our tasks ran
@@ -6536,7 +6751,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
c.Check(s.state.Changes(), HasLen, 1)
@@ -6559,7 +6774,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
c.Check(s.state.Changes(), HasLen, 0)
@@ -6569,7 +6784,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
c.Check(s.state.Changes(), HasLen, 1)
@@ -6593,7 +6808,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
c.Check(s.state.Changes(), HasLen, 1)
@@ -6657,7 +6872,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
var snapst2 snapstate.SnapState
@@ -6676,7 +6891,7 @@
c.Assert(release.ReleaseInfo.ForceDevMode(), Equals, true)
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
defer s.state.Unlock()
@@ -6711,7 +6926,7 @@
s.state.Unlock()
defer s.snapmgr.Stop()
- s.settle()
+ s.settle(c)
s.state.Lock()
var snapst2 snapstate.SnapState
@@ -6741,6 +6956,7 @@
return nil, nil
}
+ snapstate.Set(s.state, "core", nil)
snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
Sequence: []*snap.SideInfo{
{RealName: "alias-snap", Revision: snap.R(11)},
@@ -6808,6 +7024,7 @@
return nil, nil
}
+ snapstate.Set(s.state, "core", nil)
snapstate.Set(s.state, "alias-snap", &snapstate.SnapState{
Sequence: []*snap.SideInfo{
{RealName: "alias-snap", Revision: snap.R(11)},
@@ -6918,6 +7135,446 @@
}
}
+func (s *snapmgrTestSuite) TestInstallWithoutCoreRunThrough1(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ // pretend we don't have core
+ snapstate.Set(s.state, "core", nil)
+
+ chg := s.state.NewChange("install", "install a snap on a system without core")
+ ts, err := snapstate.Install(s.state, "some-snap", "some-channel", snap.R(42), s.user.ID, snapstate.Flags{})
+ c.Assert(err, IsNil)
+ chg.AddAll(ts)
+
+ s.state.Unlock()
+ defer s.snapmgr.Stop()
+ s.settle(c)
+ s.state.Lock()
+
+ // ensure all our tasks ran
+ c.Assert(chg.Err(), IsNil)
+ c.Assert(chg.IsReady(), Equals, true)
+ c.Check(s.fakeStore.downloads, DeepEquals, []fakeDownload{
+ {
+ macaroon: s.user.StoreMacaroon,
+ name: "core",
+ },
+ {
+ macaroon: s.user.StoreMacaroon,
+ name: "some-snap",
+ }})
+ expected := fakeOps{
+ // we check the snap
+ {
+ op: "storesvc-snap",
+ name: "some-snap",
+ revno: snap.R(42),
+ },
+ // then we check core because its not installed already
+ // and continue with that
+ {
+ op: "storesvc-snap",
+ name: "core",
+ revno: snap.R(11),
+ },
+ {
+ op: "storesvc-download",
+ name: "core",
+ },
+ {
+ op: "validate-snap:Doing",
+ name: "core",
+ revno: snap.R(11),
+ },
+ {
+ op: "current",
+ old: "",
+ },
+ {
+ op: "open-snap-file",
+ name: filepath.Join(dirs.SnapBlobDir, "core_11.snap"),
+ sinfo: snap.SideInfo{
+ RealName: "core",
+ Channel: "stable",
+ SnapID: "snapIDsnapidsnapidsnapidsnapidsn",
+ Revision: snap.R(11),
+ },
+ },
+ {
+ op: "setup-snap",
+ name: filepath.Join(dirs.SnapBlobDir, "core_11.snap"),
+ revno: snap.R(11),
+ },
+ {
+ op: "copy-data",
+ name: filepath.Join(dirs.SnapMountDir, "core/11"),
+ old: "",
+ },
+ {
+ op: "setup-profiles:Doing",
+ name: "core",
+ revno: snap.R(11),
+ },
+ {
+ op: "candidate",
+ sinfo: snap.SideInfo{
+ RealName: "core",
+ Channel: "stable",
+ SnapID: "snapIDsnapidsnapidsnapidsnapidsn",
+ Revision: snap.R(11),
+ },
+ },
+ {
+ op: "link-snap",
+ name: filepath.Join(dirs.SnapMountDir, "core/11"),
+ },
+ {
+ op: "update-aliases",
+ },
+ // after core is in place continue with the snap
+ {
+ op: "storesvc-download",
+ name: "some-snap",
+ },
+ {
+ op: "validate-snap:Doing",
+ name: "some-snap",
+ revno: snap.R(42),
+ },
+ {
+ op: "current",
+ old: "",
+ },
+ {
+ op: "open-snap-file",
+ name: filepath.Join(dirs.SnapBlobDir, "some-snap_42.snap"),
+ sinfo: snap.SideInfo{
+ RealName: "some-snap",
+ Channel: "some-channel",
+ SnapID: "snapIDsnapidsnapidsnapidsnapidsn",
+ Revision: snap.R(42),
+ },
+ },
+ {
+ op: "setup-snap",
+ name: filepath.Join(dirs.SnapBlobDir, "some-snap_42.snap"),
+ revno: snap.R(42),
+ },
+ {
+ op: "copy-data",
+ name: filepath.Join(dirs.SnapMountDir, "some-snap/42"),
+ old: "",
+ },
+ {
+ op: "setup-profiles:Doing",
+ name: "some-snap",
+ revno: snap.R(42),
+ },
+ {
+ op: "candidate",
+ sinfo: snap.SideInfo{
+ RealName: "some-snap",
+ Channel: "some-channel",
+ SnapID: "snapIDsnapidsnapidsnapidsnapidsn",
+ Revision: snap.R(42),
+ },
+ },
+ {
+ op: "link-snap",
+ name: filepath.Join(dirs.SnapMountDir, "some-snap/42"),
+ },
+ {
+ op: "update-aliases",
+ },
+ // cleanups order is random
+ {
+ op: "cleanup-trash",
+ name: "core",
+ revno: snap.R(11),
+ },
+ {
+ op: "cleanup-trash",
+ name: "some-snap",
+ revno: snap.R(42),
+ },
+ }
+ // start with an easier-to-read error if this fails:
+ c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
+ // compare the details without the cleanup tasks, the order is random
+ // as they run in parallel
+ opsLenWithoutCleanups := len(s.fakeBackend.ops) - 2
+ c.Assert(s.fakeBackend.ops[:opsLenWithoutCleanups], DeepEquals, expected[:opsLenWithoutCleanups])
+
+ // verify core in the system state
+ var snaps map[string]*snapstate.SnapState
+ err = s.state.Get("snaps", &snaps)
+ c.Assert(err, IsNil)
+
+ snapst := snaps["core"]
+ c.Assert(snapst.Active, Equals, true)
+ c.Assert(snapst.Channel, Equals, "stable")
+ c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{
+ RealName: "core",
+ Channel: "stable",
+ SnapID: "snapIDsnapidsnapidsnapidsnapidsn",
+ Revision: snap.R(11),
+ })
+}
+
+func (s *snapmgrTestSuite) TestInstallWithoutCoreTwoSnapsRunThrough(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ restore := snapstate.MockPrerequisitesRetryTimeout(10 * time.Millisecond)
+ defer restore()
+
+ // pretend we don't have core
+ snapstate.Set(s.state, "core", nil)
+
+ chg1 := s.state.NewChange("install", "install snap 1")
+ ts1, err := snapstate.Install(s.state, "snap1", "some-channel", snap.R(42), s.user.ID, snapstate.Flags{})
+ c.Assert(err, IsNil)
+ chg1.AddAll(ts1)
+
+ chg2 := s.state.NewChange("install", "install snap 2")
+ ts2, err := snapstate.Install(s.state, "snap2", "some-other-channel", snap.R(21), s.user.ID, snapstate.Flags{})
+ c.Assert(err, IsNil)
+ chg2.AddAll(ts2)
+
+ s.state.Unlock()
+ defer s.snapmgr.Stop()
+ s.settle(c)
+ s.state.Lock()
+
+ // ensure all our tasks ran and core was only installed once
+ c.Assert(chg1.Err(), IsNil)
+ c.Assert(chg2.Err(), IsNil)
+
+ c.Assert(chg1.IsReady(), Equals, true)
+ c.Assert(chg2.IsReady(), Equals, true)
+
+ // order in which the changes run is random
+ len1 := len(chg1.Tasks())
+ len2 := len(chg2.Tasks())
+ if len1 > len2 {
+ c.Assert(chg1.Tasks(), HasLen, 24)
+ c.Assert(chg2.Tasks(), HasLen, 12)
+ } else {
+ c.Assert(chg1.Tasks(), HasLen, 12)
+ c.Assert(chg2.Tasks(), HasLen, 24)
+ }
+
+ // FIXME: add helpers and do a DeepEquals here for the operations
+}
+
+func (s *snapmgrTestSuite) TestInstallWithoutCoreTwoSnapsWithFailureRunThrough(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ // slightly longer retry timeout to avoid deadlock when we
+ // trigger a retry quickly that the link snap for core does
+ // not have a chance to run
+ restore := snapstate.MockPrerequisitesRetryTimeout(40 * time.Millisecond)
+ defer restore()
+
+ defer s.snapmgr.Stop()
+ // Two changes are created, the first will fails, the second will
+ // be fine. The order of what change runs first is random, the
+ // first change will also install core in its own lane. This test
+ // ensures that core gets installed and there are no conflicts
+ // even if core already got installed from the first change.
+ //
+ // It runs multiple times so that both possible cases get a chance
+ // to run
+ for i := 0; i < 5; i++ {
+ // start clean
+ snapstate.Set(s.state, "core", nil)
+ snapstate.Set(s.state, "snap2", nil)
+
+ // chg1 has an error
+ chg1 := s.state.NewChange("install", "install snap 1")
+ ts1, err := snapstate.Install(s.state, "snap1", "some-channel", snap.R(42), s.user.ID, snapstate.Flags{})
+ c.Assert(err, IsNil)
+ chg1.AddAll(ts1)
+
+ tasks := ts1.Tasks()
+ last := tasks[len(tasks)-1]
+ terr := s.state.NewTask("error-trigger", "provoking total undo")
+ terr.WaitFor(last)
+ chg1.AddTask(terr)
+
+ // chg2 is good
+ chg2 := s.state.NewChange("install", "install snap 2")
+ ts2, err := snapstate.Install(s.state, "snap2", "some-other-channel", snap.R(21), s.user.ID, snapstate.Flags{})
+ c.Assert(err, IsNil)
+ chg2.AddAll(ts2)
+
+ // we use our own settle as we need a bigger timeout
+ s.state.Unlock()
+ err = s.o.Settle(15 * time.Second)
+ s.state.Lock()
+ c.Assert(err, IsNil)
+
+ // ensure expected change states
+ c.Check(chg1.Status(), Equals, state.ErrorStatus)
+ c.Check(chg2.Status(), Equals, state.DoneStatus)
+
+ // ensure we have both core and snap2
+ var snapst snapstate.SnapState
+
+ err = snapstate.Get(s.state, "core", &snapst)
+ c.Assert(err, IsNil)
+ c.Assert(snapst.Active, Equals, true)
+ c.Assert(snapst.Sequence, HasLen, 1)
+ c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{
+ RealName: "core",
+ SnapID: "snapIDsnapidsnapidsnapidsnapidsn",
+ Channel: "stable",
+ Revision: snap.R(11),
+ })
+
+ err = snapstate.Get(s.state, "snap2", &snapst)
+ c.Assert(err, IsNil)
+ c.Assert(snapst.Active, Equals, true)
+ c.Assert(snapst.Sequence, HasLen, 1)
+ c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{
+ RealName: "snap2",
+ SnapID: "snapIDsnapidsnapidsnapidsnapidsn",
+ Channel: "some-other-channel",
+ Revision: snap.R(21),
+ })
+
+ }
+}
+
+type behindYourBackStore struct {
+ *fakeStore
+ state *state.State
+
+ coreInstallRequested bool
+ coreInstalled bool
+ chg *state.Change
+}
+
+func (s behindYourBackStore) SnapInfo(spec store.SnapSpec, user *auth.UserState) (*snap.Info, error) {
+ if spec.Name == "core" {
+ s.state.Lock()
+ if !s.coreInstallRequested {
+ s.coreInstallRequested = true
+ snapsup := &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{
+ RealName: "core",
+ },
+ }
+ t := s.state.NewTask("prepare", "prepare core")
+ t.Set("snap-setup", snapsup)
+ s.chg = s.state.NewChange("install", "install core")
+ s.chg.AddAll(state.NewTaskSet(t))
+ }
+ if s.chg != nil && !s.coreInstalled {
+ // marks change ready but also
+ // tasks need to also be marked cleaned
+ for _, t := range s.chg.Tasks() {
+ t.SetStatus(state.DoneStatus)
+ t.SetClean()
+ }
+ snapstate.Set(s.state, "core", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{
+ {RealName: "core", Revision: snap.R(1)},
+ },
+ Current: snap.R(1),
+ SnapType: "os",
+ })
+ s.coreInstalled = true
+ }
+ s.state.Unlock()
+ }
+
+ return s.fakeStore.SnapInfo(spec, user)
+}
+
+// this test the scenario that some-snap gets installed and during the
+// install (when unlocking for the store info call for core) an
+// explicit "snap install core" happens. In this case the snapstate
+// will return a change conflict. we handle this via a retry, ensure
+// this is actually what happens.
+func (s *snapmgrTestSuite) TestInstallWithoutCoreConflictingInstall(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ restore := snapstate.MockPrerequisitesRetryTimeout(10 * time.Millisecond)
+ defer restore()
+
+ storestate.ReplaceStore(s.state, behindYourBackStore{fakeStore: s.fakeStore, state: s.state})
+
+ // pretend we don't have core
+ snapstate.Set(s.state, "core", nil)
+
+ // now install a snap that will pull in core
+ chg := s.state.NewChange("install", "install a snap on a system without core")
+ ts, err := snapstate.Install(s.state, "some-snap", "some-channel", snap.R(42), s.user.ID, snapstate.Flags{})
+ c.Assert(err, IsNil)
+ chg.AddAll(ts)
+
+ prereq := ts.Tasks()[0]
+ c.Assert(prereq.Kind(), Equals, "prerequisites")
+ c.Check(prereq.AtTime().IsZero(), Equals, true)
+
+ s.state.Unlock()
+ defer s.snapmgr.Stop()
+
+ // start running the change, this will trigger the
+ // prerequisites task, which will trigger the install of core
+ // and also call our mock store which will generate a parallel
+ // change
+ s.snapmgr.Ensure()
+ s.snapmgr.Wait()
+
+ // change is not ready yet, because the prerequists triggered
+ // a state.Retry{} because of the conflicting change
+ c.Assert(chg.IsReady(), Equals, false)
+ s.state.Lock()
+ // marked for retry
+ c.Check(prereq.AtTime().IsZero(), Equals, false)
+ c.Check(prereq.Status().Ready(), Equals, false)
+ s.state.Unlock()
+
+ // retry interval is 10ms so 20ms should be plenty of time
+ time.Sleep(20 * time.Millisecond)
+ s.settle(c)
+ // chg got retried, core is now installed, things are good
+ c.Assert(chg.IsReady(), Equals, true)
+
+ s.state.Lock()
+
+ // ensure all our tasks ran
+ c.Assert(chg.Err(), IsNil)
+ c.Assert(chg.IsReady(), Equals, true)
+
+ // verify core in the system state
+ var snaps map[string]*snapstate.SnapState
+ err = s.state.Get("snaps", &snaps)
+ c.Assert(err, IsNil)
+
+ snapst := snaps["core"]
+ c.Assert(snapst.Active, Equals, true)
+ c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{
+ RealName: "core",
+ Revision: snap.R(1),
+ })
+
+ snapst = snaps["some-snap"]
+ c.Assert(snapst.Active, Equals, true)
+ c.Assert(snapst.Sequence[0], DeepEquals, &snap.SideInfo{
+ RealName: "some-snap",
+ SnapID: "snapIDsnapidsnapidsnapidsnapidsn",
+ Channel: "some-channel",
+ Revision: snap.R(42),
+ })
+}
+
type canDisableSuite struct{}
var _ = Suite(&canDisableSuite{})
diff -Nru snapd-2.28.5/overlord/snapstate/storehelpers.go snapd-2.29.3/overlord/snapstate/storehelpers.go
--- snapd-2.28.5/overlord/snapstate/storehelpers.go 2017-08-24 06:46:40.000000000 +0000
+++ snapd-2.29.3/overlord/snapstate/storehelpers.go 2017-11-02 15:44:20.000000000 +0000
@@ -22,6 +22,7 @@
import (
"github.com/snapcore/snapd/overlord/auth"
"github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/overlord/storestate"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/store"
)
@@ -33,7 +34,7 @@
return auth.User(st, userID)
}
-func updateInfo(st *state.State, snapst *SnapState, channel string, userID int) (*snap.Info, error) {
+func updateInfo(st *state.State, snapst *SnapState, channel string, ignoreValidation bool, userID int) (*snap.Info, error) {
user, err := userFromUserID(st, userID)
if err != nil {
return nil, err
@@ -49,13 +50,14 @@
refreshCand := &store.RefreshCandidate{
// the desired channel
- Channel: channel,
- SnapID: curInfo.SnapID,
- Revision: curInfo.Revision,
- Epoch: curInfo.Epoch,
+ Channel: channel,
+ SnapID: curInfo.SnapID,
+ Revision: curInfo.Revision,
+ Epoch: curInfo.Epoch,
+ IgnoreValidation: ignoreValidation,
}
- theStore := Store(st)
+ theStore := storestate.Store(st)
st.Unlock() // calls to the store should be done without holding the state lock
res, err := theStore.LookupRefresh(refreshCand, user)
st.Lock()
@@ -67,7 +69,7 @@
if err != nil {
return nil, err
}
- theStore := Store(st)
+ theStore := storestate.Store(st)
st.Unlock() // calls to the store should be done without holding the state lock
spec := store.SnapSpec{
Name: name,
diff -Nru snapd-2.28.5/overlord/storestate/export_test.go snapd-2.29.3/overlord/storestate/export_test.go
--- snapd-2.28.5/overlord/storestate/export_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/overlord/storestate/export_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -0,0 +1,35 @@
+// -*- 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 storestate
+
+import (
+ "github.com/snapcore/snapd/overlord/auth"
+ "github.com/snapcore/snapd/store"
+)
+
+// MockStoreNew mocks store.New as called by storestate.SetupStore.
+func MockStoreNew(new func(*store.Config, auth.AuthContext) *store.Store) func() {
+ storeNew = new
+ return func() {
+ storeNew = store.New
+ }
+}
+
+var CachedAuthContext = cachedAuthContext
diff -Nru snapd-2.28.5/overlord/storestate/storestate.go snapd-2.29.3/overlord/storestate/storestate.go
--- snapd-2.28.5/overlord/storestate/storestate.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/overlord/storestate/storestate.go 2017-10-23 06:17:27.000000000 +0000
@@ -0,0 +1,167 @@
+// -*- 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 storestate
+
+import (
+ "fmt"
+ "io"
+ "net/url"
+
+ "golang.org/x/net/context"
+
+ "github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/overlord/auth"
+ "github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/progress"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/store"
+)
+
+var storeNew = store.New
+
+// StoreState holds the state for the store in the system.
+type StoreState struct {
+ // BaseURL is the store API's base URL.
+ BaseURL string `json:"base-url"`
+}
+
+// BaseURL returns the store API's explicit base URL.
+func BaseURL(st *state.State) string {
+ var storeState StoreState
+
+ err := st.Get("store", &storeState)
+ if err != nil {
+ return ""
+ }
+
+ return storeState.BaseURL
+}
+
+// updateBaseURL updates the store API's base URL in persistent state.
+func updateBaseURL(st *state.State, baseURL string) {
+ var storeState StoreState
+ st.Get("store", &storeState)
+ storeState.BaseURL = baseURL
+ st.Set("store", &storeState)
+}
+
+// A StoreService can find, list available updates and download snaps.
+type StoreService interface {
+ SnapInfo(spec store.SnapSpec, user *auth.UserState) (*snap.Info, error)
+ Find(search *store.Search, user *auth.UserState) ([]*snap.Info, error)
+ LookupRefresh(*store.RefreshCandidate, *auth.UserState) (*snap.Info, error)
+ ListRefresh([]*store.RefreshCandidate, *auth.UserState) ([]*snap.Info, error)
+ Sections(user *auth.UserState) ([]string, error)
+ WriteCatalogs(names io.Writer) error
+ Download(context.Context, string, string, *snap.DownloadInfo, progress.Meter, *auth.UserState) error
+
+ Assertion(assertType *asserts.AssertionType, primaryKey []string, user *auth.UserState) (asserts.Assertion, error)
+
+ SuggestedCurrency() string
+ Buy(options *store.BuyOptions, user *auth.UserState) (*store.BuyResult, error)
+ ReadyToBuy(*auth.UserState) error
+}
+
+// SetupStore configures the system's initial store.
+func SetupStore(st *state.State, authContext auth.AuthContext) error {
+ storeConfig, err := initialStoreConfig(st)
+ if err != nil {
+ return err
+ }
+ sto := storeNew(storeConfig, authContext)
+ saveAuthContext(st, authContext)
+ ReplaceStore(st, sto)
+ return nil
+}
+
+// SetBaseURL reconfigures the base URL of the store API used by the system.
+// If the URL is nil the store is reverted to the system's default.
+func SetBaseURL(state *state.State, u *url.URL) error {
+ baseURL := ""
+ config := store.DefaultConfig()
+ if u != nil {
+ baseURL = u.String()
+ err := config.SetBaseURL(u)
+ if err != nil {
+ return err
+ }
+ }
+ store := store.New(config, cachedAuthContext(state))
+ ReplaceStore(state, store)
+ updateBaseURL(state, baseURL)
+ return nil
+}
+
+func initialStoreConfig(st *state.State) (*store.Config, error) {
+ config := store.DefaultConfig()
+ if baseURL := BaseURL(st); baseURL != "" {
+ u, err := url.Parse(baseURL)
+ if err != nil {
+ return nil, fmt.Errorf("invalid store API base URL: %s", err)
+ }
+ err = config.SetBaseURL(u)
+ if err != nil {
+ return nil, err
+ }
+ }
+ // cache downloads by default
+ config.CacheDownloads = 5
+ return config, nil
+}
+
+type cachedAuthContextKey struct{}
+
+func saveAuthContext(state *state.State, authContext auth.AuthContext) {
+ state.Cache(cachedAuthContextKey{}, authContext)
+}
+
+func cachedAuthContext(state *state.State) auth.AuthContext {
+ cached := state.Cached(cachedAuthContextKey{})
+ if cached != nil {
+ return cached.(auth.AuthContext)
+ }
+ panic("internal error: needing the auth context before managers have initialized it")
+}
+
+type cachedStoreKey struct{}
+
+// ReplaceStore replaces the store used by the system.
+func ReplaceStore(state *state.State, store StoreService) {
+ state.Cache(cachedStoreKey{}, store)
+}
+
+func cachedStore(st *state.State) StoreService {
+ ubuntuStore := st.Cached(cachedStoreKey{})
+ if ubuntuStore == nil {
+ return nil
+ }
+ return ubuntuStore.(StoreService)
+}
+
+// the store implementation has the interface consumed here
+var _ StoreService = (*store.Store)(nil)
+
+// Store returns the store service used by the system.
+func Store(st *state.State) StoreService {
+ if cachedStore := cachedStore(st); cachedStore != nil {
+ return cachedStore
+ }
+ panic("internal error: needing the store before managers have initialized it")
+}
diff -Nru snapd-2.28.5/overlord/storestate/storestate_test.go snapd-2.29.3/overlord/storestate/storestate_test.go
--- snapd-2.28.5/overlord/storestate/storestate_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/overlord/storestate/storestate_test.go 2017-10-23 06:17:27.000000000 +0000
@@ -0,0 +1,259 @@
+// -*- 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 storestate_test
+
+import (
+ "io/ioutil"
+ "net/url"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/snapcore/snapd/overlord/auth"
+ "github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/overlord/storestate"
+ "github.com/snapcore/snapd/store"
+
+ . "gopkg.in/check.v1"
+)
+
+func Test(t *testing.T) { TestingT(t) }
+
+type fakeAuthContext struct{}
+
+func (*fakeAuthContext) Device() (*auth.DeviceState, error) {
+ panic("fakeAuthContext Device is not implemented")
+}
+
+func (*fakeAuthContext) UpdateDeviceAuth(*auth.DeviceState, string) (*auth.DeviceState, error) {
+ panic("fakeAuthContext UpdateDeviceAuth is not implemented")
+}
+
+func (*fakeAuthContext) UpdateUserAuth(*auth.UserState, []string) (*auth.UserState, error) {
+ panic("fakeAuthContext UpdateUserAuth is not implemented")
+}
+
+func (*fakeAuthContext) StoreID(string) (string, error) {
+ panic("fakeAuthContext StoreID is not implemented")
+}
+
+func (*fakeAuthContext) DeviceSessionRequestParams(nonce string) (*auth.DeviceSessionRequestParams, error) {
+ panic("fakeAuthContext DeviceSessionRequestParams is not implemented")
+}
+
+type storeStateSuite struct{}
+
+var _ = Suite(&storeStateSuite{})
+
+func (ss *storeStateSuite) state(c *C, data string) *state.State {
+ if data == "" {
+ return state.New(nil)
+ }
+
+ tmpdir := c.MkDir()
+ state_json := filepath.Join(tmpdir, "state.json")
+ c.Assert(ioutil.WriteFile(state_json, []byte(data), 0600), IsNil)
+
+ r, err := os.Open(state_json)
+ c.Assert(err, IsNil)
+ defer r.Close()
+ st, err := state.ReadState(nil, r)
+ c.Assert(err, IsNil)
+
+ return st
+}
+
+func (ss *storeStateSuite) TestDefaultBaseURL(c *C) {
+ st := ss.state(c, "")
+ st.Lock()
+ baseURL := storestate.BaseURL(st)
+ st.Unlock()
+ c.Check(baseURL, Equals, "")
+}
+
+func (ss *storeStateSuite) TestExplicitBaseURL(c *C) {
+ st := ss.state(c, "")
+ st.Lock()
+ defer st.Unlock()
+
+ storeState := storestate.StoreState{BaseURL: "http://example.com/"}
+ st.Set("store", &storeState)
+ baseURL := storestate.BaseURL(st)
+ c.Check(baseURL, Equals, storeState.BaseURL)
+}
+
+func (ss *storeStateSuite) TestSetupStoreCachesState(c *C) {
+ st := ss.state(c, "")
+ st.Lock()
+ defer st.Unlock()
+
+ c.Check(func() { storestate.Store(st) }, PanicMatches,
+ "internal error: needing the store before managers have initialized it")
+ c.Check(func() { storestate.CachedAuthContext(st) }, PanicMatches,
+ "internal error: needing the auth context before managers have initialized it")
+
+ err := storestate.SetupStore(st, &fakeAuthContext{})
+ c.Assert(err, IsNil)
+
+ c.Check(storestate.Store(st), NotNil)
+ c.Check(storestate.CachedAuthContext(st), NotNil)
+}
+
+func (ss *storeStateSuite) TestSetupStoreDefaultBaseURL(c *C) {
+ var config *store.Config
+ defer storestate.MockStoreNew(func(c *store.Config, _ auth.AuthContext) *store.Store {
+ config = c
+ return nil
+ })()
+
+ st := ss.state(c, "")
+ st.Lock()
+ defer st.Unlock()
+
+ err := storestate.SetupStore(st, nil)
+ c.Assert(err, IsNil)
+
+ c.Check(config.StoreBaseURL.String(), Equals, "https://api.snapcraft.io/")
+}
+
+func (ss *storeStateSuite) TestSetupStoreBaseURLFromState(c *C) {
+ var config *store.Config
+ defer storestate.MockStoreNew(func(c *store.Config, _ auth.AuthContext) *store.Store {
+ config = c
+ return nil
+ })()
+
+ st := ss.state(c, `{"data":{"store":{"base-url": "http://example.com/"}}}`)
+ st.Lock()
+ defer st.Unlock()
+
+ err := storestate.SetupStore(st, nil)
+ c.Assert(err, IsNil)
+
+ c.Check(config.StoreBaseURL.String(), Equals, "http://example.com/")
+}
+
+func (ss *storeStateSuite) TestSetupStoreBadEnvironURLOverride(c *C) {
+ // We need store state to trigger this.
+ st := ss.state(c, `{"data":{"store":{"base-url": "http://example.com/"}}}`)
+ st.Lock()
+ defer st.Unlock()
+
+ c.Assert(os.Setenv("SNAPPY_FORCE_API_URL", "://force-api.local/"), IsNil)
+ defer os.Setenv("SNAPPY_FORCE_API_URL", "")
+
+ err := storestate.SetupStore(st, nil)
+ c.Assert(err, NotNil)
+ c.Check(err, ErrorMatches, "invalid SNAPPY_FORCE_API_URL: parse ://force-api.local/: missing protocol scheme")
+}
+
+func (ss *storeStateSuite) TestSetupStoreEmptyBaseURLFromState(c *C) {
+ var config *store.Config
+ defer storestate.MockStoreNew(func(c *store.Config, _ auth.AuthContext) *store.Store {
+ config = c
+ return nil
+ })()
+
+ st := ss.state(c, `{"data":{"store":{"base-url": ""}}}`)
+ st.Lock()
+ defer st.Unlock()
+
+ err := storestate.SetupStore(st, nil)
+ c.Assert(err, IsNil)
+
+ c.Check(config.StoreBaseURL.String(), Equals, "https://api.snapcraft.io/")
+}
+
+func (ss *storeStateSuite) TestSetupStoreInvalidBaseURLFromState(c *C) {
+ st := ss.state(c, `{"data":{"store":{"base-url": "://example.com/"}}}`)
+ st.Lock()
+ defer st.Unlock()
+
+ err := storestate.SetupStore(st, nil)
+ c.Assert(err, NotNil)
+ c.Check(err, ErrorMatches, "invalid store API base URL: parse ://example.com/: missing protocol scheme")
+}
+
+func (ss *storeStateSuite) TestStore(c *C) {
+ st := ss.state(c, "")
+ st.Lock()
+ defer st.Unlock()
+
+ sto := &store.Store{}
+ storestate.ReplaceStore(st, sto)
+ store1 := storestate.Store(st)
+ c.Check(store1, Equals, sto)
+
+ // cached
+ store2 := storestate.Store(st)
+ c.Check(store2, Equals, sto)
+}
+
+func (ss *storeStateSuite) TestSetBaseURL(c *C) {
+ st := ss.state(c, "")
+ st.Lock()
+ defer st.Unlock()
+
+ err := storestate.SetupStore(st, &fakeAuthContext{})
+ c.Assert(err, IsNil)
+
+ oldStore := storestate.Store(st)
+ c.Assert(storestate.BaseURL(st), Equals, "")
+
+ u, err := url.Parse("http://example.com/")
+ c.Assert(err, IsNil)
+ err = storestate.SetBaseURL(st, u)
+ c.Assert(err, IsNil)
+
+ c.Check(storestate.Store(st), Not(Equals), oldStore)
+ c.Check(storestate.BaseURL(st), Equals, "http://example.com/")
+}
+
+func (ss *storeStateSuite) TestSetBaseURLReset(c *C) {
+ st := ss.state(c, "")
+ st.Lock()
+ defer st.Unlock()
+
+ st.Set("store", map[string]interface{}{
+ "base-url": "http://example.com/",
+ })
+ c.Assert(storestate.BaseURL(st), Not(Equals), "")
+
+ err := storestate.SetupStore(st, &fakeAuthContext{})
+ c.Assert(err, IsNil)
+ oldStore := storestate.Store(st)
+
+ err = storestate.SetBaseURL(st, nil)
+ c.Assert(err, IsNil)
+
+ c.Check(storestate.Store(st), Not(Equals), oldStore)
+ c.Check(storestate.BaseURL(st), Equals, "")
+}
+
+func (ss *storeStateSuite) TestSetBaseURLBadEnvironURLOverride(c *C) {
+ c.Assert(os.Setenv("SNAPPY_FORCE_API_URL", "://force-api.local/"), IsNil)
+ defer os.Setenv("SNAPPY_FORCE_API_URL", "")
+
+ u, _ := url.Parse("http://example.com/")
+ st := ss.state(c, "")
+ err := storestate.SetBaseURL(st, u)
+ c.Assert(err, NotNil)
+ c.Check(err, ErrorMatches, "invalid SNAPPY_FORCE_API_URL: parse ://force-api.local/: missing protocol scheme")
+}
diff -Nru snapd-2.28.5/packaging/arch/PKGBUILD snapd-2.29.3/packaging/arch/PKGBUILD
--- snapd-2.28.5/packaging/arch/PKGBUILD 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/packaging/arch/PKGBUILD 2017-10-23 06:17:27.000000000 +0000
@@ -69,7 +69,7 @@
--libexecdir=/usr/lib/snapd \
--with-snap-mount-dir=/var/lib/snapd/snap \
--disable-apparmor \
- --enable-nvidia-arch \
+ --enable-nvidia-biarch \
--enable-merged-usr
make
}
diff -Nru snapd-2.28.5/packaging/fedora/snapd.spec snapd-2.29.3/packaging/fedora/snapd.spec
--- snapd-2.28.5/packaging/fedora/snapd.spec 2017-10-13 21:25:46.000000000 +0000
+++ snapd-2.29.3/packaging/fedora/snapd.spec 2017-11-09 18:16:16.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.28.5
+Version: 2.29.3
Release: 0%{?dist}
Summary: A transactional software package manager
Group: System Environment/Base
@@ -236,7 +236,6 @@
Provides: golang(%{import_path}/errtracker) = %{version}-%{release}
Provides: golang(%{import_path}/httputil) = %{version}-%{release}
Provides: golang(%{import_path}/i18n) = %{version}-%{release}
-Provides: golang(%{import_path}/i18n/dumb) = %{version}-%{release}
Provides: golang(%{import_path}/image) = %{version}-%{release}
Provides: golang(%{import_path}/interfaces) = %{version}-%{release}
Provides: golang(%{import_path}/interfaces/apparmor) = %{version}-%{release}
@@ -366,9 +365,9 @@
%gobuild -o bin/snapd $GOFLAGS %{import_path}/cmd/snapd
%gobuild -o bin/snap $GOFLAGS %{import_path}/cmd/snap
%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
+# build snap-exec and snap-update-ns completely static for base snaps
CGO_ENABLED=0 %gobuild -o bin/snap-exec $GOFLAGS %{import_path}/cmd/snap-exec
+%gobuild -o bin/snap-update-ns --ldflags '-extldflags "-static"' $GOFLAGS %{import_path}/cmd/snap-update-ns
# 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
@@ -427,6 +426,7 @@
install -d -p %{buildroot}%{_sharedstatedir}/snapd/snaps
install -d -p %{buildroot}%{_sharedstatedir}/snapd/snap/bin
install -d -p %{buildroot}%{_localstatedir}/snap
+install -d -p %{buildroot}%{_localstatedir}/cache/snapd
install -d -p %{buildroot}%{_datadir}/selinux/devel/include/contrib
install -d -p %{buildroot}%{_datadir}/selinux/packages
@@ -574,6 +574,7 @@
%dir %{_sharedstatedir}/snapd/seccomp/bpf
%dir %{_sharedstatedir}/snapd/snaps
%dir %{_sharedstatedir}/snapd/snap
+%dir /var/cache/snapd
%ghost %dir %{_sharedstatedir}/snapd/snap/bin
%dir %{_localstatedir}/snap
%ghost %{_sharedstatedir}/snapd/state.json
@@ -590,7 +591,7 @@
%{_libexecdir}/snapd/snap-seccomp
%{_libexecdir}/snapd/snap-update-ns
%{_libexecdir}/snapd/system-shutdown
-%{_mandir}/man5/snap-confine.5*
+%{_mandir}/man1/snap-confine.1*
%{_mandir}/man5/snap-discard-ns.5*
%{_prefix}/lib/udev/snappy-app-dev
%{_udevrulesdir}/80-snappy-assign.rules
@@ -658,6 +659,232 @@
%changelog
+* Thu Nov 09 2017 Michael Vogt
+- New upstream release 2.29.3
+ - daemon: cherry-picked /v2/logs fixes
+ - cmd/snap-confine: Respect biarch nature of libdirs
+ - cmd/snap-confine: Ensure snap-confine is allowed to access os-
+ release
+ - interfaces: fix udev tagging for hooks
+ - cmd: fix re-exec bug with classic confinement for host snapd
+ - tests: disable xdg-open-compat test
+ - cmd/snap-confine: add slave PTYs and let devpts newinstance
+ perform mediation
+ - interfaces/many: misc policy updates for browser-support, cups-
+ control and network-status
+ - interfaces/raw-usb: match on SUBSYSTEM, not SUBSYSTEMS
+ - tests: fix security-device-cgroup* tests on devices with
+ framebuffer
+
+* Fri Nov 03 2017 Michael Vogt
+- New upstream release 2.29.2
+ - snapctl: disable stop/start/restart (2.29)
+ - cmd/snap-update-ns: fix collection of changes made
+
+* Fri Nov 03 2017 Michael Vogt
+- New upstream release 2.29.1
+ - interfaces: fix incorrect signature of ofono DBusPermanentSlot
+ - interfaces/serial-port: udev tag plugged slots that have just
+ 'path' via KERNEL
+ - interfaces/hidraw: udev tag plugged slots that have just 'path'
+ via KERNEL
+ - interfaces/uhid: unconditionally add existing uhid device to the
+ device cgroup
+ - cmd/snap-update-ns: fix mount rules for font sharing
+ - tests: disable refresh-undo test on trusty for now
+ - tests: use `snap change --last=install` in snapd-reexec test
+ - Revert " wrappers: fail install if exec-line cannot be re-written
+ - interfaces: don't udev tag devmode or classic snaps
+ - many: make ignore-validation sticky and send the flag with refresh
+ requests
+
+* Mon Oct 30 2017 Michael Vogt
+- New upstream release 2.29
+ - interfaces/many: miscellaneous updates based on feedback from the
+ field
+ - snap-confine: allow reading uevents from any where in /sys
+ - spread: add bionic beaver
+ - debian: make packaging/ubuntu-14.04/copyright a real file again
+ - tests: cherry pick the fix for services test into 2.29
+ - cmd/snap-update-ns: initialize logger
+ - hooks/configure: queue service restarts
+ - snap-{confine,seccomp}: make @unrestricted fully unrestricted
+ - interfaces: clean system apparmor cache on core device
+ - debian: do not build static snap-exec on powerpc
+ - snap-confine: increase sanity_timeout to 6s
+ - snapctl: cherry pick service commands changes
+ - cmd/snap: tell translators about arg names and descs req's
+ - systemd: run all mount units before snapd.service to avoid race
+ - store: add a test to show auth failures are forwarded by doRequest
+ - daemon: convert ErrInvalidCredentials to a 401 Unauthorized error.
+ - store: forward on INVALID_CREDENTIALS error as
+ ErrInvalidCredentials
+ - daemon: generate a forbidden response message if polkit dialog is
+ dismissed
+ - daemon: Allow Polkit authorization to cancel changes.
+ - travis: switch to container based test runs
+ - interfaces: reduce duplicated code in interface tests mocks
+ - tests: improve revert related testing
+ - interfaces: sanitize plugs and slots early in ReadInfo
+ - store: add download caching
+ - preserve TMPDIR and HOSTALIASES across snap-confine invocation
+ - snap-confine: init all arrays with `= {0,}`
+ - tests: adding test for network-manager interface
+ - interfaces/mount: don't generate legacy per-hook/per-app mount
+ profiles
+ - snap: introduce structured epochs
+ - tests: fix interfaces-cups-control test for cups-2.2.5
+ - snap-confine: cleanup incorrectly created nvidia udev tags
+ - cmd/snap-confine: update valid security tag regexp
+ - cmd/libsnap: enable two stranded tests
+ - cmd,packaging: enable apparmor on openSUSE
+ - overlord/ifacestate: refresh all security backends on startup
+ - interfaces/dbus: drop unneeded check for
+ release.ReleaseInfo.ForceDevMode
+ - dbus: ensure io.snapcraft.Launcher.service is created on re-
+ exec
+ - overlord/auth: continue for now supporting UBUNTU_STORE_ID if the
+ model is generic-classic
+ - snap-confine: add support for handling /dev/nvidia-modeset
+ - interfaces/network-control: remove incorrect rules for tun
+ - spread: allow setting SPREAD_DEBUG_EACH=0 to disable debug-each
+ section
+ - packaging: remove .mnt files on removal
+ - tests: fix econnreset scenario when the iptables rule was not
+ created
+ - tests: add test for lxd interface
+ - run-checks: use nakedret static checker to check for naked
+ returns on long functions
+ - progress: be more flexible in testing ansimeter
+ - interfaces: fix udev rules for tun
+ - many: implement our own ANSI-escape-using progress indicator
+ - snap-exec: update tests to follow main_test pattern
+ - snap: support "command: foo $ENV_STRING"
+ - packaging: update nvidia configure options
+ - snap: add new `snap pack` and use in tests
+ - cmd: correctly name the "Ubuntu" and "Arch" NVIDIA methods
+ - cmd: add autogen case for solus
+ - tests: do not use http://canihazip.com/ which appears to be down
+ - hooks: commands for controlling own services from snapctl
+ - snap: refactor cmdGet.Execute()
+ - interfaces/mount: make Change.Perform testable and test it
+ - interfaces/mount,cmd/snap-update-ns: move change code
+ - snap-confine: is_running_on_classic_distribution() looks into os-
+ release
+ - interfaces: misc updates for default, browser-support, home and
+ system-observe
+ - interfaces: deny lttng by default
+ - interfaces/lxd: lxd slot implementation can also be an app snap
+ - release,cmd,dirs: Redo the distro checks to take into account
+ distribution families
+ - cmd/snap: completion for alias and unalias
+ - snap-confine: add new SC_CLEANUP and use it
+ - snap: refrain from running filepath.Base on random strings
+ - cmd/snap-confine: put processes into freezer hierarchy
+ - wrappers: fail install if exec-line cannot be re-written
+ - cmd/snap-seccomp,osutil: make user/group lookup functions public
+ - snapstate: deal with snap user data in the /root/ directory
+ - interfaces: Enhance full-confinement support for biarch
+ distributions
+ - snap-confine: Only attempt to copy/mount NVIDIA libs when NVIDIA
+ is used
+ - packaging/fedora: Add Fedora 26, 27, and Rawhide symlinks
+ - overlord/snapstate: prefer a smaller corner case for doing the
+ wrong thing
+ - cmd/snap-repair: set user agent for snap-repair http requests
+ - packaging: bring down the delta between 14.04 and 16.04
+ - snap-confine: Ensure lib64 biarch directory is respected
+ - snap-confine: update apparmor rules for fedora based base snaps
+ - tests: Increase SNAPD_CONFIGURE_HOOK_TIMEOUT to 3 minutes to
+ install real snaps
+ - daemon: use client.Snap instead of map[string]interface{} for
+ snaps.
+ - hooks: rename refresh hook to post-refresh
+ - git: make the .gitingore file a bit more targeted
+ - interfaces/opengl: don't udev tag nvidia devices and use snap-
+ confine instead
+ - cmd/snap-{confine,update-ns}: apply mount profiles using snap-
+ update-ns
+ - cmd: update "make hack"
+ - interfaces/system-observe: allow clients to enumerate DBus
+ connection names
+ - snap-repair: implement `snap-repair {list,show}`
+ - dirs,interfaces: create snap-confine.d on demand when re-executing
+ - snap-confine: fix base snaps on core
+ - cmd/snap-repair: fix tests when running as root
+ - interfaces: add Connection type
+ - cmd/snap-repair: skip disabled repairs
+ - cmd/snap-repair: prefer leaking unmanaged fds on test failure over
+ closing random ones
+ - snap-repair: make `repair` binary available for repair scripts
+ - snap-repair: fix missing Close() in TestStatusHappy
+ - cmd/snap-confine,packaging: import snapd-generated policy
+ - cmd/snap: return empty document if snap has no configuration
+ - snap-seccomp: run secondary-arch tests via gcc-multilib
+ - snap: implement `snap {repair,repairs}` and pass-through to snap-
+ repair
+ - interfaces/builtin: allow receiving dbus messages
+ - snap-repair: implement `snap-repair {done,skip,retry}`
+ - data/completion: small tweak to snap completion snippet
+ - dirs: fix classic support detection
+ - cmd/snap-repair: integrate root public keys for repairs
+ - tests: fix ubuntu core services
+ - tests: add new test that checks that the compat snapd-xdg-open
+ works
+ - snap-confine: improve error message if core/u-core cannot be found
+ - tests: only run tests/regression/nmcli on amd64
+ - interfaces: mount host system fonts in desktop interface
+ - interfaces: enable partial apparmor support
+ - snapstate: auto-install missing base snaps
+ - spread: work around temporary packaging issue in debian sid
+ - asserts,cmd/snap-repair: introduce a mandatory summary for repairs
+ - asserts,cmd/snap-repair: represent RepairID internally as an int
+ - tests: test the real "xdg-open" from the core snap
+ - many: implement fetching sections and package names periodically.
+ - interfaces/network: allow using netcat as client
+ - snap-seccomp, osutil: use osutil.AtomicFile in snap-seccomp
+ - snap-seccomp: skip mknod syscall on arm64
+ - tests: add trivial canonical-livepatch test
+ - tests: add test that ensures that all core services are working
+ - many: add logger.MockLogger() and use it in the tests
+ - snap-repair: fix test failure in TestRepairHitsTimeout
+ - asserts: add empty values check in HeadersFromPrimaryKey
+ - daemon: remove unused installSnap var in test
+ - daemon: reach for Overlord.Loop less thanks to overlord.Mock
+ - snap-seccomp: manually resolve socket() call in tests
+ - tests: change regex used to validate installed ubuntu core snap
+ - cmd/snapctl: allow snapctl -h without a context (regression fix).
+ - many: use snapcore/snapd/i18n instead of i18n/dumb
+ - many: introduce asserts.NotFoundError replacing both ErrNotFound
+ and store.AssertionNotFoundError
+ - packaging: don't include any marcos in comments
+ - overlord: use overlord.Mock in more tests, make sure we check the
+ outcome of Settle
+ - tests: try to fix staging tests
+ - store: simplify api base url config
+ - systemd: add systemd.MockJournalctl()
+ - many: provide systemd.MockSystemctl() helper
+ - tests: improve the listing test to not fail for e.g. 2.28~rc2
+ - snapstate: give snapmgrTestSuite.settle() more time to settle
+ - tests: fix regex to check core version on snap list
+ - debian: update trusted account-keys check on 14.04 packaging
+ - interfaces: add udev netlink support to hardware-observe
+ - overlord: introduce Mock which enables to use Overlord.Settle for
+ settle in many more places
+ - snap-repair: execute the repair and capture logs/status
+ - tests: run the tests/unit/go everywhere
+ - daemon, snapstate: move ensureCore from daemon/api.go into
+ snapstate.go
+ - cmd/snap: get keys or root document
+ - spread.yaml: turn suse to manual given that it's breaking master
+ - many: configure store from state, reconfigure store at runtime
+ - osutil: AtomicWriter (an io.Writer), and io.Reader versions of
+ AtomicWrite*
+ - tests: check for negative syscalls in runBpf() and skip those
+ tests
+ - docs: use abolute path in PULL_REQUEST_TEMPLATE.md
+ - store: move device auth endpoint uris to config (#3831)
+
* Fri Oct 13 2017 Michael Vogt
- New upstream release 2.28.5
- snap-confine: cleanup broken nvidia udev tags
diff -Nru snapd-2.28.5/packaging/fedora/snap-mgmt.sh snapd-2.29.3/packaging/fedora/snap-mgmt.sh
--- snapd-2.28.5/packaging/fedora/snap-mgmt.sh 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/packaging/fedora/snap-mgmt.sh 2017-10-23 06:17:27.000000000 +0000
@@ -89,6 +89,10 @@
# opportunistic as those might not be actually mounted
for mnt in /run/snapd/ns/*.mnt; do
umount -l "$mnt" || true
+ rm -f "$mnt"
+ done
+ for fstab in /run/snapd/ns/*.fstab; do
+ rm -f "$fstab"
done
umount -l /run/snapd/ns/ || true
@@ -105,6 +109,9 @@
rm -rf /var/lib/snapd/seccomp/bpf/*
rm -rf /var/lib/snapd/device/*
rm -rf /var/lib/snapd/assertions/*
+
+ echo "Removing snapd catalog cache"
+ rm -f /var/cache/snapd/*
}
while [ -n "$1" ]; do
diff -Nru snapd-2.28.5/packaging/fedora-25/snapd.spec snapd-2.29.3/packaging/fedora-25/snapd.spec
--- snapd-2.28.5/packaging/fedora-25/snapd.spec 2017-10-13 21:25:46.000000000 +0000
+++ snapd-2.29.3/packaging/fedora-25/snapd.spec 2017-11-09 18:16:16.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.28.5
+Version: 2.29.3
Release: 0%{?dist}
Summary: A transactional software package manager
Group: System Environment/Base
@@ -236,7 +236,6 @@
Provides: golang(%{import_path}/errtracker) = %{version}-%{release}
Provides: golang(%{import_path}/httputil) = %{version}-%{release}
Provides: golang(%{import_path}/i18n) = %{version}-%{release}
-Provides: golang(%{import_path}/i18n/dumb) = %{version}-%{release}
Provides: golang(%{import_path}/image) = %{version}-%{release}
Provides: golang(%{import_path}/interfaces) = %{version}-%{release}
Provides: golang(%{import_path}/interfaces/apparmor) = %{version}-%{release}
@@ -366,9 +365,9 @@
%gobuild -o bin/snapd $GOFLAGS %{import_path}/cmd/snapd
%gobuild -o bin/snap $GOFLAGS %{import_path}/cmd/snap
%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
+# build snap-exec and snap-update-ns completely static for base snaps
CGO_ENABLED=0 %gobuild -o bin/snap-exec $GOFLAGS %{import_path}/cmd/snap-exec
+%gobuild -o bin/snap-update-ns --ldflags '-extldflags "-static"' $GOFLAGS %{import_path}/cmd/snap-update-ns
# 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
@@ -427,6 +426,7 @@
install -d -p %{buildroot}%{_sharedstatedir}/snapd/snaps
install -d -p %{buildroot}%{_sharedstatedir}/snapd/snap/bin
install -d -p %{buildroot}%{_localstatedir}/snap
+install -d -p %{buildroot}%{_localstatedir}/cache/snapd
install -d -p %{buildroot}%{_datadir}/selinux/devel/include/contrib
install -d -p %{buildroot}%{_datadir}/selinux/packages
@@ -574,6 +574,7 @@
%dir %{_sharedstatedir}/snapd/seccomp/bpf
%dir %{_sharedstatedir}/snapd/snaps
%dir %{_sharedstatedir}/snapd/snap
+%dir /var/cache/snapd
%ghost %dir %{_sharedstatedir}/snapd/snap/bin
%dir %{_localstatedir}/snap
%ghost %{_sharedstatedir}/snapd/state.json
@@ -590,7 +591,7 @@
%{_libexecdir}/snapd/snap-seccomp
%{_libexecdir}/snapd/snap-update-ns
%{_libexecdir}/snapd/system-shutdown
-%{_mandir}/man5/snap-confine.5*
+%{_mandir}/man1/snap-confine.1*
%{_mandir}/man5/snap-discard-ns.5*
%{_prefix}/lib/udev/snappy-app-dev
%{_udevrulesdir}/80-snappy-assign.rules
@@ -658,6 +659,232 @@
%changelog
+* Thu Nov 09 2017 Michael Vogt
+- New upstream release 2.29.3
+ - daemon: cherry-picked /v2/logs fixes
+ - cmd/snap-confine: Respect biarch nature of libdirs
+ - cmd/snap-confine: Ensure snap-confine is allowed to access os-
+ release
+ - interfaces: fix udev tagging for hooks
+ - cmd: fix re-exec bug with classic confinement for host snapd
+ - tests: disable xdg-open-compat test
+ - cmd/snap-confine: add slave PTYs and let devpts newinstance
+ perform mediation
+ - interfaces/many: misc policy updates for browser-support, cups-
+ control and network-status
+ - interfaces/raw-usb: match on SUBSYSTEM, not SUBSYSTEMS
+ - tests: fix security-device-cgroup* tests on devices with
+ framebuffer
+
+* Fri Nov 03 2017 Michael Vogt
+- New upstream release 2.29.2
+ - snapctl: disable stop/start/restart (2.29)
+ - cmd/snap-update-ns: fix collection of changes made
+
+* Fri Nov 03 2017 Michael Vogt
+- New upstream release 2.29.1
+ - interfaces: fix incorrect signature of ofono DBusPermanentSlot
+ - interfaces/serial-port: udev tag plugged slots that have just
+ 'path' via KERNEL
+ - interfaces/hidraw: udev tag plugged slots that have just 'path'
+ via KERNEL
+ - interfaces/uhid: unconditionally add existing uhid device to the
+ device cgroup
+ - cmd/snap-update-ns: fix mount rules for font sharing
+ - tests: disable refresh-undo test on trusty for now
+ - tests: use `snap change --last=install` in snapd-reexec test
+ - Revert " wrappers: fail install if exec-line cannot be re-written
+ - interfaces: don't udev tag devmode or classic snaps
+ - many: make ignore-validation sticky and send the flag with refresh
+ requests
+
+* Mon Oct 30 2017 Michael Vogt
+- New upstream release 2.29
+ - interfaces/many: miscellaneous updates based on feedback from the
+ field
+ - snap-confine: allow reading uevents from any where in /sys
+ - spread: add bionic beaver
+ - debian: make packaging/ubuntu-14.04/copyright a real file again
+ - tests: cherry pick the fix for services test into 2.29
+ - cmd/snap-update-ns: initialize logger
+ - hooks/configure: queue service restarts
+ - snap-{confine,seccomp}: make @unrestricted fully unrestricted
+ - interfaces: clean system apparmor cache on core device
+ - debian: do not build static snap-exec on powerpc
+ - snap-confine: increase sanity_timeout to 6s
+ - snapctl: cherry pick service commands changes
+ - cmd/snap: tell translators about arg names and descs req's
+ - systemd: run all mount units before snapd.service to avoid race
+ - store: add a test to show auth failures are forwarded by doRequest
+ - daemon: convert ErrInvalidCredentials to a 401 Unauthorized error.
+ - store: forward on INVALID_CREDENTIALS error as
+ ErrInvalidCredentials
+ - daemon: generate a forbidden response message if polkit dialog is
+ dismissed
+ - daemon: Allow Polkit authorization to cancel changes.
+ - travis: switch to container based test runs
+ - interfaces: reduce duplicated code in interface tests mocks
+ - tests: improve revert related testing
+ - interfaces: sanitize plugs and slots early in ReadInfo
+ - store: add download caching
+ - preserve TMPDIR and HOSTALIASES across snap-confine invocation
+ - snap-confine: init all arrays with `= {0,}`
+ - tests: adding test for network-manager interface
+ - interfaces/mount: don't generate legacy per-hook/per-app mount
+ profiles
+ - snap: introduce structured epochs
+ - tests: fix interfaces-cups-control test for cups-2.2.5
+ - snap-confine: cleanup incorrectly created nvidia udev tags
+ - cmd/snap-confine: update valid security tag regexp
+ - cmd/libsnap: enable two stranded tests
+ - cmd,packaging: enable apparmor on openSUSE
+ - overlord/ifacestate: refresh all security backends on startup
+ - interfaces/dbus: drop unneeded check for
+ release.ReleaseInfo.ForceDevMode
+ - dbus: ensure io.snapcraft.Launcher.service is created on re-
+ exec
+ - overlord/auth: continue for now supporting UBUNTU_STORE_ID if the
+ model is generic-classic
+ - snap-confine: add support for handling /dev/nvidia-modeset
+ - interfaces/network-control: remove incorrect rules for tun
+ - spread: allow setting SPREAD_DEBUG_EACH=0 to disable debug-each
+ section
+ - packaging: remove .mnt files on removal
+ - tests: fix econnreset scenario when the iptables rule was not
+ created
+ - tests: add test for lxd interface
+ - run-checks: use nakedret static checker to check for naked
+ returns on long functions
+ - progress: be more flexible in testing ansimeter
+ - interfaces: fix udev rules for tun
+ - many: implement our own ANSI-escape-using progress indicator
+ - snap-exec: update tests to follow main_test pattern
+ - snap: support "command: foo $ENV_STRING"
+ - packaging: update nvidia configure options
+ - snap: add new `snap pack` and use in tests
+ - cmd: correctly name the "Ubuntu" and "Arch" NVIDIA methods
+ - cmd: add autogen case for solus
+ - tests: do not use http://canihazip.com/ which appears to be down
+ - hooks: commands for controlling own services from snapctl
+ - snap: refactor cmdGet.Execute()
+ - interfaces/mount: make Change.Perform testable and test it
+ - interfaces/mount,cmd/snap-update-ns: move change code
+ - snap-confine: is_running_on_classic_distribution() looks into os-
+ release
+ - interfaces: misc updates for default, browser-support, home and
+ system-observe
+ - interfaces: deny lttng by default
+ - interfaces/lxd: lxd slot implementation can also be an app snap
+ - release,cmd,dirs: Redo the distro checks to take into account
+ distribution families
+ - cmd/snap: completion for alias and unalias
+ - snap-confine: add new SC_CLEANUP and use it
+ - snap: refrain from running filepath.Base on random strings
+ - cmd/snap-confine: put processes into freezer hierarchy
+ - wrappers: fail install if exec-line cannot be re-written
+ - cmd/snap-seccomp,osutil: make user/group lookup functions public
+ - snapstate: deal with snap user data in the /root/ directory
+ - interfaces: Enhance full-confinement support for biarch
+ distributions
+ - snap-confine: Only attempt to copy/mount NVIDIA libs when NVIDIA
+ is used
+ - packaging/fedora: Add Fedora 26, 27, and Rawhide symlinks
+ - overlord/snapstate: prefer a smaller corner case for doing the
+ wrong thing
+ - cmd/snap-repair: set user agent for snap-repair http requests
+ - packaging: bring down the delta between 14.04 and 16.04
+ - snap-confine: Ensure lib64 biarch directory is respected
+ - snap-confine: update apparmor rules for fedora based base snaps
+ - tests: Increase SNAPD_CONFIGURE_HOOK_TIMEOUT to 3 minutes to
+ install real snaps
+ - daemon: use client.Snap instead of map[string]interface{} for
+ snaps.
+ - hooks: rename refresh hook to post-refresh
+ - git: make the .gitingore file a bit more targeted
+ - interfaces/opengl: don't udev tag nvidia devices and use snap-
+ confine instead
+ - cmd/snap-{confine,update-ns}: apply mount profiles using snap-
+ update-ns
+ - cmd: update "make hack"
+ - interfaces/system-observe: allow clients to enumerate DBus
+ connection names
+ - snap-repair: implement `snap-repair {list,show}`
+ - dirs,interfaces: create snap-confine.d on demand when re-executing
+ - snap-confine: fix base snaps on core
+ - cmd/snap-repair: fix tests when running as root
+ - interfaces: add Connection type
+ - cmd/snap-repair: skip disabled repairs
+ - cmd/snap-repair: prefer leaking unmanaged fds on test failure over
+ closing random ones
+ - snap-repair: make `repair` binary available for repair scripts
+ - snap-repair: fix missing Close() in TestStatusHappy
+ - cmd/snap-confine,packaging: import snapd-generated policy
+ - cmd/snap: return empty document if snap has no configuration
+ - snap-seccomp: run secondary-arch tests via gcc-multilib
+ - snap: implement `snap {repair,repairs}` and pass-through to snap-
+ repair
+ - interfaces/builtin: allow receiving dbus messages
+ - snap-repair: implement `snap-repair {done,skip,retry}`
+ - data/completion: small tweak to snap completion snippet
+ - dirs: fix classic support detection
+ - cmd/snap-repair: integrate root public keys for repairs
+ - tests: fix ubuntu core services
+ - tests: add new test that checks that the compat snapd-xdg-open
+ works
+ - snap-confine: improve error message if core/u-core cannot be found
+ - tests: only run tests/regression/nmcli on amd64
+ - interfaces: mount host system fonts in desktop interface
+ - interfaces: enable partial apparmor support
+ - snapstate: auto-install missing base snaps
+ - spread: work around temporary packaging issue in debian sid
+ - asserts,cmd/snap-repair: introduce a mandatory summary for repairs
+ - asserts,cmd/snap-repair: represent RepairID internally as an int
+ - tests: test the real "xdg-open" from the core snap
+ - many: implement fetching sections and package names periodically.
+ - interfaces/network: allow using netcat as client
+ - snap-seccomp, osutil: use osutil.AtomicFile in snap-seccomp
+ - snap-seccomp: skip mknod syscall on arm64
+ - tests: add trivial canonical-livepatch test
+ - tests: add test that ensures that all core services are working
+ - many: add logger.MockLogger() and use it in the tests
+ - snap-repair: fix test failure in TestRepairHitsTimeout
+ - asserts: add empty values check in HeadersFromPrimaryKey
+ - daemon: remove unused installSnap var in test
+ - daemon: reach for Overlord.Loop less thanks to overlord.Mock
+ - snap-seccomp: manually resolve socket() call in tests
+ - tests: change regex used to validate installed ubuntu core snap
+ - cmd/snapctl: allow snapctl -h without a context (regression fix).
+ - many: use snapcore/snapd/i18n instead of i18n/dumb
+ - many: introduce asserts.NotFoundError replacing both ErrNotFound
+ and store.AssertionNotFoundError
+ - packaging: don't include any marcos in comments
+ - overlord: use overlord.Mock in more tests, make sure we check the
+ outcome of Settle
+ - tests: try to fix staging tests
+ - store: simplify api base url config
+ - systemd: add systemd.MockJournalctl()
+ - many: provide systemd.MockSystemctl() helper
+ - tests: improve the listing test to not fail for e.g. 2.28~rc2
+ - snapstate: give snapmgrTestSuite.settle() more time to settle
+ - tests: fix regex to check core version on snap list
+ - debian: update trusted account-keys check on 14.04 packaging
+ - interfaces: add udev netlink support to hardware-observe
+ - overlord: introduce Mock which enables to use Overlord.Settle for
+ settle in many more places
+ - snap-repair: execute the repair and capture logs/status
+ - tests: run the tests/unit/go everywhere
+ - daemon, snapstate: move ensureCore from daemon/api.go into
+ snapstate.go
+ - cmd/snap: get keys or root document
+ - spread.yaml: turn suse to manual given that it's breaking master
+ - many: configure store from state, reconfigure store at runtime
+ - osutil: AtomicWriter (an io.Writer), and io.Reader versions of
+ AtomicWrite*
+ - tests: check for negative syscalls in runBpf() and skip those
+ tests
+ - docs: use abolute path in PULL_REQUEST_TEMPLATE.md
+ - store: move device auth endpoint uris to config (#3831)
+
* Fri Oct 13 2017 Michael Vogt
- New upstream release 2.28.5
- snap-confine: cleanup broken nvidia udev tags
diff -Nru snapd-2.28.5/packaging/fedora-25/snap-mgmt.sh snapd-2.29.3/packaging/fedora-25/snap-mgmt.sh
--- snapd-2.28.5/packaging/fedora-25/snap-mgmt.sh 2017-09-13 14:47:18.000000000 +0000
+++ snapd-2.29.3/packaging/fedora-25/snap-mgmt.sh 2017-10-23 06:17:27.000000000 +0000
@@ -89,6 +89,10 @@
# opportunistic as those might not be actually mounted
for mnt in /run/snapd/ns/*.mnt; do
umount -l "$mnt" || true
+ rm -f "$mnt"
+ done
+ for fstab in /run/snapd/ns/*.fstab; do
+ rm -f "$fstab"
done
umount -l /run/snapd/ns/ || true
@@ -105,6 +109,9 @@
rm -rf /var/lib/snapd/seccomp/bpf/*
rm -rf /var/lib/snapd/device/*
rm -rf /var/lib/snapd/assertions/*
+
+ echo "Removing snapd catalog cache"
+ rm -f /var/cache/snapd/*
}
while [ -n "$1" ]; do
diff -Nru snapd-2.28.5/packaging/fedora-26/snapd.spec snapd-2.29.3/packaging/fedora-26/snapd.spec
--- snapd-2.28.5/packaging/fedora-26/snapd.spec 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/packaging/fedora-26/snapd.spec 2017-11-09 18:16:16.000000000 +0000
@@ -0,0 +1,1630 @@
+# With Fedora, nothing is bundled. For everything else, bundling is used.
+# To use bundled stuff, use "--with vendorized" on rpmbuild
+%if 0%{?fedora}
+%bcond_with vendorized
+%else
+%bcond_without vendorized
+%endif
+
+# A switch to allow building the package with support for testkeys which
+# are used for the spread test suite of snapd.
+%bcond_with testkeys
+
+%global with_devel 1
+%global with_debug 1
+%global with_check 0
+%global with_unit_test 0
+%global with_test_keys 0
+
+# For the moment, we don't support all golang arches...
+%global with_goarches 0
+
+%if ! %{with vendorized}
+%global with_bundled 0
+%else
+%global with_bundled 1
+%endif
+
+%if ! %{with testkeys}
+%global with_test_keys 0
+%else
+%global with_test_keys 1
+%endif
+
+%if 0%{?with_debug}
+%global _dwz_low_mem_die_limit 0
+%else
+%global debug_package %{nil}
+%endif
+
+%global provider github
+%global provider_tld com
+%global project snapcore
+%global repo snapd
+# https://github.com/snapcore/snapd
+%global provider_prefix %{provider}.%{provider_tld}/%{project}/%{repo}
+%global import_path %{provider_prefix}
+
+%global snappy_svcs snapd.service snapd.socket snapd.autoimport.service snapd.refresh.timer snapd.refresh.service
+
+Name: snapd
+Version: 2.29.3
+Release: 0%{?dist}
+Summary: A transactional software package manager
+Group: System Environment/Base
+License: GPLv3
+URL: https://%{provider_prefix}
+%if ! 0%{?with_bundled}
+Source0: https://%{provider_prefix}/archive/%{version}/%{name}-%{version}.tar.gz
+%else
+Source0: https://%{provider_prefix}/releases/download/%{version}/%{name}_%{version}.vendor.orig.tar.xz
+%endif
+
+%if 0%{?with_goarches}
+# e.g. el6 has ppc64 arch without gcc-go, so EA tag is required
+ExclusiveArch: %{?go_arches:%{go_arches}}%{!?go_arches:%{ix86} x86_64 %{arm}}
+%else
+# Verified arches from snapd upstream
+ExclusiveArch: %{ix86} x86_64 %{arm} aarch64 ppc64le s390x
+%endif
+
+# If go_compiler is not set to 1, there is no virtual provide. Use golang instead.
+BuildRequires: %{?go_compiler:compiler(go-compiler)}%{!?go_compiler:golang}
+BuildRequires: systemd
+%{?systemd_requires}
+
+Requires: snap-confine%{?_isa} = %{version}-%{release}
+Requires: squashfs-tools
+# we need squashfs.ko loaded
+Requires: kmod(squashfs.ko)
+# bash-completion owns /usr/share/bash-completion/completions
+Requires: bash-completion
+
+# Force the SELinux module to be installed
+Requires: %{name}-selinux = %{version}-%{release}
+
+%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)
+BuildRequires: golang(github.com/ojii/gettext.go)
+BuildRequires: golang(github.com/seccomp/libseccomp-golang)
+BuildRequires: golang(golang.org/x/crypto/openpgp/armor)
+BuildRequires: golang(golang.org/x/crypto/openpgp/packet)
+BuildRequires: golang(golang.org/x/crypto/sha3)
+BuildRequires: golang(golang.org/x/crypto/ssh/terminal)
+BuildRequires: golang(golang.org/x/net/context)
+BuildRequires: golang(golang.org/x/net/context/ctxhttp)
+BuildRequires: golang(gopkg.in/check.v1)
+BuildRequires: golang(gopkg.in/macaroon.v1)
+BuildRequires: golang(gopkg.in/mgo.v2/bson)
+BuildRequires: golang(gopkg.in/retry.v1)
+BuildRequires: golang(gopkg.in/tomb.v2)
+BuildRequires: golang(gopkg.in/yaml.v2)
+%endif
+
+%description
+Snappy is a modern, cross-distribution, transactional package manager
+designed for working with self-contained, immutable packages.
+
+%package -n snap-confine
+Summary: Confinement system for snap applications
+License: GPLv3
+Group: System Environment/Base
+BuildRequires: autoconf
+BuildRequires: automake
+BuildRequires: libtool
+BuildRequires: gcc
+BuildRequires: gettext
+BuildRequires: gnupg
+BuildRequires: indent
+BuildRequires: pkgconfig(glib-2.0)
+BuildRequires: pkgconfig(libcap)
+BuildRequires: pkgconfig(libseccomp)
+BuildRequires: pkgconfig(libudev)
+BuildRequires: pkgconfig(systemd)
+BuildRequires: pkgconfig(udev)
+BuildRequires: xfsprogs-devel
+BuildRequires: glibc-static
+BuildRequires: libseccomp-static
+BuildRequires: valgrind
+BuildRequires: %{_bindir}/rst2man
+%if 0%{?fedora} >= 25
+# ShellCheck in F24 and older doesn't work
+BuildRequires: %{_bindir}/shellcheck
+%endif
+
+# Ensures older version from split packaging is replaced
+Obsoletes: snap-confine < 2.19
+
+%description -n snap-confine
+This package is used internally by snapd to apply confinement to
+the started snap applications.
+
+%package selinux
+Summary: SELinux module for snapd
+Group: System Environment/Base
+License: GPLv2+
+BuildArch: noarch
+BuildRequires: selinux-policy, selinux-policy-devel
+Requires(post): selinux-policy-base >= %{_selinux_policy_version}
+Requires(post): policycoreutils
+Requires(post): policycoreutils-python-utils
+Requires(pre): libselinux-utils
+Requires(post): libselinux-utils
+
+%description selinux
+This package provides the SELinux policy module to ensure snapd
+runs properly under an environment with SELinux enabled.
+
+
+%if 0%{?with_devel}
+%package devel
+Summary: %{summary}
+BuildArch: noarch
+
+%if 0%{?with_check} && ! 0%{?with_bundled}
+%endif
+
+%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)
+Requires: golang(github.com/ojii/gettext.go)
+Requires: golang(github.com/seccomp/libseccomp-golang)
+Requires: golang(golang.org/x/crypto/openpgp/armor)
+Requires: golang(golang.org/x/crypto/openpgp/packet)
+Requires: golang(golang.org/x/crypto/sha3)
+Requires: golang(golang.org/x/crypto/ssh/terminal)
+Requires: golang(golang.org/x/net/context)
+Requires: golang(golang.org/x/net/context/ctxhttp)
+Requires: golang(gopkg.in/check.v1)
+Requires: golang(gopkg.in/macaroon.v1)
+Requires: golang(gopkg.in/mgo.v2/bson)
+Requires: golang(gopkg.in/retry.v1)
+Requires: golang(gopkg.in/tomb.v2)
+Requires: golang(gopkg.in/yaml.v2)
+%else
+# These Provides are unversioned because the sources in
+# the bundled tarball are unversioned (they go by git commit)
+# *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))
+Provides: bundled(golang(github.com/mvo5/libseccomp-golang))
+Provides: bundled(golang(github.com/ojii/gettext.go))
+Provides: bundled(golang(golang.org/x/crypto/openpgp/armor))
+Provides: bundled(golang(golang.org/x/crypto/openpgp/packet))
+Provides: bundled(golang(golang.org/x/crypto/sha3))
+Provides: bundled(golang(golang.org/x/crypto/ssh/terminal))
+Provides: bundled(golang(golang.org/x/net/context))
+Provides: bundled(golang(golang.org/x/net/context/ctxhttp))
+Provides: bundled(golang(gopkg.in/check.v1))
+Provides: bundled(golang(gopkg.in/macaroon.v1))
+Provides: bundled(golang(gopkg.in/mgo.v2/bson))
+Provides: bundled(golang(gopkg.in/retry.v1))
+Provides: bundled(golang(gopkg.in/tomb.v2))
+Provides: bundled(golang(gopkg.in/yaml.v2))
+%endif
+
+# Generated by gofed
+Provides: golang(%{import_path}/arch) = %{version}-%{release}
+Provides: golang(%{import_path}/asserts) = %{version}-%{release}
+Provides: golang(%{import_path}/asserts/assertstest) = %{version}-%{release}
+Provides: golang(%{import_path}/asserts/signtool) = %{version}-%{release}
+Provides: golang(%{import_path}/asserts/snapasserts) = %{version}-%{release}
+Provides: golang(%{import_path}/asserts/sysdb) = %{version}-%{release}
+Provides: golang(%{import_path}/asserts/systestkeys) = %{version}-%{release}
+Provides: golang(%{import_path}/boot) = %{version}-%{release}
+Provides: golang(%{import_path}/boot/boottest) = %{version}-%{release}
+Provides: golang(%{import_path}/client) = %{version}-%{release}
+Provides: golang(%{import_path}/cmd) = %{version}-%{release}
+Provides: golang(%{import_path}/daemon) = %{version}-%{release}
+Provides: golang(%{import_path}/dirs) = %{version}-%{release}
+Provides: golang(%{import_path}/errtracker) = %{version}-%{release}
+Provides: golang(%{import_path}/httputil) = %{version}-%{release}
+Provides: golang(%{import_path}/i18n) = %{version}-%{release}
+Provides: golang(%{import_path}/image) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/apparmor) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/backends) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/builtin) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/dbus) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/ifacetest) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/kmod) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/mount) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/policy) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/seccomp) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/systemd) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/udev) = %{version}-%{release}
+Provides: golang(%{import_path}/logger) = %{version}-%{release}
+Provides: golang(%{import_path}/osutil) = %{version}-%{release}
+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}
+Provides: golang(%{import_path}/overlord/hookstate) = %{version}-%{release}
+Provides: golang(%{import_path}/overlord/hookstate/ctlcmd) = %{version}-%{release}
+Provides: golang(%{import_path}/overlord/hookstate/hooktest) = %{version}-%{release}
+Provides: golang(%{import_path}/overlord/ifacestate) = %{version}-%{release}
+Provides: golang(%{import_path}/overlord/patch) = %{version}-%{release}
+Provides: golang(%{import_path}/overlord/snapstate) = %{version}-%{release}
+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}/release) = %{version}-%{release}
+Provides: golang(%{import_path}/snap) = %{version}-%{release}
+Provides: golang(%{import_path}/snap/snapdir) = %{version}-%{release}
+Provides: golang(%{import_path}/snap/snapenv) = %{version}-%{release}
+Provides: golang(%{import_path}/snap/snaptest) = %{version}-%{release}
+Provides: golang(%{import_path}/snap/squashfs) = %{version}-%{release}
+Provides: golang(%{import_path}/store) = %{version}-%{release}
+Provides: golang(%{import_path}/strutil) = %{version}-%{release}
+Provides: golang(%{import_path}/systemd) = %{version}-%{release}
+Provides: golang(%{import_path}/tests/lib/fakestore/refresh) = %{version}-%{release}
+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
+%{summary}
+
+This package contains library source intended for
+building other packages which use import path with
+%{import_path} prefix.
+%endif
+
+%if 0%{?with_unit_test} && 0%{?with_devel}
+%package unit-test-devel
+Summary: Unit tests for %{name} package
+
+%if 0%{?with_check}
+#Here comes all BuildRequires: PACKAGE the unit tests
+#in %%check section need for running
+%endif
+
+%if 0%{?with_check} && ! 0%{?with_bundled}
+BuildRequires: golang(github.com/mvo5/goconfigparser)
+%endif
+
+%if ! 0%{?with_bundled}
+Requires: golang(github.com/mvo5/goconfigparser)
+%else
+Provides: bundled(golang(github.com/mvo5/goconfigparser))
+%endif
+
+# test subpackage tests code from devel subpackage
+Requires: %{name}-devel = %{version}-%{release}
+
+%description unit-test-devel
+%{summary}
+
+This package contains unit tests for project
+providing packages with %{import_path} prefix.
+%endif
+
+%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
+./mkversion.sh "%{version}-%{release}"
+
+# Build snapd
+mkdir -p src/github.com/snapcore
+ln -s ../../../ src/github.com/snapcore/snapd
+
+%if ! 0%{?with_bundled}
+export GOPATH=$(pwd):%{gopath}
+%else
+export GOPATH=$(pwd):$(pwd)/Godeps/_workspace:%{gopath}
+%endif
+
+GOFLAGS=
+%if 0%{?with_test_keys}
+GOFLAGS="$GOFLAGS -tags withtestkeys"
+%endif
+
+# We have to build snapd first to prevent the build from
+# building various things from the tree without additional
+# set tags.
+%gobuild -o bin/snapd $GOFLAGS %{import_path}/cmd/snapd
+%gobuild -o bin/snap $GOFLAGS %{import_path}/cmd/snap
+%gobuild -o bin/snapctl $GOFLAGS %{import_path}/cmd/snapctl
+# build snap-exec and snap-update-ns completely static for base snaps
+CGO_ENABLED=0 %gobuild -o bin/snap-exec $GOFLAGS %{import_path}/cmd/snap-exec
+%gobuild -o bin/snap-update-ns --ldflags '-extldflags "-static"' $GOFLAGS %{import_path}/cmd/snap-update-ns
+
+# 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 $GOFLAGS %{import_path}/cmd/snap-seccomp
+
+# Build SELinux module
+pushd ./data/selinux
+make SHARE="%{_datadir}" TARGETS="snappy"
+popd
+
+# Build snap-confine
+pushd ./cmd
+# FIXME This is a hack to get rid of a patch we have to ship for the
+# Fedora package at the moment as /usr/lib/rpm/redhat/redhat-hardened-ld
+# accidentially adds -pie for static executables. See
+# https://bugzilla.redhat.com/show_bug.cgi?id=1343892 for a few more
+# details. To prevent this from happening we drop the linker
+# script and define our LDFLAGS manually for now.
+export LDFLAGS="-Wl,-z,relro -z now"
+autoreconf --force --install --verbose
+# selinux support is not yet available, for now just disable apparmor
+# FIXME: add --enable-caps-over-setuid as soon as possible (setuid discouraged!)
+%configure \
+ --disable-apparmor \
+ --libexecdir=%{_libexecdir}/snapd/ \
+ --with-snap-mount-dir=%{_sharedstatedir}/snapd/snap \
+ --with-merged-usr
+
+%make_build
+popd
+
+# Build systemd units
+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
+install -d -p %{buildroot}%{_mandir}/man1
+install -d -p %{buildroot}%{_unitdir}
+install -d -p %{buildroot}%{_sysconfdir}/profile.d
+install -d -p %{buildroot}%{_sysconfdir}/sysconfig
+install -d -p %{buildroot}%{_sharedstatedir}/snapd/assertions
+install -d -p %{buildroot}%{_sharedstatedir}/snapd/desktop/applications
+install -d -p %{buildroot}%{_sharedstatedir}/snapd/device
+install -d -p %{buildroot}%{_sharedstatedir}/snapd/hostfs
+install -d -p %{buildroot}%{_sharedstatedir}/snapd/mount
+install -d -p %{buildroot}%{_sharedstatedir}/snapd/seccomp/bpf
+install -d -p %{buildroot}%{_sharedstatedir}/snapd/snaps
+install -d -p %{buildroot}%{_sharedstatedir}/snapd/snap/bin
+install -d -p %{buildroot}%{_localstatedir}/snap
+install -d -p %{buildroot}%{_localstatedir}/cache/snapd
+install -d -p %{buildroot}%{_datadir}/selinux/devel/include/contrib
+install -d -p %{buildroot}%{_datadir}/selinux/packages
+
+# Install snap and snapd
+install -p -m 0755 bin/snap %{buildroot}%{_bindir}
+install -p -m 0755 bin/snap-exec %{buildroot}%{_libexecdir}/snapd
+install -p -m 0755 bin/snapctl %{buildroot}%{_bindir}/snapctl
+install -p -m 0755 bin/snapd %{buildroot}%{_libexecdir}/snapd
+install -p -m 0755 bin/snap-update-ns %{buildroot}%{_libexecdir}/snapd
+install -p -m 0755 bin/snap-seccomp %{buildroot}%{_libexecdir}/snapd
+
+# Install SELinux module
+install -p -m 0644 data/selinux/snappy.if %{buildroot}%{_datadir}/selinux/devel/include/contrib
+install -p -m 0644 data/selinux/snappy.pp.bz2 %{buildroot}%{_datadir}/selinux/packages
+
+# Install snap(1) man page
+bin/snap help --man > %{buildroot}%{_mandir}/man1/snap.1
+
+# Install the "info" data file with snapd version
+install -m 644 -D data/info %{buildroot}%{_libexecdir}/snapd/info
+
+# 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
+%make_install
+# Undo the 0000 permissions, they are restored in the files section
+chmod 0755 %{buildroot}%{_sharedstatedir}/snapd/void
+# We don't use AppArmor
+rm -rfv %{buildroot}%{_sysconfdir}/apparmor.d
+# ubuntu-core-launcher is dead
+rm -fv %{buildroot}%{_bindir}/ubuntu-core-launcher
+popd
+
+# Install all systemd units
+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}/snapd.snap-repair.*
+rm -fv %{buildroot}%{_unitdir}/snapd.core-fixup.*
+popd
+
+# Remove snappy core specific scripts
+rm %{buildroot}%{_libexecdir}/snapd/snapd.core-fixup.sh
+
+# Install environ-tweaking snippet
+pushd ./data/env
+%make_install
+popd
+
+# Disable re-exec by default
+echo 'SNAP_REEXEC=0' > %{buildroot}%{_sysconfdir}/sysconfig/snapd
+
+# Install snap management script
+install -pm 0755 packaging/fedora/snap-mgmt.sh %{buildroot}%{_libexecdir}/snapd/snap-mgmt
+
+# Create state.json file to be ghosted
+touch %{buildroot}%{_sharedstatedir}/snapd/state.json
+
+# source codes for building projects
+%if 0%{?with_devel}
+install -d -p %{buildroot}/%{gopath}/src/%{import_path}/
+echo "%%dir %%{gopath}/src/%%{import_path}/." >> devel.file-list
+# find all *.go but no *_test.go files and generate devel.file-list
+for file in $(find . -iname "*.go" -o -iname "*.s" \! -iname "*_test.go") ; do
+ echo "%%dir %%{gopath}/src/%%{import_path}/$(dirname $file)" >> devel.file-list
+ install -d -p %{buildroot}/%{gopath}/src/%{import_path}/$(dirname $file)
+ cp -pav $file %{buildroot}/%{gopath}/src/%{import_path}/$file
+ echo "%%{gopath}/src/%%{import_path}/$file" >> devel.file-list
+done
+%endif
+
+# testing files for this project
+%if 0%{?with_unit_test} && 0%{?with_devel}
+install -d -p %{buildroot}/%{gopath}/src/%{import_path}/
+# find all *_test.go files and generate unit-test.file-list
+for file in $(find . -iname "*_test.go"); do
+ echo "%%dir %%{gopath}/src/%%{import_path}/$(dirname $file)" >> devel.file-list
+ install -d -p %{buildroot}/%{gopath}/src/%{import_path}/$(dirname $file)
+ cp -pav $file %{buildroot}/%{gopath}/src/%{import_path}/$file
+ echo "%%{gopath}/src/%%{import_path}/$file" >> unit-test-devel.file-list
+done
+
+# Install additional testdata
+install -d %{buildroot}/%{gopath}/src/%{import_path}/cmd/snap/test-data/
+cp -pav cmd/snap/test-data/* %{buildroot}/%{gopath}/src/%{import_path}/cmd/snap/test-data/
+echo "%%{gopath}/src/%%{import_path}/cmd/snap/test-data" >> unit-test-devel.file-list
+%endif
+
+%if 0%{?with_devel}
+sort -u -o devel.file-list devel.file-list
+%endif
+
+%check
+# snapd tests
+%if 0%{?with_check} && 0%{?with_unit_test} && 0%{?with_devel}
+%if ! 0%{?with_bundled}
+export GOPATH=%{buildroot}/%{gopath}:%{gopath}
+%else
+export GOPATH=%{buildroot}/%{gopath}:$(pwd)/Godeps/_workspace:%{gopath}
+%endif
+%gotest %{import_path}/...
+%endif
+
+# snap-confine tests (these always run!)
+pushd ./cmd
+make check
+popd
+
+%files
+#define license tag if not already defined
+%{!?_licensedir:%global license %doc}
+%license COPYING
+%doc README.md docs/*
+%{_bindir}/snap
+%{_bindir}/snapctl
+%dir %{_libexecdir}/snapd
+%{_libexecdir}/snapd/snapd
+%{_libexecdir}/snapd/snap-exec
+%{_libexecdir}/snapd/info
+%{_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
+%{_unitdir}/snapd.autoimport.service
+%{_unitdir}/snapd.refresh.service
+%{_unitdir}/snapd.refresh.timer
+%config(noreplace) %{_sysconfdir}/sysconfig/snapd
+%dir %{_sharedstatedir}/snapd
+%dir %{_sharedstatedir}/snapd/assertions
+%dir %{_sharedstatedir}/snapd/desktop
+%dir %{_sharedstatedir}/snapd/desktop/applications
+%dir %{_sharedstatedir}/snapd/device
+%dir %{_sharedstatedir}/snapd/hostfs
+%dir %{_sharedstatedir}/snapd/mount
+%dir %{_sharedstatedir}/snapd/seccomp
+%dir %{_sharedstatedir}/snapd/seccomp/bpf
+%dir %{_sharedstatedir}/snapd/snaps
+%dir %{_sharedstatedir}/snapd/snap
+%dir /var/cache/snapd
+%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
+%license COPYING
+%dir %{_libexecdir}/snapd
+# For now, we can't use caps
+# FIXME: Switch to "%%attr(0755,root,root) %%caps(cap_sys_admin=pe)" asap!
+%attr(4755,root,root) %{_libexecdir}/snapd/snap-confine
+%{_libexecdir}/snapd/snap-discard-ns
+%{_libexecdir}/snapd/snap-seccomp
+%{_libexecdir}/snapd/snap-update-ns
+%{_libexecdir}/snapd/system-shutdown
+%{_mandir}/man1/snap-confine.1*
+%{_mandir}/man5/snap-discard-ns.5*
+%{_prefix}/lib/udev/snappy-app-dev
+%{_udevrulesdir}/80-snappy-assign.rules
+%attr(0000,root,root) %{_sharedstatedir}/snapd/void
+
+
+%files selinux
+%license data/selinux/COPYING
+%doc data/selinux/README.md
+%{_datadir}/selinux/packages/snappy.pp.bz2
+%{_datadir}/selinux/devel/include/contrib/snappy.if
+
+%if 0%{?with_devel}
+%files devel -f devel.file-list
+%license COPYING
+%doc README.md
+%dir %{gopath}/src/%{provider}.%{provider_tld}/%{project}
+%endif
+
+%if 0%{?with_unit_test} && 0%{?with_devel}
+%files unit-test-devel -f unit-test-devel.file-list
+%license COPYING
+%doc README.md
+%endif
+
+%post
+%systemd_post %{snappy_svcs}
+# If install, test if snapd socket and timer are enabled.
+# If enabled, then attempt to start them. This will silently fail
+# in chroots or other environments where services aren't expected
+# to be started.
+if [ $1 -eq 1 ] ; then
+ if systemctl -q is-enabled snapd.socket > /dev/null 2>&1 ; then
+ systemctl start snapd.socket > /dev/null 2>&1 || :
+ fi
+ if systemctl -q is-enabled snapd.refresh.timer > /dev/null 2>&1 ; then
+ systemctl start snapd.refresh.timer > /dev/null 2>&1 || :
+ fi
+fi
+
+%preun
+%systemd_preun %{snappy_svcs}
+
+# Remove all Snappy content if snapd is being fully uninstalled
+if [ $1 -eq 0 ]; then
+ %{_libexecdir}/snapd/snap-mgmt --purge || :
+fi
+
+
+%postun
+%systemd_postun_with_restart %{snappy_svcs}
+
+%pre selinux
+%selinux_relabel_pre
+
+%post selinux
+%selinux_modules_install %{_datadir}/selinux/packages/snappy.pp.bz2
+%selinux_relabel_post
+
+%postun selinux
+%selinux_modules_uninstall snappy
+if [ $1 -eq 0 ]; then
+ %selinux_relabel_post
+fi
+
+
+%changelog
+* Thu Nov 09 2017 Michael Vogt
+- New upstream release 2.29.3
+ - daemon: cherry-picked /v2/logs fixes
+ - cmd/snap-confine: Respect biarch nature of libdirs
+ - cmd/snap-confine: Ensure snap-confine is allowed to access os-
+ release
+ - interfaces: fix udev tagging for hooks
+ - cmd: fix re-exec bug with classic confinement for host snapd
+ - tests: disable xdg-open-compat test
+ - cmd/snap-confine: add slave PTYs and let devpts newinstance
+ perform mediation
+ - interfaces/many: misc policy updates for browser-support, cups-
+ control and network-status
+ - interfaces/raw-usb: match on SUBSYSTEM, not SUBSYSTEMS
+ - tests: fix security-device-cgroup* tests on devices with
+ framebuffer
+
+* Fri Nov 03 2017 Michael Vogt
+- New upstream release 2.29.2
+ - snapctl: disable stop/start/restart (2.29)
+ - cmd/snap-update-ns: fix collection of changes made
+
+* Fri Nov 03 2017 Michael Vogt
+- New upstream release 2.29.1
+ - interfaces: fix incorrect signature of ofono DBusPermanentSlot
+ - interfaces/serial-port: udev tag plugged slots that have just
+ 'path' via KERNEL
+ - interfaces/hidraw: udev tag plugged slots that have just 'path'
+ via KERNEL
+ - interfaces/uhid: unconditionally add existing uhid device to the
+ device cgroup
+ - cmd/snap-update-ns: fix mount rules for font sharing
+ - tests: disable refresh-undo test on trusty for now
+ - tests: use `snap change --last=install` in snapd-reexec test
+ - Revert " wrappers: fail install if exec-line cannot be re-written
+ - interfaces: don't udev tag devmode or classic snaps
+ - many: make ignore-validation sticky and send the flag with refresh
+ requests
+
+* Mon Oct 30 2017 Michael Vogt
+- New upstream release 2.29
+ - interfaces/many: miscellaneous updates based on feedback from the
+ field
+ - snap-confine: allow reading uevents from any where in /sys
+ - spread: add bionic beaver
+ - debian: make packaging/ubuntu-14.04/copyright a real file again
+ - tests: cherry pick the fix for services test into 2.29
+ - cmd/snap-update-ns: initialize logger
+ - hooks/configure: queue service restarts
+ - snap-{confine,seccomp}: make @unrestricted fully unrestricted
+ - interfaces: clean system apparmor cache on core device
+ - debian: do not build static snap-exec on powerpc
+ - snap-confine: increase sanity_timeout to 6s
+ - snapctl: cherry pick service commands changes
+ - cmd/snap: tell translators about arg names and descs req's
+ - systemd: run all mount units before snapd.service to avoid race
+ - store: add a test to show auth failures are forwarded by doRequest
+ - daemon: convert ErrInvalidCredentials to a 401 Unauthorized error.
+ - store: forward on INVALID_CREDENTIALS error as
+ ErrInvalidCredentials
+ - daemon: generate a forbidden response message if polkit dialog is
+ dismissed
+ - daemon: Allow Polkit authorization to cancel changes.
+ - travis: switch to container based test runs
+ - interfaces: reduce duplicated code in interface tests mocks
+ - tests: improve revert related testing
+ - interfaces: sanitize plugs and slots early in ReadInfo
+ - store: add download caching
+ - preserve TMPDIR and HOSTALIASES across snap-confine invocation
+ - snap-confine: init all arrays with `= {0,}`
+ - tests: adding test for network-manager interface
+ - interfaces/mount: don't generate legacy per-hook/per-app mount
+ profiles
+ - snap: introduce structured epochs
+ - tests: fix interfaces-cups-control test for cups-2.2.5
+ - snap-confine: cleanup incorrectly created nvidia udev tags
+ - cmd/snap-confine: update valid security tag regexp
+ - cmd/libsnap: enable two stranded tests
+ - cmd,packaging: enable apparmor on openSUSE
+ - overlord/ifacestate: refresh all security backends on startup
+ - interfaces/dbus: drop unneeded check for
+ release.ReleaseInfo.ForceDevMode
+ - dbus: ensure io.snapcraft.Launcher.service is created on re-
+ exec
+ - overlord/auth: continue for now supporting UBUNTU_STORE_ID if the
+ model is generic-classic
+ - snap-confine: add support for handling /dev/nvidia-modeset
+ - interfaces/network-control: remove incorrect rules for tun
+ - spread: allow setting SPREAD_DEBUG_EACH=0 to disable debug-each
+ section
+ - packaging: remove .mnt files on removal
+ - tests: fix econnreset scenario when the iptables rule was not
+ created
+ - tests: add test for lxd interface
+ - run-checks: use nakedret static checker to check for naked
+ returns on long functions
+ - progress: be more flexible in testing ansimeter
+ - interfaces: fix udev rules for tun
+ - many: implement our own ANSI-escape-using progress indicator
+ - snap-exec: update tests to follow main_test pattern
+ - snap: support "command: foo $ENV_STRING"
+ - packaging: update nvidia configure options
+ - snap: add new `snap pack` and use in tests
+ - cmd: correctly name the "Ubuntu" and "Arch" NVIDIA methods
+ - cmd: add autogen case for solus
+ - tests: do not use http://canihazip.com/ which appears to be down
+ - hooks: commands for controlling own services from snapctl
+ - snap: refactor cmdGet.Execute()
+ - interfaces/mount: make Change.Perform testable and test it
+ - interfaces/mount,cmd/snap-update-ns: move change code
+ - snap-confine: is_running_on_classic_distribution() looks into os-
+ release
+ - interfaces: misc updates for default, browser-support, home and
+ system-observe
+ - interfaces: deny lttng by default
+ - interfaces/lxd: lxd slot implementation can also be an app snap
+ - release,cmd,dirs: Redo the distro checks to take into account
+ distribution families
+ - cmd/snap: completion for alias and unalias
+ - snap-confine: add new SC_CLEANUP and use it
+ - snap: refrain from running filepath.Base on random strings
+ - cmd/snap-confine: put processes into freezer hierarchy
+ - wrappers: fail install if exec-line cannot be re-written
+ - cmd/snap-seccomp,osutil: make user/group lookup functions public
+ - snapstate: deal with snap user data in the /root/ directory
+ - interfaces: Enhance full-confinement support for biarch
+ distributions
+ - snap-confine: Only attempt to copy/mount NVIDIA libs when NVIDIA
+ is used
+ - packaging/fedora: Add Fedora 26, 27, and Rawhide symlinks
+ - overlord/snapstate: prefer a smaller corner case for doing the
+ wrong thing
+ - cmd/snap-repair: set user agent for snap-repair http requests
+ - packaging: bring down the delta between 14.04 and 16.04
+ - snap-confine: Ensure lib64 biarch directory is respected
+ - snap-confine: update apparmor rules for fedora based base snaps
+ - tests: Increase SNAPD_CONFIGURE_HOOK_TIMEOUT to 3 minutes to
+ install real snaps
+ - daemon: use client.Snap instead of map[string]interface{} for
+ snaps.
+ - hooks: rename refresh hook to post-refresh
+ - git: make the .gitingore file a bit more targeted
+ - interfaces/opengl: don't udev tag nvidia devices and use snap-
+ confine instead
+ - cmd/snap-{confine,update-ns}: apply mount profiles using snap-
+ update-ns
+ - cmd: update "make hack"
+ - interfaces/system-observe: allow clients to enumerate DBus
+ connection names
+ - snap-repair: implement `snap-repair {list,show}`
+ - dirs,interfaces: create snap-confine.d on demand when re-executing
+ - snap-confine: fix base snaps on core
+ - cmd/snap-repair: fix tests when running as root
+ - interfaces: add Connection type
+ - cmd/snap-repair: skip disabled repairs
+ - cmd/snap-repair: prefer leaking unmanaged fds on test failure over
+ closing random ones
+ - snap-repair: make `repair` binary available for repair scripts
+ - snap-repair: fix missing Close() in TestStatusHappy
+ - cmd/snap-confine,packaging: import snapd-generated policy
+ - cmd/snap: return empty document if snap has no configuration
+ - snap-seccomp: run secondary-arch tests via gcc-multilib
+ - snap: implement `snap {repair,repairs}` and pass-through to snap-
+ repair
+ - interfaces/builtin: allow receiving dbus messages
+ - snap-repair: implement `snap-repair {done,skip,retry}`
+ - data/completion: small tweak to snap completion snippet
+ - dirs: fix classic support detection
+ - cmd/snap-repair: integrate root public keys for repairs
+ - tests: fix ubuntu core services
+ - tests: add new test that checks that the compat snapd-xdg-open
+ works
+ - snap-confine: improve error message if core/u-core cannot be found
+ - tests: only run tests/regression/nmcli on amd64
+ - interfaces: mount host system fonts in desktop interface
+ - interfaces: enable partial apparmor support
+ - snapstate: auto-install missing base snaps
+ - spread: work around temporary packaging issue in debian sid
+ - asserts,cmd/snap-repair: introduce a mandatory summary for repairs
+ - asserts,cmd/snap-repair: represent RepairID internally as an int
+ - tests: test the real "xdg-open" from the core snap
+ - many: implement fetching sections and package names periodically.
+ - interfaces/network: allow using netcat as client
+ - snap-seccomp, osutil: use osutil.AtomicFile in snap-seccomp
+ - snap-seccomp: skip mknod syscall on arm64
+ - tests: add trivial canonical-livepatch test
+ - tests: add test that ensures that all core services are working
+ - many: add logger.MockLogger() and use it in the tests
+ - snap-repair: fix test failure in TestRepairHitsTimeout
+ - asserts: add empty values check in HeadersFromPrimaryKey
+ - daemon: remove unused installSnap var in test
+ - daemon: reach for Overlord.Loop less thanks to overlord.Mock
+ - snap-seccomp: manually resolve socket() call in tests
+ - tests: change regex used to validate installed ubuntu core snap
+ - cmd/snapctl: allow snapctl -h without a context (regression fix).
+ - many: use snapcore/snapd/i18n instead of i18n/dumb
+ - many: introduce asserts.NotFoundError replacing both ErrNotFound
+ and store.AssertionNotFoundError
+ - packaging: don't include any marcos in comments
+ - overlord: use overlord.Mock in more tests, make sure we check the
+ outcome of Settle
+ - tests: try to fix staging tests
+ - store: simplify api base url config
+ - systemd: add systemd.MockJournalctl()
+ - many: provide systemd.MockSystemctl() helper
+ - tests: improve the listing test to not fail for e.g. 2.28~rc2
+ - snapstate: give snapmgrTestSuite.settle() more time to settle
+ - tests: fix regex to check core version on snap list
+ - debian: update trusted account-keys check on 14.04 packaging
+ - interfaces: add udev netlink support to hardware-observe
+ - overlord: introduce Mock which enables to use Overlord.Settle for
+ settle in many more places
+ - snap-repair: execute the repair and capture logs/status
+ - tests: run the tests/unit/go everywhere
+ - daemon, snapstate: move ensureCore from daemon/api.go into
+ snapstate.go
+ - cmd/snap: get keys or root document
+ - spread.yaml: turn suse to manual given that it's breaking master
+ - many: configure store from state, reconfigure store at runtime
+ - osutil: AtomicWriter (an io.Writer), and io.Reader versions of
+ AtomicWrite*
+ - tests: check for negative syscalls in runBpf() and skip those
+ tests
+ - docs: use abolute path in PULL_REQUEST_TEMPLATE.md
+ - store: move device auth endpoint uris to config (#3831)
+
+* 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
+ - hooks: do not error when hook handler is not registered
+ - interfaces/alsa,pulseaudio: allow read on udev data for sound
+ - interfaces/optical-drive: read access to udev data for /dev/scd*
+ - interfaces/browser-support: read on /proc/vmstat and misc udev
+ data
+
+* Thu Aug 24 2017 Michael Vogt
+- New upstream release 2.27.4
+ - snap-seccomp: add secondary arch for unrestricted snaps as well
+
+* Fri Aug 18 2017 Michael Vogt
+- New upstream release 2.27.3
+ - 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
+ changes
+ - interfaces: don't crash if content slot has no attributes
+ - debian: do not build with -buildmode=pie on i386
+ - interfaces: backport broadcom-asic-control interface
+ - interfaces: allow /usr/bin/xdg-open in unity7
+ - 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
+ - tests: fix test failure on 14.04 due to old version of
+ flock
+ - updates for unity7/x11, browser-support, network-control,
+ 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
+
+* 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
+ - interfaces: add password-manager-service implicit classic interface
+ - interfaces/greengrass-support: adjust accesses now that have working
+ snap
+ - interfaces/many, cmd/snap-confine: miscellaneous policy updates
+ - interfaces/unity7: allow receiving media key events in (at least)
+ gnome-shell
+ - cmd: fix re-exec bug when starting from snapd 2.21
+ - tests: restore interfaces-account-control properly
+ - cmd: fix tests that assume /snap mount
+ - cmd: mark arch as non-reexecing distro
+ - snap-confine: don't share /etc/nsswitch from host
+ - store: talk to api.snapcraft.io for purchases
+ - hooks: support for install and remove hooks
+ - packaging: fix Fedora support
+ - tests: add bluetooth-control interface test
+ - store: talk to api.snapcraft.io for assertions
+ - tests: remove snapd before building from branch
+ - tests: add avahi-observe interface test
+ - store: orders API now checks if customer is ready
+ - cmd/snap: snap find only searches stable
+ - interfaces: updates default, mir, optical-observe, system-observe,
+ screen-inhibit-control and unity7
+ - tests: speedup prepare statement part 1
+ - store: do not send empty refresh requests
+ - asserts: fix error handling in snap-developer consistency check
+ - systemd: add explicit sync to snapd.core-fixup.sh
+ - snapd: generate snap cookies on startup
+ - cmd,client,daemon: expose "force devmode" in sysinfo
+ - many: introduce and use strutil.ListContains and also
+ strutil.SortedListContains
+ - assserts,overlord/assertstate: test we don't accept chains of
+ assertions founded on a self-signed key coming externally
+ - interfaces: enable access to bridge settings
+ - interfaces: fix copy-pasted iio vs io in io-ports-control
+ - cmd/snap-confine: various small fixes and tweaks to seccomp
+ support code
+ - interfaces: bring back seccomp argument filtering
+ - systemd, osutil: rework systemd logs in preparation for services
+ commands
+ - tests: store /etc/systemd/system/snap-*core*.mount in snapd-
+ state.tar.gz
+ - tests: shellcheck improvements for tests/main tasks - first set of
+ tests
+ - cmd/snap: `--last` for abort and watch, and aliases
+ (search→find, change→tasks)
+ - tests: shellcheck improvements for tests/lib scripts
+ - tests: create ramdisk if it's not present
+ - tests: shellcheck improvements for nightly upgrade and regressions
+ tests
+ - snapd: fix for snapctl get panic on null config values.
+ - tests: fix for rng-tools service not restarting
+ - systemd: add snapd.core-fixup.service unit
+ - cmd: avoid using current symlink in InternalToolPath
+ - tests: fix timeout issue for test refresh core with hanging …
+ - intefaces: control bridged vlan/ppoe-tagged traffic
+ - cmd/snap: include snap type in notes
+ - overlord/state: Abort() only visits each task once
+ - tests: extend find-private test to cover more cases
+ - snap-seccomp: skip socket() tests on systems that use socketcall()
+ instead of socket()
+ - many: support snap title as localized/title-cased name
+ - snap-seccomp: deal with mknod on aarch64 in the seccomp tests
+ - interfaces: put base policy fragments inside each interface
+ - asserts: introduce NewDecoderWithTypeMaxBodySize
+ - tests: fix snapd-notify when it takes more time to restart
+ - snap-seccomp: fix snap-seccomp tests in artful
+ - tests: fix for create-key task to avoid rng-tools service ramains
+ alive
+ - snap-seccomp: make sure snap-seccomp writes the bpf file
+ atomically
+ - tests: do not disable ipv6 on core systems
+ - arch: the kernel architecture name is armv7l instead of armv7
+ - snap-confine: ensure snap-confine waits some seconds for seccomp
+ security profiles
+ - tests: shellcheck improvements for tests/nested tasks
+ - wrappers: add SyslogIdentifier to the service unit files.
+ - tests: shellcheck improvements for unit tasks
+ - asserts: implement FindManyTrusted as well
+ - asserts: open up and optimize Encoder to help avoiding unnecessary
+ copying
+ - interfaces: simplify snap-confine by just loading pre-generated
+ bpf code
+ - tests: restart rng-tools services after few seconds
+ - interfaces, tests: add mising dbus abstraction to system-observe
+ and extend spread test
+ - store: change main store host to api.snapcraft.io
+ - overlord/cmdstate: new package for running commands as tasks.
+ - spread: help libapt resolve installing libudev-dev
+ - tests: show the IP from .travis.yaml
+ - tests/main: use pkgdb function in more test cases
+ - cmd,daemon: add debug command for displaying the base policy
+ - tests: prevent quoting error on opensuse
+ - tests: fix nightly suite
+ - tests: add linode-sru backend
+ - snap-confine: validate SNAP_NAME against security tag
+ - tests: fix ipv6 disable for ubuntu-core
+ - tests: extend core-revert test to cover bluez issues
+ - interfaces/greengrass-support: add support for Amazon Greengrass
+ as a snap
+ - asserts: support timestamp and optional disabled header on repair
+ - tests: reboot after upgrading to snapd on the -proposed pocket
+ - many: fix test cases to work with different DistroLibExecDir
+ - tests: reenable help test on ubuntu and debian systems
+ - packaging/{opensuse,fedora}: allow package build with testkeys
+ included
+ - tests/lib: generalize RPM build support
+ - interfaces/builtin: sync connected slot and permanent slot snippet
+ - tests: fix snap create-key by restarting automatically rng-tools
+ - many: switch to use http numeric statuses as agreed
+ - debian: add missing Type=notify in 14.04 packaging
+ - tests: mark interfaces-openvswitch as manual due to prepare errors
+ - debian: unify built_using between the 14.04 and 16.04 packaging
+ branch
+ - tests: pull from urandom when real entropy is not enough
+ - tests/main/manpages: install missing man package
+ - tests: add refresh --time output check
+ - debian: add missing "make -C data/systemd clean"
+ - tests: fix for upgrade test when it is repeated
+ - tests/main: use dir abstraction in a few more test cases
+ - tests/main: check for confinement in a few more interface tests
+ - spread: add fedora snap bin dir to global PATH
+ - tests: check that locale-control is not present on core
+ - many: snapctl outside hooks
+ - tests: add whoami check
+ - interfaces: compose the base declaration from interfaces
+ - tests: fix spread flaky tests linode
+ - tests,packaging: add package build support for openSUSE
+ - many: slight improvement of some snap error messaging
+ - errtracker: Include /etc/apparmor.d/usr.lib.snap-confine md5sum in
+ err reports
+ - tests: fix for the test postrm-purge
+ - tests: restoring the /etc/environment and service units config for
+ each test
+ - daemon: make snapd a "Type=notify" daemon and notify when startup
+ is done
+ - cmd/snap-confine: add support for --base snap
+ - many: derive implicit slots from interface meta-data
+ - tests: add core revert test
+ - tests,packaging: add package build support for Fedora for our
+ spread setup
+ - interfaces: move base declaration to the policy sub-package
+ - tests: fix for snapd-reexec test cheking for restart info on debug
+ log
+ - tests: show available entropy on error
+ - tests: clean journalctl logs on trusty
+ - tests: fix econnreset on staging
+ - tests: modify core before calling set
+ - tests: add snap-confine privilege test
+ - tests: add staging snap-id
+ - interfaces/builtin: silence ptrace denial for network-manager
+ - tests: add alsa interface spread test
+ - tests: prefer ipv4 over ipv6
+ - tests: fix for econnreset test checking that the download already
+ started
+ - httputil,store: extract retry code to httputil, reorg usages
+ - errtracker: report if snapd did re-execute itself
+ - errtracker: include bits of snap-confine apparmor profile
+ - tests: take into account staging snap-ids for snap-info
+ - cmd: add stub new snap-repair command and add timer
+ - many: stop "snap refresh $x --channel invalid" from working
+ - interfaces: revert "interfaces: re-add reverted ioctl and quotactl
+ - snapstate: consider connect/disconnect tasks in
+ CheckChangeConflict.
+ - interfaces: disable "mknod |N" in the default seccomp template
+ again
+ - interfaces,overlord/ifacestate: make sure installing slots after
+ plugs works similarly to plugs after slots
+ - interfaces/seccomp: add bind() syscall for forced-devmode systems
+ - packaging/fedora: Sync packaging from Fedora Dist-Git
+ - tests: move static and unit tests to spread task
+ - many: error types should be called FooError, not ErrFoo.
+ - partition: add directory sync to the save uboot.env file code
+ - cmd: test everything (100% coverage \o/)
+ - many: make shell scripts shellcheck-clean
+ - tests: remove additional setup for docker on core
+ - interfaces: add summary to each interface
+ - many: remove interface meta-data from list of connections
+ - logger (& many more, to accommodate): drop explicit syslog.
+ - packaging: import packaging bits for opensuse
+ - snapstate,many: implement snap install --unaliased
+ - tests/lib: abstract build dependency installation a bit more
+ - interfaces, osutil: move flock code from interfaces/mount to
+ osutil
+ - cmd: auto import assertions only from ext4,vfat file systems
+ - many: refactor in preparation for 'snap start'
+ - overlord/snapstate: have an explicit code path last-refresh
+ unset/zero => immediately refresh try
+ - tests: fixes for executions using the staging store
+ - tests: use pollinate to seed the rng
+ - cmd/snap,tests: show the sha3-384 of the snap for snap info
+ --verbose SNAP-FILE
+ - asserts: simplify and adjust repair assertion definition
+ - cmd/snap,tests: show the snap id if available in snap info
+ - daemon,overlord/auth: store from model assertion wins
+ - cmd/snap,tests/main: add confinement switch instead of spread
+ system blacklisting
+ - many: cleanup MockCommands and don't leave a process around after
+ hookstate tests
+ - tests: update listing test to the core version number schema
+ - interfaces: allow snaps to use the timedatectl utility
+ - packaging: Add Fedora packaging files
+ - tests/libs: add distro_auto_remove_packages function
+ - cmd/snap: correct devmode note for anomalous state
+ - tests/main/snap-info: use proper pkgdb functions to install distro
+ packages
+ - tests/lib: use mktemp instead of tempfile to work cross-distro
+ - tests: abstract common dirs which differ on distributions
+ - many: model and expose interface meta-data.
+ - overlord: make config defaults from gadget work also at first boot
+ - interfaces/log-observe: allow using journalctl from hostfs for
+ classic distro
+ - partition,snap: add support for android boot
+ - errtracker: small simplification around readMachineID
+ - snap-confine: move rm_rf_tmp to test-utils.
+ - tests/lib: introduce pkgdb helper library
+ - errtracker: try multiple paths to read machine-id
+ - overlord/hooks: make sure only one hook for given snap is executed
+ at a time.
+ - cmd/snap-confine: use SNAP_MOUNT_DIR to setup /snap inside the
+ confinement env
+ - tests: bump kill-timeout and remove quiet call on build
+ - tests/lib/snaps: add a test store snap with a passthrough
+ configure hook
+ - daemon: teach the daemon to wait on active connections when
+ shutting down
+ - tests: remove unit tests task
+ - tests/main/completion: source from /usr/share/bash-completion
+ - assertions: add "repair" assertion
+ - interfaces/seccomp: document Backend.NewSpecification
+ - wrappers: make StartSnapServices cleanup any services that were
+ added if a later one fails
+ - overlord/snapstate: avoid creating command aliases for daemons
+ - vendor: remove unused packages
+ - vendor,partition: fix panics from uenv
+ - cmd,interfaces/mount: run snap-update-ns and snap-discard-ns from
+ core if possible
+ - daemon: do not allow to install ubuntu-core anymore
+ - wrappers: service start/stop were inconsistent
+ - tests: fix failing tests (snap core version, syslog changes)
+ - cmd/snap-update-ns: add actual implementation
+ - tests: improve entropy also for ubuntu
+ - cmd/snap-confine: use /etc/ssl from the core snap
+ - wrappers: don't convert between []byte and string needlessly.
+ - hooks: default timeout
+ - overlord/snapstate: Enable() was ignoring the flags from the
+ snap's state, resulting in losing "devmode" on disable/enable.
+ - difs,interfaces/mount: add support for locking namespaces
+ - interfaces/mount: keep track of kept mount entries
+ - tests/main: move a bunch of greps over to MATCH
+ - interfaces/builtin: make all interfaces private
+ - interfaces/mount: spell unmount correctly
+ - tests: allow 16-X.Y.Z version of core snap
+ - the timezone_control interface only allows changing /etc/timezone
+ and /etc/writable/timezone. systemd-timedated also updated the
+ link of /etc/localtime and /etc/writable/localtime ... allow
+ access to this file too
+ - cmd/snap-confine: aggregate operations holding global lock
+ - 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)
+
+* Sun May 21 2017 Neal Gompa - 2.26.3-2
+- Fix error in script for removing Snappy content (RH#1444422)
+- Adjust changelog bug references to be specific on origin
+
+* Wed May 17 2017 Neal Gompa - 2.26.3-1
+- Update to snapd 2.26.3
+- Drop merged and unused patches
+- Cover more Snappy content for proper erasure on final uninstall (RH#1444422)
+- Add temporary fix to ensure generated seccomp profiles don't break snapctl
+
+* Mon May 01 2017 Neal Gompa - 2.25-1
+- Update to snapd 2.25
+- Ensure all Snappy content is gone on final uninstall (RH#1444422)
+
+* Tue Apr 11 2017 Neal Gompa - 2.24-1
+- Update to snapd 2.24
+- Drop merged patches
+- Install snap bash completion and snapd info file
+
+* Wed Apr 05 2017 Neal Gompa - 2.23.6-4
+- Test if snapd socket and timer enabled and start them if enabled on install
+
+* Sat Apr 01 2017 Neal Gompa - 2.23.6-3
+- Fix profile.d generation so that vars aren't expanded in package build
+
+* Fri Mar 31 2017 Neal Gompa - 2.23.6-2
+- Fix the overlapping file conflicts between snapd and snap-confine
+- Rework package descriptions slightly
+
+* Thu Mar 30 2017 Neal Gompa - 2.23.6-1
+- Rebase to snapd 2.23.6
+- Rediff patches
+- Re-enable seccomp
+- Fix building snap-confine on 32-bit arches
+- Set ExclusiveArch based on upstream supported arch list
+
+* Wed Mar 29 2017 Neal Gompa - 2.23.5-1
+- Rebase to snapd 2.23.5
+- Disable seccomp temporarily avoid snap-confine bugs (LP#1674193)
+- Use vendorized build for non-Fedora
+
+* Mon Mar 13 2017 Neal Gompa - 2.23.1-1
+- Rebase to snapd 2.23.1
+- Add support for vendored tarball for non-Fedora targets
+- Use merged in SELinux policy module
+
+* Sat Feb 11 2017 Fedora Release Engineering - 2.16-2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild
+
+* Wed Oct 19 2016 Zygmunt Krynicki - 2.16-1
+- New upstream release
+
+* Tue Oct 18 2016 Neal Gompa - 2.14-2
+- Add SELinux policy module subpackage
+
+* Tue Aug 30 2016 Zygmunt Krynicki - 2.14-1
+- New upstream release
+
+* Tue Aug 23 2016 Zygmunt Krynicki - 2.13-1
+- New upstream release
+
+* Thu Aug 18 2016 Zygmunt Krynicki - 2.12-2
+- Correct license identifier
+
+* Thu Aug 18 2016 Zygmunt Krynicki - 2.12-1
+- New upstream release
+
+* Thu Aug 18 2016 Zygmunt Krynicki - 2.11-8
+- Add %%dir entries for various snapd directories
+- Tweak Source0 URL
+
+* Tue Aug 16 2016 Zygmunt Krynicki - 2.11-7
+- Disable snapd re-exec feature by default
+
+* Tue Aug 16 2016 Zygmunt Krynicki - 2.11-6
+- Don't auto-start snapd.socket and snapd.refresh.timer
+
+* Tue Aug 16 2016 Zygmunt Krynicki - 2.11-5
+- Don't touch snapd state on removal
+
+* Tue Aug 16 2016 Zygmunt Krynicki - 2.11-4
+- Use ExecStartPre to load squashfs.ko before snapd starts
+- Use dedicated systemd units for Fedora
+
+* Tue Aug 16 2016 Zygmunt Krynicki - 2.11-3
+- Remove systemd preset (will be requested separately according to distribution
+ standards).
+
+* Tue Aug 16 2016 Zygmunt Krynicki - 2.11-2
+- Use Requires: kmod(squashfs.ko) instead of Requires: kernel-modules
+
+* Tue Aug 16 2016 Zygmunt Krynicki - 2.11-1
+- New upstream release
+- Move private executables to /usr/libexec/snapd/
+
+* Fri Jun 24 2016 Zygmunt Krynicki - 2.0.9-2
+- Depend on kernel-modules to ensure that squashfs can be loaded. Load it afer
+ installing the package. This hopefully fixes
+ https://github.com/zyga/snapcore-fedora/issues/2
+
+* Fri Jun 17 2016 Zygmunt Krynicki - 2.0.9
+- New upstream release
+ https://github.com/snapcore/snapd/releases/tag/2.0.9
+
+* Tue Jun 14 2016 Zygmunt Krynicki - 2.0.8.1
+- New upstream release
+
+* Fri Jun 10 2016 Zygmunt Krynicki - 2.0.8
+- First package for Fedora
diff -Nru snapd-2.28.5/packaging/fedora-26/snap-mgmt.sh snapd-2.29.3/packaging/fedora-26/snap-mgmt.sh
--- snapd-2.28.5/packaging/fedora-26/snap-mgmt.sh 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/packaging/fedora-26/snap-mgmt.sh 2017-10-23 06:17:27.000000000 +0000
@@ -0,0 +1,137 @@
+#!/bin/bash
+
+# Overlord management of snapd for package manager actions.
+# Implements actions that would be invoked in %pre(un) actions for snapd.
+# Derived from the snapd.postrm scriptlet used in the Ubuntu packaging for
+# snapd.
+
+set -e
+
+SNAP_MOUNT_DIR="/var/lib/snapd/snap"
+
+show_help() {
+ exec cat <<'EOF'
+Usage: snap-mgmt.sh [OPTIONS]
+
+A simple script to cleanup snap installations.
+
+optional arguments:
+ --help Show this help message and exit
+ --snap-mount-dir= Provide a path to be used as $SNAP_MOUNT_DIR
+ --purge Purge all data from $SNAP_MOUNT_DIR
+EOF
+}
+
+SNAP_UNIT_PREFIX="$(systemd-escape -p ${SNAP_MOUNT_DIR})"
+
+systemctl_stop() {
+ unit="$1"
+ if systemctl is-active -q "$unit"; then
+ echo "Stoping $unit"
+ systemctl stop -q "$unit" || true
+ fi
+}
+
+purge() {
+ # undo any bind mount to ${SNAP_MOUNT_DIR} that resulted from LP:#1668659
+ if grep -q "${SNAP_MOUNT_DIR} ${SNAP_MOUNT_DIR}" /proc/self/mountinfo; then
+ umount -l "${SNAP_MOUNT_DIR}" || true
+ fi
+
+ mounts=$(systemctl list-unit-files --full | grep "^${SNAP_UNIT_PREFIX}[-.].*\.mount" | cut -f1 -d ' ')
+ services=$(systemctl list-unit-files --full | grep "^${SNAP_UNIT_PREFIX}[-.].*\.service" | cut -f1 -d ' ')
+ for unit in $services $mounts; do
+ # ensure its really a snap mount unit or systemd unit
+ if ! grep -q 'What=/var/lib/snapd/snaps/' "/etc/systemd/system/$unit" && ! grep -q 'X-Snappy=yes' "/etc/systemd/system/$unit"; then
+ echo "Skipping non-snapd systemd unit $unit"
+ continue
+ fi
+
+ echo "Stopping $unit"
+ systemctl_stop "$unit"
+
+ # if it is a mount unit, we can find the snap name in the mount
+ # unit (we just ignore unit files)
+ snap=$(grep "Where=${SNAP_MOUNT_DIR}/" "/etc/systemd/system/$unit"|cut -f3 -d/)
+ rev=$(grep "Where=${SNAP_MOUNT_DIR}/" "/etc/systemd/system/$unit"|cut -f4 -d/)
+ if [ -n "$snap" ]; then
+ echo "Removing snap $snap"
+ # aliases
+ if [ -d "${SNAP_MOUNT_DIR}/bin" ]; then
+ find "${SNAP_MOUNT_DIR}/bin" -maxdepth 1 -lname "$snap" -delete
+ find "${SNAP_MOUNT_DIR}/bin" -maxdepth 1 -lname "$snap.*" -delete
+ fi
+ # generated binaries
+ rm -f "${SNAP_MOUNT_DIR}/bin/$snap"
+ rm -f "${SNAP_MOUNT_DIR}/bin/$snap".*
+ # snap mount dir
+ umount -l "${SNAP_MOUNT_DIR}/$snap/$rev" 2> /dev/null || true
+ rm -rf "${SNAP_MOUNT_DIR:?}/$snap/$rev"
+ rm -f "${SNAP_MOUNT_DIR}/$snap/current"
+ # snap data dir
+ rm -rf "/var/snap/$snap/$rev"
+ rm -rf "/var/snap/$snap/common"
+ rm -f "/var/snap/$snap/current"
+ # opportunistic remove (may fail if there are still revisions left)
+ for d in "${SNAP_MOUNT_DIR}/$snap" "/var/snap/$snap"; do
+ if [ -d "$d" ]; then
+ rmdir --ignore-fail-on-non-empty "$d"
+ fi
+ done
+ fi
+
+ echo "Removing $unit"
+ rm -f "/etc/systemd/system/$unit"
+ rm -f "/etc/systemd/system/multi-user.target.wants/$unit"
+ done
+
+ echo "Discarding preserved snap namespaces"
+ # opportunistic as those might not be actually mounted
+ for mnt in /run/snapd/ns/*.mnt; do
+ umount -l "$mnt" || true
+ rm -f "$mnt"
+ done
+ for fstab in /run/snapd/ns/*.fstab; do
+ rm -f "$fstab"
+ done
+ umount -l /run/snapd/ns/ || true
+
+
+ echo "Removing downloaded snaps"
+ rm -rf /var/lib/snapd/snaps/*
+
+ echo "Final directory cleanup"
+ rm -rf "${SNAP_MOUNT_DIR}"
+ rm -rf /var/snap
+
+ echo "Removing leftover snap shared state data"
+ rm -rf /var/lib/snapd/desktop/applications/*
+ rm -rf /var/lib/snapd/seccomp/bpf/*
+ rm -rf /var/lib/snapd/device/*
+ rm -rf /var/lib/snapd/assertions/*
+
+ echo "Removing snapd catalog cache"
+ rm -f /var/cache/snapd/*
+}
+
+while [ -n "$1" ]; do
+ case "$1" in
+ --help)
+ show_help
+ exit
+ ;;
+ --snap-mount-dir=*)
+ SNAP_MOUNT_DIR=${1#*=}
+ SNAP_UNIT_PREFIX=$(systemd-escape -p "$SNAP_MOUNT_DIR")
+ shift
+ ;;
+ --purge)
+ purge
+ shift
+ ;;
+ *)
+ echo "Unknown command: $1"
+ exit 1
+ ;;
+ esac
+done
diff -Nru snapd-2.28.5/packaging/fedora-27/snapd.spec snapd-2.29.3/packaging/fedora-27/snapd.spec
--- snapd-2.28.5/packaging/fedora-27/snapd.spec 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/packaging/fedora-27/snapd.spec 2017-11-09 18:16:16.000000000 +0000
@@ -0,0 +1,1630 @@
+# With Fedora, nothing is bundled. For everything else, bundling is used.
+# To use bundled stuff, use "--with vendorized" on rpmbuild
+%if 0%{?fedora}
+%bcond_with vendorized
+%else
+%bcond_without vendorized
+%endif
+
+# A switch to allow building the package with support for testkeys which
+# are used for the spread test suite of snapd.
+%bcond_with testkeys
+
+%global with_devel 1
+%global with_debug 1
+%global with_check 0
+%global with_unit_test 0
+%global with_test_keys 0
+
+# For the moment, we don't support all golang arches...
+%global with_goarches 0
+
+%if ! %{with vendorized}
+%global with_bundled 0
+%else
+%global with_bundled 1
+%endif
+
+%if ! %{with testkeys}
+%global with_test_keys 0
+%else
+%global with_test_keys 1
+%endif
+
+%if 0%{?with_debug}
+%global _dwz_low_mem_die_limit 0
+%else
+%global debug_package %{nil}
+%endif
+
+%global provider github
+%global provider_tld com
+%global project snapcore
+%global repo snapd
+# https://github.com/snapcore/snapd
+%global provider_prefix %{provider}.%{provider_tld}/%{project}/%{repo}
+%global import_path %{provider_prefix}
+
+%global snappy_svcs snapd.service snapd.socket snapd.autoimport.service snapd.refresh.timer snapd.refresh.service
+
+Name: snapd
+Version: 2.29.3
+Release: 0%{?dist}
+Summary: A transactional software package manager
+Group: System Environment/Base
+License: GPLv3
+URL: https://%{provider_prefix}
+%if ! 0%{?with_bundled}
+Source0: https://%{provider_prefix}/archive/%{version}/%{name}-%{version}.tar.gz
+%else
+Source0: https://%{provider_prefix}/releases/download/%{version}/%{name}_%{version}.vendor.orig.tar.xz
+%endif
+
+%if 0%{?with_goarches}
+# e.g. el6 has ppc64 arch without gcc-go, so EA tag is required
+ExclusiveArch: %{?go_arches:%{go_arches}}%{!?go_arches:%{ix86} x86_64 %{arm}}
+%else
+# Verified arches from snapd upstream
+ExclusiveArch: %{ix86} x86_64 %{arm} aarch64 ppc64le s390x
+%endif
+
+# If go_compiler is not set to 1, there is no virtual provide. Use golang instead.
+BuildRequires: %{?go_compiler:compiler(go-compiler)}%{!?go_compiler:golang}
+BuildRequires: systemd
+%{?systemd_requires}
+
+Requires: snap-confine%{?_isa} = %{version}-%{release}
+Requires: squashfs-tools
+# we need squashfs.ko loaded
+Requires: kmod(squashfs.ko)
+# bash-completion owns /usr/share/bash-completion/completions
+Requires: bash-completion
+
+# Force the SELinux module to be installed
+Requires: %{name}-selinux = %{version}-%{release}
+
+%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)
+BuildRequires: golang(github.com/ojii/gettext.go)
+BuildRequires: golang(github.com/seccomp/libseccomp-golang)
+BuildRequires: golang(golang.org/x/crypto/openpgp/armor)
+BuildRequires: golang(golang.org/x/crypto/openpgp/packet)
+BuildRequires: golang(golang.org/x/crypto/sha3)
+BuildRequires: golang(golang.org/x/crypto/ssh/terminal)
+BuildRequires: golang(golang.org/x/net/context)
+BuildRequires: golang(golang.org/x/net/context/ctxhttp)
+BuildRequires: golang(gopkg.in/check.v1)
+BuildRequires: golang(gopkg.in/macaroon.v1)
+BuildRequires: golang(gopkg.in/mgo.v2/bson)
+BuildRequires: golang(gopkg.in/retry.v1)
+BuildRequires: golang(gopkg.in/tomb.v2)
+BuildRequires: golang(gopkg.in/yaml.v2)
+%endif
+
+%description
+Snappy is a modern, cross-distribution, transactional package manager
+designed for working with self-contained, immutable packages.
+
+%package -n snap-confine
+Summary: Confinement system for snap applications
+License: GPLv3
+Group: System Environment/Base
+BuildRequires: autoconf
+BuildRequires: automake
+BuildRequires: libtool
+BuildRequires: gcc
+BuildRequires: gettext
+BuildRequires: gnupg
+BuildRequires: indent
+BuildRequires: pkgconfig(glib-2.0)
+BuildRequires: pkgconfig(libcap)
+BuildRequires: pkgconfig(libseccomp)
+BuildRequires: pkgconfig(libudev)
+BuildRequires: pkgconfig(systemd)
+BuildRequires: pkgconfig(udev)
+BuildRequires: xfsprogs-devel
+BuildRequires: glibc-static
+BuildRequires: libseccomp-static
+BuildRequires: valgrind
+BuildRequires: %{_bindir}/rst2man
+%if 0%{?fedora} >= 25
+# ShellCheck in F24 and older doesn't work
+BuildRequires: %{_bindir}/shellcheck
+%endif
+
+# Ensures older version from split packaging is replaced
+Obsoletes: snap-confine < 2.19
+
+%description -n snap-confine
+This package is used internally by snapd to apply confinement to
+the started snap applications.
+
+%package selinux
+Summary: SELinux module for snapd
+Group: System Environment/Base
+License: GPLv2+
+BuildArch: noarch
+BuildRequires: selinux-policy, selinux-policy-devel
+Requires(post): selinux-policy-base >= %{_selinux_policy_version}
+Requires(post): policycoreutils
+Requires(post): policycoreutils-python-utils
+Requires(pre): libselinux-utils
+Requires(post): libselinux-utils
+
+%description selinux
+This package provides the SELinux policy module to ensure snapd
+runs properly under an environment with SELinux enabled.
+
+
+%if 0%{?with_devel}
+%package devel
+Summary: %{summary}
+BuildArch: noarch
+
+%if 0%{?with_check} && ! 0%{?with_bundled}
+%endif
+
+%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)
+Requires: golang(github.com/ojii/gettext.go)
+Requires: golang(github.com/seccomp/libseccomp-golang)
+Requires: golang(golang.org/x/crypto/openpgp/armor)
+Requires: golang(golang.org/x/crypto/openpgp/packet)
+Requires: golang(golang.org/x/crypto/sha3)
+Requires: golang(golang.org/x/crypto/ssh/terminal)
+Requires: golang(golang.org/x/net/context)
+Requires: golang(golang.org/x/net/context/ctxhttp)
+Requires: golang(gopkg.in/check.v1)
+Requires: golang(gopkg.in/macaroon.v1)
+Requires: golang(gopkg.in/mgo.v2/bson)
+Requires: golang(gopkg.in/retry.v1)
+Requires: golang(gopkg.in/tomb.v2)
+Requires: golang(gopkg.in/yaml.v2)
+%else
+# These Provides are unversioned because the sources in
+# the bundled tarball are unversioned (they go by git commit)
+# *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))
+Provides: bundled(golang(github.com/mvo5/libseccomp-golang))
+Provides: bundled(golang(github.com/ojii/gettext.go))
+Provides: bundled(golang(golang.org/x/crypto/openpgp/armor))
+Provides: bundled(golang(golang.org/x/crypto/openpgp/packet))
+Provides: bundled(golang(golang.org/x/crypto/sha3))
+Provides: bundled(golang(golang.org/x/crypto/ssh/terminal))
+Provides: bundled(golang(golang.org/x/net/context))
+Provides: bundled(golang(golang.org/x/net/context/ctxhttp))
+Provides: bundled(golang(gopkg.in/check.v1))
+Provides: bundled(golang(gopkg.in/macaroon.v1))
+Provides: bundled(golang(gopkg.in/mgo.v2/bson))
+Provides: bundled(golang(gopkg.in/retry.v1))
+Provides: bundled(golang(gopkg.in/tomb.v2))
+Provides: bundled(golang(gopkg.in/yaml.v2))
+%endif
+
+# Generated by gofed
+Provides: golang(%{import_path}/arch) = %{version}-%{release}
+Provides: golang(%{import_path}/asserts) = %{version}-%{release}
+Provides: golang(%{import_path}/asserts/assertstest) = %{version}-%{release}
+Provides: golang(%{import_path}/asserts/signtool) = %{version}-%{release}
+Provides: golang(%{import_path}/asserts/snapasserts) = %{version}-%{release}
+Provides: golang(%{import_path}/asserts/sysdb) = %{version}-%{release}
+Provides: golang(%{import_path}/asserts/systestkeys) = %{version}-%{release}
+Provides: golang(%{import_path}/boot) = %{version}-%{release}
+Provides: golang(%{import_path}/boot/boottest) = %{version}-%{release}
+Provides: golang(%{import_path}/client) = %{version}-%{release}
+Provides: golang(%{import_path}/cmd) = %{version}-%{release}
+Provides: golang(%{import_path}/daemon) = %{version}-%{release}
+Provides: golang(%{import_path}/dirs) = %{version}-%{release}
+Provides: golang(%{import_path}/errtracker) = %{version}-%{release}
+Provides: golang(%{import_path}/httputil) = %{version}-%{release}
+Provides: golang(%{import_path}/i18n) = %{version}-%{release}
+Provides: golang(%{import_path}/image) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/apparmor) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/backends) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/builtin) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/dbus) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/ifacetest) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/kmod) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/mount) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/policy) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/seccomp) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/systemd) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/udev) = %{version}-%{release}
+Provides: golang(%{import_path}/logger) = %{version}-%{release}
+Provides: golang(%{import_path}/osutil) = %{version}-%{release}
+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}
+Provides: golang(%{import_path}/overlord/hookstate) = %{version}-%{release}
+Provides: golang(%{import_path}/overlord/hookstate/ctlcmd) = %{version}-%{release}
+Provides: golang(%{import_path}/overlord/hookstate/hooktest) = %{version}-%{release}
+Provides: golang(%{import_path}/overlord/ifacestate) = %{version}-%{release}
+Provides: golang(%{import_path}/overlord/patch) = %{version}-%{release}
+Provides: golang(%{import_path}/overlord/snapstate) = %{version}-%{release}
+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}/release) = %{version}-%{release}
+Provides: golang(%{import_path}/snap) = %{version}-%{release}
+Provides: golang(%{import_path}/snap/snapdir) = %{version}-%{release}
+Provides: golang(%{import_path}/snap/snapenv) = %{version}-%{release}
+Provides: golang(%{import_path}/snap/snaptest) = %{version}-%{release}
+Provides: golang(%{import_path}/snap/squashfs) = %{version}-%{release}
+Provides: golang(%{import_path}/store) = %{version}-%{release}
+Provides: golang(%{import_path}/strutil) = %{version}-%{release}
+Provides: golang(%{import_path}/systemd) = %{version}-%{release}
+Provides: golang(%{import_path}/tests/lib/fakestore/refresh) = %{version}-%{release}
+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
+%{summary}
+
+This package contains library source intended for
+building other packages which use import path with
+%{import_path} prefix.
+%endif
+
+%if 0%{?with_unit_test} && 0%{?with_devel}
+%package unit-test-devel
+Summary: Unit tests for %{name} package
+
+%if 0%{?with_check}
+#Here comes all BuildRequires: PACKAGE the unit tests
+#in %%check section need for running
+%endif
+
+%if 0%{?with_check} && ! 0%{?with_bundled}
+BuildRequires: golang(github.com/mvo5/goconfigparser)
+%endif
+
+%if ! 0%{?with_bundled}
+Requires: golang(github.com/mvo5/goconfigparser)
+%else
+Provides: bundled(golang(github.com/mvo5/goconfigparser))
+%endif
+
+# test subpackage tests code from devel subpackage
+Requires: %{name}-devel = %{version}-%{release}
+
+%description unit-test-devel
+%{summary}
+
+This package contains unit tests for project
+providing packages with %{import_path} prefix.
+%endif
+
+%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
+./mkversion.sh "%{version}-%{release}"
+
+# Build snapd
+mkdir -p src/github.com/snapcore
+ln -s ../../../ src/github.com/snapcore/snapd
+
+%if ! 0%{?with_bundled}
+export GOPATH=$(pwd):%{gopath}
+%else
+export GOPATH=$(pwd):$(pwd)/Godeps/_workspace:%{gopath}
+%endif
+
+GOFLAGS=
+%if 0%{?with_test_keys}
+GOFLAGS="$GOFLAGS -tags withtestkeys"
+%endif
+
+# We have to build snapd first to prevent the build from
+# building various things from the tree without additional
+# set tags.
+%gobuild -o bin/snapd $GOFLAGS %{import_path}/cmd/snapd
+%gobuild -o bin/snap $GOFLAGS %{import_path}/cmd/snap
+%gobuild -o bin/snapctl $GOFLAGS %{import_path}/cmd/snapctl
+# build snap-exec and snap-update-ns completely static for base snaps
+CGO_ENABLED=0 %gobuild -o bin/snap-exec $GOFLAGS %{import_path}/cmd/snap-exec
+%gobuild -o bin/snap-update-ns --ldflags '-extldflags "-static"' $GOFLAGS %{import_path}/cmd/snap-update-ns
+
+# 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 $GOFLAGS %{import_path}/cmd/snap-seccomp
+
+# Build SELinux module
+pushd ./data/selinux
+make SHARE="%{_datadir}" TARGETS="snappy"
+popd
+
+# Build snap-confine
+pushd ./cmd
+# FIXME This is a hack to get rid of a patch we have to ship for the
+# Fedora package at the moment as /usr/lib/rpm/redhat/redhat-hardened-ld
+# accidentially adds -pie for static executables. See
+# https://bugzilla.redhat.com/show_bug.cgi?id=1343892 for a few more
+# details. To prevent this from happening we drop the linker
+# script and define our LDFLAGS manually for now.
+export LDFLAGS="-Wl,-z,relro -z now"
+autoreconf --force --install --verbose
+# selinux support is not yet available, for now just disable apparmor
+# FIXME: add --enable-caps-over-setuid as soon as possible (setuid discouraged!)
+%configure \
+ --disable-apparmor \
+ --libexecdir=%{_libexecdir}/snapd/ \
+ --with-snap-mount-dir=%{_sharedstatedir}/snapd/snap \
+ --with-merged-usr
+
+%make_build
+popd
+
+# Build systemd units
+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
+install -d -p %{buildroot}%{_mandir}/man1
+install -d -p %{buildroot}%{_unitdir}
+install -d -p %{buildroot}%{_sysconfdir}/profile.d
+install -d -p %{buildroot}%{_sysconfdir}/sysconfig
+install -d -p %{buildroot}%{_sharedstatedir}/snapd/assertions
+install -d -p %{buildroot}%{_sharedstatedir}/snapd/desktop/applications
+install -d -p %{buildroot}%{_sharedstatedir}/snapd/device
+install -d -p %{buildroot}%{_sharedstatedir}/snapd/hostfs
+install -d -p %{buildroot}%{_sharedstatedir}/snapd/mount
+install -d -p %{buildroot}%{_sharedstatedir}/snapd/seccomp/bpf
+install -d -p %{buildroot}%{_sharedstatedir}/snapd/snaps
+install -d -p %{buildroot}%{_sharedstatedir}/snapd/snap/bin
+install -d -p %{buildroot}%{_localstatedir}/snap
+install -d -p %{buildroot}%{_localstatedir}/cache/snapd
+install -d -p %{buildroot}%{_datadir}/selinux/devel/include/contrib
+install -d -p %{buildroot}%{_datadir}/selinux/packages
+
+# Install snap and snapd
+install -p -m 0755 bin/snap %{buildroot}%{_bindir}
+install -p -m 0755 bin/snap-exec %{buildroot}%{_libexecdir}/snapd
+install -p -m 0755 bin/snapctl %{buildroot}%{_bindir}/snapctl
+install -p -m 0755 bin/snapd %{buildroot}%{_libexecdir}/snapd
+install -p -m 0755 bin/snap-update-ns %{buildroot}%{_libexecdir}/snapd
+install -p -m 0755 bin/snap-seccomp %{buildroot}%{_libexecdir}/snapd
+
+# Install SELinux module
+install -p -m 0644 data/selinux/snappy.if %{buildroot}%{_datadir}/selinux/devel/include/contrib
+install -p -m 0644 data/selinux/snappy.pp.bz2 %{buildroot}%{_datadir}/selinux/packages
+
+# Install snap(1) man page
+bin/snap help --man > %{buildroot}%{_mandir}/man1/snap.1
+
+# Install the "info" data file with snapd version
+install -m 644 -D data/info %{buildroot}%{_libexecdir}/snapd/info
+
+# 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
+%make_install
+# Undo the 0000 permissions, they are restored in the files section
+chmod 0755 %{buildroot}%{_sharedstatedir}/snapd/void
+# We don't use AppArmor
+rm -rfv %{buildroot}%{_sysconfdir}/apparmor.d
+# ubuntu-core-launcher is dead
+rm -fv %{buildroot}%{_bindir}/ubuntu-core-launcher
+popd
+
+# Install all systemd units
+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}/snapd.snap-repair.*
+rm -fv %{buildroot}%{_unitdir}/snapd.core-fixup.*
+popd
+
+# Remove snappy core specific scripts
+rm %{buildroot}%{_libexecdir}/snapd/snapd.core-fixup.sh
+
+# Install environ-tweaking snippet
+pushd ./data/env
+%make_install
+popd
+
+# Disable re-exec by default
+echo 'SNAP_REEXEC=0' > %{buildroot}%{_sysconfdir}/sysconfig/snapd
+
+# Install snap management script
+install -pm 0755 packaging/fedora/snap-mgmt.sh %{buildroot}%{_libexecdir}/snapd/snap-mgmt
+
+# Create state.json file to be ghosted
+touch %{buildroot}%{_sharedstatedir}/snapd/state.json
+
+# source codes for building projects
+%if 0%{?with_devel}
+install -d -p %{buildroot}/%{gopath}/src/%{import_path}/
+echo "%%dir %%{gopath}/src/%%{import_path}/." >> devel.file-list
+# find all *.go but no *_test.go files and generate devel.file-list
+for file in $(find . -iname "*.go" -o -iname "*.s" \! -iname "*_test.go") ; do
+ echo "%%dir %%{gopath}/src/%%{import_path}/$(dirname $file)" >> devel.file-list
+ install -d -p %{buildroot}/%{gopath}/src/%{import_path}/$(dirname $file)
+ cp -pav $file %{buildroot}/%{gopath}/src/%{import_path}/$file
+ echo "%%{gopath}/src/%%{import_path}/$file" >> devel.file-list
+done
+%endif
+
+# testing files for this project
+%if 0%{?with_unit_test} && 0%{?with_devel}
+install -d -p %{buildroot}/%{gopath}/src/%{import_path}/
+# find all *_test.go files and generate unit-test.file-list
+for file in $(find . -iname "*_test.go"); do
+ echo "%%dir %%{gopath}/src/%%{import_path}/$(dirname $file)" >> devel.file-list
+ install -d -p %{buildroot}/%{gopath}/src/%{import_path}/$(dirname $file)
+ cp -pav $file %{buildroot}/%{gopath}/src/%{import_path}/$file
+ echo "%%{gopath}/src/%%{import_path}/$file" >> unit-test-devel.file-list
+done
+
+# Install additional testdata
+install -d %{buildroot}/%{gopath}/src/%{import_path}/cmd/snap/test-data/
+cp -pav cmd/snap/test-data/* %{buildroot}/%{gopath}/src/%{import_path}/cmd/snap/test-data/
+echo "%%{gopath}/src/%%{import_path}/cmd/snap/test-data" >> unit-test-devel.file-list
+%endif
+
+%if 0%{?with_devel}
+sort -u -o devel.file-list devel.file-list
+%endif
+
+%check
+# snapd tests
+%if 0%{?with_check} && 0%{?with_unit_test} && 0%{?with_devel}
+%if ! 0%{?with_bundled}
+export GOPATH=%{buildroot}/%{gopath}:%{gopath}
+%else
+export GOPATH=%{buildroot}/%{gopath}:$(pwd)/Godeps/_workspace:%{gopath}
+%endif
+%gotest %{import_path}/...
+%endif
+
+# snap-confine tests (these always run!)
+pushd ./cmd
+make check
+popd
+
+%files
+#define license tag if not already defined
+%{!?_licensedir:%global license %doc}
+%license COPYING
+%doc README.md docs/*
+%{_bindir}/snap
+%{_bindir}/snapctl
+%dir %{_libexecdir}/snapd
+%{_libexecdir}/snapd/snapd
+%{_libexecdir}/snapd/snap-exec
+%{_libexecdir}/snapd/info
+%{_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
+%{_unitdir}/snapd.autoimport.service
+%{_unitdir}/snapd.refresh.service
+%{_unitdir}/snapd.refresh.timer
+%config(noreplace) %{_sysconfdir}/sysconfig/snapd
+%dir %{_sharedstatedir}/snapd
+%dir %{_sharedstatedir}/snapd/assertions
+%dir %{_sharedstatedir}/snapd/desktop
+%dir %{_sharedstatedir}/snapd/desktop/applications
+%dir %{_sharedstatedir}/snapd/device
+%dir %{_sharedstatedir}/snapd/hostfs
+%dir %{_sharedstatedir}/snapd/mount
+%dir %{_sharedstatedir}/snapd/seccomp
+%dir %{_sharedstatedir}/snapd/seccomp/bpf
+%dir %{_sharedstatedir}/snapd/snaps
+%dir %{_sharedstatedir}/snapd/snap
+%dir /var/cache/snapd
+%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
+%license COPYING
+%dir %{_libexecdir}/snapd
+# For now, we can't use caps
+# FIXME: Switch to "%%attr(0755,root,root) %%caps(cap_sys_admin=pe)" asap!
+%attr(4755,root,root) %{_libexecdir}/snapd/snap-confine
+%{_libexecdir}/snapd/snap-discard-ns
+%{_libexecdir}/snapd/snap-seccomp
+%{_libexecdir}/snapd/snap-update-ns
+%{_libexecdir}/snapd/system-shutdown
+%{_mandir}/man1/snap-confine.1*
+%{_mandir}/man5/snap-discard-ns.5*
+%{_prefix}/lib/udev/snappy-app-dev
+%{_udevrulesdir}/80-snappy-assign.rules
+%attr(0000,root,root) %{_sharedstatedir}/snapd/void
+
+
+%files selinux
+%license data/selinux/COPYING
+%doc data/selinux/README.md
+%{_datadir}/selinux/packages/snappy.pp.bz2
+%{_datadir}/selinux/devel/include/contrib/snappy.if
+
+%if 0%{?with_devel}
+%files devel -f devel.file-list
+%license COPYING
+%doc README.md
+%dir %{gopath}/src/%{provider}.%{provider_tld}/%{project}
+%endif
+
+%if 0%{?with_unit_test} && 0%{?with_devel}
+%files unit-test-devel -f unit-test-devel.file-list
+%license COPYING
+%doc README.md
+%endif
+
+%post
+%systemd_post %{snappy_svcs}
+# If install, test if snapd socket and timer are enabled.
+# If enabled, then attempt to start them. This will silently fail
+# in chroots or other environments where services aren't expected
+# to be started.
+if [ $1 -eq 1 ] ; then
+ if systemctl -q is-enabled snapd.socket > /dev/null 2>&1 ; then
+ systemctl start snapd.socket > /dev/null 2>&1 || :
+ fi
+ if systemctl -q is-enabled snapd.refresh.timer > /dev/null 2>&1 ; then
+ systemctl start snapd.refresh.timer > /dev/null 2>&1 || :
+ fi
+fi
+
+%preun
+%systemd_preun %{snappy_svcs}
+
+# Remove all Snappy content if snapd is being fully uninstalled
+if [ $1 -eq 0 ]; then
+ %{_libexecdir}/snapd/snap-mgmt --purge || :
+fi
+
+
+%postun
+%systemd_postun_with_restart %{snappy_svcs}
+
+%pre selinux
+%selinux_relabel_pre
+
+%post selinux
+%selinux_modules_install %{_datadir}/selinux/packages/snappy.pp.bz2
+%selinux_relabel_post
+
+%postun selinux
+%selinux_modules_uninstall snappy
+if [ $1 -eq 0 ]; then
+ %selinux_relabel_post
+fi
+
+
+%changelog
+* Thu Nov 09 2017 Michael Vogt
+- New upstream release 2.29.3
+ - daemon: cherry-picked /v2/logs fixes
+ - cmd/snap-confine: Respect biarch nature of libdirs
+ - cmd/snap-confine: Ensure snap-confine is allowed to access os-
+ release
+ - interfaces: fix udev tagging for hooks
+ - cmd: fix re-exec bug with classic confinement for host snapd
+ - tests: disable xdg-open-compat test
+ - cmd/snap-confine: add slave PTYs and let devpts newinstance
+ perform mediation
+ - interfaces/many: misc policy updates for browser-support, cups-
+ control and network-status
+ - interfaces/raw-usb: match on SUBSYSTEM, not SUBSYSTEMS
+ - tests: fix security-device-cgroup* tests on devices with
+ framebuffer
+
+* Fri Nov 03 2017 Michael Vogt
+- New upstream release 2.29.2
+ - snapctl: disable stop/start/restart (2.29)
+ - cmd/snap-update-ns: fix collection of changes made
+
+* Fri Nov 03 2017 Michael Vogt
+- New upstream release 2.29.1
+ - interfaces: fix incorrect signature of ofono DBusPermanentSlot
+ - interfaces/serial-port: udev tag plugged slots that have just
+ 'path' via KERNEL
+ - interfaces/hidraw: udev tag plugged slots that have just 'path'
+ via KERNEL
+ - interfaces/uhid: unconditionally add existing uhid device to the
+ device cgroup
+ - cmd/snap-update-ns: fix mount rules for font sharing
+ - tests: disable refresh-undo test on trusty for now
+ - tests: use `snap change --last=install` in snapd-reexec test
+ - Revert " wrappers: fail install if exec-line cannot be re-written
+ - interfaces: don't udev tag devmode or classic snaps
+ - many: make ignore-validation sticky and send the flag with refresh
+ requests
+
+* Mon Oct 30 2017 Michael Vogt
+- New upstream release 2.29
+ - interfaces/many: miscellaneous updates based on feedback from the
+ field
+ - snap-confine: allow reading uevents from any where in /sys
+ - spread: add bionic beaver
+ - debian: make packaging/ubuntu-14.04/copyright a real file again
+ - tests: cherry pick the fix for services test into 2.29
+ - cmd/snap-update-ns: initialize logger
+ - hooks/configure: queue service restarts
+ - snap-{confine,seccomp}: make @unrestricted fully unrestricted
+ - interfaces: clean system apparmor cache on core device
+ - debian: do not build static snap-exec on powerpc
+ - snap-confine: increase sanity_timeout to 6s
+ - snapctl: cherry pick service commands changes
+ - cmd/snap: tell translators about arg names and descs req's
+ - systemd: run all mount units before snapd.service to avoid race
+ - store: add a test to show auth failures are forwarded by doRequest
+ - daemon: convert ErrInvalidCredentials to a 401 Unauthorized error.
+ - store: forward on INVALID_CREDENTIALS error as
+ ErrInvalidCredentials
+ - daemon: generate a forbidden response message if polkit dialog is
+ dismissed
+ - daemon: Allow Polkit authorization to cancel changes.
+ - travis: switch to container based test runs
+ - interfaces: reduce duplicated code in interface tests mocks
+ - tests: improve revert related testing
+ - interfaces: sanitize plugs and slots early in ReadInfo
+ - store: add download caching
+ - preserve TMPDIR and HOSTALIASES across snap-confine invocation
+ - snap-confine: init all arrays with `= {0,}`
+ - tests: adding test for network-manager interface
+ - interfaces/mount: don't generate legacy per-hook/per-app mount
+ profiles
+ - snap: introduce structured epochs
+ - tests: fix interfaces-cups-control test for cups-2.2.5
+ - snap-confine: cleanup incorrectly created nvidia udev tags
+ - cmd/snap-confine: update valid security tag regexp
+ - cmd/libsnap: enable two stranded tests
+ - cmd,packaging: enable apparmor on openSUSE
+ - overlord/ifacestate: refresh all security backends on startup
+ - interfaces/dbus: drop unneeded check for
+ release.ReleaseInfo.ForceDevMode
+ - dbus: ensure io.snapcraft.Launcher.service is created on re-
+ exec
+ - overlord/auth: continue for now supporting UBUNTU_STORE_ID if the
+ model is generic-classic
+ - snap-confine: add support for handling /dev/nvidia-modeset
+ - interfaces/network-control: remove incorrect rules for tun
+ - spread: allow setting SPREAD_DEBUG_EACH=0 to disable debug-each
+ section
+ - packaging: remove .mnt files on removal
+ - tests: fix econnreset scenario when the iptables rule was not
+ created
+ - tests: add test for lxd interface
+ - run-checks: use nakedret static checker to check for naked
+ returns on long functions
+ - progress: be more flexible in testing ansimeter
+ - interfaces: fix udev rules for tun
+ - many: implement our own ANSI-escape-using progress indicator
+ - snap-exec: update tests to follow main_test pattern
+ - snap: support "command: foo $ENV_STRING"
+ - packaging: update nvidia configure options
+ - snap: add new `snap pack` and use in tests
+ - cmd: correctly name the "Ubuntu" and "Arch" NVIDIA methods
+ - cmd: add autogen case for solus
+ - tests: do not use http://canihazip.com/ which appears to be down
+ - hooks: commands for controlling own services from snapctl
+ - snap: refactor cmdGet.Execute()
+ - interfaces/mount: make Change.Perform testable and test it
+ - interfaces/mount,cmd/snap-update-ns: move change code
+ - snap-confine: is_running_on_classic_distribution() looks into os-
+ release
+ - interfaces: misc updates for default, browser-support, home and
+ system-observe
+ - interfaces: deny lttng by default
+ - interfaces/lxd: lxd slot implementation can also be an app snap
+ - release,cmd,dirs: Redo the distro checks to take into account
+ distribution families
+ - cmd/snap: completion for alias and unalias
+ - snap-confine: add new SC_CLEANUP and use it
+ - snap: refrain from running filepath.Base on random strings
+ - cmd/snap-confine: put processes into freezer hierarchy
+ - wrappers: fail install if exec-line cannot be re-written
+ - cmd/snap-seccomp,osutil: make user/group lookup functions public
+ - snapstate: deal with snap user data in the /root/ directory
+ - interfaces: Enhance full-confinement support for biarch
+ distributions
+ - snap-confine: Only attempt to copy/mount NVIDIA libs when NVIDIA
+ is used
+ - packaging/fedora: Add Fedora 26, 27, and Rawhide symlinks
+ - overlord/snapstate: prefer a smaller corner case for doing the
+ wrong thing
+ - cmd/snap-repair: set user agent for snap-repair http requests
+ - packaging: bring down the delta between 14.04 and 16.04
+ - snap-confine: Ensure lib64 biarch directory is respected
+ - snap-confine: update apparmor rules for fedora based base snaps
+ - tests: Increase SNAPD_CONFIGURE_HOOK_TIMEOUT to 3 minutes to
+ install real snaps
+ - daemon: use client.Snap instead of map[string]interface{} for
+ snaps.
+ - hooks: rename refresh hook to post-refresh
+ - git: make the .gitingore file a bit more targeted
+ - interfaces/opengl: don't udev tag nvidia devices and use snap-
+ confine instead
+ - cmd/snap-{confine,update-ns}: apply mount profiles using snap-
+ update-ns
+ - cmd: update "make hack"
+ - interfaces/system-observe: allow clients to enumerate DBus
+ connection names
+ - snap-repair: implement `snap-repair {list,show}`
+ - dirs,interfaces: create snap-confine.d on demand when re-executing
+ - snap-confine: fix base snaps on core
+ - cmd/snap-repair: fix tests when running as root
+ - interfaces: add Connection type
+ - cmd/snap-repair: skip disabled repairs
+ - cmd/snap-repair: prefer leaking unmanaged fds on test failure over
+ closing random ones
+ - snap-repair: make `repair` binary available for repair scripts
+ - snap-repair: fix missing Close() in TestStatusHappy
+ - cmd/snap-confine,packaging: import snapd-generated policy
+ - cmd/snap: return empty document if snap has no configuration
+ - snap-seccomp: run secondary-arch tests via gcc-multilib
+ - snap: implement `snap {repair,repairs}` and pass-through to snap-
+ repair
+ - interfaces/builtin: allow receiving dbus messages
+ - snap-repair: implement `snap-repair {done,skip,retry}`
+ - data/completion: small tweak to snap completion snippet
+ - dirs: fix classic support detection
+ - cmd/snap-repair: integrate root public keys for repairs
+ - tests: fix ubuntu core services
+ - tests: add new test that checks that the compat snapd-xdg-open
+ works
+ - snap-confine: improve error message if core/u-core cannot be found
+ - tests: only run tests/regression/nmcli on amd64
+ - interfaces: mount host system fonts in desktop interface
+ - interfaces: enable partial apparmor support
+ - snapstate: auto-install missing base snaps
+ - spread: work around temporary packaging issue in debian sid
+ - asserts,cmd/snap-repair: introduce a mandatory summary for repairs
+ - asserts,cmd/snap-repair: represent RepairID internally as an int
+ - tests: test the real "xdg-open" from the core snap
+ - many: implement fetching sections and package names periodically.
+ - interfaces/network: allow using netcat as client
+ - snap-seccomp, osutil: use osutil.AtomicFile in snap-seccomp
+ - snap-seccomp: skip mknod syscall on arm64
+ - tests: add trivial canonical-livepatch test
+ - tests: add test that ensures that all core services are working
+ - many: add logger.MockLogger() and use it in the tests
+ - snap-repair: fix test failure in TestRepairHitsTimeout
+ - asserts: add empty values check in HeadersFromPrimaryKey
+ - daemon: remove unused installSnap var in test
+ - daemon: reach for Overlord.Loop less thanks to overlord.Mock
+ - snap-seccomp: manually resolve socket() call in tests
+ - tests: change regex used to validate installed ubuntu core snap
+ - cmd/snapctl: allow snapctl -h without a context (regression fix).
+ - many: use snapcore/snapd/i18n instead of i18n/dumb
+ - many: introduce asserts.NotFoundError replacing both ErrNotFound
+ and store.AssertionNotFoundError
+ - packaging: don't include any marcos in comments
+ - overlord: use overlord.Mock in more tests, make sure we check the
+ outcome of Settle
+ - tests: try to fix staging tests
+ - store: simplify api base url config
+ - systemd: add systemd.MockJournalctl()
+ - many: provide systemd.MockSystemctl() helper
+ - tests: improve the listing test to not fail for e.g. 2.28~rc2
+ - snapstate: give snapmgrTestSuite.settle() more time to settle
+ - tests: fix regex to check core version on snap list
+ - debian: update trusted account-keys check on 14.04 packaging
+ - interfaces: add udev netlink support to hardware-observe
+ - overlord: introduce Mock which enables to use Overlord.Settle for
+ settle in many more places
+ - snap-repair: execute the repair and capture logs/status
+ - tests: run the tests/unit/go everywhere
+ - daemon, snapstate: move ensureCore from daemon/api.go into
+ snapstate.go
+ - cmd/snap: get keys or root document
+ - spread.yaml: turn suse to manual given that it's breaking master
+ - many: configure store from state, reconfigure store at runtime
+ - osutil: AtomicWriter (an io.Writer), and io.Reader versions of
+ AtomicWrite*
+ - tests: check for negative syscalls in runBpf() and skip those
+ tests
+ - docs: use abolute path in PULL_REQUEST_TEMPLATE.md
+ - store: move device auth endpoint uris to config (#3831)
+
+* 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
+ - hooks: do not error when hook handler is not registered
+ - interfaces/alsa,pulseaudio: allow read on udev data for sound
+ - interfaces/optical-drive: read access to udev data for /dev/scd*
+ - interfaces/browser-support: read on /proc/vmstat and misc udev
+ data
+
+* Thu Aug 24 2017 Michael Vogt
+- New upstream release 2.27.4
+ - snap-seccomp: add secondary arch for unrestricted snaps as well
+
+* Fri Aug 18 2017 Michael Vogt
+- New upstream release 2.27.3
+ - 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
+ changes
+ - interfaces: don't crash if content slot has no attributes
+ - debian: do not build with -buildmode=pie on i386
+ - interfaces: backport broadcom-asic-control interface
+ - interfaces: allow /usr/bin/xdg-open in unity7
+ - 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
+ - tests: fix test failure on 14.04 due to old version of
+ flock
+ - updates for unity7/x11, browser-support, network-control,
+ 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
+
+* 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
+ - interfaces: add password-manager-service implicit classic interface
+ - interfaces/greengrass-support: adjust accesses now that have working
+ snap
+ - interfaces/many, cmd/snap-confine: miscellaneous policy updates
+ - interfaces/unity7: allow receiving media key events in (at least)
+ gnome-shell
+ - cmd: fix re-exec bug when starting from snapd 2.21
+ - tests: restore interfaces-account-control properly
+ - cmd: fix tests that assume /snap mount
+ - cmd: mark arch as non-reexecing distro
+ - snap-confine: don't share /etc/nsswitch from host
+ - store: talk to api.snapcraft.io for purchases
+ - hooks: support for install and remove hooks
+ - packaging: fix Fedora support
+ - tests: add bluetooth-control interface test
+ - store: talk to api.snapcraft.io for assertions
+ - tests: remove snapd before building from branch
+ - tests: add avahi-observe interface test
+ - store: orders API now checks if customer is ready
+ - cmd/snap: snap find only searches stable
+ - interfaces: updates default, mir, optical-observe, system-observe,
+ screen-inhibit-control and unity7
+ - tests: speedup prepare statement part 1
+ - store: do not send empty refresh requests
+ - asserts: fix error handling in snap-developer consistency check
+ - systemd: add explicit sync to snapd.core-fixup.sh
+ - snapd: generate snap cookies on startup
+ - cmd,client,daemon: expose "force devmode" in sysinfo
+ - many: introduce and use strutil.ListContains and also
+ strutil.SortedListContains
+ - assserts,overlord/assertstate: test we don't accept chains of
+ assertions founded on a self-signed key coming externally
+ - interfaces: enable access to bridge settings
+ - interfaces: fix copy-pasted iio vs io in io-ports-control
+ - cmd/snap-confine: various small fixes and tweaks to seccomp
+ support code
+ - interfaces: bring back seccomp argument filtering
+ - systemd, osutil: rework systemd logs in preparation for services
+ commands
+ - tests: store /etc/systemd/system/snap-*core*.mount in snapd-
+ state.tar.gz
+ - tests: shellcheck improvements for tests/main tasks - first set of
+ tests
+ - cmd/snap: `--last` for abort and watch, and aliases
+ (search→find, change→tasks)
+ - tests: shellcheck improvements for tests/lib scripts
+ - tests: create ramdisk if it's not present
+ - tests: shellcheck improvements for nightly upgrade and regressions
+ tests
+ - snapd: fix for snapctl get panic on null config values.
+ - tests: fix for rng-tools service not restarting
+ - systemd: add snapd.core-fixup.service unit
+ - cmd: avoid using current symlink in InternalToolPath
+ - tests: fix timeout issue for test refresh core with hanging …
+ - intefaces: control bridged vlan/ppoe-tagged traffic
+ - cmd/snap: include snap type in notes
+ - overlord/state: Abort() only visits each task once
+ - tests: extend find-private test to cover more cases
+ - snap-seccomp: skip socket() tests on systems that use socketcall()
+ instead of socket()
+ - many: support snap title as localized/title-cased name
+ - snap-seccomp: deal with mknod on aarch64 in the seccomp tests
+ - interfaces: put base policy fragments inside each interface
+ - asserts: introduce NewDecoderWithTypeMaxBodySize
+ - tests: fix snapd-notify when it takes more time to restart
+ - snap-seccomp: fix snap-seccomp tests in artful
+ - tests: fix for create-key task to avoid rng-tools service ramains
+ alive
+ - snap-seccomp: make sure snap-seccomp writes the bpf file
+ atomically
+ - tests: do not disable ipv6 on core systems
+ - arch: the kernel architecture name is armv7l instead of armv7
+ - snap-confine: ensure snap-confine waits some seconds for seccomp
+ security profiles
+ - tests: shellcheck improvements for tests/nested tasks
+ - wrappers: add SyslogIdentifier to the service unit files.
+ - tests: shellcheck improvements for unit tasks
+ - asserts: implement FindManyTrusted as well
+ - asserts: open up and optimize Encoder to help avoiding unnecessary
+ copying
+ - interfaces: simplify snap-confine by just loading pre-generated
+ bpf code
+ - tests: restart rng-tools services after few seconds
+ - interfaces, tests: add mising dbus abstraction to system-observe
+ and extend spread test
+ - store: change main store host to api.snapcraft.io
+ - overlord/cmdstate: new package for running commands as tasks.
+ - spread: help libapt resolve installing libudev-dev
+ - tests: show the IP from .travis.yaml
+ - tests/main: use pkgdb function in more test cases
+ - cmd,daemon: add debug command for displaying the base policy
+ - tests: prevent quoting error on opensuse
+ - tests: fix nightly suite
+ - tests: add linode-sru backend
+ - snap-confine: validate SNAP_NAME against security tag
+ - tests: fix ipv6 disable for ubuntu-core
+ - tests: extend core-revert test to cover bluez issues
+ - interfaces/greengrass-support: add support for Amazon Greengrass
+ as a snap
+ - asserts: support timestamp and optional disabled header on repair
+ - tests: reboot after upgrading to snapd on the -proposed pocket
+ - many: fix test cases to work with different DistroLibExecDir
+ - tests: reenable help test on ubuntu and debian systems
+ - packaging/{opensuse,fedora}: allow package build with testkeys
+ included
+ - tests/lib: generalize RPM build support
+ - interfaces/builtin: sync connected slot and permanent slot snippet
+ - tests: fix snap create-key by restarting automatically rng-tools
+ - many: switch to use http numeric statuses as agreed
+ - debian: add missing Type=notify in 14.04 packaging
+ - tests: mark interfaces-openvswitch as manual due to prepare errors
+ - debian: unify built_using between the 14.04 and 16.04 packaging
+ branch
+ - tests: pull from urandom when real entropy is not enough
+ - tests/main/manpages: install missing man package
+ - tests: add refresh --time output check
+ - debian: add missing "make -C data/systemd clean"
+ - tests: fix for upgrade test when it is repeated
+ - tests/main: use dir abstraction in a few more test cases
+ - tests/main: check for confinement in a few more interface tests
+ - spread: add fedora snap bin dir to global PATH
+ - tests: check that locale-control is not present on core
+ - many: snapctl outside hooks
+ - tests: add whoami check
+ - interfaces: compose the base declaration from interfaces
+ - tests: fix spread flaky tests linode
+ - tests,packaging: add package build support for openSUSE
+ - many: slight improvement of some snap error messaging
+ - errtracker: Include /etc/apparmor.d/usr.lib.snap-confine md5sum in
+ err reports
+ - tests: fix for the test postrm-purge
+ - tests: restoring the /etc/environment and service units config for
+ each test
+ - daemon: make snapd a "Type=notify" daemon and notify when startup
+ is done
+ - cmd/snap-confine: add support for --base snap
+ - many: derive implicit slots from interface meta-data
+ - tests: add core revert test
+ - tests,packaging: add package build support for Fedora for our
+ spread setup
+ - interfaces: move base declaration to the policy sub-package
+ - tests: fix for snapd-reexec test cheking for restart info on debug
+ log
+ - tests: show available entropy on error
+ - tests: clean journalctl logs on trusty
+ - tests: fix econnreset on staging
+ - tests: modify core before calling set
+ - tests: add snap-confine privilege test
+ - tests: add staging snap-id
+ - interfaces/builtin: silence ptrace denial for network-manager
+ - tests: add alsa interface spread test
+ - tests: prefer ipv4 over ipv6
+ - tests: fix for econnreset test checking that the download already
+ started
+ - httputil,store: extract retry code to httputil, reorg usages
+ - errtracker: report if snapd did re-execute itself
+ - errtracker: include bits of snap-confine apparmor profile
+ - tests: take into account staging snap-ids for snap-info
+ - cmd: add stub new snap-repair command and add timer
+ - many: stop "snap refresh $x --channel invalid" from working
+ - interfaces: revert "interfaces: re-add reverted ioctl and quotactl
+ - snapstate: consider connect/disconnect tasks in
+ CheckChangeConflict.
+ - interfaces: disable "mknod |N" in the default seccomp template
+ again
+ - interfaces,overlord/ifacestate: make sure installing slots after
+ plugs works similarly to plugs after slots
+ - interfaces/seccomp: add bind() syscall for forced-devmode systems
+ - packaging/fedora: Sync packaging from Fedora Dist-Git
+ - tests: move static and unit tests to spread task
+ - many: error types should be called FooError, not ErrFoo.
+ - partition: add directory sync to the save uboot.env file code
+ - cmd: test everything (100% coverage \o/)
+ - many: make shell scripts shellcheck-clean
+ - tests: remove additional setup for docker on core
+ - interfaces: add summary to each interface
+ - many: remove interface meta-data from list of connections
+ - logger (& many more, to accommodate): drop explicit syslog.
+ - packaging: import packaging bits for opensuse
+ - snapstate,many: implement snap install --unaliased
+ - tests/lib: abstract build dependency installation a bit more
+ - interfaces, osutil: move flock code from interfaces/mount to
+ osutil
+ - cmd: auto import assertions only from ext4,vfat file systems
+ - many: refactor in preparation for 'snap start'
+ - overlord/snapstate: have an explicit code path last-refresh
+ unset/zero => immediately refresh try
+ - tests: fixes for executions using the staging store
+ - tests: use pollinate to seed the rng
+ - cmd/snap,tests: show the sha3-384 of the snap for snap info
+ --verbose SNAP-FILE
+ - asserts: simplify and adjust repair assertion definition
+ - cmd/snap,tests: show the snap id if available in snap info
+ - daemon,overlord/auth: store from model assertion wins
+ - cmd/snap,tests/main: add confinement switch instead of spread
+ system blacklisting
+ - many: cleanup MockCommands and don't leave a process around after
+ hookstate tests
+ - tests: update listing test to the core version number schema
+ - interfaces: allow snaps to use the timedatectl utility
+ - packaging: Add Fedora packaging files
+ - tests/libs: add distro_auto_remove_packages function
+ - cmd/snap: correct devmode note for anomalous state
+ - tests/main/snap-info: use proper pkgdb functions to install distro
+ packages
+ - tests/lib: use mktemp instead of tempfile to work cross-distro
+ - tests: abstract common dirs which differ on distributions
+ - many: model and expose interface meta-data.
+ - overlord: make config defaults from gadget work also at first boot
+ - interfaces/log-observe: allow using journalctl from hostfs for
+ classic distro
+ - partition,snap: add support for android boot
+ - errtracker: small simplification around readMachineID
+ - snap-confine: move rm_rf_tmp to test-utils.
+ - tests/lib: introduce pkgdb helper library
+ - errtracker: try multiple paths to read machine-id
+ - overlord/hooks: make sure only one hook for given snap is executed
+ at a time.
+ - cmd/snap-confine: use SNAP_MOUNT_DIR to setup /snap inside the
+ confinement env
+ - tests: bump kill-timeout and remove quiet call on build
+ - tests/lib/snaps: add a test store snap with a passthrough
+ configure hook
+ - daemon: teach the daemon to wait on active connections when
+ shutting down
+ - tests: remove unit tests task
+ - tests/main/completion: source from /usr/share/bash-completion
+ - assertions: add "repair" assertion
+ - interfaces/seccomp: document Backend.NewSpecification
+ - wrappers: make StartSnapServices cleanup any services that were
+ added if a later one fails
+ - overlord/snapstate: avoid creating command aliases for daemons
+ - vendor: remove unused packages
+ - vendor,partition: fix panics from uenv
+ - cmd,interfaces/mount: run snap-update-ns and snap-discard-ns from
+ core if possible
+ - daemon: do not allow to install ubuntu-core anymore
+ - wrappers: service start/stop were inconsistent
+ - tests: fix failing tests (snap core version, syslog changes)
+ - cmd/snap-update-ns: add actual implementation
+ - tests: improve entropy also for ubuntu
+ - cmd/snap-confine: use /etc/ssl from the core snap
+ - wrappers: don't convert between []byte and string needlessly.
+ - hooks: default timeout
+ - overlord/snapstate: Enable() was ignoring the flags from the
+ snap's state, resulting in losing "devmode" on disable/enable.
+ - difs,interfaces/mount: add support for locking namespaces
+ - interfaces/mount: keep track of kept mount entries
+ - tests/main: move a bunch of greps over to MATCH
+ - interfaces/builtin: make all interfaces private
+ - interfaces/mount: spell unmount correctly
+ - tests: allow 16-X.Y.Z version of core snap
+ - the timezone_control interface only allows changing /etc/timezone
+ and /etc/writable/timezone. systemd-timedated also updated the
+ link of /etc/localtime and /etc/writable/localtime ... allow
+ access to this file too
+ - cmd/snap-confine: aggregate operations holding global lock
+ - 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)
+
+* Sun May 21 2017 Neal Gompa - 2.26.3-2
+- Fix error in script for removing Snappy content (RH#1444422)
+- Adjust changelog bug references to be specific on origin
+
+* Wed May 17 2017 Neal Gompa - 2.26.3-1
+- Update to snapd 2.26.3
+- Drop merged and unused patches
+- Cover more Snappy content for proper erasure on final uninstall (RH#1444422)
+- Add temporary fix to ensure generated seccomp profiles don't break snapctl
+
+* Mon May 01 2017 Neal Gompa - 2.25-1
+- Update to snapd 2.25
+- Ensure all Snappy content is gone on final uninstall (RH#1444422)
+
+* Tue Apr 11 2017 Neal Gompa - 2.24-1
+- Update to snapd 2.24
+- Drop merged patches
+- Install snap bash completion and snapd info file
+
+* Wed Apr 05 2017 Neal Gompa - 2.23.6-4
+- Test if snapd socket and timer enabled and start them if enabled on install
+
+* Sat Apr 01 2017 Neal Gompa - 2.23.6-3
+- Fix profile.d generation so that vars aren't expanded in package build
+
+* Fri Mar 31 2017 Neal Gompa - 2.23.6-2
+- Fix the overlapping file conflicts between snapd and snap-confine
+- Rework package descriptions slightly
+
+* Thu Mar 30 2017 Neal Gompa - 2.23.6-1
+- Rebase to snapd 2.23.6
+- Rediff patches
+- Re-enable seccomp
+- Fix building snap-confine on 32-bit arches
+- Set ExclusiveArch based on upstream supported arch list
+
+* Wed Mar 29 2017 Neal Gompa - 2.23.5-1
+- Rebase to snapd 2.23.5
+- Disable seccomp temporarily avoid snap-confine bugs (LP#1674193)
+- Use vendorized build for non-Fedora
+
+* Mon Mar 13 2017 Neal Gompa - 2.23.1-1
+- Rebase to snapd 2.23.1
+- Add support for vendored tarball for non-Fedora targets
+- Use merged in SELinux policy module
+
+* Sat Feb 11 2017 Fedora Release Engineering - 2.16-2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild
+
+* Wed Oct 19 2016 Zygmunt Krynicki - 2.16-1
+- New upstream release
+
+* Tue Oct 18 2016 Neal Gompa - 2.14-2
+- Add SELinux policy module subpackage
+
+* Tue Aug 30 2016 Zygmunt Krynicki - 2.14-1
+- New upstream release
+
+* Tue Aug 23 2016 Zygmunt Krynicki - 2.13-1
+- New upstream release
+
+* Thu Aug 18 2016 Zygmunt Krynicki - 2.12-2
+- Correct license identifier
+
+* Thu Aug 18 2016 Zygmunt Krynicki - 2.12-1
+- New upstream release
+
+* Thu Aug 18 2016 Zygmunt Krynicki - 2.11-8
+- Add %%dir entries for various snapd directories
+- Tweak Source0 URL
+
+* Tue Aug 16 2016 Zygmunt Krynicki - 2.11-7
+- Disable snapd re-exec feature by default
+
+* Tue Aug 16 2016 Zygmunt Krynicki - 2.11-6
+- Don't auto-start snapd.socket and snapd.refresh.timer
+
+* Tue Aug 16 2016 Zygmunt Krynicki - 2.11-5
+- Don't touch snapd state on removal
+
+* Tue Aug 16 2016 Zygmunt Krynicki - 2.11-4
+- Use ExecStartPre to load squashfs.ko before snapd starts
+- Use dedicated systemd units for Fedora
+
+* Tue Aug 16 2016 Zygmunt Krynicki - 2.11-3
+- Remove systemd preset (will be requested separately according to distribution
+ standards).
+
+* Tue Aug 16 2016 Zygmunt Krynicki - 2.11-2
+- Use Requires: kmod(squashfs.ko) instead of Requires: kernel-modules
+
+* Tue Aug 16 2016 Zygmunt Krynicki - 2.11-1
+- New upstream release
+- Move private executables to /usr/libexec/snapd/
+
+* Fri Jun 24 2016 Zygmunt Krynicki - 2.0.9-2
+- Depend on kernel-modules to ensure that squashfs can be loaded. Load it afer
+ installing the package. This hopefully fixes
+ https://github.com/zyga/snapcore-fedora/issues/2
+
+* Fri Jun 17 2016 Zygmunt Krynicki - 2.0.9
+- New upstream release
+ https://github.com/snapcore/snapd/releases/tag/2.0.9
+
+* Tue Jun 14 2016 Zygmunt Krynicki - 2.0.8.1
+- New upstream release
+
+* Fri Jun 10 2016 Zygmunt Krynicki - 2.0.8
+- First package for Fedora
diff -Nru snapd-2.28.5/packaging/fedora-27/snap-mgmt.sh snapd-2.29.3/packaging/fedora-27/snap-mgmt.sh
--- snapd-2.28.5/packaging/fedora-27/snap-mgmt.sh 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/packaging/fedora-27/snap-mgmt.sh 2017-10-23 06:17:27.000000000 +0000
@@ -0,0 +1,137 @@
+#!/bin/bash
+
+# Overlord management of snapd for package manager actions.
+# Implements actions that would be invoked in %pre(un) actions for snapd.
+# Derived from the snapd.postrm scriptlet used in the Ubuntu packaging for
+# snapd.
+
+set -e
+
+SNAP_MOUNT_DIR="/var/lib/snapd/snap"
+
+show_help() {
+ exec cat <<'EOF'
+Usage: snap-mgmt.sh [OPTIONS]
+
+A simple script to cleanup snap installations.
+
+optional arguments:
+ --help Show this help message and exit
+ --snap-mount-dir= Provide a path to be used as $SNAP_MOUNT_DIR
+ --purge Purge all data from $SNAP_MOUNT_DIR
+EOF
+}
+
+SNAP_UNIT_PREFIX="$(systemd-escape -p ${SNAP_MOUNT_DIR})"
+
+systemctl_stop() {
+ unit="$1"
+ if systemctl is-active -q "$unit"; then
+ echo "Stoping $unit"
+ systemctl stop -q "$unit" || true
+ fi
+}
+
+purge() {
+ # undo any bind mount to ${SNAP_MOUNT_DIR} that resulted from LP:#1668659
+ if grep -q "${SNAP_MOUNT_DIR} ${SNAP_MOUNT_DIR}" /proc/self/mountinfo; then
+ umount -l "${SNAP_MOUNT_DIR}" || true
+ fi
+
+ mounts=$(systemctl list-unit-files --full | grep "^${SNAP_UNIT_PREFIX}[-.].*\.mount" | cut -f1 -d ' ')
+ services=$(systemctl list-unit-files --full | grep "^${SNAP_UNIT_PREFIX}[-.].*\.service" | cut -f1 -d ' ')
+ for unit in $services $mounts; do
+ # ensure its really a snap mount unit or systemd unit
+ if ! grep -q 'What=/var/lib/snapd/snaps/' "/etc/systemd/system/$unit" && ! grep -q 'X-Snappy=yes' "/etc/systemd/system/$unit"; then
+ echo "Skipping non-snapd systemd unit $unit"
+ continue
+ fi
+
+ echo "Stopping $unit"
+ systemctl_stop "$unit"
+
+ # if it is a mount unit, we can find the snap name in the mount
+ # unit (we just ignore unit files)
+ snap=$(grep "Where=${SNAP_MOUNT_DIR}/" "/etc/systemd/system/$unit"|cut -f3 -d/)
+ rev=$(grep "Where=${SNAP_MOUNT_DIR}/" "/etc/systemd/system/$unit"|cut -f4 -d/)
+ if [ -n "$snap" ]; then
+ echo "Removing snap $snap"
+ # aliases
+ if [ -d "${SNAP_MOUNT_DIR}/bin" ]; then
+ find "${SNAP_MOUNT_DIR}/bin" -maxdepth 1 -lname "$snap" -delete
+ find "${SNAP_MOUNT_DIR}/bin" -maxdepth 1 -lname "$snap.*" -delete
+ fi
+ # generated binaries
+ rm -f "${SNAP_MOUNT_DIR}/bin/$snap"
+ rm -f "${SNAP_MOUNT_DIR}/bin/$snap".*
+ # snap mount dir
+ umount -l "${SNAP_MOUNT_DIR}/$snap/$rev" 2> /dev/null || true
+ rm -rf "${SNAP_MOUNT_DIR:?}/$snap/$rev"
+ rm -f "${SNAP_MOUNT_DIR}/$snap/current"
+ # snap data dir
+ rm -rf "/var/snap/$snap/$rev"
+ rm -rf "/var/snap/$snap/common"
+ rm -f "/var/snap/$snap/current"
+ # opportunistic remove (may fail if there are still revisions left)
+ for d in "${SNAP_MOUNT_DIR}/$snap" "/var/snap/$snap"; do
+ if [ -d "$d" ]; then
+ rmdir --ignore-fail-on-non-empty "$d"
+ fi
+ done
+ fi
+
+ echo "Removing $unit"
+ rm -f "/etc/systemd/system/$unit"
+ rm -f "/etc/systemd/system/multi-user.target.wants/$unit"
+ done
+
+ echo "Discarding preserved snap namespaces"
+ # opportunistic as those might not be actually mounted
+ for mnt in /run/snapd/ns/*.mnt; do
+ umount -l "$mnt" || true
+ rm -f "$mnt"
+ done
+ for fstab in /run/snapd/ns/*.fstab; do
+ rm -f "$fstab"
+ done
+ umount -l /run/snapd/ns/ || true
+
+
+ echo "Removing downloaded snaps"
+ rm -rf /var/lib/snapd/snaps/*
+
+ echo "Final directory cleanup"
+ rm -rf "${SNAP_MOUNT_DIR}"
+ rm -rf /var/snap
+
+ echo "Removing leftover snap shared state data"
+ rm -rf /var/lib/snapd/desktop/applications/*
+ rm -rf /var/lib/snapd/seccomp/bpf/*
+ rm -rf /var/lib/snapd/device/*
+ rm -rf /var/lib/snapd/assertions/*
+
+ echo "Removing snapd catalog cache"
+ rm -f /var/cache/snapd/*
+}
+
+while [ -n "$1" ]; do
+ case "$1" in
+ --help)
+ show_help
+ exit
+ ;;
+ --snap-mount-dir=*)
+ SNAP_MOUNT_DIR=${1#*=}
+ SNAP_UNIT_PREFIX=$(systemd-escape -p "$SNAP_MOUNT_DIR")
+ shift
+ ;;
+ --purge)
+ purge
+ shift
+ ;;
+ *)
+ echo "Unknown command: $1"
+ exit 1
+ ;;
+ esac
+done
diff -Nru snapd-2.28.5/packaging/fedora-rawhide/snapd.spec snapd-2.29.3/packaging/fedora-rawhide/snapd.spec
--- snapd-2.28.5/packaging/fedora-rawhide/snapd.spec 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.29.3/packaging/fedora-rawhide/snapd.spec 2017-11-09 18:16:16.000000000 +0000
@@ -0,0 +1,1630 @@
+# With Fedora, nothing is bundled. For everything else, bundling is used.
+# To use bundled stuff, use "--with vendorized" on rpmbuild
+%if 0%{?fedora}
+%bcond_with vendorized
+%else
+%bcond_without vendorized
+%endif
+
+# A switch to allow building the package with support for testkeys which
+# are used for the spread test suite of snapd.
+%bcond_with testkeys
+
+%global with_devel 1
+%global with_debug 1
+%global with_check 0
+%global with_unit_test 0
+%global with_test_keys 0
+
+# For the moment, we don't support all golang arches...
+%global with_goarches 0
+
+%if ! %{with vendorized}
+%global with_bundled 0
+%else
+%global with_bundled 1
+%endif
+
+%if ! %{with testkeys}
+%global with_test_keys 0
+%else
+%global with_test_keys 1
+%endif
+
+%if 0%{?with_debug}
+%global _dwz_low_mem_die_limit 0
+%else
+%global debug_package %{nil}
+%endif
+
+%global provider github
+%global provider_tld com
+%global project snapcore
+%global repo snapd
+# https://github.com/snapcore/snapd
+%global provider_prefix %{provider}.%{provider_tld}/%{project}/%{repo}
+%global import_path %{provider_prefix}
+
+%global snappy_svcs snapd.service snapd.socket snapd.autoimport.service snapd.refresh.timer snapd.refresh.service
+
+Name: snapd
+Version: 2.29.3
+Release: 0%{?dist}
+Summary: A transactional software package manager
+Group: System Environment/Base
+License: GPLv3
+URL: https://%{provider_prefix}
+%if ! 0%{?with_bundled}
+Source0: https://%{provider_prefix}/archive/%{version}/%{name}-%{version}.tar.gz
+%else
+Source0: https://%{provider_prefix}/releases/download/%{version}/%{name}_%{version}.vendor.orig.tar.xz
+%endif
+
+%if 0%{?with_goarches}
+# e.g. el6 has ppc64 arch without gcc-go, so EA tag is required
+ExclusiveArch: %{?go_arches:%{go_arches}}%{!?go_arches:%{ix86} x86_64 %{arm}}
+%else
+# Verified arches from snapd upstream
+ExclusiveArch: %{ix86} x86_64 %{arm} aarch64 ppc64le s390x
+%endif
+
+# If go_compiler is not set to 1, there is no virtual provide. Use golang instead.
+BuildRequires: %{?go_compiler:compiler(go-compiler)}%{!?go_compiler:golang}
+BuildRequires: systemd
+%{?systemd_requires}
+
+Requires: snap-confine%{?_isa} = %{version}-%{release}
+Requires: squashfs-tools
+# we need squashfs.ko loaded
+Requires: kmod(squashfs.ko)
+# bash-completion owns /usr/share/bash-completion/completions
+Requires: bash-completion
+
+# Force the SELinux module to be installed
+Requires: %{name}-selinux = %{version}-%{release}
+
+%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)
+BuildRequires: golang(github.com/ojii/gettext.go)
+BuildRequires: golang(github.com/seccomp/libseccomp-golang)
+BuildRequires: golang(golang.org/x/crypto/openpgp/armor)
+BuildRequires: golang(golang.org/x/crypto/openpgp/packet)
+BuildRequires: golang(golang.org/x/crypto/sha3)
+BuildRequires: golang(golang.org/x/crypto/ssh/terminal)
+BuildRequires: golang(golang.org/x/net/context)
+BuildRequires: golang(golang.org/x/net/context/ctxhttp)
+BuildRequires: golang(gopkg.in/check.v1)
+BuildRequires: golang(gopkg.in/macaroon.v1)
+BuildRequires: golang(gopkg.in/mgo.v2/bson)
+BuildRequires: golang(gopkg.in/retry.v1)
+BuildRequires: golang(gopkg.in/tomb.v2)
+BuildRequires: golang(gopkg.in/yaml.v2)
+%endif
+
+%description
+Snappy is a modern, cross-distribution, transactional package manager
+designed for working with self-contained, immutable packages.
+
+%package -n snap-confine
+Summary: Confinement system for snap applications
+License: GPLv3
+Group: System Environment/Base
+BuildRequires: autoconf
+BuildRequires: automake
+BuildRequires: libtool
+BuildRequires: gcc
+BuildRequires: gettext
+BuildRequires: gnupg
+BuildRequires: indent
+BuildRequires: pkgconfig(glib-2.0)
+BuildRequires: pkgconfig(libcap)
+BuildRequires: pkgconfig(libseccomp)
+BuildRequires: pkgconfig(libudev)
+BuildRequires: pkgconfig(systemd)
+BuildRequires: pkgconfig(udev)
+BuildRequires: xfsprogs-devel
+BuildRequires: glibc-static
+BuildRequires: libseccomp-static
+BuildRequires: valgrind
+BuildRequires: %{_bindir}/rst2man
+%if 0%{?fedora} >= 25
+# ShellCheck in F24 and older doesn't work
+BuildRequires: %{_bindir}/shellcheck
+%endif
+
+# Ensures older version from split packaging is replaced
+Obsoletes: snap-confine < 2.19
+
+%description -n snap-confine
+This package is used internally by snapd to apply confinement to
+the started snap applications.
+
+%package selinux
+Summary: SELinux module for snapd
+Group: System Environment/Base
+License: GPLv2+
+BuildArch: noarch
+BuildRequires: selinux-policy, selinux-policy-devel
+Requires(post): selinux-policy-base >= %{_selinux_policy_version}
+Requires(post): policycoreutils
+Requires(post): policycoreutils-python-utils
+Requires(pre): libselinux-utils
+Requires(post): libselinux-utils
+
+%description selinux
+This package provides the SELinux policy module to ensure snapd
+runs properly under an environment with SELinux enabled.
+
+
+%if 0%{?with_devel}
+%package devel
+Summary: %{summary}
+BuildArch: noarch
+
+%if 0%{?with_check} && ! 0%{?with_bundled}
+%endif
+
+%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)
+Requires: golang(github.com/ojii/gettext.go)
+Requires: golang(github.com/seccomp/libseccomp-golang)
+Requires: golang(golang.org/x/crypto/openpgp/armor)
+Requires: golang(golang.org/x/crypto/openpgp/packet)
+Requires: golang(golang.org/x/crypto/sha3)
+Requires: golang(golang.org/x/crypto/ssh/terminal)
+Requires: golang(golang.org/x/net/context)
+Requires: golang(golang.org/x/net/context/ctxhttp)
+Requires: golang(gopkg.in/check.v1)
+Requires: golang(gopkg.in/macaroon.v1)
+Requires: golang(gopkg.in/mgo.v2/bson)
+Requires: golang(gopkg.in/retry.v1)
+Requires: golang(gopkg.in/tomb.v2)
+Requires: golang(gopkg.in/yaml.v2)
+%else
+# These Provides are unversioned because the sources in
+# the bundled tarball are unversioned (they go by git commit)
+# *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))
+Provides: bundled(golang(github.com/mvo5/libseccomp-golang))
+Provides: bundled(golang(github.com/ojii/gettext.go))
+Provides: bundled(golang(golang.org/x/crypto/openpgp/armor))
+Provides: bundled(golang(golang.org/x/crypto/openpgp/packet))
+Provides: bundled(golang(golang.org/x/crypto/sha3))
+Provides: bundled(golang(golang.org/x/crypto/ssh/terminal))
+Provides: bundled(golang(golang.org/x/net/context))
+Provides: bundled(golang(golang.org/x/net/context/ctxhttp))
+Provides: bundled(golang(gopkg.in/check.v1))
+Provides: bundled(golang(gopkg.in/macaroon.v1))
+Provides: bundled(golang(gopkg.in/mgo.v2/bson))
+Provides: bundled(golang(gopkg.in/retry.v1))
+Provides: bundled(golang(gopkg.in/tomb.v2))
+Provides: bundled(golang(gopkg.in/yaml.v2))
+%endif
+
+# Generated by gofed
+Provides: golang(%{import_path}/arch) = %{version}-%{release}
+Provides: golang(%{import_path}/asserts) = %{version}-%{release}
+Provides: golang(%{import_path}/asserts/assertstest) = %{version}-%{release}
+Provides: golang(%{import_path}/asserts/signtool) = %{version}-%{release}
+Provides: golang(%{import_path}/asserts/snapasserts) = %{version}-%{release}
+Provides: golang(%{import_path}/asserts/sysdb) = %{version}-%{release}
+Provides: golang(%{import_path}/asserts/systestkeys) = %{version}-%{release}
+Provides: golang(%{import_path}/boot) = %{version}-%{release}
+Provides: golang(%{import_path}/boot/boottest) = %{version}-%{release}
+Provides: golang(%{import_path}/client) = %{version}-%{release}
+Provides: golang(%{import_path}/cmd) = %{version}-%{release}
+Provides: golang(%{import_path}/daemon) = %{version}-%{release}
+Provides: golang(%{import_path}/dirs) = %{version}-%{release}
+Provides: golang(%{import_path}/errtracker) = %{version}-%{release}
+Provides: golang(%{import_path}/httputil) = %{version}-%{release}
+Provides: golang(%{import_path}/i18n) = %{version}-%{release}
+Provides: golang(%{import_path}/image) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/apparmor) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/backends) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/builtin) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/dbus) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/ifacetest) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/kmod) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/mount) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/policy) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/seccomp) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/systemd) = %{version}-%{release}
+Provides: golang(%{import_path}/interfaces/udev) = %{version}-%{release}
+Provides: golang(%{import_path}/logger) = %{version}-%{release}
+Provides: golang(%{import_path}/osutil) = %{version}-%{release}
+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}
+Provides: golang(%{import_path}/overlord/hookstate) = %{version}-%{release}
+Provides: golang(%{import_path}/overlord/hookstate/ctlcmd) = %{version}-%{release}
+Provides: golang(%{import_path}/overlord/hookstate/hooktest) = %{version}-%{release}
+Provides: golang(%{import_path}/overlord/ifacestate) = %{version}-%{release}
+Provides: golang(%{import_path}/overlord/patch) = %{version}-%{release}
+Provides: golang(%{import_path}/overlord/snapstate) = %{version}-%{release}
+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}/release) = %{version}-%{release}
+Provides: golang(%{import_path}/snap) = %{version}-%{release}
+Provides: golang(%{import_path}/snap/snapdir) = %{version}-%{release}
+Provides: golang(%{import_path}/snap/snapenv) = %{version}-%{release}
+Provides: golang(%{import_path}/snap/snaptest) = %{version}-%{release}
+Provides: golang(%{import_path}/snap/squashfs) = %{version}-%{release}
+Provides: golang(%{import_path}/store) = %{version}-%{release}
+Provides: golang(%{import_path}/strutil) = %{version}-%{release}
+Provides: golang(%{import_path}/systemd) = %{version}-%{release}
+Provides: golang(%{import_path}/tests/lib/fakestore/refresh) = %{version}-%{release}
+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
+%{summary}
+
+This package contains library source intended for
+building other packages which use import path with
+%{import_path} prefix.
+%endif
+
+%if 0%{?with_unit_test} && 0%{?with_devel}
+%package unit-test-devel
+Summary: Unit tests for %{name} package
+
+%if 0%{?with_check}
+#Here comes all BuildRequires: PACKAGE the unit tests
+#in %%check section need for running
+%endif
+
+%if 0%{?with_check} && ! 0%{?with_bundled}
+BuildRequires: golang(github.com/mvo5/goconfigparser)
+%endif
+
+%if ! 0%{?with_bundled}
+Requires: golang(github.com/mvo5/goconfigparser)
+%else
+Provides: bundled(golang(github.com/mvo5/goconfigparser))
+%endif
+
+# test subpackage tests code from devel subpackage
+Requires: %{name}-devel = %{version}-%{release}
+
+%description unit-test-devel
+%{summary}
+
+This package contains unit tests for project
+providing packages with %{import_path} prefix.
+%endif
+
+%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
+./mkversion.sh "%{version}-%{release}"
+
+# Build snapd
+mkdir -p src/github.com/snapcore
+ln -s ../../../ src/github.com/snapcore/snapd
+
+%if ! 0%{?with_bundled}
+export GOPATH=$(pwd):%{gopath}
+%else
+export GOPATH=$(pwd):$(pwd)/Godeps/_workspace:%{gopath}
+%endif
+
+GOFLAGS=
+%if 0%{?with_test_keys}
+GOFLAGS="$GOFLAGS -tags withtestkeys"
+%endif
+
+# We have to build snapd first to prevent the build from
+# building various things from the tree without additional
+# set tags.
+%gobuild -o bin/snapd $GOFLAGS %{import_path}/cmd/snapd
+%gobuild -o bin/snap $GOFLAGS %{import_path}/cmd/snap
+%gobuild -o bin/snapctl $GOFLAGS %{import_path}/cmd/snapctl
+# build snap-exec and snap-update-ns completely static for base snaps
+CGO_ENABLED=0 %gobuild -o bin/snap-exec $GOFLAGS %{import_path}/cmd/snap-exec
+%gobuild -o bin/snap-update-ns --ldflags '-extldflags "-static"' $GOFLAGS %{import_path}/cmd/snap-update-ns
+
+# 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 $GOFLAGS %{import_path}/cmd/snap-seccomp
+
+# Build SELinux module
+pushd ./data/selinux
+make SHARE="%{_datadir}" TARGETS="snappy"
+popd
+
+# Build snap-confine
+pushd ./cmd
+# FIXME This is a hack to get rid of a patch we have to ship for the
+# Fedora package at the moment as /usr/lib/rpm/redhat/redhat-hardened-ld
+# accidentially adds -pie for static executables. See
+# https://bugzilla.redhat.com/show_bug.cgi?id=1343892 for a few more
+# details. To prevent this from happening we drop the linker
+# script and define our LDFLAGS manually for now.
+export LDFLAGS="-Wl,-z,relro -z now"
+autoreconf --force --install --verbose
+# selinux support is not yet available, for now just disable apparmor
+# FIXME: add --enable-caps-over-setuid as soon as possible (setuid discouraged!)
+%configure \
+ --disable-apparmor \
+ --libexecdir=%{_libexecdir}/snapd/ \
+ --with-snap-mount-dir=%{_sharedstatedir}/snapd/snap \
+ --with-merged-usr
+
+%make_build
+popd
+
+# Build systemd units
+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
+install -d -p %{buildroot}%{_mandir}/man1
+install -d -p %{buildroot}%{_unitdir}
+install -d -p %{buildroot}%{_sysconfdir}/profile.d
+install -d -p %{buildroot}%{_sysconfdir}/sysconfig
+install -d -p %{buildroot}%{_sharedstatedir}/snapd/assertions
+install -d -p %{buildroot}%{_sharedstatedir}/snapd/desktop/applications
+install -d -p %{buildroot}%{_sharedstatedir}/snapd/device
+install -d -p %{buildroot}%{_sharedstatedir}/snapd/hostfs
+install -d -p %{buildroot}%{_sharedstatedir}/snapd/mount
+install -d -p %{buildroot}%{_sharedstatedir}/snapd/seccomp/bpf
+install -d -p %{buildroot}%{_sharedstatedir}/snapd/snaps
+install -d -p %{buildroot}%{_sharedstatedir}/snapd/snap/bin
+install -d -p %{buildroot}%{_localstatedir}/snap
+install -d -p %{buildroot}%{_localstatedir}/cache/snapd
+install -d -p %{buildroot}%{_datadir}/selinux/devel/include/contrib
+install -d -p %{buildroot}%{_datadir}/selinux/packages
+
+# Install snap and snapd
+install -p -m 0755 bin/snap %{buildroot}%{_bindir}
+install -p -m 0755 bin/snap-exec %{buildroot}%{_libexecdir}/snapd
+install -p -m 0755 bin/snapctl %{buildroot}%{_bindir}/snapctl
+install -p -m 0755 bin/snapd %{buildroot}%{_libexecdir}/snapd
+install -p -m 0755 bin/snap-update-ns %{buildroot}%{_libexecdir}/snapd
+install -p -m 0755 bin/snap-seccomp %{buildroot}%{_libexecdir}/snapd
+
+# Install SELinux module
+install -p -m 0644 data/selinux/snappy.if %{buildroot}%{_datadir}/selinux/devel/include/contrib
+install -p -m 0644 data/selinux/snappy.pp.bz2 %{buildroot}%{_datadir}/selinux/packages
+
+# Install snap(1) man page
+bin/snap help --man > %{buildroot}%{_mandir}/man1/snap.1
+
+# Install the "info" data file with snapd version
+install -m 644 -D data/info %{buildroot}%{_libexecdir}/snapd/info
+
+# 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
+%make_install
+# Undo the 0000 permissions, they are restored in the files section
+chmod 0755 %{buildroot}%{_sharedstatedir}/snapd/void
+# We don't use AppArmor
+rm -rfv %{buildroot}%{_sysconfdir}/apparmor.d
+# ubuntu-core-launcher is dead
+rm -fv %{buildroot}%{_bindir}/ubuntu-core-launcher
+popd
+
+# Install all systemd units
+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}/snapd.snap-repair.*
+rm -fv %{buildroot}%{_unitdir}/snapd.core-fixup.*
+popd
+
+# Remove snappy core specific scripts
+rm %{buildroot}%{_libexecdir}/snapd/snapd.core-fixup.sh
+
+# Install environ-tweaking snippet
+pushd ./data/env
+%make_install
+popd
+
+# Disable re-exec by default
+echo 'SNAP_REEXEC=0' > %{buildroot}%{_sysconfdir}/sysconfig/snapd
+
+# Install snap management script
+install -pm 0755 packaging/fedora/snap-mgmt.sh %{buildroot}%{_libexecdir}/snapd/snap-mgmt
+
+# Create state.json file to be ghosted
+touch %{buildroot}%{_sharedstatedir}/snapd/state.json
+
+# source codes for building projects
+%if 0%{?with_devel}
+install -d -p %{buildroot}/%{gopath}/src/%{import_path}/
+echo "%%dir %%{gopath}/src/%%{import_path}/." >> devel.file-list
+# find all *.go but no *_test.go files and generate devel.file-list
+for file in $(find . -iname "*.go" -o -iname "*.s" \! -iname "*_test.go") ; do
+ echo "%%dir %%{gopath}/src/%%{import_path}/$(dirname $file)" >> devel.file-list
+ install -d -p %{buildroot}/%{gopath}/src/%{import_path}/$(dirname $file)
+ cp -pav $file %{buildroot}/%{gopath}/src/%{import_path}/$file
+ echo "%%{gopath}/src/%%{import_path}/$file" >> devel.file-list
+done
+%endif
+
+# testing files for this project
+%if 0%{?with_unit_test} && 0%{?with_devel}
+install -d -p %{buildroot}/%{gopath}/src/%{import_path}/
+# find all *_test.go files and generate unit-test.file-list
+for file in $(find . -iname "*_test.go"); do
+ echo "%%dir %%{gopath}/src/%%{import_path}/$(dirname $file)" >> devel.file-list
+ install -d -p %{buildroot}/%{gopath}/src/%{import_path}/$(dirname $file)
+ cp -pav $file %{buildroot}/%{gopath}/src/%{import_path}/$file
+ echo "%%{gopath}/src/%%{import_path}/$file" >> unit-test-devel.file-list
+done
+
+# Install additional testdata
+install -d %{buildroot}/%{gopath}/src/%{import_path}/cmd/snap/test-data/
+cp -pav cmd/snap/test-data/* %{buildroot}/%{gopath}/src/%{import_path}/cmd/snap/test-data/
+echo "%%{gopath}/src/%%{import_path}/cmd/snap/test-data" >> unit-test-devel.file-list
+%endif
+
+%if 0%{?with_devel}
+sort -u -o devel.file-list devel.file-list
+%endif
+
+%check
+# snapd tests
+%if 0%{?with_check} && 0%{?with_unit_test} && 0%{?with_devel}
+%if ! 0%{?with_bundled}
+export GOPATH=%{buildroot}/%{gopath}:%{gopath}
+%else
+export GOPATH=%{buildroot}/%{gopath}:$(pwd)/Godeps/_workspace:%{gopath}
+%endif
+%gotest %{import_path}/...
+%endif
+
+# snap-confine tests (these always run!)
+pushd ./cmd
+make check
+popd
+
+%files
+#define license tag if not already defined
+%{!?_licensedir:%global license %doc}
+%license COPYING
+%doc README.md docs/*
+%{_bindir}/snap
+%{_bindir}/snapctl
+%dir %{_libexecdir}/snapd
+%{_libexecdir}/snapd/snapd
+%{_libexecdir}/snapd/snap-exec
+%{_libexecdir}/snapd/info
+%{_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
+%{_unitdir}/snapd.autoimport.service
+%{_unitdir}/snapd.refresh.service
+%{_unitdir}/snapd.refresh.timer
+%config(noreplace) %{_sysconfdir}/sysconfig/snapd
+%dir %{_sharedstatedir}/snapd
+%dir %{_sharedstatedir}/snapd/assertions
+%dir %{_sharedstatedir}/snapd/desktop
+%dir %{_sharedstatedir}/snapd/desktop/applications
+%dir %{_sharedstatedir}/snapd/device
+%dir %{_sharedstatedir}/snapd/hostfs
+%dir %{_sharedstatedir}/snapd/mount
+%dir %{_sharedstatedir}/snapd/seccomp
+%dir %{_sharedstatedir}/snapd/seccomp/bpf
+%dir %{_sharedstatedir}/snapd/snaps
+%dir %{_sharedstatedir}/snapd/snap
+%dir /var/cache/snapd
+%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
+%license COPYING
+%dir %{_libexecdir}/snapd
+# For now, we can't use caps
+# FIXME: Switch to "%%attr(0755,root,root) %%caps(cap_sys_admin=pe)" asap!
+%attr(4755,root,root) %{_libexecdir}/snapd/snap-confine
+%{_libexecdir}/snapd/snap-discard-ns
+%{_libexecdir}/snapd/snap-seccomp
+%{_libexecdir}/snapd/snap-update-ns
+%{_libexecdir}/snapd/system-shutdown
+%{_mandir}/man1/snap-confine.1*
+%{_mandir}/man5/snap-discard-ns.5*
+%{_prefix}/lib/udev/snappy-app-dev
+%{_udevrulesdir}/80-snappy-assign.rules
+%attr(0000,root,root) %{_sharedstatedir}/snapd/void
+
+
+%files selinux
+%license data/selinux/COPYING
+%doc data/selinux/README.md
+%{_datadir}/selinux/packages/snappy.pp.bz2
+%{_datadir}/selinux/devel/include/contrib/snappy.if
+
+%if 0%{?with_devel}
+%files devel -f devel.file-list
+%license COPYING
+%doc README.md
+%dir %{gopath}/src/%{provider}.%{provider_tld}/%{project}
+%endif
+
+%if 0%{?with_unit_test} && 0%{?with_devel}
+%files unit-test-devel -f unit-test-devel.file-list
+%license COPYING
+%doc README.md
+%endif
+
+%post
+%systemd_post %{snappy_svcs}
+# If install, test if snapd socket and timer are enabled.
+# If enabled, then attempt to start them. This will silently fail
+# in chroots or other environments where services aren't expected
+# to be started.
+if [ $1 -eq 1 ] ; then
+ if systemctl -q is-enabled snapd.socket > /dev/null 2>&1 ; then
+ systemctl start snapd.socket > /dev/null 2>&1 || :
+ fi
+ if systemctl -q is-enabled snapd.refresh.timer > /dev/null 2>&1 ; then
+ systemctl start snapd.refresh.timer > /dev/null 2>&1 || :
+ fi
+fi
+
+%preun
+%systemd_preun %{snappy_svcs}
+
+# Remove all Snappy content if snapd is being fully uninstalled
+if [ $1 -eq 0 ]; then
+ %{_libexecdir}/snapd/snap-mgmt --purge || :
+fi
+
+
+%postun
+%systemd_postun_with_restart %{snappy_svcs}
+
+%pre selinux
+%selinux_relabel_pre
+
+%post selinux
+%selinux_modules_install %{_datadir}/selinux/packages/snappy.pp.bz2
+%selinux_relabel_post
+
+%postun selinux
+%selinux_modules_uninstall snappy
+if [ $1 -eq 0 ]; then
+ %selinux_relabel_post
+fi
+
+
+%changelog
+* Thu Nov 09 2017 Michael Vogt
+- New upstream release 2.29.3
+ - daemon: cherry-picked /v2/logs fixes
+ - cmd/snap-confine: Respect biarch nature of libdirs
+ - cmd/snap-confine: Ensure snap-confine is allowed to access os-
+ release
+ - interfaces: fix udev tagging for hooks
+ - cmd: fix re-exec bug with classic confinement for host snapd
+ - tests: disable xdg-open-compat test
+ - cmd/snap-confine: add slave PTYs and let devpts newinstance
+ perform mediation
+ - interfaces/many: misc policy updates for browser-support, cups-
+ control and network-status
+ - interfaces/raw-usb: match on SUBSYSTEM, not SUBSYSTEMS
+ - tests: fix security-device-cgroup* tests on devices with
+ framebuffer
+
+* Fri Nov 03 2017 Michael Vogt
+- New upstream release 2.29.2
+ - snapctl: disable stop/start/restart (2.29)
+ - cmd/snap-update-ns: fix collection of changes made
+
+* Fri Nov 03 2017 Michael Vogt
+- New upstream release 2.29.1
+ - interfaces: fix incorrect signature of ofono DBusPermanentSlot
+ - interfaces/serial-port: udev tag plugged slots that have just
+ 'path' via KERNEL
+ - interfaces/hidraw: udev tag plugged slots that have just 'path'
+ via KERNEL
+ - interfaces/uhid: unconditionally add existing uhid device to the
+ device cgroup
+ - cmd/snap-update-ns: fix mount rules for font sharing
+ - tests: disable refresh-undo test on trusty for now
+ - tests: use `snap change --last=install` in snapd-reexec test
+ - Revert " wrappers: fail install if exec-line cannot be re-written
+ - interfaces: don't udev tag devmode or classic snaps
+ - many: make ignore-validation sticky and send the flag with refresh
+ requests
+
+* Mon Oct 30 2017 Michael Vogt
+- New upstream release 2.29
+ - interfaces/many: miscellaneous updates based on feedback from the
+ field
+ - snap-confine: allow reading uevents from any where in /sys
+ - spread: add bionic beaver
+ - debian: make packaging/ubuntu-14.04/copyright a real file again
+ - tests: cherry pick the fix for services test into 2.29
+ - cmd/snap-update-ns: initialize logger
+ - hooks/configure: queue service restarts
+ - snap-{confine,seccomp}: make @unrestricted fully unrestricted
+ - interfaces: clean system apparmor cache on core device
+ - debian: do not build static snap-exec on powerpc
+ - snap-confine: increase sanity_timeout to 6s
+ - snapctl: cherry pick service commands changes
+ - cmd/snap: tell translators about arg names and descs req's
+ - systemd: run all mount units before snapd.service to avoid race
+ - store: add a test to show auth failures are forwarded by doRequest
+ - daemon: convert ErrInvalidCredentials to a 401 Unauthorized error.
+ - store: forward on INVALID_CREDENTIALS error as
+ ErrInvalidCredentials
+ - daemon: generate a forbidden response message if polkit dialog is
+ dismissed
+ - daemon: Allow Polkit authorization to cancel changes.
+ - travis: switch to container based test runs
+ - interfaces: reduce duplicated code in interface tests mocks
+ - tests: improve revert related testing
+ - interfaces: sanitize plugs and slots early in ReadInfo
+ - store: add download caching
+ - preserve TMPDIR and HOSTALIASES across snap-confine invocation
+ - snap-confine: init all arrays with `= {0,}`
+ - tests: adding test for network-manager interface
+ - interfaces/mount: don't generate legacy per-hook/per-app mount
+ profiles
+ - snap: introduce structured epochs
+ - tests: fix interfaces-cups-control test for cups-2.2.5
+ - snap-confine: cleanup incorrectly created nvidia udev tags
+ - cmd/snap-confine: update valid security tag regexp
+ - cmd/libsnap: enable two stranded tests
+ - cmd,packaging: enable apparmor on openSUSE
+ - overlord/ifacestate: refresh all security backends on startup
+ - interfaces/dbus: drop unneeded check for
+ release.ReleaseInfo.ForceDevMode
+ - dbus: ensure io.snapcraft.Launcher.service is created on re-
+ exec
+ - overlord/auth: continue for now supporting UBUNTU_STORE_ID if the
+ model is generic-classic
+ - snap-confine: add support for handling /dev/nvidia-modeset
+ - interfaces/network-control: remove incorrect rules for tun
+ - spread: allow setting SPREAD_DEBUG_EACH=0 to disable debug-each
+ section
+ - packaging: remove .mnt files on removal
+ - tests: fix econnreset scenario when the iptables rule was not
+ created
+ - tests: add test for lxd interface
+ - run-checks: use nakedret static checker to check for naked
+ returns on long functions
+ - progress: be more flexible in testing ansimeter
+ - interfaces: fix udev rules for tun
+ - many: implement our own ANSI-escape-using progress indicator
+ - snap-exec: update tests to follow main_test pattern
+ - snap: support "command: foo $ENV_STRING"
+ - packaging: update nvidia configure options
+ - snap: add new `snap pack` and use in tests
+ - cmd: correctly name the "Ubuntu" and "Arch" NVIDIA methods
+ - cmd: add autogen case for solus
+ - tests: do not use http://canihazip.com/ which appears to be down
+ - hooks: commands for controlling own services from snapctl
+ - snap: refactor cmdGet.Execute()
+ - interfaces/mount: make Change.Perform testable and test it
+ - interfaces/mount,cmd/snap-update-ns: move change code
+ - snap-confine: is_running_on_classic_distribution() looks into os-
+ release
+ - interfaces: misc updates for default, browser-support, home and
+ system-observe
+ - interfaces: deny lttng by default
+ - interfaces/lxd: lxd slot implementation can also be an app snap
+ - release,cmd,dirs: Redo the distro checks to take into account
+ distribution families
+ - cmd/snap: completion for alias and unalias
+ - snap-confine: add new SC_CLEANUP and use it
+ - snap: refrain from running filepath.Base on random strings
+ - cmd/snap-confine: put processes into freezer hierarchy
+ - wrappers: fail install if exec-line cannot be re-written
+ - cmd/snap-seccomp,osutil: make user/group lookup functions public
+ - snapstate: deal with snap user data in the /root/ directory
+ - interfaces: Enhance full-confinement support for biarch
+ distributions
+ - snap-confine: Only attempt to copy/mount NVIDIA libs when NVIDIA
+ is used
+ - packaging/fedora: Add Fedora 26, 27, and Rawhide symlinks
+ - overlord/snapstate: prefer a smaller corner case for doing the
+ wrong thing
+ - cmd/snap-repair: set user agent for snap-repair http requests
+ - packaging: bring down the delta between 14.04 and 16.04
+ - snap-confine: Ensure lib64 biarch directory is respected
+ - snap-confine: update apparmor rules for fedora based base snaps
+ - tests: Increase SNAPD_CONFIGURE_HOOK_TIMEOUT to 3 minutes to
+ install real snaps
+ - daemon: use client.Snap instead of map[string]interface{} for
+ snaps.
+ - hooks: rename refresh hook to post-refresh
+ - git: make the .gitingore file a bit more targeted
+ - interfaces/opengl: don't udev tag nvidia devices and use snap-
+ confine instead
+ - cmd/snap-{confine,update-ns}: apply mount profiles using snap-
+ update-ns
+ - cmd: update "make hack"
+ - interfaces/system-observe: allow clients to enumerate DBus
+ connection names
+ - snap-repair: implement `snap-repair {list,show}`
+ - dirs,interfaces: create snap-confine.d on demand when re-executing
+ - snap-confine: fix base snaps on core
+ - cmd/snap-repair: fix tests when running as root
+ - interfaces: add Connection type
+ - cmd/snap-repair: skip disabled repairs
+ - cmd/snap-repair: prefer leaking unmanaged fds on test failure over
+ closing random ones
+ - snap-repair: make `repair` binary available for repair scripts
+ - snap-repair: fix missing Close() in TestStatusHappy
+ - cmd/snap-confine,packaging: import snapd-generated policy
+ - cmd/snap: return empty document if snap has no configuration
+ - snap-seccomp: run secondary-arch tests via gcc-multilib
+ - snap: implement `snap {repair,repairs}` and pass-through to snap-
+ repair
+ - interfaces/builtin: allow receiving dbus messages
+ - snap-repair: implement `snap-repair {done,skip,retry}`
+ - data/completion: small tweak to snap completion snippet
+ - dirs: fix classic support detection
+ - cmd/snap-repair: integrate root public keys for repairs
+ - tests: fix ubuntu core services
+ - tests: add new test that checks that the compat snapd-xdg-open
+ works
+ - snap-confine: improve error message if core/u-core cannot be found
+ - tests: only run tests/regression/nmcli on amd64
+ - interfaces: mount host system fonts in desktop interface
+ - interfaces: enable partial apparmor support
+ - snapstate: auto-install missing base snaps
+ - spread: work around temporary packaging issue in debian sid
+ - asserts,cmd/snap-repair: introduce a mandatory summary for repairs
+ - asserts,cmd/snap-repair: represent RepairID internally as an int
+ - tests: test the real "xdg-open" from the core snap
+ - many: implement fetching sections and package names periodically.
+ - interfaces/network: allow using netcat as client
+ - snap-seccomp, osutil: use osutil.AtomicFile in snap-seccomp
+ - snap-seccomp: skip mknod syscall on arm64
+ - tests: add trivial canonical-livepatch test
+ - tests: add test that ensures that all core services are working
+ - many: add logger.MockLogger() and use it in the tests
+ - snap-repair: fix test failure in TestRepairHitsTimeout
+ - asserts: add empty values check in HeadersFromPrimaryKey
+ - daemon: remove unused installSnap var in test
+ - daemon: reach for Overlord.Loop less thanks to overlord.Mock
+ - snap-seccomp: manually resolve socket() call in tests
+ - tests: change regex used to validate installed ubuntu core snap
+ - cmd/snapctl: allow snapctl -h without a context (regression fix).
+ - many: use snapcore/snapd/i18n instead of i18n/dumb
+ - many: introduce asserts.NotFoundError replacing both ErrNotFound
+ and store.AssertionNotFoundError
+ - packaging: don't include any marcos in comments
+ - overlord: use overlord.Mock in more tests, make sure we check the
+ outcome of Settle
+ - tests: try to fix staging tests
+ - store: simplify api base url config
+ - systemd: add systemd.MockJournalctl()
+ - many: provide systemd.MockSystemctl() helper
+ - tests: improve the listing test to not fail for e.g. 2.28~rc2
+ - snapstate: give snapmgrTestSuite.settle() more time to settle
+ - tests: fix regex to check core version on snap list
+ - debian: update trusted account-keys check on 14.04 packaging
+ - interfaces: add udev netlink support to hardware-observe
+ - overlord: introduce Mock which enables to use Overlord.Settle for
+ settle in many more places
+ - snap-repair: execute the repair and capture logs/status
+ - tests: run the tests/unit/go everywhere
+ - daemon, snapstate: move ensureCore from daemon/api.go into
+ snapstate.go
+ - cmd/snap: get keys or root document
+ - spread.yaml: turn suse to manual given that it's breaking master
+ - many: configure store from state, reconfigure store at runtime
+ - osutil: AtomicWriter (an io.Writer), and io.Reader versions of
+ AtomicWrite*
+ - tests: check for negative syscalls in runBpf() and skip those
+ tests
+ - docs: use abolute path in PULL_REQUEST_TEMPLATE.md
+ - store: move device auth endpoint uris to config (#3831)
+
+* 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
+ - hooks: do not error when hook handler is not registered
+ - interfaces/alsa,pulseaudio: allow read on udev data for sound
+ - interfaces/optical-drive: read access to udev data for /dev/scd*
+ - interfaces/browser-support: read on /proc/vmstat and misc udev
+ data
+
+* Thu Aug 24 2017 Michael Vogt
+- New upstream release 2.27.4
+ - snap-seccomp: add secondary arch for unrestricted snaps as well
+
+* Fri Aug 18 2017 Michael Vogt
+- New upstream release 2.27.3
+ - 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
+ changes
+ - interfaces: don't crash if content slot has no attributes
+ - debian: do not build with -buildmode=pie on i386
+ - interfaces: backport broadcom-asic-control interface
+ - interfaces: allow /usr/bin/xdg-open in unity7
+ - 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
+ - tests: fix test failure on 14.04 due to old version of
+ flock
+ - updates for unity7/x11, browser-support, network-control,
+ 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
+
+* 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
+ - interfaces: add password-manager-service implicit classic interface
+ - interfaces/greengrass-support: adjust accesses now that have working
+ snap
+ - interfaces/many, cmd/snap-confine: miscellaneous policy updates
+ - interfaces/unity7: allow receiving media key events in (at least)
+ gnome-shell
+ - cmd: fix re-exec bug when starting from snapd 2.21
+ - tests: restore interfaces-account-control properly
+ - cmd: fix tests that assume /snap mount
+ - cmd: mark arch as non-reexecing distro
+ - snap-confine: don't share /etc/nsswitch from host
+ - store: talk to api.snapcraft.io for purchases
+ - hooks: support for install and remove hooks
+ - packaging: fix Fedora support
+ - tests: add bluetooth-control interface test
+ - store: talk to api.snapcraft.io for assertions
+ - tests: remove snapd before building from branch
+ - tests: add avahi-observe interface test
+ - store: orders API now checks if customer is ready
+ - cmd/snap: snap find only searches stable
+ - interfaces: updates default, mir, optical-observe, system-observe,
+ screen-inhibit-control and unity7
+ - tests: speedup prepare statement part 1
+ - store: do not send empty refresh requests
+ - asserts: fix error handling in snap-developer consistency check
+ - systemd: add explicit sync to snapd.core-fixup.sh
+ - snapd: generate snap cookies on startup
+ - cmd,client,daemon: expose "force devmode" in sysinfo
+ - many: introduce and use strutil.ListContains and also
+ strutil.SortedListContains
+ - assserts,overlord/assertstate: test we don't accept chains of
+ assertions founded on a self-signed key coming externally
+ - interfaces: enable access to bridge settings
+ - interfaces: fix copy-pasted iio vs io in io-ports-control
+ - cmd/snap-confine: various small fixes and tweaks to seccomp
+ support code
+ - interfaces: bring back seccomp argument filtering
+ - systemd, osutil: rework systemd logs in preparation for services
+ commands
+ - tests: store /etc/systemd/system/snap-*core*.mount in snapd-
+ state.tar.gz
+ - tests: shellcheck improvements for tests/main tasks - first set of
+ tests
+ - cmd/snap: `--last` for abort and watch, and aliases
+ (search→find, change→tasks)
+ - tests: shellcheck improvements for tests/lib scripts
+ - tests: create ramdisk if it's not present
+ - tests: shellcheck improvements for nightly upgrade and regressions
+ tests
+ - snapd: fix for snapctl get panic on null config values.
+ - tests: fix for rng-tools service not restarting
+ - systemd: add snapd.core-fixup.service unit
+ - cmd: avoid using current symlink in InternalToolPath
+ - tests: fix timeout issue for test refresh core with hanging …
+ - intefaces: control bridged vlan/ppoe-tagged traffic
+ - cmd/snap: include snap type in notes
+ - overlord/state: Abort() only visits each task once
+ - tests: extend find-private test to cover more cases
+ - snap-seccomp: skip socket() tests on systems that use socketcall()
+ instead of socket()
+ - many: support snap title as localized/title-cased name
+ - snap-seccomp: deal with mknod on aarch64 in the seccomp tests
+ - interfaces: put base policy fragments inside each interface
+ - asserts: introduce NewDecoderWithTypeMaxBodySize
+ - tests: fix snapd-notify when it takes more time to restart
+ - snap-seccomp: fix snap-seccomp tests in artful
+ - tests: fix for create-key task to avoid rng-tools service ramains
+ alive
+ - snap-seccomp: make sure snap-seccomp writes the bpf file
+ atomically
+ - tests: do not disable ipv6 on core systems
+ - arch: the kernel architecture name is armv7l instead of armv7
+ - snap-confine: ensure snap-confine waits some seconds for seccomp
+ security profiles
+ - tests: shellcheck improvements for tests/nested tasks
+ - wrappers: add SyslogIdentifier to the service unit files.
+ - tests: shellcheck improvements for unit tasks
+ - asserts: implement FindManyTrusted as well
+ - asserts: open up and optimize Encoder to help avoiding unnecessary
+ copying
+ - interfaces: simplify snap-confine by just loading pre-generated
+ bpf code
+ - tests: restart rng-tools services after few seconds
+ - interfaces, tests: add mising dbus abstraction to system-observe
+ and extend spread test
+ - store: change main store host to api.snapcraft.io
+ - overlord/cmdstate: new package for running commands as tasks.
+ - spread: help libapt resolve installing libudev-dev
+ - tests: show the IP from .travis.yaml
+ - tests/main: use pkgdb function in more test cases
+ - cmd,daemon: add debug command for displaying the base policy
+ - tests: prevent quoting error on opensuse
+ - tests: fix nightly suite
+ - tests: add linode-sru backend
+ - snap-confine: validate SNAP_NAME against security tag
+ - tests: fix ipv6 disable for ubuntu-core
+ - tests: extend core-revert test to cover bluez issues
+ - interfaces/greengrass-support: add support for Amazon Greengrass
+ as a snap
+ - asserts: support timestamp and optional disabled header on repair
+ - tests: reboot after upgrading to snapd on the -proposed pocket
+ - many: fix test cases to work with different DistroLibExecDir
+ - tests: reenable help test on ubuntu and debian systems
+ - packaging/{opensuse,fedora}: allow package build with testkeys
+ included
+ - tests/lib: generalize RPM build support
+ - interfaces/builtin: sync connected slot and permanent slot snippet
+ - tests: fix snap create-key by restarting automatically rng-tools
+ - many: switch to use http numeric statuses as agreed
+ - debian: add missing Type=notify in 14.04 packaging
+ - tests: mark interfaces-openvswitch as manual due to prepare errors
+ - debian: unify built_using between the 14.04 and 16.04 packaging
+ branch
+ - tests: pull from urandom when real entropy is not enough
+ - tests/main/manpages: install missing man package
+ - tests: add refresh --time output check
+ - debian: add missing "make -C data/systemd clean"
+ - tests: fix for upgrade test when it is repeated
+ - tests/main: use dir abstraction in a few more test cases
+ - tests/main: check for confinement in a few more interface tests
+ - spread: add fedora snap bin dir to global PATH
+ - tests: check that locale-control is not present on core
+ - many: snapctl outside hooks
+ - tests: add whoami check
+ - interfaces: compose the base declaration from interfaces
+ - tests: fix spread flaky tests linode
+ - tests,packaging: add package build support for openSUSE
+ - many: slight improvement of some snap error messaging
+ - errtracker: Include /etc/apparmor.d/usr.lib.snap-confine md5sum in
+ err reports
+ - tests: fix for the test postrm-purge
+ - tests: restoring the /etc/environment and service units config for
+ each test
+ - daemon: make snapd a "Type=notify" daemon and notify when startup
+ is done
+ - cmd/snap-confine: add support for --base snap
+ - many: derive implicit slots from interface meta-data
+ - tests: add core revert test
+ - tests,packaging: add package build support for Fedora for our
+ spread setup
+ - interfaces: move base declaration to the policy sub-package
+ - tests: fix for snapd-reexec test cheking for restart info on debug
+ log
+ - tests: show available entropy on error
+ - tests: clean journalctl logs on trusty
+ - tests: fix econnreset on staging
+ - tests: modify core before calling set
+ - tests: add snap-confine privilege test
+ - tests: add staging snap-id
+ - interfaces/builtin: silence ptrace denial for network-manager
+ - tests: add alsa interface spread test
+ - tests: prefer ipv4 over ipv6
+ - tests: fix for econnreset test checking that the download already
+ started
+ - httputil,store: extract retry code to httputil, reorg usages
+ - errtracker: report if snapd did re-execute itself
+ - errtracker: include bits of snap-confine apparmor profile
+ - tests: take into account staging snap-ids for snap-info
+ - cmd: add stub new snap-repair command and add timer
+ - many: stop "snap refresh $x --channel invalid" from working
+ - interfaces: revert "interfaces: re-add reverted ioctl and quotactl
+ - snapstate: consider connect/disconnect tasks in
+ CheckChangeConflict.
+ - interfaces: disable "mknod |N" in the default seccomp template
+ again
+ - interfaces,overlord/ifacestate: make sure installing slots after
+ plugs works similarly to plugs after slots
+ - interfaces/seccomp: add bind() syscall for forced-devmode systems
+ - packaging/fedora: Sync packaging from Fedora Dist-Git
+ - tests: move static and unit tests to spread task
+ - many: error types should be called FooError, not ErrFoo.
+ - partition: add directory sync to the save uboot.env file code
+ - cmd: test everything (100% coverage \o/)
+ - many: make shell scripts shellcheck-clean
+ - tests: remove additional setup for docker on core
+ - interfaces: add summary to each interface
+ - many: remove interface meta-data from list of connections
+ - logger (& many more, to accommodate): drop explicit syslog.
+ - packaging: import packaging bits for opensuse
+ - snapstate,many: implement snap install --unaliased
+ - tests/lib: abstract build dependency installation a bit more
+ - interfaces, osutil: move flock code from interfaces/mount to
+ osutil
+ - cmd: auto import assertions only from ext4,vfat file systems
+ - many: refactor in preparation for 'snap start'
+ - overlord/snapstate: have an explicit code path last-refresh
+ unset/zero => immediately refresh try
+ - tests: fixes for executions using the staging store
+ - tests: use pollinate to seed the rng
+ - cmd/snap,tests: show the sha3-384 of the snap for snap info
+ --verbose SNAP-FILE
+ - asserts: simplify and adjust repair assertion definition
+ - cmd/snap,tests: show the snap id if available in snap info
+ - daemon,overlord/auth: store from model assertion wins
+ - cmd/snap,tests/main: add confinement switch instead of spread
+ system blacklisting
+ - many: cleanup MockCommands and don't leave a process around after
+ hookstate tests
+ - tests: update listing test to the core version number schema
+ - interfaces: allow snaps to use the timedatectl utility
+ - packaging: Add Fedora packaging files
+ - tests/libs: add distro_auto_remove_packages function
+ - cmd/snap: correct devmode note for anomalous state
+ - tests/main/snap-info: use proper pkgdb functions to install distro
+ packages
+ - tests/lib: use mktemp instead of tempfile to work cross-distro
+ - tests: abstract common dirs which differ on distributions
+ - many: model and expose interface meta-data.
+ - overlord: make config defaults from gadget work also at first boot
+ - interfaces/log-observe: allow using journalctl from hostfs for
+ classic distro
+ - partition,snap: add support for android boot
+ - errtracker: small simplification around readMachineID
+ - snap-confine: move rm_rf_tmp to test-utils.
+ - tests/lib: introduce pkgdb helper library
+ - errtracker: try multiple paths to read machine-id
+ - overlord/hooks: make sure only one hook for given snap is executed
+ at a time.
+ - cmd/snap-confine: use SNAP_MOUNT_DIR to setup /snap inside the
+ confinement env
+ - tests: bump kill-timeout and remove quiet call on build
+ - tests/lib/snaps: add a test store snap with a passthrough
+ configure hook
+ - daemon: teach the daemon to wait on active connections when
+ shutting down
+ - tests: remove unit tests task
+ - tests/main/completion: source from /usr/share/bash-completion
+ - assertions: add "repair" assertion
+ - interfaces/seccomp: document Backend.NewSpecification
+ - wrappers: make StartSnapServices cleanup any services that were
+ added if a later one fails
+ - overlord/snapstate: avoid creating command aliases for daemons
+ - vendor: remove unused packages
+ - vendor,partition: fix panics from uenv
+ - cmd,interfaces/mount: run snap-update-ns and snap-discard-ns from
+ core if possible
+ - daemon: do not allow to install ubuntu-core anymore
+ - wrappers: service start/stop were inconsistent
+ - tests: fix failing tests (snap core version, syslog changes)
+ - cmd/snap-update-ns: add actual implementation
+ - tests: improve entropy also for ubuntu
+ - cmd/snap-confine: use /etc/ssl from the core snap
+ - wrappers: don't convert between []byte and string needlessly.
+ - hooks: default timeout
+ - overlord/snapstate: Enable() was ignoring the flags from the
+ snap's state, resulting in losing "devmode" on disable/enable.
+ - difs,interfaces/mount: add support for locking namespaces
+ - interfaces/mount: keep track of kept mount entries
+ - tests/main: move a bunch of greps over to MATCH
+ - interfaces/builtin: make all interfaces private
+ - interfaces/mount: spell unmount correctly
+ - tests: allow 16-X.Y.Z version of core snap
+ - the timezone_control interface only allows changing /etc/timezone
+ and /etc/writable/timezone. systemd-timedated also updated the
+ link of /etc/localtime and /etc/writable/localtime ... allow
+ access to this file too
+ - cmd/snap-confine: aggregate operations holding global lock
+ - 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)
+
+* Sun May 21 2017 Neal Gompa - 2.26.3-2
+- Fix error in script for removing Snappy content (RH#1444422)
+- Adjust changelog bug references to be specific on origin
+
+* Wed May 17 2017 Neal Gompa - 2.26.3-1
+- Update to snapd 2.26.3
+- Drop merged and unused patches
+- Cover more Snappy content for proper erasure on final uninstall (RH#1444422)
+- Add temporary fix to ensure generated seccomp profiles don't break snapctl
+
+* Mon May 01 2017 Neal Gompa