summaryrefslogtreecommitdiff
diff options
authorMichael Vogt <mvo@ubuntu.com>2021-01-26 14:32:24 +0100
committerMichael Vogt <mvo@ubuntu.com>2021-01-26 14:32:24 +0100
commit7a714c089bcf344b32a5f174c47b6167b7404da1 (patch)
tree5f6af65ebab212f88280aa7be1fdfdad0d4fb7b2
parent7ec4e4028e085849c9b761c90ea96f29a26ed8ca (diff)
parent93decd662055ad69f090a783576e8ed7c75e4bc9 (diff)
Merge remote-tracking branch 'upstream/master' into snapshots/forget-conflictsnapshots/forget-conflict
-rw-r--r--cmd/libsnap-confine-private/classic-test.c32
-rw-r--r--cmd/libsnap-confine-private/classic.c33
-rw-r--r--cmd/libsnap-confine-private/classic.h4
-rw-r--r--cmd/snap-confine/mount-support.c20
-rw-r--r--cmd/snap-repair/export_test.go4
-rw-r--r--cmd/snap-repair/runner.go53
-rw-r--r--cmd/snap-repair/runner_test.go512
-rw-r--r--gadget/install/content.go9
-rw-r--r--gadget/install/install.go4
-rw-r--r--gadget/internal/mkfs.go59
-rw-r--r--gadget/internal/mkfs_test.go98
-rw-r--r--overlord/devicestate/devicemgr.go3
-rw-r--r--overlord/devicestate/devicestate_cloudinit_test.go26
-rw-r--r--packaging/ubuntu-16.04/changelog6
-rw-r--r--sysconfig/cloudinit.go14
-rw-r--r--sysconfig/cloudinit_test.go19
-rw-r--r--tests/lib/tools/tests.pkgs.dnf-yum.sh6
-rw-r--r--tests/main/ca-certs-for-snaps/task.yaml29
-rw-r--r--tests/main/dirs-not-shared-with-host/task.yaml5
-rw-r--r--tests/main/mount-ns/google.ubuntu-16.04-64/PER-SNAP-16.expected.txt1
-rw-r--r--tests/main/mount-ns/google.ubuntu-16.04-64/PER-SNAP-18.expected.txt1
-rw-r--r--tests/main/mount-ns/google.ubuntu-16.04-64/PER-USER-16.expected.txt1
-rw-r--r--tests/main/mount-ns/google.ubuntu-16.04-64/PER-USER-18.expected.txt1
-rw-r--r--tests/main/mount-ns/google.ubuntu-18.04-64/PER-SNAP-16.expected.txt1
-rw-r--r--tests/main/mount-ns/google.ubuntu-18.04-64/PER-SNAP-18.expected.txt1
-rw-r--r--tests/main/mount-ns/google.ubuntu-18.04-64/PER-USER-16.expected.txt1
-rw-r--r--tests/main/mount-ns/google.ubuntu-18.04-64/PER-USER-18.expected.txt1
27 files changed, 847 insertions, 97 deletions
diff --git a/cmd/libsnap-confine-private/classic-test.c b/cmd/libsnap-confine-private/classic-test.c
index 889b1f1fd6..5a7e098faa 100644
--- a/cmd/libsnap-confine-private/classic-test.c
+++ b/cmd/libsnap-confine-private/classic-test.c
@@ -176,6 +176,37 @@ static void test_is_on_custom_base(void)
g_assert_cmpint(sc_classify_distro(), ==, SC_DISTRO_CORE_OTHER);
}
+static const char *os_release_debian_like_valid = ""
+ "ID=my-fun-distro\n"
+ "ID_LIKE=debian\n";
+
+static const char *os_release_debian_like_quoted_valid = ""
+ "ID=my-fun-distro\n"
+ "ID_LIKE=\"debian\"\n";
+
+/* actual debian only sets ID=debian */
+static const char *os_release_actual_debian_valid = "ID=debian\n";
+
+static const char *os_release_invalid = "garbage\n";
+
+static void test_is_debian_like(void)
+{
+ mock_os_release(os_release_debian_like_valid);
+ g_assert_true(sc_is_debian_like());
+
+ mock_os_release(os_release_debian_like_quoted_valid);
+ g_assert_true(sc_is_debian_like());
+
+ mock_os_release(os_release_actual_debian_valid);
+ g_assert_true(sc_is_debian_like());
+
+ mock_os_release(os_release_fedora_ws);
+ g_assert_false(sc_is_debian_like());
+
+ mock_os_release(os_release_invalid);
+ g_assert_false(sc_is_debian_like());
+}
+
static void __attribute__((constructor)) init(void)
{
g_test_add_func("/classic/on-classic", test_is_on_classic);
@@ -187,4 +218,5 @@ static void __attribute__((constructor)) init(void)
g_test_add_func("/classic/on-fedora-base", test_is_on_fedora_base);
g_test_add_func("/classic/on-fedora-ws", test_is_on_fedora_ws);
g_test_add_func("/classic/on-custom-base", test_is_on_custom_base);
+ g_test_add_func("/classic/is-debian-like", test_is_debian_like);
}
diff --git a/cmd/libsnap-confine-private/classic.c b/cmd/libsnap-confine-private/classic.c
index cbfa1bafa1..720f47554c 100644
--- a/cmd/libsnap-confine-private/classic.c
+++ b/cmd/libsnap-confine-private/classic.c
@@ -1,6 +1,7 @@
#include "config.h"
#include "classic.h"
#include "../libsnap-confine-private/cleanup-funcs.h"
+#include "../libsnap-confine-private/infofile.h"
#include "../libsnap-confine-private/string-utils.h"
#include <stdbool.h>
@@ -56,3 +57,35 @@ sc_distro sc_classify_distro(void)
return SC_DISTRO_CLASSIC;
}
}
+
+bool sc_is_debian_like(void)
+{
+ FILE *f SC_CLEANUP(sc_cleanup_file) = fopen(os_release, "r");
+ if (f == NULL) {
+ return false;
+ }
+ const char *const id_keys_to_try[] = {
+ "ID", /* actual debian only sets ID */
+ "ID_LIKE", /* distros based on debian */
+ };
+ size_t id_keys_to_try_len =
+ sizeof id_keys_to_try / sizeof *id_keys_to_try;
+ for (size_t i = 0; i < id_keys_to_try_len; i++) {
+ if (fseek(f, 0L, SEEK_SET) == -1) {
+ return false;
+ }
+ char *id_val SC_CLEANUP(sc_cleanup_string) = NULL;
+ struct sc_error *err SC_CLEANUP(sc_cleanup_error) = NULL;
+ int rc =
+ sc_infofile_get_key(f, id_keys_to_try[i], &id_val, &err);
+ if (rc != 0) {
+ /* only if sc_infofile_get_key failed */
+ return false;
+ }
+ if (sc_streq(id_val, "\"debian\"")
+ || sc_streq(id_val, "debian")) {
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/cmd/libsnap-confine-private/classic.h b/cmd/libsnap-confine-private/classic.h
index ee317f4401..2db073a1ef 100644
--- a/cmd/libsnap-confine-private/classic.h
+++ b/cmd/libsnap-confine-private/classic.h
@@ -30,4 +30,8 @@ typedef enum sc_distro {
sc_distro sc_classify_distro(void);
+// Returns true if it's a Debian-like distro as determined via /etc/os-release
+// and the "ID_LIKE" key in there.
+bool sc_is_debian_like(void);
+
#endif
diff --git a/cmd/snap-confine/mount-support.c b/cmd/snap-confine/mount-support.c
index 7f4a80a51f..298db6bbc0 100644
--- a/cmd/snap-confine/mount-support.c
+++ b/cmd/snap-confine/mount-support.c
@@ -322,16 +322,32 @@ static void sc_bootstrap_mount_namespace(const struct sc_mount_config *config)
// - https://bugs.launchpad.net/snap-confine/+bug/1580018
// - https://bugzilla.opensuse.org/show_bug.cgi?id=1028568
const char *dirs_from_core[] = {
- "/etc/alternatives", "/etc/ssl", "/etc/nsswitch.conf",
- // Some specifc and privileged interfaces (e.g docker-support) give
+ "/etc/alternatives", "/etc/nsswitch.conf",
+ // Some specific and privileged interfaces (e.g docker-support) give
// access to apparmor_parser from the base snap which at a minimum
// needs to use matching configuration from the base snap instead
// of from the users host system.
"/etc/apparmor", "/etc/apparmor.d",
+ // Use ssl certs from the base by default unless
+ // using Debian/Ubuntu classic (see below)
+ "/etc/ssl",
NULL
};
+
for (const char **dirs = dirs_from_core; *dirs != NULL; dirs++) {
const char *dir = *dirs;
+
+ // Special case for ubuntu/debian based
+ // classic distros that use the core* snap:
+ // here we use the host /etc/ssl
+ // to support custom ca-cert setups
+ if (sc_streq(dir, "/etc/ssl") &&
+ config->distro == SC_DISTRO_CLASSIC &&
+ sc_is_debian_like() &&
+ sc_startswith(config->base_snap_name, "core")) {
+ continue;
+ }
+
if (access(dir, F_OK) != 0) {
continue;
}
diff --git a/cmd/snap-repair/export_test.go b/cmd/snap-repair/export_test.go
index 03b5e9fa43..0ce74903ba 100644
--- a/cmd/snap-repair/export_test.go
+++ b/cmd/snap-repair/export_test.go
@@ -86,6 +86,10 @@ func (run *Runner) BrandModel() (brand, model string) {
return run.state.Device.Brand, run.state.Device.Model
}
+func (run *Runner) BaseMode() (base, mode string) {
+ return run.state.Device.Base, run.state.Device.Mode
+}
+
func (run *Runner) SetStateModified(modified bool) {
run.stateModified = modified
}
diff --git a/cmd/snap-repair/runner.go b/cmd/snap-repair/runner.go
index c75c9f73af..ffa7566003 100644
--- a/cmd/snap-repair/runner.go
+++ b/cmd/snap-repair/runner.go
@@ -790,12 +790,18 @@ func findDevInfo16() (*deviceInfo, error) {
return nil, err
}
- // get the base snap as well
+ // get the base snap as well, on uc16 it won't be specified in the model
+ // assertion and instead will be empty, so in this case we replace it with
+ // "core"
+ base := modelAs.Base()
+ if modelAs.Base() == "" {
+ base = "core"
+ }
return &deviceInfo{
Brand: modelAs.BrandID(),
Model: modelAs.Model(),
- Base: modelAs.Base(),
+ Base: base,
// Mode is unset on uc16/uc18
}, nil
}
@@ -900,8 +906,47 @@ func (run *Runner) Applicable(headers map[string]interface{}) bool {
}
}
- // TODO:UC20: need to consider filtering by bases and modes in the assertion
- // here
+ // also filter by base snaps and modes
+ bases, err := stringList(headers, "bases")
+ if err != nil {
+ return false
+ }
+
+ if len(bases) != 0 && !strutil.ListContains(bases, run.state.Device.Base) {
+ return false
+ }
+
+ modes, err := stringList(headers, "modes")
+ if err != nil {
+ return false
+ }
+
+ // modes is slightly more nuanced, if the modes setting in the assertion
+ // header is unset, then it means it runs on all uc16/uc18 devices, but only
+ // during run mode on uc20 devices
+ if run.state.Device.Mode == "" {
+ // uc16 / uc18 device, the assertion is only applicable to us if modes
+ // is unset
+ if len(modes) != 0 {
+ return false
+ }
+ // else modes is unset and still applies to us
+ } else {
+ // uc20 device
+ switch {
+ case len(modes) == 0 && run.state.Device.Mode != "run":
+ // if modes is unset, then it is only applicable if we are
+ // in run mode
+ return false
+ case len(modes) != 0 && !strutil.ListContains(modes, run.state.Device.Mode):
+ // modes was specified and our current mode is not in the header, so
+ // not applicable to us
+ return false
+ }
+ // other cases are either that we are in run mode and modes is unset (in
+ // which case it is applicable) or modes is set to something with our
+ // current mode in the list (also in which case it is applicable)
+ }
return true
}
diff --git a/cmd/snap-repair/runner_test.go b/cmd/snap-repair/runner_test.go
index 467bbd7df0..069cdf8127 100644
--- a/cmd/snap-repair/runner_test.go
+++ b/cmd/snap-repair/runner_test.go
@@ -154,12 +154,36 @@ func (s *baseRunnerSuite) signSeqRepairs(c *C, repairs []string) []string {
return seq
}
-const freshStateJSON = `{"device":{"brand":"my-brand","model":"my-model","base":"","mode":""},"time-lower-bound":"2017-08-11T15:49:49Z"}`
+func checkStateJSON(c *C, file string, exp map[string]interface{}) {
+ stateFile := map[string]interface{}{}
+ b, err := ioutil.ReadFile(file)
+ c.Assert(err, IsNil)
+ err = json.Unmarshal(b, &stateFile)
+ c.Assert(err, IsNil)
+ c.Check(stateFile, DeepEquals, exp)
+}
func (s *baseRunnerSuite) freshState(c *C) {
+ // assume base: core18
+ s.freshStateWithBaseAndMode(c, "core18", "")
+}
+
+func (s *baseRunnerSuite) freshStateWithBaseAndMode(c *C, base, mode string) {
err := os.MkdirAll(dirs.SnapRepairDir, 0775)
c.Assert(err, IsNil)
- err = ioutil.WriteFile(dirs.SnapRepairStateFile, []byte(freshStateJSON), 0600)
+ stateJSON := map[string]interface{}{
+ "device": map[string]string{
+ "brand": "my-brand",
+ "model": "my-model",
+ "base": base,
+ "mode": mode,
+ },
+ "time-lower-bound": "2017-08-11T15:49:49Z",
+ }
+ b, err := json.Marshal(stateJSON)
+ c.Assert(err, IsNil)
+
+ err = ioutil.WriteFile(dirs.SnapRepairStateFile, b, 0600)
c.Assert(err, IsNil)
}
@@ -660,43 +684,102 @@ func (s *runnerSuite) TestSaveState(c *C) {
err = runner.SaveState()
c.Assert(err, IsNil)
- c.Check(dirs.SnapRepairStateFile, testutil.FileEquals, `{"device":{"brand":"my-brand","model":"my-model","base":"","mode":""},"sequences":{"canonical":[{"sequence":1,"revision":3,"status":0}]},"time-lower-bound":"2017-08-11T15:49:49Z"}`)
+ exp := map[string]interface{}{
+ "device": map[string]interface{}{
+ "brand": "my-brand",
+ "model": "my-model",
+ "base": "core18",
+ "mode": "",
+ },
+ "sequences": map[string]interface{}{
+ "canonical": []interface{}{
+ map[string]interface{}{
+ // all json numbers are floats
+ "sequence": 1.0,
+ "revision": 3.0,
+ "status": 0.0,
+ },
+ },
+ },
+ "time-lower-bound": "2017-08-11T15:49:49Z",
+ }
+
+ checkStateJSON(c, dirs.SnapRepairStateFile, exp)
+}
+
+type dev struct {
+ base string
+ mode string
}
func (s *runnerSuite) TestApplicable(c *C) {
- s.freshState(c)
- runner := repair.NewRunner()
- err := runner.LoadState()
- c.Assert(err, IsNil)
scenarios := []struct {
+ device *dev
headers map[string]interface{}
applicable bool
}{
- {nil, true},
- {map[string]interface{}{"series": []interface{}{"18"}}, false},
- {map[string]interface{}{"series": []interface{}{"18", "16"}}, true},
- {map[string]interface{}{"series": "18"}, false},
- {map[string]interface{}{"series": []interface{}{18}}, false},
- {map[string]interface{}{"architectures": []interface{}{arch.DpkgArchitecture()}}, true},
- {map[string]interface{}{"architectures": []interface{}{"other-arch"}}, false},
- {map[string]interface{}{"architectures": []interface{}{"other-arch", arch.DpkgArchitecture()}}, true},
- {map[string]interface{}{"architectures": arch.DpkgArchitecture()}, false},
- {map[string]interface{}{"models": []interface{}{"my-brand/my-model"}}, true},
- {map[string]interface{}{"models": []interface{}{"other-brand/other-model"}}, false},
- {map[string]interface{}{"models": []interface{}{"other-brand/other-model", "my-brand/my-model"}}, true},
- {map[string]interface{}{"models": "my-brand/my-model"}, false},
+ {nil, nil, true},
+ {nil, map[string]interface{}{"series": []interface{}{"18"}}, false},
+ {nil, map[string]interface{}{"series": []interface{}{"18", "16"}}, true},
+ {nil, map[string]interface{}{"series": "18"}, false},
+ {nil, map[string]interface{}{"series": []interface{}{18}}, false},
+ {nil, map[string]interface{}{"architectures": []interface{}{arch.DpkgArchitecture()}}, true},
+ {nil, map[string]interface{}{"architectures": []interface{}{"other-arch"}}, false},
+ {nil, map[string]interface{}{"architectures": []interface{}{"other-arch", arch.DpkgArchitecture()}}, true},
+ {nil, map[string]interface{}{"architectures": arch.DpkgArchitecture()}, false},
+ {nil, map[string]interface{}{"models": []interface{}{"my-brand/my-model"}}, true},
+ {nil, map[string]interface{}{"models": []interface{}{"other-brand/other-model"}}, false},
+ {nil, map[string]interface{}{"models": []interface{}{"other-brand/other-model", "my-brand/my-model"}}, true},
+ {nil, map[string]interface{}{"models": "my-brand/my-model"}, false},
+ // modes for uc16 / uc18 devices
+ {nil, map[string]interface{}{"modes": []interface{}{}}, true},
+ {nil, map[string]interface{}{"modes": []interface{}{"run"}}, false},
+ {nil, map[string]interface{}{"modes": []interface{}{"recover"}}, false},
+ {nil, map[string]interface{}{"modes": []interface{}{"run", "recover"}}, false},
+ // run mode for uc20 devices
+ {&dev{mode: "run"}, map[string]interface{}{"modes": []interface{}{}}, true},
+ {&dev{mode: "run"}, map[string]interface{}{"modes": []interface{}{"run"}}, true},
+ {&dev{mode: "run"}, map[string]interface{}{"modes": []interface{}{"recover"}}, false},
+ {&dev{mode: "run"}, map[string]interface{}{"modes": []interface{}{"run", "recover"}}, true},
+ // recover mode for uc20 devices
+ {&dev{mode: "recover"}, map[string]interface{}{"modes": []interface{}{}}, false},
+ {&dev{mode: "recover"}, map[string]interface{}{"modes": []interface{}{"run"}}, false},
+ {&dev{mode: "recover"}, map[string]interface{}{"modes": []interface{}{"recover"}}, true},
+ {&dev{mode: "recover"}, map[string]interface{}{"modes": []interface{}{"run", "recover"}}, true},
+ // bases for uc16 devices
+ {&dev{base: "core"}, map[string]interface{}{"bases": []interface{}{"core"}}, true},
+ {&dev{base: "core"}, map[string]interface{}{"bases": []interface{}{"core18"}}, false},
+ {&dev{base: "core"}, map[string]interface{}{"bases": []interface{}{"core", "core18"}}, true},
+ // bases for uc18 devices
+ {&dev{base: "core18"}, map[string]interface{}{"bases": []interface{}{"core18"}}, true},
+ {&dev{base: "core18"}, map[string]interface{}{"bases": []interface{}{"core"}}, false},
+ {&dev{base: "core18"}, map[string]interface{}{"bases": []interface{}{"core", "core18"}}, true},
+ // bases for uc20 devices
+ {&dev{base: "core20"}, map[string]interface{}{"bases": []interface{}{"core20"}}, true},
+ {&dev{base: "core20"}, map[string]interface{}{"bases": []interface{}{"core"}}, false},
+ {&dev{base: "core20"}, map[string]interface{}{"bases": []interface{}{"core", "core20"}}, true},
// model prefix matches
- {map[string]interface{}{"models": []interface{}{"my-brand/*"}}, true},
- {map[string]interface{}{"models": []interface{}{"my-brand/my-mod*"}}, true},
- {map[string]interface{}{"models": []interface{}{"my-brand/xxx*"}}, false},
- {map[string]interface{}{"models": []interface{}{"my-brand/my-mod*", "my-brand/xxx*"}}, true},
- {map[string]interface{}{"models": []interface{}{"my*"}}, false},
- {map[string]interface{}{"disabled": "true"}, false},
- {map[string]interface{}{"disabled": "false"}, true},
+ {nil, map[string]interface{}{"models": []interface{}{"my-brand/*"}}, true},
+ {nil, map[string]interface{}{"models": []interface{}{"my-brand/my-mod*"}}, true},
+ {nil, map[string]interface{}{"models": []interface{}{"my-brand/xxx*"}}, false},
+ {nil, map[string]interface{}{"models": []interface{}{"my-brand/my-mod*", "my-brand/xxx*"}}, true},
+ {nil, map[string]interface{}{"models": []interface{}{"my*"}}, false},
+ {nil, map[string]interface{}{"disabled": "true"}, false},
+ {nil, map[string]interface{}{"disabled": "false"}, true},
}
for _, scen := range scenarios {
+ if scen.device == nil {
+ s.freshState(c)
+ } else {
+ s.freshStateWithBaseAndMode(c, scen.device.base, scen.device.mode)
+ }
+
+ runner := repair.NewRunner()
+ err := runner.LoadState()
+ c.Assert(err, IsNil)
+
ok := runner.Applicable(scen.headers)
c.Check(ok, Equals, scen.applicable, Commentf("%v", scen))
}
@@ -1116,17 +1199,21 @@ func (s *runnerSuite) TestNextNotFound(c *C) {
runner.BaseURL = mustParseURL(mockServer.URL)
runner.LoadState()
- // sanity
- c.Check(dirs.SnapRepairStateFile, testutil.FileEquals, freshStateJSON)
-
_, err := runner.Next("canonical")
c.Assert(err, Equals, repair.ErrRepairNotFound)
// we saved new time lower bound
- t1 := runner.TimeLowerBound()
- expected := strings.Replace(freshStateJSON, "2017-08-11T15:49:49Z", t1.Format(time.RFC3339), 1)
- c.Check(expected, Not(Equals), freshStateJSON)
- c.Check(dirs.SnapRepairStateFile, testutil.FileEquals, expected)
+ stateFileExp := map[string]interface{}{
+ "device": map[string]interface{}{
+ "brand": "my-brand",
+ "model": "my-model",
+ "base": "core18",
+ "mode": "",
+ },
+ "time-lower-bound": runner.TimeLowerBound().Format(time.RFC3339),
+ }
+
+ checkStateJSON(c, dirs.SnapRepairStateFile, stateFileExp)
}
func (s *runnerSuite) TestNextSaveStateError(c *C) {
@@ -1346,6 +1433,348 @@ AXNpZw==`}
c.Check(filepath.Join(dirs.SnapRepairRunDir, "canonical", "1", "r0.script"), testutil.FileEquals, "#!/bin/sh\nexit 0\n")
}
+func (s *runnerSuite) TestRepairModesAndBases(c *C) {
+ repairTempl := `type: repair
+authority-id: canonical
+brand-id: canonical
+repair-id: 1
+summary: uc20 recovery repair
+timestamp: 2017-07-03T12:00:00Z
+body-length: 17
+sign-key-sha3-384: KPIl7M4vQ9d4AUjkoU41TGAwtOMLc_bWUCeW8AvdRWD4_xcP60Oo4ABsFNo6BtXj
+%[1]s
+#!/bin/sh
+exit 0
+
+
+AXNpZw==
+ `
+
+ r1 := sysdb.InjectTrusted(s.storeSigning.Trusted)
+ defer r1()
+ r2 := repair.MockTrustedRepairRootKeys([]*asserts.AccountKey{s.repairRootAcctKey})
+ defer r2()
+
+ tt := []struct {
+ device *dev
+ modes []string
+ bases []string
+ shouldRun bool
+ comment string
+ }{
+ // uc20 recover mode assertion
+ {
+ &dev{"core20", "recover"},
+ []string{"recover"},
+ []string{"core20"},
+ true,
+ "uc20 recover mode w/ uc20 recover mode assertion",
+ },
+ {
+ &dev{"core20", "run"},
+ []string{"recover"},
+ []string{"core20"},
+ false,
+ "uc20 run mode w/ uc20 recover mode assertion",
+ },
+ {
+ &dev{base: "core"},
+ []string{"recover"},
+ []string{"core20"},
+ false,
+ "uc16 w/ uc20 recover mode assertion",
+ },
+ {
+ &dev{base: "core18"},
+ []string{"recover"},
+ []string{"core20"},
+ false,
+ "uc18 w/ uc20 recover mode assertion",
+ },
+
+ // uc20 run mode assertion
+ {
+ &dev{"core20", "recover"},
+ []string{"run"},
+ []string{"core20"},
+ false,
+ "uc20 recover mode w/ uc20 run mode assertion",
+ },
+ {
+ &dev{"core20", "run"},
+ []string{"run"},
+ []string{"core20"},
+ true,
+ "uc20 run mode w/ uc20 run mode assertion",
+ },
+ {
+ &dev{base: "core"},
+ []string{"run"},
+ []string{"core20"},
+ false,
+ "uc16 w/ uc20 run mode assertion",
+ },
+ {
+ &dev{base: "core18"},
+ []string{"run"},
+ []string{"core20"},
+ false,
+ "uc18 w/ uc20 run mode assertion",
+ },
+
+ // all uc20 modes assertion
+ {
+ &dev{"core20", "recover"},
+ []string{"run", "recover"},
+ []string{"core20"},
+ true,
+ "uc20 recover mode w/ all uc20 modes assertion",
+ },
+ {
+ &dev{"core20", "run"},
+ []string{"run", "recover"},
+ []string{"core20"},
+ true,
+ "uc20 run mode w/ all uc20 modes assertion",
+ },
+ {
+ &dev{base: "core"},
+ []string{"run", "recover"},
+ []string{"core20"},
+ false,
+ "uc16 w/ all uc20 modes assertion",
+ },
+ {
+ &dev{base: "core18"},
+ []string{"run", "recover"},
+ []string{"core20"},
+ false,
+ "uc18 w/ all uc20 modes assertion",
+ },
+
+ // alternate uc20 run mode only assertion
+ {
+ &dev{"core20", "recover"},
+ []string{},
+ []string{"core20"},
+ false,
+ "uc20 recover mode w/ alternate uc20 run mode assertion",
+ },
+ {
+ &dev{"core20", "run"},
+ []string{},
+ []string{"core20"},
+ true,
+ "uc20 run mode w/ alternate uc20 run mode assertion",
+ },
+ {
+ &dev{base: "core"},
+ []string{},
+ []string{"core20"},
+ false,
+ "uc16 w/ alternate uc20 run mode assertion",
+ },
+ {
+ &dev{base: "core18"},
+ []string{},
+ []string{"core20"},
+ false,
+ "uc18 w/ alternate uc20 run mode assertion",
+ },
+ {
+ &dev{base: "core"},
+ []string{"run"},
+ []string{},
+ false,
+ "uc16 w/ uc20 run mode assertion",
+ },
+ {
+ &dev{base: "core18"},
+ []string{"run"},
+ []string{},
+ false,
+ "uc16 w/ uc20 run mode assertion",
+ },
+
+ // all except uc20 recover mode assertion
+ {
+ &dev{"core20", "recover"},
+ []string{},
+ []string{},
+ false,
+ "uc20 recover mode w/ all except uc20 recover mode assertion",
+ },
+ {
+ &dev{"core20", "run"},
+ []string{},
+ []string{},
+ true,
+ "uc20 run mode w/ all except uc20 recover mode assertion",
+ },
+ {
+ &dev{base: "core"},
+ []string{},
+ []string{},
+ true,
+ "uc16 w/ all except uc20 recover mode assertion",
+ },
+ {
+ &dev{base: "core18"},
+ []string{},
+ []string{},
+ true,
+ "uc18 w/ all except uc20 recover mode assertion",
+ },
+
+ // uc16 and uc18 assertion
+ {
+ &dev{"core20", "recover"},
+ []string{},
+ []string{"core", "core18"},
+ false,
+ "uc20 recover mode w/ uc16 and uc18 assertion",
+ },
+ {
+ &dev{"core20", "run"},
+ []string{},
+ []string{"core", "core18"},
+ false,
+ "uc20 run mode w/ uc16 and uc18 assertion",
+ },
+ {
+ &dev{base: "core"},
+ []string{},
+ []string{"core", "core18"},
+ true,
+ "uc16 w/ uc16 and uc18 assertion",
+ },
+ {
+ &dev{base: "core18"},
+ []string{},
+ []string{"core", "core18"},
+ true,
+ "uc18 w/ uc16 and uc18 assertion",
+ },
+
+ // just uc16 assertion
+ {
+ &dev{"core20", "recover"},
+ []string{},
+ []string{"core"},
+ false,
+ "uc20 recover mode w/ just uc16 assertion",
+ },
+ {
+ &dev{"core20", "run"},
+ []string{},
+ []string{"core"},
+ false,
+ "uc20 run mode w/ just uc16 assertion",
+ },
+ {
+ &dev{base: "core"},
+ []string{},
+ []string{"core"},
+ true,
+ "uc16 w/ just uc16 assertion",
+ },
+ {
+ &dev{base: "core18"},
+ []string{},
+ []string{"core"},
+ false,
+ "uc18 w/ just uc16 assertion",
+ },
+
+ // just uc18 assertion
+ {
+ &dev{"core20", "recover"},
+ []string{},
+ []string{"core18"},
+ false,
+ "uc20 recover mode w/ just uc18 assertion",
+ },
+ {
+ &dev{"core20", "run"},
+ []string{},
+ []string{"core18"},
+ false,
+ "uc20 run mode w/ just uc18 assertion",
+ },
+ {
+ &dev{base: "core"},
+ []string{},
+ []string{"core18"},
+ false,
+ "uc16 w/ just uc18 assertion",
+ },
+ {
+ &dev{base: "core18"},
+ []string{},
+ []string{"core18"},
+ true,
+ "uc18 w/ just uc18 assertion",
+ },
+ }
+ for _, t := range tt {
+ comment := Commentf(t.comment)
+ cleanups := []func(){}
+
+ // generate the assertion with the bases and modes
+ basesStr := ""
+ if len(t.bases) != 0 {
+ basesStr = "bases:\n"
+ for _, base := range t.bases {
+ basesStr += " - " + base + "\n"
+ }
+ }
+ modesStr := ""
+ if len(t.modes) != 0 {
+ modesStr = "modes:\n"
+ for _, mode := range t.modes {
+ modesStr += " - " + mode + "\n"
+ }
+ }
+
+ seqRepairs := s.signSeqRepairs(c, []string{fmt.Sprintf(repairTempl, basesStr+modesStr)})
+
+ mockServer := makeMockServer(c, &seqRepairs, false)
+ cleanups = append(cleanups, func() { mockServer.Close() })
+
+ if t.device == nil {
+ s.freshState(c)
+ } else {
+ s.freshStateWithBaseAndMode(c, t.device.base, t.device.mode)
+ }
+
+ runner := repair.NewRunner()
+ runner.BaseURL = mustParseURL(mockServer.URL)
+ runner.LoadState()
+
+ script := filepath.Join(dirs.SnapRepairRunDir, "canonical", "1", "r0.script")
+
+ rpr, err := runner.Next("canonical")
+ if t.shouldRun {
+ c.Assert(err, IsNil, comment)
+
+ // run the repair and make sure the script is there
+ err = rpr.Run()
+ c.Assert(err, IsNil, comment)
+ c.Check(script, testutil.FileEquals, "#!/bin/sh\nexit 0\n", comment)
+
+ // remove the script for the next iteration
+ cleanups = append(cleanups, func() { c.Assert(os.RemoveAll(dirs.SnapRepairRunDir), IsNil) })
+ } else {
+ c.Assert(err, Equals, repair.ErrRepairNotFound, comment)
+ c.Check(script, testutil.FileAbsent, comment)
+ }
+
+ for _, r := range cleanups {
+ r()
+ }
+ }
+}
+
func makeMockRepair(script string) string {
return fmt.Sprintf(`type: repair
authority-id: canonical
@@ -1661,6 +2090,11 @@ type shared1620RunnerSuite struct {
baseRunnerSuite
writeSeedAssert func(c *C, fname string, a asserts.Assertion)
+
+ // this is so we can check device details that will be different in the
+ // 20 version of tests from the 16 version of tests
+ expBase string
+ expMode string
}
func (s *shared1620RunnerSuite) TestTLSTime(c *C) {
@@ -1697,6 +2131,10 @@ func (s *shared1620RunnerSuite) TestLoadStateInitState(c *C) {
c.Check(brand, Equals, "my-brand")
c.Check(model, Equals, "my-model-2")
+ base, mode := runner.BaseMode()
+ c.Check(base, Equals, s.expBase)
+ c.Check(mode, Equals, s.expMode)
+
c.Check(runner.TimeLowerBound().Equal(s.seedTime), Equals, true)
}
@@ -1709,6 +2147,9 @@ var _ = Suite(&runner16Suite{})
func (s *runner16Suite) SetUpTest(c *C) {
s.shared1620RunnerSuite.SetUpTest(c)
+ s.shared1620RunnerSuite.expBase = "core"
+ s.shared1620RunnerSuite.expMode = ""
+
s.seedAssertsDir = filepath.Join(dirs.SnapSeedDir, "assertions")
// dummy seed yaml
@@ -1792,6 +2233,9 @@ base=core20_1.snap
func (s *runner20Suite) SetUpTest(c *C) {
s.shared1620RunnerSuite.SetUpTest(c)
+ s.shared1620RunnerSuite.expBase = "core20"
+ s.shared1620RunnerSuite.expMode = "run"
+
s.seedAssertsDir = filepath.Join(dirs.SnapSeedDir, "/systems/20201212/assertions")
err := os.MkdirAll(s.seedAssertsDir, 0755)
c.Assert(err, IsNil)
diff --git a/gadget/install/content.go b/gadget/install/content.go
index 535bd4dddd..74095f3ff6 100644
--- a/gadget/install/content.go
+++ b/gadget/install/content.go
@@ -28,6 +28,7 @@ import (
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/gadget"
"github.com/snapcore/snapd/gadget/internal"
+ "github.com/snapcore/snapd/gadget/quantity"
"github.com/snapcore/snapd/logger"
)
@@ -38,11 +39,13 @@ func init() {
}
// makeFilesystem creates a filesystem on the on-disk structure, according
-// to the filesystem type defined in the gadget.
-func makeFilesystem(ds *gadget.OnDiskStructure) error {
+// to the filesystem type defined in the gadget. If sectorSize is specified,
+// that sector size is used when creating the filesystem, otherwise if it is
+// zero, automatic values are used instead.
+func makeFilesystem(ds *gadget.OnDiskStructure, sectorSize quantity.Size) error {
if ds.HasFilesystem() {
logger.Debugf("create %s filesystem on %s with label %q", ds.VolumeStructure.Filesystem, ds.Node, ds.VolumeStructure.Label)
- if err := internal.Mkfs(ds.VolumeStructure.Filesystem, ds.Node, ds.VolumeStructure.Label, ds.Size); err != nil {
+ if err := internal.Mkfs(ds.VolumeStructure.Filesystem, ds.Node, ds.VolumeStructure.Label, ds.Size, sectorSize); err != nil {
return err
}
if err := udevTrigger(ds.Node); err != nil {
diff --git a/gadget/install/install.go b/gadget/install/install.go
index 088fd83e32..16b60a2599 100644
--- a/gadget/install/install.go
+++ b/gadget/install/install.go
@@ -160,8 +160,8 @@ func Run(model gadget.Model, gadgetRoot, device string, options Options, observe
logger.Noticef("encrypted device %v", part.Node)
}
- if err := makeFilesystem(&part); err != nil {
- return nil, err
+ if err := makeFilesystem(&part, lv.SectorSize); err != nil {
+ return nil, fmt.Errorf("cannot make filesystem for partition %s: %v", part.Role, err)
}
if err := writeContent(&part, gadgetRoot, observer); err != nil {
diff --git a/gadget/internal/mkfs.go b/gadget/internal/mkfs.go
index 867784d4fb..c6f3d6544a 100644
--- a/gadget/internal/mkfs.go
+++ b/gadget/internal/mkfs.go
@@ -30,7 +30,7 @@ import (
"github.com/snapcore/snapd/osutil"
)
-type MkfsFunc func(imgFile, label, contentsRootDir string, deviceSize quantity.Size) error
+type MkfsFunc func(imgFile, label, contentsRootDir string, deviceSize, sectorSize quantity.Size) error
var (
mkfsHandlers = map[string]MkfsFunc{
@@ -40,42 +40,54 @@ var (
)
// Mkfs creates a filesystem of given type and provided label in the device or
-// file. The device size provides hints for additional tuning of the created
-// filesystem.
-func Mkfs(typ, img, label string, deviceSize quantity.Size) error {
- return MkfsWithContent(typ, img, label, "", deviceSize)
+// file. The device size and sector size provides hints for additional tuning of
+// the created filesystem.
+func Mkfs(typ, img, label string, deviceSize, sectorSize quantity.Size) error {
+ return MkfsWithContent(typ, img, label, "", deviceSize, sectorSize)
}
-// Mkfs creates a filesystem of given type and provided label in the device or
-// file. The filesystem is populated with contents of contentRootDir. The device
-// size provides hints for additional tuning of the created filesystem.
-func MkfsWithContent(typ, img, label, contentRootDir string, deviceSize quantity.Size) error {
+// MkfsWithContent creates a filesystem of given type and provided label in the
+// device or file. The filesystem is populated with contents of contentRootDir.
+// The device size provides hints for additional tuning of the created
+// filesystem.
+func MkfsWithContent(typ, img, label, contentRootDir string, deviceSize, sectorSize quantity.Size) error {
h, ok := mkfsHandlers[typ]
if !ok {
return fmt.Errorf("cannot create unsupported filesystem %q", typ)
}
- return h(img, label, contentRootDir, deviceSize)
+ return h(img, label, contentRootDir, deviceSize, sectorSize)
}
// mkfsExt4 creates an EXT4 filesystem in given image file, with an optional
// filesystem label, and populates it with the contents of provided root
// directory.
-func mkfsExt4(img, label, contentsRootDir string, deviceSize quantity.Size) error {
+func mkfsExt4(img, label, contentsRootDir string, deviceSize, sectorSize quantity.Size) error {
// Originally taken from ubuntu-image
// Switched to use mkfs defaults for https://bugs.launchpad.net/snappy/+bug/1878374
// For caveats/requirements in case we need support for older systems:
// https://github.com/snapcore/snapd/pull/6997#discussion_r293967140
mkfsArgs := []string{"mkfs.ext4"}
+
const size32MiB = 32 * quantity.SizeMiB
if deviceSize != 0 && deviceSize <= size32MiB {
- // With the default of 4096 bytes, the minimal journal size is
- // 4M, meaning we loose a lot of usable space. Try to follow the
+ // With the default block size of 4096 bytes, the minimal journal size
+ // is 4M, meaning we loose a lot of usable space. Try to follow the
// e2fsprogs upstream and use a 1k block size for smaller
// filesystems, note that this may cause issues like
// https://bugs.launchpad.net/ubuntu/+source/lvm2/+bug/1817097
// if one migrates the filesystem to a device with a different
// block size
- mkfsArgs = append(mkfsArgs, "-b", "1024")
+
+ // though note if the sector size was specified (i.e. non-zero) and
+ // larger than 1K, then we need to use that, since you can't create
+ // a filesystem with a block-size smaller than the sector-size
+ // see e2fsprogs source code:
+ // https://github.com/tytso/e2fsprogs/blob/0d47f5ab05177c1861f16bb3644a47018e6be1d0/misc/mke2fs.c#L2151-L2156
+ defaultSectorSize := 1 * quantity.SizeKiB
+ if sectorSize > 1024 {
+ defaultSectorSize = sectorSize
+ }
+ mkfsArgs = append(mkfsArgs, "-b", defaultSectorSize.String())
}
if contentsRootDir != "" {
// mkfs.ext4 can populate the filesystem with contents of given
@@ -106,11 +118,22 @@ func mkfsExt4(img, label, contentsRootDir string, deviceSize quantity.Size) erro
// mkfsVfat creates a VFAT filesystem in given image file, with an optional
// filesystem label, and populates it with the contents of provided root
// directory.
-func mkfsVfat(img, label, contentsRootDir string, deviceSize quantity.Size) error {
- // taken from ubuntu-image
+func mkfsVfat(img, label, contentsRootDir string, deviceSize, sectorSize quantity.Size) error {
+ // 512B logical sector size by default, unless the specified sector size is
+ // larger than 512, in which case use the sector size
+ // mkfs.vfat will automatically increase the block size to the internal
+ // sector size of the disk if the specified block size is too small, but
+ // be paranoid and always set the block size to that of the sector size if
+ // we know the sector size is larger than the default 512 (originally from
+ // ubuntu-image). see dosfstools:
+ // https://github.com/dosfstools/dosfstools/blob/e579a7df89bb3a6df08847d45c70c8ebfabca7d2/src/mkfs.fat.c#L1892-L1898
+ defaultSectorSize := quantity.Size(512)
+ if sectorSize > defaultSectorSize {
+ defaultSectorSize = sectorSize
+ }
mkfsArgs := []string{
- // 512B logical sector size
- "-S", "512",
+ // options taken from ubuntu-image, except the sector size
+ "-S", defaultSectorSize.String(),
// 1 sector per cluster
"-s", "1",
// 32b FAT size
diff --git a/gadget/internal/mkfs_test.go b/gadget/internal/mkfs_test.go
index 99da8e6e99..8c5dac11c9 100644
--- a/gadget/internal/mkfs_test.go
+++ b/gadget/internal/mkfs_test.go
@@ -63,7 +63,7 @@ func (m *mkfsSuite) TestMkfsExt4Happy(c *C) {
cmd := testutil.MockCommand(c, "fakeroot", "")
defer cmd.Restore()
- err := internal.MkfsWithContent("ext4", "foo.img", "my-label", "contents", 0)
+ err := internal.MkfsWithContent("ext4", "foo.img", "my-label", "contents", 0, 0)
c.Assert(err, IsNil)
c.Check(cmd.Calls(), DeepEquals, [][]string{
{
@@ -78,7 +78,7 @@ func (m *mkfsSuite) TestMkfsExt4Happy(c *C) {
cmd.ForgetCalls()
// empty label
- err = internal.MkfsWithContent("ext4", "foo.img", "", "contents", 0)
+ err = internal.MkfsWithContent("ext4", "foo.img", "", "contents", 0, 0)
c.Assert(err, IsNil)
c.Check(cmd.Calls(), DeepEquals, [][]string{
{
@@ -92,7 +92,7 @@ func (m *mkfsSuite) TestMkfsExt4Happy(c *C) {
cmd.ForgetCalls()
// no content
- err = internal.Mkfs("ext4", "foo.img", "my-label", 0)
+ err = internal.Mkfs("ext4", "foo.img", "my-label", 0, 0)
c.Assert(err, IsNil)
c.Check(cmd.Calls(), DeepEquals, [][]string{
{
@@ -109,7 +109,7 @@ func (m *mkfsSuite) TestMkfsExt4WithSize(c *C) {
cmd := testutil.MockCommand(c, "fakeroot", "")
defer cmd.Restore()
- err := internal.MkfsWithContent("ext4", "foo.img", "my-label", "contents", 250*1024*1024)
+ err := internal.MkfsWithContent("ext4", "foo.img", "my-label", "contents", 250*1024*1024, 0)
c.Assert(err, IsNil)
c.Check(cmd.Calls(), DeepEquals, [][]string{
{
@@ -124,7 +124,7 @@ func (m *mkfsSuite) TestMkfsExt4WithSize(c *C) {
cmd.ForgetCalls()
// empty label
- err = internal.MkfsWithContent("ext4", "foo.img", "", "contents", 32*1024*1024)
+ err = internal.MkfsWithContent("ext4", "foo.img", "", "contents", 32*1024*1024, 0)
c.Assert(err, IsNil)
c.Check(cmd.Calls(), DeepEquals, [][]string{
{
@@ -137,13 +137,44 @@ func (m *mkfsSuite) TestMkfsExt4WithSize(c *C) {
})
cmd.ForgetCalls()
+
+ // with sector size of 512
+ err = internal.MkfsWithContent("ext4", "foo.img", "", "contents", 32*1024*1024, 512)
+ c.Assert(err, IsNil)
+ c.Check(cmd.Calls(), DeepEquals, [][]string{
+ {
+ "fakeroot",
+ "mkfs.ext4",
+ "-b", "1024",
+ "-d", "contents",
+ "foo.img",
+ },
+ })
+
+ cmd.ForgetCalls()
+
+ // with sector size of 4096
+ err = internal.MkfsWithContent("ext4", "foo.img", "", "contents", 32*1024*1024, 4096)
+ c.Assert(err, IsNil)
+ c.Check(cmd.Calls(), DeepEquals, [][]string{
+ {
+ "fakeroot",
+ "mkfs.ext4",
+ "-b", "4096",
+ "-d", "contents",
+ "foo.img",
+ },
+ })
+
+ cmd.ForgetCalls()
+
}
func (m *mkfsSuite) TestMkfsExt4Error(c *C) {
cmd := testutil.MockCommand(c, "fakeroot", "echo 'command failed'; exit 1")
defer cmd.Restore()
- err := internal.MkfsWithContent("ext4", "foo.img", "my-label", "contents", 0)
+ err := internal.MkfsWithContent("ext4", "foo.img", "my-label", "contents", 0, 0)
c.Assert(err, ErrorMatches, "command failed")
}
@@ -154,7 +185,7 @@ func (m *mkfsSuite) TestMkfsVfatHappySimple(c *C) {
cmd := testutil.MockCommand(c, "mkfs.vfat", "")
defer cmd.Restore()
- err := internal.MkfsWithContent("vfat", "foo.img", "my-label", d, 0)
+ err := internal.MkfsWithContent("vfat", "foo.img", "my-label", d, 0, 0)
c.Assert(err, IsNil)
c.Check(cmd.Calls(), DeepEquals, [][]string{
{
@@ -170,7 +201,7 @@ func (m *mkfsSuite) TestMkfsVfatHappySimple(c *C) {
cmd.ForgetCalls()
// empty label
- err = internal.MkfsWithContent("vfat", "foo.img", "", d, 0)
+ err = internal.MkfsWithContent("vfat", "foo.img", "", d, 0, 0)
c.Assert(err, IsNil)
c.Check(cmd.Calls(), DeepEquals, [][]string{
{
@@ -185,7 +216,7 @@ func (m *mkfsSuite) TestMkfsVfatHappySimple(c *C) {
cmd.ForgetCalls()
// no content
- err = internal.Mkfs("vfat", "foo.img", "my-label", 0)
+ err = internal.Mkfs("vfat", "foo.img", "my-label", 0, 0)
c.Assert(err, IsNil)
c.Check(cmd.Calls(), DeepEquals, [][]string{
{
@@ -205,7 +236,23 @@ func (m *mkfsSuite) TestMkfsVfatWithSize(c *C) {
cmd := testutil.MockCommand(c, "mkfs.vfat", "")
defer cmd.Restore()
- err := internal.MkfsWithContent("vfat", "foo.img", "my-label", d, 32*1024*1024)
+ err := internal.MkfsWithContent("vfat", "foo.img", "my-label", d, 32*1024*1024, 0)
+ c.Assert(err, IsNil)
+ c.Check(cmd.Calls(), DeepEquals, [][]string{
+ {
+ "mkfs.vfat",
+ "-S", "512",
+ "-s", "1",
+ "-F", "32",
+ "-n", "my-label",
+ "foo.img",
+ },
+ })
+
+ cmd.ForgetCalls()
+
+ // with sector size of 512
+ err = internal.MkfsWithContent("vfat", "foo.img", "my-label", d, 32*1024*1024, 512)
c.Assert(err, IsNil)
c.Check(cmd.Calls(), DeepEquals, [][]string{
{
@@ -217,6 +264,23 @@ func (m *mkfsSuite) TestMkfsVfatWithSize(c *C) {
"foo.img",
},
})
+
+ cmd.ForgetCalls()
+
+ // with sector size of 4096
+ err = internal.MkfsWithContent("vfat", "foo.img", "my-label", d, 32*1024*1024, 4096)
+ c.Assert(err, IsNil)
+ c.Check(cmd.Calls(), DeepEquals, [][]string{
+ {
+ "mkfs.vfat",
+ "-S", "4096",
+ "-s", "1",
+ "-F", "32",
+ "-n", "my-label",
+ "foo.img",
+ },
+ })
+
}
func (m *mkfsSuite) TestMkfsVfatHappyContents(c *C) {
@@ -230,7 +294,7 @@ func (m *mkfsSuite) TestMkfsVfatHappyContents(c *C) {
cmdMcopy := testutil.MockCommand(c, "mcopy", "")
defer cmdMcopy.Restore()
- err := internal.MkfsWithContent("vfat", "foo.img", "my-label", d, 0)
+ err := internal.MkfsWithContent("vfat", "foo.img", "my-label", d, 0, 0)
c.Assert(err, IsNil)
c.Assert(cmdMkfs.Calls(), HasLen, 1)
@@ -245,7 +309,7 @@ func (m *mkfsSuite) TestMkfsVfatErrorSimpleFail(c *C) {
cmd := testutil.MockCommand(c, "mkfs.vfat", "echo 'failed'; false")
defer cmd.Restore()
- err := internal.MkfsWithContent("vfat", "foo.img", "my-label", d, 0)
+ err := internal.MkfsWithContent("vfat", "foo.img", "my-label", d, 0, 0)
c.Assert(err, ErrorMatches, "failed")
}
@@ -253,7 +317,7 @@ func (m *mkfsSuite) TestMkfsVfatErrorUnreadableDir(c *C) {
cmd := testutil.MockCommand(c, "mkfs.vfat", "")
defer cmd.Restore()
- err := internal.MkfsWithContent("vfat", "foo.img", "my-label", "dir-does-not-exist", 0)
+ err := internal.MkfsWithContent("vfat", "foo.img", "my-label", "dir-does-not-exist", 0, 0)
c.Assert(err, ErrorMatches, "cannot list directory contents: .* no such file or directory")
c.Assert(cmd.Calls(), HasLen, 1)
}
@@ -268,7 +332,7 @@ func (m *mkfsSuite) TestMkfsVfatErrorInMcopy(c *C) {
cmdMcopy := testutil.MockCommand(c, "mcopy", "echo 'hard fail'; exit 1")
defer cmdMcopy.Restore()
- err := internal.MkfsWithContent("vfat", "foo.img", "my-label", d, 0)
+ err := internal.MkfsWithContent("vfat", "foo.img", "my-label", d, 0, 0)
c.Assert(err, ErrorMatches, "cannot populate vfat filesystem with contents: hard fail")
c.Assert(cmdMkfs.Calls(), HasLen, 1)
c.Assert(cmdMcopy.Calls(), HasLen, 1)
@@ -281,7 +345,7 @@ func (m *mkfsSuite) TestMkfsVfatHappyNoContents(c *C) {
cmdMcopy := testutil.MockCommand(c, "mcopy", "")
defer cmdMcopy.Restore()
- err := internal.MkfsWithContent("vfat", "foo.img", "my-label", "", 0)
+ err := internal.MkfsWithContent("vfat", "foo.img", "my-label", "", 0, 0)
c.Assert(err, IsNil)
c.Assert(cmdMkfs.Calls(), HasLen, 1)
// mcopy was not called
@@ -289,10 +353,10 @@ func (m *mkfsSuite) TestMkfsVfatHappyNoContents(c *C) {
}
func (m *mkfsSuite) TestMkfsInvalidFs(c *C) {
- err := internal.MkfsWithContent("no-fs", "foo.img", "my-label", "", 0)
+ err := internal.MkfsWithContent("no-fs", "foo.img", "my-label", "", 0, 0)
c.Assert(err, ErrorMatches, `cannot create unsupported filesystem "no-fs"`)
- err = internal.Mkfs("no-fs", "foo.img", "my-label", 0)
+ err = internal.Mkfs("no-fs", "foo.img", "my-label", 0, 0)
c.Assert(err, ErrorMatches, `cannot create unsupported filesystem "no-fs"`)
}
diff --git a/overlord/devicestate/devicemgr.go b/overlord/devicestate/devicemgr.go
index f2e3a96dbe..a19c5f2b69 100644
--- a/overlord/devicestate/devicemgr.go
+++ b/overlord/devicestate/devicemgr.go
@@ -674,6 +674,9 @@ func (m *DeviceManager) ensureCloudInitRestricted() error {
// already been permanently disabled, nothing to do
m.cloudInitAlreadyRestricted = true
return nil
+ case sysconfig.CloudInitNotFound:
+ // no cloud init at all
+ statusMsg = "not found"
case sysconfig.CloudInitUntriggered:
// hasn't been used
statusMsg = "reported to be in disabled state"
diff --git a/overlord/devicestate/devicestate_cloudinit_test.go b/overlord/devicestate/devicestate_cloudinit_test.go
index 558108d5a8..3040243430 100644
--- a/overlord/devicestate/devicestate_cloudinit_test.go
+++ b/overlord/devicestate/devicestate_cloudinit_test.go
@@ -1137,3 +1137,29 @@ fi`, cloudInitScriptStateFile))
// we now have a message about restricting
c.Assert(strings.TrimSpace(s.logbuf.String()), Matches, `.*System initialized, cloud-init reported to be done, set datasource_list to \[ NoCloud \] and disabled auto-import by filesystem label`)
}
+func (s *cloudInitSuite) TestCloudInitHappyNotFound(c *C) {
+ // pretend that cloud-init was not found on PATH
+ statusCalls := 0
+ r := devicestate.MockCloudInitStatus(func() (sysconfig.CloudInitState, error) {
+ statusCalls++
+ return sysconfig.CloudInitNotFound, nil
+ })
+ defer r()
+
+ restrictCalls := 0
+ r = devicestate.MockRestrictCloudInit(func(state sysconfig.CloudInitState, opts *sysconfig.CloudInitRestrictOptions) (sysconfig.CloudInitRestrictionResult, error) {
+ restrictCalls++
+ // there was no cloud-init binary, so we explicitly disabled it
+ // if it reappears in future
+ return sysconfig.CloudInitRestrictionResult{
+ Action: "disable",
+ }, nil
+ })
+ defer r()
+
+ err := devicestate.EnsureCloudInitRestricted(s.mgr)
+ c.Assert(err, IsNil)
+ c.Assert(statusCalls, Equals, 1)
+ c.Assert(restrictCalls, Equals, 1)
+ c.Assert(strings.TrimSpace(s.logbuf.String()), Matches, `.*System initialized, cloud-init not found, disabled permanently`)
+}
diff --git a/packaging/ubuntu-16.04/changelog b/packaging/ubuntu-16.04/changelog
index 5d2a3f8373..a2e0e31e4f 100644
--- a/packaging/ubuntu-16.04/changelog
+++ b/packaging/ubuntu-16.04/changelog
@@ -1,3 +1,9 @@
+snapd (2.49~rc1) xenial; urgency=medium
+
+ * New 2.49~rc1 release candidate
+
+ -- Michael Vogt <michael.vogt@ubuntu.com> Mon, 25 Jan 2021 16:38:38 +0100
+
snapd (2.48.2) xenial; urgency=medium
* New upstream release, LP: #1906690
diff --git a/sysconfig/cloudinit.go b/sysconfig/cloudinit.go
index 0eee0bf692..8702a87ada 100644
--- a/sysconfig/cloudinit.go
+++ b/sysconfig/cloudinit.go
@@ -29,6 +29,7 @@ import (
"regexp"
"github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/strutil"
)
@@ -195,6 +196,9 @@ const (
// states, as we are conservative in assuming that cloud-init is doing
// something.
CloudInitEnabled
+ // CloudInitNotFound is when there is no cloud-init executable on the
+ // device.
+ CloudInitNotFound
// CloudInitErrored is when cloud-init tried to run, but failed or had invalid
// configuration.
CloudInitErrored
@@ -221,7 +225,13 @@ func CloudInitStatus() (CloudInitState, error) {
return CloudInitDisabledPermanently, nil
}
- out, err := exec.Command("cloud-init", "status").CombinedOutput()
+ ciBinary, err := exec.LookPath("cloud-init")
+ if err != nil {
+ logger.Noticef("cannot locate cloud-init executable: %v", err)
+ return CloudInitNotFound, nil
+ }
+
+ out, err := exec.Command(ciBinary, "status").CombinedOutput()
if err != nil {
return CloudInitErrored, osutil.OutputErr(out, err)
}
@@ -315,7 +325,7 @@ func RestrictCloudInit(state CloudInitState, opts *CloudInitRestrictOptions) (Cl
return res, fmt.Errorf("cannot restrict cloud-init in error or enabled state")
}
fallthrough
- case CloudInitUntriggered:
+ case CloudInitUntriggered, CloudInitNotFound:
fallthrough
default:
res.Action = "disable"
diff --git a/sysconfig/cloudinit_test.go b/sysconfig/cloudinit_test.go
index 04bdb251d5..29bdba45af 100644
--- a/sysconfig/cloudinit_test.go
+++ b/sysconfig/cloudinit_test.go
@@ -318,6 +318,19 @@ fi
}
}
+func (s *sysconfigSuite) TestCloudInitNotFoundStatus(c *C) {
+ emptyDir := c.MkDir()
+ oldPath := os.Getenv("PATH")
+ defer func() {
+ c.Assert(os.Setenv("PATH", oldPath), IsNil)
+ }()
+ os.Setenv("PATH", emptyDir)
+
+ status, err := sysconfig.CloudInitStatus()
+ c.Assert(err, IsNil)
+ c.Check(status, Equals, sysconfig.CloudInitNotFound)
+}
+
var gceCloudInitStatusJSON = `{
"v1": {
"datasource": "DataSourceGCE",
@@ -518,6 +531,12 @@ func (s *sysconfigSuite) TestRestrictCloudInit(c *C) {
expAction: "disable",
expDisableFile: true,
},
+ {
+ comment: "no cloud-init in $PATH",
+ state: sysconfig.CloudInitNotFound,
+ expAction: "disable",
+ expDisableFile: true,
+ },
}
for _, t := range tt {
diff --git a/tests/lib/tools/tests.pkgs.dnf-yum.sh b/tests/lib/tools/tests.pkgs.dnf-yum.sh
index 0efd635c21..115023c83a 100644
--- a/tests/lib/tools/tests.pkgs.dnf-yum.sh
+++ b/tests/lib/tools/tests.pkgs.dnf-yum.sh
@@ -15,11 +15,7 @@ remap_one() {
echo "python3-gobject"
;;
test-snapd-pkg-1)
- if [ "$(command -v dnf)" != "" ]; then
- echo "robotfindskitten"
- else
- echo "libXft"
- fi
+ echo "freeglut"
;;
test-snapd-pkg-2)
echo "texlive-base"
diff --git a/tests/main/ca-certs-for-snaps/task.yaml b/tests/main/ca-certs-for-snaps/task.yaml
new file mode 100644
index 0000000000..4588c1400c
--- /dev/null
+++ b/tests/main/ca-certs-for-snaps/task.yaml
@@ -0,0 +1,29 @@
+summary: Ensure that /etc/ssl customizations are available for snaps
+
+details: |
+ The host may have custom ssl certificates in the /etc/ssl directory.
+ These should be visible from inside the snaps too.
+
+prepare: |
+ "$TESTSTOOLS"/snaps-state install-local test-snapd-sh
+ echo "Mock having a custom certificate in /etc/ssl"
+ if os.query is-classic && test -d /etc/ssl; then
+ touch /etc/ssl/file-from-host
+ fi
+
+restore: |
+ rm -f /etc/ssl/file-from-host
+
+execute: |
+ #shellcheck source=tests/lib/dirs.sh
+ . "$TESTSLIB/dirs.sh"
+
+ # XXX: update when we get more systems that support custom ssl
+ # certificate usage
+ if [[ "$SPREAD_SYSTEM" = ubuntu-[0-9]* ]] || [[ "$SPREAD_SYSTEM" = debian-* ]]; then
+ echo "Ensure the hosts /etc/ssl is visible for classic Ubuntu/Debian"
+ test-snapd-sh.sh -c "test -e /etc/ssl/file-from-host"
+ else
+ echo "The hosts /etc/ssl is not used here"
+ test-snapd-sh.sh -c "test ! -e /etc/ssl/file-from-host"
+ fi
diff --git a/tests/main/dirs-not-shared-with-host/task.yaml b/tests/main/dirs-not-shared-with-host/task.yaml
index 0cc3c29c94..9d5fc2dec5 100644
--- a/tests/main/dirs-not-shared-with-host/task.yaml
+++ b/tests/main/dirs-not-shared-with-host/task.yaml
@@ -8,10 +8,11 @@ details: |
consistent behaviour across various distributions.
environment:
- DIRECTORY/ssl: /etc/ssl
+ DIRECTORY/apparmord: /etc/apparmor.d
# This test only applies to classic systems
-systems: [-ubuntu-core-*]
+# fedora, centos, amazon: do not have /etc/apparmor.d
+systems: [-ubuntu-core-*, -fedora-*, -centos-*, -amazon-linux-*]
prepare: |
echo "Having installed the test snap"
diff --git a/tests/main/mount-ns/google.ubuntu-16.04-64/PER-SNAP-16.expected.txt b/tests/main/mount-ns/google.ubuntu-16.04-64/PER-SNAP-16.expected.txt
index a05bd21c08..0578b6a83b 100644
--- a/tests/main/mount-ns/google.ubuntu-16.04-64/PER-SNAP-16.expected.txt
+++ b/tests/main/mount-ns/google.ubuntu-16.04-64/PER-SNAP-16.expected.txt
@@ -8,7 +8,6 @@
+0:-29 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
-1:-4 /etc /etc rw,relatime master:-6 - ext4 /dev/sda1 rw,data=ordered
+2:+0 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:+16 - squashfs /dev/loop0 ro
-+0:+0 /etc/ssl /etc/ssl ro,nodev,relatime master:+0 - squashfs /dev/loop0 ro
-2:+0 /home /home rw,relatime master:-16 - ext4 /dev/sda1 rw,data=ordered
+0:+0 /lib/firmware /lib/firmware rw,relatime master:+0 - ext4 /dev/sda1 rw,data=ordered
+0:+0 /lib/modules /lib/modules rw,relatime master:+0 - ext4 /dev/sda1 rw,data=ordered
diff --git a/tests/main/mount-ns/google.ubuntu-16.04-64/PER-SNAP-18.expected.txt b/tests/main/mount-ns/google.ubuntu-16.04-64/PER-SNAP-18.expected.txt
index f036cf9f1a..69f621b15a 100644
--- a/tests/main/mount-ns/google.ubuntu-16.04-64/PER-SNAP-18.expected.txt
+++ b/tests/main/mount-ns/google.ubuntu-16.04-64/PER-SNAP-18.expected.txt
@@ -8,7 +8,6 @@
+0:-29 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
-1:-4 /etc /etc rw,relatime master:-6 - ext4 /dev/sda1 rw,data=ordered
+2:+1 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:+17 - squashfs /dev/loop1 ro
-+0:+0 /etc/ssl /etc/ssl ro,nodev,relatime master:+0 - squashfs /dev/loop1 ro
-2:-1 /home /home rw,relatime master:-17 - ext4 /dev/sda1 rw,data=ordered
+0:+0 /lib/firmware /lib/firmware rw,relatime master:+0 - ext4 /dev/sda1 rw,data=ordered
+0:+0 /lib/modules /lib/modules rw,relatime master:+0 - ext4 /dev/sda1 rw,data=ordered
diff --git a/tests/main/mount-ns/google.ubuntu-16.04-64/PER-USER-16.expected.txt b/tests/main/mount-ns/google.ubuntu-16.04-64/PER-USER-16.expected.txt
index e6637c58e3..56891b8d41 100644
--- a/tests/main/mount-ns/google.ubuntu-16.04-64/PER-USER-16.expected.txt
+++ b/tests/main/mount-ns/google.ubuntu-16.04-64/PER-USER-16.expected.txt
@@ -8,7 +8,6 @@
+0:-29 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
-1:-4 /etc /etc rw,relatime master:-6 - ext4 /dev/sda1 rw,data=ordered
+2:+0 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:+16 - squashfs /dev/loop0 ro
-+0:+0 /etc/ssl /etc/ssl ro,nodev,relatime master:+0 - squashfs /dev/loop0 ro
-2:+0 /home /home rw,relatime master:-16 - ext4 /dev/sda1 rw,data=ordered
+0:+0 /lib/firmware /lib/firmware rw,relatime master:+0 - ext4 /dev/sda1 rw,data=ordered
+0:+0 /lib/modules /lib/modules rw,relatime master:+0 - ext4 /dev/sda1 rw,data=ordered
diff --git a/tests/main/mount-ns/google.ubuntu-16.04-64/PER-USER-18.expected.txt b/tests/main/mount-ns/google.ubuntu-16.04-64/PER-USER-18.expected.txt
index 4a65a1e029..b1d4bd721f 100644
--- a/tests/main/mount-ns/google.ubuntu-16.04-64/PER-USER-18.expected.txt
+++ b/tests/main/mount-ns/google.ubuntu-16.04-64/PER-USER-18.expected.txt
@@ -8,7 +8,6 @@
+0:-29 / /dev/shm rw,nosuid,nodev master:7 - tmpfs tmpfs rw
-1:-4 /etc /etc rw,relatime master:-6 - ext4 /dev/sda1 rw,data=ordered
+2:+1 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:+17 - squashfs /dev/loop1 ro
-+0:+0 /etc/ssl /etc/ssl ro,nodev,relatime master:+0 - squashfs /dev/loop1 ro
-2:-1 /home /home rw,relatime master:-17 - ext4 /dev/sda1 rw,data=ordered
+0:+0 /lib/firmware /lib/firmware rw,relatime master:+0 - ext4 /dev/sda1 rw,data=ordered
+0:+0 /lib/modules /lib/modules rw,relatime master:+0 - ext4 /dev/sda1 rw,data=ordered
diff --git a/tests/main/mount-ns/google.ubuntu-18.04-64/PER-SNAP-16.expected.txt b/tests/main/mount-ns/google.ubuntu-18.04-64/PER-SNAP-16.expected.txt
index 5c5f7bdb4d..5b9d2a2a17 100644
--- a/tests/main/mount-ns/google.ubuntu-18.04-64/PER-SNAP-16.expected.txt
+++ b/tests/main/mount-ns/google.ubuntu-18.04-64/PER-SNAP-16.expected.txt
@@ -10,7 +10,6 @@
+2:+0 /etc/apparmor /etc/apparmor ro,nodev,relatime master:+14 - squashfs /dev/loop0 ro
+0:+0 /etc/apparmor.d /etc/apparmor.d ro,nodev,relatime master:+0 - squashfs /dev/loop0 ro
+0:+0 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:+0 - squashfs /dev/loop0 ro
-+0:+0 /etc/ssl /etc/ssl ro,nodev,relatime master:+0 - squashfs /dev/loop0 ro
-2:+0 /home /home rw,relatime master:-14 - ext4 /dev/sda1 rw
+0:+0 /lib/firmware /lib/firmware rw,relatime master:+0 - ext4 /dev/sda1 rw
+0:+0 /lib/modules /lib/modules rw,relatime master:+0 - ext4 /dev/sda1 rw
diff --git a/tests/main/mount-ns/google.ubuntu-18.04-64/PER-SNAP-18.expected.txt b/tests/main/mount-ns/google.ubuntu-18.04-64/PER-SNAP-18.expected.txt
index 5612310ba0..ca2b9d2cc9 100644
--- a/tests/main/mount-ns/google.ubuntu-18.04-64/PER-SNAP-18.expected.txt
+++ b/tests/main/mount-ns/google.ubuntu-18.04-64/PER-SNAP-18.expected.txt
@@ -10,7 +10,6 @@
+2:+1 /etc/apparmor /etc/apparmor ro,nodev,relatime master:+15 - squashfs /dev/loop1 ro
+0:+0 /etc/apparmor.d /etc/apparmor.d ro,nodev,relatime master:+0 - squashfs /dev/loop1 ro
+0:+0 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:+0 - squashfs /dev/loop1 ro
-+0:+0 /etc/ssl /etc/ssl ro,nodev,relatime master:+0 - squashfs /dev/loop1 ro
-2:-1 /home /home rw,relatime master:-15 - ext4 /dev/sda1 rw
+0:+0 /lib/firmware /lib/firmware rw,relatime master:+0 - ext4 /dev/sda1 rw
+0:+0 /lib/modules /lib/modules rw,relatime master:+0 - ext4 /dev/sda1 rw
diff --git a/tests/main/mount-ns/google.ubuntu-18.04-64/PER-USER-16.expected.txt b/tests/main/mount-ns/google.ubuntu-18.04-64/PER-USER-16.expected.txt
index 250dbfb488..ce950ba38d 100644
--- a/tests/main/mount-ns/google.ubuntu-18.04-64/PER-USER-16.expected.txt
+++ b/tests/main/mount-ns/google.ubuntu-18.04-64/PER-USER-16.expected.txt
@@ -10,7 +10,6 @@
+2:+0 /etc/apparmor /etc/apparmor ro,nodev,relatime master:+14 - squashfs /dev/loop0 ro
+0:+0 /etc/apparmor.d /etc/apparmor.d ro,nodev,relatime master:+0 - squashfs /dev/loop0 ro
+0:+0 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:+0 - squashfs /dev/loop0 ro
-+0:+0 /etc/ssl /etc/ssl ro,nodev,relatime master:+0 - squashfs /dev/loop0 ro
-2:+0 /home /home rw,relatime master:-14 - ext4 /dev/sda1 rw
+0:+0 /lib/firmware /lib/firmware rw,relatime master:+0 - ext4 /dev/sda1 rw
+0:+0 /lib/modules /lib/modules rw,relatime master:+0 - ext4 /dev/sda1 rw
diff --git a/tests/main/mount-ns/google.ubuntu-18.04-64/PER-USER-18.expected.txt b/tests/main/mount-ns/google.ubuntu-18.04-64/PER-USER-18.expected.txt
index 83fc895dac..035878bc97 100644
--- a/tests/main/mount-ns/google.ubuntu-18.04-64/PER-USER-18.expected.txt
+++ b/tests/main/mount-ns/google.ubuntu-18.04-64/PER-USER-18.expected.txt
@@ -10,7 +10,6 @@
+2:+1 /etc/apparmor /etc/apparmor ro,nodev,relatime master:+15 - squashfs /dev/loop1 ro
+0:+0 /etc/apparmor.d /etc/apparmor.d ro,nodev,relatime master:+0 - squashfs /dev/loop1 ro
+0:+0 /etc/nsswitch.conf /etc/nsswitch.conf ro,nodev,relatime master:+0 - squashfs /dev/loop1 ro
-+0:+0 /etc/ssl /etc/ssl ro,nodev,relatime master:+0 - squashfs /dev/loop1 ro
-2:-1 /home /home rw,relatime master:-15 - ext4 /dev/sda1 rw
+0:+0 /lib/firmware /lib/firmware rw,relatime master:+0 - ext4 /dev/sda1 rw
+0:+0 /lib/modules /lib/modules rw,relatime master:+0 - ext4 /dev/sda1 rw