diff -Nru snapd-2.41+19.10.1/arch/arch.go snapd-2.42.1+19.10/arch/arch.go
--- snapd-2.41+19.10.1/arch/arch.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/arch/arch.go 2019-10-30 12:17:43.000000000 +0000
@@ -33,33 +33,30 @@
// change the architecture. This is important to e.g. install
// armhf snaps onto a armhf image that is generated on an amd64
// machine
-var arch = ArchitectureType(ubuntuArchFromGoArch(runtime.GOARCH))
+var arch = ArchitectureType(dpkgArchFromGoArch(runtime.GOARCH))
// SetArchitecture allows overriding the auto detected Architecture
func SetArchitecture(newArch ArchitectureType) {
arch = newArch
}
-// FIXME: rename all Ubuntu*Architecture() to SnapdArchitecture()
-// (or DpkgArchitecture)
-
-// UbuntuArchitecture returns the debian equivalent architecture for the
+// DpkgArchitecture returns the debian equivalent architecture for the
// currently running architecture.
//
// If the architecture does not map any debian architecture, the
// GOARCH is returned.
-func UbuntuArchitecture() string {
+func DpkgArchitecture() string {
return string(arch)
}
-// ubuntuArchFromGoArch maps a go architecture string to the coresponding
-// Ubuntu architecture string.
+// dpkgArchFromGoArch maps a go architecture string to the coresponding
+// Debian equivalent architecture string.
//
// E.g. the go "386" architecture string maps to the ubuntu "i386"
// architecture.
-func ubuntuArchFromGoArch(goarch string) string {
+func dpkgArchFromGoArch(goarch string) string {
goArchMapping := map[string]string{
- // go ubuntu
+ // go dpkg
"386": "i386",
"amd64": "amd64",
"arm": "armhf",
@@ -82,27 +79,27 @@
}
}
- ubuntuArch := goArchMapping[goarch]
- if ubuntuArch == "" {
+ dpkgArch := goArchMapping[goarch]
+ if dpkgArch == "" {
log.Panicf("unknown goarch %q", goarch)
}
- return ubuntuArch
+ return dpkgArch
}
-// UbuntuKernelArchitecture return the debian equivalent architecture
+// DpkgKernelArchitecture returns the debian equivalent architecture
// for the current running kernel. This is usually the same as the
-// UbuntuArchitecture - however there maybe cases that you run e.g.
+// DpkgArchitecture - however there maybe cases that you run e.g.
// a snapd:i386 on an amd64 kernel.
-func UbuntuKernelArchitecture() string {
- return ubuntuArchFromKernelArch(osutil.MachineName())
+func DpkgKernelArchitecture() string {
+ return dpkgArchFromKernelArch(osutil.MachineName())
}
-// ubuntuArchFromkernelArch maps the kernel architecture as reported
+// dpkgArchFromkernelArch maps the kernel architecture as reported
// via uname() to the dpkg architecture
-func ubuntuArchFromKernelArch(utsMachine string) string {
+func dpkgArchFromKernelArch(utsMachine string) string {
kernelArchMapping := map[string]string{
- // kernel ubuntu
+ // kernel dpkg
"i686": "i386",
"x86_64": "amd64",
"armv7l": "armhf",
@@ -115,12 +112,12 @@
"ppc64": "ppc64",
}
- ubuntuArch := kernelArchMapping[utsMachine]
- if ubuntuArch == "" {
+ dpkgArch := kernelArchMapping[utsMachine]
+ if dpkgArch == "" {
log.Panicf("unknown kernel arch %q", utsMachine)
}
- return ubuntuArch
+ return dpkgArch
}
// IsSupportedArchitecture returns true if the system architecture is in the
diff -Nru snapd-2.41+19.10.1/arch/arch_test.go snapd-2.42.1+19.10/arch/arch_test.go
--- snapd-2.41+19.10.1/arch/arch_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/arch/arch_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -33,22 +33,24 @@
type ArchTestSuite struct {
}
-func (ts *ArchTestSuite) TestUbuntuArchitecture(c *C) {
- c.Check(ubuntuArchFromGoArch("386"), Equals, "i386")
- c.Check(ubuntuArchFromGoArch("amd64"), Equals, "amd64")
- c.Check(ubuntuArchFromGoArch("arm"), Equals, "armhf")
- c.Check(ubuntuArchFromGoArch("arm64"), Equals, "arm64")
- c.Check(ubuntuArchFromGoArch("ppc64le"), Equals, "ppc64el")
- c.Check(ubuntuArchFromGoArch("ppc64"), Equals, "ppc64")
- c.Check(ubuntuArchFromGoArch("s390x"), Equals, "s390x")
+func (ts *ArchTestSuite) TestArchDpkgArchitecture(c *C) {
+ c.Check(dpkgArchFromGoArch("386"), Equals, "i386")
+ c.Check(dpkgArchFromGoArch("amd64"), Equals, "amd64")
+ c.Check(dpkgArchFromGoArch("arm"), Equals, "armhf")
+ c.Check(dpkgArchFromGoArch("arm64"), Equals, "arm64")
+ c.Check(dpkgArchFromGoArch("ppc64le"), Equals, "ppc64el")
+ c.Check(dpkgArchFromGoArch("ppc64"), Equals, "ppc64")
+ c.Check(dpkgArchFromGoArch("s390x"), Equals, "s390x")
+ c.Check(dpkgArchFromGoArch("ppc"), Equals, "powerpc")
+ c.Check(dpkgArchFromGoArch("ppc64"), Equals, "ppc64")
}
-func (ts *ArchTestSuite) TestSetArchitecture(c *C) {
+func (ts *ArchTestSuite) TestArchSetArchitecture(c *C) {
SetArchitecture("armhf")
- c.Assert(UbuntuArchitecture(), Equals, "armhf")
+ c.Assert(DpkgArchitecture(), Equals, "armhf")
}
-func (ts *ArchTestSuite) TestSupportedArchitectures(c *C) {
+func (ts *ArchTestSuite) TestArchSupportedArchitectures(c *C) {
arch = "armhf"
c.Check(IsSupportedArchitecture([]string{"all"}), Equals, true)
c.Check(IsSupportedArchitecture([]string{"amd64", "armhf", "powerpc"}), Equals, true)
@@ -56,6 +58,7 @@
c.Check(IsSupportedArchitecture([]string{"amd64", "powerpc"}), Equals, false)
arch = "amd64"
+ c.Check(IsSupportedArchitecture([]string{"all"}), Equals, true)
c.Check(IsSupportedArchitecture([]string{"amd64", "armhf", "powerpc"}), Equals, true)
c.Check(IsSupportedArchitecture([]string{"powerpc"}), Equals, false)
}
diff -Nru snapd-2.41+19.10.1/asserts/asserts.go snapd-2.42.1+19.10/asserts/asserts.go
--- snapd-2.41+19.10.1/asserts/asserts.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/asserts/asserts.go 2019-10-30 12:17:43.000000000 +0000
@@ -37,6 +37,17 @@
noAuthority typeFlags = iota + 1
)
+// MetaHeaders is a list of headers in assertions which are about the assertion
+// itself.
+var MetaHeaders = [...]string{
+ "type",
+ "format",
+ "authority-id",
+ "revision",
+ "body-length",
+ "sign-key-sha3-384",
+}
+
// AssertionType describes a known assertion type with its name and metadata.
type AssertionType struct {
// Name of the type.
diff -Nru snapd-2.41+19.10.1/asserts/batch.go snapd-2.42.1+19.10/asserts/batch.go
--- snapd-2.41+19.10.1/asserts/batch.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/asserts/batch.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,229 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2016-2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package asserts
+
+import (
+ "fmt"
+ "io"
+ "strings"
+)
+
+// Batch allows to accumulate a set of assertions possibly out of
+// prerequisite order and then add them in one go to an assertion
+// database.
+// Nothing will be committed if there are missing prerequisites, for a full
+// consistency check beforehand there is the Precheck option.
+type Batch struct {
+ bs Backstore
+ added []Assertion
+ // added is in prereq order
+ inPrereqOrder bool
+
+ unsupported func(u *Ref, err error) error
+}
+
+// NewBatch creates a new Batch to accumulate assertions to add in one
+// go to an assertion database.
+// unsupported can be used to ignore/log assertions with unsupported formats,
+// default behavior is to error on them.
+func NewBatch(unsupported func(u *Ref, err error) error) *Batch {
+ if unsupported == nil {
+ unsupported = func(_ *Ref, err error) error {
+ return err
+ }
+ }
+
+ return &Batch{
+ bs: NewMemoryBackstore(),
+ inPrereqOrder: true, // empty list is trivially so
+ unsupported: unsupported,
+ }
+}
+
+// Add one assertion to the batch.
+func (b *Batch) Add(a Assertion) error {
+ b.inPrereqOrder = false
+
+ if !a.SupportedFormat() {
+ err := &UnsupportedFormatError{Ref: a.Ref(), Format: a.Format()}
+ return b.unsupported(a.Ref(), err)
+ }
+ if err := b.bs.Put(a.Type(), a); err != nil {
+ if revErr, ok := err.(*RevisionError); ok {
+ if revErr.Current >= a.Revision() {
+ // we already got something more recent
+ return nil
+ }
+ }
+ return err
+ }
+ b.added = append(b.added, a)
+ return nil
+}
+
+// AddStream adds a stream of assertions to the batch.
+// Returns references to the assertions effectively added.
+func (b *Batch) AddStream(r io.Reader) ([]*Ref, error) {
+ b.inPrereqOrder = false
+
+ start := len(b.added)
+ dec := NewDecoder(r)
+ for {
+ a, err := dec.Decode()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return nil, err
+ }
+ if err := b.Add(a); err != nil {
+ return nil, err
+ }
+ }
+ added := b.added[start:]
+ if len(added) == 0 {
+ return nil, nil
+ }
+ refs := make([]*Ref, len(added))
+ for i, a := range added {
+ refs[i] = a.Ref()
+ }
+ return refs, nil
+}
+
+// Fetch adds to the batch by invoking fetching to drive an internal
+// Fetcher that was built with trustedDB and retrieve.
+func (b *Batch) Fetch(trustedDB RODatabase, retrieve func(*Ref) (Assertion, error), fetching func(Fetcher) error) error {
+ f := NewFetcher(trustedDB, retrieve, b.Add)
+ return fetching(f)
+}
+
+func (b *Batch) precheck(db *Database) error {
+ db = db.WithStackedBackstore(NewMemoryBackstore())
+ return b.commitTo(db)
+}
+
+type CommitOptions struct {
+ // Precheck indicates whether to do a full consistency check
+ // before starting adding the batch.
+ Precheck bool
+}
+
+// CommitTo adds the batch of assertions to the given assertion database.
+// Nothing will be committed if there are missing prerequisites, for a full
+// consistency check beforehand there is the Precheck option.
+func (b *Batch) CommitTo(db *Database, opts *CommitOptions) error {
+ if opts == nil {
+ opts = &CommitOptions{}
+ }
+ if opts.Precheck {
+ if err := b.precheck(db); err != nil {
+ return err
+ }
+ }
+
+ return b.commitTo(db)
+}
+
+// commitTo does a best effort of adding all the batch assertions to
+// the target database.
+func (b *Batch) commitTo(db *Database) error {
+ if err := b.prereqSort(db); err != nil {
+ return err
+ }
+
+ // TODO: trigger w. caller a global sanity check if something is revoked
+ // (but try to save as much possible still),
+ // or err is a check error
+
+ var errs []error
+ for _, a := range b.added {
+ err := db.Add(a)
+ if IsUnaccceptedUpdate(err) {
+ // unsupported format case is handled before
+ // be idempotent
+ // system db has already the same or newer
+ continue
+ }
+ if err != nil {
+ errs = append(errs, err)
+ }
+ }
+ if len(errs) != 0 {
+ return &commitError{errs: errs}
+ }
+ return nil
+}
+
+func (b *Batch) prereqSort(db *Database) error {
+ if b.inPrereqOrder {
+ // nothing to do
+ return nil
+ }
+
+ // put in prereq order using a fetcher
+ ordered := make([]Assertion, 0, len(b.added))
+ retrieve := func(ref *Ref) (Assertion, error) {
+ a, err := b.bs.Get(ref.Type, ref.PrimaryKey, ref.Type.MaxSupportedFormat())
+ if IsNotFound(err) {
+ // fallback to pre-existing assertions
+ a, err = ref.Resolve(db.Find)
+ }
+ if err != nil {
+ return nil, resolveError("cannot resolve prerequisite assertion: %s", ref, err)
+ }
+ return a, nil
+ }
+ save := func(a Assertion) error {
+ ordered = append(ordered, a)
+ return nil
+ }
+ f := NewFetcher(db, retrieve, save)
+
+ for _, a := range b.added {
+ if err := f.Fetch(a.Ref()); err != nil {
+ return err
+ }
+ }
+
+ b.added = ordered
+ b.inPrereqOrder = true
+ return nil
+}
+
+func resolveError(format string, ref *Ref, err error) error {
+ if IsNotFound(err) {
+ return fmt.Errorf(format, ref)
+ } else {
+ return fmt.Errorf(format+": %v", ref, err)
+ }
+}
+
+type commitError struct {
+ errs []error
+}
+
+func (e *commitError) Error() string {
+ l := []string{""}
+ for _, e := range e.errs {
+ l = append(l, e.Error())
+ }
+ return fmt.Sprintf("cannot accept some assertions:%s", strings.Join(l, "\n - "))
+}
diff -Nru snapd-2.41+19.10.1/asserts/batch_test.go snapd-2.42.1+19.10/asserts/batch_test.go
--- snapd-2.41+19.10.1/asserts/batch_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/asserts/batch_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,475 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2016-2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package asserts_test
+
+import (
+ "bytes"
+ "fmt"
+ "time"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/asserts/assertstest"
+)
+
+type batchSuite struct {
+ storeSigning *assertstest.StoreStack
+ dev1Acct *asserts.Account
+
+ db *asserts.Database
+}
+
+var _ = Suite(&batchSuite{})
+
+func (s *batchSuite) SetUpTest(c *C) {
+ s.storeSigning = assertstest.NewStoreStack("can0nical", nil)
+
+ s.dev1Acct = assertstest.NewAccount(s.storeSigning, "developer1", nil, "")
+ err := s.storeSigning.Add(s.dev1Acct)
+ c.Assert(err, IsNil)
+
+ db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{
+ Backstore: asserts.NewMemoryBackstore(),
+ Trusted: s.storeSigning.Trusted,
+ })
+ c.Assert(err, IsNil)
+ s.db = db
+}
+
+func (s *batchSuite) snapDecl(c *C, name string, extraHeaders map[string]interface{}) *asserts.SnapDeclaration {
+ headers := map[string]interface{}{
+ "series": "16",
+ "snap-id": name + "-id",
+ "snap-name": name,
+ "publisher-id": s.dev1Acct.AccountID(),
+ "timestamp": time.Now().Format(time.RFC3339),
+ }
+ for h, v := range extraHeaders {
+ headers[h] = v
+ }
+ decl, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "")
+ c.Assert(err, IsNil)
+ err = s.storeSigning.Add(decl)
+ c.Assert(err, IsNil)
+ return decl.(*asserts.SnapDeclaration)
+}
+
+func (s *batchSuite) TestAddStream(c *C) {
+ b := &bytes.Buffer{}
+ enc := asserts.NewEncoder(b)
+ // wrong order is ok
+ err := enc.Encode(s.dev1Acct)
+ c.Assert(err, IsNil)
+ enc.Encode(s.storeSigning.StoreAccountKey(""))
+ c.Assert(err, IsNil)
+
+ batch := asserts.NewBatch(nil)
+ refs, err := batch.AddStream(b)
+ c.Assert(err, IsNil)
+ c.Check(refs, DeepEquals, []*asserts.Ref{
+ {Type: asserts.AccountType, PrimaryKey: []string{s.dev1Acct.AccountID()}},
+ {Type: asserts.AccountKeyType, PrimaryKey: []string{s.storeSigning.StoreAccountKey("").PublicKeyID()}},
+ })
+
+ // noop
+ err = batch.Add(s.storeSigning.StoreAccountKey(""))
+ c.Assert(err, IsNil)
+
+ err = batch.CommitTo(s.db, nil)
+ c.Assert(err, IsNil)
+
+ devAcct, err := s.db.Find(asserts.AccountType, map[string]string{
+ "account-id": s.dev1Acct.AccountID(),
+ })
+ c.Assert(err, IsNil)
+ c.Check(devAcct.(*asserts.Account).Username(), Equals, "developer1")
+}
+
+func (s *batchSuite) TestAddEmptyStream(c *C) {
+ b := &bytes.Buffer{}
+
+ batch := asserts.NewBatch(nil)
+ refs, err := batch.AddStream(b)
+ c.Assert(err, IsNil)
+ c.Check(refs, HasLen, 0)
+}
+
+func (s *batchSuite) TestConsiderPreexisting(c *C) {
+ // prereq store key
+ err := s.db.Add(s.storeSigning.StoreAccountKey(""))
+ c.Assert(err, IsNil)
+
+ batch := asserts.NewBatch(nil)
+ err = batch.Add(s.dev1Acct)
+ c.Assert(err, IsNil)
+
+ err = batch.CommitTo(s.db, nil)
+ c.Assert(err, IsNil)
+
+ devAcct, err := s.db.Find(asserts.AccountType, map[string]string{
+ "account-id": s.dev1Acct.AccountID(),
+ })
+ c.Assert(err, IsNil)
+ c.Check(devAcct.(*asserts.Account).Username(), Equals, "developer1")
+}
+
+func (s *batchSuite) TestAddStreamReturnsEffectivelyAddedRefs(c *C) {
+ batch := asserts.NewBatch(nil)
+
+ err := batch.Add(s.storeSigning.StoreAccountKey(""))
+ c.Assert(err, IsNil)
+
+ b := &bytes.Buffer{}
+ enc := asserts.NewEncoder(b)
+ // wrong order is ok
+ err = enc.Encode(s.dev1Acct)
+ c.Assert(err, IsNil)
+ // this was already added to the batch
+ enc.Encode(s.storeSigning.StoreAccountKey(""))
+ c.Assert(err, IsNil)
+
+ // effectively adds only the developer1 account
+ refs, err := batch.AddStream(b)
+ c.Assert(err, IsNil)
+ c.Check(refs, DeepEquals, []*asserts.Ref{
+ {Type: asserts.AccountType, PrimaryKey: []string{s.dev1Acct.AccountID()}},
+ })
+
+ err = batch.CommitTo(s.db, nil)
+ c.Assert(err, IsNil)
+
+ devAcct, err := s.db.Find(asserts.AccountType, map[string]string{
+ "account-id": s.dev1Acct.AccountID(),
+ })
+ c.Assert(err, IsNil)
+ c.Check(devAcct.(*asserts.Account).Username(), Equals, "developer1")
+}
+
+func (s *batchSuite) TestCommitRefusesSelfSignedKey(c *C) {
+ aKey, _ := assertstest.GenerateKey(752)
+ aSignDB := assertstest.NewSigningDB("can0nical", aKey)
+
+ aKeyEncoded, err := asserts.EncodePublicKey(aKey.PublicKey())
+ c.Assert(err, IsNil)
+
+ headers := map[string]interface{}{
+ "authority-id": "can0nical",
+ "account-id": "can0nical",
+ "public-key-sha3-384": aKey.PublicKey().ID(),
+ "name": "default",
+ "since": time.Now().UTC().Format(time.RFC3339),
+ }
+ acctKey, err := aSignDB.Sign(asserts.AccountKeyType, headers, aKeyEncoded, "")
+ c.Assert(err, IsNil)
+
+ headers = map[string]interface{}{
+ "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"), "")
+ c.Assert(err, IsNil)
+
+ batch := asserts.NewBatch(nil)
+
+ err = batch.Add(repair)
+ c.Assert(err, IsNil)
+
+ err = batch.Add(acctKey)
+ c.Assert(err, IsNil)
+
+ // this must fail
+ err = batch.CommitTo(s.db, nil)
+ c.Assert(err, ErrorMatches, `circular assertions are not expected:.*`)
+}
+
+func (s *batchSuite) TestAddUnsupported(c *C) {
+ restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 111)
+ defer restore()
+
+ batch := asserts.NewBatch(nil)
+
+ var a asserts.Assertion
+ (func() {
+ restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 999)
+ defer restore()
+ headers := map[string]interface{}{
+ "format": "999",
+ "revision": "1",
+ "series": "16",
+ "snap-id": "snap-id-1",
+ "snap-name": "foo",
+ "publisher-id": s.dev1Acct.AccountID(),
+ "timestamp": time.Now().Format(time.RFC3339),
+ }
+ var err error
+ a, err = s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "")
+ c.Assert(err, IsNil)
+ })()
+
+ err := batch.Add(a)
+ c.Check(err, ErrorMatches, `proposed "snap-declaration" assertion has format 999 but 111 is latest supported`)
+}
+
+func (s *batchSuite) TestAddUnsupportedIgnore(c *C) {
+ restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 111)
+ defer restore()
+
+ var uRef *asserts.Ref
+ unsupported := func(ref *asserts.Ref, _ error) error {
+ uRef = ref
+ return nil
+ }
+
+ batch := asserts.NewBatch(unsupported)
+
+ var a asserts.Assertion
+ (func() {
+ restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 999)
+ defer restore()
+ headers := map[string]interface{}{
+ "format": "999",
+ "revision": "1",
+ "series": "16",
+ "snap-id": "snap-id-1",
+ "snap-name": "foo",
+ "publisher-id": s.dev1Acct.AccountID(),
+ "timestamp": time.Now().Format(time.RFC3339),
+ }
+ var err error
+ a, err = s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "")
+ c.Assert(err, IsNil)
+ })()
+
+ err := batch.Add(a)
+ c.Check(err, IsNil)
+ c.Check(uRef, DeepEquals, &asserts.Ref{
+ Type: asserts.SnapDeclarationType,
+ PrimaryKey: []string{"16", "snap-id-1"},
+ })
+}
+
+func (s *batchSuite) TestCommitPartial(c *C) {
+ // Commit does add any successful assertion until the first error
+
+ // store key already present
+ err := s.db.Add(s.storeSigning.StoreAccountKey(""))
+ c.Assert(err, IsNil)
+
+ batch := asserts.NewBatch(nil)
+
+ snapDeclFoo := s.snapDecl(c, "foo", nil)
+
+ err = batch.Add(snapDeclFoo)
+ c.Assert(err, IsNil)
+ err = batch.Add(s.dev1Acct)
+ c.Assert(err, IsNil)
+
+ // too old
+ rev := 1
+ headers := map[string]interface{}{
+ "snap-id": "foo-id",
+ "snap-sha3-384": makeDigest(rev),
+ "snap-size": fmt.Sprintf("%d", len(fakeSnap(rev))),
+ "snap-revision": fmt.Sprintf("%d", rev),
+ "developer-id": s.dev1Acct.AccountID(),
+ "timestamp": time.Time{}.Format(time.RFC3339),
+ }
+ snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, headers, nil, "")
+ c.Assert(err, IsNil)
+
+ err = batch.Add(snapRev)
+ c.Assert(err, IsNil)
+
+ err = batch.CommitTo(s.db, &asserts.CommitOptions{Precheck: false})
+ c.Check(err, ErrorMatches, `(?ms).*validity.*`)
+
+ // snap-declaration was added anyway
+ _, err = s.db.Find(asserts.SnapDeclarationType, map[string]string{
+ "series": "16",
+ "snap-id": "foo-id",
+ })
+ c.Assert(err, IsNil)
+}
+
+func (s *batchSuite) TestCommitMissing(c *C) {
+ // store key already present
+ err := s.db.Add(s.storeSigning.StoreAccountKey(""))
+ c.Assert(err, IsNil)
+
+ batch := asserts.NewBatch(nil)
+
+ snapDeclFoo := s.snapDecl(c, "foo", nil)
+
+ err = batch.Add(snapDeclFoo)
+ c.Assert(err, IsNil)
+
+ err = batch.CommitTo(s.db, nil)
+ c.Check(err, ErrorMatches, `cannot resolve prerequisite assertion: account.*`)
+}
+
+func (s *batchSuite) TestPrecheckPartial(c *C) {
+ // store key already present
+ err := s.db.Add(s.storeSigning.StoreAccountKey(""))
+ c.Assert(err, IsNil)
+
+ batch := asserts.NewBatch(nil)
+
+ snapDeclFoo := s.snapDecl(c, "foo", nil)
+
+ err = batch.Add(snapDeclFoo)
+ c.Assert(err, IsNil)
+ err = batch.Add(s.dev1Acct)
+ c.Assert(err, IsNil)
+
+ // too old
+ rev := 1
+ headers := map[string]interface{}{
+ "snap-id": "foo-id",
+ "snap-sha3-384": makeDigest(rev),
+ "snap-size": fmt.Sprintf("%d", len(fakeSnap(rev))),
+ "snap-revision": fmt.Sprintf("%d", rev),
+ "developer-id": s.dev1Acct.AccountID(),
+ "timestamp": time.Time{}.Format(time.RFC3339),
+ }
+ snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, headers, nil, "")
+ c.Assert(err, IsNil)
+
+ err = batch.Add(snapRev)
+ c.Assert(err, IsNil)
+
+ err = batch.CommitTo(s.db, &asserts.CommitOptions{Precheck: true})
+ c.Check(err, ErrorMatches, `(?ms).*validity.*`)
+
+ // nothing was added
+ _, err = s.db.Find(asserts.SnapDeclarationType, map[string]string{
+ "series": "16",
+ "snap-id": "foo-id",
+ })
+ c.Assert(asserts.IsNotFound(err), Equals, true)
+}
+
+func (s *batchSuite) TestPrecheckHappy(c *C) {
+ // store key already present
+ err := s.db.Add(s.storeSigning.StoreAccountKey(""))
+ c.Assert(err, IsNil)
+
+ batch := asserts.NewBatch(nil)
+
+ snapDeclFoo := s.snapDecl(c, "foo", nil)
+
+ err = batch.Add(snapDeclFoo)
+ c.Assert(err, IsNil)
+ err = batch.Add(s.dev1Acct)
+ c.Assert(err, IsNil)
+
+ rev := 1
+ revDigest := makeDigest(rev)
+ headers := map[string]interface{}{
+ "snap-id": "foo-id",
+ "snap-sha3-384": revDigest,
+ "snap-size": fmt.Sprintf("%d", len(fakeSnap(rev))),
+ "snap-revision": fmt.Sprintf("%d", rev),
+ "developer-id": s.dev1Acct.AccountID(),
+ "timestamp": time.Now().Format(time.RFC3339),
+ }
+ snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, headers, nil, "")
+ c.Assert(err, IsNil)
+
+ err = batch.Add(snapRev)
+ c.Assert(err, IsNil)
+
+ // test precheck on its own
+ err = batch.DoPrecheck(s.db)
+ c.Assert(err, IsNil)
+
+ // nothing was added yet
+ _, err = s.db.Find(asserts.SnapDeclarationType, map[string]string{
+ "series": "16",
+ "snap-id": "foo-id",
+ })
+ c.Assert(asserts.IsNotFound(err), Equals, true)
+
+ // commit (with precheck)
+ err = batch.CommitTo(s.db, &asserts.CommitOptions{Precheck: true})
+ c.Assert(err, IsNil)
+
+ _, err = s.db.Find(asserts.SnapRevisionType, map[string]string{
+ "snap-sha3-384": revDigest,
+ })
+ c.Check(err, IsNil)
+}
+
+func (s *batchSuite) TestFetch(c *C) {
+ err := s.db.Add(s.storeSigning.StoreAccountKey(""))
+ c.Assert(err, IsNil)
+
+ s.snapDecl(c, "foo", nil)
+
+ rev := 10
+ revDigest := makeDigest(rev)
+ headers := map[string]interface{}{
+ "snap-id": "foo-id",
+ "snap-sha3-384": revDigest,
+ "snap-size": fmt.Sprintf("%d", len(fakeSnap(rev))),
+ "snap-revision": fmt.Sprintf("%d", rev),
+ "developer-id": s.dev1Acct.AccountID(),
+ "timestamp": time.Now().Format(time.RFC3339),
+ }
+ snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, headers, nil, "")
+ c.Assert(err, IsNil)
+
+ err = s.storeSigning.Add(snapRev)
+ c.Assert(err, IsNil)
+ ref := snapRev.Ref()
+
+ batch := asserts.NewBatch(nil)
+
+ // retrieve from storeSigning
+ retrieve := func(ref *asserts.Ref) (asserts.Assertion, error) {
+ return ref.Resolve(s.storeSigning.Find)
+ }
+ // fetching the snap-revision
+ fetching := func(f asserts.Fetcher) error {
+ return f.Fetch(ref)
+ }
+
+ err = batch.Fetch(s.db, retrieve, fetching)
+ c.Assert(err, IsNil)
+
+ // nothing was added yet
+ _, err = s.db.Find(asserts.SnapDeclarationType, map[string]string{
+ "series": "16",
+ "snap-id": "foo-id",
+ })
+ c.Assert(asserts.IsNotFound(err), Equals, true)
+
+ // commit
+ err = batch.CommitTo(s.db, nil)
+ c.Assert(err, IsNil)
+
+ _, err = s.db.Find(asserts.SnapRevisionType, map[string]string{
+ "snap-sha3-384": revDigest,
+ })
+ c.Check(err, IsNil)
+}
diff -Nru snapd-2.41+19.10.1/asserts/device_asserts.go snapd-2.42.1+19.10/asserts/device_asserts.go
--- snapd-2.41+19.10.1/asserts/device_asserts.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/asserts/device_asserts.go 1970-01-01 00:00:00.000000000 +0000
@@ -1,542 +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 asserts
-
-import (
- "fmt"
- "regexp"
- "strings"
- "time"
-
- "github.com/snapcore/snapd/snap/naming"
- "github.com/snapcore/snapd/strutil"
-)
-
-// Model holds a model assertion, which is a statement by a brand
-// about the properties of a device model.
-type Model struct {
- assertionBase
- classic bool
- requiredSnaps []string
- sysUserAuthority []string
- timestamp time.Time
-}
-
-// BrandID returns the brand identifier. Same as the authority id.
-func (mod *Model) BrandID() string {
- return mod.HeaderString("brand-id")
-}
-
-// Model returns the model name identifier.
-func (mod *Model) Model() string {
- return mod.HeaderString("model")
-}
-
-// DisplayName returns the human-friendly name of the model or
-// falls back to Model if this was not set.
-func (mod *Model) DisplayName() string {
- display := mod.HeaderString("display-name")
- if display == "" {
- return mod.Model()
- }
- return display
-}
-
-// Series returns the series of the core software the model uses.
-func (mod *Model) Series() string {
- return mod.HeaderString("series")
-}
-
-// Classic returns whether the model is a classic system.
-func (mod *Model) Classic() bool {
- return mod.classic
-}
-
-// Architecture returns the archicteture the model is based on.
-func (mod *Model) Architecture() string {
- return mod.HeaderString("architecture")
-}
-
-// snapWithTrack represents a snap that includes optional track
-// information like `snapName=trackName`
-type snapWithTrack string
-
-func (s snapWithTrack) Snap() string {
- return strings.SplitN(string(s), "=", 2)[0]
-}
-
-func (s snapWithTrack) Track() string {
- l := strings.SplitN(string(s), "=", 2)
- if len(l) > 1 {
- return l[1]
- }
- return ""
-}
-
-// Gadget returns the gadget snap the model uses.
-func (mod *Model) Gadget() string {
- return snapWithTrack(mod.HeaderString("gadget")).Snap()
-}
-
-// GadgetTrack returns the gadget track the model uses.
-func (mod *Model) GadgetTrack() string {
- return snapWithTrack(mod.HeaderString("gadget")).Track()
-}
-
-// Kernel returns the kernel snap the model uses.
-func (mod *Model) Kernel() string {
- return snapWithTrack(mod.HeaderString("kernel")).Snap()
-}
-
-// KernelTrack returns the kernel track the model uses.
-func (mod *Model) KernelTrack() string {
- return snapWithTrack(mod.HeaderString("kernel")).Track()
-}
-
-// Base returns the base snap the model uses.
-func (mod *Model) Base() string {
- return mod.HeaderString("base")
-}
-
-// Store returns the snap store the model uses.
-func (mod *Model) Store() string {
- return mod.HeaderString("store")
-}
-
-// RequiredSnaps returns the snaps that must be installed at all times and cannot be removed for this model.
-func (mod *Model) RequiredSnaps() []string {
- return mod.requiredSnaps
-}
-
-// SystemUserAuthority returns the authority ids that are accepted as signers of system-user assertions for this model. Empty list means any.
-func (mod *Model) SystemUserAuthority() []string {
- return mod.sysUserAuthority
-}
-
-// Timestamp returns the time when the model assertion was issued.
-func (mod *Model) Timestamp() time.Time {
- return mod.timestamp
-}
-
-// Implement further consistency checks.
-func (mod *Model) checkConsistency(db RODatabase, acck *AccountKey) error {
- // TODO: double check trust level of authority depending on class and possibly allowed-modes
- return nil
-}
-
-// sanity
-var _ consistencyChecker = (*Model)(nil)
-
-// limit model to only lowercase for now
-var validModel = regexp.MustCompile("^[a-zA-Z0-9](?:-?[a-zA-Z0-9])*$")
-
-func checkSnapWithTrackHeader(header string, headers map[string]interface{}) error {
- _, ok := headers[header]
- if !ok {
- return nil
- }
- value, ok := headers[header].(string)
- if !ok {
- return fmt.Errorf(`%q header must be a string`, header)
- }
- l := strings.SplitN(value, "=", 2)
-
- if err := validateSnapName(l[0], header); err != nil {
- return err
- }
- if len(l) == 1 {
- return nil
- }
- track := l[1]
- if strings.Count(track, "/") != 0 {
- return fmt.Errorf(`%q channel selector must be a track name only`, header)
- }
- channelRisks := []string{"stable", "candidate", "beta", "edge"}
- if strutil.ListContains(channelRisks, track) {
- return fmt.Errorf(`%q channel selector must be a track name`, header)
- }
- return nil
-}
-
-func checkModel(headers map[string]interface{}) (string, error) {
- s, err := checkStringMatches(headers, "model", validModel)
- if err != nil {
- return "", err
- }
-
- // TODO: support the concept of case insensitive/preserving string headers
- if strings.ToLower(s) != s {
- return "", fmt.Errorf(`"model" header cannot contain uppercase letters`)
- }
- return s, nil
-}
-
-func checkAuthorityMatchesBrand(a Assertion) error {
- typeName := a.Type().Name
- authorityID := a.AuthorityID()
- brand := a.HeaderString("brand-id")
- if brand != authorityID {
- return fmt.Errorf("authority-id and brand-id must match, %s assertions are expected to be signed by the brand: %q != %q", typeName, authorityID, brand)
- }
- return nil
-}
-
-func checkOptionalSystemUserAuthority(headers map[string]interface{}, brandID string) ([]string, error) {
- const name = "system-user-authority"
- v, ok := headers[name]
- if !ok {
- return []string{brandID}, nil
- }
- switch x := v.(type) {
- case string:
- if x == "*" {
- return nil, nil
- }
- case []interface{}:
- lst, err := checkStringListMatches(headers, name, validAccountID)
- if err == nil {
- return lst, nil
- }
- }
- return nil, fmt.Errorf("%q header must be '*' or a list of account ids", name)
-}
-
-var (
- modelMandatory = []string{"architecture", "gadget", "kernel"}
- classicModelOptional = []string{"architecture", "gadget"}
-)
-
-func validateSnapName(name string, headerName string) error {
- if err := naming.ValidateSnap(name); err != nil {
- return fmt.Errorf("invalid snap name in %q header: %s", headerName, name)
- }
- return nil
-}
-
-func assembleModel(assert assertionBase) (Assertion, error) {
- err := checkAuthorityMatchesBrand(&assert)
- if err != nil {
- return nil, err
- }
-
- _, err = checkModel(assert.headers)
- if err != nil {
- return nil, err
- }
-
- classic, err := checkOptionalBool(assert.headers, "classic")
- if err != nil {
- return nil, err
- }
-
- if classic {
- if _, ok := assert.headers["kernel"]; ok {
- return nil, fmt.Errorf("cannot specify a kernel with a classic model")
- }
- if _, ok := assert.headers["base"]; ok {
- return nil, fmt.Errorf("cannot specify a base with a classic model")
- }
- }
-
- checker := checkNotEmptyString
- toCheck := modelMandatory
- if classic {
- checker = checkOptionalString
- toCheck = classicModelOptional
- }
-
- for _, h := range toCheck {
- if _, err := checker(assert.headers, h); err != nil {
- return nil, err
- }
- }
-
- // kernel/gadget must be valid snap names and can have (optional) tracks
- // - validate those
- if err := checkSnapWithTrackHeader("kernel", assert.headers); err != nil {
- return nil, err
- }
- if err := checkSnapWithTrackHeader("gadget", assert.headers); err != nil {
- return nil, err
- }
- // base, if provided, must be a valid snap name too
- base, err := checkOptionalString(assert.headers, "base")
- if err != nil {
- return nil, err
- }
- if base != "" {
- if err := validateSnapName(base, "base"); err != nil {
- return nil, err
- }
- }
-
- // store is optional but must be a string, defaults to the ubuntu store
- _, err = checkOptionalString(assert.headers, "store")
- if err != nil {
- return nil, err
- }
-
- // display-name is optional but must be a string
- _, err = checkOptionalString(assert.headers, "display-name")
- if err != nil {
- return nil, err
- }
-
- // required snap must be valid snap names
- reqSnaps, err := checkStringList(assert.headers, "required-snaps")
- if err != nil {
- return nil, err
- }
- for _, name := range reqSnaps {
- if err := validateSnapName(name, "required-snaps"); err != nil {
- return nil, err
- }
- }
-
- sysUserAuthority, err := checkOptionalSystemUserAuthority(assert.headers, assert.HeaderString("brand-id"))
- if err != nil {
- return nil, err
- }
-
- timestamp, err := checkRFC3339Date(assert.headers, "timestamp")
- if err != nil {
- return nil, err
- }
-
- // NB:
- // * core is not supported at this time, it defaults to ubuntu-core
- // in prepare-image until rename and/or introduction of the header.
- // * some form of allowed-modes, class are postponed,
- //
- // prepare-image takes care of not allowing them for now
-
- // ignore extra headers and non-empty body for future compatibility
- return &Model{
- assertionBase: assert,
- classic: classic,
- requiredSnaps: reqSnaps,
- sysUserAuthority: sysUserAuthority,
- timestamp: timestamp,
- }, nil
-}
-
-// Serial holds a serial assertion, which is a statement binding a
-// device identity with the device public key.
-type Serial struct {
- assertionBase
- timestamp time.Time
- pubKey PublicKey
-}
-
-// BrandID returns the brand identifier of the device.
-func (ser *Serial) BrandID() string {
- return ser.HeaderString("brand-id")
-}
-
-// Model returns the model name identifier of the device.
-func (ser *Serial) Model() string {
- return ser.HeaderString("model")
-}
-
-// Serial returns the serial identifier of the device, together with
-// brand id and model they form the unique identifier of the device.
-func (ser *Serial) Serial() string {
- return ser.HeaderString("serial")
-}
-
-// DeviceKey returns the public key of the device.
-func (ser *Serial) DeviceKey() PublicKey {
- return ser.pubKey
-}
-
-// Timestamp returns the time when the serial assertion was issued.
-func (ser *Serial) Timestamp() time.Time {
- return ser.timestamp
-}
-
-// TODO: implement further consistency checks for Serial but first review approach
-
-func assembleSerial(assert assertionBase) (Assertion, error) {
- err := checkAuthorityMatchesBrand(&assert)
- if err != nil {
- return nil, err
- }
-
- _, err = checkModel(assert.headers)
- if err != nil {
- return nil, err
- }
-
- encodedKey, err := checkNotEmptyString(assert.headers, "device-key")
- if err != nil {
- return nil, err
- }
- pubKey, err := DecodePublicKey([]byte(encodedKey))
- if err != nil {
- return nil, err
- }
- keyID, err := checkNotEmptyString(assert.headers, "device-key-sha3-384")
- if err != nil {
- return nil, err
- }
- if keyID != pubKey.ID() {
- return nil, fmt.Errorf("device key does not match provided key id")
- }
-
- timestamp, err := checkRFC3339Date(assert.headers, "timestamp")
- if err != nil {
- return nil, err
- }
-
- // ignore extra headers and non-empty body for future compatibility
- return &Serial{
- assertionBase: assert,
- timestamp: timestamp,
- pubKey: pubKey,
- }, nil
-}
-
-// SerialRequest holds a serial-request assertion, which is a self-signed request to obtain a full device identity bound to the device public key.
-type SerialRequest struct {
- assertionBase
- pubKey PublicKey
-}
-
-// BrandID returns the brand identifier of the device making the request.
-func (sreq *SerialRequest) BrandID() string {
- return sreq.HeaderString("brand-id")
-}
-
-// Model returns the model name identifier of the device making the request.
-func (sreq *SerialRequest) Model() string {
- return sreq.HeaderString("model")
-}
-
-// Serial returns the optional proposed serial identifier for the device, the service taking the request might use it or ignore it.
-func (sreq *SerialRequest) Serial() string {
- return sreq.HeaderString("serial")
-}
-
-// RequestID returns the id for the request, obtained from and to be presented to the serial signing service.
-func (sreq *SerialRequest) RequestID() string {
- return sreq.HeaderString("request-id")
-}
-
-// DeviceKey returns the public key of the device making the request.
-func (sreq *SerialRequest) DeviceKey() PublicKey {
- return sreq.pubKey
-}
-
-func assembleSerialRequest(assert assertionBase) (Assertion, error) {
- _, err := checkNotEmptyString(assert.headers, "brand-id")
- if err != nil {
- return nil, err
- }
-
- _, err = checkModel(assert.headers)
- if err != nil {
- return nil, err
- }
-
- _, err = checkNotEmptyString(assert.headers, "request-id")
- if err != nil {
- return nil, err
- }
-
- _, err = checkOptionalString(assert.headers, "serial")
- if err != nil {
- return nil, err
- }
-
- encodedKey, err := checkNotEmptyString(assert.headers, "device-key")
- if err != nil {
- return nil, err
- }
- pubKey, err := DecodePublicKey([]byte(encodedKey))
- if err != nil {
- return nil, err
- }
-
- if pubKey.ID() != assert.SignKeyID() {
- return nil, fmt.Errorf("device key does not match included signing key id")
- }
-
- // ignore extra headers and non-empty body for future compatibility
- return &SerialRequest{
- assertionBase: assert,
- pubKey: pubKey,
- }, nil
-}
-
-// DeviceSessionRequest holds a device-session-request assertion, which is a request wrapping a store-provided nonce to start a session by a device signed with its key.
-type DeviceSessionRequest struct {
- assertionBase
- timestamp time.Time
-}
-
-// BrandID returns the brand identifier of the device making the request.
-func (req *DeviceSessionRequest) BrandID() string {
- return req.HeaderString("brand-id")
-}
-
-// Model returns the model name identifier of the device making the request.
-func (req *DeviceSessionRequest) Model() string {
- return req.HeaderString("model")
-}
-
-// Serial returns the serial identifier of the device making the request,
-// together with brand id and model it forms the unique identifier of
-// the device.
-func (req *DeviceSessionRequest) Serial() string {
- return req.HeaderString("serial")
-}
-
-// Nonce returns the nonce obtained from store and to be presented when requesting a device session.
-func (req *DeviceSessionRequest) Nonce() string {
- return req.HeaderString("nonce")
-}
-
-// Timestamp returns the time when the device-session-request was created.
-func (req *DeviceSessionRequest) Timestamp() time.Time {
- return req.timestamp
-}
-
-func assembleDeviceSessionRequest(assert assertionBase) (Assertion, error) {
- _, err := checkModel(assert.headers)
- if err != nil {
- return nil, err
- }
-
- _, err = checkNotEmptyString(assert.headers, "nonce")
- if err != nil {
- return nil, err
- }
-
- timestamp, err := checkRFC3339Date(assert.headers, "timestamp")
- if err != nil {
- return nil, err
- }
-
- // ignore extra headers and non-empty body for future compatibility
- return &DeviceSessionRequest{
- assertionBase: assert,
- timestamp: timestamp,
- }, nil
-}
diff -Nru snapd-2.41+19.10.1/asserts/device_asserts_test.go snapd-2.42.1+19.10/asserts/device_asserts_test.go
--- snapd-2.41+19.10.1/asserts/device_asserts_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/asserts/device_asserts_test.go 1970-01-01 00:00:00.000000000 +0000
@@ -1,714 +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 asserts_test
-
-import (
- "fmt"
- "strings"
- "time"
-
- . "gopkg.in/check.v1"
-
- "github.com/snapcore/snapd/asserts"
- "github.com/snapcore/snapd/asserts/assertstest"
-)
-
-type modelSuite struct {
- ts time.Time
- tsLine string
-}
-
-var (
- _ = Suite(&modelSuite{})
- _ = Suite(&serialSuite{})
-)
-
-func (mods *modelSuite) SetUpSuite(c *C) {
- mods.ts = time.Now().Truncate(time.Second).UTC()
- mods.tsLine = "timestamp: " + mods.ts.Format(time.RFC3339) + "\n"
-}
-
-const (
- reqSnaps = "required-snaps:\n - foo\n - bar\n"
- sysUserAuths = "system-user-authority: *\n"
-)
-
-const (
- modelExample = "type: model\n" +
- "authority-id: brand-id1\n" +
- "series: 16\n" +
- "brand-id: brand-id1\n" +
- "model: baz-3000\n" +
- "display-name: Baz 3000\n" +
- "architecture: amd64\n" +
- "gadget: brand-gadget\n" +
- "base: core18\n" +
- "kernel: baz-linux\n" +
- "store: brand-store\n" +
- sysUserAuths +
- reqSnaps +
- "TSLINE" +
- "body-length: 0\n" +
- "sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
- "\n\n" +
- "AXNpZw=="
-
- classicModelExample = "type: model\n" +
- "authority-id: brand-id1\n" +
- "series: 16\n" +
- "brand-id: brand-id1\n" +
- "model: baz-3000\n" +
- "display-name: Baz 3000\n" +
- "classic: true\n" +
- "architecture: amd64\n" +
- "gadget: brand-gadget\n" +
- "store: brand-store\n" +
- reqSnaps +
- "TSLINE" +
- "body-length: 0\n" +
- "sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
- "\n\n" +
- "AXNpZw=="
-)
-
-func (mods *modelSuite) TestDecodeOK(c *C) {
- encoded := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
- a, err := asserts.Decode([]byte(encoded))
- c.Assert(err, IsNil)
- c.Check(a.Type(), Equals, asserts.ModelType)
- model := a.(*asserts.Model)
- c.Check(model.AuthorityID(), Equals, "brand-id1")
- c.Check(model.Timestamp(), Equals, mods.ts)
- c.Check(model.Series(), Equals, "16")
- c.Check(model.BrandID(), Equals, "brand-id1")
- c.Check(model.Model(), Equals, "baz-3000")
- c.Check(model.DisplayName(), Equals, "Baz 3000")
- c.Check(model.Architecture(), Equals, "amd64")
- c.Check(model.Gadget(), Equals, "brand-gadget")
- c.Check(model.GadgetTrack(), Equals, "")
- c.Check(model.Kernel(), Equals, "baz-linux")
- c.Check(model.KernelTrack(), Equals, "")
- c.Check(model.Base(), Equals, "core18")
- c.Check(model.Store(), Equals, "brand-store")
- c.Check(model.RequiredSnaps(), DeepEquals, []string{"foo", "bar"})
- c.Check(model.SystemUserAuthority(), HasLen, 0)
-}
-
-func (mods *modelSuite) TestDecodeStoreIsOptional(c *C) {
- withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
- encoded := strings.Replace(withTimestamp, "store: brand-store\n", "store: \n", 1)
- a, err := asserts.Decode([]byte(encoded))
- c.Assert(err, IsNil)
- model := a.(*asserts.Model)
- c.Check(model.Store(), Equals, "")
-
- encoded = strings.Replace(withTimestamp, "store: brand-store\n", "", 1)
- a, err = asserts.Decode([]byte(encoded))
- c.Assert(err, IsNil)
- model = a.(*asserts.Model)
- c.Check(model.Store(), Equals, "")
-}
-
-func (mods *modelSuite) TestDecodeBaseIsOptional(c *C) {
- withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
- encoded := strings.Replace(withTimestamp, "base: core18\n", "base: \n", 1)
- a, err := asserts.Decode([]byte(encoded))
- c.Assert(err, IsNil)
- model := a.(*asserts.Model)
- c.Check(model.Base(), Equals, "")
-
- encoded = strings.Replace(withTimestamp, "base: core18\n", "", 1)
- a, err = asserts.Decode([]byte(encoded))
- c.Assert(err, IsNil)
- model = a.(*asserts.Model)
- c.Check(model.Base(), Equals, "")
-}
-
-func (mods *modelSuite) TestDecodeDisplayNameIsOptional(c *C) {
- withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
- encoded := strings.Replace(withTimestamp, "display-name: Baz 3000\n", "display-name: \n", 1)
- a, err := asserts.Decode([]byte(encoded))
- c.Assert(err, IsNil)
- model := a.(*asserts.Model)
- // optional but we fallback to Model
- c.Check(model.DisplayName(), Equals, "baz-3000")
-
- encoded = strings.Replace(withTimestamp, "display-name: Baz 3000\n", "", 1)
- a, err = asserts.Decode([]byte(encoded))
- c.Assert(err, IsNil)
- model = a.(*asserts.Model)
- // optional but we fallback to Model
- c.Check(model.DisplayName(), Equals, "baz-3000")
-}
-
-func (mods *modelSuite) TestDecodeRequiredSnapsAreOptional(c *C) {
- withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
- encoded := strings.Replace(withTimestamp, reqSnaps, "", 1)
- a, err := asserts.Decode([]byte(encoded))
- c.Assert(err, IsNil)
- model := a.(*asserts.Model)
- c.Check(model.RequiredSnaps(), HasLen, 0)
-}
-
-func (mods *modelSuite) TestDecodeValidatesSnapNames(c *C) {
- withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
- encoded := strings.Replace(withTimestamp, reqSnaps, "required-snaps:\n - foo_bar\n - bar\n", 1)
- a, err := asserts.Decode([]byte(encoded))
- c.Assert(a, IsNil)
- c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "required-snaps" header: foo_bar`)
-
- encoded = strings.Replace(withTimestamp, reqSnaps, "required-snaps:\n - foo\n - bar-;;''\n", 1)
- a, err = asserts.Decode([]byte(encoded))
- c.Assert(a, IsNil)
- c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "required-snaps" header: bar-;;''`)
-
- encoded = strings.Replace(withTimestamp, "kernel: baz-linux\n", "kernel: baz-linux_instance\n", 1)
- a, err = asserts.Decode([]byte(encoded))
- c.Assert(a, IsNil)
- c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "kernel" header: baz-linux_instance`)
-
- encoded = strings.Replace(withTimestamp, "gadget: brand-gadget\n", "gadget: brand-gadget_instance\n", 1)
- a, err = asserts.Decode([]byte(encoded))
- c.Assert(a, IsNil)
- c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "gadget" header: brand-gadget_instance`)
-
- encoded = strings.Replace(withTimestamp, "base: core18\n", "base: core18_instance\n", 1)
- a, err = asserts.Decode([]byte(encoded))
- c.Assert(a, IsNil)
- c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "base" header: core18_instance`)
-}
-
-func (mods modelSuite) TestDecodeValidSnapNames(c *C) {
- // reuse test cases for snap.ValidateName()
-
- withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
-
- validNames := []string{
- "aa", "aaa", "aaaa",
- "a-a", "aa-a", "a-aa", "a-b-c",
- "a0", "a-0", "a-0a",
- "01game", "1-or-2",
- // a regexp stresser
- "u-94903713687486543234157734673284536758",
- }
- for _, name := range validNames {
- encoded := strings.Replace(withTimestamp, "kernel: baz-linux\n", fmt.Sprintf("kernel: %s\n", name), 1)
- a, err := asserts.Decode([]byte(encoded))
- c.Assert(err, IsNil)
- model := a.(*asserts.Model)
- c.Check(model.Kernel(), Equals, name)
- }
- invalidNames := []string{
- // name cannot be empty, never reaches snap name validation
- "",
- // too short (min 2 chars)
- "a",
- // names cannot be too long
- "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
- "xxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxx",
- "1111111111111111111111111111111111111111x",
- "x1111111111111111111111111111111111111111",
- "x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x",
- // a regexp stresser
- "u-9490371368748654323415773467328453675-",
- // dashes alone are not a name
- "-", "--",
- // double dashes in a name are not allowed
- "a--a",
- // name should not end with a dash
- "a-",
- // name cannot have any spaces in it
- "a ", " a", "a a",
- // a number alone is not a name
- "0", "123",
- // identifier must be plain ASCII
- "日本語", "한글", "ру́сский язы́к",
- // instance names are invalid too
- "foo_bar", "x_1",
- }
- for _, name := range invalidNames {
- encoded := strings.Replace(withTimestamp, "kernel: baz-linux\n", fmt.Sprintf("kernel: %s\n", name), 1)
- a, err := asserts.Decode([]byte(encoded))
- c.Assert(a, IsNil)
- if name != "" {
- c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "kernel" header: .*`)
- } else {
- c.Assert(err, ErrorMatches, `assertion model: "kernel" header should not be empty`)
- }
- }
-}
-
-func (mods *modelSuite) TestDecodeSystemUserAuthorityIsOptional(c *C) {
- withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
- encoded := strings.Replace(withTimestamp, sysUserAuths, "", 1)
- a, err := asserts.Decode([]byte(encoded))
- c.Assert(err, IsNil)
- model := a.(*asserts.Model)
- // the default is just to accept the brand itself
- c.Check(model.SystemUserAuthority(), DeepEquals, []string{"brand-id1"})
-
- encoded = strings.Replace(withTimestamp, sysUserAuths, "system-user-authority:\n - foo\n - bar\n", 1)
- a, err = asserts.Decode([]byte(encoded))
- c.Assert(err, IsNil)
- model = a.(*asserts.Model)
- c.Check(model.SystemUserAuthority(), DeepEquals, []string{"foo", "bar"})
-}
-
-func (mods *modelSuite) TestDecodeKernelTrack(c *C) {
- withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
- encoded := strings.Replace(withTimestamp, "kernel: baz-linux\n", "kernel: baz-linux=18\n", 1)
- a, err := asserts.Decode([]byte(encoded))
- c.Assert(err, IsNil)
- model := a.(*asserts.Model)
- c.Check(model.Kernel(), Equals, "baz-linux")
- c.Check(model.KernelTrack(), Equals, "18")
-}
-
-func (mods *modelSuite) TestDecodeGadgetTrack(c *C) {
- withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
- encoded := strings.Replace(withTimestamp, "gadget: brand-gadget\n", "gadget: brand-gadget=18\n", 1)
- a, err := asserts.Decode([]byte(encoded))
- c.Assert(err, IsNil)
- model := a.(*asserts.Model)
- c.Check(model.Gadget(), Equals, "brand-gadget")
- c.Check(model.GadgetTrack(), Equals, "18")
-}
-
-const (
- modelErrPrefix = "assertion model: "
-)
-
-func (mods *modelSuite) TestDecodeInvalid(c *C) {
- encoded := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
-
- invalidTests := []struct{ original, invalid, expectedErr string }{
- {"series: 16\n", "", `"series" header is mandatory`},
- {"series: 16\n", "series: \n", `"series" header should not be empty`},
- {"brand-id: brand-id1\n", "", `"brand-id" header is mandatory`},
- {"brand-id: brand-id1\n", "brand-id: \n", `"brand-id" header should not be empty`},
- {"brand-id: brand-id1\n", "brand-id: random\n", `authority-id and brand-id must match, model assertions are expected to be signed by the brand: "brand-id1" != "random"`},
- {"model: baz-3000\n", "", `"model" header is mandatory`},
- {"model: baz-3000\n", "model: \n", `"model" header should not be empty`},
- {"model: baz-3000\n", "model: baz/3000\n", `"model" primary key header cannot contain '/'`},
- // lift this restriction at a later point
- {"model: baz-3000\n", "model: BAZ-3000\n", `"model" header cannot contain uppercase letters`},
- {"display-name: Baz 3000\n", "display-name:\n - xyz\n", `"display-name" header must be a string`},
- {"architecture: amd64\n", "", `"architecture" header is mandatory`},
- {"architecture: amd64\n", "architecture: \n", `"architecture" header should not be empty`},
- {"gadget: brand-gadget\n", "", `"gadget" header is mandatory`},
- {"gadget: brand-gadget\n", "gadget: \n", `"gadget" header should not be empty`},
- {"gadget: brand-gadget\n", "gadget: brand-gadget=x/x/x\n", `"gadget" channel selector must be a track name only`},
- {"gadget: brand-gadget\n", "gadget: brand-gadget=stable\n", `"gadget" channel selector must be a track name`},
- {"gadget: brand-gadget\n", "gadget: brand-gadget=18/beta\n", `"gadget" channel selector must be a track name only`},
- {"gadget: brand-gadget\n", "gadget:\n - xyz \n", `"gadget" header must be a string`},
- {"kernel: baz-linux\n", "", `"kernel" header is mandatory`},
- {"kernel: baz-linux\n", "kernel: \n", `"kernel" header should not be empty`},
- {"kernel: baz-linux\n", "kernel: baz-linux=x/x/x\n", `"kernel" channel selector must be a track name only`},
- {"kernel: baz-linux\n", "kernel: baz-linux=stable\n", `"kernel" channel selector must be a track name`},
- {"kernel: baz-linux\n", "kernel: baz-linux=18/beta\n", `"kernel" channel selector must be a track name only`},
- {"kernel: baz-linux\n", "kernel:\n - xyz \n", `"kernel" header must be a string`},
- {"store: brand-store\n", "store:\n - xyz\n", `"store" header must be a string`},
- {mods.tsLine, "", `"timestamp" header is mandatory`},
- {mods.tsLine, "timestamp: \n", `"timestamp" header should not be empty`},
- {mods.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`},
- {reqSnaps, "required-snaps: foo\n", `"required-snaps" header must be a list of strings`},
- {reqSnaps, "required-snaps:\n -\n - nested\n", `"required-snaps" header must be a list of strings`},
- {sysUserAuths, "system-user-authority:\n a: 1\n", `"system-user-authority" header must be '\*' or a list of account ids`},
- {sysUserAuths, "system-user-authority:\n - 5_6\n", `"system-user-authority" header must be '\*' or a list of account ids`},
- }
-
- for _, test := range invalidTests {
- invalid := strings.Replace(encoded, test.original, test.invalid, 1)
- _, err := asserts.Decode([]byte(invalid))
- c.Check(err, ErrorMatches, modelErrPrefix+test.expectedErr)
- }
-}
-
-func (mods *modelSuite) TestModelCheck(c *C) {
- ex, err := asserts.Decode([]byte(strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)))
- c.Assert(err, IsNil)
-
- storeDB, db := makeStoreAndCheckDB(c)
- brandDB := setup3rdPartySigning(c, "brand-id1", storeDB, db)
-
- headers := ex.Headers()
- headers["brand-id"] = brandDB.AuthorityID
- headers["timestamp"] = time.Now().Format(time.RFC3339)
- model, err := brandDB.Sign(asserts.ModelType, headers, nil, "")
- c.Assert(err, IsNil)
-
- err = db.Check(model)
- c.Assert(err, IsNil)
-}
-
-func (mods *modelSuite) TestModelCheckInconsistentTimestamp(c *C) {
- ex, err := asserts.Decode([]byte(strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)))
- c.Assert(err, IsNil)
-
- storeDB, db := makeStoreAndCheckDB(c)
- brandDB := setup3rdPartySigning(c, "brand-id1", storeDB, db)
-
- headers := ex.Headers()
- headers["brand-id"] = brandDB.AuthorityID
- headers["timestamp"] = "2011-01-01T14:00:00Z"
- model, err := brandDB.Sign(asserts.ModelType, headers, nil, "")
- c.Assert(err, IsNil)
-
- err = db.Check(model)
- c.Assert(err, ErrorMatches, `model assertion timestamp outside of signing key validity \(key valid since.*\)`)
-}
-
-func (mods *modelSuite) TestClassicDecodeOK(c *C) {
- encoded := strings.Replace(classicModelExample, "TSLINE", mods.tsLine, 1)
- a, err := asserts.Decode([]byte(encoded))
- c.Assert(err, IsNil)
- c.Check(a.Type(), Equals, asserts.ModelType)
- model := a.(*asserts.Model)
- c.Check(model.AuthorityID(), Equals, "brand-id1")
- c.Check(model.Timestamp(), Equals, mods.ts)
- c.Check(model.Series(), Equals, "16")
- c.Check(model.BrandID(), Equals, "brand-id1")
- c.Check(model.Model(), Equals, "baz-3000")
- c.Check(model.DisplayName(), Equals, "Baz 3000")
- c.Check(model.Classic(), Equals, true)
- c.Check(model.Architecture(), Equals, "amd64")
- c.Check(model.Gadget(), Equals, "brand-gadget")
- c.Check(model.Kernel(), Equals, "")
- c.Check(model.KernelTrack(), Equals, "")
- c.Check(model.Store(), Equals, "brand-store")
- c.Check(model.RequiredSnaps(), DeepEquals, []string{"foo", "bar"})
-}
-
-func (mods *modelSuite) TestClassicDecodeInvalid(c *C) {
- encoded := strings.Replace(classicModelExample, "TSLINE", mods.tsLine, 1)
-
- invalidTests := []struct{ original, invalid, expectedErr string }{
- {"classic: true\n", "classic: foo\n", `"classic" header must be 'true' or 'false'`},
- {"architecture: amd64\n", "architecture:\n - foo\n", `"architecture" header must be a string`},
- {"gadget: brand-gadget\n", "gadget:\n - foo\n", `"gadget" header must be a string`},
- {"gadget: brand-gadget\n", "kernel: brand-kernel\n", `cannot specify a kernel with a classic model`},
- {"gadget: brand-gadget\n", "base: some-base\n", `cannot specify a base with a classic model`},
- }
-
- for _, test := range invalidTests {
- invalid := strings.Replace(encoded, test.original, test.invalid, 1)
- _, err := asserts.Decode([]byte(invalid))
- c.Check(err, ErrorMatches, modelErrPrefix+test.expectedErr)
- }
-}
-
-func (mods *modelSuite) TestClassicDecodeGadgetAndArchOptional(c *C) {
- encoded := strings.Replace(classicModelExample, "TSLINE", mods.tsLine, 1)
- encoded = strings.Replace(encoded, "gadget: brand-gadget\n", "", 1)
- encoded = strings.Replace(encoded, "architecture: amd64\n", "", 1)
- a, err := asserts.Decode([]byte(encoded))
- c.Assert(err, IsNil)
- c.Check(a.Type(), Equals, asserts.ModelType)
- model := a.(*asserts.Model)
- c.Check(model.Classic(), Equals, true)
- c.Check(model.Architecture(), Equals, "")
- c.Check(model.Gadget(), Equals, "")
-}
-
-type serialSuite struct {
- ts time.Time
- tsLine string
- deviceKey asserts.PrivateKey
- encodedDevKey string
-}
-
-func (ss *serialSuite) SetUpSuite(c *C) {
- ss.ts = time.Now().Truncate(time.Second).UTC()
- ss.tsLine = "timestamp: " + ss.ts.Format(time.RFC3339) + "\n"
-
- ss.deviceKey = testPrivKey2
- encodedPubKey, err := asserts.EncodePublicKey(ss.deviceKey.PublicKey())
- c.Assert(err, IsNil)
- ss.encodedDevKey = string(encodedPubKey)
-}
-
-const serialExample = "type: serial\n" +
- "authority-id: brand-id1\n" +
- "brand-id: brand-id1\n" +
- "model: baz-3000\n" +
- "serial: 2700\n" +
- "device-key:\n DEVICEKEY\n" +
- "device-key-sha3-384: KEYID\n" +
- "TSLINE" +
- "body-length: 2\n" +
- "sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij\n\n" +
- "HW" +
- "\n\n" +
- "AXNpZw=="
-
-func (ss *serialSuite) TestDecodeOK(c *C) {
- encoded := strings.Replace(serialExample, "TSLINE", ss.tsLine, 1)
- encoded = strings.Replace(encoded, "DEVICEKEY", strings.Replace(ss.encodedDevKey, "\n", "\n ", -1), 1)
- encoded = strings.Replace(encoded, "KEYID", ss.deviceKey.PublicKey().ID(), 1)
- a, err := asserts.Decode([]byte(encoded))
- c.Assert(err, IsNil)
- c.Check(a.Type(), Equals, asserts.SerialType)
- serial := a.(*asserts.Serial)
- c.Check(serial.AuthorityID(), Equals, "brand-id1")
- c.Check(serial.Timestamp(), Equals, ss.ts)
- c.Check(serial.BrandID(), Equals, "brand-id1")
- c.Check(serial.Model(), Equals, "baz-3000")
- c.Check(serial.Serial(), Equals, "2700")
- c.Check(serial.DeviceKey().ID(), Equals, ss.deviceKey.PublicKey().ID())
-}
-
-const (
- deviceSessReqErrPrefix = "assertion device-session-request: "
- serialErrPrefix = "assertion serial: "
- serialReqErrPrefix = "assertion serial-request: "
-)
-
-func (ss *serialSuite) TestDecodeInvalid(c *C) {
- encoded := strings.Replace(serialExample, "TSLINE", ss.tsLine, 1)
-
- invalidTests := []struct{ original, invalid, expectedErr string }{
- {"brand-id: brand-id1\n", "", `"brand-id" header is mandatory`},
- {"brand-id: brand-id1\n", "brand-id: \n", `"brand-id" header should not be empty`},
- {"authority-id: brand-id1\n", "authority-id: random\n", `authority-id and brand-id must match, serial assertions are expected to be signed by the brand: "random" != "brand-id1"`},
- {"model: baz-3000\n", "", `"model" header is mandatory`},
- {"model: baz-3000\n", "model: \n", `"model" header should not be empty`},
- {"model: baz-3000\n", "model: _what\n", `"model" header contains invalid characters: "_what"`},
- {"serial: 2700\n", "", `"serial" header is mandatory`},
- {"serial: 2700\n", "serial: \n", `"serial" header should not be empty`},
- {ss.tsLine, "", `"timestamp" header is mandatory`},
- {ss.tsLine, "timestamp: \n", `"timestamp" header should not be empty`},
- {ss.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`},
- {"device-key:\n DEVICEKEY\n", "", `"device-key" header is mandatory`},
- {"device-key:\n DEVICEKEY\n", "device-key: \n", `"device-key" header should not be empty`},
- {"device-key:\n DEVICEKEY\n", "device-key: $$$\n", `cannot decode public key: .*`},
- {"device-key-sha3-384: KEYID\n", "", `"device-key-sha3-384" header is mandatory`},
- }
-
- for _, test := range invalidTests {
- invalid := strings.Replace(encoded, test.original, test.invalid, 1)
- invalid = strings.Replace(invalid, "DEVICEKEY", strings.Replace(ss.encodedDevKey, "\n", "\n ", -1), 1)
- invalid = strings.Replace(invalid, "KEYID", ss.deviceKey.PublicKey().ID(), 1)
- _, err := asserts.Decode([]byte(invalid))
- c.Check(err, ErrorMatches, serialErrPrefix+test.expectedErr)
- }
-}
-
-func (ss *serialSuite) TestDecodeKeyIDMismatch(c *C) {
- invalid := strings.Replace(serialExample, "TSLINE", ss.tsLine, 1)
- invalid = strings.Replace(invalid, "DEVICEKEY", strings.Replace(ss.encodedDevKey, "\n", "\n ", -1), 1)
- invalid = strings.Replace(invalid, "KEYID", "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 1)
-
- _, err := asserts.Decode([]byte(invalid))
- c.Check(err, ErrorMatches, serialErrPrefix+"device key does not match provided key id")
-}
-
-func (ss *serialSuite) TestSerialCheck(c *C) {
- encoded := strings.Replace(serialExample, "TSLINE", ss.tsLine, 1)
- encoded = strings.Replace(encoded, "DEVICEKEY", strings.Replace(ss.encodedDevKey, "\n", "\n ", -1), 1)
- encoded = strings.Replace(encoded, "KEYID", ss.deviceKey.PublicKey().ID(), 1)
- ex, err := asserts.Decode([]byte(encoded))
- c.Assert(err, IsNil)
-
- storeDB, db := makeStoreAndCheckDB(c)
- brandDB := setup3rdPartySigning(c, "brand1", storeDB, db)
-
- tests := []struct {
- signDB assertstest.SignerDB
- brandID string
- authID string
- keyID string
- }{
- {brandDB, brandDB.AuthorityID, "", brandDB.KeyID},
- }
-
- for _, test := range tests {
- headers := ex.Headers()
- headers["brand-id"] = test.brandID
- if test.authID != "" {
- headers["authority-id"] = test.authID
- } else {
- headers["authority-id"] = test.brandID
- }
- headers["timestamp"] = time.Now().Format(time.RFC3339)
- serial, err := test.signDB.Sign(asserts.SerialType, headers, nil, test.keyID)
- c.Assert(err, IsNil)
-
- err = db.Check(serial)
- c.Check(err, IsNil)
- }
-}
-
-func (ss *serialSuite) TestSerialRequestHappy(c *C) {
- sreq, err := asserts.SignWithoutAuthority(asserts.SerialRequestType,
- map[string]interface{}{
- "brand-id": "brand-id1",
- "model": "baz-3000",
- "device-key": ss.encodedDevKey,
- "request-id": "REQID",
- }, []byte("HW-DETAILS"), ss.deviceKey)
- c.Assert(err, IsNil)
-
- // roundtrip
- a, err := asserts.Decode(asserts.Encode(sreq))
- c.Assert(err, IsNil)
-
- sreq2, ok := a.(*asserts.SerialRequest)
- c.Assert(ok, Equals, true)
-
- // standalone signature check
- err = asserts.SignatureCheck(sreq2, sreq2.DeviceKey())
- c.Check(err, IsNil)
-
- c.Check(sreq2.BrandID(), Equals, "brand-id1")
- c.Check(sreq2.Model(), Equals, "baz-3000")
- c.Check(sreq2.RequestID(), Equals, "REQID")
-
- c.Check(sreq2.Serial(), Equals, "")
-}
-
-func (ss *serialSuite) TestSerialRequestHappyOptionalSerial(c *C) {
- sreq, err := asserts.SignWithoutAuthority(asserts.SerialRequestType,
- map[string]interface{}{
- "brand-id": "brand-id1",
- "model": "baz-3000",
- "serial": "pserial",
- "device-key": ss.encodedDevKey,
- "request-id": "REQID",
- }, []byte("HW-DETAILS"), ss.deviceKey)
- c.Assert(err, IsNil)
-
- // roundtrip
- a, err := asserts.Decode(asserts.Encode(sreq))
- c.Assert(err, IsNil)
-
- sreq2, ok := a.(*asserts.SerialRequest)
- c.Assert(ok, Equals, true)
-
- c.Check(sreq2.Model(), Equals, "baz-3000")
- c.Check(sreq2.Serial(), Equals, "pserial")
-}
-
-func (ss *serialSuite) TestSerialRequestDecodeInvalid(c *C) {
- encoded := "type: serial-request\n" +
- "brand-id: brand-id1\n" +
- "model: baz-3000\n" +
- "device-key:\n DEVICEKEY\n" +
- "request-id: REQID\n" +
- "serial: S\n" +
- "body-length: 2\n" +
- "sign-key-sha3-384: " + ss.deviceKey.PublicKey().ID() + "\n\n" +
- "HW" +
- "\n\n" +
- "AXNpZw=="
-
- invalidTests := []struct{ original, invalid, expectedErr string }{
- {"brand-id: brand-id1\n", "", `"brand-id" header is mandatory`},
- {"brand-id: brand-id1\n", "brand-id: \n", `"brand-id" header should not be empty`},
- {"model: baz-3000\n", "", `"model" header is mandatory`},
- {"model: baz-3000\n", "model: \n", `"model" header should not be empty`},
- {"request-id: REQID\n", "", `"request-id" header is mandatory`},
- {"request-id: REQID\n", "request-id: \n", `"request-id" header should not be empty`},
- {"device-key:\n DEVICEKEY\n", "", `"device-key" header is mandatory`},
- {"device-key:\n DEVICEKEY\n", "device-key: \n", `"device-key" header should not be empty`},
- {"device-key:\n DEVICEKEY\n", "device-key: $$$\n", `cannot decode public key: .*`},
- {"serial: S\n", "serial:\n - xyz\n", `"serial" header must be a string`},
- }
-
- for _, test := range invalidTests {
- invalid := strings.Replace(encoded, test.original, test.invalid, 1)
- invalid = strings.Replace(invalid, "DEVICEKEY", strings.Replace(ss.encodedDevKey, "\n", "\n ", -1), 1)
-
- _, err := asserts.Decode([]byte(invalid))
- c.Check(err, ErrorMatches, serialReqErrPrefix+test.expectedErr)
- }
-}
-
-func (ss *serialSuite) TestSerialRequestDecodeKeyIDMismatch(c *C) {
- invalid := "type: serial-request\n" +
- "brand-id: brand-id1\n" +
- "model: baz-3000\n" +
- "device-key:\n " + strings.Replace(ss.encodedDevKey, "\n", "\n ", -1) + "\n" +
- "request-id: REQID\n" +
- "body-length: 2\n" +
- "sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij\n\n" +
- "HW" +
- "\n\n" +
- "AXNpZw=="
-
- _, err := asserts.Decode([]byte(invalid))
- c.Check(err, ErrorMatches, "assertion serial-request: device key does not match included signing key id")
-}
-
-func (ss *serialSuite) TestDeviceSessionRequest(c *C) {
- ts := time.Now().UTC().Round(time.Second)
- sessReq, err := asserts.SignWithoutAuthority(asserts.DeviceSessionRequestType,
- map[string]interface{}{
- "brand-id": "brand-id1",
- "model": "baz-3000",
- "serial": "99990",
- "nonce": "NONCE",
- "timestamp": ts.Format(time.RFC3339),
- }, nil, ss.deviceKey)
- c.Assert(err, IsNil)
-
- // roundtrip
- a, err := asserts.Decode(asserts.Encode(sessReq))
- c.Assert(err, IsNil)
-
- sessReq2, ok := a.(*asserts.DeviceSessionRequest)
- c.Assert(ok, Equals, true)
-
- // standalone signature check
- err = asserts.SignatureCheck(sessReq2, ss.deviceKey.PublicKey())
- c.Check(err, IsNil)
-
- c.Check(sessReq2.BrandID(), Equals, "brand-id1")
- c.Check(sessReq2.Model(), Equals, "baz-3000")
- c.Check(sessReq2.Serial(), Equals, "99990")
- c.Check(sessReq2.Nonce(), Equals, "NONCE")
- c.Check(sessReq2.Timestamp().Equal(ts), Equals, true)
-}
-
-func (ss *serialSuite) TestDeviceSessionRequestDecodeInvalid(c *C) {
- tsLine := "timestamp: " + time.Now().Format(time.RFC3339) + "\n"
- encoded := "type: device-session-request\n" +
- "brand-id: brand-id1\n" +
- "model: baz-3000\n" +
- "serial: 99990\n" +
- "nonce: NONCE\n" +
- tsLine +
- "body-length: 0\n" +
- "sign-key-sha3-384: " + ss.deviceKey.PublicKey().ID() + "\n\n" +
- "AXNpZw=="
-
- invalidTests := []struct{ original, invalid, expectedErr string }{
- {"brand-id: brand-id1\n", "brand-id: \n", `"brand-id" header should not be empty`},
- {"model: baz-3000\n", "model: \n", `"model" header should not be empty`},
- {"serial: 99990\n", "", `"serial" header is mandatory`},
- {"nonce: NONCE\n", "nonce: \n", `"nonce" header should not be empty`},
- {tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`},
- }
-
- for _, test := range invalidTests {
- invalid := strings.Replace(encoded, test.original, test.invalid, 1)
- _, err := asserts.Decode([]byte(invalid))
- c.Check(err, ErrorMatches, deviceSessReqErrPrefix+test.expectedErr)
- }
-}
diff -Nru snapd-2.41+19.10.1/asserts/export_test.go snapd-2.42.1+19.10/asserts/export_test.go
--- snapd-2.41+19.10.1/asserts/export_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/asserts/export_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -191,3 +191,7 @@
func RuleFeature(rule featureExposer, flabel string) bool {
return rule.feature(flabel)
}
+
+func (b *Batch) DoPrecheck(db *Database) error {
+ return b.precheck(db)
+}
diff -Nru snapd-2.41+19.10.1/asserts/header_checks.go snapd-2.42.1+19.10/asserts/header_checks.go
--- snapd-2.41+19.10.1/asserts/header_checks.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/asserts/header_checks.go 2019-10-30 12:17:43.000000000 +0000
@@ -62,18 +62,22 @@
return s, nil
}
-func checkOptionalString(headers map[string]interface{}, name string) (string, error) {
+func checkOptionalStringWhat(headers map[string]interface{}, name, what string) (string, error) {
value, ok := headers[name]
if !ok {
return "", nil
}
s, ok := value.(string)
if !ok {
- return "", fmt.Errorf("%q header must be a string", name)
+ return "", fmt.Errorf("%q %s must be a string", name, what)
}
return s, nil
}
+func checkOptionalString(headers map[string]interface{}, name string) (string, error) {
+ return checkOptionalStringWhat(headers, name, "header")
+}
+
func checkPrimaryKey(headers map[string]interface{}, primKey string) (string, error) {
value, err := checkNotEmptyString(headers, primKey)
if err != nil {
diff -Nru snapd-2.41+19.10.1/asserts/model.go snapd-2.42.1+19.10/asserts/model.go
--- snapd-2.41+19.10.1/asserts/model.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/asserts/model.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,686 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2016-2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package asserts
+
+import (
+ "fmt"
+ "regexp"
+ "strings"
+ "time"
+
+ "github.com/snapcore/snapd/snap/channel"
+ "github.com/snapcore/snapd/snap/naming"
+ "github.com/snapcore/snapd/strutil"
+)
+
+// TODO: for ModelSnap
+// * consider moving snap.Type out of snap and using it in ModelSnap
+// but remember assertions use "core" (never "os") for TypeOS
+// * consider having a first-class Presence type
+
+// ModelSnap holds the details about a snap specified by a model assertion.
+type ModelSnap struct {
+ Name string
+ SnapID string
+ // SnapType is one of: app|base|gadget|kernel|core, default is app
+ SnapType string
+ // Modes in which the snap must be made available
+ Modes []string
+ // DefaultChannel is the initial tracking channel, default is stable
+ DefaultChannel string
+ // Track is a locked track for the snap, if set DefaultChannel
+ // cannot be set at the same time
+ Track string
+ // Presence is one of: required|optional
+ Presence string
+}
+
+// SnapName implements naming.SnapRef.
+func (s *ModelSnap) SnapName() string {
+ return s.Name
+}
+
+// ID implements naming.SnapRef.
+func (s *ModelSnap) ID() string {
+ return s.SnapID
+}
+
+type modelSnaps struct {
+ base *ModelSnap
+ gadget *ModelSnap
+ kernel *ModelSnap
+ snapsNoEssential []*ModelSnap
+}
+
+func (ms *modelSnaps) list() (allSnaps []*ModelSnap, requiredWithEssentialSnaps []naming.SnapRef, numEssentialSnaps int) {
+ addSnap := func(snap *ModelSnap, essentialSnap int) {
+ if snap == nil {
+ return
+ }
+ numEssentialSnaps += essentialSnap
+ allSnaps = append(allSnaps, snap)
+ if snap.Presence == "required" {
+ requiredWithEssentialSnaps = append(requiredWithEssentialSnaps, snap)
+ }
+ }
+
+ addSnap(ms.base, 1)
+ addSnap(ms.gadget, 1)
+ addSnap(ms.kernel, 1)
+ for _, snap := range ms.snapsNoEssential {
+ addSnap(snap, 0)
+ }
+ return allSnaps, requiredWithEssentialSnaps, numEssentialSnaps
+}
+
+var (
+ essentialSnapModes = []string{"run", "ephemeral"}
+ defaultModes = []string{"run"}
+)
+
+func checkExtendedSnaps(extendedSnaps interface{}, base string) (*modelSnaps, error) {
+ const wrongHeaderType = `"snaps" header must be a list of maps`
+
+ entries, ok := extendedSnaps.([]interface{})
+ if !ok {
+ return nil, fmt.Errorf(wrongHeaderType)
+ }
+
+ var modelSnaps modelSnaps
+ seen := make(map[string]bool, len(entries))
+ seenIDs := make(map[string]string, len(entries))
+
+ for _, entry := range entries {
+ snap, ok := entry.(map[string]interface{})
+ if !ok {
+ return nil, fmt.Errorf(wrongHeaderType)
+ }
+ modelSnap, err := checkModelSnap(snap)
+ if err != nil {
+ return nil, err
+ }
+
+ if seen[modelSnap.Name] {
+ return nil, fmt.Errorf("cannot list the same snap %q multiple times", modelSnap.Name)
+ }
+ // at this time we do not support parallel installing
+ // from model/seed
+ if underName := seenIDs[modelSnap.SnapID]; underName != "" {
+ return nil, fmt.Errorf("cannot specify the same snap id %q multiple times, specified for snaps %q and %q", modelSnap.SnapID, underName, modelSnap.Name)
+ }
+ seen[modelSnap.Name] = true
+ seenIDs[modelSnap.SnapID] = modelSnap.Name
+
+ essential := false
+ switch {
+ case modelSnap.SnapType == "kernel":
+ essential = true
+ if modelSnaps.kernel != nil {
+ return nil, fmt.Errorf("cannot specify multiple kernel snaps: %q and %q", modelSnaps.kernel.Name, modelSnap.Name)
+ }
+ modelSnaps.kernel = modelSnap
+ case modelSnap.SnapType == "gadget":
+ essential = true
+ if modelSnaps.gadget != nil {
+ return nil, fmt.Errorf("cannot specify multiple gadget snaps: %q and %q", modelSnaps.gadget.Name, modelSnap.Name)
+ }
+ modelSnaps.gadget = modelSnap
+ case modelSnap.Name == base:
+ essential = true
+ if modelSnap.SnapType != "base" {
+ return nil, fmt.Errorf(`boot base %q must specify type "base", not %q`, base, modelSnap.SnapType)
+ }
+ modelSnaps.base = modelSnap
+ }
+
+ if essential {
+ if len(modelSnap.Modes) != 0 || modelSnap.Presence != "" {
+ return nil, fmt.Errorf("essential snaps are always available, cannot specify modes or presence for snap %q", modelSnap.Name)
+ }
+ modelSnap.Modes = essentialSnapModes
+ }
+
+ if len(modelSnap.Modes) == 0 {
+ modelSnap.Modes = defaultModes
+ }
+ if modelSnap.Presence == "" {
+ modelSnap.Presence = "required"
+ }
+
+ if !essential {
+ modelSnaps.snapsNoEssential = append(modelSnaps.snapsNoEssential, modelSnap)
+ }
+ }
+
+ return &modelSnaps, nil
+}
+
+var (
+ validSnapTypes = []string{"app", "base", "gadget", "kernel", "core"}
+ validSnapMode = regexp.MustCompile("^[a-z][-a-z]+$")
+ validSnapPresences = []string{"required", "optional"}
+)
+
+func checkModelSnap(snap map[string]interface{}) (*ModelSnap, error) {
+ name, err := checkNotEmptyStringWhat(snap, "name", "of snap")
+ if err != nil {
+ return nil, err
+ }
+ if err := naming.ValidateSnap(name); err != nil {
+ return nil, fmt.Errorf("invalid snap name %q", name)
+ }
+
+ what := fmt.Sprintf("of snap %q", name)
+
+ snapID, err := checkStringMatchesWhat(snap, "id", what, validSnapID)
+ if err != nil {
+ return nil, err
+ }
+
+ typ, err := checkOptionalStringWhat(snap, "type", what)
+ if err != nil {
+ return nil, err
+ }
+ if typ == "" {
+ typ = "app"
+ }
+ if !strutil.ListContains(validSnapTypes, typ) {
+ return nil, fmt.Errorf("type of snap %q must be one of app|base|gadget|kernel|core", name)
+ }
+
+ modes, err := checkStringListInMap(snap, "modes", fmt.Sprintf("%q %s", "modes", what), validSnapMode)
+ if err != nil {
+ return nil, err
+ }
+
+ defaultChannel, err := checkOptionalStringWhat(snap, "default-channel", what)
+ if err != nil {
+ return nil, err
+ }
+ // TODO: final name of this
+ track, err := checkOptionalStringWhat(snap, "track", what)
+ if err != nil {
+ return nil, err
+ }
+
+ if defaultChannel != "" && track != "" {
+ return nil, fmt.Errorf("snap %q cannot specify both default channel and locked track", name)
+ }
+ if track == "" && defaultChannel == "" {
+ defaultChannel = "stable"
+ }
+
+ if defaultChannel != "" {
+ _, err := channel.Parse(defaultChannel, "-")
+ if err != nil {
+ return nil, fmt.Errorf("invalid default channel for snap %q: %v", name, err)
+ }
+ } else {
+ trackCh, err := channel.ParseVerbatim(track, "-")
+ if err != nil || !trackCh.VerbatimTrackOnly() {
+ return nil, fmt.Errorf("invalid locked track for snap %q: %s", name, track)
+ }
+ }
+
+ presence, err := checkOptionalStringWhat(snap, "presence", what)
+ if err != nil {
+ return nil, err
+ }
+ if presence != "" && !strutil.ListContains(validSnapPresences, presence) {
+ return nil, fmt.Errorf("presence of snap %q must be one of required|optional", name)
+ }
+
+ return &ModelSnap{
+ Name: name,
+ SnapID: snapID,
+ SnapType: typ,
+ Modes: modes, // can be empty
+ DefaultChannel: defaultChannel,
+ Track: track,
+ Presence: presence, // can be empty
+ }, nil
+}
+
+// unextended case support
+
+func checkSnapWithTrack(headers map[string]interface{}, which string) (*ModelSnap, error) {
+ _, ok := headers[which]
+ if !ok {
+ return nil, nil
+ }
+ value, ok := headers[which].(string)
+ if !ok {
+ return nil, fmt.Errorf(`%q header must be a string`, which)
+ }
+ l := strings.SplitN(value, "=", 2)
+
+ name := l[0]
+ track := ""
+ if err := validateSnapName(name, which); err != nil {
+ return nil, err
+ }
+ if len(l) > 1 {
+ track = l[1]
+ if strings.Count(track, "/") != 0 {
+ return nil, fmt.Errorf(`%q channel selector must be a track name only`, which)
+ }
+ channelRisks := []string{"stable", "candidate", "beta", "edge"}
+ if strutil.ListContains(channelRisks, track) {
+ return nil, fmt.Errorf(`%q channel selector must be a track name`, which)
+ }
+ }
+
+ defaultChannel := ""
+ if track == "" {
+ defaultChannel = "stable"
+ }
+
+ return &ModelSnap{
+ Name: name,
+ SnapType: which,
+ Modes: defaultModes,
+ DefaultChannel: defaultChannel,
+ Track: track,
+ Presence: "required",
+ }, nil
+}
+
+func validateSnapName(name string, headerName string) error {
+ if err := naming.ValidateSnap(name); err != nil {
+ return fmt.Errorf("invalid snap name in %q header: %s", headerName, name)
+ }
+ return nil
+}
+
+func checkRequiredSnap(name string, headerName string, snapType string) (*ModelSnap, error) {
+ if err := validateSnapName(name, headerName); err != nil {
+ return nil, err
+ }
+
+ return &ModelSnap{
+ Name: name,
+ SnapType: snapType,
+ Modes: defaultModes,
+ DefaultChannel: "stable",
+ Presence: "required",
+ }, nil
+}
+
+// Model holds a model assertion, which is a statement by a brand
+// about the properties of a device model.
+type Model struct {
+ assertionBase
+ classic bool
+
+ baseSnap *ModelSnap
+ gadgetSnap *ModelSnap
+ kernelSnap *ModelSnap
+
+ allSnaps []*ModelSnap
+ // consumers of this info should care only about snap identity =>
+ // snapRef
+ requiredWithEssentialSnaps []naming.SnapRef
+ numEssentialSnaps int
+
+ sysUserAuthority []string
+ timestamp time.Time
+}
+
+// BrandID returns the brand identifier. Same as the authority id.
+func (mod *Model) BrandID() string {
+ return mod.HeaderString("brand-id")
+}
+
+// Model returns the model name identifier.
+func (mod *Model) Model() string {
+ return mod.HeaderString("model")
+}
+
+// DisplayName returns the human-friendly name of the model or
+// falls back to Model if this was not set.
+func (mod *Model) DisplayName() string {
+ display := mod.HeaderString("display-name")
+ if display == "" {
+ return mod.Model()
+ }
+ return display
+}
+
+// Series returns the series of the core software the model uses.
+func (mod *Model) Series() string {
+ return mod.HeaderString("series")
+}
+
+// Classic returns whether the model is a classic system.
+func (mod *Model) Classic() bool {
+ return mod.classic
+}
+
+// Architecture returns the archicteture the model is based on.
+func (mod *Model) Architecture() string {
+ return mod.HeaderString("architecture")
+}
+
+// GadgetSnap returns the details of the gadget snap the model uses.
+func (mod *Model) GadgetSnap() *ModelSnap {
+ return mod.gadgetSnap
+}
+
+// Gadget returns the gadget snap the model uses.
+func (mod *Model) Gadget() string {
+ if mod.gadgetSnap == nil {
+ return ""
+ }
+ return mod.gadgetSnap.Name
+}
+
+// GadgetTrack returns the gadget track the model uses.
+// XXX this should go away
+func (mod *Model) GadgetTrack() string {
+ if mod.gadgetSnap == nil {
+ return ""
+ }
+ return mod.gadgetSnap.Track
+}
+
+// KernelSnap returns the details of the kernel snap the model uses.
+func (mod *Model) KernelSnap() *ModelSnap {
+ return mod.kernelSnap
+}
+
+// Kernel returns the kernel snap the model uses.
+// XXX this should go away
+func (mod *Model) Kernel() string {
+ if mod.kernelSnap == nil {
+ return ""
+ }
+ return mod.kernelSnap.Name
+}
+
+// KernelTrack returns the kernel track the model uses.
+// XXX this should go away
+func (mod *Model) KernelTrack() string {
+ if mod.kernelSnap == nil {
+ return ""
+ }
+ return mod.kernelSnap.Track
+}
+
+// Base returns the base snap the model uses.
+func (mod *Model) Base() string {
+ return mod.HeaderString("base")
+}
+
+// BaseSnap returns the details of the base snap the model uses.
+func (mod *Model) BaseSnap() *ModelSnap {
+ return mod.baseSnap
+}
+
+// Store returns the snap store the model uses.
+func (mod *Model) Store() string {
+ return mod.HeaderString("store")
+}
+
+// RequiredNoEssentialSnaps returns the snaps that must be installed at all times and cannot be removed for this model, excluding the essential snaps (gadget, kernel, boot base).
+func (mod *Model) RequiredNoEssentialSnaps() []naming.SnapRef {
+ return mod.requiredWithEssentialSnaps[mod.numEssentialSnaps:]
+}
+
+// RequiredWithEssentialSnaps returns the snaps that must be installed at all times and cannot be removed for this model, including the essential snaps (gadget, kernel, boot base).
+func (mod *Model) RequiredWithEssentialSnaps() []naming.SnapRef {
+ return mod.requiredWithEssentialSnaps
+}
+
+// AllSnaps returns all the snap listed by the model.
+func (mod *Model) AllSnaps() []*ModelSnap {
+ return mod.allSnaps
+}
+
+// SystemUserAuthority returns the authority ids that are accepted as signers of system-user assertions for this model. Empty list means any.
+func (mod *Model) SystemUserAuthority() []string {
+ return mod.sysUserAuthority
+}
+
+// Timestamp returns the time when the model assertion was issued.
+func (mod *Model) Timestamp() time.Time {
+ return mod.timestamp
+}
+
+// Implement further consistency checks.
+func (mod *Model) checkConsistency(db RODatabase, acck *AccountKey) error {
+ // TODO: double check trust level of authority depending on class and possibly allowed-modes
+ return nil
+}
+
+// sanity
+var _ consistencyChecker = (*Model)(nil)
+
+// limit model to only lowercase for now
+var validModel = regexp.MustCompile("^[a-zA-Z0-9](?:-?[a-zA-Z0-9])*$")
+
+func checkModel(headers map[string]interface{}) (string, error) {
+ s, err := checkStringMatches(headers, "model", validModel)
+ if err != nil {
+ return "", err
+ }
+
+ // TODO: support the concept of case insensitive/preserving string headers
+ if strings.ToLower(s) != s {
+ return "", fmt.Errorf(`"model" header cannot contain uppercase letters`)
+ }
+ return s, nil
+}
+
+func checkAuthorityMatchesBrand(a Assertion) error {
+ typeName := a.Type().Name
+ authorityID := a.AuthorityID()
+ brand := a.HeaderString("brand-id")
+ if brand != authorityID {
+ return fmt.Errorf("authority-id and brand-id must match, %s assertions are expected to be signed by the brand: %q != %q", typeName, authorityID, brand)
+ }
+ return nil
+}
+
+func checkOptionalSystemUserAuthority(headers map[string]interface{}, brandID string) ([]string, error) {
+ const name = "system-user-authority"
+ v, ok := headers[name]
+ if !ok {
+ return []string{brandID}, nil
+ }
+ switch x := v.(type) {
+ case string:
+ if x == "*" {
+ return nil, nil
+ }
+ case []interface{}:
+ lst, err := checkStringListMatches(headers, name, validAccountID)
+ if err == nil {
+ return lst, nil
+ }
+ }
+ return nil, fmt.Errorf("%q header must be '*' or a list of account ids", name)
+}
+
+var (
+ modelMandatory = []string{"architecture", "gadget", "kernel"}
+ extendedCoreMandatory = []string{"architecture", "base"}
+ extendedSnapsConflicting = []string{"gadget", "kernel", "required-snaps"}
+ classicModelOptional = []string{"architecture", "gadget"}
+)
+
+func assembleModel(assert assertionBase) (Assertion, error) {
+ err := checkAuthorityMatchesBrand(&assert)
+ if err != nil {
+ return nil, err
+ }
+
+ _, err = checkModel(assert.headers)
+ if err != nil {
+ return nil, err
+ }
+
+ classic, err := checkOptionalBool(assert.headers, "classic")
+ if err != nil {
+ return nil, err
+ }
+
+ // Core 20 extended snaps header
+ extendedSnaps, extended := assert.headers["snaps"]
+ if extended {
+ if classic {
+ return nil, fmt.Errorf("cannot use extended snaps header for a classic model (yet)")
+ }
+
+ for _, conflicting := range extendedSnapsConflicting {
+ if _, ok := assert.headers[conflicting]; ok {
+ return nil, fmt.Errorf("cannot specify separate %q header once using the extended snaps header", conflicting)
+ }
+ }
+
+ } else if classic {
+ if _, ok := assert.headers["kernel"]; ok {
+ return nil, fmt.Errorf("cannot specify a kernel with a classic model")
+ }
+ if _, ok := assert.headers["base"]; ok {
+ return nil, fmt.Errorf("cannot specify a base with a classic model")
+ }
+ }
+
+ checker := checkNotEmptyString
+ toCheck := modelMandatory
+ if extended {
+ toCheck = extendedCoreMandatory
+ } else if classic {
+ checker = checkOptionalString
+ toCheck = classicModelOptional
+ }
+
+ for _, h := range toCheck {
+ if _, err := checker(assert.headers, h); err != nil {
+ return nil, err
+ }
+ }
+
+ // base, if provided, must be a valid snap name too
+ var baseSnap *ModelSnap
+ base, err := checkOptionalString(assert.headers, "base")
+ if err != nil {
+ return nil, err
+ }
+ if base != "" {
+ baseSnap, err = checkRequiredSnap(base, "base", "base")
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ // store is optional but must be a string, defaults to the ubuntu store
+ if _, err = checkOptionalString(assert.headers, "store"); err != nil {
+ return nil, err
+ }
+
+ // display-name is optional but must be a string
+ if _, err = checkOptionalString(assert.headers, "display-name"); err != nil {
+ return nil, err
+ }
+
+ var modSnaps *modelSnaps
+ if extended {
+ // TODO: support and consider grade!
+ modSnaps, err = checkExtendedSnaps(extendedSnaps, base)
+ if err != nil {
+ return nil, err
+ }
+ if modSnaps.gadget == nil {
+ return nil, fmt.Errorf(`one "snaps" header entry must specify the model gadget`)
+ }
+ if modSnaps.kernel == nil {
+ return nil, fmt.Errorf(`one "snaps" header entry must specify the model kernel`)
+ }
+
+ if modSnaps.base == nil {
+ // complete with defaults,
+ // the assumption is that base names are very stable
+ // essentially fixed
+ modSnaps.base = baseSnap
+ modSnaps.base.Modes = essentialSnapModes
+ }
+ } else {
+ modSnaps = &modelSnaps{
+ base: baseSnap,
+ }
+ // kernel/gadget must be valid snap names and can have (optional) tracks
+ // - validate those
+ modSnaps.kernel, err = checkSnapWithTrack(assert.headers, "kernel")
+ if err != nil {
+ return nil, err
+ }
+ modSnaps.gadget, err = checkSnapWithTrack(assert.headers, "gadget")
+ if err != nil {
+ return nil, err
+ }
+
+ // required snap must be valid snap names
+ reqSnaps, err := checkStringList(assert.headers, "required-snaps")
+ if err != nil {
+ return nil, err
+ }
+ for _, name := range reqSnaps {
+ reqSnap, err := checkRequiredSnap(name, "required-snaps", "")
+ if err != nil {
+ return nil, err
+ }
+ modSnaps.snapsNoEssential = append(modSnaps.snapsNoEssential, reqSnap)
+ }
+ }
+
+ sysUserAuthority, err := checkOptionalSystemUserAuthority(assert.headers, assert.HeaderString("brand-id"))
+ if err != nil {
+ return nil, err
+ }
+
+ timestamp, err := checkRFC3339Date(assert.headers, "timestamp")
+ if err != nil {
+ return nil, err
+ }
+
+ allSnaps, requiredWithEssentialSnaps, numEssentialSnaps := modSnaps.list()
+
+ // NB:
+ // * core is not supported at this time, it defaults to ubuntu-core
+ // in prepare-image until rename and/or introduction of the header.
+ // * some form of allowed-modes, class are postponed,
+ //
+ // prepare-image takes care of not allowing them for now
+
+ // ignore extra headers and non-empty body for future compatibility
+ return &Model{
+ assertionBase: assert,
+ classic: classic,
+ baseSnap: modSnaps.base,
+ gadgetSnap: modSnaps.gadget,
+ kernelSnap: modSnaps.kernel,
+ allSnaps: allSnaps,
+ requiredWithEssentialSnaps: requiredWithEssentialSnaps,
+ numEssentialSnaps: numEssentialSnaps,
+ sysUserAuthority: sysUserAuthority,
+ timestamp: timestamp,
+ }, nil
+}
diff -Nru snapd-2.41+19.10.1/asserts/model_test.go snapd-2.42.1+19.10/asserts/model_test.go
--- snapd-2.41+19.10.1/asserts/model_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/asserts/model_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,731 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2016-2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package asserts_test
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/asserts"
+)
+
+type modelSuite struct {
+ ts time.Time
+ tsLine string
+}
+
+var (
+ _ = Suite(&modelSuite{})
+)
+
+func (mods *modelSuite) SetUpSuite(c *C) {
+ mods.ts = time.Now().Truncate(time.Second).UTC()
+ mods.tsLine = "timestamp: " + mods.ts.Format(time.RFC3339) + "\n"
+}
+
+const (
+ reqSnaps = "required-snaps:\n - foo\n - bar\n"
+ sysUserAuths = "system-user-authority: *\n"
+)
+
+const (
+ modelExample = "type: model\n" +
+ "authority-id: brand-id1\n" +
+ "series: 16\n" +
+ "brand-id: brand-id1\n" +
+ "model: baz-3000\n" +
+ "display-name: Baz 3000\n" +
+ "architecture: amd64\n" +
+ "gadget: brand-gadget\n" +
+ "base: core18\n" +
+ "kernel: baz-linux\n" +
+ "store: brand-store\n" +
+ sysUserAuths +
+ reqSnaps +
+ "TSLINE" +
+ "body-length: 0\n" +
+ "sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
+ "\n\n" +
+ "AXNpZw=="
+
+ classicModelExample = "type: model\n" +
+ "authority-id: brand-id1\n" +
+ "series: 16\n" +
+ "brand-id: brand-id1\n" +
+ "model: baz-3000\n" +
+ "display-name: Baz 3000\n" +
+ "classic: true\n" +
+ "architecture: amd64\n" +
+ "gadget: brand-gadget\n" +
+ "store: brand-store\n" +
+ reqSnaps +
+ "TSLINE" +
+ "body-length: 0\n" +
+ "sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
+ "\n\n" +
+ "AXNpZw=="
+
+ core20ModelExample = `type: model
+authority-id: brand-id1
+series: 16
+brand-id: brand-id1
+model: baz-3000
+display-name: Baz 3000
+architecture: amd64
+system-user-authority: *
+base: core20
+store: brand-store
+snaps:
+ -
+ name: baz-linux
+ id: bazlinuxidididididididididididid
+ type: kernel
+ track: 20
+ -
+ name: brand-gadget
+ id: brandgadgetdidididididididididid
+ type: gadget
+ -
+ name: other-base
+ id: otherbasedididididididididididid
+ type: base
+ modes:
+ - run
+ presence: required
+ -
+ name: nm
+ id: nmididididididididididididididid
+ modes:
+ - ephemeral
+ - run
+ default-channel: 1.0
+ -
+ name: myapp
+ id: myappdididididididididididididid
+ type: app
+ default-channel: 2.0
+ -
+ name: myappopt
+ id: myappoptidididididididididididid
+ type: app
+ presence: optional
+OTHERgrade: stable
+` + "TSLINE" +
+ "body-length: 0\n" +
+ "sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
+ "\n\n" +
+ "AXNpZw=="
+)
+
+func (mods *modelSuite) TestDecodeOK(c *C) {
+ encoded := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
+ a, err := asserts.Decode([]byte(encoded))
+ c.Assert(err, IsNil)
+ c.Check(a.Type(), Equals, asserts.ModelType)
+ model := a.(*asserts.Model)
+ c.Check(model.AuthorityID(), Equals, "brand-id1")
+ c.Check(model.Timestamp(), Equals, mods.ts)
+ c.Check(model.Series(), Equals, "16")
+ c.Check(model.BrandID(), Equals, "brand-id1")
+ c.Check(model.Model(), Equals, "baz-3000")
+ c.Check(model.DisplayName(), Equals, "Baz 3000")
+ c.Check(model.Architecture(), Equals, "amd64")
+ c.Check(model.GadgetSnap(), DeepEquals, &asserts.ModelSnap{
+ Name: "brand-gadget",
+ SnapType: "gadget",
+ Modes: []string{"run"},
+ DefaultChannel: "stable",
+ Presence: "required",
+ })
+ c.Check(model.Gadget(), Equals, "brand-gadget")
+ c.Check(model.GadgetTrack(), Equals, "")
+ c.Check(model.KernelSnap(), DeepEquals, &asserts.ModelSnap{
+ Name: "baz-linux",
+ SnapType: "kernel",
+ Modes: []string{"run"},
+ DefaultChannel: "stable",
+ Presence: "required",
+ })
+ c.Check(model.Kernel(), Equals, "baz-linux")
+ c.Check(model.KernelTrack(), Equals, "")
+ c.Check(model.Base(), Equals, "core18")
+ c.Check(model.BaseSnap(), DeepEquals, &asserts.ModelSnap{
+ Name: "core18",
+ SnapType: "base",
+ Modes: []string{"run"},
+ DefaultChannel: "stable",
+ Presence: "required",
+ })
+ c.Check(model.Store(), Equals, "brand-store")
+ allSnaps := model.AllSnaps()
+ c.Check(allSnaps, DeepEquals, []*asserts.ModelSnap{
+ model.BaseSnap(),
+ model.GadgetSnap(),
+ model.KernelSnap(),
+ {
+ Name: "foo",
+ Modes: []string{"run"},
+ DefaultChannel: "stable",
+ Presence: "required",
+ },
+ {
+ Name: "bar",
+ Modes: []string{"run"},
+ DefaultChannel: "stable",
+ Presence: "required",
+ },
+ })
+ // essential snaps included
+ reqSnaps := model.RequiredWithEssentialSnaps()
+ c.Check(reqSnaps, HasLen, len(allSnaps))
+ for i, r := range reqSnaps {
+ c.Check(r.SnapName(), Equals, allSnaps[i].Name)
+ c.Check(r.ID(), Equals, "")
+ }
+ // essential snaps excluded
+ c.Check(model.RequiredNoEssentialSnaps(), DeepEquals, reqSnaps[3:])
+ c.Check(model.SystemUserAuthority(), HasLen, 0)
+}
+
+func (mods *modelSuite) TestDecodeStoreIsOptional(c *C) {
+ withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
+ encoded := strings.Replace(withTimestamp, "store: brand-store\n", "store: \n", 1)
+ a, err := asserts.Decode([]byte(encoded))
+ c.Assert(err, IsNil)
+ model := a.(*asserts.Model)
+ c.Check(model.Store(), Equals, "")
+
+ encoded = strings.Replace(withTimestamp, "store: brand-store\n", "", 1)
+ a, err = asserts.Decode([]byte(encoded))
+ c.Assert(err, IsNil)
+ model = a.(*asserts.Model)
+ c.Check(model.Store(), Equals, "")
+}
+
+func (mods *modelSuite) TestDecodeBaseIsOptional(c *C) {
+ withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
+ encoded := strings.Replace(withTimestamp, "base: core18\n", "base: \n", 1)
+ a, err := asserts.Decode([]byte(encoded))
+ c.Assert(err, IsNil)
+ model := a.(*asserts.Model)
+ c.Check(model.Base(), Equals, "")
+
+ encoded = strings.Replace(withTimestamp, "base: core18\n", "", 1)
+ a, err = asserts.Decode([]byte(encoded))
+ c.Assert(err, IsNil)
+ model = a.(*asserts.Model)
+ c.Check(model.Base(), Equals, "")
+ c.Check(model.BaseSnap(), IsNil)
+}
+
+func (mods *modelSuite) TestDecodeDisplayNameIsOptional(c *C) {
+ withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
+ encoded := strings.Replace(withTimestamp, "display-name: Baz 3000\n", "display-name: \n", 1)
+ a, err := asserts.Decode([]byte(encoded))
+ c.Assert(err, IsNil)
+ model := a.(*asserts.Model)
+ // optional but we fallback to Model
+ c.Check(model.DisplayName(), Equals, "baz-3000")
+
+ encoded = strings.Replace(withTimestamp, "display-name: Baz 3000\n", "", 1)
+ a, err = asserts.Decode([]byte(encoded))
+ c.Assert(err, IsNil)
+ model = a.(*asserts.Model)
+ // optional but we fallback to Model
+ c.Check(model.DisplayName(), Equals, "baz-3000")
+}
+
+func (mods *modelSuite) TestDecodeRequiredSnapsAreOptional(c *C) {
+ withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
+ encoded := strings.Replace(withTimestamp, reqSnaps, "", 1)
+ a, err := asserts.Decode([]byte(encoded))
+ c.Assert(err, IsNil)
+ model := a.(*asserts.Model)
+ c.Check(model.RequiredNoEssentialSnaps(), HasLen, 0)
+}
+
+func (mods *modelSuite) TestDecodeValidatesSnapNames(c *C) {
+ withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
+ encoded := strings.Replace(withTimestamp, reqSnaps, "required-snaps:\n - foo_bar\n - bar\n", 1)
+ a, err := asserts.Decode([]byte(encoded))
+ c.Assert(a, IsNil)
+ c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "required-snaps" header: foo_bar`)
+
+ encoded = strings.Replace(withTimestamp, reqSnaps, "required-snaps:\n - foo\n - bar-;;''\n", 1)
+ a, err = asserts.Decode([]byte(encoded))
+ c.Assert(a, IsNil)
+ c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "required-snaps" header: bar-;;''`)
+
+ encoded = strings.Replace(withTimestamp, "kernel: baz-linux\n", "kernel: baz-linux_instance\n", 1)
+ a, err = asserts.Decode([]byte(encoded))
+ c.Assert(a, IsNil)
+ c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "kernel" header: baz-linux_instance`)
+
+ encoded = strings.Replace(withTimestamp, "gadget: brand-gadget\n", "gadget: brand-gadget_instance\n", 1)
+ a, err = asserts.Decode([]byte(encoded))
+ c.Assert(a, IsNil)
+ c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "gadget" header: brand-gadget_instance`)
+
+ encoded = strings.Replace(withTimestamp, "base: core18\n", "base: core18_instance\n", 1)
+ a, err = asserts.Decode([]byte(encoded))
+ c.Assert(a, IsNil)
+ c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "base" header: core18_instance`)
+}
+
+func (mods modelSuite) TestDecodeValidSnapNames(c *C) {
+ // reuse test cases for snap.ValidateName()
+
+ withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
+
+ validNames := []string{
+ "aa", "aaa", "aaaa",
+ "a-a", "aa-a", "a-aa", "a-b-c",
+ "a0", "a-0", "a-0a",
+ "01game", "1-or-2",
+ // a regexp stresser
+ "u-94903713687486543234157734673284536758",
+ }
+ for _, name := range validNames {
+ encoded := strings.Replace(withTimestamp, "kernel: baz-linux\n", fmt.Sprintf("kernel: %s\n", name), 1)
+ a, err := asserts.Decode([]byte(encoded))
+ c.Assert(err, IsNil)
+ model := a.(*asserts.Model)
+ c.Check(model.Kernel(), Equals, name)
+ }
+ invalidNames := []string{
+ // name cannot be empty, never reaches snap name validation
+ "",
+ // too short (min 2 chars)
+ "a",
+ // names cannot be too long
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
+ "xxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxx",
+ "1111111111111111111111111111111111111111x",
+ "x1111111111111111111111111111111111111111",
+ "x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x",
+ // a regexp stresser
+ "u-9490371368748654323415773467328453675-",
+ // dashes alone are not a name
+ "-", "--",
+ // double dashes in a name are not allowed
+ "a--a",
+ // name should not end with a dash
+ "a-",
+ // name cannot have any spaces in it
+ "a ", " a", "a a",
+ // a number alone is not a name
+ "0", "123",
+ // identifier must be plain ASCII
+ "日本語", "한글", "ру́сский язы́к",
+ // instance names are invalid too
+ "foo_bar", "x_1",
+ }
+ for _, name := range invalidNames {
+ encoded := strings.Replace(withTimestamp, "kernel: baz-linux\n", fmt.Sprintf("kernel: %s\n", name), 1)
+ a, err := asserts.Decode([]byte(encoded))
+ c.Assert(a, IsNil)
+ if name != "" {
+ c.Assert(err, ErrorMatches, `assertion model: invalid snap name in "kernel" header: .*`)
+ } else {
+ c.Assert(err, ErrorMatches, `assertion model: "kernel" header should not be empty`)
+ }
+ }
+}
+
+func (mods *modelSuite) TestDecodeSystemUserAuthorityIsOptional(c *C) {
+ withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
+ encoded := strings.Replace(withTimestamp, sysUserAuths, "", 1)
+ a, err := asserts.Decode([]byte(encoded))
+ c.Assert(err, IsNil)
+ model := a.(*asserts.Model)
+ // the default is just to accept the brand itself
+ c.Check(model.SystemUserAuthority(), DeepEquals, []string{"brand-id1"})
+
+ encoded = strings.Replace(withTimestamp, sysUserAuths, "system-user-authority:\n - foo\n - bar\n", 1)
+ a, err = asserts.Decode([]byte(encoded))
+ c.Assert(err, IsNil)
+ model = a.(*asserts.Model)
+ c.Check(model.SystemUserAuthority(), DeepEquals, []string{"foo", "bar"})
+}
+
+func (mods *modelSuite) TestDecodeKernelTrack(c *C) {
+ withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
+ encoded := strings.Replace(withTimestamp, "kernel: baz-linux\n", "kernel: baz-linux=18\n", 1)
+ a, err := asserts.Decode([]byte(encoded))
+ c.Assert(err, IsNil)
+ model := a.(*asserts.Model)
+ c.Check(model.KernelSnap(), DeepEquals, &asserts.ModelSnap{
+ Name: "baz-linux",
+ SnapType: "kernel",
+ Modes: []string{"run"},
+ Track: "18",
+ Presence: "required",
+ })
+ c.Check(model.Kernel(), Equals, "baz-linux")
+ c.Check(model.KernelTrack(), Equals, "18")
+}
+
+func (mods *modelSuite) TestDecodeGadgetTrack(c *C) {
+ withTimestamp := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
+ encoded := strings.Replace(withTimestamp, "gadget: brand-gadget\n", "gadget: brand-gadget=18\n", 1)
+ a, err := asserts.Decode([]byte(encoded))
+ c.Assert(err, IsNil)
+ model := a.(*asserts.Model)
+ c.Check(model.GadgetSnap(), DeepEquals, &asserts.ModelSnap{
+ Name: "brand-gadget",
+ SnapType: "gadget",
+ Modes: []string{"run"},
+ Track: "18",
+ Presence: "required",
+ })
+ c.Check(model.Gadget(), Equals, "brand-gadget")
+ c.Check(model.GadgetTrack(), Equals, "18")
+}
+
+const (
+ modelErrPrefix = "assertion model: "
+)
+
+func (mods *modelSuite) TestDecodeInvalid(c *C) {
+ encoded := strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)
+
+ invalidTests := []struct{ original, invalid, expectedErr string }{
+ {"series: 16\n", "", `"series" header is mandatory`},
+ {"series: 16\n", "series: \n", `"series" header should not be empty`},
+ {"brand-id: brand-id1\n", "", `"brand-id" header is mandatory`},
+ {"brand-id: brand-id1\n", "brand-id: \n", `"brand-id" header should not be empty`},
+ {"brand-id: brand-id1\n", "brand-id: random\n", `authority-id and brand-id must match, model assertions are expected to be signed by the brand: "brand-id1" != "random"`},
+ {"model: baz-3000\n", "", `"model" header is mandatory`},
+ {"model: baz-3000\n", "model: \n", `"model" header should not be empty`},
+ {"model: baz-3000\n", "model: baz/3000\n", `"model" primary key header cannot contain '/'`},
+ // lift this restriction at a later point
+ {"model: baz-3000\n", "model: BAZ-3000\n", `"model" header cannot contain uppercase letters`},
+ {"display-name: Baz 3000\n", "display-name:\n - xyz\n", `"display-name" header must be a string`},
+ {"architecture: amd64\n", "", `"architecture" header is mandatory`},
+ {"architecture: amd64\n", "architecture: \n", `"architecture" header should not be empty`},
+ {"gadget: brand-gadget\n", "", `"gadget" header is mandatory`},
+ {"gadget: brand-gadget\n", "gadget: \n", `"gadget" header should not be empty`},
+ {"gadget: brand-gadget\n", "gadget: brand-gadget=x/x/x\n", `"gadget" channel selector must be a track name only`},
+ {"gadget: brand-gadget\n", "gadget: brand-gadget=stable\n", `"gadget" channel selector must be a track name`},
+ {"gadget: brand-gadget\n", "gadget: brand-gadget=18/beta\n", `"gadget" channel selector must be a track name only`},
+ {"gadget: brand-gadget\n", "gadget:\n - xyz \n", `"gadget" header must be a string`},
+ {"kernel: baz-linux\n", "", `"kernel" header is mandatory`},
+ {"kernel: baz-linux\n", "kernel: \n", `"kernel" header should not be empty`},
+ {"kernel: baz-linux\n", "kernel: baz-linux=x/x/x\n", `"kernel" channel selector must be a track name only`},
+ {"kernel: baz-linux\n", "kernel: baz-linux=stable\n", `"kernel" channel selector must be a track name`},
+ {"kernel: baz-linux\n", "kernel: baz-linux=18/beta\n", `"kernel" channel selector must be a track name only`},
+ {"kernel: baz-linux\n", "kernel:\n - xyz \n", `"kernel" header must be a string`},
+ {"base: core18\n", "base:\n - xyz \n", `"base" header must be a string`},
+ {"store: brand-store\n", "store:\n - xyz\n", `"store" header must be a string`},
+ {mods.tsLine, "", `"timestamp" header is mandatory`},
+ {mods.tsLine, "timestamp: \n", `"timestamp" header should not be empty`},
+ {mods.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`},
+ {reqSnaps, "required-snaps: foo\n", `"required-snaps" header must be a list of strings`},
+ {reqSnaps, "required-snaps:\n -\n - nested\n", `"required-snaps" header must be a list of strings`},
+ {sysUserAuths, "system-user-authority:\n a: 1\n", `"system-user-authority" header must be '\*' or a list of account ids`},
+ {sysUserAuths, "system-user-authority:\n - 5_6\n", `"system-user-authority" header must be '\*' or a list of account ids`},
+ }
+
+ for _, test := range invalidTests {
+ invalid := strings.Replace(encoded, test.original, test.invalid, 1)
+ _, err := asserts.Decode([]byte(invalid))
+ c.Check(err, ErrorMatches, modelErrPrefix+test.expectedErr)
+ }
+}
+
+func (mods *modelSuite) TestModelCheck(c *C) {
+ ex, err := asserts.Decode([]byte(strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)))
+ c.Assert(err, IsNil)
+
+ storeDB, db := makeStoreAndCheckDB(c)
+ brandDB := setup3rdPartySigning(c, "brand-id1", storeDB, db)
+
+ headers := ex.Headers()
+ headers["brand-id"] = brandDB.AuthorityID
+ headers["timestamp"] = time.Now().Format(time.RFC3339)
+ model, err := brandDB.Sign(asserts.ModelType, headers, nil, "")
+ c.Assert(err, IsNil)
+
+ err = db.Check(model)
+ c.Assert(err, IsNil)
+}
+
+func (mods *modelSuite) TestModelCheckInconsistentTimestamp(c *C) {
+ ex, err := asserts.Decode([]byte(strings.Replace(modelExample, "TSLINE", mods.tsLine, 1)))
+ c.Assert(err, IsNil)
+
+ storeDB, db := makeStoreAndCheckDB(c)
+ brandDB := setup3rdPartySigning(c, "brand-id1", storeDB, db)
+
+ headers := ex.Headers()
+ headers["brand-id"] = brandDB.AuthorityID
+ headers["timestamp"] = "2011-01-01T14:00:00Z"
+ model, err := brandDB.Sign(asserts.ModelType, headers, nil, "")
+ c.Assert(err, IsNil)
+
+ err = db.Check(model)
+ c.Assert(err, ErrorMatches, `model assertion timestamp outside of signing key validity \(key valid since.*\)`)
+}
+
+func (mods *modelSuite) TestClassicDecodeOK(c *C) {
+ encoded := strings.Replace(classicModelExample, "TSLINE", mods.tsLine, 1)
+ a, err := asserts.Decode([]byte(encoded))
+ c.Assert(err, IsNil)
+ c.Check(a.Type(), Equals, asserts.ModelType)
+ model := a.(*asserts.Model)
+ c.Check(model.AuthorityID(), Equals, "brand-id1")
+ c.Check(model.Timestamp(), Equals, mods.ts)
+ c.Check(model.Series(), Equals, "16")
+ c.Check(model.BrandID(), Equals, "brand-id1")
+ c.Check(model.Model(), Equals, "baz-3000")
+ c.Check(model.DisplayName(), Equals, "Baz 3000")
+ c.Check(model.Classic(), Equals, true)
+ c.Check(model.Architecture(), Equals, "amd64")
+ c.Check(model.GadgetSnap(), DeepEquals, &asserts.ModelSnap{
+ Name: "brand-gadget",
+ SnapType: "gadget",
+ Modes: []string{"run"},
+ DefaultChannel: "stable",
+ Presence: "required",
+ })
+ c.Check(model.Gadget(), Equals, "brand-gadget")
+ c.Check(model.KernelSnap(), IsNil)
+ c.Check(model.Kernel(), Equals, "")
+ c.Check(model.KernelTrack(), Equals, "")
+ c.Check(model.Base(), Equals, "")
+ c.Check(model.BaseSnap(), IsNil)
+ c.Check(model.Store(), Equals, "brand-store")
+ allSnaps := model.AllSnaps()
+ c.Check(allSnaps, DeepEquals, []*asserts.ModelSnap{
+ model.GadgetSnap(),
+ {
+ Name: "foo",
+ Modes: []string{"run"},
+ DefaultChannel: "stable",
+ Presence: "required",
+ },
+ {
+ Name: "bar",
+ Modes: []string{"run"},
+ DefaultChannel: "stable",
+ Presence: "required",
+ },
+ })
+ // gadget included
+ reqSnaps := model.RequiredWithEssentialSnaps()
+ c.Check(reqSnaps, HasLen, len(allSnaps))
+ for i, r := range reqSnaps {
+ c.Check(r.SnapName(), Equals, allSnaps[i].Name)
+ c.Check(r.ID(), Equals, "")
+ }
+ // gadget excluded
+ c.Check(model.RequiredNoEssentialSnaps(), DeepEquals, reqSnaps[1:])
+}
+
+func (mods *modelSuite) TestClassicDecodeInvalid(c *C) {
+ encoded := strings.Replace(classicModelExample, "TSLINE", mods.tsLine, 1)
+
+ invalidTests := []struct{ original, invalid, expectedErr string }{
+ {"classic: true\n", "classic: foo\n", `"classic" header must be 'true' or 'false'`},
+ {"architecture: amd64\n", "architecture:\n - foo\n", `"architecture" header must be a string`},
+ {"gadget: brand-gadget\n", "gadget:\n - foo\n", `"gadget" header must be a string`},
+ {"gadget: brand-gadget\n", "kernel: brand-kernel\n", `cannot specify a kernel with a classic model`},
+ {"gadget: brand-gadget\n", "base: some-base\n", `cannot specify a base with a classic model`},
+ {"gadget: brand-gadget\n", "gadget:\n - xyz\n", `"gadget" header must be a string`},
+ }
+
+ for _, test := range invalidTests {
+ invalid := strings.Replace(encoded, test.original, test.invalid, 1)
+ _, err := asserts.Decode([]byte(invalid))
+ c.Check(err, ErrorMatches, modelErrPrefix+test.expectedErr)
+ }
+}
+
+func (mods *modelSuite) TestClassicDecodeGadgetAndArchOptional(c *C) {
+ encoded := strings.Replace(classicModelExample, "TSLINE", mods.tsLine, 1)
+ encoded = strings.Replace(encoded, "gadget: brand-gadget\n", "", 1)
+ encoded = strings.Replace(encoded, "architecture: amd64\n", "", 1)
+ a, err := asserts.Decode([]byte(encoded))
+ c.Assert(err, IsNil)
+ c.Check(a.Type(), Equals, asserts.ModelType)
+ model := a.(*asserts.Model)
+ c.Check(model.Classic(), Equals, true)
+ c.Check(model.Architecture(), Equals, "")
+ c.Check(model.GadgetSnap(), IsNil)
+ c.Check(model.Gadget(), Equals, "")
+ c.Check(model.GadgetTrack(), Equals, "")
+}
+
+func (mods *modelSuite) TestCore20DecodeOK(c *C) {
+ encoded := strings.Replace(core20ModelExample, "TSLINE", mods.tsLine, 1)
+ encoded = strings.Replace(encoded, "OTHER", "", 1)
+ a, err := asserts.Decode([]byte(encoded))
+ c.Assert(err, IsNil)
+ c.Check(a.Type(), Equals, asserts.ModelType)
+ model := a.(*asserts.Model)
+ c.Check(model.AuthorityID(), Equals, "brand-id1")
+ c.Check(model.Timestamp(), Equals, mods.ts)
+ c.Check(model.Series(), Equals, "16")
+ c.Check(model.BrandID(), Equals, "brand-id1")
+ c.Check(model.Model(), Equals, "baz-3000")
+ c.Check(model.DisplayName(), Equals, "Baz 3000")
+ c.Check(model.Architecture(), Equals, "amd64")
+ c.Check(model.GadgetSnap(), DeepEquals, &asserts.ModelSnap{
+ Name: "brand-gadget",
+ SnapID: "brandgadgetdidididididididididid",
+ SnapType: "gadget",
+ Modes: []string{"run", "ephemeral"},
+ DefaultChannel: "stable",
+ Presence: "required",
+ })
+ c.Check(model.Gadget(), Equals, "brand-gadget")
+ c.Check(model.GadgetTrack(), Equals, "")
+ c.Check(model.KernelSnap(), DeepEquals, &asserts.ModelSnap{
+ Name: "baz-linux",
+ SnapID: "bazlinuxidididididididididididid",
+ SnapType: "kernel",
+ Modes: []string{"run", "ephemeral"},
+ Track: "20",
+ Presence: "required",
+ })
+ c.Check(model.Kernel(), Equals, "baz-linux")
+ c.Check(model.KernelTrack(), Equals, "20")
+ c.Check(model.Base(), Equals, "core20")
+ c.Check(model.BaseSnap(), DeepEquals, &asserts.ModelSnap{
+ Name: "core20",
+ SnapType: "base",
+ Modes: []string{"run", "ephemeral"},
+ DefaultChannel: "stable",
+ Presence: "required",
+ })
+ c.Check(model.Store(), Equals, "brand-store")
+ allSnaps := model.AllSnaps()
+ c.Check(allSnaps, DeepEquals, []*asserts.ModelSnap{
+ model.BaseSnap(),
+ model.GadgetSnap(),
+ model.KernelSnap(),
+ {
+ Name: "other-base",
+ SnapID: "otherbasedididididididididididid",
+ SnapType: "base",
+ Modes: []string{"run"},
+ DefaultChannel: "stable",
+ Presence: "required",
+ },
+ {
+ Name: "nm",
+ SnapID: "nmididididididididididididididid",
+ SnapType: "app",
+ Modes: []string{"ephemeral", "run"},
+ DefaultChannel: "1.0",
+ Presence: "required",
+ },
+ {
+ Name: "myapp",
+ SnapID: "myappdididididididididididididid",
+ SnapType: "app",
+ Modes: []string{"run"},
+ DefaultChannel: "2.0",
+ Presence: "required",
+ },
+ {
+ Name: "myappopt",
+ SnapID: "myappoptidididididididididididid",
+ SnapType: "app",
+ Modes: []string{"run"},
+ DefaultChannel: "stable",
+ Presence: "optional",
+ },
+ })
+ // essential snaps included
+ reqSnaps := model.RequiredWithEssentialSnaps()
+ c.Check(reqSnaps, HasLen, len(allSnaps)-1)
+ for i, r := range reqSnaps {
+ c.Check(r.SnapName(), Equals, allSnaps[i].Name)
+ c.Check(r.ID(), Equals, allSnaps[i].SnapID)
+ }
+ // essential snaps excluded
+ c.Check(model.RequiredNoEssentialSnaps(), DeepEquals, reqSnaps[3:])
+ c.Check(model.SystemUserAuthority(), HasLen, 0)
+}
+
+func (mods *modelSuite) TestCore20ExplictBootBase(c *C) {
+ encoded := strings.Replace(core20ModelExample, "TSLINE", mods.tsLine, 1)
+ encoded = strings.Replace(encoded, "OTHER", ` -
+ name: core20
+ id: core20ididididididididididididid
+ type: base
+ default-channel: candidate
+`, 1)
+ a, err := asserts.Decode([]byte(encoded))
+ c.Assert(err, IsNil)
+ c.Check(a.Type(), Equals, asserts.ModelType)
+ // model := a.(*asserts.Model)
+}
+
+func (mods *modelSuite) TestCore20DecodeInvalid(c *C) {
+ encoded := strings.Replace(core20ModelExample, "TSLINE", mods.tsLine, 1)
+
+ snapsStanza := encoded[strings.Index(encoded, "snaps:"):strings.Index(encoded, "grade:")]
+
+ invalidTests := []struct{ original, invalid, expectedErr string }{
+ {"base: core20\n", "", `"base" header is mandatory`},
+ {"OTHER", "classic: true\n", `cannot use extended snaps header for a classic model \(yet\)`},
+ {snapsStanza, "snaps: snap\n", `"snaps" header must be a list of maps`},
+ {snapsStanza, "snaps:\n - snap\n", `"snaps" header must be a list of maps`},
+ {"name: myapp\n", "other: 1\n", `"name" of snap is mandatory`},
+ {"name: myapp\n", "name: myapp_2\n", `invalid snap name "myapp_2"`},
+ {"id: myappdididididididididididididid\n", "id: 2\n", `"id" of snap "myapp" contains invalid characters: "2"`},
+ {"type: gadget\n", "type:\n - g\n", `"type" of snap "brand-gadget" must be a string`},
+ {"type: app\n", "type: thing\n", `"type" of snap "myappopt" must be one of must be one of app|base|gadget|kernel|core`},
+ {"modes:\n - run\n", "modes: run\n", `"modes" of snap "other-base" must be a list of strings`},
+ {"track: 20\n", "track:\n - x\n", `"track" of snap "baz-linux" must be a string`},
+ {"track: 20\n", "track: 20/edge\n", `invalid locked track for snap \"baz-linux\": 20/edge`},
+ {"track: 20\n", "track: 20////\n", `invalid locked track for snap \"baz-linux\": 20////`},
+ {"default-channel: 2.0\n", "default-channel:\n - x\n", `"default-channel" of snap "myapp" must be a string`},
+ {"default-channel: 2.0\n", "default-channel: 2.0/xyz/z\n", `invalid default channel for snap "myapp": invalid risk in channel name: 2.0/xyz/z`},
+ {"track: 20\n", "track: 20\n default-channel: 20/foo\n", `snap "baz-linux" cannot specify both default channel and locked track`},
+ {"presence: optional\n", "presence:\n - opt\n", `"presence" of snap "myappopt" must be a string`},
+ {"presence: optional\n", "presence: no\n", `"presence" of snap "myappopt" must be one of must be one of required|optional`},
+ {"OTHER", " -\n name: myapp\n id: myappdididididididididididididid\n", `cannot list the same snap "myapp" multiple times`},
+ {"OTHER", " -\n name: myapp2\n id: myappdididididididididididididid\n", `cannot specify the same snap id "myappdididididididididididididid" multiple times, specified for snaps "myapp" and "myapp2"`},
+ {"OTHER", " -\n name: kernel2\n id: kernel2didididididididididididid\n type: kernel\n", `cannot specify multiple kernel snaps: "baz-linux" and "kernel2"`},
+ {"OTHER", " -\n name: gadget2\n id: gadget2didididididididididididid\n type: gadget\n", `cannot specify multiple gadget snaps: "brand-gadget" and "gadget2"`},
+ {"type: gadget\n", "type: gadget\n presence: required\n", `essential snaps are always available, cannot specify modes or presence for snap "brand-gadget"`},
+ {"type: gadget\n", "type: gadget\n modes:\n - run\n", `essential snaps are always available, cannot specify modes or presence for snap "brand-gadget"`},
+ {"type: kernel\n", "type: kernel\n presence: required\n", `essential snaps are always available, cannot specify modes or presence for snap "baz-linux"`},
+ {"OTHER", " -\n name: core20\n id: core20ididididididididididididid\n type: base\n presence: optional\n", `essential snaps are always available, cannot specify modes or presence for snap "core20"`},
+ {"type: gadget\n", "type: app\n", `one "snaps" header entry must specify the model gadget`},
+ {"type: kernel\n", "type: app\n", `one "snaps" header entry must specify the model kernel`},
+ {"OTHER", " -\n name: core20\n id: core20ididididididididididididid\n type: app\n", `boot base "core20" must specify type "base", not "app"`},
+ {"OTHER", "kernel: foo\n", `cannot specify separate "kernel" header once using the extended snaps header`},
+ {"OTHER", "gadget: foo\n", `cannot specify separate "gadget" header once using the extended snaps header`},
+ {"OTHER", "required-snaps:\n - foo\n", `cannot specify separate "required-snaps" header once using the extended snaps header`},
+ }
+ for _, test := range invalidTests {
+ invalid := strings.Replace(encoded, test.original, test.invalid, 1)
+ invalid = strings.Replace(invalid, "OTHER", "", 1)
+ _, err := asserts.Decode([]byte(invalid))
+ c.Check(err, ErrorMatches, modelErrPrefix+test.expectedErr)
+ }
+}
diff -Nru snapd-2.41+19.10.1/asserts/serial_asserts.go snapd-2.42.1+19.10/asserts/serial_asserts.go
--- snapd-2.41+19.10.1/asserts/serial_asserts.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/asserts/serial_asserts.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,229 @@
+// -*- 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 asserts
+
+import (
+ "fmt"
+ "time"
+)
+
+// Serial holds a serial assertion, which is a statement binding a
+// device identity with the device public key.
+type Serial struct {
+ assertionBase
+ timestamp time.Time
+ pubKey PublicKey
+}
+
+// BrandID returns the brand identifier of the device.
+func (ser *Serial) BrandID() string {
+ return ser.HeaderString("brand-id")
+}
+
+// Model returns the model name identifier of the device.
+func (ser *Serial) Model() string {
+ return ser.HeaderString("model")
+}
+
+// Serial returns the serial identifier of the device, together with
+// brand id and model they form the unique identifier of the device.
+func (ser *Serial) Serial() string {
+ return ser.HeaderString("serial")
+}
+
+// DeviceKey returns the public key of the device.
+func (ser *Serial) DeviceKey() PublicKey {
+ return ser.pubKey
+}
+
+// Timestamp returns the time when the serial assertion was issued.
+func (ser *Serial) Timestamp() time.Time {
+ return ser.timestamp
+}
+
+// TODO: implement further consistency checks for Serial but first review approach
+
+func assembleSerial(assert assertionBase) (Assertion, error) {
+ err := checkAuthorityMatchesBrand(&assert)
+ if err != nil {
+ return nil, err
+ }
+
+ _, err = checkModel(assert.headers)
+ if err != nil {
+ return nil, err
+ }
+
+ encodedKey, err := checkNotEmptyString(assert.headers, "device-key")
+ if err != nil {
+ return nil, err
+ }
+ pubKey, err := DecodePublicKey([]byte(encodedKey))
+ if err != nil {
+ return nil, err
+ }
+ keyID, err := checkNotEmptyString(assert.headers, "device-key-sha3-384")
+ if err != nil {
+ return nil, err
+ }
+ if keyID != pubKey.ID() {
+ return nil, fmt.Errorf("device key does not match provided key id")
+ }
+
+ timestamp, err := checkRFC3339Date(assert.headers, "timestamp")
+ if err != nil {
+ return nil, err
+ }
+
+ // ignore extra headers and non-empty body for future compatibility
+ return &Serial{
+ assertionBase: assert,
+ timestamp: timestamp,
+ pubKey: pubKey,
+ }, nil
+}
+
+// SerialRequest holds a serial-request assertion, which is a self-signed request to obtain a full device identity bound to the device public key.
+type SerialRequest struct {
+ assertionBase
+ pubKey PublicKey
+}
+
+// BrandID returns the brand identifier of the device making the request.
+func (sreq *SerialRequest) BrandID() string {
+ return sreq.HeaderString("brand-id")
+}
+
+// Model returns the model name identifier of the device making the request.
+func (sreq *SerialRequest) Model() string {
+ return sreq.HeaderString("model")
+}
+
+// Serial returns the optional proposed serial identifier for the device, the service taking the request might use it or ignore it.
+func (sreq *SerialRequest) Serial() string {
+ return sreq.HeaderString("serial")
+}
+
+// RequestID returns the id for the request, obtained from and to be presented to the serial signing service.
+func (sreq *SerialRequest) RequestID() string {
+ return sreq.HeaderString("request-id")
+}
+
+// DeviceKey returns the public key of the device making the request.
+func (sreq *SerialRequest) DeviceKey() PublicKey {
+ return sreq.pubKey
+}
+
+func assembleSerialRequest(assert assertionBase) (Assertion, error) {
+ _, err := checkNotEmptyString(assert.headers, "brand-id")
+ if err != nil {
+ return nil, err
+ }
+
+ _, err = checkModel(assert.headers)
+ if err != nil {
+ return nil, err
+ }
+
+ _, err = checkNotEmptyString(assert.headers, "request-id")
+ if err != nil {
+ return nil, err
+ }
+
+ _, err = checkOptionalString(assert.headers, "serial")
+ if err != nil {
+ return nil, err
+ }
+
+ encodedKey, err := checkNotEmptyString(assert.headers, "device-key")
+ if err != nil {
+ return nil, err
+ }
+ pubKey, err := DecodePublicKey([]byte(encodedKey))
+ if err != nil {
+ return nil, err
+ }
+
+ if pubKey.ID() != assert.SignKeyID() {
+ return nil, fmt.Errorf("device key does not match included signing key id")
+ }
+
+ // ignore extra headers and non-empty body for future compatibility
+ return &SerialRequest{
+ assertionBase: assert,
+ pubKey: pubKey,
+ }, nil
+}
+
+// DeviceSessionRequest holds a device-session-request assertion, which is a request wrapping a store-provided nonce to start a session by a device signed with its key.
+type DeviceSessionRequest struct {
+ assertionBase
+ timestamp time.Time
+}
+
+// BrandID returns the brand identifier of the device making the request.
+func (req *DeviceSessionRequest) BrandID() string {
+ return req.HeaderString("brand-id")
+}
+
+// Model returns the model name identifier of the device making the request.
+func (req *DeviceSessionRequest) Model() string {
+ return req.HeaderString("model")
+}
+
+// Serial returns the serial identifier of the device making the request,
+// together with brand id and model it forms the unique identifier of
+// the device.
+func (req *DeviceSessionRequest) Serial() string {
+ return req.HeaderString("serial")
+}
+
+// Nonce returns the nonce obtained from store and to be presented when requesting a device session.
+func (req *DeviceSessionRequest) Nonce() string {
+ return req.HeaderString("nonce")
+}
+
+// Timestamp returns the time when the device-session-request was created.
+func (req *DeviceSessionRequest) Timestamp() time.Time {
+ return req.timestamp
+}
+
+func assembleDeviceSessionRequest(assert assertionBase) (Assertion, error) {
+ _, err := checkModel(assert.headers)
+ if err != nil {
+ return nil, err
+ }
+
+ _, err = checkNotEmptyString(assert.headers, "nonce")
+ if err != nil {
+ return nil, err
+ }
+
+ timestamp, err := checkRFC3339Date(assert.headers, "timestamp")
+ if err != nil {
+ return nil, err
+ }
+
+ // ignore extra headers and non-empty body for future compatibility
+ return &DeviceSessionRequest{
+ assertionBase: assert,
+ timestamp: timestamp,
+ }, nil
+}
diff -Nru snapd-2.41+19.10.1/asserts/serial_asserts_test.go snapd-2.42.1+19.10/asserts/serial_asserts_test.go
--- snapd-2.41+19.10.1/asserts/serial_asserts_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/asserts/serial_asserts_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,320 @@
+// -*- 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 asserts_test
+
+import (
+ "strings"
+ "time"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/asserts/assertstest"
+)
+
+var (
+ _ = Suite(&serialSuite{})
+)
+
+type serialSuite struct {
+ ts time.Time
+ tsLine string
+ deviceKey asserts.PrivateKey
+ encodedDevKey string
+}
+
+func (ss *serialSuite) SetUpSuite(c *C) {
+ ss.ts = time.Now().Truncate(time.Second).UTC()
+ ss.tsLine = "timestamp: " + ss.ts.Format(time.RFC3339) + "\n"
+
+ ss.deviceKey = testPrivKey2
+ encodedPubKey, err := asserts.EncodePublicKey(ss.deviceKey.PublicKey())
+ c.Assert(err, IsNil)
+ ss.encodedDevKey = string(encodedPubKey)
+}
+
+const serialExample = "type: serial\n" +
+ "authority-id: brand-id1\n" +
+ "brand-id: brand-id1\n" +
+ "model: baz-3000\n" +
+ "serial: 2700\n" +
+ "device-key:\n DEVICEKEY\n" +
+ "device-key-sha3-384: KEYID\n" +
+ "TSLINE" +
+ "body-length: 2\n" +
+ "sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij\n\n" +
+ "HW" +
+ "\n\n" +
+ "AXNpZw=="
+
+func (ss *serialSuite) TestDecodeOK(c *C) {
+ encoded := strings.Replace(serialExample, "TSLINE", ss.tsLine, 1)
+ encoded = strings.Replace(encoded, "DEVICEKEY", strings.Replace(ss.encodedDevKey, "\n", "\n ", -1), 1)
+ encoded = strings.Replace(encoded, "KEYID", ss.deviceKey.PublicKey().ID(), 1)
+ a, err := asserts.Decode([]byte(encoded))
+ c.Assert(err, IsNil)
+ c.Check(a.Type(), Equals, asserts.SerialType)
+ serial := a.(*asserts.Serial)
+ c.Check(serial.AuthorityID(), Equals, "brand-id1")
+ c.Check(serial.Timestamp(), Equals, ss.ts)
+ c.Check(serial.BrandID(), Equals, "brand-id1")
+ c.Check(serial.Model(), Equals, "baz-3000")
+ c.Check(serial.Serial(), Equals, "2700")
+ c.Check(serial.DeviceKey().ID(), Equals, ss.deviceKey.PublicKey().ID())
+}
+
+const (
+ deviceSessReqErrPrefix = "assertion device-session-request: "
+ serialErrPrefix = "assertion serial: "
+ serialReqErrPrefix = "assertion serial-request: "
+)
+
+func (ss *serialSuite) TestDecodeInvalid(c *C) {
+ encoded := strings.Replace(serialExample, "TSLINE", ss.tsLine, 1)
+
+ invalidTests := []struct{ original, invalid, expectedErr string }{
+ {"brand-id: brand-id1\n", "", `"brand-id" header is mandatory`},
+ {"brand-id: brand-id1\n", "brand-id: \n", `"brand-id" header should not be empty`},
+ {"authority-id: brand-id1\n", "authority-id: random\n", `authority-id and brand-id must match, serial assertions are expected to be signed by the brand: "random" != "brand-id1"`},
+ {"model: baz-3000\n", "", `"model" header is mandatory`},
+ {"model: baz-3000\n", "model: \n", `"model" header should not be empty`},
+ {"model: baz-3000\n", "model: _what\n", `"model" header contains invalid characters: "_what"`},
+ {"serial: 2700\n", "", `"serial" header is mandatory`},
+ {"serial: 2700\n", "serial: \n", `"serial" header should not be empty`},
+ {ss.tsLine, "", `"timestamp" header is mandatory`},
+ {ss.tsLine, "timestamp: \n", `"timestamp" header should not be empty`},
+ {ss.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`},
+ {"device-key:\n DEVICEKEY\n", "", `"device-key" header is mandatory`},
+ {"device-key:\n DEVICEKEY\n", "device-key: \n", `"device-key" header should not be empty`},
+ {"device-key:\n DEVICEKEY\n", "device-key: $$$\n", `cannot decode public key: .*`},
+ {"device-key-sha3-384: KEYID\n", "", `"device-key-sha3-384" header is mandatory`},
+ }
+
+ for _, test := range invalidTests {
+ invalid := strings.Replace(encoded, test.original, test.invalid, 1)
+ invalid = strings.Replace(invalid, "DEVICEKEY", strings.Replace(ss.encodedDevKey, "\n", "\n ", -1), 1)
+ invalid = strings.Replace(invalid, "KEYID", ss.deviceKey.PublicKey().ID(), 1)
+ _, err := asserts.Decode([]byte(invalid))
+ c.Check(err, ErrorMatches, serialErrPrefix+test.expectedErr)
+ }
+}
+
+func (ss *serialSuite) TestDecodeKeyIDMismatch(c *C) {
+ invalid := strings.Replace(serialExample, "TSLINE", ss.tsLine, 1)
+ invalid = strings.Replace(invalid, "DEVICEKEY", strings.Replace(ss.encodedDevKey, "\n", "\n ", -1), 1)
+ invalid = strings.Replace(invalid, "KEYID", "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 1)
+
+ _, err := asserts.Decode([]byte(invalid))
+ c.Check(err, ErrorMatches, serialErrPrefix+"device key does not match provided key id")
+}
+
+func (ss *serialSuite) TestSerialCheck(c *C) {
+ encoded := strings.Replace(serialExample, "TSLINE", ss.tsLine, 1)
+ encoded = strings.Replace(encoded, "DEVICEKEY", strings.Replace(ss.encodedDevKey, "\n", "\n ", -1), 1)
+ encoded = strings.Replace(encoded, "KEYID", ss.deviceKey.PublicKey().ID(), 1)
+ ex, err := asserts.Decode([]byte(encoded))
+ c.Assert(err, IsNil)
+
+ storeDB, db := makeStoreAndCheckDB(c)
+ brandDB := setup3rdPartySigning(c, "brand1", storeDB, db)
+
+ tests := []struct {
+ signDB assertstest.SignerDB
+ brandID string
+ authID string
+ keyID string
+ }{
+ {brandDB, brandDB.AuthorityID, "", brandDB.KeyID},
+ }
+
+ for _, test := range tests {
+ headers := ex.Headers()
+ headers["brand-id"] = test.brandID
+ if test.authID != "" {
+ headers["authority-id"] = test.authID
+ } else {
+ headers["authority-id"] = test.brandID
+ }
+ headers["timestamp"] = time.Now().Format(time.RFC3339)
+ serial, err := test.signDB.Sign(asserts.SerialType, headers, nil, test.keyID)
+ c.Assert(err, IsNil)
+
+ err = db.Check(serial)
+ c.Check(err, IsNil)
+ }
+}
+
+func (ss *serialSuite) TestSerialRequestHappy(c *C) {
+ sreq, err := asserts.SignWithoutAuthority(asserts.SerialRequestType,
+ map[string]interface{}{
+ "brand-id": "brand-id1",
+ "model": "baz-3000",
+ "device-key": ss.encodedDevKey,
+ "request-id": "REQID",
+ }, []byte("HW-DETAILS"), ss.deviceKey)
+ c.Assert(err, IsNil)
+
+ // roundtrip
+ a, err := asserts.Decode(asserts.Encode(sreq))
+ c.Assert(err, IsNil)
+
+ sreq2, ok := a.(*asserts.SerialRequest)
+ c.Assert(ok, Equals, true)
+
+ // standalone signature check
+ err = asserts.SignatureCheck(sreq2, sreq2.DeviceKey())
+ c.Check(err, IsNil)
+
+ c.Check(sreq2.BrandID(), Equals, "brand-id1")
+ c.Check(sreq2.Model(), Equals, "baz-3000")
+ c.Check(sreq2.RequestID(), Equals, "REQID")
+
+ c.Check(sreq2.Serial(), Equals, "")
+}
+
+func (ss *serialSuite) TestSerialRequestHappyOptionalSerial(c *C) {
+ sreq, err := asserts.SignWithoutAuthority(asserts.SerialRequestType,
+ map[string]interface{}{
+ "brand-id": "brand-id1",
+ "model": "baz-3000",
+ "serial": "pserial",
+ "device-key": ss.encodedDevKey,
+ "request-id": "REQID",
+ }, []byte("HW-DETAILS"), ss.deviceKey)
+ c.Assert(err, IsNil)
+
+ // roundtrip
+ a, err := asserts.Decode(asserts.Encode(sreq))
+ c.Assert(err, IsNil)
+
+ sreq2, ok := a.(*asserts.SerialRequest)
+ c.Assert(ok, Equals, true)
+
+ c.Check(sreq2.Model(), Equals, "baz-3000")
+ c.Check(sreq2.Serial(), Equals, "pserial")
+}
+
+func (ss *serialSuite) TestSerialRequestDecodeInvalid(c *C) {
+ encoded := "type: serial-request\n" +
+ "brand-id: brand-id1\n" +
+ "model: baz-3000\n" +
+ "device-key:\n DEVICEKEY\n" +
+ "request-id: REQID\n" +
+ "serial: S\n" +
+ "body-length: 2\n" +
+ "sign-key-sha3-384: " + ss.deviceKey.PublicKey().ID() + "\n\n" +
+ "HW" +
+ "\n\n" +
+ "AXNpZw=="
+
+ invalidTests := []struct{ original, invalid, expectedErr string }{
+ {"brand-id: brand-id1\n", "", `"brand-id" header is mandatory`},
+ {"brand-id: brand-id1\n", "brand-id: \n", `"brand-id" header should not be empty`},
+ {"model: baz-3000\n", "", `"model" header is mandatory`},
+ {"model: baz-3000\n", "model: \n", `"model" header should not be empty`},
+ {"request-id: REQID\n", "", `"request-id" header is mandatory`},
+ {"request-id: REQID\n", "request-id: \n", `"request-id" header should not be empty`},
+ {"device-key:\n DEVICEKEY\n", "", `"device-key" header is mandatory`},
+ {"device-key:\n DEVICEKEY\n", "device-key: \n", `"device-key" header should not be empty`},
+ {"device-key:\n DEVICEKEY\n", "device-key: $$$\n", `cannot decode public key: .*`},
+ {"serial: S\n", "serial:\n - xyz\n", `"serial" header must be a string`},
+ }
+
+ for _, test := range invalidTests {
+ invalid := strings.Replace(encoded, test.original, test.invalid, 1)
+ invalid = strings.Replace(invalid, "DEVICEKEY", strings.Replace(ss.encodedDevKey, "\n", "\n ", -1), 1)
+
+ _, err := asserts.Decode([]byte(invalid))
+ c.Check(err, ErrorMatches, serialReqErrPrefix+test.expectedErr)
+ }
+}
+
+func (ss *serialSuite) TestSerialRequestDecodeKeyIDMismatch(c *C) {
+ invalid := "type: serial-request\n" +
+ "brand-id: brand-id1\n" +
+ "model: baz-3000\n" +
+ "device-key:\n " + strings.Replace(ss.encodedDevKey, "\n", "\n ", -1) + "\n" +
+ "request-id: REQID\n" +
+ "body-length: 2\n" +
+ "sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij\n\n" +
+ "HW" +
+ "\n\n" +
+ "AXNpZw=="
+
+ _, err := asserts.Decode([]byte(invalid))
+ c.Check(err, ErrorMatches, "assertion serial-request: device key does not match included signing key id")
+}
+
+func (ss *serialSuite) TestDeviceSessionRequest(c *C) {
+ ts := time.Now().UTC().Round(time.Second)
+ sessReq, err := asserts.SignWithoutAuthority(asserts.DeviceSessionRequestType,
+ map[string]interface{}{
+ "brand-id": "brand-id1",
+ "model": "baz-3000",
+ "serial": "99990",
+ "nonce": "NONCE",
+ "timestamp": ts.Format(time.RFC3339),
+ }, nil, ss.deviceKey)
+ c.Assert(err, IsNil)
+
+ // roundtrip
+ a, err := asserts.Decode(asserts.Encode(sessReq))
+ c.Assert(err, IsNil)
+
+ sessReq2, ok := a.(*asserts.DeviceSessionRequest)
+ c.Assert(ok, Equals, true)
+
+ // standalone signature check
+ err = asserts.SignatureCheck(sessReq2, ss.deviceKey.PublicKey())
+ c.Check(err, IsNil)
+
+ c.Check(sessReq2.BrandID(), Equals, "brand-id1")
+ c.Check(sessReq2.Model(), Equals, "baz-3000")
+ c.Check(sessReq2.Serial(), Equals, "99990")
+ c.Check(sessReq2.Nonce(), Equals, "NONCE")
+ c.Check(sessReq2.Timestamp().Equal(ts), Equals, true)
+}
+
+func (ss *serialSuite) TestDeviceSessionRequestDecodeInvalid(c *C) {
+ tsLine := "timestamp: " + time.Now().Format(time.RFC3339) + "\n"
+ encoded := "type: device-session-request\n" +
+ "brand-id: brand-id1\n" +
+ "model: baz-3000\n" +
+ "serial: 99990\n" +
+ "nonce: NONCE\n" +
+ tsLine +
+ "body-length: 0\n" +
+ "sign-key-sha3-384: " + ss.deviceKey.PublicKey().ID() + "\n\n" +
+ "AXNpZw=="
+
+ invalidTests := []struct{ original, invalid, expectedErr string }{
+ {"brand-id: brand-id1\n", "brand-id: \n", `"brand-id" header should not be empty`},
+ {"model: baz-3000\n", "model: \n", `"model" header should not be empty`},
+ {"serial: 99990\n", "", `"serial" header is mandatory`},
+ {"nonce: NONCE\n", "nonce: \n", `"nonce" header should not be empty`},
+ {tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`},
+ }
+
+ for _, test := range invalidTests {
+ invalid := strings.Replace(encoded, test.original, test.invalid, 1)
+ _, err := asserts.Decode([]byte(invalid))
+ c.Check(err, ErrorMatches, deviceSessReqErrPrefix+test.expectedErr)
+ }
+}
diff -Nru snapd-2.41+19.10.1/asserts/system_user.go snapd-2.42.1+19.10/asserts/system_user.go
--- snapd-2.41+19.10.1/asserts/system_user.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/asserts/system_user.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,281 @@
+// -*- 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 asserts
+
+import (
+ "fmt"
+ "net/mail"
+ "regexp"
+ "strconv"
+ "strings"
+ "time"
+)
+
+var validSystemUserUsernames = regexp.MustCompile(`^[a-z0-9][-a-z0-9+.-_]*$`)
+
+// SystemUser holds a system-user assertion which allows creating local
+// system users.
+type SystemUser struct {
+ assertionBase
+ series []string
+ models []string
+ sshKeys []string
+ since time.Time
+ until time.Time
+
+ forcePasswordChange bool
+}
+
+// BrandID returns the brand identifier that signed this assertion.
+func (su *SystemUser) BrandID() string {
+ return su.HeaderString("brand-id")
+}
+
+// Email returns the email address that this assertion is valid for.
+func (su *SystemUser) Email() string {
+ return su.HeaderString("email")
+}
+
+// Series returns the series that this assertion is valid for.
+func (su *SystemUser) Series() []string {
+ return su.series
+}
+
+// Models returns the models that this assertion is valid for.
+func (su *SystemUser) Models() []string {
+ return su.models
+}
+
+// Name returns the full name of the user (e.g. Random Guy).
+func (su *SystemUser) Name() string {
+ return su.HeaderString("name")
+}
+
+// Username returns the system user name that should be created (e.g. "foo").
+func (su *SystemUser) Username() string {
+ return su.HeaderString("username")
+}
+
+// Password returns the crypt(3) compatible password for the user.
+// Note that only ID: $6$ or stronger is supported (sha512crypt).
+func (su *SystemUser) Password() string {
+ return su.HeaderString("password")
+}
+
+// ForcePasswordChange returns true if the user needs to change the password
+// after the first login.
+func (su *SystemUser) ForcePasswordChange() bool {
+ return su.forcePasswordChange
+}
+
+// SSHKeys returns the ssh keys for the user.
+func (su *SystemUser) SSHKeys() []string {
+ return su.sshKeys
+}
+
+// Since returns the time since the assertion is valid.
+func (su *SystemUser) Since() time.Time {
+ return su.since
+}
+
+// Until returns the time until the assertion is valid.
+func (su *SystemUser) Until() time.Time {
+ return su.until
+}
+
+// ValidAt returns whether the system-user is valid at 'when' time.
+func (su *SystemUser) ValidAt(when time.Time) bool {
+ valid := when.After(su.since) || when.Equal(su.since)
+ if valid {
+ valid = when.Before(su.until)
+ }
+ return valid
+}
+
+// Implement further consistency checks.
+func (su *SystemUser) checkConsistency(db RODatabase, acck *AccountKey) error {
+ // Do the cross-checks when this assertion is actually used,
+ // i.e. in the create-user code. See also Model.checkConsitency
+
+ return nil
+}
+
+// sanity
+var _ consistencyChecker = (*SystemUser)(nil)
+
+type shadow struct {
+ ID string
+ Rounds string
+ Salt string
+ Hash string
+}
+
+// crypt(3) compatible hashes have the forms:
+// - $id$salt$hash
+// - $id$rounds=N$salt$hash
+func parseShadowLine(line string) (*shadow, error) {
+ l := strings.SplitN(line, "$", 5)
+ if len(l) != 4 && len(l) != 5 {
+ return nil, fmt.Errorf(`hashed password must be of the form "$integer-id$salt$hash", see crypt(3)`)
+ }
+
+ // if rounds is the second field, the line must consist of 4
+ if strings.HasPrefix(l[2], "rounds=") && len(l) == 4 {
+ return nil, fmt.Errorf(`missing hash field`)
+ }
+
+ // shadow line without $rounds=N$
+ if len(l) == 4 {
+ return &shadow{
+ ID: l[1],
+ Salt: l[2],
+ Hash: l[3],
+ }, nil
+ }
+ // shadow line with rounds
+ return &shadow{
+ ID: l[1],
+ Rounds: l[2],
+ Salt: l[3],
+ Hash: l[4],
+ }, nil
+}
+
+// see crypt(3) for the legal chars
+var isValidSaltAndHash = regexp.MustCompile(`^[a-zA-Z0-9./]+$`).MatchString
+
+func checkHashedPassword(headers map[string]interface{}, name string) (string, error) {
+ pw, err := checkOptionalString(headers, name)
+ if err != nil {
+ return "", err
+ }
+ // the pw string is optional, so just return if its empty
+ if pw == "" {
+ return "", nil
+ }
+
+ // parse the shadow line
+ shd, err := parseShadowLine(pw)
+ if err != nil {
+ return "", fmt.Errorf(`%q header invalid: %s`, name, err)
+ }
+
+ // and verify it
+
+ // see crypt(3), ID 6 means SHA-512 (since glibc 2.7)
+ ID, err := strconv.Atoi(shd.ID)
+ if err != nil {
+ return "", fmt.Errorf(`%q header must start with "$integer-id$", got %q`, name, shd.ID)
+ }
+ // double check that we only allow modern hashes
+ if ID < 6 {
+ return "", fmt.Errorf("%q header only supports $id$ values of 6 (sha512crypt) or higher", name)
+ }
+
+ // the $rounds=N$ part is optional
+ if strings.HasPrefix(shd.Rounds, "rounds=") {
+ rounds, err := strconv.Atoi(strings.SplitN(shd.Rounds, "=", 2)[1])
+ if err != nil {
+ return "", fmt.Errorf("%q header has invalid number of rounds: %s", name, err)
+ }
+ if rounds < 5000 || rounds > 999999999 {
+ return "", fmt.Errorf("%q header rounds parameter out of bounds: %d", name, rounds)
+ }
+ }
+
+ if !isValidSaltAndHash(shd.Salt) {
+ return "", fmt.Errorf("%q header has invalid chars in salt %q", name, shd.Salt)
+ }
+ if !isValidSaltAndHash(shd.Hash) {
+ return "", fmt.Errorf("%q header has invalid chars in hash %q", name, shd.Hash)
+ }
+
+ return pw, nil
+}
+
+func assembleSystemUser(assert assertionBase) (Assertion, error) {
+ // brand-id here can be different from authority-id,
+ // the code using the assertion must use the policy set
+ // by the model assertion system-user-authority header
+ email, err := checkNotEmptyString(assert.headers, "email")
+ if err != nil {
+ return nil, err
+ }
+ if _, err := mail.ParseAddress(email); err != nil {
+ return nil, fmt.Errorf(`"email" header must be a RFC 5322 compliant email address: %s`, err)
+ }
+
+ series, err := checkStringList(assert.headers, "series")
+ if err != nil {
+ return nil, err
+ }
+ models, err := checkStringList(assert.headers, "models")
+ if err != nil {
+ return nil, err
+ }
+ if _, err := checkOptionalString(assert.headers, "name"); err != nil {
+ return nil, err
+ }
+ if _, err := checkStringMatches(assert.headers, "username", validSystemUserUsernames); err != nil {
+ return nil, err
+ }
+ password, err := checkHashedPassword(assert.headers, "password")
+ if err != nil {
+ return nil, err
+ }
+ forcePasswordChange, err := checkOptionalBool(assert.headers, "force-password-change")
+ if err != nil {
+ return nil, err
+ }
+ if forcePasswordChange && password == "" {
+ return nil, fmt.Errorf(`cannot use "force-password-change" with an empty "password"`)
+ }
+
+ sshKeys, err := checkStringList(assert.headers, "ssh-keys")
+ if err != nil {
+ return nil, err
+ }
+ since, err := checkRFC3339Date(assert.headers, "since")
+ if err != nil {
+ return nil, err
+ }
+ until, err := checkRFC3339Date(assert.headers, "until")
+ if err != nil {
+ return nil, err
+ }
+ if until.Before(since) {
+ return nil, fmt.Errorf("'until' time cannot be before 'since' time")
+ }
+
+ // "global" system-user assertion can only be valid for 1y
+ if len(models) == 0 && until.After(since.AddDate(1, 0, 0)) {
+ return nil, fmt.Errorf("'until' time cannot be more than 365 days in the future when no models are specified")
+ }
+
+ return &SystemUser{
+ assertionBase: assert,
+ series: series,
+ models: models,
+ sshKeys: sshKeys,
+ since: since,
+ until: until,
+ forcePasswordChange: forcePasswordChange,
+ }, nil
+}
diff -Nru snapd-2.41+19.10.1/asserts/system_user_test.go snapd-2.42.1+19.10/asserts/system_user_test.go
--- snapd-2.41+19.10.1/asserts/system_user_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/asserts/system_user_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,214 @@
+// -*- 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 asserts_test
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/snapcore/snapd/asserts"
+ . "gopkg.in/check.v1"
+)
+
+var (
+ _ = Suite(&systemUserSuite{})
+)
+
+type systemUserSuite struct {
+ until time.Time
+ untilLine string
+ since time.Time
+ sinceLine string
+
+ modelsLine string
+
+ systemUserStr string
+}
+
+const systemUserExample = "type: system-user\n" +
+ "authority-id: canonical\n" +
+ "brand-id: canonical\n" +
+ "email: foo@example.com\n" +
+ "series:\n" +
+ " - 16\n" +
+ "MODELSLINE\n" +
+ "name: Nice Guy\n" +
+ "username: guy\n" +
+ "password: $6$salt$hash\n" +
+ "ssh-keys:\n" +
+ " - ssh-rsa AAAABcdefg\n" +
+ "SINCELINE\n" +
+ "UNTILLINE\n" +
+ "body-length: 0\n" +
+ "sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
+ "\n\n" +
+ "AXNpZw=="
+
+func (s *systemUserSuite) SetUpTest(c *C) {
+ s.since = time.Now().Truncate(time.Second)
+ s.sinceLine = fmt.Sprintf("since: %s\n", s.since.Format(time.RFC3339))
+ s.until = time.Now().AddDate(0, 1, 0).Truncate(time.Second)
+ s.untilLine = fmt.Sprintf("until: %s\n", s.until.Format(time.RFC3339))
+ s.modelsLine = "models:\n - frobinator\n"
+ s.systemUserStr = strings.Replace(systemUserExample, "UNTILLINE\n", s.untilLine, 1)
+ s.systemUserStr = strings.Replace(s.systemUserStr, "SINCELINE\n", s.sinceLine, 1)
+ s.systemUserStr = strings.Replace(s.systemUserStr, "MODELSLINE\n", s.modelsLine, 1)
+}
+
+func (s *systemUserSuite) TestDecodeOK(c *C) {
+ a, err := asserts.Decode([]byte(s.systemUserStr))
+ c.Assert(err, IsNil)
+ c.Check(a.Type(), Equals, asserts.SystemUserType)
+ systemUser := a.(*asserts.SystemUser)
+ c.Check(systemUser.BrandID(), Equals, "canonical")
+ c.Check(systemUser.Email(), Equals, "foo@example.com")
+ c.Check(systemUser.Series(), DeepEquals, []string{"16"})
+ c.Check(systemUser.Models(), DeepEquals, []string{"frobinator"})
+ c.Check(systemUser.Name(), Equals, "Nice Guy")
+ c.Check(systemUser.Username(), Equals, "guy")
+ c.Check(systemUser.Password(), Equals, "$6$salt$hash")
+ c.Check(systemUser.SSHKeys(), DeepEquals, []string{"ssh-rsa AAAABcdefg"})
+ c.Check(systemUser.Since().Equal(s.since), Equals, true)
+ c.Check(systemUser.Until().Equal(s.until), Equals, true)
+}
+
+func (s *systemUserSuite) TestDecodePasswd(c *C) {
+ validTests := []struct{ original, valid string }{
+ {"password: $6$salt$hash\n", "password: $6$rounds=9999$salt$hash\n"},
+ {"password: $6$salt$hash\n", ""},
+ }
+ for _, test := range validTests {
+ valid := strings.Replace(s.systemUserStr, test.original, test.valid, 1)
+ _, err := asserts.Decode([]byte(valid))
+ c.Check(err, IsNil)
+ }
+}
+
+func (s *systemUserSuite) TestDecodeForcePasswdChange(c *C) {
+
+ old := "password: $6$salt$hash\n"
+ new := "password: $6$salt$hash\nforce-password-change: true\n"
+
+ valid := strings.Replace(s.systemUserStr, old, new, 1)
+ a, err := asserts.Decode([]byte(valid))
+ c.Check(err, IsNil)
+ systemUser := a.(*asserts.SystemUser)
+ c.Check(systemUser.ForcePasswordChange(), Equals, true)
+}
+
+func (s *systemUserSuite) TestValidAt(c *C) {
+ a, err := asserts.Decode([]byte(s.systemUserStr))
+ c.Assert(err, IsNil)
+ su := a.(*asserts.SystemUser)
+
+ c.Check(su.ValidAt(su.Since()), Equals, true)
+ c.Check(su.ValidAt(su.Since().AddDate(0, 0, -1)), Equals, false)
+ c.Check(su.ValidAt(su.Since().AddDate(0, 0, 1)), Equals, true)
+
+ c.Check(su.ValidAt(su.Until()), Equals, false)
+ c.Check(su.ValidAt(su.Until().AddDate(0, -1, 0)), Equals, true)
+ c.Check(su.ValidAt(su.Until().AddDate(0, 1, 0)), Equals, false)
+}
+
+func (s *systemUserSuite) TestValidAtRevoked(c *C) {
+ // With since == until, i.e. system-user has been revoked.
+ revoked := strings.Replace(s.systemUserStr, s.sinceLine, fmt.Sprintf("since: %s\n", s.until.Format(time.RFC3339)), 1)
+ a, err := asserts.Decode([]byte(revoked))
+ c.Assert(err, IsNil)
+ su := a.(*asserts.SystemUser)
+
+ c.Check(su.ValidAt(su.Since()), Equals, false)
+ c.Check(su.ValidAt(su.Since().AddDate(0, 0, -1)), Equals, false)
+ c.Check(su.ValidAt(su.Since().AddDate(0, 0, 1)), Equals, false)
+
+ c.Check(su.ValidAt(su.Until()), Equals, false)
+ c.Check(su.ValidAt(su.Until().AddDate(0, -1, 0)), Equals, false)
+ c.Check(su.ValidAt(su.Until().AddDate(0, 1, 0)), Equals, false)
+}
+
+const (
+ systemUserErrPrefix = "assertion system-user: "
+)
+
+func (s *systemUserSuite) TestDecodeInvalid(c *C) {
+ invalidTests := []struct{ original, invalid, expectedErr string }{
+ {"brand-id: canonical\n", "", `"brand-id" header is mandatory`},
+ {"brand-id: canonical\n", "brand-id: \n", `"brand-id" header should not be empty`},
+ {"email: foo@example.com\n", "", `"email" header is mandatory`},
+ {"email: foo@example.com\n", "email: \n", `"email" header should not be empty`},
+ {"email: foo@example.com\n", "email: \n", `"email" header must be a RFC 5322 compliant email address: mail: missing @ in addr-spec`},
+ {"email: foo@example.com\n", "email: no-mail\n", `"email" header must be a RFC 5322 compliant email address:.*`},
+ {"series:\n - 16\n", "series: \n", `"series" header must be a list of strings`},
+ {"series:\n - 16\n", "series: something\n", `"series" header must be a list of strings`},
+ {"models:\n - frobinator\n", "models: \n", `"models" header must be a list of strings`},
+ {"models:\n - frobinator\n", "models: something\n", `"models" header must be a list of strings`},
+ {"ssh-keys:\n - ssh-rsa AAAABcdefg\n", "ssh-keys: \n", `"ssh-keys" header must be a list of strings`},
+ {"ssh-keys:\n - ssh-rsa AAAABcdefg\n", "ssh-keys: something\n", `"ssh-keys" header must be a list of strings`},
+ {"name: Nice Guy\n", "name:\n - foo\n", `"name" header must be a string`},
+ {"username: guy\n", "username:\n - foo\n", `"username" header must be a string`},
+ {"username: guy\n", "username: bäää\n", `"username" header contains invalid characters: "bäää"`},
+ {"username: guy\n", "", `"username" header is mandatory`},
+ {"password: $6$salt$hash\n", "password:\n - foo\n", `"password" header must be a string`},
+ {"password: $6$salt$hash\n", "password: cleartext\n", `"password" header invalid: hashed password must be of the form "\$integer-id\$salt\$hash", see crypt\(3\)`},
+ {"password: $6$salt$hash\n", "password: $ni!$salt$hash\n", `"password" header must start with "\$integer-id\$", got "ni!"`},
+ {"password: $6$salt$hash\n", "password: $3$salt$hash\n", `"password" header only supports \$id\$ values of 6 \(sha512crypt\) or higher`},
+ {"password: $6$salt$hash\n", "password: $7$invalid-salt$hash\n", `"password" header has invalid chars in salt "invalid-salt"`},
+ {"password: $6$salt$hash\n", "password: $8$salt$invalid-hash\n", `"password" header has invalid chars in hash "invalid-hash"`},
+ {"password: $6$salt$hash\n", "password: $8$rounds=9999$hash\n", `"password" header invalid: missing hash field`},
+ {"password: $6$salt$hash\n", "password: $8$rounds=xxx$salt$hash\n", `"password" header has invalid number of rounds:.*`},
+ {"password: $6$salt$hash\n", "password: $8$rounds=1$salt$hash\n", `"password" header rounds parameter out of bounds: 1`},
+ {"password: $6$salt$hash\n", "password: $8$rounds=1999999999$salt$hash\n", `"password" header rounds parameter out of bounds: 1999999999`},
+ {"password: $6$salt$hash\n", "force-password-change: true\n", `cannot use "force-password-change" with an empty "password"`},
+ {"password: $6$salt$hash\n", "password: $6$salt$hash\nforce-password-change: xxx\n", `"force-password-change" header must be 'true' or 'false'`},
+ {s.sinceLine, "since: \n", `"since" header should not be empty`},
+ {s.sinceLine, "since: 12:30\n", `"since" header is not a RFC3339 date: .*`},
+ {s.untilLine, "until: \n", `"until" header should not be empty`},
+ {s.untilLine, "until: 12:30\n", `"until" header is not a RFC3339 date: .*`},
+ {s.untilLine, "until: 1002-11-01T22:08:41+00:00\n", `'until' time cannot be before 'since' time`},
+ }
+
+ for _, test := range invalidTests {
+ invalid := strings.Replace(s.systemUserStr, test.original, test.invalid, 1)
+ _, err := asserts.Decode([]byte(invalid))
+ c.Check(err, ErrorMatches, systemUserErrPrefix+test.expectedErr)
+ }
+}
+
+func (s *systemUserSuite) TestUntilNoModels(c *C) {
+ // no models is good for <1y
+ su := strings.Replace(s.systemUserStr, s.modelsLine, "", -1)
+ _, err := asserts.Decode([]byte(su))
+ c.Check(err, IsNil)
+
+ // but invalid for more than one year
+ oneYearPlusOne := time.Now().AddDate(1, 0, 1).Truncate(time.Second)
+ su = strings.Replace(su, s.untilLine, fmt.Sprintf("until: %s\n", oneYearPlusOne.Format(time.RFC3339)), -1)
+ _, err = asserts.Decode([]byte(su))
+ c.Check(err, ErrorMatches, systemUserErrPrefix+"'until' time cannot be more than 365 days in the future when no models are specified")
+}
+
+func (s *systemUserSuite) TestUntilWithModels(c *C) {
+ // with models it can be valid forever
+ oneYearPlusOne := time.Now().AddDate(10, 0, 1).Truncate(time.Second)
+ su := strings.Replace(s.systemUserStr, s.untilLine, fmt.Sprintf("until: %s\n", oneYearPlusOne.Format(time.RFC3339)), -1)
+ _, err := asserts.Decode([]byte(su))
+ c.Check(err, IsNil)
+}
diff -Nru snapd-2.41+19.10.1/asserts/user.go snapd-2.42.1+19.10/asserts/user.go
--- snapd-2.41+19.10.1/asserts/user.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/asserts/user.go 1970-01-01 00:00:00.000000000 +0000
@@ -1,281 +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 asserts
-
-import (
- "fmt"
- "net/mail"
- "regexp"
- "strconv"
- "strings"
- "time"
-)
-
-var validSystemUserUsernames = regexp.MustCompile(`^[a-z0-9][-a-z0-9+.-_]*$`)
-
-// SystemUser holds a system-user assertion which allows creating local
-// system users.
-type SystemUser struct {
- assertionBase
- series []string
- models []string
- sshKeys []string
- since time.Time
- until time.Time
-
- forcePasswordChange bool
-}
-
-// BrandID returns the brand identifier that signed this assertion.
-func (su *SystemUser) BrandID() string {
- return su.HeaderString("brand-id")
-}
-
-// Email returns the email address that this assertion is valid for.
-func (su *SystemUser) Email() string {
- return su.HeaderString("email")
-}
-
-// Series returns the series that this assertion is valid for.
-func (su *SystemUser) Series() []string {
- return su.series
-}
-
-// Models returns the models that this assertion is valid for.
-func (su *SystemUser) Models() []string {
- return su.models
-}
-
-// Name returns the full name of the user (e.g. Random Guy).
-func (su *SystemUser) Name() string {
- return su.HeaderString("name")
-}
-
-// Username returns the system user name that should be created (e.g. "foo").
-func (su *SystemUser) Username() string {
- return su.HeaderString("username")
-}
-
-// Password returns the crypt(3) compatible password for the user.
-// Note that only ID: $6$ or stronger is supported (sha512crypt).
-func (su *SystemUser) Password() string {
- return su.HeaderString("password")
-}
-
-// ForcePasswordChange returns true if the user needs to change the password
-// after the first login.
-func (su *SystemUser) ForcePasswordChange() bool {
- return su.forcePasswordChange
-}
-
-// SSHKeys returns the ssh keys for the user.
-func (su *SystemUser) SSHKeys() []string {
- return su.sshKeys
-}
-
-// Since returns the time since the assertion is valid.
-func (su *SystemUser) Since() time.Time {
- return su.since
-}
-
-// Until returns the time until the assertion is valid.
-func (su *SystemUser) Until() time.Time {
- return su.until
-}
-
-// ValidAt returns whether the system-user is valid at 'when' time.
-func (su *SystemUser) ValidAt(when time.Time) bool {
- valid := when.After(su.since) || when.Equal(su.since)
- if valid {
- valid = when.Before(su.until)
- }
- return valid
-}
-
-// Implement further consistency checks.
-func (su *SystemUser) checkConsistency(db RODatabase, acck *AccountKey) error {
- // Do the cross-checks when this assertion is actually used,
- // i.e. in the create-user code. See also Model.checkConsitency
-
- return nil
-}
-
-// sanity
-var _ consistencyChecker = (*SystemUser)(nil)
-
-type shadow struct {
- ID string
- Rounds string
- Salt string
- Hash string
-}
-
-// crypt(3) compatible hashes have the forms:
-// - $id$salt$hash
-// - $id$rounds=N$salt$hash
-func parseShadowLine(line string) (*shadow, error) {
- l := strings.SplitN(line, "$", 5)
- if len(l) != 4 && len(l) != 5 {
- return nil, fmt.Errorf(`hashed password must be of the form "$integer-id$salt$hash", see crypt(3)`)
- }
-
- // if rounds is the second field, the line must consist of 4
- if strings.HasPrefix(l[2], "rounds=") && len(l) == 4 {
- return nil, fmt.Errorf(`missing hash field`)
- }
-
- // shadow line without $rounds=N$
- if len(l) == 4 {
- return &shadow{
- ID: l[1],
- Salt: l[2],
- Hash: l[3],
- }, nil
- }
- // shadow line with rounds
- return &shadow{
- ID: l[1],
- Rounds: l[2],
- Salt: l[3],
- Hash: l[4],
- }, nil
-}
-
-// see crypt(3) for the legal chars
-var isValidSaltAndHash = regexp.MustCompile(`^[a-zA-Z0-9./]+$`).MatchString
-
-func checkHashedPassword(headers map[string]interface{}, name string) (string, error) {
- pw, err := checkOptionalString(headers, name)
- if err != nil {
- return "", err
- }
- // the pw string is optional, so just return if its empty
- if pw == "" {
- return "", nil
- }
-
- // parse the shadow line
- shd, err := parseShadowLine(pw)
- if err != nil {
- return "", fmt.Errorf(`%q header invalid: %s`, name, err)
- }
-
- // and verify it
-
- // see crypt(3), ID 6 means SHA-512 (since glibc 2.7)
- ID, err := strconv.Atoi(shd.ID)
- if err != nil {
- return "", fmt.Errorf(`%q header must start with "$integer-id$", got %q`, name, shd.ID)
- }
- // double check that we only allow modern hashes
- if ID < 6 {
- return "", fmt.Errorf("%q header only supports $id$ values of 6 (sha512crypt) or higher", name)
- }
-
- // the $rounds=N$ part is optional
- if strings.HasPrefix(shd.Rounds, "rounds=") {
- rounds, err := strconv.Atoi(strings.SplitN(shd.Rounds, "=", 2)[1])
- if err != nil {
- return "", fmt.Errorf("%q header has invalid number of rounds: %s", name, err)
- }
- if rounds < 5000 || rounds > 999999999 {
- return "", fmt.Errorf("%q header rounds parameter out of bounds: %d", name, rounds)
- }
- }
-
- if !isValidSaltAndHash(shd.Salt) {
- return "", fmt.Errorf("%q header has invalid chars in salt %q", name, shd.Salt)
- }
- if !isValidSaltAndHash(shd.Hash) {
- return "", fmt.Errorf("%q header has invalid chars in hash %q", name, shd.Hash)
- }
-
- return pw, nil
-}
-
-func assembleSystemUser(assert assertionBase) (Assertion, error) {
- // brand-id here can be different from authority-id,
- // the code using the assertion must use the policy set
- // by the model assertion system-user-authority header
- email, err := checkNotEmptyString(assert.headers, "email")
- if err != nil {
- return nil, err
- }
- if _, err := mail.ParseAddress(email); err != nil {
- return nil, fmt.Errorf(`"email" header must be a RFC 5322 compliant email address: %s`, err)
- }
-
- series, err := checkStringList(assert.headers, "series")
- if err != nil {
- return nil, err
- }
- models, err := checkStringList(assert.headers, "models")
- if err != nil {
- return nil, err
- }
- if _, err := checkOptionalString(assert.headers, "name"); err != nil {
- return nil, err
- }
- if _, err := checkStringMatches(assert.headers, "username", validSystemUserUsernames); err != nil {
- return nil, err
- }
- password, err := checkHashedPassword(assert.headers, "password")
- if err != nil {
- return nil, err
- }
- forcePasswordChange, err := checkOptionalBool(assert.headers, "force-password-change")
- if err != nil {
- return nil, err
- }
- if forcePasswordChange && password == "" {
- return nil, fmt.Errorf(`cannot use "force-password-change" with an empty "password"`)
- }
-
- sshKeys, err := checkStringList(assert.headers, "ssh-keys")
- if err != nil {
- return nil, err
- }
- since, err := checkRFC3339Date(assert.headers, "since")
- if err != nil {
- return nil, err
- }
- until, err := checkRFC3339Date(assert.headers, "until")
- if err != nil {
- return nil, err
- }
- if until.Before(since) {
- return nil, fmt.Errorf("'until' time cannot be before 'since' time")
- }
-
- // "global" system-user assertion can only be valid for 1y
- if len(models) == 0 && until.After(since.AddDate(1, 0, 0)) {
- return nil, fmt.Errorf("'until' time cannot be more than 365 days in the future when no models are specified")
- }
-
- return &SystemUser{
- assertionBase: assert,
- series: series,
- models: models,
- sshKeys: sshKeys,
- since: since,
- until: until,
- forcePasswordChange: forcePasswordChange,
- }, nil
-}
diff -Nru snapd-2.41+19.10.1/asserts/user_test.go snapd-2.42.1+19.10/asserts/user_test.go
--- snapd-2.41+19.10.1/asserts/user_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/asserts/user_test.go 1970-01-01 00:00:00.000000000 +0000
@@ -1,214 +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 asserts_test
-
-import (
- "fmt"
- "strings"
- "time"
-
- "github.com/snapcore/snapd/asserts"
- . "gopkg.in/check.v1"
-)
-
-var (
- _ = Suite(&systemUserSuite{})
-)
-
-type systemUserSuite struct {
- until time.Time
- untilLine string
- since time.Time
- sinceLine string
-
- modelsLine string
-
- systemUserStr string
-}
-
-const systemUserExample = "type: system-user\n" +
- "authority-id: canonical\n" +
- "brand-id: canonical\n" +
- "email: foo@example.com\n" +
- "series:\n" +
- " - 16\n" +
- "MODELSLINE\n" +
- "name: Nice Guy\n" +
- "username: guy\n" +
- "password: $6$salt$hash\n" +
- "ssh-keys:\n" +
- " - ssh-rsa AAAABcdefg\n" +
- "SINCELINE\n" +
- "UNTILLINE\n" +
- "body-length: 0\n" +
- "sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
- "\n\n" +
- "AXNpZw=="
-
-func (s *systemUserSuite) SetUpTest(c *C) {
- s.since = time.Now().Truncate(time.Second)
- s.sinceLine = fmt.Sprintf("since: %s\n", s.since.Format(time.RFC3339))
- s.until = time.Now().AddDate(0, 1, 0).Truncate(time.Second)
- s.untilLine = fmt.Sprintf("until: %s\n", s.until.Format(time.RFC3339))
- s.modelsLine = "models:\n - frobinator\n"
- s.systemUserStr = strings.Replace(systemUserExample, "UNTILLINE\n", s.untilLine, 1)
- s.systemUserStr = strings.Replace(s.systemUserStr, "SINCELINE\n", s.sinceLine, 1)
- s.systemUserStr = strings.Replace(s.systemUserStr, "MODELSLINE\n", s.modelsLine, 1)
-}
-
-func (s *systemUserSuite) TestDecodeOK(c *C) {
- a, err := asserts.Decode([]byte(s.systemUserStr))
- c.Assert(err, IsNil)
- c.Check(a.Type(), Equals, asserts.SystemUserType)
- systemUser := a.(*asserts.SystemUser)
- c.Check(systemUser.BrandID(), Equals, "canonical")
- c.Check(systemUser.Email(), Equals, "foo@example.com")
- c.Check(systemUser.Series(), DeepEquals, []string{"16"})
- c.Check(systemUser.Models(), DeepEquals, []string{"frobinator"})
- c.Check(systemUser.Name(), Equals, "Nice Guy")
- c.Check(systemUser.Username(), Equals, "guy")
- c.Check(systemUser.Password(), Equals, "$6$salt$hash")
- c.Check(systemUser.SSHKeys(), DeepEquals, []string{"ssh-rsa AAAABcdefg"})
- c.Check(systemUser.Since().Equal(s.since), Equals, true)
- c.Check(systemUser.Until().Equal(s.until), Equals, true)
-}
-
-func (s *systemUserSuite) TestDecodePasswd(c *C) {
- validTests := []struct{ original, valid string }{
- {"password: $6$salt$hash\n", "password: $6$rounds=9999$salt$hash\n"},
- {"password: $6$salt$hash\n", ""},
- }
- for _, test := range validTests {
- valid := strings.Replace(s.systemUserStr, test.original, test.valid, 1)
- _, err := asserts.Decode([]byte(valid))
- c.Check(err, IsNil)
- }
-}
-
-func (s *systemUserSuite) TestDecodeForcePasswdChange(c *C) {
-
- old := "password: $6$salt$hash\n"
- new := "password: $6$salt$hash\nforce-password-change: true\n"
-
- valid := strings.Replace(s.systemUserStr, old, new, 1)
- a, err := asserts.Decode([]byte(valid))
- c.Check(err, IsNil)
- systemUser := a.(*asserts.SystemUser)
- c.Check(systemUser.ForcePasswordChange(), Equals, true)
-}
-
-func (s *systemUserSuite) TestValidAt(c *C) {
- a, err := asserts.Decode([]byte(s.systemUserStr))
- c.Assert(err, IsNil)
- su := a.(*asserts.SystemUser)
-
- c.Check(su.ValidAt(su.Since()), Equals, true)
- c.Check(su.ValidAt(su.Since().AddDate(0, 0, -1)), Equals, false)
- c.Check(su.ValidAt(su.Since().AddDate(0, 0, 1)), Equals, true)
-
- c.Check(su.ValidAt(su.Until()), Equals, false)
- c.Check(su.ValidAt(su.Until().AddDate(0, -1, 0)), Equals, true)
- c.Check(su.ValidAt(su.Until().AddDate(0, 1, 0)), Equals, false)
-}
-
-func (s *systemUserSuite) TestValidAtRevoked(c *C) {
- // With since == until, i.e. system-user has been revoked.
- revoked := strings.Replace(s.systemUserStr, s.sinceLine, fmt.Sprintf("since: %s\n", s.until.Format(time.RFC3339)), 1)
- a, err := asserts.Decode([]byte(revoked))
- c.Assert(err, IsNil)
- su := a.(*asserts.SystemUser)
-
- c.Check(su.ValidAt(su.Since()), Equals, false)
- c.Check(su.ValidAt(su.Since().AddDate(0, 0, -1)), Equals, false)
- c.Check(su.ValidAt(su.Since().AddDate(0, 0, 1)), Equals, false)
-
- c.Check(su.ValidAt(su.Until()), Equals, false)
- c.Check(su.ValidAt(su.Until().AddDate(0, -1, 0)), Equals, false)
- c.Check(su.ValidAt(su.Until().AddDate(0, 1, 0)), Equals, false)
-}
-
-const (
- systemUserErrPrefix = "assertion system-user: "
-)
-
-func (s *systemUserSuite) TestDecodeInvalid(c *C) {
- invalidTests := []struct{ original, invalid, expectedErr string }{
- {"brand-id: canonical\n", "", `"brand-id" header is mandatory`},
- {"brand-id: canonical\n", "brand-id: \n", `"brand-id" header should not be empty`},
- {"email: foo@example.com\n", "", `"email" header is mandatory`},
- {"email: foo@example.com\n", "email: \n", `"email" header should not be empty`},
- {"email: foo@example.com\n", "email: \n", `"email" header must be a RFC 5322 compliant email address: mail: missing @ in addr-spec`},
- {"email: foo@example.com\n", "email: no-mail\n", `"email" header must be a RFC 5322 compliant email address:.*`},
- {"series:\n - 16\n", "series: \n", `"series" header must be a list of strings`},
- {"series:\n - 16\n", "series: something\n", `"series" header must be a list of strings`},
- {"models:\n - frobinator\n", "models: \n", `"models" header must be a list of strings`},
- {"models:\n - frobinator\n", "models: something\n", `"models" header must be a list of strings`},
- {"ssh-keys:\n - ssh-rsa AAAABcdefg\n", "ssh-keys: \n", `"ssh-keys" header must be a list of strings`},
- {"ssh-keys:\n - ssh-rsa AAAABcdefg\n", "ssh-keys: something\n", `"ssh-keys" header must be a list of strings`},
- {"name: Nice Guy\n", "name:\n - foo\n", `"name" header must be a string`},
- {"username: guy\n", "username:\n - foo\n", `"username" header must be a string`},
- {"username: guy\n", "username: bäää\n", `"username" header contains invalid characters: "bäää"`},
- {"username: guy\n", "", `"username" header is mandatory`},
- {"password: $6$salt$hash\n", "password:\n - foo\n", `"password" header must be a string`},
- {"password: $6$salt$hash\n", "password: cleartext\n", `"password" header invalid: hashed password must be of the form "\$integer-id\$salt\$hash", see crypt\(3\)`},
- {"password: $6$salt$hash\n", "password: $ni!$salt$hash\n", `"password" header must start with "\$integer-id\$", got "ni!"`},
- {"password: $6$salt$hash\n", "password: $3$salt$hash\n", `"password" header only supports \$id\$ values of 6 \(sha512crypt\) or higher`},
- {"password: $6$salt$hash\n", "password: $7$invalid-salt$hash\n", `"password" header has invalid chars in salt "invalid-salt"`},
- {"password: $6$salt$hash\n", "password: $8$salt$invalid-hash\n", `"password" header has invalid chars in hash "invalid-hash"`},
- {"password: $6$salt$hash\n", "password: $8$rounds=9999$hash\n", `"password" header invalid: missing hash field`},
- {"password: $6$salt$hash\n", "password: $8$rounds=xxx$salt$hash\n", `"password" header has invalid number of rounds:.*`},
- {"password: $6$salt$hash\n", "password: $8$rounds=1$salt$hash\n", `"password" header rounds parameter out of bounds: 1`},
- {"password: $6$salt$hash\n", "password: $8$rounds=1999999999$salt$hash\n", `"password" header rounds parameter out of bounds: 1999999999`},
- {"password: $6$salt$hash\n", "force-password-change: true\n", `cannot use "force-password-change" with an empty "password"`},
- {"password: $6$salt$hash\n", "password: $6$salt$hash\nforce-password-change: xxx\n", `"force-password-change" header must be 'true' or 'false'`},
- {s.sinceLine, "since: \n", `"since" header should not be empty`},
- {s.sinceLine, "since: 12:30\n", `"since" header is not a RFC3339 date: .*`},
- {s.untilLine, "until: \n", `"until" header should not be empty`},
- {s.untilLine, "until: 12:30\n", `"until" header is not a RFC3339 date: .*`},
- {s.untilLine, "until: 1002-11-01T22:08:41+00:00\n", `'until' time cannot be before 'since' time`},
- }
-
- for _, test := range invalidTests {
- invalid := strings.Replace(s.systemUserStr, test.original, test.invalid, 1)
- _, err := asserts.Decode([]byte(invalid))
- c.Check(err, ErrorMatches, systemUserErrPrefix+test.expectedErr)
- }
-}
-
-func (s *systemUserSuite) TestUntilNoModels(c *C) {
- // no models is good for <1y
- su := strings.Replace(s.systemUserStr, s.modelsLine, "", -1)
- _, err := asserts.Decode([]byte(su))
- c.Check(err, IsNil)
-
- // but invalid for more than one year
- oneYearPlusOne := time.Now().AddDate(1, 0, 1).Truncate(time.Second)
- su = strings.Replace(su, s.untilLine, fmt.Sprintf("until: %s\n", oneYearPlusOne.Format(time.RFC3339)), -1)
- _, err = asserts.Decode([]byte(su))
- c.Check(err, ErrorMatches, systemUserErrPrefix+"'until' time cannot be more than 365 days in the future when no models are specified")
-}
-
-func (s *systemUserSuite) TestUntilWithModels(c *C) {
- // with models it can be valid forever
- oneYearPlusOne := time.Now().AddDate(10, 0, 1).Truncate(time.Second)
- su := strings.Replace(s.systemUserStr, s.untilLine, fmt.Sprintf("until: %s\n", oneYearPlusOne.Format(time.RFC3339)), -1)
- _, err := asserts.Decode([]byte(su))
- c.Check(err, IsNil)
-}
diff -Nru snapd-2.41+19.10.1/boot/boot.go snapd-2.42.1+19.10/boot/boot.go
--- snapd-2.41+19.10.1/boot/boot.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/boot/boot.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,367 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package boot
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/bootloader"
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/logger"
+ "github.com/snapcore/snapd/snap"
+)
+
+// A BootParticipant handles the boot process details for a snap involved in it.
+type BootParticipant interface {
+ // SetNextBoot will schedule the snap to be used in the next boot. For
+ // base snaps it is up to the caller to select the right bootable base
+ // (from the model assertion).
+ SetNextBoot() error
+ // ChangeRequiresReboot returns whether a reboot is required to switch
+ // to the snap.
+ ChangeRequiresReboot() bool
+ // Is this a trivial implementation of the interface?
+ IsTrivial() bool
+}
+
+// A BootKernel handles the bootloader setup of a kernel.
+type BootKernel interface {
+ // RemoveKernelAssets removes the unpacked kernel/initrd for the given
+ // kernel snap.
+ RemoveKernelAssets() error
+ // ExtractKernelAssets extracts kernel/initrd/dtb data from the given
+ // kernel snap, if required, to a versioned bootloader directory so
+ // that the bootloader can use it.
+ ExtractKernelAssets(snap.Container) error
+ // Is this a trivial implementation of the interface?
+ IsTrivial() bool
+}
+
+type trivial struct{}
+
+func (trivial) SetNextBoot() error { return nil }
+func (trivial) ChangeRequiresReboot() bool { return false }
+func (trivial) IsTrivial() bool { return true }
+func (trivial) RemoveKernelAssets() error { return nil }
+func (trivial) ExtractKernelAssets(snap.Container) error { return nil }
+
+// ensure trivial is a BootParticipant
+var _ BootParticipant = trivial{}
+
+// ensure trivial is a Kernel
+var _ BootKernel = trivial{}
+
+// Model carries information about the model that is relevant to boot.
+// Note *asserts.Model implements this, and that's the expected use case.
+type Model interface {
+ Kernel() string
+ Base() string
+ Classic() bool
+}
+
+// Participant figures out what the BootParticipant is for the given
+// arguments, and returns it. If the snap does _not_ participate in
+// the boot process, the returned object will be a NOP, so it's safe
+// to call anything on it always.
+//
+// Currently, on classic, nothing is a boot participant (returned will
+// always be NOP).
+func Participant(s snap.PlaceInfo, t snap.Type, model Model, onClassic bool) BootParticipant {
+ if applicable(s, t, model, onClassic) {
+ return &coreBootParticipant{s: s, t: t}
+ }
+ return trivial{}
+}
+
+// Kernel checks that the given arguments refer to a kernel snap
+// that participates in the boot process, and returns the associated
+// BootKernel, or a trivial implementation otherwise.
+func Kernel(s snap.PlaceInfo, t snap.Type, model Model, onClassic bool) BootKernel {
+ if t == snap.TypeKernel && applicable(s, t, model, onClassic) {
+ return &coreKernel{s: s}
+ }
+ return trivial{}
+}
+
+func applicable(s snap.PlaceInfo, t snap.Type, model Model, onClassic bool) bool {
+ if onClassic {
+ return false
+ }
+ if t != snap.TypeOS && t != snap.TypeKernel && t != snap.TypeBase {
+ // note we don't currently have anything useful to do with gadgets
+ return false
+ }
+
+ if model != nil {
+ switch t {
+ case snap.TypeKernel:
+ if s.InstanceName() != model.Kernel() {
+ // a remodel might leave you in this state
+ return false
+ }
+ case snap.TypeBase, snap.TypeOS:
+ base := model.Base()
+ if base == "" {
+ base = "core"
+ }
+ if s.InstanceName() != base {
+ return false
+ }
+ }
+ }
+
+ return true
+}
+
+// InUse checks if the given name/revision is used in the
+// boot environment
+func InUse(name string, rev snap.Revision) bool {
+ bootloader, err := bootloader.Find("", nil)
+ if err != nil {
+ logger.Noticef("cannot get boot settings: %s", err)
+ return false
+ }
+
+ bootVars, err := bootloader.GetBootVars("snap_kernel", "snap_try_kernel", "snap_core", "snap_try_core")
+ if err != nil {
+ logger.Noticef("cannot get boot vars: %s", err)
+ return false
+ }
+
+ snapFile := filepath.Base(snap.MountFile(name, rev))
+ for _, bootVar := range bootVars {
+ if bootVar == snapFile {
+ return true
+ }
+ }
+
+ return false
+}
+
+var (
+ ErrBootNameAndRevisionNotReady = errors.New("boot revision not yet established")
+)
+
+type NameAndRevision struct {
+ Name string
+ Revision snap.Revision
+}
+
+// GetCurrentBoot returns the currently set name and revision for boot for the given
+// type of snap, which can be snap.TypeBase (or snap.TypeOS), or snap.TypeKernel.
+// Returns ErrBootNameAndRevisionNotReady if the values are temporarily not established.
+func GetCurrentBoot(t snap.Type) (*NameAndRevision, error) {
+ var bootVar, errName string
+ switch t {
+ case snap.TypeKernel:
+ bootVar = "snap_kernel"
+ errName = "kernel"
+ case snap.TypeOS, snap.TypeBase:
+ bootVar = "snap_core"
+ errName = "base"
+ default:
+ return nil, fmt.Errorf("internal error: cannot find boot revision for snap type %q", t)
+ }
+
+ bloader, err := bootloader.Find("", nil)
+ if err != nil {
+ return nil, fmt.Errorf("cannot get boot settings: %s", err)
+ }
+
+ m, err := bloader.GetBootVars(bootVar, "snap_mode")
+ if err != nil {
+ return nil, fmt.Errorf("cannot get boot variables: %s", err)
+ }
+
+ if m["snap_mode"] == "trying" {
+ return nil, ErrBootNameAndRevisionNotReady
+ }
+
+ nameAndRevno, err := nameAndRevnoFromSnap(m[bootVar])
+ if err != nil {
+ return nil, fmt.Errorf("cannot get name and revision of boot %s: %v", errName, err)
+ }
+
+ return nameAndRevno, nil
+}
+
+// nameAndRevnoFromSnap grabs the snap name and revision from the
+// value of a boot variable. E.g., foo_2.snap -> name "foo", revno 2
+func nameAndRevnoFromSnap(sn string) (*NameAndRevision, error) {
+ if sn == "" {
+ return nil, fmt.Errorf("boot variable unset")
+ }
+ idx := strings.IndexByte(sn, '_')
+ if idx < 1 {
+ return nil, fmt.Errorf("input %q has invalid format (not enough '_')", sn)
+ }
+ name := sn[:idx]
+ revnoNSuffix := sn[idx+1:]
+ rev, err := snap.ParseRevision(strings.TrimSuffix(revnoNSuffix, ".snap"))
+ if err != nil {
+ return nil, err
+ }
+ return &NameAndRevision{Name: name, Revision: rev}, nil
+}
+
+// MarkBootSuccessful marks the current boot as successful. This means
+// that snappy will consider this combination of kernel/os a valid
+// target for rollback.
+//
+// The states that a boot goes through are the following:
+// - By default snap_mode is "" in which case the bootloader loads
+// two squashfs'es denoted by variables snap_core and snap_kernel.
+// - On a refresh of core/kernel snapd will set snap_mode=try and
+// will also set snap_try_{core,kernel} to the core/kernel that
+// will be tried next.
+// - On reboot the bootloader will inspect the snap_mode and if the
+// mode is set to "try" it will set "snap_mode=trying" and then
+// try to boot the snap_try_{core,kernel}".
+// - On a successful boot snapd resets snap_mode to "" and copies
+// snap_try_{core,kernel} to snap_{core,kernel}. The snap_try_*
+// values are cleared afterwards.
+// - On a failing boot the bootloader will see snap_mode=trying which
+// means snapd did not start successfully. In this case the bootloader
+// will set snap_mode="" and the system will boot with the known good
+// values from snap_{core,kernel}
+func MarkBootSuccessful() error {
+ bl, err := bootloader.Find("", nil)
+ if err != nil {
+ return fmt.Errorf("cannot mark boot successful: %s", err)
+ }
+ m, err := bl.GetBootVars("snap_mode", "snap_try_core", "snap_try_kernel")
+ if err != nil {
+ return err
+ }
+
+ // snap_mode goes from "" -> "try" -> "trying" -> ""
+ // so if we are not in "trying" mode, nothing to do here
+ if m["snap_mode"] != "trying" {
+ return nil
+ }
+
+ // update the boot vars
+ for _, k := range []string{"kernel", "core"} {
+ tryBootVar := fmt.Sprintf("snap_try_%s", k)
+ bootVar := fmt.Sprintf("snap_%s", k)
+ // update the boot vars
+ if m[tryBootVar] != "" {
+ m[bootVar] = m[tryBootVar]
+ m[tryBootVar] = ""
+ }
+ }
+ m["snap_mode"] = ""
+
+ return bl.SetBootVars(m)
+}
+
+// MakeBootable sets up the image filesystem with the given rootdir
+// such that it can be booted. bootWith is a map from the paths in the
+// image seed for kernel and boot base to their snap info. This
+// entails:
+// - installing the bootloader configuration from the gadget
+// - creating symlinks for boot snaps from seed to the runtime blob dir
+// - setting boot env vars pointing to the revisions of the boot snaps to use
+// - extracting kernel assets as needed by the bootloader
+func MakeBootable(model *asserts.Model, rootdir string, bootWith map[string]*snap.Info, unpackedGadgetDir string) error {
+ if len(bootWith) != 2 {
+ return fmt.Errorf("internal error: MakeBootable can only be called with exactly one kernel and exactly one core/base boot info: %v", bootWith)
+ }
+
+ // install the bootloader configuration from the gadget
+ if err := bootloader.InstallBootConfig(unpackedGadgetDir, rootdir); err != nil {
+ return err
+ }
+
+ // setup symlinks for kernel and boot base from the blob directory
+ // to the seed snaps
+
+ snapBlobDir := dirs.SnapBlobDirUnder(rootdir)
+ if err := os.MkdirAll(snapBlobDir, 0755); err != nil {
+ return err
+ }
+
+ for fn := range bootWith {
+ dst := filepath.Join(snapBlobDir, filepath.Base(fn))
+ // construct a relative symlink from the blob dir
+ // to the seed snap file
+ relSymlink, err := filepath.Rel(snapBlobDir, fn)
+ if err != nil {
+ return fmt.Errorf("cannot build symlink for boot snap: %v", err)
+ }
+ if err := os.Symlink(relSymlink, dst); err != nil {
+ return err
+ }
+ }
+
+ // Set bootvars for kernel/core snaps so the system boots and
+ // does the first-time initialization. There is also no
+ // mounted kernel/core/base snap, but just the blobs.
+ bl, err := bootloader.Find(rootdir, &bootloader.Options{
+ PrepareImageTime: true,
+ })
+ if err != nil {
+ return fmt.Errorf("cannot set kernel/core boot variables: %s", err)
+ }
+
+ m := map[string]string{
+ "snap_mode": "",
+ "snap_try_core": "",
+ "snap_try_kernel": "",
+ }
+ if model.DisplayName() != "" {
+ m["snap_menuentry"] = model.DisplayName()
+ }
+
+ for fn, info := range bootWith {
+ bootvar := ""
+
+ switch info.GetType() {
+ case snap.TypeOS, snap.TypeBase:
+ bootvar = "snap_core"
+ case snap.TypeKernel:
+ snapf, err := snap.Open(fn)
+ if err != nil {
+ return err
+ }
+
+ if err := bl.ExtractKernelAssets(info, snapf); err != nil {
+ return err
+ }
+ bootvar = "snap_kernel"
+ }
+
+ if bootvar != "" {
+ name := filepath.Base(fn)
+ m[bootvar] = name
+ }
+ }
+ if err := bl.SetBootVars(m); err != nil {
+ return err
+ }
+
+ return nil
+
+}
diff -Nru snapd-2.41+19.10.1/boot/boottest/mockbootloader.go snapd-2.42.1+19.10/boot/boottest/mockbootloader.go
--- snapd-2.41+19.10.1/boot/boottest/mockbootloader.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/boot/boottest/mockbootloader.go 1970-01-01 00:00:00.000000000 +0000
@@ -1,96 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2014-2016 Canonical Ltd
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-
-package boottest
-
-import (
- "path/filepath"
-
- "github.com/snapcore/snapd/bootloader"
- "github.com/snapcore/snapd/snap"
-)
-
-// SetBootKernel sets the current boot kernel string. Should be
-// something like "pc-kernel_1234.snap".
-func SetBootKernel(kernel string, b bootloader.Bootloader) {
- b.SetBootVars(map[string]string{"snap_kernel": kernel})
-}
-
-// SetBootBase sets the current boot base string. Should be something
-// like "core_1234.snap".
-func SetBootBase(base string, b bootloader.Bootloader) {
- b.SetBootVars(map[string]string{"snap_core": base})
-}
-
-// MockBootloader mocks the bootloader interface and records all
-// set/get calls.
-type MockBootloader struct {
- BootVars map[string]string
- SetErr error
- GetErr error
-
- name string
- bootdir string
-
- ExtractKernelAssetsCalls []*snap.Info
- RemoveKernelAssetsCalls []snap.PlaceInfo
-}
-
-func NewMockBootloader(name, bootdir string) *MockBootloader {
- return &MockBootloader{
- name: name,
- bootdir: bootdir,
-
- BootVars: make(map[string]string),
- }
-}
-
-func (b *MockBootloader) SetBootVars(values map[string]string) error {
- for k, v := range values {
- b.BootVars[k] = v
- }
- return b.SetErr
-}
-
-func (b *MockBootloader) GetBootVars(keys ...string) (map[string]string, error) {
- out := map[string]string{}
- for _, k := range keys {
- out[k] = b.BootVars[k]
- }
-
- return out, b.GetErr
-}
-
-func (b *MockBootloader) Name() string {
- return b.name
-}
-
-func (b *MockBootloader) ConfigFile() string {
- return filepath.Join(b.bootdir, "mockboot/mockboot.cfg")
-}
-
-func (b *MockBootloader) ExtractKernelAssets(s *snap.Info, snapf snap.Container) error {
- b.ExtractKernelAssetsCalls = append(b.ExtractKernelAssetsCalls, s)
- return nil
-}
-
-func (b *MockBootloader) RemoveKernelAssets(s snap.PlaceInfo) error {
- b.RemoveKernelAssetsCalls = append(b.RemoveKernelAssetsCalls, s)
- return nil
-}
diff -Nru snapd-2.41+19.10.1/boot/boot_test.go snapd-2.42.1+19.10/boot/boot_test.go
--- snapd-2.41+19.10.1/boot/boot_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/boot/boot_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,456 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2014-2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package boot_test
+
+import (
+ "errors"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "testing"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/asserts/assertstest"
+ "github.com/snapcore/snapd/boot"
+ "github.com/snapcore/snapd/bootloader"
+ "github.com/snapcore/snapd/bootloader/bootloadertest"
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/logger"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/snaptest"
+ "github.com/snapcore/snapd/testutil"
+)
+
+// set up gocheck
+func TestBoot(t *testing.T) { TestingT(t) }
+
+// baseBootSuite is used to setup the common test environment
+type baseBootSetSuite struct {
+ testutil.BaseTest
+
+ bootdir string
+}
+
+func (s *baseBootSetSuite) SetUpTest(c *C) {
+ s.BaseTest.SetUpTest(c)
+
+ dirs.SetRootDir(c.MkDir())
+ s.AddCleanup(func() { dirs.SetRootDir("") })
+ restore := snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})
+ s.AddCleanup(restore)
+
+ s.bootdir = filepath.Join(dirs.GlobalRootDir, "boot")
+}
+
+// bootSetSuite tests the abstract BootSet interface, and tools that
+// don't depend on a specific BootSet implementation
+type bootSetSuite struct {
+ baseBootSetSuite
+
+ bootloader *bootloadertest.MockBootloader
+}
+
+var _ = Suite(&bootSetSuite{})
+
+func (s *bootSetSuite) SetUpTest(c *C) {
+ s.baseBootSetSuite.SetUpTest(c)
+
+ s.bootloader = bootloadertest.Mock("mock", c.MkDir())
+ bootloader.Force(s.bootloader)
+ s.AddCleanup(func() { bootloader.Force(nil) })
+}
+
+func (s *bootSetSuite) TestNameAndRevnoFromSnapValid(c *C) {
+ info, err := boot.NameAndRevnoFromSnap("foo_2.snap")
+ c.Assert(err, IsNil)
+ c.Assert(info.Name, Equals, "foo")
+ c.Assert(info.Revision, Equals, snap.R(2))
+}
+
+func (s *bootSetSuite) TestNameAndRevnoFromSnapInvalidFormat(c *C) {
+ _, err := boot.NameAndRevnoFromSnap("invalid")
+ c.Assert(err, ErrorMatches, `input "invalid" has invalid format \(not enough '_'\)`)
+ _, err = boot.NameAndRevnoFromSnap("invalid_xxx.snap")
+ c.Assert(err, ErrorMatches, `invalid snap revision: "xxx"`)
+}
+
+func BenchmarkNameAndRevno(b *testing.B) {
+ for n := 0; n < b.N; n++ {
+ for _, sn := range []string{
+ "core_21.snap",
+ "kernel_41.snap",
+ "some-long-kernel-name-kernel_82.snap",
+ "what-is-this-core_111.snap",
+ } {
+ boot.NameAndRevnoFromSnap(sn)
+ }
+ }
+}
+
+func (s *bootSetSuite) TestInUse(c *C) {
+ for _, t := range []struct {
+ bootVarKey string
+ bootVarValue string
+
+ snapName string
+ snapRev snap.Revision
+
+ inUse bool
+ }{
+ // in use
+ {"snap_kernel", "kernel_41.snap", "kernel", snap.R(41), true},
+ {"snap_try_kernel", "kernel_82.snap", "kernel", snap.R(82), true},
+ {"snap_core", "core_21.snap", "core", snap.R(21), true},
+ {"snap_try_core", "core_42.snap", "core", snap.R(42), true},
+ // not in use
+ {"snap_core", "core_111.snap", "core", snap.R(21), false},
+ {"snap_try_core", "core_111.snap", "core", snap.R(21), false},
+ {"snap_kernel", "kernel_111.snap", "kernel", snap.R(1), false},
+ {"snap_try_kernel", "kernel_111.snap", "kernel", snap.R(1), false},
+ } {
+ s.bootloader.BootVars[t.bootVarKey] = t.bootVarValue
+ c.Assert(boot.InUse(t.snapName, t.snapRev), Equals, t.inUse, Commentf("unexpected result: %s %s %v", t.snapName, t.snapRev, t.inUse))
+ }
+}
+
+func (s *bootSetSuite) TestInUseUnhapy(c *C) {
+ logbuf, restore := logger.MockLogger()
+ defer restore()
+ s.bootloader.BootVars["snap_kernel"] = "kernel_41.snap"
+
+ // sanity check
+ c.Check(boot.InUse("kernel", snap.R(41)), Equals, true)
+
+ // make GetVars fail
+ s.bootloader.GetErr = errors.New("zap")
+ c.Check(boot.InUse("kernel", snap.R(41)), Equals, false)
+ c.Check(logbuf.String(), testutil.Contains, "cannot get boot vars: zap")
+ s.bootloader.GetErr = nil
+
+ // make bootloader.Find fail
+ bootloader.ForceError(errors.New("broken bootloader"))
+ c.Check(boot.InUse("kernel", snap.R(41)), Equals, false)
+ c.Check(logbuf.String(), testutil.Contains, "cannot get boot settings: broken bootloader")
+}
+
+func (s *bootSetSuite) TestCurrentBootNameAndRevision(c *C) {
+ s.bootloader.BootVars["snap_core"] = "core_2.snap"
+ s.bootloader.BootVars["snap_kernel"] = "canonical-pc-linux_2.snap"
+
+ current, err := boot.GetCurrentBoot(snap.TypeOS)
+ c.Check(err, IsNil)
+ c.Check(current.Name, Equals, "core")
+ c.Check(current.Revision, Equals, snap.R(2))
+
+ current, err = boot.GetCurrentBoot(snap.TypeKernel)
+ c.Check(err, IsNil)
+ c.Check(current.Name, Equals, "canonical-pc-linux")
+ c.Check(current.Revision, Equals, snap.R(2))
+
+ s.bootloader.BootVars["snap_mode"] = "trying"
+ _, err = boot.GetCurrentBoot(snap.TypeKernel)
+ c.Check(err, Equals, boot.ErrBootNameAndRevisionNotReady)
+}
+
+func (s *bootSetSuite) TestCurrentBootNameAndRevisionUnhappy(c *C) {
+ _, err := boot.GetCurrentBoot(snap.TypeKernel)
+ c.Check(err, ErrorMatches, "cannot get name and revision of boot kernel: boot variable unset")
+
+ _, err = boot.GetCurrentBoot(snap.TypeOS)
+ c.Check(err, ErrorMatches, "cannot get name and revision of boot base: boot variable unset")
+
+ _, err = boot.GetCurrentBoot(snap.TypeBase)
+ c.Check(err, ErrorMatches, "cannot get name and revision of boot base: boot variable unset")
+
+ _, err = boot.GetCurrentBoot(snap.TypeApp)
+ c.Check(err, ErrorMatches, "internal error: cannot find boot revision for snap type \"app\"")
+
+ // sanity check
+ s.bootloader.BootVars["snap_kernel"] = "kernel_41.snap"
+ current, err := boot.GetCurrentBoot(snap.TypeKernel)
+ c.Check(err, IsNil)
+ c.Check(current.Name, Equals, "kernel")
+ c.Check(current.Revision, Equals, snap.R(41))
+
+ // make GetVars fail
+ s.bootloader.GetErr = errors.New("zap")
+ _, err = boot.GetCurrentBoot(snap.TypeKernel)
+ c.Check(err, ErrorMatches, "cannot get boot variables: zap")
+ s.bootloader.GetErr = nil
+
+ // make bootloader.Find fail
+ bootloader.ForceError(errors.New("broken bootloader"))
+ _, err = boot.GetCurrentBoot(snap.TypeKernel)
+ c.Check(err, ErrorMatches, "cannot get boot settings: broken bootloader")
+}
+
+func (s *bootSetSuite) TestParticipant(c *C) {
+ info := &snap.Info{}
+ info.RealName = "some-snap"
+
+ bp := boot.Participant(info, snap.TypeApp, nil, false)
+ c.Check(bp.IsTrivial(), Equals, true)
+
+ for _, typ := range []snap.Type{
+ snap.TypeKernel,
+ snap.TypeOS,
+ snap.TypeBase,
+ } {
+ bp = boot.Participant(info, typ, nil, true)
+ c.Check(bp.IsTrivial(), Equals, true)
+
+ bp = boot.Participant(info, typ, nil, false)
+ c.Check(bp.IsTrivial(), Equals, false)
+
+ c.Check(bp, DeepEquals, boot.NewCoreBootParticipant(info, typ))
+ }
+}
+
+type mockModel string
+
+func (s mockModel) Kernel() string { return string(s) }
+func (s mockModel) Base() string { return string(s) }
+func (s mockModel) Classic() bool { return s == "" }
+
+func (s *bootSetSuite) TestParticipantBaseWithModel(c *C) {
+ core := &snap.Info{SideInfo: snap.SideInfo{RealName: "core"}, SnapType: snap.TypeOS}
+ core18 := &snap.Info{SideInfo: snap.SideInfo{RealName: "core18"}, SnapType: snap.TypeBase}
+
+ type tableT struct {
+ with *snap.Info
+ model mockModel
+ nop bool
+ }
+
+ table := []tableT{
+ {
+ with: core,
+ model: "",
+ nop: false,
+ }, {
+ with: core,
+ model: "core",
+ nop: false,
+ }, {
+ with: core,
+ model: "core18",
+ nop: true,
+ },
+ {
+ with: core18,
+ model: "",
+ nop: true,
+ },
+ {
+ with: core18,
+ model: "core",
+ nop: true,
+ },
+ {
+ with: core18,
+ model: "core18",
+ nop: false,
+ },
+ }
+
+ for i, t := range table {
+ bp := boot.Participant(t.with, t.with.GetType(), t.model, true)
+ c.Check(bp.IsTrivial(), Equals, true)
+
+ bp = boot.Participant(t.with, t.with.GetType(), t.model, false)
+ c.Check(bp.IsTrivial(), Equals, t.nop, Commentf("%d", i))
+ if !t.nop {
+ c.Check(bp, DeepEquals, boot.NewCoreBootParticipant(t.with, t.with.GetType()))
+ }
+ }
+}
+
+func (s *bootSetSuite) TestKernelWithModel(c *C) {
+ info := &snap.Info{}
+ info.RealName = "kernel"
+ expected := boot.NewCoreKernel(info)
+
+ type tableT struct {
+ model mockModel
+ nop bool
+ krn boot.BootKernel
+ }
+
+ table := []tableT{
+ {
+ model: "other-kernel",
+ nop: true,
+ krn: boot.Trivial{},
+ }, {
+ model: "kernel",
+ nop: false,
+ krn: expected,
+ }, {
+ model: "",
+ nop: true,
+ krn: boot.Trivial{},
+ },
+ }
+
+ for _, t := range table {
+ krn := boot.Kernel(info, snap.TypeKernel, t.model, true)
+ c.Check(krn.IsTrivial(), Equals, true)
+
+ krn = boot.Kernel(info, snap.TypeKernel, t.model, false)
+ c.Check(krn.IsTrivial(), Equals, t.nop)
+ c.Check(krn, DeepEquals, t.krn)
+ }
+}
+
+func (s *bootSetSuite) TestMarkBootSuccessfulAllSnap(c *C) {
+ s.bootloader.BootVars["snap_mode"] = "trying"
+ s.bootloader.BootVars["snap_try_core"] = "os1"
+ s.bootloader.BootVars["snap_try_kernel"] = "k1"
+ err := boot.MarkBootSuccessful()
+ c.Assert(err, IsNil)
+
+ expected := map[string]string{
+ // cleared
+ "snap_mode": "",
+ "snap_try_kernel": "",
+ "snap_try_core": "",
+ // updated
+ "snap_kernel": "k1",
+ "snap_core": "os1",
+ }
+ c.Assert(s.bootloader.BootVars, DeepEquals, expected)
+
+ // do it again, verify its still valid
+ err = boot.MarkBootSuccessful()
+ c.Assert(err, IsNil)
+ c.Assert(s.bootloader.BootVars, DeepEquals, expected)
+}
+
+func (s *bootSetSuite) TestMarkBootSuccessfulKKernelUpdate(c *C) {
+ s.bootloader.BootVars["snap_mode"] = "trying"
+ s.bootloader.BootVars["snap_core"] = "os1"
+ s.bootloader.BootVars["snap_kernel"] = "k1"
+ s.bootloader.BootVars["snap_try_core"] = ""
+ s.bootloader.BootVars["snap_try_kernel"] = "k2"
+ err := boot.MarkBootSuccessful()
+ c.Assert(err, IsNil)
+ c.Assert(s.bootloader.BootVars, DeepEquals, map[string]string{
+ // cleared
+ "snap_mode": "",
+ "snap_try_kernel": "",
+ "snap_try_core": "",
+ // unchanged
+ "snap_core": "os1",
+ // updated
+ "snap_kernel": "k2",
+ })
+}
+
+func (s *bootSetSuite) makeSnap(c *C, name, yaml string, revno snap.Revision) (fn string, info *snap.Info) {
+ si := &snap.SideInfo{
+ RealName: name,
+ Revision: revno,
+ }
+ fn = snaptest.MakeTestSnapWithFiles(c, yaml, nil)
+ snapf, err := snap.Open(fn)
+ c.Assert(err, IsNil)
+ info, err = snap.ReadInfoFromSnapFile(snapf, si)
+ c.Assert(err, IsNil)
+ return fn, info
+}
+
+func (s *bootSetSuite) TestMakeBootable(c *C) {
+ dirs.SetRootDir("")
+
+ headers := map[string]interface{}{
+ "type": "model",
+ "authority-id": "my-brand",
+ "series": "16",
+ "brand-id": "my-brand",
+ "model": "my-model",
+ "display-name": "My Model",
+ "architecture": "amd64",
+ "base": "core18",
+ "gadget": "pc=18",
+ "kernel": "pc-kernel=18",
+ "timestamp": "2018-01-01T08:00:00+00:00",
+ }
+ model := assertstest.FakeAssertion(headers).(*asserts.Model)
+
+ grubCfg := []byte("#grub cfg")
+ unpackedGadgetDir := c.MkDir()
+ err := ioutil.WriteFile(filepath.Join(unpackedGadgetDir, "grub.conf"), grubCfg, 0644)
+ c.Assert(err, IsNil)
+
+ rootdir := c.MkDir()
+
+ seedSnapsDirs := filepath.Join(rootdir, "/var/lib/snapd/seed", "snaps")
+ err = os.MkdirAll(seedSnapsDirs, 0755)
+ c.Assert(err, IsNil)
+
+ baseFn, baseInfo := s.makeSnap(c, "core18", `name: core18
+type: base
+version: 4.0
+`, snap.R(3))
+ baseInSeed := filepath.Join(seedSnapsDirs, filepath.Base(baseInfo.MountFile()))
+ err = os.Rename(baseFn, baseInSeed)
+ c.Assert(err, IsNil)
+ kernelFn, kernelInfo := s.makeSnap(c, "pc-kernel", `name: pc-kernel
+type: kernel
+version: 4.0
+`, snap.R(5))
+ kernelInSeed := filepath.Join(seedSnapsDirs, filepath.Base(kernelInfo.MountFile()))
+ err = os.Rename(kernelFn, kernelInSeed)
+ c.Assert(err, IsNil)
+
+ err = boot.MakeBootable(model, rootdir, map[string]*snap.Info{
+ kernelInSeed: kernelInfo,
+ baseInSeed: baseInfo,
+ }, unpackedGadgetDir)
+ c.Assert(err, IsNil)
+
+ // check the bootloader config
+ m, err := s.bootloader.GetBootVars("snap_kernel", "snap_core", "snap_menuentry")
+ c.Assert(err, IsNil)
+ c.Check(m["snap_kernel"], Equals, "pc-kernel_5.snap")
+ c.Check(m["snap_core"], Equals, "core18_3.snap")
+ c.Check(m["snap_menuentry"], Equals, "My Model")
+
+ // kernel was extracted as needed
+ c.Check(s.bootloader.ExtractKernelAssetsCalls, DeepEquals, []snap.PlaceInfo{kernelInfo})
+
+ // check symlinks from snap blob dir
+ kernelBlob := filepath.Join(dirs.SnapBlobDirUnder(rootdir), filepath.Base(kernelInfo.MountFile()))
+ dst, err := os.Readlink(filepath.Join(dirs.SnapBlobDirUnder(rootdir), filepath.Base(kernelInfo.MountFile())))
+ c.Assert(err, IsNil)
+ c.Check(dst, Equals, "../seed/snaps/pc-kernel_5.snap")
+ c.Check(kernelBlob, testutil.FilePresent)
+
+ baseBlob := filepath.Join(dirs.SnapBlobDirUnder(rootdir), filepath.Base(baseInfo.MountFile()))
+ dst, err = os.Readlink(filepath.Join(dirs.SnapBlobDirUnder(rootdir), filepath.Base(baseInfo.MountFile())))
+ c.Assert(err, IsNil)
+ c.Check(dst, Equals, "../seed/snaps/core18_3.snap")
+ c.Check(baseBlob, testutil.FilePresent)
+
+ // check that the bootloader (grub here) configuration was copied
+ c.Check(filepath.Join(rootdir, "boot", "grub/grub.cfg"), testutil.FileEquals, grubCfg)
+}
diff -Nru snapd-2.41+19.10.1/boot/export_test.go snapd-2.42.1+19.10/boot/export_test.go
--- snapd-2.41+19.10.1/boot/export_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/boot/export_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -19,6 +19,20 @@
package boot
+import (
+ "github.com/snapcore/snapd/snap"
+)
+
var (
NameAndRevnoFromSnap = nameAndRevnoFromSnap
)
+
+func NewCoreBootParticipant(s snap.PlaceInfo, t snap.Type) *coreBootParticipant {
+ return &coreBootParticipant{s: s, t: t}
+}
+
+func NewCoreKernel(s snap.PlaceInfo) *coreKernel {
+ return &coreKernel{s}
+}
+
+type Trivial = trivial
diff -Nru snapd-2.41+19.10.1/boot/kernel_os.go snapd-2.42.1+19.10/boot/kernel_os.go
--- snapd-2.41+19.10.1/boot/kernel_os.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/boot/kernel_os.go 2019-10-30 12:17:43.000000000 +0000
@@ -20,65 +20,32 @@
package boot
import (
- "errors"
"fmt"
"path/filepath"
- "strings"
"github.com/snapcore/snapd/bootloader"
"github.com/snapcore/snapd/logger"
- "github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
)
-// RemoveKernelAssets removes the unpacked kernel/initrd for the given
-// kernel snap.
-func RemoveKernelAssets(s snap.PlaceInfo) error {
- bootloader, err := bootloader.Find()
- if err != nil {
- return fmt.Errorf("no not remove kernel assets: %s", err)
- }
-
- // ask bootloader to remove the kernel assets if needed
- return bootloader.RemoveKernelAssets(s)
-}
-
-// ExtractKernelAssets extracts kernel/initrd/dtb data from the given
-// kernel snap, if required, to a versioned bootloader directory so
-// that the bootloader can use it.
-func ExtractKernelAssets(s *snap.Info, snapf snap.Container) error {
- if s.GetType() != snap.TypeKernel {
- return fmt.Errorf("cannot extract kernel assets from snap type %q", s.GetType())
- }
-
- bootloader, err := bootloader.Find()
- if err != nil {
- return fmt.Errorf("cannot extract kernel assets: %s", err)
- }
-
- // ask bootloader to extract the kernel assets if needed
- return bootloader.ExtractKernelAssets(s, snapf)
+type coreBootParticipant struct {
+ s snap.PlaceInfo
+ t snap.Type
}
-// SetNextBoot will schedule the given OS or base or kernel snap to be
-// used in the next boot. For base snaps it up to the caller to select
-// the right bootable base (from the model assertion).
-func SetNextBoot(s *snap.Info) error {
- if release.OnClassic {
- return fmt.Errorf("cannot set next boot on classic systems")
- }
+// ensure coreBootParticipant is a BootParticipant
+var _ BootParticipant = (*coreBootParticipant)(nil)
- if s.GetType() != snap.TypeOS && s.GetType() != snap.TypeKernel && s.GetType() != snap.TypeBase {
- return fmt.Errorf("cannot set next boot to snap %q with type %q", s.SnapName(), s.GetType())
- }
+func (*coreBootParticipant) IsTrivial() bool { return false }
- bootloader, err := bootloader.Find()
+func (bs *coreBootParticipant) SetNextBoot() error {
+ bootloader, err := bootloader.Find("", nil)
if err != nil {
return fmt.Errorf("cannot set next boot: %s", err)
}
var nextBoot, goodBoot string
- switch s.GetType() {
+ switch bs.t {
case snap.TypeOS, snap.TypeBase:
nextBoot = "snap_try_core"
goodBoot = "snap_core"
@@ -86,14 +53,14 @@
nextBoot = "snap_try_kernel"
goodBoot = "snap_kernel"
}
- blobName := filepath.Base(s.MountFile())
+ blobName := filepath.Base(bs.s.MountFile())
// check if we actually need to do anything, i.e. the exact same
// kernel/core revision got installed again (e.g. firstboot)
// and we are not in any special boot mode
m, err := bootloader.GetBootVars("snap_mode", goodBoot)
if err != nil {
- return err
+ return fmt.Errorf("cannot set next boot: %s", err)
}
if m[goodBoot] == blobName {
// If we were in anything but default ("") mode before
@@ -115,21 +82,15 @@
})
}
-// ChangeRequiresReboot returns whether a reboot is required to switch
-// to the given OS, base or kernel snap.
-func ChangeRequiresReboot(s *snap.Info) bool {
- if s.GetType() != snap.TypeKernel && s.GetType() != snap.TypeOS && s.GetType() != snap.TypeBase {
- return false
- }
-
- bootloader, err := bootloader.Find()
+func (bs *coreBootParticipant) ChangeRequiresReboot() bool {
+ bootloader, err := bootloader.Find("", nil)
if err != nil {
logger.Noticef("cannot get boot settings: %s", err)
return false
}
var nextBoot, goodBoot string
- switch s.GetType() {
+ switch bs.t {
case snap.TypeKernel:
nextBoot = "snap_try_kernel"
goodBoot = "snap_kernel"
@@ -144,7 +105,7 @@
return false
}
- squashfsName := filepath.Base(s.MountFile())
+ squashfsName := filepath.Base(bs.s.MountFile())
if m[nextBoot] == squashfsName && m[goodBoot] != m[nextBoot] {
return true
}
@@ -152,91 +113,32 @@
return false
}
-// InUse checks if the given name/revision is used in the
-// boot environment
-func InUse(name string, rev snap.Revision) bool {
- bootloader, err := bootloader.Find()
- if err != nil {
- logger.Noticef("cannot get boot settings: %s", err)
- return false
- }
-
- bootVars, err := bootloader.GetBootVars("snap_kernel", "snap_try_kernel", "snap_core", "snap_try_core")
- if err != nil {
- logger.Noticef("cannot get boot vars: %s", err)
- return false
- }
-
- snapFile := filepath.Base(snap.MountFile(name, rev))
- for _, bootVar := range bootVars {
- if bootVar == snapFile {
- return true
- }
- }
-
- return false
-}
-
-var (
- ErrBootNameAndRevisionAgain = errors.New("boot revision not yet established")
-)
-
-type NameAndRevision struct {
- Name string
- Revision snap.Revision
+type coreKernel struct {
+ s snap.PlaceInfo
}
-// GetCurrentBoot returns the currently set name and revision for boot for the given
-// type of snap, which can be snap.TypeBase (or snap.TypeOS), or snap.TypeKernel.
-// Returns ErrBootNameAndRevisionAgain if the values are temporarily not established.
-func GetCurrentBoot(t snap.Type) (*NameAndRevision, error) {
- var bootVar, errName string
- switch t {
- case snap.TypeKernel:
- bootVar = "snap_kernel"
- errName = "kernel"
- case snap.TypeOS, snap.TypeBase:
- bootVar = "snap_core"
- errName = "snap"
- default:
- return nil, fmt.Errorf("internal error: cannot find boot revision for snap type %q", t)
- }
-
- loader, err := bootloader.Find()
- if err != nil {
- return nil, fmt.Errorf("cannot get boot settings: %s", err)
- }
+// ensure coreKernel is a Kernel
+var _ BootKernel = (*coreKernel)(nil)
- m, err := loader.GetBootVars(bootVar, "snap_mode")
- if err != nil {
- return nil, fmt.Errorf("cannot get boot variables: %s", err)
- }
-
- if m["snap_mode"] == "trying" {
- return nil, ErrBootNameAndRevisionAgain
- }
+func (*coreKernel) IsTrivial() bool { return false }
- nameAndRevno, err := nameAndRevnoFromSnap(m[bootVar])
+func (k *coreKernel) RemoveKernelAssets() error {
+ // XXX: shouldn't we check the snap type?
+ bootloader, err := bootloader.Find("", nil)
if err != nil {
- return nil, fmt.Errorf("cannot get name and revision of boot %s: %v", errName, err)
+ return fmt.Errorf("cannot remove kernel assets: %s", err)
}
- return nameAndRevno, nil
+ // ask bootloader to remove the kernel assets if needed
+ return bootloader.RemoveKernelAssets(k.s)
}
-func nameAndRevnoFromSnap(sn string) (*NameAndRevision, error) {
- if sn == "" {
- return nil, fmt.Errorf("unset")
- }
- idx := strings.IndexByte(sn, '_')
- if idx < 1 {
- return nil, fmt.Errorf("input %q has invalid format (not enough '_')", sn)
- }
- name := sn[:idx]
- revnoNSuffix := sn[idx+1:]
- rev, err := snap.ParseRevision(strings.TrimSuffix(revnoNSuffix, ".snap"))
+func (k *coreKernel) ExtractKernelAssets(snapf snap.Container) error {
+ bootloader, err := bootloader.Find("", nil)
if err != nil {
- return nil, err
+ return fmt.Errorf("cannot extract kernel assets: %s", err)
}
- return &NameAndRevision{Name: name, Revision: rev}, nil
+
+ // ask bootloader to extract the kernel assets if needed
+ return bootloader.ExtractKernelAssets(k.s, snapf)
}
diff -Nru snapd-2.41+19.10.1/boot/kernel_os_test.go snapd-2.42.1+19.10/boot/kernel_os_test.go
--- snapd-2.41+19.10.1/boot/kernel_os_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/boot/kernel_os_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2014-2016 Canonical Ltd
+ * Copyright (C) 2014-2019 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -20,25 +20,23 @@
package boot_test
import (
+ "errors"
"io/ioutil"
"path/filepath"
- "testing"
. "gopkg.in/check.v1"
"github.com/snapcore/snapd/boot"
- "github.com/snapcore/snapd/boot/boottest"
"github.com/snapcore/snapd/bootloader"
+ "github.com/snapcore/snapd/bootloader/bootloadertest"
"github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
- "github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snaptest"
"github.com/snapcore/snapd/testutil"
)
-func TestBoot(t *testing.T) { TestingT(t) }
-
const packageKernel = `
name: ubuntu-kernel
version: 4.0-1
@@ -46,113 +44,114 @@
vendor: Someone
`
-// baseKernelOSSuite is used to setup the common test environment
-type baseKernelOSSuite struct {
- testutil.BaseTest
+// coreBootSetSuite tests the abstract bootloader behaviour including
+// bootenv setting, error handling etc., for a core BootSet.
+type coreBootSetSuite struct {
+ baseBootSetSuite
- bootdir string
+ bootloader *bootloadertest.MockBootloader
}
-func (s *baseKernelOSSuite) SetUpTest(c *C) {
- s.BaseTest.SetUpTest(c)
+var _ = Suite(&coreBootSetSuite{})
- dirs.SetRootDir(c.MkDir())
- s.AddCleanup(func() { dirs.SetRootDir("") })
- restore := snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})
- s.AddCleanup(restore)
- restore = release.MockOnClassic(false)
- s.AddCleanup(restore)
+func (s *coreBootSetSuite) SetUpTest(c *C) {
+ s.baseBootSetSuite.SetUpTest(c)
- s.bootdir = filepath.Join(dirs.GlobalRootDir, "boot")
+ s.bootloader = bootloadertest.Mock("mock", c.MkDir())
+ bootloader.Force(s.bootloader)
+ s.AddCleanup(func() { bootloader.Force(nil) })
}
-// kernelOSSuite tests the abstract bootloader behaviour including
-// bootenv setting, error handling etc
-type kernelOSSuite struct {
- baseKernelOSSuite
-
- loader *boottest.MockBootloader
+func (s *coreBootSetSuite) TestExtractKernelAssetsError(c *C) {
+ bootloader.ForceError(errors.New("brkn"))
+ err := boot.NewCoreKernel(&snap.Info{}).ExtractKernelAssets(nil)
+ c.Check(err, ErrorMatches, `cannot extract kernel assets: brkn`)
}
-var _ = Suite(&kernelOSSuite{})
+func (s *coreBootSetSuite) TestRemoveKernelAssetsError(c *C) {
+ bootloader.ForceError(errors.New("brkn"))
+ err := boot.NewCoreKernel(&snap.Info{}).RemoveKernelAssets()
+ c.Check(err, ErrorMatches, `cannot remove kernel assets: brkn`)
+}
-func (s *kernelOSSuite) SetUpTest(c *C) {
- s.baseKernelOSSuite.SetUpTest(c)
+func (s *coreBootSetSuite) TestChangeRequiresRebootError(c *C) {
+ logbuf, restore := logger.MockLogger()
+ defer restore()
+ bp := boot.NewCoreBootParticipant(&snap.Info{}, snap.TypeBase)
- s.loader = boottest.NewMockBootloader("mock", c.MkDir())
- bootloader.Force(s.loader)
- s.AddCleanup(func() { bootloader.Force(nil) })
-}
+ s.bootloader.GetErr = errors.New("zap")
-func (s *kernelOSSuite) TestExtractKernelAssetsError(c *C) {
- info := &snap.Info{}
- info.SnapType = snap.TypeApp
+ c.Check(bp.ChangeRequiresReboot(), Equals, false)
+ c.Check(logbuf.String(), testutil.Contains, `cannot get boot variables: zap`)
+ s.bootloader.GetErr = nil
+ logbuf.Reset()
- err := boot.ExtractKernelAssets(info, nil)
- c.Assert(err, ErrorMatches, `cannot extract kernel assets from snap type "app"`)
+ bootloader.ForceError(errors.New("brkn"))
+ c.Check(bp.ChangeRequiresReboot(), Equals, false)
+ c.Check(logbuf.String(), testutil.Contains, `cannot get boot settings: brkn`)
}
-// SetNextBoot should do nothing on classic LP: #1580403
-func (s *kernelOSSuite) TestSetNextBootOnClassic(c *C) {
- restore := release.MockOnClassic(true)
- defer restore()
-
- // Create a fake OS snap that we try to update
- snapInfo := snaptest.MockSnap(c, "name: os\ntype: os", &snap.SideInfo{Revision: snap.R(42)})
- err := boot.SetNextBoot(snapInfo)
- c.Assert(err, ErrorMatches, "cannot set next boot on classic systems")
+func (s *coreBootSetSuite) TestSetNextBootError(c *C) {
+ s.bootloader.GetErr = errors.New("zap")
+ err := boot.NewCoreBootParticipant(&snap.Info{}, snap.TypeApp).SetNextBoot()
+ c.Check(err, ErrorMatches, `cannot set next boot: zap`)
- c.Assert(s.loader.BootVars, HasLen, 0)
+ bootloader.ForceError(errors.New("brkn"))
+ err = boot.NewCoreBootParticipant(&snap.Info{}, snap.TypeApp).SetNextBoot()
+ c.Check(err, ErrorMatches, `cannot set next boot: brkn`)
}
-func (s *kernelOSSuite) TestSetNextBootForCore(c *C) {
+func (s *coreBootSetSuite) TestSetNextBootForCore(c *C) {
info := &snap.Info{}
info.SnapType = snap.TypeOS
info.RealName = "core"
info.Revision = snap.R(100)
- err := boot.SetNextBoot(info)
+ bs := boot.NewCoreBootParticipant(info, info.GetType())
+ err := bs.SetNextBoot()
c.Assert(err, IsNil)
- v, err := s.loader.GetBootVars("snap_try_core", "snap_mode")
+ v, err := s.bootloader.GetBootVars("snap_try_core", "snap_mode")
c.Assert(err, IsNil)
c.Assert(v, DeepEquals, map[string]string{
"snap_try_core": "core_100.snap",
"snap_mode": "try",
})
- c.Check(boot.ChangeRequiresReboot(info), Equals, true)
+ c.Check(bs.ChangeRequiresReboot(), Equals, true)
}
-func (s *kernelOSSuite) TestSetNextBootWithBaseForCore(c *C) {
+func (s *coreBootSetSuite) TestSetNextBootWithBaseForCore(c *C) {
info := &snap.Info{}
info.SnapType = snap.TypeBase
info.RealName = "core18"
info.Revision = snap.R(1818)
- err := boot.SetNextBoot(info)
+ bs := boot.NewCoreBootParticipant(info, info.GetType())
+ err := bs.SetNextBoot()
c.Assert(err, IsNil)
- v, err := s.loader.GetBootVars("snap_try_core", "snap_mode")
+ v, err := s.bootloader.GetBootVars("snap_try_core", "snap_mode")
c.Assert(err, IsNil)
c.Assert(v, DeepEquals, map[string]string{
"snap_try_core": "core18_1818.snap",
"snap_mode": "try",
})
- c.Check(boot.ChangeRequiresReboot(info), Equals, true)
+ c.Check(bs.ChangeRequiresReboot(), Equals, true)
}
-func (s *kernelOSSuite) TestSetNextBootForKernel(c *C) {
+func (s *coreBootSetSuite) TestSetNextBootForKernel(c *C) {
info := &snap.Info{}
info.SnapType = snap.TypeKernel
info.RealName = "krnl"
info.Revision = snap.R(42)
- err := boot.SetNextBoot(info)
+ bp := boot.NewCoreBootParticipant(info, snap.TypeKernel)
+ err := bp.SetNextBoot()
c.Assert(err, IsNil)
- v, err := s.loader.GetBootVars("snap_try_kernel", "snap_mode")
+ v, err := s.bootloader.GetBootVars("snap_try_kernel", "snap_mode")
c.Assert(err, IsNil)
c.Assert(v, DeepEquals, map[string]string{
"snap_try_kernel": "krnl_42.snap",
@@ -162,35 +161,35 @@
bootVars := map[string]string{
"snap_kernel": "krnl_40.snap",
"snap_try_kernel": "krnl_42.snap"}
- s.loader.SetBootVars(bootVars)
- c.Check(boot.ChangeRequiresReboot(info), Equals, true)
+ s.bootloader.SetBootVars(bootVars)
+ c.Check(bp.ChangeRequiresReboot(), Equals, true)
// simulate good boot
bootVars = map[string]string{"snap_kernel": "krnl_42.snap"}
- s.loader.SetBootVars(bootVars)
- c.Check(boot.ChangeRequiresReboot(info), Equals, false)
+ s.bootloader.SetBootVars(bootVars)
+ c.Check(bp.ChangeRequiresReboot(), Equals, false)
}
-func (s *kernelOSSuite) TestSetNextBootForKernelForTheSameKernel(c *C) {
+func (s *coreBootSetSuite) TestSetNextBootForKernelForTheSameKernel(c *C) {
info := &snap.Info{}
info.SnapType = snap.TypeKernel
info.RealName = "krnl"
info.Revision = snap.R(40)
bootVars := map[string]string{"snap_kernel": "krnl_40.snap"}
- s.loader.SetBootVars(bootVars)
+ s.bootloader.SetBootVars(bootVars)
- err := boot.SetNextBoot(info)
+ err := boot.NewCoreBootParticipant(info, snap.TypeKernel).SetNextBoot()
c.Assert(err, IsNil)
- v, err := s.loader.GetBootVars("snap_kernel")
+ v, err := s.bootloader.GetBootVars("snap_kernel")
c.Assert(err, IsNil)
c.Assert(v, DeepEquals, map[string]string{
"snap_kernel": "krnl_40.snap",
})
}
-func (s *kernelOSSuite) TestSetNextBootForKernelForTheSameKernelTryMode(c *C) {
+func (s *coreBootSetSuite) TestSetNextBootForKernelForTheSameKernelTryMode(c *C) {
info := &snap.Info{}
info.SnapType = snap.TypeKernel
info.RealName = "krnl"
@@ -200,12 +199,12 @@
"snap_kernel": "krnl_40.snap",
"snap_try_kernel": "krnl_99.snap",
"snap_mode": "try"}
- s.loader.SetBootVars(bootVars)
+ s.bootloader.SetBootVars(bootVars)
- err := boot.SetNextBoot(info)
+ err := boot.NewCoreBootParticipant(info, snap.TypeKernel).SetNextBoot()
c.Assert(err, IsNil)
- v, err := s.loader.GetBootVars("snap_kernel", "snap_try_kernel", "snap_mode")
+ v, err := s.bootloader.GetBootVars("snap_kernel", "snap_try_kernel", "snap_mode")
c.Assert(err, IsNil)
c.Assert(v, DeepEquals, map[string]string{
"snap_kernel": "krnl_40.snap",
@@ -214,118 +213,34 @@
})
}
-func (s *kernelOSSuite) TestInUse(c *C) {
- for _, t := range []struct {
- bootVarKey string
- bootVarValue string
-
- snapName string
- snapRev snap.Revision
-
- inUse bool
- }{
- // in use
- {"snap_kernel", "kernel_41.snap", "kernel", snap.R(41), true},
- {"snap_try_kernel", "kernel_82.snap", "kernel", snap.R(82), true},
- {"snap_core", "core_21.snap", "core", snap.R(21), true},
- {"snap_try_core", "core_42.snap", "core", snap.R(42), true},
- // not in use
- {"snap_core", "core_111.snap", "core", snap.R(21), false},
- {"snap_try_core", "core_111.snap", "core", snap.R(21), false},
- {"snap_kernel", "kernel_111.snap", "kernel", snap.R(1), false},
- {"snap_try_kernel", "kernel_111.snap", "kernel", snap.R(1), false},
- } {
- s.loader.BootVars[t.bootVarKey] = t.bootVarValue
- c.Assert(boot.InUse(t.snapName, t.snapRev), Equals, t.inUse, Commentf("unexpected result: %s %s %v", t.snapName, t.snapRev, t.inUse))
- }
-}
-
-func (s *kernelOSSuite) TestNameAndRevnoFromSnapValid(c *C) {
- info, err := boot.NameAndRevnoFromSnap("foo_2.snap")
- c.Assert(err, IsNil)
- c.Assert(info.Name, Equals, "foo")
- c.Assert(info.Revision, Equals, snap.R(2))
-}
-
-func (s *kernelOSSuite) TestNameAndRevnoFromSnapInvalidFormat(c *C) {
- _, err := boot.NameAndRevnoFromSnap("invalid")
- c.Assert(err, ErrorMatches, `input "invalid" has invalid format \(not enough '_'\)`)
-}
-
-func BenchmarkNameAndRevno(b *testing.B) {
- for n := 0; n < b.N; n++ {
- for _, sn := range []string{
- "core_21.snap",
- "kernel_41.snap",
- "some-long-kernel-name-kernel_82.snap",
- "what-is-this-core_111.snap",
- } {
- boot.NameAndRevnoFromSnap(sn)
- }
- }
-}
-
-func (s *kernelOSSuite) TestCurrentBootNameAndRevision(c *C) {
- s.loader.BootVars["snap_core"] = "core_2.snap"
- s.loader.BootVars["snap_kernel"] = "canonical-pc-linux_2.snap"
-
- current, err := boot.GetCurrentBoot(snap.TypeOS)
- c.Check(err, IsNil)
- c.Check(current.Name, Equals, "core")
- c.Check(current.Revision, Equals, snap.R(2))
-
- current, err = boot.GetCurrentBoot(snap.TypeKernel)
- c.Check(err, IsNil)
- c.Check(current.Name, Equals, "canonical-pc-linux")
- c.Check(current.Revision, Equals, snap.R(2))
-
- s.loader.BootVars["snap_mode"] = "trying"
- _, err = boot.GetCurrentBoot(snap.TypeKernel)
- c.Check(err, Equals, boot.ErrBootNameAndRevisionAgain)
-}
-
-func (s *kernelOSSuite) TestCurrentBootNameAndRevisionUnhappy(c *C) {
- _, err := boot.GetCurrentBoot(snap.TypeKernel)
- c.Check(err, ErrorMatches, "cannot get name and revision of boot kernel: unset")
-
- _, err = boot.GetCurrentBoot(snap.TypeOS)
- c.Check(err, ErrorMatches, "cannot get name and revision of boot snap: unset")
-
- _, err = boot.GetCurrentBoot(snap.TypeBase)
- c.Check(err, ErrorMatches, "cannot get name and revision of boot snap: unset")
-
- _, err = boot.GetCurrentBoot(snap.TypeApp)
- c.Check(err, ErrorMatches, "internal error: cannot find boot revision for snap type \"app\"")
-}
-
-// ubootKernelOSSuite tests the uboot specific code in the bootloader handling
-type ubootKernelOSSuite struct {
- baseKernelOSSuite
+// ubootBootSetSuite tests the uboot specific code in the bootloader handling
+type ubootBootSetSuite struct {
+ baseBootSetSuite
}
-var _ = Suite(&ubootKernelOSSuite{})
+var _ = Suite(&ubootBootSetSuite{})
-func (s *ubootKernelOSSuite) forceUbootBootloader(c *C) bootloader.Bootloader {
+func (s *ubootBootSetSuite) forceUbootBootloader(c *C) bootloader.Bootloader {
mockGadgetDir := c.MkDir()
err := ioutil.WriteFile(filepath.Join(mockGadgetDir, "uboot.conf"), nil, 0644)
c.Assert(err, IsNil)
- err = bootloader.InstallBootConfig(mockGadgetDir)
+ err = bootloader.InstallBootConfig(mockGadgetDir, dirs.GlobalRootDir)
c.Assert(err, IsNil)
- loader, err := bootloader.Find()
+ bloader, err := bootloader.Find("", nil)
c.Assert(err, IsNil)
- c.Check(loader, NotNil)
- bootloader.Force(loader)
+ c.Check(bloader, NotNil)
+ bootloader.Force(bloader)
s.AddCleanup(func() { bootloader.Force(nil) })
fn := filepath.Join(s.bootdir, "/uboot/uboot.env")
c.Assert(osutil.FileExists(fn), Equals, true)
- return loader
+ return bloader
}
-func (s *ubootKernelOSSuite) TestExtractKernelAssetsAndRemoveOnUboot(c *C) {
- loader := s.forceUbootBootloader(c)
- c.Assert(loader, NotNil)
+func (s *ubootBootSetSuite) TestExtractKernelAssetsAndRemoveOnUboot(c *C) {
+ bloader := s.forceUbootBootloader(c)
+ c.Assert(bloader, NotNil)
files := [][]string{
{"kernel.img", "I'm a kernel"},
@@ -347,7 +262,8 @@
info, err := snap.ReadInfoFromSnapFile(snapf, si)
c.Assert(err, IsNil)
- err = boot.ExtractKernelAssets(info, snapf)
+ bp := boot.NewCoreKernel(info)
+ err = bp.ExtractKernelAssets(snapf)
c.Assert(err, IsNil)
// this is where the kernel/initrd is unpacked
@@ -362,50 +278,50 @@
}
// it's idempotent
- err = boot.ExtractKernelAssets(info, snapf)
+ err = bp.ExtractKernelAssets(snapf)
c.Assert(err, IsNil)
// remove
- err = boot.RemoveKernelAssets(info)
+ err = bp.RemoveKernelAssets()
c.Assert(err, IsNil)
c.Check(osutil.FileExists(kernelAssetsDir), Equals, false)
// it's idempotent
- err = boot.RemoveKernelAssets(info)
+ err = bp.RemoveKernelAssets()
c.Assert(err, IsNil)
}
-// grubKernelOSSuite tests the GRUB specific code in the bootloader handling
-type grubKernelOSSuite struct {
- baseKernelOSSuite
+// grubBootSetSuite tests the GRUB specific code in the bootloader handling
+type grubBootSetSuite struct {
+ baseBootSetSuite
}
-var _ = Suite(&grubKernelOSSuite{})
+var _ = Suite(&grubBootSetSuite{})
-func (s *grubKernelOSSuite) forceGrubBootloader(c *C) bootloader.Bootloader {
+func (s *grubBootSetSuite) forceGrubBootloader(c *C) bootloader.Bootloader {
// make mock grub bootenv dir
mockGadgetDir := c.MkDir()
err := ioutil.WriteFile(filepath.Join(mockGadgetDir, "grub.conf"), nil, 0644)
c.Assert(err, IsNil)
- err = bootloader.InstallBootConfig(mockGadgetDir)
+ err = bootloader.InstallBootConfig(mockGadgetDir, dirs.GlobalRootDir)
c.Assert(err, IsNil)
- loader, err := bootloader.Find()
+ bloader, err := bootloader.Find("", nil)
c.Assert(err, IsNil)
- c.Check(loader, NotNil)
- loader.SetBootVars(map[string]string{
+ c.Check(bloader, NotNil)
+ bloader.SetBootVars(map[string]string{
"snap_kernel": "kernel_41.snap",
"snap_core": "core_21.snap",
})
- bootloader.Force(loader)
+ bootloader.Force(bloader)
s.AddCleanup(func() { bootloader.Force(nil) })
fn := filepath.Join(s.bootdir, "/grub/grub.cfg")
c.Assert(osutil.FileExists(fn), Equals, true)
- return loader
+ return bloader
}
-func (s *grubKernelOSSuite) TestExtractKernelAssetsNoUnpacksKernelForGrub(c *C) {
+func (s *grubBootSetSuite) TestExtractKernelAssetsNoUnpacksKernelForGrub(c *C) {
s.forceGrubBootloader(c)
files := [][]string{
@@ -424,7 +340,8 @@
info, err := snap.ReadInfoFromSnapFile(snapf, si)
c.Assert(err, IsNil)
- err = boot.ExtractKernelAssets(info, snapf)
+ bp := boot.NewCoreKernel(info)
+ err = bp.ExtractKernelAssets(snapf)
c.Assert(err, IsNil)
// kernel is *not* here
@@ -432,11 +349,11 @@
c.Assert(osutil.FileExists(kernimg), Equals, false)
// it's idempotent
- err = boot.ExtractKernelAssets(info, snapf)
+ err = bp.ExtractKernelAssets(snapf)
c.Assert(err, IsNil)
}
-func (s *grubKernelOSSuite) TestExtractKernelForceWorks(c *C) {
+func (s *grubBootSetSuite) TestExtractKernelForceWorks(c *C) {
s.forceGrubBootloader(c)
files := [][]string{
@@ -456,7 +373,8 @@
info, err := snap.ReadInfoFromSnapFile(snapf, si)
c.Assert(err, IsNil)
- err = boot.ExtractKernelAssets(info, snapf)
+ bp := boot.NewCoreKernel(info)
+ err = bp.ExtractKernelAssets(snapf)
c.Assert(err, IsNil)
// kernel is extracted
@@ -467,17 +385,17 @@
c.Assert(osutil.FileExists(initrdimg), Equals, true)
// it's idempotent
- err = boot.ExtractKernelAssets(info, snapf)
+ err = bp.ExtractKernelAssets(snapf)
c.Assert(err, IsNil)
// ensure that removal of assets also works
- err = boot.RemoveKernelAssets(info)
+ err = bp.RemoveKernelAssets()
c.Assert(err, IsNil)
exists, _, err := osutil.DirExists(filepath.Dir(kernimg))
c.Assert(err, IsNil)
c.Check(exists, Equals, false)
// it's idempotent
- err = boot.RemoveKernelAssets(info)
+ err = bp.RemoveKernelAssets()
c.Assert(err, IsNil)
}
diff -Nru snapd-2.41+19.10.1/bootloader/androidboot.go snapd-2.42.1+19.10/bootloader/androidboot.go
--- snapd-2.41+19.10.1/bootloader/androidboot.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/bootloader/androidboot.go 2019-10-30 12:17:43.000000000 +0000
@@ -24,16 +24,17 @@
"path/filepath"
"github.com/snapcore/snapd/bootloader/androidbootenv"
- "github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/snap"
)
-type androidboot struct{}
+type androidboot struct {
+ rootdir string
+}
// newAndroidboot creates a new Androidboot bootloader object
-func newAndroidBoot() Bootloader {
- a := &androidboot{}
+func newAndroidBoot(rootdir string) Bootloader {
+ a := &androidboot{rootdir: rootdir}
if !osutil.FileExists(a.ConfigFile()) {
return nil
}
@@ -44,8 +45,15 @@
return "androidboot"
}
+func (a *androidboot) setRootDir(rootdir string) {
+ a.rootdir = rootdir
+}
+
func (a *androidboot) dir() string {
- return filepath.Join(dirs.GlobalRootDir, "/boot/androidboot")
+ if a.rootdir == "" {
+ panic("internal error: unset rootdir")
+ }
+ return filepath.Join(a.rootdir, "/boot/androidboot")
}
func (a *androidboot) ConfigFile() string {
@@ -77,7 +85,7 @@
return env.Save()
}
-func (a *androidboot) ExtractKernelAssets(s *snap.Info, snapf snap.Container) error {
+func (a *androidboot) ExtractKernelAssets(s snap.PlaceInfo, snapf snap.Container) error {
return nil
}
diff -Nru snapd-2.41+19.10.1/bootloader/androidboot_test.go snapd-2.42.1+19.10/bootloader/androidboot_test.go
--- snapd-2.41+19.10.1/bootloader/androidboot_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/bootloader/androidboot_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -25,7 +25,6 @@
. "gopkg.in/check.v1"
"github.com/snapcore/snapd/bootloader"
- "github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snaptest"
@@ -41,22 +40,21 @@
s.baseBootenvTestSuite.SetUpTest(c)
// the file needs to exist for androidboot object to be created
- bootloader.MockAndroidBootFile(c, 0644)
+ bootloader.MockAndroidBootFile(c, s.rootdir, 0644)
}
func (s *androidBootTestSuite) TestNewAndroidbootNoAndroidbootReturnsNil(c *C) {
- dirs.GlobalRootDir = "/something/not/there"
- a := bootloader.NewAndroidBoot()
+ a := bootloader.NewAndroidBoot("/something/not/there")
c.Assert(a, IsNil)
}
func (s *androidBootTestSuite) TestNewAndroidboot(c *C) {
- a := bootloader.NewAndroidBoot()
+ a := bootloader.NewAndroidBoot(s.rootdir)
c.Assert(a, NotNil)
}
func (s *androidBootTestSuite) TestSetGetBootVar(c *C) {
- a := bootloader.NewAndroidBoot()
+ a := bootloader.NewAndroidBoot(s.rootdir)
bootVars := map[string]string{"snap_mode": "try"}
a.SetBootVars(bootVars)
@@ -67,7 +65,7 @@
}
func (s *androidBootTestSuite) TestExtractKernelAssetsNoUnpacksKernel(c *C) {
- a := bootloader.NewAndroidBoot()
+ a := bootloader.NewAndroidBoot(s.rootdir)
c.Assert(a, NotNil)
@@ -91,6 +89,6 @@
c.Assert(err, IsNil)
// kernel is *not* here
- kernimg := filepath.Join(dirs.GlobalRootDir, "boot", "androidboot", "ubuntu-kernel_42.snap", "kernel.img")
+ kernimg := filepath.Join(s.rootdir, "boot", "androidboot", "ubuntu-kernel_42.snap", "kernel.img")
c.Assert(osutil.FileExists(kernimg), Equals, false)
}
diff -Nru snapd-2.41+19.10.1/bootloader/bootloader.go snapd-2.42.1+19.10/bootloader/bootloader.go
--- snapd-2.41+19.10.1/bootloader/bootloader.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/bootloader/bootloader.go 2019-10-30 12:17:43.000000000 +0000
@@ -25,22 +25,11 @@
"os"
"path/filepath"
+ "github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/snap"
)
-const (
- // bootloader variable used to determine if boot was
- // successful. Set to value of either modeTry (when
- // attempting to boot a new rootfs) or modeSuccess (to denote
- // that the boot of the new rootfs was successful).
- bootmodeVar = "snap_mode"
-
- // Initial and final values
- modeTry = "try"
- modeSuccess = ""
-)
-
var (
// ErrBootloader is returned if the bootloader can not be determined
ErrBootloader = errors.New("cannot determine bootloader")
@@ -62,22 +51,29 @@
ConfigFile() string
// ExtractKernelAssets extracts kernel assets from the given kernel snap
- ExtractKernelAssets(s *snap.Info, snapf snap.Container) error
+ ExtractKernelAssets(s snap.PlaceInfo, snapf snap.Container) error
// RemoveKernelAssets removes the assets for the given kernel snap.
RemoveKernelAssets(s snap.PlaceInfo) error
}
+type installableBootloader interface {
+ Bootloader
+ setRootDir(string)
+}
+
// InstallBootConfig installs the bootloader config from the gadget
// snap dir into the right place.
-func InstallBootConfig(gadgetDir string) error {
- for _, bl := range []Bootloader{&grub{}, &uboot{}, &androidboot{}} {
+func InstallBootConfig(gadgetDir, rootDir string) error {
+ for _, bl := range []installableBootloader{&grub{}, &uboot{}, &androidboot{}, &lk{}} {
// the bootloader config file has to be root of the gadget snap
gadgetFile := filepath.Join(gadgetDir, bl.Name()+".conf")
if !osutil.FileExists(gadgetFile) {
continue
}
+ bl.setRootDir(rootDir)
+
systemFile := bl.ConfigFile()
if err := os.MkdirAll(filepath.Dir(systemFile), 0755); err != nil {
return err
@@ -88,88 +84,72 @@
return fmt.Errorf("cannot find boot config in %q", gadgetDir)
}
-var forcedBootloader Bootloader
+var (
+ forcedBootloader Bootloader
+ forcedError error
+)
+
+// Options carries bootloader options.
+type Options struct {
+ // PrepareImageTime indicates whether the booloader is being
+ // used at prepare-image time, that means not on a runtime
+ // system.
+ PrepareImageTime bool
+}
-// Find returns the bootloader for the given system
-// or an error if no bootloader is found
-func Find() (Bootloader, error) {
- if forcedBootloader != nil {
- return forcedBootloader, nil
+// Find returns the bootloader for the system
+// or an error if no bootloader is found.
+func Find(rootdir string, opts *Options) (Bootloader, error) {
+ if forcedBootloader != nil || forcedError != nil {
+ return forcedBootloader, forcedError
+ }
+
+ if rootdir == "" {
+ rootdir = dirs.GlobalRootDir
+ }
+ if opts == nil {
+ opts = &Options{}
}
// try uboot
- if uboot := newUboot(); uboot != nil {
+ if uboot := newUboot(rootdir); uboot != nil {
return uboot, nil
}
// no, try grub
- if grub := newGrub(); grub != nil {
+ if grub := newGrub(rootdir); grub != nil {
return grub, nil
}
// no, try androidboot
- if androidboot := newAndroidBoot(); androidboot != nil {
+ if androidboot := newAndroidBoot(rootdir); androidboot != nil {
return androidboot, nil
}
+ // no, try lk
+ if lk := newLk(rootdir, opts); lk != nil {
+ return lk, nil
+ }
+
// no, weeeee
return nil, ErrBootloader
}
// Force can be used to force setting a booloader to that Find will not use the
-// usual lookup process, use nil to reset to normal lookup.
+// usual lookup process; use nil to reset to normal lookup.
func Force(booloader Bootloader) {
forcedBootloader = booloader
+ forcedError = nil
}
-// MarkBootSuccessful marks the current boot as successful. This means
-// that snappy will consider this combination of kernel/os a valid
-// target for rollback.
-//
-// The states that a boot goes through are the following:
-// - By default snap_mode is "" in which case the bootloader loads
-// two squashfs'es denoted by variables snap_core and snap_kernel.
-// - On a refresh of core/kernel snapd will set snap_mode=try and
-// will also set snap_try_{core,kernel} to the core/kernel that
-// will be tried next.
-// - On reboot the bootloader will inspect the snap_mode and if the
-// mode is set to "try" it will set "snap_mode=trying" and then
-// try to boot the snap_try_{core,kernel}".
-// - On a successful boot snapd resets snap_mode to "" and copies
-// snap_try_{core,kernel} to snap_{core,kernel}. The snap_try_*
-// values are cleared afterwards.
-// - On a failing boot the bootloader will see snap_mode=trying which
-// means snapd did not start successfully. In this case the bootloader
-// will set snap_mode="" and the system will boot with the known good
-// values from snap_{core,kernel}
-func MarkBootSuccessful(bootloader Bootloader) error {
- m, err := bootloader.GetBootVars("snap_mode", "snap_try_core", "snap_try_kernel")
- if err != nil {
- return err
- }
-
- // snap_mode goes from "" -> "try" -> "trying" -> ""
- // so if we are not in "trying" mode, nothing to do here
- if m["snap_mode"] != "trying" {
- return nil
- }
-
- // update the boot vars
- for _, k := range []string{"kernel", "core"} {
- tryBootVar := fmt.Sprintf("snap_try_%s", k)
- bootVar := fmt.Sprintf("snap_%s", k)
- // update the boot vars
- if m[tryBootVar] != "" {
- m[bootVar] = m[tryBootVar]
- m[tryBootVar] = ""
- }
- }
- m["snap_mode"] = modeSuccess
-
- return bootloader.SetBootVars(m)
+// Force can be used to force Find to return an error; use nil to
+// reset to normal lookup.
+func ForceError(err error) {
+ forcedBootloader = nil
+ forcedError = err
}
-func extractKernelAssetsToBootDir(bootDir string, s *snap.Info, snapf snap.Container) error {
+func extractKernelAssetsToBootDir(bootDir string, s snap.PlaceInfo, snapf snap.Container) error {
// now do the kernel specific bits
blobName := filepath.Base(s.MountFile())
dstDir := filepath.Join(bootDir, blobName)
diff -Nru snapd-2.41+19.10.1/bootloader/bootloadertest/bootloadertest.go snapd-2.42.1+19.10/bootloader/bootloadertest/bootloadertest.go
--- snapd-2.41+19.10.1/bootloader/bootloadertest/bootloadertest.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/bootloader/bootloadertest/bootloadertest.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,99 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2014-2016 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package bootloadertest
+
+import (
+ "path/filepath"
+
+ "github.com/snapcore/snapd/bootloader"
+ "github.com/snapcore/snapd/snap"
+)
+
+// MockBootloader mocks the bootloader interface and records all
+// set/get calls.
+type MockBootloader struct {
+ BootVars map[string]string
+ SetErr error
+ GetErr error
+
+ name string
+ bootdir string
+
+ ExtractKernelAssetsCalls []snap.PlaceInfo
+ RemoveKernelAssetsCalls []snap.PlaceInfo
+}
+
+// ensure MockBootloader implements the Bootloader interface
+var _ bootloader.Bootloader = (*MockBootloader)(nil)
+
+func Mock(name, bootdir string) *MockBootloader {
+ return &MockBootloader{
+ name: name,
+ bootdir: bootdir,
+
+ BootVars: make(map[string]string),
+ }
+}
+
+func (b *MockBootloader) SetBootVars(values map[string]string) error {
+ for k, v := range values {
+ b.BootVars[k] = v
+ }
+ return b.SetErr
+}
+
+func (b *MockBootloader) GetBootVars(keys ...string) (map[string]string, error) {
+ out := map[string]string{}
+ for _, k := range keys {
+ out[k] = b.BootVars[k]
+ }
+
+ return out, b.GetErr
+}
+
+func (b *MockBootloader) Name() string {
+ return b.name
+}
+
+func (b *MockBootloader) ConfigFile() string {
+ return filepath.Join(b.bootdir, "mockboot/mockboot.cfg")
+}
+
+func (b *MockBootloader) ExtractKernelAssets(s snap.PlaceInfo, snapf snap.Container) error {
+ b.ExtractKernelAssetsCalls = append(b.ExtractKernelAssetsCalls, s)
+ return nil
+}
+
+func (b *MockBootloader) RemoveKernelAssets(s snap.PlaceInfo) error {
+ b.RemoveKernelAssetsCalls = append(b.RemoveKernelAssetsCalls, s)
+ return nil
+}
+
+// SetBootKernel sets the current boot kernel string. Should be
+// something like "pc-kernel_1234.snap".
+func (b *MockBootloader) SetBootKernel(kernel string) {
+ b.SetBootVars(map[string]string{"snap_kernel": kernel})
+}
+
+// SetBootBase sets the current boot base string. Should be something
+// like "core_1234.snap".
+func (b *MockBootloader) SetBootBase(base string) {
+ b.SetBootVars(map[string]string{"snap_core": base})
+}
diff -Nru snapd-2.41+19.10.1/bootloader/bootloader_test.go snapd-2.42.1+19.10/bootloader/bootloader_test.go
--- snapd-2.41+19.10.1/bootloader/bootloader_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/bootloader/bootloader_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -20,15 +20,16 @@
package bootloader_test
import (
+ "errors"
"io/ioutil"
"path/filepath"
"testing"
. "gopkg.in/check.v1"
- "github.com/snapcore/snapd/boot/boottest"
"github.com/snapcore/snapd/bootloader"
- "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/bootloader/bootloadertest"
+ //"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/testutil"
@@ -46,19 +47,20 @@
type baseBootenvTestSuite struct {
testutil.BaseTest
+
+ rootdir string
}
func (s *baseBootenvTestSuite) SetUpTest(c *C) {
s.BaseTest.SetUpTest(c)
s.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
- dirs.SetRootDir(c.MkDir())
- s.AddCleanup(func() { dirs.SetRootDir("") })
+ s.rootdir = c.MkDir()
}
type bootenvTestSuite struct {
baseBootenvTestSuite
- b *boottest.MockBootloader
+ b *bootloadertest.MockBootloader
}
var _ = Suite(&bootenvTestSuite{})
@@ -66,64 +68,30 @@
func (s *bootenvTestSuite) SetUpTest(c *C) {
s.baseBootenvTestSuite.SetUpTest(c)
- s.b = boottest.NewMockBootloader("mocky", c.MkDir())
+ s.b = bootloadertest.Mock("mocky", c.MkDir())
}
func (s *bootenvTestSuite) TestForceBootloader(c *C) {
bootloader.Force(s.b)
defer bootloader.Force(nil)
- got, err := bootloader.Find()
+ got, err := bootloader.Find("", nil)
c.Assert(err, IsNil)
c.Check(got, Equals, s.b)
}
-func (s *bootenvTestSuite) TestMarkBootSuccessfulAllSnap(c *C) {
- s.b.BootVars["snap_mode"] = "trying"
- s.b.BootVars["snap_try_core"] = "os1"
- s.b.BootVars["snap_try_kernel"] = "k1"
- err := bootloader.MarkBootSuccessful(s.b)
- c.Assert(err, IsNil)
-
- expected := map[string]string{
- // cleared
- "snap_mode": "",
- "snap_try_kernel": "",
- "snap_try_core": "",
- // updated
- "snap_kernel": "k1",
- "snap_core": "os1",
- }
- c.Assert(s.b.BootVars, DeepEquals, expected)
-
- // do it again, verify its still valid
- err = bootloader.MarkBootSuccessful(s.b)
- c.Assert(err, IsNil)
- c.Assert(s.b.BootVars, DeepEquals, expected)
-}
-
-func (s *bootenvTestSuite) TestMarkBootSuccessfulKKernelUpdate(c *C) {
- s.b.BootVars["snap_mode"] = "trying"
- s.b.BootVars["snap_core"] = "os1"
- s.b.BootVars["snap_kernel"] = "k1"
- s.b.BootVars["snap_try_core"] = ""
- s.b.BootVars["snap_try_kernel"] = "k2"
- err := bootloader.MarkBootSuccessful(s.b)
- c.Assert(err, IsNil)
- c.Assert(s.b.BootVars, DeepEquals, map[string]string{
- // cleared
- "snap_mode": "",
- "snap_try_kernel": "",
- "snap_try_core": "",
- // unchanged
- "snap_core": "os1",
- // updated
- "snap_kernel": "k2",
- })
+func (s *bootenvTestSuite) TestForceBootloaderError(c *C) {
+ myErr := errors.New("zap")
+ bootloader.ForceError(myErr)
+ defer bootloader.ForceError(nil)
+
+ got, err := bootloader.Find("", nil)
+ c.Assert(err, Equals, myErr)
+ c.Check(got, IsNil)
}
func (s *bootenvTestSuite) TestInstallBootloaderConfigNoConfig(c *C) {
- err := bootloader.InstallBootConfig(c.MkDir())
+ err := bootloader.InstallBootConfig(c.MkDir(), s.rootdir)
c.Assert(err, ErrorMatches, `cannot find boot config in.*`)
}
@@ -132,13 +100,14 @@
{"grub.conf", "/boot/grub/grub.cfg"},
{"uboot.conf", "/boot/uboot/uboot.env"},
{"androidboot.conf", "/boot/androidboot/androidboot.env"},
+ {"lk.conf", "/boot/lk/snapbootsel.bin"},
} {
mockGadgetDir := c.MkDir()
err := ioutil.WriteFile(filepath.Join(mockGadgetDir, t.gadgetFile), nil, 0644)
c.Assert(err, IsNil)
- err = bootloader.InstallBootConfig(mockGadgetDir)
+ err = bootloader.InstallBootConfig(mockGadgetDir, s.rootdir)
c.Assert(err, IsNil)
- fn := filepath.Join(dirs.GlobalRootDir, t.systemFile)
+ fn := filepath.Join(s.rootdir, t.systemFile)
c.Assert(osutil.FileExists(fn), Equals, true)
}
}
diff -Nru snapd-2.41+19.10.1/bootloader/export_test.go snapd-2.42.1+19.10/bootloader/export_test.go
--- snapd-2.41+19.10.1/bootloader/export_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/bootloader/export_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -25,28 +25,29 @@
. "gopkg.in/check.v1"
+ "github.com/snapcore/snapd/bootloader/lkenv"
"github.com/snapcore/snapd/bootloader/ubootenv"
)
// creates a new Androidboot bootloader object
-func NewAndroidBoot() Bootloader {
- return newAndroidBoot()
+func NewAndroidBoot(rootdir string) Bootloader {
+ return newAndroidBoot(rootdir)
}
-func MockAndroidBootFile(c *C, mode os.FileMode) {
- f := &androidboot{}
+func MockAndroidBootFile(c *C, rootdir string, mode os.FileMode) {
+ f := &androidboot{rootdir: rootdir}
err := os.MkdirAll(f.dir(), 0755)
c.Assert(err, IsNil)
err = ioutil.WriteFile(f.ConfigFile(), nil, mode)
c.Assert(err, IsNil)
}
-func NewUboot() Bootloader {
- return newUboot()
+func NewUboot(rootdir string) Bootloader {
+ return newUboot(rootdir)
}
-func MockUbootFiles(c *C) {
- u := &uboot{}
+func MockUbootFiles(c *C, rootdir string) {
+ u := &uboot{rootdir: rootdir}
err := os.MkdirAll(u.dir(), 0755)
c.Assert(err, IsNil)
@@ -57,14 +58,45 @@
c.Assert(err, IsNil)
}
-func NewGrub() Bootloader {
- return newGrub()
+func NewGrub(rootdir string) Bootloader {
+ return newGrub(rootdir)
}
-func MockGrubFiles(c *C) {
- g := &grub{}
+func MockGrubFiles(c *C, rootdir string) {
+ g := &grub{rootdir: rootdir}
err := os.MkdirAll(g.dir(), 0755)
c.Assert(err, IsNil)
err = ioutil.WriteFile(g.ConfigFile(), nil, 0644)
c.Assert(err, IsNil)
}
+
+func NewLk(rootdir string, opts *Options) Bootloader {
+ if opts == nil {
+ opts = &Options{}
+ }
+ return newLk(rootdir, opts)
+}
+
+func MockLkFiles(c *C, rootdir string, opts *Options) {
+ if opts == nil {
+ opts = &Options{}
+ }
+ l := &lk{rootdir: rootdir, inRuntimeMode: !opts.PrepareImageTime}
+ err := os.MkdirAll(l.dir(), 0755)
+ c.Assert(err, IsNil)
+
+ // first create empty env file
+ buf := make([]byte, 4096)
+ err = ioutil.WriteFile(l.envFile(), buf, 0660)
+ c.Assert(err, IsNil)
+ // now write env in it with correct crc
+ env := lkenv.NewEnv(l.envFile())
+ env.ConfigureBootPartitions("boot_a", "boot_b")
+ err = env.Save()
+ c.Assert(err, IsNil)
+}
+
+func LkRuntimeMode(b Bootloader) bool {
+ lk := b.(*lk)
+ return lk.inRuntimeMode
+}
diff -Nru snapd-2.41+19.10.1/bootloader/grub.go snapd-2.42.1+19.10/bootloader/grub.go
--- snapd-2.41+19.10.1/bootloader/grub.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/bootloader/grub.go 2019-10-30 12:17:43.000000000 +0000
@@ -24,16 +24,17 @@
"path/filepath"
"github.com/snapcore/snapd/bootloader/grubenv"
- "github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/snap"
)
-type grub struct{}
+type grub struct {
+ rootdir string
+}
// newGrub create a new Grub bootloader object
-func newGrub() Bootloader {
- g := &grub{}
+func newGrub(rootdir string) Bootloader {
+ g := &grub{rootdir: rootdir}
if !osutil.FileExists(g.ConfigFile()) {
return nil
}
@@ -45,8 +46,15 @@
return "grub"
}
+func (g *grub) setRootDir(rootdir string) {
+ g.rootdir = rootdir
+}
+
func (g *grub) dir() string {
- return filepath.Join(dirs.GlobalRootDir, "/boot/grub")
+ if g.rootdir == "" {
+ panic("internal error: unset rootdir")
+ }
+ return filepath.Join(g.rootdir, "/boot/grub")
}
func (g *grub) ConfigFile() string {
@@ -83,7 +91,7 @@
return env.Save()
}
-func (g *grub) ExtractKernelAssets(s *snap.Info, snapf snap.Container) error {
+func (g *grub) ExtractKernelAssets(s snap.PlaceInfo, snapf snap.Container) error {
// XXX: should we use "kernel.yaml" for this?
if _, err := snapf.ReadFile("meta/force-kernel-extraction"); err == nil {
return extractKernelAssetsToBootDir(g.dir(), s, snapf)
diff -Nru snapd-2.41+19.10.1/bootloader/grub_test.go snapd-2.42.1+19.10/bootloader/grub_test.go
--- snapd-2.41+19.10.1/bootloader/grub_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/bootloader/grub_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -44,9 +44,9 @@
func (s *grubTestSuite) SetUpTest(c *C) {
s.baseBootenvTestSuite.SetUpTest(c)
- bootloader.MockGrubFiles(c)
+ bootloader.MockGrubFiles(c, s.rootdir)
- s.bootdir = filepath.Join(dirs.GlobalRootDir, "boot")
+ s.bootdir = filepath.Join(s.rootdir, "boot")
}
// grubEditenvCmd finds the right grub{,2}-editenv command
@@ -59,25 +59,25 @@
return ""
}
-func grubEnvPath() string {
- return filepath.Join(dirs.GlobalRootDir, "boot/grub/grubenv")
+func grubEnvPath(rootdir string) string {
+ return filepath.Join(rootdir, "boot/grub/grubenv")
}
-func grubEditenvSet(c *C, key, value string) {
+func (s *grubTestSuite) grubEditenvSet(c *C, key, value string) {
if grubEditenvCmd() == "" {
c.Skip("grub{,2}-editenv is not available")
}
- err := exec.Command(grubEditenvCmd(), grubEnvPath(), "set", fmt.Sprintf("%s=%s", key, value)).Run()
+ err := exec.Command(grubEditenvCmd(), grubEnvPath(s.rootdir), "set", fmt.Sprintf("%s=%s", key, value)).Run()
c.Assert(err, IsNil)
}
-func grubEditenvGet(c *C, key string) string {
+func (s *grubTestSuite) grubEditenvGet(c *C, key string) string {
if grubEditenvCmd() == "" {
c.Skip("grub{,2}-editenv is not available")
}
- output, err := exec.Command(grubEditenvCmd(), grubEnvPath(), "list").CombinedOutput()
+ output, err := exec.Command(grubEditenvCmd(), grubEnvPath(s.rootdir), "list").CombinedOutput()
c.Assert(err, IsNil)
cfg := goconfigparser.New()
cfg.AllowNoSectionHeader = true
@@ -89,20 +89,18 @@
}
func (s *grubTestSuite) makeFakeGrubEnv(c *C) {
- grubEditenvSet(c, "k", "v")
+ s.grubEditenvSet(c, "k", "v")
}
func (s *grubTestSuite) TestNewGrubNoGrubReturnsNil(c *C) {
- dirs.GlobalRootDir = "/something/not/there"
-
- g := bootloader.NewGrub()
+ g := bootloader.NewGrub("/something/not/there")
c.Assert(g, IsNil)
}
func (s *grubTestSuite) TestNewGrub(c *C) {
s.makeFakeGrubEnv(c)
- g := bootloader.NewGrub()
+ g := bootloader.NewGrub(s.rootdir)
c.Assert(g, NotNil)
c.Assert(g.Name(), Equals, "grub")
}
@@ -110,16 +108,27 @@
func (s *grubTestSuite) TestGetBootloaderWithGrub(c *C) {
s.makeFakeGrubEnv(c)
- bootloader, err := bootloader.Find()
+ bootloader, err := bootloader.Find(s.rootdir, nil)
+ c.Assert(err, IsNil)
+ c.Assert(bootloader.Name(), Equals, "grub")
+}
+
+func (s *grubTestSuite) TestGetBootloaderWithGrubWithDefaultRoot(c *C) {
+ s.makeFakeGrubEnv(c)
+
+ dirs.SetRootDir(s.rootdir)
+ defer func() { dirs.SetRootDir("") }()
+
+ bootloader, err := bootloader.Find("", nil)
c.Assert(err, IsNil)
c.Assert(bootloader.Name(), Equals, "grub")
}
func (s *grubTestSuite) TestGetBootVer(c *C) {
s.makeFakeGrubEnv(c)
- grubEditenvSet(c, "snap_mode", "regular")
+ s.grubEditenvSet(c, "snap_mode", "regular")
- g := bootloader.NewGrub()
+ g := bootloader.NewGrub(s.rootdir)
v, err := g.GetBootVars("snap_mode")
c.Assert(err, IsNil)
c.Check(v, HasLen, 1)
@@ -129,21 +138,21 @@
func (s *grubTestSuite) TestSetBootVer(c *C) {
s.makeFakeGrubEnv(c)
- g := bootloader.NewGrub()
+ g := bootloader.NewGrub(s.rootdir)
err := g.SetBootVars(map[string]string{
"k1": "v1",
"k2": "v2",
})
c.Assert(err, IsNil)
- c.Check(grubEditenvGet(c, "k1"), Equals, "v1")
- c.Check(grubEditenvGet(c, "k2"), Equals, "v2")
+ c.Check(s.grubEditenvGet(c, "k1"), Equals, "v1")
+ c.Check(s.grubEditenvGet(c, "k2"), Equals, "v2")
}
func (s *grubTestSuite) TestExtractKernelAssetsNoUnpacksKernelForGrub(c *C) {
s.makeFakeGrubEnv(c)
- g := bootloader.NewGrub()
+ g := bootloader.NewGrub(s.rootdir)
files := [][]string{
{"kernel.img", "I'm a kernel"},
@@ -172,7 +181,7 @@
func (s *grubTestSuite) TestExtractKernelForceWorks(c *C) {
s.makeFakeGrubEnv(c)
- g := bootloader.NewGrub()
+ g := bootloader.NewGrub(s.rootdir)
c.Assert(g, NotNil)
files := [][]string{
diff -Nru snapd-2.41+19.10.1/bootloader/lkenv/export_test.go snapd-2.42.1+19.10/bootloader/lkenv/export_test.go
--- snapd-2.41+19.10.1/bootloader/lkenv/export_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/bootloader/lkenv/export_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,25 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package lkenv
+
+var (
+ CopyString = copyString
+ CToGoString = cToGoString
+)
diff -Nru snapd-2.41+19.10.1/bootloader/lkenv/lkenv.go snapd-2.42.1+19.10/bootloader/lkenv/lkenv.go
--- snapd-2.41+19.10.1/bootloader/lkenv/lkenv.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/bootloader/lkenv/lkenv.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,428 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package lkenv
+
+import (
+ "bytes"
+ "encoding/binary"
+ "fmt"
+ "hash/crc32"
+ "os"
+
+ "github.com/snapcore/snapd/logger"
+ "github.com/snapcore/snapd/osutil"
+)
+
+const SNAP_BOOTSELECT_VERSION = 0x00010001
+
+// const SNAP_BOOTSELECT_SIGNATURE ('S' | ('B' << 8) | ('s' << 16) | ('e' << 24))
+const SNAP_BOOTSELECT_SIGNATURE = 0x53 | 0x42<<8 | 0x73<<16 | 0x65<<24
+const SNAP_NAME_MAX_LEN = 256
+
+/* number of available boot partitions */
+const SNAP_BOOTIMG_PART_NUM = 2
+
+/* Default boot image file name to be used from kernel snap */
+const BOOTIMG_DEFAULT_NAME = "boot.img"
+
+// for accessing the Bootimg_matrix
+const (
+ MATRIX_ROW_PARTITION = 0
+ MATRIX_ROW_KERNEL = 1
+)
+
+/**
+ * Following structure has to be kept in sync with c structure defined by
+ * include/snappy-boot_v1.h
+ * c headerfile is used by bootloader, this ensures sync of the environment
+ * between snapd and bootloader
+
+ * when this structure needs to be updated,
+ * new version should be introduced instead together with c header file,
+ * which is to be adopted by bootloader
+ *
+ * !!! Support for old version has to be maintained, as it is not guaranteed
+ * all existing bootloader would adopt new version!
+ */
+type SnapBootSelect_v1 struct {
+ /* Contains value BOOTSELECT_SIGNATURE defined above */
+ Signature uint32
+ /* snappy boot select version */
+ Version uint32
+
+ /* snap_mode, one of: 'empty', "try", "trying" */
+ Snap_mode [SNAP_NAME_MAX_LEN]byte
+ /* current core snap revision */
+ Snap_core [SNAP_NAME_MAX_LEN]byte
+ /* try core snap revision */
+ Snap_try_core [SNAP_NAME_MAX_LEN]byte
+ /* current kernel snap revision */
+ Snap_kernel [SNAP_NAME_MAX_LEN]byte
+ /* current kernel snap revision */
+ Snap_try_kernel [SNAP_NAME_MAX_LEN]byte
+
+ /* gadget_mode, one of: 'empty', "try", "trying" */
+ Gadget_mode [SNAP_NAME_MAX_LEN]byte
+ /* GADGET assets: current gadget assets revision */
+ Snap_gadget [SNAP_NAME_MAX_LEN]byte
+ /* GADGET assets: try gadget assets revision */
+ Snap_try_gadget [SNAP_NAME_MAX_LEN]byte
+
+ /**
+ * Reboot reason
+ * optional parameter to signal bootloader alternative reboot reasons
+ * e.g. recovery/factory-reset/boot asset update
+ */
+ Reboot_reason [SNAP_NAME_MAX_LEN]byte
+
+ /**
+ * Matrix for mapping of boot img partion to installed kernel snap revision
+ *
+ * First column represents boot image partition label (e.g. boot_a,boot_b )
+ * value are static and should be populated at gadget built time
+ * or latest at image build time. Values are not further altered at run time.
+ * Second column represents name currently installed kernel snap
+ * e.g. pi2-kernel_123.snap
+ * initial value representing initial kernel snap revision
+ * is pupulated at image build time by snapd
+ *
+ * There are two rows in the matrix, representing current and previous kernel revision
+ * following describes how this matrix should be modified at different stages:
+ * - at image build time:
+ * - extracted kernel snap revision name should be filled
+ * into free slow (first row, second row)
+ * - snapd:
+ * - when new kernel snap revision is being installed, snapd cycles through
+ * matrix to find unused 'boot slot' to be used for new kernel snap revision
+ * from free slot, first column represents partition label to which kernel
+ * snap boot image should be extracted. Second column is then populated with
+ * kernel snap revision name.
+ * - snap_mode, snap_try_kernel, snap_try_core behaves same way as with u-boot
+ * - bootloader:
+ * - bootloader reads snap_mode to determine if snap_kernel or snap_kernel is used
+ * to get kernel snap revision name
+ * kernel snap revision is then used to search matrix to determine
+ * partition label to be used for current boot
+ * - bootloader NEVER alters this matrix values
+ *
+ * [ ] [ ]
+ * [ ] [ ]
+ */
+ Bootimg_matrix [SNAP_BOOTIMG_PART_NUM][2][SNAP_NAME_MAX_LEN]byte
+
+ /**
+ * name of the boot image from kernel snap to be used for extraction
+ * when not defined or empty, default boot.img will be used
+ */
+ Bootimg_file_name [SNAP_NAME_MAX_LEN]byte
+
+ /**
+ * gadget assets: Matrix for mapping of gadget asset partions
+ * Optional boot asset tracking, based on bootloader support
+ * Some boot chains support A/B boot assets for increased robustness
+ * example being A/B TrustExecutionEnvironment
+ * This matrix can be used to track current and try boot assets for
+ * robust updates
+ * Use of Gadget_asset_matrix matches use of Bootimg_matrix
+ *
+ * [ ] [ ]
+ * [ ] [ ]
+ */
+ Gadget_asset_matrix [SNAP_BOOTIMG_PART_NUM][2][SNAP_NAME_MAX_LEN]byte
+
+ /* unused placeholders for additional parameters in the future */
+ Unused_key_01 [SNAP_NAME_MAX_LEN]byte
+ Unused_key_02 [SNAP_NAME_MAX_LEN]byte
+ Unused_key_03 [SNAP_NAME_MAX_LEN]byte
+ Unused_key_04 [SNAP_NAME_MAX_LEN]byte
+ Unused_key_05 [SNAP_NAME_MAX_LEN]byte
+ Unused_key_06 [SNAP_NAME_MAX_LEN]byte
+ Unused_key_07 [SNAP_NAME_MAX_LEN]byte
+ Unused_key_08 [SNAP_NAME_MAX_LEN]byte
+ Unused_key_09 [SNAP_NAME_MAX_LEN]byte
+ Unused_key_10 [SNAP_NAME_MAX_LEN]byte
+ Unused_key_11 [SNAP_NAME_MAX_LEN]byte
+ Unused_key_12 [SNAP_NAME_MAX_LEN]byte
+ Unused_key_13 [SNAP_NAME_MAX_LEN]byte
+ Unused_key_14 [SNAP_NAME_MAX_LEN]byte
+ Unused_key_15 [SNAP_NAME_MAX_LEN]byte
+ Unused_key_16 [SNAP_NAME_MAX_LEN]byte
+ Unused_key_17 [SNAP_NAME_MAX_LEN]byte
+ Unused_key_18 [SNAP_NAME_MAX_LEN]byte
+ Unused_key_19 [SNAP_NAME_MAX_LEN]byte
+ Unused_key_20 [SNAP_NAME_MAX_LEN]byte
+
+ /* unused array of 10 key value pairs */
+ Kye_value_pairs [10][2][SNAP_NAME_MAX_LEN]byte
+
+ /* crc32 value for structure */
+ Crc32 uint32
+}
+
+// Env contains the data of the uboot environment
+// path can be file or partition device node
+type Env struct {
+ path string
+ pathbak string
+ env SnapBootSelect_v1
+}
+
+// cToGoString convert string in passed byte array into string type
+// if string in byte array is not terminated, empty string is returned
+func cToGoString(c []byte) string {
+ if end := bytes.IndexByte(c, 0); end >= 0 {
+ return string(c[:end])
+ }
+ // no trailing \0 - return ""
+ return ""
+}
+
+// copyString copy passed string into byte array
+// make sure string is terminated
+// if string does not fit into byte array, it will be concatenated
+func copyString(b []byte, s string) {
+ sl := len(s)
+ bs := len(b)
+ if bs > sl {
+ copy(b[:], s)
+ b[sl] = 0
+ } else {
+ copy(b[:bs-1], s)
+ b[bs-1] = 0
+ }
+}
+
+func NewEnv(path string) *Env {
+ // osutil.FileExists(path + "bak")
+ return &Env{
+ path: path,
+ pathbak: path + "bak",
+ env: SnapBootSelect_v1{
+ Signature: SNAP_BOOTSELECT_SIGNATURE,
+ Version: SNAP_BOOTSELECT_VERSION,
+ },
+ }
+}
+
+func (l *Env) Get(key string) string {
+ switch key {
+ case "snap_mode":
+ return cToGoString(l.env.Snap_mode[:])
+ case "snap_kernel":
+ return cToGoString(l.env.Snap_kernel[:])
+ case "snap_try_kernel":
+ return cToGoString(l.env.Snap_try_kernel[:])
+ case "snap_core":
+ return cToGoString(l.env.Snap_core[:])
+ case "snap_try_core":
+ return cToGoString(l.env.Snap_try_core[:])
+ case "snap_gadget":
+ return cToGoString(l.env.Snap_gadget[:])
+ case "snap_try_gadget":
+ return cToGoString(l.env.Snap_try_gadget[:])
+ case "reboot_reason":
+ return cToGoString(l.env.Reboot_reason[:])
+ case "bootimg_file_name":
+ return cToGoString(l.env.Bootimg_file_name[:])
+ }
+ return ""
+}
+
+func (l *Env) Set(key, value string) {
+ switch key {
+ case "snap_mode":
+ copyString(l.env.Snap_mode[:], value)
+ case "snap_kernel":
+ copyString(l.env.Snap_kernel[:], value)
+ case "snap_try_kernel":
+ copyString(l.env.Snap_try_kernel[:], value)
+ case "snap_core":
+ copyString(l.env.Snap_core[:], value)
+ case "snap_try_core":
+ copyString(l.env.Snap_try_core[:], value)
+ case "snap_gadget":
+ copyString(l.env.Snap_gadget[:], value)
+ case "snap_try_gadget":
+ copyString(l.env.Snap_try_gadget[:], value)
+ case "reboot_reason":
+ copyString(l.env.Reboot_reason[:], value)
+ case "bootimg_file_name":
+ copyString(l.env.Bootimg_file_name[:], value)
+ }
+}
+
+// ConfigureBootPartitions set boot partitions label names
+// this function should not be used at run time!
+// it should be used only at image build time,
+// if partition labels are not pre-filled by gadget built
+func (l *Env) ConfigureBootPartitions(boot_1, boot_2 string) {
+ copyString(l.env.Bootimg_matrix[0][MATRIX_ROW_PARTITION][:], boot_1)
+ copyString(l.env.Bootimg_matrix[1][MATRIX_ROW_PARTITION][:], boot_2)
+}
+
+// ConfigureBootimgName set boot image file name
+// boot image file name is used at kernel extraction time
+// this function should not be used at run time!
+// it should be used only at image build time
+// if default boot.img is not set by gadget built
+func (l *Env) ConfigureBootimgName(bootimgName string) {
+ copyString(l.env.Bootimg_file_name[:], bootimgName)
+}
+
+func (l *Env) Load() error {
+ err := l.LoadEnv(l.path)
+ if err != nil {
+ return l.LoadEnv(l.pathbak)
+ }
+ return nil
+}
+
+func (l *Env) LoadEnv(path string) error {
+ f, err := os.Open(path)
+ if err != nil {
+ return fmt.Errorf("cannot open LK env file: %v", err)
+ }
+
+ defer f.Close()
+ if err := binary.Read(f, binary.LittleEndian, &l.env); err != nil {
+ return fmt.Errorf("cannot read LK env from file: %v", err)
+ }
+
+ // calculate crc32 to validate structure
+ w := bytes.NewBuffer(nil)
+ ss := binary.Size(l.env)
+ w.Grow(ss)
+ if err := binary.Write(w, binary.LittleEndian, &l.env); err != nil {
+ return fmt.Errorf("cannot write LK env to buffer for validation: %v", err)
+ }
+ if l.env.Version != SNAP_BOOTSELECT_VERSION || l.env.Signature != SNAP_BOOTSELECT_SIGNATURE {
+ return fmt.Errorf("cannot validate version/signature for %s, got 0x%X expected 0x%X, got 0x%X expected 0x%X\n", path, l.env.Version, SNAP_BOOTSELECT_VERSION, l.env.Signature, SNAP_BOOTSELECT_SIGNATURE)
+ }
+
+ crc := crc32.ChecksumIEEE(w.Bytes()[:ss-4]) // size of crc32 itself at the end of the structure
+ if crc != l.env.Crc32 {
+ return fmt.Errorf("cannot validate environment checksum %s, got 0x%X expected 0x%X\n", path, crc, l.env.Crc32)
+ }
+ logger.Debugf("Load: validated crc32 (0x%X)", l.env.Crc32)
+ return nil
+}
+
+func (l *Env) Save() error {
+ logger.Debugf("Save")
+ w := bytes.NewBuffer(nil)
+ ss := binary.Size(l.env)
+ w.Grow(ss)
+ if err := binary.Write(w, binary.LittleEndian, &l.env); err != nil {
+ return fmt.Errorf("cannot write LK env to buffer for saving: %v", err)
+ }
+ // calculate crc32
+ l.env.Crc32 = crc32.ChecksumIEEE(w.Bytes()[:ss-4])
+ logger.Debugf("Save: calculated crc32 (0x%X)", l.env.Crc32)
+ w.Truncate(ss - 4)
+ binary.Write(w, binary.LittleEndian, &l.env.Crc32)
+
+ err := l.SaveEnv(l.path, w)
+ if err != nil {
+ logger.Debugf("Save: failed to save main environment")
+ }
+ // if there is backup environment file save to it as well
+ if osutil.FileExists(l.pathbak) {
+ if err := l.SaveEnv(l.pathbak, w); err != nil {
+ logger.Debugf("Save: failed to save backup environment %v", err)
+ }
+ }
+ return err
+}
+
+func (l *Env) SaveEnv(path string, buf *bytes.Buffer) error {
+ f, err := os.OpenFile(path, os.O_WRONLY, 0660)
+ if err != nil {
+ return fmt.Errorf("cannot open LK env file for env storing: %v", err)
+ }
+ defer f.Close()
+
+ if _, err := f.Write(buf.Bytes()); err != nil {
+ return fmt.Errorf("cannot write LK env buf to LK env file: %v", err)
+ }
+ if err := f.Sync(); err != nil {
+ return fmt.Errorf("cannot sync LK env file: %v", err)
+ }
+ return nil
+}
+
+// FindFreeBootPartition find free boot partition to be used for new kernel revision
+// - consider kernel snap blob name, if kernel name matches
+// already installed revision, return coresponding partition name
+// - protect partition used by kernel_snap, consider other as free
+// - consider only boot partitions with defined partition name
+func (l *Env) FindFreeBootPartition(kernel string) (string, error) {
+ for x := range l.env.Bootimg_matrix {
+ bp := cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_PARTITION][:])
+ if bp != "" {
+ k := cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_KERNEL][:])
+ if k != cToGoString(l.env.Snap_kernel[:]) || k == kernel || k == "" {
+ return cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_PARTITION][:]), nil
+ }
+ }
+ }
+ return "", fmt.Errorf("cannot find free partition for boot image")
+}
+
+// SetBootPartition set kernel revision name to passed boot partition
+func (l *Env) SetBootPartition(bootpart, kernel string) error {
+ for x := range l.env.Bootimg_matrix {
+ if bootpart == cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_PARTITION][:]) {
+ copyString(l.env.Bootimg_matrix[x][MATRIX_ROW_KERNEL][:], kernel)
+ return nil
+ }
+ }
+ return fmt.Errorf("cannot find defined [%s] boot image partition", bootpart)
+}
+
+func (l *Env) GetBootPartition(kernel string) (string, error) {
+ for x := range l.env.Bootimg_matrix {
+ if kernel == cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_KERNEL][:]) {
+ return cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_PARTITION][:]), nil
+ }
+ }
+ return "", fmt.Errorf("cannot find kernel %q in boot image partitions", kernel)
+}
+
+// FreeBootPartition free passed kernel revision from any boot partition
+// ignore if there is no boot partition with given kernel revision
+func (l *Env) FreeBootPartition(kernel string) (bool, error) {
+ for x := range l.env.Bootimg_matrix {
+ if "" != cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_PARTITION][:]) {
+ if kernel == cToGoString(l.env.Bootimg_matrix[x][MATRIX_ROW_KERNEL][:]) {
+ l.env.Bootimg_matrix[x][1][MATRIX_ROW_PARTITION] = 0
+ return true, nil
+ }
+ }
+ }
+ return false, fmt.Errorf("cannot find defined [%s] boot image partition", kernel)
+}
+
+// GetBootImageName return expected boot image file name in kernel snap
+func (l *Env) GetBootImageName() string {
+ if "" != cToGoString(l.env.Bootimg_file_name[:]) {
+ return cToGoString(l.env.Bootimg_file_name[:])
+ }
+ return BOOTIMG_DEFAULT_NAME
+}
diff -Nru snapd-2.41+19.10.1/bootloader/lkenv/lkenv_test.go snapd-2.42.1+19.10/bootloader/lkenv/lkenv_test.go
--- snapd-2.41+19.10.1/bootloader/lkenv/lkenv_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/bootloader/lkenv/lkenv_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,336 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package lkenv_test
+
+import (
+ "bytes"
+ "compress/gzip"
+ . "gopkg.in/check.v1"
+ "io"
+ "io/ioutil"
+ "path/filepath"
+ "testing"
+
+ "github.com/snapcore/snapd/bootloader/lkenv"
+)
+
+// Hook up check.v1 into the "go test" runner
+func Test(t *testing.T) { TestingT(t) }
+
+type lkenvTestSuite struct {
+ envPath string
+ envPathbak string
+}
+
+var _ = Suite(&lkenvTestSuite{})
+
+func (l *lkenvTestSuite) SetUpTest(c *C) {
+ l.envPath = filepath.Join(c.MkDir(), "snapbootsel.bin")
+ l.envPathbak = l.envPath + "bak"
+}
+
+// unpack test data packed with gzip
+func unpackTestData(data []byte) (resData []byte, err error) {
+ b := bytes.NewBuffer(data)
+ var r io.Reader
+ r, err = gzip.NewReader(b)
+ if err != nil {
+ return
+ }
+ var env bytes.Buffer
+ _, err = env.ReadFrom(r)
+ if err != nil {
+ return
+ }
+ return env.Bytes(), nil
+}
+
+func (l *lkenvTestSuite) TestSet(c *C) {
+ env := lkenv.NewEnv(l.envPath)
+ c.Check(env, NotNil)
+
+ env.Set("snap_mode", "try")
+ c.Check(env.Get("snap_mode"), Equals, "try")
+}
+
+func (l *lkenvTestSuite) TestSave(c *C) {
+ buf := make([]byte, 4096)
+ err := ioutil.WriteFile(l.envPathbak, buf, 0644)
+ c.Assert(err, IsNil)
+ l.TestSaveNoBak(c)
+}
+
+func (l *lkenvTestSuite) TestCtoGoString(c *C) {
+ for _, t := range []struct {
+ input []byte
+ expected string
+ }{
+ {[]byte{0, 0, 0, 0, 0}, ""},
+ {[]byte{'a', 0, 0, 0, 0}, "a"},
+ {[]byte{'a', 'b', 0, 0, 0}, "ab"},
+ {[]byte{'a', 'b', 'c', 0, 0}, "abc"},
+ {[]byte{'a', 'b', 'c', 'd', 0}, "abcd"},
+ // no trailing \0 - assume corrupted "" ?
+ {[]byte{'a', 'b', 'c', 'd', 'e'}, ""},
+ // first \0 is the cutof
+ {[]byte{'a', 'b', 0, 'z', 0}, "ab"},
+ } {
+ c.Check(lkenv.CToGoString(t.input), Equals, t.expected)
+ }
+
+}
+
+func (l *lkenvTestSuite) TestCopyStringHappy(c *C) {
+ for _, t := range []struct {
+ input string
+ expected []byte
+ }{
+ // input up to the size of the buffer works
+ {"", []byte{0, 0, 0, 0, 0}},
+ {"a", []byte{'a', 0, 0, 0, 0}},
+ {"ab", []byte{'a', 'b', 0, 0, 0}},
+ {"abc", []byte{'a', 'b', 'c', 0, 0}},
+ {"abcd", []byte{'a', 'b', 'c', 'd', 0}},
+ // only what fit is copied
+ {"abcde", []byte{'a', 'b', 'c', 'd', 0}},
+ {"abcdef", []byte{'a', 'b', 'c', 'd', 0}},
+ // strange embedded stuff works
+ {"ab\000z", []byte{'a', 'b', 0, 'z', 0}},
+ } {
+ b := make([]byte, 5)
+ lkenv.CopyString(b, t.input)
+ c.Check(b, DeepEquals, t.expected)
+ }
+}
+
+func (l *lkenvTestSuite) TestCopyStringNoPanic(c *C) {
+ // too long, string should get concatenate
+ b := make([]byte, 5)
+ defer lkenv.CopyString(b, "12345")
+ c.Assert(recover(), IsNil)
+ defer lkenv.CopyString(b, "123456")
+ c.Assert(recover(), IsNil)
+}
+
+func (l *lkenvTestSuite) TestSaveNoBak(c *C) {
+ buf := make([]byte, 4096)
+ err := ioutil.WriteFile(l.envPath, buf, 0644)
+ c.Assert(err, IsNil)
+
+ env := lkenv.NewEnv(l.envPath)
+ c.Check(env, NotNil)
+
+ env.Set("snap_mode", "trying")
+ env.Set("snap_kernel", "kernel-1")
+ env.Set("snap_try_kernel", "kernel-2")
+ env.Set("snap_core", "core-1")
+ env.Set("snap_try_core", "core-2")
+ env.Set("snap_gadget", "gadget-1")
+ env.Set("snap_try_gadget", "gadget-2")
+ env.Set("bootimg_file_name", "boot.img")
+
+ err = env.Save()
+ c.Assert(err, IsNil)
+
+ env2 := lkenv.NewEnv(l.envPath)
+ err = env2.Load()
+ c.Assert(err, IsNil)
+ c.Check(env2.Get("snap_mode"), Equals, "trying")
+ c.Check(env2.Get("snap_kernel"), Equals, "kernel-1")
+ c.Check(env2.Get("snap_try_kernel"), Equals, "kernel-2")
+ c.Check(env2.Get("snap_core"), Equals, "core-1")
+ c.Check(env2.Get("snap_try_core"), Equals, "core-2")
+ c.Check(env2.Get("snap_gadget"), Equals, "gadget-1")
+ c.Check(env2.Get("snap_try_gadget"), Equals, "gadget-2")
+ c.Check(env2.Get("bootimg_file_name"), Equals, "boot.img")
+}
+
+func (l *lkenvTestSuite) TestFailedCRC(c *C) {
+ buf := make([]byte, 4096)
+ err := ioutil.WriteFile(l.envPathbak, buf, 0644)
+ c.Assert(err, IsNil)
+ l.TestFailedCRCNoBak(c)
+}
+
+func (l *lkenvTestSuite) TestFailedCRCNoBak(c *C) {
+ buf := make([]byte, 4096)
+ err := ioutil.WriteFile(l.envPath, buf, 0644)
+ c.Assert(err, IsNil)
+
+ env := lkenv.NewEnv(l.envPath)
+ c.Check(env, NotNil)
+
+ err = env.Load()
+ c.Assert(err, NotNil)
+}
+
+func (l *lkenvTestSuite) TestFailedCRCFallBack(c *C) {
+ buf := make([]byte, 4096)
+ err := ioutil.WriteFile(l.envPath, buf, 0644)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(l.envPathbak, buf, 0644)
+ c.Assert(err, IsNil)
+
+ env := lkenv.NewEnv(l.envPath)
+ c.Check(env, NotNil)
+
+ env.Set("snap_mode", "trying")
+ env.Set("snap_kernel", "kernel-1")
+ env.Set("snap_try_kernel", "kernel-2")
+ err = env.Save()
+ c.Assert(err, IsNil)
+
+ // break main env file
+ err = ioutil.WriteFile(l.envPath, buf, 0644)
+ c.Assert(err, IsNil)
+
+ env2 := lkenv.NewEnv(l.envPath)
+ err = env2.Load()
+ c.Assert(err, IsNil)
+ c.Check(env2.Get("snap_mode"), Equals, "trying")
+ c.Check(env2.Get("snap_kernel"), Equals, "kernel-1")
+ c.Check(env2.Get("snap_try_kernel"), Equals, "kernel-2")
+}
+
+func (l *lkenvTestSuite) TestGetBootPartition(c *C) {
+ buf := make([]byte, 4096)
+ err := ioutil.WriteFile(l.envPath, buf, 0644)
+ c.Assert(err, IsNil)
+
+ env := lkenv.NewEnv(l.envPath)
+ c.Assert(err, IsNil)
+ env.ConfigureBootPartitions("boot_a", "boot_b")
+ // test no boot partition used
+ p, err := env.FindFreeBootPartition("kernel-1")
+ c.Check(p, Equals, "boot_a")
+ c.Assert(err, IsNil)
+ // set kernel-2 to boot_a partition
+ err = env.SetBootPartition("boot_a", "kernel-1")
+ c.Assert(err, IsNil)
+ // set kernel-2 to boot_a partition
+ err = env.SetBootPartition("boot_b", "kernel-2")
+ c.Assert(err, IsNil)
+
+ // 'boot_a' has 'kernel-1' revision
+ p, err = env.GetBootPartition("kernel-1")
+ c.Check(p, Equals, "boot_a")
+ c.Assert(err, IsNil)
+ // 'boot_b' has 'kernel-2' revision
+ p, err = env.GetBootPartition("kernel-2")
+ c.Check(p, Equals, "boot_b")
+ c.Assert(err, IsNil)
+}
+
+func (l *lkenvTestSuite) TestFindFree_Set_Free_BootPartition(c *C) {
+ buf := make([]byte, 4096)
+ err := ioutil.WriteFile(l.envPath, buf, 0644)
+ c.Assert(err, IsNil)
+
+ env := lkenv.NewEnv(l.envPath)
+ c.Assert(err, IsNil)
+ env.ConfigureBootPartitions("boot_a", "boot_b")
+ // test no boot partition used
+ p, err := env.FindFreeBootPartition("kernel-1")
+ c.Check(p, Equals, "boot_a")
+ c.Assert(err, IsNil)
+ // set kernel-2 to boot_a partition
+ err = env.SetBootPartition("boot_a", "kernel-2")
+ c.Assert(err, IsNil)
+
+ env.Set("snap_kernel", "kernel-2")
+ // kernel-2 should now return first part, as it's already there
+ p, err = env.FindFreeBootPartition("kernel-2")
+ c.Check(p, Equals, "boot_a")
+ c.Assert(err, IsNil)
+ // test kernel-1 snapd, it should now offer second partition
+ p, err = env.FindFreeBootPartition("kernel-1")
+ c.Check(p, Equals, "boot_b")
+ c.Assert(err, IsNil)
+ err = env.SetBootPartition("boot_b", "kernel-1")
+ c.Assert(err, IsNil)
+ // set boot kernel-1
+ env.Set("snap_kernel", "kernel-1")
+ // now kernel-2 should not be protected and boot_a shoild be offered
+ p, err = env.FindFreeBootPartition("kernel-3")
+ c.Check(p, Equals, "boot_a")
+ c.Assert(err, IsNil)
+ err = env.SetBootPartition("boot_a", "kernel-3")
+ c.Assert(err, IsNil)
+ // remove kernel
+ used, err := env.FreeBootPartition("kernel-3")
+ c.Assert(err, IsNil)
+ c.Check(used, Equals, true)
+ // repeated use should return false and error
+ used, err = env.FreeBootPartition("kernel-3")
+ c.Assert(err, NotNil)
+ c.Check(used, Equals, false)
+}
+
+func (l *lkenvTestSuite) TestZippedDataSample(c *C) {
+ // test data is generated with gadget build helper tool:
+ // $ parts/snap-boot-sel-env/build/lk-boot-env -w test.bin
+ // --snap-mode="trying" --snap-kernel="kernel-1" --snap-try-kernel="kernel-2"
+ // --snap-core="core-1" --snap-try-core="core-2" --reboot-reason=""
+ // --boot-0-part="boot_a" --boot-1-part="boot_b" --boot-0-snap="kernel-1"
+ // --boot-1-snap="kernel-3" --bootimg-file="boot.img"
+ // $ cat test.bin | gzip | xxd -i
+ gzipedData := []byte{
+ 0x1f, 0x8b, 0x08, 0x00, 0x95, 0x88, 0x77, 0x5d, 0x00, 0x03, 0xed, 0xd7,
+ 0xc1, 0x09, 0xc2, 0x40, 0x10, 0x05, 0xd0, 0xa4, 0x20, 0x05, 0x63, 0x07,
+ 0x96, 0xa0, 0x05, 0x88, 0x91, 0x25, 0x04, 0x35, 0x0b, 0x6b, 0x2e, 0x1e,
+ 0xac, 0xcb, 0xf6, 0xc4, 0x90, 0x1e, 0x06, 0xd9, 0xf7, 0x2a, 0xf8, 0xc3,
+ 0x1f, 0x18, 0xe6, 0x74, 0x78, 0xa6, 0xb6, 0x69, 0x9b, 0xb9, 0xbc, 0xc6,
+ 0x69, 0x68, 0xaa, 0x75, 0xcd, 0x25, 0x6d, 0x76, 0xd1, 0x29, 0xe2, 0x2c,
+ 0xf3, 0x77, 0xd1, 0x29, 0xe2, 0xdc, 0x52, 0x99, 0xd2, 0xbd, 0xde, 0x0d,
+ 0x58, 0xe7, 0xaf, 0x78, 0x03, 0x80, 0x5a, 0xf5, 0x39, 0xcf, 0xe7, 0x4b,
+ 0x74, 0x8a, 0x38, 0xb5, 0xdf, 0xbf, 0xa5, 0xff, 0x3e, 0x3a, 0x45, 0x9c,
+ 0xb5, 0xff, 0x7d, 0x74, 0x8e, 0x28, 0xbf, 0xfe, 0xb7, 0xe3, 0xa3, 0xe2,
+ 0x0f, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xf8, 0x17, 0xc7, 0xf7, 0xa7, 0xfb, 0x02, 0x1c, 0xdf, 0x44, 0x21, 0x0c,
+ 0x3a, 0x00, 0x00}
+
+ // uncompress test data to sample env file
+ rawData, err := unpackTestData(gzipedData)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(l.envPath, rawData, 0644)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(l.envPathbak, rawData, 0644)
+ c.Assert(err, IsNil)
+
+ env := lkenv.NewEnv(l.envPath)
+ c.Check(env, NotNil)
+ err = env.Load()
+ c.Assert(err, IsNil)
+ c.Check(env.Get("snap_mode"), Equals, "trying")
+ c.Check(env.Get("snap_kernel"), Equals, "kernel-1")
+ c.Check(env.Get("snap_try_kernel"), Equals, "kernel-2")
+ c.Check(env.Get("snap_core"), Equals, "core-1")
+ c.Check(env.Get("snap_try_core"), Equals, "core-2")
+ c.Check(env.Get("bootimg_file_name"), Equals, "boot.img")
+ c.Check(env.Get("reboot_reason"), Equals, "")
+ // first partition should be with label 'boot_a' and 'kernel-1' revision
+ p, err := env.GetBootPartition("kernel-1")
+ c.Check(p, Equals, "boot_a")
+ c.Assert(err, IsNil)
+ // test second boot partition is free with label "boot_b"
+ p, err = env.FindFreeBootPartition("kernel-2")
+ c.Check(p, Equals, "boot_b")
+ c.Assert(err, IsNil)
+}
diff -Nru snapd-2.41+19.10.1/bootloader/lk.go snapd-2.42.1+19.10/bootloader/lk.go
--- snapd-2.41+19.10.1/bootloader/lk.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/bootloader/lk.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,212 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package bootloader
+
+import (
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ "github.com/snapcore/snapd/bootloader/lkenv"
+ "github.com/snapcore/snapd/logger"
+ "github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/snap"
+)
+
+type lk struct {
+ rootdir string
+ inRuntimeMode bool
+}
+
+// newLk create a new lk bootloader object
+func newLk(rootdir string, opts *Options) Bootloader {
+ l := &lk{rootdir: rootdir}
+
+ // XXX: in the long run we want this to go away, we probably add
+ // something like "boot.PrepareImage()" and add an (optional)
+ // method "PrepareImage" to the bootloader interface that is
+ // used to setup a bootloader from prepare-image if things
+ // are very different from runtime vs image-building mode.
+ //
+ // determine mode we are in, runtime or image build
+ l.inRuntimeMode = !opts.PrepareImageTime
+
+ if !osutil.FileExists(l.envFile()) {
+ return nil
+ }
+
+ return l
+}
+
+func (l *lk) setRootDir(rootdir string) {
+ l.rootdir = rootdir
+}
+
+func (l *lk) Name() string {
+ return "lk"
+}
+
+func (l *lk) dir() string {
+ // we have two scenarios, image building and runtime
+ // during image building we store environment into file
+ // at runtime environment is written directly into dedicated partition
+ if l.inRuntimeMode {
+ return filepath.Join(l.rootdir, "/dev/disk/by-partlabel/")
+ } else {
+ return filepath.Join(l.rootdir, "/boot/lk/")
+ }
+}
+
+func (l *lk) ConfigFile() string {
+ return l.envFile()
+}
+
+func (l *lk) envFile() string {
+ // as for dir, we have two scenarios, image building and runtime
+ if l.inRuntimeMode {
+ // TO-DO: this should be eventually fetched from gadget.yaml
+ return filepath.Join(l.dir(), "snapbootsel")
+ } else {
+ return filepath.Join(l.dir(), "snapbootsel.bin")
+ }
+}
+
+func (l *lk) GetBootVars(names ...string) (map[string]string, error) {
+ out := make(map[string]string)
+
+ env := lkenv.NewEnv(l.envFile())
+ if err := env.Load(); err != nil {
+ return nil, err
+ }
+
+ for _, name := range names {
+ out[name] = env.Get(name)
+ }
+
+ return out, nil
+}
+
+func (l *lk) SetBootVars(values map[string]string) error {
+ env := lkenv.NewEnv(l.envFile())
+ if err := env.Load(); err != nil && !os.IsNotExist(err) {
+ return err
+ }
+
+ // update environment only if something change
+ dirty := false
+ for k, v := range values {
+ // already set to the right value, nothing to do
+ if env.Get(k) == v {
+ continue
+ }
+ env.Set(k, v)
+ dirty = true
+ }
+
+ if dirty {
+ return env.Save()
+ }
+
+ return nil
+}
+
+// ExtractKernelAssets extract kernel assets per bootloader specifics
+// lk bootloader requires boot partition to hold valid boot image
+// there are two boot partition available, one holding current bootimage
+// kernel assets are extracted to other (free) boot partition
+// in case this function is called as part of image creation,
+// boot image is extracted to the file
+func (l *lk) ExtractKernelAssets(s snap.PlaceInfo, snapf snap.Container) error {
+ blobName := filepath.Base(s.MountFile())
+
+ logger.Debugf("ExtractKernelAssets (%s)", blobName)
+
+ env := lkenv.NewEnv(l.envFile())
+ if err := env.Load(); err != nil && !os.IsNotExist(err) {
+ return err
+ }
+
+ bootPartition, err := env.FindFreeBootPartition(blobName)
+ if err != nil {
+ return err
+ }
+
+ if l.inRuntimeMode {
+ logger.Debugf("ExtractKernelAssets handling run time usecase")
+ // this is live system, extracted bootimg needs to be flashed to
+ // free bootimg partition and env has to be updated with
+ // new kernel snap to bootimg partition mapping
+ tmpdir, err := ioutil.TempDir("", "bootimg")
+ if err != nil {
+ return fmt.Errorf("cannot create temp directory: %v", err)
+ }
+ defer os.RemoveAll(tmpdir)
+
+ bootImg := env.GetBootImageName()
+ if err := snapf.Unpack(bootImg, tmpdir); err != nil {
+ return fmt.Errorf("cannot unpack %s: %v", bootImg, err)
+ }
+ // write boot.img to free boot partition
+ bootimgName := filepath.Join(tmpdir, bootImg)
+ bif, err := os.Open(bootimgName)
+ if err != nil {
+ return fmt.Errorf("cannot open unpacked %s: %v", bootImg, err)
+ }
+ defer bif.Close()
+ bpart := filepath.Join(l.dir(), bootPartition)
+
+ bpf, err := os.OpenFile(bpart, os.O_WRONLY, 0660)
+ if err != nil {
+ return fmt.Errorf("cannot open boot partition [%s]: %v", bpart, err)
+ }
+ defer bpf.Close()
+
+ if _, err := io.Copy(bpf, bif); err != nil {
+ return err
+ }
+ } else {
+ // we are preparing image, just extract boot image to bootloader directory
+ logger.Debugf("ExtractKernelAssets handling image prepare")
+ if err := snapf.Unpack(env.GetBootImageName(), l.dir()); err != nil {
+ return fmt.Errorf("cannot open unpacked %s: %v", env.GetBootImageName(), err)
+ }
+ }
+ if err := env.SetBootPartition(bootPartition, blobName); err != nil {
+ return err
+ }
+
+ return env.Save()
+}
+
+func (l *lk) RemoveKernelAssets(s snap.PlaceInfo) error {
+ blobName := filepath.Base(s.MountFile())
+ logger.Debugf("RemoveKernelAssets (%s)", blobName)
+ env := lkenv.NewEnv(l.envFile())
+ if err := env.Load(); err != nil && !os.IsNotExist(err) {
+ return err
+ }
+ dirty, _ := env.FreeBootPartition(blobName)
+ if dirty {
+ return env.Save()
+ }
+ return nil
+}
diff -Nru snapd-2.41+19.10.1/bootloader/lk_test.go snapd-2.42.1+19.10/bootloader/lk_test.go
--- snapd-2.41+19.10.1/bootloader/lk_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/bootloader/lk_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,234 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package bootloader_test
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "sort"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/bootloader"
+ "github.com/snapcore/snapd/bootloader/lkenv"
+ "github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/snaptest"
+)
+
+type lkTestSuite struct {
+ baseBootenvTestSuite
+}
+
+var _ = Suite(&lkTestSuite{})
+
+func (s *lkTestSuite) TestNewLkNolkReturnsNil(c *C) {
+ l := bootloader.NewLk("/does/not/exist", nil)
+ c.Assert(l, IsNil)
+}
+
+func (s *lkTestSuite) TestNewLk(c *C) {
+ bootloader.MockLkFiles(c, s.rootdir, nil)
+ l := bootloader.NewLk(s.rootdir, nil)
+ c.Assert(l, NotNil)
+ c.Check(bootloader.LkRuntimeMode(l), Equals, true)
+ c.Check(l.ConfigFile(), Equals, filepath.Join(s.rootdir, "/dev/disk/by-partlabel", "snapbootsel"))
+}
+
+func (s *lkTestSuite) TestNewLkImageBuildingTime(c *C) {
+ opts := &bootloader.Options{
+ PrepareImageTime: true,
+ }
+ bootloader.MockLkFiles(c, s.rootdir, opts)
+ l := bootloader.NewLk(s.rootdir, opts)
+ c.Assert(l, NotNil)
+ c.Check(bootloader.LkRuntimeMode(l), Equals, false)
+ c.Check(l.ConfigFile(), Equals, filepath.Join(s.rootdir, "/boot/lk", "snapbootsel.bin"))
+}
+
+func (s *lkTestSuite) TestSetGetBootVar(c *C) {
+ bootloader.MockLkFiles(c, s.rootdir, nil)
+ l := bootloader.NewLk(s.rootdir, nil)
+ bootVars := map[string]string{"snap_mode": "try"}
+ l.SetBootVars(bootVars)
+
+ v, err := l.GetBootVars("snap_mode")
+ c.Assert(err, IsNil)
+ c.Check(v, HasLen, 1)
+ c.Check(v["snap_mode"], Equals, "try")
+}
+
+func (s *lkTestSuite) TestExtractKernelAssetsUnpacksBootimgImageBuilding(c *C) {
+ opts := &bootloader.Options{
+ PrepareImageTime: true,
+ }
+ bootloader.MockLkFiles(c, s.rootdir, opts)
+ l := bootloader.NewLk(s.rootdir, opts)
+
+ c.Assert(l, NotNil)
+
+ files := [][]string{
+ {"kernel.img", "I'm a kernel"},
+ {"initrd.img", "...and I'm an initrd"},
+ {"boot.img", "...and I'm an boot image"},
+ {"dtbs/foo.dtb", "g'day, I'm foo.dtb"},
+ {"dtbs/bar.dtb", "hello, I'm bar.dtb"},
+ // must be last
+ {"meta/kernel.yaml", "version: 4.2"},
+ }
+ si := &snap.SideInfo{
+ RealName: "ubuntu-kernel",
+ Revision: snap.R(42),
+ }
+ fn := snaptest.MakeTestSnapWithFiles(c, packageKernel, files)
+ snapf, err := snap.Open(fn)
+ c.Assert(err, IsNil)
+
+ info, err := snap.ReadInfoFromSnapFile(snapf, si)
+ c.Assert(err, IsNil)
+
+ err = l.ExtractKernelAssets(info, snapf)
+ c.Assert(err, IsNil)
+
+ // just boot.img and snapbootsel.bin are there, no kernel.img
+ infos, err := ioutil.ReadDir(filepath.Join(s.rootdir, "boot", "lk", ""))
+ c.Assert(err, IsNil)
+ var fnames []string
+ for _, info := range infos {
+ fnames = append(fnames, info.Name())
+ }
+ sort.Strings(fnames)
+ c.Assert(fnames, HasLen, 2)
+ c.Assert(fnames, DeepEquals, []string{"boot.img", "snapbootsel.bin"})
+}
+
+func (s *lkTestSuite) TestExtractKernelAssetsUnpacksCustomBootimgImageBuilding(c *C) {
+ opts := &bootloader.Options{
+ PrepareImageTime: true,
+ }
+ bootloader.MockLkFiles(c, s.rootdir, opts)
+ l := bootloader.NewLk(s.rootdir, opts)
+
+ c.Assert(l, NotNil)
+
+ // first configure custom boot image file name
+ env := lkenv.NewEnv(l.ConfigFile())
+ env.Load()
+ env.ConfigureBootimgName("boot-2.img")
+ err := env.Save()
+ c.Assert(err, IsNil)
+
+ files := [][]string{
+ {"kernel.img", "I'm a kernel"},
+ {"initrd.img", "...and I'm an initrd"},
+ {"boot-2.img", "...and I'm an boot image"},
+ {"dtbs/foo.dtb", "g'day, I'm foo.dtb"},
+ {"dtbs/bar.dtb", "hello, I'm bar.dtb"},
+ // must be last
+ {"meta/kernel.yaml", "version: 4.2"},
+ }
+ si := &snap.SideInfo{
+ RealName: "ubuntu-kernel",
+ Revision: snap.R(42),
+ }
+ fn := snaptest.MakeTestSnapWithFiles(c, packageKernel, files)
+ snapf, err := snap.Open(fn)
+ c.Assert(err, IsNil)
+
+ info, err := snap.ReadInfoFromSnapFile(snapf, si)
+ c.Assert(err, IsNil)
+
+ err = l.ExtractKernelAssets(info, snapf)
+ c.Assert(err, IsNil)
+
+ // boot-2.img is there
+ bootimg := filepath.Join(s.rootdir, "boot", "lk", "boot-2.img")
+ c.Assert(osutil.FileExists(bootimg), Equals, true)
+}
+
+func (s *lkTestSuite) TestExtractKernelAssetsUnpacksAndRemoveInRuntimeMode(c *C) {
+ bootloader.MockLkFiles(c, s.rootdir, nil)
+ lk := bootloader.NewLk(s.rootdir, nil)
+ c.Assert(lk, NotNil)
+
+ // create mock bootsel, boot_a, boot_b partitions
+ for _, partName := range []string{"snapbootsel", "boot_a", "boot_b"} {
+ mockPart := filepath.Join(s.rootdir, "/dev/disk/by-partlabel/", partName)
+ err := os.MkdirAll(filepath.Dir(mockPart), 0755)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(mockPart, nil, 0600)
+ c.Assert(err, IsNil)
+ }
+ // ensure we have a valid boot env
+ bootselPartition := filepath.Join(s.rootdir, "/dev/disk/by-partlabel/snapbootsel")
+ lkenv := lkenv.NewEnv(bootselPartition)
+ lkenv.ConfigureBootPartitions("boot_a", "boot_b")
+ err := lkenv.Save()
+ c.Assert(err, IsNil)
+
+ // mock a kernel snap that has a boot.img
+ files := [][]string{
+ {"boot.img", "I'm the default boot image name"},
+ }
+ si := &snap.SideInfo{
+ RealName: "ubuntu-kernel",
+ Revision: snap.R(42),
+ }
+ fn := snaptest.MakeTestSnapWithFiles(c, packageKernel, files)
+ snapf, err := snap.Open(fn)
+ c.Assert(err, IsNil)
+
+ info, err := snap.ReadInfoFromSnapFile(snapf, si)
+ c.Assert(err, IsNil)
+
+ // now extract
+ err = lk.ExtractKernelAssets(info, snapf)
+ c.Assert(err, IsNil)
+
+ // and validate it went to the "boot_a" partition
+ bootA := filepath.Join(s.rootdir, "/dev/disk/by-partlabel/boot_a")
+ content, err := ioutil.ReadFile(bootA)
+ c.Assert(err, IsNil)
+ c.Assert(string(content), Equals, "I'm the default boot image name")
+
+ // also validate that bootB is empty
+ bootB := filepath.Join(s.rootdir, "/dev/disk/by-partlabel/boot_b")
+ content, err = ioutil.ReadFile(bootB)
+ c.Assert(err, IsNil)
+ c.Assert(content, HasLen, 0)
+
+ // test that boot partition got set
+ err = lkenv.Load()
+ c.Assert(err, IsNil)
+ bootPart, err := lkenv.GetBootPartition("ubuntu-kernel_42.snap")
+ c.Assert(err, IsNil)
+ c.Assert(bootPart, Equals, "boot_a")
+
+ // now remove the kernel
+ err = lk.RemoveKernelAssets(info)
+ c.Assert(err, IsNil)
+ // and ensure its no longer available in the boot partions
+ err = lkenv.Load()
+ c.Assert(err, IsNil)
+ bootPart, err = lkenv.GetBootPartition("ubuntu-kernel_42.snap")
+ c.Assert(err, ErrorMatches, "cannot find kernel .* in boot image partitions")
+ c.Assert(bootPart, Equals, "")
+}
diff -Nru snapd-2.41+19.10.1/bootloader/uboot.go snapd-2.42.1+19.10/bootloader/uboot.go
--- snapd-2.41+19.10.1/bootloader/uboot.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/bootloader/uboot.go 2019-10-30 12:17:43.000000000 +0000
@@ -23,16 +23,17 @@
"path/filepath"
"github.com/snapcore/snapd/bootloader/ubootenv"
- "github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/snap"
)
-type uboot struct{}
+type uboot struct {
+ rootdir string
+}
// newUboot create a new Uboot bootloader object
-func newUboot() Bootloader {
- u := &uboot{}
+func newUboot(rootdir string) Bootloader {
+ u := &uboot{rootdir: rootdir}
if !osutil.FileExists(u.envFile()) {
return nil
}
@@ -44,8 +45,15 @@
return "uboot"
}
+func (u *uboot) setRootDir(rootdir string) {
+ u.rootdir = rootdir
+}
+
func (u *uboot) dir() string {
- return filepath.Join(dirs.GlobalRootDir, "/boot/uboot")
+ if u.rootdir == "" {
+ panic("internal error: unset rootdir")
+ }
+ return filepath.Join(u.rootdir, "/boot/uboot")
}
func (u *uboot) ConfigFile() string {
@@ -94,7 +102,7 @@
return out, nil
}
-func (u *uboot) ExtractKernelAssets(s *snap.Info, snapf snap.Container) error {
+func (u *uboot) ExtractKernelAssets(s snap.PlaceInfo, snapf snap.Container) error {
return extractKernelAssetsToBootDir(u.dir(), s, snapf)
}
diff -Nru snapd-2.41+19.10.1/bootloader/uboot_test.go snapd-2.42.1+19.10/bootloader/uboot_test.go
--- snapd-2.41+19.10.1/bootloader/uboot_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/bootloader/uboot_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -28,7 +28,6 @@
"github.com/snapcore/snapd/bootloader"
"github.com/snapcore/snapd/bootloader/ubootenv"
- "github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snaptest"
@@ -42,20 +41,20 @@
var _ = Suite(&ubootTestSuite{})
func (s *ubootTestSuite) TestNewUbootNoUbootReturnsNil(c *C) {
- u := bootloader.NewUboot()
+ u := bootloader.NewUboot(s.rootdir)
c.Assert(u, IsNil)
}
func (s *ubootTestSuite) TestNewUboot(c *C) {
- bootloader.MockUbootFiles(c)
- u := bootloader.NewUboot()
+ bootloader.MockUbootFiles(c, s.rootdir)
+ u := bootloader.NewUboot(s.rootdir)
c.Assert(u, NotNil)
c.Assert(u.Name(), Equals, "uboot")
}
func (s *ubootTestSuite) TestUbootGetEnvVar(c *C) {
- bootloader.MockUbootFiles(c)
- u := bootloader.NewUboot()
+ bootloader.MockUbootFiles(c, s.rootdir)
+ u := bootloader.NewUboot(s.rootdir)
c.Assert(u, NotNil)
err := u.SetBootVars(map[string]string{
"snap_mode": "",
@@ -72,16 +71,16 @@
}
func (s *ubootTestSuite) TestGetBootloaderWithUboot(c *C) {
- bootloader.MockUbootFiles(c)
+ bootloader.MockUbootFiles(c, s.rootdir)
- bootloader, err := bootloader.Find()
+ bootloader, err := bootloader.Find(s.rootdir, nil)
c.Assert(err, IsNil)
c.Assert(bootloader.Name(), Equals, "uboot")
}
func (s *ubootTestSuite) TestUbootSetEnvNoUselessWrites(c *C) {
- bootloader.MockUbootFiles(c)
- u := bootloader.NewUboot()
+ bootloader.MockUbootFiles(c, s.rootdir)
+ u := bootloader.NewUboot(s.rootdir)
c.Assert(u, NotNil)
envFile := u.ConfigFile()
@@ -110,8 +109,8 @@
}
func (s *ubootTestSuite) TestUbootSetBootVarFwEnv(c *C) {
- bootloader.MockUbootFiles(c)
- u := bootloader.NewUboot()
+ bootloader.MockUbootFiles(c, s.rootdir)
+ u := bootloader.NewUboot(s.rootdir)
err := u.SetBootVars(map[string]string{"key": "value"})
c.Assert(err, IsNil)
@@ -122,8 +121,8 @@
}
func (s *ubootTestSuite) TestUbootGetBootVarFwEnv(c *C) {
- bootloader.MockUbootFiles(c)
- u := bootloader.NewUboot()
+ bootloader.MockUbootFiles(c, s.rootdir)
+ u := bootloader.NewUboot(s.rootdir)
err := u.SetBootVars(map[string]string{"key2": "value2"})
c.Assert(err, IsNil)
@@ -134,8 +133,8 @@
}
func (s *ubootTestSuite) TestExtractKernelAssetsAndRemove(c *C) {
- bootloader.MockUbootFiles(c)
- u := bootloader.NewUboot()
+ bootloader.MockUbootFiles(c, s.rootdir)
+ u := bootloader.NewUboot(s.rootdir)
files := [][]string{
{"kernel.img", "I'm a kernel"},
@@ -160,7 +159,7 @@
c.Assert(err, IsNil)
// this is where the kernel/initrd is unpacked
- kernelAssetsDir := filepath.Join(dirs.GlobalRootDir, "boot", "uboot", "ubuntu-kernel_42.snap")
+ kernelAssetsDir := filepath.Join(s.rootdir, "boot", "uboot", "ubuntu-kernel_42.snap")
for _, def := range files {
if def[0] == "meta/kernel.yaml" {
diff -Nru snapd-2.41+19.10.1/client/asserts.go snapd-2.42.1+19.10/client/asserts.go
--- snapd-2.41+19.10.1/client/asserts.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/client/asserts.go 2019-10-30 12:17:43.000000000 +0000
@@ -27,6 +27,7 @@
"strconv"
"github.com/snapcore/snapd/asserts" // for parsing
+ "github.com/snapcore/snapd/snap"
)
// Ack tries to add an assertion to the system assertion
@@ -102,3 +103,31 @@
return asserts, nil
}
+
+// StoreAccount returns the full store account info for the specified accountID
+func (client *Client) StoreAccount(accountID string) (*snap.StoreAccount, error) {
+ assertions, err := client.Known("account", map[string]string{"account-id": accountID})
+ if err != nil {
+ return nil, err
+ }
+ switch len(assertions) {
+ case 1:
+ // happy case, break out of the switch
+ case 0:
+ return nil, fmt.Errorf("no assertion found for account-id %s", accountID)
+ default:
+ // unknown how this could happen...
+ return nil, fmt.Errorf("multiple assertions for account-id %s", accountID)
+ }
+
+ acct, ok := assertions[0].(*asserts.Account)
+ if !ok {
+ return nil, fmt.Errorf("incorrect type of account assertion returned")
+ }
+ return &snap.StoreAccount{
+ ID: acct.AccountID(),
+ Username: acct.Username(),
+ DisplayName: acct.DisplayName(),
+ Validation: acct.Validation(),
+ }, nil
+}
diff -Nru snapd-2.41+19.10.1/client/asserts_test.go snapd-2.42.1+19.10/client/asserts_test.go
--- snapd-2.41+19.10.1/client/asserts_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/client/asserts_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -28,6 +28,7 @@
. "gopkg.in/check.v1"
"github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/snap"
)
func (cs *clientSuite) TestClientAssert(c *C) {
@@ -157,3 +158,58 @@
_, err := cs.cli.Known("snap-build", nil)
c.Assert(err, ErrorMatches, "response did not have the expected number of assertions")
}
+
+func (cs *clientSuite) TestStoreAccount(c *C) {
+ cs.header = http.Header{}
+ cs.header.Add("X-Ubuntu-Assertions-Count", "1")
+ cs.rsp = `type: account
+authority-id: canonical
+account-id: canonicalID
+display-name: canonicalDisplay
+timestamp: 2016-04-01T00:00:00.0Z
+username: canonicalUser
+validation: certified
+sign-key-sha3-384: -CvQKAwRQ5h3Ffn10FILJoEZUXOv6km9FwA80-Rcj-f-6jadQ89VRswHNiEB9Lxk
+
+AcLDXAQAAQoABgUCV7UYzwAKCRDUpVvql9g3IK7uH/4udqNOurx5WYVknzXdwekp0ovHCQJ0iBPw
+TSFxEVr9faZSzb7eqJ1WicHsShf97PYS3ClRYAiluFsjRA8Y03kkSVJHjC+sIwGFubsnkmgflt6D
+WEmYIl0UBmeaEDS8uY4Xvp9NsLTzNEj2kvzy/52gKaTc1ZSl5RDL9ppMav+0V9iBYpiDPBWH2rJ+
+aDSD8Rkyygm0UscfAKyDKH4lrvZ0WkYyi1YVNPrjQ/AtBySh6Q4iJ3LifzKa9woIyAuJET/4/FPY
+oirqHAfuvNod36yNQIyNqEc20AvTvZNH0PSsg4rq3DLjIPzv5KbJO9lhsasNJK1OdL6x8Yqrdsbk
+ldZp4qkzfjV7VOMQKaadfcZPRaVVeJWOBnBiaukzkhoNlQi1sdCdkBB/AJHZF8QXw6c7vPDcfnCV
+1lW7ddQ2p8IsJbT6LzpJu3GW/P4xhNgCjtCJ1AJm9a9RqLwQYgdLZwwDa9iCRtqTbRXBlfy3apps
+1VjbQ3h5iCd0hNfwDBnGVm1rhLKHCD1DUdNE43oN2ZlE7XGyh0HFV6vKlpqoW3eoXCIxWu+HBY96
++LSl/jQgCkb0nxYyzEYK4Reb31D0mYw1Nji5W+MIF5E09+DYZoOT0UvR05YMwMEOeSdI/hLWg/5P
+k+GDK+/KopMmpd4D1+jjtF7ZvqDpmAV98jJGB2F88RyVb4gcjmFFyTi4Kv6vzz/oLpbm0qrizC0W
+HLGDN/ymGA5sHzEgEx7U540vz/q9VX60FKqL2YZr/DcyY9GKX5kCG4sNqIIHbcJneZ4frM99oVDu
+7Jv+DIx/Di6D1ULXol2XjxbbJLKHFtHksR97ceaFvcZwTogC61IYUBJCvvMoqdXAWMhEXCr0QfQ5
+Xbi31XW2d4/lF/zWlAkRnGTzufIXFni7+nEuOK0SQEzO3/WaRedK1SGOOtTDjB8/3OJeW96AUYK5
+oTIynkYkEyHWMNCXALg+WQW6L4/YO7aUjZ97zOWIugd7Xy63aT3r/EHafqaY2nacOhLfkeKZ830b
+o/ezjoZQAxbh6ce7JnXRgE9ELxjdAhBTpGjmmmN2sYrJ7zP9bOgly0BnEPXGSQfFA+NNNw1FADx1
+MUY8q9DBjmVtgqY+1KGTV5X8KvQCBMODZIf/XJPHdCRAHxMd8COypcwgL2vDIIXpOFbi1J/B0GF+
+eklxk9wzBA8AecBMCwCzIRHDNpD1oa2we38bVFrOug6e/VId1k1jYFJjiLyLCDmV8IMYwEllHSXp
+LQAdm3xZ7t4WnxYC8YSCk9mXf3CZg59SpmnV5Q5Z6A5Pl7Nc3sj7hcsMBZEsOMPzNC9dPsBnZvjs
+WpPUffJzEdhHBFhvYMuD4Vqj6ejUv9l3oTrjQWVC
+`
+
+ account, err := cs.cli.StoreAccount("canonicalID")
+ c.Assert(err, IsNil)
+ c.Check(cs.req.Method, Equals, "GET")
+ c.Check(cs.req.URL.Query(), HasLen, 1)
+ c.Check(cs.req.URL.Query().Get("account-id"), Equals, "canonicalID")
+ c.Assert(account, DeepEquals, &snap.StoreAccount{
+ ID: "canonicalID",
+ Username: "canonicalUser",
+ DisplayName: "canonicalDisplay",
+ Validation: "verified",
+ })
+}
+
+func (cs *clientSuite) TestStoreAccountNoAssertionFound(c *C) {
+ cs.header = http.Header{}
+ cs.header.Add("X-Ubuntu-Assertions-Count", "0")
+ cs.rsp = ""
+
+ _, err := cs.cli.StoreAccount("canonicalID")
+ c.Assert(err, ErrorMatches, "no assertion found for account-id canonicalID")
+}
diff -Nru snapd-2.41+19.10.1/client/client.go snapd-2.42.1+19.10/client/client.go
--- snapd-2.41+19.10.1/client/client.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/client/client.go 2019-10-30 12:17:43.000000000 +0000
@@ -456,6 +456,8 @@
ErrorKindSystemRestart = "system-restart"
ErrorKindDaemonRestart = "daemon-restart"
+
+ ErrorKindAssertionNotFound = "assertion-not-found"
)
// IsRetryable returns true if the given error is an error
@@ -489,6 +491,17 @@
return e.Kind == ErrorKindInterfacesUnchanged
}
+// IsAssertionNotFoundError returns whether the given error means that the
+// assertion wasn't found and thus the device isn't ready/seeded.
+func IsAssertionNotFoundError(err error) bool {
+ e, ok := err.(*Error)
+ if !ok || e == nil {
+ return false
+ }
+
+ return e.Kind == ErrorKindAssertionNotFound
+}
+
// OSRelease contains information about the system extracted from /etc/os-release.
type OSRelease struct {
ID string `json:"id"`
diff -Nru snapd-2.41+19.10.1/client/model.go snapd-2.42.1+19.10/client/model.go
--- snapd-2.41+19.10.1/client/model.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/client/model.go 2019-10-30 12:17:43.000000000 +0000
@@ -23,6 +23,9 @@
"bytes"
"encoding/json"
"fmt"
+ "net/url"
+
+ "github.com/snapcore/snapd/asserts"
)
type remodelData struct {
@@ -43,3 +46,54 @@
return client.doAsync("POST", "/v2/model", nil, headers, bytes.NewReader(data))
}
+
+// CurrentModelAssertion returns the current model assertion
+func (client *Client) CurrentModelAssertion() (*asserts.Model, error) {
+ assert, err := currentAssertion(client, "/v2/model")
+ if err != nil {
+ return nil, err
+ }
+ modelAssert, ok := assert.(*asserts.Model)
+ if !ok {
+ return nil, fmt.Errorf("unexpected assertion type (%s) returned", assert.Type().Name)
+ }
+ return modelAssert, nil
+}
+
+// CurrentSerialAssertion returns the current serial assertion
+func (client *Client) CurrentSerialAssertion() (*asserts.Serial, error) {
+ assert, err := currentAssertion(client, "/v2/model/serial")
+ if err != nil {
+ return nil, err
+ }
+ serialAssert, ok := assert.(*asserts.Serial)
+ if !ok {
+ return nil, fmt.Errorf("unexpected assertion type (%s) returned", assert.Type().Name)
+ }
+ return serialAssert, nil
+}
+
+// helper function for getting assertions from the daemon via a REST path
+func currentAssertion(client *Client, path string) (asserts.Assertion, error) {
+ q := url.Values{}
+
+ response, err := client.raw("GET", path, q, nil, nil)
+ if err != nil {
+ return nil, fmt.Errorf("failed to query current assertion: %v", err)
+ }
+ defer response.Body.Close()
+ if response.StatusCode != 200 {
+ return nil, parseError(response)
+ }
+
+ dec := asserts.NewDecoder(response.Body)
+
+ // only decode a single assertion - we can't ever get more than a single
+ // assertion through these endpoints by design
+ assert, err := dec.Decode()
+ if err != nil {
+ return nil, fmt.Errorf("failed to decode assertions: %v", err)
+ }
+
+ return assert, nil
+}
diff -Nru snapd-2.41+19.10.1/client/model_test.go snapd-2.42.1+19.10/client/model_test.go
--- snapd-2.41+19.10.1/client/model_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/client/model_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -22,10 +22,82 @@
import (
"encoding/json"
"io/ioutil"
+ "net/http"
+
+ "github.com/snapcore/snapd/asserts"
. "gopkg.in/check.v1"
)
+const happyModelAssertionResponse = `type: model
+authority-id: mememe
+series: 16
+brand-id: mememe
+model: test-model
+architecture: amd64
+base: core18
+gadget: pc=18
+kernel: pc-kernel=18
+required-snaps:
+ - core
+ - hello-world
+timestamp: 2017-07-27T00:00:00.0Z
+sign-key-sha3-384: 8B3Wmemeu3H6i4dEV4Q85Q4gIUCHIBCNMHq49e085QeLGHi7v27l3Cqmemer4__t
+
+AcLBcwQAAQoAHRYhBMbX+t6MbKGH5C3nnLZW7+q0g6ELBQJdTdwTAAoJELZW7+q0g6ELEvgQAI3j
+jXTqR6kKOqvw94pArwdMDUaZ++tebASAZgso8ejrW2DQGWSc0Q7SQICIR8bvHxqS1GtupQswOzwS
+U8hjDTv7WEchH1jylyTj/1W1GernmitTKycecRlEkSOE+EpuqBFgTtj6PdA1Fj3CiCRi1rLMhgF2
+luCOitBLaP+E8P3fuATsLqqDLYzt1VY4Y14MU75hMn+CxAQdnOZTI+NzGMasPsldmOYCPNaN/b3N
+6/fDLU47RtNlMJ3K0Tz8kj0bqRbegKlD0RdNbAgo9iZwNmrr5E9WCu9f/0rUor/NIxO77H2ExIll
+zhmsZ7E6qlxvAgBmzKgAXrn68gGrBkIb0eXKiCaKy/i2ApvjVZ9HkOzA6Ldd+SwNJv/iA8rdiMsq
+p2BfKV5f3ju5b6+WktHxAakJ8iqQmj9Yh7piHjsOAUf1PEJd2s2nqQ+pEEn1F0B23gVCY/Fa9YRQ
+iKtWVeL3rBw4dSAaK9rpTMqlNcr+yrdXfTK5YzkCC6RU4yzc5MW0hKeseeSiEDSaRYxvftjFfVNa
+ZaVXKg8Lu+cHtCJDeYXEkPIDQzXswdBO1M8Mb9D0mYxQwHxwvsWv1DByB+Otq08EYgPh4kyHo7ag
+85yK2e/NQ/fxSwQJMhBF74jM1z9arq6RMiE/KOleFAOraKn2hcROKnEeinABW+sOn6vNuMVv
+`
+
+// note: this serial assertion was generated by adding print statements to the
+// test in api_model_test.go that generate a fake serial assertion
+const happySerialAssertionResponse = `type: serial
+authority-id: my-brand
+brand-id: my-brand
+model: my-old-model
+serial: serialserial
+device-key:
+ AcZrBFaFwYABAvCgEOrrLA6FKcreHxCcOoTgBUZ+IRG7Nb8tzmEAklaQPGpv7skapUjwD1luE2go
+ mTcoTssVHrfLpBoSDV1aBs44rg3NK40ZKPJP7d2zkds1GxUo1Ea5vfet3SJ4h3aRABEBAAE=
+device-key-sha3-384: iqLo9doLzK8De9925UrdUyuvPbBad72OTWVE9YJXqd6nz9dKvwJ_lHP5bVxrl3VO
+timestamp: 2019-08-26T16:34:21-05:00
+sign-key-sha3-384: anCEGC2NYq7DzDEi6y7OafQCVeVLS90XlLt9PNjrRl9sim5rmRHDDNFNO7ODcWQW
+
+AcJwBAABCgAGBQJdZFBdAADCLALwR6Sy24wm9PffwbvUhOEXneyY3BnxKC0+NgdHu1gU8go9vEP1
+i+Flh5uoS70+MBIO+nmF8T+9JWIx2QWFDDxvcuFosnIhvUajCEQohauys5FMz/H/WvB0vrbTBpvK
+eg==`
+
+const noModelAssertionYetResponse = `
+{
+ "type": "error",
+ "status-code": 404,
+ "status": "Not Found",
+ "result": {
+ "message": "no model assertion yet",
+ "kind": "assertion-not-found",
+ "value": "model"
+ }
+}`
+
+const noSerialAssertionYetResponse = `
+{
+ "type": "error",
+ "status-code": 404,
+ "status": "Not Found",
+ "result": {
+ "message": "no serial assertion yet",
+ "kind": "assertion-not-found",
+ "value": "serial"
+ }
+}`
+
func (cs *clientSuite) TestClientRemodelEndpoint(c *C) {
cs.cli.Remodel([]byte(`{"new-model": "some-model"}`))
c.Check(cs.req.Method, Equals, "POST")
@@ -54,3 +126,41 @@
c.Check(jsonBody, HasLen, 1)
c.Check(jsonBody["new-model"], Equals, string(remodelJsonData))
}
+
+func (cs *clientSuite) TestClientGetModelHappy(c *C) {
+ cs.status = 200
+ cs.rsp = happyModelAssertionResponse
+ modelAssertion, err := cs.cli.CurrentModelAssertion()
+ c.Assert(err, IsNil)
+ expectedAssert, err := asserts.Decode([]byte(happyModelAssertionResponse))
+ c.Assert(err, IsNil)
+ c.Assert(modelAssertion, DeepEquals, expectedAssert)
+}
+
+func (cs *clientSuite) TestClientGetModelNoModel(c *C) {
+ cs.status = 404
+ cs.rsp = noModelAssertionYetResponse
+ cs.header = http.Header{}
+ cs.header.Add("Content-Type", "application/json")
+ _, err := cs.cli.CurrentModelAssertion()
+ c.Assert(err, ErrorMatches, "no model assertion yet")
+}
+
+func (cs *clientSuite) TestClientGetModelNoSerial(c *C) {
+ cs.status = 404
+ cs.rsp = noSerialAssertionYetResponse
+ cs.header = http.Header{}
+ cs.header.Add("Content-Type", "application/json")
+ _, err := cs.cli.CurrentSerialAssertion()
+ c.Assert(err, ErrorMatches, "no serial assertion yet")
+}
+
+func (cs *clientSuite) TestClientGetSerialHappy(c *C) {
+ cs.status = 200
+ cs.rsp = happySerialAssertionResponse
+ serialAssertion, err := cs.cli.CurrentSerialAssertion()
+ c.Assert(err, IsNil)
+ expectedAssert, err := asserts.Decode([]byte(happySerialAssertionResponse))
+ c.Assert(err, IsNil)
+ c.Assert(serialAssertion, DeepEquals, expectedAssert)
+}
diff -Nru snapd-2.41+19.10.1/cmd/cmd_linux.go snapd-2.42.1+19.10/cmd/cmd_linux.go
--- snapd-2.41+19.10.1/cmd/cmd_linux.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/cmd_linux.go 2019-10-30 12:17:43.000000000 +0000
@@ -73,12 +73,12 @@
return true
}
-// coreSupportsReExec returns true if the given core snap should be used as re-exec target.
+// coreSupportsReExec returns true if the given core/snapd snap should be used as re-exec target.
//
// Ensure we do not use older version of snapd, look for info file and ignore
// version of core that do not yet have it.
-func coreSupportsReExec(corePath string) bool {
- fullInfo := filepath.Join(corePath, filepath.Join(dirs.CoreLibExecDir, "info"))
+func coreSupportsReExec(coreOrSnapdPath string) bool {
+ fullInfo := filepath.Join(coreOrSnapdPath, filepath.Join(dirs.CoreLibExecDir, "info"))
content, err := ioutil.ReadFile(fullInfo)
if err != nil {
if !os.IsNotExist(err) {
@@ -108,7 +108,7 @@
return false
}
if res > 0 {
- logger.Debugf("core snap (at %q) is older (%q) than distribution package (%q)", corePath, ver, Version)
+ logger.Debugf("snap (at %q) is older (%q) than distribution package (%q)", coreOrSnapdPath, ver, Version)
return false
}
return true
@@ -206,10 +206,10 @@
}
// Is this executable in the core snap too?
- corePath := snapdSnap
+ coreOrSnapdPath := snapdSnap
full := filepath.Join(snapdSnap, exe)
if !osutil.FileExists(full) {
- corePath = coreSnap
+ coreOrSnapdPath = coreSnap
full = filepath.Join(coreSnap, exe)
if !osutil.FileExists(full) {
return
@@ -217,7 +217,7 @@
}
// If the core snap doesn't support re-exec or run-from-core then don't do it.
- if !coreSupportsReExec(corePath) {
+ if !coreSupportsReExec(coreOrSnapdPath) {
return
}
diff -Nru snapd-2.41+19.10.1/cmd/libsnap-confine-private/cgroup-support.c snapd-2.42.1+19.10/cmd/libsnap-confine-private/cgroup-support.c
--- snapd-2.41+19.10.1/cmd/libsnap-confine-private/cgroup-support.c 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/libsnap-confine-private/cgroup-support.c 2019-10-30 12:17:43.000000000 +0000
@@ -25,6 +25,7 @@
#include
#include
#include
+#include
#include
#include "cleanup-funcs.h"
@@ -66,3 +67,33 @@
}
debug("moved process %ld to cgroup hierarchy %s/%s", (long)pid, parent, name);
}
+
+static const char *cgroup_dir = "/sys/fs/cgroup";
+
+// from statfs(2)
+#ifndef CGRUOP2_SUPER_MAGIC
+#define CGROUP2_SUPER_MAGIC 0x63677270
+#endif
+
+// Detect if we are running in cgroup v2 unified mode (as opposed to
+// hybrid or legacy) The algorithm is described in
+// https://systemd.io/CGROUP_DELEGATION.html
+bool sc_cgroup_is_v2() {
+ static bool did_warn = false;
+ struct statfs buf;
+
+ if (statfs(cgroup_dir, &buf) != 0) {
+ if (errno == ENOENT) {
+ return false;
+ }
+ die("cannot statfs %s", cgroup_dir);
+ }
+ if (buf.f_type == CGROUP2_SUPER_MAGIC) {
+ if (!did_warn) {
+ fprintf(stderr, "WARNING: cgroup v2 is not fully supported yet, proceeding with partial confinement\n");
+ did_warn = true;
+ }
+ return true;
+ }
+ return false;
+}
diff -Nru snapd-2.41+19.10.1/cmd/libsnap-confine-private/cgroup-support.h snapd-2.42.1+19.10/cmd/libsnap-confine-private/cgroup-support.h
--- snapd-2.41+19.10.1/cmd/libsnap-confine-private/cgroup-support.h 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/libsnap-confine-private/cgroup-support.h 2019-10-30 12:17:43.000000000 +0000
@@ -19,6 +19,7 @@
#define SC_CGROUP_SUPPORT_H
#include
+#include
/**
* sc_cgroup_create_and_join joins, perhaps creating, a cgroup hierarchy.
@@ -30,4 +31,10 @@
**/
void sc_cgroup_create_and_join(const char *parent, const char *name, pid_t pid);
+/**
+ * sc_cgroup_is_v2() returns true if running on cgroups v2
+ *
+ **/
+bool sc_cgroup_is_v2(void);
+
#endif
diff -Nru snapd-2.41+19.10.1/cmd/libsnap-confine-private/feature.c snapd-2.42.1+19.10/cmd/libsnap-confine-private/feature.c
--- snapd-2.41+19.10.1/cmd/libsnap-confine-private/feature.c 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/libsnap-confine-private/feature.c 2019-10-30 12:17:43.000000000 +0000
@@ -40,6 +40,9 @@
case SC_FEATURE_REFRESH_APP_AWARENESS:
file_name = "refresh-app-awareness";
break;
+ case SC_FEATURE_PARALLEL_INSTANCES:
+ file_name = "parallel-instances";
+ break;
default:
die("unknown feature flag code %d", flag);
}
diff -Nru snapd-2.41+19.10.1/cmd/libsnap-confine-private/feature.h snapd-2.42.1+19.10/cmd/libsnap-confine-private/feature.h
--- snapd-2.41+19.10.1/cmd/libsnap-confine-private/feature.h 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/libsnap-confine-private/feature.h 2019-10-30 12:17:43.000000000 +0000
@@ -21,8 +21,9 @@
#include
typedef enum sc_feature_flag {
- SC_FEATURE_PER_USER_MOUNT_NAMESPACE,
- SC_FEATURE_REFRESH_APP_AWARENESS,
+ SC_FEATURE_PER_USER_MOUNT_NAMESPACE = 1 << 0,
+ SC_FEATURE_REFRESH_APP_AWARENESS = 1 << 1,
+ SC_FEATURE_PARALLEL_INSTANCES = 1 << 2,
} sc_feature_flag;
/**
diff -Nru snapd-2.41+19.10.1/cmd/libsnap-confine-private/feature-test.c snapd-2.42.1+19.10/cmd/libsnap-confine-private/feature-test.c
--- snapd-2.41+19.10.1/cmd/libsnap-confine-private/feature-test.c 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/libsnap-confine-private/feature-test.c 2019-10-30 12:17:43.000000000 +0000
@@ -75,6 +75,20 @@
g_assert(sc_feature_enabled(SC_FEATURE_PER_USER_MOUNT_NAMESPACE));
}
+static void test_feature_parallel_instances(void)
+{
+ const char *d = sc_testdir();
+ sc_mock_feature_flag_dir(d);
+
+ g_assert(!sc_feature_enabled(SC_FEATURE_PARALLEL_INSTANCES));
+
+ char pname[PATH_MAX];
+ sc_must_snprintf(pname, sizeof pname, "%s/parallel-instances", d);
+ g_file_set_contents(pname, "", -1, NULL);
+
+ g_assert(sc_feature_enabled(SC_FEATURE_PARALLEL_INSTANCES));
+}
+
static void __attribute__((constructor)) init(void)
{
g_test_add_func("/feature/missing_dir",
@@ -83,4 +97,6 @@
test_feature_enabled__missing_file);
g_test_add_func("/feature/present_file",
test_feature_enabled__present_file);
+ g_test_add_func("/feature/parallel_instances",
+ test_feature_parallel_instances);
}
diff -Nru snapd-2.41+19.10.1/cmd/libsnap-confine-private/panic.c snapd-2.42.1+19.10/cmd/libsnap-confine-private/panic.c
--- snapd-2.41+19.10.1/cmd/libsnap-confine-private/panic.c 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/libsnap-confine-private/panic.c 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#include "panic.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+static sc_panic_exit_fn panic_exit_fn = NULL;
+static sc_panic_msg_fn panic_msg_fn = NULL;
+
+void sc_panic(const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ sc_panicv(fmt, ap);
+ va_end(ap);
+}
+
+void sc_panicv(const char *fmt, va_list ap) {
+ int errno_copy = errno;
+
+ if (panic_msg_fn != NULL) {
+ panic_msg_fn(fmt, ap, errno_copy);
+ } else {
+ vfprintf(stderr, fmt, ap);
+ if (errno != 0) {
+ fprintf(stderr, ": %s\n", strerror(errno_copy));
+ } else {
+ fprintf(stderr, "\n");
+ }
+ }
+
+ if (panic_exit_fn != NULL) {
+ panic_exit_fn();
+ }
+ exit(1);
+}
+
+sc_panic_exit_fn sc_set_panic_exit_fn(sc_panic_exit_fn fn) {
+ sc_panic_exit_fn old = panic_exit_fn;
+ panic_exit_fn = fn;
+ return old;
+}
+
+sc_panic_msg_fn sc_set_panic_msg_fn(sc_panic_msg_fn fn) {
+ sc_panic_msg_fn old = panic_msg_fn;
+ panic_msg_fn = fn;
+ return old;
+}
diff -Nru snapd-2.41+19.10.1/cmd/libsnap-confine-private/panic.h snapd-2.42.1+19.10/cmd/libsnap-confine-private/panic.h
--- snapd-2.41+19.10.1/cmd/libsnap-confine-private/panic.h 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/libsnap-confine-private/panic.h 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#ifndef SC_PANIC_H
+#define SC_PANIC_H
+
+#include
+
+/**
+ * sc_panic is an exit-with-message utility function.
+ *
+ * The function takes a printf-like format string that is formatted and printed
+ * somehow. The function then terminates the process by calling exit. Both
+ * aspects can be customized.
+ *
+ * The particular nature of the exit can be customized by calling
+ * sc_set_panic_action. The panic action is a function that is called before
+ * attempting to exit.
+ *
+ * The way the error message is formatted and printed can be customized by
+ * calling sc_set_panic_format_fn(). By default the error is printed to
+ * standard error. If the error is related to a system call failure then errno
+ * can be set to a non-zero value just prior to calling sc_panic. The value
+ * will then be used when crafting the error message.
+ **/
+__attribute__((noreturn, format(printf, 1, 2))) void sc_panic(const char *fmt, ...);
+
+/**
+ * sc_panicv is a variant of sc_panic with an argument list.
+ **/
+__attribute__((noreturn)) void sc_panicv(const char *fmt, va_list ap);
+
+/**
+ * sc_panic_exit_fn is the type of the exit function used by sc_panic().
+ **/
+typedef void (*sc_panic_exit_fn)(void);
+
+/**
+ * sc_set_panic_exit_fn sets the panic exit function.
+ *
+ * When sc_panic is called it will eventually exit the running process. Just
+ * prior to that, it will call the panic exit function, if one has been set.
+ *
+ * If exiting the process is undesired, for example while running in intrd as
+ * pid 1, during the system shutdown phase, then a process can set the panic
+ * exit function. Note that if the specified function returns then panic will
+ * proceed to call exit(3) anyway.
+ *
+ * The old exit function, if any, is returned.
+ **/
+sc_panic_exit_fn sc_set_panic_exit_fn(sc_panic_exit_fn fn);
+
+/**
+ * sc_panic_msg_fn is the type of the format function used by sc_panic().
+ **/
+typedef void (*sc_panic_msg_fn)(const char *fmt, va_list ap, int errno_copy);
+
+/**
+ * sc_set_panic_msg_fn sets the panic message function.
+ *
+ * When sc_panic is called it will attempt to print an error message to
+ * standard error. The message includes information provided by the caller: the
+ * format string, the argument vector for a printf-like function as well as a
+ * copy of the system errno value, which may be zero if the error is not
+ * originated by a system call error.
+ *
+ * If custom formatting of the error message is desired, for example while
+ * running in initrd as pid 1, during the system shutdown phase, then a process
+ * can set the panic message function. Once set the function takes over the
+ * responsibility of printing an error message (in whatever form is
+ * appropriate).
+ *
+ * The old message function, if any, is returned.
+ **/
+sc_panic_msg_fn sc_set_panic_msg_fn(sc_panic_msg_fn fn);
+
+#endif
diff -Nru snapd-2.41+19.10.1/cmd/libsnap-confine-private/panic-test.c snapd-2.42.1+19.10/cmd/libsnap-confine-private/panic-test.c
--- snapd-2.41+19.10.1/cmd/libsnap-confine-private/panic-test.c 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/libsnap-confine-private/panic-test.c 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#include "panic.h"
+#include "panic.c"
+
+#include
+
+static void test_panic(void)
+{
+ if (g_test_subprocess()) {
+ errno = 0;
+ sc_panic("death message");
+ g_test_message("expected die not to return");
+ g_test_fail();
+ return;
+ }
+ g_test_trap_subprocess(NULL, 0, 0);
+ g_test_trap_assert_failed();
+ g_test_trap_assert_stderr("death message\n");
+}
+
+static void test_panic_with_errno(void)
+{
+ if (g_test_subprocess()) {
+ errno = EPERM;
+ sc_panic("death message");
+ g_test_message("expected die not to return");
+ g_test_fail();
+ return;
+ }
+ g_test_trap_subprocess(NULL, 0, 0);
+ g_test_trap_assert_failed();
+ g_test_trap_assert_stderr("death message: Operation not permitted\n");
+}
+
+static void custom_panic_msg(const char *fmt, va_list ap, int errno_copy)
+{
+ fprintf(stderr, "PANIC: ");
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, " (errno: %d)", errno_copy);
+ fprintf(stderr, "\n");
+}
+
+static void custom_panic_exit(void)
+{
+ fprintf(stderr, "EXITING\n");
+ exit(2);
+}
+
+static void test_panic_customization(void)
+{
+ if (g_test_subprocess()) {
+ sc_set_panic_msg_fn(custom_panic_msg);
+ sc_set_panic_exit_fn(custom_panic_exit);
+ errno = 123;
+ sc_panic("death message");
+ g_test_message("expected die not to return");
+ g_test_fail();
+ return;
+ }
+ g_test_trap_subprocess(NULL, 0, 0);
+ g_test_trap_assert_failed();
+ g_test_trap_assert_stderr("PANIC: death message (errno: 123)\n"
+ "EXITING\n");
+ // NOTE: g_test doesn't offer facilities to observe the exit code.
+}
+
+static void __attribute__((constructor)) init(void)
+{
+ g_test_add_func("/panic/panic", test_panic);
+ g_test_add_func("/panic/panic_with_errno", test_panic_with_errno);
+ g_test_add_func("/panic/panic_customization", test_panic_customization);
+}
diff -Nru snapd-2.41+19.10.1/cmd/libsnap-confine-private/snap.c snapd-2.42.1+19.10/cmd/libsnap-confine-private/snap.c
--- snapd-2.41+19.10.1/cmd/libsnap-confine-private/snap.c 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/libsnap-confine-private/snap.c 2019-10-30 12:17:43.000000000 +0000
@@ -31,7 +31,7 @@
bool verify_security_tag(const char *security_tag, const char *snap_name)
{
const char *whitelist_re =
- "^snap\\.([a-z0-9](-?[a-z0-9])*(_[a-z0-9]{1,10})?)\\.([a-zA-Z0-9](-?[a-zA-Z0-9])*|hook\\.[a-z](-?[a-z])*)$";
+ "^snap\\.([a-z0-9](-?[a-z0-9])*(_[a-z0-9]{1,10})?)\\.([a-zA-Z0-9](-?[a-zA-Z0-9])*|hook\\.[a-z](-?[a-z0-9])*)$";
regex_t re;
if (regcomp(&re, whitelist_re, REG_EXTENDED) != 0)
die("can not compile regex %s", whitelist_re);
@@ -111,9 +111,8 @@
"snap instance name cannot be NULL");
goto out;
}
- // 40 char snap_name + '_' + 10 char instance_key + 1 extra overflow + 1
- // NULL
- char s[53] = { 0 };
+ // instance name length + 1 extra overflow + 1 NULL
+ char s[SNAP_INSTANCE_LEN + 1 + 1] = { 0 };
strncpy(s, instance_name, sizeof(s) - 1);
char *t = s;
@@ -175,7 +174,7 @@
err =
sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_KEY,
"instance key must contain at least one letter or digit");
- } else if (i > 10) {
+ } else if (i > SNAP_INSTANCE_KEY_LEN) {
err =
sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_INSTANCE_KEY,
"instance key must be shorter than 10 characters");
@@ -253,7 +252,7 @@
"snap name must be longer than 1 character");
goto out;
}
- if (n > 40) {
+ if (n > SNAP_NAME_LEN) {
err = sc_error_init(SC_SNAP_DOMAIN, SC_SNAP_INVALID_NAME,
"snap name must be shorter than 40 characters");
goto out;
diff -Nru snapd-2.41+19.10.1/cmd/libsnap-confine-private/snap.h snapd-2.42.1+19.10/cmd/libsnap-confine-private/snap.h
--- snapd-2.41+19.10.1/cmd/libsnap-confine-private/snap.h 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/libsnap-confine-private/snap.h 2019-10-30 12:17:43.000000000 +0000
@@ -37,6 +37,17 @@
SC_SNAP_INVALID_INSTANCE_NAME = 3,
};
+/* SNAP_NAME_LEN is the maximum length of a snap name, enforced by snapd and the
+ * store. */
+#define SNAP_NAME_LEN 40
+/* SNAP_INSTANCE_KEY_LEN is the maximum length of instance key, enforced locally
+ * by snapd. */
+#define SNAP_INSTANCE_KEY_LEN 10
+/* SNAP_INSTANCE_LEN is the maximum length of snap instance name, composed of
+ * the snap name, separator '_' and the instance key, enforced locally by
+ * snapd. */
+#define SNAP_INSTANCE_LEN (SNAP_NAME_LEN + 1 + SNAP_INSTANCE_KEY_LEN)
+
/**
* Validate the given snap name.
*
diff -Nru snapd-2.41+19.10.1/cmd/libsnap-confine-private/snap-test.c snapd-2.42.1+19.10/cmd/libsnap-confine-private/snap-test.c
--- snapd-2.41+19.10.1/cmd/libsnap-confine-private/snap-test.c 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/libsnap-confine-private/snap-test.c 2019-10-30 12:17:43.000000000 +0000
@@ -90,6 +90,10 @@
g_assert_true(verify_security_tag("snap.123test.123test", "123test"));
g_assert_true(verify_security_tag
("snap.123test.hook.configure", "123test"));
+
+ // regression test snap.eon-edg-shb-pulseaudio.hook.connect-plug-i2c
+ g_assert_true(verify_security_tag
+ ("snap.foo.hook.connect-plug-i2c", "foo"));
}
static void test_sc_is_hook_security_tag(void)
@@ -359,7 +363,6 @@
{
if (g_test_subprocess()) {
sc_snap_drop_instance_key("foo_bar", NULL, 0);
- g_test_fail();
return;
}
g_test_trap_subprocess(NULL, 0, 0);
@@ -373,7 +376,6 @@
char dest[10] = { 0 };
sc_snap_drop_instance_key("foo-foo-foo-foo-foo_bar", dest,
sizeof dest);
- g_test_fail();
return;
}
g_test_trap_subprocess(NULL, 0, 0);
@@ -385,7 +387,6 @@
if (g_test_subprocess()) {
char dest[3] = { 0 }; // "foo" sans the nil byte
sc_snap_drop_instance_key("foo", dest, sizeof dest);
- g_test_fail();
return;
}
g_test_trap_subprocess(NULL, 0, 0);
@@ -397,7 +398,20 @@
if (g_test_subprocess()) {
char dest[10] = { 0 };
sc_snap_drop_instance_key(NULL, dest, sizeof dest);
- g_test_fail();
+ return;
+ }
+ g_test_trap_subprocess(NULL, 0, 0);
+ g_test_trap_assert_failed();
+}
+
+static void test_sc_snap_drop_instance_key_short_dest_max(void)
+{
+ if (g_test_subprocess()) {
+ char dest[SNAP_NAME_LEN + 1] = { 0 };
+ /* 40 chars (max valid length), pretend dest is the same length, no space for terminator */
+ sc_snap_drop_instance_key
+ ("01234567890123456789012345678901234567890", dest,
+ sizeof dest - 1);
return;
}
g_test_trap_subprocess(NULL, 0, 0);
@@ -406,7 +420,7 @@
static void test_sc_snap_drop_instance_key_basic(void)
{
- char name[41] = { 0xff };
+ char name[SNAP_NAME_LEN + 1] = { 0xff };
sc_snap_drop_instance_key("foo_bar", name, sizeof name);
g_assert_cmpstr(name, ==, "foo");
@@ -426,6 +440,12 @@
memset(name, 0xff, sizeof name);
sc_snap_drop_instance_key("foo", name, sizeof name);
g_assert_cmpstr(name, ==, "foo");
+
+ memset(name, 0xff, sizeof name);
+ /* 40 chars - snap name length */
+ sc_snap_drop_instance_key("0123456789012345678901234567890123456789",
+ name, sizeof name);
+ g_assert_cmpstr(name, ==, "0123456789012345678901234567890123456789");
}
static void test_sc_snap_split_instance_name_trailing_nil(void)
@@ -434,7 +454,6 @@
char dest[3] = { 0 };
// pretend there is no place for trailing \0
sc_snap_split_instance_name("_", NULL, 0, dest, 0);
- g_test_fail();
return;
}
g_test_trap_subprocess(NULL, 0, 0);
@@ -447,7 +466,6 @@
char dest[10] = { 0 };
sc_snap_split_instance_name("foo_barbarbarbar", NULL, 0,
dest, sizeof dest);
- g_test_fail();
return;
}
g_test_trap_subprocess(NULL, 0, 0);
@@ -456,7 +474,7 @@
static void test_sc_snap_split_instance_name_basic(void)
{
- char name[41] = { 0xff };
+ char name[SNAP_NAME_LEN + 1] = { 0xff };
char instance[20] = { 0xff };
sc_snap_split_instance_name("foo_bar", name, sizeof name, instance,
@@ -558,6 +576,8 @@
test_sc_snap_drop_instance_key_short_dest);
g_test_add_func("/snap/sc_snap_drop_instance_key/short_dest2",
test_sc_snap_drop_instance_key_short_dest2);
+ g_test_add_func("/snap/sc_snap_drop_instance_key/short_dest_max",
+ test_sc_snap_drop_instance_key_short_dest_max);
g_test_add_func("/snap/sc_snap_split_instance_name/basic",
test_sc_snap_split_instance_name_basic);
diff -Nru snapd-2.41+19.10.1/cmd/libsnap-confine-private/test-utils.c snapd-2.42.1+19.10/cmd/libsnap-confine-private/test-utils.c
--- snapd-2.41+19.10.1/cmd/libsnap-confine-private/test-utils.c 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/libsnap-confine-private/test-utils.c 2019-10-30 12:17:43.000000000 +0000
@@ -16,6 +16,7 @@
*/
#include "test-utils.h"
+#include "string-utils.h"
#include "error.h"
#include "utils.h"
@@ -71,3 +72,37 @@
g_free(argv[3]);
g_free(argv);
}
+
+void
+ __attribute__((sentinel)) test_argc_argv(int *argcp, char ***argvp, ...)
+{
+ int argc = 0;
+ char **argv = NULL;
+ va_list ap;
+
+ /* find out how many elements there are */
+ va_start(ap, argvp);
+ while (NULL != va_arg(ap, const char *)) {
+ argc += 1;
+ }
+ va_end(ap);
+
+ /* argc + terminating NULL entry */
+ argv = calloc(argc + 1, sizeof argv[0]);
+ g_assert_nonnull(argv);
+
+ va_start(ap, argvp);
+ for (int i = 0; i < argc; i++) {
+ const char *arg = va_arg(ap, const char *);
+ char *arg_copy = sc_strdup(arg);
+ g_test_queue_free(arg_copy);
+ argv[i] = arg_copy;
+ }
+ va_end(ap);
+
+ /* free argv last, so that entries do not leak */
+ g_test_queue_free(argv);
+
+ *argcp = argc;
+ *argvp = argv;
+}
diff -Nru snapd-2.41+19.10.1/cmd/libsnap-confine-private/test-utils.h snapd-2.42.1+19.10/cmd/libsnap-confine-private/test-utils.h
--- snapd-2.41+19.10.1/cmd/libsnap-confine-private/test-utils.h 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/libsnap-confine-private/test-utils.h 2019-10-30 12:17:43.000000000 +0000
@@ -23,4 +23,10 @@
*/
void rm_rf_tmp(const char *dir);
+/**
+ * Create an argc + argv pair out of a NULL terminated argument list.
+ **/
+void
+ __attribute__((sentinel)) test_argc_argv(int *argcp, char ***argvp, ...);
+
#endif
diff -Nru snapd-2.41+19.10.1/cmd/libsnap-confine-private/test-utils-test.c snapd-2.42.1+19.10/cmd/libsnap-confine-private/test-utils-test.c
--- snapd-2.41+19.10.1/cmd/libsnap-confine-private/test-utils-test.c 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/libsnap-confine-private/test-utils-test.c 2019-10-30 12:17:43.000000000 +0000
@@ -39,7 +39,31 @@
g_test_trap_assert_failed();
}
+static void test_test_argc_argv(void)
+{
+ // Check that test_argc_argv() correctly stores data
+ int argc = 0;
+ char **argv = NULL;
+
+ test_argc_argv(&argc, &argv, NULL);
+ g_assert_cmpint(argc, ==, 0);
+ g_assert_nonnull(argv);
+ g_assert_null(argv[0]);
+
+ argc = 0;
+ argv = NULL;
+
+ test_argc_argv(&argc, &argv, "zero", "one", "two", NULL);
+ g_assert_cmpint(argc, ==, 3);
+ g_assert_nonnull(argv);
+ g_assert_cmpstr(argv[0], ==, "zero");
+ g_assert_cmpstr(argv[1], ==, "one");
+ g_assert_cmpstr(argv[2], ==, "two");
+ g_assert_null(argv[3]);
+}
+
static void __attribute__((constructor)) init(void)
{
g_test_add_func("/test-utils/rm_rf_tmp", test_rm_rf_tmp);
+ g_test_add_func("/test-utils/test_argc_argv", test_test_argc_argv);
}
diff -Nru snapd-2.41+19.10.1/cmd/libsnap-confine-private/utils.c snapd-2.42.1+19.10/cmd/libsnap-confine-private/utils.c
--- snapd-2.41+19.10.1/cmd/libsnap-confine-private/utils.c 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/libsnap-confine-private/utils.c 2019-10-30 12:17:43.000000000 +0000
@@ -23,23 +23,16 @@
#include
#include
-#include "utils.h"
#include "cleanup-funcs.h"
+#include "panic.h"
+#include "utils.h"
void die(const char *msg, ...)
{
- int saved_errno = errno;
- va_list va;
- va_start(va, msg);
- vfprintf(stderr, msg, va);
- va_end(va);
-
- if (errno != 0) {
- fprintf(stderr, ": %s\n", strerror(saved_errno));
- } else {
- fprintf(stderr, "\n");
- }
- exit(1);
+ va_list ap;
+ va_start(ap, msg);
+ sc_panicv(msg, ap);
+ va_end(ap);
}
struct sc_bool_name {
diff -Nru snapd-2.41+19.10.1/cmd/Makefile.am snapd-2.42.1+19.10/cmd/Makefile.am
--- snapd-2.41+19.10.1/cmd/Makefile.am 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/Makefile.am 2019-10-30 12:17:43.000000000 +0000
@@ -62,10 +62,14 @@
libsnap-confine-private/infofile-test.c \
libsnap-confine-private/infofile.c \
libsnap-confine-private/infofile.h \
+ libsnap-confine-private/panic-test.h \
+ libsnap-confine-private/panic.c \
+ libsnap-confine-private/panic.h \
snap-confine/seccomp-support-ext.c \
snap-confine/seccomp-support-ext.h \
snap-confine/selinux-support.c \
snap-confine/selinux-support.h \
+ snap-confine/snap-confine-invocation-test.c \
snap-confine/snap-confine-invocation.c \
snap-confine/snap-confine-invocation.h \
snap-discard-ns/snap-discard-ns.c
@@ -129,6 +133,8 @@
libsnap-confine-private/mount-opt.h \
libsnap-confine-private/mountinfo.c \
libsnap-confine-private/mountinfo.h \
+ libsnap-confine-private/panic.c \
+ libsnap-confine-private/panic.h \
libsnap-confine-private/privs.c \
libsnap-confine-private/privs.h \
libsnap-confine-private/secure-getenv.c \
@@ -159,6 +165,7 @@
libsnap-confine-private/locking-test.c \
libsnap-confine-private/mount-opt-test.c \
libsnap-confine-private/mountinfo-test.c \
+ libsnap-confine-private/panic-test.c \
libsnap-confine-private/privs-test.c \
libsnap-confine-private/secure-getenv-test.c \
libsnap-confine-private/snap-test.c \
@@ -330,8 +337,7 @@
snap-confine/mount-support-test.c \
snap-confine/ns-support-test.c \
snap-confine/snap-confine-args-test.c \
- snap-confine/snap-confine-invocation.c \
- snap-confine/snap-confine-invocation.h \
+ snap-confine/snap-confine-invocation-test.c \
snap-confine/snap-device-helper-test.c
snap_confine_unit_tests_CFLAGS = $(snap_confine_snap_confine_CFLAGS) $(GLIB_CFLAGS)
snap_confine_unit_tests_LDADD = $(snap_confine_snap_confine_LDADD) $(GLIB_LIBS)
diff -Nru snapd-2.41+19.10.1/cmd/snap/cmd_debug_state.go snapd-2.42.1+19.10/cmd/snap/cmd_debug_state.go
--- snapd-2.41+19.10.1/cmd/snap/cmd_debug_state.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap/cmd_debug_state.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,311 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package main
+
+import (
+ "fmt"
+ "os"
+ "sort"
+ "strconv"
+ "strings"
+ "text/tabwriter"
+
+ "github.com/jessevdk/go-flags"
+ "github.com/snapcore/snapd/i18n"
+ "github.com/snapcore/snapd/overlord/state"
+)
+
+type cmdDebugState struct {
+ timeMixin
+
+ st *state.State
+
+ Changes bool `long:"changes"`
+ TaskID string `long:"task"`
+ ChangeID string `long:"change"`
+
+ // flags for --change=N output
+ DotOutput bool `long:"dot"` // XXX: mildly useful (too crowded in many cases), but let's have it just in case
+ // When inspecting errors/undone tasks, those in Hold state are usually irrelevant, make it possible to ignore them
+ NoHoldState bool `long:"no-hold"`
+
+ Positional struct {
+ StateFilePath string `positional-args:"yes" positional-arg-name:""`
+ } `positional-args:"yes"`
+}
+
+var cmdDebugStateShortHelp = i18n.G("Inspect a snapd state file.")
+var cmdDebugStateLongHelp = i18n.G("Inspect a snapd state file, bypassing snapd API.")
+
+type byChangeID []*state.Change
+
+func (c byChangeID) Len() int { return len(c) }
+func (c byChangeID) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
+func (c byChangeID) Less(i, j int) bool { return c[i].ID() < c[j].ID() }
+
+func loadState(path string) (*state.State, error) {
+ if path == "" {
+ path = "state.json"
+ }
+ r, err := os.Open(path)
+ if err != nil {
+ return nil, fmt.Errorf("cannot read the state file: %s", err)
+ }
+ defer r.Close()
+
+ return state.ReadState(nil, r)
+}
+
+func init() {
+ addDebugCommand("state", cmdDebugStateShortHelp, cmdDebugStateLongHelp, func() flags.Commander {
+ return &cmdDebugState{}
+ }, timeDescs.also(map[string]string{
+ // TRANSLATORS: This should not start with a lowercase letter.
+ "change": i18n.G("ID of the change to inspect"),
+ "task": i18n.G("ID of the task to inspect"),
+ "dot": i18n.G("Dot (graphviz) output"),
+ "no-hold": i18n.G("Omit tasks in 'Hold' state in the change output"),
+ "changes": i18n.G("List all changes"),
+ }), nil)
+}
+
+type byLaneAndWaitTaskChain []*state.Task
+
+func (t byLaneAndWaitTaskChain) Len() int { return len(t) }
+func (t byLaneAndWaitTaskChain) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
+func (t byLaneAndWaitTaskChain) Less(i, j int) bool {
+ // cover the typical case (just one lane), and order by first lane
+ if t[i].Lanes()[0] == t[j].Lanes()[0] {
+ return waitChainSearch(t[i], t[j])
+ }
+ return t[i].Lanes()[0] < t[j].Lanes()[0]
+}
+
+func waitChainSearch(startT, searchT *state.Task) bool {
+ for _, cand := range startT.HaltTasks() {
+ if cand == searchT {
+ return true
+ }
+ if waitChainSearch(cand, searchT) {
+ return true
+ }
+ }
+
+ return false
+}
+
+func (c *cmdDebugState) writeDotOutput(st *state.State, changeID string) error {
+ st.Lock()
+ defer st.Unlock()
+
+ chg := st.Change(changeID)
+ if chg == nil {
+ return fmt.Errorf("no such change: %s", changeID)
+ }
+
+ fmt.Fprintf(Stdout, "digraph D{\n")
+ tasks := chg.Tasks()
+ for _, t := range tasks {
+ if c.NoHoldState && t.Status() == state.HoldStatus {
+ continue
+ }
+ fmt.Fprintf(Stdout, " %s [label=%q];\n", t.ID(), t.Kind())
+ for _, wt := range t.WaitTasks() {
+ if c.NoHoldState && wt.Status() == state.HoldStatus {
+ continue
+ }
+ fmt.Fprintf(Stdout, " %s -> %s;\n", t.ID(), wt.ID())
+ }
+ }
+ fmt.Fprintf(Stdout, "}\n")
+
+ return nil
+}
+
+func (c *cmdDebugState) showTasks(st *state.State, changeID string) error {
+ st.Lock()
+ defer st.Unlock()
+
+ chg := st.Change(changeID)
+ if chg == nil {
+ return fmt.Errorf("no such change: %s", changeID)
+ }
+
+ tasks := chg.Tasks()
+ sort.Sort(byLaneAndWaitTaskChain(tasks))
+
+ w := tabwriter.NewWriter(Stdout, 5, 3, 2, ' ', 0)
+ fmt.Fprintf(w, "Lanes\tID\tStatus\tSpawn\tReady\tKind\tSummary\n")
+ for _, t := range tasks {
+ if c.NoHoldState && t.Status() == state.HoldStatus {
+ continue
+ }
+ var lanes []string
+ for _, lane := range t.Lanes() {
+ lanes = append(lanes, fmt.Sprintf("%d", lane))
+ }
+ fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
+ strings.Join(lanes, ","),
+ t.ID(),
+ t.Status().String(),
+ c.fmtTime(t.SpawnTime()),
+ c.fmtTime(t.ReadyTime()),
+ t.Kind(),
+ t.Summary())
+ }
+
+ w.Flush()
+
+ for _, t := range tasks {
+ logs := t.Log()
+ if len(logs) > 0 {
+ fmt.Fprintf(Stdout, "---\n")
+ fmt.Fprintf(Stdout, "%s %s\n", t.ID(), t.Summary())
+ for _, log := range logs {
+ fmt.Fprintf(Stdout, " %s\n", log)
+ }
+ }
+ }
+
+ return nil
+}
+
+func (c *cmdDebugState) showChanges(st *state.State) error {
+ st.Lock()
+ defer st.Unlock()
+
+ changes := st.Changes()
+ sort.Sort(byChangeID(changes))
+
+ w := tabwriter.NewWriter(Stdout, 5, 3, 2, ' ', 0)
+ fmt.Fprintf(w, "ID\tStatus\tSpawn\tReady\tLabel\tSummary\n")
+ for _, chg := range changes {
+ fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n",
+ chg.ID(),
+ chg.Status().String(),
+ c.fmtTime(chg.SpawnTime()),
+ c.fmtTime(chg.ReadyTime()),
+ chg.Kind(),
+ chg.Summary())
+ }
+ w.Flush()
+
+ return nil
+}
+
+func (c *cmdDebugState) showTask(st *state.State, taskID string) error {
+ st.Lock()
+ defer st.Unlock()
+
+ task := st.Task(taskID)
+ if task == nil {
+ return fmt.Errorf("no such task: %s", taskID)
+ }
+
+ termWidth, _ := termSize()
+ termWidth -= 3
+ if termWidth > 100 {
+ // any wider than this and it gets hard to read
+ termWidth = 100
+ }
+
+ // the output of 'debug task' is yaml'ish
+ fmt.Fprintf(Stdout, "id: %s\nkind: %s\nsummary: %s\nstatus: %s\n",
+ taskID, task.Kind(),
+ task.Summary(),
+ task.Status().String())
+ log := task.Log()
+ if len(log) > 0 {
+ fmt.Fprintf(Stdout, "log: |\n")
+ for _, msg := range log {
+ if err := wrapLine(Stdout, []rune(msg), " ", termWidth); err != nil {
+ break
+ }
+ }
+ fmt.Fprintln(Stdout)
+ }
+
+ fmt.Fprintf(Stdout, "halt-tasks:")
+ if len(task.HaltTasks()) == 0 {
+ fmt.Fprintln(Stdout, " []")
+ } else {
+ fmt.Fprintln(Stdout)
+ for _, ht := range task.HaltTasks() {
+ fmt.Fprintf(Stdout, " - %s (%s)\n", ht.Kind(), ht.ID())
+ }
+ }
+
+ return nil
+}
+
+func (c *cmdDebugState) Execute(args []string) error {
+ st, err := loadState(c.Positional.StateFilePath)
+ if err != nil {
+ return err
+ }
+
+ // check valid combinations of args
+ var cmds []string
+ if c.Changes {
+ cmds = append(cmds, "--changes")
+ }
+ if c.ChangeID != "" {
+ cmds = append(cmds, "--change=")
+ }
+ if c.TaskID != "" {
+ cmds = append(cmds, "--task=")
+ }
+ if len(cmds) > 1 {
+ return fmt.Errorf("cannot use %s and %s together", cmds[0], cmds[1])
+ }
+
+ if c.DotOutput && c.ChangeID == "" {
+ return fmt.Errorf("--dot can only be used with --change=")
+ }
+ if c.NoHoldState && c.ChangeID == "" {
+ return fmt.Errorf("--no-hold can only be used with --change=")
+ }
+
+ if c.Changes {
+ return c.showChanges(st)
+ }
+
+ if c.ChangeID != "" {
+ _, err := strconv.ParseInt(c.ChangeID, 0, 64)
+ if err != nil {
+ return fmt.Errorf("invalid change: %s", c.ChangeID)
+ }
+ if c.DotOutput {
+ return c.writeDotOutput(st, c.ChangeID)
+ }
+ return c.showTasks(st, c.ChangeID)
+ }
+
+ if c.TaskID != "" {
+ _, err := strconv.ParseInt(c.TaskID, 0, 64)
+ if err != nil {
+ return fmt.Errorf("invalid task: %s", c.TaskID)
+ }
+ return c.showTask(st, c.TaskID)
+ }
+
+ // show changes by default
+ return c.showChanges(st)
+}
diff -Nru snapd-2.41+19.10.1/cmd/snap/cmd_debug_state_test.go snapd-2.42.1+19.10/cmd/snap/cmd_debug_state_test.go
--- snapd-2.41+19.10.1/cmd/snap/cmd_debug_state_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap/cmd_debug_state_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,204 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package main_test
+
+import (
+ "io/ioutil"
+ "path/filepath"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/cmd/snap"
+)
+
+var stateJSON = []byte(`
+{
+ "last-task-id": 31,
+ "last-change-id": 2,
+
+ "data": {
+ "snaps": {}
+ },
+ "changes": {
+ "1": {
+ "id": "1",
+ "kind": "install-snap",
+ "summary": "install a snap",
+ "status": 0,
+ "data": {"snap-names": ["a"]},
+ "task-ids": ["11","12"]
+ },
+ "2": {
+ "id": "2",
+ "kind": "revert-snap",
+ "summary": "revert c snap",
+ "status": 0,
+ "data": {"snap-names": ["c"]},
+ "task-ids": ["21","31"]
+ }
+ },
+ "tasks": {
+ "11": {
+ "id": "11",
+ "change": "1",
+ "kind": "download-snap",
+ "summary": "Download snap a from channel edge",
+ "status": 4,
+ "data": {"snap-setup": {
+ "channel": "edge",
+ "flags": 1
+ }},
+ "halt-tasks": ["12"]
+ },
+ "12": {"id": "12", "change": "1", "kind": "some-other-task"},
+ "21": {
+ "id": "21",
+ "change": "2",
+ "kind": "download-snap",
+ "summary": "Download snap b from channel beta",
+ "status": 4,
+ "data": {"snap-setup": {
+ "channel": "beta",
+ "flags": 2
+ }},
+ "halt-tasks": ["12"]
+ },
+ "31": {
+ "id": "31",
+ "change": "2",
+ "kind": "prepare-snap",
+ "summary": "Prepare snap c",
+ "status": 4,
+ "data": {"snap-setup": {
+ "channel": "stable",
+ "flags": 1073741828
+ }},
+ "halt-tasks": ["12"],
+ "log": ["logline1", "logline2"]
+ }
+ }
+}
+`)
+
+func (s *SnapSuite) TestDebugChanges(c *C) {
+ dir := c.MkDir()
+ stateFile := filepath.Join(dir, "test-state.json")
+ c.Assert(ioutil.WriteFile(stateFile, stateJSON, 0644), IsNil)
+
+ rest, err := main.Parser(main.Client()).ParseArgs([]string{"debug", "state", "--changes", stateFile})
+ c.Assert(err, IsNil)
+ c.Assert(rest, DeepEquals, []string{})
+ c.Check(s.Stdout(), Matches,
+ "ID Status Spawn Ready Label Summary\n"+
+ "1 Do 0001-01-01 0001-01-01 install-snap install a snap\n"+
+ "2 Done 0001-01-01 0001-01-01 revert-snap revert c snap\n")
+ c.Check(s.Stderr(), Equals, "")
+}
+
+func (s *SnapSuite) TestDebugChangesMissingState(c *C) {
+ _, err := main.Parser(main.Client()).ParseArgs([]string{"debug", "state", "--changes", "/missing-state.json"})
+ c.Check(err, ErrorMatches, "cannot read the state file: open /missing-state.json: no such file or directory")
+}
+
+func (s *SnapSuite) TestDebugTask(c *C) {
+ dir := c.MkDir()
+ stateFile := filepath.Join(dir, "test-state.json")
+ c.Assert(ioutil.WriteFile(stateFile, stateJSON, 0644), IsNil)
+
+ rest, err := main.Parser(main.Client()).ParseArgs([]string{"debug", "state", "--task=31", stateFile})
+ c.Assert(err, IsNil)
+ c.Assert(rest, DeepEquals, []string{})
+ c.Check(s.Stdout(), Equals, "id: 31\n"+
+ "kind: prepare-snap\n"+
+ "summary: Prepare snap c\n"+
+ "status: Done\n"+
+ "log: |\n"+
+ " logline1\n"+
+ " logline2\n"+
+ "\n"+
+ "halt-tasks:\n"+
+ " - some-other-task (12)\n")
+ c.Check(s.Stderr(), Equals, "")
+}
+
+func (s *SnapSuite) TestDebugTaskEmptyLists(c *C) {
+ dir := c.MkDir()
+ stateFile := filepath.Join(dir, "test-state.json")
+ c.Assert(ioutil.WriteFile(stateFile, stateJSON, 0644), IsNil)
+
+ rest, err := main.Parser(main.Client()).ParseArgs([]string{"debug", "state", "--task=12", stateFile})
+ c.Assert(err, IsNil)
+ c.Assert(rest, DeepEquals, []string{})
+ c.Check(s.Stdout(), Equals, "id: 12\n"+
+ "kind: some-other-task\n"+
+ "summary: \n"+
+ "status: Do\n"+
+ "halt-tasks: []\n")
+ c.Check(s.Stderr(), Equals, "")
+}
+
+func (s *SnapSuite) TestDebugTaskMissingState(c *C) {
+ _, err := main.Parser(main.Client()).ParseArgs([]string{"debug", "state", "--task=1", "/missing-state.json"})
+ c.Check(err, ErrorMatches, "cannot read the state file: open /missing-state.json: no such file or directory")
+}
+
+func (s *SnapSuite) TestDebugTaskNoSuchTaskError(c *C) {
+ dir := c.MkDir()
+ stateFile := filepath.Join(dir, "test-state.json")
+ c.Assert(ioutil.WriteFile(stateFile, stateJSON, 0644), IsNil)
+
+ _, err := main.Parser(main.Client()).ParseArgs([]string{"debug", "state", "--task=99", stateFile})
+ c.Check(err, ErrorMatches, "no such task: 99")
+}
+
+func (s *SnapSuite) TestDebugTaskMutuallyExclusiveCommands(c *C) {
+ dir := c.MkDir()
+ stateFile := filepath.Join(dir, "test-state.json")
+ c.Assert(ioutil.WriteFile(stateFile, stateJSON, 0644), IsNil)
+
+ _, err := main.Parser(main.Client()).ParseArgs([]string{"debug", "state", "--task=99", "--changes", stateFile})
+ c.Check(err, ErrorMatches, "cannot use --changes and --task= together")
+
+ _, err = main.Parser(main.Client()).ParseArgs([]string{"debug", "state", "--changes", "--change=1", stateFile})
+ c.Check(err, ErrorMatches, "cannot use --changes and --change= together")
+
+ _, err = main.Parser(main.Client()).ParseArgs([]string{"debug", "state", "--change=1", "--task=1", stateFile})
+ c.Check(err, ErrorMatches, "cannot use --change= and --task= together")
+}
+
+func (s *SnapSuite) TestDebugTasks(c *C) {
+ dir := c.MkDir()
+ stateFile := filepath.Join(dir, "test-state.json")
+ c.Assert(ioutil.WriteFile(stateFile, stateJSON, 0644), IsNil)
+
+ rest, err := main.Parser(main.Client()).ParseArgs([]string{"debug", "state", "--change=1", stateFile})
+ c.Assert(err, IsNil)
+ c.Assert(rest, DeepEquals, []string{})
+ c.Check(s.Stdout(), Matches,
+ "Lanes ID Status Spawn Ready Kind Summary\n"+
+ "0 11 Done 0001-01-01 0001-01-01 download-snap Download snap a from channel edge\n"+
+ "0 12 Do 0001-01-01 0001-01-01 some-other-task \n")
+ c.Check(s.Stderr(), Equals, "")
+}
+
+func (s *SnapSuite) TestDebugTasksMissingState(c *C) {
+ _, err := main.Parser(main.Client()).ParseArgs([]string{"debug", "state", "--change=1", "/missing-state.json"})
+ c.Check(err, ErrorMatches, "cannot read the state file: open /missing-state.json: no such file or directory")
+}
diff -Nru snapd-2.41+19.10.1/cmd/snap/cmd_debug_validate_seed.go snapd-2.42.1+19.10/cmd/snap/cmd_debug_validate_seed.go
--- snapd-2.41+19.10.1/cmd/snap/cmd_debug_validate_seed.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap/cmd_debug_validate_seed.go 2019-10-30 12:17:43.000000000 +0000
@@ -22,7 +22,7 @@
import (
"github.com/jessevdk/go-flags"
- "github.com/snapcore/snapd/image"
+ "github.com/snapcore/snapd/seed"
)
type cmdValidateSeed struct {
@@ -46,5 +46,5 @@
return ErrExtraArgs
}
- return image.ValidateSeed(string(x.Positionals.SeedYamlPath))
+ return seed.ValidateFromYaml(string(x.Positionals.SeedYamlPath))
}
diff -Nru snapd-2.41+19.10.1/cmd/snap/cmd_debug_validate_seed_test.go snapd-2.42.1+19.10/cmd/snap/cmd_debug_validate_seed_test.go
--- snapd-2.41+19.10.1/cmd/snap/cmd_debug_validate_seed_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap/cmd_debug_validate_seed_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -47,3 +47,18 @@
_, err = snap.Parser(snap.Client()).ParseArgs([]string{"debug", "validate-seed", tmpf})
c.Assert(err, ErrorMatches, "cannot read seed yaml: empty element in seed")
}
+
+func (s *SnapSuite) TestDebugValidateSeedDuplicatedSnap(c *C) {
+ tmpf := filepath.Join(c.MkDir(), "seed.yaml")
+ err := ioutil.WriteFile(tmpf, []byte(`
+snaps:
+ - name: foo
+ file: foo.snap
+ - name: foo
+ file: bar.snap
+`), 0644)
+ c.Assert(err, IsNil)
+
+ _, err = snap.Parser(snap.Client()).ParseArgs([]string{"debug", "validate-seed", tmpf})
+ c.Assert(err, ErrorMatches, `cannot read seed yaml: snap name "foo" must be unique`)
+}
diff -Nru snapd-2.41+19.10.1/cmd/snap/cmd_download.go snapd-2.42.1+19.10/cmd/snap/cmd_download.go
--- snapd-2.41+19.10.1/cmd/snap/cmd_download.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap/cmd_download.go 2019-10-30 12:17:43.000000000 +0000
@@ -140,6 +140,8 @@
Channel: x.Channel,
CohortKey: x.CohortKey,
Revision: revision,
+ // if something goes wrong, don't force it to start over again
+ LeavePartialOnError: true,
}
snapPath, snapInfo, err := tsto.DownloadSnap(snapName, dlOpts)
if err != nil {
diff -Nru snapd-2.41+19.10.1/cmd/snap/cmd_help.go snapd-2.42.1+19.10/cmd/snap/cmd_help.go
--- snapd-2.41+19.10.1/cmd/snap/cmd_help.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap/cmd_help.go 2019-10-30 12:17:43.000000000 +0000
@@ -216,7 +216,7 @@
}, {
Label: i18n.G("Other"),
Description: i18n.G("miscellanea"),
- Commands: []string{"version", "warnings", "okay", "ack", "known", "create-cohort"},
+ Commands: []string{"version", "warnings", "okay", "ack", "known", "model", "create-cohort"},
}, {
Label: i18n.G("Development"),
Description: i18n.G("developer-oriented features"),
diff -Nru snapd-2.41+19.10.1/cmd/snap/cmd_info.go snapd-2.42.1+19.10/cmd/snap/cmd_info.go
--- snapd-2.41+19.10.1/cmd/snap/cmd_info.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap/cmd_info.go 2019-10-30 12:17:43.000000000 +0000
@@ -683,7 +683,7 @@
noneOK := true
for i, snapName := range x.Positional.Snaps {
- snapName := norm(string(snapName))
+ snapName := string(snapName)
if i > 0 {
fmt.Fprintln(w, "---")
}
@@ -693,9 +693,9 @@
}
if diskSnap, err := clientSnapFromPath(snapName); err == nil {
- iw.setupDiskSnap(snapName, diskSnap)
+ iw.setupDiskSnap(norm(snapName), diskSnap)
} else {
- remoteSnap, resInfo, _ := x.client.FindOne(snapName)
+ remoteSnap, resInfo, _ := x.client.FindOne(snap.InstanceSnap(snapName))
localSnap, _, _ := x.client.Snap(snapName)
iw.setupSnap(localSnap, remoteSnap, resInfo)
}
diff -Nru snapd-2.41+19.10.1/cmd/snap/cmd_info_test.go snapd-2.42.1+19.10/cmd/snap/cmd_info_test.go
--- snapd-2.41+19.10.1/cmd/snap/cmd_info_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap/cmd_info_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -1059,3 +1059,80 @@
indented.
`)
}
+
+const mockInfoJSONParallelInstance = `
+{
+ "type": "sync",
+ "status-code": 200,
+ "status": "OK",
+ "result": {
+ "channel": "stable",
+ "confinement": "strict",
+ "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/",
+ "developer": "canonical",
+ "publisher": {
+ "id": "canonical",
+ "username": "canonical",
+ "display-name": "Canonical",
+ "validation": "verified"
+ },
+ "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6",
+ "install-date": "2006-01-02T22:04:07.123456789Z",
+ "installed-size": 1024,
+ "name": "hello_foo",
+ "private": false,
+ "revision": "100",
+ "status": "available",
+ "summary": "The GNU Hello snap",
+ "type": "app",
+ "version": "2.10",
+ "license": "",
+ "tracking-channel": "beta"
+ }
+}
+`
+
+func (s *infoSuite) TestInfoParllelInstance(c *check.C) {
+ n := 0
+ s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
+ switch n {
+ case 0:
+ c.Check(r.Method, check.Equals, "GET")
+ c.Check(r.URL.Path, check.Equals, "/v2/find")
+ q := r.URL.Query()
+ // asks for the instance snap
+ c.Check(q.Get("name"), check.Equals, "hello")
+ fmt.Fprintln(w, mockInfoJSONWithChannels)
+ case 1:
+ c.Check(r.Method, check.Equals, "GET")
+ c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello_foo")
+ fmt.Fprintln(w, mockInfoJSONParallelInstance)
+ default:
+ c.Fatalf("expected to get 2 requests, now on %d (%v)", n+1, r)
+ }
+
+ n++
+ })
+ rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello_foo"})
+ c.Assert(err, check.IsNil)
+ c.Assert(rest, check.DeepEquals, []string{})
+ // make sure local and remote info is combined in the output
+ c.Check(s.Stdout(), check.Equals, `name: hello_foo
+summary: The GNU Hello snap
+publisher: Canonical*
+license: unset
+description: |
+ GNU hello prints a friendly greeting. This is part of the snapcraft tour at
+ https://snapcraft.io/
+snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6
+tracking: beta
+refresh-date: 2006-01-02
+channels:
+ 1/stable: 2.10 2018-12-18 (1) 65kB -
+ 1/candidate: ^
+ 1/beta: ^
+ 1/edge: ^
+installed: 2.10 (100) 1kB disabled
+`)
+ c.Check(s.Stderr(), check.Equals, "")
+}
diff -Nru snapd-2.41+19.10.1/cmd/snap/cmd_model.go snapd-2.42.1+19.10/cmd/snap/cmd_model.go
--- snapd-2.41+19.10.1/cmd/snap/cmd_model.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap/cmd_model.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,330 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package main
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/jessevdk/go-flags"
+
+ "github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/client"
+ "github.com/snapcore/snapd/i18n"
+)
+
+var (
+ shortModelHelp = i18n.G("Get the active model for this device")
+ longModelHelp = i18n.G(`
+The model command returns the active model assertion information for this
+device.
+
+By default, only the essential model identification information is
+included in the output, but this can be expanded to include all of an
+assertion's non-meta headers.
+
+The verbose output is presented in a structured, yaml-like format.
+
+Similarly, the active serial assertion can be used for the output instead of the
+model assertion.
+`)
+
+ invalidTypeMessage = i18n.G("invalid type for %q header")
+ errNoMainAssertion = errors.New(i18n.G("device not ready yet (no assertions found)"))
+ errNoSerial = errors.New(i18n.G("device not registered yet (no serial assertion found)"))
+ errNoVerboseAssertion = errors.New(i18n.G("cannot use --verbose with --assertion"))
+
+ // this list is a "nice" "human" "readable" "ordering" of headers to print
+ // off, sorted in lexographical order with meta headers and primary key
+ // headers removed, and big nasty keys such as device-key-sha3-384 and
+ // device-key at the bottom
+ // it also contains both serial and model assertion headers, but we
+ // follow the same code path for both assertion types and some of the
+ // headers are shared between the two, so it still works out correctly
+ niceOrdering = [...]string{
+ "architecture",
+ "base",
+ "classic",
+ "display-name",
+ "gadget",
+ "kernel",
+ "revision",
+ "timestamp",
+ "required-snaps",
+ "device-key-sha3-384",
+ "device-key",
+ }
+)
+
+type cmdModel struct {
+ waitMixin
+ timeMixin
+ colorMixin
+
+ Serial bool `long:"serial"`
+ Verbose bool `long:"verbose"`
+ Assertion bool `long:"assertion"`
+}
+
+func init() {
+ addCommand("model",
+ shortModelHelp,
+ longModelHelp,
+ func() flags.Commander {
+ return &cmdModel{}
+ }, colorDescs.also(timeDescs).also(waitDescs).also(map[string]string{
+ "assertion": i18n.G("Print the raw assertion."),
+ "verbose": i18n.G("Print all specific assertion fields."),
+ "serial": i18n.G(
+ "Print the serial assertion instead of the model assertion."),
+ }),
+ []argDesc{},
+ )
+}
+
+func (x *cmdModel) Execute(args []string) error {
+ if x.Verbose && x.Assertion {
+ // can't do a verbose mode for the assertion
+ return errNoVerboseAssertion
+ }
+
+ var mainAssertion asserts.Assertion
+ serialAssertion, serialErr := x.client.CurrentSerialAssertion()
+ modelAssertion, modelErr := x.client.CurrentModelAssertion()
+
+ // if we didn't get a model assertion bail early
+ if modelErr != nil {
+ if client.IsAssertionNotFoundError(modelErr) {
+ // device is not registered yet - use specific error message
+ return errNoMainAssertion
+ }
+ return modelErr
+ }
+
+ // if the serial assertion error is anything other than not found, also
+ // bail early
+ // the serial assertion not being found may not be fatal
+ if serialErr != nil && !client.IsAssertionNotFoundError(serialErr) {
+ return serialErr
+ }
+
+ if x.Serial {
+ mainAssertion = serialAssertion
+ } else {
+ mainAssertion = modelAssertion
+ }
+
+ if x.Assertion {
+ // if we are using the serial assertion and we specifically didn't find the
+ // serial assertion, bail with specific error
+ if x.Serial && client.IsAssertionNotFoundError(serialErr) {
+ return errNoMainAssertion
+ }
+
+ _, err := Stdout.Write(asserts.Encode(mainAssertion))
+ return err
+ }
+
+ termWidth, _ := termSize()
+ termWidth -= 3
+ if termWidth > 100 {
+ // any wider than this and it gets hard to read
+ termWidth = 100
+ }
+
+ esc := x.getEscapes()
+
+ w := tabWriter()
+
+ if x.Serial && client.IsAssertionNotFoundError(serialErr) {
+ // for serial assertion, the primary keys are output (model and
+ // brand-id), but if we didn't find the serial assertion then we still
+ // output the brand-id and model from the model assertion, but also
+ // return a devNotReady error
+ fmt.Fprintf(w, "brand-id:\t%s\n", modelAssertion.HeaderString("brand-id"))
+ fmt.Fprintf(w, "model:\t%s\n", modelAssertion.HeaderString("model"))
+ w.Flush()
+ return errNoSerial
+ }
+
+ // the rest of this function is the main flow for outputting either the
+ // model or serial assertion in normal or verbose mode
+
+ // for the `snap model` case with no options, we don't want colons, we want
+ // to be like `snap version`
+ separator := ":"
+ if !x.Verbose && !x.Serial {
+ separator = ""
+ }
+
+ // ordering of the primary keys for model: brand, model, serial
+ // ordering of primary keys for serial is brand-id, model, serial
+
+ // output brand/brand-id
+ brandIDHeader := mainAssertion.HeaderString("brand-id")
+ modelHeader := mainAssertion.HeaderString("model")
+ // for the serial header, if there's no serial yet, it's not an error for
+ // model (and we already handled the serial error above) but need to add a
+ // parenthetical about the device not being registered yet
+ var serial string
+ if client.IsAssertionNotFoundError(serialErr) {
+ if x.Verbose || x.Serial {
+ // verbose and serial are yamlish, so we need to escape the dash
+ serial = esc.dash
+ } else {
+ serial = "-"
+ }
+ serial += " (device not registered yet)"
+ } else {
+ serial = serialAssertion.HeaderString("serial")
+ }
+
+ // handle brand/brand-id (the former is only on `snap model` w/o opts)
+ if x.Serial || x.Verbose {
+ fmt.Fprintf(w, "brand-id:\t%s\n", brandIDHeader)
+ } else {
+ // for the model command (not --serial) we want to show a publisher
+ // style display of "brand" instead of just "brand-id"
+ storeAccount, err := x.client.StoreAccount(brandIDHeader)
+ if err != nil {
+ return err
+ }
+ // use the longPublisher helper to format the brand store account
+ // like we do in `snap info`
+ fmt.Fprintf(w, "brand%s\t%s\n", separator, longPublisher(x.getEscapes(), storeAccount))
+ }
+
+ // handle model, on `snap model` we try to add display-name if it exists
+ if x.Serial {
+ fmt.Fprintf(w, "model:\t%s\n", modelHeader)
+ } else {
+ // for model, if there's a display-name, we show that first with the
+ // real model in parenthesis
+ if displayName := modelAssertion.HeaderString("display-name"); displayName != "" {
+ modelHeader = fmt.Sprintf("%s (%s)", displayName, modelHeader)
+ }
+ fmt.Fprintf(w, "model%s\t%s\n", separator, modelHeader)
+ }
+
+ // serial is same for all variants
+ fmt.Fprintf(w, "serial%s\t%s\n", separator, serial)
+
+ // --verbose means output more information
+ if x.Verbose {
+ allHeadersMap := mainAssertion.Headers()
+
+ for _, headerName := range niceOrdering {
+ invalidTypeErr := fmt.Errorf(invalidTypeMessage, headerName)
+
+ headerValue, ok := allHeadersMap[headerName]
+ // make sure the header is in the map
+ if !ok {
+ continue
+ }
+
+ // switch on which header it is to handle some special cases
+ switch headerName {
+ // list of scalars
+ case "required-snaps":
+ headerIfaceList, ok := headerValue.([]interface{})
+ if !ok {
+ return invalidTypeErr
+ }
+ if len(headerIfaceList) == 0 {
+ continue
+ }
+ fmt.Fprintf(w, "%s:\t\n", headerName)
+ for _, elem := range headerIfaceList {
+ headerStringElem, ok := elem.(string)
+ if !ok {
+ return invalidTypeErr
+ }
+ // note we don't wrap these, since for now this is
+ // specifically just required-snaps and so all of these
+ // will be snap names which are required to be short
+ fmt.Fprintf(w, " - %s\n", headerStringElem)
+ }
+
+ //timestamp needs to be formatted with fmtTime from the timeMixin
+ case "timestamp":
+ timestamp, ok := headerValue.(string)
+ if !ok {
+ return invalidTypeErr
+ }
+
+ // parse the time string as RFC3339, which is what the format is
+ // always in for assertions
+ t, err := time.Parse(time.RFC3339, timestamp)
+ if err != nil {
+ return err
+ }
+ fmt.Fprintf(w, "timestamp:\t%s\n", x.fmtTime(t))
+
+ // long string key we don't want to rewrap but can safely handle
+ // on "reasonable" width terminals
+ case "device-key-sha3-384":
+ // also flush the writer before continuing so the previous keys
+ // don't try to align with this key
+ w.Flush()
+ headerString, ok := headerValue.(string)
+ if !ok {
+ return invalidTypeErr
+ }
+
+ switch {
+ case termWidth > 86:
+ fmt.Fprintf(w, "device-key-sha3-384: %s\n", headerString)
+ case termWidth <= 86 && termWidth > 66:
+ fmt.Fprintln(w, "device-key-sha3-384: |")
+ wrapLine(w, []rune(headerString), " ", termWidth)
+ }
+
+ // long base64 key we can rewrap safely
+ case "device-key":
+ headerString, ok := headerValue.(string)
+ if !ok {
+ return invalidTypeErr
+ }
+ // the string value here has newlines inserted as part of the
+ // raw assertion, but base64 doesn't care about whitespace, so
+ // it's safe to split by newlines and re-wrap to make it
+ // prettier
+ headerString = strings.Join(
+ strings.Split(headerString, "\n"),
+ "")
+ fmt.Fprintln(w, "device-key: |")
+ wrapLine(w, []rune(headerString), " ", termWidth)
+
+ // the default is all the rest of short scalar values, which all
+ // should be strings
+ default:
+ headerString, ok := headerValue.(string)
+ if !ok {
+ return invalidTypeErr
+ }
+ fmt.Fprintf(w, "%s:\t%s\n", headerName, headerString)
+ }
+ }
+ }
+
+ return w.Flush()
+}
diff -Nru snapd-2.41+19.10.1/cmd/snap/cmd_model_test.go snapd-2.42.1+19.10/cmd/snap/cmd_model_test.go
--- snapd-2.41+19.10.1/cmd/snap/cmd_model_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap/cmd_model_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,458 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package main_test
+
+import (
+ "fmt"
+ "net/http"
+
+ "gopkg.in/check.v1"
+
+ snap "github.com/snapcore/snapd/cmd/snap"
+)
+
+const happyModelAssertionResponse = `type: model
+authority-id: mememe
+series: 16
+brand-id: mememe
+model: test-model
+architecture: amd64
+base: core18
+gadget: pc=18
+kernel: pc-kernel=18
+required-snaps:
+ - core
+ - hello-world
+timestamp: 2017-07-27T00:00:00.0Z
+sign-key-sha3-384: 8B3Wmemeu3H6i4dEV4Q85Q4gIUCHIBCNMHq49e085QeLGHi7v27l3Cqmemer4__t
+
+AcLBcwQAAQoAHRYhBMbX+t6MbKGH5C3nnLZW7+q0g6ELBQJdTdwTAAoJELZW7+q0g6ELEvgQAI3j
+jXTqR6kKOqvw94pArwdMDUaZ++tebASAZgso8ejrW2DQGWSc0Q7SQICIR8bvHxqS1GtupQswOzwS
+U8hjDTv7WEchH1jylyTj/1W1GernmitTKycecRlEkSOE+EpuqBFgTtj6PdA1Fj3CiCRi1rLMhgF2
+luCOitBLaP+E8P3fuATsLqqDLYzt1VY4Y14MU75hMn+CxAQdnOZTI+NzGMasPsldmOYCPNaN/b3N
+6/fDLU47RtNlMJ3K0Tz8kj0bqRbegKlD0RdNbAgo9iZwNmrr5E9WCu9f/0rUor/NIxO77H2ExIll
+zhmsZ7E6qlxvAgBmzKgAXrn68gGrBkIb0eXKiCaKy/i2ApvjVZ9HkOzA6Ldd+SwNJv/iA8rdiMsq
+p2BfKV5f3ju5b6+WktHxAakJ8iqQmj9Yh7piHjsOAUf1PEJd2s2nqQ+pEEn1F0B23gVCY/Fa9YRQ
+iKtWVeL3rBw4dSAaK9rpTMqlNcr+yrdXfTK5YzkCC6RU4yzc5MW0hKeseeSiEDSaRYxvftjFfVNa
+ZaVXKg8Lu+cHtCJDeYXEkPIDQzXswdBO1M8Mb9D0mYxQwHxwvsWv1DByB+Otq08EYgPh4kyHo7ag
+85yK2e/NQ/fxSwQJMhBF74jM1z9arq6RMiE/KOleFAOraKn2hcROKnEeinABW+sOn6vNuMVv
+`
+
+const happyModelWithDisplayNameAssertionResponse = `type: model
+authority-id: mememe
+series: 16
+brand-id: mememe
+model: test-model
+architecture: amd64
+display-name: Model Name
+base: core18
+gadget: pc=18
+kernel: pc-kernel=18
+required-snaps:
+ - core
+ - hello-world
+timestamp: 2017-07-27T00:00:00.0Z
+sign-key-sha3-384: 8B3Wmemeu3H6i4dEV4Q85Q4gIUCHIBCNMHq49e085QeLGHi7v27l3Cqmemer4__t
+
+AcLBcwQAAQoAHRYhBMbX+t6MbKGH5C3nnLZW7+q0g6ELBQJdTdwTAAoJELZW7+q0g6ELEvgQAI3j
+jXTqR6kKOqvw94pArwdMDUaZ++tebASAZgso8ejrW2DQGWSc0Q7SQICIR8bvHxqS1GtupQswOzwS
+U8hjDTv7WEchH1jylyTj/1W1GernmitTKycecRlEkSOE+EpuqBFgTtj6PdA1Fj3CiCRi1rLMhgF2
+luCOitBLaP+E8P3fuATsLqqDLYzt1VY4Y14MU75hMn+CxAQdnOZTI+NzGMasPsldmOYCPNaN/b3N
+6/fDLU47RtNlMJ3K0Tz8kj0bqRbegKlD0RdNbAgo9iZwNmrr5E9WCu9f/0rUor/NIxO77H2ExIll
+zhmsZ7E6qlxvAgBmzKgAXrn68gGrBkIb0eXKiCaKy/i2ApvjVZ9HkOzA6Ldd+SwNJv/iA8rdiMsq
+p2BfKV5f3ju5b6+WktHxAakJ8iqQmj9Yh7piHjsOAUf1PEJd2s2nqQ+pEEn1F0B23gVCY/Fa9YRQ
+iKtWVeL3rBw4dSAaK9rpTMqlNcr+yrdXfTK5YzkCC6RU4yzc5MW0hKeseeSiEDSaRYxvftjFfVNa
+ZaVXKg8Lu+cHtCJDeYXEkPIDQzXswdBO1M8Mb9D0mYxQwHxwvsWv1DByB+Otq08EYgPh4kyHo7ag
+85yK2e/NQ/fxSwQJMhBF74jM1z9arq6RMiE/KOleFAOraKn2hcROKnEeinABW+sOn6vNuMVv
+`
+
+const happyAccountAssertionResponse = `type: account
+authority-id: canonical
+account-id: mememe
+display-name: MeMeMe
+timestamp: 2016-04-01T00:00:00.0Z
+username: meuser
+validation: certified
+sign-key-sha3-384: -CvQKAwRQ5h3Ffn10FILJoEZUXOv6km9FwA80-Rcj-f-6jadQ89VRswHNiEB9Lxk
+
+AcLDXAQAAQoABgUCV7UYzwAKCRDUpVvql9g3IK7uH/4udqNOurx5WYVknzXdwekp0ovHCQJ0iBPw
+TSFxEVr9faZSzb7eqJ1WicHsShf97PYS3ClRYAiluFsjRA8Y03kkSVJHjC+sIwGFubsnkmgflt6D
+WEmYIl0UBmeaEDS8uY4Xvp9NsLTzNEj2kvzy/52gKaTc1ZSl5RDL9ppMav+0V9iBYpiDPBWH2rJ+
+aDSD8Rkyygm0UscfAKyDKH4lrvZ0WkYyi1YVNPrjQ/AtBySh6Q4iJ3LifzKa9woIyAuJET/4/FPY
+oirqHAfuvNod36yNQIyNqEc20AvTvZNH0PSsg4rq3DLjIPzv5KbJO9lhsasNJK1OdL6x8Yqrdsbk
+ldZp4qkzfjV7VOMQKaadfcZPRaVVeJWOBnBiaukzkhoNlQi1sdCdkBB/AJHZF8QXw6c7vPDcfnCV
+1lW7ddQ2p8IsJbT6LzpJu3GW/P4xhNgCjtCJ1AJm9a9RqLwQYgdLZwwDa9iCRtqTbRXBlfy3apps
+1VjbQ3h5iCd0hNfwDBnGVm1rhLKHCD1DUdNE43oN2ZlE7XGyh0HFV6vKlpqoW3eoXCIxWu+HBY96
++LSl/jQgCkb0nxYyzEYK4Reb31D0mYw1Nji5W+MIF5E09+DYZoOT0UvR05YMwMEOeSdI/hLWg/5P
+k+GDK+/KopMmpd4D1+jjtF7ZvqDpmAV98jJGB2F88RyVb4gcjmFFyTi4Kv6vzz/oLpbm0qrizC0W
+HLGDN/ymGA5sHzEgEx7U540vz/q9VX60FKqL2YZr/DcyY9GKX5kCG4sNqIIHbcJneZ4frM99oVDu
+7Jv+DIx/Di6D1ULXol2XjxbbJLKHFtHksR97ceaFvcZwTogC61IYUBJCvvMoqdXAWMhEXCr0QfQ5
+Xbi31XW2d4/lF/zWlAkRnGTzufIXFni7+nEuOK0SQEzO3/WaRedK1SGOOtTDjB8/3OJeW96AUYK5
+oTIynkYkEyHWMNCXALg+WQW6L4/YO7aUjZ97zOWIugd7Xy63aT3r/EHafqaY2nacOhLfkeKZ830b
+o/ezjoZQAxbh6ce7JnXRgE9ELxjdAhBTpGjmmmN2sYrJ7zP9bOgly0BnEPXGSQfFA+NNNw1FADx1
+MUY8q9DBjmVtgqY+1KGTV5X8KvQCBMODZIf/XJPHdCRAHxMd8COypcwgL2vDIIXpOFbi1J/B0GF+
+eklxk9wzBA8AecBMCwCzIRHDNpD1oa2we38bVFrOug6e/VId1k1jYFJjiLyLCDmV8IMYwEllHSXp
+LQAdm3xZ7t4WnxYC8YSCk9mXf3CZg59SpmnV5Q5Z6A5Pl7Nc3sj7hcsMBZEsOMPzNC9dPsBnZvjs
+WpPUffJzEdhHBFhvYMuD4Vqj6ejUv9l3oTrjQWVC`
+
+// note: this serial assertion was generated by adding print statements to the
+// test in api_model_test.go that generate a fake serial assertion
+const happySerialAssertionResponse = `type: serial
+authority-id: my-brand
+brand-id: my-brand
+model: my-old-model
+serial: serialserial
+device-key:
+ AcZrBFaFwYABAvCgEOrrLA6FKcreHxCcOoTgBUZ+IRG7Nb8tzmEAklaQPGpv7skapUjwD1luE2go
+ mTcoTssVHrfLpBoSDV1aBs44rg3NK40ZKPJP7d2zkds1GxUo1Ea5vfet3SJ4h3aRABEBAAE=
+device-key-sha3-384: iqLo9doLzK8De9925UrdUyuvPbBad72OTWVE9YJXqd6nz9dKvwJ_lHP5bVxrl3VO
+timestamp: 2019-08-26T16:34:21-05:00
+sign-key-sha3-384: anCEGC2NYq7DzDEi6y7OafQCVeVLS90XlLt9PNjrRl9sim5rmRHDDNFNO7ODcWQW
+
+AcJwBAABCgAGBQJdZFBdAADCLALwR6Sy24wm9PffwbvUhOEXneyY3BnxKC0+NgdHu1gU8go9vEP1
+i+Flh5uoS70+MBIO+nmF8T+9JWIx2QWFDDxvcuFosnIhvUajCEQohauys5FMz/H/WvB0vrbTBpvK
+eg==
+`
+
+const noModelAssertionYetResponse = `
+{
+ "type": "error",
+ "status-code": 404,
+ "status": "Not Found",
+ "result": {
+ "message": "no model assertion yet",
+ "kind": "assertion-not-found",
+ "value": "model"
+ }
+}`
+
+const noSerialAssertionYetResponse = `
+{
+ "type": "error",
+ "status-code": 404,
+ "status": "Not Found",
+ "result": {
+ "message": "no serial assertion yet",
+ "kind": "assertion-not-found",
+ "value": "serial"
+ }
+}`
+
+// helper for constructing different types of responses to the client
+type checkResponder func(c *check.C, w http.ResponseWriter, r *http.Request)
+
+func simpleHappyResponder(body string) checkResponder {
+ return func(c *check.C, w http.ResponseWriter, r *http.Request) {
+ c.Check(r.Method, check.Equals, "GET")
+ c.Check(r.URL.RawQuery, check.Equals, "")
+ fmt.Fprintln(w, body)
+ }
+}
+
+func simpleUnhappyResponder(errBody string) checkResponder {
+ return func(c *check.C, w http.ResponseWriter, r *http.Request) {
+ c.Check(r.Method, check.Equals, "GET")
+ c.Check(r.URL.RawQuery, check.Equals, "")
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(404)
+ fmt.Fprintln(w, errBody)
+ }
+}
+
+func simpleAssertionAccountResponder(body string) checkResponder {
+ return func(c *check.C, w http.ResponseWriter, r *http.Request) {
+ c.Check(r.Method, check.Equals, "GET")
+ w.Header().Set("X-Ubuntu-Assertions-Count", "1")
+ fmt.Fprintln(w, body)
+ }
+}
+
+func makeHappyTestServerHandler(c *check.C, modelResp, serialResp, accountResp checkResponder) func(w http.ResponseWriter, r *http.Request) {
+ var nModelSerial, nModel, nKnown int
+ return func(w http.ResponseWriter, r *http.Request) {
+ switch r.URL.Path {
+ case "/v2/model":
+ switch nModel {
+ case 0:
+ modelResp(c, w, r)
+ default:
+ c.Fatalf("expected to get 1 request for /v2/model, now on %d", nModel+1)
+ }
+ nModel++
+ case "/v2/model/serial":
+ switch nModelSerial {
+ case 0:
+ serialResp(c, w, r)
+ default:
+ c.Fatalf("expected to get 1 request for /v2/model, now on %d", nModelSerial+1)
+ }
+ nModelSerial++
+ case "/v2/assertions/account":
+ switch nKnown {
+ case 0:
+ accountResp(c, w, r)
+ default:
+ c.Fatalf("expected to get 1 request for /v2/model, now on %d", nKnown+1)
+ }
+ nKnown++
+ default:
+ c.Fatalf("unexpected request to %s", r.URL.Path)
+ }
+ }
+}
+
+func (s *SnapSuite) TestNoModelYet(c *check.C) {
+ s.RedirectClientToTestServer(
+ makeHappyTestServerHandler(
+ c,
+ simpleUnhappyResponder(noModelAssertionYetResponse),
+ simpleUnhappyResponder(noSerialAssertionYetResponse),
+ simpleAssertionAccountResponder(happyAccountAssertionResponse),
+ ))
+ _, err := snap.Parser(snap.Client()).ParseArgs([]string{"model"})
+ c.Assert(err, check.ErrorMatches, `device not ready yet \(no assertions found\)`)
+}
+
+func (s *SnapSuite) TestNoSerialYet(c *check.C) {
+ s.RedirectClientToTestServer(
+ makeHappyTestServerHandler(
+ c,
+ simpleHappyResponder(happyModelAssertionResponse),
+ simpleUnhappyResponder(noSerialAssertionYetResponse),
+ simpleAssertionAccountResponder(happyAccountAssertionResponse),
+ ))
+ _, err := snap.Parser(snap.Client()).ParseArgs([]string{"model", "--serial"})
+ c.Assert(err, check.ErrorMatches, `device not registered yet \(no serial assertion found\)`)
+ c.Check(s.Stderr(), check.Equals, "")
+ c.Check(s.Stdout(), check.Equals, `
+brand-id: mememe
+model: test-model
+`[1:])
+}
+
+func (s *SnapSuite) TestModel(c *check.C) {
+
+ for _, tt := range []struct {
+ comment string
+ modelF checkResponder
+ serialF checkResponder
+ outText string
+ }{
+ {
+ comment: "normal serial and model asserts",
+ modelF: simpleHappyResponder(happyModelAssertionResponse),
+ serialF: simpleHappyResponder(happySerialAssertionResponse),
+ outText: `
+brand MeMeMe (meuser*)
+model test-model
+serial serialserial
+`[1:],
+ },
+ {
+ comment: "model assert has display-name",
+ modelF: simpleHappyResponder(happyModelWithDisplayNameAssertionResponse),
+ serialF: simpleHappyResponder(happySerialAssertionResponse),
+ outText: `
+brand MeMeMe (meuser*)
+model Model Name (test-model)
+serial serialserial
+`[1:],
+ },
+ {
+ comment: "missing serial assert",
+ modelF: simpleHappyResponder(happyModelAssertionResponse),
+ serialF: simpleUnhappyResponder(noSerialAssertionYetResponse),
+ outText: `
+brand MeMeMe (meuser*)
+model test-model
+serial - (device not registered yet)
+`[1:],
+ },
+ } {
+ s.RedirectClientToTestServer(
+ makeHappyTestServerHandler(
+ c,
+ tt.modelF,
+ tt.serialF,
+ simpleAssertionAccountResponder(happyAccountAssertionResponse),
+ ))
+ rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"model"})
+ c.Assert(err, check.IsNil)
+ c.Assert(rest, check.DeepEquals, []string{})
+ c.Check(s.Stdout(), check.Equals, tt.outText, check.Commentf("\n%s\n", tt.outText))
+ c.Check(s.Stderr(), check.Equals, "")
+ s.ResetStdStreams()
+ }
+}
+
+func (s *SnapSuite) TestModelVerbose(c *check.C) {
+ s.RedirectClientToTestServer(
+ makeHappyTestServerHandler(
+ c,
+ simpleHappyResponder(happyModelAssertionResponse),
+ simpleHappyResponder(happySerialAssertionResponse),
+ simpleAssertionAccountResponder(happyAccountAssertionResponse),
+ ))
+ rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"model", "--verbose", "--abs-time"})
+ c.Assert(err, check.IsNil)
+ c.Assert(rest, check.DeepEquals, []string{})
+ c.Check(s.Stdout(), check.Equals, `
+brand-id: mememe
+model: test-model
+serial: serialserial
+architecture: amd64
+base: core18
+gadget: pc=18
+kernel: pc-kernel=18
+timestamp: 2017-07-27T00:00:00Z
+required-snaps:
+ - core
+ - hello-world
+`[1:])
+ c.Check(s.Stderr(), check.Equals, "")
+}
+
+func (s *SnapSuite) TestModelVerboseNoSerialYet(c *check.C) {
+ s.RedirectClientToTestServer(
+ makeHappyTestServerHandler(
+ c,
+ simpleHappyResponder(happyModelAssertionResponse),
+ simpleUnhappyResponder(noSerialAssertionYetResponse),
+ simpleAssertionAccountResponder(happyAccountAssertionResponse),
+ ))
+ rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"model", "--verbose", "--abs-time"})
+ c.Assert(err, check.IsNil)
+ c.Assert(rest, check.DeepEquals, []string{})
+ c.Check(s.Stdout(), check.Equals, `
+brand-id: mememe
+model: test-model
+serial: -- (device not registered yet)
+architecture: amd64
+base: core18
+gadget: pc=18
+kernel: pc-kernel=18
+timestamp: 2017-07-27T00:00:00Z
+required-snaps:
+ - core
+ - hello-world
+`[1:])
+ c.Check(s.Stderr(), check.Equals, "")
+}
+
+func (s *SnapSuite) TestModelAssertion(c *check.C) {
+ s.RedirectClientToTestServer(
+ makeHappyTestServerHandler(
+ c,
+ simpleHappyResponder(happyModelAssertionResponse),
+ simpleHappyResponder(happySerialAssertionResponse),
+ simpleAssertionAccountResponder(happyAccountAssertionResponse),
+ ))
+ rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"model", "--assertion"})
+ c.Assert(err, check.IsNil)
+ c.Assert(rest, check.DeepEquals, []string{})
+ c.Check(s.Stdout(), check.Equals, happyModelAssertionResponse)
+ c.Check(s.Stderr(), check.Equals, "")
+}
+
+func (s *SnapSuite) TestModelAssertionVerbose(c *check.C) {
+ // check that no calls to the server happen
+ s.RedirectClientToTestServer(
+ func(w http.ResponseWriter, r *http.Request) {
+ c.Fatalf("unexpected request to %s", r.URL.Path)
+ },
+ )
+ _, err := snap.Parser(snap.Client()).ParseArgs([]string{"model", "--assertion", "--verbose"})
+ c.Assert(err, check.ErrorMatches, "cannot use --verbose with --assertion")
+ c.Check(s.Stdout(), check.Equals, "")
+ c.Check(s.Stderr(), check.Equals, "")
+}
+
+func (s *SnapSuite) TestSerial(c *check.C) {
+ s.RedirectClientToTestServer(
+ makeHappyTestServerHandler(
+ c,
+ simpleHappyResponder(happyModelAssertionResponse),
+ simpleHappyResponder(happySerialAssertionResponse),
+ simpleAssertionAccountResponder(happyAccountAssertionResponse),
+ ))
+ rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"model", "--serial"})
+ c.Assert(err, check.IsNil)
+ c.Assert(rest, check.DeepEquals, []string{})
+ c.Check(s.Stdout(), check.Equals, `
+brand-id: my-brand
+model: my-old-model
+serial: serialserial
+`[1:])
+ c.Check(s.Stderr(), check.Equals, "")
+}
+
+func (s *SnapSuite) TestSerialVerbose(c *check.C) {
+ s.RedirectClientToTestServer(
+ makeHappyTestServerHandler(
+ c,
+ simpleHappyResponder(happyModelAssertionResponse),
+ simpleHappyResponder(happySerialAssertionResponse),
+ simpleAssertionAccountResponder(happyAccountAssertionResponse),
+ ))
+ rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"model", "--serial", "--verbose", "--abs-time"})
+ c.Assert(err, check.IsNil)
+ c.Assert(rest, check.DeepEquals, []string{})
+ c.Check(s.Stdout(), check.Equals, `
+brand-id: my-brand
+model: my-old-model
+serial: serialserial
+timestamp: 2019-08-26T16:34:21-05:00
+device-key-sha3-384: |
+ iqLo9doLzK8De9925UrdUyuvPbBad72OTWVE9YJXqd6nz9dKvwJ_lHP5bVxrl3VO
+device-key: |
+ AcZrBFaFwYABAvCgEOrrLA6FKcreHxCcOoTgBUZ+IRG7Nb8tzmEAklaQPGpv7skapUjwD1luE2g
+ omTcoTssVHrfLpBoSDV1aBs44rg3NK40ZKPJP7d2zkds1GxUo1Ea5vfet3SJ4h3aRABEBAAE=
+`[1:])
+ c.Check(s.Stderr(), check.Equals, "")
+}
+
+func (s *SnapSuite) TestSerialAssertion(c *check.C) {
+ s.RedirectClientToTestServer(
+ makeHappyTestServerHandler(
+ c,
+ simpleHappyResponder(happyModelAssertionResponse),
+ simpleHappyResponder(happySerialAssertionResponse),
+ simpleAssertionAccountResponder(happyAccountAssertionResponse),
+ ))
+ rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"model", "--serial", "--assertion"})
+ c.Assert(err, check.IsNil)
+ c.Assert(rest, check.DeepEquals, []string{})
+ c.Check(s.Stdout(), check.Equals, happySerialAssertionResponse)
+ c.Check(s.Stderr(), check.Equals, "")
+}
+
+func (s *SnapSuite) TestSerialAssertionSerialAssertionMissing(c *check.C) {
+ s.RedirectClientToTestServer(
+ makeHappyTestServerHandler(
+ c,
+ simpleHappyResponder(happyModelAssertionResponse),
+ simpleUnhappyResponder(noSerialAssertionYetResponse),
+ simpleAssertionAccountResponder(happyAccountAssertionResponse),
+ ))
+ _, err := snap.Parser(snap.Client()).ParseArgs([]string{"model", "--serial", "--assertion"})
+ c.Assert(err, check.ErrorMatches, `device not ready yet \(no assertions found\)`)
+ c.Assert(s.Stdout(), check.Equals, "")
+ c.Assert(s.Stderr(), check.Equals, "")
+}
diff -Nru snapd-2.41+19.10.1/cmd/snap/cmd_set.go snapd-2.42.1+19.10/cmd/snap/cmd_set.go
--- snapd-2.41+19.10.1/cmd/snap/cmd_set.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap/cmd_set.go 2019-10-30 12:17:43.000000000 +0000
@@ -40,10 +40,10 @@
Nested values may be modified via a dotted path:
- $ snap set author.name=frank
+ $ snap set snap-name author.name=frank
Configuration option may be unset with exclamation mark:
- $ snap set author!
+ $ snap set snap-name author!
`)
type cmdSet struct {
diff -Nru snapd-2.41+19.10.1/cmd/snap/cmd_snap_op.go snapd-2.42.1+19.10/cmd/snap/cmd_snap_op.go
--- snapd-2.41+19.10.1/cmd/snap/cmd_snap_op.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap/cmd_snap_op.go 2019-10-30 12:17:43.000000000 +0000
@@ -35,7 +35,7 @@
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/i18n"
"github.com/snapcore/snapd/osutil"
- "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/channel"
"github.com/snapcore/snapd/strutil"
)
@@ -281,14 +281,14 @@
}
var trackingRisk, currentRisk string
if tracking != "" {
- traCh, err := snap.ParseChannel(tracking, "")
+ traCh, err := channel.Parse(tracking, "")
if err != nil {
return false, err
}
trackingRisk = traCh.Risk
}
if current != "" {
- curCh, err := snap.ParseChannel(current, "")
+ curCh, err := channel.Parse(current, "")
if err != nil {
return false, err
}
diff -Nru snapd-2.41+19.10.1/cmd/snap/cmd_snapshot.go snapd-2.42.1+19.10/cmd/snap/cmd_snapshot.go
--- snapd-2.41+19.10.1/cmd/snap/cmd_snapshot.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap/cmd_snapshot.go 2019-10-30 12:17:43.000000000 +0000
@@ -336,14 +336,34 @@
}, waitDescs.also(map[string]string{
// TRANSLATORS: This should not start with a lowercase letter.
"users": i18n.G("Restore data of only specific users (comma-separated) (default: all users)"),
- }), nil)
+ }), []argDesc{
+ {
+ name: "",
+ // TRANSLATORS: This should not start with a lowercase letter.
+ desc: i18n.G("The snap for which data will be restored"),
+ }, {
+ name: "",
+ // TRANSLATORS: This should not start with a lowercase letter.
+ desc: i18n.G("Set id of snapshot to restore (see 'snap help saved')"),
+ },
+ })
addCommand("forget",
shortForgetHelp,
longForgetHelp,
func() flags.Commander {
return &forgetCmd{}
- }, waitDescs, nil)
+ }, waitDescs, []argDesc{
+ {
+ name: "",
+ // TRANSLATORS: This should not start with a lowercase letter.
+ desc: i18n.G("Set id of snapshot to delete (see 'snap help saved')"),
+ }, {
+ name: "",
+ // TRANSLATORS: This should not start with a lowercase letter.
+ desc: i18n.G("The snap for which data will be deleted"),
+ },
+ })
addCommand("check-snapshot",
shortCheckHelp,
@@ -353,5 +373,15 @@
}, waitDescs.also(map[string]string{
// TRANSLATORS: This should not start with a lowercase letter.
"users": i18n.G("Check data of only specific users (comma-separated) (default: all users)"),
- }), nil)
+ }), []argDesc{
+ {
+ name: "",
+ // TRANSLATORS: This should not start with a lowercase letter.
+ desc: i18n.G("Set id of snapshot to verify (see 'snap help saved')"),
+ }, {
+ name: "",
+ // TRANSLATORS: This should not start with a lowercase letter.
+ desc: i18n.G("The snap for which data will be verified"),
+ },
+ })
}
diff -Nru snapd-2.41+19.10.1/cmd/snap/cmd_snapshot_test.go snapd-2.42.1+19.10/cmd/snap/cmd_snapshot_test.go
--- snapd-2.41+19.10.1/cmd/snap/cmd_snapshot_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap/cmd_snapshot_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -33,10 +33,10 @@
var snapshotsTests = []getCmdArgs{{
args: "restore x",
- error: "invalid argument for set id: expected a non-negative integer argument",
+ error: `invalid argument for snapshot set id: expected a non-negative integer argument \(see 'snap help saved'\)`,
}, {
args: "saved --id=x",
- error: "invalid argument for set id: expected a non-negative integer argument",
+ error: `invalid argument for snapshot set id: expected a non-negative integer argument \(see 'snap help saved'\)`,
}, {
args: "saved --id=3",
stdout: "Set Snap Age Version Rev Size Notes\n3 htop .* 2 1168 1B auto\n",
@@ -45,10 +45,10 @@
stdout: "Set Snap Age Version Rev Size Notes\n1 htop .* 2 1168 1B -\n",
}, {
args: "forget x",
- error: "invalid argument for set id: expected a non-negative integer argument",
+ error: `invalid argument for snapshot set id: expected a non-negative integer argument \(see 'snap help saved'\)`,
}, {
args: "check-snapshot x",
- error: "invalid argument for set id: expected a non-negative integer argument",
+ error: `invalid argument for snapshot set id: expected a non-negative integer argument \(see 'snap help saved'\)`,
}, {
args: "restore 1",
stdout: "Restored snapshot #1.\n",
diff -Nru snapd-2.41+19.10.1/cmd/snap/cmd_unset.go snapd-2.42.1+19.10/cmd/snap/cmd_unset.go
--- snapd-2.41+19.10.1/cmd/snap/cmd_unset.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap/cmd_unset.go 2019-10-30 12:17:43.000000000 +0000
@@ -36,7 +36,7 @@
Nested values may be removed via a dotted path:
- $ snap unset user.name
+ $ snap unset snap-name user.name
`)
type cmdUnset struct {
diff -Nru snapd-2.41+19.10.1/cmd/snap/cmd_whoami_test.go snapd-2.42.1+19.10/cmd/snap/cmd_whoami_test.go
--- snapd-2.41+19.10.1/cmd/snap/cmd_whoami_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap/cmd_whoami_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,69 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package main_test
+
+import (
+ "net/http"
+
+ "github.com/snapcore/snapd/osutil"
+
+ . "gopkg.in/check.v1"
+
+ snap "github.com/snapcore/snapd/cmd/snap"
+)
+
+func (s *SnapSuite) TestWhoamiLoggedInUser(c *C) {
+ s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
+ panic("unexpected call to snapd API")
+ })
+
+ s.Login(c)
+ defer s.Logout(c)
+
+ _, err := snap.Parser(snap.Client()).ParseArgs([]string{"whoami"})
+ c.Assert(err, IsNil)
+ c.Check(s.Stdout(), Equals, "email: hello@mail.com\n")
+}
+
+func (s *SnapSuite) TestWhoamiNotLoggedInUser(c *C) {
+ s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
+ panic("unexpected call to snapd API")
+ })
+
+ _, err := snap.Parser(snap.Client()).ParseArgs([]string{"whoami"})
+ c.Assert(err, IsNil)
+ c.Check(s.Stdout(), Equals, "email: -\n")
+}
+
+func (s *SnapSuite) TestWhoamiExtraParamError(c *C) {
+ _, err := snap.Parser(snap.Client()).ParseArgs([]string{"whoami", "test"})
+ c.Check(err, ErrorMatches, "too many arguments for command")
+}
+
+func (s *SnapSuite) TestWhoamiEmptyAuthFile(c *C) {
+ s.Login(c)
+ defer s.Logout(c)
+
+ err := osutil.AtomicWriteFile(s.AuthFile, []byte(``), 0600, 0)
+ c.Assert(err, IsNil)
+
+ _, err = snap.Parser(snap.Client()).ParseArgs([]string{"whoami"})
+ c.Check(err, ErrorMatches, "EOF")
+}
diff -Nru snapd-2.41+19.10.1/cmd/snap/complete.go snapd-2.42.1+19.10/cmd/snap/complete.go
--- snapd-2.41+19.10.1/cmd/snap/complete.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap/complete.go 2019-10-30 12:17:43.000000000 +0000
@@ -508,7 +508,7 @@
func (s snapshotID) ToUint() (uint64, error) {
setID, err := strconv.ParseUint((string)(s), 10, 64)
if err != nil {
- return 0, fmt.Errorf(i18n.G("invalid argument for set id: expected a non-negative integer argument"))
+ return 0, fmt.Errorf(i18n.G("invalid argument for snapshot set id: expected a non-negative integer argument (see 'snap help saved')"))
}
return setID, nil
}
diff -Nru snapd-2.41+19.10.1/cmd/snap/error.go snapd-2.42.1+19.10/cmd/snap/error.go
--- snapd-2.41+19.10.1/cmd/snap/error.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap/error.go 2019-10-30 12:17:43.000000000 +0000
@@ -35,7 +35,7 @@
"github.com/snapcore/snapd/i18n"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
- "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/channel"
"github.com/snapcore/snapd/strutil"
)
@@ -239,16 +239,16 @@
return msg, nil
}
-func snapRevisionNotAvailableMessage(kind, snapName, action, arch, channel string, releases []interface{}) string {
+func snapRevisionNotAvailableMessage(kind, snapName, action, arch, snapChannel string, releases []interface{}) string {
// releases contains all available (arch x channel)
// as reported by the store through the daemon
- req, err := snap.ParseChannel(channel, arch)
+ req, err := channel.Parse(snapChannel, arch)
if err != nil {
// TRANSLATORS: %q is the invalid request channel, %s is the snap name
- msg := fmt.Sprintf(i18n.G("requested channel %q is not valid (see 'snap info %s' for valid ones)"), channel, snapName)
+ msg := fmt.Sprintf(i18n.G("requested channel %q is not valid (see 'snap info %s' for valid ones)"), snapChannel, snapName)
return msg
}
- avail := make([]*snap.Channel, 0, len(releases))
+ avail := make([]*channel.Channel, 0, len(releases))
for _, v := range releases {
rel, _ := v.(map[string]interface{})
relCh, _ := rel["channel"].(string)
@@ -257,7 +257,7 @@
logger.Debugf("internal error: %q daemon error carries a release with invalid/empty architecture: %v", kind, v)
continue
}
- a, err := snap.ParseChannel(relCh, relArch)
+ a, err := channel.Parse(relCh, relArch)
if err != nil {
logger.Debugf("internal error: %q daemon error carries a release with invalid/empty channel (%v): %v", kind, err, v)
continue
@@ -265,7 +265,7 @@
avail = append(avail, &a)
}
- matches := map[string][]*snap.Channel{}
+ matches := map[string][]*channel.Channel{}
for _, a := range avail {
m := req.Match(a)
matchRepr := m.String()
@@ -298,7 +298,7 @@
if req.Branch != "" {
// there are matching arch+track+risk, give main track info
if len(matches["architecture:track:risk"]) != 0 {
- trackRisk := snap.Channel{Track: req.Track, Risk: req.Risk}
+ trackRisk := channel.Channel{Track: req.Track, Risk: req.Risk}
trackRisk = trackRisk.Clean()
// TRANSLATORS: %q is for the snap name, first %s is the full requested channel
@@ -350,7 +350,7 @@
return msg
}
-func installTable(snapName, action string, avail []*snap.Channel, full bool) string {
+func installTable(snapName, action string, avail []*channel.Channel, full bool) string {
b := &bytes.Buffer{}
w := tabwriter.NewWriter(b, len("candidate")+2, 1, 2, ' ', 0)
first := true
@@ -379,7 +379,7 @@
return strings.Join(lines, "")
}
-func channelOption(c *snap.Channel) string {
+func channelOption(c *channel.Channel) string {
if c.Branch == "" {
if c.Track == "" {
return fmt.Sprintf("--%s", c.Risk)
@@ -391,7 +391,7 @@
return fmt.Sprintf("--channel=%s", c)
}
-func archsForChannels(cs []*snap.Channel) []string {
+func archsForChannels(cs []*channel.Channel) []string {
archs := []string{}
for _, c := range cs {
if !strutil.ListContains(archs, c.Architecture) {
diff -Nru snapd-2.41+19.10.1/cmd/snap-confine/cookie-support-test.c snapd-2.42.1+19.10/cmd/snap-confine/cookie-support-test.c
--- snapd-2.41+19.10.1/cmd/snap-confine/cookie-support-test.c 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap-confine/cookie-support-test.c 2019-10-30 12:17:43.000000000 +0000
@@ -18,6 +18,7 @@
#include "cookie-support.h"
#include "cookie-support.c"
+#include "../libsnap-confine-private/cleanup-funcs.h"
#include "../libsnap-confine-private/test-utils.h"
#include
@@ -64,8 +65,8 @@
static void test_cookie_get_from_snapd__successful(void)
{
- struct sc_error *err = NULL;
- char *cookie;
+ struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL;
+ char *cookie SC_CLEANUP(sc_cleanup_string) = NULL;
char *dummy = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijmnopqrst";
@@ -81,8 +82,8 @@
static void test_cookie_get_from_snapd__nofile(void)
{
- struct sc_error *err = NULL;
- char *cookie;
+ struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL;
+ char *cookie SC_CLEANUP(sc_cleanup_string) = NULL;
set_fake_cookie_dir();
diff -Nru snapd-2.41+19.10.1/cmd/snap-confine/mount-support.c snapd-2.42.1+19.10/cmd/snap-confine/mount-support.c
--- snapd-2.41+19.10.1/cmd/snap-confine/mount-support.c 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap-confine/mount-support.c 2019-10-30 12:17:43.000000000 +0000
@@ -241,10 +241,10 @@
// replicate the state of the root filesystem into the scratch directory.
sc_do_mount(config->rootfs_dir, scratch_dir, NULL, MS_REC | MS_BIND,
NULL);
- // Make the scratch directory recursively private. Nothing done there will
- // be shared with any peer group, This effectively detaches us from the
- // original namespace and coupled with pivot_root below serves as the
- // foundation of the mount sandbox.
+ // Make the scratch directory recursively slave. Nothing done there will be
+ // shared with the initial mount namespace. This effectively detaches us,
+ // in one way, from the original namespace and coupled with pivot_root
+ // below serves as the foundation of the mount sandbox.
sc_do_mount("none", scratch_dir, NULL, MS_REC | MS_SLAVE, NULL);
// Bind mount certain directories from the host filesystem to the scratch
// directory. By default mount events will propagate in both into and out
@@ -406,10 +406,14 @@
// the this directory on the host filesystem may not match the location in
// the desired root filesystem. In the "core" and "ubuntu-core" snaps the
// directory is always /snap. On the host it is a build-time configuration
- // option stored in SNAP_MOUNT_DIR.
- sc_must_snprintf(dst, sizeof dst, "%s/snap", scratch_dir);
- sc_do_mount(SNAP_MOUNT_DIR, dst, NULL, MS_BIND | MS_REC, NULL);
- sc_do_mount("none", dst, NULL, MS_REC | MS_SLAVE, NULL);
+ // option stored in SNAP_MOUNT_DIR. In legacy mode (or in other words, not
+ // in normal mode), we don't need to do this because /snap is fixed and
+ // already contains the correct view of the mounted snaps.
+ if (config->normal_mode) {
+ sc_must_snprintf(dst, sizeof dst, "%s/snap", scratch_dir);
+ sc_do_mount(SNAP_MOUNT_DIR, dst, NULL, MS_BIND | MS_REC, NULL);
+ sc_do_mount("none", dst, NULL, MS_REC | MS_SLAVE, NULL);
+ }
// Create the hostfs directory if one is missing. This directory is a part
// of packaging now so perhaps this code can be removed later.
if (access(SC_HOSTFS_DIR, F_OK) != 0) {
diff -Nru snapd-2.41+19.10.1/cmd/snap-confine/ns-support.c snapd-2.42.1+19.10/cmd/snap-confine/ns-support.c
--- snapd-2.41+19.10.1/cmd/snap-confine/ns-support.c 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap-confine/ns-support.c 2019-10-30 12:17:43.000000000 +0000
@@ -39,6 +39,7 @@
#include
#include "../libsnap-confine-private/cgroup-freezer-support.h"
+#include "../libsnap-confine-private/cgroup-support.h"
#include "../libsnap-confine-private/classic.h"
#include "../libsnap-confine-private/cleanup-funcs.h"
#include "../libsnap-confine-private/infofile.h"
@@ -486,6 +487,11 @@
debug("preserved mount is not stale, reusing");
return 0;
case SC_DISCARD_SHOULD:
+ if (sc_cgroup_is_v2()) {
+ debug
+ ("WARNING: cgroup v2 detected, preserved mount namespace process presence check unsupported, discarding");
+ break;
+ }
if (sc_cgroup_freezer_occupied(inv->snap_instance)) {
// Some processes are still using the namespace so we cannot discard it
// as that would fracture the view that the set of processes inside
@@ -873,10 +879,20 @@
char info_path[PATH_MAX] = { 0 };
sc_must_snprintf(info_path, sizeof info_path,
"/run/snapd/ns/snap.%s.info", inv->snap_instance);
- stream = fopen(info_path, "w");
- if (stream == NULL) {
+ int fd = -1;
+ fd = open(info_path,
+ O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW, 0644);
+ if (fd < 0) {
die("cannot open %s", info_path);
}
+ if (fchown(fd, 0, 0) < 0) {
+ die("cannot chown %s to root.root", info_path);
+ }
+ // The stream now owns the file descriptor.
+ stream = fdopen(fd, "w");
+ if (stream == NULL) {
+ die("cannot get stream from file descriptor");
+ }
fprintf(stream, "base-snap-name=%s\n", inv->orig_base_snap_name);
if (ferror(stream) != 0) {
die("I/O error when writing to %s", info_path);
diff -Nru snapd-2.41+19.10.1/cmd/snap-confine/snap-confine-args.c snapd-2.42.1+19.10/cmd/snap-confine/snap-confine-args.c
--- snapd-2.41+19.10.1/cmd/snap-confine/snap-confine-args.c 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap-confine/snap-confine-args.c 2019-10-30 12:17:43.000000000 +0000
@@ -21,6 +21,7 @@
#include "../libsnap-confine-private/utils.h"
#include "../libsnap-confine-private/string-utils.h"
+#include "../libsnap-confine-private/test-utils.h"
struct sc_args {
// The security tag that the application is intended to run with
diff -Nru snapd-2.41+19.10.1/cmd/snap-confine/snap-confine-args-test.c snapd-2.42.1+19.10/cmd/snap-confine/snap-confine-args-test.c
--- snapd-2.41+19.10.1/cmd/snap-confine/snap-confine-args-test.c 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap-confine/snap-confine-args-test.c 2019-10-30 12:17:43.000000000 +0000
@@ -23,57 +23,6 @@
#include
-/**
- * Create an argc + argv pair out of a NULL terminated argument list.
- **/
-static void
- __attribute__((sentinel)) test_argc_argv(int *argcp, char ***argvp, ...)
-{
- int argc = 0;
- char **argv = NULL;
- g_test_queue_free(argv);
-
- va_list ap;
- va_start(ap, argvp);
- const char *arg;
- do {
- arg = va_arg(ap, const char *);
- // XXX: yeah, wrong way but the worse that can happen is for test to fail
- argv = realloc(argv, sizeof(const char **) * (argc + 1));
- g_assert_nonnull(argv);
- if (arg != NULL) {
- char *arg_copy = sc_strdup(arg);
- g_test_queue_free(arg_copy);
- argv[argc] = arg_copy;
- argc += 1;
- } else {
- argv[argc] = NULL;
- }
- } while (arg != NULL);
- va_end(ap);
-
- *argcp = argc;
- *argvp = argv;
-}
-
-static void test_test_argc_argv(void)
-{
- // Check that test_argc_argv() correctly stores data
- int argc;
- char **argv;
-
- test_argc_argv(&argc, &argv, NULL);
- g_assert_cmpint(argc, ==, 0);
- g_assert_null(argv[0]);
-
- test_argc_argv(&argc, &argv, "zero", "one", "two", NULL);
- g_assert_cmpint(argc, ==, 3);
- g_assert_cmpstr(argv[0], ==, "zero");
- g_assert_cmpstr(argv[1], ==, "one");
- g_assert_cmpstr(argv[2], ==, "two");
- g_assert_null(argv[3]);
-}
-
static void test_sc_nonfatal_parse_args__typical(void)
{
// Test that typical invocation of snap-confine is parsed correctly.
@@ -239,6 +188,7 @@
g_assert_null(args);
g_assert_cmpstr(sc_error_msg(err), ==,
"cannot parse arguments, argcp or argvp is NULL");
+ sc_cleanup_error(&err);
int argc;
char **argv;
@@ -252,6 +202,7 @@
g_assert_null(args);
g_assert_cmpstr(sc_error_msg(err), ==,
"cannot parse arguments, argc is zero or argv is NULL");
+ sc_cleanup_error(&err);
// NULL argv[i] attack
test_argc_argv(&argc, &argv,
@@ -451,7 +402,6 @@
static void __attribute__((constructor)) init(void)
{
- g_test_add_func("/args/test_argc_argv", test_test_argc_argv);
g_test_add_func("/args/sc_cleanup_args", test_sc_cleanup_args);
g_test_add_func("/args/sc_nonfatal_parse_args/typical",
test_sc_nonfatal_parse_args__typical);
diff -Nru snapd-2.41+19.10.1/cmd/snap-confine/snap-confine.c snapd-2.42.1+19.10/cmd/snap-confine/snap-confine.c
--- snapd-2.41+19.10.1/cmd/snap-confine/snap-confine.c 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap-confine/snap-confine.c 2019-10-30 12:17:43.000000000 +0000
@@ -35,6 +35,7 @@
#include "../libsnap-confine-private/apparmor-support.h"
#include "../libsnap-confine-private/cgroup-freezer-support.h"
#include "../libsnap-confine-private/cgroup-pids-support.h"
+#include "../libsnap-confine-private/cgroup-support.h"
#include "../libsnap-confine-private/classic.h"
#include "../libsnap-confine-private/cleanup-funcs.h"
#include "../libsnap-confine-private/feature.h"
@@ -574,8 +575,11 @@
/** Populate and join the device control group. */
struct snappy_udev udev_s;
- if (snappy_udev_init(inv->security_tag, &udev_s) == 0)
- setup_devices_cgroup(inv->security_tag, &udev_s);
+ if (snappy_udev_init(inv->security_tag, &udev_s) == 0) {
+ if (!sc_cgroup_is_v2()) {
+ setup_devices_cgroup(inv->security_tag, &udev_s);
+ }
+ }
snappy_udev_cleanup(&udev_s);
/**
@@ -675,9 +679,11 @@
die("cannot set effective group id to root");
}
}
- sc_cgroup_freezer_join(inv->snap_instance, getpid());
- if (sc_feature_enabled(SC_FEATURE_REFRESH_APP_AWARENESS)) {
- sc_cgroup_pids_join(inv->security_tag, getpid());
+ if (!sc_cgroup_is_v2()) {
+ sc_cgroup_freezer_join(inv->snap_instance, getpid());
+ if (sc_feature_enabled(SC_FEATURE_REFRESH_APP_AWARENESS)) {
+ sc_cgroup_pids_join(inv->security_tag, getpid());
+ }
}
if (geteuid() == 0 && real_gid != 0) {
if (setegid(real_gid) != 0) {
diff -Nru snapd-2.41+19.10.1/cmd/snap-confine/snap-confine-invocation.c snapd-2.42.1+19.10/cmd/snap-confine/snap-confine-invocation.c
--- snapd-2.41+19.10.1/cmd/snap-confine/snap-confine-invocation.c 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap-confine/snap-confine-invocation.c 2019-10-30 12:17:43.000000000 +0000
@@ -61,6 +61,10 @@
die("cannot run with NULL executable");
}
+ /* Instance name length + NULL termination */
+ char snap_name[SNAP_NAME_LEN + 1] = {0};
+ sc_snap_drop_instance_key(snap_instance, snap_name, sizeof snap_name);
+
/* Invocation helps to pass relevant data to various parts of snap-confine. */
memset(inv, 0, sizeof *inv);
inv->base_snap_name = sc_strdup(base_snap_name);
@@ -68,6 +72,7 @@
inv->executable = sc_strdup(executable);
inv->security_tag = sc_strdup(security_tag);
inv->snap_instance = sc_strdup(snap_instance);
+ inv->snap_name = sc_strdup(snap_name);
inv->classic_confinement = sc_args_is_classic_confinement(args);
// construct rootfs_dir based on base_snap_name
@@ -84,6 +89,7 @@
void sc_cleanup_invocation(sc_invocation *inv) {
if (inv != NULL) {
sc_cleanup_string(&inv->snap_instance);
+ sc_cleanup_string(&inv->snap_name);
sc_cleanup_string(&inv->base_snap_name);
sc_cleanup_string(&inv->orig_base_snap_name);
sc_cleanup_string(&inv->security_tag);
diff -Nru snapd-2.41+19.10.1/cmd/snap-confine/snap-confine-invocation.h snapd-2.42.1+19.10/cmd/snap-confine/snap-confine-invocation.h
--- snapd-2.41+19.10.1/cmd/snap-confine/snap-confine-invocation.h 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap-confine/snap-confine-invocation.h 2019-10-30 12:17:43.000000000 +0000
@@ -29,7 +29,8 @@
**/
typedef struct sc_invocation {
/* Things declared by the system. */
- char *snap_instance;
+ char *snap_instance; /* snap instance name (_) */
+ char *snap_name; /* snap name (without instance key) */
char *orig_base_snap_name;
char *security_tag;
char *executable;
diff -Nru snapd-2.41+19.10.1/cmd/snap-confine/snap-confine-invocation-test.c snapd-2.42.1+19.10/cmd/snap-confine/snap-confine-invocation-test.c
--- snapd-2.41+19.10.1/cmd/snap-confine/snap-confine-invocation-test.c 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap-confine/snap-confine-invocation-test.c 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#include "snap-confine-invocation.h"
+#include "snap-confine-args.h"
+#include "snap-confine-invocation.c"
+
+#include "../libsnap-confine-private/cleanup-funcs.h"
+#include "../libsnap-confine-private/test-utils.h"
+
+#include
+
+#include
+
+static struct sc_args *test_prepare_args(const char *base, const char *tag) {
+ struct sc_args *args = NULL;
+ sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL;
+ int argc;
+ char **argv;
+
+ test_argc_argv(&argc, &argv, "/usr/lib/snapd/snap-confine", "--base", (base != NULL) ? base : "core",
+ (tag != NULL) ? tag : "snap.foo.app", "/usr/lib/snapd/snap-exec", NULL);
+ args = sc_nonfatal_parse_args(&argc, &argv, &err);
+ g_assert_null(err);
+ g_assert_nonnull(args);
+
+ return args;
+}
+
+static void test_sc_invocation_basic(void) {
+ struct sc_args *args SC_CLEANUP(sc_cleanup_args) = test_prepare_args("core", NULL);
+
+ sc_invocation inv SC_CLEANUP(sc_cleanup_invocation);
+ ;
+ sc_init_invocation(&inv, args, "foo");
+
+ g_assert_cmpstr(inv.base_snap_name, ==, "core");
+ g_assert_cmpstr(inv.executable, ==, "/usr/lib/snapd/snap-exec");
+ g_assert_cmpstr(inv.orig_base_snap_name, ==, "core");
+ g_assert_cmpstr(inv.rootfs_dir, ==, SNAP_MOUNT_DIR "/core/current");
+ g_assert_cmpstr(inv.security_tag, ==, "snap.foo.app");
+ g_assert_cmpstr(inv.snap_instance, ==, "foo");
+ g_assert_cmpstr(inv.snap_name, ==, "foo");
+ g_assert_false(inv.classic_confinement);
+ /* derived later */
+ g_assert_false(inv.is_normal_mode);
+}
+
+static void test_sc_invocation_instance_key(void) {
+ struct sc_args *args SC_CLEANUP(sc_cleanup_args) = test_prepare_args("core", "snap.foo_bar.app");
+
+ sc_invocation inv SC_CLEANUP(sc_cleanup_invocation);
+ ;
+ sc_init_invocation(&inv, args, "foo_bar");
+
+ // Check the error that we've got
+ g_assert_cmpstr(inv.snap_instance, ==, "foo_bar");
+ g_assert_cmpstr(inv.snap_name, ==, "foo");
+ g_assert_cmpstr(inv.orig_base_snap_name, ==, "core");
+ g_assert_cmpstr(inv.security_tag, ==, "snap.foo_bar.app");
+ g_assert_cmpstr(inv.executable, ==, "/usr/lib/snapd/snap-exec");
+ g_assert_false(inv.classic_confinement);
+ g_assert_cmpstr(inv.rootfs_dir, ==, SNAP_MOUNT_DIR "/core/current");
+ g_assert_cmpstr(inv.base_snap_name, ==, "core");
+ /* derived later */
+ g_assert_false(inv.is_normal_mode);
+}
+
+static void test_sc_invocation_base_name(void) {
+ struct sc_args *args SC_CLEANUP(sc_cleanup_args) = test_prepare_args("base-snap", NULL);
+
+ sc_invocation inv SC_CLEANUP(sc_cleanup_invocation);
+ sc_init_invocation(&inv, args, "foo");
+
+ g_assert_cmpstr(inv.base_snap_name, ==, "base-snap");
+ g_assert_cmpstr(inv.executable, ==, "/usr/lib/snapd/snap-exec");
+ g_assert_cmpstr(inv.orig_base_snap_name, ==, "base-snap");
+ g_assert_cmpstr(inv.rootfs_dir, ==, SNAP_MOUNT_DIR "/base-snap/current");
+ g_assert_cmpstr(inv.security_tag, ==, "snap.foo.app");
+ g_assert_cmpstr(inv.snap_instance, ==, "foo");
+ g_assert_cmpstr(inv.snap_name, ==, "foo");
+ g_assert_false(inv.classic_confinement);
+ /* derived later */
+ g_assert_false(inv.is_normal_mode);
+}
+
+static void test_sc_invocation_bad_instance_name(void) {
+ struct sc_args *args SC_CLEANUP(sc_cleanup_args) = test_prepare_args(NULL, NULL);
+
+ if (g_test_subprocess()) {
+ sc_invocation inv SC_CLEANUP(sc_cleanup_invocation) = {0};
+ sc_init_invocation(&inv, args, "foo_bar_bar_bar");
+ return;
+ }
+
+ g_test_trap_subprocess(NULL, 0, 0);
+ g_test_trap_assert_failed();
+ g_test_trap_assert_stderr("snap instance name can contain only one underscore\n");
+}
+
+static void test_sc_invocation_classic(void) {
+ struct sc_args *args SC_CLEANUP(sc_cleanup_args) = NULL;
+ sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL;
+ int argc;
+ char **argv = NULL;
+
+ test_argc_argv(&argc, &argv, "/usr/lib/snapd/snap-confine", "--classic", "--base", "core", "snap.foo-classic.app",
+ "/usr/lib/snapd/snap-exec", NULL);
+ args = sc_nonfatal_parse_args(&argc, &argv, &err);
+ g_assert_null(err);
+ g_assert_nonnull(args);
+
+ sc_invocation inv SC_CLEANUP(sc_cleanup_invocation) = {0};
+ sc_init_invocation(&inv, args, "foo-classic");
+
+ g_assert_cmpstr(inv.base_snap_name, ==, "core");
+ g_assert_cmpstr(inv.executable, ==, "/usr/lib/snapd/snap-exec");
+ g_assert_cmpstr(inv.orig_base_snap_name, ==, "core");
+ g_assert_cmpstr(inv.rootfs_dir, ==, SNAP_MOUNT_DIR "/core/current");
+ g_assert_cmpstr(inv.security_tag, ==, "snap.foo-classic.app");
+ g_assert_cmpstr(inv.snap_instance, ==, "foo-classic");
+ g_assert_cmpstr(inv.snap_name, ==, "foo-classic");
+ g_assert_true(inv.classic_confinement);
+}
+
+static void test_sc_invocation_tag_name_mismatch(void) {
+ struct sc_args *args SC_CLEANUP(sc_cleanup_args) = test_prepare_args("core", "snap.foo.app");
+
+ if (g_test_subprocess()) {
+ sc_invocation inv SC_CLEANUP(sc_cleanup_invocation);
+ ;
+ sc_init_invocation(&inv, args, "foo-not-foo");
+ return;
+ }
+
+ g_test_trap_subprocess(NULL, 0, 0);
+ g_test_trap_assert_failed();
+ g_test_trap_assert_stderr("security tag snap.foo.app not allowed\n");
+}
+
+static void __attribute__((constructor)) init(void) {
+ g_test_add_func("/invocation/bad_instance_name", test_sc_invocation_bad_instance_name);
+ g_test_add_func("/invocation/base_name", test_sc_invocation_base_name);
+ g_test_add_func("/invocation/basic", test_sc_invocation_basic);
+ g_test_add_func("/invocation/classic", test_sc_invocation_classic);
+ g_test_add_func("/invocation/instance_key", test_sc_invocation_instance_key);
+ g_test_add_func("/invocation/tag_name_mismatch", test_sc_invocation_tag_name_mismatch);
+}
diff -Nru snapd-2.41+19.10.1/cmd/snap-confine/udev-support.c snapd-2.42.1+19.10/cmd/snap-confine/udev-support.c
--- snapd-2.41+19.10.1/cmd/snap-confine/udev-support.c 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap-confine/udev-support.c 2019-10-30 12:17:43.000000000 +0000
@@ -143,10 +143,10 @@
if (udev_s->devices == NULL)
die("udev_enumerate_new failed");
- if (udev_enumerate_add_match_tag(udev_s->devices, udev_s->tagname) != 0)
+ if (udev_enumerate_add_match_tag(udev_s->devices, udev_s->tagname) < 0)
die("udev_enumerate_add_match_tag");
- if (udev_enumerate_scan_devices(udev_s->devices) != 0)
+ if (udev_enumerate_scan_devices(udev_s->devices) < 0)
die("udev_enumerate_scan failed");
udev_s->assigned = udev_enumerate_get_list_entry(udev_s->devices);
diff -Nru snapd-2.41+19.10.1/cmd/snap-mgmt/snap-mgmt-selinux.sh.in snapd-2.42.1+19.10/cmd/snap-mgmt/snap-mgmt-selinux.sh.in
--- snapd-2.41+19.10.1/cmd/snap-mgmt/snap-mgmt-selinux.sh.in 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap-mgmt/snap-mgmt-selinux.sh.in 2019-10-30 12:17:43.000000000 +0000
@@ -1,6 +1,7 @@
#!/bin/bash
set -e
+set +x
SNAP_MOUNT_DIR="@SNAP_MOUNT_DIR@"
diff -Nru snapd-2.41+19.10.1/cmd/snap-mgmt/snap-mgmt.sh.in snapd-2.42.1+19.10/cmd/snap-mgmt/snap-mgmt.sh.in
--- snapd-2.41+19.10.1/cmd/snap-mgmt/snap-mgmt.sh.in 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap-mgmt/snap-mgmt.sh.in 2019-10-30 12:17:43.000000000 +0000
@@ -6,6 +6,7 @@
# snapd.
set -e
+set +x
SNAP_MOUNT_DIR="@SNAP_MOUNT_DIR@"
diff -Nru snapd-2.41+19.10.1/cmd/snap-repair/runner.go snapd-2.42.1+19.10/cmd/snap-repair/runner.go
--- snapd-2.41+19.10.1/cmd/snap-repair/runner.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap-repair/runner.go 2019-10-30 12:17:43.000000000 +0000
@@ -776,7 +776,7 @@
if err != nil {
return false
}
- if len(archs) != 0 && !strutil.ListContains(archs, arch.UbuntuArchitecture()) {
+ if len(archs) != 0 && !strutil.ListContains(archs, arch.DpkgArchitecture()) {
return false
}
brandModel := fmt.Sprintf("%s/%s", run.state.Device.Brand, run.state.Device.Model)
diff -Nru snapd-2.41+19.10.1/cmd/snap-repair/runner_test.go snapd-2.42.1+19.10/cmd/snap-repair/runner_test.go
--- snapd-2.41+19.10.1/cmd/snap-repair/runner_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap-repair/runner_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -782,10 +782,10 @@
{map[string]interface{}{"series": []interface{}{"18", "16"}}, true},
{map[string]interface{}{"series": "18"}, false},
{map[string]interface{}{"series": []interface{}{18}}, false},
- {map[string]interface{}{"architectures": []interface{}{arch.UbuntuArchitecture()}}, true},
+ {map[string]interface{}{"architectures": []interface{}{arch.DpkgArchitecture()}}, true},
{map[string]interface{}{"architectures": []interface{}{"other-arch"}}, false},
- {map[string]interface{}{"architectures": []interface{}{"other-arch", arch.UbuntuArchitecture()}}, true},
- {map[string]interface{}{"architectures": arch.UbuntuArchitecture()}, false},
+ {map[string]interface{}{"architectures": []interface{}{"other-arch", arch.DpkgArchitecture()}}, true},
+ {map[string]interface{}{"architectures": arch.DpkgArchitecture()}, false},
{map[string]interface{}{"models": []interface{}{"my-brand/my-model"}}, true},
{map[string]interface{}{"models": []interface{}{"other-brand/other-model"}}, false},
{map[string]interface{}{"models": []interface{}{"other-brand/other-model", "my-brand/my-model"}}, true},
diff -Nru snapd-2.41+19.10.1/cmd/snap-seccomp/export_test.go snapd-2.42.1+19.10/cmd/snap-seccomp/export_test.go
--- snapd-2.41+19.10.1/cmd/snap-seccomp/export_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap-seccomp/export_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -26,19 +26,19 @@
GoSeccompFeatures = goSeccompFeatures
)
-func MockArchUbuntuArchitecture(f func() string) (restore func()) {
- realArchUbuntuArchitecture := archUbuntuArchitecture
- archUbuntuArchitecture = f
+func MockArchDpkgArchitecture(f func() string) (restore func()) {
+ realArchDpkgArchitecture := archDpkgArchitecture
+ archDpkgArchitecture = f
return func() {
- archUbuntuArchitecture = realArchUbuntuArchitecture
+ archDpkgArchitecture = realArchDpkgArchitecture
}
}
-func MockArchUbuntuKernelArchitecture(f func() string) (restore func()) {
- realArchUbuntuKernelArchitecture := archUbuntuKernelArchitecture
- archUbuntuKernelArchitecture = f
+func MockArchDpkgKernelArchitecture(f func() string) (restore func()) {
+ realArchDpkgKernelArchitecture := archDpkgKernelArchitecture
+ archDpkgKernelArchitecture = f
return func() {
- archUbuntuKernelArchitecture = realArchUbuntuKernelArchitecture
+ archDpkgKernelArchitecture = realArchDpkgKernelArchitecture
}
}
diff -Nru snapd-2.41+19.10.1/cmd/snap-seccomp/main.go snapd-2.42.1+19.10/cmd/snap-seccomp/main.go
--- snapd-2.41+19.10.1/cmd/snap-seccomp/main.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap-seccomp/main.go 2019-10-30 12:17:43.000000000 +0000
@@ -426,10 +426,10 @@
"PTRACE_CONT": C.PTRACE_CONT,
}
-// UbuntuArchToScmpArch takes a dpkg architecture and converts it to
+// DpkgArchToScmpArch takes a dpkg architecture and converts it to
// the seccomp.ScmpArch as used in the libseccomp-golang library
-func UbuntuArchToScmpArch(ubuntuArch string) seccomp.ScmpArch {
- switch ubuntuArch {
+func DpkgArchToScmpArch(dpkgArch string) seccomp.ScmpArch {
+ switch dpkgArch {
case "amd64":
return seccomp.ArchAMD64
case "arm64":
@@ -447,7 +447,7 @@
case "s390x":
return seccomp.ArchS390X
}
- panic(fmt.Sprintf("cannot map ubuntu arch %q to a seccomp arch", ubuntuArch))
+ panic(fmt.Sprintf("cannot map dpkg arch %q to a seccomp arch", dpkgArch))
}
// important for unit testing
@@ -631,13 +631,13 @@
// used to mock in tests
var (
- archUbuntuArchitecture = arch.UbuntuArchitecture
- archUbuntuKernelArchitecture = arch.UbuntuKernelArchitecture
+ archDpkgArchitecture = arch.DpkgArchitecture
+ archDpkgKernelArchitecture = arch.DpkgKernelArchitecture
)
var (
- ubuntuArchitecture = archUbuntuArchitecture()
- ubuntuKernelArchitecture = archUbuntuKernelArchitecture()
+ dpkgArchitecture = archDpkgArchitecture()
+ dpkgKernelArchitecture = archDpkgKernelArchitecture()
)
// For architectures that support a compat architecture, when the
@@ -653,8 +653,8 @@
// add a compat architecture for some architectures that
// support it, e.g. on amd64 kernel and userland, we add
// compat i386 syscalls.
- if ubuntuArchitecture == ubuntuKernelArchitecture {
- switch archUbuntuArchitecture() {
+ if dpkgArchitecture == dpkgKernelArchitecture {
+ switch archDpkgArchitecture() {
case "amd64":
compatArch = seccomp.ArchX86
case "arm64":
@@ -672,7 +672,7 @@
// snaps. While unusual from a traditional Linux distribution
// perspective, certain classes of embedded devices are known
// to use this configuration.
- compatArch = UbuntuArchToScmpArch(archUbuntuKernelArchitecture())
+ compatArch = DpkgArchToScmpArch(archDpkgKernelArchitecture())
}
if compatArch != seccomp.ArchInvalid {
diff -Nru snapd-2.41+19.10.1/cmd/snap-seccomp/main_test.go snapd-2.42.1+19.10/cmd/snap-seccomp/main_test.go
--- snapd-2.41+19.10.1/cmd/snap-seccomp/main_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap-seccomp/main_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -191,7 +191,7 @@
// 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" && s.canCheckCompatArch {
+ if arch.DpkgArchitecture() == "amd64" && s.canCheckCompatArch {
cmd = exec.Command(cmd.Args[0], cmd.Args[1:]...)
cmd.Args = append(cmd.Args, "-m32")
for i, k := range cmd.Args {
@@ -274,7 +274,7 @@
// 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))
+ syscallNr, err = seccomp.GetSyscallFromNameByArch(syscallName, main.DpkgArchToScmpArch(syscallArch))
c.Assert(err, IsNil)
switch syscallArch {
@@ -825,10 +825,10 @@
// https://github.com/seccomp/libseccomp/issues/86
//
// This means we can not just
- // main.MockArchUbuntuArchitecture(t.arch)
+ // main.MockArchDpkgArchitecture(t.arch)
// here because on endian mismatch the arch will *not* be
// added
- if arch.UbuntuArchitecture() == t.arch {
+ if arch.DpkgArchitecture() == t.arch {
s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected)
}
}
diff -Nru snapd-2.41+19.10.1/cmd/snap-update-ns/change.go snapd-2.42.1+19.10/cmd/snap-update-ns/change.go
--- snapd-2.41+19.10.1/cmd/snap-update-ns/change.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap-update-ns/change.go 2019-10-30 12:17:43.000000000 +0000
@@ -330,10 +330,20 @@
flags := umountNoFollow
if c.Entry.XSnapdDetach() {
flags |= syscall.MNT_DETACH
+ // If we are detaching something then before performing the actual detach
+ // switch the entire hierarchy to private event propagation (that is,
+ // none). This works around a bit of peculiar kernel behavior when the
+ // kernel reports EBUSY during a detach operation, because the changes
+ // propagate in a way that conflicts with itself. This is also documented
+ // in umount(2).
+ err = sysMount("none", c.Entry.Dir, "", syscall.MS_REC|syscall.MS_PRIVATE, "")
+ logger.Debugf("mount --make-rprivate %q (error: %v)", c.Entry.Dir, err)
}
// Perform the raw unmount operation.
- err = sysUnmount(c.Entry.Dir, flags)
+ if err == nil {
+ err = sysUnmount(c.Entry.Dir, flags)
+ }
if err == nil {
as.AddChange(c)
}
diff -Nru snapd-2.41+19.10.1/cmd/snap-update-ns/change_test.go snapd-2.42.1+19.10/cmd/snap-update-ns/change_test.go
--- snapd-2.41+19.10.1/cmd/snap-update-ns/change_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/snap-update-ns/change_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -642,6 +642,7 @@
{C: `lstat "/rofs"`, R: testutil.FileInfoDir},
{C: `mount "tmpfs" "/rofs" "tmpfs" 0 "mode=0755,uid=0,gid=0"`},
+ {C: `mount "none" "/tmp/.snap/rofs" "" MS_REC|MS_PRIVATE ""`},
{C: `unmount "/tmp/.snap/rofs" UMOUNT_NOFOLLOW|MNT_DETACH`},
// Perform clean up after the unmount operation.
@@ -805,6 +806,7 @@
synth, err := chg.Perform(s.as)
c.Assert(err, IsNil)
c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
+ {C: `mount "none" "/target" "" MS_REC|MS_PRIVATE ""`},
{C: `unmount "/target" UMOUNT_NOFOLLOW|MNT_DETACH`},
// Perform clean up after the unmount operation.
@@ -1151,6 +1153,7 @@
{C: `lstat "/rofs"`, R: testutil.FileInfoDir},
{C: `mount "tmpfs" "/rofs" "tmpfs" 0 "mode=0755,uid=0,gid=0"`},
+ {C: `mount "none" "/tmp/.snap/rofs" "" MS_REC|MS_PRIVATE ""`},
{C: `unmount "/tmp/.snap/rofs" UMOUNT_NOFOLLOW|MNT_DETACH`},
// Perform clean up after the unmount operation.
@@ -1298,6 +1301,7 @@
{C: `close 4`},
{C: `lstat "/rofs"`, R: testutil.FileInfoDir},
{C: `mount "tmpfs" "/rofs" "tmpfs" 0 "mode=0755,uid=0,gid=0"`},
+ {C: `mount "none" "/tmp/.snap/rofs" "" MS_REC|MS_PRIVATE ""`},
{C: `unmount "/tmp/.snap/rofs" UMOUNT_NOFOLLOW|MNT_DETACH`},
// Perform clean up after the unmount operation.
@@ -1690,6 +1694,7 @@
{C: `lstat "/rofs"`, R: testutil.FileInfoDir},
{C: `mount "tmpfs" "/rofs" "tmpfs" 0 "mode=0755,uid=0,gid=0"`},
+ {C: `mount "none" "/tmp/.snap/rofs" "" MS_REC|MS_PRIVATE ""`},
{C: `unmount "/tmp/.snap/rofs" UMOUNT_NOFOLLOW|MNT_DETACH`},
// Perform clean up after the unmount operation.
@@ -2082,6 +2087,7 @@
{C: `lstat "/rofs"`, R: testutil.FileInfoDir},
{C: `mount "tmpfs" "/rofs" "tmpfs" 0 "mode=0755,uid=0,gid=0"`},
+ {C: `mount "none" "/tmp/.snap/rofs" "" MS_REC|MS_PRIVATE ""`},
{C: `unmount "/tmp/.snap/rofs" UMOUNT_NOFOLLOW|MNT_DETACH`},
// Perform clean up after the unmount operation.
@@ -2340,6 +2346,7 @@
{C: `close 7`},
// We're done restoring now.
+ {C: `mount "none" "/tmp/.snap/etc" "" MS_REC|MS_PRIVATE ""`},
{C: `unmount "/tmp/.snap/etc" UMOUNT_NOFOLLOW|MNT_DETACH`},
// Perform clean up after the unmount operation.
@@ -2424,3 +2431,80 @@
{Action: update.Keep, Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/target", Type: "tmpfs"}},
})
}
+
+func (s *changeSuite) TestComplexPropagatingChanges(c *C) {
+ // This problem is more subtle. It is a variant of the regression test
+ // implemented in tests/regression/lp-1831010. Here, we have four directories:
+ //
+ // - $SNAP/a
+ // - $SNAP/b
+ // - $SNAP/b/c
+ // - $SNAP/d
+ //
+ // but snapd's mount profile contains only two entries:
+ //
+ // 1) recursive-bind $SNAP/a -> $SNAP/b/c (ie, mount --rbind $SNAP/a $SNAP/b/c)
+ // 2) recursive-bind $SNAP/b -> $SNAP/d (ie, mount --rbind $SNAP/b $SNAP/d)
+ //
+ // Both mount operations are performed under a substrate that is MS_SHARED.
+ // Therefore, due to the rules that decide upon propagation of bind mounts
+ // the propagation of the new mount entries is also shared. This is
+ // documented in section 5b of
+ // https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt.
+ //
+ // Interactive experimentation shows that the following three mount points exist
+ // after this operation, as illustrated by findmnt:
+ //
+ // TARGET SOURCE FSTYPE OPTIONS
+ // ...
+ // └─/snap/test-snapd-layout/x1 /dev/loop1 squashfs ro,nodev,relatime
+ // ├─/snap/test-snapd-layout/x1/b/c /dev/loop1[/a] squashfs ro,nodev,relatime
+ // └─/snap/test-snapd-layout/x1/d /dev/loop1[/b] squashfs ro,nodev,relatime
+ // └─/snap/test-snapd-layout/x1/d/c /dev/loop1[/a] squashfs ro,nodev,relatime
+ //
+ // Note that after the first mount operation only one mount point is created, namely
+ // $SNAP/a -> $SNAP/b/c. The second recursive bind mount not only creates
+ // $SNAP/b -> $SNAP/d, but also replicates $SNAP/a -> $SNAP/b/c as
+ // $SNAP/a -> $SNAP/d/c.
+ //
+ // The test will simulate a refresh of the snap from revision x1 to revision
+ // x2. When this happens the mount profile associated with x1 must be undone
+ // and the mount profile associated with x2 must be constructed. Because
+ // ordering matters, let's first consider the order of construction of x1
+ // itself. Starting from nothing, apply x1 as follows:
+ x1 := &osutil.MountProfile{
+ Entries: []osutil.MountEntry{
+ {Name: "/snap/app/x1/a", Dir: "/snap/app/x1/b/c", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}},
+ {Name: "/snap/app/x1/b", Dir: "/snap/app/x1/d", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}},
+ },
+ }
+ changes := update.NeededChanges(&osutil.MountProfile{}, x1)
+ c.Assert(changes, DeepEquals, []*update.Change{
+ {Action: update.Mount, Entry: osutil.MountEntry{Name: "/snap/app/x1/a", Dir: "/snap/app/x1/b/c", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}}},
+ {Action: update.Mount, Entry: osutil.MountEntry{Name: "/snap/app/x1/b", Dir: "/snap/app/x1/d", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}}},
+ })
+ // We can see that x1 is constructed in alphabetical order, first recursively
+ // bind mount at $SNAP/a the directory $SNAP/b/c, second recursively bind
+ // mount at $SNAP/b the directory $SNAP/d.
+ x2 := &osutil.MountProfile{
+ Entries: []osutil.MountEntry{
+ {Name: "/snap/app/x2/a", Dir: "/snap/app/x2/b/c", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}},
+ {Name: "/snap/app/x2/b", Dir: "/snap/app/x2/d", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}},
+ },
+ }
+ // When we are asked to refresh to revision x2, using the same layout, we
+ // simply undo x1 and then create x2, which apart from the difference in
+ // revision name, is exactly the same. The undo code, however, does not take
+ // the replicated mount point under consideration and therefore attempts to
+ // detach "x1/d", which normally fails with EBUSY. To counter this, the
+ // unmount operation first switches the mount point to recursive private
+ // propagation, before actually unmounting it. This ensures that propagation
+ // doesn't self-conflict, simply because there isn't any left.
+ changes = update.NeededChanges(x1, x2)
+ c.Assert(changes, DeepEquals, []*update.Change{
+ {Action: update.Unmount, Entry: osutil.MountEntry{Name: "/snap/app/x1/b", Dir: "/snap/app/x1/d", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout", "x-snapd.detach"}}},
+ {Action: update.Unmount, Entry: osutil.MountEntry{Name: "/snap/app/x1/a", Dir: "/snap/app/x1/b/c", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout", "x-snapd.detach"}}},
+ {Action: update.Mount, Entry: osutil.MountEntry{Name: "/snap/app/x2/a", Dir: "/snap/app/x2/b/c", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}}},
+ {Action: update.Mount, Entry: osutil.MountEntry{Name: "/snap/app/x2/b", Dir: "/snap/app/x2/d", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}}},
+ })
+}
diff -Nru snapd-2.41+19.10.1/cmd/system-shutdown/system-shutdown.c snapd-2.42.1+19.10/cmd/system-shutdown/system-shutdown.c
--- snapd-2.41+19.10.1/cmd/system-shutdown/system-shutdown.c 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/system-shutdown/system-shutdown.c 2019-10-30 12:17:43.000000000 +0000
@@ -32,10 +32,32 @@
#include // SYS_reboot
#include "system-shutdown-utils.h"
+#include "../libsnap-confine-private/panic.h"
#include "../libsnap-confine-private/string-utils.h"
+#include "../libsnap-confine-private/utils.h"
+
+static void show_error(const char *fmt, va_list ap, int errno_copy)
+{
+ fprintf(stderr, "snapd system-shutdown helper: ");
+ fprintf(stderr, "*** ");
+ vfprintf(stderr, fmt, ap);
+ if (errno_copy != 0) {
+ fprintf(stderr, ": %s", strerror(errno_copy));
+ }
+ fprintf(stderr, "\n");
+}
+
+static void sync_and_halt(void)
+{
+ sync();
+ reboot(RB_HALT_SYSTEM);
+}
int main(int argc, char *argv[])
{
+ sc_set_panic_msg_fn(show_error);
+ sc_set_panic_exit_fn(sync_and_halt);
+
// 256 should be more than enough...
char reboot_arg[256] = { 0 };
diff -Nru snapd-2.41+19.10.1/cmd/system-shutdown/system-shutdown-utils.c snapd-2.42.1+19.10/cmd/system-shutdown/system-shutdown-utils.c
--- snapd-2.41+19.10.1/cmd/system-shutdown/system-shutdown-utils.c 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/system-shutdown/system-shutdown-utils.c 2019-10-30 12:17:43.000000000 +0000
@@ -33,6 +33,7 @@
#include "../libsnap-confine-private/mountinfo.h"
#include "../libsnap-confine-private/string-utils.h"
+#include "../libsnap-confine-private/utils.h"
__attribute__((format(printf, 1, 2)))
void kmsg(const char *fmt, ...)
@@ -53,19 +54,6 @@
va_end(va);
}
-__attribute__((noreturn))
-void die(const char *msg)
-{
- if (errno == 0) {
- kmsg("*** %s", msg);
- } else {
- kmsg("*** %s: %s", msg, strerror(errno));
- }
- sync();
- reboot(RB_HALT_SYSTEM);
- exit(1);
-}
-
int sc_read_reboot_arg(char *arg, size_t max_size)
{
FILE *f;
diff -Nru snapd-2.41+19.10.1/cmd/system-shutdown/system-shutdown-utils.h snapd-2.42.1+19.10/cmd/system-shutdown/system-shutdown-utils.h
--- snapd-2.41+19.10.1/cmd/system-shutdown/system-shutdown-utils.h 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/cmd/system-shutdown/system-shutdown-utils.h 2019-10-30 12:17:43.000000000 +0000
@@ -25,8 +25,6 @@
// no longer found writable.
bool umount_all(void);
-__attribute__((noreturn))
-void die(const char *msg);
__attribute__((format(printf, 1, 2)))
void kmsg(const char *fmt, ...);
diff -Nru snapd-2.41+19.10.1/daemon/api_asserts.go snapd-2.42.1+19.10/daemon/api_asserts.go
--- snapd-2.41+19.10.1/daemon/api_asserts.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/daemon/api_asserts.go 2019-10-30 12:17:43.000000000 +0000
@@ -20,7 +20,9 @@
package daemon
import (
+ "errors"
"net/http"
+ "net/url"
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/overlord/assertstate"
@@ -43,6 +45,40 @@
}
)
+// a helper type for parsing the options specified to /v2/assertions and other
+// such endpoints that can either do JSON or assertion depending on the value
+// of the the URL query parameters
+type daemonAssertFormatOptions struct {
+ jsonResult bool
+ headersOnly bool
+ headers map[string]string
+}
+
+// helper for parsing url query options into formatting option vars
+func parseHeadersFormatOptionsFromURL(q url.Values) (*daemonAssertFormatOptions, error) {
+ res := daemonAssertFormatOptions{}
+ res.headers = make(map[string]string)
+ for k := range q {
+ if k == "json" {
+ switch q.Get(k) {
+ case "false":
+ res.jsonResult = false
+ case "headers":
+ res.headersOnly = true
+ fallthrough
+ case "true":
+ res.jsonResult = true
+ default:
+ return nil, errors.New(`"json" query parameter when used must be set to "true" or "headers"`)
+ }
+ continue
+ }
+ res.headers[k] = q.Get(k)
+ }
+
+ return &res, nil
+}
+
func getAssertTypeNames(c *Command, r *http.Request, user *auth.UserState) Response {
return SyncResponse(map[string][]string{
"types": asserts.TypeNames(),
@@ -50,7 +86,7 @@
}
func doAssert(c *Command, r *http.Request, user *auth.UserState) Response {
- batch := assertstate.NewBatch()
+ batch := asserts.NewBatch(nil)
_, err := batch.AddStream(r.Body)
if err != nil {
return BadRequest("cannot decode request body into assertions: %v", err)
@@ -60,7 +96,9 @@
state.Lock()
defer state.Unlock()
- if err := batch.Commit(state); err != nil {
+ if err := assertstate.AddBatch(state, batch, &asserts.CommitOptions{
+ Precheck: true,
+ }); err != nil {
return BadRequest("assert failed: %v", err)
}
// TODO: what more info do we want to return on success?
@@ -76,46 +114,28 @@
if assertType == nil {
return BadRequest("invalid assert type: %q", assertTypeName)
}
- jsonResult := false
- headersOnly := false
- headers := map[string]string{}
- q := r.URL.Query()
- for k := range q {
- if k == "json" {
- switch q.Get(k) {
- case "false":
- jsonResult = false
- case "headers":
- headersOnly = true
- fallthrough
- case "true":
- jsonResult = true
- default:
- return BadRequest(`"json" query parameter when used must be set to "true" or "headers"`)
- }
- continue
- }
- headers[k] = q.Get(k)
+ opts, err := parseHeadersFormatOptionsFromURL(r.URL.Query())
+ if err != nil {
+ return BadRequest(err.Error())
}
-
state := c.d.overlord.State()
state.Lock()
db := assertstate.DB(state)
state.Unlock()
- assertions, err := db.FindMany(assertType, headers)
+ assertions, err := db.FindMany(assertType, opts.headers)
if err != nil && !asserts.IsNotFound(err) {
return InternalError("searching assertions failed: %v", err)
}
- if jsonResult {
+ if opts.jsonResult {
assertsJSON := make([]struct {
Headers map[string]interface{} `json:"headers,omitempty"`
Body string `json:"body,omitempty"`
}, len(assertions))
for i := range assertions {
assertsJSON[i].Headers = assertions[i].Headers()
- if !headersOnly {
+ if !opts.headersOnly {
assertsJSON[i].Body = string(assertions[i].Body())
}
}
diff -Nru snapd-2.41+19.10.1/daemon/api.go snapd-2.42.1+19.10/daemon/api.go
--- snapd-2.41+19.10.1/daemon/api.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/daemon/api.go 2019-10-30 12:17:43.000000000 +0000
@@ -103,6 +103,7 @@
connectionsCmd,
modelCmd,
cohortsCmd,
+ serialModelCmd,
}
var (
diff -Nru snapd-2.41+19.10.1/daemon/api_model.go snapd-2.42.1+19.10/daemon/api_model.go
--- snapd-2.41+19.10.1/daemon/api_model.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/daemon/api_model.go 2019-10-30 12:17:43.000000000 +0000
@@ -26,13 +26,29 @@
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/overlord/auth"
"github.com/snapcore/snapd/overlord/devicestate"
+ "github.com/snapcore/snapd/overlord/state"
)
-var modelCmd = &Command{
- Path: "/v2/model",
- POST: postModel,
- // TODO: provide GET here too once we decided on the details of the API
-}
+var (
+ serialModelCmd = &Command{
+ Path: "/v2/model/serial",
+ GET: getSerial,
+ UserOK: true,
+ }
+ modelCmd = &Command{
+ Path: "/v2/model",
+ POST: postModel,
+ GET: getModel,
+ UserOK: true,
+ }
+)
+
+type assertType int
+
+const (
+ serialType assertType = iota
+ modelType
+)
var devicestateRemodel = devicestate.Remodel
@@ -40,6 +56,11 @@
NewModel string `json:"new-model"`
}
+type modelAssertJSONResponse struct {
+ Headers map[string]interface{} `json:"headers,omitempty"`
+ Body string `json:"body,omitempty"`
+}
+
func postModel(c *Command, r *http.Request, _ *auth.UserState) Response {
defer r.Body.Close()
var data postModelData
@@ -69,3 +90,93 @@
return AsyncResponse(nil, &Meta{Change: chg.ID()})
}
+
+// getModel gets the current model assertion using the DeviceManager
+func getModel(c *Command, r *http.Request, _ *auth.UserState) Response {
+ opts, err := parseHeadersFormatOptionsFromURL(r.URL.Query())
+ if err != nil {
+ return BadRequest(err.Error())
+ }
+
+ st := c.d.overlord.State()
+ st.Lock()
+ defer st.Unlock()
+
+ devmgr := c.d.overlord.DeviceManager()
+
+ model, err := devmgr.Model()
+ if err == state.ErrNoState {
+ res := &errorResult{
+ Message: "no model assertion yet",
+ Kind: errorKindAssertionNotFound,
+ Value: "model",
+ }
+
+ return &resp{
+ Type: ResponseTypeError,
+ Result: res,
+ Status: 404,
+ }
+ }
+ if err != nil {
+ return InternalError("accessing model failed: %v", err)
+ }
+
+ if opts.jsonResult {
+ modelJSON := modelAssertJSONResponse{}
+
+ modelJSON.Headers = model.Headers()
+ if !opts.headersOnly {
+ modelJSON.Body = string(model.Body())
+ }
+
+ return SyncResponse(modelJSON, nil)
+ }
+
+ return AssertResponse([]asserts.Assertion{model}, true)
+}
+
+// getSerial gets the current serial assertion using the DeviceManager
+func getSerial(c *Command, r *http.Request, _ *auth.UserState) Response {
+ opts, err := parseHeadersFormatOptionsFromURL(r.URL.Query())
+ if err != nil {
+ return BadRequest(err.Error())
+ }
+
+ st := c.d.overlord.State()
+ st.Lock()
+ defer st.Unlock()
+
+ devmgr := c.d.overlord.DeviceManager()
+
+ serial, err := devmgr.Serial()
+ if err == state.ErrNoState {
+ res := &errorResult{
+ Message: "no serial assertion yet",
+ Kind: errorKindAssertionNotFound,
+ Value: "serial",
+ }
+
+ return &resp{
+ Type: ResponseTypeError,
+ Result: res,
+ Status: 404,
+ }
+ }
+ if err != nil {
+ return InternalError("accessing serial failed: %v", err)
+ }
+
+ if opts.jsonResult {
+ serialJSON := modelAssertJSONResponse{}
+
+ serialJSON.Headers = serial.Headers()
+ if !opts.headersOnly {
+ serialJSON.Body = string(serial.Body())
+ }
+
+ return SyncResponse(serialJSON, nil)
+ }
+
+ return AssertResponse([]asserts.Assertion{serial}, true)
+}
diff -Nru snapd-2.41+19.10.1/daemon/api_model_test.go snapd-2.42.1+19.10/daemon/api_model_test.go
--- snapd-2.41+19.10.1/daemon/api_model_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/daemon/api_model_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -23,12 +23,16 @@
"bytes"
"encoding/json"
"net/http"
+ "time"
"gopkg.in/check.v1"
"github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/asserts/assertstest"
"github.com/snapcore/snapd/overlord/assertstate/assertstatetest"
+ "github.com/snapcore/snapd/overlord/auth"
"github.com/snapcore/snapd/overlord/devicestate"
+ "github.com/snapcore/snapd/overlord/devicestate/devicestatetest"
"github.com/snapcore/snapd/overlord/hookstate"
"github.com/snapcore/snapd/overlord/state"
)
@@ -105,3 +109,254 @@
c.Assert(soon, check.Equals, 1)
}
+
+func (s *apiSuite) TestGetModelNoModelAssertion(c *check.C) {
+
+ d := s.daemonWithOverlordMock(c)
+ hookMgr, err := hookstate.Manager(d.overlord.State(), d.overlord.TaskRunner())
+ c.Assert(err, check.IsNil)
+ deviceMgr, err := devicestate.Manager(d.overlord.State(), hookMgr, d.overlord.TaskRunner(), nil)
+ c.Assert(err, check.IsNil)
+ d.overlord.AddManager(deviceMgr)
+
+ req, err := http.NewRequest("GET", "/v2/model", nil)
+ c.Assert(err, check.IsNil)
+ response := getModel(appsCmd, req, nil)
+ c.Assert(response, check.FitsTypeOf, &resp{})
+ rsp := response.(*resp)
+ c.Assert(rsp.Status, check.Equals, 404)
+ c.Assert(rsp.Result, check.FitsTypeOf, &errorResult{})
+ errRes := rsp.Result.(*errorResult)
+ c.Assert(errRes.Kind, check.Equals, errorKindAssertionNotFound)
+ c.Assert(errRes.Value, check.Equals, "model")
+ c.Assert(errRes.Message, check.Equals, "no model assertion yet")
+}
+
+func (s *apiSuite) TestGetModelHasModelAssertion(c *check.C) {
+ // make a model assertion
+ theModel := s.brands.Model("my-brand", "my-old-model", modelDefaults)
+
+ // model assertion setup
+ d := s.daemonWithOverlordMock(c)
+ hookMgr, err := hookstate.Manager(d.overlord.State(), d.overlord.TaskRunner())
+ c.Assert(err, check.IsNil)
+ deviceMgr, err := devicestate.Manager(d.overlord.State(), hookMgr, d.overlord.TaskRunner(), nil)
+ c.Assert(err, check.IsNil)
+ d.overlord.AddManager(deviceMgr)
+ st := d.overlord.State()
+ st.Lock()
+ assertstatetest.AddMany(st, s.storeSigning.StoreAccountKey(""))
+ assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...)
+ s.mockModel(c, st, theModel)
+ st.Unlock()
+
+ // make a new get request to the model endpoint
+ req, err := http.NewRequest("GET", "/v2/model", nil)
+ c.Assert(err, check.IsNil)
+ response := getModel(appsCmd, req, nil)
+
+ // check that we get an assertion response
+ c.Assert(response, check.FitsTypeOf, &assertResponse{})
+
+ // check that there is only one assertion
+ assertions := response.(*assertResponse).assertions
+ c.Assert(assertions, check.HasLen, 1)
+
+ // check that one of the assertion keys matches what's in the model we
+ // provided
+ assert := assertions[0]
+ arch := assert.Header("architecture")
+ c.Assert(arch, check.FitsTypeOf, "")
+ c.Assert(arch.(string), check.Equals, modelDefaults["architecture"])
+}
+
+func (s *apiSuite) TestGetModelJSONHasModelAssertion(c *check.C) {
+ // make a model assertion
+ theModel := s.brands.Model("my-brand", "my-old-model", modelDefaults)
+
+ // model assertion setup
+ d := s.daemonWithOverlordMock(c)
+ hookMgr, err := hookstate.Manager(d.overlord.State(), d.overlord.TaskRunner())
+ c.Assert(err, check.IsNil)
+ deviceMgr, err := devicestate.Manager(d.overlord.State(), hookMgr, d.overlord.TaskRunner(), nil)
+ c.Assert(err, check.IsNil)
+ d.overlord.AddManager(deviceMgr)
+ st := d.overlord.State()
+ st.Lock()
+ assertstatetest.AddMany(st, s.storeSigning.StoreAccountKey(""))
+ assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...)
+ s.mockModel(c, st, theModel)
+ st.Unlock()
+
+ // make a new get request to the model endpoint with json as true
+ req, err := http.NewRequest("GET", "/v2/model?json=true", nil)
+ c.Assert(err, check.IsNil)
+ response := getModel(appsCmd, req, nil)
+
+ // check that we get an generic response type
+ c.Assert(response, check.FitsTypeOf, &resp{})
+
+ // get the body and try to unmarshal into modelAssertJSONResponse
+ c.Assert(response.(*resp).Result, check.FitsTypeOf, modelAssertJSONResponse{})
+
+ jsonResponse := response.(*resp).Result.(modelAssertJSONResponse)
+
+ // get the architecture key from the headers
+ arch, ok := jsonResponse.Headers["architecture"]
+ c.Assert(ok, check.Equals, true)
+
+ // ensure that the architecture key is what we set in the model defaults
+ c.Assert(arch, check.FitsTypeOf, "")
+ c.Assert(arch.(string), check.Equals, modelDefaults["architecture"])
+}
+
+func (s *apiSuite) TestGetModelNoSerialAssertion(c *check.C) {
+
+ d := s.daemonWithOverlordMock(c)
+ hookMgr, err := hookstate.Manager(d.overlord.State(), d.overlord.TaskRunner())
+ c.Assert(err, check.IsNil)
+ deviceMgr, err := devicestate.Manager(d.overlord.State(), hookMgr, d.overlord.TaskRunner(), nil)
+ c.Assert(err, check.IsNil)
+ d.overlord.AddManager(deviceMgr)
+
+ req, err := http.NewRequest("GET", "/v2/model/serial", nil)
+ c.Assert(err, check.IsNil)
+ response := getSerial(appsCmd, req, nil)
+ c.Assert(response, check.FitsTypeOf, &resp{})
+ rsp := response.(*resp)
+ c.Assert(rsp.Status, check.Equals, 404)
+ c.Assert(rsp.Result, check.FitsTypeOf, &errorResult{})
+ errRes := rsp.Result.(*errorResult)
+ c.Assert(errRes.Kind, check.Equals, errorKindAssertionNotFound)
+ c.Assert(errRes.Value, check.Equals, "serial")
+ c.Assert(errRes.Message, check.Equals, "no serial assertion yet")
+}
+
+func (s *apiSuite) TestGetModelHasSerialAssertion(c *check.C) {
+ // make a model assertion
+ theModel := s.brands.Model("my-brand", "my-old-model", modelDefaults)
+
+ deviceKey, _ := assertstest.GenerateKey(752)
+
+ encDevKey, err := asserts.EncodePublicKey(deviceKey.PublicKey())
+ c.Assert(err, check.IsNil)
+
+ // model assertion setup
+ d := s.daemonWithOverlordMock(c)
+ hookMgr, err := hookstate.Manager(d.overlord.State(), d.overlord.TaskRunner())
+ c.Assert(err, check.IsNil)
+ deviceMgr, err := devicestate.Manager(d.overlord.State(), hookMgr, d.overlord.TaskRunner(), nil)
+ c.Assert(err, check.IsNil)
+ d.overlord.AddManager(deviceMgr)
+ st := d.overlord.State()
+ st.Lock()
+ defer st.Unlock()
+ assertstatetest.AddMany(st, s.storeSigning.StoreAccountKey(""))
+ assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...)
+ s.mockModel(c, st, theModel)
+
+ serial, err := s.brands.Signing("my-brand").Sign(asserts.SerialType, map[string]interface{}{
+ "authority-id": "my-brand",
+ "brand-id": "my-brand",
+ "model": "my-old-model",
+ "serial": "serialserial",
+ "device-key": string(encDevKey),
+ "device-key-sha3-384": deviceKey.PublicKey().ID(),
+ "timestamp": time.Now().Format(time.RFC3339),
+ }, nil, "")
+ c.Assert(err, check.IsNil)
+ assertstatetest.AddMany(st, serial)
+ devicestatetest.SetDevice(st, &auth.DeviceState{
+ Brand: "my-brand",
+ Model: "my-old-model",
+ Serial: "serialserial",
+ })
+
+ st.Unlock()
+ defer st.Lock()
+
+ // make a new get request to the serial endpoint
+ req, err := http.NewRequest("GET", "/v2/model/serial", nil)
+ c.Assert(err, check.IsNil)
+ response := getSerial(appsCmd, req, nil)
+
+ // check that we get an assertion response
+ c.Assert(response, check.FitsTypeOf, &assertResponse{})
+
+ // check that there is only one assertion
+ assertions := response.(*assertResponse).assertions
+ c.Assert(assertions, check.HasLen, 1)
+
+ // check that the device key in the returned assertion matches what we
+ // created above
+ assert := assertions[0]
+ devKey := assert.Header("device-key")
+ c.Assert(devKey, check.FitsTypeOf, "")
+ c.Assert(devKey.(string), check.Equals, string(encDevKey))
+}
+
+func (s *apiSuite) TestGetModelJSONHasSerialAssertion(c *check.C) {
+ // make a model assertion
+ theModel := s.brands.Model("my-brand", "my-old-model", modelDefaults)
+
+ deviceKey, _ := assertstest.GenerateKey(752)
+
+ encDevKey, err := asserts.EncodePublicKey(deviceKey.PublicKey())
+ c.Assert(err, check.IsNil)
+
+ // model assertion setup
+ d := s.daemonWithOverlordMock(c)
+ hookMgr, err := hookstate.Manager(d.overlord.State(), d.overlord.TaskRunner())
+ c.Assert(err, check.IsNil)
+ deviceMgr, err := devicestate.Manager(d.overlord.State(), hookMgr, d.overlord.TaskRunner(), nil)
+ c.Assert(err, check.IsNil)
+ d.overlord.AddManager(deviceMgr)
+ st := d.overlord.State()
+ st.Lock()
+ defer st.Unlock()
+ assertstatetest.AddMany(st, s.storeSigning.StoreAccountKey(""))
+ assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...)
+ s.mockModel(c, st, theModel)
+
+ serial, err := s.brands.Signing("my-brand").Sign(asserts.SerialType, map[string]interface{}{
+ "authority-id": "my-brand",
+ "brand-id": "my-brand",
+ "model": "my-old-model",
+ "serial": "serialserial",
+ "device-key": string(encDevKey),
+ "device-key-sha3-384": deviceKey.PublicKey().ID(),
+ "timestamp": time.Now().Format(time.RFC3339),
+ }, nil, "")
+ c.Assert(err, check.IsNil)
+ assertstatetest.AddMany(st, serial)
+ devicestatetest.SetDevice(st, &auth.DeviceState{
+ Brand: "my-brand",
+ Model: "my-old-model",
+ Serial: "serialserial",
+ })
+
+ st.Unlock()
+ defer st.Lock()
+
+ // make a new get request to the model endpoint with json as true
+ req, err := http.NewRequest("GET", "/v2/model/serial?json=true", nil)
+ c.Assert(err, check.IsNil)
+ response := getSerial(appsCmd, req, nil)
+
+ // check that we get an generic response type
+ c.Assert(response, check.FitsTypeOf, &resp{})
+
+ // get the body and try to unmarshal into modelAssertJSONResponse
+ c.Assert(response.(*resp).Result, check.FitsTypeOf, modelAssertJSONResponse{})
+
+ jsonResponse := response.(*resp).Result.(modelAssertJSONResponse)
+
+ // get the architecture key from the headers
+ devKey, ok := jsonResponse.Headers["device-key"]
+ c.Assert(ok, check.Equals, true)
+
+ // check that the device key in the returned assertion matches what we
+ // created above
+ c.Assert(devKey, check.FitsTypeOf, "")
+ c.Assert(devKey.(string), check.Equals, string(encDevKey))
+}
diff -Nru snapd-2.41+19.10.1/daemon/api_test.go snapd-2.42.1+19.10/daemon/api_test.go
--- snapd-2.41+19.10.1/daemon/api_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/daemon/api_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -72,6 +72,7 @@
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/channel"
"github.com/snapcore/snapd/snap/snaptest"
"github.com/snapcore/snapd/store"
"github.com/snapcore/snapd/store/storetest"
@@ -7124,12 +7125,12 @@
func (s *apiSuite) TestErrToResponseForRevisionNotAvailable(c *check.C) {
si := &snapInstruction{Action: "frobble", Snaps: []string{"foo"}}
- thisArch := arch.UbuntuArchitecture()
+ thisArch := arch.DpkgArchitecture()
err := &store.RevisionNotAvailableError{
Action: "install",
Channel: "stable",
- Releases: []snap.Channel{
+ Releases: []channel.Channel{
snaptest.MustParseChannel("beta", thisArch),
},
}
@@ -7155,7 +7156,7 @@
err = &store.RevisionNotAvailableError{
Action: "install",
Channel: "stable",
- Releases: []snap.Channel{
+ Releases: []channel.Channel{
snaptest.MustParseChannel("beta", "other-arch"),
},
}
diff -Nru snapd-2.41+19.10.1/daemon/daemon.go snapd-2.42.1+19.10/daemon/daemon.go
--- snapd-2.41+19.10.1/daemon/daemon.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/daemon/daemon.go 2019-10-30 12:17:43.000000000 +0000
@@ -429,6 +429,16 @@
panic("internal error: no Overlord")
}
+ to, reasoning, err := d.overlord.StartupTimeout()
+ if err != nil {
+ return err
+ }
+ if to > 0 {
+ to = to.Round(time.Microsecond)
+ us := to.Nanoseconds() / 1000
+ logger.Noticef("adjusting startup timeout by %v (%s)", to, reasoning)
+ systemdSdNotify(fmt.Sprintf("EXTEND_TIMEOUT_USEC=%d", us))
+ }
// now perform expensive overlord/manages initiliazation
if err := d.overlord.StartUp(); err != nil {
return err
diff -Nru snapd-2.41+19.10.1/daemon/daemon_test.go snapd-2.42.1+19.10/daemon/daemon_test.go
--- snapd-2.41+19.10.1/daemon/daemon_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/daemon/daemon_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -529,6 +529,8 @@
Current: snap.R(1),
})
st.Unlock()
+ // 1 snap => extended timeout 30s + 5s
+ const extendedTimeoutUSec = "EXTEND_TIMEOUT_USEC=35000000"
l1, err := net.Listen("tcp", "127.0.0.1:0")
c.Assert(err, check.IsNil)
@@ -543,7 +545,7 @@
c.Assert(d.Start(), check.IsNil)
- c.Check(s.notified, check.DeepEquals, []string{"READY=1"})
+ c.Check(s.notified, check.DeepEquals, []string{extendedTimeoutUSec, "READY=1"})
snapdDone := make(chan struct{})
go func() {
@@ -571,7 +573,7 @@
err = d.Stop(nil)
c.Check(err, check.IsNil)
- c.Check(s.notified, check.DeepEquals, []string{"READY=1", "STOPPING=1"})
+ c.Check(s.notified, check.DeepEquals, []string{extendedTimeoutUSec, "READY=1", "STOPPING=1"})
}
func (s *daemonSuite) TestRestartWiring(c *check.C) {
@@ -814,7 +816,7 @@
c.Check(delays[1], check.DeepEquals, 1*time.Minute)
// we are not stopping, we wait for the reboot instead
- c.Check(s.notified, check.DeepEquals, []string{"READY=1"})
+ c.Check(s.notified, check.DeepEquals, []string{"EXTEND_TIMEOUT_USEC=30000000", "READY=1"})
st.Lock()
defer st.Unlock()
diff -Nru snapd-2.41+19.10.1/daemon/response.go snapd-2.42.1+19.10/daemon/response.go
--- snapd-2.41+19.10.1/daemon/response.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/daemon/response.go 2019-10-30 12:17:43.000000000 +0000
@@ -188,6 +188,8 @@
errorKindDaemonRestart = errorKind("daemon-restart")
errorKindSystemRestart = errorKind("system-restart")
+
+ errorKindAssertionNotFound = errorKind("assertion-not-found")
)
type errorValue interface{}
@@ -418,7 +420,7 @@
kind := errorKindSnapRevisionNotAvailable
msg := rnaErr.Error()
if len(rnaErr.Releases) != 0 && rnaErr.Channel != "" {
- thisArch := arch.UbuntuArchitecture()
+ thisArch := arch.DpkgArchitecture()
values := map[string]interface{}{
"snap-name": snapName,
"action": rnaErr.Action,
diff -Nru snapd-2.41+19.10.1/data/selinux/snappy.te snapd-2.42.1+19.10/data/selinux/snappy.te
--- snapd-2.41+19.10.1/data/selinux/snappy.te 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/data/selinux/snappy.te 2019-10-30 12:17:43.000000000 +0000
@@ -325,6 +325,8 @@
# snapd runs journalctl to fetch logs
optional_policy(`
journalctl_run(snappy_t, snappy_roles)
+ # and kills journalctl once the logs have been fetched
+ allow snappy_t journalctl_t:process sigkill;
')
# only pops up in cloud images where cloud-init.target is incorrectly labeled
diff -Nru snapd-2.41+19.10.1/debian/changelog snapd-2.42.1+19.10/debian/changelog
--- snapd-2.41+19.10.1/debian/changelog 2019-08-30 09:42:43.000000000 +0000
+++ snapd-2.42.1+19.10/debian/changelog 2019-10-30 12:17:43.000000000 +0000
@@ -1,10 +1,247 @@
-snapd (2.41+19.10.1) eoan; urgency=medium
+snapd (2.42.1+19.10) eoan; urgency=medium
- * cherry-pick https://github.com/snapcore/snapd/pull/7380
+ * New upstream release, LP: #1846181
+ - interfaces: de-duplicate emitted update-ns profiles
+ - packaging: tweak handling of usr.lib.snapd.snap-confine
+ - interfaces: allow introspecting network-manager on core
+ - tests/main/interfaces-contacts-service: disable on openSUSE
+ Tumbleweed
+ - tests/lib/lxd-snapfuse: restore mount changes introduced by LXD
+ - snap: fix default-provider in seed validation
+ - tests: update system-usernames test now that opensuse-15.1 works
+ - overlord: set fake sertial in TestRemodelSwitchToDifferentKernel
+ - gadget: rename "boot{select,img}" -> system-boot-{select,image}
+ - tests: listing test, make accepted snapd/core versions consistent
- -- Michael Vogt Fri, 30 Aug 2019 11:42:43 +0200
+ -- Michael Vogt Wed, 30 Oct 2019 13:17:43 +0100
-snapd (2.41+19.10) eoan; urgency=medium
+snapd (2.42) xenial; urgency=medium
+
+ * New upstream release, LP: #1846181
+ - tests: disable {contacts,calendar}-service tests on debian-sid
+ - tests/main/snap-run: disable strace test cases on Arch
+ - cmd/system-shutdown: include correct prototype for die
+ - snap/naming: add test for hook name connect-plug-i2c
+ - cmd/snap-confine: allow digits in hook names
+ - gadget: do not fail the update when old gadget snap is missing
+ bare content
+ - tests: disable {contacts,calendar}-service tests on Arch Linux
+ - tests: move "centos-7" to unstable systems
+ - interfaces/docker-support,kubernetes-support: misc updates for
+ strict k8s
+ - packaging: remove obsolete usr.lib.snapd.snap-confine in
+ postinst
+ - tests: add test that ensures our snapfuse binary actually works
+ - packaging: use snapfuse_ll to speed up snapfuse performance
+ - usersession/userd: make sure to export DBus interfaces before
+ requesting a name
+ - data/selinux: allow snapd to issue sigkill to journalctl
+ - store: download propagates options to delta download
+ - wrappers: allow snaps to install icon theme icons
+ - debug: state-inspect debugging utility
+ - sandbox/cgroup: introduce cgroup wrappers package
+ - snap-confine: fix return value checks for udev functions
+ - cmd/model: output tweaks, add'l tests
+ - wrappers/services: add ServicesEnableState + unit tests
+ - tests: fix newline and wrong test name pointed out in previous PRs
+ - tests: extend mount-ns test to handle mimics
+ - run-checks, tests/main/go: allow gofmt checks to be skipped on
+ 19.10
+ - tests/main/interfaces-{calendar,contacts}-service: disable on
+ 19.10
+ - tests: part3 making tests work on ubuntu-core-18
+ - tests: fix interfaces-timeserver-control on 19.10
+ - overlord/snapstate: config revision code cleanup and extra tests
+ - devicestate: allow remodel to different kernels
+ - overlord,daemon: adjust startup timeout via EXTEND_TIMEOUT_USEC
+ using an estimate
+ - tests/main/many: increase kill-timeout to 5m
+ - interfaces/kubernetes-support: allow systemd-run to ptrace read
+ unconfined
+ - snapstate: auto transition on experimental.snapd-snap=true
+ - tests: retry checking until the written file on desktop-portal-
+ filechooser
+ - tests: unit test for a refresh failing on configure hook
+ - tests: remove mount_id and parent_id from mount-ns test data
+ - tests: move classic-ubuntu-core-transition* to nightly
+ - tests/mountinfo-tool: proper formatting of opt_fields
+ - overlord/configstate: special-case "null" in transaction Changes()
+ - snap-confine: fallback gracefully on a cgroup v2 only system
+ - tests: debian sid now ships new seccomp, adjust tests
+ - tests: explicitly restore after using LXD
+ - snapstate: make progress reporting less granular
+ - bootloader: little kernel support
+ - fixme: rename ubuntu*architectures to dpkg*architectures
+ - tests: run dbus-launch inside a systemd unit
+ - channel: introduce Resolve and ResolveLocked
+ - tests: run failing tests on ubuntu eoan due to is now set as
+ unstable
+ - systemd: detach rather than unmount .mount units
+ - cmd/snap-confine: add unit tests for sc_invocation, cleanup memory
+ leaks in tests
+ - boot,dirs,image: introduce boot.MakeBootable, use it in image
+ instead of ad hoc code
+ - cmd/snap-update-ns: clarify sharing comment
+ - tests/overlord/snapstate: refactor for cleaner test failures
+ - cmd/snap-update-ns: don't propagate detaching changes
+ - interfaces: allow reading mutter Xauthority file
+ - cmd/snap-confine: fix /snap duplication in legacy mode
+ - tests: fix mountinfo-tool filtering when used with rewriting
+ - seed,image,o/devicestate: extract seed loading to seed/seed16.go
+ - many: pass the rootdir and options to bootloader.Find
+ - tests: part5 making tests work on ubuntu-core-18
+ - cmd/snap-confine: keep track of snap instance name and the snap
+ name
+ - cmd: unify die() across C programs
+ - tests: add functions to make an abstraction for the snaps
+ - packaging/fedora, tests/lib/prepare-restore: helper tool for
+ packing sources for RPM
+ - cmd/snap: improve help and error msg for snapshot commands
+ - hookstate/ctlcmd: fix snapctl set help message
+ - cmd/snap: don't append / to snap name just because a dir exists
+ - tests: support fastly-global.cdn.snapcraft.io url on proxy-no-core
+ test
+ - tests: add --quiet switch to retry-tool
+ - tests: add unstable stage for travis execution
+ - tests: disable interfaces-timeserver-control on 19.10
+ - tests: don't guess in is_classic_confinement_supported
+ - boot, etc: simplify BootParticipant (etc) usage
+ - tests: verify retry-tool not retrying missing commands
+ - tests: rewrite "retry" command as retry-tool
+ - tests: move debug section after restore
+ - cmd/libsnap-confine-private, cmd/s-c: use constants for
+ snap/instance name lengths
+ - tests: measure behavior of the device cgroup
+ - boot, bootloader, o/devicestate: boot env manip goes in boot
+ - tests: enabling ubuntu 19.10-64 on spread.yaml
+ - tests: fix ephemeral mount table in left over by prepare
+ - tests: add version-tool for comparing versions
+ - cmd/libsnap: make feature flag enum 1< Tue, 01 Oct 2019 11:24:41 +0200
+
+snapd (2.41) xenial; urgency=medium
* New upstream release, LP: #1840740
- overlord/snapstate: revert track-risk behavior
diff -Nru snapd-2.41+19.10.1/debian/rules snapd-2.42.1+19.10/debian/rules
--- snapd-2.41+19.10.1/debian/rules 2019-08-30 09:42:43.000000000 +0000
+++ snapd-2.42.1+19.10/debian/rules 2019-10-30 12:17:43.000000000 +0000
@@ -168,9 +168,9 @@
# Generate static snap-exec, snapctl and snap-update-ns - it somehow includes CGO so
# we must force a static build here. We need a static snap-{exec,update-ns}/snapctl
# inside the core snap because not all bases will have a libc
- (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=$$(pwd)/../go-build CGO_ENABLED=0 go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snap-exec)
- (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=$$(pwd)/../go-build CGO_ENABLED=0 go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snapctl)
- (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=$$(pwd)/../go-build go build --ldflags '-extldflags "-static"' $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snap-update-ns)
+ (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=/tmp/go-build CGO_ENABLED=0 go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snap-exec)
+ (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=/tmp/go-build CGO_ENABLED=0 go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snapctl)
+ (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=/tmp/go-build 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)
@@ -180,7 +180,7 @@
# ensure snap-seccomp is build with a static libseccomp on Ubuntu
ifeq ($(shell dpkg-vendor --query Vendor),Ubuntu)
sed -i "s|#cgo LDFLAGS:|#cgo LDFLAGS: /usr/lib/$(shell dpkg-architecture -qDEB_TARGET_MULTIARCH)/libseccomp.a|" _build/src/$(DH_GOPKG)/cmd/snap-seccomp/main.go
- (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=$$(pwd)/../go-build CGO_LDFLAGS_ALLOW="/.*/libseccomp.a" go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snap-seccomp)
+ (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=/tmp/go-build CGO_LDFLAGS_ALLOW="/.*/libseccomp.a" go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snap-seccomp)
# ensure that libseccomp is not dynamically linked
ldd _build/bin/snap-seccomp
test "$$(ldd _build/bin/snap-seccomp | grep libseccomp)" = ""
@@ -197,7 +197,7 @@
$(MAKE) -C data all
# build squashfuse and rename to snapfuse
- (cd vendor/github.com/snapcore/squashfuse/src && mkdir -p autom4te.cache && ./autogen.sh --disable-demo && ./configure --disable-demo && make && mv squashfuse snapfuse)
+ (cd vendor/github.com/snapcore/squashfuse/src && mkdir -p autom4te.cache && ./autogen.sh --disable-demo && ./configure --disable-demo && make && mv squashfuse_ll snapfuse)
override_dh_auto_test:
dh_auto_test -- $(GCCGOFLAGS)
diff -Nru snapd-2.41+19.10.1/debian/snapd.maintscript snapd-2.42.1+19.10/debian/snapd.maintscript
--- snapd-2.41+19.10.1/debian/snapd.maintscript 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/debian/snapd.maintscript 2019-10-30 12:17:43.000000000 +0000
@@ -2,4 +2,4 @@
# we used to ship a custom grub config that is no longer needed
rm_conffile /etc/grub.d/09_snappy 1.7.3ubuntu1
rm_conffile /etc/ld.so.conf.d/snappy.conf 2.0.7~
-rm_conffile /etc/apparmor.d/usr.lib.snapd.snap-confine 2.23.6~ snap-confine
+rm_conffile /etc/apparmor.d/usr.lib.snapd.snap-confine 2.23.6~
diff -Nru snapd-2.41+19.10.1/debian/snapd.postinst snapd-2.42.1+19.10/debian/snapd.postinst
--- snapd-2.41+19.10.1/debian/snapd.postinst 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/debian/snapd.postinst 2019-10-30 12:17:43.000000000 +0000
@@ -41,12 +41,12 @@
# In commit 0dce4704a5d (2017-03-28, snapd v2.23.6) we renamed
# /etc/apparmor.d/usr.lib.snap-confine to usr.lib.snap-confine.real
- # to fix LP: #1673247 - however some people (upgrades?) still have
+ # to fix LP: #1673247 - however some people (developers?) still have
# the old usr.lib.snap-confine file. This seems to be loaded instead
# of the correct usr.lib.snap-confine.real profile. To fix this we
- # use the rather blunt approach to remove the file forcefully.
- if test -f /etc/apparmor.d/usr.lib.snapd.snap-confine && test "$(md5sum /etc/apparmor.d/usr.lib.snapd.snap-confine | cut -f1 -d' ')" = "2a38d40fe662f46fedd0aefbe78f23e9"; then
- rm -f /etc/apparmor.d/usr.lib.snapd.snap-confine
+ # use the rather blunt approach to rename the file forcefully.
+ if test -f /etc/apparmor.d/usr.lib.snapd.snap-confine && test -f /etc/apparmor.d/usr.lib.snapd.snap-confine.real; then
+ mv /etc/apparmor.d/usr.lib.snapd.snap-confine /etc/apparmor.d/usr.lib.snapd.snap-confine.dpkg-bak
fi
# Ensure that the void directory has correct permissions.
diff -Nru snapd-2.41+19.10.1/dirs/dirs.go snapd-2.42.1+19.10/dirs/dirs.go
--- snapd-2.41+19.10.1/dirs/dirs.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/dirs/dirs.go 2019-10-30 12:17:43.000000000 +0000
@@ -88,6 +88,7 @@
SnapUserServicesDir string
SnapSystemdConfDir string
SnapDesktopFilesDir string
+ SnapDesktopIconsDir string
SnapBusPolicyDir string
SystemApparmorDir string
@@ -206,6 +207,16 @@
return err == nil, err
}
+// SnapBlobDirUnder returns the path to the snap blob dir under rootdir.
+func SnapBlobDirUnder(rootdir string) string {
+ return filepath.Join(rootdir, snappyDir, "snaps")
+}
+
+// SnapSeedDirUnder returns the path to the snap seed dir under rootdir.
+func SnapSeedDirUnder(rootdir string) string {
+ return filepath.Join(rootdir, snappyDir, "seed")
+}
+
// SetRootDir allows settings a new global root directory, this is useful
// for e.g. chroot operations
func SetRootDir(rootdir string) {
@@ -231,8 +242,12 @@
SnapSeccompDir = filepath.Join(rootdir, snappyDir, "seccomp", "bpf")
SnapMountPolicyDir = filepath.Join(rootdir, snappyDir, "mount")
SnapMetaDir = filepath.Join(rootdir, snappyDir, "meta")
- SnapBlobDir = filepath.Join(rootdir, snappyDir, "snaps")
+ SnapBlobDir = SnapBlobDirUnder(rootdir)
+ // ${snappyDir}/desktop is added to $XDG_DATA_DIRS.
+ // Subdirectories are interpreted according to the relevant
+ // freedesktop.org specifications
SnapDesktopFilesDir = filepath.Join(rootdir, snappyDir, "desktop", "applications")
+ SnapDesktopIconsDir = filepath.Join(rootdir, snappyDir, "desktop", "icons")
SnapRunDir = filepath.Join(rootdir, "/run/snapd")
SnapRunNsDir = filepath.Join(SnapRunDir, "/ns")
SnapRunLockDir = filepath.Join(SnapRunDir, "/lock")
@@ -255,7 +270,7 @@
SnapCommandsDB = filepath.Join(SnapCacheDir, "commands.db")
SnapAuxStoreInfoDir = filepath.Join(SnapCacheDir, "aux")
- SnapSeedDir = filepath.Join(rootdir, snappyDir, "seed")
+ SnapSeedDir = SnapSeedDirUnder(rootdir)
SnapDeviceDir = filepath.Join(rootdir, snappyDir, "device")
SnapRepairDir = filepath.Join(rootdir, snappyDir, "repair")
diff -Nru snapd-2.41+19.10.1/dirs/dirs_test.go snapd-2.42.1+19.10/dirs/dirs_test.go
--- snapd-2.41+19.10.1/dirs/dirs_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/dirs/dirs_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -156,3 +156,13 @@
c.Check(dirs.IsCompleteShSymlink("/etc/passwd"), Equals, false)
c.Check(dirs.IsCompleteShSymlink("/does-not-exist"), Equals, false)
}
+
+func (s *DirsTestSuite) TestUnder(c *C) {
+ dirs.SetRootDir("/nowhere")
+ defer dirs.SetRootDir("")
+
+ rootdir := "/other-root"
+
+ c.Check(dirs.SnapBlobDirUnder(rootdir), Equals, "/other-root/var/lib/snapd/snaps")
+ c.Check(dirs.SnapSeedDirUnder(rootdir), Equals, "/other-root/var/lib/snapd/seed")
+}
diff -Nru snapd-2.41+19.10.1/errtracker/errtracker.go snapd-2.42.1+19.10/errtracker/errtracker.go
--- snapd-2.41+19.10.1/errtracker/errtracker.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/errtracker/errtracker.go 2019-10-30 12:17:43.000000000 +0000
@@ -429,7 +429,7 @@
}
report := map[string]string{
- "Architecture": arch.UbuntuArchitecture(),
+ "Architecture": arch.DpkgArchitecture(),
"SnapdVersion": SnapdVersion,
"DistroRelease": distroRelease(),
"HostSnapdBuildID": hostBuildID,
diff -Nru snapd-2.41+19.10.1/errtracker/errtracker_test.go snapd-2.42.1+19.10/errtracker/errtracker_test.go
--- snapd-2.41+19.10.1/errtracker/errtracker_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/errtracker/errtracker_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -167,7 +167,7 @@
"KernelVersion": osutil.KernelVersion(),
"ErrorMessage": "failed to do stuff",
"DuplicateSignature": "[failed to do stuff]",
- "Architecture": arch.UbuntuArchitecture(),
+ "Architecture": arch.DpkgArchitecture(),
"DidSnapdReExec": "yes",
"ProblemType": "Snap",
@@ -312,7 +312,7 @@
"SnapdVersion": "some-snapd-version",
"Date": "Fri Feb 17 09:51:00 2017",
"KernelVersion": osutil.KernelVersion(),
- "Architecture": arch.UbuntuArchitecture(),
+ "Architecture": arch.DpkgArchitecture(),
"DidSnapdReExec": "yes",
"ProblemType": "Repair",
diff -Nru snapd-2.41+19.10.1/features/features.go snapd-2.42.1+19.10/features/features.go
--- snapd-2.41+19.10.1/features/features.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/features/features.go 2019-10-30 12:17:43.000000000 +0000
@@ -76,6 +76,7 @@
var featuresExported = map[SnapdFeature]bool{
PerUserMountNamespace: true,
RefreshAppAwareness: true,
+ ParallelInstances: true,
}
// String returns the name of a snapd feature.
diff -Nru snapd-2.41+19.10.1/features/features_test.go snapd-2.42.1+19.10/features/features_test.go
--- snapd-2.41+19.10.1/features/features_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/features/features_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -57,9 +57,10 @@
func (*featureSuite) TestIsExported(c *C) {
c.Check(features.Layouts.IsExported(), Equals, false)
- c.Check(features.ParallelInstances.IsExported(), Equals, false)
c.Check(features.Hotplug.IsExported(), Equals, false)
c.Check(features.SnapdSnap.IsExported(), Equals, false)
+
+ c.Check(features.ParallelInstances.IsExported(), Equals, true)
c.Check(features.PerUserMountNamespace.IsExported(), Equals, true)
c.Check(features.RefreshAppAwareness.IsExported(), Equals, true)
}
@@ -95,6 +96,7 @@
func (*featureSuite) TestControlFile(c *C) {
c.Check(features.PerUserMountNamespace.ControlFile(), Equals, "/var/lib/snapd/features/per-user-mount-namespace")
c.Check(features.RefreshAppAwareness.ControlFile(), Equals, "/var/lib/snapd/features/refresh-app-awareness")
+ c.Check(features.ParallelInstances.ControlFile(), Equals, "/var/lib/snapd/features/parallel-instances")
// Features that are not exported don't have a control file.
c.Check(features.Layouts.ControlFile, PanicMatches, `cannot compute the control file of feature "layouts" because that feature is not exported`)
}
diff -Nru snapd-2.41+19.10.1/gadget/device_darwin.go snapd-2.42.1+19.10/gadget/device_darwin.go
--- snapd-2.41+19.10.1/gadget/device_darwin.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/gadget/device_darwin.go 2019-10-30 12:17:43.000000000 +0000
@@ -24,14 +24,14 @@
var errNotImplemented = errors.New("not implemented")
-func FindDeviceForStructure(ps *PositionedStructure) (string, error) {
+func FindDeviceForStructure(ps *LaidOutStructure) (string, error) {
return "", errNotImplemented
}
-func FindDeviceForStructureWithFallback(ps *PositionedStructure) (string, Size, error) {
+func FindDeviceForStructureWithFallback(ps *LaidOutStructure) (string, Size, error) {
return "", 0, errNotImplemented
}
-func FindMountPointForStructure(ps *PositionedStructure) (string, error) {
+func FindMountPointForStructure(ps *LaidOutStructure) (string, error) {
return "", errNotImplemented
}
diff -Nru snapd-2.41+19.10.1/gadget/device_linux.go snapd-2.42.1+19.10/gadget/device_linux.go
--- snapd-2.41+19.10.1/gadget/device_linux.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/gadget/device_linux.go 2019-10-30 12:17:43.000000000 +0000
@@ -40,7 +40,7 @@
// given volume structure, by inspecting its name and, optionally, the
// filesystem label. Assumes that the host's udev has set up device symlinks
// correctly.
-func FindDeviceForStructure(ps *PositionedStructure) (string, error) {
+func FindDeviceForStructure(ps *LaidOutStructure) (string, error) {
var candidates []string
if ps.Name != "" {
@@ -102,7 +102,7 @@
//
// Returns the device name and an offset at which the structure content starts
// within the device or an error.
-func FindDeviceForStructureWithFallback(ps *PositionedStructure) (dev string, offs Size, err error) {
+func FindDeviceForStructureWithFallback(ps *LaidOutStructure) (dev string, offs Size, err error) {
if !ps.IsBare() {
return "", 0, fmt.Errorf("internal error: cannot use with filesystem structures")
}
@@ -157,7 +157,7 @@
// FindMountPointForStructure locates a mount point of a device that matches
// given structure. The structure must have a filesystem defined, otherwise an
// error is raised.
-func FindMountPointForStructure(ps *PositionedStructure) (string, error) {
+func FindMountPointForStructure(ps *LaidOutStructure) (string, error) {
if ps.IsBare() {
return "", ErrNoFilesystemDefined
}
diff -Nru snapd-2.41+19.10.1/gadget/device_test.go snapd-2.42.1+19.10/gadget/device_test.go
--- snapd-2.41+19.10.1/gadget/device_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/gadget/device_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -90,7 +90,7 @@
for _, tc := range names {
c.Logf("trying: %q", tc)
- found, err := gadget.FindDeviceForStructure(&gadget.PositionedStructure{
+ found, err := gadget.FindDeviceForStructure(&gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Name: tc.structure},
})
c.Check(err, IsNil)
@@ -102,7 +102,7 @@
err := os.Symlink("../../fakedevice", filepath.Join(d.dir, "/dev/disk/by-partlabel/relative"))
c.Assert(err, IsNil)
- found, err := gadget.FindDeviceForStructure(&gadget.PositionedStructure{
+ found, err := gadget.FindDeviceForStructure(&gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Name: "relative"},
})
c.Check(err, IsNil)
@@ -128,7 +128,7 @@
for _, tc := range names {
c.Logf("trying: %q", tc)
- found, err := gadget.FindDeviceForStructure(&gadget.PositionedStructure{
+ found, err := gadget.FindDeviceForStructure(&gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Label: tc.structure},
})
c.Check(err, IsNil)
@@ -144,7 +144,7 @@
err = os.Symlink(fakedevice, filepath.Join(d.dir, "/dev/disk/by-partlabel/bar"))
c.Assert(err, IsNil)
- found, err := gadget.FindDeviceForStructure(&gadget.PositionedStructure{
+ found, err := gadget.FindDeviceForStructure(&gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Name: "bar",
Label: "foo",
@@ -166,7 +166,7 @@
err = os.Symlink(fakedeviceOther, filepath.Join(d.dir, "/dev/disk/by-partlabel/bar"))
c.Assert(err, IsNil)
- found, err := gadget.FindDeviceForStructure(&gadget.PositionedStructure{
+ found, err := gadget.FindDeviceForStructure(&gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Name: "bar",
Label: "foo",
@@ -177,7 +177,7 @@
}
func (d *deviceSuite) TestDeviceFindNotFound(c *C) {
- found, err := gadget.FindDeviceForStructure(&gadget.PositionedStructure{
+ found, err := gadget.FindDeviceForStructure(&gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Name: "bar",
Label: "foo",
@@ -189,7 +189,7 @@
func (d *deviceSuite) TestDeviceFindNotFoundEmpty(c *C) {
// neither name nor filesystem label set
- found, err := gadget.FindDeviceForStructure(&gadget.PositionedStructure{
+ found, err := gadget.FindDeviceForStructure(&gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Name: "",
Label: "",
@@ -204,7 +204,7 @@
err := os.Symlink(fakedevice, filepath.Join(d.dir, "/dev/disk/by-label/foo"))
c.Assert(err, IsNil)
- found, err := gadget.FindDeviceForStructure(&gadget.PositionedStructure{
+ found, err := gadget.FindDeviceForStructure(&gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Label: "foo",
},
@@ -217,7 +217,7 @@
err := ioutil.WriteFile(filepath.Join(d.dir, "/dev/disk/by-label/foo"), nil, 0644)
c.Assert(err, IsNil)
- found, err := gadget.FindDeviceForStructure(&gadget.PositionedStructure{
+ found, err := gadget.FindDeviceForStructure(&gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Label: "foo",
},
@@ -238,7 +238,7 @@
})
defer restore()
- found, err := gadget.FindDeviceForStructure(&gadget.PositionedStructure{
+ found, err := gadget.FindDeviceForStructure(&gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Label: "foo",
},
@@ -254,7 +254,7 @@
mockProcSelfFilesystem(c, d.dir, badMountInfo)
- found, offs, err := gadget.FindDeviceForStructureWithFallback(&gadget.PositionedStructure{
+ found, offs, err := gadget.FindDeviceForStructureWithFallback(&gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Type: "bare",
},
@@ -268,7 +268,7 @@
func (d *deviceSuite) TestDeviceFindFallbackBadWritable(c *C) {
mockProcSelfFilesystem(c, d.dir, writableMountInfo)
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Type: "bare",
},
@@ -292,25 +292,25 @@
func (d *deviceSuite) TestDeviceFindFallbackHappyWritable(c *C) {
d.setUpWritableFallback(c, writableMountInfo)
- psJustBare := &gadget.PositionedStructure{
+ psJustBare := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Type: "bare",
},
StartOffset: 123,
}
- psBareWithName := &gadget.PositionedStructure{
+ psBareWithName := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Type: "bare",
Name: "foo",
},
StartOffset: 123,
}
- psNoName := &gadget.PositionedStructure{
+ psNoName := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{},
StartOffset: 123,
}
- for _, ps := range []*gadget.PositionedStructure{psJustBare, psBareWithName, psNoName} {
+ for _, ps := range []*gadget.LaidOutStructure{psJustBare, psBareWithName, psNoName} {
found, offs, err := gadget.FindDeviceForStructureWithFallback(ps)
c.Check(err, IsNil)
c.Check(found, Equals, filepath.Join(d.dir, "/dev/fakedevice0"))
@@ -322,7 +322,7 @@
d.setUpWritableFallback(c, writableMountInfo)
// should not hit the fallback path
- psNamed := &gadget.PositionedStructure{
+ psNamed := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Name: "foo",
},
@@ -337,7 +337,7 @@
func (d *deviceSuite) TestDeviceFindFallbackNotForFilesystem(c *C) {
d.setUpWritableFallback(c, writableMountInfo)
- psFs := &gadget.PositionedStructure{
+ psFs := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Label: "foo",
Filesystem: "ext4",
@@ -352,7 +352,7 @@
func (d *deviceSuite) TestDeviceFindFallbackBadMountInfo(c *C) {
d.setUpWritableFallback(c, "garbage")
- psFs := &gadget.PositionedStructure{
+ psFs := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Name: "foo",
Type: "bare",
@@ -369,7 +369,7 @@
err := ioutil.WriteFile(filepath.Join(d.dir, "/dev/disk/by-partlabel/foo"), nil, 0644)
c.Assert(err, IsNil)
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Name: "foo",
},
@@ -432,7 +432,7 @@
}
func (d *deviceSuite) TestDeviceFindMountPointErrorsWithBare(c *C) {
- p, err := gadget.FindMountPointForStructure(&gadget.PositionedStructure{
+ p, err := gadget.FindMountPointForStructure(&gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
// no filesystem
Filesystem: "",
@@ -441,7 +441,7 @@
c.Assert(err, ErrorMatches, "no filesystem defined")
c.Check(p, Equals, "")
- p, err = gadget.FindMountPointForStructure(&gadget.PositionedStructure{
+ p, err = gadget.FindMountPointForStructure(&gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
// also counts as bare structure
Filesystem: "none",
@@ -452,7 +452,7 @@
}
func (d *deviceSuite) TestDeviceFindMountPointErrorsFromDevice(c *C) {
- p, err := gadget.FindMountPointForStructure(&gadget.PositionedStructure{
+ p, err := gadget.FindMountPointForStructure(&gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Label: "bar",
Filesystem: "ext4",
@@ -461,7 +461,7 @@
c.Assert(err, ErrorMatches, "device not found")
c.Check(p, Equals, "")
- p, err = gadget.FindMountPointForStructure(&gadget.PositionedStructure{
+ p, err = gadget.FindMountPointForStructure(&gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Name: "bar",
Filesystem: "ext4",
@@ -490,7 +490,7 @@
mockProcSelfFilesystem(c, d.dir, "garbage")
- found, err := gadget.FindMountPointForStructure(&gadget.PositionedStructure{
+ found, err := gadget.FindMountPointForStructure(&gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Name: "EFI System",
Label: "system-boot",
@@ -518,7 +518,7 @@
`
mockProcSelfFilesystem(c, d.dir, strings.Replace(mountInfo[1:], "${rootDir}", d.dir, -1))
- found, err := gadget.FindMountPointForStructure(&gadget.PositionedStructure{
+ found, err := gadget.FindMountPointForStructure(&gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Name: "EFI System",
Label: "system-boot",
@@ -547,7 +547,7 @@
mockProcSelfFilesystem(c, d.dir, strings.Replace(mountInfoReversed[1:], "${rootDir}", d.dir, -1))
- found, err := gadget.FindMountPointForStructure(&gadget.PositionedStructure{
+ found, err := gadget.FindMountPointForStructure(&gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Name: "EFI System",
Label: "system-boot",
@@ -576,7 +576,7 @@
mockProcSelfFilesystem(c, d.dir, strings.Replace(mountInfo[1:], "${rootDir}", d.dir, -1))
- found, err := gadget.FindMountPointForStructure(&gadget.PositionedStructure{
+ found, err := gadget.FindMountPointForStructure(&gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Name: "EFI System",
Label: "system-boot",
@@ -600,7 +600,7 @@
mockProcSelfFilesystem(c, d.dir, strings.Replace(mountInfo[1:], "${rootDir}", d.dir, -1))
- found, err := gadget.FindMountPointForStructure(&gadget.PositionedStructure{
+ found, err := gadget.FindMountPointForStructure(&gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Name: "pinkié pie",
Filesystem: "ext4",
@@ -623,7 +623,7 @@
mockProcSelfFilesystem(c, d.dir, strings.Replace(mountInfo[1:], "${rootDir}", d.dir, -1))
- found, err := gadget.FindMountPointForStructure(&gadget.PositionedStructure{
+ found, err := gadget.FindMountPointForStructure(&gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Name: "label",
// different fs than mount entry
diff -Nru snapd-2.41+19.10.1/gadget/filesystemimage.go snapd-2.42.1+19.10/gadget/filesystemimage.go
--- snapd-2.41+19.10.1/gadget/filesystemimage.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/gadget/filesystemimage.go 2019-10-30 12:17:43.000000000 +0000
@@ -37,25 +37,25 @@
)
// FilesystemImageWriter is capable of creating filesystem images described by
-// positioned structures.
+// laid out structures.
type FilesystemImageWriter struct {
contentDir string
- ps *PositionedStructure
+ ps *LaidOutStructure
workDir string
}
// PostStageFunc is called after the filesystem contents for the given structure
// have been staged at a temporary location, but before the filesystem image is
// created. The function can be used to manipulate the staged data.
-type PostStageFunc func(rootDir string, ps *PositionedStructure) error
+type PostStageFunc func(rootDir string, ps *LaidOutStructure) error
// NewFilesystemImageWriter returns a writer capable of creating filesystem
// images corresponding to the provided structure, with content from the given
// content directory. A staging directory will be created in either, the
// optionally provided work directory, or the default temp location.
-func NewFilesystemImageWriter(contentDir string, ps *PositionedStructure, workDir string) (*FilesystemImageWriter, error) {
+func NewFilesystemImageWriter(contentDir string, ps *LaidOutStructure, workDir string) (*FilesystemImageWriter, error) {
if ps == nil {
- return nil, fmt.Errorf("internal error: *PositionedStructure is nil")
+ return nil, fmt.Errorf("internal error: *LaidOutStructure is nil")
}
if ps.IsBare() {
return nil, fmt.Errorf("internal error: structure has no filesystem")
diff -Nru snapd-2.41+19.10.1/gadget/filesystemimage_test.go snapd-2.42.1+19.10/gadget/filesystemimage_test.go
--- snapd-2.41+19.10.1/gadget/filesystemimage_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/gadget/filesystemimage_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -37,7 +37,7 @@
dir string
work string
content string
- psTrivial *gadget.PositionedStructure
+ psTrivial *gadget.LaidOutStructure
}
var _ = Suite(&filesystemImageTestSuite{})
@@ -54,7 +54,7 @@
// gadget content directory
s.content = filepath.Join(s.dir, "content")
- s.psTrivial = &gadget.PositionedStructure{
+ s.psTrivial = &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Filesystem: "happyfs",
Size: 2 * gadget.SizeMiB,
@@ -68,7 +68,7 @@
s.BaseTest.TearDownTest(c)
}
-func (s *filesystemImageTestSuite) imgForPs(c *C, ps *gadget.PositionedStructure) string {
+func (s *filesystemImageTestSuite) imgForPs(c *C, ps *gadget.LaidOutStructure) string {
c.Assert(ps, NotNil)
img := filepath.Join(s.dir, "img")
makeSizedFile(c, img, ps.Size, nil)
@@ -97,7 +97,7 @@
}
func (s *filesystemImageMockedTestSuite) TestSimpleErrors(c *C) {
- psValid := &gadget.PositionedStructure{
+ psValid := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Filesystem: "ext4",
Size: 2 * gadget.SizeMiB,
@@ -109,10 +109,10 @@
c.Assert(fiw, IsNil)
fiw, err = gadget.NewFilesystemImageWriter(s.dir, nil, "")
- c.Assert(err, ErrorMatches, `internal error: \*PositionedStructure is nil`)
+ c.Assert(err, ErrorMatches, `internal error: \*LaidOutStructure is nil`)
c.Assert(fiw, IsNil)
- psNoFs := &gadget.PositionedStructure{
+ psNoFs := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Filesystem: "none",
Size: 2 * gadget.SizeMiB,
@@ -123,7 +123,7 @@
c.Assert(err, ErrorMatches, "internal error: structure has no filesystem")
c.Assert(fiw, IsNil)
- psInvalidFs := &gadget.PositionedStructure{
+ psInvalidFs := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Filesystem: "xfs",
Size: 2 * gadget.SizeMiB,
@@ -135,7 +135,7 @@
}
func (s *filesystemImageMockedTestSuite) TestHappyFull(c *C) {
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Filesystem: "happyfs",
Label: "so-happy",
@@ -159,7 +159,7 @@
var cbCalled bool
var mkfsCalled bool
- cb := func(rootDir string, cbPs *gadget.PositionedStructure) error {
+ cb := func(rootDir string, cbPs *gadget.LaidOutStructure) error {
c.Assert(cbPs, DeepEquals, ps)
c.Assert(rootDir, Equals, filepath.Join(s.work, "snap-stage-content-part-0002"))
verifyWrittenGadgetData(c, rootDir, gd)
@@ -217,7 +217,7 @@
}
func (s *filesystemImageMockedTestSuite) TestChecksImage(c *C) {
- cb := func(rootDir string, cbPs *gadget.PositionedStructure) error {
+ cb := func(rootDir string, cbPs *gadget.LaidOutStructure) error {
return errors.New("unexpected call")
}
@@ -238,7 +238,7 @@
}
func (s *filesystemImageMockedTestSuite) TestPostStageError(c *C) {
- cb := func(rootDir string, cbPs *gadget.PositionedStructure) error {
+ cb := func(rootDir string, cbPs *gadget.LaidOutStructure) error {
return errors.New("post stage exploded")
}
@@ -270,7 +270,7 @@
}
func (s *filesystemImageMockedTestSuite) TestFilesystemExtraCheckError(c *C) {
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Filesystem: "happyfs",
Size: 2 * gadget.SizeMiB,
@@ -291,7 +291,7 @@
}
func (s *filesystemImageMockedTestSuite) TestMountedWriterError(c *C) {
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Filesystem: "happyfs",
Size: 2 * gadget.SizeMiB,
@@ -301,7 +301,7 @@
},
}
- cb := func(rootDir string, cbPs *gadget.PositionedStructure) error {
+ cb := func(rootDir string, cbPs *gadget.LaidOutStructure) error {
return errors.New("unexpected call")
}
@@ -316,7 +316,7 @@
}
func (s *filesystemImageMockedTestSuite) TestBadWorkDirError(c *C) {
- cb := func(rootDir string, cbPs *gadget.PositionedStructure) error {
+ cb := func(rootDir string, cbPs *gadget.LaidOutStructure) error {
return errors.New("unexpected call")
}
@@ -337,7 +337,7 @@
}
func (s *filesystemImageMockedTestSuite) TestKeepsStagingDir(c *C) {
- cb := func(rootDir string, cbPs *gadget.PositionedStructure) error {
+ cb := func(rootDir string, cbPs *gadget.LaidOutStructure) error {
return nil
}
mkfsHappyFs := func(imgFile, label, contentsRootDir string) error {
@@ -393,7 +393,7 @@
var _ = Suite(&filesystemImageMkfsTestSuite{})
func (s *filesystemImageMkfsTestSuite) TestExt4(c *C) {
- psExt4 := &gadget.PositionedStructure{
+ psExt4 := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Filesystem: "ext4",
Size: 2 * gadget.SizeMiB,
@@ -416,7 +416,7 @@
}
func (s *filesystemImageMkfsTestSuite) TestVfat(c *C) {
- psVfat := &gadget.PositionedStructure{
+ psVfat := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Filesystem: "vfat",
Size: 2 * gadget.SizeMiB,
diff -Nru snapd-2.41+19.10.1/gadget/gadget.go snapd-2.42.1+19.10/gadget/gadget.go
--- snapd-2.41+19.10.1/gadget/gadget.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/gadget/gadget.go 2019-10-30 12:17:43.000000000 +0000
@@ -47,9 +47,17 @@
SystemBoot = "system-boot"
SystemData = "system-data"
+
+ BootImage = "system-boot-image"
+ BootSelect = "system-boot-select"
+
// ImplicitSystemDataLabel is the implicit filesystem label of structure
// of system-data role
ImplicitSystemDataLabel = "writable"
+
+ // only supported for legacy reasons
+ LegacyBootImage = "bootimg"
+ LegacyBootSelect = "bootselect"
)
var (
@@ -112,7 +120,7 @@
// structure is treated as if it is of role 'mbr'.
Type string `yaml:"type"`
// Role describes the role of given structure, can be one of 'mbr',
- // 'system-data', 'system-boot'. Structures of type 'mbr', must have a
+ // 'system-data', 'system-boot', 'bootimg', 'bootselect'. Structures of type 'mbr', must have a
// size of 446 bytes and must start at 0 offset.
Role string `yaml:"role"`
// ID is the GPT partition ID
@@ -328,10 +336,10 @@
switch v.Bootloader {
case "":
// pass
- case "grub", "u-boot", "android-boot":
+ case "grub", "u-boot", "android-boot", "lk":
bootloadersFound += 1
default:
- return nil, errors.New("bootloader must be one of grub, u-boot or android-boot")
+ return nil, errors.New("bootloader must be one of grub, u-boot, android-boot or lk")
}
}
switch {
@@ -360,11 +368,11 @@
}
// named structures, for cross-referencing relative offset-write names
- knownStructures := make(map[string]*PositionedStructure, len(vol.Structure))
+ knownStructures := make(map[string]*LaidOutStructure, len(vol.Structure))
// for uniqueness of filesystem labels
knownFsLabels := make(map[string]bool, len(vol.Structure))
// for validating structure overlap
- structures := make([]PositionedStructure, len(vol.Structure))
+ structures := make([]LaidOutStructure, len(vol.Structure))
previousEnd := Size(0)
for idx, s := range vol.Structure {
@@ -378,7 +386,7 @@
start = previousEnd
}
end := start + s.Size
- ps := PositionedStructure{
+ ps := LaidOutStructure{
VolumeStructure: &vol.Structure[idx],
StartOffset: start,
Index: idx,
@@ -407,12 +415,12 @@
return validateCrossVolumeStructure(structures, knownStructures)
}
-func validateCrossVolumeStructure(structures []PositionedStructure, knownStructures map[string]*PositionedStructure) error {
+func validateCrossVolumeStructure(structures []LaidOutStructure, knownStructures map[string]*LaidOutStructure) error {
previousEnd := Size(0)
// cross structure validation:
// - relative offsets that reference other structures by name
- // - positioned structure overlap
- // use structures positioned within the volume
+ // - laid out structure overlap
+ // use structures laid out within the volume
for pidx, ps := range structures {
if ps.EffectiveRole() == MBR {
if ps.StartOffset != 0 {
@@ -591,8 +599,12 @@
if vs.Filesystem != "" && vs.Filesystem != "none" {
return errors.New("mbr structures must not specify a file system")
}
- case SystemBoot, "":
+ case SystemBoot, BootImage, BootSelect, "":
+ // noop
+ case LegacyBootImage, LegacyBootSelect:
// noop
+ // legacy role names were added in 2.42 can be removed
+ // on snapd epoch bump
default:
return fmt.Errorf("unsupported role")
}
diff -Nru snapd-2.41+19.10.1/gadget/gadget_test.go snapd-2.42.1+19.10/gadget/gadget_test.go
--- snapd-2.41+19.10.1/gadget/gadget_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/gadget/gadget_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -192,6 +192,78 @@
target: /
`)
+var gadgetYamlLk = []byte(`
+volumes:
+ volumename:
+ schema: mbr
+ bootloader: lk
+ structure:
+ - name: BOOTIMG1
+ size: 25165824
+ role: system-boot-image
+ type: 27
+ content:
+ - image: boot.img
+ - name: BOOTIMG2
+ size: 25165824
+ role: system-boot-image
+ type: 27
+ - name: snapbootsel
+ size: 131072
+ role: system-boot-select
+ type: B2
+ content:
+ - image: snapbootsel.bin
+ - name: snapbootselbak
+ size: 131072
+ role: system-boot-select
+ type: B2
+ content:
+ - image: snapbootsel.bin
+ - name: writable
+ type: 83
+ filesystem: ext4
+ filesystem-label: writable
+ size: 500M
+ role: system-data
+`)
+
+var gadgetYamlLkLegacy = []byte(`
+volumes:
+ volumename:
+ schema: mbr
+ bootloader: lk
+ structure:
+ - name: BOOTIMG1
+ size: 25165824
+ role: bootimg
+ type: 27
+ content:
+ - image: boot.img
+ - name: BOOTIMG2
+ size: 25165824
+ role: bootimg
+ type: 27
+ - name: snapbootsel
+ size: 131072
+ role: bootselect
+ type: B2
+ content:
+ - image: snapbootsel.bin
+ - name: snapbootselbak
+ size: 131072
+ role: bootselect
+ type: B2
+ content:
+ - image: snapbootsel.bin
+ - name: writable
+ type: 83
+ filesystem: ext4
+ filesystem-label: writable
+ size: 500M
+ role: system-data
+`)
+
func TestRun(t *testing.T) { TestingT(t) }
func mustParseGadgetSize(c *C, s string) gadget.Size {
@@ -374,7 +446,7 @@
c.Assert(err, IsNil)
_, err = gadget.ReadInfo(s.dir, false)
- c.Assert(err, ErrorMatches, "bootloader must be one of grub, u-boot or android-boot")
+ c.Assert(err, ErrorMatches, "bootloader must be one of grub, u-boot, android-boot or lk")
}
func (s *gadgetYamlTestSuite) TestReadGadgetYamlEmptyBootloader(c *C) {
@@ -583,6 +655,22 @@
c.Assert(err, IsNil)
}
+func (s *gadgetYamlTestSuite) TestReadGadgetYamlLkHappy(c *C) {
+ err := ioutil.WriteFile(s.gadgetYamlPath, gadgetYamlLk, 0644)
+ c.Assert(err, IsNil)
+
+ _, err = gadget.ReadInfo(s.dir, false)
+ c.Assert(err, IsNil)
+}
+
+func (s *gadgetYamlTestSuite) TestReadGadgetYamlLkLegacyHappy(c *C) {
+ err := ioutil.WriteFile(s.gadgetYamlPath, gadgetYamlLkLegacy, 0644)
+ c.Assert(err, IsNil)
+
+ _, err = gadget.ReadInfo(s.dir, false)
+ c.Assert(err, IsNil)
+}
+
func (s *gadgetYamlTestSuite) TestValidateStructureType(c *C) {
for i, tc := range []struct {
s string
@@ -1091,7 +1179,7 @@
c.Check(err, IsNil)
}
-func (s *gadgetYamlTestSuite) TestValidatePositioningOverlapPreceding(c *C) {
+func (s *gadgetYamlTestSuite) TestValidateLayoutOverlapPreceding(c *C) {
overlappingGadgetYaml := `
volumes:
pc:
@@ -1116,7 +1204,7 @@
c.Check(err, ErrorMatches, `invalid volume "pc": structure #1 \("other-name"\) overlaps with the preceding structure #0 \("mbr"\)`)
}
-func (s *gadgetYamlTestSuite) TestValidatePositioningOverlapOutOfOrder(c *C) {
+func (s *gadgetYamlTestSuite) TestValidateLayoutOverlapOutOfOrder(c *C) {
outOfOrderGadgetYaml := `
volumes:
pc:
diff -Nru snapd-2.41+19.10.1/gadget/layout.go snapd-2.42.1+19.10/gadget/layout.go
--- snapd-2.41+19.10.1/gadget/layout.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/gadget/layout.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,353 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+package gadget
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "sort"
+)
+
+// LayoutConstraints defines the constraints for arranging structures within a
+// volume
+type LayoutConstraints struct {
+ // NonMBRStartOffset is the default start offset of non-MBR structure in
+ // the volume.
+ NonMBRStartOffset Size
+ // SectorSize is the size of the sector to be used for calculations
+ SectorSize Size
+}
+
+// LaidOutVolume defines the size of a volume and arrangement of all the
+// structures within it
+type LaidOutVolume struct {
+ *Volume
+ // Size is the total size of the volume
+ Size Size
+ // SectorSize sector size of the volume
+ SectorSize Size
+ // LaidOutStructure is a list of structures within the volume, sorted
+ // by their start offsets
+ LaidOutStructure []LaidOutStructure
+ // RootDir is the root directory for volume data
+ RootDir string
+}
+
+// PartiallyLaidOutVolume defines the layout of volume structures, but lacks the
+// details about the layout of raw image content within the bare structures.
+type PartiallyLaidOutVolume struct {
+ *Volume
+ // SectorSize sector size of the volume
+ SectorSize Size
+ // LaidOutStructure is a list of structures within the volume, sorted
+ // by their start offsets
+ LaidOutStructure []LaidOutStructure
+}
+
+// LaidOutStructure describes a VolumeStructure that has been placed within the
+// volume
+type LaidOutStructure struct {
+ *VolumeStructure
+ // StartOffset defines the start offset of the structure within the
+ // enclosing volume
+ StartOffset Size
+ // AbsoluteOffsetWrite is the resolved absolute position of offset-write
+ // for this structure element within the enclosing volume
+ AbsoluteOffsetWrite *Size
+ // Index of the structure definition in gadget YAML
+ Index int
+ // LaidOutContent is a list of raw content inside the structure
+ LaidOutContent []LaidOutContent
+}
+
+func (p LaidOutStructure) String() string {
+ return fmtIndexAndName(p.Index, p.Name)
+}
+
+type byStartOffset []LaidOutStructure
+
+func (b byStartOffset) Len() int { return len(b) }
+func (b byStartOffset) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
+func (b byStartOffset) Less(i, j int) bool { return b[i].StartOffset < b[j].StartOffset }
+
+// LaidOutContent describes raw content that has been placed within the
+// encompassing structure and volume
+type LaidOutContent struct {
+ *VolumeContent
+
+ // StartOffset defines the start offset of this content image
+ StartOffset Size
+ // AbsoluteOffsetWrite is the resolved absolute position of offset-write
+ // for this content element within the enclosing volume
+ AbsoluteOffsetWrite *Size
+ // Size is the maximum size occupied by this image
+ Size Size
+ // Index of the content in structure declaration inside gadget YAML
+ Index int
+}
+
+func (p LaidOutContent) String() string {
+ if p.Image != "" {
+ return fmt.Sprintf("#%v (%q@%#x{%v})", p.Index, p.Image, p.StartOffset, p.Size)
+ }
+ return fmt.Sprintf("#%v (source:%q)", p.Index, p.Source)
+}
+
+func layoutVolumeStructures(volume *Volume, constraints LayoutConstraints) (structures []LaidOutStructure, byName map[string]*LaidOutStructure, err error) {
+ previousEnd := Size(0)
+ structures = make([]LaidOutStructure, len(volume.Structure))
+ byName = make(map[string]*LaidOutStructure, len(volume.Structure))
+
+ if constraints.SectorSize == 0 {
+ return nil, nil, fmt.Errorf("cannot lay out volume, invalid constraints: sector size cannot be 0")
+ }
+
+ for idx, s := range volume.Structure {
+ var start Size
+ if s.Offset == nil {
+ if s.EffectiveRole() != MBR && previousEnd < constraints.NonMBRStartOffset {
+ start = constraints.NonMBRStartOffset
+ } else {
+ start = previousEnd
+ }
+ } else {
+ start = *s.Offset
+ }
+
+ end := start + s.Size
+ ps := LaidOutStructure{
+ VolumeStructure: &volume.Structure[idx],
+ StartOffset: start,
+ Index: idx,
+ }
+
+ if ps.EffectiveRole() != MBR {
+ if s.Size%constraints.SectorSize != 0 {
+ return nil, nil, fmt.Errorf("cannot lay out volume, structure %v size is not a multiple of sector size %v",
+ ps, constraints.SectorSize)
+ }
+ }
+
+ if ps.Name != "" {
+ byName[ps.Name] = &ps
+ }
+
+ structures[idx] = ps
+
+ previousEnd = end
+ }
+
+ // sort by starting offset
+ sort.Sort(byStartOffset(structures))
+
+ previousEnd = Size(0)
+ for idx, ps := range structures {
+ if ps.StartOffset < previousEnd {
+ return nil, nil, fmt.Errorf("cannot lay out volume, structure %v overlaps with preceding structure %v", ps, structures[idx-1])
+ }
+ previousEnd = ps.StartOffset + ps.Size
+
+ offsetWrite, err := resolveOffsetWrite(ps.OffsetWrite, byName)
+ if err != nil {
+ return nil, nil, fmt.Errorf("cannot resolve offset-write of structure %v: %v", ps, err)
+ }
+ structures[idx].AbsoluteOffsetWrite = offsetWrite
+ }
+
+ return structures, byName, nil
+}
+
+// LayoutVolumePartially attempts to lay out only the structures in the volume using provided constraints
+func LayoutVolumePartially(volume *Volume, constraints LayoutConstraints) (*PartiallyLaidOutVolume, error) {
+ structures, _, err := layoutVolumeStructures(volume, constraints)
+ if err != nil {
+ return nil, err
+ }
+ vol := &PartiallyLaidOutVolume{
+ Volume: volume,
+ SectorSize: constraints.SectorSize,
+ LaidOutStructure: structures,
+ }
+ return vol, nil
+}
+
+// LayoutVolume attempts to completely lay out the volume, that is the
+// structures and their content, using provided constraints
+func LayoutVolume(gadgetRootDir string, volume *Volume, constraints LayoutConstraints) (*LaidOutVolume, error) {
+
+ structures, byName, err := layoutVolumeStructures(volume, constraints)
+ if err != nil {
+ return nil, err
+ }
+
+ farthestEnd := Size(0)
+ fartherstOffsetWrite := Size(0)
+
+ for idx, ps := range structures {
+ if ps.AbsoluteOffsetWrite != nil && *ps.AbsoluteOffsetWrite > fartherstOffsetWrite {
+ fartherstOffsetWrite = *ps.AbsoluteOffsetWrite
+ }
+ if end := ps.StartOffset + ps.Size; end > farthestEnd {
+ farthestEnd = end
+ }
+
+ content, err := layOutStructureContent(gadgetRootDir, &structures[idx], byName)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, c := range content {
+ if c.AbsoluteOffsetWrite != nil && *c.AbsoluteOffsetWrite > fartherstOffsetWrite {
+ fartherstOffsetWrite = *c.AbsoluteOffsetWrite
+ }
+ }
+
+ structures[idx].LaidOutContent = content
+ }
+
+ volumeSize := farthestEnd
+ if fartherstOffsetWrite+SizeLBA48Pointer > farthestEnd {
+ volumeSize = fartherstOffsetWrite + SizeLBA48Pointer
+ }
+
+ vol := &LaidOutVolume{
+ Volume: volume,
+ Size: volumeSize,
+ SectorSize: constraints.SectorSize,
+ LaidOutStructure: structures,
+ RootDir: gadgetRootDir,
+ }
+ return vol, nil
+}
+
+type byContentStartOffset []LaidOutContent
+
+func (b byContentStartOffset) Len() int { return len(b) }
+func (b byContentStartOffset) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
+func (b byContentStartOffset) Less(i, j int) bool { return b[i].StartOffset < b[j].StartOffset }
+
+func getImageSize(path string) (Size, error) {
+ stat, err := os.Stat(path)
+ if err != nil {
+ return 0, err
+ }
+ return Size(stat.Size()), nil
+}
+
+func layOutStructureContent(gadgetRootDir string, ps *LaidOutStructure, known map[string]*LaidOutStructure) ([]LaidOutContent, error) {
+ if !ps.IsBare() {
+ // structures with a filesystem do not need any extra layout
+ return nil, nil
+ }
+ if len(ps.Content) == 0 {
+ return nil, nil
+ }
+
+ content := make([]LaidOutContent, len(ps.Content))
+ previousEnd := Size(0)
+
+ for idx, c := range ps.Content {
+ imageSize, err := getImageSize(filepath.Join(gadgetRootDir, c.Image))
+ if err != nil {
+ return nil, fmt.Errorf("cannot lay out structure %v: content %q: %v", ps, c.Image, err)
+ }
+
+ var start Size
+ if c.Offset != nil {
+ start = *c.Offset
+ } else {
+ start = previousEnd
+ }
+
+ actualSize := imageSize
+
+ if c.Size != 0 {
+ if c.Size < imageSize {
+ return nil, fmt.Errorf("cannot lay out structure %v: content %q size %v is larger than declared %v", ps, c.Image, actualSize, c.Size)
+ }
+ actualSize = c.Size
+ }
+
+ offsetWrite, err := resolveOffsetWrite(c.OffsetWrite, known)
+ if err != nil {
+ return nil, fmt.Errorf("cannot resolve offset-write of structure %v content %q: %v", ps, c.Image, err)
+ }
+
+ content[idx] = LaidOutContent{
+ VolumeContent: &ps.Content[idx],
+ Size: actualSize,
+ StartOffset: ps.StartOffset + start,
+ Index: idx,
+ // break for gofmt < 1.11
+ AbsoluteOffsetWrite: offsetWrite,
+ }
+ previousEnd = start + actualSize
+ if previousEnd > ps.Size {
+ return nil, fmt.Errorf("cannot lay out structure %v: content %q does not fit in the structure", ps, c.Image)
+ }
+ }
+
+ sort.Sort(byContentStartOffset(content))
+
+ previousEnd = ps.StartOffset
+ for idx, pc := range content {
+ if pc.StartOffset < previousEnd {
+ return nil, fmt.Errorf("cannot lay out structure %v: content %q overlaps with preceding image %q", ps, pc.Image, content[idx-1].Image)
+ }
+ previousEnd = pc.StartOffset + pc.Size
+ }
+
+ return content, nil
+}
+
+func resolveOffsetWrite(offsetWrite *RelativeOffset, knownStructs map[string]*LaidOutStructure) (*Size, error) {
+ if offsetWrite == nil {
+ return nil, nil
+ }
+
+ var relativeToOffset Size
+ if offsetWrite.RelativeTo != "" {
+ otherStruct, ok := knownStructs[offsetWrite.RelativeTo]
+ if !ok {
+ return nil, fmt.Errorf("refers to an unknown structure %q", offsetWrite.RelativeTo)
+ }
+ relativeToOffset = otherStruct.StartOffset
+ }
+
+ resolvedOffsetWrite := relativeToOffset + offsetWrite.Offset
+ return &resolvedOffsetWrite, nil
+}
+
+// ShiftStructureTo translates the starting offset of a laid out structure and
+// its content to the provided offset.
+func ShiftStructureTo(ps LaidOutStructure, offset Size) LaidOutStructure {
+ change := int64(offset - ps.StartOffset)
+
+ newPs := ps
+ newPs.StartOffset = Size(int64(ps.StartOffset) + change)
+
+ newPs.LaidOutContent = make([]LaidOutContent, len(ps.LaidOutContent))
+ for idx, pc := range ps.LaidOutContent {
+ newPc := pc
+ newPc.StartOffset = Size(int64(pc.StartOffset) + change)
+ newPs.LaidOutContent[idx] = newPc
+ }
+ return newPs
+}
diff -Nru snapd-2.41+19.10.1/gadget/layout_test.go snapd-2.42.1+19.10/gadget/layout_test.go
--- snapd-2.41+19.10.1/gadget/layout_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/gadget/layout_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,1141 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package gadget_test
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+
+ . "gopkg.in/check.v1"
+ "gopkg.in/yaml.v2"
+
+ "github.com/snapcore/snapd/gadget"
+)
+
+type layoutTestSuite struct {
+ dir string
+}
+
+var _ = Suite(&layoutTestSuite{})
+
+func (p *layoutTestSuite) SetUpTest(c *C) {
+ p.dir = c.MkDir()
+}
+
+var defaultConstraints = gadget.LayoutConstraints{
+ NonMBRStartOffset: 1 * gadget.SizeMiB,
+ SectorSize: 512,
+}
+
+func (p *layoutTestSuite) TestVolumeSize(c *C) {
+ vol := gadget.Volume{
+ Structure: []gadget.VolumeStructure{
+ {Size: 2 * gadget.SizeMiB},
+ },
+ }
+ v, err := gadget.LayoutVolume(p.dir, &vol, defaultConstraints)
+ c.Assert(err, IsNil)
+
+ c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
+ Volume: &gadget.Volume{
+ Structure: []gadget.VolumeStructure{
+ {Size: 2 * gadget.SizeMiB},
+ },
+ },
+ Size: 3 * gadget.SizeMiB,
+ SectorSize: 512,
+ RootDir: p.dir,
+ LaidOutStructure: []gadget.LaidOutStructure{
+ {VolumeStructure: &gadget.VolumeStructure{Size: 2 * gadget.SizeMiB}, StartOffset: 1 * gadget.SizeMiB},
+ },
+ })
+}
+
+func mustParseVolume(c *C, gadgetYaml, volume string) *gadget.Volume {
+ var gi gadget.Info
+ err := yaml.Unmarshal([]byte(gadgetYaml), &gi)
+ c.Assert(err, IsNil)
+ v, ok := gi.Volumes[volume]
+ c.Assert(ok, Equals, true, Commentf("volume %q not found in gadget", volume))
+ err = gadget.ValidateVolume("foo", &v)
+ c.Assert(err, IsNil)
+ return &v
+}
+
+func (p *layoutTestSuite) TestLayoutVolumeMinimal(c *C) {
+ gadgetYaml := `
+volumes:
+ first-image:
+ bootloader: u-boot
+ structure:
+ - type: 00000000-0000-0000-0000-0000deadbeef
+ size: 400M
+ - type: 83,00000000-0000-0000-0000-0000feedface
+ role: system-data
+ size: 100M
+`
+ vol := mustParseVolume(c, gadgetYaml, "first-image")
+ c.Assert(vol.Structure, HasLen, 2)
+
+ v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
+ c.Assert(err, IsNil)
+
+ c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
+ Volume: vol,
+ Size: 501 * gadget.SizeMiB,
+ SectorSize: 512,
+ RootDir: p.dir,
+ LaidOutStructure: []gadget.LaidOutStructure{
+ {
+ VolumeStructure: &vol.Structure[0],
+ StartOffset: 1 * gadget.SizeMiB,
+ Index: 0,
+ },
+ {
+ VolumeStructure: &vol.Structure[1],
+ StartOffset: 401 * gadget.SizeMiB,
+ Index: 1,
+ },
+ },
+ })
+}
+
+func (p *layoutTestSuite) TestLayoutVolumeImplicitOrdering(c *C) {
+ gadgetYaml := `
+volumes:
+ first:
+ schema: gpt
+ bootloader: grub
+ structure:
+ - type: 00000000-0000-0000-0000-dd00deadbeef
+ size: 400M
+ - type: 00000000-0000-0000-0000-cc00deadbeef
+ role: system-data
+ size: 500M
+ - type: 00000000-0000-0000-0000-bb00deadbeef
+ size: 100M
+ - type: 00000000-0000-0000-0000-aa00deadbeef
+ size: 100M
+`
+ vol := mustParseVolume(c, gadgetYaml, "first")
+ c.Assert(vol.Structure, HasLen, 4)
+
+ v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
+ c.Assert(err, IsNil)
+
+ c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
+ Volume: vol,
+ Size: 1101 * gadget.SizeMiB,
+ SectorSize: 512,
+ RootDir: p.dir,
+ LaidOutStructure: []gadget.LaidOutStructure{
+ {
+ VolumeStructure: &vol.Structure[0],
+ StartOffset: 1 * gadget.SizeMiB,
+ Index: 0,
+ },
+ {
+ VolumeStructure: &vol.Structure[1],
+ StartOffset: 401 * gadget.SizeMiB,
+ Index: 1,
+ },
+ {
+ VolumeStructure: &vol.Structure[2],
+ StartOffset: 901 * gadget.SizeMiB,
+ Index: 2,
+ },
+ {
+ VolumeStructure: &vol.Structure[3],
+ StartOffset: 1001 * gadget.SizeMiB,
+ Index: 3,
+ },
+ },
+ })
+}
+
+func (p *layoutTestSuite) TestLayoutVolumeExplicitOrdering(c *C) {
+ gadgetYaml := `
+volumes:
+ first:
+ schema: gpt
+ bootloader: grub
+ structure:
+ - type: 00000000-0000-0000-0000-dd00deadbeef
+ size: 400M
+ offset: 800M
+ - type: 00000000-0000-0000-0000-cc00deadbeef
+ role: system-data
+ size: 500M
+ offset: 200M
+ - type: 00000000-0000-0000-0000-bb00deadbeef
+ size: 100M
+ offset: 1200M
+ - type: 00000000-0000-0000-0000-aa00deadbeef
+ size: 100M
+ offset: 1M
+`
+ vol := mustParseVolume(c, gadgetYaml, "first")
+ c.Assert(vol.Structure, HasLen, 4)
+
+ v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
+ c.Assert(err, IsNil)
+
+ c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
+ Volume: vol,
+ Size: 1300 * gadget.SizeMiB,
+ SectorSize: 512,
+ RootDir: p.dir,
+ LaidOutStructure: []gadget.LaidOutStructure{
+ {
+ VolumeStructure: &vol.Structure[3],
+ StartOffset: 1 * gadget.SizeMiB,
+ Index: 3,
+ },
+ {
+ VolumeStructure: &vol.Structure[1],
+ StartOffset: 200 * gadget.SizeMiB,
+ Index: 1,
+ },
+ {
+ VolumeStructure: &vol.Structure[0],
+ StartOffset: 800 * gadget.SizeMiB,
+ Index: 0,
+ },
+ {
+ VolumeStructure: &vol.Structure[2],
+ StartOffset: 1200 * gadget.SizeMiB,
+ Index: 2,
+ },
+ },
+ })
+}
+
+func (p *layoutTestSuite) TestLayoutVolumeMixedOrdering(c *C) {
+ gadgetYaml := `
+volumes:
+ first:
+ schema: gpt
+ bootloader: grub
+ structure:
+ - type: 00000000-0000-0000-0000-dd00deadbeef
+ size: 400M
+ offset: 800M
+ - type: 00000000-0000-0000-0000-cc00deadbeef
+ role: system-data
+ size: 500M
+ offset: 200M
+ - type: 00000000-0000-0000-0000-bb00deadbeef
+ size: 100M
+ - type: 00000000-0000-0000-0000-aa00deadbeef
+ size: 100M
+ offset: 1M
+`
+ vol := mustParseVolume(c, gadgetYaml, "first")
+ c.Assert(vol.Structure, HasLen, 4)
+
+ v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
+ c.Assert(err, IsNil)
+
+ c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
+ Volume: vol,
+ Size: 1200 * gadget.SizeMiB,
+ SectorSize: 512,
+ RootDir: p.dir,
+ LaidOutStructure: []gadget.LaidOutStructure{
+ {
+ VolumeStructure: &vol.Structure[3],
+ StartOffset: 1 * gadget.SizeMiB,
+ Index: 3,
+ },
+ {
+ VolumeStructure: &vol.Structure[1],
+ StartOffset: 200 * gadget.SizeMiB,
+ Index: 1,
+ },
+ {
+ VolumeStructure: &vol.Structure[2],
+ StartOffset: 700 * gadget.SizeMiB,
+ Index: 2,
+ },
+ {
+ VolumeStructure: &vol.Structure[0],
+ StartOffset: 800 * gadget.SizeMiB,
+ Index: 0,
+ },
+ },
+ })
+}
+
+func (p *layoutTestSuite) TestLayoutVolumeErrorsContentNoSuchFile(c *C) {
+ gadgetYaml := `
+volumes:
+ first:
+ schema: gpt
+ bootloader: grub
+ structure:
+ - type: 00000000-0000-0000-0000-dd00deadbeef
+ size: 400M
+ offset: 800M
+ content:
+ - image: foo.img
+`
+ vol := mustParseVolume(c, gadgetYaml, "first")
+ v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
+ c.Assert(v, IsNil)
+ c.Assert(err, ErrorMatches, `cannot lay out structure #0: content "foo.img":.*no such file or directory`)
+}
+
+func makeSizedFile(c *C, path string, size gadget.Size, content []byte) {
+ err := os.MkdirAll(filepath.Dir(path), 0755)
+ c.Assert(err, IsNil)
+
+ f, err := os.Create(path)
+ c.Assert(err, IsNil)
+ defer f.Close()
+ if size != 0 {
+ err = f.Truncate(int64(size))
+ c.Assert(err, IsNil)
+ }
+ if content != nil {
+ _, err := io.Copy(f, bytes.NewReader(content))
+ c.Assert(err, IsNil)
+ }
+}
+
+func (p *layoutTestSuite) TestLayoutVolumeErrorsContentTooLargeSingle(c *C) {
+ gadgetYaml := `
+volumes:
+ first:
+ schema: gpt
+ bootloader: grub
+ structure:
+ - type: 00000000-0000-0000-0000-dd00deadbeef
+ size: 1M
+ content:
+ - image: foo.img
+`
+ makeSizedFile(c, filepath.Join(p.dir, "foo.img"), gadget.SizeMiB+1, nil)
+
+ vol := mustParseVolume(c, gadgetYaml, "first")
+
+ v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
+ c.Assert(v, IsNil)
+ c.Assert(err, ErrorMatches, `cannot lay out structure #0: content "foo.img" does not fit in the structure`)
+}
+
+func (p *layoutTestSuite) TestLayoutVolumeErrorsContentTooLargeMany(c *C) {
+ gadgetYaml := `
+volumes:
+ first:
+ schema: gpt
+ bootloader: grub
+ structure:
+ - type: 00000000-0000-0000-0000-dd00deadbeef
+ size: 2M
+ content:
+ - image: foo.img
+ - image: bar.img
+`
+ makeSizedFile(c, filepath.Join(p.dir, "foo.img"), gadget.SizeMiB+1, nil)
+ makeSizedFile(c, filepath.Join(p.dir, "bar.img"), gadget.SizeMiB+1, nil)
+
+ vol := mustParseVolume(c, gadgetYaml, "first")
+
+ constraints := gadget.LayoutConstraints{
+ NonMBRStartOffset: 1 * gadget.SizeMiB,
+ SectorSize: 512,
+ }
+ v, err := gadget.LayoutVolume(p.dir, vol, constraints)
+ c.Assert(v, IsNil)
+ c.Assert(err, ErrorMatches, `cannot lay out structure #0: content "bar.img" does not fit in the structure`)
+}
+
+func (p *layoutTestSuite) TestLayoutVolumeErrorsContentTooLargeWithOffset(c *C) {
+ gadgetYaml := `
+volumes:
+ first:
+ schema: gpt
+ bootloader: grub
+ structure:
+ - type: 00000000-0000-0000-0000-dd00deadbeef
+ size: 1M
+ content:
+ - image: foo.img
+ # 512kB
+ offset: 524288
+`
+ makeSizedFile(c, filepath.Join(p.dir, "foo.img"), gadget.SizeMiB, nil)
+
+ vol := mustParseVolume(c, gadgetYaml, "first")
+
+ v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
+ c.Assert(v, IsNil)
+ c.Assert(err, ErrorMatches, `cannot lay out structure #0: content "foo.img" does not fit in the structure`)
+}
+
+func (p *layoutTestSuite) TestLayoutVolumeErrorsContentLargerThanDeclared(c *C) {
+ gadgetYaml := `
+volumes:
+ first:
+ schema: gpt
+ bootloader: grub
+ structure:
+ - type: 00000000-0000-0000-0000-dd00deadbeef
+ size: 2M
+ content:
+ - image: foo.img
+ size: 1M
+`
+ makeSizedFile(c, filepath.Join(p.dir, "foo.img"), gadget.SizeMiB+1, nil)
+
+ vol := mustParseVolume(c, gadgetYaml, "first")
+
+ v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
+ c.Assert(v, IsNil)
+ c.Assert(err, ErrorMatches, fmt.Sprintf(`cannot lay out structure #0: content "foo.img" size %v is larger than declared %v`, gadget.SizeMiB+1, gadget.SizeMiB))
+}
+
+func (p *layoutTestSuite) TestLayoutVolumeErrorsContentOverlap(c *C) {
+ gadgetYaml := `
+volumes:
+ first:
+ schema: gpt
+ bootloader: grub
+ structure:
+ - type: 00000000-0000-0000-0000-dd00deadbeef
+ size: 2M
+ content:
+ - image: foo.img
+ size: 1M
+ # 512kB
+ offset: 524288
+ - image: bar.img
+ size: 1M
+ offset: 0
+`
+ makeSizedFile(c, filepath.Join(p.dir, "foo.img"), gadget.SizeMiB, nil)
+ makeSizedFile(c, filepath.Join(p.dir, "bar.img"), gadget.SizeMiB, nil)
+
+ vol := mustParseVolume(c, gadgetYaml, "first")
+
+ v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
+ c.Assert(v, IsNil)
+ c.Assert(err, ErrorMatches, `cannot lay out structure #0: content "foo.img" overlaps with preceding image "bar.img"`)
+}
+
+func (p *layoutTestSuite) TestLayoutVolumeContentExplicitOrder(c *C) {
+ gadgetYaml := `
+volumes:
+ first:
+ schema: gpt
+ bootloader: grub
+ structure:
+ - type: 00000000-0000-0000-0000-0000deadbeef
+ size: 2M
+ content:
+ - image: foo.img
+ size: 1M
+ offset: 1M
+ - image: bar.img
+ size: 1M
+ offset: 0
+`
+ makeSizedFile(c, filepath.Join(p.dir, "foo.img"), gadget.SizeMiB, nil)
+ makeSizedFile(c, filepath.Join(p.dir, "bar.img"), gadget.SizeMiB, nil)
+
+ vol := mustParseVolume(c, gadgetYaml, "first")
+ c.Assert(vol.Structure, HasLen, 1)
+ c.Assert(vol.Structure[0].Content, HasLen, 2)
+
+ v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
+ c.Assert(err, IsNil)
+ c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
+ Volume: vol,
+ Size: 3 * gadget.SizeMiB,
+ SectorSize: 512,
+ RootDir: p.dir,
+ LaidOutStructure: []gadget.LaidOutStructure{
+ {
+ VolumeStructure: &vol.Structure[0],
+ StartOffset: 1 * gadget.SizeMiB,
+ LaidOutContent: []gadget.LaidOutContent{
+ {
+ VolumeContent: &vol.Structure[0].Content[1],
+ StartOffset: 1 * gadget.SizeMiB,
+ Size: gadget.SizeMiB,
+ Index: 1,
+ },
+ {
+ VolumeContent: &vol.Structure[0].Content[0],
+ StartOffset: 2 * gadget.SizeMiB,
+ Size: gadget.SizeMiB,
+ Index: 0,
+ },
+ },
+ },
+ },
+ })
+}
+
+func (p *layoutTestSuite) TestLayoutVolumeContentImplicitOrder(c *C) {
+ gadgetYaml := `
+volumes:
+ first:
+ schema: gpt
+ bootloader: grub
+ structure:
+ - type: 00000000-0000-0000-0000-0000deadbeef
+ size: 2M
+ content:
+ - image: foo.img
+ size: 1M
+ - image: bar.img
+ size: 1M
+`
+ makeSizedFile(c, filepath.Join(p.dir, "foo.img"), gadget.SizeMiB, nil)
+ makeSizedFile(c, filepath.Join(p.dir, "bar.img"), gadget.SizeMiB, nil)
+
+ vol := mustParseVolume(c, gadgetYaml, "first")
+ c.Assert(vol.Structure, HasLen, 1)
+ c.Assert(vol.Structure[0].Content, HasLen, 2)
+
+ v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
+ c.Assert(err, IsNil)
+ c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
+ Volume: vol,
+ Size: 3 * gadget.SizeMiB,
+ SectorSize: 512,
+ RootDir: p.dir,
+ LaidOutStructure: []gadget.LaidOutStructure{
+ {
+ VolumeStructure: &vol.Structure[0],
+ StartOffset: 1 * gadget.SizeMiB,
+ LaidOutContent: []gadget.LaidOutContent{
+ {
+ VolumeContent: &vol.Structure[0].Content[0],
+ StartOffset: 1 * gadget.SizeMiB,
+ Size: gadget.SizeMiB,
+ Index: 0,
+ },
+ {
+ VolumeContent: &vol.Structure[0].Content[1],
+ StartOffset: 2 * gadget.SizeMiB,
+ Size: gadget.SizeMiB,
+ Index: 1,
+ },
+ },
+ },
+ },
+ })
+}
+
+func (p *layoutTestSuite) TestLayoutVolumeContentImplicitSize(c *C) {
+ gadgetYaml := `
+volumes:
+ first:
+ schema: gpt
+ bootloader: grub
+ structure:
+ - type: 00000000-0000-0000-0000-0000deadbeef
+ size: 2M
+ content:
+ - image: foo.img
+`
+ size1_5MiB := gadget.SizeMiB + gadget.SizeMiB/2
+ makeSizedFile(c, filepath.Join(p.dir, "foo.img"), size1_5MiB, nil)
+
+ vol := mustParseVolume(c, gadgetYaml, "first")
+ c.Assert(vol.Structure, HasLen, 1)
+ c.Assert(vol.Structure[0].Content, HasLen, 1)
+
+ v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
+ c.Assert(err, IsNil)
+ c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
+ Volume: vol,
+ Size: 3 * gadget.SizeMiB,
+ SectorSize: 512,
+ RootDir: p.dir,
+ LaidOutStructure: []gadget.LaidOutStructure{
+ {
+ VolumeStructure: &vol.Structure[0],
+ StartOffset: 1 * gadget.SizeMiB,
+ LaidOutContent: []gadget.LaidOutContent{
+ {
+ VolumeContent: &vol.Structure[0].Content[0],
+ StartOffset: 1 * gadget.SizeMiB,
+ Size: size1_5MiB,
+ },
+ },
+ },
+ },
+ })
+}
+
+func (p *layoutTestSuite) TestLayoutVolumeContentNonBare(c *C) {
+ gadgetYaml := `
+volumes:
+ first:
+ schema: gpt
+ bootloader: grub
+ structure:
+ - type: 00000000-0000-0000-0000-0000deadbeef
+ filesystem: ext4
+ size: 2M
+ content:
+ - source: foo.txt
+ target: /boot
+`
+ makeSizedFile(c, filepath.Join(p.dir, "foo.txt"), 0, []byte("foobar\n"))
+
+ vol := mustParseVolume(c, gadgetYaml, "first")
+ c.Assert(vol.Structure, HasLen, 1)
+ c.Assert(vol.Structure[0].Content, HasLen, 1)
+
+ v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
+ c.Assert(err, IsNil)
+ c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
+ Volume: vol,
+ Size: 3 * gadget.SizeMiB,
+ SectorSize: 512,
+ RootDir: p.dir,
+ LaidOutStructure: []gadget.LaidOutStructure{
+ {
+ VolumeStructure: &vol.Structure[0],
+ StartOffset: 1 * gadget.SizeMiB,
+ },
+ },
+ })
+}
+
+func (p *layoutTestSuite) TestLayoutVolumeConstraintsChange(c *C) {
+ gadgetYaml := `
+volumes:
+ first:
+ schema: gpt
+ bootloader: grub
+ structure:
+ - role: mbr
+ type: bare
+ size: 446
+ offset: 0
+ - type: 00000000-0000-0000-0000-0000deadbeef
+ filesystem: ext4
+ size: 2M
+ content:
+ - source: foo.txt
+ target: /boot
+`
+ makeSizedFile(c, filepath.Join(p.dir, "foo.txt"), 0, []byte("foobar\n"))
+
+ vol := mustParseVolume(c, gadgetYaml, "first")
+ c.Assert(vol.Structure, HasLen, 2)
+ c.Assert(vol.Structure[1].Content, HasLen, 1)
+
+ v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
+ c.Assert(err, IsNil)
+ c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
+ Volume: vol,
+ Size: 3 * gadget.SizeMiB,
+ SectorSize: 512,
+ RootDir: p.dir,
+ LaidOutStructure: []gadget.LaidOutStructure{
+ {
+ VolumeStructure: &vol.Structure[0],
+ StartOffset: 0,
+ Index: 0,
+ },
+ {
+ VolumeStructure: &vol.Structure[1],
+ StartOffset: 1 * gadget.SizeMiB,
+ Index: 1,
+ },
+ },
+ })
+
+ // still valid
+ constraints := gadget.LayoutConstraints{
+ // 512kiB
+ NonMBRStartOffset: 512 * gadget.SizeKiB,
+ SectorSize: 512,
+ }
+ v, err = gadget.LayoutVolume(p.dir, vol, constraints)
+ c.Assert(err, IsNil)
+ c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
+ Volume: vol,
+ Size: 2*gadget.SizeMiB + 512*gadget.SizeKiB,
+ SectorSize: 512,
+ RootDir: p.dir,
+ LaidOutStructure: []gadget.LaidOutStructure{
+ {
+ VolumeStructure: &vol.Structure[0],
+ StartOffset: 0,
+ Index: 0,
+ },
+ {
+ VolumeStructure: &vol.Structure[1],
+ StartOffset: 512 * gadget.SizeKiB,
+ Index: 1,
+ },
+ },
+ })
+
+ // constraints would make a non MBR structure overlap with MBR, but
+ // structures start one after another unless offset is specified
+ // explicitly
+ constraintsBad := gadget.LayoutConstraints{
+ NonMBRStartOffset: 400,
+ SectorSize: 512,
+ }
+ v, err = gadget.LayoutVolume(p.dir, vol, constraintsBad)
+ c.Assert(err, IsNil)
+ c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
+ Volume: vol,
+ Size: 2*gadget.SizeMiB + 446,
+ SectorSize: 512,
+ RootDir: p.dir,
+ LaidOutStructure: []gadget.LaidOutStructure{
+ {
+ VolumeStructure: &vol.Structure[0],
+ Index: 0,
+ },
+ {
+ VolumeStructure: &vol.Structure[1],
+ StartOffset: 446,
+ Index: 1,
+ },
+ },
+ })
+
+ // sector size is properly recorded
+ constraintsSector := gadget.LayoutConstraints{
+ NonMBRStartOffset: 1 * gadget.SizeMiB,
+ SectorSize: 1024,
+ }
+ v, err = gadget.LayoutVolume(p.dir, vol, constraintsSector)
+ c.Assert(err, IsNil)
+ c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
+ Volume: vol,
+ Size: 3 * gadget.SizeMiB,
+ SectorSize: 1024,
+ RootDir: p.dir,
+ LaidOutStructure: []gadget.LaidOutStructure{
+ {
+ VolumeStructure: &vol.Structure[0],
+ Index: 0,
+ },
+ {
+ VolumeStructure: &vol.Structure[1],
+ StartOffset: 1 * gadget.SizeMiB,
+ Index: 1,
+ },
+ },
+ })
+}
+
+func (p *layoutTestSuite) TestLayoutVolumeConstraintsSectorSize(c *C) {
+ gadgetYaml := `
+volumes:
+ first:
+ schema: gpt
+ bootloader: grub
+ structure:
+ - role: mbr
+ type: bare
+ size: 446
+ offset: 0
+ - type: 00000000-0000-0000-0000-0000deadbeef
+ filesystem: ext4
+ size: 2M
+ content:
+ - source: foo.txt
+ target: /boot
+`
+ makeSizedFile(c, filepath.Join(p.dir, "foo.txt"), 0, []byte("foobar\n"))
+
+ vol := mustParseVolume(c, gadgetYaml, "first")
+
+ constraintsBadSectorSize := gadget.LayoutConstraints{
+ NonMBRStartOffset: 1 * gadget.SizeMiB,
+ SectorSize: 384,
+ }
+ _, err := gadget.LayoutVolume(p.dir, vol, constraintsBadSectorSize)
+ c.Assert(err, ErrorMatches, "cannot lay out volume, structure #1 size is not a multiple of sector size 384")
+}
+
+func (p *layoutTestSuite) TestLayoutVolumeConstraintsNeedsSectorSize(c *C) {
+ constraintsBadSectorSize := gadget.LayoutConstraints{
+ NonMBRStartOffset: 1 * gadget.SizeMiB,
+ // SectorSize left unspecified
+ }
+ _, err := gadget.LayoutVolume(p.dir, &gadget.Volume{}, constraintsBadSectorSize)
+ c.Assert(err, ErrorMatches, "cannot lay out volume, invalid constraints: sector size cannot be 0")
+}
+
+func (p *layoutTestSuite) TestLayoutVolumeMBRImplicitConstraints(c *C) {
+ gadgetYaml := `
+volumes:
+ first:
+ schema: gpt
+ bootloader: grub
+ structure:
+ - name: mbr
+ type: bare
+ role: mbr
+ size: 446
+ - name: other
+ type: 00000000-0000-0000-0000-0000deadbeef
+ size: 1M
+`
+ vol := mustParseVolume(c, gadgetYaml, "first")
+ c.Assert(vol.Structure, HasLen, 2)
+
+ v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
+ c.Assert(err, IsNil)
+ c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
+ Volume: vol,
+ Size: 2 * gadget.SizeMiB,
+ SectorSize: 512,
+ RootDir: p.dir,
+ LaidOutStructure: []gadget.LaidOutStructure{
+ {
+ // MBR
+ VolumeStructure: &vol.Structure[0],
+ StartOffset: 0,
+ Index: 0,
+ }, {
+ VolumeStructure: &vol.Structure[1],
+ StartOffset: 1 * gadget.SizeMiB,
+ Index: 1,
+ },
+ },
+ })
+}
+
+func (p *layoutTestSuite) TestLayoutVolumeOffsetWriteAll(c *C) {
+ var gadgetYaml = `
+volumes:
+ pc:
+ bootloader: grub
+ structure:
+ - name: mbr
+ type: mbr
+ size: 440
+ - name: foo
+ type: DA,21686148-6449-6E6F-744E-656564454649
+ size: 1M
+ offset: 1M
+ offset-write: mbr+92
+ content:
+ - image: foo.img
+ offset-write: bar+10
+ - name: bar
+ type: DA,21686148-6449-6E6F-744E-656564454649
+ size: 1M
+ offset-write: 600
+ content:
+ - image: bar.img
+ offset-write: 450
+`
+ makeSizedFile(c, filepath.Join(p.dir, "foo.img"), 200*gadget.SizeKiB, []byte(""))
+ makeSizedFile(c, filepath.Join(p.dir, "bar.img"), 150*gadget.SizeKiB, []byte(""))
+
+ vol := mustParseVolume(c, gadgetYaml, "pc")
+ c.Assert(vol.Structure, HasLen, 3)
+
+ v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
+ c.Assert(err, IsNil)
+ c.Assert(v, DeepEquals, &gadget.LaidOutVolume{
+ Volume: vol,
+ Size: 3 * gadget.SizeMiB,
+ SectorSize: 512,
+ RootDir: p.dir,
+ LaidOutStructure: []gadget.LaidOutStructure{
+ {
+ // mbr
+ VolumeStructure: &vol.Structure[0],
+ StartOffset: 0,
+ Index: 0,
+ }, {
+ // foo
+ VolumeStructure: &vol.Structure[1],
+ StartOffset: 1 * gadget.SizeMiB,
+ Index: 1,
+ // break for gofmt < 1.11
+ AbsoluteOffsetWrite: asSizePtr(92),
+ LaidOutContent: []gadget.LaidOutContent{
+ {
+ VolumeContent: &vol.Structure[1].Content[0],
+ Size: 200 * gadget.SizeKiB,
+ StartOffset: 1 * gadget.SizeMiB,
+ // offset-write: bar+10
+ AbsoluteOffsetWrite: asSizePtr(2*gadget.SizeMiB + 10),
+ },
+ },
+ }, {
+ // bar
+ VolumeStructure: &vol.Structure[2],
+ StartOffset: 2 * gadget.SizeMiB,
+ Index: 2,
+ // break for gofmt < 1.11
+ AbsoluteOffsetWrite: asSizePtr(600),
+ LaidOutContent: []gadget.LaidOutContent{
+ {
+ VolumeContent: &vol.Structure[2].Content[0],
+ Size: 150 * gadget.SizeKiB,
+ StartOffset: 2 * gadget.SizeMiB,
+ // offset-write: bar+10
+ AbsoluteOffsetWrite: asSizePtr(450),
+ },
+ },
+ },
+ },
+ })
+}
+
+func (p *layoutTestSuite) TestLayoutVolumeOffsetWriteBadRelativeTo(c *C) {
+ // define volumes explicitly as those would not pass validation
+ volBadStructure := gadget.Volume{
+ Structure: []gadget.VolumeStructure{
+ {
+ Name: "foo",
+ Type: "DA,21686148-6449-6E6F-744E-656564454649",
+ Size: 1 * gadget.SizeMiB,
+ OffsetWrite: &gadget.RelativeOffset{
+ RelativeTo: "bar",
+ Offset: 10,
+ },
+ },
+ },
+ }
+ volBadContent := gadget.Volume{
+ Structure: []gadget.VolumeStructure{
+ {
+ Name: "foo",
+ Type: "DA,21686148-6449-6E6F-744E-656564454649",
+ Size: 1 * gadget.SizeMiB,
+ Content: []gadget.VolumeContent{
+ {
+ Image: "foo.img",
+ OffsetWrite: &gadget.RelativeOffset{
+ RelativeTo: "bar",
+ Offset: 10,
+ },
+ },
+ },
+ },
+ },
+ }
+
+ makeSizedFile(c, filepath.Join(p.dir, "foo.img"), 200*gadget.SizeKiB, []byte(""))
+
+ v, err := gadget.LayoutVolume(p.dir, &volBadStructure, defaultConstraints)
+ c.Check(v, IsNil)
+ c.Check(err, ErrorMatches, `cannot resolve offset-write of structure #0 \("foo"\): refers to an unknown structure "bar"`)
+
+ v, err = gadget.LayoutVolume(p.dir, &volBadContent, defaultConstraints)
+ c.Check(v, IsNil)
+ c.Check(err, ErrorMatches, `cannot resolve offset-write of structure #0 \("foo"\) content "foo.img": refers to an unknown structure "bar"`)
+}
+
+func (p *layoutTestSuite) TestLayoutVolumeOffsetWriteEnlargesVolume(c *C) {
+ var gadgetYamlStructure = `
+volumes:
+ pc:
+ bootloader: grub
+ structure:
+ - name: mbr
+ type: mbr
+ size: 440
+ - name: foo
+ type: DA,21686148-6449-6E6F-744E-656564454649
+ size: 1M
+ offset: 1M
+ # 1GB
+ offset-write: mbr+1073741824
+
+`
+ vol := mustParseVolume(c, gadgetYamlStructure, "pc")
+
+ v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
+ c.Assert(err, IsNil)
+ // offset-write is at 1GB
+ c.Check(v.Size, Equals, 1*gadget.SizeGiB+gadget.SizeLBA48Pointer)
+
+ var gadgetYamlContent = `
+volumes:
+ pc:
+ bootloader: grub
+ structure:
+ - name: mbr
+ type: mbr
+ size: 440
+ - name: foo
+ type: DA,21686148-6449-6E6F-744E-656564454649
+ size: 1M
+ offset: 1M
+ content:
+ - image: foo.img
+ # 2GB
+ offset-write: mbr+2147483648
+ - image: bar.img
+ # 1GB
+ offset-write: mbr+1073741824
+ - image: baz.img
+ # 3GB
+ offset-write: mbr+3221225472
+
+`
+ makeSizedFile(c, filepath.Join(p.dir, "foo.img"), 200*gadget.SizeKiB, []byte(""))
+ makeSizedFile(c, filepath.Join(p.dir, "bar.img"), 150*gadget.SizeKiB, []byte(""))
+ makeSizedFile(c, filepath.Join(p.dir, "baz.img"), 100*gadget.SizeKiB, []byte(""))
+
+ vol = mustParseVolume(c, gadgetYamlContent, "pc")
+
+ v, err = gadget.LayoutVolume(p.dir, vol, defaultConstraints)
+ c.Assert(err, IsNil)
+ // foo.img offset-write is at 3GB
+ c.Check(v.Size, Equals, 3*gadget.SizeGiB+gadget.SizeLBA48Pointer)
+}
+
+func (p *layoutTestSuite) TestLayoutVolumePartialNoSuchFile(c *C) {
+ gadgetYaml := `
+volumes:
+ first:
+ schema: gpt
+ bootloader: grub
+ structure:
+ - type: 00000000-0000-0000-0000-dd00deadbeef
+ size: 400M
+ offset: 800M
+ content:
+ - image: foo.img
+`
+ vol := mustParseVolume(c, gadgetYaml, "first")
+ c.Assert(vol.Structure, HasLen, 1)
+
+ v, err := gadget.LayoutVolumePartially(vol, defaultConstraints)
+ c.Assert(v, DeepEquals, &gadget.PartiallyLaidOutVolume{
+ Volume: vol,
+ SectorSize: 512,
+ LaidOutStructure: []gadget.LaidOutStructure{
+ {
+ VolumeStructure: &vol.Structure[0],
+ StartOffset: 800 * gadget.SizeMiB,
+ Index: 0,
+ },
+ },
+ })
+ c.Assert(err, IsNil)
+}
+
+func (p *layoutTestSuite) TestLaidOutStructureShift(c *C) {
+ var gadgetYamlContent = `
+volumes:
+ pc:
+ bootloader: grub
+ structure:
+ - name: foo
+ type: DA,21686148-6449-6E6F-744E-656564454649
+ size: 1M
+ offset: 1M
+ content:
+ - image: foo.img
+ - image: bar.img
+ # 300KB
+ offset: 307200
+
+`
+ makeSizedFile(c, filepath.Join(p.dir, "foo.img"), 200*gadget.SizeKiB, []byte(""))
+ makeSizedFile(c, filepath.Join(p.dir, "bar.img"), 150*gadget.SizeKiB, []byte(""))
+
+ vol := mustParseVolume(c, gadgetYamlContent, "pc")
+
+ v, err := gadget.LayoutVolume(p.dir, vol, defaultConstraints)
+ c.Assert(err, IsNil)
+ c.Assert(v.LaidOutStructure, HasLen, 1)
+ c.Assert(v.LaidOutStructure[0].LaidOutContent, HasLen, 2)
+
+ ps := v.LaidOutStructure[0]
+
+ c.Assert(ps, DeepEquals, gadget.LaidOutStructure{
+ // foo
+ VolumeStructure: &vol.Structure[0],
+ StartOffset: 1 * gadget.SizeMiB,
+ Index: 0,
+ LaidOutContent: []gadget.LaidOutContent{
+ {
+ VolumeContent: &vol.Structure[0].Content[0],
+ Size: 200 * gadget.SizeKiB,
+ StartOffset: 1 * gadget.SizeMiB,
+ Index: 0,
+ }, {
+ VolumeContent: &vol.Structure[0].Content[1],
+ Size: 150 * gadget.SizeKiB,
+ StartOffset: 1*gadget.SizeMiB + 300*gadget.SizeKiB,
+ Index: 1,
+ },
+ },
+ })
+
+ shiftedTo0 := gadget.ShiftStructureTo(ps, 0)
+ c.Assert(shiftedTo0, DeepEquals, gadget.LaidOutStructure{
+ // foo
+ VolumeStructure: &vol.Structure[0],
+ StartOffset: 0,
+ Index: 0,
+ LaidOutContent: []gadget.LaidOutContent{
+ {
+ VolumeContent: &vol.Structure[0].Content[0],
+ Size: 200 * gadget.SizeKiB,
+ StartOffset: 0,
+ Index: 0,
+ }, {
+ VolumeContent: &vol.Structure[0].Content[1],
+ Size: 150 * gadget.SizeKiB,
+ StartOffset: 300 * gadget.SizeKiB,
+ Index: 1,
+ },
+ },
+ })
+
+ shiftedTo2M := gadget.ShiftStructureTo(ps, 2*gadget.SizeMiB)
+ c.Assert(shiftedTo2M, DeepEquals, gadget.LaidOutStructure{
+ // foo
+ VolumeStructure: &vol.Structure[0],
+ StartOffset: 2 * gadget.SizeMiB,
+ Index: 0,
+ LaidOutContent: []gadget.LaidOutContent{
+ {
+ VolumeContent: &vol.Structure[0].Content[0],
+ Size: 200 * gadget.SizeKiB,
+ StartOffset: 2 * gadget.SizeMiB,
+ Index: 0,
+ }, {
+ VolumeContent: &vol.Structure[0].Content[1],
+ Size: 150 * gadget.SizeKiB,
+ StartOffset: 2*gadget.SizeMiB + 300*gadget.SizeKiB,
+ Index: 1,
+ },
+ },
+ })
+}
diff -Nru snapd-2.41+19.10.1/gadget/mountedfilesystem.go snapd-2.42.1+19.10/gadget/mountedfilesystem.go
--- snapd-2.41+19.10.1/gadget/mountedfilesystem.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/gadget/mountedfilesystem.go 2019-10-30 12:17:43.000000000 +0000
@@ -59,14 +59,14 @@
// mounted filesystem.
type MountedFilesystemWriter struct {
contentDir string
- ps *PositionedStructure
+ ps *LaidOutStructure
}
// NewMountedFilesystemWriter returns a writer capable of writing provided
// structure, with content of the structure stored in the given root directory.
-func NewMountedFilesystemWriter(contentDir string, ps *PositionedStructure) (*MountedFilesystemWriter, error) {
+func NewMountedFilesystemWriter(contentDir string, ps *LaidOutStructure) (*MountedFilesystemWriter, error) {
if ps == nil {
- return nil, fmt.Errorf("internal error: *PositionedStructure is nil")
+ return nil, fmt.Errorf("internal error: *LaidOutStructure is nil")
}
if ps.IsBare() {
return nil, fmt.Errorf("structure %v has no filesystem", ps)
@@ -242,7 +242,7 @@
return f.Commit()
}
-type mountLookupFunc func(ps *PositionedStructure) (string, error)
+type mountLookupFunc func(ps *LaidOutStructure) (string, error)
// MountedFilesystemUpdater assits in applying updates to a mounted filesystem.
//
@@ -268,7 +268,7 @@
// structure, with structure content coming from provided root directory. The
// mount is located by calling a mount lookup helper. The backup directory
// contains backup state information for use during rollback.
-func NewMountedFilesystemUpdater(rootDir string, ps *PositionedStructure, backupDir string, mountLookup mountLookupFunc) (*MountedFilesystemUpdater, error) {
+func NewMountedFilesystemUpdater(rootDir string, ps *LaidOutStructure, backupDir string, mountLookup mountLookupFunc) (*MountedFilesystemUpdater, error) {
fw, err := NewMountedFilesystemWriter(rootDir, ps)
if err != nil {
return nil, err
@@ -287,7 +287,7 @@
return fu, nil
}
-func fsStructBackupPath(backupDir string, ps *PositionedStructure) string {
+func fsStructBackupPath(backupDir string, ps *LaidOutStructure) string {
return filepath.Join(backupDir, fmt.Sprintf("struct-%v", ps.Index))
}
diff -Nru snapd-2.41+19.10.1/gadget/mountedfilesystem_test.go snapd-2.42.1+19.10/gadget/mountedfilesystem_test.go
--- snapd-2.41+19.10.1/gadget/mountedfilesystem_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/gadget/mountedfilesystem_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -234,7 +234,7 @@
err := os.MkdirAll(filepath.Join(s.dir, "boot-assets/empty-dir"), 0755)
c.Assert(err, IsNil)
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -283,7 +283,7 @@
}
makeGadgetData(c, s.dir, gd)
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -308,7 +308,7 @@
}
func (s *mountedfilesystemTestSuite) TestMountedWriterErrorMissingSource(c *C) {
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -335,7 +335,7 @@
func (s *mountedfilesystemTestSuite) TestMountedWriterErrorBadDestination(c *C) {
makeSizedFile(c, filepath.Join(s.dir, "foo"), 0, []byte("foo foo foo"))
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "vfat",
@@ -368,7 +368,7 @@
{name: "foo-dir", content: "bar bar bar"},
})
- psOverwritesDirectoryWithFile := &gadget.PositionedStructure{
+ psOverwritesDirectoryWithFile := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -403,7 +403,7 @@
{name: "foo", content: "foo foo foo"},
{name: "bar", content: "bar bar bar"},
})
- psOverwritesFile := &gadget.PositionedStructure{
+ psOverwritesFile := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -440,7 +440,7 @@
{name: "foo/bar/baz", content: "data"},
})
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -504,7 +504,7 @@
makeSizedFile(c, p, 0, []byte("can't touch this"))
}
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -571,7 +571,7 @@
err := os.MkdirAll(filepath.Join(outDir, "foo"), 0755)
c.Assert(err, IsNil)
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -599,7 +599,7 @@
}
makeGadgetData(c, s.dir, gd)
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -626,7 +626,7 @@
}
func (s *mountedfilesystemTestSuite) TestMountedWriterNoFs(c *C) {
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
// no filesystem
@@ -647,10 +647,10 @@
func (s *mountedfilesystemTestSuite) TestMountedWriterTrivialValidation(c *C) {
rw, err := gadget.NewMountedFilesystemWriter(s.dir, nil)
- c.Assert(err, ErrorMatches, `internal error: \*PositionedStructure.*`)
+ c.Assert(err, ErrorMatches, `internal error: \*LaidOutStructure.*`)
c.Assert(rw, IsNil)
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -695,7 +695,7 @@
outDir := filepath.Join(c.MkDir(), "out-dir")
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -748,7 +748,7 @@
}
makeExistingData(c, outDir, backedUp)
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -777,7 +777,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return outDir, nil
})
@@ -830,7 +830,7 @@
}
makeExistingData(c, outDir, backedUp)
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -856,7 +856,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return outDir, nil
})
@@ -890,7 +890,7 @@
outDir := filepath.Join(c.MkDir(), "out-dir")
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -910,7 +910,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return outDir, nil
})
@@ -928,7 +928,7 @@
func (s *mountedfilesystemTestSuite) TestMountedUpdaterBackupFailsOnBackupDirErrors(c *C) {
outDir := filepath.Join(c.MkDir(), "out-dir")
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -944,7 +944,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return outDir, nil
})
@@ -974,7 +974,7 @@
err := os.Chmod(filepath.Join(outDir, "foo"), 0000)
c.Assert(err, IsNil)
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -990,7 +990,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return outDir, nil
})
@@ -1015,7 +1015,7 @@
{target: "foo", content: "same"},
})
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -1031,7 +1031,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return outDir, nil
})
@@ -1070,7 +1070,7 @@
outDirConflictsFoo := filepath.Join(c.MkDir(), "out-dir-foo")
makeExistingData(c, outDirConflictsFoo, existingUpFoo)
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -1097,7 +1097,7 @@
} {
err := os.MkdirAll(tc.backupDir, 0755)
c.Assert(err, IsNil)
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, tc.backupDir, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, tc.backupDir, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return tc.outDir, nil
})
@@ -1129,7 +1129,7 @@
}
makeExistingData(c, outDir, backedUp)
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -1145,7 +1145,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return outDir, nil
})
@@ -1181,7 +1181,7 @@
}
makeExistingData(c, outDir, existing)
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -1191,7 +1191,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return outDir, nil
})
@@ -1218,7 +1218,7 @@
// bar/nested-target -> nested
os.Symlink("nested-target", filepath.Join(outDir, "bar/nested"))
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -1228,7 +1228,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return outDir, nil
})
@@ -1277,7 +1277,7 @@
makeSizedFile(c, p, 0, []byte("can't touch this"))
}
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -1319,7 +1319,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return outDir, nil
})
@@ -1346,7 +1346,7 @@
{name: "canary", target: "canary", content: "data"},
})
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -1362,7 +1362,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return "", errors.New("failed")
})
@@ -1385,7 +1385,7 @@
outDir := filepath.Join(c.MkDir(), "out-dir")
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -1410,7 +1410,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return outDir, nil
})
@@ -1441,7 +1441,7 @@
{target: "/preserved", content: "preserve"},
})
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -1465,7 +1465,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return outDir, nil
})
@@ -1501,7 +1501,7 @@
outDir := filepath.Join(c.MkDir(), "out-dir")
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -1523,7 +1523,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return outDir, nil
})
@@ -1561,7 +1561,7 @@
{target: "some-dir/foo", content: "same"},
})
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -1580,7 +1580,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return outDir, nil
})
@@ -1611,7 +1611,7 @@
outDir := filepath.Join(c.MkDir(), "out-dir")
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -1633,7 +1633,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return outDir, nil
})
@@ -1659,7 +1659,7 @@
}
makeExistingData(c, outDir, existing)
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -1669,7 +1669,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return outDir, nil
})
@@ -1697,7 +1697,7 @@
}
makeExistingData(c, outDir, existing)
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -1707,7 +1707,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return outDir, nil
})
@@ -1736,7 +1736,7 @@
{target: "some-dir/foo", content: "written"},
})
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -1755,7 +1755,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return outDir, nil
})
@@ -1787,7 +1787,7 @@
{target: "foo", content: "same"},
})
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -1803,7 +1803,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return outDir, nil
})
@@ -1833,7 +1833,7 @@
{target: "foo", content: "preserved"},
})
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -1850,7 +1850,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return outDir, nil
})
@@ -1880,7 +1880,7 @@
{target: "this/is/some/deep/nesting/bar", content: "written"},
})
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -1902,7 +1902,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return outDir, nil
})
@@ -1930,7 +1930,7 @@
err := os.Chmod(filepath.Join(outDir, "foo"), 0000)
c.Assert(err, IsNil)
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -1949,7 +1949,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return outDir, nil
})
@@ -1982,7 +1982,7 @@
outDir := filepath.Join(c.MkDir(), "out-dir")
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -2001,7 +2001,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return outDir, nil
})
@@ -2038,7 +2038,7 @@
{target: "lone-dir/"},
})
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -2066,7 +2066,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return outDir, nil
})
@@ -2140,7 +2140,7 @@
"some-dir/data",
}
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -2188,7 +2188,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return outDir, nil
})
@@ -2281,7 +2281,7 @@
}
func (s *mountedfilesystemTestSuite) TestMountedUpdaterTrivialValidation(c *C) {
- psNoFs := &gadget.PositionedStructure{
+ psNoFs := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
// no filesystem
@@ -2289,7 +2289,7 @@
},
}
- lookupFail := func(to *gadget.PositionedStructure) (string, error) {
+ lookupFail := func(to *gadget.LaidOutStructure) (string, error) {
c.Fatalf("unexpected call")
return "", nil
}
@@ -2298,7 +2298,7 @@
c.Assert(err, ErrorMatches, "structure #0 has no filesystem")
c.Assert(rw, IsNil)
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -2319,10 +2319,10 @@
c.Assert(rw, IsNil)
rw, err = gadget.NewMountedFilesystemUpdater(s.dir, nil, s.backup, lookupFail)
- c.Assert(err, ErrorMatches, `internal error: \*PositionedStructure.*`)
+ c.Assert(err, ErrorMatches, `internal error: \*LaidOutStructure.*`)
c.Assert(rw, IsNil)
- lookupOk := func(to *gadget.PositionedStructure) (string, error) {
+ lookupOk := func(to *gadget.LaidOutStructure) (string, error) {
return filepath.Join(s.dir, "foobar"), nil
}
@@ -2333,7 +2333,7 @@
{content: gadget.VolumeContent{Source: "", Target: "/"}, match: "internal error: source cannot be unset"},
{content: gadget.VolumeContent{Source: "/", Target: ""}, match: "internal error: target cannot be unset"},
} {
- testPs := &gadget.PositionedStructure{
+ testPs := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -2357,7 +2357,7 @@
}
func (s *mountedfilesystemTestSuite) TestMountedUpdaterMountLookupFail(c *C) {
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -2367,7 +2367,7 @@
},
}
- lookupFail := func(to *gadget.PositionedStructure) (string, error) {
+ lookupFail := func(to *gadget.LaidOutStructure) (string, error) {
return "", errors.New("fail fail fail")
}
@@ -2397,7 +2397,7 @@
err := os.MkdirAll(filepath.Join(outDir, "foo"), 0755)
c.Assert(err, IsNil)
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
Filesystem: "ext4",
@@ -2411,7 +2411,7 @@
},
}
- rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.PositionedStructure) (string, error) {
+ rw, err := gadget.NewMountedFilesystemUpdater(s.dir, ps, s.backup, func(to *gadget.LaidOutStructure) (string, error) {
c.Check(to, DeepEquals, ps)
return outDir, nil
})
diff -Nru snapd-2.41+19.10.1/gadget/offset.go snapd-2.42.1+19.10/gadget/offset.go
--- snapd-2.41+19.10.1/gadget/offset.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/gadget/offset.go 2019-10-30 12:17:43.000000000 +0000
@@ -28,7 +28,7 @@
// and its content at locations defined by offset-write property. structures and
// their content.
type OffsetWriter struct {
- ps *PositionedStructure
+ ps *LaidOutStructure
sectorSize Size
}
@@ -47,9 +47,9 @@
}
// NewOffsetWriter returns a writer for given structure.
-func NewOffsetWriter(ps *PositionedStructure, sectorSize Size) (*OffsetWriter, error) {
+func NewOffsetWriter(ps *LaidOutStructure, sectorSize Size) (*OffsetWriter, error) {
if ps == nil {
- return nil, fmt.Errorf("internal error: *PositionedStructure is nil")
+ return nil, fmt.Errorf("internal error: *LaidOutStructure is nil")
}
if sectorSize == 0 {
return nil, fmt.Errorf("internal error: sector size cannot be 0")
@@ -65,10 +65,10 @@
// structure, at the locations defined by offset-writer property of respective
// element, in the format of LBA pointer.
func (w *OffsetWriter) Write(out io.WriteSeeker) error {
- // positioning guarantees that start offset is aligned to sector size
+ // layout step guarantees that start offset is aligned to sector size
- if w.ps.PositionedOffsetWrite != nil {
- if err := offsetWrite(out, *w.ps.PositionedOffsetWrite, asLBA(w.ps.StartOffset, w.sectorSize)); err != nil {
+ if w.ps.AbsoluteOffsetWrite != nil {
+ if err := offsetWrite(out, *w.ps.AbsoluteOffsetWrite, asLBA(w.ps.StartOffset, w.sectorSize)); err != nil {
return err
}
}
@@ -78,11 +78,11 @@
return nil
}
- for _, pc := range w.ps.PositionedContent {
- if pc.PositionedOffsetWrite == nil {
+ for _, pc := range w.ps.LaidOutContent {
+ if pc.AbsoluteOffsetWrite == nil {
continue
}
- if err := offsetWrite(out, *pc.PositionedOffsetWrite, asLBA(pc.StartOffset, w.sectorSize)); err != nil {
+ if err := offsetWrite(out, *pc.AbsoluteOffsetWrite, asLBA(pc.StartOffset, w.sectorSize)); err != nil {
return err
}
}
diff -Nru snapd-2.41+19.10.1/gadget/offset_test.go snapd-2.42.1+19.10/gadget/offset_test.go
--- snapd-2.41+19.10.1/gadget/offset_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/gadget/offset_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -33,16 +33,16 @@
var _ = Suite(&offsetSuite{})
func (m *offsetSuite) TestOffsetWriterOnlyStructure(c *C) {
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 1 * gadget.SizeMiB,
OffsetWrite: &gadget.RelativeOffset{Offset: 512},
},
StartOffset: 1024,
// start offset written at this location
- PositionedOffsetWrite: asSizePtr(512),
+ AbsoluteOffsetWrite: asSizePtr(512),
- PositionedContent: []gadget.PositionedContent{
+ LaidOutContent: []gadget.LaidOutContent{
{
VolumeContent: &gadget.VolumeContent{
Image: "foo.img",
@@ -75,13 +75,13 @@
}
func (m *offsetSuite) TestOffsetWriterOnlyRawContent(c *C) {
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 1 * gadget.SizeMiB,
},
StartOffset: gadget.Size(1024),
- PositionedContent: []gadget.PositionedContent{
+ LaidOutContent: []gadget.LaidOutContent{
{
VolumeContent: &gadget.VolumeContent{
Image: "foo.img",
@@ -91,7 +91,7 @@
Size: 128,
StartOffset: 2048,
// start offset written here
- PositionedOffsetWrite: asSizePtr(4096),
+ AbsoluteOffsetWrite: asSizePtr(4096),
},
},
}
@@ -118,15 +118,15 @@
}
func (m *offsetSuite) TestOffsetWriterOnlyFsStructure(c *C) {
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 1 * gadget.SizeMiB,
Filesystem: "ext4",
// same as in pc gadget
OffsetWrite: &gadget.RelativeOffset{Offset: 92},
},
- StartOffset: gadget.Size(348 * gadget.SizeKiB),
- PositionedOffsetWrite: asSizePtr(92),
+ StartOffset: gadget.Size(348 * gadget.SizeKiB),
+ AbsoluteOffsetWrite: asSizePtr(92),
}
const sectorSize = 512
@@ -151,15 +151,15 @@
}
func (m *offsetSuite) TestOffsetWriterErrors(c *C) {
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 1 * gadget.SizeMiB,
Filesystem: "ext4",
// same as in pc gadget
OffsetWrite: &gadget.RelativeOffset{Offset: 92},
},
- StartOffset: gadget.Size(348 * gadget.SizeKiB),
- PositionedOffsetWrite: asSizePtr(92),
+ StartOffset: gadget.Size(348 * gadget.SizeKiB),
+ AbsoluteOffsetWrite: asSizePtr(92),
}
const sectorSize = 512
@@ -188,12 +188,12 @@
err = ow.Write(mwBadWriter)
c.Assert(err, ErrorMatches, "cannot write LBA value 0x2b8 at offset 92: bad writer")
- psOnlyContent := &gadget.PositionedStructure{
+ psOnlyContent := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 1 * gadget.SizeMiB,
},
StartOffset: gadget.Size(348 * gadget.SizeKiB),
- PositionedContent: []gadget.PositionedContent{
+ LaidOutContent: []gadget.LaidOutContent{
{
VolumeContent: &gadget.VolumeContent{
Image: "foo.img",
@@ -203,7 +203,7 @@
Size: 128,
StartOffset: 2048,
// start offset written here
- PositionedOffsetWrite: asSizePtr(4096),
+ AbsoluteOffsetWrite: asSizePtr(4096),
},
},
}
@@ -217,18 +217,18 @@
func (m *offsetSuite) TestOffsetWriterErrorSimpleValidation(c *C) {
ow, err := gadget.NewOffsetWriter(nil, 512)
- c.Assert(err, ErrorMatches, `internal error: \*PositionedStructure is nil`)
+ c.Assert(err, ErrorMatches, `internal error: \*LaidOutStructure is nil`)
c.Assert(ow, IsNil)
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 1 * gadget.SizeMiB,
Filesystem: "ext4",
// same as in pc gadget
OffsetWrite: &gadget.RelativeOffset{Offset: 92},
},
- StartOffset: gadget.Size(348 * gadget.SizeKiB),
- PositionedOffsetWrite: asSizePtr(92),
+ StartOffset: gadget.Size(348 * gadget.SizeKiB),
+ AbsoluteOffsetWrite: asSizePtr(92),
}
ow, err = gadget.NewOffsetWriter(ps, 0)
diff -Nru snapd-2.41+19.10.1/gadget/partition.go snapd-2.42.1+19.10/gadget/partition.go
--- snapd-2.41+19.10.1/gadget/partition.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/gadget/partition.go 2019-10-30 12:17:43.000000000 +0000
@@ -45,7 +45,7 @@
return maybeHybridType[:idx], maybeHybridType[idx+1:]
}
-func Partition(image string, pv *PositionedVolume) error {
+func Partition(image string, pv *LaidOutVolume) error {
if image == "" {
return fmt.Errorf("internal error: image path is unset")
}
@@ -73,7 +73,7 @@
}
fmt.Fprintf(script, "\n")
- for _, ps := range pv.PositionedStructure {
+ for _, ps := range pv.LaidOutStructure {
if ps.Type == "bare" || ps.Type == "mbr" {
continue
}
diff -Nru snapd-2.41+19.10.1/gadget/partition_test.go snapd-2.42.1+19.10/gadget/partition_test.go
--- snapd-2.41+19.10.1/gadget/partition_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/gadget/partition_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -56,14 +56,14 @@
}
func (s *partitionSuite) TestGPTHappy(c *C) {
- pv := &gadget.PositionedVolume{
+ pv := &gadget.LaidOutVolume{
Volume: &gadget.Volume{
Schema: "gpt",
ID: "123-123",
},
Size: 3 * gadget.SizeMiB,
SectorSize: 512,
- PositionedStructure: []gadget.PositionedStructure{
+ LaidOutStructure: []gadget.LaidOutStructure{
{
// does not appear as partition
VolumeStructure: &gadget.VolumeStructure{
@@ -110,14 +110,14 @@
}
func (s *partitionSuite) TestMBRHappy(c *C) {
- pv := &gadget.PositionedVolume{
+ pv := &gadget.LaidOutVolume{
Volume: &gadget.Volume{
Schema: "mbr",
ID: "0x123",
},
Size: 3 * gadget.SizeMiB,
SectorSize: 512,
- PositionedStructure: []gadget.PositionedStructure{
+ LaidOutStructure: []gadget.LaidOutStructure{
{
// does not appear as partition
VolumeStructure: &gadget.VolumeStructure{
@@ -174,20 +174,20 @@
}
func (s *partitionSuite) TestHybridType(c *C) {
- ps := gadget.PositionedStructure{
+ ps := gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2 * gadget.SizeMiB,
Type: "0C,21686148-6449-6E6F-744E-656564454649",
},
StartOffset: 1 * gadget.SizeMiB,
}
- pvGPT := &gadget.PositionedVolume{
+ pvGPT := &gadget.LaidOutVolume{
Volume: &gadget.Volume{
Schema: "gpt",
},
- Size: 3 * gadget.SizeMiB,
- SectorSize: 512,
- PositionedStructure: []gadget.PositionedStructure{ps},
+ Size: 3 * gadget.SizeMiB,
+ SectorSize: 512,
+ LaidOutStructure: []gadget.LaidOutStructure{ps},
}
err := gadget.Partition("foo", pvGPT)
@@ -199,13 +199,13 @@
start=2048, size=4096, type=21686148-6449-6E6F-744E-656564454649
`)
- pvMBR := &gadget.PositionedVolume{
+ pvMBR := &gadget.LaidOutVolume{
Volume: &gadget.Volume{
Schema: "mbr",
},
- Size: 3 * gadget.SizeMiB,
- SectorSize: 512,
- PositionedStructure: []gadget.PositionedStructure{ps},
+ Size: 3 * gadget.SizeMiB,
+ SectorSize: 512,
+ LaidOutStructure: []gadget.LaidOutStructure{ps},
}
err = gadget.Partition("foo", pvMBR)
c.Assert(err, IsNil)
@@ -217,13 +217,13 @@
}
func (s *partitionSuite) TestInputErrors(c *C) {
- pv := &gadget.PositionedVolume{
+ pv := &gadget.LaidOutVolume{
Volume: &gadget.Volume{
Schema: "gpt",
},
Size: 3 * gadget.SizeMiB,
SectorSize: 512,
- PositionedStructure: []gadget.PositionedStructure{
+ LaidOutStructure: []gadget.LaidOutStructure{
{
VolumeStructure: &gadget.VolumeStructure{
Size: 2 * gadget.SizeMiB,
@@ -247,13 +247,13 @@
}
func (s *partitionSuite) TestCommandError(c *C) {
- pv := &gadget.PositionedVolume{
+ pv := &gadget.LaidOutVolume{
Volume: &gadget.Volume{
Schema: "gpt",
},
Size: 3 * gadget.SizeMiB,
SectorSize: 512,
- PositionedStructure: []gadget.PositionedStructure{
+ LaidOutStructure: []gadget.LaidOutStructure{
{
VolumeStructure: &gadget.VolumeStructure{
Size: 2 * gadget.SizeMiB,
diff -Nru snapd-2.41+19.10.1/gadget/position.go snapd-2.42.1+19.10/gadget/position.go
--- snapd-2.41+19.10.1/gadget/position.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/gadget/position.go 1970-01-01 00:00:00.000000000 +0000
@@ -1,317 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2019 Canonical Ltd
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-package gadget
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "sort"
-)
-
-// PositioningConstraints defines the constraints for positioning structures
-// within a volume
-type PositioningConstraints struct {
- // NonMBRStartOffset is the default start offset of non-MBR structure in
- // the volume.
- NonMBRStartOffset Size
- // SectorSize is the size of the sector to be used for calculations
- SectorSize Size
-}
-
-// PositionedVolume defines the size of a volume and positions of all the
-// structures within it
-type PositionedVolume struct {
- *Volume
- // Size is the total size of the volume
- Size Size
- // SectorSize sector size of the volume
- SectorSize Size
- // PositionedStructure are sorted in order of 'appearance' in the volume
- PositionedStructure []PositionedStructure
- // RootDir is the root directory for volume data
- RootDir string
-}
-
-// PositionedStructure describes a VolumeStructure that has been positioned
-// within the volume
-type PositionedStructure struct {
- *VolumeStructure
- // StartOffset defines the start offset of the structure within the
- // enclosing volume
- StartOffset Size
- // PositionedOffsetWrite is the resolved position of offset-write for
- // this structure element within the enclosing volume
- PositionedOffsetWrite *Size
- // Index of the structure definition in gadget YAML
- Index int
-
- // PositionedContent is a list of raw content included in this structure
- PositionedContent []PositionedContent
-}
-
-func (p PositionedStructure) String() string {
- return fmtIndexAndName(p.Index, p.Name)
-}
-
-type byStartOffset []PositionedStructure
-
-func (b byStartOffset) Len() int { return len(b) }
-func (b byStartOffset) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
-func (b byStartOffset) Less(i, j int) bool { return b[i].StartOffset < b[j].StartOffset }
-
-// PositionedContent describes raw content that has been positioned within the
-// encompassing structure
-type PositionedContent struct {
- *VolumeContent
-
- // StartOffset defines the start offset of this content image
- StartOffset Size
- // PositionedOffsetWrite is the resolved position of offset-write for
- // this content element within the enclosing volume
- PositionedOffsetWrite *Size
- // Size is the maximum size occupied by this image
- Size Size
- // Index of the content in structure declaration inside gadget YAML
- Index int
-}
-
-func (p PositionedContent) String() string {
- if p.Image != "" {
- return fmt.Sprintf("#%v (%q@%#x{%v})", p.Index, p.Image, p.StartOffset, p.Size)
- }
- return fmt.Sprintf("#%v (source:%q)", p.Index, p.Source)
-}
-
-// PositionVolume attempts to lay out the volume using constraints and returns a
-// fully positioned description of the resulting volume
-func PositionVolume(gadgetRootDir string, volume *Volume, constraints PositioningConstraints) (*PositionedVolume, error) {
- previousEnd := Size(0)
- farthestEnd := Size(0)
- fartherstOffsetWrite := Size(0)
- structures := make([]PositionedStructure, len(volume.Structure))
- structuresByName := make(map[string]*PositionedStructure, len(volume.Structure))
-
- if constraints.SectorSize == 0 {
- return nil, fmt.Errorf("cannot position volume, invalid constraints: sector size cannot be 0")
- }
-
- for idx, s := range volume.Structure {
- var start Size
- if s.Offset == nil {
- if s.EffectiveRole() != MBR && previousEnd < constraints.NonMBRStartOffset {
- start = constraints.NonMBRStartOffset
- } else {
- start = previousEnd
- }
- } else {
- start = *s.Offset
- }
-
- end := start + s.Size
- ps := PositionedStructure{
- VolumeStructure: &volume.Structure[idx],
- StartOffset: start,
- Index: idx,
- }
-
- if ps.EffectiveRole() != MBR {
- if s.Size%constraints.SectorSize != 0 {
- return nil, fmt.Errorf("cannot position volume, structure %v size is not a multiple of sector size %v",
- ps, constraints.SectorSize)
- }
- }
-
- if ps.Name != "" {
- structuresByName[ps.Name] = &ps
- }
-
- structures[idx] = ps
-
- if end > farthestEnd {
- farthestEnd = end
- }
- previousEnd = end
- }
-
- // sort by starting offset
- sort.Sort(byStartOffset(structures))
-
- previousEnd = Size(0)
- for idx, ps := range structures {
- if ps.StartOffset < previousEnd {
- return nil, fmt.Errorf("cannot position volume, structure %v overlaps with preceding structure %v", ps, structures[idx-1])
- }
- previousEnd = ps.StartOffset + ps.Size
-
- offsetWrite, err := resolveOffsetWrite(ps.OffsetWrite, structuresByName)
- if err != nil {
- return nil, fmt.Errorf("cannot resolve offset-write of structure %v: %v", ps, err)
- }
- structures[idx].PositionedOffsetWrite = offsetWrite
-
- if offsetWrite != nil && *offsetWrite > fartherstOffsetWrite {
- fartherstOffsetWrite = *offsetWrite
- }
-
- content, err := positionStructureContent(gadgetRootDir, &structures[idx], structuresByName)
- if err != nil {
- return nil, err
- }
-
- for _, c := range content {
- if c.PositionedOffsetWrite != nil && *c.PositionedOffsetWrite > fartherstOffsetWrite {
- fartherstOffsetWrite = *c.PositionedOffsetWrite
- }
- }
-
- structures[idx].PositionedContent = content
- }
-
- volumeSize := farthestEnd
- if fartherstOffsetWrite+SizeLBA48Pointer > farthestEnd {
- volumeSize = fartherstOffsetWrite + SizeLBA48Pointer
- }
-
- vol := &PositionedVolume{
- Volume: volume,
- Size: volumeSize,
- SectorSize: constraints.SectorSize,
- PositionedStructure: structures,
- RootDir: gadgetRootDir,
- }
- return vol, nil
-}
-
-type byContentStartOffset []PositionedContent
-
-func (b byContentStartOffset) Len() int { return len(b) }
-func (b byContentStartOffset) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
-func (b byContentStartOffset) Less(i, j int) bool { return b[i].StartOffset < b[j].StartOffset }
-
-func getImageSize(path string) (Size, error) {
- stat, err := os.Stat(path)
- if err != nil {
- return 0, err
- }
- return Size(stat.Size()), nil
-}
-
-func positionStructureContent(gadgetRootDir string, ps *PositionedStructure, known map[string]*PositionedStructure) ([]PositionedContent, error) {
- if !ps.IsBare() {
- // structures with a filesystem do not need any extra
- // positioning
- return nil, nil
- }
- if len(ps.Content) == 0 {
- return nil, nil
- }
-
- content := make([]PositionedContent, len(ps.Content))
- previousEnd := Size(0)
-
- for idx, c := range ps.Content {
- imageSize, err := getImageSize(filepath.Join(gadgetRootDir, c.Image))
- if err != nil {
- return nil, fmt.Errorf("cannot position structure %v: content %q: %v", ps, c.Image, err)
- }
-
- var start Size
- if c.Offset != nil {
- start = *c.Offset
- } else {
- start = previousEnd
- }
-
- actualSize := imageSize
-
- if c.Size != 0 {
- if c.Size < imageSize {
- return nil, fmt.Errorf("cannot position structure %v: content %q size %v is larger than declared %v", ps, c.Image, actualSize, c.Size)
- }
- actualSize = c.Size
- }
-
- offsetWrite, err := resolveOffsetWrite(c.OffsetWrite, known)
- if err != nil {
- return nil, fmt.Errorf("cannot resolve offset-write of structure %v content %q: %v", ps, c.Image, err)
- }
-
- content[idx] = PositionedContent{
- VolumeContent: &ps.Content[idx],
- Size: actualSize,
- StartOffset: ps.StartOffset + start,
- Index: idx,
- // break for gofmt < 1.11
- PositionedOffsetWrite: offsetWrite,
- }
- previousEnd = start + actualSize
- if previousEnd > ps.Size {
- return nil, fmt.Errorf("cannot position structure %v: content %q does not fit in the structure", ps, c.Image)
- }
- }
-
- sort.Sort(byContentStartOffset(content))
-
- previousEnd = ps.StartOffset
- for idx, pc := range content {
- if pc.StartOffset < previousEnd {
- return nil, fmt.Errorf("cannot position structure %v: content %q overlaps with preceding image %q", ps, pc.Image, content[idx-1].Image)
- }
- previousEnd = pc.StartOffset + pc.Size
- }
-
- return content, nil
-}
-
-func resolveOffsetWrite(offsetWrite *RelativeOffset, knownStructs map[string]*PositionedStructure) (*Size, error) {
- if offsetWrite == nil {
- return nil, nil
- }
-
- var relativeToOffset Size
- if offsetWrite.RelativeTo != "" {
- otherStruct, ok := knownStructs[offsetWrite.RelativeTo]
- if !ok {
- return nil, fmt.Errorf("refers to an unknown structure %q", offsetWrite.RelativeTo)
- }
- relativeToOffset = otherStruct.StartOffset
- }
-
- resolvedOffsetWrite := relativeToOffset + offsetWrite.Offset
- return &resolvedOffsetWrite, nil
-}
-
-// ShiftStructureTo creates a new positioned structure, shifted to start at a
-// given offset. The start offsets of positioned content within the structure is
-// updated.
-func ShiftStructureTo(ps PositionedStructure, offset Size) PositionedStructure {
- change := int64(offset - ps.StartOffset)
-
- newPs := ps
- newPs.StartOffset = Size(int64(ps.StartOffset) + change)
-
- newPs.PositionedContent = make([]PositionedContent, len(ps.PositionedContent))
- for idx, pc := range ps.PositionedContent {
- newPc := pc
- newPc.StartOffset = Size(int64(pc.StartOffset) + change)
- newPs.PositionedContent[idx] = newPc
- }
- return newPs
-}
diff -Nru snapd-2.41+19.10.1/gadget/position_test.go snapd-2.42.1+19.10/gadget/position_test.go
--- snapd-2.41+19.10.1/gadget/position_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/gadget/position_test.go 1970-01-01 00:00:00.000000000 +0000
@@ -1,1110 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2019 Canonical Ltd
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-
-package gadget_test
-
-import (
- "bytes"
- "fmt"
- "io"
- "os"
- "path/filepath"
-
- . "gopkg.in/check.v1"
- "gopkg.in/yaml.v2"
-
- "github.com/snapcore/snapd/gadget"
-)
-
-type positioningTestSuite struct {
- dir string
-}
-
-var _ = Suite(&positioningTestSuite{})
-
-func (p *positioningTestSuite) SetUpTest(c *C) {
- p.dir = c.MkDir()
-}
-
-var defaultConstraints = gadget.PositioningConstraints{
- NonMBRStartOffset: 1 * gadget.SizeMiB,
- SectorSize: 512,
-}
-
-func (p *positioningTestSuite) TestVolumeSize(c *C) {
- vol := gadget.Volume{
- Structure: []gadget.VolumeStructure{
- {Size: 2 * gadget.SizeMiB},
- },
- }
- v, err := gadget.PositionVolume(p.dir, &vol, defaultConstraints)
- c.Assert(err, IsNil)
-
- c.Assert(v, DeepEquals, &gadget.PositionedVolume{
- Volume: &gadget.Volume{
- Structure: []gadget.VolumeStructure{
- {Size: 2 * gadget.SizeMiB},
- },
- },
- Size: 3 * gadget.SizeMiB,
- SectorSize: 512,
- RootDir: p.dir,
- PositionedStructure: []gadget.PositionedStructure{
- {VolumeStructure: &gadget.VolumeStructure{Size: 2 * gadget.SizeMiB}, StartOffset: 1 * gadget.SizeMiB},
- },
- })
-}
-
-func mustParseVolume(c *C, gadgetYaml, volume string) *gadget.Volume {
- var gi gadget.Info
- err := yaml.Unmarshal([]byte(gadgetYaml), &gi)
- c.Assert(err, IsNil)
- v, ok := gi.Volumes[volume]
- c.Assert(ok, Equals, true, Commentf("volume %q not found in gadget", volume))
- err = gadget.ValidateVolume("foo", &v)
- c.Assert(err, IsNil)
- return &v
-}
-
-func (p *positioningTestSuite) TestVolumePositionMinimal(c *C) {
- gadgetYaml := `
-volumes:
- first-image:
- bootloader: u-boot
- structure:
- - type: 00000000-0000-0000-0000-0000deadbeef
- size: 400M
- - type: 83,00000000-0000-0000-0000-0000feedface
- role: system-data
- size: 100M
-`
- vol := mustParseVolume(c, gadgetYaml, "first-image")
- c.Assert(vol.Structure, HasLen, 2)
-
- v, err := gadget.PositionVolume(p.dir, vol, defaultConstraints)
- c.Assert(err, IsNil)
-
- c.Assert(v, DeepEquals, &gadget.PositionedVolume{
- Volume: vol,
- Size: 501 * gadget.SizeMiB,
- SectorSize: 512,
- RootDir: p.dir,
- PositionedStructure: []gadget.PositionedStructure{
- {
- VolumeStructure: &vol.Structure[0],
- StartOffset: 1 * gadget.SizeMiB,
- Index: 0,
- },
- {
- VolumeStructure: &vol.Structure[1],
- StartOffset: 401 * gadget.SizeMiB,
- Index: 1,
- },
- },
- })
-}
-
-func (p *positioningTestSuite) TestVolumePositionImplicitOrdering(c *C) {
- gadgetYaml := `
-volumes:
- first:
- schema: gpt
- bootloader: grub
- structure:
- - type: 00000000-0000-0000-0000-dd00deadbeef
- size: 400M
- - type: 00000000-0000-0000-0000-cc00deadbeef
- role: system-data
- size: 500M
- - type: 00000000-0000-0000-0000-bb00deadbeef
- size: 100M
- - type: 00000000-0000-0000-0000-aa00deadbeef
- size: 100M
-`
- vol := mustParseVolume(c, gadgetYaml, "first")
- c.Assert(vol.Structure, HasLen, 4)
-
- v, err := gadget.PositionVolume(p.dir, vol, defaultConstraints)
- c.Assert(err, IsNil)
-
- c.Assert(v, DeepEquals, &gadget.PositionedVolume{
- Volume: vol,
- Size: 1101 * gadget.SizeMiB,
- SectorSize: 512,
- RootDir: p.dir,
- PositionedStructure: []gadget.PositionedStructure{
- {
- VolumeStructure: &vol.Structure[0],
- StartOffset: 1 * gadget.SizeMiB,
- Index: 0,
- },
- {
- VolumeStructure: &vol.Structure[1],
- StartOffset: 401 * gadget.SizeMiB,
- Index: 1,
- },
- {
- VolumeStructure: &vol.Structure[2],
- StartOffset: 901 * gadget.SizeMiB,
- Index: 2,
- },
- {
- VolumeStructure: &vol.Structure[3],
- StartOffset: 1001 * gadget.SizeMiB,
- Index: 3,
- },
- },
- })
-}
-
-func (p *positioningTestSuite) TestVolumePositionExplicitOrdering(c *C) {
- gadgetYaml := `
-volumes:
- first:
- schema: gpt
- bootloader: grub
- structure:
- - type: 00000000-0000-0000-0000-dd00deadbeef
- size: 400M
- offset: 800M
- - type: 00000000-0000-0000-0000-cc00deadbeef
- role: system-data
- size: 500M
- offset: 200M
- - type: 00000000-0000-0000-0000-bb00deadbeef
- size: 100M
- offset: 1200M
- - type: 00000000-0000-0000-0000-aa00deadbeef
- size: 100M
- offset: 1M
-`
- vol := mustParseVolume(c, gadgetYaml, "first")
- c.Assert(vol.Structure, HasLen, 4)
-
- v, err := gadget.PositionVolume(p.dir, vol, defaultConstraints)
- c.Assert(err, IsNil)
-
- c.Assert(v, DeepEquals, &gadget.PositionedVolume{
- Volume: vol,
- Size: 1300 * gadget.SizeMiB,
- SectorSize: 512,
- RootDir: p.dir,
- PositionedStructure: []gadget.PositionedStructure{
- {
- VolumeStructure: &vol.Structure[3],
- StartOffset: 1 * gadget.SizeMiB,
- Index: 3,
- },
- {
- VolumeStructure: &vol.Structure[1],
- StartOffset: 200 * gadget.SizeMiB,
- Index: 1,
- },
- {
- VolumeStructure: &vol.Structure[0],
- StartOffset: 800 * gadget.SizeMiB,
- Index: 0,
- },
- {
- VolumeStructure: &vol.Structure[2],
- StartOffset: 1200 * gadget.SizeMiB,
- Index: 2,
- },
- },
- })
-}
-
-func (p *positioningTestSuite) TestVolumePositionMixedOrdering(c *C) {
- gadgetYaml := `
-volumes:
- first:
- schema: gpt
- bootloader: grub
- structure:
- - type: 00000000-0000-0000-0000-dd00deadbeef
- size: 400M
- offset: 800M
- - type: 00000000-0000-0000-0000-cc00deadbeef
- role: system-data
- size: 500M
- offset: 200M
- - type: 00000000-0000-0000-0000-bb00deadbeef
- size: 100M
- - type: 00000000-0000-0000-0000-aa00deadbeef
- size: 100M
- offset: 1M
-`
- vol := mustParseVolume(c, gadgetYaml, "first")
- c.Assert(vol.Structure, HasLen, 4)
-
- v, err := gadget.PositionVolume(p.dir, vol, defaultConstraints)
- c.Assert(err, IsNil)
-
- c.Assert(v, DeepEquals, &gadget.PositionedVolume{
- Volume: vol,
- Size: 1200 * gadget.SizeMiB,
- SectorSize: 512,
- RootDir: p.dir,
- PositionedStructure: []gadget.PositionedStructure{
- {
- VolumeStructure: &vol.Structure[3],
- StartOffset: 1 * gadget.SizeMiB,
- Index: 3,
- },
- {
- VolumeStructure: &vol.Structure[1],
- StartOffset: 200 * gadget.SizeMiB,
- Index: 1,
- },
- {
- VolumeStructure: &vol.Structure[2],
- StartOffset: 700 * gadget.SizeMiB,
- Index: 2,
- },
- {
- VolumeStructure: &vol.Structure[0],
- StartOffset: 800 * gadget.SizeMiB,
- Index: 0,
- },
- },
- })
-}
-
-func (p *positioningTestSuite) TestVolumePositionErrorsContentNoSuchFile(c *C) {
- gadgetYaml := `
-volumes:
- first:
- schema: gpt
- bootloader: grub
- structure:
- - type: 00000000-0000-0000-0000-dd00deadbeef
- size: 400M
- offset: 800M
- content:
- - image: foo.img
-`
- vol := mustParseVolume(c, gadgetYaml, "first")
- v, err := gadget.PositionVolume(p.dir, vol, defaultConstraints)
- c.Assert(v, IsNil)
- c.Assert(err, ErrorMatches, `cannot position structure #0: content "foo.img":.*no such file or directory`)
-}
-
-func makeSizedFile(c *C, path string, size gadget.Size, content []byte) {
- err := os.MkdirAll(filepath.Dir(path), 0755)
- c.Assert(err, IsNil)
-
- f, err := os.Create(path)
- c.Assert(err, IsNil)
- defer f.Close()
- if size != 0 {
- err = f.Truncate(int64(size))
- c.Assert(err, IsNil)
- }
- if content != nil {
- _, err := io.Copy(f, bytes.NewReader(content))
- c.Assert(err, IsNil)
- }
-}
-
-func (p *positioningTestSuite) TestVolumePositionErrorsContentTooLargeSingle(c *C) {
- gadgetYaml := `
-volumes:
- first:
- schema: gpt
- bootloader: grub
- structure:
- - type: 00000000-0000-0000-0000-dd00deadbeef
- size: 1M
- content:
- - image: foo.img
-`
- makeSizedFile(c, filepath.Join(p.dir, "foo.img"), gadget.SizeMiB+1, nil)
-
- vol := mustParseVolume(c, gadgetYaml, "first")
-
- v, err := gadget.PositionVolume(p.dir, vol, defaultConstraints)
- c.Assert(v, IsNil)
- c.Assert(err, ErrorMatches, `cannot position structure #0: content "foo.img" does not fit in the structure`)
-}
-
-func (p *positioningTestSuite) TestVolumePositionErrorsContentTooLargeMany(c *C) {
- gadgetYaml := `
-volumes:
- first:
- schema: gpt
- bootloader: grub
- structure:
- - type: 00000000-0000-0000-0000-dd00deadbeef
- size: 2M
- content:
- - image: foo.img
- - image: bar.img
-`
- makeSizedFile(c, filepath.Join(p.dir, "foo.img"), gadget.SizeMiB+1, nil)
- makeSizedFile(c, filepath.Join(p.dir, "bar.img"), gadget.SizeMiB+1, nil)
-
- vol := mustParseVolume(c, gadgetYaml, "first")
-
- constraints := gadget.PositioningConstraints{
- NonMBRStartOffset: 1 * gadget.SizeMiB,
- SectorSize: 512,
- }
- v, err := gadget.PositionVolume(p.dir, vol, constraints)
- c.Assert(v, IsNil)
- c.Assert(err, ErrorMatches, `cannot position structure #0: content "bar.img" does not fit in the structure`)
-}
-
-func (p *positioningTestSuite) TestVolumePositionErrorsContentTooLargeWithOffset(c *C) {
- gadgetYaml := `
-volumes:
- first:
- schema: gpt
- bootloader: grub
- structure:
- - type: 00000000-0000-0000-0000-dd00deadbeef
- size: 1M
- content:
- - image: foo.img
- # 512kB
- offset: 524288
-`
- makeSizedFile(c, filepath.Join(p.dir, "foo.img"), gadget.SizeMiB, nil)
-
- vol := mustParseVolume(c, gadgetYaml, "first")
-
- v, err := gadget.PositionVolume(p.dir, vol, defaultConstraints)
- c.Assert(v, IsNil)
- c.Assert(err, ErrorMatches, `cannot position structure #0: content "foo.img" does not fit in the structure`)
-}
-
-func (p *positioningTestSuite) TestVolumePositionErrorsContentLargerThanDeclared(c *C) {
- gadgetYaml := `
-volumes:
- first:
- schema: gpt
- bootloader: grub
- structure:
- - type: 00000000-0000-0000-0000-dd00deadbeef
- size: 2M
- content:
- - image: foo.img
- size: 1M
-`
- makeSizedFile(c, filepath.Join(p.dir, "foo.img"), gadget.SizeMiB+1, nil)
-
- vol := mustParseVolume(c, gadgetYaml, "first")
-
- v, err := gadget.PositionVolume(p.dir, vol, defaultConstraints)
- c.Assert(v, IsNil)
- c.Assert(err, ErrorMatches, fmt.Sprintf(`cannot position structure #0: content "foo.img" size %v is larger than declared %v`, gadget.SizeMiB+1, gadget.SizeMiB))
-}
-
-func (p *positioningTestSuite) TestVolumePositionErrorsContentOverlap(c *C) {
- gadgetYaml := `
-volumes:
- first:
- schema: gpt
- bootloader: grub
- structure:
- - type: 00000000-0000-0000-0000-dd00deadbeef
- size: 2M
- content:
- - image: foo.img
- size: 1M
- # 512kB
- offset: 524288
- - image: bar.img
- size: 1M
- offset: 0
-`
- makeSizedFile(c, filepath.Join(p.dir, "foo.img"), gadget.SizeMiB, nil)
- makeSizedFile(c, filepath.Join(p.dir, "bar.img"), gadget.SizeMiB, nil)
-
- vol := mustParseVolume(c, gadgetYaml, "first")
-
- v, err := gadget.PositionVolume(p.dir, vol, defaultConstraints)
- c.Assert(v, IsNil)
- c.Assert(err, ErrorMatches, `cannot position structure #0: content "foo.img" overlaps with preceding image "bar.img"`)
-}
-
-func (p *positioningTestSuite) TestVolumePositionContentExplicitOrder(c *C) {
- gadgetYaml := `
-volumes:
- first:
- schema: gpt
- bootloader: grub
- structure:
- - type: 00000000-0000-0000-0000-0000deadbeef
- size: 2M
- content:
- - image: foo.img
- size: 1M
- offset: 1M
- - image: bar.img
- size: 1M
- offset: 0
-`
- makeSizedFile(c, filepath.Join(p.dir, "foo.img"), gadget.SizeMiB, nil)
- makeSizedFile(c, filepath.Join(p.dir, "bar.img"), gadget.SizeMiB, nil)
-
- vol := mustParseVolume(c, gadgetYaml, "first")
- c.Assert(vol.Structure, HasLen, 1)
- c.Assert(vol.Structure[0].Content, HasLen, 2)
-
- v, err := gadget.PositionVolume(p.dir, vol, defaultConstraints)
- c.Assert(err, IsNil)
- c.Assert(v, DeepEquals, &gadget.PositionedVolume{
- Volume: vol,
- Size: 3 * gadget.SizeMiB,
- SectorSize: 512,
- RootDir: p.dir,
- PositionedStructure: []gadget.PositionedStructure{
- {
- VolumeStructure: &vol.Structure[0],
- StartOffset: 1 * gadget.SizeMiB,
- PositionedContent: []gadget.PositionedContent{
- {
- VolumeContent: &vol.Structure[0].Content[1],
- StartOffset: 1 * gadget.SizeMiB,
- Size: gadget.SizeMiB,
- Index: 1,
- },
- {
- VolumeContent: &vol.Structure[0].Content[0],
- StartOffset: 2 * gadget.SizeMiB,
- Size: gadget.SizeMiB,
- Index: 0,
- },
- },
- },
- },
- })
-}
-
-func (p *positioningTestSuite) TestVolumePositionContentImplicitOrder(c *C) {
- gadgetYaml := `
-volumes:
- first:
- schema: gpt
- bootloader: grub
- structure:
- - type: 00000000-0000-0000-0000-0000deadbeef
- size: 2M
- content:
- - image: foo.img
- size: 1M
- - image: bar.img
- size: 1M
-`
- makeSizedFile(c, filepath.Join(p.dir, "foo.img"), gadget.SizeMiB, nil)
- makeSizedFile(c, filepath.Join(p.dir, "bar.img"), gadget.SizeMiB, nil)
-
- vol := mustParseVolume(c, gadgetYaml, "first")
- c.Assert(vol.Structure, HasLen, 1)
- c.Assert(vol.Structure[0].Content, HasLen, 2)
-
- v, err := gadget.PositionVolume(p.dir, vol, defaultConstraints)
- c.Assert(err, IsNil)
- c.Assert(v, DeepEquals, &gadget.PositionedVolume{
- Volume: vol,
- Size: 3 * gadget.SizeMiB,
- SectorSize: 512,
- RootDir: p.dir,
- PositionedStructure: []gadget.PositionedStructure{
- {
- VolumeStructure: &vol.Structure[0],
- StartOffset: 1 * gadget.SizeMiB,
- PositionedContent: []gadget.PositionedContent{
- {
- VolumeContent: &vol.Structure[0].Content[0],
- StartOffset: 1 * gadget.SizeMiB,
- Size: gadget.SizeMiB,
- Index: 0,
- },
- {
- VolumeContent: &vol.Structure[0].Content[1],
- StartOffset: 2 * gadget.SizeMiB,
- Size: gadget.SizeMiB,
- Index: 1,
- },
- },
- },
- },
- })
-}
-
-func (p *positioningTestSuite) TestVolumePositionContentImplicitSize(c *C) {
- gadgetYaml := `
-volumes:
- first:
- schema: gpt
- bootloader: grub
- structure:
- - type: 00000000-0000-0000-0000-0000deadbeef
- size: 2M
- content:
- - image: foo.img
-`
- size1_5MiB := gadget.SizeMiB + gadget.SizeMiB/2
- makeSizedFile(c, filepath.Join(p.dir, "foo.img"), size1_5MiB, nil)
-
- vol := mustParseVolume(c, gadgetYaml, "first")
- c.Assert(vol.Structure, HasLen, 1)
- c.Assert(vol.Structure[0].Content, HasLen, 1)
-
- v, err := gadget.PositionVolume(p.dir, vol, defaultConstraints)
- c.Assert(err, IsNil)
- c.Assert(v, DeepEquals, &gadget.PositionedVolume{
- Volume: vol,
- Size: 3 * gadget.SizeMiB,
- SectorSize: 512,
- RootDir: p.dir,
- PositionedStructure: []gadget.PositionedStructure{
- {
- VolumeStructure: &vol.Structure[0],
- StartOffset: 1 * gadget.SizeMiB,
- PositionedContent: []gadget.PositionedContent{
- {
- VolumeContent: &vol.Structure[0].Content[0],
- StartOffset: 1 * gadget.SizeMiB,
- Size: size1_5MiB,
- },
- },
- },
- },
- })
-}
-
-func (p *positioningTestSuite) TestVolumePositionContentNonBare(c *C) {
- gadgetYaml := `
-volumes:
- first:
- schema: gpt
- bootloader: grub
- structure:
- - type: 00000000-0000-0000-0000-0000deadbeef
- filesystem: ext4
- size: 2M
- content:
- - source: foo.txt
- target: /boot
-`
- makeSizedFile(c, filepath.Join(p.dir, "foo.txt"), 0, []byte("foobar\n"))
-
- vol := mustParseVolume(c, gadgetYaml, "first")
- c.Assert(vol.Structure, HasLen, 1)
- c.Assert(vol.Structure[0].Content, HasLen, 1)
-
- v, err := gadget.PositionVolume(p.dir, vol, defaultConstraints)
- c.Assert(err, IsNil)
- c.Assert(v, DeepEquals, &gadget.PositionedVolume{
- Volume: vol,
- Size: 3 * gadget.SizeMiB,
- SectorSize: 512,
- RootDir: p.dir,
- PositionedStructure: []gadget.PositionedStructure{
- {
- VolumeStructure: &vol.Structure[0],
- StartOffset: 1 * gadget.SizeMiB,
- },
- },
- })
-}
-
-func (p *positioningTestSuite) TestVolumePositionConstraintsChange(c *C) {
- gadgetYaml := `
-volumes:
- first:
- schema: gpt
- bootloader: grub
- structure:
- - role: mbr
- type: bare
- size: 446
- offset: 0
- - type: 00000000-0000-0000-0000-0000deadbeef
- filesystem: ext4
- size: 2M
- content:
- - source: foo.txt
- target: /boot
-`
- makeSizedFile(c, filepath.Join(p.dir, "foo.txt"), 0, []byte("foobar\n"))
-
- vol := mustParseVolume(c, gadgetYaml, "first")
- c.Assert(vol.Structure, HasLen, 2)
- c.Assert(vol.Structure[1].Content, HasLen, 1)
-
- v, err := gadget.PositionVolume(p.dir, vol, defaultConstraints)
- c.Assert(err, IsNil)
- c.Assert(v, DeepEquals, &gadget.PositionedVolume{
- Volume: vol,
- Size: 3 * gadget.SizeMiB,
- SectorSize: 512,
- RootDir: p.dir,
- PositionedStructure: []gadget.PositionedStructure{
- {
- VolumeStructure: &vol.Structure[0],
- StartOffset: 0,
- Index: 0,
- },
- {
- VolumeStructure: &vol.Structure[1],
- StartOffset: 1 * gadget.SizeMiB,
- Index: 1,
- },
- },
- })
-
- // still valid
- constraints := gadget.PositioningConstraints{
- // 512kiB
- NonMBRStartOffset: 512 * gadget.SizeKiB,
- SectorSize: 512,
- }
- v, err = gadget.PositionVolume(p.dir, vol, constraints)
- c.Assert(err, IsNil)
- c.Assert(v, DeepEquals, &gadget.PositionedVolume{
- Volume: vol,
- Size: 2*gadget.SizeMiB + 512*gadget.SizeKiB,
- SectorSize: 512,
- RootDir: p.dir,
- PositionedStructure: []gadget.PositionedStructure{
- {
- VolumeStructure: &vol.Structure[0],
- StartOffset: 0,
- Index: 0,
- },
- {
- VolumeStructure: &vol.Structure[1],
- StartOffset: 512 * gadget.SizeKiB,
- Index: 1,
- },
- },
- })
-
- // constraints would make a non MBR structure overlap with MBR, but
- // structures start one after another unless offset is specified
- // explicitly
- constraintsBad := gadget.PositioningConstraints{
- NonMBRStartOffset: 400,
- SectorSize: 512,
- }
- v, err = gadget.PositionVolume(p.dir, vol, constraintsBad)
- c.Assert(err, IsNil)
- c.Assert(v, DeepEquals, &gadget.PositionedVolume{
- Volume: vol,
- Size: 2*gadget.SizeMiB + 446,
- SectorSize: 512,
- RootDir: p.dir,
- PositionedStructure: []gadget.PositionedStructure{
- {
- VolumeStructure: &vol.Structure[0],
- Index: 0,
- },
- {
- VolumeStructure: &vol.Structure[1],
- StartOffset: 446,
- Index: 1,
- },
- },
- })
-
- // sector size is properly recorded
- constraintsSector := gadget.PositioningConstraints{
- NonMBRStartOffset: 1 * gadget.SizeMiB,
- SectorSize: 1024,
- }
- v, err = gadget.PositionVolume(p.dir, vol, constraintsSector)
- c.Assert(err, IsNil)
- c.Assert(v, DeepEquals, &gadget.PositionedVolume{
- Volume: vol,
- Size: 3 * gadget.SizeMiB,
- SectorSize: 1024,
- RootDir: p.dir,
- PositionedStructure: []gadget.PositionedStructure{
- {
- VolumeStructure: &vol.Structure[0],
- Index: 0,
- },
- {
- VolumeStructure: &vol.Structure[1],
- StartOffset: 1 * gadget.SizeMiB,
- Index: 1,
- },
- },
- })
-}
-
-func (p *positioningTestSuite) TestVolumePositionConstraintsSectorSize(c *C) {
- gadgetYaml := `
-volumes:
- first:
- schema: gpt
- bootloader: grub
- structure:
- - role: mbr
- type: bare
- size: 446
- offset: 0
- - type: 00000000-0000-0000-0000-0000deadbeef
- filesystem: ext4
- size: 2M
- content:
- - source: foo.txt
- target: /boot
-`
- makeSizedFile(c, filepath.Join(p.dir, "foo.txt"), 0, []byte("foobar\n"))
-
- vol := mustParseVolume(c, gadgetYaml, "first")
-
- constraintsBadSectorSize := gadget.PositioningConstraints{
- NonMBRStartOffset: 1 * gadget.SizeMiB,
- SectorSize: 384,
- }
- _, err := gadget.PositionVolume(p.dir, vol, constraintsBadSectorSize)
- c.Assert(err, ErrorMatches, "cannot position volume, structure #1 size is not a multiple of sector size 384")
-}
-
-func (p *positioningTestSuite) TestVolumePositionConstraintsNeedsSectorSize(c *C) {
- constraintsBadSectorSize := gadget.PositioningConstraints{
- NonMBRStartOffset: 1 * gadget.SizeMiB,
- // SectorSize left unspecified
- }
- _, err := gadget.PositionVolume(p.dir, &gadget.Volume{}, constraintsBadSectorSize)
- c.Assert(err, ErrorMatches, "cannot position volume, invalid constraints: sector size cannot be 0")
-}
-
-func (p *positioningTestSuite) TestVolumePositionMBRImplicitConstraints(c *C) {
- gadgetYaml := `
-volumes:
- first:
- schema: gpt
- bootloader: grub
- structure:
- - name: mbr
- type: bare
- role: mbr
- size: 446
- - name: other
- type: 00000000-0000-0000-0000-0000deadbeef
- size: 1M
-`
- vol := mustParseVolume(c, gadgetYaml, "first")
- c.Assert(vol.Structure, HasLen, 2)
-
- v, err := gadget.PositionVolume(p.dir, vol, defaultConstraints)
- c.Assert(err, IsNil)
- c.Assert(v, DeepEquals, &gadget.PositionedVolume{
- Volume: vol,
- Size: 2 * gadget.SizeMiB,
- SectorSize: 512,
- RootDir: p.dir,
- PositionedStructure: []gadget.PositionedStructure{
- {
- // MBR
- VolumeStructure: &vol.Structure[0],
- StartOffset: 0,
- Index: 0,
- }, {
- VolumeStructure: &vol.Structure[1],
- StartOffset: 1 * gadget.SizeMiB,
- Index: 1,
- },
- },
- })
-}
-
-func (p *positioningTestSuite) TestVolumePositionOffsetWriteAll(c *C) {
- var gadgetYaml = `
-volumes:
- pc:
- bootloader: grub
- structure:
- - name: mbr
- type: mbr
- size: 440
- - name: foo
- type: DA,21686148-6449-6E6F-744E-656564454649
- size: 1M
- offset: 1M
- offset-write: mbr+92
- content:
- - image: foo.img
- offset-write: bar+10
- - name: bar
- type: DA,21686148-6449-6E6F-744E-656564454649
- size: 1M
- offset-write: 600
- content:
- - image: bar.img
- offset-write: 450
-`
- makeSizedFile(c, filepath.Join(p.dir, "foo.img"), 200*gadget.SizeKiB, []byte(""))
- makeSizedFile(c, filepath.Join(p.dir, "bar.img"), 150*gadget.SizeKiB, []byte(""))
-
- vol := mustParseVolume(c, gadgetYaml, "pc")
- c.Assert(vol.Structure, HasLen, 3)
-
- v, err := gadget.PositionVolume(p.dir, vol, defaultConstraints)
- c.Assert(err, IsNil)
- c.Assert(v, DeepEquals, &gadget.PositionedVolume{
- Volume: vol,
- Size: 3 * gadget.SizeMiB,
- SectorSize: 512,
- RootDir: p.dir,
- PositionedStructure: []gadget.PositionedStructure{
- {
- // mbr
- VolumeStructure: &vol.Structure[0],
- StartOffset: 0,
- Index: 0,
- }, {
- // foo
- VolumeStructure: &vol.Structure[1],
- StartOffset: 1 * gadget.SizeMiB,
- Index: 1,
- // break for gofmt < 1.11
- PositionedOffsetWrite: asSizePtr(92),
- PositionedContent: []gadget.PositionedContent{
- {
- VolumeContent: &vol.Structure[1].Content[0],
- Size: 200 * gadget.SizeKiB,
- StartOffset: 1 * gadget.SizeMiB,
- // offset-write: bar+10
- PositionedOffsetWrite: asSizePtr(2*gadget.SizeMiB + 10),
- },
- },
- }, {
- // bar
- VolumeStructure: &vol.Structure[2],
- StartOffset: 2 * gadget.SizeMiB,
- Index: 2,
- // break for gofmt < 1.11
- PositionedOffsetWrite: asSizePtr(600),
- PositionedContent: []gadget.PositionedContent{
- {
- VolumeContent: &vol.Structure[2].Content[0],
- Size: 150 * gadget.SizeKiB,
- StartOffset: 2 * gadget.SizeMiB,
- // offset-write: bar+10
- PositionedOffsetWrite: asSizePtr(450),
- },
- },
- },
- },
- })
-}
-
-func (p *positioningTestSuite) TestVolumePositionOffsetWriteBadRelativeTo(c *C) {
- // define volumes explicitly as those would not pass validation
- volBadStructure := gadget.Volume{
- Structure: []gadget.VolumeStructure{
- {
- Name: "foo",
- Type: "DA,21686148-6449-6E6F-744E-656564454649",
- Size: 1 * gadget.SizeMiB,
- OffsetWrite: &gadget.RelativeOffset{
- RelativeTo: "bar",
- Offset: 10,
- },
- },
- },
- }
- volBadContent := gadget.Volume{
- Structure: []gadget.VolumeStructure{
- {
- Name: "foo",
- Type: "DA,21686148-6449-6E6F-744E-656564454649",
- Size: 1 * gadget.SizeMiB,
- Content: []gadget.VolumeContent{
- {
- Image: "foo.img",
- OffsetWrite: &gadget.RelativeOffset{
- RelativeTo: "bar",
- Offset: 10,
- },
- },
- },
- },
- },
- }
-
- makeSizedFile(c, filepath.Join(p.dir, "foo.img"), 200*gadget.SizeKiB, []byte(""))
-
- v, err := gadget.PositionVolume(p.dir, &volBadStructure, defaultConstraints)
- c.Check(v, IsNil)
- c.Check(err, ErrorMatches, `cannot resolve offset-write of structure #0 \("foo"\): refers to an unknown structure "bar"`)
-
- v, err = gadget.PositionVolume(p.dir, &volBadContent, defaultConstraints)
- c.Check(v, IsNil)
- c.Check(err, ErrorMatches, `cannot resolve offset-write of structure #0 \("foo"\) content "foo.img": refers to an unknown structure "bar"`)
-}
-
-func (p *positioningTestSuite) TestVolumePositionOffsetWriteEnlargesVolume(c *C) {
- var gadgetYamlStructure = `
-volumes:
- pc:
- bootloader: grub
- structure:
- - name: mbr
- type: mbr
- size: 440
- - name: foo
- type: DA,21686148-6449-6E6F-744E-656564454649
- size: 1M
- offset: 1M
- # 1GB
- offset-write: mbr+1073741824
-
-`
- vol := mustParseVolume(c, gadgetYamlStructure, "pc")
-
- v, err := gadget.PositionVolume(p.dir, vol, defaultConstraints)
- c.Assert(err, IsNil)
- // offset-write is at 1GB
- c.Check(v.Size, Equals, 1*gadget.SizeGiB+gadget.SizeLBA48Pointer)
-
- var gadgetYamlContent = `
-volumes:
- pc:
- bootloader: grub
- structure:
- - name: mbr
- type: mbr
- size: 440
- - name: foo
- type: DA,21686148-6449-6E6F-744E-656564454649
- size: 1M
- offset: 1M
- content:
- - image: foo.img
- # 2GB
- offset-write: mbr+2147483648
- - image: bar.img
- # 1GB
- offset-write: mbr+1073741824
- - image: baz.img
- # 3GB
- offset-write: mbr+3221225472
-
-`
- makeSizedFile(c, filepath.Join(p.dir, "foo.img"), 200*gadget.SizeKiB, []byte(""))
- makeSizedFile(c, filepath.Join(p.dir, "bar.img"), 150*gadget.SizeKiB, []byte(""))
- makeSizedFile(c, filepath.Join(p.dir, "baz.img"), 100*gadget.SizeKiB, []byte(""))
-
- vol = mustParseVolume(c, gadgetYamlContent, "pc")
-
- v, err = gadget.PositionVolume(p.dir, vol, defaultConstraints)
- c.Assert(err, IsNil)
- // foo.img offset-write is at 3GB
- c.Check(v.Size, Equals, 3*gadget.SizeGiB+gadget.SizeLBA48Pointer)
-}
-
-func (p *positioningTestSuite) TestPositionedStructureShift(c *C) {
- var gadgetYamlContent = `
-volumes:
- pc:
- bootloader: grub
- structure:
- - name: foo
- type: DA,21686148-6449-6E6F-744E-656564454649
- size: 1M
- offset: 1M
- content:
- - image: foo.img
- - image: bar.img
- # 300KB
- offset: 307200
-
-`
- makeSizedFile(c, filepath.Join(p.dir, "foo.img"), 200*gadget.SizeKiB, []byte(""))
- makeSizedFile(c, filepath.Join(p.dir, "bar.img"), 150*gadget.SizeKiB, []byte(""))
-
- vol := mustParseVolume(c, gadgetYamlContent, "pc")
-
- v, err := gadget.PositionVolume(p.dir, vol, defaultConstraints)
- c.Assert(err, IsNil)
- c.Assert(v.PositionedStructure, HasLen, 1)
- c.Assert(v.PositionedStructure[0].PositionedContent, HasLen, 2)
-
- ps := v.PositionedStructure[0]
-
- c.Assert(ps, DeepEquals, gadget.PositionedStructure{
- // foo
- VolumeStructure: &vol.Structure[0],
- StartOffset: 1 * gadget.SizeMiB,
- Index: 0,
- PositionedContent: []gadget.PositionedContent{
- {
- VolumeContent: &vol.Structure[0].Content[0],
- Size: 200 * gadget.SizeKiB,
- StartOffset: 1 * gadget.SizeMiB,
- Index: 0,
- }, {
- VolumeContent: &vol.Structure[0].Content[1],
- Size: 150 * gadget.SizeKiB,
- StartOffset: 1*gadget.SizeMiB + 300*gadget.SizeKiB,
- Index: 1,
- },
- },
- })
-
- shiftedTo0 := gadget.ShiftStructureTo(ps, 0)
- c.Assert(shiftedTo0, DeepEquals, gadget.PositionedStructure{
- // foo
- VolumeStructure: &vol.Structure[0],
- StartOffset: 0,
- Index: 0,
- PositionedContent: []gadget.PositionedContent{
- {
- VolumeContent: &vol.Structure[0].Content[0],
- Size: 200 * gadget.SizeKiB,
- StartOffset: 0,
- Index: 0,
- }, {
- VolumeContent: &vol.Structure[0].Content[1],
- Size: 150 * gadget.SizeKiB,
- StartOffset: 300 * gadget.SizeKiB,
- Index: 1,
- },
- },
- })
-
- shiftedTo2M := gadget.ShiftStructureTo(ps, 2*gadget.SizeMiB)
- c.Assert(shiftedTo2M, DeepEquals, gadget.PositionedStructure{
- // foo
- VolumeStructure: &vol.Structure[0],
- StartOffset: 2 * gadget.SizeMiB,
- Index: 0,
- PositionedContent: []gadget.PositionedContent{
- {
- VolumeContent: &vol.Structure[0].Content[0],
- Size: 200 * gadget.SizeKiB,
- StartOffset: 2 * gadget.SizeMiB,
- Index: 0,
- }, {
- VolumeContent: &vol.Structure[0].Content[1],
- Size: 150 * gadget.SizeKiB,
- StartOffset: 2*gadget.SizeMiB + 300*gadget.SizeKiB,
- Index: 1,
- },
- },
- })
-}
diff -Nru snapd-2.41+19.10.1/gadget/raw.go snapd-2.42.1+19.10/gadget/raw.go
--- snapd-2.41+19.10.1/gadget/raw.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/gadget/raw.go 2019-10-30 12:17:43.000000000 +0000
@@ -33,14 +33,14 @@
// RawStructureWriter implements support for writing raw (bare) structures.
type RawStructureWriter struct {
contentDir string
- ps *PositionedStructure
+ ps *LaidOutStructure
}
// NewRawStructureWriter returns a writer for the given structure, that will load
// the structure content data from the provided gadget content directory.
-func NewRawStructureWriter(contentDir string, ps *PositionedStructure) (*RawStructureWriter, error) {
+func NewRawStructureWriter(contentDir string, ps *LaidOutStructure) (*RawStructureWriter, error) {
if ps == nil {
- return nil, fmt.Errorf("internal error: *PositionedStructure is nil")
+ return nil, fmt.Errorf("internal error: *LaidOutStructure is nil")
}
if !ps.IsBare() {
return nil, fmt.Errorf("internal error: structure %s is not bare", ps)
@@ -56,9 +56,9 @@
}
// writeRawStream writes the input stream in that corresponds to provided
-// positioned content. The number of bytes read from input stream must match
-// exactly the declared size of positioned content entry.
-func writeRawStream(out io.WriteSeeker, pc *PositionedContent, in io.Reader) error {
+// laid out content. The number of bytes read from input stream must match
+// exactly the declared size of the content entry.
+func writeRawStream(out io.WriteSeeker, pc *LaidOutContent, in io.Reader) error {
if _, err := out.Seek(int64(pc.StartOffset), io.SeekStart); err != nil {
return fmt.Errorf("cannot seek to content start offset 0x%x: %v", pc.StartOffset, err)
}
@@ -70,8 +70,8 @@
return nil
}
-// writeRawImage writes a single image described by a positioned content entry.
-func (r *RawStructureWriter) writeRawImage(out io.WriteSeeker, pc *PositionedContent) error {
+// writeRawImage writes a single image described by a laid out content entry.
+func (r *RawStructureWriter) writeRawImage(out io.WriteSeeker, pc *LaidOutContent) error {
if pc.Image == "" {
return fmt.Errorf("internal error: no image defined")
}
@@ -86,7 +86,7 @@
// Write will write whole contents of a structure into the output stream.
func (r *RawStructureWriter) Write(out io.WriteSeeker) error {
- for _, pc := range r.ps.PositionedContent {
+ for _, pc := range r.ps.LaidOutContent {
if err := r.writeRawImage(out, &pc); err != nil {
return fmt.Errorf("failed to write image %v: %v", pc, err)
}
@@ -101,13 +101,13 @@
deviceLookup deviceLookupFunc
}
-type deviceLookupFunc func(ps *PositionedStructure) (device string, offs Size, err error)
+type deviceLookupFunc func(ps *LaidOutStructure) (device string, offs Size, err error)
// NewRawStructureUpdater returns an updater for the given raw (bare) structure.
// Update data will be loaded from the provided gadget content directory.
// Backups of replaced structures are temporarily kept in the rollback
// directory.
-func NewRawStructureUpdater(contentDir string, ps *PositionedStructure, backupDir string, deviceLookup deviceLookupFunc) (*RawStructureUpdater, error) {
+func NewRawStructureUpdater(contentDir string, ps *LaidOutStructure, backupDir string, deviceLookup deviceLookupFunc) (*RawStructureUpdater, error) {
if deviceLookup == nil {
return nil, fmt.Errorf("internal error: device lookup helper must be provided")
}
@@ -127,11 +127,11 @@
return ru, nil
}
-func rawContentBackupPath(backupDir string, ps *PositionedStructure, pc *PositionedContent) string {
+func rawContentBackupPath(backupDir string, ps *LaidOutStructure, pc *LaidOutContent) string {
return filepath.Join(backupDir, fmt.Sprintf("struct-%v-%v", ps.Index, pc.Index))
}
-func (r *RawStructureUpdater) backupOrCheckpointContent(disk io.ReadSeeker, pc *PositionedContent) error {
+func (r *RawStructureUpdater) backupOrCheckpointContent(disk io.ReadSeeker, pc *LaidOutContent) error {
backupPath := rawContentBackupPath(r.backupDir, r.ps, pc)
backupName := backupPath + ".backup"
sameName := backupPath + ".same"
@@ -191,7 +191,7 @@
// matchDevice identifies the device matching the configured structure, returns
// device path and a shifted structure should any offset adjustments be needed
-func (r *RawStructureUpdater) matchDevice() (device string, shifted *PositionedStructure, err error) {
+func (r *RawStructureUpdater) matchDevice() (device string, shifted *LaidOutStructure, err error) {
device, offs, err := r.deviceLookup(r.ps)
if err != nil {
return "", nil, fmt.Errorf("cannot find device matching structure %v: %v", r.ps, err)
@@ -224,7 +224,7 @@
}
defer disk.Close()
- for _, pc := range structForDevice.PositionedContent {
+ for _, pc := range structForDevice.LaidOutContent {
if err := r.backupOrCheckpointContent(disk, &pc); err != nil {
return fmt.Errorf("cannot backup image %v: %v", pc, err)
}
@@ -233,7 +233,7 @@
return nil
}
-func (r *RawStructureUpdater) rollbackDifferent(out io.WriteSeeker, pc *PositionedContent) error {
+func (r *RawStructureUpdater) rollbackDifferent(out io.WriteSeeker, pc *LaidOutContent) error {
backupPath := rawContentBackupPath(r.backupDir, r.ps, pc)
if osutil.FileExists(backupPath + ".same") {
@@ -266,7 +266,7 @@
}
defer disk.Close()
- for _, pc := range structForDevice.PositionedContent {
+ for _, pc := range structForDevice.LaidOutContent {
if err := r.rollbackDifferent(disk, &pc); err != nil {
return fmt.Errorf("cannot rollback image %v: %v", pc, err)
}
@@ -275,7 +275,7 @@
return nil
}
-func (r *RawStructureUpdater) updateDifferent(disk io.WriteSeeker, pc *PositionedContent) error {
+func (r *RawStructureUpdater) updateDifferent(disk io.WriteSeeker, pc *LaidOutContent) error {
backupPath := rawContentBackupPath(r.backupDir, r.ps, pc)
if osutil.FileExists(backupPath + ".same") {
@@ -310,7 +310,7 @@
}
defer disk.Close()
- for _, pc := range structForDevice.PositionedContent {
+ for _, pc := range structForDevice.LaidOutContent {
if err := r.updateDifferent(disk, &pc); err != nil {
return fmt.Errorf("cannot update image %v: %v", pc, err)
}
diff -Nru snapd-2.41+19.10.1/gadget/raw_test.go snapd-2.42.1+19.10/gadget/raw_test.go
--- snapd-2.41+19.10.1/gadget/raw_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/gadget/raw_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -75,11 +75,11 @@
makeSizedFile(c, filepath.Join(r.dir, "foo.img"), 128, []byte("foo foo foo"))
makeSizedFile(c, filepath.Join(r.dir, "bar.img"), 128, []byte("bar bar bar"))
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
},
- PositionedContent: []gadget.PositionedContent{
+ LaidOutContent: []gadget.LaidOutContent{
{
VolumeContent: &gadget.VolumeContent{
Image: "foo.img",
@@ -124,11 +124,11 @@
func (r *rawTestSuite) TestRawWriterNoFile(c *C) {
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
},
- PositionedContent: []gadget.PositionedContent{
+ LaidOutContent: []gadget.LaidOutContent{
{
VolumeContent: &gadget.VolumeContent{
Image: "foo.img",
@@ -176,11 +176,11 @@
}
makeSizedFile(c, filepath.Join(r.dir, "foo.img"), 128, []byte("foo foo foo"))
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
},
- PositionedContent: []gadget.PositionedContent{
+ LaidOutContent: []gadget.LaidOutContent{
{
VolumeContent: &gadget.VolumeContent{
Image: "foo.img",
@@ -208,11 +208,11 @@
func (r *rawTestSuite) TestRawWriterNoImage(c *C) {
out := &mockWriteSeeker{}
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
},
- PositionedContent: []gadget.PositionedContent{
+ LaidOutContent: []gadget.LaidOutContent{
{
// invalid content
VolumeContent: &gadget.VolumeContent{
@@ -232,7 +232,7 @@
}
func (r *rawTestSuite) TestRawWriterFailWithNonBare(c *C) {
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
// non-bare
@@ -246,7 +246,7 @@
}
func (r *rawTestSuite) TestRawWriterInternalErrors(c *C) {
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
},
@@ -257,7 +257,7 @@
c.Assert(rw, IsNil)
rw, err = gadget.NewRawStructureWriter(r.dir, nil)
- c.Assert(err, ErrorMatches, `internal error: \*PositionedStructure is nil`)
+ c.Assert(err, ErrorMatches, `internal error: \*LaidOutStructure is nil`)
c.Assert(rw, IsNil)
}
@@ -268,7 +268,7 @@
}
func (r *rawTestSuite) TestRawUpdaterFailWithNonBare(c *C) {
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
// non-bare
@@ -276,7 +276,7 @@
},
}
- ru, err := gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.PositionedStructure) (string, gadget.Size, error) {
+ ru, err := gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.LaidOutStructure) (string, gadget.Size, error) {
c.Fatalf("unexpected call")
return "", 0, nil
})
@@ -294,12 +294,12 @@
makeSizedFile(c, filepath.Join(r.dir, "foo.img"), 128, []byte("foo foo foo"))
makeSizedFile(c, filepath.Join(r.dir, "bar.img"), 128, []byte("bar bar bar"))
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
},
StartOffset: 1 * gadget.SizeMiB,
- PositionedContent: []gadget.PositionedContent{
+ LaidOutContent: []gadget.LaidOutContent{
{
VolumeContent: &gadget.VolumeContent{
Image: "foo.img",
@@ -316,7 +316,7 @@
},
},
}
- ru, err := gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.PositionedStructure) (string, gadget.Size, error) {
+ ru, err := gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.LaidOutStructure) (string, gadget.Size, error) {
c.Check(to, DeepEquals, ps)
// Structure has a partition, thus it starts at 0 offset.
return partitionPath, 0, nil
@@ -327,15 +327,15 @@
err = ru.Backup()
c.Assert(err, IsNil)
- c.Check(osutil.FileExists(gadget.RawContentBackupPath(r.backup, ps, &ps.PositionedContent[0])+".same"), Equals, true)
- c.Check(osutil.FileExists(gadget.RawContentBackupPath(r.backup, ps, &ps.PositionedContent[1])+".same"), Equals, true)
+ c.Check(osutil.FileExists(gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[0])+".same"), Equals, true)
+ c.Check(osutil.FileExists(gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[1])+".same"), Equals, true)
emptyDiskPath := filepath.Join(r.dir, "disk-not-written.img")
err = osutil.AtomicWriteFile(emptyDiskPath, nil, 0644, 0)
c.Assert(err, IsNil)
// update should be a noop now, use the same locations, point to a file
// of 0 size, so that seek fails and write would increase the size
- ru, err = gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.PositionedStructure) (string, gadget.Size, error) {
+ ru, err = gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.LaidOutStructure) (string, gadget.Size, error) {
return emptyDiskPath, 0, nil
})
c.Assert(err, IsNil)
@@ -371,12 +371,12 @@
makeSizedFile(c, filepath.Join(r.dir, "foo.img"), 128, []byte("zzz zzz zzz zzz"))
makeSizedFile(c, filepath.Join(r.dir, "bar.img"), 256, []byte("xxx xxx xxx xxx"))
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
},
StartOffset: 1 * gadget.SizeMiB,
- PositionedContent: []gadget.PositionedContent{
+ LaidOutContent: []gadget.LaidOutContent{
{
VolumeContent: &gadget.VolumeContent{
Image: "foo.img",
@@ -393,7 +393,7 @@
},
},
}
- ru, err := gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.PositionedStructure) (string, gadget.Size, error) {
+ ru, err := gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.LaidOutStructure) (string, gadget.Size, error) {
c.Check(to, DeepEquals, ps)
// Structure has a partition, thus it starts at 0 offset.
return diskPath, 0, nil
@@ -409,10 +409,10 @@
size int64
exists bool
}{
- {gadget.RawContentBackupPath(r.backup, ps, &ps.PositionedContent[0]) + ".backup", 128, true},
- {gadget.RawContentBackupPath(r.backup, ps, &ps.PositionedContent[1]) + ".backup", 256, true},
- {gadget.RawContentBackupPath(r.backup, ps, &ps.PositionedContent[1]) + ".same", 0, false},
- {gadget.RawContentBackupPath(r.backup, ps, &ps.PositionedContent[1]) + ".same", 0, false},
+ {gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[0]) + ".backup", 128, true},
+ {gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[1]) + ".backup", 256, true},
+ {gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[1]) + ".same", 0, false},
+ {gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[1]) + ".same", 0, false},
} {
c.Check(osutil.FileExists(e.path), Equals, e.exists)
if e.exists {
@@ -454,14 +454,14 @@
makeSizedFile(c, filepath.Join(r.dir, "foo.img"), 128, []byte("zzz zzz zzz zzz"))
makeSizedFile(c, filepath.Join(r.dir, "bar.img"), 256, []byte("xxx xxx xxx xxx"))
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
// No partition table entry, would trigger fallback lookup path.
Type: "bare",
Size: 2048,
},
StartOffset: 1 * gadget.SizeMiB,
- PositionedContent: []gadget.PositionedContent{
+ LaidOutContent: []gadget.LaidOutContent{
{
VolumeContent: &gadget.VolumeContent{
Image: "foo.img",
@@ -478,7 +478,7 @@
},
},
}
- ru, err := gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.PositionedStructure) (string, gadget.Size, error) {
+ ru, err := gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.LaidOutStructure) (string, gadget.Size, error) {
c.Check(to, DeepEquals, ps)
// No partition table, returned path corresponds to a disk, start offset is non-0.
return diskPath, ps.StartOffset, nil
@@ -493,8 +493,8 @@
path string
size int64
}{
- {gadget.RawContentBackupPath(r.backup, ps, &ps.PositionedContent[0]) + ".backup", 128},
- {gadget.RawContentBackupPath(r.backup, ps, &ps.PositionedContent[1]) + ".backup", 256},
+ {gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[0]) + ".backup", 128},
+ {gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[1]) + ".backup", 256},
} {
c.Check(osutil.FileExists(e.path), Equals, true)
c.Check(getFileSize(c, e.path), Equals, e.size)
@@ -516,12 +516,12 @@
func (r *rawTestSuite) TestRawUpdaterBackupErrors(c *C) {
diskPath := filepath.Join(r.dir, "disk.img")
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
},
StartOffset: 0,
- PositionedContent: []gadget.PositionedContent{
+ LaidOutContent: []gadget.LaidOutContent{
{
VolumeContent: &gadget.VolumeContent{
Image: "foo.img",
@@ -532,7 +532,7 @@
},
}
- ru, err := gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.PositionedStructure) (string, gadget.Size, error) {
+ ru, err := gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.LaidOutStructure) (string, gadget.Size, error) {
c.Check(to, DeepEquals, ps)
return diskPath, 0, nil
})
@@ -541,14 +541,14 @@
err = ru.Backup()
c.Assert(err, ErrorMatches, "cannot open device for reading: .*")
- c.Check(osutil.FileExists(gadget.RawContentBackupPath(r.backup, ps, &ps.PositionedContent[0])+".backup"), Equals, false)
+ c.Check(osutil.FileExists(gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[0])+".backup"), Equals, false)
// 0 sized disk, copying will fail with early EOF
makeSizedFile(c, diskPath, 0, nil)
err = ru.Backup()
c.Assert(err, ErrorMatches, "cannot backup image .*: cannot backup original image: EOF")
- c.Check(osutil.FileExists(gadget.RawContentBackupPath(r.backup, ps, &ps.PositionedContent[0])+".backup"), Equals, false)
+ c.Check(osutil.FileExists(gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[0])+".backup"), Equals, false)
// make proper disk image now
err = os.Remove(diskPath)
@@ -557,7 +557,7 @@
err = ru.Backup()
c.Assert(err, ErrorMatches, "cannot backup image .*: cannot checksum update image: .*")
- c.Check(osutil.FileExists(gadget.RawContentBackupPath(r.backup, ps, &ps.PositionedContent[0])+".backup"), Equals, false)
+ c.Check(osutil.FileExists(gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[0])+".backup"), Equals, false)
}
func (r *rawTestSuite) TestRawUpdaterBackupIdempotent(c *C) {
@@ -565,12 +565,12 @@
// 0 sized disk, copying will fail with early EOF
makeSizedFile(c, diskPath, 0, nil)
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
},
StartOffset: 0,
- PositionedContent: []gadget.PositionedContent{
+ LaidOutContent: []gadget.LaidOutContent{
{
VolumeContent: &gadget.VolumeContent{
Image: "foo.img",
@@ -581,14 +581,14 @@
},
}
- ru, err := gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.PositionedStructure) (string, gadget.Size, error) {
+ ru, err := gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.LaidOutStructure) (string, gadget.Size, error) {
c.Check(to, DeepEquals, ps)
return diskPath, 0, nil
})
c.Assert(err, IsNil)
c.Assert(ru, NotNil)
- contentBackupBasePath := gadget.RawContentBackupPath(r.backup, ps, &ps.PositionedContent[0])
+ contentBackupBasePath := gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[0])
// mock content backed-up marker
makeSizedFile(c, contentBackupBasePath+".backup", 0, nil)
@@ -607,12 +607,12 @@
}
func (r *rawTestSuite) TestRawUpdaterFindDeviceFailed(c *C) {
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
},
StartOffset: 0,
- PositionedContent: []gadget.PositionedContent{
+ LaidOutContent: []gadget.LaidOutContent{
{
VolumeContent: &gadget.VolumeContent{
Image: "foo.img",
@@ -627,7 +627,7 @@
c.Assert(err, ErrorMatches, "internal error: device lookup helper must be provided")
c.Assert(ru, IsNil)
- ru, err = gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.PositionedStructure) (string, gadget.Size, error) {
+ ru, err = gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.LaidOutStructure) (string, gadget.Size, error) {
c.Check(to, DeepEquals, ps)
return "", 0, errors.New("failed")
})
@@ -649,12 +649,12 @@
// 0 sized disk, copying will fail with early EOF
makeSizedFile(c, diskPath, 0, nil)
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
},
StartOffset: 0,
- PositionedContent: []gadget.PositionedContent{
+ LaidOutContent: []gadget.LaidOutContent{
{
VolumeContent: &gadget.VolumeContent{
Image: "foo.img",
@@ -665,7 +665,7 @@
},
}
- ru, err := gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.PositionedStructure) (string, gadget.Size, error) {
+ ru, err := gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.LaidOutStructure) (string, gadget.Size, error) {
c.Check(to, DeepEquals, ps)
return diskPath, 0, nil
})
@@ -675,7 +675,7 @@
err = ru.Rollback()
c.Assert(err, ErrorMatches, `cannot rollback image #0 \("foo.img"@0x80\{128\}\): cannot open backup image: .*no such file or directory`)
- contentBackupPath := gadget.RawContentBackupPath(r.backup, ps, &ps.PositionedContent[0]) + ".backup"
+ contentBackupPath := gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[0]) + ".backup"
// trigger short read
makeSizedFile(c, contentBackupPath, 0, nil)
@@ -695,12 +695,12 @@
// 0 sized disk, copying will fail with early EOF
makeSizedFile(c, diskPath, 2048, nil)
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
},
StartOffset: 0,
- PositionedContent: []gadget.PositionedContent{
+ LaidOutContent: []gadget.LaidOutContent{
{
VolumeContent: &gadget.VolumeContent{
Image: "foo.img",
@@ -711,7 +711,7 @@
},
}
- ru, err := gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.PositionedStructure) (string, gadget.Size, error) {
+ ru, err := gadget.NewRawStructureUpdater(r.dir, ps, r.backup, func(to *gadget.LaidOutStructure) (string, gadget.Size, error) {
c.Check(to, DeepEquals, ps)
return diskPath, 0, nil
})
@@ -723,7 +723,7 @@
c.Assert(err, ErrorMatches, `cannot update image #0 \("foo.img"@0x80\{128\}\): missing backup file`)
// pretend backup was done
- makeSizedFile(c, gadget.RawContentBackupPath(r.backup, ps, &ps.PositionedContent[0])+".backup", 0, nil)
+ makeSizedFile(c, gadget.RawContentBackupPath(r.backup, ps, &ps.LaidOutContent[0])+".backup", 0, nil)
err = ru.Update()
c.Assert(err, ErrorMatches, `cannot update image #0 \("foo.img"@0x80\{128\}\).*: cannot open image file: .*no such file or directory`)
@@ -740,16 +740,16 @@
}
func (r *rawTestSuite) TestRawUpdaterContentBackupPath(c *C) {
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{},
StartOffset: 0,
- PositionedContent: []gadget.PositionedContent{
+ LaidOutContent: []gadget.LaidOutContent{
{
VolumeContent: &gadget.VolumeContent{},
},
},
}
- pc := &ps.PositionedContent[0]
+ pc := &ps.LaidOutContent[0]
p := gadget.RawContentBackupPath(r.backup, ps, pc)
c.Assert(p, Equals, r.backup+"/struct-0-0")
@@ -762,13 +762,13 @@
}
func (r *rawTestSuite) TestRawUpdaterInternalErrors(c *C) {
- ps := &gadget.PositionedStructure{
+ ps := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Size: 2048,
},
}
- f := func(to *gadget.PositionedStructure) (string, gadget.Size, error) {
+ f := func(to *gadget.LaidOutStructure) (string, gadget.Size, error) {
return "", 0, errors.New("unexpected call")
}
rw, err := gadget.NewRawStructureUpdater("", ps, r.backup, f)
@@ -776,7 +776,7 @@
c.Assert(rw, IsNil)
rw, err = gadget.NewRawStructureUpdater(r.dir, nil, r.backup, f)
- c.Assert(err, ErrorMatches, `internal error: \*PositionedStructure is nil`)
+ c.Assert(err, ErrorMatches, `internal error: \*LaidOutStructure is nil`)
c.Assert(rw, IsNil)
rw, err = gadget.NewRawStructureUpdater(r.dir, ps, "", f)
diff -Nru snapd-2.41+19.10.1/gadget/update.go snapd-2.42.1+19.10/gadget/update.go
--- snapd-2.41+19.10.1/gadget/update.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/gadget/update.go 2019-10-30 12:17:43.000000000 +0000
@@ -31,7 +31,7 @@
var (
// default positioning constraints that match ubuntu-image
- defaultConstraints = PositioningConstraints{
+ defaultConstraints = LayoutConstraints{
NonMBRStartOffset: 1 * SizeMiB,
SectorSize: 512,
}
@@ -71,14 +71,15 @@
return err
}
- // layout old
- pOld, err := PositionVolume(old.RootDir, oldVol, defaultConstraints)
+ // layout old partially, without going deep into the layout of structure
+ // content
+ pOld, err := LayoutVolumePartially(oldVol, defaultConstraints)
if err != nil {
return fmt.Errorf("cannot lay out the old volume: %v", err)
}
// layout new
- pNew, err := PositionVolume(new.RootDir, newVol, defaultConstraints)
+ pNew, err := LayoutVolume(new.RootDir, newVol, defaultConstraints)
if err != nil {
return fmt.Errorf("cannot lay out the new volume: %v", err)
}
@@ -148,13 +149,13 @@
return false
}
-func isLegacyMBRTransition(from *PositionedStructure, to *PositionedStructure) bool {
+func isLegacyMBRTransition(from *LaidOutStructure, to *LaidOutStructure) bool {
// legacy MBR could have been specified by setting type: mbr, with no
// role
return from.Type == MBR && to.EffectiveRole() == MBR
}
-func canUpdateStructure(from *PositionedStructure, to *PositionedStructure) error {
+func canUpdateStructure(from *LaidOutStructure, to *LaidOutStructure) error {
if from.Size != to.Size {
return fmt.Errorf("cannot change structure size from %v to %v", from.Size, to.Size)
}
@@ -200,38 +201,38 @@
return nil
}
-func canUpdateVolume(from *PositionedVolume, to *PositionedVolume) error {
+func canUpdateVolume(from *PartiallyLaidOutVolume, to *LaidOutVolume) error {
if from.ID != to.ID {
return fmt.Errorf("cannot change volume ID from %q to %q", from.ID, to.ID)
}
if from.EffectiveSchema() != to.EffectiveSchema() {
return fmt.Errorf("cannot change volume schema from %q to %q", from.EffectiveSchema(), to.EffectiveSchema())
}
- if len(from.PositionedStructure) != len(to.PositionedStructure) {
- return fmt.Errorf("cannot change the number of structures within volume from %v to %v", len(from.PositionedStructure), len(to.PositionedStructure))
+ if len(from.LaidOutStructure) != len(to.LaidOutStructure) {
+ return fmt.Errorf("cannot change the number of structures within volume from %v to %v", len(from.LaidOutStructure), len(to.LaidOutStructure))
}
return nil
}
type updatePair struct {
- from *PositionedStructure
- to *PositionedStructure
+ from *LaidOutStructure
+ to *LaidOutStructure
}
-func resolveUpdate(oldVol *PositionedVolume, newVol *PositionedVolume) (updates []updatePair, err error) {
- if len(oldVol.PositionedStructure) != len(newVol.PositionedStructure) {
+func resolveUpdate(oldVol *PartiallyLaidOutVolume, newVol *LaidOutVolume) (updates []updatePair, err error) {
+ if len(oldVol.LaidOutStructure) != len(newVol.LaidOutStructure) {
return nil, errors.New("internal error: the number of structures in new and old volume definitions is different")
}
- for j, oldStruct := range oldVol.PositionedStructure {
- newStruct := newVol.PositionedStructure[j]
+ for j, oldStruct := range oldVol.LaidOutStructure {
+ newStruct := newVol.LaidOutStructure[j]
// update only when new edition is higher than the old one; boot
// assets are assumed to be backwards compatible, once deployed
// are not rolled back or replaced unless a higher edition is
// available
if newStruct.Update.Edition > oldStruct.Update.Edition {
updates = append(updates, updatePair{
- from: &oldVol.PositionedStructure[j],
- to: &newVol.PositionedStructure[j],
+ from: &oldVol.LaidOutStructure[j],
+ to: &newVol.LaidOutStructure[j],
})
}
}
@@ -295,7 +296,7 @@
var updaterForStructure = updaterForStructureImpl
-func updaterForStructureImpl(ps *PositionedStructure, newRootDir, rollbackDir string) (Updater, error) {
+func updaterForStructureImpl(ps *LaidOutStructure, newRootDir, rollbackDir string) (Updater, error) {
var updater Updater
var err error
if ps.IsBare() {
@@ -307,7 +308,7 @@
}
// MockUpdaterForStructure replace internal call with a mocked one, for use in tests only
-func MockUpdaterForStructure(mock func(ps *PositionedStructure, rootDir, rollbackDir string) (Updater, error)) (restore func()) {
+func MockUpdaterForStructure(mock func(ps *LaidOutStructure, rootDir, rollbackDir string) (Updater, error)) (restore func()) {
old := updaterForStructure
updaterForStructure = mock
return func() {
diff -Nru snapd-2.41+19.10.1/gadget/update_test.go snapd-2.42.1+19.10/gadget/update_test.go
--- snapd-2.41+19.10.1/gadget/update_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/gadget/update_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -88,8 +88,8 @@
}
type canUpdateTestCase struct {
- from gadget.PositionedStructure
- to gadget.PositionedStructure
+ from gadget.LaidOutStructure
+ to gadget.LaidOutStructure
err string
}
@@ -110,19 +110,19 @@
cases := []canUpdateTestCase{
{
// size change
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Size: 1 * gadget.SizeMiB},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Size: 1*gadget.SizeMiB + 1*gadget.SizeKiB},
},
err: "cannot change structure size from [0-9]+ to [0-9]+",
}, {
// size change
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Size: 1 * gadget.SizeMiB},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Size: 1 * gadget.SizeMiB},
},
err: "",
@@ -137,12 +137,12 @@
cases := []canUpdateTestCase{
{
// offset-write change
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
OffsetWrite: &gadget.RelativeOffset{Offset: 1024},
},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
OffsetWrite: &gadget.RelativeOffset{Offset: 2048},
},
@@ -150,12 +150,12 @@
err: "cannot change structure offset-write from [0-9]+ to [0-9]+",
}, {
// offset-write, change in relative-to structure name
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
OffsetWrite: &gadget.RelativeOffset{RelativeTo: "foo", Offset: 1024},
},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
OffsetWrite: &gadget.RelativeOffset{RelativeTo: "bar", Offset: 1024},
},
@@ -163,12 +163,12 @@
err: `cannot change structure offset-write from foo\+[0-9]+ to bar\+[0-9]+`,
}, {
// offset-write, unspecified in old
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
OffsetWrite: nil,
},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
OffsetWrite: &gadget.RelativeOffset{RelativeTo: "bar", Offset: 1024},
},
@@ -176,12 +176,12 @@
err: `cannot change structure offset-write from unspecified to bar\+[0-9]+`,
}, {
// offset-write, unspecified in new
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
OffsetWrite: &gadget.RelativeOffset{RelativeTo: "foo", Offset: 1024},
},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
OffsetWrite: nil,
},
@@ -189,12 +189,12 @@
err: `cannot change structure offset-write from foo\+[0-9]+ to unspecified`,
}, {
// all ok, both nils
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
OffsetWrite: nil,
},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
OffsetWrite: nil,
},
@@ -202,12 +202,12 @@
err: ``,
}, {
// all ok, both fully specified
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
OffsetWrite: &gadget.RelativeOffset{RelativeTo: "foo", Offset: 1024},
},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
OffsetWrite: &gadget.RelativeOffset{RelativeTo: "foo", Offset: 1024},
},
@@ -215,12 +215,12 @@
err: ``,
}, {
// all ok, both fully specified
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
OffsetWrite: &gadget.RelativeOffset{Offset: 1024},
},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
OffsetWrite: &gadget.RelativeOffset{Offset: 1024},
},
@@ -236,22 +236,22 @@
cases := []canUpdateTestCase{
{
// explicitly declared start offset change
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Size: 1 * gadget.SizeMiB, Offset: asSizePtr(1024)},
StartOffset: 1024,
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Size: 1 * gadget.SizeMiB, Offset: asSizePtr(2048)},
StartOffset: 2048,
},
err: "cannot change structure offset from [0-9]+ to [0-9]+",
}, {
// explicitly declared start offset in new structure
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Size: 1 * gadget.SizeMiB, Offset: nil},
StartOffset: 1024,
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Size: 1 * gadget.SizeMiB, Offset: asSizePtr(2048)},
StartOffset: 2048,
},
@@ -259,22 +259,22 @@
}, {
// explicitly declared start offset in old structure,
// missing from new
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Size: 1 * gadget.SizeMiB, Offset: asSizePtr(1024)},
StartOffset: 1024,
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Size: 1 * gadget.SizeMiB, Offset: nil},
StartOffset: 2048,
},
err: "cannot change structure offset from [0-9]+ to unspecified",
}, {
- // start offset changed due to positioning
- from: gadget.PositionedStructure{
+ // start offset changed due to layout
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Size: 1 * gadget.SizeMiB},
StartOffset: 1 * gadget.SizeMiB,
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Size: 1 * gadget.SizeMiB},
StartOffset: 2 * gadget.SizeMiB,
},
@@ -289,46 +289,46 @@
cases := []canUpdateTestCase{
{
// new role
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Role: ""},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Role: "system-data"},
},
err: `cannot change structure role from "" to "system-data"`,
}, {
// explicitly set tole
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Role: "mbr"},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Role: "system-data"},
},
err: `cannot change structure role from "mbr" to "system-data"`,
}, {
// implicit legacy role to proper explicit role
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "mbr"},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "bare", Role: "mbr"},
},
err: "",
}, {
// but not in the opposite direction
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "bare", Role: "mbr"},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "mbr"},
},
err: `cannot change structure type from "bare" to "mbr"`,
}, {
- // start offset changed due to positioning
- from: gadget.PositionedStructure{
+ // start offset changed due to layout
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Role: ""},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Role: ""},
},
err: "",
@@ -342,75 +342,75 @@
cases := []canUpdateTestCase{
{
// from hybrid type to GUID
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "0C,00000000-0000-0000-0000-dd00deadbeef"},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "00000000-0000-0000-0000-dd00deadbeef"},
},
err: `cannot change structure type from "0C,00000000-0000-0000-0000-dd00deadbeef" to "00000000-0000-0000-0000-dd00deadbeef"`,
}, {
// from MBR type to GUID (would be stopped at volume update checks)
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "0C"},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "00000000-0000-0000-0000-dd00deadbeef"},
},
err: `cannot change structure type from "0C" to "00000000-0000-0000-0000-dd00deadbeef"`,
}, {
// from one MBR type to another
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "0C"},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "0A"},
},
err: `cannot change structure type from "0C" to "0A"`,
}, {
// from one MBR type to another
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "0C"},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "bare"},
},
err: `cannot change structure type from "0C" to "bare"`,
}, {
// from one GUID to another
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "00000000-0000-0000-0000-dd00deadcafe"},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "00000000-0000-0000-0000-dd00deadbeef"},
},
err: `cannot change structure type from "00000000-0000-0000-0000-dd00deadcafe" to "00000000-0000-0000-0000-dd00deadbeef"`,
}, {
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "bare"},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "bare"},
},
}, {
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "0C"},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "0C"},
},
}, {
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "00000000-0000-0000-0000-dd00deadbeef"},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "00000000-0000-0000-0000-dd00deadbeef"},
},
}, {
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "0C,00000000-0000-0000-0000-dd00deadbeef"},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "0C,00000000-0000-0000-0000-dd00deadbeef"},
},
},
@@ -422,10 +422,10 @@
cases := []canUpdateTestCase{
{
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{ID: "00000000-0000-0000-0000-dd00deadbeef"},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{ID: "00000000-0000-0000-0000-dd00deadcafe"},
},
err: `cannot change structure ID from "00000000-0000-0000-0000-dd00deadbeef" to "00000000-0000-0000-0000-dd00deadcafe"`,
@@ -438,52 +438,52 @@
cases := []canUpdateTestCase{
{
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "0C", Filesystem: "ext4"},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "0C", Filesystem: ""},
},
err: `cannot change a filesystem structure to a bare one`,
}, {
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "0C", Filesystem: ""},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "0C", Filesystem: "ext4"},
},
err: `cannot change a bare structure to filesystem one`,
}, {
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "0C", Filesystem: "ext4"},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "0C", Filesystem: "vfat"},
},
err: `cannot change filesystem from "ext4" to "vfat"`,
}, {
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "0C", Filesystem: "ext4", Label: "writable"},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "0C", Filesystem: "ext4"},
},
err: `cannot change filesystem label from "writable" to ""`,
}, {
// from implicit filesystem label to explicit one
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "0C", Filesystem: "ext4", Role: "system-data"},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "0C", Filesystem: "ext4", Role: "system-data", Label: "writable"},
},
err: ``,
}, {
// all ok
- from: gadget.PositionedStructure{
+ from: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "0C", Filesystem: "ext4", Label: "do-not-touch"},
},
- to: gadget.PositionedStructure{
+ to: gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{Type: "0C", Filesystem: "ext4", Label: "do-not-touch"},
},
err: ``,
@@ -495,74 +495,74 @@
func (u *updateTestSuite) TestCanUpdateVolume(c *C) {
for idx, tc := range []struct {
- from gadget.PositionedVolume
- to gadget.PositionedVolume
+ from gadget.PartiallyLaidOutVolume
+ to gadget.LaidOutVolume
err string
}{
{
- from: gadget.PositionedVolume{
+ from: gadget.PartiallyLaidOutVolume{
Volume: &gadget.Volume{Schema: ""},
},
- to: gadget.PositionedVolume{
+ to: gadget.LaidOutVolume{
Volume: &gadget.Volume{Schema: "mbr"},
},
err: `cannot change volume schema from "gpt" to "mbr"`,
}, {
- from: gadget.PositionedVolume{
+ from: gadget.PartiallyLaidOutVolume{
Volume: &gadget.Volume{Schema: "gpt"},
},
- to: gadget.PositionedVolume{
+ to: gadget.LaidOutVolume{
Volume: &gadget.Volume{Schema: "mbr"},
},
err: `cannot change volume schema from "gpt" to "mbr"`,
}, {
- from: gadget.PositionedVolume{
+ from: gadget.PartiallyLaidOutVolume{
Volume: &gadget.Volume{ID: "00000000-0000-0000-0000-0000deadbeef"},
},
- to: gadget.PositionedVolume{
+ to: gadget.LaidOutVolume{
Volume: &gadget.Volume{ID: "00000000-0000-0000-0000-0000deadcafe"},
},
err: `cannot change volume ID from "00000000-0000-0000-0000-0000deadbeef" to "00000000-0000-0000-0000-0000deadcafe"`,
}, {
- from: gadget.PositionedVolume{
+ from: gadget.PartiallyLaidOutVolume{
Volume: &gadget.Volume{},
- PositionedStructure: []gadget.PositionedStructure{
+ LaidOutStructure: []gadget.LaidOutStructure{
{}, {},
},
},
- to: gadget.PositionedVolume{
+ to: gadget.LaidOutVolume{
Volume: &gadget.Volume{},
- PositionedStructure: []gadget.PositionedStructure{
+ LaidOutStructure: []gadget.LaidOutStructure{
{},
},
},
err: `cannot change the number of structures within volume from 2 to 1`,
}, {
// valid, implicit schema
- from: gadget.PositionedVolume{
+ from: gadget.PartiallyLaidOutVolume{
Volume: &gadget.Volume{Schema: ""},
- PositionedStructure: []gadget.PositionedStructure{
+ LaidOutStructure: []gadget.LaidOutStructure{
{}, {},
},
},
- to: gadget.PositionedVolume{
+ to: gadget.LaidOutVolume{
Volume: &gadget.Volume{Schema: "gpt"},
- PositionedStructure: []gadget.PositionedStructure{
+ LaidOutStructure: []gadget.LaidOutStructure{
{}, {},
},
},
err: ``,
}, {
// valid
- from: gadget.PositionedVolume{
+ from: gadget.PartiallyLaidOutVolume{
Volume: &gadget.Volume{Schema: "mbr"},
- PositionedStructure: []gadget.PositionedStructure{
+ LaidOutStructure: []gadget.LaidOutStructure{
{}, {},
},
},
- to: gadget.PositionedVolume{
+ to: gadget.LaidOutVolume{
Volume: &gadget.Volume{Schema: "mbr"},
- PositionedStructure: []gadget.PositionedStructure{
+ LaidOutStructure: []gadget.LaidOutStructure{
{}, {},
},
},
@@ -676,7 +676,7 @@
updaterForStructureCalls := 0
updateCalls := make(map[string]bool)
backupCalls := make(map[string]bool)
- restore := gadget.MockUpdaterForStructure(func(ps *gadget.PositionedStructure, psRootDir, psRollbackDir string) (gadget.Updater, error) {
+ restore := gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string) (gadget.Updater, error) {
c.Assert(psRootDir, Equals, newData.RootDir)
c.Assert(psRollbackDir, Equals, rollbackDir)
@@ -687,9 +687,9 @@
c.Check(ps.Size, Equals, 5*gadget.SizeMiB)
// non MBR start offset defaults to 1MiB
c.Check(ps.StartOffset, Equals, 1*gadget.SizeMiB)
- c.Assert(ps.PositionedContent, HasLen, 1)
- c.Check(ps.PositionedContent[0].Image, Equals, "first.img")
- c.Check(ps.PositionedContent[0].Size, Equals, 900*gadget.SizeKiB)
+ c.Assert(ps.LaidOutContent, HasLen, 1)
+ c.Check(ps.LaidOutContent[0].Image, Equals, "first.img")
+ c.Check(ps.LaidOutContent[0].Size, Equals, 900*gadget.SizeKiB)
case 1:
c.Check(ps.Name, Equals, "second")
c.Check(ps.IsBare(), Equals, false)
@@ -697,7 +697,7 @@
c.Check(ps.Size, Equals, 10*gadget.SizeMiB)
// foo's start offset + foo's size
c.Check(ps.StartOffset, Equals, (1+5)*gadget.SizeMiB)
- c.Assert(ps.PositionedContent, HasLen, 0)
+ c.Assert(ps.LaidOutContent, HasLen, 0)
c.Assert(ps.Content, HasLen, 1)
c.Check(ps.Content[0].Source, Equals, "/second-content")
c.Check(ps.Content[0].Target, Equals, "/")
@@ -750,7 +750,7 @@
newData.Info.Volumes["foo"].Structure[2].Update.Edition = 3
updaterForStructureCalls := 0
- restore := gadget.MockUpdaterForStructure(func(ps *gadget.PositionedStructure, psRootDir, psRollbackDir string) (gadget.Updater, error) {
+ restore := gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string) (gadget.Updater, error) {
c.Assert(psRootDir, Equals, newData.RootDir)
c.Assert(psRollbackDir, Equals, rollbackDir)
@@ -777,7 +777,7 @@
c.Assert(err, IsNil)
}
-func (u *updateTestSuite) TestUpdateApplyErrorPosition(c *C) {
+func (u *updateTestSuite) TestUpdateApplyErrorLayout(c *C) {
// prepare the stage
bareStruct := gadget.VolumeStructure{
Name: "foo",
@@ -787,7 +787,6 @@
},
}
bareStructUpdate := bareStruct
- bareStructUpdate.Update.Edition = 1
oldInfo := &gadget.Info{
Volumes: map[string]gadget.Volume{
"foo": {
@@ -815,15 +814,17 @@
rollbackDir := c.MkDir()
- // cannot position the old volume without bare struct data
+ // both old and new bare struct data is missing
+
+ // cannot lay out the new volume when bare struct data is missing
err := gadget.Update(oldData, newData, rollbackDir)
- c.Assert(err, ErrorMatches, `cannot lay out the old volume: cannot position structure #0 \("foo"\): content "first.img": .* no such file or directory`)
+ c.Assert(err, ErrorMatches, `cannot lay out the new volume: cannot lay out structure #0 \("foo"\): content "first.img": .* no such file or directory`)
- makeSizedFile(c, filepath.Join(oldRootDir, "first.img"), gadget.SizeMiB, nil)
+ makeSizedFile(c, filepath.Join(newRootDir, "first.img"), gadget.SizeMiB, nil)
- // cannot position the new volume
+ // Update does not error out when when the bare struct data of the old volume is missing
err = gadget.Update(oldData, newData, rollbackDir)
- c.Assert(err, ErrorMatches, `cannot lay out the new volume: cannot position structure #0 \("foo"\): content "first.img": .* no such file or directory`)
+ c.Assert(err, Equals, gadget.ErrNoUpdate)
}
func (u *updateTestSuite) TestUpdateApplyErrorIllegalVolumeUpdate(c *C) {
@@ -954,7 +955,7 @@
newData := gadget.GadgetData{Info: newInfo, RootDir: c.MkDir()}
rollbackDir := c.MkDir()
- restore := gadget.MockUpdaterForStructure(func(ps *gadget.PositionedStructure, psRootDir, psRollbackDir string) (gadget.Updater, error) {
+ restore := gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string) (gadget.Updater, error) {
c.Fatalf("unexpected call")
return &mockUpdater{}, nil
})
@@ -998,7 +999,7 @@
rollbackDir := c.MkDir()
- restore := gadget.MockUpdaterForStructure(func(ps *gadget.PositionedStructure, psRootDir, psRollbackDir string) (gadget.Updater, error) {
+ restore := gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string) (gadget.Updater, error) {
c.Fatalf("unexpected call")
return &mockUpdater{}, nil
})
@@ -1016,7 +1017,7 @@
newData.Info.Volumes["foo"].Structure[2].Update.Edition = 3
updaterForStructureCalls := 0
- restore := gadget.MockUpdaterForStructure(func(ps *gadget.PositionedStructure, psRootDir, psRollbackDir string) (gadget.Updater, error) {
+ restore := gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string) (gadget.Updater, error) {
updater := &mockUpdater{
updateCb: func() error {
c.Fatalf("unexpected update call")
@@ -1054,7 +1055,7 @@
backupCalls := make(map[string]bool)
rollbackCalls := make(map[string]bool)
updaterForStructureCalls := 0
- restore := gadget.MockUpdaterForStructure(func(ps *gadget.PositionedStructure, psRootDir, psRollbackDir string) (gadget.Updater, error) {
+ restore := gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string) (gadget.Updater, error) {
updater := &mockUpdater{
backupCb: func() error {
backupCalls[ps.Name] = true
@@ -1117,7 +1118,7 @@
backupCalls := make(map[string]bool)
rollbackCalls := make(map[string]bool)
updaterForStructureCalls := 0
- restore = gadget.MockUpdaterForStructure(func(ps *gadget.PositionedStructure, psRootDir, psRollbackDir string) (gadget.Updater, error) {
+ restore = gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string) (gadget.Updater, error) {
updater := &mockUpdater{
backupCb: func() error {
backupCalls[ps.Name] = true
@@ -1185,7 +1186,7 @@
newData.Info.Volumes["foo"].Structure[1].Update.Edition = 2
newData.Info.Volumes["foo"].Structure[2].Update.Edition = 3
- restore := gadget.MockUpdaterForStructure(func(ps *gadget.PositionedStructure, psRootDir, psRollbackDir string) (gadget.Updater, error) {
+ restore := gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, psRootDir, psRollbackDir string) (gadget.Updater, error) {
return nil, errors.New("bad updater for structure")
})
defer restore()
@@ -1199,7 +1200,7 @@
rootDir := c.MkDir()
rollbackDir := c.MkDir()
- psBare := &gadget.PositionedStructure{
+ psBare := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Filesystem: "none",
Size: 10 * gadget.SizeMiB,
@@ -1210,7 +1211,7 @@
c.Assert(err, IsNil)
c.Assert(updater, FitsTypeOf, &gadget.RawStructureUpdater{})
- psFs := &gadget.PositionedStructure{
+ psFs := &gadget.LaidOutStructure{
VolumeStructure: &gadget.VolumeStructure{
Filesystem: "ext4",
Size: 10 * gadget.SizeMiB,
diff -Nru snapd-2.41+19.10.1/HACKING.md snapd-2.42.1+19.10/HACKING.md
--- snapd-2.41+19.10.1/HACKING.md 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/HACKING.md 2019-10-30 12:17:43.000000000 +0000
@@ -243,4 +243,5 @@
## Submitting patches
-Please run `make fmt` before sending your patches.
+Please run `(cd cmd; make fmt)` before sending your patches for the "C" part of
+the source code.
diff -Nru snapd-2.41+19.10.1/httputil/useragent.go snapd-2.42.1+19.10/httputil/useragent.go
--- snapd-2.41+19.10.1/httputil/useragent.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/httputil/useragent.go 2019-10-30 12:17:43.000000000 +0000
@@ -64,7 +64,7 @@
// assumption checks out in practice, q.v. https://github.com/zyga/os-release-zoo
userAgent = fmt.Sprintf("snapd/%v (%s)%s %s/%s (%s) linux/%s", version,
strings.Join(extras, "; "), extraProdStr, release.ReleaseInfo.ID,
- release.ReleaseInfo.VersionID, string(arch.UbuntuArchitecture()),
+ release.ReleaseInfo.VersionID, string(arch.DpkgArchitecture()),
sanitizeKernelVersion(osutil.KernelVersion()))
return func() {
userAgent = origUserAgent
diff -Nru snapd-2.41+19.10.1/image/export_test.go snapd-2.42.1+19.10/image/export_test.go
--- snapd-2.41+19.10.1/image/export_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/image/export_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -31,7 +31,6 @@
var (
LocalSnaps = localSnaps
DecodeModelAssertion = decodeModelAssertion
- DownloadUnpackGadget = downloadUnpackGadget
SetupSeed = setupSeed
InstallCloudConfig = installCloudConfig
SnapChannel = snapChannel
diff -Nru snapd-2.41+19.10.1/image/helpers.go snapd-2.42.1+19.10/image/helpers.go
--- snapd-2.41+19.10.1/image/helpers.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/image/helpers.go 2019-10-30 12:17:43.000000000 +0000
@@ -214,6 +214,8 @@
Channel string
CohortKey string
Basename string
+
+ LeavePartialOnError bool
}
var (
@@ -322,7 +324,8 @@
os.Exit(1)
}()
- if err = sto.Download(context.TODO(), name, targetFn, &snap.DownloadInfo, pb, tsto.user, nil); err != nil {
+ dlOpts := &store.DownloadOptions{LeavePartialOnError: opts.LeavePartialOnError}
+ if err = sto.Download(context.TODO(), name, targetFn, &snap.DownloadInfo, pb, tsto.user, dlOpts); err != nil {
return "", nil, err
}
diff -Nru snapd-2.41+19.10.1/image/helpers_test.go snapd-2.42.1+19.10/image/helpers_test.go
--- snapd-2.41+19.10.1/image/helpers_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/image/helpers_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -33,6 +33,7 @@
func (s *imageSuite) TestDownloadOptionsString(c *check.C) {
for opts, str := range map[image.DownloadOptions]string{
+ {LeavePartialOnError: true}: "",
{}: "",
{TargetDir: "/foo"}: `in "/foo"`,
{Basename: "foo"}: `to "foo.snap"`,
@@ -78,6 +79,9 @@
Basename: "/foo",
}: image.ErrPathInBase,
} {
+ opts.LeavePartialOnError = true
+ c.Check(opts.Validate(), check.Equals, err)
+ opts.LeavePartialOnError = false
c.Check(opts.Validate(), check.Equals, err)
}
}
diff -Nru snapd-2.41+19.10.1/image/image.go snapd-2.42.1+19.10/image/image.go
--- snapd-2.41+19.10.1/image/image.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/image/image.go 2019-10-30 12:17:43.000000000 +0000
@@ -20,12 +20,12 @@
package image
import (
- "bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
+ "sort"
"strings"
"syscall"
@@ -33,11 +33,12 @@
"github.com/snapcore/snapd/asserts/snapasserts"
"github.com/snapcore/snapd/asserts/sysdb"
"github.com/snapcore/snapd/boot"
- "github.com/snapcore/snapd/bootloader"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/release"
+ "github.com/snapcore/snapd/seed"
"github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/channel"
"github.com/snapcore/snapd/snap/squashfs"
"github.com/snapcore/snapd/strutil"
)
@@ -176,7 +177,7 @@
// classicHasSnaps returns whether the model or options specify any snaps for the classic case
func classicHasSnaps(model *asserts.Model, opts *Options) bool {
- return model.Gadget() != "" || len(model.RequiredSnaps()) != 0 || len(opts.Snaps) != 0
+ return model.Gadget() != "" || len(model.RequiredNoEssentialSnaps()) != 0 || len(opts.Snaps) != 0
}
func Prepare(opts *Options) error {
@@ -208,7 +209,7 @@
if err := validateNonLocalSnaps(opts.Snaps); err != nil {
return err
}
- if _, err := snap.ParseChannel(opts.Channel, ""); err != nil {
+ if _, err := channel.Parse(opts.Channel, ""); err != nil {
return fmt.Errorf("cannot use channel: %v", err)
}
@@ -228,9 +229,9 @@
}
if !opts.Classic {
- // unpacking the gadget for core models
- if err := downloadUnpackGadget(tsto, model, opts, local); err != nil {
- return err
+ // create directory for later unpacking the gadget in
+ if err := os.MkdirAll(opts.GadgetUnpackDir, 0755); err != nil {
+ return fmt.Errorf("cannot create gadget unpack dir %q: %s", opts.GadgetUnpackDir, err)
}
}
@@ -274,6 +275,11 @@
// fallback to default channel
snapChannel = opts.Channel
}
+ if snapChannel != "" {
+ if _, err := channel.ParseVerbatim(snapChannel, "-"); err != nil {
+ return "", fmt.Errorf("cannot use option channel for snap %q: %v", name, err)
+ }
+ }
// consider snap types that can be pinned to a track by the model
var pinnedTrack string
var kind string
@@ -285,58 +291,21 @@
kind = "kernel"
pinnedTrack = model.KernelTrack()
}
- if pinnedTrack != "" {
- ch, err := makeChannelFromTrack(kind, pinnedTrack, snapChannel)
- if err != nil {
- return "", err
- }
- snapChannel = ch
-
+ ch, err := channel.ResolveLocked(pinnedTrack, snapChannel)
+ if err == channel.ErrLockedTrackSwitch {
+ return "", fmt.Errorf("channel %q for %s has a track incompatible with the track from model assertion: %s", snapChannel, kind, pinnedTrack)
}
- return snapChannel, nil
-}
-
-func makeChannelFromTrack(what, track, snapChannel string) (string, error) {
- mch, err := snap.ParseChannel(track, "")
if err != nil {
- return "", fmt.Errorf("cannot use track %q for %s from model assertion: %v", track, what, err)
- }
- if snapChannel != "" {
- ch, err := snap.ParseChannelVerbatim(snapChannel, "")
- if err != nil {
- return "", fmt.Errorf("cannot parse channel %q for %s", snapChannel, what)
- }
- if ch.Track != "" && ch.Track != mch.Track {
- return "", fmt.Errorf("channel %q for %s has a track incompatible with the track from model assertion: %s", snapChannel, what, track)
- }
- mch.Risk = ch.Risk
+ return "", err
}
- return mch.Clean().String(), nil
+ return ch, nil
}
-func downloadUnpackGadget(tsto *ToolingStore, model *asserts.Model, opts *Options, local *localInfos) error {
- if err := os.MkdirAll(opts.GadgetUnpackDir, 0755); err != nil {
- return fmt.Errorf("cannot create gadget unpack dir %q: %s", opts.GadgetUnpackDir, err)
- }
-
- gadgetName := model.Gadget()
- gadgetChannel, err := snapChannel(gadgetName, model, opts, local)
- if err != nil {
- return err
- }
-
- dlOpts := &DownloadOptions{
- TargetDir: opts.GadgetUnpackDir,
- Channel: gadgetChannel,
- }
- snapFn, _, err := acquireSnap(tsto, gadgetName, dlOpts, local)
- if err != nil {
- return err
- }
+func unpackGadget(gadgetFname, gadgetUnpackDir string) error {
// FIXME: jumping through layers here, we need to make
// unpack part of the container interface (again)
- snap := squashfs.New(snapFn)
- return snap.Unpack("*", opts.GadgetUnpackDir)
+ snap := squashfs.New(gadgetFname)
+ return snap.Unpack("*", gadgetUnpackDir)
}
func acquireSnap(tsto *ToolingStore, name string, dlOpts *DownloadOptions, local *localInfos) (downloadedSnap string, info *snap.Info, err error) {
@@ -395,43 +364,6 @@
}
}
-// neededDefaultProviders returns the names of all default-providers for
-// the content plugs that the given snap.Info needs.
-func neededDefaultProviders(info *snap.Info) (cps []string) {
- for _, plug := range info.Plugs {
- if plug.Interface == "content" {
- var dprovider string
- if err := plug.Attr("default-provider", &dprovider); err == nil && dprovider != "" {
- cps = append(cps, dprovider)
- }
- }
- }
- return cps
-}
-
-// hasBase checks if the given snap has a base in the given localInfos and
-// snaps. If not an error is returned.
-func hasBase(snap *snap.Info, local *localInfos, snaps []string) error {
- // snap needs no base (or it simply needs core which is never listed explicitly): nothing to do
- if snap.Base == "" {
- return nil
- }
-
- // snap explicitly listed as not needing a base snap (e.g. a content-only snap)
- if snap.Base == "none" {
- return nil
- }
-
- // core provides everything that core16 needs
- if snap.Base == "core16" && local.hasName(snaps, "core") {
- return nil
- }
- if local.hasName(snaps, snap.Base) {
- return nil
- }
- return fmt.Errorf("cannot add snap %q without also adding its base %q explicitly", snap.InstanceName(), snap.Base)
-}
-
func setupSeed(tsto *ToolingStore, model *asserts.Model, opts *Options, local *localInfos) error {
if model.Classic() != opts.Classic {
return fmt.Errorf("internal error: classic model but classic mode not set")
@@ -466,7 +398,7 @@
if !osutil.GetenvBool("UBUNTU_IMAGE_SKIP_COPY_UNVERIFIED_MODEL") {
return fmt.Errorf("cannot fetch and check prerequisites for the model assertion: %v", err)
} else {
- fmt.Fprintf(Stderr, "WARNING: Cannot fetch and check prerequisites for the model assertion, it will not be copied into the image making it unusable (unless this is a test): %v\n", err)
+ fmt.Fprintf(Stderr, "WARNING: cannot fetch and check prerequisites for the model assertion, it will not be copied into the image making it unusable (unless this is a test): %v\n", err)
f.addedRefs = nil
}
}
@@ -481,24 +413,50 @@
}
baseName := defaultCore
+ basesAndApps := []string{}
if model.Base() != "" {
baseName = model.Base()
+ basesAndApps = append(basesAndApps, baseName)
+
+ }
+
+ if !opts.Classic {
+ if err := os.MkdirAll(dirs.SnapBlobDir, 0755); err != nil {
+ return err
+ }
+ }
+
+ // TODO|XXX: later use the refs directly and simplify
+ reqSnaps := make([]string, 0, len(model.RequiredNoEssentialSnaps()))
+ for _, reqRef := range model.RequiredNoEssentialSnaps() {
+ reqSnaps = append(reqSnaps, reqRef.SnapName())
+ }
+
+ basesAndApps = append(basesAndApps, reqSnaps...)
+ basesAndApps = append(basesAndApps, opts.Snaps...)
+ // TODO: required snaps should get their base from required
+ // snaps (mentioned in the model); additional snaps could
+ // fetch their base, providers instead if not already present
+ // Be careful about core vs core16
+
+ seed := &imageSeed{
+ model: model,
+ baseName: baseName,
+ opts: opts,
+ local: local,
+ tsto: tsto,
+ basesAndApps: basesAndApps,
+ snapSeedDir: snapSeedDir,
+ f: f,
+ db: db,
+ seen: make(map[string]bool),
+ downloadedSnapsInfoForBootConfig: make(map[string]*snap.Info),
}
snaps := []string{}
// always add an implicit snapd first when a base is used
if model.Base() != "" {
snaps = append(snaps, "snapd")
- // TODO: once we order snaps by what they need this
- // can go aways
- // Here we ensure that "core" is seeded very early
- // when bases are in use. This fixes the issue
- // that when people use model assertions with
- // required snaps like bluez which at this point
- // still requires core will hang forever in seeding.
- if strutil.ListContains(model.RequiredSnaps(), "core") || local.hasName(opts.Snaps, "core") {
- snaps = append(snaps, "core")
- }
}
if !opts.Classic {
@@ -507,140 +465,45 @@
snaps = append(snaps, model.Kernel())
snaps = append(snaps, model.Gadget())
} else {
- // classic image case: first core as needed and gadget
- if classicHasSnaps(model, opts) {
- // TODO: later use snapd+core16 or core18 if specified
- snaps = append(snaps, "core")
- }
+ // classic image case
if model.Gadget() != "" {
snaps = append(snaps, model.Gadget())
}
}
// then required and the user requested stuff
- snaps = append(snaps, model.RequiredSnaps()...)
+ snaps = append(snaps, reqSnaps...)
snaps = append(snaps, opts.Snaps...)
- if !opts.Classic {
- if err := os.MkdirAll(dirs.SnapBlobDir, 0755); err != nil {
- return err
- }
- }
-
- seen := make(map[string]bool)
- var locals []string
- downloadedSnapsInfoForBootConfig := map[string]*snap.Info{}
- var seedYaml snap.Seed
for _, snapName := range snaps {
- name := local.Name(snapName)
- if seen[name] {
- fmt.Fprintf(Stdout, "%s already prepared, skipping\n", name)
- continue
- }
-
- if local.IsLocal(name) {
- fmt.Fprintf(Stdout, "Copying %q (%s)\n", local.Path(name), name)
- } else {
- fmt.Fprintf(Stdout, "Fetching %s\n", name)
- }
-
- snapChannel, err := snapChannel(name, model, opts, local)
- if err != nil {
+ if err := seed.add(snapName); err != nil {
return err
}
+ }
- dlOpts := &DownloadOptions{
- TargetDir: snapSeedDir,
- Channel: snapChannel,
+ // provide snapd or core
+ if len(seed.needsCore) != 0 && !seed.seen["core"] {
+ // one of the snaps requires core as base
+ // which used to be implicit, add it
+ if model.Base() != "" {
+ // TODO: later turn this into an error? for sure for UC20
+ fmt.Fprintf(Stderr, "WARNING: model has base %q but some snaps (%s) require \"core\" as base as well, for compatibility it was added implicitly, adding \"core\" explicitly is recommended\n", model.Base(), strutil.Quoted(seed.needsCore))
}
- fn, info, err := acquireSnap(tsto, name, dlOpts, local)
- if err != nil {
+ if err := seed.add("core"); err != nil {
return err
}
-
- // Sanity check, note that we could support this case
- // if we have a use-case but it requires changes in the
- // devicestate/firstboot.go ordering code.
- if info.GetType() == snap.TypeGadget && info.Base != model.Base() {
- return fmt.Errorf("cannot use gadget snap because its base %q is different from model base %q", info.Base, model.Base())
- }
- if err := hasBase(info, local, snaps); err != nil {
+ } else if opts.Classic && len(seed.seen) != 0 {
+ if err := seed.add("snapd"); err != nil {
return err
}
- // warn about missing default providers
- for _, dp := range neededDefaultProviders(info) {
- if !local.hasName(snaps, dp) {
- // TODO: have a way to ignore this issue on a snap by snap basis?
- return fmt.Errorf("cannot use snap %q without its default content provider %q being added explicitly", info.InstanceName(), dp)
- }
- }
-
- seen[name] = true
- typ := info.GetType()
-
- needsClassic := info.NeedsClassic()
- if needsClassic && !opts.Classic {
- return fmt.Errorf("cannot use classic snap %q in a core system", info.InstanceName())
- }
-
- // if it comes from the store fetch the snap assertions too
- if info.SnapID != "" {
- snapDecl, err := FetchAndCheckSnapAssertions(fn, info, f, db)
- if err != nil {
- return err
- }
- var kind string
- switch typ {
- case snap.TypeKernel:
- kind = "kernel"
- case snap.TypeGadget:
- kind = "gadget"
- }
- if kind != "" { // kernel or gadget
- // TODO: share helpers with devicestate if the policy becomes much more complicated
- publisher := snapDecl.PublisherID()
- if publisher != model.BrandID() && publisher != "canonical" {
- return fmt.Errorf("cannot use %s %q published by %q for model by %q", kind, name, publisher, model.BrandID())
- }
- }
- } else {
- locals = append(locals, name)
- // local snaps have no channel
- snapChannel = ""
- }
-
- // kernel/os/model.base are required for booting on core
- if !opts.Classic && (typ == snap.TypeKernel || name == baseName) {
- dst := filepath.Join(dirs.SnapBlobDir, filepath.Base(fn))
- // construct a relative symlink from the blob dir
- // to the seed file
- relSymlink, err := filepath.Rel(dirs.SnapBlobDir, fn)
- if err != nil {
- return fmt.Errorf("cannot build symlink: %v", err)
- }
- if err := os.Symlink(relSymlink, dst); err != nil {
- return err
- }
- // store the snap.Info for kernel/os/base so
- // that the bootload can DTRT
- downloadedSnapsInfoForBootConfig[dst] = info
- }
+ }
- // set seed.yaml
- seedYaml.Snaps = append(seedYaml.Snaps, &snap.SeedSnap{
- Name: info.InstanceName(),
- SnapID: info.SnapID, // cross-ref
- Channel: snapChannel,
- File: filepath.Base(fn),
- DevMode: info.NeedsDevMode(),
- Classic: needsClassic,
- Contact: info.Contact,
- // no assertions for this snap were put in the seed
- Unasserted: info.SnapID == "",
- })
+ if len(seed.needsCore16) != 0 && !seed.seen["core"] {
+ return fmt.Errorf(`cannot use %s requiring base "core16" without adding "core16" (or "core") explicitly`, strutil.Quoted(seed.needsCore16))
}
- if len(locals) > 0 {
- fmt.Fprintf(Stderr, "WARNING: %s were installed from local snaps disconnected from a store and cannot be refreshed subsequently!\n", strutil.Quoted(locals))
+
+ if len(seed.locals) > 0 {
+ fmt.Fprintf(Stderr, "WARNING: %s installed from local snaps disconnected from a store cannot be refreshed subsequently!\n", strutil.Quoted(seed.locals))
}
// fetch device store assertion (and prereqs) if available
@@ -671,9 +534,8 @@
}
}
- // TODO: add the refs as an assertions list of maps section to seed.yaml
-
seedFn := filepath.Join(dirs.SnapSeedDir, "seed.yaml")
+ seedYaml := seed.seedYaml()
if err := seedYaml.Write(seedFn); err != nil {
return fmt.Errorf("cannot write seed.yaml: %s", err)
}
@@ -692,12 +554,12 @@
}
if !opts.Classic {
- // now do the bootloader stuff
- if err := bootloader.InstallBootConfig(opts.GadgetUnpackDir); err != nil {
+ // unpacking the gadget for core models
+ if err := unpackGadget(seed.gadgetFname, opts.GadgetUnpackDir); err != nil {
return err
}
- if err := setBootvars(downloadedSnapsInfoForBootConfig, model); err != nil {
+ if err := boot.MakeBootable(model, opts.RootDir, seed.downloadedSnapsInfoForBootConfig, opts.GadgetUnpackDir); err != nil {
return err
}
@@ -710,145 +572,201 @@
return nil
}
-func setBootvars(downloadedSnapsInfoForBootConfig map[string]*snap.Info, model *asserts.Model) error {
- if len(downloadedSnapsInfoForBootConfig) != 2 {
- return fmt.Errorf("setBootvars can only be called with exactly one kernel and exactly one core/base boot info: %v", downloadedSnapsInfoForBootConfig)
+type seedEntry struct {
+ snap *seed.Snap16
+ snapType snap.Type
+}
+
+type seedEntriesByType []seedEntry
+
+func (e seedEntriesByType) Len() int { return len(e) }
+func (e seedEntriesByType) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
+func (e seedEntriesByType) Less(i, j int) bool {
+ return e[i].snapType.SortsBefore(e[j].snapType)
+}
+
+type imageSeed struct {
+ model *asserts.Model
+ baseName string
+
+ opts *Options
+ local *localInfos
+
+ tsto *ToolingStore
+
+ basesAndApps []string
+
+ snapSeedDir string
+ f asserts.Fetcher
+ db *asserts.Database
+
+ entries seedEntriesByType
+
+ seen map[string]bool
+ locals []string
+ downloadedSnapsInfoForBootConfig map[string]*snap.Info
+ gadgetFname string
+
+ needsCore []string
+ needsCore16 []string
+}
+
+func (s *imageSeed) add(snapName string) error {
+ model := s.model
+ opts := s.opts
+ local := s.local
+
+ name := local.Name(snapName)
+ if s.seen[name] {
+ fmt.Fprintf(Stdout, "%s already prepared, skipping\n", name)
+ return nil
+ }
+
+ if local.IsLocal(name) {
+ fmt.Fprintf(Stdout, "Copying %q (%s)\n", local.Path(name), name)
+ } else {
+ fmt.Fprintf(Stdout, "Fetching %s\n", name)
}
- // Set bootvars for kernel/core snaps so the system boots and
- // does the first-time initialization. There is also no
- // mounted kernel/core/base snap, but just the blobs.
- loader, err := bootloader.Find()
+ snapChannel, err := snapChannel(name, model, opts, local)
if err != nil {
- return fmt.Errorf("cannot set kernel/core boot variables: %s", err)
+ return err
}
- snaps, err := filepath.Glob(filepath.Join(dirs.SnapBlobDir, "*.snap"))
- if len(snaps) == 0 || err != nil {
- return fmt.Errorf("internal error: cannot find core/kernel snap")
+ dlOpts := &DownloadOptions{
+ TargetDir: s.snapSeedDir,
+ Channel: snapChannel,
+ }
+ fn, info, err := acquireSnap(s.tsto, name, dlOpts, local)
+ if err != nil {
+ return err
}
- m := map[string]string{
- "snap_mode": "",
- "snap_try_core": "",
- "snap_try_kernel": "",
+ if err := s.checkBase(info); err != nil {
+ return err
}
- if model.DisplayName() != "" {
- m["snap_menuentry"] = model.DisplayName()
+ // warn about missing default providers
+ for _, dp := range snap.NeededDefaultProviders(info) {
+ if !local.hasName(s.basesAndApps, dp) {
+ // TODO: have a way to ignore this issue on a snap by snap basis?
+ return fmt.Errorf("cannot use snap %q without its default content provider %q being added explicitly", info.InstanceName(), dp)
+ }
}
- for _, fn := range snaps {
- bootvar := ""
+ s.seen[name] = true
+ typ := info.GetType()
- info := downloadedSnapsInfoForBootConfig[fn]
- if info == nil {
- // this should never happen, if it does print some
- // debug info
- keys := make([]string, 0, len(downloadedSnapsInfoForBootConfig))
- for k := range downloadedSnapsInfoForBootConfig {
- keys = append(keys, k)
- }
- return fmt.Errorf("cannot get download info for snap %s, available infos: %v", fn, keys)
+ needsClassic := info.NeedsClassic()
+ if needsClassic && !opts.Classic {
+ return fmt.Errorf("cannot use classic snap %q in a core system", info.InstanceName())
+ }
+
+ // if it comes from the store fetch the snap assertions too
+ if info.SnapID != "" {
+ snapDecl, err := FetchAndCheckSnapAssertions(fn, info, s.f, s.db)
+ if err != nil {
+ return err
}
- switch info.GetType() {
- case snap.TypeOS, snap.TypeBase:
- bootvar = "snap_core"
+ var kind string
+ switch typ {
case snap.TypeKernel:
- bootvar = "snap_kernel"
- if err := extractKernelAssets(fn, info); err != nil {
- return err
+ kind = "kernel"
+ case snap.TypeGadget:
+ kind = "gadget"
+ }
+ if kind != "" { // kernel or gadget
+ // TODO: share helpers with devicestate if the policy becomes much more complicated
+ publisher := snapDecl.PublisherID()
+ if publisher != model.BrandID() && publisher != "canonical" {
+ return fmt.Errorf("cannot use %s %q published by %q for model by %q", kind, name, publisher, model.BrandID())
}
}
+ } else {
+ s.locals = append(s.locals, name)
+ // local snaps have no channel
+ snapChannel = ""
+ }
- if bootvar != "" {
- name := filepath.Base(fn)
- m[bootvar] = name
- }
+ // kernel/os/model.base are required for booting on core
+ if !opts.Classic && (typ == snap.TypeKernel || name == s.baseName) {
+ // store the snap.Info for kernel/os/base so
+ // that boot.MakeBootable can DTRT
+ s.downloadedSnapsInfoForBootConfig[fn] = info
}
- if err := loader.SetBootVars(m); err != nil {
- return err
+ // remember the gadget for unpacking later
+ if !opts.Classic && typ == snap.TypeGadget {
+ s.gadgetFname = fn
}
+ s.entries = append(s.entries, seedEntry{
+ snap: &seed.Snap16{
+ Name: info.InstanceName(),
+ SnapID: info.SnapID, // cross-ref
+ Channel: snapChannel,
+ File: filepath.Base(fn),
+ DevMode: info.NeedsDevMode(),
+ Classic: needsClassic,
+ Contact: info.Contact,
+ // no assertions for this snap were put in the seed
+ Unasserted: info.SnapID == "",
+ },
+ snapType: typ,
+ })
+
return nil
}
-func extractKernelAssets(snapPath string, info *snap.Info) error {
- snapf, err := snap.Open(snapPath)
- if err != nil {
- return err
+// checkBase checks if the given snap has a base in the given localInfos and
+// snaps. If not an error is returned.
+func (s *imageSeed) checkBase(info *snap.Info) error {
+ // Sanity check, note that we could support this case
+ // if we have a use-case but it requires changes in the
+ // devicestate/firstboot.go ordering code.
+ if info.GetType() == snap.TypeGadget && !s.opts.Classic && info.Base != s.model.Base() {
+ return fmt.Errorf("cannot use gadget snap because its base %q is different from model base %q", info.Base, s.model.Base())
}
- if err := boot.ExtractKernelAssets(info, snapf); err != nil {
- return err
+ // snap needs no base (or it simply needs core which is never listed explicitly): nothing to do
+ if info.Base == "" {
+ if info.GetType() == snap.TypeGadget || info.GetType() == snap.TypeApp {
+ // remember to make sure we have core installed
+ s.needsCore = append(s.needsCore, info.SnapName())
+ }
+ return nil
}
- return nil
-}
-
-func copyLocalSnapFile(snapPath, targetDir string, info *snap.Info) (dstPath string, err error) {
- dst := filepath.Join(targetDir, filepath.Base(info.MountFile()))
- return dst, osutil.CopyFile(snapPath, dst, 0)
-}
-func ValidateSeed(seedFile string) error {
- seed, err := snap.ReadSeedYaml(seedFile)
- if err != nil {
- return err
+ // snap explicitly listed as not needing a base snap (e.g. a content-only snap)
+ if info.Base == "none" {
+ return nil
}
- var errs []error
- // read the snaps info
- snapInfos := make(map[string]*snap.Info)
- for _, seedSnap := range seed.Snaps {
- fn := filepath.Join(filepath.Dir(seedFile), "snaps", seedSnap.File)
- snapf, err := snap.Open(fn)
- if err != nil {
- errs = append(errs, err)
- } else {
- info, err := snap.ReadInfoFromSnapFile(snapf, nil)
- if err != nil {
- errs = append(errs, fmt.Errorf("cannot use snap %s: %v", fn, err))
- } else {
- snapInfos[info.InstanceName()] = info
- }
- }
+ if s.local.hasName(s.basesAndApps, info.Base) {
+ return nil
}
- // ensure we have either "core" or "snapd"
- _, haveCore := snapInfos["core"]
- _, haveSnapd := snapInfos["snapd"]
- if !(haveCore || haveSnapd) {
- errs = append(errs, fmt.Errorf("the core or snapd snap must be part of the seed"))
+ if info.Base == "core16" {
+ // check at the end
+ s.needsCore16 = append(s.needsCore16, info.SnapName())
+ return nil
}
- // check that all bases/default-providers are part of the seed
- for _, info := range snapInfos {
- // ensure base is available
- if info.Base != "" && info.Base != "none" {
- if _, ok := snapInfos[info.Base]; !ok {
- errs = append(errs, fmt.Errorf("cannot use snap %q: base %q is missing", info.InstanceName(), info.Base))
- }
- }
- // ensure core is available
- if info.Base == "" && info.SnapType == snap.TypeApp && info.InstanceName() != "snapd" {
- if _, ok := snapInfos["core"]; !ok {
- errs = append(errs, fmt.Errorf(`cannot use snap %q: required snap "core" missing`, info.InstanceName()))
- }
- }
- // ensure default-providers are available
- for _, dp := range neededDefaultProviders(info) {
- if _, ok := snapInfos[dp]; !ok {
- errs = append(errs, fmt.Errorf("cannot use snap %q: default provider %q is missing", info.InstanceName(), dp))
- }
- }
- }
+ return fmt.Errorf("cannot add snap %q without also adding its base %q explicitly", info.InstanceName(), info.Base)
+}
- if errs != nil {
- var buf bytes.Buffer
- for _, err := range errs {
- fmt.Fprintf(&buf, "\n- %s", err)
- }
- return fmt.Errorf("cannot validate seed:%s", buf.Bytes())
+func (s *imageSeed) seedYaml() *seed.Seed16 {
+ var seedYaml seed.Seed16
+
+ sort.Stable(s.entries)
+
+ seedYaml.Snaps = make([]*seed.Snap16, len(s.entries))
+ for i, e := range s.entries {
+ seedYaml.Snaps[i] = e.snap
}
- return nil
+ return &seedYaml
+}
+
+func copyLocalSnapFile(snapPath, targetDir string, info *snap.Info) (dstPath string, err error) {
+ dst := filepath.Join(targetDir, filepath.Base(info.MountFile()))
+ return dst, osutil.CopyFile(snapPath, dst, 0)
}
diff -Nru snapd-2.41+19.10.1/image/image_test.go snapd-2.42.1+19.10/image/image_test.go
--- snapd-2.41+19.10.1/image/image_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/image/image_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -35,13 +35,15 @@
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/asserts/assertstest"
- "github.com/snapcore/snapd/boot/boottest"
"github.com/snapcore/snapd/bootloader"
+ "github.com/snapcore/snapd/bootloader/bootloadertest"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/image"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/overlord/auth"
"github.com/snapcore/snapd/progress"
+ "github.com/snapcore/snapd/seed"
+ "github.com/snapcore/snapd/seed/seedtest"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snaptest"
"github.com/snapcore/snapd/store"
@@ -67,18 +69,17 @@
type imageSuite struct {
testutil.BaseTest
root string
- bootloader *boottest.MockBootloader
+ bootloader *bootloadertest.MockBootloader
stdout *bytes.Buffer
stderr *bytes.Buffer
- downloadedSnaps map[string]string
- storeSnapInfo map[string]*snap.Info
- storeActions []*store.SnapAction
- tsto *image.ToolingStore
+ storeActions []*store.SnapAction
+ tsto *image.ToolingStore
- storeSigning *assertstest.StoreStack
- brands *assertstest.SigningAccounts
+ // SeedSnaps helps creating and making available seed snaps
+ // (it provides MakeAssertedSnap etc.) for the tests.
+ *seedtest.SeedSnaps
model *asserts.Model
}
@@ -91,7 +92,7 @@
func (s *imageSuite) SetUpTest(c *C) {
s.root = c.MkDir()
- s.bootloader = boottest.NewMockBootloader("grub", c.MkDir())
+ s.bootloader = bootloadertest.Mock("grub", c.MkDir())
bootloader.Force(s.bootloader)
s.BaseTest.SetUpTest(c)
@@ -101,19 +102,17 @@
image.Stdout = s.stdout
s.stderr = &bytes.Buffer{}
image.Stderr = s.stderr
- s.downloadedSnaps = make(map[string]string)
- s.storeSnapInfo = make(map[string]*snap.Info)
s.tsto = image.MockToolingStore(s)
- s.storeSigning = assertstest.NewStoreStack("canonical", nil)
-
- s.brands = assertstest.NewSigningAccounts(s.storeSigning)
- s.brands.Register("my-brand", brandPrivKey, map[string]interface{}{
+ s.SeedSnaps = &seedtest.SeedSnaps{}
+ s.SetupAssertSigning("canonical", s)
+ s.Brands.Register("my-brand", brandPrivKey, map[string]interface{}{
"verification": "verified",
})
- assertstest.AddMany(s.storeSigning, s.brands.AccountsAndKeys("my-brand")...)
+ assertstest.AddMany(s.StoreSigning, s.Brands.AccountsAndKeys("my-brand")...)
+ s.DB = s.StoreSigning.Database
- s.model = s.brands.Model("my-brand", "my-model", map[string]interface{}{
+ s.model = s.Brands.Model("my-brand", "my-model", map[string]interface{}{
"display-name": "my display name",
"architecture": "amd64",
"gadget": "pc",
@@ -121,10 +120,10 @@
"required-snaps": []interface{}{"required-snap1"},
})
- otherAcct := assertstest.NewAccount(s.storeSigning, "other", map[string]interface{}{
+ otherAcct := assertstest.NewAccount(s.StoreSigning, "other", map[string]interface{}{
"account-id": "other",
}, "")
- s.storeSigning.Add(otherAcct)
+ s.StoreSigning.Add(otherAcct)
// mock the mount cmds (for the extract kernel assets stuff)
c1 := testutil.MockCommand(c, "mount", "")
@@ -133,35 +132,6 @@
s.AddCleanup(c2.Restore)
}
-func (s *imageSuite) addSystemSnapAssertions(c *C, snapName string, publisher string) {
- snapID := snapName + "-Id"
- decl, err := s.storeSigning.Sign(asserts.SnapDeclarationType, map[string]interface{}{
- "series": "16",
- "snap-id": snapID,
- "snap-name": snapName,
- "publisher-id": publisher,
- "timestamp": time.Now().UTC().Format(time.RFC3339),
- }, nil, "")
- c.Assert(err, IsNil, Commentf("%s %s", snapName, publisher))
- err = s.storeSigning.Add(decl)
- c.Assert(err, IsNil)
-
- snapSHA3_384, snapSize, err := asserts.SnapFileSHA3_384(s.downloadedSnaps[snapName])
- c.Assert(err, IsNil)
-
- snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, map[string]interface{}{
- "snap-sha3-384": snapSHA3_384,
- "snap-size": fmt.Sprintf("%d", snapSize),
- "snap-id": snapID,
- "snap-revision": s.storeSnapInfo[snapName].Revision.String(),
- "developer-id": publisher,
- "timestamp": time.Now().UTC().Format(time.RFC3339),
- }, nil, "")
- c.Assert(err, IsNil)
- err = s.storeSigning.Add(snapRev)
- c.Assert(err, IsNil)
-}
-
func (s *imageSuite) TearDownTest(c *C) {
s.BaseTest.TearDownTest(c)
bootloader.Force(nil)
@@ -186,7 +156,7 @@
// record
s.storeActions = append(s.storeActions, actions[0])
- if info, ok := s.storeSnapInfo[actions[0].InstanceName]; ok {
+ if info := s.AssertedSnapInfo(actions[0].InstanceName); info != nil {
info.Channel = actions[0].Channel
return []*snap.Info{info}, nil
}
@@ -194,12 +164,12 @@
}
func (s *imageSuite) Download(ctx context.Context, name, targetFn string, downloadInfo *snap.DownloadInfo, pbar progress.Meter, user *auth.UserState, dlOpts *store.DownloadOptions) error {
- return osutil.CopyFile(s.downloadedSnaps[name], targetFn, 0)
+ return osutil.CopyFile(s.AssertedSnap(name), targetFn, 0)
}
func (s *imageSuite) Assertion(assertType *asserts.AssertionType, primaryKey []string, user *auth.UserState) (asserts.Assertion, error) {
ref := &asserts.Ref{Type: assertType, PrimaryKey: primaryKey}
- return ref.Resolve(s.storeSigning.Find)
+ return ref.Resolve(s.StoreSigning.Find)
}
const packageGadget = `
@@ -220,6 +190,13 @@
type: gadget
`
+const packageClassicGadget18 = `
+name: classic-gadget18
+version: 1.0
+type: gadget
+base: core18
+`
+
const packageKernel = `
name: pc-kernel
version: 4.4-1
@@ -241,7 +218,7 @@
const snapdSnap = `
name: snapd
version: 3.14
-type: application
+type: snapd
`
const otherBase = `
@@ -269,6 +246,12 @@
version: 1.0
`
+const requiredSnap18 = `
+name: required-snap18
+version: 1.0
+base: core18
+`
+
const snapReqOtherBase = `
name: snap-req-other-base
version: 1.0
@@ -480,153 +463,54 @@
c.Check(a.Type(), Equals, asserts.ModelType)
}
-func (s *imageSuite) TestMissingGadgetUnpackDir(c *C) {
- err := image.DownloadUnpackGadget(s.tsto, s.model, &image.Options{}, nil)
- c.Assert(err, ErrorMatches, `cannot create gadget unpack dir "": mkdir : no such file or directory`)
-}
-
-func infoFromSnapYaml(c *C, snapYaml string, rev snap.Revision) *snap.Info {
- info, err := snap.InfoFromSnapYaml([]byte(snapYaml))
- c.Assert(err, IsNil)
-
- if !rev.Unset() {
- info.SnapID = info.InstanceName() + "-Id"
- info.Revision = rev
+func (s *imageSuite) setupSnaps(c *C, gadgetUnpackDir string, publishers map[string]string) {
+ if _, ok := publishers["pc"]; ok {
+ s.MakeAssertedSnap(c, packageGadget, [][]string{{"grub.conf", ""}, {"grub.cfg", "I'm a grub.cfg"}}, snap.R(1), publishers["pc"])
}
- return info
-}
-
-func (s *imageSuite) TestDownloadUnpackGadget(c *C) {
- files := [][]string{
- {"subdir/canary.txt", "I'm a canary"},
+ if _, ok := publishers["pc18"]; ok {
+ s.MakeAssertedSnap(c, packageGadgetWithBase, [][]string{{"grub.conf", ""}, {"grub.cfg", "I'm a grub.cfg"}}, snap.R(4), publishers["pc18"])
}
- s.downloadedSnaps["pc"] = snaptest.MakeTestSnapWithFiles(c, packageGadget, files)
- s.storeSnapInfo["pc"] = infoFromSnapYaml(c, packageGadget, snap.R(99))
- gadgetUnpackDir := filepath.Join(c.MkDir(), "gadget-unpack-dir")
- opts := &image.Options{
- GadgetUnpackDir: gadgetUnpackDir,
+ if _, ok := publishers["classic-gadget"]; ok {
+ s.MakeAssertedSnap(c, packageClassicGadget, [][]string{{"some-file", "Some file"}}, snap.R(5), publishers["classic-gadget"])
}
- local, err := image.LocalSnaps(s.tsto, opts)
- c.Assert(err, IsNil)
- err = image.DownloadUnpackGadget(s.tsto, s.model, opts, local)
- c.Assert(err, IsNil)
-
- // verify the right data got unpacked
- for _, t := range []struct{ file, content string }{
- {"meta/snap.yaml", packageGadget},
- {files[0][0], files[0][1]},
- } {
- fn := filepath.Join(gadgetUnpackDir, t.file)
- c.Check(fn, testutil.FileEquals, t.content)
+ if _, ok := publishers["classic-gadget18"]; ok {
+ s.MakeAssertedSnap(c, packageClassicGadget18, [][]string{{"some-file", "Some file"}}, snap.R(5), publishers["classic-gadget18"])
}
-}
-
-func (s *imageSuite) TestDownloadUnpackGadgetFromTrack(c *C) {
- s.downloadedSnaps["pc"] = snaptest.MakeTestSnapWithFiles(c, packageGadget, nil)
- s.storeSnapInfo["pc"] = infoFromSnapYaml(c, packageGadget, snap.R(1818))
- model := s.brands.Model("my-brand", "my-model", map[string]interface{}{
+ if _, ok := publishers["pc-kernel"]; ok {
+ s.MakeAssertedSnap(c, packageKernel, nil, snap.R(2), publishers["pc-kernel"])
+ }
- "architecture": "amd64",
- "gadget": "pc=18",
- "kernel": "pc-kernel=18",
- })
+ s.MakeAssertedSnap(c, packageCore, nil, snap.R(3), "canonical")
- gadgetUnpackDir := filepath.Join(c.MkDir(), "gadget-unpack-dir")
- opts := &image.Options{
- GadgetUnpackDir: gadgetUnpackDir,
- }
- local, err := image.LocalSnaps(s.tsto, opts)
- c.Assert(err, IsNil)
+ s.MakeAssertedSnap(c, packageCore18, nil, snap.R(18), "canonical")
+ s.MakeAssertedSnap(c, snapdSnap, nil, snap.R(18), "canonical")
- err = image.DownloadUnpackGadget(s.tsto, model, opts, local)
- c.Assert(err, IsNil)
+ s.MakeAssertedSnap(c, otherBase, nil, snap.R(18), "other")
- c.Check(s.storeActions, HasLen, 1)
- c.Check(s.storeActions[0], DeepEquals, &store.SnapAction{
- Action: "download",
- Channel: "18/stable",
- InstanceName: "pc",
- })
+ s.MakeAssertedSnap(c, snapReqCore16Base, nil, snap.R(16), "other")
-}
+ s.MakeAssertedSnap(c, requiredSnap1, nil, snap.R(3), "other")
+ s.AssertedSnapInfo("required-snap1").Contact = "foo@example.com"
-func (s *imageSuite) setupSnaps(c *C, gadgetUnpackDir string, publishers map[string]string) {
- if gadgetUnpackDir != "" {
- err := os.MkdirAll(gadgetUnpackDir, 0755)
- c.Assert(err, IsNil)
- err = ioutil.WriteFile(filepath.Join(gadgetUnpackDir, "grub.conf"), nil, 0644)
- c.Assert(err, IsNil)
- }
+ s.MakeAssertedSnap(c, requiredSnap18, nil, snap.R(6), "other")
+ s.AssertedSnapInfo("required-snap18").Contact = "foo@example.com"
- if _, ok := publishers["pc"]; ok {
- s.downloadedSnaps["pc"] = snaptest.MakeTestSnapWithFiles(c, packageGadget, [][]string{{"grub.cfg", "I'm a grub.cfg"}})
- s.storeSnapInfo["pc"] = infoFromSnapYaml(c, packageGadget, snap.R(1))
- s.addSystemSnapAssertions(c, "pc", publishers["pc"])
- }
- if _, ok := publishers["pc18"]; ok {
- s.downloadedSnaps["pc18"] = snaptest.MakeTestSnapWithFiles(c, packageGadgetWithBase, [][]string{{"grub.cfg", "I'm a grub.cfg"}})
- s.storeSnapInfo["pc18"] = infoFromSnapYaml(c, packageGadgetWithBase, snap.R(4))
- s.addSystemSnapAssertions(c, "pc18", publishers["pc18"])
- }
+ s.MakeAssertedSnap(c, snapReqOtherBase, nil, snap.R(5), "other")
- if _, ok := publishers["classic-gadget"]; ok {
- s.downloadedSnaps["classic-gadget"] = snaptest.MakeTestSnapWithFiles(c, packageClassicGadget, [][]string{{"some-file", "Some file"}})
- s.storeSnapInfo["classic-gadget"] = infoFromSnapYaml(c, packageClassicGadget, snap.R(5))
- s.addSystemSnapAssertions(c, "classic-gadget", publishers["classic-gadget"])
- }
+ s.MakeAssertedSnap(c, snapReqContentProvider, nil, snap.R(5), "other")
- if _, ok := publishers["pc-kernel"]; ok {
- s.downloadedSnaps["pc-kernel"] = snaptest.MakeTestSnapWithFiles(c, packageKernel, nil)
- s.storeSnapInfo["pc-kernel"] = infoFromSnapYaml(c, packageKernel, snap.R(2))
- s.addSystemSnapAssertions(c, "pc-kernel", publishers["pc-kernel"])
- }
-
- s.downloadedSnaps["core"] = snaptest.MakeTestSnapWithFiles(c, packageCore, nil)
- s.storeSnapInfo["core"] = infoFromSnapYaml(c, packageCore, snap.R(3))
- s.addSystemSnapAssertions(c, "core", "canonical")
-
- s.downloadedSnaps["core18"] = snaptest.MakeTestSnapWithFiles(c, packageCore18, nil)
- s.storeSnapInfo["core18"] = infoFromSnapYaml(c, packageCore18, snap.R(18))
- s.addSystemSnapAssertions(c, "core18", "canonical")
-
- s.downloadedSnaps["snapd"] = snaptest.MakeTestSnapWithFiles(c, snapdSnap, nil)
- s.storeSnapInfo["snapd"] = infoFromSnapYaml(c, snapdSnap, snap.R(18))
- s.addSystemSnapAssertions(c, "snapd", "canonical")
-
- s.downloadedSnaps["other-base"] = snaptest.MakeTestSnapWithFiles(c, otherBase, nil)
- s.storeSnapInfo["other-base"] = infoFromSnapYaml(c, otherBase, snap.R(18))
- s.addSystemSnapAssertions(c, "other-base", "other")
-
- s.downloadedSnaps["snap-req-core16-base"] = snaptest.MakeTestSnapWithFiles(c, snapReqCore16Base, nil)
- s.storeSnapInfo["snap-req-core16-base"] = infoFromSnapYaml(c, snapReqCore16Base, snap.R(16))
- s.addSystemSnapAssertions(c, "snap-req-core16-base", "other")
-
- s.downloadedSnaps["required-snap1"] = snaptest.MakeTestSnapWithFiles(c, requiredSnap1, nil)
- s.storeSnapInfo["required-snap1"] = infoFromSnapYaml(c, requiredSnap1, snap.R(3))
- s.storeSnapInfo["required-snap1"].Contact = "foo@example.com"
- s.addSystemSnapAssertions(c, "required-snap1", "other")
-
- s.downloadedSnaps["snap-req-other-base"] = snaptest.MakeTestSnapWithFiles(c, snapReqOtherBase, nil)
- s.storeSnapInfo["snap-req-other-base"] = infoFromSnapYaml(c, snapReqOtherBase, snap.R(5))
- s.addSystemSnapAssertions(c, "snap-req-other-base", "other")
-
- s.downloadedSnaps["snap-req-content-provider"] = snaptest.MakeTestSnapWithFiles(c, snapReqContentProvider, nil)
- s.storeSnapInfo["snap-req-content-provider"] = infoFromSnapYaml(c, snapReqContentProvider, snap.R(5))
- s.addSystemSnapAssertions(c, "snap-req-content-provider", "other")
-
- s.downloadedSnaps["snap-base-none"] = snaptest.MakeTestSnapWithFiles(c, snapBaseNone, nil)
- s.storeSnapInfo["snap-base-none"] = infoFromSnapYaml(c, snapBaseNone, snap.R(1))
- s.addSystemSnapAssertions(c, "snap-base-none", "other")
+ s.MakeAssertedSnap(c, snapBaseNone, nil, snap.R(1), "other")
}
func (s *imageSuite) TestSetupSeed(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
rootdir := filepath.Join(c.MkDir(), "imageroot")
+ blobdir := filepath.Join(rootdir, "var/lib/snapd/snaps")
seeddir := filepath.Join(rootdir, "var/lib/snapd/seed")
seedsnapsdir := filepath.Join(seeddir, "snaps")
seedassertsdir := filepath.Join(seeddir, "assertions")
@@ -648,29 +532,33 @@
c.Assert(err, IsNil)
// check seed yaml
- seed, err := snap.ReadSeedYaml(filepath.Join(seeddir, "seed.yaml"))
+ seedYaml, err := seed.ReadYaml(filepath.Join(seeddir, "seed.yaml"))
c.Assert(err, IsNil)
- c.Check(seed.Snaps, HasLen, 4)
+ c.Check(seedYaml.Snaps, HasLen, 4)
// check the files are in place
for i, name := range []string{"core", "pc-kernel", "pc"} {
- info := s.storeSnapInfo[name]
+ info := s.AssertedSnapInfo(name)
fn := filepath.Base(info.MountFile())
p := filepath.Join(seedsnapsdir, fn)
c.Check(osutil.FileExists(p), Equals, true)
- c.Check(seed.Snaps[i], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps[i], DeepEquals, &seed.Snap16{
Name: name,
- SnapID: name + "-Id",
+ SnapID: s.AssertedSnapID(name),
File: fn,
})
+ // sanity
+ if name == "core" {
+ c.Check(seedYaml.Snaps[i].SnapID, Equals, "coreidididididididididididididid")
+ }
}
- c.Check(seed.Snaps[3].Name, Equals, "required-snap1")
- c.Check(seed.Snaps[3].Contact, Equals, "foo@example.com")
+ c.Check(seedYaml.Snaps[3].Name, Equals, "required-snap1")
+ c.Check(seedYaml.Snaps[3].Contact, Equals, "foo@example.com")
- storeAccountKey := s.storeSigning.StoreAccountKey("")
- brandPubKey := s.brands.PublicKey("my-brand")
+ storeAccountKey := s.StoreSigning.StoreAccountKey("")
+ brandPubKey := s.Brands.PublicKey("my-brand")
// check the assertions are in place
for _, fn := range []string{"model", brandPubKey.ID() + ".account-key", "my-brand.account", storeAccountKey.PublicKeyID() + ".account-key"} {
@@ -687,8 +575,8 @@
c.Check(a.HeaderString("account-id"), Equals, "my-brand")
// check the snap assertions are also in place
- for _, snapId := range []string{"pc-Id", "pc-kernel-Id", "core-Id"} {
- p := filepath.Join(seedassertsdir, fmt.Sprintf("16,%s.snap-declaration", snapId))
+ for _, snapName := range []string{"pc", "pc-kernel", "core"} {
+ p := filepath.Join(seedassertsdir, fmt.Sprintf("16,%s.snap-declaration", s.AssertedSnapID(snapName)))
c.Check(osutil.FileExists(p), Equals, true)
}
@@ -699,11 +587,41 @@
c.Check(m["snap_core"], Equals, "core_3.snap")
c.Check(m["snap_menuentry"], Equals, "my display name")
+ // check symlinks from snap blob dir
+ kernelInfo := s.AssertedSnapInfo("pc-kernel")
+ coreInfo := s.AssertedSnapInfo("core")
+ kernelBlob := filepath.Join(blobdir, filepath.Base(kernelInfo.MountFile()))
+ dst, err := os.Readlink(kernelBlob)
+ c.Assert(err, IsNil)
+ c.Check(dst, Equals, "../seed/snaps/pc-kernel_2.snap")
+ c.Check(kernelBlob, testutil.FilePresent)
+
+ coreBlob := filepath.Join(blobdir, filepath.Base(coreInfo.MountFile()))
+ dst, err = os.Readlink(coreBlob)
+ c.Assert(err, IsNil)
+ c.Check(dst, Equals, "../seed/snaps/core_3.snap")
+ c.Check(coreBlob, testutil.FilePresent)
+
c.Check(s.stderr.String(), Equals, "")
+
+ // check the downloads
+ c.Check(s.storeActions, HasLen, 4)
+ c.Check(s.storeActions[0], DeepEquals, &store.SnapAction{
+ Action: "download",
+ InstanceName: "core",
+ })
+ c.Check(s.storeActions[1], DeepEquals, &store.SnapAction{
+ Action: "download",
+ InstanceName: "pc-kernel",
+ })
+ c.Check(s.storeActions[2], DeepEquals, &store.SnapAction{
+ Action: "download",
+ InstanceName: "pc",
+ })
}
func (s *imageSuite) TestSetupSeedLocalCoreBrandKernel(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
rootdir := filepath.Join(c.MkDir(), "imageroot")
@@ -716,8 +634,8 @@
opts := &image.Options{
Snaps: []string{
- s.downloadedSnaps["core"],
- s.downloadedSnaps["required-snap1"],
+ s.AssertedSnap("core"),
+ s.AssertedSnap("required-snap1"),
},
RootDir: rootdir,
GadgetUnpackDir: gadgetUnpackDir,
@@ -730,15 +648,15 @@
c.Assert(err, IsNil)
// check seed yaml
- seed, err := snap.ReadSeedYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
+ seedYaml, err := seed.ReadYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
c.Assert(err, IsNil)
- c.Check(seed.Snaps, HasLen, 4)
+ c.Check(seedYaml.Snaps, HasLen, 4)
// check the files are in place
for i, name := range []string{"core_x1.snap", "pc-kernel", "pc", "required-snap1_x1.snap"} {
unasserted := false
- info := s.storeSnapInfo[name]
+ info := s.AssertedSnapInfo(name)
if info == nil {
switch name {
case "core_x1.snap":
@@ -764,7 +682,7 @@
p := filepath.Join(rootdir, "var/lib/snapd/seed/snaps", fn)
c.Check(osutil.FileExists(p), Equals, true)
- c.Check(seed.Snaps[i], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps[i], DeepEquals, &seed.Snap16{
Name: info.InstanceName(),
SnapID: info.SnapID,
File: fn,
@@ -776,8 +694,8 @@
c.Assert(err, IsNil)
c.Check(l, HasLen, 4)
- storeAccountKey := s.storeSigning.StoreAccountKey("")
- brandPubKey := s.brands.PublicKey("my-brand")
+ storeAccountKey := s.StoreSigning.StoreAccountKey("")
+ brandPubKey := s.Brands.PublicKey("my-brand")
// check the assertions are in place
for _, fn := range []string{"model", brandPubKey.ID() + ".account-key", "my-brand.account", storeAccountKey.PublicKeyID() + ".account-key"} {
@@ -805,11 +723,11 @@
c.Assert(err, IsNil)
c.Check(m["snap_core"], Equals, "core_x1.snap")
- 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")
+ c.Check(s.stderr.String(), Equals, "WARNING: \"core\", \"required-snap1\" installed from local snaps disconnected from a store cannot be refreshed subsequently!\n")
}
func (s *imageSuite) TestSetupSeedDevmodeSnap(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
rootdir := filepath.Join(c.MkDir(), "imageroot")
@@ -819,11 +737,10 @@
"pc-kernel": "canonical",
})
- s.downloadedSnaps["devmode-snap"] = snaptest.MakeTestSnapWithFiles(c, devmodeSnap, nil)
- s.storeSnapInfo["devmode-snap"] = infoFromSnapYaml(c, devmodeSnap, snap.R(0))
+ snapFile := snaptest.MakeTestSnapWithFiles(c, devmodeSnap, nil)
opts := &image.Options{
- Snaps: []string{s.downloadedSnaps["devmode-snap"]},
+ Snaps: []string{snapFile},
RootDir: rootdir,
GadgetUnpackDir: gadgetUnpackDir,
@@ -836,37 +753,37 @@
c.Assert(err, IsNil)
// check seed yaml
- seed, err := snap.ReadSeedYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
+ seedYaml, err := seed.ReadYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
c.Assert(err, IsNil)
- c.Check(seed.Snaps, HasLen, 5)
- c.Check(seed.Snaps[0], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps, HasLen, 5)
+ c.Check(seedYaml.Snaps[0], DeepEquals, &seed.Snap16{
Name: "core",
- SnapID: "core-Id",
+ SnapID: s.AssertedSnapID("core"),
File: "core_3.snap",
Channel: "beta",
})
- c.Check(seed.Snaps[1], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps[1], DeepEquals, &seed.Snap16{
Name: "pc-kernel",
- SnapID: "pc-kernel-Id",
+ SnapID: s.AssertedSnapID("pc-kernel"),
File: "pc-kernel_2.snap",
Channel: "beta",
})
- c.Check(seed.Snaps[2], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps[2], DeepEquals, &seed.Snap16{
Name: "pc",
- SnapID: "pc-Id",
+ SnapID: s.AssertedSnapID("pc"),
File: "pc_1.snap",
Channel: "beta",
})
- c.Check(seed.Snaps[3], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps[3], DeepEquals, &seed.Snap16{
Name: "required-snap1",
- SnapID: "required-snap1-Id",
+ SnapID: s.AssertedSnapID("required-snap1"),
File: "required-snap1_3.snap",
Contact: "foo@example.com",
Channel: "beta",
})
// ensure local snaps are put last in seed.yaml
- c.Check(seed.Snaps[4], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps[4], DeepEquals, &seed.Snap16{
Name: "devmode-snap",
DevMode: true,
Unasserted: true,
@@ -887,8 +804,8 @@
c.Check(osutil.FileExists(p), Equals, true)
// ensure local snaps are put last in seed.yaml
- last := len(seed.Snaps) - 1
- c.Check(seed.Snaps[last], DeepEquals, &snap.SeedSnap{
+ last := len(seedYaml.Snaps) - 1
+ c.Check(seedYaml.Snaps[last], DeepEquals, &seed.Snap16{
Name: "devmode-snap",
File: fn,
DevMode: true,
@@ -897,7 +814,7 @@
}
func (s *imageSuite) TestSetupSeedWithClassicSnapFails(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
rootdir := filepath.Join(c.MkDir(), "imageroot")
@@ -907,11 +824,10 @@
"pc-kernel": "canonical",
})
- s.downloadedSnaps["classic-snap"] = snaptest.MakeTestSnapWithFiles(c, classicSnap, nil)
- s.storeSnapInfo["classic-snap"] = infoFromSnapYaml(c, classicSnap, snap.R(0))
+ s.MakeAssertedSnap(c, classicSnap, nil, snap.R(1), "other")
opts := &image.Options{
- Snaps: []string{s.downloadedSnaps["classic-snap"]},
+ Snaps: []string{s.AssertedSnap("classic-snap")},
RootDir: rootdir,
GadgetUnpackDir: gadgetUnpackDir,
@@ -925,11 +841,11 @@
}
func (s *imageSuite) TestSetupSeedWithBase(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
// replace model with a model that uses core18
- model := s.brands.Model("my-brand", "my-model", map[string]interface{}{
+ model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
"architecture": "amd64",
"gadget": "pc18",
"kernel": "pc-kernel",
@@ -938,6 +854,7 @@
})
rootdir := filepath.Join(c.MkDir(), "imageroot")
+ blobdir := filepath.Join(rootdir, "/var/lib/snapd/snaps")
gadgetUnpackDir := c.MkDir()
s.setupSnaps(c, gadgetUnpackDir, map[string]string{
"core18": "canonical",
@@ -958,21 +875,21 @@
c.Assert(err, IsNil)
// check seed yaml
- seed, err := snap.ReadSeedYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
+ seedYaml, err := seed.ReadYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
c.Assert(err, IsNil)
- c.Check(seed.Snaps, HasLen, 5)
+ c.Check(seedYaml.Snaps, HasLen, 5)
// check the files are in place
- for i, name := range []string{"snapd", "core18_18.snap", "pc-kernel", "pc18", "other-base"} {
+ for i, name := range []string{"snapd", "pc-kernel", "core18_18.snap", "other-base", "pc18"} {
unasserted := false
- info := s.storeSnapInfo[name]
+ info := s.AssertedSnapInfo(name)
if info == nil {
switch name {
case "core18_18.snap":
info = &snap.Info{
SideInfo: snap.SideInfo{
- SnapID: "core18-Id",
+ SnapID: "core18ididididididididididididid",
RealName: "core18",
Revision: snap.R("18"),
},
@@ -984,7 +901,7 @@
p := filepath.Join(rootdir, "var/lib/snapd/seed/snaps", fn)
c.Check(osutil.FileExists(p), Equals, true)
- c.Check(seed.Snaps[i], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps[i], DeepEquals, &seed.Snap16{
Name: info.InstanceName(),
SnapID: info.SnapID,
File: fn,
@@ -1003,11 +920,130 @@
c.Assert(err, IsNil)
c.Check(m["snap_core"], Equals, "core18_18.snap")
+ // check symlinks from snap blob dir
+ kernelInfo := s.AssertedSnapInfo("pc-kernel")
+ baseInfo := s.AssertedSnapInfo("core18")
+ kernelBlob := filepath.Join(blobdir, filepath.Base(kernelInfo.MountFile()))
+ dst, err := os.Readlink(kernelBlob)
+ c.Assert(err, IsNil)
+ c.Check(dst, Equals, "../seed/snaps/pc-kernel_2.snap")
+ c.Check(kernelBlob, testutil.FilePresent)
+
+ baseBlob := filepath.Join(blobdir, filepath.Base(baseInfo.MountFile()))
+ dst, err = os.Readlink(baseBlob)
+ c.Assert(err, IsNil)
+ c.Check(dst, Equals, "../seed/snaps/core18_18.snap")
+ c.Check(baseBlob, testutil.FilePresent)
+
c.Check(s.stderr.String(), Equals, "")
+
+ // check the downloads
+ c.Check(s.storeActions, HasLen, 5)
+ c.Check(s.storeActions[0], DeepEquals, &store.SnapAction{
+ Action: "download",
+ InstanceName: "snapd",
+ })
+ c.Check(s.storeActions[1], DeepEquals, &store.SnapAction{
+ Action: "download",
+ InstanceName: "core18",
+ })
+ c.Check(s.storeActions[2], DeepEquals, &store.SnapAction{
+ Action: "download",
+ InstanceName: "pc-kernel",
+ })
+ c.Check(s.storeActions[3], DeepEquals, &store.SnapAction{
+ Action: "download",
+ InstanceName: "pc18",
+ })
+}
+
+func (s *imageSuite) TestSetupSeedWithBaseLegacySnap(c *C) {
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
+ defer restore()
+
+ // replace model with a model that uses core18
+ model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
+ "architecture": "amd64",
+ "gadget": "pc18",
+ "kernel": "pc-kernel",
+ "base": "core18",
+ "required-snaps": []interface{}{"required-snap1"},
+ })
+
+ // required-snap1 needs core, for backward compatibility
+ // we will add it implicitly but warn about this
+
+ rootdir := filepath.Join(c.MkDir(), "imageroot")
+ gadgetUnpackDir := c.MkDir()
+ s.setupSnaps(c, gadgetUnpackDir, map[string]string{
+ "core18": "canonical",
+ "pc18": "canonical",
+ "pc-kernel": "canonical",
+ "snapd": "canonical",
+ })
+
+ opts := &image.Options{
+ RootDir: rootdir,
+ GadgetUnpackDir: gadgetUnpackDir,
+ }
+ local, err := image.LocalSnaps(s.tsto, opts)
+ c.Assert(err, IsNil)
+
+ err = image.SetupSeed(s.tsto, model, opts, local)
+ c.Assert(err, IsNil)
+
+ // check seed yaml
+ seedYaml, err := seed.ReadYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
+ c.Assert(err, IsNil)
+
+ c.Check(seedYaml.Snaps, HasLen, 6)
+
+ // check the files are in place
+ for i, name := range []string{"snapd", "core", "pc-kernel", "core18_18.snap", "pc18"} {
+ unasserted := false
+ info := s.AssertedSnapInfo(name)
+ if info == nil {
+ switch name {
+ case "core18_18.snap":
+ info = &snap.Info{
+ SideInfo: snap.SideInfo{
+ SnapID: "core18ididididididididididididid",
+ RealName: "core18",
+ Revision: snap.R("18"),
+ },
+ }
+ }
+ }
+
+ fn := filepath.Base(info.MountFile())
+ p := filepath.Join(rootdir, "var/lib/snapd/seed/snaps", fn)
+ c.Check(osutil.FileExists(p), Equals, true)
+
+ c.Check(seedYaml.Snaps[i], DeepEquals, &seed.Snap16{
+ Name: info.InstanceName(),
+ SnapID: info.SnapID,
+ File: fn,
+ Unasserted: unasserted,
+ })
+ }
+ c.Check(seedYaml.Snaps[5].Name, Equals, "required-snap1")
+
+ l, err := ioutil.ReadDir(filepath.Join(rootdir, "var/lib/snapd/seed/snaps"))
+ c.Assert(err, IsNil)
+ c.Check(l, HasLen, 6)
+
+ // check the bootloader config
+ m, err := s.bootloader.GetBootVars("snap_kernel", "snap_core")
+ c.Assert(err, IsNil)
+ c.Check(m["snap_kernel"], Equals, "pc-kernel_2.snap")
+ c.Assert(err, IsNil)
+ c.Check(m["snap_core"], Equals, "core18_18.snap")
+
+ c.Check(s.stderr.String(), Equals, "WARNING: model has base \"core18\" but some snaps (\"required-snap1\") require \"core\" as base as well, for compatibility it was added implicitly, adding \"core\" explicitly is recommended\n")
}
func (s *imageSuite) TestSetupSeedKernelPublisherMismatch(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
rootdir := filepath.Join(c.MkDir(), "imageroot")
@@ -1092,7 +1128,7 @@
}
func (s *imageSuite) TestSetupSeedLocalSnapsWithStoreAsserts(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
rootdir := filepath.Join(c.MkDir(), "imageroot")
@@ -1105,8 +1141,8 @@
opts := &image.Options{
Snaps: []string{
- s.downloadedSnaps["core"],
- s.downloadedSnaps["required-snap1"],
+ s.AssertedSnap("core"),
+ s.AssertedSnap("required-snap1"),
},
RootDir: rootdir,
GadgetUnpackDir: gadgetUnpackDir,
@@ -1118,21 +1154,21 @@
c.Assert(err, IsNil)
// check seed yaml
- seed, err := snap.ReadSeedYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
+ seedYaml, err := seed.ReadYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
c.Assert(err, IsNil)
- c.Check(seed.Snaps, HasLen, 4)
+ c.Check(seedYaml.Snaps, HasLen, 4)
// check the files are in place
for i, name := range []string{"core_3.snap", "pc-kernel", "pc", "required-snap1_3.snap"} {
- info := s.storeSnapInfo[name]
+ info := s.AssertedSnapInfo(name)
if info == nil {
switch name {
case "core_3.snap":
info = &snap.Info{
SideInfo: snap.SideInfo{
RealName: "core",
- SnapID: "core-Id",
+ SnapID: s.AssertedSnapID("core"),
Revision: snap.R(3),
},
}
@@ -1140,7 +1176,7 @@
info = &snap.Info{
SideInfo: snap.SideInfo{
RealName: "required-snap1",
- SnapID: "required-snap1-Id",
+ SnapID: s.AssertedSnapID("required-snap1"),
Revision: snap.R(3),
},
}
@@ -1153,7 +1189,7 @@
p := filepath.Join(rootdir, "var/lib/snapd/seed/snaps", fn)
c.Check(osutil.FileExists(p), Equals, true, Commentf("cannot find %s", p))
- c.Check(seed.Snaps[i], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps[i], DeepEquals, &seed.Snap16{
Name: info.InstanceName(),
SnapID: info.SnapID,
File: fn,
@@ -1165,8 +1201,8 @@
c.Assert(err, IsNil)
c.Check(l, HasLen, 4)
- storeAccountKey := s.storeSigning.StoreAccountKey("")
- brandPubKey := s.brands.PublicKey("my-brand")
+ storeAccountKey := s.StoreSigning.StoreAccountKey("")
+ brandPubKey := s.Brands.PublicKey("my-brand")
// check the assertions are in place
for _, fn := range []string{"model", brandPubKey.ID() + ".account-key", "my-brand.account", storeAccountKey.PublicKeyID() + ".account-key"} {
@@ -1197,7 +1233,7 @@
}
func (s *imageSuite) TestSetupSeedLocalSnapsWithChannels(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
rootdir := filepath.Join(c.MkDir(), "imageroot")
@@ -1209,13 +1245,13 @@
opts := &image.Options{
Snaps: []string{
- s.downloadedSnaps["required-snap1"],
+ s.AssertedSnap("required-snap1"),
},
RootDir: rootdir,
GadgetUnpackDir: gadgetUnpackDir,
SnapChannels: map[string]string{
"core": "candidate",
- s.downloadedSnaps["required-snap1"]: "edge",
+ s.AssertedSnap("required-snap1"): "edge",
},
}
local, err := image.LocalSnaps(s.tsto, opts)
@@ -1225,21 +1261,21 @@
c.Assert(err, IsNil)
// check seed yaml
- seed, err := snap.ReadSeedYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
+ seedYaml, err := seed.ReadYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
c.Assert(err, IsNil)
- c.Check(seed.Snaps, HasLen, 4)
+ c.Check(seedYaml.Snaps, HasLen, 4)
// check the files are in place
for i, name := range []string{"core_3.snap", "pc-kernel", "pc", "required-snap1_3.snap"} {
- info := s.storeSnapInfo[name]
+ info := s.AssertedSnapInfo(name)
if info == nil {
switch name {
case "core_3.snap":
info = &snap.Info{
SideInfo: snap.SideInfo{
RealName: "core",
- SnapID: "core-Id",
+ SnapID: s.AssertedSnapID("core"),
Revision: snap.R(3),
Channel: "candidate",
},
@@ -1248,7 +1284,7 @@
info = &snap.Info{
SideInfo: snap.SideInfo{
RealName: "required-snap1",
- SnapID: "required-snap1-Id",
+ SnapID: s.AssertedSnapID("required-snap1"),
Revision: snap.R(3),
Channel: "edge",
},
@@ -1262,7 +1298,7 @@
p := filepath.Join(rootdir, "var/lib/snapd/seed/snaps", fn)
c.Check(osutil.FileExists(p), Equals, true, Commentf("cannot find %s", p))
- c.Check(seed.Snaps[i], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps[i], DeepEquals, &seed.Snap16{
Name: info.InstanceName(),
SnapID: info.SnapID,
Channel: info.Channel,
@@ -1276,6 +1312,18 @@
c.Check(l, HasLen, 4)
}
+func (s *imageSuite) TestMissingGadgetUnpackDir(c *C) {
+ fn := filepath.Join(c.MkDir(), "model.assertion")
+ err := ioutil.WriteFile(fn, asserts.Encode(s.model), 0644)
+ c.Assert(err, IsNil)
+
+ err = image.Prepare(&image.Options{
+ ModelFile: fn,
+ Channel: "stable",
+ })
+ c.Assert(err, ErrorMatches, `cannot create gadget unpack dir "": mkdir : no such file or directory`)
+}
+
func (s *imageSuite) TestNoLocalParallelSnapInstances(c *C) {
fn := filepath.Join(c.MkDir(), "model.assertion")
err := ioutil.WriteFile(fn, asserts.Encode(s.model), 0644)
@@ -1325,10 +1373,10 @@
}
func (s *imageSuite) TestPrepareClassicModelNoClassicMode(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
- model := s.brands.Model("my-brand", "my-model", map[string]interface{}{
+ model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
"classic": "true",
})
@@ -1343,10 +1391,10 @@
}
func (s *imageSuite) TestPrepareClassicModelArchOverrideFails(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
- model := s.brands.Model("my-brand", "my-model", map[string]interface{}{
+ model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
"classic": "true",
"architecture": "amd64",
})
@@ -1364,10 +1412,10 @@
}
func (s *imageSuite) TestPrepareClassicModelSnapsButNoArchFails(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
- model := s.brands.Model("my-brand", "my-model", map[string]interface{}{
+ model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
"classic": "true",
"gadget": "classic-gadget",
})
@@ -1384,11 +1432,11 @@
}
func (s *imageSuite) TestSetupSeedWithKernelAndGadgetTrack(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
// replace model with a model that uses core18
- model := s.brands.Model("my-brand", "my-model", map[string]interface{}{
+ model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
"architecture": "amd64",
"gadget": "pc=18",
"kernel": "pc-kernel=18",
@@ -1405,6 +1453,7 @@
opts := &image.Options{
RootDir: rootdir,
GadgetUnpackDir: gadgetUnpackDir,
+ Channel: "stable",
}
local, err := image.LocalSnaps(s.tsto, opts)
c.Assert(err, IsNil)
@@ -1413,35 +1462,54 @@
c.Assert(err, IsNil)
// check seed yaml
- seed, err := snap.ReadSeedYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
+ seedYaml, err := seed.ReadYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
c.Assert(err, IsNil)
- c.Check(seed.Snaps, HasLen, 3)
- c.Check(seed.Snaps[0], DeepEquals, &snap.SeedSnap{
- Name: "core",
- SnapID: "core-Id",
- File: "core_3.snap",
+ c.Check(seedYaml.Snaps, HasLen, 3)
+ c.Check(seedYaml.Snaps[0], DeepEquals, &seed.Snap16{
+ Name: "core",
+ SnapID: s.AssertedSnapID("core"),
+ File: "core_3.snap",
+ Channel: "stable",
})
- c.Check(seed.Snaps[1], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps[1], DeepEquals, &seed.Snap16{
Name: "pc-kernel",
- SnapID: "pc-kernel-Id",
+ SnapID: s.AssertedSnapID("pc-kernel"),
File: "pc-kernel_2.snap",
Channel: "18/stable",
})
- c.Check(seed.Snaps[2], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps[2], DeepEquals, &seed.Snap16{
Name: "pc",
- SnapID: "pc-Id",
+ SnapID: s.AssertedSnapID("pc"),
File: "pc_1.snap",
Channel: "18/stable",
})
+
+ // check the downloads
+ c.Check(s.storeActions, HasLen, 3)
+ c.Check(s.storeActions[0], DeepEquals, &store.SnapAction{
+ Action: "download",
+ InstanceName: "core",
+ Channel: "stable",
+ })
+ c.Check(s.storeActions[1], DeepEquals, &store.SnapAction{
+ Action: "download",
+ InstanceName: "pc-kernel",
+ Channel: "18/stable",
+ })
+ c.Check(s.storeActions[2], DeepEquals, &store.SnapAction{
+ Action: "download",
+ InstanceName: "pc",
+ Channel: "18/stable",
+ })
}
func (s *imageSuite) TestSetupSeedWithKernelTrackWithDefaultChannel(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
// replace model with a model that uses core18
- model := s.brands.Model("my-brand", "my-model", map[string]interface{}{
+ model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
"architecture": "amd64",
"gadget": "pc",
"kernel": "pc-kernel=18",
@@ -1467,36 +1535,36 @@
c.Assert(err, IsNil)
// check seed yaml
- seed, err := snap.ReadSeedYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
+ seedYaml, err := seed.ReadYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
c.Assert(err, IsNil)
- c.Check(seed.Snaps, HasLen, 3)
- c.Check(seed.Snaps[0], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps, HasLen, 3)
+ c.Check(seedYaml.Snaps[0], DeepEquals, &seed.Snap16{
Name: "core",
- SnapID: "core-Id",
+ SnapID: s.AssertedSnapID("core"),
File: "core_3.snap",
Channel: "edge",
})
- c.Check(seed.Snaps[1], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps[1], DeepEquals, &seed.Snap16{
Name: "pc-kernel",
- SnapID: "pc-kernel-Id",
+ SnapID: s.AssertedSnapID("pc-kernel"),
File: "pc-kernel_2.snap",
Channel: "18/edge",
})
- c.Check(seed.Snaps[2], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps[2], DeepEquals, &seed.Snap16{
Name: "pc",
- SnapID: "pc-Id",
+ SnapID: s.AssertedSnapID("pc"),
File: "pc_1.snap",
Channel: "edge",
})
}
func (s *imageSuite) TestSetupSeedWithKernelTrackOnLocalSnap(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
// replace model with a model that uses core18
- model := s.brands.Model("my-brand", "my-model", map[string]interface{}{
+ model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
"architecture": "amd64",
"gadget": "pc",
"kernel": "pc-kernel=18",
@@ -1511,8 +1579,8 @@
})
// pretend we downloaded the core,kernel already
- cfn := s.downloadedSnaps["core"]
- kfn := s.downloadedSnaps["pc-kernel"]
+ cfn := s.AssertedSnap("core")
+ kfn := s.AssertedSnap("pc-kernel")
opts := &image.Options{
RootDir: rootdir,
GadgetUnpackDir: gadgetUnpackDir,
@@ -1527,30 +1595,30 @@
c.Assert(err, IsNil)
// check seed yaml
- seed, err := snap.ReadSeedYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
+ seedYaml, err := seed.ReadYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
c.Assert(err, IsNil)
- c.Check(seed.Snaps, HasLen, 3)
- c.Check(seed.Snaps[0], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps, HasLen, 3)
+ c.Check(seedYaml.Snaps[0], DeepEquals, &seed.Snap16{
Name: "core",
- SnapID: "core-Id",
+ SnapID: s.AssertedSnapID("core"),
File: "core_3.snap",
Channel: "beta",
})
- c.Check(seed.Snaps[1], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps[1], DeepEquals, &seed.Snap16{
Name: "pc-kernel",
- SnapID: "pc-kernel-Id",
+ SnapID: s.AssertedSnapID("pc-kernel"),
File: "pc-kernel_2.snap",
Channel: "18/beta",
})
}
func (s *imageSuite) TestSetupSeedWithBaseAndLocalLegacyCoreOrdering(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
// replace model with a model that uses core18
- model := s.brands.Model("my-brand", "my-model", map[string]interface{}{
+ model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
"architecture": "amd64",
"base": "core18",
"gadget": "pc18",
@@ -1570,7 +1638,7 @@
RootDir: rootdir,
GadgetUnpackDir: gadgetUnpackDir,
Snaps: []string{
- s.downloadedSnaps["core"],
+ s.AssertedSnap("core"),
},
}
emptyToolingStore := image.MockToolingStore(&emptyStore{})
@@ -1581,49 +1649,49 @@
c.Assert(err, IsNil)
// check seed yaml
- seed, err := snap.ReadSeedYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
+ seedYaml, err := seed.ReadYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
c.Assert(err, IsNil)
- c.Check(seed.Snaps, HasLen, 6)
- c.Check(seed.Snaps[0], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps, HasLen, 6)
+ c.Check(seedYaml.Snaps[0], DeepEquals, &seed.Snap16{
Name: "snapd",
- SnapID: "snapd-Id",
+ SnapID: s.AssertedSnapID("snapd"),
File: "snapd_18.snap",
})
- c.Check(seed.Snaps[1], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps[1], DeepEquals, &seed.Snap16{
Name: "core",
Unasserted: true,
File: "core_x1.snap",
})
- c.Check(seed.Snaps[2], DeepEquals, &snap.SeedSnap{
- Name: "core18",
- SnapID: "core18-Id",
- File: "core18_18.snap",
- })
- c.Check(seed.Snaps[3], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps[2], DeepEquals, &seed.Snap16{
Name: "pc-kernel",
- SnapID: "pc-kernel-Id",
+ SnapID: s.AssertedSnapID("pc-kernel"),
File: "pc-kernel_2.snap",
})
- c.Check(seed.Snaps[4], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps[3], DeepEquals, &seed.Snap16{
+ Name: "core18",
+ SnapID: s.AssertedSnapID("core18"),
+ File: "core18_18.snap",
+ })
+ c.Check(seedYaml.Snaps[4], DeepEquals, &seed.Snap16{
Name: "pc18",
- SnapID: "pc18-Id",
+ SnapID: s.AssertedSnapID("pc18"),
File: "pc18_4.snap",
})
- c.Check(seed.Snaps[5], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps[5], DeepEquals, &seed.Snap16{
Name: "required-snap1",
- SnapID: "required-snap1-Id",
+ SnapID: s.AssertedSnapID("required-snap1"),
File: "required-snap1_3.snap",
Contact: "foo@example.com",
})
}
func (s *imageSuite) TestSetupSeedWithBaseAndLegacyCoreOrdering(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
// replace model with a model that uses core18
- model := s.brands.Model("my-brand", "my-model", map[string]interface{}{
+ model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
"architecture": "amd64",
"base": "core18",
"gadget": "pc18",
@@ -1651,49 +1719,49 @@
c.Assert(err, IsNil)
// check seed yaml
- seed, err := snap.ReadSeedYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
+ seedYaml, err := seed.ReadYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
c.Assert(err, IsNil)
- c.Check(seed.Snaps, HasLen, 6)
- c.Check(seed.Snaps[0], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps, HasLen, 6)
+ c.Check(seedYaml.Snaps[0], DeepEquals, &seed.Snap16{
Name: "snapd",
- SnapID: "snapd-Id",
+ SnapID: s.AssertedSnapID("snapd"),
File: "snapd_18.snap",
})
- c.Check(seed.Snaps[1], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps[1], DeepEquals, &seed.Snap16{
Name: "core",
- SnapID: "core-Id",
+ SnapID: s.AssertedSnapID("core"),
File: "core_3.snap",
})
- c.Check(seed.Snaps[2], DeepEquals, &snap.SeedSnap{
- Name: "core18",
- SnapID: "core18-Id",
- File: "core18_18.snap",
- })
- c.Check(seed.Snaps[3], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps[2], DeepEquals, &seed.Snap16{
Name: "pc-kernel",
- SnapID: "pc-kernel-Id",
+ SnapID: s.AssertedSnapID("pc-kernel"),
File: "pc-kernel_2.snap",
})
- c.Check(seed.Snaps[4], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps[3], DeepEquals, &seed.Snap16{
+ Name: "core18",
+ SnapID: s.AssertedSnapID("core18"),
+ File: "core18_18.snap",
+ })
+ c.Check(seedYaml.Snaps[4], DeepEquals, &seed.Snap16{
Name: "pc18",
- SnapID: "pc18-Id",
+ SnapID: s.AssertedSnapID("pc18"),
File: "pc18_4.snap",
})
- c.Check(seed.Snaps[5], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps[5], DeepEquals, &seed.Snap16{
Name: "required-snap1",
- SnapID: "required-snap1-Id",
+ SnapID: s.AssertedSnapID("required-snap1"),
File: "required-snap1_3.snap",
Contact: "foo@example.com",
})
}
func (s *imageSuite) TestSetupSeedGadgetBaseModelBaseMismatch(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
// replace model with a model that uses core18 and a gadget
// without a base
- model := s.brands.Model("my-brand", "my-model", map[string]interface{}{
+ model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
"architecture": "amd64",
"base": "core18",
"gadget": "pc",
@@ -1719,9 +1787,9 @@
}
func (s *imageSuite) TestSetupSeedSnapReqBase(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
- model := s.brands.Model("my-brand", "my-model", map[string]interface{}{
+ model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
"architecture": "amd64",
"gadget": "pc",
"kernel": "pc-kernel",
@@ -1747,9 +1815,9 @@
}
func (s *imageSuite) TestSetupSeedBaseNone(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
- model := s.brands.Model("my-brand", "my-model", map[string]interface{}{
+ model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
"architecture": "amd64",
"gadget": "pc",
"kernel": "pc-kernel",
@@ -1774,9 +1842,9 @@
}
func (s *imageSuite) TestSetupSeedSnapCoreSatisfiesCore16(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
- model := s.brands.Model("my-brand", "my-model", map[string]interface{}{
+ model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
"architecture": "amd64",
"gadget": "pc",
"kernel": "pc-kernel",
@@ -1802,9 +1870,9 @@
}
func (s *imageSuite) TestSetupSeedStoreAssertionMissing(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
- model := s.brands.Model("my-brand", "my-model", map[string]interface{}{
+ model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
"architecture": "amd64",
"gadget": "pc",
"kernel": "pc-kernel",
@@ -1829,20 +1897,20 @@
}
func (s *imageSuite) TestSetupSeedStoreAssertionFetched(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
// add store assertion
- storeAs, err := s.storeSigning.Sign(asserts.StoreType, map[string]interface{}{
+ storeAs, err := s.StoreSigning.Sign(asserts.StoreType, map[string]interface{}{
"store": "my-store",
"operator-id": "canonical",
"timestamp": time.Now().UTC().Format(time.RFC3339),
}, nil, "")
c.Assert(err, IsNil)
- err = s.storeSigning.Add(storeAs)
+ err = s.StoreSigning.Add(storeAs)
c.Assert(err, IsNil)
- model := s.brands.Model("my-brand", "my-model", map[string]interface{}{
+ model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
"architecture": "amd64",
"gadget": "pc",
"kernel": "pc-kernel",
@@ -1874,9 +1942,9 @@
}
func (s *imageSuite) TestSetupSeedSnapReqBaseFromLocal(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
- model := s.brands.Model("my-brand", "my-model", map[string]interface{}{
+ model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
"architecture": "amd64",
"gadget": "pc",
"kernel": "pc-kernel",
@@ -1892,7 +1960,7 @@
"snap-req-other-base": "canonical",
"other-base": "canonical",
})
- bfn := s.downloadedSnaps["other-base"]
+ bfn := s.AssertedSnap("other-base")
opts := &image.Options{
RootDir: rootdir,
GadgetUnpackDir: gadgetUnpackDir,
@@ -1905,9 +1973,9 @@
}
func (s *imageSuite) TestSetupSeedMissingContentProvider(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
- model := s.brands.Model("my-brand", "my-model", map[string]interface{}{
+ model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
"architecture": "amd64",
"gadget": "pc",
"kernel": "pc-kernel",
@@ -1942,11 +2010,11 @@
}
func (s *imageSuite) TestSetupSeedClassic(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
// classic model with gadget etc
- model := s.brands.Model("my-brand", "my-model", map[string]interface{}{
+ model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
"classic": "true",
"architecture": "amd64",
"gadget": "classic-gadget",
@@ -1969,21 +2037,21 @@
c.Assert(err, IsNil)
// check seed yaml
- seed, err := snap.ReadSeedYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
+ seedYaml, err := seed.ReadYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
c.Assert(err, IsNil)
- c.Check(seed.Snaps, HasLen, 3)
+ c.Check(seedYaml.Snaps, HasLen, 3)
// check the files are in place
for i, name := range []string{"core", "classic-gadget", "required-snap1"} {
unasserted := false
- info := s.storeSnapInfo[name]
+ info := s.AssertedSnapInfo(name)
fn := filepath.Base(info.MountFile())
p := filepath.Join(rootdir, "var/lib/snapd/seed/snaps", fn)
c.Check(osutil.FileExists(p), Equals, true)
- c.Check(seed.Snaps[i], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps[i], DeepEquals, &seed.Snap16{
Name: info.InstanceName(),
SnapID: info.SnapID,
File: fn,
@@ -2011,12 +2079,12 @@
c.Check(osutil.FileExists(blobdir), Equals, false)
}
-func (s *imageSuite) TestSetupSeedClassicWithClassicSnap(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+func (s *imageSuite) TestSetupSeedClassicWithLocalClassicSnap(c *C) {
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
// classic model
- model := s.brands.Model("my-brand", "my-model", map[string]interface{}{
+ model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
"classic": "true",
"architecture": "amd64",
})
@@ -2024,12 +2092,11 @@
rootdir := filepath.Join(c.MkDir(), "classic-image-root")
s.setupSnaps(c, "", nil)
- s.downloadedSnaps["classic-snap"] = snaptest.MakeTestSnapWithFiles(c, classicSnap, nil)
- s.storeSnapInfo["classic-snap"] = infoFromSnapYaml(c, classicSnap, snap.R(0))
+ snapFile := snaptest.MakeTestSnapWithFiles(c, classicSnap, nil)
opts := &image.Options{
Classic: true,
- Snaps: []string{s.downloadedSnaps["classic-snap"]},
+ Snaps: []string{snapFile},
RootDir: rootdir,
}
local, err := image.LocalSnaps(s.tsto, opts)
@@ -2039,22 +2106,22 @@
c.Assert(err, IsNil)
// check seed yaml
- seed, err := snap.ReadSeedYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
+ seedYaml, err := seed.ReadYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
c.Assert(err, IsNil)
- c.Check(seed.Snaps, HasLen, 2)
+ c.Check(seedYaml.Snaps, HasLen, 2)
p := filepath.Join(rootdir, "var/lib/snapd/seed/snaps", "core_3.snap")
c.Check(osutil.FileExists(p), Equals, true)
- c.Check(seed.Snaps[0], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps[0], DeepEquals, &seed.Snap16{
Name: "core",
- SnapID: "core-Id",
+ SnapID: s.AssertedSnapID("core"),
File: "core_3.snap",
})
p = filepath.Join(rootdir, "var/lib/snapd/seed/snaps", "classic-snap_x1.snap")
c.Check(osutil.FileExists(p), Equals, true)
- c.Check(seed.Snaps[1], DeepEquals, &snap.SeedSnap{
+ c.Check(seedYaml.Snaps[1], DeepEquals, &seed.Snap16{
Name: "classic-snap",
File: "classic-snap_x1.snap",
Classic: true,
@@ -2074,12 +2141,82 @@
})
}
+func (s *imageSuite) TestSetupSeedClassicSnapdOnly(c *C) {
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
+ defer restore()
+
+ // classic model with gadget etc
+ model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
+ "classic": "true",
+ "architecture": "amd64",
+ "gadget": "classic-gadget18",
+ "required-snaps": []interface{}{"core18", "required-snap18"},
+ })
+
+ rootdir := filepath.Join(c.MkDir(), "classic-image-root")
+ s.setupSnaps(c, "", map[string]string{
+ "classic-gadget18": "my-brand",
+ })
+
+ opts := &image.Options{
+ Classic: true,
+ RootDir: rootdir,
+ }
+ local, err := image.LocalSnaps(s.tsto, opts)
+ c.Assert(err, IsNil)
+
+ err = image.SetupSeed(s.tsto, model, opts, local)
+ c.Assert(err, IsNil)
+
+ // check seed yaml
+ seedYaml, err := seed.ReadYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
+ c.Assert(err, IsNil)
+
+ c.Check(seedYaml.Snaps, HasLen, 4)
+
+ // check the files are in place
+ for i, name := range []string{"snapd", "core18", "classic-gadget18", "required-snap18"} {
+ unasserted := false
+ info := s.AssertedSnapInfo(name)
+
+ fn := filepath.Base(info.MountFile())
+ p := filepath.Join(rootdir, "var/lib/snapd/seed/snaps", fn)
+ c.Check(osutil.FileExists(p), Equals, true)
+
+ c.Check(seedYaml.Snaps[i], DeepEquals, &seed.Snap16{
+ Name: info.InstanceName(),
+ SnapID: info.SnapID,
+ File: fn,
+ Contact: info.Contact,
+ Unasserted: unasserted,
+ })
+ }
+
+ l, err := ioutil.ReadDir(filepath.Join(rootdir, "var/lib/snapd/seed/snaps"))
+ c.Assert(err, IsNil)
+ c.Check(l, HasLen, 4)
+
+ // check that the bootloader is unset
+ m, err := s.bootloader.GetBootVars("snap_kernel", "snap_core")
+ c.Assert(err, IsNil)
+ c.Check(m, DeepEquals, map[string]string{
+ "snap_core": "",
+ "snap_kernel": "",
+ })
+
+ c.Check(s.stderr.String(), Matches, `WARNING: ensure that the contents under .*/var/lib/snapd/seed are owned by root:root in the \(final\) image`)
+
+ // no blob dir created
+ blobdir := filepath.Join(rootdir, "var/lib/snapd/snaps")
+ c.Check(osutil.FileExists(blobdir), Equals, false)
+}
+
func (s *imageSuite) TestSetupSeedClassicNoSnaps(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
// classic model with gadget etc
- model := s.brands.Model("my-brand", "my-model", map[string]interface{}{
+ model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
"classic": "true",
})
@@ -2096,10 +2233,10 @@
c.Assert(err, IsNil)
// check seed yaml
- seed, err := snap.ReadSeedYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
+ seedYaml, err := seed.ReadYaml(filepath.Join(rootdir, "var/lib/snapd/seed/seed.yaml"))
c.Assert(err, IsNil)
- c.Check(seed.Snaps, HasLen, 0)
+ c.Check(seedYaml.Snaps, HasLen, 0)
l, err := ioutil.ReadDir(filepath.Join(rootdir, "var/lib/snapd/seed/snaps"))
c.Assert(err, IsNil)
@@ -2118,8 +2255,36 @@
c.Check(osutil.FileExists(blobdir), Equals, false)
}
+func (s *imageSuite) TestSetupSeedClassicSnapdOnlyMissingCore16(c *C) {
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
+ defer restore()
+
+ // classic model with gadget etc
+ model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
+ "classic": "true",
+ "architecture": "amd64",
+ "gadget": "classic-gadget18",
+ "required-snaps": []interface{}{"core18", "snap-req-core16-base"},
+ })
+
+ rootdir := filepath.Join(c.MkDir(), "classic-image-root")
+ s.setupSnaps(c, "", map[string]string{
+ "classic-gadget18": "my-brand",
+ })
+
+ opts := &image.Options{
+ Classic: true,
+ RootDir: rootdir,
+ }
+ local, err := image.LocalSnaps(s.tsto, opts)
+ c.Assert(err, IsNil)
+
+ err = image.SetupSeed(s.tsto, model, opts, local)
+ c.Assert(err, ErrorMatches, `cannot use "snap-req-core16-base" requiring base "core16" without adding "core16" \(or "core"\) explicitly`)
+}
+
func (s *imageSuite) TestSnapChannel(c *C) {
- model := s.brands.Model("my-brand", "my-model", map[string]interface{}{
+ model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
"architecture": "amd64",
"gadget": "pc=18",
"kernel": "pc-kernel=18",
@@ -2159,14 +2324,19 @@
opts.SnapChannels["pc-kernel"] = "lts/candidate"
_, err = image.SnapChannel("pc-kernel", model, opts, local)
c.Assert(err, ErrorMatches, `channel "lts/candidate" for kernel has a track incompatible with the track from model assertion: 18`)
+
+ opts.SnapChannels["pc-kernel"] = "track/foo"
+ _, err = image.SnapChannel("pc-kernel", model, opts, local)
+ c.Assert(err, ErrorMatches, `cannot use option channel for snap "pc-kernel": invalid risk in channel name: track/foo`)
+
}
func (s *imageSuite) TestSetupSeedLocalSnapd(c *C) {
- restore := image.MockTrusted(s.storeSigning.Trusted)
+ restore := image.MockTrusted(s.StoreSigning.Trusted)
defer restore()
// replace model with a model that uses core18
- model := s.brands.Model("my-brand", "my-model", map[string]interface{}{
+ model := s.Brands.Model("my-brand", "my-model", map[string]interface{}{
"architecture": "amd64",
"gadget": "pc18",
"kernel": "pc-kernel",
@@ -2182,8 +2352,8 @@
opts := &image.Options{
Snaps: []string{
- s.downloadedSnaps["snapd"],
- s.downloadedSnaps["core18"],
+ s.AssertedSnap("snapd"),
+ s.AssertedSnap("core18"),
},
RootDir: rootdir,
diff -Nru snapd-2.41+19.10.1/image/validate_seed_test.go snapd-2.42.1+19.10/image/validate_seed_test.go
--- snapd-2.41+19.10.1/image/validate_seed_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/image/validate_seed_test.go 1970-01-01 00:00:00.000000000 +0000
@@ -1,257 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2019 Canonical Ltd
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-
-package image_test
-
-import (
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
-
- . "gopkg.in/check.v1"
-
- "github.com/snapcore/snapd/image"
- "github.com/snapcore/snapd/snap"
- "github.com/snapcore/snapd/snap/snaptest"
- "github.com/snapcore/snapd/snap/squashfs"
- "github.com/snapcore/snapd/testutil"
-)
-
-type validateSuite struct {
- testutil.BaseTest
- root string
-}
-
-var _ = Suite(&validateSuite{})
-
-var coreYaml = `name: core
-version: 1.0
-type: os`
-
-var snapdYaml = `name: snapd
-version: 1.0
-type: snapd`
-
-func (s *validateSuite) SetUpTest(c *C) {
- s.BaseTest.SetUpTest(c)
- s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
-
- s.root = c.MkDir()
-
- err := os.MkdirAll(filepath.Join(s.root, "snaps"), 0755)
- c.Assert(err, IsNil)
-}
-
-func (s *validateSuite) makeSnapInSeed(c *C, snapYaml string) {
- info := infoFromSnapYaml(c, snapYaml, snap.R(1))
-
- src := snaptest.MakeTestSnapWithFiles(c, snapYaml, nil)
- dst := filepath.Join(s.root, "snaps", fmt.Sprintf("%s_%s.snap", info.InstanceName(), info.Revision.String()))
-
- err := os.Rename(src, dst)
- c.Assert(err, IsNil)
-}
-
-func (s *validateSuite) makeSeedYaml(c *C, seedYaml string) string {
- tmpf := filepath.Join(s.root, "seed.yaml")
- err := ioutil.WriteFile(tmpf, []byte(seedYaml), 0644)
- c.Assert(err, IsNil)
- return tmpf
-}
-
-func (s *validateSuite) TestValidateSnapHappy(c *C) {
- s.makeSnapInSeed(c, coreYaml)
- s.makeSnapInSeed(c, `name: gtk-common-themes
-version: 19.04`)
- seedFn := s.makeSeedYaml(c, `
-snaps:
- - name: core
- channel: stable
- file: core_1.snap
- - name: gtk-common-themes
- channel: stable/ubuntu-19.04
- file: gtk-common-themes_1.snap
-`)
-
- err := image.ValidateSeed(seedFn)
- c.Assert(err, IsNil)
-}
-
-func (s *validateSuite) TestValidateSnapMissingBase(c *C) {
- s.makeSnapInSeed(c, `name: need-base
-base: some-base
-version: 1.0`)
- s.makeSnapInSeed(c, coreYaml)
- seedFn := s.makeSeedYaml(c, `
-snaps:
- - name: core
- file: core_1.snap
- - name: need-base
- file: need-base_1.snap
-`)
-
- err := image.ValidateSeed(seedFn)
- c.Assert(err, ErrorMatches, `cannot validate seed:
-- cannot use snap "need-base": base "some-base" is missing`)
-}
-
-func (s *validateSuite) TestValidateSnapMissingDefaultProvider(c *C) {
- s.makeSnapInSeed(c, coreYaml)
- s.makeSnapInSeed(c, `name: need-df
-version: 1.0
-plugs:
- gtk-3-themes:
- interface: content
- default-provider: gtk-common-themes
-`)
- seedFn := s.makeSeedYaml(c, `
-snaps:
- - name: core
- file: core_1.snap
- - name: need-df
- file: need-df_1.snap
-`)
-
- err := image.ValidateSeed(seedFn)
- c.Assert(err, ErrorMatches, `cannot validate seed:
-- cannot use snap "need-df": default provider "gtk-common-themes" is missing`)
-}
-
-func (s *validateSuite) TestValidateSnapSnapdHappy(c *C) {
- s.makeSnapInSeed(c, snapdYaml)
- s.makeSnapInSeed(c, packageCore18)
- s.makeSnapInSeed(c, `name: some-snap
-version: 1.0
-base: core18
-`)
- seedFn := s.makeSeedYaml(c, `
-snaps:
- - name: snapd
- file: snapd_1.snap
- - name: some-snap
- file: some-snap_1.snap
- - name: core18
- file: core18_1.snap
-`)
-
- err := image.ValidateSeed(seedFn)
- c.Assert(err, IsNil)
-}
-
-func (s *validateSuite) TestValidateSnapMissingCore(c *C) {
- s.makeSnapInSeed(c, snapdYaml)
- s.makeSnapInSeed(c, `name: some-snap
-version: 1.0`)
- seedFn := s.makeSeedYaml(c, `
-snaps:
- - name: snapd
- file: snapd_1.snap
- - name: some-snap
- file: some-snap_1.snap
-`)
-
- err := image.ValidateSeed(seedFn)
- c.Assert(err, ErrorMatches, `cannot validate seed:
-- cannot use snap "some-snap": required snap "core" missing`)
-}
-
-func (s *validateSuite) TestValidateSnapMissingSnapdAndCore(c *C) {
- s.makeSnapInSeed(c, packageCore18)
- s.makeSnapInSeed(c, `name: some-snap
-version: 1.0
-base: core18`)
- seedFn := s.makeSeedYaml(c, `
-snaps:
- - name: some-snap
- file: some-snap_1.snap
- - name: core18
- file: core18_1.snap
-`)
-
- err := image.ValidateSeed(seedFn)
- c.Assert(err, ErrorMatches, `cannot validate seed:
-- the core or snapd snap must be part of the seed`)
-}
-
-func (s *validateSuite) TestValidateSnapMultipleErrors(c *C) {
- s.makeSnapInSeed(c, `name: some-snap
-version: 1.0`)
- seedFn := s.makeSeedYaml(c, `
-snaps:
- - name: some-snap
- file: some-snap_1.snap
-`)
-
- err := image.ValidateSeed(seedFn)
- c.Assert(err, ErrorMatches, `cannot validate seed:
-- the core or snapd snap must be part of the seed
-- cannot use snap "some-snap": required snap "core" missing`)
-}
-
-func (s *validateSuite) TestValidateSnapSnapMissing(c *C) {
- s.makeSnapInSeed(c, coreYaml)
- seedFn := s.makeSeedYaml(c, `
-snaps:
- - name: core
- file: core_1.snap
- - name: some-snap
- file: some-snap_1.snap
-`)
-
- err := image.ValidateSeed(seedFn)
- c.Assert(err, ErrorMatches, `cannot validate seed:
-- cannot open snap: open /.*/snaps/some-snap_1.snap: no such file or directory`)
-}
-
-func (s *validateSuite) TestValidateSnapSnapInvalid(c *C) {
- s.makeSnapInSeed(c, coreYaml)
-
- // "version" is missing in this yaml
- snapBuildDir := c.MkDir()
- snapYaml := `name: some-snap-invalid-yaml`
- metaSnapYaml := filepath.Join(snapBuildDir, "meta", "snap.yaml")
- err := os.MkdirAll(filepath.Dir(metaSnapYaml), 0755)
- c.Assert(err, IsNil)
- err = ioutil.WriteFile(metaSnapYaml, []byte(snapYaml), 0644)
- c.Assert(err, IsNil)
-
- // need to build the snap "manually" pack.Snap() will do validation
- snapFilePath := filepath.Join(c.MkDir(), "some-snap-invalid-yaml_1.snap")
- d := squashfs.New(snapFilePath)
- err = d.Build(snapBuildDir, "app")
- c.Assert(err, IsNil)
-
- // put the broken snap in place
- dst := filepath.Join(s.root, "snaps", "some-snap-invalid-yaml_1.snap")
- err = os.Rename(snapFilePath, dst)
- c.Assert(err, IsNil)
-
- seedFn := s.makeSeedYaml(c, `
-snaps:
- - name: core
- file: core_1.snap
- - name: some-snap-invalid-yaml
- file: some-snap-invalid-yaml_1.snap
-`)
-
- err = image.ValidateSeed(seedFn)
- c.Assert(err, ErrorMatches, `cannot validate seed:
-- cannot use snap /.*/snaps/some-snap-invalid-yaml_1.snap: invalid snap version: cannot be empty`)
-}
diff -Nru snapd-2.41+19.10.1/include/lk/snappy_boot_v1.h snapd-2.42.1+19.10/include/lk/snappy_boot_v1.h
--- snapd-2.41+19.10.1/include/lk/snappy_boot_v1.h 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/include/lk/snappy_boot_v1.h 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,149 @@
+/**
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#ifndef _BOOTLOADER_SNAP_BOOT_V1_H
+#define _BOOTLOADER_SNAP_BOOT_V1_H
+
+#define SNAP_BOOTSELECT_VERSION 0x00010001
+#define SNAP_BOOTSELECT_SIGNATURE ('S' | ('B' << 8) | ('s' << 16) | ('e' << 24))
+#define SNAP_NAME_MAX_LEN (256)
+#define HASH_LENGTH (32)
+#define SNAP_MODE_TRY "try"
+#define SNAP_MODE_TRYING "trying"
+#define FACTORY_RESET "factory-reset"
+
+/* partition label where boot select structure is stored */
+#define SNAP_BOOTSELECT_PARTITION "snapbootsel"
+
+/* number of available bootimg partitions, min 2 */
+#define SNAP_BOOTIMG_PART_NUM 2
+
+/* snappy bootselect partition format structure */
+typedef struct SNAP_BOOT_SELECTION {
+ /* Contains value BOOTSELECT_SIGNATURE defined above */
+ uint32_t signature;
+ /* snappy boot select version */
+ uint32_t version;
+
+ /* snap_mode, one of: 'empty', "try", "trying" */
+ char snap_mode[SNAP_NAME_MAX_LEN];
+ /* current core snap revision */
+ char snap_core[SNAP_NAME_MAX_LEN];
+ /* try core snap revision */
+ char snap_try_core[SNAP_NAME_MAX_LEN];
+ /* current kernel snap revision */
+ char snap_kernel[SNAP_NAME_MAX_LEN];
+ /* current kernel snap revision */
+ char snap_try_kernel[SNAP_NAME_MAX_LEN];
+
+ /* gadget_mode, one of: 'empty', "try", "trying" */
+ char gadget_mode[SNAP_NAME_MAX_LEN];
+ /* GADGET assets: current gadget assets revision */
+ char snap_gadget[SNAP_NAME_MAX_LEN];
+ /* GADGET assets: try gadget assets revision */
+ char snap_try_gadget [SNAP_NAME_MAX_LEN];
+
+ /**
+ * Reboot reason
+ * optional parameter to signal bootloader alternative reboot reasons
+ * e.g. recovery/factory-reset/boot asset update
+ */
+ char reboot_reason[SNAP_NAME_MAX_LEN];
+
+ /**
+ * Matrix for mapping of boot img partion to installed kernel snap revision
+ *
+ * First column represents boot image partition label (e.g. boot_a,boot_b )
+ * value are static and should be populated at gadget built time
+ * or latest at image build time. Values are not further altered at run time.
+ * Second column represents name currently installed kernel snap
+ * e.g. pi2-kernel_123.snap
+ * initial value representing initial kernel snap revision
+ * is pupulated at image build time by snapd
+ *
+ * There are two rows in the matrix, representing current and previous kernel revision
+ * following describes how this matrix should be modified at different stages:
+ * - at image build time:
+ * - extracted kernel snap revision name should be filled
+ * into free slow (first row, second row)
+ * - snapd:
+ * - when new kernel snap revision is being installed, snapd cycles through
+ * matrix to find unused 'boot slot' to be used for new kernel snap revision
+ * from free slot, first column represents partition label to which kernel
+ * snap boot image should be extracted. Second column is then populated with
+ * kernel snap revision name.
+ * - snap_mode, snap_try_kernel, snap_try_core behaves same way as with u-boot
+ * - bootloader:
+ * - bootloader reads snap_mode to determine if snap_kernel or snap_kernel is used
+ * to get kernel snap revision name
+ * kernel snap revision is then used to search matrix to determine
+ * partition label to be used for current boot
+ * - bootloader NEVER alters this matrix values
+ *
+ * [ ] [ ]
+ * [ ] [ ]
+ */
+ char bootimg_matrix[SNAP_BOOTIMG_PART_NUM][2][SNAP_NAME_MAX_LEN];
+
+ /* name of the boot image from kernel snap to be used for extraction
+ when not defined or empty, default boot.img will be used */
+ char bootimg_file_name[SNAP_NAME_MAX_LEN];
+
+ /**
+ * gadget assets: Matrix for mapping of gadget asset partions
+ * Optional boot asset tracking, based on bootloader support
+ * Some boot chains support A/B boot assets for increased robustness
+ * example being A/B TrustExecutionEnvironment
+ * This matrix can be used to track current and try boot assets for
+ * robust updates
+ * Use of Gadget_asset_matrix matches use of Bootimg_matrix
+ *
+ * [ ] [ ]
+ * [ ] [ ]
+ */
+ char gadget_asset_matrix [SNAP_BOOTIMG_PART_NUM][2][SNAP_NAME_MAX_LEN];
+
+ /* unused placeholders for additional parameters to be used in the future */
+ char unused_key_01 [SNAP_NAME_MAX_LEN];
+ char unused_key_02 [SNAP_NAME_MAX_LEN];
+ char unused_key_03 [SNAP_NAME_MAX_LEN];
+ char unused_key_04 [SNAP_NAME_MAX_LEN];
+ char unused_key_05 [SNAP_NAME_MAX_LEN];
+ char unused_key_06 [SNAP_NAME_MAX_LEN];
+ char unused_key_07 [SNAP_NAME_MAX_LEN];
+ char unused_key_08 [SNAP_NAME_MAX_LEN];
+ char unused_key_09 [SNAP_NAME_MAX_LEN];
+ char unused_key_10 [SNAP_NAME_MAX_LEN];
+ char unused_key_11 [SNAP_NAME_MAX_LEN];
+ char unused_key_12 [SNAP_NAME_MAX_LEN];
+ char unused_key_13 [SNAP_NAME_MAX_LEN];
+ char unused_key_14 [SNAP_NAME_MAX_LEN];
+ char unused_key_15 [SNAP_NAME_MAX_LEN];
+ char unused_key_16 [SNAP_NAME_MAX_LEN];
+ char unused_key_17 [SNAP_NAME_MAX_LEN];
+ char unused_key_18 [SNAP_NAME_MAX_LEN];
+ char unused_key_19 [SNAP_NAME_MAX_LEN];
+ char unused_key_20 [SNAP_NAME_MAX_LEN];
+
+ /* unused array of 10 key - value pairs */
+ char key_value_pairs [10][2][SNAP_NAME_MAX_LEN];
+
+ /* crc32 value for structure */
+ uint32_t crc32;
+} SNAP_BOOT_SELECTION_t;
+
+#endif
diff -Nru snapd-2.41+19.10.1/interfaces/apparmor/spec.go snapd-2.42.1+19.10/interfaces/apparmor/spec.go
--- snapd-2.41+19.10.1/interfaces/apparmor/spec.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/interfaces/apparmor/spec.go 2019-10-30 12:17:43.000000000 +0000
@@ -42,7 +42,7 @@
snippets map[string][]string
// updateNS describe parts of apparmor policy for snap-update-ns executing
// on behalf of a given snap.
- updateNS []string
+ updateNS strutil.OrderedSet
// AppArmor deny rules cannot be undone by allow rules which makes
// deny rules difficult to work with arbitrary combinations of
@@ -92,7 +92,19 @@
// AddUpdateNS adds a new apparmor snippet for the snap-update-ns program.
func (spec *Specification) AddUpdateNS(snippet string) {
- spec.updateNS = append(spec.updateNS, snippet)
+ spec.updateNS.Put(snippet)
+}
+
+// EmitUpdateNSFunc returns a function for emitting update-ns snippets.
+func (spec *Specification) EmitUpdateNSFunc() func(f string, args ...interface{}) {
+ return func(f string, args ...interface{}) {
+ spec.AddUpdateNS(fmt.Sprintf(f, args...))
+ }
+}
+
+// UpdateNSIndexOf returns the index of a previously added snippet.
+func (spec *Specification) UpdateNSIndexOf(snippet string) (idx int, ok bool) {
+ return spec.updateNS.IndexOf(snippet)
}
// AddLayout adds apparmor snippets based on the layout of the snap.
@@ -145,40 +157,44 @@
}
sort.Strings(spec.snippets[tag])
}
+
+ emit := spec.EmitUpdateNSFunc()
+
// Append update-ns snippets that allow constructing the layout.
for _, path := range paths {
- var buf bytes.Buffer
l := si.Layout[path]
- fmt.Fprintf(&buf, " # Layout %s\n", l.String())
+ emit(" # Layout %s\n", l.String())
path := si.ExpandSnapVariables(l.Path)
switch {
case l.Bind != "":
bind := si.ExpandSnapVariables(l.Bind)
// Allow bind mounting the layout element.
- fmt.Fprintf(&buf, " mount options=(rbind, rw) %s/ -> %s/,\n", bind, path)
- fmt.Fprintf(&buf, " umount %s/,\n", path)
+ emit(" mount options=(rbind, rw) %s/ -> %s/,\n", bind, path)
+ emit(" mount options=(rprivate) -> %s/,\n", path)
+ emit(" umount %s/,\n", path)
// Allow constructing writable mimic in both bind-mount source and mount point.
- WritableProfile(&buf, path, 2) // At least / and /some-top-level-directory
- WritableProfile(&buf, bind, 4) // At least /, /snap/, /snap/$SNAP_NAME and /snap/$SNAP_NAME/$SNAP_REVISION
+ GenWritableProfile(emit, path, 2) // At least / and /some-top-level-directory
+ GenWritableProfile(emit, bind, 4) // At least /, /snap/, /snap/$SNAP_NAME and /snap/$SNAP_NAME/$SNAP_REVISION
case l.BindFile != "":
bindFile := si.ExpandSnapVariables(l.BindFile)
// Allow bind mounting the layout element.
- fmt.Fprintf(&buf, " mount options=(bind, rw) %s -> %s,\n", bindFile, path)
- fmt.Fprintf(&buf, " umount %s,\n", path)
+ emit(" mount options=(bind, rw) %s -> %s,\n", bindFile, path)
+ emit(" mount options=(rprivate) -> %s,\n", path)
+ emit(" umount %s,\n", path)
// Allow constructing writable mimic in both bind-mount source and mount point.
- WritableFileProfile(&buf, path, 2) // At least / and /some-top-level-directory
- WritableFileProfile(&buf, bindFile, 4) // At least /, /snap/, /snap/$SNAP_NAME and /snap/$SNAP_NAME/$SNAP_REVISION
+ GenWritableFileProfile(emit, path, 2) // At least / and /some-top-level-directory
+ GenWritableFileProfile(emit, bindFile, 4) // At least /, /snap/, /snap/$SNAP_NAME and /snap/$SNAP_NAME/$SNAP_REVISION
case l.Type == "tmpfs":
- fmt.Fprintf(&buf, " mount fstype=tmpfs tmpfs -> %s/,\n", path)
- fmt.Fprintf(&buf, " umount %s/,\n", path)
+ emit(" mount fstype=tmpfs tmpfs -> %s/,\n", path)
+ emit(" mount options=(rprivate) -> %s/,\n", path)
+ emit(" umount %s/,\n", path)
// Allow constructing writable mimic to mount point.
- WritableProfile(&buf, path, 2) // At least / and /some-top-level-directory
+ GenWritableProfile(emit, path, 2) // At least / and /some-top-level-directory
case l.Symlink != "":
// Allow constructing writable mimic to symlink parent directory.
- fmt.Fprintf(&buf, " %s rw,\n", path)
- WritableProfile(&buf, path, 2) // At least / and /some-top-level-directory
+ emit(" %s rw,\n", path)
+ GenWritableProfile(emit, path, 2) // At least / and /some-top-level-directory
}
- spec.AddUpdateNS(buf.String())
}
}
@@ -217,9 +233,9 @@
return path == "/" || path == "/snap" || path == "/var" || path == "/var/snap" || path == "/tmp" || path == "/usr" || path == "/etc"
}
-// WritableMimicProfile writes apparmor rules for a writable mimic at the given path.
-func WritableMimicProfile(buf *bytes.Buffer, path string, assumedPrefixDepth int) {
- fmt.Fprintf(buf, " # Writable mimic %s\n", path)
+// GenWritableMimicProfile generates apparmor rules for a writable mimic at the given path.
+func GenWritableMimicProfile(emit func(f string, args ...interface{}), path string, assumedPrefixDepth int) {
+ emit(" # Writable mimic %s\n", path)
iter, err := strutil.NewPathIterator(path)
if err != nil {
@@ -227,10 +243,10 @@
}
// Handle the prefix that is assumed to exist first.
- fmt.Fprintf(buf, " # .. permissions for traversing the prefix that is assumed to exist\n")
+ emit(" # .. permissions for traversing the prefix that is assumed to exist\n")
for iter.Next() {
if iter.Depth() < assumedPrefixDepth {
- fmt.Fprintf(buf, " %s r,\n", iter.CurrentPath())
+ emit(" %s r,\n", iter.CurrentPath())
}
}
@@ -246,64 +262,68 @@
// directory path semantics.
mimicPath := filepath.Join(iter.CurrentBase(), iter.CurrentCleanName()) + "/"
mimicAuxPath := filepath.Join("/tmp/.snap", iter.CurrentPath()) + "/"
- fmt.Fprintf(buf, " # .. variant with mimic at %s\n", mimicPath)
- fmt.Fprintf(buf, " # Allow reading the mimic directory, it must exist in the first place.\n")
- fmt.Fprintf(buf, " %s r,\n", mimicPath)
- fmt.Fprintf(buf, " # Allow setting the read-only directory aside via a bind mount.\n")
- fmt.Fprintf(buf, " %s rw,\n", mimicAuxPath)
- fmt.Fprintf(buf, " mount options=(rbind, rw) %s -> %s,\n", mimicPath, mimicAuxPath)
- fmt.Fprintf(buf, " # Allow mounting tmpfs over the read-only directory.\n")
- fmt.Fprintf(buf, " mount fstype=tmpfs options=(rw) tmpfs -> %s,\n", mimicPath)
- fmt.Fprintf(buf, " # Allow creating empty files and directories for bind mounting things\n"+
+ emit(" # .. variant with mimic at %s\n", mimicPath)
+ emit(" # Allow reading the mimic directory, it must exist in the first place.\n")
+ emit(" %s r,\n", mimicPath)
+ emit(" # Allow setting the read-only directory aside via a bind mount.\n")
+ emit(" %s rw,\n", mimicAuxPath)
+ emit(" mount options=(rbind, rw) %s -> %s,\n", mimicPath, mimicAuxPath)
+ emit(" # Allow mounting tmpfs over the read-only directory.\n")
+ emit(" mount fstype=tmpfs options=(rw) tmpfs -> %s,\n", mimicPath)
+ emit(" # Allow creating empty files and directories for bind mounting things\n" +
" # to reconstruct the now-writable parent directory.\n")
- fmt.Fprintf(buf, " %s*/ rw,\n", mimicAuxPath)
- fmt.Fprintf(buf, " %s*/ rw,\n", mimicPath)
- fmt.Fprintf(buf, " mount options=(rbind, rw) %s*/ -> %s*/,\n", mimicAuxPath, mimicPath)
- fmt.Fprintf(buf, " %s* rw,\n", mimicAuxPath)
- fmt.Fprintf(buf, " %s* rw,\n", mimicPath)
- fmt.Fprintf(buf, " mount options=(bind, rw) %s* -> %s*,\n", mimicAuxPath, mimicPath)
- fmt.Fprintf(buf, " # Allow unmounting the auxiliary directory.\n"+
+ emit(" %s*/ rw,\n", mimicAuxPath)
+ emit(" %s*/ rw,\n", mimicPath)
+ emit(" mount options=(rbind, rw) %s*/ -> %s*/,\n", mimicAuxPath, mimicPath)
+ emit(" %s* rw,\n", mimicAuxPath)
+ emit(" %s* rw,\n", mimicPath)
+ emit(" mount options=(bind, rw) %s* -> %s*,\n", mimicAuxPath, mimicPath)
+ emit(" # Allow unmounting the auxiliary directory.\n" +
" # TODO: use fstype=tmpfs here for more strictness (LP: #1613403)\n")
- fmt.Fprintf(buf, " umount %s,\n", mimicAuxPath)
- fmt.Fprintf(buf, " # Allow unmounting the destination directory as well as anything\n"+
- " # inside. This lets us perform the undo plan in case the writable\n"+
+ emit(" mount options=(rprivate) -> %s,\n", mimicAuxPath)
+ emit(" umount %s,\n", mimicAuxPath)
+ emit(" # Allow unmounting the destination directory as well as anything\n" +
+ " # inside. This lets us perform the undo plan in case the writable\n" +
" # mimic fails.\n")
- fmt.Fprintf(buf, " umount %s,\n", mimicPath)
- fmt.Fprintf(buf, " umount %s*,\n", mimicPath)
- fmt.Fprintf(buf, " umount %s*/,\n", mimicPath)
+ emit(" mount options=(rprivate) -> %s,\n", mimicPath)
+ emit(" mount options=(rprivate) -> %s*,\n", mimicPath)
+ emit(" mount options=(rprivate) -> %s*/,\n", mimicPath)
+ emit(" umount %s,\n", mimicPath)
+ emit(" umount %s*,\n", mimicPath)
+ emit(" umount %s*/,\n", mimicPath)
}
}
-// WritableFileProfile writes a profile for snap-update-ns for making given file writable.
-func WritableFileProfile(buf *bytes.Buffer, path string, assumedPrefixDepth int) {
+// GenWritableFileProfile writes a profile for snap-update-ns for making given file writable.
+func GenWritableFileProfile(emit func(f string, args ...interface{}), path string, assumedPrefixDepth int) {
if path == "/" {
return
}
if isProbablyWritable(path) {
- fmt.Fprintf(buf, " # Writable file %s\n", path)
- fmt.Fprintf(buf, " %s rw,\n", path)
+ emit(" # Writable file %s\n", path)
+ emit(" %s rw,\n", path)
for p := parent(path); !isProbablyPresent(p); p = parent(p) {
- fmt.Fprintf(buf, " %s/ rw,\n", p)
+ emit(" %s/ rw,\n", p)
}
} else {
parentPath := parent(path)
- WritableMimicProfile(buf, parentPath, assumedPrefixDepth)
+ GenWritableMimicProfile(emit, parentPath, assumedPrefixDepth)
}
}
-// WritableProfile writes a profile for snap-update-ns for making given directory writable.
-func WritableProfile(buf *bytes.Buffer, path string, assumedPrefixDepth int) {
+// GenWritableProfile generates a profile for snap-update-ns for making given directory writable.
+func GenWritableProfile(emit func(f string, args ...interface{}), path string, assumedPrefixDepth int) {
if path == "/" {
return
}
if isProbablyWritable(path) {
- fmt.Fprintf(buf, " # Writable directory %s\n", path)
+ emit(" # Writable directory %s\n", path)
for p := path; !isProbablyPresent(p); p = parent(p) {
- fmt.Fprintf(buf, " %s/ rw,\n", p)
+ emit(" %s/ rw,\n", p)
}
} else {
parentPath := parent(path)
- WritableMimicProfile(buf, parentPath, assumedPrefixDepth)
+ GenWritableMimicProfile(emit, parentPath, assumedPrefixDepth)
}
}
@@ -337,9 +357,7 @@
// UpdateNS returns a deep copy of all the added snap-update-ns snippets.
func (spec *Specification) UpdateNS() []string {
- cp := make([]string, len(spec.updateNS))
- copy(cp, spec.updateNS)
- return cp
+ return spec.updateNS.Items()
}
func snippetFromLayout(layout *snap.Layout) string {
diff -Nru snapd-2.41+19.10.1/interfaces/apparmor/spec_test.go snapd-2.42.1+19.10/interfaces/apparmor/spec_test.go
--- snapd-2.41+19.10.1/interfaces/apparmor/spec_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/interfaces/apparmor/spec_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -20,6 +20,8 @@
package apparmor_test
import (
+ "strings"
+
. "gopkg.in/check.v1"
"github.com/snapcore/snapd/interfaces"
@@ -139,6 +141,11 @@
s.spec.AddUpdateNS("s-u-n snippet 1")
s.spec.AddUpdateNS("s-u-n snippet 2")
+ // Check the order of the snippets can be retrieved.
+ idx, ok := s.spec.UpdateNSIndexOf("s-u-n snippet 2")
+ c.Assert(ok, Equals, true)
+ c.Check(idx, Equals, 1)
+
// The snippets were recorded correctly and in the right place.
c.Assert(s.spec.UpdateNS(), DeepEquals, []string{
"s-u-n snippet 1", "s-u-n snippet 2",
@@ -183,6 +190,7 @@
profile0 := ` # Layout /etc/foo.conf: bind-file $SNAP/foo.conf
mount options=(bind, rw) /snap/vanguard/42/foo.conf -> /etc/foo.conf,
+ mount options=(rprivate) -> /etc/foo.conf,
umount /etc/foo.conf,
# Writable mimic /etc
# .. permissions for traversing the prefix that is assumed to exist
@@ -205,227 +213,155 @@
mount options=(bind, rw) /tmp/.snap/etc/* -> /etc/*,
# Allow unmounting the auxiliary directory.
# TODO: use fstype=tmpfs here for more strictness (LP: #1613403)
+ mount options=(rprivate) -> /tmp/.snap/etc/,
umount /tmp/.snap/etc/,
# Allow unmounting the destination directory as well as anything
# inside. This lets us perform the undo plan in case the writable
# mimic fails.
+ mount options=(rprivate) -> /etc/,
+ mount options=(rprivate) -> /etc/*,
+ mount options=(rprivate) -> /etc/*/,
umount /etc/,
umount /etc/*,
umount /etc/*/,
# Writable mimic /snap/vanguard/42
- # .. permissions for traversing the prefix that is assumed to exist
- / r,
/snap/ r,
/snap/vanguard/ r,
# .. variant with mimic at /snap/vanguard/42/
- # Allow reading the mimic directory, it must exist in the first place.
/snap/vanguard/42/ r,
- # Allow setting the read-only directory aside via a bind mount.
/tmp/.snap/snap/vanguard/42/ rw,
mount options=(rbind, rw) /snap/vanguard/42/ -> /tmp/.snap/snap/vanguard/42/,
- # Allow mounting tmpfs over the read-only directory.
mount fstype=tmpfs options=(rw) tmpfs -> /snap/vanguard/42/,
- # Allow creating empty files and directories for bind mounting things
- # to reconstruct the now-writable parent directory.
/tmp/.snap/snap/vanguard/42/*/ rw,
/snap/vanguard/42/*/ rw,
mount options=(rbind, rw) /tmp/.snap/snap/vanguard/42/*/ -> /snap/vanguard/42/*/,
/tmp/.snap/snap/vanguard/42/* rw,
/snap/vanguard/42/* rw,
mount options=(bind, rw) /tmp/.snap/snap/vanguard/42/* -> /snap/vanguard/42/*,
- # Allow unmounting the auxiliary directory.
- # TODO: use fstype=tmpfs here for more strictness (LP: #1613403)
+ mount options=(rprivate) -> /tmp/.snap/snap/vanguard/42/,
umount /tmp/.snap/snap/vanguard/42/,
- # Allow unmounting the destination directory as well as anything
- # inside. This lets us perform the undo plan in case the writable
- # mimic fails.
+ mount options=(rprivate) -> /snap/vanguard/42/,
+ mount options=(rprivate) -> /snap/vanguard/42/*,
+ mount options=(rprivate) -> /snap/vanguard/42/*/,
umount /snap/vanguard/42/,
umount /snap/vanguard/42/*,
umount /snap/vanguard/42/*/,
`
- c.Assert(updateNS[0], Equals, profile0)
+ // Find the slice that describes profile0 by looking for the first unique
+ // line of the next profile.
+ start := 0
+ end, _ := s.spec.UpdateNSIndexOf(" # Layout /usr/foo: bind $SNAP/usr/foo\n")
+ c.Assert(strings.Join(updateNS[start:end], ""), Equals, profile0)
profile1 := ` # Layout /usr/foo: bind $SNAP/usr/foo
mount options=(rbind, rw) /snap/vanguard/42/usr/foo/ -> /usr/foo/,
+ mount options=(rprivate) -> /usr/foo/,
umount /usr/foo/,
# Writable mimic /usr
- # .. permissions for traversing the prefix that is assumed to exist
- / r,
# .. variant with mimic at /usr/
- # Allow reading the mimic directory, it must exist in the first place.
/usr/ r,
- # Allow setting the read-only directory aside via a bind mount.
/tmp/.snap/usr/ rw,
mount options=(rbind, rw) /usr/ -> /tmp/.snap/usr/,
- # Allow mounting tmpfs over the read-only directory.
mount fstype=tmpfs options=(rw) tmpfs -> /usr/,
- # Allow creating empty files and directories for bind mounting things
- # to reconstruct the now-writable parent directory.
/tmp/.snap/usr/*/ rw,
/usr/*/ rw,
mount options=(rbind, rw) /tmp/.snap/usr/*/ -> /usr/*/,
/tmp/.snap/usr/* rw,
/usr/* rw,
mount options=(bind, rw) /tmp/.snap/usr/* -> /usr/*,
- # Allow unmounting the auxiliary directory.
- # TODO: use fstype=tmpfs here for more strictness (LP: #1613403)
+ mount options=(rprivate) -> /tmp/.snap/usr/,
umount /tmp/.snap/usr/,
- # Allow unmounting the destination directory as well as anything
- # inside. This lets us perform the undo plan in case the writable
- # mimic fails.
+ mount options=(rprivate) -> /usr/,
+ mount options=(rprivate) -> /usr/*,
+ mount options=(rprivate) -> /usr/*/,
umount /usr/,
umount /usr/*,
umount /usr/*/,
# Writable mimic /snap/vanguard/42/usr
- # .. permissions for traversing the prefix that is assumed to exist
- / r,
- /snap/ r,
- /snap/vanguard/ r,
- # .. variant with mimic at /snap/vanguard/42/
- # Allow reading the mimic directory, it must exist in the first place.
- /snap/vanguard/42/ r,
- # Allow setting the read-only directory aside via a bind mount.
- /tmp/.snap/snap/vanguard/42/ rw,
- mount options=(rbind, rw) /snap/vanguard/42/ -> /tmp/.snap/snap/vanguard/42/,
- # Allow mounting tmpfs over the read-only directory.
- mount fstype=tmpfs options=(rw) tmpfs -> /snap/vanguard/42/,
- # Allow creating empty files and directories for bind mounting things
- # to reconstruct the now-writable parent directory.
- /tmp/.snap/snap/vanguard/42/*/ rw,
- /snap/vanguard/42/*/ rw,
- mount options=(rbind, rw) /tmp/.snap/snap/vanguard/42/*/ -> /snap/vanguard/42/*/,
- /tmp/.snap/snap/vanguard/42/* rw,
- /snap/vanguard/42/* rw,
- mount options=(bind, rw) /tmp/.snap/snap/vanguard/42/* -> /snap/vanguard/42/*,
- # Allow unmounting the auxiliary directory.
- # TODO: use fstype=tmpfs here for more strictness (LP: #1613403)
- umount /tmp/.snap/snap/vanguard/42/,
- # Allow unmounting the destination directory as well as anything
- # inside. This lets us perform the undo plan in case the writable
- # mimic fails.
- umount /snap/vanguard/42/,
- umount /snap/vanguard/42/*,
- umount /snap/vanguard/42/*/,
# .. variant with mimic at /snap/vanguard/42/usr/
- # Allow reading the mimic directory, it must exist in the first place.
/snap/vanguard/42/usr/ r,
- # Allow setting the read-only directory aside via a bind mount.
/tmp/.snap/snap/vanguard/42/usr/ rw,
mount options=(rbind, rw) /snap/vanguard/42/usr/ -> /tmp/.snap/snap/vanguard/42/usr/,
- # Allow mounting tmpfs over the read-only directory.
mount fstype=tmpfs options=(rw) tmpfs -> /snap/vanguard/42/usr/,
- # Allow creating empty files and directories for bind mounting things
- # to reconstruct the now-writable parent directory.
/tmp/.snap/snap/vanguard/42/usr/*/ rw,
/snap/vanguard/42/usr/*/ rw,
mount options=(rbind, rw) /tmp/.snap/snap/vanguard/42/usr/*/ -> /snap/vanguard/42/usr/*/,
/tmp/.snap/snap/vanguard/42/usr/* rw,
/snap/vanguard/42/usr/* rw,
mount options=(bind, rw) /tmp/.snap/snap/vanguard/42/usr/* -> /snap/vanguard/42/usr/*,
- # Allow unmounting the auxiliary directory.
- # TODO: use fstype=tmpfs here for more strictness (LP: #1613403)
+ mount options=(rprivate) -> /tmp/.snap/snap/vanguard/42/usr/,
umount /tmp/.snap/snap/vanguard/42/usr/,
- # Allow unmounting the destination directory as well as anything
- # inside. This lets us perform the undo plan in case the writable
- # mimic fails.
+ mount options=(rprivate) -> /snap/vanguard/42/usr/,
+ mount options=(rprivate) -> /snap/vanguard/42/usr/*,
+ mount options=(rprivate) -> /snap/vanguard/42/usr/*/,
umount /snap/vanguard/42/usr/,
umount /snap/vanguard/42/usr/*,
umount /snap/vanguard/42/usr/*/,
`
- c.Assert(updateNS[1], Equals, profile1)
+ // Find the slice that describes profile1 by looking for the first unique
+ // line of the next profile.
+ start = end
+ end, _ = s.spec.UpdateNSIndexOf(" # Layout /var/cache/mylink: symlink $SNAP_DATA/link/target\n")
+ c.Assert(strings.Join(updateNS[start:end], ""), Equals, profile1)
profile2 := ` # Layout /var/cache/mylink: symlink $SNAP_DATA/link/target
/var/cache/mylink rw,
# Writable mimic /var/cache
- # .. permissions for traversing the prefix that is assumed to exist
- / r,
# .. variant with mimic at /var/
- # Allow reading the mimic directory, it must exist in the first place.
/var/ r,
- # Allow setting the read-only directory aside via a bind mount.
/tmp/.snap/var/ rw,
mount options=(rbind, rw) /var/ -> /tmp/.snap/var/,
- # Allow mounting tmpfs over the read-only directory.
mount fstype=tmpfs options=(rw) tmpfs -> /var/,
- # Allow creating empty files and directories for bind mounting things
- # to reconstruct the now-writable parent directory.
/tmp/.snap/var/*/ rw,
/var/*/ rw,
mount options=(rbind, rw) /tmp/.snap/var/*/ -> /var/*/,
/tmp/.snap/var/* rw,
/var/* rw,
mount options=(bind, rw) /tmp/.snap/var/* -> /var/*,
- # Allow unmounting the auxiliary directory.
- # TODO: use fstype=tmpfs here for more strictness (LP: #1613403)
+ mount options=(rprivate) -> /tmp/.snap/var/,
umount /tmp/.snap/var/,
- # Allow unmounting the destination directory as well as anything
- # inside. This lets us perform the undo plan in case the writable
- # mimic fails.
+ mount options=(rprivate) -> /var/,
+ mount options=(rprivate) -> /var/*,
+ mount options=(rprivate) -> /var/*/,
umount /var/,
umount /var/*,
umount /var/*/,
# .. variant with mimic at /var/cache/
- # Allow reading the mimic directory, it must exist in the first place.
/var/cache/ r,
- # Allow setting the read-only directory aside via a bind mount.
/tmp/.snap/var/cache/ rw,
mount options=(rbind, rw) /var/cache/ -> /tmp/.snap/var/cache/,
- # Allow mounting tmpfs over the read-only directory.
mount fstype=tmpfs options=(rw) tmpfs -> /var/cache/,
- # Allow creating empty files and directories for bind mounting things
- # to reconstruct the now-writable parent directory.
/tmp/.snap/var/cache/*/ rw,
/var/cache/*/ rw,
mount options=(rbind, rw) /tmp/.snap/var/cache/*/ -> /var/cache/*/,
/tmp/.snap/var/cache/* rw,
/var/cache/* rw,
mount options=(bind, rw) /tmp/.snap/var/cache/* -> /var/cache/*,
- # Allow unmounting the auxiliary directory.
- # TODO: use fstype=tmpfs here for more strictness (LP: #1613403)
+ mount options=(rprivate) -> /tmp/.snap/var/cache/,
umount /tmp/.snap/var/cache/,
- # Allow unmounting the destination directory as well as anything
- # inside. This lets us perform the undo plan in case the writable
- # mimic fails.
+ mount options=(rprivate) -> /var/cache/,
+ mount options=(rprivate) -> /var/cache/*,
+ mount options=(rprivate) -> /var/cache/*/,
umount /var/cache/,
umount /var/cache/*,
umount /var/cache/*/,
`
- c.Assert(updateNS[2], Equals, profile2)
+ // Find the slice that describes profile2 by looking for the first unique
+ // line of the next profile.
+ start = end
+ end, _ = s.spec.UpdateNSIndexOf(" # Layout /var/tmp: type tmpfs, mode: 01777\n")
+ c.Assert(strings.Join(updateNS[start:end], ""), Equals, profile2)
profile3 := ` # Layout /var/tmp: type tmpfs, mode: 01777
mount fstype=tmpfs tmpfs -> /var/tmp/,
+ mount options=(rprivate) -> /var/tmp/,
umount /var/tmp/,
# Writable mimic /var
- # .. permissions for traversing the prefix that is assumed to exist
- / r,
- # .. variant with mimic at /var/
- # Allow reading the mimic directory, it must exist in the first place.
- /var/ r,
- # Allow setting the read-only directory aside via a bind mount.
- /tmp/.snap/var/ rw,
- mount options=(rbind, rw) /var/ -> /tmp/.snap/var/,
- # Allow mounting tmpfs over the read-only directory.
- mount fstype=tmpfs options=(rw) tmpfs -> /var/,
- # Allow creating empty files and directories for bind mounting things
- # to reconstruct the now-writable parent directory.
- /tmp/.snap/var/*/ rw,
- /var/*/ rw,
- mount options=(rbind, rw) /tmp/.snap/var/*/ -> /var/*/,
- /tmp/.snap/var/* rw,
- /var/* rw,
- mount options=(bind, rw) /tmp/.snap/var/* -> /var/*,
- # Allow unmounting the auxiliary directory.
- # TODO: use fstype=tmpfs here for more strictness (LP: #1613403)
- umount /tmp/.snap/var/,
- # Allow unmounting the destination directory as well as anything
- # inside. This lets us perform the undo plan in case the writable
- # mimic fails.
- umount /var/,
- umount /var/*,
- umount /var/*/,
`
- c.Assert(updateNS[3], Equals, profile3)
- c.Assert(updateNS, DeepEquals, []string{profile0, profile1, profile2, profile3})
+ // Find the slice that describes profile2 by looking till the end of the list.
+ start = end
+ c.Assert(strings.Join(updateNS[start:], ""), Equals, profile3)
+ c.Assert(strings.Join(updateNS, ""), DeepEquals, strings.Join([]string{profile0, profile1, profile2, profile3}, ""))
}
const snapTrivial = `
diff -Nru snapd-2.41+19.10.1/interfaces/apparmor/template.go snapd-2.42.1+19.10/interfaces/apparmor/template.go
--- snapd-2.41+19.10.1/interfaces/apparmor/template.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/interfaces/apparmor/template.go 2019-10-30 12:17:43.000000000 +0000
@@ -758,6 +758,7 @@
# Allow the content interface to bind fonts from the host filesystem
mount options=(ro bind) /var/lib/snapd/hostfs/usr/share/fonts/ -> /snap/###SNAP_INSTANCE_NAME###/*/**,
+ mount options=(rw private) -> /snap/###SNAP_INSTANCE_NAME###/*/**,
umount /snap/###SNAP_INSTANCE_NAME###/*/**,
# set up user mount namespace
diff -Nru snapd-2.41+19.10.1/interfaces/builtin/appstream_metadata.go snapd-2.42.1+19.10/interfaces/builtin/appstream_metadata.go
--- snapd-2.41+19.10.1/interfaces/builtin/appstream_metadata.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/interfaces/builtin/appstream_metadata.go 2019-10-30 12:17:43.000000000 +0000
@@ -20,8 +20,6 @@
package builtin
import (
- "bytes"
- "fmt"
"path/filepath"
"github.com/snapcore/snapd/dirs"
@@ -77,19 +75,18 @@
spec.AddSnippet(appstreamMetadataConnectedPlugAppArmor)
// Generate rules to allow snap-update-ns to do its thing
- var buf bytes.Buffer
+ emit := spec.EmitUpdateNSFunc()
for _, target := range appstreamMetadataDirs {
source := "/var/lib/snapd/hostfs" + target
- fmt.Fprintf(&buf, " # Read-only access to %s\n", target)
- fmt.Fprintf(&buf, " mount options=(bind) %s/ -> %s/,\n", source, target)
- fmt.Fprintf(&buf, " remount options=(bind, ro) %s/,\n", target)
- fmt.Fprintf(&buf, " umount %s/,\n\n", target)
+ emit(" # Read-only access to %s\n", target)
+ emit(" mount options=(bind) %s/ -> %s/,\n", source, target)
+ emit(" remount options=(bind, ro) %s/,\n", target)
+ emit(" umount %s/,\n\n", target)
// Allow constructing writable mimic to mount point We
// expect three components to already exist: /, /usr,
// and /usr/share (or equivalents under /var).
- apparmor.WritableProfile(&buf, target, 3)
+ apparmor.GenWritableProfile(emit, target, 3)
}
- spec.AddUpdateNS(buf.String())
return nil
}
diff -Nru snapd-2.41+19.10.1/interfaces/builtin/appstream_metadata_test.go snapd-2.42.1+19.10/interfaces/builtin/appstream_metadata_test.go
--- snapd-2.41+19.10.1/interfaces/builtin/appstream_metadata_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/interfaces/builtin/appstream_metadata_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -83,8 +83,7 @@
c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil)
c.Assert(spec.SecurityTags(), HasLen, 1)
c.Check(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `/var/cache/app-info/** r,`)
- c.Assert(spec.UpdateNS(), HasLen, 1)
- c.Check(spec.UpdateNS()[0], testutil.Contains, `# Read-only access to /usr/share/metainfo`)
+ c.Check(spec.UpdateNS(), testutil.Contains, " # Read-only access to /usr/share/metainfo\n")
}
func (s *AppstreamMetadataInterfaceSuite) TestMountConnectedPlug(c *C) {
diff -Nru snapd-2.41+19.10.1/interfaces/builtin/bluez.go snapd-2.42.1+19.10/interfaces/builtin/bluez.go
--- snapd-2.41+19.10.1/interfaces/builtin/bluez.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/interfaces/builtin/bluez.go 2019-10-30 12:17:43.000000000 +0000
@@ -92,6 +92,11 @@
bus=system
name="org.bluez.obex",
+# Allow binding the service to the requested connection name
+dbus (bind)
+ bus=system
+ name="org.bluez.mesh",
+
# 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
@@ -163,6 +168,10 @@
bus=system
peer=(name=org.bluez.obex, label=unconfined),
+dbus (send)
+ bus=system
+ peer=(name=org.bluez.mesh, label=unconfined),
+
dbus (receive)
bus=system
path=/
@@ -191,8 +200,10 @@
+
+
@@ -203,6 +214,14 @@
+
+
+
+
+
+
+
+
diff -Nru snapd-2.41+19.10.1/interfaces/builtin/bluez_test.go snapd-2.42.1+19.10/interfaces/builtin/bluez_test.go
--- snapd-2.41+19.10.1/interfaces/builtin/bluez_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/interfaces/builtin/bluez_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -178,6 +178,7 @@
c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"})
c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "peer=(name=org.bluez, label=unconfined)")
c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "peer=(name=org.bluez.obex, label=unconfined)")
+ c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "peer=(name=org.bluez.mesh, label=unconfined)")
c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "peer=(label=unconfined),")
c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `interface=org.freedesktop.DBus.ObjectManager`)
c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, `interface=org.freedesktop.DBus.*`)
diff -Nru snapd-2.41+19.10.1/interfaces/builtin/content.go snapd-2.42.1+19.10/interfaces/builtin/content.go
--- snapd-2.41+19.10.1/interfaces/builtin/content.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/interfaces/builtin/content.go 2019-10-30 12:17:43.000000000 +0000
@@ -214,6 +214,7 @@
func (iface *contentInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
contentSnippet := bytes.NewBuffer(nil)
writePaths := iface.path(slot, "write")
+ emit := spec.EmitUpdateNSFunc()
if len(writePaths) > 0 {
fmt.Fprintf(contentSnippet, `
# In addition to the bind mount, add any AppArmor rules so that
@@ -226,17 +227,16 @@
fmt.Fprintf(contentSnippet, "%s/** mrwklix,\n",
resolveSpecialVariable(w, slot.Snap()))
source, target := sourceTarget(plug, slot, w)
- var buf bytes.Buffer
- fmt.Fprintf(&buf, " # Read-write content sharing %s -> %s (w#%d)\n", plug.Ref(), slot.Ref(), i)
- fmt.Fprintf(&buf, " mount options=(bind, rw) %s/ -> %s/,\n", source, target)
- fmt.Fprintf(&buf, " umount %s/,\n", target)
+ emit(" # Read-write content sharing %s -> %s (w#%d)\n", plug.Ref(), slot.Ref(), i)
+ emit(" mount options=(bind, rw) %s/ -> %s/,\n", source, target)
+ emit(" mount options=(rprivate) -> %s/,\n", target)
+ emit(" umount %s/,\n", target)
// TODO: The assumed prefix depth could be optimized to be more
// precise since content sharing can only take place in a fixed
// list of places with well-known paths (well, constrained set of
// paths). This can be done when the prefix is actually consumed.
- apparmor.WritableProfile(&buf, source, 1)
- apparmor.WritableProfile(&buf, target, 1)
- spec.AddUpdateNS(buf.String())
+ apparmor.GenWritableProfile(emit, source, 1)
+ apparmor.GenWritableProfile(emit, target, 1)
}
}
@@ -252,15 +252,14 @@
resolveSpecialVariable(r, slot.Snap()))
source, target := sourceTarget(plug, slot, r)
- var buf bytes.Buffer
- fmt.Fprintf(&buf, " # Read-only content sharing %s -> %s (r#%d)\n", plug.Ref(), slot.Ref(), i)
- fmt.Fprintf(&buf, " mount options=(bind) %s/ -> %s/,\n", source, target)
- fmt.Fprintf(&buf, " remount options=(bind, ro) %s/,\n", target)
- fmt.Fprintf(&buf, " umount %s/,\n", target)
+ emit(" # Read-only content sharing %s -> %s (r#%d)\n", plug.Ref(), slot.Ref(), i)
+ emit(" mount options=(bind) %s/ -> %s/,\n", source, target)
+ emit(" remount options=(bind, ro) %s/,\n", target)
+ emit(" mount options=(rprivate) -> %s/,\n", target)
+ emit(" umount %s/,\n", target)
// Look at the TODO comment above.
- apparmor.WritableProfile(&buf, source, 1)
- apparmor.WritableProfile(&buf, target, 1)
- spec.AddUpdateNS(buf.String())
+ apparmor.GenWritableProfile(emit, source, 1)
+ apparmor.GenWritableProfile(emit, target, 1)
}
}
diff -Nru snapd-2.41+19.10.1/interfaces/builtin/content_test.go snapd-2.42.1+19.10/interfaces/builtin/content_test.go
--- snapd-2.41+19.10.1/interfaces/builtin/content_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/interfaces/builtin/content_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -20,8 +20,8 @@
package builtin_test
import (
- "fmt"
"path/filepath"
+ "strings"
. "gopkg.in/check.v1"
@@ -318,6 +318,7 @@
profile0 := ` # Read-only content sharing consumer:content -> producer:content (r#0)
mount options=(bind) /snap/producer/5/export/ -> /snap/consumer/7/import/,
remount options=(bind, ro) /snap/consumer/7/import/,
+ mount options=(rprivate) -> /snap/consumer/7/import/,
umount /snap/consumer/7/import/,
# Writable mimic /snap/producer/5
# .. permissions for traversing the prefix that is assumed to exist
@@ -339,193 +340,115 @@
mount options=(bind, rw) /tmp/.snap/* -> /*,
# Allow unmounting the auxiliary directory.
# TODO: use fstype=tmpfs here for more strictness (LP: #1613403)
+ mount options=(rprivate) -> /tmp/.snap/,
umount /tmp/.snap/,
# Allow unmounting the destination directory as well as anything
# inside. This lets us perform the undo plan in case the writable
# mimic fails.
+ mount options=(rprivate) -> /,
+ mount options=(rprivate) -> /*,
+ mount options=(rprivate) -> /*/,
umount /,
umount /*,
umount /*/,
# .. variant with mimic at /snap/
- # Allow reading the mimic directory, it must exist in the first place.
/snap/ r,
- # Allow setting the read-only directory aside via a bind mount.
/tmp/.snap/snap/ rw,
mount options=(rbind, rw) /snap/ -> /tmp/.snap/snap/,
- # Allow mounting tmpfs over the read-only directory.
mount fstype=tmpfs options=(rw) tmpfs -> /snap/,
- # Allow creating empty files and directories for bind mounting things
- # to reconstruct the now-writable parent directory.
/tmp/.snap/snap/*/ rw,
/snap/*/ rw,
mount options=(rbind, rw) /tmp/.snap/snap/*/ -> /snap/*/,
/tmp/.snap/snap/* rw,
/snap/* rw,
mount options=(bind, rw) /tmp/.snap/snap/* -> /snap/*,
- # Allow unmounting the auxiliary directory.
- # TODO: use fstype=tmpfs here for more strictness (LP: #1613403)
+ mount options=(rprivate) -> /tmp/.snap/snap/,
umount /tmp/.snap/snap/,
- # Allow unmounting the destination directory as well as anything
- # inside. This lets us perform the undo plan in case the writable
- # mimic fails.
+ mount options=(rprivate) -> /snap/,
+ mount options=(rprivate) -> /snap/*,
+ mount options=(rprivate) -> /snap/*/,
umount /snap/,
umount /snap/*,
umount /snap/*/,
# .. variant with mimic at /snap/producer/
- # Allow reading the mimic directory, it must exist in the first place.
/snap/producer/ r,
- # Allow setting the read-only directory aside via a bind mount.
/tmp/.snap/snap/producer/ rw,
mount options=(rbind, rw) /snap/producer/ -> /tmp/.snap/snap/producer/,
- # Allow mounting tmpfs over the read-only directory.
mount fstype=tmpfs options=(rw) tmpfs -> /snap/producer/,
- # Allow creating empty files and directories for bind mounting things
- # to reconstruct the now-writable parent directory.
/tmp/.snap/snap/producer/*/ rw,
/snap/producer/*/ rw,
mount options=(rbind, rw) /tmp/.snap/snap/producer/*/ -> /snap/producer/*/,
/tmp/.snap/snap/producer/* rw,
/snap/producer/* rw,
mount options=(bind, rw) /tmp/.snap/snap/producer/* -> /snap/producer/*,
- # Allow unmounting the auxiliary directory.
- # TODO: use fstype=tmpfs here for more strictness (LP: #1613403)
+ mount options=(rprivate) -> /tmp/.snap/snap/producer/,
umount /tmp/.snap/snap/producer/,
- # Allow unmounting the destination directory as well as anything
- # inside. This lets us perform the undo plan in case the writable
- # mimic fails.
+ mount options=(rprivate) -> /snap/producer/,
+ mount options=(rprivate) -> /snap/producer/*,
+ mount options=(rprivate) -> /snap/producer/*/,
umount /snap/producer/,
umount /snap/producer/*,
umount /snap/producer/*/,
# .. variant with mimic at /snap/producer/5/
- # Allow reading the mimic directory, it must exist in the first place.
/snap/producer/5/ r,
- # Allow setting the read-only directory aside via a bind mount.
/tmp/.snap/snap/producer/5/ rw,
mount options=(rbind, rw) /snap/producer/5/ -> /tmp/.snap/snap/producer/5/,
- # Allow mounting tmpfs over the read-only directory.
mount fstype=tmpfs options=(rw) tmpfs -> /snap/producer/5/,
- # Allow creating empty files and directories for bind mounting things
- # to reconstruct the now-writable parent directory.
/tmp/.snap/snap/producer/5/*/ rw,
/snap/producer/5/*/ rw,
mount options=(rbind, rw) /tmp/.snap/snap/producer/5/*/ -> /snap/producer/5/*/,
/tmp/.snap/snap/producer/5/* rw,
/snap/producer/5/* rw,
mount options=(bind, rw) /tmp/.snap/snap/producer/5/* -> /snap/producer/5/*,
- # Allow unmounting the auxiliary directory.
- # TODO: use fstype=tmpfs here for more strictness (LP: #1613403)
+ mount options=(rprivate) -> /tmp/.snap/snap/producer/5/,
umount /tmp/.snap/snap/producer/5/,
- # Allow unmounting the destination directory as well as anything
- # inside. This lets us perform the undo plan in case the writable
- # mimic fails.
+ mount options=(rprivate) -> /snap/producer/5/,
+ mount options=(rprivate) -> /snap/producer/5/*,
+ mount options=(rprivate) -> /snap/producer/5/*/,
umount /snap/producer/5/,
umount /snap/producer/5/*,
umount /snap/producer/5/*/,
# Writable mimic /snap/consumer/7
- # .. permissions for traversing the prefix that is assumed to exist
- # .. variant with mimic at /
- # Allow reading the mimic directory, it must exist in the first place.
- / r,
- # Allow setting the read-only directory aside via a bind mount.
- /tmp/.snap/ rw,
- mount options=(rbind, rw) / -> /tmp/.snap/,
- # Allow mounting tmpfs over the read-only directory.
- mount fstype=tmpfs options=(rw) tmpfs -> /,
- # Allow creating empty files and directories for bind mounting things
- # to reconstruct the now-writable parent directory.
- /tmp/.snap/*/ rw,
- /*/ rw,
- mount options=(rbind, rw) /tmp/.snap/*/ -> /*/,
- /tmp/.snap/* rw,
- /* rw,
- mount options=(bind, rw) /tmp/.snap/* -> /*,
- # Allow unmounting the auxiliary directory.
- # TODO: use fstype=tmpfs here for more strictness (LP: #1613403)
- umount /tmp/.snap/,
- # Allow unmounting the destination directory as well as anything
- # inside. This lets us perform the undo plan in case the writable
- # mimic fails.
- umount /,
- umount /*,
- umount /*/,
- # .. variant with mimic at /snap/
- # Allow reading the mimic directory, it must exist in the first place.
- /snap/ r,
- # Allow setting the read-only directory aside via a bind mount.
- /tmp/.snap/snap/ rw,
- mount options=(rbind, rw) /snap/ -> /tmp/.snap/snap/,
- # Allow mounting tmpfs over the read-only directory.
- mount fstype=tmpfs options=(rw) tmpfs -> /snap/,
- # Allow creating empty files and directories for bind mounting things
- # to reconstruct the now-writable parent directory.
- /tmp/.snap/snap/*/ rw,
- /snap/*/ rw,
- mount options=(rbind, rw) /tmp/.snap/snap/*/ -> /snap/*/,
- /tmp/.snap/snap/* rw,
- /snap/* rw,
- mount options=(bind, rw) /tmp/.snap/snap/* -> /snap/*,
- # Allow unmounting the auxiliary directory.
- # TODO: use fstype=tmpfs here for more strictness (LP: #1613403)
- umount /tmp/.snap/snap/,
- # Allow unmounting the destination directory as well as anything
- # inside. This lets us perform the undo plan in case the writable
- # mimic fails.
- umount /snap/,
- umount /snap/*,
- umount /snap/*/,
# .. variant with mimic at /snap/consumer/
- # Allow reading the mimic directory, it must exist in the first place.
/snap/consumer/ r,
- # Allow setting the read-only directory aside via a bind mount.
/tmp/.snap/snap/consumer/ rw,
mount options=(rbind, rw) /snap/consumer/ -> /tmp/.snap/snap/consumer/,
- # Allow mounting tmpfs over the read-only directory.
mount fstype=tmpfs options=(rw) tmpfs -> /snap/consumer/,
- # Allow creating empty files and directories for bind mounting things
- # to reconstruct the now-writable parent directory.
/tmp/.snap/snap/consumer/*/ rw,
/snap/consumer/*/ rw,
mount options=(rbind, rw) /tmp/.snap/snap/consumer/*/ -> /snap/consumer/*/,
/tmp/.snap/snap/consumer/* rw,
/snap/consumer/* rw,
mount options=(bind, rw) /tmp/.snap/snap/consumer/* -> /snap/consumer/*,
- # Allow unmounting the auxiliary directory.
- # TODO: use fstype=tmpfs here for more strictness (LP: #1613403)
+ mount options=(rprivate) -> /tmp/.snap/snap/consumer/,
umount /tmp/.snap/snap/consumer/,
- # Allow unmounting the destination directory as well as anything
- # inside. This lets us perform the undo plan in case the writable
- # mimic fails.
+ mount options=(rprivate) -> /snap/consumer/,
+ mount options=(rprivate) -> /snap/consumer/*,
+ mount options=(rprivate) -> /snap/consumer/*/,
umount /snap/consumer/,
umount /snap/consumer/*,
umount /snap/consumer/*/,
# .. variant with mimic at /snap/consumer/7/
- # Allow reading the mimic directory, it must exist in the first place.
/snap/consumer/7/ r,
- # Allow setting the read-only directory aside via a bind mount.
/tmp/.snap/snap/consumer/7/ rw,
mount options=(rbind, rw) /snap/consumer/7/ -> /tmp/.snap/snap/consumer/7/,
- # Allow mounting tmpfs over the read-only directory.
mount fstype=tmpfs options=(rw) tmpfs -> /snap/consumer/7/,
- # Allow creating empty files and directories for bind mounting things
- # to reconstruct the now-writable parent directory.
/tmp/.snap/snap/consumer/7/*/ rw,
/snap/consumer/7/*/ rw,
mount options=(rbind, rw) /tmp/.snap/snap/consumer/7/*/ -> /snap/consumer/7/*/,
/tmp/.snap/snap/consumer/7/* rw,
/snap/consumer/7/* rw,
mount options=(bind, rw) /tmp/.snap/snap/consumer/7/* -> /snap/consumer/7/*,
- # Allow unmounting the auxiliary directory.
- # TODO: use fstype=tmpfs here for more strictness (LP: #1613403)
+ mount options=(rprivate) -> /tmp/.snap/snap/consumer/7/,
umount /tmp/.snap/snap/consumer/7/,
- # Allow unmounting the destination directory as well as anything
- # inside. This lets us perform the undo plan in case the writable
- # mimic fails.
+ mount options=(rprivate) -> /snap/consumer/7/,
+ mount options=(rprivate) -> /snap/consumer/7/*,
+ mount options=(rprivate) -> /snap/consumer/7/*/,
umount /snap/consumer/7/,
umount /snap/consumer/7/*,
umount /snap/consumer/7/*/,
`
- c.Assert(updateNS[0], Equals, profile0)
- c.Assert(updateNS, DeepEquals, []string{profile0})
+ c.Assert(strings.Join(updateNS[:], ""), Equals, profile0)
}
// Check that sharing of writable data is possible
@@ -577,6 +500,7 @@
updateNS := apparmorSpec.UpdateNS()
profile0 := ` # Read-write content sharing consumer:content -> producer:content (w#0)
mount options=(bind, rw) /var/snap/producer/5/export/ -> /var/snap/consumer/7/import/,
+ mount options=(rprivate) -> /var/snap/consumer/7/import/,
umount /var/snap/consumer/7/import/,
# Writable directory /var/snap/producer/5/export
/var/snap/producer/5/export/ rw,
@@ -587,8 +511,7 @@
/var/snap/consumer/7/ rw,
/var/snap/consumer/ rw,
`
- c.Assert(updateNS[0], Equals, profile0)
- c.Assert(updateNS, DeepEquals, []string{profile0})
+ c.Assert(strings.Join(updateNS[:], ""), Equals, profile0)
}
// Check that sharing of writable common data is possible
@@ -640,6 +563,7 @@
updateNS := apparmorSpec.UpdateNS()
profile0 := ` # Read-write content sharing consumer:content -> producer:content (w#0)
mount options=(bind, rw) /var/snap/producer/common/export/ -> /var/snap/consumer/common/import/,
+ mount options=(rprivate) -> /var/snap/consumer/common/import/,
umount /var/snap/consumer/common/import/,
# Writable directory /var/snap/producer/common/export
/var/snap/producer/common/export/ rw,
@@ -650,8 +574,7 @@
/var/snap/consumer/common/ rw,
/var/snap/consumer/ rw,
`
- c.Assert(updateNS[0], Equals, profile0)
- c.Assert(updateNS, DeepEquals, []string{profile0})
+ c.Assert(strings.Join(updateNS[:], ""), Equals, profile0)
}
func (s *ContentSuite) TestInterfaces(c *C) {
@@ -734,10 +657,11 @@
/snap/producer/2/read-snap/** mrkix,
`
c.Assert(apparmorSpec.SnippetForTag("snap.consumer.app"), Equals, expected)
- fmt.Printf("")
+
updateNS := apparmorSpec.UpdateNS()
profile0 := ` # Read-write content sharing consumer:content -> producer:content (w#0)
mount options=(bind, rw) /var/snap/producer/common/write-common/ -> /var/snap/consumer/common/import/write-common/,
+ mount options=(rprivate) -> /var/snap/consumer/common/import/write-common/,
umount /var/snap/consumer/common/import/write-common/,
# Writable directory /var/snap/producer/common/write-common
/var/snap/producer/common/write-common/ rw,
@@ -749,58 +673,64 @@
/var/snap/consumer/common/ rw,
/var/snap/consumer/ rw,
`
- c.Assert(updateNS[0], Equals, profile0)
+ // Find the slice that describes profile0 by looking for the first unique
+ // line of the next profile.
+ start := 0
+ end, _ := apparmorSpec.UpdateNSIndexOf(" # Read-write content sharing consumer:content -> producer:content (w#1)\n")
+ c.Assert(strings.Join(updateNS[start:end], ""), Equals, profile0)
profile1 := ` # Read-write content sharing consumer:content -> producer:content (w#1)
mount options=(bind, rw) /var/snap/producer/2/write-data/ -> /var/snap/consumer/common/import/write-data/,
+ mount options=(rprivate) -> /var/snap/consumer/common/import/write-data/,
umount /var/snap/consumer/common/import/write-data/,
# Writable directory /var/snap/producer/2/write-data
/var/snap/producer/2/write-data/ rw,
/var/snap/producer/2/ rw,
- /var/snap/producer/ rw,
# Writable directory /var/snap/consumer/common/import/write-data
/var/snap/consumer/common/import/write-data/ rw,
- /var/snap/consumer/common/import/ rw,
- /var/snap/consumer/common/ rw,
- /var/snap/consumer/ rw,
`
- c.Assert(updateNS[1], Equals, profile1)
+ // Find the slice that describes profile1 by looking for the first unique
+ // line of the next profile.
+ start = end
+ end, _ = apparmorSpec.UpdateNSIndexOf(" # Read-only content sharing consumer:content -> producer:content (r#0)\n")
+ c.Assert(strings.Join(updateNS[start:end], ""), Equals, profile1)
profile2 := ` # Read-only content sharing consumer:content -> producer:content (r#0)
mount options=(bind) /var/snap/producer/common/read-common/ -> /var/snap/consumer/common/import/read-common/,
remount options=(bind, ro) /var/snap/consumer/common/import/read-common/,
+ mount options=(rprivate) -> /var/snap/consumer/common/import/read-common/,
umount /var/snap/consumer/common/import/read-common/,
# Writable directory /var/snap/producer/common/read-common
/var/snap/producer/common/read-common/ rw,
- /var/snap/producer/common/ rw,
- /var/snap/producer/ rw,
# Writable directory /var/snap/consumer/common/import/read-common
/var/snap/consumer/common/import/read-common/ rw,
- /var/snap/consumer/common/import/ rw,
- /var/snap/consumer/common/ rw,
- /var/snap/consumer/ rw,
`
- c.Assert(updateNS[2], Equals, profile2)
+ // Find the slice that describes profile2 by looking for the first unique
+ // line of the next profile.
+ start = end
+ end, _ = apparmorSpec.UpdateNSIndexOf(" # Read-only content sharing consumer:content -> producer:content (r#1)\n")
+ c.Assert(strings.Join(updateNS[start:end], ""), Equals, profile2)
profile3 := ` # Read-only content sharing consumer:content -> producer:content (r#1)
mount options=(bind) /var/snap/producer/2/read-data/ -> /var/snap/consumer/common/import/read-data/,
remount options=(bind, ro) /var/snap/consumer/common/import/read-data/,
+ mount options=(rprivate) -> /var/snap/consumer/common/import/read-data/,
umount /var/snap/consumer/common/import/read-data/,
# Writable directory /var/snap/producer/2/read-data
/var/snap/producer/2/read-data/ rw,
- /var/snap/producer/2/ rw,
- /var/snap/producer/ rw,
# Writable directory /var/snap/consumer/common/import/read-data
/var/snap/consumer/common/import/read-data/ rw,
- /var/snap/consumer/common/import/ rw,
- /var/snap/consumer/common/ rw,
- /var/snap/consumer/ rw,
`
- c.Assert(updateNS[3], Equals, profile3)
+ // Find the slice that describes profile3 by looking for the first unique
+ // line of the next profile.
+ start = end
+ end, _ = apparmorSpec.UpdateNSIndexOf(" # Read-only content sharing consumer:content -> producer:content (r#2)\n")
+ c.Assert(strings.Join(updateNS[start:end], ""), Equals, profile3)
profile4 := ` # Read-only content sharing consumer:content -> producer:content (r#2)
mount options=(bind) /snap/producer/2/read-snap/ -> /var/snap/consumer/common/import/read-snap/,
remount options=(bind, ro) /var/snap/consumer/common/import/read-snap/,
+ mount options=(rprivate) -> /var/snap/consumer/common/import/read-snap/,
umount /var/snap/consumer/common/import/read-snap/,
# Writable mimic /snap/producer/2
# .. permissions for traversing the prefix that is assumed to exist
@@ -822,96 +752,81 @@
mount options=(bind, rw) /tmp/.snap/* -> /*,
# Allow unmounting the auxiliary directory.
# TODO: use fstype=tmpfs here for more strictness (LP: #1613403)
+ mount options=(rprivate) -> /tmp/.snap/,
umount /tmp/.snap/,
# Allow unmounting the destination directory as well as anything
# inside. This lets us perform the undo plan in case the writable
# mimic fails.
+ mount options=(rprivate) -> /,
+ mount options=(rprivate) -> /*,
+ mount options=(rprivate) -> /*/,
umount /,
umount /*,
umount /*/,
# .. variant with mimic at /snap/
- # Allow reading the mimic directory, it must exist in the first place.
/snap/ r,
- # Allow setting the read-only directory aside via a bind mount.
/tmp/.snap/snap/ rw,
mount options=(rbind, rw) /snap/ -> /tmp/.snap/snap/,
- # Allow mounting tmpfs over the read-only directory.
mount fstype=tmpfs options=(rw) tmpfs -> /snap/,
- # Allow creating empty files and directories for bind mounting things
- # to reconstruct the now-writable parent directory.
/tmp/.snap/snap/*/ rw,
/snap/*/ rw,
mount options=(rbind, rw) /tmp/.snap/snap/*/ -> /snap/*/,
/tmp/.snap/snap/* rw,
/snap/* rw,
mount options=(bind, rw) /tmp/.snap/snap/* -> /snap/*,
- # Allow unmounting the auxiliary directory.
- # TODO: use fstype=tmpfs here for more strictness (LP: #1613403)
+ mount options=(rprivate) -> /tmp/.snap/snap/,
umount /tmp/.snap/snap/,
- # Allow unmounting the destination directory as well as anything
- # inside. This lets us perform the undo plan in case the writable
- # mimic fails.
+ mount options=(rprivate) -> /snap/,
+ mount options=(rprivate) -> /snap/*,
+ mount options=(rprivate) -> /snap/*/,
umount /snap/,
umount /snap/*,
umount /snap/*/,
# .. variant with mimic at /snap/producer/
- # Allow reading the mimic directory, it must exist in the first place.
/snap/producer/ r,
- # Allow setting the read-only directory aside via a bind mount.
/tmp/.snap/snap/producer/ rw,
mount options=(rbind, rw) /snap/producer/ -> /tmp/.snap/snap/producer/,
- # Allow mounting tmpfs over the read-only directory.
mount fstype=tmpfs options=(rw) tmpfs -> /snap/producer/,
- # Allow creating empty files and directories for bind mounting things
- # to reconstruct the now-writable parent directory.
/tmp/.snap/snap/producer/*/ rw,
/snap/producer/*/ rw,
mount options=(rbind, rw) /tmp/.snap/snap/producer/*/ -> /snap/producer/*/,
/tmp/.snap/snap/producer/* rw,
/snap/producer/* rw,
mount options=(bind, rw) /tmp/.snap/snap/producer/* -> /snap/producer/*,
- # Allow unmounting the auxiliary directory.
- # TODO: use fstype=tmpfs here for more strictness (LP: #1613403)
+ mount options=(rprivate) -> /tmp/.snap/snap/producer/,
umount /tmp/.snap/snap/producer/,
- # Allow unmounting the destination directory as well as anything
- # inside. This lets us perform the undo plan in case the writable
- # mimic fails.
+ mount options=(rprivate) -> /snap/producer/,
+ mount options=(rprivate) -> /snap/producer/*,
+ mount options=(rprivate) -> /snap/producer/*/,
umount /snap/producer/,
umount /snap/producer/*,
umount /snap/producer/*/,
# .. variant with mimic at /snap/producer/2/
- # Allow reading the mimic directory, it must exist in the first place.
/snap/producer/2/ r,
- # Allow setting the read-only directory aside via a bind mount.
/tmp/.snap/snap/producer/2/ rw,
mount options=(rbind, rw) /snap/producer/2/ -> /tmp/.snap/snap/producer/2/,
- # Allow mounting tmpfs over the read-only directory.
mount fstype=tmpfs options=(rw) tmpfs -> /snap/producer/2/,
- # Allow creating empty files and directories for bind mounting things
- # to reconstruct the now-writable parent directory.
/tmp/.snap/snap/producer/2/*/ rw,
/snap/producer/2/*/ rw,
mount options=(rbind, rw) /tmp/.snap/snap/producer/2/*/ -> /snap/producer/2/*/,
/tmp/.snap/snap/producer/2/* rw,
/snap/producer/2/* rw,
mount options=(bind, rw) /tmp/.snap/snap/producer/2/* -> /snap/producer/2/*,
- # Allow unmounting the auxiliary directory.
- # TODO: use fstype=tmpfs here for more strictness (LP: #1613403)
+ mount options=(rprivate) -> /tmp/.snap/snap/producer/2/,
umount /tmp/.snap/snap/producer/2/,
- # Allow unmounting the destination directory as well as anything
- # inside. This lets us perform the undo plan in case the writable
- # mimic fails.
+ mount options=(rprivate) -> /snap/producer/2/,
+ mount options=(rprivate) -> /snap/producer/2/*,
+ mount options=(rprivate) -> /snap/producer/2/*/,
umount /snap/producer/2/,
umount /snap/producer/2/*,
umount /snap/producer/2/*/,
# Writable directory /var/snap/consumer/common/import/read-snap
/var/snap/consumer/common/import/read-snap/ rw,
- /var/snap/consumer/common/import/ rw,
- /var/snap/consumer/common/ rw,
- /var/snap/consumer/ rw,
`
- c.Assert(updateNS[4], Equals, profile4)
- c.Assert(updateNS, DeepEquals, []string{profile0, profile1, profile2, profile3, profile4})
+ // Find the slice that describes profile4 by looking till the end of the list.
+ start = end
+ c.Assert(strings.Join(updateNS[start:], ""), Equals, profile4)
+ c.Assert(strings.Join(updateNS, ""), DeepEquals, strings.Join([]string{profile0, profile1, profile2, profile3, profile4}, ""))
}
func (s *ContentSuite) TestModernContentInterfacePlugins(c *C) {
diff -Nru snapd-2.41+19.10.1/interfaces/builtin/desktop.go snapd-2.42.1+19.10/interfaces/builtin/desktop.go
--- snapd-2.41+19.10.1/interfaces/builtin/desktop.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/interfaces/builtin/desktop.go 2019-10-30 12:17:43.000000000 +0000
@@ -20,9 +20,6 @@
package builtin
import (
- "bytes"
- "fmt"
-
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/interfaces/apparmor"
@@ -234,11 +231,10 @@
spec.AddSnippet(desktopConnectedPlugAppArmor)
// Allow mounting document portal
- var buf bytes.Buffer
- fmt.Fprintf(&buf, " # Mount the document portal\n")
- fmt.Fprintf(&buf, " mount options=(bind) /run/user/[0-9]*/doc/by-app/snap.%s/ -> /run/user/[0-9]*/doc/,\n", plug.Snap().InstanceName())
- fmt.Fprintf(&buf, " umount /run/user/[0-9]*/doc/,\n\n")
- spec.AddUpdateNS(buf.String())
+ emit := spec.EmitUpdateNSFunc()
+ emit(" # Mount the document portal\n")
+ emit(" mount options=(bind) /run/user/[0-9]*/doc/by-app/snap.%s/ -> /run/user/[0-9]*/doc/,\n", plug.Snap().InstanceName())
+ emit(" umount /run/user/[0-9]*/doc/,\n\n")
if !release.OnClassic {
// We only need the font mount rules on classic systems
@@ -247,14 +243,12 @@
// Allow mounting fonts
for _, dir := range iface.fontconfigDirs() {
- var buf bytes.Buffer
source := "/var/lib/snapd/hostfs" + dir
target := dirs.StripRootDir(dir)
- fmt.Fprintf(&buf, " # Read-only access to %s\n", target)
- fmt.Fprintf(&buf, " mount options=(bind) %s/ -> %s/,\n", source, target)
- fmt.Fprintf(&buf, " remount options=(bind, ro) %s/,\n", target)
- fmt.Fprintf(&buf, " umount %s/,\n\n", target)
- spec.AddUpdateNS(buf.String())
+ emit(" # Read-only access to %s\n", target)
+ emit(" mount options=(bind) %s/ -> %s/,\n", source, target)
+ emit(" remount options=(bind, ro) %s/,\n", target)
+ emit(" umount %s/,\n\n", target)
}
return nil
diff -Nru snapd-2.41+19.10.1/interfaces/builtin/desktop_test.go snapd-2.42.1+19.10/interfaces/builtin/desktop_test.go
--- snapd-2.41+19.10.1/interfaces/builtin/desktop_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/interfaces/builtin/desktop_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -22,6 +22,7 @@
import (
"os"
"path/filepath"
+ "strings"
. "gopkg.in/check.v1"
@@ -103,12 +104,12 @@
// On an all-snaps system, the only UpdateNS rule is for the
// document portal.
updateNS := spec.UpdateNS()
- c.Assert(updateNS, HasLen, 1)
- c.Check(updateNS[0], Equals, ` # Mount the document portal
+ profile0 := ` # Mount the document portal
mount options=(bind) /run/user/[0-9]*/doc/by-app/snap.consumer/ -> /run/user/[0-9]*/doc/,
umount /run/user/[0-9]*/doc/,
-`)
+`
+ c.Assert(strings.Join(updateNS, ""), Equals, profile0)
// On a classic system, there are UpdateNS rules for the host
// system font mounts
@@ -117,11 +118,10 @@
spec = &apparmor.Specification{}
c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.coreSlot), IsNil)
updateNS = spec.UpdateNS()
- c.Assert(updateNS, HasLen, 4)
- c.Check(updateNS[0], testutil.Contains, "# Mount the document portal")
- c.Check(updateNS[1], testutil.Contains, "# Read-only access to /usr/share/fonts")
- c.Check(updateNS[2], testutil.Contains, "# Read-only access to /usr/local/share/fonts")
- c.Check(updateNS[3], testutil.Contains, "# Read-only access to /var/cache/fontconfig")
+ c.Check(updateNS, testutil.Contains, " # Mount the document portal\n")
+ c.Check(updateNS, testutil.Contains, " # Read-only access to /usr/share/fonts\n")
+ c.Check(updateNS, testutil.Contains, " # Read-only access to /usr/local/share/fonts\n")
+ c.Check(updateNS, testutil.Contains, " # Read-only access to /var/cache/fontconfig\n")
// connected plug to core slot
spec = &apparmor.Specification{}
diff -Nru snapd-2.41+19.10.1/interfaces/builtin/docker_support.go snapd-2.42.1+19.10/interfaces/builtin/docker_support.go
--- snapd-2.41+19.10.1/interfaces/builtin/docker_support.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/interfaces/builtin/docker_support.go 2019-10-30 12:17:43.000000000 +0000
@@ -176,7 +176,22 @@
# For details see https://bugs.launchpad.net/apparmor/+bug/1820344
/ ix,
/bin/runc ixr,
+
/pause ixr,
+/bin/busybox ixr,
+
+# When kubernetes drives containerd, containerd needs access to CNI services,
+# like flanneld's subnet.env for DNS. This would ideally be snap-specific (it
+# could if the control plane was a snap), but in deployments where the control
+# plane is not a snap, it will tell flannel to use this path.
+/run/flannel/{,**} rk,
+
+# When kubernetes drives containerd, containerd needs access to various
+# secrets for the pods which are overlayed at /run/secrets/....
+# This would ideally be snap-specific (it could if the control plane was a
+# snap), but in deployments where the control plane is not a snap, it will tell
+# containerd to use this path for various account information for pods.
+/run/secrets/kubernetes.io/{,**} rk,
`
const dockerSupportConnectedPlugSecComp = `
diff -Nru snapd-2.41+19.10.1/interfaces/builtin/kubernetes_support.go snapd-2.42.1+19.10/interfaces/builtin/kubernetes_support.go
--- snapd-2.41+19.10.1/interfaces/builtin/kubernetes_support.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/interfaces/builtin/kubernetes_support.go 2019-10-30 12:17:43.000000000 +0000
@@ -80,6 +80,7 @@
# ptrace 'trace' is coarse and not required for using the systemd private
# socket, and while the child profile omits 'capability sys_ptrace', skip
# for now since it isn't strictly required.
+ ptrace read peer=unconfined,
deny ptrace trace peer=unconfined,
/run/systemd/private rw,
@@ -95,6 +96,12 @@
# Ideally this would be snap-specific
/run/dockershim.sock rw,
+# Ideally this would be snap-specific (it could if the control plane was a
+# snap), but in deployments where the control plane is not a snap, it will tell
+# flannel to use this path.
+/run/flannel/{,**} rw,
+/run/flannel/** k,
+
# allow managing pods' cgroups
/sys/fs/cgroup/*/kubepods/{,**} rw,
@@ -129,7 +136,8 @@
mount /var/snap/@{SNAP_NAME}/common/{,**} -> /var/snap/@{SNAP_NAME}/common/{,**},
mount options=(rw, rshared) -> /var/snap/@{SNAP_NAME}/common/{,**},
-/bin/umount ixr,
+/{,usr/}bin/mount ixr,
+/{,usr/}bin/umount ixr,
deny /run/mount/utab rw,
umount /var/snap/@{SNAP_INSTANCE_NAME}/common/**,
`
@@ -137,7 +145,7 @@
const kubernetesSupportConnectedPlugAppArmorKubeletSystemdRun = `
# kubelet mount rules
capability sys_admin,
- /bin/mount ixr,
+ /{,usr/}bin/mount ixr,
mount fstype="tmpfs" tmpfs -> /var/snap/@{SNAP_INSTANCE_NAME}/common/**,
deny /run/mount/utab rw,
`
diff -Nru snapd-2.41+19.10.1/interfaces/builtin/network_manager.go snapd-2.42.1+19.10/interfaces/builtin/network_manager.go
--- snapd-2.41+19.10.1/interfaces/builtin/network_manager.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/interfaces/builtin/network_manager.go 2019-10-30 12:17:43.000000000 +0000
@@ -257,6 +257,15 @@
path=/org/freedesktop/NetworkManager{,/**}
peer=(label=###PLUG_SECURITY_TAGS###),
+# Later versions of NetworkManager implement org.freedesktop.DBus.ObjectManager
+# for clients to easily obtain all (and be alerted to added/removed) objects
+# from the service.
+dbus (receive, send)
+ bus=system
+ path=/org/freedesktop
+ interface=org.freedesktop.DBus.ObjectManager
+ peer=(label=###PLUG_SECURITY_TAGS###),
+
# Explicitly deny ptrace to silence noisy denials. These denials happen when NM
# tries to access /proc//stat. What apparmor prevents is showing
# internal process addresses that live in that file, but that has no adverse
@@ -276,6 +285,31 @@
bus=system
path=/org/freedesktop/NetworkManager{,/**}
peer=(label=###SLOT_SECURITY_TAGS###),
+
+# NM implements org.freedesktop.DBus.ObjectManager too
+dbus (receive, send)
+ bus=system
+ path=/org/freedesktop
+ interface=org.freedesktop.DBus.ObjectManager
+ peer=(label=###SLOT_SECURITY_TAGS###),
+`
+
+const networkManagerConnectedPlugIntrospectionSnippet = `
+# Allow us to introspect the network-manager providing snap
+dbus (send)
+ bus=system
+ interface="org.freedesktop.DBus.Introspectable"
+ member="Introspect"
+ peer=(label=###SLOT_SECURITY_TAGS###),
+`
+
+const networkManagerConnectedSlotIntrospectionSnippet = `
+# Allow plugs to introspect us
+dbus (receive)
+ bus=system
+ interface="org.freedesktop.DBus.Introspectable"
+ member="Introspect"
+ peer=(label=###PLUG_SECURITY_TAGS###),
`
const networkManagerConnectedPlugSecComp = `
@@ -465,6 +499,11 @@
}
snippet := strings.Replace(networkManagerConnectedPlugAppArmor, old, new, -1)
spec.AddSnippet(snippet)
+ if !release.OnClassic {
+ // See https://bugs.launchpad.net/snapd/+bug/1849291 for details.
+ snippet := strings.Replace(networkManagerConnectedPlugIntrospectionSnippet, old, new, -1)
+ spec.AddSnippet(snippet)
+ }
return nil
}
@@ -473,6 +512,11 @@
new := plugAppLabelExpr(plug)
snippet := strings.Replace(networkManagerConnectedSlotAppArmor, old, new, -1)
spec.AddSnippet(snippet)
+ if !release.OnClassic {
+ // See https://bugs.launchpad.net/snapd/+bug/1849291 for details.
+ snippet := strings.Replace(networkManagerConnectedSlotIntrospectionSnippet, old, new, -1)
+ spec.AddSnippet(snippet)
+ }
return nil
}
diff -Nru snapd-2.41+19.10.1/interfaces/builtin/network_manager_test.go snapd-2.42.1+19.10/interfaces/builtin/network_manager_test.go
--- snapd-2.41+19.10.1/interfaces/builtin/network_manager_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/interfaces/builtin/network_manager_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -158,6 +158,42 @@
c.Assert(apparmorSpec.SnippetForTag("snap.network-manager-client.nmcli"), testutil.Contains, "peer=(label=unconfined),")
}
+func (s *NetworkManagerInterfaceSuite) TestConnectedPlugIntrospectionOnCore(c *C) {
+ release.OnClassic = false
+ apparmorSpec := &apparmor.Specification{}
+ err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot)
+ c.Assert(err, IsNil)
+ c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.network-manager-client.nmcli"})
+ c.Assert(apparmorSpec.SnippetForTag("snap.network-manager-client.nmcli"), testutil.Contains, "Allow us to introspect the network-manager providing snap")
+}
+
+func (s *NetworkManagerInterfaceSuite) TestConnectedSlotIntrospectionOnCore(c *C) {
+ release.OnClassic = false
+ apparmorSpec := &apparmor.Specification{}
+ err := apparmorSpec.AddConnectedSlot(s.iface, s.plug, s.slot)
+ c.Assert(err, IsNil)
+ c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.network-manager.nm"})
+ c.Assert(apparmorSpec.SnippetForTag("snap.network-manager.nm"), testutil.Contains, "# Allow plugs to introspect us")
+}
+
+func (s *NetworkManagerInterfaceSuite) TestConnectedPlugIntrospectionOnClassic(c *C) {
+ release.OnClassic = true
+ apparmorSpec := &apparmor.Specification{}
+ err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot)
+ c.Assert(err, IsNil)
+ c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.network-manager-client.nmcli"})
+ c.Assert(apparmorSpec.SnippetForTag("snap.network-manager-client.nmcli"), Not(testutil.Contains), "Allow us to introspect the network-manager providing snap")
+}
+
+func (s *NetworkManagerInterfaceSuite) TestConnectedSlotIntrospectionOnClassic(c *C) {
+ release.OnClassic = true
+ apparmorSpec := &apparmor.Specification{}
+ err := apparmorSpec.AddConnectedSlot(s.iface, s.plug, s.slot)
+ c.Assert(err, IsNil)
+ c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.network-manager.nm"})
+ c.Assert(apparmorSpec.SnippetForTag("snap.network-manager.nm"), Not(testutil.Contains), "# Allow plugs to introspect us")
+}
+
func (s *NetworkManagerInterfaceSuite) TestConnectedSlotSnippetAppArmor(c *C) {
apparmorSpec := &apparmor.Specification{}
err := apparmorSpec.AddConnectedSlot(s.iface, s.plug, s.slot)
diff -Nru snapd-2.41+19.10.1/interfaces/builtin/opengl.go snapd-2.42.1+19.10/interfaces/builtin/opengl.go
--- snapd-2.41+19.10.1/interfaces/builtin/opengl.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/interfaces/builtin/opengl.go 2019-10-30 12:17:43.000000000 +0000
@@ -83,6 +83,10 @@
/dev/nvhost-* rw,
/dev/nvmap rw,
+# Tegra display driver
+/dev/tegra_dc_ctrl rw,
+/dev/tegra_dc_[0-9]* rw,
+
# OpenCL ICD files
/etc/OpenCL/vendors/ r,
/etc/OpenCL/vendors/** r,
@@ -91,10 +95,11 @@
@{PROC}/driver/prl_vtg rw,
# /sys/devices
-/sys/devices/pci[0-9a-f]*/**/config r,
-/sys/devices/pci[0-9a-f]*/**/revision r,
-/sys/devices/pci[0-9a-f]*/**/{,subsystem_}device r,
-/sys/devices/pci[0-9a-f]*/**/{,subsystem_}vendor r,
+/sys/devices/{,*pcie-controller/}pci[0-9a-f]*/**/config r,
+/sys/devices/{,*pcie-controller/}pci[0-9a-f]*/**/revision r,
+/sys/devices/{,*pcie-controller/}pci[0-9a-f]*/**/{,subsystem_}class r,
+/sys/devices/{,*pcie-controller/}pci[0-9a-f]*/**/{,subsystem_}device r,
+/sys/devices/{,*pcie-controller/}pci[0-9a-f]*/**/{,subsystem_}vendor r,
/sys/devices/**/drm{,_dp_aux_dev}/** r,
# FIXME: this is an information leak and snapd should instead query udev for
@@ -121,6 +126,8 @@
`KERNEL=="renderD[0-9]*"`,
`KERNEL=="nvhost-*"`,
`KERNEL=="nvmap"`,
+ `KERNEL=="tegra_dc_ctrl"`,
+ `KERNEL=="tegra_dc_[0-9]*"`,
}
func init() {
diff -Nru snapd-2.41+19.10.1/interfaces/builtin/opengl_test.go snapd-2.42.1+19.10/interfaces/builtin/opengl_test.go
--- snapd-2.41+19.10.1/interfaces/builtin/opengl_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/interfaces/builtin/opengl_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -84,7 +84,7 @@
func (s *OpenglInterfaceSuite) TestUDevSpec(c *C) {
spec := &udev.Specification{}
c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil)
- c.Assert(spec.Snippets(), HasLen, 6)
+ c.Assert(spec.Snippets(), HasLen, 8)
c.Assert(spec.Snippets(), testutil.Contains, `# opengl
SUBSYSTEM=="drm", KERNEL=="card[0-9]*", TAG+="snap_consumer_app"`)
c.Assert(spec.Snippets(), testutil.Contains, `# opengl
@@ -93,6 +93,10 @@
KERNEL=="nvhost-*", TAG+="snap_consumer_app"`)
c.Assert(spec.Snippets(), testutil.Contains, `# opengl
KERNEL=="nvmap", TAG+="snap_consumer_app"`)
+ c.Assert(spec.Snippets(), testutil.Contains, `# opengl
+KERNEL=="tegra_dc_ctrl", TAG+="snap_consumer_app"`)
+ c.Assert(spec.Snippets(), testutil.Contains, `# opengl
+KERNEL=="tegra_dc_[0-9]*", TAG+="snap_consumer_app"`)
c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_consumer_app", RUN+="/usr/lib/snapd/snap-device-helper $env{ACTION} snap_consumer_app $devpath $major:$minor"`)
}
diff -Nru snapd-2.41+19.10.1/interfaces/builtin/wayland.go snapd-2.42.1+19.10/interfaces/builtin/wayland.go
--- snapd-2.41+19.10.1/interfaces/builtin/wayland.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/interfaces/builtin/wayland.go 2019-10-30 12:17:43.000000000 +0000
@@ -59,6 +59,7 @@
# Allow reading an Xwayland Xauth file
# (see https://gitlab.gnome.org/GNOME/mutter/merge_requests/626)
/run/user/[0-9]*/.mutter-Xwaylandauth.* r,
+/run/user/[0-9]*/mutter/Xauthority r,
# Allow write access to create /run/user/* to create XDG_RUNTIME_DIR (until
# lp:1738197 is fixed). Note this is not needed if creating a session using
diff -Nru snapd-2.41+19.10.1/interfaces/builtin/x11.go snapd-2.42.1+19.10/interfaces/builtin/x11.go
--- snapd-2.41+19.10.1/interfaces/builtin/x11.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/interfaces/builtin/x11.go 2019-10-30 12:17:43.000000000 +0000
@@ -72,6 +72,12 @@
type=stream
addr="@/tmp/.ICE-unix/[0-9]*",
+# On systems with Tegra drivers, X11 needs to create the socket for clients to
+# use.
+unix (bind, listen, accept)
+ type=dgram
+ addr="@nvidia[0-9a-f]*",
+
# For Xorg to detect screens
/sys/devices/pci**/boot_vga r,
/sys/devices/pci**/resources r,
@@ -123,6 +129,8 @@
# Allow reading an Xwayland Xauth file
# (see https://gitlab.gnome.org/GNOME/mutter/merge_requests/626)
owner /run/user/[0-9]*/.mutter-Xwaylandauth.* r,
+owner /run/user/[0-9]*/mutter/Xauthority r,
+
# Needed by QtSystems on X to detect mouse and keyboard. Note, the 'netlink
# raw' rule is not finely mediated by apparmor so we mediate with seccomp arg
diff -Nru snapd-2.41+19.10.1/interfaces/seccomp/backend.go snapd-2.42.1+19.10/interfaces/seccomp/backend.go
--- snapd-2.41+19.10.1/interfaces/seccomp/backend.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/interfaces/seccomp/backend.go 2019-10-30 12:17:43.000000000 +0000
@@ -53,11 +53,11 @@
)
var (
- kernelFeatures = release.SecCompActions
- ubuntuKernelArchitecture = arch.UbuntuKernelArchitecture
- releaseInfoId = release.ReleaseInfo.ID
- releaseInfoVersionId = release.ReleaseInfo.VersionID
- requiresSocketcall = requiresSocketcallImpl
+ kernelFeatures = release.SecCompActions
+ dpkgKernelArchitecture = arch.DpkgKernelArchitecture
+ releaseInfoId = release.ReleaseInfo.ID
+ releaseInfoVersionId = release.ReleaseInfo.VersionID
+ requiresSocketcall = requiresSocketcallImpl
snapSeccompVersionInfo = snapSeccompVersionInfoImpl
seccompCompilerLookup = cmd.InternalToolPath
@@ -365,7 +365,7 @@
// - if the kernel architecture is not any of the above, force the use of
// socketcall()
func requiresSocketcallImpl(baseSnap string) bool {
- switch ubuntuKernelArchitecture() {
+ switch dpkgKernelArchitecture() {
case "i386", "s390x":
// glibc sysdeps/unix/sysv/linux/i386/kernel-features.h and
// sysdeps/unix/sysv/linux/s390/kernel-features.h added the
diff -Nru snapd-2.41+19.10.1/interfaces/seccomp/backend_test.go snapd-2.42.1+19.10/interfaces/seccomp/backend_test.go
--- snapd-2.41+19.10.1/interfaces/seccomp/backend_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/interfaces/seccomp/backend_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -491,7 +491,7 @@
func (s *backendSuite) TestRequiresSocketcallByNotNeededArch(c *C) {
testArchs := []string{"amd64", "armhf", "arm64", "powerpc", "ppc64el", "unknownDefault"}
for _, arch := range testArchs {
- restore := seccomp.MockUbuntuKernelArchitecture(func() string { return arch })
+ restore := seccomp.MockDpkgKernelArchitecture(func() string { return arch })
defer restore()
c.Assert(seccomp.RequiresSocketcall(""), Equals, false)
}
@@ -500,7 +500,7 @@
func (s *backendSuite) TestRequiresSocketcallForceByArch(c *C) {
testArchs := []string{"sparc", "sparc64"}
for _, arch := range testArchs {
- restore := seccomp.MockUbuntuKernelArchitecture(func() string { return arch })
+ restore := seccomp.MockDpkgKernelArchitecture(func() string { return arch })
defer restore()
c.Assert(seccomp.RequiresSocketcall(""), Equals, true)
}
@@ -542,7 +542,7 @@
for _, t := range tests {
restore = seccomp.MockReleaseInfoId(t.distro)
defer restore()
- restore = seccomp.MockUbuntuKernelArchitecture(func() string { return t.arch })
+ restore = seccomp.MockDpkgKernelArchitecture(func() string { return t.arch })
defer restore()
restore = seccomp.MockReleaseInfoVersionId(t.release)
defer restore()
@@ -581,7 +581,7 @@
}
for _, t := range tests {
- restore := seccomp.MockUbuntuKernelArchitecture(func() string { return t.arch })
+ restore := seccomp.MockDpkgKernelArchitecture(func() string { return t.arch })
defer restore()
restore = osutil.MockKernelVersion(t.version)
defer restore()
@@ -597,7 +597,7 @@
// check is reached
restore := seccomp.MockReleaseInfoId("other")
defer restore()
- restore = seccomp.MockUbuntuKernelArchitecture(func() string { return "i386" })
+ restore = seccomp.MockDpkgKernelArchitecture(func() string { return "i386" })
defer restore()
restore = osutil.MockKernelVersion("4.3")
defer restore()
@@ -613,7 +613,7 @@
// check is reached
restore := seccomp.MockReleaseInfoId("other")
defer restore()
- restore = seccomp.MockUbuntuKernelArchitecture(func() string { return "i386" })
+ restore = seccomp.MockDpkgKernelArchitecture(func() string { return "i386" })
defer restore()
restore = osutil.MockKernelVersion("4.3")
defer restore()
diff -Nru snapd-2.41+19.10.1/interfaces/seccomp/export_test.go snapd-2.42.1+19.10/interfaces/seccomp/export_test.go
--- snapd-2.41+19.10.1/interfaces/seccomp/export_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/interfaces/seccomp/export_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -54,11 +54,11 @@
}
}
-func MockUbuntuKernelArchitecture(f func() string) (restore func()) {
- old := ubuntuKernelArchitecture
- ubuntuKernelArchitecture = f
+func MockDpkgKernelArchitecture(f func() string) (restore func()) {
+ old := dpkgKernelArchitecture
+ dpkgKernelArchitecture = f
return func() {
- ubuntuKernelArchitecture = old
+ dpkgKernelArchitecture = old
}
}
diff -Nru snapd-2.41+19.10.1/osutil/flock_test.go snapd-2.42.1+19.10/osutil/flock_test.go
--- snapd-2.41+19.10.1/osutil/flock_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/osutil/flock_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -118,27 +118,31 @@
c.Assert(lock.Unlock(), ErrorMatches, "bad file descriptor")
}
-// Test that non-blocking
+// Test that non-blocking locking reports error on pre-acquired lock.
func (s *flockSuite) TestLockUnlockNonblockingWorks(c *C) {
if os.Getenv("TRAVIS_BUILD_NUMBER") != "" {
c.Skip("Cannot use this under travis")
return
}
+ // Use the "flock" command to grab a lock for 9999 seconds in another process.
lockPath := filepath.Join(c.MkDir(), "lock")
cmd := exec.Command("flock", "--exclusive", lockPath, "sleep", "9999")
c.Assert(cmd.Start(), IsNil)
defer cmd.Process.Kill()
+ // Give flock some chance to create the lock file.
for i := 0; i < 10; i++ {
if osutil.FileExists(lockPath) {
break
}
- time.Sleep(time.Millisecond)
+ time.Sleep(time.Millisecond * 300)
}
+ // Try to acquire the same lock file and see that it is busy.
lock, err := osutil.NewFileLock(lockPath)
c.Assert(err, IsNil)
+ c.Assert(lock, NotNil)
defer lock.Close()
c.Assert(lock.TryLock(), Equals, osutil.ErrAlreadyLocked)
diff -Nru snapd-2.41+19.10.1/osutil/squashfs/fstype.go snapd-2.42.1+19.10/osutil/squashfs/fstype.go
--- snapd-2.41+19.10.1/osutil/squashfs/fstype.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/osutil/squashfs/fstype.go 2019-10-30 12:17:43.000000000 +0000
@@ -26,10 +26,7 @@
"github.com/snapcore/snapd/osutil"
)
-// useFuse detects if we should be using squashfuse instead
-var useFuse = useFuseImpl
-
-func useFuseImpl() bool {
+var needsFuseImpl = func() bool {
if !osutil.FileExists("/dev/fuse") {
return false
}
@@ -51,13 +48,18 @@
return false
}
-// MockUseFuse is exported so useFuse can be overridden by testing.
-func MockUseFuse(r bool) func() {
- oldUseFuse := useFuse
- useFuse = func() bool {
+// MockNeedsFuse is exported so NeedsFuse can be overridden by testing.
+func MockNeedsFuse(r bool) func() {
+ oldNeedsFuseImpl := needsFuseImpl
+ needsFuseImpl = func() bool {
return r
}
- return func() { useFuse = oldUseFuse }
+ return func() { needsFuseImpl = oldNeedsFuseImpl }
+}
+
+// NeedsFuse returns true if the given system needs fuse to mount snaps
+func NeedsFuse() bool {
+ return needsFuseImpl()
}
// FsType returns what fstype to use for squashfs mounts and what
@@ -66,7 +68,7 @@
fstype = "squashfs"
options = []string{"ro", "x-gdu.hide"}
- if useFuse() {
+ if NeedsFuse() {
options = append(options, "allow_other")
switch {
case osutil.ExecutableExists("squashfuse"):
@@ -74,7 +76,7 @@
case osutil.ExecutableExists("snapfuse"):
fstype = "fuse.snapfuse"
default:
- panic("cannot happen because useFuse() ensures one of the two executables is there")
+ panic("cannot happen because NeedsFuse() ensures one of the two executables is there")
}
}
diff -Nru snapd-2.41+19.10.1/overlord/assertstate/assertstate.go snapd-2.42.1+19.10/overlord/assertstate/assertstate.go
--- snapd-2.41+19.10.1/overlord/assertstate/assertstate.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/assertstate/assertstate.go 2019-10-30 12:17:43.000000000 +0000
@@ -24,7 +24,6 @@
import (
"fmt"
- "io"
"strings"
"github.com/snapcore/snapd/asserts"
@@ -42,134 +41,9 @@
return cachedDB(s).Add(a)
}
-// Batch allows to accumulate a set of assertions possibly out of prerequisite order and then add them in one go to the system assertion database.
-type Batch struct {
- bs asserts.Backstore
- refs []*asserts.Ref
- linearized []asserts.Assertion
-}
-
-// NewBatch creates a new Batch to accumulate assertions to add in one go to the system assertion database.
-func NewBatch() *Batch {
- return &Batch{
- bs: asserts.NewMemoryBackstore(),
- refs: nil,
- linearized: nil,
- }
-}
-
-func (b *Batch) committing() error {
- if b.linearized != nil {
- return fmt.Errorf("internal error: cannot add to Batch while committing")
- }
- return nil
-}
-
-// Add one assertion to the batch.
-func (b *Batch) Add(a asserts.Assertion) error {
- if err := b.committing(); err != nil {
- return err
- }
-
- if !a.SupportedFormat() {
- return &asserts.UnsupportedFormatError{Ref: a.Ref(), Format: a.Format()}
- }
- if err := b.bs.Put(a.Type(), a); err != nil {
- if revErr, ok := err.(*asserts.RevisionError); ok {
- if revErr.Current >= a.Revision() {
- // we already got something more recent
- return nil
- }
- }
- return err
- }
- b.refs = append(b.refs, a.Ref())
- return nil
-}
-
-// AddStream adds a stream of assertions to the batch.
-// Returns references to to the assertions effectively added.
-func (b *Batch) AddStream(r io.Reader) ([]*asserts.Ref, error) {
- if err := b.committing(); err != nil {
- return nil, err
- }
-
- start := len(b.refs)
- dec := asserts.NewDecoder(r)
- for {
- a, err := dec.Decode()
- if err == io.EOF {
- break
- }
- if err != nil {
- return nil, err
- }
- if err := b.Add(a); err != nil {
- return nil, err
- }
- }
- added := b.refs[start:]
- if len(added) == 0 {
- return nil, nil
- }
- refs := make([]*asserts.Ref, len(added))
- copy(refs, added)
- return refs, nil
-}
-
-func (b *Batch) commitTo(db *asserts.Database) error {
- if err := b.linearize(db); err != nil {
- return err
- }
-
- // TODO: trigger w. caller a global sanity check if something is revoked
- // (but try to save as much possible still),
- // or err is a check error
- return commitTo(db, b.linearized)
-}
-
-func (b *Batch) linearize(db *asserts.Database) error {
- if b.linearized != nil {
- return nil
- }
-
- retrieve := func(ref *asserts.Ref) (asserts.Assertion, error) {
- a, err := b.bs.Get(ref.Type, ref.PrimaryKey, ref.Type.MaxSupportedFormat())
- if asserts.IsNotFound(err) {
- // fallback to pre-existing assertions
- a, err = ref.Resolve(db.Find)
- }
- if err != nil {
- return nil, findError("cannot find %s", ref, err)
- }
- return a, nil
- }
-
- // linearize using accumFetcher
- f := newAccumFetcher(db, retrieve)
- for _, ref := range b.refs {
- if err := f.Fetch(ref); err != nil {
- return err
- }
- }
-
- b.linearized = f.fetched
- return nil
-}
-
-// Commit adds the batch of assertions to the system assertion database.
-func (b *Batch) Commit(st *state.State) error {
- db := cachedDB(st)
-
- return b.commitTo(db)
-}
-
-// Precheck pre-checks whether adding the batch of assertions to the system assertion database should fully succeed.
-func (b *Batch) Precheck(st *state.State) error {
- db := cachedDB(st)
- db = db.WithStackedBackstore(asserts.NewMemoryBackstore())
-
- return b.commitTo(db)
+// AddBatch adds the given assertion batch to the system assertion database.
+func AddBatch(s *state.State, batch *asserts.Batch, opts *asserts.CommitOptions) error {
+ return batch.CommitTo(cachedDB(s), opts)
}
func findError(format string, ref *asserts.Ref, err error) error {
diff -Nru snapd-2.41+19.10.1/overlord/assertstate/assertstate_test.go snapd-2.42.1+19.10/overlord/assertstate/assertstate_test.go
--- snapd-2.41+19.10.1/overlord/assertstate/assertstate_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/assertstate/assertstate_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -36,6 +36,7 @@
"github.com/snapcore/snapd/asserts/assertstest"
"github.com/snapcore/snapd/asserts/sysdb"
"github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/overlord"
"github.com/snapcore/snapd/overlord/assertstate"
"github.com/snapcore/snapd/overlord/auth"
@@ -70,8 +71,9 @@
type fakeStore struct {
storetest.Store
- state *state.State
- db asserts.RODatabase
+ state *state.State
+ db asserts.RODatabase
+ maxDeclSupportedFormat int
}
func (sto *fakeStore) pokeStateLock() {
@@ -83,6 +85,10 @@
func (sto *fakeStore) Assertion(assertType *asserts.AssertionType, key []string, _ *auth.UserState) (asserts.Assertion, error) {
sto.pokeStateLock()
+
+ restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, sto.maxDeclSupportedFormat)
+ defer restore()
+
ref := &asserts.Ref{Type: assertType, PrimaryKey: key}
return ref.Resolve(sto.db.Find)
}
@@ -121,6 +127,7 @@
s.fakeStore = &fakeStore{
state: s.state,
db: s.storeSigning,
+ maxDeclSupportedFormat: asserts.SnapDeclarationType.MaxSupportedFormat(),
}
s.trivialDeviceCtx = &snapstatetest.TrivialDeviceContext{
CtxStore: s.fakeStore,
@@ -154,7 +161,7 @@
c.Check(devAcct.(*asserts.Account).Username(), Equals, "developer1")
}
-func (s *assertMgrSuite) TestBatchAddStream(c *C) {
+func (s *assertMgrSuite) TestAddBatch(c *C) {
s.state.Lock()
defer s.state.Unlock()
@@ -166,7 +173,7 @@
enc.Encode(s.storeSigning.StoreAccountKey(""))
c.Assert(err, IsNil)
- batch := assertstate.NewBatch()
+ batch := asserts.NewBatch(nil)
refs, err := batch.AddStream(b)
c.Assert(err, IsNil)
c.Check(refs, DeepEquals, []*asserts.Ref{
@@ -178,30 +185,7 @@
err = batch.Add(s.storeSigning.StoreAccountKey(""))
c.Assert(err, IsNil)
- err = batch.Commit(s.state)
- c.Assert(err, IsNil)
-
- db := assertstate.DB(s.state)
- devAcct, err := db.Find(asserts.AccountType, map[string]string{
- "account-id": s.dev1Acct.AccountID(),
- })
- c.Assert(err, IsNil)
- c.Check(devAcct.(*asserts.Account).Username(), Equals, "developer1")
-}
-
-func (s *assertMgrSuite) TestBatchConsiderPreexisting(c *C) {
- s.state.Lock()
- defer s.state.Unlock()
-
- // prereq store key
- err := assertstate.Add(s.state, s.storeSigning.StoreAccountKey(""))
- c.Assert(err, IsNil)
-
- batch := assertstate.NewBatch()
- err = batch.Add(s.dev1Acct)
- c.Assert(err, IsNil)
-
- err = batch.Commit(s.state)
+ err = assertstate.AddBatch(s.state, batch, nil)
c.Assert(err, IsNil)
db := assertstate.DB(s.state)
@@ -212,112 +196,7 @@
c.Check(devAcct.(*asserts.Account).Username(), Equals, "developer1")
}
-func (s *assertMgrSuite) TestBatchAddStreamReturnsEffectivelyAddedRefs(c *C) {
- s.state.Lock()
- defer s.state.Unlock()
-
- b := &bytes.Buffer{}
- enc := asserts.NewEncoder(b)
- // wrong order is ok
- err := enc.Encode(s.dev1Acct)
- c.Assert(err, IsNil)
- enc.Encode(s.storeSigning.StoreAccountKey(""))
- c.Assert(err, IsNil)
-
- batch := assertstate.NewBatch()
-
- err = batch.Add(s.storeSigning.StoreAccountKey(""))
- c.Assert(err, IsNil)
-
- refs, err := batch.AddStream(b)
- c.Assert(err, IsNil)
- c.Check(refs, DeepEquals, []*asserts.Ref{
- {Type: asserts.AccountType, PrimaryKey: []string{s.dev1Acct.AccountID()}},
- })
-
- err = batch.Commit(s.state)
- c.Assert(err, IsNil)
-
- db := assertstate.DB(s.state)
- devAcct, err := db.Find(asserts.AccountType, map[string]string{
- "account-id": s.dev1Acct.AccountID(),
- })
- c.Assert(err, IsNil)
- c.Check(devAcct.(*asserts.Account).Username(), Equals, "developer1")
-}
-
-func (s *assertMgrSuite) TestBatchCommitRefusesSelfSignedKey(c *C) {
- s.state.Lock()
- defer s.state.Unlock()
-
- aKey, _ := assertstest.GenerateKey(752)
- aSignDB := assertstest.NewSigningDB("can0nical", aKey)
-
- aKeyEncoded, err := asserts.EncodePublicKey(aKey.PublicKey())
- c.Assert(err, IsNil)
-
- headers := map[string]interface{}{
- "authority-id": "can0nical",
- "account-id": "can0nical",
- "public-key-sha3-384": aKey.PublicKey().ID(),
- "name": "default",
- "since": time.Now().UTC().Format(time.RFC3339),
- }
- acctKey, err := aSignDB.Sign(asserts.AccountKeyType, headers, aKeyEncoded, "")
- c.Assert(err, IsNil)
-
- headers = map[string]interface{}{
- "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"), "")
- c.Assert(err, IsNil)
-
- batch := assertstate.NewBatch()
-
- err = batch.Add(repair)
- c.Assert(err, IsNil)
-
- err = batch.Add(acctKey)
- c.Assert(err, IsNil)
-
- // this must fail
- err = batch.Commit(s.state)
- c.Assert(err, ErrorMatches, `circular assertions are not expected:.*`)
-}
-
-func (s *assertMgrSuite) TestBatchAddUnsupported(c *C) {
- restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 111)
- defer restore()
-
- batch := assertstate.NewBatch()
-
- var a asserts.Assertion
- (func() {
- restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 999)
- defer restore()
- headers := map[string]interface{}{
- "format": "999",
- "revision": "1",
- "series": "16",
- "snap-id": "snap-id-1",
- "snap-name": "foo",
- "publisher-id": s.dev1Acct.AccountID(),
- "timestamp": time.Now().Format(time.RFC3339),
- }
- var err error
- a, err = s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "")
- c.Assert(err, IsNil)
- })()
-
- err := batch.Add(a)
- c.Check(err, ErrorMatches, `proposed "snap-declaration" assertion has format 999 but 111 is latest supported`)
-}
-
-func (s *assertMgrSuite) TestBatchCommitPartial(c *C) {
+func (s *assertMgrSuite) TestAddBatchPartial(c *C) {
// Commit does add any successful assertion until the first error
s.state.Lock()
defer s.state.Unlock()
@@ -326,7 +205,7 @@
err := assertstate.Add(s.state, s.storeSigning.StoreAccountKey(""))
c.Assert(err, IsNil)
- batch := assertstate.NewBatch()
+ batch := asserts.NewBatch(nil)
snapDeclFoo := s.snapDecl(c, "foo", nil)
@@ -351,7 +230,7 @@
err = batch.Add(snapRev)
c.Assert(err, IsNil)
- err = batch.Commit(s.state)
+ err = assertstate.AddBatch(s.state, batch, nil)
c.Check(err, ErrorMatches, `(?ms).*validity.*`)
// snap-declaration was added anyway
@@ -362,7 +241,7 @@
c.Assert(err, IsNil)
}
-func (s *assertMgrSuite) TestBatchPrecheckPartial(c *C) {
+func (s *assertMgrSuite) TestAddBatchPrecheckPartial(c *C) {
s.state.Lock()
defer s.state.Unlock()
@@ -370,7 +249,7 @@
err := assertstate.Add(s.state, s.storeSigning.StoreAccountKey(""))
c.Assert(err, IsNil)
- batch := assertstate.NewBatch()
+ batch := asserts.NewBatch(nil)
snapDeclFoo := s.snapDecl(c, "foo", nil)
@@ -395,7 +274,9 @@
err = batch.Add(snapRev)
c.Assert(err, IsNil)
- err = batch.Precheck(s.state)
+ err = assertstate.AddBatch(s.state, batch, &asserts.CommitOptions{
+ Precheck: true,
+ })
c.Check(err, ErrorMatches, `(?ms).*validity.*`)
// nothing was added
@@ -406,7 +287,7 @@
c.Assert(asserts.IsNotFound(err), Equals, true)
}
-func (s *assertMgrSuite) TestBatchPrecheckHappy(c *C) {
+func (s *assertMgrSuite) TestAddBatchPrecheckHappy(c *C) {
s.state.Lock()
defer s.state.Unlock()
@@ -414,7 +295,7 @@
err := assertstate.Add(s.state, s.storeSigning.StoreAccountKey(""))
c.Assert(err, IsNil)
- batch := assertstate.NewBatch()
+ batch := asserts.NewBatch(nil)
snapDeclFoo := s.snapDecl(c, "foo", nil)
@@ -439,18 +320,9 @@
err = batch.Add(snapRev)
c.Assert(err, IsNil)
- err = batch.Precheck(s.state)
- c.Assert(err, IsNil)
-
- // nothing was added yet
- _, err = assertstate.DB(s.state).Find(asserts.SnapDeclarationType, map[string]string{
- "series": "16",
- "snap-id": "foo-id",
+ err = assertstate.AddBatch(s.state, batch, &asserts.CommitOptions{
+ Precheck: true,
})
- c.Assert(asserts.IsNotFound(err), Equals, true)
-
- // commit
- err = batch.Commit(s.state)
c.Assert(err, IsNil)
_, err = assertstate.DB(s.state).Find(asserts.SnapRevisionType, map[string]string{
@@ -562,6 +434,93 @@
c.Assert(err, IsNil)
}
+func (s *assertMgrSuite) TestFetchUnsupportedUpdateIgnored(c *C) {
+ // ATM in principle we ignore updated assertions with unsupported formats
+ // NB: this scenario can only happen if there is a bug
+ // we ask the store to filter what is returned by max supported format!
+ restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 111)
+ defer restore()
+
+ logbuf, restore := logger.MockLogger()
+ defer restore()
+
+ snapDeclFoo0 := s.snapDecl(c, "foo", nil)
+
+ s.state.Lock()
+ defer s.state.Unlock()
+ err := assertstate.Add(s.state, s.storeSigning.StoreAccountKey(""))
+ c.Assert(err, IsNil)
+
+ err = assertstate.Add(s.state, s.dev1Acct)
+ c.Assert(err, IsNil)
+ err = assertstate.Add(s.state, snapDeclFoo0)
+ c.Assert(err, IsNil)
+
+ var snapDeclFoo1 *asserts.SnapDeclaration
+ (func() {
+ restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 999)
+ defer restore()
+ snapDeclFoo1 = s.snapDecl(c, "foo", map[string]interface{}{
+ "format": "999",
+ "revision": "1",
+ })
+ })()
+ c.Check(snapDeclFoo1.Revision(), Equals, 1)
+
+ ref := &asserts.Ref{
+ Type: asserts.SnapDeclarationType,
+ PrimaryKey: []string{"16", "foo-id"},
+ }
+ fetching := func(f asserts.Fetcher) error {
+ return f.Fetch(ref)
+ }
+
+ s.fakeStore.(*fakeStore).maxDeclSupportedFormat = 999
+ err = assertstate.DoFetch(s.state, 0, s.trivialDeviceCtx, fetching)
+ // no error and the old one was kept
+ c.Assert(err, IsNil)
+ snapDecl, err := ref.Resolve(assertstate.DB(s.state).Find)
+ c.Assert(err, IsNil)
+ c.Check(snapDecl.Revision(), Equals, 0)
+
+ // we log the issue
+ c.Check(logbuf.String(), testutil.Contains, `Cannot update assertion snap-declaration (foo-id;`)
+}
+
+func (s *assertMgrSuite) TestFetchUnsupportedError(c *C) {
+ // NB: this scenario can only happen if there is a bug
+ // we ask the store to filter what is returned by max supported format!
+
+ restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 111)
+ defer restore()
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ var snapDeclFoo1 *asserts.SnapDeclaration
+ (func() {
+ restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 999)
+ defer restore()
+ snapDeclFoo1 = s.snapDecl(c, "foo", map[string]interface{}{
+ "format": "999",
+ "revision": "1",
+ })
+ })()
+ c.Check(snapDeclFoo1.Revision(), Equals, 1)
+
+ ref := &asserts.Ref{
+ Type: asserts.SnapDeclarationType,
+ PrimaryKey: []string{"16", "foo-id"},
+ }
+ fetching := func(f asserts.Fetcher) error {
+ return f.Fetch(ref)
+ }
+
+ s.fakeStore.(*fakeStore).maxDeclSupportedFormat = 999
+ err := assertstate.DoFetch(s.state, 0, s.trivialDeviceCtx, fetching)
+ c.Check(err, ErrorMatches, `(?s).*proposed "snap-declaration" assertion has format 999 but 111 is latest supported.*`)
+}
+
func (s *assertMgrSuite) setModel(model *asserts.Model) {
deviceCtx := &snapstatetest.TrivialDeviceContext{
DeviceModel: model,
@@ -780,60 +739,6 @@
c.Assert(chg.Err(), ErrorMatches, `(?s).*cannot install "f", snap "f" is undergoing a rename to "foo".*`)
}
-func (s *assertMgrSuite) TestValidateSnapSnapDeclIsTooNewFirstInstall(c *C) {
- c.Skip("the assertion service will make this scenario not possible")
-
- s.prereqSnapAssertions(c, 10)
-
- tempdir := c.MkDir()
- snapPath := filepath.Join(tempdir, "foo.snap")
- err := ioutil.WriteFile(snapPath, fakeSnap(10), 0644)
- c.Assert(err, IsNil)
-
- // update snap decl with one that is too new
- (func() {
- restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 999)
- defer restore()
- headers := map[string]interface{}{
- "format": "999",
- "revision": "1",
- "series": "16",
- "snap-id": "snap-id-1",
- "snap-name": "foo",
- "publisher-id": s.dev1Acct.AccountID(),
- "timestamp": time.Now().Format(time.RFC3339),
- }
- snapDecl, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "")
- c.Assert(err, IsNil)
- err = s.storeSigning.Add(snapDecl)
- c.Assert(err, IsNil)
- })()
-
- s.state.Lock()
- defer s.state.Unlock()
-
- chg := s.state.NewChange("install", "...")
- t := s.state.NewTask("validate-snap", "Fetch and check snap assertions")
- snapsup := snapstate.SnapSetup{
- SnapPath: snapPath,
- UserID: 0,
- SideInfo: &snap.SideInfo{
- RealName: "foo",
- SnapID: "snap-id-1",
- Revision: snap.R(10),
- },
- }
- t.Set("snap-setup", snapsup)
- chg.AddTask(t)
-
- s.state.Unlock()
- defer s.se.Stop()
- s.settle(c)
- s.state.Lock()
-
- c.Assert(chg.Err(), ErrorMatches, `(?s).*proposed "snap-declaration" assertion has format 999 but 0 is latest supported.*`)
-}
-
func (s *assertMgrSuite) snapDecl(c *C, name string, extraHeaders map[string]interface{}) *asserts.SnapDeclaration {
headers := map[string]interface{}{
"series": "16",
diff -Nru snapd-2.41+19.10.1/overlord/assertstate/helpers.go snapd-2.42.1+19.10/overlord/assertstate/helpers.go
--- snapd-2.41+19.10.1/overlord/assertstate/helpers.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/assertstate/helpers.go 2019-10-30 12:17:43.000000000 +0000
@@ -20,9 +20,6 @@
package assertstate
import (
- "fmt"
- "strings"
-
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/overlord/auth"
@@ -38,63 +35,24 @@
return auth.User(st, userID)
}
-type accumFetcher struct {
- asserts.Fetcher
- fetched []asserts.Assertion
-}
-
-// newAccumFetcher creates an accumFetcher used to retrieve assertions and later commit them to the system database in one go.
-func newAccumFetcher(db *asserts.Database, retrieve func(*asserts.Ref) (asserts.Assertion, error)) *accumFetcher {
- f := &accumFetcher{}
-
- save := func(a asserts.Assertion) error {
- f.fetched = append(f.fetched, a)
- return nil
- }
-
- f.Fetcher = asserts.NewFetcher(db, retrieve, save)
-
- return f
-}
-
-type commitError struct {
- errs []error
-}
+func doFetch(s *state.State, userID int, deviceCtx snapstate.DeviceContext, fetching func(asserts.Fetcher) error) error {
+ // TODO: once we have a bulk assertion retrieval endpoint this approach will change
-func (e *commitError) Error() string {
- l := []string{""}
- for _, e := range e.errs {
- l = append(l, e.Error())
- }
- return fmt.Sprintf("cannot add some assertions to the system database:%s", strings.Join(l, "\n - "))
-}
+ db := cachedDB(s)
-// commitTo does a best effort of adding all the fetched assertions to the system database.
-func commitTo(db *asserts.Database, assertions []asserts.Assertion) error {
- var errs []error
- for _, a := range assertions {
- err := db.Add(a)
- if asserts.IsUnaccceptedUpdate(err) {
- if _, ok := err.(*asserts.UnsupportedFormatError); ok {
- // we kept the old one, but log the issue
- logger.Noticef("Cannot update assertion: %v", err)
- }
- // be idempotent
- // system db has already the same or newer
- continue
- }
- if err != nil {
- errs = append(errs, err)
+ // this is a fallback in case of bugs, we ask the store
+ // to filter unsupported formats!
+ unsupported := func(ref *asserts.Ref, unsupportedErr error) error {
+ if _, err := ref.Resolve(db.Find); err != nil {
+ // nothing there yet or any other error
+ return unsupportedErr
}
+ // we keep the old one, but log the issue
+ logger.Noticef("Cannot update assertion %v: %v", ref, unsupportedErr)
+ return nil
}
- if len(errs) != 0 {
- return &commitError{errs: errs}
- }
- return nil
-}
-func doFetch(s *state.State, userID int, deviceCtx snapstate.DeviceContext, fetching func(asserts.Fetcher) error) error {
- // TODO: once we have a bulk assertion retrieval endpoint this approach will change
+ b := asserts.NewBatch(unsupported)
user, err := userFromUserID(s, userID)
if err != nil {
@@ -108,11 +66,8 @@
return sto.Assertion(ref.Type, ref.PrimaryKey, user)
}
- db := cachedDB(s)
- f := newAccumFetcher(db, retrieve)
-
s.Unlock()
- err = fetching(f)
+ err = b.Fetch(db, retrieve, fetching)
s.Lock()
if err != nil {
return err
@@ -121,5 +76,5 @@
// TODO: trigger w. caller a global sanity check if a is revoked
// (but try to save as much possible still),
// or err is a check error
- return commitTo(db, f.fetched)
+ return b.CommitTo(db, nil)
}
diff -Nru snapd-2.41+19.10.1/overlord/configstate/config/transaction.go snapd-2.42.1+19.10/overlord/configstate/config/transaction.go
--- snapd-2.41+19.10.1/overlord/configstate/config/transaction.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/configstate/config/transaction.go 2019-10-30 12:17:43.000000000 +0000
@@ -77,8 +77,13 @@
// check if we need to dive into a sub-config
var configm map[string]interface{}
if err := jsonutil.DecodeWithNumber(bytes.NewReader(*subCfg), &configm); err == nil {
- out = append(out, changes(cfgStr+"."+k, configm)...)
- continue
+ // curiously, json decoder decodes json.RawMessage("null") into a nil map, so no change is
+ // reported when we recurse into it. This happens when unsetting a key and the underlying
+ // config path doesn't exist.
+ if len(configm) > 0 {
+ out = append(out, changes(cfgStr+"."+k, configm)...)
+ continue
+ }
}
out = append(out, []string{cfgStr + "." + k}...)
default:
diff -Nru snapd-2.41+19.10.1/overlord/configstate/config/transaction_test.go snapd-2.42.1+19.10/overlord/configstate/config/transaction_test.go
--- snapd-2.41+19.10.1/overlord/configstate/config/transaction_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/configstate/config/transaction_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -117,6 +117,7 @@
`set doc={"one":1,"two":2}`,
`commit`,
`set doc.one=null`,
+ `changes core.doc.one`,
`get doc={"two":2}`,
`getunder doc={"one":1,"two":2}`,
`commit`,
@@ -127,6 +128,7 @@
// Nulls via dotted path, resuling in empty map
`set doc={"one":{"three":3},"two":2}`,
`set doc.one.three=null`,
+ `changes core.doc.one.three core.doc.two`,
`get doc={"one":{},"two":2}`,
`commit`,
`get doc={"one":{},"two":2}`,
@@ -137,6 +139,7 @@
`set doc.three={"four":4}`,
`get doc={"one":1,"two":2,"three":{"four":4}}`,
`set doc.three={"four":null}`,
+ `changes core.doc.one core.doc.three.four core.doc.two`,
`get doc={"one":1,"two":2,"three":{}}`,
`commit`,
`get doc={"one":1,"two":2,"three":{}}`,
@@ -155,13 +158,14 @@
// Nulls with mutating
`set doc={"one":{"two":2}}`,
`set doc.one.two=null`,
+ `changes core.doc.one.two`,
`set doc.one="foo"`,
`get doc.one="foo"`,
`commit`,
`get doc={"one":"foo"}`,
`getunder doc={"one":"foo"}`, // nils are not committed to state
}, {
- // Nulls, intermediate temporary maps get dropped
+ // Nulls, intermediate temporary maps
`set doc={"one":{"two":2}}`,
`commit`,
`set doc.one.three.four.five=null`,
@@ -170,27 +174,44 @@
`get doc={"one":{"two":2,"three":{"four":{}}}}`,
`getrootunder ={"doc":{"one":{"two":2,"three":{"four":{}}}}}`, // nils are not committed to state
}, {
- // Nulls, same transaction, intermediate non-existing maps get dropped
+ // Nulls, same transaction
`set doc={"one":{"two":2}}`,
`set doc.one.three.four.five=null`,
+ `changes core.doc.one.three.four.five core.doc.one.two`,
`get doc={"one":{"two":2,"three":{"four":{}}}}`,
`commit`,
`get doc={"one":{"two":2,"three":{"four":{}}}}`,
`getrootunder ={"doc":{"one":{"two":2,"three":{"four":{}}}}}`, // nils are not committed to state
}, {
- // Nulls, empty doc dropped
+ // Null leading to empty doc
`set doc={"one":1}`,
`set doc.one=null`,
+ `changes core.doc.one`,
`commit`,
`get doc={}`,
}, {
// Nulls leading to no snap configuration
`set doc="foo"`,
`set doc=null`,
+ `changes core.doc`,
`commit`,
`get doc=-`,
`getroot => snap "core" has no configuration`,
}, {
+ // set null over non-existing path
+ `set x.y.z=null`,
+ `changes core.x.y.z`,
+ `commit`,
+ `get x.y.z=-`,
+}, {
+ // set null over non-existing path with initial config
+ `set foo=bar`,
+ `commit`,
+ `set x=null`,
+ `changes core.x`,
+ `commit`,
+ `get x=-`,
+}, {
// Root doc
`set doc={"one":1,"two":2}`,
`changes core.doc.one core.doc.two`,
diff -Nru snapd-2.41+19.10.1/overlord/configstate/configcore/corecfg.go snapd-2.42.1+19.10/overlord/configstate/configcore/corecfg.go
--- snapd-2.41+19.10.1/overlord/configstate/configcore/corecfg.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/configstate/configcore/corecfg.go 2019-10-30 12:17:43.000000000 +0000
@@ -99,7 +99,7 @@
}
// Export experimental.* flags to a place easily accessible from snapd helpers.
- if err := handleExperimentalFlags(tr); err != nil {
+ if err := ExportExperimentalFlags(tr); err != nil {
return err
}
diff -Nru snapd-2.41+19.10.1/overlord/configstate/configcore/experimental.go snapd-2.42.1+19.10/overlord/configstate/configcore/experimental.go
--- snapd-2.41+19.10.1/overlord/configstate/configcore/experimental.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/configstate/configcore/experimental.go 2019-10-30 12:17:43.000000000 +0000
@@ -48,7 +48,7 @@
return nil
}
-func handleExperimentalFlags(tr config.Conf) error {
+func ExportExperimentalFlags(tr config.Conf) error {
dir := dirs.FeaturesDir
if err := os.MkdirAll(dir, 0755); err != nil {
return err
diff -Nru snapd-2.41+19.10.1/overlord/configstate/configmgr.go snapd-2.42.1+19.10/overlord/configstate/configmgr.go
--- snapd-2.41+19.10.1/overlord/configstate/configmgr.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/configstate/configmgr.go 2019-10-30 12:17:43.000000000 +0000
@@ -20,14 +20,17 @@
package configstate
import (
+ "fmt"
"regexp"
"github.com/snapcore/snapd/overlord/configstate/config"
"github.com/snapcore/snapd/overlord/configstate/configcore"
"github.com/snapcore/snapd/overlord/hookstate"
+ "github.com/snapcore/snapd/overlord/state"
)
var configcoreRun = configcore.Run
+var configcoreExportExperimentalFlags = configcore.ExportExperimentalFlags
func MockConfigcoreRun(f func(config.Conf) error) (restore func()) {
origConfigcoreRun := configcoreRun
@@ -37,7 +40,15 @@
}
}
-func Init(hookManager *hookstate.HookManager) {
+func MockConfigcoreExportExperimentalFlags(mock func(tr config.Conf) error) (restore func()) {
+ old := configcoreExportExperimentalFlags
+ configcoreExportExperimentalFlags = mock
+ return func() {
+ configcoreExportExperimentalFlags = old
+ }
+}
+
+func Init(st *state.State, hookManager *hookstate.HookManager) error {
// Most configuration is handled via the "configure" hook of the
// snaps. However some configuration is internally handled
hookManager.Register(regexp.MustCompile("^configure$"), newConfigureHandler)
@@ -50,4 +61,12 @@
ctx.Unlock()
return configcoreRun(tr)
})
+
+ st.Lock()
+ defer st.Unlock()
+ tr := config.NewTransaction(st)
+ if err := configcoreExportExperimentalFlags(tr); err != nil {
+ return fmt.Errorf("cannot export experimental config flags: %v", err)
+ }
+ return nil
}
diff -Nru snapd-2.41+19.10.1/overlord/configstate/configstate_test.go snapd-2.42.1+19.10/overlord/configstate/configstate_test.go
--- snapd-2.41+19.10.1/overlord/configstate/configstate_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/configstate/configstate_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -20,6 +20,7 @@
package configstate_test
import (
+ "fmt"
"time"
. "gopkg.in/check.v1"
@@ -31,6 +32,7 @@
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/testutil"
)
type tasksetsSuite struct {
@@ -214,17 +216,26 @@
}
type configcoreHijackSuite struct {
+ testutil.BaseTest
+
o *overlord.Overlord
state *state.State
}
func (s *configcoreHijackSuite) SetUpTest(c *C) {
+ s.BaseTest.SetUpTest(c)
s.o = overlord.Mock()
s.state = s.o.State()
hookMgr, err := hookstate.Manager(s.state, s.o.TaskRunner())
c.Assert(err, IsNil)
s.o.AddManager(hookMgr)
- configstate.Init(hookMgr)
+ r := configstate.MockConfigcoreExportExperimentalFlags(func(_ config.Conf) error {
+ return nil
+ })
+ s.AddCleanup(r)
+
+ err = configstate.Init(s.state, hookMgr)
+ c.Assert(err, IsNil)
s.o.AddManager(s.o.TaskRunner())
}
@@ -309,3 +320,52 @@
c.Assert(configstate.RemapSnapFromRequest("system"), Equals, "core")
c.Assert(configstate.RemapSnapToResponse("core"), Equals, "system")
}
+
+type configcoreExportSuite struct {
+ o *overlord.Overlord
+ state *state.State
+ hookMgr *hookstate.HookManager
+}
+
+func (s *configcoreExportSuite) SetUpTest(c *C) {
+ s.o = overlord.Mock()
+ s.state = s.o.State()
+ hookMgr, err := hookstate.Manager(s.state, s.o.TaskRunner())
+ c.Assert(err, IsNil)
+ s.o.AddManager(hookMgr)
+ s.hookMgr = hookMgr
+}
+
+func (s *configcoreExportSuite) TestExportHappy(c *C) {
+ var calls int
+ var val string
+
+ tr := config.NewTransaction(s.state)
+ tr.Set("core", "experimental.key", "foobar")
+ tr.Commit()
+
+ r := configstate.MockConfigcoreExportExperimentalFlags(func(conf config.Conf) error {
+ calls++
+ err := conf.Get("core", "experimental.keys", &val)
+ c.Assert(err, IsNil)
+ return nil
+ })
+ defer r()
+ err := configstate.Init(s.state, s.hookMgr)
+ c.Assert(err, IsNil)
+ c.Assert(calls, Equals, 1)
+ c.Assert(val, Equals, "foobar")
+}
+
+func (s *configcoreExportSuite) TestExportErr(c *C) {
+ var calls int
+
+ r := configstate.MockConfigcoreExportExperimentalFlags(func(conf config.Conf) error {
+ calls++
+ return fmt.Errorf("bad bad")
+ })
+ defer r()
+ err := configstate.Init(s.state, s.hookMgr)
+ c.Assert(err, ErrorMatches, "cannot export experimental config flags: bad bad")
+ c.Assert(calls, Equals, 1)
+}
diff -Nru snapd-2.41+19.10.1/overlord/configstate/export_test.go snapd-2.42.1+19.10/overlord/configstate/export_test.go
--- snapd-2.41+19.10.1/overlord/configstate/export_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/configstate/export_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -20,3 +20,4 @@
package configstate
var NewConfigureHandler = newConfigureHandler
+var SortPatchKeysByDepth = sortPatchKeysByDepth
diff -Nru snapd-2.41+19.10.1/overlord/configstate/helpers.go snapd-2.42.1+19.10/overlord/configstate/helpers.go
--- snapd-2.41+19.10.1/overlord/configstate/helpers.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/configstate/helpers.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,42 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package configstate
+
+import (
+ "sort"
+ "strings"
+)
+
+func sortPatchKeysByDepth(patch map[string]interface{}) []string {
+ if len(patch) == 0 {
+ return nil
+ }
+ depths := make(map[string]int, len(patch))
+ keys := make([]string, 0, len(patch))
+ for k := range patch {
+ depths[k] = strings.Count(k, ".")
+ keys = append(keys, k)
+ }
+
+ sort.Slice(keys, func(i, j int) bool {
+ return depths[keys[i]] < depths[keys[j]]
+ })
+ return keys
+}
diff -Nru snapd-2.41+19.10.1/overlord/configstate/helpers_test.go snapd-2.42.1+19.10/overlord/configstate/helpers_test.go
--- snapd-2.41+19.10.1/overlord/configstate/helpers_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/configstate/helpers_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,45 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package configstate_test
+
+import (
+ "github.com/snapcore/snapd/overlord/configstate"
+
+ . "gopkg.in/check.v1"
+)
+
+func (s *miscSuite) TestSortPatchKeysEmpty(c *C) {
+ patch := map[string]interface{}{}
+ keys := configstate.SortPatchKeysByDepth(patch)
+ c.Assert(keys, IsNil)
+}
+
+func (s *miscSuite) TestSortPatchKeys(c *C) {
+ patch := map[string]interface{}{
+ "a.b.c": 0,
+ "a": 0,
+ "a.b.c.d": 0,
+ "q.w.e.r.t.y.u": 0,
+ "f.g": 0,
+ }
+
+ keys := configstate.SortPatchKeysByDepth(patch)
+ c.Assert(keys, DeepEquals, []string{"a", "f.g", "a.b.c", "a.b.c.d", "q.w.e.r.t.y.u"})
+}
diff -Nru snapd-2.41+19.10.1/overlord/configstate/hooks.go snapd-2.42.1+19.10/overlord/configstate/hooks.go
--- snapd-2.41+19.10.1/overlord/configstate/hooks.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/configstate/hooks.go 2019-10-30 12:17:43.000000000 +0000
@@ -112,8 +112,9 @@
}
}
- for key, value := range patch {
- if err := tr.Set(instanceName, key, value); err != nil {
+ patchKeys := sortPatchKeysByDepth(patch)
+ for _, key := range patchKeys {
+ if err := tr.Set(instanceName, key, patch[key]); err != nil {
return err
}
}
diff -Nru snapd-2.41+19.10.1/overlord/devicestate/devicemgr.go snapd-2.42.1+19.10/overlord/devicestate/devicemgr.go
--- snapd-2.41+19.10.1/overlord/devicestate/devicemgr.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/devicestate/devicemgr.go 2019-10-30 12:17:43.000000000 +0000
@@ -27,7 +27,7 @@
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/asserts/sysdb"
- "github.com/snapcore/snapd/bootloader"
+ "github.com/snapcore/snapd/boot"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/i18n"
"github.com/snapcore/snapd/overlord/assertstate"
@@ -438,11 +438,7 @@
}
if !m.bootOkRan {
- loader, err := bootloader.Find()
- if err != nil {
- return fmt.Errorf(i18n.G("cannot mark boot successful: %s"), err)
- }
- if err := bootloader.MarkBootSuccessful(loader); err != nil {
+ if err := boot.MarkBootSuccessful(); err != nil {
return err
}
m.bootOkRan = true
diff -Nru snapd-2.41+19.10.1/overlord/devicestate/devicestate.go snapd-2.42.1+19.10/overlord/devicestate/devicestate.go
--- snapd-2.41+19.10.1/overlord/devicestate/devicestate.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/devicestate/devicestate.go 2019-10-30 12:17:43.000000000 +0000
@@ -39,6 +39,7 @@
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/naming"
)
var (
@@ -289,26 +290,9 @@
return false
}
-func getAllRequiredSnapsForModel(model *asserts.Model) map[string]bool {
- reqSnaps := model.RequiredSnaps()
- // +4 for (snapd, base, gadget, kernel)
- required := make(map[string]bool, len(reqSnaps)+4)
- for _, snap := range reqSnaps {
- required[snap] = true
- }
- if model.Base() != "" {
- required["snapd"] = true
- required[model.Base()] = true
- } else {
- required["core"] = true
- }
- if model.Kernel() != "" {
- required[model.Kernel()] = true
- }
- if model.Gadget() != "" {
- required[model.Gadget()] = true
- }
- return required
+func getAllRequiredSnapsForModel(model *asserts.Model) *naming.SnapSet {
+ reqSnaps := model.RequiredWithEssentialSnaps()
+ return naming.NewSnapSet(reqSnaps)
}
// extractDownloadInstallEdgesFromTs extracts the first, last download
@@ -336,23 +320,40 @@
func remodelTasks(ctx context.Context, st *state.State, current, new *asserts.Model, deviceCtx snapstate.DeviceContext, fromChange string) ([]*state.TaskSet, error) {
userID := 0
+ var tss []*state.TaskSet
// adjust kernel track
- var tss []*state.TaskSet
- if current.KernelTrack() != new.KernelTrack() {
+ if current.Kernel() == new.Kernel() && current.KernelTrack() != new.KernelTrack() {
ts, err := snapstateUpdateWithDeviceContext(st, new.Kernel(), &snapstate.RevisionOptions{Channel: new.KernelTrack()}, userID, snapstate.Flags{NoReRefresh: true}, deviceCtx, fromChange)
if err != nil {
return nil, err
}
tss = append(tss, ts)
}
+ // add new kernel
+ if current.Kernel() != new.Kernel() {
+ // TODO: we need to support corner cases here like:
+ // 0. start with "old-kernel"
+ // 1. remodel to "new-kernel"
+ // 2. remodel back to "old-kernel"
+ // In step (2) we will get a "already-installed" error
+ // here right now (workaround: remove "old-kernel")
+ ts, err := snapstateInstallWithDeviceContext(ctx, st, new.Kernel(), &snapstate.RevisionOptions{Channel: new.KernelTrack()}, userID, snapstate.Flags{}, deviceCtx, fromChange)
+ if err != nil {
+ return nil, err
+ }
+ tss = append(tss, ts)
+ }
+
// add new required-snaps, no longer required snaps will be cleaned
// in "set-model"
- for _, snapName := range new.RequiredSnaps() {
- _, err := snapstate.CurrentInfo(st, snapName)
+ for _, snapRef := range new.RequiredNoEssentialSnaps() {
+ // TODO|XXX: have methods that take refs directly
+ // to respect the snap ids
+ _, err := snapstate.CurrentInfo(st, snapRef.SnapName())
// If the snap is not installed we need to install it now.
if _, ok := err.(*snap.NotInstalledError); ok {
- ts, err := snapstateInstallWithDeviceContext(ctx, st, snapName, nil, userID, snapstate.Flags{Required: true}, deviceCtx, fromChange)
+ ts, err := snapstateInstallWithDeviceContext(ctx, st, snapRef.SnapName(), nil, userID, snapstate.Flags{Required: true}, deviceCtx, fromChange)
if err != nil {
return nil, err
}
@@ -476,11 +477,6 @@
if current.Base() != new.Base() {
return nil, fmt.Errorf("cannot remodel to different bases yet")
}
- // FIXME: we need to support this soon but right now only a single
- // snap of type "gadget/kernel" is allowed so this needs work
- if current.Kernel() != new.Kernel() {
- return nil, fmt.Errorf("cannot remodel to different kernels yet")
- }
if current.Gadget() != new.Gadget() {
return nil, fmt.Errorf("cannot remodel to different gadgets yet")
}
diff -Nru snapd-2.41+19.10.1/overlord/devicestate/devicestatetest/devicesvc.go snapd-2.42.1+19.10/overlord/devicestate/devicestatetest/devicesvc.go
--- snapd-2.41+19.10.1/overlord/devicestate/devicestatetest/devicesvc.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/devicestate/devicestatetest/devicesvc.go 2019-10-30 12:17:43.000000000 +0000
@@ -37,13 +37,14 @@
type DeviceServiceBehavior struct {
ReqID string
- RequestIDURLPath string
- SerialURLPath string
+ RequestIDURLPath string
+ SerialURLPath string
+ ExpectedCapabilities string
Head func(c *C, bhv *DeviceServiceBehavior, w http.ResponseWriter, r *http.Request)
PostPreflight func(c *C, bhv *DeviceServiceBehavior, w http.ResponseWriter, r *http.Request)
- SignSerial func(c *C, bhv *DeviceServiceBehavior, headers map[string]interface{}, body []byte) (asserts.Assertion, error)
+ SignSerial func(c *C, bhv *DeviceServiceBehavior, headers map[string]interface{}, body []byte) (serial asserts.Assertion, ancillary []asserts.Assertion, err error)
}
// Request IDs for hard-coded behaviors.
@@ -67,6 +68,10 @@
bhv.RequestIDURLPath = requestIDURLPath
bhv.SerialURLPath = serialURLPath
}
+ // currently supported
+ if bhv.ExpectedCapabilities == "" {
+ bhv.ExpectedCapabilities = "serial-stream"
+ }
var mu sync.Mutex
count := 0
@@ -104,6 +109,7 @@
io.WriteString(w, fmt.Sprintf(`{"request-id": "%s"}`, bhv.ReqID))
case bhv.SerialURLPath:
c.Check(r.Header.Get("User-Agent"), Equals, expectedUserAgent)
+ c.Check(r.Header.Get("Snap-Device-Capabilities"), Equals, bhv.ExpectedCapabilities)
mu.Lock()
serialNum := 9999 + count
@@ -159,7 +165,7 @@
} else {
c.Check(extra, HasLen, 0)
}
- serial, err := bhv.SignSerial(c, bhv, map[string]interface{}{
+ serial, ancillary, err := bhv.SignSerial(c, bhv, map[string]interface{}{
"authority-id": "canonical",
"brand-id": brandID,
"model": model,
@@ -171,11 +177,18 @@
c.Assert(err, IsNil)
w.Header().Set("Content-Type", asserts.MediaType)
w.WriteHeader(200)
- encoded := asserts.Encode(serial)
if reqID == ReqIDSerialWithBadModel {
+ encoded := asserts.Encode(serial)
+
encoded = bytes.Replace(encoded, []byte("model: pc"), []byte("model: bad-model-foo"), 1)
+ w.Write(encoded)
+ return
+ }
+ enc := asserts.NewEncoder(w)
+ enc.Encode(serial)
+ for _, a := range ancillary {
+ enc.Encode(a)
}
- w.Write(encoded)
}
}))
}
diff -Nru snapd-2.41+19.10.1/overlord/devicestate/devicestate_test.go snapd-2.42.1+19.10/overlord/devicestate/devicestate_test.go
--- snapd-2.41+19.10.1/overlord/devicestate/devicestate_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/devicestate/devicestate_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -41,8 +41,8 @@
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/asserts/assertstest"
"github.com/snapcore/snapd/asserts/sysdb"
- "github.com/snapcore/snapd/boot/boottest"
"github.com/snapcore/snapd/bootloader"
+ "github.com/snapcore/snapd/bootloader/bootloadertest"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/gadget"
"github.com/snapcore/snapd/httputil"
@@ -82,13 +82,15 @@
mgr *devicestate.DeviceManager
db *asserts.Database
- bootloader *boottest.MockBootloader
+ bootloader *bootloadertest.MockBootloader
storeSigning *assertstest.StoreStack
brands *assertstest.SigningAccounts
reqID string
+ ancillary []asserts.Assertion
+
restartRequests []state.RestartType
restoreOnClassic func()
@@ -134,7 +136,7 @@
s.restoreSanitize = snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})
- s.bootloader = boottest.NewMockBootloader("mock", c.MkDir())
+ s.bootloader = bootloadertest.Mock("mock", c.MkDir())
bootloader.Force(s.bootloader)
s.restoreOnClassic = release.MockOnClassic(false)
@@ -202,6 +204,7 @@
}
func (s *deviceMgrSuite) TearDownTest(c *C) {
+ s.ancillary = nil
s.state.Lock()
assertstate.ReplaceDB(s.state, nil)
s.state.Unlock()
@@ -225,7 +228,7 @@
chg.SetStatus(state.DoingStatus)
}
-func (s *deviceMgrSuite) signSerial(c *C, bhv *devicestatetest.DeviceServiceBehavior, headers map[string]interface{}, body []byte) (asserts.Assertion, error) {
+func (s *deviceMgrSuite) signSerial(c *C, bhv *devicestatetest.DeviceServiceBehavior, headers map[string]interface{}, body []byte) (serial asserts.Assertion, ancillary []asserts.Assertion, err error) {
brandID := headers["brand-id"].(string)
model := headers["model"].(string)
keyID := ""
@@ -246,7 +249,8 @@
default:
c.Fatalf("unknown model: %s", model)
}
- return signing.Sign(asserts.SerialType, headers, body, keyID)
+ a, err := signing.Sign(asserts.SerialType, headers, body, keyID)
+ return a, s.ancillary, err
}
func (s *deviceMgrSuite) mockServer(c *C, reqID string, bhv *devicestatetest.DeviceServiceBehavior) *httptest.Server {
@@ -256,6 +260,7 @@
bhv.ReqID = reqID
bhv.SignSerial = s.signSerial
+ bhv.ExpectedCapabilities = "serial-stream"
return devicestatetest.MockDeviceService(c, bhv)
}
@@ -2030,8 +2035,8 @@
return serial.(*asserts.Serial)
}
-func (s *deviceMgrSuite) makeSerialAssertionInState(c *C, brandID, model, serialN string) {
- makeSerialAssertionInState(c, s.brands, s.state, brandID, model, serialN)
+func (s *deviceMgrSuite) makeSerialAssertionInState(c *C, brandID, model, serialN string) *asserts.Serial {
+ return makeSerialAssertionInState(c, s.brands, s.state, brandID, model, serialN)
}
func (s *deviceMgrSuite) TestCanAutoRefreshOnCore(c *C) {
@@ -2498,7 +2503,6 @@
}{
{map[string]string{"architecture": "pdp-7"}, "cannot remodel to different architectures yet"},
{map[string]string{"base": "core20"}, "cannot remodel to different bases yet"},
- {map[string]string{"kernel": "other-kernel"}, "cannot remodel to different kernels yet"},
{map[string]string{"gadget": "other-gadget"}, "cannot remodel to different gadgets yet"},
} {
// copy current model unless new model test data is different
@@ -2520,7 +2524,7 @@
}
}
-func (s *deviceMgrSuite) TestRemodelTasksSmoke(c *C) {
+func (s *deviceMgrSuite) TestRemodelTasksSwitchKernelTrack(c *C) {
s.state.Lock()
defer s.state.Unlock()
s.state.Set("seeded", true)
@@ -2593,6 +2597,60 @@
c.Assert(tss, HasLen, 4)
}
+func (s *deviceMgrSuite) TestRemodelTasksSwitchKernel(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+ s.state.Set("seeded", true)
+ s.state.Set("refresh-privacy-key", "some-privacy-key")
+
+ var testDeviceCtx snapstate.DeviceContext
+
+ restore := devicestate.MockSnapstateInstallWithDeviceContext(func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) {
+ c.Check(deviceCtx, Equals, testDeviceCtx)
+ c.Check(name, Equals, "other-kernel")
+ c.Check(opts.Channel, Equals, "18")
+
+ tDownload := s.state.NewTask("fake-download", fmt.Sprintf("Download %s", name))
+ tValidate := s.state.NewTask("validate-snap", fmt.Sprintf("Validate %s", name))
+ tValidate.WaitFor(tDownload)
+ tInstall := s.state.NewTask("fake-install", fmt.Sprintf("Install %s", name))
+ tInstall.WaitFor(tValidate)
+ ts := state.NewTaskSet(tDownload, tValidate, tInstall)
+ ts.MarkEdge(tValidate, snapstate.DownloadAndChecksDoneEdge)
+ return ts, nil
+ })
+ defer restore()
+
+ // set a model assertion
+ current := s.brands.Model("canonical", "pc-model", map[string]interface{}{
+ "architecture": "amd64",
+ "kernel": "pc-kernel",
+ "gadget": "pc",
+ "base": "core18",
+ })
+ err := assertstate.Add(s.state, current)
+ c.Assert(err, IsNil)
+ devicestatetest.SetDevice(s.state, &auth.DeviceState{
+ Brand: "canonical",
+ Model: "pc-model",
+ })
+
+ new := s.brands.Model("canonical", "pc-model", map[string]interface{}{
+ "architecture": "amd64",
+ "kernel": "other-kernel=18",
+ "gadget": "pc",
+ "base": "core18",
+ "revision": "1",
+ })
+
+ testDeviceCtx = &snapstatetest.TrivialDeviceContext{Remodeling: true}
+
+ tss, err := devicestate.RemodelTasks(context.Background(), s.state, current, new, testDeviceCtx, "99")
+ c.Assert(err, IsNil)
+ // 1 new kernel plus the remodel task
+ c.Assert(tss, HasLen, 2)
+}
+
func (s *deviceMgrSuite) TestRemodelRequiredSnaps(c *C) {
s.state.Lock()
defer s.state.Unlock()
@@ -3180,16 +3238,14 @@
c.Check(devicestate.Remodeling(s.state), Equals, false)
}
-func (s *deviceMgrSuite) TestDoRequestSerialReregistration(c *C) {
+func (s *deviceMgrSuite) testDoRequestSerialReregistration(c *C, setAncillary func(origSerial *asserts.Serial)) *state.Task {
mockServer := s.mockServer(c, "REQID-1", nil)
defer mockServer.Close()
restore := devicestate.MockBaseStoreURL(mockServer.URL)
defer restore()
- assertstest.AddMany(s.storeSigning, s.brands.AccountsAndKeys("rereg-brand")...)
-
- // setup state as done by first-boot/Ensure/doGenerateDeviceKey
+ // setup state as after initial registration
s.state.Lock()
defer s.state.Unlock()
@@ -3210,7 +3266,12 @@
devicestate.KeypairManager(s.mgr).Put(devKey)
// have a serial assertion
- s.makeSerialAssertionInState(c, "my-brand", "my-model", "9999")
+ serial0 := s.makeSerialAssertionInState(c, "my-brand", "my-model", "9999")
+ // give a chance to the test to setup returning a stream vs
+ // just the serial assertion
+ if setAncillary != nil {
+ setAncillary(serial0)
+ }
new := s.brands.Model("rereg-brand", "rereg-model", map[string]interface{}{
"architecture": "amd64",
@@ -3252,6 +3313,18 @@
s.se.Wait()
s.state.Lock()
+ return t
+}
+
+func (s *deviceMgrSuite) TestDoRequestSerialReregistration(c *C) {
+ assertstest.AddMany(s.storeSigning, s.brands.AccountsAndKeys("rereg-brand")...)
+
+ t := s.testDoRequestSerialReregistration(c, nil)
+
+ s.state.Lock()
+ defer s.state.Unlock()
+ chg := t.Change()
+
c.Check(chg.Status(), Equals, state.DoneStatus, Commentf("%s", t.Log()))
c.Check(chg.Err(), IsNil)
device, err := devicestatetest.Device(s.state)
@@ -3265,6 +3338,66 @@
c.Assert(err, IsNil)
}
+func (s *deviceMgrSuite) TestDoRequestSerialReregistrationStreamFromService(c *C) {
+ setAncillary := func(_ *asserts.Serial) {
+ // sets up such that re-registration returns a stream
+ // of assertions
+ s.ancillary = s.brands.AccountsAndKeys("rereg-brand")
+ }
+
+ t := s.testDoRequestSerialReregistration(c, setAncillary)
+
+ s.state.Lock()
+ defer s.state.Unlock()
+ chg := t.Change()
+
+ c.Check(chg.Status(), Equals, state.DoneStatus, Commentf("%s", t.Log()))
+ c.Check(chg.Err(), IsNil)
+ device, err := devicestatetest.Device(s.state)
+ c.Check(err, IsNil)
+ c.Check(device.Serial, Equals, "9999")
+ _, err = s.db.Find(asserts.SerialType, map[string]string{
+ "brand-id": "rereg-brand",
+ "model": "rereg-model",
+ "serial": "9999",
+ })
+ c.Assert(err, IsNil)
+}
+
+func (s *deviceMgrSuite) TestDoRequestSerialReregistrationIncompleteStreamFromService(c *C) {
+ setAncillary := func(_ *asserts.Serial) {
+ // will produce an incomplete stream!
+ s.ancillary = s.brands.AccountsAndKeys("rereg-brand")[:1]
+ }
+
+ t := s.testDoRequestSerialReregistration(c, setAncillary)
+
+ s.state.Lock()
+ defer s.state.Unlock()
+ chg := t.Change()
+
+ c.Check(chg.Status(), Equals, state.ErrorStatus, Commentf("%s", t.Log()))
+ c.Check(chg.Err(), ErrorMatches, `(?ms).*cannot accept stream of assertions from device service:.*`)
+}
+
+func (s *deviceMgrSuite) TestDoRequestSerialReregistrationDoubleSerialStreamFromService(c *C) {
+ setAncillary := func(serial0 *asserts.Serial) {
+ // will produce a stream with confusingly two serial
+ // assertions
+ s.ancillary = s.brands.AccountsAndKeys("rereg-brand")
+ s.ancillary = append(s.ancillary, serial0)
+ }
+
+ t := s.testDoRequestSerialReregistration(c, setAncillary)
+
+ s.state.Lock()
+ defer s.state.Unlock()
+ chg := t.Change()
+
+ c.Check(chg.Status(), Equals, state.ErrorStatus, Commentf("%s", t.Log()))
+ c.Check(chg.Err(), ErrorMatches, `(?ms).*cannot accept more than a single device serial assertion from the device service.*`)
+}
+
func (s *deviceMgrSuite) TestDeviceCtxNoTask(c *C) {
s.state.Lock()
defer s.state.Unlock()
@@ -3668,7 +3801,7 @@
expectedRollbackDir := filepath.Join(dirs.SnapRollbackDir, "foo-gadget_34")
updaterForStructureCalls := 0
- gadget.MockUpdaterForStructure(func(ps *gadget.PositionedStructure, rootDir, rollbackDir string) (gadget.Updater, error) {
+ gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, rootDir, rollbackDir string) (gadget.Updater, error) {
updaterForStructureCalls++
c.Assert(ps.Name, Equals, "foo")
diff -Nru snapd-2.41+19.10.1/overlord/devicestate/firstboot.go snapd-2.42.1+19.10/overlord/devicestate/firstboot.go
--- snapd-2.41+19.10.1/overlord/devicestate/firstboot.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/devicestate/firstboot.go 2019-10-30 12:17:43.000000000 +0000
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2014-2015 Canonical Ltd
+ * Copyright (C) 2014-2019 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -22,28 +22,27 @@
import (
"errors"
"fmt"
- "io/ioutil"
- "os"
- "path/filepath"
"sort"
"github.com/snapcore/snapd/asserts"
- "github.com/snapcore/snapd/asserts/snapasserts"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/i18n"
- "github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/overlord/assertstate"
"github.com/snapcore/snapd/overlord/devicestate/internal"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/release"
+ "github.com/snapcore/snapd/seed"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/timings"
)
var errNothingToDo = errors.New("nothing to do")
-func installSeedSnap(st *state.State, sn *snap.SeedSnap, flags snapstate.Flags, tm timings.Measurer) (*state.TaskSet, *snap.Info, error) {
+func installSeedSnap(st *state.State, sn *seed.Snap, flags snapstate.Flags) (*state.TaskSet, *snap.Info, error) {
+ if sn.Required {
+ flags.Required = true
+ }
if sn.Classic {
flags.Classic = true
}
@@ -51,29 +50,7 @@
flags.DevMode = true
}
- path := filepath.Join(dirs.SnapSeedDir, "snaps", sn.File)
-
- var sideInfo snap.SideInfo
- if sn.Unasserted {
- sideInfo.RealName = sn.Name
- } else {
- var si *snap.SideInfo
- var err error
- timings.Run(tm, "derive-side-info", fmt.Sprintf("hash and derive side info for snap %q", sn.Name), func(nested timings.Measurer) {
- si, err = snapasserts.DeriveSideInfo(path, assertstate.DB(st))
- })
- if asserts.IsNotFound(err) {
- return nil, nil, fmt.Errorf("cannot find signatures with metadata for snap %q (%q)", sn.Name, path)
- }
- if err != nil {
- return nil, nil, err
- }
- sideInfo = *si
- sideInfo.Private = sn.Private
- sideInfo.Contact = sn.Contact
- }
-
- return snapstate.InstallPath(st, &sideInfo, path, "", sn.Channel, flags)
+ return snapstate.InstallPath(st, sn.SideInfo, sn.Path, "", sn.Channel, flags)
}
func trivialSeeding(st *state.State, markSeeded *state.Task) []*state.TaskSet {
@@ -97,10 +74,15 @@
markSeeded := st.NewTask("mark-seeded", i18n.G("Mark system seeded"))
+ deviceSeed, err := seed.Open(dirs.SnapSeedDir)
+ if err != nil {
+ return nil, err
+ }
+
// ack all initial assertions
var model *asserts.Model
timings.Run(tm, "import-assertions", "import assertions from seed", func(nested timings.Measurer) {
- model, err = importAssertionsFromSeed(st)
+ model, err = importAssertionsFromSeed(st, deviceSeed)
})
if err == errNothingToDo {
return trivialSeeding(st, markSeeded), nil
@@ -109,23 +91,23 @@
return nil, err
}
- seedYamlFile := filepath.Join(dirs.SnapSeedDir, "seed.yaml")
- if release.OnClassic && !osutil.FileExists(seedYamlFile) {
+ err = deviceSeed.LoadMeta(tm)
+ if release.OnClassic && err == seed.ErrNoMeta {
// on classic it is ok to not seed any snaps
return trivialSeeding(st, markSeeded), nil
}
-
- seed, err := snap.ReadSeedYaml(seedYamlFile)
if err != nil {
return nil, err
}
- required := getAllRequiredSnapsForModel(model)
- seeding := make(map[string]*snap.SeedSnap, len(seed.Snaps))
- for _, sn := range seed.Snaps {
- seeding[sn.Name] = sn
+ essentialSeedSnaps := deviceSeed.EssentialSnaps()
+ seedSnaps, err := deviceSeed.ModeSnaps("run") // XXX mode should be passed in
+ if err != nil {
+ return nil, err
}
- alreadySeeded := make(map[string]bool, 3)
+
+ // allSnapInfos are collected for cross-check validation of bases
+ allSnapInfos := make(map[string]*snap.Info, len(essentialSeedSnaps)+len(seedSnaps))
tsAll := []*state.TaskSet{}
configTss := []*state.TaskSet{}
@@ -136,94 +118,40 @@
}
return append(all, ts)
}
-
- baseSnap := "core"
- classicWithSnapd := false
- if model.Base() != "" {
- baseSnap = model.Base()
- }
- if _, ok := seeding["snapd"]; release.OnClassic && ok {
- classicWithSnapd = true
- // there is no system-wide base as such
- // if there is a gadget we will install its base first though
- baseSnap = ""
- }
-
- var installSeedEssential func(snapName string) error
- var installGadgetBase func(gadget *snap.Info) error
- installSeedEssential = func(snapName string) error {
- // be idempotent for installGadgetBase use
- if alreadySeeded[snapName] {
- return nil
- }
- seedSnap := seeding[snapName]
- if seedSnap == nil {
- return fmt.Errorf("cannot proceed without seeding %q", snapName)
- }
- ts, info, err := installSeedSnap(st, seedSnap, snapstate.Flags{SkipConfigure: true, Required: true}, tm)
- if err != nil {
- return err
- }
- if info.GetType() == snap.TypeGadget {
- // always make sure the base of gadget is installed first
- if err := installGadgetBase(info); err != nil {
- return err
- }
- }
- tsAll = chainTs(tsAll, ts)
- alreadySeeded[snapName] = true
- return nil
- }
- installGadgetBase = func(gadget *snap.Info) error {
- gadgetBase := gadget.Base
- if gadgetBase == "" {
- gadgetBase = "core"
+ chainSorted := func(infos []*snap.Info, infoToTs map[*snap.Info]*state.TaskSet) {
+ sort.Stable(snap.ByType(infos))
+ for _, info := range infos {
+ ts := infoToTs[info]
+ tsAll = chainTs(tsAll, ts)
}
- // Sanity check
- // TODO: do we want to relax this? the new logic would allow
- // but it might just be confusing for now
- if baseSnap != "" && gadgetBase != baseSnap {
- return fmt.Errorf("cannot use gadget snap because its base %q is different from model base %q", gadgetBase, model.Base())
- }
- return installSeedEssential(gadgetBase)
}
- // if there are snaps to seed, core/base needs to be seeded too
- if len(seed.Snaps) != 0 {
- // ensure "snapd" snap is installed first
- if model.Base() != "" || classicWithSnapd {
- if err := installSeedEssential("snapd"); err != nil {
- return nil, err
- }
- }
- if !classicWithSnapd {
- if err := installSeedEssential(baseSnap); err != nil {
- return nil, err
- }
- }
+ essInfoToTs := make(map[*snap.Info]*state.TaskSet, len(essentialSeedSnaps))
+ essInfos := make([]*snap.Info, 0, len(essentialSeedSnaps))
+
+ if len(essentialSeedSnaps) != 0 {
// we *always* configure "core" here even if bases are used
// for booting. "core" if where the system config lives.
configTss = chainTs(configTss, snapstate.ConfigureSnap(st, "core", snapstate.UseConfigDefaults))
}
- if kernelName := model.Kernel(); kernelName != "" {
- if err := installSeedEssential(kernelName); err != nil {
- return nil, err
- }
- configTs := snapstate.ConfigureSnap(st, kernelName, snapstate.UseConfigDefaults)
- // wait for the previous configTss
- configTss = chainTs(configTss, configTs)
- }
-
- if gadgetName := model.Gadget(); gadgetName != "" {
- err := installSeedEssential(gadgetName)
+ for _, seedSnap := range essentialSeedSnaps {
+ ts, info, err := installSeedSnap(st, seedSnap, snapstate.Flags{SkipConfigure: true})
if err != nil {
return nil, err
}
- configTs := snapstate.ConfigureSnap(st, gadgetName, snapstate.UseConfigDefaults)
- // wait for the previous configTss
- configTss = chainTs(configTss, configTs)
- }
+ if info.GetType() == snap.TypeKernel || info.GetType() == snap.TypeGadget {
+ configTs := snapstate.ConfigureSnap(st, info.SnapName(), snapstate.UseConfigDefaults)
+ // wait for the previous configTss
+ configTss = chainTs(configTss, configTs)
+ }
+ essInfos = append(essInfos, info)
+ essInfoToTs[info] = ts
+ allSnapInfos[info.SnapName()] = info
+ }
+ // now add/chain the tasksets in the right order based on essential
+ // snap types
+ chainSorted(essInfos, essInfoToTs)
// chain together configuring core, kernel, and gadget after
// installing them so that defaults are availabble from gadget
@@ -233,34 +161,30 @@
}
// ensure we install in the right order
- infoToTs := make(map[*snap.Info]*state.TaskSet, len(seed.Snaps))
- infos := make([]*snap.Info, 0, len(seed.Snaps))
-
- for _, sn := range seed.Snaps {
- if alreadySeeded[sn.Name] {
- continue
- }
+ infoToTs := make(map[*snap.Info]*state.TaskSet, len(seedSnaps))
+ infos := make([]*snap.Info, 0, len(seedSnaps))
+ for _, seedSnap := range seedSnaps {
var flags snapstate.Flags
- if required[sn.Name] {
- flags.Required = true
- }
-
- ts, info, err := installSeedSnap(st, sn, flags, tm)
+ ts, info, err := installSeedSnap(st, seedSnap, flags)
if err != nil {
return nil, err
}
infos = append(infos, info)
infoToTs[info] = ts
+ allSnapInfos[info.SnapName()] = info
+ }
+
+ // validate that all snaps have bases
+ errs := snap.ValidateBasesAndProviders(allSnapInfos)
+ if errs != nil {
+ // only report the first error encountered
+ return nil, errs[0]
}
// now add/chain the tasksets in the right order, note that we
// only have tasksets that we did not already seeded
- sort.Stable(snap.ByType(infos))
- for _, info := range infos {
- ts := infoToTs[info]
- tsAll = chainTs(tsAll, ts)
- }
+ chainSorted(infos, infoToTs)
if len(tsAll) == 0 {
return nil, fmt.Errorf("cannot proceed, no snaps to seed")
@@ -283,26 +207,21 @@
return tsAll, nil
}
-func readAsserts(fn string, batch *assertstate.Batch) ([]*asserts.Ref, error) {
- f, err := os.Open(fn)
- if err != nil {
- return nil, err
- }
- defer f.Close()
- return batch.AddStream(f)
-}
-
-func importAssertionsFromSeed(st *state.State) (*asserts.Model, error) {
+func importAssertionsFromSeed(st *state.State, deviceSeed seed.Seed) (*asserts.Model, error) {
// TODO: use some kind of context fo Device/SetDevice?
device, err := internal.Device(st)
if err != nil {
return nil, err
}
+ // collect and
// set device,model from the model assertion
- assertSeedDir := filepath.Join(dirs.SnapSeedDir, "assertions")
- dc, err := ioutil.ReadDir(assertSeedDir)
- if release.OnClassic && os.IsNotExist(err) {
+ commitTo := func(batch *asserts.Batch) error {
+ return assertstate.AddBatch(st, batch, nil)
+ }
+
+ err = deviceSeed.LoadAssertions(assertstate.DB(st), commitTo)
+ if err == seed.ErrNoAssertions && release.OnClassic {
// on classic seeding is optional
// set the fallback model
err := setClassicFallbackModel(st, device)
@@ -312,41 +231,13 @@
return nil, errNothingToDo
}
if err != nil {
- return nil, fmt.Errorf("cannot read assert seed dir: %s", err)
- }
-
- // collect
- var modelRef *asserts.Ref
- batch := assertstate.NewBatch()
- for _, fi := range dc {
- fn := filepath.Join(assertSeedDir, fi.Name())
- refs, err := readAsserts(fn, batch)
- if err != nil {
- return nil, fmt.Errorf("cannot read assertions: %s", err)
- }
- for _, ref := range refs {
- if ref.Type == asserts.ModelType {
- if modelRef != nil && modelRef.Unique() != ref.Unique() {
- return nil, fmt.Errorf("cannot add more than one model assertion")
- }
- modelRef = ref
- }
- }
- }
- // verify we have one model assertion
- if modelRef == nil {
- return nil, fmt.Errorf("need a model assertion")
- }
-
- if err := batch.Commit(st); err != nil {
return nil, err
}
- a, err := modelRef.Resolve(assertstate.DB(st).Find)
+ modelAssertion, err := deviceSeed.Model()
if err != nil {
- return nil, fmt.Errorf("internal error: cannot find just added assertion %v: %v", modelRef, err)
+ return nil, err
}
- modelAssertion := a.(*asserts.Model)
classicModel := modelAssertion.Classic()
if release.OnClassic != classicModel {
diff -Nru snapd-2.41+19.10.1/overlord/devicestate/firstboot_test.go snapd-2.42.1+19.10/overlord/devicestate/firstboot_test.go
--- snapd-2.41+19.10.1/overlord/devicestate/firstboot_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/devicestate/firstboot_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -33,9 +33,8 @@
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/asserts/assertstest"
- "github.com/snapcore/snapd/asserts/sysdb"
- "github.com/snapcore/snapd/boot/boottest"
"github.com/snapcore/snapd/bootloader"
+ "github.com/snapcore/snapd/bootloader/bootloadertest"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/overlord"
@@ -50,6 +49,8 @@
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/release"
+ "github.com/snapcore/snapd/seed"
+ "github.com/snapcore/snapd/seed/seedtest"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snaptest"
"github.com/snapcore/snapd/systemd"
@@ -58,27 +59,31 @@
)
type FirstBootTestSuite struct {
+ testutil.BaseTest
+
systemctl *testutil.MockCmd
- storeSigning *assertstest.StoreStack
- restore func()
+ // TestingSeed helps populating seeds (it provides
+ // MakeAssertedSnap, WriteAssertions etc.) for tests.
+ *seedtest.TestingSeed
- brands *assertstest.SigningAccounts
+ devAcct *asserts.Account
overlord *overlord.Overlord
perfTimings timings.Measurer
-
- restoreOnClassic func()
- restoreBackends func()
}
var _ = Suite(&FirstBootTestSuite{})
func (s *FirstBootTestSuite) SetUpTest(c *C) {
+ s.BaseTest.SetUpTest(c)
+
tempdir := c.MkDir()
dirs.SetRootDir(tempdir)
- s.restoreOnClassic = release.MockOnClassic(false)
+ s.AddCleanup(func() { dirs.SetRootDir("/") })
+
+ s.AddCleanup(release.MockOnClassic(false))
// mock the world!
err := os.MkdirAll(filepath.Join(dirs.SnapSeedDir, "snaps"), 0755)
@@ -89,20 +94,27 @@
err = os.MkdirAll(dirs.SnapServicesDir, 0755)
c.Assert(err, IsNil)
os.Setenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS", "1")
+ s.AddCleanup(func() { os.Unsetenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS") })
s.systemctl = testutil.MockCommand(c, "systemctl", "")
+ s.AddCleanup(s.systemctl.Restore)
err = ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), nil, 0644)
c.Assert(err, IsNil)
- s.storeSigning = assertstest.NewStoreStack("can0nical", nil)
- s.restore = sysdb.InjectTrusted(s.storeSigning.Trusted)
-
- s.brands = assertstest.NewSigningAccounts(s.storeSigning)
- s.brands.Register("my-brand", brandPrivKey, map[string]interface{}{
+ s.TestingSeed = &seedtest.TestingSeed{}
+ s.SetupAssertSigning("can0nical", s)
+ s.Brands.Register("my-brand", brandPrivKey, map[string]interface{}{
"verification": "verified",
})
- s.restoreBackends = ifacestate.MockSecurityBackends(nil)
+ s.SnapsDir = filepath.Join(dirs.SnapSeedDir, "snaps")
+ s.AssertsDir = filepath.Join(dirs.SnapSeedDir, "assertions")
+
+ s.devAcct = assertstest.NewAccount(s.StoreSigning, "developer", map[string]interface{}{
+ "account-id": "developerid",
+ }, "")
+
+ s.AddCleanup(ifacestate.MockSecurityBackends(nil))
ovld, err := overlord.New(nil)
c.Assert(err, IsNil)
@@ -117,16 +129,6 @@
s.perfTimings = timings.New(nil)
}
-func (s *FirstBootTestSuite) TearDownTest(c *C) {
- os.Unsetenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS")
- s.systemctl.Restore()
-
- s.restore()
- s.restoreOnClassic()
- s.restoreBackends()
- dirs.SetRootDir("/")
-}
-
func checkTrivialSeeding(c *C, tsAll []*state.TaskSet) {
// run internal core config and mark seeded
c.Check(tsAll, HasLen, 2)
@@ -143,6 +145,31 @@
c.Check(tasks[0].Kind(), Equals, "mark-seeded")
}
+func (s *FirstBootTestSuite) modelHeaders(modelStr string, reqSnaps ...string) map[string]interface{} {
+ headers := map[string]interface{}{
+ "architecture": "amd64",
+ "store": "canonical",
+ }
+ if strings.HasSuffix(modelStr, "-classic") {
+ headers["classic"] = "true"
+ } else {
+ headers["kernel"] = "pc-kernel"
+ headers["gadget"] = "pc"
+ }
+ if len(reqSnaps) != 0 {
+ reqs := make([]interface{}, len(reqSnaps))
+ for i, req := range reqSnaps {
+ reqs[i] = req
+ }
+ headers["required-snaps"] = reqs
+ }
+ return headers
+}
+
+func (s *FirstBootTestSuite) makeModelAssertionChain(c *C, modName string, extraHeaders map[string]interface{}, reqSnaps ...string) []asserts.Assertion {
+ return s.MakeModelAssertionChain("my-brand", modName, s.modelHeaders(modName, reqSnaps...), extraHeaders)
+}
+
func (s *FirstBootTestSuite) TestPopulateFromSeedOnClassicNoop(c *C) {
restore := release.MockOnClassic(true)
defer restore()
@@ -185,13 +212,9 @@
c.Assert(err, IsNil)
st := ovld.State()
- // add a bunch of assert files
+ // add the model assertion and its chain
assertsChain := s.makeModelAssertionChain(c, "my-model-classic", nil)
- for i, as := range assertsChain {
- fn := filepath.Join(dirs.SnapSeedDir, "assertions", strconv.Itoa(i))
- err := ioutil.WriteFile(fn, asserts.Encode(as), 0644)
- c.Assert(err, IsNil)
- }
+ s.WriteAssertions("model.asserts", assertsChain...)
err = os.Remove(filepath.Join(dirs.SnapSeedDir, "seed.yaml"))
c.Assert(err, IsNil)
@@ -217,13 +240,9 @@
c.Assert(err, IsNil)
st := ovld.State()
- // add a bunch of assert files
+ // add the model assertion and its chain
assertsChain := s.makeModelAssertionChain(c, "my-model-classic", nil)
- for i, as := range assertsChain {
- fn := filepath.Join(dirs.SnapSeedDir, "assertions", strconv.Itoa(i))
- err := ioutil.WriteFile(fn, asserts.Encode(as), 0644)
- c.Assert(err, IsNil)
- }
+ s.WriteAssertions("model.asserts", assertsChain...)
// create an empty seed.yaml
err = ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), nil, 0644)
@@ -242,13 +261,9 @@
st := s.overlord.State()
- // add a bunch of assert files
+ // add the model assertion and its chain
assertsChain := s.makeModelAssertionChain(c, "my-model-classic", nil)
- for i, as := range assertsChain {
- fn := filepath.Join(dirs.SnapSeedDir, "assertions", strconv.Itoa(i))
- err := ioutil.WriteFile(fn, asserts.Encode(as), 0644)
- c.Assert(err, IsNil)
- }
+ s.WriteAssertions("model.asserts", assertsChain...)
err := os.Remove(filepath.Join(dirs.SnapSeedDir, "seed.yaml"))
c.Assert(err, IsNil)
@@ -324,67 +339,6 @@
c.Assert(err, ErrorMatches, "cannot populate state: already seeded")
}
-func (s *FirstBootTestSuite) makeAssertedSnap(c *C, snapYaml string, files [][]string, revision snap.Revision, developerID string) (snapFname string, snapDecl *asserts.SnapDeclaration, snapRev *asserts.SnapRevision) {
- info, err := snap.InfoFromSnapYaml([]byte(snapYaml))
- c.Assert(err, IsNil)
- snapName := info.InstanceName()
-
- mockSnapFile := snaptest.MakeTestSnapWithFiles(c, snapYaml, files)
- snapFname = filepath.Base(mockSnapFile)
-
- targetFile := filepath.Join(dirs.SnapSeedDir, "snaps", snapFname)
- err = os.Rename(mockSnapFile, targetFile)
- c.Assert(err, IsNil)
-
- snapID := (snapName + "-snap-" + strings.Repeat("id", 20))[:32]
- // FIXME: snapd is special in the interface policy code and it
- // identified by its snap-id. so we fake the real snap-id
- // here. Instead we should add a "type: snapd" for snaps.
- if snapName == "snapd" {
- snapID = "PMrrV4ml8uWuEUDBT8dSGnKUYbevVhc4"
- }
-
- declA, err := s.storeSigning.Sign(asserts.SnapDeclarationType, map[string]interface{}{
- "series": "16",
- "snap-id": snapID,
- "publisher-id": developerID,
- "snap-name": snapName,
- "timestamp": time.Now().UTC().Format(time.RFC3339),
- }, nil, "")
- c.Assert(err, IsNil)
-
- sha3_384, size, err := asserts.SnapFileSHA3_384(targetFile)
- c.Assert(err, IsNil)
-
- revA, err := s.storeSigning.Sign(asserts.SnapRevisionType, map[string]interface{}{
- "snap-sha3-384": sha3_384,
- "snap-size": fmt.Sprintf("%d", size),
- "snap-id": snapID,
- "developer-id": developerID,
- "snap-revision": revision.String(),
- "timestamp": time.Now().UTC().Format(time.RFC3339),
- }, nil, "")
- c.Assert(err, IsNil)
-
- return snapFname, declA.(*asserts.SnapDeclaration), revA.(*asserts.SnapRevision)
-}
-
-func checkSeedTasks(c *C, tsAll []*state.TaskSet) {
- // the tasks of the last taskset must be gadget-connect, mark-seeded
- lastTasks := tsAll[len(tsAll)-1].Tasks()
- c.Check(lastTasks, HasLen, 2)
- gadgetConnectTask := lastTasks[0]
- markSeededTask := lastTasks[1]
- c.Check(gadgetConnectTask.Kind(), Equals, "gadget-connect")
- c.Check(markSeededTask.Kind(), Equals, "mark-seeded")
- // and the gadget-connect must wait for the other tasks
- prevTasks := tsAll[len(tsAll)-2].Tasks()
- otherTask := prevTasks[len(prevTasks)-1]
- c.Check(gadgetConnectTask.WaitTasks(), testutil.Contains, otherTask)
- // add the mark-seeded waits for gadget-connects
- c.Check(markSeededTask.WaitTasks(), DeepEquals, []*state.Task{gadgetConnectTask})
-}
-
func (s *FirstBootTestSuite) makeCoreSnaps(c *C, extraGadgetYaml string) (coreFname, kernelFname, gadgetFname string) {
files := [][]string{}
if strings.Contains(extraGadgetYaml, "defaults:") {
@@ -395,17 +349,15 @@
snapYaml := `name: core
version: 1.0
type: os`
- coreFname, coreDecl, coreRev := s.makeAssertedSnap(c, snapYaml, files, snap.R(1), "canonical")
-
- writeAssertionsToFile("core.asserts", []asserts.Assertion{coreRev, coreDecl})
+ coreFname, coreDecl, coreRev := s.MakeAssertedSnap(c, snapYaml, files, snap.R(1), "canonical")
+ s.WriteAssertions("core.asserts", coreRev, coreDecl)
// put kernel snap into the SnapBlobDir
snapYaml = `name: pc-kernel
version: 1.0
type: kernel`
- kernelFname, kernelDecl, kernelRev := s.makeAssertedSnap(c, snapYaml, files, snap.R(1), "canonical")
-
- writeAssertionsToFile("kernel.asserts", []asserts.Assertion{kernelRev, kernelDecl})
+ kernelFname, kernelDecl, kernelRev := s.MakeAssertedSnap(c, snapYaml, files, snap.R(1), "canonical")
+ s.WriteAssertions("kernel.asserts", kernelRev, kernelDecl)
gadgetYaml := `
volumes:
@@ -420,50 +372,75 @@
snapYaml = `name: pc
version: 1.0
type: gadget`
- gadgetFname, gadgetDecl, gadgetRev := s.makeAssertedSnap(c, snapYaml, files, snap.R(1), "canonical")
-
- writeAssertionsToFile("gadget.asserts", []asserts.Assertion{gadgetRev, gadgetDecl})
+ gadgetFname, gadgetDecl, gadgetRev := s.MakeAssertedSnap(c, snapYaml, files, snap.R(1), "canonical")
+ s.WriteAssertions("gadget.asserts", gadgetRev, gadgetDecl)
return coreFname, kernelFname, gadgetFname
}
+func checkOrder(c *C, tsAll []*state.TaskSet, snaps ...string) {
+ matched := 0
+ var prevTask *state.Task
+ for i, ts := range tsAll {
+ task0 := ts.Tasks()[0]
+ waitTasks := task0.WaitTasks()
+ if i == 0 {
+ c.Check(waitTasks, HasLen, 0)
+ } else {
+ c.Check(waitTasks, testutil.Contains, prevTask)
+ }
+ prevTask = task0
+ if task0.Kind() != "prerequisites" {
+ continue
+ }
+ snapsup, err := snapstate.TaskSnapSetup(task0)
+ c.Assert(err, IsNil, Commentf("%#v", task0))
+ c.Check(snapsup.InstanceName(), Equals, snaps[matched])
+ matched++
+ }
+ c.Check(matched, Equals, len(snaps))
+}
+
+func checkSeedTasks(c *C, tsAll []*state.TaskSet) {
+ // the tasks of the last taskset must be gadget-connect, mark-seeded
+ lastTasks := tsAll[len(tsAll)-1].Tasks()
+ c.Check(lastTasks, HasLen, 2)
+ gadgetConnectTask := lastTasks[0]
+ markSeededTask := lastTasks[1]
+ c.Check(gadgetConnectTask.Kind(), Equals, "gadget-connect")
+ c.Check(markSeededTask.Kind(), Equals, "mark-seeded")
+ // and the gadget-connect must wait for the other tasks
+ prevTasks := tsAll[len(tsAll)-2].Tasks()
+ otherTask := prevTasks[len(prevTasks)-1]
+ c.Check(gadgetConnectTask.WaitTasks(), testutil.Contains, otherTask)
+ // add the mark-seeded waits for gadget-connects
+ c.Check(markSeededTask.WaitTasks(), DeepEquals, []*state.Task{gadgetConnectTask})
+}
+
func (s *FirstBootTestSuite) makeSeedChange(c *C, st *state.State) *state.Change {
coreFname, kernelFname, gadgetFname := s.makeCoreSnaps(c, "")
- devAcct := assertstest.NewAccount(s.storeSigning, "developer", map[string]interface{}{
- "account-id": "developerid",
- }, "")
- devAcctFn := filepath.Join(dirs.SnapSeedDir, "assertions", "developer.account")
- err := ioutil.WriteFile(devAcctFn, asserts.Encode(devAcct), 0644)
- c.Assert(err, IsNil)
+ s.WriteAssertions("developer.account", s.devAcct)
// put a firstboot snap into the SnapBlobDir
snapYaml := `name: foo
version: 1.0`
- fooFname, fooDecl, fooRev := s.makeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid")
+ fooFname, fooDecl, fooRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid")
+ s.WriteAssertions("foo.snap-declaration", fooDecl)
+ s.WriteAssertions("foo.snap-revision", fooRev)
// put a firstboot local snap into the SnapBlobDir
snapYaml = `name: local
version: 1.0`
mockSnapFile := snaptest.MakeTestSnapWithFiles(c, snapYaml, nil)
targetSnapFile2 := filepath.Join(dirs.SnapSeedDir, "snaps", filepath.Base(mockSnapFile))
- err = os.Rename(mockSnapFile, targetSnapFile2)
- c.Assert(err, IsNil)
-
- declFn := filepath.Join(dirs.SnapSeedDir, "assertions", "foo.snap-declaration")
- err = ioutil.WriteFile(declFn, asserts.Encode(fooDecl), 0644)
- c.Assert(err, IsNil)
-
- revFn := filepath.Join(dirs.SnapSeedDir, "assertions", "foo.snap-revision")
- err = ioutil.WriteFile(revFn, asserts.Encode(fooRev), 0644)
+ err := os.Rename(mockSnapFile, targetSnapFile2)
c.Assert(err, IsNil)
// add a model assertion and its chain
assertsChain := s.makeModelAssertionChain(c, "my-model", nil, "foo")
for i, as := range assertsChain {
- fn := filepath.Join(dirs.SnapSeedDir, "assertions", strconv.Itoa(i))
- err := ioutil.WriteFile(fn, asserts.Encode(as), 0644)
- c.Assert(err, IsNil)
+ s.WriteAssertions(strconv.Itoa(i), as)
}
// create a seed.yaml
@@ -510,35 +487,12 @@
return chg
}
-func checkOrder(c *C, tsAll []*state.TaskSet, snaps ...string) {
- matched := 0
- var prevTask *state.Task
- for i, ts := range tsAll {
- task0 := ts.Tasks()[0]
- waitTasks := task0.WaitTasks()
- if i == 0 {
- c.Check(waitTasks, HasLen, 0)
- } else {
- c.Check(waitTasks, testutil.Contains, prevTask)
- }
- prevTask = task0
- if task0.Kind() != "prerequisites" {
- continue
- }
- snapsup, err := snapstate.TaskSnapSetup(task0)
- c.Assert(err, IsNil, Commentf("%#v", task0))
- c.Check(snapsup.InstanceName(), Equals, snaps[matched])
- matched++
- }
- c.Check(matched, Equals, len(snaps))
-}
-
func (s *FirstBootTestSuite) TestPopulateFromSeedHappy(c *C) {
- loader := boottest.NewMockBootloader("mock", c.MkDir())
- bootloader.Force(loader)
+ bloader := bootloadertest.Mock("mock", c.MkDir())
+ bootloader.Force(bloader)
defer bootloader.Force(nil)
- boottest.SetBootKernel("pc-kernel_1.snap", loader)
- boottest.SetBootBase("core_1.snap", loader)
+ bloader.SetBootKernel("pc-kernel_1.snap")
+ bloader.SetBootBase("core_1.snap")
st := s.overlord.State()
chg := s.makeSeedChange(c, st)
@@ -571,7 +525,7 @@
_, err = snapstate.CurrentInfo(state, "pc")
c.Assert(err, IsNil)
- // ensure requied flag is set on all essential snaps
+ // ensure required flag is set on all essential snaps
var snapst snapstate.SnapState
for _, reqName := range []string{"core", "pc-kernel", "pc"} {
err = snapstate.Get(state, reqName, &snapst)
@@ -582,7 +536,7 @@
// check foo
info, err := snapstate.CurrentInfo(state, "foo")
c.Assert(err, IsNil)
- c.Assert(info.SnapID, Equals, "foo-snap-idididididididididididi")
+ c.Assert(info.SnapID, Equals, "foodidididididididididididididid")
c.Assert(info.Revision, Equals, snap.R(128))
c.Assert(info.Contact, Equals, "mailto:some.guy@example.com")
pubAcct, err := assertstate.Publisher(st, info.SnapID)
@@ -658,52 +612,30 @@
c.Assert(chg.Err(), ErrorMatches, `(?s).* cannot determine bootloader.*`)
}
-func writeAssertionsToFile(fn string, assertions []asserts.Assertion) {
- multifn := filepath.Join(dirs.SnapSeedDir, "assertions", fn)
- f, err := os.Create(multifn)
- if err != nil {
- panic(err)
- }
- defer f.Close()
- enc := asserts.NewEncoder(f)
- for _, a := range assertions {
- err := enc.Encode(a)
- if err != nil {
- panic(err)
- }
- }
-}
-
func (s *FirstBootTestSuite) TestPopulateFromSeedHappyMultiAssertsFiles(c *C) {
- loader := boottest.NewMockBootloader("mock", c.MkDir())
- bootloader.Force(loader)
+ bloader := bootloadertest.Mock("mock", c.MkDir())
+ bootloader.Force(bloader)
defer bootloader.Force(nil)
- boottest.SetBootKernel("pc-kernel_1.snap", loader)
- boottest.SetBootBase("core_1.snap", loader)
+ bloader.SetBootKernel("pc-kernel_1.snap")
+ bloader.SetBootBase("core_1.snap")
coreFname, kernelFname, gadgetFname := s.makeCoreSnaps(c, "")
- devAcct := assertstest.NewAccount(s.storeSigning, "developer", map[string]interface{}{
- "account-id": "developerid",
- }, "")
-
// put a firstboot snap into the SnapBlobDir
snapYaml := `name: foo
version: 1.0`
- fooFname, fooDecl, fooRev := s.makeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid")
-
- writeAssertionsToFile("foo.asserts", []asserts.Assertion{devAcct, fooRev, fooDecl})
+ fooFname, fooDecl, fooRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid")
+ s.WriteAssertions("foo.asserts", s.devAcct, fooRev, fooDecl)
// put a 2nd firstboot snap into the SnapBlobDir
snapYaml = `name: bar
version: 1.0`
- barFname, barDecl, barRev := s.makeAssertedSnap(c, snapYaml, nil, snap.R(65), "developerid")
-
- writeAssertionsToFile("bar.asserts", []asserts.Assertion{devAcct, barDecl, barRev})
+ barFname, barDecl, barRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(65), "developerid")
+ s.WriteAssertions("bar.asserts", s.devAcct, barDecl, barRev)
// add a model assertion and its chain
assertsChain := s.makeModelAssertionChain(c, "my-model", nil)
- writeAssertionsToFile("model.asserts", assertsChain)
+ s.WriteAssertions("model.asserts", assertsChain...)
// create a seed.yaml
content := []byte(fmt.Sprintf(`
@@ -763,7 +695,7 @@
// check foo
info, err := snapstate.CurrentInfo(state, "foo")
c.Assert(err, IsNil)
- c.Check(info.SnapID, Equals, "foo-snap-idididididididididididi")
+ c.Check(info.SnapID, Equals, "foodidididididididididididididid")
c.Check(info.Revision, Equals, snap.R(128))
pubAcct, err := assertstate.Publisher(st, info.SnapID)
c.Assert(err, IsNil)
@@ -772,97 +704,45 @@
// check bar
info, err = snapstate.CurrentInfo(state, "bar")
c.Assert(err, IsNil)
- c.Check(info.SnapID, Equals, "bar-snap-idididididididididididi")
+ c.Check(info.SnapID, Equals, "bardidididididididididididididid")
c.Check(info.Revision, Equals, snap.R(65))
pubAcct, err = assertstate.Publisher(st, info.SnapID)
c.Assert(err, IsNil)
c.Check(pubAcct.AccountID(), Equals, "developerid")
}
-func (s *FirstBootTestSuite) makeModelAssertion(c *C, modelStr string, extraHeaders map[string]interface{}, reqSnaps ...string) *asserts.Model {
- headers := map[string]interface{}{
- "architecture": "amd64",
- "store": "canonical",
- }
- if strings.HasSuffix(modelStr, "-classic") {
- headers["classic"] = "true"
- } else {
- headers["kernel"] = "pc-kernel"
- headers["gadget"] = "pc"
- }
- if len(reqSnaps) != 0 {
- reqs := make([]interface{}, len(reqSnaps))
- for i, req := range reqSnaps {
- reqs[i] = req
- }
- headers["required-snaps"] = reqs
- }
- return s.brands.Model("my-brand", modelStr, headers, extraHeaders)
-}
-
-func (s *FirstBootTestSuite) makeModelAssertionChain(c *C, modName string, extraHeaders map[string]interface{}, reqSnaps ...string) []asserts.Assertion {
- assertChain := []asserts.Assertion{}
-
- assertChain = append(assertChain, s.brands.Account("my-brand"))
- assertChain = append(assertChain, s.brands.AccountKey("my-brand"))
-
- model := s.makeModelAssertion(c, modName, extraHeaders, reqSnaps...)
- assertChain = append(assertChain, model)
-
- storeAccountKey := s.storeSigning.StoreAccountKey("")
- assertChain = append(assertChain, storeAccountKey)
- return assertChain
-}
-
func (s *FirstBootTestSuite) TestPopulateFromSeedConfigureHappy(c *C) {
- loader := boottest.NewMockBootloader("mock", c.MkDir())
- bootloader.Force(loader)
+ bloader := bootloadertest.Mock("mock", c.MkDir())
+ bootloader.Force(bloader)
defer bootloader.Force(nil)
- boottest.SetBootKernel("pc-kernel_1.snap", loader)
- boottest.SetBootBase("core_1.snap", loader)
+ bloader.SetBootKernel("pc-kernel_1.snap")
+ bloader.SetBootBase("core_1.snap")
const defaultsYaml = `
defaults:
- foo-snap-idididididididididididi:
+ foodidididididididididididididid:
foo-cfg: foo.
- core-snap-ididididididididididid:
+ coreidididididididididididididid:
core-cfg: core_cfg_defl
- pc-kernel-snap-ididididididididi:
+ pckernelidididididididididididid:
pc-kernel-cfg: pc-kernel_cfg_defl
- pc-snap-idididididididididididid:
+ pcididididididididididididididid:
pc-cfg: pc_cfg_defl
`
coreFname, kernelFname, gadgetFname := s.makeCoreSnaps(c, defaultsYaml)
- devAcct := assertstest.NewAccount(s.storeSigning, "developer", map[string]interface{}{
- "account-id": "developerid",
- }, "")
-
- devAcctFn := filepath.Join(dirs.SnapSeedDir, "assertions", "developer.account")
- err := ioutil.WriteFile(devAcctFn, asserts.Encode(devAcct), 0644)
- c.Assert(err, IsNil)
+ s.WriteAssertions("developer.account", s.devAcct)
// put a firstboot snap into the SnapBlobDir
files := [][]string{{"meta/hooks/configure", ""}}
snapYaml := `name: foo
version: 1.0`
- fooFname, fooDecl, fooRev := s.makeAssertedSnap(c, snapYaml, files, snap.R(128), "developerid")
-
- declFn := filepath.Join(dirs.SnapSeedDir, "assertions", "foo.snap-declaration")
- err = ioutil.WriteFile(declFn, asserts.Encode(fooDecl), 0644)
- c.Assert(err, IsNil)
-
- revFn := filepath.Join(dirs.SnapSeedDir, "assertions", "foo.snap-revision")
- err = ioutil.WriteFile(revFn, asserts.Encode(fooRev), 0644)
- c.Assert(err, IsNil)
+ fooFname, fooDecl, fooRev := s.MakeAssertedSnap(c, snapYaml, files, snap.R(128), "developerid")
+ s.WriteAssertions("foo.asserts", fooDecl, fooRev)
// add a model assertion and its chain
assertsChain := s.makeModelAssertionChain(c, "my-model", nil, "foo")
- for i, as := range assertsChain {
- fn := filepath.Join(dirs.SnapSeedDir, "assertions", strconv.Itoa(i))
- err := ioutil.WriteFile(fn, asserts.Encode(as), 0644)
- c.Assert(err, IsNil)
- }
+ s.WriteAssertions("model.asserts", assertsChain...)
// create a seed.yaml
content := []byte(fmt.Sprintf(`
@@ -876,7 +756,7 @@
- name: foo
file: %s
`, coreFname, kernelFname, gadgetFname, fooFname))
- err = ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644)
+ err := ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644)
c.Assert(err, IsNil)
// run the firstboot stuff
@@ -964,7 +844,7 @@
// check foo
info, err := snapstate.CurrentInfo(state, "foo")
c.Assert(err, IsNil)
- c.Assert(info.SnapID, Equals, "foo-snap-idididididididididididi")
+ c.Assert(info.SnapID, Equals, "foodidididididididididididididid")
c.Assert(info.Revision, Equals, snap.R(128))
pubAcct, err := assertstate.Publisher(st, info.SnapID)
c.Assert(err, IsNil)
@@ -985,48 +865,31 @@
}
func (s *FirstBootTestSuite) TestPopulateFromSeedGadgetConnectHappy(c *C) {
- loader := boottest.NewMockBootloader("mock", c.MkDir())
- bootloader.Force(loader)
+ bloader := bootloadertest.Mock("mock", c.MkDir())
+ bootloader.Force(bloader)
defer bootloader.Force(nil)
- boottest.SetBootKernel("pc-kernel_1.snap", loader)
- boottest.SetBootBase("core_1.snap", loader)
+ bloader.SetBootKernel("pc-kernel_1.snap")
+ bloader.SetBootBase("core_1.snap")
const connectionsYaml = `
connections:
- - plug: foo-snap-idididididididididididi:network-control
+ - plug: foodidididididididididididididid:network-control
`
coreFname, kernelFname, gadgetFname := s.makeCoreSnaps(c, connectionsYaml)
- devAcct := assertstest.NewAccount(s.storeSigning, "developer", map[string]interface{}{
- "account-id": "developerid",
- }, "")
-
- devAcctFn := filepath.Join(dirs.SnapSeedDir, "assertions", "developer.account")
- err := ioutil.WriteFile(devAcctFn, asserts.Encode(devAcct), 0644)
- c.Assert(err, IsNil)
+ s.WriteAssertions("developer.account", s.devAcct)
snapYaml := `name: foo
version: 1.0
plugs:
network-control:
`
- fooFname, fooDecl, fooRev := s.makeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid")
-
- declFn := filepath.Join(dirs.SnapSeedDir, "assertions", "foo.snap-declaration")
- err = ioutil.WriteFile(declFn, asserts.Encode(fooDecl), 0644)
- c.Assert(err, IsNil)
-
- revFn := filepath.Join(dirs.SnapSeedDir, "assertions", "foo.snap-revision")
- err = ioutil.WriteFile(revFn, asserts.Encode(fooRev), 0644)
- c.Assert(err, IsNil)
+ fooFname, fooDecl, fooRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid")
+ s.WriteAssertions("foo.asserts", fooDecl, fooRev)
// add a model assertion and its chain
assertsChain := s.makeModelAssertionChain(c, "my-model", nil, "foo")
- for i, as := range assertsChain {
- fn := filepath.Join(dirs.SnapSeedDir, "assertions", strconv.Itoa(i))
- err := ioutil.WriteFile(fn, asserts.Encode(as), 0644)
- c.Assert(err, IsNil)
- }
+ s.WriteAssertions("model.asserts", assertsChain...)
// create a seed.yaml
content := []byte(fmt.Sprintf(`
@@ -1040,7 +903,7 @@
- name: foo
file: %s
`, coreFname, kernelFname, gadgetFname, fooFname))
- err = ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644)
+ err := ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644)
c.Assert(err, IsNil)
// run the firstboot stuff
@@ -1085,7 +948,7 @@
// check foo
info, err := snapstate.CurrentInfo(state, "foo")
c.Assert(err, IsNil)
- c.Assert(info.SnapID, Equals, "foo-snap-idididididididididididi")
+ c.Assert(info.SnapID, Equals, "foodidididididididididididididid")
c.Assert(info.Revision, Equals, snap.R(128))
pubAcct, err := assertstate.Publisher(st, info.SnapID)
c.Assert(err, IsNil)
@@ -1117,19 +980,18 @@
c.Assert(err, IsNil)
st := ovld.State()
- // add a bunch of assert files
+ // add the odel assertion and its chain
assertsChain := s.makeModelAssertionChain(c, "my-model", nil)
- for i, as := range assertsChain {
- fn := filepath.Join(dirs.SnapSeedDir, "assertions", strconv.Itoa(i))
- err := ioutil.WriteFile(fn, asserts.Encode(as), 0644)
- c.Assert(err, IsNil)
- }
+ s.WriteAssertions("model.asserts", assertsChain...)
// import them
st.Lock()
defer st.Unlock()
- _, err = devicestate.ImportAssertionsFromSeed(st)
+ deviceSeed, err := seed.Open(dirs.SnapSeedDir)
+ c.Assert(err, IsNil)
+
+ _, err = devicestate.ImportAssertionsFromSeed(st, deviceSeed)
c.Assert(err, ErrorMatches, "cannot seed a classic system with an all-snaps model")
}
@@ -1138,19 +1000,18 @@
c.Assert(err, IsNil)
st := ovld.State()
- // add a bunch of assert files
+ // add the model assertion and its chain
assertsChain := s.makeModelAssertionChain(c, "my-model-classic", nil)
- for i, as := range assertsChain {
- fn := filepath.Join(dirs.SnapSeedDir, "assertions", strconv.Itoa(i))
- err := ioutil.WriteFile(fn, asserts.Encode(as), 0644)
- c.Assert(err, IsNil)
- }
+ s.WriteAssertions("model.asserts", assertsChain...)
// import them
st.Lock()
defer st.Unlock()
- _, err = devicestate.ImportAssertionsFromSeed(st)
+ deviceSeed, err := seed.Open(dirs.SnapSeedDir)
+ c.Assert(err, IsNil)
+
+ _, err = devicestate.ImportAssertionsFromSeed(st, deviceSeed)
c.Assert(err, ErrorMatches, "cannot seed an all-snaps system with a classic model")
}
@@ -1159,19 +1020,24 @@
c.Assert(err, IsNil)
st := ovld.State()
- // add a bunch of assert files
+ // add a bunch of assertions (model assertion and its chain)
assertsChain := s.makeModelAssertionChain(c, "my-model", nil)
for i, as := range assertsChain {
- fn := filepath.Join(dirs.SnapSeedDir, "assertions", strconv.Itoa(i))
- err := ioutil.WriteFile(fn, asserts.Encode(as), 0644)
- c.Assert(err, IsNil)
+ fname := strconv.Itoa(i)
+ if as.Type() == asserts.ModelType {
+ fname = "model"
+ }
+ s.WriteAssertions(fname, as)
}
// import them
st.Lock()
defer st.Unlock()
- model, err := devicestate.ImportAssertionsFromSeed(st)
+ deviceSeed, err := seed.Open(dirs.SnapSeedDir)
+ c.Assert(err, IsNil)
+
+ model, err := devicestate.ImportAssertionsFromSeed(st, deviceSeed)
c.Assert(err, IsNil)
c.Assert(model, NotNil)
@@ -1204,17 +1070,18 @@
assertsChain := s.makeModelAssertionChain(c, "my-model", nil)
for _, as := range assertsChain {
if as.Type() == asserts.ModelType {
- fn := filepath.Join(dirs.SnapSeedDir, "assertions", "model")
- err := ioutil.WriteFile(fn, asserts.Encode(as), 0644)
- c.Assert(err, IsNil)
+ s.WriteAssertions("model", as)
break
}
}
+ deviceSeed, err := seed.Open(dirs.SnapSeedDir)
+ c.Assert(err, IsNil)
+
// try import and verify that its rejects because other assertions are
// missing
- _, err := devicestate.ImportAssertionsFromSeed(st)
- c.Assert(err, ErrorMatches, "cannot find account-key .*")
+ _, err = devicestate.ImportAssertionsFromSeed(st, deviceSeed)
+ c.Assert(err, ErrorMatches, "cannot resolve prerequisite assertion: account-key .*")
}
func (s *FirstBootTestSuite) TestImportAssertionsFromSeedTwoModelAsserts(c *C) {
@@ -1223,20 +1090,19 @@
defer st.Unlock()
// write out two model assertions
- model := s.makeModelAssertion(c, "my-model", nil)
- fn := filepath.Join(dirs.SnapSeedDir, "assertions", "model")
- err := ioutil.WriteFile(fn, asserts.Encode(model), 0644)
- c.Assert(err, IsNil)
+ model := s.Brands.Model("my-brand", "my-model", s.modelHeaders("my-model"))
+ s.WriteAssertions("model", model)
+
+ model2 := s.Brands.Model("my-brand", "my-second-model", s.modelHeaders("my-second-model"))
+ s.WriteAssertions("model2", model2)
- model2 := s.makeModelAssertion(c, "my-second-model", nil)
- fn = filepath.Join(dirs.SnapSeedDir, "assertions", "model2")
- err = ioutil.WriteFile(fn, asserts.Encode(model2), 0644)
+ deviceSeed, err := seed.Open(dirs.SnapSeedDir)
c.Assert(err, IsNil)
// try import and verify that its rejects because other assertions are
// missing
- _, err = devicestate.ImportAssertionsFromSeed(st)
- c.Assert(err, ErrorMatches, "cannot add more than one model assertion")
+ _, err = devicestate.ImportAssertionsFromSeed(st, deviceSeed)
+ c.Assert(err, ErrorMatches, "cannot have multiple model assertions in seed")
}
func (s *FirstBootTestSuite) TestImportAssertionsFromSeedNoModelAsserts(c *C) {
@@ -1245,19 +1111,19 @@
defer st.Unlock()
assertsChain := s.makeModelAssertionChain(c, "my-model", nil)
- for _, as := range assertsChain {
+ for i, as := range assertsChain {
if as.Type() != asserts.ModelType {
- fn := filepath.Join(dirs.SnapSeedDir, "assertions", "model")
- err := ioutil.WriteFile(fn, asserts.Encode(as), 0644)
- c.Assert(err, IsNil)
- break
+ s.WriteAssertions(strconv.Itoa(i), as)
}
}
+ deviceSeed, err := seed.Open(dirs.SnapSeedDir)
+ c.Assert(err, IsNil)
+
// try import and verify that its rejects because other assertions are
// missing
- _, err := devicestate.ImportAssertionsFromSeed(st)
- c.Assert(err, ErrorMatches, "need a model assertion")
+ _, err = devicestate.ImportAssertionsFromSeed(st, deviceSeed)
+ c.Assert(err, ErrorMatches, "seed must have a model assertion")
}
type core18SnapsOpts struct {
@@ -1275,23 +1141,22 @@
core18Yaml := `name: core18
version: 1.0
type: base`
- core18Fname, core18Decl, core18Rev := s.makeAssertedSnap(c, core18Yaml, files, snap.R(1), "canonical")
- writeAssertionsToFile("core18.asserts", []asserts.Assertion{core18Rev, core18Decl})
+ core18Fname, core18Decl, core18Rev := s.MakeAssertedSnap(c, core18Yaml, files, snap.R(1), "canonical")
+ s.WriteAssertions("core18.asserts", core18Rev, core18Decl)
snapdYaml := `name: snapd
version: 1.0
`
- snapdFname, snapdDecl, snapdRev := s.makeAssertedSnap(c, snapdYaml, nil, snap.R(2), "canonical")
- writeAssertionsToFile("snapd.asserts", []asserts.Assertion{snapdRev, snapdDecl})
+ snapdFname, snapdDecl, snapdRev := s.MakeAssertedSnap(c, snapdYaml, nil, snap.R(2), "canonical")
+ s.WriteAssertions("snapd.asserts", snapdRev, snapdDecl)
var kernelFname string
if !opts.classic {
kernelYaml := `name: pc-kernel
version: 1.0
type: kernel`
- fname, kernelDecl, kernelRev := s.makeAssertedSnap(c, kernelYaml, files, snap.R(1), "canonical")
-
- writeAssertionsToFile("kernel.asserts", []asserts.Assertion{kernelRev, kernelDecl})
+ fname, kernelDecl, kernelRev := s.MakeAssertedSnap(c, kernelYaml, files, snap.R(1), "canonical")
+ s.WriteAssertions("kernel.asserts", kernelRev, kernelDecl)
kernelFname = fname
}
@@ -1311,9 +1176,8 @@
type: gadget
base: core18
`
- fname, gadgetDecl, gadgetRev := s.makeAssertedSnap(c, gaYaml, files, snap.R(1), "canonical")
-
- writeAssertionsToFile("gadget.asserts", []asserts.Assertion{gadgetRev, gadgetDecl})
+ fname, gadgetDecl, gadgetRev := s.MakeAssertedSnap(c, gaYaml, files, snap.R(1), "canonical")
+ s.WriteAssertions("gadget.asserts", gadgetRev, gadgetDecl)
gadgetFname = fname
}
@@ -1328,29 +1192,19 @@
})
defer systemctlRestorer()
- loader := boottest.NewMockBootloader("mock", c.MkDir())
- bootloader.Force(loader)
+ bloader := bootloadertest.Mock("mock", c.MkDir())
+ bootloader.Force(bloader)
defer bootloader.Force(nil)
- boottest.SetBootKernel("pc-kernel_1.snap", loader)
- boottest.SetBootBase("core18_1.snap", loader)
+ bloader.SetBootKernel("pc-kernel_1.snap")
+ bloader.SetBootBase("core18_1.snap")
core18Fname, snapdFname, kernelFname, gadgetFname := s.makeCore18Snaps(c, nil)
- devAcct := assertstest.NewAccount(s.storeSigning, "developer", map[string]interface{}{
- "account-id": "developerid",
- }, "")
-
- devAcctFn := filepath.Join(dirs.SnapSeedDir, "assertions", "developer.account")
- err := ioutil.WriteFile(devAcctFn, asserts.Encode(devAcct), 0644)
- c.Assert(err, IsNil)
+ s.WriteAssertions("developer.account", s.devAcct)
// add a model assertion and its chain
assertsChain := s.makeModelAssertionChain(c, "my-model", map[string]interface{}{"base": "core18"})
- for i, as := range assertsChain {
- fn := filepath.Join(dirs.SnapSeedDir, "assertions", strconv.Itoa(i))
- err := ioutil.WriteFile(fn, asserts.Encode(as), 0644)
- c.Assert(err, IsNil)
- }
+ s.WriteAssertions("model.asserts", assertsChain...)
// create a seed.yaml
content := []byte(fmt.Sprintf(`
@@ -1364,7 +1218,7 @@
- name: pc
file: %s
`, snapdFname, core18Fname, kernelFname, gadgetFname))
- err = ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644)
+ err := ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644)
c.Assert(err, IsNil)
// run the firstboot stuff
@@ -1374,7 +1228,7 @@
tsAll, err := devicestate.PopulateStateFromSeedImpl(st, s.perfTimings)
c.Assert(err, IsNil)
- checkOrder(c, tsAll, "snapd", "core18", "pc-kernel", "pc")
+ checkOrder(c, tsAll, "snapd", "pc-kernel", "core18", "pc")
// now run the change and check the result
// use the expected kind otherwise settle with start another one
@@ -1424,7 +1278,7 @@
_, err = snapstate.CurrentInfo(state, "pc")
c.Check(err, IsNil)
- // ensure requied flag is set on all essential snaps
+ // ensure required flag is set on all essential snaps
var snapst snapstate.SnapState
for _, reqName := range []string{"snapd", "core18", "pc-kernel", "pc"} {
err = snapstate.Get(state, reqName, &snapst)
@@ -1449,21 +1303,11 @@
}
func (s *FirstBootTestSuite) TestPopulateFromSeedOrdering(c *C) {
- devAcct := assertstest.NewAccount(s.storeSigning, "developer", map[string]interface{}{
- "account-id": "developerid",
- }, "")
-
- devAcctFn := filepath.Join(dirs.SnapSeedDir, "assertions", "developer.account")
- err := ioutil.WriteFile(devAcctFn, asserts.Encode(devAcct), 0644)
- c.Assert(err, IsNil)
+ s.WriteAssertions("developer.account", s.devAcct)
// add a model assertion and its chain
assertsChain := s.makeModelAssertionChain(c, "my-model", map[string]interface{}{"base": "core18"})
- for i, as := range assertsChain {
- fn := filepath.Join(dirs.SnapSeedDir, "assertions", strconv.Itoa(i))
- err := ioutil.WriteFile(fn, asserts.Encode(as), 0644)
- c.Assert(err, IsNil)
- }
+ s.WriteAssertions("model.asserts", assertsChain...)
core18Fname, snapdFname, kernelFname, gadgetFname := s.makeCore18Snaps(c, nil)
@@ -1471,14 +1315,14 @@
version: 1.0
base: other-base
`
- snapFname, snapDecl, snapRev := s.makeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid")
- writeAssertionsToFile("snap-req-other-base.asserts", []asserts.Assertion{devAcct, snapRev, snapDecl})
+ snapFname, snapDecl, snapRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid")
+ s.WriteAssertions("snap-req-other-base.asserts", s.devAcct, snapRev, snapDecl)
baseYaml := `name: other-base
version: 1.0
type: base
`
- baseFname, baseDecl, baseRev := s.makeAssertedSnap(c, baseYaml, nil, snap.R(127), "developerid")
- writeAssertionsToFile("other-base.asserts", []asserts.Assertion{devAcct, baseRev, baseDecl})
+ baseFname, baseDecl, baseRev := s.MakeAssertedSnap(c, baseYaml, nil, snap.R(127), "developerid")
+ s.WriteAssertions("other-base.asserts", s.devAcct, baseRev, baseDecl)
// create a seed.yaml
content := []byte(fmt.Sprintf(`
@@ -1496,7 +1340,7 @@
- name: other-base
file: %s
`, snapdFname, core18Fname, kernelFname, gadgetFname, snapFname, baseFname))
- err = ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644)
+ err := ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644)
c.Assert(err, IsNil)
// run the firstboot stuff
@@ -1506,25 +1350,15 @@
tsAll, err := devicestate.PopulateStateFromSeedImpl(st, s.perfTimings)
c.Assert(err, IsNil)
- checkOrder(c, tsAll, "snapd", "core18", "pc-kernel", "pc", "other-base", "snap-req-other-base")
+ checkOrder(c, tsAll, "snapd", "pc-kernel", "core18", "pc", "other-base", "snap-req-other-base")
}
func (s *FirstBootTestSuite) TestFirstbootGadgetBaseModelBaseMismatch(c *C) {
- devAcct := assertstest.NewAccount(s.storeSigning, "developer", map[string]interface{}{
- "account-id": "developerid",
- }, "")
-
- devAcctFn := filepath.Join(dirs.SnapSeedDir, "assertions", "developer.account")
- err := ioutil.WriteFile(devAcctFn, asserts.Encode(devAcct), 0644)
- c.Assert(err, IsNil)
+ s.WriteAssertions("developer.account", s.devAcct)
// add a model assertion and its chain
assertsChain := s.makeModelAssertionChain(c, "my-model", map[string]interface{}{"base": "core18"})
- for i, as := range assertsChain {
- fn := filepath.Join(dirs.SnapSeedDir, "assertions", strconv.Itoa(i))
- err := ioutil.WriteFile(fn, asserts.Encode(as), 0644)
- c.Assert(err, IsNil)
- }
+ s.WriteAssertions("model.asserts", assertsChain...)
core18Fname, snapdFname, kernelFname, _ := s.makeCore18Snaps(c, nil)
// take the gadget without "base: core18"
@@ -1542,7 +1376,7 @@
- name: pc
file: %s
`, snapdFname, core18Fname, kernelFname, gadgetFname))
- err = ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644)
+ err := ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644)
c.Assert(err, IsNil)
// run the firstboot stuff
@@ -1555,18 +1389,14 @@
}
func (s *FirstBootTestSuite) TestPopulateFromSeedWrongContentProviderOrder(c *C) {
- loader := boottest.NewMockBootloader("mock", c.MkDir())
- bootloader.Force(loader)
+ bloader := bootloadertest.Mock("mock", c.MkDir())
+ bootloader.Force(bloader)
defer bootloader.Force(nil)
- boottest.SetBootKernel("pc-kernel_1.snap", loader)
- boottest.SetBootBase("core_1.snap", loader)
+ bloader.SetBootKernel("pc-kernel_1.snap")
+ bloader.SetBootBase("core_1.snap")
coreFname, kernelFname, gadgetFname := s.makeCoreSnaps(c, "")
- devAcct := assertstest.NewAccount(s.storeSigning, "developer", map[string]interface{}{
- "account-id": "developerid",
- }, "")
-
// a snap that uses content providers
snapYaml := `name: gnome-calculator
version: 1.0
@@ -1576,9 +1406,8 @@
default-provider: gtk-common-themes
target: $SNAP/data-dir/themes
`
- calcFname, calcDecl, calcRev := s.makeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid")
-
- writeAssertionsToFile("calc.asserts", []asserts.Assertion{devAcct, calcRev, calcDecl})
+ calcFname, calcDecl, calcRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid")
+ s.WriteAssertions("calc.asserts", s.devAcct, calcRev, calcDecl)
// put a 2nd firstboot snap into the SnapBlobDir
snapYaml = `name: gtk-common-themes
@@ -1590,13 +1419,12 @@
read:
- $SNAP/share/themes/Adawaita
`
- themesFname, themesDecl, themesRev := s.makeAssertedSnap(c, snapYaml, nil, snap.R(65), "developerid")
-
- writeAssertionsToFile("themes.asserts", []asserts.Assertion{devAcct, themesDecl, themesRev})
+ themesFname, themesDecl, themesRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(65), "developerid")
+ s.WriteAssertions("themes.asserts", s.devAcct, themesDecl, themesRev)
// add a model assertion and its chain
assertsChain := s.makeModelAssertionChain(c, "my-model", nil)
- writeAssertionsToFile("model.asserts", assertsChain)
+ s.WriteAssertions("model.asserts", assertsChain...)
// create a seed.yaml
content := []byte(fmt.Sprintf(`
@@ -1651,6 +1479,49 @@
c.Check(conn.(map[string]interface{})["interface"], Equals, "content")
}
+func (s *FirstBootTestSuite) TestPopulateFromSeedMissingBase(c *C) {
+ s.WriteAssertions("developer.account", s.devAcct)
+
+ // add a model assertion and its chain
+ assertsChain := s.makeModelAssertionChain(c, "my-model", nil)
+ s.WriteAssertions("model.asserts", assertsChain...)
+
+ coreFname, kernelFname, gadgetFname := s.makeCoreSnaps(c, "")
+
+ // TODO: this test doesn't particularly need to use a local snap
+ // local snap with unknown base
+ snapYaml = `name: local
+base: foo
+version: 1.0`
+ mockSnapFile := snaptest.MakeTestSnapWithFiles(c, snapYaml, nil)
+ localFname := filepath.Base(mockSnapFile)
+ targetSnapFile2 := filepath.Join(dirs.SnapSeedDir, "snaps", localFname)
+ c.Assert(os.Rename(mockSnapFile, targetSnapFile2), IsNil)
+
+ // create a seed.yaml
+ content := []byte(fmt.Sprintf(`
+snaps:
+ - name: core
+ file: %s
+ - name: pc-kernel
+ file: %s
+ - name: pc
+ file: %s
+ - name: local
+ unasserted: true
+ file: %s
+`, coreFname, kernelFname, gadgetFname, localFname))
+
+ c.Assert(ioutil.WriteFile(filepath.Join(dirs.SnapSeedDir, "seed.yaml"), content, 0644), IsNil)
+
+ // run the firstboot stuff
+ st := s.overlord.State()
+ st.Lock()
+ defer st.Unlock()
+ _, err := devicestate.PopulateStateFromSeedImpl(st, s.perfTimings)
+ c.Assert(err, ErrorMatches, `cannot use snap "local": base "foo" is missing`)
+}
+
func (s *FirstBootTestSuite) TestPopulateFromSeedOnClassicWithSnapdOnlyHappy(c *C) {
restore := release.MockOnClassic(true)
defer restore()
@@ -1666,26 +1537,17 @@
classic: true,
})
- devAcct := assertstest.NewAccount(s.storeSigning, "developer", map[string]interface{}{
- "account-id": "developerid",
- }, "")
-
// put a firstboot snap into the SnapBlobDir
snapYaml := `name: foo
version: 1.0
base: core18
`
- fooFname, fooDecl, fooRev := s.makeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid")
-
- writeAssertionsToFile("foo.asserts", []asserts.Assertion{devAcct, fooRev, fooDecl})
+ fooFname, fooDecl, fooRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid")
+ s.WriteAssertions("foo.asserts", s.devAcct, fooRev, fooDecl)
// add a model assertion and its chain
assertsChain := s.makeModelAssertionChain(c, "my-model-classic", nil)
- for i, as := range assertsChain {
- fn := filepath.Join(dirs.SnapSeedDir, "assertions", strconv.Itoa(i))
- err := ioutil.WriteFile(fn, asserts.Encode(as), 0644)
- c.Assert(err, IsNil)
- }
+ s.WriteAssertions("model.asserts", assertsChain...)
// create a seed.yaml
content := []byte(fmt.Sprintf(`
@@ -1784,26 +1646,18 @@
gadget: true,
})
- devAcct := assertstest.NewAccount(s.storeSigning, "developer", map[string]interface{}{
- "account-id": "developerid",
- }, "")
-
// put a firstboot snap into the SnapBlobDir
snapYaml := `name: foo
version: 1.0
base: core18
`
- fooFname, fooDecl, fooRev := s.makeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid")
+ fooFname, fooDecl, fooRev := s.MakeAssertedSnap(c, snapYaml, nil, snap.R(128), "developerid")
- writeAssertionsToFile("foo.asserts", []asserts.Assertion{devAcct, fooRev, fooDecl})
+ s.WriteAssertions("foo.asserts", s.devAcct, fooRev, fooDecl)
// add a model assertion and its chain
assertsChain := s.makeModelAssertionChain(c, "my-model-classic", map[string]interface{}{"gadget": "pc"})
- for i, as := range assertsChain {
- fn := filepath.Join(dirs.SnapSeedDir, "assertions", strconv.Itoa(i))
- err := ioutil.WriteFile(fn, asserts.Encode(as), 0644)
- c.Assert(err, IsNil)
- }
+ s.WriteAssertions("model.asserts", assertsChain...)
// create a seed.yaml
content := []byte(fmt.Sprintf(`
diff -Nru snapd-2.41+19.10.1/overlord/devicestate/handlers.go snapd-2.42.1+19.10/overlord/devicestate/handlers.go
--- snapd-2.41+19.10.1/overlord/devicestate/handlers.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/devicestate/handlers.go 2019-10-30 12:17:43.000000000 +0000
@@ -24,6 +24,7 @@
"encoding/json"
"errors"
"fmt"
+ "io"
"net"
"net/http"
"net/url"
@@ -49,6 +50,7 @@
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/naming"
"github.com/snapcore/snapd/timings"
)
@@ -92,23 +94,24 @@
// unmark no-longer required snaps
requiredSnaps := getAllRequiredSnapsForModel(new)
+ // TODO:XXX: have AllByRef
snapStates, err := snapstate.All(st)
if err != nil {
return err
}
for snapName, snapst := range snapStates {
// TODO: remove this type restriction once we remodel
- // kernels/gadgets and add tests that ensure
+ // gadgets and add tests that ensure
// that the required flag is properly set/unset
typ, err := snapst.Type()
if err != nil {
return err
}
- if typ != snap.TypeApp && typ != snap.TypeBase {
+ if typ != snap.TypeApp && typ != snap.TypeBase && typ != snap.TypeKernel {
continue
}
// clean required flag if no-longer needed
- if snapst.Flags.Required && !requiredSnaps[snapName] {
+ if snapst.Flags.Required && !requiredSnaps.Contains(naming.Snap(snapName)) {
snapst.Flags.Required = false
snapstate.Set(st, snapName, snapst)
}
@@ -158,6 +161,9 @@
reqIdRef = mustParse("request-id")
serialRef = mustParse("serial")
devicesRef = mustParse("devices")
+
+ // we accept a stream with the serial assertion as well
+ registrationCapabilities = []string{"serial-stream"}
)
func newEnoughProxy(st *state.State, proxyURL *url.URL, client *http.Client) bool {
@@ -472,64 +478,83 @@
var errPoll = errors.New("serial-request accepted, poll later")
-func submitSerialRequest(t *state.Task, serialRequest string, client *http.Client, cfg *serialRequestConfig) (*asserts.Serial, error) {
+func submitSerialRequest(t *state.Task, serialRequest string, client *http.Client, cfg *serialRequestConfig) (*asserts.Serial, *asserts.Batch, error) {
st := t.State()
st.Unlock()
defer st.Lock()
req, err := http.NewRequest("POST", cfg.serialRequestURL, bytes.NewBufferString(serialRequest))
if err != nil {
- return nil, fmt.Errorf("internal error: cannot create serial-request request %q", cfg.serialRequestURL)
+ return nil, nil, fmt.Errorf("internal error: cannot create serial-request request %q", cfg.serialRequestURL)
}
req.Header.Set("User-Agent", httputil.UserAgent())
+ req.Header.Set("Snap-Device-Capabilities", strings.Join(registrationCapabilities, " "))
cfg.applyHeaders(req)
req.Header.Set("Content-Type", asserts.MediaType)
resp, err := client.Do(req)
if err != nil {
- return nil, retryErr(t, 0, "cannot deliver device serial request: %v", err)
+ return nil, nil, retryErr(t, 0, "cannot deliver device serial request: %v", err)
}
defer resp.Body.Close()
switch resp.StatusCode {
case 200, 201:
case 202:
- return nil, errPoll
+ return nil, nil, errPoll
default:
- return nil, retryBadStatus(t, 0, "cannot deliver device serial request", resp)
+ return nil, nil, retryBadStatus(t, 0, "cannot deliver device serial request", resp)
}
- // TODO: support a stream of assertions instead of just the serial
-
- // decode body with serial assertion
+ var serial *asserts.Serial
+ var batch *asserts.Batch
+ // decode body with stream of assertions, of which one is the serial
dec := asserts.NewDecoder(resp.Body)
- got, err := dec.Decode()
- if err != nil { // assume broken i/o
- return nil, retryErr(t, 0, "cannot read response to request for a serial: %v", err)
+ for {
+ got, err := dec.Decode()
+ if err == io.EOF {
+ break
+ }
+ if err != nil { // assume broken i/o
+ return nil, nil, retryErr(t, 0, "cannot read response to request for a serial: %v", err)
+ }
+ if got.Type() == asserts.SerialType {
+ if serial != nil {
+ return nil, nil, fmt.Errorf("cannot accept more than a single device serial assertion from the device service")
+ }
+ serial = got.(*asserts.Serial)
+ } else {
+ if batch == nil {
+ batch = asserts.NewBatch(nil)
+ }
+ if err := batch.Add(got); err != nil {
+ return nil, nil, err
+ }
+ }
+ // TODO: consider a size limit?
}
- serial, ok := got.(*asserts.Serial)
- if !ok {
- return nil, fmt.Errorf("cannot use device serial assertion of type %q", got.Type().Name)
+ if serial == nil {
+ return nil, nil, fmt.Errorf("cannot proceed, received assertion stream from the device service missing device serial assertion")
}
- return serial, nil
+ return serial, batch, nil
}
-func getSerial(t *state.Task, regCtx registrationContext, privKey asserts.PrivateKey, device *auth.DeviceState, tm timings.Measurer) (*asserts.Serial, error) {
+func getSerial(t *state.Task, regCtx registrationContext, privKey asserts.PrivateKey, device *auth.DeviceState, tm timings.Measurer) (serial *asserts.Serial, ancillaryBatch *asserts.Batch, err error) {
var serialSup serialSetup
- err := t.Get("serial-setup", &serialSup)
+ err = t.Get("serial-setup", &serialSup)
if err != nil && err != state.ErrNoState {
- return nil, err
+ return nil, nil, err
}
if serialSup.Serial != "" {
// we got a serial, just haven't managed to save its info yet
a, err := asserts.Decode([]byte(serialSup.Serial))
if err != nil {
- return nil, fmt.Errorf("internal error: cannot decode previously saved serial: %v", err)
+ return nil, nil, fmt.Errorf("internal error: cannot decode previously saved serial: %v", err)
}
- return a.(*asserts.Serial), nil
+ return a.(*asserts.Serial), nil, nil
}
st := t.State()
@@ -542,7 +567,7 @@
cfg, err := getSerialRequestConfig(t, regCtx, client)
if err != nil {
- return nil, err
+ return nil, nil, err
}
// NB: until we get at least an Accepted (202) we need to
@@ -556,39 +581,40 @@
serialRequest, err = prepareSerialRequest(t, regCtx, privKey, device, client, cfg)
})
if err != nil { // errors & retries
- return nil, err
+ return nil, nil, err
}
serialSup.SerialRequest = serialRequest
}
- var serial *asserts.Serial
timings.Run(tm, "submit-serial-request", "submit device serial request", func(timings.Measurer) {
- serial, err = submitSerialRequest(t, serialSup.SerialRequest, client, cfg)
+ serial, ancillaryBatch, err = submitSerialRequest(t, serialSup.SerialRequest, client, cfg)
})
if err == errPoll {
// we can/should reuse the serial-request
t.Set("serial-setup", serialSup)
- return nil, errPoll
+ return nil, nil, errPoll
}
if err != nil { // errors & retries
- return nil, err
+ return nil, nil, err
}
keyID := privKey.PublicKey().ID()
if serial.BrandID() != device.Brand || serial.Model() != device.Model || serial.DeviceKey().ID() != keyID {
- return nil, fmt.Errorf("obtained serial assertion does not match provided device identity information (brand, model, key id): %s / %s / %s != %s / %s / %s", serial.BrandID(), serial.Model(), serial.DeviceKey().ID(), device.Brand, device.Model, keyID)
+ return nil, nil, fmt.Errorf("obtained serial assertion does not match provided device identity information (brand, model, key id): %s / %s / %s != %s / %s / %s", serial.BrandID(), serial.Model(), serial.DeviceKey().ID(), device.Brand, device.Model, keyID)
}
- serialSup.Serial = string(asserts.Encode(serial))
- t.Set("serial-setup", serialSup)
+ if ancillaryBatch == nil {
+ serialSup.Serial = string(asserts.Encode(serial))
+ t.Set("serial-setup", serialSup)
+ }
if repeatRequestSerial == "after-got-serial" {
// For testing purposes, ensure a crash in this state works.
- return nil, &state.Retry{}
+ return nil, nil, &state.Retry{}
}
- return serial, nil
+ return serial, ancillaryBatch, nil
}
type serialRequestConfig struct {
@@ -726,8 +752,9 @@
}
var serial *asserts.Serial
+ var ancillaryBatch *asserts.Batch
timings.Run(perfTimings, "get-serial", "get device serial", func(tm timings.Measurer) {
- serial, err = getSerial(t, regCtx, privKey, device, tm)
+ serial, ancillaryBatch, err = getSerial(t, regCtx, privKey, device, tm)
})
if err == errPoll {
t.Logf("Will poll for device serial assertion in 60 seconds")
@@ -738,6 +765,33 @@
}
+ if ancillaryBatch == nil {
+ // the device service returned only the serial
+ if err := acceptSerialOnly(t, serial, perfTimings); err != nil {
+ return err
+ }
+ } else {
+ // the device service returned a stream of assertions
+ timings.Run(perfTimings, "fetch-keys", "fetch signing key chain", func(timings.Measurer) {
+ err = acceptSerialPlusBatch(t, serial, ancillaryBatch)
+ })
+ if err != nil {
+ t.Errorf("cannot accept stream of assertions from device service: %v", err)
+ return err
+ }
+ }
+
+ if repeatRequestSerial == "after-add-serial" {
+ // For testing purposes, ensure a crash in this state works.
+ return &state.Retry{}
+ }
+
+ return finish(serial)
+}
+
+func acceptSerialOnly(t *state.Task, serial *asserts.Serial, perfTimings *timings.Timings) error {
+ st := t.State()
+ var err error
var errAcctKey error
// try to fetch the signing key chain of the serial
timings.Run(perfTimings, "fetch-keys", "fetch signing key chain", func(timings.Measurer) {
@@ -758,20 +812,24 @@
return err
}
- if repeatRequestSerial == "after-add-serial" {
- // For testing purposes, ensure a crash in this state works.
- return &state.Retry{}
- }
+ return nil
+}
- return finish(serial)
+func acceptSerialPlusBatch(t *state.Task, serial *asserts.Serial, batch *asserts.Batch) error {
+ st := t.State()
+ err := batch.Add(serial)
+ if err != nil {
+ return err
+ }
+ return assertstate.AddBatch(st, batch, &asserts.CommitOptions{Precheck: true})
}
var repeatRequestSerial string // for tests
func fetchKeys(st *state.State, keyID string) (errAcctKey error, err error) {
// TODO: right now any store should be good enough here but
- // that might change, also this is brittle, best would be to
- // receive a stream with any relevant assertions
+ // that might change. As an alternative we do support
+ // receiving a stream with any relevant assertions.
sto := snapstate.Store(st, nil)
db := assertstate.DB(st)
diff -Nru snapd-2.41+19.10.1/overlord/devicestate/handlers_test.go snapd-2.42.1+19.10/overlord/devicestate/handlers_test.go
--- snapd-2.41+19.10.1/overlord/devicestate/handlers_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/devicestate/handlers_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -33,6 +33,7 @@
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/overlord/storecontext"
+ "github.com/snapcore/snapd/snap"
)
// TODO: should we move this into a new handlers suite?
@@ -43,18 +44,54 @@
Model: "pc-model",
})
s.makeModelAssertionInState(c, "canonical", "pc-model", map[string]interface{}{
- "architecture": "amd64",
- "kernel": "pc-kernel",
- "gadget": "pc",
- "revision": "1",
+ "architecture": "amd64",
+ "kernel": "pc-kernel",
+ "gadget": "pc",
+ "revision": "1",
+ "required-snaps": []interface{}{"foo", "bar"},
+ })
+ // foo and bar
+ fooSI := &snap.SideInfo{
+ RealName: "foo",
+ Revision: snap.R(1),
+ }
+ barSI := &snap.SideInfo{
+ RealName: "foo",
+ Revision: snap.R(1),
+ }
+ pcKernelSI := &snap.SideInfo{
+ RealName: "pc-kernel",
+ Revision: snap.R(1),
+ }
+ snapstate.Set(s.state, "foo", &snapstate.SnapState{
+ SnapType: "app",
+ Active: true,
+ Sequence: []*snap.SideInfo{fooSI},
+ Current: fooSI.Revision,
+ Flags: snapstate.Flags{Required: true},
+ })
+ snapstate.Set(s.state, "bar", &snapstate.SnapState{
+ SnapType: "app",
+ Active: true,
+ Sequence: []*snap.SideInfo{barSI},
+ Current: barSI.Revision,
+ Flags: snapstate.Flags{Required: true},
+ })
+ snapstate.Set(s.state, "pc-kernel", &snapstate.SnapState{
+ SnapType: "kernel",
+ Active: true,
+ Sequence: []*snap.SideInfo{pcKernelSI},
+ Current: pcKernelSI.Revision,
+ Flags: snapstate.Flags{Required: true},
})
s.state.Unlock()
newModel := s.brands.Model("canonical", "pc-model", map[string]interface{}{
- "architecture": "amd64",
- "kernel": "pc-kernel",
- "gadget": "pc",
- "revision": "2",
+ "architecture": "amd64",
+ "kernel": "other-kernel",
+ "gadget": "pc",
+ "revision": "2",
+ "required-snaps": []interface{}{"foo"},
})
s.state.Lock()
@@ -75,6 +112,21 @@
c.Assert(m, DeepEquals, newModel)
c.Assert(chg.Err(), IsNil)
+
+ // check required
+ var fooState snapstate.SnapState
+ var barState snapstate.SnapState
+ err = snapstate.Get(s.state, "foo", &fooState)
+ c.Assert(err, IsNil)
+ err = snapstate.Get(s.state, "bar", &barState)
+ c.Assert(err, IsNil)
+ c.Check(fooState.Flags.Required, Equals, true)
+ c.Check(barState.Flags.Required, Equals, false)
+ // the kernel is no longer required
+ var kernelState snapstate.SnapState
+ err = snapstate.Get(s.state, "pc-kernel", &kernelState)
+ c.Assert(err, IsNil)
+ c.Check(kernelState.Flags.Required, Equals, false)
}
func (s *deviceMgrSuite) TestSetModelHandlerSameRevisionNoError(c *C) {
diff -Nru snapd-2.41+19.10.1/overlord/export_test.go snapd-2.42.1+19.10/overlord/export_test.go
--- snapd-2.41+19.10.1/overlord/export_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/export_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -25,6 +25,7 @@
"github.com/snapcore/snapd/overlord/configstate"
"github.com/snapcore/snapd/overlord/hookstate"
"github.com/snapcore/snapd/overlord/snapstate"
+ "github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/overlord/storecontext"
"github.com/snapcore/snapd/store"
)
@@ -74,7 +75,7 @@
}
}
-func MockConfigstateInit(new func(hookmgr *hookstate.HookManager)) (restore func()) {
+func MockConfigstateInit(new func(*state.State, *hookstate.HookManager) error) (restore func()) {
configstateInit = new
return func() {
configstateInit = configstate.Init
diff -Nru snapd-2.41+19.10.1/overlord/hookstate/ctlcmd/set.go snapd-2.42.1+19.10/overlord/hookstate/ctlcmd/set.go
--- snapd-2.41+19.10.1/overlord/hookstate/ctlcmd/set.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/hookstate/ctlcmd/set.go 2019-10-30 12:17:43.000000000 +0000
@@ -54,7 +54,7 @@
$ snapctl set author.name=frank
Configuration option may be unset with exclamation mark:
- $ snap set author!
+ $ snapctl set author!
Plug and slot attributes may be set in the respective prepare and connect hooks by
naming the respective plug or slot:
diff -Nru snapd-2.41+19.10.1/overlord/hookstate/ctlcmd/set_test.go snapd-2.42.1+19.10/overlord/hookstate/ctlcmd/set_test.go
--- snapd-2.41+19.10.1/overlord/hookstate/ctlcmd/set_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/hookstate/ctlcmd/set_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -99,38 +99,15 @@
c.Check(value, Equals, "qux")
}
-func (s *getSuite) TestSetRegularUserForbidden(c *C) {
- state := state.New(nil)
- state.Lock()
-
- task := state.NewTask("test-task", "my test task")
- setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "test-hook"}
-
- state.Unlock()
-
- mockHandler := hooktest.NewMockHandler()
- mockContext, err := hookstate.NewContext(task, task.State(), setup, mockHandler, "")
- c.Assert(err, IsNil)
- _, _, err = ctlcmd.Run(mockContext, []string{"set", "test-key1"}, 1000)
- c.Assert(err, NotNil)
+func (s *setSuite) TestSetRegularUserForbidden(c *C) {
+ _, _, err := ctlcmd.Run(s.mockContext, []string{"set", "test-key1"}, 1000)
c.Assert(err, ErrorMatches, `cannot use "set" with uid 1000, try with sudo`)
forbidden, _ := err.(*ctlcmd.ForbiddenCommandError)
c.Assert(forbidden, NotNil)
}
-func (s *getSuite) TestSetHelpRegularUserAllowed(c *C) {
- state := state.New(nil)
- state.Lock()
-
- task := state.NewTask("test-task", "my test task")
- setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "test-hook"}
-
- state.Unlock()
-
- mockHandler := hooktest.NewMockHandler()
- mockContext, err := hookstate.NewContext(task, task.State(), setup, mockHandler, "")
- c.Assert(err, IsNil)
- _, _, err = ctlcmd.Run(mockContext, []string{"set", "-h"}, 1000)
+func (s *setSuite) TestSetHelpRegularUserAllowed(c *C) {
+ _, _, err := ctlcmd.Run(s.mockContext, []string{"set", "-h"}, 1000)
c.Assert(err, NotNil)
c.Assert(strings.HasPrefix(err.Error(), "Usage:"), Equals, true)
}
@@ -352,7 +329,7 @@
func (s *setAttrSuite) TestPlugOrSlotEmpty(c *C) {
stdout, stderr, err := ctlcmd.Run(s.mockPlugHookContext, []string{"set", ":", "foo=bar"}, 0)
- c.Check(err.Error(), Equals, "plug or slot name not provided")
+ c.Check(err, ErrorMatches, "plug or slot name not provided")
c.Check(string(stdout), Equals, "")
c.Check(string(stderr), Equals, "")
}
@@ -371,8 +348,7 @@
c.Assert(err, IsNil)
stdout, stderr, err := ctlcmd.Run(mockContext, []string{"set", ":aplug", "foo=bar"}, 0)
- c.Check(err, NotNil)
- c.Check(err.Error(), Equals, `interface attributes can only be set during the execution of prepare hooks`)
+ c.Check(err, ErrorMatches, `interface attributes can only be set during the execution of prepare hooks`)
c.Check(string(stdout), Equals, "")
c.Check(string(stderr), Equals, "")
}
diff -Nru snapd-2.41+19.10.1/overlord/hookstate/ctlcmd/unset.go snapd-2.42.1+19.10/overlord/hookstate/ctlcmd/unset.go
--- snapd-2.41+19.10.1/overlord/hookstate/ctlcmd/unset.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/hookstate/ctlcmd/unset.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,74 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package ctlcmd
+
+import (
+ "fmt"
+
+ "github.com/snapcore/snapd/i18n"
+ "github.com/snapcore/snapd/overlord/configstate"
+)
+
+type unsetCommand struct {
+ baseCommand
+
+ Positional struct {
+ ConfKeys []string
+ } `positional-args:"yes"`
+}
+
+var shortUnsetHelp = i18n.G("Remove configuration options")
+var longUnsetHelp = i18n.G(`
+The unset command removes the provided configuration options as requested.
+
+$ snapctl unset name address
+
+All configuration changes are persisted at once, and only after the
+snap's configuration hook returns successfully.
+
+Nested values may be removed via a dotted path:
+
+$ snapctl unset user.name
+`)
+
+func init() {
+ addCommand("unset", shortUnsetHelp, longUnsetHelp, func() command { return &unsetCommand{} })
+}
+
+func (s *unsetCommand) Execute(args []string) error {
+ if len(s.Positional.ConfKeys) == 0 {
+ return fmt.Errorf(i18n.G("unset which option?"))
+ }
+
+ context := s.context()
+ if context == nil {
+ return fmt.Errorf("cannot unset without a context")
+ }
+
+ context.Lock()
+ tr := configstate.ContextTransaction(context)
+ context.Unlock()
+
+ for _, confKey := range s.Positional.ConfKeys {
+ tr.Set(context.InstanceName(), confKey, nil)
+ }
+
+ return nil
+}
diff -Nru snapd-2.41+19.10.1/overlord/hookstate/ctlcmd/unset_test.go snapd-2.42.1+19.10/overlord/hookstate/ctlcmd/unset_test.go
--- snapd-2.41+19.10.1/overlord/hookstate/ctlcmd/unset_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/hookstate/ctlcmd/unset_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,137 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package ctlcmd_test
+
+import (
+ "strings"
+
+ "github.com/snapcore/snapd/overlord/configstate/config"
+ "github.com/snapcore/snapd/overlord/hookstate"
+ "github.com/snapcore/snapd/overlord/hookstate/ctlcmd"
+ "github.com/snapcore/snapd/overlord/hookstate/hooktest"
+ "github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/snap"
+
+ . "gopkg.in/check.v1"
+)
+
+type unsetSuite struct {
+ mockContext *hookstate.Context
+ mockHandler *hooktest.MockHandler
+}
+
+var _ = Suite(&unsetSuite{})
+
+func (s *unsetSuite) SetUpTest(c *C) {
+ s.mockHandler = hooktest.NewMockHandler()
+
+ state := state.New(nil)
+ state.Lock()
+ defer state.Unlock()
+
+ task := state.NewTask("test-task", "my test task")
+ setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "hook"}
+
+ var err error
+ s.mockContext, err = hookstate.NewContext(task, task.State(), setup, s.mockHandler, "")
+ c.Assert(err, IsNil)
+}
+
+func (s *unsetSuite) TestInvalidArguments(c *C) {
+ _, _, err := ctlcmd.Run(s.mockContext, []string{"unset"}, 0)
+ c.Check(err, ErrorMatches, "unset which option.*")
+}
+
+func (s *unsetSuite) TestUnsetOne(c *C) {
+ // Setup an initial configuration
+ s.mockContext.State().Lock()
+ tr := config.NewTransaction(s.mockContext.State())
+ tr.Set("test-snap", "foo", "a")
+ tr.Commit()
+ s.mockContext.State().Unlock()
+
+ // Sanity check
+ var value interface{}
+ s.mockContext.State().Lock()
+ tr = config.NewTransaction(s.mockContext.State())
+ c.Check(tr.Get("test-snap", "foo", &value), IsNil)
+ s.mockContext.State().Unlock()
+ c.Check(value, Equals, "a")
+
+ stdout, stderr, err := ctlcmd.Run(s.mockContext, []string{"unset", "foo"}, 0)
+ c.Check(err, IsNil)
+ c.Check(string(stdout), Equals, "")
+ c.Check(string(stderr), Equals, "")
+
+ // Notify the context that we're done. This should save the config.
+ s.mockContext.Lock()
+ defer s.mockContext.Unlock()
+ c.Check(s.mockContext.Done(), IsNil)
+
+ // Verify that the global config has been updated.
+ tr = config.NewTransaction(s.mockContext.State())
+ c.Check(tr.Get("test-snap", "foo", &value), ErrorMatches, `snap "test-snap" has no "foo" configuration option`)
+}
+
+func (s *unsetSuite) TestUnsetMany(c *C) {
+ // Setup an initial configuration
+ s.mockContext.State().Lock()
+ tr := config.NewTransaction(s.mockContext.State())
+ tr.Set("test-snap", "foo", "a")
+ tr.Set("test-snap", "bar", "b")
+ tr.Set("test-snap", "baz", "c")
+ tr.Commit()
+ s.mockContext.State().Unlock()
+
+ stdout, stderr, err := ctlcmd.Run(s.mockContext, []string{"unset", "foo", "bar"}, 0)
+ c.Check(err, IsNil)
+ c.Check(string(stdout), Equals, "")
+ c.Check(string(stderr), Equals, "")
+
+ // Notify the context that we're done. This should save the config.
+ s.mockContext.Lock()
+ defer s.mockContext.Unlock()
+ c.Check(s.mockContext.Done(), IsNil)
+
+ // Verify that the global config has been updated.
+ var value interface{}
+ tr = config.NewTransaction(s.mockContext.State())
+ c.Check(tr.Get("test-snap", "foo", &value), ErrorMatches, `snap "test-snap" has no "foo" configuration option`)
+ c.Check(tr.Get("test-snap", "bar", &value), ErrorMatches, `snap "test-snap" has no "bar" configuration option`)
+ c.Check(tr.Get("test-snap", "baz", &value), IsNil)
+ c.Check(value, Equals, "c")
+}
+
+func (s *unsetSuite) TestUnsetRegularUserForbidden(c *C) {
+ _, _, err := ctlcmd.Run(s.mockContext, []string{"unset", "key"}, 1000)
+ c.Assert(err, ErrorMatches, `cannot use "unset" with uid 1000, try with sudo`)
+ forbidden, _ := err.(*ctlcmd.ForbiddenCommandError)
+ c.Assert(forbidden, NotNil)
+}
+
+func (s *unsetSuite) TestUnsetHelpRegularUserAllowed(c *C) {
+ _, _, err := ctlcmd.Run(s.mockContext, []string{"unset", "-h"}, 1000)
+ c.Assert(strings.HasPrefix(err.Error(), "Usage:"), Equals, true)
+}
+
+func (s *unsetSuite) TestCommandWithoutContext(c *C) {
+ _, _, err := ctlcmd.Run(nil, []string{"unset", "foo"}, 0)
+ c.Check(err, ErrorMatches, ".*cannot unset without a context.*")
+}
diff -Nru snapd-2.41+19.10.1/overlord/ifacestate/export_test.go snapd-2.42.1+19.10/overlord/ifacestate/export_test.go
--- snapd-2.41+19.10.1/overlord/ifacestate/export_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/ifacestate/export_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -55,6 +55,8 @@
AllocHotplugSeq = allocHotplugSeq
AddHotplugSeqWaitTask = addHotplugSeqWaitTask
AddHotplugSlot = addHotplugSlot
+
+ BatchConnectTasks = batchConnectTasks
)
func NewConnectOptsWithAutoSet() connectOpts {
@@ -65,6 +67,10 @@
return disconnectOpts{ByHotplug: true}
}
+func NewConnectOptsWithDelayProfilesSet() connectOpts {
+ return connectOpts{AutoConnect: true, ByGadget: false, DelayedSetupProfiles: true}
+}
+
func MockRemoveStaleConnections(f func(st *state.State) error) (restore func()) {
old := removeStaleConnections
removeStaleConnections = f
diff -Nru snapd-2.41+19.10.1/overlord/ifacestate/handlers.go snapd-2.42.1+19.10/overlord/ifacestate/handlers.go
--- snapd-2.41+19.10.1/overlord/ifacestate/handlers.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/ifacestate/handlers.go 2019-10-30 12:17:43.000000000 +0000
@@ -28,6 +28,7 @@
"gopkg.in/tomb.v2"
+ "github.com/snapcore/snapd/i18n"
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/interfaces/hotplug"
"github.com/snapcore/snapd/logger"
@@ -401,6 +402,10 @@
if err := task.Get("by-gadget", &byGadget); err != nil && err != state.ErrNoState {
return err
}
+ var delayedSetupProfiles bool
+ if err := task.Get("delayed-setup-profiles", &delayedSetupProfiles); err != nil && err != state.ErrNoState {
+ return err
+ }
deviceCtx, err := snapstate.DeviceCtx(st, task, nil)
if err != nil {
@@ -475,14 +480,18 @@
return err
}
- slotOpts := confinementOptions(slotSnapst.Flags)
- if err := m.setupSnapSecurity(task, slot.Snap, slotOpts, perfTimings); err != nil {
- return err
- }
+ if !delayedSetupProfiles {
+ slotOpts := confinementOptions(slotSnapst.Flags)
+ if err := m.setupSnapSecurity(task, slot.Snap, slotOpts, perfTimings); err != nil {
+ return err
+ }
- plugOpts := confinementOptions(plugSnapst.Flags)
- if err := m.setupSnapSecurity(task, plug.Snap, plugOpts, perfTimings); err != nil {
- return err
+ plugOpts := confinementOptions(plugSnapst.Flags)
+ if err := m.setupSnapSecurity(task, plug.Snap, plugOpts, perfTimings); err != nil {
+ return err
+ }
+ } else {
+ logger.Debugf("Connect handler: skipping setupSnapSecurity for snaps %q and %q", plug.Snap.InstanceName(), slot.Snap.InstanceName())
}
conns[connRef.ID()] = &connState{
@@ -895,6 +904,47 @@
return false
}
+// batchConnectTasks creates connect tasks and interface hooks for
+// conns and sets their wait chain with regard to the setupProfiles
+// task.
+//
+// The tasks are chained so that: - prepare-plug-, prepare-slot- and
+// connect tasks are all executed before setup-profiles -
+// connect-plug-, connect-slot- are all executed after setup-profiles.
+// The "delayed-setup-profiles" flag is set on the connect tasks to
+// indicate that doConnect handler should not set security backends up
+// because this will be done later by the setup-profiles task.
+func batchConnectTasks(st *state.State, snapsup *snapstate.SnapSetup, conns map[string]*interfaces.ConnRef, autoconnect bool) (*state.TaskSet, error) {
+ setupProfiles := st.NewTask("setup-profiles", fmt.Sprintf(i18n.G("Setup snap %q (%s) security profiles for auto-connections"), snapsup.InstanceName(), snapsup.Revision()))
+ setupProfiles.Set("snap-setup", snapsup)
+
+ ts := state.NewTaskSet()
+ for _, conn := range conns {
+ connectTs, err := connect(st, conn.PlugRef.Snap, conn.PlugRef.Name, conn.SlotRef.Snap, conn.SlotRef.Name, connectOpts{AutoConnect: autoconnect, DelayedSetupProfiles: true})
+ if err != nil {
+ return nil, fmt.Errorf("internal error: auto-connect of %q failed: %s", conn, err)
+ }
+
+ // setup-profiles needs to wait for the main "connect" task
+ connectTask, _ := connectTs.Edge(ConnectTaskEdge)
+ if connectTask == nil {
+ return nil, fmt.Errorf("internal error: no 'connect' task found for %q", conn)
+ }
+ setupProfiles.WaitFor(connectTask)
+
+ // setup-profiles must be run before the task that marks the end of connect-plug- and connect-slot- hooks
+ afterConnectTask, _ := connectTs.Edge(AfterConnectHooksEdge)
+ if afterConnectTask != nil {
+ afterConnectTask.WaitFor(setupProfiles)
+ }
+ ts.AddAll(connectTs)
+ }
+ if len(ts.Tasks()) > 0 {
+ ts.AddTask(setupProfiles)
+ }
+ return ts, nil
+}
+
// doAutoConnect creates task(s) to connect the given snap to viable candidates.
func (m *InterfaceManager) doAutoConnect(task *state.Task, _ *tomb.Tomb) error {
st := task.State()
@@ -926,7 +976,6 @@
snapName := snapsup.InstanceName()
- autots := state.NewTaskSet()
autochecker, err := newAutoConnectChecker(st, deviceCtx)
if err != nil {
return err
@@ -1068,13 +1117,10 @@
}
}
- // Create connect tasks and interface hooks
- for _, conn := range newconns {
- ts, err := connect(st, conn.PlugRef.Snap, conn.PlugRef.Name, conn.SlotRef.Snap, conn.SlotRef.Name, connectOpts{AutoConnect: true})
- if err != nil {
- return fmt.Errorf("internal error: auto-connect of %q failed: %s", conn, err)
- }
- autots.AddAll(ts)
+ autoconnect := true
+ autots, err := batchConnectTasks(st, snapsup, newconns, autoconnect)
+ if err != nil {
+ return err
}
if len(autots.Tasks()) > 0 {
diff -Nru snapd-2.41+19.10.1/overlord/ifacestate/helpers.go snapd-2.42.1+19.10/overlord/ifacestate/helpers.go
--- snapd-2.41+19.10.1/overlord/ifacestate/helpers.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/ifacestate/helpers.go 2019-10-30 12:17:43.000000000 +0000
@@ -387,22 +387,7 @@
}
func (m *InterfaceManager) setupSnapSecurity(task *state.Task, snapInfo *snap.Info, opts interfaces.ConfinementOptions, tm timings.Measurer) error {
- st := task.State()
- instanceName := snapInfo.InstanceName()
-
- for _, backend := range m.repo.Backends() {
- st.Unlock()
- var err error
- timings.Run(tm, "setup-security-backend", fmt.Sprintf("setup security backend %q for snap %q", backend.Name(), snapInfo.InstanceName()), func(nesttm timings.Measurer) {
- err = backend.Setup(snapInfo, opts, m.repo, nesttm)
- })
- st.Lock()
- if err != nil {
- task.Errorf("cannot setup %s for snap %q: %s", backend.Name(), instanceName, err)
- return err
- }
- }
- return nil
+ return m.setupSecurityByBackend(task, []*snap.Info{snapInfo}, []interfaces.ConfinementOptions{opts}, tm)
}
func (m *InterfaceManager) removeSnapSecurity(task *state.Task, instanceName string) error {
diff -Nru snapd-2.41+19.10.1/overlord/ifacestate/ifacestate.go snapd-2.42.1+19.10/overlord/ifacestate/ifacestate.go
--- snapd-2.41+19.10.1/overlord/ifacestate/ifacestate.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/ifacestate/ifacestate.go 2019-10-30 12:17:43.000000000 +0000
@@ -44,6 +44,11 @@
Connection interfaces.ConnRef
}
+const (
+ ConnectTaskEdge = state.TaskSetEdge("connect-task")
+ AfterConnectHooksEdge = state.TaskSetEdge("after-connect-hooks")
+)
+
func (e ErrAlreadyConnected) Error() string {
return fmt.Sprintf("already connected: %q", e.Connection.ID())
}
@@ -77,6 +82,8 @@
type connectOpts struct {
ByGadget bool
AutoConnect bool
+
+ DelayedSetupProfiles bool
}
// Connect returns a set of tasks for connecting an interface.
@@ -198,6 +205,9 @@
if flags.ByGadget {
connectInterface.Set("by-gadget", true)
}
+ if flags.DelayedSetupProfiles {
+ connectInterface.Set("delayed-setup-profiles", true)
+ }
// Expose a copy of all plug and slot attributes coming from yaml to interface hooks. The hooks will be able
// to modify them but all attributes will be checked against assertions after the hooks are run.
@@ -213,6 +223,11 @@
addTask(connectInterface)
prev = connectInterface
+ if flags.DelayedSetupProfiles {
+ // mark as the last task in connect prepare
+ tasks.MarkEdge(connectInterface, ConnectTaskEdge)
+ }
+
connectSlotHookName := fmt.Sprintf("connect-slot-%s", slotName)
if slotSnapInfo.Hooks[connectSlotHookName] != nil {
connectSlotHookSetup := &hookstate.HookSetup{
@@ -231,6 +246,9 @@
connectSlotConnection := hookstate.HookTaskWithUndo(st, summary, connectSlotHookSetup, undoConnectSlotHookSetup, initialContext)
addTask(connectSlotConnection)
prev = connectSlotConnection
+ if flags.DelayedSetupProfiles {
+ tasks.MarkEdge(connectSlotConnection, AfterConnectHooksEdge)
+ }
}
connectPlugHookName := fmt.Sprintf("connect-plug-%s", plugName)
@@ -250,6 +268,13 @@
summary := fmt.Sprintf(i18n.G("Run hook %s of snap %q"), connectPlugHookSetup.Hook, connectPlugHookSetup.Snap)
connectPlugConnection := hookstate.HookTaskWithUndo(st, summary, connectPlugHookSetup, undoConnectPlugHookSetup, initialContext)
addTask(connectPlugConnection)
+
+ if flags.DelayedSetupProfiles {
+ // only mark AfterConnectHooksEdge if not already set on connect-slot- hook task
+ if edge, _ := tasks.Edge(AfterConnectHooksEdge); edge == nil {
+ tasks.MarkEdge(connectPlugConnection, AfterConnectHooksEdge)
+ }
+ }
prev = connectPlugConnection
}
return tasks, nil
diff -Nru snapd-2.41+19.10.1/overlord/ifacestate/ifacestate_test.go snapd-2.42.1+19.10/overlord/ifacestate/ifacestate_test.go
--- snapd-2.41+19.10.1/overlord/ifacestate/ifacestate_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/ifacestate/ifacestate_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -316,6 +316,10 @@
i++
task = ts.Tasks()[i]
c.Assert(task.Kind(), Equals, "connect")
+ var flag bool
+ c.Assert(task.Get("auto", &flag), Equals, state.ErrNoState)
+ c.Assert(task.Get("delayed-setup-profiles", &flag), Equals, state.ErrNoState)
+ c.Assert(task.Get("by-gadget", &flag), Equals, state.ErrNoState)
var plug interfaces.PlugRef
c.Assert(task.Get("plug", &plug), IsNil)
c.Assert(plug.Snap, Equals, "consumer")
@@ -325,6 +329,10 @@
c.Assert(slot.Snap, Equals, "producer")
c.Assert(slot.Name, Equals, "slot")
+ // "connect" task edge is not present
+ _, err = ts.Edge(ifacestate.ConnectTaskEdge)
+ c.Assert(err, ErrorMatches, `internal error: missing .* edge in task set`)
+
var autoconnect bool
err = task.Get("auto", &autoconnect)
c.Assert(err, Equals, state.ErrNoState)
@@ -359,6 +367,82 @@
c.Assert(hs, Equals, hookstate.HookSetup{Snap: "consumer", Hook: "connect-plug-plug", Optional: true})
c.Assert(task.Get("undo-hook-setup", &undoHookSetup), IsNil)
c.Assert(undoHookSetup, Equals, hookstate.HookSetup{Snap: "consumer", Hook: "disconnect-plug-plug", Optional: true, IgnoreError: true})
+
+ // after-connect-hooks task edge is not present
+ _, err = ts.Edge(ifacestate.AfterConnectHooksEdge)
+ c.Assert(err, ErrorMatches, `internal error: missing .* edge in task set`)
+}
+
+func (s *interfaceManagerSuite) TestConnectTasksDelayProfilesFlag(c *C) {
+ s.mockSnap(c, consumerYaml)
+ s.mockSnap(c, producerYaml)
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ ts, err := ifacestate.ConnectPriv(s.state, "consumer", "plug", "producer", "slot", ifacestate.NewConnectOptsWithDelayProfilesSet())
+ c.Assert(err, IsNil)
+ c.Assert(ts.Tasks(), HasLen, 5)
+ connectTask := ts.Tasks()[2]
+ c.Assert(connectTask.Kind(), Equals, "connect")
+ var delayedSetupProfiles bool
+ connectTask.Get("delayed-setup-profiles", &delayedSetupProfiles)
+ c.Assert(delayedSetupProfiles, Equals, true)
+}
+
+func (s *interfaceManagerSuite) TestBatchConnectTasks(c *C) {
+ s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"}, &ifacetest.TestInterface{InterfaceName: "test2"})
+ s.mockSnap(c, consumerYaml)
+ s.mockSnap(c, consumer2Yaml)
+ s.mockSnap(c, producerYaml)
+ _ = s.manager(c)
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapsup := &snapstate.SnapSetup{SideInfo: &snap.SideInfo{RealName: "snap"}}
+ conns := make(map[string]*interfaces.ConnRef)
+
+ // no connections
+ autoconnect := true
+ ts, err := ifacestate.BatchConnectTasks(s.state, snapsup, conns, autoconnect)
+ c.Assert(err, IsNil)
+ c.Check(ts.Tasks(), HasLen, 0)
+
+ // two connections
+ cref1 := interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"}, SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"}}
+ cref2 := interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "consumer2", Name: "plug"}, SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"}}
+ conns[cref1.ID()] = &cref1
+ conns[cref2.ID()] = &cref2
+
+ ts, err = ifacestate.BatchConnectTasks(s.state, snapsup, conns, autoconnect)
+ c.Assert(err, IsNil)
+ c.Check(ts.Tasks(), HasLen, 9)
+
+ // "setup-profiles" task waits for "connect" tasks of both connections
+ setupProfiles := ts.Tasks()[len(ts.Tasks())-1]
+ c.Assert(setupProfiles.Kind(), Equals, "setup-profiles")
+
+ wt := setupProfiles.WaitTasks()
+ c.Assert(wt, HasLen, 2)
+ for i := 0; i < 2; i++ {
+ c.Check(wt[i].Kind(), Equals, "connect")
+
+ // sanity, check flags on "connect" tasks
+ var flag bool
+ c.Assert(wt[i].Get("delayed-setup-profiles", &flag), IsNil)
+ c.Check(flag, Equals, true)
+ c.Assert(wt[i].Get("auto", &flag), IsNil)
+ c.Check(flag, Equals, true)
+ }
+
+ // connect-slot-slot hooks wait for "setup-profiles"
+ ht := setupProfiles.HaltTasks()
+ c.Assert(ht, HasLen, 2)
+ for i := 0; i < 2; i++ {
+ c.Check(ht[i].Kind(), Equals, "run-hook")
+ c.Check(ht[i].Summary(), Matches, "Run hook connect-slot-slot .*")
+ }
}
type interfaceHooksTestData struct {
@@ -401,41 +485,98 @@
}
-func (s *interfaceManagerSuite) TestConnectTaskHooksConditionals(c *C) {
+var connectHooksTests = []interfaceHooksTestData{{
+ consumer: []string{"prepare-plug-plug"},
+ producer: []string{"prepare-slot-slot"},
+ waitChain: []string{"hook:prepare-plug-plug", "hook:prepare-slot-slot", "task:connect"},
+}, {
+ consumer: []string{"prepare-plug-plug"},
+ producer: []string{"prepare-slot-slot", "connect-slot-slot"},
+ waitChain: []string{"hook:prepare-plug-plug", "hook:prepare-slot-slot", "task:connect", "hook:connect-slot-slot"},
+}, {
+ consumer: []string{"prepare-plug-plug"},
+ producer: []string{"connect-slot-slot"},
+ waitChain: []string{"hook:prepare-plug-plug", "task:connect", "hook:connect-slot-slot"},
+}, {
+ consumer: []string{"connect-plug-plug"},
+ producer: []string{"prepare-slot-slot", "connect-slot-slot"},
+ waitChain: []string{"hook:prepare-slot-slot", "task:connect", "hook:connect-slot-slot", "hook:connect-plug-plug"},
+}, {
+ consumer: []string{"connect-plug-plug"},
+ producer: []string{"connect-slot-slot"},
+ waitChain: []string{"task:connect", "hook:connect-slot-slot", "hook:connect-plug-plug"},
+}, {
+ consumer: []string{"prepare-plug-plug", "connect-plug-plug"},
+ producer: []string{"prepare-slot-slot"},
+ waitChain: []string{"hook:prepare-plug-plug", "hook:prepare-slot-slot", "task:connect", "hook:connect-plug-plug"},
+}, {
+ consumer: []string{"prepare-plug-plug", "connect-plug-plug"},
+ producer: []string{"prepare-slot-slot", "connect-slot-slot"},
+ waitChain: []string{"hook:prepare-plug-plug", "hook:prepare-slot-slot", "task:connect", "hook:connect-slot-slot", "hook:connect-plug-plug"},
+}}
+
+func (s *interfaceManagerSuite) TestConnectTaskHookdEdges(c *C) {
s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"})
- hooksTests := []interfaceHooksTestData{{
- consumer: []string{"prepare-plug-plug"},
- producer: []string{"prepare-slot-slot"},
- waitChain: []string{"hook:prepare-plug-plug", "hook:prepare-slot-slot", "task:connect"},
- }, {
- consumer: []string{"prepare-plug-plug"},
- producer: []string{"prepare-slot-slot", "connect-slot-slot"},
- waitChain: []string{"hook:prepare-plug-plug", "hook:prepare-slot-slot", "task:connect", "hook:connect-slot-slot"},
- }, {
- consumer: []string{"prepare-plug-plug"},
- producer: []string{"connect-slot-slot"},
- waitChain: []string{"hook:prepare-plug-plug", "task:connect", "hook:connect-slot-slot"},
- }, {
- consumer: []string{"connect-plug-plug"},
- producer: []string{"prepare-slot-slot", "connect-slot-slot"},
- waitChain: []string{"hook:prepare-slot-slot", "task:connect", "hook:connect-slot-slot", "hook:connect-plug-plug"},
- }, {
- consumer: []string{"connect-plug-plug"},
- producer: []string{"connect-slot-slot"},
- waitChain: []string{"task:connect", "hook:connect-slot-slot", "hook:connect-plug-plug"},
- }, {
- consumer: []string{"prepare-plug-plug", "connect-plug-plug"},
- producer: []string{"prepare-slot-slot"},
- waitChain: []string{"hook:prepare-plug-plug", "hook:prepare-slot-slot", "task:connect", "hook:connect-plug-plug"},
- }, {
- consumer: []string{"prepare-plug-plug", "connect-plug-plug"},
- producer: []string{"prepare-slot-slot", "connect-slot-slot"},
- waitChain: []string{"hook:prepare-plug-plug", "hook:prepare-slot-slot", "task:connect", "hook:connect-slot-slot", "hook:connect-plug-plug"},
- }}
+ _ = s.manager(c)
+ for _, hooks := range connectHooksTests {
+ var hooksYaml string
+ for _, name := range hooks.consumer {
+ hooksYaml = fmt.Sprintf("%s %s:\n", hooksYaml, name)
+ }
+ consumer := fmt.Sprintf(consumerYaml3, hooksYaml)
+
+ hooksYaml = ""
+ for _, name := range hooks.producer {
+ hooksYaml = fmt.Sprintf("%s %s:\n", hooksYaml, name)
+ }
+ producer := fmt.Sprintf(producerYaml3, hooksYaml)
+
+ s.mockSnap(c, consumer)
+ s.mockSnap(c, producer)
+
+ s.state.Lock()
+
+ ts, err := ifacestate.ConnectPriv(s.state, "consumer", "plug", "producer", "slot", ifacestate.NewConnectOptsWithDelayProfilesSet())
+ c.Assert(err, IsNil)
+
+ // check task edges
+ edge, err := ts.Edge(ifacestate.ConnectTaskEdge)
+ c.Assert(err, IsNil)
+ c.Check(edge.Kind(), Equals, "connect")
+
+ // AfterConnectHooks task edge is set on "connect-slot-" or "connect-plug-" hook task (whichever comes first after "connect")
+ // and is not present if neither of them exists.
+ var expectedAfterConnectEdge string
+ for _, hookName := range hooks.producer {
+ if strings.HasPrefix(hookName, "connect-") {
+ expectedAfterConnectEdge = "hook:" + hookName
+ }
+ }
+ if expectedAfterConnectEdge == "" {
+ for _, hookName := range hooks.consumer {
+ if strings.HasPrefix(hookName, "connect-") {
+ expectedAfterConnectEdge = "hook:" + hookName
+ }
+ }
+ }
+ edge, err = ts.Edge(ifacestate.AfterConnectHooksEdge)
+ if expectedAfterConnectEdge != "" {
+ c.Assert(err, IsNil)
+ c.Check(hookNameOrTaskKind(c, edge), Equals, expectedAfterConnectEdge)
+ } else {
+ c.Assert(err, ErrorMatches, `internal error: missing .* edge in task set`)
+ }
+
+ s.state.Unlock()
+ }
+}
+
+func (s *interfaceManagerSuite) TestConnectTaskHooksConditionals(c *C) {
+ s.mockIfaces(c, &ifacetest.TestInterface{InterfaceName: "test"})
_ = s.manager(c)
- for _, hooks := range hooksTests {
+ for _, hooks := range connectHooksTests {
var hooksYaml string
for _, name := range hooks.consumer {
hooksYaml = fmt.Sprintf("%s %s:\n", hooksYaml, name)
@@ -1731,6 +1872,23 @@
interface: network
`
+var sampleSnapYamlManyPlugs = `
+name: snap
+version: 1
+apps:
+ app:
+ command: foo
+plugs:
+ network:
+ interface: network
+ home:
+ interface: home
+ x11:
+ interface: x11
+ wayland:
+ interface: wayland
+`
+
var consumerYaml = `
name: consumer
version: 1
@@ -2012,6 +2170,7 @@
Revision: snapInfo.Revision,
},
})
+
s.settle(c)
s.state.Lock()
@@ -3008,12 +3167,65 @@
// Ensure that both snaps were setup correctly.
c.Assert(s.secBackend.SetupCalls, HasLen, 3)
c.Assert(s.secBackend.RemoveCalls, HasLen, 0)
- // The sample snap was setup, with the correct new revision.
+
+ // The sample snap was setup, with the correct new revision:
+ // 1st call is for initial setup-profiles, 2nd call is for setup-profiles after connect task.
c.Check(s.secBackend.SetupCalls[0].SnapInfo.InstanceName(), Equals, snapInfo.InstanceName())
c.Check(s.secBackend.SetupCalls[0].SnapInfo.Revision, Equals, snapInfo.Revision)
+ c.Check(s.secBackend.SetupCalls[1].SnapInfo.InstanceName(), Equals, snapInfo.InstanceName())
+ c.Check(s.secBackend.SetupCalls[1].SnapInfo.Revision, Equals, snapInfo.Revision)
+
// The OS snap was setup (because its connected to sample snap).
- c.Check(s.secBackend.SetupCalls[1].SnapInfo.InstanceName(), Equals, coreSnapInfo.InstanceName())
- c.Check(s.secBackend.SetupCalls[1].SnapInfo.Revision, Equals, coreSnapInfo.Revision)
+ c.Check(s.secBackend.SetupCalls[2].SnapInfo.InstanceName(), Equals, coreSnapInfo.InstanceName())
+ c.Check(s.secBackend.SetupCalls[2].SnapInfo.Revision, Equals, coreSnapInfo.Revision)
+}
+
+// auto-connect needs to setup security for connected slots after autoconnection
+func (s *interfaceManagerSuite) TestAutoConnectSetupSecurityOnceWithMultiplePlugs(c *C) {
+ s.MockModel(c, nil)
+
+ // Add an OS snap.
+ _ = s.mockSnap(c, ubuntuCoreSnapYaml)
+
+ // Initialize the manager. This registers the OS snap.
+ mgr := s.manager(c)
+
+ // Add a sample snap with a multiple plugs which should be auto-connected.
+ snapInfo := s.mockSnap(c, sampleSnapYamlManyPlugs)
+
+ // Run the setup-snap-security task and let it finish.
+ change := s.addSetupSnapSecurityChange(c, &snapstate.SnapSetup{
+ SideInfo: &snap.SideInfo{
+ RealName: snapInfo.SnapName(),
+ Revision: snapInfo.Revision,
+ },
+ })
+ s.settle(c)
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ // Ensure that the task succeeded.
+ c.Assert(change.Err(), IsNil)
+ c.Assert(change.Status(), Equals, state.DoneStatus)
+
+ repo := mgr.Repository()
+
+ for _, ifaceName := range []string{"network", "home", "x11", "wayland"} {
+ cref := &interfaces.ConnRef{PlugRef: interfaces.PlugRef{Snap: "snap", Name: ifaceName}, SlotRef: interfaces.SlotRef{Snap: "ubuntu-core", Name: ifaceName}}
+ conn, _ := repo.Connection(cref)
+ c.Check(conn, NotNil, Commentf("missing connection for %s interface", ifaceName))
+ }
+
+ // Three backend calls: initial setup profiles, 2 setup calls for both core and snap.
+ c.Assert(s.secBackend.SetupCalls, HasLen, 3)
+ c.Assert(s.secBackend.RemoveCalls, HasLen, 0)
+ setupCalls := make(map[string]int)
+ for _, sc := range s.secBackend.SetupCalls {
+ setupCalls[sc.SnapInfo.InstanceName()]++
+ }
+ c.Check(setupCalls["snap"], Equals, 2)
+ c.Check(setupCalls["ubuntu-core"], Equals, 1)
}
func (s *interfaceManagerSuite) TestDoDiscardConnsPlug(c *C) {
diff -Nru snapd-2.41+19.10.1/overlord/managers_test.go snapd-2.42.1+19.10/overlord/managers_test.go
--- snapd-2.41+19.10.1/overlord/managers_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/managers_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -43,8 +43,8 @@
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/asserts/assertstest"
"github.com/snapcore/snapd/asserts/sysdb"
- "github.com/snapcore/snapd/boot/boottest"
"github.com/snapcore/snapd/bootloader"
+ "github.com/snapcore/snapd/bootloader/bootloadertest"
"github.com/snapcore/snapd/client"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/interfaces"
@@ -1529,8 +1529,8 @@
}
func (s *mgrsSuite) TestInstallCoreSnapUpdatesBootloaderAndSplitsAcrossRestart(c *C) {
- loader := boottest.NewMockBootloader("mock", c.MkDir())
- bootloader.Force(loader)
+ bloader := bootloadertest.Mock("mock", c.MkDir())
+ bootloader.Force(bloader)
defer bootloader.Force(nil)
restore := release.MockOnClassic(false)
@@ -1579,15 +1579,15 @@
c.Assert(t.Status(), Equals, state.DoingStatus, Commentf("install-snap change failed with: %v", chg.Err()))
// this is already set
- c.Assert(loader.BootVars, DeepEquals, map[string]string{
+ c.Assert(bloader.BootVars, DeepEquals, map[string]string{
"snap_try_core": "core_x1.snap",
"snap_mode": "try",
})
// simulate successful restart happened
state.MockRestarting(st, state.RestartUnset)
- loader.BootVars["snap_mode"] = ""
- boottest.SetBootBase("core_x1.snap", loader)
+ bloader.BootVars["snap_mode"] = ""
+ bloader.SetBootBase("core_x1.snap")
st.Unlock()
err = s.o.Settle(settleTimeout)
@@ -1599,8 +1599,8 @@
}
func (s *mgrsSuite) TestInstallKernelSnapUpdatesBootloader(c *C) {
- loader := boottest.NewMockBootloader("mock", c.MkDir())
- bootloader.Force(loader)
+ bloader := bootloadertest.Mock("mock", c.MkDir())
+ bootloader.Force(bloader)
defer bootloader.Force(nil)
restore := release.MockOnClassic(false)
@@ -1659,7 +1659,7 @@
c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("install-snap change failed with: %v", chg.Err()))
- c.Assert(loader.BootVars, DeepEquals, map[string]string{
+ c.Assert(bloader.BootVars, DeepEquals, map[string]string{
"snap_try_kernel": "pc-kernel_x1.snap",
"snap_mode": "try",
})
@@ -2614,9 +2614,12 @@
c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("install-snap change failed with: %v", chg.Err()))
tasks := chg.Tasks()
- connectTask := tasks[len(tasks)-1]
+ connectTask := tasks[len(tasks)-2]
c.Assert(connectTask.Kind(), Equals, "connect")
+ setupProfilesTask := tasks[len(tasks)-1]
+ c.Assert(setupProfilesTask.Kind(), Equals, "setup-profiles")
+
// verify connect task data
var plugRef interfaces.PlugRef
var slotRef interfaces.SlotRef
@@ -3399,10 +3402,10 @@
}
func (s *mgrsSuite) TestRemodelSwitchKernelTrack(c *C) {
- loader := boottest.NewMockBootloader("mock", c.MkDir())
- boottest.SetBootKernel("pc-kernel_1.snap", loader)
- boottest.SetBootBase("core_1.snap", loader)
- bootloader.Force(loader)
+ bloader := bootloadertest.Mock("mock", c.MkDir())
+ bloader.SetBootKernel("pc-kernel_1.snap")
+ bloader.SetBootBase("core_1.snap")
+ bootloader.Force(bloader)
defer bootloader.Force(nil)
restore := release.MockOnClassic(false)
@@ -3495,6 +3498,139 @@
c.Assert(tasks, HasLen, i+1)
}
+func (ms *mgrsSuite) TestRemodelSwitchToDifferentKernel(c *C) {
+ bloader := bootloadertest.Mock("mock", c.MkDir())
+ bootloader.Force(bloader)
+ defer bootloader.Force(nil)
+
+ restore := release.MockOnClassic(false)
+ defer restore()
+
+ mockServer := ms.mockStore(c)
+ defer mockServer.Close()
+
+ st := ms.o.State()
+ st.Lock()
+ defer st.Unlock()
+
+ si := &snap.SideInfo{RealName: "pc-kernel", SnapID: fakeSnapID("pc-kernel"), Revision: snap.R(1)}
+ snapstate.Set(st, "pc-kernel", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{si},
+ Current: snap.R(1),
+ SnapType: "kernel",
+ })
+ bloader.SetBootVars(map[string]string{
+ "snap_mode": "",
+ "snap_core": "core_1.snap",
+ "snap_kernel": "pc-kernel_1.snap",
+ })
+ si2 := &snap.SideInfo{RealName: "pc", SnapID: fakeSnapID("pc"), Revision: snap.R(1)}
+ gadgetSnapYaml := "name: pc\nversion: 1.0\ntype: gadget"
+ snapstate.Set(st, "pc", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{si2},
+ Current: snap.R(1),
+ SnapType: "gadget",
+ })
+ gadgetYaml := `
+volumes:
+ volume-id:
+ bootloader: grub
+`
+ snaptest.MockSnapWithFiles(c, gadgetSnapYaml, si2, [][]string{
+ {"meta/gadget.yaml", gadgetYaml},
+ })
+
+ // add "brand-kernel" snap to fake store
+ const brandKernelYaml = `name: brand-kernel
+type: kernel
+version: 1.0`
+ ms.prereqSnapAssertions(c, map[string]interface{}{
+ "snap-name": "brand-kernel",
+ "publisher-id": "can0nical",
+ })
+ snapPath, _ := ms.makeStoreTestSnap(c, brandKernelYaml, "2")
+ ms.serveSnap(snapPath, "2")
+
+ // add "foo" snap to fake store
+ ms.prereqSnapAssertions(c, map[string]interface{}{
+ "snap-name": "foo",
+ })
+ snapPath, _ = ms.makeStoreTestSnap(c, `{name: "foo", version: 1.0}`, "1")
+ ms.serveSnap(snapPath, "1")
+
+ // create/set custom model assertion
+ model := ms.brands.Model("can0nical", "my-model", modelDefaults)
+
+ // setup model assertion
+ devicestatetest.SetDevice(st, &auth.DeviceState{
+ Brand: "can0nical",
+ Model: "my-model",
+ Serial: "serialserialserial",
+ })
+ err := assertstate.Add(st, model)
+ c.Assert(err, IsNil)
+
+ // create a new model
+ newModel := ms.brands.Model("can0nical", "my-model", modelDefaults, map[string]interface{}{
+ "kernel": "brand-kernel",
+ "revision": "1",
+ "required-snaps": []interface{}{"foo"},
+ })
+
+ chg, err := devicestate.Remodel(st, newModel)
+ c.Assert(err, IsNil)
+
+ st.Unlock()
+ // regular settleTimeout is not enough on arm buildds :/
+ err = ms.o.Settle(4 * settleTimeout)
+ st.Lock()
+ c.Assert(err, IsNil)
+ c.Assert(chg.Err(), IsNil)
+
+ // system waits for a restart because of the new kernel
+ t := findKind(chg, "auto-connect")
+ c.Assert(t, NotNil)
+ c.Assert(t.Status(), Equals, state.DoingStatus)
+
+ // simulate successful restart happened
+ state.MockRestarting(st, state.RestartUnset)
+
+ // continue
+ st.Unlock()
+ // regular settleTimeout is not enough on arm buildds :/
+ err = ms.o.Settle(4 * settleTimeout)
+ st.Lock()
+ c.Assert(err, IsNil)
+
+ c.Assert(chg.Status(), Equals, state.DoneStatus, Commentf("upgrade-snap change failed with: %v", chg.Err()))
+
+ // ensure tasks were run in the right order
+ tasks := chg.Tasks()
+ sort.Sort(byReadyTime(tasks))
+
+ // first all downloads/checks in sequential order
+ var i int
+ i += validateDownloadCheckTasks(c, tasks[i:], "brand-kernel", "2", "stable")
+ i += validateDownloadCheckTasks(c, tasks[i:], "foo", "1", "stable")
+
+ // then all installs in sequential order
+ i += validateInstallTasks(c, tasks[i:], "brand-kernel", "2")
+ i += validateInstallTasks(c, tasks[i:], "foo", "1")
+
+ // ensure that we only have the tasks we checked (plus the one
+ // extra "set-model" task)
+ c.Assert(tasks, HasLen, i+1)
+
+ // ensure we did not try device registration
+ for _, t := range st.Tasks() {
+ if t.Kind() == "request-serial" {
+ c.Fatalf("test should not create a request-serial task but did")
+ }
+ }
+}
+
func (s *mgrsSuite) TestRemodelStoreSwitch(c *C) {
s.prereqSnapAssertions(c, map[string]interface{}{
"snap-name": "foo",
@@ -3628,13 +3764,14 @@
err = assertstate.Add(st, model)
c.Assert(err, IsNil)
- signSerial := func(c *C, bhv *devicestatetest.DeviceServiceBehavior, headers map[string]interface{}, body []byte) (asserts.Assertion, error) {
+ signSerial := func(c *C, bhv *devicestatetest.DeviceServiceBehavior, headers map[string]interface{}, body []byte) (serial asserts.Assertion, ancillary []asserts.Assertion, err error) {
brandID := headers["brand-id"].(string)
model := headers["model"].(string)
c.Check(brandID, Equals, "my-brand")
c.Check(model, Equals, "my-model")
headers["authority-id"] = brandID
- return s.brands.Signing("my-brand").Sign(asserts.SerialType, headers, body, "")
+ a, err := s.brands.Signing("my-brand").Sign(asserts.SerialType, headers, body, "")
+ return a, nil, err
}
bhv := &devicestatetest.DeviceServiceBehavior{
@@ -3768,13 +3905,14 @@
err = assertstate.Add(st, serial)
c.Assert(err, IsNil)
- signSerial := func(c *C, bhv *devicestatetest.DeviceServiceBehavior, headers map[string]interface{}, body []byte) (asserts.Assertion, error) {
+ signSerial := func(c *C, bhv *devicestatetest.DeviceServiceBehavior, headers map[string]interface{}, body []byte) (serial asserts.Assertion, ancillary []asserts.Assertion, err error) {
brandID := headers["brand-id"].(string)
model := headers["model"].(string)
c.Check(brandID, Equals, "my-brand")
c.Check(model, Equals, "other-model")
headers["authority-id"] = brandID
- return s.brands.Signing("my-brand").Sign(asserts.SerialType, headers, body, "")
+ a, err := s.brands.Signing("my-brand").Sign(asserts.SerialType, headers, body, "")
+ return a, nil, err
}
bhv := &devicestatetest.DeviceServiceBehavior{
@@ -3849,3 +3987,96 @@
// we have a session with the new store
c.Check(device.SessionMacaroon, Equals, "other-store-session")
}
+
+func (s *mgrsSuite) TestCheckRefreshFailureWithConcurrentRemoveOfConnectedSnap(c *C) {
+ hookMgr := s.o.HookManager()
+ c.Assert(hookMgr, NotNil)
+
+ // force configure hook failure for some-snap.
+ hookMgr.RegisterHijack("configure", "some-snap", func(ctx *hookstate.Context) error {
+ return fmt.Errorf("failing configure hook")
+ })
+
+ snapPath, _ := s.makeStoreTestSnap(c, someSnapYaml, "40")
+ s.serveSnap(snapPath, "40")
+ snapPath, _ = s.makeStoreTestSnap(c, otherSnapYaml, "50")
+ s.serveSnap(snapPath, "50")
+
+ mockServer := s.mockStore(c)
+ defer mockServer.Close()
+
+ st := s.o.State()
+ st.Lock()
+ defer st.Unlock()
+
+ st.Set("conns", map[string]interface{}{
+ "other-snap:media-hub some-snap:media-hub": map[string]interface{}{"interface": "media-hub", "auto": false},
+ })
+
+ si := &snap.SideInfo{RealName: "some-snap", SnapID: fakeSnapID("some-snap"), Revision: snap.R(1)}
+ snapInfo := snaptest.MockSnap(c, someSnapYaml, si)
+
+ oi := &snap.SideInfo{RealName: "other-snap", SnapID: fakeSnapID("other-snap"), Revision: snap.R(1)}
+ otherInfo := snaptest.MockSnap(c, otherSnapYaml, oi)
+
+ snapstate.Set(st, "some-snap", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{si},
+ Current: snap.R(1),
+ SnapType: "app",
+ })
+ snapstate.Set(st, "other-snap", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{oi},
+ Current: snap.R(1),
+ SnapType: "app",
+ })
+
+ // add snaps to the repo and connect them
+ repo := s.o.InterfaceManager().Repository()
+ c.Assert(repo.AddSnap(snapInfo), IsNil)
+ c.Assert(repo.AddSnap(otherInfo), IsNil)
+ _, err := repo.Connect(&interfaces.ConnRef{
+ PlugRef: interfaces.PlugRef{Snap: "other-snap", Name: "media-hub"},
+ SlotRef: interfaces.SlotRef{Snap: "some-snap", Name: "media-hub"},
+ }, nil, nil, nil, nil, nil)
+ c.Assert(err, IsNil)
+
+ // refresh all
+ c.Assert(assertstate.RefreshSnapDeclarations(st, 0), IsNil)
+
+ ts, err := snapstate.Update(st, "some-snap", nil, 0, snapstate.Flags{})
+ c.Assert(err, IsNil)
+ chg := st.NewChange("refresh", "...")
+ chg.AddAll(ts)
+
+ // remove other-snap
+ ts2, err := snapstate.Remove(st, "other-snap", snap.R(0), nil)
+ c.Assert(err, IsNil)
+ chg2 := st.NewChange("remove-snap", "...")
+ chg2.AddAll(ts2)
+
+ st.Unlock()
+ err = s.o.Settle(settleTimeout)
+ st.Lock()
+
+ c.Check(err, IsNil)
+
+ // the refresh change has failed due to configure hook error
+ c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*failing configure hook.*`)
+ c.Check(chg.Status(), Equals, state.ErrorStatus)
+
+ // download-snap is one of the first tasks in the refresh change, check that it was undone
+ var downloadSnapStatus state.Status
+ for _, t := range chg.Tasks() {
+ if t.Kind() == "download-snap" {
+ downloadSnapStatus = t.Status()
+ break
+ }
+ }
+ c.Check(downloadSnapStatus, Equals, state.UndoneStatus)
+
+ // the remove change succeeded
+ c.Check(chg2.Err(), IsNil)
+ c.Check(chg2.Status(), Equals, state.DoneStatus)
+}
diff -Nru snapd-2.41+19.10.1/overlord/overlord.go snapd-2.42.1+19.10/overlord/overlord.go
--- snapd-2.41+19.10.1/overlord/overlord.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/overlord.go 2019-10-30 12:17:43.000000000 +0000
@@ -169,7 +169,9 @@
o.addManager(cmdstate.Manager(s, o.runner))
o.addManager(snapshotstate.Manager(s, o.runner))
- configstateInit(hookMgr)
+ if err := configstateInit(s, hookMgr); err != nil {
+ return nil, err
+ }
healthstate.Init(hookMgr)
// the shared task runner should be added last!
@@ -302,9 +304,36 @@
return nil
}
o.startedUp = true
+
+ // slow down for tests
+ if s := os.Getenv("SNAPD_SLOW_STARTUP"); s != "" {
+ if d, err := time.ParseDuration(s); err == nil {
+ logger.Noticef("slowing down startup by %v as requested", d)
+
+ time.Sleep(d)
+ }
+ }
+
return o.stateEng.StartUp()
}
+// StartupTimeout computes a usable timeout for the startup
+// initializations by using a pessimistic estimate.
+func (o *Overlord) StartupTimeout() (timeout time.Duration, reasoning string, err error) {
+ // TODO: adjust based on real hardware measurements
+ st := o.State()
+ st.Lock()
+ defer st.Unlock()
+ n, err := snapstate.NumSnaps(st)
+ if err != nil {
+ return 0, "", err
+ }
+ // number of snaps (and connections) play a role
+ reasoning = "pessimistic estimate of 30s plus 5s per snap"
+ to := (30 * time.Second) + time.Duration(n)*(5*time.Second)
+ return to, reasoning, nil
+}
+
func (o *Overlord) ensureTimerSetup() {
o.ensureLock.Lock()
defer o.ensureLock.Unlock()
diff -Nru snapd-2.41+19.10.1/overlord/overlord_test.go snapd-2.42.1+19.10/overlord/overlord_test.go
--- snapd-2.41+19.10.1/overlord/overlord_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/overlord_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -44,6 +44,7 @@
"github.com/snapcore/snapd/overlord/patch"
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/store"
"github.com/snapcore/snapd/testutil"
)
@@ -74,8 +75,9 @@
defer restore()
var configstateInitCalled bool
- overlord.MockConfigstateInit(func(*hookstate.HookManager) {
+ overlord.MockConfigstateInit(func(*state.State, *hookstate.HookManager) error {
configstateInitCalled = true
+ return nil
})
o, err := overlord.New(nil)
@@ -225,6 +227,23 @@
c.Check(b, Equals, true)
}
+func (ovs *overlordSuite) TestNewFailedConfigstate(c *C) {
+ restore := patch.Mock(42, 2, nil)
+ defer restore()
+
+ var configstateInitCalled bool
+ restore = overlord.MockConfigstateInit(func(*state.State, *hookstate.HookManager) error {
+ configstateInitCalled = true
+ return fmt.Errorf("bad bad")
+ })
+ defer restore()
+
+ o, err := overlord.New(nil)
+ c.Assert(err, ErrorMatches, "bad bad")
+ c.Check(o, IsNil)
+ c.Check(configstateInitCalled, Equals, true)
+}
+
type witnessManager struct {
state *state.State
expectedEnsure int
@@ -981,3 +1000,42 @@
c.Assert(o.CanStandby(), Equals, true)
}
+
+func (ovs *overlordSuite) TestStartupTimeout(c *C) {
+ o, err := overlord.New(nil)
+ c.Assert(err, IsNil)
+
+ to, _, err := o.StartupTimeout()
+ c.Assert(err, IsNil)
+ c.Check(to, Equals, 30*time.Second)
+
+ st := o.State()
+ st.Lock()
+ defer st.Unlock()
+
+ // have two snaps
+ snapstate.Set(st, "core18", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{
+ {RealName: "core18", Revision: snap.R(1)},
+ },
+ Current: snap.R(1),
+ SnapType: "base",
+ })
+ snapstate.Set(st, "foo", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{
+ {RealName: "foo", Revision: snap.R(1)},
+ },
+ Current: snap.R(1),
+ SnapType: "app",
+ })
+
+ st.Unlock()
+ to, reasoning, err := o.StartupTimeout()
+ st.Lock()
+ c.Assert(err, IsNil)
+
+ c.Check(to, Equals, (30+5+5)*time.Second)
+ c.Check(reasoning, Equals, "pessimistic estimate of 30s plus 5s per snap")
+}
diff -Nru snapd-2.41+19.10.1/overlord/snapstate/backend/link.go snapd-2.42.1+19.10/overlord/snapstate/backend/link.go
--- snapd-2.41+19.10.1/overlord/snapstate/backend/link.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/snapstate/backend/link.go 2019-10-30 12:17:43.000000000 +0000
@@ -113,19 +113,8 @@
})
}
- // XXX/TODO: this needs to be a task with proper undo and tests!
- if model != nil && !release.OnClassic {
- bootBase := "core"
- if model.Base() != "" {
- bootBase = model.Base()
- }
- switch info.InstanceName() {
- case model.Kernel(), bootBase:
- // XXX: This *needs* to clean up if updateCurrentSymlinks fails
- if err := boot.SetNextBoot(info); err != nil {
- return err
- }
- }
+ if err := boot.Participant(info, info.GetType(), model, release.OnClassic).SetNextBoot(); err != nil {
+ return err
}
if err := updateCurrentSymlinks(info); err != nil {
@@ -154,22 +143,41 @@
return wrappers.StopServices(apps, reason, meter, tm)
}
-func generateWrappers(s *snap.Info) error {
+func generateWrappers(s *snap.Info) (err error) {
+ var cleanupFuncs []func(*snap.Info) error
+ defer func() {
+ if err != nil {
+ for _, cleanup := range cleanupFuncs {
+ cleanup(s)
+ }
+ }
+ }()
+
// add the CLI apps from the snap.yaml
- if err := wrappers.AddSnapBinaries(s); err != nil {
+ if err = wrappers.AddSnapBinaries(s); err != nil {
return err
}
+ cleanupFuncs = append(cleanupFuncs, wrappers.RemoveSnapBinaries)
+
// add the daemons from the snap.yaml
- if err := wrappers.AddSnapServices(s, progress.Null); err != nil {
- wrappers.RemoveSnapBinaries(s)
+ if err = wrappers.AddSnapServices(s, progress.Null); err != nil {
return err
}
+ cleanupFuncs = append(cleanupFuncs, func(s *snap.Info) error {
+ return wrappers.RemoveSnapServices(s, progress.Null)
+ })
+
// add the desktop files
- if err := wrappers.AddSnapDesktopFiles(s); err != nil {
- wrappers.RemoveSnapServices(s, progress.Null)
- wrappers.RemoveSnapBinaries(s)
+ if err = wrappers.AddSnapDesktopFiles(s); err != nil {
return err
}
+ cleanupFuncs = append(cleanupFuncs, wrappers.RemoveSnapDesktopFiles)
+
+ // add the desktop icons
+ if err = wrappers.AddSnapIcons(s); err != nil {
+ return err
+ }
+ cleanupFuncs = append(cleanupFuncs, wrappers.RemoveSnapIcons)
return nil
}
@@ -190,7 +198,12 @@
logger.Noticef("Cannot remove desktop files for %q: %v", s.InstanceName(), err3)
}
- return firstErr(err1, err2, err3)
+ err4 := wrappers.RemoveSnapIcons(s)
+ if err4 != nil {
+ logger.Noticef("Cannot remove desktop icons for %q: %v", s.InstanceName(), err4)
+ }
+
+ return firstErr(err1, err2, err3, err4)
}
// UnlinkSnap makes the snap unavailable to the system removing wrappers and symlinks.
diff -Nru snapd-2.41+19.10.1/overlord/snapstate/backend/mountunit_test.go snapd-2.42.1+19.10/overlord/snapstate/backend/mountunit_test.go
--- snapd-2.41+19.10.1/overlord/snapstate/backend/mountunit_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/snapstate/backend/mountunit_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -63,7 +63,7 @@
}
func (s *mountunitSuite) TestAddMountUnit(c *C) {
- restore := squashfs.MockUseFuse(false)
+ restore := squashfs.MockNeedsFuse(false)
defer restore()
info := &snap.Info{
@@ -89,6 +89,7 @@
Where=%s/foo/13
Type=squashfs
Options=nodev,ro,x-gdu.hide
+LazyUnmount=yes
[Install]
WantedBy=multi-user.target
diff -Nru snapd-2.41+19.10.1/overlord/snapstate/backend/setup.go snapd-2.42.1+19.10/overlord/snapstate/backend/setup.go
--- snapd-2.41+19.10.1/overlord/snapstate/backend/setup.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/snapstate/backend/setup.go 2019-10-30 12:17:43.000000000 +0000
@@ -26,6 +26,7 @@
"github.com/snapcore/snapd/boot"
"github.com/snapcore/snapd/progress"
+ "github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
)
@@ -73,13 +74,13 @@
return snapType, err
}
- if s.GetType() == snap.TypeKernel {
- if err := boot.ExtractKernelAssets(s, snapf); err != nil {
- return snapType, fmt.Errorf("cannot install kernel: %s", err)
- }
+ t := s.GetType()
+ // TODO: maybe look into passing the model
+ if err := boot.Kernel(s, t, nil, release.OnClassic).ExtractKernelAssets(snapf); err != nil {
+ return snapType, fmt.Errorf("cannot install kernel: %s", err)
}
- return s.GetType(), err
+ return t, nil
}
// RemoveSnapFiles removes the snap files from the disk after unmounting the snap.
@@ -99,10 +100,9 @@
snapPath := s.MountFile()
if _, err := os.Lstat(snapPath); err == nil {
// remove the kernel assets (if any)
- if typ == snap.TypeKernel {
- if err := boot.RemoveKernelAssets(s); err != nil {
- return err
- }
+ // TODO: maybe look into passing the model
+ if err := boot.Kernel(s, typ, nil, release.OnClassic).RemoveKernelAssets(); err != nil {
+ return err
}
// remove the snap
diff -Nru snapd-2.41+19.10.1/overlord/snapstate/backend/setup_test.go snapd-2.42.1+19.10/overlord/snapstate/backend/setup_test.go
--- snapd-2.41+19.10.1/overlord/snapstate/backend/setup_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/snapstate/backend/setup_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -27,12 +27,13 @@
. "gopkg.in/check.v1"
- "github.com/snapcore/snapd/boot/boottest"
"github.com/snapcore/snapd/bootloader"
+ "github.com/snapcore/snapd/bootloader/bootloadertest"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/overlord/snapstate/backend"
"github.com/snapcore/snapd/progress"
+ "github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snaptest"
"github.com/snapcore/snapd/systemd"
@@ -144,8 +145,10 @@
}
func (s *setupSuite) TestSetupDoUndoKernel(c *C) {
- loader := boottest.NewMockBootloader("mock", c.MkDir())
- bootloader.Force(loader)
+ // kernel snaps only happen on non-classic
+ defer release.MockOnClassic(false)()
+ bloader := bootloadertest.Mock("mock", c.MkDir())
+ bootloader.Force(bloader)
// we don't get real mounting
os.Setenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS", "1")
@@ -171,15 +174,15 @@
snapType, err := s.be.SetupSnap(snapPath, "kernel", &si, progress.Null)
c.Assert(err, IsNil)
c.Check(snapType, Equals, snap.TypeKernel)
- c.Assert(loader.ExtractKernelAssetsCalls, HasLen, 1)
- c.Assert(loader.ExtractKernelAssetsCalls[0].InstanceName(), Equals, "kernel")
+ c.Assert(bloader.ExtractKernelAssetsCalls, HasLen, 1)
+ c.Assert(bloader.ExtractKernelAssetsCalls[0].InstanceName(), Equals, "kernel")
minInfo := snap.MinimalPlaceInfo("kernel", snap.R(140))
// undo deletes the kernel assets again
err = s.be.UndoSetupSnap(minInfo, "kernel", progress.Null)
c.Assert(err, IsNil)
- c.Assert(loader.RemoveKernelAssetsCalls, HasLen, 1)
- c.Assert(loader.RemoveKernelAssetsCalls[0].InstanceName(), Equals, "kernel")
+ c.Assert(bloader.RemoveKernelAssetsCalls, HasLen, 1)
+ c.Assert(bloader.RemoveKernelAssetsCalls[0].InstanceName(), Equals, "kernel")
}
func (s *setupSuite) TestSetupDoIdempotent(c *C) {
@@ -188,8 +191,10 @@
// this cannot check systemd own behavior though around mounts!
- loader := boottest.NewMockBootloader("mock", c.MkDir())
- bootloader.Force(loader)
+ // kernel snaps only happen on non-classic
+ defer release.MockOnClassic(false)()
+ bloader := bootloadertest.Mock("mock", c.MkDir())
+ bootloader.Force(bloader)
// we don't get real mounting
os.Setenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS", "1")
defer os.Unsetenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS")
@@ -213,14 +218,14 @@
_, err := s.be.SetupSnap(snapPath, "kernel", &si, progress.Null)
c.Assert(err, IsNil)
- c.Assert(loader.ExtractKernelAssetsCalls, HasLen, 1)
- c.Assert(loader.ExtractKernelAssetsCalls[0].InstanceName(), Equals, "kernel")
+ c.Assert(bloader.ExtractKernelAssetsCalls, HasLen, 1)
+ c.Assert(bloader.ExtractKernelAssetsCalls[0].InstanceName(), Equals, "kernel")
// retry run
_, err = s.be.SetupSnap(snapPath, "kernel", &si, progress.Null)
c.Assert(err, IsNil)
- c.Assert(loader.ExtractKernelAssetsCalls, HasLen, 2)
- c.Assert(loader.ExtractKernelAssetsCalls[1].InstanceName(), Equals, "kernel")
+ c.Assert(bloader.ExtractKernelAssetsCalls, HasLen, 2)
+ c.Assert(bloader.ExtractKernelAssetsCalls[1].InstanceName(), Equals, "kernel")
minInfo := snap.MinimalPlaceInfo("kernel", snap.R(140))
// sanity checks
@@ -237,8 +242,10 @@
// this cannot check systemd own behavior though around mounts!
- loader := boottest.NewMockBootloader("mock", c.MkDir())
- bootloader.Force(loader)
+ // kernel snaps only happen on non-classic
+ defer release.MockOnClassic(false)()
+ bloader := bootloadertest.Mock("mock", c.MkDir())
+ bootloader.Force(bloader)
// we don't get real mounting
os.Setenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS", "1")
defer os.Unsetenv("SNAPPY_SQUASHFS_UNPACK_FOR_TESTS")
@@ -280,8 +287,8 @@
c.Assert(osutil.FileExists(minInfo.MountFile()), Equals, false)
// assets got extracted and then removed again
- c.Assert(loader.ExtractKernelAssetsCalls, HasLen, 1)
- c.Assert(loader.RemoveKernelAssetsCalls, HasLen, 1)
+ c.Assert(bloader.ExtractKernelAssetsCalls, HasLen, 1)
+ c.Assert(bloader.RemoveKernelAssetsCalls, HasLen, 1)
}
func (s *setupSuite) TestSetupCleanupAfterFail(c *C) {
diff -Nru snapd-2.41+19.10.1/overlord/snapstate/booted_test.go snapd-2.42.1+19.10/overlord/snapstate/booted_test.go
--- snapd-2.41+19.10.1/overlord/snapstate/booted_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/snapstate/booted_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -28,8 +28,8 @@
. "gopkg.in/check.v1"
- "github.com/snapcore/snapd/boot/boottest"
"github.com/snapcore/snapd/bootloader"
+ "github.com/snapcore/snapd/bootloader/bootloadertest"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/overlord"
"github.com/snapcore/snapd/overlord/snapstate"
@@ -43,7 +43,7 @@
type bootedSuite struct {
testutil.BaseTest
- bootloader *boottest.MockBootloader
+ bootloader *bootloadertest.MockBootloader
o *overlord.Overlord
state *state.State
@@ -66,9 +66,9 @@
// booted is not running on classic
release.MockOnClassic(false)
- bs.bootloader = boottest.NewMockBootloader("mock", c.MkDir())
- boottest.SetBootKernel("canonical-pc-linux_2.snap", bs.bootloader)
- boottest.SetBootBase("core_2.snap", bs.bootloader)
+ bs.bootloader = bootloadertest.Mock("mock", c.MkDir())
+ bs.bootloader.SetBootKernel("canonical-pc-linux_2.snap")
+ bs.bootloader.SetBootBase("core_2.snap")
bootloader.Force(bs.bootloader)
bs.fakeBackend = &fakeSnappyBackend{}
@@ -137,7 +137,7 @@
bs.makeInstalledKernelOS(c, st)
- boottest.SetBootBase("core_1.snap", bs.bootloader)
+ bs.bootloader.SetBootBase("core_1.snap")
err := snapstate.UpdateBootRevisions(st)
c.Assert(err, IsNil)
@@ -171,7 +171,7 @@
bs.makeInstalledKernelOS(c, st)
- boottest.SetBootKernel("canonical-pc-linux_1.snap", bs.bootloader)
+ bs.bootloader.SetBootKernel("canonical-pc-linux_1.snap")
err := snapstate.UpdateBootRevisions(st)
c.Assert(err, IsNil)
@@ -205,7 +205,7 @@
bs.makeInstalledKernelOS(c, st)
- boottest.SetBootKernel("canonical-pc-linux_99.snap", bs.bootloader)
+ bs.bootloader.SetBootKernel("canonical-pc-linux_99.snap")
err := snapstate.UpdateBootRevisions(st)
c.Assert(err, ErrorMatches, `cannot find revision 99 for snap "canonical-pc-linux"`)
}
@@ -217,7 +217,7 @@
bs.makeInstalledKernelOS(c, st)
- boottest.SetBootBase("core_99.snap", bs.bootloader)
+ bs.bootloader.SetBootBase("core_99.snap")
err := snapstate.UpdateBootRevisions(st)
c.Assert(err, ErrorMatches, `cannot find revision 99 for snap "core"`)
}
@@ -246,7 +246,7 @@
})
bs.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.SnapMountDir, "/core/1")
- boottest.SetBootBase("core_1.snap", bs.bootloader)
+ bs.bootloader.SetBootBase("core_1.snap")
err := snapstate.UpdateBootRevisions(st)
c.Assert(err, IsNil)
@@ -299,7 +299,7 @@
c.Check(err, IsNil)
// core snap, restarted, wrong core revision, rollback!
- boottest.SetBootBase("core_1.snap", bs.bootloader)
+ bs.bootloader.SetBootBase("core_1.snap")
err = snapstate.WaitRestart(task, snapsup)
c.Check(err, ErrorMatches, `cannot finish core installation, there was a rollback across reboot`)
}
@@ -346,12 +346,12 @@
// core snap, restarted, right core revision, no rollback
bs.bootloader.BootVars["snap_mode"] = ""
- boottest.SetBootBase("core18_2.snap", bs.bootloader)
+ bs.bootloader.SetBootBase("core18_2.snap")
err = snapstate.WaitRestart(task, snapsup)
c.Check(err, IsNil)
// core snap, restarted, wrong core revision, rollback!
- boottest.SetBootBase("core18_1.snap", bs.bootloader)
+ bs.bootloader.SetBootBase("core18_1.snap")
err = snapstate.WaitRestart(task, snapsup)
c.Check(err, ErrorMatches, `cannot finish core18 installation, there was a rollback across reboot`)
}
diff -Nru snapd-2.41+19.10.1/overlord/snapstate/check_snap.go snapd-2.42.1+19.10/overlord/snapstate/check_snap.go
--- snapd-2.41+19.10.1/overlord/snapstate/check_snap.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/snapstate/check_snap.go 2019-10-30 12:17:43.000000000 +0000
@@ -199,6 +199,7 @@
var versionExp = regexp.MustCompile(`^([1-9][0-9]*)(?:\.([0-9]+)(?:\.([0-9]+))?)?`)
func checkVersion(version string) bool {
+ // double check that the input looks like a snapd version
req := versionExp.FindStringSubmatch(version)
if req == nil || req[0] != version {
return false
@@ -208,6 +209,10 @@
return true // Development tree.
}
+ // We could (should?) use strutil.VersionCompare here and simplify
+ // this code (see PR#7344). However this would change current
+ // behavior, i.e. "2.41~pre1" would *not* match [snapd2.41] anymore
+ // (which the code below does).
cur := versionExp.FindStringSubmatch(cmd.Version)
if cur == nil {
return false
@@ -315,7 +320,7 @@
// verify we have a valid architecture
if !arch.IsSupportedArchitecture(info.Architectures) {
- return fmt.Errorf("snap %q supported architectures (%s) are incompatible with this system (%s)", info.InstanceName(), strings.Join(info.Architectures, ", "), arch.UbuntuArchitecture())
+ return fmt.Errorf("snap %q supported architectures (%s) are incompatible with this system (%s)", info.InstanceName(), strings.Join(info.Architectures, ", "), arch.DpkgArchitecture())
}
// check assumes
@@ -475,13 +480,24 @@
currentSnap, err := infoForDeviceSnap(st, deviceCtx, kind, whichName)
if err == state.ErrNoState {
- // TODO: remodeling logic
- return fmt.Errorf("internal error: cannot remodel kernel/gadget yet")
+ // check if we are in the remodel case
+ if deviceCtx != nil && deviceCtx.ForRemodeling() {
+ model := deviceCtx.Model()
+ if model.Kernel() == snapInfo.InstanceName() {
+ return nil
+ }
+ }
+
+ return fmt.Errorf("internal error: cannot remodel gadget yet")
}
if err != nil {
return fmt.Errorf("cannot find original %s snap: %v", kind, err)
}
+ if currentSnap.SnapID != "" && snapInfo.SnapID == "" {
+ return fmt.Errorf("cannot replace signed %s snap with an unasserted one", kind)
+ }
+
if currentSnap.SnapID != "" && snapInfo.SnapID != "" {
if currentSnap.SnapID == snapInfo.SnapID {
// same snap
@@ -490,10 +506,6 @@
return fmt.Errorf("cannot replace %s snap with a different one", kind)
}
- if currentSnap.SnapID != "" && snapInfo.SnapID == "" {
- return fmt.Errorf("cannot replace signed %s snap with an unasserted one", kind)
- }
-
if currentSnap.InstanceName() != snapInfo.InstanceName() {
return fmt.Errorf("cannot replace %s snap with a different one", kind)
}
diff -Nru snapd-2.41+19.10.1/overlord/snapstate/check_snap_test.go snapd-2.42.1+19.10/overlord/snapstate/check_snap_test.go
--- snapd-2.41+19.10.1/overlord/snapstate/check_snap_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/snapstate/check_snap_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -88,7 +88,7 @@
err = snapstate.CheckSnap(s.st, "snap-path", "hello", nil, nil, snapstate.Flags{}, nil)
- errorMsg := fmt.Sprintf(`snap "hello" supported architectures (yadayada, blahblah) are incompatible with this system (%s)`, arch.UbuntuArchitecture())
+ errorMsg := fmt.Sprintf(`snap "hello" supported architectures (yadayada, blahblah) are incompatible with this system (%s)`, arch.DpkgArchitecture())
c.Assert(err.Error(), Equals, errorMsg)
}
@@ -151,6 +151,11 @@
version: "2.15.0",
error: `.* unsupported features: snapd2\.15\.1 .*`,
}, {
+ // Note that this is different from how strconv.VersionCompare
+ // (dpkg version numbering) would behave - it would error here
+ assumes: "[snapd2.15]",
+ version: "2.15~pre1",
+}, {
assumes: "[command-chain]",
}}
diff -Nru snapd-2.41+19.10.1/overlord/snapstate/conflict.go snapd-2.42.1+19.10/overlord/snapstate/conflict.go
--- snapd-2.41+19.10.1/overlord/snapstate/conflict.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/snapstate/conflict.go 2019-10-30 12:17:43.000000000 +0000
@@ -109,6 +109,9 @@
if chg.Kind() == "transition-ubuntu-core" {
return &ChangeConflictError{Message: "ubuntu-core to core transition in progress, no other changes allowed until this is done", ChangeKind: "transition-ubuntu-core"}
}
+ if chg.Kind() == "transition-to-snapd-snap" {
+ return &ChangeConflictError{Message: "transition to snapd snap in progress, no other changes allowed until this is done", ChangeKind: "transition-to-snapd-snap"}
+ }
if chg.Kind() == "remodel" {
if ignoreChangeID != "" && chg.ID() == ignoreChangeID {
continue
diff -Nru snapd-2.41+19.10.1/overlord/snapstate/handlers.go snapd-2.42.1+19.10/overlord/snapstate/handlers.go
--- snapd-2.41+19.10.1/overlord/snapstate/handlers.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/snapstate/handlers.go 2019-10-30 12:17:43.000000000 +0000
@@ -771,11 +771,6 @@
}
}
- // Make a copy of configuration of given snap revision
- if err = config.SaveRevisionConfig(st, snapsup.InstanceName(), snapst.Current); err != nil {
- return err
- }
-
snapst.Active = false
pb := NewTaskProgressAdapterLocked(t)
@@ -978,6 +973,9 @@
return err
}
+ // find if the snap is already installed before we modify snapst below
+ isInstalled := snapst.IsInstalled()
+
cand := snapsup.SideInfo
m.backend.Candidate(cand)
@@ -1056,7 +1054,15 @@
return err
}
- // Restore configuration of the target revision (if available) on revert
+ if isInstalled {
+ // Make a copy of configuration of current snap revision
+ if err = config.SaveRevisionConfig(st, snapsup.InstanceName(), oldCurrent); err != nil {
+ return err
+ }
+ }
+
+ // Restore configuration of the target revision (if available; nothing happens if it's not).
+ // We only do this on reverts (and not on refreshes).
if snapsup.Revert {
if err = config.RestoreRevisionConfig(st, snapsup.InstanceName(), snapsup.Revision()); err != nil {
return err
@@ -1148,36 +1154,53 @@
func maybeRestart(t *state.Task, info *snap.Info) {
st := t.State()
- if release.OnClassic {
- // ignore error here as we have no way to return to caller
- snapdSnapInstalled, _ := isInstalled(st, "snapd")
- if (info.GetType() == snap.TypeOS && !snapdSnapInstalled) ||
- info.GetType() == snap.TypeSnapd {
- t.Logf("Requested daemon restart.")
- st.RequestRestart(state.RestartDaemon)
- }
+ model, err := ModelFromTask(t)
+ if err != nil {
+ logger.Noticef("cannot get model assertion: %v", model)
return
}
- // On a core system we may need a full reboot if
- // core/base or the kernel changes.
- if boot.ChangeRequiresReboot(info) {
+ typ := info.GetType()
+ bp := boot.Participant(info, typ, model, release.OnClassic)
+ if bp.ChangeRequiresReboot() {
t.Logf("Requested system restart.")
st.RequestRestart(state.RestartSystem)
return
}
- // On core systems that use a base snap we need to restart
- // snapd when the snapd snap changes.
- model, err := ModelFromTask(t)
- if err != nil {
- logger.Noticef("cannot get model assertion: %v", model)
+ // if bp is non-trivial then either we're not on classic, or the snap is
+ // snapd. So daemonRestartReason will always return "" which is what we
+ // want. If that combination stops being true and there's a situation
+ // where a non-trivial bp could return a non-empty reason, use IsTrivial
+ // to check and bail before reaching this far.
+
+ restartReason := daemonRestartReason(st, typ)
+ if restartReason == "" {
+ // no message -> no restart
return
}
- if model.Base() != "" && info.GetType() == snap.TypeSnapd {
- t.Logf("Requested daemon restart (snapd snap).")
- st.RequestRestart(state.RestartDaemon)
+
+ t.Logf(restartReason)
+ st.RequestRestart(state.RestartDaemon)
+}
+
+func daemonRestartReason(st *state.State, typ snap.Type) string {
+ if !((release.OnClassic && typ == snap.TypeOS) || typ == snap.TypeSnapd) {
+ // not interesting
+ return ""
}
+
+ if typ == snap.TypeOS {
+ // ignore error here as we have no way to return to caller
+ snapdSnapInstalled, _ := isInstalled(st, "snapd")
+ if snapdSnapInstalled {
+ // this snap is the base, but snapd is running from the snapd snap
+ return ""
+ }
+ return "Requested daemon restart."
+ }
+
+ return "Requested daemon restart (snapd snap)."
}
func (m *SnapManager) undoLinkSnap(t *state.Task, _ *tomb.Tomb) error {
@@ -1290,6 +1313,8 @@
return err
}
+ // we need to undo potential changes to current snap configuration (e.g. if modified by post-refresh/install/configure hooks
+ // as part of failed refresh/install) by restoring the configuration of "old current".
if len(snapst.Sequence) > 0 {
if err = config.RestoreRevisionConfig(st, snapsup.InstanceName(), oldCurrent); err != nil {
return err
diff -Nru snapd-2.41+19.10.1/overlord/snapstate/handlers_link_test.go snapd-2.42.1+19.10/overlord/snapstate/handlers_link_test.go
--- snapd-2.41+19.10.1/overlord/snapstate/handlers_link_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/snapstate/handlers_link_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -387,7 +387,7 @@
c.Assert(err, Equals, state.ErrNoState)
// tried to cleanup
- c.Check(s.fakeBackend.ops, DeepEquals, fakeOps{
+ expected := fakeOps{
{
op: "candidate",
sinfo: *si,
@@ -400,7 +400,11 @@
op: "unlink-snap",
path: filepath.Join(dirs.SnapMountDir, "foo/35"),
},
- })
+ }
+
+ // start with an easier-to-read error if this fails:
+ c.Check(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
+ c.Check(s.fakeBackend.ops, DeepEquals, expected)
}
func (s *linkSnapSuite) TestDoLinkSnapSuccessCoreRestarts(c *C) {
diff -Nru snapd-2.41+19.10.1/overlord/snapstate/progress.go snapd-2.42.1+19.10/overlord/snapstate/progress.go
--- snapd-2.41+19.10.1/overlord/snapstate/progress.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/snapstate/progress.go 2019-10-30 12:17:43.000000000 +0000
@@ -20,6 +20,8 @@
package snapstate
import (
+ "math"
+
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/progress"
)
@@ -32,6 +34,8 @@
label string
total float64
current float64
+
+ lastReported float64
}
// NewTaskProgressAdapterUnlocked creates an adapter of the task into a progress.Meter to use while the state is unlocked
@@ -48,10 +52,23 @@
func (t *taskProgressAdapter) Start(label string, total float64) {
t.label = label
t.total = total
+ t.Set(0.0)
}
// Set sets the current progress
func (t *taskProgressAdapter) Set(current float64) {
+ t.current = current
+
+ // check if we made at least "minProgress" before we lock the state
+ // (using Abs to ensure that even if lastReported is smaller than
+ // current we still report progress)
+ const minProgress = 0.2 / 100.0
+ if current != 0.0 && math.Abs(t.current-t.lastReported)/t.total < minProgress {
+ return
+ }
+
+ t.lastReported = t.current
+ // set progress in task
if t.unlocked {
t.task.State().Lock()
defer t.task.State().Unlock()
@@ -75,13 +92,7 @@
// Write sets the current write progress
func (t *taskProgressAdapter) Write(p []byte) (n int, err error) {
- if t.unlocked {
- t.task.State().Lock()
- defer t.task.State().Unlock()
- }
-
- t.current += float64(len(p))
- t.task.SetProgress(t.label, int(t.current), int(t.total))
+ t.Set(t.current + float64(len(p)))
return len(p), nil
}
diff -Nru snapd-2.41+19.10.1/overlord/snapstate/progress_test.go snapd-2.42.1+19.10/overlord/snapstate/progress_test.go
--- snapd-2.41+19.10.1/overlord/snapstate/progress_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/snapstate/progress_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -56,3 +56,35 @@
m.Write([]byte("some-bytes"))
c.Check(p.current, Equals, float64(len("some-bytes")))
}
+
+func (s *progressAdapterTestSuite) TestProgressAdapterSetTaskProgress(c *C) {
+ st := state.New(nil)
+
+ st.Lock()
+ t := st.NewTask("op", "msg")
+ m := NewTaskProgressAdapterUnlocked(t)
+ st.Unlock()
+
+ // we expect 1000 bytes
+ m.Start("msg", 1000)
+
+ // write a single byte (0.1% of the toal)
+ m.Write([]byte("1"))
+
+ // check that the progress is not updated yet
+ st.Lock()
+ msg, done, total := t.Progress()
+ st.Unlock()
+ c.Check(msg, Equals, "msg")
+ c.Check(done, Equals, 0)
+ c.Check(total, Equals, 1000)
+
+ // write another byte (0.2% now)
+ m.Write([]byte("2"))
+ // now the progress in the task gets updated (we update every 0.2%)
+ st.Lock()
+ _, done, total = t.Progress()
+ st.Unlock()
+ c.Check(done, Equals, 2)
+ c.Check(total, Equals, 1000)
+}
diff -Nru snapd-2.41+19.10.1/overlord/snapstate/snapmgr.go snapd-2.42.1+19.10/overlord/snapstate/snapmgr.go
--- snapd-2.41+19.10.1/overlord/snapstate/snapmgr.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/snapstate/snapmgr.go 2019-10-30 12:17:43.000000000 +0000
@@ -20,9 +20,11 @@
package snapstate
import (
+ "context"
"errors"
"fmt"
"io"
+ "math/rand"
"os"
"strings"
"time"
@@ -42,6 +44,10 @@
"github.com/snapcore/snapd/strutil"
)
+var (
+ snapdTransitionDelayWithRandomess = 3*time.Hour + time.Duration(rand.Int63n(int64(4*time.Hour)))
+)
+
// overridden in the tests
var errtrackerReport = errtracker.Report
@@ -540,6 +546,110 @@
return nil
}
+// changeInFlight returns true if there is any change in the state
+// in non-ready state.
+func changeInFlight(st *state.State) bool {
+ for _, chg := range st.Changes() {
+ if !chg.IsReady() {
+ // another change already in motion
+ return true
+ }
+ }
+ return false
+}
+
+// ensureSnapdSnapTransition will migrate systems to use the "snapd" snap
+func (m *SnapManager) ensureSnapdSnapTransition() error {
+ m.state.Lock()
+ defer m.state.Unlock()
+
+ // we only auto-transition people on classic systems, for core we
+ // will need to do a proper re-model
+ if !release.OnClassic {
+ return nil
+ }
+
+ // check if snapd snap is installed
+ var snapst SnapState
+ err := Get(m.state, "snapd", &snapst)
+ if err != nil && err != state.ErrNoState {
+ return err
+ }
+ // nothing to do
+ if snapst.IsInstalled() {
+ return nil
+ }
+
+ // check if the user opts into the snapd snap
+ optedIntoSnapdTransition, err := optedIntoSnapdSnap(m.state)
+ if err != nil {
+ return err
+ }
+ // nothing to do: the user does not want the snapd snap yet
+ if !optedIntoSnapdTransition {
+ return nil
+ }
+
+ // ensure we only transition systems that have snaps already
+ installedSnaps, err := NumSnaps(m.state)
+ if err != nil {
+ return err
+ }
+ // no installed snaps (yet): do nothing (fresh classic install)
+ if installedSnaps == 0 {
+ return nil
+ }
+
+ // get current core snap and use same channel/user for the snapd snap
+ err = Get(m.state, "core", &snapst)
+ // Note that state.ErrNoState should never happen in practise. However
+ // if it *does* happen we still want to fix those systems by installing
+ // the snapd snap.
+ if err != nil && err != state.ErrNoState {
+ return err
+ }
+ coreChannel := snapst.Channel
+ // snapd/core are never blocked on auth so we don't need to copy
+ // the userID from the snapst here
+ userID := 0
+
+ if changeInFlight(m.state) {
+ // check that there is no change in flight already, this is a
+ // precaution to ensure the snapd transition is safe
+ return nil
+ }
+
+ // ensure we limit the retries in case something goes wrong
+ var lastSnapdTransitionAttempt time.Time
+ err = m.state.Get("snapd-transition-last-retry-time", &lastSnapdTransitionAttempt)
+ if err != nil && err != state.ErrNoState {
+ return err
+ }
+ now := time.Now()
+ if !lastSnapdTransitionAttempt.IsZero() && lastSnapdTransitionAttempt.Add(snapdTransitionDelayWithRandomess).After(now) {
+ return nil
+ }
+ m.state.Set("snapd-transition-last-retry-time", now)
+
+ var retryCount int
+ err = m.state.Get("snapd-transition-retry", &retryCount)
+ if err != nil && err != state.ErrNoState {
+ return err
+ }
+ m.state.Set("snapd-transition-retry", retryCount+1)
+
+ ts, err := Install(context.Background(), m.state, "snapd", &RevisionOptions{Channel: coreChannel}, userID, Flags{})
+ if err != nil {
+ return err
+ }
+
+ msg := i18n.G("Transition to the snapd snap")
+ chg := m.state.NewChange("transition-to-snapd-snap", msg)
+ chg.AddAll(ts)
+
+ return nil
+}
+
// ensureUbuntuCoreTransition will migrate systems that use "ubuntu-core"
// to the new "core" snap
func (m *SnapManager) ensureUbuntuCoreTransition() error {
@@ -557,11 +667,9 @@
// check that there is no change in flight already, this is a
// precaution to ensure the core transition is safe
- for _, chg := range m.state.Changes() {
- if !chg.Status().Ready() {
- // another change already in motion
- return nil
- }
+ if changeInFlight(m.state) {
+ // another change already in motion
+ return nil
}
// ensure we limit the retries in case something goes wrong
@@ -686,6 +794,7 @@
m.ensureAliasesV2(),
m.ensureForceDevmodeDropsDevmodeFromState(),
m.ensureUbuntuCoreTransition(),
+ m.ensureSnapdSnapTransition(),
// we should check for full regular refreshes before
// considering issuing a hint only refresh request
m.autoRefresh.Ensure(),
diff -Nru snapd-2.41+19.10.1/overlord/snapstate/snapstate.go snapd-2.42.1+19.10/overlord/snapstate/snapstate.go
--- snapd-2.41+19.10.1/overlord/snapstate/snapstate.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/snapstate/snapstate.go 2019-10-30 12:17:43.000000000 +0000
@@ -45,6 +45,7 @@
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/channel"
"github.com/snapcore/snapd/store"
"github.com/snapcore/snapd/strutil"
)
@@ -77,6 +78,15 @@
return fmt.Errorf("cannot install snap of type %v as %q", snapsup.Type, snapsup.InstanceName())
}
+func optedIntoSnapdSnap(st *state.State) (bool, error) {
+ tr := config.NewTransaction(st)
+ experimentalAllowSnapd, err := config.GetFeatureFlag(tr, features.SnapdSnap)
+ if err != nil && !config.IsNoOption(err) {
+ return false, err
+ }
+ return experimentalAllowSnapd, nil
+}
+
func doInstall(st *state.State, snapst *SnapState, snapsup *SnapSetup, flags int, fromChange string) (*state.TaskSet, error) {
// NB: we should strive not to need or propagate deviceCtx
// here, the resulting effects/changes were not pleasant at
@@ -90,7 +100,6 @@
if snapsup.InstanceName() == "system" {
return nil, fmt.Errorf("cannot install reserved snap name 'system'")
}
-
if snapst.IsInstalled() && !snapst.Active {
return nil, fmt.Errorf("cannot update disabled snap %q", snapsup.InstanceName())
}
@@ -437,7 +446,7 @@
}
current, err := boot.GetCurrentBoot(typ)
- if err == boot.ErrBootNameAndRevisionAgain {
+ if err == boot.ErrBootNameAndRevisionNotReady {
return &state.Retry{After: 5 * time.Second}
}
if err != nil {
@@ -538,16 +547,8 @@
// Check if the snapd can be installed on Ubuntu Core systems, it is
// always ok to install on classic
if info.GetType() == snap.TypeSnapd && !release.OnClassic {
- tr := config.NewTransaction(st)
- experimentalAllowSnapd, err := config.GetFeatureFlag(tr, features.SnapdSnap)
- if err != nil && !config.IsNoOption(err) {
- return err
- }
-
if deviceCtx.Model().Base() == "" {
- if !experimentalAllowSnapd {
- return fmt.Errorf("cannot install snapd snap on a model without a base snap yet")
- }
+ return fmt.Errorf("cannot install snapd snap on a model without a base snap yet")
}
}
@@ -1205,24 +1206,19 @@
return newChannel, nil
}
- nch, err := snap.ParseChannelVerbatim(newChannel, "")
- if err != nil {
- return "", err
- }
-
- if nch.Track == "" {
- // channel name is valid and consist of risk level or
- // risk/branch only, do the right thing and default to risk (or
- // risk/branch) within the pinned track
- return pinnedTrack + "/" + newChannel, nil
- }
- if nch.Track != "" && nch.Track != pinnedTrack {
+ // channel name is valid and consist of risk level or
+ // risk/branch only, do the right thing and default to risk (or
+ // risk/branch) within the pinned track
+ resChannel, err := channel.ResolveLocked(pinnedTrack, newChannel)
+ if err == channel.ErrLockedTrackSwitch {
// switching to a different track is not allowed
- return "", fmt.Errorf("cannot switch from %s track %q as specified for the (device) model to %q", which, pinnedTrack, nch.Clean().String())
+ return "", fmt.Errorf("cannot switch from %s track %q as specified for the (device) model to %q", which, pinnedTrack, newChannel)
}
-
- return newChannel, nil
+ if err != nil {
+ return "", err
+ }
+ return resChannel, nil
}
var errRevisionSwitch = errors.New("cannot switch revision")
diff -Nru snapd-2.41+19.10.1/overlord/snapstate/snapstate_test.go snapd-2.42.1+19.10/overlord/snapstate/snapstate_test.go
--- snapd-2.41+19.10.1/overlord/snapstate/snapstate_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/overlord/snapstate/snapstate_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -37,8 +37,8 @@
"gopkg.in/tomb.v2"
"github.com/snapcore/snapd/asserts"
- "github.com/snapcore/snapd/boot/boottest"
"github.com/snapcore/snapd/bootloader"
+ "github.com/snapcore/snapd/bootloader/bootloadertest"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/gadget"
"github.com/snapcore/snapd/interfaces"
@@ -234,6 +234,7 @@
runner.AddHandler("discard-conns", fakeHandler, fakeHandler)
runner.AddHandler("validate-snap", fakeHandler, nil)
runner.AddHandler("transition-ubuntu-core", fakeHandler, nil)
+ runner.AddHandler("transition-to-snapd-snap", fakeHandler, nil)
// Add handler to test full aborting of changes
erroringHandler := func(task *state.Task, _ *tomb.Tomb) error {
@@ -1673,7 +1674,7 @@
})
_, err := snapstate.Switch(s.state, "kernel", &snapstate.RevisionOptions{Channel: "new-channel"})
- c.Assert(err, ErrorMatches, `cannot switch from kernel track "18" as specified for the \(device\) model to "new-channel/stable"`)
+ c.Assert(err, ErrorMatches, `cannot switch from kernel track "18" as specified for the \(device\) model to "new-channel"`)
}
func (s *snapmgrTestSuite) TestSwitchKernelTrackRiskOnlyIsOK(c *C) {
@@ -1730,7 +1731,7 @@
})
_, err := snapstate.Switch(s.state, "brand-gadget", &snapstate.RevisionOptions{Channel: "new-channel"})
- c.Assert(err, ErrorMatches, `cannot switch from gadget track "18" as specified for the \(device\) model to "new-channel/stable"`)
+ c.Assert(err, ErrorMatches, `cannot switch from gadget track "18" as specified for the \(device\) model to "new-channel"`)
}
func (s *snapmgrTestSuite) TestSwitchGadgetTrackRiskOnlyIsOK(c *C) {
@@ -6597,7 +6598,7 @@
// switching tracks is not ok
_, err := snapstate.Update(s.state, "kernel", &snapstate.RevisionOptions{Channel: "new-channel"}, s.user.ID, snapstate.Flags{})
- c.Assert(err, ErrorMatches, `cannot switch from kernel track "18" as specified for the \(device\) model to "new-channel/stable"`)
+ c.Assert(err, ErrorMatches, `cannot switch from kernel track "18" as specified for the \(device\) model to "new-channel"`)
// no change to the channel is ok
_, err = snapstate.Update(s.state, "kernel", nil, s.user.ID, snapstate.Flags{})
@@ -6633,7 +6634,7 @@
// switching tracks is not ok
_, err := snapstate.Update(s.state, "brand-gadget", &snapstate.RevisionOptions{Channel: "new-channel"}, s.user.ID, snapstate.Flags{})
- c.Assert(err, ErrorMatches, `cannot switch from gadget track "18" as specified for the \(device\) model to "new-channel/stable"`)
+ c.Assert(err, ErrorMatches, `cannot switch from gadget track "18" as specified for the \(device\) model to "new-channel"`)
// no change to the channel is ok
_, err = snapstate.Update(s.state, "brand-gadget", nil, s.user.ID, snapstate.Flags{})
@@ -6725,6 +6726,7 @@
},
}
+ // start with an easier-to-read error if this fails:
c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
c.Check(s.fakeBackend.ops, DeepEquals, expected)
@@ -6788,41 +6790,64 @@
s.settle(c)
s.state.Lock()
- ops := s.fakeBackend.ops
- // ensure only local install was run, i.e. first action is pseudo-action current
- c.Assert(ops.Ops(), HasLen, 11)
- c.Check(ops[0].op, Equals, "current")
- c.Check(ops[0].old, Equals, filepath.Join(dirs.SnapMountDir, "mock/x2"))
- // and setup-snap
- c.Check(ops[1].op, Equals, "setup-snap")
- c.Check(ops[1].name, Matches, "mock")
- c.Check(ops[1].path, Matches, `.*/mock_1.0_all.snap`)
- c.Check(ops[1].revno, Equals, snap.R("x3"))
- // and cleanup
- c.Check(ops[len(ops)-1], DeepEquals, fakeOp{
- op: "cleanup-trash",
- name: "mock",
- revno: snap.R("x3"),
- })
-
- c.Check(ops[3].op, Equals, "unlink-snap")
- c.Check(ops[3].path, Equals, filepath.Join(dirs.SnapMountDir, "mock/x2"))
-
- c.Check(ops[4].op, Equals, "copy-data")
- c.Check(ops[4].path, Equals, filepath.Join(dirs.SnapMountDir, "mock/x3"))
- c.Check(ops[4].old, Equals, filepath.Join(dirs.SnapMountDir, "mock/x2"))
-
- c.Check(ops[5].op, Equals, "setup-profiles:Doing")
- c.Check(ops[5].name, Equals, "mock")
- c.Check(ops[5].revno, Equals, snap.R(-3))
+ expected := fakeOps{
+ {
+ op: "current",
+ old: filepath.Join(dirs.SnapMountDir, "mock/x2"),
+ },
+ {
+ op: "setup-snap",
+ name: "mock",
+ path: mockSnap,
+ revno: snap.R("x3"),
+ },
+ {
+ op: "remove-snap-aliases",
+ name: "mock",
+ },
+ {
+ op: "unlink-snap",
+ path: filepath.Join(dirs.SnapMountDir, "mock/x2"),
+ },
+ {
+ op: "copy-data",
+ path: filepath.Join(dirs.SnapMountDir, "mock/x3"),
+ old: filepath.Join(dirs.SnapMountDir, "mock/x2"),
+ },
+ {
+ op: "setup-profiles:Doing",
+ name: "mock",
+ revno: snap.R(-3),
+ },
+ {
+ op: "candidate",
+ sinfo: snap.SideInfo{
+ RealName: "mock",
+ Revision: snap.R(-3),
+ },
+ },
+ {
+ op: "link-snap",
+ path: filepath.Join(dirs.SnapMountDir, "mock/x3"),
+ },
+ {
+ op: "auto-connect:Doing",
+ name: "mock",
+ revno: snap.R("x3"),
+ },
+ {
+ op: "update-aliases",
+ },
+ {
+ op: "cleanup-trash",
+ name: "mock",
+ revno: snap.R("x3"),
+ },
+ }
- c.Check(ops[6].op, Equals, "candidate")
- c.Check(ops[6].sinfo, DeepEquals, snap.SideInfo{
- RealName: "mock",
- Revision: snap.R(-3),
- })
- c.Check(ops[7].op, Equals, "link-snap")
- c.Check(ops[7].path, Equals, filepath.Join(dirs.SnapMountDir, "mock/x3"))
+ // start with an easier-to-read error if this fails:
+ c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
+ c.Check(s.fakeBackend.ops, DeepEquals, expected)
// verify snapSetup info
var snapsup snapstate.SnapSetup
@@ -6942,6 +6967,7 @@
revno: snap.R("x1"),
},
}
+ // start with an easier-to-read error if this fails:
c.Assert(s.fakeBackend.ops.Ops(), DeepEquals, expected.Ops())
c.Check(s.fakeBackend.ops, DeepEquals, expected)
@@ -8051,6 +8077,61 @@
c.Assert(res, Equals, "100")
}
+func (s *snapmgrTestSuite) TestRefreshDoesntRestoreRevisionConfig(c *C) {
+ restore := release.MockOnClassic(false)
+ defer restore()
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{
+ {RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)},
+ },
+ Current: snap.R(1),
+ SnapType: "app",
+ })
+
+ // set global configuration (affecting current snap)
+ tr := config.NewTransaction(s.state)
+ tr.Set("some-snap", "foo", "100")
+ tr.Commit()
+
+ // set per-revision config for the upcoming rev. 2, we don't expect it restored though
+ // since only revert restores revision configs.
+ s.state.Set("revision-config", map[string]interface{}{
+ "some-snap": map[string]interface{}{
+ "2": map[string]interface{}{"foo": "200"},
+ },
+ })
+
+ // simulate a refresh to rev. 2
+ chg := s.state.NewChange("update", "update some-snap")
+ ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Channel: "some-channel", Revision: snap.R(2)}, s.user.ID, snapstate.Flags{})
+ c.Assert(err, IsNil)
+ chg.AddAll(ts)
+
+ s.state.Unlock()
+ defer s.se.Stop()
+ s.settle(c)
+
+ s.state.Lock()
+ // config of rev. 1 has been stored in per-revision map
+ var cfgs map[string]interface{}
+ c.Assert(s.state.Get("revision-config", &cfgs), IsNil)
+ c.Assert(cfgs["some-snap"], DeepEquals, map[string]interface{}{
+ "1": map[string]interface{}{"foo": "100"},
+ "2": map[string]interface{}{"foo": "200"},
+ })
+
+ // config of rev. 2 hasn't been restored by refresh, old value returned
+ tr = config.NewTransaction(s.state)
+ var res string
+ c.Assert(tr.Get("some-snap", "foo", &res), IsNil)
+ c.Assert(res, Equals, "100")
+}
+
func (s *snapmgrTestSuite) TestUpdateDoesGC(c *C) {
s.state.Lock()
defer s.state.Unlock()
@@ -10952,7 +11033,7 @@
st *state.State
deviceCtx snapstate.DeviceContext
- bs bootloader.Bootloader
+ bootloader *bootloadertest.MockBootloader
}
var _ = Suite(&canRemoveSuite{})
@@ -10962,8 +11043,8 @@
s.st = state.New(nil)
s.deviceCtx = &snapstatetest.TrivialDeviceContext{DeviceModel: DefaultModel()}
- s.bs = boottest.NewMockBootloader("mock", c.MkDir())
- bootloader.Force(s.bs)
+ s.bootloader = bootloadertest.Mock("mock", c.MkDir())
+ bootloader.Force(s.bootloader)
}
func (s *canRemoveSuite) TearDownTest(c *C) {
@@ -11015,7 +11096,7 @@
}
kernel.RealName = "kernel"
- boottest.SetBootKernel(fmt.Sprintf("%s_%s.snap", kernel.RealName, kernel.SideInfo.Revision), s.bs)
+ s.bootloader.SetBootKernel(fmt.Sprintf("%s_%s.snap", kernel.RealName, kernel.SideInfo.Revision))
removeAll := false
c.Check(snapstate.CanRemove(s.st, kernel, &snapstate.SnapState{}, removeAll, s.deviceCtx), Equals, false)
@@ -11030,7 +11111,7 @@
}
base.RealName = "core18"
- boottest.SetBootBase(fmt.Sprintf("%s_%s.snap", base.RealName, base.SideInfo.Revision), s.bs)
+ s.bootloader.SetBootBase(fmt.Sprintf("%s_%s.snap", base.RealName, base.SideInfo.Revision))
removeAll := false
c.Check(snapstate.CanRemove(s.st, base, &snapstate.SnapState{}, removeAll, s.deviceCtx), Equals, false)
@@ -11054,7 +11135,7 @@
}
kernel.RealName = "other-non-model-kernel"
- boottest.SetBootKernel(fmt.Sprintf("%s_%s.snap", kernel.RealName, kernel.SideInfo.Revision), s.bs)
+ s.bootloader.SetBootKernel(fmt.Sprintf("%s_%s.snap", kernel.RealName, kernel.SideInfo.Revision))
c.Check(snapstate.CanRemove(s.st, kernel, &snapstate.SnapState{}, true, s.deviceCtx), Equals, false)
}
@@ -12596,6 +12677,233 @@
c.Check(ts, NotNil)
}
+func (s *snapmgrTestSuite) TestTransitionSnapdSnapDoesNotRunWithoutSnaps(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ tr := config.NewTransaction(s.state)
+ tr.Set("core", "experimental.snapd-snap", true)
+ tr.Commit()
+
+ // no snaps installed on this system (e.g. fresh classic)
+ snapstate.Set(s.state, "core", nil)
+
+ s.state.Unlock()
+ defer s.se.Stop()
+ s.settle(c)
+ s.state.Lock()
+
+ c.Check(s.state.Changes(), HasLen, 0)
+}
+
+func (s *snapmgrTestSuite) TestTransitionSnapdSnapDoesRunWithAnySnap(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ tr := config.NewTransaction(s.state)
+ tr.Set("core", "experimental.snapd-snap", true)
+ tr.Commit()
+
+ // some snap installed on this system but no core
+ snapstate.Set(s.state, "core", nil)
+ snapstate.Set(s.state, "foo", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{{RealName: "foo", SnapID: "foo-id", Revision: snap.R(1), Channel: "beta"}},
+ Current: snap.R(1),
+ })
+
+ s.state.Unlock()
+ defer s.se.Stop()
+ s.settle(c)
+ s.state.Lock()
+
+ c.Check(s.state.Changes(), HasLen, 1)
+}
+
+func (s *snapmgrTestSuite) TestTransitionSnapdSnapDoesNotRunWhenNotEnabled(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.Set(s.state, "core", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{{RealName: "corecore", SnapID: "core-snap-id", Revision: snap.R(1), Channel: "beta"}},
+ Current: snap.R(1),
+ SnapType: "os",
+ })
+
+ s.state.Unlock()
+ defer s.se.Stop()
+ s.settle(c)
+ s.state.Lock()
+
+ c.Check(s.state.Changes(), HasLen, 0)
+}
+
+func (s *snapmgrTestSuite) TestTransitionSnapdSnapStartsAutomaticallyWhenEnabled(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.Set(s.state, "core", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{{RealName: "corecore", SnapID: "core-snap-id", Revision: snap.R(1), Channel: "beta"}},
+ Current: snap.R(1),
+ SnapType: "os",
+ })
+ tr := config.NewTransaction(s.state)
+ tr.Set("core", "experimental.snapd-snap", true)
+ tr.Commit()
+
+ s.state.Unlock()
+ defer s.se.Stop()
+ s.settle(c)
+ s.state.Lock()
+
+ c.Check(s.state.Changes(), HasLen, 1)
+ chg := s.state.Changes()[0]
+ c.Check(chg.Kind(), Equals, "transition-to-snapd-snap")
+ c.Assert(chg.Err(), IsNil)
+ c.Assert(chg.IsReady(), Equals, true)
+
+ // snapd snap is instaleld from the default channel
+ var snapst snapstate.SnapState
+ snapstate.Get(s.state, "snapd", &snapst)
+ c.Assert(snapst.Channel, Equals, "stable")
+}
+
+func (s *snapmgrTestSuite) TestTransitionSnapdSnapWithCoreRunthrough(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.Set(s.state, "core", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{{RealName: "corecore", SnapID: "core-snap-id", Revision: snap.R(1), Channel: "edge"}},
+ Current: snap.R(1),
+ SnapType: "os",
+ // TrackingChannel
+ Channel: "beta",
+ })
+ tr := config.NewTransaction(s.state)
+ tr.Set("core", "experimental.snapd-snap", true)
+ tr.Commit()
+
+ s.state.Unlock()
+ defer s.se.Stop()
+ s.settle(c)
+ s.state.Lock()
+
+ c.Assert(s.state.Changes(), HasLen, 1)
+ chg := s.state.Changes()[0]
+ c.Assert(chg.Kind(), Equals, "transition-to-snapd-snap")
+ c.Assert(chg.Err(), IsNil)
+ c.Assert(chg.IsReady(), Equals, true)
+ c.Check(s.fakeStore.downloads, HasLen, 1)
+ ts := state.NewTaskSet(chg.Tasks()...)
+ verifyInstallTasks(c, noConfigure, 0, ts, s.state)
+
+ // ensure preferences from the core snap got transferred over
+ var snapst snapstate.SnapState
+ snapstate.Get(s.state, "snapd", &snapst)
+ c.Assert(snapst.Channel, Equals, "beta")
+}
+
+func (s *snapmgrTestSuite) TestTransitionSnapdSnapTimeLimitWorks(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ tr := config.NewTransaction(s.state)
+ tr.Set("core", "experimental.snapd-snap", true)
+ tr.Commit()
+
+ // tried 3h ago, no retry
+ s.state.Set("snapd-transition-last-retry-time", time.Now().Add(-3*time.Hour))
+
+ s.state.Unlock()
+ defer s.se.Stop()
+ s.settle(c)
+ s.state.Lock()
+
+ c.Check(s.state.Changes(), HasLen, 0)
+
+ // tried 7h ago, retry
+ s.state.Set("snapd-transition-last-retry-time", time.Now().Add(-7*time.Hour))
+
+ s.state.Unlock()
+ defer s.se.Stop()
+ s.settle(c)
+ s.state.Lock()
+ c.Check(s.state.Changes(), HasLen, 1)
+
+ var t time.Time
+ s.state.Get("snapd-transition-last-retry-time", &t)
+ c.Assert(time.Now().Sub(t) < 2*time.Minute, Equals, true)
+}
+
+type unhappyStore struct {
+ *fakeStore
+}
+
+func (s unhappyStore) SnapAction(ctx context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, user *auth.UserState, opts *store.RefreshOptions) ([]*snap.Info, error) {
+
+ return nil, fmt.Errorf("a grumpy store")
+}
+
+func (s *snapmgrTestSuite) TestTransitionSnapdSnapError(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ snapstate.ReplaceStore(s.state, unhappyStore{fakeStore: s.fakeStore})
+
+ tr := config.NewTransaction(s.state)
+ tr.Set("core", "experimental.snapd-snap", true)
+ tr.Commit()
+
+ s.state.Unlock()
+ defer s.se.Stop()
+ err := s.o.Settle(5 * time.Second)
+ c.Assert(err, ErrorMatches, `state ensure errors: \[a grumpy store\]`)
+
+ s.state.Lock()
+ c.Check(s.state.Changes(), HasLen, 0)
+
+ // all the attempts were recorded
+ var t time.Time
+ s.state.Get("snapd-transition-last-retry-time", &t)
+ c.Assert(time.Now().Sub(t) < 2*time.Minute, Equals, true)
+
+ var cnt int
+ s.state.Get("snapd-transition-retry", &cnt)
+ c.Assert(cnt, Equals, 1)
+
+ // the transition is not tried again (because of retry time)
+ s.state.Unlock()
+ err = s.o.Settle(5 * time.Second)
+ c.Assert(err, IsNil)
+ s.state.Lock()
+
+ s.state.Get("snapd-transition-retry", &cnt)
+ c.Assert(cnt, Equals, 1)
+}
+
+func (s *snapmgrTestSuite) TestTransitionSnapdSnapBlocksOtherChanges(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ // if we have a snapd transition
+ chg := s.state.NewChange("transition-to-snapd-snap", "...")
+ chg.SetStatus(state.DoStatus)
+
+ // other tasks block until the transition is done
+ _, err := snapstate.Install(context.Background(), s.state, "some-snap", &snapstate.RevisionOptions{Channel: "stable"}, s.user.ID, snapstate.Flags{})
+ c.Check(err, FitsTypeOf, &snapstate.ChangeConflictError{})
+ c.Check(err, ErrorMatches, "transition to snapd snap in progress, no other changes allowed until this is done")
+
+ // and when the transition is done, other tasks run
+ chg.SetStatus(state.DoneStatus)
+ ts, err := snapstate.Install(context.Background(), s.state, "some-snap", &snapstate.RevisionOptions{Channel: "stable"}, s.user.ID, snapstate.Flags{})
+ c.Check(err, IsNil)
+ c.Check(ts, NotNil)
+}
+
func (s *snapmgrTestSuite) TestForceDevModeCleanupRunsForUbuntuCore(c *C) {
s.checkForceDevModeCleanupRuns(c, "ubuntu-core", true)
}
@@ -13874,7 +14182,7 @@
Channel: "some-channel",
}
_, _, err := snapstate.InstallPath(s.state, si, someSnap, "", "some-channel", snapstate.Flags{Required: true})
- c.Assert(err, ErrorMatches, `cannot switch from kernel track "18" as specified for the \(device\) model to "some-channel/stable"`)
+ c.Assert(err, ErrorMatches, `cannot switch from kernel track "18" as specified for the \(device\) model to "some-channel"`)
}
func (s *snapmgrTestSuite) TestInstallPathWithMetadataChannelSwitchGadget(c *C) {
@@ -13905,7 +14213,7 @@
Channel: "some-channel",
}
_, _, err := snapstate.InstallPath(s.state, si, someSnap, "", "some-channel", snapstate.Flags{Required: true})
- c.Assert(err, ErrorMatches, `cannot switch from gadget track "18" as specified for the \(device\) model to "some-channel/stable"`)
+ c.Assert(err, ErrorMatches, `cannot switch from gadget track "18" as specified for the \(device\) model to "some-channel"`)
}
func (s *snapmgrTestSuite) TestInstallLayoutsChecksFeatureFlag(c *C) {
@@ -14234,12 +14542,27 @@
c.Check(hasConfigureTask(ts), Equals, false)
}
-func (s *snapmgrTestSuite) TestNoSnapdSnapOnSystemsWithoutBaseOnUbuntuCore(c *C) {
- restore := release.MockOnClassic(false)
- defer restore()
+func (s *snapmgrTestSuite) TestNoSnapdSnapOnCoreWithoutBase(c *C) {
+ s.state.Lock()
+ defer s.state.Unlock()
+ r := release.MockOnClassic(false)
+ defer r()
+ // but snapd do not for install
+ _, err := snapstate.Install(context.Background(), s.state, "snapd", &snapstate.RevisionOptions{Channel: "some-channel"}, s.user.ID, snapstate.Flags{})
+ c.Assert(err, ErrorMatches, "cannot install snapd snap on a model without a base snap yet")
+}
+
+func (s *snapmgrTestSuite) TestNoSnapdSnapOnSystemsWithoutBaseOnUbuntuCore(c *C) {
s.state.Lock()
defer s.state.Unlock()
+ r := release.MockOnClassic(false)
+ defer r()
+
+ // it is not possible to opt-into the snapd snap on core yet
+ tr := config.NewTransaction(s.state)
+ tr.Set("core", "experimental.snapd-snap", true)
+ tr.Commit()
// but snapd do not for install
_, err := snapstate.Install(context.Background(), s.state, "snapd", nil, s.user.ID, snapstate.Flags{})
diff -Nru snapd-2.41+19.10.1/packaging/amzn-2/snapd.spec snapd-2.42.1+19.10/packaging/amzn-2/snapd.spec
--- snapd-2.41+19.10.1/packaging/amzn-2/snapd.spec 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/amzn-2/snapd.spec 2019-10-30 12:17:43.000000000 +0000
@@ -92,7 +92,7 @@
%endif
Name: snapd
-Version: 2.41
+Version: 2.42.1
Release: 0%{?dist}
Summary: A transactional software package manager
License: GPLv3
@@ -868,6 +868,243 @@
%changelog
+* Wed Oct 30 2019 Michael Vogt
+- New upstream release 2.42.1
+ - interfaces: de-duplicate emitted update-ns profiles
+ - packaging: tweak handling of usr.lib.snapd.snap-confine
+ - interfaces: allow introspecting network-manager on core
+ - tests/main/interfaces-contacts-service: disable on openSUSE
+ Tumbleweed
+ - tests/lib/lxd-snapfuse: restore mount changes introduced by LXD
+ - snap: fix default-provider in seed validation
+ - tests: update system-usernames test now that opensuse-15.1 works
+ - overlord: set fake sertial in TestRemodelSwitchToDifferentKernel
+ - gadget: rename "boot{select,img}" -> system-boot-{select,image}
+ - tests: listing test, make accepted snapd/core versions consistent
+
+* Tue Oct 01 2019 Michael Vogt
+- New upstream release 2.42
+ - tests: disable {contacts,calendar}-service tests on debian-sid
+ - tests/main/snap-run: disable strace test cases on Arch
+ - cmd/system-shutdown: include correct prototype for die
+ - snap/naming: add test for hook name connect-plug-i2c
+ - cmd/snap-confine: allow digits in hook names
+ - gadget: do not fail the update when old gadget snap is missing
+ bare content
+ - tests: disable {contacts,calendar}-service tests on Arch Linux
+ - tests: move "centos-7" to unstable systems
+ - interfaces/docker-support,kubernetes-support: misc updates for
+ strict k8s
+ - packaging: remove obsolete usr.lib.snapd.snap-confine in
+ postinst
+ - tests: add test that ensures our snapfuse binary actually works
+ - packaging: use snapfuse_ll to speed up snapfuse performance
+ - usersession/userd: make sure to export DBus interfaces before
+ requesting a name
+ - data/selinux: allow snapd to issue sigkill to journalctl
+ - store: download propagates options to delta download
+ - wrappers: allow snaps to install icon theme icons
+ - debug: state-inspect debugging utility
+ - sandbox/cgroup: introduce cgroup wrappers package
+ - snap-confine: fix return value checks for udev functions
+ - cmd/model: output tweaks, add'l tests
+ - wrappers/services: add ServicesEnableState + unit tests
+ - tests: fix newline and wrong test name pointed out in previous PRs
+ - tests: extend mount-ns test to handle mimics
+ - run-checks, tests/main/go: allow gofmt checks to be skipped on
+ 19.10
+ - tests/main/interfaces-{calendar,contacts}-service: disable on
+ 19.10
+ - tests: part3 making tests work on ubuntu-core-18
+ - tests: fix interfaces-timeserver-control on 19.10
+ - overlord/snapstate: config revision code cleanup and extra tests
+ - devicestate: allow remodel to different kernels
+ - overlord,daemon: adjust startup timeout via EXTEND_TIMEOUT_USEC
+ using an estimate
+ - tests/main/many: increase kill-timeout to 5m
+ - interfaces/kubernetes-support: allow systemd-run to ptrace read
+ unconfined
+ - snapstate: auto transition on experimental.snapd-snap=true
+ - tests: retry checking until the written file on desktop-portal-
+ filechooser
+ - tests: unit test for a refresh failing on configure hook
+ - tests: remove mount_id and parent_id from mount-ns test data
+ - tests: move classic-ubuntu-core-transition* to nightly
+ - tests/mountinfo-tool: proper formatting of opt_fields
+ - overlord/configstate: special-case "null" in transaction Changes()
+ - snap-confine: fallback gracefully on a cgroup v2 only system
+ - tests: debian sid now ships new seccomp, adjust tests
+ - tests: explicitly restore after using LXD
+ - snapstate: make progress reporting less granular
+ - bootloader: little kernel support
+ - fixme: rename ubuntu*architectures to dpkg*architectures
+ - tests: run dbus-launch inside a systemd unit
+ - channel: introduce Resolve and ResolveLocked
+ - tests: run failing tests on ubuntu eoan due to is now set as
+ unstable
+ - systemd: detach rather than unmount .mount units
+ - cmd/snap-confine: add unit tests for sc_invocation, cleanup memory
+ leaks in tests
+ - boot,dirs,image: introduce boot.MakeBootable, use it in image
+ instead of ad hoc code
+ - cmd/snap-update-ns: clarify sharing comment
+ - tests/overlord/snapstate: refactor for cleaner test failures
+ - cmd/snap-update-ns: don't propagate detaching changes
+ - interfaces: allow reading mutter Xauthority file
+ - cmd/snap-confine: fix /snap duplication in legacy mode
+ - tests: fix mountinfo-tool filtering when used with rewriting
+ - seed,image,o/devicestate: extract seed loading to seed/seed16.go
+ - many: pass the rootdir and options to bootloader.Find
+ - tests: part5 making tests work on ubuntu-core-18
+ - cmd/snap-confine: keep track of snap instance name and the snap
+ name
+ - cmd: unify die() across C programs
+ - tests: add functions to make an abstraction for the snaps
+ - packaging/fedora, tests/lib/prepare-restore: helper tool for
+ packing sources for RPM
+ - cmd/snap: improve help and error msg for snapshot commands
+ - hookstate/ctlcmd: fix snapctl set help message
+ - cmd/snap: don't append / to snap name just because a dir exists
+ - tests: support fastly-global.cdn.snapcraft.io url on proxy-no-core
+ test
+ - tests: add --quiet switch to retry-tool
+ - tests: add unstable stage for travis execution
+ - tests: disable interfaces-timeserver-control on 19.10
+ - tests: don't guess in is_classic_confinement_supported
+ - boot, etc: simplify BootParticipant (etc) usage
+ - tests: verify retry-tool not retrying missing commands
+ - tests: rewrite "retry" command as retry-tool
+ - tests: move debug section after restore
+ - cmd/libsnap-confine-private, cmd/s-c: use constants for
+ snap/instance name lengths
+ - tests: measure behavior of the device cgroup
+ - boot, bootloader, o/devicestate: boot env manip goes in boot
+ - tests: enabling ubuntu 19.10-64 on spread.yaml
+ - tests: fix ephemeral mount table in left over by prepare
+ - tests: add version-tool for comparing versions
+ - cmd/libsnap: make feature flag enum 1<
- New upstream release 2.41
- overlord/snapstate: revert track-risk behavior
diff -Nru snapd-2.41+19.10.1/packaging/arch/PKGBUILD snapd-2.42.1+19.10/packaging/arch/PKGBUILD
--- snapd-2.41+19.10.1/packaging/arch/PKGBUILD 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/arch/PKGBUILD 2019-10-30 12:17:43.000000000 +0000
@@ -10,7 +10,7 @@
pkgdesc="Service and tools for management of snap packages."
depends=('squashfs-tools' 'libseccomp' 'libsystemd')
optdepends=('bash-completion: bash completion support')
-pkgver=2.41
+pkgver=2.42.1
pkgrel=1
arch=('x86_64')
url="https://github.com/snapcore/snapd"
diff -Nru snapd-2.41+19.10.1/packaging/centos-7/snapd.spec snapd-2.42.1+19.10/packaging/centos-7/snapd.spec
--- snapd-2.41+19.10.1/packaging/centos-7/snapd.spec 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/centos-7/snapd.spec 2019-10-30 12:17:43.000000000 +0000
@@ -92,7 +92,7 @@
%endif
Name: snapd
-Version: 2.41
+Version: 2.42.1
Release: 0%{?dist}
Summary: A transactional software package manager
License: GPLv3
@@ -868,6 +868,243 @@
%changelog
+* Wed Oct 30 2019 Michael Vogt
+- New upstream release 2.42.1
+ - interfaces: de-duplicate emitted update-ns profiles
+ - packaging: tweak handling of usr.lib.snapd.snap-confine
+ - interfaces: allow introspecting network-manager on core
+ - tests/main/interfaces-contacts-service: disable on openSUSE
+ Tumbleweed
+ - tests/lib/lxd-snapfuse: restore mount changes introduced by LXD
+ - snap: fix default-provider in seed validation
+ - tests: update system-usernames test now that opensuse-15.1 works
+ - overlord: set fake sertial in TestRemodelSwitchToDifferentKernel
+ - gadget: rename "boot{select,img}" -> system-boot-{select,image}
+ - tests: listing test, make accepted snapd/core versions consistent
+
+* Tue Oct 01 2019 Michael Vogt
+- New upstream release 2.42
+ - tests: disable {contacts,calendar}-service tests on debian-sid
+ - tests/main/snap-run: disable strace test cases on Arch
+ - cmd/system-shutdown: include correct prototype for die
+ - snap/naming: add test for hook name connect-plug-i2c
+ - cmd/snap-confine: allow digits in hook names
+ - gadget: do not fail the update when old gadget snap is missing
+ bare content
+ - tests: disable {contacts,calendar}-service tests on Arch Linux
+ - tests: move "centos-7" to unstable systems
+ - interfaces/docker-support,kubernetes-support: misc updates for
+ strict k8s
+ - packaging: remove obsolete usr.lib.snapd.snap-confine in
+ postinst
+ - tests: add test that ensures our snapfuse binary actually works
+ - packaging: use snapfuse_ll to speed up snapfuse performance
+ - usersession/userd: make sure to export DBus interfaces before
+ requesting a name
+ - data/selinux: allow snapd to issue sigkill to journalctl
+ - store: download propagates options to delta download
+ - wrappers: allow snaps to install icon theme icons
+ - debug: state-inspect debugging utility
+ - sandbox/cgroup: introduce cgroup wrappers package
+ - snap-confine: fix return value checks for udev functions
+ - cmd/model: output tweaks, add'l tests
+ - wrappers/services: add ServicesEnableState + unit tests
+ - tests: fix newline and wrong test name pointed out in previous PRs
+ - tests: extend mount-ns test to handle mimics
+ - run-checks, tests/main/go: allow gofmt checks to be skipped on
+ 19.10
+ - tests/main/interfaces-{calendar,contacts}-service: disable on
+ 19.10
+ - tests: part3 making tests work on ubuntu-core-18
+ - tests: fix interfaces-timeserver-control on 19.10
+ - overlord/snapstate: config revision code cleanup and extra tests
+ - devicestate: allow remodel to different kernels
+ - overlord,daemon: adjust startup timeout via EXTEND_TIMEOUT_USEC
+ using an estimate
+ - tests/main/many: increase kill-timeout to 5m
+ - interfaces/kubernetes-support: allow systemd-run to ptrace read
+ unconfined
+ - snapstate: auto transition on experimental.snapd-snap=true
+ - tests: retry checking until the written file on desktop-portal-
+ filechooser
+ - tests: unit test for a refresh failing on configure hook
+ - tests: remove mount_id and parent_id from mount-ns test data
+ - tests: move classic-ubuntu-core-transition* to nightly
+ - tests/mountinfo-tool: proper formatting of opt_fields
+ - overlord/configstate: special-case "null" in transaction Changes()
+ - snap-confine: fallback gracefully on a cgroup v2 only system
+ - tests: debian sid now ships new seccomp, adjust tests
+ - tests: explicitly restore after using LXD
+ - snapstate: make progress reporting less granular
+ - bootloader: little kernel support
+ - fixme: rename ubuntu*architectures to dpkg*architectures
+ - tests: run dbus-launch inside a systemd unit
+ - channel: introduce Resolve and ResolveLocked
+ - tests: run failing tests on ubuntu eoan due to is now set as
+ unstable
+ - systemd: detach rather than unmount .mount units
+ - cmd/snap-confine: add unit tests for sc_invocation, cleanup memory
+ leaks in tests
+ - boot,dirs,image: introduce boot.MakeBootable, use it in image
+ instead of ad hoc code
+ - cmd/snap-update-ns: clarify sharing comment
+ - tests/overlord/snapstate: refactor for cleaner test failures
+ - cmd/snap-update-ns: don't propagate detaching changes
+ - interfaces: allow reading mutter Xauthority file
+ - cmd/snap-confine: fix /snap duplication in legacy mode
+ - tests: fix mountinfo-tool filtering when used with rewriting
+ - seed,image,o/devicestate: extract seed loading to seed/seed16.go
+ - many: pass the rootdir and options to bootloader.Find
+ - tests: part5 making tests work on ubuntu-core-18
+ - cmd/snap-confine: keep track of snap instance name and the snap
+ name
+ - cmd: unify die() across C programs
+ - tests: add functions to make an abstraction for the snaps
+ - packaging/fedora, tests/lib/prepare-restore: helper tool for
+ packing sources for RPM
+ - cmd/snap: improve help and error msg for snapshot commands
+ - hookstate/ctlcmd: fix snapctl set help message
+ - cmd/snap: don't append / to snap name just because a dir exists
+ - tests: support fastly-global.cdn.snapcraft.io url on proxy-no-core
+ test
+ - tests: add --quiet switch to retry-tool
+ - tests: add unstable stage for travis execution
+ - tests: disable interfaces-timeserver-control on 19.10
+ - tests: don't guess in is_classic_confinement_supported
+ - boot, etc: simplify BootParticipant (etc) usage
+ - tests: verify retry-tool not retrying missing commands
+ - tests: rewrite "retry" command as retry-tool
+ - tests: move debug section after restore
+ - cmd/libsnap-confine-private, cmd/s-c: use constants for
+ snap/instance name lengths
+ - tests: measure behavior of the device cgroup
+ - boot, bootloader, o/devicestate: boot env manip goes in boot
+ - tests: enabling ubuntu 19.10-64 on spread.yaml
+ - tests: fix ephemeral mount table in left over by prepare
+ - tests: add version-tool for comparing versions
+ - cmd/libsnap: make feature flag enum 1<
- New upstream release 2.41
- overlord/snapstate: revert track-risk behavior
diff -Nru snapd-2.41+19.10.1/packaging/debian-sid/changelog snapd-2.42.1+19.10/packaging/debian-sid/changelog
--- snapd-2.41+19.10.1/packaging/debian-sid/changelog 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/debian-sid/changelog 2019-10-30 12:17:43.000000000 +0000
@@ -1,3 +1,26 @@
+snapd (2.42.1-1) unstable; urgency=medium
+
+ * New upstream release, LP: #1846181
+ - interfaces: de-duplicate emitted update-ns profiles
+ - packaging: tweak handling of usr.lib.snapd.snap-confine
+ - interfaces: allow introspecting network-manager on core
+ - tests/main/interfaces-contacts-service: disable on openSUSE
+ Tumbleweed
+ - tests/lib/lxd-snapfuse: restore mount changes introduced by LXD
+ - snap: fix default-provider in seed validation
+ - tests: update system-usernames test now that opensuse-15.1 works
+ - overlord: set fake sertial in TestRemodelSwitchToDifferentKernel
+ - gadget: rename "boot{select,img}" -> system-boot-{select,image}
+ - tests: listing test, make accepted snapd/core versions consistent
+
+ -- Michael Vogt Wed, 30 Oct 2019 13:17:43 +0100
+
+snapd (2.42-1) unstable; urgency=medium
+
+ * New upstream release
+
+ -- Michael Vogt Tue, 01 Oct 2019 11:40:58 +0200
+
snapd (2.41-1) unstable; urgency=medium
[ Michael Vogt ]
diff -Nru snapd-2.41+19.10.1/packaging/debian-sid/patches/0003-cmd-snap-seccomp-skip-tests-that-use-m32.patch snapd-2.42.1+19.10/packaging/debian-sid/patches/0003-cmd-snap-seccomp-skip-tests-that-use-m32.patch
--- snapd-2.41+19.10.1/packaging/debian-sid/patches/0003-cmd-snap-seccomp-skip-tests-that-use-m32.patch 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/debian-sid/patches/0003-cmd-snap-seccomp-skip-tests-that-use-m32.patch 2019-10-30 12:17:43.000000000 +0000
@@ -24,14 +24,14 @@
cmd/snap-seccomp/main_test.go | 8 ++++++++
1 file changed, 8 insertions(+)
-diff --git a/cmd/snap-seccomp/main_test.go b/cmd/snap-seccomp/main_test.go
-index d4ca193b2..8977385c2 100644
---- a/cmd/snap-seccomp/main_test.go
-+++ b/cmd/snap-seccomp/main_test.go
-@@ -185,6 +185,14 @@ func (s *snapSeccompSuite) SetUpSuite(c *C) {
+Index: snapd/cmd/snap-seccomp/main_test.go
+===================================================================
+--- snapd.orig/cmd/snap-seccomp/main_test.go
++++ snapd/cmd/snap-seccomp/main_test.go
+@@ -192,6 +192,14 @@ func (s *snapSeccompSuite) SetUpSuite(c
// 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" && s.canCheckCompatArch {
+ if arch.DpkgArchitecture() == "amd64" && s.canCheckCompatArch {
+ // This test fails on Debian amd64
+ // cannot build multi-lib syscall runner: exit status 1
+ // In file included from /usr/include/errno.h:25,
@@ -43,6 +43,3 @@
cmd = exec.Command(cmd.Args[0], cmd.Args[1:]...)
cmd.Args = append(cmd.Args, "-m32")
for i, k := range cmd.Args {
---
-2.17.1
-
diff -Nru snapd-2.41+19.10.1/packaging/debian-sid/rules snapd-2.42.1+19.10/packaging/debian-sid/rules
--- snapd-2.41+19.10.1/packaging/debian-sid/rules 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/debian-sid/rules 2019-10-30 12:17:43.000000000 +0000
@@ -146,9 +146,9 @@
# Generate static snap-exec, snapctl 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)/.. GOCACHE=$$(pwd)/../go-build CGO_ENABLED=0 go build $(GCCGOFLAGS) -pkgdir=$$(pwd)/std $(DH_GOPKG)/cmd/snap-exec)
- (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=$$(pwd)/../go-build CGO_ENABLED=0 go build $(GCCGOFLAGS) -pkgdir=$$(pwd)/std $(DH_GOPKG)/cmd/snapctl)
- (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=$$(pwd)/../go-build go build --ldflags '-extldflags "-static"' $(GCCGOFLAGS) -pkgdir=$$(pwd)/std $(DH_GOPKG)/cmd/snap-update-ns)
+ (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=/tmp/go-build CGO_ENABLED=0 go build $(GCCGOFLAGS) -pkgdir=$$(pwd)/std $(DH_GOPKG)/cmd/snap-exec)
+ (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=/tmp/go-build CGO_ENABLED=0 go build $(GCCGOFLAGS) -pkgdir=$$(pwd)/std $(DH_GOPKG)/cmd/snapctl)
+ (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=/tmp/go-build go build --ldflags '-extldflags "-static"' $(GCCGOFLAGS) -pkgdir=$$(pwd)/std $(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)
diff -Nru snapd-2.41+19.10.1/packaging/fedora/snapd.spec snapd-2.42.1+19.10/packaging/fedora/snapd.spec
--- snapd-2.41+19.10.1/packaging/fedora/snapd.spec 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/fedora/snapd.spec 2019-10-30 12:17:43.000000000 +0000
@@ -92,7 +92,7 @@
%endif
Name: snapd
-Version: 2.41
+Version: 2.42.1
Release: 0%{?dist}
Summary: A transactional software package manager
License: GPLv3
@@ -868,6 +868,243 @@
%changelog
+* Wed Oct 30 2019 Michael Vogt
+- New upstream release 2.42.1
+ - interfaces: de-duplicate emitted update-ns profiles
+ - packaging: tweak handling of usr.lib.snapd.snap-confine
+ - interfaces: allow introspecting network-manager on core
+ - tests/main/interfaces-contacts-service: disable on openSUSE
+ Tumbleweed
+ - tests/lib/lxd-snapfuse: restore mount changes introduced by LXD
+ - snap: fix default-provider in seed validation
+ - tests: update system-usernames test now that opensuse-15.1 works
+ - overlord: set fake sertial in TestRemodelSwitchToDifferentKernel
+ - gadget: rename "boot{select,img}" -> system-boot-{select,image}
+ - tests: listing test, make accepted snapd/core versions consistent
+
+* Tue Oct 01 2019 Michael Vogt
+- New upstream release 2.42
+ - tests: disable {contacts,calendar}-service tests on debian-sid
+ - tests/main/snap-run: disable strace test cases on Arch
+ - cmd/system-shutdown: include correct prototype for die
+ - snap/naming: add test for hook name connect-plug-i2c
+ - cmd/snap-confine: allow digits in hook names
+ - gadget: do not fail the update when old gadget snap is missing
+ bare content
+ - tests: disable {contacts,calendar}-service tests on Arch Linux
+ - tests: move "centos-7" to unstable systems
+ - interfaces/docker-support,kubernetes-support: misc updates for
+ strict k8s
+ - packaging: remove obsolete usr.lib.snapd.snap-confine in
+ postinst
+ - tests: add test that ensures our snapfuse binary actually works
+ - packaging: use snapfuse_ll to speed up snapfuse performance
+ - usersession/userd: make sure to export DBus interfaces before
+ requesting a name
+ - data/selinux: allow snapd to issue sigkill to journalctl
+ - store: download propagates options to delta download
+ - wrappers: allow snaps to install icon theme icons
+ - debug: state-inspect debugging utility
+ - sandbox/cgroup: introduce cgroup wrappers package
+ - snap-confine: fix return value checks for udev functions
+ - cmd/model: output tweaks, add'l tests
+ - wrappers/services: add ServicesEnableState + unit tests
+ - tests: fix newline and wrong test name pointed out in previous PRs
+ - tests: extend mount-ns test to handle mimics
+ - run-checks, tests/main/go: allow gofmt checks to be skipped on
+ 19.10
+ - tests/main/interfaces-{calendar,contacts}-service: disable on
+ 19.10
+ - tests: part3 making tests work on ubuntu-core-18
+ - tests: fix interfaces-timeserver-control on 19.10
+ - overlord/snapstate: config revision code cleanup and extra tests
+ - devicestate: allow remodel to different kernels
+ - overlord,daemon: adjust startup timeout via EXTEND_TIMEOUT_USEC
+ using an estimate
+ - tests/main/many: increase kill-timeout to 5m
+ - interfaces/kubernetes-support: allow systemd-run to ptrace read
+ unconfined
+ - snapstate: auto transition on experimental.snapd-snap=true
+ - tests: retry checking until the written file on desktop-portal-
+ filechooser
+ - tests: unit test for a refresh failing on configure hook
+ - tests: remove mount_id and parent_id from mount-ns test data
+ - tests: move classic-ubuntu-core-transition* to nightly
+ - tests/mountinfo-tool: proper formatting of opt_fields
+ - overlord/configstate: special-case "null" in transaction Changes()
+ - snap-confine: fallback gracefully on a cgroup v2 only system
+ - tests: debian sid now ships new seccomp, adjust tests
+ - tests: explicitly restore after using LXD
+ - snapstate: make progress reporting less granular
+ - bootloader: little kernel support
+ - fixme: rename ubuntu*architectures to dpkg*architectures
+ - tests: run dbus-launch inside a systemd unit
+ - channel: introduce Resolve and ResolveLocked
+ - tests: run failing tests on ubuntu eoan due to is now set as
+ unstable
+ - systemd: detach rather than unmount .mount units
+ - cmd/snap-confine: add unit tests for sc_invocation, cleanup memory
+ leaks in tests
+ - boot,dirs,image: introduce boot.MakeBootable, use it in image
+ instead of ad hoc code
+ - cmd/snap-update-ns: clarify sharing comment
+ - tests/overlord/snapstate: refactor for cleaner test failures
+ - cmd/snap-update-ns: don't propagate detaching changes
+ - interfaces: allow reading mutter Xauthority file
+ - cmd/snap-confine: fix /snap duplication in legacy mode
+ - tests: fix mountinfo-tool filtering when used with rewriting
+ - seed,image,o/devicestate: extract seed loading to seed/seed16.go
+ - many: pass the rootdir and options to bootloader.Find
+ - tests: part5 making tests work on ubuntu-core-18
+ - cmd/snap-confine: keep track of snap instance name and the snap
+ name
+ - cmd: unify die() across C programs
+ - tests: add functions to make an abstraction for the snaps
+ - packaging/fedora, tests/lib/prepare-restore: helper tool for
+ packing sources for RPM
+ - cmd/snap: improve help and error msg for snapshot commands
+ - hookstate/ctlcmd: fix snapctl set help message
+ - cmd/snap: don't append / to snap name just because a dir exists
+ - tests: support fastly-global.cdn.snapcraft.io url on proxy-no-core
+ test
+ - tests: add --quiet switch to retry-tool
+ - tests: add unstable stage for travis execution
+ - tests: disable interfaces-timeserver-control on 19.10
+ - tests: don't guess in is_classic_confinement_supported
+ - boot, etc: simplify BootParticipant (etc) usage
+ - tests: verify retry-tool not retrying missing commands
+ - tests: rewrite "retry" command as retry-tool
+ - tests: move debug section after restore
+ - cmd/libsnap-confine-private, cmd/s-c: use constants for
+ snap/instance name lengths
+ - tests: measure behavior of the device cgroup
+ - boot, bootloader, o/devicestate: boot env manip goes in boot
+ - tests: enabling ubuntu 19.10-64 on spread.yaml
+ - tests: fix ephemeral mount table in left over by prepare
+ - tests: add version-tool for comparing versions
+ - cmd/libsnap: make feature flag enum 1<
- New upstream release 2.41
- overlord/snapstate: revert track-risk behavior
diff -Nru snapd-2.41+19.10.1/packaging/fedora-29/snapd.spec snapd-2.42.1+19.10/packaging/fedora-29/snapd.spec
--- snapd-2.41+19.10.1/packaging/fedora-29/snapd.spec 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/fedora-29/snapd.spec 2019-10-30 12:17:43.000000000 +0000
@@ -92,7 +92,7 @@
%endif
Name: snapd
-Version: 2.41
+Version: 2.42.1
Release: 0%{?dist}
Summary: A transactional software package manager
License: GPLv3
@@ -868,6 +868,243 @@
%changelog
+* Wed Oct 30 2019 Michael Vogt
+- New upstream release 2.42.1
+ - interfaces: de-duplicate emitted update-ns profiles
+ - packaging: tweak handling of usr.lib.snapd.snap-confine
+ - interfaces: allow introspecting network-manager on core
+ - tests/main/interfaces-contacts-service: disable on openSUSE
+ Tumbleweed
+ - tests/lib/lxd-snapfuse: restore mount changes introduced by LXD
+ - snap: fix default-provider in seed validation
+ - tests: update system-usernames test now that opensuse-15.1 works
+ - overlord: set fake sertial in TestRemodelSwitchToDifferentKernel
+ - gadget: rename "boot{select,img}" -> system-boot-{select,image}
+ - tests: listing test, make accepted snapd/core versions consistent
+
+* Tue Oct 01 2019 Michael Vogt
+- New upstream release 2.42
+ - tests: disable {contacts,calendar}-service tests on debian-sid
+ - tests/main/snap-run: disable strace test cases on Arch
+ - cmd/system-shutdown: include correct prototype for die
+ - snap/naming: add test for hook name connect-plug-i2c
+ - cmd/snap-confine: allow digits in hook names
+ - gadget: do not fail the update when old gadget snap is missing
+ bare content
+ - tests: disable {contacts,calendar}-service tests on Arch Linux
+ - tests: move "centos-7" to unstable systems
+ - interfaces/docker-support,kubernetes-support: misc updates for
+ strict k8s
+ - packaging: remove obsolete usr.lib.snapd.snap-confine in
+ postinst
+ - tests: add test that ensures our snapfuse binary actually works
+ - packaging: use snapfuse_ll to speed up snapfuse performance
+ - usersession/userd: make sure to export DBus interfaces before
+ requesting a name
+ - data/selinux: allow snapd to issue sigkill to journalctl
+ - store: download propagates options to delta download
+ - wrappers: allow snaps to install icon theme icons
+ - debug: state-inspect debugging utility
+ - sandbox/cgroup: introduce cgroup wrappers package
+ - snap-confine: fix return value checks for udev functions
+ - cmd/model: output tweaks, add'l tests
+ - wrappers/services: add ServicesEnableState + unit tests
+ - tests: fix newline and wrong test name pointed out in previous PRs
+ - tests: extend mount-ns test to handle mimics
+ - run-checks, tests/main/go: allow gofmt checks to be skipped on
+ 19.10
+ - tests/main/interfaces-{calendar,contacts}-service: disable on
+ 19.10
+ - tests: part3 making tests work on ubuntu-core-18
+ - tests: fix interfaces-timeserver-control on 19.10
+ - overlord/snapstate: config revision code cleanup and extra tests
+ - devicestate: allow remodel to different kernels
+ - overlord,daemon: adjust startup timeout via EXTEND_TIMEOUT_USEC
+ using an estimate
+ - tests/main/many: increase kill-timeout to 5m
+ - interfaces/kubernetes-support: allow systemd-run to ptrace read
+ unconfined
+ - snapstate: auto transition on experimental.snapd-snap=true
+ - tests: retry checking until the written file on desktop-portal-
+ filechooser
+ - tests: unit test for a refresh failing on configure hook
+ - tests: remove mount_id and parent_id from mount-ns test data
+ - tests: move classic-ubuntu-core-transition* to nightly
+ - tests/mountinfo-tool: proper formatting of opt_fields
+ - overlord/configstate: special-case "null" in transaction Changes()
+ - snap-confine: fallback gracefully on a cgroup v2 only system
+ - tests: debian sid now ships new seccomp, adjust tests
+ - tests: explicitly restore after using LXD
+ - snapstate: make progress reporting less granular
+ - bootloader: little kernel support
+ - fixme: rename ubuntu*architectures to dpkg*architectures
+ - tests: run dbus-launch inside a systemd unit
+ - channel: introduce Resolve and ResolveLocked
+ - tests: run failing tests on ubuntu eoan due to is now set as
+ unstable
+ - systemd: detach rather than unmount .mount units
+ - cmd/snap-confine: add unit tests for sc_invocation, cleanup memory
+ leaks in tests
+ - boot,dirs,image: introduce boot.MakeBootable, use it in image
+ instead of ad hoc code
+ - cmd/snap-update-ns: clarify sharing comment
+ - tests/overlord/snapstate: refactor for cleaner test failures
+ - cmd/snap-update-ns: don't propagate detaching changes
+ - interfaces: allow reading mutter Xauthority file
+ - cmd/snap-confine: fix /snap duplication in legacy mode
+ - tests: fix mountinfo-tool filtering when used with rewriting
+ - seed,image,o/devicestate: extract seed loading to seed/seed16.go
+ - many: pass the rootdir and options to bootloader.Find
+ - tests: part5 making tests work on ubuntu-core-18
+ - cmd/snap-confine: keep track of snap instance name and the snap
+ name
+ - cmd: unify die() across C programs
+ - tests: add functions to make an abstraction for the snaps
+ - packaging/fedora, tests/lib/prepare-restore: helper tool for
+ packing sources for RPM
+ - cmd/snap: improve help and error msg for snapshot commands
+ - hookstate/ctlcmd: fix snapctl set help message
+ - cmd/snap: don't append / to snap name just because a dir exists
+ - tests: support fastly-global.cdn.snapcraft.io url on proxy-no-core
+ test
+ - tests: add --quiet switch to retry-tool
+ - tests: add unstable stage for travis execution
+ - tests: disable interfaces-timeserver-control on 19.10
+ - tests: don't guess in is_classic_confinement_supported
+ - boot, etc: simplify BootParticipant (etc) usage
+ - tests: verify retry-tool not retrying missing commands
+ - tests: rewrite "retry" command as retry-tool
+ - tests: move debug section after restore
+ - cmd/libsnap-confine-private, cmd/s-c: use constants for
+ snap/instance name lengths
+ - tests: measure behavior of the device cgroup
+ - boot, bootloader, o/devicestate: boot env manip goes in boot
+ - tests: enabling ubuntu 19.10-64 on spread.yaml
+ - tests: fix ephemeral mount table in left over by prepare
+ - tests: add version-tool for comparing versions
+ - cmd/libsnap: make feature flag enum 1<
- New upstream release 2.41
- overlord/snapstate: revert track-risk behavior
diff -Nru snapd-2.41+19.10.1/packaging/fedora-30/snapd.spec snapd-2.42.1+19.10/packaging/fedora-30/snapd.spec
--- snapd-2.41+19.10.1/packaging/fedora-30/snapd.spec 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/fedora-30/snapd.spec 2019-10-30 12:17:43.000000000 +0000
@@ -92,7 +92,7 @@
%endif
Name: snapd
-Version: 2.41
+Version: 2.42.1
Release: 0%{?dist}
Summary: A transactional software package manager
License: GPLv3
@@ -868,6 +868,243 @@
%changelog
+* Wed Oct 30 2019 Michael Vogt
+- New upstream release 2.42.1
+ - interfaces: de-duplicate emitted update-ns profiles
+ - packaging: tweak handling of usr.lib.snapd.snap-confine
+ - interfaces: allow introspecting network-manager on core
+ - tests/main/interfaces-contacts-service: disable on openSUSE
+ Tumbleweed
+ - tests/lib/lxd-snapfuse: restore mount changes introduced by LXD
+ - snap: fix default-provider in seed validation
+ - tests: update system-usernames test now that opensuse-15.1 works
+ - overlord: set fake sertial in TestRemodelSwitchToDifferentKernel
+ - gadget: rename "boot{select,img}" -> system-boot-{select,image}
+ - tests: listing test, make accepted snapd/core versions consistent
+
+* Tue Oct 01 2019 Michael Vogt
+- New upstream release 2.42
+ - tests: disable {contacts,calendar}-service tests on debian-sid
+ - tests/main/snap-run: disable strace test cases on Arch
+ - cmd/system-shutdown: include correct prototype for die
+ - snap/naming: add test for hook name connect-plug-i2c
+ - cmd/snap-confine: allow digits in hook names
+ - gadget: do not fail the update when old gadget snap is missing
+ bare content
+ - tests: disable {contacts,calendar}-service tests on Arch Linux
+ - tests: move "centos-7" to unstable systems
+ - interfaces/docker-support,kubernetes-support: misc updates for
+ strict k8s
+ - packaging: remove obsolete usr.lib.snapd.snap-confine in
+ postinst
+ - tests: add test that ensures our snapfuse binary actually works
+ - packaging: use snapfuse_ll to speed up snapfuse performance
+ - usersession/userd: make sure to export DBus interfaces before
+ requesting a name
+ - data/selinux: allow snapd to issue sigkill to journalctl
+ - store: download propagates options to delta download
+ - wrappers: allow snaps to install icon theme icons
+ - debug: state-inspect debugging utility
+ - sandbox/cgroup: introduce cgroup wrappers package
+ - snap-confine: fix return value checks for udev functions
+ - cmd/model: output tweaks, add'l tests
+ - wrappers/services: add ServicesEnableState + unit tests
+ - tests: fix newline and wrong test name pointed out in previous PRs
+ - tests: extend mount-ns test to handle mimics
+ - run-checks, tests/main/go: allow gofmt checks to be skipped on
+ 19.10
+ - tests/main/interfaces-{calendar,contacts}-service: disable on
+ 19.10
+ - tests: part3 making tests work on ubuntu-core-18
+ - tests: fix interfaces-timeserver-control on 19.10
+ - overlord/snapstate: config revision code cleanup and extra tests
+ - devicestate: allow remodel to different kernels
+ - overlord,daemon: adjust startup timeout via EXTEND_TIMEOUT_USEC
+ using an estimate
+ - tests/main/many: increase kill-timeout to 5m
+ - interfaces/kubernetes-support: allow systemd-run to ptrace read
+ unconfined
+ - snapstate: auto transition on experimental.snapd-snap=true
+ - tests: retry checking until the written file on desktop-portal-
+ filechooser
+ - tests: unit test for a refresh failing on configure hook
+ - tests: remove mount_id and parent_id from mount-ns test data
+ - tests: move classic-ubuntu-core-transition* to nightly
+ - tests/mountinfo-tool: proper formatting of opt_fields
+ - overlord/configstate: special-case "null" in transaction Changes()
+ - snap-confine: fallback gracefully on a cgroup v2 only system
+ - tests: debian sid now ships new seccomp, adjust tests
+ - tests: explicitly restore after using LXD
+ - snapstate: make progress reporting less granular
+ - bootloader: little kernel support
+ - fixme: rename ubuntu*architectures to dpkg*architectures
+ - tests: run dbus-launch inside a systemd unit
+ - channel: introduce Resolve and ResolveLocked
+ - tests: run failing tests on ubuntu eoan due to is now set as
+ unstable
+ - systemd: detach rather than unmount .mount units
+ - cmd/snap-confine: add unit tests for sc_invocation, cleanup memory
+ leaks in tests
+ - boot,dirs,image: introduce boot.MakeBootable, use it in image
+ instead of ad hoc code
+ - cmd/snap-update-ns: clarify sharing comment
+ - tests/overlord/snapstate: refactor for cleaner test failures
+ - cmd/snap-update-ns: don't propagate detaching changes
+ - interfaces: allow reading mutter Xauthority file
+ - cmd/snap-confine: fix /snap duplication in legacy mode
+ - tests: fix mountinfo-tool filtering when used with rewriting
+ - seed,image,o/devicestate: extract seed loading to seed/seed16.go
+ - many: pass the rootdir and options to bootloader.Find
+ - tests: part5 making tests work on ubuntu-core-18
+ - cmd/snap-confine: keep track of snap instance name and the snap
+ name
+ - cmd: unify die() across C programs
+ - tests: add functions to make an abstraction for the snaps
+ - packaging/fedora, tests/lib/prepare-restore: helper tool for
+ packing sources for RPM
+ - cmd/snap: improve help and error msg for snapshot commands
+ - hookstate/ctlcmd: fix snapctl set help message
+ - cmd/snap: don't append / to snap name just because a dir exists
+ - tests: support fastly-global.cdn.snapcraft.io url on proxy-no-core
+ test
+ - tests: add --quiet switch to retry-tool
+ - tests: add unstable stage for travis execution
+ - tests: disable interfaces-timeserver-control on 19.10
+ - tests: don't guess in is_classic_confinement_supported
+ - boot, etc: simplify BootParticipant (etc) usage
+ - tests: verify retry-tool not retrying missing commands
+ - tests: rewrite "retry" command as retry-tool
+ - tests: move debug section after restore
+ - cmd/libsnap-confine-private, cmd/s-c: use constants for
+ snap/instance name lengths
+ - tests: measure behavior of the device cgroup
+ - boot, bootloader, o/devicestate: boot env manip goes in boot
+ - tests: enabling ubuntu 19.10-64 on spread.yaml
+ - tests: fix ephemeral mount table in left over by prepare
+ - tests: add version-tool for comparing versions
+ - cmd/libsnap: make feature flag enum 1<
- New upstream release 2.41
- overlord/snapstate: revert track-risk behavior
diff -Nru snapd-2.41+19.10.1/packaging/fedora-rawhide/snapd.spec snapd-2.42.1+19.10/packaging/fedora-rawhide/snapd.spec
--- snapd-2.41+19.10.1/packaging/fedora-rawhide/snapd.spec 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/fedora-rawhide/snapd.spec 2019-10-30 12:17:43.000000000 +0000
@@ -92,7 +92,7 @@
%endif
Name: snapd
-Version: 2.41
+Version: 2.42.1
Release: 0%{?dist}
Summary: A transactional software package manager
License: GPLv3
@@ -868,6 +868,243 @@
%changelog
+* Wed Oct 30 2019 Michael Vogt
+- New upstream release 2.42.1
+ - interfaces: de-duplicate emitted update-ns profiles
+ - packaging: tweak handling of usr.lib.snapd.snap-confine
+ - interfaces: allow introspecting network-manager on core
+ - tests/main/interfaces-contacts-service: disable on openSUSE
+ Tumbleweed
+ - tests/lib/lxd-snapfuse: restore mount changes introduced by LXD
+ - snap: fix default-provider in seed validation
+ - tests: update system-usernames test now that opensuse-15.1 works
+ - overlord: set fake sertial in TestRemodelSwitchToDifferentKernel
+ - gadget: rename "boot{select,img}" -> system-boot-{select,image}
+ - tests: listing test, make accepted snapd/core versions consistent
+
+* Tue Oct 01 2019 Michael Vogt
+- New upstream release 2.42
+ - tests: disable {contacts,calendar}-service tests on debian-sid
+ - tests/main/snap-run: disable strace test cases on Arch
+ - cmd/system-shutdown: include correct prototype for die
+ - snap/naming: add test for hook name connect-plug-i2c
+ - cmd/snap-confine: allow digits in hook names
+ - gadget: do not fail the update when old gadget snap is missing
+ bare content
+ - tests: disable {contacts,calendar}-service tests on Arch Linux
+ - tests: move "centos-7" to unstable systems
+ - interfaces/docker-support,kubernetes-support: misc updates for
+ strict k8s
+ - packaging: remove obsolete usr.lib.snapd.snap-confine in
+ postinst
+ - tests: add test that ensures our snapfuse binary actually works
+ - packaging: use snapfuse_ll to speed up snapfuse performance
+ - usersession/userd: make sure to export DBus interfaces before
+ requesting a name
+ - data/selinux: allow snapd to issue sigkill to journalctl
+ - store: download propagates options to delta download
+ - wrappers: allow snaps to install icon theme icons
+ - debug: state-inspect debugging utility
+ - sandbox/cgroup: introduce cgroup wrappers package
+ - snap-confine: fix return value checks for udev functions
+ - cmd/model: output tweaks, add'l tests
+ - wrappers/services: add ServicesEnableState + unit tests
+ - tests: fix newline and wrong test name pointed out in previous PRs
+ - tests: extend mount-ns test to handle mimics
+ - run-checks, tests/main/go: allow gofmt checks to be skipped on
+ 19.10
+ - tests/main/interfaces-{calendar,contacts}-service: disable on
+ 19.10
+ - tests: part3 making tests work on ubuntu-core-18
+ - tests: fix interfaces-timeserver-control on 19.10
+ - overlord/snapstate: config revision code cleanup and extra tests
+ - devicestate: allow remodel to different kernels
+ - overlord,daemon: adjust startup timeout via EXTEND_TIMEOUT_USEC
+ using an estimate
+ - tests/main/many: increase kill-timeout to 5m
+ - interfaces/kubernetes-support: allow systemd-run to ptrace read
+ unconfined
+ - snapstate: auto transition on experimental.snapd-snap=true
+ - tests: retry checking until the written file on desktop-portal-
+ filechooser
+ - tests: unit test for a refresh failing on configure hook
+ - tests: remove mount_id and parent_id from mount-ns test data
+ - tests: move classic-ubuntu-core-transition* to nightly
+ - tests/mountinfo-tool: proper formatting of opt_fields
+ - overlord/configstate: special-case "null" in transaction Changes()
+ - snap-confine: fallback gracefully on a cgroup v2 only system
+ - tests: debian sid now ships new seccomp, adjust tests
+ - tests: explicitly restore after using LXD
+ - snapstate: make progress reporting less granular
+ - bootloader: little kernel support
+ - fixme: rename ubuntu*architectures to dpkg*architectures
+ - tests: run dbus-launch inside a systemd unit
+ - channel: introduce Resolve and ResolveLocked
+ - tests: run failing tests on ubuntu eoan due to is now set as
+ unstable
+ - systemd: detach rather than unmount .mount units
+ - cmd/snap-confine: add unit tests for sc_invocation, cleanup memory
+ leaks in tests
+ - boot,dirs,image: introduce boot.MakeBootable, use it in image
+ instead of ad hoc code
+ - cmd/snap-update-ns: clarify sharing comment
+ - tests/overlord/snapstate: refactor for cleaner test failures
+ - cmd/snap-update-ns: don't propagate detaching changes
+ - interfaces: allow reading mutter Xauthority file
+ - cmd/snap-confine: fix /snap duplication in legacy mode
+ - tests: fix mountinfo-tool filtering when used with rewriting
+ - seed,image,o/devicestate: extract seed loading to seed/seed16.go
+ - many: pass the rootdir and options to bootloader.Find
+ - tests: part5 making tests work on ubuntu-core-18
+ - cmd/snap-confine: keep track of snap instance name and the snap
+ name
+ - cmd: unify die() across C programs
+ - tests: add functions to make an abstraction for the snaps
+ - packaging/fedora, tests/lib/prepare-restore: helper tool for
+ packing sources for RPM
+ - cmd/snap: improve help and error msg for snapshot commands
+ - hookstate/ctlcmd: fix snapctl set help message
+ - cmd/snap: don't append / to snap name just because a dir exists
+ - tests: support fastly-global.cdn.snapcraft.io url on proxy-no-core
+ test
+ - tests: add --quiet switch to retry-tool
+ - tests: add unstable stage for travis execution
+ - tests: disable interfaces-timeserver-control on 19.10
+ - tests: don't guess in is_classic_confinement_supported
+ - boot, etc: simplify BootParticipant (etc) usage
+ - tests: verify retry-tool not retrying missing commands
+ - tests: rewrite "retry" command as retry-tool
+ - tests: move debug section after restore
+ - cmd/libsnap-confine-private, cmd/s-c: use constants for
+ snap/instance name lengths
+ - tests: measure behavior of the device cgroup
+ - boot, bootloader, o/devicestate: boot env manip goes in boot
+ - tests: enabling ubuntu 19.10-64 on spread.yaml
+ - tests: fix ephemeral mount table in left over by prepare
+ - tests: add version-tool for comparing versions
+ - cmd/libsnap: make feature flag enum 1<
- New upstream release 2.41
- overlord/snapstate: revert track-risk behavior
diff -Nru snapd-2.41+19.10.1/packaging/opensuse/snapd.changes snapd-2.42.1+19.10/packaging/opensuse/snapd.changes
--- snapd-2.41+19.10.1/packaging/opensuse/snapd.changes 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/opensuse/snapd.changes 2019-10-30 12:17:43.000000000 +0000
@@ -1,4 +1,14 @@
-------------------------------------------------------------------
+Wed Oct 30 11:17:43 UTC 2019 - mvo@ubuntu.com
+
+- Update to upstream release 2.42.1
+
+-------------------------------------------------------------------
+Tue Oct 01 09:42:48 UTC 2019 - mvo@ubuntu.com
+
+- Update to upstream release 2.42
+
+-------------------------------------------------------------------
Fri Aug 30 06:55:43 UTC 2019 - mvo@ubuntu.com
- Update to upstream release 2.41
diff -Nru snapd-2.41+19.10.1/packaging/opensuse/snapd.spec snapd-2.42.1+19.10/packaging/opensuse/snapd.spec
--- snapd-2.41+19.10.1/packaging/opensuse/snapd.spec 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/opensuse/snapd.spec 2019-10-30 12:17:43.000000000 +0000
@@ -77,7 +77,7 @@
Name: snapd
-Version: 2.41
+Version: 2.42.1
Release: 0
Summary: Tools enabling systems to work with .snap files
License: GPL-3.0
diff -Nru snapd-2.41+19.10.1/packaging/opensuse-15.0/snapd.changes snapd-2.42.1+19.10/packaging/opensuse-15.0/snapd.changes
--- snapd-2.41+19.10.1/packaging/opensuse-15.0/snapd.changes 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/opensuse-15.0/snapd.changes 2019-10-30 12:17:43.000000000 +0000
@@ -1,4 +1,14 @@
-------------------------------------------------------------------
+Wed Oct 30 11:17:43 UTC 2019 - mvo@ubuntu.com
+
+- Update to upstream release 2.42.1
+
+-------------------------------------------------------------------
+Tue Oct 01 09:42:48 UTC 2019 - mvo@ubuntu.com
+
+- Update to upstream release 2.42
+
+-------------------------------------------------------------------
Fri Aug 30 06:55:43 UTC 2019 - mvo@ubuntu.com
- Update to upstream release 2.41
diff -Nru snapd-2.41+19.10.1/packaging/opensuse-15.0/snapd.spec snapd-2.42.1+19.10/packaging/opensuse-15.0/snapd.spec
--- snapd-2.41+19.10.1/packaging/opensuse-15.0/snapd.spec 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/opensuse-15.0/snapd.spec 2019-10-30 12:17:43.000000000 +0000
@@ -77,7 +77,7 @@
Name: snapd
-Version: 2.41
+Version: 2.42.1
Release: 0
Summary: Tools enabling systems to work with .snap files
License: GPL-3.0
diff -Nru snapd-2.41+19.10.1/packaging/opensuse-15.1/snapd.changes snapd-2.42.1+19.10/packaging/opensuse-15.1/snapd.changes
--- snapd-2.41+19.10.1/packaging/opensuse-15.1/snapd.changes 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/opensuse-15.1/snapd.changes 2019-10-30 12:17:43.000000000 +0000
@@ -1,4 +1,14 @@
-------------------------------------------------------------------
+Wed Oct 30 11:17:43 UTC 2019 - mvo@ubuntu.com
+
+- Update to upstream release 2.42.1
+
+-------------------------------------------------------------------
+Tue Oct 01 09:42:48 UTC 2019 - mvo@ubuntu.com
+
+- Update to upstream release 2.42
+
+-------------------------------------------------------------------
Fri Aug 30 06:55:43 UTC 2019 - mvo@ubuntu.com
- Update to upstream release 2.41
diff -Nru snapd-2.41+19.10.1/packaging/opensuse-15.1/snapd.spec snapd-2.42.1+19.10/packaging/opensuse-15.1/snapd.spec
--- snapd-2.41+19.10.1/packaging/opensuse-15.1/snapd.spec 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/opensuse-15.1/snapd.spec 2019-10-30 12:17:43.000000000 +0000
@@ -77,7 +77,7 @@
Name: snapd
-Version: 2.41
+Version: 2.42.1
Release: 0
Summary: Tools enabling systems to work with .snap files
License: GPL-3.0
diff -Nru snapd-2.41+19.10.1/packaging/opensuse-tumbleweed/snapd.changes snapd-2.42.1+19.10/packaging/opensuse-tumbleweed/snapd.changes
--- snapd-2.41+19.10.1/packaging/opensuse-tumbleweed/snapd.changes 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/opensuse-tumbleweed/snapd.changes 2019-10-30 12:17:43.000000000 +0000
@@ -1,4 +1,14 @@
-------------------------------------------------------------------
+Wed Oct 30 11:17:43 UTC 2019 - mvo@ubuntu.com
+
+- Update to upstream release 2.42.1
+
+-------------------------------------------------------------------
+Tue Oct 01 09:42:48 UTC 2019 - mvo@ubuntu.com
+
+- Update to upstream release 2.42
+
+-------------------------------------------------------------------
Fri Aug 30 06:55:43 UTC 2019 - mvo@ubuntu.com
- Update to upstream release 2.41
diff -Nru snapd-2.41+19.10.1/packaging/opensuse-tumbleweed/snapd.spec snapd-2.42.1+19.10/packaging/opensuse-tumbleweed/snapd.spec
--- snapd-2.41+19.10.1/packaging/opensuse-tumbleweed/snapd.spec 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/opensuse-tumbleweed/snapd.spec 2019-10-30 12:17:43.000000000 +0000
@@ -77,7 +77,7 @@
Name: snapd
-Version: 2.41
+Version: 2.42.1
Release: 0
Summary: Tools enabling systems to work with .snap files
License: GPL-3.0
diff -Nru snapd-2.41+19.10.1/packaging/pack-source snapd-2.42.1+19.10/packaging/pack-source
--- snapd-2.41+19.10.1/packaging/pack-source 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/pack-source 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,63 @@
+#!/bin/bash
+
+set -e
+
+show_help() {
+ echo "usage: $(basename "$0") [-v ] [-o ] [-g] [-h]"
+ echo " -v set version"
+ echo " -o write source packages to this directory"
+ echo " -g use 'git describe' output as version string"
+ echo " -s use single source archive instead of no-vendor and"
+ echo " only-vendor packages"
+ echo " -h show help"
+ exit 1
+}
+
+if [ ! -e "packaging/$(basename "$0")" ]; then
+ echo "must be executed at the top of srcdir"
+ exit 1
+fi
+
+outdir=.
+single=0
+
+while getopts "v:o:gsh" arg; do
+ case "$arg" in
+ o)
+ outdir="$OPTARG"
+ ;;
+ v)
+ version="$OPTARG"
+ ;;
+ g)
+ version="$(git describe | tr '-' '.')"
+ ;;
+ s)
+ single=1
+ ;;
+ h|*)
+ show_help
+ ;;
+ esac
+done
+
+if [ -z "$version" ]; then
+ echo "error: version is unset"
+ exit 1
+fi
+
+set -x
+
+tmpdir=$(mktemp -d)
+trap 'rm -rf "$tmpdir"' EXIT
+
+if [[ "$single" == 0 ]]; then
+ tar -cJf "$tmpdir"/snapd_"$version".no-vendor.tar.xz --exclude='vendor/*' --exclude='.git/*' --transform "s#^#snapd-$version/#" .
+ tar -cJf "$tmpdir"/snapd_"$version".only-vendor.tar.xz --exclude='.git/*' --transform "s#^#snapd-$version/#" vendor
+
+ mv "$tmpdir"/snapd_"$version".no-vendor.tar.xz "$outdir"/
+ mv "$tmpdir"/snapd_"$version".only-vendor.tar.xz "$outdir"/
+else
+ tar -cJf "$tmpdir"/snapd_"$version".vendor.tar.xz --exclude='.git/*' --transform "s#^#snapd-$version/#" .
+ mv "$tmpdir"/snapd_"$version".vendor.tar.xz "$outdir"/
+fi
diff -Nru snapd-2.41+19.10.1/packaging/ubuntu-14.04/changelog snapd-2.42.1+19.10/packaging/ubuntu-14.04/changelog
--- snapd-2.41+19.10.1/packaging/ubuntu-14.04/changelog 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/ubuntu-14.04/changelog 2019-10-30 12:17:43.000000000 +0000
@@ -1,3 +1,246 @@
+snapd (2.42.1~14.04) trusty; urgency=medium
+
+ * New upstream release, LP: #1846181
+ - interfaces: de-duplicate emitted update-ns profiles
+ - packaging: tweak handling of usr.lib.snapd.snap-confine
+ - interfaces: allow introspecting network-manager on core
+ - tests/main/interfaces-contacts-service: disable on openSUSE
+ Tumbleweed
+ - tests/lib/lxd-snapfuse: restore mount changes introduced by LXD
+ - snap: fix default-provider in seed validation
+ - tests: update system-usernames test now that opensuse-15.1 works
+ - overlord: set fake sertial in TestRemodelSwitchToDifferentKernel
+ - gadget: rename "boot{select,img}" -> system-boot-{select,image}
+ - tests: listing test, make accepted snapd/core versions consistent
+
+ -- Michael Vogt Wed, 30 Oct 2019 13:17:43 +0100
+
+snapd (2.42~14.04) trusty; urgency=medium
+
+ * New upstream release, LP: #1846181
+ - tests: disable {contacts,calendar}-service tests on debian-sid
+ - tests/main/snap-run: disable strace test cases on Arch
+ - cmd/system-shutdown: include correct prototype for die
+ - snap/naming: add test for hook name connect-plug-i2c
+ - cmd/snap-confine: allow digits in hook names
+ - gadget: do not fail the update when old gadget snap is missing
+ bare content
+ - tests: disable {contacts,calendar}-service tests on Arch Linux
+ - tests: move "centos-7" to unstable systems
+ - interfaces/docker-support,kubernetes-support: misc updates for
+ strict k8s
+ - packaging: remove obsolete usr.lib.snapd.snap-confine in
+ postinst
+ - tests: add test that ensures our snapfuse binary actually works
+ - packaging: use snapfuse_ll to speed up snapfuse performance
+ - usersession/userd: make sure to export DBus interfaces before
+ requesting a name
+ - data/selinux: allow snapd to issue sigkill to journalctl
+ - store: download propagates options to delta download
+ - wrappers: allow snaps to install icon theme icons
+ - debug: state-inspect debugging utility
+ - sandbox/cgroup: introduce cgroup wrappers package
+ - snap-confine: fix return value checks for udev functions
+ - cmd/model: output tweaks, add'l tests
+ - wrappers/services: add ServicesEnableState + unit tests
+ - tests: fix newline and wrong test name pointed out in previous PRs
+ - tests: extend mount-ns test to handle mimics
+ - run-checks, tests/main/go: allow gofmt checks to be skipped on
+ 19.10
+ - tests/main/interfaces-{calendar,contacts}-service: disable on
+ 19.10
+ - tests: part3 making tests work on ubuntu-core-18
+ - tests: fix interfaces-timeserver-control on 19.10
+ - overlord/snapstate: config revision code cleanup and extra tests
+ - devicestate: allow remodel to different kernels
+ - overlord,daemon: adjust startup timeout via EXTEND_TIMEOUT_USEC
+ using an estimate
+ - tests/main/many: increase kill-timeout to 5m
+ - interfaces/kubernetes-support: allow systemd-run to ptrace read
+ unconfined
+ - snapstate: auto transition on experimental.snapd-snap=true
+ - tests: retry checking until the written file on desktop-portal-
+ filechooser
+ - tests: unit test for a refresh failing on configure hook
+ - tests: remove mount_id and parent_id from mount-ns test data
+ - tests: move classic-ubuntu-core-transition* to nightly
+ - tests/mountinfo-tool: proper formatting of opt_fields
+ - overlord/configstate: special-case "null" in transaction Changes()
+ - snap-confine: fallback gracefully on a cgroup v2 only system
+ - tests: debian sid now ships new seccomp, adjust tests
+ - tests: explicitly restore after using LXD
+ - snapstate: make progress reporting less granular
+ - bootloader: little kernel support
+ - fixme: rename ubuntu*architectures to dpkg*architectures
+ - tests: run dbus-launch inside a systemd unit
+ - channel: introduce Resolve and ResolveLocked
+ - tests: run failing tests on ubuntu eoan due to is now set as
+ unstable
+ - systemd: detach rather than unmount .mount units
+ - cmd/snap-confine: add unit tests for sc_invocation, cleanup memory
+ leaks in tests
+ - boot,dirs,image: introduce boot.MakeBootable, use it in image
+ instead of ad hoc code
+ - cmd/snap-update-ns: clarify sharing comment
+ - tests/overlord/snapstate: refactor for cleaner test failures
+ - cmd/snap-update-ns: don't propagate detaching changes
+ - interfaces: allow reading mutter Xauthority file
+ - cmd/snap-confine: fix /snap duplication in legacy mode
+ - tests: fix mountinfo-tool filtering when used with rewriting
+ - seed,image,o/devicestate: extract seed loading to seed/seed16.go
+ - many: pass the rootdir and options to bootloader.Find
+ - tests: part5 making tests work on ubuntu-core-18
+ - cmd/snap-confine: keep track of snap instance name and the snap
+ name
+ - cmd: unify die() across C programs
+ - tests: add functions to make an abstraction for the snaps
+ - packaging/fedora, tests/lib/prepare-restore: helper tool for
+ packing sources for RPM
+ - cmd/snap: improve help and error msg for snapshot commands
+ - hookstate/ctlcmd: fix snapctl set help message
+ - cmd/snap: don't append / to snap name just because a dir exists
+ - tests: support fastly-global.cdn.snapcraft.io url on proxy-no-core
+ test
+ - tests: add --quiet switch to retry-tool
+ - tests: add unstable stage for travis execution
+ - tests: disable interfaces-timeserver-control on 19.10
+ - tests: don't guess in is_classic_confinement_supported
+ - boot, etc: simplify BootParticipant (etc) usage
+ - tests: verify retry-tool not retrying missing commands
+ - tests: rewrite "retry" command as retry-tool
+ - tests: move debug section after restore
+ - cmd/libsnap-confine-private, cmd/s-c: use constants for
+ snap/instance name lengths
+ - tests: measure behavior of the device cgroup
+ - boot, bootloader, o/devicestate: boot env manip goes in boot
+ - tests: enabling ubuntu 19.10-64 on spread.yaml
+ - tests: fix ephemeral mount table in left over by prepare
+ - tests: add version-tool for comparing versions
+ - cmd/libsnap: make feature flag enum 1< Tue, 01 Oct 2019 11:40:34 +0200
+
snapd (2.41~14.04) trusty; urgency=medium
* New upstream release, LP: #1840740
diff -Nru snapd-2.41+19.10.1/packaging/ubuntu-14.04/snapd.maintscript snapd-2.42.1+19.10/packaging/ubuntu-14.04/snapd.maintscript
--- snapd-2.41+19.10.1/packaging/ubuntu-14.04/snapd.maintscript 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/ubuntu-14.04/snapd.maintscript 2019-10-30 12:17:43.000000000 +0000
@@ -1,5 +1,5 @@
-# keep mount point busy
# we used to ship a custom grub config that is no longer needed
rm_conffile /etc/grub.d/09_snappy 1.7.3ubuntu1
rm_conffile /etc/ld.so.conf.d/snappy.conf 2.0.7~
-rm_conffile /etc/apparmor.d/usr.lib.snapd.snap-confine 2.23.6~ snap-confine
+# on trusty /etc/apparmor.d/usr.lib.snapd.snap-confine was never renamed
+# so we don't need to handle this file here (unlike in xenial+)
diff -Nru snapd-2.41+19.10.1/packaging/ubuntu-16.04/changelog snapd-2.42.1+19.10/packaging/ubuntu-16.04/changelog
--- snapd-2.41+19.10.1/packaging/ubuntu-16.04/changelog 2019-08-30 09:42:43.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/ubuntu-16.04/changelog 2019-10-30 12:17:43.000000000 +0000
@@ -1,10 +1,247 @@
-snapd (2.41+19.10.1) eoan; urgency=medium
+snapd (2.42.1+19.10) eoan; urgency=medium
- * cherry-pick https://github.com/snapcore/snapd/pull/7380
+ * New upstream release, LP: #1846181
+ - interfaces: de-duplicate emitted update-ns profiles
+ - packaging: tweak handling of usr.lib.snapd.snap-confine
+ - interfaces: allow introspecting network-manager on core
+ - tests/main/interfaces-contacts-service: disable on openSUSE
+ Tumbleweed
+ - tests/lib/lxd-snapfuse: restore mount changes introduced by LXD
+ - snap: fix default-provider in seed validation
+ - tests: update system-usernames test now that opensuse-15.1 works
+ - overlord: set fake sertial in TestRemodelSwitchToDifferentKernel
+ - gadget: rename "boot{select,img}" -> system-boot-{select,image}
+ - tests: listing test, make accepted snapd/core versions consistent
- -- Michael Vogt Fri, 30 Aug 2019 11:42:43 +0200
+ -- Michael Vogt Wed, 30 Oct 2019 13:17:43 +0100
-snapd (2.41+19.10) eoan; urgency=medium
+snapd (2.42) xenial; urgency=medium
+
+ * New upstream release, LP: #1846181
+ - tests: disable {contacts,calendar}-service tests on debian-sid
+ - tests/main/snap-run: disable strace test cases on Arch
+ - cmd/system-shutdown: include correct prototype for die
+ - snap/naming: add test for hook name connect-plug-i2c
+ - cmd/snap-confine: allow digits in hook names
+ - gadget: do not fail the update when old gadget snap is missing
+ bare content
+ - tests: disable {contacts,calendar}-service tests on Arch Linux
+ - tests: move "centos-7" to unstable systems
+ - interfaces/docker-support,kubernetes-support: misc updates for
+ strict k8s
+ - packaging: remove obsolete usr.lib.snapd.snap-confine in
+ postinst
+ - tests: add test that ensures our snapfuse binary actually works
+ - packaging: use snapfuse_ll to speed up snapfuse performance
+ - usersession/userd: make sure to export DBus interfaces before
+ requesting a name
+ - data/selinux: allow snapd to issue sigkill to journalctl
+ - store: download propagates options to delta download
+ - wrappers: allow snaps to install icon theme icons
+ - debug: state-inspect debugging utility
+ - sandbox/cgroup: introduce cgroup wrappers package
+ - snap-confine: fix return value checks for udev functions
+ - cmd/model: output tweaks, add'l tests
+ - wrappers/services: add ServicesEnableState + unit tests
+ - tests: fix newline and wrong test name pointed out in previous PRs
+ - tests: extend mount-ns test to handle mimics
+ - run-checks, tests/main/go: allow gofmt checks to be skipped on
+ 19.10
+ - tests/main/interfaces-{calendar,contacts}-service: disable on
+ 19.10
+ - tests: part3 making tests work on ubuntu-core-18
+ - tests: fix interfaces-timeserver-control on 19.10
+ - overlord/snapstate: config revision code cleanup and extra tests
+ - devicestate: allow remodel to different kernels
+ - overlord,daemon: adjust startup timeout via EXTEND_TIMEOUT_USEC
+ using an estimate
+ - tests/main/many: increase kill-timeout to 5m
+ - interfaces/kubernetes-support: allow systemd-run to ptrace read
+ unconfined
+ - snapstate: auto transition on experimental.snapd-snap=true
+ - tests: retry checking until the written file on desktop-portal-
+ filechooser
+ - tests: unit test for a refresh failing on configure hook
+ - tests: remove mount_id and parent_id from mount-ns test data
+ - tests: move classic-ubuntu-core-transition* to nightly
+ - tests/mountinfo-tool: proper formatting of opt_fields
+ - overlord/configstate: special-case "null" in transaction Changes()
+ - snap-confine: fallback gracefully on a cgroup v2 only system
+ - tests: debian sid now ships new seccomp, adjust tests
+ - tests: explicitly restore after using LXD
+ - snapstate: make progress reporting less granular
+ - bootloader: little kernel support
+ - fixme: rename ubuntu*architectures to dpkg*architectures
+ - tests: run dbus-launch inside a systemd unit
+ - channel: introduce Resolve and ResolveLocked
+ - tests: run failing tests on ubuntu eoan due to is now set as
+ unstable
+ - systemd: detach rather than unmount .mount units
+ - cmd/snap-confine: add unit tests for sc_invocation, cleanup memory
+ leaks in tests
+ - boot,dirs,image: introduce boot.MakeBootable, use it in image
+ instead of ad hoc code
+ - cmd/snap-update-ns: clarify sharing comment
+ - tests/overlord/snapstate: refactor for cleaner test failures
+ - cmd/snap-update-ns: don't propagate detaching changes
+ - interfaces: allow reading mutter Xauthority file
+ - cmd/snap-confine: fix /snap duplication in legacy mode
+ - tests: fix mountinfo-tool filtering when used with rewriting
+ - seed,image,o/devicestate: extract seed loading to seed/seed16.go
+ - many: pass the rootdir and options to bootloader.Find
+ - tests: part5 making tests work on ubuntu-core-18
+ - cmd/snap-confine: keep track of snap instance name and the snap
+ name
+ - cmd: unify die() across C programs
+ - tests: add functions to make an abstraction for the snaps
+ - packaging/fedora, tests/lib/prepare-restore: helper tool for
+ packing sources for RPM
+ - cmd/snap: improve help and error msg for snapshot commands
+ - hookstate/ctlcmd: fix snapctl set help message
+ - cmd/snap: don't append / to snap name just because a dir exists
+ - tests: support fastly-global.cdn.snapcraft.io url on proxy-no-core
+ test
+ - tests: add --quiet switch to retry-tool
+ - tests: add unstable stage for travis execution
+ - tests: disable interfaces-timeserver-control on 19.10
+ - tests: don't guess in is_classic_confinement_supported
+ - boot, etc: simplify BootParticipant (etc) usage
+ - tests: verify retry-tool not retrying missing commands
+ - tests: rewrite "retry" command as retry-tool
+ - tests: move debug section after restore
+ - cmd/libsnap-confine-private, cmd/s-c: use constants for
+ snap/instance name lengths
+ - tests: measure behavior of the device cgroup
+ - boot, bootloader, o/devicestate: boot env manip goes in boot
+ - tests: enabling ubuntu 19.10-64 on spread.yaml
+ - tests: fix ephemeral mount table in left over by prepare
+ - tests: add version-tool for comparing versions
+ - cmd/libsnap: make feature flag enum 1< Tue, 01 Oct 2019 11:24:41 +0200
+
+snapd (2.41) xenial; urgency=medium
* New upstream release, LP: #1840740
- overlord/snapstate: revert track-risk behavior
diff -Nru snapd-2.41+19.10.1/packaging/ubuntu-16.04/rules snapd-2.42.1+19.10/packaging/ubuntu-16.04/rules
--- snapd-2.41+19.10.1/packaging/ubuntu-16.04/rules 2019-08-30 09:42:43.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/ubuntu-16.04/rules 2019-10-30 12:17:43.000000000 +0000
@@ -168,9 +168,9 @@
# Generate static snap-exec, snapctl and snap-update-ns - it somehow includes CGO so
# we must force a static build here. We need a static snap-{exec,update-ns}/snapctl
# inside the core snap because not all bases will have a libc
- (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=$$(pwd)/../go-build CGO_ENABLED=0 go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snap-exec)
- (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=$$(pwd)/../go-build CGO_ENABLED=0 go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snapctl)
- (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=$$(pwd)/../go-build go build --ldflags '-extldflags "-static"' $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snap-update-ns)
+ (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=/tmp/go-build CGO_ENABLED=0 go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snap-exec)
+ (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=/tmp/go-build CGO_ENABLED=0 go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snapctl)
+ (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=/tmp/go-build 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)
@@ -180,7 +180,7 @@
# ensure snap-seccomp is build with a static libseccomp on Ubuntu
ifeq ($(shell dpkg-vendor --query Vendor),Ubuntu)
sed -i "s|#cgo LDFLAGS:|#cgo LDFLAGS: /usr/lib/$(shell dpkg-architecture -qDEB_TARGET_MULTIARCH)/libseccomp.a|" _build/src/$(DH_GOPKG)/cmd/snap-seccomp/main.go
- (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=$$(pwd)/../go-build CGO_LDFLAGS_ALLOW="/.*/libseccomp.a" go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snap-seccomp)
+ (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=/tmp/go-build CGO_LDFLAGS_ALLOW="/.*/libseccomp.a" go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snap-seccomp)
# ensure that libseccomp is not dynamically linked
ldd _build/bin/snap-seccomp
test "$$(ldd _build/bin/snap-seccomp | grep libseccomp)" = ""
@@ -197,7 +197,7 @@
$(MAKE) -C data all
# build squashfuse and rename to snapfuse
- (cd vendor/github.com/snapcore/squashfuse/src && mkdir -p autom4te.cache && ./autogen.sh --disable-demo && ./configure --disable-demo && make && mv squashfuse snapfuse)
+ (cd vendor/github.com/snapcore/squashfuse/src && mkdir -p autom4te.cache && ./autogen.sh --disable-demo && ./configure --disable-demo && make && mv squashfuse_ll snapfuse)
override_dh_auto_test:
dh_auto_test -- $(GCCGOFLAGS)
diff -Nru snapd-2.41+19.10.1/packaging/ubuntu-16.04/snapd.maintscript snapd-2.42.1+19.10/packaging/ubuntu-16.04/snapd.maintscript
--- snapd-2.41+19.10.1/packaging/ubuntu-16.04/snapd.maintscript 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/ubuntu-16.04/snapd.maintscript 2019-10-30 12:17:43.000000000 +0000
@@ -2,4 +2,4 @@
# we used to ship a custom grub config that is no longer needed
rm_conffile /etc/grub.d/09_snappy 1.7.3ubuntu1
rm_conffile /etc/ld.so.conf.d/snappy.conf 2.0.7~
-rm_conffile /etc/apparmor.d/usr.lib.snapd.snap-confine 2.23.6~ snap-confine
+rm_conffile /etc/apparmor.d/usr.lib.snapd.snap-confine 2.23.6~
diff -Nru snapd-2.41+19.10.1/packaging/ubuntu-16.04/snapd.postinst snapd-2.42.1+19.10/packaging/ubuntu-16.04/snapd.postinst
--- snapd-2.41+19.10.1/packaging/ubuntu-16.04/snapd.postinst 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/ubuntu-16.04/snapd.postinst 2019-10-30 12:17:43.000000000 +0000
@@ -41,12 +41,12 @@
# In commit 0dce4704a5d (2017-03-28, snapd v2.23.6) we renamed
# /etc/apparmor.d/usr.lib.snap-confine to usr.lib.snap-confine.real
- # to fix LP: #1673247 - however some people (upgrades?) still have
+ # to fix LP: #1673247 - however some people (developers?) still have
# the old usr.lib.snap-confine file. This seems to be loaded instead
# of the correct usr.lib.snap-confine.real profile. To fix this we
- # use the rather blunt approach to remove the file forcefully.
- if test -f /etc/apparmor.d/usr.lib.snapd.snap-confine && test "$(md5sum /etc/apparmor.d/usr.lib.snapd.snap-confine | cut -f1 -d' ')" = "2a38d40fe662f46fedd0aefbe78f23e9"; then
- rm -f /etc/apparmor.d/usr.lib.snapd.snap-confine
+ # use the rather blunt approach to rename the file forcefully.
+ if test -f /etc/apparmor.d/usr.lib.snapd.snap-confine && test -f /etc/apparmor.d/usr.lib.snapd.snap-confine.real; then
+ mv /etc/apparmor.d/usr.lib.snapd.snap-confine /etc/apparmor.d/usr.lib.snapd.snap-confine.dpkg-bak
fi
# Ensure that the void directory has correct permissions.
diff -Nru snapd-2.41+19.10.1/packaging/ubuntu-16.10/changelog snapd-2.42.1+19.10/packaging/ubuntu-16.10/changelog
--- snapd-2.41+19.10.1/packaging/ubuntu-16.10/changelog 2019-08-30 09:42:43.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/ubuntu-16.10/changelog 2019-10-30 12:17:43.000000000 +0000
@@ -1,10 +1,247 @@
-snapd (2.41+19.10.1) eoan; urgency=medium
+snapd (2.42.1+19.10) eoan; urgency=medium
- * cherry-pick https://github.com/snapcore/snapd/pull/7380
+ * New upstream release, LP: #1846181
+ - interfaces: de-duplicate emitted update-ns profiles
+ - packaging: tweak handling of usr.lib.snapd.snap-confine
+ - interfaces: allow introspecting network-manager on core
+ - tests/main/interfaces-contacts-service: disable on openSUSE
+ Tumbleweed
+ - tests/lib/lxd-snapfuse: restore mount changes introduced by LXD
+ - snap: fix default-provider in seed validation
+ - tests: update system-usernames test now that opensuse-15.1 works
+ - overlord: set fake sertial in TestRemodelSwitchToDifferentKernel
+ - gadget: rename "boot{select,img}" -> system-boot-{select,image}
+ - tests: listing test, make accepted snapd/core versions consistent
- -- Michael Vogt Fri, 30 Aug 2019 11:42:43 +0200
+ -- Michael Vogt Wed, 30 Oct 2019 13:17:43 +0100
-snapd (2.41+19.10) eoan; urgency=medium
+snapd (2.42) xenial; urgency=medium
+
+ * New upstream release, LP: #1846181
+ - tests: disable {contacts,calendar}-service tests on debian-sid
+ - tests/main/snap-run: disable strace test cases on Arch
+ - cmd/system-shutdown: include correct prototype for die
+ - snap/naming: add test for hook name connect-plug-i2c
+ - cmd/snap-confine: allow digits in hook names
+ - gadget: do not fail the update when old gadget snap is missing
+ bare content
+ - tests: disable {contacts,calendar}-service tests on Arch Linux
+ - tests: move "centos-7" to unstable systems
+ - interfaces/docker-support,kubernetes-support: misc updates for
+ strict k8s
+ - packaging: remove obsolete usr.lib.snapd.snap-confine in
+ postinst
+ - tests: add test that ensures our snapfuse binary actually works
+ - packaging: use snapfuse_ll to speed up snapfuse performance
+ - usersession/userd: make sure to export DBus interfaces before
+ requesting a name
+ - data/selinux: allow snapd to issue sigkill to journalctl
+ - store: download propagates options to delta download
+ - wrappers: allow snaps to install icon theme icons
+ - debug: state-inspect debugging utility
+ - sandbox/cgroup: introduce cgroup wrappers package
+ - snap-confine: fix return value checks for udev functions
+ - cmd/model: output tweaks, add'l tests
+ - wrappers/services: add ServicesEnableState + unit tests
+ - tests: fix newline and wrong test name pointed out in previous PRs
+ - tests: extend mount-ns test to handle mimics
+ - run-checks, tests/main/go: allow gofmt checks to be skipped on
+ 19.10
+ - tests/main/interfaces-{calendar,contacts}-service: disable on
+ 19.10
+ - tests: part3 making tests work on ubuntu-core-18
+ - tests: fix interfaces-timeserver-control on 19.10
+ - overlord/snapstate: config revision code cleanup and extra tests
+ - devicestate: allow remodel to different kernels
+ - overlord,daemon: adjust startup timeout via EXTEND_TIMEOUT_USEC
+ using an estimate
+ - tests/main/many: increase kill-timeout to 5m
+ - interfaces/kubernetes-support: allow systemd-run to ptrace read
+ unconfined
+ - snapstate: auto transition on experimental.snapd-snap=true
+ - tests: retry checking until the written file on desktop-portal-
+ filechooser
+ - tests: unit test for a refresh failing on configure hook
+ - tests: remove mount_id and parent_id from mount-ns test data
+ - tests: move classic-ubuntu-core-transition* to nightly
+ - tests/mountinfo-tool: proper formatting of opt_fields
+ - overlord/configstate: special-case "null" in transaction Changes()
+ - snap-confine: fallback gracefully on a cgroup v2 only system
+ - tests: debian sid now ships new seccomp, adjust tests
+ - tests: explicitly restore after using LXD
+ - snapstate: make progress reporting less granular
+ - bootloader: little kernel support
+ - fixme: rename ubuntu*architectures to dpkg*architectures
+ - tests: run dbus-launch inside a systemd unit
+ - channel: introduce Resolve and ResolveLocked
+ - tests: run failing tests on ubuntu eoan due to is now set as
+ unstable
+ - systemd: detach rather than unmount .mount units
+ - cmd/snap-confine: add unit tests for sc_invocation, cleanup memory
+ leaks in tests
+ - boot,dirs,image: introduce boot.MakeBootable, use it in image
+ instead of ad hoc code
+ - cmd/snap-update-ns: clarify sharing comment
+ - tests/overlord/snapstate: refactor for cleaner test failures
+ - cmd/snap-update-ns: don't propagate detaching changes
+ - interfaces: allow reading mutter Xauthority file
+ - cmd/snap-confine: fix /snap duplication in legacy mode
+ - tests: fix mountinfo-tool filtering when used with rewriting
+ - seed,image,o/devicestate: extract seed loading to seed/seed16.go
+ - many: pass the rootdir and options to bootloader.Find
+ - tests: part5 making tests work on ubuntu-core-18
+ - cmd/snap-confine: keep track of snap instance name and the snap
+ name
+ - cmd: unify die() across C programs
+ - tests: add functions to make an abstraction for the snaps
+ - packaging/fedora, tests/lib/prepare-restore: helper tool for
+ packing sources for RPM
+ - cmd/snap: improve help and error msg for snapshot commands
+ - hookstate/ctlcmd: fix snapctl set help message
+ - cmd/snap: don't append / to snap name just because a dir exists
+ - tests: support fastly-global.cdn.snapcraft.io url on proxy-no-core
+ test
+ - tests: add --quiet switch to retry-tool
+ - tests: add unstable stage for travis execution
+ - tests: disable interfaces-timeserver-control on 19.10
+ - tests: don't guess in is_classic_confinement_supported
+ - boot, etc: simplify BootParticipant (etc) usage
+ - tests: verify retry-tool not retrying missing commands
+ - tests: rewrite "retry" command as retry-tool
+ - tests: move debug section after restore
+ - cmd/libsnap-confine-private, cmd/s-c: use constants for
+ snap/instance name lengths
+ - tests: measure behavior of the device cgroup
+ - boot, bootloader, o/devicestate: boot env manip goes in boot
+ - tests: enabling ubuntu 19.10-64 on spread.yaml
+ - tests: fix ephemeral mount table in left over by prepare
+ - tests: add version-tool for comparing versions
+ - cmd/libsnap: make feature flag enum 1< Tue, 01 Oct 2019 11:24:41 +0200
+
+snapd (2.41) xenial; urgency=medium
* New upstream release, LP: #1840740
- overlord/snapstate: revert track-risk behavior
diff -Nru snapd-2.41+19.10.1/packaging/ubuntu-16.10/rules snapd-2.42.1+19.10/packaging/ubuntu-16.10/rules
--- snapd-2.41+19.10.1/packaging/ubuntu-16.10/rules 2019-08-30 09:42:43.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/ubuntu-16.10/rules 2019-10-30 12:17:43.000000000 +0000
@@ -168,9 +168,9 @@
# Generate static snap-exec, snapctl and snap-update-ns - it somehow includes CGO so
# we must force a static build here. We need a static snap-{exec,update-ns}/snapctl
# inside the core snap because not all bases will have a libc
- (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=$$(pwd)/../go-build CGO_ENABLED=0 go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snap-exec)
- (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=$$(pwd)/../go-build CGO_ENABLED=0 go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snapctl)
- (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=$$(pwd)/../go-build go build --ldflags '-extldflags "-static"' $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snap-update-ns)
+ (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=/tmp/go-build CGO_ENABLED=0 go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snap-exec)
+ (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=/tmp/go-build CGO_ENABLED=0 go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snapctl)
+ (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=/tmp/go-build 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)
@@ -180,7 +180,7 @@
# ensure snap-seccomp is build with a static libseccomp on Ubuntu
ifeq ($(shell dpkg-vendor --query Vendor),Ubuntu)
sed -i "s|#cgo LDFLAGS:|#cgo LDFLAGS: /usr/lib/$(shell dpkg-architecture -qDEB_TARGET_MULTIARCH)/libseccomp.a|" _build/src/$(DH_GOPKG)/cmd/snap-seccomp/main.go
- (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=$$(pwd)/../go-build CGO_LDFLAGS_ALLOW="/.*/libseccomp.a" go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snap-seccomp)
+ (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=/tmp/go-build CGO_LDFLAGS_ALLOW="/.*/libseccomp.a" go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snap-seccomp)
# ensure that libseccomp is not dynamically linked
ldd _build/bin/snap-seccomp
test "$$(ldd _build/bin/snap-seccomp | grep libseccomp)" = ""
@@ -197,7 +197,7 @@
$(MAKE) -C data all
# build squashfuse and rename to snapfuse
- (cd vendor/github.com/snapcore/squashfuse/src && mkdir -p autom4te.cache && ./autogen.sh --disable-demo && ./configure --disable-demo && make && mv squashfuse snapfuse)
+ (cd vendor/github.com/snapcore/squashfuse/src && mkdir -p autom4te.cache && ./autogen.sh --disable-demo && ./configure --disable-demo && make && mv squashfuse_ll snapfuse)
override_dh_auto_test:
dh_auto_test -- $(GCCGOFLAGS)
diff -Nru snapd-2.41+19.10.1/packaging/ubuntu-16.10/snapd.maintscript snapd-2.42.1+19.10/packaging/ubuntu-16.10/snapd.maintscript
--- snapd-2.41+19.10.1/packaging/ubuntu-16.10/snapd.maintscript 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/ubuntu-16.10/snapd.maintscript 2019-10-30 12:17:43.000000000 +0000
@@ -2,4 +2,4 @@
# we used to ship a custom grub config that is no longer needed
rm_conffile /etc/grub.d/09_snappy 1.7.3ubuntu1
rm_conffile /etc/ld.so.conf.d/snappy.conf 2.0.7~
-rm_conffile /etc/apparmor.d/usr.lib.snapd.snap-confine 2.23.6~ snap-confine
+rm_conffile /etc/apparmor.d/usr.lib.snapd.snap-confine 2.23.6~
diff -Nru snapd-2.41+19.10.1/packaging/ubuntu-16.10/snapd.postinst snapd-2.42.1+19.10/packaging/ubuntu-16.10/snapd.postinst
--- snapd-2.41+19.10.1/packaging/ubuntu-16.10/snapd.postinst 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/ubuntu-16.10/snapd.postinst 2019-10-30 12:17:43.000000000 +0000
@@ -41,12 +41,12 @@
# In commit 0dce4704a5d (2017-03-28, snapd v2.23.6) we renamed
# /etc/apparmor.d/usr.lib.snap-confine to usr.lib.snap-confine.real
- # to fix LP: #1673247 - however some people (upgrades?) still have
+ # to fix LP: #1673247 - however some people (developers?) still have
# the old usr.lib.snap-confine file. This seems to be loaded instead
# of the correct usr.lib.snap-confine.real profile. To fix this we
- # use the rather blunt approach to remove the file forcefully.
- if test -f /etc/apparmor.d/usr.lib.snapd.snap-confine && test "$(md5sum /etc/apparmor.d/usr.lib.snapd.snap-confine | cut -f1 -d' ')" = "2a38d40fe662f46fedd0aefbe78f23e9"; then
- rm -f /etc/apparmor.d/usr.lib.snapd.snap-confine
+ # use the rather blunt approach to rename the file forcefully.
+ if test -f /etc/apparmor.d/usr.lib.snapd.snap-confine && test -f /etc/apparmor.d/usr.lib.snapd.snap-confine.real; then
+ mv /etc/apparmor.d/usr.lib.snapd.snap-confine /etc/apparmor.d/usr.lib.snapd.snap-confine.dpkg-bak
fi
# Ensure that the void directory has correct permissions.
diff -Nru snapd-2.41+19.10.1/packaging/ubuntu-17.04/changelog snapd-2.42.1+19.10/packaging/ubuntu-17.04/changelog
--- snapd-2.41+19.10.1/packaging/ubuntu-17.04/changelog 2019-08-30 09:42:43.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/ubuntu-17.04/changelog 2019-10-30 12:17:43.000000000 +0000
@@ -1,10 +1,247 @@
-snapd (2.41+19.10.1) eoan; urgency=medium
+snapd (2.42.1+19.10) eoan; urgency=medium
- * cherry-pick https://github.com/snapcore/snapd/pull/7380
+ * New upstream release, LP: #1846181
+ - interfaces: de-duplicate emitted update-ns profiles
+ - packaging: tweak handling of usr.lib.snapd.snap-confine
+ - interfaces: allow introspecting network-manager on core
+ - tests/main/interfaces-contacts-service: disable on openSUSE
+ Tumbleweed
+ - tests/lib/lxd-snapfuse: restore mount changes introduced by LXD
+ - snap: fix default-provider in seed validation
+ - tests: update system-usernames test now that opensuse-15.1 works
+ - overlord: set fake sertial in TestRemodelSwitchToDifferentKernel
+ - gadget: rename "boot{select,img}" -> system-boot-{select,image}
+ - tests: listing test, make accepted snapd/core versions consistent
- -- Michael Vogt Fri, 30 Aug 2019 11:42:43 +0200
+ -- Michael Vogt Wed, 30 Oct 2019 13:17:43 +0100
-snapd (2.41+19.10) eoan; urgency=medium
+snapd (2.42) xenial; urgency=medium
+
+ * New upstream release, LP: #1846181
+ - tests: disable {contacts,calendar}-service tests on debian-sid
+ - tests/main/snap-run: disable strace test cases on Arch
+ - cmd/system-shutdown: include correct prototype for die
+ - snap/naming: add test for hook name connect-plug-i2c
+ - cmd/snap-confine: allow digits in hook names
+ - gadget: do not fail the update when old gadget snap is missing
+ bare content
+ - tests: disable {contacts,calendar}-service tests on Arch Linux
+ - tests: move "centos-7" to unstable systems
+ - interfaces/docker-support,kubernetes-support: misc updates for
+ strict k8s
+ - packaging: remove obsolete usr.lib.snapd.snap-confine in
+ postinst
+ - tests: add test that ensures our snapfuse binary actually works
+ - packaging: use snapfuse_ll to speed up snapfuse performance
+ - usersession/userd: make sure to export DBus interfaces before
+ requesting a name
+ - data/selinux: allow snapd to issue sigkill to journalctl
+ - store: download propagates options to delta download
+ - wrappers: allow snaps to install icon theme icons
+ - debug: state-inspect debugging utility
+ - sandbox/cgroup: introduce cgroup wrappers package
+ - snap-confine: fix return value checks for udev functions
+ - cmd/model: output tweaks, add'l tests
+ - wrappers/services: add ServicesEnableState + unit tests
+ - tests: fix newline and wrong test name pointed out in previous PRs
+ - tests: extend mount-ns test to handle mimics
+ - run-checks, tests/main/go: allow gofmt checks to be skipped on
+ 19.10
+ - tests/main/interfaces-{calendar,contacts}-service: disable on
+ 19.10
+ - tests: part3 making tests work on ubuntu-core-18
+ - tests: fix interfaces-timeserver-control on 19.10
+ - overlord/snapstate: config revision code cleanup and extra tests
+ - devicestate: allow remodel to different kernels
+ - overlord,daemon: adjust startup timeout via EXTEND_TIMEOUT_USEC
+ using an estimate
+ - tests/main/many: increase kill-timeout to 5m
+ - interfaces/kubernetes-support: allow systemd-run to ptrace read
+ unconfined
+ - snapstate: auto transition on experimental.snapd-snap=true
+ - tests: retry checking until the written file on desktop-portal-
+ filechooser
+ - tests: unit test for a refresh failing on configure hook
+ - tests: remove mount_id and parent_id from mount-ns test data
+ - tests: move classic-ubuntu-core-transition* to nightly
+ - tests/mountinfo-tool: proper formatting of opt_fields
+ - overlord/configstate: special-case "null" in transaction Changes()
+ - snap-confine: fallback gracefully on a cgroup v2 only system
+ - tests: debian sid now ships new seccomp, adjust tests
+ - tests: explicitly restore after using LXD
+ - snapstate: make progress reporting less granular
+ - bootloader: little kernel support
+ - fixme: rename ubuntu*architectures to dpkg*architectures
+ - tests: run dbus-launch inside a systemd unit
+ - channel: introduce Resolve and ResolveLocked
+ - tests: run failing tests on ubuntu eoan due to is now set as
+ unstable
+ - systemd: detach rather than unmount .mount units
+ - cmd/snap-confine: add unit tests for sc_invocation, cleanup memory
+ leaks in tests
+ - boot,dirs,image: introduce boot.MakeBootable, use it in image
+ instead of ad hoc code
+ - cmd/snap-update-ns: clarify sharing comment
+ - tests/overlord/snapstate: refactor for cleaner test failures
+ - cmd/snap-update-ns: don't propagate detaching changes
+ - interfaces: allow reading mutter Xauthority file
+ - cmd/snap-confine: fix /snap duplication in legacy mode
+ - tests: fix mountinfo-tool filtering when used with rewriting
+ - seed,image,o/devicestate: extract seed loading to seed/seed16.go
+ - many: pass the rootdir and options to bootloader.Find
+ - tests: part5 making tests work on ubuntu-core-18
+ - cmd/snap-confine: keep track of snap instance name and the snap
+ name
+ - cmd: unify die() across C programs
+ - tests: add functions to make an abstraction for the snaps
+ - packaging/fedora, tests/lib/prepare-restore: helper tool for
+ packing sources for RPM
+ - cmd/snap: improve help and error msg for snapshot commands
+ - hookstate/ctlcmd: fix snapctl set help message
+ - cmd/snap: don't append / to snap name just because a dir exists
+ - tests: support fastly-global.cdn.snapcraft.io url on proxy-no-core
+ test
+ - tests: add --quiet switch to retry-tool
+ - tests: add unstable stage for travis execution
+ - tests: disable interfaces-timeserver-control on 19.10
+ - tests: don't guess in is_classic_confinement_supported
+ - boot, etc: simplify BootParticipant (etc) usage
+ - tests: verify retry-tool not retrying missing commands
+ - tests: rewrite "retry" command as retry-tool
+ - tests: move debug section after restore
+ - cmd/libsnap-confine-private, cmd/s-c: use constants for
+ snap/instance name lengths
+ - tests: measure behavior of the device cgroup
+ - boot, bootloader, o/devicestate: boot env manip goes in boot
+ - tests: enabling ubuntu 19.10-64 on spread.yaml
+ - tests: fix ephemeral mount table in left over by prepare
+ - tests: add version-tool for comparing versions
+ - cmd/libsnap: make feature flag enum 1< Tue, 01 Oct 2019 11:24:41 +0200
+
+snapd (2.41) xenial; urgency=medium
* New upstream release, LP: #1840740
- overlord/snapstate: revert track-risk behavior
diff -Nru snapd-2.41+19.10.1/packaging/ubuntu-17.04/rules snapd-2.42.1+19.10/packaging/ubuntu-17.04/rules
--- snapd-2.41+19.10.1/packaging/ubuntu-17.04/rules 2019-08-30 09:42:43.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/ubuntu-17.04/rules 2019-10-30 12:17:43.000000000 +0000
@@ -168,9 +168,9 @@
# Generate static snap-exec, snapctl and snap-update-ns - it somehow includes CGO so
# we must force a static build here. We need a static snap-{exec,update-ns}/snapctl
# inside the core snap because not all bases will have a libc
- (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=$$(pwd)/../go-build CGO_ENABLED=0 go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snap-exec)
- (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=$$(pwd)/../go-build CGO_ENABLED=0 go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snapctl)
- (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=$$(pwd)/../go-build go build --ldflags '-extldflags "-static"' $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snap-update-ns)
+ (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=/tmp/go-build CGO_ENABLED=0 go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snap-exec)
+ (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=/tmp/go-build CGO_ENABLED=0 go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snapctl)
+ (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=/tmp/go-build 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)
@@ -180,7 +180,7 @@
# ensure snap-seccomp is build with a static libseccomp on Ubuntu
ifeq ($(shell dpkg-vendor --query Vendor),Ubuntu)
sed -i "s|#cgo LDFLAGS:|#cgo LDFLAGS: /usr/lib/$(shell dpkg-architecture -qDEB_TARGET_MULTIARCH)/libseccomp.a|" _build/src/$(DH_GOPKG)/cmd/snap-seccomp/main.go
- (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=$$(pwd)/../go-build CGO_LDFLAGS_ALLOW="/.*/libseccomp.a" go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snap-seccomp)
+ (cd _build/bin && GOPATH=$$(pwd)/.. GOCACHE=/tmp/go-build CGO_LDFLAGS_ALLOW="/.*/libseccomp.a" go build $(GCCGOFLAGS) $(DH_GOPKG)/cmd/snap-seccomp)
# ensure that libseccomp is not dynamically linked
ldd _build/bin/snap-seccomp
test "$$(ldd _build/bin/snap-seccomp | grep libseccomp)" = ""
@@ -197,7 +197,7 @@
$(MAKE) -C data all
# build squashfuse and rename to snapfuse
- (cd vendor/github.com/snapcore/squashfuse/src && mkdir -p autom4te.cache && ./autogen.sh --disable-demo && ./configure --disable-demo && make && mv squashfuse snapfuse)
+ (cd vendor/github.com/snapcore/squashfuse/src && mkdir -p autom4te.cache && ./autogen.sh --disable-demo && ./configure --disable-demo && make && mv squashfuse_ll snapfuse)
override_dh_auto_test:
dh_auto_test -- $(GCCGOFLAGS)
diff -Nru snapd-2.41+19.10.1/packaging/ubuntu-17.04/snapd.maintscript snapd-2.42.1+19.10/packaging/ubuntu-17.04/snapd.maintscript
--- snapd-2.41+19.10.1/packaging/ubuntu-17.04/snapd.maintscript 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/ubuntu-17.04/snapd.maintscript 2019-10-30 12:17:43.000000000 +0000
@@ -2,4 +2,4 @@
# we used to ship a custom grub config that is no longer needed
rm_conffile /etc/grub.d/09_snappy 1.7.3ubuntu1
rm_conffile /etc/ld.so.conf.d/snappy.conf 2.0.7~
-rm_conffile /etc/apparmor.d/usr.lib.snapd.snap-confine 2.23.6~ snap-confine
+rm_conffile /etc/apparmor.d/usr.lib.snapd.snap-confine 2.23.6~
diff -Nru snapd-2.41+19.10.1/packaging/ubuntu-17.04/snapd.postinst snapd-2.42.1+19.10/packaging/ubuntu-17.04/snapd.postinst
--- snapd-2.41+19.10.1/packaging/ubuntu-17.04/snapd.postinst 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/packaging/ubuntu-17.04/snapd.postinst 2019-10-30 12:17:43.000000000 +0000
@@ -41,12 +41,12 @@
# In commit 0dce4704a5d (2017-03-28, snapd v2.23.6) we renamed
# /etc/apparmor.d/usr.lib.snap-confine to usr.lib.snap-confine.real
- # to fix LP: #1673247 - however some people (upgrades?) still have
+ # to fix LP: #1673247 - however some people (developers?) still have
# the old usr.lib.snap-confine file. This seems to be loaded instead
# of the correct usr.lib.snap-confine.real profile. To fix this we
- # use the rather blunt approach to remove the file forcefully.
- if test -f /etc/apparmor.d/usr.lib.snapd.snap-confine && test "$(md5sum /etc/apparmor.d/usr.lib.snapd.snap-confine | cut -f1 -d' ')" = "2a38d40fe662f46fedd0aefbe78f23e9"; then
- rm -f /etc/apparmor.d/usr.lib.snapd.snap-confine
+ # use the rather blunt approach to rename the file forcefully.
+ if test -f /etc/apparmor.d/usr.lib.snapd.snap-confine && test -f /etc/apparmor.d/usr.lib.snapd.snap-confine.real; then
+ mv /etc/apparmor.d/usr.lib.snapd.snap-confine /etc/apparmor.d/usr.lib.snapd.snap-confine.dpkg-bak
fi
# Ensure that the void directory has correct permissions.
diff -Nru snapd-2.41+19.10.1/parts/plugins/x_builddeb.py snapd-2.42.1+19.10/parts/plugins/x_builddeb.py
--- snapd-2.41+19.10.1/parts/plugins/x_builddeb.py 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/parts/plugins/x_builddeb.py 2019-10-30 12:17:43.000000000 +0000
@@ -56,6 +56,6 @@
# run the real build
self.run(["dpkg-buildpackage"], env=env)
# and "install" into the right place
- snapd_deb = glob.glob("parts/snapd/snapd_*.deb")[0]
+ snapd_deb = glob.glob(os.path.join(self.partdir, "snapd_*.deb"))[0]
self.run(["dpkg-deb", "-x", os.path.abspath(snapd_deb), self.installdir])
diff -Nru snapd-2.41+19.10.1/README.md snapd-2.42.1+19.10/README.md
--- snapd-2.41+19.10.1/README.md 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/README.md 2019-10-30 12:17:43.000000000 +0000
@@ -12,6 +12,8 @@
Head over to [snapcraft.io](https://snapcraft.io) to get started.
+[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/snapd)
+
## Development
To get started with development off the snapd code itself, please check
diff -Nru snapd-2.41+19.10.1/run-checks snapd-2.42.1+19.10/run-checks
--- snapd-2.41+19.10.1/run-checks 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/run-checks 2019-10-30 12:17:43.000000000 +0000
@@ -63,6 +63,9 @@
--spread-no-ubuntu)
SPREAD=no-ubuntu
;;
+ --spread-unstable)
+ SPREAD=unstable
+ ;;
*)
echo "Wrong flag ${1}. To run a single suite use --static, --unit, --spread."
exit 1
@@ -164,7 +167,11 @@
if [ -n "$fmt" ]; then
echo "Formatting wrong in following files:"
echo "$fmt" | sed -e 's/\\n/\n/g'
- exit 1
+ if [ -z "${SKIP_GOFMT:-}" ]; then
+ exit 1
+ else
+ echo "Ignoring gofmt errors as requested"
+ fi
fi
# go vet
@@ -270,9 +277,11 @@
fi
fi
- # python unit test for mountinfo-tool
+ # python unit test for mountinfo-tool and version-tool
command -v python2 && python2 ./tests/lib/bin/mountinfo-tool --run-unit-tests
command -v python3 && python3 ./tests/lib/bin/mountinfo-tool --run-unit-tests
+ command -v python2 && python2 ./tests/lib/bin/version-tool --run-unit-tests
+ command -v python3 && python3 ./tests/lib/bin/version-tool --run-unit-tests
fi
if [ -n "$SPREAD" ]; then
@@ -292,6 +301,9 @@
no-ubuntu)
spread "google:[^u]...:tests/..."
;;
+ unstable)
+ spread "google-unstable:"
+ ;;
*)
echo "Spread parameter $SPREAD not supported"
exit 1
diff -Nru snapd-2.41+19.10.1/sandbox/cgroup/cgroup.go snapd-2.42.1+19.10/sandbox/cgroup/cgroup.go
--- snapd-2.41+19.10.1/sandbox/cgroup/cgroup.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/sandbox/cgroup/cgroup.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,110 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package cgroup
+
+import (
+ "fmt"
+ "path/filepath"
+ "syscall"
+
+ "github.com/snapcore/snapd/dirs"
+)
+
+const (
+ // From golang.org/x/sys/unix
+ cgroup2SuperMagic = 0x63677270
+
+ // The only cgroup path we expect, for v2 this is where the unified
+ // hierarchy is mounted, for v1 this is usually a tmpfs mount, under
+ // which the controller-hierarchies are mounted
+ expectedMountPoint = "/sys/fs/cgroup"
+)
+
+const (
+ // Separate block, because iota is fun
+ Unknown = iota
+ V1
+ V2
+)
+
+var (
+ probeVersion = Unknown
+ probeErr error = nil
+)
+
+func init() {
+ probeVersion, probeErr = probeCgroupVersion()
+}
+
+var fsTypeForPath = fsTypeForPathImpl
+
+func fsTypeForPathImpl(path string) (int64, error) {
+ var statfs syscall.Statfs_t
+ if err := syscall.Statfs(path, &statfs); err != nil {
+ return 0, fmt.Errorf("cannot statfs path: %v", err)
+ }
+ // Typs is int32 on 386, use explicit conversion to keep the code
+ // working for both
+ return int64(statfs.Type), nil
+}
+
+// ProcPidPath returns the path to the cgroup file under /proc for the given
+// process id.
+func ProcPidPath(pid int) string {
+ return filepath.Join(dirs.GlobalRootDir, fmt.Sprintf("proc/%v/cgroup", pid))
+}
+
+// ControllerPathV1 returns the path to given controller assuming cgroup v1
+// hierarchy
+func ControllerPathV1(controller string) string {
+ return filepath.Join(dirs.GlobalRootDir, expectedMountPoint, controller)
+}
+
+func probeCgroupVersion() (version int, err error) {
+ cgroupMount := filepath.Join(dirs.GlobalRootDir, expectedMountPoint)
+ typ, err := fsTypeForPath(cgroupMount)
+ if err != nil {
+ return Unknown, fmt.Errorf("cannot determine filesystem type: %v", err)
+ }
+ if typ == cgroup2SuperMagic {
+ return V2, nil
+ }
+ return V1, nil
+}
+
+// IsUnified returns true when a unified cgroup hierarchy is in use
+func IsUnified() bool {
+ version, _ := Version()
+ return version == V2
+}
+
+// Version returns the detected cgroup version
+func Version() (int, error) {
+ return probeVersion, probeErr
+}
+
+// MockVersion sets the reported version of cgroup support. For use in testing only
+func MockVersion(mockVersion int, mockErr error) (restore func()) {
+ oldVersion, oldErr := probeVersion, probeErr
+ probeVersion, probeErr = mockVersion, mockErr
+ return func() {
+ probeVersion, probeErr = oldVersion, oldErr
+ }
+}
diff -Nru snapd-2.41+19.10.1/sandbox/cgroup/cgroup_test.go snapd-2.42.1+19.10/sandbox/cgroup/cgroup_test.go
--- snapd-2.41+19.10.1/sandbox/cgroup/cgroup_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/sandbox/cgroup/cgroup_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,124 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+package cgroup_test
+
+import (
+ "errors"
+ "path/filepath"
+ "testing"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/sandbox/cgroup"
+)
+
+type cgroupSuite struct{}
+
+var _ = Suite(&cgroupSuite{})
+
+func TestCgroup(t *testing.T) { TestingT(t) }
+
+func (s *cgroupSuite) TearDownTest(c *C) {
+ dirs.SetRootDir("/")
+}
+
+func (s *cgroupSuite) TestIsUnified(c *C) {
+ restore := cgroup.MockVersion(cgroup.V2, nil)
+ defer restore()
+ c.Assert(cgroup.IsUnified(), Equals, true)
+
+ restore = cgroup.MockVersion(cgroup.V1, nil)
+ defer restore()
+ c.Assert(cgroup.IsUnified(), Equals, false)
+
+ restore = cgroup.MockVersion(cgroup.Unknown, nil)
+ defer restore()
+ c.Assert(cgroup.IsUnified(), Equals, false)
+}
+
+func (s *cgroupSuite) TestProbeVersion2(c *C) {
+ restore := cgroup.MockFsTypeForPath(func(p string) (int64, error) {
+ c.Assert(p, Equals, filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup"))
+ return int64(cgroup.Cgroup2SuperMagic), nil
+ })
+ defer restore()
+ v, err := cgroup.ProbeCgroupVersion()
+ c.Assert(err, IsNil)
+ c.Assert(v, Equals, cgroup.V2)
+}
+
+func (s *cgroupSuite) TestProbeVersion1(c *C) {
+ const TMPFS_MAGIC = 0x1021994
+ restore := cgroup.MockFsTypeForPath(func(p string) (int64, error) {
+ c.Assert(p, Equals, filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup"))
+ return TMPFS_MAGIC, nil
+ })
+ defer restore()
+ v, err := cgroup.ProbeCgroupVersion()
+ c.Assert(err, IsNil)
+ c.Assert(v, Equals, cgroup.V1)
+}
+
+func (s *cgroupSuite) TestProbeVersionUnhappy(c *C) {
+ restore := cgroup.MockFsTypeForPath(func(p string) (int64, error) {
+ c.Assert(p, Equals, filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup"))
+ return 0, errors.New("statfs fail")
+ })
+ defer restore()
+ v, err := cgroup.ProbeCgroupVersion()
+ c.Assert(err, ErrorMatches, "cannot determine filesystem type: statfs fail")
+ c.Assert(v, Equals, cgroup.Unknown)
+}
+
+func (s *cgroupSuite) TestVersion(c *C) {
+ restore := cgroup.MockVersion(cgroup.V2, nil)
+ defer restore()
+ v, err := cgroup.Version()
+ c.Assert(v, Equals, cgroup.V2)
+ c.Assert(err, IsNil)
+
+ restore = cgroup.MockVersion(cgroup.V1, nil)
+ defer restore()
+ v, err = cgroup.Version()
+ c.Assert(v, Equals, cgroup.V1)
+ c.Assert(err, IsNil)
+
+ restore = cgroup.MockVersion(cgroup.Unknown, nil)
+ defer restore()
+ v, err = cgroup.Version()
+ c.Assert(v, Equals, cgroup.Unknown)
+ c.Assert(err, IsNil)
+
+ restore = cgroup.MockVersion(cgroup.Unknown, errors.New("foo"))
+ defer restore()
+ v, err = cgroup.Version()
+ c.Assert(v, Equals, cgroup.Unknown)
+ c.Assert(err, ErrorMatches, "foo")
+}
+
+func (s *cgroupSuite) TestProcPidPath(c *C) {
+ c.Assert(cgroup.ProcPidPath(1), Equals, filepath.Join(dirs.GlobalRootDir, "/proc/1/cgroup"))
+ c.Assert(cgroup.ProcPidPath(1234), Equals, filepath.Join(dirs.GlobalRootDir, "/proc/1234/cgroup"))
+}
+
+func (s *cgroupSuite) TestControllerPathV1(c *C) {
+ c.Assert(cgroup.ControllerPathV1("freezer"), Equals, filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/freezer"))
+ c.Assert(cgroup.ControllerPathV1("memory"), Equals, filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/memory"))
+}
diff -Nru snapd-2.41+19.10.1/sandbox/cgroup/export_test.go snapd-2.42.1+19.10/sandbox/cgroup/export_test.go
--- snapd-2.41+19.10.1/sandbox/cgroup/export_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/sandbox/cgroup/export_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,32 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+package cgroup
+
+var (
+ Cgroup2SuperMagic = cgroup2SuperMagic
+ ProbeCgroupVersion = probeCgroupVersion
+)
+
+func MockFsTypeForPath(mock func(string) (int64, error)) (restore func()) {
+ old := fsTypeForPath
+ fsTypeForPath = mock
+ return func() {
+ fsTypeForPath = old
+ }
+}
diff -Nru snapd-2.41+19.10.1/sanity/export_test.go snapd-2.42.1+19.10/sanity/export_test.go
--- snapd-2.41+19.10.1/sanity/export_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/sanity/export_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -24,6 +24,8 @@
CheckKernelVersion = checkKernelVersion
CheckApparmorUsable = checkApparmorUsable
CheckWSL = checkWSL
+
+ CheckFuse = firstCheckFuse
)
func Checks() []func() error {
@@ -45,3 +47,11 @@
apparmorProfilesPath = old
}
}
+
+func MockFuseBinary(new string) (restore func()) {
+ oldFuseBinary := fuseBinary
+ fuseBinary = new
+ return func() {
+ fuseBinary = oldFuseBinary
+ }
+}
diff -Nru snapd-2.41+19.10.1/sanity/squashfs.go snapd-2.42.1+19.10/sanity/squashfs.go
--- snapd-2.41+19.10.1/sanity/squashfs.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/sanity/squashfs.go 2019-10-30 12:17:43.000000000 +0000
@@ -70,7 +70,22 @@
bAEA+f+YuAAQAAA=
`)
+var fuseBinary = "mount.fuse"
+
+func firstCheckFuse() error {
+ if squashfs.NeedsFuse() {
+ if _, err := exec.LookPath(fuseBinary); err != nil {
+ return fmt.Errorf(`The "fuse" filesystem is required on this system but not available. Please try to install the fuse package.`)
+ }
+ }
+ return nil
+}
+
func checkSquashfsMount() error {
+ if err := firstCheckFuse(); err != nil {
+ return err
+ }
+
tmpSquashfsFile, err := ioutil.TempFile("", "sanity-squashfs-")
if err != nil {
return err
diff -Nru snapd-2.41+19.10.1/sanity/squashfs_test.go snapd-2.42.1+19.10/sanity/squashfs_test.go
--- snapd-2.41+19.10.1/sanity/squashfs_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/sanity/squashfs_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -29,7 +29,7 @@
)
func (s *sanitySuite) TestCheckSquashfsMountHappy(c *C) {
- restore := squashfs.MockUseFuse(false)
+ restore := squashfs.MockNeedsFuse(false)
defer restore()
// we create a canary.txt with the same prefix as the real one
@@ -56,7 +56,7 @@
}
func (s *sanitySuite) TestCheckSquashfsMountNotHappy(c *C) {
- restore := squashfs.MockUseFuse(false)
+ restore := squashfs.MockNeedsFuse(false)
defer restore()
mockMount := testutil.MockCommand(c, "mount", "echo iz-broken;false")
@@ -79,7 +79,7 @@
}
func (s *sanitySuite) TestCheckSquashfsMountWrongContent(c *C) {
- restore := squashfs.MockUseFuse(false)
+ restore := squashfs.MockNeedsFuse(false)
defer restore()
mockMount := testutil.MockCommand(c, "mount", `echo 'wrong content' > "$4"/canary.txt`)
@@ -96,7 +96,7 @@
}
func (s *sanitySuite) TestCheckSquashfsMountSELinuxContext(c *C) {
- restore := squashfs.MockUseFuse(false)
+ restore := squashfs.MockNeedsFuse(false)
defer restore()
mockMount := testutil.MockCommand(c, "mount", "echo 'mock ran'")
@@ -120,3 +120,30 @@
{"mount", "-t", "squashfs", "-o", "context=system_u:object_r:snappy_snap_t:s0", squashfsFile, mountPoint},
})
}
+
+func (s *sanitySuite) TestCheckFuseNoFuseHappy(c *C) {
+ restore := squashfs.MockNeedsFuse(false)
+ defer restore()
+
+ c.Assert(sanity.CheckFuse(), IsNil)
+}
+
+func (s *sanitySuite) TestCheckFuseNeedsFuseAndHasFuse(c *C) {
+ restore := squashfs.MockNeedsFuse(true)
+ defer restore()
+
+ restore = sanity.MockFuseBinary("true")
+ defer restore()
+
+ c.Assert(sanity.CheckFuse(), IsNil)
+}
+
+func (s *sanitySuite) TestCheckFuseNoDevFuseUnhappy(c *C) {
+ restore := squashfs.MockNeedsFuse(true)
+ defer restore()
+
+ restore = sanity.MockFuseBinary("/it/does/not/exist")
+ defer restore()
+
+ c.Assert(sanity.CheckFuse(), ErrorMatches, `The "fuse" filesystem is required on this system but not available. Please try to install the fuse package.`)
+}
diff -Nru snapd-2.41+19.10.1/seed/export_test.go snapd-2.42.1+19.10/seed/export_test.go
--- snapd-2.41+19.10.1/seed/export_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/seed/export_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,24 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package seed
+
+var (
+ LoadAssertions = loadAssertions
+)
diff -Nru snapd-2.41+19.10.1/seed/helpers.go snapd-2.42.1+19.10/seed/helpers.go
--- snapd-2.41+19.10.1/seed/helpers.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/seed/helpers.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,93 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2016-2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package seed
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ "github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/asserts/sysdb"
+)
+
+var trusted = sysdb.Trusted()
+
+func MockTrusted(mockTrusted []asserts.Assertion) (restore func()) {
+ prevTrusted := trusted
+ trusted = mockTrusted
+ return func() {
+ trusted = prevTrusted
+ }
+}
+
+func newMemAssertionsDB() (db asserts.RODatabase, commitTo func(*asserts.Batch) error, err error) {
+ memDB, err := asserts.OpenDatabase(&asserts.DatabaseConfig{
+ Backstore: asserts.NewMemoryBackstore(),
+ Trusted: trusted,
+ })
+ if err != nil {
+ return nil, nil, err
+ }
+
+ commitTo = func(b *asserts.Batch) error {
+ return b.CommitTo(memDB, nil)
+ }
+
+ return memDB, commitTo, nil
+}
+
+func loadAssertions(assertsDir string, loadedFunc func(*asserts.Ref) error) (*asserts.Batch, error) {
+ dc, err := ioutil.ReadDir(assertsDir)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil, ErrNoAssertions
+ }
+ return nil, fmt.Errorf("cannot read assertions dir: %s", err)
+ }
+
+ batch := asserts.NewBatch(nil)
+ for _, fi := range dc {
+ fn := filepath.Join(assertsDir, fi.Name())
+ refs, err := readAsserts(batch, fn)
+ if err != nil {
+ return nil, fmt.Errorf("cannot read assertions: %s", err)
+ }
+ if loadedFunc != nil {
+ for _, ref := range refs {
+ if err := loadedFunc(ref); err != nil {
+ return nil, err
+ }
+ }
+ }
+ }
+
+ return batch, nil
+}
+
+func readAsserts(batch *asserts.Batch, fn string) ([]*asserts.Ref, error) {
+ f, err := os.Open(fn)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ return batch.AddStream(f)
+}
diff -Nru snapd-2.41+19.10.1/seed/helpers_test.go snapd-2.42.1+19.10/seed/helpers_test.go
--- snapd-2.41+19.10.1/seed/helpers_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/seed/helpers_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,163 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package seed_test
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/asserts/assertstest"
+ "github.com/snapcore/snapd/seed"
+ "github.com/snapcore/snapd/seed/seedtest"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/testutil"
+)
+
+type helpersSuite struct {
+ testutil.BaseTest
+
+ *seedtest.TestingSeed
+ devAcct *asserts.Account
+}
+
+var _ = Suite(&helpersSuite{})
+
+func (s *helpersSuite) SetUpTest(c *C) {
+ s.BaseTest.SetUpTest(c)
+ s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
+
+ s.TestingSeed = &seedtest.TestingSeed{}
+ s.SetupAssertSigning("canonical", s)
+
+ dir := c.MkDir()
+
+ s.SnapsDir = filepath.Join(dir, "snaps")
+ s.AssertsDir = filepath.Join(dir, "assertions")
+ err := os.MkdirAll(s.SnapsDir, 0755)
+ c.Assert(err, IsNil)
+ err = os.MkdirAll(s.AssertsDir, 0755)
+ c.Assert(err, IsNil)
+
+ s.devAcct = assertstest.NewAccount(s.StoreSigning, "developer", map[string]interface{}{
+ "account-id": "developerid",
+ }, "")
+
+}
+
+const fooSnap = `type: app
+name: foo
+version: 1.0
+`
+
+const barSnap = `type: app
+name: bar
+version: 2.0
+`
+
+func (s *helpersSuite) TestLoadAssertionsNoAssertions(c *C) {
+ os.Remove(s.AssertsDir)
+
+ b, err := seed.LoadAssertions(s.AssertsDir, nil)
+ c.Check(err, Equals, seed.ErrNoAssertions)
+ c.Check(b, IsNil)
+}
+
+func (s *helpersSuite) TestLoadAssertions(c *C) {
+ _, fooDecl, fooRev := s.MakeAssertedSnap(c, fooSnap, nil, snap.R(1), "developerid")
+ _, barDecl, barRev := s.MakeAssertedSnap(c, barSnap, nil, snap.R(2), "developerid")
+
+ s.WriteAssertions("ground.asserts", s.StoreSigning.StoreAccountKey(""))
+ s.WriteAssertions("foo.asserts", s.devAcct, fooDecl, fooRev)
+ s.WriteAssertions("bar.asserts", barDecl, barRev)
+
+ b, err := seed.LoadAssertions(s.AssertsDir, nil)
+ c.Assert(err, IsNil)
+
+ db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{
+ Backstore: asserts.NewMemoryBackstore(),
+ Trusted: s.StoreSigning.Trusted,
+ })
+ c.Assert(err, IsNil)
+
+ err = b.CommitTo(db, nil)
+ c.Assert(err, IsNil)
+
+ _, err = db.Find(asserts.SnapRevisionType, map[string]string{
+ "snap-sha3-384": fooRev.SnapSHA3_384(),
+ })
+ c.Check(err, IsNil)
+
+ _, err = db.Find(asserts.SnapRevisionType, map[string]string{
+ "snap-sha3-384": barRev.SnapSHA3_384(),
+ })
+ c.Check(err, IsNil)
+}
+
+func (s *helpersSuite) TestLoadAssertionsLoadedCallback(c *C) {
+ _, fooDecl, fooRev := s.MakeAssertedSnap(c, fooSnap, nil, snap.R(1), "developerid")
+ _, barDecl, barRev := s.MakeAssertedSnap(c, barSnap, nil, snap.R(2), "developerid")
+
+ s.WriteAssertions("ground.asserts", s.StoreSigning.StoreAccountKey(""))
+ s.WriteAssertions("foo.asserts", s.devAcct, fooDecl, fooRev)
+ s.WriteAssertions("bar.asserts", barDecl, barRev)
+
+ counts := make(map[string]int)
+ seen := make(map[string]bool)
+
+ loaded := func(ref *asserts.Ref) error {
+ if ref.Type == asserts.SnapDeclarationType {
+ seen[ref.PrimaryKey[1]] = true
+ }
+ counts[ref.Type.Name]++
+ return nil
+ }
+
+ _, err := seed.LoadAssertions(s.AssertsDir, loaded)
+ c.Assert(err, IsNil)
+
+ c.Check(seen, DeepEquals, map[string]bool{
+ "bardidididididididididididididid": true,
+ "foodidididididididididididididid": true,
+ })
+
+ // overall
+ c.Check(counts, DeepEquals, map[string]int{
+ "account": 1,
+ "account-key": 1,
+ "snap-declaration": 2,
+ "snap-revision": 2,
+ })
+}
+
+func (s *helpersSuite) TestLoadAssertionsLoadedCallbackError(c *C) {
+ s.WriteAssertions("ground.asserts", s.StoreSigning.StoreAccountKey(""))
+
+ loaded := func(ref *asserts.Ref) error {
+ return fmt.Errorf("boom")
+
+ }
+
+ _, err := seed.LoadAssertions(s.AssertsDir, loaded)
+ c.Assert(err, ErrorMatches, "boom")
+}
diff -Nru snapd-2.41+19.10.1/seed/seed16.go snapd-2.42.1+19.10/seed/seed16.go
--- snapd-2.41+19.10.1/seed/seed16.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/seed/seed16.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,294 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2014-2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package seed
+
+/* ATTN this should *not* use:
+
+* dirs package: it is passed an explicit directory to work on
+
+* release.OnClassic: it assumes classic based on the model classic
+ option; consistency between system and model can/must be enforced
+ elsewhere
+
+*/
+
+import (
+ "fmt"
+ "path/filepath"
+
+ "github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/asserts/snapasserts"
+ "github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/naming"
+ "github.com/snapcore/snapd/timings"
+)
+
+type seed16 struct {
+ seedDir string
+
+ db asserts.RODatabase
+
+ model *asserts.Model
+
+ snaps []*Snap
+ essentialSnapsNum int
+
+ usesSnapdSnap bool
+}
+
+func (s *seed16) LoadAssertions(db asserts.RODatabase, commitTo func(*asserts.Batch) error) error {
+ if db == nil {
+ // a db was not provided, create an internal temporary one
+ var err error
+ db, commitTo, err = newMemAssertionsDB()
+ if err != nil {
+ return err
+ }
+ }
+
+ assertSeedDir := filepath.Join(s.seedDir, "assertions")
+ // collect assertions and find model assertion
+ var modelRef *asserts.Ref
+ checkForModel := func(ref *asserts.Ref) error {
+ if ref.Type == asserts.ModelType {
+ if modelRef != nil && modelRef.Unique() != ref.Unique() {
+ return fmt.Errorf("cannot have multiple model assertions in seed")
+ }
+ modelRef = ref
+ }
+ return nil
+ }
+
+ batch, err := loadAssertions(assertSeedDir, checkForModel)
+ if err != nil {
+ return err
+ }
+
+ // verify we have one model assertion
+ if modelRef == nil {
+ return fmt.Errorf("seed must have a model assertion")
+ }
+
+ if err := commitTo(batch); err != nil {
+ return err
+ }
+
+ a, err := modelRef.Resolve(db.Find)
+ if err != nil {
+ return fmt.Errorf("internal error: cannot find just added assertion %v: %v", modelRef, err)
+ }
+
+ // remember db for later use
+ s.db = db
+ s.model = a.(*asserts.Model)
+
+ return nil
+}
+
+func (s *seed16) Model() (*asserts.Model, error) {
+ if s.model == nil {
+ return nil, fmt.Errorf("internal error: model assertion unset")
+ }
+ return s.model, nil
+}
+
+func (s *seed16) addSnap(sn *Snap16, tm timings.Measurer) (*Snap, error) {
+ path := filepath.Join(s.seedDir, "snaps", sn.File)
+ seedSnap := &Snap{
+ Path: path,
+ // TODO|XXX: make sure channel is right for pinned tracks
+ Channel: sn.Channel,
+ Classic: sn.Classic,
+ DevMode: sn.DevMode,
+ }
+
+ var sideInfo snap.SideInfo
+ if sn.Unasserted {
+ sideInfo.RealName = sn.Name
+ } else {
+ var si *snap.SideInfo
+ var err error
+ timings.Run(tm, "derive-side-info", fmt.Sprintf("hash and derive side info for snap %q", sn.Name), func(nested timings.Measurer) {
+ si, err = snapasserts.DeriveSideInfo(path, s.db)
+ })
+ if asserts.IsNotFound(err) {
+ return nil, fmt.Errorf("cannot find signatures with metadata for snap %q (%q)", sn.Name, path)
+ }
+ if err != nil {
+ return nil, err
+ }
+ sideInfo = *si
+ sideInfo.Private = sn.Private
+ sideInfo.Contact = sn.Contact
+ }
+
+ seedSnap.SideInfo = &sideInfo
+
+ s.snaps = append(s.snaps, seedSnap)
+
+ return seedSnap, nil
+}
+
+func (s *seed16) LoadMeta(tm timings.Measurer) error {
+ model, err := s.Model()
+ if err != nil {
+ return err
+ }
+
+ seedYamlFile := filepath.Join(s.seedDir, "seed.yaml")
+ if !osutil.FileExists(seedYamlFile) {
+ return ErrNoMeta
+ }
+
+ seedYaml, err := ReadYaml(seedYamlFile)
+ if err != nil {
+ return err
+ }
+ yamlSnaps := seedYaml.Snaps
+
+ required := naming.NewSnapSet(model.RequiredWithEssentialSnaps())
+ seeding := make(map[string]*Snap16, len(yamlSnaps))
+ for _, sn := range yamlSnaps {
+ seeding[sn.Name] = sn
+ }
+ added := make(map[string]bool, 3)
+ classic := model.Classic()
+ _, s.usesSnapdSnap = seeding["snapd"]
+
+ baseSnap := "core"
+ classicWithSnapd := false
+ if model.Base() != "" {
+ baseSnap = model.Base()
+ }
+ if classic && s.usesSnapdSnap {
+ classicWithSnapd = true
+ // there is no system-wide base as such
+ // if there is a gadget we will install its base first though
+ baseSnap = ""
+ }
+
+ // add the essential snaps
+ addEssential := func(snapName string) (*Snap, error) {
+ // be idempotent
+ if added[snapName] {
+ return nil, nil
+ }
+ yamlSnap := seeding[snapName]
+ if yamlSnap == nil {
+ return nil, fmt.Errorf("essential snap %q required by the model is missing in the seed", snapName)
+ }
+
+ seedSnap, err := s.addSnap(yamlSnap, tm)
+ if err != nil {
+ return nil, err
+ }
+
+ seedSnap.Essential = true
+ seedSnap.Required = true
+ added[snapName] = true
+
+ return seedSnap, nil
+ }
+
+ // if there are snaps to seed, core/base needs to be seeded too
+ if len(yamlSnaps) != 0 {
+ // ensure "snapd" snap is installed first
+ if model.Base() != "" || classicWithSnapd {
+ if _, err := addEssential("snapd"); err != nil {
+ return err
+ }
+ }
+ if !classicWithSnapd {
+ if _, err := addEssential(baseSnap); err != nil {
+ return err
+ }
+ }
+ }
+
+ if kernelName := model.Kernel(); kernelName != "" {
+ if _, err := addEssential(kernelName); err != nil {
+ return err
+ }
+ }
+
+ if gadgetName := model.Gadget(); gadgetName != "" {
+ gadget, err := addEssential(gadgetName)
+ if err != nil {
+ return err
+ }
+
+ // always make sure the base of gadget is installed first
+ snapf, err := snap.Open(gadget.Path)
+ if err != nil {
+ return err
+ }
+ info, err := snap.ReadInfoFromSnapFile(snapf, gadget.SideInfo)
+ if err != nil {
+ return err
+ }
+ gadgetBase := info.Base
+ if gadgetBase == "" {
+ gadgetBase = "core"
+ }
+ // Sanity check
+ // TODO: do we want to relax this? the new logic would allow
+ // but it might just be confusing for now
+ if baseSnap != "" && gadgetBase != baseSnap {
+ return fmt.Errorf("cannot use gadget snap because its base %q is different from model base %q", gadgetBase, model.Base())
+ }
+ if _, err = addEssential(gadgetBase); err != nil {
+ return err
+ }
+ }
+
+ s.essentialSnapsNum = len(s.snaps)
+
+ // the rest of the snaps
+ for _, sn := range yamlSnaps {
+ if added[sn.Name] {
+ continue
+ }
+ seedSnap, err := s.addSnap(sn, tm)
+ if err != nil {
+ return err
+ }
+ if required.Contains(seedSnap) {
+ seedSnap.Required = true
+ }
+ }
+
+ return nil
+}
+
+func (s *seed16) UsesSnapdSnap() bool {
+ return s.usesSnapdSnap
+}
+
+func (s *seed16) EssentialSnaps() []*Snap {
+ return s.snaps[:s.essentialSnapsNum]
+}
+
+func (s *seed16) ModeSnaps(mode string) ([]*Snap, error) {
+ if mode != "run" {
+ return nil, fmt.Errorf("internal error: Core 16/18 have only run mode, got: %s", mode)
+ }
+ return s.snaps[s.essentialSnapsNum:], nil
+}
diff -Nru snapd-2.41+19.10.1/seed/seed16_test.go snapd-2.42.1+19.10/seed/seed16_test.go
--- snapd-2.41+19.10.1/seed/seed16_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/seed/seed16_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,1058 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package seed_test
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ . "gopkg.in/check.v1"
+ "gopkg.in/yaml.v2"
+
+ "github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/asserts/assertstest"
+ "github.com/snapcore/snapd/seed"
+ "github.com/snapcore/snapd/seed/seedtest"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/snaptest"
+ "github.com/snapcore/snapd/testutil"
+ "github.com/snapcore/snapd/timings"
+)
+
+type seed16Suite struct {
+ testutil.BaseTest
+
+ *seedtest.TestingSeed
+ devAcct *asserts.Account
+
+ seedDir string
+
+ seed16 seed.Seed
+
+ db *asserts.Database
+
+ perfTimings timings.Measurer
+}
+
+var _ = Suite(&seed16Suite{})
+
+var (
+ brandPrivKey, _ = assertstest.GenerateKey(752)
+)
+
+func (s *seed16Suite) SetUpTest(c *C) {
+ s.BaseTest.SetUpTest(c)
+ s.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
+
+ s.TestingSeed = &seedtest.TestingSeed{}
+ s.SetupAssertSigning("canonical", s)
+ s.Brands.Register("my-brand", brandPrivKey, map[string]interface{}{
+ "verification": "verified",
+ })
+
+ s.seedDir = c.MkDir()
+
+ s.SnapsDir = filepath.Join(s.seedDir, "snaps")
+ s.AssertsDir = filepath.Join(s.seedDir, "assertions")
+
+ s.devAcct = assertstest.NewAccount(s.StoreSigning, "developer", map[string]interface{}{
+ "account-id": "developerid",
+ }, "")
+ assertstest.AddMany(s.StoreSigning, s.devAcct)
+
+ seed16, err := seed.Open(s.seedDir)
+ c.Assert(err, IsNil)
+ s.seed16 = seed16
+
+ db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{
+ Backstore: asserts.NewMemoryBackstore(),
+ Trusted: s.StoreSigning.Trusted,
+ })
+ c.Assert(err, IsNil)
+ s.db = db
+
+ s.perfTimings = timings.New(nil)
+}
+
+func (s *seed16Suite) commitTo(b *asserts.Batch) error {
+ return b.CommitTo(s.db, nil)
+}
+
+func (s *seed16Suite) TestLoadAssertionsNoAssertions(c *C) {
+ c.Check(s.seed16.LoadAssertions(s.db, s.commitTo), Equals, seed.ErrNoAssertions)
+}
+
+func (s *seed16Suite) TestLoadAssertionsNoModelAssertion(c *C) {
+ err := os.Mkdir(s.AssertsDir, 0755)
+ c.Assert(err, IsNil)
+
+ c.Check(s.seed16.LoadAssertions(s.db, s.commitTo), ErrorMatches, "seed must have a model assertion")
+}
+
+func (s *seed16Suite) TestLoadAssertionsTwoModelAssertionsError(c *C) {
+ err := os.Mkdir(s.AssertsDir, 0755)
+ c.Assert(err, IsNil)
+
+ headers := map[string]interface{}{
+ "architecture": "amd64",
+ "kernel": "pc-kernel",
+ "gadget": "pc",
+ }
+ modelChain := s.MakeModelAssertionChain("my-brand", "my-model", headers)
+ s.WriteAssertions("model.asserts", modelChain...)
+ modelChain = s.MakeModelAssertionChain("my-brand", "my-model-2", headers)
+ s.WriteAssertions("model2.asserts", modelChain...)
+
+ c.Check(s.seed16.LoadAssertions(s.db, s.commitTo), ErrorMatches, "cannot have multiple model assertions in seed")
+}
+
+func (s *seed16Suite) TestLoadAssertionsConsistencyError(c *C) {
+ err := os.Mkdir(s.AssertsDir, 0755)
+ c.Assert(err, IsNil)
+
+ // write out only the model assertion
+ headers := map[string]interface{}{
+ "architecture": "amd64",
+ "kernel": "pc-kernel",
+ "gadget": "pc",
+ }
+ model := s.Brands.Model("my-brand", "my-model", headers)
+ s.WriteAssertions("model.asserts", model)
+
+ c.Check(s.seed16.LoadAssertions(s.db, s.commitTo), ErrorMatches, "cannot resolve prerequisite assertion: account-key .*")
+}
+
+func (s *seed16Suite) TestLoadAssertionsModelHappy(c *C) {
+ err := os.Mkdir(s.AssertsDir, 0755)
+ c.Assert(err, IsNil)
+
+ headers := map[string]interface{}{
+ "architecture": "amd64",
+ "kernel": "pc-kernel",
+ "gadget": "pc",
+ }
+ modelChain := s.MakeModelAssertionChain("my-brand", "my-model", headers)
+ s.WriteAssertions("model.asserts", modelChain...)
+
+ err = s.seed16.LoadAssertions(s.db, s.commitTo)
+ c.Assert(err, IsNil)
+
+ model, err := s.seed16.Model()
+ c.Assert(err, IsNil)
+ c.Check(model.Model(), Equals, "my-model")
+
+ _, err = s.db.Find(asserts.ModelType, map[string]string{
+ "series": "16",
+ "brand-id": "my-brand",
+ "model": "my-model",
+ })
+ c.Assert(err, IsNil)
+}
+
+func (s *seed16Suite) TestLoadAssertionsModelTempDBHappy(c *C) {
+ r := seed.MockTrusted(s.StoreSigning.Trusted)
+ defer r()
+
+ err := os.Mkdir(s.AssertsDir, 0755)
+ c.Assert(err, IsNil)
+
+ headers := map[string]interface{}{
+ "architecture": "amd64",
+ "kernel": "pc-kernel",
+ "gadget": "pc",
+ }
+ modelChain := s.MakeModelAssertionChain("my-brand", "my-model", headers)
+ s.WriteAssertions("model.asserts", modelChain...)
+
+ err = s.seed16.LoadAssertions(nil, nil)
+ c.Assert(err, IsNil)
+
+ model, err := s.seed16.Model()
+ c.Assert(err, IsNil)
+ c.Check(model.Model(), Equals, "my-model")
+}
+
+func (s *seed16Suite) TestSkippedLoadAssertion(c *C) {
+ _, err := s.seed16.Model()
+ c.Check(err, ErrorMatches, "internal error: model assertion unset")
+
+ err = s.seed16.LoadMeta(s.perfTimings)
+ c.Check(err, ErrorMatches, "internal error: model assertion unset")
+}
+
+func (s *seed16Suite) TestLoadMetaNoMeta(c *C) {
+ err := os.Mkdir(s.AssertsDir, 0755)
+ c.Assert(err, IsNil)
+
+ headers := map[string]interface{}{
+ "architecture": "amd64",
+ "kernel": "pc-kernel",
+ "gadget": "pc",
+ }
+ modelChain := s.MakeModelAssertionChain("my-brand", "my-model", headers)
+ s.WriteAssertions("model.asserts", modelChain...)
+
+ err = s.seed16.LoadAssertions(s.db, s.commitTo)
+ c.Assert(err, IsNil)
+
+ err = s.seed16.LoadMeta(s.perfTimings)
+ c.Check(err, Equals, seed.ErrNoMeta)
+}
+
+func (s *seed16Suite) TestLoadMetaInvalidSeedYaml(c *C) {
+ err := os.Mkdir(s.AssertsDir, 0755)
+ c.Assert(err, IsNil)
+
+ headers := map[string]interface{}{
+ "architecture": "amd64",
+ "kernel": "pc-kernel",
+ "gadget": "pc",
+ }
+ modelChain := s.MakeModelAssertionChain("my-brand", "my-model", headers)
+ s.WriteAssertions("model.asserts", modelChain...)
+
+ err = s.seed16.LoadAssertions(s.db, s.commitTo)
+ c.Assert(err, IsNil)
+
+ // create a seed.yaml
+ content, err := yaml.Marshal(map[string]interface{}{
+ "snaps": []*seed.Snap16{{
+ Name: "core",
+ Channel: "track/not-a-risk",
+ }},
+ })
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(filepath.Join(s.seedDir, "seed.yaml"), content, 0644)
+ c.Assert(err, IsNil)
+
+ err = s.seed16.LoadMeta(s.perfTimings)
+ c.Check(err, ErrorMatches, `cannot read seed yaml: invalid risk in channel name: track/not-a-risk`)
+}
+
+var snapYaml = map[string]string{
+ "core": `name: core
+type: os
+version: 1.0
+`,
+ "pc-kernel": `name: pc-kernel
+type: kernel
+version: 1.0
+`,
+ "pc": `name: pc
+type: gadget
+version: 1.0
+`,
+ "required": `name: required
+type: app
+version: 1.0
+`,
+ "snapd": `name: snapd
+type: snapd
+version: 1.0
+`,
+ "core18": `name: core18
+type: base
+version: 1.0
+`,
+ "pc-kernel=18": `name: pc-kernel
+type: kernel
+version: 1.0
+`,
+ "pc=18": `name: pc
+type: gadget
+base: core18
+version: 1.0
+`,
+ "required18": `name: required18
+type: app
+base: core18
+version: 1.0
+`,
+ "classic-snap": `name: classic-snap
+type: app
+confinement: classic
+version: 1.0
+`,
+ "classic-gadget": `name: classic-gadget
+type: gadget
+version: 1.0
+`,
+ "classic-gadget18": `name: classic-gadget18
+type: gadget
+base: core18
+version: 1.0
+`,
+ "private-snap": `name: private-snap
+base: core18
+version: 1.0
+`,
+ "contactable-snap": `name: contactable-snap
+base: core18
+version: 1.0
+`,
+}
+
+var snapPublishers = map[string]string{
+ "required": "developerid",
+}
+
+var (
+ coreSeed = &seed.Snap16{
+ Name: "core",
+ Channel: "stable",
+ }
+ kernelSeed = &seed.Snap16{
+ Name: "pc-kernel",
+ Channel: "stable",
+ }
+ gadgetSeed = &seed.Snap16{
+ Name: "pc",
+ Channel: "stable",
+ }
+ requiredSeed = &seed.Snap16{
+ Name: "required",
+ Channel: "stable",
+ }
+ // Core 18
+ snapdSeed = &seed.Snap16{
+ Name: "snapd",
+ Channel: "stable",
+ }
+ core18Seed = &seed.Snap16{
+ Name: "core18",
+ Channel: "stable",
+ }
+ kernel18Seed = &seed.Snap16{
+ Name: "pc-kernel",
+ Channel: "18",
+ }
+ gadget18Seed = &seed.Snap16{
+ Name: "pc",
+ Channel: "18",
+ }
+ required18Seed = &seed.Snap16{
+ Name: "required18",
+ Channel: "stable",
+ }
+ classicSnapSeed = &seed.Snap16{
+ Name: "classic-snap",
+ Channel: "stable",
+ Classic: true,
+ }
+ classicGadgetSeed = &seed.Snap16{
+ Name: "classic-gadget",
+ Channel: "stable",
+ }
+ classicGadget18Seed = &seed.Snap16{
+ Name: "classic-gadget18",
+ Channel: "stable",
+ }
+ privateSnapSeed = &seed.Snap16{
+ Name: "private-snap",
+ Channel: "stable",
+ Private: true,
+ }
+ contactableSnapSeed = &seed.Snap16{
+ Name: "contactable-snap",
+ Channel: "stable",
+ Contact: "author@example.com",
+ }
+)
+
+func (s *seed16Suite) makeSeed(c *C, modelHeaders map[string]interface{}, seedSnaps ...*seed.Snap16) []*seed.Snap16 {
+ coreHeaders := map[string]interface{}{
+ "architecture": "amd64",
+ }
+
+ if _, ok := modelHeaders["classic"]; !ok {
+ coreHeaders["kernel"] = "pc-kernel"
+ coreHeaders["gadget"] = "pc"
+ }
+
+ err := os.Mkdir(s.AssertsDir, 0755)
+ c.Assert(err, IsNil)
+
+ modelChain := s.MakeModelAssertionChain("my-brand", "my-model", coreHeaders, modelHeaders)
+ s.WriteAssertions("model.asserts", modelChain...)
+
+ err = os.Mkdir(s.SnapsDir, 0755)
+ c.Assert(err, IsNil)
+
+ var completeSeedSnaps []*seed.Snap16
+ for _, seedSnap := range seedSnaps {
+ completeSeedSnap := *seedSnap
+ var snapFname string
+ if seedSnap.Unasserted {
+ mockSnapFile := snaptest.MakeTestSnapWithFiles(c, snapYaml[seedSnap.Name], nil)
+ snapFname = filepath.Base(mockSnapFile)
+ err := os.Rename(mockSnapFile, filepath.Join(s.seedDir, "snaps", snapFname))
+ c.Assert(err, IsNil)
+ } else {
+ publisher := snapPublishers[seedSnap.Name]
+ if publisher == "" {
+ publisher = "canonical"
+ }
+ whichYaml := seedSnap.Name
+ if seedSnap.Channel != "stable" {
+ whichYaml = whichYaml + "=" + seedSnap.Channel
+ }
+ fname, decl, rev := s.MakeAssertedSnap(c, snapYaml[whichYaml], nil, snap.R(1), publisher)
+ acct, err := s.StoreSigning.Find(asserts.AccountType, map[string]string{"account-id": publisher})
+ c.Assert(err, IsNil)
+ s.WriteAssertions(fmt.Sprintf("%s.asserts", seedSnap.Name), rev, decl, acct)
+ snapFname = fname
+ }
+ completeSeedSnap.File = snapFname
+ completeSeedSnaps = append(completeSeedSnaps, &completeSeedSnap)
+ }
+
+ // create a seed.yaml
+ content, err := yaml.Marshal(map[string]interface{}{
+ "snaps": completeSeedSnaps,
+ })
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(filepath.Join(s.seedDir, "seed.yaml"), content, 0644)
+ c.Assert(err, IsNil)
+
+ return completeSeedSnaps
+}
+
+func (s *seed16Suite) expectedPath(snapName string) string {
+ return filepath.Join(s.seedDir, "snaps", filepath.Base(s.AssertedSnap(snapName)))
+}
+
+func (s *seed16Suite) TestLoadMetaCore16Minimal(c *C) {
+ s.makeSeed(c, nil, coreSeed, kernelSeed, gadgetSeed)
+
+ err := s.seed16.LoadAssertions(s.db, s.commitTo)
+ c.Assert(err, IsNil)
+
+ err = s.seed16.LoadMeta(s.perfTimings)
+ c.Assert(err, IsNil)
+
+ c.Check(s.seed16.UsesSnapdSnap(), Equals, false)
+
+ essSnaps := s.seed16.EssentialSnaps()
+ c.Check(essSnaps, HasLen, 3)
+
+ c.Check(essSnaps, DeepEquals, []*seed.Snap{
+ {
+ Path: s.expectedPath("core"),
+ SideInfo: &s.AssertedSnapInfo("core").SideInfo,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
+ }, {
+ Path: s.expectedPath("pc-kernel"),
+ SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
+ }, {
+ Path: s.expectedPath("pc"),
+ SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
+ },
+ })
+
+ runSnaps, err := s.seed16.ModeSnaps("run")
+ c.Assert(err, IsNil)
+ c.Check(runSnaps, HasLen, 0)
+}
+
+func (s *seed16Suite) TestLoadMetaCore16(c *C) {
+ s.makeSeed(c, map[string]interface{}{
+ "required-snaps": []interface{}{"required"},
+ }, coreSeed, kernelSeed, gadgetSeed, requiredSeed)
+
+ err := s.seed16.LoadAssertions(s.db, s.commitTo)
+ c.Assert(err, IsNil)
+
+ err = s.seed16.LoadMeta(s.perfTimings)
+ c.Assert(err, IsNil)
+
+ essSnaps := s.seed16.EssentialSnaps()
+ c.Check(essSnaps, HasLen, 3)
+
+ runSnaps, err := s.seed16.ModeSnaps("run")
+ c.Assert(err, IsNil)
+ c.Check(runSnaps, HasLen, 1)
+
+ c.Check(runSnaps, DeepEquals, []*seed.Snap{
+ {
+ Path: s.expectedPath("required"),
+ SideInfo: &s.AssertedSnapInfo("required").SideInfo,
+ Required: true,
+ Channel: "stable",
+ },
+ })
+}
+
+func (s *seed16Suite) TestLoadMetaCore18Minimal(c *C) {
+ s.makeSeed(c, map[string]interface{}{
+ "base": "core18",
+ "kernel": "pc-kernel=18",
+ "gadget": "pc=18",
+ }, snapdSeed, core18Seed, kernel18Seed, gadget18Seed)
+
+ err := s.seed16.LoadAssertions(s.db, s.commitTo)
+ c.Assert(err, IsNil)
+
+ err = s.seed16.LoadMeta(s.perfTimings)
+ c.Assert(err, IsNil)
+
+ c.Check(s.seed16.UsesSnapdSnap(), Equals, true)
+
+ essSnaps := s.seed16.EssentialSnaps()
+ c.Check(essSnaps, HasLen, 4)
+
+ c.Check(essSnaps, DeepEquals, []*seed.Snap{
+ {
+ Path: s.expectedPath("snapd"),
+ SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
+ }, {
+ Path: s.expectedPath("core18"),
+ SideInfo: &s.AssertedSnapInfo("core18").SideInfo,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
+ }, {
+ Path: s.expectedPath("pc-kernel"),
+ SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
+ Essential: true,
+ Required: true,
+ Channel: "18",
+ }, {
+ Path: s.expectedPath("pc"),
+ SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
+ Essential: true,
+ Required: true,
+ Channel: "18",
+ },
+ })
+
+ runSnaps, err := s.seed16.ModeSnaps("run")
+ c.Assert(err, IsNil)
+ c.Check(runSnaps, HasLen, 0)
+}
+
+func (s *seed16Suite) TestLoadMetaCore18(c *C) {
+ s.makeSeed(c, map[string]interface{}{
+ "base": "core18",
+ "kernel": "pc-kernel=18",
+ "gadget": "pc=18",
+ "required-snaps": []interface{}{"core", "required", "required18"},
+ }, snapdSeed, core18Seed, kernel18Seed, gadget18Seed, requiredSeed, coreSeed, required18Seed)
+
+ err := s.seed16.LoadAssertions(s.db, s.commitTo)
+ c.Assert(err, IsNil)
+
+ err = s.seed16.LoadMeta(s.perfTimings)
+ c.Assert(err, IsNil)
+
+ essSnaps := s.seed16.EssentialSnaps()
+ c.Check(essSnaps, HasLen, 4)
+
+ c.Check(essSnaps, DeepEquals, []*seed.Snap{
+ {
+ Path: s.expectedPath("snapd"),
+ SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
+ }, {
+ Path: s.expectedPath("core18"),
+ SideInfo: &s.AssertedSnapInfo("core18").SideInfo,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
+ }, {
+ Path: s.expectedPath("pc-kernel"),
+ SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
+ Essential: true,
+ Required: true,
+ Channel: "18",
+ }, {
+ Path: s.expectedPath("pc"),
+ SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
+ Essential: true,
+ Required: true,
+ Channel: "18",
+ },
+ })
+
+ runSnaps, err := s.seed16.ModeSnaps("run")
+ c.Assert(err, IsNil)
+ c.Check(runSnaps, HasLen, 3)
+
+ // these are not sorted by type, firstboot will do that
+ c.Check(runSnaps, DeepEquals, []*seed.Snap{
+ {
+ Path: s.expectedPath("required"),
+ SideInfo: &s.AssertedSnapInfo("required").SideInfo,
+ Required: true,
+ Channel: "stable",
+ }, {
+ Path: s.expectedPath("core"),
+ SideInfo: &s.AssertedSnapInfo("core").SideInfo,
+ Required: true,
+ Channel: "stable",
+ }, {
+ Path: s.expectedPath("required18"),
+ SideInfo: &s.AssertedSnapInfo("required18").SideInfo,
+ Required: true,
+ Channel: "stable",
+ },
+ })
+}
+
+func (s *seed16Suite) TestLoadMetaClassicNothing(c *C) {
+ s.makeSeed(c, map[string]interface{}{
+ "classic": "true",
+ })
+
+ err := s.seed16.LoadAssertions(s.db, s.commitTo)
+ c.Assert(err, IsNil)
+
+ err = s.seed16.LoadMeta(s.perfTimings)
+ c.Assert(err, IsNil)
+
+ c.Check(s.seed16.UsesSnapdSnap(), Equals, false)
+
+ essSnaps := s.seed16.EssentialSnaps()
+ c.Check(essSnaps, HasLen, 0)
+
+ runSnaps, err := s.seed16.ModeSnaps("run")
+ c.Assert(err, IsNil)
+ c.Check(runSnaps, HasLen, 0)
+}
+
+func (s *seed16Suite) TestLoadMetaClassicCore(c *C) {
+ s.makeSeed(c, map[string]interface{}{
+ "classic": "true",
+ }, coreSeed, classicSnapSeed)
+
+ err := s.seed16.LoadAssertions(s.db, s.commitTo)
+ c.Assert(err, IsNil)
+
+ err = s.seed16.LoadMeta(s.perfTimings)
+ c.Assert(err, IsNil)
+
+ c.Check(s.seed16.UsesSnapdSnap(), Equals, false)
+
+ essSnaps := s.seed16.EssentialSnaps()
+ c.Check(essSnaps, HasLen, 1)
+ c.Check(essSnaps, DeepEquals, []*seed.Snap{
+ {
+ Path: s.expectedPath("core"),
+ SideInfo: &s.AssertedSnapInfo("core").SideInfo,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
+ },
+ })
+
+ // classic-snap is not required, just an extra snap
+ runSnaps, err := s.seed16.ModeSnaps("run")
+ c.Assert(err, IsNil)
+ c.Check(runSnaps, HasLen, 1)
+ c.Check(runSnaps, DeepEquals, []*seed.Snap{
+ {
+ Path: s.expectedPath("classic-snap"),
+ SideInfo: &s.AssertedSnapInfo("classic-snap").SideInfo,
+ Channel: "stable",
+ Classic: true,
+ },
+ })
+}
+
+func (s *seed16Suite) TestLoadMetaClassicCoreWithGadget(c *C) {
+ s.makeSeed(c, map[string]interface{}{
+ "classic": "true",
+ "gadget": "classic-gadget",
+ }, coreSeed, classicGadgetSeed)
+
+ err := s.seed16.LoadAssertions(s.db, s.commitTo)
+ c.Assert(err, IsNil)
+
+ err = s.seed16.LoadMeta(s.perfTimings)
+ c.Assert(err, IsNil)
+
+ c.Check(s.seed16.UsesSnapdSnap(), Equals, false)
+
+ essSnaps := s.seed16.EssentialSnaps()
+ c.Check(essSnaps, HasLen, 2)
+ c.Check(essSnaps, DeepEquals, []*seed.Snap{
+ {
+ Path: s.expectedPath("core"),
+ SideInfo: &s.AssertedSnapInfo("core").SideInfo,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
+ },
+ {
+ Path: s.expectedPath("classic-gadget"),
+ SideInfo: &s.AssertedSnapInfo("classic-gadget").SideInfo,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
+ },
+ })
+
+ runSnaps, err := s.seed16.ModeSnaps("run")
+ c.Assert(err, IsNil)
+ c.Check(runSnaps, HasLen, 0)
+}
+
+func (s *seed16Suite) TestLoadMetaClassicSnapd(c *C) {
+ s.makeSeed(c, map[string]interface{}{
+ "classic": "true",
+ "required-snaps": []interface{}{"core18", "required18"},
+ }, snapdSeed, core18Seed, required18Seed)
+
+ err := s.seed16.LoadAssertions(s.db, s.commitTo)
+ c.Assert(err, IsNil)
+
+ err = s.seed16.LoadMeta(s.perfTimings)
+ c.Assert(err, IsNil)
+
+ c.Check(s.seed16.UsesSnapdSnap(), Equals, true)
+
+ essSnaps := s.seed16.EssentialSnaps()
+ c.Check(essSnaps, HasLen, 1)
+ c.Check(essSnaps, DeepEquals, []*seed.Snap{
+ {
+ Path: s.expectedPath("snapd"),
+ SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
+ },
+ })
+
+ runSnaps, err := s.seed16.ModeSnaps("run")
+ c.Assert(err, IsNil)
+ c.Check(runSnaps, HasLen, 2)
+ c.Check(runSnaps, DeepEquals, []*seed.Snap{
+ {
+ Path: s.expectedPath("core18"),
+ SideInfo: &s.AssertedSnapInfo("core18").SideInfo,
+ Required: true,
+ Channel: "stable",
+ }, {
+ Path: s.expectedPath("required18"),
+ SideInfo: &s.AssertedSnapInfo("required18").SideInfo,
+ Required: true,
+ Channel: "stable",
+ },
+ })
+}
+
+func (s *seed16Suite) TestLoadMetaClassicSnapdWithGadget(c *C) {
+ s.makeSeed(c, map[string]interface{}{
+ "classic": "true",
+ "gadget": "classic-gadget",
+ }, snapdSeed, classicGadgetSeed, coreSeed)
+
+ err := s.seed16.LoadAssertions(s.db, s.commitTo)
+ c.Assert(err, IsNil)
+
+ err = s.seed16.LoadMeta(s.perfTimings)
+ c.Assert(err, IsNil)
+
+ c.Check(s.seed16.UsesSnapdSnap(), Equals, true)
+
+ essSnaps := s.seed16.EssentialSnaps()
+ c.Check(essSnaps, HasLen, 3)
+ c.Check(essSnaps, DeepEquals, []*seed.Snap{
+ {
+ Path: s.expectedPath("snapd"),
+ SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
+ }, {
+ Path: s.expectedPath("classic-gadget"),
+ SideInfo: &s.AssertedSnapInfo("classic-gadget").SideInfo,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
+ }, {
+ Path: s.expectedPath("core"),
+ SideInfo: &s.AssertedSnapInfo("core").SideInfo,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
+ },
+ })
+
+ runSnaps, err := s.seed16.ModeSnaps("run")
+ c.Assert(err, IsNil)
+ c.Check(runSnaps, HasLen, 0)
+}
+
+func (s *seed16Suite) TestLoadMetaClassicSnapdWithGadget18(c *C) {
+ s.makeSeed(c, map[string]interface{}{
+ "classic": "true",
+ "gadget": "classic-gadget18",
+ "required-snaps": []interface{}{"core", "required"},
+ }, snapdSeed, coreSeed, requiredSeed, classicGadget18Seed, core18Seed)
+
+ err := s.seed16.LoadAssertions(s.db, s.commitTo)
+ c.Assert(err, IsNil)
+
+ err = s.seed16.LoadMeta(s.perfTimings)
+ c.Assert(err, IsNil)
+
+ c.Check(s.seed16.UsesSnapdSnap(), Equals, true)
+
+ essSnaps := s.seed16.EssentialSnaps()
+ c.Check(essSnaps, HasLen, 3)
+ c.Check(essSnaps, DeepEquals, []*seed.Snap{
+ {
+ Path: s.expectedPath("snapd"),
+ SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
+ }, {
+ Path: s.expectedPath("classic-gadget18"),
+ SideInfo: &s.AssertedSnapInfo("classic-gadget18").SideInfo,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
+ }, {
+ Path: s.expectedPath("core18"),
+ SideInfo: &s.AssertedSnapInfo("core18").SideInfo,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
+ },
+ })
+
+ runSnaps, err := s.seed16.ModeSnaps("run")
+ c.Assert(err, IsNil)
+ c.Check(runSnaps, HasLen, 2)
+ c.Check(runSnaps, DeepEquals, []*seed.Snap{
+ {
+ Path: s.expectedPath("core"),
+ SideInfo: &s.AssertedSnapInfo("core").SideInfo,
+ Required: true,
+ Channel: "stable",
+ }, {
+ Path: s.expectedPath("required"),
+ SideInfo: &s.AssertedSnapInfo("required").SideInfo,
+ Required: true,
+ Channel: "stable",
+ },
+ })
+}
+
+func (s *seed16Suite) TestLoadMetaCore18Local(c *C) {
+ localRequired18Seed := &seed.Snap16{
+ Name: "required18",
+ Unasserted: true,
+ DevMode: true,
+ }
+ s.makeSeed(c, map[string]interface{}{
+ "base": "core18",
+ "kernel": "pc-kernel=18",
+ "gadget": "pc=18",
+ "required-snaps": []interface{}{"core", "required18"},
+ }, snapdSeed, core18Seed, kernel18Seed, gadget18Seed, localRequired18Seed)
+
+ err := s.seed16.LoadAssertions(s.db, s.commitTo)
+ c.Assert(err, IsNil)
+
+ err = s.seed16.LoadMeta(s.perfTimings)
+ c.Assert(err, IsNil)
+
+ essSnaps := s.seed16.EssentialSnaps()
+ c.Check(essSnaps, HasLen, 4)
+
+ c.Check(essSnaps, DeepEquals, []*seed.Snap{
+ {
+ Path: s.expectedPath("snapd"),
+ SideInfo: &s.AssertedSnapInfo("snapd").SideInfo,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
+ }, {
+ Path: s.expectedPath("core18"),
+ SideInfo: &s.AssertedSnapInfo("core18").SideInfo,
+ Essential: true,
+ Required: true,
+ Channel: "stable",
+ }, {
+ Path: s.expectedPath("pc-kernel"),
+ SideInfo: &s.AssertedSnapInfo("pc-kernel").SideInfo,
+ Essential: true,
+ Required: true,
+ Channel: "18",
+ }, {
+ Path: s.expectedPath("pc"),
+ SideInfo: &s.AssertedSnapInfo("pc").SideInfo,
+ Essential: true,
+ Required: true,
+ Channel: "18",
+ },
+ })
+
+ runSnaps, err := s.seed16.ModeSnaps("run")
+ c.Assert(err, IsNil)
+ c.Check(runSnaps, HasLen, 1)
+
+ c.Check(runSnaps, DeepEquals, []*seed.Snap{
+ {
+ Path: filepath.Join(s.seedDir, "snaps", "required18_1.0_all.snap"),
+ SideInfo: &snap.SideInfo{RealName: "required18"},
+ Required: true,
+ DevMode: true,
+ },
+ })
+}
+
+func (s *seed16Suite) TestLoadMetaCore18StoreInfo(c *C) {
+ s.makeSeed(c, map[string]interface{}{
+ "base": "core18",
+ "kernel": "pc-kernel=18",
+ "gadget": "pc=18",
+ }, snapdSeed, core18Seed, kernel18Seed, gadget18Seed, privateSnapSeed, contactableSnapSeed)
+
+ err := s.seed16.LoadAssertions(s.db, s.commitTo)
+ c.Assert(err, IsNil)
+
+ err = s.seed16.LoadMeta(s.perfTimings)
+ c.Assert(err, IsNil)
+
+ essSnaps := s.seed16.EssentialSnaps()
+ c.Check(essSnaps, HasLen, 4)
+
+ runSnaps, err := s.seed16.ModeSnaps("run")
+ c.Assert(err, IsNil)
+ c.Check(runSnaps, HasLen, 2)
+
+ privateSnapSideInfo := s.AssertedSnapInfo("private-snap").SideInfo
+ privateSnapSideInfo.Private = true
+ contactableSnapSideInfo := s.AssertedSnapInfo("contactable-snap").SideInfo
+ contactableSnapSideInfo.Contact = "author@example.com"
+
+ // these are not sorted by type, firstboot will do that
+ c.Check(runSnaps, DeepEquals, []*seed.Snap{
+ {
+ Path: s.expectedPath("private-snap"),
+ SideInfo: &privateSnapSideInfo,
+ Channel: "stable",
+ }, {
+ Path: s.expectedPath("contactable-snap"),
+ SideInfo: &contactableSnapSideInfo,
+ Channel: "stable",
+ },
+ })
+}
+
+func (s *seed16Suite) TestLoadMetaBrokenSeed(c *C) {
+ seedSnap16s := s.makeSeed(c, map[string]interface{}{
+ "base": "core18",
+ "kernel": "pc-kernel=18",
+ "gadget": "pc=18",
+ "required-snaps": []interface{}{"required18"},
+ }, snapdSeed, core18Seed, kernel18Seed, gadget18Seed, required18Seed)
+
+ otherSnapFile := snaptest.MakeTestSnapWithFiles(c, `name: other
+version: other`, nil)
+ otherFname := filepath.Base(otherSnapFile)
+ err := os.Rename(otherSnapFile, filepath.Join(s.seedDir, "snaps", otherFname))
+ c.Assert(err, IsNil)
+
+ const otherBaseGadget = `name: pc
+type: gadget
+base: other-base
+version: other-base
+`
+ otherBaseGadgetFname, obgDecl, obgRev := s.MakeAssertedSnap(c, otherBaseGadget, nil, snap.R(3), "canonical")
+ s.WriteAssertions("other-gadget.asserts", obgDecl, obgRev)
+
+ err = s.seed16.LoadAssertions(s.db, s.commitTo)
+ c.Assert(err, IsNil)
+
+ omit := func(which int) func([]*seed.Snap16) []*seed.Snap16 {
+ return func(snaps []*seed.Snap16) []*seed.Snap16 {
+ broken := make([]*seed.Snap16, 0, len(snaps)-1)
+ for i, sn := range snaps {
+ if i == which {
+ continue
+ }
+ broken = append(broken, sn)
+ }
+ return broken
+ }
+ }
+ replaceFile := func(snapName, fname string) func([]*seed.Snap16) []*seed.Snap16 {
+ return func(snaps []*seed.Snap16) []*seed.Snap16 {
+ for i := range snaps {
+ if snaps[i].Name != snapName {
+ continue
+ }
+ sn := *snaps[i]
+ sn.File = fname
+ snaps[i] = &sn
+ }
+ return snaps
+ }
+ }
+
+ tests := []struct {
+ breakSeed func([]*seed.Snap16) []*seed.Snap16
+ err string
+ }{
+ {omit(0), `essential snap "snapd" required by the model is missing in the seed`},
+ {omit(1), `essential snap "core18" required by the model is missing in the seed`},
+ {omit(2), `essential snap "pc-kernel" required by the model is missing in the seed`},
+ {omit(3), `essential snap "pc" required by the model is missing in the seed`},
+ // omitting "required18" currently doesn't error in any way
+ {replaceFile("core18", otherFname), `cannot find signatures with metadata for snap "core18".*`},
+ {replaceFile("required18", otherFname), `cannot find signatures with metadata for snap "required18".*`},
+ {replaceFile("core18", "not-existent"), `cannot compute snap .* digest: .*`},
+ {replaceFile("pc", otherBaseGadgetFname), `cannot use gadget snap because its base "other-base" is different from model base "core18"`},
+ }
+
+ for _, t := range tests {
+ testSeedSnap16s := make([]*seed.Snap16, 5)
+ copy(testSeedSnap16s, seedSnap16s)
+
+ testSeedSnap16s = t.breakSeed(testSeedSnap16s)
+ content, err := yaml.Marshal(map[string]interface{}{
+ "snaps": testSeedSnap16s,
+ })
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(filepath.Join(s.seedDir, "seed.yaml"), content, 0644)
+ c.Assert(err, IsNil)
+
+ c.Check(s.seed16.LoadMeta(s.perfTimings), ErrorMatches, t.err)
+ }
+}
diff -Nru snapd-2.41+19.10.1/seed/seed.go snapd-2.42.1+19.10/seed/seed.go
--- snapd-2.41+19.10.1/seed/seed.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/seed/seed.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,98 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+// Package seed implements loading and validating of seed data.
+package seed
+
+import (
+ "errors"
+
+ "github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/timings"
+)
+
+var (
+ ErrNoAssertions = errors.New("no seed assertions")
+ ErrNoMeta = errors.New("no seed metadata")
+)
+
+// Snap holds the details of a snap in a seed.
+type Snap struct {
+ Path string
+
+ SideInfo *snap.SideInfo
+
+ Essential bool
+ Required bool
+
+ // options
+ Channel string
+ DevMode bool
+ Classic bool
+}
+
+func (s *Snap) SnapName() string {
+ return s.SideInfo.RealName
+}
+
+func (s *Snap) ID() string {
+ return s.SideInfo.SnapID
+}
+
+// Seed supports loading assertions and seed snaps' metadata.
+type Seed interface {
+ // LoadAssertions loads all assertions from the seed with
+ // cross-checks. A read-only view on an assertions database
+ // can be passed in together with a commitTo function which
+ // will be used to commit the assertions to the underlying
+ // database. If db is nil an internal temporary database will
+ // be setup instead. ErrNoAssertions will be returned if there
+ // is no assertions directory in the seed, this is legitimate
+ // only on classic.
+ LoadAssertions(db asserts.RODatabase, commitTo func(*asserts.Batch) error) error
+
+ // Model returns the seed provided model assertion. It is an
+ // error to call Model before LoadAssertions.
+ Model() (*asserts.Model, error)
+
+ // LoadMeta loads the seed and seed's snaps metadata. It can
+ // return ErrNoMeta if there is no metadata nor snaps in the
+ // seed, this is legitimate only on classic. It is an error to
+ // call LoadMeta before LoadAssertions.
+ LoadMeta(tm timings.Measurer) error
+
+ // UsesSnapdSnap returns whether the system as defined by the
+ // seed will use the snapd snap, after LoadMeta.
+ UsesSnapdSnap() bool
+
+ // EssentialSnaps returns the essential snaps as defined by
+ // the seed, after LoadMeta.
+ EssentialSnaps() []*Snap
+
+ // ModeSnaps returns the snaps that should be available
+ // in the given mode as defined by the seed, after LoadMeta.
+ ModeSnaps(mode string) ([]*Snap, error)
+}
+
+// Open returns a Seed implementation for the seed at seedDir.
+// TODO: more parameters for the Core20 case
+func Open(seedDir string) (Seed, error) {
+ return &seed16{seedDir: seedDir}, nil
+}
diff -Nru snapd-2.41+19.10.1/seed/seedtest/seedtest.go snapd-2.42.1+19.10/seed/seedtest/seedtest.go
--- snapd-2.41+19.10.1/seed/seedtest/seedtest.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/seed/seedtest/seedtest.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,177 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2015-2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package seedtest
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/asserts/assertstest"
+ "github.com/snapcore/snapd/asserts/sysdb"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/snaptest"
+)
+
+// SeedSnaps helps creating snaps for a seed.
+type SeedSnaps struct {
+ StoreSigning *assertstest.StoreStack
+ Brands *assertstest.SigningAccounts
+
+ // DB will be populated with snap assertions if not nil
+ DB *asserts.Database
+
+ snaps map[string]string
+ infos map[string]*snap.Info
+}
+
+type Cleaner interface {
+ AddCleanup(func())
+}
+
+// SetupAssertSigning initializes StoreSigning for storeBrandID and Brands.
+func (ss *SeedSnaps) SetupAssertSigning(storeBrandID string, cleaner Cleaner) {
+ ss.StoreSigning = assertstest.NewStoreStack(storeBrandID, nil)
+ cleaner.AddCleanup(sysdb.InjectTrusted(ss.StoreSigning.Trusted))
+
+ ss.Brands = assertstest.NewSigningAccounts(ss.StoreSigning)
+}
+
+func (ss *SeedSnaps) AssertedSnapID(snapName string) string {
+ cleanedName := strings.Replace(snapName, "-", "", -1)
+ return (cleanedName + strings.Repeat("id", 16)[len(cleanedName):])
+}
+
+func (ss *SeedSnaps) MakeAssertedSnap(c *C, snapYaml string, files [][]string, revision snap.Revision, developerID string) (*asserts.SnapDeclaration, *asserts.SnapRevision) {
+ info, err := snap.InfoFromSnapYaml([]byte(snapYaml))
+ c.Assert(err, IsNil)
+ snapName := info.SnapName()
+
+ snapFile := snaptest.MakeTestSnapWithFiles(c, snapYaml, files)
+
+ snapID := ss.AssertedSnapID(snapName)
+ declA, err := ss.StoreSigning.Sign(asserts.SnapDeclarationType, map[string]interface{}{
+ "series": "16",
+ "snap-id": snapID,
+ "publisher-id": developerID,
+ "snap-name": snapName,
+ "timestamp": time.Now().UTC().Format(time.RFC3339),
+ }, nil, "")
+ c.Assert(err, IsNil)
+
+ sha3_384, size, err := asserts.SnapFileSHA3_384(snapFile)
+ c.Assert(err, IsNil)
+
+ revA, err := ss.StoreSigning.Sign(asserts.SnapRevisionType, map[string]interface{}{
+ "snap-sha3-384": sha3_384,
+ "snap-size": fmt.Sprintf("%d", size),
+ "snap-id": snapID,
+ "developer-id": developerID,
+ "snap-revision": revision.String(),
+ "timestamp": time.Now().UTC().Format(time.RFC3339),
+ }, nil, "")
+ c.Assert(err, IsNil)
+
+ if !revision.Unset() {
+ info.SnapID = snapID
+ info.Revision = revision
+ }
+
+ if ss.DB != nil {
+ err := ss.DB.Add(declA)
+ c.Assert(err, IsNil)
+ err = ss.DB.Add(revA)
+ c.Assert(err, IsNil)
+ }
+
+ if ss.snaps == nil {
+ ss.snaps = make(map[string]string)
+ ss.infos = make(map[string]*snap.Info)
+ }
+
+ ss.snaps[snapName] = snapFile
+ info.SideInfo.RealName = snapName
+ ss.infos[snapName] = info
+
+ return declA.(*asserts.SnapDeclaration), revA.(*asserts.SnapRevision)
+}
+
+func (ss *SeedSnaps) AssertedSnap(snapName string) (snapFile string) {
+ return ss.snaps[snapName]
+}
+
+func (ss *SeedSnaps) AssertedSnapInfo(snapName string) *snap.Info {
+ return ss.infos[snapName]
+}
+
+// TestingSeed helps setting up a populated testing seed.
+type TestingSeed struct {
+ SeedSnaps
+
+ SnapsDir string
+ AssertsDir string
+}
+
+func (s *TestingSeed) MakeAssertedSnap(c *C, snapYaml string, files [][]string, revision snap.Revision, developerID string) (snapFname string, snapDecl *asserts.SnapDeclaration, snapRev *asserts.SnapRevision) {
+ decl, rev := s.SeedSnaps.MakeAssertedSnap(c, snapYaml, files, revision, developerID)
+
+ snapFile := s.snaps[decl.SnapName()]
+
+ snapFname = filepath.Base(snapFile)
+ targetFile := filepath.Join(s.SnapsDir, snapFname)
+ err := os.Rename(snapFile, targetFile)
+ c.Assert(err, IsNil)
+
+ return snapFname, decl, rev
+}
+
+func (s *TestingSeed) MakeModelAssertionChain(brandID, model string, extras ...map[string]interface{}) []asserts.Assertion {
+ assertChain := []asserts.Assertion{}
+ modelA := s.Brands.Model(brandID, model, extras...)
+
+ assertChain = append(assertChain, s.Brands.Account(modelA.BrandID()))
+ assertChain = append(assertChain, s.Brands.AccountKey(modelA.BrandID()))
+ assertChain = append(assertChain, modelA)
+
+ storeAccountKey := s.StoreSigning.StoreAccountKey("")
+ assertChain = append(assertChain, storeAccountKey)
+ return assertChain
+}
+
+func (s *TestingSeed) WriteAssertions(fn string, assertions ...asserts.Assertion) {
+ multifn := filepath.Join(s.AssertsDir, fn)
+ f, err := os.OpenFile(multifn, os.O_CREATE|os.O_WRONLY, 0644)
+ if err != nil {
+ panic(err)
+ }
+ defer f.Close()
+ enc := asserts.NewEncoder(f)
+ for _, a := range assertions {
+ err := enc.Encode(a)
+ if err != nil {
+ panic(err)
+ }
+ }
+}
diff -Nru snapd-2.41+19.10.1/seed/seed_yaml.go snapd-2.42.1+19.10/seed/seed_yaml.go
--- snapd-2.41+19.10.1/seed/seed_yaml.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/seed/seed_yaml.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,120 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2014-2016 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package seed
+
+import (
+ "fmt"
+ "io/ioutil"
+ "strings"
+
+ "gopkg.in/yaml.v2"
+
+ "github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/snap/channel"
+ "github.com/snapcore/snapd/snap/naming"
+)
+
+// Snap points to a snap in the seed to install, together with
+// assertions (or alone if unasserted is true) it will be used to
+// drive the installation and ultimately set SideInfo/SnapState for it.
+// TODO: make this internal
+type Snap16 struct {
+ Name string `yaml:"name"`
+
+ // cross-reference/audit
+ SnapID string `yaml:"snap-id,omitempty"`
+
+ // bits that are orthongonal/not in assertions
+ Channel string `yaml:"channel,omitempty"`
+ DevMode bool `yaml:"devmode,omitempty"`
+ Classic bool `yaml:"classic,omitempty"`
+
+ Private bool `yaml:"private,omitempty"`
+
+ Contact string `yaml:"contact,omitempty"`
+
+ // no assertions are available in the seed for this snap
+ Unasserted bool `yaml:"unasserted,omitempty"`
+
+ File string `yaml:"file"`
+}
+
+// TODO: make all of this internal only
+
+type Seed16 struct {
+ Snaps []*Snap16 `yaml:"snaps"`
+}
+
+func ReadYaml(fn string) (*Seed16, error) {
+ errPrefix := "cannot read seed yaml"
+
+ yamlData, err := ioutil.ReadFile(fn)
+ if err != nil {
+ return nil, fmt.Errorf("%s: %v", errPrefix, err)
+ }
+
+ var seed Seed16
+ if err := yaml.Unmarshal(yamlData, &seed); err != nil {
+ return nil, fmt.Errorf("%s: cannot unmarshal %q: %s", errPrefix, yamlData, err)
+ }
+
+ seenNames := make(map[string]bool, len(seed.Snaps))
+ // validate
+ for _, sn := range seed.Snaps {
+ if sn == nil {
+ return nil, fmt.Errorf("%s: empty element in seed", errPrefix)
+ }
+ // TODO: check if it's a parallel install explicitly,
+ // need to move *Instance* helpers from snap to naming
+ if err := naming.ValidateSnap(sn.Name); err != nil {
+ return nil, fmt.Errorf("%s: %v", errPrefix, err)
+ }
+ if sn.Channel != "" {
+ if _, err := channel.Parse(sn.Channel, ""); err != nil {
+ return nil, fmt.Errorf("%s: %v", errPrefix, err)
+ }
+ }
+ if sn.File == "" {
+ return nil, fmt.Errorf(`%s: "file" attribute for %q cannot be empty`, errPrefix, sn.Name)
+ }
+ if strings.Contains(sn.File, "/") {
+ return nil, fmt.Errorf("%s: %q must be a filename, not a path", errPrefix, sn.File)
+ }
+
+ // make sure names and file names are unique
+ if seenNames[sn.Name] {
+ return nil, fmt.Errorf("%s: snap name %q must be unique", errPrefix, sn.Name)
+ }
+ seenNames[sn.Name] = true
+ }
+
+ return &seed, nil
+}
+
+func (seed *Seed16) Write(seedFn string) error {
+ data, err := yaml.Marshal(&seed)
+ if err != nil {
+ return err
+ }
+ if err := osutil.AtomicWriteFile(seedFn, data, 0644, 0); err != nil {
+ return err
+ }
+ return nil
+}
diff -Nru snapd-2.41+19.10.1/seed/seed_yaml_test.go snapd-2.42.1+19.10/seed/seed_yaml_test.go
--- snapd-2.41+19.10.1/seed/seed_yaml_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/seed/seed_yaml_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,166 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2014-2016 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package seed_test
+
+import (
+ "io/ioutil"
+ "path/filepath"
+ "testing"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/seed"
+)
+
+func Test(t *testing.T) { TestingT(t) }
+
+type seedYamlTestSuite struct{}
+
+var _ = Suite(&seedYamlTestSuite{})
+
+var mockSeedYaml = []byte(`
+snaps:
+ - name: foo
+ snap-id: snapidsnapidsnapid
+ channel: stable
+ devmode: true
+ file: foo_1.0_all.snap
+ - name: local
+ unasserted: true
+ file: local.snap
+`)
+
+func (s *seedYamlTestSuite) TestSimple(c *C) {
+ fn := filepath.Join(c.MkDir(), "seed.yaml")
+ err := ioutil.WriteFile(fn, mockSeedYaml, 0644)
+ c.Assert(err, IsNil)
+
+ seedYaml, err := seed.ReadYaml(fn)
+ c.Assert(err, IsNil)
+ c.Assert(seedYaml.Snaps, HasLen, 2)
+ c.Assert(seedYaml.Snaps[0], DeepEquals, &seed.Snap16{
+ File: "foo_1.0_all.snap",
+ Name: "foo",
+ SnapID: "snapidsnapidsnapid",
+
+ Channel: "stable",
+ DevMode: true,
+ })
+ c.Assert(seedYaml.Snaps[1], DeepEquals, &seed.Snap16{
+ File: "local.snap",
+ Name: "local",
+ Unasserted: true,
+ })
+}
+
+var badMockSeedYaml = []byte(`
+snaps:
+ - name: foo
+ file: foo/bar.snap
+`)
+
+func (s *seedYamlTestSuite) TestNoPathAllowed(c *C) {
+ fn := filepath.Join(c.MkDir(), "seed.yaml")
+ err := ioutil.WriteFile(fn, badMockSeedYaml, 0644)
+ c.Assert(err, IsNil)
+
+ _, err = seed.ReadYaml(fn)
+ c.Assert(err, ErrorMatches, `cannot read seed yaml: "foo/bar.snap" must be a filename, not a path`)
+}
+
+func (s *seedYamlTestSuite) TestDuplicatedSnapName(c *C) {
+ fn := filepath.Join(c.MkDir(), "seed.yaml")
+ err := ioutil.WriteFile(fn, []byte(`
+snaps:
+ - name: foo
+ channel: stable
+ file: foo_1.0_all.snap
+ - name: foo
+ channel: edge
+ file: bar_1.0_all.snap
+`), 0644)
+ c.Assert(err, IsNil)
+
+ _, err = seed.ReadYaml(fn)
+ c.Assert(err, ErrorMatches, `cannot read seed yaml: snap name "foo" must be unique`)
+}
+
+func (s *seedYamlTestSuite) TestValidateChannelUnhappy(c *C) {
+ fn := filepath.Join(c.MkDir(), "seed.yaml")
+ err := ioutil.WriteFile(fn, []byte(`
+snaps:
+ - name: foo
+ channel: invalid/channel/
+`), 0644)
+ c.Assert(err, IsNil)
+
+ _, err = seed.ReadYaml(fn)
+ c.Assert(err, ErrorMatches, `cannot read seed yaml: invalid risk in channel name: invalid/channel/`)
+}
+
+func (s *seedYamlTestSuite) TestValidateNameUnhappy(c *C) {
+ fn := filepath.Join(c.MkDir(), "seed.yaml")
+ err := ioutil.WriteFile(fn, []byte(`
+snaps:
+ - name: invalid--name
+ file: ./foo.snap
+`), 0644)
+ c.Assert(err, IsNil)
+
+ _, err = seed.ReadYaml(fn)
+ c.Assert(err, ErrorMatches, `cannot read seed yaml: invalid snap name: "invalid--name"`)
+}
+
+func (s *seedYamlTestSuite) TestValidateNameInstanceUnsupported(c *C) {
+ fn := filepath.Join(c.MkDir(), "seed.yaml")
+ err := ioutil.WriteFile(fn, []byte(`
+snaps:
+ - name: foo_1
+ file: ./foo.snap
+`), 0644)
+ c.Assert(err, IsNil)
+
+ _, err = seed.ReadYaml(fn)
+ c.Assert(err, ErrorMatches, `cannot read seed yaml: invalid snap name: "foo_1"`)
+}
+
+func (s *seedYamlTestSuite) TestValidateNameMissing(c *C) {
+ fn := filepath.Join(c.MkDir(), "seed.yaml")
+ err := ioutil.WriteFile(fn, []byte(`
+snaps:
+ - file: ./foo.snap
+`), 0644)
+ c.Assert(err, IsNil)
+
+ _, err = seed.ReadYaml(fn)
+ c.Assert(err, ErrorMatches, `cannot read seed yaml: invalid snap name: ""`)
+}
+
+func (s *seedYamlTestSuite) TestValidateFileMissing(c *C) {
+ fn := filepath.Join(c.MkDir(), "seed.yaml")
+ err := ioutil.WriteFile(fn, []byte(`
+snaps:
+ - name: foo
+`), 0644)
+ c.Assert(err, IsNil)
+
+ _, err = seed.ReadYaml(fn)
+ c.Assert(err, ErrorMatches, `cannot read seed yaml: "file" attribute for "foo" cannot be empty`)
+}
diff -Nru snapd-2.41+19.10.1/seed/validate.go snapd-2.42.1+19.10/seed/validate.go
--- snapd-2.41+19.10.1/seed/validate.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/seed/validate.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,74 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2014-2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package seed
+
+import (
+ "bytes"
+ "fmt"
+ "path/filepath"
+
+ "github.com/snapcore/snapd/snap"
+)
+
+// ValidateFromYaml validates the given seed.yaml file and surrounding seed.
+func ValidateFromYaml(seedYamlFile string) error {
+ seed, err := ReadYaml(seedYamlFile)
+ if err != nil {
+ return err
+ }
+
+ var errs []error
+ // read the snaps info
+ snapInfos := make(map[string]*snap.Info)
+ for _, seedSnap := range seed.Snaps {
+ fn := filepath.Join(filepath.Dir(seedYamlFile), "snaps", seedSnap.File)
+ snapf, err := snap.Open(fn)
+ if err != nil {
+ errs = append(errs, err)
+ } else {
+ info, err := snap.ReadInfoFromSnapFile(snapf, nil)
+ if err != nil {
+ errs = append(errs, fmt.Errorf("cannot use snap %s: %v", fn, err))
+ } else {
+ snapInfos[info.InstanceName()] = info
+ }
+ }
+ }
+
+ // ensure we have either "core" or "snapd"
+ _, haveCore := snapInfos["core"]
+ _, haveSnapd := snapInfos["snapd"]
+ if !(haveCore || haveSnapd) {
+ errs = append(errs, fmt.Errorf("the core or snapd snap must be part of the seed"))
+ }
+
+ if errs2 := snap.ValidateBasesAndProviders(snapInfos); errs2 != nil {
+ errs = append(errs, errs2...)
+ }
+ if errs != nil {
+ var buf bytes.Buffer
+ for _, err := range errs {
+ fmt.Fprintf(&buf, "\n- %s", err)
+ }
+ return fmt.Errorf("cannot validate seed:%s", buf.Bytes())
+ }
+
+ return nil
+}
diff -Nru snapd-2.41+19.10.1/seed/validate_test.go snapd-2.42.1+19.10/seed/validate_test.go
--- snapd-2.41+19.10.1/seed/validate_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/seed/validate_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,264 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package seed_test
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/seed"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/snaptest"
+ "github.com/snapcore/snapd/snap/squashfs"
+ "github.com/snapcore/snapd/testutil"
+)
+
+type validateSuite struct {
+ testutil.BaseTest
+ root string
+}
+
+var _ = Suite(&validateSuite{})
+
+var coreYaml = `name: core
+version: 1.0
+type: os
+`
+
+const snapdYaml = `name: snapd
+version: 1.0
+type: snapd
+`
+
+const packageCore18 = `
+name: core18
+version: 18.04
+type: base
+`
+
+func (s *validateSuite) SetUpTest(c *C) {
+ s.BaseTest.SetUpTest(c)
+ s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
+
+ s.root = c.MkDir()
+
+ err := os.MkdirAll(filepath.Join(s.root, "snaps"), 0755)
+ c.Assert(err, IsNil)
+}
+
+func (s *validateSuite) makeSnapInSeed(c *C, snapYaml string) {
+ info, err := snap.InfoFromSnapYaml([]byte(snapYaml))
+ c.Assert(err, IsNil)
+
+ src := snaptest.MakeTestSnapWithFiles(c, snapYaml, nil)
+ dst := filepath.Join(s.root, "snaps", fmt.Sprintf("%s_%s.snap", info.SnapName(), snap.R(1)))
+ c.Assert(os.Rename(src, dst), IsNil)
+}
+
+func (s *validateSuite) makeSeedYaml(c *C, seedYaml string) string {
+ tmpf := filepath.Join(s.root, "seed.yaml")
+ err := ioutil.WriteFile(tmpf, []byte(seedYaml), 0644)
+ c.Assert(err, IsNil)
+ return tmpf
+}
+
+func (s *validateSuite) TestValidateFromYamlSnapHappy(c *C) {
+ s.makeSnapInSeed(c, coreYaml)
+ s.makeSnapInSeed(c, `name: gtk-common-themes
+version: 19.04`)
+ seedFn := s.makeSeedYaml(c, `
+snaps:
+ - name: core
+ channel: stable
+ file: core_1.snap
+ - name: gtk-common-themes
+ channel: stable/ubuntu-19.04
+ file: gtk-common-themes_1.snap
+`)
+
+ err := seed.ValidateFromYaml(seedFn)
+ c.Assert(err, IsNil)
+}
+
+func (s *validateSuite) TestValidateFromYamlSnapMissingBase(c *C) {
+ s.makeSnapInSeed(c, `name: need-base
+base: some-base
+version: 1.0`)
+ s.makeSnapInSeed(c, coreYaml)
+ seedFn := s.makeSeedYaml(c, `
+snaps:
+ - name: core
+ file: core_1.snap
+ - name: need-base
+ file: need-base_1.snap
+`)
+
+ err := seed.ValidateFromYaml(seedFn)
+ c.Assert(err, ErrorMatches, `cannot validate seed:
+- cannot use snap "need-base": base "some-base" is missing`)
+}
+
+func (s *validateSuite) TestValidateFromYamlSnapMissingDefaultProvider(c *C) {
+ s.makeSnapInSeed(c, coreYaml)
+ s.makeSnapInSeed(c, `name: need-df
+version: 1.0
+plugs:
+ gtk-3-themes:
+ interface: content
+ default-provider: gtk-common-themes
+`)
+ seedFn := s.makeSeedYaml(c, `
+snaps:
+ - name: core
+ file: core_1.snap
+ - name: need-df
+ file: need-df_1.snap
+`)
+
+ err := seed.ValidateFromYaml(seedFn)
+ c.Assert(err, ErrorMatches, `cannot validate seed:
+- cannot use snap "need-df": default provider "gtk-common-themes" is missing`)
+}
+
+func (s *validateSuite) TestValidateFromYamlSnapSnapdHappy(c *C) {
+ s.makeSnapInSeed(c, snapdYaml)
+ s.makeSnapInSeed(c, packageCore18)
+ s.makeSnapInSeed(c, `name: some-snap
+version: 1.0
+base: core18
+`)
+ seedFn := s.makeSeedYaml(c, `
+snaps:
+ - name: snapd
+ file: snapd_1.snap
+ - name: some-snap
+ file: some-snap_1.snap
+ - name: core18
+ file: core18_1.snap
+`)
+
+ err := seed.ValidateFromYaml(seedFn)
+ c.Assert(err, IsNil)
+}
+
+func (s *validateSuite) TestValidateFromYamlSnapMissingCore(c *C) {
+ s.makeSnapInSeed(c, snapdYaml)
+ s.makeSnapInSeed(c, `name: some-snap
+version: 1.0`)
+ seedFn := s.makeSeedYaml(c, `
+snaps:
+ - name: snapd
+ file: snapd_1.snap
+ - name: some-snap
+ file: some-snap_1.snap
+`)
+
+ err := seed.ValidateFromYaml(seedFn)
+ c.Assert(err, ErrorMatches, `cannot validate seed:
+- cannot use snap "some-snap": required snap "core" missing`)
+}
+
+func (s *validateSuite) TestValidateFromYamlSnapMissingSnapdAndCore(c *C) {
+ s.makeSnapInSeed(c, packageCore18)
+ s.makeSnapInSeed(c, `name: some-snap
+version: 1.0
+base: core18`)
+ seedFn := s.makeSeedYaml(c, `
+snaps:
+ - name: some-snap
+ file: some-snap_1.snap
+ - name: core18
+ file: core18_1.snap
+`)
+
+ err := seed.ValidateFromYaml(seedFn)
+ c.Assert(err, ErrorMatches, `cannot validate seed:
+- the core or snapd snap must be part of the seed`)
+}
+
+func (s *validateSuite) TestValidateFromYamlSnapMultipleErrors(c *C) {
+ s.makeSnapInSeed(c, `name: some-snap
+version: 1.0`)
+ seedFn := s.makeSeedYaml(c, `
+snaps:
+ - name: some-snap
+ file: some-snap_1.snap
+`)
+
+ err := seed.ValidateFromYaml(seedFn)
+ c.Assert(err, ErrorMatches, `cannot validate seed:
+- the core or snapd snap must be part of the seed
+- cannot use snap "some-snap": required snap "core" missing`)
+}
+
+func (s *validateSuite) TestValidateFromYamlSnapSnapMissing(c *C) {
+ s.makeSnapInSeed(c, coreYaml)
+ seedFn := s.makeSeedYaml(c, `
+snaps:
+ - name: core
+ file: core_1.snap
+ - name: some-snap
+ file: some-snap_1.snap
+`)
+
+ err := seed.ValidateFromYaml(seedFn)
+ c.Assert(err, ErrorMatches, `cannot validate seed:
+- cannot open snap: open /.*/snaps/some-snap_1.snap: no such file or directory`)
+}
+
+func (s *validateSuite) TestValidateFromYamlSnapSnapInvalid(c *C) {
+ s.makeSnapInSeed(c, coreYaml)
+
+ // "version" is missing in this yaml
+ snapBuildDir := c.MkDir()
+ snapYaml := `name: some-snap-invalid-yaml`
+ metaSnapYaml := filepath.Join(snapBuildDir, "meta", "snap.yaml")
+ err := os.MkdirAll(filepath.Dir(metaSnapYaml), 0755)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(metaSnapYaml, []byte(snapYaml), 0644)
+ c.Assert(err, IsNil)
+
+ // need to build the snap "manually" pack.Snap() will do validation
+ snapFilePath := filepath.Join(c.MkDir(), "some-snap-invalid-yaml_1.snap")
+ d := squashfs.New(snapFilePath)
+ err = d.Build(snapBuildDir, "app")
+ c.Assert(err, IsNil)
+
+ // put the broken snap in place
+ dst := filepath.Join(s.root, "snaps", "some-snap-invalid-yaml_1.snap")
+ err = os.Rename(snapFilePath, dst)
+ c.Assert(err, IsNil)
+
+ seedFn := s.makeSeedYaml(c, `
+snaps:
+ - name: core
+ file: core_1.snap
+ - name: some-snap-invalid-yaml
+ file: some-snap-invalid-yaml_1.snap
+`)
+
+ err = seed.ValidateFromYaml(seedFn)
+ c.Assert(err, ErrorMatches, `cannot validate seed:
+- cannot use snap /.*/snaps/some-snap-invalid-yaml_1.snap: invalid snap version: cannot be empty`)
+}
diff -Nru snapd-2.41+19.10.1/snap/channel/channel.go snapd-2.42.1+19.10/snap/channel/channel.go
--- snapd-2.41+19.10.1/snap/channel/channel.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/snap/channel/channel.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,264 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2018-2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package channel
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+
+ "github.com/snapcore/snapd/arch"
+ "github.com/snapcore/snapd/strutil"
+)
+
+var channelRisks = []string{"stable", "candidate", "beta", "edge"}
+
+// Channel identifies and describes completely a store channel.
+type Channel struct {
+ Architecture string `json:"architecture"`
+ Name string `json:"name"`
+ Track string `json:"track"`
+ Risk string `json:"risk"`
+ Branch string `json:"branch,omitempty"`
+}
+
+// ParseVerbatim parses a string representing a store channel and
+// includes the given architecture, if architecture is "" the system
+// architecture is included. The channel representation is not normalized.
+// Parse() should be used in most cases.
+func ParseVerbatim(s string, architecture string) (Channel, error) {
+ if s == "" {
+ return Channel{}, fmt.Errorf("channel name cannot be empty")
+ }
+ p := strings.Split(s, "/")
+ var risk, track, branch *string
+ switch len(p) {
+ default:
+ return Channel{}, fmt.Errorf("channel name has too many components: %s", s)
+ case 3:
+ track, risk, branch = &p[0], &p[1], &p[2]
+ case 2:
+ if strutil.ListContains(channelRisks, p[0]) {
+ risk, branch = &p[0], &p[1]
+ } else {
+ track, risk = &p[0], &p[1]
+ }
+ case 1:
+ if strutil.ListContains(channelRisks, p[0]) {
+ risk = &p[0]
+ } else {
+ track = &p[0]
+ }
+ }
+
+ if architecture == "" {
+ architecture = arch.DpkgArchitecture()
+ }
+
+ ch := Channel{
+ Architecture: architecture,
+ }
+
+ if risk != nil {
+ if !strutil.ListContains(channelRisks, *risk) {
+ return Channel{}, fmt.Errorf("invalid risk in channel name: %s", s)
+ }
+ ch.Risk = *risk
+ }
+ if track != nil {
+ if *track == "" {
+ return Channel{}, fmt.Errorf("invalid track in channel name: %s", s)
+ }
+ ch.Track = *track
+ }
+ if branch != nil {
+ if *branch == "" {
+ return Channel{}, fmt.Errorf("invalid branch in channel name: %s", s)
+ }
+ ch.Branch = *branch
+ }
+
+ return ch, nil
+}
+
+// Parse parses a string representing a store channel and includes given
+// architecture, , if architecture is "" the system architecture is included.
+// The returned channel's track, risk and name are normalized.
+func Parse(s string, architecture string) (Channel, error) {
+ channel, err := ParseVerbatim(s, architecture)
+ if err != nil {
+ return Channel{}, err
+ }
+ return channel.Clean(), nil
+}
+
+// Clean returns a Channel with a normalized track, risk and name.
+func (c Channel) Clean() Channel {
+ track := c.Track
+ risk := c.Risk
+
+ if track == "latest" {
+ track = ""
+ }
+ if risk == "" {
+ risk = "stable"
+ }
+
+ // normalized name
+ name := risk
+ if track != "" {
+ name = track + "/" + name
+ }
+ if c.Branch != "" {
+ name = name + "/" + c.Branch
+ }
+
+ return Channel{
+ Architecture: c.Architecture,
+ Name: name,
+ Track: track,
+ Risk: risk,
+ Branch: c.Branch,
+ }
+}
+
+func (c Channel) String() string {
+ return c.Name
+}
+
+// Full returns the full name of the channel, inclusive the default track "latest".
+func (c *Channel) Full() string {
+ if c.Track == "" {
+ return "latest/" + c.Name
+ }
+ return c.String()
+}
+
+// VerbatimTrackOnly returns whether the channel represents a track only.
+func (c *Channel) VerbatimTrackOnly() bool {
+ return c.Track != "" && c.Risk == "" && c.Branch == ""
+}
+
+// VerbatimRiskOnly returns whether the channel represents a risk only.
+func (c *Channel) VerbatimRiskOnly() bool {
+ return c.Track == "" && c.Risk != "" && c.Branch == ""
+}
+
+func riskLevel(risk string) int {
+ for i, r := range channelRisks {
+ if r == risk {
+ return i
+ }
+ }
+ return -1
+}
+
+// ChannelMatch represents on which fields two channels are matching.
+type ChannelMatch struct {
+ Architecture bool
+ Track bool
+ Risk bool
+}
+
+// String returns the string represantion of the match, results can be:
+// "architecture:track:risk"
+// "architecture:track"
+// "architecture:risk"
+// "track:risk"
+// "architecture"
+// "track"
+// "risk"
+// ""
+func (cm ChannelMatch) String() string {
+ matching := []string{}
+ if cm.Architecture {
+ matching = append(matching, "architecture")
+ }
+ if cm.Track {
+ matching = append(matching, "track")
+ }
+ if cm.Risk {
+ matching = append(matching, "risk")
+ }
+ return strings.Join(matching, ":")
+
+}
+
+// Match returns a ChannelMatch of which fields among architecture,track,risk match between c and c1 store channels, risk is matched taking channel inheritance into account and considering c the requested channel.
+func (c *Channel) Match(c1 *Channel) ChannelMatch {
+ requestedRiskLevel := riskLevel(c.Risk)
+ rl1 := riskLevel(c1.Risk)
+ return ChannelMatch{
+ Architecture: c.Architecture == c1.Architecture,
+ Track: c.Track == c1.Track,
+ Risk: requestedRiskLevel >= rl1,
+ }
+}
+
+// Resolve resolves newChannel wrt channel, this means if newChannel
+// is risk/branch only it will preserve the track of channel. It
+// assumes that if both are not empty, channel is parseable.
+func Resolve(channel, newChannel string) (string, error) {
+ if newChannel == "" {
+ return channel, nil
+ }
+ if channel == "" {
+ return newChannel, nil
+ }
+ ch, err := ParseVerbatim(channel, "-")
+ if err != nil {
+ return "", err
+ }
+ p := strings.Split(newChannel, "/")
+ if strutil.ListContains(channelRisks, p[0]) && ch.Track != "" {
+ // risk/branch inherits the track if any
+ return ch.Track + "/" + newChannel, nil
+ }
+ return newChannel, nil
+}
+
+var ErrLockedTrackSwitch = errors.New("cannot switch locked track")
+
+// ResolveLocked resolves newChannel wrt a locked track, newChannel
+// can only be risk/branch-only or have the same track, otherwise
+// ErrLockedTrackSwitch is returned.
+func ResolveLocked(track, newChannel string) (string, error) {
+ if track == "" {
+ return newChannel, nil
+ }
+ ch, err := ParseVerbatim(track, "-")
+ if err != nil || !ch.VerbatimTrackOnly() {
+ return "", fmt.Errorf("invalid locked track: %s", track)
+ }
+ if newChannel == "" {
+ return track, nil
+ }
+ trackPrefix := ch.Track + "/"
+ p := strings.Split(newChannel, "/")
+ if strutil.ListContains(channelRisks, p[0]) && ch.Track != "" {
+ // risk/branch inherits the track if any
+ return trackPrefix + newChannel, nil
+ }
+ if newChannel != track && !strings.HasPrefix(newChannel, trackPrefix) {
+ // the track is locked/pinned
+ return "", ErrLockedTrackSwitch
+ }
+ return newChannel, nil
+}
diff -Nru snapd-2.41+19.10.1/snap/channel/channel_test.go snapd-2.42.1+19.10/snap/channel/channel_test.go
--- snapd-2.41+19.10.1/snap/channel/channel_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/snap/channel/channel_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,355 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2018 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package channel_test
+
+import (
+ "testing"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/arch"
+ "github.com/snapcore/snapd/snap/channel"
+)
+
+func Test(t *testing.T) { TestingT(t) }
+
+type storeChannelSuite struct{}
+
+var _ = Suite(&storeChannelSuite{})
+
+func (s storeChannelSuite) TestParse(c *C) {
+ ch, err := channel.Parse("stable", "")
+ c.Assert(err, IsNil)
+ c.Check(ch, DeepEquals, channel.Channel{
+ Architecture: arch.DpkgArchitecture(),
+ Name: "stable",
+ Track: "",
+ Risk: "stable",
+ Branch: "",
+ })
+
+ ch, err = channel.Parse("latest/stable", "")
+ c.Assert(err, IsNil)
+ c.Check(ch, DeepEquals, channel.Channel{
+ Architecture: arch.DpkgArchitecture(),
+ Name: "stable",
+ Track: "",
+ Risk: "stable",
+ Branch: "",
+ })
+
+ ch, err = channel.Parse("1.0/edge", "")
+ c.Assert(err, IsNil)
+ c.Check(ch, DeepEquals, channel.Channel{
+ Architecture: arch.DpkgArchitecture(),
+ Name: "1.0/edge",
+ Track: "1.0",
+ Risk: "edge",
+ Branch: "",
+ })
+
+ ch, err = channel.Parse("1.0", "")
+ c.Assert(err, IsNil)
+ c.Check(ch, DeepEquals, channel.Channel{
+ Architecture: arch.DpkgArchitecture(),
+ Name: "1.0/stable",
+ Track: "1.0",
+ Risk: "stable",
+ Branch: "",
+ })
+
+ ch, err = channel.Parse("1.0/beta/foo", "")
+ c.Assert(err, IsNil)
+ c.Check(ch, DeepEquals, channel.Channel{
+ Architecture: arch.DpkgArchitecture(),
+ Name: "1.0/beta/foo",
+ Track: "1.0",
+ Risk: "beta",
+ Branch: "foo",
+ })
+
+ ch, err = channel.Parse("candidate/foo", "")
+ c.Assert(err, IsNil)
+ c.Check(ch, DeepEquals, channel.Channel{
+ Architecture: arch.DpkgArchitecture(),
+ Name: "candidate/foo",
+ Track: "",
+ Risk: "candidate",
+ Branch: "foo",
+ })
+
+ ch, err = channel.Parse("candidate/foo", "other-arch")
+ c.Assert(err, IsNil)
+ c.Check(ch, DeepEquals, channel.Channel{
+ Architecture: "other-arch",
+ Name: "candidate/foo",
+ Track: "",
+ Risk: "candidate",
+ Branch: "foo",
+ })
+}
+
+func mustParse(c *C, channelStr string) channel.Channel {
+ ch, err := channel.Parse(channelStr, "")
+ c.Assert(err, IsNil)
+ return ch
+}
+
+func (s storeChannelSuite) TestParseVerbatim(c *C) {
+ ch, err := channel.ParseVerbatim("sometrack", "")
+ c.Assert(err, IsNil)
+ c.Check(ch, DeepEquals, channel.Channel{
+ Architecture: arch.DpkgArchitecture(),
+ Track: "sometrack",
+ })
+ c.Check(ch.VerbatimTrackOnly(), Equals, true)
+ c.Check(ch.VerbatimRiskOnly(), Equals, false)
+ c.Check(mustParse(c, "sometrack"), DeepEquals, ch.Clean())
+
+ ch, err = channel.ParseVerbatim("latest", "")
+ c.Assert(err, IsNil)
+ c.Check(ch, DeepEquals, channel.Channel{
+ Architecture: arch.DpkgArchitecture(),
+ Track: "latest",
+ })
+ c.Check(ch.VerbatimTrackOnly(), Equals, true)
+ c.Check(ch.VerbatimRiskOnly(), Equals, false)
+ c.Check(mustParse(c, "latest"), DeepEquals, ch.Clean())
+
+ ch, err = channel.ParseVerbatim("edge", "")
+ c.Assert(err, IsNil)
+ c.Check(ch, DeepEquals, channel.Channel{
+ Architecture: arch.DpkgArchitecture(),
+ Risk: "edge",
+ })
+ c.Check(ch.VerbatimTrackOnly(), Equals, false)
+ c.Check(ch.VerbatimRiskOnly(), Equals, true)
+ c.Check(mustParse(c, "edge"), DeepEquals, ch.Clean())
+
+ ch, err = channel.ParseVerbatim("latest/stable", "")
+ c.Assert(err, IsNil)
+ c.Check(ch, DeepEquals, channel.Channel{
+ Architecture: arch.DpkgArchitecture(),
+ Track: "latest",
+ Risk: "stable",
+ })
+ c.Check(ch.VerbatimTrackOnly(), Equals, false)
+ c.Check(ch.VerbatimRiskOnly(), Equals, false)
+ c.Check(mustParse(c, "latest/stable"), DeepEquals, ch.Clean())
+
+ ch, err = channel.ParseVerbatim("latest/stable/foo", "")
+ c.Assert(err, IsNil)
+ c.Check(ch, DeepEquals, channel.Channel{
+ Architecture: arch.DpkgArchitecture(),
+ Track: "latest",
+ Risk: "stable",
+ Branch: "foo",
+ })
+ c.Check(ch.VerbatimTrackOnly(), Equals, false)
+ c.Check(ch.VerbatimRiskOnly(), Equals, false)
+ c.Check(mustParse(c, "latest/stable/foo"), DeepEquals, ch.Clean())
+}
+
+func (s storeChannelSuite) TestClean(c *C) {
+ ch := channel.Channel{
+ Architecture: "arm64",
+ Track: "latest",
+ Name: "latest/stable",
+ Risk: "stable",
+ }
+
+ cleanedCh := ch.Clean()
+ c.Check(cleanedCh, Not(DeepEquals), c)
+ c.Check(cleanedCh, DeepEquals, channel.Channel{
+ Architecture: "arm64",
+ Track: "",
+ Name: "stable",
+ Risk: "stable",
+ })
+}
+
+func (s storeChannelSuite) TestParseErrors(c *C) {
+ for _, tc := range []struct {
+ channel string
+ err string
+ }{
+ {"", "channel name cannot be empty"},
+ {"1.0////", "channel name has too many components: 1.0////"},
+ {"1.0/cand", "invalid risk in channel name: 1.0/cand"},
+ {"fix//hotfix", "invalid risk in channel name: fix//hotfix"},
+ {"/stable/", "invalid track in channel name: /stable/"},
+ {"//stable", "invalid risk in channel name: //stable"},
+ {"stable/", "invalid branch in channel name: stable/"},
+ {"/stable", "invalid track in channel name: /stable"},
+ } {
+ _, err := channel.Parse(tc.channel, "")
+ c.Check(err, ErrorMatches, tc.err)
+ _, err = channel.ParseVerbatim(tc.channel, "")
+ c.Check(err, ErrorMatches, tc.err)
+ }
+}
+
+func (s *storeChannelSuite) TestString(c *C) {
+ tests := []struct {
+ channel string
+ str string
+ }{
+ {"stable", "stable"},
+ {"latest/stable", "stable"},
+ {"1.0/edge", "1.0/edge"},
+ {"1.0/beta/foo", "1.0/beta/foo"},
+ {"1.0", "1.0/stable"},
+ {"candidate/foo", "candidate/foo"},
+ }
+
+ for _, t := range tests {
+ ch, err := channel.Parse(t.channel, "")
+ c.Assert(err, IsNil)
+
+ c.Check(ch.String(), Equals, t.str)
+ }
+}
+
+func (s *storeChannelSuite) TestFull(c *C) {
+ tests := []struct {
+ channel string
+ str string
+ }{
+ {"stable", "latest/stable"},
+ {"latest/stable", "latest/stable"},
+ {"1.0/edge", "1.0/edge"},
+ {"1.0/beta/foo", "1.0/beta/foo"},
+ {"1.0", "1.0/stable"},
+ {"candidate/foo", "latest/candidate/foo"},
+ }
+
+ for _, t := range tests {
+ ch, err := channel.Parse(t.channel, "")
+ c.Assert(err, IsNil)
+
+ c.Check(ch.Full(), Equals, t.str)
+ }
+}
+
+func (s *storeChannelSuite) TestMatch(c *C) {
+ tests := []struct {
+ req string
+ c1 string
+ sameArch bool
+ res string
+ }{
+ {"stable", "stable", true, "architecture:track:risk"},
+ {"stable", "beta", true, "architecture:track"},
+ {"beta", "stable", true, "architecture:track:risk"},
+ {"stable", "edge", false, "track"},
+ {"edge", "stable", false, "track:risk"},
+ {"1.0/stable", "1.0/edge", true, "architecture:track"},
+ {"1.0/edge", "stable", true, "architecture:risk"},
+ {"1.0/edge", "stable", false, "risk"},
+ {"1.0/stable", "stable", false, "risk"},
+ {"1.0/stable", "beta", false, ""},
+ {"1.0/stable", "2.0/beta", false, ""},
+ {"2.0/stable", "2.0/beta", false, "track"},
+ {"1.0/stable", "2.0/beta", true, "architecture"},
+ }
+
+ for _, t := range tests {
+ reqArch := "amd64"
+ c1Arch := "amd64"
+ if !t.sameArch {
+ c1Arch = "arm64"
+ }
+ req, err := channel.Parse(t.req, reqArch)
+ c.Assert(err, IsNil)
+ c1, err := channel.Parse(t.c1, c1Arch)
+ c.Assert(err, IsNil)
+
+ c.Check(req.Match(&c1).String(), Equals, t.res)
+ }
+}
+
+func (s *storeChannelSuite) TestResolve(c *C) {
+ tests := []struct {
+ channel string
+ new string
+ result string
+ expErr string
+ }{
+ {"", "", "", ""},
+ {"", "edge", "edge", ""},
+ {"track/foo", "", "track/foo", ""},
+ {"stable", "", "stable", ""},
+ {"stable", "edge", "edge", ""},
+ {"stable/branch1", "edge/branch2", "edge/branch2", ""},
+ {"track", "track", "track", ""},
+ {"track", "beta", "track/beta", ""},
+ {"track/stable", "beta", "track/beta", ""},
+ {"track/stable", "stable/branch", "track/stable/branch", ""},
+ {"track/stable", "track/edge/branch", "track/edge/branch", ""},
+ {"track/stable", "track/candidate", "track/candidate", ""},
+ {"track/stable", "track/stable/branch", "track/stable/branch", ""},
+ {"track1/stable", "track2/stable", "track2/stable", ""},
+ {"track1/stable", "track2/stable/branch", "track2/stable/branch", ""},
+ {"track/foo", "track/stable/branch", "", "invalid risk in channel name: track/foo"},
+ }
+
+ for _, t := range tests {
+ r, err := channel.Resolve(t.channel, t.new)
+ tcomm := Commentf("%#v", t)
+ if t.expErr == "" {
+ c.Assert(err, IsNil, tcomm)
+ c.Check(r, Equals, t.result, tcomm)
+ } else {
+ c.Assert(err, ErrorMatches, t.expErr, tcomm)
+ }
+ }
+}
+
+func (s *storeChannelSuite) TestResolveLocked(c *C) {
+ tests := []struct {
+ track string
+ new string
+ result string
+ expErr string
+ }{
+ {"", "", "", ""},
+ {"", "anytrack/stable", "anytrack/stable", ""},
+ {"track/foo", "", "", "invalid locked track: track/foo"},
+ {"track", "", "track", ""},
+ {"track", "track", "track", ""},
+ {"track", "beta", "track/beta", ""},
+ {"track", "stable/branch", "track/stable/branch", ""},
+ {"track", "track/edge/branch", "track/edge/branch", ""},
+ {"track", "track/candidate", "track/candidate", ""},
+ {"track", "track/stable/branch", "track/stable/branch", ""},
+ {"track1", "track2/stable", "track2/stable", "cannot switch locked track"},
+ {"track1", "track2/stable/branch", "track2/stable/branch", "cannot switch locked track"},
+ }
+ for _, t := range tests {
+ r, err := channel.ResolveLocked(t.track, t.new)
+ tcomm := Commentf("%#v", t)
+ if t.expErr == "" {
+ c.Assert(err, IsNil, tcomm)
+ c.Check(r, Equals, t.result, tcomm)
+ } else {
+ c.Assert(err, ErrorMatches, t.expErr, tcomm)
+ }
+ }
+}
diff -Nru snapd-2.41+19.10.1/snap/channel.go snapd-2.42.1+19.10/snap/channel.go
--- snapd-2.41+19.10.1/snap/channel.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/snap/channel.go 1970-01-01 00:00:00.000000000 +0000
@@ -1,202 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2018 Canonical Ltd
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-
-package snap
-
-import (
- "fmt"
- "strings"
-
- "github.com/snapcore/snapd/arch"
- "github.com/snapcore/snapd/strutil"
-)
-
-var channelRisks = []string{"stable", "candidate", "beta", "edge"}
-
-// Channel identifies and describes completely a store channel.
-type Channel struct {
- Architecture string `json:"architecture"`
- Name string `json:"name"`
- Track string `json:"track"`
- Risk string `json:"risk"`
- Branch string `json:"branch,omitempty"`
-}
-
-// ParseChannelVerbatim parses a string representing a store channel and
-// includes the given architecture, if architecture is "" the system
-// architecture is included. The channel representation is not normalized.
-// ParseChannel() should be used in most cases.
-func ParseChannelVerbatim(s string, architecture string) (Channel, error) {
- if s == "" {
- return Channel{}, fmt.Errorf("channel name cannot be empty")
- }
- p := strings.Split(s, "/")
- var risk, track, branch *string
- switch len(p) {
- default:
- return Channel{}, fmt.Errorf("channel name has too many components: %s", s)
- case 3:
- track, risk, branch = &p[0], &p[1], &p[2]
- case 2:
- if strutil.ListContains(channelRisks, p[0]) {
- risk, branch = &p[0], &p[1]
- } else {
- track, risk = &p[0], &p[1]
- }
- case 1:
- if strutil.ListContains(channelRisks, p[0]) {
- risk = &p[0]
- } else {
- track = &p[0]
- }
- }
-
- if architecture == "" {
- architecture = arch.UbuntuArchitecture()
- }
-
- ch := Channel{
- Architecture: architecture,
- }
-
- if risk != nil {
- if !strutil.ListContains(channelRisks, *risk) {
- return Channel{}, fmt.Errorf("invalid risk in channel name: %s", s)
- }
- ch.Risk = *risk
- }
- if track != nil {
- if *track == "" {
- return Channel{}, fmt.Errorf("invalid track in channel name: %s", s)
- }
- ch.Track = *track
- }
- if branch != nil {
- if *branch == "" {
- return Channel{}, fmt.Errorf("invalid branch in channel name: %s", s)
- }
- ch.Branch = *branch
- }
-
- return ch, nil
-}
-
-// ParseChannel parses a string representing a store channel and includes given
-// architecture, , if architecture is "" the system architecture is included.
-// The returned channel's track, risk and name are normalized.
-func ParseChannel(s string, architecture string) (Channel, error) {
- channel, err := ParseChannelVerbatim(s, architecture)
- if err != nil {
- return Channel{}, err
- }
- return channel.Clean(), nil
-}
-
-// Clean returns a Channel with a normalized track, risk and name.
-func (c Channel) Clean() Channel {
- track := c.Track
- risk := c.Risk
-
- if track == "latest" {
- track = ""
- }
- if risk == "" {
- risk = "stable"
- }
-
- // normalized name
- name := risk
- if track != "" {
- name = track + "/" + name
- }
- if c.Branch != "" {
- name = name + "/" + c.Branch
- }
-
- return Channel{
- Architecture: c.Architecture,
- Name: name,
- Track: track,
- Risk: risk,
- Branch: c.Branch,
- }
-}
-
-func (c Channel) String() string {
- return c.Name
-}
-
-// Full returns the full name of the channel, inclusive the default track "latest".
-func (c *Channel) Full() string {
- if c.Track == "" {
- return "latest/" + c.Name
- }
- return c.String()
-}
-
-func riskLevel(risk string) int {
- for i, r := range channelRisks {
- if r == risk {
- return i
- }
- }
- return -1
-}
-
-// ChannelMatch represents on which fields two channels are matching.
-type ChannelMatch struct {
- Architecture bool
- Track bool
- Risk bool
-}
-
-// String returns the string represantion of the match, results can be:
-// "architecture:track:risk"
-// "architecture:track"
-// "architecture:risk"
-// "track:risk"
-// "architecture"
-// "track"
-// "risk"
-// ""
-func (cm ChannelMatch) String() string {
- matching := []string{}
- if cm.Architecture {
- matching = append(matching, "architecture")
- }
- if cm.Track {
- matching = append(matching, "track")
- }
- if cm.Risk {
- matching = append(matching, "risk")
- }
- return strings.Join(matching, ":")
-
-}
-
-// Match returns a ChannelMatch of which fields among architecture,track,risk match between c and c1 store channels, risk is matched taking channel inheritance into account and considering c the requested channel.
-func (c *Channel) Match(c1 *Channel) ChannelMatch {
- requestedRiskLevel := riskLevel(c.Risk)
- rl1 := riskLevel(c1.Risk)
- return ChannelMatch{
- Architecture: c.Architecture == c1.Architecture,
- Track: c.Track == c1.Track,
- Risk: requestedRiskLevel >= rl1,
- }
-}
diff -Nru snapd-2.41+19.10.1/snap/channel_test.go snapd-2.42.1+19.10/snap/channel_test.go
--- snapd-2.41+19.10.1/snap/channel_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/snap/channel_test.go 1970-01-01 00:00:00.000000000 +0000
@@ -1,264 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2018 Canonical Ltd
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-
-package snap_test
-
-import (
- . "gopkg.in/check.v1"
-
- "github.com/snapcore/snapd/arch"
- "github.com/snapcore/snapd/snap"
-)
-
-type storeChannelSuite struct{}
-
-var _ = Suite(&storeChannelSuite{})
-
-func (s storeChannelSuite) TestParseChannel(c *C) {
- ch, err := snap.ParseChannel("stable", "")
- c.Assert(err, IsNil)
- c.Check(ch, DeepEquals, snap.Channel{
- Architecture: arch.UbuntuArchitecture(),
- Name: "stable",
- Track: "",
- Risk: "stable",
- Branch: "",
- })
-
- ch, err = snap.ParseChannel("latest/stable", "")
- c.Assert(err, IsNil)
- c.Check(ch, DeepEquals, snap.Channel{
- Architecture: arch.UbuntuArchitecture(),
- Name: "stable",
- Track: "",
- Risk: "stable",
- Branch: "",
- })
-
- ch, err = snap.ParseChannel("1.0/edge", "")
- c.Assert(err, IsNil)
- c.Check(ch, DeepEquals, snap.Channel{
- Architecture: arch.UbuntuArchitecture(),
- Name: "1.0/edge",
- Track: "1.0",
- Risk: "edge",
- Branch: "",
- })
-
- ch, err = snap.ParseChannel("1.0", "")
- c.Assert(err, IsNil)
- c.Check(ch, DeepEquals, snap.Channel{
- Architecture: arch.UbuntuArchitecture(),
- Name: "1.0/stable",
- Track: "1.0",
- Risk: "stable",
- Branch: "",
- })
-
- ch, err = snap.ParseChannel("1.0/beta/foo", "")
- c.Assert(err, IsNil)
- c.Check(ch, DeepEquals, snap.Channel{
- Architecture: arch.UbuntuArchitecture(),
- Name: "1.0/beta/foo",
- Track: "1.0",
- Risk: "beta",
- Branch: "foo",
- })
-
- ch, err = snap.ParseChannel("candidate/foo", "")
- c.Assert(err, IsNil)
- c.Check(ch, DeepEquals, snap.Channel{
- Architecture: arch.UbuntuArchitecture(),
- Name: "candidate/foo",
- Track: "",
- Risk: "candidate",
- Branch: "foo",
- })
-
- ch, err = snap.ParseChannel("candidate/foo", "other-arch")
- c.Assert(err, IsNil)
- c.Check(ch, DeepEquals, snap.Channel{
- Architecture: "other-arch",
- Name: "candidate/foo",
- Track: "",
- Risk: "candidate",
- Branch: "foo",
- })
-}
-
-func mustParseChannel(c *C, channel string) snap.Channel {
- ch, err := snap.ParseChannel(channel, "")
- c.Assert(err, IsNil)
- return ch
-}
-
-func (s storeChannelSuite) TestParseChannelVerbatim(c *C) {
- ch, err := snap.ParseChannelVerbatim("sometrack", "")
- c.Assert(err, IsNil)
- c.Check(ch, DeepEquals, snap.Channel{
- Architecture: arch.UbuntuArchitecture(),
- Track: "sometrack",
- })
- c.Check(mustParseChannel(c, "sometrack"), DeepEquals, ch.Clean())
-
- ch, err = snap.ParseChannelVerbatim("latest", "")
- c.Assert(err, IsNil)
- c.Check(ch, DeepEquals, snap.Channel{
- Architecture: arch.UbuntuArchitecture(),
- Track: "latest",
- })
- c.Check(mustParseChannel(c, "latest"), DeepEquals, ch.Clean())
-
- ch, err = snap.ParseChannelVerbatim("latest/stable", "")
- c.Assert(err, IsNil)
- c.Check(ch, DeepEquals, snap.Channel{
- Architecture: arch.UbuntuArchitecture(),
- Track: "latest",
- Risk: "stable",
- })
- c.Check(mustParseChannel(c, "latest/stable"), DeepEquals, ch.Clean())
-
- ch, err = snap.ParseChannelVerbatim("latest/stable/foo", "")
- c.Assert(err, IsNil)
- c.Check(ch, DeepEquals, snap.Channel{
- Architecture: arch.UbuntuArchitecture(),
- Track: "latest",
- Risk: "stable",
- Branch: "foo",
- })
- c.Check(mustParseChannel(c, "latest/stable/foo"), DeepEquals, ch.Clean())
-}
-
-func (s storeChannelSuite) TestClean(c *C) {
- ch := snap.Channel{
- Architecture: "arm64",
- Track: "latest",
- Name: "latest/stable",
- Risk: "stable",
- }
-
- cleanedCh := ch.Clean()
- c.Check(cleanedCh, Not(DeepEquals), c)
- c.Check(cleanedCh, DeepEquals, snap.Channel{
- Architecture: "arm64",
- Track: "",
- Name: "stable",
- Risk: "stable",
- })
-}
-
-func (s storeChannelSuite) TestParseChannelErrors(c *C) {
- for _, tc := range []struct {
- channel string
- err string
- }{
- {"", "channel name cannot be empty"},
- {"1.0////", "channel name has too many components: 1.0////"},
- {"1.0/cand", "invalid risk in channel name: 1.0/cand"},
- {"fix//hotfix", "invalid risk in channel name: fix//hotfix"},
- {"/stable/", "invalid track in channel name: /stable/"},
- {"//stable", "invalid risk in channel name: //stable"},
- {"stable/", "invalid branch in channel name: stable/"},
- {"/stable", "invalid track in channel name: /stable"},
- } {
- _, err := snap.ParseChannel(tc.channel, "")
- c.Check(err, ErrorMatches, tc.err)
- _, err = snap.ParseChannelVerbatim(tc.channel, "")
- c.Check(err, ErrorMatches, tc.err)
- }
-}
-
-func (s *storeChannelSuite) TestString(c *C) {
- tests := []struct {
- channel string
- str string
- }{
- {"stable", "stable"},
- {"latest/stable", "stable"},
- {"1.0/edge", "1.0/edge"},
- {"1.0/beta/foo", "1.0/beta/foo"},
- {"1.0", "1.0/stable"},
- {"candidate/foo", "candidate/foo"},
- }
-
- for _, t := range tests {
- ch, err := snap.ParseChannel(t.channel, "")
- c.Assert(err, IsNil)
-
- c.Check(ch.String(), Equals, t.str)
- }
-}
-
-func (s *storeChannelSuite) TestFull(c *C) {
- tests := []struct {
- channel string
- str string
- }{
- {"stable", "latest/stable"},
- {"latest/stable", "latest/stable"},
- {"1.0/edge", "1.0/edge"},
- {"1.0/beta/foo", "1.0/beta/foo"},
- {"1.0", "1.0/stable"},
- {"candidate/foo", "latest/candidate/foo"},
- }
-
- for _, t := range tests {
- ch, err := snap.ParseChannel(t.channel, "")
- c.Assert(err, IsNil)
-
- c.Check(ch.Full(), Equals, t.str)
- }
-}
-
-func (s *storeChannelSuite) TestMatch(c *C) {
- tests := []struct {
- req string
- c1 string
- sameArch bool
- res string
- }{
- {"stable", "stable", true, "architecture:track:risk"},
- {"stable", "beta", true, "architecture:track"},
- {"beta", "stable", true, "architecture:track:risk"},
- {"stable", "edge", false, "track"},
- {"edge", "stable", false, "track:risk"},
- {"1.0/stable", "1.0/edge", true, "architecture:track"},
- {"1.0/edge", "stable", true, "architecture:risk"},
- {"1.0/edge", "stable", false, "risk"},
- {"1.0/stable", "stable", false, "risk"},
- {"1.0/stable", "beta", false, ""},
- {"1.0/stable", "2.0/beta", false, ""},
- {"2.0/stable", "2.0/beta", false, "track"},
- {"1.0/stable", "2.0/beta", true, "architecture"},
- }
-
- for _, t := range tests {
- reqArch := "amd64"
- c1Arch := "amd64"
- if !t.sameArch {
- c1Arch = "arm64"
- }
- req, err := snap.ParseChannel(t.req, reqArch)
- c.Assert(err, IsNil)
- c1, err := snap.ParseChannel(t.c1, c1Arch)
- c.Assert(err, IsNil)
-
- c.Check(req.Match(&c1).String(), Equals, t.res)
- }
-}
diff -Nru snapd-2.41+19.10.1/snap/naming/snapref.go snapd-2.42.1+19.10/snap/naming/snapref.go
--- snapd-2.41+19.10.1/snap/naming/snapref.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/snap/naming/snapref.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,122 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package naming
+
+// A SnapRef references a snap by name and/or id.
+type SnapRef interface {
+ SnapName() string
+ ID() string
+}
+
+// Snap references a snap by name only.
+type Snap string
+
+func (s Snap) SnapName() string {
+ return string(s)
+}
+
+func (s Snap) ID() string {
+ return ""
+}
+
+type snapRef struct {
+ name string
+ id string
+}
+
+// NewSnapRef returns a reference to the snap with given name and id.
+func NewSnapRef(name, id string) SnapRef {
+ return &snapRef{name: name, id: id}
+}
+
+func (r *snapRef) SnapName() string {
+ return r.name
+}
+
+func (r *snapRef) ID() string {
+ return r.id
+}
+
+// SameSnap returns whether the two arguments refer to the same snap.
+// If ids are not available for both it will fallback to names.
+func SameSnap(snapRef1, snapRef2 SnapRef) bool {
+ id1 := snapRef1.ID()
+ id2 := snapRef2.ID()
+ if id1 != "" && id2 != "" {
+ return id1 == id2
+ }
+ return snapRef1.SnapName() == snapRef2.SnapName()
+}
+
+// SnapSet can hold a set of references to snaps.
+type SnapSet struct {
+ byID map[string]SnapRef
+ byName map[string]SnapRef
+}
+
+// NewSnapSet builds a snap set with the given references.
+func NewSnapSet(refs []SnapRef) *SnapSet {
+ sz := len(refs) + 2
+ s := &SnapSet{
+ byID: make(map[string]SnapRef, sz),
+ byName: make(map[string]SnapRef, sz),
+ }
+ for _, r := range refs {
+ s.Add(r)
+ }
+ return s
+}
+
+// Lookup finds the reference in the set matching the given one if any.
+func (s *SnapSet) Lookup(which SnapRef) SnapRef {
+ whichID := which.ID()
+ name := which.SnapName()
+ if whichID != "" {
+ if ref := s.byID[whichID]; ref != nil {
+ return ref
+ }
+ }
+ ref := s.byName[name]
+ if ref == nil || (ref.ID() != "" && whichID != "") {
+ return nil
+ }
+ return ref
+}
+
+// Contains returns whether the set has a matching reference already.
+func (s *SnapSet) Contains(ref SnapRef) bool {
+ return s.Lookup(ref) != nil
+}
+
+// Add adds one reference to the set.
+// Already added ids or names will be ignored. The assumption is that
+// a SnapSet is populated with distinct snaps.
+func (s *SnapSet) Add(ref SnapRef) {
+ if s.Contains(ref) {
+ // nothing to do
+ return
+ }
+ if id := ref.ID(); id != "" {
+ s.byID[id] = ref
+ }
+ if name := ref.SnapName(); name != "" {
+ s.byName[name] = ref
+ }
+}
diff -Nru snapd-2.41+19.10.1/snap/naming/snapref_test.go snapd-2.42.1+19.10/snap/naming/snapref_test.go
--- snapd-2.41+19.10.1/snap/naming/snapref_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/snap/naming/snapref_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,89 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package naming_test
+
+import (
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/snap/naming"
+)
+
+type snapRefSuite struct{}
+
+var _ = Suite(&snapRefSuite{})
+
+func (s *snapRefSuite) TestNewSnapRef(c *C) {
+ fooRef := naming.NewSnapRef("foo", "foo-id")
+ c.Check(fooRef.SnapName(), Equals, "foo")
+ c.Check(fooRef.ID(), Equals, "foo-id")
+
+ fooNameOnlyRef := naming.NewSnapRef("foo", "")
+ c.Check(fooNameOnlyRef.SnapName(), Equals, "foo")
+ c.Check(fooNameOnlyRef.ID(), Equals, "")
+}
+
+func (s *snapRefSuite) TestSnap(c *C) {
+ fooNameOnlyRef := naming.Snap("foo")
+ c.Check(fooNameOnlyRef.SnapName(), Equals, "foo")
+ c.Check(fooNameOnlyRef.ID(), Equals, "")
+}
+
+func (s *snapRefSuite) TestSameSnap(c *C) {
+ fooRef := naming.NewSnapRef("foo", "foo-id")
+ fooNameOnlyRef := naming.NewSnapRef("foo", "")
+ altFooRef := naming.NewSnapRef("foo-proj", "foo-id")
+ barNameOnylRef := naming.NewSnapRef("bar", "")
+ unrelFooRef := naming.NewSnapRef("foo", "unrel-id")
+
+ c.Check(naming.SameSnap(fooRef, altFooRef), Equals, true)
+ c.Check(naming.SameSnap(fooRef, fooNameOnlyRef), Equals, true)
+ c.Check(naming.SameSnap(altFooRef, fooNameOnlyRef), Equals, false)
+ c.Check(naming.SameSnap(unrelFooRef, fooRef), Equals, false)
+ c.Check(naming.SameSnap(fooRef, barNameOnylRef), Equals, false)
+ // weakness but expected
+ c.Check(naming.SameSnap(unrelFooRef, fooNameOnlyRef), Equals, true)
+}
+
+func (s *snapRefSuite) TestSnapSet(c *C) {
+ ss := naming.NewSnapSet(nil)
+ fooRef := naming.NewSnapRef("foo", "foo-id")
+ fooNameOnlyRef := naming.Snap("foo")
+
+ ss.Add(fooRef)
+ ss.Add(fooNameOnlyRef)
+
+ altFooRef := naming.NewSnapRef("foo-proj", "foo-id")
+ c.Check(ss.Lookup(fooRef), Equals, fooRef)
+ c.Check(ss.Lookup(fooNameOnlyRef), Equals, fooRef)
+ c.Check(ss.Lookup(altFooRef), Equals, fooRef)
+
+ barNameOnylRef := naming.NewSnapRef("bar", "")
+ unrelFooRef := naming.NewSnapRef("foo", "unrel-id")
+ c.Check(ss.Lookup(barNameOnylRef), Equals, nil)
+ c.Check(ss.Lookup(unrelFooRef), Equals, nil)
+
+ // weaker behavior but expected
+ ss1 := naming.NewSnapSet([]naming.SnapRef{fooNameOnlyRef})
+ ss1.Add(fooRef)
+ c.Check(ss1.Lookup(fooRef), Equals, fooNameOnlyRef)
+ c.Check(ss1.Lookup(altFooRef), Equals, nil)
+ c.Check(ss1.Lookup(barNameOnylRef), Equals, nil)
+ c.Check(ss1.Lookup(unrelFooRef), Equals, fooNameOnlyRef)
+}
diff -Nru snapd-2.41+19.10.1/snap/naming/validate.go snapd-2.42.1+19.10/snap/naming/validate.go
--- snapd-2.41+19.10.1/snap/naming/validate.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/snap/naming/validate.go 2019-10-30 12:17:43.000000000 +0000
@@ -17,7 +17,7 @@
*
*/
-// Package naming implements naming constraints for snaps and their elements.
+// Package naming implements naming constraints and concepts for snaps and their elements.
package naming
import (
diff -Nru snapd-2.41+19.10.1/snap/naming/validate_test.go snapd-2.42.1+19.10/snap/naming/validate_test.go
--- snapd-2.41+19.10.1/snap/naming/validate_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/snap/naming/validate_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -159,6 +159,8 @@
err := naming.ValidateHook(hook)
c.Assert(err, ErrorMatches, `invalid hook name: ".*"`)
}
+ // Regression test for https://bugs.launchpad.net/snapd/+bug/1638988
+ c.Assert(naming.ValidateHook("connect-plug-i2c"), IsNil)
}
func (s *ValidateSuite) TestValidateAppName(c *C) {
diff -Nru snapd-2.41+19.10.1/snap/seed_yaml.go snapd-2.42.1+19.10/snap/seed_yaml.go
--- snapd-2.41+19.10.1/snap/seed_yaml.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/snap/seed_yaml.go 1970-01-01 00:00:00.000000000 +0000
@@ -1,106 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2014-2016 Canonical Ltd
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-
-package snap
-
-import (
- "fmt"
- "io/ioutil"
- "strings"
-
- "gopkg.in/yaml.v2"
-
- "github.com/snapcore/snapd/osutil"
-)
-
-// SeedSnap points to a snap in the seed to install, together with
-// assertions (or alone if unasserted is true) it will be used to
-// drive the installation and ultimately set SideInfo/SnapState for it.
-type SeedSnap struct {
- Name string `yaml:"name"`
-
- // cross-reference/audit
- SnapID string `yaml:"snap-id,omitempty"`
-
- // bits that are orthongonal/not in assertions
- Channel string `yaml:"channel,omitempty"`
- DevMode bool `yaml:"devmode,omitempty"`
- Classic bool `yaml:"classic,omitempty"`
-
- Private bool `yaml:"private,omitempty"`
-
- Contact string `yaml:"contact,omitempty"`
-
- // no assertions are available in the seed for this snap
- Unasserted bool `yaml:"unasserted,omitempty"`
-
- File string `yaml:"file"`
-}
-
-type Seed struct {
- Snaps []*SeedSnap `yaml:"snaps"`
-}
-
-func ReadSeedYaml(fn string) (*Seed, error) {
- errPrefix := "cannot read seed yaml"
-
- yamlData, err := ioutil.ReadFile(fn)
- if err != nil {
- return nil, fmt.Errorf("%s: %v", errPrefix, err)
- }
-
- var seed Seed
- if err := yaml.Unmarshal(yamlData, &seed); err != nil {
- return nil, fmt.Errorf("%s: cannot unmarshal %q: %s", errPrefix, yamlData, err)
- }
-
- // validate
- for _, sn := range seed.Snaps {
- if sn == nil {
- return nil, fmt.Errorf("%s: empty element in seed", errPrefix)
- }
- if err := ValidateInstanceName(sn.Name); err != nil {
- return nil, fmt.Errorf("%s: %v", errPrefix, err)
- }
- if sn.Channel != "" {
- if _, err := ParseChannel(sn.Channel, ""); err != nil {
- return nil, fmt.Errorf("%s: %v", errPrefix, err)
- }
- }
- if sn.File == "" {
- return nil, fmt.Errorf(`%s: "file" attribute for %q cannot be empty`, errPrefix, sn.Name)
- }
- if strings.Contains(sn.File, "/") {
- return nil, fmt.Errorf("%s: %q must be a filename, not a path", errPrefix, sn.File)
- }
- }
-
- return &seed, nil
-}
-
-func (seed *Seed) Write(seedFn string) error {
- data, err := yaml.Marshal(&seed)
- if err != nil {
- return err
- }
- if err := osutil.AtomicWriteFile(seedFn, data, 0644, 0); err != nil {
- return err
- }
- return nil
-}
diff -Nru snapd-2.41+19.10.1/snap/seed_yaml_test.go snapd-2.42.1+19.10/snap/seed_yaml_test.go
--- snapd-2.41+19.10.1/snap/seed_yaml_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/snap/seed_yaml_test.go 1970-01-01 00:00:00.000000000 +0000
@@ -1,133 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2014-2016 Canonical Ltd
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-
-package snap_test
-
-import (
- "io/ioutil"
- "path/filepath"
-
- . "gopkg.in/check.v1"
-
- "github.com/snapcore/snapd/snap"
-)
-
-type seedYamlTestSuite struct{}
-
-var _ = Suite(&seedYamlTestSuite{})
-
-var mockSeedYaml = []byte(`
-snaps:
- - name: foo
- snap-id: snapidsnapidsnapid
- channel: stable
- devmode: true
- file: foo_1.0_all.snap
- - name: local
- unasserted: true
- file: local.snap
-`)
-
-func (s *seedYamlTestSuite) TestSimple(c *C) {
- fn := filepath.Join(c.MkDir(), "seed.yaml")
- err := ioutil.WriteFile(fn, mockSeedYaml, 0644)
- c.Assert(err, IsNil)
-
- seed, err := snap.ReadSeedYaml(fn)
- c.Assert(err, IsNil)
- c.Assert(seed.Snaps, HasLen, 2)
- c.Assert(seed.Snaps[0], DeepEquals, &snap.SeedSnap{
- File: "foo_1.0_all.snap",
- Name: "foo",
- SnapID: "snapidsnapidsnapid",
-
- Channel: "stable",
- DevMode: true,
- })
- c.Assert(seed.Snaps[1], DeepEquals, &snap.SeedSnap{
- File: "local.snap",
- Name: "local",
- Unasserted: true,
- })
-}
-
-var badMockSeedYaml = []byte(`
-snaps:
- - name: foo
- file: foo/bar.snap
-`)
-
-func (s *seedYamlTestSuite) TestNoPathAllowed(c *C) {
- fn := filepath.Join(c.MkDir(), "seed.yaml")
- err := ioutil.WriteFile(fn, badMockSeedYaml, 0644)
- c.Assert(err, IsNil)
-
- _, err = snap.ReadSeedYaml(fn)
- c.Assert(err, ErrorMatches, `cannot read seed yaml: "foo/bar.snap" must be a filename, not a path`)
-}
-
-func (s *seedYamlTestSuite) TestValidateChannelUnhappy(c *C) {
- fn := filepath.Join(c.MkDir(), "seed.yaml")
- err := ioutil.WriteFile(fn, []byte(`
-snaps:
- - name: foo
- channel: invalid/channel/
-`), 0644)
- c.Assert(err, IsNil)
-
- _, err = snap.ReadSeedYaml(fn)
- c.Assert(err, ErrorMatches, `cannot read seed yaml: invalid risk in channel name: invalid/channel/`)
-}
-
-func (s *seedYamlTestSuite) TestValidateNameUnhappy(c *C) {
- fn := filepath.Join(c.MkDir(), "seed.yaml")
- err := ioutil.WriteFile(fn, []byte(`
-snaps:
- - name: invalid--name
- file: ./foo.snap
-`), 0644)
- c.Assert(err, IsNil)
-
- _, err = snap.ReadSeedYaml(fn)
- c.Assert(err, ErrorMatches, `cannot read seed yaml: invalid snap name: "invalid--name"`)
-}
-
-func (s *seedYamlTestSuite) TestValidateNameMissing(c *C) {
- fn := filepath.Join(c.MkDir(), "seed.yaml")
- err := ioutil.WriteFile(fn, []byte(`
-snaps:
- - file: ./foo.snap
-`), 0644)
- c.Assert(err, IsNil)
-
- _, err = snap.ReadSeedYaml(fn)
- c.Assert(err, ErrorMatches, `cannot read seed yaml: invalid snap name: ""`)
-}
-
-func (s *seedYamlTestSuite) TestValidateFileMissing(c *C) {
- fn := filepath.Join(c.MkDir(), "seed.yaml")
- err := ioutil.WriteFile(fn, []byte(`
-snaps:
- - name: foo
-`), 0644)
- c.Assert(err, IsNil)
-
- _, err = snap.ReadSeedYaml(fn)
- c.Assert(err, ErrorMatches, `cannot read seed yaml: "file" attribute for "foo" cannot be empty`)
-}
diff -Nru snapd-2.41+19.10.1/snap/snapenv/snapenv.go snapd-2.42.1+19.10/snap/snapenv/snapenv.go
--- snapd-2.41+19.10.1/snap/snapenv/snapenv.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/snap/snapenv/snapenv.go 2019-10-30 12:17:43.000000000 +0000
@@ -112,7 +112,7 @@
"SNAP_INSTANCE_KEY": info.InstanceKey,
"SNAP_VERSION": info.Version,
"SNAP_REVISION": info.Revision.String(),
- "SNAP_ARCH": arch.UbuntuArchitecture(),
+ "SNAP_ARCH": arch.DpkgArchitecture(),
// see https://github.com/snapcore/snapd/pull/2732#pullrequestreview-18827193
"SNAP_LIBRARY_PATH": "/var/lib/snapd/lib/gl:/var/lib/snapd/lib/gl32:/var/lib/snapd/void",
"SNAP_REEXEC": os.Getenv("SNAP_REEXEC"),
diff -Nru snapd-2.41+19.10.1/snap/snapenv/snapenv_test.go snapd-2.42.1+19.10/snap/snapenv/snapenv_test.go
--- snapd-2.41+19.10.1/snap/snapenv/snapenv_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/snap/snapenv/snapenv_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -82,7 +82,7 @@
c.Assert(env, DeepEquals, map[string]string{
"SNAP": fmt.Sprintf("%s/foo/17", dirs.CoreSnapMountDir),
- "SNAP_ARCH": arch.UbuntuArchitecture(),
+ "SNAP_ARCH": arch.DpkgArchitecture(),
"SNAP_COMMON": "/var/snap/foo/common",
"SNAP_DATA": "/var/snap/foo/17",
"SNAP_LIBRARY_PATH": "/var/lib/snapd/lib/gl:/var/lib/snapd/lib/gl32:/var/lib/snapd/void",
@@ -136,7 +136,7 @@
env := snapEnv(info)
c.Check(env, DeepEquals, map[string]string{
- "SNAP_ARCH": arch.UbuntuArchitecture(),
+ "SNAP_ARCH": arch.DpkgArchitecture(),
"SNAP_LIBRARY_PATH": "/var/lib/snapd/lib/gl:/var/lib/snapd/lib/gl32:/var/lib/snapd/void",
"SNAP_NAME": "snapname",
"SNAP_INSTANCE_NAME": "snapname",
@@ -178,7 +178,7 @@
env := snapEnv(info)
c.Check(env, DeepEquals, map[string]string{
- "SNAP_ARCH": arch.UbuntuArchitecture(),
+ "SNAP_ARCH": arch.DpkgArchitecture(),
"SNAP_LIBRARY_PATH": "/var/lib/snapd/lib/gl:/var/lib/snapd/lib/gl32:/var/lib/snapd/void",
"SNAP_NAME": "snapname",
"SNAP_INSTANCE_NAME": "snapname_foo",
diff -Nru snapd-2.41+19.10.1/snap/snaptest/snaptest.go snapd-2.42.1+19.10/snap/snaptest/snaptest.go
--- snapd-2.41+19.10.1/snap/snaptest/snaptest.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/snap/snaptest/snaptest.go 2019-10-30 12:17:43.000000000 +0000
@@ -30,6 +30,7 @@
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/channel"
"github.com/snapcore/snapd/snap/pack"
)
@@ -219,8 +220,8 @@
// MustParseChannel parses a string representing a store channel and
// includes the given architecture, if architecture is "" the system
// architecture is included. It panics on error.
-func MustParseChannel(s string, architecture string) snap.Channel {
- c, err := snap.ParseChannel(s, architecture)
+func MustParseChannel(s string, architecture string) channel.Channel {
+ c, err := channel.Parse(s, architecture)
if err != nil {
panic(err)
}
diff -Nru snapd-2.41+19.10.1/snap/validate.go snapd-2.42.1+19.10/snap/validate.go
--- snapd-2.41+19.10.1/snap/validate.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/snap/validate.go 2019-10-30 12:17:43.000000000 +0000
@@ -951,3 +951,47 @@
}
return nil
}
+
+// neededDefaultProviders returns the names of all default-providers for
+// the content plugs that the given snap.Info needs.
+func NeededDefaultProviders(info *Info) (cps []string) {
+ // XXX: unify with the other places that parse default-providers
+ for _, plug := range info.Plugs {
+ if plug.Interface == "content" {
+ var dprovider string
+ if err := plug.Attr("default-provider", &dprovider); err == nil && dprovider != "" {
+ // usage can be "snap:slot" but we only check
+ // the snap here
+ name := strings.Split(dprovider, ":")[0]
+ cps = append(cps, name)
+ }
+ }
+ }
+ return cps
+}
+
+// ValidateBasesAndProviders checks that all bases/default-providers are part of the seed
+func ValidateBasesAndProviders(snapInfos map[string]*Info) []error {
+ var errs []error
+ for _, info := range snapInfos {
+ // ensure base is available
+ if info.Base != "" && info.Base != "none" {
+ if _, ok := snapInfos[info.Base]; !ok {
+ errs = append(errs, fmt.Errorf("cannot use snap %q: base %q is missing", info.InstanceName(), info.Base))
+ }
+ }
+ // ensure core is available
+ if info.Base == "" && info.SnapType == TypeApp && info.InstanceName() != "snapd" {
+ if _, ok := snapInfos["core"]; !ok {
+ errs = append(errs, fmt.Errorf(`cannot use snap %q: required snap "core" missing`, info.InstanceName()))
+ }
+ }
+ // ensure default-providers are available
+ for _, dp := range NeededDefaultProviders(info) {
+ if _, ok := snapInfos[dp]; !ok {
+ errs = append(errs, fmt.Errorf("cannot use snap %q: default provider %q is missing", info.InstanceName(), dp))
+ }
+ }
+ }
+ return errs
+}
diff -Nru snapd-2.41+19.10.1/snap/validate_test.go snapd-2.42.1+19.10/snap/validate_test.go
--- snapd-2.41+19.10.1/snap/validate_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/snap/validate_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -1574,3 +1574,112 @@
err = Validate(info)
c.Assert(err, ErrorMatches, `invalid system username "b@d"`)
}
+
+const yamlNeedDf = `name: need-df
+version: 1.0
+plugs:
+ gtk-3-themes:
+ interface: content
+ default-provider: gtk-common-themes
+`
+
+func (s *ValidateSuite) TestNeededDefaultProviders(c *C) {
+ strk := NewScopedTracker()
+ info, err := InfoFromSnapYamlWithSideInfo([]byte(yamlNeedDf), nil, strk)
+ c.Assert(err, IsNil)
+
+ dps := NeededDefaultProviders(info)
+ c.Check(dps, DeepEquals, []string{"gtk-common-themes"})
+}
+
+const yamlNeedDfWithSlot = `name: need-df
+version: 1.0
+plugs:
+ gtk-3-themes:
+ interface: content
+ default-provider: gtk-common-themes2:with-slot
+`
+
+func (s *ValidateSuite) TestNeededDefaultProvidersLegacyColonSyntax(c *C) {
+ strk := NewScopedTracker()
+ info, err := InfoFromSnapYamlWithSideInfo([]byte(yamlNeedDfWithSlot), nil, strk)
+ c.Assert(err, IsNil)
+
+ dps := NeededDefaultProviders(info)
+ c.Check(dps, DeepEquals, []string{"gtk-common-themes2"})
+}
+
+func (s *validateSuite) TestValidateSnapMissingCore(c *C) {
+ const yaml = `name: some-snap
+version: 1.0`
+
+ strk := NewScopedTracker()
+ info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml), nil, strk)
+ c.Assert(err, IsNil)
+
+ infos := map[string]*Info{"some-snap": info}
+ errors := ValidateBasesAndProviders(infos)
+ c.Assert(errors, HasLen, 1)
+ c.Assert(errors[0], ErrorMatches, `cannot use snap "some-snap": required snap "core" missing`)
+}
+
+func (s *validateSuite) TestValidateSnapMissingBase(c *C) {
+ const yaml = `name: some-snap
+base: some-base
+version: 1.0`
+
+ strk := NewScopedTracker()
+ info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml), nil, strk)
+ c.Assert(err, IsNil)
+
+ infos := map[string]*Info{"some-snap": info}
+ errors := ValidateBasesAndProviders(infos)
+ c.Assert(errors, HasLen, 1)
+ c.Assert(errors[0], ErrorMatches, `cannot use snap "some-snap": base "some-base" is missing`)
+}
+
+func (s *validateSuite) TestValidateSnapMissingDefaultProvider(c *C) {
+ strk := NewScopedTracker()
+ snapInfo, err := InfoFromSnapYamlWithSideInfo([]byte(yamlNeedDf), nil, strk)
+ c.Assert(err, IsNil)
+
+ var coreYaml = `name: core
+version: 1.0
+type: os`
+
+ coreInfo, err := InfoFromSnapYamlWithSideInfo([]byte(coreYaml), nil, strk)
+ c.Assert(err, IsNil)
+
+ infos := map[string]*Info{"some-snap": snapInfo, "core": coreInfo}
+ errors := ValidateBasesAndProviders(infos)
+ c.Assert(errors, HasLen, 1)
+ c.Assert(errors[0], ErrorMatches, `cannot use snap "need-df": default provider "gtk-common-themes" is missing`)
+}
+
+func (s *validateSuite) TestValidateSnapBaseNoneOK(c *C) {
+ const yaml = `name: some-snap
+base: none
+version: 1.0`
+
+ strk := NewScopedTracker()
+ info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml), nil, strk)
+ c.Assert(err, IsNil)
+
+ infos := map[string]*Info{"some-snap": info}
+ errors := ValidateBasesAndProviders(infos)
+ c.Assert(errors, IsNil)
+}
+
+func (s *validateSuite) TestValidateSnapSnapd(c *C) {
+ const yaml = `name: snapd
+type: snapd
+version: 1.0`
+
+ strk := NewScopedTracker()
+ info, err := InfoFromSnapYamlWithSideInfo([]byte(yaml), nil, strk)
+ c.Assert(err, IsNil)
+
+ infos := map[string]*Info{"snapd": info}
+ errors := ValidateBasesAndProviders(infos)
+ c.Assert(errors, IsNil)
+}
diff -Nru snapd-2.41+19.10.1/spread.yaml snapd-2.42.1+19.10/spread.yaml
--- snapd-2.41+19.10.1/spread.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/spread.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -101,6 +101,14 @@
workers: 6
storage: preserve-size
+ google-unstable:
+ type: google
+ key: '$(HOST: echo "$SPREAD_GOOGLE_KEY")'
+ location: computeengine/us-east1-b
+ halt-timeout: 2h
+ systems:
+ - ubuntu-19.10-64:
+ workers: 6
- centos-7-64:
workers: 6
image: centos-7-64
@@ -112,14 +120,11 @@
halt-timeout: 2h
systems:
- ubuntu-16.04-64:
- workers: 4
+ workers: 6
- ubuntu-18.04-64:
- workers: 4
- - ubuntu-18.10-64:
- image: ubuntu-1810
- workers: 4
+ workers: 6
- ubuntu-19.04-64:
- workers: 4
+ workers: 6
google-nested:
type: google
@@ -526,11 +531,6 @@
fi
fi
- if [[ "$SPREAD_SYSTEM" == ubuntu-* ]] && [[ "$SPREAD_SYSTEM" != ubuntu-core-* ]]; then
- apt-get remove --purge -y lxd lxcfs || true
- apt-get autoremove --purge -y
- fi
-
if [[ "$SPREAD_SYSTEM" == debian-* ]]; then
apt-get update && apt-get install -y eatmydata
fi
@@ -602,25 +602,10 @@
"$TESTSLIB"/prepare-restore.sh --prepare-project
prepare-each: |
"$TESTSLIB"/prepare-restore.sh --prepare-project-each
- grep -v /var/lib/lxcfs /proc/self/mountinfo
restore: |
"$TESTSLIB"/prepare-restore.sh --restore-project
restore-each: |
"$TESTSLIB"/prepare-restore.sh --restore-project-each
- if journalctl -u snapd.service | grep -F "signal: terminated"; then exit 1; fi
- case "$SPREAD_SYSTEM" in
- fedora-*|centos-*)
- # Make sure that we are not leaving behind incorrectly labeled snap
- # files on systems supporting SELinux
- (
- find /root/snap -printf '%Z\t%H/%P\n' || true
- find /home -regex '/home/[^/]*/snap\(/.*\)?' -printf '%Z\t%H/%P\n' || true
- ) | grep -c -v snappy_home_t | MATCH "0"
-
- find /var/snap -printf '%Z\t%H/%P\n' | grep -c -v snappy_var_t | MATCH "0"
- ;;
- esac
- grep -v /var/lib/lxcfs /proc/self/mountinfo
suites:
# The essential tests designed to run inside the autopkgtest
# environment on each platform. On autopkgtest we cannot run all tests
@@ -643,17 +628,17 @@
summary: Full-system tests for snapd
prepare: |
"$TESTSLIB"/prepare-restore.sh --prepare-suite
- debug: |
- if [ "$SPREAD_DEBUG_EACH" = 1 ]; then
- systemctl status snapd.socket || true
- journalctl -xe
- fi
prepare-each: |
"$TESTSLIB"/prepare-restore.sh --prepare-suite-each
restore-each: |
"$TESTSLIB"/prepare-restore.sh --restore-suite-each
restore: |
"$TESTSLIB"/prepare-restore.sh --restore-suite
+ debug: |
+ if [ "$SPREAD_DEBUG_EACH" = 1 ]; then
+ systemctl status snapd.socket || true
+ journalctl -xe
+ fi
tests/core18/:
summary: Subset of core18 specific tests
systems: [ubuntu-core-18-*]
@@ -787,7 +772,6 @@
# Test cases are not yet ported to Fedora/openSUSE/Arch/AMZN2 that is why
# we keep them disabled. A later PR will enable most tests and
# drop this blacklist.
- systems: [-fedora-*, -opensuse-*, -arch-*, -amazon-*, -centos-*]
prepare: |
"$TESTSLIB"/prepare-restore.sh --prepare-suite
prepare-each: |
diff -Nru snapd-2.41+19.10.1/store/errors.go snapd-2.42.1+19.10/store/errors.go
--- snapd-2.41+19.10.1/store/errors.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/store/errors.go 2019-10-30 12:17:43.000000000 +0000
@@ -26,7 +26,7 @@
"sort"
"strings"
- "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/channel"
"github.com/snapcore/snapd/strutil"
)
@@ -71,7 +71,7 @@
type RevisionNotAvailableError struct {
Action string
Channel string
- Releases []snap.Channel
+ Releases []channel.Channel
}
func (e *RevisionNotAvailableError) Error() string {
@@ -234,18 +234,18 @@
errDeviceAuthorizationNeedsRefresh = errors.New("soft-expired device authorization needs refresh")
)
-func translateSnapActionError(action, channel, code, message string, releases []snapRelease) error {
+func translateSnapActionError(action, snapChannel, code, message string, releases []snapRelease) error {
switch code {
case "revision-not-found":
e := &RevisionNotAvailableError{
Action: action,
- Channel: channel,
+ Channel: snapChannel,
}
if len(releases) != 0 {
- parsedReleases := make([]snap.Channel, len(releases))
+ parsedReleases := make([]channel.Channel, len(releases))
for i := 0; i < len(releases); i++ {
var err error
- parsedReleases[i], err = snap.ParseChannel(releases[i].Channel, releases[i].Architecture)
+ parsedReleases[i], err = channel.Parse(releases[i].Channel, releases[i].Architecture)
if err != nil {
// shouldn't happen, return error without Releases
return e
diff -Nru snapd-2.41+19.10.1/store/export_test.go snapd-2.42.1+19.10/store/export_test.go
--- snapd-2.41+19.10.1/store/export_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/store/export_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -141,8 +141,8 @@
sto.deltaFormat = dfmt
}
-func (sto *Store) DownloadDelta(deltaName string, downloadInfo *snap.DownloadInfo, w io.ReadWriteSeeker, pbar progress.Meter, user *auth.UserState) error {
- return sto.downloadDelta(deltaName, downloadInfo, w, pbar, user)
+func (sto *Store) DownloadDelta(deltaName string, downloadInfo *snap.DownloadInfo, w io.ReadWriteSeeker, pbar progress.Meter, user *auth.UserState, dlOpts *DownloadOptions) error {
+ return sto.downloadDelta(deltaName, downloadInfo, w, pbar, user, dlOpts)
}
func (sto *Store) DoRequest(ctx context.Context, client *http.Client, reqOptions *requestOptions, user *auth.UserState) (*http.Response, error) {
diff -Nru snapd-2.41+19.10.1/store/store.go snapd-2.42.1+19.10/store/store.go
--- snapd-2.41+19.10.1/store/store.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/store/store.go 2019-10-30 12:17:43.000000000 +0000
@@ -348,7 +348,7 @@
architecture := cfg.Architecture
if cfg.Architecture == "" {
- architecture = arch.UbuntuArchitecture()
+ architecture = arch.DpkgArchitecture()
}
series := cfg.Series
@@ -1334,8 +1334,9 @@
}
type DownloadOptions struct {
- RateLimit int64
- IsAutoRefresh bool
+ RateLimit int64
+ IsAutoRefresh bool
+ LeavePartialOnError bool
}
// Download downloads the snap addressed by download info and returns its
@@ -1356,7 +1357,7 @@
logger.Debugf("Available deltas returned by store: %v", downloadInfo.Deltas)
if len(downloadInfo.Deltas) == 1 {
- err := s.downloadAndApplyDelta(name, targetPath, downloadInfo, pbar, user)
+ err := s.downloadAndApplyDelta(name, targetPath, downloadInfo, pbar, user, dlOpts)
if err == nil {
return nil
}
@@ -1375,10 +1376,14 @@
return err
}
defer func() {
+ fi, _ := w.Stat()
if cerr := w.Close(); cerr != nil && err == nil {
err = cerr
}
- if err != nil {
+ if err == nil {
+ return
+ }
+ if dlOpts == nil || !dlOpts.LeavePartialOnError || fi == nil || fi.Size() == 0 {
os.Remove(w.Name())
}
}()
@@ -1645,7 +1650,7 @@
}
// downloadDelta downloads the delta for the preferred format, returning the path.
-func (s *Store) downloadDelta(deltaName string, downloadInfo *snap.DownloadInfo, w io.ReadWriteSeeker, pbar progress.Meter, user *auth.UserState) error {
+func (s *Store) downloadDelta(deltaName string, downloadInfo *snap.DownloadInfo, w io.ReadWriteSeeker, pbar progress.Meter, user *auth.UserState, dlOpts *DownloadOptions) error {
if len(downloadInfo.Deltas) != 1 {
return errors.New("store returned more than one download delta")
@@ -1667,7 +1672,7 @@
url = deltaInfo.DownloadURL
}
- return download(context.TODO(), deltaName, deltaInfo.Sha3_384, url, user, s, w, 0, pbar, nil)
+ return download(context.TODO(), deltaName, deltaInfo.Sha3_384, url, user, s, w, 0, pbar, dlOpts)
}
func getXdelta3Cmd(args ...string) (*exec.Cmd, error) {
@@ -1732,7 +1737,7 @@
}
// downloadAndApplyDelta downloads and then applies the delta to the current snap.
-func (s *Store) downloadAndApplyDelta(name, targetPath string, downloadInfo *snap.DownloadInfo, pbar progress.Meter, user *auth.UserState) error {
+func (s *Store) downloadAndApplyDelta(name, targetPath string, downloadInfo *snap.DownloadInfo, pbar progress.Meter, user *auth.UserState, dlOpts *DownloadOptions) error {
deltaInfo := &downloadInfo.Deltas[0]
deltaPath := fmt.Sprintf("%s.%s-%d-to-%d.partial", targetPath, deltaInfo.Format, deltaInfo.FromRevision, deltaInfo.ToRevision)
@@ -1749,7 +1754,7 @@
os.Remove(deltaPath)
}()
- err = s.downloadDelta(deltaName, downloadInfo, w, pbar, user)
+ err = s.downloadDelta(deltaName, downloadInfo, w, pbar, user, dlOpts)
if err != nil {
return err
}
diff -Nru snapd-2.41+19.10.1/store/store_test.go snapd-2.42.1+19.10/store/store_test.go
--- snapd-2.41+19.10.1/store/store_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/store/store_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -56,6 +56,7 @@
"github.com/snapcore/snapd/progress"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/channel"
"github.com/snapcore/snapd/snap/snaptest"
"github.com/snapcore/snapd/store"
"github.com/snapcore/snapd/testutil"
@@ -861,6 +862,56 @@
c.Assert(err, ErrorMatches, "uh, it failed")
// ... and ensure that the tempfile is removed
c.Assert(osutil.FileExists(tmpfile.Name()), Equals, false)
+ // ... and not because it succeeded either
+ c.Assert(osutil.FileExists(path), Equals, false)
+}
+
+func (s *storeTestSuite) TestDownloadFailsLeavePartial(c *C) {
+ var tmpfile *os.File
+ restore := store.MockDownload(func(ctx context.Context, name, sha3, url string, user *auth.UserState, s *store.Store, w io.ReadWriteSeeker, resume int64, pbar progress.Meter, dlOpts *store.DownloadOptions) error {
+ tmpfile = w.(*os.File)
+ w.Write([]byte{'X'}) // so it's not empty
+ return fmt.Errorf("uh, it failed")
+ })
+ defer restore()
+
+ snap := &snap.Info{}
+ snap.RealName = "foo"
+ snap.AnonDownloadURL = "anon-url"
+ snap.DownloadURL = "AUTH-URL"
+ snap.Size = 1
+ // simulate a failed download
+ path := filepath.Join(c.MkDir(), "downloaded-file")
+ err := s.store.Download(s.ctx, "foo", path, &snap.DownloadInfo, nil, nil, &store.DownloadOptions{LeavePartialOnError: true})
+ c.Assert(err, ErrorMatches, "uh, it failed")
+ // ... and ensure that the tempfile is *NOT* removed
+ c.Assert(osutil.FileExists(tmpfile.Name()), Equals, true)
+ // ... but the target path isn't there
+ c.Assert(osutil.FileExists(path), Equals, false)
+}
+
+func (s *storeTestSuite) TestDownloadFailsDoesNotLeavePartialIfEmpty(c *C) {
+ var tmpfile *os.File
+ restore := store.MockDownload(func(ctx context.Context, name, sha3, url string, user *auth.UserState, s *store.Store, w io.ReadWriteSeeker, resume int64, pbar progress.Meter, dlOpts *store.DownloadOptions) error {
+ tmpfile = w.(*os.File)
+ // no write, so the partial is empty
+ return fmt.Errorf("uh, it failed")
+ })
+ defer restore()
+
+ snap := &snap.Info{}
+ snap.RealName = "foo"
+ snap.AnonDownloadURL = "anon-url"
+ snap.DownloadURL = "AUTH-URL"
+ snap.Size = 1
+ // simulate a failed download
+ path := filepath.Join(c.MkDir(), "downloaded-file")
+ err := s.store.Download(s.ctx, "foo", path, &snap.DownloadInfo, nil, nil, &store.DownloadOptions{LeavePartialOnError: true})
+ c.Assert(err, ErrorMatches, "uh, it failed")
+ // ... and ensure that the tempfile *is* removed
+ c.Assert(osutil.FileExists(tmpfile.Name()), Equals, false)
+ // ... and the target path isn't there
+ c.Assert(osutil.FileExists(path), Equals, false)
}
func (s *storeTestSuite) TestDownloadSyncFails(c *C) {
@@ -886,6 +937,8 @@
c.Assert(err, ErrorMatches, `(sync|fsync:) .*`)
// ... and ensure that the tempfile is removed
c.Assert(osutil.FileExists(tmpfile.Name()), Equals, false)
+ // ... because it's been renamed to the target path already
+ c.Assert(osutil.FileExists(path), Equals, true)
}
var downloadDeltaTests = []struct {
@@ -993,6 +1046,7 @@
for _, testCase := range downloadDeltaTests {
sto.SetDeltaFormat(testCase.format)
restore := store.MockDownload(func(ctx context.Context, name, sha3, url string, user *auth.UserState, _ *store.Store, w io.ReadWriteSeeker, resume int64, pbar progress.Meter, dlOpts *store.DownloadOptions) error {
+ c.Check(dlOpts, DeepEquals, &store.DownloadOptions{IsAutoRefresh: true})
expectedUser := s.user
if testCase.useLocalUser {
expectedUser = s.localUser
@@ -1024,7 +1078,7 @@
authedUser = nil
}
- err = sto.DownloadDelta("snapname", &testCase.info, w, nil, authedUser)
+ err = sto.DownloadDelta("snapname", &testCase.info, w, nil, authedUser, &store.DownloadOptions{IsAutoRefresh: true})
if testCase.expectError {
c.Assert(err, NotNil)
@@ -1944,7 +1998,7 @@
query := r.URL.Query()
c.Check(query.Get("fields"), Equals, "abc,def")
- c.Check(query.Get("architecture"), Equals, arch.UbuntuArchitecture())
+ c.Check(query.Get("architecture"), Equals, arch.DpkgArchitecture())
w.Header().Set("X-Suggested-Currency", "GBP")
w.WriteHeader(200)
@@ -2908,7 +2962,7 @@
c.Check(r.URL.Query().Get("fields"), Equals, "abc,def")
c.Check(r.Header.Get("X-Ubuntu-Series"), Equals, release.Series)
- c.Check(r.Header.Get("X-Ubuntu-Architecture"), Equals, arch.UbuntuArchitecture())
+ c.Check(r.Header.Get("X-Ubuntu-Architecture"), Equals, arch.DpkgArchitecture())
c.Check(r.Header.Get("X-Ubuntu-Classic"), Equals, "false")
c.Check(r.Header.Get("X-Ubuntu-Confinement"), Equals, "")
@@ -4399,7 +4453,7 @@
c.Check(storeID, Equals, "")
c.Check(r.Header.Get("Snap-Device-Series"), Equals, release.Series)
- c.Check(r.Header.Get("Snap-Device-Architecture"), Equals, arch.UbuntuArchitecture())
+ c.Check(r.Header.Get("Snap-Device-Architecture"), Equals, arch.DpkgArchitecture())
c.Check(r.Header.Get("Snap-Classic"), Equals, "false")
jsonReq, err := ioutil.ReadAll(r.Body)
@@ -4509,7 +4563,7 @@
c.Check(storeID, Equals, "")
c.Check(r.Header.Get("Snap-Device-Series"), Equals, release.Series)
- c.Check(r.Header.Get("Snap-Device-Architecture"), Equals, arch.UbuntuArchitecture())
+ c.Check(r.Header.Get("Snap-Device-Architecture"), Equals, arch.DpkgArchitecture())
c.Check(r.Header.Get("Snap-Classic"), Equals, "false")
jsonReq, err := ioutil.ReadAll(r.Body)
@@ -5514,7 +5568,7 @@
c.Check(storeID, Equals, "")
c.Check(r.Header.Get("Snap-Device-Series"), Equals, release.Series)
- c.Check(r.Header.Get("Snap-Device-Architecture"), Equals, arch.UbuntuArchitecture())
+ c.Check(r.Header.Get("Snap-Device-Architecture"), Equals, arch.DpkgArchitecture())
c.Check(r.Header.Get("Snap-Classic"), Equals, "false")
jsonReq, err := ioutil.ReadAll(r.Body)
@@ -5611,7 +5665,7 @@
c.Check(storeID, Equals, "")
c.Check(r.Header.Get("Snap-Device-Series"), Equals, release.Series)
- c.Check(r.Header.Get("Snap-Device-Architecture"), Equals, arch.UbuntuArchitecture())
+ c.Check(r.Header.Get("Snap-Device-Architecture"), Equals, arch.DpkgArchitecture())
c.Check(r.Header.Get("Snap-Classic"), Equals, "false")
jsonReq, err := ioutil.ReadAll(r.Body)
@@ -5781,7 +5835,7 @@
c.Check(storeID, Equals, "")
c.Check(r.Header.Get("Snap-Device-Series"), Equals, release.Series)
- c.Check(r.Header.Get("Snap-Device-Architecture"), Equals, arch.UbuntuArchitecture())
+ c.Check(r.Header.Get("Snap-Device-Architecture"), Equals, arch.DpkgArchitecture())
c.Check(r.Header.Get("Snap-Classic"), Equals, "false")
jsonReq, err := ioutil.ReadAll(r.Body)
@@ -6015,7 +6069,7 @@
"snap2": &store.RevisionNotAvailableError{
Action: "refresh",
Channel: "candidate",
- Releases: []snap.Channel{
+ Releases: []channel.Channel{
snaptest.MustParseChannel("beta", "amd64"),
snaptest.MustParseChannel("beta", "arm64"),
},
@@ -6511,7 +6565,7 @@
switch r.URL.Path {
case "/v2/snaps/info/core":
c.Check(r.Method, Equals, "GET")
- c.Check(r.URL.Query(), DeepEquals, url.Values{"fields": {"download"}, "architecture": {arch.UbuntuArchitecture()}})
+ c.Check(r.URL.Query(), DeepEquals, url.Values{"fields": {"download"}, "architecture": {arch.DpkgArchitecture()}})
u, err := url.Parse("/download/core")
c.Assert(err, IsNil)
io.WriteString(w,
diff -Nru snapd-2.41+19.10.1/strutil/set.go snapd-2.42.1+19.10/strutil/set.go
--- snapd-2.41+19.10.1/strutil/set.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/strutil/set.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,81 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package strutil
+
+// OrderedSet is a set of strings that maintains the order of insertion.
+//
+// The order of putting items into the set is retained. Putting items "a", "b"
+// and then "a", results in order "a", "b".
+//
+// External synchronization is required for safe concurrent access.
+type OrderedSet struct {
+ positionOf map[string]int
+}
+
+// Items returns a slice of strings representing insertion order.
+//
+// Items is O(N) in the size of the set.
+func (o *OrderedSet) Items() []string {
+ if len(o.positionOf) == 0 {
+ return nil
+ }
+ items := make([]string, len(o.positionOf))
+ for item, idx := range o.positionOf {
+ items[idx] = item
+ }
+ return items
+}
+
+// Contains returns true if the set contains a given item.
+//
+// Contains is O(1) in the size of the set.
+func (o *OrderedSet) Contains(item string) bool {
+ _, ok := o.positionOf[item]
+ return ok
+}
+
+// IndexOf returns the position of an item in the set.
+func (o *OrderedSet) IndexOf(item string) (idx int, ok bool) {
+ idx, ok = o.positionOf[item]
+ return idx, ok
+}
+
+// Put adds an item into the set.
+//
+// If the item was not present then it is stored and ordered after all existing
+// elements. If the item was already present its position is not changed.
+//
+// Put is O(1) in the size of the set.
+func (o *OrderedSet) Put(item string) {
+ if o.positionOf == nil {
+ o.positionOf = make(map[string]int)
+ }
+
+ if _, ok := o.positionOf[item]; ok {
+ return
+ }
+
+ o.positionOf[item] = len(o.positionOf)
+}
+
+// Size returns the number of elements in the set.
+func (o *OrderedSet) Size() int {
+ return len(o.positionOf)
+}
diff -Nru snapd-2.41+19.10.1/strutil/set_test.go snapd-2.42.1+19.10/strutil/set_test.go
--- snapd-2.41+19.10.1/strutil/set_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/strutil/set_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,79 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package strutil_test
+
+import (
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/strutil"
+)
+
+type orderedSetSuite struct {
+ set strutil.OrderedSet
+}
+
+var _ = Suite(&orderedSetSuite{})
+
+func (s *orderedSetSuite) SetUpTest(c *C) {
+ s.set = strutil.OrderedSet{}
+}
+
+func (s *orderedSetSuite) TestZeroValueItems(c *C) {
+ c.Assert(s.set.Items(), HasLen, 0)
+}
+
+func (s *orderedSetSuite) TestZeroValueContains(c *C) {
+ c.Check(s.set.Contains("foo"), Equals, false)
+}
+
+func (s *orderedSetSuite) TestZeroValueIndexOf(c *C) {
+ _, ok := s.set.IndexOf("foo")
+ c.Check(ok, Equals, false)
+}
+
+func (s *orderedSetSuite) TestZeroValuePut(c *C) {
+ items := []string{"foo", "bar", "froz"}
+ for idx, item := range items {
+ s.set.Put(item)
+ c.Check(s.set.Contains(item), Equals, true)
+ realIdx, ok := s.set.IndexOf(item)
+ c.Check(ok, Equals, true)
+ c.Check(idx, Equals, realIdx)
+ c.Check(s.set.Size(), Equals, idx+1)
+ c.Check(s.set.Items(), DeepEquals, items[:idx+1])
+ }
+}
+
+func (s *orderedSetSuite) TestZeroValueSize(c *C) {
+ c.Assert(s.set.Size(), Equals, 0)
+}
+
+func (s *orderedSetSuite) TestDeduplication(c *C) {
+ s.set.Put("a")
+ s.set.Put("b")
+ s.set.Put("a")
+ s.set.Put("c")
+
+ c.Assert(s.set.Items(), DeepEquals, []string{"a", "b", "c"})
+ c.Check(s.set.Size(), Equals, 3)
+ c.Check(s.set.Contains("a"), Equals, true)
+ c.Check(s.set.Contains("b"), Equals, true)
+ c.Check(s.set.Contains("c"), Equals, true)
+}
diff -Nru snapd-2.41+19.10.1/systemd/systemd.go snapd-2.42.1+19.10/systemd/systemd.go
--- snapd-2.41+19.10.1/systemd/systemd.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/systemd/systemd.go 2019-10-30 12:17:43.000000000 +0000
@@ -674,6 +674,7 @@
Where=%s
Type=%s
Options=%s
+LazyUnmount=yes
[Install]
WantedBy=multi-user.target
diff -Nru snapd-2.41+19.10.1/systemd/systemd_test.go snapd-2.42.1+19.10/systemd/systemd_test.go
--- snapd-2.41+19.10.1/systemd/systemd_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/systemd/systemd_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -505,7 +505,7 @@
func (s *SystemdTestSuite) TestAddMountUnit(c *C) {
rootDir := dirs.GlobalRootDir
- restore := squashfs.MockUseFuse(false)
+ restore := squashfs.MockNeedsFuse(false)
defer restore()
mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap")
@@ -525,6 +525,7 @@
Where=/snap/snapname/123
Type=squashfs
Options=nodev,ro,x-gdu.hide
+LazyUnmount=yes
[Install]
WantedBy=multi-user.target
@@ -538,7 +539,7 @@
}
func (s *SystemdTestSuite) TestAddMountUnitForDirs(c *C) {
- restore := squashfs.MockUseFuse(false)
+ restore := squashfs.MockNeedsFuse(false)
defer restore()
// a directory instead of a file produces a different output
@@ -557,6 +558,7 @@
Where=/snap/snapname/x1
Type=none
Options=nodev,ro,x-gdu.hide,bind
+LazyUnmount=yes
[Install]
WantedBy=multi-user.target
@@ -572,7 +574,7 @@
func (s *SystemdTestSuite) TestWriteSELinuxMountUnit(c *C) {
restore := release.MockSELinuxIsEnabled(func() (bool, error) { return true, nil })
defer restore()
- restore = squashfs.MockUseFuse(false)
+ restore = squashfs.MockNeedsFuse(false)
defer restore()
mockSnapPath := filepath.Join(c.MkDir(), "/var/lib/snappy/snaps/foo_1.0.snap")
@@ -595,6 +597,7 @@
Where=/snap/snapname/123
Type=squashfs
Options=nodev,ro,x-gdu.hide,context=system_u:object_r:snappy_snap_t:s0
+LazyUnmount=yes
[Install]
WantedBy=multi-user.target
@@ -637,6 +640,7 @@
Where=/snap/snapname/123
Type=fuse.squashfuse
Options=nodev,ro,x-gdu.hide,allow_other
+LazyUnmount=yes
[Install]
WantedBy=multi-user.target
@@ -675,6 +679,7 @@
Where=/snap/snapname/123
Type=squashfs
Options=nodev,ro,x-gdu.hide
+LazyUnmount=yes
[Install]
WantedBy=multi-user.target
diff -Nru snapd-2.41+19.10.1/tests/cross/go-build/task.yaml snapd-2.42.1+19.10/tests/cross/go-build/task.yaml
--- snapd-2.41+19.10.1/tests/cross/go-build/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/cross/go-build/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -57,6 +57,7 @@
execute: |
cd /tmp/cross-build/src/github.com/snapcore/snapd
- for cmd in $( find ./cmd -mindepth 2 -maxdepth 2 -name \*.go -printf '%h\n' | sort -u ); do
+ # grab only packages whose name is 'main'
+ for cmd in $( GOPATH=/tmp/cross-build go list -f '{{if eq .Name "main"}}{{.ImportPath}}{{end}}' ./cmd/...); do
su -c "GOPATH=/tmp/cross-build CGO_ENABLED=1 GOARCH=$X_GOARCH CC=$X_CC go build -v -o /dev/null $cmd" test
done
diff -Nru snapd-2.41+19.10.1/tests/lib/assertions/developer1-my-classic-w-gadget-18.model snapd-2.42.1+19.10/tests/lib/assertions/developer1-my-classic-w-gadget-18.model
--- snapd-2.41+19.10.1/tests/lib/assertions/developer1-my-classic-w-gadget-18.model 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/assertions/developer1-my-classic-w-gadget-18.model 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,22 @@
+type: model
+authority-id: developer1
+series: 16
+brand-id: developer1
+model: my-classic-w-gadget-18
+classic: true
+gadget: classic-gadget-18
+required-snaps:
+ - core18
+timestamp: 2019-07-20T14:50:00+00:00
+sign-key-sha3-384: EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu
+
+AcLBUgQAAQoABgUCXVu5EQAALCwQAHq/HmEIMha+rrOILXhfv7qYZ90sbwC34L5r13MIEDHATweI
+AMHzxpmmDp2SuwoWHHoYQa4ond0CtSMaXD1Tj9tqxDZBUb9Zxd3DeBwYKwaWSHIoqnBT7G3g8fx1
+J2q1I5jII9ReHaNZbGTQPXfmv3od/5LTUJvovdfepCTZrK4neUNyx4d8YaYWVSyQEpVK90hlB2Ft
+vgAviiMQ8qgIHzFi5IDeDuKbRotPp6okcIQXQ9OSH8J7sYcrlQ26q40V25ipyO/WsMXDxHyexMTt
+r+tD+rwpSr4obY21qt9XWPGxs7rR3vXFDKxW7HKdOKS1q41K36Np4K4gtEAVYZ7S45OGUNSk9qvc
+1yXwtBw3AqKVuRbK/UaD/aHXa/Ues5YDmOW/18/qPkdaE01AvGgoE2OtgEVEZ39nK2Za+2dWa7Jg
+Loa5gAAI9mv7IsUtfvMbhaQp5Tpi07j2JgeibX9hEI8Fr10XF4PsX8Iso8QDYvxeh6he6BbWcyk7
+ouZH85Cf3rOgQ+F8JvOVhruYJVa9kwkpLZasudRr+VdMIerr6Tubw9L0IVGFNbMyARJ27eEHINOc
+IV7bBwp83tv8m5t67Lo6Ysk0EDiHsoBb/GGCEAQXfBPN7wvGAKVcd0bHeZu2SwkArf9+OWe90icE
+iXWpOCGMvYoMCvBkP5U0BXjPgiuX
diff -Nru snapd-2.41+19.10.1/tests/lib/assertions/developer1-pc-new-kernel.model snapd-2.42.1+19.10/tests/lib/assertions/developer1-pc-new-kernel.model
--- snapd-2.41+19.10.1/tests/lib/assertions/developer1-pc-new-kernel.model 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/assertions/developer1-pc-new-kernel.model 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,22 @@
+type: model
+authority-id: developer1
+revision: 2
+series: 16
+brand-id: developer1
+model: my-model
+architecture: amd64
+gadget: pc
+kernel: test-snapd-pc-kernel
+timestamp: 2019-05-07T10:00:00+00:00
+sign-key-sha3-384: EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu
+
+AcLBUgQAAQoABgUCXNFHYgAAXv0QAE6MkNwPrIJ8Zro+OW8vwm7ziBbojCuHuSYAUpscDYUaKEtE
+eJCybYpC1gOXPfo6cpqriqvTgTAaSRqmloQXcI2Ep6DTlXC0VJGvbll8ZFRh8CwkcvBsx6gU1aKv
+I6pUpAAr+2u0uaX0oCqt8GEr2AmmRy5384KCEabSy1/3cMNxZu2WOfwMiLaD6/eXfINM/P+jpSQX
+fdiFmO7nZASEEqQlEEBf1icJTfxZd8KWowRMlFAKBq1keBm7pTKOTZpToLjT5mgNNiObRsn/URYz
+S4Wkwme1G3jTo2ueigcVcwrA7UeKl/wm4bWZ/9bgz2vDbe5Xv0IlUxS62DqLSWA+u/y9NF9bvkiE
+pF0jb+X0KB3v8XegwXjBMe+upA17+i5v1z2cYBT2IZD780oV62Wsgxa4pumMf6cDyUgZUPPeBsgQ
+RWexshNWgdaMYgiZrkDJI6Iq8MWLwiq/je34pUf4MbdXLj3ZBka3OxQlAe1GsO73txd6TH0/Ic2U
+pw9dAh12Epjo4ojmnnejp+XW7/ByhXoeOp/+ekhIkQJ6MlPN734NPj5kyy09V4pV/r37CZfr0yXh
+W27kWr/L9XKf8MXJ8czUkyLSK/lxrqAYMRgh4AiClx1j3qPLxDhr18pK3IyUcdXfqLA4MkRByamS
+ML7BN87LXt0Z7ox8NNMle3vQHX2K
diff -Nru snapd-2.41+19.10.1/tests/lib/bin/apt-tool snapd-2.42.1+19.10/tests/lib/bin/apt-tool
--- snapd-2.41+19.10.1/tests/lib/bin/apt-tool 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/bin/apt-tool 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,36 @@
+#!/usr/bin/python3
+
+import sys
+
+import apt
+
+
+def checkpoint():
+ pkgs = set()
+ cache = apt.Cache()
+ for pkg in cache:
+ if pkg.is_installed:
+ pkgs.add(pkg.name)
+ for name in sorted(pkgs):
+ print(name)
+
+
+def restore(fname):
+ desired = set([line.strip() for line in open(fname)])
+
+ cache = apt.Cache()
+ for pkg in cache:
+ if pkg.is_installed and not pkg.name in desired:
+ print("removing", pkg)
+ pkg.mark_delete(auto_fix=False)
+ if not pkg.is_installed and pkg.name in desired:
+ print("installing", pkg)
+ pkg.mark_install(auto_fix=False, auto_install=False)
+ cache.commit()
+
+
+if __name__ == "__main__":
+ if sys.argv[1] == "checkpoint":
+ checkpoint()
+ elif sys.argv[1] == "restore":
+ restore(sys.argv[2])
diff -Nru snapd-2.41+19.10.1/tests/lib/bin/lxd-tool snapd-2.42.1+19.10/tests/lib/bin/lxd-tool
--- snapd-2.41+19.10.1/tests/lib/bin/lxd-tool 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/bin/lxd-tool 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,35 @@
+#!/bin/sh -e
+case "${1:-}" in
+ undo-lxd-mount-changes)
+ # Vanilla systems have /sys/fs/cgroup/cpuset without clone_children option.
+ # Using LXD to create a container enables this option, as can be seen here:
+ #
+ # -37 32 0:32 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:15 - cgroup cgroup rw,cpuset
+ # +37 32 0:32 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:15 - cgroup cgroup rw,cpuset,clone_children
+ #
+ # To restore vanilla state, disable the option now.
+ if [ "$(mountinfo-tool /sys/fs/cgroup/cpuset .fs_type)" = cgroup ]; then
+ echo 0 > /sys/fs/cgroup/cpuset/cgroup.clone_children
+ fi
+
+ # Vanilla system have /sys/fs/cgroup/unified mounted with the nsdelegate
+ # option which is available since kernel 4.13 Using LXD to create a
+ # container disables this options, as can be seen here:
+ #
+ # -32 31 0:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime shared:10 - cgroup2 cgroup rw,nsdelegate
+ # +32 31 0:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime shared:10 - cgroup2 cgroup rw
+ #
+ # To restore vanilla state, enable the option now, but only if the kernel supports that.
+ # https://lore.kernel.org/patchwork/patch/803265/
+ # https://github.com/systemd/systemd/commit/4095205ecccdfddb822ee8fdc44d11f2ded9be24
+ # The kernel version must be made compatible with the strict version
+ # comparison. I chose to cut at the "-" and take the stuff before it.
+ if [ "$(mountinfo-tool /sys/fs/cgroup/unified .fs_type)" = cgroup2 ] && version-tool --strict "$(uname -r | cut -d- -f 1)" -ge 4.13; then
+ mount -o remount,nsdelegate /sys/fs/cgroup/unified
+ fi
+ ;;
+ *)
+ echo "lxd-tool: unknown command $*" >&2
+ exit 1
+ ;;
+esac
diff -Nru snapd-2.41+19.10.1/tests/lib/bin/mountinfo-tool snapd-2.42.1+19.10/tests/lib/bin/mountinfo-tool
--- snapd-2.41+19.10.1/tests/lib/bin/mountinfo-tool 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/bin/mountinfo-tool 2019-10-30 12:17:43.000000000 +0000
@@ -66,6 +66,47 @@
return self & ((1 << 16) - 1)
+def _format_opt_fields(fields):
+ # type: (List[Text]) -> str
+ """_format_opt_fields returns the special formatting of optional fields."""
+ if len(fields):
+ result = " ".join(fields) + " -"
+ else:
+ result = "-"
+ if PY2:
+ return result.encode()
+ return result
+
+
+# OptionalFields is a specialization of List[Text] but because of some Python 2
+# distributions lacking the typing module, we must define a variant for mypy
+# and one for without mypy. In both cases the customized formatting logic is
+# implemented by _format_opt_fields().
+if MYPY:
+
+ class OptionalFields(List[Text]):
+ def __str__(self):
+ # type: () -> str
+ return _format_opt_fields(self)
+
+
+else:
+
+ class OptionalFields(list):
+ def __str__(self):
+ # type: () -> str
+ return _format_opt_fields(self)
+
+
+class OptionalFieldsTests(unittest.TestCase):
+ def test_str(self):
+ # type: () -> None
+ fields = OptionalFields()
+ self.assertEqual("{}".format(fields), "-")
+ fields.append("master:123")
+ self.assertEqual("{}".format(fields), "master:123 -")
+
+
class MountInfoEntry(object):
"""Single entry in /proc/pid/mointinfo, see proc(5)"""
@@ -90,10 +131,13 @@
self.root_dir = ""
self.mount_point = ""
self.mount_opts = ""
- self.opt_fields = [] # type: List[Text]
+ self.opt_fields = OptionalFields() # type: OptionalFields
self.fs_type = ""
self.mount_source = ""
self.sb_opts = ""
+ # This field does not represent kernel state.
+ # It is a marker for an entry being matched by a filter.
+ self.matched = False
def __eq__(self, other):
# type: (object) -> Union[NotImplemented, bool]
@@ -124,7 +168,7 @@
self.root_dir = next(it)
self.mount_point = next(it)
self.mount_opts = next(it)
- self.opt_fields = []
+ self.opt_fields = OptionalFields()
for opt_field in it:
if opt_field == "-":
break
@@ -144,9 +188,9 @@
# type: () -> str
result = (
"{0.mount_id} {0.parent_id} {0.dev} {0.root_dir}"
- " {0.mount_point} {0.mount_opts} {opt_fields} {0.fs_type}"
+ " {0.mount_point} {0.mount_opts} {0.opt_fields} {0.fs_type}"
" {0.mount_source} {0.sb_opts}"
- ).format(self, opt_fields=" ".join(self.opt_fields + ["-"]))
+ ).format(self)
if PY2:
return result.encode()
return result
@@ -374,7 +418,9 @@
# type: (Match[Text]) -> Text
return "{}".format(alloc_n(int(m.group(1))))
- entry.opt_fields = [re.sub("(\\d+)", fn, opt) for opt in entry.opt_fields]
+ entry.opt_fields = OptionalFields(
+ [re.sub("(\\d+)", fn, opt) for opt in entry.opt_fields]
+ )
def renumber_loop_devices(entry, seen):
@@ -687,6 +733,13 @@
raise SystemExit(exc)
entries = [MountInfoEntry.parse(line) for line in opts.file]
+ # Apply entry filtering ahead of any renumbering.
+ num_matched = 0
+ for e in entries:
+ if matches(e, filters):
+ e.matched = True
+ num_matched += 1
+
# Build rewrite state based on reference tables. This way the entries
# we will display can be correlated to other tables.
rs = RewriteState()
@@ -725,9 +778,6 @@
if opts.rename:
rewrite_rename(entries, rewrite_order, rs)
- # Apply entry filtering.
- entries = [e for e in entries if matches(e, filters)]
-
# Apply entry reordering for display.
if opts.display_order:
@@ -737,25 +787,22 @@
entries.sort(key=display_key_fn)
for e in entries:
+ if not e.matched:
+ continue
if attrs:
- values = [] # type: List[Any]
- for attr in attrs:
- value = getattr(e, attr)
- if isinstance(value, list):
- value = " ".join(value)
- values.append(value)
- print(*values)
+ print(*[getattr(e, attr) for attr in attrs])
else:
print(e)
- if opts.one and len(entries) != 1:
+ if opts.one and num_matched != 1:
raise SystemExit(
- "--one requires exactly one match, found {}".format(len(entries))
+ "--one requires exactly one match, found {}".format(num_matched)
)
- # Return with an exit code indicating if anything matched.
- # This allows mountinfo-tool to be used in scripts.
- if len(entries) == 0:
+ # Return with an exit code indicating if anything matched.
+ # This allows mountinfo-tool to be used in scripts.
+ if num_matched == 0:
raise SystemExit(1)
+
class MountInfoEntryTests(unittest.TestCase):
non_zero_values = {
@@ -765,7 +812,7 @@
"root_dir": "/root-dir",
"mount_point": "/mount-point",
"mount_opts": "mount-opts",
- "opt_fields": ["opt:1", "fields:2"],
+ "opt_fields": OptionalFields(["opt:1", "fields:2"]),
"fs_type": "fs-type",
"mount_source": "mount-source",
"sb_opts": "sb-opts",
@@ -1090,7 +1137,7 @@
# Renumber the first mount entry, from the "initial" mount namespace,
# with the initial, zero multiple.
entry = MountInfoEntry()
- entry.opt_fields = ["shared:12"]
+ entry.opt_fields = OptionalFields(["shared:12"])
renumber_opt_fields(entry, self.seen, 0)
self.assertEqual(entry.opt_fields, ["shared:1"])
@@ -1098,7 +1145,7 @@
# different multiplier. Given that the peer group number was not seen
# before it gets allocated into the 1000-1999 range.
entry = MountInfoEntry()
- entry.opt_fields = ["shared:13"]
+ entry.opt_fields = OptionalFields(["shared:13"])
renumber_opt_fields(entry, self.seen, 1000)
self.assertEqual(entry.opt_fields, ["shared:1001"])
@@ -1107,7 +1154,7 @@
# namespace it is renumbered the same was as the original was, even
# though the actual sharing mode is different.
entry = MountInfoEntry()
- entry.opt_fields = ["master:12"]
+ entry.opt_fields = OptionalFields(["master:12"])
renumber_opt_fields(entry, self.seen, 1000)
self.assertEqual(entry.opt_fields, ["master:1"])
diff -Nru snapd-2.41+19.10.1/tests/lib/bin/retry-tool snapd-2.42.1+19.10/tests/lib/bin/retry-tool
--- snapd-2.41+19.10.1/tests/lib/bin/retry-tool 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/bin/retry-tool 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,111 @@
+#!/usr/bin/env any-python
+from __future__ import print_function, absolute_import, unicode_literals
+
+import argparse
+import subprocess
+import sys
+import time
+
+
+# Define MYPY as False and use it as a conditional for typing import. Despite
+# this declaration mypy will really treat MYPY as True when type-checking.
+# This is required so that we can import typing on Python 2.x without the
+# typing module installed. For more details see:
+# https://mypy.readthedocs.io/en/latest/common_issues.html#import-cycles
+MYPY = False
+if MYPY:
+ from typing import List, Text
+
+
+def _make_parser():
+ # type: () -> argparse.ArgumentParser
+ parser = argparse.ArgumentParser(
+ description="""
+Retry executes COMMAND at most N times, waiting for SECONDS between each
+attempt. On failure the exit code from the final attempt is returned.
+"""
+ )
+ parser.add_argument(
+ "-n",
+ "--attempts",
+ metavar="N",
+ type=int,
+ default=3,
+ help="number of attempts (default %(default)s)",
+ )
+ parser.add_argument(
+ "--wait",
+ metavar="SECONDS",
+ type=float,
+ default=1,
+ help="grace period between attempts (default %(default)ss)",
+ )
+ parser.add_argument(
+ "--quiet",
+ dest="verbose",
+ action="store_false",
+ default=True,
+ help="refrain from printing any output",
+ )
+ parser.add_argument(
+ "cmd", metavar="COMMAND", nargs="...", help="command to execute"
+ )
+ return parser
+
+
+def run_cmd(cmd, n, wait, verbose):
+ # type: (List[Text], int, float, bool) -> int
+ retcode = 0
+ for i in range(1, n + 1):
+ retcode = subprocess.call(cmd)
+ if retcode == 0:
+ break
+ if verbose:
+ print(
+ "retry: command {} failed with code {}".format(" ".join(cmd), retcode),
+ file=sys.stderr,
+ )
+ if i < n:
+ if verbose:
+ print(
+ "retry: next attempt in {} second(s) (attempt {} of {})".format(
+ wait, i, n
+ ),
+ file=sys.stderr,
+ )
+ time.sleep(wait)
+ else:
+ if verbose and n > 1:
+ print(
+ "retry: command {} keeps failing after {} attempts".format(
+ " ".join(cmd), n
+ ),
+ file=sys.stderr,
+ )
+ return retcode
+
+
+def main():
+ # type: () -> None
+ parser = _make_parser()
+ ns = parser.parse_args()
+ # The command cannot be empty but it is difficult to express in argparse itself.
+ if len(ns.cmd) == 0:
+ parser.print_usage()
+ parser.exit(0)
+ # Return the last exit code as the exit code of this process.
+ try:
+ retcode = run_cmd(ns.cmd, ns.attempts, ns.wait, ns.verbose)
+ except OSError as exc:
+ if ns.verbose:
+ print(
+ "retry: cannot execute command {}: {}".format(" ".join(ns.cmd), exc),
+ file=sys.stderr,
+ )
+ raise SystemExit(1)
+ else:
+ raise SystemExit(retcode)
+
+
+if __name__ == "__main__":
+ main()
diff -Nru snapd-2.41+19.10.1/tests/lib/bin/user-tool snapd-2.42.1+19.10/tests/lib/bin/user-tool
--- snapd-2.41+19.10.1/tests/lib/bin/user-tool 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/bin/user-tool 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,55 @@
+#!/usr/bin/env any-python
+from __future__ import print_function, absolute_import, unicode_literals
+
+import argparse
+import os
+import subprocess
+
+# Define MYPY as False and use it as a conditional for typing import. Despite
+# this declaration mypy will really treat MYPY as True when type-checking.
+# This is required so that we can import typing on Python 2.x without the
+# typing module installed. For more details see:
+# https://mypy.readthedocs.io/en/latest/common_issues.html#import-cycles
+MYPY = False
+if MYPY:
+ from typing import Text
+
+
+def remove_user_with_group(user_name):
+ # type: (Text) -> None
+ """remove the user and group with the same name, if present."""
+ if os.path.exists("/var/lib/extrausers/passwd"):
+ subprocess.call(["userdel", "--extrausers", "--force", "--remove", user_name])
+ else:
+ subprocess.call(["userdel", "--force", "--remove", user_name])
+ # Some systems do not set "USERGROUPS_ENAB yes" so we need to cleanup
+ # the group manually. Use "-f" (force) when available, older versions
+ # do not have it.
+ proc = subprocess.Popen(["groupdel", "-h"], stdout=subprocess.PIPE)
+ out, _ = proc.communicate()
+ if b"force" in out:
+ subprocess.call(["groupdel", "-f", user_name])
+ else:
+ subprocess.call(["groupdel", user_name])
+ # Ensure the user user really got deleted
+ if subprocess.call(["getent", "passwd", user_name]) == 0:
+ raise SystemExit("user exists after removal?")
+ if subprocess.call(["getent", "group", user_name]) == 0:
+ raise SystemExit("group exists after removal?")
+
+
+def main():
+ # type: () -> None
+ parser = argparse.ArgumentParser()
+ sub = parser.add_subparsers()
+ cmd = sub.add_parser(
+ "remove-with-group", description="Remove system user and group, if present"
+ )
+ cmd.set_defaults(func=lambda ns: remove_user_with_group(ns.user))
+ cmd.add_argument("user", help="name of the user and group to remove")
+ ns = parser.parse_args()
+ ns.func(ns)
+
+
+if __name__ == "__main__":
+ main()
diff -Nru snapd-2.41+19.10.1/tests/lib/bin/version-tool snapd-2.42.1+19.10/tests/lib/bin/version-tool
--- snapd-2.41+19.10.1/tests/lib/bin/version-tool 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/bin/version-tool 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,308 @@
+#!/usr/bin/env any-python
+from __future__ import print_function, absolute_import, unicode_literals
+
+from argparse import Action, ArgumentParser, RawTextHelpFormatter, SUPPRESS
+import itertools
+import re
+import sys
+import sys
+import unittest
+
+# PY2 is true when we're running under Python 2.x It is used for appropriate
+# return value selection of __str__ and __repr_ methods, which must both
+# return str, not unicode (in Python 2) and str (in Python 3). In both cases
+# the return type annotation is exactly the same, but due to unicode_literals
+# being in effect, and the fact we often use a format string (which is an
+# unicode string in Python 2), we must encode the it to byte string when
+# running under Python 2.
+PY2 = sys.version_info[0] == 2
+
+# Define MYPY as False and use it as a conditional for typing import. Despite
+# this declaration mypy will really treat MYPY as True when type-checking.
+# This is required so that we can import typing on Python 2.x without the
+# typing module installed. For more details see:
+# https://mypy.readthedocs.io/en/latest/common_issues.html#import-cycles
+MYPY = False
+if MYPY:
+ from typing import Any, Text, Tuple, Optional, Union, Sequence
+ from argparse import Namespace
+
+
+class _UnitTestAction(Action):
+ def __init__(
+ self,
+ option_strings,
+ dest=SUPPRESS,
+ default=SUPPRESS,
+ help="run program's unit test suite and exit",
+ ):
+ # type: (Text, Text, Text, Text) -> None
+ super(_UnitTestAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ default=default,
+ nargs="...",
+ help=help,
+ )
+
+ def __call__(self, parser, ns, values, option_string=None):
+ # type: (ArgumentParser, Namespace, Union[str, Sequence[Any], None], Optional[Text]) -> None
+ # We allow the caller to provide the test to invoke by giving
+ # --run-unit-tests a set of arguments.
+ argv = [sys.argv[0]]
+ if isinstance(values, list):
+ argv += values
+ unittest.main(argv=argv)
+ parser.exit()
+
+
+def consistent_relation(rel_op, delta):
+ # type: (Text, int) -> bool
+ """
+ consistent_relation returns true if the relation is consistent with delta.
+
+ The relation operator is one of ==, !=, <, <=, > or >=.
+ Delta is either 0, a positive or a negative number.
+ """
+ if rel_op == "==":
+ return delta == 0
+ elif rel_op == "!=":
+ return delta != 0
+ elif rel_op == ">":
+ return delta > 0
+ elif rel_op == ">=":
+ return delta >= 0
+ elif rel_op == "<":
+ return delta < 0
+ elif rel_op == "<=":
+ return delta <= 0
+ raise ValueError("unexpected relational operator " + rel_op)
+
+
+class ConsistentRelationTests(unittest.TestCase):
+ def test_eq(self):
+ # type: () -> None
+ self.assertFalse(consistent_relation("==", -1))
+ self.assertTrue(consistent_relation("==", 0))
+ self.assertFalse(consistent_relation("==", +1))
+
+ def test_ne(self):
+ # type: () -> None
+ self.assertTrue(consistent_relation("!=", -1))
+ self.assertFalse(consistent_relation("!=", 0))
+ self.assertTrue(consistent_relation("!=", +1))
+
+ def test_gt(self):
+ # type: () -> None
+ self.assertFalse(consistent_relation(">", -1))
+ self.assertFalse(consistent_relation(">", 0))
+ self.assertTrue(consistent_relation(">", +1))
+
+ def test_ge(self):
+ # type: () -> None
+ self.assertFalse(consistent_relation(">=", -1))
+ self.assertTrue(consistent_relation(">=", 0))
+ self.assertTrue(consistent_relation(">=", +1))
+
+ def test_lt(self):
+ # type: () -> None
+ self.assertTrue(consistent_relation("<", -1))
+ self.assertFalse(consistent_relation("<", 0))
+ self.assertFalse(consistent_relation("<", +1))
+
+ def test_le(self):
+ # type: () -> None
+ self.assertTrue(consistent_relation("<=", -1))
+ self.assertTrue(consistent_relation("<=", 0))
+ self.assertFalse(consistent_relation("<=", +1))
+
+ def test_unknown(self):
+ # type: () -> None
+ with self.assertRaises(ValueError):
+ consistent_relation("???", 0)
+
+
+def strict_version_cmp(a, b):
+ # type: (Text, Text) -> int
+ """
+ strictly_version_cmp compares two version numbers without leeway.
+
+ The algorithm considers each version to be a tuple of integers. Non,
+ integer elements or element fragments are regarded as an error and raised
+ as ValueError.
+
+ Comparison is performed on by considering the leftmost element in each
+ tuple. First pair of numbers that are not equal determine the result of the
+ comparison. Tuples have unequal length then missing elements are
+ substituted with zero.
+
+ The return value is 0 if the version strings are equal, -1 if version a is
+ smaller or +1 if version b is smaller.
+ """
+ try:
+ a_items = [int(item, 10) for item in a.split(".")]
+ except ValueError:
+ raise ValueError("version {} is not purely numeric".format(a))
+ try:
+ b_items = [int(item, 10) for item in b.split(".")]
+ except ValueError:
+ raise ValueError("version {} is not purely numeric".format(b))
+ if PY2:
+ zip_longest_fn = itertools.izip_longest
+ else:
+ zip_longest_fn = itertools.zip_longest
+ for a_val, b_val in zip_longest_fn(a_items, b_items, fillvalue=0):
+ delta = a_val - b_val
+ if delta != 0:
+ return 1 if delta > 0 else -1
+ return 0
+
+
+class StrictVersionCmpTests(unittest.TestCase):
+ def test_simple(self):
+ # type: () -> None
+ self.assertEqual(strict_version_cmp("10", "10"), 0)
+ self.assertEqual(strict_version_cmp("10", "20"), -1)
+ self.assertEqual(strict_version_cmp("20", "10"), +1)
+
+ def test_many_segments(self):
+ # type: () -> None
+ self.assertEqual(strict_version_cmp("1.2.3", "1.2.3"), 0)
+ self.assertEqual(strict_version_cmp("1.2.3", "1.3.4"), -1)
+ self.assertEqual(strict_version_cmp("1.4.3", "1.2.3"), +1)
+ self.assertEqual(strict_version_cmp("1.0.0", "1.1.0"), -1)
+ self.assertEqual(strict_version_cmp("0.1.2", "1.1.2"), -1)
+
+ def test_unequal_length(self):
+ # type: () -> None
+ self.assertEqual(strict_version_cmp("1", "1.0"), 0)
+ self.assertEqual(strict_version_cmp("1", "1.2"), -1)
+ self.assertEqual(strict_version_cmp("1.2", "1"), +1)
+ self.assertEqual(strict_version_cmp("1", "1.0.1"), -1)
+ self.assertEqual(strict_version_cmp("1.1", "1.0.1"), +1)
+
+ def test_version_with_text(self):
+ # type: () -> None
+ with self.assertRaises(ValueError) as cm:
+ strict_version_cmp("1-foo", "1")
+ self.assertEqual(cm.exception.args, ("version 1-foo is not purely numeric",))
+ with self.assertRaises(ValueError) as cm:
+ strict_version_cmp("1.2-foo", "1.2")
+ self.assertEqual(cm.exception.args, ("version 1.2-foo is not purely numeric",))
+
+
+def _make_parser():
+ # type: () -> ArgumentParser
+ parser = ArgumentParser(
+ epilog="""
+Relational operator is one of:
+
+ -eq -ne -gt -ge -lt -le
+
+Version comparison is performed using the selected algorithm.
+
+strict:
+ The algorithm considers each version to be a tuple of integers.
+ Non-integer elements are considered to be invalid version.
+
+ Comparison is performed on by considering the leftmost element in each
+ tuple. First pair of numbers that are not equal determine the result of the
+ comparison. Tuples have unequal length then missing elements are
+ substituted with zero.
+ """,
+ formatter_class=RawTextHelpFormatter,
+ )
+ parser.register("action", "unit-test", _UnitTestAction)
+ parser.add_argument("-v", "--version", action="version", version="1.0")
+ parser.add_argument(
+ "--verbose", action="store_true", help="describe comparison process"
+ )
+
+ parser.add_argument("version_a", metavar="VERSION-A")
+ parser.add_argument("version_b", metavar="VERSION-B")
+
+ # algorithm selection
+ alg_grp = parser.add_mutually_exclusive_group(required=True)
+ alg_grp.add_argument(
+ "--strict",
+ dest="algorithm",
+ action="store_const",
+ const=strict_version_cmp,
+ help="select the strict version comparison",
+ )
+ # relation selection
+ rel_op_grp = parser.add_mutually_exclusive_group(required=True)
+ rel_op_grp.add_argument(
+ "-eq",
+ action="store_const",
+ const="==",
+ dest="rel_op",
+ help="test that versions are equal",
+ )
+ rel_op_grp.add_argument(
+ "-ne",
+ action="store_const",
+ const="!=",
+ dest="rel_op",
+ help="test that versions are not equal",
+ )
+ rel_op_grp.add_argument(
+ "-gt",
+ action="store_const",
+ const=">",
+ dest="rel_op",
+ help="test that version-a is greater than version-b",
+ )
+ rel_op_grp.add_argument(
+ "-ge",
+ action="store_const",
+ const=">=",
+ dest="rel_op",
+ help="test that version-a is greater than or equal to version-b",
+ )
+ rel_op_grp.add_argument(
+ "-lt",
+ action="store_const",
+ const="<",
+ dest="rel_op",
+ help="test that version-a is less than version-b",
+ )
+ rel_op_grp.add_argument(
+ "-le",
+ action="store_const",
+ const="<=",
+ dest="rel_op",
+ help="test that version-a is less than or equal to version-b",
+ )
+
+ # maintenance commands
+ maint_grp = parser.add_argument_group("maintenance commands")
+ maint_grp.add_argument("--run-unit-tests", action="unit-test", help=SUPPRESS)
+ return parser
+
+
+def main():
+ # type: () -> None
+ opts = _make_parser().parse_args()
+ try:
+ delta = opts.algorithm(opts.version_a, opts.version_b)
+ except ValueError as exc:
+ print("error: {}".format(exc), file=sys.stderr)
+ raise SystemExit(2)
+ else:
+ is_consistent = consistent_relation(opts.rel_op, delta)
+ if opts.verbose:
+ print(
+ "delta between {} and {} is: {}".format(
+ opts.version_a, opts.version_b, delta
+ )
+ )
+ if is_consistent:
+ print("delta {} is consistent with {}".format(delta, opts.rel_op))
+ else:
+ print("delta {} is inconsistent with {}".format(delta, opts.rel_op))
+ raise SystemExit(0 if is_consistent else 1)
+
+
+if __name__ == "__main__":
+ main()
diff -Nru snapd-2.41+19.10.1/tests/lib/boot.sh snapd-2.42.1+19.10/tests/lib/boot.sh
--- snapd-2.41+19.10.1/tests/lib/boot.sh 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/boot.sh 2019-10-30 12:17:43.000000000 +0000
@@ -1,6 +1,7 @@
#!/bin/bash
GRUB_EDITENV=grub-editenv
+GRUBENV_FILE=/boot/grub/grubenv
case "$SPREAD_SYSTEM" in
fedora-*|opensuse-*|amazon-*|centos-*)
GRUB_EDITENV=grub2-editenv
@@ -11,12 +12,16 @@
if [ $# -eq 0 ]; then
if command -v "$GRUB_EDITENV" >/dev/null; then
"$GRUB_EDITENV" list
+ elif [ -s "$GRUBENV_FILE" ]; then
+ cat "$GRUBENV_FILE"
else
fw_printenv
fi
else
if command -v "$GRUB_EDITENV" >/dev/null; then
"$GRUB_EDITENV" list | grep "^$1"
+ elif [ -s "$GRUBENV_FILE" ]; then
+ grep "^$1" "$GRUBENV_FILE"
else
fw_printenv "$1"
fi | sed "s/^${1}=//"
@@ -29,6 +34,8 @@
if command -v "$GRUB_EDITENV" >/dev/null; then
"$GRUB_EDITENV" /boot/grub/grubenv unset "$var"
+ elif [ -s "$GRUBENV_FILE" ]; then
+ sed -i "/^$var=/d" "$GRUBENV_FILE"
else
fw_setenv "$var"
fi
@@ -45,3 +52,10 @@
exit 1
fi
}
+
+wait_core_post_boot() {
+ # booted
+ while [ "$(bootenv snap_mode)" != "" ]; do
+ sleep 1
+ done
+}
diff -Nru snapd-2.41+19.10.1/tests/lib/desktop-portal.sh snapd-2.42.1+19.10/tests/lib/desktop-portal.sh
--- snapd-2.41+19.10.1/tests/lib/desktop-portal.sh 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/desktop-portal.sh 2019-10-30 12:17:43.000000000 +0000
@@ -51,7 +51,7 @@
distro_purge_package xdg-desktop-portal
distro_auto_remove_packages
- if [ -d "${USER_RUNTIME_DIR}" ]; then
+ if [ -d "${USER_RUNTIME_DIR}" ]; then
umount --lazy "${USER_RUNTIME_DIR}/doc" || :
rm -rf "${USER_RUNTIME_DIR:?}"/* "${USER_RUNTIME_DIR:?}"/.[!.]*
fi
diff -Nru snapd-2.41+19.10.1/tests/lib/pkgdb.sh snapd-2.42.1+19.10/tests/lib/pkgdb.sh
--- snapd-2.41+19.10.1/tests/lib/pkgdb.sh 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/pkgdb.sh 2019-10-30 12:17:43.000000000 +0000
@@ -54,6 +54,9 @@
xdelta3)
echo "xdelta"
;;
+ openvswitch-switch)
+ echo "openvswitch"
+ ;;
*)
echo "$i"
;;
@@ -238,7 +241,7 @@
;;
esac
- # fix dependency issue where libp11-kit0 needs to be downgraded to
+ # fix dependency issue where libp11-kit0 needs to be downgraded to
# install gnome-keyring
case "$SPREAD_SYSTEM" in
debian-9-*)
@@ -626,7 +629,7 @@
packagekit
"
;;
- ubuntu-19.04-64)
+ ubuntu-19.04-64|ubuntu-19.10-64)
echo "
evolution-data-server
packagekit
diff -Nru snapd-2.41+19.10.1/tests/lib/prepare-restore.sh snapd-2.42.1+19.10/tests/lib/prepare-restore.sh
--- snapd-2.41+19.10.1/tests/lib/prepare-restore.sh 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/prepare-restore.sh 2019-10-30 12:17:43.000000000 +0000
@@ -90,19 +90,14 @@
base_version="$(head -1 debian/changelog | awk -F '[()]' '{print $2}')"
version="1337.$base_version"
packaging_path=packaging/$distro-$release
- archive_name=snapd-$version.tar.gz
- archive_compression=z
- extra_tar_args=
rpm_dir=$(rpm --eval "%_topdir")
-
+ pack_args=
case "$SPREAD_SYSTEM" in
fedora-*|amazon-*|centos-*)
- extra_tar_args="$extra_tar_args --exclude=vendor/*"
- archive_name=snapd_$version.no-vendor.tar.xz
;;
opensuse-*)
- archive_name=snapd_$version.vendor.tar.xz
- archive_compression=J
+ # use bundled snapd*.vendor.tar.xz archive
+ pack_args=-s
;;
*)
echo "ERROR: RPM build for system $SPREAD_SYSTEM is not yet supported"
@@ -112,18 +107,10 @@
sed -i -e "s/^Version:.*$/Version: $version/g" "$packaging_path/snapd.spec"
# Create a source tarball for the current snapd sources
- mkdir -p "/tmp/pkg/snapd-$version"
- cp -ra -- * "/tmp/pkg/snapd-$version/"
mkdir -p "$rpm_dir/SOURCES"
- # shellcheck disable=SC2086
- (cd /tmp/pkg && tar "-c${archive_compression}f" "$rpm_dir/SOURCES/$archive_name" $extra_tar_args "snapd-$version")
- case "$SPREAD_SYSTEM" in
- fedora-*|amazon-*|centos-*)
- # need to build the vendor tree
- (cd /tmp/pkg && tar "-cJf" "$rpm_dir/SOURCES/snapd_${version}.only-vendor.tar.xz" "snapd-$version/vendor")
- ;;
- esac
cp "$packaging_path"/* "$rpm_dir/SOURCES/"
+ # shellcheck disable=SC2086
+ ./packaging/pack-source -v "$version" -o "$rpm_dir/SOURCES" $pack_args
# Cleanup all artifacts from previous builds
rm -rf "$rpm_dir"/BUILD/*
@@ -222,6 +209,12 @@
###
prepare_project() {
+ if [[ "$SPREAD_SYSTEM" == ubuntu-* ]] && [[ "$SPREAD_SYSTEM" != ubuntu-core-* ]]; then
+ apt-get remove --purge -y lxd lxcfs || true
+ apt-get autoremove --purge -y
+ lxd-tool undo-lxd-mount-changes
+ fi
+
# Check if running inside a container.
# The testsuite will not work in such an environment
if systemd-detect-virt -c; then
@@ -363,7 +356,7 @@
quiet eatmydata apt-get install -y --force-yes apparmor libapparmor1 seccomp libseccomp2 systemd cgroup-lite util-linux
fi
- # WORKAROUND for older postrm scripts that did not do
+ # WORKAROUND for older postrm scripts that did not do
# "rm -rf /var/cache/snapd"
rm -rf /var/cache/snapd/aux
case "$SPREAD_SYSTEM" in
@@ -465,13 +458,6 @@
RateLimitBurst=0
EOF
systemctl restart systemd-journald.service
-
- # Re-configure cgroups in a way that LXD would so that
- # installation, use and removal of LXD does not leave any changes
- # in the system.
- if [ -f /sys/fs/cgroup/cpuset/cgroup.clone_children ]; then
- echo 1 > /sys/fs/cgroup/cpuset/cgroup.clone_children
- fi
}
prepare_project_each() {
@@ -495,11 +481,7 @@
echo "install snaps profiler"
if [ "$PROFILE_SNAPS" = 1 ]; then
- profiler_snap=test-snapd-profiler
- if is_core18_system; then
- profiler_snap=test-snapd-profiler-core18
- fi
-
+ profiler_snap="$(get_snap_for_system test-snapd-profiler)"
rm -f "/var/snap/${profiler_snap}/common/profiler.log"
snap install "${profiler_snap}"
snap connect "${profiler_snap}":system-observe
@@ -557,10 +539,7 @@
logs_id=$(find "$logs_dir" -maxdepth 1 -name '*.journal.log' | wc -l)
logs_file=$(echo "${logs_id}_${SPREAD_JOB}" | tr '/' '_' | tr ':' '__')
- profiler_snap=test-snapd-profiler
- if is_core18_system; then
- profiler_snap=test-snapd-profiler-core18
- fi
+ profiler_snap="$(get_snap_for_system test-snapd-profiler)"
mkdir -p "$logs_dir"
if [ -e "/var/snap/${profiler_snap}/common/profiler.log" ]; then
@@ -568,6 +547,18 @@
fi
get_journalctl_log > "${logs_dir}/${logs_file}.journal.log"
fi
+
+ # On Arch it seems that using sudo / su for working with the test user
+ # spawns the /run/user/12345 tmpfs for XDG_RUNTIME_DIR which asynchronously
+ # cleans up itself sometime after the test but not instantly, leading to
+ # random failures in the mount leak detector. Give it a moment but don't
+ # clean it up ourselves, this should report actual test errors, if any.
+ for i in $(seq 10); do
+ if not mountinfo-tool /run/user/12345 .fs_type=tmpfs; then
+ break
+ fi
+ sleep 1
+ done
}
restore_suite() {
@@ -625,12 +616,38 @@
exit 1
fi
+ if getent passwd snap_daemon; then
+ echo "Test left the snap_daemon user behind, this should not happen"
+ exit 1
+ fi
+ if getent group snap_daemon; then
+ echo "Test left the snap_daemon group behind, this should not happen"
+ exit 1
+ fi
+
# Something is hosing the filesystem so look for signs of that
not grep -F "//deleted /etc" /proc/self/mountinfo
+
+ if journalctl -u snapd.service | grep -F "signal: terminated"; then
+ exit 1;
+ fi
+
+ case "$SPREAD_SYSTEM" in
+ fedora-*|centos-*)
+ # Make sure that we are not leaving behind incorrectly labeled snap
+ # files on systems supporting SELinux
+ (
+ find /root/snap -printf '%Z\t%H/%P\n' || true
+ find /home -regex '/home/[^/]*/snap\(/.*\)?' -printf '%Z\t%H/%P\n' || true
+ ) | grep -c -v snappy_home_t | MATCH "0"
+
+ find /var/snap -printf '%Z\t%H/%P\n' | grep -c -v snappy_var_t | MATCH "0"
+ ;;
+ esac
}
restore_project() {
- # Delete the snapd state used to accelerate prepare/restore code in certain suites.
+ # Delete the snapd state used to accelerate prepare/restore code in certain suites.
delete_snapd_state
# Remove all of the code we pushed and any build results. This removes
diff -Nru snapd-2.41+19.10.1/tests/lib/prepare.sh snapd-2.42.1+19.10/tests/lib/prepare.sh
--- snapd-2.41+19.10.1/tests/lib/prepare.sh 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/prepare.sh 2019-10-30 12:17:43.000000000 +0000
@@ -410,7 +410,7 @@
# ubuntu-image channel to that of the gadget, so that we don't
# need to download it
snap download --channel="$KERNEL_CHANNEL" pc-kernel
-
+
EXTRA_FUNDAMENTAL="--extra-snaps $PWD/pc-kernel_*.snap"
IMAGE_CHANNEL="$GADGET_CHANNEL"
fi
@@ -438,7 +438,7 @@
"$EXTRA_FUNDAMENTAL" \
--extra-snaps "${extra_snap[0]}" \
--output "$IMAGE_HOME/$IMAGE"
- rm -f ./pc-kernel_*.{snap,assert} ./pc_*.{snap,assert}
+ rm -f ./pc-kernel_*.{snap,assert} ./pc_*.{snap,assert} ./snapd_*.{snap,assert}
# mount fresh image and add all our SPREAD_PROJECT data
kpartx -avs "$IMAGE_HOME/$IMAGE"
@@ -461,7 +461,7 @@
--exclude /gopath/bin/govendor \
--exclude /gopath/pkg/ \
/home/gopath /mnt/user-data/
-
+
# now modify the image
if is_core18_system; then
UNPACK_DIR="/tmp/core18-snap"
@@ -509,12 +509,12 @@
grep -v "^root:" "$UNPACK_DIR/etc/$f" > /mnt/system-data/root/test-etc/"$f"
# append this systems root user so that linode can connect
grep "^root:" /etc/"$f" >> /mnt/system-data/root/test-etc/"$f"
-
+
# make sure the group is as expected
chgrp --reference "$UNPACK_DIR/etc/$f" /mnt/system-data/root/test-etc/"$f"
# now bind mount read-only those passwd files on boot
cat >/mnt/system-data/etc/systemd/system/etc-"$f".mount <> /mnt/system-data/var/lib/extrausers/"$f"
@@ -547,7 +547,7 @@
# inside and outside which is a pain (see 12345 above), but
# using the ids directly is the wrong kind of fragile
chown --verbose test:test /mnt/user-data/test
-
+
# we do what sync-dirs is normally doing on boot, but because
# we have subdirs/files in /etc/systemd/system (created below)
# the writeable-path sync-boot won't work
diff -Nru snapd-2.41+19.10.1/tests/lib/reset.sh snapd-2.42.1+19.10/tests/lib/reset.sh
--- snapd-2.41+19.10.1/tests/lib/reset.sh 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/reset.sh 2019-10-30 12:17:43.000000000 +0000
@@ -17,19 +17,6 @@
# Reload all service units as in some situations the unit might
# have changed on the disk.
systemctl daemon-reload
-
- echo "Ensure the service is active before stopping it"
- retries=20
- while systemctl status snapd.service snapd.socket | grep -q "Active: activating"; do
- if [ $retries -eq 0 ]; then
- echo "snapd service or socket not active"
- systemctl status snapd.service snapd.socket || true
- exit 1
- fi
- retries=$(( retries - 1 ))
- sleep 1
- done
-
systemd_stop_units snapd.service snapd.socket
case "$SPREAD_SYSTEM" in
@@ -122,7 +109,15 @@
if ! systemctl status snapd.service snapd.socket >/dev/null; then
systemctl start snapd.service snapd.socket
fi
- if ! echo "$SKIP_REMOVE_SNAPS" | grep -w "$snap"; then
+ # Check if a snap should be kept, there's a list of those in spread.yaml.
+ keep=0
+ for precious_snap in $SKIP_REMOVE_SNAPS; do
+ if [ "$snap" = "$precious_snap" ]; then
+ keep=1
+ break
+ fi
+ done
+ if [ "$keep" -eq 0 ]; then
if snap info --verbose "$snap" | grep -E '^type: +(base|core)'; then
if [ -z "$remove_bases" ]; then
remove_bases="$snap"
diff -Nru snapd-2.41+19.10.1/tests/lib/snaps/classic-gadget/meta/gadget.yaml snapd-2.42.1+19.10/tests/lib/snaps/classic-gadget/meta/gadget.yaml
--- snapd-2.41+19.10.1/tests/lib/snaps/classic-gadget/meta/gadget.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/snaps/classic-gadget/meta/gadget.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -1 +1 @@
-# on classic this can be empty (or absent)
\ No newline at end of file
+# on classic this can be empty (or absent)
diff -Nru snapd-2.41+19.10.1/tests/lib/snaps/classic-gadget-18/meta/gadget.yaml snapd-2.42.1+19.10/tests/lib/snaps/classic-gadget-18/meta/gadget.yaml
--- snapd-2.41+19.10.1/tests/lib/snaps/classic-gadget-18/meta/gadget.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/snaps/classic-gadget-18/meta/gadget.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1 @@
+# on classic this can be empty (or absent)
diff -Nru snapd-2.41+19.10.1/tests/lib/snaps/classic-gadget-18/meta/hooks/prepare-device snapd-2.42.1+19.10/tests/lib/snaps/classic-gadget-18/meta/hooks/prepare-device
--- snapd-2.41+19.10.1/tests/lib/snaps/classic-gadget-18/meta/hooks/prepare-device 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/snaps/classic-gadget-18/meta/hooks/prepare-device 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,2 @@
+#!/bin/sh
+snapctl set device-service.url=http://localhost:11029
Binary files /tmp/tmprKDYI4/0rE34eZg0R/snapd-2.41+19.10.1/tests/lib/snaps/classic-gadget-18/meta/icon.png and /tmp/tmprKDYI4/7C8DT3gMvq/snapd-2.42.1+19.10/tests/lib/snaps/classic-gadget-18/meta/icon.png differ
diff -Nru snapd-2.41+19.10.1/tests/lib/snaps/classic-gadget-18/meta/snap.yaml snapd-2.42.1+19.10/tests/lib/snaps/classic-gadget-18/meta/snap.yaml
--- snapd-2.41+19.10.1/tests/lib/snaps/classic-gadget-18/meta/snap.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/snaps/classic-gadget-18/meta/snap.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,5 @@
+name: classic-gadget-18
+type: gadget
+version: 1.0
+summary: Classic gadget using core18
+base: core18
diff -Nru snapd-2.41+19.10.1/tests/lib/snaps/config-versions/meta/hooks/configure snapd-2.42.1+19.10/tests/lib/snaps/config-versions/meta/hooks/configure
--- snapd-2.41+19.10.1/tests/lib/snaps/config-versions/meta/hooks/configure 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/snaps/config-versions/meta/hooks/configure 2019-10-30 12:17:43.000000000 +0000
@@ -1,2 +1,3 @@
#!/bin/sh
+snapctl set configure-marker="executed-for-v1"
diff -Nru snapd-2.41+19.10.1/tests/lib/snaps/config-versions/meta/hooks/post-refresh snapd-2.42.1+19.10/tests/lib/snaps/config-versions/meta/hooks/post-refresh
--- snapd-2.41+19.10.1/tests/lib/snaps/config-versions/meta/hooks/post-refresh 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/snaps/config-versions/meta/hooks/post-refresh 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+snapctl set post-refresh-hook-marker="executed-for-v1"
diff -Nru snapd-2.41+19.10.1/tests/lib/snaps/config-versions-v2/meta/hooks/configure snapd-2.42.1+19.10/tests/lib/snaps/config-versions-v2/meta/hooks/configure
--- snapd-2.41+19.10.1/tests/lib/snaps/config-versions-v2/meta/hooks/configure 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/snaps/config-versions-v2/meta/hooks/configure 2019-10-30 12:17:43.000000000 +0000
@@ -1,2 +1,9 @@
#!/bin/sh
+snapctl set configure-marker="executed-for-v2"
+
+command=$(snapctl get fail-configure)
+if [ "x$command" = "xyes" ]; then
+ echo "failing configure hook as requested"
+ exit 1
+fi
diff -Nru snapd-2.41+19.10.1/tests/lib/snaps/config-versions-v2/meta/hooks/post-refresh snapd-2.42.1+19.10/tests/lib/snaps/config-versions-v2/meta/hooks/post-refresh
--- snapd-2.41+19.10.1/tests/lib/snaps/config-versions-v2/meta/hooks/post-refresh 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/snaps/config-versions-v2/meta/hooks/post-refresh 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+snapctl set post-refresh-hook-marker="executed-for-v2"
diff -Nru snapd-2.41+19.10.1/tests/lib/snaps/snapctl-hooks/meta/hooks/configure snapd-2.42.1+19.10/tests/lib/snaps/snapctl-hooks/meta/hooks/configure
--- snapd-2.41+19.10.1/tests/lib/snaps/snapctl-hooks/meta/hooks/configure 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/snaps/snapctl-hooks/meta/hooks/configure 2019-10-30 12:17:43.000000000 +0000
@@ -98,6 +98,14 @@
fi
}
+test_snapctl_unset_with_unset() {
+ echo "Unsetting an option"
+ if ! snapctl unset root.key2 ; then
+ echo "snapctl set unexpectedly failed when un-setting root.key2"
+ exit 1
+ fi
+}
+
test_exit_one() {
echo "Failing as requested."
exit 1
@@ -131,6 +139,11 @@
"test-get-nested")
test_get_nested
;;
+ "test-unset-with-unset")
+ test_snapctl_unset_with_unset
+ ;;
+ "noop")
+ ;;
"test-unset")
test_snapctl_unset
;;
diff -Nru snapd-2.41+19.10.1/tests/lib/snaps/test-snapd-icon-theme/bin/echo snapd-2.42.1+19.10/tests/lib/snaps/test-snapd-icon-theme/bin/echo
--- snapd-2.41+19.10.1/tests/lib/snaps/test-snapd-icon-theme/bin/echo 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/snaps/test-snapd-icon-theme/bin/echo 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo "From test-snapd-icon-theme snap"
diff -Nru snapd-2.41+19.10.1/tests/lib/snaps/test-snapd-icon-theme/meta/gui/echo.desktop snapd-2.42.1+19.10/tests/lib/snaps/test-snapd-icon-theme/meta/gui/echo.desktop
--- snapd-2.41+19.10.1/tests/lib/snaps/test-snapd-icon-theme/meta/gui/echo.desktop 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/snaps/test-snapd-icon-theme/meta/gui/echo.desktop 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,5 @@
+[Desktop Entry]
+Type=Application
+Name=Echo
+Exec=test-snapd-icon-theme.echo
+Icon=snap.test-snapd-icon-theme.foo
diff -Nru snapd-2.41+19.10.1/tests/lib/snaps/test-snapd-icon-theme/meta/gui/icons/hicolor/scalable/apps/snap.test-snapd-icon-theme.foo.svg snapd-2.42.1+19.10/tests/lib/snaps/test-snapd-icon-theme/meta/gui/icons/hicolor/scalable/apps/snap.test-snapd-icon-theme.foo.svg
--- snapd-2.41+19.10.1/tests/lib/snaps/test-snapd-icon-theme/meta/gui/icons/hicolor/scalable/apps/snap.test-snapd-icon-theme.foo.svg 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/snaps/test-snapd-icon-theme/meta/gui/icons/hicolor/scalable/apps/snap.test-snapd-icon-theme.foo.svg 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,5 @@
+
+
diff -Nru snapd-2.41+19.10.1/tests/lib/snaps/test-snapd-icon-theme/meta/snap.yaml snapd-2.42.1+19.10/tests/lib/snaps/test-snapd-icon-theme/meta/snap.yaml
--- snapd-2.41+19.10.1/tests/lib/snaps/test-snapd-icon-theme/meta/snap.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/snaps/test-snapd-icon-theme/meta/snap.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,6 @@
+name: test-snapd-icon-theme
+version: 1.0
+
+apps:
+ echo:
+ command: bin/echo
diff -Nru snapd-2.41+19.10.1/tests/lib/snaps/test-snapd-with-configure-core18/snapcraft.yaml snapd-2.42.1+19.10/tests/lib/snaps/test-snapd-with-configure-core18/snapcraft.yaml
--- snapd-2.41+19.10.1/tests/lib/snaps/test-snapd-with-configure-core18/snapcraft.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/snaps/test-snapd-with-configure-core18/snapcraft.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,10 @@
+name: test-snapd-with-configure-core18
+version: '1.0'
+summary: Basic snap with a configure hook for core18
+description: A basic snap with a passthrough configure hook for core18
+base: core18
+
+parts:
+ copy:
+ plugin: dump
+ source: .
diff -Nru snapd-2.41+19.10.1/tests/lib/snaps.sh snapd-2.42.1+19.10/tests/lib/snaps.sh
--- snapd-2.41+19.10.1/tests/lib/snaps.sh 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/snaps.sh 2019-10-30 12:17:43.000000000 +0000
@@ -72,21 +72,8 @@
}
is_classic_confinement_supported() {
- case "$SPREAD_SYSTEM" in
- ubuntu-core-*)
- return 1
- ;;
- ubuntu-*|debian-*)
- return 0
- ;;
- fedora-*|centos-*)
- return 1
- ;;
- opensuse-*)
- return 0
- ;;
- *)
- return 0
- ;;
- esac
+ if snap debug sandbox-features --required=confinement-options:classic; then
+ return 0
+ fi
+ return 1
}
diff -Nru snapd-2.41+19.10.1/tests/lib/state.sh snapd-2.42.1+19.10/tests/lib/state.sh
--- snapd-2.41+19.10.1/tests/lib/state.sh 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/state.sh 2019-10-30 12:17:43.000000000 +0000
@@ -111,14 +111,14 @@
}
restore_snapd_lib() {
- # Clean all the state but the snaps and seed dirs. Then make a selective clean for
+ # Clean all the state but the snaps and seed dirs. Then make a selective clean for
# snaps and seed dirs leaving the .snap files which then are going to be synchronized.
find /var/lib/snapd/* -maxdepth 0 ! \( -name 'snaps' -o -name 'seed' -o -name 'cache' \) -exec rm -rf {} \;
# Copy the whole state but the snaps, seed and cache dirs
find "$SNAPD_STATE_PATH"/snapd-lib/* -maxdepth 0 ! \( -name 'snaps' -o -name 'seed' -o -name 'cache' \) -exec cp -rf {} /var/lib/snapd \;
- # Synchronize snaps, seed and cache directories. The this is done separately in order to avoid copying
+ # Synchronize snaps, seed and cache directories. The this is done separately in order to avoid copying
# the snap files due to it is a heavy task and take most of the time of the restore phase.
rsync -av --delete "$SNAPD_STATE_PATH"/snapd-lib/snaps /var/lib/snapd
rsync -av --delete "$SNAPD_STATE_PATH"/snapd-lib/seed /var/lib/snapd
diff -Nru snapd-2.41+19.10.1/tests/lib/store.sh snapd-2.42.1+19.10/tests/lib/store.sh
--- snapd-2.41+19.10.1/tests/lib/store.sh 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/store.sh 2019-10-30 12:17:43.000000000 +0000
@@ -36,7 +36,7 @@
local dir="$1"
shift
- fakestore make-refreshable --dir "$dir" "$@"
+ fakestore make-refreshable --dir "$dir" "$@"
}
make_snap_installable(){
diff -Nru snapd-2.41+19.10.1/tests/lib/systemd.sh snapd-2.42.1+19.10/tests/lib/systemd.sh
--- snapd-2.41+19.10.1/tests/lib/systemd.sh 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/systemd.sh 2019-10-30 12:17:43.000000000 +0000
@@ -52,7 +52,7 @@
return
fi
# show debug output every 1min
- if [ $(( i % 60 )) = 0 ]; then
+ if [ "$i" -gt 0 ] && [ $(( i % 60 )) = 0 ]; then
systemctl status "$service_name" || true;
fi
sleep 1;
@@ -67,10 +67,10 @@
if systemctl is-active "$unit"; then
echo "Ensure the service is active before stopping it"
retries=20
- systemctl status "$unit" || true
- while systemctl status "$unit" | grep "Active: activating"; do
+ while systemctl status "$unit" | grep -q "Active: activating"; do
if [ $retries -eq 0 ]; then
echo "$unit unit not active"
+ systemctl status "$unit" || true
exit 1
fi
retries=$(( retries - 1 ))
diff -Nru snapd-2.41+19.10.1/tests/lib/systems.sh snapd-2.42.1+19.10/tests/lib/systems.sh
--- snapd-2.41+19.10.1/tests/lib/systems.sh 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/lib/systems.sh 2019-10-30 12:17:43.000000000 +0000
@@ -14,6 +14,13 @@
return 1
}
+is_core20_system(){
+ if [[ "$SPREAD_SYSTEM" == ubuntu-core-20-* ]]; then
+ return 0
+ fi
+ return 1
+}
+
is_classic_system(){
if [[ "$SPREAD_SYSTEM" != ubuntu-core-* ]]; then
return 0
@@ -21,9 +28,40 @@
return 1
}
+
is_ubuntu_14_system(){
if [[ "$SPREAD_SYSTEM" == ubuntu-14.04-* ]]; then
return 0
fi
return 1
}
+
+get_snap_for_system(){
+ local snap=$1
+
+ case "$SPREAD_SYSTEM" in
+ ubuntu-core-18-*)
+ echo "${snap}-core18"
+ ;;
+ ubuntu-core-20-*)
+ echo "${snap}-core20"
+ ;;
+ *)
+ echo "$snap"
+ ;;
+ esac
+}
+
+get_core_for_system(){
+ case "$SPREAD_SYSTEM" in
+ ubuntu-core-18-*)
+ echo "core18"
+ ;;
+ ubuntu-core-20-*)
+ echo "core20"
+ ;;
+ *)
+ echo "core"
+ ;;
+ esac
+}
diff -Nru snapd-2.41+19.10.1/tests/main/apt-hooks/task.yaml snapd-2.42.1+19.10/tests/main/apt-hooks/task.yaml
--- snapd-2.41+19.10.1/tests/main/apt-hooks/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/apt-hooks/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -1,7 +1,7 @@
summary: Ensure apt hooks work
# apt hook only available on 18.04+ and aws-cli only for amd64
-systems: [ubuntu-18.04-64, ubuntu-19.04-64]
+systems: [ubuntu-18.04-64, ubuntu-19.04-64, ubuntu-19.10-64]
debug: |
ls -lh /var/cache/snapd
diff -Nru snapd-2.41+19.10.1/tests/main/base-migration/task.yaml snapd-2.42.1+19.10/tests/main/base-migration/task.yaml
--- snapd-2.41+19.10.1/tests/main/base-migration/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/base-migration/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -10,8 +10,13 @@
# snap runs on top of the core16 runtime environment. This can be seen by
# looking at the os-release file which will match that of ubuntu core 16.
snap install --dangerous test-snapd-core-migration_1_all.snap
- test-snapd-core-migration.sh -c "cat /usr/lib/os-release" | MATCH 'VERSION_ID="16"'
+ su test -c 'snap run test-snapd-core-migration.sh -c "cat /usr/lib/os-release"' | MATCH 'VERSION_ID="16"'
+
+ # The mount namespace information file is owned by root.root and has mode 0644
+ # even though the user invoking the snap command was non-root.
MATCH 'base-snap-name=core' < /run/snapd/ns/snap.test-snapd-core-migration.info
+ test "$(stat -c '%u.%g' /run/snapd/ns/snap.test-snapd-core-migration.info)" = 0.0
+ test "$(stat -c '%a' /run/snapd/ns/snap.test-snapd-core-migration.info)" = 644
# When said snap is refreshed to use "base: core18" then, because there are
# no active processes in that snap, the base will change correctly to core18.
diff -Nru snapd-2.41+19.10.1/tests/main/cgroup-devices/task.sh snapd-2.42.1+19.10/tests/main/cgroup-devices/task.sh
--- snapd-2.41+19.10.1/tests/main/cgroup-devices/task.sh 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/cgroup-devices/task.sh 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,93 @@
+#!/bin/sh -ex
+echo "Install a test service"
+snap pack test-snapd-service
+
+# Because this service is of type "simple" it is considered "ready" instantly.
+# In reality the process needs to go through snap "run" chain to be really
+# ready. As a workaround, touch a "remove-me" file that is removed by the
+# service on startup, restart the service and the wait for the file to
+# disappear.
+mkdir -p /var/snap/test-snapd-service/common
+touch /var/snap/test-snapd-service/common/remove-me
+snap install --dangerous ./test-snapd-service_1.0_all.snap
+# Wait for the service to really be alive and running. Otherwise the "main pid"
+# will be still tracking snap-run-confine-exec chain and be unreliable.
+for _ in $(seq 5); do
+ if [ ! -e /var/snap/test-snapd-service/common/remove-me ]; then
+ break
+ fi
+ sleep 1
+done
+
+echo "Extract the PID of the main process tracked by systemd"
+# It would be nicer to use "systemctl show --property=... --value" but it doesn't work on older systemd.
+pid=$(systemctl show snap.test-snapd-service.test-snapd-service.service --property=ExecMainPID | cut -d = -f 2)
+
+echo "Extract the device cgroup of the main process"
+initial_device_cgroup=$(grep devices < "/proc/$pid/cgroup" | cut -d : -f 3)
+
+# Initially, because there are no udev tags corresponding to this application,
+# snap-confine is not moving the process to a new cgroup. As such the service
+# runs in the cgroup created by systemd, which varies across version of systemd.
+case "$initial_device_cgroup" in
+ /)
+ ;;
+ /system.slice)
+ ;;
+ /system.slice/snap.test-snapd-service.test-snapd-service.service)
+ ;;
+ *)
+ echo "Unexpected initial device cgroup: $initial_device_cgroup"
+ exit 1
+esac
+
+echo "Ensure that the claim of the process is consistent with the claim of the cgroup"
+# This is just a sanity check.
+MATCH "$pid" < "/sys/fs/cgroup/devices/$initial_device_cgroup/cgroup.procs"
+
+echo "Verify the constraints imposed by the device cgroup made by systemd"
+# This may change over time as it is governed by systemd.
+test 'a *:* rwm' = "$(cat "/sys/fs/cgroup/devices/$initial_device_cgroup/devices.list")"
+
+echo "Connect the joystick interface"
+snap connect test-snapd-service:joystick
+
+echo "Refresh the value of the main pid and the effective device cgroup after snap connect"
+# NOTE: As of snapd 2.40 the PID and cgroup are expected to be the same as before.
+pid_check=$(systemctl show snap.test-snapd-service.test-snapd-service.service --property=ExecMainPID | cut -d = -f 2)
+test "$pid" -eq "$pid_check"
+updated_device_cgroup=$(grep devices < "/proc/$pid/cgroup" | cut -d : -f 3)
+
+echo "Verify that the main process is still in the systemd-made cgroup"
+test "$updated_device_cgroup" = "$initial_device_cgroup"
+
+echo "Verify the constraints imposed by the device cgroup made by systemd"
+test 'a *:* rwm' = "$(cat "/sys/fs/cgroup/devices/$updated_device_cgroup/devices.list")"
+
+echo "Run /bin/true via snap-confine, so that we create the device cgroup made by snapd"
+snap run --shell test-snapd-service -c /bin/true
+
+echo "Verify the constraints imposed by the device cgroup made by snapd"
+# NOTE: the actual permissions may drift over time. We just care about the fact
+# that there *are* some constraints here now and there were none before.
+test 'c 1:3 rwm' = "$(head -n 1 "/sys/fs/cgroup/devices/snap.test-snapd-service.test-snapd-service/devices.list")"
+# The device cgroup made by snapd is, currently, still empty.
+test -z "$(cat "/sys/fs/cgroup/devices/snap.test-snapd-service.test-snapd-service/cgroup.procs")"
+
+echo "Restart the test service"
+# See the comment for the similar code above.
+touch /var/snap/test-snapd-service/common/remove-me
+systemctl restart snap.test-snapd-service.test-snapd-service.service
+for _ in $(seq 5); do
+ if [ ! -e /var/snap/test-snapd-service/common/remove-me ]; then
+ break
+ fi
+ sleep 1
+done
+
+echo "Refresh the value of the main pid and the effective device cgroup after service restart"
+pid=$(systemctl show snap.test-snapd-service.test-snapd-service.service --property=ExecMainPID | cut -d = -f 2)
+final_device_cgroup=$(grep devices < "/proc/$pid/cgroup" | cut -d : -f 3)
+
+echo "Verify that the main process is now in the snapd-made cgroup"
+test "$final_device_cgroup" = /snap.test-snapd-service.test-snapd-service
diff -Nru snapd-2.41+19.10.1/tests/main/cgroup-devices/task.yaml snapd-2.42.1+19.10/tests/main/cgroup-devices/task.yaml
--- snapd-2.41+19.10.1/tests/main/cgroup-devices/task.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/cgroup-devices/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,5 @@
+summary: measuring basic properties of device cgroup
+execute: ./task.sh
+restore: |
+ rm -f test-snapd-service_1.0_all.snap
+ snap remove test-snapd-service
diff -Nru snapd-2.41+19.10.1/tests/main/cgroup-devices/test-snapd-service/bin/service snapd-2.42.1+19.10/tests/main/cgroup-devices/test-snapd-service/bin/service
--- snapd-2.41+19.10.1/tests/main/cgroup-devices/test-snapd-service/bin/service 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/cgroup-devices/test-snapd-service/bin/service 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,6 @@
+#!/bin/sh
+rm -f "$SNAP_COMMON/remove-me"
+while true; do
+ echo "running"
+ sleep 10
+done
diff -Nru snapd-2.41+19.10.1/tests/main/cgroup-devices/test-snapd-service/meta/snap.yaml snapd-2.42.1+19.10/tests/main/cgroup-devices/test-snapd-service/meta/snap.yaml
--- snapd-2.41+19.10.1/tests/main/cgroup-devices/test-snapd-service/meta/snap.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/cgroup-devices/test-snapd-service/meta/snap.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,7 @@
+name: test-snapd-service
+version: 1.0
+apps:
+ test-snapd-service:
+ command: bin/service
+ daemon: simple
+ plugs: [joystick]
diff -Nru snapd-2.41+19.10.1/tests/main/classic-custom-device-reg/task.yaml snapd-2.42.1+19.10/tests/main/classic-custom-device-reg/task.yaml
--- snapd-2.41+19.10.1/tests/main/classic-custom-device-reg/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/classic-custom-device-reg/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -1,12 +1,12 @@
summary: |
- Test gadget customized device initialisation and registration also on classic
+ Test gadget customized device initialisation and registration also on classic
systems: [-ubuntu-core-*]
environment:
SEED_DIR: /var/lib/snapd/seed
-kill-timeout: 3m
+kill-timeout: 5m
prepare: |
if [ "$TRUST_TEST_KEYS" = "false" ]; then
diff -Nru snapd-2.41+19.10.1/tests/main/classic-prepare-image/task.yaml snapd-2.42.1+19.10/tests/main/classic-prepare-image/task.yaml
--- snapd-2.41+19.10.1/tests/main/classic-prepare-image/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/classic-prepare-image/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -10,7 +10,7 @@
STORE_ADDR: localhost:11028
SEED_DIR: /var/lib/snapd/seed
-kill-timeout: 3m
+kill-timeout: 5m
prepare: |
if [ "$TRUST_TEST_KEYS" = "false" ]; then
diff -Nru snapd-2.41+19.10.1/tests/main/classic-prepare-image-no-core/task.yaml snapd-2.42.1+19.10/tests/main/classic-prepare-image-no-core/task.yaml
--- snapd-2.41+19.10.1/tests/main/classic-prepare-image-no-core/task.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/classic-prepare-image-no-core/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,98 @@
+summary: Check that prepare-image --classic works.
+
+systems: [-ubuntu-core-*, -fedora-*, -opensuse-*, -arch-*, -amazon-*, -centos-*]
+
+backends: [-autopkgtest]
+
+environment:
+ ROOT: /tmp/root
+ STORE_DIR: $(pwd)/fake-store-blobdir
+ STORE_ADDR: localhost:11028
+ SEED_DIR: /var/lib/snapd/seed
+
+kill-timeout: 5m
+
+prepare: |
+ if [ "$TRUST_TEST_KEYS" = "false" ]; then
+ echo "This test needs test keys to be trusted"
+ exit
+ fi
+
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
+ setup_fake_store "$STORE_DIR"
+
+ snap pack "$TESTSLIB/snaps/basic18"
+ snap pack "$TESTSLIB/snaps/classic-gadget-18"
+
+ echo Expose the needed assertions through the fakestore
+ cp "$TESTSLIB"/assertions/developer1.account "$STORE_DIR/asserts"
+ cp "$TESTSLIB"/assertions/developer1.account-key "$STORE_DIR/asserts"
+ # have snap use the fakestore for assertions (but nothing else)
+ export SNAPPY_FORCE_SAS_URL=http://$STORE_ADDR
+
+ echo Running prepare-image
+ #shellcheck disable=SC2086
+ ARCH="$(dpkg-architecture -qDEB_HOST_ARCH)"
+ su -c "SNAPPY_USE_STAGING_STORE=$SNAPPY_USE_STAGING_STORE snap prepare-image --classic --arch $ARCH --channel $CORE_CHANNEL --snap basic18_*.snap --snap classic-gadget-18_*.snap $TESTSLIB/assertions/developer1-my-classic-w-gadget-18.model $ROOT"
+
+ "$TESTSLIB/reset.sh" --keep-stopped
+ cp -ar "$ROOT/$SEED_DIR" "$SEED_DIR"
+
+ # start fake device svc
+ systemd_create_and_start_unit fakedevicesvc "$(command -v fakedevicesvc) localhost:11029"
+
+restore: |
+ if [ "$TRUST_TEST_KEYS" = "false" ]; then
+ echo "This test needs test keys to be trusted"
+ exit
+ fi
+
+ #shellcheck source=tests/lib/systemd.sh
+ . "$TESTSLIB/systemd.sh"
+ systemctl stop snapd.service snapd.socket
+ systemd_stop_and_destroy_unit fakedevicesvc
+
+ rm -rf "$SEED_DIR"
+ systemctl start snapd.socket snapd.service
+
+ #shellcheck source=tests/lib/store.sh
+ . "$TESTSLIB"/store.sh
+ teardown_fake_store "$STORE_DIR"
+ rm -f -- *.snap
+ rm -rf "$ROOT"
+
+execute: |
+ if [ "$TRUST_TEST_KEYS" = "false" ]; then
+ echo "This test needs test keys to be trusted"
+ exit
+ fi
+
+ # kick seeding
+ systemctl start snapd.service snapd.socket
+
+ echo "Wait for seeding to be done"
+ snap wait system seed.loaded
+
+ echo "We have a model assertion"
+ snap known model|MATCH "model: my-classic-w-gadget-18"
+
+ echo "Wait for device initialisation to be done"
+ while ! snap changes | grep -q "Done.*Initialize device"; do sleep 1; done
+
+ echo "Check we have a serial"
+ snap known serial|MATCH "authority-id: developer1"
+ snap known serial|MATCH "brand-id: developer1"
+ snap known serial|MATCH "model: my-classic-w-gadget-18"
+ snap known serial|MATCH "serial: 7777"
+
+ snap list | MATCH "^basic18"
+ test -f "$SEED_DIR/snaps/basic18_"*.snap
+ snap list | MATCH "^classic-gadget-18"
+ test -f "$SEED_DIR/snaps/classic-gadget-18_"*.snap
+ snap list | MATCH "^core18"
+ test -f "$SEED_DIR/snaps/core18_"*.snap
+ if snap list |MATCH "^core " ; then
+ echo "Should not have needed or installed core"
+ exit 1
+ fi
diff -Nru snapd-2.41+19.10.1/tests/main/classic-ubuntu-core-transition/task.yaml snapd-2.42.1+19.10/tests/main/classic-ubuntu-core-transition/task.yaml
--- snapd-2.41+19.10.1/tests/main/classic-ubuntu-core-transition/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/classic-ubuntu-core-transition/task.yaml 1970-01-01 00:00:00.000000000 +0000
@@ -1,128 +0,0 @@
-summary: Ensure that the ubuntu-core -> core transition works
-
-# we never test on core because the transition can only happen on "classic" we
-# disable on ppc64el because the downloads are very slow there Fedora, openSUSE,
-# Arch, CentOS are disabled at the moment as there is something fishy going on
-# and the snapd service gets terminated during the process.
-systems: [-ubuntu-core-*, -ubuntu-*-ppc64el, -fedora-*, -opensuse-*, -ubuntu-*-i386, -arch-*, -amazon-*, -centos-*, -debian-sid-*]
-
-# autopkgtest run only a subset of tests that deals with the integration
-# with the distro
-backends: [-autopkgtest]
-
-warn-timeout: 1m
-kill-timeout: 5m
-
-debug: |
- snap list || true
- snap info core || true
- snap info ubuntu-core || true
- snap changes
- #shellcheck source=tests/lib/changes.sh
- . "$TESTSLIB/changes.sh"
- snap change "$(change_id 'Transition ubuntu-core to core')" || true
-
-execute: |
- #shellcheck source=tests/lib/pkgdb.sh
- . "$TESTSLIB/pkgdb.sh"
- #shellcheck source=tests/lib/systemd.sh
- . "$TESTSLIB"/systemd.sh
- curl() {
- local url="$1"
- # sadly systemd active means not that its really ready so we wait
- # here for the socket to be available
- while ! ss -t -l -n|grep :80; do
- ss -l -l -n
- sleep 1
- done
- python3 -c "import urllib.request; print(urllib.request.urlopen(\"$url\").read().decode(\"utf-8\"))"
- }
-
- #shellcheck source=tests/lib/pkgdb.sh
- . "$TESTSLIB/pkgdb.sh"
- echo "Ensure core is gone and we have ubuntu-core instead"
- distro_purge_package snapd
- distro_install_build_snapd
-
- # need to be seeded to allow snap install
- snap wait system seed.loaded
-
- # modify daemon state to set ubuntu-core-transition-last-retry-time to the
- # current time to prevent the ubuntu-core transition before the test snap is
- # installed
- systemctl stop snapd.{service,socket}
- now="$(date --utc -Ins)"
- jq -c '. + {data: (.data + {"ubuntu-core-transition-last-retry-time": "'"$now"'"})}' < /var/lib/snapd/state.json > state.json.new
- mv state.json.new /var/lib/snapd/state.json
- systemctl start snapd.{service,socket}
-
- snap download "--${CORE_CHANNEL}" ubuntu-core
- snap ack ./ubuntu-core_*.assert
- snap install ./ubuntu-core_*.snap
-
- snap install test-snapd-python-webserver
- snap interfaces -i network | MATCH ":network +test-snapd-python-webserver"
- snap interfaces -i network-bind | MATCH ":network-bind +.*test-snapd-python-webserver"
-
- echo "Ensure the webserver is working"
- wait_for_service snap.test-snapd-python-webserver.test-snapd-python-webserver
- curl http://localhost | MATCH "XKCD rocks"
-
- # restore ubuntu-core-transition-last-retry-time to its previous value and restart the daemon
- systemctl stop snapd.{service,socket}
- jq -c 'del(.["data"]["ubuntu-core-transition-last-retry-time"])' < /var/lib/snapd/state.json > state.json.new
- mv state.json.new /var/lib/snapd/state.json
- systemctl start snapd.{service,socket}
-
- echo "Ensure transition is triggered"
- # wait for steady state or ensure-state-soon will be pointless
- ok=0
- for i in $(seq 40); do
- if ! snap changes|grep -q ".*.Doing.*" ; then
- ok=1
- break
- fi
- sleep .5
- done
- if [ $ok -ne 1 ] ; then
- echo "Did not reach steady state"
- exit 1
- fi
- snap debug ensure-state-soon
-
- # wait for transition
- ok=0
- for i in $(seq 240); do
- if snap changes|grep -q ".*Done.*Transition ubuntu-core to core" ; then
- ok=1
- break
- fi
- sleep 1
- done
- if [ $ok -ne 1 ] ; then
- echo "Transition did not start or finish"
- exit 1
- fi
-
- if snap list|grep ubuntu-core; then
- echo "ubuntu-core still installed, transition failed"
- exit 1
- fi
- snap interfaces -i network | MATCH ":network +test-snapd-python-webserver"
- snap interfaces -i network-bind | MATCH ":network-bind +.*test-snapd-python-webserver"
- echo "Ensure the webserver is still working"
- wait_for_service snap.test-snapd-python-webserver.test-snapd-python-webserver
- curl http://localhost | MATCH "XKCD rocks"
-
- systemctl restart snap.test-snapd-python-webserver.test-snapd-python-webserver
- wait_for_service snap.test-snapd-python-webserver.test-snapd-python-webserver
- echo "Ensure the webserver is working after a snap restart"
- curl http://localhost | MATCH "XKCD rocks"
-
- echo "Ensure snap set core works"
- snap set core system.power-key-action=ignore
- if [ "$(snap get core system.power-key-action)" != "ignore" ]; then
- echo "snap get did not return the expected result: "
- snap get core system.power-key-action
- exit 1
- fi
diff -Nru snapd-2.41+19.10.1/tests/main/classic-ubuntu-core-transition-auth/task.yaml snapd-2.42.1+19.10/tests/main/classic-ubuntu-core-transition-auth/task.yaml
--- snapd-2.41+19.10.1/tests/main/classic-ubuntu-core-transition-auth/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/classic-ubuntu-core-transition-auth/task.yaml 1970-01-01 00:00:00.000000000 +0000
@@ -1,76 +0,0 @@
-summary: Ensure that the ubuntu-core -> core transition works with auth.json
-
-# we never test on core because the transition can only happen on "classic"
-# we disable on ppc64el because the downloads are very slow there
-# Fedora, openSUSE and Arch are disabled at the moment as there is something
-# fishy going on and the snapd service gets terminated during the process.
-systems: [-ubuntu-core-*, -ubuntu-*-ppc64el, -fedora-*, -opensuse-*, -arch-*]
-
-# autopkgtest run only a subset of tests that deals with the integration
-# with the distro
-backends: [-autopkgtest]
-
-warn-timeout: 1m
-
-kill-timeout: 5m
-
-debug: |
- snap changes
- #shellcheck source=tests/lib/changes.sh
- . "$TESTSLIB/changes.sh"
- snap change "$(change_id 'Transition ubuntu-core to core')" || true
-
-execute: |
- #shellcheck source=tests/lib/pkgdb.sh
- . "$TESTSLIB/pkgdb.sh"
- echo "Ensure core is gone and we have ubuntu-core instead"
- distro_purge_package snapd
- distro_install_build_snapd
-
- # need to be seeded to allow snap install
- snap wait system seed.loaded
-
- snap download "--${CORE_CHANNEL}" ubuntu-core
- snap ack ./ubuntu-core_*.assert
- snap install ./ubuntu-core_*.snap
-
- mkdir -p /root/.snap/
- echo '{}' > /root/.snap/auth.json
- mkdir -p /home/test/.snap/
- echo '{}' > /home/test/.snap/auth.json
-
- echo "Ensure transition is triggered"
- # wait for steady state or ensure-state-soon will be pointless
- ok=0
- for i in $(seq 40); do
- if ! snap changes|grep -q ".*.Doing.*" ; then
- ok=1
- break
- fi
- sleep .5
- done
- if [ $ok -ne 1 ] ; then
- echo "Did not reach steady state"
- exit 1
- fi
- snap debug ensure-state-soon
-
-
- # wait for transition
- ok=0
- for i in $(seq 240); do
- if snap changes|grep -q ".*Done.*Transition ubuntu-core to core" ; then
- ok=1
- break
- fi
- sleep 1
- done
- if [ $ok -ne 1 ] ; then
- echo "Transition did not start or finish"
- exit 1
- fi
-
- if snap list|grep ubuntu-core; then
- echo "ubuntu-core still installed, transition failed"
- exit 1
- fi
diff -Nru snapd-2.41+19.10.1/tests/main/classic-ubuntu-core-transition-two-cores/task.yaml snapd-2.42.1+19.10/tests/main/classic-ubuntu-core-transition-two-cores/task.yaml
--- snapd-2.41+19.10.1/tests/main/classic-ubuntu-core-transition-two-cores/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/classic-ubuntu-core-transition-two-cores/task.yaml 1970-01-01 00:00:00.000000000 +0000
@@ -1,78 +0,0 @@
-summary: Ensure that the ubuntu-core -> core transition works with two cores
-
-# we never test on core because the transition can only happen on "classic"
-# we disable on ppc64el because the downloads are very slow there
-systems: [-ubuntu-core-*, -ubuntu-*-ppc64el]
-
-# autopkgtest run only a subset of tests that deals with the integration
-# with the distro
-backends: [-autopkgtest]
-
-warn-timeout: 1m
-
-kill-timeout: 5m
-
-debug: |
- snap changes
- #shellcheck source=tests/lib/changes.sh
- . "$TESTSLIB"/changes.sh
- snap change "$(change_id 'Transition ubuntu-core to core')" || true
-
-execute: |
- echo "install a snap"
- snap install test-snapd-python-webserver
- snap interfaces -i network | MATCH ":network.*test-snapd-python-webserver"
-
- #shellcheck source=tests/lib/names.sh
- . "$TESTSLIB/names.sh"
- cp /var/lib/snapd/state.json /var/lib/snapd/state.json.old
- jq -r '.data.snaps["core"].type="xxx"' < /var/lib/snapd/state.json.old > /var/lib/snapd/state.json
-
- systemctl stop snapd.service snapd.socket
- systemctl start snapd.service snapd.socket
-
- snap download "--${CORE_CHANNEL}" ubuntu-core
- snap ack ./ubuntu-core_*.assert
- snap install ./ubuntu-core_*.snap
-
- cp /var/lib/snapd/state.json /var/lib/snapd/state.json.old
- jq -r '.data.snaps["core"].type="os"' < /var/lib/snapd/state.json.old > /var/lib/snapd/state.json
-
- snap list | MATCH "ubuntu-core "
- snap list | MATCH "core "
-
- echo "Ensure transition is triggered"
- # wait for steady state or ensure-state-soon will be pointless
- ok=0
- for _ in $(seq 40); do
- if ! snap changes|grep -q ".*.Doing.*" ; then
- ok=1
- break
- fi
- sleep .5
- done
- if [ $ok -ne 1 ] ; then
- echo "Did not reach steady state"
- exit 1
- fi
- snap debug ensure-state-soon
-
- # wait for transition
- ok=0
- for _ in $(seq 240); do
- if snap changes|grep -q ".*Done.*Transition ubuntu-core to core" ; then
- ok=1
- break
- fi
- sleep 1
- done
- if [ $ok -ne 1 ] ; then
- echo "Transition did not start or finish"
- exit 1
- fi
-
- if ! snap list|MATCH -v ubuntu-core; then
- echo "ubuntu-core still installed, transition failed"
- exit 1
- fi
- snap interfaces -i network | MATCH ":network.*test-snapd-python-webserver"
diff -Nru snapd-2.41+19.10.1/tests/main/config-versions/task.yaml snapd-2.42.1+19.10/tests/main/config-versions/task.yaml
--- snapd-2.41+19.10.1/tests/main/config-versions/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/config-versions/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -22,12 +22,19 @@
echo "Install test snap"
install_local config-versions
+ # sanity
+ snap get config-versions configure-marker | MATCH "executed-for-v1"
+
echo "Setting config value affecting rev 1"
snap set config-versions value=100
echo "Install a new version of the test snap"
install_local config-versions-v2
+ # sanity
+ snap get config-versions configure-marker | MATCH "executed-for-v2"
+ snap get config-versions post-refresh-hook-marker | MATCH "executed-for-v2"
+
echo "Expecting config value to be carried over to the new version 2"
verify_config_value 100
@@ -57,3 +64,13 @@
echo "Revert back to the rev 2"
snap revert --revision=x2 config-versions
verify_config_value 400
+
+ echo "Failing refresh of rev 2"
+ snap refresh --revision=x1 config-versions
+ snap get config-versions post-refresh-hook-marker | MATCH "executed-for-v1"
+ snap get config-versions configure-marker | MATCH "executed-for-v1"
+ # force failure of v2 configure hook
+ snap set config-versions fail-configure=yes
+ snap refresh --revision=x2 config-versions || true
+ snap changes | MATCH "Error .* Refresh \"config-versions\" snap"
+ snap get config-versions post-refresh-hook-marker | MATCH "executed-for-v1"
diff -Nru snapd-2.41+19.10.1/tests/main/desktop-portal-filechooser/task.yaml snapd-2.42.1+19.10/tests/main/desktop-portal-filechooser/task.yaml
--- snapd-2.41+19.10.1/tests/main/desktop-portal-filechooser/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/desktop-portal-filechooser/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -22,6 +22,8 @@
# Ships xdg-desktop-portal 1.2.0
- ubuntu-19.04-*
+ - ubuntu-19.10-*
+
prepare: |
#shellcheck source=tests/lib/desktop-portal.sh
. "$TESTSLIB"/desktop-portal.sh
@@ -50,16 +52,28 @@
echo "The confined application can write files via the portal"
[ ! -f /tmp/file-to-write.txt ]
+
+ # TODO: workaround for testing on 18.04
# The python code does open(path, 'w'), which attempts to truncate the
# file if it exists. Then in fuse handlers inside document-portal, the
# code path for when the inode exists and the caller requested O_TRUNC,
# returns -ENOSYS, resulting in OSError:
# [Errno 38] Function not implemented on the Python side
- # To avoid the issue described we are creating manually the file.
+ # Creating the file beforehand prevents that.
touch /tmp/file-to-write.txt
chown test:test /tmp/file-to-write.txt
+
as_user test-snapd-portal-client save-file "from-sandbox"
- [ -f /tmp/file-to-write.txt ]
+
+ # TODO: another workaround for testing on 18.04
+ # Retry checking until the file contains the written text. In particular on
+ # 18.04, xdg-desktop-portal 0.11 (perhaps still the same for later
+ # versions), the writes were implemented such that the client writes to a
+ # fuse filesystem entry, while the portal forwards the writes to the actual
+ # backing file. It was observed that, even after client does close and the
+ # corresponding *fuse_release() code completes on the portal side, the
+ # contents are not visible in the destination file.
+ retry-tool -n 5 test -s /tmp/file-to-write.txt
MATCH "from-sandbox" < /tmp/file-to-write.txt
debug: |
diff -Nru snapd-2.41+19.10.1/tests/main/desktop-portal-open-file/task.yaml snapd-2.42.1+19.10/tests/main/desktop-portal-open-file/task.yaml
--- snapd-2.41+19.10.1/tests/main/desktop-portal-open-file/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/desktop-portal-open-file/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -12,6 +12,7 @@
systems:
- ubuntu-18.04-*
- ubuntu-19.04-*
+ - ubuntu-19.10-*
environment:
EDITOR_HISTORY: /tmp/editor-history.txt
diff -Nru snapd-2.41+19.10.1/tests/main/desktop-portal-screenshot/task.yaml snapd-2.42.1+19.10/tests/main/desktop-portal-screenshot/task.yaml
--- snapd-2.41+19.10.1/tests/main/desktop-portal-screenshot/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/desktop-portal-screenshot/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -23,6 +23,7 @@
systems:
- ubuntu-18.04-*
- ubuntu-19.04-*
+ - ubuntu-19.10-*
prepare: |
#shellcheck source=tests/lib/desktop-portal.sh
diff -Nru snapd-2.41+19.10.1/tests/main/experimental-features/task.yaml snapd-2.42.1+19.10/tests/main/experimental-features/task.yaml
--- snapd-2.41+19.10.1/tests/main/experimental-features/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/experimental-features/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -15,3 +15,11 @@
# When a feature that is not exported is enabled, a file is not created.
snap set core experimental.layouts=true
test ! -f /var/lib/snapd/features/layouts
+
+ # Features are exported when snapd starts up
+ snap set core experimental.parallel-instances=true
+ test -f /var/lib/snapd/features/parallel-instances
+ systemctl stop snapd
+ rm /var/lib/snapd/features/parallel-instances
+ systemctl start snapd
+ test -f /var/lib/snapd/features/parallel-instances
diff -Nru snapd-2.41+19.10.1/tests/main/install-snaps/task.yaml snapd-2.42.1+19.10/tests/main/install-snaps/task.yaml
--- snapd-2.41+19.10.1/tests/main/install-snaps/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/install-snaps/task.yaml 1970-01-01 00:00:00.000000000 +0000
@@ -1,134 +0,0 @@
-summary: Check install popular snaps
-
-details: |
- This test is intended to install some popular snaps from
- different channels. The idea is to detect any problem
- installing that are currently published and some of them
- with many revisions.
- The execution of this test is made on the nightly build.
-
-manual: true
-
-environment:
- # High Profile
- SNAP/azurecli: azure-cli
- SNAP/awscli: aws-cli
- SNAP/heroku: heroku
- SNAP/hiri: hiri
- SNAP/kubectl: kubectl
- SNAP/rocketchatserver: rocketchat-server
- # Selected from recent insights posts
- SNAP/corebird: corebird
- SNAP/gitterdesktop: gitter-desktop
- SNAP/helm: helm
- SNAP/mattermostdesktop: mattermost-desktop
- SNAP/mentaplexmediaserver: menta-plexmediaserver
- SNAP/openspades: openspades
- SNAP/pintown: pin-town
- SNAP/postgresql10: postgresql10
- SNAP/storjshare: storjshare
- SNAP/slackterm: slack-term
- SNAP/vectr: vectr
- SNAP/wekan: wekan
- SNAP/wormhole: wormhole
- # Featured snaps in Ubuntu Software
- SNAP/anboxinstaller: anbox-installer
- SNAP/lxd: lxd
- # Top non canonical snaps
- SNAP/atom: atom
- SNAP/discord: discord
- SNAP/docker: docker
- SNAP/etcd: etcd
- SNAP/geocoder: geocoder
- SNAP/gimp: gimp
- SNAP/huggle: huggle
- SNAP/hugo: hugo
- SNAP/ia: ia
- SNAP/kurly: kurly
- SNAP/micro: micro
- SNAP/nikola: nikola
- SNAP/parity: parity
- SNAP/paritybitcoin: parity-bitcoin
- SNAP/remmina: remmina
- SNAP/skype: skype
- SNAP/slack: slack
- SNAP/spotify: spotify
- SNAP/telegramsergiusens: telegram-sergiusens
- SNAP/zeronet: zeronet
- SNAP/zeronetjs: zeronet-js
- # Top canonical snaps
- SNAP/bare: bare
- SNAP/bluez: bluez
- SNAP/conjureup: conjure-up
- SNAP/gedit: gedit
- SNAP/go: go
- SNAP/juju: juju
- SNAP/neutron: neutron
- SNAP/nova: nova
- SNAP/snapcraft: snapcraft
- SNAP/solc: solc
- SNAP/vault: vault
-
-prepare: |
- cp /etc/systemd/system/snapd.service.d/local.conf /etc/systemd/system/snapd.service.d/local.conf.bak
- sed 's/SNAPD_CONFIGURE_HOOK_TIMEOUT=.*s/SNAPD_CONFIGURE_HOOK_TIMEOUT=180s/g' -i /etc/systemd/system/snapd.service.d/local.conf
- systemctl daemon-reload
- systemctl restart snapd.socket
-
- if [ ! -d '/snap' ]; then
- #shellcheck source=tests/lib/dirs.sh
- . "$TESTSLIB/dirs.sh"
- ln -s "$SNAP_MOUNT_DIR" /snap
- fi
-
-restore: |
- mv /etc/systemd/system/snapd.service.d/local.conf.bak /etc/systemd/system/snapd.service.d/local.conf
- systemctl daemon-reload
- systemctl restart snapd.socket
-
- if [ -L /snap ]; then
- unlink /snap
- fi
-
-execute: |
- #shellcheck source=tests/lib/snaps.sh
- . "$TESTSLIB/snaps.sh"
-
- CHANNELS="stable candidate beta edge"
- for CHANNEL in $CHANNELS; do
- # shellcheck disable=SC2153
- if ! CHANNEL_INFO="$(snap info --unicode=never "$SNAP" | grep " $CHANNEL: ")"; then
- echo "Snap $SNAP not found"
- exit
- fi
- if echo "$CHANNEL_INFO" | MATCH "$CHANNEL:.*--"; then
- continue
- fi
-
- if echo "$CHANNEL_INFO" | MATCH "$CHANNEL:.*classic"; then
- if is_classic_confinement_supported; then
- snap install "$SNAP" "--$CHANNEL" --classic
- else
- echo "The snap $SNAP requires classic confinement which is not supported yet"
- exit
- fi
- elif echo "$CHANNEL_INFO" | MATCH "$CHANNEL:.*jailmode"; then
- snap install "$SNAP" "--$CHANNEL" --jailmode
- elif echo "$CHANNEL_INFO" | MATCH "$CHANNEL:.*devmode"; then
- snap install "$SNAP" "--$CHANNEL" --devmode
- else
- snap install "$SNAP" "--$CHANNEL"
- fi
- break
- done
-
- echo "Check the snap is properly installed"
- snap list | MATCH "$SNAP"
-
- echo "Check the snap is properly removed"
- snap remove "$SNAP"
-
- if snap list | MATCH "$SNAP"; then
- echo "Snap $SNAP not removed properly"
- exit 1
- fi
diff -Nru snapd-2.41+19.10.1/tests/main/interfaces-account-control/task.yaml snapd-2.42.1+19.10/tests/main/interfaces-account-control/task.yaml
--- snapd-2.41+19.10.1/tests/main/interfaces-account-control/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/interfaces-account-control/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -27,11 +27,8 @@
snap remove "$TSNAP"
execute: |
- #shellcheck source=tests/lib/dirs.sh
- . "$TESTSLIB"/dirs.sh
-
- "$SNAP_MOUNT_DIR"/bin/"$TSNAP".useradd --extrausers alice
- echo alice:password | "$SNAP_MOUNT_DIR"/bin/"$TSNAP".chpasswd
+ snap run "$TSNAP".useradd --extrausers alice
+ echo alice:password | snap run "$TSNAP".chpasswd
# User deletion is unsupported yet on Core: https://bugs.launchpad.net/ubuntu/+source/shadow/+bug/1659534
- # $SNAP_MOUNT_DIR/bin/"$TSNAP".userdel --extrausers alice
+ # snap run $TSNAP".userdel --extrausers alice
diff -Nru snapd-2.41+19.10.1/tests/main/interfaces-calendar-service/task.yaml snapd-2.42.1+19.10/tests/main/interfaces-calendar-service/task.yaml
--- snapd-2.41+19.10.1/tests/main/interfaces-calendar-service/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/interfaces-calendar-service/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -5,7 +5,9 @@
#
# FIXME: disable opensuse-tumbleweed until
# https://github.com/snapcore/snapd/pull/7230 is landed
-systems: [-ubuntu-core-*, -ubuntu-14.04-*, -amazon-*, -centos-*, -opensuse-tumbleweed-*]
+# ubuntu-19.10: test-snapd-eds is incompatible with eds version shipped with the distro
+# arch-linux: test-snapd-eds is incompatible with eds version shipped with the distro
+systems: [-ubuntu-core-*, -ubuntu-14.04-*, -amazon-*, -centos-*, -opensuse-tumbleweed-*, -ubuntu-19.10-*, -arch-linux-*, -debian-sid-*]
# fails in the autopkgtest env with:
# [Wed Aug 15 16:34:12 2018] audit: type=1400
diff -Nru snapd-2.41+19.10.1/tests/main/interfaces-contacts-service/task.yaml snapd-2.42.1+19.10/tests/main/interfaces-contacts-service/task.yaml
--- snapd-2.41+19.10.1/tests/main/interfaces-contacts-service/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/interfaces-contacts-service/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -3,7 +3,10 @@
# Only test on classic systems. Don't test on Ubuntu 14.04, which
# does not ship a new enough evolution-data-server.
# amazon: no need to run this on amazon
-systems: [-ubuntu-core-*, -ubuntu-14.04-*, -amazon-*, -centos-*]
+# ubuntu-19.10: test-snapd-eds is incompatible with eds shipped with the distro
+# arch-linux: test-snapd-eds is incompatible with eds version shipped with the distro
+# opensuse-tumbleweed: test-snapd-eds is incompatible with eds version shipped with the distro
+systems: [-ubuntu-core-*, -ubuntu-14.04-*, -amazon-*, -centos-*, -ubuntu-19.10-*, -arch-linux-*, -debian-sid-*, -opensuse-tumbleweed-*]
# fails in autopkgtest environment with:
# [Wed Aug 15 16:08:23 2018] audit: type=1400
@@ -15,21 +18,23 @@
backends: [-autopkgtest]
environment:
- XDG: $(pwd)/xdg
+ XDG: /tmp/xdg
XDG_CONFIG_HOME: $XDG/config
XDG_DATA_HOME: $XDG/share
XDG_CACHE_HOME: $XDG/cache
-restore: |
- if [ -e dbus-launch.pid ]; then
- kill "$(cat dbus-launch.pid)"
- fi
+debug: |
+ echo "Output process to see what might write to $XDG"
+ ps uafx
+ echo "Output dbus-session"
+ systemctl status dbus-session || true
+ echo "Show what is in $XDG"
+ ls -alR "$XDG"
- # In case the process gvfsd-metadata does not finish by itself, it is manually stopped
- # The reason is that gvfsd-metadata locks the xdg/share/gvfs-metadata directory content
- # producing an error when the xdg directory is removed.
- if pid="$(pidof gvfsd-metadata)"; then
- kill -9 "$pid" || true
+restore: |
+ echo "Stop dbus session bus and all its children"
+ if systemctl is-active dbus-session; then
+ systemctl stop dbus-session
fi
rm -rf "$XDG"
@@ -44,9 +49,10 @@
fi
mkdir -p "$XDG_CONFIG_HOME" "$XDG_DATA_HOME" "$XDG_CACHE_HOME"
- echo "Setting up D-Bus session bus"
- eval "$(dbus-launch --sh-syntax)"
- echo "$DBUS_SESSION_BUS_PID" > dbus-launch.pid
+ echo "Setting up D-Bus session bus in a systemd unit"
+ systemd-run --unit=dbus-session --property=Type=forking -r /bin/sh -c "XDG_CONFIG_HOME=$XDG_CONFIG_HOME XDG_DATA_HOME=$XDG_DATA_HOME XDG_CACHE_HOME=$XDG_CACHE_HOME dbus-launch --sh-syntax > /tmp/dbus-sh"
+ retry-tool -n 20 test -e /tmp/dbus-sh
+ eval "$(cat /tmp/dbus-sh)"
echo "The interface is initially disconnected"
snap interfaces -i contacts-service | MATCH -- '- +test-snapd-eds:contacts-service'
diff -Nru snapd-2.41+19.10.1/tests/main/interfaces-fuse_support/task.yaml snapd-2.42.1+19.10/tests/main/interfaces-fuse_support/task.yaml
--- snapd-2.41+19.10.1/tests/main/interfaces-fuse_support/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/interfaces-fuse_support/task.yaml 1970-01-01 00:00:00.000000000 +0000
@@ -1,97 +0,0 @@
-summary: Ensure that the fuse-support interface works.
-
-# no support for fuse on 14.04
-systems: [-ubuntu-14.04-*]
-
-details: |
- The fuse-support interface allows a snap to manage FUSE file systems.
-
- A snap which defines the fuse-support plug must be shown in the interfaces list.
- The plug must be autoconnected on install and, as usual, must be able to be
- reconnected.
-
- A snap declaring a plug on this interface must be able to create a fuse filesystem
- in a writable zone. The fuse-consumer test snap creates a readable file with a known
- name and content in the mount point given to the command.
-
-environment:
- MOUNT_POINT/regular: /var/snap/test-snapd-fuse-consumer/current/mount_point
- MOUNT_POINT_OUTSIDE/regular: /var/snap/test-snapd-fuse-consumer/current/mount_point
- NAME/regular: test-snapd-fuse-consumer
- # snap with instance key 'foo'
- MOUNT_POINT/parallel: /var/snap/test-snapd-fuse-consumer_foo/current/mount_point
- MOUNT_POINT_OUTSIDE/parallel: /var/snap/test-snapd-fuse-consumer_foo/current/mount_point
- NAME/parallel: test-snapd-fuse-consumer_foo
-
-prepare: |
- if [[ "$SPREAD_VARIANT" == "parallel" ]]; then
- snap set system experimental.parallel-instances=true
- fi
- echo "Given a snap declaring a fuse plug is installed"
- snap install "$NAME"
-
- echo "And a user writable mount point is created"
- mkdir -p "$MOUNT_POINT_OUTSIDE"
-
-restore: |
- # remove the mount point
- mounts=$(nsenter "--mount=/run/snapd/ns/$NAME.mnt" cat /proc/mounts | \
- grep "$(basename "$MOUNT_POINT") fuse" | cut -f2 -d' ')
- for m in $mounts; do
- nsenter "--mount=/run/snapd/ns/$NAME.mnt" umount "$m"
- done
- rm -rf "$MOUNT_POINT_OUTSIDE"
-
- if [[ "$SPREAD_VARIANT" == "parallel" ]]; then
- snap set system experimental.parallel-instances=null
- fi
-
-execute: |
- echo "The interface is disconnected by default"
- snap interfaces -i fuse-support | MATCH "^- +$NAME:fuse-support"
-
- if [ "$(snap debug confinement)" = strict ]; then
- echo "The snap is not able to create a fuse file system with the plug disconnected"
- if "$NAME.create" -f "$MOUNT_POINT" 2> fuse.error; then
- echo "Expected permission error creating fuse filesystem with disconnected plug"
- exit 1
- fi
- MATCH "Permission denied" < fuse.error
- fi
-
- echo "When the plug is connected"
- snap connect "$NAME:fuse-support"
-
- echo "Then the snap is able to create a fuse filesystem"
- # start fuse consumer in foreground and make it a background job
- "$NAME.create" -f "$MOUNT_POINT" &
- createpid=$!
- # cleanup the background job on exit
- trap 'kill $createpid; wait $createpid' EXIT
-
- # it may take a while for hello file to appear
- for _ in $(seq 100); do
- if test -r "/proc/${createpid}/root/${MOUNT_POINT}/hello"; then
- break
- fi
- sleep .1
- done
- test -r "/proc/${createpid}/root/${MOUNT_POINT}/hello"
- # prefer cat ... | MATCH so that we can see which PID is used
- #shellcheck disable=SC2002
- cat "/proc/${createpid}/root/${MOUNT_POINT}/hello" | MATCH "Hello World!"
-
- # SIGTERM triggers a clean exit
- kill $createpid
- trap - EXIT
-
- # the create app will try to unmount the fuse filesystem while exiting,
- # it will fail, as seccomp rules are blocking umount2
- echo "The snap exited with an error"
- # SIGSYS - 31 on x86, wait exit status if killed by signal = 128 +
- wait $createpid || test "$?" = "159"
-
- # verify that the mount_point was not removed from mount namespace of the snap
- mountpath=$(nsenter "--mount=/run/snapd/ns/$NAME.mnt" cat /proc/mounts | \
- grep "$(basename "$MOUNT_POINT") fuse" | cut -f2 -d' ')
- test -n "$mountpath"
diff -Nru snapd-2.41+19.10.1/tests/main/interfaces-fuse-support/task.yaml snapd-2.42.1+19.10/tests/main/interfaces-fuse-support/task.yaml
--- snapd-2.41+19.10.1/tests/main/interfaces-fuse-support/task.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/interfaces-fuse-support/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,107 @@
+summary: Ensure that the fuse-support interface works.
+
+# no support for fuse on 14.04
+systems: [-ubuntu-14.04-*]
+
+details: |
+ The fuse-support interface allows a snap to manage FUSE file systems.
+
+ A snap which defines the fuse-support plug must be shown in the interfaces list.
+ The plug must be autoconnected on install and, as usual, must be able to be
+ reconnected.
+
+ A snap declaring a plug on this interface must be able to create a fuse filesystem
+ in a writable zone. The fuse-consumer test snap creates a readable file with a known
+ name and content in the mount point given to the command.
+
+environment:
+ MOUNT_POINT/regular: /var/snap/test-snapd-fuse-consumer/current/mount_point
+ MOUNT_POINT_OUTSIDE/regular: /var/snap/test-snapd-fuse-consumer/current/mount_point
+ NAME/regular: test-snapd-fuse-consumer
+ # snap with instance key 'foo'
+ MOUNT_POINT/parallel: /var/snap/test-snapd-fuse-consumer_foo/current/mount_point
+ MOUNT_POINT_OUTSIDE/parallel: /var/snap/test-snapd-fuse-consumer_foo/current/mount_point
+ NAME/parallel: test-snapd-fuse-consumer_foo
+
+prepare: |
+ if not mountinfo-tool /sys/fs/fuse/connections .fs_type=fusectl; then
+ touch please-unmount-fuse-connections
+ fi
+
+ if [[ "$SPREAD_VARIANT" == "parallel" ]]; then
+ snap set system experimental.parallel-instances=true
+ fi
+ echo "Given a snap declaring a fuse plug is installed"
+ snap install "$NAME"
+
+ echo "And a user writable mount point is created"
+ mkdir -p "$MOUNT_POINT_OUTSIDE"
+
+restore: |
+ # remove the mount point
+ mounts=$(nsenter "--mount=/run/snapd/ns/$NAME.mnt" cat /proc/mounts | \
+ grep "$(basename "$MOUNT_POINT") fuse" | cut -f2 -d' ')
+ for m in $mounts; do
+ nsenter "--mount=/run/snapd/ns/$NAME.mnt" umount "$m"
+ done
+ rm -rf "$MOUNT_POINT_OUTSIDE"
+
+ if [[ "$SPREAD_VARIANT" == "parallel" ]]; then
+ snap set system experimental.parallel-instances=null
+ fi
+ if [ -e please-unmount-fuse-connections ]; then
+ if mountinfo-tool /sys/fs/fuse/connections .fs_type=fusectl; then
+ umount /sys/fs/fuse/connections
+ fi
+ rm -f please-unmount-fuse-connections
+ fi
+
+execute: |
+ echo "The interface is disconnected by default"
+ snap interfaces -i fuse-support | MATCH "^- +$NAME:fuse-support"
+
+ if [ "$(snap debug confinement)" = strict ]; then
+ echo "The snap is not able to create a fuse file system with the plug disconnected"
+ if "$NAME.create" -f "$MOUNT_POINT" 2> fuse.error; then
+ echo "Expected permission error creating fuse filesystem with disconnected plug"
+ exit 1
+ fi
+ MATCH "Permission denied" < fuse.error
+ fi
+
+ echo "When the plug is connected"
+ snap connect "$NAME:fuse-support"
+
+ echo "Then the snap is able to create a fuse filesystem"
+ # start fuse consumer in foreground and make it a background job
+ "$NAME.create" -f "$MOUNT_POINT" &
+ createpid=$!
+ # cleanup the background job on exit
+ trap 'kill $createpid; wait $createpid' EXIT
+
+ # it may take a while for hello file to appear
+ for _ in $(seq 100); do
+ if test -r "/proc/${createpid}/root/${MOUNT_POINT}/hello"; then
+ break
+ fi
+ sleep .1
+ done
+ test -r "/proc/${createpid}/root/${MOUNT_POINT}/hello"
+ # prefer cat ... | MATCH so that we can see which PID is used
+ #shellcheck disable=SC2002
+ cat "/proc/${createpid}/root/${MOUNT_POINT}/hello" | MATCH "Hello World!"
+
+ # SIGTERM triggers a clean exit
+ kill $createpid
+ trap - EXIT
+
+ # the create app will try to unmount the fuse filesystem while exiting,
+ # it will fail, as seccomp rules are blocking umount2
+ echo "The snap exited with an error"
+ # SIGSYS - 31 on x86, wait exit status if killed by signal = 128 +
+ wait $createpid || test "$?" = "159"
+
+ # verify that the mount_point was not removed from mount namespace of the snap
+ mountpath=$(nsenter "--mount=/run/snapd/ns/$NAME.mnt" cat /proc/mounts | \
+ grep "$(basename "$MOUNT_POINT") fuse" | cut -f2 -d' ')
+ test -n "$mountpath"
diff -Nru snapd-2.41+19.10.1/tests/main/interfaces-many-core-provided/task.yaml snapd-2.42.1+19.10/tests/main/interfaces-many-core-provided/task.yaml
--- snapd-2.41+19.10.1/tests/main/interfaces-many-core-provided/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/interfaces-many-core-provided/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -16,7 +16,7 @@
# - Debian sid amd64 VM
# - Debian 9 amd64 VM
# - TODO: All Fedora systems (for classic-only; unrelated error elsewhere)
-systems: [ubuntu-core-16-64, ubuntu-14.04-32, ubuntu-16.04-64, ubuntu-18.04-64, ubuntu-18.04-32, ubuntu-*-amd64, ubuntu-*-armhf, ubuntu-*-arm64, ubuntu-*-i386, ubuntu-*-ppc64el, debian-*]
+systems: [ubuntu-core-1*-64, ubuntu-14.04-32, ubuntu-16.04-64, ubuntu-18.04-64, ubuntu-18.04-32, ubuntu-*-amd64, ubuntu-*-armhf, ubuntu-*-arm64, ubuntu-*-i386, ubuntu-*-ppc64el, debian-*]
# memory issue inside the adt environment
backends: [-autopkgtest]
diff -Nru snapd-2.41+19.10.1/tests/main/interfaces-many-snap-provided/task.yaml snapd-2.42.1+19.10/tests/main/interfaces-many-snap-provided/task.yaml
--- snapd-2.41+19.10.1/tests/main/interfaces-many-snap-provided/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/interfaces-many-snap-provided/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -16,7 +16,7 @@
# - Debian sid amd64 VM
# - Debian 9 amd64 VM
# - TODO: All Fedora systems (for classic-only; unrelated error elsewhere)
-systems: [ubuntu-core-16-64, ubuntu-14.04-32, ubuntu-16.04-64, ubuntu-18.04-64, ubuntu-18.04-32, ubuntu-*-amd64, ubuntu-*-armhf, ubuntu-*-arm64, ubuntu-*-i386, ubuntu-*-ppc64el, debian-*]
+systems: [ubuntu-core-1*-64, ubuntu-14.04-32, ubuntu-16.04-64, ubuntu-18.04-64, ubuntu-18.04-32, ubuntu-*-amd64, ubuntu-*-armhf, ubuntu-*-arm64, ubuntu-*-i386, ubuntu-*-ppc64el, debian-*]
# memory issue inside the adt environment
backends: [-autopkgtest]
diff -Nru snapd-2.41+19.10.1/tests/main/interfaces-network-manager/task.yaml snapd-2.42.1+19.10/tests/main/interfaces-network-manager/task.yaml
--- snapd-2.41+19.10.1/tests/main/interfaces-network-manager/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/interfaces-network-manager/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -12,7 +12,7 @@
# boards because the (wifi) network is already managed by netplan
# there and when n-m gets installed/removed it will hang when
# trying to deconfigure the wifi network which is already owned.
-systems: [ubuntu-core-16-64, ubuntu-core-16-32]
+systems: [ubuntu-core-1*-64, ubuntu-core-1*-32]
prepare: |
echo "Give a network-manager snap is installed"
diff -Nru snapd-2.41+19.10.1/tests/main/interfaces-openvswitch/task.yaml snapd-2.42.1+19.10/tests/main/interfaces-openvswitch/task.yaml
--- snapd-2.41+19.10.1/tests/main/interfaces-openvswitch/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/interfaces-openvswitch/task.yaml 1970-01-01 00:00:00.000000000 +0000
@@ -1,111 +0,0 @@
-summary: Ensure that the openvswitch interface works.
-
-# amazon: no openvswitch package
-systems:
- - -ubuntu-core-*
- - -amazon-*
-
-details: |
- The openvswitch interface allows to task to the openvswitch socket (rw mode).
-
- A snap which defines a openvswitch plug must be shown in the interfaces list.
- The plug must not be autoconnected on install and, as usual, must be able to be
- reconnected.
-
- A snap declaring a plug on this interface must be able to do all the operations that
- are carried through the socket, in this test we exercise bridge and port creation,
- list and deletion.
-
-# marking as manual due to errors during prepare
-manual: true
-
-prepare: |
- #shellcheck source=tests/lib/pkgdb.sh
- . "$TESTSLIB/pkgdb.sh"
-
- echo "Given openvswitch is installed"
- distro_install_package --no-install-recommends openvswitch-switch
-
- # Ensure the openvswitch service is started which isn't the case by
- # default on all distributions
- systemctl enable --now openvswitch
-
- echo "And a snap declaring a plug on the openvswitch interface is installed"
- snap install --edge test-snapd-openvswitch-consumer
-
- echo "And a tap interface is defined"
- ip tuntap add tap1 mode tap
-
-restore: |
- #shellcheck source=tests/lib/pkgdb.sh
- . "$TESTSLIB/pkgdb.sh"
-
- ovs-vsctl del-port br0 tap1 || true
- ovs-vsctl del-br br0 || true
-
- distro_purge_package openvswitch-switch
- distro_auto_remove_packages
-
- ip link delete tap1 || true
-
-execute: |
- echo "The interface is disconnected by default"
- snap interfaces -i openvswitch | MATCH -- '^- +test-snapd-openvswitch-consumer:openvswitch'
-
- echo "When the plug is connected"
- snap connect test-snapd-openvswitch-consumer:openvswitch
-
- echo "Then the snap is able to create a bridge"
- test-snapd-openvswitch-consumer.ovs-vsctl add-br br0
- ovs-vsctl list-br | MATCH br0
-
- echo "And the snap is able to create a port"
- test-snapd-openvswitch-consumer.ovs-vsctl add-port br0 tap1
- ovs-vsctl list-ports br0 | MATCH tap1
-
- echo "And the snap is able to delete a port"
- test-snapd-openvswitch-consumer.ovs-vsctl del-port br0 tap1
- ovs-vsctl list-ports br0 | MATCH -v tap1
-
- echo "And the snap is able to delete a bridge"
- test-snapd-openvswitch-consumer.ovs-vsctl del-br br0
- ovs-vsctl list-br | MATCH -v br0
-
- if [ "$(snap debug confinement)" = partial ] ; then
- exit 0
- fi
-
- echo "When the plug is disconnected"
- snap disconnect test-snapd-openvswitch-consumer:openvswitch
-
- echo "Then the snap is not able to create a bridge"
- if test-snapd-openvswitch-consumer.ovs-vsctl add-br br0 2> bridge-creation.error; then
- echo "Expected permission error accessing openvswitch socket with disconnected plug"
- exit 1
- fi
- MATCH 'database connection failed \(Permission denied\)' < bridge-creation.error
-
- ovs-vsctl add-br br0
-
- echo "And the snap is not able to create a port"
- if test-snapd-openvswitch-consumer.ovs-vsctl add-port br0 tap1 2> port-creation.error; then
- echo "Expected permission error accessing openvswitch socket with disconnected plug"
- exit 1
- fi
- MATCH 'database connection failed \(Permission denied\)' < port-creation.error
-
- ovs-vsctl add-port br0 tap1
-
- echo "And the snap is not able to delete a port"
- if test-snapd-openvswitch-consumer.ovs-vsctl del-port br0 tap1 2> port-deletion.error; then
- echo "Expected permission error accessing openvswitch socket with disconnected plug"
- exit 1
- fi
- MATCH 'database connection failed \(Permission denied\)' < port-deletion.error
-
- echo "And the snap is not able to delete a bridge"
- if test-snapd-openvswitch-consumer.ovs-vsctl del-br br0 2> br-creation.error; then
- echo "Expected permission error accessing openvswitch socket with disconnected plug"
- exit 1
- fi
- MATCH 'database connection failed \(Permission denied\)' < br-creation.error
diff -Nru snapd-2.41+19.10.1/tests/main/interfaces-timeserver-control/task.yaml snapd-2.42.1+19.10/tests/main/interfaces-timeserver-control/task.yaml
--- snapd-2.41+19.10.1/tests/main/interfaces-timeserver-control/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/interfaces-timeserver-control/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -16,7 +16,20 @@
# Install a snap declaring a plug on timeserver-control
install_local test-snapd-timedate-control-consumer
+ if [[ "$SPREAD_SYSTEM" == ubuntu-19.10-* ]]; then
+ # ensure chrony is not installed as it interferes with
+ # the systemd-timesyncd.service
+ apt remove -y chrony
+ systemctl restart systemd-timesyncd.service
+ fi
+
restore: |
+ if [[ "$SPREAD_SYSTEM" == ubuntu-19.10-* ]]; then
+ # bring it back
+ apt install -y chrony
+ systemctl restart systemd-timesyncd.service
+ fi
+
# Restore the initial timeserver
if [ -s timeserver.txt ]; then
timedatectl set-ntp "$(cat timeserver.txt)"
diff -Nru snapd-2.41+19.10.1/tests/main/kernel-snap-refresh-on-core/task.yaml snapd-2.42.1+19.10/tests/main/kernel-snap-refresh-on-core/task.yaml
--- snapd-2.41+19.10.1/tests/main/kernel-snap-refresh-on-core/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/kernel-snap-refresh-on-core/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -20,13 +20,6 @@
grub-editenv list
execute: |
- wait_core_post_boot() {
- # booted
- while [ "$(bootenv snap_mode)" != "" ]; do
- sleep 1
- done
- }
-
same=$(snap info pc-kernel | awk "
/^ ${KERNEL_CHANNEL}:/ {ch1=\$2}
/^ ${NEW_KERNEL_CHANNEL}:/ {ch2=\$2}
diff -Nru snapd-2.41+19.10.1/tests/main/listing/task.yaml snapd-2.42.1+19.10/tests/main/listing/task.yaml
--- snapd-2.41+19.10.1/tests/main/listing/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/listing/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -8,7 +8,7 @@
snap set system experimental.parallel-instances=true
install_local_as test-snapd-tools test-snapd-tools_foo
-restore:
+restore: |
snap set system experimental.parallel-instances=null
execute: |
diff -Nru snapd-2.41+19.10.1/tests/main/lxd/task.yaml snapd-2.42.1+19.10/tests/main/lxd/task.yaml
--- snapd-2.41+19.10.1/tests/main/lxd/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/lxd/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -31,6 +31,8 @@
lxd.lxc stop my-ubuntu --force
lxd.lxc delete my-ubuntu
+ lxd-tool undo-lxd-mount-changes
+
debug: |
# shellcheck source=tests/lib/journalctl.sh
. "$TESTSLIB/journalctl.sh"
diff -Nru snapd-2.41+19.10.1/tests/main/lxd-no-fuse/task.yaml snapd-2.42.1+19.10/tests/main/lxd-no-fuse/task.yaml
--- snapd-2.41+19.10.1/tests/main/lxd-no-fuse/task.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/lxd-no-fuse/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,44 @@
+summary: Check that we give a useful error when fuse is missing in lxd
+
+# we just need a single system to verify this
+systems: [ubuntu-18.04-64]
+
+execute: |
+ echo "Ensure we use the snap"
+ apt autoremove -y lxd
+
+ echo "Install lxd"
+ snap install --candidate lxd
+
+ echo "Create a trivial container using the lxd snap"
+ snap set lxd waitready.timeout=240
+ lxd waitready
+ lxd init --auto
+
+ echo "Setting up proxy for lxc"
+ if [ -n "${http_proxy:-}" ]; then
+ lxd.lxc config set core.proxy_http "$http_proxy"
+ fi
+ if [ -n "${https_proxy:-}" ]; then
+ lxd.lxc config set core.proxy_https "$http_proxy"
+ fi
+
+ lxd.lxc launch "images:ubuntu/18.04" my-ubuntu
+
+ echo "Remove fuse to trigger the fuse sanity check"
+ lxd.lxc exec my-ubuntu -- apt autoremove -y fuse
+
+ echo "Install snapd"
+ lxd.lxc exec my-ubuntu -- mkdir -p "$GOHOME"
+ lxd.lxc file push "$GOHOME"/snapd_*.deb "my-ubuntu/$GOHOME/"
+ lxd.lxc exec my-ubuntu -- apt install -y "$GOHOME"/snapd_*.deb
+
+ echo "And validate that we get a useful error"
+ if lxd.lxc exec my-ubuntu snap install test-snapd-tools 2> err.txt; then
+ echo "snap install should fail but it did not?"
+ exit 1
+ fi
+ MATCH 'The "fuse" filesystem is required' < err.txt
+
+restore: |
+ lxd-tool undo-lxd-mount-changes
diff -Nru snapd-2.41+19.10.1/tests/main/lxd-snapfuse/task.yaml snapd-2.42.1+19.10/tests/main/lxd-snapfuse/task.yaml
--- snapd-2.41+19.10.1/tests/main/lxd-snapfuse/task.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/lxd-snapfuse/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,50 @@
+summary: Check snapfuse works
+
+# we just need a single system to verify this
+systems: [ubuntu-18.04-64]
+
+restore: |
+ lxd-tool undo-lxd-mount-changes
+
+execute: |
+ echo "Ensure we use the snap"
+ apt autoremove -y lxd
+
+ echo "Ensure we have no squashfuse package installed"
+ apt autoremove -y squashfuse
+
+ echo "Install lxd"
+ snap install --candidate lxd
+
+ echo "Create a trivial container using the lxd snap"
+ snap set lxd waitready.timeout=240
+ lxd waitready
+ lxd init --auto
+
+ echo "Setting up proxy for lxc"
+ if [ -n "${http_proxy:-}" ]; then
+ lxd.lxc config set core.proxy_http "$http_proxy"
+ fi
+ if [ -n "${https_proxy:-}" ]; then
+ lxd.lxc config set core.proxy_https "$http_proxy"
+ fi
+
+ lxd.lxc launch "images:ubuntu/18.04" my-ubuntu
+
+ echo "Install snapd"
+ lxd.lxc exec my-ubuntu -- mkdir -p "$GOHOME"
+ lxd.lxc file push "$GOHOME"/snapd_*.deb "my-ubuntu/$GOHOME/"
+ lxd.lxc exec my-ubuntu -- apt install -y "$GOHOME"/snapd_*.deb
+
+ echo "And validate that we can use snaps"
+ lxd.lxc exec my-ubuntu -- snap install test-snapd-tools
+ echo "And we can run snaps as regular users"
+ lxd.lxc exec my-ubuntu -- su -c "/snap/bin/test-snapd-tools.echo from-the-inside" ubuntu | MATCH from-the-inside
+ echo "And as root"
+ lxd.lxc exec my-ubuntu -- test-snapd-tools.echo from-the-inside | MATCH from-the-inside
+
+ echo "And snapfuse is actually running"
+ ps afx | MATCH snapfuse
+
+ echo "We can also remove snaps successfully"
+ lxd.lxc exec my-ubuntu -- snap remove test-snapd-tools
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-16.04-64/HOST.expected.txt snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-16.04-64/HOST.expected.txt
--- snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-16.04-64/HOST.expected.txt 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-16.04-64/HOST.expected.txt 2019-10-30 12:17:43.000000000 +0000
@@ -1,39 +1,39 @@
-1 0 0:0 / / rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
-2 1 1:0 / /dev rw,nosuid,relatime shared:2 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
-3 2 1:1 / /dev/hugepages rw,relatime shared:3 - hugetlbfs hugetlbfs rw,pagesize=2M
-4 2 1:2 / /dev/mqueue rw,relatime shared:4 - mqueue mqueue rw
-5 2 1:3 / /dev/pts rw,nosuid,noexec,relatime shared:5 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
-6 2 1:4 / /dev/shm rw,nosuid,nodev shared:6 - tmpfs tmpfs rw
-7 1 1:5 / /proc rw,nosuid,nodev,noexec,relatime shared:7 - proc proc rw
-8 7 1:6 / /proc/fs/nfsd rw,relatime shared:8 - nfsd nfsd rw
-10 9 1:7 / /proc/sys/fs/binfmt_misc rw,relatime shared:9 - binfmt_misc binfmt_misc rw
-9 7 1:8 / /proc/sys/fs/binfmt_misc rw,relatime shared:10 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
-11 1 1:9 / /run rw,nosuid,noexec,relatime shared:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-12 11 1:10 / /run/cgmanager/fs rw,relatime shared:12 - tmpfs cgmfs rw,size=VARIABLE,mode=755
-13 11 1:11 / /run/lock rw,nosuid,nodev,noexec,relatime shared:13 - tmpfs tmpfs rw,size=VARIABLE
-14 11 1:12 / /run/rpc_pipefs rw,relatime shared:14 - rpc_pipefs sunrpc rw
-15 11 1:13 / /run/user/0 rw,nosuid,nodev,relatime shared:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-16 1 2:0 / /snap/core/1 ro,nodev,relatime shared:16 - squashfs /dev/loop0 ro
-17 1 2:1 / /snap/core18/1 ro,nodev,relatime shared:17 - squashfs /dev/loop1 ro
-18 1 2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime shared:18 - squashfs /dev/loop2 ro
-19 1 2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime shared:19 - squashfs /dev/loop3 ro
-20 1 2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime shared:20 - squashfs /dev/loop4 ro
-21 1 1:14 / /sys rw,nosuid,nodev,noexec,relatime shared:21 - sysfs sysfs rw
-22 21 1:15 / /sys/fs/cgroup rw shared:22 - tmpfs tmpfs rw,mode=755
-23 22 1:16 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:23 - cgroup cgroup rw,blkio
-24 22 1:17 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:24 - cgroup cgroup rw,cpu,cpuacct
-25 22 1:18 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:25 - cgroup cgroup rw,cpuset,clone_children
-26 22 1:19 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:26 - cgroup cgroup rw,devices
-27 22 1:20 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:27 - cgroup cgroup rw,freezer
-28 22 1:21 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:28 - cgroup cgroup rw,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb
-29 22 1:22 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:29 - cgroup cgroup rw,memory
-30 22 1:23 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:30 - cgroup cgroup rw,net_cls,net_prio
-31 22 1:24 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:31 - cgroup cgroup rw,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event
-32 22 1:25 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:32 - cgroup cgroup rw,pids,release_agent=/run/cgmanager/agents/cgm-release-agent.pids
-33 22 1:26 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime shared:33 - cgroup cgroup rw,rdma,release_agent=/run/cgmanager/agents/cgm-release-agent.rdma
-34 22 1:27 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:34 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
-35 21 1:28 / /sys/fs/fuse/connections rw,relatime shared:35 - fusectl fusectl rw
-36 21 1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime shared:36 - pstore pstore rw
-37 21 1:30 / /sys/kernel/config rw,relatime shared:37 - configfs configfs rw
-38 21 1:31 / /sys/kernel/debug rw,relatime shared:38 - debugfs debugfs rw
-39 21 1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:39 - securityfs securityfs rw
+0:0 / / rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
+1:0 / /dev rw,nosuid,relatime shared:2 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
+1:1 / /dev/hugepages rw,relatime shared:3 - hugetlbfs hugetlbfs rw,pagesize=2M
+1:2 / /dev/mqueue rw,relatime shared:4 - mqueue mqueue rw
+1:3 / /dev/pts rw,nosuid,noexec,relatime shared:5 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
+1:4 / /dev/shm rw,nosuid,nodev shared:6 - tmpfs tmpfs rw
+1:5 / /proc rw,nosuid,nodev,noexec,relatime shared:7 - proc proc rw
+1:6 / /proc/fs/nfsd rw,relatime shared:8 - nfsd nfsd rw
+1:7 / /proc/sys/fs/binfmt_misc rw,relatime shared:9 - binfmt_misc binfmt_misc rw
+1:8 / /proc/sys/fs/binfmt_misc rw,relatime shared:10 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
+1:9 / /run rw,nosuid,noexec,relatime shared:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:10 / /run/cgmanager/fs rw,relatime shared:12 - tmpfs cgmfs rw,size=VARIABLE,mode=755
+1:11 / /run/lock rw,nosuid,nodev,noexec,relatime shared:13 - tmpfs tmpfs rw,size=VARIABLE
+1:12 / /run/rpc_pipefs rw,relatime shared:14 - rpc_pipefs sunrpc rw
+1:13 / /run/user/0 rw,nosuid,nodev,relatime shared:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+2:0 / /snap/core/1 ro,nodev,relatime shared:16 - squashfs /dev/loop0 ro
+2:1 / /snap/core18/1 ro,nodev,relatime shared:17 - squashfs /dev/loop1 ro
+2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime shared:18 - squashfs /dev/loop2 ro
+2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime shared:19 - squashfs /dev/loop3 ro
+2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime shared:20 - squashfs /dev/loop4 ro
+1:14 / /sys rw,nosuid,nodev,noexec,relatime shared:21 - sysfs sysfs rw
+1:15 / /sys/fs/cgroup rw shared:22 - tmpfs tmpfs rw,mode=755
+1:16 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:23 - cgroup cgroup rw,blkio
+1:17 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:24 - cgroup cgroup rw,cpu,cpuacct
+1:18 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:25 - cgroup cgroup rw,cpuset
+1:19 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:26 - cgroup cgroup rw,devices
+1:20 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:27 - cgroup cgroup rw,freezer
+1:21 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:28 - cgroup cgroup rw,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb
+1:22 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:29 - cgroup cgroup rw,memory
+1:23 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:30 - cgroup cgroup rw,net_cls,net_prio
+1:24 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:31 - cgroup cgroup rw,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event
+1:25 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:32 - cgroup cgroup rw,pids,release_agent=/run/cgmanager/agents/cgm-release-agent.pids
+1:26 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime shared:33 - cgroup cgroup rw,rdma,release_agent=/run/cgmanager/agents/cgm-release-agent.rdma
+1:27 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:34 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
+1:28 / /sys/fs/fuse/connections rw,relatime shared:35 - fusectl fusectl rw
+1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime shared:36 - pstore pstore rw
+1:30 / /sys/kernel/config rw,relatime shared:37 - configfs configfs rw
+1:31 / /sys/kernel/debug rw,relatime shared:38 - debugfs debugfs rw
+1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:39 - securityfs securityfs rw
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-16.04-64/PER-SNAP-16.expected.txt snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-16.04-64/PER-SNAP-16.expected.txt
--- snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-16.04-64/PER-SNAP-16.expected.txt 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-16.04-64/PER-SNAP-16.expected.txt 2019-10-30 12:17:43.000000000 +0000
@@ -1,73 +1,76 @@
-1001 1000 2:0 / / ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-1002 1001 1:0 / /dev rw,nosuid,relatime master:2 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
-1003 1002 1:1 / /dev/hugepages rw,relatime master:3 - hugetlbfs hugetlbfs rw,pagesize=2M
-1004 1002 1:2 / /dev/mqueue rw,relatime master:4 - mqueue mqueue rw
-1005 1002 1:33 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-1006 1002 1:3 / /dev/pts rw,nosuid,noexec,relatime master:5 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
-1007 1006 1:33 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-1008 1002 1:4 / /dev/shm rw,nosuid,nodev master:6 - tmpfs tmpfs rw
-1009 1001 0:0 /etc /etc rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1010 1009 2:0 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-1011 1009 2:0 /etc/ssl /etc/ssl ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-1012 1001 0:0 /home /home rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1013 1001 0:0 /lib/firmware /lib/firmware rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1014 1001 0:0 /lib/modules /lib/modules rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1015 1001 0:0 /media /media rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
-1016 1001 0:0 /mnt /mnt rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1017 1001 1:5 / /proc rw,nosuid,nodev,noexec,relatime master:7 - proc proc rw
-1018 1017 1:6 / /proc/fs/nfsd rw,relatime master:8 - nfsd nfsd rw
-1020 1019 1:7 / /proc/sys/fs/binfmt_misc rw,relatime master:9 - binfmt_misc binfmt_misc rw
-1019 1017 1:8 / /proc/sys/fs/binfmt_misc rw,relatime master:10 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
-1021 1001 0:0 /root /root rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1022 1001 1:9 / /run rw,nosuid,noexec,relatime master:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1023 1022 1:10 / /run/cgmanager/fs rw,relatime master:12 - tmpfs cgmfs rw,size=VARIABLE,mode=755
-1024 1022 1:11 / /run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
-1025 1022 1:9 /netns /run/netns rw,nosuid,noexec,relatime shared:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1026 1022 1:12 / /run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
-1027 1022 1:9 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1028 1022 1:13 / /run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-1029 1001 0:0 /snap /snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1030 1029 2:0 / /snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-1031 1029 2:1 / /snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-1032 1029 2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
-1033 1029 2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
-1034 1029 2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
-1035 1001 1:14 / /sys rw,nosuid,nodev,noexec,relatime master:21 - sysfs sysfs rw
-1036 1035 1:15 / /sys/fs/cgroup rw master:22 - tmpfs tmpfs rw,mode=755
-1037 1036 1:16 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:23 - cgroup cgroup rw,blkio
-1038 1036 1:17 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:24 - cgroup cgroup rw,cpu,cpuacct
-1039 1036 1:18 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:25 - cgroup cgroup rw,cpuset,clone_children
-1040 1036 1:19 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:26 - cgroup cgroup rw,devices
-1041 1036 1:20 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:27 - cgroup cgroup rw,freezer
-1042 1036 1:21 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:28 - cgroup cgroup rw,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb
-1043 1036 1:22 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:29 - cgroup cgroup rw,memory
-1044 1036 1:23 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:30 - cgroup cgroup rw,net_cls,net_prio
-1045 1036 1:24 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:31 - cgroup cgroup rw,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event
-1046 1036 1:25 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:32 - cgroup cgroup rw,pids,release_agent=/run/cgmanager/agents/cgm-release-agent.pids
-1047 1036 1:26 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime master:33 - cgroup cgroup rw,rdma,release_agent=/run/cgmanager/agents/cgm-release-agent.rdma
-1048 1036 1:27 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:34 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
-1049 1035 1:28 / /sys/fs/fuse/connections rw,relatime master:35 - fusectl fusectl rw
-1050 1035 1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:36 - pstore pstore rw
-1051 1035 1:30 / /sys/kernel/config rw,relatime master:37 - configfs configfs rw
-1052 1035 1:31 / /sys/kernel/debug rw,relatime master:38 - debugfs debugfs rw
-1053 1035 1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:39 - securityfs securityfs rw
-1054 1001 0:0 /tmp /tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1055 1054 0:0 /tmp/snap.test-snapd-mountinfo-core16/tmp /tmp rw,relatime - ext4 /dev/sda1 rw,data=ordered
-1056 1001 0:0 /usr/src /usr/src rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1057 1001 0:0 /var/lib/snapd /var/lib/snapd rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1059 1058 0:0 / /var/lib/snapd/hostfs rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1058 1057 0:0 /var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda1 rw,data=ordered
-1060 1059 1:9 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1061 1060 1:10 / /var/lib/snapd/hostfs/run/cgmanager/fs rw,relatime master:12 - tmpfs cgmfs rw,size=VARIABLE,mode=755
-1062 1060 1:11 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
-1063 1060 1:12 / /var/lib/snapd/hostfs/run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
-1064 1060 1:9 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1065 1060 1:13 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-1066 1059 2:0 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-1067 1059 2:1 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-1068 1059 2:2 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
-1069 1059 2:3 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
-1070 1059 2:4 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
-1071 1001 0:0 /var/log /var/log rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1072 1001 0:0 /var/snap /var/snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1073 1001 0:0 /var/tmp /var/tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+2:0 / / ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+1:0 / /dev rw,nosuid,relatime master:2 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
+1:1 / /dev/hugepages rw,relatime master:3 - hugetlbfs hugetlbfs rw,pagesize=2M
+1:2 / /dev/mqueue rw,relatime master:4 - mqueue mqueue rw
+1:33 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+1:3 / /dev/pts rw,nosuid,noexec,relatime master:5 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
+1:33 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+1:4 / /dev/shm rw,nosuid,nodev master:6 - tmpfs tmpfs rw
+0:0 /etc /etc rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+2:0 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+2:0 /etc/ssl /etc/ssl ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+0:0 /home /home rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /lib/firmware /lib/firmware rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /lib/modules /lib/modules rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /media /media rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /mnt /mnt rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+1:5 / /proc rw,nosuid,nodev,noexec,relatime master:7 - proc proc rw
+1:6 / /proc/fs/nfsd rw,relatime master:8 - nfsd nfsd rw
+1:7 / /proc/sys/fs/binfmt_misc rw,relatime master:9 - binfmt_misc binfmt_misc rw
+1:8 / /proc/sys/fs/binfmt_misc rw,relatime master:10 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
+0:0 /root /root rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+1:9 / /run rw,nosuid,noexec,relatime master:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:10 / /run/cgmanager/fs rw,relatime master:12 - tmpfs cgmfs rw,size=VARIABLE,mode=755
+1:11 / /run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
+1:9 /netns /run/netns rw,nosuid,noexec,relatime shared:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:12 / /run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
+1:9 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:13 / /run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+0:0 /snap /snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+2:0 / /snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+2:1 / /snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
+2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
+2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
+1:14 / /sys rw,nosuid,nodev,noexec,relatime master:21 - sysfs sysfs rw
+1:15 / /sys/fs/cgroup rw master:22 - tmpfs tmpfs rw,mode=755
+1:16 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:23 - cgroup cgroup rw,blkio
+1:17 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:24 - cgroup cgroup rw,cpu,cpuacct
+1:18 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:25 - cgroup cgroup rw,cpuset
+1:19 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:26 - cgroup cgroup rw,devices
+1:20 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:27 - cgroup cgroup rw,freezer
+1:21 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:28 - cgroup cgroup rw,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb
+1:22 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:29 - cgroup cgroup rw,memory
+1:23 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:30 - cgroup cgroup rw,net_cls,net_prio
+1:24 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:31 - cgroup cgroup rw,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event
+1:25 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:32 - cgroup cgroup rw,pids,release_agent=/run/cgmanager/agents/cgm-release-agent.pids
+1:26 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime master:33 - cgroup cgroup rw,rdma,release_agent=/run/cgmanager/agents/cgm-release-agent.rdma
+1:27 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:34 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
+1:28 / /sys/fs/fuse/connections rw,relatime master:35 - fusectl fusectl rw
+1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:36 - pstore pstore rw
+1:30 / /sys/kernel/config rw,relatime master:37 - configfs configfs rw
+1:31 / /sys/kernel/debug rw,relatime master:38 - debugfs debugfs rw
+1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:39 - securityfs securityfs rw
+0:0 /tmp /tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /tmp/snap.test-snapd-mountinfo-core16/tmp /tmp rw,relatime - ext4 /dev/sda1 rw,data=ordered
+1:34 / /usr/share/gdb rw,relatime - tmpfs tmpfs rw,mode=755
+2:0 /usr/share/gdb/auto-load /usr/share/gdb/auto-load ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+1:35 / /usr/share/gdb/test rw,relatime - tmpfs tmpfs rw
+0:0 /usr/src /usr/src rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/lib/snapd /var/lib/snapd rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 / /var/lib/snapd/hostfs rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda1 rw,data=ordered
+1:9 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:10 / /var/lib/snapd/hostfs/run/cgmanager/fs rw,relatime master:12 - tmpfs cgmfs rw,size=VARIABLE,mode=755
+1:11 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
+1:12 / /var/lib/snapd/hostfs/run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
+1:9 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:13 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+2:0 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+2:1 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+2:2 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
+2:3 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
+2:4 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
+0:0 /var/log /var/log rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/snap /var/snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/tmp /var/tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-16.04-64/PER-SNAP-18.expected.txt snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-16.04-64/PER-SNAP-18.expected.txt
--- snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-16.04-64/PER-SNAP-18.expected.txt 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-16.04-64/PER-SNAP-18.expected.txt 2019-10-30 12:17:43.000000000 +0000
@@ -1,74 +1,77 @@
-1001 1000 2:1 / / ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-1002 1001 1:0 / /dev rw,nosuid,relatime master:2 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
-1003 1002 1:1 / /dev/hugepages rw,relatime master:3 - hugetlbfs hugetlbfs rw,pagesize=2M
-1004 1002 1:2 / /dev/mqueue rw,relatime master:4 - mqueue mqueue rw
-1005 1002 1:33 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-1006 1002 1:3 / /dev/pts rw,nosuid,noexec,relatime master:5 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
-1007 1006 1:33 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-1008 1002 1:4 / /dev/shm rw,nosuid,nodev master:6 - tmpfs tmpfs rw
-1009 1001 0:0 /etc /etc rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1010 1009 2:1 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-1011 1009 2:1 /etc/ssl /etc/ssl ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-1012 1001 0:0 /home /home rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1013 1001 0:0 /lib/firmware /lib/firmware rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1014 1001 0:0 /lib/modules /lib/modules rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1015 1001 0:0 /media /media rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
-1016 1001 0:0 /mnt /mnt rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1017 1001 1:5 / /proc rw,nosuid,nodev,noexec,relatime master:7 - proc proc rw
-1018 1017 1:6 / /proc/fs/nfsd rw,relatime master:8 - nfsd nfsd rw
-1020 1019 1:7 / /proc/sys/fs/binfmt_misc rw,relatime master:9 - binfmt_misc binfmt_misc rw
-1019 1017 1:8 / /proc/sys/fs/binfmt_misc rw,relatime master:10 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
-1021 1001 0:0 /root /root rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1022 1001 1:9 / /run rw,nosuid,noexec,relatime master:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1023 1022 1:10 / /run/cgmanager/fs rw,relatime master:12 - tmpfs cgmfs rw,size=VARIABLE,mode=755
-1024 1022 1:11 / /run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
-1025 1022 1:9 /netns /run/netns rw,nosuid,noexec,relatime shared:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1026 1022 1:12 / /run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
-1027 1022 1:9 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1028 1022 1:13 / /run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-1029 1001 0:0 /snap /snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1030 1029 2:0 / /snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-1031 1029 2:1 / /snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-1032 1029 2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
-1033 1029 2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
-1034 1029 2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
-1035 1001 1:14 / /sys rw,nosuid,nodev,noexec,relatime master:21 - sysfs sysfs rw
-1036 1035 1:15 / /sys/fs/cgroup rw master:22 - tmpfs tmpfs rw,mode=755
-1037 1036 1:16 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:23 - cgroup cgroup rw,blkio
-1038 1036 1:17 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:24 - cgroup cgroup rw,cpu,cpuacct
-1039 1036 1:18 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:25 - cgroup cgroup rw,cpuset,clone_children
-1040 1036 1:19 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:26 - cgroup cgroup rw,devices
-1041 1036 1:20 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:27 - cgroup cgroup rw,freezer
-1042 1036 1:21 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:28 - cgroup cgroup rw,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb
-1043 1036 1:22 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:29 - cgroup cgroup rw,memory
-1044 1036 1:23 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:30 - cgroup cgroup rw,net_cls,net_prio
-1045 1036 1:24 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:31 - cgroup cgroup rw,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event
-1046 1036 1:25 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:32 - cgroup cgroup rw,pids,release_agent=/run/cgmanager/agents/cgm-release-agent.pids
-1047 1036 1:26 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime master:33 - cgroup cgroup rw,rdma,release_agent=/run/cgmanager/agents/cgm-release-agent.rdma
-1048 1036 1:27 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:34 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
-1049 1035 1:28 / /sys/fs/fuse/connections rw,relatime master:35 - fusectl fusectl rw
-1050 1035 1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:36 - pstore pstore rw
-1051 1035 1:30 / /sys/kernel/config rw,relatime master:37 - configfs configfs rw
-1052 1035 1:31 / /sys/kernel/debug rw,relatime master:38 - debugfs debugfs rw
-1053 1035 1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:39 - securityfs securityfs rw
-1054 1001 0:0 /tmp /tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1055 1054 0:0 /tmp/snap.test-snapd-mountinfo-core18/tmp /tmp rw,relatime - ext4 /dev/sda1 rw,data=ordered
-1056 1001 2:0 /usr/lib/snapd /usr/lib/snapd ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-1057 1001 0:0 /usr/src /usr/src rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1058 1001 0:0 /var/lib/snapd /var/lib/snapd rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1060 1059 0:0 / /var/lib/snapd/hostfs rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1059 1058 0:0 /var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda1 rw,data=ordered
-1061 1060 1:9 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1062 1061 1:10 / /var/lib/snapd/hostfs/run/cgmanager/fs rw,relatime master:12 - tmpfs cgmfs rw,size=VARIABLE,mode=755
-1063 1061 1:11 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
-1064 1061 1:12 / /var/lib/snapd/hostfs/run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
-1065 1061 1:9 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1066 1061 1:13 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-1067 1060 2:0 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-1068 1060 2:1 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-1069 1060 2:2 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
-1070 1060 2:3 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
-1071 1060 2:4 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
-1072 1001 0:0 /var/log /var/log rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1073 1001 0:0 /var/snap /var/snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1074 1001 0:0 /var/tmp /var/tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+2:1 / / ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+1:0 / /dev rw,nosuid,relatime master:2 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
+1:1 / /dev/hugepages rw,relatime master:3 - hugetlbfs hugetlbfs rw,pagesize=2M
+1:2 / /dev/mqueue rw,relatime master:4 - mqueue mqueue rw
+1:33 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+1:3 / /dev/pts rw,nosuid,noexec,relatime master:5 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
+1:33 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+1:4 / /dev/shm rw,nosuid,nodev master:6 - tmpfs tmpfs rw
+0:0 /etc /etc rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+2:1 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+2:1 /etc/ssl /etc/ssl ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+0:0 /home /home rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /lib/firmware /lib/firmware rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /lib/modules /lib/modules rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /media /media rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /mnt /mnt rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+1:5 / /proc rw,nosuid,nodev,noexec,relatime master:7 - proc proc rw
+1:6 / /proc/fs/nfsd rw,relatime master:8 - nfsd nfsd rw
+1:7 / /proc/sys/fs/binfmt_misc rw,relatime master:9 - binfmt_misc binfmt_misc rw
+1:8 / /proc/sys/fs/binfmt_misc rw,relatime master:10 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
+0:0 /root /root rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+1:9 / /run rw,nosuid,noexec,relatime master:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:10 / /run/cgmanager/fs rw,relatime master:12 - tmpfs cgmfs rw,size=VARIABLE,mode=755
+1:11 / /run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
+1:9 /netns /run/netns rw,nosuid,noexec,relatime shared:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:12 / /run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
+1:9 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:13 / /run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+0:0 /snap /snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+2:0 / /snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+2:1 / /snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
+2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
+2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
+1:14 / /sys rw,nosuid,nodev,noexec,relatime master:21 - sysfs sysfs rw
+1:15 / /sys/fs/cgroup rw master:22 - tmpfs tmpfs rw,mode=755
+1:16 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:23 - cgroup cgroup rw,blkio
+1:17 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:24 - cgroup cgroup rw,cpu,cpuacct
+1:18 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:25 - cgroup cgroup rw,cpuset
+1:19 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:26 - cgroup cgroup rw,devices
+1:20 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:27 - cgroup cgroup rw,freezer
+1:21 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:28 - cgroup cgroup rw,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb
+1:22 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:29 - cgroup cgroup rw,memory
+1:23 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:30 - cgroup cgroup rw,net_cls,net_prio
+1:24 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:31 - cgroup cgroup rw,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event
+1:25 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:32 - cgroup cgroup rw,pids,release_agent=/run/cgmanager/agents/cgm-release-agent.pids
+1:26 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime master:33 - cgroup cgroup rw,rdma,release_agent=/run/cgmanager/agents/cgm-release-agent.rdma
+1:27 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:34 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
+1:28 / /sys/fs/fuse/connections rw,relatime master:35 - fusectl fusectl rw
+1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:36 - pstore pstore rw
+1:30 / /sys/kernel/config rw,relatime master:37 - configfs configfs rw
+1:31 / /sys/kernel/debug rw,relatime master:38 - debugfs debugfs rw
+1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:39 - securityfs securityfs rw
+0:0 /tmp /tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /tmp/snap.test-snapd-mountinfo-core18/tmp /tmp rw,relatime - ext4 /dev/sda1 rw,data=ordered
+2:0 /usr/lib/snapd /usr/lib/snapd ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+1:34 / /usr/share/gdb rw,relatime - tmpfs tmpfs rw,mode=755
+2:1 /usr/share/gdb/auto-load /usr/share/gdb/auto-load ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+1:35 / /usr/share/gdb/test rw,relatime - tmpfs tmpfs rw
+0:0 /usr/src /usr/src rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/lib/snapd /var/lib/snapd rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 / /var/lib/snapd/hostfs rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda1 rw,data=ordered
+1:9 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:10 / /var/lib/snapd/hostfs/run/cgmanager/fs rw,relatime master:12 - tmpfs cgmfs rw,size=VARIABLE,mode=755
+1:11 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
+1:12 / /var/lib/snapd/hostfs/run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
+1:9 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:13 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+2:0 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+2:1 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+2:2 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
+2:3 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
+2:4 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
+0:0 /var/log /var/log rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/snap /var/snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/tmp /var/tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-16.04-64/PER-SNAP-C7.expected.txt snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-16.04-64/PER-SNAP-C7.expected.txt
--- snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-16.04-64/PER-SNAP-C7.expected.txt 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-16.04-64/PER-SNAP-C7.expected.txt 2019-10-30 12:17:43.000000000 +0000
@@ -1,39 +1,39 @@
-1 0 0:0 / / rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
-2 1 1:0 / /dev rw,nosuid,relatime shared:2 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
-3 2 1:1 / /dev/hugepages rw,relatime shared:3 - hugetlbfs hugetlbfs rw,pagesize=2M
-4 2 1:2 / /dev/mqueue rw,relatime shared:4 - mqueue mqueue rw
-5 2 1:3 / /dev/pts rw,nosuid,noexec,relatime shared:5 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
-6 2 1:4 / /dev/shm rw,nosuid,nodev shared:6 - tmpfs tmpfs rw
-7 1 1:5 / /proc rw,nosuid,nodev,noexec,relatime shared:7 - proc proc rw
-8 7 1:6 / /proc/fs/nfsd rw,relatime shared:8 - nfsd nfsd rw
-10 9 1:7 / /proc/sys/fs/binfmt_misc rw,relatime shared:9 - binfmt_misc binfmt_misc rw
-9 7 1:8 / /proc/sys/fs/binfmt_misc rw,relatime shared:10 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
-11 1 1:9 / /run rw,nosuid,noexec,relatime shared:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-12 11 1:10 / /run/cgmanager/fs rw,relatime shared:12 - tmpfs cgmfs rw,size=VARIABLE,mode=755
-13 11 1:11 / /run/lock rw,nosuid,nodev,noexec,relatime shared:13 - tmpfs tmpfs rw,size=VARIABLE
-14 11 1:12 / /run/rpc_pipefs rw,relatime shared:14 - rpc_pipefs sunrpc rw
-15 11 1:13 / /run/user/0 rw,nosuid,nodev,relatime shared:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-16 1 2:0 / /snap/core/1 ro,nodev,relatime shared:16 - squashfs /dev/loop0 ro
-17 1 2:1 / /snap/core18/1 ro,nodev,relatime shared:17 - squashfs /dev/loop1 ro
-18 1 2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime shared:18 - squashfs /dev/loop2 ro
-19 1 2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime shared:19 - squashfs /dev/loop3 ro
-20 1 2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime shared:20 - squashfs /dev/loop4 ro
-21 1 1:14 / /sys rw,nosuid,nodev,noexec,relatime shared:21 - sysfs sysfs rw
-22 21 1:15 / /sys/fs/cgroup rw shared:22 - tmpfs tmpfs rw,mode=755
-23 22 1:16 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:23 - cgroup cgroup rw,blkio
-24 22 1:17 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:24 - cgroup cgroup rw,cpu,cpuacct
-25 22 1:18 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:25 - cgroup cgroup rw,cpuset,clone_children
-26 22 1:19 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:26 - cgroup cgroup rw,devices
-27 22 1:20 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:27 - cgroup cgroup rw,freezer
-28 22 1:21 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:28 - cgroup cgroup rw,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb
-29 22 1:22 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:29 - cgroup cgroup rw,memory
-30 22 1:23 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:30 - cgroup cgroup rw,net_cls,net_prio
-31 22 1:24 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:31 - cgroup cgroup rw,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event
-32 22 1:25 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:32 - cgroup cgroup rw,pids,release_agent=/run/cgmanager/agents/cgm-release-agent.pids
-33 22 1:26 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime shared:33 - cgroup cgroup rw,rdma,release_agent=/run/cgmanager/agents/cgm-release-agent.rdma
-34 22 1:27 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:34 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
-35 21 1:28 / /sys/fs/fuse/connections rw,relatime shared:35 - fusectl fusectl rw
-36 21 1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime shared:36 - pstore pstore rw
-37 21 1:30 / /sys/kernel/config rw,relatime shared:37 - configfs configfs rw
-38 21 1:31 / /sys/kernel/debug rw,relatime shared:38 - debugfs debugfs rw
-39 21 1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:39 - securityfs securityfs rw
+0:0 / / rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
+1:0 / /dev rw,nosuid,relatime shared:2 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
+1:1 / /dev/hugepages rw,relatime shared:3 - hugetlbfs hugetlbfs rw,pagesize=2M
+1:2 / /dev/mqueue rw,relatime shared:4 - mqueue mqueue rw
+1:3 / /dev/pts rw,nosuid,noexec,relatime shared:5 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
+1:4 / /dev/shm rw,nosuid,nodev shared:6 - tmpfs tmpfs rw
+1:5 / /proc rw,nosuid,nodev,noexec,relatime shared:7 - proc proc rw
+1:6 / /proc/fs/nfsd rw,relatime shared:8 - nfsd nfsd rw
+1:7 / /proc/sys/fs/binfmt_misc rw,relatime shared:9 - binfmt_misc binfmt_misc rw
+1:8 / /proc/sys/fs/binfmt_misc rw,relatime shared:10 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
+1:9 / /run rw,nosuid,noexec,relatime shared:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:10 / /run/cgmanager/fs rw,relatime shared:12 - tmpfs cgmfs rw,size=VARIABLE,mode=755
+1:11 / /run/lock rw,nosuid,nodev,noexec,relatime shared:13 - tmpfs tmpfs rw,size=VARIABLE
+1:12 / /run/rpc_pipefs rw,relatime shared:14 - rpc_pipefs sunrpc rw
+1:13 / /run/user/0 rw,nosuid,nodev,relatime shared:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+2:0 / /snap/core/1 ro,nodev,relatime shared:16 - squashfs /dev/loop0 ro
+2:1 / /snap/core18/1 ro,nodev,relatime shared:17 - squashfs /dev/loop1 ro
+2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime shared:18 - squashfs /dev/loop2 ro
+2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime shared:19 - squashfs /dev/loop3 ro
+2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime shared:20 - squashfs /dev/loop4 ro
+1:14 / /sys rw,nosuid,nodev,noexec,relatime shared:21 - sysfs sysfs rw
+1:15 / /sys/fs/cgroup rw shared:22 - tmpfs tmpfs rw,mode=755
+1:16 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:23 - cgroup cgroup rw,blkio
+1:17 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:24 - cgroup cgroup rw,cpu,cpuacct
+1:18 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:25 - cgroup cgroup rw,cpuset
+1:19 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:26 - cgroup cgroup rw,devices
+1:20 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:27 - cgroup cgroup rw,freezer
+1:21 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:28 - cgroup cgroup rw,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb
+1:22 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:29 - cgroup cgroup rw,memory
+1:23 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:30 - cgroup cgroup rw,net_cls,net_prio
+1:24 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:31 - cgroup cgroup rw,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event
+1:25 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:32 - cgroup cgroup rw,pids,release_agent=/run/cgmanager/agents/cgm-release-agent.pids
+1:26 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime shared:33 - cgroup cgroup rw,rdma,release_agent=/run/cgmanager/agents/cgm-release-agent.rdma
+1:27 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:34 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
+1:28 / /sys/fs/fuse/connections rw,relatime shared:35 - fusectl fusectl rw
+1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime shared:36 - pstore pstore rw
+1:30 / /sys/kernel/config rw,relatime shared:37 - configfs configfs rw
+1:31 / /sys/kernel/debug rw,relatime shared:38 - debugfs debugfs rw
+1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:39 - securityfs securityfs rw
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-16.04-64/PER-USER-16.expected.txt snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-16.04-64/PER-USER-16.expected.txt
--- snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-16.04-64/PER-USER-16.expected.txt 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-16.04-64/PER-USER-16.expected.txt 2019-10-30 12:17:43.000000000 +0000
@@ -1,73 +1,76 @@
-2001 2000 2:0 / / ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-2002 2001 1:0 / /dev rw,nosuid,relatime master:2 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
-2003 2002 1:1 / /dev/hugepages rw,relatime master:3 - hugetlbfs hugetlbfs rw,pagesize=2M
-2004 2002 1:2 / /dev/mqueue rw,relatime master:4 - mqueue mqueue rw
-2005 2002 1:33 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-2006 2002 1:3 / /dev/pts rw,nosuid,noexec,relatime master:5 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
-2007 2006 1:33 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-2008 2002 1:4 / /dev/shm rw,nosuid,nodev master:6 - tmpfs tmpfs rw
-2009 2001 0:0 /etc /etc rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2010 2009 2:0 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-2011 2009 2:0 /etc/ssl /etc/ssl ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-2012 2001 0:0 /home /home rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2013 2001 0:0 /lib/firmware /lib/firmware rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2014 2001 0:0 /lib/modules /lib/modules rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2015 2001 0:0 /media /media rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
-2016 2001 0:0 /mnt /mnt rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2017 2001 1:5 / /proc rw,nosuid,nodev,noexec,relatime master:7 - proc proc rw
-2018 2017 1:6 / /proc/fs/nfsd rw,relatime master:8 - nfsd nfsd rw
-2020 2019 1:7 / /proc/sys/fs/binfmt_misc rw,relatime master:9 - binfmt_misc binfmt_misc rw
-2019 2017 1:8 / /proc/sys/fs/binfmt_misc rw,relatime master:10 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
-2021 2001 0:0 /root /root rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2022 2001 1:9 / /run rw,nosuid,noexec,relatime master:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2023 2022 1:10 / /run/cgmanager/fs rw,relatime master:12 - tmpfs cgmfs rw,size=VARIABLE,mode=755
-2024 2022 1:11 / /run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
-2025 2022 1:9 /netns /run/netns rw,nosuid,noexec,relatime shared:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2026 2022 1:12 / /run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
-2027 2022 1:9 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2028 2022 1:13 / /run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-2029 2001 0:0 /snap /snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2030 2029 2:0 / /snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-2031 2029 2:1 / /snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-2032 2029 2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
-2033 2029 2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
-2034 2029 2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
-2035 2001 1:14 / /sys rw,nosuid,nodev,noexec,relatime master:21 - sysfs sysfs rw
-2036 2035 1:15 / /sys/fs/cgroup rw master:22 - tmpfs tmpfs rw,mode=755
-2037 2036 1:16 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:23 - cgroup cgroup rw,blkio
-2038 2036 1:17 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:24 - cgroup cgroup rw,cpu,cpuacct
-2039 2036 1:18 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:25 - cgroup cgroup rw,cpuset,clone_children
-2040 2036 1:19 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:26 - cgroup cgroup rw,devices
-2041 2036 1:20 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:27 - cgroup cgroup rw,freezer
-2042 2036 1:21 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:28 - cgroup cgroup rw,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb
-2043 2036 1:22 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:29 - cgroup cgroup rw,memory
-2044 2036 1:23 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:30 - cgroup cgroup rw,net_cls,net_prio
-2045 2036 1:24 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:31 - cgroup cgroup rw,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event
-2046 2036 1:25 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:32 - cgroup cgroup rw,pids,release_agent=/run/cgmanager/agents/cgm-release-agent.pids
-2047 2036 1:26 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime master:33 - cgroup cgroup rw,rdma,release_agent=/run/cgmanager/agents/cgm-release-agent.rdma
-2048 2036 1:27 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:34 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
-2049 2035 1:28 / /sys/fs/fuse/connections rw,relatime master:35 - fusectl fusectl rw
-2050 2035 1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:36 - pstore pstore rw
-2051 2035 1:30 / /sys/kernel/config rw,relatime master:37 - configfs configfs rw
-2052 2035 1:31 / /sys/kernel/debug rw,relatime master:38 - debugfs debugfs rw
-2053 2035 1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:39 - securityfs securityfs rw
-2054 2001 0:0 /tmp /tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2055 2054 0:0 /tmp/snap.test-snapd-mountinfo-core16/tmp /tmp rw,relatime - ext4 /dev/sda1 rw,data=ordered
-2056 2001 0:0 /usr/src /usr/src rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2057 2001 0:0 /var/lib/snapd /var/lib/snapd rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2058 2057 0:0 /var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda1 rw,data=ordered
-2059 2058 0:0 / /var/lib/snapd/hostfs rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2060 2059 1:9 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2061 2060 1:10 / /var/lib/snapd/hostfs/run/cgmanager/fs rw,relatime master:12 - tmpfs cgmfs rw,size=VARIABLE,mode=755
-2062 2060 1:11 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
-2063 2060 1:12 / /var/lib/snapd/hostfs/run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
-2064 2060 1:9 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2065 2060 1:13 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-2066 2059 2:0 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-2067 2059 2:1 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-2068 2059 2:2 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
-2069 2059 2:3 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
-2070 2059 2:4 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
-2071 2001 0:0 /var/log /var/log rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2072 2001 0:0 /var/snap /var/snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2073 2001 0:0 /var/tmp /var/tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+2:0 / / ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+1:0 / /dev rw,nosuid,relatime master:2 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
+1:1 / /dev/hugepages rw,relatime master:3 - hugetlbfs hugetlbfs rw,pagesize=2M
+1:2 / /dev/mqueue rw,relatime master:4 - mqueue mqueue rw
+1:33 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+1:3 / /dev/pts rw,nosuid,noexec,relatime master:5 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
+1:33 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+1:4 / /dev/shm rw,nosuid,nodev master:6 - tmpfs tmpfs rw
+0:0 /etc /etc rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+2:0 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+2:0 /etc/ssl /etc/ssl ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+0:0 /home /home rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /lib/firmware /lib/firmware rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /lib/modules /lib/modules rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /media /media rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /mnt /mnt rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+1:5 / /proc rw,nosuid,nodev,noexec,relatime master:7 - proc proc rw
+1:6 / /proc/fs/nfsd rw,relatime master:8 - nfsd nfsd rw
+1:7 / /proc/sys/fs/binfmt_misc rw,relatime master:9 - binfmt_misc binfmt_misc rw
+1:8 / /proc/sys/fs/binfmt_misc rw,relatime master:10 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
+0:0 /root /root rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+1:9 / /run rw,nosuid,noexec,relatime master:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:10 / /run/cgmanager/fs rw,relatime master:12 - tmpfs cgmfs rw,size=VARIABLE,mode=755
+1:11 / /run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
+1:9 /netns /run/netns rw,nosuid,noexec,relatime shared:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:12 / /run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
+1:9 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:13 / /run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+0:0 /snap /snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+2:0 / /snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+2:1 / /snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
+2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
+2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
+1:14 / /sys rw,nosuid,nodev,noexec,relatime master:21 - sysfs sysfs rw
+1:15 / /sys/fs/cgroup rw master:22 - tmpfs tmpfs rw,mode=755
+1:16 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:23 - cgroup cgroup rw,blkio
+1:17 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:24 - cgroup cgroup rw,cpu,cpuacct
+1:18 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:25 - cgroup cgroup rw,cpuset
+1:19 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:26 - cgroup cgroup rw,devices
+1:20 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:27 - cgroup cgroup rw,freezer
+1:21 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:28 - cgroup cgroup rw,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb
+1:22 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:29 - cgroup cgroup rw,memory
+1:23 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:30 - cgroup cgroup rw,net_cls,net_prio
+1:24 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:31 - cgroup cgroup rw,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event
+1:25 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:32 - cgroup cgroup rw,pids,release_agent=/run/cgmanager/agents/cgm-release-agent.pids
+1:26 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime master:33 - cgroup cgroup rw,rdma,release_agent=/run/cgmanager/agents/cgm-release-agent.rdma
+1:27 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:34 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
+1:28 / /sys/fs/fuse/connections rw,relatime master:35 - fusectl fusectl rw
+1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:36 - pstore pstore rw
+1:30 / /sys/kernel/config rw,relatime master:37 - configfs configfs rw
+1:31 / /sys/kernel/debug rw,relatime master:38 - debugfs debugfs rw
+1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:39 - securityfs securityfs rw
+0:0 /tmp /tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /tmp/snap.test-snapd-mountinfo-core16/tmp /tmp rw,relatime - ext4 /dev/sda1 rw,data=ordered
+1:34 / /usr/share/gdb rw,relatime - tmpfs tmpfs rw,mode=755
+2:0 /usr/share/gdb/auto-load /usr/share/gdb/auto-load ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+1:35 / /usr/share/gdb/test rw,relatime - tmpfs tmpfs rw
+0:0 /usr/src /usr/src rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/lib/snapd /var/lib/snapd rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda1 rw,data=ordered
+0:0 / /var/lib/snapd/hostfs rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+1:9 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:10 / /var/lib/snapd/hostfs/run/cgmanager/fs rw,relatime master:12 - tmpfs cgmfs rw,size=VARIABLE,mode=755
+1:11 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
+1:12 / /var/lib/snapd/hostfs/run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
+1:9 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:13 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+2:0 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+2:1 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+2:2 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
+2:3 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
+2:4 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
+0:0 /var/log /var/log rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/snap /var/snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/tmp /var/tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-16.04-64/PER-USER-18.expected.txt snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-16.04-64/PER-USER-18.expected.txt
--- snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-16.04-64/PER-USER-18.expected.txt 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-16.04-64/PER-USER-18.expected.txt 2019-10-30 12:17:43.000000000 +0000
@@ -1,74 +1,77 @@
-2001 2000 2:1 / / ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-2002 2001 1:0 / /dev rw,nosuid,relatime master:2 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
-2003 2002 1:1 / /dev/hugepages rw,relatime master:3 - hugetlbfs hugetlbfs rw,pagesize=2M
-2004 2002 1:2 / /dev/mqueue rw,relatime master:4 - mqueue mqueue rw
-2005 2002 1:33 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-2006 2002 1:3 / /dev/pts rw,nosuid,noexec,relatime master:5 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
-2007 2006 1:33 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-2008 2002 1:4 / /dev/shm rw,nosuid,nodev master:6 - tmpfs tmpfs rw
-2009 2001 0:0 /etc /etc rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2010 2009 2:1 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-2011 2009 2:1 /etc/ssl /etc/ssl ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-2012 2001 0:0 /home /home rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2013 2001 0:0 /lib/firmware /lib/firmware rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2014 2001 0:0 /lib/modules /lib/modules rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2015 2001 0:0 /media /media rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
-2016 2001 0:0 /mnt /mnt rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2017 2001 1:5 / /proc rw,nosuid,nodev,noexec,relatime master:7 - proc proc rw
-2018 2017 1:6 / /proc/fs/nfsd rw,relatime master:8 - nfsd nfsd rw
-2020 2019 1:7 / /proc/sys/fs/binfmt_misc rw,relatime master:9 - binfmt_misc binfmt_misc rw
-2019 2017 1:8 / /proc/sys/fs/binfmt_misc rw,relatime master:10 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
-2021 2001 0:0 /root /root rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2022 2001 1:9 / /run rw,nosuid,noexec,relatime master:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2023 2022 1:10 / /run/cgmanager/fs rw,relatime master:12 - tmpfs cgmfs rw,size=VARIABLE,mode=755
-2024 2022 1:11 / /run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
-2025 2022 1:9 /netns /run/netns rw,nosuid,noexec,relatime shared:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2026 2022 1:12 / /run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
-2027 2022 1:9 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2028 2022 1:13 / /run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-2029 2001 0:0 /snap /snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2030 2029 2:0 / /snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-2031 2029 2:1 / /snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-2032 2029 2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
-2033 2029 2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
-2034 2029 2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
-2035 2001 1:14 / /sys rw,nosuid,nodev,noexec,relatime master:21 - sysfs sysfs rw
-2036 2035 1:15 / /sys/fs/cgroup rw master:22 - tmpfs tmpfs rw,mode=755
-2037 2036 1:16 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:23 - cgroup cgroup rw,blkio
-2038 2036 1:17 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:24 - cgroup cgroup rw,cpu,cpuacct
-2039 2036 1:18 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:25 - cgroup cgroup rw,cpuset,clone_children
-2040 2036 1:19 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:26 - cgroup cgroup rw,devices
-2041 2036 1:20 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:27 - cgroup cgroup rw,freezer
-2042 2036 1:21 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:28 - cgroup cgroup rw,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb
-2043 2036 1:22 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:29 - cgroup cgroup rw,memory
-2044 2036 1:23 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:30 - cgroup cgroup rw,net_cls,net_prio
-2045 2036 1:24 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:31 - cgroup cgroup rw,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event
-2046 2036 1:25 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:32 - cgroup cgroup rw,pids,release_agent=/run/cgmanager/agents/cgm-release-agent.pids
-2047 2036 1:26 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime master:33 - cgroup cgroup rw,rdma,release_agent=/run/cgmanager/agents/cgm-release-agent.rdma
-2048 2036 1:27 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:34 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
-2049 2035 1:28 / /sys/fs/fuse/connections rw,relatime master:35 - fusectl fusectl rw
-2050 2035 1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:36 - pstore pstore rw
-2051 2035 1:30 / /sys/kernel/config rw,relatime master:37 - configfs configfs rw
-2052 2035 1:31 / /sys/kernel/debug rw,relatime master:38 - debugfs debugfs rw
-2053 2035 1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:39 - securityfs securityfs rw
-2054 2001 0:0 /tmp /tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2055 2054 0:0 /tmp/snap.test-snapd-mountinfo-core18/tmp /tmp rw,relatime - ext4 /dev/sda1 rw,data=ordered
-2056 2001 2:0 /usr/lib/snapd /usr/lib/snapd ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-2057 2001 0:0 /usr/src /usr/src rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2058 2001 0:0 /var/lib/snapd /var/lib/snapd rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2059 2058 0:0 /var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda1 rw,data=ordered
-2060 2059 0:0 / /var/lib/snapd/hostfs rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2061 2060 1:9 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2062 2061 1:10 / /var/lib/snapd/hostfs/run/cgmanager/fs rw,relatime master:12 - tmpfs cgmfs rw,size=VARIABLE,mode=755
-2063 2061 1:11 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
-2064 2061 1:12 / /var/lib/snapd/hostfs/run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
-2065 2061 1:9 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2066 2061 1:13 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-2067 2060 2:0 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-2068 2060 2:1 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-2069 2060 2:2 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
-2070 2060 2:3 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
-2071 2060 2:4 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
-2072 2001 0:0 /var/log /var/log rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2073 2001 0:0 /var/snap /var/snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2074 2001 0:0 /var/tmp /var/tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+2:1 / / ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+1:0 / /dev rw,nosuid,relatime master:2 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
+1:1 / /dev/hugepages rw,relatime master:3 - hugetlbfs hugetlbfs rw,pagesize=2M
+1:2 / /dev/mqueue rw,relatime master:4 - mqueue mqueue rw
+1:33 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+1:3 / /dev/pts rw,nosuid,noexec,relatime master:5 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
+1:33 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+1:4 / /dev/shm rw,nosuid,nodev master:6 - tmpfs tmpfs rw
+0:0 /etc /etc rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+2:1 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+2:1 /etc/ssl /etc/ssl ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+0:0 /home /home rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /lib/firmware /lib/firmware rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /lib/modules /lib/modules rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /media /media rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /mnt /mnt rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+1:5 / /proc rw,nosuid,nodev,noexec,relatime master:7 - proc proc rw
+1:6 / /proc/fs/nfsd rw,relatime master:8 - nfsd nfsd rw
+1:7 / /proc/sys/fs/binfmt_misc rw,relatime master:9 - binfmt_misc binfmt_misc rw
+1:8 / /proc/sys/fs/binfmt_misc rw,relatime master:10 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
+0:0 /root /root rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+1:9 / /run rw,nosuid,noexec,relatime master:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:10 / /run/cgmanager/fs rw,relatime master:12 - tmpfs cgmfs rw,size=VARIABLE,mode=755
+1:11 / /run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
+1:9 /netns /run/netns rw,nosuid,noexec,relatime shared:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:12 / /run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
+1:9 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:13 / /run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+0:0 /snap /snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+2:0 / /snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+2:1 / /snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
+2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
+2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
+1:14 / /sys rw,nosuid,nodev,noexec,relatime master:21 - sysfs sysfs rw
+1:15 / /sys/fs/cgroup rw master:22 - tmpfs tmpfs rw,mode=755
+1:16 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:23 - cgroup cgroup rw,blkio
+1:17 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:24 - cgroup cgroup rw,cpu,cpuacct
+1:18 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:25 - cgroup cgroup rw,cpuset
+1:19 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:26 - cgroup cgroup rw,devices
+1:20 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:27 - cgroup cgroup rw,freezer
+1:21 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:28 - cgroup cgroup rw,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb
+1:22 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:29 - cgroup cgroup rw,memory
+1:23 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:30 - cgroup cgroup rw,net_cls,net_prio
+1:24 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:31 - cgroup cgroup rw,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event
+1:25 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:32 - cgroup cgroup rw,pids,release_agent=/run/cgmanager/agents/cgm-release-agent.pids
+1:26 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime master:33 - cgroup cgroup rw,rdma,release_agent=/run/cgmanager/agents/cgm-release-agent.rdma
+1:27 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:34 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
+1:28 / /sys/fs/fuse/connections rw,relatime master:35 - fusectl fusectl rw
+1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:36 - pstore pstore rw
+1:30 / /sys/kernel/config rw,relatime master:37 - configfs configfs rw
+1:31 / /sys/kernel/debug rw,relatime master:38 - debugfs debugfs rw
+1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:39 - securityfs securityfs rw
+0:0 /tmp /tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /tmp/snap.test-snapd-mountinfo-core18/tmp /tmp rw,relatime - ext4 /dev/sda1 rw,data=ordered
+2:0 /usr/lib/snapd /usr/lib/snapd ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+1:34 / /usr/share/gdb rw,relatime - tmpfs tmpfs rw,mode=755
+2:1 /usr/share/gdb/auto-load /usr/share/gdb/auto-load ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+1:35 / /usr/share/gdb/test rw,relatime - tmpfs tmpfs rw
+0:0 /usr/src /usr/src rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/lib/snapd /var/lib/snapd rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda1 rw,data=ordered
+0:0 / /var/lib/snapd/hostfs rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+1:9 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:10 / /var/lib/snapd/hostfs/run/cgmanager/fs rw,relatime master:12 - tmpfs cgmfs rw,size=VARIABLE,mode=755
+1:11 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
+1:12 / /var/lib/snapd/hostfs/run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
+1:9 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:13 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+2:0 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+2:1 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+2:2 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
+2:3 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
+2:4 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
+0:0 /var/log /var/log rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/snap /var/snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/tmp /var/tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-16.04-64/PER-USER-C7.expected.txt snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-16.04-64/PER-USER-C7.expected.txt
--- snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-16.04-64/PER-USER-C7.expected.txt 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-16.04-64/PER-USER-C7.expected.txt 2019-10-30 12:17:43.000000000 +0000
@@ -1,39 +1,39 @@
-1 0 0:0 / / rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
-2 1 1:0 / /dev rw,nosuid,relatime shared:2 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
-3 2 1:1 / /dev/hugepages rw,relatime shared:3 - hugetlbfs hugetlbfs rw,pagesize=2M
-4 2 1:2 / /dev/mqueue rw,relatime shared:4 - mqueue mqueue rw
-5 2 1:3 / /dev/pts rw,nosuid,noexec,relatime shared:5 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
-6 2 1:4 / /dev/shm rw,nosuid,nodev shared:6 - tmpfs tmpfs rw
-7 1 1:5 / /proc rw,nosuid,nodev,noexec,relatime shared:7 - proc proc rw
-8 7 1:6 / /proc/fs/nfsd rw,relatime shared:8 - nfsd nfsd rw
-10 9 1:7 / /proc/sys/fs/binfmt_misc rw,relatime shared:9 - binfmt_misc binfmt_misc rw
-9 7 1:8 / /proc/sys/fs/binfmt_misc rw,relatime shared:10 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
-11 1 1:9 / /run rw,nosuid,noexec,relatime shared:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-12 11 1:10 / /run/cgmanager/fs rw,relatime shared:12 - tmpfs cgmfs rw,size=VARIABLE,mode=755
-13 11 1:11 / /run/lock rw,nosuid,nodev,noexec,relatime shared:13 - tmpfs tmpfs rw,size=VARIABLE
-14 11 1:12 / /run/rpc_pipefs rw,relatime shared:14 - rpc_pipefs sunrpc rw
-15 11 1:13 / /run/user/0 rw,nosuid,nodev,relatime shared:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-16 1 2:0 / /snap/core/1 ro,nodev,relatime shared:16 - squashfs /dev/loop0 ro
-17 1 2:1 / /snap/core18/1 ro,nodev,relatime shared:17 - squashfs /dev/loop1 ro
-18 1 2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime shared:18 - squashfs /dev/loop2 ro
-19 1 2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime shared:19 - squashfs /dev/loop3 ro
-20 1 2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime shared:20 - squashfs /dev/loop4 ro
-21 1 1:14 / /sys rw,nosuid,nodev,noexec,relatime shared:21 - sysfs sysfs rw
-22 21 1:15 / /sys/fs/cgroup rw shared:22 - tmpfs tmpfs rw,mode=755
-23 22 1:16 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:23 - cgroup cgroup rw,blkio
-24 22 1:17 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:24 - cgroup cgroup rw,cpu,cpuacct
-25 22 1:18 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:25 - cgroup cgroup rw,cpuset,clone_children
-26 22 1:19 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:26 - cgroup cgroup rw,devices
-27 22 1:20 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:27 - cgroup cgroup rw,freezer
-28 22 1:21 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:28 - cgroup cgroup rw,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb
-29 22 1:22 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:29 - cgroup cgroup rw,memory
-30 22 1:23 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:30 - cgroup cgroup rw,net_cls,net_prio
-31 22 1:24 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:31 - cgroup cgroup rw,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event
-32 22 1:25 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:32 - cgroup cgroup rw,pids,release_agent=/run/cgmanager/agents/cgm-release-agent.pids
-33 22 1:26 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime shared:33 - cgroup cgroup rw,rdma,release_agent=/run/cgmanager/agents/cgm-release-agent.rdma
-34 22 1:27 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:34 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
-35 21 1:28 / /sys/fs/fuse/connections rw,relatime shared:35 - fusectl fusectl rw
-36 21 1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime shared:36 - pstore pstore rw
-37 21 1:30 / /sys/kernel/config rw,relatime shared:37 - configfs configfs rw
-38 21 1:31 / /sys/kernel/debug rw,relatime shared:38 - debugfs debugfs rw
-39 21 1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:39 - securityfs securityfs rw
+0:0 / / rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
+1:0 / /dev rw,nosuid,relatime shared:2 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
+1:1 / /dev/hugepages rw,relatime shared:3 - hugetlbfs hugetlbfs rw,pagesize=2M
+1:2 / /dev/mqueue rw,relatime shared:4 - mqueue mqueue rw
+1:3 / /dev/pts rw,nosuid,noexec,relatime shared:5 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
+1:4 / /dev/shm rw,nosuid,nodev shared:6 - tmpfs tmpfs rw
+1:5 / /proc rw,nosuid,nodev,noexec,relatime shared:7 - proc proc rw
+1:6 / /proc/fs/nfsd rw,relatime shared:8 - nfsd nfsd rw
+1:7 / /proc/sys/fs/binfmt_misc rw,relatime shared:9 - binfmt_misc binfmt_misc rw
+1:8 / /proc/sys/fs/binfmt_misc rw,relatime shared:10 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
+1:9 / /run rw,nosuid,noexec,relatime shared:11 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:10 / /run/cgmanager/fs rw,relatime shared:12 - tmpfs cgmfs rw,size=VARIABLE,mode=755
+1:11 / /run/lock rw,nosuid,nodev,noexec,relatime shared:13 - tmpfs tmpfs rw,size=VARIABLE
+1:12 / /run/rpc_pipefs rw,relatime shared:14 - rpc_pipefs sunrpc rw
+1:13 / /run/user/0 rw,nosuid,nodev,relatime shared:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+2:0 / /snap/core/1 ro,nodev,relatime shared:16 - squashfs /dev/loop0 ro
+2:1 / /snap/core18/1 ro,nodev,relatime shared:17 - squashfs /dev/loop1 ro
+2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime shared:18 - squashfs /dev/loop2 ro
+2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime shared:19 - squashfs /dev/loop3 ro
+2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime shared:20 - squashfs /dev/loop4 ro
+1:14 / /sys rw,nosuid,nodev,noexec,relatime shared:21 - sysfs sysfs rw
+1:15 / /sys/fs/cgroup rw shared:22 - tmpfs tmpfs rw,mode=755
+1:16 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:23 - cgroup cgroup rw,blkio
+1:17 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:24 - cgroup cgroup rw,cpu,cpuacct
+1:18 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:25 - cgroup cgroup rw,cpuset
+1:19 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:26 - cgroup cgroup rw,devices
+1:20 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:27 - cgroup cgroup rw,freezer
+1:21 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:28 - cgroup cgroup rw,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb
+1:22 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:29 - cgroup cgroup rw,memory
+1:23 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:30 - cgroup cgroup rw,net_cls,net_prio
+1:24 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:31 - cgroup cgroup rw,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event
+1:25 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:32 - cgroup cgroup rw,pids,release_agent=/run/cgmanager/agents/cgm-release-agent.pids
+1:26 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime shared:33 - cgroup cgroup rw,rdma,release_agent=/run/cgmanager/agents/cgm-release-agent.rdma
+1:27 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:34 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
+1:28 / /sys/fs/fuse/connections rw,relatime shared:35 - fusectl fusectl rw
+1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime shared:36 - pstore pstore rw
+1:30 / /sys/kernel/config rw,relatime shared:37 - configfs configfs rw
+1:31 / /sys/kernel/debug rw,relatime shared:38 - debugfs debugfs rw
+1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:39 - securityfs securityfs rw
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-18.04-64/HOST.expected.txt snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-18.04-64/HOST.expected.txt
--- snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-18.04-64/HOST.expected.txt 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-18.04-64/HOST.expected.txt 2019-10-30 12:17:43.000000000 +0000
@@ -1,40 +1,40 @@
-1 0 0:0 / / rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
-2 1 0:1 / /boot/efi rw,relatime shared:2 - vfat /dev/sda15 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-3 1 1:0 / /dev rw,nosuid,relatime shared:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
-4 3 1:1 / /dev/hugepages rw,relatime shared:4 - hugetlbfs hugetlbfs rw,pagesize=2M
-5 3 1:2 / /dev/mqueue rw,relatime shared:5 - mqueue mqueue rw
-6 3 1:3 / /dev/pts rw,nosuid,noexec,relatime shared:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
-7 3 1:4 / /dev/shm rw,nosuid,nodev shared:7 - tmpfs tmpfs rw
-8 1 1:5 / /proc rw,nosuid,nodev,noexec,relatime shared:8 - proc proc rw
-9 8 1:6 / /proc/fs/nfsd rw,relatime shared:9 - nfsd nfsd rw
-11 10 1:7 / /proc/sys/fs/binfmt_misc rw,relatime shared:10 - binfmt_misc binfmt_misc rw
-10 8 1:8 / /proc/sys/fs/binfmt_misc rw,relatime shared:11 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
-12 1 1:9 / /run rw,nosuid,noexec,relatime shared:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-13 12 1:10 / /run/lock rw,nosuid,nodev,noexec,relatime shared:13 - tmpfs tmpfs rw,size=VARIABLE
-14 12 1:11 / /run/rpc_pipefs rw,relatime shared:14 - rpc_pipefs sunrpc rw
-15 12 1:12 / /run/user/0 rw,nosuid,nodev,relatime shared:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-16 1 2:0 / /snap/core/1 ro,nodev,relatime shared:16 - squashfs /dev/loop0 ro
-17 1 2:1 / /snap/core18/1 ro,nodev,relatime shared:17 - squashfs /dev/loop1 ro
-18 1 2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime shared:18 - squashfs /dev/loop2 ro
-19 1 2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime shared:19 - squashfs /dev/loop3 ro
-20 1 2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime shared:20 - squashfs /dev/loop4 ro
-21 1 1:13 / /sys rw,nosuid,nodev,noexec,relatime shared:21 - sysfs sysfs rw
-22 21 1:14 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:22 - tmpfs tmpfs ro,mode=755
-23 22 1:15 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:23 - cgroup cgroup rw,blkio
-24 22 1:16 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:24 - cgroup cgroup rw,cpu,cpuacct
-25 22 1:17 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:25 - cgroup cgroup rw,cpuset
-26 22 1:18 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:26 - cgroup cgroup rw,devices
-27 22 1:19 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:27 - cgroup cgroup rw,freezer
-28 22 1:20 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:28 - cgroup cgroup rw,hugetlb
-29 22 1:21 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:29 - cgroup cgroup rw,memory
-30 22 1:22 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:30 - cgroup cgroup rw,net_cls,net_prio
-31 22 1:23 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:31 - cgroup cgroup rw,perf_event
-32 22 1:24 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:32 - cgroup cgroup rw,pids
-33 22 1:25 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime shared:33 - cgroup cgroup rw,rdma
-34 22 1:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:34 - cgroup cgroup rw,xattr,name=systemd
-35 22 1:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime shared:35 - cgroup2 cgroup rw
-36 21 1:28 / /sys/fs/fuse/connections rw,relatime shared:36 - fusectl fusectl rw
-37 21 1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime shared:37 - pstore pstore rw
-38 21 1:30 / /sys/kernel/config rw,relatime shared:38 - configfs configfs rw
-39 21 1:31 / /sys/kernel/debug rw,relatime shared:39 - debugfs debugfs rw
-40 21 1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:40 - securityfs securityfs rw
+0:0 / / rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
+0:1 / /boot/efi rw,relatime shared:2 - vfat /dev/sda15 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:0 / /dev rw,nosuid,relatime shared:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
+1:1 / /dev/hugepages rw,relatime shared:4 - hugetlbfs hugetlbfs rw,pagesize=2M
+1:2 / /dev/mqueue rw,relatime shared:5 - mqueue mqueue rw
+1:3 / /dev/pts rw,nosuid,noexec,relatime shared:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
+1:4 / /dev/shm rw,nosuid,nodev shared:7 - tmpfs tmpfs rw
+1:5 / /proc rw,nosuid,nodev,noexec,relatime shared:8 - proc proc rw
+1:6 / /proc/fs/nfsd rw,relatime shared:9 - nfsd nfsd rw
+1:7 / /proc/sys/fs/binfmt_misc rw,relatime shared:10 - binfmt_misc binfmt_misc rw
+1:8 / /proc/sys/fs/binfmt_misc rw,relatime shared:11 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
+1:9 / /run rw,nosuid,noexec,relatime shared:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:10 / /run/lock rw,nosuid,nodev,noexec,relatime shared:13 - tmpfs tmpfs rw,size=VARIABLE
+1:11 / /run/rpc_pipefs rw,relatime shared:14 - rpc_pipefs sunrpc rw
+1:12 / /run/user/0 rw,nosuid,nodev,relatime shared:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+2:0 / /snap/core/1 ro,nodev,relatime shared:16 - squashfs /dev/loop0 ro
+2:1 / /snap/core18/1 ro,nodev,relatime shared:17 - squashfs /dev/loop1 ro
+2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime shared:18 - squashfs /dev/loop2 ro
+2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime shared:19 - squashfs /dev/loop3 ro
+2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime shared:20 - squashfs /dev/loop4 ro
+1:13 / /sys rw,nosuid,nodev,noexec,relatime shared:21 - sysfs sysfs rw
+1:14 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:22 - tmpfs tmpfs ro,mode=755
+1:15 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:23 - cgroup cgroup rw,blkio
+1:16 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:24 - cgroup cgroup rw,cpu,cpuacct
+1:17 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:25 - cgroup cgroup rw,cpuset
+1:18 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:26 - cgroup cgroup rw,devices
+1:19 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:27 - cgroup cgroup rw,freezer
+1:20 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:28 - cgroup cgroup rw,hugetlb
+1:21 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:29 - cgroup cgroup rw,memory
+1:22 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:30 - cgroup cgroup rw,net_cls,net_prio
+1:23 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:31 - cgroup cgroup rw,perf_event
+1:24 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:32 - cgroup cgroup rw,pids
+1:25 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime shared:33 - cgroup cgroup rw,rdma
+1:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:34 - cgroup cgroup rw,xattr,name=systemd
+1:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime shared:35 - cgroup2 cgroup rw,nsdelegate
+1:28 / /sys/fs/fuse/connections rw,relatime shared:36 - fusectl fusectl rw
+1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime shared:37 - pstore pstore rw
+1:30 / /sys/kernel/config rw,relatime shared:38 - configfs configfs rw
+1:31 / /sys/kernel/debug rw,relatime shared:39 - debugfs debugfs rw
+1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:40 - securityfs securityfs rw
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-18.04-64/PER-SNAP-16.expected.txt snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-18.04-64/PER-SNAP-16.expected.txt
--- snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-18.04-64/PER-SNAP-16.expected.txt 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-18.04-64/PER-SNAP-16.expected.txt 2019-10-30 12:17:43.000000000 +0000
@@ -1,72 +1,75 @@
-1001 1000 2:0 / / ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-1002 1001 1:0 / /dev rw,nosuid,relatime master:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
-1003 1002 1:1 / /dev/hugepages rw,relatime master:4 - hugetlbfs hugetlbfs rw,pagesize=2M
-1004 1002 1:2 / /dev/mqueue rw,relatime master:5 - mqueue mqueue rw
-1005 1002 1:33 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-1006 1002 1:3 / /dev/pts rw,nosuid,noexec,relatime master:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
-1007 1006 1:33 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-1008 1002 1:4 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
-1009 1001 0:0 /etc /etc rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1010 1009 2:0 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-1011 1009 2:0 /etc/ssl /etc/ssl ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-1012 1001 0:0 /home /home rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1013 1001 0:0 /lib/modules /lib/modules rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1014 1001 0:0 /media /media rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
-1015 1001 0:0 /mnt /mnt rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1016 1001 1:5 / /proc rw,nosuid,nodev,noexec,relatime master:8 - proc proc rw
-1017 1016 1:6 / /proc/fs/nfsd rw,relatime master:9 - nfsd nfsd rw
-1019 1018 1:7 / /proc/sys/fs/binfmt_misc rw,relatime master:10 - binfmt_misc binfmt_misc rw
-1018 1016 1:8 / /proc/sys/fs/binfmt_misc rw,relatime master:11 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
-1020 1001 0:0 /root /root rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1021 1001 1:9 / /run rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1022 1021 1:10 / /run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
-1023 1021 1:9 /netns /run/netns rw,nosuid,noexec,relatime shared:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1024 1021 1:11 / /run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
-1025 1021 1:9 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1026 1021 1:12 / /run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-1027 1001 0:0 /snap /snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1028 1027 2:0 / /snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-1029 1027 2:1 / /snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-1030 1027 2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
-1031 1027 2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
-1032 1027 2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
-1033 1001 1:13 / /sys rw,nosuid,nodev,noexec,relatime master:21 - sysfs sysfs rw
-1034 1033 1:14 / /sys/fs/cgroup ro,nosuid,nodev,noexec master:22 - tmpfs tmpfs ro,mode=755
-1035 1034 1:15 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:23 - cgroup cgroup rw,blkio
-1036 1034 1:16 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:24 - cgroup cgroup rw,cpu,cpuacct
-1037 1034 1:17 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:25 - cgroup cgroup rw,cpuset
-1038 1034 1:18 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:26 - cgroup cgroup rw,devices
-1039 1034 1:19 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:27 - cgroup cgroup rw,freezer
-1040 1034 1:20 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:28 - cgroup cgroup rw,hugetlb
-1041 1034 1:21 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:29 - cgroup cgroup rw,memory
-1042 1034 1:22 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:30 - cgroup cgroup rw,net_cls,net_prio
-1043 1034 1:23 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:31 - cgroup cgroup rw,perf_event
-1044 1034 1:24 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:32 - cgroup cgroup rw,pids
-1045 1034 1:25 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime master:33 - cgroup cgroup rw,rdma
-1046 1034 1:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:34 - cgroup cgroup rw,xattr,name=systemd
-1047 1034 1:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime master:35 - cgroup2 cgroup rw
-1048 1033 1:28 / /sys/fs/fuse/connections rw,relatime master:36 - fusectl fusectl rw
-1049 1033 1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:37 - pstore pstore rw
-1050 1033 1:30 / /sys/kernel/config rw,relatime master:38 - configfs configfs rw
-1051 1033 1:31 / /sys/kernel/debug rw,relatime master:39 - debugfs debugfs rw
-1052 1033 1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:40 - securityfs securityfs rw
-1053 1001 0:0 /tmp /tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1054 1053 0:0 /tmp/snap.test-snapd-mountinfo-core16/tmp /tmp rw,relatime - ext4 /dev/sda1 rw,data=ordered
-1055 1001 0:0 /usr/src /usr/src rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1056 1001 0:0 /var/lib/snapd /var/lib/snapd rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1058 1057 0:0 / /var/lib/snapd/hostfs rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1057 1056 0:0 /var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda1 rw,data=ordered
-1059 1058 0:1 / /var/lib/snapd/hostfs/boot/efi rw,relatime master:2 - vfat /dev/sda15 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-1060 1058 1:9 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1061 1060 1:10 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
-1062 1060 1:11 / /var/lib/snapd/hostfs/run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
-1063 1060 1:9 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1064 1060 1:12 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-1065 1058 2:0 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-1066 1058 2:1 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-1067 1058 2:2 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
-1068 1058 2:3 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
-1069 1058 2:4 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
-1070 1001 0:0 /var/log /var/log rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1071 1001 0:0 /var/snap /var/snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1072 1001 0:0 /var/tmp /var/tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+2:0 / / ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+1:0 / /dev rw,nosuid,relatime master:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
+1:1 / /dev/hugepages rw,relatime master:4 - hugetlbfs hugetlbfs rw,pagesize=2M
+1:2 / /dev/mqueue rw,relatime master:5 - mqueue mqueue rw
+1:33 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+1:3 / /dev/pts rw,nosuid,noexec,relatime master:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
+1:33 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+1:4 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
+0:0 /etc /etc rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+2:0 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+2:0 /etc/ssl /etc/ssl ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+0:0 /home /home rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /lib/modules /lib/modules rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /media /media rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /mnt /mnt rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+1:5 / /proc rw,nosuid,nodev,noexec,relatime master:8 - proc proc rw
+1:6 / /proc/fs/nfsd rw,relatime master:9 - nfsd nfsd rw
+1:7 / /proc/sys/fs/binfmt_misc rw,relatime master:10 - binfmt_misc binfmt_misc rw
+1:8 / /proc/sys/fs/binfmt_misc rw,relatime master:11 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
+0:0 /root /root rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+1:9 / /run rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:10 / /run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
+1:9 /netns /run/netns rw,nosuid,noexec,relatime shared:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:11 / /run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
+1:9 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:12 / /run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+0:0 /snap /snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+2:0 / /snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+2:1 / /snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
+2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
+2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
+1:13 / /sys rw,nosuid,nodev,noexec,relatime master:21 - sysfs sysfs rw
+1:14 / /sys/fs/cgroup ro,nosuid,nodev,noexec master:22 - tmpfs tmpfs ro,mode=755
+1:15 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:23 - cgroup cgroup rw,blkio
+1:16 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:24 - cgroup cgroup rw,cpu,cpuacct
+1:17 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:25 - cgroup cgroup rw,cpuset
+1:18 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:26 - cgroup cgroup rw,devices
+1:19 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:27 - cgroup cgroup rw,freezer
+1:20 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:28 - cgroup cgroup rw,hugetlb
+1:21 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:29 - cgroup cgroup rw,memory
+1:22 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:30 - cgroup cgroup rw,net_cls,net_prio
+1:23 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:31 - cgroup cgroup rw,perf_event
+1:24 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:32 - cgroup cgroup rw,pids
+1:25 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime master:33 - cgroup cgroup rw,rdma
+1:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:34 - cgroup cgroup rw,xattr,name=systemd
+1:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime master:35 - cgroup2 cgroup rw,nsdelegate
+1:28 / /sys/fs/fuse/connections rw,relatime master:36 - fusectl fusectl rw
+1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:37 - pstore pstore rw
+1:30 / /sys/kernel/config rw,relatime master:38 - configfs configfs rw
+1:31 / /sys/kernel/debug rw,relatime master:39 - debugfs debugfs rw
+1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:40 - securityfs securityfs rw
+0:0 /tmp /tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /tmp/snap.test-snapd-mountinfo-core16/tmp /tmp rw,relatime - ext4 /dev/sda1 rw,data=ordered
+1:34 / /usr/share/gdb rw,relatime - tmpfs tmpfs rw,mode=755
+2:0 /usr/share/gdb/auto-load /usr/share/gdb/auto-load ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+1:35 / /usr/share/gdb/test rw,relatime - tmpfs tmpfs rw
+0:0 /usr/src /usr/src rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/lib/snapd /var/lib/snapd rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 / /var/lib/snapd/hostfs rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda1 rw,data=ordered
+0:1 / /var/lib/snapd/hostfs/boot/efi rw,relatime master:2 - vfat /dev/sda15 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:9 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:10 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
+1:11 / /var/lib/snapd/hostfs/run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
+1:9 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:12 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+2:0 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+2:1 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+2:2 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
+2:3 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
+2:4 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
+0:0 /var/log /var/log rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/snap /var/snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/tmp /var/tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-18.04-64/PER-SNAP-18.expected.txt snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-18.04-64/PER-SNAP-18.expected.txt
--- snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-18.04-64/PER-SNAP-18.expected.txt 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-18.04-64/PER-SNAP-18.expected.txt 2019-10-30 12:17:43.000000000 +0000
@@ -1,73 +1,76 @@
-1001 1000 2:1 / / ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-1002 1001 1:0 / /dev rw,nosuid,relatime master:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
-1003 1002 1:1 / /dev/hugepages rw,relatime master:4 - hugetlbfs hugetlbfs rw,pagesize=2M
-1004 1002 1:2 / /dev/mqueue rw,relatime master:5 - mqueue mqueue rw
-1005 1002 1:33 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-1006 1002 1:3 / /dev/pts rw,nosuid,noexec,relatime master:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
-1007 1006 1:33 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-1008 1002 1:4 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
-1009 1001 0:0 /etc /etc rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1010 1009 2:1 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-1011 1009 2:1 /etc/ssl /etc/ssl ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-1012 1001 0:0 /home /home rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1013 1001 0:0 /lib/modules /lib/modules rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1014 1001 0:0 /media /media rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
-1015 1001 0:0 /mnt /mnt rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1016 1001 1:5 / /proc rw,nosuid,nodev,noexec,relatime master:8 - proc proc rw
-1017 1016 1:6 / /proc/fs/nfsd rw,relatime master:9 - nfsd nfsd rw
-1019 1018 1:7 / /proc/sys/fs/binfmt_misc rw,relatime master:10 - binfmt_misc binfmt_misc rw
-1018 1016 1:8 / /proc/sys/fs/binfmt_misc rw,relatime master:11 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
-1020 1001 0:0 /root /root rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1021 1001 1:9 / /run rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1022 1021 1:10 / /run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
-1023 1021 1:9 /netns /run/netns rw,nosuid,noexec,relatime shared:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1024 1021 1:11 / /run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
-1025 1021 1:9 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1026 1021 1:12 / /run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-1027 1001 0:0 /snap /snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1028 1027 2:0 / /snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-1029 1027 2:1 / /snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-1030 1027 2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
-1031 1027 2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
-1032 1027 2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
-1033 1001 1:13 / /sys rw,nosuid,nodev,noexec,relatime master:21 - sysfs sysfs rw
-1034 1033 1:14 / /sys/fs/cgroup ro,nosuid,nodev,noexec master:22 - tmpfs tmpfs ro,mode=755
-1035 1034 1:15 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:23 - cgroup cgroup rw,blkio
-1036 1034 1:16 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:24 - cgroup cgroup rw,cpu,cpuacct
-1037 1034 1:17 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:25 - cgroup cgroup rw,cpuset
-1038 1034 1:18 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:26 - cgroup cgroup rw,devices
-1039 1034 1:19 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:27 - cgroup cgroup rw,freezer
-1040 1034 1:20 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:28 - cgroup cgroup rw,hugetlb
-1041 1034 1:21 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:29 - cgroup cgroup rw,memory
-1042 1034 1:22 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:30 - cgroup cgroup rw,net_cls,net_prio
-1043 1034 1:23 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:31 - cgroup cgroup rw,perf_event
-1044 1034 1:24 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:32 - cgroup cgroup rw,pids
-1045 1034 1:25 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime master:33 - cgroup cgroup rw,rdma
-1046 1034 1:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:34 - cgroup cgroup rw,xattr,name=systemd
-1047 1034 1:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime master:35 - cgroup2 cgroup rw
-1048 1033 1:28 / /sys/fs/fuse/connections rw,relatime master:36 - fusectl fusectl rw
-1049 1033 1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:37 - pstore pstore rw
-1050 1033 1:30 / /sys/kernel/config rw,relatime master:38 - configfs configfs rw
-1051 1033 1:31 / /sys/kernel/debug rw,relatime master:39 - debugfs debugfs rw
-1052 1033 1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:40 - securityfs securityfs rw
-1053 1001 0:0 /tmp /tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1054 1053 0:0 /tmp/snap.test-snapd-mountinfo-core18/tmp /tmp rw,relatime - ext4 /dev/sda1 rw,data=ordered
-1055 1001 2:0 /usr/lib/snapd /usr/lib/snapd ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-1056 1001 0:0 /usr/src /usr/src rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1057 1001 0:0 /var/lib/snapd /var/lib/snapd rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1059 1058 0:0 / /var/lib/snapd/hostfs rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1058 1057 0:0 /var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda1 rw,data=ordered
-1060 1059 0:1 / /var/lib/snapd/hostfs/boot/efi rw,relatime master:2 - vfat /dev/sda15 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-1061 1059 1:9 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1062 1061 1:10 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
-1063 1061 1:11 / /var/lib/snapd/hostfs/run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
-1064 1061 1:9 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1065 1061 1:12 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-1066 1059 2:0 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-1067 1059 2:1 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-1068 1059 2:2 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
-1069 1059 2:3 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
-1070 1059 2:4 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
-1071 1001 0:0 /var/log /var/log rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1072 1001 0:0 /var/snap /var/snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-1073 1001 0:0 /var/tmp /var/tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+2:1 / / ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+1:0 / /dev rw,nosuid,relatime master:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
+1:1 / /dev/hugepages rw,relatime master:4 - hugetlbfs hugetlbfs rw,pagesize=2M
+1:2 / /dev/mqueue rw,relatime master:5 - mqueue mqueue rw
+1:33 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+1:3 / /dev/pts rw,nosuid,noexec,relatime master:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
+1:33 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+1:4 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
+0:0 /etc /etc rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+2:1 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+2:1 /etc/ssl /etc/ssl ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+0:0 /home /home rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /lib/modules /lib/modules rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /media /media rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /mnt /mnt rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+1:5 / /proc rw,nosuid,nodev,noexec,relatime master:8 - proc proc rw
+1:6 / /proc/fs/nfsd rw,relatime master:9 - nfsd nfsd rw
+1:7 / /proc/sys/fs/binfmt_misc rw,relatime master:10 - binfmt_misc binfmt_misc rw
+1:8 / /proc/sys/fs/binfmt_misc rw,relatime master:11 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
+0:0 /root /root rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+1:9 / /run rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:10 / /run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
+1:9 /netns /run/netns rw,nosuid,noexec,relatime shared:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:11 / /run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
+1:9 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:12 / /run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+0:0 /snap /snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+2:0 / /snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+2:1 / /snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
+2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
+2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
+1:13 / /sys rw,nosuid,nodev,noexec,relatime master:21 - sysfs sysfs rw
+1:14 / /sys/fs/cgroup ro,nosuid,nodev,noexec master:22 - tmpfs tmpfs ro,mode=755
+1:15 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:23 - cgroup cgroup rw,blkio
+1:16 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:24 - cgroup cgroup rw,cpu,cpuacct
+1:17 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:25 - cgroup cgroup rw,cpuset
+1:18 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:26 - cgroup cgroup rw,devices
+1:19 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:27 - cgroup cgroup rw,freezer
+1:20 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:28 - cgroup cgroup rw,hugetlb
+1:21 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:29 - cgroup cgroup rw,memory
+1:22 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:30 - cgroup cgroup rw,net_cls,net_prio
+1:23 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:31 - cgroup cgroup rw,perf_event
+1:24 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:32 - cgroup cgroup rw,pids
+1:25 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime master:33 - cgroup cgroup rw,rdma
+1:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:34 - cgroup cgroup rw,xattr,name=systemd
+1:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime master:35 - cgroup2 cgroup rw,nsdelegate
+1:28 / /sys/fs/fuse/connections rw,relatime master:36 - fusectl fusectl rw
+1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:37 - pstore pstore rw
+1:30 / /sys/kernel/config rw,relatime master:38 - configfs configfs rw
+1:31 / /sys/kernel/debug rw,relatime master:39 - debugfs debugfs rw
+1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:40 - securityfs securityfs rw
+0:0 /tmp /tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /tmp/snap.test-snapd-mountinfo-core18/tmp /tmp rw,relatime - ext4 /dev/sda1 rw,data=ordered
+2:0 /usr/lib/snapd /usr/lib/snapd ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+1:34 / /usr/share/gdb rw,relatime - tmpfs tmpfs rw,mode=755
+2:1 /usr/share/gdb/auto-load /usr/share/gdb/auto-load ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+1:35 / /usr/share/gdb/test rw,relatime - tmpfs tmpfs rw
+0:0 /usr/src /usr/src rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/lib/snapd /var/lib/snapd rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 / /var/lib/snapd/hostfs rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda1 rw,data=ordered
+0:1 / /var/lib/snapd/hostfs/boot/efi rw,relatime master:2 - vfat /dev/sda15 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:9 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:10 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
+1:11 / /var/lib/snapd/hostfs/run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
+1:9 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:12 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+2:0 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+2:1 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+2:2 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
+2:3 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
+2:4 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
+0:0 /var/log /var/log rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/snap /var/snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/tmp /var/tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-18.04-64/PER-SNAP-C7.expected.txt snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-18.04-64/PER-SNAP-C7.expected.txt
--- snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-18.04-64/PER-SNAP-C7.expected.txt 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-18.04-64/PER-SNAP-C7.expected.txt 2019-10-30 12:17:43.000000000 +0000
@@ -1,40 +1,40 @@
-1 0 0:0 / / rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
-2 1 0:1 / /boot/efi rw,relatime shared:2 - vfat /dev/sda15 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-3 1 1:0 / /dev rw,nosuid,relatime shared:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
-4 3 1:1 / /dev/hugepages rw,relatime shared:4 - hugetlbfs hugetlbfs rw,pagesize=2M
-5 3 1:2 / /dev/mqueue rw,relatime shared:5 - mqueue mqueue rw
-6 3 1:3 / /dev/pts rw,nosuid,noexec,relatime shared:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
-7 3 1:4 / /dev/shm rw,nosuid,nodev shared:7 - tmpfs tmpfs rw
-8 1 1:5 / /proc rw,nosuid,nodev,noexec,relatime shared:8 - proc proc rw
-9 8 1:6 / /proc/fs/nfsd rw,relatime shared:9 - nfsd nfsd rw
-11 10 1:7 / /proc/sys/fs/binfmt_misc rw,relatime shared:10 - binfmt_misc binfmt_misc rw
-10 8 1:8 / /proc/sys/fs/binfmt_misc rw,relatime shared:11 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
-12 1 1:9 / /run rw,nosuid,noexec,relatime shared:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-13 12 1:10 / /run/lock rw,nosuid,nodev,noexec,relatime shared:13 - tmpfs tmpfs rw,size=VARIABLE
-14 12 1:11 / /run/rpc_pipefs rw,relatime shared:14 - rpc_pipefs sunrpc rw
-15 12 1:12 / /run/user/0 rw,nosuid,nodev,relatime shared:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-16 1 2:0 / /snap/core/1 ro,nodev,relatime shared:16 - squashfs /dev/loop0 ro
-17 1 2:1 / /snap/core18/1 ro,nodev,relatime shared:17 - squashfs /dev/loop1 ro
-18 1 2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime shared:18 - squashfs /dev/loop2 ro
-19 1 2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime shared:19 - squashfs /dev/loop3 ro
-20 1 2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime shared:20 - squashfs /dev/loop4 ro
-21 1 1:13 / /sys rw,nosuid,nodev,noexec,relatime shared:21 - sysfs sysfs rw
-22 21 1:14 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:22 - tmpfs tmpfs ro,mode=755
-23 22 1:15 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:23 - cgroup cgroup rw,blkio
-24 22 1:16 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:24 - cgroup cgroup rw,cpu,cpuacct
-25 22 1:17 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:25 - cgroup cgroup rw,cpuset
-26 22 1:18 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:26 - cgroup cgroup rw,devices
-27 22 1:19 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:27 - cgroup cgroup rw,freezer
-28 22 1:20 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:28 - cgroup cgroup rw,hugetlb
-29 22 1:21 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:29 - cgroup cgroup rw,memory
-30 22 1:22 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:30 - cgroup cgroup rw,net_cls,net_prio
-31 22 1:23 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:31 - cgroup cgroup rw,perf_event
-32 22 1:24 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:32 - cgroup cgroup rw,pids
-33 22 1:25 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime shared:33 - cgroup cgroup rw,rdma
-34 22 1:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:34 - cgroup cgroup rw,xattr,name=systemd
-35 22 1:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime shared:35 - cgroup2 cgroup rw
-36 21 1:28 / /sys/fs/fuse/connections rw,relatime shared:36 - fusectl fusectl rw
-37 21 1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime shared:37 - pstore pstore rw
-38 21 1:30 / /sys/kernel/config rw,relatime shared:38 - configfs configfs rw
-39 21 1:31 / /sys/kernel/debug rw,relatime shared:39 - debugfs debugfs rw
-40 21 1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:40 - securityfs securityfs rw
+0:0 / / rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
+0:1 / /boot/efi rw,relatime shared:2 - vfat /dev/sda15 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:0 / /dev rw,nosuid,relatime shared:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
+1:1 / /dev/hugepages rw,relatime shared:4 - hugetlbfs hugetlbfs rw,pagesize=2M
+1:2 / /dev/mqueue rw,relatime shared:5 - mqueue mqueue rw
+1:3 / /dev/pts rw,nosuid,noexec,relatime shared:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
+1:4 / /dev/shm rw,nosuid,nodev shared:7 - tmpfs tmpfs rw
+1:5 / /proc rw,nosuid,nodev,noexec,relatime shared:8 - proc proc rw
+1:6 / /proc/fs/nfsd rw,relatime shared:9 - nfsd nfsd rw
+1:7 / /proc/sys/fs/binfmt_misc rw,relatime shared:10 - binfmt_misc binfmt_misc rw
+1:8 / /proc/sys/fs/binfmt_misc rw,relatime shared:11 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
+1:9 / /run rw,nosuid,noexec,relatime shared:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:10 / /run/lock rw,nosuid,nodev,noexec,relatime shared:13 - tmpfs tmpfs rw,size=VARIABLE
+1:11 / /run/rpc_pipefs rw,relatime shared:14 - rpc_pipefs sunrpc rw
+1:12 / /run/user/0 rw,nosuid,nodev,relatime shared:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+2:0 / /snap/core/1 ro,nodev,relatime shared:16 - squashfs /dev/loop0 ro
+2:1 / /snap/core18/1 ro,nodev,relatime shared:17 - squashfs /dev/loop1 ro
+2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime shared:18 - squashfs /dev/loop2 ro
+2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime shared:19 - squashfs /dev/loop3 ro
+2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime shared:20 - squashfs /dev/loop4 ro
+1:13 / /sys rw,nosuid,nodev,noexec,relatime shared:21 - sysfs sysfs rw
+1:14 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:22 - tmpfs tmpfs ro,mode=755
+1:15 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:23 - cgroup cgroup rw,blkio
+1:16 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:24 - cgroup cgroup rw,cpu,cpuacct
+1:17 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:25 - cgroup cgroup rw,cpuset
+1:18 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:26 - cgroup cgroup rw,devices
+1:19 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:27 - cgroup cgroup rw,freezer
+1:20 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:28 - cgroup cgroup rw,hugetlb
+1:21 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:29 - cgroup cgroup rw,memory
+1:22 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:30 - cgroup cgroup rw,net_cls,net_prio
+1:23 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:31 - cgroup cgroup rw,perf_event
+1:24 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:32 - cgroup cgroup rw,pids
+1:25 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime shared:33 - cgroup cgroup rw,rdma
+1:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:34 - cgroup cgroup rw,xattr,name=systemd
+1:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime shared:35 - cgroup2 cgroup rw,nsdelegate
+1:28 / /sys/fs/fuse/connections rw,relatime shared:36 - fusectl fusectl rw
+1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime shared:37 - pstore pstore rw
+1:30 / /sys/kernel/config rw,relatime shared:38 - configfs configfs rw
+1:31 / /sys/kernel/debug rw,relatime shared:39 - debugfs debugfs rw
+1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:40 - securityfs securityfs rw
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-18.04-64/PER-USER-16.expected.txt snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-18.04-64/PER-USER-16.expected.txt
--- snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-18.04-64/PER-USER-16.expected.txt 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-18.04-64/PER-USER-16.expected.txt 2019-10-30 12:17:43.000000000 +0000
@@ -1,72 +1,75 @@
-2001 2000 2:0 / / ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-2002 2001 1:0 / /dev rw,nosuid,relatime master:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
-2003 2002 1:1 / /dev/hugepages rw,relatime master:4 - hugetlbfs hugetlbfs rw,pagesize=2M
-2004 2002 1:2 / /dev/mqueue rw,relatime master:5 - mqueue mqueue rw
-2005 2002 1:33 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-2006 2002 1:3 / /dev/pts rw,nosuid,noexec,relatime master:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
-2007 2006 1:33 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-2008 2002 1:4 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
-2009 2001 0:0 /etc /etc rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2010 2009 2:0 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-2011 2009 2:0 /etc/ssl /etc/ssl ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-2012 2001 0:0 /home /home rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2013 2001 0:0 /lib/modules /lib/modules rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2014 2001 0:0 /media /media rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
-2015 2001 0:0 /mnt /mnt rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2016 2001 1:5 / /proc rw,nosuid,nodev,noexec,relatime master:8 - proc proc rw
-2017 2016 1:6 / /proc/fs/nfsd rw,relatime master:9 - nfsd nfsd rw
-2019 2018 1:7 / /proc/sys/fs/binfmt_misc rw,relatime master:10 - binfmt_misc binfmt_misc rw
-2018 2016 1:8 / /proc/sys/fs/binfmt_misc rw,relatime master:11 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
-2020 2001 0:0 /root /root rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2021 2001 1:9 / /run rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2022 2021 1:10 / /run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
-2023 2021 1:9 /netns /run/netns rw,nosuid,noexec,relatime shared:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2024 2021 1:11 / /run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
-2025 2021 1:9 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2026 2021 1:12 / /run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-2027 2001 0:0 /snap /snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2028 2027 2:0 / /snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-2029 2027 2:1 / /snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-2030 2027 2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
-2031 2027 2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
-2032 2027 2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
-2033 2001 1:13 / /sys rw,nosuid,nodev,noexec,relatime master:21 - sysfs sysfs rw
-2034 2033 1:14 / /sys/fs/cgroup ro,nosuid,nodev,noexec master:22 - tmpfs tmpfs ro,mode=755
-2035 2034 1:15 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:23 - cgroup cgroup rw,blkio
-2036 2034 1:16 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:24 - cgroup cgroup rw,cpu,cpuacct
-2037 2034 1:17 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:25 - cgroup cgroup rw,cpuset
-2038 2034 1:18 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:26 - cgroup cgroup rw,devices
-2039 2034 1:19 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:27 - cgroup cgroup rw,freezer
-2040 2034 1:20 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:28 - cgroup cgroup rw,hugetlb
-2041 2034 1:21 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:29 - cgroup cgroup rw,memory
-2042 2034 1:22 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:30 - cgroup cgroup rw,net_cls,net_prio
-2043 2034 1:23 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:31 - cgroup cgroup rw,perf_event
-2044 2034 1:24 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:32 - cgroup cgroup rw,pids
-2045 2034 1:25 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime master:33 - cgroup cgroup rw,rdma
-2046 2034 1:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:34 - cgroup cgroup rw,xattr,name=systemd
-2047 2034 1:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime master:35 - cgroup2 cgroup rw
-2048 2033 1:28 / /sys/fs/fuse/connections rw,relatime master:36 - fusectl fusectl rw
-2049 2033 1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:37 - pstore pstore rw
-2050 2033 1:30 / /sys/kernel/config rw,relatime master:38 - configfs configfs rw
-2051 2033 1:31 / /sys/kernel/debug rw,relatime master:39 - debugfs debugfs rw
-2052 2033 1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:40 - securityfs securityfs rw
-2053 2001 0:0 /tmp /tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2054 2053 0:0 /tmp/snap.test-snapd-mountinfo-core16/tmp /tmp rw,relatime - ext4 /dev/sda1 rw,data=ordered
-2055 2001 0:0 /usr/src /usr/src rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2056 2001 0:0 /var/lib/snapd /var/lib/snapd rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2057 2056 0:0 /var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda1 rw,data=ordered
-2058 2057 0:0 / /var/lib/snapd/hostfs rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2059 2058 0:1 / /var/lib/snapd/hostfs/boot/efi rw,relatime master:2 - vfat /dev/sda15 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-2060 2058 1:9 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2061 2060 1:10 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
-2062 2060 1:11 / /var/lib/snapd/hostfs/run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
-2063 2060 1:9 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2064 2060 1:12 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-2065 2058 2:0 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-2066 2058 2:1 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-2067 2058 2:2 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
-2068 2058 2:3 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
-2069 2058 2:4 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
-2070 2001 0:0 /var/log /var/log rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2071 2001 0:0 /var/snap /var/snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2072 2001 0:0 /var/tmp /var/tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+2:0 / / ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+1:0 / /dev rw,nosuid,relatime master:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
+1:1 / /dev/hugepages rw,relatime master:4 - hugetlbfs hugetlbfs rw,pagesize=2M
+1:2 / /dev/mqueue rw,relatime master:5 - mqueue mqueue rw
+1:33 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+1:3 / /dev/pts rw,nosuid,noexec,relatime master:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
+1:33 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+1:4 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
+0:0 /etc /etc rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+2:0 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+2:0 /etc/ssl /etc/ssl ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+0:0 /home /home rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /lib/modules /lib/modules rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /media /media rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /mnt /mnt rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+1:5 / /proc rw,nosuid,nodev,noexec,relatime master:8 - proc proc rw
+1:6 / /proc/fs/nfsd rw,relatime master:9 - nfsd nfsd rw
+1:7 / /proc/sys/fs/binfmt_misc rw,relatime master:10 - binfmt_misc binfmt_misc rw
+1:8 / /proc/sys/fs/binfmt_misc rw,relatime master:11 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
+0:0 /root /root rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+1:9 / /run rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:10 / /run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
+1:9 /netns /run/netns rw,nosuid,noexec,relatime shared:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:11 / /run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
+1:9 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:12 / /run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+0:0 /snap /snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+2:0 / /snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+2:1 / /snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
+2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
+2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
+1:13 / /sys rw,nosuid,nodev,noexec,relatime master:21 - sysfs sysfs rw
+1:14 / /sys/fs/cgroup ro,nosuid,nodev,noexec master:22 - tmpfs tmpfs ro,mode=755
+1:15 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:23 - cgroup cgroup rw,blkio
+1:16 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:24 - cgroup cgroup rw,cpu,cpuacct
+1:17 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:25 - cgroup cgroup rw,cpuset
+1:18 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:26 - cgroup cgroup rw,devices
+1:19 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:27 - cgroup cgroup rw,freezer
+1:20 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:28 - cgroup cgroup rw,hugetlb
+1:21 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:29 - cgroup cgroup rw,memory
+1:22 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:30 - cgroup cgroup rw,net_cls,net_prio
+1:23 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:31 - cgroup cgroup rw,perf_event
+1:24 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:32 - cgroup cgroup rw,pids
+1:25 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime master:33 - cgroup cgroup rw,rdma
+1:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:34 - cgroup cgroup rw,xattr,name=systemd
+1:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime master:35 - cgroup2 cgroup rw,nsdelegate
+1:28 / /sys/fs/fuse/connections rw,relatime master:36 - fusectl fusectl rw
+1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:37 - pstore pstore rw
+1:30 / /sys/kernel/config rw,relatime master:38 - configfs configfs rw
+1:31 / /sys/kernel/debug rw,relatime master:39 - debugfs debugfs rw
+1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:40 - securityfs securityfs rw
+0:0 /tmp /tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /tmp/snap.test-snapd-mountinfo-core16/tmp /tmp rw,relatime - ext4 /dev/sda1 rw,data=ordered
+1:34 / /usr/share/gdb rw,relatime - tmpfs tmpfs rw,mode=755
+2:0 /usr/share/gdb/auto-load /usr/share/gdb/auto-load ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+1:35 / /usr/share/gdb/test rw,relatime - tmpfs tmpfs rw
+0:0 /usr/src /usr/src rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/lib/snapd /var/lib/snapd rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda1 rw,data=ordered
+0:0 / /var/lib/snapd/hostfs rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:1 / /var/lib/snapd/hostfs/boot/efi rw,relatime master:2 - vfat /dev/sda15 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:9 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:10 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
+1:11 / /var/lib/snapd/hostfs/run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
+1:9 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:12 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+2:0 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+2:1 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+2:2 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
+2:3 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
+2:4 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
+0:0 /var/log /var/log rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/snap /var/snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/tmp /var/tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-18.04-64/PER-USER-18.expected.txt snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-18.04-64/PER-USER-18.expected.txt
--- snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-18.04-64/PER-USER-18.expected.txt 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-18.04-64/PER-USER-18.expected.txt 2019-10-30 12:17:43.000000000 +0000
@@ -1,73 +1,76 @@
-2001 2000 2:1 / / ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-2002 2001 1:0 / /dev rw,nosuid,relatime master:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
-2003 2002 1:1 / /dev/hugepages rw,relatime master:4 - hugetlbfs hugetlbfs rw,pagesize=2M
-2004 2002 1:2 / /dev/mqueue rw,relatime master:5 - mqueue mqueue rw
-2005 2002 1:33 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-2006 2002 1:3 / /dev/pts rw,nosuid,noexec,relatime master:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
-2007 2006 1:33 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-2008 2002 1:4 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
-2009 2001 0:0 /etc /etc rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2010 2009 2:1 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-2011 2009 2:1 /etc/ssl /etc/ssl ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-2012 2001 0:0 /home /home rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2013 2001 0:0 /lib/modules /lib/modules rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2014 2001 0:0 /media /media rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
-2015 2001 0:0 /mnt /mnt rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2016 2001 1:5 / /proc rw,nosuid,nodev,noexec,relatime master:8 - proc proc rw
-2017 2016 1:6 / /proc/fs/nfsd rw,relatime master:9 - nfsd nfsd rw
-2019 2018 1:7 / /proc/sys/fs/binfmt_misc rw,relatime master:10 - binfmt_misc binfmt_misc rw
-2018 2016 1:8 / /proc/sys/fs/binfmt_misc rw,relatime master:11 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
-2020 2001 0:0 /root /root rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2021 2001 1:9 / /run rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2022 2021 1:10 / /run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
-2023 2021 1:9 /netns /run/netns rw,nosuid,noexec,relatime shared:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2024 2021 1:11 / /run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
-2025 2021 1:9 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2026 2021 1:12 / /run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-2027 2001 0:0 /snap /snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2028 2027 2:0 / /snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-2029 2027 2:1 / /snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-2030 2027 2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
-2031 2027 2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
-2032 2027 2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
-2033 2001 1:13 / /sys rw,nosuid,nodev,noexec,relatime master:21 - sysfs sysfs rw
-2034 2033 1:14 / /sys/fs/cgroup ro,nosuid,nodev,noexec master:22 - tmpfs tmpfs ro,mode=755
-2035 2034 1:15 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:23 - cgroup cgroup rw,blkio
-2036 2034 1:16 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:24 - cgroup cgroup rw,cpu,cpuacct
-2037 2034 1:17 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:25 - cgroup cgroup rw,cpuset
-2038 2034 1:18 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:26 - cgroup cgroup rw,devices
-2039 2034 1:19 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:27 - cgroup cgroup rw,freezer
-2040 2034 1:20 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:28 - cgroup cgroup rw,hugetlb
-2041 2034 1:21 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:29 - cgroup cgroup rw,memory
-2042 2034 1:22 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:30 - cgroup cgroup rw,net_cls,net_prio
-2043 2034 1:23 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:31 - cgroup cgroup rw,perf_event
-2044 2034 1:24 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:32 - cgroup cgroup rw,pids
-2045 2034 1:25 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime master:33 - cgroup cgroup rw,rdma
-2046 2034 1:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:34 - cgroup cgroup rw,xattr,name=systemd
-2047 2034 1:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime master:35 - cgroup2 cgroup rw
-2048 2033 1:28 / /sys/fs/fuse/connections rw,relatime master:36 - fusectl fusectl rw
-2049 2033 1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:37 - pstore pstore rw
-2050 2033 1:30 / /sys/kernel/config rw,relatime master:38 - configfs configfs rw
-2051 2033 1:31 / /sys/kernel/debug rw,relatime master:39 - debugfs debugfs rw
-2052 2033 1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:40 - securityfs securityfs rw
-2053 2001 0:0 /tmp /tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2054 2053 0:0 /tmp/snap.test-snapd-mountinfo-core18/tmp /tmp rw,relatime - ext4 /dev/sda1 rw,data=ordered
-2055 2001 2:0 /usr/lib/snapd /usr/lib/snapd ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-2056 2001 0:0 /usr/src /usr/src rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2057 2001 0:0 /var/lib/snapd /var/lib/snapd rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2058 2057 0:0 /var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda1 rw,data=ordered
-2059 2058 0:0 / /var/lib/snapd/hostfs rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2060 2059 0:1 / /var/lib/snapd/hostfs/boot/efi rw,relatime master:2 - vfat /dev/sda15 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-2061 2059 1:9 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2062 2061 1:10 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
-2063 2061 1:11 / /var/lib/snapd/hostfs/run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
-2064 2061 1:9 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2065 2061 1:12 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-2066 2059 2:0 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
-2067 2059 2:1 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
-2068 2059 2:2 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
-2069 2059 2:3 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
-2070 2059 2:4 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
-2071 2001 0:0 /var/log /var/log rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2072 2001 0:0 /var/snap /var/snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
-2073 2001 0:0 /var/tmp /var/tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+2:1 / / ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+1:0 / /dev rw,nosuid,relatime master:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
+1:1 / /dev/hugepages rw,relatime master:4 - hugetlbfs hugetlbfs rw,pagesize=2M
+1:2 / /dev/mqueue rw,relatime master:5 - mqueue mqueue rw
+1:33 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+1:3 / /dev/pts rw,nosuid,noexec,relatime master:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
+1:33 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+1:4 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
+0:0 /etc /etc rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+2:1 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+2:1 /etc/ssl /etc/ssl ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+0:0 /home /home rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /lib/modules /lib/modules rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /media /media rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /mnt /mnt rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+1:5 / /proc rw,nosuid,nodev,noexec,relatime master:8 - proc proc rw
+1:6 / /proc/fs/nfsd rw,relatime master:9 - nfsd nfsd rw
+1:7 / /proc/sys/fs/binfmt_misc rw,relatime master:10 - binfmt_misc binfmt_misc rw
+1:8 / /proc/sys/fs/binfmt_misc rw,relatime master:11 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
+0:0 /root /root rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+1:9 / /run rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:10 / /run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
+1:9 /netns /run/netns rw,nosuid,noexec,relatime shared:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:11 / /run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
+1:9 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:12 / /run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+0:0 /snap /snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+2:0 / /snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+2:1 / /snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
+2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
+2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
+1:13 / /sys rw,nosuid,nodev,noexec,relatime master:21 - sysfs sysfs rw
+1:14 / /sys/fs/cgroup ro,nosuid,nodev,noexec master:22 - tmpfs tmpfs ro,mode=755
+1:15 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:23 - cgroup cgroup rw,blkio
+1:16 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:24 - cgroup cgroup rw,cpu,cpuacct
+1:17 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:25 - cgroup cgroup rw,cpuset
+1:18 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:26 - cgroup cgroup rw,devices
+1:19 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:27 - cgroup cgroup rw,freezer
+1:20 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:28 - cgroup cgroup rw,hugetlb
+1:21 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:29 - cgroup cgroup rw,memory
+1:22 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:30 - cgroup cgroup rw,net_cls,net_prio
+1:23 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:31 - cgroup cgroup rw,perf_event
+1:24 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:32 - cgroup cgroup rw,pids
+1:25 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime master:33 - cgroup cgroup rw,rdma
+1:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:34 - cgroup cgroup rw,xattr,name=systemd
+1:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime master:35 - cgroup2 cgroup rw,nsdelegate
+1:28 / /sys/fs/fuse/connections rw,relatime master:36 - fusectl fusectl rw
+1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:37 - pstore pstore rw
+1:30 / /sys/kernel/config rw,relatime master:38 - configfs configfs rw
+1:31 / /sys/kernel/debug rw,relatime master:39 - debugfs debugfs rw
+1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:40 - securityfs securityfs rw
+0:0 /tmp /tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /tmp/snap.test-snapd-mountinfo-core18/tmp /tmp rw,relatime - ext4 /dev/sda1 rw,data=ordered
+2:0 /usr/lib/snapd /usr/lib/snapd ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+1:34 / /usr/share/gdb rw,relatime - tmpfs tmpfs rw,mode=755
+2:1 /usr/share/gdb/auto-load /usr/share/gdb/auto-load ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+1:35 / /usr/share/gdb/test rw,relatime - tmpfs tmpfs rw
+0:0 /usr/src /usr/src rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/lib/snapd /var/lib/snapd rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda1 rw,data=ordered
+0:0 / /var/lib/snapd/hostfs rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:1 / /var/lib/snapd/hostfs/boot/efi rw,relatime master:2 - vfat /dev/sda15 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:9 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:10 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:13 - tmpfs tmpfs rw,size=VARIABLE
+1:11 / /var/lib/snapd/hostfs/run/rpc_pipefs rw,relatime master:14 - rpc_pipefs sunrpc rw
+1:9 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:12 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+2:0 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:16 - squashfs /dev/loop0 ro
+2:1 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:17 - squashfs /dev/loop1 ro
+2:2 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime master:18 - squashfs /dev/loop2 ro
+2:3 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:19 - squashfs /dev/loop3 ro
+2:4 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:20 - squashfs /dev/loop4 ro
+0:0 /var/log /var/log rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/snap /var/snap rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
+0:0 /var/tmp /var/tmp rw,relatime master:1 - ext4 /dev/sda1 rw,data=ordered
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-18.04-64/PER-USER-C7.expected.txt snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-18.04-64/PER-USER-C7.expected.txt
--- snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-18.04-64/PER-USER-C7.expected.txt 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-18.04-64/PER-USER-C7.expected.txt 2019-10-30 12:17:43.000000000 +0000
@@ -1,40 +1,40 @@
-1 0 0:0 / / rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
-2 1 0:1 / /boot/efi rw,relatime shared:2 - vfat /dev/sda15 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-3 1 1:0 / /dev rw,nosuid,relatime shared:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
-4 3 1:1 / /dev/hugepages rw,relatime shared:4 - hugetlbfs hugetlbfs rw,pagesize=2M
-5 3 1:2 / /dev/mqueue rw,relatime shared:5 - mqueue mqueue rw
-6 3 1:3 / /dev/pts rw,nosuid,noexec,relatime shared:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
-7 3 1:4 / /dev/shm rw,nosuid,nodev shared:7 - tmpfs tmpfs rw
-8 1 1:5 / /proc rw,nosuid,nodev,noexec,relatime shared:8 - proc proc rw
-9 8 1:6 / /proc/fs/nfsd rw,relatime shared:9 - nfsd nfsd rw
-11 10 1:7 / /proc/sys/fs/binfmt_misc rw,relatime shared:10 - binfmt_misc binfmt_misc rw
-10 8 1:8 / /proc/sys/fs/binfmt_misc rw,relatime shared:11 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
-12 1 1:9 / /run rw,nosuid,noexec,relatime shared:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-13 12 1:10 / /run/lock rw,nosuid,nodev,noexec,relatime shared:13 - tmpfs tmpfs rw,size=VARIABLE
-14 12 1:11 / /run/rpc_pipefs rw,relatime shared:14 - rpc_pipefs sunrpc rw
-15 12 1:12 / /run/user/0 rw,nosuid,nodev,relatime shared:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-16 1 2:0 / /snap/core/1 ro,nodev,relatime shared:16 - squashfs /dev/loop0 ro
-17 1 2:1 / /snap/core18/1 ro,nodev,relatime shared:17 - squashfs /dev/loop1 ro
-18 1 2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime shared:18 - squashfs /dev/loop2 ro
-19 1 2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime shared:19 - squashfs /dev/loop3 ro
-20 1 2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime shared:20 - squashfs /dev/loop4 ro
-21 1 1:13 / /sys rw,nosuid,nodev,noexec,relatime shared:21 - sysfs sysfs rw
-22 21 1:14 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:22 - tmpfs tmpfs ro,mode=755
-23 22 1:15 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:23 - cgroup cgroup rw,blkio
-24 22 1:16 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:24 - cgroup cgroup rw,cpu,cpuacct
-25 22 1:17 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:25 - cgroup cgroup rw,cpuset
-26 22 1:18 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:26 - cgroup cgroup rw,devices
-27 22 1:19 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:27 - cgroup cgroup rw,freezer
-28 22 1:20 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:28 - cgroup cgroup rw,hugetlb
-29 22 1:21 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:29 - cgroup cgroup rw,memory
-30 22 1:22 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:30 - cgroup cgroup rw,net_cls,net_prio
-31 22 1:23 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:31 - cgroup cgroup rw,perf_event
-32 22 1:24 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:32 - cgroup cgroup rw,pids
-33 22 1:25 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime shared:33 - cgroup cgroup rw,rdma
-34 22 1:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:34 - cgroup cgroup rw,xattr,name=systemd
-35 22 1:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime shared:35 - cgroup2 cgroup rw
-36 21 1:28 / /sys/fs/fuse/connections rw,relatime shared:36 - fusectl fusectl rw
-37 21 1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime shared:37 - pstore pstore rw
-38 21 1:30 / /sys/kernel/config rw,relatime shared:38 - configfs configfs rw
-39 21 1:31 / /sys/kernel/debug rw,relatime shared:39 - debugfs debugfs rw
-40 21 1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:40 - securityfs securityfs rw
+0:0 / / rw,relatime shared:1 - ext4 /dev/sda1 rw,data=ordered
+0:1 / /boot/efi rw,relatime shared:2 - vfat /dev/sda15 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:0 / /dev rw,nosuid,relatime shared:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
+1:1 / /dev/hugepages rw,relatime shared:4 - hugetlbfs hugetlbfs rw,pagesize=2M
+1:2 / /dev/mqueue rw,relatime shared:5 - mqueue mqueue rw
+1:3 / /dev/pts rw,nosuid,noexec,relatime shared:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
+1:4 / /dev/shm rw,nosuid,nodev shared:7 - tmpfs tmpfs rw
+1:5 / /proc rw,nosuid,nodev,noexec,relatime shared:8 - proc proc rw
+1:6 / /proc/fs/nfsd rw,relatime shared:9 - nfsd nfsd rw
+1:7 / /proc/sys/fs/binfmt_misc rw,relatime shared:10 - binfmt_misc binfmt_misc rw
+1:8 / /proc/sys/fs/binfmt_misc rw,relatime shared:11 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
+1:9 / /run rw,nosuid,noexec,relatime shared:12 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+1:10 / /run/lock rw,nosuid,nodev,noexec,relatime shared:13 - tmpfs tmpfs rw,size=VARIABLE
+1:11 / /run/rpc_pipefs rw,relatime shared:14 - rpc_pipefs sunrpc rw
+1:12 / /run/user/0 rw,nosuid,nodev,relatime shared:15 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+2:0 / /snap/core/1 ro,nodev,relatime shared:16 - squashfs /dev/loop0 ro
+2:1 / /snap/core18/1 ro,nodev,relatime shared:17 - squashfs /dev/loop1 ro
+2:2 / /snap/test-snapd-mountinfo-classic/1 ro,nodev,relatime shared:18 - squashfs /dev/loop2 ro
+2:3 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime shared:19 - squashfs /dev/loop3 ro
+2:4 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime shared:20 - squashfs /dev/loop4 ro
+1:13 / /sys rw,nosuid,nodev,noexec,relatime shared:21 - sysfs sysfs rw
+1:14 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:22 - tmpfs tmpfs ro,mode=755
+1:15 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:23 - cgroup cgroup rw,blkio
+1:16 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:24 - cgroup cgroup rw,cpu,cpuacct
+1:17 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:25 - cgroup cgroup rw,cpuset
+1:18 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:26 - cgroup cgroup rw,devices
+1:19 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:27 - cgroup cgroup rw,freezer
+1:20 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:28 - cgroup cgroup rw,hugetlb
+1:21 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:29 - cgroup cgroup rw,memory
+1:22 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:30 - cgroup cgroup rw,net_cls,net_prio
+1:23 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:31 - cgroup cgroup rw,perf_event
+1:24 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:32 - cgroup cgroup rw,pids
+1:25 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime shared:33 - cgroup cgroup rw,rdma
+1:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:34 - cgroup cgroup rw,xattr,name=systemd
+1:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime shared:35 - cgroup2 cgroup rw,nsdelegate
+1:28 / /sys/fs/fuse/connections rw,relatime shared:36 - fusectl fusectl rw
+1:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime shared:37 - pstore pstore rw
+1:30 / /sys/kernel/config rw,relatime shared:38 - configfs configfs rw
+1:31 / /sys/kernel/debug rw,relatime shared:39 - debugfs debugfs rw
+1:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:40 - securityfs securityfs rw
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-core-16-64/HOST.expected.txt snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-core-16-64/HOST.expected.txt
--- snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-core-16-64/HOST.expected.txt 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-core-16-64/HOST.expected.txt 2019-10-30 12:17:43.000000000 +0000
@@ -1,119 +1,119 @@
-1 0 0:0 / / ro,relatime shared:1 - squashfs /dev/loop0 ro
-2 1 1:0 / /boot/efi rw,relatime shared:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-3 1 1:0 /EFI/ubuntu /boot/grub rw,relatime shared:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-4 1 2:0 / /dev rw,nosuid,relatime shared:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
-5 4 2:1 / /dev/hugepages rw,relatime shared:4 - hugetlbfs hugetlbfs rw
-6 4 2:2 / /dev/mqueue rw,relatime shared:5 - mqueue mqueue rw
-7 4 2:3 / /dev/pts rw,nosuid,noexec,relatime shared:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
-8 4 2:4 / /dev/shm rw,nosuid,nodev shared:7 - tmpfs tmpfs rw
-9 1 1:1 /system-data/etc/apparmor.d/cache /etc/apparmor.d/cache rw,relatime shared:8 - ext4 /dev/sda3 rw,data=ordered
-10 1 1:1 /system-data/etc/cloud /etc/cloud rw,relatime shared:9 - ext4 /dev/sda3 rw,data=ordered
-11 1 1:1 /system-data/etc/dbus-1/system.d /etc/dbus-1/system.d rw,relatime shared:10 - ext4 /dev/sda3 rw,data=ordered
-12 1 1:1 /system-data/etc/default/keyboard /etc/default/keyboard rw,relatime shared:11 - ext4 /dev/sda3 rw,data=ordered
-13 1 1:1 /system-data/etc/default/swapfile /etc/default/swapfile rw,relatime shared:12 - ext4 /dev/sda3 rw,data=ordered
-14 1 1:1 /system-data/etc/environment /etc/environment rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
-15 1 2:5 /image.fstab /etc/fstab rw,nosuid,noexec,relatime shared:14 - tmpfs tmpfs rw,mode=755
-16 1 1:1 /system-data/root/test-etc/group /etc/group ro,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-17 1 1:1 /system-data/root/test-etc/gshadow /etc/gshadow ro,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-18 1 1:1 /system-data/etc/hosts /etc/hosts rw,relatime shared:16 - ext4 /dev/sda3 rw,data=ordered
-19 1 1:1 /system-data/etc/init /etc/init rw,relatime shared:17 - ext4 /dev/sda3 rw,data=ordered
-20 1 1:1 /system-data/etc/init.d /etc/init.d rw,relatime shared:18 - ext4 /dev/sda3 rw,data=ordered
-21 1 1:1 /system-data/etc/iproute2 /etc/iproute2 rw,relatime shared:19 - ext4 /dev/sda3 rw,data=ordered
-22 1 1:1 /system-data/etc/machine-id /etc/machine-id rw,relatime shared:20 - ext4 /dev/sda3 rw,data=ordered
-23 1 1:1 /system-data/etc/modprobe.d /etc/modprobe.d rw,relatime shared:21 - ext4 /dev/sda3 rw,data=ordered
-24 1 1:1 /system-data/etc/modules-load.d /etc/modules-load.d rw,relatime shared:22 - ext4 /dev/sda3 rw,data=ordered
-25 1 1:1 /system-data/etc/netplan /etc/netplan rw,relatime shared:23 - ext4 /dev/sda3 rw,data=ordered
-26 1 1:1 /system-data/etc/network/if-up.d /etc/network/if-up.d rw,relatime shared:24 - ext4 /dev/sda3 rw,data=ordered
-27 1 1:1 /system-data/etc/network/interfaces.d /etc/network/interfaces.d rw,relatime shared:25 - ext4 /dev/sda3 rw,data=ordered
-28 1 1:1 /system-data/root/test-etc/passwd /etc/passwd ro,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-29 1 1:1 /system-data/etc/ppp /etc/ppp rw,relatime shared:26 - ext4 /dev/sda3 rw,data=ordered
-30 1 1:1 /system-data/etc/rc0.d /etc/rc0.d rw,relatime shared:27 - ext4 /dev/sda3 rw,data=ordered
-31 1 1:1 /system-data/etc/rc1.d /etc/rc1.d rw,relatime shared:28 - ext4 /dev/sda3 rw,data=ordered
-32 1 1:1 /system-data/etc/rc2.d /etc/rc2.d rw,relatime shared:29 - ext4 /dev/sda3 rw,data=ordered
-33 1 1:1 /system-data/etc/rc3.d /etc/rc3.d rw,relatime shared:30 - ext4 /dev/sda3 rw,data=ordered
-34 1 1:1 /system-data/etc/rc4.d /etc/rc4.d rw,relatime shared:31 - ext4 /dev/sda3 rw,data=ordered
-35 1 1:1 /system-data/etc/rc5.d /etc/rc5.d rw,relatime shared:32 - ext4 /dev/sda3 rw,data=ordered
-36 1 1:1 /system-data/etc/rc6.d /etc/rc6.d rw,relatime shared:33 - ext4 /dev/sda3 rw,data=ordered
-37 1 1:1 /system-data/etc/rcS.d /etc/rcS.d rw,relatime shared:34 - ext4 /dev/sda3 rw,data=ordered
-38 1 1:1 /system-data/etc/rsyslog.d /etc/rsyslog.d rw,relatime shared:35 - ext4 /dev/sda3 rw,data=ordered
-39 1 1:1 /system-data/root/test-etc/shadow /etc/shadow ro,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-40 1 1:1 /system-data/etc/ssh /etc/ssh rw,relatime shared:36 - ext4 /dev/sda3 rw,data=ordered
-41 1 1:1 /system-data/etc/sudoers.d /etc/sudoers.d rw,relatime shared:37 - ext4 /dev/sda3 rw,data=ordered
-42 1 1:1 /system-data/etc/sysctl.d /etc/sysctl.d rw,relatime shared:38 - ext4 /dev/sda3 rw,data=ordered
-43 1 1:1 /system-data/etc/systemd/logind.conf.d /etc/systemd/logind.conf.d rw,relatime shared:39 - ext4 /dev/sda3 rw,data=ordered
-44 1 1:1 /system-data/etc/systemd/network /etc/systemd/network rw,relatime shared:40 - ext4 /dev/sda3 rw,data=ordered
-45 1 1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime shared:41 - ext4 /dev/sda3 rw,data=ordered
-46 45 1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime shared:42 - ext4 /dev/sda3 rw,data=ordered
-47 1 1:1 /system-data/etc/systemd/system.conf.d /etc/systemd/system.conf.d rw,relatime shared:43 - ext4 /dev/sda3 rw,data=ordered
-48 1 1:1 /system-data/etc/systemd/timesyncd.conf /etc/systemd/timesyncd.conf rw,relatime shared:44 - ext4 /dev/sda3 rw,data=ordered
-49 1 1:1 /system-data/etc/systemd/user.conf.d /etc/systemd/user.conf.d rw,relatime shared:45 - ext4 /dev/sda3 rw,data=ordered
-50 1 1:1 /system-data/etc/udev/rules.d /etc/udev/rules.d rw,relatime shared:46 - ext4 /dev/sda3 rw,data=ordered
-51 1 1:1 /system-data/etc/update-motd.d /etc/update-motd.d rw,relatime shared:47 - ext4 /dev/sda3 rw,data=ordered
-52 1 1:1 /system-data/etc/writable /etc/writable rw,relatime shared:48 - ext4 /dev/sda3 rw,data=ordered
-53 1 1:1 /user-data /home rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-54 1 0:1 /firmware /lib/firmware ro,relatime shared:49 - squashfs /dev/loop1 ro
-55 1 0:1 /modules /lib/modules ro,relatime shared:50 - squashfs /dev/loop1 ro
-56 1 2:6 / /media rw,relatime shared:51 - tmpfs tmpfs rw
-57 1 2:7 / /mnt rw,relatime shared:52 - tmpfs tmpfs rw
-58 1 2:8 / /proc rw,nosuid,nodev,noexec,relatime shared:53 - proc proc rw
-59 58 2:9 / /proc/sys/fs/binfmt_misc rw,relatime shared:54 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct
-60 1 1:1 /system-data/root /root rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-62 61 2:10 / /run rw,nosuid,noexec,relatime shared:55 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-61 1 2:5 / /run rw,nosuid,noexec,relatime shared:56 - tmpfs tmpfs rw,mode=755
-63 62 2:11 / /run/cgmanager/fs rw,relatime shared:57 - tmpfs cgmfs rw,size=VARIABLE,mode=755
-64 62 2:12 / /run/lock rw,nosuid,nodev,noexec,relatime shared:58 - tmpfs tmpfs rw,size=VARIABLE
-65 62 2:10 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-66 62 2:13 / /run/user/0 rw,nosuid,nodev,relatime shared:59 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-67 1 1:1 /system-data/snap /snap rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-68 67 0:2 / /snap/core/1 ro,nodev,relatime shared:60 - squashfs /dev/loop2 ro
-69 67 0:3 / /snap/core18/1 ro,nodev,relatime shared:61 - squashfs /dev/loop3 ro
-70 67 0:4 / /snap/pc-kernel/1 ro,nodev,relatime shared:62 - squashfs /dev/loop4 ro
-71 67 0:5 / /snap/pc/1 ro,nodev,relatime shared:63 - squashfs /dev/loop5 ro
-72 67 0:6 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime shared:64 - squashfs /dev/loop6 ro
-73 67 0:7 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime shared:65 - squashfs /dev/loop7 ro
-74 67 0:8 / /snap/test-snapd-rsync/1 ro,nodev,relatime shared:66 - squashfs /dev/loop8 ro
-75 1 2:14 / /sys rw,nosuid,nodev,noexec,relatime shared:67 - sysfs sysfs rw
-76 75 2:15 / /sys/fs/cgroup rw shared:68 - tmpfs tmpfs rw,mode=755
-77 76 2:16 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:69 - cgroup cgroup rw,blkio
-78 76 2:17 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:70 - cgroup cgroup rw,cpu,cpuacct
-79 76 2:18 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:71 - cgroup cgroup rw,cpuset,clone_children
-80 76 2:19 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:72 - cgroup cgroup rw,devices
-81 76 2:20 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:73 - cgroup cgroup rw,freezer
-82 76 2:21 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:74 - cgroup cgroup rw,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb
-83 76 2:22 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:75 - cgroup cgroup rw,memory
-84 76 2:23 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:76 - cgroup cgroup rw,net_cls,net_prio
-85 76 2:24 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:77 - cgroup cgroup rw,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event
-86 76 2:25 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:78 - cgroup cgroup rw,pids,release_agent=/run/cgmanager/agents/cgm-release-agent.pids
-87 76 2:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:79 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
-88 75 2:27 / /sys/fs/fuse/connections rw,relatime shared:80 - fusectl fusectl rw
-89 75 2:28 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime shared:81 - pstore pstore rw
-90 75 2:29 / /sys/kernel/debug rw,relatime shared:82 - debugfs debugfs rw
-91 75 2:30 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:83 - securityfs securityfs rw
-92 1 2:31 / /tmp rw,relatime shared:84 - tmpfs tmpfs rw
-93 1 1:1 /system-data/var/cache/apparmor /var/cache/apparmor rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-94 1 1:1 /system-data/var/cache/snapd /var/cache/snapd rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-95 1 1:1 /system-data/var/lib/apparmor /var/lib/apparmor rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-96 1 1:1 /system-data/var/lib/cloud /var/lib/cloud rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-97 1 1:1 /system-data/var/lib/console-conf /var/lib/console-conf rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-98 1 1:1 /system-data/var/lib/dbus /var/lib/dbus rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-99 1 1:1 /system-data/var/lib/dhcp /var/lib/dhcp rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-100 1 1:1 /system-data/var/lib/extrausers /var/lib/extrausers rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-101 1 1:1 /system-data/var/lib/initramfs-tools /var/lib/initramfs-tools rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-102 1 1:1 /system-data/var/lib/logrotate /var/lib/logrotate rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-103 1 1:1 /system-data/var/lib/misc /var/lib/misc rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-104 1 1:1 /system-data/var/lib/snapd /var/lib/snapd rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-105 1 2:32 / /var/lib/sudo rw,relatime shared:85 - tmpfs tmpfs rw,mode=700
-106 1 1:1 /system-data/var/lib/systemd/random-seed /var/lib/systemd/random-seed rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-107 1 1:1 /system-data/var/lib/systemd/rfkill /var/lib/systemd/rfkill rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-108 1 1:1 /system-data/var/lib/waagent /var/lib/waagent rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-109 1 1:1 /system-data/var/log /var/log rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-110 1 1:1 /system-data/var/snap /var/snap rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-111 1 1:1 /system-data/var/tmp /var/tmp rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-112 1 1:1 / /writable rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-113 112 0:2 / /writable/system-data/snap/core/1 ro,nodev,relatime shared:60 - squashfs /dev/loop2 ro
-114 112 0:3 / /writable/system-data/snap/core18/1 ro,nodev,relatime shared:61 - squashfs /dev/loop3 ro
-115 112 0:4 / /writable/system-data/snap/pc-kernel/1 ro,nodev,relatime shared:62 - squashfs /dev/loop4 ro
-116 112 0:5 / /writable/system-data/snap/pc/1 ro,nodev,relatime shared:63 - squashfs /dev/loop5 ro
-117 112 0:6 / /writable/system-data/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime shared:64 - squashfs /dev/loop6 ro
-118 112 0:7 / /writable/system-data/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime shared:65 - squashfs /dev/loop7 ro
-119 112 0:8 / /writable/system-data/snap/test-snapd-rsync/1 ro,nodev,relatime shared:66 - squashfs /dev/loop8 ro
+0:0 / / ro,relatime shared:1 - squashfs /dev/loop0 ro
+1:0 / /boot/efi rw,relatime shared:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:0 /EFI/ubuntu /boot/grub rw,relatime shared:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+2:0 / /dev rw,nosuid,relatime shared:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
+2:1 / /dev/hugepages rw,relatime shared:4 - hugetlbfs hugetlbfs rw
+2:2 / /dev/mqueue rw,relatime shared:5 - mqueue mqueue rw
+2:3 / /dev/pts rw,nosuid,noexec,relatime shared:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
+2:4 / /dev/shm rw,nosuid,nodev shared:7 - tmpfs tmpfs rw
+1:1 /system-data/etc/apparmor.d/cache /etc/apparmor.d/cache rw,relatime shared:8 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/cloud /etc/cloud rw,relatime shared:9 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/dbus-1/system.d /etc/dbus-1/system.d rw,relatime shared:10 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/keyboard /etc/default/keyboard rw,relatime shared:11 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/swapfile /etc/default/swapfile rw,relatime shared:12 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/environment /etc/environment rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
+2:5 /image.fstab /etc/fstab rw,nosuid,noexec,relatime shared:14 - tmpfs tmpfs rw,mode=755
+1:1 /system-data/root/test-etc/group /etc/group ro,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/gshadow /etc/gshadow ro,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/hosts /etc/hosts rw,relatime shared:16 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/init /etc/init rw,relatime shared:17 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/init.d /etc/init.d rw,relatime shared:18 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/iproute2 /etc/iproute2 rw,relatime shared:19 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/machine-id /etc/machine-id rw,relatime shared:20 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modprobe.d /etc/modprobe.d rw,relatime shared:21 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modules-load.d /etc/modules-load.d rw,relatime shared:22 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/netplan /etc/netplan rw,relatime shared:23 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/if-up.d /etc/network/if-up.d rw,relatime shared:24 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/interfaces.d /etc/network/interfaces.d rw,relatime shared:25 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/passwd /etc/passwd ro,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ppp /etc/ppp rw,relatime shared:26 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc0.d /etc/rc0.d rw,relatime shared:27 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc1.d /etc/rc1.d rw,relatime shared:28 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc2.d /etc/rc2.d rw,relatime shared:29 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc3.d /etc/rc3.d rw,relatime shared:30 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc4.d /etc/rc4.d rw,relatime shared:31 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc5.d /etc/rc5.d rw,relatime shared:32 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc6.d /etc/rc6.d rw,relatime shared:33 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rcS.d /etc/rcS.d rw,relatime shared:34 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rsyslog.d /etc/rsyslog.d rw,relatime shared:35 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/shadow /etc/shadow ro,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ssh /etc/ssh rw,relatime shared:36 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sudoers.d /etc/sudoers.d rw,relatime shared:37 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sysctl.d /etc/sysctl.d rw,relatime shared:38 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/logind.conf.d /etc/systemd/logind.conf.d rw,relatime shared:39 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/network /etc/systemd/network rw,relatime shared:40 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime shared:41 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime shared:42 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system.conf.d /etc/systemd/system.conf.d rw,relatime shared:43 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/timesyncd.conf /etc/systemd/timesyncd.conf rw,relatime shared:44 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/user.conf.d /etc/systemd/user.conf.d rw,relatime shared:45 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/udev/rules.d /etc/udev/rules.d rw,relatime shared:46 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/update-motd.d /etc/update-motd.d rw,relatime shared:47 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/writable /etc/writable rw,relatime shared:48 - ext4 /dev/sda3 rw,data=ordered
+1:1 /user-data /home rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+0:1 /firmware /lib/firmware ro,relatime shared:49 - squashfs /dev/loop1 ro
+0:1 /modules /lib/modules ro,relatime shared:50 - squashfs /dev/loop1 ro
+2:6 / /media rw,relatime shared:51 - tmpfs tmpfs rw
+2:7 / /mnt rw,relatime shared:52 - tmpfs tmpfs rw
+2:8 / /proc rw,nosuid,nodev,noexec,relatime shared:53 - proc proc rw
+2:9 / /proc/sys/fs/binfmt_misc rw,relatime shared:54 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct
+1:1 /system-data/root /root rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+2:10 / /run rw,nosuid,noexec,relatime shared:55 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:5 / /run rw,nosuid,noexec,relatime shared:56 - tmpfs tmpfs rw,mode=755
+2:11 / /run/cgmanager/fs rw,relatime shared:57 - tmpfs cgmfs rw,size=VARIABLE,mode=755
+2:12 / /run/lock rw,nosuid,nodev,noexec,relatime shared:58 - tmpfs tmpfs rw,size=VARIABLE
+2:10 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:13 / /run/user/0 rw,nosuid,nodev,relatime shared:59 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+1:1 /system-data/snap /snap rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /snap/core/1 ro,nodev,relatime shared:60 - squashfs /dev/loop2 ro
+0:3 / /snap/core18/1 ro,nodev,relatime shared:61 - squashfs /dev/loop3 ro
+0:4 / /snap/pc-kernel/1 ro,nodev,relatime shared:62 - squashfs /dev/loop4 ro
+0:5 / /snap/pc/1 ro,nodev,relatime shared:63 - squashfs /dev/loop5 ro
+0:6 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime shared:64 - squashfs /dev/loop6 ro
+0:7 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime shared:65 - squashfs /dev/loop7 ro
+0:8 / /snap/test-snapd-rsync/1 ro,nodev,relatime shared:66 - squashfs /dev/loop8 ro
+2:14 / /sys rw,nosuid,nodev,noexec,relatime shared:67 - sysfs sysfs rw
+2:15 / /sys/fs/cgroup rw shared:68 - tmpfs tmpfs rw,mode=755
+2:16 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:69 - cgroup cgroup rw,blkio
+2:17 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:70 - cgroup cgroup rw,cpu,cpuacct
+2:18 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:71 - cgroup cgroup rw,cpuset,clone_children
+2:19 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:72 - cgroup cgroup rw,devices
+2:20 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:73 - cgroup cgroup rw,freezer
+2:21 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:74 - cgroup cgroup rw,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb
+2:22 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:75 - cgroup cgroup rw,memory
+2:23 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:76 - cgroup cgroup rw,net_cls,net_prio
+2:24 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:77 - cgroup cgroup rw,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event
+2:25 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:78 - cgroup cgroup rw,pids,release_agent=/run/cgmanager/agents/cgm-release-agent.pids
+2:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:79 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
+2:27 / /sys/fs/fuse/connections rw,relatime shared:80 - fusectl fusectl rw
+2:28 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime shared:81 - pstore pstore rw
+2:29 / /sys/kernel/debug rw,relatime shared:82 - debugfs debugfs rw
+2:30 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:83 - securityfs securityfs rw
+2:31 / /tmp rw,relatime shared:84 - tmpfs tmpfs rw
+1:1 /system-data/var/cache/apparmor /var/cache/apparmor rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/cache/snapd /var/cache/snapd rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/apparmor /var/lib/apparmor rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/cloud /var/lib/cloud rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/console-conf /var/lib/console-conf rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/dbus /var/lib/dbus rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/dhcp /var/lib/dhcp rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/extrausers /var/lib/extrausers rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/initramfs-tools /var/lib/initramfs-tools rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/logrotate /var/lib/logrotate rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/misc /var/lib/misc rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/snapd /var/lib/snapd rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+2:32 / /var/lib/sudo rw,relatime shared:85 - tmpfs tmpfs rw,mode=700
+1:1 /system-data/var/lib/systemd/random-seed /var/lib/systemd/random-seed rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/systemd/rfkill /var/lib/systemd/rfkill rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/waagent /var/lib/waagent rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/log /var/log rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/snap /var/snap rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/tmp /var/tmp rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 / /writable rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /writable/system-data/snap/core/1 ro,nodev,relatime shared:60 - squashfs /dev/loop2 ro
+0:3 / /writable/system-data/snap/core18/1 ro,nodev,relatime shared:61 - squashfs /dev/loop3 ro
+0:4 / /writable/system-data/snap/pc-kernel/1 ro,nodev,relatime shared:62 - squashfs /dev/loop4 ro
+0:5 / /writable/system-data/snap/pc/1 ro,nodev,relatime shared:63 - squashfs /dev/loop5 ro
+0:6 / /writable/system-data/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime shared:64 - squashfs /dev/loop6 ro
+0:7 / /writable/system-data/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime shared:65 - squashfs /dev/loop7 ro
+0:8 / /writable/system-data/snap/test-snapd-rsync/1 ro,nodev,relatime shared:66 - squashfs /dev/loop8 ro
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-core-16-64/PER-SNAP-16.expected.txt snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-core-16-64/PER-SNAP-16.expected.txt
--- snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-core-16-64/PER-SNAP-16.expected.txt 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-core-16-64/PER-SNAP-16.expected.txt 2019-10-30 12:17:43.000000000 +0000
@@ -1,228 +1,223 @@
-1001 1000 0:0 / / ro,relatime master:1 - squashfs /dev/loop0 ro
-1002 1001 1:0 / /boot/efi rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-1003 1001 1:0 /EFI/ubuntu /boot/grub rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-1004 1001 2:0 / /dev rw,nosuid,relatime master:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
-1005 1004 2:1 / /dev/hugepages rw,relatime master:4 - hugetlbfs hugetlbfs rw
-1006 1004 2:2 / /dev/mqueue rw,relatime master:5 - mqueue mqueue rw
-1007 1004 2:33 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-1008 1004 2:3 / /dev/pts rw,nosuid,noexec,relatime master:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
-1009 1008 2:33 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-1010 1004 2:4 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
-1011 1001 1:1 /system-data/etc/apparmor.d/cache /etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
-1012 1001 1:1 /system-data/etc/cloud /etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
-1013 1001 1:1 /system-data/etc/dbus-1/system.d /etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
-1014 1001 1:1 /system-data/etc/default/keyboard /etc/default/keyboard rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
-1015 1001 1:1 /system-data/etc/default/swapfile /etc/default/swapfile rw,relatime master:12 - ext4 /dev/sda3 rw,data=ordered
-1016 1001 1:1 /system-data/etc/environment /etc/environment rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1017 1001 2:5 /image.fstab /etc/fstab rw,nosuid,noexec,relatime master:14 - tmpfs tmpfs rw,mode=755
-1018 1001 1:1 /system-data/root/test-etc/group /etc/group ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1019 1001 1:1 /system-data/root/test-etc/gshadow /etc/gshadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1020 1001 1:1 /system-data/etc/hosts /etc/hosts rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
-1021 1001 1:1 /system-data/etc/init /etc/init rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
-1022 1001 1:1 /system-data/etc/init.d /etc/init.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
-1023 1001 1:1 /system-data/etc/iproute2 /etc/iproute2 rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
-1024 1001 1:1 /system-data/etc/machine-id /etc/machine-id rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
-1025 1001 1:1 /system-data/etc/modprobe.d /etc/modprobe.d rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
-1026 1001 1:1 /system-data/etc/modules-load.d /etc/modules-load.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
-1027 1001 1:1 /system-data/etc/netplan /etc/netplan rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
-1028 1001 1:1 /system-data/etc/network/if-up.d /etc/network/if-up.d rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
-1029 1001 1:1 /system-data/etc/network/interfaces.d /etc/network/interfaces.d rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
-1030 1001 1:1 /system-data/root/test-etc/passwd /etc/passwd ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1031 1001 1:1 /system-data/etc/ppp /etc/ppp rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
-1032 1001 1:1 /system-data/etc/rc0.d /etc/rc0.d rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
-1033 1001 1:1 /system-data/etc/rc1.d /etc/rc1.d rw,relatime master:28 - ext4 /dev/sda3 rw,data=ordered
-1034 1001 1:1 /system-data/etc/rc2.d /etc/rc2.d rw,relatime master:29 - ext4 /dev/sda3 rw,data=ordered
-1035 1001 1:1 /system-data/etc/rc3.d /etc/rc3.d rw,relatime master:30 - ext4 /dev/sda3 rw,data=ordered
-1036 1001 1:1 /system-data/etc/rc4.d /etc/rc4.d rw,relatime master:31 - ext4 /dev/sda3 rw,data=ordered
-1037 1001 1:1 /system-data/etc/rc5.d /etc/rc5.d rw,relatime master:32 - ext4 /dev/sda3 rw,data=ordered
-1038 1001 1:1 /system-data/etc/rc6.d /etc/rc6.d rw,relatime master:33 - ext4 /dev/sda3 rw,data=ordered
-1039 1001 1:1 /system-data/etc/rcS.d /etc/rcS.d rw,relatime master:34 - ext4 /dev/sda3 rw,data=ordered
-1040 1001 1:1 /system-data/etc/rsyslog.d /etc/rsyslog.d rw,relatime master:35 - ext4 /dev/sda3 rw,data=ordered
-1041 1001 1:1 /system-data/root/test-etc/shadow /etc/shadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1042 1001 1:1 /system-data/etc/ssh /etc/ssh rw,relatime master:36 - ext4 /dev/sda3 rw,data=ordered
-1043 1001 1:1 /system-data/etc/sudoers.d /etc/sudoers.d rw,relatime master:37 - ext4 /dev/sda3 rw,data=ordered
-1044 1001 1:1 /system-data/etc/sysctl.d /etc/sysctl.d rw,relatime master:38 - ext4 /dev/sda3 rw,data=ordered
-1045 1001 1:1 /system-data/etc/systemd/logind.conf.d /etc/systemd/logind.conf.d rw,relatime master:39 - ext4 /dev/sda3 rw,data=ordered
-1046 1001 1:1 /system-data/etc/systemd/network /etc/systemd/network rw,relatime master:40 - ext4 /dev/sda3 rw,data=ordered
-1047 1001 1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime master:41 - ext4 /dev/sda3 rw,data=ordered
-1048 1047 1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime master:42 - ext4 /dev/sda3 rw,data=ordered
-1049 1001 1:1 /system-data/etc/systemd/system.conf.d /etc/systemd/system.conf.d rw,relatime master:43 - ext4 /dev/sda3 rw,data=ordered
-1050 1001 1:1 /system-data/etc/systemd/timesyncd.conf /etc/systemd/timesyncd.conf rw,relatime master:44 - ext4 /dev/sda3 rw,data=ordered
-1051 1001 1:1 /system-data/etc/systemd/user.conf.d /etc/systemd/user.conf.d rw,relatime master:45 - ext4 /dev/sda3 rw,data=ordered
-1052 1001 1:1 /system-data/etc/udev/rules.d /etc/udev/rules.d rw,relatime master:46 - ext4 /dev/sda3 rw,data=ordered
-1053 1001 1:1 /system-data/etc/update-motd.d /etc/update-motd.d rw,relatime master:47 - ext4 /dev/sda3 rw,data=ordered
-1054 1001 1:1 /system-data/etc/writable /etc/writable rw,relatime master:48 - ext4 /dev/sda3 rw,data=ordered
-1055 1001 1:1 /user-data /home rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1056 1001 0:1 /firmware /lib/firmware ro,relatime master:49 - squashfs /dev/loop1 ro
-1057 1001 0:1 /modules /lib/modules ro,relatime master:50 - squashfs /dev/loop1 ro
-1058 1001 2:6 / /media rw,relatime master:51 - tmpfs tmpfs rw
-1059 1058 2:6 / /media rw,relatime shared:51 - tmpfs tmpfs rw
-1060 1001 2:7 / /mnt rw,relatime master:52 - tmpfs tmpfs rw
-1061 1001 2:8 / /proc rw,nosuid,nodev,noexec,relatime master:53 - proc proc rw
-1062 1061 2:9 / /proc/sys/fs/binfmt_misc rw,relatime master:54 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct
-1063 1001 1:1 /system-data/root /root rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1064 1001 2:5 / /run rw,nosuid,noexec,relatime master:56 - tmpfs tmpfs rw,mode=755
-1065 1064 2:10 / /run rw,nosuid,noexec,relatime master:55 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1066 1065 2:11 / /run/cgmanager/fs rw,relatime master:57 - tmpfs cgmfs rw,size=VARIABLE,mode=755
-1067 1065 2:12 / /run/lock rw,nosuid,nodev,noexec,relatime master:58 - tmpfs tmpfs rw,size=VARIABLE
-1068 1065 2:10 /netns /run/netns rw,nosuid,noexec,relatime shared:55 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1069 1065 2:10 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1070 1065 2:13 / /run/user/0 rw,nosuid,nodev,relatime master:59 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-1071 1001 1:1 /system-data/snap /snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1072 1071 1:1 /system-data/snap /snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1073 1071 0:2 / /snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
-1074 1072 0:2 / /snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
-1075 1071 0:3 / /snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
-1076 1072 0:3 / /snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
-1077 1071 0:4 / /snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
-1078 1072 0:4 / /snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
-1079 1071 0:5 / /snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
-1080 1072 0:5 / /snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
-1081 1071 0:6 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
-1082 1072 0:6 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
-1083 1071 0:7 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
-1084 1072 0:7 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
-1085 1071 0:8 / /snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
-1086 1072 0:8 / /snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
-1087 1001 2:14 / /sys rw,nosuid,nodev,noexec,relatime master:67 - sysfs sysfs rw
-1088 1087 2:15 / /sys/fs/cgroup rw master:68 - tmpfs tmpfs rw,mode=755
-1089 1088 2:16 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:69 - cgroup cgroup rw,blkio
-1090 1088 2:17 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:70 - cgroup cgroup rw,cpu,cpuacct
-1091 1088 2:18 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:71 - cgroup cgroup rw,cpuset,clone_children
-1092 1088 2:19 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:72 - cgroup cgroup rw,devices
-1093 1088 2:20 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:73 - cgroup cgroup rw,freezer
-1094 1088 2:21 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:74 - cgroup cgroup rw,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb
-1095 1088 2:22 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:75 - cgroup cgroup rw,memory
-1096 1088 2:23 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:76 - cgroup cgroup rw,net_cls,net_prio
-1097 1088 2:24 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:77 - cgroup cgroup rw,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event
-1098 1088 2:25 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:78 - cgroup cgroup rw,pids,release_agent=/run/cgmanager/agents/cgm-release-agent.pids
-1099 1088 2:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:79 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
-1100 1087 2:27 / /sys/fs/fuse/connections rw,relatime master:80 - fusectl fusectl rw
-1101 1087 2:28 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:81 - pstore pstore rw
-1102 1087 2:29 / /sys/kernel/debug rw,relatime master:82 - debugfs debugfs rw
-1103 1087 2:30 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:83 - securityfs securityfs rw
-1104 1001 2:31 / /tmp rw,relatime master:84 - tmpfs tmpfs rw
-1105 1104 2:31 /snap.test-snapd-mountinfo-core16/tmp /tmp rw,relatime - tmpfs tmpfs rw
-1106 1001 1:1 /system-data/var/cache/apparmor /var/cache/apparmor rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1107 1001 1:1 /system-data/var/cache/snapd /var/cache/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1108 1001 1:1 /system-data/var/lib/apparmor /var/lib/apparmor rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1109 1001 1:1 /system-data/var/lib/cloud /var/lib/cloud rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1110 1001 1:1 /system-data/var/lib/console-conf /var/lib/console-conf rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1111 1001 1:1 /system-data/var/lib/dbus /var/lib/dbus rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1112 1001 1:1 /system-data/var/lib/dhcp /var/lib/dhcp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1113 1001 1:1 /system-data/var/lib/extrausers /var/lib/extrausers rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1114 1001 1:1 /system-data/var/lib/initramfs-tools /var/lib/initramfs-tools rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1115 1001 1:1 /system-data/var/lib/logrotate /var/lib/logrotate rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1116 1001 1:1 /system-data/var/lib/misc /var/lib/misc rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1117 1001 1:1 /system-data/var/lib/snapd /var/lib/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1119 1118 0:0 / /var/lib/snapd/hostfs ro,relatime master:1 - squashfs /dev/loop0 ro
-1118 1117 1:1 /system-data/var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda3 rw,data=ordered
-1120 1119 1:0 / /var/lib/snapd/hostfs/boot/efi rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-1121 1119 1:0 /EFI/ubuntu /var/lib/snapd/hostfs/boot/grub rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-1122 1119 1:1 /system-data/etc/apparmor.d/cache /var/lib/snapd/hostfs/etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
-1123 1119 1:1 /system-data/etc/cloud /var/lib/snapd/hostfs/etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
-1124 1119 1:1 /system-data/etc/dbus-1/system.d /var/lib/snapd/hostfs/etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
-1125 1119 1:1 /system-data/etc/default/keyboard /var/lib/snapd/hostfs/etc/default/keyboard rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
-1126 1119 1:1 /system-data/etc/default/swapfile /var/lib/snapd/hostfs/etc/default/swapfile rw,relatime master:12 - ext4 /dev/sda3 rw,data=ordered
-1127 1119 1:1 /system-data/etc/environment /var/lib/snapd/hostfs/etc/environment rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1128 1119 2:5 /image.fstab /var/lib/snapd/hostfs/etc/fstab rw,nosuid,noexec,relatime master:14 - tmpfs tmpfs rw,mode=755
-1129 1119 1:1 /system-data/root/test-etc/group /var/lib/snapd/hostfs/etc/group ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1130 1119 1:1 /system-data/root/test-etc/gshadow /var/lib/snapd/hostfs/etc/gshadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1131 1119 1:1 /system-data/etc/hosts /var/lib/snapd/hostfs/etc/hosts rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
-1132 1119 1:1 /system-data/etc/init /var/lib/snapd/hostfs/etc/init rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
-1133 1119 1:1 /system-data/etc/init.d /var/lib/snapd/hostfs/etc/init.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
-1134 1119 1:1 /system-data/etc/iproute2 /var/lib/snapd/hostfs/etc/iproute2 rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
-1135 1119 1:1 /system-data/etc/machine-id /var/lib/snapd/hostfs/etc/machine-id rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
-1136 1119 1:1 /system-data/etc/modprobe.d /var/lib/snapd/hostfs/etc/modprobe.d rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
-1137 1119 1:1 /system-data/etc/modules-load.d /var/lib/snapd/hostfs/etc/modules-load.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
-1138 1119 1:1 /system-data/etc/netplan /var/lib/snapd/hostfs/etc/netplan rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
-1139 1119 1:1 /system-data/etc/network/if-up.d /var/lib/snapd/hostfs/etc/network/if-up.d rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
-1140 1119 1:1 /system-data/etc/network/interfaces.d /var/lib/snapd/hostfs/etc/network/interfaces.d rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
-1141 1119 1:1 /system-data/root/test-etc/passwd /var/lib/snapd/hostfs/etc/passwd ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1142 1119 1:1 /system-data/etc/ppp /var/lib/snapd/hostfs/etc/ppp rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
-1143 1119 1:1 /system-data/etc/rc0.d /var/lib/snapd/hostfs/etc/rc0.d rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
-1144 1119 1:1 /system-data/etc/rc1.d /var/lib/snapd/hostfs/etc/rc1.d rw,relatime master:28 - ext4 /dev/sda3 rw,data=ordered
-1145 1119 1:1 /system-data/etc/rc2.d /var/lib/snapd/hostfs/etc/rc2.d rw,relatime master:29 - ext4 /dev/sda3 rw,data=ordered
-1146 1119 1:1 /system-data/etc/rc3.d /var/lib/snapd/hostfs/etc/rc3.d rw,relatime master:30 - ext4 /dev/sda3 rw,data=ordered
-1147 1119 1:1 /system-data/etc/rc4.d /var/lib/snapd/hostfs/etc/rc4.d rw,relatime master:31 - ext4 /dev/sda3 rw,data=ordered
-1148 1119 1:1 /system-data/etc/rc5.d /var/lib/snapd/hostfs/etc/rc5.d rw,relatime master:32 - ext4 /dev/sda3 rw,data=ordered
-1149 1119 1:1 /system-data/etc/rc6.d /var/lib/snapd/hostfs/etc/rc6.d rw,relatime master:33 - ext4 /dev/sda3 rw,data=ordered
-1150 1119 1:1 /system-data/etc/rcS.d /var/lib/snapd/hostfs/etc/rcS.d rw,relatime master:34 - ext4 /dev/sda3 rw,data=ordered
-1151 1119 1:1 /system-data/etc/rsyslog.d /var/lib/snapd/hostfs/etc/rsyslog.d rw,relatime master:35 - ext4 /dev/sda3 rw,data=ordered
-1152 1119 1:1 /system-data/root/test-etc/shadow /var/lib/snapd/hostfs/etc/shadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1153 1119 1:1 /system-data/etc/ssh /var/lib/snapd/hostfs/etc/ssh rw,relatime master:36 - ext4 /dev/sda3 rw,data=ordered
-1154 1119 1:1 /system-data/etc/sudoers.d /var/lib/snapd/hostfs/etc/sudoers.d rw,relatime master:37 - ext4 /dev/sda3 rw,data=ordered
-1155 1119 1:1 /system-data/etc/sysctl.d /var/lib/snapd/hostfs/etc/sysctl.d rw,relatime master:38 - ext4 /dev/sda3 rw,data=ordered
-1156 1119 1:1 /system-data/etc/systemd/logind.conf.d /var/lib/snapd/hostfs/etc/systemd/logind.conf.d rw,relatime master:39 - ext4 /dev/sda3 rw,data=ordered
-1157 1119 1:1 /system-data/etc/systemd/network /var/lib/snapd/hostfs/etc/systemd/network rw,relatime master:40 - ext4 /dev/sda3 rw,data=ordered
-1158 1119 1:1 /system-data/etc/systemd/system /var/lib/snapd/hostfs/etc/systemd/system rw,relatime master:41 - ext4 /dev/sda3 rw,data=ordered
-1159 1158 1:1 /system-data/etc/systemd/system /var/lib/snapd/hostfs/etc/systemd/system rw,relatime master:42 - ext4 /dev/sda3 rw,data=ordered
-1160 1119 1:1 /system-data/etc/systemd/system.conf.d /var/lib/snapd/hostfs/etc/systemd/system.conf.d rw,relatime master:43 - ext4 /dev/sda3 rw,data=ordered
-1161 1119 1:1 /system-data/etc/systemd/timesyncd.conf /var/lib/snapd/hostfs/etc/systemd/timesyncd.conf rw,relatime master:44 - ext4 /dev/sda3 rw,data=ordered
-1162 1119 1:1 /system-data/etc/systemd/user.conf.d /var/lib/snapd/hostfs/etc/systemd/user.conf.d rw,relatime master:45 - ext4 /dev/sda3 rw,data=ordered
-1163 1119 1:1 /system-data/etc/udev/rules.d /var/lib/snapd/hostfs/etc/udev/rules.d rw,relatime master:46 - ext4 /dev/sda3 rw,data=ordered
-1164 1119 1:1 /system-data/etc/update-motd.d /var/lib/snapd/hostfs/etc/update-motd.d rw,relatime master:47 - ext4 /dev/sda3 rw,data=ordered
-1165 1119 1:1 /system-data/etc/writable /var/lib/snapd/hostfs/etc/writable rw,relatime master:48 - ext4 /dev/sda3 rw,data=ordered
-1166 1119 1:1 /user-data /var/lib/snapd/hostfs/home rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1167 1119 0:1 /firmware /var/lib/snapd/hostfs/lib/firmware ro,relatime master:49 - squashfs /dev/loop1 ro
-1168 1119 0:1 /modules /var/lib/snapd/hostfs/lib/modules ro,relatime master:50 - squashfs /dev/loop1 ro
-1169 1119 2:6 / /var/lib/snapd/hostfs/media rw,relatime master:51 - tmpfs tmpfs rw
-1170 1119 2:7 / /var/lib/snapd/hostfs/mnt rw,relatime master:52 - tmpfs tmpfs rw
-1171 1119 1:1 /system-data/root /var/lib/snapd/hostfs/root rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1172 1119 2:5 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:56 - tmpfs tmpfs rw,mode=755
-1173 1172 2:10 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:55 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1174 1173 2:11 / /var/lib/snapd/hostfs/run/cgmanager/fs rw,relatime master:57 - tmpfs cgmfs rw,size=VARIABLE,mode=755
-1175 1173 2:12 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:58 - tmpfs tmpfs rw,size=VARIABLE
-1176 1173 2:10 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1177 1173 2:13 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:59 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-1178 1119 1:1 /system-data/snap /var/lib/snapd/hostfs/snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1179 1178 0:2 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
-1180 1178 0:3 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
-1181 1178 0:4 / /var/lib/snapd/hostfs/snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
-1182 1178 0:5 / /var/lib/snapd/hostfs/snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
-1183 1178 0:6 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
-1184 1178 0:7 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
-1185 1178 0:8 / /var/lib/snapd/hostfs/snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
-1186 1119 2:31 / /var/lib/snapd/hostfs/tmp rw,relatime master:84 - tmpfs tmpfs rw
-1187 1119 1:1 /system-data/var/cache/apparmor /var/lib/snapd/hostfs/var/cache/apparmor rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1188 1119 1:1 /system-data/var/cache/snapd /var/lib/snapd/hostfs/var/cache/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1189 1119 1:1 /system-data/var/lib/apparmor /var/lib/snapd/hostfs/var/lib/apparmor rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1190 1119 1:1 /system-data/var/lib/cloud /var/lib/snapd/hostfs/var/lib/cloud rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1191 1119 1:1 /system-data/var/lib/console-conf /var/lib/snapd/hostfs/var/lib/console-conf rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1192 1119 1:1 /system-data/var/lib/dbus /var/lib/snapd/hostfs/var/lib/dbus rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1193 1119 1:1 /system-data/var/lib/dhcp /var/lib/snapd/hostfs/var/lib/dhcp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1194 1119 1:1 /system-data/var/lib/extrausers /var/lib/snapd/hostfs/var/lib/extrausers rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1195 1119 1:1 /system-data/var/lib/initramfs-tools /var/lib/snapd/hostfs/var/lib/initramfs-tools rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1196 1119 1:1 /system-data/var/lib/logrotate /var/lib/snapd/hostfs/var/lib/logrotate rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1197 1119 1:1 /system-data/var/lib/misc /var/lib/snapd/hostfs/var/lib/misc rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1198 1119 1:1 /system-data/var/lib/snapd /var/lib/snapd/hostfs/var/lib/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1199 1119 2:32 / /var/lib/snapd/hostfs/var/lib/sudo rw,relatime master:85 - tmpfs tmpfs rw,mode=700
-1200 1119 1:1 /system-data/var/lib/systemd/random-seed /var/lib/snapd/hostfs/var/lib/systemd/random-seed rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1201 1119 1:1 /system-data/var/lib/systemd/rfkill /var/lib/snapd/hostfs/var/lib/systemd/rfkill rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1202 1119 1:1 /system-data/var/lib/waagent /var/lib/snapd/hostfs/var/lib/waagent rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1203 1119 1:1 /system-data/var/log /var/lib/snapd/hostfs/var/log rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1204 1119 1:1 /system-data/var/snap /var/lib/snapd/hostfs/var/snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1205 1119 1:1 /system-data/var/tmp /var/lib/snapd/hostfs/var/tmp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1206 1119 1:1 / /var/lib/snapd/hostfs/writable rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1207 1206 0:2 / /var/lib/snapd/hostfs/writable/system-data/snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
-1208 1206 0:3 / /var/lib/snapd/hostfs/writable/system-data/snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
-1209 1206 0:4 / /var/lib/snapd/hostfs/writable/system-data/snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
-1210 1206 0:5 / /var/lib/snapd/hostfs/writable/system-data/snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
-1211 1206 0:6 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
-1212 1206 0:7 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
-1213 1206 0:8 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
-1214 1001 2:32 / /var/lib/sudo rw,relatime master:85 - tmpfs tmpfs rw,mode=700
-1215 1001 1:1 /system-data/var/lib/systemd/random-seed /var/lib/systemd/random-seed rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1216 1001 1:1 /system-data/var/lib/systemd/rfkill /var/lib/systemd/rfkill rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1217 1001 1:1 /system-data/var/lib/waagent /var/lib/waagent rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1218 1001 1:1 /system-data/var/log /var/log rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1219 1001 1:1 /system-data/var/snap /var/snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1220 1001 1:1 /system-data/var/tmp /var/tmp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1221 1001 1:1 / /writable rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1222 1221 0:2 / /writable/system-data/snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
-1223 1221 0:3 / /writable/system-data/snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
-1224 1221 0:4 / /writable/system-data/snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
-1225 1221 0:5 / /writable/system-data/snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
-1226 1221 0:6 / /writable/system-data/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
-1227 1221 0:7 / /writable/system-data/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
-1228 1221 0:8 / /writable/system-data/snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
+0:0 / / ro,relatime master:1 - squashfs /dev/loop0 ro
+1:0 / /boot/efi rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:0 /EFI/ubuntu /boot/grub rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+2:0 / /dev rw,nosuid,relatime master:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
+2:1 / /dev/hugepages rw,relatime master:4 - hugetlbfs hugetlbfs rw
+2:2 / /dev/mqueue rw,relatime master:5 - mqueue mqueue rw
+2:33 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+2:3 / /dev/pts rw,nosuid,noexec,relatime master:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
+2:33 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+2:4 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
+1:1 /system-data/etc/apparmor.d/cache /etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/cloud /etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/dbus-1/system.d /etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/keyboard /etc/default/keyboard rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/swapfile /etc/default/swapfile rw,relatime master:12 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/environment /etc/environment rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+2:5 /image.fstab /etc/fstab rw,nosuid,noexec,relatime master:14 - tmpfs tmpfs rw,mode=755
+1:1 /system-data/root/test-etc/group /etc/group ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/gshadow /etc/gshadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/hosts /etc/hosts rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/init /etc/init rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/init.d /etc/init.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/iproute2 /etc/iproute2 rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/machine-id /etc/machine-id rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modprobe.d /etc/modprobe.d rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modules-load.d /etc/modules-load.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/netplan /etc/netplan rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/if-up.d /etc/network/if-up.d rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/interfaces.d /etc/network/interfaces.d rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/passwd /etc/passwd ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ppp /etc/ppp rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc0.d /etc/rc0.d rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc1.d /etc/rc1.d rw,relatime master:28 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc2.d /etc/rc2.d rw,relatime master:29 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc3.d /etc/rc3.d rw,relatime master:30 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc4.d /etc/rc4.d rw,relatime master:31 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc5.d /etc/rc5.d rw,relatime master:32 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc6.d /etc/rc6.d rw,relatime master:33 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rcS.d /etc/rcS.d rw,relatime master:34 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rsyslog.d /etc/rsyslog.d rw,relatime master:35 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/shadow /etc/shadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ssh /etc/ssh rw,relatime master:36 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sudoers.d /etc/sudoers.d rw,relatime master:37 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sysctl.d /etc/sysctl.d rw,relatime master:38 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/logind.conf.d /etc/systemd/logind.conf.d rw,relatime master:39 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/network /etc/systemd/network rw,relatime master:40 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime master:41 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime master:42 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system.conf.d /etc/systemd/system.conf.d rw,relatime master:43 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/timesyncd.conf /etc/systemd/timesyncd.conf rw,relatime master:44 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/user.conf.d /etc/systemd/user.conf.d rw,relatime master:45 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/udev/rules.d /etc/udev/rules.d rw,relatime master:46 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/update-motd.d /etc/update-motd.d rw,relatime master:47 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/writable /etc/writable rw,relatime master:48 - ext4 /dev/sda3 rw,data=ordered
+1:1 /user-data /home rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:1 /firmware /lib/firmware ro,relatime master:49 - squashfs /dev/loop1 ro
+0:1 /modules /lib/modules ro,relatime master:50 - squashfs /dev/loop1 ro
+2:6 / /media rw,relatime master:51 - tmpfs tmpfs rw
+2:6 / /media rw,relatime shared:51 - tmpfs tmpfs rw
+2:7 / /mnt rw,relatime master:52 - tmpfs tmpfs rw
+2:8 / /proc rw,nosuid,nodev,noexec,relatime master:53 - proc proc rw
+2:9 / /proc/sys/fs/binfmt_misc rw,relatime master:54 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct
+1:1 /system-data/root /root rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+2:5 / /run rw,nosuid,noexec,relatime master:56 - tmpfs tmpfs rw,mode=755
+2:10 / /run rw,nosuid,noexec,relatime master:55 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:11 / /run/cgmanager/fs rw,relatime master:57 - tmpfs cgmfs rw,size=VARIABLE,mode=755
+2:12 / /run/lock rw,nosuid,nodev,noexec,relatime master:58 - tmpfs tmpfs rw,size=VARIABLE
+2:10 /netns /run/netns rw,nosuid,noexec,relatime shared:55 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:10 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:13 / /run/user/0 rw,nosuid,nodev,relatime master:59 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+1:1 /system-data/snap /snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
+0:3 / /snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
+0:4 / /snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
+0:5 / /snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
+0:6 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
+0:7 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
+0:8 / /snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
+2:14 / /sys rw,nosuid,nodev,noexec,relatime master:67 - sysfs sysfs rw
+2:15 / /sys/fs/cgroup rw master:68 - tmpfs tmpfs rw,mode=755
+2:16 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:69 - cgroup cgroup rw,blkio
+2:17 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:70 - cgroup cgroup rw,cpu,cpuacct
+2:18 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:71 - cgroup cgroup rw,cpuset,clone_children
+2:19 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:72 - cgroup cgroup rw,devices
+2:20 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:73 - cgroup cgroup rw,freezer
+2:21 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:74 - cgroup cgroup rw,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb
+2:22 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:75 - cgroup cgroup rw,memory
+2:23 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:76 - cgroup cgroup rw,net_cls,net_prio
+2:24 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:77 - cgroup cgroup rw,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event
+2:25 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:78 - cgroup cgroup rw,pids,release_agent=/run/cgmanager/agents/cgm-release-agent.pids
+2:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:79 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
+2:27 / /sys/fs/fuse/connections rw,relatime master:80 - fusectl fusectl rw
+2:28 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:81 - pstore pstore rw
+2:29 / /sys/kernel/debug rw,relatime master:82 - debugfs debugfs rw
+2:30 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:83 - securityfs securityfs rw
+2:31 / /tmp rw,relatime master:84 - tmpfs tmpfs rw
+2:31 /snap.test-snapd-mountinfo-core16/tmp /tmp rw,relatime - tmpfs tmpfs rw
+2:34 / /usr/share/gdb rw,relatime - tmpfs tmpfs rw,mode=755
+0:0 /usr/share/gdb/auto-load /usr/share/gdb/auto-load ro,relatime master:1 - squashfs /dev/loop0 ro
+2:35 / /usr/share/gdb/test rw,relatime - tmpfs tmpfs rw
+1:1 /system-data/var/cache/apparmor /var/cache/apparmor rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/cache/snapd /var/cache/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/apparmor /var/lib/apparmor rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/cloud /var/lib/cloud rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/console-conf /var/lib/console-conf rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/dbus /var/lib/dbus rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/dhcp /var/lib/dhcp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/extrausers /var/lib/extrausers rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/initramfs-tools /var/lib/initramfs-tools rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/logrotate /var/lib/logrotate rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/misc /var/lib/misc rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/snapd /var/lib/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:0 / /var/lib/snapd/hostfs ro,relatime master:1 - squashfs /dev/loop0 ro
+1:1 /system-data/var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda3 rw,data=ordered
+1:0 / /var/lib/snapd/hostfs/boot/efi rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:0 /EFI/ubuntu /var/lib/snapd/hostfs/boot/grub rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:1 /system-data/etc/apparmor.d/cache /var/lib/snapd/hostfs/etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/cloud /var/lib/snapd/hostfs/etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/dbus-1/system.d /var/lib/snapd/hostfs/etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/keyboard /var/lib/snapd/hostfs/etc/default/keyboard rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/swapfile /var/lib/snapd/hostfs/etc/default/swapfile rw,relatime master:12 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/environment /var/lib/snapd/hostfs/etc/environment rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+2:5 /image.fstab /var/lib/snapd/hostfs/etc/fstab rw,nosuid,noexec,relatime master:14 - tmpfs tmpfs rw,mode=755
+1:1 /system-data/root/test-etc/group /var/lib/snapd/hostfs/etc/group ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/gshadow /var/lib/snapd/hostfs/etc/gshadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/hosts /var/lib/snapd/hostfs/etc/hosts rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/init /var/lib/snapd/hostfs/etc/init rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/init.d /var/lib/snapd/hostfs/etc/init.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/iproute2 /var/lib/snapd/hostfs/etc/iproute2 rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/machine-id /var/lib/snapd/hostfs/etc/machine-id rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modprobe.d /var/lib/snapd/hostfs/etc/modprobe.d rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modules-load.d /var/lib/snapd/hostfs/etc/modules-load.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/netplan /var/lib/snapd/hostfs/etc/netplan rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/if-up.d /var/lib/snapd/hostfs/etc/network/if-up.d rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/interfaces.d /var/lib/snapd/hostfs/etc/network/interfaces.d rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/passwd /var/lib/snapd/hostfs/etc/passwd ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ppp /var/lib/snapd/hostfs/etc/ppp rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc0.d /var/lib/snapd/hostfs/etc/rc0.d rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc1.d /var/lib/snapd/hostfs/etc/rc1.d rw,relatime master:28 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc2.d /var/lib/snapd/hostfs/etc/rc2.d rw,relatime master:29 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc3.d /var/lib/snapd/hostfs/etc/rc3.d rw,relatime master:30 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc4.d /var/lib/snapd/hostfs/etc/rc4.d rw,relatime master:31 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc5.d /var/lib/snapd/hostfs/etc/rc5.d rw,relatime master:32 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc6.d /var/lib/snapd/hostfs/etc/rc6.d rw,relatime master:33 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rcS.d /var/lib/snapd/hostfs/etc/rcS.d rw,relatime master:34 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rsyslog.d /var/lib/snapd/hostfs/etc/rsyslog.d rw,relatime master:35 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/shadow /var/lib/snapd/hostfs/etc/shadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ssh /var/lib/snapd/hostfs/etc/ssh rw,relatime master:36 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sudoers.d /var/lib/snapd/hostfs/etc/sudoers.d rw,relatime master:37 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sysctl.d /var/lib/snapd/hostfs/etc/sysctl.d rw,relatime master:38 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/logind.conf.d /var/lib/snapd/hostfs/etc/systemd/logind.conf.d rw,relatime master:39 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/network /var/lib/snapd/hostfs/etc/systemd/network rw,relatime master:40 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /var/lib/snapd/hostfs/etc/systemd/system rw,relatime master:41 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /var/lib/snapd/hostfs/etc/systemd/system rw,relatime master:42 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system.conf.d /var/lib/snapd/hostfs/etc/systemd/system.conf.d rw,relatime master:43 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/timesyncd.conf /var/lib/snapd/hostfs/etc/systemd/timesyncd.conf rw,relatime master:44 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/user.conf.d /var/lib/snapd/hostfs/etc/systemd/user.conf.d rw,relatime master:45 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/udev/rules.d /var/lib/snapd/hostfs/etc/udev/rules.d rw,relatime master:46 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/update-motd.d /var/lib/snapd/hostfs/etc/update-motd.d rw,relatime master:47 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/writable /var/lib/snapd/hostfs/etc/writable rw,relatime master:48 - ext4 /dev/sda3 rw,data=ordered
+1:1 /user-data /var/lib/snapd/hostfs/home rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:1 /firmware /var/lib/snapd/hostfs/lib/firmware ro,relatime master:49 - squashfs /dev/loop1 ro
+0:1 /modules /var/lib/snapd/hostfs/lib/modules ro,relatime master:50 - squashfs /dev/loop1 ro
+2:6 / /var/lib/snapd/hostfs/media rw,relatime master:51 - tmpfs tmpfs rw
+2:7 / /var/lib/snapd/hostfs/mnt rw,relatime master:52 - tmpfs tmpfs rw
+1:1 /system-data/root /var/lib/snapd/hostfs/root rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+2:5 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:56 - tmpfs tmpfs rw,mode=755
+2:10 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:55 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:11 / /var/lib/snapd/hostfs/run/cgmanager/fs rw,relatime master:57 - tmpfs cgmfs rw,size=VARIABLE,mode=755
+2:12 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:58 - tmpfs tmpfs rw,size=VARIABLE
+2:10 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:13 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:59 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+1:1 /system-data/snap /var/lib/snapd/hostfs/snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
+0:3 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
+0:4 / /var/lib/snapd/hostfs/snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
+0:5 / /var/lib/snapd/hostfs/snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
+0:6 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
+0:7 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
+0:8 / /var/lib/snapd/hostfs/snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
+2:31 / /var/lib/snapd/hostfs/tmp rw,relatime master:84 - tmpfs tmpfs rw
+1:1 /system-data/var/cache/apparmor /var/lib/snapd/hostfs/var/cache/apparmor rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/cache/snapd /var/lib/snapd/hostfs/var/cache/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/apparmor /var/lib/snapd/hostfs/var/lib/apparmor rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/cloud /var/lib/snapd/hostfs/var/lib/cloud rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/console-conf /var/lib/snapd/hostfs/var/lib/console-conf rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/dbus /var/lib/snapd/hostfs/var/lib/dbus rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/dhcp /var/lib/snapd/hostfs/var/lib/dhcp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/extrausers /var/lib/snapd/hostfs/var/lib/extrausers rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/initramfs-tools /var/lib/snapd/hostfs/var/lib/initramfs-tools rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/logrotate /var/lib/snapd/hostfs/var/lib/logrotate rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/misc /var/lib/snapd/hostfs/var/lib/misc rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/snapd /var/lib/snapd/hostfs/var/lib/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+2:32 / /var/lib/snapd/hostfs/var/lib/sudo rw,relatime master:85 - tmpfs tmpfs rw,mode=700
+1:1 /system-data/var/lib/systemd/random-seed /var/lib/snapd/hostfs/var/lib/systemd/random-seed rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/systemd/rfkill /var/lib/snapd/hostfs/var/lib/systemd/rfkill rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/waagent /var/lib/snapd/hostfs/var/lib/waagent rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/log /var/lib/snapd/hostfs/var/log rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/snap /var/lib/snapd/hostfs/var/snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/tmp /var/lib/snapd/hostfs/var/tmp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 / /var/lib/snapd/hostfs/writable rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /var/lib/snapd/hostfs/writable/system-data/snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
+0:3 / /var/lib/snapd/hostfs/writable/system-data/snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
+0:4 / /var/lib/snapd/hostfs/writable/system-data/snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
+0:5 / /var/lib/snapd/hostfs/writable/system-data/snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
+0:6 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
+0:7 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
+0:8 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
+2:32 / /var/lib/sudo rw,relatime master:85 - tmpfs tmpfs rw,mode=700
+1:1 /system-data/var/lib/systemd/random-seed /var/lib/systemd/random-seed rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/systemd/rfkill /var/lib/systemd/rfkill rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/waagent /var/lib/waagent rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/log /var/log rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/snap /var/snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/tmp /var/tmp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 / /writable rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /writable/system-data/snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
+0:3 / /writable/system-data/snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
+0:4 / /writable/system-data/snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
+0:5 / /writable/system-data/snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
+0:6 / /writable/system-data/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
+0:7 / /writable/system-data/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
+0:8 / /writable/system-data/snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-core-16-64/PER-SNAP-18.expected.txt snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-core-16-64/PER-SNAP-18.expected.txt
--- snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-core-16-64/PER-SNAP-18.expected.txt 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-core-16-64/PER-SNAP-18.expected.txt 2019-10-30 12:17:43.000000000 +0000
@@ -1,199 +1,202 @@
-1001 1000 0:3 / / ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
-1002 1001 2:0 / /dev rw,nosuid,relatime master:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
-1003 1002 2:1 / /dev/hugepages rw,relatime master:4 - hugetlbfs hugetlbfs rw
-1004 1002 2:2 / /dev/mqueue rw,relatime master:5 - mqueue mqueue rw
-1005 1002 2:33 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-1006 1002 2:3 / /dev/pts rw,nosuid,noexec,relatime master:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
-1007 1006 2:33 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-1008 1002 2:4 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
-1009 1001 0:0 /etc /etc ro,relatime master:1 - squashfs /dev/loop0 ro
-1010 1009 1:1 /system-data/etc/apparmor.d/cache /etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
-1011 1009 1:1 /system-data/etc/cloud /etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
-1012 1009 1:1 /system-data/etc/dbus-1/system.d /etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
-1013 1009 1:1 /system-data/etc/default/keyboard /etc/default/keyboard rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
-1014 1009 1:1 /system-data/etc/default/swapfile /etc/default/swapfile rw,relatime master:12 - ext4 /dev/sda3 rw,data=ordered
-1015 1009 1:1 /system-data/etc/environment /etc/environment rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1016 1009 2:5 /image.fstab /etc/fstab rw,nosuid,noexec,relatime master:14 - tmpfs tmpfs rw,mode=755
-1017 1009 1:1 /system-data/root/test-etc/group /etc/group ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1018 1009 1:1 /system-data/root/test-etc/gshadow /etc/gshadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1019 1009 1:1 /system-data/etc/hosts /etc/hosts rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
-1020 1009 1:1 /system-data/etc/init /etc/init rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
-1021 1009 1:1 /system-data/etc/init.d /etc/init.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
-1022 1009 1:1 /system-data/etc/iproute2 /etc/iproute2 rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
-1023 1009 1:1 /system-data/etc/machine-id /etc/machine-id rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
-1024 1009 1:1 /system-data/etc/modprobe.d /etc/modprobe.d rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
-1025 1009 1:1 /system-data/etc/modules-load.d /etc/modules-load.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
-1026 1009 1:1 /system-data/etc/netplan /etc/netplan rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
-1027 1009 1:1 /system-data/etc/network/if-up.d /etc/network/if-up.d rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
-1028 1009 1:1 /system-data/etc/network/interfaces.d /etc/network/interfaces.d rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
-1029 1009 0:3 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
-1030 1009 1:1 /system-data/root/test-etc/passwd /etc/passwd ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1031 1009 1:1 /system-data/etc/ppp /etc/ppp rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
-1032 1009 1:1 /system-data/etc/rc0.d /etc/rc0.d rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
-1033 1009 1:1 /system-data/etc/rc1.d /etc/rc1.d rw,relatime master:28 - ext4 /dev/sda3 rw,data=ordered
-1034 1009 1:1 /system-data/etc/rc2.d /etc/rc2.d rw,relatime master:29 - ext4 /dev/sda3 rw,data=ordered
-1035 1009 1:1 /system-data/etc/rc3.d /etc/rc3.d rw,relatime master:30 - ext4 /dev/sda3 rw,data=ordered
-1036 1009 1:1 /system-data/etc/rc4.d /etc/rc4.d rw,relatime master:31 - ext4 /dev/sda3 rw,data=ordered
-1037 1009 1:1 /system-data/etc/rc5.d /etc/rc5.d rw,relatime master:32 - ext4 /dev/sda3 rw,data=ordered
-1038 1009 1:1 /system-data/etc/rc6.d /etc/rc6.d rw,relatime master:33 - ext4 /dev/sda3 rw,data=ordered
-1039 1009 1:1 /system-data/etc/rcS.d /etc/rcS.d rw,relatime master:34 - ext4 /dev/sda3 rw,data=ordered
-1040 1009 1:1 /system-data/etc/rsyslog.d /etc/rsyslog.d rw,relatime master:35 - ext4 /dev/sda3 rw,data=ordered
-1041 1009 1:1 /system-data/root/test-etc/shadow /etc/shadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1042 1009 1:1 /system-data/etc/ssh /etc/ssh rw,relatime master:36 - ext4 /dev/sda3 rw,data=ordered
-1043 1009 0:3 /etc/ssl /etc/ssl ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
-1044 1009 1:1 /system-data/etc/sudoers.d /etc/sudoers.d rw,relatime master:37 - ext4 /dev/sda3 rw,data=ordered
-1045 1009 1:1 /system-data/etc/sysctl.d /etc/sysctl.d rw,relatime master:38 - ext4 /dev/sda3 rw,data=ordered
-1046 1009 1:1 /system-data/etc/systemd/logind.conf.d /etc/systemd/logind.conf.d rw,relatime master:39 - ext4 /dev/sda3 rw,data=ordered
-1047 1009 1:1 /system-data/etc/systemd/network /etc/systemd/network rw,relatime master:40 - ext4 /dev/sda3 rw,data=ordered
-1048 1009 1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime master:41 - ext4 /dev/sda3 rw,data=ordered
-1049 1048 1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime master:42 - ext4 /dev/sda3 rw,data=ordered
-1050 1009 1:1 /system-data/etc/systemd/system.conf.d /etc/systemd/system.conf.d rw,relatime master:43 - ext4 /dev/sda3 rw,data=ordered
-1051 1009 1:1 /system-data/etc/systemd/timesyncd.conf /etc/systemd/timesyncd.conf rw,relatime master:44 - ext4 /dev/sda3 rw,data=ordered
-1052 1009 1:1 /system-data/etc/systemd/user.conf.d /etc/systemd/user.conf.d rw,relatime master:45 - ext4 /dev/sda3 rw,data=ordered
-1053 1009 1:1 /system-data/etc/udev/rules.d /etc/udev/rules.d rw,relatime master:46 - ext4 /dev/sda3 rw,data=ordered
-1054 1009 1:1 /system-data/etc/update-motd.d /etc/update-motd.d rw,relatime master:47 - ext4 /dev/sda3 rw,data=ordered
-1055 1009 1:1 /system-data/etc/writable /etc/writable rw,relatime master:48 - ext4 /dev/sda3 rw,data=ordered
-1056 1001 1:1 /user-data /home rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1057 1001 0:1 /firmware /lib/firmware ro,relatime master:49 - squashfs /dev/loop1 ro
-1058 1001 0:1 /modules /lib/modules ro,relatime master:50 - squashfs /dev/loop1 ro
-1059 1001 2:6 / /media rw,relatime shared:51 - tmpfs tmpfs rw
-1060 1001 2:7 / /mnt rw,relatime master:52 - tmpfs tmpfs rw
-1061 1001 2:8 / /proc rw,nosuid,nodev,noexec,relatime master:53 - proc proc rw
-1062 1061 2:9 / /proc/sys/fs/binfmt_misc rw,relatime master:54 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct
-1063 1001 1:1 /system-data/root /root rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1064 1001 2:10 / /run rw,nosuid,noexec,relatime master:55 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1065 1064 2:11 / /run/cgmanager/fs rw,relatime master:57 - tmpfs cgmfs rw,size=VARIABLE,mode=755
-1066 1064 2:12 / /run/lock rw,nosuid,nodev,noexec,relatime master:58 - tmpfs tmpfs rw,size=VARIABLE
-1067 1064 2:10 /netns /run/netns rw,nosuid,noexec,relatime shared:55 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1068 1064 2:10 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1069 1064 2:13 / /run/user/0 rw,nosuid,nodev,relatime master:59 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-1070 1001 1:1 /system-data/snap /snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1071 1070 0:2 / /snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
-1072 1070 0:3 / /snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
-1073 1070 0:4 / /snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
-1074 1070 0:5 / /snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
-1075 1070 0:6 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
-1076 1070 0:7 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
-1077 1070 0:8 / /snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
-1078 1001 2:14 / /sys rw,nosuid,nodev,noexec,relatime master:67 - sysfs sysfs rw
-1079 1078 2:15 / /sys/fs/cgroup rw master:68 - tmpfs tmpfs rw,mode=755
-1080 1079 2:16 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:69 - cgroup cgroup rw,blkio
-1081 1079 2:17 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:70 - cgroup cgroup rw,cpu,cpuacct
-1082 1079 2:18 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:71 - cgroup cgroup rw,cpuset,clone_children
-1083 1079 2:19 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:72 - cgroup cgroup rw,devices
-1084 1079 2:20 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:73 - cgroup cgroup rw,freezer
-1085 1079 2:21 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:74 - cgroup cgroup rw,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb
-1086 1079 2:22 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:75 - cgroup cgroup rw,memory
-1087 1079 2:23 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:76 - cgroup cgroup rw,net_cls,net_prio
-1088 1079 2:24 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:77 - cgroup cgroup rw,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event
-1089 1079 2:25 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:78 - cgroup cgroup rw,pids,release_agent=/run/cgmanager/agents/cgm-release-agent.pids
-1090 1079 2:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:79 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
-1091 1078 2:27 / /sys/fs/fuse/connections rw,relatime master:80 - fusectl fusectl rw
-1092 1078 2:28 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:81 - pstore pstore rw
-1093 1078 2:29 / /sys/kernel/debug rw,relatime master:82 - debugfs debugfs rw
-1094 1078 2:30 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:83 - securityfs securityfs rw
-1095 1001 2:31 / /tmp rw,relatime master:84 - tmpfs tmpfs rw
-1096 1095 2:31 /snap.test-snapd-mountinfo-core18/tmp /tmp rw,relatime - tmpfs tmpfs rw
-1097 1001 0:0 /usr/lib/snapd /usr/lib/snapd ro,relatime master:1 - squashfs /dev/loop0 ro
-1098 1001 0:0 /usr/src /usr/src ro,relatime master:1 - squashfs /dev/loop0 ro
-1099 1001 1:1 /system-data/var/lib/extrausers /var/lib/extrausers rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1100 1001 1:1 /system-data/var/lib/snapd /var/lib/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1102 1101 0:0 / /var/lib/snapd/hostfs ro,relatime master:1 - squashfs /dev/loop0 ro
-1101 1100 1:1 /system-data/var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda3 rw,data=ordered
-1103 1102 1:0 / /var/lib/snapd/hostfs/boot/efi rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-1104 1102 1:0 /EFI/ubuntu /var/lib/snapd/hostfs/boot/grub rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-1105 1102 1:1 /system-data/etc/apparmor.d/cache /var/lib/snapd/hostfs/etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
-1106 1102 1:1 /system-data/etc/cloud /var/lib/snapd/hostfs/etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
-1107 1102 1:1 /system-data/etc/dbus-1/system.d /var/lib/snapd/hostfs/etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
-1108 1102 1:1 /system-data/etc/default/keyboard /var/lib/snapd/hostfs/etc/default/keyboard rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
-1109 1102 1:1 /system-data/etc/default/swapfile /var/lib/snapd/hostfs/etc/default/swapfile rw,relatime master:12 - ext4 /dev/sda3 rw,data=ordered
-1110 1102 1:1 /system-data/etc/environment /var/lib/snapd/hostfs/etc/environment rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1111 1102 2:5 /image.fstab /var/lib/snapd/hostfs/etc/fstab rw,nosuid,noexec,relatime master:14 - tmpfs tmpfs rw,mode=755
-1112 1102 1:1 /system-data/root/test-etc/group /var/lib/snapd/hostfs/etc/group ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1113 1102 1:1 /system-data/root/test-etc/gshadow /var/lib/snapd/hostfs/etc/gshadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1114 1102 1:1 /system-data/etc/hosts /var/lib/snapd/hostfs/etc/hosts rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
-1115 1102 1:1 /system-data/etc/init /var/lib/snapd/hostfs/etc/init rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
-1116 1102 1:1 /system-data/etc/init.d /var/lib/snapd/hostfs/etc/init.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
-1117 1102 1:1 /system-data/etc/iproute2 /var/lib/snapd/hostfs/etc/iproute2 rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
-1118 1102 1:1 /system-data/etc/machine-id /var/lib/snapd/hostfs/etc/machine-id rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
-1119 1102 1:1 /system-data/etc/modprobe.d /var/lib/snapd/hostfs/etc/modprobe.d rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
-1120 1102 1:1 /system-data/etc/modules-load.d /var/lib/snapd/hostfs/etc/modules-load.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
-1121 1102 1:1 /system-data/etc/netplan /var/lib/snapd/hostfs/etc/netplan rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
-1122 1102 1:1 /system-data/etc/network/if-up.d /var/lib/snapd/hostfs/etc/network/if-up.d rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
-1123 1102 1:1 /system-data/etc/network/interfaces.d /var/lib/snapd/hostfs/etc/network/interfaces.d rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
-1124 1102 1:1 /system-data/root/test-etc/passwd /var/lib/snapd/hostfs/etc/passwd ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1125 1102 1:1 /system-data/etc/ppp /var/lib/snapd/hostfs/etc/ppp rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
-1126 1102 1:1 /system-data/etc/rc0.d /var/lib/snapd/hostfs/etc/rc0.d rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
-1127 1102 1:1 /system-data/etc/rc1.d /var/lib/snapd/hostfs/etc/rc1.d rw,relatime master:28 - ext4 /dev/sda3 rw,data=ordered
-1128 1102 1:1 /system-data/etc/rc2.d /var/lib/snapd/hostfs/etc/rc2.d rw,relatime master:29 - ext4 /dev/sda3 rw,data=ordered
-1129 1102 1:1 /system-data/etc/rc3.d /var/lib/snapd/hostfs/etc/rc3.d rw,relatime master:30 - ext4 /dev/sda3 rw,data=ordered
-1130 1102 1:1 /system-data/etc/rc4.d /var/lib/snapd/hostfs/etc/rc4.d rw,relatime master:31 - ext4 /dev/sda3 rw,data=ordered
-1131 1102 1:1 /system-data/etc/rc5.d /var/lib/snapd/hostfs/etc/rc5.d rw,relatime master:32 - ext4 /dev/sda3 rw,data=ordered
-1132 1102 1:1 /system-data/etc/rc6.d /var/lib/snapd/hostfs/etc/rc6.d rw,relatime master:33 - ext4 /dev/sda3 rw,data=ordered
-1133 1102 1:1 /system-data/etc/rcS.d /var/lib/snapd/hostfs/etc/rcS.d rw,relatime master:34 - ext4 /dev/sda3 rw,data=ordered
-1134 1102 1:1 /system-data/etc/rsyslog.d /var/lib/snapd/hostfs/etc/rsyslog.d rw,relatime master:35 - ext4 /dev/sda3 rw,data=ordered
-1135 1102 1:1 /system-data/root/test-etc/shadow /var/lib/snapd/hostfs/etc/shadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1136 1102 1:1 /system-data/etc/ssh /var/lib/snapd/hostfs/etc/ssh rw,relatime master:36 - ext4 /dev/sda3 rw,data=ordered
-1137 1102 1:1 /system-data/etc/sudoers.d /var/lib/snapd/hostfs/etc/sudoers.d rw,relatime master:37 - ext4 /dev/sda3 rw,data=ordered
-1138 1102 1:1 /system-data/etc/sysctl.d /var/lib/snapd/hostfs/etc/sysctl.d rw,relatime master:38 - ext4 /dev/sda3 rw,data=ordered
-1139 1102 1:1 /system-data/etc/systemd/logind.conf.d /var/lib/snapd/hostfs/etc/systemd/logind.conf.d rw,relatime master:39 - ext4 /dev/sda3 rw,data=ordered
-1140 1102 1:1 /system-data/etc/systemd/network /var/lib/snapd/hostfs/etc/systemd/network rw,relatime master:40 - ext4 /dev/sda3 rw,data=ordered
-1141 1102 1:1 /system-data/etc/systemd/system /var/lib/snapd/hostfs/etc/systemd/system rw,relatime master:41 - ext4 /dev/sda3 rw,data=ordered
-1142 1141 1:1 /system-data/etc/systemd/system /var/lib/snapd/hostfs/etc/systemd/system rw,relatime master:42 - ext4 /dev/sda3 rw,data=ordered
-1143 1102 1:1 /system-data/etc/systemd/system.conf.d /var/lib/snapd/hostfs/etc/systemd/system.conf.d rw,relatime master:43 - ext4 /dev/sda3 rw,data=ordered
-1144 1102 1:1 /system-data/etc/systemd/timesyncd.conf /var/lib/snapd/hostfs/etc/systemd/timesyncd.conf rw,relatime master:44 - ext4 /dev/sda3 rw,data=ordered
-1145 1102 1:1 /system-data/etc/systemd/user.conf.d /var/lib/snapd/hostfs/etc/systemd/user.conf.d rw,relatime master:45 - ext4 /dev/sda3 rw,data=ordered
-1146 1102 1:1 /system-data/etc/udev/rules.d /var/lib/snapd/hostfs/etc/udev/rules.d rw,relatime master:46 - ext4 /dev/sda3 rw,data=ordered
-1147 1102 1:1 /system-data/etc/update-motd.d /var/lib/snapd/hostfs/etc/update-motd.d rw,relatime master:47 - ext4 /dev/sda3 rw,data=ordered
-1148 1102 1:1 /system-data/etc/writable /var/lib/snapd/hostfs/etc/writable rw,relatime master:48 - ext4 /dev/sda3 rw,data=ordered
-1149 1102 1:1 /user-data /var/lib/snapd/hostfs/home rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1150 1102 0:1 /firmware /var/lib/snapd/hostfs/lib/firmware ro,relatime master:49 - squashfs /dev/loop1 ro
-1151 1102 0:1 /modules /var/lib/snapd/hostfs/lib/modules ro,relatime master:50 - squashfs /dev/loop1 ro
-1152 1102 2:6 / /var/lib/snapd/hostfs/media rw,relatime master:51 - tmpfs tmpfs rw
-1153 1102 2:7 / /var/lib/snapd/hostfs/mnt rw,relatime master:52 - tmpfs tmpfs rw
-1154 1102 1:1 /system-data/root /var/lib/snapd/hostfs/root rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1155 1102 2:5 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:56 - tmpfs tmpfs rw,mode=755
-1156 1155 2:10 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:55 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1157 1156 2:11 / /var/lib/snapd/hostfs/run/cgmanager/fs rw,relatime master:57 - tmpfs cgmfs rw,size=VARIABLE,mode=755
-1158 1156 2:12 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:58 - tmpfs tmpfs rw,size=VARIABLE
-1159 1156 2:10 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1160 1156 2:13 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:59 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-1161 1102 1:1 /system-data/snap /var/lib/snapd/hostfs/snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1162 1161 0:2 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
-1163 1161 0:3 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
-1164 1161 0:4 / /var/lib/snapd/hostfs/snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
-1165 1161 0:5 / /var/lib/snapd/hostfs/snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
-1166 1161 0:6 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
-1167 1161 0:7 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
-1168 1161 0:8 / /var/lib/snapd/hostfs/snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
-1169 1102 2:31 / /var/lib/snapd/hostfs/tmp rw,relatime master:84 - tmpfs tmpfs rw
-1170 1102 1:1 /system-data/var/cache/apparmor /var/lib/snapd/hostfs/var/cache/apparmor rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1171 1102 1:1 /system-data/var/cache/snapd /var/lib/snapd/hostfs/var/cache/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1172 1102 1:1 /system-data/var/lib/apparmor /var/lib/snapd/hostfs/var/lib/apparmor rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1173 1102 1:1 /system-data/var/lib/cloud /var/lib/snapd/hostfs/var/lib/cloud rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1174 1102 1:1 /system-data/var/lib/console-conf /var/lib/snapd/hostfs/var/lib/console-conf rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1175 1102 1:1 /system-data/var/lib/dbus /var/lib/snapd/hostfs/var/lib/dbus rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1176 1102 1:1 /system-data/var/lib/dhcp /var/lib/snapd/hostfs/var/lib/dhcp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1177 1102 1:1 /system-data/var/lib/extrausers /var/lib/snapd/hostfs/var/lib/extrausers rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1178 1102 1:1 /system-data/var/lib/initramfs-tools /var/lib/snapd/hostfs/var/lib/initramfs-tools rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1179 1102 1:1 /system-data/var/lib/logrotate /var/lib/snapd/hostfs/var/lib/logrotate rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1180 1102 1:1 /system-data/var/lib/misc /var/lib/snapd/hostfs/var/lib/misc rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1181 1102 1:1 /system-data/var/lib/snapd /var/lib/snapd/hostfs/var/lib/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1182 1102 2:32 / /var/lib/snapd/hostfs/var/lib/sudo rw,relatime master:85 - tmpfs tmpfs rw,mode=700
-1183 1102 1:1 /system-data/var/lib/systemd/random-seed /var/lib/snapd/hostfs/var/lib/systemd/random-seed rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1184 1102 1:1 /system-data/var/lib/systemd/rfkill /var/lib/snapd/hostfs/var/lib/systemd/rfkill rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1185 1102 1:1 /system-data/var/lib/waagent /var/lib/snapd/hostfs/var/lib/waagent rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1186 1102 1:1 /system-data/var/log /var/lib/snapd/hostfs/var/log rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1187 1102 1:1 /system-data/var/snap /var/lib/snapd/hostfs/var/snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1188 1102 1:1 /system-data/var/tmp /var/lib/snapd/hostfs/var/tmp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1189 1102 1:1 / /var/lib/snapd/hostfs/writable rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1190 1189 0:2 / /var/lib/snapd/hostfs/writable/system-data/snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
-1191 1189 0:3 / /var/lib/snapd/hostfs/writable/system-data/snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
-1192 1189 0:4 / /var/lib/snapd/hostfs/writable/system-data/snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
-1193 1189 0:5 / /var/lib/snapd/hostfs/writable/system-data/snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
-1194 1189 0:6 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
-1195 1189 0:7 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
-1196 1189 0:8 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
-1197 1001 1:1 /system-data/var/log /var/log rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1198 1001 1:1 /system-data/var/snap /var/snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1199 1001 1:1 /system-data/var/tmp /var/tmp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:3 / / ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
+2:0 / /dev rw,nosuid,relatime master:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
+2:1 / /dev/hugepages rw,relatime master:4 - hugetlbfs hugetlbfs rw
+2:2 / /dev/mqueue rw,relatime master:5 - mqueue mqueue rw
+2:33 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+2:3 / /dev/pts rw,nosuid,noexec,relatime master:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
+2:33 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+2:4 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
+0:0 /etc /etc ro,relatime master:1 - squashfs /dev/loop0 ro
+1:1 /system-data/etc/apparmor.d/cache /etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/cloud /etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/dbus-1/system.d /etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/keyboard /etc/default/keyboard rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/swapfile /etc/default/swapfile rw,relatime master:12 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/environment /etc/environment rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+2:5 /image.fstab /etc/fstab rw,nosuid,noexec,relatime master:14 - tmpfs tmpfs rw,mode=755
+1:1 /system-data/root/test-etc/group /etc/group ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/gshadow /etc/gshadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/hosts /etc/hosts rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/init /etc/init rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/init.d /etc/init.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/iproute2 /etc/iproute2 rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/machine-id /etc/machine-id rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modprobe.d /etc/modprobe.d rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modules-load.d /etc/modules-load.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/netplan /etc/netplan rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/if-up.d /etc/network/if-up.d rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/interfaces.d /etc/network/interfaces.d rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
+0:3 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
+1:1 /system-data/root/test-etc/passwd /etc/passwd ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ppp /etc/ppp rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc0.d /etc/rc0.d rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc1.d /etc/rc1.d rw,relatime master:28 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc2.d /etc/rc2.d rw,relatime master:29 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc3.d /etc/rc3.d rw,relatime master:30 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc4.d /etc/rc4.d rw,relatime master:31 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc5.d /etc/rc5.d rw,relatime master:32 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc6.d /etc/rc6.d rw,relatime master:33 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rcS.d /etc/rcS.d rw,relatime master:34 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rsyslog.d /etc/rsyslog.d rw,relatime master:35 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/shadow /etc/shadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ssh /etc/ssh rw,relatime master:36 - ext4 /dev/sda3 rw,data=ordered
+0:3 /etc/ssl /etc/ssl ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
+1:1 /system-data/etc/sudoers.d /etc/sudoers.d rw,relatime master:37 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sysctl.d /etc/sysctl.d rw,relatime master:38 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/logind.conf.d /etc/systemd/logind.conf.d rw,relatime master:39 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/network /etc/systemd/network rw,relatime master:40 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime master:41 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime master:42 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system.conf.d /etc/systemd/system.conf.d rw,relatime master:43 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/timesyncd.conf /etc/systemd/timesyncd.conf rw,relatime master:44 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/user.conf.d /etc/systemd/user.conf.d rw,relatime master:45 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/udev/rules.d /etc/udev/rules.d rw,relatime master:46 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/update-motd.d /etc/update-motd.d rw,relatime master:47 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/writable /etc/writable rw,relatime master:48 - ext4 /dev/sda3 rw,data=ordered
+1:1 /user-data /home rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:1 /firmware /lib/firmware ro,relatime master:49 - squashfs /dev/loop1 ro
+0:1 /modules /lib/modules ro,relatime master:50 - squashfs /dev/loop1 ro
+2:6 / /media rw,relatime shared:51 - tmpfs tmpfs rw
+2:7 / /mnt rw,relatime master:52 - tmpfs tmpfs rw
+2:8 / /proc rw,nosuid,nodev,noexec,relatime master:53 - proc proc rw
+2:9 / /proc/sys/fs/binfmt_misc rw,relatime master:54 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct
+1:1 /system-data/root /root rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+2:10 / /run rw,nosuid,noexec,relatime master:55 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:11 / /run/cgmanager/fs rw,relatime master:57 - tmpfs cgmfs rw,size=VARIABLE,mode=755
+2:12 / /run/lock rw,nosuid,nodev,noexec,relatime master:58 - tmpfs tmpfs rw,size=VARIABLE
+2:10 /netns /run/netns rw,nosuid,noexec,relatime shared:55 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:10 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:13 / /run/user/0 rw,nosuid,nodev,relatime master:59 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+1:1 /system-data/snap /snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
+0:3 / /snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
+0:4 / /snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
+0:5 / /snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
+0:6 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
+0:7 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
+0:8 / /snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
+2:14 / /sys rw,nosuid,nodev,noexec,relatime master:67 - sysfs sysfs rw
+2:15 / /sys/fs/cgroup rw master:68 - tmpfs tmpfs rw,mode=755
+2:16 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:69 - cgroup cgroup rw,blkio
+2:17 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:70 - cgroup cgroup rw,cpu,cpuacct
+2:18 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:71 - cgroup cgroup rw,cpuset,clone_children
+2:19 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:72 - cgroup cgroup rw,devices
+2:20 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:73 - cgroup cgroup rw,freezer
+2:21 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:74 - cgroup cgroup rw,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb
+2:22 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:75 - cgroup cgroup rw,memory
+2:23 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:76 - cgroup cgroup rw,net_cls,net_prio
+2:24 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:77 - cgroup cgroup rw,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event
+2:25 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:78 - cgroup cgroup rw,pids,release_agent=/run/cgmanager/agents/cgm-release-agent.pids
+2:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:79 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
+2:27 / /sys/fs/fuse/connections rw,relatime master:80 - fusectl fusectl rw
+2:28 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:81 - pstore pstore rw
+2:29 / /sys/kernel/debug rw,relatime master:82 - debugfs debugfs rw
+2:30 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:83 - securityfs securityfs rw
+2:31 / /tmp rw,relatime master:84 - tmpfs tmpfs rw
+2:31 /snap.test-snapd-mountinfo-core18/tmp /tmp rw,relatime - tmpfs tmpfs rw
+0:0 /usr/lib/snapd /usr/lib/snapd ro,relatime master:1 - squashfs /dev/loop0 ro
+2:34 / /usr/share/gdb rw,relatime - tmpfs tmpfs rw,mode=755
+0:3 /usr/share/gdb/auto-load /usr/share/gdb/auto-load ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
+2:35 / /usr/share/gdb/test rw,relatime - tmpfs tmpfs rw
+0:0 /usr/src /usr/src ro,relatime master:1 - squashfs /dev/loop0 ro
+1:1 /system-data/var/lib/extrausers /var/lib/extrausers rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/snapd /var/lib/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:0 / /var/lib/snapd/hostfs ro,relatime master:1 - squashfs /dev/loop0 ro
+1:1 /system-data/var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda3 rw,data=ordered
+1:0 / /var/lib/snapd/hostfs/boot/efi rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:0 /EFI/ubuntu /var/lib/snapd/hostfs/boot/grub rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:1 /system-data/etc/apparmor.d/cache /var/lib/snapd/hostfs/etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/cloud /var/lib/snapd/hostfs/etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/dbus-1/system.d /var/lib/snapd/hostfs/etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/keyboard /var/lib/snapd/hostfs/etc/default/keyboard rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/swapfile /var/lib/snapd/hostfs/etc/default/swapfile rw,relatime master:12 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/environment /var/lib/snapd/hostfs/etc/environment rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+2:5 /image.fstab /var/lib/snapd/hostfs/etc/fstab rw,nosuid,noexec,relatime master:14 - tmpfs tmpfs rw,mode=755
+1:1 /system-data/root/test-etc/group /var/lib/snapd/hostfs/etc/group ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/gshadow /var/lib/snapd/hostfs/etc/gshadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/hosts /var/lib/snapd/hostfs/etc/hosts rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/init /var/lib/snapd/hostfs/etc/init rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/init.d /var/lib/snapd/hostfs/etc/init.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/iproute2 /var/lib/snapd/hostfs/etc/iproute2 rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/machine-id /var/lib/snapd/hostfs/etc/machine-id rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modprobe.d /var/lib/snapd/hostfs/etc/modprobe.d rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modules-load.d /var/lib/snapd/hostfs/etc/modules-load.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/netplan /var/lib/snapd/hostfs/etc/netplan rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/if-up.d /var/lib/snapd/hostfs/etc/network/if-up.d rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/interfaces.d /var/lib/snapd/hostfs/etc/network/interfaces.d rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/passwd /var/lib/snapd/hostfs/etc/passwd ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ppp /var/lib/snapd/hostfs/etc/ppp rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc0.d /var/lib/snapd/hostfs/etc/rc0.d rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc1.d /var/lib/snapd/hostfs/etc/rc1.d rw,relatime master:28 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc2.d /var/lib/snapd/hostfs/etc/rc2.d rw,relatime master:29 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc3.d /var/lib/snapd/hostfs/etc/rc3.d rw,relatime master:30 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc4.d /var/lib/snapd/hostfs/etc/rc4.d rw,relatime master:31 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc5.d /var/lib/snapd/hostfs/etc/rc5.d rw,relatime master:32 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc6.d /var/lib/snapd/hostfs/etc/rc6.d rw,relatime master:33 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rcS.d /var/lib/snapd/hostfs/etc/rcS.d rw,relatime master:34 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rsyslog.d /var/lib/snapd/hostfs/etc/rsyslog.d rw,relatime master:35 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/shadow /var/lib/snapd/hostfs/etc/shadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ssh /var/lib/snapd/hostfs/etc/ssh rw,relatime master:36 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sudoers.d /var/lib/snapd/hostfs/etc/sudoers.d rw,relatime master:37 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sysctl.d /var/lib/snapd/hostfs/etc/sysctl.d rw,relatime master:38 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/logind.conf.d /var/lib/snapd/hostfs/etc/systemd/logind.conf.d rw,relatime master:39 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/network /var/lib/snapd/hostfs/etc/systemd/network rw,relatime master:40 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /var/lib/snapd/hostfs/etc/systemd/system rw,relatime master:41 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /var/lib/snapd/hostfs/etc/systemd/system rw,relatime master:42 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system.conf.d /var/lib/snapd/hostfs/etc/systemd/system.conf.d rw,relatime master:43 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/timesyncd.conf /var/lib/snapd/hostfs/etc/systemd/timesyncd.conf rw,relatime master:44 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/user.conf.d /var/lib/snapd/hostfs/etc/systemd/user.conf.d rw,relatime master:45 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/udev/rules.d /var/lib/snapd/hostfs/etc/udev/rules.d rw,relatime master:46 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/update-motd.d /var/lib/snapd/hostfs/etc/update-motd.d rw,relatime master:47 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/writable /var/lib/snapd/hostfs/etc/writable rw,relatime master:48 - ext4 /dev/sda3 rw,data=ordered
+1:1 /user-data /var/lib/snapd/hostfs/home rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:1 /firmware /var/lib/snapd/hostfs/lib/firmware ro,relatime master:49 - squashfs /dev/loop1 ro
+0:1 /modules /var/lib/snapd/hostfs/lib/modules ro,relatime master:50 - squashfs /dev/loop1 ro
+2:6 / /var/lib/snapd/hostfs/media rw,relatime master:51 - tmpfs tmpfs rw
+2:7 / /var/lib/snapd/hostfs/mnt rw,relatime master:52 - tmpfs tmpfs rw
+1:1 /system-data/root /var/lib/snapd/hostfs/root rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+2:5 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:56 - tmpfs tmpfs rw,mode=755
+2:10 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:55 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:11 / /var/lib/snapd/hostfs/run/cgmanager/fs rw,relatime master:57 - tmpfs cgmfs rw,size=VARIABLE,mode=755
+2:12 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:58 - tmpfs tmpfs rw,size=VARIABLE
+2:10 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:13 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:59 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+1:1 /system-data/snap /var/lib/snapd/hostfs/snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
+0:3 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
+0:4 / /var/lib/snapd/hostfs/snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
+0:5 / /var/lib/snapd/hostfs/snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
+0:6 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
+0:7 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
+0:8 / /var/lib/snapd/hostfs/snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
+2:31 / /var/lib/snapd/hostfs/tmp rw,relatime master:84 - tmpfs tmpfs rw
+1:1 /system-data/var/cache/apparmor /var/lib/snapd/hostfs/var/cache/apparmor rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/cache/snapd /var/lib/snapd/hostfs/var/cache/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/apparmor /var/lib/snapd/hostfs/var/lib/apparmor rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/cloud /var/lib/snapd/hostfs/var/lib/cloud rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/console-conf /var/lib/snapd/hostfs/var/lib/console-conf rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/dbus /var/lib/snapd/hostfs/var/lib/dbus rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/dhcp /var/lib/snapd/hostfs/var/lib/dhcp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/extrausers /var/lib/snapd/hostfs/var/lib/extrausers rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/initramfs-tools /var/lib/snapd/hostfs/var/lib/initramfs-tools rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/logrotate /var/lib/snapd/hostfs/var/lib/logrotate rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/misc /var/lib/snapd/hostfs/var/lib/misc rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/snapd /var/lib/snapd/hostfs/var/lib/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+2:32 / /var/lib/snapd/hostfs/var/lib/sudo rw,relatime master:85 - tmpfs tmpfs rw,mode=700
+1:1 /system-data/var/lib/systemd/random-seed /var/lib/snapd/hostfs/var/lib/systemd/random-seed rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/systemd/rfkill /var/lib/snapd/hostfs/var/lib/systemd/rfkill rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/waagent /var/lib/snapd/hostfs/var/lib/waagent rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/log /var/lib/snapd/hostfs/var/log rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/snap /var/lib/snapd/hostfs/var/snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/tmp /var/lib/snapd/hostfs/var/tmp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 / /var/lib/snapd/hostfs/writable rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /var/lib/snapd/hostfs/writable/system-data/snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
+0:3 / /var/lib/snapd/hostfs/writable/system-data/snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
+0:4 / /var/lib/snapd/hostfs/writable/system-data/snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
+0:5 / /var/lib/snapd/hostfs/writable/system-data/snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
+0:6 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
+0:7 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
+0:8 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
+1:1 /system-data/var/log /var/log rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/snap /var/snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/tmp /var/tmp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-core-16-64/PER-USER-16.expected.txt snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-core-16-64/PER-USER-16.expected.txt
--- snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-core-16-64/PER-USER-16.expected.txt 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-core-16-64/PER-USER-16.expected.txt 2019-10-30 12:17:43.000000000 +0000
@@ -1,228 +1,223 @@
-2001 2000 0:0 / / ro,relatime master:1 - squashfs /dev/loop0 ro
-2002 2001 1:0 / /boot/efi rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-2003 2001 1:0 /EFI/ubuntu /boot/grub rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-2004 2001 2:0 / /dev rw,nosuid,relatime master:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
-2005 2004 2:1 / /dev/hugepages rw,relatime master:4 - hugetlbfs hugetlbfs rw
-2006 2004 2:2 / /dev/mqueue rw,relatime master:5 - mqueue mqueue rw
-2007 2004 2:33 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-2008 2004 2:3 / /dev/pts rw,nosuid,noexec,relatime master:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
-2009 2008 2:33 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-2010 2004 2:4 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
-2011 2001 1:1 /system-data/etc/apparmor.d/cache /etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
-2012 2001 1:1 /system-data/etc/cloud /etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
-2013 2001 1:1 /system-data/etc/dbus-1/system.d /etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
-2014 2001 1:1 /system-data/etc/default/keyboard /etc/default/keyboard rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
-2015 2001 1:1 /system-data/etc/default/swapfile /etc/default/swapfile rw,relatime master:12 - ext4 /dev/sda3 rw,data=ordered
-2016 2001 1:1 /system-data/etc/environment /etc/environment rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2017 2001 2:5 /image.fstab /etc/fstab rw,nosuid,noexec,relatime master:14 - tmpfs tmpfs rw,mode=755
-2018 2001 1:1 /system-data/root/test-etc/group /etc/group ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2019 2001 1:1 /system-data/root/test-etc/gshadow /etc/gshadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2020 2001 1:1 /system-data/etc/hosts /etc/hosts rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
-2021 2001 1:1 /system-data/etc/init /etc/init rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
-2022 2001 1:1 /system-data/etc/init.d /etc/init.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
-2023 2001 1:1 /system-data/etc/iproute2 /etc/iproute2 rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
-2024 2001 1:1 /system-data/etc/machine-id /etc/machine-id rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
-2025 2001 1:1 /system-data/etc/modprobe.d /etc/modprobe.d rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
-2026 2001 1:1 /system-data/etc/modules-load.d /etc/modules-load.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
-2027 2001 1:1 /system-data/etc/netplan /etc/netplan rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
-2028 2001 1:1 /system-data/etc/network/if-up.d /etc/network/if-up.d rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
-2029 2001 1:1 /system-data/etc/network/interfaces.d /etc/network/interfaces.d rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
-2030 2001 1:1 /system-data/root/test-etc/passwd /etc/passwd ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2031 2001 1:1 /system-data/etc/ppp /etc/ppp rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
-2032 2001 1:1 /system-data/etc/rc0.d /etc/rc0.d rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
-2033 2001 1:1 /system-data/etc/rc1.d /etc/rc1.d rw,relatime master:28 - ext4 /dev/sda3 rw,data=ordered
-2034 2001 1:1 /system-data/etc/rc2.d /etc/rc2.d rw,relatime master:29 - ext4 /dev/sda3 rw,data=ordered
-2035 2001 1:1 /system-data/etc/rc3.d /etc/rc3.d rw,relatime master:30 - ext4 /dev/sda3 rw,data=ordered
-2036 2001 1:1 /system-data/etc/rc4.d /etc/rc4.d rw,relatime master:31 - ext4 /dev/sda3 rw,data=ordered
-2037 2001 1:1 /system-data/etc/rc5.d /etc/rc5.d rw,relatime master:32 - ext4 /dev/sda3 rw,data=ordered
-2038 2001 1:1 /system-data/etc/rc6.d /etc/rc6.d rw,relatime master:33 - ext4 /dev/sda3 rw,data=ordered
-2039 2001 1:1 /system-data/etc/rcS.d /etc/rcS.d rw,relatime master:34 - ext4 /dev/sda3 rw,data=ordered
-2040 2001 1:1 /system-data/etc/rsyslog.d /etc/rsyslog.d rw,relatime master:35 - ext4 /dev/sda3 rw,data=ordered
-2041 2001 1:1 /system-data/root/test-etc/shadow /etc/shadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2042 2001 1:1 /system-data/etc/ssh /etc/ssh rw,relatime master:36 - ext4 /dev/sda3 rw,data=ordered
-2043 2001 1:1 /system-data/etc/sudoers.d /etc/sudoers.d rw,relatime master:37 - ext4 /dev/sda3 rw,data=ordered
-2044 2001 1:1 /system-data/etc/sysctl.d /etc/sysctl.d rw,relatime master:38 - ext4 /dev/sda3 rw,data=ordered
-2045 2001 1:1 /system-data/etc/systemd/logind.conf.d /etc/systemd/logind.conf.d rw,relatime master:39 - ext4 /dev/sda3 rw,data=ordered
-2046 2001 1:1 /system-data/etc/systemd/network /etc/systemd/network rw,relatime master:40 - ext4 /dev/sda3 rw,data=ordered
-2047 2001 1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime master:41 - ext4 /dev/sda3 rw,data=ordered
-2048 2047 1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime master:42 - ext4 /dev/sda3 rw,data=ordered
-2049 2001 1:1 /system-data/etc/systemd/system.conf.d /etc/systemd/system.conf.d rw,relatime master:43 - ext4 /dev/sda3 rw,data=ordered
-2050 2001 1:1 /system-data/etc/systemd/timesyncd.conf /etc/systemd/timesyncd.conf rw,relatime master:44 - ext4 /dev/sda3 rw,data=ordered
-2051 2001 1:1 /system-data/etc/systemd/user.conf.d /etc/systemd/user.conf.d rw,relatime master:45 - ext4 /dev/sda3 rw,data=ordered
-2052 2001 1:1 /system-data/etc/udev/rules.d /etc/udev/rules.d rw,relatime master:46 - ext4 /dev/sda3 rw,data=ordered
-2053 2001 1:1 /system-data/etc/update-motd.d /etc/update-motd.d rw,relatime master:47 - ext4 /dev/sda3 rw,data=ordered
-2054 2001 1:1 /system-data/etc/writable /etc/writable rw,relatime master:48 - ext4 /dev/sda3 rw,data=ordered
-2055 2001 1:1 /user-data /home rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2056 2001 0:1 /firmware /lib/firmware ro,relatime master:49 - squashfs /dev/loop1 ro
-2057 2001 0:1 /modules /lib/modules ro,relatime master:50 - squashfs /dev/loop1 ro
-2058 2001 2:6 / /media rw,relatime master:51 - tmpfs tmpfs rw
-2059 2058 2:6 / /media rw,relatime shared:51 - tmpfs tmpfs rw
-2060 2001 2:7 / /mnt rw,relatime master:52 - tmpfs tmpfs rw
-2061 2001 2:8 / /proc rw,nosuid,nodev,noexec,relatime master:53 - proc proc rw
-2062 2061 2:9 / /proc/sys/fs/binfmt_misc rw,relatime master:54 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct
-2063 2001 1:1 /system-data/root /root rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2064 2001 2:5 / /run rw,nosuid,noexec,relatime master:56 - tmpfs tmpfs rw,mode=755
-2065 2064 2:10 / /run rw,nosuid,noexec,relatime master:55 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2066 2065 2:11 / /run/cgmanager/fs rw,relatime master:57 - tmpfs cgmfs rw,size=VARIABLE,mode=755
-2067 2065 2:12 / /run/lock rw,nosuid,nodev,noexec,relatime master:58 - tmpfs tmpfs rw,size=VARIABLE
-2068 2065 2:10 /netns /run/netns rw,nosuid,noexec,relatime shared:55 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2069 2065 2:10 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2070 2065 2:13 / /run/user/0 rw,nosuid,nodev,relatime master:59 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-2071 2001 1:1 /system-data/snap /snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2072 2071 1:1 /system-data/snap /snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2073 2071 0:2 / /snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
-2074 2072 0:2 / /snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
-2075 2071 0:3 / /snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
-2076 2072 0:3 / /snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
-2077 2071 0:4 / /snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
-2078 2072 0:4 / /snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
-2079 2071 0:5 / /snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
-2080 2072 0:5 / /snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
-2081 2071 0:6 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
-2082 2072 0:6 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
-2083 2071 0:7 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
-2084 2072 0:7 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
-2085 2071 0:8 / /snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
-2086 2072 0:8 / /snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
-2087 2001 2:14 / /sys rw,nosuid,nodev,noexec,relatime master:67 - sysfs sysfs rw
-2088 2087 2:15 / /sys/fs/cgroup rw master:68 - tmpfs tmpfs rw,mode=755
-2089 2088 2:16 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:69 - cgroup cgroup rw,blkio
-2090 2088 2:17 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:70 - cgroup cgroup rw,cpu,cpuacct
-2091 2088 2:18 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:71 - cgroup cgroup rw,cpuset,clone_children
-2092 2088 2:19 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:72 - cgroup cgroup rw,devices
-2093 2088 2:20 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:73 - cgroup cgroup rw,freezer
-2094 2088 2:21 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:74 - cgroup cgroup rw,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb
-2095 2088 2:22 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:75 - cgroup cgroup rw,memory
-2096 2088 2:23 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:76 - cgroup cgroup rw,net_cls,net_prio
-2097 2088 2:24 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:77 - cgroup cgroup rw,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event
-2098 2088 2:25 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:78 - cgroup cgroup rw,pids,release_agent=/run/cgmanager/agents/cgm-release-agent.pids
-2099 2088 2:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:79 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
-2100 2087 2:27 / /sys/fs/fuse/connections rw,relatime master:80 - fusectl fusectl rw
-2101 2087 2:28 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:81 - pstore pstore rw
-2102 2087 2:29 / /sys/kernel/debug rw,relatime master:82 - debugfs debugfs rw
-2103 2087 2:30 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:83 - securityfs securityfs rw
-2104 2001 2:31 / /tmp rw,relatime master:84 - tmpfs tmpfs rw
-2105 2104 2:31 /snap.test-snapd-mountinfo-core16/tmp /tmp rw,relatime - tmpfs tmpfs rw
-2106 2001 1:1 /system-data/var/cache/apparmor /var/cache/apparmor rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2107 2001 1:1 /system-data/var/cache/snapd /var/cache/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2108 2001 1:1 /system-data/var/lib/apparmor /var/lib/apparmor rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2109 2001 1:1 /system-data/var/lib/cloud /var/lib/cloud rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2110 2001 1:1 /system-data/var/lib/console-conf /var/lib/console-conf rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2111 2001 1:1 /system-data/var/lib/dbus /var/lib/dbus rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2112 2001 1:1 /system-data/var/lib/dhcp /var/lib/dhcp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2113 2001 1:1 /system-data/var/lib/extrausers /var/lib/extrausers rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2114 2001 1:1 /system-data/var/lib/initramfs-tools /var/lib/initramfs-tools rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2115 2001 1:1 /system-data/var/lib/logrotate /var/lib/logrotate rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2116 2001 1:1 /system-data/var/lib/misc /var/lib/misc rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2117 2001 1:1 /system-data/var/lib/snapd /var/lib/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2119 2118 0:0 / /var/lib/snapd/hostfs ro,relatime master:1 - squashfs /dev/loop0 ro
-2118 2117 1:1 /system-data/var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda3 rw,data=ordered
-2120 2119 1:0 / /var/lib/snapd/hostfs/boot/efi rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-2121 2119 1:0 /EFI/ubuntu /var/lib/snapd/hostfs/boot/grub rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-2122 2119 1:1 /system-data/etc/apparmor.d/cache /var/lib/snapd/hostfs/etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
-2123 2119 1:1 /system-data/etc/cloud /var/lib/snapd/hostfs/etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
-2124 2119 1:1 /system-data/etc/dbus-1/system.d /var/lib/snapd/hostfs/etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
-2125 2119 1:1 /system-data/etc/default/keyboard /var/lib/snapd/hostfs/etc/default/keyboard rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
-2126 2119 1:1 /system-data/etc/default/swapfile /var/lib/snapd/hostfs/etc/default/swapfile rw,relatime master:12 - ext4 /dev/sda3 rw,data=ordered
-2127 2119 1:1 /system-data/etc/environment /var/lib/snapd/hostfs/etc/environment rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2128 2119 2:5 /image.fstab /var/lib/snapd/hostfs/etc/fstab rw,nosuid,noexec,relatime master:14 - tmpfs tmpfs rw,mode=755
-2129 2119 1:1 /system-data/root/test-etc/group /var/lib/snapd/hostfs/etc/group ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2130 2119 1:1 /system-data/root/test-etc/gshadow /var/lib/snapd/hostfs/etc/gshadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2131 2119 1:1 /system-data/etc/hosts /var/lib/snapd/hostfs/etc/hosts rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
-2132 2119 1:1 /system-data/etc/init /var/lib/snapd/hostfs/etc/init rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
-2133 2119 1:1 /system-data/etc/init.d /var/lib/snapd/hostfs/etc/init.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
-2134 2119 1:1 /system-data/etc/iproute2 /var/lib/snapd/hostfs/etc/iproute2 rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
-2135 2119 1:1 /system-data/etc/machine-id /var/lib/snapd/hostfs/etc/machine-id rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
-2136 2119 1:1 /system-data/etc/modprobe.d /var/lib/snapd/hostfs/etc/modprobe.d rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
-2137 2119 1:1 /system-data/etc/modules-load.d /var/lib/snapd/hostfs/etc/modules-load.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
-2138 2119 1:1 /system-data/etc/netplan /var/lib/snapd/hostfs/etc/netplan rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
-2139 2119 1:1 /system-data/etc/network/if-up.d /var/lib/snapd/hostfs/etc/network/if-up.d rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
-2140 2119 1:1 /system-data/etc/network/interfaces.d /var/lib/snapd/hostfs/etc/network/interfaces.d rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
-2141 2119 1:1 /system-data/root/test-etc/passwd /var/lib/snapd/hostfs/etc/passwd ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2142 2119 1:1 /system-data/etc/ppp /var/lib/snapd/hostfs/etc/ppp rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
-2143 2119 1:1 /system-data/etc/rc0.d /var/lib/snapd/hostfs/etc/rc0.d rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
-2144 2119 1:1 /system-data/etc/rc1.d /var/lib/snapd/hostfs/etc/rc1.d rw,relatime master:28 - ext4 /dev/sda3 rw,data=ordered
-2145 2119 1:1 /system-data/etc/rc2.d /var/lib/snapd/hostfs/etc/rc2.d rw,relatime master:29 - ext4 /dev/sda3 rw,data=ordered
-2146 2119 1:1 /system-data/etc/rc3.d /var/lib/snapd/hostfs/etc/rc3.d rw,relatime master:30 - ext4 /dev/sda3 rw,data=ordered
-2147 2119 1:1 /system-data/etc/rc4.d /var/lib/snapd/hostfs/etc/rc4.d rw,relatime master:31 - ext4 /dev/sda3 rw,data=ordered
-2148 2119 1:1 /system-data/etc/rc5.d /var/lib/snapd/hostfs/etc/rc5.d rw,relatime master:32 - ext4 /dev/sda3 rw,data=ordered
-2149 2119 1:1 /system-data/etc/rc6.d /var/lib/snapd/hostfs/etc/rc6.d rw,relatime master:33 - ext4 /dev/sda3 rw,data=ordered
-2150 2119 1:1 /system-data/etc/rcS.d /var/lib/snapd/hostfs/etc/rcS.d rw,relatime master:34 - ext4 /dev/sda3 rw,data=ordered
-2151 2119 1:1 /system-data/etc/rsyslog.d /var/lib/snapd/hostfs/etc/rsyslog.d rw,relatime master:35 - ext4 /dev/sda3 rw,data=ordered
-2152 2119 1:1 /system-data/root/test-etc/shadow /var/lib/snapd/hostfs/etc/shadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2153 2119 1:1 /system-data/etc/ssh /var/lib/snapd/hostfs/etc/ssh rw,relatime master:36 - ext4 /dev/sda3 rw,data=ordered
-2154 2119 1:1 /system-data/etc/sudoers.d /var/lib/snapd/hostfs/etc/sudoers.d rw,relatime master:37 - ext4 /dev/sda3 rw,data=ordered
-2155 2119 1:1 /system-data/etc/sysctl.d /var/lib/snapd/hostfs/etc/sysctl.d rw,relatime master:38 - ext4 /dev/sda3 rw,data=ordered
-2156 2119 1:1 /system-data/etc/systemd/logind.conf.d /var/lib/snapd/hostfs/etc/systemd/logind.conf.d rw,relatime master:39 - ext4 /dev/sda3 rw,data=ordered
-2157 2119 1:1 /system-data/etc/systemd/network /var/lib/snapd/hostfs/etc/systemd/network rw,relatime master:40 - ext4 /dev/sda3 rw,data=ordered
-2158 2119 1:1 /system-data/etc/systemd/system /var/lib/snapd/hostfs/etc/systemd/system rw,relatime master:41 - ext4 /dev/sda3 rw,data=ordered
-2159 2158 1:1 /system-data/etc/systemd/system /var/lib/snapd/hostfs/etc/systemd/system rw,relatime master:42 - ext4 /dev/sda3 rw,data=ordered
-2160 2119 1:1 /system-data/etc/systemd/system.conf.d /var/lib/snapd/hostfs/etc/systemd/system.conf.d rw,relatime master:43 - ext4 /dev/sda3 rw,data=ordered
-2161 2119 1:1 /system-data/etc/systemd/timesyncd.conf /var/lib/snapd/hostfs/etc/systemd/timesyncd.conf rw,relatime master:44 - ext4 /dev/sda3 rw,data=ordered
-2162 2119 1:1 /system-data/etc/systemd/user.conf.d /var/lib/snapd/hostfs/etc/systemd/user.conf.d rw,relatime master:45 - ext4 /dev/sda3 rw,data=ordered
-2163 2119 1:1 /system-data/etc/udev/rules.d /var/lib/snapd/hostfs/etc/udev/rules.d rw,relatime master:46 - ext4 /dev/sda3 rw,data=ordered
-2164 2119 1:1 /system-data/etc/update-motd.d /var/lib/snapd/hostfs/etc/update-motd.d rw,relatime master:47 - ext4 /dev/sda3 rw,data=ordered
-2165 2119 1:1 /system-data/etc/writable /var/lib/snapd/hostfs/etc/writable rw,relatime master:48 - ext4 /dev/sda3 rw,data=ordered
-2166 2119 1:1 /user-data /var/lib/snapd/hostfs/home rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2167 2119 0:1 /firmware /var/lib/snapd/hostfs/lib/firmware ro,relatime master:49 - squashfs /dev/loop1 ro
-2168 2119 0:1 /modules /var/lib/snapd/hostfs/lib/modules ro,relatime master:50 - squashfs /dev/loop1 ro
-2169 2119 2:6 / /var/lib/snapd/hostfs/media rw,relatime master:51 - tmpfs tmpfs rw
-2170 2119 2:7 / /var/lib/snapd/hostfs/mnt rw,relatime master:52 - tmpfs tmpfs rw
-2171 2119 1:1 /system-data/root /var/lib/snapd/hostfs/root rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2172 2119 2:5 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:56 - tmpfs tmpfs rw,mode=755
-2173 2172 2:10 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:55 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2174 2173 2:11 / /var/lib/snapd/hostfs/run/cgmanager/fs rw,relatime master:57 - tmpfs cgmfs rw,size=VARIABLE,mode=755
-2175 2173 2:12 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:58 - tmpfs tmpfs rw,size=VARIABLE
-2176 2173 2:10 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2177 2173 2:13 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:59 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-2178 2119 1:1 /system-data/snap /var/lib/snapd/hostfs/snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2179 2178 0:2 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
-2180 2178 0:3 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
-2181 2178 0:4 / /var/lib/snapd/hostfs/snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
-2182 2178 0:5 / /var/lib/snapd/hostfs/snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
-2183 2178 0:6 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
-2184 2178 0:7 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
-2185 2178 0:8 / /var/lib/snapd/hostfs/snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
-2186 2119 2:31 / /var/lib/snapd/hostfs/tmp rw,relatime master:84 - tmpfs tmpfs rw
-2187 2119 1:1 /system-data/var/cache/apparmor /var/lib/snapd/hostfs/var/cache/apparmor rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2188 2119 1:1 /system-data/var/cache/snapd /var/lib/snapd/hostfs/var/cache/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2189 2119 1:1 /system-data/var/lib/apparmor /var/lib/snapd/hostfs/var/lib/apparmor rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2190 2119 1:1 /system-data/var/lib/cloud /var/lib/snapd/hostfs/var/lib/cloud rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2191 2119 1:1 /system-data/var/lib/console-conf /var/lib/snapd/hostfs/var/lib/console-conf rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2192 2119 1:1 /system-data/var/lib/dbus /var/lib/snapd/hostfs/var/lib/dbus rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2193 2119 1:1 /system-data/var/lib/dhcp /var/lib/snapd/hostfs/var/lib/dhcp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2194 2119 1:1 /system-data/var/lib/extrausers /var/lib/snapd/hostfs/var/lib/extrausers rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2195 2119 1:1 /system-data/var/lib/initramfs-tools /var/lib/snapd/hostfs/var/lib/initramfs-tools rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2196 2119 1:1 /system-data/var/lib/logrotate /var/lib/snapd/hostfs/var/lib/logrotate rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2197 2119 1:1 /system-data/var/lib/misc /var/lib/snapd/hostfs/var/lib/misc rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2198 2119 1:1 /system-data/var/lib/snapd /var/lib/snapd/hostfs/var/lib/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2199 2119 2:32 / /var/lib/snapd/hostfs/var/lib/sudo rw,relatime master:85 - tmpfs tmpfs rw,mode=700
-2200 2119 1:1 /system-data/var/lib/systemd/random-seed /var/lib/snapd/hostfs/var/lib/systemd/random-seed rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2201 2119 1:1 /system-data/var/lib/systemd/rfkill /var/lib/snapd/hostfs/var/lib/systemd/rfkill rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2202 2119 1:1 /system-data/var/lib/waagent /var/lib/snapd/hostfs/var/lib/waagent rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2203 2119 1:1 /system-data/var/log /var/lib/snapd/hostfs/var/log rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2204 2119 1:1 /system-data/var/snap /var/lib/snapd/hostfs/var/snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2205 2119 1:1 /system-data/var/tmp /var/lib/snapd/hostfs/var/tmp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2206 2119 1:1 / /var/lib/snapd/hostfs/writable rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2207 2206 0:2 / /var/lib/snapd/hostfs/writable/system-data/snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
-2208 2206 0:3 / /var/lib/snapd/hostfs/writable/system-data/snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
-2209 2206 0:4 / /var/lib/snapd/hostfs/writable/system-data/snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
-2210 2206 0:5 / /var/lib/snapd/hostfs/writable/system-data/snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
-2211 2206 0:6 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
-2212 2206 0:7 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
-2213 2206 0:8 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
-2214 2001 2:32 / /var/lib/sudo rw,relatime master:85 - tmpfs tmpfs rw,mode=700
-2215 2001 1:1 /system-data/var/lib/systemd/random-seed /var/lib/systemd/random-seed rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2216 2001 1:1 /system-data/var/lib/systemd/rfkill /var/lib/systemd/rfkill rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2217 2001 1:1 /system-data/var/lib/waagent /var/lib/waagent rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2218 2001 1:1 /system-data/var/log /var/log rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2219 2001 1:1 /system-data/var/snap /var/snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2220 2001 1:1 /system-data/var/tmp /var/tmp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2221 2001 1:1 / /writable rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2222 2221 0:2 / /writable/system-data/snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
-2223 2221 0:3 / /writable/system-data/snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
-2224 2221 0:4 / /writable/system-data/snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
-2225 2221 0:5 / /writable/system-data/snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
-2226 2221 0:6 / /writable/system-data/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
-2227 2221 0:7 / /writable/system-data/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
-2228 2221 0:8 / /writable/system-data/snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
+0:0 / / ro,relatime master:1 - squashfs /dev/loop0 ro
+1:0 / /boot/efi rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:0 /EFI/ubuntu /boot/grub rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+2:0 / /dev rw,nosuid,relatime master:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
+2:1 / /dev/hugepages rw,relatime master:4 - hugetlbfs hugetlbfs rw
+2:2 / /dev/mqueue rw,relatime master:5 - mqueue mqueue rw
+2:33 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+2:3 / /dev/pts rw,nosuid,noexec,relatime master:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
+2:33 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+2:4 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
+1:1 /system-data/etc/apparmor.d/cache /etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/cloud /etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/dbus-1/system.d /etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/keyboard /etc/default/keyboard rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/swapfile /etc/default/swapfile rw,relatime master:12 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/environment /etc/environment rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+2:5 /image.fstab /etc/fstab rw,nosuid,noexec,relatime master:14 - tmpfs tmpfs rw,mode=755
+1:1 /system-data/root/test-etc/group /etc/group ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/gshadow /etc/gshadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/hosts /etc/hosts rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/init /etc/init rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/init.d /etc/init.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/iproute2 /etc/iproute2 rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/machine-id /etc/machine-id rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modprobe.d /etc/modprobe.d rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modules-load.d /etc/modules-load.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/netplan /etc/netplan rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/if-up.d /etc/network/if-up.d rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/interfaces.d /etc/network/interfaces.d rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/passwd /etc/passwd ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ppp /etc/ppp rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc0.d /etc/rc0.d rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc1.d /etc/rc1.d rw,relatime master:28 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc2.d /etc/rc2.d rw,relatime master:29 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc3.d /etc/rc3.d rw,relatime master:30 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc4.d /etc/rc4.d rw,relatime master:31 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc5.d /etc/rc5.d rw,relatime master:32 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc6.d /etc/rc6.d rw,relatime master:33 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rcS.d /etc/rcS.d rw,relatime master:34 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rsyslog.d /etc/rsyslog.d rw,relatime master:35 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/shadow /etc/shadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ssh /etc/ssh rw,relatime master:36 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sudoers.d /etc/sudoers.d rw,relatime master:37 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sysctl.d /etc/sysctl.d rw,relatime master:38 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/logind.conf.d /etc/systemd/logind.conf.d rw,relatime master:39 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/network /etc/systemd/network rw,relatime master:40 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime master:41 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime master:42 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system.conf.d /etc/systemd/system.conf.d rw,relatime master:43 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/timesyncd.conf /etc/systemd/timesyncd.conf rw,relatime master:44 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/user.conf.d /etc/systemd/user.conf.d rw,relatime master:45 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/udev/rules.d /etc/udev/rules.d rw,relatime master:46 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/update-motd.d /etc/update-motd.d rw,relatime master:47 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/writable /etc/writable rw,relatime master:48 - ext4 /dev/sda3 rw,data=ordered
+1:1 /user-data /home rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:1 /firmware /lib/firmware ro,relatime master:49 - squashfs /dev/loop1 ro
+0:1 /modules /lib/modules ro,relatime master:50 - squashfs /dev/loop1 ro
+2:6 / /media rw,relatime master:51 - tmpfs tmpfs rw
+2:6 / /media rw,relatime shared:51 - tmpfs tmpfs rw
+2:7 / /mnt rw,relatime master:52 - tmpfs tmpfs rw
+2:8 / /proc rw,nosuid,nodev,noexec,relatime master:53 - proc proc rw
+2:9 / /proc/sys/fs/binfmt_misc rw,relatime master:54 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct
+1:1 /system-data/root /root rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+2:5 / /run rw,nosuid,noexec,relatime master:56 - tmpfs tmpfs rw,mode=755
+2:10 / /run rw,nosuid,noexec,relatime master:55 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:11 / /run/cgmanager/fs rw,relatime master:57 - tmpfs cgmfs rw,size=VARIABLE,mode=755
+2:12 / /run/lock rw,nosuid,nodev,noexec,relatime master:58 - tmpfs tmpfs rw,size=VARIABLE
+2:10 /netns /run/netns rw,nosuid,noexec,relatime shared:55 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:10 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:13 / /run/user/0 rw,nosuid,nodev,relatime master:59 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+1:1 /system-data/snap /snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
+0:3 / /snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
+0:4 / /snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
+0:5 / /snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
+0:6 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
+0:7 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
+0:8 / /snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
+2:14 / /sys rw,nosuid,nodev,noexec,relatime master:67 - sysfs sysfs rw
+2:15 / /sys/fs/cgroup rw master:68 - tmpfs tmpfs rw,mode=755
+2:16 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:69 - cgroup cgroup rw,blkio
+2:17 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:70 - cgroup cgroup rw,cpu,cpuacct
+2:18 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:71 - cgroup cgroup rw,cpuset,clone_children
+2:19 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:72 - cgroup cgroup rw,devices
+2:20 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:73 - cgroup cgroup rw,freezer
+2:21 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:74 - cgroup cgroup rw,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb
+2:22 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:75 - cgroup cgroup rw,memory
+2:23 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:76 - cgroup cgroup rw,net_cls,net_prio
+2:24 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:77 - cgroup cgroup rw,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event
+2:25 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:78 - cgroup cgroup rw,pids,release_agent=/run/cgmanager/agents/cgm-release-agent.pids
+2:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:79 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
+2:27 / /sys/fs/fuse/connections rw,relatime master:80 - fusectl fusectl rw
+2:28 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:81 - pstore pstore rw
+2:29 / /sys/kernel/debug rw,relatime master:82 - debugfs debugfs rw
+2:30 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:83 - securityfs securityfs rw
+2:31 / /tmp rw,relatime master:84 - tmpfs tmpfs rw
+2:31 /snap.test-snapd-mountinfo-core16/tmp /tmp rw,relatime - tmpfs tmpfs rw
+2:34 / /usr/share/gdb rw,relatime - tmpfs tmpfs rw,mode=755
+0:0 /usr/share/gdb/auto-load /usr/share/gdb/auto-load ro,relatime master:1 - squashfs /dev/loop0 ro
+2:35 / /usr/share/gdb/test rw,relatime - tmpfs tmpfs rw
+1:1 /system-data/var/cache/apparmor /var/cache/apparmor rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/cache/snapd /var/cache/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/apparmor /var/lib/apparmor rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/cloud /var/lib/cloud rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/console-conf /var/lib/console-conf rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/dbus /var/lib/dbus rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/dhcp /var/lib/dhcp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/extrausers /var/lib/extrausers rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/initramfs-tools /var/lib/initramfs-tools rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/logrotate /var/lib/logrotate rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/misc /var/lib/misc rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/snapd /var/lib/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:0 / /var/lib/snapd/hostfs ro,relatime master:1 - squashfs /dev/loop0 ro
+1:1 /system-data/var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda3 rw,data=ordered
+1:0 / /var/lib/snapd/hostfs/boot/efi rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:0 /EFI/ubuntu /var/lib/snapd/hostfs/boot/grub rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:1 /system-data/etc/apparmor.d/cache /var/lib/snapd/hostfs/etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/cloud /var/lib/snapd/hostfs/etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/dbus-1/system.d /var/lib/snapd/hostfs/etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/keyboard /var/lib/snapd/hostfs/etc/default/keyboard rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/swapfile /var/lib/snapd/hostfs/etc/default/swapfile rw,relatime master:12 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/environment /var/lib/snapd/hostfs/etc/environment rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+2:5 /image.fstab /var/lib/snapd/hostfs/etc/fstab rw,nosuid,noexec,relatime master:14 - tmpfs tmpfs rw,mode=755
+1:1 /system-data/root/test-etc/group /var/lib/snapd/hostfs/etc/group ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/gshadow /var/lib/snapd/hostfs/etc/gshadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/hosts /var/lib/snapd/hostfs/etc/hosts rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/init /var/lib/snapd/hostfs/etc/init rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/init.d /var/lib/snapd/hostfs/etc/init.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/iproute2 /var/lib/snapd/hostfs/etc/iproute2 rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/machine-id /var/lib/snapd/hostfs/etc/machine-id rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modprobe.d /var/lib/snapd/hostfs/etc/modprobe.d rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modules-load.d /var/lib/snapd/hostfs/etc/modules-load.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/netplan /var/lib/snapd/hostfs/etc/netplan rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/if-up.d /var/lib/snapd/hostfs/etc/network/if-up.d rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/interfaces.d /var/lib/snapd/hostfs/etc/network/interfaces.d rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/passwd /var/lib/snapd/hostfs/etc/passwd ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ppp /var/lib/snapd/hostfs/etc/ppp rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc0.d /var/lib/snapd/hostfs/etc/rc0.d rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc1.d /var/lib/snapd/hostfs/etc/rc1.d rw,relatime master:28 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc2.d /var/lib/snapd/hostfs/etc/rc2.d rw,relatime master:29 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc3.d /var/lib/snapd/hostfs/etc/rc3.d rw,relatime master:30 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc4.d /var/lib/snapd/hostfs/etc/rc4.d rw,relatime master:31 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc5.d /var/lib/snapd/hostfs/etc/rc5.d rw,relatime master:32 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc6.d /var/lib/snapd/hostfs/etc/rc6.d rw,relatime master:33 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rcS.d /var/lib/snapd/hostfs/etc/rcS.d rw,relatime master:34 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rsyslog.d /var/lib/snapd/hostfs/etc/rsyslog.d rw,relatime master:35 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/shadow /var/lib/snapd/hostfs/etc/shadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ssh /var/lib/snapd/hostfs/etc/ssh rw,relatime master:36 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sudoers.d /var/lib/snapd/hostfs/etc/sudoers.d rw,relatime master:37 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sysctl.d /var/lib/snapd/hostfs/etc/sysctl.d rw,relatime master:38 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/logind.conf.d /var/lib/snapd/hostfs/etc/systemd/logind.conf.d rw,relatime master:39 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/network /var/lib/snapd/hostfs/etc/systemd/network rw,relatime master:40 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /var/lib/snapd/hostfs/etc/systemd/system rw,relatime master:41 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /var/lib/snapd/hostfs/etc/systemd/system rw,relatime master:42 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system.conf.d /var/lib/snapd/hostfs/etc/systemd/system.conf.d rw,relatime master:43 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/timesyncd.conf /var/lib/snapd/hostfs/etc/systemd/timesyncd.conf rw,relatime master:44 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/user.conf.d /var/lib/snapd/hostfs/etc/systemd/user.conf.d rw,relatime master:45 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/udev/rules.d /var/lib/snapd/hostfs/etc/udev/rules.d rw,relatime master:46 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/update-motd.d /var/lib/snapd/hostfs/etc/update-motd.d rw,relatime master:47 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/writable /var/lib/snapd/hostfs/etc/writable rw,relatime master:48 - ext4 /dev/sda3 rw,data=ordered
+1:1 /user-data /var/lib/snapd/hostfs/home rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:1 /firmware /var/lib/snapd/hostfs/lib/firmware ro,relatime master:49 - squashfs /dev/loop1 ro
+0:1 /modules /var/lib/snapd/hostfs/lib/modules ro,relatime master:50 - squashfs /dev/loop1 ro
+2:6 / /var/lib/snapd/hostfs/media rw,relatime master:51 - tmpfs tmpfs rw
+2:7 / /var/lib/snapd/hostfs/mnt rw,relatime master:52 - tmpfs tmpfs rw
+1:1 /system-data/root /var/lib/snapd/hostfs/root rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+2:5 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:56 - tmpfs tmpfs rw,mode=755
+2:10 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:55 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:11 / /var/lib/snapd/hostfs/run/cgmanager/fs rw,relatime master:57 - tmpfs cgmfs rw,size=VARIABLE,mode=755
+2:12 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:58 - tmpfs tmpfs rw,size=VARIABLE
+2:10 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:13 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:59 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+1:1 /system-data/snap /var/lib/snapd/hostfs/snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
+0:3 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
+0:4 / /var/lib/snapd/hostfs/snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
+0:5 / /var/lib/snapd/hostfs/snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
+0:6 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
+0:7 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
+0:8 / /var/lib/snapd/hostfs/snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
+2:31 / /var/lib/snapd/hostfs/tmp rw,relatime master:84 - tmpfs tmpfs rw
+1:1 /system-data/var/cache/apparmor /var/lib/snapd/hostfs/var/cache/apparmor rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/cache/snapd /var/lib/snapd/hostfs/var/cache/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/apparmor /var/lib/snapd/hostfs/var/lib/apparmor rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/cloud /var/lib/snapd/hostfs/var/lib/cloud rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/console-conf /var/lib/snapd/hostfs/var/lib/console-conf rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/dbus /var/lib/snapd/hostfs/var/lib/dbus rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/dhcp /var/lib/snapd/hostfs/var/lib/dhcp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/extrausers /var/lib/snapd/hostfs/var/lib/extrausers rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/initramfs-tools /var/lib/snapd/hostfs/var/lib/initramfs-tools rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/logrotate /var/lib/snapd/hostfs/var/lib/logrotate rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/misc /var/lib/snapd/hostfs/var/lib/misc rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/snapd /var/lib/snapd/hostfs/var/lib/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+2:32 / /var/lib/snapd/hostfs/var/lib/sudo rw,relatime master:85 - tmpfs tmpfs rw,mode=700
+1:1 /system-data/var/lib/systemd/random-seed /var/lib/snapd/hostfs/var/lib/systemd/random-seed rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/systemd/rfkill /var/lib/snapd/hostfs/var/lib/systemd/rfkill rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/waagent /var/lib/snapd/hostfs/var/lib/waagent rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/log /var/lib/snapd/hostfs/var/log rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/snap /var/lib/snapd/hostfs/var/snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/tmp /var/lib/snapd/hostfs/var/tmp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 / /var/lib/snapd/hostfs/writable rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /var/lib/snapd/hostfs/writable/system-data/snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
+0:3 / /var/lib/snapd/hostfs/writable/system-data/snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
+0:4 / /var/lib/snapd/hostfs/writable/system-data/snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
+0:5 / /var/lib/snapd/hostfs/writable/system-data/snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
+0:6 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
+0:7 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
+0:8 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
+2:32 / /var/lib/sudo rw,relatime master:85 - tmpfs tmpfs rw,mode=700
+1:1 /system-data/var/lib/systemd/random-seed /var/lib/systemd/random-seed rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/systemd/rfkill /var/lib/systemd/rfkill rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/waagent /var/lib/waagent rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/log /var/log rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/snap /var/snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/tmp /var/tmp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 / /writable rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /writable/system-data/snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
+0:3 / /writable/system-data/snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
+0:4 / /writable/system-data/snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
+0:5 / /writable/system-data/snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
+0:6 / /writable/system-data/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
+0:7 / /writable/system-data/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
+0:8 / /writable/system-data/snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-core-16-64/PER-USER-18.expected.txt snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-core-16-64/PER-USER-18.expected.txt
--- snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-core-16-64/PER-USER-18.expected.txt 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-core-16-64/PER-USER-18.expected.txt 2019-10-30 12:17:43.000000000 +0000
@@ -1,199 +1,202 @@
-2001 2000 0:3 / / ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
-2002 2001 2:0 / /dev rw,nosuid,relatime master:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
-2003 2002 2:1 / /dev/hugepages rw,relatime master:4 - hugetlbfs hugetlbfs rw
-2004 2002 2:2 / /dev/mqueue rw,relatime master:5 - mqueue mqueue rw
-2005 2002 2:33 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-2006 2002 2:3 / /dev/pts rw,nosuid,noexec,relatime master:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
-2007 2006 2:33 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-2008 2002 2:4 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
-2009 2001 0:0 /etc /etc ro,relatime master:1 - squashfs /dev/loop0 ro
-2010 2009 1:1 /system-data/etc/apparmor.d/cache /etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
-2011 2009 1:1 /system-data/etc/cloud /etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
-2012 2009 1:1 /system-data/etc/dbus-1/system.d /etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
-2013 2009 1:1 /system-data/etc/default/keyboard /etc/default/keyboard rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
-2014 2009 1:1 /system-data/etc/default/swapfile /etc/default/swapfile rw,relatime master:12 - ext4 /dev/sda3 rw,data=ordered
-2015 2009 1:1 /system-data/etc/environment /etc/environment rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2016 2009 2:5 /image.fstab /etc/fstab rw,nosuid,noexec,relatime master:14 - tmpfs tmpfs rw,mode=755
-2017 2009 1:1 /system-data/root/test-etc/group /etc/group ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2018 2009 1:1 /system-data/root/test-etc/gshadow /etc/gshadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2019 2009 1:1 /system-data/etc/hosts /etc/hosts rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
-2020 2009 1:1 /system-data/etc/init /etc/init rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
-2021 2009 1:1 /system-data/etc/init.d /etc/init.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
-2022 2009 1:1 /system-data/etc/iproute2 /etc/iproute2 rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
-2023 2009 1:1 /system-data/etc/machine-id /etc/machine-id rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
-2024 2009 1:1 /system-data/etc/modprobe.d /etc/modprobe.d rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
-2025 2009 1:1 /system-data/etc/modules-load.d /etc/modules-load.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
-2026 2009 1:1 /system-data/etc/netplan /etc/netplan rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
-2027 2009 1:1 /system-data/etc/network/if-up.d /etc/network/if-up.d rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
-2028 2009 1:1 /system-data/etc/network/interfaces.d /etc/network/interfaces.d rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
-2029 2009 0:3 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
-2030 2009 1:1 /system-data/root/test-etc/passwd /etc/passwd ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2031 2009 1:1 /system-data/etc/ppp /etc/ppp rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
-2032 2009 1:1 /system-data/etc/rc0.d /etc/rc0.d rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
-2033 2009 1:1 /system-data/etc/rc1.d /etc/rc1.d rw,relatime master:28 - ext4 /dev/sda3 rw,data=ordered
-2034 2009 1:1 /system-data/etc/rc2.d /etc/rc2.d rw,relatime master:29 - ext4 /dev/sda3 rw,data=ordered
-2035 2009 1:1 /system-data/etc/rc3.d /etc/rc3.d rw,relatime master:30 - ext4 /dev/sda3 rw,data=ordered
-2036 2009 1:1 /system-data/etc/rc4.d /etc/rc4.d rw,relatime master:31 - ext4 /dev/sda3 rw,data=ordered
-2037 2009 1:1 /system-data/etc/rc5.d /etc/rc5.d rw,relatime master:32 - ext4 /dev/sda3 rw,data=ordered
-2038 2009 1:1 /system-data/etc/rc6.d /etc/rc6.d rw,relatime master:33 - ext4 /dev/sda3 rw,data=ordered
-2039 2009 1:1 /system-data/etc/rcS.d /etc/rcS.d rw,relatime master:34 - ext4 /dev/sda3 rw,data=ordered
-2040 2009 1:1 /system-data/etc/rsyslog.d /etc/rsyslog.d rw,relatime master:35 - ext4 /dev/sda3 rw,data=ordered
-2041 2009 1:1 /system-data/root/test-etc/shadow /etc/shadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2042 2009 1:1 /system-data/etc/ssh /etc/ssh rw,relatime master:36 - ext4 /dev/sda3 rw,data=ordered
-2043 2009 0:3 /etc/ssl /etc/ssl ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
-2044 2009 1:1 /system-data/etc/sudoers.d /etc/sudoers.d rw,relatime master:37 - ext4 /dev/sda3 rw,data=ordered
-2045 2009 1:1 /system-data/etc/sysctl.d /etc/sysctl.d rw,relatime master:38 - ext4 /dev/sda3 rw,data=ordered
-2046 2009 1:1 /system-data/etc/systemd/logind.conf.d /etc/systemd/logind.conf.d rw,relatime master:39 - ext4 /dev/sda3 rw,data=ordered
-2047 2009 1:1 /system-data/etc/systemd/network /etc/systemd/network rw,relatime master:40 - ext4 /dev/sda3 rw,data=ordered
-2048 2009 1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime master:41 - ext4 /dev/sda3 rw,data=ordered
-2049 2048 1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime master:42 - ext4 /dev/sda3 rw,data=ordered
-2050 2009 1:1 /system-data/etc/systemd/system.conf.d /etc/systemd/system.conf.d rw,relatime master:43 - ext4 /dev/sda3 rw,data=ordered
-2051 2009 1:1 /system-data/etc/systemd/timesyncd.conf /etc/systemd/timesyncd.conf rw,relatime master:44 - ext4 /dev/sda3 rw,data=ordered
-2052 2009 1:1 /system-data/etc/systemd/user.conf.d /etc/systemd/user.conf.d rw,relatime master:45 - ext4 /dev/sda3 rw,data=ordered
-2053 2009 1:1 /system-data/etc/udev/rules.d /etc/udev/rules.d rw,relatime master:46 - ext4 /dev/sda3 rw,data=ordered
-2054 2009 1:1 /system-data/etc/update-motd.d /etc/update-motd.d rw,relatime master:47 - ext4 /dev/sda3 rw,data=ordered
-2055 2009 1:1 /system-data/etc/writable /etc/writable rw,relatime master:48 - ext4 /dev/sda3 rw,data=ordered
-2056 2001 1:1 /user-data /home rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2057 2001 0:1 /firmware /lib/firmware ro,relatime master:49 - squashfs /dev/loop1 ro
-2058 2001 0:1 /modules /lib/modules ro,relatime master:50 - squashfs /dev/loop1 ro
-2059 2001 2:6 / /media rw,relatime shared:51 - tmpfs tmpfs rw
-2060 2001 2:7 / /mnt rw,relatime master:52 - tmpfs tmpfs rw
-2061 2001 2:8 / /proc rw,nosuid,nodev,noexec,relatime master:53 - proc proc rw
-2062 2061 2:9 / /proc/sys/fs/binfmt_misc rw,relatime master:54 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct
-2063 2001 1:1 /system-data/root /root rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2064 2001 2:10 / /run rw,nosuid,noexec,relatime master:55 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2065 2064 2:11 / /run/cgmanager/fs rw,relatime master:57 - tmpfs cgmfs rw,size=VARIABLE,mode=755
-2066 2064 2:12 / /run/lock rw,nosuid,nodev,noexec,relatime master:58 - tmpfs tmpfs rw,size=VARIABLE
-2067 2064 2:10 /netns /run/netns rw,nosuid,noexec,relatime shared:55 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2068 2064 2:10 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2069 2064 2:13 / /run/user/0 rw,nosuid,nodev,relatime master:59 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-2070 2001 1:1 /system-data/snap /snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2071 2070 0:2 / /snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
-2072 2070 0:3 / /snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
-2073 2070 0:4 / /snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
-2074 2070 0:5 / /snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
-2075 2070 0:6 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
-2076 2070 0:7 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
-2077 2070 0:8 / /snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
-2078 2001 2:14 / /sys rw,nosuid,nodev,noexec,relatime master:67 - sysfs sysfs rw
-2079 2078 2:15 / /sys/fs/cgroup rw master:68 - tmpfs tmpfs rw,mode=755
-2080 2079 2:16 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:69 - cgroup cgroup rw,blkio
-2081 2079 2:17 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:70 - cgroup cgroup rw,cpu,cpuacct
-2082 2079 2:18 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:71 - cgroup cgroup rw,cpuset,clone_children
-2083 2079 2:19 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:72 - cgroup cgroup rw,devices
-2084 2079 2:20 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:73 - cgroup cgroup rw,freezer
-2085 2079 2:21 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:74 - cgroup cgroup rw,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb
-2086 2079 2:22 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:75 - cgroup cgroup rw,memory
-2087 2079 2:23 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:76 - cgroup cgroup rw,net_cls,net_prio
-2088 2079 2:24 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:77 - cgroup cgroup rw,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event
-2089 2079 2:25 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:78 - cgroup cgroup rw,pids,release_agent=/run/cgmanager/agents/cgm-release-agent.pids
-2090 2079 2:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:79 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
-2091 2078 2:27 / /sys/fs/fuse/connections rw,relatime master:80 - fusectl fusectl rw
-2092 2078 2:28 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:81 - pstore pstore rw
-2093 2078 2:29 / /sys/kernel/debug rw,relatime master:82 - debugfs debugfs rw
-2094 2078 2:30 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:83 - securityfs securityfs rw
-2095 2001 2:31 / /tmp rw,relatime master:84 - tmpfs tmpfs rw
-2096 2095 2:31 /snap.test-snapd-mountinfo-core18/tmp /tmp rw,relatime - tmpfs tmpfs rw
-2097 2001 0:0 /usr/lib/snapd /usr/lib/snapd ro,relatime master:1 - squashfs /dev/loop0 ro
-2098 2001 0:0 /usr/src /usr/src ro,relatime master:1 - squashfs /dev/loop0 ro
-2099 2001 1:1 /system-data/var/lib/extrausers /var/lib/extrausers rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2100 2001 1:1 /system-data/var/lib/snapd /var/lib/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2102 2101 0:0 / /var/lib/snapd/hostfs ro,relatime master:1 - squashfs /dev/loop0 ro
-2101 2100 1:1 /system-data/var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda3 rw,data=ordered
-2103 2102 1:0 / /var/lib/snapd/hostfs/boot/efi rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-2104 2102 1:0 /EFI/ubuntu /var/lib/snapd/hostfs/boot/grub rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-2105 2102 1:1 /system-data/etc/apparmor.d/cache /var/lib/snapd/hostfs/etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
-2106 2102 1:1 /system-data/etc/cloud /var/lib/snapd/hostfs/etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
-2107 2102 1:1 /system-data/etc/dbus-1/system.d /var/lib/snapd/hostfs/etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
-2108 2102 1:1 /system-data/etc/default/keyboard /var/lib/snapd/hostfs/etc/default/keyboard rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
-2109 2102 1:1 /system-data/etc/default/swapfile /var/lib/snapd/hostfs/etc/default/swapfile rw,relatime master:12 - ext4 /dev/sda3 rw,data=ordered
-2110 2102 1:1 /system-data/etc/environment /var/lib/snapd/hostfs/etc/environment rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2111 2102 2:5 /image.fstab /var/lib/snapd/hostfs/etc/fstab rw,nosuid,noexec,relatime master:14 - tmpfs tmpfs rw,mode=755
-2112 2102 1:1 /system-data/root/test-etc/group /var/lib/snapd/hostfs/etc/group ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2113 2102 1:1 /system-data/root/test-etc/gshadow /var/lib/snapd/hostfs/etc/gshadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2114 2102 1:1 /system-data/etc/hosts /var/lib/snapd/hostfs/etc/hosts rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
-2115 2102 1:1 /system-data/etc/init /var/lib/snapd/hostfs/etc/init rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
-2116 2102 1:1 /system-data/etc/init.d /var/lib/snapd/hostfs/etc/init.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
-2117 2102 1:1 /system-data/etc/iproute2 /var/lib/snapd/hostfs/etc/iproute2 rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
-2118 2102 1:1 /system-data/etc/machine-id /var/lib/snapd/hostfs/etc/machine-id rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
-2119 2102 1:1 /system-data/etc/modprobe.d /var/lib/snapd/hostfs/etc/modprobe.d rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
-2120 2102 1:1 /system-data/etc/modules-load.d /var/lib/snapd/hostfs/etc/modules-load.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
-2121 2102 1:1 /system-data/etc/netplan /var/lib/snapd/hostfs/etc/netplan rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
-2122 2102 1:1 /system-data/etc/network/if-up.d /var/lib/snapd/hostfs/etc/network/if-up.d rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
-2123 2102 1:1 /system-data/etc/network/interfaces.d /var/lib/snapd/hostfs/etc/network/interfaces.d rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
-2124 2102 1:1 /system-data/root/test-etc/passwd /var/lib/snapd/hostfs/etc/passwd ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2125 2102 1:1 /system-data/etc/ppp /var/lib/snapd/hostfs/etc/ppp rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
-2126 2102 1:1 /system-data/etc/rc0.d /var/lib/snapd/hostfs/etc/rc0.d rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
-2127 2102 1:1 /system-data/etc/rc1.d /var/lib/snapd/hostfs/etc/rc1.d rw,relatime master:28 - ext4 /dev/sda3 rw,data=ordered
-2128 2102 1:1 /system-data/etc/rc2.d /var/lib/snapd/hostfs/etc/rc2.d rw,relatime master:29 - ext4 /dev/sda3 rw,data=ordered
-2129 2102 1:1 /system-data/etc/rc3.d /var/lib/snapd/hostfs/etc/rc3.d rw,relatime master:30 - ext4 /dev/sda3 rw,data=ordered
-2130 2102 1:1 /system-data/etc/rc4.d /var/lib/snapd/hostfs/etc/rc4.d rw,relatime master:31 - ext4 /dev/sda3 rw,data=ordered
-2131 2102 1:1 /system-data/etc/rc5.d /var/lib/snapd/hostfs/etc/rc5.d rw,relatime master:32 - ext4 /dev/sda3 rw,data=ordered
-2132 2102 1:1 /system-data/etc/rc6.d /var/lib/snapd/hostfs/etc/rc6.d rw,relatime master:33 - ext4 /dev/sda3 rw,data=ordered
-2133 2102 1:1 /system-data/etc/rcS.d /var/lib/snapd/hostfs/etc/rcS.d rw,relatime master:34 - ext4 /dev/sda3 rw,data=ordered
-2134 2102 1:1 /system-data/etc/rsyslog.d /var/lib/snapd/hostfs/etc/rsyslog.d rw,relatime master:35 - ext4 /dev/sda3 rw,data=ordered
-2135 2102 1:1 /system-data/root/test-etc/shadow /var/lib/snapd/hostfs/etc/shadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2136 2102 1:1 /system-data/etc/ssh /var/lib/snapd/hostfs/etc/ssh rw,relatime master:36 - ext4 /dev/sda3 rw,data=ordered
-2137 2102 1:1 /system-data/etc/sudoers.d /var/lib/snapd/hostfs/etc/sudoers.d rw,relatime master:37 - ext4 /dev/sda3 rw,data=ordered
-2138 2102 1:1 /system-data/etc/sysctl.d /var/lib/snapd/hostfs/etc/sysctl.d rw,relatime master:38 - ext4 /dev/sda3 rw,data=ordered
-2139 2102 1:1 /system-data/etc/systemd/logind.conf.d /var/lib/snapd/hostfs/etc/systemd/logind.conf.d rw,relatime master:39 - ext4 /dev/sda3 rw,data=ordered
-2140 2102 1:1 /system-data/etc/systemd/network /var/lib/snapd/hostfs/etc/systemd/network rw,relatime master:40 - ext4 /dev/sda3 rw,data=ordered
-2141 2102 1:1 /system-data/etc/systemd/system /var/lib/snapd/hostfs/etc/systemd/system rw,relatime master:41 - ext4 /dev/sda3 rw,data=ordered
-2142 2141 1:1 /system-data/etc/systemd/system /var/lib/snapd/hostfs/etc/systemd/system rw,relatime master:42 - ext4 /dev/sda3 rw,data=ordered
-2143 2102 1:1 /system-data/etc/systemd/system.conf.d /var/lib/snapd/hostfs/etc/systemd/system.conf.d rw,relatime master:43 - ext4 /dev/sda3 rw,data=ordered
-2144 2102 1:1 /system-data/etc/systemd/timesyncd.conf /var/lib/snapd/hostfs/etc/systemd/timesyncd.conf rw,relatime master:44 - ext4 /dev/sda3 rw,data=ordered
-2145 2102 1:1 /system-data/etc/systemd/user.conf.d /var/lib/snapd/hostfs/etc/systemd/user.conf.d rw,relatime master:45 - ext4 /dev/sda3 rw,data=ordered
-2146 2102 1:1 /system-data/etc/udev/rules.d /var/lib/snapd/hostfs/etc/udev/rules.d rw,relatime master:46 - ext4 /dev/sda3 rw,data=ordered
-2147 2102 1:1 /system-data/etc/update-motd.d /var/lib/snapd/hostfs/etc/update-motd.d rw,relatime master:47 - ext4 /dev/sda3 rw,data=ordered
-2148 2102 1:1 /system-data/etc/writable /var/lib/snapd/hostfs/etc/writable rw,relatime master:48 - ext4 /dev/sda3 rw,data=ordered
-2149 2102 1:1 /user-data /var/lib/snapd/hostfs/home rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2150 2102 0:1 /firmware /var/lib/snapd/hostfs/lib/firmware ro,relatime master:49 - squashfs /dev/loop1 ro
-2151 2102 0:1 /modules /var/lib/snapd/hostfs/lib/modules ro,relatime master:50 - squashfs /dev/loop1 ro
-2152 2102 2:6 / /var/lib/snapd/hostfs/media rw,relatime master:51 - tmpfs tmpfs rw
-2153 2102 2:7 / /var/lib/snapd/hostfs/mnt rw,relatime master:52 - tmpfs tmpfs rw
-2154 2102 1:1 /system-data/root /var/lib/snapd/hostfs/root rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2155 2102 2:5 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:56 - tmpfs tmpfs rw,mode=755
-2156 2155 2:10 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:55 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2157 2156 2:11 / /var/lib/snapd/hostfs/run/cgmanager/fs rw,relatime master:57 - tmpfs cgmfs rw,size=VARIABLE,mode=755
-2158 2156 2:12 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:58 - tmpfs tmpfs rw,size=VARIABLE
-2159 2156 2:10 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2160 2156 2:13 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:59 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-2161 2102 1:1 /system-data/snap /var/lib/snapd/hostfs/snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2162 2161 0:2 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
-2163 2161 0:3 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
-2164 2161 0:4 / /var/lib/snapd/hostfs/snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
-2165 2161 0:5 / /var/lib/snapd/hostfs/snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
-2166 2161 0:6 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
-2167 2161 0:7 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
-2168 2161 0:8 / /var/lib/snapd/hostfs/snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
-2169 2102 2:31 / /var/lib/snapd/hostfs/tmp rw,relatime master:84 - tmpfs tmpfs rw
-2170 2102 1:1 /system-data/var/cache/apparmor /var/lib/snapd/hostfs/var/cache/apparmor rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2171 2102 1:1 /system-data/var/cache/snapd /var/lib/snapd/hostfs/var/cache/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2172 2102 1:1 /system-data/var/lib/apparmor /var/lib/snapd/hostfs/var/lib/apparmor rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2173 2102 1:1 /system-data/var/lib/cloud /var/lib/snapd/hostfs/var/lib/cloud rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2174 2102 1:1 /system-data/var/lib/console-conf /var/lib/snapd/hostfs/var/lib/console-conf rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2175 2102 1:1 /system-data/var/lib/dbus /var/lib/snapd/hostfs/var/lib/dbus rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2176 2102 1:1 /system-data/var/lib/dhcp /var/lib/snapd/hostfs/var/lib/dhcp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2177 2102 1:1 /system-data/var/lib/extrausers /var/lib/snapd/hostfs/var/lib/extrausers rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2178 2102 1:1 /system-data/var/lib/initramfs-tools /var/lib/snapd/hostfs/var/lib/initramfs-tools rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2179 2102 1:1 /system-data/var/lib/logrotate /var/lib/snapd/hostfs/var/lib/logrotate rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2180 2102 1:1 /system-data/var/lib/misc /var/lib/snapd/hostfs/var/lib/misc rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2181 2102 1:1 /system-data/var/lib/snapd /var/lib/snapd/hostfs/var/lib/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2182 2102 2:32 / /var/lib/snapd/hostfs/var/lib/sudo rw,relatime master:85 - tmpfs tmpfs rw,mode=700
-2183 2102 1:1 /system-data/var/lib/systemd/random-seed /var/lib/snapd/hostfs/var/lib/systemd/random-seed rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2184 2102 1:1 /system-data/var/lib/systemd/rfkill /var/lib/snapd/hostfs/var/lib/systemd/rfkill rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2185 2102 1:1 /system-data/var/lib/waagent /var/lib/snapd/hostfs/var/lib/waagent rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2186 2102 1:1 /system-data/var/log /var/lib/snapd/hostfs/var/log rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2187 2102 1:1 /system-data/var/snap /var/lib/snapd/hostfs/var/snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2188 2102 1:1 /system-data/var/tmp /var/lib/snapd/hostfs/var/tmp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2189 2102 1:1 / /var/lib/snapd/hostfs/writable rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2190 2189 0:2 / /var/lib/snapd/hostfs/writable/system-data/snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
-2191 2189 0:3 / /var/lib/snapd/hostfs/writable/system-data/snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
-2192 2189 0:4 / /var/lib/snapd/hostfs/writable/system-data/snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
-2193 2189 0:5 / /var/lib/snapd/hostfs/writable/system-data/snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
-2194 2189 0:6 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
-2195 2189 0:7 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
-2196 2189 0:8 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
-2197 2001 1:1 /system-data/var/log /var/log rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2198 2001 1:1 /system-data/var/snap /var/snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2199 2001 1:1 /system-data/var/tmp /var/tmp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:3 / / ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
+2:0 / /dev rw,nosuid,relatime master:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
+2:1 / /dev/hugepages rw,relatime master:4 - hugetlbfs hugetlbfs rw
+2:2 / /dev/mqueue rw,relatime master:5 - mqueue mqueue rw
+2:33 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+2:3 / /dev/pts rw,nosuid,noexec,relatime master:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
+2:33 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+2:4 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
+0:0 /etc /etc ro,relatime master:1 - squashfs /dev/loop0 ro
+1:1 /system-data/etc/apparmor.d/cache /etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/cloud /etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/dbus-1/system.d /etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/keyboard /etc/default/keyboard rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/swapfile /etc/default/swapfile rw,relatime master:12 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/environment /etc/environment rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+2:5 /image.fstab /etc/fstab rw,nosuid,noexec,relatime master:14 - tmpfs tmpfs rw,mode=755
+1:1 /system-data/root/test-etc/group /etc/group ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/gshadow /etc/gshadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/hosts /etc/hosts rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/init /etc/init rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/init.d /etc/init.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/iproute2 /etc/iproute2 rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/machine-id /etc/machine-id rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modprobe.d /etc/modprobe.d rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modules-load.d /etc/modules-load.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/netplan /etc/netplan rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/if-up.d /etc/network/if-up.d rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/interfaces.d /etc/network/interfaces.d rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
+0:3 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
+1:1 /system-data/root/test-etc/passwd /etc/passwd ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ppp /etc/ppp rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc0.d /etc/rc0.d rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc1.d /etc/rc1.d rw,relatime master:28 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc2.d /etc/rc2.d rw,relatime master:29 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc3.d /etc/rc3.d rw,relatime master:30 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc4.d /etc/rc4.d rw,relatime master:31 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc5.d /etc/rc5.d rw,relatime master:32 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc6.d /etc/rc6.d rw,relatime master:33 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rcS.d /etc/rcS.d rw,relatime master:34 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rsyslog.d /etc/rsyslog.d rw,relatime master:35 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/shadow /etc/shadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ssh /etc/ssh rw,relatime master:36 - ext4 /dev/sda3 rw,data=ordered
+0:3 /etc/ssl /etc/ssl ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
+1:1 /system-data/etc/sudoers.d /etc/sudoers.d rw,relatime master:37 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sysctl.d /etc/sysctl.d rw,relatime master:38 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/logind.conf.d /etc/systemd/logind.conf.d rw,relatime master:39 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/network /etc/systemd/network rw,relatime master:40 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime master:41 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime master:42 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system.conf.d /etc/systemd/system.conf.d rw,relatime master:43 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/timesyncd.conf /etc/systemd/timesyncd.conf rw,relatime master:44 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/user.conf.d /etc/systemd/user.conf.d rw,relatime master:45 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/udev/rules.d /etc/udev/rules.d rw,relatime master:46 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/update-motd.d /etc/update-motd.d rw,relatime master:47 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/writable /etc/writable rw,relatime master:48 - ext4 /dev/sda3 rw,data=ordered
+1:1 /user-data /home rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:1 /firmware /lib/firmware ro,relatime master:49 - squashfs /dev/loop1 ro
+0:1 /modules /lib/modules ro,relatime master:50 - squashfs /dev/loop1 ro
+2:6 / /media rw,relatime shared:51 - tmpfs tmpfs rw
+2:7 / /mnt rw,relatime master:52 - tmpfs tmpfs rw
+2:8 / /proc rw,nosuid,nodev,noexec,relatime master:53 - proc proc rw
+2:9 / /proc/sys/fs/binfmt_misc rw,relatime master:54 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct
+1:1 /system-data/root /root rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+2:10 / /run rw,nosuid,noexec,relatime master:55 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:11 / /run/cgmanager/fs rw,relatime master:57 - tmpfs cgmfs rw,size=VARIABLE,mode=755
+2:12 / /run/lock rw,nosuid,nodev,noexec,relatime master:58 - tmpfs tmpfs rw,size=VARIABLE
+2:10 /netns /run/netns rw,nosuid,noexec,relatime shared:55 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:10 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:13 / /run/user/0 rw,nosuid,nodev,relatime master:59 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+1:1 /system-data/snap /snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
+0:3 / /snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
+0:4 / /snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
+0:5 / /snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
+0:6 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
+0:7 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
+0:8 / /snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
+2:14 / /sys rw,nosuid,nodev,noexec,relatime master:67 - sysfs sysfs rw
+2:15 / /sys/fs/cgroup rw master:68 - tmpfs tmpfs rw,mode=755
+2:16 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:69 - cgroup cgroup rw,blkio
+2:17 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:70 - cgroup cgroup rw,cpu,cpuacct
+2:18 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:71 - cgroup cgroup rw,cpuset,clone_children
+2:19 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:72 - cgroup cgroup rw,devices
+2:20 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:73 - cgroup cgroup rw,freezer
+2:21 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:74 - cgroup cgroup rw,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb
+2:22 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:75 - cgroup cgroup rw,memory
+2:23 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:76 - cgroup cgroup rw,net_cls,net_prio
+2:24 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:77 - cgroup cgroup rw,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event
+2:25 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:78 - cgroup cgroup rw,pids,release_agent=/run/cgmanager/agents/cgm-release-agent.pids
+2:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:79 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
+2:27 / /sys/fs/fuse/connections rw,relatime master:80 - fusectl fusectl rw
+2:28 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:81 - pstore pstore rw
+2:29 / /sys/kernel/debug rw,relatime master:82 - debugfs debugfs rw
+2:30 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:83 - securityfs securityfs rw
+2:31 / /tmp rw,relatime master:84 - tmpfs tmpfs rw
+2:31 /snap.test-snapd-mountinfo-core18/tmp /tmp rw,relatime - tmpfs tmpfs rw
+0:0 /usr/lib/snapd /usr/lib/snapd ro,relatime master:1 - squashfs /dev/loop0 ro
+2:34 / /usr/share/gdb rw,relatime - tmpfs tmpfs rw,mode=755
+0:3 /usr/share/gdb/auto-load /usr/share/gdb/auto-load ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
+2:35 / /usr/share/gdb/test rw,relatime - tmpfs tmpfs rw
+0:0 /usr/src /usr/src ro,relatime master:1 - squashfs /dev/loop0 ro
+1:1 /system-data/var/lib/extrausers /var/lib/extrausers rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/snapd /var/lib/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:0 / /var/lib/snapd/hostfs ro,relatime master:1 - squashfs /dev/loop0 ro
+1:1 /system-data/var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda3 rw,data=ordered
+1:0 / /var/lib/snapd/hostfs/boot/efi rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:0 /EFI/ubuntu /var/lib/snapd/hostfs/boot/grub rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:1 /system-data/etc/apparmor.d/cache /var/lib/snapd/hostfs/etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/cloud /var/lib/snapd/hostfs/etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/dbus-1/system.d /var/lib/snapd/hostfs/etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/keyboard /var/lib/snapd/hostfs/etc/default/keyboard rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/swapfile /var/lib/snapd/hostfs/etc/default/swapfile rw,relatime master:12 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/environment /var/lib/snapd/hostfs/etc/environment rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+2:5 /image.fstab /var/lib/snapd/hostfs/etc/fstab rw,nosuid,noexec,relatime master:14 - tmpfs tmpfs rw,mode=755
+1:1 /system-data/root/test-etc/group /var/lib/snapd/hostfs/etc/group ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/gshadow /var/lib/snapd/hostfs/etc/gshadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/hosts /var/lib/snapd/hostfs/etc/hosts rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/init /var/lib/snapd/hostfs/etc/init rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/init.d /var/lib/snapd/hostfs/etc/init.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/iproute2 /var/lib/snapd/hostfs/etc/iproute2 rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/machine-id /var/lib/snapd/hostfs/etc/machine-id rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modprobe.d /var/lib/snapd/hostfs/etc/modprobe.d rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modules-load.d /var/lib/snapd/hostfs/etc/modules-load.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/netplan /var/lib/snapd/hostfs/etc/netplan rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/if-up.d /var/lib/snapd/hostfs/etc/network/if-up.d rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/interfaces.d /var/lib/snapd/hostfs/etc/network/interfaces.d rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/passwd /var/lib/snapd/hostfs/etc/passwd ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ppp /var/lib/snapd/hostfs/etc/ppp rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc0.d /var/lib/snapd/hostfs/etc/rc0.d rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc1.d /var/lib/snapd/hostfs/etc/rc1.d rw,relatime master:28 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc2.d /var/lib/snapd/hostfs/etc/rc2.d rw,relatime master:29 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc3.d /var/lib/snapd/hostfs/etc/rc3.d rw,relatime master:30 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc4.d /var/lib/snapd/hostfs/etc/rc4.d rw,relatime master:31 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc5.d /var/lib/snapd/hostfs/etc/rc5.d rw,relatime master:32 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rc6.d /var/lib/snapd/hostfs/etc/rc6.d rw,relatime master:33 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rcS.d /var/lib/snapd/hostfs/etc/rcS.d rw,relatime master:34 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/rsyslog.d /var/lib/snapd/hostfs/etc/rsyslog.d rw,relatime master:35 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/shadow /var/lib/snapd/hostfs/etc/shadow ro,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ssh /var/lib/snapd/hostfs/etc/ssh rw,relatime master:36 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sudoers.d /var/lib/snapd/hostfs/etc/sudoers.d rw,relatime master:37 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sysctl.d /var/lib/snapd/hostfs/etc/sysctl.d rw,relatime master:38 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/logind.conf.d /var/lib/snapd/hostfs/etc/systemd/logind.conf.d rw,relatime master:39 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/network /var/lib/snapd/hostfs/etc/systemd/network rw,relatime master:40 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /var/lib/snapd/hostfs/etc/systemd/system rw,relatime master:41 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /var/lib/snapd/hostfs/etc/systemd/system rw,relatime master:42 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system.conf.d /var/lib/snapd/hostfs/etc/systemd/system.conf.d rw,relatime master:43 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/timesyncd.conf /var/lib/snapd/hostfs/etc/systemd/timesyncd.conf rw,relatime master:44 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/user.conf.d /var/lib/snapd/hostfs/etc/systemd/user.conf.d rw,relatime master:45 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/udev/rules.d /var/lib/snapd/hostfs/etc/udev/rules.d rw,relatime master:46 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/update-motd.d /var/lib/snapd/hostfs/etc/update-motd.d rw,relatime master:47 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/writable /var/lib/snapd/hostfs/etc/writable rw,relatime master:48 - ext4 /dev/sda3 rw,data=ordered
+1:1 /user-data /var/lib/snapd/hostfs/home rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:1 /firmware /var/lib/snapd/hostfs/lib/firmware ro,relatime master:49 - squashfs /dev/loop1 ro
+0:1 /modules /var/lib/snapd/hostfs/lib/modules ro,relatime master:50 - squashfs /dev/loop1 ro
+2:6 / /var/lib/snapd/hostfs/media rw,relatime master:51 - tmpfs tmpfs rw
+2:7 / /var/lib/snapd/hostfs/mnt rw,relatime master:52 - tmpfs tmpfs rw
+1:1 /system-data/root /var/lib/snapd/hostfs/root rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+2:5 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:56 - tmpfs tmpfs rw,mode=755
+2:10 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:55 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:11 / /var/lib/snapd/hostfs/run/cgmanager/fs rw,relatime master:57 - tmpfs cgmfs rw,size=VARIABLE,mode=755
+2:12 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:58 - tmpfs tmpfs rw,size=VARIABLE
+2:10 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:13 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:59 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+1:1 /system-data/snap /var/lib/snapd/hostfs/snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
+0:3 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
+0:4 / /var/lib/snapd/hostfs/snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
+0:5 / /var/lib/snapd/hostfs/snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
+0:6 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
+0:7 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
+0:8 / /var/lib/snapd/hostfs/snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
+2:31 / /var/lib/snapd/hostfs/tmp rw,relatime master:84 - tmpfs tmpfs rw
+1:1 /system-data/var/cache/apparmor /var/lib/snapd/hostfs/var/cache/apparmor rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/cache/snapd /var/lib/snapd/hostfs/var/cache/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/apparmor /var/lib/snapd/hostfs/var/lib/apparmor rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/cloud /var/lib/snapd/hostfs/var/lib/cloud rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/console-conf /var/lib/snapd/hostfs/var/lib/console-conf rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/dbus /var/lib/snapd/hostfs/var/lib/dbus rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/dhcp /var/lib/snapd/hostfs/var/lib/dhcp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/extrausers /var/lib/snapd/hostfs/var/lib/extrausers rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/initramfs-tools /var/lib/snapd/hostfs/var/lib/initramfs-tools rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/logrotate /var/lib/snapd/hostfs/var/lib/logrotate rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/misc /var/lib/snapd/hostfs/var/lib/misc rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/snapd /var/lib/snapd/hostfs/var/lib/snapd rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+2:32 / /var/lib/snapd/hostfs/var/lib/sudo rw,relatime master:85 - tmpfs tmpfs rw,mode=700
+1:1 /system-data/var/lib/systemd/random-seed /var/lib/snapd/hostfs/var/lib/systemd/random-seed rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/systemd/rfkill /var/lib/snapd/hostfs/var/lib/systemd/rfkill rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/waagent /var/lib/snapd/hostfs/var/lib/waagent rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/log /var/lib/snapd/hostfs/var/log rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/snap /var/lib/snapd/hostfs/var/snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/tmp /var/lib/snapd/hostfs/var/tmp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 / /var/lib/snapd/hostfs/writable rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /var/lib/snapd/hostfs/writable/system-data/snap/core/1 ro,nodev,relatime master:60 - squashfs /dev/loop2 ro
+0:3 / /var/lib/snapd/hostfs/writable/system-data/snap/core18/1 ro,nodev,relatime master:61 - squashfs /dev/loop3 ro
+0:4 / /var/lib/snapd/hostfs/writable/system-data/snap/pc-kernel/1 ro,nodev,relatime master:62 - squashfs /dev/loop4 ro
+0:5 / /var/lib/snapd/hostfs/writable/system-data/snap/pc/1 ro,nodev,relatime master:63 - squashfs /dev/loop5 ro
+0:6 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:64 - squashfs /dev/loop6 ro
+0:7 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:65 - squashfs /dev/loop7 ro
+0:8 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-rsync/1 ro,nodev,relatime master:66 - squashfs /dev/loop8 ro
+1:1 /system-data/var/log /var/log rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/snap /var/snap rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/tmp /var/tmp rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-core-18-64/HOST.expected.txt snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-core-18-64/HOST.expected.txt
--- snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-core-18-64/HOST.expected.txt 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-core-18-64/HOST.expected.txt 2019-10-30 12:17:43.000000000 +0000
@@ -1,98 +1,98 @@
-1 0 0:0 / / ro,relatime shared:1 - squashfs /dev/loop0 ro
-2 1 1:0 / /boot/efi rw,relatime shared:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-3 1 1:0 /EFI/ubuntu /boot/grub rw,relatime shared:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-4 1 2:0 / /dev rw,nosuid,relatime shared:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
-5 4 2:1 / /dev/hugepages rw,relatime shared:4 - hugetlbfs hugetlbfs rw,pagesize=2M
-6 4 2:2 / /dev/mqueue rw,relatime shared:5 - mqueue mqueue rw
-7 4 2:3 / /dev/pts rw,nosuid,noexec,relatime shared:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
-8 4 2:4 / /dev/shm rw,nosuid,nodev shared:7 - tmpfs tmpfs rw
-9 1 1:1 /system-data/etc/apparmor.d/cache /etc/apparmor.d/cache rw,relatime shared:8 - ext4 /dev/sda3 rw,data=ordered
-10 1 1:1 /system-data/etc/cloud /etc/cloud rw,relatime shared:9 - ext4 /dev/sda3 rw,data=ordered
-11 1 1:1 /system-data/etc/dbus-1/system.d /etc/dbus-1/system.d rw,relatime shared:10 - ext4 /dev/sda3 rw,data=ordered
-12 1 1:1 /system-data/etc/default/swapfile /etc/default/swapfile rw,relatime shared:11 - ext4 /dev/sda3 rw,data=ordered
-13 1 2:5 /image.fstab /etc/fstab rw,nosuid,noexec,relatime shared:12 - tmpfs tmpfs rw,mode=755
-14 1 1:1 /system-data/root/test-etc/group /etc/group ro,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
-15 1 1:1 /system-data/root/test-etc/gshadow /etc/gshadow ro,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
-16 1 1:1 /system-data/etc/hosts /etc/hosts rw,relatime shared:14 - ext4 /dev/sda3 rw,data=ordered
-17 1 1:1 /system-data/etc/iproute2 /etc/iproute2 rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
-18 1 1:1 /system-data/etc/machine-id /etc/machine-id rw,relatime shared:16 - ext4 /dev/sda3 rw,data=ordered
-19 1 1:1 /system-data/etc/modprobe.d /etc/modprobe.d rw,relatime shared:17 - ext4 /dev/sda3 rw,data=ordered
-20 1 1:1 /system-data/etc/modules-load.d /etc/modules-load.d rw,relatime shared:18 - ext4 /dev/sda3 rw,data=ordered
-21 1 1:1 /system-data/etc/netplan /etc/netplan rw,relatime shared:19 - ext4 /dev/sda3 rw,data=ordered
-22 1 1:1 /system-data/etc/network/if-up.d /etc/network/if-up.d rw,relatime shared:20 - ext4 /dev/sda3 rw,data=ordered
-23 1 1:1 /system-data/root/test-etc/passwd /etc/passwd ro,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
-24 1 1:1 /system-data/root/test-etc/shadow /etc/shadow ro,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
-25 1 1:1 /system-data/etc/ssh /etc/ssh rw,relatime shared:21 - ext4 /dev/sda3 rw,data=ordered
-26 1 1:1 /system-data/etc/sudoers.d /etc/sudoers.d rw,relatime shared:22 - ext4 /dev/sda3 rw,data=ordered
-27 1 1:1 /system-data/etc/sysctl.d /etc/sysctl.d rw,relatime shared:23 - ext4 /dev/sda3 rw,data=ordered
-28 1 1:1 /system-data/etc/systemd /etc/systemd rw,relatime shared:24 - ext4 /dev/sda3 rw,data=ordered
-29 28 1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime shared:25 - ext4 /dev/sda3 rw,data=ordered
-30 1 1:1 /system-data/etc/udev/rules.d /etc/udev/rules.d rw,relatime shared:26 - ext4 /dev/sda3 rw,data=ordered
-31 1 1:1 /system-data/etc/writable /etc/writable rw,relatime shared:27 - ext4 /dev/sda3 rw,data=ordered
-32 1 1:1 /user-data /home rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
-33 1 0:1 /firmware /lib/firmware ro,relatime shared:28 - squashfs /dev/loop1 ro
-34 1 0:1 /modules /lib/modules ro,relatime shared:29 - squashfs /dev/loop1 ro
-35 1 2:6 / /media rw,relatime shared:30 - tmpfs tmpfs rw
-36 1 2:7 / /mnt rw,relatime shared:31 - tmpfs tmpfs rw
-37 1 2:8 / /proc rw,nosuid,nodev,noexec,relatime shared:32 - proc proc rw
-38 37 2:9 / /proc/sys/fs/binfmt_misc rw,relatime shared:33 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
-39 1 1:1 /system-data/root /root rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
-41 40 2:10 / /run rw,nosuid,noexec,relatime shared:34 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-40 1 2:5 / /run rw,nosuid,noexec,relatime shared:35 - tmpfs tmpfs rw,mode=755
-42 41 2:11 / /run/lock rw,nosuid,nodev,noexec,relatime shared:36 - tmpfs tmpfs rw,size=VARIABLE
-43 41 2:10 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-44 41 2:12 / /run/user/0 rw,nosuid,nodev,relatime shared:37 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-45 1 1:1 /system-data/snap /snap rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
-46 45 0:2 / /snap/core/1 ro,nodev,relatime shared:38 - squashfs /dev/loop2 ro
-47 45 0:0 / /snap/core18/1 ro,nodev,relatime shared:39 - squashfs /dev/loop0 ro
-48 45 0:1 / /snap/pc-kernel/1 ro,nodev,relatime shared:40 - squashfs /dev/loop1 ro
-49 45 0:3 / /snap/pc/1 ro,nodev,relatime shared:41 - squashfs /dev/loop3 ro
-50 45 0:4 / /snap/snapd/1 ro,nodev,relatime shared:42 - squashfs /dev/loop4 ro
-51 45 0:5 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime shared:43 - squashfs /dev/loop5 ro
-52 45 0:6 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime shared:44 - squashfs /dev/loop6 ro
-53 45 0:7 / /snap/test-snapd-rsync-core18/1 ro,nodev,relatime shared:45 - squashfs /dev/loop7 ro
-54 1 2:13 / /sys rw,nosuid,nodev,noexec,relatime shared:46 - sysfs sysfs rw
-55 54 2:14 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:47 - tmpfs tmpfs ro,mode=755
-56 55 2:15 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:48 - cgroup cgroup rw,blkio
-57 55 2:16 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:49 - cgroup cgroup rw,cpu,cpuacct
-58 55 2:17 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:50 - cgroup cgroup rw,cpuset
-59 55 2:18 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:51 - cgroup cgroup rw,devices
-60 55 2:19 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:52 - cgroup cgroup rw,freezer
-61 55 2:20 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:53 - cgroup cgroup rw,hugetlb
-62 55 2:21 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:54 - cgroup cgroup rw,memory
-63 55 2:22 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:55 - cgroup cgroup rw,net_cls,net_prio
-64 55 2:23 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:56 - cgroup cgroup rw,perf_event
-65 55 2:24 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:57 - cgroup cgroup rw,pids
-66 55 2:25 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime shared:58 - cgroup cgroup rw,rdma
-67 55 2:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:59 - cgroup cgroup rw,xattr,name=systemd
-68 55 2:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime shared:60 - cgroup2 cgroup rw,nsdelegate
-69 54 2:28 / /sys/fs/fuse/connections rw,relatime shared:61 - fusectl fusectl rw
-70 54 2:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime shared:62 - pstore pstore rw
-71 54 2:30 / /sys/kernel/config rw,relatime shared:63 - configfs configfs rw
-72 54 2:31 / /sys/kernel/debug rw,relatime shared:64 - debugfs debugfs rw
-73 54 2:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:65 - securityfs securityfs rw
-74 1 2:33 / /tmp rw,relatime shared:66 - tmpfs tmpfs rw
-75 1 0:4 /usr/lib/snapd /usr/lib/snapd ro,nodev,relatime shared:42 - squashfs /dev/loop4 ro
-76 1 1:1 /system-data/var/cache /var/cache rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
-77 1 1:1 /system-data/var/lib/cloud /var/lib/cloud rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
-78 1 1:1 /system-data/var/lib/console-conf /var/lib/console-conf rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
-79 1 1:1 /system-data/var/lib/dbus /var/lib/dbus rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
-80 1 1:1 /system-data/var/lib/dhcp /var/lib/dhcp rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
-81 1 1:1 /system-data/var/lib/extrausers /var/lib/extrausers rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
-82 1 1:1 /system-data/var/lib/misc /var/lib/misc rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
-83 1 1:1 /system-data/var/lib/private/systemd /var/lib/private/systemd rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
-84 1 1:1 /system-data/var/lib/snapd /var/lib/snapd rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
-85 1 2:34 / /var/lib/sudo rw,relatime shared:67 - tmpfs tmpfs rw,mode=700
-86 1 1:1 /system-data/var/lib/systemd /var/lib/systemd rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
-87 1 1:1 /system-data/var/log /var/log rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
-88 1 1:1 /system-data/var/snap /var/snap rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
-89 1 1:1 /system-data/var/tmp /var/tmp rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
-90 1 1:1 / /writable rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
-91 90 0:2 / /writable/system-data/snap/core/1 ro,nodev,relatime shared:38 - squashfs /dev/loop2 ro
-92 90 0:0 / /writable/system-data/snap/core18/1 ro,nodev,relatime shared:39 - squashfs /dev/loop0 ro
-93 90 0:1 / /writable/system-data/snap/pc-kernel/1 ro,nodev,relatime shared:40 - squashfs /dev/loop1 ro
-94 90 0:3 / /writable/system-data/snap/pc/1 ro,nodev,relatime shared:41 - squashfs /dev/loop3 ro
-95 90 0:4 / /writable/system-data/snap/snapd/1 ro,nodev,relatime shared:42 - squashfs /dev/loop4 ro
-96 90 0:5 / /writable/system-data/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime shared:43 - squashfs /dev/loop5 ro
-97 90 0:6 / /writable/system-data/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime shared:44 - squashfs /dev/loop6 ro
-98 90 0:7 / /writable/system-data/snap/test-snapd-rsync-core18/1 ro,nodev,relatime shared:45 - squashfs /dev/loop7 ro
+0:0 / / ro,relatime shared:1 - squashfs /dev/loop0 ro
+1:0 / /boot/efi rw,relatime shared:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:0 /EFI/ubuntu /boot/grub rw,relatime shared:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+2:0 / /dev rw,nosuid,relatime shared:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
+2:1 / /dev/hugepages rw,relatime shared:4 - hugetlbfs hugetlbfs rw,pagesize=2M
+2:2 / /dev/mqueue rw,relatime shared:5 - mqueue mqueue rw
+2:3 / /dev/pts rw,nosuid,noexec,relatime shared:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
+2:4 / /dev/shm rw,nosuid,nodev shared:7 - tmpfs tmpfs rw
+1:1 /system-data/etc/apparmor.d/cache /etc/apparmor.d/cache rw,relatime shared:8 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/cloud /etc/cloud rw,relatime shared:9 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/dbus-1/system.d /etc/dbus-1/system.d rw,relatime shared:10 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/swapfile /etc/default/swapfile rw,relatime shared:11 - ext4 /dev/sda3 rw,data=ordered
+2:5 /image.fstab /etc/fstab rw,nosuid,noexec,relatime shared:12 - tmpfs tmpfs rw,mode=755
+1:1 /system-data/root/test-etc/group /etc/group ro,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/gshadow /etc/gshadow ro,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/hosts /etc/hosts rw,relatime shared:14 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/iproute2 /etc/iproute2 rw,relatime shared:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/machine-id /etc/machine-id rw,relatime shared:16 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modprobe.d /etc/modprobe.d rw,relatime shared:17 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modules-load.d /etc/modules-load.d rw,relatime shared:18 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/netplan /etc/netplan rw,relatime shared:19 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/if-up.d /etc/network/if-up.d rw,relatime shared:20 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/passwd /etc/passwd ro,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/shadow /etc/shadow ro,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ssh /etc/ssh rw,relatime shared:21 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sudoers.d /etc/sudoers.d rw,relatime shared:22 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sysctl.d /etc/sysctl.d rw,relatime shared:23 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd /etc/systemd rw,relatime shared:24 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime shared:25 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/udev/rules.d /etc/udev/rules.d rw,relatime shared:26 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/writable /etc/writable rw,relatime shared:27 - ext4 /dev/sda3 rw,data=ordered
+1:1 /user-data /home rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
+0:1 /firmware /lib/firmware ro,relatime shared:28 - squashfs /dev/loop1 ro
+0:1 /modules /lib/modules ro,relatime shared:29 - squashfs /dev/loop1 ro
+2:6 / /media rw,relatime shared:30 - tmpfs tmpfs rw
+2:7 / /mnt rw,relatime shared:31 - tmpfs tmpfs rw
+2:8 / /proc rw,nosuid,nodev,noexec,relatime shared:32 - proc proc rw
+2:9 / /proc/sys/fs/binfmt_misc rw,relatime shared:33 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
+1:1 /system-data/root /root rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
+2:10 / /run rw,nosuid,noexec,relatime shared:34 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:5 / /run rw,nosuid,noexec,relatime shared:35 - tmpfs tmpfs rw,mode=755
+2:11 / /run/lock rw,nosuid,nodev,noexec,relatime shared:36 - tmpfs tmpfs rw,size=VARIABLE
+2:10 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:12 / /run/user/0 rw,nosuid,nodev,relatime shared:37 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+1:1 /system-data/snap /snap rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /snap/core/1 ro,nodev,relatime shared:38 - squashfs /dev/loop2 ro
+0:0 / /snap/core18/1 ro,nodev,relatime shared:39 - squashfs /dev/loop0 ro
+0:1 / /snap/pc-kernel/1 ro,nodev,relatime shared:40 - squashfs /dev/loop1 ro
+0:3 / /snap/pc/1 ro,nodev,relatime shared:41 - squashfs /dev/loop3 ro
+0:4 / /snap/snapd/1 ro,nodev,relatime shared:42 - squashfs /dev/loop4 ro
+0:5 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime shared:43 - squashfs /dev/loop5 ro
+0:6 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime shared:44 - squashfs /dev/loop6 ro
+0:7 / /snap/test-snapd-rsync-core18/1 ro,nodev,relatime shared:45 - squashfs /dev/loop7 ro
+2:13 / /sys rw,nosuid,nodev,noexec,relatime shared:46 - sysfs sysfs rw
+2:14 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:47 - tmpfs tmpfs ro,mode=755
+2:15 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:48 - cgroup cgroup rw,blkio
+2:16 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:49 - cgroup cgroup rw,cpu,cpuacct
+2:17 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:50 - cgroup cgroup rw,cpuset
+2:18 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:51 - cgroup cgroup rw,devices
+2:19 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:52 - cgroup cgroup rw,freezer
+2:20 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:53 - cgroup cgroup rw,hugetlb
+2:21 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:54 - cgroup cgroup rw,memory
+2:22 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:55 - cgroup cgroup rw,net_cls,net_prio
+2:23 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:56 - cgroup cgroup rw,perf_event
+2:24 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:57 - cgroup cgroup rw,pids
+2:25 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime shared:58 - cgroup cgroup rw,rdma
+2:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:59 - cgroup cgroup rw,xattr,name=systemd
+2:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime shared:60 - cgroup2 cgroup rw,nsdelegate
+2:28 / /sys/fs/fuse/connections rw,relatime shared:61 - fusectl fusectl rw
+2:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime shared:62 - pstore pstore rw
+2:30 / /sys/kernel/config rw,relatime shared:63 - configfs configfs rw
+2:31 / /sys/kernel/debug rw,relatime shared:64 - debugfs debugfs rw
+2:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:65 - securityfs securityfs rw
+2:33 / /tmp rw,relatime shared:66 - tmpfs tmpfs rw
+0:4 /usr/lib/snapd /usr/lib/snapd ro,nodev,relatime shared:42 - squashfs /dev/loop4 ro
+1:1 /system-data/var/cache /var/cache rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/cloud /var/lib/cloud rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/console-conf /var/lib/console-conf rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/dbus /var/lib/dbus rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/dhcp /var/lib/dhcp rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/extrausers /var/lib/extrausers rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/misc /var/lib/misc rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/private/systemd /var/lib/private/systemd rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/snapd /var/lib/snapd rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
+2:34 / /var/lib/sudo rw,relatime shared:67 - tmpfs tmpfs rw,mode=700
+1:1 /system-data/var/lib/systemd /var/lib/systemd rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/log /var/log rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/snap /var/snap rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/tmp /var/tmp rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 / /writable rw,relatime shared:13 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /writable/system-data/snap/core/1 ro,nodev,relatime shared:38 - squashfs /dev/loop2 ro
+0:0 / /writable/system-data/snap/core18/1 ro,nodev,relatime shared:39 - squashfs /dev/loop0 ro
+0:1 / /writable/system-data/snap/pc-kernel/1 ro,nodev,relatime shared:40 - squashfs /dev/loop1 ro
+0:3 / /writable/system-data/snap/pc/1 ro,nodev,relatime shared:41 - squashfs /dev/loop3 ro
+0:4 / /writable/system-data/snap/snapd/1 ro,nodev,relatime shared:42 - squashfs /dev/loop4 ro
+0:5 / /writable/system-data/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime shared:43 - squashfs /dev/loop5 ro
+0:6 / /writable/system-data/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime shared:44 - squashfs /dev/loop6 ro
+0:7 / /writable/system-data/snap/test-snapd-rsync-core18/1 ro,nodev,relatime shared:45 - squashfs /dev/loop7 ro
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-core-18-64/PER-SNAP-16.expected.txt snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-core-18-64/PER-SNAP-16.expected.txt
--- snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-core-18-64/PER-SNAP-16.expected.txt 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-core-18-64/PER-SNAP-16.expected.txt 2019-10-30 12:17:43.000000000 +0000
@@ -1,157 +1,160 @@
-1001 1000 0:2 / / ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
-1002 1001 2:0 / /dev rw,nosuid,relatime master:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
-1003 1002 2:1 / /dev/hugepages rw,relatime master:4 - hugetlbfs hugetlbfs rw,pagesize=2M
-1004 1002 2:2 / /dev/mqueue rw,relatime master:5 - mqueue mqueue rw
-1005 1002 2:35 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-1006 1002 2:3 / /dev/pts rw,nosuid,noexec,relatime master:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
-1007 1006 2:35 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-1008 1002 2:4 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
-1009 1001 0:0 /etc /etc ro,relatime master:1 - squashfs /dev/loop0 ro
-1010 1009 1:1 /system-data/etc/apparmor.d/cache /etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
-1011 1009 1:1 /system-data/etc/cloud /etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
-1012 1009 1:1 /system-data/etc/dbus-1/system.d /etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
-1013 1009 1:1 /system-data/etc/default/swapfile /etc/default/swapfile rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
-1014 1009 2:5 /image.fstab /etc/fstab rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,mode=755
-1015 1009 1:1 /system-data/root/test-etc/group /etc/group ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1016 1009 1:1 /system-data/root/test-etc/gshadow /etc/gshadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1017 1009 1:1 /system-data/etc/hosts /etc/hosts rw,relatime master:14 - ext4 /dev/sda3 rw,data=ordered
-1018 1009 1:1 /system-data/etc/iproute2 /etc/iproute2 rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1019 1009 1:1 /system-data/etc/machine-id /etc/machine-id rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
-1020 1009 1:1 /system-data/etc/modprobe.d /etc/modprobe.d rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
-1021 1009 1:1 /system-data/etc/modules-load.d /etc/modules-load.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
-1022 1009 1:1 /system-data/etc/netplan /etc/netplan rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
-1023 1009 1:1 /system-data/etc/network/if-up.d /etc/network/if-up.d rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
-1024 1009 0:2 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
-1025 1009 1:1 /system-data/root/test-etc/passwd /etc/passwd ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1026 1009 1:1 /system-data/root/test-etc/shadow /etc/shadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1027 1009 1:1 /system-data/etc/ssh /etc/ssh rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
-1028 1009 0:2 /etc/ssl /etc/ssl ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
-1029 1009 1:1 /system-data/etc/sudoers.d /etc/sudoers.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
-1030 1009 1:1 /system-data/etc/sysctl.d /etc/sysctl.d rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
-1031 1009 1:1 /system-data/etc/systemd /etc/systemd rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
-1032 1031 1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
-1033 1009 1:1 /system-data/etc/udev/rules.d /etc/udev/rules.d rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
-1034 1009 1:1 /system-data/etc/writable /etc/writable rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
-1035 1001 1:1 /user-data /home rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1036 1001 0:1 /firmware /lib/firmware ro,relatime master:28 - squashfs /dev/loop1 ro
-1037 1001 0:1 /modules /lib/modules ro,relatime master:29 - squashfs /dev/loop1 ro
-1038 1001 2:6 / /media rw,relatime shared:30 - tmpfs tmpfs rw
-1039 1001 2:7 / /mnt rw,relatime master:31 - tmpfs tmpfs rw
-1040 1001 2:8 / /proc rw,nosuid,nodev,noexec,relatime master:32 - proc proc rw
-1041 1040 2:9 / /proc/sys/fs/binfmt_misc rw,relatime master:33 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
-1042 1001 1:1 /system-data/root /root rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1043 1001 2:10 / /run rw,nosuid,noexec,relatime master:34 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1044 1043 2:11 / /run/lock rw,nosuid,nodev,noexec,relatime master:36 - tmpfs tmpfs rw,size=VARIABLE
-1045 1043 2:10 /netns /run/netns rw,nosuid,noexec,relatime shared:34 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1046 1043 2:10 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1047 1043 2:12 / /run/user/0 rw,nosuid,nodev,relatime master:37 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-1048 1001 1:1 /system-data/snap /snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1049 1048 0:2 / /snap/core/1 ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
-1050 1048 0:0 / /snap/core18/1 ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
-1051 1048 0:1 / /snap/pc-kernel/1 ro,nodev,relatime master:40 - squashfs /dev/loop1 ro
-1052 1048 0:3 / /snap/pc/1 ro,nodev,relatime master:41 - squashfs /dev/loop3 ro
-1053 1048 0:4 / /snap/snapd/1 ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
-1054 1048 0:5 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:43 - squashfs /dev/loop5 ro
-1055 1048 0:6 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:44 - squashfs /dev/loop6 ro
-1056 1048 0:7 / /snap/test-snapd-rsync-core18/1 ro,nodev,relatime master:45 - squashfs /dev/loop7 ro
-1057 1001 2:13 / /sys rw,nosuid,nodev,noexec,relatime master:46 - sysfs sysfs rw
-1058 1057 2:14 / /sys/fs/cgroup ro,nosuid,nodev,noexec master:47 - tmpfs tmpfs ro,mode=755
-1059 1058 2:15 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:48 - cgroup cgroup rw,blkio
-1060 1058 2:16 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:49 - cgroup cgroup rw,cpu,cpuacct
-1061 1058 2:17 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:50 - cgroup cgroup rw,cpuset
-1062 1058 2:18 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:51 - cgroup cgroup rw,devices
-1063 1058 2:19 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:52 - cgroup cgroup rw,freezer
-1064 1058 2:20 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:53 - cgroup cgroup rw,hugetlb
-1065 1058 2:21 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:54 - cgroup cgroup rw,memory
-1066 1058 2:22 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:55 - cgroup cgroup rw,net_cls,net_prio
-1067 1058 2:23 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:56 - cgroup cgroup rw,perf_event
-1068 1058 2:24 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:57 - cgroup cgroup rw,pids
-1069 1058 2:25 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime master:58 - cgroup cgroup rw,rdma
-1070 1058 2:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:59 - cgroup cgroup rw,xattr,name=systemd
-1071 1058 2:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime master:60 - cgroup2 cgroup rw,nsdelegate
-1072 1057 2:28 / /sys/fs/fuse/connections rw,relatime master:61 - fusectl fusectl rw
-1073 1057 2:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:62 - pstore pstore rw
-1074 1057 2:30 / /sys/kernel/config rw,relatime master:63 - configfs configfs rw
-1075 1057 2:31 / /sys/kernel/debug rw,relatime master:64 - debugfs debugfs rw
-1076 1057 2:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:65 - securityfs securityfs rw
-1077 1001 2:33 / /tmp rw,relatime master:66 - tmpfs tmpfs rw
-1078 1077 2:33 /snap.test-snapd-mountinfo-core16/tmp /tmp rw,relatime - tmpfs tmpfs rw
-1079 1001 0:4 /usr/lib/snapd /usr/lib/snapd ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
-1080 1001 0:0 /usr/src /usr/src ro,relatime master:1 - squashfs /dev/loop0 ro
-1081 1001 1:1 /system-data/var/lib/extrausers /var/lib/extrausers rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1082 1001 1:1 /system-data/var/lib/snapd /var/lib/snapd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1084 1083 0:0 / /var/lib/snapd/hostfs ro,relatime master:1 - squashfs /dev/loop0 ro
-1083 1082 1:1 /system-data/var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda3 rw,data=ordered
-1085 1084 1:0 / /var/lib/snapd/hostfs/boot/efi rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-1086 1084 1:0 /EFI/ubuntu /var/lib/snapd/hostfs/boot/grub rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-1087 1084 1:1 /system-data/etc/apparmor.d/cache /var/lib/snapd/hostfs/etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
-1088 1084 1:1 /system-data/etc/cloud /var/lib/snapd/hostfs/etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
-1089 1084 1:1 /system-data/etc/dbus-1/system.d /var/lib/snapd/hostfs/etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
-1090 1084 1:1 /system-data/etc/default/swapfile /var/lib/snapd/hostfs/etc/default/swapfile rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
-1091 1084 2:5 /image.fstab /var/lib/snapd/hostfs/etc/fstab rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,mode=755
-1092 1084 1:1 /system-data/root/test-etc/group /var/lib/snapd/hostfs/etc/group ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1093 1084 1:1 /system-data/root/test-etc/gshadow /var/lib/snapd/hostfs/etc/gshadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1094 1084 1:1 /system-data/etc/hosts /var/lib/snapd/hostfs/etc/hosts rw,relatime master:14 - ext4 /dev/sda3 rw,data=ordered
-1095 1084 1:1 /system-data/etc/iproute2 /var/lib/snapd/hostfs/etc/iproute2 rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1096 1084 1:1 /system-data/etc/machine-id /var/lib/snapd/hostfs/etc/machine-id rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
-1097 1084 1:1 /system-data/etc/modprobe.d /var/lib/snapd/hostfs/etc/modprobe.d rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
-1098 1084 1:1 /system-data/etc/modules-load.d /var/lib/snapd/hostfs/etc/modules-load.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
-1099 1084 1:1 /system-data/etc/netplan /var/lib/snapd/hostfs/etc/netplan rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
-1100 1084 1:1 /system-data/etc/network/if-up.d /var/lib/snapd/hostfs/etc/network/if-up.d rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
-1101 1084 1:1 /system-data/root/test-etc/passwd /var/lib/snapd/hostfs/etc/passwd ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1102 1084 1:1 /system-data/root/test-etc/shadow /var/lib/snapd/hostfs/etc/shadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1103 1084 1:1 /system-data/etc/ssh /var/lib/snapd/hostfs/etc/ssh rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
-1104 1084 1:1 /system-data/etc/sudoers.d /var/lib/snapd/hostfs/etc/sudoers.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
-1105 1084 1:1 /system-data/etc/sysctl.d /var/lib/snapd/hostfs/etc/sysctl.d rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
-1106 1084 1:1 /system-data/etc/systemd /var/lib/snapd/hostfs/etc/systemd rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
-1107 1106 1:1 /system-data/etc/systemd/system /var/lib/snapd/hostfs/etc/systemd/system rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
-1108 1084 1:1 /system-data/etc/udev/rules.d /var/lib/snapd/hostfs/etc/udev/rules.d rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
-1109 1084 1:1 /system-data/etc/writable /var/lib/snapd/hostfs/etc/writable rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
-1110 1084 1:1 /user-data /var/lib/snapd/hostfs/home rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1111 1084 0:1 /firmware /var/lib/snapd/hostfs/lib/firmware ro,relatime master:28 - squashfs /dev/loop1 ro
-1112 1084 0:1 /modules /var/lib/snapd/hostfs/lib/modules ro,relatime master:29 - squashfs /dev/loop1 ro
-1113 1084 2:6 / /var/lib/snapd/hostfs/media rw,relatime master:30 - tmpfs tmpfs rw
-1114 1084 2:7 / /var/lib/snapd/hostfs/mnt rw,relatime master:31 - tmpfs tmpfs rw
-1115 1084 1:1 /system-data/root /var/lib/snapd/hostfs/root rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1116 1084 2:5 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:35 - tmpfs tmpfs rw,mode=755
-1117 1116 2:10 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:34 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1118 1117 2:11 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:36 - tmpfs tmpfs rw,size=VARIABLE
-1119 1117 2:10 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1120 1117 2:12 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:37 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-1121 1084 1:1 /system-data/snap /var/lib/snapd/hostfs/snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1122 1121 0:2 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
-1123 1121 0:0 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
-1124 1121 0:1 / /var/lib/snapd/hostfs/snap/pc-kernel/1 ro,nodev,relatime master:40 - squashfs /dev/loop1 ro
-1125 1121 0:3 / /var/lib/snapd/hostfs/snap/pc/1 ro,nodev,relatime master:41 - squashfs /dev/loop3 ro
-1126 1121 0:4 / /var/lib/snapd/hostfs/snap/snapd/1 ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
-1127 1121 0:5 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:43 - squashfs /dev/loop5 ro
-1128 1121 0:6 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:44 - squashfs /dev/loop6 ro
-1129 1121 0:7 / /var/lib/snapd/hostfs/snap/test-snapd-rsync-core18/1 ro,nodev,relatime master:45 - squashfs /dev/loop7 ro
-1130 1084 2:33 / /var/lib/snapd/hostfs/tmp rw,relatime master:66 - tmpfs tmpfs rw
-1131 1084 0:4 /usr/lib/snapd /var/lib/snapd/hostfs/usr/lib/snapd ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
-1132 1084 1:1 /system-data/var/cache /var/lib/snapd/hostfs/var/cache rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1133 1084 1:1 /system-data/var/lib/cloud /var/lib/snapd/hostfs/var/lib/cloud rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1134 1084 1:1 /system-data/var/lib/console-conf /var/lib/snapd/hostfs/var/lib/console-conf rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1135 1084 1:1 /system-data/var/lib/dbus /var/lib/snapd/hostfs/var/lib/dbus rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1136 1084 1:1 /system-data/var/lib/dhcp /var/lib/snapd/hostfs/var/lib/dhcp rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1137 1084 1:1 /system-data/var/lib/extrausers /var/lib/snapd/hostfs/var/lib/extrausers rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1138 1084 1:1 /system-data/var/lib/misc /var/lib/snapd/hostfs/var/lib/misc rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1139 1084 1:1 /system-data/var/lib/private/systemd /var/lib/snapd/hostfs/var/lib/private/systemd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1140 1084 1:1 /system-data/var/lib/snapd /var/lib/snapd/hostfs/var/lib/snapd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1141 1084 2:34 / /var/lib/snapd/hostfs/var/lib/sudo rw,relatime master:67 - tmpfs tmpfs rw,mode=700
-1142 1084 1:1 /system-data/var/lib/systemd /var/lib/snapd/hostfs/var/lib/systemd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1143 1084 1:1 /system-data/var/log /var/lib/snapd/hostfs/var/log rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1144 1084 1:1 /system-data/var/snap /var/lib/snapd/hostfs/var/snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1145 1084 1:1 /system-data/var/tmp /var/lib/snapd/hostfs/var/tmp rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1146 1084 1:1 / /var/lib/snapd/hostfs/writable rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1147 1146 0:2 / /var/lib/snapd/hostfs/writable/system-data/snap/core/1 ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
-1148 1146 0:0 / /var/lib/snapd/hostfs/writable/system-data/snap/core18/1 ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
-1149 1146 0:1 / /var/lib/snapd/hostfs/writable/system-data/snap/pc-kernel/1 ro,nodev,relatime master:40 - squashfs /dev/loop1 ro
-1150 1146 0:3 / /var/lib/snapd/hostfs/writable/system-data/snap/pc/1 ro,nodev,relatime master:41 - squashfs /dev/loop3 ro
-1151 1146 0:4 / /var/lib/snapd/hostfs/writable/system-data/snap/snapd/1 ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
-1152 1146 0:5 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:43 - squashfs /dev/loop5 ro
-1153 1146 0:6 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:44 - squashfs /dev/loop6 ro
-1154 1146 0:7 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-rsync-core18/1 ro,nodev,relatime master:45 - squashfs /dev/loop7 ro
-1155 1001 1:1 /system-data/var/log /var/log rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1156 1001 1:1 /system-data/var/snap /var/snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1157 1001 1:1 /system-data/var/tmp /var/tmp rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:2 / / ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
+2:0 / /dev rw,nosuid,relatime master:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
+2:1 / /dev/hugepages rw,relatime master:4 - hugetlbfs hugetlbfs rw,pagesize=2M
+2:2 / /dev/mqueue rw,relatime master:5 - mqueue mqueue rw
+2:35 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+2:3 / /dev/pts rw,nosuid,noexec,relatime master:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
+2:35 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+2:4 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
+0:0 /etc /etc ro,relatime master:1 - squashfs /dev/loop0 ro
+1:1 /system-data/etc/apparmor.d/cache /etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/cloud /etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/dbus-1/system.d /etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/swapfile /etc/default/swapfile rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
+2:5 /image.fstab /etc/fstab rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,mode=755
+1:1 /system-data/root/test-etc/group /etc/group ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/gshadow /etc/gshadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/hosts /etc/hosts rw,relatime master:14 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/iproute2 /etc/iproute2 rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/machine-id /etc/machine-id rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modprobe.d /etc/modprobe.d rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modules-load.d /etc/modules-load.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/netplan /etc/netplan rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/if-up.d /etc/network/if-up.d rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
+0:2 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
+1:1 /system-data/root/test-etc/passwd /etc/passwd ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/shadow /etc/shadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ssh /etc/ssh rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
+0:2 /etc/ssl /etc/ssl ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
+1:1 /system-data/etc/sudoers.d /etc/sudoers.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sysctl.d /etc/sysctl.d rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd /etc/systemd rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/udev/rules.d /etc/udev/rules.d rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/writable /etc/writable rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
+1:1 /user-data /home rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:1 /firmware /lib/firmware ro,relatime master:28 - squashfs /dev/loop1 ro
+0:1 /modules /lib/modules ro,relatime master:29 - squashfs /dev/loop1 ro
+2:6 / /media rw,relatime shared:30 - tmpfs tmpfs rw
+2:7 / /mnt rw,relatime master:31 - tmpfs tmpfs rw
+2:8 / /proc rw,nosuid,nodev,noexec,relatime master:32 - proc proc rw
+2:9 / /proc/sys/fs/binfmt_misc rw,relatime master:33 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
+1:1 /system-data/root /root rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+2:10 / /run rw,nosuid,noexec,relatime master:34 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:11 / /run/lock rw,nosuid,nodev,noexec,relatime master:36 - tmpfs tmpfs rw,size=VARIABLE
+2:10 /netns /run/netns rw,nosuid,noexec,relatime shared:34 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:10 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:12 / /run/user/0 rw,nosuid,nodev,relatime master:37 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+1:1 /system-data/snap /snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /snap/core/1 ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
+0:0 / /snap/core18/1 ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
+0:1 / /snap/pc-kernel/1 ro,nodev,relatime master:40 - squashfs /dev/loop1 ro
+0:3 / /snap/pc/1 ro,nodev,relatime master:41 - squashfs /dev/loop3 ro
+0:4 / /snap/snapd/1 ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
+0:5 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:43 - squashfs /dev/loop5 ro
+0:6 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:44 - squashfs /dev/loop6 ro
+0:7 / /snap/test-snapd-rsync-core18/1 ro,nodev,relatime master:45 - squashfs /dev/loop7 ro
+2:13 / /sys rw,nosuid,nodev,noexec,relatime master:46 - sysfs sysfs rw
+2:14 / /sys/fs/cgroup ro,nosuid,nodev,noexec master:47 - tmpfs tmpfs ro,mode=755
+2:15 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:48 - cgroup cgroup rw,blkio
+2:16 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:49 - cgroup cgroup rw,cpu,cpuacct
+2:17 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:50 - cgroup cgroup rw,cpuset
+2:18 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:51 - cgroup cgroup rw,devices
+2:19 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:52 - cgroup cgroup rw,freezer
+2:20 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:53 - cgroup cgroup rw,hugetlb
+2:21 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:54 - cgroup cgroup rw,memory
+2:22 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:55 - cgroup cgroup rw,net_cls,net_prio
+2:23 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:56 - cgroup cgroup rw,perf_event
+2:24 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:57 - cgroup cgroup rw,pids
+2:25 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime master:58 - cgroup cgroup rw,rdma
+2:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:59 - cgroup cgroup rw,xattr,name=systemd
+2:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime master:60 - cgroup2 cgroup rw,nsdelegate
+2:28 / /sys/fs/fuse/connections rw,relatime master:61 - fusectl fusectl rw
+2:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:62 - pstore pstore rw
+2:30 / /sys/kernel/config rw,relatime master:63 - configfs configfs rw
+2:31 / /sys/kernel/debug rw,relatime master:64 - debugfs debugfs rw
+2:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:65 - securityfs securityfs rw
+2:33 / /tmp rw,relatime master:66 - tmpfs tmpfs rw
+2:33 /snap.test-snapd-mountinfo-core16/tmp /tmp rw,relatime - tmpfs tmpfs rw
+0:4 /usr/lib/snapd /usr/lib/snapd ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
+2:36 / /usr/share/gdb rw,relatime - tmpfs tmpfs rw,mode=755
+0:2 /usr/share/gdb/auto-load /usr/share/gdb/auto-load ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
+2:37 / /usr/share/gdb/test rw,relatime - tmpfs tmpfs rw
+0:0 /usr/src /usr/src ro,relatime master:1 - squashfs /dev/loop0 ro
+1:1 /system-data/var/lib/extrausers /var/lib/extrausers rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/snapd /var/lib/snapd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:0 / /var/lib/snapd/hostfs ro,relatime master:1 - squashfs /dev/loop0 ro
+1:1 /system-data/var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda3 rw,data=ordered
+1:0 / /var/lib/snapd/hostfs/boot/efi rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:0 /EFI/ubuntu /var/lib/snapd/hostfs/boot/grub rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:1 /system-data/etc/apparmor.d/cache /var/lib/snapd/hostfs/etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/cloud /var/lib/snapd/hostfs/etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/dbus-1/system.d /var/lib/snapd/hostfs/etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/swapfile /var/lib/snapd/hostfs/etc/default/swapfile rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
+2:5 /image.fstab /var/lib/snapd/hostfs/etc/fstab rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,mode=755
+1:1 /system-data/root/test-etc/group /var/lib/snapd/hostfs/etc/group ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/gshadow /var/lib/snapd/hostfs/etc/gshadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/hosts /var/lib/snapd/hostfs/etc/hosts rw,relatime master:14 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/iproute2 /var/lib/snapd/hostfs/etc/iproute2 rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/machine-id /var/lib/snapd/hostfs/etc/machine-id rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modprobe.d /var/lib/snapd/hostfs/etc/modprobe.d rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modules-load.d /var/lib/snapd/hostfs/etc/modules-load.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/netplan /var/lib/snapd/hostfs/etc/netplan rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/if-up.d /var/lib/snapd/hostfs/etc/network/if-up.d rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/passwd /var/lib/snapd/hostfs/etc/passwd ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/shadow /var/lib/snapd/hostfs/etc/shadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ssh /var/lib/snapd/hostfs/etc/ssh rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sudoers.d /var/lib/snapd/hostfs/etc/sudoers.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sysctl.d /var/lib/snapd/hostfs/etc/sysctl.d rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd /var/lib/snapd/hostfs/etc/systemd rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /var/lib/snapd/hostfs/etc/systemd/system rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/udev/rules.d /var/lib/snapd/hostfs/etc/udev/rules.d rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/writable /var/lib/snapd/hostfs/etc/writable rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
+1:1 /user-data /var/lib/snapd/hostfs/home rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:1 /firmware /var/lib/snapd/hostfs/lib/firmware ro,relatime master:28 - squashfs /dev/loop1 ro
+0:1 /modules /var/lib/snapd/hostfs/lib/modules ro,relatime master:29 - squashfs /dev/loop1 ro
+2:6 / /var/lib/snapd/hostfs/media rw,relatime master:30 - tmpfs tmpfs rw
+2:7 / /var/lib/snapd/hostfs/mnt rw,relatime master:31 - tmpfs tmpfs rw
+1:1 /system-data/root /var/lib/snapd/hostfs/root rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+2:5 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:35 - tmpfs tmpfs rw,mode=755
+2:10 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:34 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:11 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:36 - tmpfs tmpfs rw,size=VARIABLE
+2:10 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:12 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:37 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+1:1 /system-data/snap /var/lib/snapd/hostfs/snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
+0:0 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
+0:1 / /var/lib/snapd/hostfs/snap/pc-kernel/1 ro,nodev,relatime master:40 - squashfs /dev/loop1 ro
+0:3 / /var/lib/snapd/hostfs/snap/pc/1 ro,nodev,relatime master:41 - squashfs /dev/loop3 ro
+0:4 / /var/lib/snapd/hostfs/snap/snapd/1 ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
+0:5 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:43 - squashfs /dev/loop5 ro
+0:6 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:44 - squashfs /dev/loop6 ro
+0:7 / /var/lib/snapd/hostfs/snap/test-snapd-rsync-core18/1 ro,nodev,relatime master:45 - squashfs /dev/loop7 ro
+2:33 / /var/lib/snapd/hostfs/tmp rw,relatime master:66 - tmpfs tmpfs rw
+0:4 /usr/lib/snapd /var/lib/snapd/hostfs/usr/lib/snapd ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
+1:1 /system-data/var/cache /var/lib/snapd/hostfs/var/cache rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/cloud /var/lib/snapd/hostfs/var/lib/cloud rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/console-conf /var/lib/snapd/hostfs/var/lib/console-conf rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/dbus /var/lib/snapd/hostfs/var/lib/dbus rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/dhcp /var/lib/snapd/hostfs/var/lib/dhcp rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/extrausers /var/lib/snapd/hostfs/var/lib/extrausers rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/misc /var/lib/snapd/hostfs/var/lib/misc rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/private/systemd /var/lib/snapd/hostfs/var/lib/private/systemd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/snapd /var/lib/snapd/hostfs/var/lib/snapd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+2:34 / /var/lib/snapd/hostfs/var/lib/sudo rw,relatime master:67 - tmpfs tmpfs rw,mode=700
+1:1 /system-data/var/lib/systemd /var/lib/snapd/hostfs/var/lib/systemd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/log /var/lib/snapd/hostfs/var/log rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/snap /var/lib/snapd/hostfs/var/snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/tmp /var/lib/snapd/hostfs/var/tmp rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 / /var/lib/snapd/hostfs/writable rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /var/lib/snapd/hostfs/writable/system-data/snap/core/1 ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
+0:0 / /var/lib/snapd/hostfs/writable/system-data/snap/core18/1 ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
+0:1 / /var/lib/snapd/hostfs/writable/system-data/snap/pc-kernel/1 ro,nodev,relatime master:40 - squashfs /dev/loop1 ro
+0:3 / /var/lib/snapd/hostfs/writable/system-data/snap/pc/1 ro,nodev,relatime master:41 - squashfs /dev/loop3 ro
+0:4 / /var/lib/snapd/hostfs/writable/system-data/snap/snapd/1 ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
+0:5 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:43 - squashfs /dev/loop5 ro
+0:6 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:44 - squashfs /dev/loop6 ro
+0:7 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-rsync-core18/1 ro,nodev,relatime master:45 - squashfs /dev/loop7 ro
+1:1 /system-data/var/log /var/log rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/snap /var/snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/tmp /var/tmp rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-core-18-64/PER-SNAP-18.expected.txt snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-core-18-64/PER-SNAP-18.expected.txt
--- snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-core-18-64/PER-SNAP-18.expected.txt 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-core-18-64/PER-SNAP-18.expected.txt 2019-10-30 12:17:43.000000000 +0000
@@ -1,157 +1,160 @@
-1001 1000 0:0 / / ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
-1002 1001 2:0 / /dev rw,nosuid,relatime master:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
-1003 1002 2:1 / /dev/hugepages rw,relatime master:4 - hugetlbfs hugetlbfs rw,pagesize=2M
-1004 1002 2:2 / /dev/mqueue rw,relatime master:5 - mqueue mqueue rw
-1005 1002 2:35 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-1006 1002 2:3 / /dev/pts rw,nosuid,noexec,relatime master:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
-1007 1006 2:35 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-1008 1002 2:4 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
-1009 1001 0:0 /etc /etc ro,relatime master:1 - squashfs /dev/loop0 ro
-1010 1009 1:1 /system-data/etc/apparmor.d/cache /etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
-1011 1009 1:1 /system-data/etc/cloud /etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
-1012 1009 1:1 /system-data/etc/dbus-1/system.d /etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
-1013 1009 1:1 /system-data/etc/default/swapfile /etc/default/swapfile rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
-1014 1009 2:5 /image.fstab /etc/fstab rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,mode=755
-1015 1009 1:1 /system-data/root/test-etc/group /etc/group ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1016 1009 1:1 /system-data/root/test-etc/gshadow /etc/gshadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1017 1009 1:1 /system-data/etc/hosts /etc/hosts rw,relatime master:14 - ext4 /dev/sda3 rw,data=ordered
-1018 1009 1:1 /system-data/etc/iproute2 /etc/iproute2 rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1019 1009 1:1 /system-data/etc/machine-id /etc/machine-id rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
-1020 1009 1:1 /system-data/etc/modprobe.d /etc/modprobe.d rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
-1021 1009 1:1 /system-data/etc/modules-load.d /etc/modules-load.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
-1022 1009 1:1 /system-data/etc/netplan /etc/netplan rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
-1023 1009 1:1 /system-data/etc/network/if-up.d /etc/network/if-up.d rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
-1024 1009 0:0 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
-1025 1009 1:1 /system-data/root/test-etc/passwd /etc/passwd ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1026 1009 1:1 /system-data/root/test-etc/shadow /etc/shadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1027 1009 1:1 /system-data/etc/ssh /etc/ssh rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
-1028 1009 0:0 /etc/ssl /etc/ssl ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
-1029 1009 1:1 /system-data/etc/sudoers.d /etc/sudoers.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
-1030 1009 1:1 /system-data/etc/sysctl.d /etc/sysctl.d rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
-1031 1009 1:1 /system-data/etc/systemd /etc/systemd rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
-1032 1031 1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
-1033 1009 1:1 /system-data/etc/udev/rules.d /etc/udev/rules.d rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
-1034 1009 1:1 /system-data/etc/writable /etc/writable rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
-1035 1001 1:1 /user-data /home rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1036 1001 0:1 /firmware /lib/firmware ro,relatime master:28 - squashfs /dev/loop1 ro
-1037 1001 0:1 /modules /lib/modules ro,relatime master:29 - squashfs /dev/loop1 ro
-1038 1001 2:6 / /media rw,relatime shared:30 - tmpfs tmpfs rw
-1039 1001 2:7 / /mnt rw,relatime master:31 - tmpfs tmpfs rw
-1040 1001 2:8 / /proc rw,nosuid,nodev,noexec,relatime master:32 - proc proc rw
-1041 1040 2:9 / /proc/sys/fs/binfmt_misc rw,relatime master:33 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
-1042 1001 1:1 /system-data/root /root rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1043 1001 2:10 / /run rw,nosuid,noexec,relatime master:34 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1044 1043 2:11 / /run/lock rw,nosuid,nodev,noexec,relatime master:36 - tmpfs tmpfs rw,size=VARIABLE
-1045 1043 2:10 /netns /run/netns rw,nosuid,noexec,relatime shared:34 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1046 1043 2:10 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1047 1043 2:12 / /run/user/0 rw,nosuid,nodev,relatime master:37 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-1048 1001 1:1 /system-data/snap /snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1049 1048 0:2 / /snap/core/1 ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
-1050 1048 0:0 / /snap/core18/1 ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
-1051 1048 0:1 / /snap/pc-kernel/1 ro,nodev,relatime master:40 - squashfs /dev/loop1 ro
-1052 1048 0:3 / /snap/pc/1 ro,nodev,relatime master:41 - squashfs /dev/loop3 ro
-1053 1048 0:4 / /snap/snapd/1 ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
-1054 1048 0:5 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:43 - squashfs /dev/loop5 ro
-1055 1048 0:6 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:44 - squashfs /dev/loop6 ro
-1056 1048 0:7 / /snap/test-snapd-rsync-core18/1 ro,nodev,relatime master:45 - squashfs /dev/loop7 ro
-1057 1001 2:13 / /sys rw,nosuid,nodev,noexec,relatime master:46 - sysfs sysfs rw
-1058 1057 2:14 / /sys/fs/cgroup ro,nosuid,nodev,noexec master:47 - tmpfs tmpfs ro,mode=755
-1059 1058 2:15 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:48 - cgroup cgroup rw,blkio
-1060 1058 2:16 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:49 - cgroup cgroup rw,cpu,cpuacct
-1061 1058 2:17 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:50 - cgroup cgroup rw,cpuset
-1062 1058 2:18 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:51 - cgroup cgroup rw,devices
-1063 1058 2:19 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:52 - cgroup cgroup rw,freezer
-1064 1058 2:20 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:53 - cgroup cgroup rw,hugetlb
-1065 1058 2:21 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:54 - cgroup cgroup rw,memory
-1066 1058 2:22 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:55 - cgroup cgroup rw,net_cls,net_prio
-1067 1058 2:23 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:56 - cgroup cgroup rw,perf_event
-1068 1058 2:24 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:57 - cgroup cgroup rw,pids
-1069 1058 2:25 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime master:58 - cgroup cgroup rw,rdma
-1070 1058 2:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:59 - cgroup cgroup rw,xattr,name=systemd
-1071 1058 2:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime master:60 - cgroup2 cgroup rw,nsdelegate
-1072 1057 2:28 / /sys/fs/fuse/connections rw,relatime master:61 - fusectl fusectl rw
-1073 1057 2:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:62 - pstore pstore rw
-1074 1057 2:30 / /sys/kernel/config rw,relatime master:63 - configfs configfs rw
-1075 1057 2:31 / /sys/kernel/debug rw,relatime master:64 - debugfs debugfs rw
-1076 1057 2:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:65 - securityfs securityfs rw
-1077 1001 2:33 / /tmp rw,relatime master:66 - tmpfs tmpfs rw
-1078 1077 2:33 /snap.test-snapd-mountinfo-core18/tmp /tmp rw,relatime - tmpfs tmpfs rw
-1079 1001 0:4 /usr/lib/snapd /usr/lib/snapd ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
-1080 1001 0:0 /usr/src /usr/src ro,relatime master:1 - squashfs /dev/loop0 ro
-1081 1001 1:1 /system-data/var/lib/extrausers /var/lib/extrausers rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1082 1001 1:1 /system-data/var/lib/snapd /var/lib/snapd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1084 1083 0:0 / /var/lib/snapd/hostfs ro,relatime master:1 - squashfs /dev/loop0 ro
-1083 1082 1:1 /system-data/var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda3 rw,data=ordered
-1085 1084 1:0 / /var/lib/snapd/hostfs/boot/efi rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-1086 1084 1:0 /EFI/ubuntu /var/lib/snapd/hostfs/boot/grub rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-1087 1084 1:1 /system-data/etc/apparmor.d/cache /var/lib/snapd/hostfs/etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
-1088 1084 1:1 /system-data/etc/cloud /var/lib/snapd/hostfs/etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
-1089 1084 1:1 /system-data/etc/dbus-1/system.d /var/lib/snapd/hostfs/etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
-1090 1084 1:1 /system-data/etc/default/swapfile /var/lib/snapd/hostfs/etc/default/swapfile rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
-1091 1084 2:5 /image.fstab /var/lib/snapd/hostfs/etc/fstab rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,mode=755
-1092 1084 1:1 /system-data/root/test-etc/group /var/lib/snapd/hostfs/etc/group ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1093 1084 1:1 /system-data/root/test-etc/gshadow /var/lib/snapd/hostfs/etc/gshadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1094 1084 1:1 /system-data/etc/hosts /var/lib/snapd/hostfs/etc/hosts rw,relatime master:14 - ext4 /dev/sda3 rw,data=ordered
-1095 1084 1:1 /system-data/etc/iproute2 /var/lib/snapd/hostfs/etc/iproute2 rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-1096 1084 1:1 /system-data/etc/machine-id /var/lib/snapd/hostfs/etc/machine-id rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
-1097 1084 1:1 /system-data/etc/modprobe.d /var/lib/snapd/hostfs/etc/modprobe.d rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
-1098 1084 1:1 /system-data/etc/modules-load.d /var/lib/snapd/hostfs/etc/modules-load.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
-1099 1084 1:1 /system-data/etc/netplan /var/lib/snapd/hostfs/etc/netplan rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
-1100 1084 1:1 /system-data/etc/network/if-up.d /var/lib/snapd/hostfs/etc/network/if-up.d rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
-1101 1084 1:1 /system-data/root/test-etc/passwd /var/lib/snapd/hostfs/etc/passwd ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1102 1084 1:1 /system-data/root/test-etc/shadow /var/lib/snapd/hostfs/etc/shadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1103 1084 1:1 /system-data/etc/ssh /var/lib/snapd/hostfs/etc/ssh rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
-1104 1084 1:1 /system-data/etc/sudoers.d /var/lib/snapd/hostfs/etc/sudoers.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
-1105 1084 1:1 /system-data/etc/sysctl.d /var/lib/snapd/hostfs/etc/sysctl.d rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
-1106 1084 1:1 /system-data/etc/systemd /var/lib/snapd/hostfs/etc/systemd rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
-1107 1106 1:1 /system-data/etc/systemd/system /var/lib/snapd/hostfs/etc/systemd/system rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
-1108 1084 1:1 /system-data/etc/udev/rules.d /var/lib/snapd/hostfs/etc/udev/rules.d rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
-1109 1084 1:1 /system-data/etc/writable /var/lib/snapd/hostfs/etc/writable rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
-1110 1084 1:1 /user-data /var/lib/snapd/hostfs/home rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1111 1084 0:1 /firmware /var/lib/snapd/hostfs/lib/firmware ro,relatime master:28 - squashfs /dev/loop1 ro
-1112 1084 0:1 /modules /var/lib/snapd/hostfs/lib/modules ro,relatime master:29 - squashfs /dev/loop1 ro
-1113 1084 2:6 / /var/lib/snapd/hostfs/media rw,relatime master:30 - tmpfs tmpfs rw
-1114 1084 2:7 / /var/lib/snapd/hostfs/mnt rw,relatime master:31 - tmpfs tmpfs rw
-1115 1084 1:1 /system-data/root /var/lib/snapd/hostfs/root rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1116 1084 2:5 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:35 - tmpfs tmpfs rw,mode=755
-1117 1116 2:10 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:34 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1118 1117 2:11 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:36 - tmpfs tmpfs rw,size=VARIABLE
-1119 1117 2:10 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-1120 1117 2:12 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:37 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-1121 1084 1:1 /system-data/snap /var/lib/snapd/hostfs/snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1122 1121 0:2 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
-1123 1121 0:0 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
-1124 1121 0:1 / /var/lib/snapd/hostfs/snap/pc-kernel/1 ro,nodev,relatime master:40 - squashfs /dev/loop1 ro
-1125 1121 0:3 / /var/lib/snapd/hostfs/snap/pc/1 ro,nodev,relatime master:41 - squashfs /dev/loop3 ro
-1126 1121 0:4 / /var/lib/snapd/hostfs/snap/snapd/1 ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
-1127 1121 0:5 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:43 - squashfs /dev/loop5 ro
-1128 1121 0:6 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:44 - squashfs /dev/loop6 ro
-1129 1121 0:7 / /var/lib/snapd/hostfs/snap/test-snapd-rsync-core18/1 ro,nodev,relatime master:45 - squashfs /dev/loop7 ro
-1130 1084 2:33 / /var/lib/snapd/hostfs/tmp rw,relatime master:66 - tmpfs tmpfs rw
-1131 1084 0:4 /usr/lib/snapd /var/lib/snapd/hostfs/usr/lib/snapd ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
-1132 1084 1:1 /system-data/var/cache /var/lib/snapd/hostfs/var/cache rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1133 1084 1:1 /system-data/var/lib/cloud /var/lib/snapd/hostfs/var/lib/cloud rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1134 1084 1:1 /system-data/var/lib/console-conf /var/lib/snapd/hostfs/var/lib/console-conf rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1135 1084 1:1 /system-data/var/lib/dbus /var/lib/snapd/hostfs/var/lib/dbus rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1136 1084 1:1 /system-data/var/lib/dhcp /var/lib/snapd/hostfs/var/lib/dhcp rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1137 1084 1:1 /system-data/var/lib/extrausers /var/lib/snapd/hostfs/var/lib/extrausers rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1138 1084 1:1 /system-data/var/lib/misc /var/lib/snapd/hostfs/var/lib/misc rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1139 1084 1:1 /system-data/var/lib/private/systemd /var/lib/snapd/hostfs/var/lib/private/systemd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1140 1084 1:1 /system-data/var/lib/snapd /var/lib/snapd/hostfs/var/lib/snapd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1141 1084 2:34 / /var/lib/snapd/hostfs/var/lib/sudo rw,relatime master:67 - tmpfs tmpfs rw,mode=700
-1142 1084 1:1 /system-data/var/lib/systemd /var/lib/snapd/hostfs/var/lib/systemd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1143 1084 1:1 /system-data/var/log /var/lib/snapd/hostfs/var/log rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1144 1084 1:1 /system-data/var/snap /var/lib/snapd/hostfs/var/snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1145 1084 1:1 /system-data/var/tmp /var/lib/snapd/hostfs/var/tmp rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1146 1084 1:1 / /var/lib/snapd/hostfs/writable rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1147 1146 0:2 / /var/lib/snapd/hostfs/writable/system-data/snap/core/1 ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
-1148 1146 0:0 / /var/lib/snapd/hostfs/writable/system-data/snap/core18/1 ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
-1149 1146 0:1 / /var/lib/snapd/hostfs/writable/system-data/snap/pc-kernel/1 ro,nodev,relatime master:40 - squashfs /dev/loop1 ro
-1150 1146 0:3 / /var/lib/snapd/hostfs/writable/system-data/snap/pc/1 ro,nodev,relatime master:41 - squashfs /dev/loop3 ro
-1151 1146 0:4 / /var/lib/snapd/hostfs/writable/system-data/snap/snapd/1 ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
-1152 1146 0:5 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:43 - squashfs /dev/loop5 ro
-1153 1146 0:6 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:44 - squashfs /dev/loop6 ro
-1154 1146 0:7 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-rsync-core18/1 ro,nodev,relatime master:45 - squashfs /dev/loop7 ro
-1155 1001 1:1 /system-data/var/log /var/log rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1156 1001 1:1 /system-data/var/snap /var/snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-1157 1001 1:1 /system-data/var/tmp /var/tmp rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:0 / / ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
+2:0 / /dev rw,nosuid,relatime master:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
+2:1 / /dev/hugepages rw,relatime master:4 - hugetlbfs hugetlbfs rw,pagesize=2M
+2:2 / /dev/mqueue rw,relatime master:5 - mqueue mqueue rw
+2:35 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+2:3 / /dev/pts rw,nosuid,noexec,relatime master:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
+2:35 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+2:4 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
+0:0 /etc /etc ro,relatime master:1 - squashfs /dev/loop0 ro
+1:1 /system-data/etc/apparmor.d/cache /etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/cloud /etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/dbus-1/system.d /etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/swapfile /etc/default/swapfile rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
+2:5 /image.fstab /etc/fstab rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,mode=755
+1:1 /system-data/root/test-etc/group /etc/group ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/gshadow /etc/gshadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/hosts /etc/hosts rw,relatime master:14 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/iproute2 /etc/iproute2 rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/machine-id /etc/machine-id rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modprobe.d /etc/modprobe.d rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modules-load.d /etc/modules-load.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/netplan /etc/netplan rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/if-up.d /etc/network/if-up.d rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
+0:0 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
+1:1 /system-data/root/test-etc/passwd /etc/passwd ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/shadow /etc/shadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ssh /etc/ssh rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
+0:0 /etc/ssl /etc/ssl ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
+1:1 /system-data/etc/sudoers.d /etc/sudoers.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sysctl.d /etc/sysctl.d rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd /etc/systemd rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/udev/rules.d /etc/udev/rules.d rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/writable /etc/writable rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
+1:1 /user-data /home rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:1 /firmware /lib/firmware ro,relatime master:28 - squashfs /dev/loop1 ro
+0:1 /modules /lib/modules ro,relatime master:29 - squashfs /dev/loop1 ro
+2:6 / /media rw,relatime shared:30 - tmpfs tmpfs rw
+2:7 / /mnt rw,relatime master:31 - tmpfs tmpfs rw
+2:8 / /proc rw,nosuid,nodev,noexec,relatime master:32 - proc proc rw
+2:9 / /proc/sys/fs/binfmt_misc rw,relatime master:33 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
+1:1 /system-data/root /root rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+2:10 / /run rw,nosuid,noexec,relatime master:34 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:11 / /run/lock rw,nosuid,nodev,noexec,relatime master:36 - tmpfs tmpfs rw,size=VARIABLE
+2:10 /netns /run/netns rw,nosuid,noexec,relatime shared:34 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:10 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:12 / /run/user/0 rw,nosuid,nodev,relatime master:37 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+1:1 /system-data/snap /snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /snap/core/1 ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
+0:0 / /snap/core18/1 ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
+0:1 / /snap/pc-kernel/1 ro,nodev,relatime master:40 - squashfs /dev/loop1 ro
+0:3 / /snap/pc/1 ro,nodev,relatime master:41 - squashfs /dev/loop3 ro
+0:4 / /snap/snapd/1 ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
+0:5 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:43 - squashfs /dev/loop5 ro
+0:6 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:44 - squashfs /dev/loop6 ro
+0:7 / /snap/test-snapd-rsync-core18/1 ro,nodev,relatime master:45 - squashfs /dev/loop7 ro
+2:13 / /sys rw,nosuid,nodev,noexec,relatime master:46 - sysfs sysfs rw
+2:14 / /sys/fs/cgroup ro,nosuid,nodev,noexec master:47 - tmpfs tmpfs ro,mode=755
+2:15 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:48 - cgroup cgroup rw,blkio
+2:16 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:49 - cgroup cgroup rw,cpu,cpuacct
+2:17 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:50 - cgroup cgroup rw,cpuset
+2:18 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:51 - cgroup cgroup rw,devices
+2:19 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:52 - cgroup cgroup rw,freezer
+2:20 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:53 - cgroup cgroup rw,hugetlb
+2:21 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:54 - cgroup cgroup rw,memory
+2:22 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:55 - cgroup cgroup rw,net_cls,net_prio
+2:23 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:56 - cgroup cgroup rw,perf_event
+2:24 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:57 - cgroup cgroup rw,pids
+2:25 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime master:58 - cgroup cgroup rw,rdma
+2:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:59 - cgroup cgroup rw,xattr,name=systemd
+2:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime master:60 - cgroup2 cgroup rw,nsdelegate
+2:28 / /sys/fs/fuse/connections rw,relatime master:61 - fusectl fusectl rw
+2:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:62 - pstore pstore rw
+2:30 / /sys/kernel/config rw,relatime master:63 - configfs configfs rw
+2:31 / /sys/kernel/debug rw,relatime master:64 - debugfs debugfs rw
+2:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:65 - securityfs securityfs rw
+2:33 / /tmp rw,relatime master:66 - tmpfs tmpfs rw
+2:33 /snap.test-snapd-mountinfo-core18/tmp /tmp rw,relatime - tmpfs tmpfs rw
+0:4 /usr/lib/snapd /usr/lib/snapd ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
+2:36 / /usr/share/gdb rw,relatime - tmpfs tmpfs rw,mode=755
+0:0 /usr/share/gdb/auto-load /usr/share/gdb/auto-load ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
+2:37 / /usr/share/gdb/test rw,relatime - tmpfs tmpfs rw
+0:0 /usr/src /usr/src ro,relatime master:1 - squashfs /dev/loop0 ro
+1:1 /system-data/var/lib/extrausers /var/lib/extrausers rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/snapd /var/lib/snapd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:0 / /var/lib/snapd/hostfs ro,relatime master:1 - squashfs /dev/loop0 ro
+1:1 /system-data/var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda3 rw,data=ordered
+1:0 / /var/lib/snapd/hostfs/boot/efi rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:0 /EFI/ubuntu /var/lib/snapd/hostfs/boot/grub rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:1 /system-data/etc/apparmor.d/cache /var/lib/snapd/hostfs/etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/cloud /var/lib/snapd/hostfs/etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/dbus-1/system.d /var/lib/snapd/hostfs/etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/swapfile /var/lib/snapd/hostfs/etc/default/swapfile rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
+2:5 /image.fstab /var/lib/snapd/hostfs/etc/fstab rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,mode=755
+1:1 /system-data/root/test-etc/group /var/lib/snapd/hostfs/etc/group ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/gshadow /var/lib/snapd/hostfs/etc/gshadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/hosts /var/lib/snapd/hostfs/etc/hosts rw,relatime master:14 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/iproute2 /var/lib/snapd/hostfs/etc/iproute2 rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/machine-id /var/lib/snapd/hostfs/etc/machine-id rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modprobe.d /var/lib/snapd/hostfs/etc/modprobe.d rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modules-load.d /var/lib/snapd/hostfs/etc/modules-load.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/netplan /var/lib/snapd/hostfs/etc/netplan rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/if-up.d /var/lib/snapd/hostfs/etc/network/if-up.d rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/passwd /var/lib/snapd/hostfs/etc/passwd ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/shadow /var/lib/snapd/hostfs/etc/shadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ssh /var/lib/snapd/hostfs/etc/ssh rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sudoers.d /var/lib/snapd/hostfs/etc/sudoers.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sysctl.d /var/lib/snapd/hostfs/etc/sysctl.d rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd /var/lib/snapd/hostfs/etc/systemd rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /var/lib/snapd/hostfs/etc/systemd/system rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/udev/rules.d /var/lib/snapd/hostfs/etc/udev/rules.d rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/writable /var/lib/snapd/hostfs/etc/writable rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
+1:1 /user-data /var/lib/snapd/hostfs/home rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:1 /firmware /var/lib/snapd/hostfs/lib/firmware ro,relatime master:28 - squashfs /dev/loop1 ro
+0:1 /modules /var/lib/snapd/hostfs/lib/modules ro,relatime master:29 - squashfs /dev/loop1 ro
+2:6 / /var/lib/snapd/hostfs/media rw,relatime master:30 - tmpfs tmpfs rw
+2:7 / /var/lib/snapd/hostfs/mnt rw,relatime master:31 - tmpfs tmpfs rw
+1:1 /system-data/root /var/lib/snapd/hostfs/root rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+2:5 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:35 - tmpfs tmpfs rw,mode=755
+2:10 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:34 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:11 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:36 - tmpfs tmpfs rw,size=VARIABLE
+2:10 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:12 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:37 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+1:1 /system-data/snap /var/lib/snapd/hostfs/snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
+0:0 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
+0:1 / /var/lib/snapd/hostfs/snap/pc-kernel/1 ro,nodev,relatime master:40 - squashfs /dev/loop1 ro
+0:3 / /var/lib/snapd/hostfs/snap/pc/1 ro,nodev,relatime master:41 - squashfs /dev/loop3 ro
+0:4 / /var/lib/snapd/hostfs/snap/snapd/1 ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
+0:5 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:43 - squashfs /dev/loop5 ro
+0:6 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:44 - squashfs /dev/loop6 ro
+0:7 / /var/lib/snapd/hostfs/snap/test-snapd-rsync-core18/1 ro,nodev,relatime master:45 - squashfs /dev/loop7 ro
+2:33 / /var/lib/snapd/hostfs/tmp rw,relatime master:66 - tmpfs tmpfs rw
+0:4 /usr/lib/snapd /var/lib/snapd/hostfs/usr/lib/snapd ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
+1:1 /system-data/var/cache /var/lib/snapd/hostfs/var/cache rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/cloud /var/lib/snapd/hostfs/var/lib/cloud rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/console-conf /var/lib/snapd/hostfs/var/lib/console-conf rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/dbus /var/lib/snapd/hostfs/var/lib/dbus rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/dhcp /var/lib/snapd/hostfs/var/lib/dhcp rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/extrausers /var/lib/snapd/hostfs/var/lib/extrausers rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/misc /var/lib/snapd/hostfs/var/lib/misc rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/private/systemd /var/lib/snapd/hostfs/var/lib/private/systemd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/snapd /var/lib/snapd/hostfs/var/lib/snapd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+2:34 / /var/lib/snapd/hostfs/var/lib/sudo rw,relatime master:67 - tmpfs tmpfs rw,mode=700
+1:1 /system-data/var/lib/systemd /var/lib/snapd/hostfs/var/lib/systemd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/log /var/lib/snapd/hostfs/var/log rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/snap /var/lib/snapd/hostfs/var/snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/tmp /var/lib/snapd/hostfs/var/tmp rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 / /var/lib/snapd/hostfs/writable rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /var/lib/snapd/hostfs/writable/system-data/snap/core/1 ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
+0:0 / /var/lib/snapd/hostfs/writable/system-data/snap/core18/1 ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
+0:1 / /var/lib/snapd/hostfs/writable/system-data/snap/pc-kernel/1 ro,nodev,relatime master:40 - squashfs /dev/loop1 ro
+0:3 / /var/lib/snapd/hostfs/writable/system-data/snap/pc/1 ro,nodev,relatime master:41 - squashfs /dev/loop3 ro
+0:4 / /var/lib/snapd/hostfs/writable/system-data/snap/snapd/1 ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
+0:5 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:43 - squashfs /dev/loop5 ro
+0:6 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:44 - squashfs /dev/loop6 ro
+0:7 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-rsync-core18/1 ro,nodev,relatime master:45 - squashfs /dev/loop7 ro
+1:1 /system-data/var/log /var/log rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/snap /var/snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/tmp /var/tmp rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-core-18-64/PER-USER-16.expected.txt snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-core-18-64/PER-USER-16.expected.txt
--- snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-core-18-64/PER-USER-16.expected.txt 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-core-18-64/PER-USER-16.expected.txt 2019-10-30 12:17:43.000000000 +0000
@@ -1,157 +1,160 @@
-2001 2000 0:2 / / ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
-2002 2001 2:0 / /dev rw,nosuid,relatime master:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
-2003 2002 2:1 / /dev/hugepages rw,relatime master:4 - hugetlbfs hugetlbfs rw,pagesize=2M
-2004 2002 2:2 / /dev/mqueue rw,relatime master:5 - mqueue mqueue rw
-2005 2002 2:35 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-2006 2002 2:3 / /dev/pts rw,nosuid,noexec,relatime master:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
-2007 2006 2:35 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-2008 2002 2:4 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
-2009 2001 0:0 /etc /etc ro,relatime master:1 - squashfs /dev/loop0 ro
-2010 2009 1:1 /system-data/etc/apparmor.d/cache /etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
-2011 2009 1:1 /system-data/etc/cloud /etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
-2012 2009 1:1 /system-data/etc/dbus-1/system.d /etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
-2013 2009 1:1 /system-data/etc/default/swapfile /etc/default/swapfile rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
-2014 2009 2:5 /image.fstab /etc/fstab rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,mode=755
-2015 2009 1:1 /system-data/root/test-etc/group /etc/group ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2016 2009 1:1 /system-data/root/test-etc/gshadow /etc/gshadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2017 2009 1:1 /system-data/etc/hosts /etc/hosts rw,relatime master:14 - ext4 /dev/sda3 rw,data=ordered
-2018 2009 1:1 /system-data/etc/iproute2 /etc/iproute2 rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2019 2009 1:1 /system-data/etc/machine-id /etc/machine-id rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
-2020 2009 1:1 /system-data/etc/modprobe.d /etc/modprobe.d rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
-2021 2009 1:1 /system-data/etc/modules-load.d /etc/modules-load.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
-2022 2009 1:1 /system-data/etc/netplan /etc/netplan rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
-2023 2009 1:1 /system-data/etc/network/if-up.d /etc/network/if-up.d rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
-2024 2009 0:2 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
-2025 2009 1:1 /system-data/root/test-etc/passwd /etc/passwd ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2026 2009 1:1 /system-data/root/test-etc/shadow /etc/shadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2027 2009 1:1 /system-data/etc/ssh /etc/ssh rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
-2028 2009 0:2 /etc/ssl /etc/ssl ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
-2029 2009 1:1 /system-data/etc/sudoers.d /etc/sudoers.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
-2030 2009 1:1 /system-data/etc/sysctl.d /etc/sysctl.d rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
-2031 2009 1:1 /system-data/etc/systemd /etc/systemd rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
-2032 2031 1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
-2033 2009 1:1 /system-data/etc/udev/rules.d /etc/udev/rules.d rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
-2034 2009 1:1 /system-data/etc/writable /etc/writable rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
-2035 2001 1:1 /user-data /home rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2036 2001 0:1 /firmware /lib/firmware ro,relatime master:28 - squashfs /dev/loop1 ro
-2037 2001 0:1 /modules /lib/modules ro,relatime master:29 - squashfs /dev/loop1 ro
-2038 2001 2:6 / /media rw,relatime shared:30 - tmpfs tmpfs rw
-2039 2001 2:7 / /mnt rw,relatime master:31 - tmpfs tmpfs rw
-2040 2001 2:8 / /proc rw,nosuid,nodev,noexec,relatime master:32 - proc proc rw
-2041 2040 2:9 / /proc/sys/fs/binfmt_misc rw,relatime master:33 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
-2042 2001 1:1 /system-data/root /root rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2043 2001 2:10 / /run rw,nosuid,noexec,relatime master:34 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2044 2043 2:11 / /run/lock rw,nosuid,nodev,noexec,relatime master:36 - tmpfs tmpfs rw,size=VARIABLE
-2045 2043 2:10 /netns /run/netns rw,nosuid,noexec,relatime shared:34 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2046 2043 2:10 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2047 2043 2:12 / /run/user/0 rw,nosuid,nodev,relatime master:37 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-2048 2001 1:1 /system-data/snap /snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2049 2048 0:2 / /snap/core/1 ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
-2050 2048 0:0 / /snap/core18/1 ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
-2051 2048 0:1 / /snap/pc-kernel/1 ro,nodev,relatime master:40 - squashfs /dev/loop1 ro
-2052 2048 0:3 / /snap/pc/1 ro,nodev,relatime master:41 - squashfs /dev/loop3 ro
-2053 2048 0:4 / /snap/snapd/1 ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
-2054 2048 0:5 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:43 - squashfs /dev/loop5 ro
-2055 2048 0:6 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:44 - squashfs /dev/loop6 ro
-2056 2048 0:7 / /snap/test-snapd-rsync-core18/1 ro,nodev,relatime master:45 - squashfs /dev/loop7 ro
-2057 2001 2:13 / /sys rw,nosuid,nodev,noexec,relatime master:46 - sysfs sysfs rw
-2058 2057 2:14 / /sys/fs/cgroup ro,nosuid,nodev,noexec master:47 - tmpfs tmpfs ro,mode=755
-2059 2058 2:15 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:48 - cgroup cgroup rw,blkio
-2060 2058 2:16 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:49 - cgroup cgroup rw,cpu,cpuacct
-2061 2058 2:17 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:50 - cgroup cgroup rw,cpuset
-2062 2058 2:18 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:51 - cgroup cgroup rw,devices
-2063 2058 2:19 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:52 - cgroup cgroup rw,freezer
-2064 2058 2:20 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:53 - cgroup cgroup rw,hugetlb
-2065 2058 2:21 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:54 - cgroup cgroup rw,memory
-2066 2058 2:22 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:55 - cgroup cgroup rw,net_cls,net_prio
-2067 2058 2:23 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:56 - cgroup cgroup rw,perf_event
-2068 2058 2:24 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:57 - cgroup cgroup rw,pids
-2069 2058 2:25 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime master:58 - cgroup cgroup rw,rdma
-2070 2058 2:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:59 - cgroup cgroup rw,xattr,name=systemd
-2071 2058 2:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime master:60 - cgroup2 cgroup rw,nsdelegate
-2072 2057 2:28 / /sys/fs/fuse/connections rw,relatime master:61 - fusectl fusectl rw
-2073 2057 2:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:62 - pstore pstore rw
-2074 2057 2:30 / /sys/kernel/config rw,relatime master:63 - configfs configfs rw
-2075 2057 2:31 / /sys/kernel/debug rw,relatime master:64 - debugfs debugfs rw
-2076 2057 2:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:65 - securityfs securityfs rw
-2077 2001 2:33 / /tmp rw,relatime master:66 - tmpfs tmpfs rw
-2078 2077 2:33 /snap.test-snapd-mountinfo-core16/tmp /tmp rw,relatime - tmpfs tmpfs rw
-2079 2001 0:4 /usr/lib/snapd /usr/lib/snapd ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
-2080 2001 0:0 /usr/src /usr/src ro,relatime master:1 - squashfs /dev/loop0 ro
-2081 2001 1:1 /system-data/var/lib/extrausers /var/lib/extrausers rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2082 2001 1:1 /system-data/var/lib/snapd /var/lib/snapd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2084 2083 0:0 / /var/lib/snapd/hostfs ro,relatime master:1 - squashfs /dev/loop0 ro
-2083 2082 1:1 /system-data/var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda3 rw,data=ordered
-2085 2084 1:0 / /var/lib/snapd/hostfs/boot/efi rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-2086 2084 1:0 /EFI/ubuntu /var/lib/snapd/hostfs/boot/grub rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-2087 2084 1:1 /system-data/etc/apparmor.d/cache /var/lib/snapd/hostfs/etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
-2088 2084 1:1 /system-data/etc/cloud /var/lib/snapd/hostfs/etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
-2089 2084 1:1 /system-data/etc/dbus-1/system.d /var/lib/snapd/hostfs/etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
-2090 2084 1:1 /system-data/etc/default/swapfile /var/lib/snapd/hostfs/etc/default/swapfile rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
-2091 2084 2:5 /image.fstab /var/lib/snapd/hostfs/etc/fstab rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,mode=755
-2092 2084 1:1 /system-data/root/test-etc/group /var/lib/snapd/hostfs/etc/group ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2093 2084 1:1 /system-data/root/test-etc/gshadow /var/lib/snapd/hostfs/etc/gshadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2094 2084 1:1 /system-data/etc/hosts /var/lib/snapd/hostfs/etc/hosts rw,relatime master:14 - ext4 /dev/sda3 rw,data=ordered
-2095 2084 1:1 /system-data/etc/iproute2 /var/lib/snapd/hostfs/etc/iproute2 rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2096 2084 1:1 /system-data/etc/machine-id /var/lib/snapd/hostfs/etc/machine-id rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
-2097 2084 1:1 /system-data/etc/modprobe.d /var/lib/snapd/hostfs/etc/modprobe.d rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
-2098 2084 1:1 /system-data/etc/modules-load.d /var/lib/snapd/hostfs/etc/modules-load.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
-2099 2084 1:1 /system-data/etc/netplan /var/lib/snapd/hostfs/etc/netplan rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
-2100 2084 1:1 /system-data/etc/network/if-up.d /var/lib/snapd/hostfs/etc/network/if-up.d rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
-2101 2084 1:1 /system-data/root/test-etc/passwd /var/lib/snapd/hostfs/etc/passwd ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2102 2084 1:1 /system-data/root/test-etc/shadow /var/lib/snapd/hostfs/etc/shadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2103 2084 1:1 /system-data/etc/ssh /var/lib/snapd/hostfs/etc/ssh rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
-2104 2084 1:1 /system-data/etc/sudoers.d /var/lib/snapd/hostfs/etc/sudoers.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
-2105 2084 1:1 /system-data/etc/sysctl.d /var/lib/snapd/hostfs/etc/sysctl.d rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
-2106 2084 1:1 /system-data/etc/systemd /var/lib/snapd/hostfs/etc/systemd rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
-2107 2106 1:1 /system-data/etc/systemd/system /var/lib/snapd/hostfs/etc/systemd/system rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
-2108 2084 1:1 /system-data/etc/udev/rules.d /var/lib/snapd/hostfs/etc/udev/rules.d rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
-2109 2084 1:1 /system-data/etc/writable /var/lib/snapd/hostfs/etc/writable rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
-2110 2084 1:1 /user-data /var/lib/snapd/hostfs/home rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2111 2084 0:1 /firmware /var/lib/snapd/hostfs/lib/firmware ro,relatime master:28 - squashfs /dev/loop1 ro
-2112 2084 0:1 /modules /var/lib/snapd/hostfs/lib/modules ro,relatime master:29 - squashfs /dev/loop1 ro
-2113 2084 2:6 / /var/lib/snapd/hostfs/media rw,relatime master:30 - tmpfs tmpfs rw
-2114 2084 2:7 / /var/lib/snapd/hostfs/mnt rw,relatime master:31 - tmpfs tmpfs rw
-2115 2084 1:1 /system-data/root /var/lib/snapd/hostfs/root rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2116 2084 2:5 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:35 - tmpfs tmpfs rw,mode=755
-2117 2116 2:10 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:34 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2118 2117 2:11 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:36 - tmpfs tmpfs rw,size=VARIABLE
-2119 2117 2:10 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2120 2117 2:12 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:37 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-2121 2084 1:1 /system-data/snap /var/lib/snapd/hostfs/snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2122 2121 0:2 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
-2123 2121 0:0 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
-2124 2121 0:1 / /var/lib/snapd/hostfs/snap/pc-kernel/1 ro,nodev,relatime master:40 - squashfs /dev/loop1 ro
-2125 2121 0:3 / /var/lib/snapd/hostfs/snap/pc/1 ro,nodev,relatime master:41 - squashfs /dev/loop3 ro
-2126 2121 0:4 / /var/lib/snapd/hostfs/snap/snapd/1 ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
-2127 2121 0:5 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:43 - squashfs /dev/loop5 ro
-2128 2121 0:6 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:44 - squashfs /dev/loop6 ro
-2129 2121 0:7 / /var/lib/snapd/hostfs/snap/test-snapd-rsync-core18/1 ro,nodev,relatime master:45 - squashfs /dev/loop7 ro
-2130 2084 2:33 / /var/lib/snapd/hostfs/tmp rw,relatime master:66 - tmpfs tmpfs rw
-2131 2084 0:4 /usr/lib/snapd /var/lib/snapd/hostfs/usr/lib/snapd ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
-2132 2084 1:1 /system-data/var/cache /var/lib/snapd/hostfs/var/cache rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2133 2084 1:1 /system-data/var/lib/cloud /var/lib/snapd/hostfs/var/lib/cloud rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2134 2084 1:1 /system-data/var/lib/console-conf /var/lib/snapd/hostfs/var/lib/console-conf rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2135 2084 1:1 /system-data/var/lib/dbus /var/lib/snapd/hostfs/var/lib/dbus rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2136 2084 1:1 /system-data/var/lib/dhcp /var/lib/snapd/hostfs/var/lib/dhcp rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2137 2084 1:1 /system-data/var/lib/extrausers /var/lib/snapd/hostfs/var/lib/extrausers rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2138 2084 1:1 /system-data/var/lib/misc /var/lib/snapd/hostfs/var/lib/misc rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2139 2084 1:1 /system-data/var/lib/private/systemd /var/lib/snapd/hostfs/var/lib/private/systemd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2140 2084 1:1 /system-data/var/lib/snapd /var/lib/snapd/hostfs/var/lib/snapd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2141 2084 2:34 / /var/lib/snapd/hostfs/var/lib/sudo rw,relatime master:67 - tmpfs tmpfs rw,mode=700
-2142 2084 1:1 /system-data/var/lib/systemd /var/lib/snapd/hostfs/var/lib/systemd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2143 2084 1:1 /system-data/var/log /var/lib/snapd/hostfs/var/log rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2144 2084 1:1 /system-data/var/snap /var/lib/snapd/hostfs/var/snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2145 2084 1:1 /system-data/var/tmp /var/lib/snapd/hostfs/var/tmp rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2146 2084 1:1 / /var/lib/snapd/hostfs/writable rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2147 2146 0:2 / /var/lib/snapd/hostfs/writable/system-data/snap/core/1 ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
-2148 2146 0:0 / /var/lib/snapd/hostfs/writable/system-data/snap/core18/1 ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
-2149 2146 0:1 / /var/lib/snapd/hostfs/writable/system-data/snap/pc-kernel/1 ro,nodev,relatime master:40 - squashfs /dev/loop1 ro
-2150 2146 0:3 / /var/lib/snapd/hostfs/writable/system-data/snap/pc/1 ro,nodev,relatime master:41 - squashfs /dev/loop3 ro
-2151 2146 0:4 / /var/lib/snapd/hostfs/writable/system-data/snap/snapd/1 ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
-2152 2146 0:5 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:43 - squashfs /dev/loop5 ro
-2153 2146 0:6 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:44 - squashfs /dev/loop6 ro
-2154 2146 0:7 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-rsync-core18/1 ro,nodev,relatime master:45 - squashfs /dev/loop7 ro
-2155 2001 1:1 /system-data/var/log /var/log rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2156 2001 1:1 /system-data/var/snap /var/snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2157 2001 1:1 /system-data/var/tmp /var/tmp rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:2 / / ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
+2:0 / /dev rw,nosuid,relatime master:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
+2:1 / /dev/hugepages rw,relatime master:4 - hugetlbfs hugetlbfs rw,pagesize=2M
+2:2 / /dev/mqueue rw,relatime master:5 - mqueue mqueue rw
+2:35 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+2:3 / /dev/pts rw,nosuid,noexec,relatime master:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
+2:35 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+2:4 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
+0:0 /etc /etc ro,relatime master:1 - squashfs /dev/loop0 ro
+1:1 /system-data/etc/apparmor.d/cache /etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/cloud /etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/dbus-1/system.d /etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/swapfile /etc/default/swapfile rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
+2:5 /image.fstab /etc/fstab rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,mode=755
+1:1 /system-data/root/test-etc/group /etc/group ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/gshadow /etc/gshadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/hosts /etc/hosts rw,relatime master:14 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/iproute2 /etc/iproute2 rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/machine-id /etc/machine-id rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modprobe.d /etc/modprobe.d rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modules-load.d /etc/modules-load.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/netplan /etc/netplan rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/if-up.d /etc/network/if-up.d rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
+0:2 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
+1:1 /system-data/root/test-etc/passwd /etc/passwd ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/shadow /etc/shadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ssh /etc/ssh rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
+0:2 /etc/ssl /etc/ssl ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
+1:1 /system-data/etc/sudoers.d /etc/sudoers.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sysctl.d /etc/sysctl.d rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd /etc/systemd rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/udev/rules.d /etc/udev/rules.d rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/writable /etc/writable rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
+1:1 /user-data /home rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:1 /firmware /lib/firmware ro,relatime master:28 - squashfs /dev/loop1 ro
+0:1 /modules /lib/modules ro,relatime master:29 - squashfs /dev/loop1 ro
+2:6 / /media rw,relatime shared:30 - tmpfs tmpfs rw
+2:7 / /mnt rw,relatime master:31 - tmpfs tmpfs rw
+2:8 / /proc rw,nosuid,nodev,noexec,relatime master:32 - proc proc rw
+2:9 / /proc/sys/fs/binfmt_misc rw,relatime master:33 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
+1:1 /system-data/root /root rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+2:10 / /run rw,nosuid,noexec,relatime master:34 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:11 / /run/lock rw,nosuid,nodev,noexec,relatime master:36 - tmpfs tmpfs rw,size=VARIABLE
+2:10 /netns /run/netns rw,nosuid,noexec,relatime shared:34 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:10 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:12 / /run/user/0 rw,nosuid,nodev,relatime master:37 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+1:1 /system-data/snap /snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /snap/core/1 ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
+0:0 / /snap/core18/1 ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
+0:1 / /snap/pc-kernel/1 ro,nodev,relatime master:40 - squashfs /dev/loop1 ro
+0:3 / /snap/pc/1 ro,nodev,relatime master:41 - squashfs /dev/loop3 ro
+0:4 / /snap/snapd/1 ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
+0:5 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:43 - squashfs /dev/loop5 ro
+0:6 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:44 - squashfs /dev/loop6 ro
+0:7 / /snap/test-snapd-rsync-core18/1 ro,nodev,relatime master:45 - squashfs /dev/loop7 ro
+2:13 / /sys rw,nosuid,nodev,noexec,relatime master:46 - sysfs sysfs rw
+2:14 / /sys/fs/cgroup ro,nosuid,nodev,noexec master:47 - tmpfs tmpfs ro,mode=755
+2:15 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:48 - cgroup cgroup rw,blkio
+2:16 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:49 - cgroup cgroup rw,cpu,cpuacct
+2:17 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:50 - cgroup cgroup rw,cpuset
+2:18 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:51 - cgroup cgroup rw,devices
+2:19 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:52 - cgroup cgroup rw,freezer
+2:20 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:53 - cgroup cgroup rw,hugetlb
+2:21 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:54 - cgroup cgroup rw,memory
+2:22 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:55 - cgroup cgroup rw,net_cls,net_prio
+2:23 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:56 - cgroup cgroup rw,perf_event
+2:24 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:57 - cgroup cgroup rw,pids
+2:25 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime master:58 - cgroup cgroup rw,rdma
+2:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:59 - cgroup cgroup rw,xattr,name=systemd
+2:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime master:60 - cgroup2 cgroup rw,nsdelegate
+2:28 / /sys/fs/fuse/connections rw,relatime master:61 - fusectl fusectl rw
+2:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:62 - pstore pstore rw
+2:30 / /sys/kernel/config rw,relatime master:63 - configfs configfs rw
+2:31 / /sys/kernel/debug rw,relatime master:64 - debugfs debugfs rw
+2:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:65 - securityfs securityfs rw
+2:33 / /tmp rw,relatime master:66 - tmpfs tmpfs rw
+2:33 /snap.test-snapd-mountinfo-core16/tmp /tmp rw,relatime - tmpfs tmpfs rw
+0:4 /usr/lib/snapd /usr/lib/snapd ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
+2:36 / /usr/share/gdb rw,relatime - tmpfs tmpfs rw,mode=755
+0:2 /usr/share/gdb/auto-load /usr/share/gdb/auto-load ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
+2:37 / /usr/share/gdb/test rw,relatime - tmpfs tmpfs rw
+0:0 /usr/src /usr/src ro,relatime master:1 - squashfs /dev/loop0 ro
+1:1 /system-data/var/lib/extrausers /var/lib/extrausers rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/snapd /var/lib/snapd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:0 / /var/lib/snapd/hostfs ro,relatime master:1 - squashfs /dev/loop0 ro
+1:1 /system-data/var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda3 rw,data=ordered
+1:0 / /var/lib/snapd/hostfs/boot/efi rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:0 /EFI/ubuntu /var/lib/snapd/hostfs/boot/grub rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:1 /system-data/etc/apparmor.d/cache /var/lib/snapd/hostfs/etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/cloud /var/lib/snapd/hostfs/etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/dbus-1/system.d /var/lib/snapd/hostfs/etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/swapfile /var/lib/snapd/hostfs/etc/default/swapfile rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
+2:5 /image.fstab /var/lib/snapd/hostfs/etc/fstab rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,mode=755
+1:1 /system-data/root/test-etc/group /var/lib/snapd/hostfs/etc/group ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/gshadow /var/lib/snapd/hostfs/etc/gshadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/hosts /var/lib/snapd/hostfs/etc/hosts rw,relatime master:14 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/iproute2 /var/lib/snapd/hostfs/etc/iproute2 rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/machine-id /var/lib/snapd/hostfs/etc/machine-id rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modprobe.d /var/lib/snapd/hostfs/etc/modprobe.d rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modules-load.d /var/lib/snapd/hostfs/etc/modules-load.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/netplan /var/lib/snapd/hostfs/etc/netplan rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/if-up.d /var/lib/snapd/hostfs/etc/network/if-up.d rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/passwd /var/lib/snapd/hostfs/etc/passwd ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/shadow /var/lib/snapd/hostfs/etc/shadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ssh /var/lib/snapd/hostfs/etc/ssh rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sudoers.d /var/lib/snapd/hostfs/etc/sudoers.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sysctl.d /var/lib/snapd/hostfs/etc/sysctl.d rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd /var/lib/snapd/hostfs/etc/systemd rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /var/lib/snapd/hostfs/etc/systemd/system rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/udev/rules.d /var/lib/snapd/hostfs/etc/udev/rules.d rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/writable /var/lib/snapd/hostfs/etc/writable rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
+1:1 /user-data /var/lib/snapd/hostfs/home rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:1 /firmware /var/lib/snapd/hostfs/lib/firmware ro,relatime master:28 - squashfs /dev/loop1 ro
+0:1 /modules /var/lib/snapd/hostfs/lib/modules ro,relatime master:29 - squashfs /dev/loop1 ro
+2:6 / /var/lib/snapd/hostfs/media rw,relatime master:30 - tmpfs tmpfs rw
+2:7 / /var/lib/snapd/hostfs/mnt rw,relatime master:31 - tmpfs tmpfs rw
+1:1 /system-data/root /var/lib/snapd/hostfs/root rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+2:5 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:35 - tmpfs tmpfs rw,mode=755
+2:10 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:34 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:11 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:36 - tmpfs tmpfs rw,size=VARIABLE
+2:10 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:12 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:37 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+1:1 /system-data/snap /var/lib/snapd/hostfs/snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
+0:0 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
+0:1 / /var/lib/snapd/hostfs/snap/pc-kernel/1 ro,nodev,relatime master:40 - squashfs /dev/loop1 ro
+0:3 / /var/lib/snapd/hostfs/snap/pc/1 ro,nodev,relatime master:41 - squashfs /dev/loop3 ro
+0:4 / /var/lib/snapd/hostfs/snap/snapd/1 ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
+0:5 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:43 - squashfs /dev/loop5 ro
+0:6 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:44 - squashfs /dev/loop6 ro
+0:7 / /var/lib/snapd/hostfs/snap/test-snapd-rsync-core18/1 ro,nodev,relatime master:45 - squashfs /dev/loop7 ro
+2:33 / /var/lib/snapd/hostfs/tmp rw,relatime master:66 - tmpfs tmpfs rw
+0:4 /usr/lib/snapd /var/lib/snapd/hostfs/usr/lib/snapd ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
+1:1 /system-data/var/cache /var/lib/snapd/hostfs/var/cache rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/cloud /var/lib/snapd/hostfs/var/lib/cloud rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/console-conf /var/lib/snapd/hostfs/var/lib/console-conf rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/dbus /var/lib/snapd/hostfs/var/lib/dbus rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/dhcp /var/lib/snapd/hostfs/var/lib/dhcp rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/extrausers /var/lib/snapd/hostfs/var/lib/extrausers rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/misc /var/lib/snapd/hostfs/var/lib/misc rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/private/systemd /var/lib/snapd/hostfs/var/lib/private/systemd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/snapd /var/lib/snapd/hostfs/var/lib/snapd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+2:34 / /var/lib/snapd/hostfs/var/lib/sudo rw,relatime master:67 - tmpfs tmpfs rw,mode=700
+1:1 /system-data/var/lib/systemd /var/lib/snapd/hostfs/var/lib/systemd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/log /var/lib/snapd/hostfs/var/log rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/snap /var/lib/snapd/hostfs/var/snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/tmp /var/lib/snapd/hostfs/var/tmp rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 / /var/lib/snapd/hostfs/writable rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /var/lib/snapd/hostfs/writable/system-data/snap/core/1 ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
+0:0 / /var/lib/snapd/hostfs/writable/system-data/snap/core18/1 ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
+0:1 / /var/lib/snapd/hostfs/writable/system-data/snap/pc-kernel/1 ro,nodev,relatime master:40 - squashfs /dev/loop1 ro
+0:3 / /var/lib/snapd/hostfs/writable/system-data/snap/pc/1 ro,nodev,relatime master:41 - squashfs /dev/loop3 ro
+0:4 / /var/lib/snapd/hostfs/writable/system-data/snap/snapd/1 ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
+0:5 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:43 - squashfs /dev/loop5 ro
+0:6 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:44 - squashfs /dev/loop6 ro
+0:7 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-rsync-core18/1 ro,nodev,relatime master:45 - squashfs /dev/loop7 ro
+1:1 /system-data/var/log /var/log rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/snap /var/snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/tmp /var/tmp rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-core-18-64/PER-USER-18.expected.txt snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-core-18-64/PER-USER-18.expected.txt
--- snapd-2.41+19.10.1/tests/main/mount-ns/google.ubuntu-core-18-64/PER-USER-18.expected.txt 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/google.ubuntu-core-18-64/PER-USER-18.expected.txt 2019-10-30 12:17:43.000000000 +0000
@@ -1,157 +1,160 @@
-2001 2000 0:0 / / ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
-2002 2001 2:0 / /dev rw,nosuid,relatime master:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
-2003 2002 2:1 / /dev/hugepages rw,relatime master:4 - hugetlbfs hugetlbfs rw,pagesize=2M
-2004 2002 2:2 / /dev/mqueue rw,relatime master:5 - mqueue mqueue rw
-2005 2002 2:35 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-2006 2002 2:3 / /dev/pts rw,nosuid,noexec,relatime master:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
-2007 2006 2:35 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
-2008 2002 2:4 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
-2009 2001 0:0 /etc /etc ro,relatime master:1 - squashfs /dev/loop0 ro
-2010 2009 1:1 /system-data/etc/apparmor.d/cache /etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
-2011 2009 1:1 /system-data/etc/cloud /etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
-2012 2009 1:1 /system-data/etc/dbus-1/system.d /etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
-2013 2009 1:1 /system-data/etc/default/swapfile /etc/default/swapfile rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
-2014 2009 2:5 /image.fstab /etc/fstab rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,mode=755
-2015 2009 1:1 /system-data/root/test-etc/group /etc/group ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2016 2009 1:1 /system-data/root/test-etc/gshadow /etc/gshadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2017 2009 1:1 /system-data/etc/hosts /etc/hosts rw,relatime master:14 - ext4 /dev/sda3 rw,data=ordered
-2018 2009 1:1 /system-data/etc/iproute2 /etc/iproute2 rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2019 2009 1:1 /system-data/etc/machine-id /etc/machine-id rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
-2020 2009 1:1 /system-data/etc/modprobe.d /etc/modprobe.d rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
-2021 2009 1:1 /system-data/etc/modules-load.d /etc/modules-load.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
-2022 2009 1:1 /system-data/etc/netplan /etc/netplan rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
-2023 2009 1:1 /system-data/etc/network/if-up.d /etc/network/if-up.d rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
-2024 2009 0:0 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
-2025 2009 1:1 /system-data/root/test-etc/passwd /etc/passwd ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2026 2009 1:1 /system-data/root/test-etc/shadow /etc/shadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2027 2009 1:1 /system-data/etc/ssh /etc/ssh rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
-2028 2009 0:0 /etc/ssl /etc/ssl ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
-2029 2009 1:1 /system-data/etc/sudoers.d /etc/sudoers.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
-2030 2009 1:1 /system-data/etc/sysctl.d /etc/sysctl.d rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
-2031 2009 1:1 /system-data/etc/systemd /etc/systemd rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
-2032 2031 1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
-2033 2009 1:1 /system-data/etc/udev/rules.d /etc/udev/rules.d rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
-2034 2009 1:1 /system-data/etc/writable /etc/writable rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
-2035 2001 1:1 /user-data /home rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2036 2001 0:1 /firmware /lib/firmware ro,relatime master:28 - squashfs /dev/loop1 ro
-2037 2001 0:1 /modules /lib/modules ro,relatime master:29 - squashfs /dev/loop1 ro
-2038 2001 2:6 / /media rw,relatime shared:30 - tmpfs tmpfs rw
-2039 2001 2:7 / /mnt rw,relatime master:31 - tmpfs tmpfs rw
-2040 2001 2:8 / /proc rw,nosuid,nodev,noexec,relatime master:32 - proc proc rw
-2041 2040 2:9 / /proc/sys/fs/binfmt_misc rw,relatime master:33 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
-2042 2001 1:1 /system-data/root /root rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2043 2001 2:10 / /run rw,nosuid,noexec,relatime master:34 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2044 2043 2:11 / /run/lock rw,nosuid,nodev,noexec,relatime master:36 - tmpfs tmpfs rw,size=VARIABLE
-2045 2043 2:10 /netns /run/netns rw,nosuid,noexec,relatime shared:34 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2046 2043 2:10 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2047 2043 2:12 / /run/user/0 rw,nosuid,nodev,relatime master:37 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-2048 2001 1:1 /system-data/snap /snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2049 2048 0:2 / /snap/core/1 ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
-2050 2048 0:0 / /snap/core18/1 ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
-2051 2048 0:1 / /snap/pc-kernel/1 ro,nodev,relatime master:40 - squashfs /dev/loop1 ro
-2052 2048 0:3 / /snap/pc/1 ro,nodev,relatime master:41 - squashfs /dev/loop3 ro
-2053 2048 0:4 / /snap/snapd/1 ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
-2054 2048 0:5 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:43 - squashfs /dev/loop5 ro
-2055 2048 0:6 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:44 - squashfs /dev/loop6 ro
-2056 2048 0:7 / /snap/test-snapd-rsync-core18/1 ro,nodev,relatime master:45 - squashfs /dev/loop7 ro
-2057 2001 2:13 / /sys rw,nosuid,nodev,noexec,relatime master:46 - sysfs sysfs rw
-2058 2057 2:14 / /sys/fs/cgroup ro,nosuid,nodev,noexec master:47 - tmpfs tmpfs ro,mode=755
-2059 2058 2:15 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:48 - cgroup cgroup rw,blkio
-2060 2058 2:16 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:49 - cgroup cgroup rw,cpu,cpuacct
-2061 2058 2:17 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:50 - cgroup cgroup rw,cpuset
-2062 2058 2:18 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:51 - cgroup cgroup rw,devices
-2063 2058 2:19 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:52 - cgroup cgroup rw,freezer
-2064 2058 2:20 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:53 - cgroup cgroup rw,hugetlb
-2065 2058 2:21 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:54 - cgroup cgroup rw,memory
-2066 2058 2:22 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:55 - cgroup cgroup rw,net_cls,net_prio
-2067 2058 2:23 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:56 - cgroup cgroup rw,perf_event
-2068 2058 2:24 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:57 - cgroup cgroup rw,pids
-2069 2058 2:25 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime master:58 - cgroup cgroup rw,rdma
-2070 2058 2:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:59 - cgroup cgroup rw,xattr,name=systemd
-2071 2058 2:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime master:60 - cgroup2 cgroup rw,nsdelegate
-2072 2057 2:28 / /sys/fs/fuse/connections rw,relatime master:61 - fusectl fusectl rw
-2073 2057 2:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:62 - pstore pstore rw
-2074 2057 2:30 / /sys/kernel/config rw,relatime master:63 - configfs configfs rw
-2075 2057 2:31 / /sys/kernel/debug rw,relatime master:64 - debugfs debugfs rw
-2076 2057 2:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:65 - securityfs securityfs rw
-2077 2001 2:33 / /tmp rw,relatime master:66 - tmpfs tmpfs rw
-2078 2077 2:33 /snap.test-snapd-mountinfo-core18/tmp /tmp rw,relatime - tmpfs tmpfs rw
-2079 2001 0:4 /usr/lib/snapd /usr/lib/snapd ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
-2080 2001 0:0 /usr/src /usr/src ro,relatime master:1 - squashfs /dev/loop0 ro
-2081 2001 1:1 /system-data/var/lib/extrausers /var/lib/extrausers rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2082 2001 1:1 /system-data/var/lib/snapd /var/lib/snapd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2084 2083 0:0 / /var/lib/snapd/hostfs ro,relatime master:1 - squashfs /dev/loop0 ro
-2083 2082 1:1 /system-data/var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda3 rw,data=ordered
-2085 2084 1:0 / /var/lib/snapd/hostfs/boot/efi rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-2086 2084 1:0 /EFI/ubuntu /var/lib/snapd/hostfs/boot/grub rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
-2087 2084 1:1 /system-data/etc/apparmor.d/cache /var/lib/snapd/hostfs/etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
-2088 2084 1:1 /system-data/etc/cloud /var/lib/snapd/hostfs/etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
-2089 2084 1:1 /system-data/etc/dbus-1/system.d /var/lib/snapd/hostfs/etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
-2090 2084 1:1 /system-data/etc/default/swapfile /var/lib/snapd/hostfs/etc/default/swapfile rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
-2091 2084 2:5 /image.fstab /var/lib/snapd/hostfs/etc/fstab rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,mode=755
-2092 2084 1:1 /system-data/root/test-etc/group /var/lib/snapd/hostfs/etc/group ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2093 2084 1:1 /system-data/root/test-etc/gshadow /var/lib/snapd/hostfs/etc/gshadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2094 2084 1:1 /system-data/etc/hosts /var/lib/snapd/hostfs/etc/hosts rw,relatime master:14 - ext4 /dev/sda3 rw,data=ordered
-2095 2084 1:1 /system-data/etc/iproute2 /var/lib/snapd/hostfs/etc/iproute2 rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
-2096 2084 1:1 /system-data/etc/machine-id /var/lib/snapd/hostfs/etc/machine-id rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
-2097 2084 1:1 /system-data/etc/modprobe.d /var/lib/snapd/hostfs/etc/modprobe.d rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
-2098 2084 1:1 /system-data/etc/modules-load.d /var/lib/snapd/hostfs/etc/modules-load.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
-2099 2084 1:1 /system-data/etc/netplan /var/lib/snapd/hostfs/etc/netplan rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
-2100 2084 1:1 /system-data/etc/network/if-up.d /var/lib/snapd/hostfs/etc/network/if-up.d rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
-2101 2084 1:1 /system-data/root/test-etc/passwd /var/lib/snapd/hostfs/etc/passwd ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2102 2084 1:1 /system-data/root/test-etc/shadow /var/lib/snapd/hostfs/etc/shadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2103 2084 1:1 /system-data/etc/ssh /var/lib/snapd/hostfs/etc/ssh rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
-2104 2084 1:1 /system-data/etc/sudoers.d /var/lib/snapd/hostfs/etc/sudoers.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
-2105 2084 1:1 /system-data/etc/sysctl.d /var/lib/snapd/hostfs/etc/sysctl.d rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
-2106 2084 1:1 /system-data/etc/systemd /var/lib/snapd/hostfs/etc/systemd rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
-2107 2106 1:1 /system-data/etc/systemd/system /var/lib/snapd/hostfs/etc/systemd/system rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
-2108 2084 1:1 /system-data/etc/udev/rules.d /var/lib/snapd/hostfs/etc/udev/rules.d rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
-2109 2084 1:1 /system-data/etc/writable /var/lib/snapd/hostfs/etc/writable rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
-2110 2084 1:1 /user-data /var/lib/snapd/hostfs/home rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2111 2084 0:1 /firmware /var/lib/snapd/hostfs/lib/firmware ro,relatime master:28 - squashfs /dev/loop1 ro
-2112 2084 0:1 /modules /var/lib/snapd/hostfs/lib/modules ro,relatime master:29 - squashfs /dev/loop1 ro
-2113 2084 2:6 / /var/lib/snapd/hostfs/media rw,relatime master:30 - tmpfs tmpfs rw
-2114 2084 2:7 / /var/lib/snapd/hostfs/mnt rw,relatime master:31 - tmpfs tmpfs rw
-2115 2084 1:1 /system-data/root /var/lib/snapd/hostfs/root rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2116 2084 2:5 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:35 - tmpfs tmpfs rw,mode=755
-2117 2116 2:10 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:34 - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2118 2117 2:11 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:36 - tmpfs tmpfs rw,size=VARIABLE
-2119 2117 2:10 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
-2120 2117 2:12 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:37 - tmpfs tmpfs rw,size=VARIABLE,mode=700
-2121 2084 1:1 /system-data/snap /var/lib/snapd/hostfs/snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2122 2121 0:2 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
-2123 2121 0:0 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
-2124 2121 0:1 / /var/lib/snapd/hostfs/snap/pc-kernel/1 ro,nodev,relatime master:40 - squashfs /dev/loop1 ro
-2125 2121 0:3 / /var/lib/snapd/hostfs/snap/pc/1 ro,nodev,relatime master:41 - squashfs /dev/loop3 ro
-2126 2121 0:4 / /var/lib/snapd/hostfs/snap/snapd/1 ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
-2127 2121 0:5 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:43 - squashfs /dev/loop5 ro
-2128 2121 0:6 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:44 - squashfs /dev/loop6 ro
-2129 2121 0:7 / /var/lib/snapd/hostfs/snap/test-snapd-rsync-core18/1 ro,nodev,relatime master:45 - squashfs /dev/loop7 ro
-2130 2084 2:33 / /var/lib/snapd/hostfs/tmp rw,relatime master:66 - tmpfs tmpfs rw
-2131 2084 0:4 /usr/lib/snapd /var/lib/snapd/hostfs/usr/lib/snapd ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
-2132 2084 1:1 /system-data/var/cache /var/lib/snapd/hostfs/var/cache rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2133 2084 1:1 /system-data/var/lib/cloud /var/lib/snapd/hostfs/var/lib/cloud rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2134 2084 1:1 /system-data/var/lib/console-conf /var/lib/snapd/hostfs/var/lib/console-conf rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2135 2084 1:1 /system-data/var/lib/dbus /var/lib/snapd/hostfs/var/lib/dbus rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2136 2084 1:1 /system-data/var/lib/dhcp /var/lib/snapd/hostfs/var/lib/dhcp rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2137 2084 1:1 /system-data/var/lib/extrausers /var/lib/snapd/hostfs/var/lib/extrausers rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2138 2084 1:1 /system-data/var/lib/misc /var/lib/snapd/hostfs/var/lib/misc rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2139 2084 1:1 /system-data/var/lib/private/systemd /var/lib/snapd/hostfs/var/lib/private/systemd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2140 2084 1:1 /system-data/var/lib/snapd /var/lib/snapd/hostfs/var/lib/snapd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2141 2084 2:34 / /var/lib/snapd/hostfs/var/lib/sudo rw,relatime master:67 - tmpfs tmpfs rw,mode=700
-2142 2084 1:1 /system-data/var/lib/systemd /var/lib/snapd/hostfs/var/lib/systemd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2143 2084 1:1 /system-data/var/log /var/lib/snapd/hostfs/var/log rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2144 2084 1:1 /system-data/var/snap /var/lib/snapd/hostfs/var/snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2145 2084 1:1 /system-data/var/tmp /var/lib/snapd/hostfs/var/tmp rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2146 2084 1:1 / /var/lib/snapd/hostfs/writable rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2147 2146 0:2 / /var/lib/snapd/hostfs/writable/system-data/snap/core/1 ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
-2148 2146 0:0 / /var/lib/snapd/hostfs/writable/system-data/snap/core18/1 ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
-2149 2146 0:1 / /var/lib/snapd/hostfs/writable/system-data/snap/pc-kernel/1 ro,nodev,relatime master:40 - squashfs /dev/loop1 ro
-2150 2146 0:3 / /var/lib/snapd/hostfs/writable/system-data/snap/pc/1 ro,nodev,relatime master:41 - squashfs /dev/loop3 ro
-2151 2146 0:4 / /var/lib/snapd/hostfs/writable/system-data/snap/snapd/1 ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
-2152 2146 0:5 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:43 - squashfs /dev/loop5 ro
-2153 2146 0:6 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:44 - squashfs /dev/loop6 ro
-2154 2146 0:7 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-rsync-core18/1 ro,nodev,relatime master:45 - squashfs /dev/loop7 ro
-2155 2001 1:1 /system-data/var/log /var/log rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2156 2001 1:1 /system-data/var/snap /var/snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
-2157 2001 1:1 /system-data/var/tmp /var/tmp rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:0 / / ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
+2:0 / /dev rw,nosuid,relatime master:3 - devtmpfs udev rw,size=VARIABLE,nr_inodes=0,mode=755
+2:1 / /dev/hugepages rw,relatime master:4 - hugetlbfs hugetlbfs rw,pagesize=2M
+2:2 / /dev/mqueue rw,relatime master:5 - mqueue mqueue rw
+2:35 /ptmx /dev/ptmx rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+2:3 / /dev/pts rw,nosuid,noexec,relatime master:6 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
+2:35 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
+2:4 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
+0:0 /etc /etc ro,relatime master:1 - squashfs /dev/loop0 ro
+1:1 /system-data/etc/apparmor.d/cache /etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/cloud /etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/dbus-1/system.d /etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/swapfile /etc/default/swapfile rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
+2:5 /image.fstab /etc/fstab rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,mode=755
+1:1 /system-data/root/test-etc/group /etc/group ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/gshadow /etc/gshadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/hosts /etc/hosts rw,relatime master:14 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/iproute2 /etc/iproute2 rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/machine-id /etc/machine-id rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modprobe.d /etc/modprobe.d rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modules-load.d /etc/modules-load.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/netplan /etc/netplan rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/if-up.d /etc/network/if-up.d rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
+0:0 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
+1:1 /system-data/root/test-etc/passwd /etc/passwd ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/shadow /etc/shadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ssh /etc/ssh rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
+0:0 /etc/ssl /etc/ssl ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
+1:1 /system-data/etc/sudoers.d /etc/sudoers.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sysctl.d /etc/sysctl.d rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd /etc/systemd rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /etc/systemd/system rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/udev/rules.d /etc/udev/rules.d rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/writable /etc/writable rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
+1:1 /user-data /home rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:1 /firmware /lib/firmware ro,relatime master:28 - squashfs /dev/loop1 ro
+0:1 /modules /lib/modules ro,relatime master:29 - squashfs /dev/loop1 ro
+2:6 / /media rw,relatime shared:30 - tmpfs tmpfs rw
+2:7 / /mnt rw,relatime master:31 - tmpfs tmpfs rw
+2:8 / /proc rw,nosuid,nodev,noexec,relatime master:32 - proc proc rw
+2:9 / /proc/sys/fs/binfmt_misc rw,relatime master:33 - autofs systemd-1 rw,fd=0,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=0
+1:1 /system-data/root /root rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+2:10 / /run rw,nosuid,noexec,relatime master:34 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:11 / /run/lock rw,nosuid,nodev,noexec,relatime master:36 - tmpfs tmpfs rw,size=VARIABLE
+2:10 /netns /run/netns rw,nosuid,noexec,relatime shared:34 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:10 /snapd/ns /run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:12 / /run/user/0 rw,nosuid,nodev,relatime master:37 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+1:1 /system-data/snap /snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /snap/core/1 ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
+0:0 / /snap/core18/1 ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
+0:1 / /snap/pc-kernel/1 ro,nodev,relatime master:40 - squashfs /dev/loop1 ro
+0:3 / /snap/pc/1 ro,nodev,relatime master:41 - squashfs /dev/loop3 ro
+0:4 / /snap/snapd/1 ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
+0:5 / /snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:43 - squashfs /dev/loop5 ro
+0:6 / /snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:44 - squashfs /dev/loop6 ro
+0:7 / /snap/test-snapd-rsync-core18/1 ro,nodev,relatime master:45 - squashfs /dev/loop7 ro
+2:13 / /sys rw,nosuid,nodev,noexec,relatime master:46 - sysfs sysfs rw
+2:14 / /sys/fs/cgroup ro,nosuid,nodev,noexec master:47 - tmpfs tmpfs ro,mode=755
+2:15 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime master:48 - cgroup cgroup rw,blkio
+2:16 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime master:49 - cgroup cgroup rw,cpu,cpuacct
+2:17 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime master:50 - cgroup cgroup rw,cpuset
+2:18 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime master:51 - cgroup cgroup rw,devices
+2:19 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime master:52 - cgroup cgroup rw,freezer
+2:20 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime master:53 - cgroup cgroup rw,hugetlb
+2:21 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:54 - cgroup cgroup rw,memory
+2:22 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime master:55 - cgroup cgroup rw,net_cls,net_prio
+2:23 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime master:56 - cgroup cgroup rw,perf_event
+2:24 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime master:57 - cgroup cgroup rw,pids
+2:25 / /sys/fs/cgroup/rdma rw,nosuid,nodev,noexec,relatime master:58 - cgroup cgroup rw,rdma
+2:26 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime master:59 - cgroup cgroup rw,xattr,name=systemd
+2:27 / /sys/fs/cgroup/unified rw,nosuid,nodev,noexec,relatime master:60 - cgroup2 cgroup rw,nsdelegate
+2:28 / /sys/fs/fuse/connections rw,relatime master:61 - fusectl fusectl rw
+2:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime master:62 - pstore pstore rw
+2:30 / /sys/kernel/config rw,relatime master:63 - configfs configfs rw
+2:31 / /sys/kernel/debug rw,relatime master:64 - debugfs debugfs rw
+2:32 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime master:65 - securityfs securityfs rw
+2:33 / /tmp rw,relatime master:66 - tmpfs tmpfs rw
+2:33 /snap.test-snapd-mountinfo-core18/tmp /tmp rw,relatime - tmpfs tmpfs rw
+0:4 /usr/lib/snapd /usr/lib/snapd ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
+2:36 / /usr/share/gdb rw,relatime - tmpfs tmpfs rw,mode=755
+0:0 /usr/share/gdb/auto-load /usr/share/gdb/auto-load ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
+2:37 / /usr/share/gdb/test rw,relatime - tmpfs tmpfs rw
+0:0 /usr/src /usr/src ro,relatime master:1 - squashfs /dev/loop0 ro
+1:1 /system-data/var/lib/extrausers /var/lib/extrausers rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/snapd /var/lib/snapd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:0 / /var/lib/snapd/hostfs ro,relatime master:1 - squashfs /dev/loop0 ro
+1:1 /system-data/var/lib/snapd/hostfs /var/lib/snapd/hostfs rw,relatime - ext4 /dev/sda3 rw,data=ordered
+1:0 / /var/lib/snapd/hostfs/boot/efi rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:0 /EFI/ubuntu /var/lib/snapd/hostfs/boot/grub rw,relatime master:2 - vfat /dev/sda2 rw,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
+1:1 /system-data/etc/apparmor.d/cache /var/lib/snapd/hostfs/etc/apparmor.d/cache rw,relatime master:8 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/cloud /var/lib/snapd/hostfs/etc/cloud rw,relatime master:9 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/dbus-1/system.d /var/lib/snapd/hostfs/etc/dbus-1/system.d rw,relatime master:10 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/default/swapfile /var/lib/snapd/hostfs/etc/default/swapfile rw,relatime master:11 - ext4 /dev/sda3 rw,data=ordered
+2:5 /image.fstab /var/lib/snapd/hostfs/etc/fstab rw,nosuid,noexec,relatime master:12 - tmpfs tmpfs rw,mode=755
+1:1 /system-data/root/test-etc/group /var/lib/snapd/hostfs/etc/group ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/gshadow /var/lib/snapd/hostfs/etc/gshadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/hosts /var/lib/snapd/hostfs/etc/hosts rw,relatime master:14 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/iproute2 /var/lib/snapd/hostfs/etc/iproute2 rw,relatime master:15 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/machine-id /var/lib/snapd/hostfs/etc/machine-id rw,relatime master:16 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modprobe.d /var/lib/snapd/hostfs/etc/modprobe.d rw,relatime master:17 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/modules-load.d /var/lib/snapd/hostfs/etc/modules-load.d rw,relatime master:18 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/netplan /var/lib/snapd/hostfs/etc/netplan rw,relatime master:19 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/network/if-up.d /var/lib/snapd/hostfs/etc/network/if-up.d rw,relatime master:20 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/passwd /var/lib/snapd/hostfs/etc/passwd ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/root/test-etc/shadow /var/lib/snapd/hostfs/etc/shadow ro,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/ssh /var/lib/snapd/hostfs/etc/ssh rw,relatime master:21 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sudoers.d /var/lib/snapd/hostfs/etc/sudoers.d rw,relatime master:22 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/sysctl.d /var/lib/snapd/hostfs/etc/sysctl.d rw,relatime master:23 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd /var/lib/snapd/hostfs/etc/systemd rw,relatime master:24 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/systemd/system /var/lib/snapd/hostfs/etc/systemd/system rw,relatime master:25 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/udev/rules.d /var/lib/snapd/hostfs/etc/udev/rules.d rw,relatime master:26 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/etc/writable /var/lib/snapd/hostfs/etc/writable rw,relatime master:27 - ext4 /dev/sda3 rw,data=ordered
+1:1 /user-data /var/lib/snapd/hostfs/home rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:1 /firmware /var/lib/snapd/hostfs/lib/firmware ro,relatime master:28 - squashfs /dev/loop1 ro
+0:1 /modules /var/lib/snapd/hostfs/lib/modules ro,relatime master:29 - squashfs /dev/loop1 ro
+2:6 / /var/lib/snapd/hostfs/media rw,relatime master:30 - tmpfs tmpfs rw
+2:7 / /var/lib/snapd/hostfs/mnt rw,relatime master:31 - tmpfs tmpfs rw
+1:1 /system-data/root /var/lib/snapd/hostfs/root rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+2:5 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:35 - tmpfs tmpfs rw,mode=755
+2:10 / /var/lib/snapd/hostfs/run rw,nosuid,noexec,relatime master:34 - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:11 / /var/lib/snapd/hostfs/run/lock rw,nosuid,nodev,noexec,relatime master:36 - tmpfs tmpfs rw,size=VARIABLE
+2:10 /snapd/ns /var/lib/snapd/hostfs/run/snapd/ns rw,nosuid,noexec,relatime - tmpfs tmpfs rw,size=VARIABLE,mode=755
+2:12 / /var/lib/snapd/hostfs/run/user/0 rw,nosuid,nodev,relatime master:37 - tmpfs tmpfs rw,size=VARIABLE,mode=700
+1:1 /system-data/snap /var/lib/snapd/hostfs/snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /var/lib/snapd/hostfs/snap/core/1 ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
+0:0 / /var/lib/snapd/hostfs/snap/core18/1 ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
+0:1 / /var/lib/snapd/hostfs/snap/pc-kernel/1 ro,nodev,relatime master:40 - squashfs /dev/loop1 ro
+0:3 / /var/lib/snapd/hostfs/snap/pc/1 ro,nodev,relatime master:41 - squashfs /dev/loop3 ro
+0:4 / /var/lib/snapd/hostfs/snap/snapd/1 ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
+0:5 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:43 - squashfs /dev/loop5 ro
+0:6 / /var/lib/snapd/hostfs/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:44 - squashfs /dev/loop6 ro
+0:7 / /var/lib/snapd/hostfs/snap/test-snapd-rsync-core18/1 ro,nodev,relatime master:45 - squashfs /dev/loop7 ro
+2:33 / /var/lib/snapd/hostfs/tmp rw,relatime master:66 - tmpfs tmpfs rw
+0:4 /usr/lib/snapd /var/lib/snapd/hostfs/usr/lib/snapd ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
+1:1 /system-data/var/cache /var/lib/snapd/hostfs/var/cache rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/cloud /var/lib/snapd/hostfs/var/lib/cloud rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/console-conf /var/lib/snapd/hostfs/var/lib/console-conf rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/dbus /var/lib/snapd/hostfs/var/lib/dbus rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/dhcp /var/lib/snapd/hostfs/var/lib/dhcp rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/extrausers /var/lib/snapd/hostfs/var/lib/extrausers rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/misc /var/lib/snapd/hostfs/var/lib/misc rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/private/systemd /var/lib/snapd/hostfs/var/lib/private/systemd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/lib/snapd /var/lib/snapd/hostfs/var/lib/snapd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+2:34 / /var/lib/snapd/hostfs/var/lib/sudo rw,relatime master:67 - tmpfs tmpfs rw,mode=700
+1:1 /system-data/var/lib/systemd /var/lib/snapd/hostfs/var/lib/systemd rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/log /var/lib/snapd/hostfs/var/log rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/snap /var/lib/snapd/hostfs/var/snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/tmp /var/lib/snapd/hostfs/var/tmp rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 / /var/lib/snapd/hostfs/writable rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+0:2 / /var/lib/snapd/hostfs/writable/system-data/snap/core/1 ro,nodev,relatime master:38 - squashfs /dev/loop2 ro
+0:0 / /var/lib/snapd/hostfs/writable/system-data/snap/core18/1 ro,nodev,relatime master:39 - squashfs /dev/loop0 ro
+0:1 / /var/lib/snapd/hostfs/writable/system-data/snap/pc-kernel/1 ro,nodev,relatime master:40 - squashfs /dev/loop1 ro
+0:3 / /var/lib/snapd/hostfs/writable/system-data/snap/pc/1 ro,nodev,relatime master:41 - squashfs /dev/loop3 ro
+0:4 / /var/lib/snapd/hostfs/writable/system-data/snap/snapd/1 ro,nodev,relatime master:42 - squashfs /dev/loop4 ro
+0:5 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core16/1 ro,nodev,relatime master:43 - squashfs /dev/loop5 ro
+0:6 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-mountinfo-core18/1 ro,nodev,relatime master:44 - squashfs /dev/loop6 ro
+0:7 / /var/lib/snapd/hostfs/writable/system-data/snap/test-snapd-rsync-core18/1 ro,nodev,relatime master:45 - squashfs /dev/loop7 ro
+1:1 /system-data/var/log /var/log rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/snap /var/snap rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
+1:1 /system-data/var/tmp /var/tmp rw,relatime master:13 - ext4 /dev/sda3 rw,data=ordered
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/task.yaml snapd-2.42.1+19.10/tests/main/mount-ns/task.yaml
--- snapd-2.41+19.10.1/tests/main/mount-ns/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -1,9 +1,17 @@
summary: The shape of the mount namespace on classic systems for non-classic snaps
-systems: [ubuntu-16.04-64, ubuntu-18.04-64, ubuntu-core-16-64, ubuntu-core-18-64]
+systems: [ubuntu-16.04-64, ubuntu-18.04-64]
# The test itself works perfectly fine but in conjunction with our leaky test
# suite it often fails because it detects cruft left over by other tests in a
-# way that was not detected before.
+# way that was not detected before. Classic systems should be clear of mount
+# side-effects now. The test should be _eventually_ enabled on
+# ubuntu-core-16-64 and ubuntu-core-18-64.
+
+# set to manual in release/2.42 because core has changed and fixing
+# the tests would require larger changes, i.e.
+# https://github.com/snapcore/snapd/pull/7676 so it seems more expedient
+# disable this test here
manual: true
+
# The test is sensitive to backend type, which designates the used image.
# Backends are enabled one-by-one along with the matching data set.
backends: [google]
@@ -50,7 +58,35 @@
If you see this test randomly failing it may be because it has observed
state leaked by another test that ran on the same machine earlier in the
spread execution chain.
+environment:
+ MACHINE_STATE/inherit: inherit
+ MACHINE_STATE/reboot: reboot
prepare: |
+ case "$MACHINE_STATE" in
+ inherit)
+ # The test will run with whatever the machine state was originally.
+ true
+ ;;
+ reboot)
+ # TODO: when https://github.com/snapcore/spread/pull/85 is merged
+ # and released this test can be allowed to run on bash 4.3. Without
+ # the workaround for a bug in bash REBOOT causes the spread test to
+ # fail instead of asking spread to reboot the machine.
+ if version-tool --strict "$(echo "$BASH_VERSION" | cut -d. -f 1-2)" -eq 4.3; then
+ echo "SKIP: this test cannot operate on bash 4.3.x"
+ touch please-skip-this-test
+ exit 0
+ fi
+ #
+ # The test will reboot once before performing the test. This will
+ # remove any ephemeral state that may be left in the kernel by prior
+ # test cases or by project-wide prepare that is does not persist across
+ # boots.
+ if [ "$SPREAD_REBOOT" -eq 0 ]; then
+ REBOOT
+ fi
+ ;;
+ esac
# The --renumber and --rename options renumber and rename various
# non-deterministic elements of the mount table. The --ref-x1000 option
# sets a multiple of 1000 as the base value for allocated renumbered
@@ -75,6 +111,7 @@
--display-order mount_point \
--display-order mount_source \
--display-order fs_type \
+ .dev .root_dir .mount_point .mount_opts .opt_fields .fs_type .mount_source .sb_opts \
"$@"
}
@@ -130,6 +167,9 @@
snap remove test-snapd-mountinfo-core16
snap remove test-snapd-mountinfo-core18
execute: |
+ if [ -e please-skip-this-test ]; then
+ exit 0
+ fi
diff -u "$SPREAD_BACKEND.$SPREAD_SYSTEM/HOST.expected.txt" HOST.deterministic.txt
diff -u "$SPREAD_BACKEND.$SPREAD_SYSTEM/PER-SNAP-16.expected.txt" PER-SNAP-16.deterministic.txt
diff -u "$SPREAD_BACKEND.$SPREAD_SYSTEM/PER-USER-16.expected.txt" PER-USER-16.deterministic.txt
@@ -144,6 +184,7 @@
rm -f test-snapd-mountinfo-classic_1_all.snap
rm -f test-snapd-mountinfo-core16_1_all.snap
rm -f test-snapd-mountinfo-core18-1_all.snap
+ rm -f please-skip-this-test
debug: |
for fname in ./*.deterministic.txt; do
echo
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/test-snapd-mountinfo-core16/meta/snap.yaml snapd-2.42.1+19.10/tests/main/mount-ns/test-snapd-mountinfo-core16/meta/snap.yaml
--- snapd-2.41+19.10.1/tests/main/mount-ns/test-snapd-mountinfo-core16/meta/snap.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/test-snapd-mountinfo-core16/meta/snap.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -4,6 +4,14 @@
architecture: [all]
plugs:
mount-observe:
+# This layout is designed to create a writable mimic on top of production base
+# snap, in this case core, without being extremely painful to analyze. The core
+# snap contains the directory /usr/share/gdb/auto-load which will be re-created
+# by the mimic at /usr/share/gdb. This allows us to have a test with just one
+# re-created element and without engineering a custom core for this test.
+layout:
+ /usr/share/gdb/test:
+ type: tmpfs
apps:
test-snapd-mountinfo-core16:
command: bin/mountinfo
diff -Nru snapd-2.41+19.10.1/tests/main/mount-ns/test-snapd-mountinfo-core18/meta/snap.yaml snapd-2.42.1+19.10/tests/main/mount-ns/test-snapd-mountinfo-core18/meta/snap.yaml
--- snapd-2.41+19.10.1/tests/main/mount-ns/test-snapd-mountinfo-core18/meta/snap.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/mount-ns/test-snapd-mountinfo-core18/meta/snap.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -5,6 +5,10 @@
base: core18
plugs:
mount-observe:
+# Please see the comment in test-snapd-mountinfo-core16 snap for rationale.
+layout:
+ /usr/share/gdb/test:
+ type: tmpfs
apps:
test-snapd-mountinfo-core18:
command: bin/mountinfo
diff -Nru snapd-2.41+19.10.1/tests/main/nfs-support/task.yaml snapd-2.42.1+19.10/tests/main/nfs-support/task.yaml
--- snapd-2.41+19.10.1/tests/main/nfs-support/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/nfs-support/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -19,16 +19,29 @@
. "$TESTSLIB/snaps.sh"
install_local test-snapd-sh
+ # If /proc/fs/nfsd is not initially mounted then ask the test to unmount it later.
+ if not mountinfo-tool /proc/fs/nfsd .fs_type=nfsd; then
+ touch /tmp/please-unmount-nfsd
+ echo "the test needs to unmount /proc/fs/nfsd if it becomes mounted"
+ fi
+ # If /var/lib/nfs/rpc_pipefs is not initially mounted then ask the test to unmount it later.
+ if not mountinfo-tool /var/lib/nfs/rpc_pipefs .fs_type=rpc_pipefs; then
+ touch /tmp/please-unmount-rpc-pipefs
+ echo "the test needs to unmount /var/lib/nfs/rpc_pipefs if it becomes mounted"
+ fi
+
restore: |
#shellcheck source=tests/lib/pkgdb.sh
. "$TESTSLIB/pkgdb.sh"
# Unmount NFS mount over /home if one exists.
- umount /home || true
+ if mountinfo-tool /home; then
+ umount /home
+ fi
# Restore the fstab backup file if one exists.
- if [ -e fstab.orig ]; then
- mv fstab.orig /etc/fstab
+ if [ -e /tmp/fstab.orig ]; then
+ mv /tmp/fstab.orig /etc/fstab
fi
# Remove the NFS server and its configuration data.
@@ -41,6 +54,46 @@
systemctl reset-failed snapd.service
systemctl start snapd.service
+ # Depending on OS do cleanup appropriate for the host.
+ case "$SPREAD_SYSTEM" in
+ ubuntu-14.04-*)
+ # On Ubuntu 14.04 we started the NFS server so stop it here. On
+ # other versions of Ubuntu the server was pre-installed and
+ # running so we don't have to stop it.
+ service nfs-kernel-server stop
+ ;;
+ arch-*)
+ # The nfsdcld service may keep rpc_pipefs busy, as seen in this output from lsof.
+ # nfsdcld 5736 root 10u FIFO 0,47 0t0 114 /var/lib/nfs/rpc_pipefs/nfsd/cld (deleted)
+ systemctl stop nfsdcld.service
+ systemctl stop nfs-server.service
+ systemctl disable nfs-server.service
+ ;;
+ amazon-*|centos-*)
+ systemctl stop nfs
+ systemctl disable nfs
+ ;;
+ esac
+ if [ -e /tmp/please-unmount-nfsd ]; then
+ if mountinfo-tool /proc/fs/nfsd .fs_type=nfsd; then
+ umount /proc/fs/nfsd
+ fi
+ rm -f /tmp/please-unmount-nfsd
+ fi
+ if [ -e /tmp/please-unmount-rpc-pipefs ]; then
+ if mountinfo-tool /var/lib/nfs/rpc_pipefs .fs_type=rpc_pipefs; then
+ umount /var/lib/nfs/rpc_pipefs
+ fi
+ rm -f /tmp/please-unmount-rpc-pipefs
+ fi
+ # If the system originally had NFS installed, restore the status after
+ # changes made in switch-case above.
+ case "$SPREAD_SYSTEM" in
+ ubuntu-14.04-*)
+ service nfs-kernel-server start
+ ;;
+ esac
+
execute: |
# only needed because we do it 11 times (!)
restart_snapd() {
@@ -65,7 +118,7 @@
# Export /home over NFS.
mkdir -p /etc/exports.d/
echo '/home localhost(rw,no_subtree_check,no_root_squash)' > /etc/exports.d/test.exports
-
+
# Make sure the nfs service is running
case "$SPREAD_SYSTEM" in
ubuntu-14.04-*)
@@ -75,8 +128,9 @@
systemctl restart nfs-kernel-server
;;
fedora-*)
- # Enable udp protocol for nfs on fedora which is disable by default
+ # Enable udp protocol for nfs on fedora which is disabled by default
sed -i -e 's/RPCNFSDARGS=.*/RPCNFSDARGS="--udp"/g' /etc/sysconfig/nfs
+ # FIXME: this is not restored anywhere.
systemctl restart nfs
;;
arch-*)
@@ -176,7 +230,7 @@
ensure_normal_perms
# Back up the /etc/fstab file and define a NFS mount mount there.
- cp -a /etc/fstab fstab.orig
+ cp -a /etc/fstab /tmp/fstab.orig
echo 'localhost:/home /home nfs defaults 0 0' >> /etc/fstab
# Restart snapd and ensure that we have extra permissions again.
diff -Nru snapd-2.41+19.10.1/tests/main/non-home/task.yaml snapd-2.42.1+19.10/tests/main/non-home/task.yaml
--- snapd-2.41+19.10.1/tests/main/non-home/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/non-home/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -13,7 +13,7 @@
adduser --home "$THOME" "$TUSER"
restore: |
- deluser --remove-home "$TUSER"
+ user-tool remove-with-group "$TUSER"
execute: |
echo "Install a snap"
diff -Nru snapd-2.41+19.10.1/tests/main/parallel-install-basic/task.yaml snapd-2.42.1+19.10/tests/main/parallel-install-basic/task.yaml
--- snapd-2.41+19.10.1/tests/main/parallel-install-basic/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/parallel-install-basic/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -81,5 +81,5 @@
su -l -c "test-snapd-tools_foo.cmd sh -c 'cat \$SNAP_USER_COMMON/canary'" test | MATCH canary-instance-common
su -l -c "test-snapd-tools_foo.cmd sh -c 'cat \$SNAP_USER_DATA/canary'" test | MATCH canary-instance-snap
-restore:
+restore: |
snap set system experimental.parallel-instances=null
diff -Nru snapd-2.41+19.10.1/tests/main/parallel-install-common-dirs/task.yaml snapd-2.42.1+19.10/tests/main/parallel-install-common-dirs/task.yaml
--- snapd-2.41+19.10.1/tests/main/parallel-install-common-dirs/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/parallel-install-common-dirs/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -80,5 +80,5 @@
not test -d "$SNAP_MOUNT_DIR/test-snapd-tools"
not test -d "/var/snap/test-snapd-tools"
-restore:
+restore: |
snap set system experimental.parallel-instances=null
diff -Nru snapd-2.41+19.10.1/tests/main/parallel-install-common-dirs-undo/task.yaml snapd-2.42.1+19.10/tests/main/parallel-install-common-dirs-undo/task.yaml
--- snapd-2.41+19.10.1/tests/main/parallel-install-common-dirs-undo/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/parallel-install-common-dirs-undo/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -33,5 +33,5 @@
not test -d "$SNAP_MOUNT_DIR/test-snapd-service"
not test -d "/var/snap/test-snapd-service"
-restore:
+restore: |
snap set system experimental.parallel-instances=null
diff -Nru snapd-2.41+19.10.1/tests/main/parallel-install-desktop/task.yaml snapd-2.42.1+19.10/tests/main/parallel-install-desktop/task.yaml
--- snapd-2.41+19.10.1/tests/main/parallel-install-desktop/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/parallel-install-desktop/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -41,5 +41,5 @@
snap remove basic-desktop
test -f /var/lib/snapd/desktop/applications/basic-desktop+longname_echo.desktop
-restore:
+restore: |
snap set system experimental.parallel-instances=null
diff -Nru snapd-2.41+19.10.1/tests/main/parallel-install-snap-icons/task.yaml snapd-2.42.1+19.10/tests/main/parallel-install-snap-icons/task.yaml
--- snapd-2.41+19.10.1/tests/main/parallel-install-snap-icons/task.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/parallel-install-snap-icons/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,37 @@
+summary: Parallel installed snaps have non-conflicting icons
+
+restore: |
+ snap unset system experimental.parallel-instances
+
+execute: |
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
+
+ echo "Install a snap providing icons"
+ install_local test-snapd-icon-theme
+
+ echo "Install additional instances of the snap"
+ snap set system experimental.parallel-instances=true
+ install_local_as test-snapd-icon-theme test-snapd-icon-theme_longname
+ install_local_as test-snapd-icon-theme test-snapd-icon-theme_foo
+
+ echo "Each instance provides its own icons"
+ icondir=/var/lib/snapd/desktop/icons/hicolor/scalable/apps
+ [ -f "$icondir/snap.test-snapd-icon-theme.foo.svg" ]
+ [ -f "$icondir/snap.test-snapd-icon-theme_longname.foo.svg" ]
+ [ -f "$icondir/snap.test-snapd-icon-theme_foo.foo.svg" ]
+
+ echo "Each instance's desktop file references its own icon"
+ desktopdir=/var/lib/snapd/desktop/applications
+ MATCH '^Icon=snap.test-snapd-icon-theme.foo$' < "$desktopdir/test-snapd-icon-theme_echo.desktop"
+ MATCH '^Icon=snap.test-snapd-icon-theme_longname.foo$' < "$desktopdir/test-snapd-icon-theme+longname_echo.desktop"
+ MATCH '^Icon=snap.test-snapd-icon-theme_foo.foo$' < "$desktopdir/test-snapd-icon-theme+foo_echo.desktop"
+
+ echo "Removing once instance does not remove the other instances' icons"
+ snap remove test-snapd-icon-theme_foo
+ [ -f "$icondir/snap.test-snapd-icon-theme.foo.svg" ]
+ [ -f "$icondir/snap.test-snapd-icon-theme_longname.foo.svg" ]
+ [ ! -f "$icondir/snap.test-snapd-icon-theme_foo.foo.svg" ]
+
+ snap remove test-snapd-icon-theme
+ [ -f "$icondir/snap.test-snapd-icon-theme_longname.foo.svg" ]
diff -Nru snapd-2.41+19.10.1/tests/main/prepare-image-grub-core18/task.yaml snapd-2.42.1+19.10/tests/main/prepare-image-grub-core18/task.yaml
--- snapd-2.41+19.10.1/tests/main/prepare-image-grub-core18/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/prepare-image-grub-core18/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -11,14 +11,14 @@
restore: |
rm -rf "$ROOT"
-
+
execute: |
echo Running prepare-image
- su -c "SNAPPY_USE_STAGING_STORE=$SNAPPY_USE_STAGING_STORE snap prepare-image --channel edge --snap test-snapd-tools $TESTSLIB/assertions/ubuntu-core-18-amd64.model $ROOT" test
+ su -c "SNAPPY_USE_STAGING_STORE=$SNAPPY_USE_STAGING_STORE snap prepare-image --channel edge --snap test-snapd-tools-core18 $TESTSLIB/assertions/ubuntu-core-18-amd64.model $ROOT" test
echo Verifying the result
ls -lR "$IMAGE"
- for f in pc pc-kernel core18 snapd test-snapd-tools; do
+ for f in pc pc-kernel core18 snapd test-snapd-tools-core18; do
ls "$IMAGE"/var/lib/snapd/seed/snaps/"${f}"*.snap
done
MATCH snap_core=core18 < "$IMAGE"/boot/grub/grubenv
diff -Nru snapd-2.41+19.10.1/tests/main/proxy-no-core/task.yaml snapd-2.42.1+19.10/tests/main/proxy-no-core/task.yaml
--- snapd-2.41+19.10.1/tests/main/proxy-no-core/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/proxy-no-core/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -52,4 +52,4 @@
# shellcheck source=tests/lib/journalctl.sh
. "$TESTSLIB/journalctl.sh"
- check_journalctl_log 'CONNECT fastly.cdn.snapcraft.io' -u tinyproxy
+ check_journalctl_log 'CONNECT fastly.*.cdn.snapcraft.io' -u tinyproxy
diff -Nru snapd-2.41+19.10.1/tests/main/remodel/task.yaml snapd-2.42.1+19.10/tests/main/remodel/task.yaml
--- snapd-2.41+19.10.1/tests/main/remodel/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/remodel/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -74,10 +74,7 @@
#shellcheck source=tests/lib/systems.sh
. "$TESTSLIB"/systems.sh
- SNAP=test-snapd-tools
- if is_core18_system; then
- SNAP=test-snapd-tools-core18
- fi
+ SNAP="$(get_snap_for_system test-snapd-tools)"
# sanity check
not snap list "$SNAP"
diff -Nru snapd-2.41+19.10.1/tests/main/remodel-kernel/task.yaml snapd-2.42.1+19.10/tests/main/remodel-kernel/task.yaml
--- snapd-2.41+19.10.1/tests/main/remodel-kernel/task.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/remodel-kernel/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,145 @@
+summary: |
+ Test a remodel that switches to a new kernel
+
+environment:
+ OLD_KERNEL: pc-kernel
+ NEW_KERNEL: test-snapd-pc-kernel
+
+# FIXME: add core18 test as well
+systems: [ubuntu-core-16-64]
+
+prepare: |
+ if [ "$TRUST_TEST_KEYS" = "false" ]; then
+ echo "This test needs test keys to be trusted"
+ exit
+ fi
+ #shellcheck source=tests/lib/systemd.sh
+ . "$TESTSLIB"/systemd.sh
+ systemctl stop snapd.service snapd.socket
+ rm -rf /var/lib/snapd/assertions/*
+ rm -rf /var/lib/snapd/device
+ rm -rf /var/lib/snapd/state.json
+ mv /var/lib/snapd/seed/assertions/model model.bak
+ cp "$TESTSLIB"/assertions/developer1.account /var/lib/snapd/seed/assertions
+ cp "$TESTSLIB"/assertions/developer1.account-key /var/lib/snapd/seed/assertions
+ cp "$TESTSLIB"/assertions/developer1-pc.model /var/lib/snapd/seed/assertions
+ cp "$TESTSLIB"/assertions/testrootorg-store.account-key /var/lib/snapd/seed/assertions
+ # kick first boot again
+ systemctl start snapd.service snapd.socket
+ while ! snap changes | grep -q "Done.*Initialize system state"; do sleep 1; done
+
+restore: |
+ if [ "$TRUST_TEST_KEYS" = "false" ]; then
+ echo "This test needs test keys to be trusted"
+ exit
+ fi
+
+ #shellcheck source=tests/lib/systemd.sh
+ . "$TESTSLIB"/systemd.sh
+ systemctl stop snapd.service snapd.socket
+ rm -rf /var/lib/snapd/assertions/*
+ rm -rf /var/lib/snapd/device
+ rm -rf /var/lib/snapd/state.json
+
+ rm -f /var/lib/snapd/seed/assertions/developer1.account
+ rm -f /var/lib/snapd/seed/assertions/developer1.account-key
+ rm -f /var/lib/snapd/seed/assertions/developer1-pc.model
+ rm -f /var/lib/snapd/seed/assertions/testrootorg-store.account-key
+ cp model.bak /var/lib/snapd/seed/assertions/model
+ rm -f ./*.bak
+ # kick first boot again
+ systemctl start snapd.service snapd.socket
+ # wait for first boot to be done
+ snap wait system seed.loaded
+ while ! snap changes | grep -q "Done.*Initialize system state"; do sleep 1; done
+ # extra paranoia because failure to cleanup earlier took us a long time
+ # to find
+ if [ -e /var/snap/$NEW_KERNEL/current ]; then
+ echo "Leftover $NEW_KERNEL data dir found, test does not "
+ echo "properly cleanup"
+ echo "see https://github.com/snapcore/snapd/pull/6620"
+ echo
+ find /var/snap
+ exit 1
+ fi
+
+execute: |
+ if [ "$TRUST_TEST_KEYS" = "false" ]; then
+ echo "This test needs test keys to be trusted"
+ exit
+ fi
+
+ #shellcheck source=tests/lib/boot.sh
+ . "$TESTSLIB"/boot.sh
+
+ wait_change_done() {
+ chg_summary="$1"
+ for _ in $(seq 10); do
+ if snap changes | grep -qE "[0-9]+\ +Done\ +.* $chg_summary"; then
+ break
+ fi
+ # some debug output
+ snap changes
+ # wait a bit
+ sleep 5
+ done
+ snap changes | MATCH "$chg_summary"
+ }
+
+ # initial boot with the current model
+ if [ "$SPREAD_REBOOT" = 0 ]; then
+ # sanity check
+ snap list "$OLD_KERNEL"
+
+ echo "We have the right model assertion"
+ snap debug model|MATCH "model: my-model"
+
+ echo "Now we remodel"
+ snap remodel "$TESTSLIB"/assertions/developer1-pc-new-kernel.model
+
+ echo "Double check that we boot into the right kernel"
+ grub-editenv list | MATCH "snap_try_kernel=$NEW_KERNEL"
+
+ echo "reboot to finish the change"
+ REBOOT
+ fi
+
+ # first boot with the new model kernel
+ if [ "$SPREAD_REBOOT" = 1 ]; then
+ echo "and we have the new kernel snap installed"
+ snap list "$NEW_KERNEL"
+
+ echo "And are using it"
+ wait_core_post_boot
+ grub-editenv list | MATCH "snap_kernel=$NEW_KERNEL"
+
+ echo "and we got the new model assertion"
+ wait_change_done "Refresh model assertion from revision 0 to 2"
+ snap debug model|MATCH "revision: 2"
+
+ echo "and we cannot remove the kernel snap"
+ not snap remove "$NEW_KERNEL"
+
+ # TODO: test when keeping the old kernel
+ echo "but we can remove the old kernel"
+ snap remove "$OLD_KERNEL"
+
+ echo "And we can remodel again and remove the new kernel"
+ snap remodel "$TESTSLIB"/assertions/developer1-pc-revno3.model
+ REBOOT
+ fi
+
+ # reboot from new model to undo the new model again (to not pollute tests)
+ if [ "$SPREAD_REBOOT" = 2 ]; then
+ wait_core_post_boot
+ grub-editenv list | MATCH "snap_kernel=$OLD_KERNEL"
+
+ wait_change_done "Refresh model assertion from revision 2 to 3"
+ snap debug model|MATCH "revision: 3"
+ echo "cleanup"
+ snap remove "$NEW_KERNEL"
+
+ echo "Ensure we are back to the original kernel channel and kernel"
+ snap refresh --channel="$KERNEL_CHANNEL" "$OLD_KERNEL"
+ REBOOT
+ fi
diff -Nru snapd-2.41+19.10.1/tests/main/retry-tool/task.yaml snapd-2.42.1+19.10/tests/main/retry-tool/task.yaml
--- snapd-2.41+19.10.1/tests/main/retry-tool/task.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/retry-tool/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,15 @@
+summary: smoke test for the retry test tool
+execute: |
+ # Retry runs the command that was passed as argument and returns the exit
+ # code of that command.
+ retry-tool true
+ not retry-tool -n 1 false
+ # If the command doesn't exist it is not re-tried multiple times.
+ ( not retry-tool this-command-does-not-exist ) 2>&1 \
+ | grep -F 'retry: cannot execute command this-command-does-not-exist: [Errno 2] No such file or directory'
+ # On failure it tells us about it, showing progress.
+ retry-tool -n 2 --wait 0.1 false 2>&1 | grep -F "retry: command false failed with code 1"
+ retry-tool -n 2 --wait 0.1 false 2>&1 | grep -F "retry: next attempt in 0.1 second(s) (attempt 1 of 2)"
+ retry-tool -n 2 --wait 0.1 false 2>&1 | grep -F "retry: command false keeps failing after 2 attempts"
+ # Though all output is removed with the --quiet switch.
+ test "$(retry-tool -n 2 --wait 0.1 --quiet false 2>&1 | wc -l)" -eq 0
diff -Nru snapd-2.41+19.10.1/tests/main/sbuild/task.yaml snapd-2.42.1+19.10/tests/main/sbuild/task.yaml
--- snapd-2.41+19.10.1/tests/main/sbuild/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/sbuild/task.yaml 1970-01-01 00:00:00.000000000 +0000
@@ -1,46 +0,0 @@
-summary: Ensure snapd builds correctly in sbuild
-
-# takes a while
-priority: 500
-
-environment:
- BUILD_MODE/normal: normal
- BUILD_MODE/any: any
-
-manual: true
-
-systems: [debian-sid-*]
-
-execute: |
- echo "Create a sid sbuild env"
- eatmydata sbuild-createchroot --include=eatmydata,ccache,gnupg sid /srv/chroot/sid-amd64-sbuild http://deb.debian.org/debian
-
- echo "Allow test user to run sbuild"
- sbuild-adduser test
-
- BUILD_PARAM=""
- if [ "$BUILD_MODE" == "any" ]; then
- BUILD_PARAM="--arch-any"
- fi
-
- echo "Build mode: $BUILD_MODE"
- su -c "sbuild $BUILD_PARAM -d sid --run-autopkgtest $SPREAD_PATH/../*.dsc" test
-
-restore: |
- rm --recursive --one-file-system /srv/chroot/sid-amd64-sbuild
- rm -f /etc/schroot/chroot.d/sid-amd64-sbuild-*
-
-debug: |
- # Test that there's a log file and a symbolic link pointing to it.
- # The non-symlink has a time-stamp and we can match on the "Z" timezone
- # marker to find it.
- test "$(find . -maxdepth 1 -name '*Z.build' | wc -l)" -ge 1 && tail -n 100 ./*Z.build
- cat <&1 | MATCH 'no matches'
diff -Nru snapd-2.41+19.10.1/tests/main/snap-confine/task.yaml snapd-2.42.1+19.10/tests/main/snap-confine/task.yaml
--- snapd-2.41+19.10.1/tests/main/snap-confine/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/snap-confine/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -42,12 +42,11 @@
not test -f /run/udev/tags/snap_test-snapd-tools_echo/+module:nvidia_modeset
echo "Ensure apparmor profile for snap-confine is parsable"
- for f in /etc/apparmor.d/usr.lib.snapd.snap-confine*; do
+ while IFS= read -r -d '' file; do
if command -v apparmor_parser 2>/dev/null; then
- apparmor_parser -QTK "$f"
+ apparmor_parser -QTK "$file"
fi
if command -v aa-enforce 2>/dev/null; then
- aa-enforce "$f"
+ aa-enforce "$file"
fi
- done
-
+ done < <(find /etc/apparmor.d -maxdepth 1 -name 'usr.lib.snapd.snap-confine*')
diff -Nru snapd-2.41+19.10.1/tests/main/snap-core-symlinks/task.yaml snapd-2.42.1+19.10/tests/main/snap-core-symlinks/task.yaml
--- snapd-2.41+19.10.1/tests/main/snap-core-symlinks/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/snap-core-symlinks/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -1,12 +1,20 @@
summary: Check that the seed symlinks work
-systems: [ubuntu-core-16-*]
+systems: [ubuntu-core-1*]
execute: |
- echo "Ensure that the core snap is a symlink into the seed"
- core_symlink="$(readlink -f /var/lib/snapd/snaps/core_*.snap)"
+ # shellcheck source=tests/lib/systems.sh
+ . "$TESTSLIB/systems.sh"
+
+ TARGET_SNAP=core
+ if is_core18_system; then
+ TARGET_SNAP=core18
+ fi
+
+ echo "Ensure that the $TARGET_SNAP snap is a symlink into the seed"
+ core_symlink="$(readlink -f /var/lib/snapd/snaps/${TARGET_SNAP}_*.snap)"
if [[ "${core_symlink}" != /var/lib/snapd/seed/snaps/* ]]; then
- echo "The initial core snap should symlink into the seed directory"
+ echo "The initial $TARGET_SNAP snap should symlink into the seed directory"
echo "but it does not."
exit 1
fi
diff -Nru snapd-2.41+19.10.1/tests/main/snap-debug-state/task.yaml snapd-2.42.1+19.10/tests/main/snap-debug-state/task.yaml
--- snapd-2.41+19.10.1/tests/main/snap-debug-state/task.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/snap-debug-state/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,35 @@
+summary: Ensure `snap debug changes|tasks|task` commands work
+
+prepare: |
+ snap install hello-world
+ # The debug commands access state.json directly, snapd should be stopped
+ systemctl stop snapd.{socket,service}
+
+ if [[ "$SPREAD_SYSTEM" == ubuntu-1* ]]; then
+ apt install -y graphviz
+ fi
+
+restore: |
+ systemctl start snapd.{socket,service}
+ rm -f out.png
+
+execute: |
+ echo "Changes can be listed"
+ snap debug state --changes /var/lib/snapd/state.json | MATCH "seed .*Initialize system state"
+ snap debug state /var/lib/snapd/state.json | MATCH "seed .*Initialize system state"
+
+ echo "Snap changes defaults to state.json in the current directory"
+ cd /var/lib/snapd
+ snap debug state --changes | MATCH "install-snap .*Install \"hello-world\" snap"
+
+ echo "Tasks can be listed"
+ snap debug state --change=1 /var/lib/snapd/state.json | MATCH "mark-seeded .*Mark system seeded"
+
+ echo "Individual task can be examined"
+ snap debug state --task=1 /var/lib/snapd/state.json | MATCH "kind: mark-seeded"
+ snap debug state --task=1 /var/lib/snapd/state.json | MATCH "summary: Mark system seeded"
+
+ # sanity check, dot shouldn't fail
+ if [[ "$SPREAD_SYSTEM" == ubuntu-1* ]]; then
+ snap debug state --change=1 --dot /var/lib/snapd/state.json | dot -Tpng > out.png
+ fi
diff -Nru snapd-2.41+19.10.1/tests/main/snapd-reexec/task.yaml snapd-2.42.1+19.10/tests/main/snapd-reexec/task.yaml
--- snapd-2.41+19.10.1/tests/main/snapd-reexec/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/snapd-reexec/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -48,7 +48,7 @@
mount --bind /tmp/old-info $SNAP_MOUNT_DIR/core/current/usr/lib/snapd/info
systemctl start snapd.service snapd.socket
snap list
- check_journalctl_log 'core snap \(at .*\) is older \(.*\) than distribution package'
+ check_journalctl_log 'snap \(at .*\) is older \(.*\) than distribution package'
echo "Revert back to normal"
systemctl stop snapd.service snapd.socket
diff -Nru snapd-2.41+19.10.1/tests/main/snapd-reexec-snapd-snap/task.yaml snapd-2.42.1+19.10/tests/main/snapd-reexec-snapd-snap/task.yaml
--- snapd-2.41+19.10.1/tests/main/snapd-reexec-snapd-snap/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/snapd-reexec-snapd-snap/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -12,9 +12,12 @@
exit 0
fi
- echo "Enable installing the snapd snap"
+ echo "Enable installing the snapd snap, this happens automatically"
snap set core experimental.snapd-snap=true
- snap install --edge snapd
+ # give the state time to create the change and then start watching
+ # it
+ retry-tool -n 30 snap watch --last=transition-to-snapd-snap
+ snap list snapd
# shellcheck source=tests/lib/journalctl.sh
. "$TESTSLIB/journalctl.sh"
diff -Nru snapd-2.41+19.10.1/tests/main/snapd-slow-startup/task.yaml snapd-2.42.1+19.10/tests/main/snapd-slow-startup/task.yaml
--- snapd-2.41+19.10.1/tests/main/snapd-slow-startup/task.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/snapd-slow-startup/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,44 @@
+summary: Test that snapd is not terminated by systemd on a slow startup
+
+systems: [ubuntu-18.04-64,ubuntu-19.04-64]
+
+restore: |
+ # extra cleanup in case something in this test went wrong
+ rm -f /etc/systemd/system/snapd.service.d/slow-startup.conf
+ systemctl stop snapd.service snapd.socket
+
+debug: |
+ ls /etc/systemd/system/snapd.service.d
+ cat /etc/systemd/system/snapd.service.d/*
+
+execute: |
+ systemd_ver="$(systemctl --version|head -1|cut -d ' ' -f2)"
+ if [ "${systemd_ver}" -lt 236 ]; then
+ echo "systemd ${systemd_ver} too old, no EXTEND_TIMEOUT_USEC support"
+ exit 0
+ fi
+
+ # have 6 extra snaps installed, makes 7 with core
+ snap pack "$TESTSLIB"/snaps/basic
+ snap set system experimental.parallel-instances=true
+ for i in $(seq 6); do
+ snap install --dangerous --name="basic_$i" basic_1.0_all.snap
+ done
+
+ # shellcheck source=tests/lib/journalctl.sh
+ . "$TESTSLIB/journalctl.sh"
+
+ echo "Simulate a slow startup"
+ systemctl stop snapd.service snapd.socket
+ cat > /etc/systemd/system/snapd.service.d/slow-startup.conf <&1 | MATCH 'snap "snapctl-hooks" has no "root.key2" configuration option'
+ echo "Test unsetting of root.key2 with via snapctl unset"
+ # init and sanity check
+ snap set snapctl-hooks command=noop root.key2="b"
+ snap get snapctl-hooks root.key2 | MATCH "b"
+ # note, unsetting happens in the configure hook in response to "test-unset-with-unset" value
+ snap set snapctl-hooks command=test-unset-with-unset
+ snap get snapctl-hooks root.key2 2>&1 | MATCH 'snap "snapctl-hooks" has no "root.key2" configuration option'
+
echo "Test that config values are not available once snap is removed"
snap remove snapctl-hooks
if output=$(snap get snapctl-hooks foo); then
diff -Nru snapd-2.41+19.10.1/tests/main/snap-icons/task.yaml snapd-2.42.1+19.10/tests/main/snap-icons/task.yaml
--- snapd-2.41+19.10.1/tests/main/snap-icons/task.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/snap-icons/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,29 @@
+summary: Snaps can install icon theme icons
+
+execute: |
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB"/snaps.sh
+
+ echo "Install a snap providing icons"
+ install_local test-snapd-icon-theme
+
+ echo "Icons provided by the snap are installed to a shared location"
+ iconfile=/var/lib/snapd/desktop/icons/hicolor/scalable/apps/snap.test-snapd-icon-theme.foo.svg
+ [ -f "$iconfile" ]
+ MATCH "icon from test-snapd-icon-theme" < "$iconfile"
+
+ echo "Desktop files can reference installed icons"
+ desktopfile=/var/lib/snapd/desktop/applications/test-snapd-icon-theme_echo.desktop
+ MATCH '^Icon=snap.test-snapd-icon-theme.foo$' < "$desktopfile"
+
+ echo "Remove the snap"
+ snap remove test-snapd-icon-theme
+
+ echo "The icon has been removed"
+ [ ! -f "$iconfile" ]
+
+ echo "The empty icon theme subdirectories have also been removed"
+ [ ! -d /var/lib/snapd/desktop/icons/hicolor ]
+
+ echo "But the base icons directory remains"
+ [ -d /var/lib/snapd/desktop/icons ]
diff -Nru snapd-2.41+19.10.1/tests/main/snap-info/task.yaml snapd-2.42.1+19.10/tests/main/snap-info/task.yaml
--- snapd-2.41+19.10.1/tests/main/snap-info/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/snap-info/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -8,6 +8,11 @@
snap pack "$TESTSLIB"/snaps/basic
snap install test-snapd-tools
snap install --channel beta --devmode test-snapd-devmode
+ # ensures that an empty directory doesn't confuse things
+ mkdir -v core
+
+restore: |
+ rmdir -v core
execute: |
echo "With no arguments, errors out"
diff -Nru snapd-2.41+19.10.1/tests/main/snap-model/task.yaml snapd-2.42.1+19.10/tests/main/snap-model/task.yaml
--- snapd-2.41+19.10.1/tests/main/snap-model/task.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/snap-model/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,50 @@
+summary: Check that snap model works
+
+execute: |
+ knownCmdAssertion=$(snap known model)
+ modelCmdAssertion=$(snap model --assertion)
+ echo "Check that model assertion from \"snap known\" matches \"snap model\""
+ if [ "$modelCmdAssertion" != "$knownCmdAssertion" ]; then
+ echo "model assertions not the same, difference is:"
+ diff -u <(echo "$modelCmdAssertion") <(echo "$knownCmdAssertion")
+ exit 1
+ fi
+
+ knownCmdAssertion=$(snap known serial)
+ modelCmdAssertion=$(snap model --serial --assertion)
+ echo "Check that serial assertion from \"snap known\" matches \"snap model\""
+ if [ "$modelCmdAssertion" != "$knownCmdAssertion" ]; then
+ echo "serial assertions not the same, difference is:"
+ diff -u <(echo "$modelCmdAssertion") <(echo "$knownCmdAssertion")
+ exit 1
+ fi
+
+ modelCmdSerial="$(snap model --serial | grep -Po "serial:\s+\K(.*)")"
+ knownCmdSerial="$(snap known serial | grep -Po "serial:\s+\K(.*)")"
+ echo "Check that serial from \"snap known\" matches \"snap model\""
+ if [ "$modelCmdSerial" != "$knownCmdSerial" ]; then
+ echo "serial numbers not the same, difference is:"
+ diff -u <(echo "$knownCmdSerial") <(echo "$knownCmdSerial")
+ exit 1
+ fi
+
+ modelCmdModel="$(snap model | grep -Po "model\s+\K(.*)")"
+ knownCmdModel="$(snap known model | grep -Po "model:\s+\K(.*)")"
+ echo "Check that model from \"snap known\" matches \"snap model\""
+ if snap known model | grep -q -P '^display-name:'; then
+ # the model has a display-name so `snap model` output will be different
+ echo "note: model has a display-name in it"
+ modelDisplayName="$(snap known model | grep -Po "display-name:\s+\K(.*)")"
+ fullModelShown="$modelDisplayName ($knownCmdModel)"
+ if [ "$modelCmdModel" != "$fullModelShown" ]; then
+ echo "model not the same, difference is:"
+ diff -u <(echo "$modelCmdModel") <(echo "$fullModelShown")
+ exit 1
+ fi
+ else
+ if [ "$modelCmdModel" != "$knownCmdModel" ]; then
+ echo "model not the same, difference is:"
+ diff -u <(echo "$modelCmdModel") <(echo "$knownCmdModel")
+ exit 1
+ fi
+ fi
diff -Nru snapd-2.41+19.10.1/tests/main/snap-run/task.yaml snapd-2.42.1+19.10/tests/main/snap-run/task.yaml
--- snapd-2.41+19.10.1/tests/main/snap-run/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/snap-run/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -19,6 +19,13 @@
echo "Test that snap run use environments"
basic-run.echo-data | MATCH ^/var/snap
+ if command -v gdb; then
+ echo "Test snap run --gdb works"
+ echo "c" | snap run --gdb test-snapd-tools.echo hello > stdout
+ MATCH 'Continuing.' < stdout
+ MATCH hello < stdout
+ fi
+
# the strace on 14.04 is too old
if grep -q 'VERSION_ID="14.04"' /etc/os-release; then
snap install strace-static
@@ -32,6 +39,23 @@
snap install strace-static
fi
+ echo "Test snap --strace invalid works"
+ if snap run --strace="invalid" test-snapd-tools.echo hello 2>stderr ; then
+ echo "snap run with an invalid strace option should fail but it did not"
+ exit 1
+ fi
+ MATCH "Can't stat 'invalid': No such file or directory" < stderr
+
+ if [[ "$SPREAD_SYSTEM" == arch-linux-* ]]; then
+ # Arch runs the mainline kernel, strace (with event filter or not) *may*
+ # randomly get stuck on the kernel side, see:
+ # - proposed patch: https://lore.kernel.org/patchwork/patch/719314/
+ # - snap-exec & strace stuck: https://paste.ubuntu.com/p/8nVzj8Sqfq/
+ echo "SKIP further tests due to know kernel/strace problems"
+ exit 0
+ fi
+ # XXX: any tests that execute strace should be added below this point
+
echo "Test snap run --strace"
snap run --strace test-snapd-tools.echo "hello-world" >stdout 2>stderr
MATCH hello-world < stdout
@@ -47,20 +71,6 @@
MATCH "strace -- version" < stdout
[ ! -s stderr ]
- echo "Test snap --strace invalid works"
- if snap run --strace="invalid" test-snapd-tools.echo hello 2>stderr ; then
- echo "snap run with an invalid strace option should fail but it did not"
- exit 1
- fi
- MATCH "Can't stat 'invalid': No such file or directory" < stderr
-
- if command -v gdb; then
- echo "Test snap run --gdb works"
- echo "c" | snap run --gdb test-snapd-tools.echo hello > stdout
- MATCH 'Continuing.' < stdout
- MATCH hello < stdout
- fi
-
snap run --trace-exec test-snapd-tools.echo hello 2> stderr
MATCH "Slowest [0-9]+ exec calls during snap run" < stderr
MATCH " [0-9.]+s .*/snap-exec" < stderr
diff -Nru snapd-2.41+19.10.1/tests/main/snap-set/task.yaml snapd-2.42.1+19.10/tests/main/snap-set/task.yaml
--- snapd-2.41+19.10.1/tests/main/snap-set/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/snap-set/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -71,3 +71,11 @@
exit 1
fi
[[ "$obtained" == *"error from within configure hook"* ]]
+
+ echo "Test that the 'snap set' order is deterministic"
+ for _ in $(seq 50); do
+ snap set snapctl-hooks command=noop one!
+ snap set snapctl-hooks one.two=2 one='{"three":3}'
+ snap get snapctl-hooks -l one.two | MATCH "one.two[ ]*2"
+ snap get snapctl-hooks -l one.three | MATCH "one.three[ ]*3"
+ done
diff -Nru snapd-2.41+19.10.1/tests/main/snap-set-core-w-no-core/task.yaml snapd-2.42.1+19.10/tests/main/snap-set-core-w-no-core/task.yaml
--- snapd-2.41+19.10.1/tests/main/snap-set-core-w-no-core/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/snap-set-core-w-no-core/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -27,3 +27,11 @@
echo "snap set core must error for unknown options"
exit 1
fi
+ if snap set core unknown!; then
+ echo "snap set core must error for unknown options"
+ exit 1
+ fi
+ if snap unset core unknown.option; then
+ echo "snap unset core must error for unknown options"
+ exit 1
+ fi
diff -Nru snapd-2.41+19.10.1/tests/main/snap-system-env/task.yaml snapd-2.42.1+19.10/tests/main/snap-system-env/task.yaml
--- snapd-2.41+19.10.1/tests/main/snap-system-env/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/snap-system-env/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -1,7 +1,7 @@
summary: Ensure systemd environment generator works
# systemd environment generators are only supported on 17.10+
-systems: [ubuntu-18.04-*, ubuntu-19.04-*, ubuntu-2*]
+systems: [ubuntu-18.04-*, ubuntu-19*, ubuntu-2*]
execute: |
# integration test to ensure it works on the real system
diff -Nru snapd-2.41+19.10.1/tests/main/snap-wait/task.yaml snapd-2.42.1+19.10/tests/main/snap-wait/task.yaml
--- snapd-2.41+19.10.1/tests/main/snap-wait/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/snap-wait/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -1,6 +1,6 @@
summary: Check that `snap wait` works
-kill-timeout: 2m
+kill-timeout: 5m
prepare: |
# shellcheck source=tests/lib/snaps.sh
diff -Nru snapd-2.41+19.10.1/tests/main/system-usernames/task.yaml snapd-2.42.1+19.10/tests/main/system-usernames/task.yaml
--- snapd-2.41+19.10.1/tests/main/system-usernames/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/system-usernames/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -8,7 +8,7 @@
# List of expected snap install failures due to libseccomp/golang-seccomp
# being too old. This should only reduce with time since new systems should
# have newer libseccomp and golang-seccomp
- EXFAIL: "amazon-linux-2-64 centos-7-64 debian-9-64 debian-sid-64 fedora-29-64 fedora-30-64 opensuse-15\\.0-64 opensuse-15\\.1-64 ubuntu-14"
+ EXFAIL: "amazon-linux-2-64 centos-7-64 debian-9-64 fedora-29-64 fedora-30-64 opensuse-15\\.0-64 ubuntu-14"
prepare: |
echo "Install helper snaps with default confinement"
@@ -22,18 +22,7 @@
# snapd will create this for us, but we'll remove it for consistency in
# test runs
- if [ -e /var/lib/extrausers/passwd ]; then
- userdel --extrausers --force snap_daemon || true
- else
- userdel --force snap_daemon || true
- # some systems do not set "USERGROUPS_ENAB yes" so we need to cleanup
- # the group manually. --force is documented but not available with
- # groupdel and -f is only available on newer releases
- groupdel snap_daemon || true
- fi
- # ensure the snap_daemon user really got deleted
- not getent passwd snap_daemon
- not getent group snap_daemon
+ user-tool remove-with-group snap_daemon
execute: |
# to accommodate different distros, we pick the LSB required 'daemon' user
diff -Nru snapd-2.41+19.10.1/tests/main/system-usernames-illegal/task.yaml snapd-2.42.1+19.10/tests/main/system-usernames-illegal/task.yaml
--- snapd-2.41+19.10.1/tests/main/system-usernames-illegal/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/system-usernames-illegal/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -3,12 +3,7 @@
# List of expected snap install failures due to libseccomp/golang-seccomp being
# too old. Since the illegal name check happens after verifying system support,
# we can ignore these.
-systems: [-amazon-linux-2-*, -centos-7-*, -debian-9-*, -debian-sid-*, -fedora-29-*, -fedora-30-*, -opensuse-15.0-*, -opensuse-15.1-*, -ubuntu-14.04-*]
-
-restore:
- # Make sure the snap is removed if the test failed and the snap was
- # installed
- snap remove test-snapd-illegal-system-username || true
+systems: [-amazon-linux-2-*, -centos-7-*, -debian-9-*, -fedora-29-*, -fedora-30-*, -opensuse-15.0-*, -opensuse-15.1-*, -ubuntu-14.04-*]
execute: |
#shellcheck source=tests/lib/snaps.sh
@@ -16,3 +11,7 @@
snap_path=$(make_snap test-snapd-illegal-system-username)
echo "Try to install a snap with an illegal user in 'system-usernames'"
snap install --dangerous "${snap_path}" 2>&1 | MATCH 'requires unsupported system username "daemon"'
+
+ # Make sure neiter snap_deaemon user nor group are created
+ not getent passwd snap_daemon
+ not getent group snap_daemon
diff -Nru snapd-2.41+19.10.1/tests/main/system-usernames-install-twice/task.yaml snapd-2.42.1+19.10/tests/main/system-usernames-install-twice/task.yaml
--- snapd-2.41+19.10.1/tests/main/system-usernames-install-twice/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/system-usernames-install-twice/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -4,7 +4,7 @@
# too old. Since the illegal name check happens after verifying system support,
# we can ignore these. Ignore ubuntu-core since groupdel doesn't support
# --extrausers
-systems: [-amazon-linux-2-*, -centos-7-*, -debian-9-*, -debian-sid-*, -fedora-29-*, -fedora-30-*, -opensuse-15.0-*, -opensuse-15.1-*, -ubuntu-14.04-*, -ubuntu-core-*]
+systems: [-amazon-linux-2-*, -centos-7-*, -debian-9-*, -fedora-29-*, -fedora-30-*, -opensuse-15.0-*, -opensuse-15.1-*, -ubuntu-14.04-*, -ubuntu-core-*]
prepare: |
snap install --edge test-snapd-daemon-user
@@ -12,10 +12,9 @@
restore: |
snap remove test-snapd-daemon-user || true
- # Clean user and group to be used on the following tests
- userdel --force snap_daemon || true
- groupdel snap_daemon || true
- not getent group snap_daemon
+ # snapd will create this for us, but we'll remove it for consistency in
+ # test runs
+ user-tool remove-with-group snap_daemon
execute: |
echo "When the snap is removed"
diff -Nru snapd-2.41+19.10.1/tests/main/system-usernames-missing-user/task.yaml snapd-2.42.1+19.10/tests/main/system-usernames-missing-user/task.yaml
--- snapd-2.41+19.10.1/tests/main/system-usernames-missing-user/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/system-usernames-missing-user/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -4,7 +4,7 @@
# too old. Since the illegal name check happens after verifying system support,
# we can ignore these. Ignore ubuntu-core since groupdel doesn't support
# --extrausers
-systems: [-amazon-linux-2-*, -centos-7-*, -debian-9-*, -debian-sid-*, -fedora-29-*, -fedora-30-*, -opensuse-15.0-*, -opensuse-15.1-*, -ubuntu-14.04-*, -ubuntu-core-*]
+systems: [-amazon-linux-2-*, -centos-7-*, -debian-9-*, -fedora-29-*, -fedora-30-*, -opensuse-15.0-*, -opensuse-15.1-*, -ubuntu-14.04-*, -ubuntu-core-*]
prepare: |
groupadd --system snap_daemon
@@ -13,6 +13,7 @@
# Make sure the snap is removed if the test failed and the snap was
# installed
snap remove test-snapd-daemon-user || true
+
# snapd will create this for us, but we'll remove it for consistency in
# test runs
groupdel snap_daemon || true
diff -Nru snapd-2.41+19.10.1/tests/main/ubuntu-core-device-reg/task.yaml snapd-2.42.1+19.10/tests/main/ubuntu-core-device-reg/task.yaml
--- snapd-2.41+19.10.1/tests/main/ubuntu-core-device-reg/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/ubuntu-core-device-reg/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -31,6 +31,9 @@
ubuntu-core-18-64)
snap known serial | MATCH "model: ubuntu-core-18-amd64"
;;
+ ubuntu-core-18-arm-*)
+ snap known serial | MATCH "model: ubuntu-core-18-$gadget_name"
+ ;;
ubuntu-core-16-64)
snap known serial | MATCH "model: pc"
;;
diff -Nru snapd-2.41+19.10.1/tests/main/ubuntu-core-grub/task.yaml snapd-2.42.1+19.10/tests/main/ubuntu-core-grub/task.yaml
--- snapd-2.41+19.10.1/tests/main/ubuntu-core-grub/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/ubuntu-core-grub/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -1,6 +1,6 @@
summary: Ensure we have no unpacked kernel.img/initrd.img on grub systems
-systems: [ubuntu-core-16-64]
+systems: [ubuntu-core-1*-64]
environment:
NAME/initrdimg: initrd.img*
diff -Nru snapd-2.41+19.10.1/tests/main/ubuntu-core-refresh/task.yaml snapd-2.42.1+19.10/tests/main/ubuntu-core-refresh/task.yaml
--- snapd-2.41+19.10.1/tests/main/ubuntu-core-refresh/task.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/ubuntu-core-refresh/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,82 @@
+summary: Check that the ubuntu-core system is rebooted after the core snap is refreshed
+
+details: |
+ This test checks that when invoking a manual refresh/revert for core or core18 snaps,
+ a reboot is triggered and the command would exit after the first phase of the installation
+ reporting "snapd is about to reboot the system"
+
+systems: [ubuntu-core-*]
+
+execute: |
+ #shellcheck source=tests/lib/journalctl.sh
+ . "$TESTSLIB"/journalctl.sh
+ #shellcheck source=tests/lib/systems.sh
+ . "$TESTSLIB"/systems.sh
+
+ TARGET_SNAP_NAME=core
+ if is_core18_system; then
+ TARGET_SNAP_NAME=core18
+ fi
+
+ # After installing a new version of the core/core18 snap the system is rebooted
+ if [ "$SPREAD_REBOOT" = 0 ]; then
+ currRev="$(readlink /snap/${TARGET_SNAP_NAME}/current)"
+ echo "$currRev" > initialRev
+
+ # use journalctl wrapper to grep only the logs collected while the test is running
+ if get_journalctl_log | MATCH "Waiting for system reboot"; then
+ echo "Already waiting for system reboot, exiting..."
+ exit 1
+ fi
+
+ # install new target snap
+
+ snap install --dangerous "/var/lib/snapd/snaps/${TARGET_SNAP_NAME}_${currRev}.snap" &> refresh.log
+ MATCH "snapd is about to reboot the system" < refresh.log
+
+ # Detect in the logs when the reboot can been triggered
+ for _ in $(seq 50); do
+ if check_journalctl_log "Waiting for system reboot"; then
+ break
+ fi
+ sleep 2
+ done
+
+ if ! check_journalctl_log "Waiting for system reboot"; then
+ echo "Taking too long to reach waiting for reboot"
+ exit 1
+ fi
+
+ REBOOT
+ elif [ "$SPREAD_REBOOT" = 1 ]; then
+ while ! snap changes | MATCH "Done.*Install \"${TARGET_SNAP_NAME}\" snap from file.*"; do
+ sleep 1
+ done
+
+ # Check the current revision has changed
+ currRev="$(readlink /snap/${TARGET_SNAP_NAME}/current)"
+ [ "$(cat initialRev)" != "$currRev" ]
+
+ # revert the target snap
+ snap revert "$TARGET_SNAP_NAME" &> revert.log
+ MATCH "snapd is about to reboot the system" < revert.log
+
+ # Detect in the logs when the reboot can been triggered
+ for _ in $(seq 50); do
+ if check_journalctl_log "Waiting for system reboot" -b; then
+ break
+ fi
+ sleep 2
+ done
+
+ if ! check_journalctl_log "Waiting for system reboot" -b; then
+ echo "Taking too long to reach waiting for reboot"
+ exit 1
+ fi
+
+ REBOOT
+ elif [ "$SPREAD_REBOOT" = 2 ]; then
+ # Check the current revision is the same than the original
+ currRev="$(readlink /snap/${TARGET_SNAP_NAME}/current)"
+ [ "$(cat initialRev)" == "$currRev" ]
+ fi
diff -Nru snapd-2.41+19.10.1/tests/main/ubuntu-core-upgrade/task.yaml snapd-2.42.1+19.10/tests/main/ubuntu-core-upgrade/task.yaml
--- snapd-2.41+19.10.1/tests/main/ubuntu-core-upgrade/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/ubuntu-core-upgrade/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -1,6 +1,9 @@
summary: Upgrade the core snap and revert a few times
-systems: [ubuntu-core-16-*]
+# ARM devices are not supported on ubuntu-core-18 due to fw_printenv/setenv are
+# not provided by the system and as the devices boot with uboot so it is not
+# possible to get any grub information as it is done with non arm devices.
+systems: [ubuntu-core-16-*, ubuntu-core-18-32*, ubuntu-core-18-64*]
# Start early as it takes a long time.
priority: 100
@@ -18,17 +21,36 @@
if [ -f curChg ] ; then
snap abort "$(cat curChg)" || true
fi
+ # Remove the revisions installed during the test.
+ # The x1 revision is the one we use initially.
+ snap remove core --revision=x2
+ snap remove core --revision=x3
prepare: |
- snap list | awk "/^core / {print(\$3)}" > nextBoot
+ #shellcheck source=tests/lib/systems.sh
+ . "$TESTSLIB"/systems.sh
+
+ TARGET_SNAP=core
+ if is_core18_system; then
+ TARGET_SNAP=core18
+ fi
+
+ snap list | awk "/^${TARGET_SNAP} / {print(\$3)}" > nextBoot
snap install test-snapd-tools
execute: |
#shellcheck source=tests/lib/boot.sh
. "$TESTSLIB"/boot.sh
+ #shellcheck source=tests/lib/systems.sh
+ . "$TESTSLIB"/systems.sh
+
+ TARGET_SNAP=core
+ if is_core18_system; then
+ TARGET_SNAP=core18
+ fi
# FIXME Why it starting with snap_mode=try the first time?
- # Perhaps because core is installed after seeding? Do we
+ # Perhaps because $TARGET_SNAP is installed after seeding? Do we
# want that on pristine images?
if [ "$SPREAD_REBOOT" != 0 ]; then
echo "Waiting for snapd to clean snap_mode"
@@ -37,12 +59,12 @@
done
echo "Ensure the bootloader is correct after reboot"
- test "$(bootenv snap_core)" = "core_$(cat nextBoot).snap"
+ test "$(bootenv snap_core)" = "${TARGET_SNAP}_$(cat nextBoot).snap"
test "$(bootenv snap_try_core)" = ""
test "$(bootenv snap_mode)" = ""
fi
- snap list | awk "/^core / {print(\$3)}" > prevBoot
+ snap list | awk "/^${TARGET_SNAP} / {print(\$3)}" > prevBoot
# wait for ongoing change if there is one
if [ -f curChg ] ; then
@@ -52,10 +74,10 @@
case "$SPREAD_REBOOT" in
- 0) cmd="snap install --dangerous /var/lib/snapd/snaps/core_$(cat prevBoot).snap" ;;
- 1) cmd="snap revert core" ;;
- 2) cmd="snap install --dangerous /var/lib/snapd/snaps/core_$(cat prevBoot).snap" ;;
- 3) cmd="snap revert core" ;;
+ 0) cmd="snap install --dangerous /var/lib/snapd/snaps/${TARGET_SNAP}_$(cat prevBoot).snap" ;;
+ 1) cmd="snap revert $TARGET_SNAP" ;;
+ 2) cmd="snap install --dangerous /var/lib/snapd/snaps/${TARGET_SNAP}_$(cat prevBoot).snap" ;;
+ 3) cmd="snap revert $TARGET_SNAP" ;;
4) exit 0 ;;
esac
@@ -74,9 +96,9 @@
test-snapd-tools.echo hello | MATCH hello
echo "Ensure the bootloader is correct before reboot"
- readlink /snap/core/current > nextBoot
+ readlink "/snap/${TARGET_SNAP}/current" > nextBoot
test "$(cat prevBoot)" != "$(cat nextBoot)"
- test "$(bootenv snap_try_core)" = "core_$(cat nextBoot).snap"
+ test "$(bootenv snap_try_core)" = "${TARGET_SNAP}_$(cat nextBoot).snap"
test "$(bootenv snap_mode)" = "try"
echo "Ensure the device is scheduled for auto-reboot"
diff -Nru snapd-2.41+19.10.1/tests/main/ubuntu-core-writablepaths/task.yaml snapd-2.42.1+19.10/tests/main/ubuntu-core-writablepaths/task.yaml
--- snapd-2.41+19.10.1/tests/main/ubuntu-core-writablepaths/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/ubuntu-core-writablepaths/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -1,6 +1,6 @@
summary: Ensure that the writable paths on the image are correct
-systems: [ubuntu-core-16-*]
+systems: [ubuntu-core-1*]
execute: |
echo "Ensure everything in writable-paths is actually writable"
diff -Nru snapd-2.41+19.10.1/tests/main/upgrade-from-2.15/task.yaml snapd-2.42.1+19.10/tests/main/upgrade-from-2.15/task.yaml
--- snapd-2.41+19.10.1/tests/main/upgrade-from-2.15/task.yaml 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/upgrade-from-2.15/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -3,6 +3,7 @@
systems: [ubuntu-16.04-64]
prepare: |
+ apt-tool checkpoint > installed.pkgs
#shellcheck source=tests/lib/pkgdb.sh
. "$TESTSLIB/pkgdb.sh"
distro_purge_package snapd
@@ -16,6 +17,9 @@
fi
distro_install_build_snapd
+ apt-tool restore installed.pkgs
+ rm -f installed.pkgs
+
execute: |
#shellcheck source=tests/lib/systemd.sh
. "$TESTSLIB"/systemd.sh
diff -Nru snapd-2.41+19.10.1/tests/main/version-tool/task.yaml snapd-2.42.1+19.10/tests/main/version-tool/task.yaml
--- snapd-2.41+19.10.1/tests/main/version-tool/task.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/main/version-tool/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,43 @@
+summary: integration tests for version-tool
+execute: |
+ # ==
+ version-tool --strict 1 -eq 1
+ version-tool --strict 1 -eq 1.0
+ version-tool --strict 1.0 -eq 1
+ not version-tool --strict 1 -eq 2
+
+ # !=
+ not version-tool --strict 1.2 -ne 1.2
+ version-tool --strict 1 -ne 2
+ version-tool --strict 2 -ne 1
+
+ # < and <=
+ version-tool --strict 1 -lt 2
+ not version-tool --strict 2 -lt 1
+ version-tool --strict 1 -le 2
+ version-tool --strict 2 -le 2
+ not version-tool --strict 2 -le 1
+
+ # > and >=
+ version-tool --strict 2 -gt 1
+ not version-tool --strict 1 -gt 2
+ version-tool --strict 2 -ge 1
+ version-tool --strict 2 -ge 2
+ not version-tool --strict 1 -ge 2
+
+ # --verbose
+ version-tool --verbose --strict 1 -eq 2 | MATCH 'delta between 1 and 2 is: -1'
+ version-tool --verbose --strict 1 -eq 2 | MATCH 'delta -1 is inconsistent with =='
+
+ # --version
+ # NOTE: older python versions print the version string to stderr
+ version-tool --version 2>&1 | MATCH 1.0
+
+ # Strict requires all version components to be integers.
+ version-tool --strict 1.2 -eq 1.2-foo 2>&1 | MATCH 'error: version 1.2-foo is not purely numeric'
+ # Such invalid comparison also returns a distinct error code.
+ set +e
+ version-tool --strict 1.2 -eq 1.2-foo
+ error_code=$?
+ set -e
+ test "$error_code" -eq 2
diff -Nru snapd-2.41+19.10.1/tests/nightly/classic-ubuntu-core-transition/task.yaml snapd-2.42.1+19.10/tests/nightly/classic-ubuntu-core-transition/task.yaml
--- snapd-2.41+19.10.1/tests/nightly/classic-ubuntu-core-transition/task.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/nightly/classic-ubuntu-core-transition/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,128 @@
+summary: Ensure that the ubuntu-core -> core transition works
+
+# we never test on core because the transition can only happen on "classic" we
+# disable on ppc64el because the downloads are very slow there Fedora, openSUSE,
+# Arch, CentOS are disabled at the moment as there is something fishy going on
+# and the snapd service gets terminated during the process.
+systems: [-ubuntu-core-*, -ubuntu-*-ppc64el, -fedora-*, -opensuse-*, -ubuntu-*-i386, -arch-*, -amazon-*, -centos-*, -debian-sid-*]
+
+# autopkgtest run only a subset of tests that deals with the integration
+# with the distro
+backends: [-autopkgtest]
+
+warn-timeout: 1m
+kill-timeout: 5m
+
+debug: |
+ snap list || true
+ snap info core || true
+ snap info ubuntu-core || true
+ snap changes
+ #shellcheck source=tests/lib/changes.sh
+ . "$TESTSLIB/changes.sh"
+ snap change "$(change_id 'Transition ubuntu-core to core')" || true
+
+execute: |
+ #shellcheck source=tests/lib/pkgdb.sh
+ . "$TESTSLIB/pkgdb.sh"
+ #shellcheck source=tests/lib/systemd.sh
+ . "$TESTSLIB"/systemd.sh
+ curl() {
+ local url="$1"
+ # sadly systemd active means not that its really ready so we wait
+ # here for the socket to be available
+ while ! ss -t -l -n|grep :80; do
+ ss -l -l -n
+ sleep 1
+ done
+ python3 -c "import urllib.request; print(urllib.request.urlopen(\"$url\").read().decode(\"utf-8\"))"
+ }
+
+ #shellcheck source=tests/lib/pkgdb.sh
+ . "$TESTSLIB/pkgdb.sh"
+ echo "Ensure core is gone and we have ubuntu-core instead"
+ distro_purge_package snapd
+ distro_install_build_snapd
+
+ # need to be seeded to allow snap install
+ snap wait system seed.loaded
+
+ # modify daemon state to set ubuntu-core-transition-last-retry-time to the
+ # current time to prevent the ubuntu-core transition before the test snap is
+ # installed
+ systemctl stop snapd.{service,socket}
+ now="$(date --utc -Ins)"
+ jq -c '. + {data: (.data + {"ubuntu-core-transition-last-retry-time": "'"$now"'"})}' < /var/lib/snapd/state.json > state.json.new
+ mv state.json.new /var/lib/snapd/state.json
+ systemctl start snapd.{service,socket}
+
+ snap download "--${CORE_CHANNEL}" ubuntu-core
+ snap ack ./ubuntu-core_*.assert
+ snap install ./ubuntu-core_*.snap
+
+ snap install test-snapd-python-webserver
+ snap interfaces -i network | MATCH ":network +test-snapd-python-webserver"
+ snap interfaces -i network-bind | MATCH ":network-bind +.*test-snapd-python-webserver"
+
+ echo "Ensure the webserver is working"
+ wait_for_service snap.test-snapd-python-webserver.test-snapd-python-webserver
+ curl http://localhost | MATCH "XKCD rocks"
+
+ # restore ubuntu-core-transition-last-retry-time to its previous value and restart the daemon
+ systemctl stop snapd.{service,socket}
+ jq -c 'del(.["data"]["ubuntu-core-transition-last-retry-time"])' < /var/lib/snapd/state.json > state.json.new
+ mv state.json.new /var/lib/snapd/state.json
+ systemctl start snapd.{service,socket}
+
+ echo "Ensure transition is triggered"
+ # wait for steady state or ensure-state-soon will be pointless
+ ok=0
+ for i in $(seq 40); do
+ if ! snap changes|grep -q ".*.Doing.*" ; then
+ ok=1
+ break
+ fi
+ sleep .5
+ done
+ if [ $ok -ne 1 ] ; then
+ echo "Did not reach steady state"
+ exit 1
+ fi
+ snap debug ensure-state-soon
+
+ # wait for transition
+ ok=0
+ for i in $(seq 240); do
+ if snap changes|grep -q ".*Done.*Transition ubuntu-core to core" ; then
+ ok=1
+ break
+ fi
+ sleep 1
+ done
+ if [ $ok -ne 1 ] ; then
+ echo "Transition did not start or finish"
+ exit 1
+ fi
+
+ if snap list|grep ubuntu-core; then
+ echo "ubuntu-core still installed, transition failed"
+ exit 1
+ fi
+ snap interfaces -i network | MATCH ":network +test-snapd-python-webserver"
+ snap interfaces -i network-bind | MATCH ":network-bind +.*test-snapd-python-webserver"
+ echo "Ensure the webserver is still working"
+ wait_for_service snap.test-snapd-python-webserver.test-snapd-python-webserver
+ curl http://localhost | MATCH "XKCD rocks"
+
+ systemctl restart snap.test-snapd-python-webserver.test-snapd-python-webserver
+ wait_for_service snap.test-snapd-python-webserver.test-snapd-python-webserver
+ echo "Ensure the webserver is working after a snap restart"
+ curl http://localhost | MATCH "XKCD rocks"
+
+ echo "Ensure snap set core works"
+ snap set core system.power-key-action=ignore
+ if [ "$(snap get core system.power-key-action)" != "ignore" ]; then
+ echo "snap get did not return the expected result: "
+ snap get core system.power-key-action
+ exit 1
+ fi
diff -Nru snapd-2.41+19.10.1/tests/nightly/classic-ubuntu-core-transition-auth/task.yaml snapd-2.42.1+19.10/tests/nightly/classic-ubuntu-core-transition-auth/task.yaml
--- snapd-2.41+19.10.1/tests/nightly/classic-ubuntu-core-transition-auth/task.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/nightly/classic-ubuntu-core-transition-auth/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,76 @@
+summary: Ensure that the ubuntu-core -> core transition works with auth.json
+
+# we never test on core because the transition can only happen on "classic"
+# we disable on ppc64el because the downloads are very slow there
+# Fedora, openSUSE and Arch are disabled at the moment as there is something
+# fishy going on and the snapd service gets terminated during the process.
+systems: [-ubuntu-core-*, -ubuntu-*-ppc64el, -fedora-*, -opensuse-*, -arch-*]
+
+# autopkgtest run only a subset of tests that deals with the integration
+# with the distro
+backends: [-autopkgtest]
+
+warn-timeout: 1m
+
+kill-timeout: 5m
+
+debug: |
+ snap changes
+ #shellcheck source=tests/lib/changes.sh
+ . "$TESTSLIB/changes.sh"
+ snap change "$(change_id 'Transition ubuntu-core to core')" || true
+
+execute: |
+ #shellcheck source=tests/lib/pkgdb.sh
+ . "$TESTSLIB/pkgdb.sh"
+ echo "Ensure core is gone and we have ubuntu-core instead"
+ distro_purge_package snapd
+ distro_install_build_snapd
+
+ # need to be seeded to allow snap install
+ snap wait system seed.loaded
+
+ snap download "--${CORE_CHANNEL}" ubuntu-core
+ snap ack ./ubuntu-core_*.assert
+ snap install ./ubuntu-core_*.snap
+
+ mkdir -p /root/.snap/
+ echo '{}' > /root/.snap/auth.json
+ mkdir -p /home/test/.snap/
+ echo '{}' > /home/test/.snap/auth.json
+
+ echo "Ensure transition is triggered"
+ # wait for steady state or ensure-state-soon will be pointless
+ ok=0
+ for i in $(seq 40); do
+ if ! snap changes|grep -q ".*.Doing.*" ; then
+ ok=1
+ break
+ fi
+ sleep .5
+ done
+ if [ $ok -ne 1 ] ; then
+ echo "Did not reach steady state"
+ exit 1
+ fi
+ snap debug ensure-state-soon
+
+
+ # wait for transition
+ ok=0
+ for i in $(seq 240); do
+ if snap changes|grep -q ".*Done.*Transition ubuntu-core to core" ; then
+ ok=1
+ break
+ fi
+ sleep 1
+ done
+ if [ $ok -ne 1 ] ; then
+ echo "Transition did not start or finish"
+ exit 1
+ fi
+
+ if snap list|grep ubuntu-core; then
+ echo "ubuntu-core still installed, transition failed"
+ exit 1
+ fi
diff -Nru snapd-2.41+19.10.1/tests/nightly/classic-ubuntu-core-transition-two-cores/task.yaml snapd-2.42.1+19.10/tests/nightly/classic-ubuntu-core-transition-two-cores/task.yaml
--- snapd-2.41+19.10.1/tests/nightly/classic-ubuntu-core-transition-two-cores/task.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/nightly/classic-ubuntu-core-transition-two-cores/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,78 @@
+summary: Ensure that the ubuntu-core -> core transition works with two cores
+
+# we never test on core because the transition can only happen on "classic"
+# we disable on ppc64el because the downloads are very slow there
+systems: [-ubuntu-core-*, -ubuntu-*-ppc64el]
+
+# autopkgtest run only a subset of tests that deals with the integration
+# with the distro
+backends: [-autopkgtest]
+
+warn-timeout: 1m
+
+kill-timeout: 5m
+
+debug: |
+ snap changes
+ #shellcheck source=tests/lib/changes.sh
+ . "$TESTSLIB"/changes.sh
+ snap change "$(change_id 'Transition ubuntu-core to core')" || true
+
+execute: |
+ echo "install a snap"
+ snap install test-snapd-python-webserver
+ snap interfaces -i network | MATCH ":network.*test-snapd-python-webserver"
+
+ #shellcheck source=tests/lib/names.sh
+ . "$TESTSLIB/names.sh"
+ cp /var/lib/snapd/state.json /var/lib/snapd/state.json.old
+ jq -r '.data.snaps["core"].type="xxx"' < /var/lib/snapd/state.json.old > /var/lib/snapd/state.json
+
+ systemctl stop snapd.service snapd.socket
+ systemctl start snapd.service snapd.socket
+
+ snap download "--${CORE_CHANNEL}" ubuntu-core
+ snap ack ./ubuntu-core_*.assert
+ snap install ./ubuntu-core_*.snap
+
+ cp /var/lib/snapd/state.json /var/lib/snapd/state.json.old
+ jq -r '.data.snaps["core"].type="os"' < /var/lib/snapd/state.json.old > /var/lib/snapd/state.json
+
+ snap list | MATCH "ubuntu-core "
+ snap list | MATCH "core "
+
+ echo "Ensure transition is triggered"
+ # wait for steady state or ensure-state-soon will be pointless
+ ok=0
+ for _ in $(seq 40); do
+ if ! snap changes|grep -q ".*.Doing.*" ; then
+ ok=1
+ break
+ fi
+ sleep .5
+ done
+ if [ $ok -ne 1 ] ; then
+ echo "Did not reach steady state"
+ exit 1
+ fi
+ snap debug ensure-state-soon
+
+ # wait for transition
+ ok=0
+ for _ in $(seq 240); do
+ if snap changes|grep -q ".*Done.*Transition ubuntu-core to core" ; then
+ ok=1
+ break
+ fi
+ sleep 1
+ done
+ if [ $ok -ne 1 ] ; then
+ echo "Transition did not start or finish"
+ exit 1
+ fi
+
+ if ! snap list|MATCH -v ubuntu-core; then
+ echo "ubuntu-core still installed, transition failed"
+ exit 1
+ fi
+ snap interfaces -i network | MATCH ":network.*test-snapd-python-webserver"
diff -Nru snapd-2.41+19.10.1/tests/nightly/install-snaps/task.yaml snapd-2.42.1+19.10/tests/nightly/install-snaps/task.yaml
--- snapd-2.41+19.10.1/tests/nightly/install-snaps/task.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/nightly/install-snaps/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,136 @@
+summary: Check install popular snaps
+
+details: |
+ This test is intended to install some popular snaps from
+ different channels. The idea is to detect any problem
+ installing that are currently published and some of them
+ with many revisions.
+ The execution of this test is made on the nightly build.
+
+environment:
+ # High Profile
+ SNAP/azurecli: azure-cli
+ SNAP/awscli: aws-cli
+ SNAP/heroku: heroku
+ SNAP/hiri: hiri
+ SNAP/kubectl: kubectl
+ SNAP/rocketchatserver: rocketchat-server
+ # Selected from recent insights posts
+ SNAP/corebird: corebird
+ SNAP/gitterdesktop: gitter-desktop
+ SNAP/helm: helm
+ SNAP/mattermostdesktop: mattermost-desktop
+ SNAP/mentaplexmediaserver: menta-plexmediaserver
+ SNAP/openspades: openspades
+ SNAP/pintown: pin-town
+ SNAP/postgresql10: postgresql10
+ SNAP/storjshare: storjshare
+ SNAP/slackterm: slack-term
+ SNAP/vectr: vectr
+ SNAP/wekan: wekan
+ SNAP/wormhole: wormhole
+ # Featured snaps in Ubuntu Software
+ SNAP/anboxinstaller: anbox-installer
+ SNAP/lxd: lxd
+ # Top non canonical snaps
+ SNAP/atom: atom
+ SNAP/discord: discord
+ SNAP/docker: docker
+ SNAP/etcd: etcd
+ SNAP/geocoder: geocoder
+ SNAP/gimp: gimp
+ SNAP/huggle: huggle
+ SNAP/hugo: hugo
+ SNAP/ia: ia
+ SNAP/kurly: kurly
+ SNAP/micro: micro
+ SNAP/nikola: nikola
+ SNAP/parity: parity
+ SNAP/paritybitcoin: parity-bitcoin
+ SNAP/remmina: remmina
+ SNAP/skype: skype
+ SNAP/slack: slack
+ SNAP/spotify: spotify
+ SNAP/telegramsergiusens: telegram-sergiusens
+ SNAP/zeronet: zeronet
+ SNAP/zeronetjs: zeronet-js
+ # Top canonical snaps
+ SNAP/bare: bare
+ SNAP/bluez: bluez
+ SNAP/conjureup: conjure-up
+ SNAP/gedit: gedit
+ SNAP/go: go
+ SNAP/juju: juju
+ SNAP/neutron: neutron
+ SNAP/nova: nova
+ SNAP/snapcraft: snapcraft
+ SNAP/solc: solc
+ SNAP/vault: vault
+
+prepare: |
+ cp /etc/systemd/system/snapd.service.d/local.conf /etc/systemd/system/snapd.service.d/local.conf.bak
+ sed 's/SNAPD_CONFIGURE_HOOK_TIMEOUT=.*s/SNAPD_CONFIGURE_HOOK_TIMEOUT=180s/g' -i /etc/systemd/system/snapd.service.d/local.conf
+ systemctl daemon-reload
+ systemctl restart snapd.socket
+
+ if [ ! -d '/snap' ]; then
+ #shellcheck source=tests/lib/dirs.sh
+ . "$TESTSLIB/dirs.sh"
+ ln -s "$SNAP_MOUNT_DIR" /snap
+ fi
+
+restore: |
+ mv /etc/systemd/system/snapd.service.d/local.conf.bak /etc/systemd/system/snapd.service.d/local.conf
+ systemctl daemon-reload
+ systemctl restart snapd.socket
+
+ if [ -L /snap ]; then
+ unlink /snap
+ fi
+
+ if [ "$SNAP" = lxd ]; then
+ lxd-tool undo-lxd-mount-changes
+ fi
+
+execute: |
+ #shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB/snaps.sh"
+
+ CHANNELS="stable candidate beta edge"
+ for CHANNEL in $CHANNELS; do
+ # shellcheck disable=SC2153
+ if ! CHANNEL_INFO="$(snap info --unicode=never "$SNAP" | grep " $CHANNEL: ")"; then
+ echo "Snap $SNAP not found"
+ exit
+ fi
+ if echo "$CHANNEL_INFO" | MATCH "$CHANNEL:.*--"; then
+ continue
+ fi
+
+ if echo "$CHANNEL_INFO" | MATCH "$CHANNEL:.*classic"; then
+ if is_classic_confinement_supported; then
+ snap install "$SNAP" "--$CHANNEL" --classic
+ else
+ echo "The snap $SNAP requires classic confinement which is not supported yet"
+ exit
+ fi
+ elif echo "$CHANNEL_INFO" | MATCH "$CHANNEL:.*jailmode"; then
+ snap install "$SNAP" "--$CHANNEL" --jailmode
+ elif echo "$CHANNEL_INFO" | MATCH "$CHANNEL:.*devmode"; then
+ snap install "$SNAP" "--$CHANNEL" --devmode
+ else
+ snap install "$SNAP" "--$CHANNEL"
+ fi
+ break
+ done
+
+ echo "Check the snap is properly installed"
+ snap list | MATCH "$SNAP"
+
+ echo "Check the snap is properly removed"
+ snap remove "$SNAP"
+
+ if snap list | MATCH "$SNAP"; then
+ echo "Snap $SNAP not removed properly"
+ exit 1
+ fi
diff -Nru snapd-2.41+19.10.1/tests/nightly/interfaces-openvswitch/task.yaml snapd-2.42.1+19.10/tests/nightly/interfaces-openvswitch/task.yaml
--- snapd-2.41+19.10.1/tests/nightly/interfaces-openvswitch/task.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/nightly/interfaces-openvswitch/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,111 @@
+summary: Ensure that the openvswitch interface works.
+
+# Openvswitch getting stuck during installation sporadically on ubuntu-14.04-64
+# Openvswitch service not available on the following systems
+systems: [-ubuntu-core-*, -opensuse-*, -amazon-*, -arch-linux-*, -ubuntu-14.04-64]
+
+details: |
+ The openvswitch interface allows to task to the openvswitch socket (rw mode).
+
+ A snap which defines a openvswitch plug must be shown in the interfaces list.
+ The plug must not be autoconnected on install and, as usual, must be able to be
+ reconnected.
+
+ A snap declaring a plug on this interface must be able to do all the operations that
+ are carried through the socket, in this test we exercise bridge and port creation,
+ list and deletion.
+
+prepare: |
+ #shellcheck source=tests/lib/pkgdb.sh
+ . "$TESTSLIB/pkgdb.sh"
+
+ echo "Given openvswitch is installed"
+ distro_install_package --no-install-recommends openvswitch-switch
+
+ # Ensure the openvswitch service is started which isn't the case by
+ # default on all distributions
+ if systemctl status openvswitch-switch.service | MATCH "Loaded:.*loaded"; then
+ systemctl enable --now openvswitch-switch.service
+ elif systemctl status openvswitch.service | MATCH "Loaded:.*loaded"; then
+ systemctl enable --now openvswitch.service
+ fi
+
+ echo "And a snap declaring a plug on the openvswitch interface is installed"
+ snap install --edge test-snapd-openvswitch-consumer
+
+ echo "And a tap interface is defined"
+ ip tuntap add tap1 mode tap
+
+restore: |
+ #shellcheck source=tests/lib/pkgdb.sh
+ . "$TESTSLIB/pkgdb.sh"
+
+ ovs-vsctl del-port br0 tap1 || true
+ ovs-vsctl del-br br0 || true
+
+ distro_purge_package openvswitch-switch
+ distro_auto_remove_packages
+
+ ip link delete tap1 || true
+
+execute: |
+ echo "The interface is disconnected by default"
+ snap interfaces -i openvswitch | MATCH -- '^- +test-snapd-openvswitch-consumer:openvswitch'
+
+ echo "When the plug is connected"
+ snap connect test-snapd-openvswitch-consumer:openvswitch
+
+ echo "Then the snap is able to create a bridge"
+ test-snapd-openvswitch-consumer.ovs-vsctl add-br br0
+ ovs-vsctl list-br | MATCH br0
+
+ echo "And the snap is able to create a port"
+ test-snapd-openvswitch-consumer.ovs-vsctl add-port br0 tap1
+ ovs-vsctl list-ports br0 | MATCH tap1
+
+ echo "And the snap is able to delete a port"
+ test-snapd-openvswitch-consumer.ovs-vsctl del-port br0 tap1
+ ovs-vsctl list-ports br0 | MATCH -v tap1
+
+ echo "And the snap is able to delete a bridge"
+ test-snapd-openvswitch-consumer.ovs-vsctl del-br br0
+ ovs-vsctl list-br | MATCH -v br0
+
+ if [ "$(snap debug confinement)" = partial ] ; then
+ exit 0
+ fi
+
+ echo "When the plug is disconnected"
+ snap disconnect test-snapd-openvswitch-consumer:openvswitch
+
+ echo "Then the snap is not able to create a bridge"
+ if test-snapd-openvswitch-consumer.ovs-vsctl add-br br0 2> bridge-creation.error; then
+ echo "Expected permission error accessing openvswitch socket with disconnected plug"
+ exit 1
+ fi
+ MATCH 'database connection failed \(Permission denied\)' < bridge-creation.error
+
+ ovs-vsctl add-br br0
+
+ echo "And the snap is not able to create a port"
+ if test-snapd-openvswitch-consumer.ovs-vsctl add-port br0 tap1 2> port-creation.error; then
+ echo "Expected permission error accessing openvswitch socket with disconnected plug"
+ exit 1
+ fi
+ MATCH 'database connection failed \(Permission denied\)' < port-creation.error
+
+ ovs-vsctl add-port br0 tap1
+
+ echo "And the snap is not able to delete a port"
+ if test-snapd-openvswitch-consumer.ovs-vsctl del-port br0 tap1 2> port-deletion.error; then
+ echo "Expected permission error accessing openvswitch socket with disconnected plug"
+ exit 1
+ fi
+ MATCH 'database connection failed \(Permission denied\)' < port-deletion.error
+
+ echo "And the snap is not able to delete a bridge"
+ if test-snapd-openvswitch-consumer.ovs-vsctl del-br br0 2> br-creation.error; then
+ echo "Expected permission error accessing openvswitch socket with disconnected plug"
+ exit 1
+ fi
+ MATCH 'database connection failed \(Permission denied\)' < br-creation.error
diff -Nru snapd-2.41+19.10.1/tests/nightly/sbuild/task.yaml snapd-2.42.1+19.10/tests/nightly/sbuild/task.yaml
--- snapd-2.41+19.10.1/tests/nightly/sbuild/task.yaml 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/tests/nightly/sbuild/task.yaml 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,44 @@
+summary: Ensure snapd builds correctly in sbuild
+
+# takes a while
+priority: 500
+
+environment:
+ BUILD_MODE/normal: normal
+ BUILD_MODE/any: any
+
+systems: [debian-sid-*]
+
+execute: |
+ echo "Create a sid sbuild env"
+ eatmydata sbuild-createchroot --include=eatmydata,ccache,gnupg sid /srv/chroot/sid-amd64-sbuild http://deb.debian.org/debian
+
+ echo "Allow test user to run sbuild"
+ sbuild-adduser test
+
+ BUILD_PARAM=""
+ if [ "$BUILD_MODE" == "any" ]; then
+ BUILD_PARAM="--arch-any"
+ fi
+
+ echo "Build mode: $BUILD_MODE"
+ su -c "sbuild $BUILD_PARAM -d sid --run-autopkgtest $SPREAD_PATH/../*.dsc" test
+
+restore: |
+ rm --recursive --one-file-system /srv/chroot/sid-amd64-sbuild
+ rm -f /etc/schroot/chroot.d/sid-amd64-sbuild-*
+
+debug: |
+ # Test that there's a log file and a symbolic link pointing to it.
+ # The non-symlink has a time-stamp and we can match on the "Z" timezone
+ # marker to find it.
+ test "$(find . -maxdepth 1 -name '*Z.build' | wc -l)" -ge 1 && tail -n 100 ./*Z.build
+ cat <"
+ ud.conn.Export(iface, iface.BasePath(), iface.Name())
+ ud.conn.Export(introspect.Introspectable(xml), iface.BasePath(), "org.freedesktop.DBus.Introspectable")
+
+ // beyond this point the name is available and all handlers must
+ // have been set up
reply, err := ud.conn.RequestName(iface.Name(), dbus.NameFlagDoNotQueue)
if err != nil {
return err
@@ -80,10 +90,6 @@
if reply != dbus.RequestNameReplyPrimaryOwner {
return fmt.Errorf("cannot obtain bus name '%s'", iface.Name())
}
-
- xml := "" + iface.IntrospectionData() + introspect.IntrospectDataString + ""
- ud.conn.Export(iface, iface.BasePath(), iface.Name())
- ud.conn.Export(introspect.Introspectable(xml), iface.BasePath(), "org.freedesktop.DBus.Introspectable")
}
return nil
}
diff -Nru snapd-2.41+19.10.1/wrappers/desktop.go snapd-2.42.1+19.10/wrappers/desktop.go
--- snapd-2.41+19.10.1/wrappers/desktop.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/wrappers/desktop.go 2019-10-30 12:17:43.000000000 +0000
@@ -142,6 +142,36 @@
return "", fmt.Errorf("invalid exec command: %q", cmd)
}
+func rewriteIconLine(s *snap.Info, line string) (string, error) {
+ icon := strings.SplitN(line, "=", 2)[1]
+
+ // If there is a path separator, assume the icon is a path name
+ if strings.ContainsRune(icon, filepath.Separator) {
+ if !strings.HasPrefix(icon, "${SNAP}/") {
+ return "", fmt.Errorf("icon path %q is not part of the snap", icon)
+ }
+ if filepath.Clean(icon) != icon {
+ return "", fmt.Errorf("icon path %q is not canonicalized, did you mean %q?", icon, filepath.Clean(icon))
+ }
+ return line, nil
+ }
+
+ // If the icon is prefixed with "snap.${SNAP_NAME}.", rewrite
+ // to the instance name.
+ snapIconPrefix := fmt.Sprintf("snap.%s.", s.SnapName())
+ if strings.HasPrefix(icon, snapIconPrefix) {
+ return fmt.Sprintf("Icon=snap.%s.%s", s.InstanceName(), icon[len(snapIconPrefix):]), nil
+ }
+
+ // If the icon has any other "snap." prefix, treat this as an error.
+ if strings.HasPrefix(icon, "snap.") {
+ return "", fmt.Errorf("invalid icon name: %q, must start with %q", icon, snapIconPrefix)
+ }
+
+ // Allow other icons names through unchanged.
+ return line, nil
+}
+
func sanitizeDesktopFile(s *snap.Info, desktopFile string, rawcontent []byte) []byte {
var newContent bytes.Buffer
mountDir := []byte(s.MountDir())
@@ -163,6 +193,16 @@
continue
}
bline = []byte(line)
+ }
+
+ // rewrite icon line if it references an icon theme icon
+ if bytes.HasPrefix(bline, []byte("Icon=")) {
+ line, err := rewriteIconLine(s, string(bline))
+ if err != nil {
+ logger.Debugf("ignoring icon in source desktop file %q: %s", filepath.Base(desktopFile), err)
+ continue
+ }
+ bline = []byte(line)
}
// do variable substitution
diff -Nru snapd-2.41+19.10.1/wrappers/desktop_test.go snapd-2.42.1+19.10/wrappers/desktop_test.go
--- snapd-2.41+19.10.1/wrappers/desktop_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/wrappers/desktop_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -482,6 +482,68 @@
}
}
+func (s *sanitizeDesktopFileSuite) TestRewriteIconLine(c *C) {
+ snap, err := snap.InfoFromSnapYaml([]byte(`
+name: snap
+version: 1.0
+`))
+ c.Assert(err, IsNil)
+
+ newl, err := wrappers.RewriteIconLine(snap, "Icon=${SNAP}/icon.png")
+ c.Check(err, IsNil)
+ c.Check(newl, Equals, "Icon=${SNAP}/icon.png")
+
+ newl, err = wrappers.RewriteIconLine(snap, "Icon=snap.snap.icon")
+ c.Check(err, IsNil)
+ c.Check(newl, Equals, "Icon=snap.snap.icon")
+
+ newl, err = wrappers.RewriteIconLine(snap, "Icon=other-icon")
+ c.Check(err, IsNil)
+ c.Check(newl, Equals, "Icon=other-icon")
+
+ snap.InstanceKey = "bar"
+ newl, err = wrappers.RewriteIconLine(snap, "Icon=snap.snap.icon")
+ c.Check(err, IsNil)
+ c.Check(newl, Equals, "Icon=snap.snap_bar.icon")
+
+ _, err = wrappers.RewriteIconLine(snap, "Icon=snap.othersnap.icon")
+ c.Check(err, ErrorMatches, `invalid icon name: "snap.othersnap.icon", must start with "snap.snap."`)
+
+ _, err = wrappers.RewriteIconLine(snap, "Icon=/etc/passwd")
+ c.Check(err, ErrorMatches, `icon path "/etc/passwd" is not part of the snap`)
+
+ _, err = wrappers.RewriteIconLine(snap, "Icon=${SNAP}/./icon.png")
+ c.Check(err, ErrorMatches, `icon path "\${SNAP}/./icon.png" is not canonicalized, did you mean "\${SNAP}/icon.png"\?`)
+
+ _, err = wrappers.RewriteIconLine(snap, "Icon=${SNAP}/../outside/icon.png")
+ c.Check(err, ErrorMatches, `icon path "\${SNAP}/../outside/icon.png" is not canonicalized, did you mean "outside/icon.png"\?`)
+}
+
+func (s *sanitizeDesktopFileSuite) TestSanitizeParallelInstancesIconName(c *C) {
+ snap, err := snap.InfoFromSnapYaml([]byte(`
+name: snap
+version: 1.0
+apps:
+ app:
+ command: cmd
+`))
+ snap.InstanceKey = "bar"
+ c.Assert(err, IsNil)
+ desktopContent := []byte(`[Desktop Entry]
+Name=foo
+Icon=snap.snap.icon
+Exec=snap.app
+`)
+ df := filepath.Base(snap.Apps["app"].DesktopFile())
+ e := wrappers.SanitizeDesktopFile(snap, df, desktopContent)
+ c.Assert(string(e), Equals, fmt.Sprintf(`[Desktop Entry]
+X-SnapInstanceName=snap_bar
+Name=foo
+Icon=snap.snap_bar.icon
+Exec=env BAMF_DESKTOP_FILE_HINT=snap_bar_app.desktop %s/bin/snap_bar.app
+`, dirs.SnapMountDir))
+}
+
func (s *desktopSuite) TestAddRemoveDesktopFiles(c *C) {
var tests = []struct {
instance string
diff -Nru snapd-2.41+19.10.1/wrappers/export_test.go snapd-2.42.1+19.10/wrappers/export_test.go
--- snapd-2.41+19.10.1/wrappers/export_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/wrappers/export_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -33,10 +33,14 @@
// desktop
SanitizeDesktopFile = sanitizeDesktopFile
RewriteExecLine = rewriteExecLine
+ RewriteIconLine = rewriteIconLine
IsValidDesktopFileLine = isValidDesktopFileLine
// timers
GenerateOnCalendarSchedules = generateOnCalendarSchedules
+
+ // icons
+ FindIconFiles = findIconFiles
)
func MockKillWait(wait time.Duration) (restore func()) {
diff -Nru snapd-2.41+19.10.1/wrappers/icons.go snapd-2.42.1+19.10/wrappers/icons.go
--- snapd-2.41+19.10.1/wrappers/icons.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/wrappers/icons.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,126 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package wrappers
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/snap"
+)
+
+func findIconFiles(snapName string, rootDir string) (icons []string, err error) {
+ if !osutil.IsDirectory(rootDir) {
+ return nil, nil
+ }
+ iconGlob := fmt.Sprintf("snap.%s.*", snapName)
+ forbiddenDirGlob := "snap.*"
+ err = filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ rel, err := filepath.Rel(rootDir, path)
+ if err != nil {
+ return err
+ }
+ base := filepath.Base(path)
+ if info.IsDir() {
+ // Ignore directories that could match an icon glob
+ if ok, err := filepath.Match(forbiddenDirGlob, base); ok || err != nil {
+ return filepath.SkipDir
+ }
+ } else {
+ if ok, err := filepath.Match(iconGlob, base); err != nil {
+ return err
+ } else if ok {
+ ext := filepath.Ext(base)
+ if ext == ".png" || ext == ".svg" {
+ icons = append(icons, rel)
+ }
+ }
+ }
+ return nil
+ })
+ return icons, err
+}
+
+func deriveIconContent(instanceName string, rootDir string, icons []string) (content map[string]map[string]*osutil.FileState, err error) {
+ content = make(map[string]map[string]*osutil.FileState)
+ snapPrefix := fmt.Sprintf("snap.%s.", snap.InstanceSnap(instanceName))
+ instancePrefix := fmt.Sprintf("snap.%s.", instanceName)
+
+ for _, iconFile := range icons {
+ dir := filepath.Dir(iconFile)
+ base := filepath.Base(iconFile)
+ dirContent := content[dir]
+ if dirContent == nil {
+ dirContent = make(map[string]*osutil.FileState)
+ content[dir] = dirContent
+ }
+ data, err := ioutil.ReadFile(filepath.Join(rootDir, iconFile))
+ if err != nil {
+ return nil, err
+ }
+ if !strings.HasPrefix(base, snapPrefix) {
+ return nil, fmt.Errorf("cannot use icon file %q: must start with snap prefix %q", iconFile, snapPrefix)
+ }
+ // rename icons to match snap instance name
+ base = instancePrefix + base[len(snapPrefix):]
+ dirContent[base] = &osutil.FileState{
+ Content: data,
+ Mode: 0644,
+ }
+ }
+ return content, nil
+}
+
+func AddSnapIcons(s *snap.Info) error {
+ if err := os.MkdirAll(dirs.SnapDesktopIconsDir, 0755); err != nil {
+ return err
+ }
+
+ rootDir := filepath.Join(s.MountDir(), "meta", "gui", "icons")
+ icons, err := findIconFiles(s.SnapName(), rootDir)
+ if err != nil {
+ return err
+ }
+
+ content, err := deriveIconContent(s.InstanceName(), rootDir, icons)
+ if err != nil {
+ return err
+ }
+ iconGlob := fmt.Sprintf("snap.%s.*", s.InstanceName())
+ _, _, err = osutil.EnsureTreeState(dirs.SnapDesktopIconsDir, []string{iconGlob}, content)
+ return err
+}
+
+func RemoveSnapIcons(s *snap.Info) error {
+ if !osutil.IsDirectory(dirs.SnapDesktopIconsDir) {
+ return nil
+ }
+ iconGlob := fmt.Sprintf("snap.%s.*", s.InstanceName())
+ _, _, err := osutil.EnsureTreeState(dirs.SnapDesktopIconsDir, []string{iconGlob}, nil)
+ return err
+}
diff -Nru snapd-2.41+19.10.1/wrappers/icons_test.go snapd-2.42.1+19.10/wrappers/icons_test.go
--- snapd-2.41+19.10.1/wrappers/icons_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.42.1+19.10/wrappers/icons_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -0,0 +1,137 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package wrappers_test
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "sort"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/snaptest"
+ "github.com/snapcore/snapd/testutil"
+ "github.com/snapcore/snapd/wrappers"
+)
+
+type iconsTestSuite struct {
+ testutil.BaseTest
+ tempdir string
+}
+
+var _ = Suite(&iconsTestSuite{})
+
+func (s *iconsTestSuite) SetUpTest(c *C) {
+ s.BaseTest.SetUpTest(c)
+ s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
+ s.tempdir = c.MkDir()
+ dirs.SetRootDir(s.tempdir)
+}
+
+func (s *iconsTestSuite) TearDownTest(c *C) {
+ dirs.SetRootDir("")
+ s.BaseTest.TearDownTest(c)
+}
+
+func (s *iconsTestSuite) TestFindIconFiles(c *C) {
+ info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(11)})
+
+ baseDir := info.MountDir()
+ iconsDir := filepath.Join(baseDir, "meta", "gui", "icons")
+ c.Assert(os.MkdirAll(filepath.Join(iconsDir, "hicolor", "256x256", "apps"), 0755), IsNil)
+ c.Assert(os.MkdirAll(filepath.Join(iconsDir, "hicolor", "scalable", "apps"), 0755), IsNil)
+ c.Assert(ioutil.WriteFile(filepath.Join(iconsDir, "hicolor", "256x256", "apps", "snap.hello-snap.foo.png"), []byte("256x256"), 0644), IsNil)
+ c.Assert(ioutil.WriteFile(filepath.Join(iconsDir, "hicolor", "scalable", "apps", "snap.hello-snap.foo.svg"), []byte("scalable"), 0644), IsNil)
+
+ // Some files that shouldn't be picked up
+ c.Assert(ioutil.WriteFile(filepath.Join(iconsDir, "hicolor", "scalable", "apps", "snap.hello-snap.foo.exe"), []byte("bad extension"), 0644), IsNil)
+ c.Assert(ioutil.WriteFile(filepath.Join(iconsDir, "hicolor", "scalable", "apps", "org.example.hello.png"), []byte("bad prefix"), 0644), IsNil)
+ c.Assert(os.MkdirAll(filepath.Join(iconsDir, "snap.whatever"), 0755), IsNil)
+ c.Assert(ioutil.WriteFile(filepath.Join(iconsDir, "snap.whatever", "snap.hello-snap.foo.png"), []byte("bad dir"), 0644), IsNil)
+
+ icons, err := wrappers.FindIconFiles(info.SnapName(), iconsDir)
+ sort.Strings(icons)
+ c.Assert(err, IsNil)
+ c.Check(icons, DeepEquals, []string{
+ "hicolor/256x256/apps/snap.hello-snap.foo.png",
+ "hicolor/scalable/apps/snap.hello-snap.foo.svg",
+ })
+}
+
+func (s *iconsTestSuite) TestAddSnapIcons(c *C) {
+ info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(11)})
+
+ baseDir := info.MountDir()
+ iconsDir := filepath.Join(baseDir, "meta", "gui", "icons")
+ c.Assert(os.MkdirAll(filepath.Join(iconsDir, "hicolor", "scalable", "apps"), 0755), IsNil)
+ c.Assert(ioutil.WriteFile(filepath.Join(iconsDir, "hicolor", "scalable", "apps", "snap.hello-snap.foo.svg"), []byte("scalable"), 0644), IsNil)
+
+ c.Assert(wrappers.AddSnapIcons(info), IsNil)
+ iconFile := filepath.Join(dirs.SnapDesktopIconsDir, "hicolor", "scalable", "apps", "snap.hello-snap.foo.svg")
+ c.Check(iconFile, testutil.FileEquals, "scalable")
+}
+
+func (s *iconsTestSuite) TestRemoveSnapIcons(c *C) {
+ iconDir := filepath.Join(dirs.SnapDesktopIconsDir, "hicolor", "scalable", "apps")
+ iconFile := filepath.Join(iconDir, "snap.hello-snap.foo.svg")
+ c.Assert(os.MkdirAll(iconDir, 0755), IsNil)
+ c.Assert(ioutil.WriteFile(iconFile, []byte("contents"), 0644), IsNil)
+
+ info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(11)})
+ c.Assert(wrappers.RemoveSnapIcons(info), IsNil)
+ c.Check(iconFile, testutil.FileAbsent)
+}
+
+func (s *iconsTestSuite) TestParallelInstanceAddIcons(c *C) {
+ info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(11)})
+ info.InstanceKey = "instance"
+
+ baseDir := info.MountDir()
+ iconsDir := filepath.Join(baseDir, "meta", "gui", "icons")
+ c.Assert(os.MkdirAll(filepath.Join(iconsDir, "hicolor", "scalable", "apps"), 0755), IsNil)
+ c.Assert(ioutil.WriteFile(filepath.Join(iconsDir, "hicolor", "scalable", "apps", "snap.hello-snap.foo.svg"), []byte("scalable"), 0644), IsNil)
+
+ c.Assert(wrappers.AddSnapIcons(info), IsNil)
+ iconFile := filepath.Join(dirs.SnapDesktopIconsDir, "hicolor", "scalable", "apps", "snap.hello-snap_instance.foo.svg")
+ c.Check(iconFile, testutil.FileEquals, "scalable")
+
+ // No file installed without the instance qualifier
+ iconFile = filepath.Join(dirs.SnapDesktopIconsDir, "hicolor", "scalable", "apps", "snap.hello-snap.foo.svg")
+ c.Check(iconFile, testutil.FileAbsent)
+}
+
+func (s *iconsTestSuite) TestParallelInstanceRemoveIcons(c *C) {
+ iconDir := filepath.Join(dirs.SnapDesktopIconsDir, "hicolor", "scalable", "apps")
+ c.Assert(os.MkdirAll(iconDir, 0755), IsNil)
+ snapNameFile := filepath.Join(iconDir, "snap.hello-snap.foo.svg")
+ c.Assert(ioutil.WriteFile(snapNameFile, []byte("contents"), 0644), IsNil)
+ instanceNameFile := filepath.Join(iconDir, "snap.hello-snap_instance.foo.svg")
+ c.Assert(ioutil.WriteFile(instanceNameFile, []byte("contents"), 0644), IsNil)
+
+ info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(11)})
+ info.InstanceKey = "instance"
+ c.Assert(wrappers.RemoveSnapIcons(info), IsNil)
+ c.Check(instanceNameFile, testutil.FileAbsent)
+ // The non-instance qualified icon remains
+ c.Check(snapNameFile, testutil.FilePresent)
+}
diff -Nru snapd-2.41+19.10.1/wrappers/services.go snapd-2.42.1+19.10/wrappers/services.go
--- snapd-2.41+19.10.1/wrappers/services.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/wrappers/services.go 2019-10-30 12:17:43.000000000 +0000
@@ -335,7 +335,27 @@
}
return nil
+}
+
+// ServicesEnableState returns a map of service names from the given snap,
+// together with their enable/disable status.
+func ServicesEnableState(s *snap.Info, inter interacter) (map[string]bool, error) {
+ sysd := systemd.New(dirs.GlobalRootDir, systemd.SystemMode, inter)
+ // loop over all services in the snap, querying systemd for the current
+ // systemd state of the snaps
+ snapSvcsState := make(map[string]bool, len(s.Apps))
+ for name, app := range s.Apps {
+ if !app.IsService() {
+ continue
+ }
+ state, err := sysd.IsEnabled(app.ServiceName())
+ if err != nil {
+ return nil, err
+ }
+ snapSvcsState[name] = state
+ }
+ return snapSvcsState, nil
}
// RemoveSnapServices disables and removes service units for the applications from the snap which are services.
diff -Nru snapd-2.41+19.10.1/wrappers/services_test.go snapd-2.42.1+19.10/wrappers/services_test.go
--- snapd-2.41+19.10.1/wrappers/services_test.go 2019-08-30 06:56:16.000000000 +0000
+++ snapd-2.42.1+19.10/wrappers/services_test.go 2019-10-30 12:17:43.000000000 +0000
@@ -189,6 +189,111 @@
})
}
+func (s *servicesTestSuite) TestServicesEnableState(c *C) {
+ info := snaptest.MockSnap(c, packageHello+`
+ svc2:
+ command: bin/hello
+ daemon: forking
+`, &snap.SideInfo{Revision: snap.R(12)})
+ svc1File := "snap.hello-snap.svc1.service"
+ svc2File := "snap.hello-snap.svc2.service"
+
+ s.systemctlRestorer()
+ r := testutil.MockCommand(c, "systemctl", `#!/bin/sh
+ if [ "$1" = "--root" ]; then
+ # shifting by 2 also drops the temp dir arg to --root
+ shift 2
+ fi
+
+ case "$1" in
+ is-enabled)
+ case "$2" in
+ "snap.hello-snap.svc1.service")
+ echo "disabled"
+ exit 1
+ ;;
+ "snap.hello-snap.svc2.service")
+ echo "enabled"
+ exit 0
+ ;;
+ *)
+ echo "unexpected service $*"
+ exit 2
+ ;;
+ esac
+ ;;
+ *)
+ echo "unexpected op $*"
+ exit 2
+ esac
+
+ exit 1
+ `)
+ defer r.Restore()
+
+ states, err := wrappers.ServicesEnableState(info, progress.Null)
+ c.Assert(err, IsNil)
+
+ c.Assert(states, DeepEquals, map[string]bool{
+ "svc1": false,
+ "svc2": true,
+ })
+
+ // the calls could be out of order in the list, since iterating over a map
+ // is non-deterministic, so manually check each call
+ c.Assert(r.Calls(), HasLen, 2)
+ for _, call := range r.Calls() {
+ c.Assert(call, HasLen, 5)
+ c.Assert(call[:4], DeepEquals, []string{"systemctl", "--root", s.tempdir, "is-enabled"})
+ switch call[4] {
+ case svc1File, svc2File:
+ default:
+ c.Errorf("unknown service for systemctl call: %s", call[4])
+ }
+ }
+}
+
+func (s *servicesTestSuite) TestServicesEnableStateFail(c *C) {
+ info := snaptest.MockSnap(c, packageHello, &snap.SideInfo{Revision: snap.R(12)})
+ svc1File := "snap.hello-snap.svc1.service"
+
+ s.systemctlRestorer()
+ r := testutil.MockCommand(c, "systemctl", `#!/bin/sh
+ if [ "$1" = "--root" ]; then
+ # shifting by 2 also drops the temp dir arg to --root
+ shift 2
+ fi
+
+ case "$1" in
+ is-enabled)
+ case "$2" in
+ "snap.hello-snap.svc1.service")
+ echo "whoops"
+ exit 1
+ ;;
+ *)
+ echo "unexpected service $*"
+ exit 2
+ ;;
+ esac
+ ;;
+ *)
+ echo "unexpected op $*"
+ exit 2
+ esac
+
+ exit 1
+ `)
+ defer r.Restore()
+
+ _, err := wrappers.ServicesEnableState(info, progress.Null)
+ c.Assert(err, ErrorMatches, ".*is-enabled snap.hello-snap.svc1.service\\] failed with exit status 1: whoops\n.*")
+
+ c.Assert(r.Calls(), DeepEquals, [][]string{
+ {"systemctl", "--root", s.tempdir, "is-enabled", svc1File},
+ })
+}
+
func (s *servicesTestSuite) TestStopServicesWithSockets(c *C) {
var sysdLog []string
r := systemd.MockSystemctl(func(cmd ...string) ([]byte, error) {