+ elems = attr_class[0].split(" ")
+ if "labels" in elems:
+ self.in_labels = True
+ self.deep = 1
+ logging.debug("labels start")
+ else:
+ # nesting counter
+ self.deep += 1
+
+ # inside labels
+ # label entry has
+ #
+ attr_data_name = [attr[1] for attr in attributes if attr[0] == "data-name"]
+ if len(attr_data_name) == 0:
+ return
+ data_name = attr_data_name[0]
+ logging.debug("found label: %s", data_name)
+ self.labels.append(data_name)
+
+ def handle_endtag(self, tag):
+ if self.in_labels:
+ self.deep -= 1
+ if self.deep < 1:
+ logging.debug("labels end")
+ self.in_labels = False
+
+ def handle_data(self, data):
+ if self.in_labels:
+ logging.debug("data: %s", data)
+
+
+def grab_pr_labels(pr_number: int):
+ # ideally we would use the github API - however we can't because:
+ # a) its rate limiting and travis IPs hit the API a lot so we regularly
+ # get errors
+ # b) using a API token is tricky because travis will not allow the secure
+ # vars for forks
+ # so instead we just scrape the html title which is unlikely to change
+ # radically
+ parser = GithubLabelsParser()
+ with urllib.request.urlopen(
+ "https://github.com/snapcore/snapd/pull/{}".format(pr_number)
+ ) as f:
+ parser.feed(f.read().decode("utf-8"))
+ return parser.labels
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "pr_number", metavar="PR number", help="the github PR number to check"
+ )
+ parser.add_argument(
+ "-d", "--debug", help="enable debug logging", action="store_true"
+ )
+ args = parser.parse_args()
+
+ lvl = logging.INFO
+ if args.debug:
+ lvl = logging.DEBUG
+ logging.basicConfig(level=lvl)
+
+ labels = grab_pr_labels(args.pr_number)
+ print("labels:", labels)
+
+ if LABEL_SKIP_SPREAD_JOB not in labels:
+ raise SystemExit(1)
+
+ print("requested to skip the spread job")
+
+
+if __name__ == "__main__":
+ main()
diff -Nru snapd-2.42.1+18.04/check-pr-title.py snapd-2.45.1+18.04/check-pr-title.py
--- snapd-2.42.1+18.04/check-pr-title.py 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/check-pr-title.py 2020-06-05 13:13:49.000000000 +0000
@@ -1,15 +1,12 @@
#!/usr/bin/python3
import argparse
-import base64
-import json
-import os
import re
-import sys
import urllib.request
from html.parser import HTMLParser
+
class InvalidPRTitle(Exception):
def __init__(self, invalid_title):
self.invalid_title = invalid_title
@@ -20,10 +17,13 @@
HTMLParser.__init__(self)
self._cur_tag = ""
self.title = ""
+
def handle_starttag(self, tag, attributes):
self._cur_tag = tag
+
def handle_endtag(self, tag):
self._cur_tag = ""
+
def handle_data(self, data):
if self._cur_tag == "title":
self.title = data
@@ -38,7 +38,9 @@
# so instead we just scrape the html title which is unlikely to change
# radically
parser = GithubTitleParser()
- with urllib.request.urlopen('https://github.com/snapcore/snapd/pull/{}'.format(pr_number)) as f:
+ with urllib.request.urlopen(
+ "https://github.com/snapcore/snapd/pull/{}".format(pr_number)
+ ) as f:
parser.feed(f.read().decode("utf-8"))
# the title has the format:
# "Added api endpoint for downloading snaps by glower · Pull Request #6958 · snapcore/snapd · GitHub"
@@ -51,24 +53,25 @@
# package, otherpackage/subpackage: this is a title
# tests/regression/lp-12341234: foo
# [RFC] foo: bar
- if not re.match(r'[a-zA-Z0-9_\-/,. \[\]{}]+: .*', title):
+ if not re.match(r"[a-zA-Z0-9_\-/,. \[\]{}]+: .*", title):
raise InvalidPRTitle(title)
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
- 'pr_number', metavar='PR number', help='the github PR number to check')
+ "pr_number", metavar="PR number", help="the github PR number to check"
+ )
args = parser.parse_args()
try:
check_pr_title(args.pr_number)
except InvalidPRTitle as e:
- print("Invalid PR title: \"{}\"\n".format(e.invalid_title))
+ print('Invalid PR title: "{}"\n'.format(e.invalid_title))
print("Please provide a title in the following format:")
print("module: short description")
print("E.g.:")
print("daemon: fix frobinator bug")
- sys.exit(1)
+ raise SystemExit(1)
if __name__ == "__main__":
diff -Nru snapd-2.42.1+18.04/client/apps.go snapd-2.45.1+18.04/client/apps.go
--- snapd-2.42.1+18.04/client/apps.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/client/apps.go 2020-06-05 13:13:49.000000000 +0000
@@ -22,6 +22,7 @@
import (
"bufio"
"bytes"
+ "context"
"encoding/json"
"errors"
"fmt"
@@ -119,7 +120,7 @@
query.Set("follow", strconv.FormatBool(opts.Follow))
}
- rsp, err := client.raw("GET", "/v2/logs", query, nil, nil)
+ rsp, err := client.raw(context.Background(), "GET", "/v2/logs", query, nil, nil)
if err != nil {
return nil, err
}
diff -Nru snapd-2.42.1+18.04/client/apps_test.go snapd-2.45.1+18.04/client/apps_test.go
--- snapd-2.42.1+18.04/client/apps_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/client/apps_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -107,6 +107,11 @@
ch, err := cs.cli.Logs([]string{"foo", "bar"}, client.LogOptions{N: -1, Follow: false})
c.Check(cs.req.URL.Path, check.Equals, "/v2/logs")
c.Check(cs.req.Method, check.Equals, "GET")
+
+ // logs cannot have a deadline because of "-f"
+ _, ok := cs.req.Context().Deadline()
+ c.Check(ok, check.Equals, false)
+
query := cs.req.URL.Query()
c.Check(query, check.HasLen, 2)
c.Check(query.Get("names"), check.Equals, "foo,bar")
diff -Nru snapd-2.42.1+18.04/client/asserts.go snapd-2.45.1+18.04/client/asserts.go
--- snapd-2.42.1+18.04/client/asserts.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/client/asserts.go 2020-06-05 13:13:49.000000000 +0000
@@ -21,11 +21,14 @@
import (
"bytes"
+ "context"
"fmt"
"io"
"net/url"
"strconv"
+ "golang.org/x/xerrors"
+
"github.com/snapcore/snapd/asserts" // for parsing
"github.com/snapcore/snapd/snap"
)
@@ -50,14 +53,25 @@
}
_, err := client.doSync("GET", "/v2/assertions", nil, nil, nil, &types)
if err != nil {
- return nil, fmt.Errorf("cannot get assertion type names: %v", err)
+ fmt := "cannot get assertion type names: %w"
+ return nil, xerrors.Errorf(fmt, err)
}
return types.Types, nil
}
+// KnownOptions represent the options of the Known call.
+type KnownOptions struct {
+ // If Remote is true, the store is queried to find the assertion
+ Remote bool
+}
+
// Known queries assertions with type assertTypeName and matching assertion headers.
-func (client *Client) Known(assertTypeName string, headers map[string]string) ([]asserts.Assertion, error) {
+func (client *Client) Known(assertTypeName string, headers map[string]string, opts *KnownOptions) ([]asserts.Assertion, error) {
+ if opts == nil {
+ opts = &KnownOptions{}
+ }
+
path := fmt.Sprintf("/v2/assertions/%s", assertTypeName)
q := url.Values{}
@@ -66,11 +80,18 @@
q.Set(k, v)
}
}
+ if opts.Remote {
+ q.Set("remote", "true")
+ }
- response, err := client.raw("GET", path, q, nil, nil)
+ ctx, cancel := context.WithTimeout(context.Background(), doTimeout)
+ defer cancel()
+ response, err := client.raw(ctx, "GET", path, q, nil, nil)
if err != nil {
- return nil, fmt.Errorf("failed to query assertions: %v", err)
+ fmt := "failed to query assertions: %w"
+ return nil, xerrors.Errorf(fmt, err)
}
+
defer response.Body.Close()
if response.StatusCode != 200 {
return nil, parseError(response)
@@ -106,7 +127,7 @@
// 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})
+ assertions, err := client.Known("account", map[string]string{"account-id": accountID}, nil)
if err != nil {
return nil, err
}
diff -Nru snapd-2.42.1+18.04/client/asserts_test.go snapd-2.45.1+18.04/client/asserts_test.go
--- snapd-2.42.1+18.04/client/asserts_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/client/asserts_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -25,9 +25,12 @@
"net/http"
"net/url"
+ "golang.org/x/xerrors"
+
. "gopkg.in/check.v1"
"github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/client"
"github.com/snapcore/snapd/snap"
)
@@ -61,16 +64,23 @@
}
func (cs *clientSuite) TestClientAssertsCallsEndpoint(c *C) {
- _, _ = cs.cli.Known("snap-revision", nil)
+ _, _ = cs.cli.Known("snap-revision", nil, nil)
+ c.Check(cs.req.Method, Equals, "GET")
+ c.Check(cs.req.URL.Path, Equals, "/v2/assertions/snap-revision")
+}
+
+func (cs *clientSuite) TestClientAssertsOptsCallsEndpoint(c *C) {
+ _, _ = cs.cli.Known("snap-revision", nil, &client.KnownOptions{Remote: true})
c.Check(cs.req.Method, Equals, "GET")
c.Check(cs.req.URL.Path, Equals, "/v2/assertions/snap-revision")
+ c.Check(cs.req.URL.Query()["remote"], DeepEquals, []string{"true"})
}
func (cs *clientSuite) TestClientAssertsCallsEndpointWithFilter(c *C) {
_, _ = cs.cli.Known("snap-revision", map[string]string{
"snap-id": "snap-id-1",
"snap-sha3-384": "sha3-384...",
- })
+ }, nil)
u, err := url.ParseRequestURI(cs.req.URL.String())
c.Assert(err, IsNil)
c.Check(u.Path, Equals, "/v2/assertions/snap-revision")
@@ -82,7 +92,7 @@
func (cs *clientSuite) TestClientAssertsHttpError(c *C) {
cs.err = errors.New("fail")
- _, err := cs.cli.Known("snap-build", nil)
+ _, err := cs.cli.Known("snap-build", nil, nil)
c.Assert(err, ErrorMatches, "failed to query assertions: cannot communicate with server: fail")
}
@@ -97,7 +107,7 @@
"message": "invalid"
}
}`
- _, err := cs.cli.Known("snap-build", nil)
+ _, err := cs.cli.Known("snap-build", nil, nil)
c.Assert(err, ErrorMatches, "invalid")
}
@@ -133,7 +143,7 @@
openpgp ...
`
- a, err := cs.cli.Known("snap-revision", nil)
+ a, err := cs.cli.Known("snap-revision", nil, nil)
c.Assert(err, IsNil)
c.Check(a, HasLen, 2)
@@ -145,7 +155,7 @@
cs.header.Add("X-Ubuntu-Assertions-Count", "0")
cs.rsp = ""
cs.status = 200
- a, err := cs.cli.Known("snap-revision", nil)
+ a, err := cs.cli.Known("snap-revision", nil, nil)
c.Assert(err, IsNil)
c.Check(a, HasLen, 0)
}
@@ -155,7 +165,7 @@
cs.header.Add("X-Ubuntu-Assertions-Count", "4")
cs.rsp = ""
cs.status = 200
- _, err := cs.cli.Known("snap-build", nil)
+ _, err := cs.cli.Known("snap-build", nil, nil)
c.Assert(err, ErrorMatches, "response did not have the expected number of assertions")
}
@@ -213,3 +223,17 @@
_, err := cs.cli.StoreAccount("canonicalID")
c.Assert(err, ErrorMatches, "no assertion found for account-id canonicalID")
}
+
+func (cs *clientSuite) TestClientAssertTypesErrIsWrapped(c *C) {
+ cs.err = errors.New("boom")
+ _, err := cs.cli.AssertionTypes()
+ var e xerrors.Wrapper
+ c.Assert(err, Implements, &e)
+}
+
+func (cs *clientSuite) TestClientKnownErrIsWrapped(c *C) {
+ cs.err = errors.New("boom")
+ _, err := cs.cli.Known("foo", nil, nil)
+ var e xerrors.Wrapper
+ c.Assert(err, Implements, &e)
+}
diff -Nru snapd-2.42.1+18.04/client/client.go snapd-2.45.1+18.04/client/client.go
--- snapd-2.42.1+18.04/client/client.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/client/client.go 2020-06-05 13:13:49.000000000 +0000
@@ -21,6 +21,7 @@
import (
"bytes"
+ "context"
"encoding/json"
"fmt"
"io"
@@ -179,10 +180,23 @@
return fmt.Sprintf("cannot add authorization: %v", e.error)
}
-type ConnectionError struct{ error }
+type ConnectionError struct{ Err error }
func (e ConnectionError) Error() string {
- return fmt.Sprintf("cannot communicate with server: %v", e.error)
+ var errStr string
+ switch e.Err {
+ case context.DeadlineExceeded:
+ errStr = "timeout exceeded while waiting for response"
+ case context.Canceled:
+ errStr = "request canceled"
+ default:
+ errStr = e.Err.Error()
+ }
+ return fmt.Sprintf("cannot communicate with server: %s", errStr)
+}
+
+func (e ConnectionError) Unwrap() error {
+ return e.Err
}
// AllowInteractionHeader is the HTTP request header used to indicate
@@ -192,7 +206,7 @@
// raw performs a request and returns the resulting http.Response and
// error you usually only need to call this directly if you expect the
// response to not be JSON, otherwise you'd call Do(...) instead.
-func (client *Client) raw(method, urlpath string, query url.Values, headers map[string]string, body io.Reader) (*http.Response, error) {
+func (client *Client) raw(ctx context.Context, method, urlpath string, query url.Values, headers map[string]string, body io.Reader) (*http.Response, error) {
// fake a url to keep http.Client happy
u := client.baseURL
u.Path = path.Join(client.baseURL.Path, urlpath)
@@ -221,6 +235,10 @@
req.Header.Set(AllowInteractionHeader, "true")
}
+ if ctx != nil {
+ req = req.WithContext(ctx)
+ }
+
rsp, err := client.doer.Do(req)
if err != nil {
return nil, ConnectionError{err}
@@ -229,13 +247,36 @@
return rsp, nil
}
+// rawWithTimeout is like raw(), but sets a timeout for the whole of request and
+// response (including rsp.Body() read) round trip. The caller is responsible
+// for canceling the internal context to release the resources associated with
+// the request by calling the returned cancel function.
+func (client *Client) rawWithTimeout(ctx context.Context, method, urlpath string, query url.Values, headers map[string]string, body io.Reader, timeout time.Duration) (*http.Response, context.CancelFunc, error) {
+ if timeout == 0 {
+ return nil, nil, fmt.Errorf("internal error: timeout not set for rawWithTimeout")
+ }
+
+ ctx, cancel := context.WithTimeout(ctx, timeout)
+ rsp, err := client.raw(ctx, method, urlpath, query, headers, body)
+ if err != nil && ctx.Err() != nil {
+ cancel()
+ return nil, nil, ConnectionError{ctx.Err()}
+ }
+
+ return rsp, cancel, err
+}
+
var (
- doRetry = 250 * time.Millisecond
- doTimeout = 5 * time.Second
+ doRetry = 250 * time.Millisecond
+ // snapd may need to reach out to the store, where it uses a fixed 10s
+ // timeout for the whole of a single request to complete, requests are
+ // retried for up to 38s in total, make sure that the client timeout is
+ // not shorter than that
+ doTimeout = 50 * time.Second
)
-// MockDoRetry mocks the delays used by the do retry loop.
-func MockDoRetry(retry, timeout time.Duration) (restore func()) {
+// MockDoTimings mocks the delay used by the do retry loop and request timeout.
+func MockDoTimings(retry, timeout time.Duration) (restore func()) {
oldRetry := doRetry
oldTimeout := doTimeout
doRetry = retry
@@ -259,23 +300,41 @@
client.doer = hijacked{f}
}
+type doFlags struct {
+ NoTimeout bool
+}
+
// do performs a request and decodes the resulting json into the given
// value. It's low-level, for testing/experimenting only; you should
// usually use a higher level interface that builds on this.
-func (client *Client) do(method, path string, query url.Values, headers map[string]string, body io.Reader, v interface{}) (statusCode int, err error) {
+func (client *Client) do(method, path string, query url.Values, headers map[string]string, body io.Reader, v interface{}, flags doFlags) (statusCode int, err error) {
retry := time.NewTicker(doRetry)
defer retry.Stop()
- timeout := time.After(doTimeout)
+ timeout := time.NewTimer(doTimeout)
+ defer timeout.Stop()
+
var rsp *http.Response
+ var ctx context.Context = context.Background()
for {
- rsp, err = client.raw(method, path, query, headers, body)
+ if flags.NoTimeout {
+ rsp, err = client.raw(ctx, method, path, query, headers, body)
+ } else {
+ var cancel context.CancelFunc
+ // use the same timeout as for the whole of the retry
+ // loop to error out the whole do() call when a single
+ // request exceeds the deadline
+ rsp, cancel, err = client.rawWithTimeout(ctx, method, path, query, headers, body, doTimeout)
+ if err == nil {
+ defer cancel()
+ }
+ }
if err == nil || method != "GET" {
break
}
select {
case <-retry.C:
continue
- case <-timeout:
+ case <-timeout.C:
}
break
}
@@ -312,7 +371,7 @@
// which produces json.Numbers instead of float64 types for numbers.
func (client *Client) doSync(method, path string, query url.Values, headers map[string]string, body io.Reader, v interface{}) (*ResultInfo, error) {
var rsp response
- statusCode, err := client.do(method, path, query, headers, body, &rsp)
+ statusCode, err := client.do(method, path, query, headers, body, &rsp, doFlags{})
if err != nil {
return nil, err
}
@@ -336,13 +395,18 @@
}
func (client *Client) doAsync(method, path string, query url.Values, headers map[string]string, body io.Reader) (changeID string, err error) {
- _, changeID, err = client.doAsyncFull(method, path, query, headers, body)
+ _, changeID, err = client.doAsyncFull(method, path, query, headers, body, doFlags{})
return
}
-func (client *Client) doAsyncFull(method, path string, query url.Values, headers map[string]string, body io.Reader) (result json.RawMessage, changeID string, err error) {
+func (client *Client) doAsyncNoTimeout(method, path string, query url.Values, headers map[string]string, body io.Reader) (changeID string, err error) {
+ _, changeID, err = client.doAsyncFull(method, path, query, headers, body, doFlags{NoTimeout: true})
+ return changeID, err
+}
+
+func (client *Client) doAsyncFull(method, path string, query url.Values, headers map[string]string, body io.Reader, flags doFlags) (result json.RawMessage, changeID string, err error) {
var rsp response
- statusCode, err := client.do(method, path, query, headers, body, &rsp)
+ statusCode, err := client.do(method, path, query, headers, body, &rsp, flags)
if err != nil {
return nil, "", err
}
@@ -369,7 +433,9 @@
OSVersionID string
OnClassic bool
- KernelVersion string
+ KernelVersion string
+ Architecture string
+ Virtualization string
}
func (client *Client) ServerVersion() (*ServerVersion, error) {
@@ -385,7 +451,9 @@
OSVersionID: sysInfo.OSRelease.VersionID,
OnClassic: sysInfo.OnClassic,
- KernelVersion: sysInfo.KernelVersion,
+ KernelVersion: sysInfo.KernelVersion,
+ Architecture: sysInfo.Architecture,
+ Virtualization: sysInfo.Virtualization,
}, nil
}
@@ -458,6 +526,8 @@
ErrorKindDaemonRestart = "daemon-restart"
ErrorKindAssertionNotFound = "assertion-not-found"
+
+ ErrorKindUnsuccessful = "unsuccessful"
)
// IsRetryable returns true if the given error is an error
@@ -528,7 +598,9 @@
OnClassic bool `json:"on-classic"`
Managed bool `json:"managed"`
- KernelVersion string `json:"kernel-version,omitempty"`
+ KernelVersion string `json:"kernel-version,omitempty"`
+ Architecture string `json:"architecture,omitempty"`
+ Virtualization string `json:"virtualization,omitempty"`
Refresh RefreshInfo `json:"refresh,omitempty"`
Confinement string `json:"confinement"`
@@ -587,105 +659,6 @@
return &sysInfo, nil
}
-// CreateUserResult holds the result of a user creation.
-type CreateUserResult struct {
- Username string `json:"username"`
- SSHKeys []string `json:"ssh-keys"`
-}
-
-// CreateUserOptions holds options for creating a local system user.
-//
-// If Known is false, the provided email is used to query the store for
-// username and SSH key details.
-//
-// If Known is true, the user will be created by looking through existing
-// system-user assertions and looking for a matching email. If Email is
-// empty then all such assertions are considered and multiple users may
-// be created.
-type CreateUserOptions struct {
- Email string `json:"email,omitempty"`
- Sudoer bool `json:"sudoer,omitempty"`
- Known bool `json:"known,omitempty"`
- ForceManaged bool `json:"force-managed,omitempty"`
-}
-
-// CreateUser creates a local system user. See CreateUserOptions for details.
-func (client *Client) CreateUser(options *CreateUserOptions) (*CreateUserResult, error) {
- if options.Email == "" {
- return nil, fmt.Errorf("cannot create a user without providing an email")
- }
-
- var result CreateUserResult
- data, err := json.Marshal(options)
- if err != nil {
- return nil, err
- }
-
- if _, err := client.doSync("POST", "/v2/create-user", nil, nil, bytes.NewReader(data), &result); err != nil {
- return nil, fmt.Errorf("while creating user: %v", err)
- }
- return &result, nil
-}
-
-// CreateUsers creates multiple local system users. See CreateUserOptions for details.
-//
-// Results may be provided even if there are errors.
-func (client *Client) CreateUsers(options []*CreateUserOptions) ([]*CreateUserResult, error) {
- for _, opts := range options {
- if opts.Email == "" && !opts.Known {
- return nil, fmt.Errorf("cannot create user from store details without an email to query for")
- }
- }
-
- var results []*CreateUserResult
- var errs []error
-
- for _, opts := range options {
- data, err := json.Marshal(opts)
- if err != nil {
- return nil, err
- }
-
- if opts.Email == "" {
- var result []*CreateUserResult
- if _, err := client.doSync("POST", "/v2/create-user", nil, nil, bytes.NewReader(data), &result); err != nil {
- errs = append(errs, err)
- } else {
- results = append(results, result...)
- }
- } else {
- var result *CreateUserResult
- if _, err := client.doSync("POST", "/v2/create-user", nil, nil, bytes.NewReader(data), &result); err != nil {
- errs = append(errs, err)
- } else {
- results = append(results, result)
- }
- }
- }
-
- if len(errs) == 1 {
- return results, errs[0]
- }
- if len(errs) > 1 {
- var buf bytes.Buffer
- for _, err := range errs {
- fmt.Fprintf(&buf, "\n- %s", err)
- }
- return results, fmt.Errorf("while creating users:%s", buf.Bytes())
- }
- return results, nil
-}
-
-// Users returns the local users.
-func (client *Client) Users() ([]*User, error) {
- var result []*User
-
- if _, err := client.doSync("GET", "/v2/users", nil, nil, nil, &result); err != nil {
- return nil, fmt.Errorf("while getting users: %v", err)
- }
- return result, nil
-}
-
type debugAction struct {
Action string `json:"action"`
Params interface{} `json:"params,omitempty"`
diff -Nru snapd-2.42.1+18.04/client/client_test.go snapd-2.45.1+18.04/client/client_test.go
--- snapd-2.42.1+18.04/client/client_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/client/client_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -43,16 +43,17 @@
func Test(t *testing.T) { TestingT(t) }
type clientSuite struct {
- cli *client.Client
- req *http.Request
- reqs []*http.Request
- rsp string
- rsps []string
- err error
- doCalls int
- header http.Header
- status int
- restore func()
+ cli *client.Client
+ req *http.Request
+ reqs []*http.Request
+ rsp string
+ rsps []string
+ err error
+ doCalls int
+ header http.Header
+ status int
+ contentLength int64
+ restore func()
}
var _ = Suite(&clientSuite{})
@@ -70,10 +71,11 @@
cs.header = nil
cs.status = 200
cs.doCalls = 0
+ cs.contentLength = 0
dirs.SetRootDir(c.MkDir())
- cs.restore = client.MockDoRetry(time.Millisecond, 10*time.Millisecond)
+ cs.restore = client.MockDoTimings(time.Millisecond, 100*time.Millisecond)
}
func (cs *clientSuite) TearDownTest(c *C) {
@@ -89,9 +91,10 @@
body = cs.rsps[cs.doCalls]
}
rsp := &http.Response{
- Body: ioutil.NopCloser(strings.NewReader(body)),
- Header: cs.header,
- StatusCode: cs.status,
+ Body: ioutil.NopCloser(strings.NewReader(body)),
+ Header: cs.header,
+ StatusCode: cs.status,
+ ContentLength: cs.contentLength,
}
cs.doCalls++
return rsp, cs.err
@@ -100,12 +103,12 @@
func (cs *clientSuite) TestNewPanics(c *C) {
c.Assert(func() {
client.New(&client.Config{BaseURL: ":"})
- }, PanicMatches, `cannot parse server base URL: ":" \(parse :: missing protocol scheme\)`)
+ }, PanicMatches, `cannot parse server base URL: ":" \(parse \"?:\"?: missing protocol scheme\)`)
}
func (cs *clientSuite) TestClientDoReportsErrors(c *C) {
cs.err = errors.New("ouchie")
- _, err := cs.cli.Do("GET", "/", nil, nil, nil)
+ _, err := cs.cli.Do("GET", "/", nil, nil, nil, client.DoFlags{})
c.Check(err, ErrorMatches, "cannot communicate with server: ouchie")
if cs.doCalls < 2 {
c.Fatalf("do did not retry")
@@ -116,7 +119,7 @@
var v []int
cs.rsp = `[1,2]`
reqBody := ioutil.NopCloser(strings.NewReader(""))
- statusCode, err := cs.cli.Do("GET", "/this", nil, reqBody, &v)
+ statusCode, err := cs.cli.Do("GET", "/this", nil, reqBody, &v, client.DoFlags{})
c.Check(err, IsNil)
c.Check(statusCode, Equals, 200)
c.Check(v, DeepEquals, []int{1, 2})
@@ -132,7 +135,7 @@
cs.status = 202
cs.rsp = `[1,2]`
reqBody := ioutil.NopCloser(strings.NewReader(""))
- statusCode, err := cs.cli.Do("GET", "/this", nil, reqBody, &v)
+ statusCode, err := cs.cli.Do("GET", "/this", nil, reqBody, &v, client.DoFlags{})
c.Check(err, IsNil)
c.Check(statusCode, Equals, 202)
c.Check(v, DeepEquals, []int{1, 2})
@@ -148,7 +151,7 @@
defer os.Unsetenv(client.TestAuthFileEnvKey)
var v string
- _, _ = cs.cli.Do("GET", "/this", nil, nil, &v)
+ _, _ = cs.cli.Do("GET", "/this", nil, nil, &v, client.DoFlags{})
c.Assert(cs.req, NotNil)
authorization := cs.req.Header.Get("Authorization")
c.Check(authorization, Equals, "")
@@ -166,7 +169,7 @@
c.Assert(err, IsNil)
var v string
- _, _ = cs.cli.Do("GET", "/this", nil, nil, &v)
+ _, _ = cs.cli.Do("GET", "/this", nil, nil, &v, client.DoFlags{})
authorization := cs.req.Header.Get("Authorization")
c.Check(authorization, Equals, `Macaroon root="macaroon", discharge="discharge"`)
}
@@ -185,7 +188,7 @@
var v string
cli := client.New(&client.Config{DisableAuth: true})
cli.SetDoer(cs)
- _, _ = cli.Do("GET", "/this", nil, nil, &v)
+ _, _ = cli.Do("GET", "/this", nil, nil, &v, client.DoFlags{})
authorization := cs.req.Header.Get("Authorization")
c.Check(authorization, Equals, "")
}
@@ -194,13 +197,13 @@
var v string
cli := client.New(&client.Config{Interactive: false})
cli.SetDoer(cs)
- _, _ = cli.Do("GET", "/this", nil, nil, &v)
+ _, _ = cli.Do("GET", "/this", nil, nil, &v, client.DoFlags{})
interactive := cs.req.Header.Get(client.AllowInteractionHeader)
c.Check(interactive, Equals, "")
cli = client.New(&client.Config{Interactive: true})
cli.SetDoer(cs)
- _, _ = cli.Do("GET", "/this", nil, nil, &v)
+ _, _ = cli.Do("GET", "/this", nil, nil, &v, client.DoFlags{})
interactive = cs.req.Header.Get(client.AllowInteractionHeader)
c.Check(interactive, Equals, "true")
}
@@ -238,6 +241,8 @@
"on-classic": true,
"build-id": "1234",
"confinement": "strict",
+ "architecture": "TI-99/4A",
+ "virtualization": "MESS",
"sandbox-features": {"backend": ["feature-1", "feature-2"]}}}`
sysInfo, err := cs.cli.SysInfo()
c.Check(err, IsNil)
@@ -253,7 +258,9 @@
SandboxFeatures: map[string][]string{
"backend": {"feature-1", "feature-2"},
},
- BuildID: "1234",
+ BuildID: "1234",
+ Architecture: "TI-99/4A",
+ Virtualization: "MESS",
})
}
@@ -261,14 +268,19 @@
cs.rsp = `{"type": "sync", "result":
{"series": "16",
"version": "2",
- "os-release": {"id": "zyggy", "version-id": "123"}}}`
+ "os-release": {"id": "zyggy", "version-id": "123"},
+ "architecture": "m32",
+ "virtualization": "qemu"
+}}}`
version, err := cs.cli.ServerVersion()
c.Check(err, IsNil)
c.Check(version, DeepEquals, &client.ServerVersion{
- Version: "2",
- Series: "16",
- OSID: "zyggy",
- OSVersionID: "123",
+ Version: "2",
+ Series: "16",
+ OSID: "zyggy",
+ OSVersionID: "123",
+ Architecture: "m32",
+ Virtualization: "qemu",
})
}
@@ -295,7 +307,7 @@
cli := client.New(nil)
si, err := cli.SysInfo()
- c.Check(err, IsNil)
+ c.Assert(err, IsNil)
c.Check(si.Series, Equals, "42")
}
@@ -452,117 +464,16 @@
c.Check(client.IsRetryable(&client.Error{Kind: client.ErrorKindChangeConflict}), Equals, true)
}
-func (cs *clientSuite) TestClientCreateUser(c *C) {
- _, err := cs.cli.CreateUser(&client.CreateUserOptions{})
- c.Assert(err, ErrorMatches, "cannot create a user without providing an email")
-
- cs.rsp = `{
- "type": "sync",
- "result": {
- "username": "karl",
- "ssh-keys": ["one", "two"]
- }
- }`
- rsp, err := cs.cli.CreateUser(&client.CreateUserOptions{Email: "one@email.com", Sudoer: true, Known: true})
- c.Assert(cs.req.Method, Equals, "POST")
- c.Assert(cs.req.URL.Path, Equals, "/v2/create-user")
- c.Assert(err, IsNil)
-
- body, err := ioutil.ReadAll(cs.req.Body)
- c.Assert(err, IsNil)
- c.Assert(string(body), Equals, `{"email":"one@email.com","sudoer":true,"known":true}`)
-
- c.Assert(rsp, DeepEquals, &client.CreateUserResult{
- Username: "karl",
- SSHKeys: []string{"one", "two"},
- })
-}
-
func (cs *clientSuite) TestUserAgent(c *C) {
cli := client.New(&client.Config{UserAgent: "some-agent/9.87"})
cli.SetDoer(cs)
var v string
- _, _ = cli.Do("GET", "/", nil, nil, &v)
+ _, _ = cli.Do("GET", "/", nil, nil, &v, client.DoFlags{})
c.Assert(cs.req, NotNil)
c.Check(cs.req.Header.Get("User-Agent"), Equals, "some-agent/9.87")
}
-var createUsersTests = []struct {
- options []*client.CreateUserOptions
- bodies []string
- responses []string
- results []*client.CreateUserResult
- error string
-}{{
- options: []*client.CreateUserOptions{{}},
- error: "cannot create user from store details without an email to query for",
-}, {
- options: []*client.CreateUserOptions{{
- Email: "one@example.com",
- Sudoer: true,
- }, {
- Known: true,
- }},
- bodies: []string{
- `{"email":"one@example.com","sudoer":true}`,
- `{"known":true}`,
- },
- responses: []string{
- `{"type": "sync", "result": {"username": "one", "ssh-keys":["a", "b"]}}`,
- `{"type": "sync", "result": [{"username": "two"}, {"username": "three"}]}`,
- },
- results: []*client.CreateUserResult{{
- Username: "one",
- SSHKeys: []string{"a", "b"},
- }, {
- Username: "two",
- }, {
- Username: "three",
- }},
-}}
-
-func (cs *clientSuite) TestClientCreateUsers(c *C) {
- for _, test := range createUsersTests {
- cs.rsps = test.responses
-
- results, err := cs.cli.CreateUsers(test.options)
- if test.error != "" {
- c.Assert(err, ErrorMatches, test.error)
- }
- c.Assert(results, DeepEquals, test.results)
-
- var bodies []string
- for _, req := range cs.reqs {
- c.Assert(req.Method, Equals, "POST")
- c.Assert(req.URL.Path, Equals, "/v2/create-user")
- data, err := ioutil.ReadAll(req.Body)
- c.Assert(err, IsNil)
- bodies = append(bodies, string(data))
- }
-
- c.Assert(bodies, DeepEquals, test.bodies)
- }
-}
-
-func (cs *clientSuite) TestClientJSONError(c *C) {
- cs.rsp = `some non-json error message`
- _, err := cs.cli.SysInfo()
- c.Assert(err, ErrorMatches, `cannot obtain system details: cannot decode "some non-json error message": invalid char.*`)
-}
-
-func (cs *clientSuite) TestUsers(c *C) {
- cs.rsp = `{"type": "sync", "result":
- [{"username": "foo","email":"foo@example.com"},
- {"username": "bar","email":"bar@example.com"}]}`
- users, err := cs.cli.Users()
- c.Check(err, IsNil)
- c.Check(users, DeepEquals, []*client.User{
- {Username: "foo", Email: "foo@example.com"},
- {Username: "bar", Email: "bar@example.com"},
- })
-}
-
func (cs *clientSuite) TestDebugEnsureStateSoon(c *C) {
cs.rsp = `{"type": "sync", "result":true}`
err := cs.cli.Debug("ensure-state-soon", nil, nil)
@@ -602,3 +513,24 @@
c.Check(cs.reqs[0].URL.Path, Equals, "/v2/debug")
c.Check(cs.reqs[0].URL.Query(), DeepEquals, url.Values{"aspect": []string{"do-something"}, "foo": []string{"bar"}})
}
+
+type integrationSuite struct{}
+
+var _ = Suite(&integrationSuite{})
+
+func (cs *integrationSuite) TestClientTimeoutLP1837804(c *C) {
+ restore := client.MockDoTimings(time.Millisecond, 5*time.Millisecond)
+ defer restore()
+
+ testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
+ time.Sleep(25 * time.Millisecond)
+ }))
+ defer func() { testServer.Close() }()
+
+ cli := client.New(&client.Config{BaseURL: testServer.URL})
+ _, err := cli.Do("GET", "/", nil, nil, nil, client.DoFlags{})
+ c.Assert(err, ErrorMatches, `.* timeout exceeded while waiting for response`)
+
+ _, err = cli.Do("POST", "/", nil, nil, nil, client.DoFlags{})
+ c.Assert(err, ErrorMatches, `.* timeout exceeded while waiting for response`)
+}
diff -Nru snapd-2.42.1+18.04/client/cohort.go snapd-2.45.1+18.04/client/cohort.go
--- snapd-2.42.1+18.04/client/cohort.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/client/cohort.go 2020-06-05 13:13:49.000000000 +0000
@@ -23,6 +23,8 @@
"bytes"
"encoding/json"
"fmt"
+
+ "golang.org/x/xerrors"
)
type CohortAction struct {
@@ -39,7 +41,8 @@
var cohorts map[string]string
if _, err := client.doSync("POST", "/v2/cohorts", nil, nil, bytes.NewReader(data), &cohorts); err != nil {
- return nil, fmt.Errorf("cannot create cohorts: %v", err)
+ fmt := "cannot create cohorts: %w"
+ return nil, xerrors.Errorf(fmt, err)
}
return cohorts, nil
diff -Nru snapd-2.42.1+18.04/client/cohort_test.go snapd-2.45.1+18.04/client/cohort_test.go
--- snapd-2.42.1+18.04/client/cohort_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/client/cohort_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -21,8 +21,11 @@
import (
"encoding/json"
+ "errors"
"io/ioutil"
+ "golang.org/x/xerrors"
+
"gopkg.in/check.v1"
)
@@ -65,3 +68,10 @@
"snaps": []interface{}{"foo", "bar"},
})
}
+
+func (cs *clientSuite) TestClientCreateCohortsErrIsWrapped(c *check.C) {
+ cs.err = errors.New("boom")
+ _, err := cs.cli.CreateCohorts([]string{"foo", "bar"})
+ var e xerrors.Wrapper
+ c.Assert(err, check.Implements, &e)
+}
diff -Nru snapd-2.42.1+18.04/client/export_test.go snapd-2.45.1+18.04/client/export_test.go
--- snapd-2.42.1+18.04/client/export_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/client/export_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -30,9 +30,11 @@
client.doer = d
}
+type DoFlags = doFlags
+
// Do does do.
-func (client *Client) Do(method, path string, query url.Values, body io.Reader, v interface{}) (statusCode int, err error) {
- return client.do(method, path, query, nil, body, v)
+func (client *Client) Do(method, path string, query url.Values, body io.Reader, v interface{}, flags DoFlags) (statusCode int, err error) {
+ return client.do(method, path, query, nil, body, v, flags)
}
// expose parseError for testing
@@ -49,3 +51,5 @@
err = json.NewDecoder(body).Decode(&act)
return
}
+
+type DownloadAction = downloadAction
diff -Nru snapd-2.42.1+18.04/client/icons.go snapd-2.45.1+18.04/client/icons.go
--- snapd-2.42.1+18.04/client/icons.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/client/icons.go 2020-06-05 13:13:49.000000000 +0000
@@ -20,9 +20,12 @@
package client
import (
+ "context"
"fmt"
"io/ioutil"
"regexp"
+
+ "golang.org/x/xerrors"
)
// Icon represents the icon of an installed snap
@@ -37,9 +40,12 @@
func (c *Client) Icon(pkgID string) (*Icon, error) {
const errPrefix = "cannot retrieve icon"
- response, err := c.raw("GET", fmt.Sprintf("/v2/icons/%s/icon", pkgID), nil, nil, nil)
+ ctx, cancel := context.WithTimeout(context.Background(), doTimeout)
+ defer cancel()
+ response, err := c.raw(ctx, "GET", fmt.Sprintf("/v2/icons/%s/icon", pkgID), nil, nil, nil)
if err != nil {
- return nil, fmt.Errorf("%s: failed to communicate with server: %s", errPrefix, err)
+ fmt := "%s: failed to communicate with server: %w"
+ return nil, xerrors.Errorf(fmt, errPrefix, err)
}
defer response.Body.Close()
diff -Nru snapd-2.42.1+18.04/client/icons_test.go snapd-2.45.1+18.04/client/icons_test.go
--- snapd-2.42.1+18.04/client/icons_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/client/icons_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -24,6 +24,8 @@
"fmt"
"net/http"
+ "golang.org/x/xerrors"
+
. "gopkg.in/check.v1"
)
@@ -63,3 +65,10 @@
c.Assert(icon.Filename, Equals, "myicon.png")
c.Assert(icon.Content, DeepEquals, []byte("pixels"))
}
+
+func (cs *clientSuite) TestClientIconErrIsWrapped(c *C) {
+ cs.err = errors.New("boom")
+ _, err := cs.cli.Icon("something")
+ var e xerrors.Wrapper
+ c.Assert(err, Implements, &e)
+}
diff -Nru snapd-2.42.1+18.04/client/interfaces.go snapd-2.45.1+18.04/client/interfaces.go
--- snapd-2.42.1+18.04/client/interfaces.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/client/interfaces.go 2020-06-05 13:13:49.000000000 +0000
@@ -72,6 +72,7 @@
// InterfaceAction represents an action performed on the interface system.
type InterfaceAction struct {
Action string `json:"action"`
+ Forget bool `json:"forget,omitempty"`
Plugs []Plug `json:"plugs,omitempty"`
Slots []Slot `json:"slots,omitempty"`
}
@@ -85,6 +86,11 @@
Connected bool
}
+// DisconnectOptions represents extra options for disconnect op
+type DisconnectOptions struct {
+ Forget bool
+}
+
func (client *Client) Interfaces(opts *InterfaceOptions) ([]*Interface, error) {
query := url.Values{}
if opts != nil && len(opts.Names) > 0 {
@@ -133,9 +139,10 @@
}
// Disconnect breaks the connection between a plug and a slot.
-func (client *Client) Disconnect(plugSnapName, plugName, slotSnapName, slotName string) (changeID string, err error) {
+func (client *Client) Disconnect(plugSnapName, plugName, slotSnapName, slotName string, opts *DisconnectOptions) (changeID string, err error) {
return client.performInterfaceAction(&InterfaceAction{
Action: "disconnect",
+ Forget: opts != nil && opts.Forget,
Plugs: []Plug{{Snap: plugSnapName, Name: plugName}},
Slots: []Slot{{Snap: slotSnapName, Name: slotName}},
})
diff -Nru snapd-2.42.1+18.04/client/interfaces_test.go snapd-2.45.1+18.04/client/interfaces_test.go
--- snapd-2.42.1+18.04/client/interfaces_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/client/interfaces_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -195,7 +195,7 @@
}
func (cs *clientSuite) TestClientDisconnectCallsEndpoint(c *check.C) {
- cs.cli.Disconnect("producer", "plug", "consumer", "slot")
+ cs.cli.Disconnect("producer", "plug", "consumer", "slot", nil)
c.Check(cs.req.Method, check.Equals, "POST")
c.Check(cs.req.URL.Path, check.Equals, "/v2/interfaces")
}
@@ -208,7 +208,8 @@
"result": { },
"change": "42"
}`
- id, err := cs.cli.Disconnect("producer", "plug", "consumer", "slot")
+ opts := &client.DisconnectOptions{Forget: false}
+ id, err := cs.cli.Disconnect("producer", "plug", "consumer", "slot", opts)
c.Assert(err, check.IsNil)
c.Check(id, check.Equals, "42")
var body map[string]interface{}
@@ -220,6 +221,40 @@
"plugs": []interface{}{
map[string]interface{}{
"snap": "producer",
+ "plug": "plug",
+ },
+ },
+ "slots": []interface{}{
+ map[string]interface{}{
+ "snap": "consumer",
+ "slot": "slot",
+ },
+ },
+ })
+}
+
+func (cs *clientSuite) TestClientDisconnectForget(c *check.C) {
+ cs.status = 202
+ cs.rsp = `{
+ "type": "async",
+ "status-code": 202,
+ "result": { },
+ "change": "42"
+ }`
+ opts := &client.DisconnectOptions{Forget: true}
+ id, err := cs.cli.Disconnect("producer", "plug", "consumer", "slot", opts)
+ c.Assert(err, check.IsNil)
+ c.Check(id, check.Equals, "42")
+ var body map[string]interface{}
+ decoder := json.NewDecoder(cs.req.Body)
+ err = decoder.Decode(&body)
+ c.Check(err, check.IsNil)
+ c.Check(body, check.DeepEquals, map[string]interface{}{
+ "action": "disconnect",
+ "forget": true,
+ "plugs": []interface{}{
+ map[string]interface{}{
+ "snap": "producer",
"plug": "plug",
},
},
diff -Nru snapd-2.42.1+18.04/client/model.go snapd-2.45.1+18.04/client/model.go
--- snapd-2.42.1+18.04/client/model.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/client/model.go 2020-06-05 13:13:49.000000000 +0000
@@ -21,10 +21,13 @@
import (
"bytes"
+ "context"
"encoding/json"
"fmt"
"net/url"
+ "golang.org/x/xerrors"
+
"github.com/snapcore/snapd/asserts"
)
@@ -77,9 +80,12 @@
func currentAssertion(client *Client, path string) (asserts.Assertion, error) {
q := url.Values{}
- response, err := client.raw("GET", path, q, nil, nil)
+ ctx, cancel := context.WithTimeout(context.Background(), doTimeout)
+ defer cancel()
+ response, err := client.raw(ctx, "GET", path, q, nil, nil)
if err != nil {
- return nil, fmt.Errorf("failed to query current assertion: %v", err)
+ fmt := "failed to query current assertion: %w"
+ return nil, xerrors.Errorf(fmt, err)
}
defer response.Body.Close()
if response.StatusCode != 200 {
diff -Nru snapd-2.42.1+18.04/client/model_test.go snapd-2.45.1+18.04/client/model_test.go
--- snapd-2.42.1+18.04/client/model_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/client/model_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -21,12 +21,15 @@
import (
"encoding/json"
+ "errors"
"io/ioutil"
"net/http"
- "github.com/snapcore/snapd/asserts"
+ "golang.org/x/xerrors"
. "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/asserts"
)
const happyModelAssertionResponse = `type: model
@@ -164,3 +167,10 @@
c.Assert(err, IsNil)
c.Assert(serialAssertion, DeepEquals, expectedAssert)
}
+
+func (cs *clientSuite) TestClientCurrentModelAssertionErrIsWrapped(c *C) {
+ cs.err = errors.New("boom")
+ _, err := cs.cli.CurrentModelAssertion()
+ var e xerrors.Wrapper
+ c.Assert(err, Implements, &e)
+}
diff -Nru snapd-2.42.1+18.04/client/packages.go snapd-2.45.1+18.04/client/packages.go
--- snapd-2.42.1+18.04/client/packages.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/client/packages.go 2020-06-05 13:13:49.000000000 +0000
@@ -27,6 +27,8 @@
"strings"
"time"
+ "golang.org/x/xerrors"
+
"github.com/snapcore/snapd/snap"
)
@@ -42,6 +44,7 @@
InstallDate time.Time `json:"install-date,omitempty"`
Name string `json:"name"`
Publisher *snap.StoreAccount `json:"publisher,omitempty"`
+ StoreURL string `json:"store-url,omitempty"`
// Developer is also the publisher's username for historic reasons.
Developer string `json:"developer"`
Status string `json:"status"`
@@ -64,6 +67,7 @@
CommonIDs []string `json:"common-ids,omitempty"`
MountedFrom string `json:"mounted-from,omitempty"`
CohortKey string `json:"cohort-key,omitempty"`
+ Website string `json:"website,omitempty"`
Prices map[string]float64 `json:"prices,omitempty"`
Screenshots []snap.ScreenshotInfo `json:"screenshots,omitempty"`
@@ -179,7 +183,8 @@
var sections []string
_, err := client.doSync("GET", "/v2/sections", nil, nil, nil, §ions)
if err != nil {
- return nil, fmt.Errorf("cannot get snap sections: %s", err)
+ fmt := "cannot get snap sections: %w"
+ return nil, xerrors.Errorf(fmt, err)
}
return sections, nil
}
@@ -227,7 +232,8 @@
snaps, ri, err := client.snapsFromPath("/v2/find", q)
if err != nil {
- return nil, nil, fmt.Errorf("cannot find snap %q: %s", name, err)
+ fmt := "cannot find snap %q: %w"
+ return nil, nil, xerrors.Errorf(fmt, name, err)
}
if len(snaps) == 0 {
@@ -244,7 +250,8 @@
return nil, nil, e
}
if err != nil {
- return nil, nil, fmt.Errorf("cannot list snaps: %s", err)
+ fmt := "cannot list snaps: %w"
+ return nil, nil, xerrors.Errorf(fmt, err)
}
return snaps, ri, nil
}
@@ -256,7 +263,8 @@
path := fmt.Sprintf("/v2/snaps/%s", name)
ri, err := client.doSync("GET", path, nil, nil, nil, &snap)
if err != nil {
- return nil, nil, fmt.Errorf("cannot retrieve snap %q: %s", name, err)
+ fmt := "cannot retrieve snap %q: %w"
+ return nil, nil, xerrors.Errorf(fmt, name, err)
}
return snap, ri, nil
}
diff -Nru snapd-2.42.1+18.04/client/packages_test.go snapd-2.45.1+18.04/client/packages_test.go
--- snapd-2.42.1+18.04/client/packages_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/client/packages_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -21,10 +21,14 @@
import (
"encoding/json"
+ "errors"
"fmt"
+ "io/ioutil"
"net/url"
+ "os"
"time"
+ "golang.org/x/xerrors"
"gopkg.in/check.v1"
"github.com/snapcore/snapd/client"
@@ -258,7 +262,9 @@
{"type": "screenshot", "url":"http://example.com/shot2.png"}
],
"cohort-key": "some-long-cohort-key",
- "common-ids": ["org.funky.snap"]
+ "website": "http://example.com/funky",
+ "common-ids": ["org.funky.snap"],
+ "store-url": "https://snapcraft.io/chatroom"
}
}`
pkg, _, err := cs.cli.Snap(pkgName)
@@ -302,6 +308,8 @@
},
CommonIDs: []string{"org.funky.snap"},
CohortKey: "some-long-cohort-key",
+ Website: "http://example.com/funky",
+ StoreURL: "https://snapcraft.io/chatroom",
})
}
@@ -356,3 +364,44 @@
c.Check(app.Name, check.Equals, "hello")
c.Check(app.IsService(), check.Equals, true)
}
+
+func (cs *clientSuite) TestClientSectionsErrIsWrapped(c *check.C) {
+ cs.err = errors.New("boom")
+ _, err := cs.cli.Sections()
+ var e xerrors.Wrapper
+ c.Assert(err, check.Implements, &e)
+}
+
+func (cs *clientSuite) TestClientFindOneErrIsWrapped(c *check.C) {
+ cs.err = errors.New("boom")
+ _, _, err := cs.cli.FindOne("snap")
+ var e xerrors.Wrapper
+ c.Assert(err, check.Implements, &e)
+}
+
+func (cs *clientSuite) TestClientSnapErrIsWrapped(c *check.C) {
+ // setting cs.err will trigger a "client.ClientError"
+ cs.err = errors.New("boom")
+ _, _, err := cs.cli.Snap("snap")
+ var e xerrors.Wrapper
+ c.Assert(err, check.Implements, &e)
+}
+
+func (cs *clientSuite) TestClientFindFromPathErrIsWrapped(c *check.C) {
+ var e client.AuthorizationError
+
+ // this will trigger a "client.AuthorizationError"
+ err := ioutil.WriteFile(client.TestStoreAuthFilename(os.Getenv("HOME")), []byte("rubbish"), 0644)
+ c.Assert(err, check.IsNil)
+
+ // check that all the functions that use snapsFromPath() get a
+ // wrapped error
+ _, _, err = cs.cli.FindOne("snap")
+ c.Assert(xerrors.As(err, &e), check.Equals, true)
+
+ _, _, err = cs.cli.Find(nil)
+ c.Assert(xerrors.As(err, &e), check.Equals, true)
+
+ _, err = cs.cli.List([]string{"snap"}, nil)
+ c.Assert(xerrors.As(err, &e), check.Equals, true)
+}
diff -Nru snapd-2.42.1+18.04/client/snap_op.go snapd-2.45.1+18.04/client/snap_op.go
--- snapd-2.42.1+18.04/client/snap_op.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/client/snap_op.go 2020-06-05 13:13:49.000000000 +0000
@@ -21,6 +21,7 @@
import (
"bytes"
+ "context"
"encoding/json"
"fmt"
"io"
@@ -203,7 +204,7 @@
"Content-Type": "application/json",
}
- return client.doAsyncFull("POST", "/v2/snaps", nil, headers, bytes.NewBuffer(data))
+ return client.doAsyncFull("POST", "/v2/snaps", nil, headers, bytes.NewBuffer(data), doFlags{})
}
// InstallPath sideloads the snap with the given path under optional provided name,
@@ -229,7 +230,7 @@
"Content-Type": mw.FormDataContentType(),
}
- return client.doAsync("POST", "/v2/snaps", nil, headers, pr)
+ return client.doAsyncNoTimeout("POST", "/v2/snaps", nil, headers, pr)
}
// Try
@@ -305,3 +306,89 @@
mw.Close()
pw.Close()
}
+
+type snapRevisionOptions struct {
+ Channel string `json:"channel,omitempty"`
+ Revision string `json:"revision,omitempty"`
+ CohortKey string `json:"cohort-key,omitempty"`
+}
+
+type downloadAction struct {
+ SnapName string `json:"snap-name"`
+
+ snapRevisionOptions
+
+ HeaderPeek bool `json:"header-peek,omitempty"`
+ ResumeToken string `json:"resume-token,omitempty"`
+}
+
+type DownloadInfo struct {
+ SuggestedFileName string
+ Size int64
+ Sha3_384 string
+ ResumeToken string
+}
+
+type DownloadOptions struct {
+ SnapOptions
+
+ HeaderPeek bool
+ ResumeToken string
+ Resume int64
+}
+
+// Download will stream the given snap to the client
+func (client *Client) Download(name string, options *DownloadOptions) (dlInfo *DownloadInfo, r io.ReadCloser, err error) {
+ if options == nil {
+ options = &DownloadOptions{}
+ }
+ action := downloadAction{
+ SnapName: name,
+ snapRevisionOptions: snapRevisionOptions{
+ Channel: options.Channel,
+ CohortKey: options.CohortKey,
+ Revision: options.Revision,
+ },
+ HeaderPeek: options.HeaderPeek,
+ ResumeToken: options.ResumeToken,
+ }
+ data, err := json.Marshal(&action)
+ if err != nil {
+ return nil, nil, fmt.Errorf("cannot marshal snap action: %s", err)
+ }
+ headers := map[string]string{
+ "Content-Type": "application/json",
+ }
+ if options.Resume > 0 {
+ headers["range"] = fmt.Sprintf("bytes: %d-", options.Resume)
+ }
+
+ // no deadline for downloads
+ ctx := context.Background()
+ rsp, err := client.raw(ctx, "POST", "/v2/download", nil, headers, bytes.NewBuffer(data))
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if rsp.StatusCode != 200 {
+ var r response
+ defer rsp.Body.Close()
+ if err := decodeInto(rsp.Body, &r); err != nil {
+ return nil, nil, err
+ }
+ return nil, nil, r.err(client, rsp.StatusCode)
+ }
+ matches := contentDispositionMatcher(rsp.Header.Get("Content-Disposition"))
+ if matches == nil || matches[1] == "" {
+ return nil, nil, fmt.Errorf("cannot determine filename")
+ }
+
+ dlInfo = &DownloadInfo{
+ SuggestedFileName: matches[1],
+ Size: rsp.ContentLength,
+ Sha3_384: rsp.Header.Get("Snap-Sha3-384"),
+ ResumeToken: rsp.Header.Get("Snap-Download-Token"),
+ }
+
+ return dlInfo, rsp.Body, nil
+}
diff -Nru snapd-2.42.1+18.04/client/snap_op_test.go snapd-2.45.1+18.04/client/snap_op_test.go
--- snapd-2.42.1+18.04/client/snap_op_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/client/snap_op_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -27,6 +27,7 @@
"io/ioutil"
"mime"
"mime/multipart"
+ "net/http"
"path/filepath"
"gopkg.in/check.v1"
@@ -140,6 +141,9 @@
c.Assert(cs.req.Header.Get("Content-Type"), check.Equals, "application/json", check.Commentf(s.action))
+ _, ok := cs.req.Context().Deadline()
+ c.Check(ok, check.Equals, true)
+
body, err := ioutil.ReadAll(cs.req.Body)
c.Assert(err, check.IsNil, check.Commentf(s.action))
jsonBody := make(map[string]string)
@@ -233,6 +237,8 @@
c.Check(cs.req.Method, check.Equals, "POST")
c.Check(cs.req.URL.Path, check.Equals, fmt.Sprintf("/v2/snaps"))
c.Assert(cs.req.Header.Get("Content-Type"), check.Matches, "multipart/form-data; boundary=.*")
+ _, ok := cs.req.Context().Deadline()
+ c.Assert(ok, check.Equals, false)
c.Check(id, check.Equals, "66b3")
}
@@ -434,3 +440,99 @@
c.Check(string(buf), check.Equals, expected)
}
}
+
+func (cs *clientSuite) TestClientOpDownload(c *check.C) {
+ cs.status = 200
+ cs.header = http.Header{
+ "Content-Disposition": {"attachment; filename=foo_2.snap"},
+ "Snap-Sha3-384": {"sha3sha3sha3"},
+ "Snap-Download-Token": {"some-token"},
+ }
+ cs.contentLength = 1234
+
+ cs.rsp = `lots-of-foo-data`
+
+ dlInfo, rc, err := cs.cli.Download("foo", &client.DownloadOptions{
+ SnapOptions: client.SnapOptions{
+ Revision: "2",
+ Channel: "edge",
+ },
+ HeaderPeek: true,
+ })
+ c.Check(err, check.IsNil)
+ c.Check(dlInfo, check.DeepEquals, &client.DownloadInfo{
+ SuggestedFileName: "foo_2.snap",
+ Size: 1234,
+ Sha3_384: "sha3sha3sha3",
+ ResumeToken: "some-token",
+ })
+
+ // check we posted the right stuff
+ c.Assert(cs.req.Header.Get("Content-Type"), check.Equals, "application/json")
+ c.Assert(cs.req.Header.Get("range"), check.Equals, "")
+ body, err := ioutil.ReadAll(cs.req.Body)
+ c.Assert(err, check.IsNil)
+ var jsonBody client.DownloadAction
+ err = json.Unmarshal(body, &jsonBody)
+ c.Assert(err, check.IsNil)
+ c.Check(jsonBody.SnapName, check.DeepEquals, "foo")
+ c.Check(jsonBody.Revision, check.Equals, "2")
+ c.Check(jsonBody.Channel, check.Equals, "edge")
+ c.Check(jsonBody.HeaderPeek, check.Equals, true)
+
+ // ensure we can read the response
+ content, err := ioutil.ReadAll(rc)
+ c.Assert(err, check.IsNil)
+ c.Check(string(content), check.Equals, cs.rsp)
+ // and we can close it
+ c.Check(rc.Close(), check.IsNil)
+}
+
+func (cs *clientSuite) TestClientOpDownloadResume(c *check.C) {
+ cs.status = 200
+ cs.header = http.Header{
+ "Content-Disposition": {"attachment; filename=foo_2.snap"},
+ "Snap-Sha3-384": {"sha3sha3sha3"},
+ }
+ // we resume
+ cs.contentLength = 1234 - 64
+
+ cs.rsp = `lots-of-foo-data`
+
+ dlInfo, rc, err := cs.cli.Download("foo", &client.DownloadOptions{
+ SnapOptions: client.SnapOptions{
+ Revision: "2",
+ Channel: "edge",
+ },
+ HeaderPeek: true,
+ ResumeToken: "some-token",
+ Resume: 64,
+ })
+ c.Check(err, check.IsNil)
+ c.Check(dlInfo, check.DeepEquals, &client.DownloadInfo{
+ SuggestedFileName: "foo_2.snap",
+ Size: 1234 - 64,
+ Sha3_384: "sha3sha3sha3",
+ })
+
+ // check we posted the right stuff
+ c.Assert(cs.req.Header.Get("Content-Type"), check.Equals, "application/json")
+ c.Assert(cs.req.Header.Get("range"), check.Equals, "bytes: 64-")
+ body, err := ioutil.ReadAll(cs.req.Body)
+ c.Assert(err, check.IsNil)
+ var jsonBody client.DownloadAction
+ err = json.Unmarshal(body, &jsonBody)
+ c.Assert(err, check.IsNil)
+ c.Check(jsonBody.SnapName, check.DeepEquals, "foo")
+ c.Check(jsonBody.Revision, check.Equals, "2")
+ c.Check(jsonBody.Channel, check.Equals, "edge")
+ c.Check(jsonBody.HeaderPeek, check.Equals, true)
+ c.Check(jsonBody.ResumeToken, check.Equals, "some-token")
+
+ // ensure we can read the response
+ content, err := ioutil.ReadAll(rc)
+ c.Assert(err, check.IsNil)
+ c.Check(string(content), check.Equals, cs.rsp)
+ // and we can close it
+ c.Check(rc.Close(), check.IsNil)
+}
diff -Nru snapd-2.42.1+18.04/client/systems.go snapd-2.45.1+18.04/client/systems.go
--- snapd-2.42.1+18.04/client/systems.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/client/systems.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,105 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
.
+ *
+ */
+
+package client
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+
+ "golang.org/x/xerrors"
+
+ "github.com/snapcore/snapd/snap"
+)
+
+// SystemModelData contains information about the model
+type SystemModelData struct {
+ // Model as the model assertion
+ Model string `json:"model,omitempty"`
+ // BrandID corresponds to brand-id in the model assertion
+ BrandID string `json:"brand-id,omitempty"`
+ // DisplayName is human friendly name, corresponds to display-name in
+ // the model assertion
+ DisplayName string `json:"display-name,omitempty"`
+}
+
+type System struct {
+ // Current is true when the system running now was installed from that
+ // recovery seed
+ Current bool `json:"current,omitempty"`
+ // Label of the recovery system
+ Label string `json:"label,omitempty"`
+ // Model information
+ Model SystemModelData `json:"model,omitempty"`
+ // Brand information
+ Brand snap.StoreAccount `json:"brand,omitempty"`
+ // Actions available for this system
+ Actions []SystemAction `json:"actions,omitempty"`
+}
+
+type SystemAction struct {
+ // Title is a user presentable action description
+ Title string `json:"title,omitempty"`
+ // Mode given action can be executed in
+ Mode string `json:"mode,omitempty"`
+}
+
+// ListSystems list all systems available for seeding or recovery.
+func (client *Client) ListSystems() ([]System, error) {
+ type systemsResponse struct {
+ Systems []System `json:"systems,omitempty"`
+ }
+
+ var rsp systemsResponse
+
+ if _, err := client.doSync("GET", "/v2/systems", nil, nil, nil, &rsp); err != nil {
+ return nil, xerrors.Errorf("cannot list recovery systems: %v", err)
+ }
+ return rsp.Systems, nil
+}
+
+// DoSystemAction issues a request to perform an action using the given seed
+// system and its mode.
+func (client *Client) DoSystemAction(systemLabel string, action *SystemAction) error {
+ if systemLabel == "" {
+ return fmt.Errorf("cannot request an action without the system")
+ }
+ if action == nil {
+ return fmt.Errorf("cannot request an action without one")
+ }
+ // deeper verification is done by the backend
+
+ req := struct {
+ Action string `json:"action"`
+ *SystemAction
+ }{
+ Action: "do",
+ SystemAction: action,
+ }
+
+ var body bytes.Buffer
+ if err := json.NewEncoder(&body).Encode(&req); err != nil {
+ return err
+ }
+ if _, err := client.doSync("POST", "/v2/systems/"+systemLabel, nil, nil, &body, nil); err != nil {
+ return xerrors.Errorf("cannot request system action: %v", err)
+ }
+ return nil
+}
diff -Nru snapd-2.42.1+18.04/client/systems_test.go snapd-2.45.1+18.04/client/systems_test.go
--- snapd-2.42.1+18.04/client/systems_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/client/systems_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,171 @@
+// -*- 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 client_test
+
+import (
+ "encoding/json"
+ "io/ioutil"
+
+ "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/client"
+ "github.com/snapcore/snapd/snap"
+)
+
+func (cs *clientSuite) TestListSystemsSome(c *check.C) {
+ cs.rsp = `{
+ "type": "sync",
+ "status-code": 200,
+ "result": {
+ "systems": [
+ {
+ "current": true,
+ "label": "20200101",
+ "model": {
+ "model": "this-is-model-id",
+ "brand-id": "brand-id-1",
+ "display-name": "wonky model"
+ },
+ "brand": {
+ "id": "brand-id-1",
+ "username": "brand",
+ "display-name": "wonky publishing"
+ },
+ "actions": [
+ {"title": "recover", "mode": "recover"},
+ {"title": "reinstall", "mode": "install"}
+ ]
+ }, {
+ "label": "20200311",
+ "model": {
+ "model": "different-model-id",
+ "brand-id": "bulky-brand-id-1",
+ "display-name": "bulky model"
+ },
+ "brand": {
+ "id": "bulky-brand-id-1",
+ "username": "bulky-brand",
+ "display-name": "bulky publishing"
+ },
+ "actions": [
+ {"title": "factory-reset", "mode": "install"}
+ ]
+ }
+ ]
+ }
+ }`
+ systems, err := cs.cli.ListSystems()
+ c.Assert(err, check.IsNil)
+ c.Check(cs.req.Method, check.Equals, "GET")
+ c.Check(cs.req.URL.Path, check.Equals, "/v2/systems")
+ c.Check(systems, check.DeepEquals, []client.System{
+ {
+ Current: true,
+ Label: "20200101",
+ Model: client.SystemModelData{
+ Model: "this-is-model-id",
+ BrandID: "brand-id-1",
+ DisplayName: "wonky model",
+ },
+ Brand: snap.StoreAccount{
+ ID: "brand-id-1",
+ Username: "brand",
+ DisplayName: "wonky publishing",
+ },
+ Actions: []client.SystemAction{
+ {Title: "recover", Mode: "recover"},
+ {Title: "reinstall", Mode: "install"},
+ },
+ }, {
+ Label: "20200311",
+ Model: client.SystemModelData{
+ Model: "different-model-id",
+ BrandID: "bulky-brand-id-1",
+ DisplayName: "bulky model",
+ },
+ Brand: snap.StoreAccount{
+ ID: "bulky-brand-id-1",
+ Username: "bulky-brand",
+ DisplayName: "bulky publishing",
+ },
+ Actions: []client.SystemAction{
+ {Title: "factory-reset", Mode: "install"},
+ },
+ },
+ })
+}
+
+func (cs *clientSuite) TestListSystemsNone(c *check.C) {
+ cs.rsp = `{
+ "type": "sync",
+ "status-code": 200,
+ "result": {}
+ }`
+ systems, err := cs.cli.ListSystems()
+ c.Assert(err, check.IsNil)
+ c.Check(cs.req.Method, check.Equals, "GET")
+ c.Check(cs.req.URL.Path, check.Equals, "/v2/systems")
+ c.Check(systems, check.HasLen, 0)
+}
+
+func (cs *clientSuite) TestRequestSystemActionHappy(c *check.C) {
+ cs.rsp = `{
+ "type": "sync",
+ "status-code": 200,
+ "result": {}
+ }`
+ err := cs.cli.DoSystemAction("1234", &client.SystemAction{
+ Title: "reinstall",
+ Mode: "install",
+ })
+ c.Assert(err, check.IsNil)
+ c.Check(cs.req.Method, check.Equals, "POST")
+ c.Check(cs.req.URL.Path, check.Equals, "/v2/systems/1234")
+
+ body, err := ioutil.ReadAll(cs.req.Body)
+ c.Assert(err, check.IsNil)
+ var req map[string]interface{}
+ err = json.Unmarshal(body, &req)
+ c.Assert(err, check.IsNil)
+ c.Assert(req, check.DeepEquals, map[string]interface{}{
+ "action": "do",
+ "title": "reinstall",
+ "mode": "install",
+ })
+}
+
+func (cs *clientSuite) TestRequestSystemActionError(c *check.C) {
+ cs.rsp = `{
+ "type": "error",
+ "status-code": 500,
+ "result": {"message": "failed"}
+ }`
+ err := cs.cli.DoSystemAction("1234", &client.SystemAction{Mode: "install"})
+ c.Assert(err, check.ErrorMatches, "cannot request system action: failed")
+ c.Check(cs.req.Method, check.Equals, "POST")
+ c.Check(cs.req.URL.Path, check.Equals, "/v2/systems/1234")
+}
+
+func (cs *clientSuite) TestRequestSystemActionInvalid(c *check.C) {
+ err := cs.cli.DoSystemAction("", &client.SystemAction{})
+ c.Assert(err, check.ErrorMatches, "cannot request an action without the system")
+ err = cs.cli.DoSystemAction("1234", nil)
+ c.Assert(err, check.ErrorMatches, "cannot request an action without one")
+}
diff -Nru snapd-2.42.1+18.04/client/users.go snapd-2.45.1+18.04/client/users.go
--- snapd-2.42.1+18.04/client/users.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/client/users.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,143 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2015-2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
.
+ *
+ */
+
+package client
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+)
+
+// CreateUserResult holds the result of a user creation.
+type CreateUserResult struct {
+ Username string `json:"username"`
+ SSHKeys []string `json:"ssh-keys"`
+}
+
+// CreateUserOptions holds options for creating a local system user.
+//
+// If Known is false, the provided email is used to query the store for
+// username and SSH key details.
+//
+// If Known is true, the user will be created by looking through existing
+// system-user assertions and looking for a matching email. If Email is
+// empty then all such assertions are considered and multiple users may
+// be created.
+type CreateUserOptions struct {
+ Email string `json:"email,omitempty"`
+ Sudoer bool `json:"sudoer,omitempty"`
+ Known bool `json:"known,omitempty"`
+ ForceManaged bool `json:"force-managed,omitempty"`
+}
+
+// RemoveUserOptions holds options for removing a local system user.
+type RemoveUserOptions struct {
+ // Username indicates which user to remove.
+ Username string `json:"username,omitempty"`
+}
+
+type userAction struct {
+ Action string `json:"action"`
+ *CreateUserOptions
+ *RemoveUserOptions
+}
+
+func (client *Client) doUserAction(act *userAction, result interface{}) error {
+ data, err := json.Marshal(act)
+ if err != nil {
+ return err
+ }
+
+ _, err = client.doSync("POST", "/v2/users", nil, nil, bytes.NewReader(data), result)
+ return err
+}
+
+// CreateUser creates a local system user. See CreateUserOptions for details.
+func (client *Client) CreateUser(options *CreateUserOptions) (*CreateUserResult, error) {
+ if options == nil || options.Email == "" {
+ return nil, fmt.Errorf("cannot create a user without providing an email")
+ }
+
+ var result []*CreateUserResult
+ err := client.doUserAction(&userAction{Action: "create", CreateUserOptions: options}, &result)
+ if err != nil {
+ return nil, fmt.Errorf("while creating user: %v", err)
+ }
+ return result[0], nil
+}
+
+// CreateUsers creates multiple local system users. See CreateUserOptions for details.
+//
+// Results may be provided even if there are errors.
+func (client *Client) CreateUsers(options []*CreateUserOptions) ([]*CreateUserResult, error) {
+ for _, opts := range options {
+ if opts == nil || (opts.Email == "" && !opts.Known) {
+ return nil, fmt.Errorf("cannot create user from store details without an email to query for")
+ }
+ }
+
+ var results []*CreateUserResult
+ var errs []error
+ for _, opts := range options {
+ var result []*CreateUserResult
+ err := client.doUserAction(&userAction{Action: "create", CreateUserOptions: opts}, &result)
+ if err != nil {
+ errs = append(errs, err)
+ } else {
+ results = append(results, result...)
+ }
+ }
+
+ if len(errs) == 1 {
+ return results, errs[0]
+ }
+ if len(errs) > 1 {
+ var buf bytes.Buffer
+ for _, err := range errs {
+ fmt.Fprintf(&buf, "\n- %s", err)
+ }
+ return results, fmt.Errorf("while creating users:%s", buf.Bytes())
+ }
+ return results, nil
+}
+
+// RemoveUser removes a local system user.
+func (client *Client) RemoveUser(options *RemoveUserOptions) (removed []*User, err error) {
+ if options == nil || options.Username == "" {
+ return nil, fmt.Errorf("cannot remove a user without providing a username")
+ }
+ var result struct {
+ Removed []*User `json:"removed"`
+ }
+ if err := client.doUserAction(&userAction{Action: "remove", RemoveUserOptions: options}, &result); err != nil {
+ return nil, err
+ }
+ return result.Removed, nil
+}
+
+// Users returns the local users.
+func (client *Client) Users() ([]*User, error) {
+ var result []*User
+
+ if _, err := client.doSync("GET", "/v2/users", nil, nil, nil, &result); err != nil {
+ return nil, fmt.Errorf("while getting users: %v", err)
+ }
+ return result, nil
+}
diff -Nru snapd-2.42.1+18.04/client/users_test.go snapd-2.45.1+18.04/client/users_test.go
--- snapd-2.42.1+18.04/client/users_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/client/users_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,184 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2015-2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
.
+ *
+ */
+
+package client_test
+
+import (
+ "io/ioutil"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/client"
+)
+
+func (cs *clientSuite) TestClientRemoveUser(c *C) {
+ removed, err := cs.cli.RemoveUser(&client.RemoveUserOptions{})
+ c.Assert(err, ErrorMatches, "cannot remove a user without providing a username")
+ c.Assert(removed, IsNil)
+
+ cs.rsp = `{
+ "type": "sync",
+ "result": {
+ "removed": [{"id": 11, "username": "one-user", "email": "user@test.com"}]
+ }
+ }`
+ removed, err = cs.cli.RemoveUser(&client.RemoveUserOptions{Username: "one-user"})
+ c.Assert(cs.req.Method, Equals, "POST")
+ c.Assert(cs.req.URL.Path, Equals, "/v2/users")
+ c.Assert(err, IsNil)
+ c.Assert(removed, DeepEquals, []*client.User{
+ {ID: 11, Username: "one-user", Email: "user@test.com"},
+ })
+
+ body, err := ioutil.ReadAll(cs.req.Body)
+ c.Assert(err, IsNil)
+ c.Assert(string(body), Equals, `{"action":"remove","username":"one-user"}`)
+}
+
+func (cs *clientSuite) TestClientRemoveUserError(c *C) {
+ removed, err := cs.cli.RemoveUser(nil)
+ c.Assert(err, ErrorMatches, "cannot remove a user without providing a username")
+ c.Assert(removed, IsNil)
+ removed, err = cs.cli.RemoveUser(&client.RemoveUserOptions{})
+ c.Assert(err, ErrorMatches, "cannot remove a user without providing a username")
+ c.Assert(removed, IsNil)
+
+ cs.rsp = `{
+ "type": "error",
+ "result": {"message": "no can do"}
+ }`
+ removed, err = cs.cli.RemoveUser(&client.RemoveUserOptions{Username: "one-user"})
+ c.Assert(cs.req.Method, Equals, "POST")
+ c.Assert(cs.req.URL.Path, Equals, "/v2/users")
+ c.Assert(err, ErrorMatches, "no can do")
+ c.Assert(removed, IsNil)
+
+ body, err := ioutil.ReadAll(cs.req.Body)
+ c.Assert(err, IsNil)
+ c.Assert(string(body), Equals, `{"action":"remove","username":"one-user"}`)
+}
+
+func (cs *clientSuite) TestClientCreateUser(c *C) {
+ _, err := cs.cli.CreateUser(nil)
+ c.Assert(err, ErrorMatches, "cannot create a user without providing an email")
+ _, err = cs.cli.CreateUser(&client.CreateUserOptions{})
+ c.Assert(err, ErrorMatches, "cannot create a user without providing an email")
+
+ cs.rsp = `{
+ "type": "sync",
+ "result": [{
+ "username": "karl",
+ "ssh-keys": ["one", "two"]
+ }]
+ }`
+ rsp, err := cs.cli.CreateUser(&client.CreateUserOptions{Email: "one@email.com", Sudoer: true, Known: true})
+ c.Assert(cs.req.Method, Equals, "POST")
+ c.Assert(cs.req.URL.Path, Equals, "/v2/users")
+ c.Assert(err, IsNil)
+
+ body, err := ioutil.ReadAll(cs.req.Body)
+ c.Assert(err, IsNil)
+ c.Assert(string(body), Equals, `{"action":"create","email":"one@email.com","sudoer":true,"known":true}`)
+
+ c.Assert(rsp, DeepEquals, &client.CreateUserResult{
+ Username: "karl",
+ SSHKeys: []string{"one", "two"},
+ })
+}
+
+var createUsersTests = []struct {
+ options []*client.CreateUserOptions
+ bodies []string
+ responses []string
+ results []*client.CreateUserResult
+ error string
+}{{
+ // nothing in -> nothing out
+ options: nil,
+}, {
+ options: []*client.CreateUserOptions{nil},
+ error: "cannot create user from store details without an email to query for",
+}, {
+ options: []*client.CreateUserOptions{{}},
+ error: "cannot create user from store details without an email to query for",
+}, {
+ options: []*client.CreateUserOptions{{
+ Email: "one@example.com",
+ Sudoer: true,
+ }, {
+ Known: true,
+ }},
+ bodies: []string{
+ `{"action":"create","email":"one@example.com","sudoer":true}`,
+ `{"action":"create","known":true}`,
+ },
+ responses: []string{
+ `{"type": "sync", "result": [{"username": "one", "ssh-keys":["a", "b"]}]}`,
+ `{"type": "sync", "result": [{"username": "two"}, {"username": "three"}]}`,
+ },
+ results: []*client.CreateUserResult{{
+ Username: "one",
+ SSHKeys: []string{"a", "b"},
+ }, {
+ Username: "two",
+ }, {
+ Username: "three",
+ }},
+}}
+
+func (cs *clientSuite) TestClientCreateUsers(c *C) {
+ for _, test := range createUsersTests {
+ cs.rsps = test.responses
+
+ results, err := cs.cli.CreateUsers(test.options)
+ if test.error != "" {
+ c.Assert(err, ErrorMatches, test.error)
+ }
+ c.Assert(results, DeepEquals, test.results)
+
+ var bodies []string
+ for _, req := range cs.reqs {
+ c.Assert(req.Method, Equals, "POST")
+ c.Assert(req.URL.Path, Equals, "/v2/users")
+ data, err := ioutil.ReadAll(req.Body)
+ c.Assert(err, IsNil)
+ bodies = append(bodies, string(data))
+ }
+
+ c.Assert(bodies, DeepEquals, test.bodies)
+ }
+}
+
+func (cs *clientSuite) TestClientJSONError(c *C) {
+ cs.rsp = `some non-json error message`
+ _, err := cs.cli.SysInfo()
+ c.Assert(err, ErrorMatches, `cannot obtain system details: cannot decode "some non-json error message": invalid char.*`)
+}
+
+func (cs *clientSuite) TestUsers(c *C) {
+ cs.rsp = `{"type": "sync", "result":
+ [{"username": "foo","email":"foo@example.com"},
+ {"username": "bar","email":"bar@example.com"}]}`
+ users, err := cs.cli.Users()
+ c.Check(err, IsNil)
+ c.Check(users, DeepEquals, []*client.User{
+ {Username: "foo", Email: "foo@example.com"},
+ {Username: "bar", Email: "bar@example.com"},
+ })
+}
diff -Nru snapd-2.42.1+18.04/cmd/cmd_linux.go snapd-2.45.1+18.04/cmd/cmd_linux.go
--- snapd-2.42.1+18.04/cmd/cmd_linux.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/cmd_linux.go 2020-06-05 13:13:49.000000000 +0000
@@ -20,14 +20,13 @@
package cmd
import (
- "bytes"
- "io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"syscall"
+ "github.com/snapcore/snapd/cmd/cmdutil"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
@@ -78,29 +77,13 @@
// 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(coreOrSnapdPath string) bool {
- fullInfo := filepath.Join(coreOrSnapdPath, filepath.Join(dirs.CoreLibExecDir, "info"))
- content, err := ioutil.ReadFile(fullInfo)
+ infoPath := filepath.Join(coreOrSnapdPath, filepath.Join(dirs.CoreLibExecDir, "info"))
+ ver, err := cmdutil.SnapdVersionFromInfoFile(infoPath)
if err != nil {
- if !os.IsNotExist(err) {
- logger.Noticef("cannot open snapd info file %q: %s", fullInfo, err)
- }
+ logger.Noticef("%v", err)
return false
}
- if !bytes.HasPrefix(content, []byte("VERSION=")) {
- idx := bytes.Index(content, []byte("\nVERSION="))
- if idx < 0 {
- logger.Noticef("cannot find snapd version information in %q", content)
- return false
- }
- content = content[idx+1:]
- }
- content = content[8:]
- idx := bytes.IndexByte(content, '\n')
- if idx > -1 {
- content = content[:idx]
- }
- ver := string(content)
// > 0 means our Version is bigger than the version of snapd in core
res, err := strutil.VersionCompare(Version, ver)
if err != nil {
diff -Nru snapd-2.42.1+18.04/cmd/cmdutil/cmdutil.go snapd-2.45.1+18.04/cmd/cmdutil/cmdutil.go
--- snapd-2.42.1+18.04/cmd/cmdutil/cmdutil.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/cmdutil/cmdutil.go 2020-06-05 13:13:49.000000000 +0000
@@ -16,6 +16,7 @@
* along with this program. If not, see
.
*
*/
+
package cmdutil
import (
diff -Nru snapd-2.42.1+18.04/cmd/cmdutil/version.go snapd-2.45.1+18.04/cmd/cmdutil/version.go
--- snapd-2.42.1+18.04/cmd/cmdutil/version.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/cmdutil/version.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,53 @@
+// -*- 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 cmdutil
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+)
+
+// SnapdVersionFromInfoFile returns snapd version read for the
+// given info" file, pointed by infoPath.
+// The format of the "info" file is a single line with "VERSION=..."
+// in it. The file is produced by mkversion.sh and normally installed
+// along snapd binary in /usr/lib/snapd.
+func SnapdVersionFromInfoFile(infoPath string) (string, error) {
+ content, err := ioutil.ReadFile(infoPath)
+ if err != nil {
+ return "", fmt.Errorf("cannot open snapd info file %q: %s", infoPath, err)
+ }
+
+ if !bytes.HasPrefix(content, []byte("VERSION=")) {
+ idx := bytes.Index(content, []byte("\nVERSION="))
+ if idx < 0 {
+ return "", fmt.Errorf("cannot find snapd version information in %q", content)
+ }
+ content = content[idx+1:]
+ }
+ content = content[8:]
+ idx := bytes.IndexByte(content, '\n')
+ if idx > -1 {
+ content = content[:idx]
+ }
+
+ return string(content), nil
+}
diff -Nru snapd-2.42.1+18.04/cmd/cmdutil/version_test.go snapd-2.45.1+18.04/cmd/cmdutil/version_test.go
--- snapd-2.42.1+18.04/cmd/cmdutil/version_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/cmdutil/version_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,57 @@
+// -*- 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 cmdutil_test
+
+import (
+ "io/ioutil"
+ "path/filepath"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/cmd/cmdutil"
+)
+
+type versionSuite struct{}
+
+var _ = Suite(&versionSuite{})
+
+func (s *versionSuite) TestNoVersionFile(c *C) {
+ _, err := cmdutil.SnapdVersionFromInfoFile("/non-existing-file")
+ c.Assert(err, ErrorMatches, `cannot open snapd info file "/non-existing-file":.*`)
+}
+
+func (s *versionSuite) TestNoVersionData(c *C) {
+ top := c.MkDir()
+ infoFile := filepath.Join(top, "info")
+ c.Assert(ioutil.WriteFile(infoFile, []byte("foo"), 0644), IsNil)
+
+ _, err := cmdutil.SnapdVersionFromInfoFile(infoFile)
+ c.Assert(err, ErrorMatches, `cannot find snapd version information in "foo"`)
+}
+
+func (s *versionSuite) TestVersionHappy(c *C) {
+ top := c.MkDir()
+ infoFile := filepath.Join(top, "info")
+ c.Assert(ioutil.WriteFile(infoFile, []byte("VERSION=1.2.3"), 0644), IsNil)
+
+ ver, err := cmdutil.SnapdVersionFromInfoFile(infoFile)
+ c.Assert(err, IsNil)
+ c.Check(ver, Equals, "1.2.3")
+}
diff -Nru snapd-2.42.1+18.04/cmd/configure.ac snapd-2.45.1+18.04/cmd/configure.ac
--- snapd-2.42.1+18.04/cmd/configure.ac 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/configure.ac 2020-06-05 13:13:49.000000000 +0000
@@ -159,18 +159,9 @@
AC_SUBST(SNAP_MOUNT_DIR)
AC_DEFINE_UNQUOTED([SNAP_MOUNT_DIR], "${SNAP_MOUNT_DIR}", [Location of the snap mount points])
-AC_ARG_ENABLE([caps-over-setuid],
- AS_HELP_STRING([--enable-caps-over-setuid], [Use capabilities rather than setuid bit]),
- [case "${enableval}" in
- yes) enable_caps_over_setuid=yes ;;
- no) enable_caps_over_setuid=no ;;
- *) AC_MSG_ERROR([bad value ${enableval} for --enable-caps-over-setuid])
- esac], [enable_caps_over_setuid=no])
-AM_CONDITIONAL([CAPS_OVER_SETUID], [test "x$enable_caps_over_setuid" = "xyes"])
-
-AS_IF([test "x$enable_caps_over_setuid" = "xyes"], [
- AC_DEFINE([CAPS_OVER_SETUID], [1],
- [Use capabilities rather than setuid bit])])
+SNAP_MOUNT_DIR_SYSTEMD_UNIT="$(systemd-escape -p "$SNAP_MOUNT_DIR")"
+AC_SUBST([SNAP_MOUNT_DIR_SYSTEMD_UNIT])
+AC_DEFINE_UNQUOTED([SNAP_MOUNT_DIR_SYSTEMD_UNIT], "${SNAP_MOUNT_DIR_SYSTEMD_UNIT}", [Systemd unit name for snap mount points location])
AC_PATH_PROGS([HAVE_RST2MAN],[rst2man rst2man.py])
AS_IF([test "x$HAVE_RST2MAN" = "x"], [AC_MSG_WARN(["cannot find the rst2man tool, install python-docutils or similar"])])
diff -Nru snapd-2.42.1+18.04/cmd/libsnap-confine-private/cgroup-freezer-support.c snapd-2.45.1+18.04/cmd/libsnap-confine-private/cgroup-freezer-support.c
--- snapd-2.42.1+18.04/cmd/libsnap-confine-private/cgroup-freezer-support.c 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/libsnap-confine-private/cgroup-freezer-support.c 2020-06-05 13:13:49.000000000 +0000
@@ -84,7 +84,7 @@
FILE *cgroup_procs SC_CLEANUP(sc_cleanup_file) = NULL;
cgroup_procs = fdopen(cgroup_procs_fd, "r");
if (cgroup_procs == NULL) {
- die("cannot convert tasks file descriptor to FILE");
+ die("cannot convert cgroups.procs file descriptor to FILE");
}
cgroup_procs_fd = -1; // cgroup_procs_fd will now be closed by fclose.
@@ -92,7 +92,7 @@
size_t line_buf_size = 0;
ssize_t num_read;
struct stat statbuf;
- do {
+ for (;;) {
num_read = getline(&line_buf, &line_buf_size, cgroup_procs);
if (num_read < 0 && errno != 0) {
die("cannot read next PID belonging to snap %s",
@@ -119,7 +119,7 @@
debug("found process %s belonging to user %d",
line_buf, statbuf.st_uid);
return true;
- } while (num_read > 0);
+ }
return false;
}
diff -Nru snapd-2.42.1+18.04/cmd/libsnap-confine-private/cgroup-freezer-support.h snapd-2.45.1+18.04/cmd/libsnap-confine-private/cgroup-freezer-support.h
--- snapd-2.42.1+18.04/cmd/libsnap-confine-private/cgroup-freezer-support.h 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/libsnap-confine-private/cgroup-freezer-support.h 2020-06-05 13:13:49.000000000 +0000
@@ -31,7 +31,7 @@
* allows us to track processes belonging to a given snap. This makes the
* measurement "are any processes of this snap still alive" very simple.
*
- * The "tasks" file belonging to the cgroup contains the set of all the
+ * The "cgroup.procs" file belonging to the cgroup contains the set of all the
* processes that originate from the given snap. Examining that file one can
* reliably determine if the set is empty or not.
*
diff -Nru snapd-2.42.1+18.04/cmd/libsnap-confine-private/cgroup-support.c snapd-2.45.1+18.04/cmd/libsnap-confine-private/cgroup-support.c
--- snapd-2.42.1+18.04/cmd/libsnap-confine-private/cgroup-support.c 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/libsnap-confine-private/cgroup-support.c 2020-06-05 13:13:49.000000000 +0000
@@ -38,31 +38,31 @@
if (parent_fd < 0) {
die("cannot open cgroup hierarchy %s", parent);
}
+ // Since we may be running from a setuid but not setgid executable, switch
+ // to the effective group to root so that the mkdirat call creates a cgroup
+ // that is always owned by root.root.
+ sc_identity old = sc_set_effective_identity(sc_root_group_identity());
if (mkdirat(parent_fd, name, 0755) < 0 && errno != EEXIST) {
die("cannot create cgroup hierarchy %s/%s", parent, name);
}
+ (void)sc_set_effective_identity(old);
int hierarchy_fd SC_CLEANUP(sc_cleanup_close) = -1;
hierarchy_fd = openat(parent_fd, name, O_PATH | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
if (hierarchy_fd < 0) {
die("cannot open cgroup hierarchy %s/%s", parent, name);
}
- // Since we may be running from a setuid but not setgid executable, ensure
- // that the group and owner of the hierarchy directory is root.root.
- if (fchownat(hierarchy_fd, "", 0, 0, AT_EMPTY_PATH) < 0) {
- die("cannot change owner of cgroup hierarchy %s/%s to root.root", parent, name);
+ // Open the cgroup.procs file.
+ int procs_fd SC_CLEANUP(sc_cleanup_close) = -1;
+ procs_fd = openat(hierarchy_fd, "cgroup.procs", O_WRONLY | O_NOFOLLOW | O_CLOEXEC);
+ if (procs_fd < 0) {
+ die("cannot open file %s/%s/cgroup.procs", parent, name);
}
- // Open the tasks file.
- int tasks_fd SC_CLEANUP(sc_cleanup_close) = -1;
- tasks_fd = openat(hierarchy_fd, "tasks", O_WRONLY | O_NOFOLLOW | O_CLOEXEC);
- if (tasks_fd < 0) {
- die("cannot open file %s/%s/tasks", parent, name);
- }
- // Write the process (task) number to the tasks file. Linux task IDs are
+ // Write the process (task) number to the procs file. Linux task IDs are
// limited to 2^29 so a long int is enough to represent it.
// See include/linux/threads.h in the kernel source tree for details.
char buf[22] = {0}; // 2^64 base10 + 2 for NUL and '-' for long
int n = sc_must_snprintf(buf, sizeof buf, "%ld", (long)pid);
- if (write(tasks_fd, buf, n) < n) {
+ if (write(procs_fd, buf, n) < n) {
die("cannot move process %ld to cgroup hierarchy %s/%s", (long)pid, parent, name);
}
debug("moved process %ld to cgroup hierarchy %s/%s", (long)pid, parent, name);
diff -Nru snapd-2.42.1+18.04/cmd/libsnap-confine-private/locking.c snapd-2.45.1+18.04/cmd/libsnap-confine-private/locking.c
--- snapd-2.42.1+18.04/cmd/libsnap-confine-private/locking.c 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/libsnap-confine-private/locking.c 2020-06-05 13:13:49.000000000 +0000
@@ -94,12 +94,14 @@
{
// Create (if required) and open the lock directory.
debug("creating lock directory %s (if missing)", sc_lock_dir);
+ sc_identity old = sc_set_effective_identity(sc_root_group_identity());
if (sc_nonfatal_mkpath(sc_lock_dir, 0755) < 0) {
die("cannot create lock directory %s", sc_lock_dir);
}
debug("opening lock directory %s", sc_lock_dir);
int dir_fd =
open(sc_lock_dir, O_DIRECTORY | O_PATH | O_CLOEXEC | O_NOFOLLOW);
+ (void)sc_set_effective_identity(old);
if (dir_fd < 0) {
die("cannot open lock directory");
}
@@ -131,8 +133,10 @@
// Open the lock file and acquire an exclusive lock.
debug("opening lock file: %s/%s", sc_lock_dir, lock_fname);
+ sc_identity old = sc_set_effective_identity(sc_root_group_identity());
lock_fd = openat(dir_fd, lock_fname,
O_CREAT | O_RDWR | O_CLOEXEC | O_NOFOLLOW, 0600);
+ (void)sc_set_effective_identity(old);
if (lock_fd < 0) {
die("cannot open lock file: %s/%s", sc_lock_dir, lock_fname);
}
diff -Nru snapd-2.42.1+18.04/cmd/libsnap-confine-private/locking-test.c snapd-2.45.1+18.04/cmd/libsnap-confine-private/locking-test.c
--- snapd-2.42.1+18.04/cmd/libsnap-confine-private/locking-test.c 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/libsnap-confine-private/locking-test.c 2020-06-05 13:13:49.000000000 +0000
@@ -68,6 +68,11 @@
// Check that locking a namespace actually flock's the mutex with LOCK_EX
static void test_sc_lock_unlock(void)
{
+ if (geteuid() != 0) {
+ g_test_skip("this test only runs as root");
+ return;
+ }
+
const char *lock_dir = sc_test_use_fake_lock_dir();
int fd = sc_lock_generic("foo", 123);
// Construct the name of the lock file
@@ -95,6 +100,11 @@
// Check that holding a lock is properly detected.
static void test_sc_verify_snap_lock__locked(void)
{
+ if (geteuid() != 0) {
+ g_test_skip("this test only runs as root");
+ return;
+ }
+
(void)sc_test_use_fake_lock_dir();
int fd = sc_lock_snap("foo");
sc_verify_snap_lock("foo");
@@ -104,6 +114,11 @@
// Check that holding a lock is properly detected.
static void test_sc_verify_snap_lock__unlocked(void)
{
+ if (geteuid() != 0) {
+ g_test_skip("this test only runs as root");
+ return;
+ }
+
(void)sc_test_use_fake_lock_dir();
if (g_test_subprocess()) {
sc_verify_snap_lock("foo");
@@ -117,6 +132,11 @@
static void test_sc_enable_sanity_timeout(void)
{
+ if (geteuid() != 0) {
+ g_test_skip("this test only runs as root");
+ return;
+ }
+
if (g_test_subprocess()) {
sc_enable_sanity_timeout();
debug("waiting...");
diff -Nru snapd-2.42.1+18.04/cmd/libsnap-confine-private/tool.c snapd-2.45.1+18.04/cmd/libsnap-confine-private/tool.c
--- snapd-2.42.1+18.04/cmd/libsnap-confine-private/tool.c 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/libsnap-confine-private/tool.c 2020-06-05 13:13:49.000000000 +0000
@@ -88,9 +88,14 @@
snap_name_copy, NULL
};
char *envp[] = { "SNAPD_DEBUG=x", NULL };
+
+ /* Switch the group to root so that directories, files and locks created by
+ * snap-update-ns are owned by the root group. */
+ sc_identity old = sc_set_effective_identity(sc_root_group_identity());
sc_call_snapd_tool_with_apparmor(snap_update_ns_fd,
"snap-update-ns", apparmor,
aa_profile, argv, envp);
+ (void)sc_set_effective_identity(old);
}
void sc_call_snap_update_ns_as_user(int snap_update_ns_fd,
@@ -145,7 +150,11 @@
/* SNAPD_DEBUG=x is replaced by sc_call_snapd_tool_with_apparmor with
* either SNAPD_DEBUG=0 or SNAPD_DEBUG=1, see that function for details. */
char *envp[] = { "SNAPD_DEBUG=x", NULL };
+ /* Switch the group to root so that directories and locks created by
+ * snap-discard-ns are owned by the root group. */
+ sc_identity old = sc_set_effective_identity(sc_root_group_identity());
sc_call_snapd_tool(snap_discard_ns_fd, "snap-discard-ns", argv, envp);
+ (void)sc_set_effective_identity(old);
}
static int sc_open_snapd_tool(const char *tool_name)
diff -Nru snapd-2.42.1+18.04/cmd/libsnap-confine-private/utils.c snapd-2.45.1+18.04/cmd/libsnap-confine-private/utils.c
--- snapd-2.42.1+18.04/cmd/libsnap-confine-private/utils.c 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/libsnap-confine-private/utils.c 2020-06-05 13:13:49.000000000 +0000
@@ -140,6 +140,43 @@
die("fclose failed");
}
+sc_identity sc_set_effective_identity(sc_identity identity)
+{
+ debug("set_effective_identity uid:%d (change: %s), gid:%d (change: %s)",
+ identity.uid, identity.change_uid ? "yes" : "no",
+ identity.gid, identity.change_gid ? "yes" : "no");
+ /* We are being careful not to return a value instructing us to change GID
+ * or UID by accident. */
+ sc_identity old = {
+ .change_gid = 0,
+ .change_uid = 0,
+ };
+
+ if (identity.change_gid) {
+ old.gid = getegid();
+ old.change_gid = 1;
+ if (setegid(identity.gid) < 0) {
+ die("cannot set effective group to %d", identity.gid);
+ }
+ if (getegid() != identity.gid) {
+ die("effective group change from %d to %d has failed",
+ old.gid, identity.gid);
+ }
+ }
+ if (identity.change_uid) {
+ old.uid = geteuid();
+ old.change_uid = 1;
+ if (seteuid(identity.uid) < 0) {
+ die("cannot set effective user to %d", identity.uid);
+ }
+ if (geteuid() != identity.uid) {
+ die("effective user change from %d to %d has failed",
+ old.uid, identity.uid);
+ }
+ }
+ return old;
+}
+
int sc_nonfatal_mkpath(const char *const path, mode_t mode)
{
// If asked to create an empty path, return immediately.
diff -Nru snapd-2.42.1+18.04/cmd/libsnap-confine-private/utils.h snapd-2.45.1+18.04/cmd/libsnap-confine-private/utils.h
--- snapd-2.42.1+18.04/cmd/libsnap-confine-private/utils.h 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/libsnap-confine-private/utils.h 2020-06-05 13:13:49.000000000 +0000
@@ -39,6 +39,50 @@
**/
bool sc_is_reexec_enabled(void);
+/**
+ * sc_identity describes the user performing certain operation.
+ *
+ * UID and GID represent user and group accounts numbers and are controlled by
+ * change_uid and change_gid flags.
+**/
+typedef struct sc_identity {
+ uid_t uid;
+ gid_t gid;
+ unsigned change_uid:1;
+ unsigned change_gid:1;
+} sc_identity;
+
+/**
+ * Identity of the root group.
+ *
+ * The return value is suitable for passing to sc_set_effective_identity. It
+ * causes the effective group to change to the root group. No change is made to
+ * effective user identity.
+ **/
+static inline sc_identity sc_root_group_identity(void)
+{
+ sc_identity id = {
+ /* Explicitly set our intent of changing just the GID.
+ * Refactoring of this code must retain this property. */
+ .change_uid = 0,
+ .change_gid = 1,
+ .gid = 0,
+ };
+ return id;
+}
+
+/**
+ * Set the effective user and group IDs to given values.
+ *
+ * Effective user and group identifiers are applied to the system. The
+ * current values are returned as another identity that can be restored via
+ * another call to sc_set_effective_identity.
+ *
+ * The fields change_uid and change_gid control if user and group ID is changed.
+ * The returned old identity has identical values of both use flags.
+**/
+sc_identity sc_set_effective_identity(sc_identity identity);
+
void write_string_to_file(const char *filepath, const char *buf);
/**
diff -Nru snapd-2.42.1+18.04/cmd/Makefile.am snapd-2.45.1+18.04/cmd/Makefile.am
--- snapd-2.42.1+18.04/cmd/Makefile.am 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/Makefile.am 2020-06-05 13:13:49.000000000 +0000
@@ -82,11 +82,11 @@
fmt:: $(filter-out $(addprefix %,$(new_format)),$(foreach dir,$(subdirs),$(wildcard $(srcdir)/$(dir)/*.[ch])))
HOME=$(srcdir) indent $^
-# The hack target helps devlopers work on snap-confine on their live system by
+# The hack target helps developers work on snap-confine on their live system by
# installing a fresh copy of snap confine and the appropriate apparmor profile.
.PHONY: hack
hack: snap-confine/snap-confine-debug snap-confine/snap-confine.apparmor snap-update-ns/snap-update-ns snap-seccomp/snap-seccomp snap-discard-ns/snap-discard-ns
- sudo install -D -m 6755 snap-confine/snap-confine-debug $(DESTDIR)$(libexecdir)/snap-confine
+ sudo install -D -m 4755 snap-confine/snap-confine-debug $(DESTDIR)$(libexecdir)/snap-confine
if [ -d /etc/apparmor.d ]; then sudo install -m 644 snap-confine/snap-confine.apparmor $(DESTDIR)/etc/apparmor.d/$(patsubst .%,%,$(subst /,.,$(libexecdir))).snap-confine.real; fi
sudo install -d -m 755 $(DESTDIR)/var/lib/snapd/apparmor/snap-confine/
if [ "$$(command -v apparmor_parser)" != "" ]; then sudo apparmor_parser -r snap-confine/snap-confine.apparmor; fi
@@ -358,7 +358,7 @@
endif
snap-confine/snap-confine.apparmor: snap-confine/snap-confine.apparmor.in Makefile
- sed -e 's,[@]LIBEXECDIR[@],$(libexecdir),g' -e 's,[@]SNAP_MOUNT_DIR[@],$(SNAP_MOUNT_DIR),' <$< >$@
+ sed -e 's,[@]LIBEXECDIR[@],$(libexecdir),g' -e 's,[@]SNAP_MOUNT_DIR[@],$(SNAP_MOUNT_DIR),g' <$< >$@
# Install the apparmor profile
#
@@ -377,13 +377,8 @@
install -d -m 111 $(DESTDIR)/var/lib/snapd/void
install-exec-hook::
-if CAPS_OVER_SETUID
-# Ensure that snap-confine has CAP_SYS_ADMIN capability
- setcap cap_sys_admin=pe $(DESTDIR)$(libexecdir)/snap-confine
-else
-# Ensure that snap-confine is u+s,g+s (setuid and setgid)
- chmod 6755 $(DESTDIR)$(libexecdir)/snap-confine
-endif
+# Ensure that snap-confine is u+s (setuid)
+ chmod 4755 $(DESTDIR)$(libexecdir)/snap-confine
##
## snap-mgmt
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_auto_import.go snapd-2.45.1+18.04/cmd/snap/cmd_auto_import.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_auto_import.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_auto_import.go 2020-06-05 13:13:49.000000000 +0000
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2014-2016 Canonical Ltd
+ * Copyright (C) 2014-2020 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -28,17 +28,20 @@
"os"
"os/exec"
"path/filepath"
+ "sort"
"strings"
"syscall"
"github.com/jessevdk/go-flags"
+ "github.com/snapcore/snapd/boot"
"github.com/snapcore/snapd/client"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/i18n"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/release"
+ "github.com/snapcore/snapd/snapdenv"
)
const autoImportsName = "auto-import.assert"
@@ -56,6 +59,8 @@
}
defer f.Close()
+ isTesting := snapdenv.Testing()
+
scanner := bufio.NewScanner(f)
for scanner.Scan() {
l := strings.Fields(scanner.Text())
@@ -85,7 +90,7 @@
continue
}
// skip all ram disks (unless in tests)
- if !osutil.GetenvBool("SNAPPY_TESTING") && strings.HasPrefix(mountSrc, "/dev/ram") {
+ if !isTesting && strings.HasPrefix(mountSrc, "/dev/ram") {
continue
}
@@ -183,8 +188,10 @@
return added, nil
}
+var ioutilTempDir = ioutil.TempDir
+
func tryMount(deviceName string) (string, error) {
- tmpMountTarget, err := ioutil.TempDir("", "snapd-auto-import-mount-")
+ tmpMountTarget, err := ioutilTempDir("", "snapd-auto-import-mount-")
if err != nil {
err = fmt.Errorf("cannot create temporary mount point: %v", err)
logger.Noticef("error: %v", err)
@@ -206,8 +213,10 @@
return tmpMountTarget, nil
}
+var syscallUnmount = syscall.Unmount
+
func doUmount(mp string) error {
- if err := syscall.Unmount(mp, 0); err != nil {
+ if err := syscallUnmount(mp, 0); err != nil {
return err
}
return os.Remove(mp)
@@ -259,6 +268,55 @@
return cmd.Execute(nil)
}
+func removableBlockDevices() (removableDevices []string) {
+ // eg. /sys/block/sda/removable
+ removable, err := filepath.Glob(filepath.Join(dirs.GlobalRootDir, "/sys/block/*/removable"))
+ if err != nil {
+ return nil
+ }
+ for _, removableAttr := range removable {
+ val, err := ioutil.ReadFile(removableAttr)
+ if err != nil || string(val) != "1\n" {
+ // non removable
+ continue
+ }
+ // let's see if it has partitions
+ dev := filepath.Base(filepath.Dir(removableAttr))
+
+ pattern := fmt.Sprintf(filepath.Join(dirs.GlobalRootDir, "/sys/block/%s/%s*/partition"), dev, dev)
+ // eg. /sys/block/sda/sda1/partition
+ partitionAttrs, _ := filepath.Glob(pattern)
+
+ if len(partitionAttrs) == 0 {
+ // not partitioned? try to use the main device
+ removableDevices = append(removableDevices, fmt.Sprintf("/dev/%s", dev))
+ continue
+ }
+
+ for _, partAttr := range partitionAttrs {
+ val, err := ioutil.ReadFile(partAttr)
+ if err != nil || string(val) != "1\n" {
+ // non partition?
+ continue
+ }
+ pdev := filepath.Base(filepath.Dir(partAttr))
+ removableDevices = append(removableDevices, fmt.Sprintf("/dev/%s", pdev))
+ // hasPartitions = true
+ }
+ }
+ sort.Strings(removableDevices)
+ return removableDevices
+}
+
+// inInstallmode returns true if it's UC20 system in install mode
+func inInstallMode() bool {
+ mode, _, err := boot.ModeAndRecoverySystemFromKernelCommandLine()
+ if err != nil {
+ return false
+ }
+ return mode == "install"
+}
+
func (x *cmdAutoImport) Execute(args []string) error {
if len(args) > 0 {
return ErrExtraArgs
@@ -268,8 +326,19 @@
fmt.Fprintf(Stderr, "auto-import is disabled on classic\n")
return nil
}
+ // TODO:UC20: workaround for LP: #1860231
+ if inInstallMode() {
+ fmt.Fprintf(Stderr, "auto-import is disabled in install-mode\n")
+ return nil
+ }
+
+ devices := x.Mount
+ if len(devices) == 0 {
+ // coldplug scenario, try all removable devices
+ devices = removableBlockDevices()
+ }
- for _, path := range x.Mount {
+ for _, path := range devices {
// udev adds new /dev/loopX devices on the fly when a
// loop mount happens and there is no loop device left.
//
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_auto_import_test.go snapd-2.45.1+18.04/cmd/snap/cmd_auto_import_test.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_auto_import_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_auto_import_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -28,11 +28,14 @@
. "gopkg.in/check.v1"
+ "github.com/snapcore/snapd/boot"
snap "github.com/snapcore/snapd/cmd/snap"
"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/snaptest"
+ "github.com/snapcore/snapd/testutil"
)
func makeMockMountInfo(c *C, content string) string {
@@ -62,10 +65,10 @@
n++
case 1:
c.Check(r.Method, Equals, "POST")
- c.Check(r.URL.Path, Equals, "/v2/create-user")
+ c.Check(r.URL.Path, Equals, "/v2/users")
postData, err := ioutil.ReadAll(r.Body)
c.Assert(err, IsNil)
- c.Check(string(postData), Equals, `{"sudoer":true,"known":true}`)
+ c.Check(string(postData), Equals, `{"action":"create","sudoer":true,"known":true}`)
fmt.Fprintln(w, `{"type": "sync", "result": [{"username": "foo"}]}`)
n++
@@ -239,10 +242,10 @@
n++
case 1:
c.Check(r.Method, Equals, "POST")
- c.Check(r.URL.Path, Equals, "/v2/create-user")
+ c.Check(r.URL.Path, Equals, "/v2/users")
postData, err := ioutil.ReadAll(r.Body)
c.Assert(err, IsNil)
- c.Check(string(postData), Equals, `{"sudoer":true,"known":true}`)
+ c.Check(string(postData), Equals, `{"action":"create","sudoer":true,"known":true}`)
fmt.Fprintln(w, `{"type": "sync", "result": [{"username": "foo"}]}`)
n++
@@ -300,3 +303,163 @@
_, err = snap.Parser(snap.Client()).ParseArgs([]string{"auto-import"})
c.Assert(err, ErrorMatches, "cannot queue .*, file size too big: 656384")
}
+
+func (s *SnapSuite) TestAutoImportUnhappyInInstallMode(c *C) {
+ restore := release.MockOnClassic(false)
+ defer restore()
+
+ _, restoreLogger := logger.MockLogger()
+ defer restoreLogger()
+
+ mockProcCmdlinePath := filepath.Join(c.MkDir(), "cmdline")
+ err := ioutil.WriteFile(mockProcCmdlinePath, []byte("foo=bar snapd_recovery_mode=install snapd_recovery_system=20191118"), 0644)
+ c.Assert(err, IsNil)
+
+ restore = boot.MockProcCmdline(mockProcCmdlinePath)
+ defer restore()
+
+ _, err = snap.Parser(snap.Client()).ParseArgs([]string{"auto-import"})
+ c.Assert(err, IsNil)
+ c.Check(s.Stdout(), Equals, "")
+ c.Check(s.Stderr(), Equals, "auto-import is disabled in install-mode\n")
+}
+
+var mountStatic = []string{"mount", "-t", "ext4,vfat", "-o", "ro", "--make-private"}
+
+func (s *SnapSuite) TestAutoImportFromRemovable(c *C) {
+ restore := release.MockOnClassic(false)
+ defer restore()
+
+ _, restoreLogger := logger.MockLogger()
+ defer restoreLogger()
+
+ rootdir := c.MkDir()
+ dirs.SetRootDir(rootdir)
+
+ var umounts []string
+ restore = snap.MockSyscallUmount(func(p string, _ int) error {
+ umounts = append(umounts, p)
+ return nil
+ })
+ defer restore()
+
+ var tmpdirIdx int
+ restore = snap.MockIoutilTempDir(func(where string, p string) (string, error) {
+ c.Check(where, Equals, "")
+ tmpdirIdx++
+ return filepath.Join(rootdir, fmt.Sprintf("/tmp/%s%d", p, tmpdirIdx)), nil
+ })
+ defer restore()
+
+ mountCmd := testutil.MockCommand(c, "mount", "")
+ defer mountCmd.Restore()
+
+ snaptest.PopulateDir(rootdir, [][]string{
+ // removable without partitions
+ {"sys/block/sdremovable/removable", "1\n"},
+ // fixed disk
+ {"sys/block/sdfixed/removable", "0\n"},
+ // removable with partitions
+ {"sys/block/sdpart/removable", "1\n"},
+ {"sys/block/sdpart/sdpart1/partition", "1\n"},
+ {"sys/block/sdpart/sdpart2/partition", "0\n"},
+ {"sys/block/sdpart/sdpart3/partition", "1\n"},
+ // removable but subdevices are not partitions?
+ {"sys/block/sdother/removable", "1\n"},
+ {"sys/block/sdother/sdother1/partition", "0\n"},
+ })
+
+ // do not mock mountinfo contents, we just want to observe whether we
+ // try to mount and umount the right stuff
+
+ _, err := snap.Parser(snap.Client()).ParseArgs([]string{"auto-import"})
+ c.Assert(err, IsNil)
+ c.Check(s.Stdout(), Equals, "")
+ c.Check(s.Stderr(), Equals, "")
+ c.Check(mountCmd.Calls(), DeepEquals, [][]string{
+ append(mountStatic, "/dev/sdpart1", filepath.Join(rootdir, "/tmp/snapd-auto-import-mount-1")),
+ append(mountStatic, "/dev/sdpart3", filepath.Join(rootdir, "/tmp/snapd-auto-import-mount-2")),
+ append(mountStatic, "/dev/sdremovable", filepath.Join(rootdir, "/tmp/snapd-auto-import-mount-3")),
+ })
+ c.Check(umounts, DeepEquals, []string{
+ filepath.Join(rootdir, "/tmp/snapd-auto-import-mount-3"),
+ filepath.Join(rootdir, "/tmp/snapd-auto-import-mount-2"),
+ filepath.Join(rootdir, "/tmp/snapd-auto-import-mount-1"),
+ })
+}
+
+func (s *SnapSuite) TestAutoImportNoRemovable(c *C) {
+ restore := release.MockOnClassic(false)
+ defer restore()
+
+ rootdir := c.MkDir()
+ dirs.SetRootDir(rootdir)
+
+ var umounts []string
+ restore = snap.MockSyscallUmount(func(p string, _ int) error {
+ return fmt.Errorf("unexpected call")
+ })
+ defer restore()
+
+ mountCmd := testutil.MockCommand(c, "mount", "exit 1")
+ defer mountCmd.Restore()
+
+ snaptest.PopulateDir(rootdir, [][]string{
+ // fixed disk
+ {"sys/block/sdfixed/removable", "0\n"},
+ // removable but subdevices are not partitions?
+ {"sys/block/sdother/removable", "1\n"},
+ {"sys/block/sdother/sdother1/partition", "0\n"},
+ })
+
+ _, err := snap.Parser(snap.Client()).ParseArgs([]string{"auto-import"})
+ c.Assert(err, IsNil)
+ c.Check(s.Stdout(), Equals, "")
+ c.Check(s.Stderr(), Equals, "")
+ c.Check(mountCmd.Calls(), HasLen, 0)
+ c.Check(umounts, HasLen, 0)
+}
+
+func (s *SnapSuite) TestAutoImportFromMount(c *C) {
+ restore := release.MockOnClassic(false)
+ defer restore()
+
+ _, restoreLogger := logger.MockLogger()
+ defer restoreLogger()
+
+ mountCmd := testutil.MockCommand(c, "mount", "")
+
+ rootdir := c.MkDir()
+ dirs.SetRootDir(rootdir)
+
+ var umounts []string
+ restore = snap.MockSyscallUmount(func(p string, _ int) error {
+ c.Assert(umounts, HasLen, 0)
+ umounts = append(umounts, p)
+ return nil
+ })
+ defer restore()
+
+ var tmpdircalls int
+ restore = snap.MockIoutilTempDir(func(where string, p string) (string, error) {
+ c.Check(where, Equals, "")
+ c.Assert(tmpdircalls, Equals, 0)
+ tmpdircalls++
+ return filepath.Join(rootdir, fmt.Sprintf("/tmp/%s1", p)), nil
+ })
+ defer restore()
+
+ // do not mock mountinfo contents, we just want to observe whether we
+ // try to mount and umount the right stuff
+
+ _, err := snap.Parser(snap.Client()).ParseArgs([]string{"auto-import", "--mount", "/dev/foobar"})
+ c.Assert(err, IsNil)
+ c.Check(s.Stdout(), Equals, "")
+ c.Check(s.Stderr(), Equals, "")
+ c.Check(mountCmd.Calls(), DeepEquals, [][]string{
+ append(mountStatic, "/dev/foobar", filepath.Join(rootdir, "/tmp/snapd-auto-import-mount-1")),
+ })
+ c.Check(umounts, DeepEquals, []string{
+ filepath.Join(rootdir, "/tmp/snapd-auto-import-mount-1"),
+ })
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_blame_generated.go snapd-2.45.1+18.04/cmd/snap/cmd_blame_generated.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_blame_generated.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_blame_generated.go 1970-01-01 00:00:00.000000000 +0000
@@ -1,7 +0,0 @@
-package main
-
-// generated by mkauthors.sh; do not edit
-
-func init() {
- authors = []string{"Mark Shuttleworth", "Gustavo Niemeyer", "Sergio Schvezov", "Simon Fels", "Kyle Fazzari", "Leo Arias", "Sergio Cazzolato", "Gustavo Niemeyer", "Federico Gimenez", "Maciej Borzecki", "Jamie Strandboge", "Pawel Stolowski", "John R. Lenton", "Samuele Pedroni", "Zygmunt Krynicki", "Michael Vogt"}
-}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_blame.go snapd-2.45.1+18.04/cmd/snap/cmd_blame.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_blame.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_blame.go 1970-01-01 00:00:00.000000000 +0000
@@ -1,55 +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 main
-
-//go:generate mkauthors.sh
-
-import (
- "fmt"
- "math/rand"
-
- "github.com/jessevdk/go-flags"
-)
-
-type cmdBlame struct{}
-
-var authors []string
-
-func init() {
- cmd := addCommand("blame",
- "",
- "",
- func() flags.Commander {
- return &cmdBlame{}
- }, nil, nil)
- cmd.hidden = true
-}
-
-func (x *cmdBlame) Execute(args []string) error {
- if len(args) > 0 {
- return ErrExtraArgs
- }
- if len(authors) == 0 {
- return nil
- }
-
- fmt.Fprintf(Stdout, "It's all %s's fault.\n", authors[rand.Intn(len(authors))])
- return nil
-}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_booted.go snapd-2.45.1+18.04/cmd/snap/cmd_booted.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_booted.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_booted.go 2020-06-05 13:13:49.000000000 +0000
@@ -29,7 +29,7 @@
func init() {
cmd := addCommand("booted",
- "Internal",
+ "Deprecated (hidden)",
"The booted command is only retained for backwards compatibility.",
func() flags.Commander {
return &cmdBooted{}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_create_key.go snapd-2.45.1+18.04/cmd/snap/cmd_create_key.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_create_key.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_create_key.go 2020-06-05 13:13:49.000000000 +0000
@@ -52,6 +52,7 @@
desc: i18n.G("Name of key to create; defaults to 'default'"),
}})
cmd.hidden = true
+ cmd.completeHidden = true
}
func (x *cmdCreateKey) Execute(args []string) error {
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_create_user_test.go snapd-2.45.1+18.04/cmd/snap/cmd_create_user_test.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_create_user_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_create_user_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -35,13 +35,15 @@
switch *n {
case 0:
c.Check(r.Method, check.Equals, "POST")
- c.Check(r.URL.Path, check.Equals, "/v2/create-user")
+ c.Check(r.URL.Path, check.Equals, "/v2/users")
var gotBody map[string]interface{}
dec := json.NewDecoder(r.Body)
err := dec.Decode(&gotBody)
c.Assert(err, check.IsNil)
- wantBody := make(map[string]interface{})
+ wantBody := map[string]interface{}{
+ "action": "create",
+ }
if email != "" {
wantBody["email"] = "one@email.com"
}
@@ -56,7 +58,7 @@
if email == "" {
fmt.Fprintln(w, `{"type": "sync", "result": [{"username": "karl", "ssh-keys": ["a","b"]}]}`)
} else {
- fmt.Fprintln(w, `{"type": "sync", "result": {"username": "karl", "ssh-keys": ["a","b"]}}`)
+ fmt.Fprintln(w, `{"type": "sync", "result": [{"username": "karl", "ssh-keys": ["a","b"]}]}`)
}
default:
c.Fatalf("got too many requests (now on %d)", *n+1)
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_debug_bootvars.go snapd-2.45.1+18.04/cmd/snap/cmd_debug_bootvars.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_debug_bootvars.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_debug_bootvars.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,57 @@
+// -*- 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"
+
+ "github.com/jessevdk/go-flags"
+
+ "github.com/snapcore/snapd/boot"
+ "github.com/snapcore/snapd/i18n"
+ "github.com/snapcore/snapd/release"
+)
+
+type cmdBootvars struct {
+ UC20 bool `long:"uc20"`
+ RootDir string `long:"root-dir"`
+}
+
+func init() {
+ cmd := addDebugCommand("boot-vars",
+ "(internal) obtain the snapd boot variables",
+ "(internal) obtain the snapd boot variables",
+ func() flags.Commander {
+ return &cmdBootvars{}
+ }, map[string]string{
+ "uc20": i18n.G("Whether to use uc20 boot vars or not"),
+ "root-dir": i18n.G("Root directory to look for boot variables in"),
+ }, nil)
+ if release.OnClassic {
+ cmd.hidden = true
+ }
+}
+
+func (x *cmdBootvars) Execute(args []string) error {
+ if release.OnClassic {
+ return errors.New(`the "boot-vars" command is not available on classic systems`)
+ }
+ return boot.DumpBootVars(Stdout, x.RootDir, x.UC20)
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_debug_bootvars_test.go snapd-2.45.1+18.04/cmd/snap/cmd_debug_bootvars_test.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_debug_bootvars_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_debug_bootvars_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,62 @@
+// -*- 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 (
+ "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/bootloader"
+ "github.com/snapcore/snapd/bootloader/bootloadertest"
+ snap "github.com/snapcore/snapd/cmd/snap"
+ "github.com/snapcore/snapd/release"
+)
+
+func (s *SnapSuite) TestDebugBootvars(c *check.C) {
+ restore := release.MockOnClassic(false)
+ defer restore()
+ bloader := bootloadertest.Mock("mock", c.MkDir())
+ bootloader.Force(bloader)
+ bloader.BootVars = map[string]string{
+ "snap_mode": "try",
+ "unrelated": "thing",
+ "snap_core": "core18_1.snap",
+ "snap_try_core": "core18_2.snap",
+ "snap_kernel": "pc-kernel_3.snap",
+ "snap_try_kernel": "pc-kernel_4.snap",
+ }
+
+ rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"debug", "boot-vars"})
+ c.Assert(err, check.IsNil)
+ c.Assert(rest, check.DeepEquals, []string{})
+ c.Check(s.Stdout(), check.Equals, `snap_mode=try
+snap_core=core18_1.snap
+snap_try_core=core18_2.snap
+snap_kernel=pc-kernel_3.snap
+snap_try_kernel=pc-kernel_4.snap
+`)
+ c.Check(s.Stderr(), check.Equals, "")
+}
+
+func (s *SnapSuite) TestDebugBootvarsNotOnClassic(c *check.C) {
+ restore := release.MockOnClassic(true)
+ defer restore()
+ _, err := snap.Parser(snap.Client()).ParseArgs([]string{"debug", "boot-vars"})
+ c.Assert(err, check.ErrorMatches, `the "boot-vars" command is not available on classic systems`)
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_debug_state.go snapd-2.45.1+18.04/cmd/snap/cmd_debug_state.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_debug_state.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_debug_state.go 2020-06-05 13:13:49.000000000 +0000
@@ -41,6 +41,8 @@
TaskID string `long:"task"`
ChangeID string `long:"change"`
+ IsSeeded bool `long:"is-seeded"`
+
// 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
@@ -78,11 +80,12 @@
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"),
+ "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"),
+ "is-seeded": i18n.G("Output seeding status (true or false)"),
}), nil)
}
@@ -210,6 +213,20 @@
return nil
}
+func (c *cmdDebugState) showIsSeeded(st *state.State) error {
+ st.Lock()
+ defer st.Unlock()
+
+ var isSeeded bool
+ err := st.Get("seeded", &isSeeded)
+ if err != nil && err != state.ErrNoState {
+ return err
+ }
+ fmt.Fprintf(Stdout, "%v\n", isSeeded)
+
+ return nil
+}
+
func (c *cmdDebugState) showTask(st *state.State, taskID string) error {
st.Lock()
defer st.Unlock()
@@ -272,10 +289,17 @@
if c.TaskID != "" {
cmds = append(cmds, "--task=")
}
+ if c.IsSeeded != false {
+ cmds = append(cmds, "--is-seeded")
+ }
if len(cmds) > 1 {
return fmt.Errorf("cannot use %s and %s together", cmds[0], cmds[1])
}
+ if c.IsSeeded {
+ return c.showIsSeeded(st)
+ }
+
if c.DotOutput && c.ChangeID == "" {
return fmt.Errorf("--dot can only be used with --change=")
}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_debug_state_test.go snapd-2.45.1+18.04/cmd/snap/cmd_debug_state_test.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_debug_state_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_debug_state_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -25,7 +25,7 @@
. "gopkg.in/check.v1"
- "github.com/snapcore/snapd/cmd/snap"
+ main "github.com/snapcore/snapd/cmd/snap"
)
var stateJSON = []byte(`
@@ -34,7 +34,8 @@
"last-change-id": 2,
"data": {
- "snaps": {}
+ "snaps": {},
+ "seeded": true
},
"changes": {
"1": {
@@ -102,13 +103,13 @@
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})
+ rest, err := main.Parser(main.Client()).ParseArgs([]string{"debug", "state", "--abs-time", "--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")
+ "ID Status Spawn Ready Label Summary\n"+
+ "1 Do 0001-01-01T00:00:00Z 0001-01-01T00:00:00Z install-snap install a snap\n"+
+ "2 Done 0001-01-01T00:00:00Z 0001-01-01T00:00:00Z revert-snap revert c snap\n")
c.Check(s.Stderr(), Equals, "")
}
@@ -181,6 +182,9 @@
_, err = main.Parser(main.Client()).ParseArgs([]string{"debug", "state", "--change=1", "--task=1", stateFile})
c.Check(err, ErrorMatches, "cannot use --change= and --task= together")
+
+ _, err = main.Parser(main.Client()).ParseArgs([]string{"debug", "state", "--change=1", "--is-seeded", stateFile})
+ c.Check(err, ErrorMatches, "cannot use --change= and --is-seeded together")
}
func (s *SnapSuite) TestDebugTasks(c *C) {
@@ -188,13 +192,13 @@
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})
+ rest, err := main.Parser(main.Client()).ParseArgs([]string{"debug", "state", "--abs-time", "--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")
+ "Lanes ID Status Spawn Ready Kind Summary\n"+
+ "0 11 Done 0001-01-01T00:00:00Z 0001-01-01T00:00:00Z download-snap Download snap a from channel edge\n"+
+ "0 12 Do 0001-01-01T00:00:00Z 0001-01-01T00:00:00Z some-other-task \n")
c.Check(s.Stderr(), Equals, "")
}
@@ -202,3 +206,27 @@
_, 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")
}
+
+func (s *SnapSuite) TestDebugIsSeededHappy(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", "--is-seeded", stateFile})
+ c.Assert(err, IsNil)
+ c.Assert(rest, DeepEquals, []string{})
+ c.Check(s.Stdout(), Matches, "true\n")
+ c.Check(s.Stderr(), Equals, "")
+}
+
+func (s *SnapSuite) TestDebugIsSeededNo(c *C) {
+ dir := c.MkDir()
+ stateFile := filepath.Join(dir, "test-state.json")
+ c.Assert(ioutil.WriteFile(stateFile, []byte("{}"), 0644), IsNil)
+
+ rest, err := main.Parser(main.Client()).ParseArgs([]string{"debug", "state", "--is-seeded", stateFile})
+ c.Assert(err, IsNil)
+ c.Assert(rest, DeepEquals, []string{})
+ c.Check(s.Stdout(), Matches, "false\n")
+ c.Check(s.Stderr(), Equals, "")
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_debug_timings.go snapd-2.45.1+18.04/cmd/snap/cmd_debug_timings.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_debug_timings.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_debug_timings.go 2020-06-05 13:13:49.000000000 +0000
@@ -22,6 +22,7 @@
import (
"fmt"
"io"
+ "sort"
"strings"
"time"
@@ -92,29 +93,71 @@
printTiming(w, verbose, t.Level+1, "", "", doingTimeStr, undoingTimeStr, t.Label, t.Summary)
}
+// sortTimingsTasks sorts tasks from changeTimings by lane and ready time with special treatment of lane 0 tasks:
+// - tasks from lanes >0 are grouped by lanes and sorted by ready time.
+// - tasks from lane 0 are sorted by ready time and inserted before and after other lanes based on the min/max
+// ready times of non-zero lanes.
+// - tasks from lane 0 with ready time between non-zero lane tasks are not really expected in our system and will
+// appear after non-zero lane tasks.
+func sortTimingsTasks(timings map[string]changeTimings) []string {
+ tasks := make([]string, 0, len(timings))
+
+ var minReadyTime time.Time
+ // determine min ready time from all non-zero lane tasks
+ for taskID, taskData := range timings {
+ if taskData.Lane > 0 {
+ if minReadyTime.IsZero() {
+ minReadyTime = taskData.ReadyTime
+ }
+ if taskData.ReadyTime.Before(minReadyTime) {
+ minReadyTime = taskData.ReadyTime
+ }
+ }
+ tasks = append(tasks, taskID)
+ }
+
+ sort.Slice(tasks, func(i, j int) bool {
+ t1 := timings[tasks[i]]
+ t2 := timings[tasks[j]]
+ if t1.Lane != t2.Lane {
+ // if either t1 or t2 is from lane 0, then it comes before or after non-zero lane tasks
+ if t1.Lane == 0 {
+ return t1.ReadyTime.Before(minReadyTime)
+ }
+ if t2.Lane == 0 {
+ return !t2.ReadyTime.Before(minReadyTime)
+ }
+ // different lanes (but neither of them is 0), order by lane
+ return t1.Lane < t2.Lane
+ }
+
+ // same lane - order by ready time
+ return t1.ReadyTime.Before(t2.ReadyTime)
+ })
+
+ return tasks
+}
+
func (x *cmdChangeTimings) printChangeTimings(w io.Writer, timing *timingsData) error {
- chgid := timing.ChangeID
- chg, err := x.client.Change(chgid)
- if err != nil {
- return err
- }
+ tasks := sortTimingsTasks(timing.ChangeTimings)
- for _, t := range chg.Tasks {
- doingTime := formatDuration(timing.ChangeTimings[t.ID].DoingTime)
- if timing.ChangeTimings[t.ID].DoingTime == 0 {
+ for _, taskID := range tasks {
+ chgTiming := timing.ChangeTimings[taskID]
+ doingTime := formatDuration(timing.ChangeTimings[taskID].DoingTime)
+ if chgTiming.DoingTime == 0 {
doingTime = "-"
}
- undoingTime := formatDuration(timing.ChangeTimings[t.ID].UndoingTime)
- if timing.ChangeTimings[t.ID].UndoingTime == 0 {
+ undoingTime := formatDuration(timing.ChangeTimings[taskID].UndoingTime)
+ if chgTiming.UndoingTime == 0 {
undoingTime = "-"
}
- printTiming(w, x.Verbose, 0, t.ID, t.Status, doingTime, undoingTime, t.Kind, t.Summary)
- for _, nested := range timing.ChangeTimings[t.ID].DoingTimings {
+ printTiming(w, x.Verbose, 0, taskID, chgTiming.Status, doingTime, undoingTime, chgTiming.Kind, chgTiming.Summary)
+ for _, nested := range timing.ChangeTimings[taskID].DoingTimings {
showDoing := true
printTaskTiming(w, &nested, x.Verbose, showDoing)
}
- for _, nested := range timing.ChangeTimings[t.ID].UndoingTimings {
+ for _, nested := range timing.ChangeTimings[taskID].UndoingTimings {
showDoing := false
printTaskTiming(w, &nested, x.Verbose, showDoing)
}
@@ -125,7 +168,7 @@
func (x *cmdChangeTimings) printEnsureTimings(w io.Writer, timings []*timingsData) error {
for _, td := range timings {
- printTiming(w, x.Verbose, 0, x.EnsureTag, "", "-", "-", "", "")
+ printTiming(w, x.Verbose, 0, x.EnsureTag, "", formatDuration(td.TotalDuration), "-", "", "")
for _, t := range td.EnsureTimings {
printTiming(w, x.Verbose, t.Level+1, "", "", formatDuration(t.Duration), "-", t.Label, t.Summary)
}
@@ -140,7 +183,7 @@
func (x *cmdChangeTimings) printStartupTimings(w io.Writer, timings []*timingsData) error {
for _, td := range timings {
- printTiming(w, x.Verbose, 0, x.StartupTag, "", "-", "-", "", "")
+ printTiming(w, x.Verbose, 0, x.StartupTag, "", formatDuration(td.TotalDuration), "-", "", "")
for _, t := range td.StartupTimings {
printTiming(w, x.Verbose, t.Level+1, "", "", formatDuration(t.Duration), "-", t.Label, t.Summary)
}
@@ -148,16 +191,25 @@
return nil
}
+type changeTimings struct {
+ Status string `json:"status,omitempty"`
+ Kind string `json:"kind,omitempty"`
+ Summary string `json:"summary,omitempty"`
+ Lane int `json:"lane,omitempty"`
+ ReadyTime time.Time `json:"ready-time,omitempty"`
+ DoingTime time.Duration `json:"doing-time,omitempty"`
+ UndoingTime time.Duration `json:"undoing-time,omitempty"`
+ DoingTimings []Timing `json:"doing-timings,omitempty"`
+ UndoingTimings []Timing `json:"undoing-timings,omitempty"`
+}
+
type timingsData struct {
- ChangeID string `json:"change-id"`
- EnsureTimings []Timing `json:"ensure-timings,omitempty"`
- StartupTimings []Timing `json:"startup-timings,omitempty"`
- ChangeTimings map[string]struct {
- DoingTime time.Duration `json:"doing-time,omitempty"`
- UndoingTime time.Duration `json:"undoing-time,omitempty"`
- DoingTimings []Timing `json:"doing-timings,omitempty"`
- UndoingTimings []Timing `json:"undoing-timings,omitempty"`
- } `json:"change-timings,omitempty"`
+ ChangeID string `json:"change-id"`
+ EnsureTimings []Timing `json:"ensure-timings,omitempty"`
+ StartupTimings []Timing `json:"startup-timings,omitempty"`
+ TotalDuration time.Duration `json:"total-duration,omitempty"`
+ // ChangeTimings are indexed by task id
+ ChangeTimings map[string]changeTimings `json:"change-timings,omitempty"`
}
func (x *cmdChangeTimings) checkConflictingFlags() error {
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_debug_timings_test.go snapd-2.45.1+18.04/cmd/snap/cmd_debug_timings_test.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_debug_timings_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_debug_timings_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -23,6 +23,7 @@
"fmt"
"net/http"
"strings"
+ "time"
. "gopkg.in/check.v1"
@@ -57,25 +58,34 @@
}, {
args: "debug timings --last=install",
stdout: "ID Status Doing Undoing Summary\n" +
- "40 Doing 910ms - task bar summary\n" +
+ "40 Doing 910ms - lane 0 task bar summary\n" +
" ^ 1ms - foo summary\n" +
- " ^ 1ms - bar summary\n\n",
+ " ^ 1ms - bar summary\n" +
+ "41 Done 210ms - lane 1 task baz summary\n" +
+ "42 Done 310ms - lane 1 task boo summary\n" +
+ "43 Done 310ms - lane 0 task doh summary\n\n",
}, {
args: "debug timings 1",
stdout: "ID Status Doing Undoing Summary\n" +
- "40 Doing 910ms - task bar summary\n" +
+ "40 Doing 910ms - lane 0 task bar summary\n" +
" ^ 1ms - foo summary\n" +
- " ^ 1ms - bar summary\n\n",
+ " ^ 1ms - bar summary\n" +
+ "41 Done 210ms - lane 1 task baz summary\n" +
+ "42 Done 310ms - lane 1 task boo summary\n" +
+ "43 Done 310ms - lane 0 task doh summary\n\n",
}, {
args: "debug timings 1 --verbose",
stdout: "ID Status Doing Undoing Label Summary\n" +
- "40 Doing 910ms - bar task bar summary\n" +
+ "40 Doing 910ms - bar lane 0 task bar summary\n" +
" ^ 1ms - foo foo summary\n" +
- " ^ 1ms - bar bar summary\n\n",
+ " ^ 1ms - bar bar summary\n" +
+ "41 Done 210ms - baz lane 1 task baz summary\n" +
+ "42 Done 310ms - boo lane 1 task boo summary\n" +
+ "43 Done 310ms - doh lane 0 task doh summary\n\n",
}, {
args: "debug timings --ensure=seed",
stdout: "ID Status Doing Undoing Summary\n" +
- "seed - - \n" +
+ "seed 8ms - \n" +
" ^ 8ms - baz summary\n" +
" ^ 8ms - booze summary\n" +
"40 Doing 910ms - task bar summary\n" +
@@ -84,32 +94,43 @@
}, {
args: "debug timings --ensure=seed --all",
stdout: "ID Status Doing Undoing Summary\n" +
- "seed - - \n" +
+ "seed 8ms - \n" +
" ^ 8ms - bar summary 1\n" +
" ^ 8ms - bar summary 2\n" +
"40 Doing 910ms - task bar summary\n" +
" ^ 1ms - foo summary\n" +
+ " ^ 1ms - bar summary\n" +
+ "seed 7ms - \n" +
+ " ^ 7ms - baz summary 2\n" +
+ "60 Doing 910ms - task bar summary\n" +
+ " ^ 1ms - foo summary\n" +
" ^ 1ms - bar summary\n\n",
}, {
args: "debug timings --ensure=seed --all --verbose",
stdout: "ID Status Doing Undoing Label Summary\n" +
- "seed - - \n" +
+ "seed 8ms - \n" +
" ^ 8ms - abc bar summary 1\n" +
" ^ 8ms - abc bar summary 2\n" +
"40 Doing 910ms - bar task bar summary\n" +
" ^ 1ms - foo foo summary\n" +
+ " ^ 1ms - bar bar summary\n" +
+ "seed 7ms - \n" +
+ " ^ 7ms - ghi baz summary 2\n" +
+ "60 Doing 910ms - bar task bar summary\n" +
+ " ^ 1ms - foo foo summary\n" +
" ^ 1ms - bar bar summary\n\n",
}, {
args: "debug timings --startup=ifacemgr",
stdout: "ID Status Doing Undoing Summary\n" +
- "ifacemgr - - \n" +
+ "ifacemgr 8ms - \n" +
" ^ 8ms - baz summary\n" +
" ^ 8ms - booze summary\n\n",
}, {
args: "debug timings --startup=ifacemgr --all",
stdout: "ID Status Doing Undoing Summary\n" +
- "ifacemgr - - \n" +
+ "ifacemgr 8ms - \n" +
" ^ 8ms - baz summary\n" +
+ "ifacemgr 9ms - \n" +
" ^ 9ms - baz summary\n\n",
}}
@@ -152,22 +173,28 @@
switch {
case changeID == "1":
+ // lane 0 and lane 1 tasks, interleaved
fmt.Fprintln(w, `{"type":"sync","status-code":200,"status":"OK","result":[
{"change-id":"1", "change-timings":{
- "40":{"doing-time":910000000,
+ "41":{"doing-time":210000000, "status": "Done", "lane": 1, "ready-time": "2016-04-22T01:02:04Z", "kind": "baz", "summary": "lane 1 task baz summary"},
+ "43":{"doing-time":310000000, "status": "Done", "ready-time": "2016-04-25T01:02:04Z", "kind": "doh", "summary": "lane 0 task doh summary"},
+ "40":{"doing-time":910000000, "status": "Doing", "ready-time": "2016-04-20T00:00:00Z", "kind": "bar", "summary": "lane 0 task bar summary",
"doing-timings":[
{"label":"foo", "summary": "foo summary", "duration": 1000001},
{"level":1, "label":"bar", "summary": "bar summary", "duration": 1000002}
- ]}}}]}`)
+ ]},
+ "42":{"doing-time":310000000, "status": "Done", "lane": 1, "ready-time": "2016-04-23T01:02:04Z", "kind": "boo", "summary": "lane 1 task boo summary"}
+ }}]}`)
case ensure == "seed" && all == "false":
fmt.Fprintln(w, `{"type":"sync","status-code":200,"status":"OK","result":[
{"change-id":"1",
+ "total-duration": 8000002,
"ensure-timings": [
{"label":"baz", "summary": "baz summary", "duration": 8000001},
{"level":1, "label":"booze", "summary": "booze summary", "duration": 8000002}
],
"change-timings":{
- "40":{"doing-time":910000000,
+ "40":{"doing-time":910000000, "status": "Doing", "kind": "bar", "summary": "task bar summary",
"doing-timings":[
{"label":"foo", "summary": "foo summary", "duration": 1000001},
{"level":1, "label":"bar", "summary": "bar summary", "duration": 1000002}
@@ -175,26 +202,38 @@
case ensure == "seed" && all == "true":
fmt.Fprintln(w, `{"type":"sync","status-code":200,"status":"OK","result":[
{"change-id":"1",
+ "total-duration": 8000002,
"ensure-timings": [
{"label":"abc", "summary": "bar summary 1", "duration": 8000001},
{"label":"abc", "summary": "bar summary 2", "duration": 8000002}
],
"change-timings":{
- "40":{"doing-time":910000000,
+ "40":{"doing-time":910000000, "status": "Doing", "kind": "bar", "summary": "task bar summary",
+ "doing-timings":[
+ {"label":"foo", "summary": "foo summary", "duration": 1000001},
+ {"level":1, "label":"bar", "summary": "bar summary", "duration": 1000002}
+ ]}}},
+ {"change-id":"2",
+ "total-duration": 7000002,
+ "ensure-timings": [{"label":"ghi", "summary": "baz summary 2", "duration": 7000002}],
+ "change-timings":{
+ "60":{"doing-time":910000000, "status": "Doing", "kind": "bar", "summary": "task bar summary",
"doing-timings":[
{"label":"foo", "summary": "foo summary", "duration": 1000001},
{"level":1, "label":"bar", "summary": "bar summary", "duration": 1000002}
- ]}}}]}`)
+ ]}}}]}`)
case startup == "ifacemgr" && all == "false":
fmt.Fprintln(w, `{"type":"sync","status-code":200,"status":"OK","result":[
- {"startup-timings": [
+ {"total-duration": 8000002, "startup-timings": [
{"label":"baz", "summary": "baz summary", "duration": 8000001},
{"level":1, "label":"booze", "summary": "booze summary", "duration": 8000002}
]}]}`)
case startup == "ifacemgr" && all == "true":
fmt.Fprintln(w, `{"type":"sync","status-code":200,"status":"OK","result":[
- {"startup-timings": [
- {"label":"baz", "summary": "baz summary", "duration": 8000001},
+ {"total-duration": 8000002, "startup-timings": [
+ {"label":"baz", "summary": "baz summary", "duration": 8000001}
+ ]},
+ {"total-duration": 9000002, "startup-timings": [
{"label":"baz", "summary": "baz summary", "duration": 9000001}
]}]}`)
default:
@@ -217,22 +256,82 @@
}]}`)
return
}
-
- // request for specific change
- if r.URL.Path == "/v2/changes/1" {
- fmt.Fprintln(w, `{"type":"sync","status-code":200,"status":"OK","result":{
- "id": "1",
- "kind": "foo",
- "summary": "a",
- "status": "Doing",
- "ready": false,
- "spawn-time": "2016-04-21T01:02:03Z",
- "ready-time": "2016-04-21T01:02:04Z",
- "tasks": [{"id":"40", "kind": "bar", "summary": "task bar summary", "status": "Doing", "progress": {"done": 0, "total": 1}, "spawn-time": "2016-04-21T01:02:03Z", "ready-time": "2016-04-21T01:02:04Z"}]
- }}`)
- return
- }
-
c.Errorf("unexpected path %q", r.URL.Path)
})
}
+
+type TaskDef struct {
+ TaskID string
+ Lane int
+ ReadyTime time.Time
+}
+
+func (s *SnapSuite) TestSortTimingsTasks(c *C) {
+ mkTime := func(timeStr string) time.Time {
+ t, err := time.Parse(time.RFC3339, timeStr)
+ c.Assert(err, IsNil)
+ return t
+ }
+
+ testData := []struct {
+ ChangeTimings map[string]main.ChangeTimings
+ Expected []string
+ }{{
+ // nothing to do
+ ChangeTimings: map[string]main.ChangeTimings{},
+ Expected: []string{},
+ }, {
+ ChangeTimings: map[string]main.ChangeTimings{
+ // tasks in lane 0 only
+ "1": {ReadyTime: mkTime("2019-04-21T00:00:00Z")},
+ "2": {ReadyTime: mkTime("2019-05-21T00:00:00Z")},
+ "3": {ReadyTime: mkTime("2019-02-21T00:00:00Z")},
+ "4": {ReadyTime: mkTime("2019-03-21T00:00:00Z")},
+ "5": {ReadyTime: mkTime("2019-01-21T00:00:00Z")},
+ },
+ Expected: []string{"5", "3", "4", "1", "2"},
+ }, {
+ // task in lane 1 with a task in lane 0 before and after it
+ ChangeTimings: map[string]main.ChangeTimings{
+ "1": {Lane: 1, ReadyTime: mkTime("2019-01-21T00:00:00Z")},
+ "2": {Lane: 0, ReadyTime: mkTime("2019-01-20T00:00:00Z")},
+ "3": {Lane: 0, ReadyTime: mkTime("2019-01-22T00:00:00Z")},
+ },
+ Expected: []string{"2", "1", "3"},
+ }, {
+ // tasks in lane 1 only
+ ChangeTimings: map[string]main.ChangeTimings{
+ "1": {Lane: 1, ReadyTime: mkTime("2019-01-21T00:00:00Z")},
+ "2": {Lane: 1, ReadyTime: mkTime("2019-01-20T00:00:00Z")},
+ "3": {Lane: 1, ReadyTime: mkTime("2019-01-16T00:00:00Z")},
+ },
+ Expected: []string{"3", "2", "1"},
+ }, {
+ // tasks in lanes 0, 1, 2 with tasks from line 0 before and after lanes 1, 2
+ ChangeTimings: map[string]main.ChangeTimings{
+ "1": {Lane: 1, ReadyTime: mkTime("2019-01-21T00:00:00Z")},
+ "2": {Lane: 0, ReadyTime: mkTime("2019-01-19T00:00:00Z")},
+ "3": {Lane: 2, ReadyTime: mkTime("2019-01-20T00:00:00Z")},
+ "4": {Lane: 0, ReadyTime: mkTime("2019-01-25T00:00:00Z")},
+ "5": {Lane: 1, ReadyTime: mkTime("2019-01-20T00:00:00Z")},
+ "6": {Lane: 2, ReadyTime: mkTime("2019-01-21T00:00:00Z")},
+ "7": {Lane: 0, ReadyTime: mkTime("2019-01-18T00:00:00Z")},
+ "8": {Lane: 0, ReadyTime: mkTime("2019-01-27T00:00:00Z")},
+ },
+ Expected: []string{"7", "2", "5", "1", "3", "6", "4", "8"},
+ }, {
+ // pathological case: lane 0 tasks have ready-time between lane 1 tasks
+ ChangeTimings: map[string]main.ChangeTimings{
+ "1": {Lane: 1, ReadyTime: mkTime("2019-01-20T00:00:00Z")},
+ "2": {Lane: 1, ReadyTime: mkTime("2019-01-30T00:00:00Z")},
+ "3": {Lane: 0, ReadyTime: mkTime("2019-01-27T00:00:00Z")},
+ "4": {Lane: 0, ReadyTime: mkTime("2019-01-25T00:00:00Z")},
+ },
+ Expected: []string{"1", "2", "4", "3"},
+ }}
+
+ for _, data := range testData {
+ tasks := main.SortTimingsTasks(data.ChangeTimings)
+ c.Check(tasks, DeepEquals, data.Expected)
+ }
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_debug_validate_seed_test.go snapd-2.45.1+18.04/cmd/snap/cmd_debug_validate_seed_test.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_debug_validate_seed_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_debug_validate_seed_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2019 Canonical Ltd
+ * Copyright (C) 2019-2020 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -28,7 +28,7 @@
snap "github.com/snapcore/snapd/cmd/snap"
)
-func (s *SnapSuite) TestDebugValidateSeedRegressionLp1825437(c *C) {
+func (s *SnapSuite) TestDebugValidateCannotValidate(c *C) {
tmpf := filepath.Join(c.MkDir(), "seed.yaml")
err := ioutil.WriteFile(tmpf, []byte(`
snaps:
@@ -36,29 +36,10 @@
name: core
channel: stable
file: core_6673.snap
- -
- -
- name: gnome-foo
- channel: stable/ubuntu-19.04
- file: gtk-common-themes_1198.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: 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`)
+ c.Assert(err, ErrorMatches, `cannot validate seed:
+ - no seed assertions`)
}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_delete_key.go snapd-2.45.1+18.04/cmd/snap/cmd_delete_key.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_delete_key.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_delete_key.go 2020-06-05 13:13:49.000000000 +0000
@@ -48,6 +48,7 @@
desc: i18n.G("Name of key to delete"),
}})
cmd.hidden = true
+ cmd.completeHidden = true
}
func (x *cmdDeleteKey) Execute(args []string) error {
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_disconnect.go snapd-2.45.1+18.04/cmd/snap/cmd_disconnect.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_disconnect.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_disconnect.go 2020-06-05 13:13:49.000000000 +0000
@@ -30,6 +30,7 @@
type cmdDisconnect struct {
waitMixin
+ Forget bool `long:"forget"`
Positionals struct {
Offer disconnectSlotOrPlugSpec `required:"true"`
Use disconnectSlotSpec
@@ -49,12 +50,17 @@
Disconnects everything from the provided plug or slot.
The snap name may be omitted for the core snap.
+
+When an automatic connection is manually disconnected, its disconnected state
+is retained after a snap refresh. The --forget flag can be added to the
+disconnect command to reset this behaviour, and consequently re-enable
+an automatic reconnection after a snap refresh.
`)
func init() {
addCommand("disconnect", shortDisconnectHelp, longDisconnectHelp, func() flags.Commander {
return &cmdDisconnect{}
- }, waitDescs, []argDesc{
+ }, waitDescs.also(map[string]string{"forget": "Forget remembered state about the given connection."}), []argDesc{
// TRANSLATORS: This needs to begin with < and end with >
{name: i18n.G("
:")},
// TRANSLATORS: This needs to begin with < and end with >
@@ -80,7 +86,8 @@
return fmt.Errorf("please provide the plug or slot name to disconnect from snap %q", use.Snap)
}
- id, err := x.client.Disconnect(offer.Snap, offer.Name, use.Snap, use.Name)
+ opts := &client.DisconnectOptions{Forget: x.Forget}
+ id, err := x.client.Disconnect(offer.Snap, offer.Name, use.Snap, use.Name, opts)
if err != nil {
if client.IsInterfacesUnchangedError(err) {
fmt.Fprintf(Stdout, i18n.G("No connections to disconnect"))
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_disconnect_test.go snapd-2.45.1+18.04/cmd/snap/cmd_disconnect_test.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_disconnect_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_disconnect_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -46,9 +46,15 @@
Disconnects everything from the provided plug or slot.
The snap name may be omitted for the core snap.
+When an automatic connection is manually disconnected, its disconnected state
+is retained after a snap refresh. The --forget flag can be added to the
+disconnect command to reset this behaviour, and consequently re-enable
+an automatic reconnection after a snap refresh.
+
[disconnect command options]
--no-wait Do not wait for the operation to finish but just print
the change id.
+ --forget Forget remembered state about the given connection.
`
s.testSubCommandHelp(c, "disconnect", msg)
}
@@ -86,6 +92,43 @@
c.Assert(err, IsNil)
c.Assert(rest, DeepEquals, []string{})
c.Assert(s.Stdout(), Equals, "")
+ c.Assert(s.Stderr(), Equals, "")
+}
+
+func (s *SnapSuite) TestDisconnectWithForgetFlag(c *C) {
+ s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
+ switch r.URL.Path {
+ case "/v2/interfaces":
+ c.Check(r.Method, Equals, "POST")
+ c.Check(DecodedRequestBody(c, r), DeepEquals, map[string]interface{}{
+ "action": "disconnect",
+ "forget": true,
+ "plugs": []interface{}{
+ map[string]interface{}{
+ "snap": "consumer",
+ "plug": "plug",
+ },
+ },
+ "slots": []interface{}{
+ map[string]interface{}{
+ "snap": "producer",
+ "slot": "slot",
+ },
+ },
+ })
+ w.WriteHeader(202)
+ fmt.Fprintln(w, `{"type":"async", "status-code": 202, "change": "zzz"}`)
+ case "/v2/changes/zzz":
+ c.Check(r.Method, Equals, "GET")
+ fmt.Fprintln(w, `{"type":"sync", "result":{"ready": true, "status": "Done"}}`)
+ default:
+ c.Fatalf("unexpected path %q", r.URL.Path)
+ }
+ })
+ rest, err := Parser(Client()).ParseArgs([]string{"disconnect", "--forget", "consumer:plug", "producer:slot"})
+ c.Assert(err, IsNil)
+ c.Assert(rest, DeepEquals, []string{})
+ c.Assert(s.Stdout(), Equals, "")
c.Assert(s.Stderr(), Equals, "")
}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_download.go snapd-2.45.1+18.04/cmd/snap/cmd_download.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_download.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_download.go 2020-06-05 13:13:49.000000000 +0000
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2016-2017 Canonical Ltd
+ * Copyright (C) 2016-2020 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -97,6 +97,22 @@
return assertPath, err
}
+func printInstallHint(assertPath, snapPath string) {
+ // simplify paths
+ wd, _ := os.Getwd()
+ if p, err := filepath.Rel(wd, assertPath); err == nil {
+ assertPath = p
+ }
+ if p, err := filepath.Rel(wd, snapPath); err == nil {
+ snapPath = p
+ }
+ // add a hint what to do with the downloaded snap (LP:1676707)
+ fmt.Fprintf(Stdout, i18n.G(`Install the snap with:
+ snap ack %s
+ snap install %s
+`), assertPath, snapPath)
+}
+
func (x *cmdDownload) Execute(args []string) error {
if strings.ContainsRune(x.Basename, filepath.Separator) {
return fmt.Errorf(i18n.G("cannot specify a path in basename (use --target-dir for that)"))
@@ -143,7 +159,7 @@
// if something goes wrong, don't force it to start over again
LeavePartialOnError: true,
}
- snapPath, snapInfo, err := tsto.DownloadSnap(snapName, dlOpts)
+ snapPath, snapInfo, _, err := tsto.DownloadSnap(snapName, dlOpts)
if err != nil {
return err
}
@@ -153,20 +169,7 @@
if err != nil {
return err
}
-
- // simplify paths
- wd, _ := os.Getwd()
- if p, err := filepath.Rel(wd, assertPath); err == nil {
- assertPath = p
- }
- if p, err := filepath.Rel(wd, snapPath); err == nil {
- snapPath = p
- }
- // add a hint what to do with the downloaded snap (LP:1676707)
- fmt.Fprintf(Stdout, i18n.G(`Install the snap with:
- snap ack %s
- snap install %s
-`), assertPath, snapPath)
+ printInstallHint(assertPath, snapPath)
return nil
}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_download_test.go snapd-2.45.1+18.04/cmd/snap/cmd_download_test.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_download_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_download_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -20,6 +20,9 @@
package main_test
import (
+ "os"
+ "path/filepath"
+
"gopkg.in/check.v1"
snap "github.com/snapcore/snapd/cmd/snap"
@@ -59,3 +62,23 @@
c.Check(err, check.ErrorMatches, "cannot specify both channel and revision")
}
+
+func (s *SnapSuite) TestPrintInstalHint(c *check.C) {
+ snap.PrintInstallHint("foo_1.assert", "foo_1.snap")
+ c.Check(s.Stdout(), check.Equals, `Install the snap with:
+ snap ack foo_1.assert
+ snap install foo_1.snap
+`)
+ s.stdout.Reset()
+
+ cwd, err := os.Getwd()
+ c.Assert(err, check.IsNil)
+ as := filepath.Join(cwd, "some-dir/foo_1.assert")
+ sn := filepath.Join(cwd, "some-dir/foo_1.snap")
+ snap.PrintInstallHint(as, sn)
+ c.Check(s.Stdout(), check.Equals, `Install the snap with:
+ snap ack some-dir/foo_1.assert
+ snap install some-dir/foo_1.snap
+`)
+
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_find.go snapd-2.45.1+18.04/cmd/snap/cmd_find.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_find.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_find.go 2020-06-05 13:13:49.000000000 +0000
@@ -38,7 +38,7 @@
var shortFindHelp = i18n.G("Find packages to install")
var longFindHelp = i18n.G(`
-The find command queries the store for available packages in the stable channel.
+The find command queries the store for available packages.
With the --private flag, which requires the user to be logged-in to the store
(see 'snap help login'), it instead searches for private snaps that the user
@@ -153,7 +153,7 @@
clientMixin
Private bool `long:"private"`
Narrow bool `long:"narrow"`
- Section SectionName `long:"section" optional:"true" optional-value:"show-all-sections-please" default:"no-section-specified"`
+ Section SectionName `long:"section" optional:"true" optional-value:"show-all-sections-please" default:"no-section-specified" default-mask:"-"`
Positional struct {
Query string
} `positional-args:"yes"`
@@ -165,11 +165,11 @@
return &cmdFind{}
}, colorDescs.also(map[string]string{
// TRANSLATORS: This should not start with a lowercase letter.
- "private": i18n.G("Search private snaps"),
+ "private": i18n.G("Search private snaps."),
// TRANSLATORS: This should not start with a lowercase letter.
- "narrow": i18n.G("Only search for snaps in “stable”"),
+ "narrow": i18n.G("Only search for snaps in “stable”."),
// TRANSLATORS: This should not start with a lowercase letter.
- "section": i18n.G("Restrict the search to a given section"),
+ "section": i18n.G("Restrict the search to a given section."),
}), []argDesc{{
// TRANSLATORS: This needs to begin with < and end with >
name: i18n.G(""),
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_first_boot.go snapd-2.45.1+18.04/cmd/snap/cmd_first_boot.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_first_boot.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_first_boot.go 2020-06-05 13:13:49.000000000 +0000
@@ -29,7 +29,7 @@
func init() {
cmd := addCommand("firstboot",
- "Internal",
+ "Deprecated (hidden)",
"The firstboot command is only retained for backwards compatibility.",
func() flags.Commander {
return &cmdInternalFirstBoot{}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_get.go snapd-2.45.1+18.04/cmd/snap/cmd_get.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_get.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_get.go 2020-06-05 13:13:49.000000000 +0000
@@ -37,13 +37,12 @@
$ snap get snap-name username
frank
-If multiple option names are provided, a document is returned:
+If multiple option names are provided, the corresponding values are returned:
$ snap get snap-name username password
- {
- "username": "frank",
- "password": "..."
- }
+ Key Value
+ username frank
+ password ...
Nested values may be retrieved via a dotted path:
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_info.go snapd-2.45.1+18.04/cmd/snap/cmd_info.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_info.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_info.go 2020-06-05 13:13:49.000000000 +0000
@@ -39,6 +39,7 @@
"github.com/snapcore/snapd/i18n"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/snapfile"
"github.com/snapcore/snapd/snap/squashfs"
"github.com/snapcore/snapd/strutil"
)
@@ -112,7 +113,7 @@
}
func clientSnapFromPath(path string) (*client.Snap, error) {
- snapf, err := snap.Open(path)
+ snapf, err := snapfile.Open(path)
if err != nil {
return nil, err
}
@@ -207,11 +208,10 @@
width := termWidth - indentWidth
// establish the indent of the whole block
- idx := 0
var err error
for len(text) > width && err == nil {
// find a good place to chop the text
- idx = runesLastIndexSpace(text[:width+1])
+ idx := runesLastIndexSpace(text[:width+1])
if idx < 0 {
// there's no whitespace; just chop at line width
idx = width
@@ -401,6 +401,23 @@
wrapFlow(iw, quotedIfNeeded(iw.theSnap.Summary), "summary:\t", iw.termWidth)
}
+func (iw *infoWriter) maybePrintStoreURL() {
+ storeURL := ""
+ // XXX: store-url for local snaps comes from aux data, but that gets
+ // updated only when the snap is refreshed, be smart and poke remote
+ // snap info if available
+ switch {
+ case iw.theSnap.StoreURL != "":
+ storeURL = iw.theSnap.StoreURL
+ case iw.remoteSnap != nil && iw.remoteSnap.StoreURL != "":
+ storeURL = iw.remoteSnap.StoreURL
+ }
+ if storeURL == "" {
+ return
+ }
+ fmt.Fprintf(iw, "store-url:\t%s\n", storeURL)
+}
+
func (iw *infoWriter) maybePrintPublisher() {
if iw.diskSnap != nil {
// snaps read from disk won't have a publisher
@@ -639,9 +656,6 @@
for _, risk := range channelRisks {
chName := fmt.Sprintf("%s/%s", tr, risk)
ch, ok := remote.Channels[chName]
- if tr == "latest" {
- chName = risk
- }
if ok {
chInfos.addOpenChannel(chName, ch.Version, ch.Revision, ch.ReleasedAt, ch.Size, NotesFromChannelSnapInfo(ch))
trackHasOpenChannel = true
@@ -717,6 +731,7 @@
iw.printSummary()
iw.maybePrintHealth()
iw.maybePrintPublisher()
+ iw.maybePrintStoreURL()
iw.maybePrintStandaloneVersion()
iw.maybePrintBuildDate()
iw.maybePrintContact()
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_info_test.go snapd-2.45.1+18.04/cmd/snap/cmd_info_test.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_info_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_info_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -273,7 +273,7 @@
c.Assert(ioutil.WriteFile(arbfile, nil, 0600), check.IsNil)
filename := filepath.Join(c.MkDir(), "foo.snap")
diskSnap := squashfs.New(filename)
- c.Assert(diskSnap.Build(dir, "app"), check.IsNil)
+ c.Assert(diskSnap.Build(dir, nil), check.IsNil)
buildDate := diskSnap.BuildDate().Format(time.Kitchen)
// no disk snap -> no build date
@@ -305,7 +305,7 @@
dir := c.MkDir()
filename := filepath.Join(c.MkDir(), "foo.snap")
diskSnap := squashfs.New(filename)
- c.Assert(diskSnap.Build(dir, "app"), check.IsNil)
+ c.Assert(diskSnap.Build(dir, nil), check.IsNil)
iw := snap.NewInfoWriter(&buf)
snap.SetVerbose(iw, true)
@@ -337,9 +337,10 @@
iw := snap.NewInfoWriter(&buf)
for contact, expected := range map[string]string{
- "": "",
"mailto:joe@example.com": "contact:\tjoe@example.com\n",
+ // gofmt 1.9 being silly
"foo": "contact:\tfoo\n",
+ "": "",
} {
buf.Reset()
snap.SetupDiskSnap(iw, "", &client.Snap{Contact: contact})
@@ -540,6 +541,7 @@
"revision": "1",
"status": "available",
"summary": "The GNU Hello snap",
+ "store-url": "https://snapcraft.io/hello",
"type": "app",
"version": "2.10",
"license": "MIT",
@@ -786,6 +788,7 @@
c.Check(s.Stdout(), check.Equals, `name: hello
summary: The GNU Hello snap
publisher: Canonical*
+store-url: https://snapcraft.io/hello
license: unset
description: |
GNU hello prints a friendly greeting. This is part of the snapcraft tour at
@@ -811,6 +814,7 @@
c.Check(s.Stdout(), check.Equals, `name: hello
summary: The GNU Hello snap
publisher: Canonical*
+store-url: https://snapcraft.io/hello
license: unset
description: |
GNU hello prints a friendly greeting. This is part of the snapcraft tour at
@@ -836,6 +840,7 @@
c.Check(s.Stdout(), check.Equals, `name: hello
summary: The GNU Hello snap
publisher: Canonical✓
+store-url: https://snapcraft.io/hello
license: unset
description: |
GNU hello prints a friendly greeting. This is part of the snapcraft tour at
@@ -1120,6 +1125,86 @@
c.Check(s.Stdout(), check.Equals, `name: hello_foo
summary: The GNU Hello snap
publisher: Canonical*
+store-url: https://snapcraft.io/hello
+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, "")
+}
+
+const mockInfoJSONWithStoreURL = `
+{
+ "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",
+ "private": false,
+ "revision": "100",
+ "status": "available",
+ "store-url": "https://snapcraft.io/hello",
+ "summary": "The GNU Hello snap",
+ "type": "app",
+ "version": "2.10",
+ "license": "",
+ "tracking-channel": "beta"
+ }
+}
+`
+
+func (s *infoSuite) TestInfoStoreURL(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")
+ fmt.Fprintln(w, mockInfoJSONWithStoreURL)
+ 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"})
+ 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
+summary: The GNU Hello snap
+publisher: Canonical*
+store-url: https://snapcraft.io/hello
license: unset
description: |
GNU hello prints a friendly greeting. This is part of the snapcraft tour at
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_keys.go snapd-2.45.1+18.04/cmd/snap/cmd_keys.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_keys.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_keys.go 2020-06-05 13:13:49.000000000 +0000
@@ -47,6 +47,7 @@
"json": i18n.G("Output results in JSON format"),
}, nil)
cmd.hidden = true
+ cmd.completeHidden = true
}
// Key represents a key that can be used for signing assertions.
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_known.go snapd-2.45.1+18.04/cmd/snap/cmd_known.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_known.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_known.go 2020-06-05 13:13:49.000000000 +0000
@@ -23,12 +23,14 @@
"fmt"
"strings"
+ "github.com/jessevdk/go-flags"
+ "golang.org/x/xerrors"
+
"github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/client"
"github.com/snapcore/snapd/i18n"
"github.com/snapcore/snapd/overlord/auth"
"github.com/snapcore/snapd/store"
-
- "github.com/jessevdk/go-flags"
)
type cmdKnown struct {
@@ -40,6 +42,7 @@
} `positional-args:"true" required:"true"`
Remote bool `long:"remote"`
+ Direct bool `long:"direct"`
}
var shortKnownHelp = i18n.G("Show known assertions of the provided type")
@@ -52,7 +55,12 @@
func init() {
addCommand("known", shortKnownHelp, longKnownHelp, func() flags.Commander {
return &cmdKnown{}
- }, nil, []argDesc{
+ }, map[string]string{
+ // TRANSLATORS: This should not start with a lowercase letter.
+ "remote": i18n.G("Query the store for the assertion, via snapd if possible"),
+ // TRANSLATORS: This should not start with a lowercase letter.
+ "direct": i18n.G("Query the store for the assertion, without attempting to go via snapd"),
+ }, []argDesc{
{
// TRANSLATORS: This needs to begin with < and end with >
name: i18n.G(""),
@@ -110,10 +118,21 @@
var assertions []asserts.Assertion
var err error
- if x.Remote {
+ switch {
+ case x.Remote && !x.Direct:
+ // --remote will query snapd
+ assertions, err = x.client.Known(string(x.KnownOptions.AssertTypeName), headers, &client.KnownOptions{Remote: true})
+ // if snapd is unavailable automatically fallback
+ var connErr client.ConnectionError
+ if xerrors.As(err, &connErr) {
+ assertions, err = downloadAssertion(string(x.KnownOptions.AssertTypeName), headers)
+ }
+ case x.Direct:
+ // --direct implies remote
assertions, err = downloadAssertion(string(x.KnownOptions.AssertTypeName), headers)
- } else {
- assertions, err = x.client.Known(string(x.KnownOptions.AssertTypeName), headers)
+ default:
+ // default is to look only local
+ assertions, err = x.client.Known(string(x.KnownOptions.AssertTypeName), headers, nil)
}
if err != nil {
return err
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_known_test.go snapd-2.45.1+18.04/cmd/snap/cmd_known_test.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_known_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_known_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -28,6 +28,7 @@
"github.com/jessevdk/go-flags"
"gopkg.in/check.v1"
+ "github.com/snapcore/snapd/client"
"github.com/snapcore/snapd/store"
snap "github.com/snapcore/snapd/cmd/snap"
@@ -49,7 +50,76 @@
AcLorsomethingthatlooksvaguelylikeasignature==
`
-func (s *SnapSuite) TestKnownRemote(c *check.C) {
+func (s *SnapSuite) TestKnownViaSnapd(c *check.C) {
+ n := 0
+ expectedQuery := url.Values{
+ "series": []string{"16"},
+ "brand-id": []string{"canonical"},
+ "model": []string{"pi99"},
+ }
+
+ s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
+ switch n {
+ case 0:
+ c.Check(r.URL.Path, check.Equals, "/v2/assertions/model")
+ c.Check(r.URL.Query(), check.DeepEquals, expectedQuery)
+ w.Header().Set("X-Ubuntu-Assertions-Count", "1")
+ fmt.Fprintln(w, mockModelAssertion)
+ default:
+ c.Fatalf("expected to get 1 requests, now on %d", n+1)
+ }
+ n++
+ })
+
+ // first run "normal"
+ rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"known", "model", "series=16", "brand-id=canonical", "model=pi99"})
+ c.Assert(err, check.IsNil)
+ c.Assert(rest, check.DeepEquals, []string{})
+ c.Check(s.Stdout(), check.Equals, mockModelAssertion)
+ c.Check(s.Stderr(), check.Equals, "")
+ c.Check(n, check.Equals, 1)
+
+ // then with "--remote"
+ n = 0
+ s.stdout.Reset()
+ expectedQuery["remote"] = []string{"true"}
+ rest, err = snap.Parser(snap.Client()).ParseArgs([]string{"known", "--remote", "model", "series=16", "brand-id=canonical", "model=pi99"})
+ c.Assert(err, check.IsNil)
+ c.Assert(rest, check.DeepEquals, []string{})
+ c.Check(s.Stdout(), check.Equals, mockModelAssertion)
+ c.Check(s.Stderr(), check.Equals, "")
+ c.Check(n, check.Equals, 1)
+}
+
+func (s *SnapSuite) TestKnownRemoteViaSnapd(c *check.C) {
+ n := 0
+ s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
+ switch n {
+ case 0:
+ c.Check(r.URL.Path, check.Equals, "/v2/assertions/model")
+ c.Check(r.URL.Query(), check.DeepEquals, url.Values{
+ "series": []string{"16"},
+ "brand-id": []string{"canonical"},
+ "model": []string{"pi99"},
+ "remote": []string{"true"},
+ })
+ w.Header().Set("X-Ubuntu-Assertions-Count", "1")
+ fmt.Fprintln(w, mockModelAssertion)
+ default:
+ c.Fatalf("expected to get 1 requests, now on %d", n+1)
+ }
+ n++
+ })
+
+ rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"known", "--remote", "model", "series=16", "brand-id=canonical", "model=pi99"})
+ c.Assert(err, check.IsNil)
+ c.Assert(rest, check.DeepEquals, []string{})
+ c.Check(s.Stdout(), check.Equals, mockModelAssertion)
+ c.Check(s.Stderr(), check.Equals, "")
+ c.Check(n, check.Equals, 1)
+}
+
+func (s *SnapSuite) TestKnownRemoteDirect(c *check.C) {
var server *httptest.Server
restorer := snap.MockStoreNew(func(cfg *store.Config, stoCtx store.DeviceAndAuthContext) *store.Store {
@@ -77,7 +147,58 @@
n++
}))
- rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"known", "--remote", "model", "series=16", "brand-id=canonical", "model=pi99"})
+ // first test "--remote --direct"
+ rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"known", "--remote", "--direct", "model", "series=16", "brand-id=canonical", "model=pi99"})
+ c.Assert(err, check.IsNil)
+ c.Assert(rest, check.DeepEquals, []string{})
+ c.Check(s.Stdout(), check.Equals, mockModelAssertion)
+ c.Check(s.Stderr(), check.Equals, "")
+ c.Check(n, check.Equals, 1)
+
+ // "--direct" behave the same as "--remote --direct"
+ s.stdout.Reset()
+ n = 0
+ rest, err = snap.Parser(snap.Client()).ParseArgs([]string{"known", "--direct", "model", "series=16", "brand-id=canonical", "model=pi99"})
+ c.Assert(err, check.IsNil)
+ c.Assert(rest, check.DeepEquals, []string{})
+ c.Check(s.Stdout(), check.Equals, mockModelAssertion)
+ c.Check(s.Stderr(), check.Equals, "")
+ c.Check(n, check.Equals, 1)
+}
+
+func (s *SnapSuite) TestKnownRemoteAutoFallback(c *check.C) {
+ var server *httptest.Server
+
+ restorer := snap.MockStoreNew(func(cfg *store.Config, stoCtx store.DeviceAndAuthContext) *store.Store {
+ if cfg == nil {
+ cfg = store.DefaultConfig()
+ }
+ serverURL, _ := url.Parse(server.URL)
+ cfg.AssertionsBaseURL = serverURL
+ return store.New(cfg, stoCtx)
+ })
+ defer restorer()
+
+ n := 0
+ server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ c.Assert(r.URL.Path, check.Matches, ".*/assertions/.*") // sanity check request
+ switch n {
+ case 0:
+ c.Check(r.Method, check.Equals, "GET")
+ c.Check(r.URL.Path, check.Equals, "/api/v1/snaps/assertions/model/16/canonical/pi99")
+ fmt.Fprintln(w, mockModelAssertion)
+ default:
+ c.Fatalf("expected to get 1 requests, now on %d", n+1)
+ }
+ n++
+ }))
+
+ cli := snap.Client()
+ cli.Hijack(func(*http.Request) (*http.Response, error) {
+ return nil, client.ConnectionError{Err: fmt.Errorf("no snapd")}
+ })
+
+ rest, err := snap.Parser(cli).ParseArgs([]string{"known", "--remote", "model", "series=16", "brand-id=canonical", "model=pi99"})
c.Assert(err, check.IsNil)
c.Assert(rest, check.DeepEquals, []string{})
c.Check(s.Stdout(), check.Equals, mockModelAssertion)
@@ -85,7 +206,7 @@
}
func (s *SnapSuite) TestKnownRemoteMissingPrimaryKey(c *check.C) {
- _, err := snap.Parser(snap.Client()).ParseArgs([]string{"known", "--remote", "model", "series=16", "brand-id=canonical"})
+ _, err := snap.Parser(snap.Client()).ParseArgs([]string{"known", "--remote", "--direct", "model", "series=16", "brand-id=canonical"})
c.Assert(err, check.ErrorMatches, `cannot query remote assertion: must provide primary key: model`)
}
@@ -105,4 +226,5 @@
})
c.Check(snap.AssertTypeNameCompletion("v"), check.DeepEquals, []flags.Completion{{Item: "validation"}})
+ c.Check(n, check.Equals, 1)
}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_list.go snapd-2.45.1+18.04/cmd/snap/cmd_list.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_list.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_list.go 2020-06-05 13:13:49.000000000 +0000
@@ -30,7 +30,6 @@
"github.com/snapcore/snapd/client"
"github.com/snapcore/snapd/i18n"
- "github.com/snapcore/snapd/strutil"
)
var shortListHelp = i18n.G("List installed snaps")
@@ -69,38 +68,20 @@
// snapd will give us and we want
// "" (local snap) "-"
-// risk risk
-// track track (not yet returned by snapd)
-// track/stable track
+// latest/risk latest/risk
// track/risk track/risk
-// risk/branch risk/…
// track/risk/branch track/risk/…
+// anything else SISO
func fmtChannel(ch string) string {
if ch == "" {
// "" -> "-" (local snap)
return "-"
}
- idx := strings.IndexByte(ch, '/')
- if idx < 0 {
- // risk -> risk
+ if strings.Count(ch, "/") != 2 {
return ch
}
- first, rest := ch[:idx], ch[idx+1:]
- if rest == "stable" && first != "" {
- // track/stable -> track
- return first
- }
- if idx2 := strings.IndexByte(rest, '/'); idx2 >= 0 {
- // track/risk/branch -> track/risk/…
- return ch[:idx2+idx+2] + "…"
- }
- // so it's foo/bar -> either risk/branch, or track/risk.
- if strutil.ListContains(channelRisks, first) {
- // risk/branch -> risk/…
- return first + "/…"
- }
- // track/risk -> track/risk
- return ch
+ idx := strings.LastIndexByte(ch, '/')
+ return ch[:idx+1] + "…"
}
func (x *cmdList) Execute(args []string) error {
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_list_test.go snapd-2.45.1+18.04/cmd/snap/cmd_list_test.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_list_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_list_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -227,15 +227,11 @@
}
for _, t := range []tableT{
{"", "-"},
- {"stable", "stable"},
- {"edge", "edge"},
- {"foo/stable", "foo"},
+ {"latest/stable", "latest/stable"},
+ {"foo/stable", "foo/stable"},
{"foo/edge", "foo/edge"},
- {"foo", "foo"},
{"foo/stable/bar", "foo/stable/…"},
{"foo/edge/bar", "foo/edge/…"},
- {"stable/bar", "stable/…"},
- {"edge/bar", "edge/…"},
} {
c.Check(snap.FormatChannel(t.channel), check.Equals, t.expected, check.Commentf(t.channel))
}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_model.go snapd-2.45.1+18.04/cmd/snap/cmd_model.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_model.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_model.go 2020-06-05 13:13:49.000000000 +0000
@@ -76,7 +76,7 @@
)
type cmdModel struct {
- waitMixin
+ clientMixin
timeMixin
colorMixin
@@ -91,7 +91,7 @@
longModelHelp,
func() flags.Commander {
return &cmdModel{}
- }, colorDescs.also(timeDescs).also(waitDescs).also(map[string]string{
+ }, colorDescs.also(timeDescs).also(map[string]string{
"assertion": i18n.G("Print the raw assertion."),
"verbose": i18n.G("Print all specific assertion fields."),
"serial": i18n.G(
@@ -198,9 +198,11 @@
serial = serialAssertion.HeaderString("serial")
}
- // handle brand/brand-id (the former is only on `snap model` w/o opts)
+ // handle brand/brand-id and model/model + display-name differently on just
+ // `snap model` w/o opts
if x.Serial || x.Verbose {
fmt.Fprintf(w, "brand-id:\t%s\n", brandIDHeader)
+ fmt.Fprintf(w, "model:\t%s\n", modelHeader)
} else {
// for the model command (not --serial) we want to show a publisher
// style display of "brand" instead of just "brand-id"
@@ -211,12 +213,7 @@
// 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 != "" {
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_model_test.go snapd-2.45.1+18.04/cmd/snap/cmd_model_test.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_model_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_model_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -329,6 +329,34 @@
c.Check(s.Stderr(), check.Equals, "")
}
+func (s *SnapSuite) TestModelVerboseDisplayName(c *check.C) {
+ s.RedirectClientToTestServer(
+ makeHappyTestServerHandler(
+ c,
+ simpleHappyResponder(happyModelWithDisplayNameAssertionResponse),
+ 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
+display-name: Model Name
+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(
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_pack.go snapd-2.45.1+18.04/cmd/snap/cmd_pack.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_pack.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_pack.go 2020-06-05 13:13:49.000000000 +0000
@@ -28,11 +28,15 @@
"github.com/snapcore/snapd/i18n"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/pack"
+
+ // for SanitizePlugsSlots
+ "github.com/snapcore/snapd/interfaces/builtin"
)
type packCmd struct {
CheckSkeleton bool `long:"check-skeleton"`
Filename string `long:"filename"`
+ Compression string `long:"compression" hidden:"yes"`
Positional struct {
SnapDir string `positional-arg-name:""`
TargetDir string `positional-arg-name:""`
@@ -69,6 +73,8 @@
"check-skeleton": i18n.G("Validate snap-dir metadata only"),
// TRANSLATORS: This should not start with a lowercase letter.
"filename": i18n.G("Output to this filename"),
+ // TRANSLATORS: This should not start with a lowercase letter.
+ "compression": i18n.G("Compression to use (e.g. xz)"),
}, nil)
cmd.extra = func(cmd *flags.Command) {
// TRANSLATORS: this describes the default filename for a snap, e.g. core_16-2.35.2_amd64.snap
@@ -77,6 +83,10 @@
}
func (x *packCmd) Execute([]string) error {
+ // plug/slot sanitization is disabled (no-op) by default at the package level for "snap" command,
+ // for "snap pack" however we want real validation.
+ snap.SanitizePlugsSlots = builtin.SanitizePlugsSlots
+
if x.Positional.TargetDir != "" && x.Filename != "" && filepath.IsAbs(x.Filename) {
return fmt.Errorf(i18n.G("you can't specify an absolute filename while also specifying target dir."))
}
@@ -89,14 +99,18 @@
}
if x.CheckSkeleton {
- err := pack.CheckSkeleton(x.Positional.SnapDir)
+ err := pack.CheckSkeleton(Stderr, x.Positional.SnapDir)
if err == snap.ErrMissingPaths {
return nil
}
return err
}
- snapPath, err := pack.Snap(x.Positional.SnapDir, x.Positional.TargetDir, x.Filename)
+ snapPath, err := pack.Snap(x.Positional.SnapDir, &pack.Options{
+ TargetDir: x.Positional.TargetDir,
+ SnapName: x.Filename,
+ Compression: x.Compression,
+ })
if err != nil {
// TRANSLATORS: the %q is the snap-dir (the first positional
// argument to the command); the %v is an error
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_pack_test.go snapd-2.45.1+18.04/cmd/snap/cmd_pack_test.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_pack_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_pack_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -1,6 +1,7 @@
package main_test
import (
+ "fmt"
"io/ioutil"
"os"
"path/filepath"
@@ -71,6 +72,20 @@
c.Assert(err, check.ErrorMatches, `cannot validate snap "foo": application ("bar" common-id "org.foo.foo" must be unique, already used by application "foo"|"foo" common-id "org.foo.foo" must be unique, already used by application "bar")`)
}
+func (s *SnapSuite) TestPackCheckSkeletonWonkyInterfaces(c *check.C) {
+ snapYaml := `
+name: foo
+version: 1.0.1
+slots:
+ kale:
+`
+ snapDir := makeSnapDirForPack(c, snapYaml)
+
+ _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"pack", "--check-skeleton", snapDir})
+ c.Assert(err, check.IsNil)
+ c.Check(s.stderr.String(), check.Equals, "snap \"foo\" has bad plugs or slots: kale (unknown interface \"kale\")\n")
+}
+
func (s *SnapSuite) TestPackPacksFailsForMissingPaths(c *check.C) {
_, r := logger.MockLogger()
defer r()
@@ -101,3 +116,27 @@
c.Assert(err, check.IsNil)
c.Assert(matches, check.HasLen, 1)
}
+
+func (s *SnapSuite) TestPackPacksASnapWithCompressionHappy(c *check.C) {
+ snapDir := makeSnapDirForPack(c, "name: hello\nversion: 1.0")
+
+ for _, comp := range []string{"xz", "lzo"} {
+ _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"pack", "--compression", comp, snapDir, snapDir})
+ c.Assert(err, check.IsNil)
+
+ matches, err := filepath.Glob(snapDir + "/hello*.snap")
+ c.Assert(err, check.IsNil)
+ c.Assert(matches, check.HasLen, 1)
+ err = os.Remove(matches[0])
+ c.Assert(err, check.IsNil)
+ }
+}
+
+func (s *SnapSuite) TestPackPacksASnapWithCompressionUnhappy(c *check.C) {
+ snapDir := makeSnapDirForPack(c, "name: hello\nversion: 1.0")
+
+ for _, comp := range []string{"gzip", "zstd", "silly"} {
+ _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"pack", "--compression", comp, snapDir, snapDir})
+ c.Assert(err, check.ErrorMatches, fmt.Sprintf(`cannot pack "/.*": cannot use compression %q`, comp))
+ }
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_prepare_image.go snapd-2.45.1+18.04/cmd/snap/cmd_prepare_image.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_prepare_image.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_prepare_image.go 2020-06-05 13:13:49.000000000 +0000
@@ -20,7 +20,6 @@
package main
import (
- "path/filepath"
"strings"
"github.com/jessevdk/go-flags"
@@ -35,10 +34,10 @@
Positional struct {
ModelAssertionFn string
- Rootdir string
+ TargetDir string
} `positional-args:"yes" required:"yes"`
- Channel string `long:"channel" default:"stable"`
+ Channel string `long:"channel"`
// TODO: introduce SnapWithChannel?
Snaps []string `long:"snap" value-name:"[=]"`
ExtraSnaps []string `long:"extra-snaps" hidden:"yes"` // DEPRECATED
@@ -75,7 +74,7 @@
desc: i18n.G("The model assertion name"),
}, {
// TRANSLATORS: This needs to begin with < and end with >
- name: i18n.G(""),
+ name: i18n.G(""),
// TRANSLATORS: This should not start with a lowercase letter.
desc: i18n.G("The target directory"),
},
@@ -111,13 +110,8 @@
opts.SnapChannels = snapChannels
}
- if x.Classic {
- opts.Classic = true
- opts.RootDir = x.Positional.Rootdir
- } else {
- opts.RootDir = filepath.Join(x.Positional.Rootdir, "image")
- opts.GadgetUnpackDir = filepath.Join(x.Positional.Rootdir, "gadget")
- }
+ opts.PrepareDir = x.Positional.TargetDir
+ opts.Classic = x.Classic
return imagePrepare(opts)
}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_prepare_image_test.go snapd-2.45.1+18.04/cmd/snap/cmd_prepare_image_test.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_prepare_image_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_prepare_image_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -41,15 +41,13 @@
r := snap.MockImagePrepare(prep)
defer r()
- rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"prepare-image", "model", "root-dir"})
+ rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"prepare-image", "model", "prepare-dir"})
c.Assert(err, IsNil)
c.Assert(rest, DeepEquals, []string{})
c.Check(opts, DeepEquals, &image.Options{
- ModelFile: "model",
- Channel: "stable",
- RootDir: "root-dir/image",
- GadgetUnpackDir: "root-dir/gadget",
+ ModelFile: "model",
+ PrepareDir: "prepare-dir",
})
}
@@ -62,15 +60,14 @@
r := snap.MockImagePrepare(prep)
defer r()
- rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"prepare-image", "--classic", "model", "root-dir"})
+ rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"prepare-image", "--classic", "model", "prepare-dir"})
c.Assert(err, IsNil)
c.Assert(rest, DeepEquals, []string{})
c.Check(opts, DeepEquals, &image.Options{
- Classic: true,
- ModelFile: "model",
- Channel: "stable",
- RootDir: "root-dir",
+ Classic: true,
+ ModelFile: "model",
+ PrepareDir: "prepare-dir",
})
}
@@ -83,7 +80,7 @@
r := snap.MockImagePrepare(prep)
defer r()
- rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"prepare-image", "--classic", "--arch", "i386", "model", "root-dir"})
+ rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"prepare-image", "--classic", "--arch", "i386", "model", "prepare-dir"})
c.Assert(err, IsNil)
c.Assert(rest, DeepEquals, []string{})
@@ -91,8 +88,7 @@
Classic: true,
Architecture: "i386",
ModelFile: "model",
- Channel: "stable",
- RootDir: "root-dir",
+ PrepareDir: "prepare-dir",
})
}
@@ -105,16 +101,15 @@
r := snap.MockImagePrepare(prep)
defer r()
- rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"prepare-image", "model", "root-dir", "--snap", "foo", "--snap", "bar=t/edge", "--snap", "local.snap", "--extra-snaps", "local2.snap", "--extra-snaps", "store-snap"})
+ rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"prepare-image", "model", "prepare-dir", "--channel", "candidate", "--snap", "foo", "--snap", "bar=t/edge", "--snap", "local.snap", "--extra-snaps", "local2.snap", "--extra-snaps", "store-snap"})
c.Assert(err, IsNil)
c.Assert(rest, DeepEquals, []string{})
c.Check(opts, DeepEquals, &image.Options{
- ModelFile: "model",
- Channel: "stable",
- RootDir: "root-dir/image",
- GadgetUnpackDir: "root-dir/gadget",
- Snaps: []string{"foo", "bar", "local.snap", "local2.snap", "store-snap"},
- SnapChannels: map[string]string{"bar": "t/edge"},
+ ModelFile: "model",
+ Channel: "candidate",
+ PrepareDir: "prepare-dir",
+ Snaps: []string{"foo", "bar", "local.snap", "local2.snap", "store-snap"},
+ SnapChannels: map[string]string{"bar": "t/edge"},
})
}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_remove_user.go snapd-2.45.1+18.04/cmd/snap/cmd_remove_user.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_remove_user.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_remove_user.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,75 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package main
+
+import (
+ "fmt"
+
+ "github.com/snapcore/snapd/client"
+ "github.com/snapcore/snapd/i18n"
+
+ "github.com/jessevdk/go-flags"
+)
+
+var shortRemoveUserHelp = i18n.G("Remove a local system user")
+var longRemoveUserHelp = i18n.G(`
+The remove-user command removes a local system user.
+`)
+
+type cmdRemoveUser struct {
+ clientMixin
+ Positional struct {
+ Username string
+ } `positional-args:"yes"`
+}
+
+func init() {
+ cmd := addCommand("remove-user", shortRemoveUserHelp, longRemoveUserHelp, func() flags.Commander { return &cmdRemoveUser{} },
+ map[string]string{}, []argDesc{{
+ // TRANSLATORS: This is a noun and it needs to begin with < and end with >
+ name: i18n.G(""),
+ // TRANSLATORS: This should not start with a lowercase letter
+ desc: i18n.G("The username to remove"),
+ }})
+ cmd.hidden = true
+}
+
+func (x *cmdRemoveUser) Execute(args []string) error {
+ if len(args) > 0 {
+ return ErrExtraArgs
+ }
+
+ username := x.Positional.Username
+
+ options := client.RemoveUserOptions{
+ Username: username,
+ }
+
+ removed, err := x.client.RemoveUser(&options)
+ if err != nil {
+ return err
+ }
+ if len(removed) != 1 {
+ return fmt.Errorf("internal error: RemoveUser returned unexpected number of removed users: %v", len(removed))
+ }
+ fmt.Fprintf(Stdout, i18n.G("removed user %q\n"), removed[0].Username)
+
+ return nil
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_remove_user_test.go snapd-2.45.1+18.04/cmd/snap/cmd_remove_user_test.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_remove_user_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_remove_user_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,109 @@
+// -*- 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 (
+ "encoding/json"
+ "fmt"
+ "net/http"
+
+ "gopkg.in/check.v1"
+
+ snap "github.com/snapcore/snapd/cmd/snap"
+)
+
+var removeUserJsonFmtReplyHappy = `{
+ "type": "sync",
+ "result": {
+ "removed": [{"username": %q}]
+ }
+}`
+
+var removeUserJsonReplyTooMany = `{
+ "type": "sync",
+ "result": {
+ "removed": [{"username": "too"}, {"username": "many"}]
+ }
+}`
+
+var removeUserJsonReplyTooFew = `{
+ "type": "sync",
+ "result": {
+ "removed": []
+ }
+}`
+
+func makeRemoveUserChecker(c *check.C, n *int, username string, fmtJsonReply string) func(w http.ResponseWriter, r *http.Request) {
+ f := func(w http.ResponseWriter, r *http.Request) {
+ switch *n {
+ case 0:
+ c.Check(r.Method, check.Equals, "POST")
+ c.Check(r.URL.Path, check.Equals, "/v2/users")
+ var gotBody map[string]interface{}
+ dec := json.NewDecoder(r.Body)
+ err := dec.Decode(&gotBody)
+ c.Assert(err, check.IsNil)
+
+ wantBody := map[string]interface{}{
+ "username": username,
+ "action": "remove",
+ }
+ c.Check(gotBody, check.DeepEquals, wantBody)
+
+ fmt.Fprint(w, fmtJsonReply)
+ default:
+ c.Fatalf("got too many requests (now on %d)", *n+1)
+ }
+
+ *n++
+ }
+ return f
+}
+
+func (s *SnapSuite) TestRemoveUser(c *check.C) {
+ n := 0
+ username := "karl"
+ s.RedirectClientToTestServer(makeRemoveUserChecker(c, &n, username, fmt.Sprintf(removeUserJsonFmtReplyHappy, username)))
+
+ rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"remove-user", "karl"})
+ c.Assert(err, check.IsNil)
+ c.Check(rest, check.DeepEquals, []string{})
+ c.Check(n, check.Equals, 1)
+ c.Assert(s.Stdout(), check.Equals, fmt.Sprintf("removed user %q\n", username))
+ c.Assert(s.Stderr(), check.Equals, "")
+}
+
+func (s *SnapSuite) TestRemoveUserUnhappyTooMany(c *check.C) {
+ n := 0
+ s.RedirectClientToTestServer(makeRemoveUserChecker(c, &n, "karl", removeUserJsonReplyTooMany))
+
+ _, err := snap.Parser(snap.Client()).ParseArgs([]string{"remove-user", "karl"})
+ c.Assert(err, check.ErrorMatches, `internal error: RemoveUser returned unexpected number of removed users: 2`)
+ c.Check(n, check.Equals, 1)
+}
+
+func (s *SnapSuite) TestRemoveUserUnhappyTooFew(c *check.C) {
+ n := 0
+ s.RedirectClientToTestServer(makeRemoveUserChecker(c, &n, "karl", removeUserJsonReplyTooFew))
+
+ _, err := snap.Parser(snap.Client()).ParseArgs([]string{"remove-user", "karl"})
+ c.Assert(err, check.ErrorMatches, `internal error: RemoveUser returned unexpected number of removed users: 0`)
+ c.Check(n, check.Equals, 1)
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_repair_repairs.go snapd-2.45.1+18.04/cmd/snap/cmd_repair_repairs.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_repair_repairs.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_repair_repairs.go 2020-06-05 13:13:49.000000000 +0000
@@ -69,6 +69,10 @@
}
func (x *cmdShowRepair) Execute(args []string) error {
+ if len(x.Positional.Repair) == 0 {
+ return fmt.Errorf("no given. Try 'snap repairs' to list all repairs or specify a specific repair id.")
+ }
+
return runSnapRepair("show", x.Positional.Repair)
}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_repair_repairs_test.go snapd-2.45.1+18.04/cmd/snap/cmd_repair_repairs_test.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_repair_repairs_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_repair_repairs_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -20,7 +20,6 @@
package main_test
import (
- "os"
"path/filepath"
. "gopkg.in/check.v1"
@@ -32,10 +31,7 @@
)
func mockSnapRepair(c *C) *testutil.MockCmd {
- coreLibExecDir := filepath.Join(dirs.GlobalRootDir, dirs.CoreLibExecDir)
- err := os.MkdirAll(coreLibExecDir, 0755)
- c.Assert(err, IsNil)
- return testutil.MockCommand(c, filepath.Join(coreLibExecDir, "snap-repair"), "")
+ return testutil.MockCommand(c, filepath.Join(dirs.GlobalRootDir, dirs.CoreLibExecDir, "snap-repair"), "")
}
func (s *SnapSuite) TestSnapShowRepair(c *C) {
@@ -52,6 +48,14 @@
})
}
+func (s *SnapSuite) TestSnapShowRepairNoArgs(c *C) {
+ restore := release.MockOnClassic(false)
+ defer restore()
+
+ _, err := snap.Parser(snap.Client()).ParseArgs([]string{"repair"})
+ c.Assert(err, ErrorMatches, "no given. Try 'snap repairs' to list all repairs or specify a specific repair id.")
+}
+
func (s *SnapSuite) TestSnapListRepairs(c *C) {
restore := release.MockOnClassic(false)
defer restore()
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_routine_file_access.go snapd-2.45.1+18.04/cmd/snap/cmd_routine_file_access.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_routine_file_access.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_routine_file_access.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,216 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public 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"
+ "path/filepath"
+ "strings"
+
+ "github.com/jessevdk/go-flags"
+
+ "github.com/snapcore/snapd/client"
+ "github.com/snapcore/snapd/i18n"
+)
+
+type cmdRoutineFileAccess struct {
+ clientMixin
+ FileAccessOptions struct {
+ Snap installedSnapName
+ Path flags.Filename
+ } `positional-args:"true" required:"true"`
+}
+
+var shortRoutineFileAccessHelp = i18n.G("Return information about file access by a snap")
+var longRoutineFileAccessHelp = i18n.G(`
+The file-access command returns information about a snap's file system access.
+
+This command is used by the xdg-document-portal service to identify
+files that do not need to be proxied to provide access within
+confinement.
+
+File paths are interpreted as host file system paths. The tool may
+return false negatives (e.g. report that a file path is unreadable,
+despite being readable under a different path). It also does not
+check if file system permissions would render a file unreadable.
+`)
+
+func init() {
+ addRoutineCommand("file-access", shortRoutineFileAccessHelp, longRoutineFileAccessHelp, func() flags.Commander {
+ return &cmdRoutineFileAccess{}
+ }, nil, []argDesc{
+ {
+ // TRANSLATORS: This needs to begin with < and end with >
+ name: i18n.G(""),
+ // TRANSLATORS: This should not start with a lowercase letter.
+ desc: i18n.G("Snap name"),
+ },
+ {
+ // TRANSLATORS: This needs to begin with < and end with >
+ name: i18n.G(""),
+ // TRANSLATORS: This should not start with a lowercase letter.
+ desc: i18n.G("File path"),
+ },
+ })
+}
+
+func (x *cmdRoutineFileAccess) Execute(args []string) error {
+ if len(args) > 0 {
+ return ErrExtraArgs
+ }
+
+ snapName := string(x.FileAccessOptions.Snap)
+ path := string(x.FileAccessOptions.Path)
+
+ snap, _, err := x.client.Snap(snapName)
+ if err != nil {
+ return fmt.Errorf("cannot retrieve info for snap %q: %v", snapName, err)
+ }
+
+ // Check whether the snap has home or removable-media plugs connected
+ connections, err := x.client.Connections(&client.ConnectionOptions{
+ Snap: snap.Name,
+ })
+ if err != nil {
+ return fmt.Errorf("cannot get connections for snap %q: %v", snap.Name, err)
+ }
+ var hasHome, hasRemovableMedia bool
+ for _, conn := range connections.Established {
+ if conn.Plug.Snap != snap.Name {
+ continue
+ }
+ switch conn.Interface {
+ case "home":
+ hasHome = true
+ case "removable-media":
+ hasRemovableMedia = true
+ }
+ }
+
+ access, err := x.checkAccess(snap, hasHome, hasRemovableMedia, path)
+ if err != nil {
+ return err
+ }
+ fmt.Fprintln(Stdout, access)
+ return nil
+}
+
+type FileAccess string
+
+const (
+ FileAccessHidden FileAccess = "hidden"
+ FileAccessReadOnly FileAccess = "read-only"
+ FileAccessReadWrite FileAccess = "read-write"
+)
+
+func splitPathAbs(path string) ([]string, error) {
+ // Abs also cleans the path, removing any ".." components
+ path, err := filepath.Abs(path)
+ if err != nil {
+ return nil, err
+ }
+ // Ignore the empty component before the first slash
+ return strings.Split(path, string(os.PathSeparator))[1:], nil
+}
+
+func pathHasPrefix(path, prefix []string) bool {
+ if len(path) < len(prefix) {
+ return false
+ }
+ for i := range prefix {
+ if path[i] != prefix[i] {
+ return false
+ }
+ }
+ return true
+}
+
+func (x *cmdRoutineFileAccess) checkAccess(snap *client.Snap, hasHome, hasRemovableMedia bool, path string) (FileAccess, error) {
+ // Classic confinement snaps run in the host system namespace,
+ // so can see everything.
+ if snap.Confinement == client.ClassicConfinement {
+ return FileAccessReadWrite, nil
+ }
+
+ pathParts, err := splitPathAbs(path)
+ if err != nil {
+ return "", err
+ }
+
+ // Snaps have access to $SNAP_DATA and $SNAP_COMMON
+ if pathHasPrefix(pathParts, []string{"var", "snap", snap.Name}) {
+ if len(pathParts) == 3 {
+ return FileAccessReadOnly, nil
+ }
+ switch pathParts[3] {
+ case "common", "current", snap.Revision.String():
+ return FileAccessReadWrite, nil
+ default:
+ return FileAccessReadOnly, nil
+ }
+ }
+
+ // Snaps with removable-media plugged can access removable
+ // media mount points.
+ if hasRemovableMedia {
+ if pathHasPrefix(pathParts, []string{"mnt"}) || pathHasPrefix(pathParts, []string{"media"}) || pathHasPrefix(pathParts, []string{"run", "media"}) {
+ return FileAccessReadWrite, nil
+ }
+ }
+
+ usr, err := userCurrent()
+ if err != nil {
+ return "", fmt.Errorf("cannot get the current user: %v", err)
+ }
+
+ home, err := splitPathAbs(usr.HomeDir)
+ if err != nil {
+ return "", err
+ }
+ if pathHasPrefix(pathParts, home) {
+ pathInHome := pathParts[len(home):]
+ // Snaps have access to $SNAP_USER_DATA and $SNAP_USER_COMMON
+ if pathHasPrefix(pathInHome, []string{"snap"}) {
+ if !pathHasPrefix(pathInHome, []string{"snap", snap.Name}) {
+ return FileAccessHidden, nil
+ }
+ if len(pathInHome) < 3 {
+ return FileAccessReadOnly, nil
+ }
+ switch pathInHome[2] {
+ case "common", "current", snap.Revision.String():
+ return FileAccessReadWrite, nil
+ default:
+ return FileAccessReadOnly, nil
+ }
+ }
+ // If the home interface is connected, the snap has
+ // access to other files in home, except top-level dot
+ // files.
+ if hasHome {
+ if len(pathInHome) == 0 || !strings.HasPrefix(pathInHome[0], ".") {
+ return FileAccessReadWrite, nil
+ }
+ }
+ }
+
+ return FileAccessHidden, nil
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_routine_file_access_test.go snapd-2.45.1+18.04/cmd/snap/cmd_routine_file_access_test.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_routine_file_access_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_routine_file_access_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,185 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public 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"
+ "net/url"
+ "os/user"
+ "path/filepath"
+ "strings"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/client"
+ snap "github.com/snapcore/snapd/cmd/snap"
+)
+
+type SnapRoutineFileAccessSuite struct {
+ BaseSnapSuite
+
+ fakeHome string
+}
+
+var _ = Suite(&SnapRoutineFileAccessSuite{})
+
+func (s *SnapRoutineFileAccessSuite) SetUpTest(c *C) {
+ s.BaseSnapSuite.SetUpTest(c)
+
+ s.fakeHome = c.MkDir()
+ u, err := user.Current()
+ c.Assert(err, IsNil)
+ s.AddCleanup(snap.MockUserCurrent(func() (*user.User, error) {
+ return &user.User{Uid: u.Uid, HomeDir: s.fakeHome}, nil
+ }))
+}
+
+func (s *SnapRoutineFileAccessSuite) setUpClient(c *C, isClassic, hasHome, hasRemovableMedia bool) {
+ s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
+ switch r.URL.Path {
+ case "/v2/snaps/hello":
+ c.Check(r.Method, Equals, "GET")
+ // snap hello at revision 100
+ response := mockInfoJSONNoLicense
+ if isClassic {
+ response = strings.Replace(response, `"confinement": "strict"`, `"confinement": "classic"`, 1)
+ }
+ fmt.Fprintln(w, response)
+ case "/v2/connections":
+ c.Check(r.Method, Equals, "GET")
+ c.Check(r.URL.Path, Equals, "/v2/connections")
+ c.Check(r.URL.Query(), DeepEquals, url.Values{
+ "snap": []string{"hello"},
+ })
+ connections := []client.Connection{}
+ if hasHome {
+ connections = append(connections, client.Connection{
+ Slot: client.SlotRef{
+ Snap: "core",
+ Name: "home",
+ },
+ Plug: client.PlugRef{
+ Snap: "hello",
+ Name: "home",
+ },
+ Interface: "home",
+ })
+ }
+ if hasRemovableMedia {
+ connections = append(connections, client.Connection{
+ Slot: client.SlotRef{
+ Snap: "core",
+ Name: "removable-media",
+ },
+ Plug: client.PlugRef{
+ Snap: "hello",
+ Name: "removable-media",
+ },
+ Interface: "removable-media",
+ })
+ }
+ result := client.Connections{Established: connections}
+ EncodeResponseBody(c, w, map[string]interface{}{
+ "type": "sync",
+ "result": result,
+ })
+ default:
+ c.Fatalf("unexpected request: %v", r)
+ }
+ })
+}
+
+func (s *SnapRoutineFileAccessSuite) checkAccess(c *C, path, access string) {
+ _, err := snap.Parser(snap.Client()).ParseArgs([]string{"routine", "file-access", "hello", path})
+ c.Assert(err, IsNil)
+ c.Check(s.Stdout(), Equals, access)
+ c.Check(s.Stderr(), Equals, "")
+ s.ResetStdStreams()
+}
+
+func (s *SnapRoutineFileAccessSuite) checkBasicAccess(c *C) {
+ // Check access to SNAP_DATA and SNAP_COMMON
+ s.checkAccess(c, "/var/snap", "hidden\n")
+ s.checkAccess(c, "/var/snap/other-snap", "hidden\n")
+ s.checkAccess(c, "/var/snap/hello", "read-only\n")
+ s.checkAccess(c, "/var/snap/hello/common", "read-write\n")
+ s.checkAccess(c, "/var/snap/hello/current", "read-write\n")
+ s.checkAccess(c, "/var/snap/hello/100", "read-write\n")
+ s.checkAccess(c, "/var/snap/hello/99", "read-only\n")
+
+ // Check access to SNAP_USER_DATA and SNAP_USER_COMMON
+ s.checkAccess(c, filepath.Join(s.fakeHome, "snap"), "hidden\n")
+ s.checkAccess(c, filepath.Join(s.fakeHome, "snap/other-snap"), "hidden\n")
+ s.checkAccess(c, filepath.Join(s.fakeHome, "snap/hello"), "read-only\n")
+ s.checkAccess(c, filepath.Join(s.fakeHome, "snap/hello/common"), "read-write\n")
+ s.checkAccess(c, filepath.Join(s.fakeHome, "snap/hello/current"), "read-write\n")
+ s.checkAccess(c, filepath.Join(s.fakeHome, "snap/hello/100"), "read-write\n")
+ s.checkAccess(c, filepath.Join(s.fakeHome, "snap/hello/99"), "read-only\n")
+}
+
+func (s *SnapRoutineFileAccessSuite) TestAccessDefault(c *C) {
+ s.setUpClient(c, false, false, false)
+ s.checkBasicAccess(c)
+
+ // No access to root
+ s.checkAccess(c, "/", "hidden\n")
+ s.checkAccess(c, "/usr/lib/libfoo.so", "hidden\n")
+ // No access to removable media
+ s.checkAccess(c, "/media/foo", "hidden\n")
+ // No access to home directory
+ s.checkAccess(c, s.fakeHome, "hidden\n")
+ s.checkAccess(c, filepath.Join(s.fakeHome, "Documents"), "hidden\n")
+}
+
+func (s *SnapRoutineFileAccessSuite) TestAccessClassicConfinement(c *C) {
+ s.setUpClient(c, true, false, false)
+
+ // Classic confinement snaps run in the host file system
+ // namespace, so have access to everything.
+ s.checkAccess(c, "/", "read-write\n")
+ s.checkAccess(c, "/usr/lib/libfoo.so", "read-write\n")
+ s.checkAccess(c, "/", "read-write\n")
+ s.checkAccess(c, s.fakeHome, "read-write\n")
+ s.checkAccess(c, filepath.Join(s.fakeHome, "snap/other-snap"), "read-write\n")
+}
+
+func (s *SnapRoutineFileAccessSuite) TestAccessHomeInterface(c *C) {
+ s.setUpClient(c, false, true, false)
+ s.checkBasicAccess(c)
+
+ // Access to non-hidden files in the home directory
+ s.checkAccess(c, s.fakeHome, "read-write\n")
+ s.checkAccess(c, filepath.Join(s.fakeHome, "Documents/foo.txt"), "read-write\n")
+ s.checkAccess(c, filepath.Join(s.fakeHome, "Documents/.hidden"), "read-write\n")
+ s.checkAccess(c, filepath.Join(s.fakeHome, ".config"), "hidden\n")
+}
+
+func (s *SnapRoutineFileAccessSuite) TestAccessRemovableMedia(c *C) {
+ s.setUpClient(c, false, false, true)
+ s.checkBasicAccess(c)
+
+ s.checkAccess(c, "/mnt", "read-write\n")
+ s.checkAccess(c, "/mnt/path/file.txt", "read-write\n")
+ s.checkAccess(c, "/media", "read-write\n")
+ s.checkAccess(c, "/media/path/file.txt", "read-write\n")
+ s.checkAccess(c, "/run/media", "read-write\n")
+ s.checkAccess(c, "/run/media/path/file.txt", "read-write\n")
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_routine.go snapd-2.45.1+18.04/cmd/snap/cmd_routine.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_routine.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_routine.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,35 @@
+// -*- 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 (
+ "github.com/snapcore/snapd/i18n"
+)
+
+type cmdRoutine struct{}
+
+var shortRoutineHelp = i18n.G("Run routine commands")
+var longRoutineHelp = i18n.G(`
+The routine command contains a selection of additional sub-commands.
+
+Routine commands are not intended to be directly invoked by the user.
+Instead, they are intended to be called by other programs and produce
+machine readable output.
+`)
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_routine_portal_info.go snapd-2.45.1+18.04/cmd/snap/cmd_routine_portal_info.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_routine_portal_info.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_routine_portal_info.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,152 @@
+// -*- 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"
+ "path/filepath"
+ "text/template"
+
+ "github.com/jessevdk/go-flags"
+
+ "github.com/snapcore/snapd/client"
+ "github.com/snapcore/snapd/i18n"
+ "github.com/snapcore/snapd/sandbox/apparmor"
+ "github.com/snapcore/snapd/sandbox/cgroup"
+)
+
+type cmdRoutinePortalInfo struct {
+ clientMixin
+ PortalInfoOptions struct {
+ Pid int
+ } `positional-args:"true" required:"true"`
+}
+
+var shortRoutinePortalInfoHelp = i18n.G("Return information about a process")
+var longRoutinePortalInfoHelp = i18n.G(`
+The portal-info command returns information about a process in keyfile format.
+
+This command is used by the xdg-desktop-portal service to retrieve
+information about snap confined processes.
+`)
+
+func init() {
+ addRoutineCommand("portal-info", shortRoutinePortalInfoHelp, longRoutinePortalInfoHelp, func() flags.Commander {
+ return &cmdRoutinePortalInfo{}
+ }, nil, []argDesc{{
+ // TRANSLATORS: This needs to begin with < and end with >
+ name: i18n.G(""),
+ // TRANSLATORS: This should not start with a lowercase letter.
+ desc: i18n.G("Process ID of confined app"),
+ }})
+}
+
+var (
+ cgroupSnapNameFromPid = cgroup.SnapNameFromPid
+ apparmorSnapAppFromPid = apparmor.SnapAppFromPid
+)
+
+func (x *cmdRoutinePortalInfo) Execute(args []string) error {
+ if len(args) > 0 {
+ return ErrExtraArgs
+ }
+
+ snapName, err := cgroupSnapNameFromPid(x.PortalInfoOptions.Pid)
+ if err != nil {
+ return err
+ }
+ snap, _, err := x.client.Snap(snapName)
+ if err != nil {
+ return fmt.Errorf("cannot retrieve info for snap %q: %v", snapName, err)
+ }
+
+ // Try to identify the application name from AppArmor
+ var app *client.AppInfo
+ if snapName, appName, _, err := apparmorSnapAppFromPid(x.PortalInfoOptions.Pid); err == nil && snapName == snap.Name && appName != "" {
+ for i := range snap.Apps {
+ if snap.Apps[i].Name == appName {
+ app = &snap.Apps[i]
+ break
+ }
+ }
+ }
+ // As a fallback, pick an app with a desktop file, favouring
+ // the app named identically to the snap.
+ if app == nil {
+ for i := range snap.Apps {
+ if snap.Apps[i].DesktopFile != "" && (app == nil || snap.Apps[i].Name == snap.Name) {
+ app = &snap.Apps[i]
+ }
+ }
+ }
+
+ var desktopFile string
+ if app != nil {
+ desktopFile = filepath.Base(app.DesktopFile)
+ }
+
+ // Determine whether the snap has access to the network status
+ // TODO: use direct API for asking about interface being connected if
+ // that becomes available
+ connections, err := x.client.Connections(&client.ConnectionOptions{
+ Snap: snap.Name,
+ Interface: "network-status",
+ })
+ if err != nil {
+ return fmt.Errorf("cannot get connections for snap %q: %v", snap.Name, err)
+ }
+ // XXX: on non-AppArmor systems, or systems where there is only a
+ // partial AppArmor support, the snap may still be able to access the
+ // network despite the 'network' interface being disconnected
+ var hasNetworkStatus bool
+ for _, conn := range connections.Established {
+ if conn.Plug.Snap == snap.Name && conn.Interface == "network-status" {
+ hasNetworkStatus = true
+ break
+ }
+ }
+
+ const portalInfoTemplate = `[Snap Info]
+InstanceName={{.Snap.Name}}
+{{- if .App}}
+AppName={{.App.Name}}
+{{- end}}
+{{- if .DesktopFile}}
+DesktopFile={{.DesktopFile}}
+{{- end}}
+HasNetworkStatus={{.HasNetworkStatus}}
+`
+ t := template.Must(template.New("portal-info").Parse(portalInfoTemplate))
+ data := struct {
+ Snap *client.Snap
+ App *client.AppInfo
+ DesktopFile string
+ HasNetworkStatus bool
+ }{
+ Snap: snap,
+ App: app,
+ DesktopFile: desktopFile,
+ HasNetworkStatus: hasNetworkStatus,
+ }
+ if err := t.Execute(Stdout, data); err != nil {
+ return fmt.Errorf("cannot render output template: %s", err)
+ }
+ return nil
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_routine_portal_info_test.go snapd-2.45.1+18.04/cmd/snap/cmd_routine_portal_info_test.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_routine_portal_info_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_routine_portal_info_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,188 @@
+// -*- 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 (
+ "errors"
+ "fmt"
+ "net/http"
+ "net/url"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/client"
+ snap "github.com/snapcore/snapd/cmd/snap"
+)
+
+// only used for /v2/snaps/hello
+const mockInfoJSONWithApps = `
+{
+ "type": "sync",
+ "status-code": 200,
+ "status": "OK",
+ "result": {
+ "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6",
+ "title": "hello",
+ "summary": "GNU Hello, the \"hello world\" snap",
+ "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/",
+ "installed-size": 98304,
+ "name": "hello",
+ "publisher": {
+ "id": "canonical",
+ "username": "canonical",
+ "display-name": "Canonical",
+ "validation": "verified"
+ },
+ "developer": "canonical",
+ "status": "active",
+ "type": "app",
+ "version": "2.10",
+ "channel": "stable",
+ "tracking-channel": "stable",
+ "ignore-validation": false,
+ "revision": "38",
+ "confinement": "strict",
+ "private": false,
+ "devmode": false,
+ "jailmode": false,
+ "apps": [
+ {
+ "snap": "hello",
+ "name": "hello",
+ "desktop-file": "/path/to/hello_hello.desktop"
+ },
+ {
+ "snap": "hello",
+ "name": "universe",
+ "desktop-file": "/path/to/hello_universe.desktop"
+ }
+ ],
+ "contact": "mailto:snaps@canonical.com",
+ "mounted-from": "/var/lib/snapd/snaps/hello_38.snap",
+ "install-date": "2019-10-11T13:34:15.630955389+08:00"
+ }
+}
+`
+
+func (s *SnapSuite) TestPortalInfo(c *C) {
+ restore := snap.MockCgroupSnapNameFromPid(func(pid int) (string, error) {
+ c.Check(pid, Equals, 42)
+ return "hello", nil
+ })
+ defer restore()
+ restore = snap.MockApparmorSnapAppFromPid(func(pid int) (string, string, string, error) {
+ c.Check(pid, Equals, 42)
+ return "hello", "universe", "", nil
+ })
+ defer restore()
+ n := 0
+ s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
+ switch n {
+ case 0:
+ c.Check(r.Method, Equals, "GET")
+ c.Check(r.URL.Path, Equals, "/v2/snaps/hello")
+ fmt.Fprintln(w, mockInfoJSONWithApps)
+ case 1:
+ c.Check(r.Method, Equals, "GET")
+ c.Check(r.URL.Path, Equals, "/v2/connections")
+ c.Check(r.URL.Query(), DeepEquals, url.Values{
+ "snap": []string{"hello"},
+ "interface": []string{"network-status"},
+ })
+ result := client.Connections{
+ Established: []client.Connection{
+ {
+ Slot: client.SlotRef{
+ Snap: "core",
+ Name: "network-status",
+ },
+ Plug: client.PlugRef{
+ Snap: "hello",
+ Name: "network-status",
+ },
+ Interface: "network-status",
+ },
+ },
+ }
+ EncodeResponseBody(c, w, map[string]interface{}{
+ "type": "sync",
+ "result": result,
+ })
+ default:
+ c.Fatalf("expected to get 2 requests, now on %d (%v)", n+1, r)
+ }
+ n++
+ })
+ _, err := snap.Parser(snap.Client()).ParseArgs([]string{"routine", "portal-info", "42"})
+ c.Assert(err, IsNil)
+ c.Check(s.Stdout(), Equals, `[Snap Info]
+InstanceName=hello
+AppName=universe
+DesktopFile=hello_universe.desktop
+HasNetworkStatus=true
+`)
+ c.Check(s.Stderr(), Equals, "")
+}
+
+func (s *SnapSuite) TestPortalInfoNoAppInfo(c *C) {
+ restore := snap.MockCgroupSnapNameFromPid(func(pid int) (string, error) {
+ c.Check(pid, Equals, 42)
+ return "hello", nil
+ })
+ defer restore()
+ restore = snap.MockApparmorSnapAppFromPid(func(pid int) (string, string, string, error) {
+ c.Check(pid, Equals, 42)
+ return "", "", "", errors.New("no apparmor")
+ })
+ defer restore()
+ n := 0
+ s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
+ switch n {
+ case 0:
+ c.Check(r.Method, Equals, "GET")
+ c.Check(r.URL.Path, Equals, "/v2/snaps/hello")
+ fmt.Fprintln(w, mockInfoJSONWithApps)
+ case 1:
+ c.Check(r.Method, Equals, "GET")
+ c.Check(r.URL.Path, Equals, "/v2/connections")
+ c.Check(r.URL.Query(), DeepEquals, url.Values{
+ "snap": []string{"hello"},
+ "interface": []string{"network-status"},
+ })
+ result := client.Connections{}
+ EncodeResponseBody(c, w, map[string]interface{}{
+ "type": "sync",
+ "result": result,
+ })
+ default:
+ c.Fatalf("expected to get 2 requests, now on %d (%v)", n+1, r)
+ }
+ n++
+ })
+ _, err := snap.Parser(snap.Client()).ParseArgs([]string{"routine", "portal-info", "42"})
+ c.Assert(err, IsNil)
+ c.Check(s.Stdout(), Equals, `[Snap Info]
+InstanceName=hello
+AppName=hello
+DesktopFile=hello_hello.desktop
+HasNetworkStatus=false
+`)
+ c.Check(s.Stderr(), Equals, "")
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_run.go snapd-2.45.1+18.04/cmd/snap/cmd_run.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_run.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_run.go 2020-06-05 13:13:49.000000000 +0000
@@ -44,7 +44,7 @@
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/osutil/strace"
- "github.com/snapcore/snapd/selinux"
+ "github.com/snapcore/snapd/sandbox/selinux"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snapenv"
"github.com/snapcore/snapd/strutil/shlex"
@@ -112,6 +112,26 @@
}, nil)
}
+// isStopping returns true if the system is shutting down.
+func isStopping() (bool, error) {
+ // Make sure, just in case, that systemd doesn't localize the output string.
+ env, err := osutil.OSEnvironment()
+ if err != nil {
+ return false, err
+ }
+ env["LC_MESSAGES"] = "C"
+ // Check if systemd is stopping (shutting down or rebooting).
+ cmd := exec.Command("systemctl", "is-system-running")
+ cmd.Env = env.ForExec()
+ stdout, err := cmd.Output()
+ // systemctl is-system-running returns non-zero for outcomes other than "running"
+ // As such, ignore any ExitError and just process the stdout buffer.
+ if _, ok := err.(*exec.ExitError); ok {
+ return string(stdout) == "stopping\n", nil
+ }
+ return false, err
+}
+
func maybeWaitForSecurityProfileRegeneration(cli *client.Client) error {
// check if the security profiles key has changed, if so, we need
// to wait for snapd to re-generate all profiles
@@ -125,6 +145,18 @@
logger.Debugf("SystemKeyMismatch returned an error: %v", err)
}
+ // We have a mismatch but maybe it is only because systemd is shutting down
+ // and core or snapd were already unmounted and we failed to re-execute.
+ // For context see: https://bugs.launchpad.net/snapd/+bug/1871652
+ stopping, err := isStopping()
+ if err != nil {
+ logger.Debugf("cannot check if system is stopping: %s", err)
+ }
+ if stopping {
+ logger.Debugf("ignoring system key mismatch during system shutdown/reboot")
+ return nil
+ }
+
// We have a mismatch, try to connect to snapd, once we can
// connect we just continue because that usually means that
// a new snapd is ready and has generated profiles.
@@ -145,11 +177,13 @@
}
}
+ logger.Debugf("system key mismatch detected, waiting for snapd to start responding...")
+
for i := 0; i < timeout; i++ {
if _, err := cli.SysInfo(); err == nil {
return nil
}
- // sleep a litte bit for good measure
+ // sleep a little bit for good measure
time.Sleep(1 * time.Second)
}
@@ -712,9 +746,9 @@
return nil
}
-func (x *cmdRun) runCmdUnderGdb(origCmd, env []string) error {
- env = append(env, "SNAP_CONFINE_RUN_UNDER_GDB=1")
+type envForExecFunc func(extra map[string]string) []string
+func (x *cmdRun) runCmdUnderGdb(origCmd []string, envForExec envForExecFunc) error {
cmd := []string{"sudo", "-E", "gdb", "-ex=run", "-ex=catch exec", "-ex=continue", "--args"}
cmd = append(cmd, origCmd...)
@@ -722,11 +756,11 @@
gcmd.Stdin = os.Stdin
gcmd.Stdout = os.Stdout
gcmd.Stderr = os.Stderr
- gcmd.Env = env
+ gcmd.Env = envForExec(map[string]string{"SNAP_CONFINE_RUN_UNDER_GDB": "1"})
return gcmd.Run()
}
-func (x *cmdRun) runCmdWithTraceExec(origCmd, env []string) error {
+func (x *cmdRun) runCmdWithTraceExec(origCmd []string, envForExec envForExecFunc) error {
// setup private tmp dir with strace fifo
straceTmp, err := ioutil.TempDir("", "exec-trace")
if err != nil {
@@ -761,7 +795,7 @@
return err
}
// run
- cmd.Env = env
+ cmd.Env = envForExec(nil)
cmd.Stdin = Stdin
cmd.Stdout = Stdout
cmd.Stderr = Stderr
@@ -781,7 +815,7 @@
return err
}
-func (x *cmdRun) runCmdUnderStrace(origCmd, env []string) error {
+func (x *cmdRun) runCmdUnderStrace(origCmd []string, envForExec envForExecFunc) error {
extraStraceOpts, raw, err := x.straceOpts()
if err != nil {
return err
@@ -792,7 +826,7 @@
}
// run with filter
- cmd.Env = env
+ cmd.Env = envForExec(nil)
cmd.Stdin = Stdin
cmd.Stdout = Stdout
stderr, err := cmd.StderrPipe()
@@ -938,19 +972,44 @@
cmd = append(cmd, snapApp)
cmd = append(cmd, args...)
- extraEnv := make(map[string]string)
+ env, err := osutil.OSEnvironment()
+ if err != nil {
+ return err
+ }
+ snapenv.ExtendEnvForRun(env, info)
+
if len(xauthPath) > 0 {
- extraEnv["XAUTHORITY"] = xauthPath
+ // Environment is not nil here because it comes from
+ // osutil.OSEnvironment and that guarantees this
+ // property.
+ env["XAUTHORITY"] = xauthPath
+ }
+
+ // on each run variant path this will be used once to get
+ // the environment plus additions in the right form
+ envForExec := func(extra map[string]string) []string {
+ for varName, value := range extra {
+ env[varName] = value
+ }
+ if !info.NeedsClassic() {
+ return env.ForExec()
+ }
+ // For a classic snap, environment variables that are
+ // usually stripped out by ld.so when starting a
+ // setuid process are presevered by being renamed by
+ // prepending PreservedUnsafePrefix -- which snap-exec
+ // will remove, restoring the variables to their
+ // original names.
+ return env.ForExecEscapeUnsafe(snapenv.PreservedUnsafePrefix)
}
- env := snapenv.ExecEnv(info, extraEnv)
if x.TraceExec {
- return x.runCmdWithTraceExec(cmd, env)
+ return x.runCmdWithTraceExec(cmd, envForExec)
} else if x.Gdb {
- return x.runCmdUnderGdb(cmd, env)
+ return x.runCmdUnderGdb(cmd, envForExec)
} else if x.useStrace() {
- return x.runCmdUnderStrace(cmd, env)
+ return x.runCmdUnderStrace(cmd, envForExec)
} else {
- return syscallExec(cmd[0], cmd, env)
+ return syscallExec(cmd[0], cmd, envForExec(nil))
}
}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_run_test.go snapd-2.45.1+18.04/cmd/snap/cmd_run_test.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_run_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_run_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -34,7 +34,7 @@
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
- "github.com/snapcore/snapd/selinux"
+ "github.com/snapcore/snapd/sandbox/selinux"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snaptest"
"github.com/snapcore/snapd/testutil"
@@ -159,6 +159,13 @@
func (s *RunSuite) TestSnapRunAppIntegration(c *check.C) {
defer mockSnapConfine(dirs.DistroLibExecDir)()
+ tmpdir := os.Getenv("TMPDIR")
+ if tmpdir == "" {
+ tmpdir = "/var/tmp"
+ os.Setenv("TMPDIR", tmpdir)
+ defer os.Unsetenv("TMPDIR")
+ }
+
// mock installed snap
snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{
Revision: snap.R("x2"),
@@ -187,11 +194,19 @@
filepath.Join(dirs.CoreLibExecDir, "snap-exec"),
"snapname.app", "--arg1", "arg2"})
c.Check(execEnv, testutil.Contains, "SNAP_REVISION=x2")
+ c.Check(execEnv, testutil.Contains, fmt.Sprintf("TMPDIR=%s", tmpdir))
}
func (s *RunSuite) TestSnapRunClassicAppIntegration(c *check.C) {
defer mockSnapConfine(dirs.DistroLibExecDir)()
+ tmpdir := os.Getenv("TMPDIR")
+ if tmpdir == "" {
+ tmpdir = "/var/tmp"
+ os.Setenv("TMPDIR", tmpdir)
+ defer os.Unsetenv("TMPDIR")
+ }
+
// mock installed snap
snaptest.MockSnapCurrent(c, string(mockYaml)+"confinement: classic\n", &snap.SideInfo{
Revision: snap.R("x2"),
@@ -220,7 +235,7 @@
filepath.Join(dirs.DistroLibExecDir, "snap-exec"),
"snapname.app", "--arg1", "arg2"})
c.Check(execEnv, testutil.Contains, "SNAP_REVISION=x2")
-
+ c.Check(execEnv, testutil.Contains, fmt.Sprintf("SNAP_SAVED_TMPDIR=%s", tmpdir))
}
func (s *RunSuite) TestSnapRunClassicAppIntegrationReexecedFromCore(c *check.C) {
@@ -1182,3 +1197,63 @@
c.Check(verifyCalls, check.Equals, 2)
c.Check(restoreCalls, check.Equals, 1)
}
+
+// systemctl is-system-running returns "running" in normal situations.
+func (s *RunSuite) TestIsStoppingRunning(c *check.C) {
+ systemctl := testutil.MockCommand(c, "systemctl", `
+case "$1" in
+ is-system-running)
+ echo "running"
+ exit 0
+ ;;
+esac
+`)
+ defer systemctl.Restore()
+ stop, err := snaprun.IsStopping()
+ c.Check(err, check.IsNil)
+ c.Check(stop, check.Equals, false)
+ c.Check(systemctl.Calls(), check.DeepEquals, [][]string{
+ {"systemctl", "is-system-running"},
+ })
+}
+
+// systemctl is-system-running returns "stopping" when the system is
+// shutting down or rebooting. At the same time it returns a non-zero
+// exit status.
+func (s *RunSuite) TestIsStoppingStopping(c *check.C) {
+ systemctl := testutil.MockCommand(c, "systemctl", `
+case "$1" in
+ is-system-running)
+ echo "stopping"
+ exit 1
+ ;;
+esac
+`)
+ defer systemctl.Restore()
+ stop, err := snaprun.IsStopping()
+ c.Check(err, check.IsNil)
+ c.Check(stop, check.Equals, true)
+ c.Check(systemctl.Calls(), check.DeepEquals, [][]string{
+ {"systemctl", "is-system-running"},
+ })
+}
+
+// systemctl is-system-running can often return "degraded"
+// Let's make sure that is not confusing us.
+func (s *RunSuite) TestIsStoppingDegraded(c *check.C) {
+ systemctl := testutil.MockCommand(c, "systemctl", `
+case "$1" in
+ is-system-running)
+ echo "degraded"
+ exit 1
+ ;;
+esac
+`)
+ defer systemctl.Restore()
+ stop, err := snaprun.IsStopping()
+ c.Check(err, check.IsNil)
+ c.Check(stop, check.Equals, false)
+ c.Check(systemctl.Calls(), check.DeepEquals, [][]string{
+ {"systemctl", "is-system-running"},
+ })
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_services_test.go snapd-2.45.1+18.04/cmd/snap/cmd_services_test.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_services_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_services_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -35,8 +35,6 @@
type appOpSuite struct {
BaseSnapSuite
-
- restoreAll func()
}
var _ = check.Suite(&appOpSuite{})
@@ -44,16 +42,13 @@
func (s *appOpSuite) SetUpTest(c *check.C) {
s.BaseSnapSuite.SetUpTest(c)
- restoreClientRetry := client.MockDoRetry(time.Millisecond, 10*time.Millisecond)
+ restoreClientRetry := client.MockDoTimings(time.Millisecond, 100*time.Millisecond)
restorePollTime := snap.MockPollTime(time.Millisecond)
- s.restoreAll = func() {
- restoreClientRetry()
- restorePollTime()
- }
+ s.AddCleanup(restoreClientRetry)
+ s.AddCleanup(restorePollTime)
}
func (s *appOpSuite) TearDownTest(c *check.C) {
- s.restoreAll()
s.BaseSnapSuite.TearDownTest(c)
}
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_sign.go snapd-2.45.1+18.04/cmd/snap/cmd_sign.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_sign.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_sign.go 2020-06-05 13:13:49.000000000 +0000
@@ -38,6 +38,10 @@
`)
type cmdSign struct {
+ Positional struct {
+ Filename flags.Filename
+ } `positional-args:"yes"`
+
KeyName keyName `short:"k" default:"default"`
}
@@ -47,8 +51,14 @@
}, map[string]string{
// TRANSLATORS: This should not start with a lowercase letter.
"k": i18n.G("Name of the key to use, otherwise use the default key"),
- }, nil)
+ }, []argDesc{{
+ // TRANSLATORS: This needs to begin with < and end with >
+ name: i18n.G(""),
+ // TRANSLATORS: This should not start with a lowercase letter.
+ desc: i18n.G("File to sign (defaults to stdin)"),
+ }})
cmd.hidden = true
+ cmd.completeHidden = true
}
func (x *cmdSign) Execute(args []string) error {
@@ -56,7 +66,17 @@
return ErrExtraArgs
}
- statement, err := ioutil.ReadAll(Stdin)
+ useStdin := x.Positional.Filename == "" || x.Positional.Filename == "-"
+
+ var (
+ statement []byte
+ err error
+ )
+ if !useStdin {
+ statement, err = ioutil.ReadFile(string(x.Positional.Filename))
+ } else {
+ statement, err = ioutil.ReadAll(Stdin)
+ }
if err != nil {
return fmt.Errorf(i18n.G("cannot read assertion input: %v"), err)
}
@@ -64,7 +84,8 @@
keypairMgr := asserts.NewGPGKeypairManager()
privKey, err := keypairMgr.GetByName(string(x.KeyName))
if err != nil {
- return err
+ // TRANSLATORS: %q is the key name, %v the error message
+ return fmt.Errorf(i18n.G("cannot use %q key: %v"), x.KeyName, err)
}
signOpts := signtool.Options{
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_snap_op.go snapd-2.45.1+18.04/cmd/snap/cmd_snap_op.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_snap_op.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_snap_op.go 2020-06-05 13:13:49.000000000 +0000
@@ -252,10 +252,21 @@
mx.Channel = ch.chName
}
- if !strings.Contains(mx.Channel, "/") && mx.Channel != "" && mx.Channel != "edge" && mx.Channel != "beta" && mx.Channel != "candidate" && mx.Channel != "stable" {
- // shortcut to jump to a different track, e.g.
- // snap install foo --channel=3.4 # implies 3.4/stable
- mx.Channel += "/stable"
+ if mx.Channel != "" {
+ if _, err := channel.Parse(mx.Channel, ""); err != nil {
+ full, er := channel.Full(mx.Channel)
+ if er != nil {
+ // the parse error has more detailed info
+ return err
+ }
+
+ // TODO: get escapes in here so we can bold the Warning
+ head := i18n.G("Warning:")
+ msg := i18n.G("Specifying a channel %q is relying on undefined behaviour. Interpreting it as %q for now, but this will be an error later.\n")
+ warn := fill(fmt.Sprintf(msg, mx.Channel, full), utf8.RuneCountInString(head)+1) // +1 for the space
+ fmt.Fprint(Stderr, head, " ", warn, "\n\n")
+ mx.Channel = full // so a malformed-but-eh channel will always be full, i.e. //stable// -> latest/stable
+ }
}
return nil
@@ -307,8 +318,14 @@
needsPathWarning := !isSnapInPath()
for _, snap := range snaps {
channelStr := ""
- if snap.Channel != "" && snap.Channel != "stable" {
- channelStr = fmt.Sprintf(" (%s)", snap.Channel)
+ if snap.Channel != "" {
+ ch, err := channel.Parse(snap.Channel, "")
+ if err != nil {
+ return err
+ }
+ if ch.Name != "stable" {
+ channelStr = fmt.Sprintf(" (%s)", ch.Name)
+ }
}
switch op {
case "install":
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_snap_op_test.go snapd-2.45.1+18.04/cmd/snap/cmd_snap_op_test.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_snap_op_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_snap_op_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -100,7 +100,7 @@
func (s *SnapOpSuite) SetUpTest(c *check.C) {
s.BaseSnapSuite.SetUpTest(c)
- restoreClientRetry := client.MockDoRetry(time.Millisecond, 10*time.Millisecond)
+ restoreClientRetry := client.MockDoTimings(time.Millisecond, 100*time.Millisecond)
restorePollTime := snap.MockPollTime(time.Millisecond)
s.restoreAll = func() {
restoreClientRetry()
@@ -140,8 +140,6 @@
func (s *SnapOpSuite) TestWaitRecovers(c *check.C) {
meter := &progresstest.Meter{}
defer progress.MockMeter(meter)()
- restore := snap.MockMaxGoneTime(time.Millisecond)
- defer restore()
nah := true
s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
@@ -155,7 +153,7 @@
cli := snap.Client()
chg, err := snap.Wait(cli, "x")
// we got the change
- c.Assert(chg, check.NotNil)
+ c.Check(chg, check.NotNil)
c.Assert(err, check.IsNil)
// but only after recovering
@@ -235,7 +233,7 @@
c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
"action": "install",
- "channel": "3.4/stable",
+ "channel": "3.4",
})
s.srv.channel = "3.4/stable"
}
@@ -451,11 +449,7 @@
})
_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel=mytrack", "foo"})
- c.Assert(err, check.NotNil)
- c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, `
-error: snap "foo" not available on channel "mytrack/stable" (see 'snap info
- foo')
-`)
+ c.Check(err, check.ErrorMatches, `snap "foo" not available on channel "mytrack" \(see 'snap info foo'\)`)
c.Check(s.Stdout(), check.Equals, "")
c.Check(s.Stderr(), check.Equals, "")
@@ -662,21 +656,11 @@
func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailableInvalidChannel(c *check.C) {
s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
- fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision on specified channel", "value": {
- "snap-name": "foo",
- "action": "install",
- "architecture": "amd64",
- "channel": "a/b/c/d",
- "releases": [{"architecture": "amd64", "channel": "stable"}]
-}, "kind": "snap-channel-not-available"}, "status-code": 404}`)
+ c.Fatal("unexpected call to server")
})
_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel=a/b/c/d", "foo"})
- c.Assert(err, check.NotNil)
- c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, `
-error: requested channel "a/b/c/d" is not valid (see 'snap info foo' for valid
- ones)
-`)
+ c.Assert(err, check.ErrorMatches, "channel name has too many components: a/b/c/d")
c.Check(s.Stdout(), check.Equals, "")
c.Check(s.Stderr(), check.Equals, "")
@@ -1301,6 +1285,25 @@
}
+func (s *SnapOpSuite) TestRefreshOneChanDeprecated(c *check.C) {
+ var in, out string
+ s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
+ c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{"action": "refresh", "channel": out})
+ fmt.Fprintln(w, `{"type": "error", "result": {"message": "snap not found", "value": "foo", "kind": "snap-not-found"}, "status-code": 404}`)
+ })
+
+ for in, out = range map[string]string{
+ "/foo": "foo/stable",
+ "/stable": "latest/stable",
+ "///foo/stable//": "foo/stable",
+ } {
+ s.stderr.Reset()
+ _, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--channel=" + in, "one"})
+ c.Assert(err, check.ErrorMatches, "snap \"one\" not found")
+ c.Check(s.Stderr(), testutil.EqualsWrapped, `Warning: Specifying a channel "`+in+`" is relying on undefined behaviour. Interpreting it as "`+out+`" for now, but this will be an error later.`)
+ }
+}
+
func (s *SnapOpSuite) TestRefreshOneModeErr(c *check.C) {
s.RedirectClientToTestServer(nil)
_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--jailmode", "--devmode", "one"})
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_snapshot.go snapd-2.45.1+18.04/cmd/snap/cmd_snapshot.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_snapshot.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_snapshot.go 2020-06-05 13:13:49.000000000 +0000
@@ -338,13 +338,13 @@
"users": i18n.G("Restore data of only specific users (comma-separated) (default: all users)"),
}), []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')"),
+ }, {
+ name: "",
+ // TRANSLATORS: This should not start with a lowercase letter.
+ desc: i18n.G("The snap for which data will be restored"),
},
})
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_userd.go snapd-2.45.1+18.04/cmd/snap/cmd_userd.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_userd.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_userd.go 2020-06-05 13:13:49.000000000 +0000
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+// +build !darwin
/*
* Copyright (C) 2017-2019 Canonical Ltd
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_userd_test.go snapd-2.45.1+18.04/cmd/snap/cmd_userd_test.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_userd_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_userd_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -1,4 +1,5 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
+// +build !darwin
/*
* Copyright (C) 2016-2019 Canonical Ltd
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_version.go snapd-2.45.1+18.04/cmd/snap/cmd_version.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_version.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_version.go 2020-06-05 13:13:49.000000000 +0000
@@ -68,6 +68,7 @@
if sv.KernelVersion != "" {
fmt.Fprintf(w, "kernel\t%s\n", sv.KernelVersion)
}
+
w.Flush()
return nil
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_version_test.go snapd-2.45.1+18.04/cmd/snap/cmd_version_test.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_version_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_version_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -30,7 +30,7 @@
func (s *SnapSuite) TestVersionCommandOnClassic(c *C) {
s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
- fmt.Fprintln(w, `{"type":"sync","status-code":200,"status":"OK","result":{"on-classic":true,"os-release":{"id":"ubuntu","version-id":"12.34"},"series":"56","version":"7.89"}}`)
+ fmt.Fprintln(w, `{"type":"sync","status-code":200,"status":"OK","result":{"on-classic":true,"os-release":{"id":"ubuntu","version-id":"12.34"},"series":"56","version":"7.89","architecture":"ia64"}}`)
})
restore := mockArgs("snap", "version")
defer restore()
@@ -45,7 +45,7 @@
func (s *SnapSuite) TestVersionCommandOnAllSnap(c *C) {
s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
- fmt.Fprintln(w, `{"type":"sync","status-code":200,"status":"OK","result":{"os-release":{"id":"ubuntu","version-id":"12.34"},"series":"56","version":"7.89"}}`)
+ fmt.Fprintln(w, `{"type":"sync","status-code":200,"status":"OK","result":{"os-release":{"id":"ubuntu","version-id":"12.34"},"series":"56","version":"7.89","architecture":"powerpc","virtualization":"qemu"}}`)
})
restore := mockArgs("snap", "--version")
defer restore()
diff -Nru snapd-2.42.1+18.04/cmd/snap/cmd_wait.go snapd-2.45.1+18.04/cmd/snap/cmd_wait.go
--- snapd-2.42.1+18.04/cmd/snap/cmd_wait.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/cmd_wait.go 2020-06-05 13:13:49.000000000 +0000
@@ -22,7 +22,6 @@
import (
"encoding/json"
"fmt"
- "math/rand"
"reflect"
"time"
@@ -117,21 +116,6 @@
snapName := string(x.Positional.Snap)
confKey := x.Positional.Key
- // This is fine because not providing a confKey is unsupported so this
- // won't interfere with supported uses of `snap wait`.
- if snapName == "godot" && confKey == "" {
- switch rand.Intn(10) {
- case 0:
- fmt.Fprintln(Stdout, `The tears of the world are a constant quantity.
-For each one who begins to weep somewhere else another stops.
-The same is true of the laugh.`)
- case 1:
- fmt.Fprintln(Stdout, "Nothing happens. Nobody comes, nobody goes. It's awful.")
- default:
- fmt.Fprintln(Stdout, `"Let's go." "We can't." "Why not?" "We're waiting for Godot."`)
- }
- return nil
- }
if confKey == "" {
return fmt.Errorf("the required argument `` was not provided")
}
diff -Nru snapd-2.42.1+18.04/cmd/snap/error.go snapd-2.45.1+18.04/cmd/snap/error.go
--- snapd-2.42.1+18.04/cmd/snap/error.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/error.go 2020-06-05 13:13:49.000000000 +0000
@@ -244,6 +244,7 @@
// as reported by the store through the daemon
req, err := channel.Parse(snapChannel, arch)
if err != nil {
+ // XXX: this is no longer possible (should be caught before hitting the store), unless the state itself has an invalid channel
// 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)"), snapChannel, snapName)
return msg
diff -Nru snapd-2.42.1+18.04/cmd/snap/export_test.go snapd-2.45.1+18.04/cmd/snap/export_test.go
--- snapd-2.42.1+18.04/cmd/snap/export_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/export_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -28,7 +28,7 @@
"github.com/snapcore/snapd/client"
"github.com/snapcore/snapd/image"
- "github.com/snapcore/snapd/selinux"
+ "github.com/snapcore/snapd/sandbox/selinux"
"github.com/snapcore/snapd/store"
)
@@ -39,16 +39,18 @@
FirstNonOptionIsRun = firstNonOptionIsRun
- CreateUserDataDirs = createUserDataDirs
- ResolveApp = resolveApp
- SnapdHelperPath = snapdHelperPath
- SortByPath = sortByPath
- AdviseCommand = adviseCommand
- Antialias = antialias
- FormatChannel = fmtChannel
- PrintDescr = printDescr
- WrapFlow = wrapFlow
- TrueishJSON = trueishJSON
+ CreateUserDataDirs = createUserDataDirs
+ ResolveApp = resolveApp
+ SnapdHelperPath = snapdHelperPath
+ SortByPath = sortByPath
+ AdviseCommand = adviseCommand
+ Antialias = antialias
+ FormatChannel = fmtChannel
+ PrintDescr = printDescr
+ WrapFlow = wrapFlow
+ TrueishJSON = trueishJSON
+ CompletionHandler = completionHandler
+ MarkForNoCompletion = markForNoCompletion
CanUnicode = canUnicode
ColorTable = colorTable
@@ -80,8 +82,24 @@
InterfacesDeprecationNotice = interfacesDeprecationNotice
SignalNotify = signalNotify
+
+ SortTimingsTasks = sortTimingsTasks
+
+ PrintInstallHint = printInstallHint
+
+ IsStopping = isStopping
)
+func HiddenCmd(descr string, completeHidden bool) *cmdInfo {
+ return &cmdInfo{
+ shortHelp: descr,
+ hidden: true,
+ completeHidden: completeHidden,
+ }
+}
+
+type ChangeTimings = changeTimings
+
func NewInfoWriter(w writeflusher) *infoWriter {
return &infoWriter{
writeflusher: w,
@@ -307,3 +325,35 @@
}
type ServiceName = serviceName
+
+func MockApparmorSnapAppFromPid(f func(pid int) (string, string, string, error)) (restore func()) {
+ old := apparmorSnapAppFromPid
+ apparmorSnapAppFromPid = f
+ return func() {
+ apparmorSnapAppFromPid = old
+ }
+}
+
+func MockCgroupSnapNameFromPid(f func(pid int) (string, error)) (restore func()) {
+ old := cgroupSnapNameFromPid
+ cgroupSnapNameFromPid = f
+ return func() {
+ cgroupSnapNameFromPid = old
+ }
+}
+
+func MockSyscallUmount(f func(string, int) error) (restore func()) {
+ old := syscallUnmount
+ syscallUnmount = f
+ return func() {
+ syscallUnmount = old
+ }
+}
+
+func MockIoutilTempDir(f func(string, string) (string, error)) (restore func()) {
+ old := ioutilTempDir
+ ioutilTempDir = f
+ return func() {
+ ioutilTempDir = old
+ }
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap/main.go snapd-2.45.1+18.04/cmd/snap/main.go
--- snapd-2.42.1+18.04/cmd/snap/main.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/main.go 2020-06-05 13:13:49.000000000 +0000
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2014-2015 Canonical Ltd
+ * Copyright (C) 2014-2020 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -37,24 +37,25 @@
"github.com/snapcore/snapd/client"
"github.com/snapcore/snapd/cmd"
"github.com/snapcore/snapd/dirs"
- "github.com/snapcore/snapd/httputil"
"github.com/snapcore/snapd/i18n"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snapdenv"
)
func init() {
// set User-Agent for when 'snap' talks to the store directly (snap download etc...)
- httputil.SetUserAgentFromVersion(cmd.Version, "snap")
+ snapdenv.SetUserAgentFromVersion(cmd.Version, nil, "snap")
- if osutil.GetenvBool("SNAPD_DEBUG") || osutil.GetenvBool("SNAPPY_TESTING") {
+ if osutil.GetenvBool("SNAPD_DEBUG") || snapdenv.Testing() {
// in tests or when debugging, enforce the "tidy" lint checks
noticef = logger.Panicf
}
- // plug/slot sanitization not used nor possible from snap command, make it no-op
+ // plug/slot sanitization not used by snap commands (except for snap pack
+ // which re-sets it), make it no-op.
snap.SanitizePlugsSlots = func(snapInfo *snap.Info) {}
}
@@ -88,10 +89,13 @@
name, shortHelp, longHelp string
builder func() flags.Commander
hidden bool
- optDescs map[string]string
- argDescs []argDesc
- alias string
- extra func(*flags.Command)
+ // completeHidden set to true forces completion even of
+ // a hidden command
+ completeHidden bool
+ optDescs map[string]string
+ argDescs []argDesc
+ alias string
+ extra func(*flags.Command)
}
// commands holds information about all non-debug commands.
@@ -100,6 +104,9 @@
// debugCommands holds information about all debug commands.
var debugCommands []*cmdInfo
+// routineCommands holds information about all internal commands.
+var routineCommands []*cmdInfo
+
// addCommand replaces parser.addCommand() in a way that is compatible with
// re-constructing a pristine parser.
func addCommand(name, shortHelp, longHelp string, builder func() flags.Commander, optDescs map[string]string, argDescs []argDesc) *cmdInfo {
@@ -131,6 +138,22 @@
return info
}
+// addRoutineCommand replaces parser.addCommand() in a way that is
+// compatible with re-constructing a pristine parser. It is meant for
+// adding "snap routine" commands.
+func addRoutineCommand(name, shortHelp, longHelp string, builder func() flags.Commander, optDescs map[string]string, argDescs []argDesc) *cmdInfo {
+ info := &cmdInfo{
+ name: name,
+ shortHelp: shortHelp,
+ longHelp: longHelp,
+ builder: builder,
+ optDescs: optDescs,
+ argDescs: argDescs,
+ }
+ routineCommands = append(routineCommands, info)
+ return info
+}
+
type parserSetter interface {
setParser(*flags.Parser)
}
@@ -202,32 +225,35 @@
return false
}
-// Parser creates and populates a fresh parser.
-// Since commands have local state a fresh parser is required to isolate tests
-// from each other.
-func Parser(cli *client.Client) *flags.Parser {
- optionsData.Version = func() {
- printVersions(cli)
- panic(&exitStatus{0})
- }
- flagopts := flags.Options(flags.PassDoubleDash)
- if firstNonOptionIsRun() {
- flagopts |= flags.PassAfterNonOption
+// noCompletion marks command descriptions of commands that should not
+// be completed
+var noCompletion = make(map[string]bool)
+
+func markForNoCompletion(ci *cmdInfo) {
+ if ci.hidden && !ci.completeHidden {
+ if ci.shortHelp == "" {
+ logger.Panicf("%q missing short help", ci.name)
+ }
+ noCompletion[ci.shortHelp] = true
}
- parser := flags.NewParser(&optionsData, flagopts)
- parser.ShortDescription = i18n.G("Tool to interact with snaps")
- parser.LongDescription = longSnapDescription
- // hide the unhelpful "[OPTIONS]" from help output
- parser.Usage = ""
- if version := parser.FindOptionByLongName("version"); version != nil {
- version.Description = i18n.G("Print the version and exit")
- version.Hidden = true
+}
+
+// completionHandler filters out unwanted completions based on
+// the noCompletion map before dumping them to stdout.
+func completionHandler(comps []flags.Completion) {
+ for _, comp := range comps {
+ if noCompletion[comp.Description] {
+ continue
+ }
+ fmt.Fprintln(Stdout, comp.Item)
}
- // add --help like what go-flags would do for us, but hidden
- addHelp(parser)
+}
- // Add all regular commands
+func registerCommands(cli *client.Client, parser *flags.Parser, baseCmd *flags.Command, commands []*cmdInfo, checkUnique func(*cmdInfo)) {
for _, c := range commands {
+ checkUnique(c)
+ markForNoCompletion(c)
+
obj := c.builder()
if x, ok := obj.(clientSetter); ok {
x.setClient(cli)
@@ -236,7 +262,7 @@
x.setParser(parser)
}
- cmd, err := parser.AddCommand(c.name, c.shortHelp, strings.TrimSpace(c.longHelp), obj)
+ cmd, err := baseCmd.AddCommand(c.name, c.shortHelp, strings.TrimSpace(c.longHelp), obj)
if err != nil {
logger.Panicf("cannot add command %q: %v", c.name, err)
}
@@ -283,6 +309,45 @@
c.extra(cmd)
}
}
+}
+
+// Parser creates and populates a fresh parser.
+// Since commands have local state a fresh parser is required to isolate tests
+// from each other.
+func Parser(cli *client.Client) *flags.Parser {
+ optionsData.Version = func() {
+ printVersions(cli)
+ panic(&exitStatus{0})
+ }
+ flagopts := flags.Options(flags.PassDoubleDash)
+ if firstNonOptionIsRun() {
+ flagopts |= flags.PassAfterNonOption
+ }
+ parser := flags.NewParser(&optionsData, flagopts)
+ parser.CompletionHandler = completionHandler
+ parser.ShortDescription = i18n.G("Tool to interact with snaps")
+ parser.LongDescription = longSnapDescription
+ // hide the unhelpful "[OPTIONS]" from help output
+ parser.Usage = ""
+ if version := parser.FindOptionByLongName("version"); version != nil {
+ version.Description = i18n.G("Print the version and exit")
+ version.Hidden = true
+ }
+ // add --help like what go-flags would do for us, but hidden
+ addHelp(parser)
+
+ seen := make(map[string]bool, len(commands)+len(debugCommands)+len(routineCommands))
+ checkUnique := func(ci *cmdInfo, kind string) {
+ if seen[ci.shortHelp] && ci.shortHelp != "Internal" && ci.shortHelp != "Deprecated (hidden)" {
+ logger.Panicf(`%scommand %q has an already employed description != "Internal"|"Deprecated (hidden)": %s`, kind, ci.name, ci.shortHelp)
+ }
+ seen[ci.shortHelp] = true
+ }
+
+ // Add all regular commands
+ registerCommands(cli, parser, parser.Command, commands, func(ci *cmdInfo) {
+ checkUnique(ci, "")
+ })
// Add the debug command
debugCommand, err := parser.AddCommand("debug", shortDebugHelp, longDebugHelp, &cmdDebug{})
debugCommand.Hidden = true
@@ -290,51 +355,19 @@
logger.Panicf("cannot add command %q: %v", "debug", err)
}
// Add all the sub-commands of the debug command
- for _, c := range debugCommands {
- obj := c.builder()
- if x, ok := obj.(clientSetter); ok {
- x.setClient(cli)
- }
- cmd, err := debugCommand.AddCommand(c.name, c.shortHelp, strings.TrimSpace(c.longHelp), obj)
- if err != nil {
- logger.Panicf("cannot add debug command %q: %v", c.name, err)
- }
- cmd.Hidden = c.hidden
- opts := cmd.Options()
- if c.optDescs != nil && len(opts) != len(c.optDescs) {
- logger.Panicf("wrong number of option descriptions for %s: expected %d, got %d", c.name, len(opts), len(c.optDescs))
- }
- for _, opt := range opts {
- name := opt.LongName
- if name == "" {
- name = string(opt.ShortName)
- }
- desc, ok := c.optDescs[name]
- if !(c.optDescs == nil || ok) {
- logger.Panicf("%s missing description for %s", c.name, name)
- }
- lintDesc(c.name, name, desc, opt.Description)
- if desc != "" {
- opt.Description = desc
- }
- }
-
- args := cmd.Args()
- if c.argDescs != nil && len(args) != len(c.argDescs) {
- logger.Panicf("wrong number of argument descriptions for %s: expected %d, got %d", c.name, len(args), len(c.argDescs))
- }
- for i, arg := range args {
- name, desc := arg.Name, ""
- if c.argDescs != nil {
- name = c.argDescs[i].name
- desc = c.argDescs[i].desc
- }
- lintArg(c.name, name, desc, arg.Description)
- name = fixupArg(name)
- arg.Name = name
- arg.Description = desc
- }
+ registerCommands(cli, parser, debugCommand, debugCommands, func(ci *cmdInfo) {
+ checkUnique(ci, "debug ")
+ })
+ // Add the internal command
+ routineCommand, err := parser.AddCommand("routine", shortRoutineHelp, longRoutineHelp, &cmdRoutine{})
+ routineCommand.Hidden = true
+ if err != nil {
+ logger.Panicf("cannot add command %q: %v", "internal", err)
}
+ // Add all the sub-commands of the routine command
+ registerCommands(cli, parser, routineCommand, routineCommands, func(ci *cmdInfo) {
+ checkUnique(ci, "routine ")
+ })
return parser
}
@@ -354,7 +387,7 @@
cfg := &ClientConfig
// Set client user-agent when talking to the snapd daemon to the
// same value as when talking to the store.
- cfg.UserAgent = httputil.UserAgent()
+ cfg.UserAgent = snapdenv.UserAgent()
cli := client.New(cfg)
goos := runtime.GOOS
diff -Nru snapd-2.42.1+18.04/cmd/snap/main_test.go snapd-2.45.1+18.04/cmd/snap/main_test.go
--- snapd-2.42.1+18.04/cmd/snap/main_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap/main_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -31,16 +31,15 @@
"strings"
"testing"
- . "gopkg.in/check.v1"
-
+ "github.com/jessevdk/go-flags"
"golang.org/x/crypto/ssh/terminal"
+ . "gopkg.in/check.v1"
"github.com/snapcore/snapd/cmd"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
- snapdsnap "github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/testutil"
snap "github.com/snapcore/snapd/cmd/snap"
@@ -85,8 +84,6 @@
s.AuthFile = filepath.Join(c.MkDir(), "json")
os.Setenv(TestAuthFileEnvKey, s.AuthFile)
- s.AddCleanup(snapdsnap.MockSanitizePlugsSlots(func(snapInfo *snapdsnap.Info) {}))
-
s.AddCleanup(interfaces.MockSystemKey(`
{
"build-id": "7a94e9736c091b3984bd63f5aebfc883c4d859e0",
@@ -423,3 +420,14 @@
_ = snap.RunMain()
c.Assert(testServerHit, Equals, true)
}
+
+func (s *SnapSuite) TestCompletionHandlerSkipsHidden(c *C) {
+ snap.MarkForNoCompletion(snap.HiddenCmd("bar yadda yack", false))
+ snap.MarkForNoCompletion(snap.HiddenCmd("bar yack yack yack", true))
+ snap.CompletionHandler([]flags.Completion{
+ {Item: "foo", Description: "foo yadda yadda"},
+ {Item: "bar", Description: "bar yadda yack"},
+ {Item: "baz", Description: "bar yack yack yack"},
+ })
+ c.Check(s.Stdout(), Equals, "foo\nbaz\n")
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/bootstrap/bootstrap_dummy.go snapd-2.45.1+18.04/cmd/snap-bootstrap/bootstrap/bootstrap_dummy.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/bootstrap/bootstrap_dummy.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/bootstrap/bootstrap_dummy.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,29 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+// +build nosecboot
+
+/*
+ * Copyright (C) 2019-2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package bootstrap
+
+import (
+ "fmt"
+)
+
+func Run(gadgetRoot, device string, options Options) error {
+ return fmt.Errorf("build without secboot support")
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/bootstrap/bootstrap.go snapd-2.45.1+18.04/cmd/snap-bootstrap/bootstrap/bootstrap.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/bootstrap/bootstrap.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/bootstrap/bootstrap.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,319 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+// +build !nosecboot
+
+/*
+ * Copyright (C) 2019-2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package bootstrap
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/boot"
+ "github.com/snapcore/snapd/cmd/snap-bootstrap/partition"
+ "github.com/snapcore/snapd/gadget"
+ "github.com/snapcore/snapd/secboot"
+)
+
+const (
+ ubuntuDataLabel = "ubuntu-data"
+)
+
+func deviceFromRole(lv *gadget.LaidOutVolume, role string) (device string, err error) {
+ for _, vs := range lv.LaidOutStructure {
+ // XXX: this part of the finding maybe should be a
+ // method on gadget.*Volume
+ if vs.Role == role {
+ device, err = gadget.FindDeviceForStructure(&vs)
+ if err != nil {
+ return "", fmt.Errorf("cannot find device for role %q: %v", role, err)
+ }
+ return gadget.ParentDiskFromPartition(device)
+ }
+ }
+ return "", fmt.Errorf("cannot find role %s in gadget", role)
+}
+
+// Run bootstraps the partitions of a device, by either creating
+// missing ones or recreating installed ones.
+func Run(gadgetRoot, device string, options Options) error {
+ if options.Encrypt && (options.KeyFile == "" || options.RecoveryKeyFile == "") {
+ return fmt.Errorf("key file and recovery key file must be specified when encrypting")
+ }
+
+ if gadgetRoot == "" {
+ return fmt.Errorf("cannot use empty gadget root directory")
+ }
+
+ lv, err := gadget.PositionedVolumeFromGadget(gadgetRoot)
+ if err != nil {
+ return fmt.Errorf("cannot layout the volume: %v", err)
+ }
+
+ // XXX: the only situation where auto-detect is not desired is
+ // in (spread) testing - consider to remove forcing a device
+ //
+ // auto-detect device if no device is forced
+ if device == "" {
+ device, err = deviceFromRole(lv, gadget.SystemSeed)
+ if err != nil {
+ return fmt.Errorf("cannot find device to create partitions on: %v", err)
+ }
+ }
+
+ diskLayout, err := partition.DeviceLayoutFromDisk(device)
+ if err != nil {
+ return fmt.Errorf("cannot read %v partitions: %v", device, err)
+ }
+
+ // check if the current partition table is compatible with the gadget,
+ // ignoring partitions added by the installer (will be removed later)
+ if err := ensureLayoutCompatibility(lv, diskLayout); err != nil {
+ return fmt.Errorf("gadget and %v partition table not compatible: %v", device, err)
+ }
+
+ // remove partitions added during a previous (failed) install attempt
+ if err := diskLayout.RemoveCreated(); err != nil {
+ return fmt.Errorf("cannot remove partitions from previous install: %v", err)
+ }
+ // at this point we removed any existing partition, nuke any
+ // of the existing sealed key files placed outside of the
+ // encrypted partitions (LP: #1879338)
+ if options.KeyFile != "" {
+ if err := os.Remove(options.KeyFile); err != nil && !os.IsNotExist(err) {
+ return fmt.Errorf("cannot cleanup obsolete key file: %v", options.KeyFile)
+ }
+
+ }
+
+ created, err := diskLayout.CreateMissing(lv)
+ if err != nil {
+ return fmt.Errorf("cannot create the partitions: %v", err)
+ }
+
+ // generate keys externally so multiple encrypted partitions can use the same key
+ var key partition.EncryptionKey
+ var rkey partition.RecoveryKey
+
+ if options.Encrypt {
+ key, err = partition.NewEncryptionKey()
+ if err != nil {
+ return fmt.Errorf("cannot create encryption key: %v", err)
+ }
+
+ rkey, err = partition.NewRecoveryKey()
+ if err != nil {
+ return fmt.Errorf("cannot create recovery key: %v", err)
+ }
+ }
+
+ for _, part := range created {
+ if options.Encrypt && part.Role == gadget.SystemData {
+ dataPart, err := partition.NewEncryptedDevice(&part, key, ubuntuDataLabel)
+ if err != nil {
+ return err
+ }
+
+ if err := dataPart.AddRecoveryKey(key, rkey); err != nil {
+ return err
+ }
+
+ // update the encrypted device node
+ part.Node = dataPart.Node
+ }
+
+ if err := partition.MakeFilesystem(part); err != nil {
+ return err
+ }
+
+ if err := partition.DeployContent(part, gadgetRoot); err != nil {
+ return err
+ }
+
+ if options.Mount && part.Label != "" && part.HasFilesystem() {
+ if err := partition.MountFilesystem(part, boot.InitramfsRunMntDir); err != nil {
+ return err
+ }
+ }
+ }
+
+ // store the encryption key as the last part of the process to reduce the
+ // possibility of exiting with an error after the TPM provisioning
+ if options.Encrypt {
+ if err := tpmSealKey(key, rkey, options); err != nil {
+ return fmt.Errorf("cannot seal the encryption key: %v", err)
+ }
+ }
+
+ return nil
+}
+
+func tpmSealKey(key partition.EncryptionKey, rkey partition.RecoveryKey, options Options) error {
+ if err := rkey.Store(options.RecoveryKeyFile); err != nil {
+ return fmt.Errorf("cannot store recovery key: %v", err)
+ }
+
+ tpm, err := secboot.NewTPMSupport()
+ if err != nil {
+ return fmt.Errorf("cannot initialize TPM: %v", err)
+ }
+
+ if options.TPMLockoutAuthFile != "" {
+ if err := tpm.StoreLockoutAuth(options.TPMLockoutAuthFile); err != nil {
+ return fmt.Errorf("cannot store TPM lockout authorization: %v", err)
+ }
+ }
+
+ shim := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI/boot/bootx64.efi")
+ grub := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI/boot/grubx64.efi")
+
+ // TODO:UC20: Fix EFI image loading events
+ //
+ // The idea of EFIImageLoadEvent is to build a set of load paths for the current
+ // device configuration. So you could have something like this:
+ //
+ // shim -> recovery grub -> recovery kernel 1
+ // |-> recovery kernel 2
+ // |-> recovery kernel ...
+ // |-> normal grub -> run kernel good
+ // |-> run kernel try
+ //
+ // Or it could look like this, which is the same thing:
+ //
+ // shim -> recovery grub -> recovery kernel 1
+ // shim -> recovery grub -> recovery kernel 2
+ // shim -> recovery grub -> recovery kernel ...
+ // shim -> recovery grub -> normal grub -> run kernel good
+ // shim -> recovery grub -> normal grub -> run kernel try
+ //
+ // This implementation in #8476, seems to just build a tree of shim -> grub -> kernel
+ // sequences for every combination of supplied input file, although the code here just
+ // specifies a single shim, grub and kernel binary, so you get one load path that looks
+ // like this:
+ //
+ // shim -> grub -> kernel
+ //
+ // This is ok for now because every boot path uses the same chain of trust, regardless
+ // of which kernel you're booting or whether you're booting through both the recovery
+ // and normal grubs. But when we add the ability to seal against specific binaries in
+ // order to secure the system with the Microsoft chain of trust, then the actual trees
+ // of EFIImageLoadEvents will need to match the exact supported boot sequences.
+
+ if err := tpm.SetShimFiles(shim); err != nil {
+ return err
+ }
+ if err := tpm.SetBootloaderFiles(grub); err != nil {
+ return err
+ }
+ if options.KernelPath != "" {
+ if err := tpm.SetKernelFiles(options.KernelPath); err != nil {
+ return err
+ }
+ }
+
+ // TODO:UC20: get cmdline definition from bootloaders
+ kernelCmdlines := []string{
+ // run mode
+ "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
+ // recover mode
+ fmt.Sprintf("snapd_recovery_mode=recover snapd_recovery_system=%s console=ttyS0 console=tty1 panic=-1", options.SystemLabel),
+ }
+
+ tpm.SetKernelCmdlines(kernelCmdlines)
+
+ if options.Model != nil {
+ tpm.SetModels([]*asserts.Model{options.Model})
+ }
+
+ // Provision the TPM as late as possible
+ // TODO:UC20: ideally we should ask the firmware to clear the TPM and then reboot
+ // if the device has previously been provisioned, see
+ // https://godoc.org/github.com/snapcore/secboot#RequestTPMClearUsingPPI
+ if err := tpm.Provision(); err != nil {
+ return fmt.Errorf("cannot provision the TPM: %v", err)
+ }
+
+ if err := tpm.Seal(key[:], options.KeyFile, options.PolicyUpdateDataFile); err != nil {
+ return fmt.Errorf("cannot seal and store encryption key: %v", err)
+ }
+
+ return nil
+}
+
+func ensureLayoutCompatibility(gadgetLayout *gadget.LaidOutVolume, diskLayout *partition.DeviceLayout) error {
+ eq := func(ds partition.DeviceStructure, gs gadget.LaidOutStructure) bool {
+ dv := ds.VolumeStructure
+ gv := gs.VolumeStructure
+ nameMatch := gv.Name == dv.Name
+ if gadgetLayout.Schema == "mbr" {
+ // partitions have no names in MBR
+ nameMatch = true
+ }
+ // Previous installation may have failed before filesystem creation or partition may be encrypted
+ check := nameMatch && ds.StartOffset == gs.StartOffset && (ds.CreatedDuringInstall || dv.Filesystem == gv.Filesystem)
+ if gv.Role == gadget.SystemData {
+ // system-data may have been expanded
+ return check && dv.Size >= gv.Size
+ }
+ return check && dv.Size == gv.Size
+ }
+ contains := func(haystack []gadget.LaidOutStructure, needle partition.DeviceStructure) bool {
+ for _, h := range haystack {
+ if eq(needle, h) {
+ return true
+ }
+ }
+ return false
+ }
+
+ if gadgetLayout.Size > diskLayout.Size {
+ return fmt.Errorf("device %v (%s) is too small to fit the requested layout (%s)", diskLayout.Device,
+ diskLayout.Size.IECString(), gadgetLayout.Size.IECString())
+ }
+
+ // Check if top level properties match
+ if !isCompatibleSchema(gadgetLayout.Volume.Schema, diskLayout.Schema) {
+ return fmt.Errorf("disk partitioning schema %q doesn't match gadget schema %q", diskLayout.Schema, gadgetLayout.Volume.Schema)
+ }
+ if gadgetLayout.Volume.ID != "" && gadgetLayout.Volume.ID != diskLayout.ID {
+ return fmt.Errorf("disk ID %q doesn't match gadget volume ID %q", diskLayout.ID, gadgetLayout.Volume.ID)
+ }
+
+ // Check if all existing device partitions are also in gadget
+ for _, ds := range diskLayout.Structure {
+ if !contains(gadgetLayout.LaidOutStructure, ds) {
+ return fmt.Errorf("cannot find disk partition %s (starting at %d) in gadget", ds.Node, ds.StartOffset)
+ }
+ }
+
+ return nil
+}
+
+func isCompatibleSchema(gadgetSchema, diskSchema string) bool {
+ switch gadgetSchema {
+ // XXX: "mbr,gpt" is currently unsupported
+ case "", "gpt":
+ return diskSchema == "gpt"
+ case "mbr":
+ return diskSchema == "dos"
+ default:
+ return false
+ }
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/bootstrap/bootstrap_test.go snapd-2.45.1+18.04/cmd/snap-bootstrap/bootstrap/bootstrap_test.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/bootstrap/bootstrap_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/bootstrap/bootstrap_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,421 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+// +build !nosecboot
+
+/*
+ * 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 bootstrap_test
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "testing"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/cmd/snap-bootstrap/bootstrap"
+ "github.com/snapcore/snapd/cmd/snap-bootstrap/partition"
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/gadget"
+ "github.com/snapcore/snapd/testutil"
+)
+
+func TestBootstrap(t *testing.T) { TestingT(t) }
+
+type bootstrapSuite struct {
+ testutil.BaseTest
+
+ dir string
+}
+
+var _ = Suite(&bootstrapSuite{})
+
+// XXX: write a very high level integration like test here that
+// mocks the world (sfdisk,lsblk,mkfs,...)? probably silly as
+// each part inside bootstrap is tested and we have a spread test
+
+func (s *bootstrapSuite) SetUpTest(c *C) {
+ s.BaseTest.SetUpTest(c)
+
+ s.dir = c.MkDir()
+ dirs.SetRootDir(s.dir)
+ s.AddCleanup(func() { dirs.SetRootDir("/") })
+}
+
+func (s *bootstrapSuite) TestBootstrapRunError(c *C) {
+ err := bootstrap.Run("", "", bootstrap.Options{})
+ c.Assert(err, ErrorMatches, "cannot use empty gadget root directory")
+}
+
+const mockGadgetYaml = `volumes:
+ pc:
+ bootloader: grub
+ structure:
+ - name: mbr
+ type: mbr
+ size: 440
+ - name: BIOS Boot
+ type: DA,21686148-6449-6E6F-744E-656564454649
+ size: 1M
+ offset: 1M
+ offset-write: mbr+92
+`
+
+const mockExtraStructure = `
+ - name: Writable
+ role: system-data
+ filesystem-label: writable
+ filesystem: ext4
+ type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
+ size: 1200M
+`
+
+var mockDeviceLayout = partition.DeviceLayout{
+ Structure: []partition.DeviceStructure{
+ {
+ LaidOutStructure: gadget.LaidOutStructure{
+ VolumeStructure: &gadget.VolumeStructure{
+ Name: "mbr",
+ Size: 440,
+ },
+ StartOffset: 0,
+ },
+ Node: "/dev/node1",
+ },
+ {
+ LaidOutStructure: gadget.LaidOutStructure{
+ VolumeStructure: &gadget.VolumeStructure{
+ Name: "BIOS Boot",
+ Size: 1 * gadget.SizeMiB,
+ },
+ StartOffset: 1 * gadget.SizeMiB,
+ },
+ Node: "/dev/node2",
+ },
+ },
+ ID: "anything",
+ Device: "/dev/node",
+ Schema: "gpt",
+ Size: 2 * gadget.SizeGiB,
+ SectorSize: 512,
+}
+
+func (s *bootstrapSuite) TestLayoutCompatibility(c *C) {
+ // same contents (the locally created structure should be ignored)
+ gadgetLayout := layoutFromYaml(c, mockGadgetYaml)
+ err := bootstrap.EnsureLayoutCompatibility(gadgetLayout, &mockDeviceLayout)
+ c.Assert(err, IsNil)
+
+ // missing structure (that's ok)
+ gadgetLayoutWithExtras := layoutFromYaml(c, mockGadgetYaml+mockExtraStructure)
+ err = bootstrap.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &mockDeviceLayout)
+ c.Assert(err, IsNil)
+
+ deviceLayoutWithExtras := mockDeviceLayout
+ deviceLayoutWithExtras.Structure = append(deviceLayoutWithExtras.Structure,
+ partition.DeviceStructure{
+ LaidOutStructure: gadget.LaidOutStructure{
+ VolumeStructure: &gadget.VolumeStructure{
+ Name: "Extra partition",
+ Size: 10 * gadget.SizeMiB,
+ Label: "extra",
+ },
+ StartOffset: 2 * gadget.SizeMiB,
+ },
+ Node: "/dev/node3",
+ },
+ )
+ // extra structure (should fail)
+ err = bootstrap.EnsureLayoutCompatibility(gadgetLayout, &deviceLayoutWithExtras)
+ c.Assert(err, ErrorMatches, `cannot find disk partition /dev/node3.* in gadget`)
+
+ // layout is not compatible if the device is too small
+ smallDeviceLayout := mockDeviceLayout
+ smallDeviceLayout.Size = 100 * gadget.SizeMiB
+ // sanity check
+ c.Check(gadgetLayoutWithExtras.Size > smallDeviceLayout.Size, Equals, true)
+ err = bootstrap.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &smallDeviceLayout)
+ c.Assert(err, ErrorMatches, `device /dev/node \(100 MiB\) is too small to fit the requested layout \(1\.17 GiB\)`)
+}
+
+func (s *bootstrapSuite) TestMBRLayoutCompatibility(c *C) {
+ const mockMBRGadgetYaml = `volumes:
+ pc:
+ schema: mbr
+ bootloader: grub
+ structure:
+ - name: mbr
+ type: mbr
+ size: 440
+ - name: BIOS Boot
+ type: DA,21686148-6449-6E6F-744E-656564454649
+ size: 1M
+ offset: 1M
+ offset-write: mbr+92
+`
+ var mockMBRDeviceLayout = partition.DeviceLayout{
+ Structure: []partition.DeviceStructure{
+ {
+ LaidOutStructure: gadget.LaidOutStructure{
+ VolumeStructure: &gadget.VolumeStructure{
+ // partition names have no
+ // meaning in MBR schema
+ Name: "other",
+ Size: 440,
+ },
+ StartOffset: 0,
+ },
+ Node: "/dev/node1",
+ },
+ {
+ LaidOutStructure: gadget.LaidOutStructure{
+ VolumeStructure: &gadget.VolumeStructure{
+ // partition names have no
+ // meaning in MBR schema
+ Name: "different BIOS Boot",
+ Size: 1 * gadget.SizeMiB,
+ },
+ StartOffset: 1 * gadget.SizeMiB,
+ },
+ Node: "/dev/node2",
+ },
+ },
+ ID: "anything",
+ Device: "/dev/node",
+ Schema: "dos",
+ Size: 2 * gadget.SizeGiB,
+ SectorSize: 512,
+ }
+ gadgetLayout := layoutFromYaml(c, mockMBRGadgetYaml)
+ err := bootstrap.EnsureLayoutCompatibility(gadgetLayout, &mockMBRDeviceLayout)
+ c.Assert(err, IsNil)
+ // structure is missing from disk
+ gadgetLayoutWithExtras := layoutFromYaml(c, mockMBRGadgetYaml+mockExtraStructure)
+ err = bootstrap.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &mockMBRDeviceLayout)
+ c.Assert(err, IsNil)
+ // add it now
+ deviceLayoutWithExtras := mockMBRDeviceLayout
+ deviceLayoutWithExtras.Structure = append(deviceLayoutWithExtras.Structure,
+ partition.DeviceStructure{
+ LaidOutStructure: gadget.LaidOutStructure{
+ VolumeStructure: &gadget.VolumeStructure{
+ // name is ignored with MBR schema
+ Name: "Extra partition",
+ Size: 1200 * gadget.SizeMiB,
+ Label: "extra",
+ Filesystem: "ext4",
+ Type: "83",
+ },
+ StartOffset: 2 * gadget.SizeMiB,
+ },
+ Node: "/dev/node3",
+ },
+ )
+ err = bootstrap.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &deviceLayoutWithExtras)
+ c.Assert(err, IsNil)
+ // add another structure that's not part of the gadget
+ deviceLayoutWithExtras.Structure = append(deviceLayoutWithExtras.Structure,
+ partition.DeviceStructure{
+ LaidOutStructure: gadget.LaidOutStructure{
+ VolumeStructure: &gadget.VolumeStructure{
+ // name is ignored with MBR schema
+ Name: "Extra extra partition",
+ Size: 1 * gadget.SizeMiB,
+ },
+ StartOffset: 1202 * gadget.SizeMiB,
+ },
+ Node: "/dev/node4",
+ },
+ )
+ err = bootstrap.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &deviceLayoutWithExtras)
+ c.Assert(err, ErrorMatches, `cannot find disk partition /dev/node4 .* in gadget`)
+}
+
+func (s *bootstrapSuite) TestLayoutCompatibilityWithCreatedPartitions(c *C) {
+ gadgetLayoutWithExtras := layoutFromYaml(c, mockGadgetYaml+mockExtraStructure)
+ deviceLayout := mockDeviceLayout
+ // device matches gadget except for the filesystem type
+ deviceLayout.Structure = append(deviceLayout.Structure,
+ partition.DeviceStructure{
+ LaidOutStructure: gadget.LaidOutStructure{
+ VolumeStructure: &gadget.VolumeStructure{
+ Name: "Writable",
+ Size: 1200 * gadget.SizeMiB,
+ Label: "writable",
+ Filesystem: "something_else",
+ },
+ StartOffset: 2 * gadget.SizeMiB,
+ },
+ Node: "/dev/node3",
+ CreatedDuringInstall: true,
+ },
+ )
+ err := bootstrap.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &deviceLayout)
+ c.Assert(err, IsNil)
+
+ // compare layouts without partitions created at install time (should fail)
+ deviceLayout.Structure[len(deviceLayout.Structure)-1].CreatedDuringInstall = false
+ err = bootstrap.EnsureLayoutCompatibility(gadgetLayoutWithExtras, &deviceLayout)
+ c.Assert(err, ErrorMatches, `cannot find disk partition /dev/node3.* in gadget`)
+
+}
+
+func (s *bootstrapSuite) TestSchemaCompatibility(c *C) {
+ gadgetLayout := layoutFromYaml(c, mockGadgetYaml)
+ deviceLayout := mockDeviceLayout
+
+ error_msg := "disk partitioning.* doesn't match gadget.*"
+
+ for i, tc := range []struct {
+ gs string
+ ds string
+ e string
+ }{
+ {"", "dos", error_msg},
+ {"", "gpt", ""},
+ {"", "xxx", error_msg},
+ {"mbr", "dos", ""},
+ {"mbr", "gpt", error_msg},
+ {"mbr", "xxx", error_msg},
+ {"gpt", "dos", error_msg},
+ {"gpt", "gpt", ""},
+ {"gpt", "xxx", error_msg},
+ // XXX: "mbr,gpt" is currently unsupported
+ {"mbr,gpt", "dos", error_msg},
+ {"mbr,gpt", "gpt", error_msg},
+ {"mbr,gpt", "xxx", error_msg},
+ } {
+ c.Logf("%d: %q %q\n", i, tc.gs, tc.ds)
+ gadgetLayout.Volume.Schema = tc.gs
+ deviceLayout.Schema = tc.ds
+ err := bootstrap.EnsureLayoutCompatibility(gadgetLayout, &deviceLayout)
+ if tc.e == "" {
+ c.Assert(err, IsNil)
+ } else {
+ c.Assert(err, ErrorMatches, tc.e)
+ }
+ }
+ c.Logf("-----")
+}
+
+func (s *bootstrapSuite) TestIDCompatibility(c *C) {
+ gadgetLayout := layoutFromYaml(c, mockGadgetYaml)
+ deviceLayout := mockDeviceLayout
+
+ error_msg := "disk ID.* doesn't match gadget volume ID.*"
+
+ for i, tc := range []struct {
+ gid string
+ did string
+ e string
+ }{
+ {"", "", ""},
+ {"", "123", ""},
+ {"123", "345", error_msg},
+ {"123", "123", ""},
+ } {
+ c.Logf("%d: %q %q\n", i, tc.gid, tc.did)
+ gadgetLayout.Volume.ID = tc.gid
+ deviceLayout.ID = tc.did
+ err := bootstrap.EnsureLayoutCompatibility(gadgetLayout, &deviceLayout)
+ if tc.e == "" {
+ c.Assert(err, IsNil)
+ } else {
+ c.Assert(err, ErrorMatches, tc.e)
+ }
+ }
+ c.Logf("-----")
+}
+
+func layoutFromYaml(c *C, gadgetYaml string) *gadget.LaidOutVolume {
+ gadgetRoot := filepath.Join(c.MkDir(), "gadget")
+ err := os.MkdirAll(filepath.Join(gadgetRoot, "meta"), 0755)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(filepath.Join(gadgetRoot, "meta", "gadget.yaml"), []byte(gadgetYaml), 0644)
+ c.Assert(err, IsNil)
+ pv, err := gadget.PositionedVolumeFromGadget(gadgetRoot)
+ c.Assert(err, IsNil)
+ return pv
+}
+
+const mockUC20GadgetYaml = `volumes:
+ pc:
+ bootloader: grub
+ structure:
+ - name: mbr
+ type: mbr
+ size: 440
+ - name: BIOS Boot
+ type: DA,21686148-6449-6E6F-744E-656564454649
+ size: 1M
+ offset: 1M
+ offset-write: mbr+92
+ - name: ubuntu-seed
+ role: system-seed
+ filesystem: vfat
+ # UEFI will boot the ESP partition by default first
+ type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B
+ size: 1200M
+ - name: ubuntu-data
+ role: system-data
+ filesystem: ext4
+ type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
+ size: 750M
+`
+
+func (s *bootstrapSuite) setupMockSysfs(c *C) {
+ err := os.MkdirAll(filepath.Join(s.dir, "/dev/disk/by-partlabel"), 0755)
+ c.Assert(err, IsNil)
+
+ err = ioutil.WriteFile(filepath.Join(s.dir, "/dev/fakedevice0p1"), nil, 0644)
+ c.Assert(err, IsNil)
+ err = os.Symlink("../../fakedevice0p1", filepath.Join(s.dir, "/dev/disk/by-partlabel/ubuntu-seed"))
+ c.Assert(err, IsNil)
+
+ // make parent device
+ err = ioutil.WriteFile(filepath.Join(s.dir, "/dev/fakedevice0"), nil, 0644)
+ c.Assert(err, IsNil)
+ // and fake /sys/block structure
+ err = os.MkdirAll(filepath.Join(s.dir, "/sys/block/fakedevice0/fakedevice0p1"), 0755)
+ c.Assert(err, IsNil)
+}
+
+func (s *bootstrapSuite) TestDeviceFromRoleHappy(c *C) {
+ s.setupMockSysfs(c)
+ lv := layoutFromYaml(c, mockUC20GadgetYaml)
+
+ device, err := bootstrap.DeviceFromRole(lv, gadget.SystemSeed)
+ c.Assert(err, IsNil)
+ c.Check(device, Matches, ".*/dev/fakedevice0")
+}
+
+func (s *bootstrapSuite) TestDeviceFromRoleErrorNoMatchingSysfs(c *C) {
+ // note no sysfs mocking
+ lv := layoutFromYaml(c, mockUC20GadgetYaml)
+
+ _, err := bootstrap.DeviceFromRole(lv, gadget.SystemSeed)
+ c.Assert(err, ErrorMatches, `cannot find device for role "system-seed": device not found`)
+}
+
+func (s *bootstrapSuite) TestDeviceFromRoleErrorNoRole(c *C) {
+ s.setupMockSysfs(c)
+ lv := layoutFromYaml(c, mockGadgetYaml)
+
+ _, err := bootstrap.DeviceFromRole(lv, gadget.SystemSeed)
+ c.Assert(err, ErrorMatches, "cannot find role system-seed in gadget")
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/bootstrap/export_test.go snapd-2.45.1+18.04/cmd/snap-bootstrap/bootstrap/export_test.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/bootstrap/export_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/bootstrap/export_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,26 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+// +build !nosecboot
+
+/*
+ * 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 bootstrap
+
+var (
+ EnsureLayoutCompatibility = ensureLayoutCompatibility
+ DeviceFromRole = deviceFromRole
+)
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/bootstrap/options.go snapd-2.45.1+18.04/cmd/snap-bootstrap/bootstrap/options.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/bootstrap/options.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/bootstrap/options.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,45 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019-2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package bootstrap
+
+import (
+ "github.com/snapcore/snapd/asserts"
+)
+
+type Options struct {
+ // Also mount the filesystems after creation
+ Mount bool
+ // Encrypt the data partition
+ Encrypt bool
+ // KeyFile is the location where the encryption key is written to
+ KeyFile string
+ // RecoveryKeyFile is the location where the recovery key is written to
+ RecoveryKeyFile string
+ // TPMLockoutAuthFile is the location where the TPM lockout authorization is written to
+ TPMLockoutAuthFile string
+ // PolicyUpdateDataFile is the location where the authorization policy update data is written to
+ PolicyUpdateDataFile string
+ // KernelPath is the path to the kernel to seal the keyfile to
+ KernelPath string
+ // Model is the device model to seal the keyfile to
+ Model *asserts.Model
+ // SystemLabel is the recover system label to seal the keyfile to
+ SystemLabel string
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/cmd_create_partitions.go snapd-2.45.1+18.04/cmd/snap-bootstrap/cmd_create_partitions.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/cmd_create_partitions.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/cmd_create_partitions.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,102 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019-2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public 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"
+
+ "github.com/jessevdk/go-flags"
+
+ "github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/cmd/snap-bootstrap/bootstrap"
+)
+
+var bootstrapRun = bootstrap.Run
+
+func init() {
+ const (
+ short = "Create missing partitions for the device"
+ long = ""
+ )
+
+ addCommandBuilder(func(parser *flags.Parser) {
+ if _, err := parser.AddCommand("create-partitions", short, long, &cmdCreatePartitions{}); err != nil {
+ panic(err)
+ }
+ })
+}
+
+type cmdCreatePartitions struct {
+ Mount bool `short:"m" long:"mount" description:"Also mount filesystems after creation"`
+ Encrypt bool `long:"encrypt" description:"Encrypt the data partition"`
+ KeyFile string `long:"key-file" value-name:"filename" description:"Where the key file will be stored"`
+ RecoveryKeyFile string `long:"recovery-key-file" value-name:"filename" description:"Where the recovery key file will be stored"`
+ TPMLockoutAuthFile string `long:"tpm-lockout-auth" value-name:"filename" descrition:"Where the TPM lockout authorization data file will be stored"`
+ PolicyUpdateDataFile string `long:"policy-update-data-file" value-name:"filename" description:"Where the authorization policy update data file will be stored"`
+ KernelPath string `long:"kernel" value-name:"path" description:"Path to the kernel to be installed"`
+ ModelPath string `long:"model" value-name:"filename" description:"The model to seal the key file to"`
+
+ Positional struct {
+ GadgetRoot string `positional-arg-name:""`
+ Device string `positional-arg-name:""`
+ } `positional-args:"yes"`
+}
+
+func (c *cmdCreatePartitions) Execute(args []string) error {
+ var model *asserts.Model
+ if c.ModelPath != "" {
+ var err error
+ model, err = readModel(c.ModelPath)
+ if err != nil {
+ return fmt.Errorf("cannot load model: %v", err)
+ }
+ }
+
+ options := bootstrap.Options{
+ Mount: c.Mount,
+ Encrypt: c.Encrypt,
+ KeyFile: c.KeyFile,
+ RecoveryKeyFile: c.RecoveryKeyFile,
+ TPMLockoutAuthFile: c.TPMLockoutAuthFile,
+ PolicyUpdateDataFile: c.PolicyUpdateDataFile,
+ KernelPath: c.KernelPath,
+ Model: model,
+ }
+
+ return bootstrapRun(c.Positional.GadgetRoot, c.Positional.Device, options)
+}
+
+func readModel(modelPath string) (*asserts.Model, error) {
+ f, err := os.Open(modelPath)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ a, err := asserts.NewDecoder(f).Decode()
+ if err != nil {
+ return nil, fmt.Errorf("cannot decode assertion: %v", err)
+ }
+ if a.Type() != asserts.ModelType {
+ return nil, fmt.Errorf("not a model assertion")
+ }
+ return a.(*asserts.Model), nil
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/cmd_create_partitions_test.go snapd-2.45.1+18.04/cmd/snap-bootstrap/cmd_create_partitions_test.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/cmd_create_partitions_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/cmd_create_partitions_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,132 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019-2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public 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"
+
+ main "github.com/snapcore/snapd/cmd/snap-bootstrap"
+ "github.com/snapcore/snapd/cmd/snap-bootstrap/bootstrap"
+)
+
+func (s *cmdSuite) TestCreatePartitionsHappy(c *C) {
+ n := 0
+ restore := main.MockBootstrapRun(func(gadgetRoot, device string, opts bootstrap.Options) error {
+ c.Check(gadgetRoot, Equals, "gadget-dir")
+ c.Check(device, Equals, "device")
+ n++
+ return nil
+ })
+ defer restore()
+
+ rest, err := main.Parser().ParseArgs([]string{"create-partitions", "gadget-dir", "device"})
+ c.Assert(err, IsNil)
+ c.Assert(rest, HasLen, 0)
+ c.Assert(n, Equals, 1)
+}
+
+func (s *cmdSuite) TestCreatePartitionsMount(c *C) {
+ n := 0
+ restore := main.MockBootstrapRun(func(gadgetRoot, device string, opts bootstrap.Options) error {
+ c.Check(gadgetRoot, Equals, "gadget-dir")
+ c.Check(device, Equals, "device")
+ c.Check(opts.Mount, Equals, true)
+ n++
+ return nil
+ })
+ defer restore()
+
+ rest, err := main.Parser().ParseArgs([]string{"create-partitions", "--mount", "gadget-dir", "device"})
+ c.Assert(err, IsNil)
+ c.Assert(rest, HasLen, 0)
+ c.Assert(n, Equals, 1)
+}
+
+var testModel = `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
+ default-channel: 20
+ -
+ name: brand-gadget
+ id: brandgadgetdidididididididididid
+ type: gadget
+ -
+ name: other-base
+ id: otherbasedididididididididididid
+ type: base
+grade: secured
+body-length: 0
+timestamp: 2002-10-02T10:00:00-05:00
+sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij
+
+AXNpZw==`
+
+func (s *cmdSuite) TestCreatePartitionsWithEncryption(c *C) {
+ n := 0
+ restore := main.MockBootstrapRun(func(gadgetRoot, device string, opts bootstrap.Options) error {
+ c.Check(gadgetRoot, Equals, "gadget-dir")
+ c.Check(device, Equals, "device")
+ c.Check(opts.Encrypt, Equals, true)
+ c.Check(opts.KeyFile, Equals, "keyfile")
+ c.Check(opts.RecoveryKeyFile, Equals, "recovery")
+ c.Check(opts.TPMLockoutAuthFile, Equals, "lockout")
+ c.Check(opts.PolicyUpdateDataFile, Equals, "update")
+ c.Check(opts.KernelPath, Equals, "kernel")
+ c.Check(opts.Model.Model(), Equals, "baz-3000")
+ n++
+ return nil
+ })
+ defer restore()
+
+ mockModelPath := filepath.Join(c.MkDir(), "model")
+ err := ioutil.WriteFile(mockModelPath, []byte(testModel), 0644)
+ c.Assert(err, IsNil)
+
+ rest, err := main.Parser().ParseArgs([]string{
+ "create-partitions",
+ "--encrypt",
+ "--key-file", "keyfile",
+ "--recovery-key-file", "recovery",
+ "--tpm-lockout-auth", "lockout",
+ "--policy-update-data-file", "update",
+ "--kernel", "kernel",
+ "--model", mockModelPath,
+ "gadget-dir",
+ "device",
+ })
+ c.Assert(err, IsNil)
+ c.Assert(rest, HasLen, 0)
+ c.Assert(n, Equals, 1)
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/cmd_initramfs_mounts.go snapd-2.45.1+18.04/cmd/snap-bootstrap/cmd_initramfs_mounts.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/cmd_initramfs_mounts.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/cmd_initramfs_mounts.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,739 @@
+// -*- 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"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+ "syscall"
+
+ "github.com/jessevdk/go-flags"
+ // XXX: import as "to" sb to be consistent with snapcore/snapd/secboot
+ "github.com/snapcore/secboot"
+ "golang.org/x/xerrors"
+
+ "github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/boot"
+ "github.com/snapcore/snapd/bootloader"
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/logger"
+ "github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/sysconfig"
+)
+
+func init() {
+ const (
+ short = "Generate initramfs mount tuples"
+ long = "Generate mount tuples for the initramfs until nothing more can be done"
+ )
+
+ addCommandBuilder(func(parser *flags.Parser) {
+ if _, err := parser.AddCommand("initramfs-mounts", short, long, &cmdInitramfsMounts{}); err != nil {
+ panic(err)
+ }
+ })
+
+ snap.SanitizePlugsSlots = func(*snap.Info) {}
+}
+
+type cmdInitramfsMounts struct{}
+
+func (c *cmdInitramfsMounts) Execute(args []string) error {
+ return generateInitramfsMounts()
+}
+
+// XXX: move all var () to the top, it's very spread out right now
+var (
+ // Stdout - can be overridden in tests
+ stdout io.Writer = os.Stdout
+)
+
+var (
+ osutilIsMounted = osutil.IsMounted
+)
+
+var (
+ // for mocking by tests
+ devDiskByLabelDir = "/dev/disk/by-label"
+)
+
+// generateMountsMode* is called multiple times from initramfs until it
+// no longer generates more mount points and just returns an empty output.
+func generateMountsModeInstall(mst initramfsMountsState, recoverySystem string) error {
+ allMounted, err := generateMountsCommonInstallRecover(mst, recoverySystem)
+ if err != nil {
+ return err
+ }
+ if !allMounted {
+ return nil
+ }
+
+ // n+1: final step: write $(tmpfs-data)/var/lib/snapd/modeenv - this
+ // is the tmpfs we just created above
+ modeEnv := &boot.Modeenv{
+ Mode: "install",
+ RecoverySystem: recoverySystem,
+ }
+ if err := modeEnv.WriteTo(boot.InitramfsWritableDir); err != nil {
+ return err
+ }
+ // and disable cloud-init in install mode
+ if err := sysconfig.DisableCloudInit(boot.InitramfsWritableDir); err != nil {
+ return err
+ }
+
+ // n+3: done, no output, no error indicates to initramfs we are done
+ // with mounting stuff
+ return nil
+}
+
+// copyUbuntuDataAuth copies the authenication files like
+// - extrausers passwd,shadow etc
+// - sshd host configuration
+// - user .ssh dir
+// to the target directory. This is used to copy the authentication
+// data from a real uc20 ubuntu-data partition into a ephemeral one.
+func copyUbuntuDataAuth(src, dst string) error {
+ for _, globEx := range []string{
+ "system-data/var/lib/extrausers/*",
+ "system-data/etc/ssh/*",
+ "user-data/*/.ssh/*",
+ // this ensures we also get non-ssh enabled accounts copied
+ "user-data/*/.profile",
+ // so that users have proper perms, i.e. console-conf added users are
+ // sudoers
+ "system-data/etc/sudoers.d/*",
+ } {
+ matches, err := filepath.Glob(filepath.Join(src, globEx))
+ if err != nil {
+ return err
+ }
+ for _, p := range matches {
+ comps := strings.Split(strings.TrimPrefix(p, src), "/")
+ for i := range comps {
+ part := filepath.Join(comps[0 : i+1]...)
+ fi, err := os.Stat(filepath.Join(src, part))
+ if err != nil {
+ return err
+ }
+ if fi.IsDir() {
+ if err := os.Mkdir(filepath.Join(dst, part), fi.Mode()); err != nil && !os.IsExist(err) {
+ return err
+ }
+ st, ok := fi.Sys().(*syscall.Stat_t)
+ if !ok {
+ return fmt.Errorf("cannot get stat data: %v", err)
+ }
+ if err := os.Chown(filepath.Join(dst, part), int(st.Uid), int(st.Gid)); err != nil {
+ return err
+ }
+ } else {
+ if err := osutil.CopyFile(p, filepath.Join(dst, part), osutil.CopyFlagPreserveAll); err != nil {
+ return err
+ }
+ }
+ }
+ }
+ }
+
+ // ensure the user state is transferred as well
+ srcState := filepath.Join(src, "system-data/var/lib/snapd/state.json")
+ dstState := filepath.Join(dst, "system-data/var/lib/snapd/state.json")
+ err := state.CopyState(srcState, dstState, []string{"auth.users"})
+ if err != nil && err != state.ErrNoState {
+ return fmt.Errorf("cannot copy user state: %v", err)
+ }
+
+ return nil
+}
+
+func generateMountsModeRecover(mst initramfsMountsState, recoverySystem string) error {
+ allMounted, err := generateMountsCommonInstallRecover(mst, recoverySystem)
+ if err != nil {
+ return err
+ }
+ if !allMounted {
+ return nil
+ }
+
+ // n+1: mount ubuntu-data for recovery
+ isRecoverDataMounted, err := osutilIsMounted(boot.InitramfsHostUbuntuDataDir)
+ if err != nil {
+ return err
+ }
+ if !isRecoverDataMounted {
+ const lockKeysForLast = true
+ device, err := unlockIfEncrypted("ubuntu-data", lockKeysForLast)
+ if err != nil {
+ return err
+ }
+
+ fmt.Fprintf(stdout, "%s %s\n", device, boot.InitramfsHostUbuntuDataDir)
+ return nil
+ }
+
+ // now copy the auth data from the real ubuntu-data dir to the ephemeral
+ // ubuntu-data dir
+ if err := copyUbuntuDataAuth(boot.InitramfsHostUbuntuDataDir, boot.InitramfsDataDir); err != nil {
+ return err
+ }
+
+ // n+2: final step: write $(tmpfs-data)/var/lib/snapd/modeenv - this
+ // is the tmpfs we just created above
+ modeEnv := &boot.Modeenv{
+ Mode: "recover",
+ RecoverySystem: recoverySystem,
+ }
+ if err := modeEnv.WriteTo(boot.InitramfsWritableDir); err != nil {
+ return err
+ }
+ // and disable cloud-init in recover mode
+ if err := sysconfig.DisableCloudInit(boot.InitramfsWritableDir); err != nil {
+ return err
+ }
+
+ // n+3: done, no output, no error indicates to initramfs we are done
+ // with mounting stuff
+ return nil
+}
+
+func generateMountsCommonInstallRecover(mst initramfsMountsState, recoverySystem string) (allMounted bool, err error) {
+ // 1. always ensure seed partition is mounted
+ isMounted, err := osutilIsMounted(boot.InitramfsUbuntuSeedDir)
+ if err != nil {
+ return false, err
+ }
+ if !isMounted {
+ fmt.Fprintf(stdout, "/dev/disk/by-label/ubuntu-seed %s\n", boot.InitramfsUbuntuSeedDir)
+ return false, nil
+ }
+
+ // 1a. measure model
+ err = stampedAction(fmt.Sprintf("%s-model-measured", recoverySystem), func() error {
+ return measureWhenPossible(func(tpm *secboot.TPMConnection) error {
+ model, err := mst.Model()
+ if err != nil {
+ return err
+ }
+ return secMeasureModel(tpm, model)
+ })
+ })
+ if err != nil {
+ return false, err
+ }
+
+ // 2. (auto) select recovery system for now
+ isBaseMounted, err := osutilIsMounted(filepath.Join(boot.InitramfsRunMntDir, "base"))
+ if err != nil {
+ return false, err
+ }
+ isKernelMounted, err := osutilIsMounted(filepath.Join(boot.InitramfsRunMntDir, "kernel"))
+ if err != nil {
+ return false, err
+ }
+ isSnapdMounted, err := osutilIsMounted(filepath.Join(boot.InitramfsRunMntDir, "snapd"))
+ if err != nil {
+ return false, err
+ }
+ if !isBaseMounted || !isKernelMounted || !isSnapdMounted {
+ // load the recovery system and generate mounts for kernel/base
+ // and snapd
+ var whichTypes []snap.Type
+ if !isBaseMounted {
+ whichTypes = append(whichTypes, snap.TypeBase)
+ }
+ if !isKernelMounted {
+ whichTypes = append(whichTypes, snap.TypeKernel)
+ }
+ if !isSnapdMounted {
+ whichTypes = append(whichTypes, snap.TypeSnapd)
+ }
+ essSnaps, err := mst.RecoverySystemEssentialSnaps("", whichTypes)
+ if err != nil {
+ return false, fmt.Errorf("cannot load metadata and verify essential bootstrap snaps %v: %v", whichTypes, err)
+ }
+
+ // TODO:UC20: do we need more cross checks here?
+ for _, essentialSnap := range essSnaps {
+ switch essentialSnap.EssentialType {
+ case snap.TypeBase:
+ fmt.Fprintf(stdout, "%s %s\n", essentialSnap.Path, filepath.Join(boot.InitramfsRunMntDir, "base"))
+ case snap.TypeKernel:
+ // TODO:UC20: we need to cross-check the kernel path with snapd_recovery_kernel used by grub
+ fmt.Fprintf(stdout, "%s %s\n", essentialSnap.Path, filepath.Join(boot.InitramfsRunMntDir, "kernel"))
+ case snap.TypeSnapd:
+ fmt.Fprintf(stdout, "%s %s\n", essentialSnap.Path, filepath.Join(boot.InitramfsRunMntDir, "snapd"))
+ }
+ }
+ }
+
+ // 3. the ephemeral data partition
+ isMounted, err = osutilIsMounted(boot.InitramfsDataDir)
+ if err != nil {
+ return false, err
+ }
+ if !isMounted {
+ fmt.Fprintf(stdout, "--type=tmpfs tmpfs %s\n", boot.InitramfsDataDir)
+ return false, nil
+ }
+
+ return true, nil
+}
+
+// TODO:UC20 move some of these helpers somehow to our secboot
+
+const tpmPCR = 12
+
+func secMeasureEpoch(tpm *secboot.TPMConnection) error {
+ if err := secbootMeasureSnapSystemEpochToTPM(tpm, tpmPCR); err != nil {
+ return fmt.Errorf("cannot measure snap system epoch: %v", err)
+ }
+ return nil
+}
+
+func secMeasureModel(tpm *secboot.TPMConnection, model *asserts.Model) error {
+ if err := secbootMeasureSnapModelToTPM(tpm, tpmPCR, model); err != nil {
+ return fmt.Errorf("cannot measure snap system model: %v", err)
+ }
+ return nil
+}
+
+func measureWhenPossible(whatHow func(tpm *secboot.TPMConnection) error) error {
+ // the model is ready, we're good to try measuring it now
+ tpm, err := insecureConnectToTPM()
+ if err != nil {
+ var perr *os.PathError
+ // XXX: xerrors.Is() does not work with PathErrors?
+ if xerrors.As(err, &perr) {
+ // no TPM
+ return nil
+ }
+ return fmt.Errorf("cannot open TPM connection: %v", err)
+ }
+ defer tpm.Close()
+
+ return whatHow(tpm)
+}
+
+func generateMountsModeRun(mst initramfsMountsState) error {
+ // 1.1 always ensure basic partitions are mounted
+ for _, d := range []string{boot.InitramfsUbuntuBootDir, boot.InitramfsUbuntuSeedDir} {
+ isMounted, err := osutilIsMounted(d)
+ if err != nil {
+ return err
+ }
+ if !isMounted {
+ // we need ubuntu-seed to be mounted before we can continue to
+ // check ubuntu-data, so return if we need something mounted
+ fmt.Fprintf(stdout, "/dev/disk/by-label/%s %s\n", filepath.Base(d), d)
+ return nil
+ }
+ }
+
+ // 1.1a measure model
+ err := stampedAction("run-model-measured", func() error {
+ return measureWhenPossible(func(tpm *secboot.TPMConnection) error {
+ model, err := mst.UnverifiedBootModel()
+ if err != nil {
+ return err
+ }
+ return secMeasureModel(tpm, model)
+ })
+ })
+ if err != nil {
+ return err
+ }
+ // TODO:UC20: cross check the model we read from ubuntu-boot/model with
+ // one recorded in ubuntu-data modeenv during install
+
+ // 1.2 mount Data, and exit, as it needs to be mounted for us to do step 2
+ isDataMounted, err := osutilIsMounted(boot.InitramfsDataDir)
+ if err != nil {
+ return err
+ }
+ if !isDataMounted {
+ const lockKeysForLast = true
+ device, err := unlockIfEncrypted("ubuntu-data", lockKeysForLast)
+ if err != nil {
+ return err
+ }
+
+ fmt.Fprintf(stdout, "%s %s\n", device, boot.InitramfsDataDir)
+ return nil
+ }
+
+ // 2.1 read modeenv
+ modeEnv, err := boot.ReadModeenv(boot.InitramfsWritableDir)
+ if err != nil {
+ return err
+ }
+
+ // 2.2.1 check if base is mounted
+ isBaseMounted, err := osutilIsMounted(filepath.Join(boot.InitramfsRunMntDir, "base"))
+ if err != nil {
+ return err
+ }
+ if !isBaseMounted {
+ // 2.2.2 use modeenv base_status and try_base to see if we are trying
+ // an update to the base snap
+ base := modeEnv.Base
+ if base == "" {
+ // we have no fallback base!
+ return fmt.Errorf("modeenv corrupt: missing base setting")
+ }
+ if modeEnv.BaseStatus == boot.TryStatus {
+ // then we are trying a base snap update and there should be a
+ // try_base set in the modeenv too
+ if modeEnv.TryBase != "" {
+ // check that the TryBase exists in ubuntu-data
+ tryBaseSnapPath := filepath.Join(dirs.SnapBlobDirUnder(boot.InitramfsWritableDir), modeEnv.TryBase)
+ if osutil.FileExists(tryBaseSnapPath) {
+ // set the TryBase and have the initramfs mount this base
+ // snap
+ modeEnv.BaseStatus = boot.TryingStatus
+ base = modeEnv.TryBase
+ } else {
+ logger.Noticef("try-base snap %q does not exist", modeEnv.TryBase)
+ }
+ } else {
+ logger.Noticef("try-base snap is empty, but \"base_status\" is \"trying\"")
+ }
+ // TODO:UC20: log a message if try_base is unset here?
+ } else if modeEnv.BaseStatus == boot.TryingStatus {
+ // snapd failed to start with the base snap update, so we need to
+ // fallback to the old base snap and clear base_status
+ modeEnv.BaseStatus = boot.DefaultStatus
+ } else if modeEnv.BaseStatus != boot.DefaultStatus {
+ logger.Noticef("\"base_status\" has an invalid setting: %q", modeEnv.BaseStatus)
+ }
+
+ baseSnapPath := filepath.Join(dirs.SnapBlobDirUnder(boot.InitramfsWritableDir), base)
+ fmt.Fprintf(stdout, "%s %s\n", baseSnapPath, filepath.Join(boot.InitramfsRunMntDir, "base"))
+ }
+
+ // 2.3.1 check if the kernel is mounted
+ isKernelMounted, err := osutilIsMounted(filepath.Join(boot.InitramfsRunMntDir, "kernel"))
+ if err != nil {
+ return err
+ }
+ if !isKernelMounted {
+ // make a map to easily check if a kernel snap is valid or not
+ validKernels := make(map[string]bool, len(modeEnv.CurrentKernels))
+ for _, validKernel := range modeEnv.CurrentKernels {
+ validKernels[validKernel] = true
+ }
+
+ // find ubuntu-boot bootloader to get the kernel_status and kernel.efi
+ // status so we can determine the right kernel snap to have mounted
+
+ // TODO:UC20: should all this logic move to boot package? feels awfully
+ // similar to the logic in revisions() for bootState20
+
+ // At this point the run mode bootloader is under the native
+ // layout, no /boot mount.
+ opts := &bootloader.Options{NoSlashBoot: true}
+ bl, err := bootloader.Find(boot.InitramfsUbuntuBootDir, opts)
+ if err != nil {
+ return fmt.Errorf("internal error: cannot find run system bootloader: %v", err)
+ }
+
+ var kern, tryKern snap.PlaceInfo
+ var kernStatus string
+
+ ebl, ok := bl.(bootloader.ExtractedRunKernelImageBootloader)
+ if ok {
+ // use ebl methods
+ kern, err = ebl.Kernel()
+ if err != nil {
+ return fmt.Errorf("no fallback kernel snap: %v", err)
+ }
+
+ tryKern, err = ebl.TryKernel()
+ if err != nil && err != bootloader.ErrNoTryKernelRef {
+ return err
+ }
+
+ m, err := ebl.GetBootVars("kernel_status")
+ if err != nil {
+ return fmt.Errorf("cannot get kernel_status from bootloader %s", ebl.Name())
+ }
+
+ kernStatus = m["kernel_status"]
+ } else {
+ // use the bootenv
+ m, err := bl.GetBootVars("snap_kernel", "snap_try_kernel", "kernel_status")
+ if err != nil {
+ return err
+ }
+ kern, err = snap.ParsePlaceInfoFromSnapFileName(m["snap_kernel"])
+ if err != nil {
+ return fmt.Errorf("no fallback kernel snap: %v", err)
+ }
+
+ // only try to parse snap_try_kernel if it is set
+ if m["snap_try_kernel"] != "" {
+ tryKern, err = snap.ParsePlaceInfoFromSnapFileName(m["snap_try_kernel"])
+ if err != nil {
+ logger.Noticef("try-kernel setting is invalid: %v", err)
+ }
+ }
+
+ kernStatus = m["kernel_status"]
+ }
+
+ kernelFile := kern.Filename()
+ if !validKernels[kernelFile] {
+ // we don't trust the fallback kernel!
+ return fmt.Errorf("fallback kernel snap %q is not trusted in the modeenv", kernelFile)
+ }
+
+ if kernStatus == boot.TryingStatus {
+ // check for the try kernel
+ if tryKern != nil {
+ tryKernelFile := tryKern.Filename()
+ if validKernels[tryKernelFile] {
+ kernelFile = tryKernelFile
+ } else {
+ logger.Noticef("try-kernel %q is not trusted in the modeenv", tryKernelFile)
+ }
+ } else {
+ logger.Noticef("missing try-kernel, even though \"kernel_status\" is \"trying\"")
+ }
+
+ // TODO:UC20: actually we really shouldn't be falling back here at
+ // all - if the kernel we booted isn't mountable in the
+ // initramfs, we should trigger a reboot so that we boot
+ // the fallback kernel and then mount that one
+ }
+
+ kernelPath := filepath.Join(dirs.SnapBlobDirUnder(boot.InitramfsWritableDir), kernelFile)
+ fmt.Fprintf(stdout, "%s %s\n", kernelPath, filepath.Join(boot.InitramfsRunMntDir, "kernel"))
+ }
+
+ // 3.1 Maybe mount the snapd snap on first boot of run-mode
+ // TODO:UC20: Make RecoverySystem empty after successful first boot
+ // somewhere in devicestate
+ if modeEnv.RecoverySystem != "" {
+ isSnapdMounted, err := osutilIsMounted(filepath.Join(boot.InitramfsRunMntDir, "snapd"))
+ if err != nil {
+ return err
+ }
+
+ if !isSnapdMounted {
+ // load the recovery system and generate mount for snapd
+ essSnaps, err := mst.RecoverySystemEssentialSnaps(modeEnv.RecoverySystem, []snap.Type{snap.TypeSnapd})
+ if err != nil {
+ return fmt.Errorf("cannot load metadata and verify snapd snap: %v", err)
+ }
+ fmt.Fprintf(stdout, "%s %s\n", essSnaps[0].Path, filepath.Join(boot.InitramfsRunMntDir, "snapd"))
+ }
+ }
+
+ // 4.1 Write the modeenv out again
+ return modeEnv.Write()
+}
+
+func stampedAction(stamp string, action func() error) error {
+ stampFile := filepath.Join(dirs.SnapBootstrapRunDir, stamp)
+ if osutil.FileExists(stampFile) {
+ return nil
+ }
+ if err := os.MkdirAll(filepath.Dir(stampFile), 0755); err != nil {
+ return err
+ }
+ if err := action(); err != nil {
+ return err
+ }
+ return ioutil.WriteFile(stampFile, nil, 0644)
+}
+
+func generateInitramfsMounts() error {
+ // Ensure there is a very early initial measurement
+ err := stampedAction("secboot-epoch-measured", func() error {
+ return measureWhenPossible(secMeasureEpoch)
+ })
+ if err != nil {
+ return err
+ }
+
+ mode, recoverySystem, err := boot.ModeAndRecoverySystemFromKernelCommandLine()
+ if err != nil {
+ return err
+ }
+
+ mst := newInitramfsMountsState(mode, recoverySystem)
+
+ switch mode {
+ case "recover":
+ // XXX: don't pass both args
+ return generateMountsModeRecover(mst, recoverySystem)
+ case "install":
+ // XXX: don't pass both args
+ return generateMountsModeInstall(mst, recoverySystem)
+ case "run":
+ return generateMountsModeRun(mst)
+ }
+ // this should never be reached
+ return fmt.Errorf("internal error: mode in generateInitramfsMounts not handled")
+}
+
+var (
+ secbootLockAccessToSealedKeys = secboot.LockAccessToSealedKeys
+)
+
+func isDeviceEncrypted(name string) (ok bool, encdev string) {
+ encdev = filepath.Join(devDiskByLabelDir, name+"-enc")
+ if osutil.FileExists(encdev) {
+ return true, encdev
+ }
+ return false, ""
+}
+
+// TODO:UC20: move this somewhere appropriate
+var readKernelUUID = func() string {
+ b, err := ioutil.ReadFile("/proc/sys/kernel/random/uuid")
+ if err != nil {
+ panic("can't read kernel uuid")
+ }
+ return strings.TrimSpace(string(b))
+}
+
+// unlockIfEncrypted verifies whether an encrypted volume with the specified
+// name exists and unlocks it. With lockKeysOnFinish set, access to the sealed
+// keys will be locked when this function completes. The path to the unencrypted
+// device node is returned.
+func unlockIfEncrypted(name string, lockKeysOnFinish bool) (string, error) {
+ // TODO:UC20: use secureConnectToTPM if we decide there's benefit in doing that or we
+ // have a hard requirement for a valid EK cert chain for every boot (ie, panic
+ // if there isn't one). But we can't do that as long as we need to download
+ // intermediate certs from the manufacturer.
+ tpm, tpmErr := insecureConnectToTPM()
+ if tpmErr != nil {
+ logger.Noticef("cannot open TPM connection: %v", tpmErr)
+ } else {
+ defer tpm.Close()
+ }
+
+ var lockErr error
+ var mapperName string
+ err := func() error {
+ defer func() {
+ // TODO:UC20: we might want some better error handling here - eg, if tpmErr is a
+ // *os.PathError returned from go-tpm2 then this is an indicator that there
+ // is no TPM device. But other errors probably shouldn't be ignored.
+ if lockKeysOnFinish && tpmErr == nil {
+ // Lock access to the sealed keys. This should be called whenever there
+ // is a TPM device detected, regardless of whether secure boot is enabled
+ // or there is an encrypted volume to unlock. Note that snap-bootstrap can
+ // be called several times during initialization, and if there are multiple
+ // volumes to unlock we should lock access to the sealed keys only after
+ // the last encrypted volume is unlocked, in which case lockKeysOnFinish
+ // should be set to true.
+ lockErr = secbootLockAccessToSealedKeys(tpm)
+ }
+ }()
+
+ ok, encdev := isDeviceEncrypted(name)
+ if !ok {
+ return nil
+ }
+
+ if tpmErr != nil {
+ return fmt.Errorf("cannot unlock encrypted device %q: %v", name, tpmErr)
+ }
+ // TODO:UC20: snap-bootstrap should validate that -enc is what
+ // we expect (and not e.g. an external disk), and also that
+ // is from -enc and not an unencrypted partition
+ // with the same name (LP #1863886)
+ sealedKeyPath := filepath.Join(boot.InitramfsEncryptionKeyDir, name+".sealed-key")
+ mapperName = name + "-" + readKernelUUID()
+ return unlockEncryptedPartition(tpm, mapperName, encdev, sealedKeyPath, "", lockKeysOnFinish)
+ }()
+ if err != nil {
+ return "", err
+ }
+ if lockErr != nil {
+ return "", fmt.Errorf("cannot lock access to sealed keys: %v", lockErr)
+ }
+
+ // return the encrypted device if the device we are maybe unlocked is an
+ // encrypted device
+ if mapperName != "" {
+ return filepath.Join("/dev/mapper", mapperName), nil
+ }
+
+ // otherwise use the device from /dev/disk/by-label
+ // TODO:UC20: we want to always determine the ubuntu-data partition by
+ // referencing the ubuntu-boot or ubuntu-seed partitions and not
+ // by using labels
+ return filepath.Join(devDiskByLabelDir, name), nil
+}
+
+var (
+ secbootConnectToDefaultTPM = secboot.ConnectToDefaultTPM
+ secbootSecureConnectToDefaultTPM = secboot.SecureConnectToDefaultTPM
+ secbootActivateVolumeWithTPMSealedKey = secboot.ActivateVolumeWithTPMSealedKey
+ secbootMeasureSnapModelToTPM = secboot.MeasureSnapModelToTPM
+ secbootMeasureSnapSystemEpochToTPM = secboot.MeasureSnapSystemEpochToTPM
+)
+
+// TODO:UC20 move the connect methods somehow to our secboot
+
+func secureConnectToTPM(ekcfile string) (*secboot.TPMConnection, error) {
+ ekCertReader, err := os.Open(ekcfile)
+ if err != nil {
+ return nil, fmt.Errorf("cannot open endorsement key certificate file: %v", err)
+ }
+ defer ekCertReader.Close()
+
+ return secbootSecureConnectToDefaultTPM(ekCertReader, nil)
+}
+
+func insecureConnectToTPM() (*secboot.TPMConnection, error) {
+ return secbootConnectToDefaultTPM()
+}
+
+// unlockEncryptedPartition unseals the keyfile and opens an encrypted device.
+func unlockEncryptedPartition(tpm *secboot.TPMConnection, name, device, keyfile, pinfile string, lock bool) error {
+ options := secboot.ActivateWithTPMSealedKeyOptions{
+ PINTries: 1,
+ RecoveryKeyTries: 3,
+ LockSealedKeyAccess: lock,
+ }
+
+ activated, err := secbootActivateVolumeWithTPMSealedKey(tpm, name, device, keyfile, nil, &options)
+ if !activated {
+ // ActivateVolumeWithTPMSealedKey should always return an error if activated == false
+ return fmt.Errorf("cannot activate encrypted device %q: %v", device, err)
+ }
+ if err != nil {
+ logger.Noticef("successfully activated encrypted device %q using a fallback activation method", device)
+ } else {
+ logger.Noticef("successfully activated encrypted device %q with TPM", device)
+ }
+
+ return nil
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go snapd-2.45.1+18.04/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,1583 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2019-2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package main_test
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "syscall"
+
+ "github.com/canonical/go-tpm2"
+ "github.com/snapcore/secboot"
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/asserts/assertstest"
+ "github.com/snapcore/snapd/boot"
+ "github.com/snapcore/snapd/boot/boottest"
+ "github.com/snapcore/snapd/bootloader"
+ "github.com/snapcore/snapd/bootloader/bootloadertest"
+ main "github.com/snapcore/snapd/cmd/snap-bootstrap"
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/logger"
+ "github.com/snapcore/snapd/seed"
+ "github.com/snapcore/snapd/seed/seedtest"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/testutil"
+)
+
+var brandPrivKey, _ = assertstest.GenerateKey(752)
+
+type initramfsMountsSuite struct {
+ testutil.BaseTest
+
+ // makes available a bunch of helper (like MakeAssertedSnap)
+ *seedtest.TestingSeed20
+
+ Stdout *bytes.Buffer
+
+ seedDir string
+ sysLabel string
+ model *asserts.Model
+
+ mockTPM *secboot.TPMConnection
+}
+
+var _ = Suite(&initramfsMountsSuite{})
+
+// because 1.9 vet does not like xerrors.Errorf(".. %w")
+type mockedWrappedError struct {
+ err error
+ fmt string
+}
+
+func (m *mockedWrappedError) Unwrap() error { return m.err }
+
+func (m *mockedWrappedError) Error() string { return fmt.Sprintf(m.fmt, m.err) }
+
+func (s *initramfsMountsSuite) SetUpTest(c *C) {
+ s.BaseTest.SetUpTest(c)
+
+ s.Stdout = bytes.NewBuffer(nil)
+ restore := main.MockStdout(s.Stdout)
+ s.AddCleanup(restore)
+
+ _, restore = logger.MockLogger()
+ s.AddCleanup(restore)
+
+ // mock /run/mnt
+ dirs.SetRootDir(c.MkDir())
+ restore = func() { dirs.SetRootDir("") }
+ s.AddCleanup(restore)
+
+ // pretend /run/mnt/ubuntu-seed has a valid seed
+ s.seedDir = boot.InitramfsUbuntuSeedDir
+
+ // now create a minimal uc20 seed dir with snaps/assertions
+ seed20 := &seedtest.TestingSeed20{SeedDir: s.seedDir}
+ seed20.SetupAssertSigning("canonical")
+ restore = seed.MockTrusted(seed20.StoreSigning.Trusted)
+ s.AddCleanup(restore)
+
+ // XXX: we don't really use this but seedtest always expects my-brand
+ seed20.Brands.Register("my-brand", brandPrivKey, map[string]interface{}{
+ "verification": "verified",
+ })
+
+ // add a bunch of snaps
+ seed20.MakeAssertedSnap(c, "name: snapd\nversion: 1\ntype: snapd", nil, snap.R(1), "canonical", seed20.StoreSigning.Database)
+ seed20.MakeAssertedSnap(c, "name: pc\nversion: 1\ntype: gadget\nbase: core20", nil, snap.R(1), "canonical", seed20.StoreSigning.Database)
+ seed20.MakeAssertedSnap(c, "name: pc-kernel\nversion: 1\ntype: kernel", nil, snap.R(1), "canonical", seed20.StoreSigning.Database)
+ seed20.MakeAssertedSnap(c, "name: core20\nversion: 1\ntype: base", nil, snap.R(1), "canonical", seed20.StoreSigning.Database)
+
+ s.sysLabel = "20191118"
+ s.model = seed20.MakeSeed(c, s.sysLabel, "my-brand", "my-model", map[string]interface{}{
+ "display-name": "my model",
+ "architecture": "amd64",
+ "base": "core20",
+ "snaps": []interface{}{
+ map[string]interface{}{
+ "name": "pc-kernel",
+ "id": seed20.AssertedSnapID("pc-kernel"),
+ "type": "kernel",
+ "default-channel": "20",
+ },
+ map[string]interface{}{
+ "name": "pc",
+ "id": seed20.AssertedSnapID("pc"),
+ "type": "gadget",
+ "default-channel": "20",
+ }},
+ }, nil)
+
+ mockTPM, restoreTPM := mockSecbootTPM(c)
+ s.AddCleanup(restoreTPM)
+ s.mockTPM = mockTPM
+
+ restoreConnect := main.MockSecbootConnectToDefaultTPM(func() (*secboot.TPMConnection, error) {
+ // XXX: we should use xerrors.Errorf("no tpm: %w", &os.PathError{})
+ // but 1.9 vet complains about unknown verb %w
+ return nil, &mockedWrappedError{
+ fmt: "no tpm: %v",
+ err: &os.PathError{
+ Op: "open", Path: "/dev/mock/tpm0", Err: syscall.ENOENT,
+ },
+ }
+ })
+ s.AddCleanup(restoreConnect)
+}
+
+func (s *initramfsMountsSuite) mockProcCmdlineContent(c *C, newContent string) {
+ mockProcCmdline := filepath.Join(c.MkDir(), "proc-cmdline")
+ err := ioutil.WriteFile(mockProcCmdline, []byte(newContent), 0644)
+ c.Assert(err, IsNil)
+ restore := boot.MockProcCmdline(mockProcCmdline)
+ s.AddCleanup(restore)
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsNoModeError(c *C) {
+ s.mockProcCmdlineContent(c, "nothing-to-see")
+
+ _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, ErrorMatches, "cannot detect mode nor recovery system to use")
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsUnknownMode(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=install-foo")
+
+ _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, ErrorMatches, `cannot use unknown mode "install-foo"`)
+}
+
+// these types represent lists of expected mount directories to be
+// checked with IsMounted with an associated mounted state to simulate
+type (
+ expectedMountDirs interface {
+ size() int
+ // dirAndIsMounted returns the dir expected for the
+ // IsMounted call with relative call number callNum
+ // plus the simulated mounted state
+ dirAndIsMounted(callNum int) (dir string, mounted bool)
+ }
+ mounted []string
+ notYetMounted []string
+)
+
+func (m mounted) size() int { return len(m) }
+func (m mounted) dirAndIsMounted(callNum int) (dir string, state bool) { return m[callNum], true }
+
+func (n notYetMounted) size() int { return len(n) }
+func (n notYetMounted) dirAndIsMounted(callNum int) (dir string, state bool) {
+ return n[callNum], false
+}
+
+func (s *initramfsMountsSuite) mockExpectedMountChecks(c *C, expectedDirs ...expectedMountDirs) *int {
+ var n int // call counter
+ r := main.MockOsutilIsMounted(func(path string) (bool, error) {
+ callNum := n
+ n++
+ // find expected covering callNum
+ for _, expected := range expectedDirs {
+ // is callNum within expected?
+ if callNum < expected.size() {
+ dir, mounted := expected.dirAndIsMounted(callNum)
+ c.Check(path, Equals, dir)
+ return mounted, nil
+ }
+ // adjust callNum for indexing within the next expected
+ callNum -= expected.size()
+ }
+ return false, fmt.Errorf("unexpected number of calls: %v", n)
+ })
+ s.AddCleanup(r)
+ return &n
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeStep1(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=install snapd_recovery_system="+s.sysLabel)
+
+ n := s.mockExpectedMountChecks(c,
+ notYetMounted{boot.InitramfsUbuntuSeedDir},
+ )
+
+ _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, IsNil)
+ c.Assert(*n, Equals, 1)
+ c.Check(s.Stdout.String(), Equals, fmt.Sprintf("/dev/disk/by-label/ubuntu-seed %s/ubuntu-seed\n", boot.InitramfsRunMntDir))
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeStep2(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=install snapd_recovery_system="+s.sysLabel)
+
+ n := s.mockExpectedMountChecks(c,
+ mounted{boot.InitramfsUbuntuSeedDir},
+ notYetMounted{
+ filepath.Join(boot.InitramfsRunMntDir, "base"),
+ filepath.Join(boot.InitramfsRunMntDir, "kernel"),
+ filepath.Join(boot.InitramfsRunMntDir, "snapd"),
+ boot.InitramfsDataDir,
+ },
+ )
+
+ _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, IsNil)
+ c.Assert(*n, Equals, 5)
+ c.Check(s.Stdout.String(), Equals, fmt.Sprintf(`%[1]s/snaps/snapd_1.snap %[2]s/snapd
+%[1]s/snaps/pc-kernel_1.snap %[2]s/kernel
+%[1]s/snaps/core20_1.snap %[2]s/base
+--type=tmpfs tmpfs %[2]s/data
+`, s.seedDir, boot.InitramfsRunMntDir))
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeStep4(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=install snapd_recovery_system="+s.sysLabel)
+
+ n := s.mockExpectedMountChecks(c,
+ mounted{boot.InitramfsUbuntuSeedDir,
+ filepath.Join(boot.InitramfsRunMntDir, "base"),
+ filepath.Join(boot.InitramfsRunMntDir, "kernel"),
+ filepath.Join(boot.InitramfsRunMntDir, "snapd"),
+ boot.InitramfsDataDir,
+ },
+ )
+
+ _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, IsNil)
+ c.Assert(*n, Equals, 5)
+ c.Check(s.Stdout.String(), Equals, "")
+ modeEnv := dirs.SnapModeenvFileUnder(boot.InitramfsWritableDir)
+ c.Check(modeEnv, testutil.FileEquals, `mode=install
+recovery_system=20191118
+`)
+ cloudInitDisable := filepath.Join(boot.InitramfsWritableDir, "etc/cloud/cloud-init.disabled")
+ c.Check(cloudInitDisable, testutil.FilePresent)
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsRunModeStep1Boot(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
+
+ n := s.mockExpectedMountChecks(c,
+ notYetMounted{boot.InitramfsUbuntuBootDir},
+ )
+
+ _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, IsNil)
+ c.Assert(*n, Equals, 1)
+ c.Check(s.Stdout.String(), Equals, fmt.Sprintf(`/dev/disk/by-label/ubuntu-boot %[1]s/ubuntu-boot
+`, boot.InitramfsRunMntDir))
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsRunModeStep1Seed(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
+
+ n := s.mockExpectedMountChecks(c,
+ mounted{boot.InitramfsUbuntuBootDir},
+ notYetMounted{boot.InitramfsUbuntuSeedDir},
+ )
+
+ _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, IsNil)
+ c.Assert(*n, Equals, 2)
+ c.Check(s.Stdout.String(), Equals, fmt.Sprintf(`/dev/disk/by-label/ubuntu-seed %[1]s/ubuntu-seed
+`, boot.InitramfsRunMntDir))
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsRunModeStep1Data(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
+
+ n := s.mockExpectedMountChecks(c,
+ mounted{
+ boot.InitramfsUbuntuBootDir,
+ boot.InitramfsUbuntuSeedDir,
+ },
+ notYetMounted{boot.InitramfsDataDir},
+ )
+
+ _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, IsNil)
+ c.Assert(*n, Equals, 3)
+ c.Check(s.Stdout.String(), Equals, fmt.Sprintf(`/dev/disk/by-label/ubuntu-data %[1]s/data
+`, boot.InitramfsRunMntDir))
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsRunModeStep1EncryptedData(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
+
+ // write the installed model like makebootable does it
+ err := os.MkdirAll(boot.InitramfsUbuntuBootDir, 0755)
+ c.Assert(err, IsNil)
+ mf, err := os.Create(filepath.Join(boot.InitramfsUbuntuBootDir, "model"))
+ c.Assert(err, IsNil)
+ defer mf.Close()
+ err = asserts.NewEncoder(mf).Encode(s.model)
+ c.Assert(err, IsNil)
+
+ // setup ubuntu-data-enc
+ devDiskByLabel, restore := mockDevDiskByLabel(c)
+ defer restore()
+
+ ubuntuDataEnc := filepath.Join(devDiskByLabel, "ubuntu-data-enc")
+ err = ioutil.WriteFile(ubuntuDataEnc, nil, 0644)
+ c.Assert(err, IsNil)
+
+ restore = main.MockRandomKernelUUID(func() string {
+ return "some-kernel-uuid"
+ })
+ defer restore()
+
+ // setup a fake tpm
+ mockTPM, restore := mockSecbootTPM(c)
+ defer restore()
+
+ activated := false
+ // setup activating the fake tpm
+ restore = main.MockSecbootActivateVolumeWithTPMSealedKey(func(tpm *secboot.TPMConnection, volumeName, sourceDevicePath,
+ keyPath string, pinReader io.Reader, options *secboot.ActivateWithTPMSealedKeyOptions) (bool, error) {
+ c.Assert(tpm, Equals, mockTPM)
+ c.Assert(volumeName, Equals, "ubuntu-data-some-kernel-uuid")
+ c.Assert(sourceDevicePath, Equals, ubuntuDataEnc)
+ // the keyfile will be on ubuntu-seed as ubuntu-data.sealed-key
+ c.Assert(keyPath, Equals, filepath.Join(boot.InitramfsUbuntuSeedDir, "device/fde", "ubuntu-data.sealed-key"))
+ c.Assert(*options, DeepEquals, secboot.ActivateWithTPMSealedKeyOptions{
+ PINTries: 1,
+ RecoveryKeyTries: 3,
+ LockSealedKeyAccess: true,
+ })
+ activated = true
+ return true, nil
+ })
+ defer restore()
+
+ n := s.mockExpectedMountChecks(c,
+ mounted{
+ boot.InitramfsUbuntuBootDir,
+ boot.InitramfsUbuntuSeedDir,
+ },
+ notYetMounted{boot.InitramfsDataDir},
+ )
+
+ sealedKeysLocked := false
+ restore = main.MockSecbootLockAccessToSealedKeys(func(tpm *secboot.TPMConnection) error {
+ sealedKeysLocked = true
+ return nil
+ })
+ defer restore()
+
+ epochPCR := -1
+ modelPCR := -1
+ restore = main.MockSecbootMeasureSnapSystemEpochToTPM(func(tpm *secboot.TPMConnection, pcrIndex int) error {
+ epochPCR = pcrIndex
+ return nil
+ })
+ defer restore()
+
+ var measuredModel *asserts.Model
+ restore = main.MockSecbootMeasureSnapModelToTPM(func(tpm *secboot.TPMConnection, pcrIndex int, model *asserts.Model) error {
+ modelPCR = pcrIndex
+ measuredModel = model
+ return nil
+ })
+ defer restore()
+
+ _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, IsNil)
+ c.Check(*n, Equals, 3)
+ c.Check(s.Stdout.String(), Equals, fmt.Sprintf(`/dev/mapper/ubuntu-data-some-kernel-uuid %[1]s/data
+`, boot.InitramfsRunMntDir))
+ c.Check(activated, Equals, true)
+ c.Check(sealedKeysLocked, Equals, true)
+ c.Check(epochPCR, Equals, 12)
+ c.Check(modelPCR, Equals, 12)
+ c.Check(measuredModel, NotNil)
+ c.Check(measuredModel, DeepEquals, s.model)
+
+ c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent)
+ c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "run-model-measured"), testutil.FilePresent)
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsStep1EncryptedNoModelRun(c *C) {
+ s.testInitramfsMountsStep1EncryptedNoModel(c, "run", "")
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsStep1EncryptedNoModelInstall(c *C) {
+ s.testInitramfsMountsStep1EncryptedNoModel(c, "install", s.sysLabel)
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsStep1EncryptedNoModelRecovery(c *C) {
+ s.testInitramfsMountsStep1EncryptedNoModel(c, "recover", s.sysLabel)
+}
+
+func (s *initramfsMountsSuite) testInitramfsMountsStep1EncryptedNoModel(c *C, mode, label string) {
+ s.mockProcCmdlineContent(c, fmt.Sprintf("snapd_recovery_mode=%s", mode))
+ if label != "" {
+ s.mockProcCmdlineContent(c,
+ fmt.Sprintf("snapd_recovery_mode=%s snapd_recovery_system=%s", mode, label))
+ // break the seed
+ err := os.Remove(filepath.Join(s.seedDir, "systems", label, "model"))
+ c.Assert(err, IsNil)
+ }
+
+ // setup ubuntu-data-enc
+ devDiskByLabel, restore := mockDevDiskByLabel(c)
+ defer restore()
+
+ ubuntuDataEnc := filepath.Join(devDiskByLabel, "ubuntu-data-enc")
+ err := ioutil.WriteFile(ubuntuDataEnc, nil, 0644)
+ c.Assert(err, IsNil)
+
+ restore = main.MockOsutilIsMounted(func(path string) (bool, error) {
+ return true, nil
+ })
+ defer restore()
+ restore = main.MockSecbootConnectToDefaultTPM(func() (*secboot.TPMConnection, error) {
+ return s.mockTPM, nil
+ })
+ defer restore()
+
+ restore = main.MockSecbootLockAccessToSealedKeys(func(tpm *secboot.TPMConnection) error {
+ return fmt.Errorf("unexpected call")
+ })
+ defer restore()
+ measureEpochCalls := 0
+ restore = main.MockSecbootMeasureSnapSystemEpochToTPM(func(tpm *secboot.TPMConnection, pcrIndex int) error {
+ measureEpochCalls++
+ return nil
+ })
+ defer restore()
+ measureModelCalls := 0
+ restore = main.MockSecbootMeasureSnapModelToTPM(func(tpm *secboot.TPMConnection, pcrIndex int, model *asserts.Model) error {
+ measureModelCalls++
+ return fmt.Errorf("unexpected call")
+ })
+ defer restore()
+
+ _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ where := "/run/mnt/ubuntu-boot/model"
+ if mode != "run" {
+ where = fmt.Sprintf("/run/mnt/ubuntu-seed/systems/%s/model", label)
+ }
+ c.Assert(err, ErrorMatches,
+ fmt.Sprintf("cannot read model assertion: open .*%s: no such file or directory", where))
+ c.Assert(measureEpochCalls, Equals, 1)
+ c.Assert(measureModelCalls, Equals, 0)
+ c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent)
+ gl, err := filepath.Glob(filepath.Join(dirs.SnapBootstrapRunDir, "*-model-measured"))
+ c.Assert(err, IsNil)
+ c.Assert(gl, HasLen, 0)
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsRunModeStep2(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
+
+ n := s.mockExpectedMountChecks(c,
+ mounted{
+ boot.InitramfsUbuntuBootDir,
+ boot.InitramfsUbuntuSeedDir,
+ boot.InitramfsDataDir,
+ },
+ notYetMounted{
+ filepath.Join(boot.InitramfsRunMntDir, "base"),
+ filepath.Join(boot.InitramfsRunMntDir, "kernel"),
+ filepath.Join(boot.InitramfsRunMntDir, "snapd"),
+ },
+ )
+
+ // write modeenv
+ modeEnv := boot.Modeenv{
+ RecoverySystem: "20191118",
+ Base: "core20_123.snap",
+ CurrentKernels: []string{"pc-kernel_1.snap"},
+ }
+ err := modeEnv.WriteTo(boot.InitramfsWritableDir)
+ c.Assert(err, IsNil)
+
+ // mock a bootloader
+ bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir()))
+ bootloader.Force(bloader)
+ defer bootloader.Force(nil)
+
+ // set the current kernel
+ kernel, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_1.snap")
+ c.Assert(err, IsNil)
+ r := bloader.SetRunKernelImageEnabledKernel(kernel)
+ defer r()
+
+ _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, IsNil)
+ c.Assert(*n, Equals, 6)
+ c.Check(s.Stdout.String(), Equals, fmt.Sprintf(`%[1]s/data/system-data/var/lib/snapd/snaps/core20_123.snap %[1]s/base
+%[1]s/data/system-data/var/lib/snapd/snaps/pc-kernel_1.snap %[1]s/kernel
+%[1]s/ubuntu-seed/snaps/snapd_1.snap %[1]s/snapd
+`, boot.InitramfsRunMntDir))
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsRunModeStep2BaseSnapUpgradeFailsHappy(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
+
+ n := s.mockExpectedMountChecks(c,
+ mounted{
+ boot.InitramfsUbuntuBootDir,
+ boot.InitramfsUbuntuSeedDir,
+ boot.InitramfsDataDir,
+ },
+ notYetMounted{filepath.Join(boot.InitramfsRunMntDir, "base")},
+ mounted{filepath.Join(boot.InitramfsRunMntDir, "kernel")},
+ )
+
+ // write modeenv as if we failed to boot and were rebooted because the
+ // base snap was broken
+ modeEnv := &boot.Modeenv{
+ Base: "core20_123.snap",
+ TryBase: "core20_124.snap",
+ BaseStatus: boot.TryingStatus,
+ }
+ err := modeEnv.WriteTo(boot.InitramfsWritableDir)
+ c.Assert(err, IsNil)
+
+ tryBaseSnap := filepath.Join(boot.InitramfsWritableDir, dirs.SnapBlobDir, "core20_124.snap")
+ err = os.MkdirAll(filepath.Dir(tryBaseSnap), 0755)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(tryBaseSnap, []byte{0}, 0644)
+ c.Assert(err, IsNil)
+ defer os.Remove(tryBaseSnap)
+
+ _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, IsNil)
+ c.Assert(*n, Equals, 5)
+ c.Check(s.Stdout.String(), Equals, fmt.Sprintf(`%[1]s/data/system-data/var/lib/snapd/snaps/core20_123.snap %[1]s/base
+`, boot.InitramfsRunMntDir))
+
+ // check that the modeenv was re-written
+ newModeenv, err := boot.ReadModeenv(boot.InitramfsWritableDir)
+ c.Assert(err, IsNil)
+ // BaseStatus was re-set to default
+ c.Assert(newModeenv.BaseStatus, DeepEquals, boot.DefaultStatus)
+ c.Assert(newModeenv.TryBase, DeepEquals, modeEnv.TryBase)
+ c.Assert(newModeenv.Base, DeepEquals, modeEnv.Base)
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsRunModeStep2ModeenvTryBaseEmptyHappy(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
+
+ n := s.mockExpectedMountChecks(c,
+ mounted{
+ boot.InitramfsUbuntuBootDir,
+ boot.InitramfsUbuntuSeedDir,
+ boot.InitramfsDataDir,
+ },
+ notYetMounted{filepath.Join(boot.InitramfsRunMntDir, "base")},
+ mounted{filepath.Join(boot.InitramfsRunMntDir, "kernel")},
+ )
+
+ // write a modeenv with no try_base so we fall back to using base
+ modeEnv := &boot.Modeenv{
+ Base: "core20_123.snap",
+ BaseStatus: boot.TryStatus,
+ }
+ err := modeEnv.WriteTo(boot.InitramfsWritableDir)
+ c.Assert(err, IsNil)
+
+ _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, IsNil)
+ c.Assert(*n, Equals, 5)
+ c.Check(s.Stdout.String(), Equals, fmt.Sprintf(`%[1]s/data/system-data/var/lib/snapd/snaps/core20_123.snap %[1]s/base
+`, boot.InitramfsRunMntDir))
+
+ // check that the modeenv is the same
+ newModeenv, err := boot.ReadModeenv(boot.InitramfsWritableDir)
+ c.Assert(err, IsNil)
+ c.Assert(newModeenv.BaseStatus, DeepEquals, modeEnv.BaseStatus)
+ c.Assert(newModeenv.TryBase, DeepEquals, modeEnv.TryBase)
+ c.Assert(newModeenv.Base, DeepEquals, modeEnv.Base)
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsRunModeStep2BaseSnapUpgradeHappy(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
+
+ n := s.mockExpectedMountChecks(c,
+ mounted{
+ boot.InitramfsUbuntuBootDir,
+ boot.InitramfsUbuntuSeedDir,
+ boot.InitramfsDataDir,
+ },
+ notYetMounted{filepath.Join(boot.InitramfsRunMntDir, "base")},
+ mounted{filepath.Join(boot.InitramfsRunMntDir, "kernel")},
+ )
+
+ // write modeenv
+ modeEnv := &boot.Modeenv{
+ Base: "core20_123.snap",
+ TryBase: "core20_124.snap",
+ BaseStatus: boot.TryStatus,
+ }
+ err := modeEnv.WriteTo(boot.InitramfsWritableDir)
+ c.Assert(err, IsNil)
+
+ tryBaseSnap := filepath.Join(dirs.SnapBlobDirUnder(boot.InitramfsWritableDir), "core20_124.snap")
+ err = os.MkdirAll(filepath.Dir(tryBaseSnap), 0755)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(tryBaseSnap, []byte{0}, 0644)
+ c.Assert(err, IsNil)
+ defer os.Remove(tryBaseSnap)
+
+ _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, IsNil)
+ c.Assert(*n, Equals, 5)
+ c.Check(s.Stdout.String(), Equals, fmt.Sprintf(`%[1]s/data/system-data/var/lib/snapd/snaps/core20_124.snap %[1]s/base
+`, boot.InitramfsRunMntDir))
+
+ // check that the modeenv was re-written
+ newModeenv, err := boot.ReadModeenv(boot.InitramfsWritableDir)
+ c.Assert(err, IsNil)
+ c.Assert(newModeenv.BaseStatus, DeepEquals, boot.TryingStatus)
+ c.Assert(newModeenv.TryBase, DeepEquals, modeEnv.TryBase)
+ c.Assert(newModeenv.Base, DeepEquals, modeEnv.Base)
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsRunModeStep2ModeenvBaseEmptyUnhappy(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
+
+ n := s.mockExpectedMountChecks(c,
+ mounted{
+ boot.InitramfsUbuntuBootDir,
+ boot.InitramfsUbuntuSeedDir,
+ boot.InitramfsDataDir,
+ },
+ notYetMounted{filepath.Join(boot.InitramfsRunMntDir, "base")},
+ mounted{filepath.Join(boot.InitramfsRunMntDir, "kernel")},
+ )
+
+ // write an empty modeenv
+ modeEnv := &boot.Modeenv{}
+ err := modeEnv.WriteTo(boot.InitramfsWritableDir)
+ c.Assert(err, IsNil)
+
+ _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, ErrorMatches, "modeenv corrupt: missing base setting")
+ c.Assert(*n, Equals, 4)
+ c.Check(s.Stdout.String(), Equals, "")
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsRunModeStep2ModeenvTryBaseNotExistsHappy(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
+
+ n := s.mockExpectedMountChecks(c,
+ mounted{
+ boot.InitramfsUbuntuBootDir,
+ boot.InitramfsUbuntuSeedDir,
+ boot.InitramfsDataDir,
+ },
+ notYetMounted{filepath.Join(boot.InitramfsRunMntDir, "base")},
+ mounted{filepath.Join(boot.InitramfsRunMntDir, "kernel")},
+ )
+
+ // write a modeenv with try_base not existing on disk so we fall back to
+ // using the normal base
+ modeEnv := &boot.Modeenv{
+ Base: "core20_123.snap",
+ TryBase: "core20_124.snap",
+ BaseStatus: boot.TryStatus,
+ }
+ err := modeEnv.WriteTo(boot.InitramfsWritableDir)
+ c.Assert(err, IsNil)
+
+ _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, IsNil)
+ c.Assert(*n, Equals, 5)
+ c.Check(s.Stdout.String(), Equals, fmt.Sprintf(`%[1]s/data/system-data/var/lib/snapd/snaps/core20_123.snap %[1]s/base
+`, boot.InitramfsRunMntDir))
+
+ // check that the modeenv is the same
+ newModeenv, err := boot.ReadModeenv(boot.InitramfsWritableDir)
+ c.Assert(err, IsNil)
+ c.Assert(newModeenv.BaseStatus, DeepEquals, modeEnv.BaseStatus)
+ c.Assert(newModeenv.TryBase, DeepEquals, modeEnv.TryBase)
+ c.Assert(newModeenv.Base, DeepEquals, modeEnv.Base)
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsRunModeStep2KernelSnapUpgradeHappy(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
+
+ n := s.mockExpectedMountChecks(c,
+ mounted{
+ boot.InitramfsUbuntuBootDir,
+ boot.InitramfsUbuntuSeedDir,
+ boot.InitramfsDataDir,
+ filepath.Join(boot.InitramfsRunMntDir, "base"),
+ },
+ notYetMounted{filepath.Join(boot.InitramfsRunMntDir, "kernel")},
+ )
+
+ // write modeenv
+ modeEnv := &boot.Modeenv{
+ Base: "core20_123.snap",
+ CurrentKernels: []string{"pc-kernel_1.snap", "pc-kernel_2.snap"},
+ }
+ err := modeEnv.WriteTo(boot.InitramfsWritableDir)
+ c.Assert(err, IsNil)
+
+ tryBaseSnap := filepath.Join(dirs.SnapBlobDirUnder(boot.InitramfsWritableDir), "core20_124.snap")
+ err = os.MkdirAll(filepath.Dir(tryBaseSnap), 0755)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(tryBaseSnap, []byte{0}, 0644)
+ c.Assert(err, IsNil)
+ defer os.Remove(tryBaseSnap)
+
+ // mock a bootloader
+ bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir()))
+ bootloader.Force(bloader)
+ defer bootloader.Force(nil)
+
+ bloader.BootVars["kernel_status"] = boot.TryingStatus
+
+ // set the current kernel
+ kernel, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_1.snap")
+ c.Assert(err, IsNil)
+ r := bloader.SetRunKernelImageEnabledKernel(kernel)
+ defer r()
+
+ // set the try kernel
+ tryKernel, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_2.snap")
+ c.Assert(err, IsNil)
+ r = bloader.SetRunKernelImageEnabledTryKernel(tryKernel)
+ defer r()
+
+ _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, IsNil)
+ c.Assert(*n, Equals, 5)
+ c.Check(s.Stdout.String(), Equals, fmt.Sprintf(`%[1]s/data/system-data/var/lib/snapd/snaps/pc-kernel_2.snap %[1]s/kernel
+`, boot.InitramfsRunMntDir))
+}
+
+// TODO:UC20: in this case snap-bootstrap should request a reboot, since we
+// already booted the try snap, so mounting the fallback kernel will
+// not match in some cases
+func (s *initramfsMountsSuite) TestInitramfsMountsRunModeStep2UntrustedKernelSnap(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
+
+ n := s.mockExpectedMountChecks(c,
+ mounted{
+ boot.InitramfsUbuntuBootDir,
+ boot.InitramfsUbuntuSeedDir,
+ boot.InitramfsDataDir,
+ filepath.Join(boot.InitramfsRunMntDir, "base"),
+ },
+ notYetMounted{filepath.Join(boot.InitramfsRunMntDir, "kernel")},
+ )
+
+ // write modeenv
+ modeEnv := boot.Modeenv{
+ Base: "core20_123.snap",
+ CurrentKernels: []string{"pc-kernel_1.snap"},
+ }
+ err := modeEnv.WriteTo(boot.InitramfsWritableDir)
+ c.Assert(err, IsNil)
+
+ // mock a bootloader
+ bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir()))
+ bootloader.Force(bloader)
+ defer bootloader.Force(nil)
+
+ // set the current kernel as a kernel not in CurrentKernels
+ kernel, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_2.snap")
+ c.Assert(err, IsNil)
+ r := bloader.SetRunKernelImageEnabledKernel(kernel)
+ defer r()
+
+ _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, ErrorMatches, fmt.Sprintf("fallback kernel snap %q is not trusted in the modeenv", "pc-kernel_2.snap"))
+ c.Assert(*n, Equals, 5)
+}
+
+// TODO:UC20: in this case snap-bootstrap should request a reboot, since we
+// already booted the try snap, so mounting the fallback kernel will
+// not match in some cases
+func (s *initramfsMountsSuite) TestInitramfsMountsRunModeStep2UntrustedTryKernelSnapFallsBack(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
+
+ n := s.mockExpectedMountChecks(c,
+ mounted{
+ boot.InitramfsUbuntuBootDir,
+ boot.InitramfsUbuntuSeedDir,
+ boot.InitramfsDataDir,
+ filepath.Join(boot.InitramfsRunMntDir, "base"),
+ },
+ notYetMounted{filepath.Join(boot.InitramfsRunMntDir, "kernel")},
+ )
+
+ // write modeenv
+ modeEnv := boot.Modeenv{
+ Base: "core20_123.snap",
+ CurrentKernels: []string{"pc-kernel_1.snap"},
+ }
+ err := modeEnv.WriteTo(boot.InitramfsWritableDir)
+ c.Assert(err, IsNil)
+
+ // mock a bootloader
+ bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir()))
+ bootloader.Force(bloader)
+ defer bootloader.Force(nil)
+
+ // set the try kernel as a kernel not in CurrentKernels
+ kernel2, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_2.snap")
+ c.Assert(err, IsNil)
+ r := bloader.SetRunKernelImageEnabledTryKernel(kernel2)
+ defer r()
+
+ // set the normal kernel as a valid kernel
+ kernel1, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_1.snap")
+ c.Assert(err, IsNil)
+ r = bloader.SetRunKernelImageEnabledKernel(kernel1)
+ defer r()
+
+ _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
+
+ // TODO:UC20: if we have somewhere to log errors from snap-bootstrap during
+ // the initramfs, check that log here
+ c.Assert(err, IsNil)
+ c.Assert(*n, Equals, 5)
+ c.Check(s.Stdout.String(), Equals, fmt.Sprintf(`%[1]s/data/system-data/var/lib/snapd/snaps/pc-kernel_1.snap %[1]s/kernel
+`, boot.InitramfsRunMntDir))
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsRunModeStep2KernelStatusTryingNoTryKernel(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
+
+ n := s.mockExpectedMountChecks(c,
+ mounted{
+ boot.InitramfsUbuntuBootDir,
+ boot.InitramfsUbuntuSeedDir,
+ boot.InitramfsDataDir,
+ filepath.Join(boot.InitramfsRunMntDir, "base"),
+ },
+ notYetMounted{filepath.Join(boot.InitramfsRunMntDir, "kernel")},
+ )
+
+ // write modeenv
+ modeEnv := boot.Modeenv{
+ Base: "core20_123.snap",
+ CurrentKernels: []string{"pc-kernel_1.snap"},
+ }
+ err := modeEnv.WriteTo(boot.InitramfsWritableDir)
+ c.Assert(err, IsNil)
+
+ // mock a bootloader
+ bloader := boottest.MockUC20RunBootenv(bootloadertest.Mock("mock", c.MkDir()))
+ bootloader.Force(bloader)
+ defer bootloader.Force(nil)
+
+ // we are in trying mode, but don't set a try-kernel so we fallback to the
+ // fallback kernel
+ err = bloader.SetBootVars(map[string]string{"kernel_status": boot.TryingStatus})
+ c.Assert(err, IsNil)
+
+ // set the normal kernel as a valid kernel
+ kernel, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_1.snap")
+ c.Assert(err, IsNil)
+ r := bloader.SetRunKernelImageEnabledKernel(kernel)
+ defer r()
+
+ _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
+
+ // TODO:UC20: if we have somewhere to log errors from snap-bootstrap during
+ // the initramfs, check that log here
+ c.Assert(err, IsNil)
+ c.Assert(*n, Equals, 5)
+ c.Check(s.Stdout.String(), Equals, fmt.Sprintf(`%[1]s/data/system-data/var/lib/snapd/snaps/pc-kernel_1.snap %[1]s/kernel
+`, boot.InitramfsRunMntDir))
+}
+
+func (s *initramfsMountsSuite) TestUnlockIfEncrypted(c *C) {
+ for idx, tc := range []struct {
+ hasTPM bool
+ tpmErr error
+ hasEncdev bool
+ last bool
+ lockOk bool
+ activated bool
+ device string
+ err string
+ }{
+ // TODO: verify which cases are possible
+ {
+ hasTPM: true, hasEncdev: true, last: true, lockOk: true,
+ activated: true, device: "name",
+ }, {
+ hasTPM: true, hasEncdev: true, last: true, lockOk: true,
+ err: "cannot activate encrypted device .*: activation error",
+ }, {
+ hasTPM: true, hasEncdev: true, last: true, activated: true,
+ err: "cannot lock access to sealed keys: lock failed",
+ }, {
+ hasTPM: true, hasEncdev: true, lockOk: true, activated: true,
+ device: "name",
+ }, {
+ hasTPM: true, hasEncdev: true, lockOk: true,
+ err: "cannot activate encrypted device .*: activation error",
+ }, {
+ hasTPM: true, hasEncdev: true, activated: true, device: "name",
+ }, {
+ hasTPM: true, hasEncdev: true,
+ err: "cannot activate encrypted device .*: activation error",
+ }, {
+ hasTPM: true, last: true, lockOk: true, activated: true,
+ device: "name",
+ }, {
+ hasTPM: true, last: true, activated: true,
+ err: "cannot lock access to sealed keys: lock failed",
+ }, {
+ hasTPM: true, lockOk: true, activated: true, device: "name",
+ }, {
+ hasTPM: true, activated: true, device: "name",
+ }, {
+ hasTPM: true, hasEncdev: true, last: true,
+ tpmErr: errors.New("tpm error"),
+ err: `cannot unlock encrypted device "name": tpm error`,
+ }, {
+ hasTPM: true, hasEncdev: true,
+ tpmErr: errors.New("tpm error"),
+ err: `cannot unlock encrypted device "name": tpm error`,
+ }, {
+ hasTPM: true, last: true, device: "name",
+ tpmErr: errors.New("tpm error"),
+ }, {
+ hasTPM: true, device: "name",
+ tpmErr: errors.New("tpm error"),
+ }, {
+ hasEncdev: true, last: true,
+ tpmErr: errors.New("no tpm"),
+ err: `cannot unlock encrypted device "name": no tpm`,
+ }, {
+ hasEncdev: true,
+ tpmErr: errors.New("no tpm"),
+ err: `cannot unlock encrypted device "name": no tpm`,
+ }, {
+ last: true, device: "name", tpmErr: errors.New("no tpm"),
+ }, {
+ tpmErr: errors.New("no tpm"), device: "name",
+ },
+ } {
+ randomUUID := fmt.Sprintf("random-uuid-for-test-%d", idx)
+ restore := main.MockRandomKernelUUID(func() string {
+ return randomUUID
+ })
+ defer restore()
+
+ c.Logf("tc %v: %+v", idx, tc)
+ mockTPM, restoreTPM := mockSecbootTPM(c)
+ defer restoreTPM()
+
+ restoreConnect := main.MockSecbootConnectToDefaultTPM(func() (*secboot.TPMConnection, error) {
+ return mockTPM, tc.tpmErr
+ })
+ defer restoreConnect()
+
+ n := 0
+ restoreLock := main.MockSecbootLockAccessToSealedKeys(func(tpm *secboot.TPMConnection) error {
+ n++
+ c.Assert(tpm, Equals, mockTPM)
+ if tc.lockOk {
+ return nil
+ }
+ return errors.New("lock failed")
+ })
+ defer restoreLock()
+
+ devDiskByLabel, restoreDev := mockDevDiskByLabel(c)
+ defer restoreDev()
+ if tc.hasEncdev {
+ err := ioutil.WriteFile(filepath.Join(devDiskByLabel, "name-enc"), nil, 0644)
+ c.Assert(err, IsNil)
+ }
+
+ restoreActivate := main.MockSecbootActivateVolumeWithTPMSealedKey(func(tpm *secboot.TPMConnection, volumeName, sourceDevicePath,
+ keyPath string, pinReader io.Reader, options *secboot.ActivateWithTPMSealedKeyOptions) (bool, error) {
+ c.Assert(tpm, Equals, mockTPM)
+ c.Assert(volumeName, Equals, "name-"+randomUUID)
+ c.Assert(sourceDevicePath, Equals, filepath.Join(devDiskByLabel, "name-enc"))
+ c.Assert(keyPath, Equals, filepath.Join(boot.InitramfsEncryptionKeyDir, "name.sealed-key"))
+ c.Assert(*options, DeepEquals, secboot.ActivateWithTPMSealedKeyOptions{
+ PINTries: 1,
+ RecoveryKeyTries: 3,
+ LockSealedKeyAccess: tc.last,
+ })
+ if !tc.activated {
+ return false, errors.New("activation error")
+ }
+ return true, nil
+ })
+ defer restoreActivate()
+
+ device, err := main.UnlockIfEncrypted("name", tc.last)
+ if tc.device == "" {
+ c.Check(device, Equals, tc.device)
+ } else {
+ if tc.hasEncdev {
+ c.Assert(device, Equals, filepath.Join("/dev/mapper", tc.device+"-"+randomUUID))
+ } else {
+ c.Assert(device, Equals, filepath.Join(devDiskByLabel, tc.device))
+ }
+ }
+ if tc.err == "" {
+ c.Assert(err, IsNil)
+ } else {
+ c.Assert(err, ErrorMatches, tc.err)
+ }
+ // LockAccessToSealedKeys should be called whenever there is a TPM device
+ // detected, regardless of whether secure boot is enabled or there is an
+ // encrypted volume to unlock. If we have multiple encrypted volumes, we
+ // should call it after the last one is unlocked.
+ if tc.hasTPM && tc.tpmErr == nil && tc.last {
+ c.Assert(n, Equals, 1)
+ }
+ }
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsRunModeStep2EnvRefKernelBootstate(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
+
+ n := s.mockExpectedMountChecks(c,
+ mounted{
+ boot.InitramfsUbuntuBootDir,
+ boot.InitramfsUbuntuSeedDir,
+ boot.InitramfsDataDir,
+ },
+ notYetMounted{
+ filepath.Join(boot.InitramfsRunMntDir, "base"),
+ filepath.Join(boot.InitramfsRunMntDir, "kernel"),
+ filepath.Join(boot.InitramfsRunMntDir, "snapd"),
+ },
+ )
+
+ // write modeenv
+ modeEnv := boot.Modeenv{
+ RecoverySystem: "20191118",
+ Base: "core20_123.snap",
+ CurrentKernels: []string{"pc-kernel_1.snap"},
+ }
+ err := modeEnv.WriteTo(boot.InitramfsWritableDir)
+ c.Assert(err, IsNil)
+
+ // mock a bootloader
+ bloader := boottest.MockUC20EnvRefExtractedKernelRunBootenv(bootloadertest.Mock("mock", c.MkDir()))
+ bootloader.Force(bloader)
+ defer bootloader.Force(nil)
+
+ // set the current kernel
+ bloader.SetBootKernel("pc-kernel_1.snap")
+
+ _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, IsNil)
+ c.Assert(*n, Equals, 6)
+ c.Check(s.Stdout.String(), Equals, fmt.Sprintf(`%[1]s/data/system-data/var/lib/snapd/snaps/core20_123.snap %[1]s/base
+%[1]s/data/system-data/var/lib/snapd/snaps/pc-kernel_1.snap %[1]s/kernel
+%[1]s/ubuntu-seed/snaps/snapd_1.snap %[1]s/snapd
+`, boot.InitramfsRunMntDir))
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsRunModeStep2EnvRefKernelBootstateKernelSnapUpgradeHappy(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
+
+ n := s.mockExpectedMountChecks(c,
+ mounted{
+ boot.InitramfsUbuntuBootDir,
+ boot.InitramfsUbuntuSeedDir,
+ boot.InitramfsDataDir,
+ filepath.Join(boot.InitramfsRunMntDir, "base"),
+ },
+ notYetMounted{filepath.Join(boot.InitramfsRunMntDir, "kernel")},
+ )
+
+ // write modeenv
+ modeEnv := &boot.Modeenv{
+ Base: "core20_123.snap",
+ CurrentKernels: []string{"pc-kernel_1.snap", "pc-kernel_2.snap"},
+ }
+ err := modeEnv.WriteTo(boot.InitramfsWritableDir)
+ c.Assert(err, IsNil)
+
+ tryBaseSnap := filepath.Join(dirs.SnapBlobDirUnder(boot.InitramfsWritableDir), "core20_124.snap")
+ err = os.MkdirAll(filepath.Dir(tryBaseSnap), 0755)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(tryBaseSnap, []byte{0}, 0644)
+ c.Assert(err, IsNil)
+ defer os.Remove(tryBaseSnap)
+
+ // mock a bootloader
+ bloader := boottest.MockUC20EnvRefExtractedKernelRunBootenv(bootloadertest.Mock("mock", c.MkDir()))
+ bootloader.Force(bloader)
+ defer bootloader.Force(nil)
+
+ bloader.BootVars["kernel_status"] = boot.TryingStatus
+
+ // set the current kernel and try kernel
+ bloader.SetBootKernel("pc-kernel_1.snap")
+ bloader.SetBootTryKernel("pc-kernel_2.snap")
+
+ _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, IsNil)
+ c.Assert(*n, Equals, 5)
+ c.Check(s.Stdout.String(), Equals, fmt.Sprintf(`%[1]s/data/system-data/var/lib/snapd/snaps/pc-kernel_2.snap %[1]s/kernel
+`, boot.InitramfsRunMntDir))
+}
+
+// TODO:UC20: in this case snap-bootstrap should request a reboot, since we
+// already booted the try snap, so mounting the fallback kernel will
+// not match in some cases
+func (s *initramfsMountsSuite) TestInitramfsMountsRunModeStep2EnvRefKernelBootstateUntrustedKernelSnap(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
+
+ n := s.mockExpectedMountChecks(c,
+ mounted{
+ boot.InitramfsUbuntuBootDir,
+ boot.InitramfsUbuntuSeedDir,
+ boot.InitramfsDataDir,
+ filepath.Join(boot.InitramfsRunMntDir, "base"),
+ },
+ notYetMounted{filepath.Join(boot.InitramfsRunMntDir, "kernel")},
+ )
+
+ // write modeenv
+ modeEnv := boot.Modeenv{
+ Base: "core20_123.snap",
+ CurrentKernels: []string{"pc-kernel_1.snap"},
+ }
+ err := modeEnv.WriteTo(boot.InitramfsWritableDir)
+ c.Assert(err, IsNil)
+
+ // mock a bootloader
+ bloader := boottest.MockUC20EnvRefExtractedKernelRunBootenv(bootloadertest.Mock("mock", c.MkDir()))
+ bootloader.Force(bloader)
+ defer bootloader.Force(nil)
+
+ // set the current kernel as a kernel not in CurrentKernels
+ bloader.SetBootKernel("pc-kernel_2.snap")
+
+ _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, ErrorMatches, fmt.Sprintf("fallback kernel snap %q is not trusted in the modeenv", "pc-kernel_2.snap"))
+ c.Assert(*n, Equals, 5)
+}
+
+// TODO:UC20: in this case snap-bootstrap should request a reboot, since we
+// already booted the try snap, so mounting the fallback kernel will
+// not match in some cases
+func (s *initramfsMountsSuite) TestInitramfsMountsRunModeStep2EnvRefKernelBootstateUntrustedTryKernelSnapFallsBack(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
+
+ n := s.mockExpectedMountChecks(c,
+ mounted{
+ boot.InitramfsUbuntuBootDir,
+ boot.InitramfsUbuntuSeedDir,
+ boot.InitramfsDataDir,
+ filepath.Join(boot.InitramfsRunMntDir, "base"),
+ },
+ notYetMounted{filepath.Join(boot.InitramfsRunMntDir, "kernel")},
+ )
+
+ // write modeenv
+ modeEnv := boot.Modeenv{
+ Base: "core20_123.snap",
+ CurrentKernels: []string{"pc-kernel_1.snap"},
+ }
+ err := modeEnv.WriteTo(boot.InitramfsWritableDir)
+ c.Assert(err, IsNil)
+
+ // mock a bootloader
+ bloader := boottest.MockUC20EnvRefExtractedKernelRunBootenv(bootloadertest.Mock("mock", c.MkDir()))
+ bootloader.Force(bloader)
+ defer bootloader.Force(nil)
+
+ // set the try kernel as a kernel not in CurrentKernels
+ bloader.SetBootTryKernel("pc-kernel_2.snap")
+
+ // set the normal kernel as a valid kernel
+ bloader.SetBootKernel("pc-kernel_1.snap")
+
+ bloader.BootVars["kernel_status"] = boot.TryingStatus
+
+ _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
+
+ // TODO:UC20: if we have somewhere to log errors from snap-bootstrap during
+ // the initramfs, check that log here
+ c.Assert(err, IsNil)
+ c.Assert(*n, Equals, 5)
+ c.Check(s.Stdout.String(), Equals, fmt.Sprintf(`%[1]s/data/system-data/var/lib/snapd/snaps/pc-kernel_1.snap %[1]s/kernel
+`, boot.InitramfsRunMntDir))
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsRunModeStep2EnvRefKernelBootstateKernelStatusTryingNoTryKernel(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
+
+ n := s.mockExpectedMountChecks(c,
+ mounted{
+ boot.InitramfsUbuntuBootDir,
+ boot.InitramfsUbuntuSeedDir,
+ boot.InitramfsDataDir,
+ filepath.Join(boot.InitramfsRunMntDir, "base"),
+ },
+ notYetMounted{filepath.Join(boot.InitramfsRunMntDir, "kernel")},
+ )
+
+ // write modeenv
+ modeEnv := boot.Modeenv{
+ Base: "core20_123.snap",
+ CurrentKernels: []string{"pc-kernel_1.snap"},
+ }
+ err := modeEnv.WriteTo(boot.InitramfsWritableDir)
+ c.Assert(err, IsNil)
+
+ // mock a bootloader
+ bloader := boottest.MockUC20EnvRefExtractedKernelRunBootenv(bootloadertest.Mock("mock", c.MkDir()))
+ bootloader.Force(bloader)
+ defer bootloader.Force(nil)
+
+ // we are in trying mode, but don't set a try-kernel so we fallback to the
+ // fallback kernel
+ err = bloader.SetBootVars(map[string]string{"kernel_status": boot.TryingStatus})
+ c.Assert(err, IsNil)
+
+ // set the normal kernel as a valid kernel
+ bloader.SetBootKernel("pc-kernel_1.snap")
+
+ _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
+
+ // TODO:UC20: if we have somewhere to log errors from snap-bootstrap during
+ // the initramfs, check that log here
+ c.Assert(err, IsNil)
+ c.Assert(*n, Equals, 5)
+ c.Check(s.Stdout.String(), Equals, fmt.Sprintf(`%[1]s/data/system-data/var/lib/snapd/snaps/pc-kernel_1.snap %[1]s/kernel
+`, boot.InitramfsRunMntDir))
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeStep1(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel)
+
+ n := s.mockExpectedMountChecks(c,
+ notYetMounted{boot.InitramfsUbuntuSeedDir},
+ )
+
+ _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, IsNil)
+ c.Assert(*n, Equals, 1)
+ c.Check(s.Stdout.String(), Equals, fmt.Sprintf("/dev/disk/by-label/ubuntu-seed %s/ubuntu-seed\n", boot.InitramfsRunMntDir))
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeStep2(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel)
+
+ n := s.mockExpectedMountChecks(c,
+ mounted{boot.InitramfsUbuntuSeedDir},
+ notYetMounted{
+ filepath.Join(boot.InitramfsRunMntDir, "base"),
+ filepath.Join(boot.InitramfsRunMntDir, "kernel"),
+ filepath.Join(boot.InitramfsRunMntDir, "snapd"),
+ filepath.Join(boot.InitramfsRunMntDir, "data"),
+ },
+ )
+
+ _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, IsNil)
+ c.Assert(*n, Equals, 5)
+ c.Check(s.Stdout.String(), Equals, fmt.Sprintf(`%[1]s/snaps/snapd_1.snap %[2]s/snapd
+%[1]s/snaps/pc-kernel_1.snap %[2]s/kernel
+%[1]s/snaps/core20_1.snap %[2]s/base
+--type=tmpfs tmpfs %[2]s/data
+`, s.seedDir, boot.InitramfsRunMntDir))
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeStep3(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel)
+
+ n := s.mockExpectedMountChecks(c,
+ mounted{
+ boot.InitramfsUbuntuSeedDir,
+ filepath.Join(boot.InitramfsRunMntDir, "base"),
+ filepath.Join(boot.InitramfsRunMntDir, "kernel"),
+ filepath.Join(boot.InitramfsRunMntDir, "snapd"),
+ filepath.Join(boot.InitramfsRunMntDir, "data"),
+ },
+ notYetMounted{filepath.Join(boot.InitramfsRunMntDir, "host/ubuntu-data")},
+ )
+
+ _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, IsNil)
+ c.Assert(*n, Equals, 6)
+ c.Check(s.Stdout.String(), Equals, fmt.Sprintf(`/dev/disk/by-label/ubuntu-data %s/host/ubuntu-data
+`, boot.InitramfsRunMntDir))
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeStep3Encrypted(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel)
+
+ // setup ubuntu-data-enc
+ devDiskByLabel, restore := mockDevDiskByLabel(c)
+ defer restore()
+
+ ubuntuDataEnc := filepath.Join(devDiskByLabel, "ubuntu-data-enc")
+ err := ioutil.WriteFile(ubuntuDataEnc, nil, 0644)
+ c.Assert(err, IsNil)
+
+ // setup a fake tpm
+ mockTPM, restore := mockSecbootTPM(c)
+ defer restore()
+
+ restore = main.MockRandomKernelUUID(func() string {
+ return "some-kernel-uuid"
+ })
+ defer restore()
+
+ activated := false
+ // setup activating the fake tpm
+ restore = main.MockSecbootActivateVolumeWithTPMSealedKey(func(tpm *secboot.TPMConnection, volumeName, sourceDevicePath,
+ keyPath string, pinReader io.Reader, options *secboot.ActivateWithTPMSealedKeyOptions) (bool, error) {
+ c.Assert(tpm, Equals, mockTPM)
+ c.Assert(volumeName, Equals, "ubuntu-data-some-kernel-uuid")
+ c.Assert(sourceDevicePath, Equals, ubuntuDataEnc)
+ // the keyfile will be on ubuntu-seed as ubuntu-data.sealed-key
+ c.Assert(keyPath, Equals, filepath.Join(boot.InitramfsUbuntuSeedDir, "device/fde", "ubuntu-data.sealed-key"))
+ c.Assert(*options, DeepEquals, secboot.ActivateWithTPMSealedKeyOptions{
+ PINTries: 1,
+ RecoveryKeyTries: 3,
+ LockSealedKeyAccess: true,
+ })
+ activated = true
+ return true, nil
+ })
+ defer restore()
+
+ n := s.mockExpectedMountChecks(c,
+ mounted{
+ boot.InitramfsUbuntuSeedDir,
+ filepath.Join(boot.InitramfsRunMntDir, "base"),
+ filepath.Join(boot.InitramfsRunMntDir, "kernel"),
+ filepath.Join(boot.InitramfsRunMntDir, "snapd"),
+ filepath.Join(boot.InitramfsRunMntDir, "data"),
+ },
+ notYetMounted{filepath.Join(boot.InitramfsRunMntDir, "host/ubuntu-data")},
+ )
+
+ sealedKeysLocked := false
+ restore = main.MockSecbootLockAccessToSealedKeys(func(tpm *secboot.TPMConnection) error {
+ sealedKeysLocked = true
+ return nil
+ })
+ defer restore()
+
+ epochPCR := -1
+ modelPCR := -1
+ restore = main.MockSecbootMeasureSnapSystemEpochToTPM(func(tpm *secboot.TPMConnection, pcrIndex int) error {
+ epochPCR = pcrIndex
+ return nil
+ })
+ defer restore()
+
+ var measuredModel *asserts.Model
+ restore = main.MockSecbootMeasureSnapModelToTPM(func(tpm *secboot.TPMConnection, pcrIndex int, model *asserts.Model) error {
+ modelPCR = pcrIndex
+ measuredModel = model
+ return nil
+ })
+ defer restore()
+
+ _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, IsNil)
+ c.Assert(*n, Equals, 6)
+ c.Check(s.Stdout.String(), Equals, fmt.Sprintf(`/dev/mapper/ubuntu-data-some-kernel-uuid %[1]s/host/ubuntu-data
+`, boot.InitramfsRunMntDir))
+
+ c.Check(activated, Equals, true)
+ c.Check(sealedKeysLocked, Equals, true)
+ c.Check(epochPCR, Equals, 12)
+ c.Check(modelPCR, Equals, 12)
+ c.Check(measuredModel, NotNil)
+ c.Check(measuredModel, DeepEquals, s.model)
+
+ c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent)
+ c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, fmt.Sprintf("%s-model-measured", s.sysLabel)), testutil.FilePresent)
+}
+
+var mockStateContent = `{"data":{"auth":{"users":[{"name":"mvo"}]}},"some":{"other":"stuff"}}`
+
+func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeStep4(c *C) {
+ s.mockProcCmdlineContent(c, "snapd_recovery_mode=recover snapd_recovery_system="+s.sysLabel)
+
+ n := s.mockExpectedMountChecks(c,
+ mounted{
+ boot.InitramfsUbuntuSeedDir,
+ filepath.Join(boot.InitramfsRunMntDir, "base"),
+ filepath.Join(boot.InitramfsRunMntDir, "kernel"),
+ filepath.Join(boot.InitramfsRunMntDir, "snapd"),
+ filepath.Join(boot.InitramfsRunMntDir, "data"),
+ filepath.Join(boot.InitramfsRunMntDir, "host/ubuntu-data"),
+ },
+ )
+
+ ephemeralUbuntuData := filepath.Join(boot.InitramfsRunMntDir, "data/")
+ err := os.MkdirAll(ephemeralUbuntuData, 0755)
+ c.Assert(err, IsNil)
+ // mock a auth data in the host's ubuntu-data
+ hostUbuntuData := filepath.Join(boot.InitramfsRunMntDir, "host/ubuntu-data/")
+ err = os.MkdirAll(hostUbuntuData, 0755)
+ c.Assert(err, IsNil)
+ mockAuthFiles := []string{
+ // extrausers
+ "system-data/var/lib/extrausers/passwd",
+ "system-data/var/lib/extrausers/shadow",
+ "system-data/var/lib/extrausers/group",
+ "system-data/var/lib/extrausers/gshadow",
+ // sshd
+ "system-data/etc/ssh/ssh_host_rsa.key",
+ "system-data/etc/ssh/ssh_host_rsa.key.pub",
+ // user ssh
+ "user-data/user1/.ssh/authorized_keys",
+ "user-data/user2/.ssh/authorized_keys",
+ // sudoers
+ "system-data/etc/sudoers.d/create-user-test",
+ }
+ mockUnrelatedFiles := []string{
+ "system-data/var/lib/foo",
+ "system-data/etc/passwd",
+ "user-data/user1/some-random-data",
+ "user-data/user2/other-random-data",
+ }
+ for _, mockAuthFile := range append(mockAuthFiles, mockUnrelatedFiles...) {
+ p := filepath.Join(hostUbuntuData, mockAuthFile)
+ err = os.MkdirAll(filepath.Dir(p), 0750)
+ c.Assert(err, IsNil)
+ mockContent := fmt.Sprintf("content of %s", filepath.Base(mockAuthFile))
+ err = ioutil.WriteFile(p, []byte(mockContent), 0640)
+ c.Assert(err, IsNil)
+ }
+ // create a mock state
+ mockedState := filepath.Join(hostUbuntuData, "system-data/var/lib/snapd/state.json")
+ err = os.MkdirAll(filepath.Dir(mockedState), 0750)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(mockedState, []byte(mockStateContent), 0640)
+ c.Assert(err, IsNil)
+
+ _, err = main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, IsNil)
+ c.Assert(*n, Equals, 6)
+ c.Check(s.Stdout.String(), Equals, "")
+
+ modeEnv := filepath.Join(ephemeralUbuntuData, "/system-data/var/lib/snapd/modeenv")
+ c.Check(modeEnv, testutil.FileEquals, `mode=recover
+recovery_system=20191118
+`)
+ for _, p := range mockUnrelatedFiles {
+ c.Check(filepath.Join(ephemeralUbuntuData, p), testutil.FileAbsent)
+ }
+ for _, p := range mockAuthFiles {
+ c.Check(filepath.Join(ephemeralUbuntuData, p), testutil.FilePresent)
+ fi, err := os.Stat(filepath.Join(ephemeralUbuntuData, p))
+ // check file mode is set
+ c.Assert(err, IsNil)
+ c.Check(fi.Mode(), Equals, os.FileMode(0640))
+ // check dir mode is set in parent dir
+ fiParent, err := os.Stat(filepath.Dir(filepath.Join(ephemeralUbuntuData, p)))
+ c.Assert(err, IsNil)
+ c.Check(fiParent.Mode(), Equals, os.FileMode(os.ModeDir|0750))
+ }
+
+ c.Check(filepath.Join(ephemeralUbuntuData, "system-data/var/lib/snapd/state.json"), testutil.FileEquals, `{"data":{"auth":{"users":[{"name":"mvo"}]}},"changes":{},"tasks":{},"last-change-id":0,"last-task-id":0,"last-lane-id":0}`)
+}
+
+func mockSecbootTPM(c *C) (tpm *secboot.TPMConnection, restore func()) {
+ tcti, err := os.Open("/dev/null")
+ c.Assert(err, IsNil)
+ tpmctx, err := tpm2.NewTPMContext(tcti)
+ c.Assert(err, IsNil)
+ mockTPM := &secboot.TPMConnection{TPMContext: tpmctx}
+
+ restoreConnect := main.MockSecbootConnectToDefaultTPM(func() (*secboot.TPMConnection, error) {
+ return mockTPM, nil
+ })
+ return mockTPM, restoreConnect
+}
+
+func mockDevDiskByLabel(c *C) (string, func()) {
+ devDir := filepath.Join(c.MkDir(), "dev/disk/by-label")
+ err := os.MkdirAll(devDir, 0755)
+ c.Assert(err, IsNil)
+ restore := main.MockDevDiskByLabelDir(devDir)
+ return devDir, restore
+}
+
+func (s *initramfsMountsSuite) testInitramfsMountsInstallRecoverModeStep1Measure(c *C, mode string) {
+ s.mockProcCmdlineContent(c, fmt.Sprintf("snapd_recovery_mode=%s snapd_recovery_system=%s", mode, s.sysLabel))
+
+ n := s.mockExpectedMountChecks(c,
+ mounted{boot.InitramfsUbuntuSeedDir},
+ notYetMounted{
+ filepath.Join(boot.InitramfsRunMntDir, "base"),
+ filepath.Join(boot.InitramfsRunMntDir, "kernel"),
+ filepath.Join(boot.InitramfsRunMntDir, "snapd"),
+ filepath.Join(boot.InitramfsRunMntDir, "data"),
+ },
+ )
+
+ // setup a fake tpm
+ _, restore := mockSecbootTPM(c)
+ defer restore()
+
+ epochPCR := -1
+ modelPCR := -1
+ restore = main.MockSecbootMeasureSnapSystemEpochToTPM(func(tpm *secboot.TPMConnection, pcrIndex int) error {
+ epochPCR = pcrIndex
+ return nil
+ })
+ defer restore()
+
+ var measuredModel *asserts.Model
+ restore = main.MockSecbootMeasureSnapModelToTPM(func(tpm *secboot.TPMConnection, pcrIndex int, model *asserts.Model) error {
+ modelPCR = pcrIndex
+ measuredModel = model
+ return nil
+ })
+ defer restore()
+
+ _, err := main.Parser().ParseArgs([]string{"initramfs-mounts"})
+ c.Assert(err, IsNil)
+ c.Check(s.Stdout.String(), Equals, fmt.Sprintf(`%[1]s/snaps/snapd_1.snap %[2]s/snapd
+%[1]s/snaps/pc-kernel_1.snap %[2]s/kernel
+%[1]s/snaps/core20_1.snap %[2]s/base
+--type=tmpfs tmpfs %[2]s/data
+`, s.seedDir, boot.InitramfsRunMntDir))
+ c.Check(epochPCR, Equals, 12)
+ c.Check(modelPCR, Equals, 12)
+ c.Check(measuredModel, NotNil)
+ c.Check(measuredModel, DeepEquals, s.model)
+ c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, "secboot-epoch-measured"), testutil.FilePresent)
+ c.Assert(filepath.Join(dirs.SnapBootstrapRunDir, s.sysLabel+"-model-measured"), testutil.FilePresent)
+ c.Check(*n, Equals, 5)
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsInstallModeStep1Measure(c *C) {
+ s.testInitramfsMountsInstallRecoverModeStep1Measure(c, "")
+}
+
+func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeStep1Measure(c *C) {
+ s.testInitramfsMountsInstallRecoverModeStep1Measure(c, "recover")
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/cmd_recovery_chooser_trigger.go snapd-2.45.1+18.04/cmd/snap-bootstrap/cmd_recovery_chooser_trigger.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/cmd_recovery_chooser_trigger.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/cmd_recovery_chooser_trigger.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,111 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public 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"
+ "time"
+
+ "github.com/jessevdk/go-flags"
+
+ "github.com/snapcore/snapd/cmd/snap-bootstrap/triggerwatch"
+ "github.com/snapcore/snapd/logger"
+)
+
+func init() {
+ const (
+ short = "Detect Ubuntu Core recovery chooser trigger"
+ long = ""
+ )
+
+ addCommandBuilder(func(parser *flags.Parser) {
+ if _, err := parser.AddCommand("recovery-chooser-trigger", short, long, &cmdRecoveryChooserTrigger{}); err != nil {
+ panic(err)
+ }
+ })
+}
+
+var (
+ triggerwatchWait = triggerwatch.Wait
+
+ // default trigger wait timeout
+ defaultTimeout = 10 * time.Second
+
+ // default marker file location
+ defaultMarkerFile = "/run/snapd-recovery-chooser-triggered"
+)
+
+type cmdRecoveryChooserTrigger struct {
+ MarkerFile string `long:"marker-file" value-name:"filename" description:"trigger marker file location"`
+ WaitTimeout string `long:"wait-timeout" value-name:"duration" description:"trigger wait timeout"`
+}
+
+func (c *cmdRecoveryChooserTrigger) Execute(args []string) error {
+ // TODO:UC20: check in the gadget if there is a hook or some binary we
+ // should run for trigger detection. This will require some design work
+ // and also thinking if/how such a hook can be confined.
+
+ timeout := defaultTimeout
+ markerFile := defaultMarkerFile
+
+ if c.WaitTimeout != "" {
+ userTimeout, err := time.ParseDuration(c.WaitTimeout)
+ if err != nil {
+ logger.Noticef("cannot parse duration %q, using default", c.WaitTimeout)
+ } else {
+ timeout = userTimeout
+ }
+ }
+ if c.MarkerFile != "" {
+ markerFile = c.MarkerFile
+ }
+ logger.Noticef("trigger timeout %v", timeout)
+ logger.Noticef("marker file %v", markerFile)
+
+ _, err := os.Stat(markerFile)
+ if err == nil {
+ logger.Noticef("marker already present")
+ return nil
+ }
+
+ err = triggerwatchWait(timeout)
+ if err != nil {
+ switch err {
+ case triggerwatch.ErrTriggerNotDetected:
+ logger.Noticef("trigger not detected")
+ return nil
+ case triggerwatch.ErrNoMatchingInputDevices:
+ logger.Noticef("no matching input devices")
+ return nil
+ default:
+ return err
+ }
+ }
+
+ // got the trigger, try to create the marker file
+ m, err := os.Create(markerFile)
+ if err != nil {
+ return fmt.Errorf("cannot create the marker file: %q", err)
+ }
+ m.Close()
+
+ return nil
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/cmd_recovery_chooser_trigger_test.go snapd-2.45.1+18.04/cmd/snap-bootstrap/cmd_recovery_chooser_trigger_test.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/cmd_recovery_chooser_trigger_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/cmd_recovery_chooser_trigger_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,165 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package main_test
+
+import (
+ "errors"
+ "io/ioutil"
+ "path/filepath"
+ "time"
+
+ . "gopkg.in/check.v1"
+
+ main "github.com/snapcore/snapd/cmd/snap-bootstrap"
+ "github.com/snapcore/snapd/cmd/snap-bootstrap/triggerwatch"
+ "github.com/snapcore/snapd/testutil"
+)
+
+func (s *cmdSuite) TestRecoveryChooserTriggerDefaults(c *C) {
+ n := 0
+ marker := filepath.Join(c.MkDir(), "marker")
+ passedTimeout := time.Duration(0)
+
+ restore := main.MockDefaultMarkerFile(marker)
+ defer restore()
+ restore = main.MockTriggerwatchWait(func(timeout time.Duration) error {
+ passedTimeout = timeout
+ n++
+ // trigger happened
+ return nil
+ })
+ defer restore()
+
+ rest, err := main.Parser().ParseArgs([]string{"recovery-chooser-trigger"})
+ c.Assert(err, IsNil)
+ c.Assert(rest, HasLen, 0)
+ c.Check(n, Equals, 1)
+ c.Check(passedTimeout, Equals, main.DefaultTimeout)
+ c.Check(marker, testutil.FilePresent)
+}
+
+func (s *cmdSuite) TestRecoveryChooserTriggerNoTrigger(c *C) {
+ n := 0
+ marker := filepath.Join(c.MkDir(), "marker")
+
+ restore := main.MockDefaultMarkerFile(marker)
+ defer restore()
+ restore = main.MockTriggerwatchWait(func(_ time.Duration) error {
+ n++
+ // trigger did not happen
+ return triggerwatch.ErrTriggerNotDetected
+ })
+ defer restore()
+
+ _, err := main.Parser().ParseArgs([]string{"recovery-chooser-trigger"})
+ c.Assert(err, IsNil)
+ c.Check(n, Equals, 1)
+ c.Check(marker, testutil.FileAbsent)
+}
+
+func (s *cmdSuite) TestRecoveryChooserTriggerTakesOptions(c *C) {
+ marker := filepath.Join(c.MkDir(), "foobar")
+ n := 0
+ passedTimeout := time.Duration(0)
+
+ restore := main.MockTriggerwatchWait(func(timeout time.Duration) error {
+ passedTimeout = timeout
+ n++
+ // trigger happened
+ return nil
+ })
+ defer restore()
+
+ rest, err := main.Parser().ParseArgs([]string{
+ "recovery-chooser-trigger",
+ "--wait-timeout", "2m",
+ "--marker-file", marker,
+ })
+ c.Assert(err, IsNil)
+ c.Assert(rest, HasLen, 0)
+ c.Check(n, Equals, 1)
+ c.Check(passedTimeout, Equals, 2*time.Minute)
+ c.Check(marker, testutil.FilePresent)
+}
+
+func (s *cmdSuite) TestRecoveryChooserTriggerDoesNothingWhenMarkerPresent(c *C) {
+ marker := filepath.Join(c.MkDir(), "foobar")
+ n := 0
+ restore := main.MockTriggerwatchWait(func(_ time.Duration) error {
+ n++
+ return errors.New("unexpected call")
+ })
+ defer restore()
+
+ err := ioutil.WriteFile(marker, nil, 0644)
+ c.Assert(err, IsNil)
+
+ rest, err := main.Parser().ParseArgs([]string{
+ "recovery-chooser-trigger",
+ "--marker-file", marker,
+ })
+ c.Assert(err, IsNil)
+ c.Assert(rest, HasLen, 0)
+ // not called
+ c.Check(n, Equals, 0)
+}
+
+func (s *cmdSuite) TestRecoveryChooserTriggerBadDurationFallback(c *C) {
+ n := 0
+ passedTimeout := time.Duration(0)
+ restore := main.MockDefaultMarkerFile(filepath.Join(c.MkDir(), "marker"))
+ defer restore()
+
+ restore = main.MockTriggerwatchWait(func(timeout time.Duration) error {
+ passedTimeout = timeout
+ n++
+ // trigger happened
+ return triggerwatch.ErrTriggerNotDetected
+ })
+ defer restore()
+
+ _, err := main.Parser().ParseArgs([]string{
+ "recovery-chooser-trigger",
+ "--wait-timeout=foobar",
+ })
+ c.Assert(err, IsNil)
+ c.Check(n, Equals, 1)
+ c.Check(passedTimeout, Equals, main.DefaultTimeout)
+}
+
+func (s *cmdSuite) TestRecoveryChooserTriggerNoInputDevsNoError(c *C) {
+ n := 0
+ marker := filepath.Join(c.MkDir(), "marker")
+
+ restore := main.MockDefaultMarkerFile(marker)
+ defer restore()
+ restore = main.MockTriggerwatchWait(func(_ time.Duration) error {
+ n++
+ // no input devices
+ return triggerwatch.ErrNoMatchingInputDevices
+ })
+ defer restore()
+
+ _, err := main.Parser().ParseArgs([]string{"recovery-chooser-trigger"})
+ // does not trigger an error
+ c.Assert(err, IsNil)
+ c.Check(n, Equals, 1)
+ c.Check(marker, testutil.FileAbsent)
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/export_test.go snapd-2.45.1+18.04/cmd/snap-bootstrap/export_test.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/export_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/export_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,145 @@
+// -*- 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 main
+
+import (
+ "io"
+ "time"
+
+ "github.com/snapcore/secboot"
+ "github.com/snapcore/snapd/asserts"
+
+ "github.com/snapcore/snapd/cmd/snap-bootstrap/bootstrap"
+)
+
+var (
+ Parser = parser
+)
+
+func MockBootstrapRun(f func(string, string, bootstrap.Options) error) (restore func()) {
+ oldBootstrapRun := bootstrapRun
+ bootstrapRun = f
+ return func() {
+ bootstrapRun = oldBootstrapRun
+ }
+}
+
+func MockStdout(newStdout io.Writer) (restore func()) {
+ oldStdout := stdout
+ stdout = newStdout
+ return func() {
+ stdout = oldStdout
+ }
+}
+
+func MockOsutilIsMounted(f func(path string) (bool, error)) (restore func()) {
+ oldOsutilIsMounted := osutilIsMounted
+ osutilIsMounted = f
+ return func() {
+ osutilIsMounted = oldOsutilIsMounted
+ }
+}
+
+func MockTriggerwatchWait(f func(_ time.Duration) error) (restore func()) {
+ oldTriggerwatchWait := triggerwatchWait
+ triggerwatchWait = f
+ return func() {
+ triggerwatchWait = oldTriggerwatchWait
+ }
+}
+
+var DefaultTimeout = defaultTimeout
+
+func MockDefaultMarkerFile(p string) (restore func()) {
+ old := defaultMarkerFile
+ defaultMarkerFile = p
+ return func() {
+ defaultMarkerFile = old
+ }
+}
+
+var (
+ UnlockIfEncrypted = unlockIfEncrypted
+)
+
+func MockSecbootConnectToDefaultTPM(f func() (*secboot.TPMConnection, error)) (restore func()) {
+ old := secbootConnectToDefaultTPM
+ secbootConnectToDefaultTPM = f
+ return func() {
+ secbootConnectToDefaultTPM = old
+ }
+}
+
+func MockSecbootLockAccessToSealedKeys(f func(tpm *secboot.TPMConnection) error) (restore func()) {
+ old := secbootLockAccessToSealedKeys
+ secbootLockAccessToSealedKeys = f
+ return func() {
+ secbootLockAccessToSealedKeys = old
+ }
+}
+
+func MockSecbootSecureConnectToDefaultTPM(f func(ekCertDataReader io.Reader,
+ endorsementAuth []byte) (*secboot.TPMConnection, error)) (restore func()) {
+ old := secbootSecureConnectToDefaultTPM
+ secbootSecureConnectToDefaultTPM = f
+ return func() {
+ secbootSecureConnectToDefaultTPM = old
+ }
+}
+
+func MockSecbootActivateVolumeWithTPMSealedKey(f func(tpm *secboot.TPMConnection, volumeName, sourceDevicePath, keyPath string, pinReader io.Reader, options *secboot.ActivateWithTPMSealedKeyOptions) (bool, error)) (restore func()) {
+ old := secbootActivateVolumeWithTPMSealedKey
+ secbootActivateVolumeWithTPMSealedKey = f
+ return func() {
+ secbootActivateVolumeWithTPMSealedKey = old
+ }
+}
+
+func MockDevDiskByLabelDir(new string) (restore func()) {
+ old := devDiskByLabelDir
+ devDiskByLabelDir = new
+ return func() {
+ devDiskByLabelDir = old
+ }
+}
+
+func MockRandomKernelUUID(new func() string) (restore func()) {
+ old := readKernelUUID
+ readKernelUUID = new
+ return func() {
+ readKernelUUID = old
+ }
+}
+
+func MockSecbootMeasureSnapSystemEpochToTPM(f func(tpm *secboot.TPMConnection, pcrIndex int) error) (restore func()) {
+ old := secbootMeasureSnapSystemEpochToTPM
+ secbootMeasureSnapSystemEpochToTPM = f
+ return func() {
+ secbootMeasureSnapSystemEpochToTPM = old
+ }
+}
+
+func MockSecbootMeasureSnapModelToTPM(f func(tpm *secboot.TPMConnection, pcrIndex int, model *asserts.Model) error) (restore func()) {
+ old := secbootMeasureSnapModelToTPM
+ secbootMeasureSnapModelToTPM = f
+ return func() {
+ secbootMeasureSnapModelToTPM = old
+ }
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/initramfs_mounts_state.go snapd-2.45.1+18.04/cmd/snap-bootstrap/initramfs_mounts_state.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/initramfs_mounts_state.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/initramfs_mounts_state.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,148 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public 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"
+ "os"
+ "path/filepath"
+
+ "github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/boot"
+ "github.com/snapcore/snapd/seed"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/timings"
+)
+
+// initramfsMountsState helps tracking the state and progress
+// of the mounting driving process.
+type initramfsMountsState interface {
+ // Model returns the verified model from the seed (only for
+ // modes other than run).
+ Model() (*asserts.Model, error)
+
+ // RecoverySystemEssentialSnaps returns the verified essential
+ // snaps from the recoverySystem. If recoverySystem is "" the
+ // implied one will be used (only for modes other than run).
+ RecoverySystemEssentialSnaps(recoverySystem string, essentialTypes []snap.Type) ([]*seed.Snap, error)
+
+ // UnverifiedBootModel returns the unverified model from the
+ // boot partition for run mode. The current and only use case
+ // is measuring the model for run mode. Otherwise no decisions
+ // should be based on an unverified model. Note that the model
+ // is verified at the time the key auth policy is computed.
+ UnverifiedBootModel() (*asserts.Model, error)
+}
+
+var newInitramfsMountsState = func(mode, recoverySystem string) initramfsMountsState {
+ return &initramfsMountsStateImpl{
+ mode: mode,
+ recoverySystem: recoverySystem,
+ }
+}
+
+type initramfsMountsStateImpl struct {
+ mode string
+ recoverySystem string
+
+ seed seed.EssentialMetaLoaderSeed
+}
+
+var errRunModeNoImpliedRecoverySystem = errors.New("internal error: no implied recovery system in run mode")
+
+// loadSeed opens the seed and reads its assertions; it does not
+// re-open or re-read the seed when called multiple times.
+// The opened seed is available is mst.seed
+func (mst *initramfsMountsStateImpl) loadSeed(recoverySystem string) error {
+ if mst.seed != nil {
+ return nil
+ }
+
+ if recoverySystem == "" {
+ if mst.mode == "run" {
+ return errRunModeNoImpliedRecoverySystem
+ }
+ recoverySystem = mst.recoverySystem
+ }
+
+ systemSeed, err := seed.Open(boot.InitramfsUbuntuSeedDir, recoverySystem)
+ if err != nil {
+ return err
+ }
+
+ seed20, ok := systemSeed.(seed.EssentialMetaLoaderSeed)
+ if !ok {
+ return fmt.Errorf("internal error: UC20 seed must implement EssentialMetaLoaderSeed")
+ }
+
+ // load assertions into a temporary database
+ if err := seed20.LoadAssertions(nil, nil); err != nil {
+ return err
+ }
+
+ mst.seed = seed20
+ return nil
+}
+
+func (mst *initramfsMountsStateImpl) Model() (*asserts.Model, error) {
+ if mst.mode == "run" {
+ return nil, errRunModeNoImpliedRecoverySystem
+ }
+ if err := mst.loadSeed(""); err != nil {
+ return nil, err
+ }
+ mod, _ := mst.seed.Model()
+ return mod, nil
+}
+
+func (mst *initramfsMountsStateImpl) RecoverySystemEssentialSnaps(recoverySystem string, essentialTypes []snap.Type) ([]*seed.Snap, error) {
+ if err := mst.loadSeed(recoverySystem); err != nil {
+ return nil, err
+ }
+
+ // load and verify metadata only for the relevant essential snaps
+ perf := timings.New(nil)
+ if err := mst.seed.LoadEssentialMeta(essentialTypes, perf); err != nil {
+ return nil, err
+ }
+
+ return mst.seed.EssentialSnaps(), nil
+}
+
+func (mst *initramfsMountsStateImpl) UnverifiedBootModel() (*asserts.Model, error) {
+ if mst.mode != "run" {
+ return nil, fmt.Errorf("internal error: unverified boot model access is for limited run mode use")
+ }
+
+ mf, err := os.Open(filepath.Join(boot.InitramfsUbuntuBootDir, "model"))
+ if err != nil {
+ return nil, fmt.Errorf("cannot read model assertion: %v", err)
+ }
+ defer mf.Close()
+ ma, err := asserts.NewDecoder(mf).Decode()
+ if err != nil {
+ return nil, fmt.Errorf("cannot decode assertion: %v", err)
+ }
+ if ma.Type() != asserts.ModelType {
+ return nil, fmt.Errorf("unexpected assertion: %q", ma.Type().Name)
+ }
+ return ma.(*asserts.Model), nil
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/main.go snapd-2.45.1+18.04/cmd/snap-bootstrap/main.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/main.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/main.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,84 @@
+// -*- 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"
+
+ "github.com/jessevdk/go-flags"
+
+ "github.com/snapcore/snapd/logger"
+)
+
+var (
+ shortHelp = "Bootstrap a Ubuntu Core system"
+ longHelp = `
+snap-bootstrap is a tool to bootstrap Ubuntu Core from ephemeral systems
+such as initramfs.
+`
+
+ opts struct{}
+ commandBuilders []func(*flags.Parser)
+)
+
+func init() {
+ err := logger.SimpleSetup()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "WARNING: failed to activate logging: %s\n", err)
+ }
+}
+
+func main() {
+ err := run(os.Args[1:])
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "error: %s\n", err)
+ os.Exit(1)
+ }
+}
+
+func run(args []string) error {
+ if os.Getuid() != 0 {
+ return fmt.Errorf("please run as root")
+ }
+ logger.SimpleSetup()
+ return parseArgs(args)
+}
+
+func parseArgs(args []string) error {
+ p := parser()
+
+ _, err := p.ParseArgs(args)
+ return err
+}
+
+func parser() *flags.Parser {
+ p := flags.NewParser(&opts, flags.HelpFlag|flags.PassDoubleDash|flags.PassAfterNonOption)
+ p.ShortDescription = shortHelp
+ p.LongDescription = longHelp
+ for _, builder := range commandBuilders {
+ builder(p)
+ }
+ return p
+}
+
+func addCommandBuilder(builder func(*flags.Parser)) {
+ commandBuilders = append(commandBuilders, builder)
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/main_test.go snapd-2.45.1+18.04/cmd/snap-bootstrap/main_test.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/main_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/main_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,50 @@
+// -*- 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 (
+ "testing"
+
+ . "gopkg.in/check.v1"
+
+ main "github.com/snapcore/snapd/cmd/snap-bootstrap"
+ "github.com/snapcore/snapd/logger"
+ "github.com/snapcore/snapd/testutil"
+)
+
+// Hook up check.v1 into the "go test" runner
+func Test(t *testing.T) { TestingT(t) }
+
+type cmdSuite struct {
+ testutil.BaseTest
+}
+
+var _ = Suite(&cmdSuite{})
+
+func (s *cmdSuite) SetUpTest(c *C) {
+ s.BaseTest.SetUpTest(c)
+ _, r := logger.MockLogger()
+ s.AddCleanup(r)
+}
+
+func (s *cmdSuite) TestNoArgsErrors(c *C) {
+ _, err := main.Parser().ParseArgs(nil)
+ c.Assert(err, ErrorMatches, "Please specify .*")
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/partition/deploy.go snapd-2.45.1+18.04/cmd/snap-bootstrap/partition/deploy.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/partition/deploy.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/partition/deploy.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,121 @@
+// -*- 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 partition
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strconv"
+ "syscall"
+
+ "github.com/snapcore/snapd/gadget"
+)
+
+var (
+ deployMountpoint = "/run/snap-recover"
+
+ sysMount = syscall.Mount
+ sysUnmount = syscall.Unmount
+)
+
+func deployFilesystemContent(part DeviceStructure, gadgetRoot string) (err error) {
+ mountpoint := filepath.Join(deployMountpoint, strconv.Itoa(part.Index))
+ if err := os.MkdirAll(mountpoint, 0755); err != nil {
+ return err
+ }
+
+ // temporarily mount the filesystem
+ if err := sysMount(part.Node, mountpoint, part.Filesystem, 0, ""); err != nil {
+ return fmt.Errorf("cannot mount filesystem %q at %q: %v", part.Node, mountpoint, err)
+ }
+ defer func() {
+ errUnmount := sysUnmount(mountpoint, 0)
+ if err == nil {
+ err = errUnmount
+ }
+ }()
+
+ fs, err := gadget.NewMountedFilesystemWriter(gadgetRoot, &part.LaidOutStructure)
+ if err != nil {
+ return fmt.Errorf("cannot create filesystem image writer: %v", err)
+ }
+
+ var preserveFiles []string
+ if err := fs.Write(mountpoint, preserveFiles); err != nil {
+ return fmt.Errorf("cannot create filesystem image: %v", err)
+ }
+
+ return nil
+}
+
+func deployNonFSContent(part DeviceStructure, gadgetRoot string) error {
+ f, err := os.OpenFile(part.Node, os.O_RDWR, 0644)
+ if err != nil {
+ return fmt.Errorf("cannot deploy bare content for %q: %v", part.Node, err)
+ }
+ defer f.Close()
+
+ // Laid out structures start relative to the beginning of the
+ // volume, shift the structure offsets to 0, so that it starts
+ // at the beginning of the partition
+ l := gadget.ShiftStructureTo(part.LaidOutStructure, 0)
+ raw, err := gadget.NewRawStructureWriter(gadgetRoot, &l)
+ if err != nil {
+ return err
+ }
+ return raw.Write(f)
+}
+
+func DeployContent(part DeviceStructure, gadgetRoot string) error {
+ switch {
+ case !part.IsPartition():
+ return fmt.Errorf("cannot deploy non-partitions yet")
+ case !part.HasFilesystem():
+ if err := deployNonFSContent(part, gadgetRoot); err != nil {
+ return err
+ }
+ case part.HasFilesystem():
+ if err := deployFilesystemContent(part, gadgetRoot); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func MountFilesystem(part DeviceStructure, baseMntPoint string) error {
+ if !part.HasFilesystem() {
+ return fmt.Errorf("cannot mount a partition with no filesystem")
+ }
+ if part.Label == "" {
+ return fmt.Errorf("cannot mount a filesystem with no label")
+ }
+
+ mountpoint := filepath.Join(baseMntPoint, part.Label)
+ if err := os.MkdirAll(mountpoint, 0755); err != nil {
+ return fmt.Errorf("cannot create mountpoint: %v", err)
+ }
+ if err := sysMount(part.Node, mountpoint, part.Filesystem, 0, ""); err != nil {
+ return fmt.Errorf("cannot mount filesystem %q at %q: %v", part.Node, mountpoint, err)
+ }
+
+ return nil
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/partition/deploy_test.go snapd-2.45.1+18.04/cmd/snap-bootstrap/partition/deploy_test.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/partition/deploy_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/partition/deploy_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,153 @@
+// -*- 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 partition_test
+
+import (
+ "fmt"
+ "io/ioutil"
+ "path/filepath"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/boot"
+ "github.com/snapcore/snapd/cmd/snap-bootstrap/partition"
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/gadget"
+ "github.com/snapcore/snapd/testutil"
+)
+
+type deploySuite struct {
+ testutil.BaseTest
+
+ gadgetRoot string
+
+ mockMountPoint string
+ mockMountCalls []struct{ source, target, fstype string }
+ mockUnmountCalls []string
+
+ mockMountErr error
+}
+
+var _ = Suite(&deploySuite{})
+
+func (s *deploySuite) SetUpTest(c *C) {
+ s.mockMountErr = nil
+ s.mockMountCalls = nil
+ s.mockUnmountCalls = nil
+
+ s.gadgetRoot = c.MkDir()
+ err := makeMockGadget(s.gadgetRoot, gadgetContent)
+ c.Assert(err, IsNil)
+
+ s.mockMountPoint = c.MkDir()
+ restore := partition.MockDeployMountpoint(s.mockMountPoint)
+ s.AddCleanup(restore)
+
+ restore = partition.MockSysMount(func(source, target, fstype string, flags uintptr, data string) error {
+ s.mockMountCalls = append(s.mockMountCalls, struct{ source, target, fstype string }{source, target, fstype})
+ return s.mockMountErr
+ })
+ s.AddCleanup(restore)
+
+ restore = partition.MockSysUnmount(func(target string, flags int) error {
+ s.mockUnmountCalls = append(s.mockUnmountCalls, target)
+ return nil
+ })
+ s.AddCleanup(restore)
+}
+
+func (s *deploySuite) TestDeployMountedContentErr(c *C) {
+ s.mockMountErr = fmt.Errorf("boom")
+
+ node2MountPoint := filepath.Join(s.mockMountPoint, "2")
+ err := partition.DeployContent(mockDeviceStructureSystemSeed, s.gadgetRoot)
+ c.Assert(err, ErrorMatches, fmt.Sprintf(`cannot mount filesystem "/dev/node2" at %q: boom`, node2MountPoint))
+}
+
+func (s *deploySuite) TestDeployMountedContent(c *C) {
+ err := partition.DeployContent(mockDeviceStructureSystemSeed, s.gadgetRoot)
+ c.Assert(err, IsNil)
+
+ node2MountPoint := filepath.Join(s.mockMountPoint, "2")
+ c.Check(s.mockMountCalls, DeepEquals, []struct{ source, target, fstype string }{
+ {"/dev/node2", node2MountPoint, "vfat"},
+ })
+ c.Check(s.mockUnmountCalls, DeepEquals, []string{node2MountPoint})
+
+ c.Check(filepath.Join(node2MountPoint, "EFI/boot/grubx64.efi"), testutil.FilePresent)
+ c.Assert(err, IsNil)
+}
+
+func (s *deploySuite) TestDeployRawContent(c *C) {
+ mockNode := filepath.Join(c.MkDir(), "mock-node")
+ err := ioutil.WriteFile(mockNode, nil, 0644)
+ c.Assert(err, IsNil)
+
+ // copy existing mock
+ m := mockDeviceStructureBiosBoot
+ m.Node = mockNode
+ m.LaidOutContent = []gadget.LaidOutContent{
+ {
+ VolumeContent: &gadget.VolumeContent{
+ Image: "pc-core.img",
+ },
+ StartOffset: 2,
+ Size: gadget.Size(len("pc-core.img content")),
+ },
+ }
+
+ err = partition.DeployContent(m, s.gadgetRoot)
+ c.Assert(err, IsNil)
+
+ content, err := ioutil.ReadFile(m.Node)
+ c.Assert(err, IsNil)
+ // note the 2 zero byte start offset
+ c.Check(string(content), Equals, "\x00\x00pc-core.img content")
+}
+
+func (s *deploySuite) TestMountFilesystem(c *C) {
+ dirs.SetRootDir(c.MkDir())
+ defer dirs.SetRootDir("")
+
+ // mounting will only happen for devices with a label
+ mockDeviceStructureBiosBoot.Label = "bios-boot"
+ defer func() { mockDeviceStructureBiosBoot.Label = "" }()
+
+ err := partition.MountFilesystem(mockDeviceStructureBiosBoot, boot.InitramfsRunMntDir)
+ c.Assert(err, ErrorMatches, "cannot mount a partition with no filesystem")
+
+ // mount a filesystem...
+ err = partition.MountFilesystem(mockDeviceStructureSystemSeed, boot.InitramfsRunMntDir)
+ c.Assert(err, IsNil)
+
+ // ...and check if it was mounted at the right mount point
+ c.Check(s.mockMountCalls, HasLen, 1)
+ c.Check(s.mockMountCalls, DeepEquals, []struct{ source, target, fstype string }{
+ {"/dev/node2", boot.InitramfsUbuntuSeedDir, "vfat"},
+ })
+
+ // now try to mount a filesystem with no label
+ mockDeviceStructureSystemSeed.Label = ""
+ defer func() { mockDeviceStructureSystemSeed.Label = "ubuntu-seed" }()
+
+ err = partition.MountFilesystem(mockDeviceStructureSystemSeed, boot.InitramfsRunMntDir)
+ c.Assert(err, ErrorMatches, "cannot mount a filesystem with no label")
+
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/partition/encrypt.go snapd-2.45.1+18.04/cmd/snap-bootstrap/partition/encrypt.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/partition/encrypt.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/partition/encrypt.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,224 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package partition
+
+import (
+ "bytes"
+ "crypto/rand"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "syscall"
+
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/osutil"
+)
+
+var (
+ tempFile = ioutil.TempFile
+)
+
+const (
+ // The encryption key size is set so it has the same entropy as the derived
+ // key. The recovery key is shorter and goes through KDF iterations.
+ encryptionKeySize = 64
+ recoveryKeySize = 16
+)
+
+type EncryptionKey [encryptionKeySize]byte
+
+func NewEncryptionKey() (EncryptionKey, error) {
+ var key EncryptionKey
+ // rand.Read() is protected against short reads
+ _, err := rand.Read(key[:])
+ // On return, n == len(b) if and only if err == nil
+ return key, err
+}
+
+type RecoveryKey [recoveryKeySize]byte
+
+func NewRecoveryKey() (RecoveryKey, error) {
+ var key RecoveryKey
+ // rand.Read() is protected against short reads
+ _, err := rand.Read(key[:])
+ // On return, n == len(b) if and only if err == nil
+ return key, err
+}
+
+// Store writes the recovery key in the location specified by filename.
+func (key RecoveryKey) Store(filename string) error {
+ if err := os.MkdirAll(filepath.Dir(filename), 0755); err != nil {
+ return err
+ }
+ return osutil.AtomicWriteFile(filename, key[:], 0600, 0)
+}
+
+// EncryptedDevice represents a LUKS-backed encrypted block device.
+type EncryptedDevice struct {
+ parent *DeviceStructure
+ name string
+ Node string
+}
+
+// NewEncryptedDevice creates an encrypted device in the existing partition using the
+// specified key.
+func NewEncryptedDevice(part *DeviceStructure, key EncryptionKey, name string) (*EncryptedDevice, error) {
+ dev := &EncryptedDevice{
+ parent: part,
+ name: name,
+ // A new block device is used to access the encrypted data. Note that
+ // you can't open an encrypted device under different names and a name
+ // can't be used in more than one device at the same time.
+ Node: fmt.Sprintf("/dev/mapper/%s", name),
+ }
+
+ if err := cryptsetupFormat(key, name+"-enc", part.Node); err != nil {
+ return nil, fmt.Errorf("cannot format encrypted device: %v", err)
+ }
+
+ if err := cryptsetupOpen(key, part.Node, name); err != nil {
+ return nil, fmt.Errorf("cannot open encrypted device on %s: %s", part.Node, err)
+ }
+
+ return dev, nil
+}
+
+func (dev *EncryptedDevice) AddRecoveryKey(key EncryptionKey, rkey RecoveryKey) error {
+ return cryptsetupAddKey(key, rkey, dev.parent.Node)
+}
+
+func (dev *EncryptedDevice) Close() error {
+ return cryptsetupClose(dev.name)
+}
+
+func cryptsetupFormat(key EncryptionKey, label, node string) error {
+ // We use a keyfile with the same entropy as the derived key so we can
+ // keep the KDF iteration count to a minimum. Longer processing will not
+ // increase security in this case.
+ args := []string{
+ // batch processing, no password verification
+ "-q",
+ // formatting a new device
+ "luksFormat",
+ // use LUKS2
+ "--type", "luks2",
+ // read key from stdin
+ "--key-file", "-",
+ // use AES-256 with XTS block cipher mode (XTS requires 2 keys)
+ "--cipher", "aes-xts-plain64", "--key-size", "512",
+ // use --iter-time 1 with the default KDF argon2i so
+ // to do virtually no derivation, here key is a random
+ // key with good entropy, not a passphrase, so
+ // spending time deriving from it is not necessary or
+ // makes sense
+ "--pbkdf", "argon2i", "--iter-time", "1",
+ // set LUKS2 label
+ "--label", label,
+ // device to format
+ node,
+ }
+ cmd := exec.Command("cryptsetup", args...)
+ cmd.Stdin = bytes.NewReader(key[:])
+ if output, err := cmd.CombinedOutput(); err != nil {
+ return osutil.OutputErr(output, err)
+ }
+ return nil
+}
+
+func cryptsetupOpen(key EncryptionKey, node, name string) error {
+ cmd := exec.Command("cryptsetup", "open", "--key-file", "-", node, name)
+ cmd.Stdin = bytes.NewReader(key[:])
+ if output, err := cmd.CombinedOutput(); err != nil {
+ return osutil.OutputErr(output, err)
+ }
+ return nil
+}
+
+func cryptsetupClose(name string) error {
+ if output, err := exec.Command("cryptsetup", "close", name).CombinedOutput(); err != nil {
+ return osutil.OutputErr(output, err)
+ }
+ return nil
+}
+
+func cryptsetupAddKey(key EncryptionKey, rkey RecoveryKey, node string) error {
+ // create a named pipe to pass the recovery key
+ fpath := filepath.Join(dirs.SnapRunDir, "tmp-rkey")
+ if err := os.MkdirAll(dirs.SnapRunDir, 0755); err != nil {
+ return err
+ }
+ if err := syscall.Mkfifo(fpath, 0600); err != nil {
+ return fmt.Errorf("cannot create named pipe: %v", err)
+ }
+ defer os.RemoveAll(fpath)
+
+ // add a new key to slot 1 reading the passphrase from the named pipe
+ // (explicitly choose keyslot 1 to ensure we have a predictable slot
+ // number in case we decide to kill all other slots later)
+ args := []string{
+ // add a new key
+ "luksAddKey",
+ // the encrypted device
+ node,
+ // batch processing, no password verification
+ "-q",
+ // read existing key from stdin
+ "--key-file", "-",
+ // store it in keyslot 1
+ "--key-slot", "1",
+ // the named pipe
+ fpath,
+ }
+
+ cmd := exec.Command("cryptsetup", args...)
+ cmd.Stdin = bytes.NewReader(key[:])
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Start(); err != nil {
+ return err
+ }
+
+ // open the named pipe and write the recovery key
+ file, err := os.OpenFile(fpath, os.O_WRONLY, 0600)
+ if err != nil {
+ return fmt.Errorf("cannot open recovery key pipe: %v", err)
+ }
+ n, err := file.Write(rkey[:])
+ if n != recoveryKeySize {
+ file.Close()
+ return fmt.Errorf("cannot write recovery key: short write (%d bytes written)", n)
+ }
+ if err != nil {
+ cmd.Process.Kill()
+ file.Close()
+ return fmt.Errorf("cannot write recovery key: %v", err)
+ }
+ if err := file.Close(); err != nil {
+ cmd.Process.Kill()
+ return fmt.Errorf("cannot close recovery key pipe: %v", err)
+ }
+ if err := cmd.Wait(); err != nil {
+ return fmt.Errorf("cannot add recovery key: %v", err)
+ }
+
+ return nil
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/partition/encrypt_test.go snapd-2.45.1+18.04/cmd/snap-bootstrap/partition/encrypt_test.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/partition/encrypt_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/partition/encrypt_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,148 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package partition_test
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/cmd/snap-bootstrap/partition"
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/gadget"
+ "github.com/snapcore/snapd/testutil"
+)
+
+type encryptSuite struct {
+ testutil.BaseTest
+
+ mockCryptsetup *testutil.MockCmd
+}
+
+var _ = Suite(&encryptSuite{})
+
+var mockDeviceStructure = partition.DeviceStructure{
+ LaidOutStructure: gadget.LaidOutStructure{
+ VolumeStructure: &gadget.VolumeStructure{
+ Name: "Test structure",
+ Size: 0x100000,
+ },
+ StartOffset: 0,
+ Index: 1,
+ },
+ Node: "/dev/node1",
+}
+
+func (s *encryptSuite) SetUpTest(c *C) {
+ dirs.SetRootDir(c.MkDir())
+ c.Assert(os.MkdirAll(dirs.SnapRunDir, 0755), IsNil)
+}
+
+func (s *encryptSuite) TestEncryptHappy(c *C) {
+ s.mockCryptsetup = testutil.MockCommand(c, "cryptsetup", "")
+ s.AddCleanup(s.mockCryptsetup.Restore)
+
+ // create empty key to prevent blocking on lack of system entropy
+ key := partition.EncryptionKey{}
+ dev, err := partition.NewEncryptedDevice(&mockDeviceStructure, key, "some-label")
+ c.Assert(err, IsNil)
+ c.Assert(dev.Node, Equals, "/dev/mapper/some-label")
+
+ c.Assert(s.mockCryptsetup.Calls(), DeepEquals, [][]string{
+ {
+ "cryptsetup", "-q", "luksFormat", "--type", "luks2", "--key-file", "-",
+ "--cipher", "aes-xts-plain64", "--key-size", "512", "--pbkdf", "argon2i",
+ "--iter-time", "1", "--label", "some-label-enc", "/dev/node1",
+ },
+ {
+ "cryptsetup", "open", "--key-file", "-", "/dev/node1", "some-label",
+ },
+ })
+
+ err = dev.Close()
+ c.Assert(err, IsNil)
+}
+
+func (s *encryptSuite) TestEncryptFormatError(c *C) {
+ s.mockCryptsetup = testutil.MockCommand(c, "cryptsetup", `[ "$2" == "luksFormat" ] && exit 127 || exit 0`)
+ s.AddCleanup(s.mockCryptsetup.Restore)
+
+ key := partition.EncryptionKey{}
+ _, err := partition.NewEncryptedDevice(&mockDeviceStructure, key, "some-label")
+ c.Assert(err, ErrorMatches, "cannot format encrypted device:.*")
+}
+
+func (s *encryptSuite) TestEncryptOpenError(c *C) {
+ s.mockCryptsetup = testutil.MockCommand(c, "cryptsetup", `[ "$1" == "open" ] && exit 127 || exit 0`)
+ s.AddCleanup(s.mockCryptsetup.Restore)
+
+ key := partition.EncryptionKey{}
+ _, err := partition.NewEncryptedDevice(&mockDeviceStructure, key, "some-label")
+ c.Assert(err, ErrorMatches, "cannot open encrypted device on /dev/node1:.*")
+}
+
+func (s *encryptSuite) TestEncryptAddKey(c *C) {
+ capturedFifo := filepath.Join(c.MkDir(), "captured-stdin")
+ s.mockCryptsetup = testutil.MockCommand(c, "cryptsetup", fmt.Sprintf(`[ "$1" == "luksAddKey" ] && cat %s/tmp-rkey > %s || exit 0`, dirs.SnapRunDir, capturedFifo))
+ s.AddCleanup(s.mockCryptsetup.Restore)
+
+ key := partition.EncryptionKey{}
+ dev, err := partition.NewEncryptedDevice(&mockDeviceStructure, key, "some-label")
+ c.Assert(err, IsNil)
+
+ rkey := partition.RecoveryKey{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
+ err = dev.AddRecoveryKey(key, rkey)
+ c.Assert(err, IsNil)
+
+ c.Assert(s.mockCryptsetup.Calls(), DeepEquals, [][]string{
+ {
+ "cryptsetup", "-q", "luksFormat", "--type", "luks2", "--key-file", "-",
+ "--cipher", "aes-xts-plain64", "--key-size", "512", "--pbkdf", "argon2i",
+ "--iter-time", "1", "--label", "some-label-enc", "/dev/node1",
+ },
+ {
+ "cryptsetup", "open", "--key-file", "-", "/dev/node1", "some-label",
+ },
+ {
+ "cryptsetup", "luksAddKey", "/dev/node1", "-q", "--key-file", "-",
+ "--key-slot", "1", filepath.Join(dirs.SnapRunDir, "tmp-rkey"),
+ },
+ })
+ c.Assert(capturedFifo, testutil.FileEquals, rkey[:])
+
+ err = dev.Close()
+ c.Assert(err, IsNil)
+}
+
+func (s *encryptSuite) TestRecoveryKeyStore(c *C) {
+ rkey := partition.RecoveryKey{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 255}
+ err := rkey.Store("test-key")
+ c.Assert(err, IsNil)
+ fileInfo, err := os.Stat("test-key")
+ c.Assert(err, IsNil)
+ c.Assert(fileInfo.Mode(), Equals, os.FileMode(0600))
+ data, err := ioutil.ReadFile("test-key")
+ c.Assert(err, IsNil)
+ c.Assert(data, DeepEquals, rkey[:])
+ os.Remove("test-key")
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/partition/export_test.go snapd-2.45.1+18.04/cmd/snap-bootstrap/partition/export_test.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/partition/export_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/partition/export_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,70 @@
+// -*- 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 partition
+
+import (
+ "time"
+)
+
+type LsblkFilesystemInfo = lsblkFilesystemInfo
+type LsblkBlockDevice = lsblkBlockDevice
+type SFDiskPartitionTable = sfdiskPartitionTable
+type SFDiskPartition = sfdiskPartition
+
+var (
+ FilesystemInfo = filesystemInfo
+ BuildPartitionList = buildPartitionList
+ Mkfs = mkfs
+ EnsureNodesExist = ensureNodesExist
+ DeviceLayoutFromPartitionTable = deviceLayoutFromPartitionTable
+ ListCreatedPartitions = listCreatedPartitions
+)
+
+func MockDeployMountpoint(new string) (restore func()) {
+ old := deployMountpoint
+ deployMountpoint = new
+ return func() {
+ deployMountpoint = old
+ }
+}
+
+func MockSysMount(f func(source, target, fstype string, flags uintptr, data string) error) (restore func()) {
+ old := sysMount
+ sysMount = f
+ return func() {
+ sysMount = old
+ }
+}
+
+func MockSysUnmount(f func(target string, flags int) error) (restore func()) {
+ old := sysUnmount
+ sysUnmount = f
+ return func() {
+ sysUnmount = old
+ }
+}
+
+func MockEnsureNodesExist(f func(ds []DeviceStructure, timeout time.Duration) error) (restore func()) {
+ old := ensureNodesExist
+ ensureNodesExist = f
+ return func() {
+ ensureNodesExist = old
+ }
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/partition/mkfs.go snapd-2.45.1+18.04/cmd/snap-bootstrap/partition/mkfs.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/partition/mkfs.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/partition/mkfs.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,55 @@
+// -*- 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 partition
+
+import (
+ "fmt"
+
+ "github.com/snapcore/snapd/gadget"
+)
+
+func MakeFilesystem(part DeviceStructure) error {
+ if part.VolumeStructure == nil {
+ return fmt.Errorf("cannot use incomplete device %v", part.Node)
+ }
+
+ if part.VolumeStructure.Filesystem != "" {
+ if err := mkfs(part.Node, part.VolumeStructure.Label, part.VolumeStructure.Filesystem); err != nil {
+ return err
+ }
+ if err := udevTrigger(part.Node); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// mkfs will create a single filesystem on the given node with
+// the given label and filesystem type.
+func mkfs(node, label, filesystem string) error {
+ switch filesystem {
+ case "vfat":
+ return gadget.MkfsVfat(node, label, "")
+ case "ext4":
+ return gadget.MkfsExt4(node, label, "")
+ default:
+ return fmt.Errorf("cannot create unsupported filesystem %q", filesystem)
+ }
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/partition/mkfs_test.go snapd-2.45.1+18.04/cmd/snap-bootstrap/partition/mkfs_test.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/partition/mkfs_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/partition/mkfs_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,148 @@
+// -*- 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 partition_test
+
+import (
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/cmd/snap-bootstrap/partition"
+ "github.com/snapcore/snapd/gadget"
+ "github.com/snapcore/snapd/testutil"
+)
+
+type mkfsSuite struct {
+ testutil.BaseTest
+
+ mockMkfsVfat *testutil.MockCmd
+ mockMkfsExt4 *testutil.MockCmd
+ mockUdevadm *testutil.MockCmd
+}
+
+var _ = Suite(&mkfsSuite{})
+
+func (s *mkfsSuite) SetUpTest(c *C) {
+ // some commads use fakeroot, mock one that just calls the other script
+ cmdFakeroot := testutil.MockCommand(c, "fakeroot", `exec "$@"`)
+ s.AddCleanup(cmdFakeroot.Restore)
+ s.mockMkfsVfat = testutil.MockCommand(c, "mkfs.vfat", "")
+ s.AddCleanup(s.mockMkfsVfat.Restore)
+ s.mockMkfsExt4 = testutil.MockCommand(c, "mkfs.ext4", "")
+ s.AddCleanup(s.mockMkfsExt4.Restore)
+ s.mockUdevadm = testutil.MockCommand(c, "udevadm", "")
+ s.AddCleanup(s.mockUdevadm.Restore)
+}
+
+func (s *mkfsSuite) TestMkfsUnhappy(c *C) {
+ err := partition.Mkfs("/dev/node", "some-label", "unsupported-filesystem-type")
+ c.Assert(err, ErrorMatches, `cannot create unsupported filesystem "unsupported-filesystem-type"`)
+}
+
+func (s *mkfsSuite) TestMkfsVfat(c *C) {
+ err := partition.Mkfs("/dev/node", "some-label", "vfat")
+ c.Assert(err, IsNil)
+ // details are already tested in the gadget package
+ c.Assert(s.mockMkfsVfat.Calls(), HasLen, 1)
+}
+
+func (s *mkfsSuite) TestMkfsExt4(c *C) {
+ err := partition.Mkfs("/dev/node", "some-label", "ext4")
+ c.Assert(err, IsNil)
+ // details are already tested in the gadget package
+ c.Assert(s.mockMkfsExt4.Calls(), HasLen, 1)
+}
+
+func (s *mkfsSuite) TestMakefilesystemNothing(c *C) {
+ part := partition.DeviceStructure{}
+ err := partition.MakeFilesystem(part)
+ c.Assert(err, ErrorMatches, "cannot use incomplete device ")
+ c.Assert(s.mockMkfsExt4.Calls(), HasLen, 0)
+ c.Assert(s.mockMkfsVfat.Calls(), HasLen, 0)
+ c.Assert(s.mockUdevadm.Calls(), HasLen, 0)
+}
+
+func (s *mkfsSuite) TestMakefilesystem(c *C) {
+ created := []partition.DeviceStructure{
+ {
+ Node: "/dev/node2",
+ LaidOutStructure: gadget.LaidOutStructure{
+ VolumeStructure: &gadget.VolumeStructure{
+ Name: "Recovery",
+ Size: 1258291200,
+ Type: "EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
+ Role: "system-seed",
+ Filesystem: "vfat",
+ Content: []gadget.VolumeContent{
+ {
+ Source: "grubx64.efi",
+ Target: "EFI/boot/grubx64.efi",
+ },
+ },
+ },
+ StartOffset: 2097152,
+ Index: 2,
+ },
+ }, {
+ Node: "/dev/node3",
+ LaidOutStructure: gadget.LaidOutStructure{
+ VolumeStructure: &gadget.VolumeStructure{
+ Name: "Writable",
+ Size: 1258291200,
+ Type: "83,0FC63DAF-8483-4772-8E79-3D69D8477DE4",
+ Role: "system-data",
+ Filesystem: "ext4",
+ },
+ StartOffset: 1260388352,
+ Index: 3,
+ }}, {
+ Node: "/dev/node4",
+ LaidOutStructure: gadget.LaidOutStructure{
+ VolumeStructure: &gadget.VolumeStructure{
+ Name: "Writable",
+ Size: 12345,
+ Type: "83,0FC63DAF-8483-4772-8E79-3D69D8477DE4",
+ Role: "system-save",
+ Filesystem: "ext4",
+ },
+ StartOffset: 1260388352 + 1258291200,
+ Index: 4,
+ },
+ },
+ }
+
+ // single fat partition is created first, then 2 ext4 partitions
+ for n, part := range created {
+ err := partition.MakeFilesystem(part)
+ c.Assert(err, IsNil)
+ c.Assert(s.mockMkfsVfat.Calls(), HasLen, 1)
+ c.Assert(s.mockMkfsExt4.Calls(), HasLen, n)
+ }
+
+ // ensure ordering is correct
+ calls := s.mockMkfsExt4.Calls()[0]
+ c.Assert(calls[len(calls)-1:], DeepEquals, []string{"/dev/node3"})
+ calls = s.mockMkfsExt4.Calls()[1]
+ c.Assert(calls[len(calls)-1:], DeepEquals, []string{"/dev/node4"})
+ c.Assert(s.mockMkfsVfat.Calls(), HasLen, 1)
+ c.Assert(s.mockUdevadm.Calls(), DeepEquals, [][]string{
+ {"udevadm", "trigger", "--settle", "/dev/node2"},
+ {"udevadm", "trigger", "--settle", "/dev/node3"},
+ {"udevadm", "trigger", "--settle", "/dev/node4"},
+ })
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/partition/partition_test.go snapd-2.45.1+18.04/cmd/snap-bootstrap/partition/partition_test.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/partition/partition_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/partition/partition_test.go 2020-06-05 13:13:49.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 partition_test
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "testing"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/cmd/snap-bootstrap/partition"
+ "github.com/snapcore/snapd/gadget"
+ "github.com/snapcore/snapd/testutil"
+)
+
+func TestPartition(t *testing.T) { TestingT(t) }
+
+type partitionTestSuite struct {
+ testutil.BaseTest
+
+ dir string
+}
+
+var _ = Suite(&partitionTestSuite{})
+
+func (s *partitionTestSuite) SetUpTest(c *C) {
+ s.BaseTest.SetUpTest(c)
+
+ s.dir = c.MkDir()
+}
+
+var mockDeviceStructureBiosBoot = partition.DeviceStructure{
+ Node: "/dev/node1",
+ LaidOutStructure: gadget.LaidOutStructure{
+ VolumeStructure: &gadget.VolumeStructure{
+ Name: "BIOS Boot",
+ Size: 1 * 1024 * 1024,
+ Type: "DA,21686148-6449-6E6F-744E-656564454649",
+ Content: []gadget.VolumeContent{
+ {
+ Image: "pc-core.img",
+ },
+ },
+ },
+ StartOffset: 0,
+ Index: 1,
+ },
+}
+
+var mockDeviceStructureSystemSeed = partition.DeviceStructure{
+ Node: "/dev/node2",
+ LaidOutStructure: gadget.LaidOutStructure{
+ VolumeStructure: &gadget.VolumeStructure{
+ Name: "Recovery",
+ Size: 1258291200,
+ Type: "EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
+ Role: "system-seed",
+ Label: "ubuntu-seed",
+ Filesystem: "vfat",
+ Content: []gadget.VolumeContent{
+ {
+ Source: "grubx64.efi",
+ Target: "EFI/boot/grubx64.efi",
+ },
+ },
+ },
+ StartOffset: 2097152,
+ Index: 2,
+ },
+}
+
+var mockDeviceStructureWritable = partition.DeviceStructure{
+ Node: "/dev/node3",
+ CreatedDuringInstall: true,
+ LaidOutStructure: gadget.LaidOutStructure{
+ VolumeStructure: &gadget.VolumeStructure{
+ Name: "Writable",
+ Size: 1258291200,
+ Type: "83,0FC63DAF-8483-4772-8E79-3D69D8477DE4",
+ Role: "system-data",
+ Label: "ubuntu-data",
+ Filesystem: "ext4",
+ },
+ StartOffset: 1260388352,
+ Index: 3,
+ },
+}
+
+func makeMockGadget(gadgetRoot, gadgetContent string) error {
+ if err := os.MkdirAll(filepath.Join(gadgetRoot, "meta"), 0755); err != nil {
+ return err
+ }
+ if err := ioutil.WriteFile(filepath.Join(gadgetRoot, "meta", "gadget.yaml"), []byte(gadgetContent), 0644); err != nil {
+ return err
+ }
+ if err := ioutil.WriteFile(filepath.Join(gadgetRoot, "pc-boot.img"), []byte("pc-boot.img content"), 0644); err != nil {
+ return err
+ }
+ if err := ioutil.WriteFile(filepath.Join(gadgetRoot, "pc-core.img"), []byte("pc-core.img content"), 0644); err != nil {
+ return err
+ }
+ if err := ioutil.WriteFile(filepath.Join(gadgetRoot, "grubx64.efi"), []byte("grubx64.efi content"), 0644); err != nil {
+ return err
+ }
+
+ return nil
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/partition/sfdisk.go snapd-2.45.1+18.04/cmd/snap-bootstrap/partition/sfdisk.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/partition/sfdisk.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/partition/sfdisk.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,538 @@
+// -*- 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 partition
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "os/exec"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/snapcore/snapd/gadget"
+ "github.com/snapcore/snapd/logger"
+ "github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/strutil"
+)
+
+const (
+ ubuntuBootLabel = "ubuntu-boot"
+ ubuntuSeedLabel = "ubuntu-seed"
+ ubuntuDataLabel = "ubuntu-data"
+
+ sectorSize gadget.Size = 512
+
+ createdPartitionAttr = "59"
+)
+
+var createdPartitionGUID = []string{
+ "0FC63DAF-8483-4772-8E79-3D69D8477DE4", // Linux filesystem data
+ "0657FD6D-A4AB-43C4-84E5-0933C84B4F4F", // Linux swap partition
+}
+
+// creationSupported returns whether we support and expect to create partitions
+// of the given type, it also means we are ready to remove them for re-installation
+// or retried installation if they are appropriately marked with createdPartitionAttr.
+func creationSupported(ptype string) bool {
+ return strutil.ListContains(createdPartitionGUID, strings.ToUpper(ptype))
+}
+
+// sfdiskDeviceDump represents the sfdisk --dump JSON output format.
+type sfdiskDeviceDump struct {
+ PartitionTable sfdiskPartitionTable `json:"partitiontable"`
+}
+
+type sfdiskPartitionTable struct {
+ Label string `json:"label"`
+ ID string `json:"id"`
+ Device string `json:"device"`
+ Unit string `json:"unit"`
+ FirstLBA uint64 `json:"firstlba"`
+ LastLBA uint64 `json:"lastlba"`
+ Partitions []sfdiskPartition `json:"partitions"`
+}
+
+type sfdiskPartition struct {
+ Node string `json:"node"`
+ Start uint64 `json:"start"`
+ Size uint64 `json:"size"`
+ // List of GPT partition attributes in [ ] format, numeric attributes
+ // are listed as GUID:[,]. Note that the even though the sfdisk(8) manpage
+ // says --part-attrs takes a space or comma separated list, the output from
+ // --json/--dump uses a different format.
+ Attrs string `json:"attrs"`
+ Type string `json:"type"`
+ UUID string `json:"uuid"`
+ Name string `json:"name"`
+}
+
+func isCreatedDuringInstall(p *sfdiskPartition, fs *lsblkBlockDevice, sfdiskLabel string) bool {
+ switch sfdiskLabel {
+ case "gpt":
+ // the created partitions use specific GPT GUID types and set a
+ // specific bit in partition attributes
+ if !creationSupported(p.Type) {
+ return false
+ }
+ for _, a := range strings.Fields(p.Attrs) {
+ if !strings.HasPrefix(a, "GUID:") {
+ continue
+ }
+ attrs := strings.Split(a[5:], ",")
+ if strutil.ListContains(attrs, createdPartitionAttr) {
+ return true
+ }
+ }
+ case "dos":
+ // we have no similar type/bit attribute setting for MBR, on top
+ // of that MBR does not support partition names, fall back to
+ // reasonable assumption that only partitions carrying
+ // ubuntu-boot and ubuntu-data labels are created during
+ // install, everything else was part of factory image
+
+ // TODO:UC20 consider using gadget layout information to build a
+ // mapping of partition start offset to label/name
+ createdDuringInstall := []string{ubuntuBootLabel, ubuntuDataLabel}
+ return strutil.ListContains(createdDuringInstall, fs.Label)
+ }
+ return false
+}
+
+type DeviceLayout struct {
+ Structure []DeviceStructure
+ ID string
+ Device string
+ Schema string
+ // size in bytes
+ Size gadget.Size
+ // sector size in bytes
+ SectorSize gadget.Size
+ partitionTable *sfdiskPartitionTable
+}
+
+type DeviceStructure struct {
+ gadget.LaidOutStructure
+
+ Node string
+ CreatedDuringInstall bool
+}
+
+// DeviceLayoutFromDisk obtains the partitioning and filesystem information from
+// the block device.
+func DeviceLayoutFromDisk(device string) (*DeviceLayout, error) {
+ output, err := exec.Command("sfdisk", "--json", "-d", device).Output()
+ if err != nil {
+ return nil, osutil.OutputErr(output, err)
+ }
+
+ var dump sfdiskDeviceDump
+ if err := json.Unmarshal(output, &dump); err != nil {
+ return nil, fmt.Errorf("cannot parse sfdisk output: %v", err)
+ }
+
+ dl, err := deviceLayoutFromPartitionTable(dump.PartitionTable)
+ if err != nil {
+ return nil, err
+ }
+ dl.Device = device
+
+ return dl, nil
+}
+
+var (
+ ensureNodesExist = ensureNodesExistImpl
+)
+
+// CreateMissing creates the partitions listed in the positioned volume pv
+// that are missing from the existing device layout.
+func (dl *DeviceLayout) CreateMissing(pv *gadget.LaidOutVolume) ([]DeviceStructure, error) {
+ buf, created := buildPartitionList(dl, pv)
+ if len(created) == 0 {
+ return created, nil
+ }
+
+ // Write the partition table. By default sfdisk will try to re-read the
+ // partition table with the BLKRRPART ioctl but will fail because the
+ // kernel side rescan removes and adds partitions and we have partitions
+ // mounted (so it fails on removal). Use --no-reread to skip this attempt.
+ cmd := exec.Command("sfdisk", "--append", "--no-reread", dl.Device)
+ cmd.Stdin = buf
+ if output, err := cmd.CombinedOutput(); err != nil {
+ return created, osutil.OutputErr(output, err)
+ }
+
+ // Re-read the partition table
+ if err := reloadPartitionTable(dl.Device); err != nil {
+ return nil, err
+ }
+
+ // Make sure the devices for the partitions we created are available
+ if err := ensureNodesExist(created, 5*time.Second); err != nil {
+ return nil, fmt.Errorf("partition not available: %v", err)
+ }
+
+ return created, nil
+}
+
+// RemoveCreated removes partitions added during a previous failed install
+// attempt.
+func (dl *DeviceLayout) RemoveCreated() error {
+ indexes := make([]string, 0, len(dl.partitionTable.Partitions))
+ for i, s := range dl.Structure {
+ if s.CreatedDuringInstall {
+ logger.Noticef("partition %s was created during previous install", s.Node)
+ indexes = append(indexes, strconv.Itoa(i+1))
+ }
+ }
+ if len(indexes) == 0 {
+ return nil
+ }
+
+ // Delete disk partitions
+ cmd := exec.Command("sfdisk", append([]string{"--no-reread", "--delete", dl.Device}, indexes...)...)
+ if output, err := cmd.CombinedOutput(); err != nil {
+ return osutil.OutputErr(output, err)
+ }
+
+ // Reload the partition table
+ if err := reloadPartitionTable(dl.Device); err != nil {
+ return err
+ }
+
+ // Re-read the partition table from the device to update our partition list
+ layout, err := DeviceLayoutFromDisk(dl.Device)
+ if err != nil {
+ return fmt.Errorf("cannot read disk layout: %v", err)
+ }
+ if dl.ID != layout.ID {
+ return fmt.Errorf("partition table IDs don't match")
+ }
+ dl.Structure = layout.Structure
+ dl.partitionTable = layout.partitionTable
+
+ // Ensure all created partitions were removed
+ if remaining := listCreatedPartitions(layout); len(remaining) > 0 {
+ return fmt.Errorf("cannot remove partitions: %s", strings.Join(remaining, ", "))
+ }
+
+ return nil
+}
+
+// ensureNodeExists makes sure the device nodes for all device structures are
+// available and notified to udev, within a specified amount of time.
+func ensureNodesExistImpl(ds []DeviceStructure, timeout time.Duration) error {
+ t0 := time.Now()
+ for _, part := range ds {
+ found := false
+ for time.Since(t0) < timeout {
+ if osutil.FileExists(part.Node) {
+ found = true
+ break
+ }
+ time.Sleep(100 * time.Millisecond)
+ }
+ if found {
+ if err := udevTrigger(part.Node); err != nil {
+ return err
+ }
+ } else {
+ return fmt.Errorf("device %s not available", part.Node)
+ }
+ }
+ return nil
+}
+
+func fromSfdiskPartitionType(st string, sfdiskLabel string) (string, error) {
+ switch sfdiskLabel {
+ case "dos":
+ // sometimes sfdisk reports what is "0C" in gadget.yaml as "c",
+ // normalize the values
+ v, err := strconv.ParseUint(st, 16, 8)
+ if err != nil {
+ return "", fmt.Errorf("cannot convert MBR partition type %q", st)
+ }
+ return fmt.Sprintf("%02X", v), nil
+ case "gpt":
+ return st, nil
+ default:
+ return "", fmt.Errorf("unsupported partitioning schema %q", sfdiskLabel)
+ }
+}
+
+func blockDeviceSizeInSectors(devpath string) (gadget.Size, error) {
+ // the size is reported in 512-byte sectors
+ // XXX: consider using /sys/block//size directly
+ out, err := exec.Command("blockdev", "--getsz", devpath).CombinedOutput()
+ if err != nil {
+ return 0, osutil.OutputErr(out, err)
+ }
+ nospace := strings.TrimSpace(string(out))
+ sz, err := strconv.Atoi(nospace)
+ if err != nil {
+ return 0, fmt.Errorf("cannot parse device size %q: %v", nospace, err)
+ }
+ return gadget.Size(sz), nil
+}
+
+// deviceLayoutFromPartitionTable takes an sfdisk dump partition table and returns
+// the partitioning information as a device layout.
+func deviceLayoutFromPartitionTable(ptable sfdiskPartitionTable) (*DeviceLayout, error) {
+ if ptable.Unit != "sectors" {
+ return nil, fmt.Errorf("cannot position partitions: unknown unit %q", ptable.Unit)
+ }
+
+ structure := make([]gadget.VolumeStructure, len(ptable.Partitions))
+ ds := make([]DeviceStructure, len(ptable.Partitions))
+
+ for i, p := range ptable.Partitions {
+ info, err := filesystemInfo(p.Node)
+ if err != nil {
+ return nil, fmt.Errorf("cannot obtain filesystem information: %v", err)
+ }
+ switch {
+ case len(info.BlockDevices) == 0:
+ continue
+ case len(info.BlockDevices) > 1:
+ return nil, fmt.Errorf("unexpected number of blockdevices for node %q: %v", p.Node, info.BlockDevices)
+ }
+ bd := info.BlockDevices[0]
+
+ vsType, err := fromSfdiskPartitionType(p.Type, ptable.Label)
+ if err != nil {
+ return nil, fmt.Errorf("cannot convert sfdisk partition type %q: %v", p.Type, err)
+ }
+
+ structure[i] = gadget.VolumeStructure{
+ Name: p.Name,
+ Size: gadget.Size(p.Size) * sectorSize,
+ Label: bd.Label,
+ Type: vsType,
+ Filesystem: bd.FSType,
+ }
+
+ ds[i] = DeviceStructure{
+ LaidOutStructure: gadget.LaidOutStructure{
+ VolumeStructure: &structure[i],
+ StartOffset: gadget.Size(p.Start) * sectorSize,
+ Index: i + 1,
+ },
+ Node: p.Node,
+ CreatedDuringInstall: isCreatedDuringInstall(&p, &bd, ptable.Label),
+ }
+ }
+
+ var numSectors gadget.Size
+ if ptable.LastLBA != 0 {
+ // sfdisk reports the last usable LBA for GPT disks only
+ numSectors = gadget.Size(ptable.LastLBA + 1)
+ } else {
+ // sfdisk does not report any information about the size of a
+ // MBR partitioned disk, find out the size of the device by
+ // other means
+ sz, err := blockDeviceSizeInSectors(ptable.Device)
+ if err != nil {
+ return nil, fmt.Errorf("cannot obtain the size of device %q: %v", ptable.Device, err)
+ }
+ numSectors = sz
+ }
+
+ dl := &DeviceLayout{
+ Structure: ds,
+ ID: ptable.ID,
+ Device: ptable.Device,
+ Schema: ptable.Label,
+ Size: numSectors * sectorSize,
+ SectorSize: sectorSize,
+ partitionTable: &ptable,
+ }
+
+ return dl, nil
+}
+
+func deviceName(name string, index int) string {
+ if len(name) > 0 {
+ last := name[len(name)-1]
+ if last >= '0' && last <= '9' {
+ return fmt.Sprintf("%sp%d", name, index)
+ }
+ }
+ return fmt.Sprintf("%s%d", name, index)
+}
+
+// buildPartitionList builds a list of partitions based on the current
+// device contents and gadget structure list, in sfdisk dump format, and
+// returns a partitioning description suitable for sfdisk input and a
+// list of the partitions to be created.
+func buildPartitionList(dl *DeviceLayout, pv *gadget.LaidOutVolume) (sfdiskInput *bytes.Buffer, toBeCreated []DeviceStructure) {
+ ptable := dl.partitionTable
+
+ // Keep track what partitions we already have on disk
+ seen := map[uint64]bool{}
+ for _, p := range ptable.Partitions {
+ seen[p.Start] = true
+ }
+
+ // Check if the last partition has a system-data role
+ canExpandData := false
+ if n := len(pv.LaidOutStructure); n > 0 {
+ last := pv.LaidOutStructure[n-1]
+ if last.VolumeStructure.Role == gadget.SystemData {
+ canExpandData = true
+ }
+ }
+
+ // The partition index
+ pIndex := 0
+
+ // Write new partition data in named-fields format
+ buf := &bytes.Buffer{}
+ for _, p := range pv.LaidOutStructure {
+ if !p.IsPartition() {
+ continue
+ }
+
+ pIndex++
+ s := p.VolumeStructure
+
+ // Skip partitions that are already in the volume
+ start := p.StartOffset / sectorSize
+ if seen[uint64(start)] {
+ continue
+ }
+
+ // Only allow the creation of partitions with known GUIDs
+ // TODO:UC20: also provide a mechanism for MBR (RPi)
+ ptype := partitionType(ptable.Label, p.Type)
+ if ptable.Label == "gpt" && !creationSupported(ptype) {
+ logger.Noticef("cannot create partition with unsupported type %s", ptype)
+ continue
+ }
+
+ // Check if the data partition should be expanded
+ size := s.Size
+ if s.Role == gadget.SystemData && canExpandData && p.StartOffset+s.Size < dl.Size {
+ size = dl.Size - p.StartOffset
+ }
+
+ // Can we use the index here? Get the largest existing partition number and
+ // build from there could be safer if the disk partitions are not consecutive
+ // (can this actually happen in our images?)
+ node := deviceName(ptable.Device, pIndex)
+ fmt.Fprintf(buf, "%s : start=%12d, size=%12d, type=%s, name=%q, attrs=\"GUID:%s\"\n", node,
+ p.StartOffset/sectorSize, size/sectorSize, ptype, s.Name, createdPartitionAttr)
+
+ // Set expected labels based on role
+ switch s.Role {
+ case gadget.SystemBoot:
+ s.Label = ubuntuBootLabel
+ case gadget.SystemSeed:
+ s.Label = ubuntuSeedLabel
+ case gadget.SystemData:
+ s.Label = ubuntuDataLabel
+ }
+
+ toBeCreated = append(toBeCreated, DeviceStructure{
+ LaidOutStructure: p,
+ Node: node,
+ CreatedDuringInstall: true,
+ })
+ }
+
+ return buf, toBeCreated
+}
+
+// listCreatedPartitions returns a list of partitions created during the
+// install process.
+func listCreatedPartitions(layout *DeviceLayout) []string {
+ created := make([]string, 0, len(layout.Structure))
+ for _, s := range layout.Structure {
+ if s.CreatedDuringInstall {
+ created = append(created, s.Node)
+ }
+ }
+ return created
+}
+
+// udevTrigger triggers udev for the specified device and waits until
+// all events in the udev queue are handled.
+func udevTrigger(device string) error {
+ if output, err := exec.Command("udevadm", "trigger", "--settle", device).CombinedOutput(); err != nil {
+ return osutil.OutputErr(output, err)
+ }
+ return nil
+}
+
+// reloadPartitionTable instructs the kernel to re-read the partition
+// table of a given block device.
+func reloadPartitionTable(device string) error {
+ // Re-read the partition table using the BLKPG ioctl, which doesn't
+ // remove existing partitions, only appends new partitions with the right
+ // size and offset. As long as we provide consistent partitioning from
+ // userspace we're safe. At this point we also trigger udev to create
+ // the new partition device nodes.
+ output, err := exec.Command("partx", "-u", device).CombinedOutput()
+ if err != nil {
+ return osutil.OutputErr(output, err)
+ }
+ return nil
+}
+
+func partitionType(label, ptype string) string {
+ t := strings.Split(ptype, ",")
+ if len(t) < 1 {
+ return ""
+ }
+ if len(t) == 1 {
+ return t[0]
+ }
+ if label == "gpt" {
+ return t[1]
+ }
+ return t[0]
+}
+
+// lsblkFilesystemInfo represents the lsblk --fs JSON output format.
+type lsblkFilesystemInfo struct {
+ BlockDevices []lsblkBlockDevice `json:"blockdevices"`
+}
+
+type lsblkBlockDevice struct {
+ Name string `json:"name"`
+ FSType string `json:"fstype"`
+ Label string `json:"label"`
+ UUID string `json:"uuid"`
+ Mountpoint string `json:"mountpoint"`
+}
+
+func filesystemInfo(node string) (*lsblkFilesystemInfo, error) {
+ output, err := exec.Command("lsblk", "--fs", "--json", node).CombinedOutput()
+ if err != nil {
+ return nil, osutil.OutputErr(output, err)
+ }
+
+ var info lsblkFilesystemInfo
+ if err := json.Unmarshal(output, &info); err != nil {
+ return nil, fmt.Errorf("cannot parse lsblk output: %v", err)
+ }
+
+ return &info, nil
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/partition/sfdisk_test.go snapd-2.45.1+18.04/cmd/snap-bootstrap/partition/sfdisk_test.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/partition/sfdisk_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/partition/sfdisk_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,750 @@
+// -*- 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 partition_test
+
+import (
+ "fmt"
+ "io/ioutil"
+ "path/filepath"
+ "time"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/cmd/snap-bootstrap/partition"
+ "github.com/snapcore/snapd/gadget"
+ "github.com/snapcore/snapd/testutil"
+)
+
+const mockSfdiskScriptBiosAndRecovery = `
+>&2 echo "Some warning from sfdisk"
+echo '{
+ "partitiontable": {
+ "label": "gpt",
+ "id": "9151F25B-CDF0-48F1-9EDE-68CBD616E2CA",
+ "device": "/dev/node",
+ "unit": "sectors",
+ "firstlba": 34,
+ "lastlba": 8388574,
+ "partitions": [
+ {"node": "/dev/node1", "start": 2048, "size": 2048, "type": "21686148-6449-6E6F-744E-656564454649", "uuid": "2E59D969-52AB-430B-88AC-F83873519F6F", "name": "BIOS Boot"},
+ {"node": "/dev/node2", "start": 4096, "size": 2457600, "type": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B", "uuid": "44C3D5C3-CAE1-4306-83E8-DF437ACDB32F", "name": "Recovery", "attrs": "GUID:59"}
+ ]
+ }
+}'`
+
+const mockSfdiskScriptBiosRecoveryWritable = `
+>&2 echo "Some warning from sfdisk"
+echo '{
+ "partitiontable": {
+ "label": "gpt",
+ "id": "9151F25B-CDF0-48F1-9EDE-68CBD616E2CA",
+ "device": "/dev/node",
+ "unit": "sectors",
+ "firstlba": 34,
+ "lastlba": 8388574,
+ "partitions": [
+ {"node": "/dev/node1", "start": 2048, "size": 2048, "type": "21686148-6449-6E6F-744E-656564454649", "uuid": "2E59D969-52AB-430B-88AC-F83873519F6F", "name": "BIOS Boot"},
+ {"node": "/dev/node2", "start": 4096, "size": 2457600, "type": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B", "uuid": "44C3D5C3-CAE1-4306-83E8-DF437ACDB32F", "name": "Recovery"},
+ {"node": "/dev/node3", "start": 2461696, "size": 2457600, "type": "0FC63DAF-8483-4772-8E79-3D69D8477DE4", "uuid": "f940029d-bfbb-4887-9d44-321e85c63866", "name": "Writable", "attrs": "GUID:59"}
+ ]
+ }
+}'`
+
+const mockSfdiskScriptBiosAndRemovableRecovery = `
+if [ -f %[1]s/2 ]; then
+ rm %[1]s/[0-9]
+elif [ -f %[1]s/1 ]; then
+ touch %[1]s/2
+ exit 0
+else
+ PART=',{"node": "/dev/node2", "start": 4096, "size": 2457600, "type": "0FC63DAF-8483-4772-8E79-3D69D8477DE4", "uuid": "44C3D5C3-CAE1-4306-83E8-DF437ACDB32F", "name": "Recovery", "attrs": "GUID:59"}'
+ touch %[1]s/1
+fi
+echo '{
+ "partitiontable": {
+ "label": "gpt",
+ "id": "9151F25B-CDF0-48F1-9EDE-68CBD616E2CA",
+ "device": "/dev/node",
+ "unit": "sectors",
+ "firstlba": 34,
+ "lastlba": 8388574,
+ "partitions": [
+ {"node": "/dev/node1", "start": 2048, "size": 2048, "type": "21686148-6449-6E6F-744E-656564454649", "uuid": "2E59D969-52AB-430B-88AC-F83873519F6F", "name": "BIOS Boot"}
+ '"$PART
+ ]
+ }
+}"`
+
+const mockSfdiskScriptBios = `echo '{
+ "partitiontable": {
+ "label": "gpt",
+ "id": "9151F25B-CDF0-48F1-9EDE-68CBD616E2CA",
+ "device": "/dev/node",
+ "unit": "sectors",
+ "firstlba": 34,
+ "lastlba": 8388574,
+ "partitions": [
+ {
+ "node": "/dev/node1",
+ "start": 2048,
+ "size": 2048,
+ "type": "21686148-6449-6E6F-744E-656564454649",
+ "uuid": "2E59D969-52AB-430B-88AC-F83873519F6F",
+ "name": "BIOS Boot"
+ }
+ ]
+ }
+}'`
+
+// XXX: improve mocking later
+const mockLsblkScript = `echo '{
+ "blockdevices": [
+ {"name": "nodeX", "fstype": null, "label": null, "uuid": null, "mountpoint": null}
+ ]
+}'`
+
+const mockLsblkScriptBiosAndRecovery = `
+[ "$3" == "/dev/node1" ] && echo '{
+ "blockdevices": [ {"name": "node1", "fstype": null, "label": null, "uuid": null, "mountpoint": null} ]
+}'
+[ "$3" == "/dev/node2" ] && echo '{
+ "blockdevices": [ {"name": "node2", "fstype": "vfat", "label": "ubuntu-seed", "uuid": "A644-B807", "mountpoint": null} ]
+}'
+exit 0`
+
+const mockLsblkScriptBiosRecoveryWritable = `
+[ "$3" == "/dev/node1" ] && echo '{
+ "blockdevices": [ {"name": "node1", "fstype": null, "label": null, "uuid": null, "mountpoint": null} ]
+}'
+[ "$3" == "/dev/node2" ] && echo '{
+ "blockdevices": [ {"name": "node2", "fstype": "vfat", "label": "ubuntu-seed", "uuid": "A644-B807", "mountpoint": null} ]
+}'
+[ "$3" == "/dev/node3" ] && echo '{
+ "blockdevices": [ {"name": "node3", "fstype": "ext4", "label": "ubuntu-data", "uuid": "8781-433a", "mountpoint": null} ]
+}'
+exit 0`
+
+const gadgetContent = `volumes:
+ pc:
+ bootloader: grub
+ structure:
+ - name: mbr
+ type: mbr
+ size: 440
+ content:
+ - image: pc-boot.img
+ - name: BIOS Boot
+ type: DA,21686148-6449-6E6F-744E-656564454649
+ size: 1M
+ offset: 1M
+ offset-write: mbr+92
+ content:
+ - image: pc-core.img
+ - name: Recovery
+ role: system-seed
+ filesystem: vfat
+ # UEFI will boot the ESP partition by default first
+ type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B
+ size: 1200M
+ content:
+ - source: grubx64.efi
+ target: EFI/boot/grubx64.efi
+ - name: Writable
+ role: system-data
+ filesystem: ext4
+ type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4
+ size: 1200M
+`
+
+func (s *partitionTestSuite) TestDeviceInfoGPT(c *C) {
+ cmdSfdisk := testutil.MockCommand(c, "sfdisk", mockSfdiskScriptBiosAndRecovery)
+ defer cmdSfdisk.Restore()
+
+ cmdLsblk := testutil.MockCommand(c, "lsblk", mockLsblkScriptBiosAndRecovery)
+ defer cmdLsblk.Restore()
+
+ dl, err := partition.DeviceLayoutFromDisk("/dev/node")
+ c.Assert(err, IsNil)
+ c.Assert(cmdSfdisk.Calls(), DeepEquals, [][]string{
+ {"sfdisk", "--json", "-d", "/dev/node"},
+ })
+ c.Assert(cmdLsblk.Calls(), DeepEquals, [][]string{
+ {"lsblk", "--fs", "--json", "/dev/node1"},
+ {"lsblk", "--fs", "--json", "/dev/node2"},
+ })
+ c.Assert(err, IsNil)
+ c.Assert(dl.Schema, Equals, "gpt")
+ c.Assert(dl.ID, Equals, "9151F25B-CDF0-48F1-9EDE-68CBD616E2CA")
+ c.Assert(dl.Device, Equals, "/dev/node")
+ c.Assert(dl.SectorSize, Equals, gadget.Size(512))
+ c.Assert(dl.Size, Equals, gadget.Size(8388575*512))
+ c.Assert(len(dl.Structure), Equals, 2)
+
+ c.Assert(dl.Structure, DeepEquals, []partition.DeviceStructure{
+ {
+ LaidOutStructure: gadget.LaidOutStructure{
+ VolumeStructure: &gadget.VolumeStructure{
+ Name: "BIOS Boot",
+ Size: 0x100000,
+ Label: "",
+ Type: "21686148-6449-6E6F-744E-656564454649",
+ Filesystem: "",
+ },
+ StartOffset: 0x100000,
+ Index: 1,
+ },
+ Node: "/dev/node1",
+ },
+ {
+ LaidOutStructure: gadget.LaidOutStructure{
+ VolumeStructure: &gadget.VolumeStructure{
+ Name: "Recovery",
+ Size: 0x4b000000,
+ Label: "ubuntu-seed",
+ Type: "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
+ Filesystem: "vfat",
+ },
+ StartOffset: 0x200000,
+ Index: 2,
+ },
+ Node: "/dev/node2",
+ },
+ })
+}
+
+func (s *partitionTestSuite) TestDeviceInfoMBR(c *C) {
+ const mockSfdiskWithMBR = `
+>&2 echo "Some warning from sfdisk"
+echo '{
+ "partitiontable": {
+ "label": "dos",
+ "device": "/dev/node",
+ "unit": "sectors",
+ "partitions": [
+ {"node": "/dev/node1", "start": 4096, "size": 2457600, "type": "c"},
+ {"node": "/dev/node2", "start": 2461696, "size": 1048576, "type": "d"},
+ {"node": "/dev/node3", "start": 3510272, "size": 1048576, "type": "d"}
+ ]
+ }
+}'`
+ const mockLsblkForMBR = `
+[ "$3" == "/dev/node1" ] && echo '{
+ "blockdevices": [ {"name": "node1", "fstype": "vfat", "label": "ubuntu-seed", "uuid": "A644-B807", "mountpoint": null} ]
+}'
+[ "$3" == "/dev/node2" ] && echo '{
+ "blockdevices": [ {"name": "node2", "fstype": "vfat", "label": "ubuntu-boot", "uuid": "A644-B808", "mountpoint": null} ]
+}'
+[ "$3" == "/dev/node3" ] && echo '{
+ "blockdevices": [ {"name": "node3", "fstype": "ext4", "label": "ubuntu-data", "mountpoint": null} ]
+}'
+exit 0`
+
+ cmdSfdisk := testutil.MockCommand(c, "sfdisk", mockSfdiskWithMBR)
+ defer cmdSfdisk.Restore()
+
+ cmdLsblk := testutil.MockCommand(c, "lsblk", mockLsblkForMBR)
+ defer cmdLsblk.Restore()
+
+ cmdBlockdev := testutil.MockCommand(c, "blockdev", "echo 12345670")
+ defer cmdBlockdev.Restore()
+
+ dl, err := partition.DeviceLayoutFromDisk("/dev/node")
+ c.Assert(err, IsNil)
+ c.Assert(cmdSfdisk.Calls(), DeepEquals, [][]string{
+ {"sfdisk", "--json", "-d", "/dev/node"},
+ })
+ c.Assert(cmdLsblk.Calls(), DeepEquals, [][]string{
+ {"lsblk", "--fs", "--json", "/dev/node1"},
+ {"lsblk", "--fs", "--json", "/dev/node2"},
+ {"lsblk", "--fs", "--json", "/dev/node3"},
+ })
+ c.Assert(cmdBlockdev.Calls(), DeepEquals, [][]string{
+ {"blockdev", "--getsz", "/dev/node"},
+ })
+ c.Assert(err, IsNil)
+ c.Assert(dl.ID, Equals, "")
+ c.Assert(dl.Schema, Equals, "dos")
+ c.Assert(dl.Device, Equals, "/dev/node")
+ c.Assert(dl.SectorSize, Equals, gadget.Size(512))
+ c.Assert(dl.Size, Equals, gadget.Size(12345670*512))
+ c.Assert(len(dl.Structure), Equals, 3)
+
+ c.Assert(dl.Structure, DeepEquals, []partition.DeviceStructure{
+ {
+ LaidOutStructure: gadget.LaidOutStructure{
+ VolumeStructure: &gadget.VolumeStructure{
+ Size: 0x4b000000,
+ Label: "ubuntu-seed",
+ Type: "0C",
+ Filesystem: "vfat",
+ },
+ StartOffset: 0x200000,
+ Index: 1,
+ },
+ Node: "/dev/node1",
+ CreatedDuringInstall: false,
+ },
+ {
+ LaidOutStructure: gadget.LaidOutStructure{
+ VolumeStructure: &gadget.VolumeStructure{
+ Size: 0x20000000,
+ Label: "ubuntu-boot",
+ Type: "0D",
+ Filesystem: "vfat",
+ },
+ StartOffset: 0x4b200000,
+ Index: 2,
+ },
+ Node: "/dev/node2",
+ CreatedDuringInstall: true,
+ },
+ {
+ LaidOutStructure: gadget.LaidOutStructure{
+ VolumeStructure: &gadget.VolumeStructure{
+ Size: 0x20000000,
+ Label: "ubuntu-data",
+ Type: "0D",
+ Filesystem: "ext4",
+ },
+ StartOffset: 0x6b200000,
+ Index: 3,
+ },
+ Node: "/dev/node3",
+ CreatedDuringInstall: true,
+ },
+ })
+}
+
+func (s *partitionTestSuite) TestDeviceInfoNotSectors(c *C) {
+ cmdSfdisk := testutil.MockCommand(c, "sfdisk", `echo '{
+ "partitiontable": {
+ "label": "gpt",
+ "id": "9151F25B-CDF0-48F1-9EDE-68CBD616E2CA",
+ "device": "/dev/node",
+ "unit": "not_sectors",
+ "firstlba": 34,
+ "lastlba": 8388574,
+ "partitions": [
+ {"node": "/dev/node1", "start": 2048, "size": 2048, "type": "21686148-6449-6E6F-744E-656564454649", "uuid": "2E59D969-52AB-430B-88AC-F83873519F6F", "name": "BIOS Boot"}
+ ]
+ }
+}'`)
+ defer cmdSfdisk.Restore()
+
+ _, err := partition.DeviceLayoutFromDisk("/dev/node")
+ c.Assert(err, ErrorMatches, "cannot position partitions: unknown unit .*")
+}
+
+func (s *partitionTestSuite) TestDeviceInfoFilesystemInfoError(c *C) {
+ cmdSfdisk := testutil.MockCommand(c, "sfdisk", `echo '{
+ "partitiontable": {
+ "label": "gpt",
+ "id": "9151F25B-CDF0-48F1-9EDE-68CBD616E2CA",
+ "device": "/dev/node",
+ "unit": "sectors",
+ "firstlba": 34,
+ "lastlba": 8388574,
+ "partitions": [
+ {"node": "/dev/node1", "start": 2048, "size": 2048, "type": "21686148-6449-6E6F-744E-656564454649", "uuid": "2E59D969-52AB-430B-88AC-F83873519F6F", "name": "BIOS Boot"}
+ ]
+ }
+}'`)
+ defer cmdSfdisk.Restore()
+
+ cmdLsblk := testutil.MockCommand(c, "lsblk", "echo lsblk error; exit 1")
+ defer cmdLsblk.Restore()
+
+ _, err := partition.DeviceLayoutFromDisk("/dev/node")
+ c.Assert(err, ErrorMatches, "cannot obtain filesystem information: lsblk error")
+}
+
+func (s *partitionTestSuite) TestDeviceInfoJsonError(c *C) {
+ cmd := testutil.MockCommand(c, "sfdisk", `echo 'This is not a json'`)
+ defer cmd.Restore()
+
+ dl, err := partition.DeviceLayoutFromDisk("/dev/node")
+ c.Assert(err, ErrorMatches, "cannot parse sfdisk output: invalid .*")
+ c.Assert(dl, IsNil)
+}
+
+func (s *partitionTestSuite) TestDeviceInfoError(c *C) {
+ cmd := testutil.MockCommand(c, "sfdisk", "echo 'sfdisk: not found'; exit 127")
+ defer cmd.Restore()
+
+ dl, err := partition.DeviceLayoutFromDisk("/dev/node")
+ c.Assert(err, ErrorMatches, "sfdisk: not found")
+ c.Assert(dl, IsNil)
+}
+
+func (s *partitionTestSuite) TestBuildPartitionList(c *C) {
+ cmdSfdisk := testutil.MockCommand(c, "sfdisk", mockSfdiskScriptBiosAndRecovery)
+ defer cmdSfdisk.Restore()
+
+ cmdLsblk := testutil.MockCommand(c, "lsblk", mockLsblkScript)
+ defer cmdLsblk.Restore()
+
+ ptable := partition.SFDiskPartitionTable{
+ Label: "gpt",
+ ID: "9151F25B-CDF0-48F1-9EDE-68CBD616E2CA",
+ Device: "/dev/node",
+ Unit: "sectors",
+ FirstLBA: 34,
+ LastLBA: 8388574,
+ Partitions: []partition.SFDiskPartition{
+ {
+ Node: "/dev/node1",
+ Start: 2048,
+ Size: 2048,
+ Type: "21686148-6449-6E6F-744E-656564454649",
+ UUID: "2E59D969-52AB-430B-88AC-F83873519F6F",
+ Name: "BIOS Boot",
+ },
+ {
+ Node: "/dev/node2",
+ Start: 4096,
+ Size: 2457600,
+ Type: "EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
+ UUID: "216c34ff-9be6-4787-9ab3-a4c1429c3e73",
+ Name: "Recovery",
+ },
+ },
+ }
+
+ gadgetRoot := filepath.Join(c.MkDir(), "gadget")
+ err := makeMockGadget(gadgetRoot, gadgetContent)
+ c.Assert(err, IsNil)
+ pv, err := gadget.PositionedVolumeFromGadget(gadgetRoot)
+ c.Assert(err, IsNil)
+
+ dl, err := partition.DeviceLayoutFromPartitionTable(ptable)
+ c.Assert(err, IsNil)
+
+ // the expected expanded writable partition size is:
+ // start offset = (2M + 1200M), expanded size in sectors = (8388575*512 - start offset)/512
+ sfdiskInput, create := partition.BuildPartitionList(dl, pv)
+ c.Assert(sfdiskInput.String(), Equals, `/dev/node3 : start= 2461696, size= 5926879, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, name="Writable", attrs="GUID:59"
+`)
+ c.Assert(create, DeepEquals, []partition.DeviceStructure{mockDeviceStructureWritable})
+}
+
+func (s *partitionTestSuite) TestCreatePartitions(c *C) {
+ restore := partition.MockEnsureNodesExist(func(ds []partition.DeviceStructure, timeout time.Duration) error {
+ return nil
+ })
+ defer restore()
+
+ cmdSfdisk := testutil.MockCommand(c, "sfdisk", mockSfdiskScriptBiosAndRecovery)
+ defer cmdSfdisk.Restore()
+
+ cmdLsblk := testutil.MockCommand(c, "lsblk", mockLsblkScript)
+ defer cmdLsblk.Restore()
+
+ cmdPartx := testutil.MockCommand(c, "partx", "")
+ defer cmdPartx.Restore()
+
+ gadgetRoot := filepath.Join(c.MkDir(), "gadget")
+ err := makeMockGadget(gadgetRoot, gadgetContent)
+ c.Assert(err, IsNil)
+ pv, err := gadget.PositionedVolumeFromGadget(gadgetRoot)
+ c.Assert(err, IsNil)
+
+ dl, err := partition.DeviceLayoutFromDisk("/dev/node")
+ c.Assert(err, IsNil)
+ created, err := dl.CreateMissing(pv)
+ c.Assert(err, IsNil)
+ c.Assert(created, DeepEquals, []partition.DeviceStructure{mockDeviceStructureWritable})
+
+ // Check partition table read and write
+ c.Assert(cmdSfdisk.Calls(), DeepEquals, [][]string{
+ {"sfdisk", "--json", "-d", "/dev/node"},
+ {"sfdisk", "--append", "--no-reread", "/dev/node"},
+ })
+
+ // Check partition table update
+ c.Assert(cmdPartx.Calls(), DeepEquals, [][]string{
+ {"partx", "-u", "/dev/node"},
+ })
+}
+
+func (s *partitionTestSuite) TestRemovePartitionsTrivial(c *C) {
+ // no locally created partitions
+ cmdSfdisk := testutil.MockCommand(c, "sfdisk", mockSfdiskScriptBios)
+ defer cmdSfdisk.Restore()
+
+ cmdLsblk := testutil.MockCommand(c, "lsblk", mockLsblkScriptBiosAndRecovery)
+ defer cmdLsblk.Restore()
+
+ cmdPartx := testutil.MockCommand(c, "partx", "")
+ defer cmdPartx.Restore()
+
+ dl, err := partition.DeviceLayoutFromDisk("/dev/node")
+ c.Assert(err, IsNil)
+
+ err = dl.RemoveCreated()
+ c.Assert(err, IsNil)
+
+ c.Assert(cmdSfdisk.Calls(), DeepEquals, [][]string{
+ {"sfdisk", "--json", "-d", "/dev/node"},
+ })
+}
+
+func (s *partitionTestSuite) TestRemovePartitions(c *C) {
+ cmdSfdisk := testutil.MockCommand(c, "sfdisk", fmt.Sprintf(mockSfdiskScriptBiosAndRemovableRecovery, s.dir))
+ defer cmdSfdisk.Restore()
+
+ cmdLsblk := testutil.MockCommand(c, "lsblk", mockLsblkScriptBiosAndRecovery)
+ defer cmdLsblk.Restore()
+
+ cmdPartx := testutil.MockCommand(c, "partx", "")
+ defer cmdPartx.Restore()
+
+ dl, err := partition.DeviceLayoutFromDisk("/dev/node")
+ c.Assert(err, IsNil)
+
+ err = dl.RemoveCreated()
+ c.Assert(err, IsNil)
+
+ c.Assert(cmdSfdisk.Calls(), DeepEquals, [][]string{
+ {"sfdisk", "--json", "-d", "/dev/node"},
+ {"sfdisk", "--no-reread", "--delete", "/dev/node", "2"},
+ {"sfdisk", "--json", "-d", "/dev/node"},
+ })
+}
+
+func (s *partitionTestSuite) TestRemovePartitionsError(c *C) {
+ cmdSfdisk := testutil.MockCommand(c, "sfdisk", mockSfdiskScriptBiosRecoveryWritable)
+ defer cmdSfdisk.Restore()
+
+ cmdLsblk := testutil.MockCommand(c, "lsblk", mockLsblkScriptBiosRecoveryWritable)
+ defer cmdLsblk.Restore()
+
+ cmdPartx := testutil.MockCommand(c, "partx", "")
+ defer cmdPartx.Restore()
+
+ dl, err := partition.DeviceLayoutFromDisk("/dev/node")
+ c.Assert(err, IsNil)
+
+ err = dl.RemoveCreated()
+ c.Assert(err, ErrorMatches, "cannot remove partitions: /dev/node3")
+}
+
+func (s *partitionTestSuite) TestListCreatedPartitionsGPT(c *C) {
+ cmdLsblk := testutil.MockCommand(c, "lsblk", `echo '{ "blockdevices": [ {"fstype":"ext4", "label":null} ] }'`)
+ defer cmdLsblk.Restore()
+
+ ptable := partition.SFDiskPartitionTable{
+ Label: "gpt",
+ ID: "9151F25B-CDF0-48F1-9EDE-68CBD616E2CA",
+ Device: "/dev/node",
+ Unit: "sectors",
+ FirstLBA: 34,
+ LastLBA: 8388574,
+ Partitions: []partition.SFDiskPartition{
+ {
+ Node: "/dev/node1",
+ Start: 1024,
+ Size: 1024,
+ Type: "0fc63daf-8483-4772-8e79-3d69d8477de4",
+ UUID: "641764aa-a680-4d36-a7ad-f7bd01fd8d12",
+ Name: "Linux filesystem",
+ },
+ {
+ Node: "/dev/node2",
+ Start: 2048,
+ Size: 2048,
+ Type: "0657FD6D-A4AB-43C4-84E5-0933C84B4F4F",
+ UUID: "7ea3a75a-3f6d-4647-8134-89ae61fe88d5",
+ Name: "Linux swap",
+ },
+ {
+ Node: "/dev/node3",
+ Start: 8192,
+ Size: 8192,
+ Type: "21686148-6449-6E6F-744E-656564454649",
+ UUID: "30a26851-4b08-4b8d-8aea-f686e723ed8c",
+ Name: "BIOS boot partition",
+ },
+ {
+ Node: "/dev/node4",
+ Start: 16384,
+ Size: 16384,
+ Type: "0fc63daf-8483-4772-8e79-3d69d8477de4",
+ UUID: "8ab3e8fd-d53d-4d72-9c5e-56146915fd07",
+ Name: "Another Linux filesystem",
+ },
+ },
+ }
+ dl, err := partition.DeviceLayoutFromPartitionTable(ptable)
+ c.Assert(err, IsNil)
+ list := partition.ListCreatedPartitions(dl)
+ c.Assert(list, HasLen, 0)
+
+ // Set attribute bit for all partitions except the last one
+ for i := 0; i < len(ptable.Partitions)-1; i++ {
+ ptable.Partitions[i].Attrs = "RequiredPartition LegacyBIOSBootable GUID:58,59"
+ }
+
+ dl, err = partition.DeviceLayoutFromPartitionTable(ptable)
+ c.Assert(err, IsNil)
+ list = partition.ListCreatedPartitions(dl)
+ c.Assert(list, DeepEquals, []string{"/dev/node1", "/dev/node2"})
+}
+
+func (s *partitionTestSuite) TestListCreatedPartitionsMBR(c *C) {
+ cmdLsblk := testutil.MockCommand(c, "lsblk", `
+what=
+shift 2
+case "$1" in
+ /dev/node1)
+ what='{"name": "node1", "fstype":"ext4", "label":"ubuntu-seed"}'
+ ;;
+ /dev/node2)
+ what='{"name": "node2", "fstype":"vfat", "label":"ubuntu-boot"}'
+ ;;
+ /dev/node3)
+ what='{"name": "node3", "fstype":null, "label":null}'
+ ;;
+ /dev/node4)
+ what='{"name": "node4", "fstype":"ext4", "label":"ubuntu-data"}'
+ ;;
+ *)
+ echo "unexpected call"
+ exit 1
+esac
+
+cat <= timeout, Equals, true)
+ c.Assert(cmdUdevadm.Calls(), HasLen, 0)
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/triggerwatch/evdev.go snapd-2.45.1+18.04/cmd/snap-bootstrap/triggerwatch/evdev.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/triggerwatch/evdev.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/triggerwatch/evdev.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,244 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package triggerwatch
+
+import (
+ "fmt"
+ "syscall"
+ "time"
+ "unsafe"
+
+ // TODO:UC20: not packaged, reimplement the minimal things we need?
+ evdev "github.com/gvalkov/golang-evdev"
+
+ "github.com/snapcore/snapd/logger"
+)
+
+type keyEvent struct {
+ Dev triggerDevice
+ Err error
+}
+
+type triggerEventFilter struct {
+ Key string
+}
+
+var (
+ strToKey = map[string]int{
+ "KEY_ESC": evdev.KEY_ESC,
+ "KEY_1": evdev.KEY_1,
+ "KEY_2": evdev.KEY_2,
+ "KEY_3": evdev.KEY_3,
+ "KEY_4": evdev.KEY_4,
+ "KEY_5": evdev.KEY_5,
+ "KEY_6": evdev.KEY_6,
+ "KEY_7": evdev.KEY_7,
+ "KEY_8": evdev.KEY_8,
+ "KEY_9": evdev.KEY_9,
+ "KEY_0": evdev.KEY_0,
+ }
+ evKeyCapability = evdev.CapabilityType{Type: evdev.EV_KEY, Name: "EV_KEY"}
+
+ // hold time needed to trigger the event
+ holdToTrigger = 2 * time.Second
+)
+
+func init() {
+ trigger = &evdevInput{}
+}
+
+type evdevKeyboardInputDevice struct {
+ keyCode uint16
+ dev *evdev.InputDevice
+}
+
+func (e *evdevKeyboardInputDevice) probeKeyState() (bool, error) {
+ // XXX: evdev defines EVIOCGKEY using MAX_NAME_SIZE which is larger than
+ // what is needed to store the key bitmap with KEY_MAX bits, but we need
+ // to play along since the value is already encoded
+ keyBitmap := new([evdev.MAX_NAME_SIZE]byte)
+
+ // obtain the large bitmap with all key states
+ // https://elixir.bootlin.com/linux/v5.5.5/source/drivers/input/evdev.c#L1163
+ _, _, err := syscall.RawSyscall(syscall.SYS_IOCTL, e.dev.File.Fd(), uintptr(evdev.EVIOCGKEY), uintptr(unsafe.Pointer(keyBitmap)))
+ if err != 0 {
+ return false, err
+ }
+ byteIdx := e.keyCode / 8
+ keyMask := byte(1 << (e.keyCode % 8))
+ isDown := keyBitmap[byteIdx]&keyMask != 0
+ return isDown, nil
+}
+
+func (e *evdevKeyboardInputDevice) WaitForTrigger(ch chan keyEvent) {
+ logger.Noticef("%s: starting wait", e)
+
+ // XXX: do not mess with setting the key repeat rate, as it's cumbersome
+ // and golang-evdev SetRepeatRate() parameter order is actually reversed
+ // wrt. what the kernel does. The evdev interprets EVIOCSREP arguments
+ // as (delay, repeat)
+ // https://elixir.bootlin.com/linux/latest/source/drivers/input/evdev.c#L1072
+ // but the wrapper is passing is passing (repeat, delay)
+ // https://github.com/gvalkov/golang-evdev/blob/287e62b94bcb850ab42e711bd74b2875da83af2c/device.go#L226-L230
+
+ keyDown, err := e.probeKeyState()
+ if err != nil {
+ ch <- keyEvent{Err: fmt.Errorf("cannot obtain initial key state: %v", err), Dev: e}
+ }
+ if keyDown {
+ // looks like the key is pressed initially, we don't know when
+ // that happened, but pretend it happened just now
+ logger.Noticef("%s: key is already down", e)
+ }
+
+ type evdevEvent struct {
+ kev *evdev.KeyEvent
+ err error
+ }
+
+ // buffer large enough to collect some events
+ evChan := make(chan evdevEvent, 10)
+
+ monitorKey := func() {
+ for {
+ ies, err := e.dev.Read()
+ if err != nil {
+ evChan <- evdevEvent{err: err}
+ break
+ }
+ for _, ie := range ies {
+ if ie.Type != evdev.EV_KEY || ie.Code != e.keyCode {
+ continue
+ }
+ kev := evdev.NewKeyEvent(&ie)
+ evChan <- evdevEvent{kev: kev}
+ }
+ }
+ close(evChan)
+ }
+
+ go monitorKey()
+
+ holdTimer := time.NewTimer(holdToTrigger)
+ // no sense to keep it running later either
+ defer holdTimer.Stop()
+
+ if !keyDown {
+ // key isn't held yet, stop the timer
+ holdTimer.Stop()
+ }
+
+ // invariant: tholdTimer is running iff keyDown is true, otherwise is stopped
+Loop:
+ for {
+ select {
+ case ev := <-evChan:
+ if ev.err != nil {
+ holdTimer.Stop()
+ ch <- keyEvent{Err: err, Dev: e}
+ break Loop
+ }
+ kev := ev.kev
+ switch kev.State {
+ case evdev.KeyDown:
+ if keyDown {
+ // unexpected, but possible if we missed
+ // a key up event right after checking
+ // the initial keyboard state when the
+ // key was still down
+ if !holdTimer.Stop() {
+ // drain the channel before the
+ // timer gets reset
+ <-holdTimer.C
+ }
+ }
+ keyDown = true
+ // timer is stopped at this point
+ holdTimer.Reset(holdToTrigger)
+ logger.Noticef("%s: trigger key down", e)
+ case evdev.KeyHold:
+ if !keyDown {
+ keyDown = true
+ // timer is not running yet at this point
+ holdTimer.Reset(holdToTrigger)
+ logger.Noticef("%s: unexpected hold without down", e)
+ }
+ case evdev.KeyUp:
+ // no need to drain the channel, if it expired,
+ // we'll handle it in next iteration
+ holdTimer.Stop()
+ keyDown = false
+ logger.Noticef("%s: trigger key up", e)
+ }
+ case <-holdTimer.C:
+ logger.Noticef("%s: hold complete", e)
+ ch <- keyEvent{Dev: e}
+ break Loop
+ }
+ }
+}
+
+func (e *evdevKeyboardInputDevice) String() string {
+ return fmt.Sprintf("%s: %s", e.dev.Phys, e.dev.Name)
+}
+
+func (e *evdevKeyboardInputDevice) Close() {
+ e.dev.File.Close()
+}
+
+type evdevInput struct{}
+
+func (e *evdevInput) FindMatchingDevices(filter triggerEventFilter) ([]triggerDevice, error) {
+ devices, err := evdev.ListInputDevices()
+ if err != nil {
+ return nil, fmt.Errorf("cannot list input devices: %v", err)
+ }
+
+ // NOTE: this supports so far only key input devices
+
+ kc, ok := strToKey[filter.Key]
+ if !ok {
+ return nil, fmt.Errorf("cannot find a key matching the filter %q", filter.Key)
+ }
+ cap := evdev.CapabilityCode{Code: kc, Name: filter.Key}
+
+ match := func(dev *evdev.InputDevice) triggerDevice {
+ for _, cc := range dev.Capabilities[evKeyCapability] {
+ if cc == cap {
+ return &evdevKeyboardInputDevice{
+ dev: dev,
+ keyCode: uint16(cap.Code),
+ }
+ }
+ }
+ return nil
+ }
+ // collect all input devices that can emit the trigger key
+ var devs []triggerDevice
+ for _, dev := range devices {
+ idev := match(dev)
+ if idev != nil {
+ devs = append(devs, idev)
+ } else {
+ defer dev.File.Close()
+ }
+ }
+ return devs, nil
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/triggerwatch/export_test.go snapd-2.45.1+18.04/cmd/snap-bootstrap/triggerwatch/export_test.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/triggerwatch/export_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/triggerwatch/export_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,32 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+package triggerwatch
+
+func MockInput(newInput TriggerProvider) (restore func()) {
+ oldInput := trigger
+ trigger = newInput
+ return func() {
+ trigger = oldInput
+ }
+}
+
+type TriggerProvider = triggerProvider
+type TriggerDevice = triggerDevice
+type TriggerCapabilityFilter = triggerEventFilter
+type KeyEvent = keyEvent
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/triggerwatch/triggerwatch.go snapd-2.45.1+18.04/cmd/snap-bootstrap/triggerwatch/triggerwatch.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/triggerwatch/triggerwatch.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/triggerwatch/triggerwatch.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,87 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package triggerwatch
+
+import (
+ "errors"
+ "fmt"
+ "time"
+
+ "github.com/snapcore/snapd/logger"
+)
+
+type triggerProvider interface {
+ FindMatchingDevices(filter triggerEventFilter) ([]triggerDevice, error)
+}
+
+type triggerDevice interface {
+ WaitForTrigger(chan keyEvent)
+ String() string
+ Close()
+}
+
+var (
+ // trigger mechanism
+ trigger triggerProvider
+
+ // wait for '1' to be pressed
+ triggerFilter = triggerEventFilter{Key: "KEY_1"}
+
+ ErrTriggerNotDetected = errors.New("trigger not detected")
+ ErrNoMatchingInputDevices = errors.New("no matching input devices")
+)
+
+// Wait waits for a trigger on the available trigger devices for a given amount
+// of time. Returns nil if one was detected, ErrTriggerNotDetected if timeout
+// was hit, or other non-nil error.
+func Wait(timeout time.Duration) error {
+ if trigger == nil {
+ logger.Panicf("trigger is unset")
+ }
+
+ devices, err := trigger.FindMatchingDevices(triggerFilter)
+ if err != nil {
+ return fmt.Errorf("cannot list trigger devices: %v", err)
+ }
+ if devices == nil {
+ return ErrNoMatchingInputDevices
+ }
+
+ logger.Noticef("waiting for trigger key: %v", triggerFilter.Key)
+
+ detectKeyCh := make(chan keyEvent, len(devices))
+ for _, dev := range devices {
+ go dev.WaitForTrigger(detectKeyCh)
+ defer dev.Close()
+ }
+
+ select {
+ case kev := <-detectKeyCh:
+ if kev.Err != nil {
+ return err
+ }
+ // channel got closed without an error
+ logger.Noticef("%s: + got trigger key %v", kev.Dev, triggerFilter.Key)
+ case <-time.After(timeout):
+ return ErrTriggerNotDetected
+ }
+
+ return nil
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-bootstrap/triggerwatch/triggerwatch_test.go snapd-2.45.1+18.04/cmd/snap-bootstrap/triggerwatch/triggerwatch_test.go
--- snapd-2.42.1+18.04/cmd/snap-bootstrap/triggerwatch/triggerwatch_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-bootstrap/triggerwatch/triggerwatch_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,130 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package triggerwatch_test
+
+import (
+ "fmt"
+ "testing"
+ "time"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/cmd/snap-bootstrap/triggerwatch"
+)
+
+// Hook up check.v1 into the "go test" runner
+func Test(t *testing.T) { TestingT(t) }
+
+type triggerwatchSuite struct{}
+
+var _ = Suite(&triggerwatchSuite{})
+
+type mockTriggerDevice struct {
+ waitForTriggerCalls int
+ closeCalls int
+ ev *triggerwatch.KeyEvent
+}
+
+func (m *mockTriggerDevice) WaitForTrigger(n chan triggerwatch.KeyEvent) {
+ m.waitForTriggerCalls++
+ if m.ev != nil {
+ ev := *m.ev
+ ev.Dev = m
+ n <- ev
+ }
+}
+
+func (m *mockTriggerDevice) String() string { return "mock-device" }
+func (m *mockTriggerDevice) Close() { m.closeCalls++ }
+
+type mockTrigger struct {
+ f triggerwatch.TriggerCapabilityFilter
+ d *mockTriggerDevice
+ err error
+
+ findMatchingCalls int
+}
+
+func (m *mockTrigger) FindMatchingDevices(f triggerwatch.TriggerCapabilityFilter) ([]triggerwatch.TriggerDevice, error) {
+ m.findMatchingCalls++
+
+ m.f = f
+ if m.err != nil {
+ return nil, m.err
+ }
+ if m.d != nil {
+ return []triggerwatch.TriggerDevice{m.d}, nil
+ }
+ return nil, nil
+}
+
+const testTriggerTimeout = 5 * time.Millisecond
+
+func (s *triggerwatchSuite) TestNoDevsWaitKey(c *C) {
+ md := &mockTriggerDevice{ev: &triggerwatch.KeyEvent{}}
+ mi := &mockTrigger{d: md}
+ restore := triggerwatch.MockInput(mi)
+ defer restore()
+
+ err := triggerwatch.Wait(testTriggerTimeout)
+ c.Assert(err, IsNil)
+ c.Assert(mi.findMatchingCalls, Equals, 1)
+ c.Assert(md.waitForTriggerCalls, Equals, 1)
+ c.Assert(md.closeCalls, Equals, 1)
+}
+
+func (s *triggerwatchSuite) TestNoDevsWaitKeyTimeout(c *C) {
+ md := &mockTriggerDevice{}
+ mi := &mockTrigger{d: md}
+ restore := triggerwatch.MockInput(mi)
+ defer restore()
+
+ err := triggerwatch.Wait(testTriggerTimeout)
+ c.Assert(err, Equals, triggerwatch.ErrTriggerNotDetected)
+ c.Assert(mi.findMatchingCalls, Equals, 1)
+ c.Assert(md.waitForTriggerCalls, Equals, 1)
+ c.Assert(md.closeCalls, Equals, 1)
+}
+
+func (s *triggerwatchSuite) TestNoDevsWaitNoMatching(c *C) {
+ mi := &mockTrigger{}
+ restore := triggerwatch.MockInput(mi)
+ defer restore()
+
+ err := triggerwatch.Wait(testTriggerTimeout)
+ c.Assert(err, Equals, triggerwatch.ErrNoMatchingInputDevices)
+}
+
+func (s *triggerwatchSuite) TestNoDevsWaitMatchingError(c *C) {
+ mi := &mockTrigger{err: fmt.Errorf("failed")}
+ restore := triggerwatch.MockInput(mi)
+ defer restore()
+
+ err := triggerwatch.Wait(testTriggerTimeout)
+ c.Assert(err, ErrorMatches, "cannot list trigger devices: failed")
+}
+
+func (s *triggerwatchSuite) TestChecksInput(c *C) {
+ restore := triggerwatch.MockInput(nil)
+ defer restore()
+
+ c.Assert(func() { triggerwatch.Wait(testTriggerTimeout) },
+ Panics, "trigger is unset")
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/mount-support.c snapd-2.45.1+18.04/cmd/snap-confine/mount-support.c
--- snapd-2.42.1+18.04/cmd/snap-confine/mount-support.c 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/mount-support.c 2020-06-05 13:13:49.000000000 +0000
@@ -50,6 +50,8 @@
#define MAX_BUF 1000
+static void sc_detach_views_of_writable(sc_distro distro, bool normal_mode);
+
// TODO: simplify this, after all it is just a tmpfs
// TODO: fold this into bootstrap
static void setup_private_mount(const char *snap_name)
@@ -82,6 +84,9 @@
sc_must_snprintf(base_dir, sizeof(base_dir), "/tmp/snap.%s", snap_name);
sc_must_snprintf(tmp_dir, sizeof(tmp_dir), "%s/tmp", base_dir);
+ /* Switch to root group so that mkdir and open calls below create filesystem
+ * elements that are not owned by the user calling into snap-confine. */
+ sc_identity old = sc_set_effective_identity(sc_root_group_identity());
// Create /tmp/snap.$SNAP_NAME/ 0700 root.root. Ignore EEXIST since we want
// to reuse and we will open with O_NOFOLLOW, below.
if (mkdir(base_dir, 0700) < 0 && errno != EEXIST) {
@@ -92,6 +97,13 @@
if (base_dir_fd < 0) {
die("cannot open base directory %s", base_dir);
}
+ /* This seems redundant on first read but it has the non-obvious
+ * property of changing existing directories that have already existed
+ * but had incorrect ownership or permission. This is possible due to
+ * earlier bugs in snap-confine and due to the fact that some systems
+ * use persistent /tmp directory and may not clean up leftover files
+ * for arbitrarily long. This comment applies the following two pairs
+ * of fchmod and fchown. */
if (fchmod(base_dir_fd, 0700) < 0) {
die("cannot chmod base directory %s to 0700", base_dir);
}
@@ -103,6 +115,7 @@
if (mkdirat(base_dir_fd, "tmp", 01777) < 0 && errno != EEXIST) {
die("cannot create private tmp directory %s/tmp", base_dir);
}
+ (void)sc_set_effective_identity(old);
tmp_dir_fd = openat(base_dir_fd, "tmp",
O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
if (tmp_dir_fd < 0) {
@@ -253,9 +266,14 @@
// disabling the "is_bidirectional" flag as can be seen below.
for (const struct sc_mount * mnt = config->mounts; mnt->path != NULL;
mnt++) {
- if (mnt->is_bidirectional && mkdir(mnt->path, 0755) < 0 &&
- errno != EEXIST) {
- die("cannot create %s", mnt->path);
+
+ if (mnt->is_bidirectional) {
+ sc_identity old =
+ sc_set_effective_identity(sc_root_group_identity());
+ if (mkdir(mnt->path, 0755) < 0 && errno != EEXIST) {
+ die("cannot create %s", mnt->path);
+ }
+ (void)sc_set_effective_identity(old);
}
sc_must_snprintf(dst, sizeof dst, "%s/%s", scratch_dir,
mnt->path);
@@ -384,9 +402,16 @@
// that we are re-execing from
char *src = NULL;
char self[PATH_MAX + 1] = { 0 };
- if (readlink("/proc/self/exe", self, sizeof(self) - 1) < 0) {
+ ssize_t nread;
+ nread = readlink("/proc/self/exe", self, sizeof self - 1);
+ if (nread < 0) {
die("cannot read /proc/self/exe");
}
+ // Though we initialized self to NULs and passed one less to
+ // readlink, therefore guaranteeing that self is
+ // zero-terminated, perform an explicit assignment to make
+ // Coverity happy.
+ self[nread] = '\0';
// this cannot happen except when the kernel is buggy
if (strstr(self, "/snap-confine") == NULL) {
die("cannot use result from readlink: %s", self);
@@ -416,13 +441,13 @@
}
// 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) {
- debug("creating missing hostfs directory");
- if (mkdir(SC_HOSTFS_DIR, 0755) != 0) {
- die("cannot perform operation: mkdir %s",
- SC_HOSTFS_DIR);
+ sc_identity old = sc_set_effective_identity(sc_root_group_identity());
+ if (mkdir(SC_HOSTFS_DIR, 0755) < 0) {
+ if (errno != EEXIST) {
+ die("cannot perform operation: mkdir %s", SC_HOSTFS_DIR);
}
}
+ (void)sc_set_effective_identity(old);
// Ensure that hostfs isgroup owned by root. We may have (now or earlier)
// created the directory as the user who first ran a snap on a given
// system and the group identity of that user is visilbe on disk.
@@ -509,6 +534,43 @@
// mount table and software inspecting the mount table may become confused.
sc_must_snprintf(src, sizeof src, "%s/proc", SC_HOSTFS_DIR);
sc_do_umount(src, UMOUNT_NOFOLLOW | MNT_DETACH);
+ // Detach both views of /writable: the one from hostfs and the one directly
+ // visible in /writable. Interfaces don't grant access to this directory
+ // and it has a large duplicated view of many mount points. Note that this
+ // is only applicable to ubuntu-core systems.
+ sc_detach_views_of_writable(config->distro, config->normal_mode);
+}
+
+static void sc_detach_views_of_writable(sc_distro distro, bool normal_mode)
+{
+ // Note that prior to detaching either mount point we switch the
+ // propagation to private to both limit the change to just this view and to
+ // prevent otherwise occurring event propagation from self-conflicting and
+ // returning EBUSY. A similar approach is used by snap-update-ns and is
+ // documented in umount(2).
+ const char *writable_dir = "/writable";
+ const char *hostfs_writable_dir = "/var/lib/snapd/hostfs/writable";
+
+ // Writable only exists on ubuntu-core.
+ if (distro == SC_DISTRO_CLASSIC) {
+ return;
+ }
+ // On all core distributions we see /var/lib/snapd/hostfs/writable that
+ // exposes writable, with a structure specific to ubuntu-core.
+ debug("detaching %s", hostfs_writable_dir);
+ sc_do_mount("none", hostfs_writable_dir, NULL,
+ MS_REC | MS_PRIVATE, NULL);
+ sc_do_umount(hostfs_writable_dir, UMOUNT_NOFOLLOW | MNT_DETACH);
+
+ // On ubuntu-core 16, when the executed snap uses core as base we also see
+ // the /writable that we directly inherited from the initial mount
+ // namespace.
+ if (distro == SC_DISTRO_CORE16 && !normal_mode) {
+ debug("detaching %s", writable_dir);
+ sc_do_mount("none", writable_dir, NULL, MS_REC | MS_PRIVATE,
+ NULL);
+ sc_do_umount(writable_dir, UMOUNT_NOFOLLOW | MNT_DETACH);
+ }
}
/**
@@ -568,7 +630,8 @@
}
void sc_populate_mount_ns(struct sc_apparmor *apparmor, int snap_update_ns_fd,
- const sc_invocation * inv)
+ const sc_invocation * inv, const gid_t real_gid,
+ const gid_t saved_gid)
{
// Classify the current distribution, as claimed by /etc/os-release.
sc_distro distro = sc_classify_distro();
@@ -585,6 +648,7 @@
{"/sys"}, // fundamental filesystem
{"/tmp"}, // to get writable tmp
{"/var/snap"}, // to get access to global snap data
+ {"/var/lib/dhcp",.is_optional = true}, // to support network-control interface
{"/var/lib/snapd"}, // to get access to snapd state and seccomp profiles
{"/var/tmp"}, // to get access to the other temporary directory
{"/run"}, // to get /run with sockets and what not
@@ -631,10 +695,8 @@
sc_bootstrap_mount_namespace(&legacy_config);
}
- // set up private mounts
// TODO: rename this and fold it into bootstrap
setup_private_mount(inv->snap_instance);
-
// set up private /dev/pts
// TODO: fold this into bootstrap
setup_private_pts();
@@ -702,5 +764,54 @@
// to slave mode, so we see changes from the parent namespace
// but don't propagate our own changes.
sc_do_mount("none", "/", NULL, MS_REC | MS_SLAVE, NULL);
+ sc_identity old = sc_set_effective_identity(sc_root_group_identity());
sc_call_snap_update_ns_as_user(snap_update_ns_fd, snap_name, apparmor);
+ (void)sc_set_effective_identity(old);
+}
+
+void sc_ensure_snap_dir_shared_mounts(void)
+{
+ const char *dirs[] = { SNAP_MOUNT_DIR, "/var/snap", NULL };
+ for (int i = 0; dirs[i] != NULL; i++) {
+ const char *dir = dirs[i];
+ if (!is_mounted_with_shared_option(dir)) {
+ /* Since this directory isn't yet shared (but it should be),
+ * recursively bind mount it, then recursively share it so that
+ * changes to the host are seen in the snap and vice-versa. This
+ * allows us to fine-tune propagation events elsewhere for this new
+ * mountpoint.
+ *
+ * Not using MS_SLAVE because it's too late for SNAP_MOUNT_DIR,
+ * since snaps are already mounted, and it's not needed for
+ * /var/snap.
+ */
+ sc_do_mount(dir, dir, "none", MS_BIND | MS_REC, 0);
+ sc_do_mount("none", dir, NULL, MS_REC | MS_SHARED,
+ NULL);
+ }
+ }
+}
+
+void sc_setup_parallel_instance_classic_mounts(const char *snap_name,
+ const char *snap_instance_name)
+{
+ char src[PATH_MAX] = { 0 };
+ char dst[PATH_MAX] = { 0 };
+
+ const char *dirs[] = { SNAP_MOUNT_DIR, "/var/snap", NULL };
+ for (int i = 0; dirs[i] != NULL; i++) {
+ const char *dir = dirs[i];
+ sc_do_mount("none", dir, NULL, MS_REC | MS_SLAVE, NULL);
+ }
+
+ /* Mount SNAP_MOUNT_DIR/_ on SNAP_MOUNT_DIR/ */
+ sc_must_snprintf(src, sizeof src, "%s/%s", SNAP_MOUNT_DIR,
+ snap_instance_name);
+ sc_must_snprintf(dst, sizeof dst, "%s/%s", SNAP_MOUNT_DIR, snap_name);
+ sc_do_mount(src, dst, "none", MS_BIND | MS_REC, 0);
+
+ /* Mount /var/snap/_ on /var/snap/ */
+ sc_must_snprintf(src, sizeof src, "/var/snap/%s", snap_instance_name);
+ sc_must_snprintf(dst, sizeof dst, "/var/snap/%s", snap_name);
+ sc_do_mount(src, dst, "none", MS_BIND | MS_REC, 0);
}
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/mount-support.h snapd-2.45.1+18.04/cmd/snap-confine/mount-support.h
--- snapd-2.42.1+18.04/cmd/snap-confine/mount-support.h 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/mount-support.h 2020-06-05 13:13:49.000000000 +0000
@@ -20,6 +20,7 @@
#include "../libsnap-confine-private/apparmor-support.h"
#include "snap-confine-invocation.h"
+#include
/**
* Assuming a new mountspace, populate it accordingly.
@@ -31,7 +32,8 @@
* - processes mount profiles
**/
void sc_populate_mount_ns(struct sc_apparmor *apparmor, int snap_update_ns_fd,
- const sc_invocation * inv);
+ const sc_invocation * inv, const gid_t real_gid,
+ const gid_t saved_gid);
/**
* Ensure that / or /snap is mounted with the SHARED option.
@@ -54,4 +56,20 @@
void sc_setup_user_mounts(struct sc_apparmor *apparmor, int snap_update_ns_fd,
const char *snap_name);
+/**
+ * Ensure that SNAP_MOUNT_DIR and /var/snap are mount points.
+ *
+ * Create bind mounts and set up shared propagation for SNAP_MOUNT_DIR and
+ * /var/snap as needed. This allows for further propagation changes after the
+ * initial mount namespace is unshared.
+ */
+void sc_ensure_snap_dir_shared_mounts(void);
+
+/**
+ * Set up mount namespace for parallel installed classic snap
+ *
+ * Create bind mounts from instance specific locations to non-instance ones.
+ */
+void sc_setup_parallel_instance_classic_mounts(const char *snap_name,
+ const char *snap_instance_name);
#endif
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/mount-support-nvidia.c snapd-2.45.1+18.04/cmd/snap-confine/mount-support-nvidia.c
--- snapd-2.42.1+18.04/cmd/snap-confine/mount-support-nvidia.c 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/mount-support-nvidia.c 2020-06-05 13:13:49.000000000 +0000
@@ -112,8 +112,11 @@
"libnvidia-ifr.so*",
"libnvidia-ml.so*",
"libnvidia-opencl.so*",
+ "libnvidia-opticalflow.so*",
"libnvidia-ptxjitcompiler.so*",
+ "libnvidia-rtcore.so*",
"libnvidia-tls.so*",
+ "libnvoptix.so*",
"tls/libnvidia-tls.so*",
"vdpau/libvdpau_nvidia.so*",
};
@@ -121,8 +124,6 @@
static const size_t nvidia_globs_len =
sizeof nvidia_globs / sizeof *nvidia_globs;
-#define LIBNVIDIA_GLCORE_SO_PATTERN "libnvidia-glcore.so.%d.%d"
-
#endif // defined(NVIDIA_BIARCH) || defined(NVIDIA_MULTIARCH)
// Populate libgl_dir with a symlink farm to files matching glob_list.
@@ -184,10 +185,13 @@
sc_must_snprintf(prefix_dir, sizeof prefix_dir,
"%s%s", libgl_dir,
&directory_name[source_dir_len]);
+ sc_identity old =
+ sc_set_effective_identity(sc_root_group_identity());
if (sc_nonfatal_mkpath(prefix_dir, 0755) != 0) {
die("failed to create prefix path: %s",
prefix_dir);
}
+ (void)sc_set_effective_identity(old);
}
struct stat stat_buf;
@@ -198,12 +202,12 @@
switch (stat_buf.st_mode & S_IFMT) {
case S_IFLNK:;
// Read the target of the symbolic link
- char hostfs_symlink_target[512];
+ char hostfs_symlink_target[512] = { 0 };
ssize_t num_read;
hostfs_symlink_target[0] = 0;
num_read =
readlink(pathname, hostfs_symlink_target,
- sizeof hostfs_symlink_target);
+ sizeof hostfs_symlink_target - 1);
if (num_read == -1) {
die("cannot read symbolic link %s", pathname);
}
@@ -261,6 +265,7 @@
sc_must_snprintf(buf, sizeof(buf), "%s%s", rootfs_dir, tgt_dir);
const char *libgl_dir = buf;
+ sc_identity old = sc_set_effective_identity(sc_root_group_identity());
int res = mkdir(libgl_dir, 0755);
if (res != 0 && errno != EEXIST) {
die("cannot create tmpfs target %s", libgl_dir);
@@ -269,6 +274,7 @@
// Adjust the ownership only if we created the directory.
die("cannot change ownership of %s", libgl_dir);
}
+ (void)sc_set_effective_identity(old);
debug("mounting tmpfs at %s", libgl_dir);
if (mount("none", libgl_dir, "tmpfs", MS_NODEV | MS_NOEXEC, NULL) != 0) {
@@ -345,55 +351,62 @@
#ifdef NVIDIA_MULTIARCH
-struct sc_nvidia_driver {
- int major_version;
- int minor_version;
-};
+typedef struct {
+ int major;
+ // Driver version format is MAJOR.MINOR[.MICRO] but we only care about the
+ // major version and the full version string. The micro component has been
+ // seen with relevant leading zeros (e.g. "440.48.02").
+ char raw[128]; // The size was picked as "big enough" for version strings.
+} sc_nv_version;
-static void sc_probe_nvidia_driver(struct sc_nvidia_driver *driver)
+static void sc_probe_nvidia_driver(sc_nv_version * version)
{
+ memset(version, 0, sizeof *version);
+
FILE *file SC_CLEANUP(sc_cleanup_file) = NULL;
debug("opening file describing nvidia driver version");
file = fopen(SC_NVIDIA_DRIVER_VERSION_FILE, "rt");
if (file == NULL) {
if (errno == ENOENT) {
debug("nvidia driver version file doesn't exist");
- driver->major_version = 0;
- driver->minor_version = 0;
return;
}
die("cannot open file describing nvidia driver version");
}
- // Driver version format is MAJOR.MINOR where both MAJOR and MINOR are
- // integers. We can use sscanf to parse this data.
- if (fscanf
- (file, "%d.%d", &driver->major_version,
- &driver->minor_version) != 2) {
- die("cannot parse nvidia driver version string");
+ int nread = fread(version->raw, 1, sizeof version->raw - 1, file);
+ if (nread < 0) {
+ die("cannot read nvidia driver version string");
+ }
+ if (nread == sizeof version->raw - 1 && !feof(file)) {
+ die("cannot fit entire nvidia driver version string");
+ }
+ version->raw[nread] = '\0';
+ if (nread > 0 && version->raw[nread - 1] == '\n') {
+ version->raw[nread - 1] = '\0';
+ }
+ if (sscanf(version->raw, "%d.", &version->major) != 1) {
+ die("cannot parse major version from nvidia driver version string");
}
- debug("parsed nvidia driver version: %d.%d", driver->major_version,
- driver->minor_version);
}
static void sc_mkdir_and_mount_and_bind(const char *rootfs_dir,
const char *src_dir,
const char *tgt_dir)
{
- struct sc_nvidia_driver driver;
+ sc_nv_version version;
// Probe sysfs to get the version of the driver that is currently inserted.
- sc_probe_nvidia_driver(&driver);
+ sc_probe_nvidia_driver(&version);
// If there's driver in the kernel then don't mount userspace.
- if (driver.major_version == 0) {
+ if (version.major == 0) {
return;
}
// Construct the paths for the driver userspace libraries
// and for the gl directory.
char src[PATH_MAX] = { 0 };
char dst[PATH_MAX] = { 0 };
- sc_must_snprintf(src, sizeof src, "%s-%d", src_dir,
- driver.major_version);
+ sc_must_snprintf(src, sizeof src, "%s-%d", src_dir, version.major);
sc_must_snprintf(dst, sizeof dst, "%s%s", rootfs_dir, tgt_dir);
// If there is no userspace driver available then don't try to mount it.
@@ -404,6 +417,7 @@
if (access(src, F_OK) != 0) {
return;
}
+ sc_identity old = sc_set_effective_identity(sc_root_group_identity());
int res = mkdir(dst, 0755);
if (res != 0 && errno != EEXIST) {
die("cannot create directory %s", dst);
@@ -412,6 +426,7 @@
// Adjust the ownership only if we created the directory.
die("cannot change ownership of %s", dst);
}
+ (void)sc_set_effective_identity(old);
// Bind mount the binary nvidia driver into $tgt_dir (i.e. /var/lib/snapd/lib/gl).
debug("bind mounting nvidia driver %s -> %s", src, dst);
if (mount(src, dst, NULL, MS_BIND, NULL) != 0) {
@@ -423,21 +438,24 @@
{
char driver_path[512] = { 0 };
- struct sc_nvidia_driver driver;
+ sc_nv_version version;
// Probe sysfs to get the version of the driver that is currently inserted.
- sc_probe_nvidia_driver(&driver);
+ sc_probe_nvidia_driver(&version);
// If there's no driver then we should not bother ourselves with finding the
// matching library
- if (driver.major_version == 0) {
+ if (version.major == 0) {
return 0;
}
- // Probe if a well known library is found in directory dir
+
+ // Probe if a well known library is found in directory dir. We must use the
+ // raw version because it may contain more than just major.minor. In
+ // practice the micro version may have leading zeros that are relevant.
sc_must_snprintf(driver_path, sizeof driver_path,
- "%s/" LIBNVIDIA_GLCORE_SO_PATTERN, dir,
- driver.major_version, driver.minor_version);
+ "%s/libnvidia-glcore.so.%s", dir, version.raw);
+ debug("looking for nvidia canary file %s", driver_path);
if (access(driver_path, F_OK) == 0) {
debug("nvidia library detected at path %s", driver_path);
return 1;
@@ -527,6 +545,7 @@
return;
}
+ sc_identity old = sc_set_effective_identity(sc_root_group_identity());
int res = mkdir(SC_LIB, 0755);
if (res != 0 && errno != EEXIST) {
die("cannot create " SC_LIB);
@@ -535,6 +554,7 @@
// Adjust the ownership only if we created the directory.
die("cannot change ownership of " SC_LIB);
}
+ (void)sc_set_effective_identity(old);
#ifdef NVIDIA_MULTIARCH
sc_mount_nvidia_driver_multiarch(rootfs_dir);
#endif // ifdef NVIDIA_MULTIARCH
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/ns-support.c snapd-2.45.1+18.04/cmd/snap-confine/ns-support.c
--- snapd-2.42.1+18.04/cmd/snap-confine/ns-support.c 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/ns-support.c 2020-06-05 13:13:49.000000000 +0000
@@ -42,6 +42,7 @@
#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"
#include "../libsnap-confine-private/infofile.h"
#include "../libsnap-confine-private/locking.h"
#include "../libsnap-confine-private/mountinfo.h"
@@ -49,6 +50,7 @@
#include "../libsnap-confine-private/tool.h"
#include "../libsnap-confine-private/utils.h"
#include "user-support.h"
+#include "mount-support.h"
/**
* Directory where snap-confine keeps namespace files.
@@ -118,12 +120,16 @@
}
}
-void sc_initialize_mount_ns(void)
+void sc_initialize_mount_ns(unsigned int experimental_features)
{
+ debug("unsharing snap namespace directory");
+
/* Ensure that /run/snapd/ns is a directory. */
+ sc_identity old = sc_set_effective_identity(sc_root_group_identity());
if (sc_nonfatal_mkpath(sc_ns_dir, 0755) < 0) {
die("cannot create directory %s", sc_ns_dir);
}
+ (void)sc_set_effective_identity(old);
/* Read and analyze the mount table. We need to see whether /run/snapd/ns
* is a mount point with private event propagation. */
@@ -161,6 +167,14 @@
die("cannot change propagation type to MS_PRIVATE in %s", sc_ns_dir);
}
}
+
+ /* code that follows is experimental */
+ if (experimental_features & SC_FEATURE_PARALLEL_INSTANCES) {
+ // Ensure that SNAP_MOUNT_DIR and /var/snap are shared mount points
+ debug
+ ("(experimental) ensuring snap mount and data directories are mount points");
+ sc_ensure_snap_dir_shared_mounts();
+ }
}
struct sc_mount_ns {
@@ -441,16 +455,15 @@
value = SC_DISCARD_SHOULD;
value_str = "should";
- // The namespace is stale so also check if we must discard it due to the
- // base snap changing. If the base snap changed, we must discard since even
- // though currently running processes from this snap will continue to see
- // the old base, we want new processes to use the new base. See LP:
- // #1819875 for details.
- if (is_base_transition(inv)) {
- // The base snap has changed. We must discard ...
- value = SC_DISCARD_MUST;
- value_str = "must";
- }
+ }
+ // If the base snap changed, we must discard the mount namespace and
+ // start over to allow the newly started process to see the requested
+ // base snap. Due to the TODO above always perform explicit transition
+ // check to protect against LP:#1819875 and LP:#1861901
+ if (is_base_transition(inv)) {
+ // The base snap has changed. We must discard ...
+ value = SC_DISCARD_MUST;
+ value_str = "must";
}
// Send this back to the parent: 3 - force discard 2 - prefer discard, 1 - keep.
// Note that we cannot just use 0 and 1 because of the semantics of eventfd(2).
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/ns-support.h snapd-2.45.1+18.04/cmd/snap-confine/ns-support.h
--- snapd-2.42.1+18.04/cmd/snap-confine/ns-support.h 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/ns-support.h 2020-06-05 13:13:49.000000000 +0000
@@ -53,9 +53,11 @@
* where namespaces are kept (/run/snapd/ns) is correctly prepared as described
* above.
*
+ * Experimental features can be enabled via optional feature flags.
+ *
* For more details see namespaces(7).
**/
-void sc_initialize_mount_ns(void);
+void sc_initialize_mount_ns(unsigned int experimental_features);
/**
* Data required to manage namespaces amongst a group of processes.
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/seccomp-support.c snapd-2.45.1+18.04/cmd/snap-confine/seccomp-support.c
--- snapd-2.42.1+18.04/cmd/snap-confine/seccomp-support.c 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/seccomp-support.c 2020-06-05 13:13:49.000000000 +0000
@@ -152,7 +152,15 @@
// set on the system.
validate_bpfpath_is_safe(profile_path);
- char bpf[MAX_BPF_SIZE + 1] = { 0 }; // account for EOF
+ /* The extra space has dual purpose. First of all, it is required to detect
+ * feof() while still being able to correctly read MAX_BPF_SIZE bytes of
+ * seccomp profile. In addition, because we treat the profile as a
+ * quasi-string and use sc_streq(), to compare it. The extra space is used
+ * as a way to ensure the result is a terminated string (though in practice
+ * it can contain embedded NULs any earlier position). Note that
+ * sc_read_seccomp_filter knows about the extra space and ensures that the
+ * buffer is never empty. */
+ char bpf[MAX_BPF_SIZE + 1] = { 0 };
size_t num_read = sc_read_seccomp_filter(profile_path, bpf, sizeof bpf);
if (sc_streq(bpf, "@unrestricted\n")) {
return false;
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/seccomp-support-ext.c snapd-2.45.1+18.04/cmd/snap-confine/seccomp-support-ext.c
--- snapd-2.42.1+18.04/cmd/snap-confine/seccomp-support-ext.c 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/seccomp-support-ext.c 2020-06-05 13:13:49.000000000 +0000
@@ -45,11 +45,15 @@
#endif
size_t sc_read_seccomp_filter(const char *filename, char *buf, size_t buf_size) {
+ if (buf_size == 0) {
+ die("seccomp load buffer cannot be empty");
+ }
FILE *file = fopen(filename, "rb");
if (file == NULL) {
die("cannot open seccomp filter %s", filename);
}
- size_t num_read = fread(buf, 1, buf_size, file);
+ size_t num_read = fread(buf, 1, buf_size - 1, file);
+ buf[num_read] = '\0';
if (ferror(file) != 0) {
die("cannot read seccomp profile %s", filename);
}
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/snap-confine.apparmor.in snapd-2.45.1+18.04/cmd/snap-confine/snap-confine.apparmor.in
--- snapd-2.42.1+18.04/cmd/snap-confine/snap-confine.apparmor.in 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/snap-confine.apparmor.in 2020-06-05 13:13:49.000000000 +0000
@@ -14,24 +14,24 @@
# any abstractions
/etc/ld.so.cache r,
/etc/ld.so.preload r,
- /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}ld-*.so mrix,
+ /{,usr/}lib{,32,64,x32}/{,@{multiarch}/{,atomics/}}ld-*.so mrix,
# libc, you are funny
- /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libc{,-[0-9]*}.so* mr,
- /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libpthread{,-[0-9]*}.so* mr,
+ /{,usr/}lib{,32,64,x32}/{,@{multiarch}/{,atomics/}}libc{,-[0-9]*}.so* mr,
+ /{,usr/}lib{,32,64,x32}/{,@{multiarch}/{,atomics/}}libpthread{,-[0-9]*}.so* mr,
/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libreadline{,-[0-9]*}.so* mr,
- /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}librt{,-[0-9]*}.so* mr,
+ /{,usr/}lib{,32,64,x32}/{,@{multiarch}/{,atomics/}}librt{,-[0-9]*}.so* mr,
/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libgcc_s.so* mr,
/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libncursesw{,-[0-9]*}.so* mr,
- /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libresolv{,-[0-9]*}.so* mr,
+ /{,usr/}lib{,32,64,x32}/{,@{multiarch}/{,atomics/}}libresolv{,-[0-9]*}.so* mr,
/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libselinux.so* mr,
- /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libpcre.so* mr,
+ /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libpcre{,2}{,-[0-9]*}.so* mr,
/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libmount.so* mr,
/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libblkid.so* mr,
/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libuuid.so* mr,
# normal libs in order
/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libapparmor.so* mr,
/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libcgmanager.so* mr,
- /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libdl{,-[0-9]*}.so* mr,
+ /{,usr/}lib{,32,64,x32}/{,@{multiarch}/{,atomics/}}libdl{,-[0-9]*}.so* mr,
/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libnih.so* mr,
/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libnih-dbus.so* mr,
/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libdbus-1.so* mr,
@@ -56,25 +56,24 @@
capability dac_read_search,
capability dac_override,
/sys/fs/cgroup/devices/snap{,py}.*/ w,
- /sys/fs/cgroup/devices/snap{,py}.*/tasks w,
+ /sys/fs/cgroup/devices/snap{,py}.*/cgroup.procs w,
/sys/fs/cgroup/devices/snap{,py}.*/devices.{allow,deny} w,
# cgroup: freezer
# Allow creating per-snap cgroup freezers and adding snap command (task)
# invocations to the freezer. This allows for reliably enumerating all
- # running tasks for the snap. In addition, allow enumerating processes in
- # the cgroup to determine if it is occupied.
+ # running processes for the snap. In addition, allow enumerating processes
+ # in the cgroup to determine if it is occupied.
/sys/fs/cgroup/freezer/ r,
/sys/fs/cgroup/freezer/snap.*/ w,
- /sys/fs/cgroup/freezer/snap.*/tasks w,
- /sys/fs/cgroup/freezer/snap.*/cgroup.procs r,
+ /sys/fs/cgroup/freezer/snap.*/cgroup.procs rw,
# cgroup: pids
# allow creating per snap-security-tag hierarchy and adding snap command (task)
# invocations to the controller.
/sys/fs/cgroup/pids/ r,
/sys/fs/cgroup/pids/snap.*/ w,
- /sys/fs/cgroup/pids/snap.*/tasks w,
+ /sys/fs/cgroup/pids/snap.*/cgroup.procs w,
# querying udev
/etc/udev/udev.conf r,
@@ -131,9 +130,11 @@
# reading seccomp filters
/{tmp/snap.rootfs_*/,}var/lib/snapd/seccomp/bpf/*.bin r,
- # LP: #1668659
+ # LP: #1668659 and parallel instaces of classic snaps
mount options=(rw rbind) /snap/ -> /snap/,
mount options=(rw rshared) -> /snap/,
+ mount options=(rw rbind) /var/lib/snapd/snap/ -> /var/lib/snapd/snap/,
+ mount options=(rw rshared) -> /var/lib/snapd/snap/,
# boostrapping the mount namespace
mount options=(rw rshared) -> /,
@@ -175,6 +176,9 @@
mount options=(rw rbind) /tmp/ -> /tmp/snap.rootfs_*/tmp/,
mount options=(rw rslave) -> /tmp/snap.rootfs_*/tmp/,
+ mount options=(rw rbind) /var/lib/dhcp/ -> /tmp/snap.rootfs_*/var/lib/dhcp/,
+ mount options=(rw rslave) -> /tmp/snap.rootfs_*/var/lib/dhcp/,
+
mount options=(rw rbind) /var/lib/snapd/ -> /tmp/snap.rootfs_*/var/lib/snapd/,
mount options=(rw rslave) -> /tmp/snap.rootfs_*/var/lib/snapd/,
@@ -232,8 +236,17 @@
# pivot_root preparation and execution
mount options=(rw bind) /tmp/snap.rootfs_*/var/lib/snapd/hostfs/ -> /tmp/snap.rootfs_*/var/lib/snapd/hostfs/,
mount options=(rw private) -> /tmp/snap.rootfs_*/var/lib/snapd/hostfs/,
- # pivot_root mediation in AppArmor is not complete. See LP: #1791711
- pivot_root,
+
+ # pivot_root mediation in AppArmor is not complete. See LP: #1791711.
+ # However, we can mediate the new_root and put_old to be what we expect,
+ # and then deny directory creation within old_root to prevent trivial
+ # pivoting into a whitelisted path.
+ pivot_root oldroot=/tmp/snap.rootfs_*/var/lib/snapd/hostfs/ /tmp/snap.rootfs_*/,
+ # Explicitly deny creating the old_root directory in case it is
+ # inadvertently added somewhere else. While this doesn't resolve
+ # LP: #1791711, it provides some hardening.
+ audit deny /tmp/snap.rootfs_*/{var/,var/lib/,var/lib/snapd/,var/lib/snapd/hostfs/} w,
+
# cleanup
umount /var/lib/snapd/hostfs/tmp/snap.rootfs_*/,
umount /var/lib/snapd/hostfs/sys/,
@@ -241,9 +254,20 @@
umount /var/lib/snapd/hostfs/proc/,
mount options=(rw rslave) -> /var/lib/snapd/hostfs/,
+ # Hide /writable from view of snaps.
+ mount options=(rprivate) -> /{,var/lib/snapd/hostfs/}writable/,
+ umount /{,var/lib/snapd/hostfs/}writable/,
+
# set up user mount namespace
mount options=(rslave) -> /,
+ # set up mount namespace for parallel instances of classic snaps
+ mount options=(rw rbind) @SNAP_MOUNT_DIR@/{,*/} -> @SNAP_MOUNT_DIR@/{,*/},
+ mount options=(rslave) -> @SNAP_MOUNT_DIR@/,
+ mount options=(rslave) -> /var/snap/,
+ mount options=(rw rbind) /var/snap/{,*/} -> /var/snap/{,*/},
+ mount options=(rw rshared) -> /var/snap/,
+
# Allow reading the os-release file (possibly a symlink to /usr/lib).
/{etc/,usr/lib/}os-release r,
@@ -419,15 +443,15 @@
# We run privileged, so be fanatical about what we include and don't use
# any abstractions
/etc/ld.so.cache r,
- /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}ld-*.so mrix,
+ /{,usr/}lib{,32,64,x32}/{,@{multiarch}/{,atomics/}}ld-*.so mrix,
# libc, you are funny
- /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libc{,-[0-9]*}.so* mr,
- /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libpthread{,-[0-9]*}.so* mr,
+ /{,usr/}lib{,32,64,x32}/{,@{multiarch}/{,atomics/}}libc{,-[0-9]*}.so* mr,
+ /{,usr/}lib{,32,64,x32}/{,@{multiarch}/{,atomics/}}libpthread{,-[0-9]*}.so* mr,
/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libreadline{,-[0-9]*}.so* mr,
- /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}librt{,-[0-9]*}.so* mr,
+ /{,usr/}lib{,32,64,x32}/{,@{multiarch}/{,atomics/}}librt{,-[0-9]*}.so* mr,
/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libgcc_s.so* mr,
/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libncursesw{,-[0-9]*}.so* mr,
- /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libresolv{,-[0-9]*}.so* mr,
+ /{,usr/}lib{,32,64,x32}/{,@{multiarch}/{,atomics/}}libresolv{,-[0-9]*}.so* mr,
/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libselinux.so* mr,
/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libpcre.so* mr,
/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libmount.so* mr,
@@ -436,7 +460,7 @@
# normal libs in order
/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libapparmor.so* mr,
/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libcgmanager.so* mr,
- /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libdl{,-[0-9]*}.so* mr,
+ /{,usr/}lib{,32,64,x32}/{,@{multiarch}/{,atomics/}}libdl{,-[0-9]*}.so* mr,
/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libnih.so* mr,
/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libnih-dbus.so* mr,
/{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libdbus-1.so* mr,
@@ -531,4 +555,22 @@
# Allow mounting /var/lib/jenkins from the host into the snap.
mount options=(rw rbind) /var/lib/jenkins/ -> /tmp/snap.rootfs_*/var/lib/jenkins/,
mount options=(rw rslave) -> /tmp/snap.rootfs_*/var/lib/jenkins/,
+
+ # Suppress noisy file_inherit denials (LP: #1850552) until LP: #1849753 is
+ # fixed.
+ deny /dev/shm/.org.chromium.Chromium.* rw,
+
+ # While snap-confine itself doesn't require unix rules and therefore all
+ # unix rules are implicitly denied, adding an explicit deny for unix to
+ # silence noisy denials breaks nested lxd. Until the cause is determined,
+ # do not use an explicit deny for unix. (LP: #1855355)
+ #deny unix,
+
+ # Explicitly deny these accesses which show up on Arch to silence the
+ # denials for this unneeded access.
+ deny /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libnss_files-[0-9]*.so* mr,
+ deny /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libnss_mymachines.[0-9]*.so* mr,
+ deny /{,usr/}lib{,32,64,x32}/{,@{multiarch}/}libnss_systemd.[0-9]*.so* mr,
+ deny /etc/nsswitch.conf r,
+ deny /etc/passwd r,
}
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/snap-confine.c snapd-2.45.1+18.04/cmd/snap-confine/snap-confine.c
--- snapd-2.42.1+18.04/cmd/snap-confine/snap-confine.c 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/snap-confine.c 2020-06-05 13:13:49.000000000 +0000
@@ -62,15 +62,20 @@
// this directory was created with permissions 1777.
static void sc_maybe_fixup_permissions(void)
{
+ int fd SC_CLEANUP(sc_cleanup_close) = -1;
struct stat buf;
- if (stat("/var/lib", &buf) != 0) {
+ fd = open("/var/lib", O_PATH | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
+ if (fd < 0) {
+ die("cannot open /var/lib");
+ }
+ if (fstat(fd, &buf) < 0) {
die("cannot stat /var/lib");
}
if ((buf.st_mode & 0777) == 0777) {
- if (chmod("/var/lib", 0755) != 0) {
+ if (fchmod(fd, 0755) != 0) {
die("cannot chmod /var/lib");
}
- if (chown("/var/lib", 0, 0) != 0) {
+ if (fchown(fd, 0, 0) != 0) {
die("cannot chown /var/lib");
}
}
@@ -291,13 +296,18 @@
sc_cleanup_close(&proc_state->orig_cwd_fd);
}
-static void enter_classic_execution_environment(void);
+static void enter_classic_execution_environment(const sc_invocation * inv,
+ gid_t real_gid,
+ gid_t saved_gid);
static void enter_non_classic_execution_environment(sc_invocation * inv,
struct sc_apparmor *aa,
uid_t real_uid,
gid_t real_gid,
gid_t saved_gid);
+static void maybe_join_tracking_cgroup(const sc_invocation * inv,
+ gid_t real_gid, gid_t saved_gid);
+
int main(int argc, char **argv)
{
// Use our super-defensive parser to figure out what we've been asked to do.
@@ -345,21 +355,10 @@
debug("rgid: %d, egid: %d, sgid: %d",
real_gid, effective_gid, saved_gid);
- // snap-confine runs as both setuid root and setgid root.
- // Temporarily drop group privileges here and reraise later
- // as needed.
- if (effective_gid == 0 && real_gid != 0) {
- if (setegid(real_gid) != 0) {
- die("cannot set effective group id to %d", real_gid);
- }
- }
-#ifndef CAPS_OVER_SETUID
- // this code always needs to run as root for the cgroup/udev setup,
- // however for the tests we allow it to run as non-root
- if (geteuid() != 0 && secure_getenv("SNAP_CONFINE_NO_ROOT") == NULL) {
+ // snap-confine needs to run as root for cgroup/udev/mount/apparmor/etc setup.
+ if (effective_uid != 0) {
die("need to run as root or suid");
}
-#endif
char *snap_context SC_CLEANUP(sc_cleanup_string) = NULL;
// Do no get snap context value if running a hook (we don't want to overwrite hook's SNAP_COOKIE)
@@ -389,30 +388,63 @@
" but should be. Refusing to continue to avoid"
" permission escalation attacks");
}
- // TODO: check for similar situation and linux capabilities.
- if (geteuid() == 0) {
- if (invocation.classic_confinement) {
- enter_classic_execution_environment();
- } else {
- enter_non_classic_execution_environment(&invocation,
- &apparmor,
- real_uid,
- real_gid,
- saved_gid);
- }
- // The rest does not so temporarily drop privs back to calling
- // user (we'll permanently drop after loading seccomp)
- if (setegid(real_gid) != 0)
- die("setegid failed");
- if (seteuid(real_uid) != 0)
- die("seteuid failed");
-
- if (real_gid != 0 && geteuid() == 0)
- die("dropping privs did not work");
- if (real_uid != 0 && getegid() == 0)
- die("dropping privs did not work");
- }
- // Ensure that the user data path exists.
+
+ /* perform global initialization of mount namespace support for non-classic
+ * snaps or both classic and non-classic when parallel-instances feature is
+ * enabled */
+ if (!invocation.classic_confinement ||
+ sc_feature_enabled(SC_FEATURE_PARALLEL_INSTANCES)) {
+
+ /* snap-confine uses privately-shared /run/snapd/ns to store bind-mounted
+ * mount namespaces of each snap. In the case that snap-confine is invoked
+ * from the mount namespace it typically constructs, the said directory
+ * does not contain mount entries for preserved namespaces as those are
+ * only visible in the main, outer namespace.
+ *
+ * In order to operate in such an environment snap-confine must first
+ * re-associate its own process with another namespace in which the
+ * /run/snapd/ns directory is visible. The most obvious candidate is pid
+ * one, which definitely doesn't run in a snap-specific namespace, has a
+ * predictable PID and is long lived.
+ */
+ sc_reassociate_with_pid1_mount_ns();
+ // Do global initialization:
+ int global_lock_fd = sc_lock_global();
+ // Ensure that "/" or "/snap" is mounted with the
+ // "shared" option on legacy systems, see LP:#1668659
+ debug("ensuring that snap mount directory is shared");
+ sc_ensure_shared_snap_mount();
+ unsigned int experimental_features = 0;
+ if (sc_feature_enabled(SC_FEATURE_PARALLEL_INSTANCES)) {
+ experimental_features |= SC_FEATURE_PARALLEL_INSTANCES;
+ }
+ sc_initialize_mount_ns(experimental_features);
+ sc_unlock(global_lock_fd);
+ }
+
+ if (invocation.classic_confinement) {
+ enter_classic_execution_environment(&invocation, real_gid,
+ saved_gid);
+ } else {
+ enter_non_classic_execution_environment(&invocation,
+ &apparmor,
+ real_uid,
+ real_gid, saved_gid);
+ }
+ // Temporarily drop privileges back to the calling user until we can
+ // permanently drop (which we can't do just yet due to seccomp, see
+ // below).
+ sc_identity real_user_identity = {
+ .uid = real_uid,
+ .gid = real_gid,
+ .change_uid = 1,
+ .change_gid = 1,
+ };
+ sc_set_effective_identity(real_user_identity);
+ // Ensure that the user data path exists. When creating it use the identity
+ // of the calling user (by using real user and group identifiers). This
+ // allows the creation of directories inside ~/ on NFS with root_squash
+ // attribute.
setup_user_data();
#if 0
setup_user_xdg_runtime_dir();
@@ -516,16 +548,66 @@
return 1;
}
-static void enter_classic_execution_environment(void)
+static void enter_classic_execution_environment(const sc_invocation * inv,
+ gid_t real_gid, gid_t saved_gid)
{
+ /* with parallel-instances enabled, main() reassociated with the mount ns of
+ * PID 1 to make /run/snapd/ns visible */
+
/* 'classic confinement' is designed to run without the sandbox inside the
* shared namespace. Specifically:
- * - snap-confine skips using the snap-specific mount namespace
+ * - snap-confine skips using the snap-specific, private, mount namespace
* - snap-confine skips using device cgroups
* - snapd sets up a lenient AppArmor profile for snap-confine to use
* - snapd sets up a lenient seccomp profile for snap-confine to use
*/
- debug("skipping sandbox setup, classic confinement in use");
+ debug("preparing classic execution environment");
+
+ /* Join a tracking cgroup if appropriate feature is enabled. */
+ int snap_lock_fd = sc_lock_snap(inv->snap_instance);
+ maybe_join_tracking_cgroup(inv, real_gid, saved_gid);
+ sc_unlock(snap_lock_fd);
+
+ if (!sc_feature_enabled(SC_FEATURE_PARALLEL_INSTANCES)) {
+ return;
+ }
+
+ /* all of the following code is experimental and part of parallel instances
+ * of classic snaps support */
+
+ debug
+ ("(experimental) unsharing the mount namespace (per-classic-snap)");
+
+ /* Construct a mount namespace where the snap instance directories are
+ * visible under the regular snap name. In order to do that we will:
+ *
+ * - convert SNAP_MOUNT_DIR into a mount point (global init)
+ * - convert /var/snap into a mount point (global init)
+ * - always create a new mount namespace
+ * - for snaps with non empty instance key:
+ * - set slave propagation recursively on SNAP_MOUNT_DIR and /var/snap
+ * - recursively bind mount SNAP_MOUNT_DIR/_ on top of SNAP_MOUNT_DIR/
+ * - recursively bind mount /var/snap/_ on top of /var/snap/
+ *
+ * The destination directories /var/snap/ and SNAP_MOUNT_DIR/
+ * are guaranteed to exist and were created during installation of a given
+ * instance.
+ */
+
+ if (unshare(CLONE_NEWNS) < 0) {
+ die("cannot unshare the mount namespace for parallel installed classic snap");
+ }
+
+ /* Parallel installed classic snap get special handling */
+ if (!sc_streq(inv->snap_instance, inv->snap_name)) {
+ debug
+ ("(experimental) setting up environment for classic snap instance %s",
+ inv->snap_instance);
+
+ /* set up mappings for snap and data directories */
+ sc_setup_parallel_instance_classic_mounts(inv->snap_name,
+ inv->snap_instance);
+ }
}
static void enter_non_classic_execution_environment(sc_invocation * inv,
@@ -534,28 +616,8 @@
gid_t real_gid,
gid_t saved_gid)
{
- /* snap-confine uses privately-shared /run/snapd/ns to store bind-mounted
- * mount namespaces of each snap. In the case that snap-confine is invoked
- * from the mount namespace it typically constructs, the said directory
- * does not contain mount entries for preserved namespaces as those are
- * only visible in the main, outer namespace.
- *
- * In order to operate in such an environment snap-confine must first
- * re-associate its own process with another namespace in which the
- * /run/snapd/ns directory is visible. The most obvious candidate is pid
- * one, which definitely doesn't run in a snap-specific namespace, has a
- * predictable PID and is long lived.
- */
- sc_reassociate_with_pid1_mount_ns();
- // Do global initialization:
- int global_lock_fd = sc_lock_global();
- // ensure that "/" or "/snap" is mounted with the
- // "shared" option, see LP:#1668659
- debug("ensuring that snap mount directory is shared");
- sc_ensure_shared_snap_mount();
- debug("unsharing snap namespace directory");
- sc_initialize_mount_ns();
- sc_unlock(global_lock_fd);
+ // main() reassociated with the mount ns of PID 1 to make /run/snapd/ns
+ // visible
// Find and open snap-update-ns and snap-discard-ns from the same
// path as where we (snap-confine) were called.
@@ -625,7 +687,8 @@
if (unshare(CLONE_NEWNS) < 0) {
die("cannot unshare the mount namespace");
}
- sc_populate_mount_ns(aa, snap_update_ns_fd, inv);
+ sc_populate_mount_ns(aa, snap_update_ns_fd, inv, real_gid,
+ saved_gid);
sc_store_ns_info(inv);
/* Preserve the mount namespace. */
@@ -673,24 +736,12 @@
// This simplifies testing if any processes belonging to a given snap are
// still alive as well as to properly account for each application and
// service.
- if (getegid() != 0 && saved_gid == 0) {
- // Temporarily raise egid so we can chown the freezer cgroup under LXD.
- if (setegid(0) != 0) {
- die("cannot set effective group id to root");
- }
- }
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) {
- die("cannot set effective group id to %d", real_gid);
- }
}
+ maybe_join_tracking_cgroup(inv, real_gid, saved_gid);
+
sc_unlock(snap_lock_fd);
sc_close_mount_ns(group);
@@ -719,3 +770,13 @@
}
}
}
+
+static void maybe_join_tracking_cgroup(const sc_invocation * inv,
+ gid_t real_gid, gid_t saved_gid)
+{
+ if (!sc_cgroup_is_v2()) {
+ if (sc_feature_enabled(SC_FEATURE_REFRESH_APP_AWARENESS)) {
+ sc_cgroup_pids_join(inv->security_tag, getpid());
+ }
+ }
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/snap-confine.rst snapd-2.45.1+18.04/cmd/snap-confine/snap-confine.rst
--- snapd-2.42.1+18.04/cmd/snap-confine/snap-confine.rst 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/snap-confine.rst 2020-06-05 13:13:49.000000000 +0000
@@ -114,9 +114,6 @@
`SNAPPY_LAUNCHER_INSIDE_TESTS`:
Internal variable that should not be relied upon.
-`SNAP_CONFINE_NO_ROOT`:
- Internal variable that should not be relied upon.
-
`SNAPPY_LAUNCHER_SECCOMP_PROFILE_DIR`:
Internal variable that should not be relied upon.
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/data/apt-keys/README.md snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/data/apt-keys/README.md
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/data/apt-keys/README.md 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/data/apt-keys/README.md 1970-01-01 00:00:00.000000000 +0000
@@ -1,4 +0,0 @@
-This directory contains keys used by the sbuild program to sign the temporary
-archive. Those keys are kept in the tree as ephemeral test virtual machines do
-not have sufficient entropy to generate keys by themselves in reasonable amount
-of time.
Binary files /tmp/tmpVdGiJf/yxIP3HeHGM/snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/data/apt-keys/sbuild-key.pub and /tmp/tmpVdGiJf/GBXG3G_EnR/snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/data/apt-keys/sbuild-key.pub differ
Binary files /tmp/tmpVdGiJf/yxIP3HeHGM/snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/data/apt-keys/sbuild-key.sec and /tmp/tmpVdGiJf/GBXG3G_EnR/snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/data/apt-keys/sbuild-key.sec differ
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/distros/debian. snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/distros/debian.
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/distros/debian. 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/distros/debian. 1970-01-01 00:00:00.000000000 +0000
@@ -1,2 +0,0 @@
-distro_codename=sid
-distro_packaging_git_branch=debian
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/distros/debian.common snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/distros/debian.common
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/distros/debian.common 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/distros/debian.common 1970-01-01 00:00:00.000000000 +0000
@@ -1,12 +0,0 @@
-if [ -n "${APT_PROXY:-}" ]; then
- distro_archive=${APT_PROXY}/ftp.debian.org/debian
-else
- distro_archive=http://ftp.debian.org/debian
-fi
-# NOTE: Debian packaging needs to be updated. I sent a mail to the
-# debian maintainer with instructions on what needs to happen and
-# how it fits into the CI system.
-#
-# For now all builds on debian will fail as they still contains
-# debian/patches that are now applied upstream.
-distro_packaging_git=git://anonscm.debian.org/collab-maint/snap-confine.git
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/distros/ubuntu.14.04 snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/distros/ubuntu.14.04
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/distros/ubuntu.14.04 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/distros/ubuntu.14.04 1970-01-01 00:00:00.000000000 +0000
@@ -1,2 +0,0 @@
-distro_codename=trusty
-distro_packaging_git_branch=14.04
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/distros/ubuntu.16.04 snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/distros/ubuntu.16.04
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/distros/ubuntu.16.04 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/distros/ubuntu.16.04 1970-01-01 00:00:00.000000000 +0000
@@ -1,2 +0,0 @@
-distro_codename=xenial
-distro_packaging_git_branch=16.04
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/distros/ubuntu.16.10 snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/distros/ubuntu.16.10
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/distros/ubuntu.16.10 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/distros/ubuntu.16.10 1970-01-01 00:00:00.000000000 +0000
@@ -1,2 +0,0 @@
-distro_codename=yakkety
-distro_packaging_git_branch=16.10
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/distros/ubuntu.common snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/distros/ubuntu.common
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/distros/ubuntu.common 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/distros/ubuntu.common 1970-01-01 00:00:00.000000000 +0000
@@ -1,7 +0,0 @@
-if [ -n "${APT_PROXY:-}" ]; then
- distro_archive=${APT_PROXY}/archive.ubuntu.com/ubuntu
-else
- distro_archive=http://archive.ubuntu.com/ubuntu
-fi
-distro_packaging_git=https://git.launchpad.net/snap-confine
-sbuild_createchroot_extra="--components=main,universe"
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/cgroup-used/task.yaml snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/cgroup-used/task.yaml
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/cgroup-used/task.yaml 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/cgroup-used/task.yaml 2020-06-05 13:13:49.000000000 +0000
@@ -28,7 +28,7 @@
snapd-hacker-toolbelt.busybox echo "Hello World" | grep Hello
if ! grep 'c 1:11 rwm' /sys/fs/cgroup/devices/snap.snapd-hacker-toolbelt.busybox/devices.list ; then exit 1; fi
restore: |
- snap remove snapd-hacker-toolbelt
+ snap remove --purge snapd-hacker-toolbelt
rm -f /etc/udev/rules.d/70-spread-test.rules
udevadm control --reload-rules
udevadm settle
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/core-is-preferred/task.yaml snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/core-is-preferred/task.yaml
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/core-is-preferred/task.yaml 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/core-is-preferred/task.yaml 2020-06-05 13:13:49.000000000 +0000
@@ -5,6 +5,6 @@
execute: |
snapd-hacker-toolbelt.busybox cat /meta/snap.yaml | grep -q -F 'name: core'
restore: |
- snap remove snapd-hacker-toolbelt
+ snap remove --purge snapd-hacker-toolbelt
# XXX: the core snap cannot be removed, we should use a trick to remove it
# in some other way but this can wait.
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/hostfs-created-on-demand/task.yaml snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/hostfs-created-on-demand/task.yaml
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/hostfs-created-on-demand/task.yaml 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/hostfs-created-on-demand/task.yaml 2020-06-05 13:13:49.000000000 +0000
@@ -18,7 +18,7 @@
echo "We can now check that the directory was created on the system"
test -d /var/lib/snapd/hostfs
restore: |
- snap remove snapd-hacker-toolbelt
+ snap remove --purge snapd-hacker-toolbelt
if [ -d /var/lib/snapd/hostfs.orig ]; then
mv /var/lib/snapd/hostfs.orig /var/lib/snapd/hostfs
fi
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/media-visible-in-devmode/task.yaml snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/media-visible-in-devmode/task.yaml
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/media-visible-in-devmode/task.yaml 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/media-visible-in-devmode/task.yaml 2020-06-05 13:13:49.000000000 +0000
@@ -11,5 +11,5 @@
echo "We can see the canary file in /media"
[ "$(snapd-hacker-toolbelt.busybox cat /media/canary)" = "test" ]
restore: |
- snap remove snapd-hacker-toolbelt
+ snap remove --purge snapd-hacker-toolbelt
rm -f /media/canary
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.core.json snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.core.json
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.core.json 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.core.json 1970-01-01 00:00:00.000000000 +0000
@@ -1,1028 +0,0 @@
-[
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devtmpfs",
- "mount_opts": "rw,nosuid,relatime",
- "mount_point": "/dev",
- "mount_src": "udev",
- "opt_fields": [
- "master:renumbered/1"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "hugetlbfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/hugepages",
- "mount_src": "hugetlbfs",
- "opt_fields": [
- "master:renumbered/2"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "mqueue",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/mqueue",
- "mount_src": "mqueue",
- "opt_fields": [
- "master:renumbered/3"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/ptmx",
- "mount_src": "devpts",
- "opt_fields": [],
- "root_dir": "/ptmx"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [
- "master:renumbered/4"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev",
- "mount_point": "/dev/shm",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/5"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/etc",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/etc"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/alternatives",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/etc/alternatives"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/home",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/home"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/lib/modules",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/lib/modules"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/media",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "shared:renumbered/7"
- ],
- "root_dir": "/media"
- },
- {
- "fs_type": "proc",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/proc",
- "mount_src": "proc",
- "opt_fields": [
- "master:renumbered/8"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "autofs",
- "mount_opts": "rw,relatime",
- "mount_point": "/proc/sys/fs/binfmt_misc",
- "mount_src": "systemd-1",
- "opt_fields": [
- "master:renumbered/9"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/root",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/root"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/10"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/run/lock",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/11"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/run/snapd/ns",
- "mount_src": "tmpfs",
- "opt_fields": [],
- "root_dir": "/snapd/ns"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/12"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/snap",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/snap"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/snapd-hacker-toolbelt/NUMBER",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/13"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/ubuntu-core/NUMBER",
- "mount_src": "/dev/remapped-loop2",
- "opt_fields": [
- "master:renumbered/14"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "sysfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys",
- "mount_src": "sysfs",
- "opt_fields": [
- "master:renumbered/15"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "ro,nosuid,nodev,noexec",
- "mount_point": "/sys/fs/cgroup",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/16"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/blkio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/17"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/cpu,cpuacct",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/18"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/cpuset",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/19"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/devices",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/20"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/freezer",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/21"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/hugetlb",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/22"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/memory",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/23"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/net_cls,net_prio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/24"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/perf_event",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/25"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/pids",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/26"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/systemd",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/27"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "fusectl",
- "mount_opts": "rw,relatime",
- "mount_point": "/sys/fs/fuse/connections",
- "mount_src": "fusectl",
- "opt_fields": [
- "master:renumbered/28"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "pstore",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/pstore",
- "mount_src": "pstore",
- "opt_fields": [
- "master:renumbered/29"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "debugfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/sys/kernel/debug",
- "mount_src": "debugfs",
- "opt_fields": [
- "master:renumbered/30"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "securityfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/kernel/security",
- "mount_src": "securityfs",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/tmp",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/tmp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/tmp",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [],
- "root_dir": "/tmp/snap.0_snap.snapd-hacker-toolbelt.busybox_XXXXXX/tmp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/usr/src",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/usr/src"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/var/lib",
- "mount_src": "none",
- "opt_fields": [],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/apparmor",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/apparmor"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/classic",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/classic"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/console-conf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/console-conf"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/dbus",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/dbus"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/dhcp",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/dhcp"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/extrausers",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/extrausers"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/initramfs-tools",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/initramfs-tools"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/initscripts",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/initscripts"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/insserv",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/insserv"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/logrotate",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/logrotate"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/machines",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/machines"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/misc",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/misc"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/pam",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/pam"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/python",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/python"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/resolvconf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/resolvconf"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/lib/snapd",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/lib/snapd"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/lib/snapd/hostfs",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/lib/snapd/hostfs",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [],
- "root_dir": "/var/lib/snapd/hostfs"
- },
- {
- "fs_type": "devtmpfs",
- "mount_opts": "rw,nosuid,relatime",
- "mount_point": "/var/lib/snapd/hostfs/dev",
- "mount_src": "udev",
- "opt_fields": [
- "master:renumbered/1"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "hugetlbfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/dev/hugepages",
- "mount_src": "hugetlbfs",
- "opt_fields": [
- "master:renumbered/2"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "mqueue",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/dev/mqueue",
- "mount_src": "mqueue",
- "opt_fields": [
- "master:renumbered/3"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [
- "master:renumbered/4"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev",
- "mount_point": "/var/lib/snapd/hostfs/dev/shm",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/5"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "proc",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/proc",
- "mount_src": "proc",
- "opt_fields": [
- "master:renumbered/8"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "autofs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/proc/sys/fs/binfmt_misc",
- "mount_src": "systemd-1",
- "opt_fields": [
- "master:renumbered/9"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/10"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/lock",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/11"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/snapd/ns",
- "mount_src": "tmpfs",
- "opt_fields": [],
- "root_dir": "/snapd/ns"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/12"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/core/NUMBER",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/snapd-hacker-toolbelt/NUMBER",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/13"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/ubuntu-core/NUMBER",
- "mount_src": "/dev/remapped-loop2",
- "opt_fields": [
- "master:renumbered/14"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "sysfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys",
- "mount_src": "sysfs",
- "opt_fields": [
- "master:renumbered/15"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "ro,nosuid,nodev,noexec",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/16"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/blkio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/17"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/cpu,cpuacct",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/18"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/cpuset",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/19"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/devices",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/20"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/freezer",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/21"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/hugetlb",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/22"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/memory",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/23"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/net_cls,net_prio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/24"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/perf_event",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/25"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/pids",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/26"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/systemd",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/27"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "fusectl",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/fuse/connections",
- "mount_src": "fusectl",
- "opt_fields": [
- "master:renumbered/28"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "pstore",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/pstore",
- "mount_src": "pstore",
- "opt_fields": [
- "master:renumbered/29"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "debugfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/kernel/debug",
- "mount_src": "debugfs",
- "opt_fields": [
- "master:renumbered/30"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "securityfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/kernel/security",
- "mount_src": "securityfs",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/sudo",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/sudo"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/systemd",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/systemd"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/ubuntu-fan",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/ubuntu-fan"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/ucf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/ucf"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/update-rc.d",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/update-rc.d"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/urandom",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/urandom"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/vim",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/vim"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/waagent",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/waagent"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/log",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/log"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/snap",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/snap"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/tmp",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/tmp"
- }
-]
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.core.linode.amd64.json snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.core.linode.amd64.json
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.core.linode.amd64.json 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.core.linode.amd64.json 1970-01-01 00:00:00.000000000 +0000
@@ -1,798 +0,0 @@
-[
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devtmpfs",
- "mount_opts": "rw,nosuid,relatime",
- "mount_point": "/dev",
- "mount_src": "udev",
- "opt_fields": [
- "master:renumbered/1"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "hugetlbfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/hugepages",
- "mount_src": "hugetlbfs",
- "opt_fields": [
- "master:renumbered/2"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "mqueue",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/mqueue",
- "mount_src": "mqueue",
- "opt_fields": [
- "master:renumbered/3"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/ptmx",
- "mount_src": "devpts",
- "opt_fields": [],
- "root_dir": "/ptmx"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [
- "master:renumbered/4"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev",
- "mount_point": "/dev/shm",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/5"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/etc",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/etc"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/alternatives",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/etc/alternatives"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/home",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/home"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/lib/modules",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/lib/modules"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/media",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "shared:renumbered/7"
- ],
- "root_dir": "/media"
- },
- {
- "fs_type": "proc",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/proc",
- "mount_src": "proc",
- "opt_fields": [
- "master:renumbered/8"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "autofs",
- "mount_opts": "rw,relatime",
- "mount_point": "/proc/sys/fs/binfmt_misc",
- "mount_src": "systemd-1",
- "opt_fields": [
- "master:renumbered/9"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/root",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/root"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/10"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/run/lock",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/11"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/run/snapd/ns",
- "mount_src": "tmpfs",
- "opt_fields": [],
- "root_dir": "/snapd/ns"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/12"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/snap",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/snap"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/snapd-hacker-toolbelt/NUMBER",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/13"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/ubuntu-core/NUMBER",
- "mount_src": "/dev/remapped-loop2",
- "opt_fields": [
- "master:renumbered/14"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "sysfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys",
- "mount_src": "sysfs",
- "opt_fields": [
- "master:renumbered/15"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "ro,nosuid,nodev,noexec",
- "mount_point": "/sys/fs/cgroup",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/16"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/blkio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/17"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/cpu,cpuacct",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/18"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/cpuset",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/19"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/devices",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/20"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/freezer",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/21"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/hugetlb",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/22"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/memory",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/23"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/net_cls,net_prio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/24"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/perf_event",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/25"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/pids",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/26"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/systemd",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/27"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "fusectl",
- "mount_opts": "rw,relatime",
- "mount_point": "/sys/fs/fuse/connections",
- "mount_src": "fusectl",
- "opt_fields": [
- "master:renumbered/28"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "pstore",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/pstore",
- "mount_src": "pstore",
- "opt_fields": [
- "master:renumbered/29"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "debugfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/sys/kernel/debug",
- "mount_src": "debugfs",
- "opt_fields": [
- "master:renumbered/30"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "securityfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/kernel/security",
- "mount_src": "securityfs",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/tmp",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/tmp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/tmp",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [],
- "root_dir": "/tmp/snap.0_snap.snapd-hacker-toolbelt.busybox_XXXXXX/tmp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/usr/src",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/usr/src"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/var/lib",
- "mount_src": "none",
- "opt_fields": [],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/apparmor",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/apparmor"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/classic",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/classic"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/cloud",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/cloud"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/console-conf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/console-conf"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/dbus",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/dbus"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/dhcp",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/dhcp"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/extrausers",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/extrausers"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/initramfs-tools",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/initramfs-tools"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/initscripts",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/initscripts"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/insserv",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/insserv"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/logrotate",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/logrotate"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/machines",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/machines"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/misc",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/misc"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/pam",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/pam"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/python",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/python"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/resolvconf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/resolvconf"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/lib/snapd",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/lib/snapd"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/lib/snapd/hostfs",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/lib/snapd/hostfs",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [],
- "root_dir": "/var/lib/snapd/hostfs"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/10"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/lock",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/11"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/snapd/ns",
- "mount_src": "tmpfs",
- "opt_fields": [],
- "root_dir": "/snapd/ns"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/12"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/core/NUMBER",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/snapd-hacker-toolbelt/NUMBER",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/13"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/ubuntu-core/NUMBER",
- "mount_src": "/dev/remapped-loop2",
- "opt_fields": [
- "master:renumbered/14"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/sudo",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/sudo"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/systemd",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/systemd"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/ubuntu-fan",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/ubuntu-fan"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/ucf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/ucf"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/update-rc.d",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/update-rc.d"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/urandom",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/urandom"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/vim",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/vim"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/waagent",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/waagent"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/log",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/log"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/snap",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/snap"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/tmp",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/tmp"
- }
-]
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.core.linode.i386.json snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.core.linode.i386.json
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.core.linode.i386.json 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.core.linode.i386.json 1970-01-01 00:00:00.000000000 +0000
@@ -1,808 +0,0 @@
-[
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devtmpfs",
- "mount_opts": "rw,nosuid,relatime",
- "mount_point": "/dev",
- "mount_src": "udev",
- "opt_fields": [
- "master:renumbered/1"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "hugetlbfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/hugepages",
- "mount_src": "hugetlbfs",
- "opt_fields": [
- "master:renumbered/2"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "mqueue",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/mqueue",
- "mount_src": "mqueue",
- "opt_fields": [
- "master:renumbered/3"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/ptmx",
- "mount_src": "devpts",
- "opt_fields": [],
- "root_dir": "/ptmx"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [
- "master:renumbered/4"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev",
- "mount_point": "/dev/shm",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/5"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/etc",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/etc"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/alternatives",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/etc/alternatives"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/home",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/home"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/lib/modules",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/lib/modules"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/media",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "shared:renumbered/7"
- ],
- "root_dir": "/media"
- },
- {
- "fs_type": "proc",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/proc",
- "mount_src": "proc",
- "opt_fields": [
- "master:renumbered/8"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "autofs",
- "mount_opts": "rw,relatime",
- "mount_point": "/proc/sys/fs/binfmt_misc",
- "mount_src": "systemd-1",
- "opt_fields": [
- "master:renumbered/9"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/root",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/root"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/10"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/run/lock",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/11"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/run/snapd/ns",
- "mount_src": "tmpfs",
- "opt_fields": [],
- "root_dir": "/snapd/ns"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/12"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/snap",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/snap"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/snapd-hacker-toolbelt/NUMBER",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/13"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/ubuntu-core/NUMBER",
- "mount_src": "/dev/remapped-loop2",
- "opt_fields": [
- "master:renumbered/14"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "sysfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys",
- "mount_src": "sysfs",
- "opt_fields": [
- "master:renumbered/15"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "ro,nosuid,nodev,noexec",
- "mount_point": "/sys/fs/cgroup",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/16"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/blkio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/17"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/cpu,cpuacct",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/18"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/cpuset",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/19"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/devices",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/20"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/freezer",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/21"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/hugetlb",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/22"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/memory",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/23"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/net_cls,net_prio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/24"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/perf_event",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/25"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/pids",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/26"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/systemd",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/27"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "fusectl",
- "mount_opts": "rw,relatime",
- "mount_point": "/sys/fs/fuse/connections",
- "mount_src": "fusectl",
- "opt_fields": [
- "master:renumbered/28"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "pstore",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/pstore",
- "mount_src": "pstore",
- "opt_fields": [
- "master:renumbered/29"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "debugfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/sys/kernel/debug",
- "mount_src": "debugfs",
- "opt_fields": [
- "master:renumbered/30"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "securityfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/kernel/security",
- "mount_src": "securityfs",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/tmp",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/tmp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/tmp",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [],
- "root_dir": "/tmp/snap.0_snap.snapd-hacker-toolbelt.busybox_XXXXXX/tmp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/usr/src",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/usr/src"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/var/lib",
- "mount_src": "none",
- "opt_fields": [],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/apparmor",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/apparmor"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/classic",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/classic"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/cloud",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/cloud"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/console-conf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/console-conf"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/dbus",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/dbus"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/dhcp",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/dhcp"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/extrausers",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/extrausers"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/initramfs-tools",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/initramfs-tools"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/initscripts",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/initscripts"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/insserv",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/insserv"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/logrotate",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/logrotate"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/machines",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/machines"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/misc",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/misc"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/pam",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/pam"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/python",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/python"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/resolvconf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/resolvconf"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/lib/snapd",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/lib/snapd"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/lib/snapd/hostfs",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/lib/snapd/hostfs",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [],
- "root_dir": "/var/lib/snapd/hostfs"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/10"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/lock",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/11"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/snapd/ns",
- "mount_src": "tmpfs",
- "opt_fields": [],
- "root_dir": "/snapd/ns"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/12"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/core/NUMBER",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/snapd-hacker-toolbelt/NUMBER",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/13"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/ubuntu-core/NUMBER",
- "mount_src": "/dev/remapped-loop2",
- "opt_fields": [
- "master:renumbered/14"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "fuse.lxcfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/lxcfs",
- "mount_src": "lxcfs",
- "opt_fields": [
- "master:renumbered/32"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/sudo",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/sudo"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/systemd",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/systemd"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/ubuntu-fan",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/ubuntu-fan"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/ucf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/ucf"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/update-rc.d",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/update-rc.d"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/urandom",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/urandom"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/vim",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/vim"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/waagent",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/waagent"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/log",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/log"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/snap",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/snap"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/tmp",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/tmp"
- }
-]
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.core.qemu.amd64.json snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.core.qemu.amd64.json
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.core.qemu.amd64.json 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.core.qemu.amd64.json 1970-01-01 00:00:00.000000000 +0000
@@ -1,808 +0,0 @@
-[
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devtmpfs",
- "mount_opts": "rw,nosuid,relatime",
- "mount_point": "/dev",
- "mount_src": "udev",
- "opt_fields": [
- "master:renumbered/1"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "hugetlbfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/hugepages",
- "mount_src": "hugetlbfs",
- "opt_fields": [
- "master:renumbered/2"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "mqueue",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/mqueue",
- "mount_src": "mqueue",
- "opt_fields": [
- "master:renumbered/3"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/ptmx",
- "mount_src": "devpts",
- "opt_fields": [],
- "root_dir": "/ptmx"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [
- "master:renumbered/4"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev",
- "mount_point": "/dev/shm",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/5"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/etc"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/alternatives",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/etc/alternatives"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/home",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/home"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/lib/modules",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/lib/modules"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/media",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "shared:renumbered/7"
- ],
- "root_dir": "/media"
- },
- {
- "fs_type": "proc",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/proc",
- "mount_src": "proc",
- "opt_fields": [
- "master:renumbered/8"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "autofs",
- "mount_opts": "rw,relatime",
- "mount_point": "/proc/sys/fs/binfmt_misc",
- "mount_src": "systemd-1",
- "opt_fields": [
- "master:renumbered/9"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/root",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/root"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/10"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/run/lock",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/11"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/run/snapd/ns",
- "mount_src": "tmpfs",
- "opt_fields": [],
- "root_dir": "/snapd/ns"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/12"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/snap"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/snapd-hacker-toolbelt/NUMBER",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/13"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/ubuntu-core/NUMBER",
- "mount_src": "/dev/remapped-loop2",
- "opt_fields": [
- "master:renumbered/14"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "sysfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys",
- "mount_src": "sysfs",
- "opt_fields": [
- "master:renumbered/15"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "ro,nosuid,nodev,noexec",
- "mount_point": "/sys/fs/cgroup",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/16"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/blkio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/17"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/cpu,cpuacct",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/18"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/cpuset",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/19"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/devices",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/20"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/freezer",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/21"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/hugetlb",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/22"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/memory",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/23"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/net_cls,net_prio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/24"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/perf_event",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/25"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/pids",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/26"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/systemd",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/27"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "fusectl",
- "mount_opts": "rw,relatime",
- "mount_point": "/sys/fs/fuse/connections",
- "mount_src": "fusectl",
- "opt_fields": [
- "master:renumbered/28"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "pstore",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/pstore",
- "mount_src": "pstore",
- "opt_fields": [
- "master:renumbered/29"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "debugfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/sys/kernel/debug",
- "mount_src": "debugfs",
- "opt_fields": [
- "master:renumbered/30"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "securityfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/kernel/security",
- "mount_src": "securityfs",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/tmp",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/tmp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/tmp",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [],
- "root_dir": "/tmp/snap.0_snap.snapd-hacker-toolbelt.busybox_XXXXXX/tmp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/usr/src",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/usr/src"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/var/lib",
- "mount_src": "none",
- "opt_fields": [],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/apparmor",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/apparmor"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/classic",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/classic"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/cloud",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/cloud"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/console-conf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/console-conf"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/dbus",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/dbus"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/dhcp",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/dhcp"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/extrausers",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/extrausers"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/initramfs-tools",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/initramfs-tools"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/initscripts",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/initscripts"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/insserv",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/insserv"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/logrotate",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/logrotate"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/lxd",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/lib/lxd"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/machines",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/machines"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/misc",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/misc"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/pam",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/pam"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/python",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/python"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/resolvconf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/resolvconf"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/lib/snapd"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [],
- "root_dir": "/var/lib/snapd/hostfs"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/10"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/lock",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/11"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/snapd/ns",
- "mount_src": "tmpfs",
- "opt_fields": [],
- "root_dir": "/snapd/ns"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/12"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/core/NUMBER",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/snapd-hacker-toolbelt/NUMBER",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/13"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/ubuntu-core/NUMBER",
- "mount_src": "/dev/remapped-loop2",
- "opt_fields": [
- "master:renumbered/14"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/sudo",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/sudo"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/systemd",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/systemd"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/ubuntu-fan",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/ubuntu-fan"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/ucf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/ucf"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/update-rc.d",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/update-rc.d"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/urandom",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/urandom"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/vim",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/vim"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/waagent",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/waagent"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/log",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/log"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/snap",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/snap"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/tmp",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/tmp"
- }
-]
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.core.qemu.i386.json snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.core.qemu.i386.json
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.core.qemu.i386.json 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.core.qemu.i386.json 1970-01-01 00:00:00.000000000 +0000
@@ -1,808 +0,0 @@
-[
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devtmpfs",
- "mount_opts": "rw,nosuid,relatime",
- "mount_point": "/dev",
- "mount_src": "udev",
- "opt_fields": [
- "master:renumbered/1"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "hugetlbfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/hugepages",
- "mount_src": "hugetlbfs",
- "opt_fields": [
- "master:renumbered/2"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "mqueue",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/mqueue",
- "mount_src": "mqueue",
- "opt_fields": [
- "master:renumbered/3"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/ptmx",
- "mount_src": "devpts",
- "opt_fields": [],
- "root_dir": "/ptmx"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [
- "master:renumbered/4"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev",
- "mount_point": "/dev/shm",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/5"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/etc"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/alternatives",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/etc/alternatives"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/home",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/home"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/lib/modules",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/lib/modules"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/media",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "shared:renumbered/7"
- ],
- "root_dir": "/media"
- },
- {
- "fs_type": "proc",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/proc",
- "mount_src": "proc",
- "opt_fields": [
- "master:renumbered/8"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "autofs",
- "mount_opts": "rw,relatime",
- "mount_point": "/proc/sys/fs/binfmt_misc",
- "mount_src": "systemd-1",
- "opt_fields": [
- "master:renumbered/9"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/root",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/root"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/10"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/run/lock",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/11"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/run/snapd/ns",
- "mount_src": "tmpfs",
- "opt_fields": [],
- "root_dir": "/snapd/ns"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/12"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/snap"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/snapd-hacker-toolbelt/NUMBER",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/13"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/ubuntu-core/NUMBER",
- "mount_src": "/dev/remapped-loop2",
- "opt_fields": [
- "master:renumbered/14"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "sysfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys",
- "mount_src": "sysfs",
- "opt_fields": [
- "master:renumbered/15"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "ro,nosuid,nodev,noexec",
- "mount_point": "/sys/fs/cgroup",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/16"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/blkio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/17"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/cpu,cpuacct",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/18"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/cpuset",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/19"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/devices",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/20"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/freezer",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/21"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/hugetlb",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/22"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/memory",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/23"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/net_cls,net_prio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/24"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/perf_event",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/25"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/pids",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/26"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/systemd",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/27"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "fusectl",
- "mount_opts": "rw,relatime",
- "mount_point": "/sys/fs/fuse/connections",
- "mount_src": "fusectl",
- "opt_fields": [
- "master:renumbered/28"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "pstore",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/pstore",
- "mount_src": "pstore",
- "opt_fields": [
- "master:renumbered/29"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "debugfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/sys/kernel/debug",
- "mount_src": "debugfs",
- "opt_fields": [
- "master:renumbered/30"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "securityfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/kernel/security",
- "mount_src": "securityfs",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/tmp",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/tmp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/tmp",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [],
- "root_dir": "/tmp/snap.0_snap.snapd-hacker-toolbelt.busybox_XXXXXX/tmp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/usr/src",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/usr/src"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/var/lib",
- "mount_src": "none",
- "opt_fields": [],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/apparmor",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/apparmor"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/classic",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/classic"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/cloud",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/cloud"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/console-conf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/console-conf"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/dbus",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/dbus"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/dhcp",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/dhcp"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/extrausers",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/extrausers"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/initramfs-tools",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/initramfs-tools"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/initscripts",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/initscripts"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/insserv",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/insserv"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/logrotate",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/logrotate"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/lxd",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/lib/lxd"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/machines",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/machines"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/misc",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/misc"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/pam",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/pam"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/python",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/python"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/resolvconf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/resolvconf"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/lib/snapd"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [],
- "root_dir": "/var/lib/snapd/hostfs"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/10"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/lock",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/11"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/snapd/ns",
- "mount_src": "tmpfs",
- "opt_fields": [],
- "root_dir": "/snapd/ns"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/12"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/core/NUMBER",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/snapd-hacker-toolbelt/NUMBER",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/13"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/ubuntu-core/NUMBER",
- "mount_src": "/dev/remapped-loop2",
- "opt_fields": [
- "master:renumbered/14"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/sudo",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/sudo"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/systemd",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/systemd"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/ubuntu-fan",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/ubuntu-fan"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/ucf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/ucf"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/update-rc.d",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/update-rc.d"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/urandom",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/urandom"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/vim",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/vim"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/waagent",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/waagent"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/log",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/log"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/snap",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/snap"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/tmp",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/tmp"
- }
-]
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.ubuntu-core.json snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.ubuntu-core.json
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.ubuntu-core.json 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.ubuntu-core.json 1970-01-01 00:00:00.000000000 +0000
@@ -1,1028 +0,0 @@
-[
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devtmpfs",
- "mount_opts": "rw,nosuid,relatime",
- "mount_point": "/dev",
- "mount_src": "udev",
- "opt_fields": [
- "master:renumbered/1"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "hugetlbfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/hugepages",
- "mount_src": "hugetlbfs",
- "opt_fields": [
- "master:renumbered/2"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "mqueue",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/mqueue",
- "mount_src": "mqueue",
- "opt_fields": [
- "master:renumbered/3"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/ptmx",
- "mount_src": "devpts",
- "opt_fields": [],
- "root_dir": "/ptmx"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [
- "master:renumbered/4"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev",
- "mount_point": "/dev/shm",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/5"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/etc",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/etc"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/alternatives",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/etc/alternatives"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/home",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/home"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/lib/modules",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/lib/modules"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/media",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "shared:renumbered/7"
- ],
- "root_dir": "/media"
- },
- {
- "fs_type": "proc",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/proc",
- "mount_src": "proc",
- "opt_fields": [
- "master:renumbered/8"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "autofs",
- "mount_opts": "rw,relatime",
- "mount_point": "/proc/sys/fs/binfmt_misc",
- "mount_src": "systemd-1",
- "opt_fields": [
- "master:renumbered/9"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/root",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/root"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/10"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/run/lock",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/11"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/run/snapd/ns",
- "mount_src": "tmpfs",
- "opt_fields": [],
- "root_dir": "/snapd/ns"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/12"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/snap",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/snap"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/snapd-hacker-toolbelt/NUMBER",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/13"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/ubuntu-core/NUMBER",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "sysfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys",
- "mount_src": "sysfs",
- "opt_fields": [
- "master:renumbered/14"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "ro,nosuid,nodev,noexec",
- "mount_point": "/sys/fs/cgroup",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/15"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/blkio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/16"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/cpu,cpuacct",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/17"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/cpuset",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/18"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/devices",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/19"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/freezer",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/20"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/hugetlb",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/21"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/memory",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/22"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/net_cls,net_prio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/23"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/perf_event",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/24"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/pids",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/25"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/systemd",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/26"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "fusectl",
- "mount_opts": "rw,relatime",
- "mount_point": "/sys/fs/fuse/connections",
- "mount_src": "fusectl",
- "opt_fields": [
- "master:renumbered/27"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "pstore",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/pstore",
- "mount_src": "pstore",
- "opt_fields": [
- "master:renumbered/28"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "debugfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/sys/kernel/debug",
- "mount_src": "debugfs",
- "opt_fields": [
- "master:renumbered/29"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "securityfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/kernel/security",
- "mount_src": "securityfs",
- "opt_fields": [
- "master:renumbered/30"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/tmp",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/tmp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/tmp",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [],
- "root_dir": "/tmp/snap.0_snap.snapd-hacker-toolbelt.busybox_XXXXXX/tmp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/usr/src",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/usr/src"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/var/lib",
- "mount_src": "none",
- "opt_fields": [],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/apparmor",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/apparmor"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/classic",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/classic"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/cloud",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/cloud"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/console-conf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/console-conf"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/dbus",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/dbus"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/dhcp",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/dhcp"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/extrausers",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/extrausers"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/initramfs-tools",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/initramfs-tools"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/initscripts",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/initscripts"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/insserv",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/insserv"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/logrotate",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/logrotate"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/machines",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/machines"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/misc",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/misc"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/pam",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/pam"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/python",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/python"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/resolvconf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/resolvconf"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/lib/snapd",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/lib/snapd"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/lib/snapd/hostfs",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/lib/snapd/hostfs",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [],
- "root_dir": "/var/lib/snapd/hostfs"
- },
- {
- "fs_type": "devtmpfs",
- "mount_opts": "rw,nosuid,relatime",
- "mount_point": "/var/lib/snapd/hostfs/dev",
- "mount_src": "udev",
- "opt_fields": [
- "master:renumbered/1"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "hugetlbfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/dev/hugepages",
- "mount_src": "hugetlbfs",
- "opt_fields": [
- "master:renumbered/2"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "mqueue",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/dev/mqueue",
- "mount_src": "mqueue",
- "opt_fields": [
- "master:renumbered/3"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [
- "master:renumbered/4"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev",
- "mount_point": "/var/lib/snapd/hostfs/dev/shm",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/5"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "proc",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/proc",
- "mount_src": "proc",
- "opt_fields": [
- "master:renumbered/8"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "autofs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/proc/sys/fs/binfmt_misc",
- "mount_src": "systemd-1",
- "opt_fields": [
- "master:renumbered/9"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/10"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/lock",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/11"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/snapd/ns",
- "mount_src": "tmpfs",
- "opt_fields": [],
- "root_dir": "/snapd/ns"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/12"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/snapd-hacker-toolbelt/NUMBER",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/13"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/ubuntu-core/NUMBER",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "sysfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys",
- "mount_src": "sysfs",
- "opt_fields": [
- "master:renumbered/14"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "ro,nosuid,nodev,noexec",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/15"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/blkio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/16"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/cpu,cpuacct",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/17"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/cpuset",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/18"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/devices",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/19"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/freezer",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/20"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/hugetlb",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/21"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/memory",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/22"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/net_cls,net_prio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/23"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/perf_event",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/24"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/pids",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/25"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/systemd",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/26"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "fusectl",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/fuse/connections",
- "mount_src": "fusectl",
- "opt_fields": [
- "master:renumbered/27"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "pstore",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/pstore",
- "mount_src": "pstore",
- "opt_fields": [
- "master:renumbered/28"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "debugfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/kernel/debug",
- "mount_src": "debugfs",
- "opt_fields": [
- "master:renumbered/29"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "securityfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/kernel/security",
- "mount_src": "securityfs",
- "opt_fields": [
- "master:renumbered/30"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/sudo",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/sudo"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/systemd",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/systemd"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/ubuntu-fan",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/ubuntu-fan"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/ucf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/ucf"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/update-rc.d",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/update-rc.d"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/urandom",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/urandom"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/vim",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/vim"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/waagent",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/waagent"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/log",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/log"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/snap",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/snap"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/tmp",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/tmp"
- }
-]
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.ubuntu-core.linode.amd64.json snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.ubuntu-core.linode.amd64.json
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.ubuntu-core.linode.amd64.json 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.ubuntu-core.linode.amd64.json 1970-01-01 00:00:00.000000000 +0000
@@ -1,788 +0,0 @@
-[
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devtmpfs",
- "mount_opts": "rw,nosuid,relatime",
- "mount_point": "/dev",
- "mount_src": "udev",
- "opt_fields": [
- "master:renumbered/1"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "hugetlbfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/hugepages",
- "mount_src": "hugetlbfs",
- "opt_fields": [
- "master:renumbered/2"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "mqueue",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/mqueue",
- "mount_src": "mqueue",
- "opt_fields": [
- "master:renumbered/3"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/ptmx",
- "mount_src": "devpts",
- "opt_fields": [],
- "root_dir": "/ptmx"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [
- "master:renumbered/4"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev",
- "mount_point": "/dev/shm",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/5"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/etc",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/etc"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/alternatives",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/etc/alternatives"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/home",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/home"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/lib/modules",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/lib/modules"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/media",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "shared:renumbered/7"
- ],
- "root_dir": "/media"
- },
- {
- "fs_type": "proc",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/proc",
- "mount_src": "proc",
- "opt_fields": [
- "master:renumbered/8"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "autofs",
- "mount_opts": "rw,relatime",
- "mount_point": "/proc/sys/fs/binfmt_misc",
- "mount_src": "systemd-1",
- "opt_fields": [
- "master:renumbered/9"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/root",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/root"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/10"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/run/lock",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/11"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/run/snapd/ns",
- "mount_src": "tmpfs",
- "opt_fields": [],
- "root_dir": "/snapd/ns"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/12"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/snap",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/snap"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/snapd-hacker-toolbelt/NUMBER",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/13"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/ubuntu-core/NUMBER",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "sysfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys",
- "mount_src": "sysfs",
- "opt_fields": [
- "master:renumbered/14"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "ro,nosuid,nodev,noexec",
- "mount_point": "/sys/fs/cgroup",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/15"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/blkio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/16"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/cpu,cpuacct",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/17"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/cpuset",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/18"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/devices",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/19"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/freezer",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/20"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/hugetlb",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/21"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/memory",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/22"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/net_cls,net_prio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/23"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/perf_event",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/24"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/pids",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/25"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/systemd",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/26"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "fusectl",
- "mount_opts": "rw,relatime",
- "mount_point": "/sys/fs/fuse/connections",
- "mount_src": "fusectl",
- "opt_fields": [
- "master:renumbered/27"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "pstore",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/pstore",
- "mount_src": "pstore",
- "opt_fields": [
- "master:renumbered/28"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "debugfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/sys/kernel/debug",
- "mount_src": "debugfs",
- "opt_fields": [
- "master:renumbered/29"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "securityfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/kernel/security",
- "mount_src": "securityfs",
- "opt_fields": [
- "master:renumbered/30"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/tmp",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/tmp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/tmp",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [],
- "root_dir": "/tmp/snap.0_snap.snapd-hacker-toolbelt.busybox_XXXXXX/tmp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/usr/src",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/usr/src"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/var/lib",
- "mount_src": "none",
- "opt_fields": [],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/apparmor",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/apparmor"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/classic",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/classic"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/cloud",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/cloud"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/console-conf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/console-conf"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/dbus",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/dbus"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/dhcp",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/dhcp"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/extrausers",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/extrausers"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/initramfs-tools",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/initramfs-tools"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/initscripts",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/initscripts"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/insserv",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/insserv"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/logrotate",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/logrotate"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/machines",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/machines"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/misc",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/misc"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/pam",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/pam"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/python",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/python"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/resolvconf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/resolvconf"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/lib/snapd",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/lib/snapd"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/lib/snapd/hostfs",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/lib/snapd/hostfs",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [],
- "root_dir": "/var/lib/snapd/hostfs"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/10"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/lock",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/11"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/snapd/ns",
- "mount_src": "tmpfs",
- "opt_fields": [],
- "root_dir": "/snapd/ns"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/12"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/snapd-hacker-toolbelt/NUMBER",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/13"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/ubuntu-core/NUMBER",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/sudo",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/sudo"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/systemd",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/systemd"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/ubuntu-fan",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/ubuntu-fan"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/ucf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/ucf"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/update-rc.d",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/update-rc.d"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/urandom",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/urandom"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/vim",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/vim"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/waagent",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/waagent"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/log",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/log"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/snap",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/snap"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/tmp",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/tmp"
- }
-]
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.ubuntu-core.linode.i386.json snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.ubuntu-core.linode.i386.json
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.ubuntu-core.linode.i386.json 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.ubuntu-core.linode.i386.json 1970-01-01 00:00:00.000000000 +0000
@@ -1,798 +0,0 @@
-[
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devtmpfs",
- "mount_opts": "rw,nosuid,relatime",
- "mount_point": "/dev",
- "mount_src": "udev",
- "opt_fields": [
- "master:renumbered/1"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "hugetlbfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/hugepages",
- "mount_src": "hugetlbfs",
- "opt_fields": [
- "master:renumbered/2"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "mqueue",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/mqueue",
- "mount_src": "mqueue",
- "opt_fields": [
- "master:renumbered/3"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/ptmx",
- "mount_src": "devpts",
- "opt_fields": [],
- "root_dir": "/ptmx"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [
- "master:renumbered/4"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev",
- "mount_point": "/dev/shm",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/5"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/etc",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/etc"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/alternatives",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/etc/alternatives"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/home",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/home"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/lib/modules",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/lib/modules"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/media",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "shared:renumbered/7"
- ],
- "root_dir": "/media"
- },
- {
- "fs_type": "proc",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/proc",
- "mount_src": "proc",
- "opt_fields": [
- "master:renumbered/8"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "autofs",
- "mount_opts": "rw,relatime",
- "mount_point": "/proc/sys/fs/binfmt_misc",
- "mount_src": "systemd-1",
- "opt_fields": [
- "master:renumbered/9"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/root",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/root"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/10"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/run/lock",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/11"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/run/snapd/ns",
- "mount_src": "tmpfs",
- "opt_fields": [],
- "root_dir": "/snapd/ns"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/12"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/snap",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/snap"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/snapd-hacker-toolbelt/NUMBER",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/13"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/ubuntu-core/NUMBER",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "sysfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys",
- "mount_src": "sysfs",
- "opt_fields": [
- "master:renumbered/14"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "ro,nosuid,nodev,noexec",
- "mount_point": "/sys/fs/cgroup",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/15"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/blkio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/16"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/cpu,cpuacct",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/17"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/cpuset",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/18"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/devices",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/19"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/freezer",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/20"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/hugetlb",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/21"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/memory",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/22"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/net_cls,net_prio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/23"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/perf_event",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/24"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/pids",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/25"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/systemd",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/26"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "fusectl",
- "mount_opts": "rw,relatime",
- "mount_point": "/sys/fs/fuse/connections",
- "mount_src": "fusectl",
- "opt_fields": [
- "master:renumbered/27"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "pstore",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/pstore",
- "mount_src": "pstore",
- "opt_fields": [
- "master:renumbered/28"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "debugfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/sys/kernel/debug",
- "mount_src": "debugfs",
- "opt_fields": [
- "master:renumbered/29"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "securityfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/kernel/security",
- "mount_src": "securityfs",
- "opt_fields": [
- "master:renumbered/30"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/tmp",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/tmp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/tmp",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [],
- "root_dir": "/tmp/snap.0_snap.snapd-hacker-toolbelt.busybox_XXXXXX/tmp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/usr/src",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/usr/src"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/var/lib",
- "mount_src": "none",
- "opt_fields": [],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/apparmor",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/apparmor"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/classic",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/classic"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/cloud",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/cloud"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/console-conf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/console-conf"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/dbus",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/dbus"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/dhcp",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/dhcp"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/extrausers",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/extrausers"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/initramfs-tools",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/initramfs-tools"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/initscripts",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/initscripts"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/insserv",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/insserv"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/logrotate",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/logrotate"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/machines",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/machines"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/misc",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/misc"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/pam",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/pam"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/python",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/python"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/resolvconf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/resolvconf"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/lib/snapd",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/lib/snapd"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/lib/snapd/hostfs",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/lib/snapd/hostfs",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [],
- "root_dir": "/var/lib/snapd/hostfs"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/10"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/lock",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/11"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/snapd/ns",
- "mount_src": "tmpfs",
- "opt_fields": [],
- "root_dir": "/snapd/ns"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/12"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/snapd-hacker-toolbelt/NUMBER",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/13"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/ubuntu-core/NUMBER",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "fuse.lxcfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/lxcfs",
- "mount_src": "lxcfs",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/sudo",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/sudo"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/systemd",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/systemd"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/ubuntu-fan",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/ubuntu-fan"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/ucf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/ucf"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/update-rc.d",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/update-rc.d"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/urandom",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/urandom"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/vim",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/vim"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/waagent",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/waagent"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/log",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/log"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/snap",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/snap"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,noatime",
- "mount_point": "/var/tmp",
- "mount_src": "/dev/BLOCK",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/tmp"
- }
-]
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.ubuntu-core.qemu.amd64.json snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.ubuntu-core.qemu.amd64.json
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.ubuntu-core.qemu.amd64.json 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.ubuntu-core.qemu.amd64.json 1970-01-01 00:00:00.000000000 +0000
@@ -1,798 +0,0 @@
-[
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devtmpfs",
- "mount_opts": "rw,nosuid,relatime",
- "mount_point": "/dev",
- "mount_src": "udev",
- "opt_fields": [
- "master:renumbered/1"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "hugetlbfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/hugepages",
- "mount_src": "hugetlbfs",
- "opt_fields": [
- "master:renumbered/2"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "mqueue",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/mqueue",
- "mount_src": "mqueue",
- "opt_fields": [
- "master:renumbered/3"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/ptmx",
- "mount_src": "devpts",
- "opt_fields": [],
- "root_dir": "/ptmx"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [
- "master:renumbered/4"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev",
- "mount_point": "/dev/shm",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/5"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/etc"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/alternatives",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/etc/alternatives"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/home",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/home"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/lib/modules",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/lib/modules"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/media",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "shared:renumbered/7"
- ],
- "root_dir": "/media"
- },
- {
- "fs_type": "proc",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/proc",
- "mount_src": "proc",
- "opt_fields": [
- "master:renumbered/8"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "autofs",
- "mount_opts": "rw,relatime",
- "mount_point": "/proc/sys/fs/binfmt_misc",
- "mount_src": "systemd-1",
- "opt_fields": [
- "master:renumbered/9"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/root",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/root"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/10"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/run/lock",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/11"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/run/snapd/ns",
- "mount_src": "tmpfs",
- "opt_fields": [],
- "root_dir": "/snapd/ns"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/12"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/snap"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/snapd-hacker-toolbelt/NUMBER",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/13"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/ubuntu-core/NUMBER",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "sysfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys",
- "mount_src": "sysfs",
- "opt_fields": [
- "master:renumbered/14"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "ro,nosuid,nodev,noexec",
- "mount_point": "/sys/fs/cgroup",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/15"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/blkio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/16"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/cpu,cpuacct",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/17"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/cpuset",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/18"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/devices",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/19"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/freezer",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/20"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/hugetlb",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/21"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/memory",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/22"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/net_cls,net_prio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/23"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/perf_event",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/24"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/pids",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/25"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/systemd",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/26"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "fusectl",
- "mount_opts": "rw,relatime",
- "mount_point": "/sys/fs/fuse/connections",
- "mount_src": "fusectl",
- "opt_fields": [
- "master:renumbered/27"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "pstore",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/pstore",
- "mount_src": "pstore",
- "opt_fields": [
- "master:renumbered/28"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "debugfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/sys/kernel/debug",
- "mount_src": "debugfs",
- "opt_fields": [
- "master:renumbered/29"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "securityfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/kernel/security",
- "mount_src": "securityfs",
- "opt_fields": [
- "master:renumbered/30"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/tmp",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/tmp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/tmp",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [],
- "root_dir": "/tmp/snap.0_snap.snapd-hacker-toolbelt.busybox_XXXXXX/tmp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/usr/src",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/usr/src"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/var/lib",
- "mount_src": "none",
- "opt_fields": [],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/apparmor",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/apparmor"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/classic",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/classic"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/cloud",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/cloud"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/console-conf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/console-conf"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/dbus",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/dbus"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/dhcp",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/dhcp"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/extrausers",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/extrausers"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/initramfs-tools",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/initramfs-tools"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/initscripts",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/initscripts"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/insserv",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/insserv"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/logrotate",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/logrotate"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/lxd",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/lib/lxd"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/machines",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/machines"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/misc",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/misc"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/pam",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/pam"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/python",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/python"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/resolvconf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/resolvconf"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/lib/snapd"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [],
- "root_dir": "/var/lib/snapd/hostfs"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/10"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/lock",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/11"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/snapd/ns",
- "mount_src": "tmpfs",
- "opt_fields": [],
- "root_dir": "/snapd/ns"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/12"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/snapd-hacker-toolbelt/NUMBER",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/13"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/ubuntu-core/NUMBER",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/sudo",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/sudo"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/systemd",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/systemd"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/ubuntu-fan",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/ubuntu-fan"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/ucf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/ucf"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/update-rc.d",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/update-rc.d"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/urandom",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/urandom"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/vim",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/vim"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/waagent",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/waagent"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/log",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/log"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/snap",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/snap"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/tmp",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/tmp"
- }
-]
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.ubuntu-core.qemu.i386.json snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.ubuntu-core.qemu.i386.json
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.ubuntu-core.qemu.i386.json 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.classic.ubuntu-core.qemu.i386.json 1970-01-01 00:00:00.000000000 +0000
@@ -1,798 +0,0 @@
-[
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devtmpfs",
- "mount_opts": "rw,nosuid,relatime",
- "mount_point": "/dev",
- "mount_src": "udev",
- "opt_fields": [
- "master:renumbered/1"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "hugetlbfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/hugepages",
- "mount_src": "hugetlbfs",
- "opt_fields": [
- "master:renumbered/2"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "mqueue",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/mqueue",
- "mount_src": "mqueue",
- "opt_fields": [
- "master:renumbered/3"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/ptmx",
- "mount_src": "devpts",
- "opt_fields": [],
- "root_dir": "/ptmx"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [
- "master:renumbered/4"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev",
- "mount_point": "/dev/shm",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/5"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/etc"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/alternatives",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/etc/alternatives"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/home",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/home"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/lib/modules",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/lib/modules"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/media",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "shared:renumbered/7"
- ],
- "root_dir": "/media"
- },
- {
- "fs_type": "proc",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/proc",
- "mount_src": "proc",
- "opt_fields": [
- "master:renumbered/8"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "autofs",
- "mount_opts": "rw,relatime",
- "mount_point": "/proc/sys/fs/binfmt_misc",
- "mount_src": "systemd-1",
- "opt_fields": [
- "master:renumbered/9"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/root",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/root"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/10"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/run/lock",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/11"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/run/snapd/ns",
- "mount_src": "tmpfs",
- "opt_fields": [],
- "root_dir": "/snapd/ns"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/12"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/snap"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/snapd-hacker-toolbelt/NUMBER",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/13"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/ubuntu-core/NUMBER",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "sysfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys",
- "mount_src": "sysfs",
- "opt_fields": [
- "master:renumbered/14"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "ro,nosuid,nodev,noexec",
- "mount_point": "/sys/fs/cgroup",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/15"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/blkio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/16"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/cpu,cpuacct",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/17"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/cpuset",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/18"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/devices",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/19"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/freezer",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/20"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/hugetlb",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/21"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/memory",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/22"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/net_cls,net_prio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/23"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/perf_event",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/24"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/pids",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/25"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/systemd",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/26"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "fusectl",
- "mount_opts": "rw,relatime",
- "mount_point": "/sys/fs/fuse/connections",
- "mount_src": "fusectl",
- "opt_fields": [
- "master:renumbered/27"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "pstore",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/pstore",
- "mount_src": "pstore",
- "opt_fields": [
- "master:renumbered/28"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "debugfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/sys/kernel/debug",
- "mount_src": "debugfs",
- "opt_fields": [
- "master:renumbered/29"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "securityfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/kernel/security",
- "mount_src": "securityfs",
- "opt_fields": [
- "master:renumbered/30"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/tmp",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/tmp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/tmp",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [],
- "root_dir": "/tmp/snap.0_snap.snapd-hacker-toolbelt.busybox_XXXXXX/tmp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/usr/src",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/usr/src"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/var/lib",
- "mount_src": "none",
- "opt_fields": [],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/apparmor",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/apparmor"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/classic",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/classic"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/cloud",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/cloud"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/console-conf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/console-conf"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/dbus",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/dbus"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/dhcp",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/dhcp"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/extrausers",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/extrausers"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/initramfs-tools",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/initramfs-tools"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/initscripts",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/initscripts"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/insserv",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/insserv"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/logrotate",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/logrotate"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/lxd",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/lib/lxd"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/machines",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/machines"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/misc",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/misc"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/pam",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/pam"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/python",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/python"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/resolvconf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/resolvconf"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/lib/snapd"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [],
- "root_dir": "/var/lib/snapd/hostfs"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/10"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/lock",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/11"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/snapd/ns",
- "mount_src": "tmpfs",
- "opt_fields": [],
- "root_dir": "/snapd/ns"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/12"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/snapd-hacker-toolbelt/NUMBER",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/13"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/ubuntu-core/NUMBER",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/sudo",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/sudo"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/systemd",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/systemd"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/ubuntu-fan",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/ubuntu-fan"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/ucf",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/ucf"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/update-rc.d",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/update-rc.d"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/urandom",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/urandom"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/vim",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/vim"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/waagent",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/var/lib/waagent"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/log",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/log"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/snap",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/snap"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/tmp",
- "mount_src": "/dev/BLOCK1",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/var/tmp"
- }
-]
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.core.json snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.core.json
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.core.json 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.core.json 1970-01-01 00:00:00.000000000 +0000
@@ -1,2050 +0,0 @@
-[
- {
- "fs_type": "squashfs",
- "mount_opts": "ro,relatime",
- "mount_point": "/",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "vfat",
- "mount_opts": "rw,relatime",
- "mount_point": "/boot/efi",
- "mount_src": "/dev/BLOCK2",
- "opt_fields": [
- "master:renumbered/1"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "vfat",
- "mount_opts": "rw,relatime",
- "mount_point": "/boot/grub",
- "mount_src": "/dev/BLOCK2",
- "opt_fields": [
- "master:renumbered/1"
- ],
- "root_dir": "/EFI/ubuntu"
- },
- {
- "fs_type": "devtmpfs",
- "mount_opts": "rw,nosuid,relatime",
- "mount_point": "/dev",
- "mount_src": "udev",
- "opt_fields": [
- "master:renumbered/2"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "hugetlbfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/hugepages",
- "mount_src": "hugetlbfs",
- "opt_fields": [
- "master:renumbered/3"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "mqueue",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/mqueue",
- "mount_src": "mqueue",
- "opt_fields": [
- "master:renumbered/4"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/ptmx",
- "mount_src": "devpts",
- "opt_fields": [],
- "root_dir": "/ptmx"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [
- "master:renumbered/5"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev",
- "mount_point": "/dev/shm",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "ro,relatime",
- "mount_point": "/etc/alternatives",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/etc/alternatives"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/apparmor.d/cache",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/7"
- ],
- "root_dir": "/system-data/etc/apparmor.d/cache"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/cloud",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/8"
- ],
- "root_dir": "/system-data/etc/cloud"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/dbus-1/system.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/9"
- ],
- "root_dir": "/system-data/etc/dbus-1/system.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/default/keyboard",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/10"
- ],
- "root_dir": "/system-data/etc/default/keyboard"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/etc/fstab",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/11"
- ],
- "root_dir": "/image.fstab"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/hosts",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/12"
- ],
- "root_dir": "/system-data/etc/hosts"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/machine-id",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/13"
- ],
- "root_dir": "/system-data/etc/machine-id"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/modprobe.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/14"
- ],
- "root_dir": "/system-data/etc/modprobe.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/modules-load.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/15"
- ],
- "root_dir": "/system-data/etc/modules-load.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/netplan",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/16"
- ],
- "root_dir": "/system-data/etc/netplan"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/network/if-up.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/17"
- ],
- "root_dir": "/system-data/etc/network/if-up.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/network/interfaces.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/18"
- ],
- "root_dir": "/system-data/etc/network/interfaces.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/ppp",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/19"
- ],
- "root_dir": "/system-data/etc/ppp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/ssh",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/20"
- ],
- "root_dir": "/system-data/etc/ssh"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/sudoers.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/21"
- ],
- "root_dir": "/system-data/etc/sudoers.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/sysctl.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/22"
- ],
- "root_dir": "/system-data/etc/sysctl.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/systemd/network",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/23"
- ],
- "root_dir": "/system-data/etc/systemd/network"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/systemd/system",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/24"
- ],
- "root_dir": "/system-data/etc/systemd/system"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/systemd/system",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/25"
- ],
- "root_dir": "/system-data/etc/systemd/system"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/systemd/system.conf.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/26"
- ],
- "root_dir": "/system-data/etc/systemd/system.conf.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/systemd/user.conf.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/27"
- ],
- "root_dir": "/system-data/etc/systemd/user.conf.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/udev/rules.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/28"
- ],
- "root_dir": "/system-data/etc/udev/rules.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/ufw",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/29"
- ],
- "root_dir": "/system-data/etc/ufw"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/writable",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/30"
- ],
- "root_dir": "/system-data/etc/writable"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/home",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/user-data"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/lib/firmware",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/32"
- ],
- "root_dir": "/firmware"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/lib/firmware",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/33"
- ],
- "root_dir": "/firmware"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/lib/modules",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/34"
- ],
- "root_dir": "/modules"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/lib/modules",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/35"
- ],
- "root_dir": "/modules"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "ro,relatime",
- "mount_point": "/media",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "shared:renumbered/36"
- ],
- "root_dir": "/media"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/mnt",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/37"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "proc",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/proc",
- "mount_src": "proc",
- "opt_fields": [
- "master:renumbered/38"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "binfmt_misc",
- "mount_opts": "rw,relatime",
- "mount_point": "/proc/sys/fs/binfmt_misc",
- "mount_src": "binfmt_misc",
- "opt_fields": [
- "master:renumbered/40"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "autofs",
- "mount_opts": "rw,relatime",
- "mount_point": "/proc/sys/fs/binfmt_misc",
- "mount_src": "systemd-1",
- "opt_fields": [
- "master:renumbered/39"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/root",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/root"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/41"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/42"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/run/cgmanager/fs",
- "mount_src": "cgmfs",
- "opt_fields": [
- "master:renumbered/43"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/run/lock",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/44"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/run/snapd/ns",
- "mount_src": "tmpfs",
- "opt_fields": [],
- "root_dir": "/snapd/ns"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/45"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/46"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/snap"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/snap"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/pc-kernel/NUMBER",
- "mount_src": "/dev/remapped-loop2",
- "opt_fields": [
- "master:renumbered/47"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/pc-kernel/NUMBER",
- "mount_src": "/dev/remapped-loop2",
- "opt_fields": [
- "master:renumbered/47"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/pc/NUMBER",
- "mount_src": "/dev/remapped-loop3",
- "opt_fields": [
- "master:renumbered/48"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/pc/NUMBER",
- "mount_src": "/dev/remapped-loop3",
- "opt_fields": [
- "master:renumbered/48"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/snapd-hacker-toolbelt/x1",
- "mount_src": "/dev/remapped-loop4",
- "opt_fields": [
- "master:renumbered/49"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/snapd-hacker-toolbelt/x1",
- "mount_src": "/dev/remapped-loop4",
- "opt_fields": [
- "master:renumbered/49"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "sysfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys",
- "mount_src": "sysfs",
- "opt_fields": [
- "master:renumbered/50"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw",
- "mount_point": "/sys/fs/cgroup",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/51"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/blkio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/52"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/cpu,cpuacct",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/53"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/cpuset",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/54"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/devices",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/55"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/freezer",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/56"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/hugetlb",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/57"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/memory",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/58"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/net_cls,net_prio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/59"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/perf_event",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/60"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/pids",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/61"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/systemd",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/62"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "fusectl",
- "mount_opts": "rw,relatime",
- "mount_point": "/sys/fs/fuse/connections",
- "mount_src": "fusectl",
- "opt_fields": [
- "master:renumbered/63"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "pstore",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/pstore",
- "mount_src": "pstore",
- "opt_fields": [
- "master:renumbered/64"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "debugfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/sys/kernel/debug",
- "mount_src": "debugfs",
- "opt_fields": [
- "master:renumbered/65"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "securityfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/kernel/security",
- "mount_src": "securityfs",
- "opt_fields": [
- "master:renumbered/66"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/tmp",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/67"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/tmp",
- "mount_src": "tmpfs",
- "opt_fields": [],
- "root_dir": "/snap.1001_snap.snapd-hacker-toolbelt.busybox_XXXXXX/tmp"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/tmp/snap.rootfs_XXXXXX",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/67"
- ],
- "root_dir": "/snap.rootfs_XXXXXX"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/usr/lib/snapd/snap-confine",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/user-data/zyga/snap-confine"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/cache/apparmor",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/cache/apparmor"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/apparmor",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/apparmor"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/cloud",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/cloud"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/console-conf",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/console-conf"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/dbus",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/dbus"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/dhcp",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/dhcp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/extrausers",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/extrausers"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/initramfs-tools",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/initramfs-tools"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/logrotate",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/logrotate"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/misc",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/misc"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/snapd"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [],
- "root_dir": "/system-data/var/lib/snapd/hostfs"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "ro,relatime",
- "mount_point": "/var/lib/snapd/hostfs",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "vfat",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/boot/efi",
- "mount_src": "/dev/BLOCK2",
- "opt_fields": [
- "master:renumbered/1"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "vfat",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/boot/grub",
- "mount_src": "/dev/BLOCK2",
- "opt_fields": [
- "master:renumbered/1"
- ],
- "root_dir": "/EFI/ubuntu"
- },
- {
- "fs_type": "devtmpfs",
- "mount_opts": "rw,nosuid,relatime",
- "mount_point": "/var/lib/snapd/hostfs/dev",
- "mount_src": "udev",
- "opt_fields": [
- "master:renumbered/2"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "hugetlbfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/dev/hugepages",
- "mount_src": "hugetlbfs",
- "opt_fields": [
- "master:renumbered/3"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "mqueue",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/dev/mqueue",
- "mount_src": "mqueue",
- "opt_fields": [
- "master:renumbered/4"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [
- "master:renumbered/5"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev",
- "mount_point": "/var/lib/snapd/hostfs/dev/shm",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/apparmor.d/cache",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/7"
- ],
- "root_dir": "/system-data/etc/apparmor.d/cache"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/cloud",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/8"
- ],
- "root_dir": "/system-data/etc/cloud"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/dbus-1/system.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/9"
- ],
- "root_dir": "/system-data/etc/dbus-1/system.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/default/keyboard",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/10"
- ],
- "root_dir": "/system-data/etc/default/keyboard"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/fstab",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/11"
- ],
- "root_dir": "/image.fstab"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/hosts",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/12"
- ],
- "root_dir": "/system-data/etc/hosts"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/machine-id",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/13"
- ],
- "root_dir": "/system-data/etc/machine-id"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/modprobe.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/14"
- ],
- "root_dir": "/system-data/etc/modprobe.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/modules-load.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/15"
- ],
- "root_dir": "/system-data/etc/modules-load.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/netplan",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/16"
- ],
- "root_dir": "/system-data/etc/netplan"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/network/if-up.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/17"
- ],
- "root_dir": "/system-data/etc/network/if-up.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/network/interfaces.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/18"
- ],
- "root_dir": "/system-data/etc/network/interfaces.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/ppp",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/19"
- ],
- "root_dir": "/system-data/etc/ppp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/ssh",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/20"
- ],
- "root_dir": "/system-data/etc/ssh"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/sudoers.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/21"
- ],
- "root_dir": "/system-data/etc/sudoers.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/sysctl.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/22"
- ],
- "root_dir": "/system-data/etc/sysctl.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/systemd/network",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/23"
- ],
- "root_dir": "/system-data/etc/systemd/network"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/systemd/system",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/24"
- ],
- "root_dir": "/system-data/etc/systemd/system"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/systemd/system",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/25"
- ],
- "root_dir": "/system-data/etc/systemd/system"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/systemd/system.conf.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/26"
- ],
- "root_dir": "/system-data/etc/systemd/system.conf.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/systemd/user.conf.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/27"
- ],
- "root_dir": "/system-data/etc/systemd/user.conf.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/udev/rules.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/28"
- ],
- "root_dir": "/system-data/etc/udev/rules.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/ufw",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/29"
- ],
- "root_dir": "/system-data/etc/ufw"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/writable",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/30"
- ],
- "root_dir": "/system-data/etc/writable"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/home",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/user-data"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/lib/firmware",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/32"
- ],
- "root_dir": "/firmware"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/lib/firmware",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/33"
- ],
- "root_dir": "/firmware"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/lib/modules",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/34"
- ],
- "root_dir": "/modules"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/lib/modules",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/35"
- ],
- "root_dir": "/modules"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/mnt",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/37"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "proc",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/proc",
- "mount_src": "proc",
- "opt_fields": [
- "master:renumbered/38"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "binfmt_misc",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/proc/sys/fs/binfmt_misc",
- "mount_src": "binfmt_misc",
- "opt_fields": [
- "master:renumbered/40"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "autofs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/proc/sys/fs/binfmt_misc",
- "mount_src": "systemd-1",
- "opt_fields": [
- "master:renumbered/39"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/root",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/root"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/41"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/42"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/cgmanager/fs",
- "mount_src": "cgmfs",
- "opt_fields": [
- "master:renumbered/43"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/lock",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/44"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/snapd/ns",
- "mount_src": "tmpfs",
- "opt_fields": [],
- "root_dir": "/snapd/ns"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/45"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/46"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/snap"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/core/NUMBER",
- "mount_src": "/dev/remapped-loop5",
- "opt_fields": [
- "master:renumbered/68"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/core/NUMBER",
- "mount_src": "/dev/remapped-loop6",
- "opt_fields": [
- "master:renumbered/69"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/pc-kernel/NUMBER",
- "mount_src": "/dev/remapped-loop2",
- "opt_fields": [
- "master:renumbered/47"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/pc/NUMBER",
- "mount_src": "/dev/remapped-loop3",
- "opt_fields": [
- "master:renumbered/48"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/snapd-hacker-toolbelt/x1",
- "mount_src": "/dev/remapped-loop4",
- "opt_fields": [
- "master:renumbered/49"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "sysfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys",
- "mount_src": "sysfs",
- "opt_fields": [
- "master:renumbered/50"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/51"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/blkio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/52"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/cpu,cpuacct",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/53"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/cpuset",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/54"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/devices",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/55"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/freezer",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/56"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/hugetlb",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/57"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/memory",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/58"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/net_cls,net_prio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/59"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/perf_event",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/60"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/pids",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/61"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/cgroup/systemd",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/62"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "fusectl",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/fuse/connections",
- "mount_src": "fusectl",
- "opt_fields": [
- "master:renumbered/63"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "pstore",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/fs/pstore",
- "mount_src": "pstore",
- "opt_fields": [
- "master:renumbered/64"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "debugfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/kernel/debug",
- "mount_src": "debugfs",
- "opt_fields": [
- "master:renumbered/65"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "securityfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/sys/kernel/security",
- "mount_src": "securityfs",
- "opt_fields": [
- "master:renumbered/66"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/tmp",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/67"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/tmp/snap.rootfs_XXXXXX",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/67"
- ],
- "root_dir": "/snap.rootfs_XXXXXX"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/usr/lib/snapd/snap-confine",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/user-data/zyga/snap-confine"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/cache/apparmor",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/cache/apparmor"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/apparmor",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/apparmor"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/cloud",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/cloud"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/console-conf",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/console-conf"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/dbus",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/dbus"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/dhcp",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/dhcp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/extrausers",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/extrausers"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/initramfs-tools",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/initramfs-tools"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/logrotate",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/logrotate"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/misc",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/misc"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/snapd",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/snapd"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/sudo",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/70"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/systemd/random-seed",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/systemd/random-seed"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/systemd/rfkill",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/systemd/rfkill"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/waagent",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/waagent"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/log",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/log"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/snap",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/snap"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/tmp",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/tmp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/writable",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/writable/system-data/snap/core/NUMBER",
- "mount_src": "/dev/remapped-loop5",
- "opt_fields": [
- "master:renumbered/68"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/writable/system-data/snap/core/NUMBER",
- "mount_src": "/dev/remapped-loop6",
- "opt_fields": [
- "master:renumbered/69"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/writable/system-data/snap/pc-kernel/NUMBER",
- "mount_src": "/dev/remapped-loop2",
- "opt_fields": [
- "master:renumbered/47"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/writable/system-data/snap/pc/NUMBER",
- "mount_src": "/dev/remapped-loop3",
- "opt_fields": [
- "master:renumbered/48"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/writable/system-data/snap/snapd-hacker-toolbelt/x1",
- "mount_src": "/dev/remapped-loop4",
- "opt_fields": [
- "master:renumbered/49"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/sudo",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/70"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/systemd/random-seed",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/systemd/random-seed"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/systemd/rfkill",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/systemd/rfkill"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/waagent",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/waagent"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/log",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/log"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/snap",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/snap"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/tmp",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/tmp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/writable",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/writable/system-data/snap/core/NUMBER",
- "mount_src": "/dev/remapped-loop5",
- "opt_fields": [
- "master:renumbered/68"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/writable/system-data/snap/core/NUMBER",
- "mount_src": "/dev/remapped-loop6",
- "opt_fields": [
- "master:renumbered/69"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/writable/system-data/snap/pc-kernel/NUMBER",
- "mount_src": "/dev/remapped-loop2",
- "opt_fields": [
- "master:renumbered/47"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/writable/system-data/snap/pc/NUMBER",
- "mount_src": "/dev/remapped-loop3",
- "opt_fields": [
- "master:renumbered/48"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/writable/system-data/snap/snapd-hacker-toolbelt/x1",
- "mount_src": "/dev/remapped-loop4",
- "opt_fields": [
- "master:renumbered/49"
- ],
- "root_dir": "/"
- }
-]
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.core.linode.json snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.core.linode.json
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.core.linode.json 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/expected.core.linode.json 1970-01-01 00:00:00.000000000 +0000
@@ -1,1800 +0,0 @@
-[
- {
- "fs_type": "squashfs",
- "mount_opts": "ro,relatime",
- "mount_point": "/",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "vfat",
- "mount_opts": "rw,relatime",
- "mount_point": "/boot/efi",
- "mount_src": "/dev/BLOCK2",
- "opt_fields": [
- "master:renumbered/1"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "vfat",
- "mount_opts": "rw,relatime",
- "mount_point": "/boot/grub",
- "mount_src": "/dev/BLOCK2",
- "opt_fields": [
- "master:renumbered/1"
- ],
- "root_dir": "/EFI/ubuntu"
- },
- {
- "fs_type": "devtmpfs",
- "mount_opts": "rw,nosuid,relatime",
- "mount_point": "/dev",
- "mount_src": "udev",
- "opt_fields": [
- "master:renumbered/2"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "hugetlbfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/hugepages",
- "mount_src": "hugetlbfs",
- "opt_fields": [
- "master:renumbered/3"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "mqueue",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/mqueue",
- "mount_src": "mqueue",
- "opt_fields": [
- "master:renumbered/4"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/ptmx",
- "mount_src": "devpts",
- "opt_fields": [],
- "root_dir": "/ptmx"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [
- "master:renumbered/5"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "devpts",
- "mount_opts": "rw,relatime",
- "mount_point": "/dev/pts",
- "mount_src": "devpts",
- "opt_fields": [],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev",
- "mount_point": "/dev/shm",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/6"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "ro,relatime",
- "mount_point": "/etc/alternatives",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/etc/alternatives"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/apparmor.d/cache",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/7"
- ],
- "root_dir": "/system-data/etc/apparmor.d/cache"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/cloud",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/8"
- ],
- "root_dir": "/system-data/etc/cloud"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/dbus-1/system.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/9"
- ],
- "root_dir": "/system-data/etc/dbus-1/system.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/default/keyboard",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/10"
- ],
- "root_dir": "/system-data/etc/default/keyboard"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/etc/fstab",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/11"
- ],
- "root_dir": "/image.fstab"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/hosts",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/12"
- ],
- "root_dir": "/system-data/etc/hosts"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/machine-id",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/13"
- ],
- "root_dir": "/system-data/etc/machine-id"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/modprobe.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/14"
- ],
- "root_dir": "/system-data/etc/modprobe.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/modules-load.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/15"
- ],
- "root_dir": "/system-data/etc/modules-load.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/netplan",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/16"
- ],
- "root_dir": "/system-data/etc/netplan"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/network/if-up.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/17"
- ],
- "root_dir": "/system-data/etc/network/if-up.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/network/interfaces.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/18"
- ],
- "root_dir": "/system-data/etc/network/interfaces.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/ppp",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/19"
- ],
- "root_dir": "/system-data/etc/ppp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/ssh",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/20"
- ],
- "root_dir": "/system-data/etc/ssh"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/sudoers.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/21"
- ],
- "root_dir": "/system-data/etc/sudoers.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/sysctl.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/22"
- ],
- "root_dir": "/system-data/etc/sysctl.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/systemd/network",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/23"
- ],
- "root_dir": "/system-data/etc/systemd/network"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/systemd/system",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/24"
- ],
- "root_dir": "/system-data/etc/systemd/system"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/systemd/system",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/25"
- ],
- "root_dir": "/system-data/etc/systemd/system"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/systemd/system.conf.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/26"
- ],
- "root_dir": "/system-data/etc/systemd/system.conf.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/systemd/user.conf.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/27"
- ],
- "root_dir": "/system-data/etc/systemd/user.conf.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/udev/rules.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/28"
- ],
- "root_dir": "/system-data/etc/udev/rules.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/ufw",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/29"
- ],
- "root_dir": "/system-data/etc/ufw"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/etc/writable",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/30"
- ],
- "root_dir": "/system-data/etc/writable"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/home",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/user-data"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/lib/firmware",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/32"
- ],
- "root_dir": "/firmware"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/lib/firmware",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/33"
- ],
- "root_dir": "/firmware"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/lib/modules",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/34"
- ],
- "root_dir": "/modules"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/lib/modules",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/35"
- ],
- "root_dir": "/modules"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "ro,relatime",
- "mount_point": "/media",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "shared:renumbered/36"
- ],
- "root_dir": "/media"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/mnt",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/37"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "proc",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/proc",
- "mount_src": "proc",
- "opt_fields": [
- "master:renumbered/38"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "binfmt_misc",
- "mount_opts": "rw,relatime",
- "mount_point": "/proc/sys/fs/binfmt_misc",
- "mount_src": "binfmt_misc",
- "opt_fields": [
- "master:renumbered/40"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "autofs",
- "mount_opts": "rw,relatime",
- "mount_point": "/proc/sys/fs/binfmt_misc",
- "mount_src": "systemd-1",
- "opt_fields": [
- "master:renumbered/39"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/root",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/root"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/41"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/42"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/run/cgmanager/fs",
- "mount_src": "cgmfs",
- "opt_fields": [
- "master:renumbered/43"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/run/lock",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/44"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/run/snapd/ns",
- "mount_src": "tmpfs",
- "opt_fields": [],
- "root_dir": "/snapd/ns"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/45"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/46"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/snap"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/snap"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/pc-kernel/NUMBER",
- "mount_src": "/dev/remapped-loop2",
- "opt_fields": [
- "master:renumbered/47"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/pc-kernel/NUMBER",
- "mount_src": "/dev/remapped-loop2",
- "opt_fields": [
- "master:renumbered/47"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/pc/NUMBER",
- "mount_src": "/dev/remapped-loop3",
- "opt_fields": [
- "master:renumbered/48"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/pc/NUMBER",
- "mount_src": "/dev/remapped-loop3",
- "opt_fields": [
- "master:renumbered/48"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/snapd-hacker-toolbelt/x1",
- "mount_src": "/dev/remapped-loop4",
- "opt_fields": [
- "master:renumbered/49"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/snap/snapd-hacker-toolbelt/x1",
- "mount_src": "/dev/remapped-loop4",
- "opt_fields": [
- "master:renumbered/49"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "sysfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys",
- "mount_src": "sysfs",
- "opt_fields": [
- "master:renumbered/50"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw",
- "mount_point": "/sys/fs/cgroup",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/51"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/blkio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/52"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/cpu,cpuacct",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/53"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/cpuset",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/54"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/devices",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/55"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/freezer",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/56"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/hugetlb",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/57"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/memory",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/58"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/net_cls,net_prio",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/59"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/perf_event",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/60"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/pids",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/61"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "cgroup",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/cgroup/systemd",
- "mount_src": "cgroup",
- "opt_fields": [
- "master:renumbered/62"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "fusectl",
- "mount_opts": "rw,relatime",
- "mount_point": "/sys/fs/fuse/connections",
- "mount_src": "fusectl",
- "opt_fields": [
- "master:renumbered/63"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "pstore",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/fs/pstore",
- "mount_src": "pstore",
- "opt_fields": [
- "master:renumbered/64"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "debugfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/sys/kernel/debug",
- "mount_src": "debugfs",
- "opt_fields": [
- "master:renumbered/65"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "securityfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/sys/kernel/security",
- "mount_src": "securityfs",
- "opt_fields": [
- "master:renumbered/66"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/tmp",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/67"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/tmp",
- "mount_src": "tmpfs",
- "opt_fields": [],
- "root_dir": "/snap.1001_snap.snapd-hacker-toolbelt.busybox_XXXXXX/tmp"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/tmp/snap.rootfs_XXXXXX",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/67"
- ],
- "root_dir": "/snap.rootfs_XXXXXX"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/usr/lib/snapd/snap-confine",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/user-data/zyga/snap-confine"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/cache/apparmor",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/cache/apparmor"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/apparmor",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/apparmor"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/cloud",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/cloud"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/console-conf",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/console-conf"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/dbus",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/dbus"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/dhcp",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/dhcp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/extrausers",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/extrausers"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/initramfs-tools",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/initramfs-tools"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/logrotate",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/logrotate"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/misc",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/misc"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/snapd"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [],
- "root_dir": "/system-data/var/lib/snapd/hostfs"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "ro,relatime",
- "mount_point": "/var/lib/snapd/hostfs",
- "mount_src": "/dev/remapped-loop0",
- "opt_fields": [
- "master:renumbered/0"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "vfat",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/boot/efi",
- "mount_src": "/dev/BLOCK2",
- "opt_fields": [
- "master:renumbered/1"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "vfat",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/boot/grub",
- "mount_src": "/dev/BLOCK2",
- "opt_fields": [
- "master:renumbered/1"
- ],
- "root_dir": "/EFI/ubuntu"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/apparmor.d/cache",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/7"
- ],
- "root_dir": "/system-data/etc/apparmor.d/cache"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/cloud",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/8"
- ],
- "root_dir": "/system-data/etc/cloud"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/dbus-1/system.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/9"
- ],
- "root_dir": "/system-data/etc/dbus-1/system.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/default/keyboard",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/10"
- ],
- "root_dir": "/system-data/etc/default/keyboard"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/fstab",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/11"
- ],
- "root_dir": "/image.fstab"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/hosts",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/12"
- ],
- "root_dir": "/system-data/etc/hosts"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/machine-id",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/13"
- ],
- "root_dir": "/system-data/etc/machine-id"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/modprobe.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/14"
- ],
- "root_dir": "/system-data/etc/modprobe.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/modules-load.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/15"
- ],
- "root_dir": "/system-data/etc/modules-load.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/netplan",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/16"
- ],
- "root_dir": "/system-data/etc/netplan"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/network/if-up.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/17"
- ],
- "root_dir": "/system-data/etc/network/if-up.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/network/interfaces.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/18"
- ],
- "root_dir": "/system-data/etc/network/interfaces.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/ppp",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/19"
- ],
- "root_dir": "/system-data/etc/ppp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/ssh",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/20"
- ],
- "root_dir": "/system-data/etc/ssh"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/sudoers.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/21"
- ],
- "root_dir": "/system-data/etc/sudoers.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/sysctl.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/22"
- ],
- "root_dir": "/system-data/etc/sysctl.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/systemd/network",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/23"
- ],
- "root_dir": "/system-data/etc/systemd/network"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/systemd/system",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/24"
- ],
- "root_dir": "/system-data/etc/systemd/system"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/systemd/system",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/25"
- ],
- "root_dir": "/system-data/etc/systemd/system"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/systemd/system.conf.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/26"
- ],
- "root_dir": "/system-data/etc/systemd/system.conf.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/systemd/user.conf.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/27"
- ],
- "root_dir": "/system-data/etc/systemd/user.conf.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/udev/rules.d",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/28"
- ],
- "root_dir": "/system-data/etc/udev/rules.d"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/ufw",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/29"
- ],
- "root_dir": "/system-data/etc/ufw"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/etc/writable",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/30"
- ],
- "root_dir": "/system-data/etc/writable"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/home",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/user-data"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/lib/firmware",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/32"
- ],
- "root_dir": "/firmware"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/lib/firmware",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/33"
- ],
- "root_dir": "/firmware"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/lib/modules",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/34"
- ],
- "root_dir": "/modules"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/lib/modules",
- "mount_src": "/dev/remapped-loop1",
- "opt_fields": [
- "master:renumbered/35"
- ],
- "root_dir": "/modules"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/mnt",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/37"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/root",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/root"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/41"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/42"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/cgmanager/fs",
- "mount_src": "cgmfs",
- "opt_fields": [
- "master:renumbered/43"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/lock",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/44"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,noexec,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/snapd/ns",
- "mount_src": "tmpfs",
- "opt_fields": [],
- "root_dir": "/snapd/ns"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/45"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,nosuid,nodev,relatime",
- "mount_point": "/var/lib/snapd/hostfs/run/user/NUMBER",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/46"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/snap"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/core/NUMBER",
- "mount_src": "/dev/remapped-loop5",
- "opt_fields": [
- "master:renumbered/68"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/core/NUMBER",
- "mount_src": "/dev/remapped-loop6",
- "opt_fields": [
- "master:renumbered/69"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/pc-kernel/NUMBER",
- "mount_src": "/dev/remapped-loop2",
- "opt_fields": [
- "master:renumbered/47"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/pc/NUMBER",
- "mount_src": "/dev/remapped-loop3",
- "opt_fields": [
- "master:renumbered/48"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/snap/snapd-hacker-toolbelt/x1",
- "mount_src": "/dev/remapped-loop4",
- "opt_fields": [
- "master:renumbered/49"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/tmp",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/67"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/tmp/snap.rootfs_XXXXXX",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/67"
- ],
- "root_dir": "/snap.rootfs_XXXXXX"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/usr/lib/snapd/snap-confine",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/user-data/zyga/snap-confine"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/cache/apparmor",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/cache/apparmor"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/apparmor",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/apparmor"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/cloud",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/cloud"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/console-conf",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/console-conf"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/dbus",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/dbus"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/dhcp",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/dhcp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/extrausers",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/extrausers"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/initramfs-tools",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/initramfs-tools"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/logrotate",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/logrotate"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/misc",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/misc"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/snapd",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/snapd"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/sudo",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/70"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/systemd/random-seed",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/systemd/random-seed"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/systemd/rfkill",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/systemd/rfkill"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/lib/waagent",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/waagent"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/log",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/log"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/snap",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/snap"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/var/tmp",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/tmp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/writable",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/writable/system-data/snap/core/NUMBER",
- "mount_src": "/dev/remapped-loop5",
- "opt_fields": [
- "master:renumbered/68"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/writable/system-data/snap/core/NUMBER",
- "mount_src": "/dev/remapped-loop6",
- "opt_fields": [
- "master:renumbered/69"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/writable/system-data/snap/pc-kernel/NUMBER",
- "mount_src": "/dev/remapped-loop2",
- "opt_fields": [
- "master:renumbered/47"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/writable/system-data/snap/pc/NUMBER",
- "mount_src": "/dev/remapped-loop3",
- "opt_fields": [
- "master:renumbered/48"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/snapd/hostfs/writable/system-data/snap/snapd-hacker-toolbelt/x1",
- "mount_src": "/dev/remapped-loop4",
- "opt_fields": [
- "master:renumbered/49"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "tmpfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/sudo",
- "mount_src": "tmpfs",
- "opt_fields": [
- "master:renumbered/70"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/systemd/random-seed",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/systemd/random-seed"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/systemd/rfkill",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/systemd/rfkill"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/lib/waagent",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/lib/waagent"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/log",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/log"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/snap",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/snap"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/var/tmp",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/system-data/var/tmp"
- },
- {
- "fs_type": "ext4",
- "mount_opts": "rw,relatime",
- "mount_point": "/writable",
- "mount_src": "/dev/BLOCK3",
- "opt_fields": [
- "master:renumbered/31"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/writable/system-data/snap/core/NUMBER",
- "mount_src": "/dev/remapped-loop5",
- "opt_fields": [
- "master:renumbered/68"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/writable/system-data/snap/core/NUMBER",
- "mount_src": "/dev/remapped-loop6",
- "opt_fields": [
- "master:renumbered/69"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/writable/system-data/snap/pc-kernel/NUMBER",
- "mount_src": "/dev/remapped-loop2",
- "opt_fields": [
- "master:renumbered/47"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/writable/system-data/snap/pc/NUMBER",
- "mount_src": "/dev/remapped-loop3",
- "opt_fields": [
- "master:renumbered/48"
- ],
- "root_dir": "/"
- },
- {
- "fs_type": "squashfs",
- "mount_opts": "rw,relatime",
- "mount_point": "/writable/system-data/snap/snapd-hacker-toolbelt/x1",
- "mount_src": "/dev/remapped-loop4",
- "opt_fields": [
- "master:renumbered/49"
- ],
- "root_dir": "/"
- }
-]
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/process.py snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/process.py
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/process.py 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/process.py 1970-01-01 00:00:00.000000000 +0000
@@ -1,117 +0,0 @@
-#!/usr/bin/env python3
-import sys
-import json
-import re
-
-class mountinfo_entry:
-
- def __init__(self, fs_type, mount_id, mount_opts, mount_point, mount_src, opt_fields, root_dir):
- self.fs_type = fs_type
- self.mount_id = mount_id
- self.mount_opts = mount_opts
- self.mount_point = mount_point
- self.mount_src = mount_src
- self.opt_fields = opt_fields
- self.root_dir = root_dir
-
- @classmethod
- def parse(cls, line):
- parts = line.split()
- fs_type = parts[-3]
- mount_id = parts[0]
- mount_opts = parts[5]
- mount_point = parts[4]
- mount_src = parts[-2]
- root_dir = parts[3]
- opt_fields = []
- i = 6
- while parts[i] != '-':
- opt = parts[i]
- opt_fields.append(opt)
- i += 1
- opt_fields.sort()
- return cls(fs_type, mount_id, mount_opts, mount_point, mount_src,
- opt_fields, root_dir)
-
- def _fix_nondeterministic_mount_point(self):
- self.mount_point = re.sub('_\w{6}', '_XXXXXX', self.mount_point)
- self.mount_point = re.sub('/\d+$', '/NUMBER', self.mount_point)
-
- def _fix_nondeterministic_root_dir(self):
- self.root_dir = re.sub('_\w{6}', '_XXXXXX', self.root_dir)
-
- def _fix_nondeterministic_mount_src(self):
- self.mount_src = re.sub('/dev/[sv]da', '/dev/BLOCK', self.mount_src)
-
- def _fix_nondeterministic_opt_fields(self, seen):
- fixed = []
- for opt in self.opt_fields:
- if opt not in seen:
- opt_id = len(seen)
- seen[opt] = opt_id
- else:
- opt_id = seen[opt]
- remapped_opt = re.sub(':\d+$', lambda m: ':renumbered/{}'.format(opt_id), opt)
- fixed.append(remapped_opt)
- self.opt_fields = fixed
-
- def _fix_nondeterministic_loop(self, seen):
- if not self.mount_src.startswith("/dev/loop"):
- return
- if self.mount_src not in seen:
- loop_id = len(seen)
- seen[self.mount_src] = loop_id
- else:
- loop_id = seen[self.mount_src]
- self.mount_src = re.sub('loop\d+$', lambda m: 'remapped-loop{}'.format(loop_id), self.mount_src)
-
- def as_json(self):
- return {
- "fs_type": self.fs_type,
- "mount_opts": self.mount_opts,
- "mount_point": self.mount_point,
- "mount_src": self.mount_src,
- "opt_fields": self.opt_fields,
- "root_dir": self.root_dir,
- }
-
-
-def parse_mountinfo(lines):
- return [mountinfo_entry.parse(line) for line in lines]
-
-
-def fix_initial_nondeterminism(entries):
- for entry in entries:
- entry._fix_nondeterministic_mount_point()
-
-
-def fix_remaining_nondeterminism(entries):
- seen_opt_fields = {}
- seen_loops = {}
- for entry in entries:
- entry._fix_nondeterministic_root_dir()
- entry._fix_nondeterministic_mount_src()
- entry._fix_nondeterministic_opt_fields(seen_opt_fields)
- entry._fix_nondeterministic_loop(seen_loops)
-
-
-def main():
- entries = parse_mountinfo(sys.stdin)
- # Get rid of the core snap as it is not certain that we'll see one and we want determinism
- entries = [entry for entry in entries if not re.match("/snap/core/\d+", entry.mount_point)]
- # Fix random directories and non-deterministic revisions
- fix_initial_nondeterminism(entries)
- # Sort by just the mount point,
- entries.sort(key=lambda entry: (entry.mount_point))
- # Fix remainder of the non-determinism
- fix_remaining_nondeterminism(entries)
- # Make entries nicely deterministic, by sorting them by mount location
- entries.sort(key=lambda entry: (entry.mount_point, entry.mount_src, entry.root_dir))
- # Export everything
- json.dump([entry.as_json() for entry in entries],
- sys.stdout, sort_keys=True, indent=2, separators=(',', ': '))
- sys.stdout.write('\n')
-
-
-if __name__ == '__main__':
- main()
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/snap-arch.py snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/snap-arch.py
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/snap-arch.py 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/snap-arch.py 1970-01-01 00:00:00.000000000 +0000
@@ -1,22 +0,0 @@
-#!/usr/bin/env python3
-import os
-import sys
-
-def main():
- kernel_arch = os.uname().machine
- # Because off by one bugs and naming ...
- snap_arch_map = {
- 'aarch64': 'arm64',
- 'armv7l': 'armhf',
- 'x86_64': 'amd64',
- 'i686': 'i386',
- }
- try:
- print(snap_arch_map[kernel_arch])
- except KeyError:
- print("unsupported kernel architecture: {!a}".format(kernel_arch), file=sys.stderr)
- return 1
-
-
-if __name__ == '__main__':
- main()
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/task.yaml snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/task.yaml
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/task.yaml 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-layout/task.yaml 1970-01-01 00:00:00.000000000 +0000
@@ -1,46 +0,0 @@
-summary: Ensure that the mount namespace a given layout
-details: |
- This test analyzes /proc/self/mountinfo which contains a representation of
- the mount table of the current process. The mount table is a very sensitive
- part of the confinement design. This test measures the effective table,
- normalizes it (to remove some inherent randomness of certain identifiers
- and make it uniform regardless of particular names of block devices, snap
- revisions, etc.) and then compares it to a canned copy.
-
- There are several reference tables, one for core (aka all-snap system) and
- one for classic. At this time only classic systems are measured and tested.
- The classic systems are further divided into those using the core snap and
- those using the older ubuntu-core snap. Lastly, they are divided by
- architectures to take account any architecture specific differences.
-prepare: |
- echo "Having installed a busybox"
- snap install snapd-hacker-toolbelt
-execute: |
- echo "We can map the kernel architecture name to snap architecture name"
- arch=$(./snap-arch.py)
- echo "We can run busybox true so that snap-confine creates a mount namespace"
- snapd-hacker-toolbelt.busybox true
- echo "Using nsenter we can move to that namespace, inspect and normalize the mount table"
- nsenter -m/run/snapd/ns/snapd-hacker-toolbelt.mnt \
- cat /proc/self/mountinfo | ./process.py > observed.json
- echo "We can now compare the obtained mount table to expected values"
- if [ -e /snap/core/current ]; then
- cmp observed.json expected.classic.core.$SPREAD_BACKEND.$arch.json
- else
- cmp observed.json expected.classic.ubuntu-core.$SPREAD_BACKEND.$arch.json
- fi
-debug: |
- echo "When something goes wrong we can display a human-readable diff"
- arch=$(./snap-arch.py)
- if [ -e /snap/core/current ]; then
- diff -u observed.json expected.classic.core.$SPREAD_BACKEND.$arch.json || :
- else
- diff -u observed.json expected.classic.ubuntu-core.$SPREAD_BACKEND.$arch.json || :
- fi
- echo "And pastebin the raw table for analysis"
- apt-get install pastebinit
- nsenter -m/run/snapd/ns/snapd-hacker-toolbelt.mnt \
- cat /proc/self/mountinfo | pastebinit
-restore: |
- snap remove snapd-hacker-toolbelt
- rm -f observed.json
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-sharing/task.yaml snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-sharing/task.yaml
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-sharing/task.yaml 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-ns-sharing/task.yaml 2020-06-05 13:13:49.000000000 +0000
@@ -17,4 +17,4 @@
[ "$inner_mnt_ns" = "$(snapd-hacker-toolbelt.busybox readlink /proc/self/ns/mnt)" ]
done
restore: |
- snap remove snapd-hacker-toolbelt
+ snap remove --purge snapd-hacker-toolbelt
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-bin-snap-destination/task.yaml snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-bin-snap-destination/task.yaml
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-bin-snap-destination/task.yaml 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-bin-snap-destination/task.yaml 2020-06-05 13:13:49.000000000 +0000
@@ -20,7 +20,7 @@
echo "Not only the command failed because snap-confine failed, we see why!"
dmesg --ctime | grep 'apparmor="DENIED" operation="mount" info="failed srcname match" error=-13 profile="/usr/lib/snapd/snap-confine" name="/snap/bin/" pid=[0-9]\+ comm="ubuntu-core-lau" srcname="/snap/snapd-hacker-toolbelt/[0-9]\+/mnt/" flags="rw, bind"'
restore: |
- snap remove snapd-hacker-toolbelt
+ snap remove --purge snapd-hacker-toolbelt
rm -rf /var/snap/snapd-hacker-toolbelt
rm -f /var/lib/snapd/mount/snap.snapd-hacker-toolbelt.busybox.fstab
dmesg -c
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-bin-snap-source/task.yaml snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-bin-snap-source/task.yaml
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-bin-snap-source/task.yaml 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-bin-snap-source/task.yaml 2020-06-05 13:13:49.000000000 +0000
@@ -20,7 +20,7 @@
echo "Not only the command failed because snap-confine failed, we see why!"
dmesg --ctime | grep 'apparmor="DENIED" operation="mount" info="failed srcname match" error=-13 profile="/usr/lib/snapd/snap-confine" name="/snap/snapd-hacker-toolbelt/[0-9]\+/mnt/" pid=[0-9]\+ comm="ubuntu-core-lau" srcname="/snap/bin/" flags="rw, bind"'
restore: |
- snap remove snapd-hacker-toolbelt
+ snap remove --purge snapd-hacker-toolbelt
rm -rf /var/snap/snapd-hacker-toolbelt
rm -f /var/lib/snapd/mount/snap.snapd-hacker-toolbelt.busybox.fstab
dmesg -c
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-missing-dst/task.yaml snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-missing-dst/task.yaml
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-missing-dst/task.yaml 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-missing-dst/task.yaml 2020-06-05 13:13:49.000000000 +0000
@@ -14,6 +14,6 @@
echo "We can now run busybox.true and expect it to fail"
( cd / && ! /snap/bin/snapd-hacker-toolbelt.busybox true )
restore: |
- snap remove snapd-hacker-toolbelt
+ snap remove --purge snapd-hacker-toolbelt
rm -rf /var/snap/snapd-hacker-toolbelt
rm -f /var/lib/snapd/mount/snap.snapd-hacker-toolbelt.busybox.fstab
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-missing-src/task.yaml snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-missing-src/task.yaml
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-missing-src/task.yaml 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-missing-src/task.yaml 2020-06-05 13:13:49.000000000 +0000
@@ -14,6 +14,6 @@
echo "We can now run busybox.true and expect it to fail"
( cd / && ! /snap/bin/snapd-hacker-toolbelt.busybox true )
restore: |
- snap remove snapd-hacker-toolbelt
+ snap remove --purge snapd-hacker-toolbelt
rm -rf /var/snap/snapd-hacker-toolbelt
rm -f /var/lib/snapd/mount/snap.snapd-hacker-toolbelt.busybox.fstab
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-mount-tmpfs/task.yaml snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-mount-tmpfs/task.yaml
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-mount-tmpfs/task.yaml 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-mount-tmpfs/task.yaml 2020-06-05 13:13:49.000000000 +0000
@@ -2,7 +2,7 @@
# This is blacklisted on debian because we first have to get the dpkg-vendor patches
systems: [-debian-8]
restore: |
- snap remove snapd-hacker-toolbelt
+ snap remove --purge snapd-hacker-toolbelt
rm -rf /var/snap/snapd-hacker-toolbelt
rm -f /var/lib/snapd/mount/snap.snapd-hacker-toolbelt.busybox.fstab
execute: |
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-ro-mount/task.yaml snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-ro-mount/task.yaml
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-ro-mount/task.yaml 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-ro-mount/task.yaml 2020-06-05 13:13:49.000000000 +0000
@@ -13,6 +13,6 @@
echo "We can now look at the .id file in the destination directory"
[ "$(/snap/bin/snapd-hacker-toolbelt.busybox cat /snap/snapd-hacker-toolbelt/current/dst/.id)" = "source" ]
restore: |
- snap remove snapd-hacker-toolbelt
+ snap remove --purge snapd-hacker-toolbelt
rm -rf /var/snap/snapd-hacker-toolbelt
rm -f /var/lib/snapd/mount/snap.snapd-hacker-toolbelt.busybox.fstab
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-rw-mount/task.yaml snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-rw-mount/task.yaml
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-rw-mount/task.yaml 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-profiles-rw-mount/task.yaml 2020-06-05 13:13:49.000000000 +0000
@@ -19,6 +19,6 @@
# seems that busybox is not any different here.
/snap/bin/snapd-hacker-toolbelt.busybox mount | grep snapd-hacker-toolbelt
restore: |
- snap remove snapd-hacker-toolbelt
+ snap remove --purge snapd-hacker-toolbelt
rm -rf /var/snap/snapd-hacker-toolbelt
rm -f /var/lib/snapd/mount/snap.snapd-hacker-toolbelt.busybox.fstab
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-usr-src/task.yaml snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-usr-src/task.yaml
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/mount-usr-src/task.yaml 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/mount-usr-src/task.yaml 2020-06-05 13:13:49.000000000 +0000
@@ -14,4 +14,4 @@
echo "We can ensure that /usr/src is mounted"
/snap/bin/snapd-hacker-toolbelt.busybox cat /proc/self/mounts | grep ' /usr/src '
restore: |
- snap remove snapd-hacker-toolbelt
+ snap remove --purge snapd-hacker-toolbelt
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/test-seccomp-compat/task.yaml snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/test-seccomp-compat/task.yaml
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/test-seccomp-compat/task.yaml 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/test-seccomp-compat/task.yaml 2020-06-05 13:13:49.000000000 +0000
@@ -13,4 +13,4 @@
echo Run the 32 bit binary
test-seccomp-compat.true32
restore: |
- snap remove test-seccomp-compat
+ snap remove --purge test-seccomp-compat
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/test-snap-runs/task.yaml snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/test-snap-runs/task.yaml
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/test-snap-runs/task.yaml 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/test-snap-runs/task.yaml 2020-06-05 13:13:49.000000000 +0000
@@ -12,4 +12,4 @@
if snapd-hacker-toolbelt.busybox touch /var/tmp/evil; then exit 1; fi
dmesg -c
restore: |
- snap remove snapd-hacker-toolbelt
+ snap remove --purge snapd-hacker-toolbelt
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/ubuntu-core-launcher-exists/task.yaml snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/ubuntu-core-launcher-exists/task.yaml
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/ubuntu-core-launcher-exists/task.yaml 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/ubuntu-core-launcher-exists/task.yaml 1970-01-01 00:00:00.000000000 +0000
@@ -1,6 +0,0 @@
-summary: Check that ubuntu-core-launcher executes correctly
-# This is blacklisted on debian because we first have to get the dpkg-vendor patches
-systems: [-debian-8]
-execute: |
- echo "ubuntu-core-launcher is installed and responds to --help"
- ubuntu-core-launcher --help 2>&1 | grep -F -q 'Usage: ubuntu-core-launcher '
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/user-data-dir-created/task.yaml snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/user-data-dir-created/task.yaml
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/user-data-dir-created/task.yaml 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/user-data-dir-created/task.yaml 2020-06-05 13:13:49.000000000 +0000
@@ -19,5 +19,5 @@
echo "And see that the SNAP_USER_DATA directory was created"
test -d $HOME/snap/snapd-hacker-toolbelt
restore: |
- snap remove snapd-hacker-toolbelt
+ snap remove --purge snapd-hacker-toolbelt
rm -rf "$HOME/snap/snapd-hacker-toolbelt/"
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/user-xdg-runtime-dir-created/task.yaml snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/user-xdg-runtime-dir-created/task.yaml
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/main/user-xdg-runtime-dir-created/task.yaml 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/main/user-xdg-runtime-dir-created/task.yaml 2020-06-05 13:13:49.000000000 +0000
@@ -17,5 +17,5 @@
echo "And see that the XDG_RUNTIME_DIR directory was created"
test -d /run/user/`id -u`/snapd-hacker-toolbelt
restore: |
- snap remove snapd-hacker-toolbelt
+ snap remove --purge snapd-hacker-toolbelt
rm -rf "/run/user/`id -u`/snapd-hacker-toolbelt"
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/regression/lp-1599608/task.yaml snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/regression/lp-1599608/task.yaml
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/regression/lp-1599608/task.yaml 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/regression/lp-1599608/task.yaml 2020-06-05 13:13:49.000000000 +0000
@@ -50,7 +50,7 @@
restore: |
exit 0
echo "Remove hello-world"
- snap remove hello-world
+ snap remove --purge hello-world
systemctl stop snapd.service snapd.socket
echo "Unmount the modified core snap"
# all of this ls madness can go away when we have remote environment
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/release.sh snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/release.sh
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/release.sh 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/release.sh 1970-01-01 00:00:00.000000000 +0000
@@ -1,41 +0,0 @@
-#!/bin/bash
-# This script creates a new release tarball
-set -xue
-
-# Sanity check, are we in the top-level directory of the tree?
-test -f configure.ac || ( echo 'this script must be executed from the top-level of the tree' && exit 1)
-
-# Record where the top level directory is
-top_dir=$(pwd)
-
-# Create source distribution tarball and place it in the top-level directory.
-create_dist_tarball() {
- # Load the version number from a dedicated file
- local pkg_version=
- pkg_version="$(cat "$top_dir/VERSION")"
-
- # Ensure that build system is up-to-date and ready
- autoreconf -f -i
- # XXX: This fixes somewhat odd error when configure below (in an empty directory) fails with:
- # configure: error: source directory already configured; run "make distclean" there first
- test -f Makefile && make distclean
-
- # Create a scratch space to run configure
- scratch_dir="$(mktemp -d)"
- trap 'rm -rf "$scratch_dir"' EXIT
-
- # Configure the project in a scratch directory
- cd "$scratch_dir"
- "$top_dir/configure" --prefix=/usr
-
- # Create the distribution tarball
- make dist
-
- # Ensure we got the tarball we were expecting to see
- test -f "snap-confine-$pkg_version.tar.gz"
-
- # Move it to the top-level directory
- mv "snap-confine-$pkg_version.tar.gz" "$top_dir/"
-}
-
-create_dist_tarball
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/spread-prepare.sh snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/spread-prepare.sh
--- snapd-2.42.1+18.04/cmd/snap-confine/spread-tests/spread-prepare.sh 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/spread-tests/spread-prepare.sh 1970-01-01 00:00:00.000000000 +0000
@@ -1,179 +0,0 @@
-#!/bin/bash
-# This script is started by spread to prepare the execution environment
-set -xue
-
-# Sanity check, are we in the top-level directory of the tree?
-test -f configure.ac || ( echo 'this script must be executed from the top-level of the tree' && exit 1)
-
-# Record where the top level directory is
-top_dir=$(pwd)
-
-# Record the current distribution release data to know what to do
-# shellcheck disable=SC1091
-{
- release_ID="$( . /etc/os-release && echo "${ID:-linux}" )"
- release_VERSION_ID="$( . /etc/os-release && echo "${VERSION_ID:-}" )"
-}
-
-
-build_debian_or_ubuntu_package() {
- local pkg_version
- local distro_packaging_git_branch
- local distro_packaging_git
- local distro_archive
- local distro_codename
- local sbuild_createchroot_extra=""
- pkg_version="$(cat "$top_dir/VERSION")"
-
- if [ ! -f "$top_dir/spread-tests/distros/$release_ID.$release_VERSION_ID" ] || \
- [ ! -f "$top_dir/spread-tests/distros/$release_ID.common" ]; then
- echo "Distribution: $release_ID (release $release_VERSION_ID) is not supported"
- echo "please read this script and create new files in spread-test/distros"
- exit 1
- fi
-
- # source the distro specific vars
- # shellcheck disable=SC1090
- {
- . "$top_dir/spread-tests/distros/$release_ID.$release_VERSION_ID"
- . "$top_dir/spread-tests/distros/$release_ID.common"
- }
-
- # sanity check, ensure that essential variables were defined
- test -n "$distro_packaging_git_branch"
- test -n "$distro_packaging_git"
- test -n "$distro_archive"
- test -n "$distro_codename"
-
- # Create a scratch space
- scratch_dir="$(mktemp -d)"
- trap 'rm -rf "$scratch_dir"' EXIT
-
- # Do everything in the scratch directory
- cd "$scratch_dir"
-
- # Fetch the current Ubuntu packaging for the appropriate release
- git clone -b "$distro_packaging_git_branch" "$distro_packaging_git" distro-packaging
-
- # Install all the build dependencies declared by the package.
- eatmydata apt-get install --quiet -y gdebi-core
- gdebi --quiet --apt-line ./distro-packaging/debian/control | xargs -r apt-get install --quiet -y
-
- # Generate a new upstream tarball from the current state of the tree
- ( cd "$top_dir" && spread-tests/release.sh )
-
- # Prepare the .orig tarball and unpackaged source tree
- cp "$top_dir/snap-confine-$pkg_version.tar.gz" "snap-confine_$pkg_version.orig.tar.gz"
- tar -zxf "snap-confine_$pkg_version.orig.tar.gz"
-
- # Apply the debian directory from downstream packaging to form a complete source package
- mv "distro-packaging/debian" "snap-confine-$pkg_version/debian"
- rm -rf distro-packaging
-
- # Add an automatically-generated changelog entry
- # The --controlmaint takes the maintainer details from debian/control
- ( cd "snap-confine-$pkg_version" && dch --controlmaint --newversion "${pkg_version}-1" "Automatic CI build")
-
- # Build an unsigned source package
- ( cd "snap-confine-$pkg_version" && dpkg-buildpackage -uc -us -S )
-
- # Copy source package files to the top-level directory (this helps for
- # interactive debugging since the package is available right there)
- cp ./*.dsc ./*.debian.tar.* ./*.orig.tar.gz "$top_dir/"
-
- # Ensure that we have a sbuild chroot ready
- if ! schroot -l | grep "chroot:${distro_codename}-.*-sbuild"; then
- sbuild-createchroot \
- --include=eatmydata \
- "--make-sbuild-tarball=/var/lib/sbuild/${distro_codename}-amd64.tar.gz" \
- "$sbuild_createchroot_extra" \
- "$distro_codename" "$(mktemp -d)" \
- "$distro_archive"
- fi
-
- # Build a binary package in a clean chroot.
- # NOTE: nocheck is because the package still includes old unit tests that
- # are deeply integrated into how ubuntu apparmor denials are logged. This
- # should be removed once those test are migrated to spread testes.
- DEB_BUILD_OPTIONS=nocheck sbuild \
- --arch-all \
- --dist="$distro_codename" \
- --batch \
- "snap-confine_${pkg_version}-1.dsc"
-
- # Copy all binary packages to the top-level directory
- cp ./*.deb "$top_dir/"
-}
-
-
-# Apply tweaks
-case "$release_ID" in
- ubuntu)
- # apt update is hanging on security.ubuntu.com with IPv6.
- sysctl -w net.ipv6.conf.all.disable_ipv6=1
- trap "sysctl -w net.ipv6.conf.all.disable_ipv6=0" EXIT
- ;;
-esac
-
-# Install all the build dependencies
-case "$release_ID" in
- ubuntu|debian)
- # treat APT_PROXY as a location of apt-cacher-ng to use
- if [ -n "${APT_PROXY:-}" ]; then
- printf 'Acquire::http::Proxy "%s";\n' "$APT_PROXY" > /etc/apt/apt.conf.d/00proxy
- fi
- # cope with unexpected /etc/apt/apt.conf.d/95cloud-init-proxy that may be in the image
- rm -f /etc/apt/apt.conf.d/95cloud-init-proxy || :
- # trusty support is under development right now
- # we special-case the release until we have officially landed
- if [ "$release_ID" = "ubuntu" ] && [ "$release_VERSION_ID" = "14.04" ]; then
- add-apt-repository ppa:thomas-voss/trusty
- fi
- apt-get update
- apt-get dist-upgrade -y
- if [ "$release_ID" = "ubuntu" ] && [ "$release_VERSION_ID" = "14.04" ]; then
- apt-get install -y systemd
- # starting systemd manually is working around
- # systemd not running as PID 1 on trusty systems.
- service systemd start
- fi
- # On Debian and derivatives we need the following things:
- # - sbuild -- to build the binary package with extra hygiene
- # - devscripts -- to modify the changelog automatically
- # - git -- to clone native downstream packaging
- apt-get install --quiet -y sbuild devscripts git
- # XXX: Taken from https://wiki.debian.org/sbuild
- mkdir -p /root/.gnupg
- # NOTE: We cannot use sbuild-update --keygen as virtual machines lack
- # the necessary entropy to generate keys before the spread timeout
- # kicks in. Instead we just copy pre-made, insecure keys from the
- # source repository.
- mkdir -p /var/lib/sbuild/apt-keys/
- cp -a "$top_dir/spread-tests/data/apt-keys/"* /var/lib/sbuild/apt-keys/
- sbuild-adduser "$LOGNAME"
- ;;
- *)
- echo "unsupported distribution: $release_ID"
- echo "patch spread-prepare to teach it about how to install build dependencies"
- exit 1
- ;;
-esac
-
-# Build and install the native package using downstream packaging and the fresh upstream tarball
-case "$release_ID" in
- ubuntu|debian)
- build_debian_or_ubuntu_package "$release_ID" "$release_VERSION_ID"
- # Install the freshly-built packages
- dpkg -i snap-confine_*.deb || apt-get -f install -y
- dpkg -i ubuntu-core-launcher_*.deb || apt-get -f install -y
- # Install snapd (testes require it)
- apt-get install -y snapd
- ;;
- *)
- echo "unsupported distribution: $release_ID"
- exit 1
- ;;
-esac
-
-# Install the core snap
-snap list | grep -q ubuntu-core || snap install ubuntu-core
diff -Nru snapd-2.42.1+18.04/cmd/snap-confine/udev-support.c snapd-2.45.1+18.04/cmd/snap-confine/udev-support.c
--- snapd-2.42.1+18.04/cmd/snap-confine/udev-support.c 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-confine/udev-support.c 2020-06-05 13:13:49.000000000 +0000
@@ -201,14 +201,15 @@
sc_must_snprintf(cgroup_dir, sizeof(cgroup_dir),
"/sys/fs/cgroup/devices/%s/", security_tag);
-
+ sc_identity old = sc_set_effective_identity(sc_root_group_identity());
if (mkdir(cgroup_dir, 0755) < 0 && errno != EEXIST)
die("cannot create cgroup hierarchy %s", cgroup_dir);
+ (void)sc_set_effective_identity(old);
// move ourselves into it
char cgroup_file[PATH_MAX] = { 0 };
sc_must_snprintf(cgroup_file, sizeof(cgroup_file), "%s%s", cgroup_dir,
- "tasks");
+ "cgroup.procs");
char buf[128] = { 0 };
sc_must_snprintf(buf, sizeof(buf), "%i", getpid());
@@ -296,6 +297,16 @@
major(sbuf.st_rdev),
minor(sbuf.st_rdev));
}
+ // When CONFIG_TUN=m, /dev/net/tun will exist but using it doesn't
+ // autoload the tun module but also /dev/net/tun isn't udev tagged
+ // until it is loaded. To work around this, if /dev/net/tun exists, add
+ // it unconditionally to the cgroup and rely on AppArmor to mediate the
+ // access. LP: #1859084
+ if (stat("/dev/net/tun", &sbuf) == 0) {
+ _run_snappy_app_dev_add_majmin(udev_s, "/dev/net/tun",
+ major(sbuf.st_rdev),
+ minor(sbuf.st_rdev));
+ }
// add the assigned devices
while (udev_s->assigned != NULL) {
const char *path = udev_list_entry_get_name(udev_s->assigned);
diff -Nru snapd-2.42.1+18.04/cmd/snapctl/main.go snapd-2.45.1+18.04/cmd/snapctl/main.go
--- snapd-2.42.1+18.04/cmd/snapctl/main.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snapctl/main.go 2020-06-05 13:13:49.000000000 +0000
@@ -58,6 +58,22 @@
// no internal command, route via snapd
stdout, stderr, err := run()
if err != nil {
+ if e, ok := err.(*client.Error); ok {
+ switch e.Kind {
+ case client.ErrorKindUnsuccessful:
+ if errRes, ok := e.Value.(map[string]interface{}); ok {
+ if stdout, ok := errRes["stdout"].(string); ok {
+ os.Stdout.Write([]byte(stdout))
+ }
+ if stderr, ok := errRes["stderr"].(string); ok {
+ os.Stderr.Write([]byte(stderr))
+ }
+ if errCode, ok := errRes["exit-code"].(float64); ok {
+ os.Exit(int(errCode))
+ }
+ }
+ }
+ }
fmt.Fprintf(os.Stderr, "error: %s\n", err)
os.Exit(1)
}
diff -Nru snapd-2.42.1+18.04/cmd/snapctl/main_test.go snapd-2.45.1+18.04/cmd/snapctl/main_test.go
--- snapd-2.42.1+18.04/cmd/snapctl/main_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snapctl/main_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -47,6 +47,8 @@
func (s *snapctlSuite) SetUpTest(c *C) {
os.Setenv("SNAP_COOKIE", "snap-context-test")
+ // don't use SNAP_CONTEXT, in case other tests accidentally leak this
+ os.Unsetenv("SNAP_CONTEXT")
n := 0
s.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch n {
diff -Nru snapd-2.42.1+18.04/cmd/snapd/main.go snapd-2.45.1+18.04/cmd/snapd/main.go
--- snapd-2.42.1+18.04/cmd/snapd/main.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snapd/main.go 2020-06-05 13:13:49.000000000 +0000
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2015 Canonical Ltd
+ * Copyright (C) 2015-2020 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -29,10 +29,11 @@
"github.com/snapcore/snapd/cmd"
"github.com/snapcore/snapd/daemon"
"github.com/snapcore/snapd/errtracker"
- "github.com/snapcore/snapd/httputil"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/sandbox"
"github.com/snapcore/snapd/sanity"
+ "github.com/snapcore/snapd/snapdenv"
"github.com/snapcore/snapd/systemd"
)
@@ -51,7 +52,12 @@
}
func main() {
- cmd.ExecInSnapdOrCoreSnap()
+ // When preseeding re-exec is not used
+ if snapdenv.Preseeding() {
+ logger.Noticef("running for preseeding")
+ } else {
+ cmd.ExecInSnapdOrCoreSnap()
+ }
ch := make(chan os.Signal, 2)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
@@ -103,7 +109,7 @@
func run(ch chan os.Signal) error {
t0 := time.Now().Truncate(time.Millisecond)
- httputil.SetUserAgentFromVersion(cmd.Version)
+ snapdenv.SetUserAgentFromVersion(cmd.Version, sandbox.ForceDevMode)
d, err := daemon.New()
if err != nil {
diff -Nru snapd-2.42.1+18.04/cmd/snapd/main_test.go snapd-2.45.1+18.04/cmd/snapd/main_test.go
--- snapd-2.42.1+18.04/cmd/snapd/main_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snapd/main_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -34,6 +34,7 @@
"github.com/snapcore/snapd/interfaces/apparmor"
"github.com/snapcore/snapd/interfaces/seccomp"
"github.com/snapcore/snapd/logger"
+ "github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/testutil"
snapd "github.com/snapcore/snapd/cmd/snapd"
@@ -44,6 +45,7 @@
type snapdSuite struct {
tmpdir string
+ testutil.BaseTest
}
var _ = Suite(&snapdSuite{})
@@ -55,6 +57,9 @@
c.Assert(err, IsNil)
}
dirs.SetRootDir(s.tmpdir)
+
+ restore := osutil.MockMountInfo("")
+ s.AddCleanup(restore)
}
func (s *snapdSuite) TestSanityFailGoesIntoDegradedMode(c *C) {
diff -Nru snapd-2.42.1+18.04/cmd/snapd-generator/main.c snapd-2.45.1+18.04/cmd/snapd-generator/main.c
--- snapd-2.42.1+18.04/cmd/snapd-generator/main.c 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snapd-generator/main.c 2020-06-05 13:13:49.000000000 +0000
@@ -72,7 +72,7 @@
// Construct the file name for a new systemd mount unit.
char fname[PATH_MAX + 1] = { 0 };
sc_must_snprintf(fname, sizeof fname,
- "%s/" SNAP_MOUNT_DIR ".mount", normal_dir);
+ "%s/" SNAP_MOUNT_DIR_SYSTEMD_UNIT ".mount", normal_dir);
// Open the mount unit and write the contents.
FILE *f SC_CLEANUP(sc_cleanup_file) = NULL;
diff -Nru snapd-2.42.1+18.04/cmd/snap-discard-ns/snap-discard-ns.c snapd-2.45.1+18.04/cmd/snap-discard-ns/snap-discard-ns.c
--- snapd-2.42.1+18.04/cmd/snap-discard-ns/snap-discard-ns.c 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-discard-ns/snap-discard-ns.c 2020-06-05 13:13:49.000000000 +0000
@@ -117,7 +117,7 @@
sc_must_snprintf(usr_fstab_pattern, sizeof usr_fstab_pattern, "snap\\.%s\\.*\\.user-fstab", snap_instance_name);
sc_must_snprintf(sys_mnt_pattern, sizeof sys_mnt_pattern, "%s\\.mnt", snap_instance_name);
sc_must_snprintf(usr_mnt_pattern, sizeof usr_mnt_pattern, "%s\\.*\\.mnt", snap_instance_name);
- sc_must_snprintf(sys_info_pattern, sizeof sys_info_pattern, "%s\\.*\\.info", snap_instance_name);
+ sc_must_snprintf(sys_info_pattern, sizeof sys_info_pattern, "snap\\.%s\\.info", snap_instance_name);
DIR* ns_dir = fdopendir(ns_dir_fd);
if (ns_dir == NULL) {
diff -Nru snapd-2.42.1+18.04/cmd/snap-exec/main.go snapd-2.45.1+18.04/cmd/snap-exec/main.go
--- snapd-2.42.1+18.04/cmd/snap-exec/main.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-exec/main.go 2020-06-05 13:13:49.000000000 +0000
@@ -138,11 +138,11 @@
// expandEnvCmdArgs takes the string list of commandline arguments
// and expands any $VAR with the given var from the env argument.
-func expandEnvCmdArgs(args []string, env map[string]string) []string {
+func expandEnvCmdArgs(args []string, env osutil.Environment) []string {
cmdArgs := make([]string, 0, len(args))
for _, arg := range args {
- maybeExpanded := os.Expand(arg, func(k string) string {
- return env[k]
+ maybeExpanded := os.Expand(arg, func(varName string) string {
+ return env[varName]
})
if maybeExpanded != "" {
cmdArgs = append(cmdArgs, maybeExpanded)
@@ -186,21 +186,20 @@
// build the environment from the yaml, translating TMPDIR and
// similar variables back from where they were hidden when
// invoking the setuid snap-confine.
- env := []string{}
- for _, kv := range os.Environ() {
- if strings.HasPrefix(kv, snapenv.PreservedUnsafePrefix) {
- kv = kv[len(snapenv.PreservedUnsafePrefix):]
- }
- env = append(env, kv)
+ env, err := osutil.OSEnvironmentUnescapeUnsafe(snapenv.PreservedUnsafePrefix)
+ if err != nil {
+ return err
+ }
+ for _, eenv := range app.EnvChain() {
+ env.ExtendWithExpanded(eenv)
}
- env = append(env, osutil.SubstituteEnv(app.Env())...)
// strings.Split() is ok here because we validate all app fields and the
// whitelist is pretty strict (see snap/validate.go:appContentWhitelist)
// (see also overlord/snapstate/check_snap.go's normPath)
tmpArgv := strings.Split(cmdAndArgs, " ")
cmd := tmpArgv[0]
- cmdArgs := expandEnvCmdArgs(tmpArgv[1:], osutil.EnvMap(env))
+ cmdArgs := expandEnvCmdArgs(tmpArgv[1:], env)
// run the command
fullCmd := []string{filepath.Join(app.Snap.MountDir(), cmd)}
@@ -227,7 +226,7 @@
fullCmd = append(absoluteCommandChain(app.Snap, app.CommandChain), fullCmd...)
- if err := syscallExec(fullCmd[0], fullCmd, env); err != nil {
+ if err := syscallExec(fullCmd[0], fullCmd, env.ForExec()); err != nil {
return fmt.Errorf("cannot exec %q: %s", fullCmd[0], err)
}
// this is never reached except in tests
@@ -253,9 +252,18 @@
}
// build the environment
- env := append(os.Environ(), osutil.SubstituteEnv(hook.Env())...)
+ // NOTE: we do not use OSEnvironmentUnescapeUnsafe, we do not
+ // particurly want to transmit snapd exec environment details
+ // to the hooks
+ env, err := osutil.OSEnvironment()
+ if err != nil {
+ return err
+ }
+ for _, eenv := range hook.EnvChain() {
+ env.ExtendWithExpanded(eenv)
+ }
// run the hook
cmd := append(absoluteCommandChain(hook.Snap, hook.CommandChain), filepath.Join(hook.Snap.HooksDir(), hook.Name))
- return syscallExec(cmd[0], cmd, env)
+ return syscallExec(cmd[0], cmd, env.ForExec())
}
diff -Nru snapd-2.42.1+18.04/cmd/snap-exec/main_test.go snapd-2.45.1+18.04/cmd/snap-exec/main_test.go
--- snapd-2.42.1+18.04/cmd/snap-exec/main_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-exec/main_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -30,6 +30,7 @@
. "gopkg.in/check.v1"
"github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snaptest"
@@ -67,6 +68,7 @@
BASE_PATH: /some/path
LD_LIBRARY_PATH: ${BASE_PATH}/lib
MY_PATH: $PATH
+ TEST_PATH: /custom
app2:
command: run-app2
stop-command: stop-app2
@@ -161,6 +163,12 @@
})
defer restore()
+ // FIXME: TEST_PATH was meant to be just PATH but this uncovers another
+ // bug in the test suite where mocking binaries misbehaves.
+ oldPath := os.Getenv("TEST_PATH")
+ os.Setenv("TEST_PATH", "/vanilla")
+ defer os.Setenv("TEST_PATH", oldPath)
+
// launch and verify its run the right way
err := snapExec.ExecApp("snapname.app", "42", "stop", []string{"arg1", "arg2"})
c.Assert(err, IsNil)
@@ -169,6 +177,11 @@
c.Check(execEnv, testutil.Contains, "BASE_PATH=/some/path")
c.Check(execEnv, testutil.Contains, "LD_LIBRARY_PATH=/some/path/lib")
c.Check(execEnv, testutil.Contains, fmt.Sprintf("MY_PATH=%s", os.Getenv("PATH")))
+ // TEST_PATH is properly handled and we only see one value, /custom, defined
+ // as an app-specific override.
+ // See also https://bugs.launchpad.net/snapd/+bug/1860369
+ c.Check(execEnv, Not(testutil.Contains), "TEST_PATH=/vanilla")
+ c.Check(execEnv, testutil.Contains, "TEST_PATH=/custom")
}
func (s *snapExecSuite) TestSnapExecAppCommandChainIntegration(c *C) {
@@ -465,8 +478,8 @@
expected: []string{"foo", "bar", "baz"},
},
} {
- c.Check(snapExec.ExpandEnvCmdArgs(t.args, t.env), DeepEquals, t.expected)
-
+ env := osutil.Environment(t.env)
+ c.Check(snapExec.ExpandEnvCmdArgs(t.args, env), DeepEquals, t.expected)
}
}
@@ -575,9 +588,11 @@
execArgv0 := ""
execArgs := []string{}
+ execEnv := []string{}
restore = snapExec.MockSyscallExec(func(argv0 string, argv []string, env []string) error {
execArgv0 = argv0
execArgs = argv
+ execEnv = env
return nil
})
defer restore()
@@ -592,6 +607,8 @@
// setup env
os.Setenv("SNAP_DATA", "/var/snap/snapname/42")
defer os.Unsetenv("SNAP_DATA")
+ os.Setenv("SNAP_SAVED_TMPDIR", "/var/tmp99")
+ defer os.Unsetenv("SNAP_SAVED_TMPDIR")
// launch and verify its run the right way
err := snapExec.ExecApp("snapname.app", "42", "complete", []string{"foo"})
@@ -601,4 +618,6 @@
filepath.Join(dirs.DistroLibExecDir, "etelpmoc.sh"),
filepath.Join(dirs.SnapMountDir, "snapname/42/you/complete/me"),
"foo"})
+ c.Check(execEnv, testutil.Contains, "SNAP_DATA=/var/snap/snapname/42")
+ c.Check(execEnv, testutil.Contains, "TMPDIR=/var/tmp99")
}
diff -Nru snapd-2.42.1+18.04/cmd/snap-failure/cmd_snapd.go snapd-2.45.1+18.04/cmd/snap-failure/cmd_snapd.go
--- snapd-2.42.1+18.04/cmd/snap-failure/cmd_snapd.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-failure/cmd_snapd.go 2020-06-05 13:13:49.000000000 +0000
@@ -27,8 +27,10 @@
"os"
"os/exec"
"path/filepath"
+ "time"
"github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
)
@@ -51,13 +53,14 @@
}
type snapSeq struct {
- Current string `json:"current"`
- Sequence []*sideInfo `json:"sequence"`
+ Current string `json:"current"`
+ Sequence []sideInfo `json:"sequence"`
}
type cmdSnapd struct{}
var errNoSnapd = errors.New("no snapd sequence file found")
+var errNoPrevious = errors.New("no revision to go back to")
func prevRevision(snapName string) (string, error) {
seqFile := filepath.Join(dirs.SnapSeqDir, snapName+".json")
@@ -71,59 +74,137 @@
var seq snapSeq
if err := json.Unmarshal(content, &seq); err != nil {
- return "", err
+ return "", fmt.Errorf("cannot parse %q sequence file: %v", filepath.Base(seqFile), err)
}
var prev string
for i, si := range seq.Sequence {
if seq.Current == si.Revision {
if i == 0 {
- return "", fmt.Errorf("no revision to go back to")
+ return "", errNoPrevious
}
prev = seq.Sequence[i-1].Revision
break
}
}
if prev == "" {
- return "", fmt.Errorf("internal error: current not found in sequence: %v %v", seq.Current, seq.Sequence)
+ return "", fmt.Errorf("internal error: current %v not found in sequence: %+v", seq.Current, seq.Sequence)
}
return prev, nil
}
+func runCmd(prog string, args []string, env []string) *exec.Cmd {
+ cmd := exec.Command(prog, args...)
+ cmd.Env = os.Environ()
+ for _, envVar := range env {
+ cmd.Env = append(cmd.Env, envVar)
+ }
+
+ cmd.Stdout = Stdout
+ cmd.Stderr = Stderr
+
+ return cmd
+}
+
+var (
+ sampleForActiveInterval = 5 * time.Second
+ restartSnapdCoolOffWait = 12500 * time.Millisecond
+)
+
// FIXME: also do error reporting via errtracker
func (c *cmdSnapd) Execute(args []string) error {
- // find previous snapd
+ var snapdPath string
+ // find previous the snapd snap
prevRev, err := prevRevision("snapd")
- if err != nil {
- if err == errNoSnapd {
- return nil
- }
+ switch err {
+ case errNoSnapd:
+ // the snapd snap is not installed
+ return nil
+ case errNoPrevious:
+ // this is the first revision of snapd to be installed on the
+ // system, either a remodel or a plain snapd installation, call
+ // the snapd from the core snap
+ snapdPath = filepath.Join(dirs.SnapMountDir, "core", "current", "/usr/lib/snapd/snapd")
+ prevRev = "0"
+ case nil:
+ // the snapd snap was installed before, use the previous revision
+ snapdPath = filepath.Join(dirs.SnapMountDir, "snapd", prevRev, "/usr/lib/snapd/snapd")
+ default:
return err
}
+ logger.Noticef("stopping snapd socket")
// stop the socket unit so that we can start snapd on its own
output, err := exec.Command("systemctl", "stop", "snapd.socket").CombinedOutput()
if err != nil {
return osutil.OutputErr(output, err)
}
+ logger.Noticef("restoring invoking snapd from: %v", snapdPath)
// start previous snapd
- snapdPath := filepath.Join(dirs.SnapMountDir, "snapd", prevRev, "/usr/lib/snapd/snapd")
- cmd := exec.Command(snapdPath)
- cmd.Env = os.Environ()
- cmd.Env = append(cmd.Env, "SNAPD_REVERT_TO_REV="+prevRev)
- output, err = cmd.CombinedOutput()
- if err != nil {
- return osutil.OutputErr(output, err)
+ cmd := runCmd(snapdPath, nil, []string{"SNAPD_REVERT_TO_REV=" + prevRev, "SNAPD_DEBUG=1"})
+ if err = cmd.Run(); err != nil {
+ return fmt.Errorf("snapd failed: %v", err)
}
+ isFailedCmd := runCmd("systemctl", []string{"is-failed", "snapd.socket", "snapd.service"}, nil)
+ if err := isFailedCmd.Run(); err != nil {
+ // the ephemeral snapd we invoked seems to have fixed
+ // snapd.service and snapd.socket, check whether they get
+ // reported as active for 5 * 5s
+ for i := 0; i < 5; i++ {
+ if i != 0 {
+ time.Sleep(sampleForActiveInterval)
+ }
+ isActiveCmd := runCmd("systemctl", []string{"is-active", "snapd.socket", "snapd.service"}, nil)
+ err := isActiveCmd.Run()
+ if err == nil && osutil.FileExists(dirs.SnapdSocket) && osutil.FileExists(dirs.SnapSocket) {
+ logger.Noticef("snapd is active again, sockets are available, nothing more to do")
+ return nil
+ }
+ }
+ }
+
+ logger.Noticef("restarting snapd socket")
+ // we need to reset the failure state to be able to restart again
+ resetCmd := runCmd("systemctl", []string{"reset-failed", "snapd.socket", "snapd.service"}, nil)
+ if err = resetCmd.Run(); err != nil {
+ // don't die if we fail to reset the failed state of snapd.socket, as
+ // the restart itself could still work
+ logger.Noticef("failed to reset-failed snapd.socket: %v", err)
+ }
// at this point our manually started snapd stopped and
- // removed the /run/snap* sockets (this is a feature of
+ // should have removed the /run/snap* sockets (this is a feature of
// golang) - we need to restart snapd.socket to make them
// available again.
- output, err = exec.Command("systemctl", "restart", "snapd.socket").CombinedOutput()
- if err != nil {
- return osutil.OutputErr(output, err)
+
+ // be extra robust and if the socket file still somehow exists delete it
+ // before restarting, otherwise the restart command will fail because the
+ // systemd can't create the file
+ // always remove to avoid TOCTOU issues but don't complain about ENOENT
+ for _, fn := range []string{dirs.SnapdSocket, dirs.SnapSocket} {
+ err = os.Remove(fn)
+ if err != nil && !os.IsNotExist(err) {
+ logger.Noticef("snapd socket %s still exists before restarting socket service, but unable to remove: %v", fn, err)
+ }
+ }
+
+ restartCmd := runCmd("systemctl", []string{"restart", "snapd.socket"}, nil)
+ if err := restartCmd.Run(); err != nil {
+ logger.Noticef("failed to restart snapd.socket: %v", err)
+ // fallback to try snapd itself
+ // wait more than DefaultStartLimitIntervalSec
+ //
+ // TODO: consider parsing
+ // systemctl show snapd -p StartLimitIntervalUSec
+ // might need system-analyze timespan which is relatively new
+ // for the general case
+ time.Sleep(restartSnapdCoolOffWait)
+ logger.Noticef("fallback, restarting snapd itself")
+ restartCmd := runCmd("systemctl", []string{"restart", "snapd.service"}, nil)
+ if err := restartCmd.Run(); err != nil {
+ logger.Noticef("failed to restart snapd: %v", err)
+ }
}
return nil
diff -Nru snapd-2.42.1+18.04/cmd/snap-failure/cmd_snapd_test.go snapd-2.45.1+18.04/cmd/snap-failure/cmd_snapd_test.go
--- snapd-2.42.1+18.04/cmd/snap-failure/cmd_snapd_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-failure/cmd_snapd_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -20,11 +20,19 @@
package main_test
import (
+ "encoding/json"
+ "io/ioutil"
"os"
+ "path/filepath"
+ "time"
. "gopkg.in/check.v1"
failure "github.com/snapcore/snapd/cmd/snap-failure"
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/testutil"
)
func (r *failureSuite) TestRun(c *C) {
@@ -35,3 +43,375 @@
c.Check(err, IsNil)
c.Check(r.Stderr(), HasLen, 0)
}
+
+func writeSeqFile(c *C, name string, current snap.Revision, seq []*snap.SideInfo) {
+ seqPath := filepath.Join(dirs.SnapSeqDir, name+".json")
+
+ err := os.MkdirAll(dirs.SnapSeqDir, 0755)
+ c.Assert(err, IsNil)
+
+ b, err := json.Marshal(&struct {
+ Sequence []*snap.SideInfo `json:"sequence"`
+ Current string `json:"current"`
+ }{
+ Sequence: seq,
+ Current: current.String(),
+ })
+ c.Assert(err, IsNil)
+
+ err = ioutil.WriteFile(seqPath, b, 0644)
+ c.Assert(err, IsNil)
+}
+
+func (r *failureSuite) TestCallPrevSnapdFromSnap(c *C) {
+ origArgs := os.Args
+ defer func() { os.Args = origArgs }()
+
+ writeSeqFile(c, "snapd", snap.R(123), []*snap.SideInfo{
+ {Revision: snap.R(99)},
+ {Revision: snap.R(100)},
+ {Revision: snap.R(123)},
+ })
+
+ // mock snapd command from 'previous' revision
+ snapdCmd := testutil.MockCommand(c, filepath.Join(dirs.SnapMountDir, "snapd", "100", "/usr/lib/snapd/snapd"),
+ `test "$SNAPD_REVERT_TO_REV" = "100"`)
+ defer snapdCmd.Restore()
+
+ systemctlCmd := testutil.MockCommand(c, "systemctl", "")
+ defer systemctlCmd.Restore()
+
+ os.Args = []string{"snap-failure", "snapd"}
+ err := failure.Run()
+ c.Check(err, IsNil)
+ c.Check(r.Stderr(), HasLen, 0)
+
+ c.Check(snapdCmd.Calls(), DeepEquals, [][]string{
+ {"snapd"},
+ })
+ c.Check(systemctlCmd.Calls(), DeepEquals, [][]string{
+ {"systemctl", "stop", "snapd.socket"},
+ {"systemctl", "is-failed", "snapd.socket", "snapd.service"},
+ {"systemctl", "reset-failed", "snapd.socket", "snapd.service"},
+ {"systemctl", "restart", "snapd.socket"},
+ })
+}
+
+func (r *failureSuite) TestCallPrevSnapdFromSnapRestartSnapdFallback(c *C) {
+ defer failure.MockWaitTimes(1*time.Millisecond, 1*time.Millisecond)()
+
+ origArgs := os.Args
+ defer func() { os.Args = origArgs }()
+
+ writeSeqFile(c, "snapd", snap.R(123), []*snap.SideInfo{
+ {Revision: snap.R(99)},
+ {Revision: snap.R(100)},
+ {Revision: snap.R(123)},
+ })
+
+ // mock snapd command from 'previous' revision
+ snapdCmd := testutil.MockCommand(c, filepath.Join(dirs.SnapMountDir, "snapd", "100", "/usr/lib/snapd/snapd"),
+ `test "$SNAPD_REVERT_TO_REV" = "100"`)
+ defer snapdCmd.Restore()
+
+ systemctlCmd := testutil.MockCommand(c, "systemctl", `
+if [ "$1" = restart ] && [ "$2" == snapd.socket ] ; then
+ exit 1
+fi
+`)
+ defer systemctlCmd.Restore()
+
+ os.Args = []string{"snap-failure", "snapd"}
+ err := failure.Run()
+ c.Check(err, IsNil)
+ c.Check(r.Stderr(), HasLen, 0)
+
+ c.Check(snapdCmd.Calls(), DeepEquals, [][]string{
+ {"snapd"},
+ })
+ c.Check(systemctlCmd.Calls(), DeepEquals, [][]string{
+ {"systemctl", "stop", "snapd.socket"},
+ {"systemctl", "is-failed", "snapd.socket", "snapd.service"},
+ {"systemctl", "reset-failed", "snapd.socket", "snapd.service"},
+ {"systemctl", "restart", "snapd.socket"},
+ {"systemctl", "restart", "snapd.service"},
+ })
+}
+
+func (r *failureSuite) TestCallPrevSnapdFromSnapBackToFullyActive(c *C) {
+ defer failure.MockWaitTimes(1*time.Millisecond, 0)()
+
+ origArgs := os.Args
+ defer func() { os.Args = origArgs }()
+
+ writeSeqFile(c, "snapd", snap.R(123), []*snap.SideInfo{
+ {Revision: snap.R(99)},
+ {Revision: snap.R(100)},
+ {Revision: snap.R(123)},
+ })
+
+ // mock snapd command from 'previous' revision
+ snapdCmd := testutil.MockCommand(c, filepath.Join(dirs.SnapMountDir, "snapd", "100", "/usr/lib/snapd/snapd"),
+ `test "$SNAPD_REVERT_TO_REV" = "100"`)
+ defer snapdCmd.Restore()
+
+ systemctlCmd := testutil.MockCommand(c, "systemctl", `
+if [ "$1" = is-failed ] ; then
+ exit 1
+fi
+`)
+ defer systemctlCmd.Restore()
+
+ // mock the sockets re-appearing
+ err := os.MkdirAll(filepath.Dir(dirs.SnapdSocket), 0755)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(dirs.SnapdSocket, nil, 0755)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(dirs.SnapSocket, nil, 0755)
+ c.Assert(err, IsNil)
+
+ os.Args = []string{"snap-failure", "snapd"}
+ err = failure.Run()
+ c.Check(err, IsNil)
+ c.Check(r.Stderr(), HasLen, 0)
+
+ c.Check(snapdCmd.Calls(), DeepEquals, [][]string{
+ {"snapd"},
+ })
+ c.Check(systemctlCmd.Calls(), DeepEquals, [][]string{
+ {"systemctl", "stop", "snapd.socket"},
+ {"systemctl", "is-failed", "snapd.socket", "snapd.service"},
+ {"systemctl", "is-active", "snapd.socket", "snapd.service"},
+ })
+}
+
+func (r *failureSuite) TestCallPrevSnapdFromSnapBackActiveNoSockets(c *C) {
+ defer failure.MockWaitTimes(1*time.Millisecond, 0)()
+
+ origArgs := os.Args
+ defer func() { os.Args = origArgs }()
+
+ writeSeqFile(c, "snapd", snap.R(123), []*snap.SideInfo{
+ {Revision: snap.R(99)},
+ {Revision: snap.R(100)},
+ {Revision: snap.R(123)},
+ })
+
+ // mock snapd command from 'previous' revision
+ snapdCmd := testutil.MockCommand(c, filepath.Join(dirs.SnapMountDir, "snapd", "100", "/usr/lib/snapd/snapd"),
+ `test "$SNAPD_REVERT_TO_REV" = "100"`)
+ defer snapdCmd.Restore()
+
+ systemctlCmd := testutil.MockCommand(c, "systemctl", `
+if [ "$1" = is-failed ] ; then
+ exit 1
+fi
+`)
+ defer systemctlCmd.Restore()
+
+ // no sockets
+
+ os.Args = []string{"snap-failure", "snapd"}
+ err := failure.Run()
+ c.Check(err, IsNil)
+ c.Check(r.Stderr(), HasLen, 0)
+
+ c.Check(snapdCmd.Calls(), DeepEquals, [][]string{
+ {"snapd"},
+ })
+ c.Check(systemctlCmd.Calls(), DeepEquals, [][]string{
+ {"systemctl", "stop", "snapd.socket"},
+ {"systemctl", "is-failed", "snapd.socket", "snapd.service"},
+ {"systemctl", "is-active", "snapd.socket", "snapd.service"},
+ {"systemctl", "is-active", "snapd.socket", "snapd.service"},
+ {"systemctl", "is-active", "snapd.socket", "snapd.service"},
+ {"systemctl", "is-active", "snapd.socket", "snapd.service"},
+ {"systemctl", "is-active", "snapd.socket", "snapd.service"},
+ {"systemctl", "reset-failed", "snapd.socket", "snapd.service"},
+ {"systemctl", "restart", "snapd.socket"},
+ })
+}
+
+func (r *failureSuite) TestCallPrevSnapdFromCore(c *C) {
+ origArgs := os.Args
+ defer func() { os.Args = origArgs }()
+
+ // only one entry in sequence
+ writeSeqFile(c, "snapd", snap.R(123), []*snap.SideInfo{
+ {Revision: snap.R(123)},
+ })
+
+ // mock snapd in the core snap
+ snapdCmd := testutil.MockCommand(c, filepath.Join(dirs.SnapMountDir, "core", "current", "/usr/lib/snapd/snapd"),
+ `test "$SNAPD_REVERT_TO_REV" = "0"`)
+ defer snapdCmd.Restore()
+
+ systemctlCmd := testutil.MockCommand(c, "systemctl", "")
+ defer systemctlCmd.Restore()
+
+ os.Args = []string{"snap-failure", "snapd"}
+ err := failure.Run()
+ c.Check(err, IsNil)
+ c.Check(r.Stderr(), HasLen, 0)
+
+ c.Check(snapdCmd.Calls(), DeepEquals, [][]string{
+ {"snapd"},
+ })
+ c.Check(systemctlCmd.Calls(), DeepEquals, [][]string{
+ {"systemctl", "stop", "snapd.socket"},
+ {"systemctl", "is-failed", "snapd.socket", "snapd.service"},
+ {"systemctl", "reset-failed", "snapd.socket", "snapd.service"},
+ {"systemctl", "restart", "snapd.socket"},
+ })
+}
+
+func (r *failureSuite) TestCallPrevSnapdFail(c *C) {
+ origArgs := os.Args
+ defer func() { os.Args = origArgs }()
+
+ writeSeqFile(c, "snapd", snap.R(123), []*snap.SideInfo{
+ {Revision: snap.R(100)},
+ {Revision: snap.R(123)},
+ })
+
+ // mock snapd in the core snap
+ snapdCmd := testutil.MockCommand(c, filepath.Join(dirs.SnapMountDir, "snapd", "100", "/usr/lib/snapd/snapd"),
+ `exit 2`)
+ defer snapdCmd.Restore()
+
+ systemctlCmd := testutil.MockCommand(c, "systemctl", "")
+ defer systemctlCmd.Restore()
+
+ os.Args = []string{"snap-failure", "snapd"}
+ err := failure.Run()
+ c.Check(err, ErrorMatches, "snapd failed: exit status 2")
+ c.Check(r.Stderr(), HasLen, 0)
+
+ c.Check(snapdCmd.Calls(), DeepEquals, [][]string{
+ {"snapd"},
+ })
+ c.Check(systemctlCmd.Calls(), DeepEquals, [][]string{
+ {"systemctl", "stop", "snapd.socket"},
+ })
+}
+
+func (r *failureSuite) TestGarbageSeq(c *C) {
+ origArgs := os.Args
+ defer func() { os.Args = origArgs }()
+
+ seqPath := filepath.Join(dirs.SnapSeqDir, "snapd.json")
+ err := os.MkdirAll(dirs.SnapSeqDir, 0755)
+ c.Assert(err, IsNil)
+
+ err = ioutil.WriteFile(seqPath, []byte("this is garbage"), 0644)
+ c.Assert(err, IsNil)
+
+ snapdCmd := testutil.MockCommand(c, filepath.Join(dirs.SnapMountDir, "snapd", "100", "/usr/lib/snapd/snapd"),
+ `exit 99`)
+ defer snapdCmd.Restore()
+
+ systemctlCmd := testutil.MockCommand(c, "systemctl", "exit 98")
+ defer systemctlCmd.Restore()
+
+ os.Args = []string{"snap-failure", "snapd"}
+ err = failure.Run()
+ c.Check(err, ErrorMatches, `cannot parse "snapd.json" sequence file: invalid .*`)
+ c.Check(r.Stderr(), HasLen, 0)
+
+ c.Check(snapdCmd.Calls(), HasLen, 0)
+ c.Check(systemctlCmd.Calls(), HasLen, 0)
+}
+
+func (r *failureSuite) TestBadSeq(c *C) {
+ origArgs := os.Args
+ defer func() { os.Args = origArgs }()
+
+ writeSeqFile(c, "snapd", snap.R(123), []*snap.SideInfo{
+ {Revision: snap.R(100)},
+ // current not in sequence
+ })
+
+ snapdCmd := testutil.MockCommand(c, filepath.Join(dirs.SnapMountDir, "snapd", "100", "/usr/lib/snapd/snapd"), "")
+ defer snapdCmd.Restore()
+ systemctlCmd := testutil.MockCommand(c, "systemctl", "")
+ defer systemctlCmd.Restore()
+
+ os.Args = []string{"snap-failure", "snapd"}
+ err := failure.Run()
+ c.Check(err, ErrorMatches, "internal error: current 123 not found in sequence: .*Revision:100.*")
+ c.Check(r.Stderr(), HasLen, 0)
+
+ c.Check(snapdCmd.Calls(), HasLen, 0)
+ c.Check(systemctlCmd.Calls(), HasLen, 0)
+}
+
+func (r *failureSuite) TestSnapdOutputPassthrough(c *C) {
+ origArgs := os.Args
+ defer func() { os.Args = origArgs }()
+
+ writeSeqFile(c, "snapd", snap.R(123), []*snap.SideInfo{
+ {Revision: snap.R(100)},
+ {Revision: snap.R(123)},
+ })
+
+ snapdCmd := testutil.MockCommand(c, filepath.Join(dirs.SnapMountDir, "snapd", "100", "/usr/lib/snapd/snapd"), `
+echo 'stderr: hello from snapd' >&2
+echo 'stdout: hello from snapd'
+exit 123
+`)
+ defer snapdCmd.Restore()
+ systemctlCmd := testutil.MockCommand(c, "systemctl", "")
+ defer systemctlCmd.Restore()
+
+ os.Args = []string{"snap-failure", "snapd"}
+ err := failure.Run()
+ c.Check(err, ErrorMatches, "snapd failed: exit status 123")
+ c.Check(r.Stderr(), Equals, "stderr: hello from snapd\n")
+ c.Check(r.Stdout(), Equals, "stdout: hello from snapd\n")
+
+ c.Check(snapdCmd.Calls(), HasLen, 1)
+ c.Check(systemctlCmd.Calls(), DeepEquals, [][]string{
+ {"systemctl", "stop", "snapd.socket"},
+ })
+}
+
+func (r *failureSuite) TestStickySnapdSocket(c *C) {
+ origArgs := os.Args
+ defer func() { os.Args = origArgs }()
+
+ writeSeqFile(c, "snapd", snap.R(123), []*snap.SideInfo{
+ {Revision: snap.R(100)},
+ {Revision: snap.R(123)},
+ })
+
+ err := os.MkdirAll(filepath.Dir(dirs.SnapdSocket), 0755)
+ c.Assert(err, IsNil)
+ err = ioutil.WriteFile(dirs.SnapdSocket, []byte{}, 0755)
+ c.Assert(err, IsNil)
+
+ // mock snapd in the core snap
+ snapdCmd := testutil.MockCommand(c, filepath.Join(dirs.SnapMountDir, "snapd", "100", "/usr/lib/snapd/snapd"),
+ `test "$SNAPD_REVERT_TO_REV" = "100"`)
+ defer snapdCmd.Restore()
+
+ systemctlCmd := testutil.MockCommand(c, "systemctl", "")
+ defer systemctlCmd.Restore()
+
+ os.Args = []string{"snap-failure", "snapd"}
+ err = failure.Run()
+ c.Check(err, IsNil)
+ c.Check(r.Stderr(), HasLen, 0)
+
+ c.Check(snapdCmd.Calls(), DeepEquals, [][]string{
+ {"snapd"},
+ })
+ c.Check(systemctlCmd.Calls(), DeepEquals, [][]string{
+ {"systemctl", "stop", "snapd.socket"},
+ {"systemctl", "is-failed", "snapd.socket", "snapd.service"},
+ {"systemctl", "reset-failed", "snapd.socket", "snapd.service"},
+ {"systemctl", "restart", "snapd.socket"},
+ })
+
+ // make sure the socket file was deleted
+ c.Assert(osutil.FileExists(dirs.SnapdSocket), Equals, false)
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-failure/export_test.go snapd-2.45.1+18.04/cmd/snap-failure/export_test.go
--- snapd-2.42.1+18.04/cmd/snap-failure/export_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-failure/export_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -19,7 +19,20 @@
package main
+import "time"
+
var (
Run = run
ParseArgs = parseArgs
)
+
+func MockWaitTimes(sampleForActive, restartSnapdCoolOff time.Duration) (restore func()) {
+ oldSampleForActive := sampleForActiveInterval
+ oldRestartSnapdCoolOff := restartSnapdCoolOffWait
+ sampleForActiveInterval = sampleForActive
+ restartSnapdCoolOffWait = restartSnapdCoolOff
+ return func() {
+ sampleForActiveInterval = oldSampleForActive
+ restartSnapdCoolOffWait = oldRestartSnapdCoolOff
+ }
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-failure/main.go snapd-2.45.1+18.04/cmd/snap-failure/main.go
--- snapd-2.42.1+18.04/cmd/snap-failure/main.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-failure/main.go 2020-06-05 13:13:49.000000000 +0000
@@ -32,6 +32,7 @@
var (
Stderr io.Writer = os.Stderr
+ Stdout io.Writer = os.Stdout
opts struct{}
parser *flags.Parser = flags.NewParser(&opts, flags.HelpFlag|flags.PassDoubleDash|flags.PassAfterNonOption)
diff -Nru snapd-2.42.1+18.04/cmd/snap-failure/main_test.go snapd-2.45.1+18.04/cmd/snap-failure/main_test.go
--- snapd-2.42.1+18.04/cmd/snap-failure/main_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-failure/main_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -27,6 +27,7 @@
failure "github.com/snapcore/snapd/cmd/snap-failure"
"github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/testutil"
)
@@ -39,24 +40,40 @@
rootdir string
stderr *bytes.Buffer
+ stdout *bytes.Buffer
+ log *bytes.Buffer
}
func (r *failureSuite) SetUpTest(c *C) {
r.stderr = bytes.NewBuffer(nil)
+ r.stdout = bytes.NewBuffer(nil)
oldStderr := failure.Stderr
- r.AddCleanup(func() { failure.Stderr = oldStderr })
+ oldStdout := failure.Stdout
+ r.AddCleanup(func() {
+ failure.Stderr = oldStderr
+ failure.Stdout = oldStdout
+ })
failure.Stderr = r.stderr
+ failure.Stdout = r.stdout
r.rootdir = c.MkDir()
dirs.SetRootDir(r.rootdir)
r.AddCleanup(func() { dirs.SetRootDir("/") })
+
+ log, restore := logger.MockLogger()
+ r.log = log
+ r.AddCleanup(restore)
}
func (r *failureSuite) Stderr() string {
return r.stderr.String()
}
+func (r *failureSuite) Stdout() string {
+ return r.stdout.String()
+}
+
var _ = Suite(&failureSuite{})
func (r *failureSuite) TestUnknownArg(c *C) {
diff -Nru snapd-2.42.1+18.04/cmd/snapinfo.go snapd-2.45.1+18.04/cmd/snapinfo.go
--- snapd-2.42.1+18.04/cmd/snapinfo.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snapinfo.go 2020-06-05 13:13:49.000000000 +0000
@@ -72,12 +72,13 @@
Contact: snapInfo.Contact,
Title: snapInfo.Title(),
License: snapInfo.License,
- Screenshots: snapInfo.Media.Screenshots(),
Media: snapInfo.Media,
Prices: snapInfo.Prices,
Channels: snapInfo.Channels,
Tracks: snapInfo.Tracks,
CommonIDs: snapInfo.CommonIDs,
+ Website: snapInfo.Website,
+ StoreURL: snapInfo.StoreURL,
}
return result, err
diff -Nru snapd-2.42.1+18.04/cmd/snapinfo_test.go snapd-2.45.1+18.04/cmd/snapinfo_test.go
--- snapd-2.42.1+18.04/cmd/snapinfo_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snapinfo_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -24,8 +24,19 @@
"github.com/snapcore/snapd/client"
"github.com/snapcore/snapd/cmd"
+ "github.com/snapcore/snapd/snap"
)
+func (*cmdSuite) TestC2S(c *check.C) {
+ // TODO: add moar fields!
+ si := &snap.Info{
+ Website: "http://example.com/xyzzy",
+ }
+ ci, err := cmd.ClientSnapFromSnapInfo(si)
+ c.Check(err, check.IsNil)
+ c.Check(ci.Website, check.Equals, si.Website)
+}
+
func (*cmdSuite) TestAppStatusNotes(c *check.C) {
ai := client.AppInfo{}
c.Check(cmd.ClientAppInfoNotes(&ai), check.Equals, "-")
diff -Nru snapd-2.42.1+18.04/cmd/snap-mgmt/snap-mgmt.sh.in snapd-2.45.1+18.04/cmd/snap-mgmt/snap-mgmt.sh.in
--- snapd-2.42.1+18.04/cmd/snap-mgmt/snap-mgmt.sh.in 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-mgmt/snap-mgmt.sh.in 2020-06-05 13:13:49.000000000 +0000
@@ -40,14 +40,16 @@
if [ "$distribution" = "ubuntu-14.04" ]; then
# snap.mount.service is a trusty thing
systemctl_stop snap.mount.service
- else
- # undo any bind mount to ${SNAP_MOUNT_DIR} that resulted from LP:#1668659
- # (that bug can't happen in trusty -- and doing this would mess up snap.mount.service there)
- if grep -q "${SNAP_MOUNT_DIR} ${SNAP_MOUNT_DIR}" /proc/self/mountinfo; then
- umount -l "${SNAP_MOUNT_DIR}" || true
- fi
fi
+ # Undo any bind mounts to ${SNAP_MOUNT_DIR} or /var/snap done by parallel
+ # installs or LP:#1668659
+ for mp in "$SNAP_MOUNT_DIR" /var/snap; do
+ if grep -q " $mp $mp" /proc/self/mountinfo; then
+ umount -l "$mp" || true
+ fi
+ done
+
units=$(systemctl list-unit-files --no-legend --full | grep -vF snap.mount.service || true)
# *.snap mount points
mounts=$(echo "$units" | grep "^${SNAP_UNIT_PREFIX}[-.].*\\.mount" | cut -f1 -d ' ')
@@ -104,8 +106,8 @@
if [ -d /etc/dbus-1/system.d ]; then
find /etc/dbus-1/system.d -name "snap.${snap}.*.conf" -execdir rm -f "{}" \;
fi
- # timer files
- find /etc/systemd/system -name "snap.${snap}.*.timer" | while read -r f; do
+ # timer and socket units
+ find /etc/systemd/system -name "snap.${snap}.*.timer" -o -name "snap.${snap}.*.socket" | while read -r f; do
systemctl_stop "$(basename "$f")"
rm -f "$f"
done
diff -Nru snapd-2.42.1+18.04/cmd/snap-preseed/export_test.go snapd-2.45.1+18.04/cmd/snap-preseed/export_test.go
--- snapd-2.42.1+18.04/cmd/snap-preseed/export_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-preseed/export_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,62 @@
+// -*- 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 (
+ "github.com/snapcore/snapd/seed"
+)
+
+var (
+ Run = run
+ SystemSnapFromSeed = systemSnapFromSeed
+ CheckTargetSnapdVersion = checkTargetSnapdVersion
+)
+
+func MockOsGetuid(f func() int) (restore func()) {
+ oldOsGetuid := osGetuid
+ osGetuid = f
+ return func() { osGetuid = oldOsGetuid }
+}
+
+func MockSyscallChroot(f func(string) error) (restore func()) {
+ oldSyscallChroot := syscallChroot
+ syscallChroot = f
+ return func() { syscallChroot = oldSyscallChroot }
+}
+
+func MockSnapdMountPath(path string) (restore func()) {
+ oldMountPath := snapdMountPath
+ snapdMountPath = path
+ return func() { snapdMountPath = oldMountPath }
+}
+
+func MockSystemSnapFromSeed(f func(rootDir string) (string, error)) (restore func()) {
+ oldSystemSnapFromSeed := systemSnapFromSeed
+ systemSnapFromSeed = f
+ return func() { systemSnapFromSeed = oldSystemSnapFromSeed }
+}
+
+func MockSeedOpen(f func(rootDir, label string) (seed.Seed, error)) (restore func()) {
+ oldSeedOpen := seedOpen
+ seedOpen = f
+ return func() {
+ seedOpen = oldSeedOpen
+ }
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-preseed/main.go snapd-2.45.1+18.04/cmd/snap-preseed/main.go
--- snapd-2.42.1+18.04/cmd/snap-preseed/main.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-preseed/main.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,106 @@
+// -*- 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"
+ "io"
+ "os"
+
+ "github.com/jessevdk/go-flags"
+)
+
+const (
+ shortHelp = "Prerun the first boot seeding of snaps in an image filesystem chroot with a snapd seed."
+ longHelp = `
+The snap-preseed command takes a directory containing an image, including seed
+snaps (at /var/lib/snapd/seed), and runs through the snapd first-boot process
+up to hook execution. No boot actions unrelated to snapd are performed.
+It creates systemd units for seeded snaps, makes any connections, and generates
+security profiles. The image is updated and consequently optimised to reduce
+first-boot startup time`
+)
+
+type options struct {
+ Reset bool `long:"reset"`
+}
+
+var (
+ osGetuid = os.Getuid
+ Stdout io.Writer = os.Stdout
+ Stderr io.Writer = os.Stderr
+
+ opts options
+)
+
+func Parser() *flags.Parser {
+ opts = options{}
+ parser := flags.NewParser(&opts, flags.HelpFlag|flags.PassDoubleDash|flags.PassAfterNonOption)
+ parser.ShortDescription = shortHelp
+ parser.LongDescription = longHelp
+ return parser
+}
+
+func main() {
+ parser := Parser()
+ if err := run(parser, os.Args[1:]); err != nil {
+ fmt.Fprintf(Stderr, "error: %v\n", err)
+ os.Exit(1)
+ }
+}
+
+func run(parser *flags.Parser, args []string) error {
+ if osGetuid() != 0 {
+ return fmt.Errorf("must be run as root")
+ }
+
+ rest, err := parser.ParseArgs(args)
+ if err != nil {
+ return err
+ }
+
+ if len(rest) == 0 {
+ return fmt.Errorf("need chroot path as argument")
+ }
+
+ chrootDir := rest[0]
+ // safety check
+ if chrootDir == "/" {
+ return fmt.Errorf("cannot run snap-preseed against /")
+ }
+
+ if opts.Reset {
+ return resetPreseededChroot(chrootDir)
+ }
+
+ if err := checkChroot(chrootDir); err != nil {
+ return err
+ }
+
+ cleanup, err := prepareChroot(chrootDir)
+ if err != nil {
+ return err
+ }
+
+ // executing inside the chroot
+ err = runPreseedMode(chrootDir)
+ cleanup()
+ return err
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-preseed/main_test.go snapd-2.45.1+18.04/cmd/snap-preseed/main_test.go
--- snapd-2.42.1+18.04/cmd/snap-preseed/main_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-preseed/main_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,473 @@
+// -*- 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"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ "testing"
+
+ "github.com/jessevdk/go-flags"
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/asserts"
+ "github.com/snapcore/snapd/asserts/assertstest"
+ "github.com/snapcore/snapd/cmd/snap-preseed"
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/osutil"
+ apparmor_sandbox "github.com/snapcore/snapd/sandbox/apparmor"
+ "github.com/snapcore/snapd/seed"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/testutil"
+ "github.com/snapcore/snapd/timings"
+)
+
+func Test(t *testing.T) { TestingT(t) }
+
+var _ = Suite(&startPreseedSuite{})
+
+type startPreseedSuite struct {
+ testutil.BaseTest
+}
+
+func (s *startPreseedSuite) SetUpTest(c *C) {
+ s.BaseTest.SetUpTest(c)
+}
+
+func (s *startPreseedSuite) TearDownTest(c *C) {
+ s.BaseTest.TearDownTest(c)
+ dirs.SetRootDir("")
+}
+
+func testParser(c *C) *flags.Parser {
+ parser := main.Parser()
+ _, err := parser.ParseArgs([]string{})
+ c.Assert(err, IsNil)
+ return parser
+}
+
+func mockChrootDirs(c *C, rootDir string) {
+ c.Assert(os.MkdirAll(filepath.Join(rootDir, "/sys/kernel/security/apparmor"), 0755), IsNil)
+ c.Assert(os.MkdirAll(filepath.Join(rootDir, "/proc/self"), 0755), IsNil)
+ c.Assert(os.MkdirAll(filepath.Join(rootDir, "/dev/mem"), 0755), IsNil)
+}
+
+func (s *startPreseedSuite) TestRequiresRoot(c *C) {
+ restore := main.MockOsGetuid(func() int {
+ return 1000
+ })
+ defer restore()
+
+ parser := testParser(c)
+ c.Check(main.Run(parser, []string{"/"}), ErrorMatches, `must be run as root`)
+}
+
+func (s *startPreseedSuite) TestMissingArg(c *C) {
+ restore := main.MockOsGetuid(func() int {
+ return 0
+ })
+ defer restore()
+
+ parser := testParser(c)
+ c.Check(main.Run(parser, nil), ErrorMatches, `need chroot path as argument`)
+}
+
+func (s *startPreseedSuite) TestChrootDoesntExist(c *C) {
+ restore := main.MockOsGetuid(func() int { return 0 })
+ defer restore()
+
+ parser := testParser(c)
+ c.Check(main.Run(parser, []string{"/non-existing-dir"}), ErrorMatches, `cannot verify "/non-existing-dir": is not a directory`)
+}
+
+func (s *startPreseedSuite) TestChrootValidationUnhappy(c *C) {
+ restore := main.MockOsGetuid(func() int { return 0 })
+ defer restore()
+
+ tmpDir := c.MkDir()
+
+ parser := testParser(c)
+ c.Check(main.Run(parser, []string{tmpDir}), ErrorMatches, `cannot pre-seed without access to ".*sys/kernel/security/apparmor"`)
+}
+
+func (s *startPreseedSuite) TestChrootValidationAlreadyPreseeded(c *C) {
+ restore := main.MockOsGetuid(func() int { return 0 })
+ defer restore()
+
+ tmpDir := c.MkDir()
+ snapdDir := filepath.Dir(dirs.SnapStateFile)
+ c.Assert(os.MkdirAll(filepath.Join(tmpDir, snapdDir), 0755), IsNil)
+ c.Assert(ioutil.WriteFile(filepath.Join(tmpDir, dirs.SnapStateFile), nil, os.ModePerm), IsNil)
+
+ parser := testParser(c)
+ c.Check(main.Run(parser, []string{tmpDir}), ErrorMatches, fmt.Sprintf("the system at %q appears to be preseeded, pass --reset flag to clean it up", tmpDir))
+}
+
+func (s *startPreseedSuite) TestChrootFailure(c *C) {
+ restoreOsGuid := main.MockOsGetuid(func() int { return 0 })
+ defer restoreOsGuid()
+
+ restoreSyscallChroot := main.MockSyscallChroot(func(path string) error {
+ return fmt.Errorf("FAIL: %s", path)
+ })
+ defer restoreSyscallChroot()
+
+ tmpDir := c.MkDir()
+ mockChrootDirs(c, tmpDir)
+
+ parser := testParser(c)
+ c.Check(main.Run(parser, []string{tmpDir}), ErrorMatches, fmt.Sprintf("cannot chroot into %s: FAIL: %s", tmpDir, tmpDir))
+}
+
+func (s *startPreseedSuite) TestRunPreseedHappy(c *C) {
+ tmpDir := c.MkDir()
+ dirs.SetRootDir(tmpDir)
+ mockChrootDirs(c, tmpDir)
+
+ restoreOsGuid := main.MockOsGetuid(func() int { return 0 })
+ defer restoreOsGuid()
+
+ restoreSyscallChroot := main.MockSyscallChroot(func(path string) error { return nil })
+ defer restoreSyscallChroot()
+
+ mockMountCmd := testutil.MockCommand(c, "mount", "")
+ defer mockMountCmd.Restore()
+
+ mockUmountCmd := testutil.MockCommand(c, "umount", "")
+ defer mockUmountCmd.Restore()
+
+ targetSnapdRoot := filepath.Join(tmpDir, "target-core-mounted-here")
+ restoreMountPath := main.MockSnapdMountPath(targetSnapdRoot)
+ defer restoreMountPath()
+
+ restoreSystemSnapFromSeed := main.MockSystemSnapFromSeed(func(string) (string, error) { return "/a/core.snap", nil })
+ defer restoreSystemSnapFromSeed()
+
+ c.Assert(os.MkdirAll(filepath.Join(targetSnapdRoot, "usr/lib/snapd/"), 0755), IsNil)
+ mockTargetSnapd := testutil.MockCommand(c, filepath.Join(targetSnapdRoot, "usr/lib/snapd/snapd"), `#!/bin/sh
+ if [ "$SNAPD_PRESEED" != "1" ]; then
+ exit 1
+ fi
+`)
+ defer mockTargetSnapd.Restore()
+
+ infoFile := filepath.Join(filepath.Join(targetSnapdRoot, dirs.CoreLibExecDir, "info"))
+ c.Assert(ioutil.WriteFile(infoFile, []byte("VERSION=2.44.0"), 0644), IsNil)
+
+ parser := testParser(c)
+ c.Check(main.Run(parser, []string{tmpDir}), IsNil)
+
+ c.Assert(mockMountCmd.Calls(), HasLen, 1)
+ // note, tmpDir, targetSnapdRoot are contactenated again cause we're not really chrooting in the test
+ // and mocking dirs.RootDir
+ c.Check(mockMountCmd.Calls()[0], DeepEquals, []string{"mount", "-t", "squashfs", "/a/core.snap", filepath.Join(tmpDir, targetSnapdRoot)})
+
+ c.Assert(mockTargetSnapd.Calls(), HasLen, 1)
+ c.Check(mockTargetSnapd.Calls()[0], DeepEquals, []string{"snapd"})
+}
+
+type Fake16Seed struct {
+ AssertsModel *asserts.Model
+ Essential []*seed.Snap
+ LoadMetaErr error
+ LoadAssertionsErr error
+ UsesSnapd bool
+}
+
+// Fake implementation of seed.Seed interface
+
+func mockClassicModel() *asserts.Model {
+ headers := map[string]interface{}{
+ "type": "model",
+ "authority-id": "brand",
+ "series": "16",
+ "brand-id": "brand",
+ "model": "classicbaz-3000",
+ "classic": "true",
+ "timestamp": "2018-01-01T08:00:00+00:00",
+ }
+ return assertstest.FakeAssertion(headers, nil).(*asserts.Model)
+}
+
+func (fs *Fake16Seed) LoadAssertions(db asserts.RODatabase, commitTo func(*asserts.Batch) error) error {
+ return fs.LoadAssertionsErr
+}
+
+func (fs *Fake16Seed) Model() (*asserts.Model, error) {
+ return fs.AssertsModel, nil
+}
+
+func (fs *Fake16Seed) Brand() (*asserts.Account, error) {
+ headers := map[string]interface{}{
+ "type": "account",
+ "account-id": "brand",
+ "display-name": "fake brand",
+ "username": "brand",
+ "timestamp": "2018-01-01T08:00:00+00:00",
+ }
+ return assertstest.FakeAssertion(headers, nil).(*asserts.Account), nil
+}
+
+func (fs *Fake16Seed) LoadMeta(tm timings.Measurer) error {
+ return fs.LoadMetaErr
+}
+
+func (fs *Fake16Seed) UsesSnapdSnap() bool {
+ return fs.UsesSnapd
+}
+
+func (fs *Fake16Seed) EssentialSnaps() []*seed.Snap {
+ return fs.Essential
+}
+
+func (fs *Fake16Seed) ModeSnaps(mode string) ([]*seed.Snap, error) {
+ return nil, nil
+}
+
+func (s *startPreseedSuite) TestSystemSnapFromSeed(c *C) {
+ tmpDir := c.MkDir()
+
+ restore := main.MockSeedOpen(func(rootDir, label string) (seed.Seed, error) {
+ return &Fake16Seed{
+ AssertsModel: mockClassicModel(),
+ Essential: []*seed.Snap{{Path: "/some/path/core", SideInfo: &snap.SideInfo{RealName: "core"}}},
+ }, nil
+ })
+ defer restore()
+
+ path, err := main.SystemSnapFromSeed(tmpDir)
+ c.Assert(err, IsNil)
+ c.Check(path, Equals, "/some/path/core")
+}
+
+func (s *startPreseedSuite) TestSystemSnapFromSnapdSeed(c *C) {
+ tmpDir := c.MkDir()
+
+ restore := main.MockSeedOpen(func(rootDir, label string) (seed.Seed, error) {
+ return &Fake16Seed{
+ AssertsModel: mockClassicModel(),
+ Essential: []*seed.Snap{{Path: "/some/path/snapd.snap", SideInfo: &snap.SideInfo{RealName: "snapd"}}},
+ UsesSnapd: true,
+ }, nil
+ })
+ defer restore()
+
+ path, err := main.SystemSnapFromSeed(tmpDir)
+ c.Assert(err, IsNil)
+ c.Check(path, Equals, "/some/path/snapd.snap")
+}
+
+func (s *startPreseedSuite) TestSystemSnapFromSeedOpenError(c *C) {
+ tmpDir := c.MkDir()
+
+ restore := main.MockSeedOpen(func(rootDir, label string) (seed.Seed, error) { return nil, fmt.Errorf("fail") })
+ defer restore()
+
+ _, err := main.SystemSnapFromSeed(tmpDir)
+ c.Assert(err, ErrorMatches, "fail")
+}
+
+func (s *startPreseedSuite) TestSystemSnapFromSeedErrors(c *C) {
+ tmpDir := c.MkDir()
+
+ fakeSeed := &Fake16Seed{}
+ fakeSeed.AssertsModel = mockClassicModel()
+
+ restore := main.MockSeedOpen(func(rootDir, label string) (seed.Seed, error) { return fakeSeed, nil })
+ defer restore()
+
+ fakeSeed.Essential = []*seed.Snap{{Path: "", SideInfo: &snap.SideInfo{RealName: "core"}}}
+ _, err := main.SystemSnapFromSeed(tmpDir)
+ c.Assert(err, ErrorMatches, "core snap not found")
+
+ fakeSeed.Essential = []*seed.Snap{{Path: "/some/path", SideInfo: &snap.SideInfo{RealName: "foosnap"}}}
+ _, err = main.SystemSnapFromSeed(tmpDir)
+ c.Assert(err, ErrorMatches, "core snap not found")
+
+ fakeSeed.LoadMetaErr = fmt.Errorf("load meta failed")
+ _, err = main.SystemSnapFromSeed(tmpDir)
+ c.Assert(err, ErrorMatches, "load meta failed")
+
+ fakeSeed.LoadMetaErr = nil
+ fakeSeed.LoadAssertionsErr = fmt.Errorf("load assertions failed")
+ _, err = main.SystemSnapFromSeed(tmpDir)
+ c.Assert(err, ErrorMatches, "load assertions failed")
+}
+
+func (s *startPreseedSuite) TestClassicRequired(c *C) {
+ tmpDir := c.MkDir()
+
+ headers := map[string]interface{}{
+ "type": "model",
+ "authority-id": "brand",
+ "series": "16",
+ "brand-id": "brand",
+ "model": "baz-3000",
+ "architecture": "armhf",
+ "gadget": "brand-gadget",
+ "kernel": "kernel",
+ "timestamp": "2018-01-01T08:00:00+00:00",
+ }
+
+ fakeSeed := &Fake16Seed{}
+ fakeSeed.AssertsModel = assertstest.FakeAssertion(headers, nil).(*asserts.Model)
+
+ restore := main.MockSeedOpen(func(rootDir, label string) (seed.Seed, error) { return fakeSeed, nil })
+ defer restore()
+
+ _, err := main.SystemSnapFromSeed(tmpDir)
+ c.Assert(err, ErrorMatches, "preseeding is only supported on classic systems")
+}
+
+func (s *startPreseedSuite) TestRunPreseedUnsupportedVersion(c *C) {
+ tmpDir := c.MkDir()
+ dirs.SetRootDir(tmpDir)
+ mockChrootDirs(c, tmpDir)
+
+ restoreOsGuid := main.MockOsGetuid(func() int { return 0 })
+ defer restoreOsGuid()
+
+ restoreSyscallChroot := main.MockSyscallChroot(func(path string) error { return nil })
+ defer restoreSyscallChroot()
+
+ mockMountCmd := testutil.MockCommand(c, "mount", "")
+ defer mockMountCmd.Restore()
+
+ targetSnapdRoot := filepath.Join(tmpDir, "target-core-mounted-here")
+ restoreMountPath := main.MockSnapdMountPath(targetSnapdRoot)
+ defer restoreMountPath()
+
+ restoreSystemSnapFromSeed := main.MockSystemSnapFromSeed(func(string) (string, error) { return "/a/core.snap", nil })
+ defer restoreSystemSnapFromSeed()
+
+ c.Assert(os.MkdirAll(filepath.Join(targetSnapdRoot, "usr/lib/snapd/"), 0755), IsNil)
+ mockTargetSnapd := testutil.MockCommand(c, filepath.Join(targetSnapdRoot, "usr/lib/snapd/snapd"), "")
+ defer mockTargetSnapd.Restore()
+
+ infoFile := filepath.Join(targetSnapdRoot, dirs.CoreLibExecDir, "info")
+ c.Assert(ioutil.WriteFile(infoFile, []byte("VERSION=2.43.0"), 0644), IsNil)
+
+ parser := testParser(c)
+ c.Check(main.Run(parser, []string{tmpDir}), ErrorMatches,
+ `snapd 2.43.0 from the target system does not support preseeding, the minimum required version is 2.43.3\+`)
+}
+
+func (s *startPreseedSuite) TestVersionCheckWithGitVer(c *C) {
+ tmpDir := c.MkDir()
+ dirs.SetRootDir(tmpDir)
+
+ infoFile := filepath.Join(tmpDir, "info")
+ c.Assert(ioutil.WriteFile(infoFile, []byte("VERSION=2.43.3+git123"), 0644), IsNil)
+
+ c.Check(main.CheckTargetSnapdVersion(infoFile), IsNil)
+}
+
+func (s *startPreseedSuite) TestRunPreseedAgainstFilesystemRoot(c *C) {
+ restore := main.MockOsGetuid(func() int { return 0 })
+ defer restore()
+
+ parser := testParser(c)
+ c.Assert(main.Run(parser, []string{"/"}), ErrorMatches, `cannot run snap-preseed against /`)
+}
+
+func (s *startPreseedSuite) TestReset(c *C) {
+ restore := main.MockOsGetuid(func() int { return 0 })
+ defer restore()
+
+ tmpDir := c.MkDir()
+
+ // mock some preseeding artifacts
+ artifacts := []struct {
+ path string
+ // if symlinkTarget is not empty, then a path -> symlinkTarget symlink
+ // will be created instead of a regular file.
+ symlinkTarget string
+ }{
+ {dirs.SnapStateFile, ""},
+ {dirs.SnapSystemKeyFile, ""},
+ {filepath.Join(dirs.SnapDesktopFilesDir, "foo.desktop"), ""},
+ {filepath.Join(dirs.SnapDesktopIconsDir, "foo.png"), ""},
+ {filepath.Join(dirs.SnapMountPolicyDir, "foo.fstab"), ""},
+ {filepath.Join(dirs.SnapBlobDir, "foo.snap"), ""},
+ {filepath.Join(dirs.SnapUdevRulesDir, "foo-snap.bar.rules"), ""},
+ {filepath.Join(dirs.SnapBusPolicyDir, "snap.foo.bar.conf"), ""},
+ {filepath.Join(dirs.SnapServicesDir, "snap.foo.service"), ""},
+ {filepath.Join(dirs.SnapServicesDir, "snap.foo.timer"), ""},
+ {filepath.Join(dirs.SnapServicesDir, "snap.foo.socket"), ""},
+ {filepath.Join(dirs.SnapServicesDir, "snap-foo.mount"), ""},
+ {filepath.Join(dirs.SnapServicesDir, "multi-user.target.wants", "snap-foo.mount"), ""},
+ {filepath.Join(dirs.SnapDataDir, "foo", "bar"), ""},
+ {filepath.Join(dirs.SnapCacheDir, "foocache", "bar"), ""},
+ {filepath.Join(apparmor_sandbox.CacheDir, "foo", "bar"), ""},
+ {filepath.Join(dirs.SnapAppArmorDir, "foo"), ""},
+ {filepath.Join(dirs.SnapAssertsDBDir, "foo"), ""},
+ {filepath.Join(dirs.FeaturesDir, "foo"), ""},
+ {filepath.Join(dirs.SnapDeviceDir, "foo-1", "bar"), ""},
+ {filepath.Join(dirs.SnapCookieDir, "foo"), ""},
+ {filepath.Join(dirs.SnapSeqDir, "foo.json"), ""},
+ {filepath.Join(dirs.SnapMountDir, "foo", "bin"), ""},
+ {filepath.Join(dirs.SnapSeccompDir, "foo.bin"), ""},
+ // bash-completion symlinks
+ {filepath.Join(dirs.CompletersDir, "foo.bar"), "/a/snapd/complete.sh"},
+ {filepath.Join(dirs.CompletersDir, "foo"), "foo.bar"},
+ }
+
+ for _, art := range artifacts {
+ fullPath := filepath.Join(tmpDir, art.path)
+ // create parent dir
+ c.Assert(os.MkdirAll(filepath.Dir(fullPath), 0755), IsNil)
+ if art.symlinkTarget != "" {
+ // note, symlinkTarget is not relative to tmpDir
+ c.Assert(os.Symlink(art.symlinkTarget, fullPath), IsNil)
+ } else {
+ c.Assert(ioutil.WriteFile(fullPath, nil, os.ModePerm), IsNil)
+ }
+ }
+
+ checkArtifacts := func(exists bool) {
+ for _, art := range artifacts {
+ fullPath := filepath.Join(tmpDir, art.path)
+ if art.symlinkTarget != "" {
+ c.Check(osutil.IsSymlink(fullPath), Equals, exists, Commentf("offending symlink: %s", fullPath))
+ } else {
+ c.Check(osutil.FileExists(fullPath), Equals, exists, Commentf("offending file: %s", fullPath))
+ }
+ }
+ }
+
+ // sanity
+ checkArtifacts(true)
+
+ snapdDir := filepath.Dir(dirs.SnapStateFile)
+ c.Assert(os.MkdirAll(filepath.Join(tmpDir, snapdDir), 0755), IsNil)
+ c.Assert(ioutil.WriteFile(filepath.Join(tmpDir, dirs.SnapStateFile), nil, os.ModePerm), IsNil)
+
+ parser := testParser(c)
+ c.Assert(main.Run(parser, []string{"--reset", tmpDir}), IsNil)
+
+ checkArtifacts(false)
+
+ // running reset again is ok
+ parser = testParser(c)
+ c.Assert(main.Run(parser, []string{"--reset", tmpDir}), IsNil)
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-preseed/preseed_linux.go snapd-2.45.1+18.04/cmd/snap-preseed/preseed_linux.go
--- snapd-2.42.1+18.04/cmd/snap-preseed/preseed_linux.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-preseed/preseed_linux.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,225 @@
+// -*- 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"
+ "os/exec"
+ "path/filepath"
+ "syscall"
+
+ "github.com/snapcore/snapd/cmd/cmdutil"
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/seed"
+ "github.com/snapcore/snapd/strutil"
+ "github.com/snapcore/snapd/timings"
+)
+
+var (
+ // snapdMountPath is where target core/snapd is going to be mounted in the target chroot
+ snapdMountPath = "/tmp/snapd-preseed"
+ syscallMount = syscall.Mount
+ syscallChroot = syscall.Chroot
+)
+
+// checkChroot does a basic sanity check of the target chroot environment, e.g. makes
+// sure critical virtual filesystems (such as proc) are mounted. This is not meant to
+// be exhaustive check, but one that prevents running the tool against a wrong directory
+// by an accident, which would lead to hard to understand errors from snapd in preseed
+// mode.
+func checkChroot(preseedChroot string) error {
+ exists, isDir, err := osutil.DirExists(preseedChroot)
+ if err != nil {
+ return fmt.Errorf("cannot verify %q: %v", preseedChroot, err)
+ }
+ if !exists || !isDir {
+ return fmt.Errorf("cannot verify %q: is not a directory", preseedChroot)
+ }
+
+ if osutil.FileExists(filepath.Join(preseedChroot, dirs.SnapStateFile)) {
+ return fmt.Errorf("the system at %q appears to be preseeded, pass --reset flag to clean it up", preseedChroot)
+ }
+
+ // sanity checks of the critical mountpoints inside chroot directory
+ for _, p := range []string{"/sys/kernel/security/apparmor", "/proc/self", "/dev/mem"} {
+ path := filepath.Join(preseedChroot, p)
+ if exists := osutil.FileExists(path); !exists {
+ return fmt.Errorf("cannot pre-seed without access to %q", path)
+ }
+ }
+
+ return nil
+}
+
+var seedOpen = seed.Open
+
+var systemSnapFromSeed = func(rootDir string) (string, error) {
+ seedDir := filepath.Join(dirs.SnapSeedDirUnder(rootDir))
+ seed, err := seedOpen(seedDir, "")
+ if err != nil {
+ return "", err
+ }
+
+ // load assertions into temporary database
+ if err := seed.LoadAssertions(nil, nil); err != nil {
+ return "", err
+ }
+
+ tm := timings.New(nil)
+ if err := seed.LoadMeta(tm); err != nil {
+ return "", err
+ }
+
+ model, err := seed.Model()
+ if err != nil {
+ return "", err
+ }
+
+ // TODO: implement preseeding for core.
+ if !model.Classic() {
+ return "", fmt.Errorf("preseeding is only supported on classic systems")
+ }
+
+ var required string
+ if seed.UsesSnapdSnap() {
+ required = "snapd"
+ } else {
+ required = "core"
+ }
+
+ var snapPath string
+ ess := seed.EssentialSnaps()
+ if len(ess) > 0 {
+ // core / snapd snap is the first essential snap.
+ if ess[0].SnapName() == required {
+ snapPath = ess[0].Path
+ }
+ }
+
+ if snapPath == "" {
+ return "", fmt.Errorf("%s snap not found", required)
+ }
+
+ return snapPath, nil
+}
+
+const snapdPreseedSupportVer = `2.43.3+`
+
+func checkTargetSnapdVersion(infoPath string) error {
+ ver, err := cmdutil.SnapdVersionFromInfoFile(infoPath)
+ if err != nil {
+ return err
+ }
+
+ res, err := strutil.VersionCompare(ver, snapdPreseedSupportVer)
+ if err != nil {
+ return err
+ }
+ if res < 0 {
+ return fmt.Errorf("snapd %s from the target system does not support preseeding, the minimum required version is %s",
+ ver, snapdPreseedSupportVer)
+ }
+ return nil
+}
+
+func prepareChroot(preseedChroot string) (func(), error) {
+ if err := syscallChroot(preseedChroot); err != nil {
+ return nil, fmt.Errorf("cannot chroot into %s: %v", preseedChroot, err)
+ }
+
+ if err := os.Chdir("/"); err != nil {
+ return nil, fmt.Errorf("cannot chdir to /: %v", err)
+ }
+
+ // GlobalRootDir is now relative to chroot env. We assume all paths
+ // inside the chroot to be identical with the host.
+ rootDir := dirs.GlobalRootDir
+ if rootDir == "" {
+ rootDir = "/"
+ }
+
+ coreSnapPath, err := systemSnapFromSeed(rootDir)
+ if err != nil {
+ return nil, err
+ }
+
+ // create mountpoint for core/snapd
+ where := filepath.Join(rootDir, snapdMountPath)
+ if err := os.MkdirAll(where, 0755); err != nil {
+ return nil, err
+ }
+
+ removeMountpoint := func() {
+ if err := os.Remove(where); err != nil {
+ fmt.Fprintf(Stderr, "%v", err)
+ }
+ }
+
+ cmd := exec.Command("mount", "-t", "squashfs", coreSnapPath, where)
+ if err := cmd.Run(); err != nil {
+ removeMountpoint()
+ return nil, fmt.Errorf("cannot mount %s at %s in preseed mode: %v ", coreSnapPath, where, err)
+ }
+
+ unmount := func() {
+ fmt.Fprintf(Stdout, "unmounting: %s\n", snapdMountPath)
+ cmd := exec.Command("umount", snapdMountPath)
+ if err := cmd.Run(); err != nil {
+ fmt.Fprintf(Stderr, "%v", err)
+ }
+ }
+
+ // read version from the mounted core snap
+ infoPath := filepath.Join(snapdMountPath, dirs.CoreLibExecDir, "info")
+ if err := checkTargetSnapdVersion(infoPath); err != nil {
+ unmount()
+ removeMountpoint()
+ return nil, err
+ }
+
+ return func() {
+ unmount()
+ removeMountpoint()
+ }, nil
+}
+
+// runPreseedMode runs snapd in a preseed mode. It assumes running in a chroot.
+// The chroot is expected to be set-up and ready to use (critical system directories mounted).
+func runPreseedMode(preseedChroot string) error {
+ // exec snapd relative to new chroot, e.g. /snapd-preseed/usr/lib/snapd/snapd
+ path := filepath.Join(snapdMountPath, dirs.CoreLibExecDir, "snapd")
+
+ // run snapd in preseed mode
+ cmd := exec.Command(path)
+ cmd.Env = os.Environ()
+ cmd.Env = append(cmd.Env, "SNAPD_PRESEED=1")
+ cmd.Stderr = Stderr
+ cmd.Stdout = Stdout
+
+ fmt.Fprintf(Stdout, "starting to preseed root: %s\n", preseedChroot)
+
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("error running snapd in preseed mode: %v\n", err)
+ }
+
+ return nil
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-preseed/preseed_other.go snapd-2.45.1+18.04/cmd/snap-preseed/preseed_other.go
--- snapd-2.42.1+18.04/cmd/snap-preseed/preseed_other.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-preseed/preseed_other.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,41 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+// +build !linux
+
+/*
+ * 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"
+)
+
+var preseedNotAvailableError = errors.New("preseed mode not available for systems other than linux")
+
+func checkChroot(preseedChroot string) error {
+ return preseedNotAvailableError
+}
+
+func prepareChroot(preseedChroot string) (func(), error) {
+ return nil, preseedNotAvailableError
+}
+
+func runPreseedMode(rootDir string) error {
+ return preseedNotAvailableError
+}
+
+func cleanup() {}
diff -Nru snapd-2.42.1+18.04/cmd/snap-preseed/reset.go snapd-2.45.1+18.04/cmd/snap-preseed/reset.go
--- snapd-2.42.1+18.04/cmd/snap-preseed/reset.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-preseed/reset.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,144 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ "github.com/snapcore/snapd/dirs"
+ apparmor_sandbox "github.com/snapcore/snapd/sandbox/apparmor"
+)
+
+func resetPreseededChroot(preseedChroot string) error {
+ // globs that yield individual files
+ globs := []string{
+ dirs.SnapStateFile,
+ dirs.SnapSystemKeyFile,
+ filepath.Join(dirs.SnapBlobDir, "*.snap"),
+ filepath.Join(dirs.SnapUdevRulesDir, "*-snap.*.rules"),
+ filepath.Join(dirs.SnapBusPolicyDir, "snap.*.*.conf"),
+ filepath.Join(dirs.SnapServicesDir, "snap.*.service"),
+ filepath.Join(dirs.SnapServicesDir, "snap.*.timer"),
+ filepath.Join(dirs.SnapServicesDir, "snap.*.socket"),
+ filepath.Join(dirs.SnapServicesDir, "snap-*.mount"),
+ filepath.Join(dirs.SnapServicesDir, "multi-user.target.wants", "snap-*.mount"),
+ }
+
+ for _, gl := range globs {
+ matches, err := filepath.Glob(filepath.Join(preseedChroot, gl))
+ if err != nil {
+ // the only possible error from Glob() is ErrBadPattern
+ return err
+ }
+ for _, path := range matches {
+ if err := os.Remove(path); err != nil {
+ return fmt.Errorf("error removing %s: %v", path, err)
+ }
+ }
+ }
+
+ // directories that need to be removed recursively (but
+ // leaving parent directory intact).
+ globs = []string{
+ filepath.Join(dirs.SnapDataDir, "*"),
+ filepath.Join(dirs.SnapCacheDir, "*"),
+ filepath.Join(apparmor_sandbox.CacheDir, "*"),
+ filepath.Join(dirs.SnapDesktopFilesDir, "*"),
+ }
+
+ for _, gl := range globs {
+ matches, err := filepath.Glob(filepath.Join(preseedChroot, gl))
+ if err != nil {
+ // the only possible error from Glob() is ErrBadPattern
+ return err
+ }
+ for _, path := range matches {
+ if err := os.RemoveAll(path); err != nil {
+ return fmt.Errorf("error removing %s: %v", path, err)
+ }
+ }
+ }
+
+ // directories removed entirely
+ paths := []string{
+ dirs.SnapAssertsDBDir,
+ dirs.FeaturesDir,
+ dirs.SnapDesktopIconsDir,
+ dirs.SnapDeviceDir,
+ dirs.SnapCookieDir,
+ dirs.SnapMountPolicyDir,
+ dirs.SnapAppArmorDir,
+ dirs.SnapSeqDir,
+ dirs.SnapMountDir,
+ dirs.SnapSeccompBase,
+ }
+
+ for _, path := range paths {
+ if err := os.RemoveAll(filepath.Join(preseedChroot, path)); err != nil {
+ // report the error and carry on
+ return fmt.Errorf("error removing %s: %v", path, err)
+ }
+ }
+
+ // bash-completion symlinks; note there are symlinks that point at
+ // completer, and symlinks that point at the completer symlinks.
+ // e.g.
+ // lxd.lxc -> /snap/core/current/usr/lib/snapd/complete.sh
+ // lxc -> lxd.lxc
+ files, err := ioutil.ReadDir(filepath.Join(preseedChroot, dirs.CompletersDir))
+ if err != nil && !os.IsNotExist(err) {
+ return fmt.Errorf("error reading %s: %v", dirs.CompletersDir, err)
+ }
+ completeShSymlinks := make(map[string]string)
+ var otherSymlinks []string
+
+ // pass 1: find all symlinks pointing at complete.sh
+ for _, fileInfo := range files {
+ if fileInfo.Mode()&os.ModeSymlink == 0 {
+ continue
+ }
+ fullPath := filepath.Join(preseedChroot, dirs.CompletersDir, fileInfo.Name())
+ if dirs.IsCompleteShSymlink(fullPath) {
+ if err := os.Remove(fullPath); err != nil {
+ return fmt.Errorf("error removing symlink %s: %v", fullPath, err)
+ }
+ completeShSymlinks[fileInfo.Name()] = fullPath
+ } else {
+ otherSymlinks = append(otherSymlinks, fullPath)
+ }
+ }
+ // pass 2: find all symlinks that point at the symlinks found in pass 1.
+ for _, other := range otherSymlinks {
+ target, err := os.Readlink(other)
+ if err != nil {
+ return fmt.Errorf("error reading symlink target of %s: %v", other, err)
+ }
+ if _, ok := completeShSymlinks[target]; ok {
+ if err := os.Remove(other); err != nil {
+ return fmt.Errorf("error removing symlink %s: %v", other, err)
+ }
+ }
+ }
+
+ return nil
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-recovery-chooser/export_test.go snapd-2.45.1+18.04/cmd/snap-recovery-chooser/export_test.go
--- snapd-2.42.1+18.04/cmd/snap-recovery-chooser/export_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-recovery-chooser/export_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,65 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public 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 (
+ "io"
+ "log/syslog"
+ "os/exec"
+)
+
+var (
+ OutputForUI = outputForUI
+ RunUI = runUI
+ Chooser = chooser
+ LoggerWithSyslogMaybe = loggerWithSyslogMaybe
+)
+
+func MockStdStreams(stdout, stderr io.Writer) (restore func()) {
+ oldStdout, oldStderr := Stdout, Stderr
+ Stdout, Stderr = stdout, stderr
+ return func() {
+ Stdout, Stderr = oldStdout, oldStderr
+ }
+}
+
+func MockChooserTool(f func() (*exec.Cmd, error)) (restore func()) {
+ oldTool := chooserTool
+ chooserTool = f
+ return func() {
+ chooserTool = oldTool
+ }
+}
+
+func MockDefaultMarkerFile(p string) (restore func()) {
+ old := defaultMarkerFile
+ defaultMarkerFile = p
+ return func() {
+ defaultMarkerFile = old
+ }
+}
+
+func MockSyslogNew(f func(syslog.Priority, string) (io.Writer, error)) (restore func()) {
+ oldSyslogNew := syslogNew
+ syslogNew = f
+ return func() {
+ syslogNew = oldSyslogNew
+ }
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-recovery-chooser/main.go snapd-2.45.1+18.04/cmd/snap-recovery-chooser/main.go
--- snapd-2.42.1+18.04/cmd/snap-recovery-chooser/main.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-recovery-chooser/main.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,221 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+// The `snap-recovery-chooser` acts as a proxy between the chooser UI process
+// and the actual snapd daemon.
+//
+// It obtains the list of seed systems and their actions from the snapd API and
+// passed that directly to the standard input of the UI process. The UI process
+// is expected to present the list of options to the user and print out a JSON
+// object with the choice to its standard output.
+//
+// The JSON object carrying the list of systems is the client.ChooserSystems
+// structure. The response is defined as follows:
+// {
+// "label": ".
+ *
+ */
+
+package main_test
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log/syslog"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "testing"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/client"
+ main "github.com/snapcore/snapd/cmd/snap-recovery-chooser"
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/logger"
+ "github.com/snapcore/snapd/testutil"
+)
+
+// Hook up check.v1 into the "go test" runner
+func Test(t *testing.T) { TestingT(t) }
+
+type baseCmdSuite struct {
+ testutil.BaseTest
+
+ stdout, stderr bytes.Buffer
+ markerFile string
+}
+
+func (s *baseCmdSuite) SetUpTest(c *C) {
+ s.BaseTest.SetUpTest(c)
+ _, r := logger.MockLogger()
+ s.AddCleanup(r)
+ r = main.MockStdStreams(&s.stdout, &s.stderr)
+ s.AddCleanup(r)
+
+ d := c.MkDir()
+ s.markerFile = filepath.Join(d, "marker")
+ err := ioutil.WriteFile(s.markerFile, nil, 0644)
+ c.Assert(err, IsNil)
+}
+
+type cmdSuite struct {
+ baseCmdSuite
+}
+
+var _ = Suite(&cmdSuite{})
+
+var mockSystems = &main.ChooserSystems{
+ Systems: []client.System{
+ {
+ Label: "foo",
+ Actions: []client.SystemAction{
+ {Title: "reinstall", Mode: "install"},
+ },
+ },
+ },
+}
+
+func (s *cmdSuite) TestRunUIHappy(c *C) {
+ mockCmd := testutil.MockCommand(c, "tool", `
+echo '{}'
+`)
+ defer mockCmd.Restore()
+
+ rsp, err := main.RunUI(exec.Command(mockCmd.Exe()), mockSystems)
+ c.Assert(err, IsNil)
+ c.Assert(rsp, NotNil)
+}
+
+func (s *cmdSuite) TestRunUIBadJSON(c *C) {
+ mockCmd := testutil.MockCommand(c, "tool", `
+echo 'garbage'
+`)
+ defer mockCmd.Restore()
+
+ rsp, err := main.RunUI(exec.Command(mockCmd.Exe()), mockSystems)
+ c.Assert(err, ErrorMatches, "cannot decode response: .*")
+ c.Assert(rsp, IsNil)
+}
+
+func (s *cmdSuite) TestRunUIToolErr(c *C) {
+ mockCmd := testutil.MockCommand(c, "tool", `
+echo foo
+exit 22
+`)
+ defer mockCmd.Restore()
+
+ _, err := main.RunUI(exec.Command(mockCmd.Exe()), mockSystems)
+ c.Assert(err, ErrorMatches, "cannot collect output of the UI process: exit status 22")
+}
+
+func (s *cmdSuite) TestRunUIInputJSON(c *C) {
+ d := c.MkDir()
+ tf := filepath.Join(d, "json-input")
+ mockCmd := testutil.MockCommand(c, "tool", fmt.Sprintf(`
+cat > %s
+echo '{}'
+`, tf))
+ defer mockCmd.Restore()
+
+ _, err := main.RunUI(exec.Command(mockCmd.Exe()), mockSystems)
+ c.Assert(err, IsNil)
+
+ data, err := ioutil.ReadFile(tf)
+ c.Assert(err, IsNil)
+ var input *main.ChooserSystems
+ err = json.Unmarshal(data, &input)
+ c.Assert(err, IsNil)
+
+ c.Assert(input, DeepEquals, mockSystems)
+}
+
+func (s *cmdSuite) TestStdoutUI(c *C) {
+ var buf bytes.Buffer
+ err := main.OutputForUI(&buf, mockSystems)
+ c.Assert(err, IsNil)
+
+ var out *main.ChooserSystems
+
+ err = json.Unmarshal(buf.Bytes(), &out)
+ c.Assert(err, IsNil)
+ c.Assert(out, DeepEquals, mockSystems)
+}
+
+type mockedClientCmdSuite struct {
+ baseCmdSuite
+
+ config client.Config
+}
+
+var _ = Suite(&mockedClientCmdSuite{})
+
+func (s *mockedClientCmdSuite) SetUpTest(c *C) {
+ s.baseCmdSuite.SetUpTest(c)
+}
+
+func (s *mockedClientCmdSuite) RedirectClientToTestServer(handler func(http.ResponseWriter, *http.Request)) {
+ server := httptest.NewServer(http.HandlerFunc(handler))
+ s.BaseTest.AddCleanup(func() { server.Close() })
+ s.config.BaseURL = server.URL
+}
+
+type mockSystemRequestResponse struct {
+ label string
+ code int
+ reboot bool
+ expect map[string]interface{}
+}
+
+func (s *mockedClientCmdSuite) mockSuccessfulResponse(c *C, rspSystems *main.ChooserSystems, rspPostSystem *mockSystemRequestResponse) {
+ n := 0
+ s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
+ switch n {
+ case 0:
+ c.Check(r.URL.Path, Equals, "/v2/systems")
+ err := json.NewEncoder(w).Encode(apiResponse{
+ Type: "sync",
+ Result: rspSystems,
+ StatusCode: 200,
+ })
+ c.Assert(err, IsNil)
+ case 1:
+ if rspPostSystem == nil {
+ c.Fatalf("unexpected request to %q", r.URL.Path)
+ }
+ c.Check(r.URL.Path, Equals, "/v2/systems/"+rspPostSystem.label)
+ c.Check(r.Method, Equals, "POST")
+
+ var data map[string]interface{}
+ err := json.NewDecoder(r.Body).Decode(&data)
+ c.Assert(err, IsNil)
+ c.Check(data, DeepEquals, rspPostSystem.expect)
+
+ rspType := "sync"
+ var rspData map[string]string
+ if rspPostSystem.code >= 400 {
+ rspType = "error"
+ rspData = map[string]string{"message": "failed in mock"}
+ }
+ var maintenance map[string]interface{}
+ if rspPostSystem.reboot {
+ maintenance = map[string]interface{}{
+ "kind": client.ErrorKindSystemRestart,
+ "message": "system is restartring",
+ }
+ }
+ err = json.NewEncoder(w).Encode(apiResponse{
+ Type: rspType,
+ Result: rspData,
+ StatusCode: rspPostSystem.code,
+ Maintenance: maintenance,
+ })
+ c.Assert(err, IsNil)
+ default:
+ c.Fatalf("expected to get 1 requests, now on %d", n+1)
+ }
+ n++
+ })
+}
+
+type apiResponse struct {
+ Type string `json:"type"`
+ Result interface{} `json:"result"`
+ StatusCode int `json:"status-code"`
+ Maintenance interface{} `json:"maintenance"`
+}
+
+func (s *mockedClientCmdSuite) TestMainChooserWithTool(c *C) {
+ r := main.MockDefaultMarkerFile(s.markerFile)
+ defer r()
+ // sanity
+ c.Assert(s.markerFile, testutil.FilePresent)
+
+ capturedStdinPath := filepath.Join(c.MkDir(), "stdin")
+ mockCmd := testutil.MockCommand(c, "tool", fmt.Sprintf(`
+cat - > %s
+echo '{"label":"label","action":{"mode":"install","title":"reinstall"}}'
+`, capturedStdinPath))
+ defer mockCmd.Restore()
+ r = main.MockChooserTool(func() (*exec.Cmd, error) {
+ return exec.Command(mockCmd.Exe()), nil
+ })
+ defer r()
+
+ s.mockSuccessfulResponse(c, mockSystems, &mockSystemRequestResponse{
+ code: 200,
+ label: "label",
+ expect: map[string]interface{}{
+ "action": "do",
+ "mode": "install",
+ "title": "reinstall",
+ },
+ reboot: true,
+ })
+
+ rbt, err := main.Chooser(client.New(&s.config))
+ c.Assert(err, IsNil)
+ c.Assert(rbt, Equals, true)
+ c.Assert(mockCmd.Calls(), DeepEquals, [][]string{
+ {"tool"},
+ })
+
+ capturedStdin, err := ioutil.ReadFile(capturedStdinPath)
+ c.Assert(err, IsNil)
+ var stdoutSystems main.ChooserSystems
+ err = json.Unmarshal(capturedStdin, &stdoutSystems)
+ c.Assert(err, IsNil)
+ c.Check(&stdoutSystems, DeepEquals, mockSystems)
+
+ c.Assert(s.markerFile, testutil.FileAbsent)
+}
+
+func (s *mockedClientCmdSuite) TestMainChooserToolNotFound(c *C) {
+ r := main.MockDefaultMarkerFile(s.markerFile)
+ defer r()
+ // sanity
+ c.Assert(s.markerFile, testutil.FilePresent)
+
+ s.mockSuccessfulResponse(c, mockSystems, nil)
+
+ r = main.MockChooserTool(func() (*exec.Cmd, error) {
+ return nil, fmt.Errorf("tool not found")
+ })
+ defer r()
+
+ rbt, err := main.Chooser(client.New(&s.config))
+ c.Assert(err, ErrorMatches, "cannot locate the chooser UI tool: tool not found")
+ c.Assert(rbt, Equals, false)
+
+ c.Assert(s.markerFile, testutil.FileAbsent)
+}
+
+func (s *mockedClientCmdSuite) TestMainChooserBadAPI(c *C) {
+ r := main.MockDefaultMarkerFile(s.markerFile)
+ defer r()
+ // sanity
+ c.Assert(s.markerFile, testutil.FilePresent)
+
+ n := 0
+ s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
+ switch n {
+ case 0:
+ c.Check(r.URL.Path, Equals, "/v2/systems")
+ enc := json.NewEncoder(w)
+ err := enc.Encode(apiResponse{
+ Type: "error",
+ Result: map[string]string{
+ "message": "no systems for you",
+ },
+ StatusCode: 400,
+ })
+ c.Assert(err, IsNil)
+ default:
+ c.Fatalf("expected to get 1 requests, now on %d", n+1)
+ }
+ n++
+ })
+
+ rbt, err := main.Chooser(client.New(&s.config))
+ c.Assert(err, ErrorMatches, "cannot list recovery systems: no systems for you")
+ c.Assert(rbt, Equals, false)
+
+ c.Assert(s.markerFile, testutil.FileAbsent)
+}
+
+func (s *mockedClientCmdSuite) TestMainChooserDefaultsToConsoleConf(c *C) {
+ d := c.MkDir()
+ dirs.SetRootDir(d)
+ defer dirs.SetRootDir("/")
+
+ r := main.MockDefaultMarkerFile(s.markerFile)
+ defer r()
+ // sanity
+ c.Assert(s.markerFile, testutil.FilePresent)
+
+ s.mockSuccessfulResponse(c, mockSystems, &mockSystemRequestResponse{
+ code: 200,
+ label: "label",
+ expect: map[string]interface{}{
+ "action": "do",
+ "mode": "install",
+ "title": "reinstall",
+ },
+ })
+
+ mockCmd := testutil.MockCommand(c, filepath.Join(dirs.GlobalRootDir, "/usr/bin/console-conf"), `
+echo '{"label":"label","action":{"mode":"install","title":"reinstall"}}'
+`)
+ defer mockCmd.Restore()
+
+ rbt, err := main.Chooser(client.New(&s.config))
+ c.Assert(err, IsNil)
+ c.Assert(rbt, Equals, false)
+
+ c.Check(mockCmd.Calls(), DeepEquals, [][]string{
+ {"console-conf", "--recovery-chooser-mode"},
+ })
+
+ c.Assert(s.markerFile, testutil.FileAbsent)
+}
+
+func (s *mockedClientCmdSuite) TestMainChooserNoConsoleConf(c *C) {
+ d := c.MkDir()
+ dirs.SetRootDir(d)
+ defer dirs.SetRootDir("/")
+
+ r := main.MockDefaultMarkerFile(s.markerFile)
+ defer r()
+ // sanity
+ c.Assert(s.markerFile, testutil.FilePresent)
+
+ // not expecting a POST request
+ s.mockSuccessfulResponse(c, mockSystems, nil)
+
+ // tries to look up the console-conf binary but fails
+ rbt, err := main.Chooser(client.New(&s.config))
+ c.Assert(err, ErrorMatches, `cannot locate the chooser UI tool: chooser UI tool ".*/usr/bin/console-conf" does not exist`)
+ c.Assert(rbt, Equals, false)
+ c.Assert(s.markerFile, testutil.FileAbsent)
+}
+
+func (s *mockedClientCmdSuite) TestMainChooserGarbageNoActionRequested(c *C) {
+ d := c.MkDir()
+ dirs.SetRootDir(d)
+ defer dirs.SetRootDir("/")
+
+ r := main.MockDefaultMarkerFile(s.markerFile)
+ defer r()
+ // sanity
+ c.Assert(s.markerFile, testutil.FilePresent)
+
+ // not expecting a POST request
+ s.mockSuccessfulResponse(c, mockSystems, nil)
+
+ mockCmd := testutil.MockCommand(c, filepath.Join(dirs.GlobalRootDir, "/usr/bin/console-conf"), `
+echo 'garbage'
+`)
+ defer mockCmd.Restore()
+
+ rbt, err := main.Chooser(client.New(&s.config))
+ c.Assert(err, ErrorMatches, "UI process failed: cannot decode response: .*")
+ c.Assert(rbt, Equals, false)
+
+ c.Check(mockCmd.Calls(), DeepEquals, [][]string{
+ {"console-conf", "--recovery-chooser-mode"},
+ })
+
+ c.Assert(s.markerFile, testutil.FileAbsent)
+}
+
+func (s *mockedClientCmdSuite) TestMainChooserNoMarkerNoCalls(c *C) {
+ r := main.MockDefaultMarkerFile(s.markerFile + ".notfound")
+ defer r()
+
+ mockCmd := testutil.MockCommand(c, "tool", `
+exit 123
+`)
+ defer mockCmd.Restore()
+ r = main.MockChooserTool(func() (*exec.Cmd, error) {
+ return exec.Command(mockCmd.Exe()), nil
+ })
+ defer r()
+
+ rbt, err := main.Chooser(client.New(&s.config))
+ c.Assert(err, ErrorMatches, "cannot run chooser without the marker file")
+ c.Assert(rbt, Equals, false)
+
+ c.Assert(mockCmd.Calls(), HasLen, 0)
+}
+
+func (s *mockedClientCmdSuite) TestMainChooserSnapdAPIBad(c *C) {
+ r := main.MockDefaultMarkerFile(s.markerFile)
+ defer r()
+ // sanity
+ c.Assert(s.markerFile, testutil.FilePresent)
+
+ mockCmd := testutil.MockCommand(c, "tool", `
+echo '{"label":"label","action":{"mode":"install","title":"reinstall"}}'
+`)
+ defer mockCmd.Restore()
+ r = main.MockChooserTool(func() (*exec.Cmd, error) {
+ return exec.Command(mockCmd.Exe()), nil
+ })
+ defer r()
+
+ s.mockSuccessfulResponse(c, mockSystems, &mockSystemRequestResponse{
+ code: 400,
+ label: "label",
+ expect: map[string]interface{}{
+ "action": "do",
+ "mode": "install",
+ "title": "reinstall",
+ },
+ })
+
+ rbt, err := main.Chooser(client.New(&s.config))
+ c.Assert(err, ErrorMatches, "cannot request system action: .* failed in mock")
+ c.Assert(rbt, Equals, false)
+ c.Assert(mockCmd.Calls(), DeepEquals, [][]string{
+ {"tool"},
+ })
+
+ c.Assert(s.markerFile, testutil.FileAbsent)
+
+}
+
+type mockedSyslogCmdSuite struct {
+ baseCmdSuite
+
+ term string
+}
+
+var _ = Suite(&mockedSyslogCmdSuite{})
+
+func (s *mockedSyslogCmdSuite) SetUpTest(c *C) {
+ s.baseCmdSuite.SetUpTest(c)
+
+ s.term = os.Getenv("TERM")
+ s.AddCleanup(func() { os.Setenv("TERM", s.term) })
+
+ r := main.MockSyslogNew(func(p syslog.Priority, t string) (io.Writer, error) {
+ c.Fatal("not mocked")
+ return nil, fmt.Errorf("not mocked")
+ })
+ s.AddCleanup(r)
+}
+
+func (s *mockedSyslogCmdSuite) TestNoSyslogFallback(c *C) {
+ err := os.Setenv("TERM", "someterm")
+ c.Assert(err, IsNil)
+
+ called := false
+ r := main.MockSyslogNew(func(_ syslog.Priority, _ string) (io.Writer, error) {
+ called = true
+ return nil, fmt.Errorf("no syslog")
+ })
+ defer r()
+ err = main.LoggerWithSyslogMaybe()
+ c.Assert(err, IsNil)
+ c.Check(called, Equals, true)
+ // this likely goes to stderr
+ logger.Noticef("ping")
+}
+
+func (s *mockedSyslogCmdSuite) TestWithSyslog(c *C) {
+ err := os.Setenv("TERM", "someterm")
+ c.Assert(err, IsNil)
+
+ called := false
+ tag := ""
+ prio := syslog.Priority(0)
+ buf := bytes.Buffer{}
+ r := main.MockSyslogNew(func(p syslog.Priority, tg string) (io.Writer, error) {
+ tag = tg
+ prio = p
+ called = true
+ return &buf, nil
+ })
+ defer r()
+ err = main.LoggerWithSyslogMaybe()
+ c.Assert(err, IsNil)
+ c.Check(called, Equals, true)
+ c.Check(tag, Equals, "snap-recovery-chooser")
+ c.Check(prio, Equals, syslog.LOG_INFO|syslog.LOG_DAEMON)
+
+ logger.Noticef("ping")
+ c.Check(buf.String(), testutil.Contains, "ping")
+}
+
+func (s *mockedSyslogCmdSuite) TestSimple(c *C) {
+ err := os.Unsetenv("TERM")
+ c.Assert(err, IsNil)
+
+ r := main.MockSyslogNew(func(p syslog.Priority, tg string) (io.Writer, error) {
+ c.Fatalf("unexpected call")
+ return nil, fmt.Errorf("unexpected call")
+ })
+ defer r()
+ err = main.LoggerWithSyslogMaybe()
+ c.Assert(err, IsNil)
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-repair/cmd_run.go snapd-2.45.1+18.04/cmd/snap-repair/cmd_run.go
--- snapd-2.42.1+18.04/cmd/snap-repair/cmd_run.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-repair/cmd_run.go 2020-06-05 13:13:49.000000000 +0000
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2017 Canonical Ltd
+ * Copyright (C) 2017-2020 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -27,6 +27,7 @@
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/snapdenv"
)
func init() {
@@ -47,7 +48,7 @@
func init() {
var baseurl string
- if osutil.GetenvBool("SNAPPY_USE_STAGING_STORE") {
+ if snapdenv.UseStagingStore() {
baseurl = "https://api.staging.snapcraft.io/v2/"
} else {
baseurl = "https://api.snapcraft.io/v2/"
diff -Nru snapd-2.42.1+18.04/cmd/snap-repair/cmd_run_test.go snapd-2.45.1+18.04/cmd/snap-repair/cmd_run_test.go
--- snapd-2.42.1+18.04/cmd/snap-repair/cmd_run_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-repair/cmd_run_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -33,8 +33,24 @@
"github.com/snapcore/snapd/release"
)
+func (r *repairSuite) TestNonRoot(c *C) {
+ restore := repair.MockOsGetuid(func() int { return 1000 })
+ defer restore()
+ restore = release.MockOnClassic(false)
+ defer restore()
+
+ origArgs := os.Args
+ defer func() { os.Args = origArgs }()
+ os.Args = []string{"snap-repair", "run"}
+ err := repair.Run()
+ c.Assert(err, ErrorMatches, "must be run as root")
+}
+
func (r *repairSuite) TestRun(c *C) {
- defer release.MockOnClassic(false)()
+ restore := repair.MockOsGetuid(func() int { return 0 })
+ defer restore()
+ restore = release.MockOnClassic(false)
+ defer restore()
r1 := sysdb.InjectTrusted(r.storeSigning.Trusted)
defer r1()
diff -Nru snapd-2.42.1+18.04/cmd/snap-repair/export_test.go snapd-2.45.1+18.04/cmd/snap-repair/export_test.go
--- snapd-2.42.1+18.04/cmd/snap-repair/export_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-repair/export_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -139,3 +139,9 @@
cmdShow.Positional.Repair = args
return cmdShow
}
+
+func MockOsGetuid(f func() int) (restore func()) {
+ origOsGetuid := osGetuid
+ osGetuid = f
+ return func() { osGetuid = origOsGetuid }
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-repair/main.go snapd-2.45.1+18.04/cmd/snap-repair/main.go
--- snapd-2.42.1+18.04/cmd/snap-repair/main.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-repair/main.go 2020-06-05 13:13:49.000000000 +0000
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2017 Canonical Ltd
+ * Copyright (C) 2017-2020 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -28,9 +28,9 @@
"github.com/jessevdk/go-flags"
"github.com/snapcore/snapd/cmd"
- "github.com/snapcore/snapd/httputil"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/release"
+ "github.com/snapcore/snapd/snapdenv"
)
var (
@@ -67,11 +67,16 @@
}
}
+var osGetuid = os.Getuid
+
func run() error {
if release.OnClassic {
return errOnClassic
}
- httputil.SetUserAgentFromVersion(cmd.Version, "snap-repair")
+ if osGetuid() != 0 {
+ return fmt.Errorf("must be run as root")
+ }
+ snapdenv.SetUserAgentFromVersion(cmd.Version, nil, "snap-repair")
if err := parseArgs(os.Args[1:]); err != nil {
return err
diff -Nru snapd-2.42.1+18.04/cmd/snap-repair/main_test.go snapd-2.45.1+18.04/cmd/snap-repair/main_test.go
--- snapd-2.42.1+18.04/cmd/snap-repair/main_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-repair/main_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2017 Canonical Ltd
+ * Copyright (C) 2017-2020 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -27,8 +27,8 @@
repair "github.com/snapcore/snapd/cmd/snap-repair"
"github.com/snapcore/snapd/dirs"
- "github.com/snapcore/snapd/httputil"
"github.com/snapcore/snapd/release"
+ "github.com/snapcore/snapd/snapdenv"
"github.com/snapcore/snapd/testutil"
)
@@ -49,7 +49,7 @@
func (r *repairSuite) SetUpSuite(c *C) {
r.baseRunnerSuite.SetUpSuite(c)
- r.restore = httputil.SetUserAgentFromVersion("", "")
+ r.restore = snapdenv.SetUserAgentFromVersion("", nil, "")
}
func (r *repairSuite) TearDownSuite(c *C) {
diff -Nru snapd-2.42.1+18.04/cmd/snap-repair/runner.go snapd-2.45.1+18.04/cmd/snap-repair/runner.go
--- snapd-2.42.1+18.04/cmd/snap-repair/runner.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-repair/runner.go 2020-06-05 13:13:49.000000000 +0000
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2017 Canonical Ltd
+ * Copyright (C) 2017-2020 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -49,6 +49,7 @@
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/release"
+ "github.com/snapcore/snapd/snapdenv"
"github.com/snapcore/snapd/strutil"
)
@@ -282,10 +283,14 @@
sequenceNext: make(map[string]int),
}
opts := httputil.ClientOptions{
- MayLogBody: false,
+ MayLogBody: false,
+ ProxyConnectHeader: http.Header{"User-Agent": []string{snapdenv.UserAgent()}},
TLSConfig: &tls.Config{
Time: run.now,
},
+ ExtraSSLCerts: &httputil.ExtraSSLCertsFromDir{
+ Dir: dirs.SnapdStoreSSLCertsDir,
+ },
}
run.cli = httputil.NewHTTPClient(&opts)
return run
@@ -332,7 +337,7 @@
if err != nil {
return nil, err
}
- req.Header.Set("User-Agent", httputil.UserAgent())
+ req.Header.Set("User-Agent", snapdenv.UserAgent())
req.Header.Set("Accept", "application/x.ubuntu.assertion")
if revision >= 0 {
req.Header.Set("If-None-Match", fmt.Sprintf(`"%d"`, revision))
@@ -437,7 +442,7 @@
if err != nil {
return nil, err
}
- req.Header.Set("User-Agent", httputil.UserAgent())
+ req.Header.Set("User-Agent", snapdenv.UserAgent())
req.Header.Set("Accept", "application/json")
return run.cli.Do(req)
}, func(resp *http.Response) error {
@@ -984,6 +989,9 @@
trustedBS.Put(asserts.AccountKeyType, t)
}
for _, t := range sysdb.Trusted() {
+ // we do *not* add the defalt sysdb trusted account
+ // keys here because the repair assertions have their
+ // own *dedicated* root of trust
if t.Type() == asserts.AccountType {
trustedBS.Put(asserts.AccountType, t)
}
diff -Nru snapd-2.42.1+18.04/cmd/snap-repair/runner_test.go snapd-2.45.1+18.04/cmd/snap-repair/runner_test.go
--- snapd-2.42.1+18.04/cmd/snap-repair/runner_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-repair/runner_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2017 Canonical Ltd
+ * Copyright (C) 2017-2020 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -43,9 +43,9 @@
"github.com/snapcore/snapd/asserts/sysdb"
repair "github.com/snapcore/snapd/cmd/snap-repair"
"github.com/snapcore/snapd/dirs"
- "github.com/snapcore/snapd/httputil"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/snapdenv"
"github.com/snapcore/snapd/testutil"
)
@@ -170,7 +170,7 @@
func (s *runnerSuite) SetUpSuite(c *C) {
s.baseRunnerSuite.SetUpSuite(c)
- s.restore = httputil.SetUserAgentFromVersion("1", "snap-repair")
+ s.restore = snapdenv.SetUserAgentFromVersion("1", nil, "snap-repair")
}
func (s *runnerSuite) TearDownSuite(c *C) {
@@ -1435,9 +1435,10 @@
series:
- 16
timestamp: 2017-07-02T12:00:00Z
-body-length: 7
+body-length: 17
sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
+#!/bin/sh
exit 0
@@ -1460,8 +1461,9 @@
rpr, err := runner.Next("canonical")
c.Assert(err, IsNil)
- rpr.Run()
- c.Check(filepath.Join(dirs.SnapRepairRunDir, "canonical", "1", "r0.script"), testutil.FileEquals, "exit 0\n")
+ err = rpr.Run()
+ c.Assert(err, IsNil)
+ c.Check(filepath.Join(dirs.SnapRepairRunDir, "canonical", "1", "r0.script"), testutil.FileEquals, "#!/bin/sh\nexit 0\n")
}
func makeMockRepair(script string) string {
diff -Nru snapd-2.42.1+18.04/cmd/snap-repair/staging.go snapd-2.45.1+18.04/cmd/snap-repair/staging.go
--- snapd-2.42.1+18.04/cmd/snap-repair/staging.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-repair/staging.go 2020-06-05 13:13:49.000000000 +0000
@@ -2,7 +2,7 @@
// +build withtestkeys withstagingkeys
/*
- * Copyright (C) 2017 Canonical Ltd
+ * Copyright (C) 2017-2020 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -24,7 +24,7 @@
"fmt"
"github.com/snapcore/snapd/asserts"
- "github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/snapdenv"
)
const (
@@ -75,7 +75,7 @@
if err != nil {
panic(fmt.Sprintf("cannot decode trusted account-key: %v", err))
}
- if osutil.GetenvBool("SNAPPY_USE_STAGING_STORE") {
+ if snapdenv.UseStagingStore() {
trustedRepairRootKeys = append(trustedRepairRootKeys, repairRootAccountKey.(*asserts.AccountKey))
}
}
diff -Nru snapd-2.42.1+18.04/cmd/snap-repair/trusted.go snapd-2.45.1+18.04/cmd/snap-repair/trusted.go
--- snapd-2.42.1+18.04/cmd/snap-repair/trusted.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-repair/trusted.go 2020-06-05 13:13:49.000000000 +0000
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2017 Canonical Ltd
+ * Copyright (C) 2017-2020 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -23,7 +23,7 @@
"fmt"
"github.com/snapcore/snapd/asserts"
- "github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/snapdenv"
)
const (
@@ -83,7 +83,7 @@
if err != nil {
panic(fmt.Sprintf("cannot decode trusted account-key: %v", err))
}
- if !osutil.GetenvBool("SNAPPY_USE_STAGING_STORE") {
+ if !snapdenv.UseStagingStore() {
trustedRepairRootKeys = append(trustedRepairRootKeys, repairRootAccountKey.(*asserts.AccountKey))
}
}
diff -Nru snapd-2.42.1+18.04/cmd/snap-seccomp/main_test.go snapd-2.45.1+18.04/cmd/snap-seccomp/main_test.go
--- snapd-2.42.1+18.04/cmd/snap-seccomp/main_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-seccomp/main_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -142,7 +142,7 @@
{
uint32_t l[7];
int syscall_ret, ret = 0;
- for (int i = 0; i < 7; i++) {
+ for (int i = 0; i < 7 && argv[i+1] != NULL; i++) {
errno = 0;
l[i] = strtoll(argv[i + 1], NULL, 10);
// exit '11' let's us know strtoll failed
@@ -249,6 +249,8 @@
faccessat
# i386 from amd64
restart_syscall
+# libc6 2.31/gcc-9.3
+mprotect
`
bpfPath := filepath.Join(c.MkDir(), "bpf")
err := main.Compile([]byte(common+seccompWhitelist), bpfPath)
@@ -328,6 +330,11 @@
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
+ // the exit code of the test binary is either 0 or 10, everything
+ // else is unexpected (segv, strtoll failure, ...)
+ exitCode, e := osutil.ExitCode(err)
+ c.Assert(e, IsNil)
+ c.Assert(exitCode == 0 || exitCode == 10, Equals, true, Commentf("unexpected exit code: %v for %v - test setup broken", exitCode, seccompWhitelist))
switch expected {
case Allow:
if err != nil {
diff -Nru snapd-2.42.1+18.04/cmd/snap-seccomp/syscalls/syscalls.go snapd-2.45.1+18.04/cmd/snap-seccomp/syscalls/syscalls.go
--- snapd-2.42.1+18.04/cmd/snap-seccomp/syscalls/syscalls.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-seccomp/syscalls/syscalls.go 2020-06-05 13:13:49.000000000 +0000
@@ -20,7 +20,7 @@
package syscalls
// Generated using arch-syscall-dump test tool from libseccomp tree, git
-// revision 584ca7a5e69d87a4c2c4e4c07ce8415fa59e1351.
+// revision bf747eb21e428c2b3ead6ebcca27951b681963a0.
var SeccompSyscalls = []string{
"_llseek",
"_newselect",
@@ -52,11 +52,17 @@
"chown32",
"chroot",
"clock_adjtime",
+ "clock_adjtime64",
"clock_getres",
+ "clock_getres_time64",
"clock_gettime",
+ "clock_gettime64",
"clock_nanosleep",
+ "clock_nanosleep_time64",
"clock_settime",
+ "clock_settime64",
"clone",
+ "clone3",
"close",
"connect",
"copy_file_range",
@@ -100,7 +106,11 @@
"flock",
"fork",
"fremovexattr",
+ "fsconfig",
"fsetxattr",
+ "fsmount",
+ "fsopen",
+ "fspick",
"fstat",
"fstat64",
"fstatat64",
@@ -111,6 +121,7 @@
"ftruncate",
"ftruncate64",
"futex",
+ "futex_time64",
"futimesat",
"get_kernel_syms",
"get_mempolicy",
@@ -163,6 +174,7 @@
"io_destroy",
"io_getevents",
"io_pgetevents",
+ "io_pgetevents_time64",
"io_setup",
"io_submit",
"io_uring_enter",
@@ -211,6 +223,7 @@
"mmap2",
"modify_ldt",
"mount",
+ "move_mount",
"move_pages",
"mprotect",
"mpx",
@@ -218,7 +231,9 @@
"mq_notify",
"mq_open",
"mq_timedreceive",
+ "mq_timedreceive_time64",
"mq_timedsend",
+ "mq_timedsend_time64",
"mq_unlink",
"mremap",
"msgctl",
@@ -240,16 +255,20 @@
"oldolduname",
"oldstat",
"olduname",
- "oldwait4",
"open",
"open_by_handle_at",
+ "open_tree",
"openat",
+ "openat2",
"pause",
"pciconfig_iobase",
"pciconfig_read",
"pciconfig_write",
"perf_event_open",
"personality",
+ "pidfd_getfd",
+ "pidfd_open",
+ "pidfd_send_signal",
"pipe",
"pipe2",
"pivot_root",
@@ -258,6 +277,7 @@
"pkey_mprotect",
"poll",
"ppoll",
+ "ppoll_time64",
"prctl",
"pread64",
"preadv",
@@ -268,6 +288,7 @@
"prof",
"profil",
"pselect6",
+ "pselect6_time64",
"ptrace",
"putpmsg",
"pwrite64",
@@ -285,6 +306,7 @@
"recv",
"recvfrom",
"recvmmsg",
+ "recvmmsg_time64",
"recvmsg",
"remap_file_pages",
"removexattr",
@@ -293,6 +315,7 @@
"renameat2",
"request_key",
"restart_syscall",
+ "riscv_flush_icache",
"rmdir",
"rseq",
"rt_sigaction",
@@ -302,6 +325,7 @@
"rt_sigreturn",
"rt_sigsuspend",
"rt_sigtimedwait",
+ "rt_sigtimedwait_time64",
"rt_tgsigqueueinfo",
"rtas",
"s390_guarded_storage",
@@ -316,6 +340,7 @@
"sched_getparam",
"sched_getscheduler",
"sched_rr_get_interval",
+ "sched_rr_get_interval_time64",
"sched_setaffinity",
"sched_setattr",
"sched_setparam",
@@ -328,6 +353,7 @@
"semget",
"semop",
"semtimedop",
+ "semtimedop_time64",
"send",
"sendfile",
"sendfile64",
@@ -421,11 +447,15 @@
"timer_delete",
"timer_getoverrun",
"timer_gettime",
+ "timer_gettime64",
"timer_settime",
+ "timer_settime64",
"timerfd",
"timerfd_create",
"timerfd_gettime",
+ "timerfd_gettime64",
"timerfd_settime",
+ "timerfd_settime64",
"times",
"tkill",
"truncate",
@@ -447,6 +477,7 @@
"ustat",
"utime",
"utimensat",
+ "utimensat_time64",
"utimes",
"vfork",
"vhangup",
diff -Nru snapd-2.42.1+18.04/cmd/snap-update-ns/change.go snapd-2.45.1+18.04/cmd/snap-update-ns/change.go
--- snapd-2.42.1+18.04/cmd/snap-update-ns/change.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-update-ns/change.go 2020-06-05 13:13:49.000000000 +0000
@@ -28,8 +28,10 @@
"strings"
"syscall"
+ "github.com/snapcore/snapd/features"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/osutil/mount"
)
// Action represents a mount action (mount, remount, unmount, etc).
@@ -119,6 +121,7 @@
if needsMimic, mimicPath := mimicRequired(err); needsMimic && pokeHoles {
// If the error can be recovered by using a writable mimic
// then construct one and try again.
+ logger.Debugf("need to create writable mimic needed to create path %q (original error: %v)", path, err)
changes, err = createWritableMimic(mimicPath, path, as)
if err != nil {
err = fmt.Errorf("cannot create writable mimic over %q: %s", mimicPath, err)
@@ -306,13 +309,23 @@
flagsForMount = uintptr(maskedFlagsNotPropagationNotRecursive)
err = sysMount(c.Entry.Name, c.Entry.Dir, c.Entry.Type, uintptr(flagsForMount), strings.Join(unparsed, ","))
}
- logger.Debugf("mount %q %q %q %d %q (error: %v)", c.Entry.Name, c.Entry.Dir, c.Entry.Type, flagsForMount, strings.Join(unparsed, ","), err)
+ mountOpts, unknownFlags := mount.MountFlagsToOpts(int(flagsForMount))
+ if unknownFlags != 0 {
+ mountOpts = append(mountOpts, fmt.Sprintf("%#x", unknownFlags))
+ }
+ logger.Debugf("mount name:%q dir:%q type:%q opts:%s unparsed:%q (error: %v)",
+ c.Entry.Name, c.Entry.Dir, c.Entry.Type, strings.Join(mountOpts, "|"), strings.Join(unparsed, ","), err)
if err == nil && maskedFlagsPropagation != 0 {
// now change mount propagation (shared/rshared, private/rprivate,
// slave/rslave, unbindable/runbindable).
flagsForMount := uintptr(maskedFlagsPropagation | maskedFlagsRecursive)
+ mountOpts, unknownFlags := mount.MountFlagsToOpts(int(flagsForMount))
+ if unknownFlags != 0 {
+ mountOpts = append(mountOpts, fmt.Sprintf("%#x", unknownFlags))
+ }
err = sysMount("none", c.Entry.Dir, "", flagsForMount, "")
- logger.Debugf("mount %q %q %q %d %q (error: %v)", "none", c.Entry.Dir, "", flagsForMount, "", err)
+ logger.Debugf("mount name:%q dir:%q type:%q opts:%s unparsed:%q (error: %v)",
+ "none", c.Entry.Dir, "", strings.Join(mountOpts, "|"), strings.Join(unparsed, ","), err)
}
if err == nil {
as.AddChange(c)
@@ -343,14 +356,18 @@
// Perform the raw unmount operation.
if err == nil {
err = sysUnmount(c.Entry.Dir, flags)
+ umountOpts, unknownFlags := mount.UnmountFlagsToOpts(flags)
+ if unknownFlags != 0 {
+ umountOpts = append(umountOpts, fmt.Sprintf("%#x", unknownFlags))
+ }
+ logger.Debugf("umount %q %s (error: %v)", c.Entry.Dir, strings.Join(umountOpts, "|"), err)
+ if err != nil {
+ return err
+ }
}
if err == nil {
as.AddChange(c)
}
- logger.Debugf("umount %q (error: %v)", c.Entry.Dir, err)
- if err != nil {
- return err
- }
// Open a path of the file we are considering the removal of.
path := c.Entry.Dir
@@ -362,6 +379,7 @@
defer sysClose(fd)
// Don't attempt to remove anything from squashfs.
+ // Note that this is not a perfect check and we also handle EROFS below.
var statfsBuf syscall.Statfs_t
err = sysFstatfs(fd, &statfsBuf)
if err != nil {
@@ -388,10 +406,28 @@
// no way to avoid a race here since there's no way to unlink a
// file solely by file descriptor.
err = osRemove(path)
+ logger.Debugf("remove %q (error: %v)", path, err)
// Unpack the low-level error that osRemove wraps into PathError.
if packed, ok := err.(*os.PathError); ok {
err = packed.Err
}
+ if err == syscall.EROFS {
+ // If the underlying medium is read-only then ignore the error.
+ // Instead of checking up front we just try to remove because
+ // of https://bugs.launchpad.net/snapd/+bug/1867752 which showed us
+ // two important properties:
+ // 1) inside containers we cannot detect squashfs reliably and
+ // will always see FUSE instead. The problem is that there's no
+ // indication as to what is really mounted via statfs(2) and
+ // we would have to deduce that from mountinfo, trusting
+ // that fuse. is not spoofed (as in, the name is not
+ // spoofed).
+ // 2) rmdir of a bind mount (from a normal writable filesystem like ext4)
+ // over a read-only filesystem also yields EROFS without any indication
+ // that this is to be expected.
+ logger.Debugf("cannot remove a mount point on read-only filesystem %q", path)
+ return nil
+ }
// If we were removing a directory but it was not empty then just
// ignore the error. This is the equivalent of the non-empty file
// check we do above. See rmdir(2) for explanation why we accept
@@ -399,6 +435,23 @@
if kind == "" && (err == syscall.ENOTEMPTY || err == syscall.EEXIST) {
return nil
}
+ if features.RobustMountNamespaceUpdates.IsEnabled() {
+ // FIXME: This should not be necessary. It is necessary because
+ // mimic construction code is not considering all layouts in tandem
+ // and doesn't know enough about base file system to construct
+ // mimics in the order that would prevent them from nesting.
+ //
+ // By ignoring EBUSY here and by continuing to tear down the mimic
+ // tmpfs entirely (without any reuse) we guarantee that at the end
+ // of the day the nested mimic case is entirely removed.
+ //
+ // In an ideal world we would model this better and could do
+ // without this edge case.
+ if (kind == "" || kind == "file") && err == syscall.EBUSY {
+ logger.Debugf("cannot remove busy mount point %q", path)
+ return nil
+ }
+ }
}
return err
case Keep:
@@ -408,16 +461,9 @@
return fmt.Errorf("cannot process mount change: unknown action: %q", c.Action)
}
-// NeededChanges computes the changes required to change current to desired mount entries.
-//
-// The current and desired profiles is a fstab like list of mount entries. The
-// lists are processed and a "diff" of mount changes is produced. The mount
-// changes, when applied in order, transform the current profile into the
-// desired profile.
-var NeededChanges = neededChangesImpl
-
-// neededChangesImpl is the real implementation of NeededChanges
-func neededChangesImpl(currentProfile, desiredProfile *osutil.MountProfile) []*Change {
+// neededChangesOld is the real implementation of NeededChanges
+// This function is used when RobustMountNamespaceUpdate is not enabled.
+func neededChangesOld(currentProfile, desiredProfile *osutil.MountProfile) []*Change {
// Copy both profiles as we will want to mutate them.
current := make([]osutil.MountEntry, len(currentProfile.Entries))
copy(current, currentProfile.Entries)
@@ -530,3 +576,83 @@
return changes
}
+
+// neededChangesNew is the real implementation of NeededChanges
+// This function is used when RobustMountNamespaceUpdate is enabled.
+func neededChangesNew(currentProfile, desiredProfile *osutil.MountProfile) []*Change {
+ // Copy both profiles as we will want to mutate them.
+ current := make([]osutil.MountEntry, len(currentProfile.Entries))
+ copy(current, currentProfile.Entries)
+ desired := make([]osutil.MountEntry, len(desiredProfile.Entries))
+ copy(desired, desiredProfile.Entries)
+
+ // Clean the directory part of both profiles. This is done so that we can
+ // easily test if a given directory is a subdirectory with
+ // strings.HasPrefix coupled with an extra slash character.
+ for i := range current {
+ current[i].Dir = filepath.Clean(current[i].Dir)
+ }
+ for i := range desired {
+ desired[i].Dir = filepath.Clean(desired[i].Dir)
+ }
+
+ // Sort both lists by directory name with implicit trailing slash.
+ sort.Sort(byOriginAndMagicDir(current))
+ sort.Sort(byOriginAndMagicDir(desired))
+
+ // We are now ready to compute the necessary mount changes.
+ var changes []*Change
+
+ // Unmount entries in reverse order, so that the most nested element is
+ // always processed first.
+ for i := len(current) - 1; i >= 0; i-- {
+ var entry osutil.MountEntry = current[i]
+ entry.Options = append([]string(nil), entry.Options...)
+ switch {
+ case entry.XSnapdSynthetic() && entry.Type == "tmpfs":
+ // Synthetic changes are rooted under a tmpfs, detach that tmpfs to
+ // remove them all.
+ if !entry.XSnapdDetach() {
+ entry.Options = append(entry.Options, osutil.XSnapdDetach())
+ }
+ case entry.XSnapdSynthetic():
+ // Consume all other syn ethic entries without emitting either a
+ // mount, unmount or keep change. This relies on the fact that all
+ // synthetic mounts are created by a mimic underneath a tmpfs that
+ // is detached, as coded above.
+ continue
+ case entry.OptBool("rbind") || entry.Type == "tmpfs":
+ // Recursive bind mounts and non-mimic tmpfs mounts need to be
+ // detached because they can contain other mount points that can
+ // otherwise propagate in a self-conflicting way.
+ if !entry.XSnapdDetach() {
+ entry.Options = append(entry.Options, osutil.XSnapdDetach())
+ }
+ }
+ // Unmount all changes that were not eliminated.
+ changes = append(changes, &Change{Action: Unmount, Entry: entry})
+ }
+
+ // Mount desired entries.
+ for i := range desired {
+ changes = append(changes, &Change{Action: Mount, Entry: desired[i]})
+ }
+
+ return changes
+}
+
+// NeededChanges computes the changes required to change current to desired mount entries.
+//
+// The algorithm differs depending on the value of the robust mount namespace
+// updates feature flag. If the flag is enabled then the current profile is
+// entirely undone and the desired profile is constructed from scratch.
+//
+// If the flag is disabled then a diff-like operation on the mount profile is
+// computed. Some of the mount entries from the current profile may be reused.
+// The diff approach doesn't function correctly in cases of nested mimics.
+var NeededChanges = func(current, desired *osutil.MountProfile) []*Change {
+ if features.RobustMountNamespaceUpdates.IsEnabled() {
+ return neededChangesNew(current, desired)
+ }
+ return neededChangesOld(current, desired)
+}
diff -Nru snapd-2.42.1+18.04/cmd/snap-update-ns/change_test.go snapd-2.45.1+18.04/cmd/snap-update-ns/change_test.go
--- snapd-2.42.1+18.04/cmd/snap-update-ns/change_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-update-ns/change_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -21,6 +21,7 @@
import (
"errors"
+ "io/ioutil"
"os"
"strings"
"syscall"
@@ -28,6 +29,8 @@
. "gopkg.in/check.v1"
update "github.com/snapcore/snapd/cmd/snap-update-ns"
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/features"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/testutil"
)
@@ -46,6 +49,8 @@
func (s *changeSuite) SetUpTest(c *C) {
s.BaseTest.SetUpTest(c)
+ // This isolates us from host's experimental settings.
+ dirs.SetRootDir(c.MkDir())
// Mock and record system interactions.
s.sys = &testutil.SyscallRecorder{}
s.BaseTest.AddCleanup(update.MockSystemCalls(s.sys))
@@ -55,6 +60,29 @@
func (s *changeSuite) TearDownTest(c *C) {
s.BaseTest.TearDownTest(c)
s.sys.CheckForStrayDescriptors(c)
+ dirs.SetRootDir("")
+}
+
+func (s *changeSuite) disableRobustMountNamespaceUpdates(c *C) {
+ if dirs.GlobalRootDir == "/" {
+ dirs.SetRootDir(c.MkDir())
+ s.BaseTest.AddCleanup(func() { dirs.SetRootDir("/") })
+ }
+ c.Assert(os.MkdirAll(dirs.FeaturesDir, 0755), IsNil)
+ err := os.Remove(features.RobustMountNamespaceUpdates.ControlFile())
+ if err != nil && !os.IsNotExist(err) {
+ c.Assert(err, IsNil)
+ }
+}
+
+func (s *changeSuite) enableRobustMountNamespaceUpdates(c *C) {
+ if dirs.GlobalRootDir == "/" {
+ dirs.SetRootDir(c.MkDir())
+ s.BaseTest.AddCleanup(func() { dirs.SetRootDir("/") })
+ }
+ c.Assert(os.MkdirAll(dirs.FeaturesDir, 0755), IsNil)
+ err := ioutil.WriteFile(features.RobustMountNamespaceUpdates.ControlFile(), []byte(nil), 0644)
+ c.Assert(err, IsNil)
}
func (s *changeSuite) TestFakeFileInfo(c *C) {
@@ -72,7 +100,19 @@
}
// When there are no profiles we don't do anything.
-func (s *changeSuite) TestNeededChangesNoProfiles(c *C) {
+func (s *changeSuite) TestNeededChangesNoProfilesOld(c *C) {
+ s.disableRobustMountNamespaceUpdates(c)
+
+ current := &osutil.MountProfile{}
+ desired := &osutil.MountProfile{}
+ changes := update.NeededChanges(current, desired)
+ c.Assert(changes, IsNil)
+}
+
+// When there are no profiles we don't do anything.
+func (s *changeSuite) TestNeededChangesNoProfilesNew(c *C) {
+ s.enableRobustMountNamespaceUpdates(c)
+
current := &osutil.MountProfile{}
desired := &osutil.MountProfile{}
changes := update.NeededChanges(current, desired)
@@ -80,7 +120,9 @@
}
// When the profiles are the same we don't do anything.
-func (s *changeSuite) TestNeededChangesNoChange(c *C) {
+func (s *changeSuite) TestNeededChangesNoChangeOld(c *C) {
+ s.disableRobustMountNamespaceUpdates(c)
+
current := &osutil.MountProfile{Entries: []osutil.MountEntry{{Dir: "/common/stuff"}}}
desired := &osutil.MountProfile{Entries: []osutil.MountEntry{{Dir: "/common/stuff"}}}
changes := update.NeededChanges(current, desired)
@@ -89,8 +131,33 @@
})
}
+func (s *changeSuite) TestNeededChangesNoChangeNew(c *C) {
+ s.enableRobustMountNamespaceUpdates(c)
+
+ current := &osutil.MountProfile{Entries: []osutil.MountEntry{{Dir: "/common/stuff"}}}
+ desired := &osutil.MountProfile{Entries: []osutil.MountEntry{{Dir: "/common/stuff"}}}
+ changes := update.NeededChanges(current, desired)
+ c.Assert(changes, DeepEquals, []*update.Change{
+ {Entry: osutil.MountEntry{Dir: "/common/stuff"}, Action: update.Unmount},
+ {Entry: osutil.MountEntry{Dir: "/common/stuff"}, Action: update.Mount},
+ })
+}
+
// When the content interface is connected we should mount the new entry.
-func (s *changeSuite) TestNeededChangesTrivialMount(c *C) {
+func (s *changeSuite) TestNeededChangesTrivialMountOld(c *C) {
+ s.disableRobustMountNamespaceUpdates(c)
+
+ current := &osutil.MountProfile{}
+ desired := &osutil.MountProfile{Entries: []osutil.MountEntry{{Dir: "/common/stuff"}}}
+ changes := update.NeededChanges(current, desired)
+ c.Assert(changes, DeepEquals, []*update.Change{
+ {Entry: desired.Entries[0], Action: update.Mount},
+ })
+}
+
+func (s *changeSuite) TestNeededChangesTrivialMountNew(c *C) {
+ s.enableRobustMountNamespaceUpdates(c)
+
current := &osutil.MountProfile{}
desired := &osutil.MountProfile{Entries: []osutil.MountEntry{{Dir: "/common/stuff"}}}
changes := update.NeededChanges(current, desired)
@@ -100,7 +167,20 @@
}
// When the content interface is disconnected we should unmount the mounted entry.
-func (s *changeSuite) TestNeededChangesTrivialUnmount(c *C) {
+func (s *changeSuite) TestNeededChangesTrivialUnmountOld(c *C) {
+ s.disableRobustMountNamespaceUpdates(c)
+
+ current := &osutil.MountProfile{Entries: []osutil.MountEntry{{Dir: "/common/stuff"}}}
+ desired := &osutil.MountProfile{}
+ changes := update.NeededChanges(current, desired)
+ c.Assert(changes, DeepEquals, []*update.Change{
+ {Entry: current.Entries[0], Action: update.Unmount},
+ })
+}
+
+func (s *changeSuite) TestNeededChangesTrivialUnmountNew(c *C) {
+ s.enableRobustMountNamespaceUpdates(c)
+
current := &osutil.MountProfile{Entries: []osutil.MountEntry{{Dir: "/common/stuff"}}}
desired := &osutil.MountProfile{}
changes := update.NeededChanges(current, desired)
@@ -110,7 +190,24 @@
}
// When umounting we unmount children before parents.
-func (s *changeSuite) TestNeededChangesUnmountOrder(c *C) {
+func (s *changeSuite) TestNeededChangesUnmountOrderOld(c *C) {
+ s.disableRobustMountNamespaceUpdates(c)
+
+ current := &osutil.MountProfile{Entries: []osutil.MountEntry{
+ {Dir: "/common/stuff/extra"},
+ {Dir: "/common/stuff"},
+ }}
+ desired := &osutil.MountProfile{}
+ changes := update.NeededChanges(current, desired)
+ c.Assert(changes, DeepEquals, []*update.Change{
+ {Entry: osutil.MountEntry{Dir: "/common/stuff/extra"}, Action: update.Unmount},
+ {Entry: osutil.MountEntry{Dir: "/common/stuff"}, Action: update.Unmount},
+ })
+}
+
+func (s *changeSuite) TestNeededChangesUnmountOrderNew(c *C) {
+ s.enableRobustMountNamespaceUpdates(c)
+
current := &osutil.MountProfile{Entries: []osutil.MountEntry{
{Dir: "/common/stuff/extra"},
{Dir: "/common/stuff"},
@@ -124,7 +221,24 @@
}
// When mounting we mount the parents before the children.
-func (s *changeSuite) TestNeededChangesMountOrder(c *C) {
+func (s *changeSuite) TestNeededChangesMountOrderOld(c *C) {
+ s.disableRobustMountNamespaceUpdates(c)
+
+ current := &osutil.MountProfile{}
+ desired := &osutil.MountProfile{Entries: []osutil.MountEntry{
+ {Dir: "/common/stuff/extra"},
+ {Dir: "/common/stuff"},
+ }}
+ changes := update.NeededChanges(current, desired)
+ c.Assert(changes, DeepEquals, []*update.Change{
+ {Entry: osutil.MountEntry{Dir: "/common/stuff"}, Action: update.Mount},
+ {Entry: osutil.MountEntry{Dir: "/common/stuff/extra"}, Action: update.Mount},
+ })
+}
+
+func (s *changeSuite) TestNeededChangesMountOrderNew(c *C) {
+ s.enableRobustMountNamespaceUpdates(c)
+
current := &osutil.MountProfile{}
desired := &osutil.MountProfile{Entries: []osutil.MountEntry{
{Dir: "/common/stuff/extra"},
@@ -138,7 +252,10 @@
}
// When parent changes we don't reuse its children
-func (s *changeSuite) TestNeededChangesChangedParentSameChild(c *C) {
+
+func (s *changeSuite) TestNeededChangesChangedParentSameChildOld(c *C) {
+ s.disableRobustMountNamespaceUpdates(c)
+
current := &osutil.MountProfile{Entries: []osutil.MountEntry{
{Dir: "/common/stuff", Name: "/dev/sda1"},
{Dir: "/common/stuff/extra"},
@@ -159,8 +276,34 @@
})
}
+func (s *changeSuite) TestNeededChangesChangedParentSameChildNew(c *C) {
+ s.enableRobustMountNamespaceUpdates(c)
+
+ current := &osutil.MountProfile{Entries: []osutil.MountEntry{
+ {Dir: "/common/stuff", Name: "/dev/sda1"},
+ {Dir: "/common/stuff/extra"},
+ {Dir: "/common/unrelated"},
+ }}
+ desired := &osutil.MountProfile{Entries: []osutil.MountEntry{
+ {Dir: "/common/stuff", Name: "/dev/sda2"},
+ {Dir: "/common/stuff/extra"},
+ {Dir: "/common/unrelated"},
+ }}
+ changes := update.NeededChanges(current, desired)
+ c.Assert(changes, DeepEquals, []*update.Change{
+ {Entry: osutil.MountEntry{Dir: "/common/unrelated"}, Action: update.Unmount},
+ {Entry: osutil.MountEntry{Dir: "/common/stuff/extra"}, Action: update.Unmount},
+ {Entry: osutil.MountEntry{Dir: "/common/stuff", Name: "/dev/sda1"}, Action: update.Unmount},
+ {Entry: osutil.MountEntry{Dir: "/common/stuff", Name: "/dev/sda2"}, Action: update.Mount},
+ {Entry: osutil.MountEntry{Dir: "/common/stuff/extra"}, Action: update.Mount},
+ {Entry: osutil.MountEntry{Dir: "/common/unrelated"}, Action: update.Mount},
+ })
+}
+
// When child changes we don't touch the unchanged parent
-func (s *changeSuite) TestNeededChangesSameParentChangedChild(c *C) {
+func (s *changeSuite) TestNeededChangesSameParentChangedChildOld(c *C) {
+ s.disableRobustMountNamespaceUpdates(c)
+
current := &osutil.MountProfile{Entries: []osutil.MountEntry{
{Dir: "/common/stuff"},
{Dir: "/common/stuff/extra", Name: "/dev/sda1"},
@@ -180,8 +323,34 @@
})
}
+func (s *changeSuite) TestNeededChangesSameParentChangedChildNew(c *C) {
+ s.enableRobustMountNamespaceUpdates(c)
+
+ current := &osutil.MountProfile{Entries: []osutil.MountEntry{
+ {Dir: "/common/stuff"},
+ {Dir: "/common/stuff/extra", Name: "/dev/sda1"},
+ {Dir: "/common/unrelated"},
+ }}
+ desired := &osutil.MountProfile{Entries: []osutil.MountEntry{
+ {Dir: "/common/stuff"},
+ {Dir: "/common/stuff/extra", Name: "/dev/sda2"},
+ {Dir: "/common/unrelated"},
+ }}
+ changes := update.NeededChanges(current, desired)
+ c.Assert(changes, DeepEquals, []*update.Change{
+ {Entry: osutil.MountEntry{Dir: "/common/unrelated"}, Action: update.Unmount},
+ {Entry: osutil.MountEntry{Dir: "/common/stuff/extra", Name: "/dev/sda1"}, Action: update.Unmount},
+ {Entry: osutil.MountEntry{Dir: "/common/stuff"}, Action: update.Unmount},
+ {Entry: osutil.MountEntry{Dir: "/common/stuff"}, Action: update.Mount},
+ {Entry: osutil.MountEntry{Dir: "/common/stuff/extra", Name: "/dev/sda2"}, Action: update.Mount},
+ {Entry: osutil.MountEntry{Dir: "/common/unrelated"}, Action: update.Mount},
+ })
+}
+
// Unused bind mount farms are unmounted.
-func (s *changeSuite) TestNeededChangesTmpfsBindMountFarmUnused(c *C) {
+func (s *changeSuite) TestNeededChangesTmpfsBindMountFarmUnusedOld(c *C) {
+ s.disableRobustMountNamespaceUpdates(c)
+
current := &osutil.MountProfile{Entries: []osutil.MountEntry{{
// The tmpfs that lets us write into immutable squashfs. We mock
// x-snapd.needed-by to the last entry in the current profile (the bind
@@ -232,7 +401,57 @@
})
}
-func (s *changeSuite) TestNeededChangesTmpfsBindMountFarmUsed(c *C) {
+func (s *changeSuite) TestNeededChangesTmpfsBindMountFarmUnusedNew(c *C) {
+ s.enableRobustMountNamespaceUpdates(c)
+
+ current := &osutil.MountProfile{Entries: []osutil.MountEntry{{
+ // The tmpfs that lets us write into immutable squashfs. We mock
+ // x-snapd.needed-by to the last entry in the current profile (the bind
+ // mount). Mark it synthetic since it is a helper mount that is needed
+ // to facilitate the following mounts.
+ Name: "tmpfs",
+ Dir: "/snap/name/42/subdir",
+ Type: "tmpfs",
+ Options: []string{"x-snapd.needed-by=/snap/name/42/subdir", "x-snapd.synthetic"},
+ }, {
+ // A bind mount to preserve a directory hidden by the tmpfs (the mount
+ // point is created elsewhere). We mock x-snapd.needed-by to the
+ // location of the bind mount below that is no longer desired.
+ Name: "/var/lib/snapd/hostfs/snap/name/42/subdir/existing",
+ Dir: "/snap/name/42/subdir/existing",
+ Options: []string{"bind", "ro", "x-snapd.needed-by=/snap/name/42/subdir", "x-snapd.synthetic"},
+ }, {
+ // A bind mount to put some content from another snap. The bind mount
+ // is nothing special but the fact that it is possible is the reason
+ // the two entries above exist. The mount point (created) is created
+ // elsewhere.
+ Name: "/snap/other/123/libs",
+ Dir: "/snap/name/42/subdir/created",
+ Options: []string{"bind", "ro"},
+ }}}
+
+ desired := &osutil.MountProfile{}
+
+ changes := update.NeededChanges(current, desired)
+
+ c.Assert(changes, DeepEquals, []*update.Change{
+ {Entry: osutil.MountEntry{
+ Name: "/snap/other/123/libs",
+ Dir: "/snap/name/42/subdir/created",
+ Options: []string{"bind", "ro"},
+ }, Action: update.Unmount},
+ {Entry: osutil.MountEntry{
+ Name: "tmpfs",
+ Dir: "/snap/name/42/subdir",
+ Type: "tmpfs",
+ Options: []string{"x-snapd.needed-by=/snap/name/42/subdir", "x-snapd.synthetic", "x-snapd.detach"},
+ }, Action: update.Unmount},
+ })
+}
+
+func (s *changeSuite) TestNeededChangesTmpfsBindMountFarmUsedOld(c *C) {
+ s.disableRobustMountNamespaceUpdates(c)
+
// NOTE: the current profile is the same as in the test
// TestNeededChangesTmpfsBindMountFarmUnused written above.
current := &osutil.MountProfile{Entries: []osutil.MountEntry{{
@@ -280,12 +499,64 @@
})
}
+func (s *changeSuite) TestNeededChangesTmpfsBindMountFarmUsedNew(c *C) {
+ s.enableRobustMountNamespaceUpdates(c)
+
+ // NOTE: the current profile is the same as in the test
+ // TestNeededChangesTmpfsBindMountFarmUnused written above.
+ current := &osutil.MountProfile{Entries: []osutil.MountEntry{{
+ Name: "tmpfs",
+ Dir: "/snap/name/42/subdir",
+ Type: "tmpfs",
+ Options: []string{"x-snapd.needed-by=/snap/name/42/subdir/created", "x-snapd.synthetic"},
+ }, {
+ Name: "/var/lib/snapd/hostfs/snap/name/42/subdir/existing",
+ Dir: "/snap/name/42/subdir/existing",
+ Options: []string{"bind", "ro", "x-snapd.needed-by=/snap/name/42/subdir/created", "x-snapd.synthetic"},
+ }, {
+ Name: "/snap/other/123/libs",
+ Dir: "/snap/name/42/subdir/created",
+ Options: []string{"bind", "ro"},
+ }}}
+
+ desired := &osutil.MountProfile{Entries: []osutil.MountEntry{{
+ // This is the only entry that we explicitly want but in order to
+ // support it we need to keep the remaining implicit entries.
+ Name: "/snap/other/123/libs",
+ Dir: "/snap/name/42/subdir/created",
+ Options: []string{"bind", "ro"},
+ }}}
+
+ changes := update.NeededChanges(current, desired)
+
+ c.Assert(changes, DeepEquals, []*update.Change{
+ {Entry: osutil.MountEntry{
+ Name: "/snap/other/123/libs",
+ Dir: "/snap/name/42/subdir/created",
+ Options: []string{"bind", "ro"},
+ }, Action: update.Unmount},
+ {Entry: osutil.MountEntry{
+ Name: "tmpfs",
+ Dir: "/snap/name/42/subdir",
+ Type: "tmpfs",
+ Options: []string{"x-snapd.needed-by=/snap/name/42/subdir/created", "x-snapd.synthetic", "x-snapd.detach"},
+ }, Action: update.Unmount},
+ {Entry: osutil.MountEntry{
+ Name: "/snap/other/123/libs",
+ Dir: "/snap/name/42/subdir/created",
+ Options: []string{"bind", "ro"},
+ }, Action: update.Mount},
+ })
+}
+
// cur = ['/a/b', '/a/b-1', '/a/b-1/3', '/a/b/c']
// des = ['/a/b', '/a/b-1', '/a/b/c'
//
// We are smart about comparing entries as directories. Here even though "/a/b"
// is a prefix of "/a/b-1" it is correctly reused.
-func (s *changeSuite) TestNeededChangesSmartEntryComparison(c *C) {
+func (s *changeSuite) TestNeededChangesSmartEntryComparisonOld(c *C) {
+ s.disableRobustMountNamespaceUpdates(c)
+
current := &osutil.MountProfile{Entries: []osutil.MountEntry{
{Dir: "/a/b", Name: "/dev/sda1"},
{Dir: "/a/b-1"},
@@ -309,8 +580,57 @@
})
}
+func (s *changeSuite) TestNeededChangesSmartEntryComparisonNew(c *C) {
+ s.enableRobustMountNamespaceUpdates(c)
+
+ current := &osutil.MountProfile{Entries: []osutil.MountEntry{
+ {Dir: "/a/b", Name: "/dev/sda1"},
+ {Dir: "/a/b-1"},
+ {Dir: "/a/b-1/3"},
+ {Dir: "/a/b/c"},
+ }}
+ desired := &osutil.MountProfile{Entries: []osutil.MountEntry{
+ {Dir: "/a/b", Name: "/dev/sda2"},
+ {Dir: "/a/b-1"},
+ {Dir: "/a/b/c"},
+ }}
+ changes := update.NeededChanges(current, desired)
+ c.Assert(changes, DeepEquals, []*update.Change{
+ {Entry: osutil.MountEntry{Dir: "/a/b/c"}, Action: update.Unmount},
+ {Entry: osutil.MountEntry{Dir: "/a/b", Name: "/dev/sda1"}, Action: update.Unmount},
+ {Entry: osutil.MountEntry{Dir: "/a/b-1/3"}, Action: update.Unmount},
+ {Entry: osutil.MountEntry{Dir: "/a/b-1"}, Action: update.Unmount},
+
+ {Entry: osutil.MountEntry{Dir: "/a/b-1"}, Action: update.Mount},
+ {Entry: osutil.MountEntry{Dir: "/a/b", Name: "/dev/sda2"}, Action: update.Mount},
+ {Entry: osutil.MountEntry{Dir: "/a/b/c"}, Action: update.Mount},
+ })
+}
+
// Parallel instance changes are executed first
-func (s *changeSuite) TestNeededChangesParallelInstancesManyComeFirst(c *C) {
+func (s *changeSuite) TestNeededChangesParallelInstancesManyComeFirstOld(c *C) {
+ s.disableRobustMountNamespaceUpdates(c)
+
+ desired := &osutil.MountProfile{Entries: []osutil.MountEntry{
+ {Dir: "/common/stuff", Name: "/dev/sda1"},
+ {Dir: "/common/stuff/extra"},
+ {Dir: "/common/unrelated"},
+ {Dir: "/foo/bar", Name: "/foo/bar_bar", Options: []string{osutil.XSnapdOriginOvername()}},
+ {Dir: "/snap/foo", Name: "/snap/foo_bar", Options: []string{osutil.XSnapdOriginOvername()}},
+ }}
+ changes := update.NeededChanges(&osutil.MountProfile{}, desired)
+ c.Assert(changes, DeepEquals, []*update.Change{
+ {Entry: osutil.MountEntry{Dir: "/foo/bar", Name: "/foo/bar_bar", Options: []string{osutil.XSnapdOriginOvername()}}, Action: update.Mount},
+ {Entry: osutil.MountEntry{Dir: "/snap/foo", Name: "/snap/foo_bar", Options: []string{osutil.XSnapdOriginOvername()}}, Action: update.Mount},
+ {Entry: osutil.MountEntry{Dir: "/common/stuff", Name: "/dev/sda1"}, Action: update.Mount},
+ {Entry: osutil.MountEntry{Dir: "/common/stuff/extra"}, Action: update.Mount},
+ {Entry: osutil.MountEntry{Dir: "/common/unrelated"}, Action: update.Mount},
+ })
+}
+
+func (s *changeSuite) TestNeededChangesParallelInstancesManyComeFirstNew(c *C) {
+ s.enableRobustMountNamespaceUpdates(c)
+
desired := &osutil.MountProfile{Entries: []osutil.MountEntry{
{Dir: "/common/stuff", Name: "/dev/sda1"},
{Dir: "/common/stuff/extra"},
@@ -329,7 +649,9 @@
}
// Parallel instance changes are kept if already present
-func (s *changeSuite) TestNeededChangesParallelInstancesKeep(c *C) {
+func (s *changeSuite) TestNeededChangesParallelInstancesKeepOld(c *C) {
+ s.disableRobustMountNamespaceUpdates(c)
+
desired := &osutil.MountProfile{Entries: []osutil.MountEntry{
{Dir: "/common/stuff", Name: "/dev/sda1"},
{Dir: "/common/unrelated"},
@@ -349,8 +671,34 @@
})
}
+func (s *changeSuite) TestNeededChangesParallelInstancesKeepNew(c *C) {
+ s.enableRobustMountNamespaceUpdates(c)
+
+ desired := &osutil.MountProfile{Entries: []osutil.MountEntry{
+ {Dir: "/common/stuff", Name: "/dev/sda1"},
+ {Dir: "/common/unrelated"},
+ {Dir: "/foo/bar", Name: "/foo/bar_bar", Options: []string{osutil.XSnapdOriginOvername()}},
+ {Dir: "/snap/foo", Name: "/snap/foo_bar", Options: []string{osutil.XSnapdOriginOvername()}},
+ }}
+ current := &osutil.MountProfile{Entries: []osutil.MountEntry{
+ {Dir: "/snap/foo", Name: "/snap/foo_bar", Options: []string{osutil.XSnapdOriginOvername()}},
+ {Dir: "/foo/bar", Name: "/foo/bar_bar", Options: []string{osutil.XSnapdOriginOvername()}},
+ }}
+ changes := update.NeededChanges(current, desired)
+ c.Assert(changes, DeepEquals, []*update.Change{
+ {Entry: osutil.MountEntry{Dir: "/snap/foo", Name: "/snap/foo_bar", Options: []string{osutil.XSnapdOriginOvername()}}, Action: update.Unmount},
+ {Entry: osutil.MountEntry{Dir: "/foo/bar", Name: "/foo/bar_bar", Options: []string{osutil.XSnapdOriginOvername()}}, Action: update.Unmount},
+ {Entry: osutil.MountEntry{Dir: "/foo/bar", Name: "/foo/bar_bar", Options: []string{osutil.XSnapdOriginOvername()}}, Action: update.Mount},
+ {Entry: osutil.MountEntry{Dir: "/snap/foo", Name: "/snap/foo_bar", Options: []string{osutil.XSnapdOriginOvername()}}, Action: update.Mount},
+ {Entry: osutil.MountEntry{Dir: "/common/stuff", Name: "/dev/sda1"}, Action: update.Mount},
+ {Entry: osutil.MountEntry{Dir: "/common/unrelated"}, Action: update.Mount},
+ })
+}
+
// Parallel instance with mounts inside
-func (s *changeSuite) TestNeededChangesParallelInstancesInsideMount(c *C) {
+func (s *changeSuite) TestNeededChangesParallelInstancesInsideMountOld(c *C) {
+ s.disableRobustMountNamespaceUpdates(c)
+
desired := &osutil.MountProfile{Entries: []osutil.MountEntry{
{Dir: "/foo/bar/baz"},
{Dir: "/foo/bar", Name: "/foo/bar_bar", Options: []string{osutil.XSnapdOriginOvername()}},
@@ -370,6 +718,30 @@
})
}
+func (s *changeSuite) TestNeededChangesParallelInstancesInsideMountNew(c *C) {
+ s.enableRobustMountNamespaceUpdates(c)
+
+ desired := &osutil.MountProfile{Entries: []osutil.MountEntry{
+ {Dir: "/foo/bar/baz"},
+ {Dir: "/foo/bar", Name: "/foo/bar_bar", Options: []string{osutil.XSnapdOriginOvername()}},
+ {Dir: "/snap/foo", Name: "/snap/foo_bar", Options: []string{osutil.XSnapdOriginOvername()}},
+ }}
+ current := &osutil.MountProfile{Entries: []osutil.MountEntry{
+ {Dir: "/foo/bar/zed"},
+ {Dir: "/snap/foo", Name: "/snap/foo_bar", Options: []string{osutil.XSnapdOriginOvername()}},
+ {Dir: "/foo/bar", Name: "/foo/bar_bar", Options: []string{osutil.XSnapdOriginOvername()}},
+ }}
+ changes := update.NeededChanges(current, desired)
+ c.Assert(changes, DeepEquals, []*update.Change{
+ {Entry: osutil.MountEntry{Dir: "/foo/bar/zed"}, Action: update.Unmount},
+ {Entry: osutil.MountEntry{Dir: "/snap/foo", Name: "/snap/foo_bar", Options: []string{osutil.XSnapdOriginOvername()}}, Action: update.Unmount},
+ {Entry: osutil.MountEntry{Dir: "/foo/bar", Name: "/foo/bar_bar", Options: []string{osutil.XSnapdOriginOvername()}}, Action: update.Unmount},
+ {Entry: osutil.MountEntry{Dir: "/foo/bar", Name: "/foo/bar_bar", Options: []string{osutil.XSnapdOriginOvername()}}, Action: update.Mount},
+ {Entry: osutil.MountEntry{Dir: "/snap/foo", Name: "/snap/foo_bar", Options: []string{osutil.XSnapdOriginOvername()}}, Action: update.Mount},
+ {Entry: osutil.MountEntry{Dir: "/foo/bar/baz"}, Action: update.Mount},
+ })
+}
+
func mustReadProfile(profileStr string) *osutil.MountProfile {
profile, err := osutil.ReadMountProfile(strings.NewReader(profileStr))
if err != nil {
@@ -378,81 +750,152 @@
return profile
}
-func (s *changeSuite) TestRuntimeUsingSymlinks(c *C) {
+func (s *changeSuite) TestRuntimeUsingSymlinksOld(c *C) {
+ s.disableRobustMountNamespaceUpdates(c)
+
// We start with a runtime shared from one snap to another and then exposed
// to /opt with a symbolic link. This is the initial state of the
// application in version v1.
- initial := mustReadProfile("")
- desiredV1 := mustReadProfile(
- "none /opt/runtime none x-snapd.kind=symlink,x-snapd.symlink=/snap/app/x1/runtime,x-snapd.origin=layout 0 0\n" +
- "/snap/runtime/x1/opt/runtime /snap/app/x1/runtime none bind,ro 0 0\n")
+ initial := &osutil.MountProfile{}
+ desiredV1 := &osutil.MountProfile{Entries: []osutil.MountEntry{
+ {Name: "none", Dir: "/opt/runtime", Type: "none", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/snap/app/x1/runtime", "x-snapd.origin=layout"}},
+ {Name: "/snap/runtime/x1/opt/runtime", Dir: "/snap/app/x1/runtime", Type: "none", Options: []string{"bind", "ro"}},
+ }}
// The changes we compute are trivial, simply perform each operation in order.
changes := update.NeededChanges(initial, desiredV1)
c.Assert(changes, DeepEquals, []*update.Change{
- {Entry: desiredV1.Entries[0], Action: update.Mount},
- {Entry: desiredV1.Entries[1], Action: update.Mount},
+ {Entry: osutil.MountEntry{Name: "none", Dir: "/opt/runtime", Type: "none", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/snap/app/x1/runtime", "x-snapd.origin=layout"}}, Action: update.Mount},
+ {Entry: osutil.MountEntry{Name: "/snap/runtime/x1/opt/runtime", Dir: "/snap/app/x1/runtime", Type: "none", Options: []string{"bind", "ro"}}, Action: update.Mount},
})
// After performing both changes we have a new synthesized entry. We get an
// extra writable mimic over /opt so that we can add our symlink. The
// content sharing into $SNAP is applied as expected since the snap ships
// the required mount point.
- currentV1 := mustReadProfile(
- "/snap/runtime/x1/opt/runtime /snap/app/x1/runtime none bind,ro 0 0\n" +
- "none /opt/runtime none x-snapd.kind=symlink,x-snapd.symlink=/snap/app/x1/runtime,x-snapd.origin=layout 0 0\n" +
- "tmpfs /opt tmpfs x-snapd.synthetic,x-snapd.needed-by=/opt/runtime,mode=0755,uid=0,gid=0 0")
+ currentV1 := &osutil.MountProfile{Entries: []osutil.MountEntry{
+ {Name: "/snap/runtime/x1/opt/runtime", Dir: "/snap/app/x1/runtime", Type: "none", Options: []string{"bind", "ro"}},
+ {Name: "none", Dir: "/opt/runtime", Type: "none", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/snap/app/x1/runtime", "x-snapd.origin=layout"}},
+ {Name: "tmpfs", Dir: "/opt", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/opt/runtime", "mode=0755", "uid=0", "gid=0"}},
+ }}
+ // We now proceed to replace app v1 with v2 which uses a bind mount instead
+ // of a symlink. First, let's start with the updated desired profile:
+ desiredV2 := &osutil.MountProfile{Entries: []osutil.MountEntry{
+ {Name: "/snap/app/x2/runtime", Dir: "/opt/runtime", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}},
+ {Name: "/snap/runtime/x1/opt/runtime", Dir: "/snap/app/x2/runtime", Type: "none", Options: []string{"bind", "ro"}},
+ }}
+ // Let's see what the update algorithm thinks.
+ changes = update.NeededChanges(currentV1, desiredV2)
+ c.Assert(changes, DeepEquals, []*update.Change{
+ // We are dropping the content interface bind mount because app changed revision
+ {Entry: osutil.MountEntry{Name: "/snap/runtime/x1/opt/runtime", Dir: "/snap/app/x1/runtime", Type: "none", Options: []string{"bind", "ro", "x-snapd.detach"}}, Action: update.Unmount},
+ // We are not keeping /opt, it's safer this way.
+ {Entry: osutil.MountEntry{Name: "none", Dir: "/opt/runtime", Type: "none", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/snap/app/x1/runtime", "x-snapd.origin=layout"}}, Action: update.Unmount},
+ // We are re-creating /opt from scratch.
+ {Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/opt", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/opt/runtime", "mode=0755", "uid=0", "gid=0"}}, Action: update.Keep},
+ // We are adding a new bind mount for /opt/runtime
+ {Entry: osutil.MountEntry{Name: "/snap/app/x2/runtime", Dir: "/opt/runtime", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}}, Action: update.Mount},
+ // We also adding the updated path of the content interface (for revision x2)
+ {Entry: osutil.MountEntry{Name: "/snap/runtime/x1/opt/runtime", Dir: "/snap/app/x2/runtime", Type: "none", Options: []string{"bind", "ro"}}, Action: update.Mount},
+ })
+
+ // After performing all those changes this is the profile we observe.
+ currentV2 := &osutil.MountProfile{Entries: []osutil.MountEntry{
+ {Name: "tmpfs", Dir: "/opt", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/opt/runtime", "mode=0755", "uid=0", "gid=0", "x-snapd.detach"}},
+ {Name: "/snap/app/x2/runtime", Dir: "/opt/runtime", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}},
+ {Name: "/snap/runtime/x1/opt/runtime", Dir: "/snap/app/x2/runtime", Type: "none", Options: []string{"bind", "ro"}},
+ }}
+
+ // So far so good. To trigger the issue we now revert or refresh to v1
+ // again. Let's see what happens here. The desired profiles are already
+ // known so let's see what the algorithm thinks now.
+ changes = update.NeededChanges(currentV2, desiredV1)
+ c.Assert(changes, DeepEquals, []*update.Change{
+ // We are, again, dropping the content interface bind mount because app changed revision
+ {Entry: osutil.MountEntry{Name: "/snap/runtime/x1/opt/runtime", Dir: "/snap/app/x2/runtime", Type: "none", Options: []string{"bind", "ro", "x-snapd.detach"}}, Action: update.Unmount},
+ // We are also dropping the bind mount from /opt/runtime since we want a symlink instead
+ {Entry: osutil.MountEntry{Name: "/snap/app/x2/runtime", Dir: "/opt/runtime", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout", "x-snapd.detach"}}, Action: update.Unmount},
+ // Again, recreate the tmpfs.
+ {Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/opt", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/opt/runtime", "mode=0755", "uid=0", "gid=0", "x-snapd.detach"}}, Action: update.Keep},
+ // We are providing a symlink /opt/runtime -> to $SNAP/runtime.
+ {Entry: osutil.MountEntry{Name: "none", Dir: "/opt/runtime", Type: "none", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/snap/app/x1/runtime", "x-snapd.origin=layout"}}, Action: update.Mount},
+ // We are bind mounting the runtime from another snap into $SNAP/runtime
+ {Entry: osutil.MountEntry{Name: "/snap/runtime/x1/opt/runtime", Dir: "/snap/app/x1/runtime", Type: "none", Options: []string{"bind", "ro"}}, Action: update.Mount},
+ })
+
+ // The problem is that the tmpfs contains leftovers from the things we
+ // created and those prevent the execution of this mount profile.
+}
+
+func (s *changeSuite) TestRuntimeUsingSymlinksNew(c *C) {
+ s.enableRobustMountNamespaceUpdates(c)
+
+ // We start with a runtime shared from one snap to another and then exposed
+ // to /opt with a symbolic link. This is the initial state of the
+ // application in version v1.
+ initial := &osutil.MountProfile{}
+ desiredV1 := &osutil.MountProfile{Entries: []osutil.MountEntry{
+ {Name: "none", Dir: "/opt/runtime", Type: "none", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/snap/app/x1/runtime", "x-snapd.origin=layout"}},
+ {Name: "/snap/runtime/x1/opt/runtime", Dir: "/snap/app/x1/runtime", Type: "none", Options: []string{"bind", "ro"}},
+ }}
+ // The changes we compute are trivial, simply perform each operation in order.
+ changes := update.NeededChanges(initial, desiredV1)
+ c.Assert(changes, DeepEquals, []*update.Change{
+ {Entry: osutil.MountEntry{Name: "none", Dir: "/opt/runtime", Type: "none", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/snap/app/x1/runtime", "x-snapd.origin=layout"}}, Action: update.Mount},
+ {Entry: osutil.MountEntry{Name: "/snap/runtime/x1/opt/runtime", Dir: "/snap/app/x1/runtime", Type: "none", Options: []string{"bind", "ro"}}, Action: update.Mount},
+ })
+ // After performing both changes we have a new synthesized entry. We get an
+ // extra writable mimic over /opt so that we can add our symlink. The
+ // content sharing into $SNAP is applied as expected since the snap ships
+ // the required mount point.
+ currentV1 := &osutil.MountProfile{Entries: []osutil.MountEntry{
+ {Name: "/snap/runtime/x1/opt/runtime", Dir: "/snap/app/x1/runtime", Type: "none", Options: []string{"bind", "ro"}},
+ {Name: "none", Dir: "/opt/runtime", Type: "none", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/snap/app/x1/runtime", "x-snapd.origin=layout"}},
+ {Name: "tmpfs", Dir: "/opt", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/opt/runtime", "mode=0755", "uid=0", "gid=0"}},
+ }}
// We now proceed to replace app v1 with v2 which uses a bind mount instead
// of a symlink. First, let's start with the updated desired profile:
- desiredV2 := mustReadProfile(
- "/snap/app/x2/runtime /opt/runtime none rbind,rw,x-snapd.origin=layout 0 0\n" +
- "/snap/runtime/x1/opt/runtime /snap/app/x2/runtime none bind,ro 0 0\n")
+ desiredV2 := &osutil.MountProfile{Entries: []osutil.MountEntry{
+ {Name: "/snap/app/x2/runtime", Dir: "/opt/runtime", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}},
+ {Name: "/snap/runtime/x1/opt/runtime", Dir: "/snap/app/x2/runtime", Type: "none", Options: []string{"bind", "ro"}},
+ }}
// Let's see what the update algorithm thinks.
changes = update.NeededChanges(currentV1, desiredV2)
- // e0 and e1 are like currentV1.Entries[0] and [1] but with different options.
- currentV1Entries0 := currentV1.Entries[0]
- currentV1Entries0.Options = append([]string(nil), currentV1Entries0.Options...)
- currentV1Entries0.Options = append(currentV1Entries0.Options, osutil.XSnapdDetach())
c.Assert(changes, DeepEquals, []*update.Change{
// We are dropping the content interface bind mount because app changed revision
- {Entry: currentV1Entries0, Action: update.Unmount},
- // We are also dropping the symlink we had in /opt/runtime
- {Entry: currentV1.Entries[1], Action: update.Unmount},
- // But, we are keeping the /opt tmpfs because we still want /opt/runtime to exist (neat!)
- {Entry: currentV1.Entries[2], Action: update.Keep},
+ {Entry: osutil.MountEntry{Name: "/snap/runtime/x1/opt/runtime", Dir: "/snap/app/x1/runtime", Type: "none", Options: []string{"bind", "ro"}}, Action: update.Unmount},
+ // We are not keeping /opt, it's safer this way.
+ {Entry: osutil.MountEntry{Name: "none", Dir: "/opt/runtime", Type: "none", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/snap/app/x1/runtime", "x-snapd.origin=layout"}}, Action: update.Unmount},
+ // We are re-creating /opt from scratch.
+ {Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/opt", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/opt/runtime", "mode=0755", "uid=0", "gid=0", "x-snapd.detach"}}, Action: update.Unmount},
// We are adding a new bind mount for /opt/runtime
- {Entry: desiredV2.Entries[0], Action: update.Mount},
+ {Entry: osutil.MountEntry{Name: "/snap/app/x2/runtime", Dir: "/opt/runtime", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}}, Action: update.Mount},
// We also adding the updated path of the content interface (for revision x2)
- {Entry: desiredV2.Entries[1], Action: update.Mount},
+ {Entry: osutil.MountEntry{Name: "/snap/runtime/x1/opt/runtime", Dir: "/snap/app/x2/runtime", Type: "none", Options: []string{"bind", "ro"}}, Action: update.Mount},
})
// After performing all those changes this is the profile we observe.
- currentV2 := mustReadProfile(
- "tmpfs /opt tmpfs x-snapd.synthetic,x-snapd.needed-by=/opt/runtime,mode=0755,uid=0,gid=0 0 0\n" +
- "/snap/app/x2/runtime /opt/runtime none rbind,rw,x-snapd.origin=layout 0 0\n" +
- "/snap/runtime/x1/opt/runtime /snap/app/x2/runtime none bind,ro 0 0\n")
+ currentV2 := &osutil.MountProfile{Entries: []osutil.MountEntry{
+ {Name: "tmpfs", Dir: "/opt", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/opt/runtime", "mode=0755", "uid=0", "gid=0", "x-snapd.detach"}},
+ {Name: "/snap/app/x2/runtime", Dir: "/opt/runtime", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}},
+ {Name: "/snap/runtime/x1/opt/runtime", Dir: "/snap/app/x2/runtime", Type: "none", Options: []string{"bind", "ro"}},
+ }}
// So far so good. To trigger the issue we now revert or refresh to v1
// again. Let's see what happens here. The desired profiles are already
// known so let's see what the algorithm thinks now.
changes = update.NeededChanges(currentV2, desiredV1)
- currentV2Entries1 := currentV2.Entries[1]
- currentV2Entries1.Options = append([]string(nil), currentV2Entries1.Options...)
- currentV2Entries1.Options = append(currentV2Entries1.Options, osutil.XSnapdDetach())
- currentV2Entries2 := currentV2.Entries[2]
- currentV2Entries2.Options = append([]string(nil), currentV2Entries2.Options...)
- currentV2Entries2.Options = append(currentV2Entries2.Options, osutil.XSnapdDetach())
c.Assert(changes, DeepEquals, []*update.Change{
// We are, again, dropping the content interface bind mount because app changed revision
- {Entry: currentV2Entries2, Action: update.Unmount},
+ {Entry: osutil.MountEntry{Name: "/snap/runtime/x1/opt/runtime", Dir: "/snap/app/x2/runtime", Type: "none", Options: []string{"bind", "ro"}}, Action: update.Unmount},
// We are also dropping the bind mount from /opt/runtime since we want a symlink instead
- {Entry: currentV2Entries1, Action: update.Unmount},
- // Again, we reuse the tmpfs.
- {Entry: currentV2.Entries[0], Action: update.Keep},
+ {Entry: osutil.MountEntry{Name: "/snap/app/x2/runtime", Dir: "/opt/runtime", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout", "x-snapd.detach"}}, Action: update.Unmount},
+ // Again, recreate the tmpfs.
+ {Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/opt", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/opt/runtime", "mode=0755", "uid=0", "gid=0", "x-snapd.detach"}}, Action: update.Unmount},
// We are providing a symlink /opt/runtime -> to $SNAP/runtime.
- {Entry: desiredV1.Entries[0], Action: update.Mount},
+ {Entry: osutil.MountEntry{Name: "none", Dir: "/opt/runtime", Type: "none", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/snap/app/x1/runtime", "x-snapd.origin=layout"}}, Action: update.Mount},
// We are bind mounting the runtime from another snap into $SNAP/runtime
- {Entry: desiredV1.Entries[1], Action: update.Mount},
+ {Entry: osutil.MountEntry{Name: "/snap/runtime/x1/opt/runtime", Dir: "/snap/app/x1/runtime", Type: "none", Options: []string{"bind", "ro"}}, Action: update.Mount},
})
// The problem is that the tmpfs contains leftovers from the things we
@@ -2378,13 +2821,46 @@
})
}
+// Change.Perform wants to remove a directory which is a bind mount of ext4 from onto squashfs.
+func (s *changeSuite) TestPerformRmdirOnExt4OnSquashfs(c *C) {
+ defer s.as.MockUnrestrictedPaths("/tmp/")() // Allow writing to /tmp
+
+ s.sys.InsertFstatResult(`fstat 4 `, syscall.Stat_t{})
+ // Pretend that /root is an ext4 bind mount from somewhere.
+ s.sys.InsertFstatfsResult(`fstatfs 4 `, syscall.Statfs_t{Type: update.Ext4Magic})
+ // Pretend that removing /root returns EROFS (it really can!).
+ s.sys.InsertFault(`remove "/root"`, syscall.EROFS)
+
+ // This is the change we want to perform:
+ // - unmount a layout from /root
+ chg := &update.Change{Action: update.Unmount, Entry: osutil.MountEntry{Name: "unused", Dir: "/root", Options: []string{"x-snapd.origin=layout"}}}
+ synth, err := chg.Perform(s.as)
+ // The change succeeded even though we were unable to remove the /root
+ // directory because it is backed by a squashfs, which is not modelled by
+ // this test but is modelled by the integration test.
+ c.Check(err, IsNil)
+ c.Check(synth, HasLen, 0)
+
+ // And this is exactly how we made that happen:
+ c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
+ {C: `unmount "/root" UMOUNT_NOFOLLOW`},
+ {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3},
+ {C: `openat 3 "root" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4},
+ {C: `fstat 4 `, R: syscall.Stat_t{}},
+ {C: `close 3`},
+ {C: `fstatfs 4 `, R: syscall.Statfs_t{Type: update.Ext4Magic}},
+ {C: `remove "/root"`, E: syscall.EROFS},
+ {C: `close 4`},
+ })
+}
+
// ###########
// Topic: misc
// ###########
// Change.Perform handles unknown actions.
func (s *changeSuite) TestPerformUnknownAction(c *C) {
- chg := &update.Change{Action: update.Action(42)}
+ chg := &update.Change{Action: update.Action("42")}
synth, err := chg.Perform(s.as)
c.Assert(err, ErrorMatches, `cannot process mount change: unknown action: .*`)
c.Assert(synth, HasLen, 0)
diff -Nru snapd-2.42.1+18.04/cmd/snap-update-ns/common.go snapd-2.45.1+18.04/cmd/snap-update-ns/common.go
--- snapd-2.42.1+18.04/cmd/snap-update-ns/common.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-update-ns/common.go 2020-06-05 13:13:49.000000000 +0000
@@ -25,6 +25,7 @@
"github.com/snapcore/snapd/cmd/snaplock"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/sandbox/cgroup"
)
type CommonProfileUpdateContext struct {
@@ -78,7 +79,7 @@
// introduce a symlink that would cause us to mount something other
// than what we expected).
logger.Debugf("freezing processes of snap %q", instanceName)
- if err := freezeSnapProcesses(instanceName); err != nil {
+ if err := cgroup.FreezeSnapProcesses(instanceName); err != nil {
// If we cannot freeze the processes we should drop the lock.
lock.Close()
return nil, err
@@ -88,7 +89,7 @@
logger.Debugf("unlocking mount namespace of snap %q", instanceName)
lock.Close()
logger.Debugf("thawing processes of snap %q", instanceName)
- thawSnapProcesses(instanceName)
+ cgroup.ThawSnapProcesses(instanceName)
}
return unlock, nil
}
diff -Nru snapd-2.42.1+18.04/cmd/snap-update-ns/common_test.go snapd-2.45.1+18.04/cmd/snap-update-ns/common_test.go
--- snapd-2.42.1+18.04/cmd/snap-update-ns/common_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-update-ns/common_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -31,6 +31,7 @@
"github.com/snapcore/snapd/cmd/snaplock"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/osutil"
+ "github.com/snapcore/snapd/sandbox/cgroup"
"github.com/snapcore/snapd/testutil"
)
@@ -55,7 +56,7 @@
func (s *commonSuite) TestLock(c *C) {
// Mock away real freezer code, allowing test code to return an error when freezing.
var freezingError error
- restore := update.MockFreezing(func(string) error { return freezingError }, func(string) error { return nil })
+ restore := cgroup.MockFreezing(func(string) error { return freezingError }, func(string) error { return nil })
defer restore()
// Mock system directories, we use the lock directory.
dirs.SetRootDir(s.dir)
diff -Nru snapd-2.42.1+18.04/cmd/snap-update-ns/debug.go snapd-2.45.1+18.04/cmd/snap-update-ns/debug.go
--- snapd-2.42.1+18.04/cmd/snap-update-ns/debug.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-update-ns/debug.go 1970-01-01 00:00:00.000000000 +0000
@@ -1,47 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2017 Canonical Ltd
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-
-package main
-
-import (
- "github.com/snapcore/snapd/logger"
- "github.com/snapcore/snapd/osutil"
-)
-
-func debugShowProfile(profile *osutil.MountProfile, header string) {
- if len(profile.Entries) > 0 {
- logger.Debugf("%s:", header)
- for _, entry := range profile.Entries {
- logger.Debugf("\t%s", entry)
- }
- } else {
- logger.Debugf("%s: (none)", header)
- }
-}
-
-func debugShowChanges(changes []*Change, header string) {
- if len(changes) > 0 {
- logger.Debugf("%s:", header)
- for _, change := range changes {
- logger.Debugf("\t%s", change)
- }
- } else {
- logger.Debugf("%s: (none)", header)
- }
-}
diff -Nru snapd-2.42.1+18.04/cmd/snap-update-ns/export_test.go snapd-2.45.1+18.04/cmd/snap-update-ns/export_test.go
--- snapd-2.42.1+18.04/cmd/snap-update-ns/export_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-update-ns/export_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -23,8 +23,6 @@
"os"
"syscall"
- . "gopkg.in/check.v1"
-
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/osutil/sys"
)
@@ -34,10 +32,6 @@
ValidateInstanceName = validateInstanceName
ProcessArguments = processArguments
- // freezer
- FreezeSnapProcesses = freezeSnapProcesses
- ThawSnapProcesses = thawSnapProcesses
-
// utils
PlanWritableMimic = planWritableMimic
ExecWritableMimic = execWritableMimic
@@ -149,31 +143,6 @@
}
}
-func MockFreezerCgroupDir(c *C) (restore func()) {
- old := freezerCgroupDir
- freezerCgroupDir = c.MkDir()
- return func() {
- freezerCgroupDir = old
- }
-}
-
-func FreezerCgroupDir() string {
- return freezerCgroupDir
-}
-
-func MockFreezing(freeze, thaw func(snapName string) error) (restore func()) {
- oldFreeze := freezeSnapProcesses
- oldThaw := thawSnapProcesses
-
- freezeSnapProcesses = freeze
- thawSnapProcesses = thaw
-
- return func() {
- freezeSnapProcesses = oldFreeze
- thawSnapProcesses = oldThaw
- }
-}
-
func MockChangePerform(f func(chg *Change, as *Assumptions) ([]*Change, error)) func() {
origChangePerform := changePerform
changePerform = f
diff -Nru snapd-2.42.1+18.04/cmd/snap-update-ns/freezer.go snapd-2.45.1+18.04/cmd/snap-update-ns/freezer.go
--- snapd-2.42.1+18.04/cmd/snap-update-ns/freezer.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-update-ns/freezer.go 1970-01-01 00:00:00.000000000 +0000
@@ -1,79 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2017 Canonical Ltd
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-
-package main
-
-import (
- "bytes"
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "time"
-)
-
-var freezerCgroupDir = "/sys/fs/cgroup/freezer"
-
-var (
- freezeSnapProcesses = freezeSnapProcessesImpl
- thawSnapProcesses = thawSnapProcessesImpl
-)
-
-// freezeSnapProcessesImpl freezes all the processes originating from the given snap.
-// Processes are frozen regardless of which particular snap application they
-// originate from.
-func freezeSnapProcessesImpl(snapName string) error {
- fname := filepath.Join(freezerCgroupDir, fmt.Sprintf("snap.%s", snapName), "freezer.state")
- if err := ioutil.WriteFile(fname, []byte("FROZEN"), 0644); err != nil && os.IsNotExist(err) {
- // When there's no freezer cgroup we don't have to freeze anything.
- // This can happen when no process belonging to a given snap has been
- // started yet.
- return nil
- } else if err != nil {
- return fmt.Errorf("cannot freeze processes of snap %q, %v", snapName, err)
- }
- for i := 0; i < 30; i++ {
- data, err := ioutil.ReadFile(fname)
- if err != nil {
- return fmt.Errorf("cannot determine the freeze state of processes of snap %q, %v", snapName, err)
- }
- // If the cgroup is still freezing then wait a moment and try again.
- if bytes.Equal(data, []byte("FREEZING")) {
- time.Sleep(100 * time.Millisecond)
- continue
- }
- return nil
- }
- // If we got here then we timed out after seeing FREEZING for too long.
- thawSnapProcesses(snapName) // ignore the error, this is best-effort.
- return fmt.Errorf("cannot finish freezing processes of snap %q", snapName)
-}
-
-func thawSnapProcessesImpl(snapName string) error {
- fname := filepath.Join(freezerCgroupDir, fmt.Sprintf("snap.%s", snapName), "freezer.state")
- if err := ioutil.WriteFile(fname, []byte("THAWED"), 0644); err != nil && os.IsNotExist(err) {
- // When there's no freezer cgroup we don't have to thaw anything.
- // This can happen when no process belonging to a given snap has been
- // started yet.
- return nil
- } else if err != nil {
- return fmt.Errorf("cannot thaw processes of snap %q", snapName)
- }
- return nil
-}
diff -Nru snapd-2.42.1+18.04/cmd/snap-update-ns/freezer_test.go snapd-2.45.1+18.04/cmd/snap-update-ns/freezer_test.go
--- snapd-2.42.1+18.04/cmd/snap-update-ns/freezer_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-update-ns/freezer_test.go 1970-01-01 00:00:00.000000000 +0000
@@ -1,91 +0,0 @@
-// -*- Mode: Go; indent-tabs-mode: t -*-
-
-/*
- * Copyright (C) 2017 Canonical Ltd
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 3 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-
-package main_test
-
-import (
- "fmt"
- "os"
- "path/filepath"
-
- . "gopkg.in/check.v1"
-
- update "github.com/snapcore/snapd/cmd/snap-update-ns"
- "github.com/snapcore/snapd/testutil"
-)
-
-type freezerSuite struct{}
-
-var _ = Suite(&freezerSuite{})
-
-func (s *freezerSuite) TestFreezeSnapProcesses(c *C) {
- restore := update.MockFreezerCgroupDir(c)
- defer restore()
-
- n := "foo" // snap name
- p := filepath.Join(update.FreezerCgroupDir(), fmt.Sprintf("snap.%s", n)) // snap freezer cgroup
- f := filepath.Join(p, "freezer.state") // freezer.state file of the cgroup
-
- // When the freezer cgroup filesystem doesn't exist we do nothing at all.
- c.Assert(update.FreezeSnapProcesses(n), IsNil)
- _, err := os.Stat(f)
- c.Assert(os.IsNotExist(err), Equals, true)
-
- // When the freezer cgroup filesystem exists but the particular cgroup
- // doesn't exist we don nothing at all.
- c.Assert(os.MkdirAll(update.FreezerCgroupDir(), 0755), IsNil)
- c.Assert(update.FreezeSnapProcesses(n), IsNil)
- _, err = os.Stat(f)
- c.Assert(os.IsNotExist(err), Equals, true)
-
- // When the cgroup exists we write FROZEN the freezer.state file.
- c.Assert(os.MkdirAll(p, 0755), IsNil)
- c.Assert(update.FreezeSnapProcesses(n), IsNil)
- _, err = os.Stat(f)
- c.Assert(err, IsNil)
- c.Assert(f, testutil.FileEquals, `FROZEN`)
-}
-
-func (s *freezerSuite) TestThawSnapProcesses(c *C) {
- restore := update.MockFreezerCgroupDir(c)
- defer restore()
-
- n := "foo" // snap name
- p := filepath.Join(update.FreezerCgroupDir(), fmt.Sprintf("snap.%s", n)) // snap freezer cgroup
- f := filepath.Join(p, "freezer.state") // freezer.state file of the cgroup
-
- // When the freezer cgroup filesystem doesn't exist we do nothing at all.
- c.Assert(update.ThawSnapProcesses(n), IsNil)
- _, err := os.Stat(f)
- c.Assert(os.IsNotExist(err), Equals, true)
-
- // When the freezer cgroup filesystem exists but the particular cgroup
- // doesn't exist we don nothing at all.
- c.Assert(os.MkdirAll(update.FreezerCgroupDir(), 0755), IsNil)
- c.Assert(update.ThawSnapProcesses(n), IsNil)
- _, err = os.Stat(f)
- c.Assert(os.IsNotExist(err), Equals, true)
-
- // When the cgroup exists we write THAWED the freezer.state file.
- c.Assert(os.MkdirAll(p, 0755), IsNil)
- c.Assert(update.ThawSnapProcesses(n), IsNil)
- _, err = os.Stat(f)
- c.Assert(err, IsNil)
- c.Assert(f, testutil.FileEquals, `THAWED`)
-}
diff -Nru snapd-2.42.1+18.04/cmd/snap-update-ns/main_test.go snapd-2.45.1+18.04/cmd/snap-update-ns/main_test.go
--- snapd-2.42.1+18.04/cmd/snap-update-ns/main_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-update-ns/main_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -31,6 +31,7 @@
update "github.com/snapcore/snapd/cmd/snap-update-ns"
"github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/features"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/testutil"
@@ -162,6 +163,9 @@
dirs.SetRootDir(c.MkDir())
defer dirs.SetRootDir("/")
+ c.Assert(os.MkdirAll(dirs.FeaturesDir, 0755), IsNil)
+ c.Assert(ioutil.WriteFile(features.RobustMountNamespaceUpdates.ControlFile(), []byte(nil), 0644), IsNil)
+
// The snap `mysnap` no longer wishes to export it's usr/share/mysnap
// directory. All the synthetic changes that were associated with that mount
// entry can be discarded.
@@ -191,29 +195,13 @@
Entry: osutil.MountEntry{
Name: "/snap/mysnap/42/usr/share/mysnap",
Dir: "/usr/share/mysnap", Type: "none",
- Options: []string{"bind", "ro", "x-snapd.detach"},
+ Options: []string{"bind", "ro"},
},
})
case 1:
c.Assert(chg, DeepEquals, &update.Change{
Action: update.Unmount,
Entry: osutil.MountEntry{
- Name: "/usr/share/awk", Dir: "/usr/share/awk", Type: "none",
- Options: []string{"bind", "ro", "x-snapd.synthetic", "x-snapd.needed-by=/usr/share/mysnap", "x-snapd.detach"},
- },
- })
- case 2:
- c.Assert(chg, DeepEquals, &update.Change{
- Action: update.Unmount,
- Entry: osutil.MountEntry{
- Name: "/usr/share/adduser", Dir: "/usr/share/adduser", Type: "none",
- Options: []string{"bind", "ro", "x-snapd.synthetic", "x-snapd.needed-by=/usr/share/mysnap", "x-snapd.detach"},
- },
- })
- case 3:
- c.Assert(chg, DeepEquals, &update.Change{
- Action: update.Unmount,
- Entry: osutil.MountEntry{
Name: "tmpfs", Dir: "/usr/share", Type: "tmpfs",
Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/usr/share/mysnap", "x-snapd.detach"},
},
diff -Nru snapd-2.42.1+18.04/cmd/snap-update-ns/update.go snapd-2.45.1+18.04/cmd/snap-update-ns/update.go
--- snapd-2.42.1+18.04/cmd/snap-update-ns/update.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/cmd/snap-update-ns/update.go 2020-06-05 13:13:49.000000000 +0000
@@ -52,13 +52,11 @@
if err != nil {
return err
}
- debugShowProfile(desired, "desired mount profile")
currentBefore, err := upCtx.LoadCurrentProfile()
if err != nil {
return err
}
- debugShowProfile(currentBefore, "current mount profile (before applying changes)")
// Synthesize mount changes that were applied before for the purpose of the tmpfs detector.
as := upCtx.Assumptions()
@@ -70,20 +68,11 @@
// needed, collecting those that we managed to perform or that
// were performed already.
changesNeeded := NeededChanges(currentBefore, desired)
- debugShowChanges(changesNeeded, "mount changes needed")
- logger.Debugf("performing mount changes:")
var changesMade []*Change
for _, change := range changesNeeded {
- logger.Debugf("\t * %s", change)
synthesised, err := change.Perform(as)
changesMade = append(changesMade, synthesised...)
- if len(synthesised) > 0 {
- logger.Debugf("\tsynthesised additional mount changes:")
- for _, synth := range synthesised {
- logger.Debugf(" * \t\t%s", synth)
- }
- }
if err != nil {
// We may have done something even if Perform itself has
// failed. We need to collect synthesized changes and
@@ -109,6 +98,5 @@
currentAfter.Entries = append(currentAfter.Entries, change.Entry)
}
}
- debugShowProfile(¤tAfter, "current mount profile (after applying changes)")
return upCtx.SaveCurrentProfile(¤tAfter)
}
diff -Nru snapd-2.42.1+18.04/CODE_OF_CONDUCT.md snapd-2.45.1+18.04/CODE_OF_CONDUCT.md
--- snapd-2.42.1+18.04/CODE_OF_CONDUCT.md 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/CODE_OF_CONDUCT.md 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,76 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, sex characteristics, gender identity and expression,
+level of experience, education, socio-economic status, nationality, personal
+appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at snap-advocacy@canonical.com. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see
+https://www.contributor-covenant.org/faq
diff -Nru snapd-2.42.1+18.04/daemon/api_asserts.go snapd-2.45.1+18.04/daemon/api_asserts.go
--- snapd-2.42.1+18.04/daemon/api_asserts.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/daemon/api_asserts.go 2020-06-05 13:13:49.000000000 +0000
@@ -21,8 +21,10 @@
import (
"errors"
+ "fmt"
"net/http"
"net/url"
+ "strconv"
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/overlord/assertstate"
@@ -48,19 +50,29 @@
// 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 {
+type daemonAssertOptions struct {
jsonResult bool
headersOnly bool
+ remote bool
headers map[string]string
}
// helper for parsing url query options into formatting option vars
-func parseHeadersFormatOptionsFromURL(q url.Values) (*daemonAssertFormatOptions, error) {
- res := daemonAssertFormatOptions{}
+func parseHeadersFormatOptionsFromURL(q url.Values) (*daemonAssertOptions, error) {
+ res := daemonAssertOptions{}
res.headers = make(map[string]string)
for k := range q {
- if k == "json" {
- switch q.Get(k) {
+ v := q.Get(k)
+ switch k {
+ case "remote":
+ switch v {
+ case "true", "false":
+ res.remote, _ = strconv.ParseBool(v)
+ default:
+ return nil, errors.New(`"remote" query parameter when used must be set to "true" or "false" or left unset`)
+ }
+ case "json":
+ switch v {
case "false":
res.jsonResult = false
case "headers":
@@ -71,9 +83,9 @@
default:
return nil, errors.New(`"json" query parameter when used must be set to "true" or "headers"`)
}
- continue
+ default:
+ res.headers[k] = v
}
- res.headers[k] = q.Get(k)
}
return &res, nil
@@ -108,6 +120,29 @@
}
}
+func assertsFindOneRemote(c *Command, at *asserts.AssertionType, headers map[string]string, user *auth.UserState) ([]asserts.Assertion, error) {
+ primaryKeys, err := asserts.PrimaryKeyFromHeaders(at, headers)
+ if err != nil {
+ return nil, fmt.Errorf("cannot query remote assertion: %v", err)
+ }
+ sto := getStore(c)
+ as, err := sto.Assertion(at, primaryKeys, user)
+ if err != nil {
+ return nil, err
+ }
+
+ return []asserts.Assertion{as}, nil
+}
+
+func assertsFindManyInState(c *Command, at *asserts.AssertionType, headers map[string]string, opts *daemonAssertOptions) ([]asserts.Assertion, error) {
+ state := c.d.overlord.State()
+ state.Lock()
+ db := assertstate.DB(state)
+ state.Unlock()
+
+ return db.FindMany(at, opts.headers)
+}
+
func assertsFindMany(c *Command, r *http.Request, user *auth.UserState) Response {
assertTypeName := muxVars(r)["assertType"]
assertType := asserts.Type(assertTypeName)
@@ -118,12 +153,13 @@
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, opts.headers)
+ var assertions []asserts.Assertion
+ if opts.remote {
+ assertions, err = assertsFindOneRemote(c, assertType, opts.headers, user)
+ } else {
+ assertions, err = assertsFindManyInState(c, assertType, opts.headers, opts)
+ }
if err != nil && !asserts.IsNotFound(err) {
return InternalError("searching assertions failed: %v", err)
}
diff -Nru snapd-2.42.1+18.04/daemon/api_asserts_test.go snapd-2.45.1+18.04/daemon/api_asserts_test.go
--- snapd-2.42.1+18.04/daemon/api_asserts_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/daemon/api_asserts_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -38,6 +38,9 @@
"github.com/snapcore/snapd/overlord"
"github.com/snapcore/snapd/overlord/assertstate"
"github.com/snapcore/snapd/overlord/assertstate/assertstatetest"
+ "github.com/snapcore/snapd/overlord/auth"
+ "github.com/snapcore/snapd/overlord/snapstate"
+ "github.com/snapcore/snapd/store/storetest"
"github.com/snapcore/snapd/testutil"
)
@@ -47,6 +50,9 @@
storeSigning *assertstest.StoreStack
trustedRestorer func()
+
+ storetest.Store
+ mockAssertionFn func(at *asserts.AssertionType, headers []string, user *auth.UserState) (asserts.Assertion, error)
}
var _ = check.Suite(&assertsSuite{})
@@ -61,13 +67,18 @@
s.storeSigning = assertstest.NewStoreStack("can0nical", nil)
s.trustedRestorer = sysdb.InjectTrusted(s.storeSigning.Trusted)
- assertstate.Manager(s.o.State(), s.o.TaskRunner())
+ st := s.o.State()
+ st.Lock()
+ snapstate.ReplaceStore(st, s)
+ st.Unlock()
+ assertstate.Manager(st, s.o.TaskRunner())
}
func (s *assertsSuite) TearDownTest(c *check.C) {
s.trustedRestorer()
s.o = nil
s.d = nil
+ s.mockAssertionFn = nil
}
func (s *assertsSuite) TestGetAsserts(c *check.C) {
@@ -268,11 +279,20 @@
}
func (s *assertsSuite) TestAssertsFindManyJSONFilter(c *check.C) {
+ s.testAssertsFindManyJSONFilter(c, "/v2/assertions/account?json=true&username=developer1")
+}
+
+func (s *assertsSuite) TestAssertsFindManyJSONFilterRemoteIsFalse(c *check.C) {
+ // setting "remote=false" is the defalt and should not change anything
+ s.testAssertsFindManyJSONFilter(c, "/v2/assertions/account?json=true&username=developer1&remote=false")
+}
+
+func (s *assertsSuite) testAssertsFindManyJSONFilter(c *check.C, urlPath string) {
acct := assertstest.NewAccount(s.storeSigning, "developer1", nil, "")
s.addAsserts(acct)
// Execute
- req, err := http.NewRequest("POST", "/v2/assertions/account?json=true&username=developer1", nil)
+ req, err := http.NewRequest("POST", urlPath, nil)
c.Assert(err, check.IsNil)
defer daemon.MockMuxVars(func(*http.Request) map[string]string {
return map[string]string{"assertType": "account"}
@@ -433,3 +453,68 @@
_, err = dec.Decode()
c.Check(err, check.Equals, io.EOF)
}
+
+func (s *assertsSuite) TestAssertsFindManyRemoteInvalidParam(c *check.C) {
+ // Execute
+ req, err := http.NewRequest("POST", "/v2/assertions/account-key?remote=invalid&account-id=can0nical", nil)
+ c.Assert(err, check.IsNil)
+ defer daemon.MockMuxVars(func(*http.Request) map[string]string {
+ return map[string]string{"assertType": "account-key"}
+ })()
+
+ rec := httptest.NewRecorder()
+ daemon.AssertsFindManyCmd.GET(daemon.AssertsFindManyCmd, req, nil).ServeHTTP(rec, req)
+ // Verify
+ c.Check(rec.Code, check.Equals, 400, check.Commentf("body %q", rec.Body))
+ c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json")
+ var rsp daemon.Resp
+ c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), check.IsNil)
+ c.Check(rsp.Status, check.Equals, 400)
+ c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError)
+ c.Check(rsp.Result, check.DeepEquals, map[string]interface{}{
+ "message": `"remote" query parameter when used must be set to "true" or "false" or left unset`,
+ })
+}
+
+func (s *assertsSuite) Assertion(at *asserts.AssertionType, headers []string, user *auth.UserState) (asserts.Assertion, error) {
+ return s.mockAssertionFn(at, headers, user)
+}
+
+func (s *assertsSuite) TestAssertsFindManyRemote(c *check.C) {
+ var assertFnCalled int
+ s.mockAssertionFn = func(at *asserts.AssertionType, headers []string, user *auth.UserState) (asserts.Assertion, error) {
+ assertFnCalled++
+ c.Assert(at.Name, check.Equals, "account")
+ c.Assert(headers, check.DeepEquals, []string{"can0nical"})
+ return assertstest.NewAccount(s.storeSigning, "some-developer", nil, ""), nil
+ }
+
+ acct := assertstest.NewAccount(s.storeSigning, "developer1", nil, "")
+ s.addAsserts(acct)
+
+ // Execute
+ req, err := http.NewRequest("POST", "/v2/assertions/account?remote=true&account-id=can0nical", nil)
+ c.Assert(err, check.IsNil)
+ defer daemon.MockMuxVars(func(*http.Request) map[string]string {
+ return map[string]string{"assertType": "account"}
+ })()
+
+ rec := httptest.NewRecorder()
+ daemon.AssertsFindManyCmd.GET(daemon.AssertsFindManyCmd, req, nil).ServeHTTP(rec, req)
+ // Verify
+ c.Check(assertFnCalled, check.Equals, 1)
+ c.Check(rec.Code, check.Equals, 200, check.Commentf("body %q", rec.Body))
+ c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/x.ubuntu.assertion; bundle=y")
+
+ data := rec.Body.Bytes()
+ c.Check(string(data), check.Matches, `(?ms)type: account
+authority-id: can0nical
+account-id: [a-zA-Z0-9]+
+display-name: Some-developer
+timestamp: .*
+username: some-developer
+validation: unproven
+.*
+`)
+
+}
diff -Nru snapd-2.42.1+18.04/daemon/api_connections.go snapd-2.45.1+18.04/daemon/api_connections.go
--- snapd-2.42.1+18.04/daemon/api_connections.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/daemon/api_connections.go 2020-06-05 13:13:49.000000000 +0000
@@ -134,6 +134,18 @@
if err != nil {
return nil, err
}
+
+ // plug or slot not in the repository, e.g. cref is referring to an
+ // inactive revision of the snap; this can happen when the new revision
+ // doesn't have given plug/slot anymore (but the connection state is
+ // kept in case of revert).
+ // XXX: if we decide to show such connections with special tags, then
+ // this needs to be tweaked together with collectFilter definition and
+ // connectionJSON output.
+ if repo.Plug(cref.PlugRef.Snap, cref.PlugRef.Name) == nil || repo.Slot(cref.SlotRef.Snap, cref.SlotRef.Name) == nil {
+ continue
+ }
+
if !filter.plugOrConnectedSlotMatches(&cref.PlugRef, nil) && !filter.slotOrConnectedPlugMatches(&cref.SlotRef, nil) {
continue
}
diff -Nru snapd-2.42.1+18.04/daemon/api_connections_test.go snapd-2.45.1+18.04/daemon/api_connections_test.go
--- snapd-2.42.1+18.04/daemon/api_connections_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/daemon/api_connections_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -30,15 +30,22 @@
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/interfaces/builtin"
"github.com/snapcore/snapd/interfaces/ifacetest"
+ "github.com/snapcore/snapd/strutil"
)
// Tests for GET /v2/connections
-func (s *apiSuite) testConnectionsConnected(c *check.C, query string, connsState map[string]interface{}, expected map[string]interface{}) {
+func (s *apiSuite) testConnectionsConnected(c *check.C, query string, connsState map[string]interface{}, repoConnected []string, expected map[string]interface{}) {
c.Assert(s.d, check.NotNil, check.Commentf("call s.daemon() first"))
repo := s.d.overlord.InterfaceManager().Repository()
for crefStr, cstate := range connsState {
+ // if repoConnected is defined, then given connection must be on
+ // list, otherwise it's not going to be connected in the repository
+ // to simulate missing plugs/slots.
+ if repoConnected != nil && !strutil.ListContains(repoConnected, crefStr) {
+ continue
+ }
cref, err := interfaces.ParseConnRef(crefStr)
c.Assert(err, check.IsNil)
conn := cstate.(map[string]interface{})
@@ -236,7 +243,7 @@
"consumer:plug producer:slot": map[string]interface{}{
"interface": "test",
},
- }, map[string]interface{}{
+ }, nil, map[string]interface{}{
"result": map[string]interface{}{
"plugs": []interface{}{
map[string]interface{}{
@@ -279,6 +286,69 @@
})
}
+func (s *apiSuite) TestConnectionsMissingPlugSlotFilteredOut(c *check.C) {
+ restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
+ defer restore()
+
+ s.daemon(c)
+
+ s.mockSnap(c, consumerYaml)
+ s.mockSnap(c, producerYaml)
+
+ for _, missingPlugOrSlot := range []string{"consumer:plug2 producer:slot", "consumer:plug producer:slot2"} {
+ s.testConnectionsConnected(c, "/v2/connections?snap=producer", map[string]interface{}{
+ "consumer:plug producer:slot": map[string]interface{}{
+ "interface": "test",
+ },
+ missingPlugOrSlot: map[string]interface{}{
+ "interface": "test",
+ },
+ },
+ []string{"consumer:plug producer:slot"},
+ map[string]interface{}{
+ "result": map[string]interface{}{
+ "plugs": []interface{}{
+ map[string]interface{}{
+ "snap": "consumer",
+ "plug": "plug",
+ "interface": "test",
+ "attrs": map[string]interface{}{"key": "value"},
+ "apps": []interface{}{"app"},
+ "label": "label",
+ "connections": []interface{}{
+ map[string]interface{}{"snap": "producer", "slot": "slot"},
+ },
+ },
+ },
+ "slots": []interface{}{
+ map[string]interface{}{
+ "snap": "producer",
+ "slot": "slot",
+ "interface": "test",
+ "attrs": map[string]interface{}{"key": "value"},
+ "apps": []interface{}{"app"},
+ "label": "label",
+ "connections": []interface{}{
+ map[string]interface{}{"snap": "consumer", "plug": "plug"},
+ },
+ },
+ },
+ "established": []interface{}{
+ map[string]interface{}{
+ "plug": map[string]interface{}{"snap": "consumer", "plug": "plug"},
+ "slot": map[string]interface{}{"snap": "producer", "slot": "slot"},
+ "manual": true,
+ "interface": "test",
+ },
+ },
+ },
+ "status": "OK",
+ "status-code": 200.0,
+ "type": "sync",
+ })
+ }
+}
+
func (s *apiSuite) TestConnectionsBySnapAlias(c *check.C) {
restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
defer restore()
@@ -355,7 +425,7 @@
"consumer:plug core:slot": map[string]interface{}{
"interface": "test",
},
- }, map[string]interface{}{
+ }, nil, map[string]interface{}{
"result": expectedConnmected,
"status": "OK",
"status-code": 200.0,
@@ -467,7 +537,7 @@
"consumer:plug producer:slot": map[string]interface{}{
"interface": "test",
},
- }, map[string]interface{}{
+ }, nil, map[string]interface{}{
"result": map[string]interface{}{
"plugs": []interface{}{
map[string]interface{}{
@@ -534,7 +604,7 @@
"consumer:plug producer:slot": map[string]interface{}{
"interface": "test",
},
- }, map[string]interface{}{
+ }, nil, map[string]interface{}{
"result": map[string]interface{}{
"plugs": []interface{}{
map[string]interface{}{
@@ -603,7 +673,7 @@
"foo-slot-dynamic": "bar-dynamic",
},
},
- }, map[string]interface{}{
+ }, nil, map[string]interface{}{
"result": map[string]interface{}{
"plugs": []interface{}{
map[string]interface{}{
@@ -668,7 +738,7 @@
"by-gadget": true,
"auto": true,
},
- }, map[string]interface{}{
+ }, nil, map[string]interface{}{
"result": map[string]interface{}{
"plugs": []interface{}{
map[string]interface{}{
@@ -727,7 +797,7 @@
"auto": true,
"undesired": true,
},
- }, map[string]interface{}{
+ }, nil, map[string]interface{}{
"result": map[string]interface{}{
"established": []interface{}{},
"plugs": []interface{}{
@@ -782,7 +852,7 @@
"auto": true,
"undesired": true,
},
- }, map[string]interface{}{
+ }, nil, map[string]interface{}{
"result": map[string]interface{}{
"established": []interface{}{},
"plugs": []interface{}{},
@@ -808,7 +878,7 @@
"interface": "test",
"hotplug-gone": true,
},
- }, map[string]interface{}{
+ }, nil, map[string]interface{}{
"result": map[string]interface{}{
"established": []interface{}{},
"plugs": []interface{}{},
@@ -877,7 +947,7 @@
"by-gadget": true,
"auto": true,
},
- }, map[string]interface{}{
+ }, nil, map[string]interface{}{
"result": map[string]interface{}{
"plugs": []interface{}{
map[string]interface{}{
diff -Nru snapd-2.42.1+18.04/daemon/api_debug.go snapd-2.45.1+18.04/daemon/api_debug.go
--- snapd-2.42.1+18.04/daemon/api_debug.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/daemon/api_debug.go 2020-06-05 13:13:49.000000000 +0000
@@ -86,6 +86,11 @@
}
type changeTimings struct {
+ Status string `json:"status,omitempty"`
+ Kind string `json:"kind,omitempty"`
+ Summary string `json:"summary,omitempty"`
+ Lane int `json:"lane,omitempty"`
+ ReadyTime time.Time `json:"ready-time,omitempty"`
DoingTime time.Duration `json:"doing-time,omitempty"`
UndoingTime time.Duration `json:"undoing-time,omitempty"`
DoingTimings []*timings.TimingJSON `json:"doing-timings,omitempty"`
@@ -93,10 +98,25 @@
}
type debugTimings struct {
- ChangeID string `json:"change-id"`
- EnsureTimings []*timings.TimingJSON `json:"ensure-timings,omitempty"`
- StartupTimings []*timings.TimingJSON `json:"startup-timings,omitempty"`
- ChangeTimings map[string]*changeTimings `json:"change-timings,omitempty"`
+ ChangeID string `json:"change-id"`
+ // total duration of the activity - present for ensure and startup timings only
+ TotalDuration time.Duration `json:"total-duration,omitempty"`
+ EnsureTimings []*timings.TimingJSON `json:"ensure-timings,omitempty"`
+ StartupTimings []*timings.TimingJSON `json:"startup-timings,omitempty"`
+ // ChangeTimings are indexed by task id
+ ChangeTimings map[string]*changeTimings `json:"change-timings,omitempty"`
+}
+
+// minLane determines the lowest lane number for the task
+func minLane(t *state.Task) int {
+ lanes := t.Lanes()
+ minLane := lanes[0]
+ for _, l := range lanes[1:] {
+ if l < minLane {
+ minLane = l
+ }
+ }
+ return minLane
}
func collectChangeTimings(st *state.State, changeID string) (map[string]*changeTimings, error) {
@@ -130,6 +150,11 @@
m := map[string]*changeTimings{}
for _, t := range chg.Tasks() {
m[t.ID()] = &changeTimings{
+ Kind: t.Kind(),
+ Status: t.Status().String(),
+ Summary: t.Summary(),
+ Lane: minLane(t),
+ ReadyTime: t.ReadyTime(),
DoingTime: t.DoingTime(),
UndoingTime: t.UndoingTime(),
DoingTimings: doingTimingsByTask[t.ID()],
@@ -168,6 +193,7 @@
ChangeID: ensureChangeID,
ChangeTimings: changeTimings,
EnsureTimings: ensureTm.NestedTimings,
+ TotalDuration: ensureTm.Duration,
}
responseData = append(responseData, debugTm)
}
@@ -195,6 +221,7 @@
for _, startTm := range starts[first:] {
debugTm := &debugTimings{
StartupTimings: startTm.NestedTimings,
+ TotalDuration: startTm.Duration,
}
responseData = append(responseData, debugTm)
}
@@ -293,6 +320,13 @@
return SyncResponse(devicestate.CanManageRefreshes(st), nil)
case "connectivity":
return checkConnectivity(st)
+ case "prune":
+ opTime, err := c.d.overlord.DeviceManager().StartOfOperationTime()
+ if err != nil {
+ return BadRequest("cannot get start of operation time: %s", err)
+ }
+ st.Prune(opTime, 0, 0, 0)
+ return SyncResponse(true, nil)
default:
return BadRequest("unknown debug action: %v", a.Action)
}
diff -Nru snapd-2.42.1+18.04/daemon/api_debug_test.go snapd-2.45.1+18.04/daemon/api_debug_test.go
--- snapd-2.42.1+18.04/daemon/api_debug_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/daemon/api_debug_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -190,7 +190,7 @@
task3 := st.NewTask("bar", "...")
chg3.AddTask(task3)
- tm1 := timings.NewForTask(task3)
+ tm1 := state.TimingsForTask(task3)
sp1 := tm1.StartSpan("span", "span...")
sp1.Stop()
tm1.Save(st)
@@ -238,6 +238,7 @@
tmData := dataJSON[0].(map[string]interface{})
c.Check(tmData["change-id"], check.DeepEquals, "2")
c.Check(tmData["change-timings"], check.NotNil)
+ c.Check(tmData["total-duration"], check.NotNil)
}
func (s *postDebugSuite) TestGetDebugTimingsEnsureAll(c *check.C) {
@@ -247,10 +248,12 @@
tmData := dataJSON[0].(map[string]interface{})
c.Check(tmData["change-id"], check.DeepEquals, "1")
c.Check(tmData["change-timings"], check.NotNil)
+ c.Check(tmData["total-duration"], check.NotNil)
tmData = dataJSON[1].(map[string]interface{})
c.Check(tmData["change-id"], check.DeepEquals, "2")
c.Check(tmData["change-timings"], check.NotNil)
+ c.Check(tmData["total-duration"], check.NotNil)
}
func (s *postDebugSuite) TestGetDebugTimingsError(c *check.C) {
@@ -266,3 +269,23 @@
rsp = getDebug(debugCmd, req, nil).(*resp)
c.Check(rsp.Status, check.Equals, 400)
}
+
+func (s *postDebugSuite) TestMinLane(c *check.C) {
+ st := state.New(nil)
+ st.Lock()
+ defer st.Unlock()
+
+ t := st.NewTask("bar", "")
+ c.Check(MinLane(t), check.Equals, 0)
+
+ lane1 := st.NewLane()
+ t.JoinLane(lane1)
+ c.Check(MinLane(t), check.Equals, lane1)
+
+ lane2 := st.NewLane()
+ t.JoinLane(lane2)
+ c.Check(MinLane(t), check.Equals, lane1)
+
+ // sanity
+ c.Check(t.Lanes(), check.DeepEquals, []int{lane1, lane2})
+}
diff -Nru snapd-2.42.1+18.04/daemon/api_download.go snapd-2.45.1+18.04/daemon/api_download.go
--- snapd-2.42.1+18.04/daemon/api_download.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/daemon/api_download.go 2020-06-05 13:13:49.000000000 +0000
@@ -21,10 +21,23 @@
import (
"context"
+ "crypto/hmac"
+ "crypto/sha256"
+ "encoding/base64"
"encoding/json"
+ "errors"
+ "fmt"
"net/http"
+ "path/filepath"
+ "regexp"
+ "strconv"
+ "time"
+ "github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/overlord/auth"
+ "github.com/snapcore/snapd/overlord/state"
+ "github.com/snapcore/snapd/randutil"
+ "github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/store"
)
@@ -34,10 +47,38 @@
POST: postSnapDownload,
}
+var validRangeRegexp = regexp.MustCompile(`^\s*bytes=(\d+)-\s*$`)
+
// SnapDownloadAction is used to request a snap download
type snapDownloadAction struct {
- Action string `json:"action"`
- Snaps []string `json:"snaps,omitempty"`
+ SnapName string `json:"snap-name"`
+ snapRevisionOptions
+
+ // HeaderPeek if set requests a peek at the header without the
+ // body being returned.
+ HeaderPeek bool `json:"header-peek"`
+
+ ResumeToken string `json:"resume-token"`
+ resumePosition int64
+}
+
+var (
+ errDownloadNameRequired = errors.New("download operation requires one snap name")
+ errDownloadHeaderPeekResume = errors.New("cannot request header-only peek when resuming")
+ errDownloadResumeNoToken = errors.New("cannot resume without a token")
+)
+
+func (action *snapDownloadAction) validate() error {
+ if action.SnapName == "" {
+ return errDownloadNameRequired
+ }
+ if action.HeaderPeek && (action.resumePosition > 0 || action.ResumeToken != "") {
+ return errDownloadHeaderPeekResume
+ }
+ if action.resumePosition > 0 && action.ResumeToken == "" {
+ return errDownloadResumeNoToken
+ }
+ return action.snapRevisionOptions.validate()
}
func postSnapDownload(c *Command, r *http.Request, user *auth.UserState) Response {
@@ -49,43 +90,176 @@
if decoder.More() {
return BadRequest("extra content found after download operation")
}
+ if rangestr := r.Header.Get("Range"); rangestr != "" {
+ // "An origin server MUST ignore a Range header field
+ // that contains a range unit it does not understand."
+ subs := validRangeRegexp.FindStringSubmatch(rangestr)
+ if len(subs) == 2 {
+ n, err := strconv.ParseInt(subs[1], 10, 64)
+ if err == nil {
+ action.resumePosition = n
+ }
+ }
+ }
+ if err := action.validate(); err != nil {
+ return BadRequest(err.Error())
+ }
- if len(action.Snaps) == 0 {
- return BadRequest("download operation requires one snap name")
+ return streamOneSnap(c, action, user)
+}
+
+func streamOneSnap(c *Command, action snapDownloadAction, user *auth.UserState) Response {
+ secret, err := downloadTokensSecret(c)
+ if err != nil {
+ return InternalError(err.Error())
}
+ theStore := getStore(c)
- if len(action.Snaps) != 1 {
- return BadRequest("download operation supports only one snap")
+ var ss *snapStream
+ if action.ResumeToken == "" {
+ var info *snap.Info
+ actions := []*store.SnapAction{{
+ Action: "download",
+ InstanceName: action.SnapName,
+ Revision: action.Revision,
+ CohortKey: action.CohortKey,
+ Channel: action.Channel,
+ }}
+ results, err := theStore.SnapAction(context.TODO(), nil, actions, user, nil)
+ if err != nil {
+ return errToResponse(err, []string{action.SnapName}, InternalError, "cannot download snap: %v")
+ }
+ if len(results) != 1 {
+ return InternalError("internal error: unexpected number %v of results for a single download", len(results))
+ }
+ info = results[0].Info
+
+ ss, err = newSnapStream(action.SnapName, info, secret)
+ if err != nil {
+ return InternalError(err.Error())
+ }
+ } else {
+ var err error
+ ss, err = newResumingSnapStream(action.SnapName, action.ResumeToken, secret)
+ if err != nil {
+ return BadRequest(err.Error())
+ }
+ ss.resume = action.resumePosition
+ }
+
+ if !action.HeaderPeek {
+ stream, status, err := theStore.DownloadStream(context.TODO(), action.SnapName, ss.Info, action.resumePosition, user)
+ if err != nil {
+ return InternalError(err.Error())
+ }
+ ss.stream = stream
+ if status != 206 {
+ // store/cdn has no partial content (valid
+ // reply per RFC)
+ logger.Debugf("store refused our range request")
+ ss.resume = 0
+ }
}
- if action.Action == "" {
- return BadRequest("download operation requires action")
+ return ss
+}
+
+func newSnapStream(snapName string, info *snap.Info, secret []byte) (*snapStream, error) {
+ dlInfo := &info.DownloadInfo
+ fname := filepath.Base(info.MountFile())
+ tokenJSON := downloadTokenJSON{
+ SnapName: snapName,
+ Filename: fname,
+ Info: dlInfo,
}
+ tokStr, err := sealDownloadToken(&tokenJSON, secret)
+ if err != nil {
+ return nil, err
+ }
+ return &snapStream{
+ SnapName: snapName,
+ Filename: fname,
+ Info: dlInfo,
+ Token: tokStr,
+ }, nil
+}
- switch action.Action {
- case "download":
- snapName := action.Snaps[0]
- return streamOneSnap(c, user, snapName)
- default:
- return BadRequest("unknown download operation %q", action.Action)
+func newResumingSnapStream(snapName string, tokStr string, secret []byte) (*snapStream, error) {
+ d, err := unsealDownloadToken(tokStr, secret)
+ if err != nil {
+ return nil, err
}
+ if d.SnapName != snapName {
+ return nil, fmt.Errorf("resume snap name does not match original snap name")
+ }
+ return &snapStream{
+ SnapName: snapName,
+ Filename: d.Filename,
+ Info: d.Info,
+ Token: tokStr,
+ }, nil
+}
+
+type downloadTokenJSON struct {
+ SnapName string `json:"snap-name"`
+ Filename string `json:"filename"`
+ Info *snap.DownloadInfo `json:"dl-info"`
}
-func streamOneSnap(c *Command, user *auth.UserState, snapName string) Response {
- info, err := getStore(c).SnapInfo(context.TODO(), store.SnapSpec{Name: snapName}, user)
+func sealDownloadToken(d *downloadTokenJSON, secret []byte) (string, error) {
+ b, err := json.Marshal(d)
if err != nil {
- return SnapNotFound(snapName, err)
+ return "", err
}
+ mac := hmac.New(sha256.New, secret)
+ mac.Write(b)
+ // append the HMAC hash to b to build the full raw token tok
+ tok := mac.Sum(b)
+ return base64.RawURLEncoding.EncodeToString(tok), nil
+}
+
+var errInvalidDownloadToken = errors.New("download token is invalid")
- downloadInfo := info.DownloadInfo
- r, err := getStore(c).DownloadStream(context.TODO(), snapName, &downloadInfo, user)
+func unsealDownloadToken(tokStr string, secret []byte) (*downloadTokenJSON, error) {
+ tok, err := base64.RawURLEncoding.DecodeString(tokStr)
if err != nil {
- return InternalError(err.Error())
+ return nil, errInvalidDownloadToken
}
+ sz := len(tok)
+ if sz < sha256.Size {
+ return nil, errInvalidDownloadToken
+ }
+ h := tok[sz-sha256.Size:]
+ b := tok[:sz-sha256.Size]
+ mac := hmac.New(sha256.New, secret)
+ mac.Write(b)
+ if !hmac.Equal(h, mac.Sum(nil)) {
+ return nil, errInvalidDownloadToken
+ }
+ var d downloadTokenJSON
+ if err := json.Unmarshal(b, &d); err != nil {
+ return nil, err
+ }
+ return &d, nil
+}
- return fileStream{
- SnapName: snapName,
- Info: downloadInfo,
- stream: r,
+func downloadTokensSecret(c *Command) (secret []byte, err error) {
+ st := c.d.overlord.State()
+ st.Lock()
+ defer st.Unlock()
+ const k = "api-download-tokens-secret"
+ err = st.Get(k, &secret)
+ if err == nil {
+ return secret, nil
+ }
+ if err != nil && err != state.ErrNoState {
+ return nil, err
+ }
+ secret, err = randutil.CryptoTokenBytes(32)
+ if err != nil {
+ return nil, err
}
+ st.Set(k, secret)
+ st.Set(k+"-time", time.Now().UTC())
+ return secret, nil
}
diff -Nru snapd-2.42.1+18.04/daemon/api_download_test.go snapd-2.45.1+18.04/daemon/api_download_test.go
--- snapd-2.42.1+18.04/daemon/api_download_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/daemon/api_download_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -22,6 +22,7 @@
import (
"bytes"
"context"
+ "encoding/base64"
"fmt"
"io"
"io/ioutil"
@@ -65,34 +66,101 @@
dirs.SetRootDir(c.MkDir())
}
-var content = "SNAP"
+var snapContent = "SNAP"
-func (s *snapDownloadSuite) SnapInfo(ctx context.Context, spec store.SnapSpec, user *auth.UserState) (*snap.Info, error) {
- switch spec.Name {
- case "bar":
- return &snap.Info{
- DownloadInfo: snap.DownloadInfo{
- Size: int64(len(content)),
- AnonDownloadURL: "http://localhost/bar",
- },
- }, nil
- case "download-error-trigger-snap":
- return &snap.Info{
- DownloadInfo: snap.DownloadInfo{
- Size: 100,
- AnonDownloadURL: "http://localhost/foo",
- },
- }, nil
- default:
+var storeSnaps = map[string]*snap.Info{
+ "bar": {
+ SideInfo: snap.SideInfo{
+ RealName: "bar",
+ Revision: snap.R(1),
+ },
+ DownloadInfo: snap.DownloadInfo{
+ Size: int64(len(snapContent)),
+ AnonDownloadURL: "http://localhost/bar",
+ Sha3_384: "sha3sha3sha3",
+ },
+ },
+ "edge-bar": {
+ SideInfo: snap.SideInfo{
+ RealName: "edge-bar",
+ Revision: snap.R(1),
+ // this is the channel we expect in the test
+ Channel: "edge",
+ },
+ DownloadInfo: snap.DownloadInfo{
+ Size: int64(len(snapContent)),
+ AnonDownloadURL: "http://localhost/edge-bar",
+ Sha3_384: "sha3sha3sha3",
+ },
+ },
+ "rev7-bar": {
+ SideInfo: snap.SideInfo{
+ RealName: "rev7-bar",
+ // this is the revision we expect in the test
+ Revision: snap.R(7),
+ },
+ DownloadInfo: snap.DownloadInfo{
+ Size: int64(len(snapContent)),
+ AnonDownloadURL: "http://localhost/rev7-bar",
+ Sha3_384: "sha3sha3sha3",
+ },
+ },
+ "download-error-trigger-snap": {
+ DownloadInfo: snap.DownloadInfo{
+ Size: 100,
+ AnonDownloadURL: "http://localhost/foo",
+ Sha3_384: "sha3sha3sha3",
+ },
+ },
+ "foo-resume-3": {
+ SideInfo: snap.SideInfo{
+ RealName: "foo-resume-3",
+ Revision: snap.R(1),
+ },
+ DownloadInfo: snap.DownloadInfo{
+ Size: int64(len(snapContent)),
+ AnonDownloadURL: "http://localhost/foo-resume-3",
+ Sha3_384: "sha3sha3sha3",
+ },
+ },
+}
+
+func (s *snapDownloadSuite) SnapAction(ctx context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, user *auth.UserState, opts *store.RefreshOptions) ([]store.SnapActionResult, error) {
+ if len(actions) != 1 {
+ panic(fmt.Sprintf("unexpected amount of actions: %v", len(actions)))
+ }
+ action := actions[0]
+ if action.Action != "download" {
+ panic(fmt.Sprintf("unexpected action: %q", action.Action))
+ }
+ info, ok := storeSnaps[action.InstanceName]
+ if !ok {
return nil, store.ErrSnapNotFound
}
+ if action.Channel != info.Channel {
+ panic(fmt.Sprintf("unexpected channel %q for %v snap", action.Channel, action.InstanceName))
+ }
+ if !action.Revision.Unset() && action.Revision != info.Revision {
+ panic(fmt.Sprintf("unexpected revision %q for %s snap", action.Revision, action.InstanceName))
+ }
+ return []store.SnapActionResult{{Info: info}}, nil
}
-func (s *snapDownloadSuite) DownloadStream(ctx context.Context, name string, downloadInfo *snap.DownloadInfo, user *auth.UserState) (io.ReadCloser, error) {
- if name == "bar" {
- return ioutil.NopCloser(bytes.NewReader([]byte(content))), nil
+func (s *snapDownloadSuite) DownloadStream(ctx context.Context, name string, downloadInfo *snap.DownloadInfo, resume int64, user *auth.UserState) (io.ReadCloser, int, error) {
+ if name == "download-error-trigger-snap" {
+ return nil, 0, fmt.Errorf("error triggered by download-error-trigger-snap")
+ }
+ if name == "foo-resume-3" && resume != 3 {
+ return nil, 0, fmt.Errorf("foo-resume-3 should set resume position to 3 instead of %v", resume)
}
- return nil, fmt.Errorf("unexpected error")
+ if _, ok := storeSnaps[name]; ok {
+ status := 200
+ if resume > 0 {
+ status = 206
+ }
+ return ioutil.NopCloser(bytes.NewReader([]byte(snapContent[resume:]))), status, nil
+ }
+ panic(fmt.Sprintf("internal error: trying to download %s but not in storeSnaps", name))
}
func (s *snapDownloadSuite) TestDownloadSnapErrors(c *check.C) {
@@ -104,29 +172,19 @@
for _, scen := range []scenario{
{
- dataJSON: `{"action": "download"}`,
+ dataJSON: `{"snap-name": ""}`,
status: 400,
err: "download operation requires one snap name",
},
{
- dataJSON: `{"action": "foo", "snaps": ["foo"]}`,
- status: 400,
- err: `unknown download operation "foo"`,
- },
- {
- dataJSON: `{"snaps": ["foo"]}`,
+ dataJSON: `{"}`,
status: 400,
- err: `download operation requires action`,
+ err: `cannot decode request body into download operation: unexpected EOF`,
},
{
- dataJSON: `{"action": "foo", "snaps": ["foo", "bar"]}`,
+ dataJSON: `{"snap-name": "doom","channel":"latest/potato"}`,
status: 400,
- err: `download operation supports only one snap`,
- },
- {
- dataJSON: `{"}`,
- status: 400,
- err: `cannot decode request body into download operation: unexpected EOF`,
+ err: `invalid risk in channel name: latest/potato`,
},
} {
var err error
@@ -146,52 +204,219 @@
}
func (s *snapDownloadSuite) TestStreamOneSnap(c *check.C) {
-
type scenario struct {
+ snapName string
dataJSON string
status int
+ resume int
+ noBody bool
err string
}
+ sec, err := daemon.DownloadTokensSecret(daemon.SnapDownloadCmd)
+ c.Assert(err, check.IsNil)
+
+ fooResume3SS, err := daemon.NewSnapStream("foo-resume-3", storeSnaps["foo-resume-3"], sec)
+ c.Assert(err, check.IsNil)
+ tok, err := base64.RawURLEncoding.DecodeString(fooResume3SS.Token)
+ c.Assert(err, check.IsNil)
+ c.Assert(bytes.HasPrefix(tok, []byte(`{"snap-name":"foo-resume-3","filename":"foo-resume-3_1.snap","dl-info":{"`)), check.Equals, true)
+
+ brokenHashToken := base64.RawURLEncoding.EncodeToString(append(tok[:len(tok)-1], tok[len(tok)-1]-1))
+
for _, s := range []scenario{
{
- dataJSON: `{"action": "download", "snaps": ["doom"]}`,
+ snapName: "doom",
+ dataJSON: `{"snap-name": "doom"}`,
status: 404,
err: "snap not found",
},
{
- dataJSON: `{"action": "download", "snaps": ["download-error-trigger-snap"]}`,
+ snapName: "download-error-trigger-snap",
+ dataJSON: `{"snap-name": "download-error-trigger-snap"}`,
status: 500,
- err: "unexpected error",
+ err: "error triggered by download-error-trigger-snap",
},
{
- dataJSON: `{"action": "download", "snaps": ["bar"]}`,
+ snapName: "bar",
+ dataJSON: `{"snap-name": "bar"}`,
status: 200,
err: "",
},
+ {
+ snapName: "edge-bar",
+ dataJSON: `{"snap-name": "edge-bar", "channel":"edge"}`,
+ status: 200,
+ err: "",
+ },
+ {
+ snapName: "rev7-bar",
+ dataJSON: `{"snap-name": "rev7-bar", "revision":"7"}`,
+ status: 200,
+ err: "",
+ },
+ // happy resume
+ {
+ snapName: "foo-resume-3",
+ dataJSON: fmt.Sprintf(`{"snap-name": "foo-resume-3", "resume-token": %q}`, fooResume3SS.Token),
+ status: 206,
+ resume: 3,
+ err: "",
+ },
+ // unhappy resume
+ {
+ snapName: "foo-resume-3",
+ dataJSON: fmt.Sprintf(`{"snap-name": "foo-resume-other", "resume-token": %q}`, fooResume3SS.Token),
+ status: 400,
+ resume: 3,
+ err: "resume snap name does not match original snap name",
+ },
+ {
+ snapName: "foo-resume-3",
+ dataJSON: `{"snap-name": "foo-resume-3", "resume-token": "invalid token"}`, // not base64
+ status: 400,
+ resume: 3,
+ err: "download token is invalid",
+ },
+ {
+ snapName: "foo-resume-3",
+ dataJSON: `{"snap-name": "foo-resume-3", "resume-token": "e30"}`, // too short token content
+ status: 400,
+ resume: 3,
+ err: "download token is invalid",
+ },
+ {
+ snapName: "foo-resume-3",
+ dataJSON: fmt.Sprintf(`{"snap-name": "foo-resume-3", "resume-token": %q}`, brokenHashToken), // token with broken hash
+ status: 400,
+ resume: 3,
+ err: "download token is invalid",
+ },
+
+ {
+ snapName: "foo-resume-3",
+ dataJSON: `{"snap-name": "foo-resume-3", "resume-stamp": ""}`,
+ status: 400,
+ resume: 3,
+ err: "cannot resume without a token",
+ },
+ {
+ snapName: "foo-resume-3",
+ dataJSON: fmt.Sprintf(`{"snap-name": "foo-resume-3", "resume-stamp": %q}`, fooResume3SS.Token),
+ status: 500,
+ resume: -10,
+ // negative values are ignored and resume is set to 0
+ err: "foo-resume-3 should set resume position to 3 instead of 0",
+ },
+ {
+ snapName: "foo-resume-3",
+ dataJSON: `{"snap-name": "foo-resume-3", "header-peek": true}`,
+ status: 400,
+ resume: 3,
+ err: "cannot request header-only peek when resuming",
+ },
+ {
+ snapName: "foo-resume-3",
+ dataJSON: `{"snap-name": "foo-resume-3", "header-peek": true, "resume-token": "something"}`,
+ status: 400,
+ err: "cannot request header-only peek when resuming",
+ },
+ {
+ snapName: "foo-resume-3",
+ dataJSON: `{"snap-name": "foo-resume-3", "header-peek": true, "resume-token": "something"}`,
+ resume: 3,
+ status: 400,
+ err: "cannot request header-only peek when resuming",
+ },
} {
req, err := http.NewRequest("POST", "/v2/download", strings.NewReader(s.dataJSON))
c.Assert(err, check.IsNil)
+ if s.resume != 0 {
+ req.Header.Add("Range", fmt.Sprintf("bytes=%d-", s.resume))
+ }
+
rsp := daemon.SnapDownloadCmd.POST(daemon.SnapDownloadCmd, req, nil)
if s.err != "" {
- c.Assert(rsp.(*daemon.Resp).Status, check.Equals, s.status)
+ c.Check(rsp.(*daemon.Resp).Status, check.Equals, s.status, check.Commentf("unexpected result for %v", s.dataJSON))
result := rsp.(*daemon.Resp).Result
- c.Check(result.(*daemon.ErrorResult).Message, check.Matches, s.err)
+ c.Check(result.(*daemon.ErrorResult).Message, check.Matches, s.err, check.Commentf("unexpected result for %v", s.dataJSON))
} else {
- c.Assert(rsp.(daemon.FileStream).SnapName, check.Equals, "bar")
- c.Assert(rsp.(daemon.FileStream).Info.Size, check.Equals, int64(len(content)))
+ c.Assert(rsp, check.FitsTypeOf, &daemon.SnapStream{}, check.Commentf("unexpected result for %v", s.dataJSON))
+ ss := rsp.(*daemon.SnapStream)
+ c.Assert(ss.SnapName, check.Equals, s.snapName, check.Commentf("invalid result %v for %v", rsp, s.dataJSON))
+ c.Assert(ss.Info.Size, check.Equals, int64(len(snapContent)))
w := httptest.NewRecorder()
- rsp.(daemon.FileStream).ServeHTTP(w, nil)
+ ss.ServeHTTP(w, nil)
- expectedLength := fmt.Sprintf("%d", len(content))
+ expectedLength := fmt.Sprintf("%d", len(snapContent)-s.resume)
+ info := storeSnaps[s.snapName]
c.Assert(w.Code, check.Equals, s.status)
c.Assert(w.Header().Get("Content-Length"), check.Equals, expectedLength)
c.Assert(w.Header().Get("Content-Type"), check.Equals, "application/octet-stream")
- c.Assert(w.Header().Get("Content-Disposition"), check.Equals, "attachment; filename=bar")
- c.Assert(w.Body.String(), check.Equals, "SNAP")
+ c.Assert(w.Header().Get("Content-Disposition"), check.Equals, fmt.Sprintf("attachment; filename=%s_%s.snap", s.snapName, info.Revision))
+ c.Assert(w.Header().Get("Snap-Sha3-384"), check.Equals, "sha3sha3sha3", check.Commentf("invalid sha3 for %v", s.snapName))
+ c.Assert(w.Body.Bytes(), check.DeepEquals, []byte("SNAP")[s.resume:])
+ c.Assert(w.Header().Get("Snap-Download-Token"), check.Equals, ss.Token)
+ if s.status == 206 {
+ c.Assert(w.Header().Get("Content-Range"), check.Equals, fmt.Sprintf("bytes %d-%d/%d", s.resume, len(snapContent)-1, len(snapContent)))
+ c.Assert(ss.Token, check.Not(check.HasLen), 0)
+ }
+ }
+ }
+}
+
+func (s *snapDownloadSuite) TestStreamOneSnapHeaderOnlyPeek(c *check.C) {
+ dataJSON := `{"snap-name": "bar", "header-peek": true}`
+ req, err := http.NewRequest("POST", "/v2/download", strings.NewReader(dataJSON))
+ c.Assert(err, check.IsNil)
+
+ rsp := daemon.SnapDownloadCmd.POST(daemon.SnapDownloadCmd, req, nil)
+
+ c.Assert(rsp, check.FitsTypeOf, &daemon.SnapStream{})
+ ss := rsp.(*daemon.SnapStream)
+ c.Assert(ss.SnapName, check.Equals, "bar")
+ c.Assert(ss.Info.Size, check.Equals, int64(len(snapContent)))
+
+ w := httptest.NewRecorder()
+ ss.ServeHTTP(w, nil)
+ c.Assert(w.Code, check.Equals, 200)
+
+ // we get the relevant headers
+ c.Check(w.Header().Get("Content-Disposition"), check.Equals, "attachment; filename=bar_1.snap")
+ c.Check(w.Header().Get("Snap-Sha3-384"), check.Equals, "sha3sha3sha3")
+ // but no body
+ c.Check(w.Body.Bytes(), check.HasLen, 0)
+}
+
+func (s *snapDownloadSuite) TestStreamRangeHeaderErrors(c *check.C) {
+ dataJSON := `{"snap-name":"bar"}`
+
+ for _, s := range []string{
+ // missing "-" at the end
+ "bytes=123",
+ // missing "bytes="
+ "123-",
+ // real range, not supported
+ "bytes=1-2",
+ // almost
+ "bytes=1--",
+ } {
+ req, err := http.NewRequest("POST", "/v2/download", strings.NewReader(dataJSON))
+ c.Assert(err, check.IsNil)
+ // missng "-" at the end
+ req.Header.Add("Range", s)
+
+ rsp := daemon.SnapDownloadCmd.POST(daemon.SnapDownloadCmd, req, nil)
+ if dr, ok := rsp.(*daemon.Resp); ok {
+ c.Fatalf("unexpected daemon result (test broken): %v", dr.Result)
}
+ w := httptest.NewRecorder()
+ ss := rsp.(*daemon.SnapStream)
+ ss.ServeHTTP(w, nil)
+ // range header is invalid and ignored
+ c.Assert(w.Code, check.Equals, 200)
}
}
diff -Nru snapd-2.42.1+18.04/daemon/api.go snapd-2.45.1+18.04/daemon/api.go
--- snapd-2.42.1+18.04/daemon/api.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/daemon/api.go 2020-06-05 13:13:49.000000000 +0000
@@ -20,6 +20,7 @@
package daemon
import (
+ "bytes"
"context"
"encoding/json"
"errors"
@@ -32,6 +33,7 @@
"net/http"
"net/url"
"os"
+ "os/exec"
"path/filepath"
"sort"
"strconv"
@@ -41,6 +43,7 @@
"github.com/gorilla/mux"
"github.com/jessevdk/go-flags"
+ "github.com/snapcore/snapd/arch"
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/asserts/snapasserts"
"github.com/snapcore/snapd/client"
@@ -64,7 +67,10 @@
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/progress"
"github.com/snapcore/snapd/release"
+ "github.com/snapcore/snapd/sandbox"
"github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/snap/channel"
+ "github.com/snapcore/snapd/snap/snapfile"
"github.com/snapcore/snapd/store"
"github.com/snapcore/snapd/strutil"
"github.com/snapcore/snapd/systemd"
@@ -104,6 +110,8 @@
modelCmd,
cohortsCmd,
serialModelCmd,
+ systemsCmd,
+ systemsActionCmd,
}
var (
@@ -229,12 +237,18 @@
buildID = "unknown"
)
+var systemdVirt = ""
+
func init() {
// cache the build-id on startup to ensure that changes in
// the underlying binary do not affect us
if bid, err := osutil.MyBuildID(); err == nil {
buildID = bid
}
+ // cache systemd-detect-virt output as it's unlikely to change :-)
+ if buf, err := exec.Command("systemd-detect-virt").CombinedOutput(); err == nil {
+ systemdVirt = string(bytes.TrimSpace(buf))
+ }
}
func tbd(c *Command, r *http.Request, user *auth.UserState) Response {
@@ -288,14 +302,19 @@
"snap-mount-dir": dirs.SnapMountDir,
"snap-bin-dir": dirs.SnapBinariesDir,
},
- "refresh": refreshInfo,
+ "refresh": refreshInfo,
+ "architecture": arch.DpkgArchitecture(),
+ }
+ if systemdVirt != "" {
+ m["virtualization"] = systemdVirt
}
+
// NOTE: Right now we don't have a good way to differentiate if we
// only have partial confinement (ala AppArmor disabled and Seccomp
// enabled) or no confinement at all. Once we have a better system
// in place how we can dynamically retrieve these information from
// snapd we will use this here.
- if release.ReleaseInfo.ForceDevMode() {
+ if sandbox.ForceDevMode() {
m["confinement"] = "partial"
} else {
m["confinement"] = "strict"
@@ -322,7 +341,7 @@
// Add information about supported confinement types as a fake backend
features := make([]string, 1, 3)
features[0] = "devmode"
- if !release.ReleaseInfo.ForceDevMode() {
+ if !sandbox.ForceDevMode() {
features = append(features, "strict")
}
if dirs.SupportsClassicConfinement() {
@@ -458,6 +477,7 @@
query := r.URL.Query()
q := query.Get("q")
commonID := query.Get("common-id")
+ // TODO: support both "category" (search v2) and "section"
section := query.Get("section")
name := query.Get("name")
scope := query.Get("scope")
@@ -508,7 +528,7 @@
Query: q,
Prefix: prefix,
CommonID: commonID,
- Section: section,
+ Category: section,
Private: private,
Scope: scope,
}, user)
@@ -731,20 +751,45 @@
return "license agreement required"
}
+type snapRevisionOptions struct {
+ Channel string `json:"channel"`
+ Revision snap.Revision `json:"revision"`
+
+ CohortKey string `json:"cohort-key"`
+ LeaveCohort bool `json:"leave-cohort"`
+}
+
+func (ropt *snapRevisionOptions) validate() error {
+ if ropt.CohortKey != "" {
+ if ropt.LeaveCohort {
+ return fmt.Errorf("cannot specify both cohort-key and leave-cohort")
+ }
+ if !ropt.Revision.Unset() {
+ return fmt.Errorf("cannot specify both cohort-key and revision")
+ }
+ }
+
+ if ropt.Channel != "" {
+ _, err := channel.Parse(ropt.Channel, "-")
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
type snapInstruction struct {
progress.NullMeter
- Action string `json:"action"`
- Amend bool `json:"amend"`
- Channel string `json:"channel"`
- Revision snap.Revision `json:"revision"`
- CohortKey string `json:"cohort-key"`
- LeaveCohort bool `json:"leave-cohort"`
- DevMode bool `json:"devmode"`
- JailMode bool `json:"jailmode"`
- Classic bool `json:"classic"`
- IgnoreValidation bool `json:"ignore-validation"`
- Unaliased bool `json:"unaliased"`
- Purge bool `json:"purge,omitempty"`
+
+ Action string `json:"action"`
+ Amend bool `json:"amend"`
+ snapRevisionOptions
+ DevMode bool `json:"devmode"`
+ JailMode bool `json:"jailmode"`
+ Classic bool `json:"classic"`
+ IgnoreValidation bool `json:"ignore-validation"`
+ Unaliased bool `json:"unaliased"`
+ Purge bool `json:"purge,omitempty"`
// dropping support temporarely until flag confusion is sorted,
// this isn't supported by client atm anyway
LeaveOld bool `json:"temp-dropped-leave-old"`
@@ -781,6 +826,30 @@
return flags, nil
}
+func (inst *snapInstruction) validate() error {
+ if inst.CohortKey != "" {
+ if inst.Action != "install" && inst.Action != "refresh" && inst.Action != "switch" {
+ return fmt.Errorf("cohort-key can only be specified for install, refresh, or switch")
+ }
+ }
+ if inst.LeaveCohort {
+ if inst.Action != "refresh" && inst.Action != "switch" {
+ return fmt.Errorf("leave-cohort can only be specified for refresh or switch")
+ }
+ }
+ if inst.Action == "install" {
+ for _, snapName := range inst.Snaps {
+ // FIXME: alternatively we could simply mutate *inst
+ // and s/ubuntu-core/core/ ?
+ if snapName == "ubuntu-core" {
+ return fmt.Errorf(`cannot install "ubuntu-core", please use "core" instead`)
+ }
+ }
+ }
+
+ return inst.snapRevisionOptions.validate()
+}
+
type snapInstructionResult struct {
Summary string
Affected []string
@@ -822,7 +891,7 @@
func modeFlags(devMode, jailMode, classic bool) (snapstate.Flags, error) {
flags := snapstate.Flags{}
- devModeOS := release.ReleaseInfo.ForceDevMode()
+ devModeOS := sandbox.ForceDevMode()
switch {
case jailMode && devModeOS:
return flags, errNoJailMode
@@ -876,37 +945,6 @@
}, nil
}
-func verifySnapInstructions(inst *snapInstruction) error {
- if inst.CohortKey != "" {
- if inst.LeaveCohort {
- return fmt.Errorf("cannot specify both cohort-key and leave-cohort")
- }
- if !inst.Revision.Unset() {
- return fmt.Errorf("cannot specify both cohort-key and revision")
- }
- if inst.Action != "install" && inst.Action != "refresh" && inst.Action != "switch" {
- return fmt.Errorf("cohort-key can only be specified for install, refresh, or switch")
- }
- }
- if inst.LeaveCohort {
- if inst.Action != "refresh" && inst.Action != "switch" {
- return fmt.Errorf("leave-cohort can only be specified for refresh or switch")
- }
- }
- switch inst.Action {
- case "install":
- for _, snapName := range inst.Snaps {
- // FIXME: alternatively we could simply mutate *inst
- // and s/ubuntu-core/core/ ?
- if snapName == "ubuntu-core" {
- return fmt.Errorf(`cannot install "ubuntu-core", please use "core" instead`)
- }
- }
- }
-
- return nil
-}
-
func snapInstallMany(inst *snapInstruction, st *state.State) (*snapInstructionResult, error) {
for _, name := range inst.Snaps {
if len(name) == 0 {
@@ -1180,7 +1218,7 @@
vars := muxVars(r)
inst.Snaps = []string{vars["name"]}
- if err := verifySnapInstructions(&inst); err != nil {
+ if err := inst.validate(); err != nil {
return BadRequest("%s", err)
}
@@ -1285,7 +1323,7 @@
if inst.Channel != "" || !inst.Revision.Unset() || inst.DevMode || inst.JailMode || inst.CohortKey != "" || inst.LeaveCohort {
return BadRequest("unsupported option provided for multi-snap operation")
}
- if err := verifySnapInstructions(&inst); err != nil {
+ if err := inst.validate(); err != nil {
return BadRequest("%v", err)
}
@@ -1506,7 +1544,7 @@
func unsafeReadSnapInfoImpl(snapPath string) (*snap.Info, error) {
// Condider using DeriveSideInfo before falling back to this!
- snapf, err := snap.Open(snapPath)
+ snapf, err := snapfile.Open(snapPath)
if err != nil {
return nil, err
}
@@ -1777,21 +1815,28 @@
}
case "disconnect":
var conns []*interfaces.ConnRef
- repo := c.d.overlord.InterfaceManager().Repository()
summary = fmt.Sprintf("Disconnect %s:%s from %s:%s", a.Plugs[0].Snap, a.Plugs[0].Name, a.Slots[0].Snap, a.Slots[0].Name)
- conns, err = repo.ResolveDisconnect(a.Plugs[0].Snap, a.Plugs[0].Name, a.Slots[0].Snap, a.Slots[0].Name)
+ conns, err = c.d.overlord.InterfaceManager().ResolveDisconnect(a.Plugs[0].Snap, a.Plugs[0].Name, a.Slots[0].Snap, a.Slots[0].Name, a.Forget)
if err == nil {
if len(conns) == 0 {
return InterfacesUnchanged("nothing to do")
}
+ repo := c.d.overlord.InterfaceManager().Repository()
for _, connRef := range conns {
var ts *state.TaskSet
var conn *interfaces.Connection
- conn, err = repo.Connection(connRef)
- if err != nil {
- break
+ if a.Forget {
+ ts, err = ifacestate.Forget(st, repo, connRef)
+ } else {
+ conn, err = repo.Connection(connRef)
+ if err != nil {
+ break
+ }
+ ts, err = ifacestate.Disconnect(st, conn)
+ if err != nil {
+ break
+ }
}
- ts, err = ifacestate.Disconnect(st, conn)
if err != nil {
break
}
@@ -2103,6 +2148,22 @@
context, _ := c.d.overlord.HookManager().Context(snapctlOptions.ContextID)
stdout, stderr, err := ctlcmdRun(context, snapctlOptions.Args, uid)
if err != nil {
+ if e, ok := err.(*ctlcmd.UnsuccessfulError); ok {
+ result := map[string]interface{}{
+ "stdout": string(stdout),
+ "stderr": string(stderr),
+ "exit-code": e.ExitCode,
+ }
+ return &resp{
+ Type: ResponseTypeError,
+ Result: &errorResult{
+ Message: e.Error(),
+ Kind: errorKindUnsuccessful,
+ Value: result,
+ },
+ Status: 200,
+ }
+ }
if e, ok := err.(*ctlcmd.ForbiddenCommandError); ok {
return Forbidden(e.Error())
}
diff -Nru snapd-2.42.1+18.04/daemon/api_json.go snapd-2.45.1+18.04/daemon/api_json.go
--- snapd-2.42.1+18.04/daemon/api_json.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/daemon/api_json.go 2020-06-05 13:13:49.000000000 +0000
@@ -59,6 +59,7 @@
// interfaceAction is an action performed on the interface system.
type interfaceAction struct {
Action string `json:"action"`
+ Forget bool `json:"forget,omitempty"`
Plugs []plugJSON `json:"plugs,omitempty"`
Slots []slotJSON `json:"slots,omitempty"`
}
diff -Nru snapd-2.42.1+18.04/daemon/api_systems.go snapd-2.45.1+18.04/daemon/api_systems.go
--- snapd-2.42.1+18.04/daemon/api_systems.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/daemon/api_systems.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,135 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package daemon
+
+import (
+ "encoding/json"
+ "net/http"
+ "os"
+
+ "github.com/snapcore/snapd/client"
+ "github.com/snapcore/snapd/overlord/auth"
+ "github.com/snapcore/snapd/overlord/devicestate"
+ "github.com/snapcore/snapd/snap"
+)
+
+var systemsCmd = &Command{
+ Path: "/v2/systems",
+ GET: getSystems,
+}
+
+var systemsActionCmd = &Command{
+ Path: "/v2/systems/{label}",
+ POST: postSystemsAction,
+ RootOnly: true,
+}
+
+type systemsResponse struct {
+ Systems []client.System `json:"systems,omitempty"`
+}
+
+func getSystems(c *Command, r *http.Request, user *auth.UserState) Response {
+ var rsp systemsResponse
+
+ seedSystems, err := c.d.overlord.DeviceManager().Systems()
+ if err != nil {
+ if err == devicestate.ErrNoSystems {
+ // no systems available
+ return SyncResponse(&rsp, nil)
+ }
+
+ return InternalError(err.Error())
+ }
+
+ rsp.Systems = make([]client.System, 0, len(seedSystems))
+
+ for _, ss := range seedSystems {
+ // untangle the model
+
+ actions := make([]client.SystemAction, 0, len(ss.Actions))
+ for _, sa := range ss.Actions {
+ actions = append(actions, client.SystemAction{
+ Title: sa.Title,
+ Mode: sa.Mode,
+ })
+ }
+
+ rsp.Systems = append(rsp.Systems, client.System{
+ Current: ss.Current,
+ Label: ss.Label,
+ Model: client.SystemModelData{
+ Model: ss.Model.Model(),
+ BrandID: ss.Model.BrandID(),
+ DisplayName: ss.Model.DisplayName(),
+ },
+ Brand: snap.StoreAccount{
+ ID: ss.Brand.AccountID(),
+ Username: ss.Brand.Username(),
+ DisplayName: ss.Brand.DisplayName(),
+ Validation: ss.Brand.Validation(),
+ },
+ Actions: actions,
+ })
+ }
+ return SyncResponse(&rsp, nil)
+}
+
+type systemActionRequest struct {
+ Action string `json:"action"`
+ client.SystemAction
+}
+
+func postSystemsAction(c *Command, r *http.Request, user *auth.UserState) Response {
+ var req systemActionRequest
+
+ systemLabel := muxVars(r)["label"]
+ if systemLabel == "" {
+ return BadRequest("system action requires the system label to be provided")
+ }
+
+ decoder := json.NewDecoder(r.Body)
+ if err := decoder.Decode(&req); err != nil {
+ return BadRequest("cannot decode request body into system action: %v", err)
+ }
+ if decoder.More() {
+ return BadRequest("extra content found in request body")
+ }
+ if req.Action != "do" {
+ return BadRequest("unsupported action %q", req.Action)
+ }
+ if req.Mode == "" {
+ return BadRequest("system action requires the mode to be provided")
+ }
+
+ sa := devicestate.SystemAction{
+ Title: req.Title,
+ Mode: req.Mode,
+ }
+ if err := c.d.overlord.DeviceManager().RequestSystemAction(systemLabel, sa); err != nil {
+ if os.IsNotExist(err) {
+ return NotFound("requested seed system %q does not exist", systemLabel)
+ }
+ if err == devicestate.ErrUnsupportedAction {
+ return BadRequest("requested action is not supported by system %q", systemLabel)
+ }
+ return InternalError(err.Error())
+ }
+ return SyncResponse(nil, nil)
+}
diff -Nru snapd-2.42.1+18.04/daemon/api_systems_test.go snapd-2.45.1+18.04/daemon/api_systems_test.go
--- snapd-2.42.1+18.04/daemon/api_systems_test.go 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/daemon/api_systems_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,511 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2020 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package daemon
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "gopkg.in/check.v1"
+
+ "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/client"
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/overlord/assertstate/assertstatetest"
+ "github.com/snapcore/snapd/overlord/devicestate"
+ "github.com/snapcore/snapd/overlord/hookstate"
+ "github.com/snapcore/snapd/overlord/state"
+ "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"
+)
+
+func (s *apiSuite) mockSystemSeeds(c *check.C) (restore func()) {
+ // now create a minimal uc20 seed dir with snaps/assertions
+ seed20 := &seedtest.TestingSeed20{
+ SeedSnaps: seedtest.SeedSnaps{
+ StoreSigning: s.storeSigning,
+ Brands: s.brands,
+ },
+ SeedDir: dirs.SnapSeedDir,
+ }
+
+ restore = seed.MockTrusted(seed20.StoreSigning.Trusted)
+
+ assertstest.AddMany(s.storeSigning.Database, s.brands.AccountsAndKeys("my-brand")...)
+ // add essential snaps
+ seed20.MakeAssertedSnap(c, "name: snapd\nversion: 1\ntype: snapd", nil, snap.R(1), "my-brand", s.storeSigning.Database)
+ seed20.MakeAssertedSnap(c, "name: pc\nversion: 1\ntype: gadget\nbase: core20", nil, snap.R(1), "my-brand", s.storeSigning.Database)
+ seed20.MakeAssertedSnap(c, "name: pc-kernel\nversion: 1\ntype: kernel", nil, snap.R(1), "my-brand", s.storeSigning.Database)
+ seed20.MakeAssertedSnap(c, "name: core20\nversion: 1\ntype: base", nil, snap.R(1), "my-brand", s.storeSigning.Database)
+ seed20.MakeSeed(c, "20191119", "my-brand", "my-model", map[string]interface{}{
+ "display-name": "my fancy model",
+ "architecture": "amd64",
+ "base": "core20",
+ "snaps": []interface{}{
+ map[string]interface{}{
+ "name": "pc-kernel",
+ "id": seed20.AssertedSnapID("pc-kernel"),
+ "type": "kernel",
+ "default-channel": "20",
+ },
+ map[string]interface{}{
+ "name": "pc",
+ "id": seed20.AssertedSnapID("pc"),
+ "type": "gadget",
+ "default-channel": "20",
+ }},
+ }, nil)
+ seed20.MakeSeed(c, "20200318", "my-brand", "my-model-2", map[string]interface{}{
+ "display-name": "same brand different model",
+ "architecture": "amd64",
+ "base": "core20",
+ "snaps": []interface{}{
+ map[string]interface{}{
+ "name": "pc-kernel",
+ "id": seed20.AssertedSnapID("pc-kernel"),
+ "type": "kernel",
+ "default-channel": "20",
+ },
+ map[string]interface{}{
+ "name": "pc",
+ "id": seed20.AssertedSnapID("pc"),
+ "type": "gadget",
+ "default-channel": "20",
+ }},
+ }, nil)
+
+ return restore
+}
+
+func (s *apiSuite) TestGetSystemsSome(c *check.C) {
+ d := s.daemonWithOverlordMock(c)
+ hookMgr, err := hookstate.Manager(d.overlord.State(), d.overlord.TaskRunner())
+ c.Assert(err, check.IsNil)
+ mgr, err := devicestate.Manager(d.overlord.State(), hookMgr, d.overlord.TaskRunner(), nil)
+ c.Assert(err, check.IsNil)
+ d.overlord.AddManager(mgr)
+
+ st := d.overlord.State()
+ st.Lock()
+ st.Set("seeded-systems", []map[string]interface{}{{
+ "system": "20200318", "model": "my-model-2", "brand-id": "my-brand",
+ "revision": 2, "timestamp": "2009-11-10T23:00:00Z",
+ "seed-time": "2009-11-10T23:00:00Z",
+ }})
+ st.Unlock()
+
+ restore := s.mockSystemSeeds(c)
+ defer restore()
+
+ req, err := http.NewRequest("GET", "/v2/systems", nil)
+ c.Assert(err, check.IsNil)
+ rsp := getSystems(systemsCmd, req, nil).(*resp)
+
+ c.Assert(rsp.Status, check.Equals, 200)
+ sys := rsp.Result.(*systemsResponse)
+
+ c.Assert(sys, check.DeepEquals, &systemsResponse{
+ Systems: []client.System{
+ {
+ Current: false,
+ Label: "20191119",
+ Model: client.SystemModelData{
+ Model: "my-model",
+ BrandID: "my-brand",
+ DisplayName: "my fancy model",
+ },
+ Brand: snap.StoreAccount{
+ ID: "my-brand",
+ Username: "my-brand",
+ DisplayName: "My-brand",
+ Validation: "unproven",
+ },
+ Actions: []client.SystemAction{
+ {Title: "Install", Mode: "install"},
+ },
+ }, {
+ Current: true,
+ Label: "20200318",
+ Model: client.SystemModelData{
+ Model: "my-model-2",
+ BrandID: "my-brand",
+ DisplayName: "same brand different model",
+ },
+ Brand: snap.StoreAccount{
+ ID: "my-brand",
+ Username: "my-brand",
+ DisplayName: "My-brand",
+ Validation: "unproven",
+ },
+ Actions: []client.SystemAction{
+ {Title: "Reinstall", Mode: "install"},
+ {Title: "Recover", Mode: "recover"},
+ {Title: "Run normally", Mode: "run"},
+ },
+ },
+ }})
+}
+
+func (s *apiSuite) TestGetSystemsNone(c *check.C) {
+ // model assertion setup
+ d := s.daemonWithOverlordMock(c)
+ hookMgr, err := hookstate.Manager(d.overlord.State(), d.overlord.TaskRunner())
+ c.Assert(err, check.IsNil)
+ mgr, err := devicestate.Manager(d.overlord.State(), hookMgr, d.overlord.TaskRunner(), nil)
+ c.Assert(err, check.IsNil)
+ d.overlord.AddManager(mgr)
+
+ // no system seeds
+ req, err := http.NewRequest("GET", "/v2/systems", nil)
+ c.Assert(err, check.IsNil)
+ rsp := getSystems(systemsCmd, req, nil).(*resp)
+
+ c.Assert(rsp.Status, check.Equals, 200)
+ sys := rsp.Result.(*systemsResponse)
+
+ c.Assert(sys, check.DeepEquals, &systemsResponse{})
+}
+
+func (s *apiSuite) TestSystemActionRequestErrors(c *check.C) {
+ d := s.daemonWithOverlordMock(c)
+ hookMgr, err := hookstate.Manager(d.overlord.State(), d.overlord.TaskRunner())
+ c.Assert(err, check.IsNil)
+ mgr, err := devicestate.Manager(d.overlord.State(), hookMgr, d.overlord.TaskRunner(), nil)
+ c.Assert(err, check.IsNil)
+ d.overlord.AddManager(mgr)
+
+ restore := s.mockSystemSeeds(c)
+ defer restore()
+
+ type table struct {
+ label, body, error string
+ status int
+ }
+ tests := []table{
+ {
+ label: "foobar",
+ body: `"bogus"`,
+ error: "cannot decode request body into system action:.*",
+ status: 400,
+ }, {
+ label: "",
+ body: `{"action":"do","mode":"install"}`,
+ error: "system action requires the system label to be provided",
+ status: 400,
+ }, {
+ label: "foobar",
+ body: `{"action":"do"}`,
+ error: "system action requires the mode to be provided",
+ status: 400,
+ }, {
+ label: "foobar",
+ body: `{"action":"nope","mode":"install"}`,
+ error: `unsupported action "nope"`,
+ status: 400,
+ }, {
+ label: "foobar",
+ body: `{"action":"do","mode":"install"}`,
+ error: `requested seed system "foobar" does not exist`,
+ status: 404,
+ }, {
+ // valid system label but incorrect action
+ label: "20191119",
+ body: `{"action":"do","mode":"foobar"}`,
+ error: `requested action is not supported by system "20191119"`,
+ status: 400,
+ },
+ }
+ for _, tc := range tests {
+ s.vars = map[string]string{"label": tc.label}
+ c.Logf("tc: %#v", tc)
+ req, err := http.NewRequest("POST", "/v2/systems/"+tc.label, strings.NewReader(tc.body))
+ c.Assert(err, check.IsNil)
+ rsp := postSystemsAction(systemsActionCmd, req, nil).(*resp)
+ c.Assert(rsp.Type, check.Equals, ResponseTypeError)
+ c.Check(rsp.Status, check.Equals, tc.status)
+ c.Check(rsp.ErrorResult().Message, check.Matches, tc.error)
+ }
+}
+
+func (s *apiSuite) TestSystemActionRequestWithSeeded(c *check.C) {
+ bt := bootloadertest.Mock("mock", c.MkDir())
+ bootloader.Force(bt)
+ defer func() { bootloader.Force(nil) }()
+
+ cmd := testutil.MockCommand(c, "shutdown", "")
+ defer cmd.Restore()
+
+ restore := s.mockSystemSeeds(c)
+ defer restore()
+
+ model := s.brands.Model("my-brand", "pc", map[string]interface{}{
+ "architecture": "amd64",
+ // UC20
+ "grade": "dangerous",
+ "base": "core20",
+ "snaps": []interface{}{
+ map[string]interface{}{
+ "name": "pc-kernel",
+ "id": snaptest.AssertedSnapID("oc-kernel"),
+ "type": "kernel",
+ "default-channel": "20",
+ },
+ map[string]interface{}{
+ "name": "pc",
+ "id": snaptest.AssertedSnapID("pc"),
+ "type": "gadget",
+ "default-channel": "20",
+ },
+ },
+ })
+
+ currentSystem := []map[string]interface{}{{
+ "system": "20191119", "model": "my-model", "brand-id": "my-brand",
+ "revision": 2, "timestamp": "2009-11-10T23:00:00Z",
+ "seed-time": "2009-11-10T23:00:00Z",
+ }}
+
+ tt := []struct {
+ currentMode string
+ actionMode string
+ expUnsupported bool
+ expRestart bool
+ comment string
+ }{
+ {
+ // from run mode -> install mode works to reinstall the system
+ currentMode: "run",
+ actionMode: "install",
+ expRestart: true,
+ comment: "run mode to install mode",
+ },
+ {
+ // from run mode -> recover mode works to recover the system
+ currentMode: "run",
+ actionMode: "recover",
+ expRestart: true,
+ comment: "run mode to recover mode",
+ },
+ {
+ // from run mode -> run mode is no-op
+ currentMode: "run",
+ actionMode: "run",
+ comment: "run mode to run mode",
+ },
+ {
+ // from recover mode -> run mode works to stop recovering and "restore" the system to normal
+ currentMode: "recover",
+ actionMode: "run",
+ expRestart: true,
+ comment: "recover mode to run mode",
+ },
+ {
+ // from recover mode -> install mode works to stop recovering and reinstall the system if all is lost
+ currentMode: "recover",
+ actionMode: "install",
+ expRestart: true,
+ comment: "recover mode to install mode",
+ },
+ {
+ // from recover mode -> recover mode is no-op
+ currentMode: "recover",
+ actionMode: "recover",
+ comment: "recover mode to recover mode",
+ },
+ {
+ // from install mode -> install mode is no-no
+ currentMode: "install",
+ actionMode: "install",
+ expUnsupported: true,
+ comment: "install mode to install mode not supported",
+ },
+ {
+ // from install mode -> run mode is no-no
+ currentMode: "install",
+ actionMode: "run",
+ expUnsupported: true,
+ comment: "install mode to run mode not supported",
+ },
+ {
+ // from install mode -> recover mode is no-no
+ currentMode: "install",
+ actionMode: "recover",
+ expUnsupported: true,
+ comment: "install mode to recover mode not supported",
+ },
+ }
+ s.vars = map[string]string{"label": "20191119"}
+
+ for _, t := range tt {
+ // daemon setup - need to do this per-test because we need to re-read
+ // the modeenv during devicemgr startup
+ m := boot.Modeenv{
+ Mode: t.currentMode,
+ }
+ err := m.WriteTo("")
+ c.Assert(err, check.IsNil)
+ d := s.daemon(c)
+ st := d.overlord.State()
+ st.Lock()
+ // devicemgr needs boot id to request a reboot
+ st.VerifyReboot("boot-id-0")
+ // device model
+ assertstatetest.AddMany(st, s.storeSigning.StoreAccountKey(""))
+ assertstatetest.AddMany(st, s.brands.AccountsAndKeys("my-brand")...)
+ s.mockModel(c, st, model)
+ st.Set("seeded-systems", currentSystem)
+ st.Unlock()
+
+ body := map[string]string{
+ "action": "do",
+ "mode": t.actionMode,
+ }
+ b, err := json.Marshal(body)
+ c.Assert(err, check.IsNil, check.Commentf(t.comment))
+ buf := bytes.NewBuffer(b)
+ req, err := http.NewRequest("POST", "/v2/systems/20191119", buf)
+ c.Assert(err, check.IsNil, check.Commentf(t.comment))
+ // as root
+ req.RemoteAddr = "pid=100;uid=0;socket=;"
+ rec := httptest.NewRecorder()
+ systemsActionCmd.ServeHTTP(rec, req)
+ if t.expUnsupported {
+ c.Check(rec.Code, check.Equals, 400, check.Commentf(t.comment))
+ } else {
+ c.Check(rec.Code, check.Equals, 200, check.Commentf(t.comment))
+ }
+
+ var rspBody map[string]interface{}
+ err = json.Unmarshal(rec.Body.Bytes(), &rspBody)
+ c.Assert(err, check.IsNil, check.Commentf(t.comment))
+
+ var expResp map[string]interface{}
+ if t.expUnsupported {
+ expResp = map[string]interface{}{
+ "result": map[string]interface{}{
+ "message": fmt.Sprintf("requested action is not supported by system %q", "20191119"),
+ },
+ "status": "Bad Request",
+ "status-code": 400.0,
+ "type": "error",
+ }
+ } else {
+ expResp = map[string]interface{}{
+ "result": nil,
+ "status": "OK",
+ "status-code": 200.0,
+ "type": "sync",
+ }
+ if t.expRestart {
+ expResp["maintenance"] = map[string]interface{}{
+ "kind": "system-restart",
+ "message": "system is restarting",
+ }
+
+ // daemon is not started, only check whether reboot was scheduled as expected
+
+ // reboot flag
+ c.Check(d.restartSystem, check.Equals, state.RestartSystemNow, check.Commentf(t.comment))
+ // slow reboot schedule
+ c.Check(cmd.Calls(), check.DeepEquals, [][]string{
+ {"shutdown", "-r", "+10", "reboot scheduled to update the system"},
+ },
+ check.Commentf(t.comment),
+ )
+ }
+ }
+
+ c.Assert(rspBody, check.DeepEquals, expResp, check.Commentf(t.comment))
+
+ cmd.ForgetCalls()
+ s.d = nil
+ }
+
+}
+
+func (s *apiSuite) TestSystemActionBrokenSeed(c *check.C) {
+ d := s.daemonWithOverlordMock(c)
+ hookMgr, err := hookstate.Manager(d.overlord.State(), d.overlord.TaskRunner())
+ c.Assert(err, check.IsNil)
+ mgr, err := devicestate.Manager(d.overlord.State(), hookMgr, d.overlord.TaskRunner(), nil)
+ c.Assert(err, check.IsNil)
+ d.overlord.AddManager(mgr)
+
+ restore := s.mockSystemSeeds(c)
+ defer restore()
+
+ err = os.Remove(filepath.Join(dirs.SnapSeedDir, "systems", "20191119", "model"))
+ c.Assert(err, check.IsNil)
+
+ s.vars = map[string]string{"label": "20191119"}
+ body := `{"action":"do","title":"reinstall","mode":"install"}`
+ req, err := http.NewRequest("POST", "/v2/systems/20191119", strings.NewReader(body))
+ c.Assert(err, check.IsNil)
+ rsp := postSystemsAction(systemsActionCmd, req, nil).(*resp)
+ c.Check(rsp.Status, check.Equals, 500)
+ c.Check(rsp.ErrorResult().Message, check.Matches, `cannot load seed system: cannot load assertions: .*`)
+}
+
+func (s *apiSuite) TestSystemActionNonRoot(c *check.C) {
+ d := s.daemonWithOverlordMock(c)
+ hookMgr, err := hookstate.Manager(d.overlord.State(), d.overlord.TaskRunner())
+ c.Assert(err, check.IsNil)
+ mgr, err := devicestate.Manager(d.overlord.State(), hookMgr, d.overlord.TaskRunner(), nil)
+ c.Assert(err, check.IsNil)
+ d.overlord.AddManager(mgr)
+
+ s.vars = map[string]string{"label": "20191119"}
+ body := `{"action":"do","title":"reinstall","mode":"install"}`
+
+ // pretend to be a simple user
+ req, err := http.NewRequest("POST", "/v2/systems/20191119", strings.NewReader(body))
+ c.Assert(err, check.IsNil)
+ // non root
+ req.RemoteAddr = "pid=100;uid=1234;socket=;"
+
+ rec := httptest.NewRecorder()
+ systemsActionCmd.ServeHTTP(rec, req)
+ c.Assert(rec.Code, check.Equals, 401)
+
+ var rspBody map[string]interface{}
+ err = json.Unmarshal(rec.Body.Bytes(), &rspBody)
+ c.Check(err, check.IsNil)
+ c.Check(rspBody, check.DeepEquals, map[string]interface{}{
+ "result": map[string]interface{}{
+ "message": "access denied",
+ "kind": "login-required",
+ },
+ "status": "Unauthorized",
+ "status-code": 401.0,
+ "type": "error",
+ })
+}
diff -Nru snapd-2.42.1+18.04/daemon/api_test.go snapd-2.45.1+18.04/daemon/api_test.go
--- snapd-2.42.1+18.04/daemon/api_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/daemon/api_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -71,6 +71,7 @@
"github.com/snapcore/snapd/overlord/snapstate"
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/release"
+ "github.com/snapcore/snapd/sandbox"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/channel"
"github.com/snapcore/snapd/snap/snaptest"
@@ -120,6 +121,8 @@
userInfoExpectedEmail string
restoreSanitize func()
+
+ testutil.BaseTest
}
func (s *apiBaseSuite) pokeStateLock() {
@@ -150,7 +153,7 @@
return s.rsnaps, s.err
}
-func (s *apiBaseSuite) SnapAction(ctx context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, user *auth.UserState, opts *store.RefreshOptions) ([]*snap.Info, error) {
+func (s *apiBaseSuite) SnapAction(ctx context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, user *auth.UserState, opts *store.RefreshOptions) ([]store.SnapActionResult, error) {
s.pokeStateLock()
if ctx == nil {
@@ -160,7 +163,11 @@
s.actions = actions
s.user = user
- return s.rsnaps, s.err
+ sars := make([]store.SnapActionResult, len(s.rsnaps))
+ for i, rsnap := range s.rsnaps {
+ sars[i] = store.SnapActionResult{Info: rsnap}
+ }
+ return sars, s.err
}
func (s *apiBaseSuite) SuggestedCurrency() string {
@@ -211,7 +218,7 @@
func (s *apiBaseSuite) SetUpSuite(c *check.C) {
muxVars = s.muxVars
- s.restoreRelease = release.MockForcedDevmode(false)
+ s.restoreRelease = sandbox.MockForceDevMode(false)
s.systemctlRestorer = systemd.MockSystemctl(s.systemctl)
s.journalctlRestorer = systemd.MockJournalctl(s.journalctl)
s.restoreSanitize = snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})
@@ -228,7 +235,14 @@
func (s *apiBaseSuite) systemctl(args ...string) (buf []byte, err error) {
s.sysctlArgses = append(s.sysctlArgses, args)
- if args[0] != "show" && args[0] != "start" && args[0] != "stop" && args[0] != "restart" {
+ if len(args) > 2 && args[0] == "--root" && args[2] == "is-enabled" {
+ // drop the first 2 args which are "--root some-dir"
+ args = args[2:]
+ }
+
+ switch args[0] {
+ case "show", "start", "stop", "restart", "is-enabled":
+ default:
panic(fmt.Sprintf("unexpected systemctl call: %v", args))
}
@@ -269,6 +283,9 @@
dirs.SetRootDir(c.MkDir())
err := os.MkdirAll(filepath.Dir(dirs.SnapStateFile), 0755)
+ restore := osutil.MockMountInfo("")
+ s.AddCleanup(restore)
+
c.Assert(err, check.IsNil)
c.Assert(os.MkdirAll(dirs.SnapMountDir, 0755), check.IsNil)
c.Assert(os.MkdirAll(dirs.SnapBlobDir, 0755), check.IsNil)
@@ -502,7 +519,7 @@
snapst.Active = active
snapst.Sequence = append(snapst.Sequence, &snapInfo.SideInfo)
snapst.Current = snapInfo.SideInfo.Revision
- snapst.Channel = "stable"
+ snapst.TrackingChannel = "stable"
snapst.InstanceKey = instanceKey
snapstate.Set(st, instanceName, &snapst)
@@ -676,7 +693,7 @@
c.Assert(err, check.IsNil)
// modify state
- snapst.Channel = "beta"
+ snapst.TrackingChannel = "beta"
snapst.IgnoreValidation = true
snapst.CohortKey = "some-long-cohort-key"
st.Lock()
@@ -790,12 +807,11 @@
},
},
},
- Broken: "",
- Contact: "",
- License: "GPL-3.0",
- CommonIDs: []string{"org.foo.cmd"},
- Screenshots: []snap.ScreenshotInfo{{Note: snap.ScreenshotsDeprecationNotice}},
- CohortKey: "some-long-cohort-key",
+ Broken: "",
+ Contact: "",
+ License: "GPL-3.0",
+ CommonIDs: []string{"org.foo.cmd"},
+ CohortKey: "some-long-cohort-key",
},
Meta: meta,
}
@@ -909,9 +925,9 @@
about := aboutSnap{
info: info,
snapst: &snapstate.SnapState{
- Active: true,
- Channel: "flaky/beta",
- Current: snap.R(7),
+ Active: true,
+ TrackingChannel: "flaky/beta",
+ Current: snap.R(7),
Flags: snapstate.Flags{
IgnoreValidation: true,
DevMode: true,
@@ -949,7 +965,6 @@
CommonIDs: []string{"foo", "bar"},
MountedFrom: filepath.Join(dirs.SnapBlobDir, "some-snap_instance_7.snap"),
Media: media,
- Screenshots: []snap.ScreenshotInfo{{Note: snap.ScreenshotsDeprecationNotice}},
Apps: []client.AppInfo{
{Snap: "some-snap_instance", Name: "bar"},
{Snap: "some-snap_instance", Name: "foo"},
@@ -993,7 +1008,6 @@
// check it only does GET
c.Check(rootCmd.PUT, check.IsNil)
c.Check(rootCmd.POST, check.IsNil)
- c.Check(rootCmd.DELETE, check.IsNil)
c.Assert(rootCmd.GET, check.NotNil)
rec := httptest.NewRecorder()
@@ -1010,11 +1024,16 @@
c.Check(rsp.Result, check.DeepEquals, expected)
}
+func mockSystemdVirt(newVirt string) (restore func()) {
+ oldVirt := systemdVirt
+ systemdVirt = newVirt
+ return func() { systemdVirt = oldVirt }
+}
+
func (s *apiSuite) TestSysInfo(c *check.C) {
// check it only does GET
c.Check(sysInfoCmd.PUT, check.IsNil)
c.Check(sysInfoCmd.POST, check.IsNil)
- c.Check(sysInfoCmd.DELETE, check.IsNil)
c.Assert(sysInfoCmd.GET, check.NotNil)
rec := httptest.NewRecorder()
@@ -1036,10 +1055,12 @@
defer restore()
restore = release.MockOnClassic(true)
defer restore()
- restore = release.MockForcedDevmode(true)
+ restore = sandbox.MockForceDevMode(true)
defer restore()
// reload dirs for release info to have effect
dirs.SetRootDir(dirs.GlobalRootDir)
+ restore = mockSystemdVirt("magic")
+ defer restore()
buildID := "this-is-my-build-id"
restore = MockBuildID(buildID)
@@ -1069,6 +1090,8 @@
},
"confinement": "partial",
"sandbox-features": map[string]interface{}{"confinement-options": []interface{}{"classic", "devmode"}},
+ "architecture": arch.DpkgArchitecture(),
+ "virtualization": "magic",
}
var rsp resp
c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), check.IsNil)
@@ -1091,7 +1114,9 @@
defer restore()
restore = release.MockOnClassic(true)
defer restore()
- restore = release.MockForcedDevmode(true)
+ restore = sandbox.MockForceDevMode(true)
+ defer restore()
+ restore = mockSystemdVirt("kvm")
defer restore()
// reload dirs for release info to have effect
dirs.SetRootDir(dirs.GlobalRootDir)
@@ -1143,6 +1168,8 @@
"apparmor": []interface{}{"feature-1", "feature-2"},
"confinement-options": []interface{}{"classic", "devmode"}, // we know it's this because of the release.Mock... calls above
},
+ "architecture": arch.DpkgArchitecture(),
+ "virtualization": "kvm",
}
var rsp resp
c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), check.IsNil)
@@ -1766,7 +1793,6 @@
c.Assert(snaps, check.HasLen, 1)
c.Assert(snaps[0]["name"], check.Equals, "store")
c.Check(snaps[0]["prices"], check.IsNil)
- c.Check(snaps[0]["screenshots"], check.DeepEquals, []interface{}{map[string]interface{}{"note": snap.ScreenshotsDeprecationNotice}})
c.Check(snaps[0]["channels"], check.IsNil)
c.Check(rsp.SuggestedCurrency, check.Equals, "EUR")
@@ -1925,8 +1951,8 @@
_ = searchStore(findCmd, req, nil).(*resp)
c.Check(s.storeSearch, check.DeepEquals, store.Search{
- Query: "foo",
- Section: "bar",
+ Query: "foo",
+ Category: "bar",
})
}
@@ -2188,11 +2214,6 @@
c.Assert(snaps, check.HasLen, 1)
c.Check(snaps[0]["name"], check.Equals, "test-screenshot")
- c.Check(snaps[0]["screenshots"], check.DeepEquals, []interface{}{
- map[string]interface{}{
- "note": snap.ScreenshotsDeprecationNotice,
- },
- })
c.Check(snaps[0]["media"], check.DeepEquals, []interface{}{
map[string]interface{}{
"type": "screenshot",
@@ -2414,7 +2435,79 @@
c.Check(rsp.Result, check.NotNil)
}
+func (s *apiSuite) TestPostSnapBadChannel(c *check.C) {
+ buf := bytes.NewBufferString(`{"channel": "1/2/3/4"}`)
+ req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf)
+ c.Assert(err, check.IsNil)
+
+ rsp := postSnap(snapCmd, req, nil).(*resp)
+
+ c.Check(rsp.Type, check.Equals, ResponseTypeError)
+ c.Check(rsp.Status, check.Equals, 400)
+ c.Check(rsp.Result, check.NotNil)
+}
+
func (s *apiSuite) TestPostSnap(c *check.C) {
+ s.testPostSnap(c, false)
+}
+
+func (s *apiSuite) TestPostSnapWithChannel(c *check.C) {
+ s.testPostSnap(c, true)
+}
+
+func (s *apiSuite) testPostSnap(c *check.C, withChannel bool) {
+ d := s.daemonWithOverlordMock(c)
+
+ soon := 0
+ ensureStateSoon = func(st *state.State) {
+ soon++
+ ensureStateSoonImpl(st)
+ }
+
+ s.vars = map[string]string{"name": "foo"}
+
+ snapInstructionDispTable["install"] = func(inst *snapInstruction, _ *state.State) (string, []*state.TaskSet, error) {
+ if withChannel {
+ // channel in -> channel out
+ c.Check(inst.Channel, check.Equals, "xyzzy")
+ } else {
+ // no channel in -> no channel out
+ c.Check(inst.Channel, check.Equals, "")
+ }
+ return "foooo", nil, nil
+ }
+ defer func() {
+ snapInstructionDispTable["install"] = snapInstall
+ }()
+
+ var buf *bytes.Buffer
+ if withChannel {
+ buf = bytes.NewBufferString(`{"action": "install", "channel": "xyzzy"}`)
+ } else {
+ buf = bytes.NewBufferString(`{"action": "install"}`)
+ }
+ req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf)
+ c.Assert(err, check.IsNil)
+
+ rsp := postSnap(snapCmd, req, nil).(*resp)
+
+ c.Check(rsp.Type, check.Equals, ResponseTypeAsync)
+
+ st := d.overlord.State()
+ st.Lock()
+ defer st.Unlock()
+ chg := st.Change(rsp.Change)
+ c.Assert(chg, check.NotNil)
+ c.Check(chg.Summary(), check.Equals, "foooo")
+ var names []string
+ err = chg.Get("snap-names", &names)
+ c.Assert(err, check.IsNil)
+ c.Check(names, check.DeepEquals, []string{"foo"})
+
+ c.Check(soon, check.Equals, 1)
+}
+
+func (s *apiSuite) TestPostSnapChannel(c *check.C) {
d := s.daemonWithOverlordMock(c)
soon := 0
@@ -2674,7 +2767,7 @@
// try a multipart/form-data upload
body := sideLoadBodyWithoutDevMode
head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"}
- restore := release.MockForcedDevmode(true)
+ restore := sandbox.MockForceDevMode(true)
defer restore()
flags := snapstate.Flags{RemoveSnapPath: true}
chgSummary := s.sideloadCheck(c, body, head, "local", flags)
@@ -2846,7 +2939,7 @@
c.Assert(err, check.IsNil)
req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--")
- restore := release.MockForcedDevmode(true)
+ restore := sandbox.MockForceDevMode(true)
defer restore()
rsp := postSnaps(snapsCmd, req, nil).(*resp)
@@ -3609,7 +3702,7 @@
func (s *apiSuite) testInstall(c *check.C, forcedDevmode bool, flags snapstate.Flags, revision snap.Revision) {
calledFlags := snapstate.Flags{}
installQueue := []string{}
- restore := release.MockForcedDevmode(forcedDevmode)
+ restore := sandbox.MockForceDevMode(forcedDevmode)
defer restore()
snapstateInstall = func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
@@ -3851,9 +3944,11 @@
d := s.daemon(c)
inst := &snapInstruction{
- Action: "refresh",
- CohortKey: "xyzzy",
- Snaps: []string{"some-snap"},
+ Action: "refresh",
+ Snaps: []string{"some-snap"},
+ snapRevisionOptions: snapRevisionOptions{
+ CohortKey: "xyzzy",
+ },
}
st := d.overlord.State()
@@ -3881,9 +3976,9 @@
d := s.daemon(c)
inst := &snapInstruction{
- Action: "refresh",
- LeaveCohort: true,
- Snaps: []string{"some-snap"},
+ Action: "refresh",
+ snapRevisionOptions: snapRevisionOptions{LeaveCohort: true},
+ Snaps: []string{"some-snap"},
}
st := d.overlord.State()
@@ -3929,11 +4024,13 @@
cohort, channel = "", ""
leave = nil
inst := &snapInstruction{
- Action: "switch",
- CohortKey: t.cohort,
- Channel: t.channel,
- Snaps: []string{"some-snap"},
- LeaveCohort: t.leave,
+ Action: "switch",
+ snapRevisionOptions: snapRevisionOptions{
+ CohortKey: t.cohort,
+ LeaveCohort: t.leave,
+ Channel: t.channel,
+ },
+ Snaps: []string{"some-snap"},
}
st.Lock()
@@ -4233,9 +4330,11 @@
d := s.daemon(c)
inst := &snapInstruction{
- Action: "install",
- CohortKey: "To the legion of the lost ones, to the cohort of the damned.",
- Snaps: []string{"fake"},
+ Action: "install",
+ snapRevisionOptions: snapRevisionOptions{
+ CohortKey: "To the legion of the lost ones, to the cohort of the damned.",
+ },
+ Snaps: []string{"fake"},
}
st := d.overlord.State()
@@ -4302,7 +4401,7 @@
}
func (s *apiSuite) TestInstallJailModeDevModeOS(c *check.C) {
- restore := release.MockForcedDevmode(true)
+ restore := sandbox.MockForceDevMode(true)
defer restore()
d := s.daemon(c)
@@ -4403,19 +4502,19 @@
}
func (s *apiSuite) TestRevertSnapToRevision(c *check.C) {
- s.testRevertSnap(&snapInstruction{Revision: snap.R(1)}, c)
+ s.testRevertSnap(&snapInstruction{snapRevisionOptions: snapRevisionOptions{Revision: snap.R(1)}}, c)
}
func (s *apiSuite) TestRevertSnapToRevisionDevMode(c *check.C) {
- s.testRevertSnap(&snapInstruction{Revision: snap.R(1), DevMode: true}, c)
+ s.testRevertSnap(&snapInstruction{snapRevisionOptions: snapRevisionOptions{Revision: snap.R(1)}, DevMode: true}, c)
}
func (s *apiSuite) TestRevertSnapToRevisionJailMode(c *check.C) {
- s.testRevertSnap(&snapInstruction{Revision: snap.R(1), JailMode: true}, c)
+ s.testRevertSnap(&snapInstruction{snapRevisionOptions: snapRevisionOptions{Revision: snap.R(1)}, JailMode: true}, c)
}
func (s *apiSuite) TestRevertSnapToRevisionClassic(c *check.C) {
- s.testRevertSnap(&snapInstruction{Revision: snap.R(1), Classic: true}, c)
+ s.testRevertSnap(&snapInstruction{snapRevisionOptions: snapRevisionOptions{Revision: snap.R(1)}, Classic: true}, c)
}
func snapList(rawSnaps interface{}) []map[string]interface{} {
@@ -5160,6 +5259,42 @@
})
}
+func (s *apiSuite) TestDisconnectForgetPlugFailureNotConnected(c *check.C) {
+ revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
+ defer revert()
+ s.daemon(c)
+
+ s.mockSnap(c, consumerYaml)
+ s.mockSnap(c, producerYaml)
+
+ action := &interfaceAction{
+ Action: "disconnect",
+ Forget: true,
+ Plugs: []plugJSON{{Snap: "consumer", Name: "plug"}},
+ Slots: []slotJSON{{Snap: "producer", Name: "slot"}},
+ }
+ text, err := json.Marshal(action)
+ c.Assert(err, check.IsNil)
+ buf := bytes.NewBuffer(text)
+ req, err := http.NewRequest("POST", "/v2/interfaces", buf)
+ c.Assert(err, check.IsNil)
+ rec := httptest.NewRecorder()
+ interfacesCmd.POST(interfacesCmd, req, nil).ServeHTTP(rec, req)
+
+ c.Check(rec.Code, check.Equals, 400)
+ var body map[string]interface{}
+ err = json.Unmarshal(rec.Body.Bytes(), &body)
+ c.Check(err, check.IsNil)
+ c.Check(body, check.DeepEquals, map[string]interface{}{
+ "result": map[string]interface{}{
+ "message": "cannot forget connection consumer:plug from producer:slot, it was not connected",
+ },
+ "status": "Bad Request",
+ "status-code": 400.0,
+ "type": "error",
+ })
+}
+
func (s *apiSuite) TestDisconnectConflict(c *check.C) {
revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
defer revert()
@@ -6487,6 +6622,32 @@
c.Assert(rsp.Status, check.Equals, 403)
}
+func (s *apiSuite) TestSnapctlUnsuccesfulError(c *check.C) {
+ _ = s.daemon(c)
+
+ runSnapctlUcrednetGet = func(string) (int32, uint32, string, error) {
+ return 100, 9999, dirs.SnapSocket, nil
+ }
+ defer func() { runSnapctlUcrednetGet = ucrednetGet }()
+
+ ctlcmdRun = func(ctx *hookstate.Context, arg []string, uid uint32) ([]byte, []byte, error) {
+ return nil, nil, &ctlcmd.UnsuccessfulError{ExitCode: 123}
+ }
+ defer func() { ctlcmdRun = ctlcmd.Run }()
+
+ buf := bytes.NewBufferString(fmt.Sprintf(`{"context-id": "some-context", "args": [%q, %q]}`, "is-connected", "plug"))
+ req, err := http.NewRequest("POST", "/v2/snapctl", buf)
+ c.Assert(err, check.IsNil)
+ rsp := runSnapctl(snapctlCmd, req, nil).(*resp)
+ c.Check(rsp.Status, check.Equals, 200)
+ c.Check(rsp.Result.(*errorResult).Kind, check.Equals, errorKindUnsuccessful)
+ c.Check(rsp.Result.(*errorResult).Value, check.DeepEquals, map[string]interface{}{
+ "stdout": "",
+ "stderr": "",
+ "exit-code": 123,
+ })
+}
+
type appSuite struct {
apiBaseSuite
cmd *testutil.MockCmd
@@ -7310,6 +7471,15 @@
e := errors.New("other error")
+ sa1e := &store.SnapActionError{Refresh: map[string]error{"foo": store.ErrSnapNotFound}}
+ sa2e := &store.SnapActionError{Refresh: map[string]error{
+ "foo": store.ErrSnapNotFound,
+ "bar": store.ErrSnapNotFound,
+ }}
+ saOe := &store.SnapActionError{Other: []error{e}}
+ // this one can't happen (but fun to test):
+ saXe := &store.SnapActionError{Refresh: map[string]error{"foo": sa1e}}
+
makeErrorRsp := func(kind errorKind, err error, value interface{}) Response {
return SyncResponse(&resp{
Type: ResponseTypeError,
@@ -7336,6 +7506,13 @@
{netoe, BadRequest("ERR: %v", netoe)},
{nettmpe, BadRequest("ERR: %v", nettmpe)},
{e, BadRequest("ERR: %v", e)},
+
+ // action error unwrapping:
+ {sa1e, SnapNotFound("foo", store.ErrSnapNotFound)},
+ {saXe, SnapNotFound("foo", store.ErrSnapNotFound)},
+ // action errors, unwrapped:
+ {sa2e, BadRequest(`ERR: cannot refresh: snap not found: "bar", "foo"`)},
+ {saOe, BadRequest("ERR: cannot refresh, install, or download: other error")},
}
for _, t := range tests {
diff -Nru snapd-2.42.1+18.04/daemon/api_users.go snapd-2.45.1+18.04/daemon/api_users.go
--- snapd-2.42.1+18.04/daemon/api_users.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/daemon/api_users.go 2020-06-05 13:13:49.000000000 +0000
@@ -48,10 +48,12 @@
}
logoutCmd = &Command{
- Path: "/v2/logout",
- POST: logoutUser,
+ Path: "/v2/logout",
+ POST: logoutUser,
+ PolkitOK: "io.snapcraft.snapd.login",
}
+ // backwards compat; to-be-deprecated
createUserCmd = &Command{
Path: "/v2/create-user",
POST: postCreateUser,
@@ -61,11 +63,15 @@
usersCmd = &Command{
Path: "/v2/users",
GET: getUsers,
+ POST: postUsers,
RootOnly: true,
}
)
-var osutilAddUser = osutil.AddUser
+var (
+ osutilAddUser = osutil.AddUser
+ osutilDelUser = osutil.DelUser
+)
// userResponseData contains the data releated to user creation/login/query
type userResponseData struct {
@@ -200,7 +206,7 @@
if user == nil {
return BadRequest("not logged in")
}
- err := auth.RemoveUser(state, user.ID)
+ _, err := auth.RemoveUser(state, user.ID)
if err != nil {
return InternalError(err.Error())
}
@@ -208,7 +214,81 @@
return SyncResponse(nil, nil)
}
+// this might need to become a function, if having user admin becomes a config option
+var hasUserAdmin = !release.OnClassic
+
+const noUserAdmin = "system user administration via snapd is not allowed on this system"
+
+func postUsers(c *Command, r *http.Request, user *auth.UserState) Response {
+ if !hasUserAdmin {
+ return MethodNotAllowed(noUserAdmin)
+ }
+
+ var postData postUserData
+
+ decoder := json.NewDecoder(r.Body)
+ if err := decoder.Decode(&postData); err != nil {
+ return BadRequest("cannot decode user action data from request body: %v", err)
+ }
+ if decoder.More() {
+ return BadRequest("spurious content after user action")
+ }
+ switch postData.Action {
+ case "create":
+ return createUser(c, postData.postUserCreateData)
+ case "remove":
+ return removeUser(c, postData.Username, postData.postUserDeleteData)
+ case "":
+ return BadRequest("missing user action")
+ }
+ return BadRequest("unsupported user action %q", postData.Action)
+}
+
+func removeUser(c *Command, username string, opts postUserDeleteData) Response {
+ // TODO: allow to remove user entries by email as well
+
+ // catch silly errors
+ if username == "" {
+ return BadRequest("need a username to remove")
+ }
+ // check the user is known to snapd
+ st := c.d.overlord.State()
+ st.Lock()
+ _, err := auth.UserByUsername(st, username)
+ st.Unlock()
+ if err == auth.ErrInvalidUser {
+ return BadRequest("user %q is not known", username)
+ }
+ if err != nil {
+ return InternalError(err.Error())
+ }
+
+ // first remove the system user
+ if err := osutilDelUser(username, &osutil.DelUserOptions{ExtraUsers: !release.OnClassic}); err != nil {
+ return InternalError(err.Error())
+ }
+
+ // then the UserState
+ st.Lock()
+ u, err := auth.RemoveUserByUsername(st, username)
+ st.Unlock()
+ // ErrInvalidUser means "not found" in this case
+ if err != nil && err != auth.ErrInvalidUser {
+ return InternalError(err.Error())
+ }
+
+ result := map[string]interface{}{
+ "removed": []userResponseData{
+ {ID: u.ID, Username: u.Username, Email: u.Email},
+ },
+ }
+ return SyncResponse(result, nil)
+}
+
func postCreateUser(c *Command, r *http.Request, user *auth.UserState) Response {
+ if !hasUserAdmin {
+ return Forbidden(noUserAdmin)
+ }
var createData postUserCreateData
decoder := json.NewDecoder(r.Body)
@@ -216,6 +296,14 @@
return BadRequest("cannot decode create-user data from request body: %v", err)
}
+ // this is /v2/create-user, meaning we want the
+ // backwards-compatible wackiness
+ createData.singleUserResultCompat = true
+
+ return createUser(c, createData)
+}
+
+func createUser(c *Command, createData postUserCreateData) Response {
// verify request
st := c.d.overlord.State()
st.Lock()
@@ -278,10 +366,17 @@
return InternalError("%s", err)
}
- return SyncResponse(&userResponseData{
+ result := userResponseData{
Username: username,
SSHKeys: opts.SSHKeys,
- }, nil)
+ }
+
+ if createData.singleUserResultCompat {
+ // return a single userResponseData in this case
+ return SyncResponse(&result, nil)
+ } else {
+ return SyncResponse([]userResponseData{result}, nil)
+ }
}
func getUserDetailsFromStore(theStore snapstate.StoreService, email string) (string, *osutil.AddUserOptions, error) {
@@ -396,13 +491,28 @@
return su.Username(), opts, nil
}
+type postUserData struct {
+ Action string `json:"action"`
+ Username string `json:"username"`
+ postUserCreateData
+ postUserDeleteData
+}
+
type postUserCreateData struct {
Email string `json:"email"`
Sudoer bool `json:"sudoer"`
Known bool `json:"known"`
ForceManaged bool `json:"force-managed"`
+
+ // singleUserResultCompat indicates whether to preserve
+ // backwards compatibility, which results in more clunky
+ // return values (userResponseData OR [userResponseData] vs now
+ // uniform [userResponseData]); internal, not from JSON.
+ singleUserResultCompat bool
}
+type postUserDeleteData struct{}
+
var userLookup = user.Lookup
func setupLocalUser(st *state.State, username, email string) error {
diff -Nru snapd-2.42.1+18.04/daemon/api_users_test.go snapd-2.45.1+18.04/daemon/api_users_test.go
--- snapd-2.42.1+18.04/daemon/api_users_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/daemon/api_users_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -50,6 +50,7 @@
mockUserHome string
restoreClassic func()
+ oldUserAdmin bool
}
func (s *userSuite) SetUpTest(c *check.C) {
@@ -60,6 +61,18 @@
s.daemon(c)
s.mockUserHome = c.MkDir()
userLookup = mkUserLookup(s.mockUserHome)
+ s.oldUserAdmin = hasUserAdmin
+ hasUserAdmin = true
+
+ // make sure we don't call these by accident
+ osutilAddUser = func(name string, opts *osutil.AddUserOptions) error {
+ c.Fatalf("unexpected add user %q call", name)
+ return fmt.Errorf("unexpected add user %q call", name)
+ }
+ osutilDelUser = func(name string, opts *osutil.DelUserOptions) error {
+ c.Fatalf("unexpected del user %q call", name)
+ return fmt.Errorf("unexpected del user %q call", name)
+ }
}
func (s *userSuite) TearDownTest(c *check.C) {
@@ -67,8 +80,10 @@
userLookup = user.Lookup
osutilAddUser = osutil.AddUser
+ osutilDelUser = osutil.DelUser
s.restoreClassic()
+ hasUserAdmin = s.oldUserAdmin
}
func mkUserLookup(userHomeDir string) func(string) (*user.User, error) {
@@ -97,6 +112,14 @@
}
func (s *userSuite) TestPostCreateUser(c *check.C) {
+ s.testCreateUser(c, true)
+}
+
+func (s *userSuite) TestPostUserCreate(c *check.C) {
+ s.testCreateUser(c, false)
+}
+
+func (s *userSuite) testCreateUser(c *check.C, oldWay bool) {
expectedUsername := "karl"
s.userInfoExpectedEmail = "popper@lse.ac.uk"
s.userInfoResult = &store.User{
@@ -112,17 +135,29 @@
return nil
}
- buf := bytes.NewBufferString(fmt.Sprintf(`{"email": "%s"}`, s.userInfoExpectedEmail))
- req, err := http.NewRequest("POST", "/v2/create-user", buf)
- c.Assert(err, check.IsNil)
-
- rsp := postCreateUser(createUserCmd, req, nil).(*resp)
-
- expected := &userResponseData{
+ var rsp *resp
+ var expected interface{}
+ expectedItem := userResponseData{
Username: expectedUsername,
SSHKeys: []string{"ssh1", "ssh2"},
}
+ if oldWay {
+ buf := bytes.NewBufferString(fmt.Sprintf(`{"email": "%s"}`, s.userInfoExpectedEmail))
+ req, err := http.NewRequest("POST", "/v2/create-user", buf)
+ c.Assert(err, check.IsNil)
+
+ rsp = postCreateUser(createUserCmd, req, nil).(*resp)
+ expected = &expectedItem
+ } else {
+ buf := bytes.NewBufferString(fmt.Sprintf(`{"action":"create","email": "%s"}`, s.userInfoExpectedEmail))
+ req, err := http.NewRequest("POST", "/v2/users", buf)
+ c.Assert(err, check.IsNil)
+
+ rsp = postUsers(usersCmd, req, nil).(*resp)
+ expected = []userResponseData{expectedItem}
+ }
+
c.Check(rsp.Type, check.Equals, ResponseTypeSync)
c.Check(rsp.Result, check.FitsTypeOf, expected)
c.Check(rsp.Result, check.DeepEquals, expected)
@@ -144,6 +179,170 @@
1, expectedUsername, s.userInfoExpectedEmail, user.Macaroon))
}
+func (s *userSuite) TestNoUserAdminCreateUser(c *check.C) { s.testNoUserAdmin(c, "/v2/create-user") }
+func (s *userSuite) TestNoUserAdminPostUser(c *check.C) { s.testNoUserAdmin(c, "/v2/users") }
+func (s *userSuite) testNoUserAdmin(c *check.C, endpoint string) {
+ hasUserAdmin = false
+
+ buf := bytes.NewBufferString("{}")
+ req, err := http.NewRequest("POST", endpoint, buf)
+ c.Assert(err, check.IsNil)
+
+ switch endpoint {
+ case "/v2/users":
+ rsp := postUsers(usersCmd, req, nil).(*resp)
+ c.Check(rsp, check.DeepEquals, MethodNotAllowed(noUserAdmin))
+ case "/v2/create-user":
+ rsp := postCreateUser(createUserCmd, req, nil).(*resp)
+ c.Check(rsp, check.DeepEquals, Forbidden(noUserAdmin))
+ default:
+ c.Fatalf("unknown endpoint %q", endpoint)
+ }
+}
+
+func (s *userSuite) TestPostUserBadBody(c *check.C) {
+ buf := bytes.NewBufferString(`42`)
+ req, err := http.NewRequest("POST", "/v2/users", buf)
+ c.Assert(err, check.IsNil)
+
+ rsp := postUsers(usersCmd, req, nil).(*resp)
+ c.Check(rsp.Type, check.Equals, ResponseTypeError)
+ c.Check(rsp.Result.(*errorResult).Message, check.Matches, "cannot decode user action data from request body: .*")
+}
+
+func (s *userSuite) TestPostUserBadAfterBody(c *check.C) {
+ buf := bytes.NewBufferString(`{}42`)
+ req, err := http.NewRequest("POST", "/v2/users", buf)
+ c.Assert(err, check.IsNil)
+
+ rsp := postUsers(usersCmd, req, nil).(*resp)
+ c.Check(rsp, check.DeepEquals, BadRequest("spurious content after user action"))
+}
+
+func (s *userSuite) TestPostUserNoAction(c *check.C) {
+ buf := bytes.NewBufferString("{}")
+ req, err := http.NewRequest("POST", "/v2/users", buf)
+ c.Assert(err, check.IsNil)
+
+ rsp := postUsers(usersCmd, req, nil).(*resp)
+ c.Check(rsp, check.DeepEquals, BadRequest("missing user action"))
+}
+
+func (s *userSuite) TestPostUserBadAction(c *check.C) {
+ buf := bytes.NewBufferString(`{"action":"patatas"}`)
+ req, err := http.NewRequest("POST", "/v2/users", buf)
+ c.Assert(err, check.IsNil)
+
+ rsp := postUsers(usersCmd, req, nil).(*resp)
+ c.Check(rsp, check.DeepEquals, BadRequest(`unsupported user action "patatas"`))
+}
+
+func (s *userSuite) TestPostUserActionRemoveNoUsername(c *check.C) {
+ buf := bytes.NewBufferString(`{"action":"remove"}`)
+ req, err := http.NewRequest("POST", "/v2/users", buf)
+ c.Assert(err, check.IsNil)
+
+ rsp := postUsers(usersCmd, req, nil).(*resp)
+ c.Check(rsp, check.DeepEquals, BadRequest("need a username to remove"))
+}
+
+func (s *userSuite) TestPostUserActionRemoveDelUserErr(c *check.C) {
+ st := s.d.overlord.State()
+ st.Lock()
+ _, err := auth.NewUser(st, "some-user", "email@test.com", "macaroon", []string{"discharge"})
+ st.Unlock()
+ c.Check(err, check.IsNil)
+
+ called := 0
+ osutilDelUser = func(username string, opts *osutil.DelUserOptions) error {
+ called++
+ c.Check(username, check.Equals, "some-user")
+ return fmt.Errorf("wat")
+ }
+
+ buf := bytes.NewBufferString(`{"action":"remove","username":"some-user"}`)
+ req, err := http.NewRequest("POST", "/v2/users", buf)
+ c.Assert(err, check.IsNil)
+
+ rsp := postUsers(usersCmd, req, nil).(*resp)
+ c.Check(rsp.Status, check.Equals, 500)
+ c.Check(rsp.Result.(*errorResult).Message, check.Equals, "wat")
+ c.Check(called, check.Equals, 1)
+}
+
+func (s *userSuite) TestPostUserActionRemoveStateErr(c *check.C) {
+ st := s.d.overlord.State()
+ st.Lock()
+ st.Set("auth", 42) // breaks auth
+ st.Unlock()
+ called := 0
+ osutilDelUser = func(username string, opts *osutil.DelUserOptions) error {
+ called++
+ c.Check(username, check.Equals, "some-user")
+ return nil
+ }
+
+ buf := bytes.NewBufferString(`{"action":"remove","username":"some-user"}`)
+ req, err := http.NewRequest("POST", "/v2/users", buf)
+ c.Assert(err, check.IsNil)
+
+ rsp := postUsers(usersCmd, req, nil).(*resp)
+ c.Check(rsp.Status, check.Equals, 500)
+ c.Check(rsp.Result.(*errorResult).Message, check.Matches, `internal error: could not unmarshal state entry "auth": .*`)
+ c.Check(called, check.Equals, 0)
+}
+
+func (s *userSuite) TestPostUserActionRemoveNoUserInState(c *check.C) {
+ called := 0
+ osutilDelUser = func(username string, opts *osutil.DelUserOptions) error {
+ called++
+ c.Check(username, check.Equals, "some-user")
+ return nil
+ }
+
+ buf := bytes.NewBufferString(`{"action":"remove","username":"some-user"}`)
+ req, err := http.NewRequest("POST", "/v2/users", buf)
+ c.Assert(err, check.IsNil)
+
+ rsp := postUsers(usersCmd, req, nil).(*resp)
+ c.Check(rsp, check.DeepEquals, BadRequest(`user "some-user" is not known`))
+ c.Check(called, check.Equals, 0)
+}
+
+func (s *userSuite) TestPostUserActionRemove(c *check.C) {
+ st := s.d.overlord.State()
+ st.Lock()
+ user, err := auth.NewUser(st, "some-user", "email@test.com", "macaroon", []string{"discharge"})
+ st.Unlock()
+ c.Check(err, check.IsNil)
+
+ called := 0
+ osutilDelUser = func(username string, opts *osutil.DelUserOptions) error {
+ called++
+ c.Check(username, check.Equals, "some-user")
+ return nil
+ }
+
+ buf := bytes.NewBufferString(`{"action":"remove","username":"some-user"}`)
+ req, err := http.NewRequest("POST", "/v2/users", buf)
+ c.Assert(err, check.IsNil)
+ rsp := postUsers(usersCmd, req, nil).(*resp)
+ c.Check(rsp.Status, check.Equals, 200)
+ expected := []userResponseData{
+ {ID: user.ID, Username: user.Username, Email: user.Email},
+ }
+ c.Check(rsp.Result, check.DeepEquals, map[string]interface{}{
+ "removed": expected,
+ })
+ c.Check(called, check.Equals, 1)
+
+ // and the user is removed from state
+ st.Lock()
+ _, err = auth.User(st, user.ID)
+ st.Unlock()
+ c.Check(err, check.Equals, auth.ErrInvalidUser)
+}
+
func (s *userSuite) setupSigner(accountID string, signerPrivKey asserts.PrivateKey) *assertstest.SigningDB {
st := s.d.overlord.State()
diff -Nru snapd-2.42.1+18.04/daemon/daemon.go snapd-2.45.1+18.04/daemon/daemon.go
--- snapd-2.42.1+18.04/daemon/daemon.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/daemon/daemon.go 2020-06-05 13:13:49.000000000 +0000
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
- * Copyright (C) 2015-2016 Canonical Ltd
+ * Copyright (C) 2015-2020 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
@@ -37,7 +37,6 @@
"github.com/snapcore/snapd/client"
"github.com/snapcore/snapd/dirs"
- "github.com/snapcore/snapd/httputil"
"github.com/snapcore/snapd/i18n"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/netutil"
@@ -47,6 +46,7 @@
"github.com/snapcore/snapd/overlord/standby"
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/polkit"
+ "github.com/snapcore/snapd/snapdenv"
"github.com/snapcore/snapd/store"
"github.com/snapcore/snapd/systemd"
)
@@ -69,7 +69,7 @@
standbyOpinions *standby.StandbyOpinions
// set to remember we need to restart the system
- restartSystem bool
+ restartSystem state.RestartType
// set to remember that we need to exit the daemon in a way that
// prevents systemd from restarting it
restartSocket bool
@@ -89,10 +89,9 @@
Path string
PathPrefix string
//
- GET ResponseFunc
- PUT ResponseFunc
- POST ResponseFunc
- DELETE ResponseFunc
+ GET ResponseFunc
+ PUT ResponseFunc
+ POST ResponseFunc
// can guest GET?
GuestOK bool
// can non-admin GET?
@@ -123,7 +122,7 @@
//
// - if the user is `root` everything is allowed
// - if a user is logged in (via `snap login`) and the command doesn't have RootOnly, everything is allowed
-// - POST/PUT/DELETE all require `root`, or just `snap login` if not RootOnly
+// - POST/PUT all require `root`, or just `snap login` if not RootOnly
//
// Otherwise for GET requests the following parameters are honored:
// - GuestOK: anyone can access GET
@@ -252,8 +251,6 @@
rspf = c.PUT
case "POST":
rspf = c.POST
- case "DELETE":
- rspf = c.DELETE
}
if rspf != nil {
@@ -263,7 +260,7 @@
if rsp, ok := rsp.(*resp); ok {
_, rst := st.Restarting()
switch rst {
- case state.RestartSystem:
+ case state.RestartSystem, state.RestartSystemNow:
rsp.transmitMaintenance(errorKindSystemRestart, "system is restarting")
case state.RestartDaemon:
rsp.transmitMaintenance(errorKindDaemonRestart, "daemon is restarting")
@@ -343,7 +340,7 @@
d.addRoutes()
- logger.Noticef("started %v.", httputil.UserAgent())
+ logger.Noticef("started %v.", snapdenv.UserAgent())
return nil
}
@@ -484,7 +481,7 @@
// die when asked to restart (systemd should get us back up!) etc
switch t {
case state.RestartDaemon:
- case state.RestartSystem:
+ case state.RestartSystem, state.RestartSystemNow:
// try to schedule a fallback slow reboot already here
// in case we get stuck shutting down
if err := reboot(rebootWaitTimeout); err != nil {
@@ -494,11 +491,13 @@
d.mu.Lock()
defer d.mu.Unlock()
// remember we need to restart the system
- d.restartSystem = true
+ d.restartSystem = t
case state.RestartSocket:
d.mu.Lock()
defer d.mu.Unlock()
d.restartSocket = true
+ case state.StopDaemon:
+ logger.Noticef("stopping snapd as requested")
default:
logger.Noticef("internal error: restart handler called with unknown restart type: %v", t)
}
@@ -516,7 +515,9 @@
func (d *Daemon) Stop(sigCh chan<- os.Signal) error {
// we need to schedule/wait for a system restart again
if d.expectedRebootDidNotHappen {
- return d.doReboot(sigCh, rebootRetryWaitTimeout)
+ // make the reboot retry immediate
+ immediateReboot := true
+ return d.doReboot(sigCh, immediateReboot, rebootRetryWaitTimeout)
}
if d.overlord == nil {
return fmt.Errorf("internal error: no Overlord")
@@ -525,7 +526,8 @@
d.tomb.Kill(nil)
d.mu.Lock()
- restartSystem := d.restartSystem
+ restartSystem := d.restartSystem != state.RestartUnset
+ immediateReboot := d.restartSystem == state.RestartSystemNow
restartSocket := d.restartSocket
d.mu.Unlock()
@@ -580,19 +582,26 @@
err := d.tomb.Wait()
if err != nil {
- // do not stop the shutdown even if the tomb errors
- // because we already scheduled a slow shutdown and
- // exiting here will just restart snapd (via systemd)
- // which will lead to confusing results.
- if restartSystem {
- logger.Noticef("WARNING: cannot stop daemon: %v", err)
+ if err == context.DeadlineExceeded {
+ logger.Noticef("WARNING: cannot gracefully shut down in-flight snapd API activity within: %v", shutdownTimeout)
+ // the process is shutting down anyway, so we may just
+ // as well close the active connections right now
+ d.serve.Close()
} else {
- return err
+ // do not stop the shutdown even if the tomb errors
+ // because we already scheduled a slow shutdown and
+ // exiting here will just restart snapd (via systemd)
+ // which will lead to confusing results.
+ if restartSystem {
+ logger.Noticef("WARNING: cannot stop daemon: %v", err)
+ } else {
+ return err
+ }
}
}
if restartSystem {
- return d.doReboot(sigCh, rebootWaitTimeout)
+ return d.doReboot(sigCh, immediateReboot, rebootWaitTimeout)
}
if d.restartSocket {
@@ -602,7 +611,7 @@
return nil
}
-func (d *Daemon) rebootDelay() (time.Duration, error) {
+func (d *Daemon) rebootDelay(immediate bool) (time.Duration, error) {
d.state.Lock()
defer d.state.Unlock()
now := time.Now()
@@ -613,11 +622,14 @@
return 0, err
}
rebootDelay := 1 * time.Minute
+ if immediate {
+ rebootDelay = 0
+ }
if err == nil {
rebootDelay = rebootAt.Sub(now)
} else {
ovr := os.Getenv("SNAPD_REBOOT_DELAY") // for tests
- if ovr != "" {
+ if ovr != "" && !immediate {
d, err := time.ParseDuration(ovr)
if err == nil {
rebootDelay = d
@@ -629,8 +641,8 @@
return rebootDelay, nil
}
-func (d *Daemon) doReboot(sigCh chan<- os.Signal, waitTimeout time.Duration) error {
- rebootDelay, err := d.rebootDelay()
+func (d *Daemon) doReboot(sigCh chan<- os.Signal, immediate bool, waitTimeout time.Duration) error {
+ rebootDelay, err := d.rebootDelay(immediate)
if err != nil {
return err
}
diff -Nru snapd-2.42.1+18.04/daemon/daemon_test.go snapd-2.45.1+18.04/daemon/daemon_test.go
--- snapd-2.42.1+18.04/daemon/daemon_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/daemon/daemon_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -138,9 +138,8 @@
cmd.GET = rf
cmd.PUT = rf
cmd.POST = rf
- cmd.DELETE = rf
- for _, method := range []string{"GET", "POST", "PUT", "DELETE"} {
+ for _, method := range []string{"GET", "POST", "PUT"} {
req, err := http.NewRequest(method, "", nil)
req.Header.Add("User-Agent", fakeUserAgent)
c.Assert(err, check.IsNil)
@@ -638,7 +637,6 @@
} else {
w.Write([]byte("Gone"))
}
- return
})
// mark as already seeded
@@ -723,7 +721,99 @@
}
}
-func (s *daemonSuite) TestRestartSystemWiring(c *check.C) {
+func (s *daemonSuite) TestGracefulStopHasLimits(c *check.C) {
+ d := newTestDaemon(c)
+
+ // mark as already seeded
+ s.markSeeded(d)
+
+ restore := MockShutdownTimeout(time.Second)
+ defer restore()
+
+ responding := make(chan struct{})
+ doRespond := make(chan bool, 1)
+
+ d.router.HandleFunc("/endp", func(w http.ResponseWriter, r *http.Request) {
+ close(responding)
+ if <-doRespond {
+ for {
+ // write in a loop to keep the handler running
+ if _, err := w.Write([]byte("OKOK")); err != nil {
+ break
+ }
+ time.Sleep(50 * time.Millisecond)
+ }
+ } else {
+ w.Write([]byte("Gone"))
+ }
+ })
+
+ snapdL, err := net.Listen("tcp", "127.0.0.1:0")
+ c.Assert(err, check.IsNil)
+
+ snapL, err := net.Listen("tcp", "127.0.0.1:0")
+ c.Assert(err, check.IsNil)
+
+ snapdAccept := make(chan struct{})
+ snapdClosed := make(chan struct{})
+ d.snapdListener = &witnessAcceptListener{Listener: snapdL, accept: snapdAccept, closed: snapdClosed}
+
+ snapAccept := make(chan struct{})
+ d.snapListener = &witnessAcceptListener{Listener: snapL, accept: snapAccept}
+
+ c.Assert(d.Start(), check.IsNil)
+
+ snapdAccepting := make(chan struct{})
+ go func() {
+ select {
+ case <-snapdAccept:
+ case <-time.After(2 * time.Second):
+ c.Fatal("snapd accept was not called")
+ }
+ close(snapdAccepting)
+ }()
+
+ snapAccepting := make(chan struct{})
+ go func() {
+ select {
+ case <-snapAccept:
+ case <-time.After(2 * time.Second):
+ c.Fatal("snapd accept was not called")
+ }
+ close(snapAccepting)
+ }()
+
+ <-snapdAccepting
+ <-snapAccepting
+
+ clientErr := make(chan error)
+
+ go func() {
+ _, err := http.Get(fmt.Sprintf("http://%s/endp", snapdL.Addr()))
+ c.Assert(err, check.NotNil)
+ clientErr <- err
+ close(clientErr)
+ }()
+ go func() {
+ <-snapdClosed
+ time.Sleep(200 * time.Millisecond)
+ doRespond <- true
+ }()
+
+ <-responding
+ err = d.Stop(nil)
+ doRespond <- false
+ c.Check(err, check.IsNil)
+
+ select {
+ case cErr := <-clientErr:
+ c.Check(cErr, check.ErrorMatches, ".*: EOF")
+ case <-time.After(5 * time.Second):
+ c.Fatal("never got proper response")
+ }
+}
+
+func (s *daemonSuite) testRestartSystemWiring(c *check.C, restartKind state.RestartType) {
d := newTestDaemon(c)
// mark as already seeded
s.markSeeded(d)
@@ -782,12 +872,12 @@
}
st.Lock()
- st.RequestRestart(state.RestartSystem)
+ st.RequestRestart(restartKind)
st.Unlock()
defer func() {
d.mu.Lock()
- d.restartSystem = false
+ d.restartSystem = state.RestartUnset
d.mu.Unlock()
}()
@@ -801,7 +891,7 @@
rs := d.restartSystem
d.mu.Unlock()
- c.Check(rs, check.Equals, true)
+ c.Check(rs, check.Equals, restartKind)
c.Check(delays, check.HasLen, 1)
c.Check(delays[0], check.DeepEquals, rebootWaitTimeout)
@@ -813,7 +903,11 @@
c.Check(err, check.ErrorMatches, "expected reboot did not happen")
c.Check(delays, check.HasLen, 2)
- c.Check(delays[1], check.DeepEquals, 1*time.Minute)
+ if restartKind == state.RestartSystem {
+ c.Check(delays[1], check.DeepEquals, 1*time.Minute)
+ } else if restartKind == state.RestartSystemNow {
+ c.Check(delays[1], check.DeepEquals, time.Duration(0))
+ }
// we are not stopping, we wait for the reboot instead
c.Check(s.notified, check.DeepEquals, []string{"EXTEND_TIMEOUT_USEC=30000000", "READY=1"})
@@ -823,8 +917,21 @@
var rebootAt time.Time
err = st.Get("daemon-system-restart-at", &rebootAt)
c.Assert(err, check.IsNil)
- approxAt := now.Add(time.Minute)
- c.Check(rebootAt.After(approxAt) || rebootAt.Equal(approxAt), check.Equals, true)
+ if restartKind == state.RestartSystem {
+ approxAt := now.Add(time.Minute)
+ c.Check(rebootAt.After(approxAt) || rebootAt.Equal(approxAt), check.Equals, true)
+ } else if restartKind == state.RestartSystemNow {
+ // should be good enough
+ c.Check(rebootAt.Before(now.Add(10*time.Second)), check.Equals, true)
+ }
+}
+
+func (s *daemonSuite) TestRestartSystemGracefulWiring(c *check.C) {
+ s.testRestartSystemWiring(c, state.RestartSystem)
+}
+
+func (s *daemonSuite) TestRestartSystemImmediateWiring(c *check.C) {
+ s.testRestartSystemWiring(c, state.RestartSystemNow)
}
func (s *daemonSuite) TestRebootHelper(c *check.C) {
@@ -926,7 +1033,7 @@
st.Unlock()
sigCh := make(chan os.Signal, 2)
- // stop (this will timeout but thats not relevant for this test)
+ // stop (this will timeout but that's not relevant for this test)
d.Stop(sigCh)
// ensure that the sigCh got closed as part of the stop
diff -Nru snapd-2.42.1+18.04/daemon/export_api_download_test.go snapd-2.45.1+18.04/daemon/export_api_download_test.go
--- snapd-2.42.1+18.04/daemon/export_api_download_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/daemon/export_api_download_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -20,10 +20,12 @@
package daemon
var (
- SnapDownloadCmd = snapDownloadCmd
- PostSnapDownload = postSnapDownload
+ SnapDownloadCmd = snapDownloadCmd
+ PostSnapDownload = postSnapDownload
+ NewSnapStream = newSnapStream
+ DownloadTokensSecret = downloadTokensSecret
)
type (
- FileStream = fileStream
+ SnapStream = snapStream
)
diff -Nru snapd-2.42.1+18.04/daemon/export_test.go snapd-2.45.1+18.04/daemon/export_test.go
--- snapd-2.42.1+18.04/daemon/export_test.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/daemon/export_test.go 2020-06-05 13:13:49.000000000 +0000
@@ -21,6 +21,7 @@
import (
"net/http"
+ "time"
"github.com/snapcore/snapd/overlord"
)
@@ -28,6 +29,8 @@
type Resp = resp
type ErrorResult = errorResult
+var MinLane = minLane
+
func NewWithOverlord(o *overlord.Overlord) *Daemon {
d := &Daemon{overlord: o}
d.addRoutes()
@@ -49,3 +52,11 @@
buildID = old
}
}
+
+func MockShutdownTimeout(tm time.Duration) (restore func()) {
+ old := shutdownTimeout
+ shutdownTimeout = tm
+ return func() {
+ shutdownTimeout = old
+ }
+}
diff -Nru snapd-2.42.1+18.04/daemon/response.go snapd-2.45.1+18.04/daemon/response.go
--- snapd-2.42.1+18.04/daemon/response.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/daemon/response.go 2020-06-05 13:13:49.000000000 +0000
@@ -190,6 +190,8 @@
errorKindSystemRestart = errorKind("system-restart")
errorKindAssertionNotFound = errorKind("assertion-not-found")
+
+ errorKindUnsuccessful = errorKind("unsuccessful")
)
type errorValue interface{}
@@ -248,22 +250,41 @@
}
}
-// A FileStream ServeHTTP method streams the snap
-type fileStream struct {
+// A snapStream ServeHTTP method streams a snap
+type snapStream struct {
SnapName string
- Info snap.DownloadInfo
+ Filename string
+ Info *snap.DownloadInfo
+ Token string
stream io.ReadCloser
+ resume int64
}
// ServeHTTP from the Response interface
-func (s fileStream) ServeHTTP(w http.ResponseWriter, _ *http.Request) {
+func (s *snapStream) ServeHTTP(w http.ResponseWriter, _ *http.Request) {
hdr := w.Header()
hdr.Set("Content-Type", "application/octet-stream")
- snapname := fmt.Sprintf("attachment; filename=%s", s.SnapName)
+ snapname := fmt.Sprintf("attachment; filename=%s", s.Filename)
hdr.Set("Content-Disposition", snapname)
- size := fmt.Sprintf("%d", s.Info.Size)
- hdr.Set("Content-Length", size)
+ hdr.Set("Snap-Sha3-384", s.Info.Sha3_384)
+ // can't set Content-Length when stream is nil as it breaks http clients
+ // setting it also when there is a stream, for consistency
+ hdr.Set("Snap-Length", strconv.FormatInt(s.Info.Size, 10))
+ if s.Token != "" {
+ hdr.Set("Snap-Download-Token", s.Token)
+ }
+
+ if s.stream == nil {
+ // nothing to actually stream
+ return
+ }
+ hdr.Set("Content-Length", strconv.FormatInt(s.Info.Size-s.resume, 10))
+
+ if s.resume > 0 {
+ hdr.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", s.resume, s.Info.Size-1, s.Info.Size))
+ w.WriteHeader(206)
+ }
defer s.stream.Close()
bytesCopied, err := io.Copy(w, s.stream)
@@ -271,7 +292,7 @@
logger.Noticef("cannot copy snap %s (%#v) to the stream: %v", s.SnapName, s.Info, err)
http.Error(w, err.Error(), 500)
}
- if bytesCopied != s.Info.Size {
+ if bytesCopied != s.Info.Size-s.resume {
logger.Noticef("cannot copy snap %s (%#v) to the stream: bytes copied=%d, expected=%d", s.SnapName, s.Info, bytesCopied, s.Info.Size)
http.Error(w, io.EOF.Error(), 502)
}
@@ -587,6 +608,14 @@
} else {
handled = false
}
+ case *store.SnapActionError:
+ // we only handle a few specific cases
+ _, _, e := err.SingleOpError()
+ if e != nil {
+ // 👉😎👉
+ return errToResponse(e, snaps, fallback, format)
+ }
+ handled = false
default:
handled = false
}
diff -Nru snapd-2.42.1+18.04/daemon/snap.go snapd-2.45.1+18.04/daemon/snap.go
--- snapd-2.42.1+18.04/daemon/snap.go 2019-10-30 12:17:43.000000000 +0000
+++ snapd-2.45.1+18.04/daemon/snap.go 2020-06-05 13:13:49.000000000 +0000
@@ -303,7 +303,7 @@
result.Status = "active"
}
- result.TrackingChannel = snapst.Channel
+ result.TrackingChannel = snapst.TrackingChannel
result.IgnoreValidation = snapst.IgnoreValidation
result.CohortKey = snapst.CohortKey
result.DevMode = snapst.DevMode
diff -Nru snapd-2.42.1+18.04/data/completion/bash/complete.sh snapd-2.45.1+18.04/data/completion/bash/complete.sh
--- snapd-2.42.1+18.04/data/completion/bash/complete.sh 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/data/completion/bash/complete.sh 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,129 @@
+# shellcheck shell=bash
+#
+# Copyright (C) 2017 Canonical Ltd
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+# _complete_from_snap performs the tab completion request by calling the
+# appropriate 'snap run --command=complete' with serialized args, and
+# deserializes the response into the usual tab completion result.
+#
+# How snap command completion works is:
+# 1. snapd's complete.sh is sourced into the user's shell environment
+# 2. user performs ' '. If '' is a snap command,
+# proceed to step '3', otherwise perform normal bash completion
+# 3. run 'snap run --command=complete ...', converting bash completion
+# environment into serialized command line arguments
+# 4. 'snap run --command=complete ...' exec()s 'etelpmoc.sh' within the snap's
+# runtime environment and confinement
+# 5. 'etelpmoc.sh' takes the serialized command line arguments from step '3'
+# and puts them back into the bash completion environment variables
+# 6. 'etelpmoc.sh' sources the snap's 'completer' script, performs the bash
+# completion and serializes the resulting completion environment variables
+# by printing to stdout the results in a format that snapd's complete.sh
+# will understand, then exits
+# 7. control returns to snapd's 'complete.sh' and it deserializes the output
+# from 'etelpmoc.sh', validates the results and puts the validated results
+# into the bash completion environment variables
+# 8. bash displays the results to the user
+type -t _complete_from_snap > /dev/null ||
+_complete_from_snap() {
+ {
+ # De-serialize the output of 'snap run --command=complete ...' into the format
+ # bash expects:
+ read -r -a opts
+ # opts is expected to be a series of compopt options
+ if [[ ${#opts[@]} -gt 0 ]]; then
+ if [[ "${opts[0]}" == "cannot" ]]; then
+ # older snap-execs sent errors over stdout :-(
+ return 1
+ fi
+
+ for i in "${opts[@]}"; do
+ if ! [[ "$i" =~ ^[a-z]+$ ]]; then
+ # only lowercase alpha characters allowed
+ return 2
+ fi
+ done
+ fi
+
+ read -r bounced
+ case "$bounced" in
+ ""|"alias"|"export"|"job"|"variable")
+ # OK
+ ;;
+ *)
+ # unrecognised bounce
+ return 2
+ ;;
+ esac
+
+ read -r sep
+ if [ -n "$sep" ]; then
+ # non-blank separator? madness!
+ return 2
+ fi
+ local oldIFS="$IFS"
+
+ if [ ! "$bounced" ]; then
+ local IFS=$'\n'
+ # Ignore any suspicious results that are uncommon in filenames and that
+ # might be used to trick the user. A whitelist approach would be better
+ # but is impractical with UTF-8 and common characters like quotes.
+ COMPREPLY=( $( command grep -v '[[:cntrl:];&?*{}]' ) )
+ IFS="$oldIFS"
+ fi
+
+ if [[ ${#opts[@]} -gt 0 ]]; then
+ # shellcheck disable=SC2046
+ # (we *want* word splitting to happen here)
+ compopt $(printf " -o %s" "${opts[@]}")
+ fi
+ if [ "$bounced" ]; then
+ # We validated '$bounced' above and '${COMP_WORDS[$COMP_CWORD]}' is
+ # coming from the user's session, not the snap so skip input
+ # validation: we aren't trying to protect the user from themselves.
+ COMPREPLY+=(compgen -A "$bounced" -- "${COMP_WORDS[$COMP_CWORD]}")
+ fi
+ } < <(
+ snap run --command=complete "$1" "$COMP_TYPE" "$COMP_KEY" "$COMP_POINT" "$COMP_CWORD" "$COMP_WORDBREAKS" "$COMP_LINE" "${COMP_WORDS[@]}" 2>/dev/null || return 1
+ )
+
+}
+
+# this file can be sourced directly as e.g. /usr/lib/snapd/complete.sh, or via
+# a symlink from /usr/share/bash-completion/completions/. In the first case we
+# want to load the default loader; in the second, the specific one.
+#
+if [[ "${BASH_SOURCE[0]}" =~ ^/usr/share/bash-completion/completions/ ]]; then
+ complete -F _complete_from_snap "$1"
+else
+
+ # _complete_from_snap_maybe calls _complete_from_snap if the command is in
+ # bin/snap, and otherwise does bash-completion's _completion_loader (which is
+ # what -D would've done before).
+ type -t _complete_from_snap_maybe > /dev/null ||
+ _complete_from_snap_maybe() {
+ local etel=snap/core/current/usr/lib/snapd/etelpmoc.sh
+ # catch /snap/bin and /var/lib/snapd/snap/bin
+ if [[ "$(command -v "$1")" =~ ^(/var/lib/snapd)?/snap/bin/ && ( -e "/var/lib/snapd/$etel" || -e "/$etel" ) ]]; then
+ complete -F _complete_from_snap "$1"
+ return 124
+ fi
+ # fallback to the old -D
+ _completion_loader "$1"
+ }
+
+ complete -D -F _complete_from_snap_maybe
+fi
+
diff -Nru snapd-2.42.1+18.04/data/completion/bash/etelpmoc.sh snapd-2.45.1+18.04/data/completion/bash/etelpmoc.sh
--- snapd-2.42.1+18.04/data/completion/bash/etelpmoc.sh 1970-01-01 00:00:00.000000000 +0000
+++ snapd-2.45.1+18.04/data/completion/bash/etelpmoc.sh 2020-06-05 13:13:49.000000000 +0000
@@ -0,0 +1,225 @@
+# shellcheck shell=bash
+#
+# Copyright (C) 2017 Canonical Ltd
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 3 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+# etelpmoc is the reverse of complete: it de-serialises the tab completion
+# request into the appropriate environment variables expected by the tab
+# completion tools, performs whatever action is wanted, and serialises the
+# result. It accomplishes this by having functions override the builtin
+# completion commands.
+#
+# this always runs "inside", in the same environment you get when doing "snap
+# run --shell", and snap-exec is the one setting the first argument to the
+# completion script set in the snap. The rest of the arguments come through
+# from snap-run --command=complete
+
+_die() {
+ echo "$*" >&2
+ exit 1
+}
+
+if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then
+ _die "ERROR: this is meant to be run, not sourced."
+fi
+
+if [[ "${#@}" -lt 8 ]]; then
+ _die "USAGE: $0