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 <" + iface.IntrospectionData() + introspect.IntrospectDataString + "" + 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) {