summaryrefslogtreecommitdiff
diff options
authorPaweł Stołowski <stolowski@gmail.com>2021-10-20 10:08:18 +0200
committerPaweł Stołowski <stolowski@gmail.com>2021-10-20 10:08:18 +0200
commit19777ca67d6a78577a01d11aaf7bb011fde60ce1 (patch)
tree8127e10e39577f26842e4e15bb42d61e72aa25cf
parentb6abd7f487bb0ea29e8bf78bbbcd0aff3242dcd7 (diff)
parent79cec58dc0cee0c757ffe758279ddc44210b4ed8 (diff)
Merge branch 'master' into validation-sets/enforce-with-prereqvalidation-sets/enforce-with-prereq
-rw-r--r--bootloader/export_test.go44
-rw-r--r--cmd/libsnap-confine-private/device-cgroup-support.c22
-rw-r--r--cmd/snap-bootstrap/cmd_initramfs_mounts_test.go162
-rw-r--r--cmd/snap-confine/udev-support.c3
-rw-r--r--cmd/snap/cmd_snap_op.go25
-rw-r--r--cmd/snap/cmd_snap_op_test.go20
-rw-r--r--gadget/gadget.go15
-rw-r--r--gadget/gadget_test.go14
-rw-r--r--go.mod8
-rw-r--r--go.sum18
-rw-r--r--interfaces/builtin/hardware_observe.go3
-rw-r--r--interfaces/builtin/modem_manager.go2
-rw-r--r--interfaces/builtin/unity7.go8
-rw-r--r--osutil/disks/disks_linux.go21
-rw-r--r--osutil/disks/disks_linux_test.go57
-rw-r--r--osutil/disks/labels.go94
-rw-r--r--osutil/disks/labels_test.go70
-rw-r--r--osutil/disks/mockdisk.go187
-rw-r--r--osutil/disks/mockdisk_test.go78
-rw-r--r--overlord/devicestate/devicestate.go85
-rw-r--r--overlord/devicestate/devicestate_remodel_test.go135
-rw-r--r--overlord/hookstate/ctlcmd/refresh.go15
-rw-r--r--overlord/hookstate/ctlcmd/refresh_test.go5
-rw-r--r--overlord/hookstate/hooks.go2
-rw-r--r--overlord/hookstate/hooks_test.go6
-rw-r--r--overlord/managers_test.go218
-rw-r--r--overlord/snapstate/autorefresh_gating.go24
-rw-r--r--overlord/snapstate/autorefresh_gating_test.go182
-rw-r--r--overlord/snapstate/snapstate.go120
-rw-r--r--overlord/snapstate/snapstate_remove_test.go9
-rw-r--r--overlord/snapstate/snapstate_update_test.go3
-rw-r--r--sandbox/apparmor/apparmor_test.go41
-rw-r--r--secboot/export_sb_test.go54
-rw-r--r--secboot/secboot_sb_test.go318
-rw-r--r--secboot/secboot_tpm.go113
-rw-r--r--secboot/test-data/keyfilebin131303 -> 0 bytes
-rw-r--r--spread.yaml3
-rw-r--r--tests/lib/nested.sh13
-rw-r--r--tests/main/auto-refresh-gating/task.yaml5
-rw-r--r--tests/main/cwd/task.yaml15
-rw-r--r--tests/main/download-timeout/task.yaml4
-rw-r--r--tests/main/lxd-postrm-purge/task.yaml3
-rw-r--r--tests/main/lxd-services-smoke/task.yaml3
-rw-r--r--tests/main/lxd/task.yaml3
-rw-r--r--tests/main/non-home/task.yaml14
-rw-r--r--tests/main/parallel-install-basic/task.yaml37
-rw-r--r--tests/main/security-private-tmp/task.yaml5
-rw-r--r--tests/main/selinux-snap-restorecon/task.yaml8
-rw-r--r--tests/main/snap-confine-drops-sys-admin/task.yaml27
-rw-r--r--tests/main/snap-confine-privs/task.yaml32
-rw-r--r--tests/main/snap-confine-undesired-mode-group/task.yaml7
-rw-r--r--tests/main/snapd-sigterm/task.yaml9
-rw-r--r--tests/main/snapd-snap/task.yaml4
-rw-r--r--tests/main/snapshot-users/task.yaml9
-rw-r--r--tests/nested/core/core20-create-recovery/task.yaml6
-rw-r--r--tests/nested/core/extra-snaps-assertions/task.yaml18
-rw-r--r--tests/nested/manual/core20-grade-signed-above-testkeys-boot/defaults.yaml (renamed from tests/nested/manual/grade-signed-above-testkeys-boot/defaults.yaml)0
-rwxr-xr-xtests/nested/manual/core20-grade-signed-above-testkeys-boot/prepare-device (renamed from tests/nested/manual/grade-signed-above-testkeys-boot/prepare-device)0
-rw-r--r--tests/nested/manual/core20-grade-signed-above-testkeys-boot/task.yaml (renamed from tests/nested/manual/grade-signed-above-testkeys-boot/task.yaml)0
-rw-r--r--tests/nested/manual/core20-grade-signed-cloud-init-testkeys/defaults.yaml (renamed from tests/nested/manual/grade-signed-cloud-init-testkeys/defaults.yaml)0
-rwxr-xr-xtests/nested/manual/core20-grade-signed-cloud-init-testkeys/prepare-device (renamed from tests/nested/manual/grade-signed-cloud-init-testkeys/prepare-device)0
-rw-r--r--tests/nested/manual/core20-grade-signed-cloud-init-testkeys/task.yaml (renamed from tests/nested/manual/grade-signed-cloud-init-testkeys/task.yaml)0
-rw-r--r--tests/nested/manual/core20-new-snapd-does-not-break-old-initrd/task.yaml97
-rw-r--r--tests/nested/manual/refresh-revert-fundamentals/task.yaml11
64 files changed, 1775 insertions, 739 deletions
diff --git a/bootloader/export_test.go b/bootloader/export_test.go
index 94fdfe622c..c37992d584 100644
--- a/bootloader/export_test.go
+++ b/bootloader/export_test.go
@@ -123,17 +123,41 @@ func MockLkFiles(c *C, rootdir string, opts *Options) (restore func()) {
lkBootDisk := &disks.MockDiskMapping{
// mock the partition labels, since these structures won't have
// filesystems, but they will have partition labels
- PartitionLabelToPartUUID: map[string]string{
- "snapbootsel": "snapbootsel-partuuid",
- "snapbootselbak": "snapbootselbak-partuuid",
- "snaprecoverysel": "snaprecoverysel-partuuid",
- "snaprecoveryselbak": "snaprecoveryselbak-partuuid",
+ Structure: []disks.Partition{
+ {
+ PartitionLabel: "snapbootsel",
+ PartitionUUID: "snapbootsel-partuuid",
+ },
+ {
+ PartitionLabel: "snapbootselbak",
+ PartitionUUID: "snapbootselbak-partuuid",
+ },
+ {
+ PartitionLabel: "snaprecoverysel",
+ PartitionUUID: "snaprecoverysel-partuuid",
+ },
+ {
+ PartitionLabel: "snaprecoveryselbak",
+ PartitionUUID: "snaprecoveryselbak-partuuid",
+ },
// for run mode kernel snaps
- "boot_a": "boot-a-partuuid",
- "boot_b": "boot-b-partuuid",
+ {
+ PartitionLabel: "boot_a",
+ PartitionUUID: "boot-a-partuuid",
+ },
+ {
+ PartitionLabel: "boot_b",
+ PartitionUUID: "boot-b-partuuid",
+ },
// for recovery system kernel snaps
- "boot_ra": "boot-ra-partuuid",
- "boot_rb": "boot-rb-partuuid",
+ {
+ PartitionLabel: "boot_ra",
+ PartitionUUID: "boot-ra-partuuid",
+ },
+ {
+ PartitionLabel: "boot_rb",
+ PartitionUUID: "boot-rb-partuuid",
+ },
},
DiskHasPartitions: true,
DevNum: "lk-boot-disk-dev-num",
@@ -144,7 +168,7 @@ func MockLkFiles(c *C, rootdir string, opts *Options) (restore func()) {
}
// mock the disk
- r := disks.MockDeviceNameDisksToPartitionMapping(m)
+ r := disks.MockDeviceNameToDiskMapping(m)
cleanups = append(cleanups, r)
// now mock the kernel command line
diff --git a/cmd/libsnap-confine-private/device-cgroup-support.c b/cmd/libsnap-confine-private/device-cgroup-support.c
index 8c45709dd6..0889f0a6cc 100644
--- a/cmd/libsnap-confine-private/device-cgroup-support.c
+++ b/cmd/libsnap-confine-private/device-cgroup-support.c
@@ -284,6 +284,20 @@ static struct rlimit _sc_cgroup_v2_adjust_memlock_limit(void) {
return old_limit;
}
+static bool _sc_is_snap_cgroup(const char *group) {
+ /* make a copy as basename may modify its input */
+ char copy[PATH_MAX] = {0};
+ strncpy(copy, group, sizeof(copy) - 1);
+ char *leaf = basename(copy);
+ if (!sc_startswith(leaf, "snap.")) {
+ return false;
+ }
+ if (!sc_endswith(leaf, ".service") && !sc_endswith(leaf, ".scope")) {
+ return false;
+ }
+ return true;
+}
+
static int _sc_cgroup_v2_init_bpf(sc_device_cgroup *self, int flags) {
self->v2.devmap_fd = -1;
self->v2.cgroup_fd = -1;
@@ -292,6 +306,14 @@ static int _sc_cgroup_v2_init_bpf(sc_device_cgroup *self, int flags) {
if (own_group == NULL) {
die("cannot obtain own group path");
}
+ debug("process in cgroup %s", own_group);
+ if (!_sc_is_snap_cgroup(own_group)) {
+ /* we cannot proceed to install a device filtering program when the
+ * process is not in a snap specific cgroup, as we would effectively
+ * lock down the group that can be shared with other processes or even
+ * the whole desktop session */
+ die("%s is not a snap cgroup", own_group);
+ }
/* fix the memlock limit if needed, this affects creating maps */
self->v2.old_limit = _sc_cgroup_v2_adjust_memlock_limit();
diff --git a/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go b/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go
index c2f0dc2394..7d2733a770 100644
--- a/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go
+++ b/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go
@@ -93,34 +93,64 @@ var (
NoSuid: true,
}
+ seedPart = disks.Partition{
+ FilesystemLabel: "ubuntu-seed",
+ PartitionUUID: "ubuntu-seed-partuuid",
+ }
+
+ bootPart = disks.Partition{
+ FilesystemLabel: "ubuntu-boot",
+ PartitionUUID: "ubuntu-boot-partuuid",
+ }
+
+ savePart = disks.Partition{
+ FilesystemLabel: "ubuntu-save",
+ PartitionUUID: "ubuntu-save-partuuid",
+ }
+
+ dataPart = disks.Partition{
+ FilesystemLabel: "ubuntu-data",
+ PartitionUUID: "ubuntu-data-partuuid",
+ }
+
+ saveEncPart = disks.Partition{
+ FilesystemLabel: "ubuntu-save-enc",
+ PartitionUUID: "ubuntu-save-enc-partuuid",
+ }
+
+ dataEncPart = disks.Partition{
+ FilesystemLabel: "ubuntu-data-enc",
+ PartitionUUID: "ubuntu-data-enc-partuuid",
+ }
+
// a boot disk without ubuntu-save
defaultBootDisk = &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "ubuntu-boot": "ubuntu-boot-partuuid",
- "ubuntu-seed": "ubuntu-seed-partuuid",
- "ubuntu-data": "ubuntu-data-partuuid",
+ Structure: []disks.Partition{
+ seedPart,
+ bootPart,
+ dataPart,
},
DiskHasPartitions: true,
DevNum: "default",
}
defaultBootWithSaveDisk = &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "ubuntu-boot": "ubuntu-boot-partuuid",
- "ubuntu-seed": "ubuntu-seed-partuuid",
- "ubuntu-data": "ubuntu-data-partuuid",
- "ubuntu-save": "ubuntu-save-partuuid",
+ Structure: []disks.Partition{
+ seedPart,
+ bootPart,
+ dataPart,
+ savePart,
},
DiskHasPartitions: true,
DevNum: "default-with-save",
}
defaultEncBootDisk = &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "ubuntu-boot": "ubuntu-boot-partuuid",
- "ubuntu-seed": "ubuntu-seed-partuuid",
- "ubuntu-data-enc": "ubuntu-data-enc-partuuid",
- "ubuntu-save-enc": "ubuntu-save-enc-partuuid",
+ Structure: []disks.Partition{
+ bootPart,
+ seedPart,
+ dataEncPart,
+ saveEncPart,
},
DiskHasPartitions: true,
DevNum: "defaultEncDev",
@@ -2086,10 +2116,10 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRunModeEncryptedDataUnhappyNoS
s.mockProcCmdlineContent(c, "snapd_recovery_mode=run")
defaultEncNoSaveBootDisk := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "ubuntu-boot": "ubuntu-boot-partuuid",
- "ubuntu-seed": "ubuntu-seed-partuuid",
- "ubuntu-data-enc": "ubuntu-data-enc-partuuid",
+ Structure: []disks.Partition{
+ seedPart,
+ bootPart,
+ dataEncPart,
// missing ubuntu-save
},
DiskHasPartitions: true,
@@ -3542,10 +3572,11 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedDegradedAb
defer bootloader.Force(nil)
defaultEncDiskNoBoot := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "ubuntu-seed": "ubuntu-seed-partuuid",
- "ubuntu-data-enc": "ubuntu-data-enc-partuuid",
- "ubuntu-save-enc": "ubuntu-save-enc-partuuid",
+ Structure: []disks.Partition{
+ seedPart,
+ // missing ubuntu-boot
+ dataEncPart,
+ saveEncPart,
},
DiskHasPartitions: true,
DevNum: "defaultEncDevNoBoot",
@@ -3698,10 +3729,11 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedDegradedAb
defer bootloader.Force(nil)
defaultEncDiskNoBoot := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "ubuntu-seed": "ubuntu-seed-partuuid",
- "ubuntu-data-enc": "ubuntu-data-enc-partuuid",
- "ubuntu-save-enc": "ubuntu-save-enc-partuuid",
+ Structure: []disks.Partition{
+ seedPart,
+ // missing ubuntu-boot
+ dataEncPart,
+ saveEncPart,
},
DiskHasPartitions: true,
DevNum: "defaultEncDevNoBoot",
@@ -4059,10 +4091,10 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeDegradedAbsentDataU
// no ubuntu-data on the disk at all
mockDiskNoData := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "ubuntu-boot": "ubuntu-boot-partuuid",
- "ubuntu-seed": "ubuntu-seed-partuuid",
- "ubuntu-save": "ubuntu-save-partuuid",
+ Structure: []disks.Partition{
+ seedPart,
+ bootPart,
+ savePart,
},
DiskHasPartitions: true,
DevNum: "noDataUnenc",
@@ -4239,12 +4271,12 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeDegradedUnencrypted
// no ubuntu-data on the disk at all
mockDiskDataUnencSaveEnc := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "ubuntu-boot": "ubuntu-boot-partuuid",
- "ubuntu-seed": "ubuntu-seed-partuuid",
+ Structure: []disks.Partition{
+ seedPart,
+ bootPart,
// ubuntu-data is unencrypted but ubuntu-save is encrypted
- "ubuntu-data": "ubuntu-data-partuuid",
- "ubuntu-save-enc": "ubuntu-save-enc-partuuid",
+ dataPart,
+ saveEncPart,
},
DiskHasPartitions: true,
DevNum: "dataUnencSaveEnc",
@@ -4372,12 +4404,12 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeDegradedEncryptedDa
defer bootloader.Force(nil)
mockDiskDataUnencSaveEnc := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "ubuntu-boot": "ubuntu-boot-partuuid",
- "ubuntu-seed": "ubuntu-seed-partuuid",
- "ubuntu-data-enc": "ubuntu-data-enc-partuuid",
+ Structure: []disks.Partition{
+ seedPart,
+ bootPart,
// ubuntu-data is encrypted but ubuntu-save is not
- "ubuntu-save": "ubuntu-save-partuuid",
+ savePart,
+ dataEncPart,
},
DiskHasPartitions: true,
DevNum: "dataUnencSaveEnc",
@@ -4627,12 +4659,12 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedDegradedAb
bootloader.Force(bloader)
defer bootloader.Force(nil)
- // no ubuntu-data on the disk at all
mockDiskNoData := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "ubuntu-boot": "ubuntu-boot-partuuid",
- "ubuntu-seed": "ubuntu-seed-partuuid",
- "ubuntu-save-enc": "ubuntu-save-enc-partuuid",
+ Structure: []disks.Partition{
+ seedPart,
+ bootPart,
+ // no ubuntu-data on the disk at all
+ saveEncPart,
},
DiskHasPartitions: true,
DevNum: "defaultEncDev",
@@ -5193,21 +5225,33 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedAttackerFS
defer bootloader.Force(nil)
mockDisk := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "ubuntu-seed": "ubuntu-seed-partuuid",
- "ubuntu-boot": "ubuntu-boot-partuuid",
- "ubuntu-data-enc": "ubuntu-data-enc-partuuid",
- "ubuntu-save-enc": "ubuntu-save-enc-partuuid",
+ Structure: []disks.Partition{
+ seedPart,
+ bootPart,
+ saveEncPart,
+ dataEncPart,
},
DiskHasPartitions: true,
DevNum: "bootDev",
}
attackerDisk := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "ubuntu-seed": "ubuntu-seed-attacker-partuuid",
- "ubuntu-boot": "ubuntu-boot-attacker-partuuid",
- "ubuntu-data-enc": "ubuntu-data-enc-attacker-partuuid",
- "ubuntu-save-enc": "ubuntu-save-enc-attacker-partuuid",
+ Structure: []disks.Partition{
+ {
+ FilesystemLabel: "ubuntu-seed",
+ PartitionUUID: "ubuntu-seed-attacker-partuuid",
+ },
+ {
+ FilesystemLabel: "ubuntu-boot",
+ PartitionUUID: "ubuntu-boot-attacker-partuuid",
+ },
+ {
+ FilesystemLabel: "ubuntu-save-enc",
+ PartitionUUID: "ubuntu-save-enc-attacker-partuuid",
+ },
+ {
+ FilesystemLabel: "ubuntu-data-enc",
+ PartitionUUID: "ubuntu-data-enc-attacker-partuuid",
+ },
},
DiskHasPartitions: true,
DevNum: "attackerDev",
@@ -5332,8 +5376,8 @@ func (s *initramfsMountsSuite) testInitramfsMountsInstallRecoverModeMeasure(c *C
mockDiskMapping := map[disks.Mountpoint]*disks.MockDiskMapping{
{Mountpoint: boot.InitramfsUbuntuSeedDir}: {
- FilesystemLabelToPartUUID: map[string]string{
- "ubuntu-seed": "ubuntu-seed-partuuid",
+ Structure: []disks.Partition{
+ seedPart,
},
DiskHasPartitions: true,
},
@@ -5366,9 +5410,7 @@ func (s *initramfsMountsSuite) testInitramfsMountsInstallRecoverModeMeasure(c *C
// also add the ubuntu-data and ubuntu-save fs labels to the
// disk referenced by the ubuntu-seed partition
disk := mockDiskMapping[disks.Mountpoint{Mountpoint: boot.InitramfsUbuntuSeedDir}]
- disk.FilesystemLabelToPartUUID["ubuntu-boot"] = "ubuntu-boot-partuuid"
- disk.FilesystemLabelToPartUUID["ubuntu-data"] = "ubuntu-data-partuuid"
- disk.FilesystemLabelToPartUUID["ubuntu-save"] = "ubuntu-save-partuuid"
+ disk.Structure = append(disk.Structure, bootPart, savePart, dataPart)
// and also add the /run/mnt/host/ubuntu-{boot,data,save} mountpoints
// for cross-checking after mounting
diff --git a/cmd/snap-confine/udev-support.c b/cmd/snap-confine/udev-support.c
index b09ca3a385..8f937c7ba1 100644
--- a/cmd/snap-confine/udev-support.c
+++ b/cmd/snap-confine/udev-support.c
@@ -306,9 +306,6 @@ void sc_setup_device_cgroup(const char *security_tag)
return;
}
- /* Note that -1 is the neutral value for a file descriptor.
- * The cleanup function associated with this variable closes
- * descriptors other than -1. */
sc_device_cgroup *cgroup SC_CLEANUP(sc_device_cgroup_cleanup) =
sc_device_cgroup_new(security_tag, 0);
/* Setup the device group access control list */
diff --git a/cmd/snap/cmd_snap_op.go b/cmd/snap/cmd_snap_op.go
index 4f68096db7..f839a2f492 100644
--- a/cmd/snap/cmd_snap_op.go
+++ b/cmd/snap/cmd_snap_op.go
@@ -474,9 +474,10 @@ type cmdInstall struct {
Name string `long:"name"`
- Cohort string `long:"cohort"`
- IgnoreRunning bool `long:"ignore-running" hidden:"yes"`
- Positional struct {
+ Cohort string `long:"cohort"`
+ IgnoreValidation bool `long:"ignore-validation"`
+ IgnoreRunning bool `long:"ignore-running" hidden:"yes"`
+ Positional struct {
Snaps []remoteSnapName `positional-arg-name:"<snap>"`
} `positional-args:"yes" required:"yes"`
}
@@ -592,12 +593,13 @@ func (x *cmdInstall) Execute([]string) error {
dangerous := x.Dangerous || x.ForceDangerous
opts := &client.SnapOptions{
- Channel: x.Channel,
- Revision: x.Revision,
- Dangerous: dangerous,
- Unaliased: x.Unaliased,
- CohortKey: x.Cohort,
- IgnoreRunning: x.IgnoreRunning,
+ Channel: x.Channel,
+ Revision: x.Revision,
+ Dangerous: dangerous,
+ Unaliased: x.Unaliased,
+ CohortKey: x.Cohort,
+ IgnoreValidation: x.IgnoreValidation,
+ IgnoreRunning: x.IgnoreRunning,
}
x.setModes(opts)
@@ -618,6 +620,9 @@ func (x *cmdInstall) Execute([]string) error {
if x.asksForMode() || x.asksForChannel() {
return errors.New(i18n.G("a single snap name is needed to specify mode or channel flags"))
}
+ if x.IgnoreValidation {
+ return errors.New(i18n.G("a single snap name must be specified when ignoring validation"))
+ }
if x.Name != "" {
return errors.New(i18n.G("cannot use instance name when installing multiple snaps"))
@@ -1107,6 +1112,8 @@ func init() {
// TRANSLATORS: This should not start with a lowercase letter.
"cohort": i18n.G("Install the snap in the given cohort"),
// TRANSLATORS: This should not start with a lowercase letter.
+ "ignore-validation": i18n.G("Ignore validation by other snaps blocking the installation"),
+ // TRANSLATORS: This should not start with a lowercase letter.
"ignore-running": i18n.G("Ignore running hooks or applications blocking the installation"),
}), nil)
addCommand("refresh", shortRefreshHelp, longRefreshHelp, func() flags.Commander { return &cmdRefresh{} },
diff --git a/cmd/snap/cmd_snap_op_test.go b/cmd/snap/cmd_snap_op_test.go
index 4aa831664d..e222a2f185 100644
--- a/cmd/snap/cmd_snap_op_test.go
+++ b/cmd/snap/cmd_snap_op_test.go
@@ -1627,6 +1627,26 @@ func (s *SnapOpSuite) TestInstallFromChannel(c *check.C) {
c.Check(s.srv.n, check.Equals, s.srv.total)
}
+func (s *SnapOpSuite) TestInstallOneIgnoreValidation(c *check.C) {
+ s.RedirectClientToTestServer(s.srv.handle)
+ s.srv.checker = func(r *http.Request) {
+ c.Check(r.Method, check.Equals, "POST")
+ c.Check(r.URL.Path, check.Equals, "/v2/snaps/one")
+ c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
+ "action": "install",
+ "ignore-validation": true,
+ })
+ }
+ _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--ignore-validation", "one"})
+ c.Assert(err, check.IsNil)
+}
+
+func (s *SnapOpSuite) TestInstallManyIgnoreValidation(c *check.C) {
+ s.RedirectClientToTestServer(nil)
+ _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--ignore-validation", "one", "two"})
+ c.Assert(err, check.ErrorMatches, `a single snap name must be specified when ignoring validation`)
+}
+
func (s *SnapOpSuite) TestEnable(c *check.C) {
s.srv.total = 3
s.srv.checker = func(r *http.Request) {
diff --git a/gadget/gadget.go b/gadget/gadget.go
index bb550ba517..a0da7a8830 100644
--- a/gadget/gadget.go
+++ b/gadget/gadget.go
@@ -35,7 +35,6 @@ import (
"gopkg.in/yaml.v2"
"github.com/snapcore/snapd/asserts"
- "github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/gadget/edition"
"github.com/snapcore/snapd/gadget/quantity"
"github.com/snapcore/snapd/metautil"
@@ -272,16 +271,16 @@ type DiskStructureDeviceTraits struct {
Size quantity.Size `json:"size"`
}
-// SaveDiskVolumesDeviceTraits saves the mapping of volume names to volume / device
-// traits to a file on disk for later loading and verification.
-func SaveDiskVolumesDeviceTraits(mapping map[string]DiskVolumeDeviceTraits) error {
+// SaveDiskVolumesDeviceTraits saves the mapping of volume names to volume /
+// device traits to a file inside the provided directory on disk for
+// later loading and verification.
+func SaveDiskVolumesDeviceTraits(dir string, mapping map[string]DiskVolumeDeviceTraits) error {
b, err := json.Marshal(mapping)
if err != nil {
return err
}
- // TODO: should this live in dirs?
- filename := filepath.Join(dirs.SnapDeviceDir, "disk-mapping.json")
+ filename := filepath.Join(dir, "disk-mapping.json")
if err := os.MkdirAll(filepath.Dir(filename), 0755); err != nil {
return err
@@ -292,10 +291,10 @@ func SaveDiskVolumesDeviceTraits(mapping map[string]DiskVolumeDeviceTraits) erro
// LoadDiskVolumesDeviceTraits loads the mapping of volumes to disk traits if
// there is any. If there is no file with the mapping available, nil is
// returned.
-func LoadDiskVolumesDeviceTraits() (map[string]DiskVolumeDeviceTraits, error) {
+func LoadDiskVolumesDeviceTraits(dir string) (map[string]DiskVolumeDeviceTraits, error) {
var mapping map[string]DiskVolumeDeviceTraits
- filename := filepath.Join(dirs.SnapDeviceDir, "disk-mapping.json")
+ filename := filepath.Join(dir, "disk-mapping.json")
if !osutil.FileExists(filename) {
return nil, nil
}
diff --git a/gadget/gadget_test.go b/gadget/gadget_test.go
index c54f0c730f..a8817b34da 100644
--- a/gadget/gadget_test.go
+++ b/gadget/gadget_test.go
@@ -3034,14 +3034,22 @@ func (s *gadgetYamlTestSuite) TestSaveLoadDiskVolumeDeviceTraits(c *C) {
// when there is no mapping file, it is not an error, the map returned is
// just nil/has no items in it
- mAbsent, err := gadget.LoadDiskVolumesDeviceTraits()
+ mAbsent, err := gadget.LoadDiskVolumesDeviceTraits(dirs.SnapDeviceDir)
c.Assert(err, IsNil)
c.Assert(mAbsent, HasLen, 0)
- err = gadget.SaveDiskVolumesDeviceTraits(m)
+ // load looks in SnapDeviceDir since it is meant to be used during run mode
+ // when /var/lib/snapd/device/disk-mapping.json is the real version from
+ // ubuntu-data, but during install mode, we will need to save to the host
+ // ubuntu-data which is not located at /run/mnt/data or
+ // /var/lib/snapd/device, but rather
+ // /run/mnt/ubuntu-data/system-data/var/lib/snapd/device so this takes a
+ // directory argument when we save it
+ err = gadget.SaveDiskVolumesDeviceTraits(dirs.SnapDeviceDir, m)
c.Assert(err, IsNil)
- m2, err := gadget.LoadDiskVolumesDeviceTraits()
+ // now that it was saved to dirs.SnapDeviceDir, we can load it correctly
+ m2, err := gadget.LoadDiskVolumesDeviceTraits(dirs.SnapDeviceDir)
c.Assert(err, IsNil)
c.Assert(m, DeepEquals, m2)
diff --git a/go.mod b/go.mod
index 5f5c15c233..e371076058 100644
--- a/go.mod
+++ b/go.mod
@@ -3,10 +3,9 @@ module github.com/snapcore/snapd
go 1.13
require (
- github.com/canonical/go-efilib v0.0.0-20210909101908-41435fa545d4 // indirect
github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3 // indirect
- github.com/canonical/go-tpm2 v0.0.0-20210827151749-f80ff5afff61
- github.com/canonical/tcglog-parser v0.0.0-20210824131805-69fa1e9f0ad2 // indirect
+ github.com/canonical/go-tpm2 v0.0.0-20210314160024-32171bd353b1
+ github.com/canonical/tcglog-parser v0.0.0-20200908165021-12a3a7bcf5a1 // indirect
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2
github.com/gorilla/mux v1.7.4-0.20190701202633-d83b6ffe499a
@@ -18,7 +17,8 @@ require (
github.com/mvo5/libseccomp-golang v0.9.1-0.20180308152521-f4de83b52afb
github.com/snapcore/bolt v1.3.2-0.20210908134111-63c8bfcf7af8
github.com/snapcore/go-gettext v0.0.0-20191107141714-82bbea49e785
- github.com/snapcore/secboot v0.0.0-20210909111405-e3a397e2da90
+ github.com/snapcore/secboot v0.0.0-20210805184555-c9f2139ee92b
+ go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 // indirect
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
diff --git a/go.sum b/go.sum
index f579a8bb87..80162bf027 100644
--- a/go.sum
+++ b/go.sum
@@ -1,13 +1,9 @@
-github.com/canonical/go-efilib v0.0.0-20210909101908-41435fa545d4 h1:rSWREoNHHbcIC1iQeKKraBlsDm7cmKg8eS+N48jMVKA=
-github.com/canonical/go-efilib v0.0.0-20210909101908-41435fa545d4/go.mod h1:9Sr9kd7IhQPYqaU5nut8Ky97/CtlhHDzQncQnrULgDM=
-github.com/canonical/go-sp800.108-kdf v0.0.0-20210314145419-a3359f2d21b9 h1:USzKjrfWo/ESzozv2i3OMM7XDgxrZRvaHFrKkIKRtwU=
-github.com/canonical/go-sp800.108-kdf v0.0.0-20210314145419-a3359f2d21b9/go.mod h1:Zrs3YjJr+w51u0R/dyLh/oWt/EcBVdLPCVFYC4daW5s=
github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3 h1:oe6fCvaEpkhyW3qAicT0TnGtyht/UrgvOwMcEgLb7Aw=
github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3/go.mod h1:qdP0gaj0QtgX2RUZhnlVrceJ+Qln8aSlDyJwelLLFeM=
-github.com/canonical/go-tpm2 v0.0.0-20210827151749-f80ff5afff61 h1:DsyeCtFXqOdukmhPOunohjSlyxDHTqWSW1O4rD9N3L8=
-github.com/canonical/go-tpm2 v0.0.0-20210827151749-f80ff5afff61/go.mod h1:vG41hdbBjV4+/fkubTT1ENBBqSkLwLr7mCeW9Y6kpZY=
-github.com/canonical/tcglog-parser v0.0.0-20210824131805-69fa1e9f0ad2 h1:CbwVq64ruNLx/S3XA0LO6QMsw6Vc2inK+RcS6D2c4Ns=
-github.com/canonical/tcglog-parser v0.0.0-20210824131805-69fa1e9f0ad2/go.mod h1:QoW2apR2tBl6T/4czdND/EHjL1Ia9cCmQnIj9Xe0Kt8=
+github.com/canonical/go-tpm2 v0.0.0-20210314160024-32171bd353b1 h1:FGWb/opVaD42utMEAkDgO9QqXiTlwESSr7VAirVtW/Q=
+github.com/canonical/go-tpm2 v0.0.0-20210314160024-32171bd353b1/go.mod h1:j23KcThy5uN+suQ1HiKSpQxuR54apc2EF2P8C/FHSuE=
+github.com/canonical/tcglog-parser v0.0.0-20200908165021-12a3a7bcf5a1 h1:8uxbbF6v0M9G9sadgaGYnmTlhYXN6vDTahXnKsEdz00=
+github.com/canonical/tcglog-parser v0.0.0-20200908165021-12a3a7bcf5a1/go.mod h1:QoW2apR2tBl6T/4czdND/EHjL1Ia9cCmQnIj9Xe0Kt8=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5tgDm3YN7+9dYrpK96E5wFilTFWIDZOM=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/frankban/quicktest v1.2.2 h1:xfmOhhoH5fGPgbEAlhLpJH9p0z/0Qizio9osmvn9IUY=
@@ -42,8 +38,8 @@ github.com/snapcore/bolt v1.3.2-0.20210908134111-63c8bfcf7af8 h1:WmyDfH38e3MaMWr
github.com/snapcore/bolt v1.3.2-0.20210908134111-63c8bfcf7af8/go.mod h1:Z6z3sf12AMDjT/4tbT/PmzzdACAxkWGhkuKWiVpTWLM=
github.com/snapcore/go-gettext v0.0.0-20191107141714-82bbea49e785 h1:PaunR+BhraKSLxt2awQ42zofkP+NKh/VjQ0PjIMk/y4=
github.com/snapcore/go-gettext v0.0.0-20191107141714-82bbea49e785/go.mod h1:D3SsWAXK7wCCBZu+Vk5hc1EuKj/L3XN1puEMXTU4LrQ=
-github.com/snapcore/secboot v0.0.0-20210909111405-e3a397e2da90 h1:XCbqRVVmFztGHPWN+wdPcRahQY7moJBRd/g/zMXLJmA=
-github.com/snapcore/secboot v0.0.0-20210909111405-e3a397e2da90/go.mod h1:72paVOkm4sJugXt+v9ItmnjXgO921D8xqsbH2OekouY=
+github.com/snapcore/secboot v0.0.0-20210805184555-c9f2139ee92b h1:r8G3o2em2zKDyMDdHthy+FARm9qEiyGtIsJIkGVBMYo=
+github.com/snapcore/secboot v0.0.0-20210805184555-c9f2139ee92b/go.mod h1:72paVOkm4sJugXt+v9ItmnjXgO921D8xqsbH2OekouY=
github.com/snapcore/snapd v0.0.0-20201005140838-501d14ac146e/go.mod h1:3xrn7QDDKymcE5VO2rgWEQ5ZAUGb9htfwlXnoel6Io8=
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 h1:A/5uWzF44DlIgdm/PQFwfMkW0JX+cIcQi/SwLAmZP5M=
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
@@ -56,7 +52,6 @@ golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c/go.mod h1:iQL9McJNjoIa5mjH6n
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365 h1:6wSTsvPddg9gc/mVEEyk9oOAoxn+bT4Z9q1zx+4RwA4=
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
@@ -65,6 +60,7 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/macaroon.v1 v1.0.0-20150121114231-ab3940c6c165 h1:85xqOSyTpSzplW7fyO9bOZpSsemJc9UKzEQR2L4k32k=
diff --git a/interfaces/builtin/hardware_observe.go b/interfaces/builtin/hardware_observe.go
index 3bab1331b5..2ee7f8f115 100644
--- a/interfaces/builtin/hardware_observe.go
+++ b/interfaces/builtin/hardware_observe.go
@@ -45,6 +45,9 @@ capability sys_admin,
/etc/modprobe.d/{,*} r,
/{,usr/}lib/modprobe.d/{,*} r,
+# for reading the available input devices on the system
+/proc/bus/input/devices r,
+
# files in /sys pertaining to hardware (eg, 'lspci -A linux-sysfs')
/sys/{block,bus,class,devices,firmware}/{,**} r,
diff --git a/interfaces/builtin/modem_manager.go b/interfaces/builtin/modem_manager.go
index ead2cd1c0c..9fe81508bd 100644
--- a/interfaces/builtin/modem_manager.go
+++ b/interfaces/builtin/modem_manager.go
@@ -67,7 +67,7 @@ network netlink raw,
capability sys_admin,
# For {mbim,qmi}-proxy
-unix (bind, listen) type=stream addr="@{mbim,qmi}-proxy",
+unix (bind, listen, accept) type=stream addr="@{mbim,qmi}-proxy",
/sys/devices/**/usb**/{descriptors,manufacturer,product,bInterfaceClass,bInterfaceSubClass,bInterfaceProtocol,bInterfaceNumber} r,
# See https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-net-qmi
/sys/devices/**/net/*/qmi/* rw,
diff --git a/interfaces/builtin/unity7.go b/interfaces/builtin/unity7.go
index 3dc9a71a13..04fb6f6c36 100644
--- a/interfaces/builtin/unity7.go
+++ b/interfaces/builtin/unity7.go
@@ -359,28 +359,28 @@ dbus (send)
# dbusmenu
dbus (send)
bus=session
- path=/{MenuBar{,/[0-9A-F]*},com/canonical/menu/[0-9A-F]*}
+ path=/{MenuBar{,/[0-9A-F]*},com/canonical/{menu/[0-9A-F]*,dbusmenu}}
interface=com.canonical.dbusmenu
member="{LayoutUpdated,ItemsPropertiesUpdated}"
peer=(name=org.freedesktop.DBus, label=unconfined),
dbus (receive)
bus=session
- path=/{MenuBar{,/[0-9A-F]*},com/canonical/menu/[0-9A-F]*}
+ path=/{MenuBar{,/[0-9A-F]*},com/canonical/{menu/[0-9A-F]*,dbusmenu}}
interface="{com.canonical.dbusmenu,org.freedesktop.DBus.Properties}"
member=Get*
peer=(label=unconfined),
dbus (receive)
bus=session
- path=/{MenuBar{,/[0-9A-F]*},com/canonical/menu/[0-9A-F]*}
+ path=/{MenuBar{,/[0-9A-F]*},com/canonical/{menu/[0-9A-F]*,dbusmenu}}
interface=com.canonical.dbusmenu
member="{AboutTo*,Event*}"
peer=(label=unconfined),
dbus (receive)
bus=session
- path=/{MenuBar{,/[0-9A-F]*},com/canonical/menu/[0-9A-F]*}
+ path=/{MenuBar{,/[0-9A-F]*},com/canonical/{menu/[0-9A-F]*,dbusmenu}}
interface=org.freedesktop.DBus.Introspectable
member=Introspect
peer=(label=unconfined),
diff --git a/osutil/disks/disks_linux.go b/osutil/disks/disks_linux.go
index 7f2c05ac43..09142bddf4 100644
--- a/osutil/disks/disks_linux.go
+++ b/osutil/disks/disks_linux.go
@@ -144,11 +144,20 @@ func diskFromUdevProps(deviceIdentifier string, deviceIDType string, props map[s
// create the full path by pre-pending /sys, since udev doesn't include /sys
devpath = filepath.Join(dirs.SysfsDir, devpath)
+ // check if the device has partitions by attempting to actually search for
+ // them in /sys with the DEVPATH and DEVNAME
+
+ paths, err := filepath.Glob(filepath.Join(devpath, filepath.Base(devname)+"*"))
+ if err != nil {
+ return nil, fmt.Errorf("internal error with glob pattern: %v", err)
+ }
+
return &disk{
- major: major,
- minor: minor,
- devname: devname,
- devpath: devpath,
+ major: major,
+ minor: minor,
+ devname: devname,
+ devpath: devpath,
+ hasPartitions: len(paths) != 0,
}, nil
}
@@ -159,7 +168,7 @@ func DiskFromDevicePath(devicePath string) (Disk, error) {
}
// diskFromDevicePath is exposed for mocking from other tests via
-// MockDeviceNameDisksToPartitionMapping.
+// MockDevicePathToDiskMapping (which is yet to be added).
var diskFromDevicePath = func(devicePath string) (Disk, error) {
// query for the disk props using udev with --path
props, err := udevPropertiesForPath(devicePath)
@@ -177,7 +186,7 @@ func DiskFromDeviceName(deviceName string) (Disk, error) {
}
// diskFromDeviceName is exposed for mocking from other tests via
-// MockDeviceNameDisksToPartitionMapping.
+// MockDeviceNameToDiskMapping.
var diskFromDeviceName = func(deviceName string) (Disk, error) {
// query for the disk props using udev with --name
props, err := udevPropertiesForName(deviceName)
diff --git a/osutil/disks/disks_linux_test.go b/osutil/disks/disks_linux_test.go
index f12a5dcd6c..fb8f5f8170 100644
--- a/osutil/disks/disks_linux_test.go
+++ b/osutil/disks/disks_linux_test.go
@@ -89,8 +89,11 @@ var (
}
)
-func createVirtioDevicesInSysfs(c *C, devsToPartition map[string]bool) {
- diskDir := filepath.Join(dirs.SysfsDir, virtioDiskDevPath)
+func createVirtioDevicesInSysfs(c *C, path string, devsToPartition map[string]bool) {
+ if path == "" {
+ path = virtioDiskDevPath
+ }
+ diskDir := filepath.Join(dirs.SysfsDir, path)
for dev, isPartition := range devsToPartition {
err := os.MkdirAll(filepath.Join(diskDir, dev), 0755)
c.Assert(err, IsNil)
@@ -111,7 +114,7 @@ func (s *diskSuite) SetUpTest(c *C) {
dirs.SetRootDir(c.MkDir())
}
-func (s *diskSuite) TestDiskFromNameHappy(c *C) {
+func (s *diskSuite) TestDiskFromDeviceNameHappy(c *C) {
const sdaSysfsPath = "/devices/pci0000:00/0000:00:01.1/0000:01:00.1/ata1/host0/target0:0:0/0:0:0:0/block/sda"
restore := disks.MockUdevPropertiesForDevice(func(typeOpt, dev string) (map[string]string, error) {
c.Assert(typeOpt, Equals, "--name")
@@ -131,13 +134,29 @@ func (s *diskSuite) TestDiskFromNameHappy(c *C) {
c.Assert(d.Dev(), Equals, "1:2")
c.Assert(d.KernelDeviceNode(), Equals, "/dev/sda")
c.Assert(d.KernelDevicePath(), Equals, filepath.Join(dirs.SysfsDir, sdaSysfsPath))
+ // it doesn't have any partitions since we didn't mock any in sysfs
+ c.Assert(d.HasPartitions(), Equals, false)
+
+ // if we mock some sysfs partitions then it has partitions when we it has
+ // some partitions on it it
+ createVirtioDevicesInSysfs(c, sdaSysfsPath, map[string]bool{
+ "sda1": true,
+ "sda2": true,
+ })
+
+ d, err = disks.DiskFromDeviceName("sda")
+ c.Assert(err, IsNil)
+ c.Assert(d.Dev(), Equals, "1:2")
+ c.Assert(d.KernelDeviceNode(), Equals, "/dev/sda")
+ c.Assert(d.HasPartitions(), Equals, true)
}
-func (s *diskSuite) TestDiskFromPathHappy(c *C) {
+func (s *diskSuite) TestDiskFromDevicePathHappy(c *C) {
const vdaSysfsPath = "/devices/pci0000:00/0000:00:04.0/virtio2/block/vdb"
+ fullSysPath := filepath.Join("/sys", vdaSysfsPath)
restore := disks.MockUdevPropertiesForDevice(func(typeOpt, dev string) (map[string]string, error) {
c.Assert(typeOpt, Equals, "--path")
- c.Assert(dev, Equals, filepath.Join("/sys", vdaSysfsPath))
+ c.Assert(dev, Equals, fullSysPath)
return map[string]string{
"MAJOR": "1",
"MINOR": "2",
@@ -148,15 +167,31 @@ func (s *diskSuite) TestDiskFromPathHappy(c *C) {
})
defer restore()
- d, err := disks.DiskFromDevicePath(filepath.Join("/sys", vdaSysfsPath))
+ d, err := disks.DiskFromDevicePath(fullSysPath)
c.Assert(err, IsNil)
c.Assert(d.Dev(), Equals, "1:2")
c.Assert(d.KernelDeviceNode(), Equals, "/dev/vdb")
// note that we don't always prepend exactly /sys, we use dirs.SysfsDir
c.Assert(d.KernelDevicePath(), Equals, filepath.Join(dirs.SysfsDir, vdaSysfsPath))
+
+ // it doesn't have any partitions since we didn't mock any in sysfs
+ c.Assert(d.HasPartitions(), Equals, false)
+
+ // if we mock some sysfs partitions then it has partitions when we it has
+ // some partitions on it it
+ createVirtioDevicesInSysfs(c, vdaSysfsPath, map[string]bool{
+ "vdb1": true,
+ "vdb2": true,
+ })
+
+ d, err = disks.DiskFromDevicePath(fullSysPath)
+ c.Assert(err, IsNil)
+ c.Assert(d.Dev(), Equals, "1:2")
+ c.Assert(d.KernelDeviceNode(), Equals, "/dev/vdb")
+ c.Assert(d.HasPartitions(), Equals, true)
}
-func (s *diskSuite) TestDiskFromNameUnhappyPartition(c *C) {
+func (s *diskSuite) TestDiskFromDeviceNameUnhappyPartition(c *C) {
restore := disks.MockUdevPropertiesForDevice(func(typeOpt, dev string) (map[string]string, error) {
c.Assert(typeOpt, Equals, "--name")
c.Assert(dev, Equals, "sda1")
@@ -172,7 +207,7 @@ func (s *diskSuite) TestDiskFromNameUnhappyPartition(c *C) {
c.Assert(err, ErrorMatches, "device \"sda1\" is not a disk, it has DEVTYPE of \"partition\"")
}
-func (s *diskSuite) TestDiskFromNameUnhappyBadUdevOutput(c *C) {
+func (s *diskSuite) TestDiskFromDeviceNameUnhappyBadUdevOutput(c *C) {
restore := disks.MockUdevPropertiesForDevice(func(typeOpt, dev string) (map[string]string, error) {
c.Assert(typeOpt, Equals, "--name")
c.Assert(dev, Equals, "sda")
@@ -346,7 +381,7 @@ func (s *diskSuite) TestDiskFromMountPointHappySinglePartitionIgnoresNonPartitio
// create just the single valid partition in sysfs, and an invalid
// non-partition device that we should ignore
- createVirtioDevicesInSysfs(c, map[string]bool{
+ createVirtioDevicesInSysfs(c, "", map[string]bool{
"vda4": true,
"vda5": false,
})
@@ -601,7 +636,7 @@ func (s *diskSuite) TestDiskFromMountPointPartitionsHappy(c *C) {
defer restore()
// create all 4 partitions as device nodes in sysfs
- createVirtioDevicesInSysfs(c, map[string]bool{
+ createVirtioDevicesInSysfs(c, "", map[string]bool{
"vda1": true,
"vda2": true,
"vda3": true,
@@ -833,7 +868,7 @@ func (s *diskSuite) TestDiskFromMountPointDecryptedDevicePartitionsHappy(c *C) {
c.Assert(err, IsNil)
// mock the dev nodes in sysfs for the partitions
- createVirtioDevicesInSysfs(c, map[string]bool{
+ createVirtioDevicesInSysfs(c, "", map[string]bool{
"vda1": true,
"vda2": true,
"vda3": true,
diff --git a/osutil/disks/labels.go b/osutil/disks/labels.go
index ab5c25245e..19aba4fc1d 100644
--- a/osutil/disks/labels.go
+++ b/osutil/disks/labels.go
@@ -22,6 +22,7 @@ package disks
import (
"bytes"
"fmt"
+ "strconv"
"strings"
"unicode/utf8"
)
@@ -39,10 +40,101 @@ func BlkIDEncodeLabel(in string) string {
case utf8.RuneLen(r) > 1:
buf.WriteRune(r)
case !strings.ContainsRune(allowed, r):
- fmt.Fprintf(buf, `\x%x`, r)
+ fmt.Fprintf(buf, `\x%02x`, r)
default:
buf.WriteRune(r)
}
}
return buf.String()
}
+
+type blkIdDecodeState int
+
+const (
+ stNormal blkIdDecodeState = iota
+ stSlashEscape
+ stSlashEscapeX
+ stSlashEscapeXNum
+)
+
+// BlkIDDecodeLabel decodes a string such as a filesystem or partition label
+// encoded by udev in BlkIDEncodeLabel for normal comparison, i.e.
+// "BIOS\x20Boot" becomes "BIOS Boot"
+func BlkIDDecodeLabel(in string) (string, error) {
+
+ const errFmtStr = "string is malformed, unexpected character '%c' not part of a valid escape sequence"
+
+ out := strings.Builder{}
+ escapedHexDigits := [2]rune{}
+ st := stNormal
+ for _, r := range in {
+ switch st {
+ case stNormal:
+ // check if this char is the beginning of an escape sequence
+ if r == '\\' {
+ st = stSlashEscape
+ continue
+ }
+ // otherwise write it
+ out.WriteRune(r)
+ case stSlashEscape:
+ // next char to check is 'x'
+ if r == 'x' {
+ st = stSlashEscapeX
+ continue
+ }
+ // otherwise it's a format error, "\" is not in the set of
+ // characters allowed, so if we see one that is not followed by an
+ // x, then the string is malformed and can't be decoded
+ return "", fmt.Errorf(errFmtStr, '\\')
+ case stSlashEscapeX:
+ // now we expect exactly two hex digits, since the encoding would
+ // have written valid multi-byte runes that are UTF8 directly
+ // without escaping, the only possible escaped runes are those which
+ // are one byte and not in the allowed set
+
+ // TODO: though can one have multi-byte runes that are not UTF8
+ // encodable? it seems the only possibilities are runes that are
+ // either in the surrogate range or that are larger than the maximum
+ // rune value - for now we will just ignore those
+
+ if strings.ContainsRune(`0123456789abcedf`, r) {
+ escapedHexDigits[0] = r
+ st = stSlashEscapeXNum
+ continue
+ }
+ return "", fmt.Errorf(errFmtStr, r)
+ case stSlashEscapeXNum:
+ // got one digit, make sure we get a second digit
+ if strings.ContainsRune(`0123456789abcedf`, r) {
+ escapedHexDigits[1] = r
+
+ // the escapedHexDigits can now be decoded and written out
+ v, err := strconv.ParseUint(string(escapedHexDigits[:]), 16, 8)
+ if err != nil {
+ // should be logically impossible, we ensured that only
+ // rune digits in the hexadecimal range above were put into this rune
+ // buffer
+ return "", fmt.Errorf("internal error, unable to parse escape sequence: %v", err)
+ }
+ escapedHexDigits = [2]rune{0, 0}
+ out.WriteRune(rune(v))
+ st = stNormal
+ continue
+ }
+ return "", fmt.Errorf(errFmtStr, r)
+ default:
+ return "", fmt.Errorf("internal error, unexpected parsing state")
+ }
+ }
+
+ // check that we had a valid end state
+ switch st {
+ case stNormal:
+ return out.String(), nil
+ case stSlashEscape, stSlashEscapeX, stSlashEscapeXNum:
+ return "", fmt.Errorf("string is malformed, unfinished escape sequence")
+ default:
+ return "", fmt.Errorf("internal error, unexpected parsing state")
+ }
+}
diff --git a/osutil/disks/labels_test.go b/osutil/disks/labels_test.go
index bc89b067e8..10277d8c54 100644
--- a/osutil/disks/labels_test.go
+++ b/osutil/disks/labels_test.go
@@ -33,7 +33,7 @@ type diskLabelSuite struct{}
var _ = Suite(&diskLabelSuite{})
-func (ts *diskLabelSuite) TestEncodeHexBlkIDFormat(c *C) {
+func (ts *diskLabelSuite) TestBlkIDEncodeDecodeLabelHappy(c *C) {
// Test output obtained with the following program:
//
// #include <string.h>
@@ -72,8 +72,9 @@ func (ts *diskLabelSuite) TestEncodeHexBlkIDFormat(c *C) {
// these are "unsafe" chars, so they get encoded
{"ubuntu data", `ubuntu\x20data`},
- {"ubuntu\ttab", `ubuntu\x9tab`},
- {"ubuntu\nnewline", `ubuntu\xanewline`},
+ {"ubuntu\ttab", `ubuntu\x09tab`},
+ {"ubuntu\t9tab", `ubuntu\x099tab`},
+ {"ubuntu\nnewline", `ubuntu\x0anewline`},
{"foo bar", `foo\x20bar`},
{"foo/bar", `foo\x2fbar`},
{"foo/../bar", `foo\x2f..\x2fbar`},
@@ -81,9 +82,72 @@ func (ts *diskLabelSuite) TestEncodeHexBlkIDFormat(c *C) {
{"pinkié pie", `pinkié\x20pie`},
{"(EFI Boot)", `\x28EFI\x20Boot\x29`},
{"[System Boot]", `\x5bSystem\x20Boot\x5d`},
+ // 0x7e is just a 1-rune long character that is not in the allowed set
+ // to demonstrate that these two input strings are encoded/decoded
+ // properly with the constant double width
+ {"ubuntu\x7etab", `ubuntu\x7etab`},
+ {"ubuntu\x07" + "etab", `ubuntu\x07etab`},
+ // works when the only character is an escaped one too
+ {"\t", `\x09`},
}
for _, t := range tt {
c.Logf("tc: %v %q", t.in, t.out)
c.Assert(disks.BlkIDEncodeLabel(t.in), Equals, t.out)
+
+ // make sure the other way around works too
+ expin, err := disks.BlkIDDecodeLabel(t.out)
+ c.Assert(err, IsNil)
+
+ c.Assert(expin, Equals, t.in)
+ }
+}
+
+func (ts *diskLabelSuite) TestBlkIDDecodeLabelUnhappy(c *C) {
+ tt := []struct {
+ in string
+ experr string
+ }{
+ {
+ `\x7z`,
+ "string is malformed, unexpected character 'z' not part of a valid escape sequence",
+ },
+ {
+ `\x09\x7y`,
+ "string is malformed, unexpected character 'y' not part of a valid escape sequence",
+ },
+ {
+ `\z`,
+ `string is malformed, unexpected character '\\' not part of a valid escape sequence`,
+ },
+ {
+ `\`,
+ `string is malformed, unfinished escape sequence`,
+ },
+ {
+ `\x40\`,
+ `string is malformed, unfinished escape sequence`,
+ },
+ {
+ `\x`,
+ `string is malformed, unfinished escape sequence`,
+ },
+ {
+ `\x40\x`,
+ `string is malformed, unfinished escape sequence`,
+ },
+ {
+ `\x0`,
+ `string is malformed, unfinished escape sequence`,
+ },
+ {
+ `\x40\x4`,
+ `string is malformed, unfinished escape sequence`,
+ },
+ }
+
+ for _, t := range tt {
+ c.Logf("input: %q", t.in)
+ _, err := disks.BlkIDDecodeLabel(t.in)
+ c.Assert(err, ErrorMatches, t.experr)
}
}
diff --git a/osutil/disks/mockdisk.go b/osutil/disks/mockdisk.go
index 898827f92d..6546eb66a4 100644
--- a/osutil/disks/mockdisk.go
+++ b/osutil/disks/mockdisk.go
@@ -34,20 +34,19 @@ var _ = Disk(&MockDiskMapping{})
// DevNum must be a unique string per unique mocked disk, if only one disk is
// being mocked it can be left empty.
type MockDiskMapping struct {
- // TODO: eliminate these manual mappings and instead switch all the users
- // over to providing the full list of Partitions instead, but in the
- // interest of smaller PR's we are doing that in a separate PR.
- // FilesystemLabelToPartUUID is a mapping of the udev encoded filesystem
- // labels to the expected partition uuids.
- FilesystemLabelToPartUUID map[string]string
- // PartitionLabelToPartUUID is a mapping of the udev encoded partition
- // labels to the expected partition uuids.
- PartitionLabelToPartUUID map[string]string
- DiskHasPartitions bool
-
- // TODO: add an exported list of Partitions here
-
- // static variables for the disk
+ // TODO: should this be automatically determined if Structure has non-zero
+ // len instead?
+ DiskHasPartitions bool
+
+ // Structure is the set of partitions or structures on the disk. These
+ // partitions are used with Partitions() as well as
+ // FindMatchingPartitionWith{Fs,Part}Label
+ Structure []Partition
+
+ // static variables for the disk that must be unique for different disks,
+ // but note that there are potentially multiple DevNode values that could
+ // map to a single disk, but it's not worth encoding that complexity here
+ // by making DevNodes a list
DevNum string
DevNode string
DevPath string
@@ -58,19 +57,13 @@ type MockDiskMapping struct {
func (d *MockDiskMapping) FindMatchingPartitionWithFsLabel(label string) (Partition, error) {
// TODO: this should just iterate over the static list when that is a thing
osutil.MustBeTestBinary("mock disks only to be used in tests")
- if partuuid, ok := d.FilesystemLabelToPartUUID[label]; ok {
- part := Partition{
- PartitionUUID: partuuid,
- FilesystemLabel: label,
- }
- // add the partition label too if we have one for this partition uuid
- for partlabel, partuuid2 := range d.PartitionLabelToPartUUID {
- if partuuid2 == partuuid {
- part.PartitionLabel = partlabel
- }
+
+ for _, p := range d.Structure {
+ if p.FilesystemLabel == label {
+ return p, nil
}
- return part, nil
}
+
return Partition{}, PartitionNotFoundError{
SearchType: "filesystem-label",
SearchQuery: label,
@@ -80,21 +73,14 @@ func (d *MockDiskMapping) FindMatchingPartitionWithFsLabel(label string) (Partit
// FindMatchingPartitionUUIDWithPartLabel returns a matching PartitionUUID
// for the specified filesystem label if it exists. Part of the Disk interface.
func (d *MockDiskMapping) FindMatchingPartitionWithPartLabel(label string) (Partition, error) {
- // TODO: this should just iterate over the static list when that is a thing
osutil.MustBeTestBinary("mock disks only to be used in tests")
- if partuuid, ok := d.PartitionLabelToPartUUID[label]; ok {
- part := Partition{
- PartitionUUID: partuuid,
- PartitionLabel: label,
- }
- // add the filesystem label too if we have one for this partition uuid
- for fsLabel, partuuid2 := range d.FilesystemLabelToPartUUID {
- if partuuid2 == partuuid {
- part.FilesystemLabel = fsLabel
- }
+
+ for _, p := range d.Structure {
+ if p.PartitionLabel == label {
+ return p, nil
}
- return part, nil
}
+
return Partition{}, PartitionNotFoundError{
SearchType: "partition-label",
SearchQuery: label,
@@ -118,39 +104,7 @@ func (d *MockDiskMapping) FindMatchingPartitionUUIDWithPartLabel(label string) (
}
func (d *MockDiskMapping) Partitions() ([]Partition, error) {
- // TODO: this should just return the static list that was in the mapping
- // when that is a thing
-
- // dynamically build up a list of partitions with the mappings we were
- // provided
- parts := make([]Partition, 0, len(d.PartitionLabelToPartUUID))
-
- partUUIDToPart := map[string]Partition{}
-
- // first populate with all the partition labels
- for partLabel, partuuid := range d.PartitionLabelToPartUUID {
- part := Partition{
- PartitionLabel: partLabel,
- PartitionUUID: partuuid,
- }
-
- partUUIDToPart[partuuid] = part
- }
-
- for fsLabel, partuuid := range d.FilesystemLabelToPartUUID {
- existingPart, ok := partUUIDToPart[partuuid]
- if !ok {
- parts = append(parts, Partition{
- FilesystemLabel: fsLabel,
- PartitionUUID: partuuid,
- })
- continue
- }
- existingPart.FilesystemLabel = fsLabel
- parts = append(parts, existingPart)
- }
-
- return parts, nil
+ return d.Structure, nil
}
// HasPartitions returns if the mock disk has partitions or not. Part of the
@@ -202,19 +156,100 @@ type Mountpoint struct {
IsDecryptedDevice bool
}
-// MockDeviceNameDisksToPartitionMapping will mock DiskFromDeviceName such that
-// the provided map of device names to mock disks is used instead of the actual
+func checkMockDiskMappingsForDuplicates(mockedDisks map[string]*MockDiskMapping) {
+ // we do the minimal amount of validation here, where if things are
+ // specified as non-zero value we check that they make sense, but we don't
+ // require that every field is set for every partition since many tests
+ // don't care about every field
+
+ // check partition uuid's and partition labels for duplication inter-disk
+ // we could have valid cloned disks where the same partition uuid/label
+ // appears on two disks, but never on the same disk
+ for _, disk := range mockedDisks {
+ seenPartUUID := make(map[string]bool, len(disk.Structure))
+ seenPartLabel := make(map[string]bool, len(disk.Structure))
+ for _, p := range disk.Structure {
+ if p.PartitionUUID != "" {
+ if seenPartUUID[p.PartitionUUID] {
+ panic("mock error: disk has duplicated partition uuids in its structure")
+ }
+ seenPartUUID[p.PartitionUUID] = true
+ }
+
+ if p.PartitionLabel != "" {
+ if seenPartLabel[p.PartitionLabel] {
+ panic("mock error: disk has duplicated partition labels in its structure")
+ }
+ seenPartLabel[p.PartitionLabel] = true
+ }
+ }
+ }
+
+ // check major/minors across all structures
+ type majmin struct{ maj, min int }
+ seenMajorMinors := map[majmin]bool{}
+ for _, disk := range mockedDisks {
+ for _, p := range disk.Structure {
+ if p.Major == 0 && p.Minor == 0 {
+ continue
+ }
+
+ m := majmin{maj: p.Major, min: p.Minor}
+ if seenMajorMinors[m] {
+ panic("mock error: duplicated major minor numbers for partitions in disk mapping")
+ }
+ seenMajorMinors[m] = true
+ }
+ }
+
+ // check device paths across all structures
+ seenDevPaths := map[string]bool{}
+ for _, disk := range mockedDisks {
+ for _, p := range disk.Structure {
+ if p.KernelDevicePath == "" {
+ continue
+ }
+ if seenDevPaths[p.KernelDevicePath] {
+ panic("mock error: duplicated kernel device paths for partitions in disk mapping")
+ }
+ seenDevPaths[p.KernelDevicePath] = true
+ }
+ }
+
+ // check device nodes across all structures
+ seendDevNodes := map[string]bool{}
+ for _, disk := range mockedDisks {
+ for _, p := range disk.Structure {
+ if p.KernelDevicePath == "" {
+ continue
+ }
+
+ if seendDevNodes[p.KernelDeviceNode] {
+ panic("mock error: duplicated kernel device nodes for partitions in disk mapping")
+ }
+ seendDevNodes[p.KernelDeviceNode] = true
+ }
+ }
+
+ // no checking of filesystem label/uuid since those could be duplicated as
+ // they exist independent of any other structure
+}
+
+// MockDeviceNameToDiskMapping will mock DiskFromDeviceName such that the
+// provided map of device names to mock disks is used instead of the actual
// implementation using udev.
-func MockDeviceNameDisksToPartitionMapping(mockedMountPoints map[string]*MockDiskMapping) (restore func()) {
+func MockDeviceNameToDiskMapping(mockedDisks map[string]*MockDiskMapping) (restore func()) {
osutil.MustBeTestBinary("mock disks only to be used in tests")
- // note that devices can have many names that are recognized by
+ checkMockDiskMappingsForDuplicates(mockedDisks)
+
+ // note that devices can have multiple "names" that are recognized by
// udev/kernel, so we don't do any validation of the mapping here like we do
- // for MockMountPointDisksToPartitionMapping
+ // for MockMountPointDisksToPartitionMapping and MockDevicePathDisksToPartitionMapping
old := diskFromDeviceName
diskFromDeviceName = func(deviceName string) (Disk, error) {
- disk, ok := mockedMountPoints[deviceName]
+ disk, ok := mockedDisks[deviceName]
if !ok {
return nil, fmt.Errorf("device name %q not mocked", deviceName)
}
diff --git a/osutil/disks/mockdisk_test.go b/osutil/disks/mockdisk_test.go
index 79ca4cfd5d..a7b9127adb 100644
--- a/osutil/disks/mockdisk_test.go
+++ b/osutil/disks/mockdisk_test.go
@@ -39,11 +39,14 @@ func (s *mockDiskSuite) SetUpTest(c *C) {
dirs.SetRootDir(c.MkDir())
}
-func (s *mockDiskSuite) TestMockDeviceNameDisksToPartitionMapping(c *C) {
+func (s *mockDiskSuite) TestMockDeviceNameToDiskMapping(c *C) {
// one disk with different device names
d1 := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "label1": "part1",
+ Structure: []disks.Partition{
+ {
+ FilesystemLabel: "label1",
+ PartitionUUID: "part1",
+ },
},
DiskHasPartitions: true,
DevNum: "d1",
@@ -52,8 +55,11 @@ func (s *mockDiskSuite) TestMockDeviceNameDisksToPartitionMapping(c *C) {
}
d2 := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "label2": "part2",
+ Structure: []disks.Partition{
+ {
+ FilesystemLabel: "label2",
+ PartitionUUID: "part2",
+ },
},
DiskHasPartitions: true,
DevNum: "d2",
@@ -67,7 +73,7 @@ func (s *mockDiskSuite) TestMockDeviceNameDisksToPartitionMapping(c *C) {
"other-disk": d2,
}
- r := disks.MockDeviceNameDisksToPartitionMapping(m)
+ r := disks.MockDeviceNameToDiskMapping(m)
defer r()
res, err := disks.DiskFromDeviceName("devName1")
@@ -104,16 +110,22 @@ func (s *mockDiskSuite) TestMockDeviceNameDisksToPartitionMapping(c *C) {
func (s *mockDiskSuite) TestMockMountPointDisksToPartitionMappingVerifiesUniqueness(c *C) {
// two different disks with different DevNum's
d1 := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "label1": "part1",
+ Structure: []disks.Partition{
+ {
+ FilesystemLabel: "label1",
+ PartitionUUID: "part1",
+ },
},
DiskHasPartitions: true,
DevNum: "d1",
}
d2 := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "label1": "part1",
+ Structure: []disks.Partition{
+ {
+ FilesystemLabel: "label1",
+ PartitionUUID: "part1",
+ },
},
DiskHasPartitions: false,
DevNum: "d2",
@@ -152,8 +164,11 @@ func (s *mockDiskSuite) TestMockMountPointDisksToPartitionMappingVerifiesUniquen
func (s *mockDiskSuite) TestMockMountPointDisksToPartitionMappingVerifiesConsistency(c *C) {
d1 := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "label1": "part1",
+ Structure: []disks.Partition{
+ {
+ FilesystemLabel: "label1",
+ PartitionUUID: "part1",
+ },
},
DiskHasPartitions: true,
DevNum: "d1",
@@ -177,22 +192,24 @@ func (s *mockDiskSuite) TestMockMountPointDisksToPartitionMappingVerifiesConsist
func (s *mockDiskSuite) TestMockMountPointDisksToPartitionMapping(c *C) {
d1 := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "label1": "part1",
- },
- PartitionLabelToPartUUID: map[string]string{
- "part-label1": "part1",
+ Structure: []disks.Partition{
+ {
+ FilesystemLabel: "label1",
+ PartitionUUID: "part1",
+ PartitionLabel: "part-label1",
+ },
},
DiskHasPartitions: true,
DevNum: "d1",
}
d2 := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "label2": "part2",
- },
- PartitionLabelToPartUUID: map[string]string{
- "part-label2": "part2",
+ Structure: []disks.Partition{
+ {
+ FilesystemLabel: "label2",
+ PartitionUUID: "part2",
+ PartitionLabel: "part-label2",
+ },
},
DiskHasPartitions: true,
DevNum: "d2",
@@ -322,10 +339,19 @@ func (s *mockDiskSuite) TestMockMountPointDisksToPartitionMapping(c *C) {
func (s *mockDiskSuite) TestMockMountPointDisksToPartitionMappingDecryptedDevices(c *C) {
d1 := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "ubuntu-seed": "ubuntu-seed-part",
- "ubuntu-boot": "ubuntu-boot-part",
- "ubuntu-data-enc": "ubuntu-data-enc-part",
+ Structure: []disks.Partition{
+ {
+ FilesystemLabel: "ubuntu-seed",
+ PartitionUUID: "ubuntu-seed-part",
+ },
+ {
+ FilesystemLabel: "ubuntu-boot",
+ PartitionUUID: "ubuntu-boot-part",
+ },
+ {
+ FilesystemLabel: "ubuntu-data-enc",
+ PartitionUUID: "ubuntu-data-enc-part",
+ },
},
DiskHasPartitions: true,
DevNum: "d1",
diff --git a/overlord/devicestate/devicestate.go b/overlord/devicestate/devicestate.go
index 0ad0fcc28b..46a601245b 100644
--- a/overlord/devicestate/devicestate.go
+++ b/overlord/devicestate/devicestate.go
@@ -401,23 +401,34 @@ type modelSnapsForRemodel struct {
newModelSnap *asserts.ModelSnap
}
-func remodelKernelOrBaseTasks(ctx context.Context, st *state.State, ms modelSnapsForRemodel, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) {
+func (ms *modelSnapsForRemodel) canHaveUC18PinnedTrack() bool {
+ return ms.newModelSnap != nil &&
+ (ms.newModelSnap.SnapType == "kernel" || ms.newModelSnap.SnapType == "gadget")
+}
+
+func remodelEssentialSnapTasks(ctx context.Context, st *state.State, ms modelSnapsForRemodel, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) {
userID := 0
newModelSnapChannel, err := modelSnapChannelFromDefaultOrPinnedTrack(ms.new, ms.newModelSnap)
if err != nil {
return nil, err
}
+
+ addExistingSnapTasks := snapstate.LinkNewBaseOrKernel
+ if ms.newModelSnap != nil && ms.newModelSnap.SnapType == "gadget" {
+ addExistingSnapTasks = snapstate.SwitchToNewGadget
+ }
+
if ms.currentSnap == ms.newSnap {
+ // new model uses the same base, kernel or gadget snap
changed := false
if ms.new.Grade() != asserts.ModelGradeUnset {
// UC20 models can specify default channel for all snaps
- // including base and kernel
- // new model uses the same base or kernel
+ // including base, kernel and gadget
changed, err = installedSnapChannelChanged(st, ms.newSnap, newModelSnapChannel)
if err != nil {
return nil, err
}
- } else if ms.newModelSnap != nil && ms.newModelSnap.SnapType == "kernel" {
+ } else if ms.canHaveUC18PinnedTrack() {
// UC18 models could only specify track for the kernel
// and gadget snaps
changed = ms.currentModelSnap.PinnedTrack != ms.newModelSnap.PinnedTrack
@@ -465,49 +476,31 @@ func remodelKernelOrBaseTasks(ctx context.Context, st *state.State, ms modelSnap
// switch-snap-channel
return ts, nil
} else {
- // in other cases make sure that the
- // kernel or base is linked and available
- return snapstate.AddLinkNewBaseOrKernel(st, ts)
+ if ms.newModelSnap.SnapType == "kernel" || ms.newModelSnap.SnapType == "base" {
+ // in other cases make sure that
+ // the kernel or base is linked
+ // and available, and that
+ // kernel updates boot assets if
+ // needed
+ ts, err = snapstate.AddLinkNewBaseOrKernel(st, ts)
+ if err != nil {
+ return nil, err
+ }
+ } else if ms.newModelSnap.SnapType == "gadget" {
+ // gadget snaps may need gadget
+ // related tasks such as assets
+ // update or command line update
+ ts, err = snapstate.AddGadgetAssetsTasks(st, ts)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return ts, nil
}
}
}
}
- return snapstate.LinkNewBaseOrKernel(st, ms.newSnap)
-}
-
-func remodelGadgetTasks(ctx context.Context, st *state.State, ms modelSnapsForRemodel, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) {
- userID := 0
- newGadgetChannel, err := modelSnapChannelFromDefaultOrPinnedTrack(ms.new, ms.newModelSnap)
- if err != nil {
- return nil, err
- }
- if ms.currentSnap == ms.newSnap {
- // already installed, but may be using a different channel
- changed := false
- if ms.new.Grade() != asserts.ModelGradeUnset {
- // UC20 models can specify default channel for all snaps
- // including the gadget
- changed, err = installedSnapChannelChanged(st, ms.newSnap, newGadgetChannel)
- if err != nil {
- return nil, err
- }
- } else {
- // pre UC20 models could only specify a track for the
- // gadget
- changed = ms.currentModelSnap.PinnedTrack != ms.newModelSnap.PinnedTrack
- }
- if changed {
- return snapstateUpdateWithDeviceContext(st, ms.newSnap,
- &snapstate.RevisionOptions{Channel: newGadgetChannel},
- userID, snapstate.Flags{NoReRefresh: true}, deviceCtx, fromChange)
- }
- return nil, nil
- }
-
- // install the new gadget
- return snapstateInstallWithDeviceContext(ctx, st, ms.newSnap,
- &snapstate.RevisionOptions{Channel: newGadgetChannel},
- userID, snapstate.Flags{}, deviceCtx, fromChange)
+ return addExistingSnapTasks(st, ms.newSnap)
}
func remodelTasks(ctx context.Context, st *state.State, current, new *asserts.Model, deviceCtx snapstate.DeviceContext, fromChange string) ([]*state.TaskSet, error) {
@@ -522,7 +515,7 @@ func remodelTasks(ctx context.Context, st *state.State, current, new *asserts.Mo
newSnap: new.Kernel(),
newModelSnap: new.KernelSnap(),
}
- ts, err := remodelKernelOrBaseTasks(ctx, st, kms, deviceCtx, fromChange)
+ ts, err := remodelEssentialSnapTasks(ctx, st, kms, deviceCtx, fromChange)
if err != nil {
return nil, err
}
@@ -537,7 +530,7 @@ func remodelTasks(ctx context.Context, st *state.State, current, new *asserts.Mo
newSnap: new.Base(),
newModelSnap: new.BaseSnap(),
}
- ts, err = remodelKernelOrBaseTasks(ctx, st, bms, deviceCtx, fromChange)
+ ts, err = remodelEssentialSnapTasks(ctx, st, bms, deviceCtx, fromChange)
if err != nil {
return nil, err
}
@@ -552,7 +545,7 @@ func remodelTasks(ctx context.Context, st *state.State, current, new *asserts.Mo
newSnap: new.Gadget(),
newModelSnap: new.GadgetSnap(),
}
- ts, err = remodelGadgetTasks(ctx, st, gms, deviceCtx, fromChange)
+ ts, err = remodelEssentialSnapTasks(ctx, st, gms, deviceCtx, fromChange)
if err != nil {
return nil, err
}
diff --git a/overlord/devicestate/devicestate_remodel_test.go b/overlord/devicestate/devicestate_remodel_test.go
index 8e28ec3bfb..d7364e5b6d 100644
--- a/overlord/devicestate/devicestate_remodel_test.go
+++ b/overlord/devicestate/devicestate_remodel_test.go
@@ -2096,7 +2096,12 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelGadgetBaseSnaps(c *C)
})
}
-func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSnaps(c *C) {
+func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseGadgetSnapsInstalledSnaps(c *C) {
+ // remodel switches to a new set of kernel, base and gadget snaps, but
+ // those happen to be already installed and tracking the right channels,
+ // this scenario can happen when the system has gone through many
+ // remodels and the new gadget, kernel, base snaps were required by one
+ // of the prior models
s.state.Lock()
defer s.state.Unlock()
s.state.Set("seeded", true)
@@ -2173,13 +2178,16 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna
Active: true,
TrackingChannel: "20/stable",
})
- // new gadget and kernel which are already installed
- for _, alreadyInstalledName := range []string{"pc-kernel-new", "core20-new"} {
+ // new gadget, base and kernel which are already installed
+ for _, alreadyInstalledName := range []string{"pc-new", "pc-kernel-new", "core20-new"} {
snapYaml := "name: pc-kernel-new\nversion: 1\ntype: kernel\n"
channel := "20/stable"
- if alreadyInstalledName == "core20-new" {
+ switch alreadyInstalledName {
+ case "core20-new":
snapYaml = "name: core20-new\nversion: 1\ntype: base\n"
channel = "latest/stable"
+ case "pc-new":
+ snapYaml = "name: pc-new\nversion: 1\ntype: gadget\n"
}
si := &snap.SideInfo{
RealName: alreadyInstalledName,
@@ -2212,8 +2220,8 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna
"default-channel": "20/stable",
},
map[string]interface{}{
- "name": "pc",
- "id": snaptest.AssertedSnapID("pc"),
+ "name": "pc-new",
+ "id": snaptest.AssertedSnapID("pc-new"),
"type": "gadget",
"default-channel": "20",
},
@@ -2224,8 +2232,8 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna
c.Assert(chg.Summary(), Equals, "Refresh model assertion from revision 0 to 1")
tl := chg.Tasks()
- // 2 snaps (2 tasks for each) + recovery system (2 tasks) + set-model
- c.Assert(tl, HasLen, 2*2+2+1)
+ // 2 snaps (2 tasks for each) + assets update from kernel + gadget (3 tasks) + recovery system (2 tasks) + set-model
+ c.Assert(tl, HasLen, 2*2+1+3+2+1)
deviceCtx, err := devicestate.DeviceCtx(s.state, tl[0], nil)
c.Assert(err, IsNil)
@@ -2240,11 +2248,15 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna
// check the tasks
tPrepareKernel := tl[0]
tLinkKernel := tl[1]
- tPrepareBase := tl[2]
- tLinkBase := tl[3]
- tCreateRecovery := tl[4]
- tFinalizeRecovery := tl[5]
- tSetModel := tl[6]
+ tUpdateAssetsKernel := tl[2]
+ tPrepareBase := tl[3]
+ tLinkBase := tl[4]
+ tPrepareGadget := tl[5]
+ tUpdateAssets := tl[6]
+ tUpdateCmdline := tl[7]
+ tCreateRecovery := tl[8]
+ tFinalizeRecovery := tl[9]
+ tSetModel := tl[10]
// check the tasks
c.Assert(tPrepareKernel.Kind(), Equals, "prepare-snap")
@@ -2252,11 +2264,22 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna
c.Assert(tPrepareKernel.WaitTasks(), HasLen, 0)
c.Assert(tLinkKernel.Kind(), Equals, "link-snap")
c.Assert(tLinkKernel.Summary(), Equals, `Make snap "pc-kernel-new" (222) available to the system during remodel`)
+ c.Assert(tUpdateAssetsKernel.Kind(), Equals, "update-gadget-assets")
+ c.Assert(tUpdateAssetsKernel.Summary(), Equals, `Update assets from kernel "pc-kernel-new" (222) for remodel`)
c.Assert(tPrepareBase.Kind(), Equals, "prepare-snap")
c.Assert(tPrepareBase.Summary(), Equals, `Prepare snap "core20-new" (222) for remodel`)
c.Assert(tPrepareBase.WaitTasks(), HasLen, 1)
c.Assert(tLinkBase.Kind(), Equals, "link-snap")
c.Assert(tLinkBase.Summary(), Equals, `Make snap "core20-new" (222) available to the system during remodel`)
+ c.Assert(tPrepareGadget.Kind(), Equals, "prepare-snap")
+ c.Assert(tPrepareGadget.Summary(), Equals, `Prepare snap "pc-new" (222) for remodel`)
+ c.Assert(tPrepareGadget.WaitTasks(), HasLen, 1)
+ c.Assert(tUpdateAssets.Kind(), Equals, "update-gadget-assets")
+ c.Assert(tUpdateAssets.Summary(), Equals, `Update assets from gadget "pc-new" (222) for remodel`)
+ c.Assert(tUpdateAssets.WaitTasks(), HasLen, 2)
+ c.Assert(tUpdateCmdline.Kind(), Equals, "update-gadget-cmdline")
+ c.Assert(tUpdateCmdline.Summary(), Equals, `Update kernel command line from gadget "pc-new" (222) for remodel`)
+ c.Assert(tUpdateCmdline.WaitTasks(), HasLen, 1)
expectedLabel := now.Format("20060102")
c.Assert(tCreateRecovery.Kind(), Equals, "create-recovery-system")
c.Assert(tCreateRecovery.Summary(), Equals, fmt.Sprintf("Create recovery system with label %q", expectedLabel))
@@ -2268,32 +2291,46 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna
c.Assert(tPrepareKernel.WaitTasks(), HasLen, 0)
c.Assert(tLinkKernel.WaitTasks(), DeepEquals, []*state.Task{
tPrepareKernel,
- tPrepareBase,
+ tPrepareGadget,
tCreateRecovery,
tFinalizeRecovery,
})
+ c.Assert(tUpdateAssetsKernel.WaitTasks(), DeepEquals, []*state.Task{
+ tLinkKernel,
+ })
c.Assert(tPrepareBase.WaitTasks(), DeepEquals, []*state.Task{
tPrepareKernel,
})
c.Assert(tLinkBase.WaitTasks(), DeepEquals, []*state.Task{
tPrepareBase,
- tLinkKernel,
+ tUpdateAssetsKernel,
+ })
+ c.Assert(tPrepareGadget.WaitTasks(), DeepEquals, []*state.Task{
+ tPrepareBase,
+ })
+ c.Assert(tUpdateAssets.WaitTasks(), DeepEquals, []*state.Task{
+ tPrepareGadget,
+ tLinkBase,
+ })
+ c.Assert(tUpdateCmdline.WaitTasks(), DeepEquals, []*state.Task{
+ tUpdateAssets,
})
c.Assert(tCreateRecovery.WaitTasks(), DeepEquals, []*state.Task{
// last snap of the download chain (in this case prepare & link
// for existing snaps)
- tPrepareBase,
+ tPrepareGadget,
})
c.Assert(tFinalizeRecovery.WaitTasks(), DeepEquals, []*state.Task{
// recovery system being created
tCreateRecovery,
// last snap of the download chain (see above)
- tPrepareBase,
+ tPrepareGadget,
})
// setModel waits for everything in the change
c.Assert(tSetModel.WaitTasks(), DeepEquals, []*state.Task{
- tPrepareKernel, tLinkKernel,
+ tPrepareKernel, tLinkKernel, tUpdateAssetsKernel,
tPrepareBase, tLinkBase,
+ tPrepareGadget, tUpdateAssets, tUpdateCmdline,
tCreateRecovery, tFinalizeRecovery,
})
// verify recovery system setup data on appropriate tasks
@@ -2303,18 +2340,21 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna
c.Assert(systemSetupData, DeepEquals, map[string]interface{}{
"label": expectedLabel,
"directory": filepath.Join(boot.InitramfsUbuntuSeedDir, "systems", expectedLabel),
- "snap-setup-tasks": []interface{}{tPrepareKernel.ID(), tPrepareBase.ID()},
+ "snap-setup-tasks": []interface{}{tPrepareKernel.ID(), tPrepareBase.ID(), tPrepareGadget.ID()},
})
}
-func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSnapsDifferentChannelThanNew(c *C) {
+func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseGadgetSnapsInstalledSnapsDifferentChannelThanNew(c *C) {
+ // kernel, base and gadget snaps that are used by the new model are
+ // already installed, but track a different channel from what is set in
+ // the new model
s.state.Lock()
defer s.state.Unlock()
s.state.Set("seeded", true)
s.state.Set("refresh-privacy-key", "some-privacy-key")
restore := devicestate.MockSnapstateUpdateWithDeviceContext(func(st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) {
- c.Assert(strutil.ListContains([]string{"core20-new", "pc-kernel-new"}, name), Equals, true,
+ c.Assert(strutil.ListContains([]string{"core20-new", "pc-kernel-new", "pc-new"}, name), Equals, true,
Commentf("unexpected snap %q", name))
c.Check(flags.Required, Equals, false)
c.Check(flags.NoReRefresh, Equals, true)
@@ -2328,6 +2368,9 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna
if name == "core20-new" {
typ = "base"
rev = snap.R(223)
+ } else if name == "pc-new" {
+ typ = "gadget"
+ rev = snap.R(224)
}
tSwitchChannel.Set("snap-setup", &snapstate.SnapSetup{
SideInfo: &snap.SideInfo{
@@ -2409,11 +2452,13 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna
TrackingChannel: "20/stable",
})
// new gadget and kernel which are already installed
- for _, alreadyInstalledName := range []string{"pc-kernel-new", "core20-new"} {
+ for _, alreadyInstalledName := range []string{"pc-kernel-new", "core20-new", "pc-new"} {
snapYaml := "name: pc-kernel-new\nversion: 1\ntype: kernel\n"
channel := "other/other"
if alreadyInstalledName == "core20-new" {
snapYaml = "name: core20-new\nversion: 1\ntype: base\n"
+ } else if alreadyInstalledName == "pc-new" {
+ snapYaml = "name: pc-new\nversion: 1\ntype: gadget\n"
}
si := &snap.SideInfo{
RealName: alreadyInstalledName,
@@ -2447,8 +2492,8 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna
"default-channel": "20",
},
map[string]interface{}{
- "name": "pc",
- "id": snaptest.AssertedSnapID("pc"),
+ "name": "pc-new",
+ "id": snaptest.AssertedSnapID("pc-new"),
"type": "gadget",
"default-channel": "20",
},
@@ -2466,8 +2511,10 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna
c.Assert(chg.Summary(), Equals, "Refresh model assertion from revision 0 to 1")
tl := chg.Tasks()
- // 2 snaps with (snap switch channel + link snap) + recovery system (2 tasks) + set-model
- c.Assert(tl, HasLen, 2*2+2+1)
+ // 2 snaps with (snap switch channel + link snap) + gadget assets update
+ // for the kernel snap + gadget snap (switch channel, assets update, cmdline update) +
+ // recovery system (2 tasks) + set-model
+ c.Assert(tl, HasLen, 2*2+1+3+2+1)
deviceCtx, err := devicestate.DeviceCtx(s.state, tl[0], nil)
c.Assert(err, IsNil)
@@ -2482,11 +2529,15 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna
// check the tasks
tSwitchChannelKernel := tl[0]
tLinkKernel := tl[1]
- tSwitchChannelBase := tl[2]
- tLinkBase := tl[3]
- tCreateRecovery := tl[4]
- tFinalizeRecovery := tl[5]
- tSetModel := tl[6]
+ tUpdateAssetsFromKernel := tl[2]
+ tSwitchChannelBase := tl[3]
+ tLinkBase := tl[4]
+ tSwitchChannelGadget := tl[5]
+ tUpdateAssetsFromGadget := tl[6]
+ tUpdateCmdlineFromGadget := tl[7]
+ tCreateRecovery := tl[8]
+ tFinalizeRecovery := tl[9]
+ tSetModel := tl[10]
// check the tasks
c.Assert(tSwitchChannelKernel.Kind(), Equals, "switch-snap-channel")
@@ -2494,11 +2545,20 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna
c.Assert(tSwitchChannelKernel.WaitTasks(), HasLen, 0)
c.Assert(tLinkKernel.Kind(), Equals, "link-snap")
c.Assert(tLinkKernel.Summary(), Equals, `Make snap "pc-kernel-new" (222) available to the system during remodel`)
+ c.Assert(tUpdateAssetsFromKernel.Kind(), Equals, "update-gadget-assets")
+ c.Assert(tUpdateAssetsFromKernel.Summary(), Equals, `Update assets from kernel "pc-kernel-new" (222) for remodel`)
c.Assert(tSwitchChannelBase.Kind(), Equals, "switch-snap-channel")
c.Assert(tSwitchChannelBase.Summary(), Equals, `Switch core20-new channel to latest/stable`)
c.Assert(tSwitchChannelBase.WaitTasks(), HasLen, 0)
c.Assert(tLinkBase.Kind(), Equals, "link-snap")
c.Assert(tLinkBase.Summary(), Equals, `Make snap "core20-new" (223) available to the system during remodel`)
+ c.Assert(tSwitchChannelGadget.Kind(), Equals, "switch-snap-channel")
+ c.Assert(tSwitchChannelGadget.Summary(), Equals, `Switch pc-new channel to 20/stable`)
+ c.Assert(tSwitchChannelGadget.WaitTasks(), HasLen, 0)
+ c.Assert(tUpdateAssetsFromGadget.Kind(), Equals, "update-gadget-assets")
+ c.Assert(tUpdateAssetsFromGadget.Summary(), Equals, `Update assets from gadget "pc-new" (224) for remodel`)
+ c.Assert(tUpdateCmdlineFromGadget.Kind(), Equals, "update-gadget-cmdline")
+ c.Assert(tUpdateCmdlineFromGadget.Summary(), Equals, `Update kernel command line from gadget "pc-new" (224) for remodel`)
expectedLabel := now.Format("20060102")
c.Assert(tCreateRecovery.Kind(), Equals, "create-recovery-system")
c.Assert(tCreateRecovery.Summary(), Equals, fmt.Sprintf("Create recovery system with label %q", expectedLabel))
@@ -2521,8 +2581,9 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna
})
// setModel waits for everything in the change
c.Assert(tSetModel.WaitTasks(), DeepEquals, []*state.Task{
- tSwitchChannelKernel, tLinkKernel,
+ tSwitchChannelKernel, tLinkKernel, tUpdateAssetsFromKernel,
tSwitchChannelBase, tLinkBase,
+ tSwitchChannelGadget, tUpdateAssetsFromGadget, tUpdateCmdlineFromGadget,
tCreateRecovery, tFinalizeRecovery,
})
// verify recovery system setup data on appropriate tasks
@@ -2771,6 +2832,9 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna
}
func (s *deviceMgrRemodelSuite) TestRemodelUC20EssentialSnapsTrackingDifferentChannelThanDefaultSameAsNew(c *C) {
+ // essential snaps from new model are already installed and track
+ // channels different than declared in the old model, but already the
+ // same as in the new one
s.state.Lock()
defer s.state.Unlock()
s.state.Set("seeded", true)
@@ -2945,6 +3009,8 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20EssentialSnapsTrackingDifferentCh
}
func (s *deviceMgrRemodelSuite) TestRemodelUC20EssentialSnapsAlreadyInstalledAndLocal(c *C) {
+ // remodel when the essential snaps declared in new model are already
+ // installed, but have a local revision
s.state.Lock()
defer s.state.Unlock()
s.state.Set("seeded", true)
@@ -3115,7 +3181,10 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20EssentialSnapsAlreadyInstalledAnd
})
}
-func (s *deviceMgrRemodelSuite) TestRemodelUC20NoDownloadSimpleChannelSwitch(c *C) {
+func (s *deviceMgrRemodelSuite) TestRemodelUC20BaseNoDownloadSimpleChannelSwitch(c *C) {
+ // remodel when a channel declared in new model carries the same
+ // revision as already installed, so there is no full fledged, but a
+ // simple channel switch
s.state.Lock()
defer s.state.Unlock()
s.state.Set("seeded", true)
diff --git a/overlord/hookstate/ctlcmd/refresh.go b/overlord/hookstate/ctlcmd/refresh.go
index c715776ad3..34867f0c8f 100644
--- a/overlord/hookstate/ctlcmd/refresh.go
+++ b/overlord/hookstate/ctlcmd/refresh.go
@@ -152,6 +152,10 @@ type updateDetails struct {
Restart bool `yaml:"restart"`
}
+type holdDetails struct {
+ Hold string `yaml:"hold"`
+}
+
// refreshCandidate is a subset of refreshCandidate defined by snapstate and
// stored in "refresh-candidates".
type refreshCandidate struct {
@@ -259,10 +263,19 @@ func (c *refreshCommand) hold() error {
// no duration specified, use maximum allowed for this gating snap.
var holdDuration time.Duration
- if err := snapstate.HoldRefresh(st, ctx.InstanceName(), holdDuration, affecting...); err != nil {
+ remaining, err := snapstate.HoldRefresh(st, ctx.InstanceName(), holdDuration, affecting...)
+ if err != nil {
// TODO: let a snap hold again once for 1h.
return err
}
+ var details holdDetails
+ details.Hold = remaining.String()
+
+ out, err := yaml.Marshal(details)
+ if err != nil {
+ return err
+ }
+ c.printf("%s", string(out))
return nil
}
diff --git a/overlord/hookstate/ctlcmd/refresh_test.go b/overlord/hookstate/ctlcmd/refresh_test.go
index 045ca15e4b..72a8c12229 100644
--- a/overlord/hookstate/ctlcmd/refresh_test.go
+++ b/overlord/hookstate/ctlcmd/refresh_test.go
@@ -197,7 +197,7 @@ version: 1
stdout, stderr, err := ctlcmd.Run(mockContext, []string{"refresh", "--hold"}, 0)
c.Assert(err, IsNil)
- c.Check(string(stdout), Equals, "")
+ c.Check(string(stdout), Equals, "hold: 48h0m0s\n")
c.Check(string(stderr), Equals, "")
mockContext.Lock()
@@ -223,7 +223,8 @@ version: 1
`)
// pretend snap foo is held initially
- c.Check(snapstate.HoldRefresh(s.st, "snap1", 0, "foo"), IsNil)
+ _, err = snapstate.HoldRefresh(s.st, "snap1", 0, "foo")
+ c.Check(err, IsNil)
s.st.Unlock()
// sanity check
diff --git a/overlord/hookstate/hooks.go b/overlord/hookstate/hooks.go
index 9f994d3d74..448c83e32b 100644
--- a/overlord/hookstate/hooks.go
+++ b/overlord/hookstate/hooks.go
@@ -241,7 +241,7 @@ func (h *gateAutoRefreshHookHandler) Error(hookErr error) (ignoreHookErr bool, e
// no duration specified, use maximum allowed for this gating snap.
var holdDuration time.Duration
- if err := snapstate.HoldRefresh(st, snapName, holdDuration, affecting...); err != nil {
+ if _, err := snapstate.HoldRefresh(st, snapName, holdDuration, affecting...); err != nil {
// log the original hook error as we either ignore it or error out from
// this handler, in both cases hookErr won't be logged by hook manager.
h.context.Errorf("error: %v (while handling previous hook error: %v)", err, hookErr)
diff --git a/overlord/hookstate/hooks_test.go b/overlord/hookstate/hooks_test.go
index d4efdb29b6..6dd4d642f5 100644
--- a/overlord/hookstate/hooks_test.go
+++ b/overlord/hookstate/hooks_test.go
@@ -242,7 +242,8 @@ func (s *gateAutoRefreshHookSuite) TestGateAutorefreshDefaultProceedUnlocksRunin
defer st.Unlock()
// pretend that snap-a is initially held by itself.
- c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-a"), IsNil)
+ _, err := snapstate.HoldRefresh(st, "snap-a", 0, "snap-a")
+ c.Assert(err, IsNil)
// sanity
checkIsHeld(c, st, "snap-a", "snap-a")
@@ -292,7 +293,8 @@ func (s *gateAutoRefreshHookSuite) TestGateAutorefreshDefaultProceed(c *C) {
defer st.Unlock()
// pretend that snap-b is initially held by snap-a.
- c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b"), IsNil)
+ _, err := snapstate.HoldRefresh(st, "snap-a", 0, "snap-b")
+ c.Assert(err, IsNil)
// sanity
checkIsHeld(c, st, "snap-b", "snap-a")
diff --git a/overlord/managers_test.go b/overlord/managers_test.go
index c80abdff03..d54cf8afe8 100644
--- a/overlord/managers_test.go
+++ b/overlord/managers_test.go
@@ -4294,6 +4294,15 @@ func validateRecoverySystemTasks(c *C, tasks []*state.Task, label string) int {
return i
}
+func validateGadgetSwitchTasks(c *C, tasks []*state.Task, label, rev string) int {
+ var i int
+ c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Update assets from gadget %q (%s) for remodel`, label, rev))
+ i++
+ c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Update kernel command line from gadget %q (%s) for remodel`, label, rev))
+ i++
+ return i
+}
+
// byReadyTime sorts a list of tasks by their "ready" time
type byReadyTime []*state.Task
@@ -5679,14 +5688,18 @@ volumes:
}
type mockUpdater struct {
- onUpdate error
+ updateCalls int
+ onUpdate error
}
func (m *mockUpdater) Backup() error { return nil }
func (m *mockUpdater) Rollback() error { return nil }
-func (m *mockUpdater) Update() error { return m.onUpdate }
+func (m *mockUpdater) Update() error {
+ m.updateCalls++
+ return m.onUpdate
+}
func (s *mgrsSuite) TestRemodelSwitchToDifferentGadget(c *C) {
bloader := bootloadertest.Mock("mock", c.MkDir())
@@ -6185,6 +6198,13 @@ type: gadget
base: core20
`
+const oldPcGadgetSnapYaml = `
+version: 1.0
+name: pc
+type: gadget
+base: core20
+`
+
const pcKernelSnapYaml = `
version: 1.0
name: pc-kernel
@@ -6203,6 +6223,32 @@ name: snapd
type: snapd
`
+const oldPcGadgetYamlForRemodel = `
+volumes:
+ pc:
+ schema: gpt
+ bootloader: grub
+ structure:
+ - name: ubuntu-seed
+ filesystem: vfat
+ type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B
+ role: system-seed
+ size: 100M
+ content:
+ - source: grubx64.efi
+ target: grubx64.efi
+ - name: ubuntu-boot
+ type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B
+ role: system-boot
+ filesystem: ext4
+ size: 100M
+ - name: ubuntu-data
+ role: system-data
+ type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B
+ filesystem: ext4
+ size: 500M
+`
+
const grubBootConfig = "# Snapd-Boot-Config-Edition: 1\n"
var (
@@ -6214,10 +6260,17 @@ var (
{"bootx64.efi", "content"},
{"grubx64.efi", "content"},
}
+ oldPcGadgetFiles = append(pcGadgetFiles, [][]string{
+ {"meta/gadget.yaml", oldPcGadgetYamlForRemodel},
+ // SHA3-384: 7e5c973da86f7398deffd45b9225175da1dd6ae8fcffa1a20219b32bab9f4846da10e823736cd818ceada74d35337c98
+ {"grubx64.efi", "old-gadget-content"},
+ {"cmdline.extra", "foo bar baz"},
+ }...)
pcKernelFiles = [][]string{
{"kernel.efi", "kernel-efi"},
}
snapYamlsForRemodel = map[string]string{
+ "old-pc": oldPcGadgetSnapYaml,
"pc": pcGadgetSnapYaml,
"pc-kernel": pcKernelSnapYaml,
"core20": core20SnapYaml,
@@ -6225,7 +6278,8 @@ var (
"baz": "version: 1.0\nname: baz\nbase: core20",
}
snapFilesForRemodel = map[string][][]string{
- "pc": pcGadgetFiles,
+ "old-pc": oldPcGadgetFiles,
+ "pc": pcGadgetFiles,
// use a different fileset, such that the pc snap with this
// content will have a different digest than the regular pc snap
"pc-rev-33": append(pcGadgetFiles, []string{
@@ -7211,6 +7265,164 @@ func (s *mgrsSuite) TestRemodelUC20DifferentBaseChannel(c *C) {
validateRefreshTasks(c, tasks[i:], "core20", "33", noConfigure)
}
+func (s *mgrsSuite) TestRemodelUC20BackToPreviousGadget(c *C) {
+ s.testRemodelUC20WithRecoverySystemSimpleSetUp(c)
+ c.Assert(os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "proc"), 0755), IsNil)
+ restore := osutil.MockProcCmdline(filepath.Join(dirs.GlobalRootDir, "proc/cmdline"))
+ defer restore()
+ newModel := s.brands.Model("can0nical", "my-model", uc20ModelDefaults, map[string]interface{}{
+ "snaps": []interface{}{
+ map[string]interface{}{
+ "name": "pc-kernel",
+ "id": fakeSnapID("pc-kernel"),
+ "type": "kernel",
+ "default-channel": "20",
+ },
+ map[string]interface{}{
+ "name": "old-pc",
+ "id": fakeSnapID("old-pc"),
+ "type": "gadget",
+ "default-channel": "20/edge",
+ },
+ },
+ "revision": "1",
+ })
+ bl, err := bootloader.Find(boot.InitramfsUbuntuSeedDir, &bootloader.Options{Role: bootloader.RoleRecovery})
+ c.Assert(err, IsNil)
+
+ st := s.o.State()
+ st.Lock()
+ defer st.Unlock()
+
+ a11, err := s.storeSigning.Sign(asserts.SnapDeclarationType, map[string]interface{}{
+ "series": "16",
+ "snap-name": "old-pc",
+ "snap-id": fakeSnapID("old-pc"),
+ "publisher-id": "can0nical",
+ "timestamp": time.Now().Format(time.RFC3339),
+ }, nil, "")
+ c.Assert(err, IsNil)
+ c.Assert(assertstate.Add(st, a11), IsNil)
+ c.Assert(s.storeSigning.Add(a11), IsNil)
+
+ s.makeInstalledSnapInStateForRemodel(c, "old-pc", snap.R(1), "20/edge")
+
+ now := time.Now()
+ expectedLabel := now.Format("20060102")
+
+ updater := &mockUpdater{}
+ restore = gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, rootDir, rollbackDir string, observer gadget.ContentUpdateObserver) (gadget.Updater, error) {
+ // use a mock updater pretends an update was applied
+ return updater, nil
+ })
+ defer restore()
+
+ chg, err := devicestate.Remodel(st, newModel)
+ c.Assert(err, IsNil)
+ st.Unlock()
+ err = s.o.Settle(settleTimeout)
+ st.Lock()
+ c.Assert(err, IsNil, Commentf(s.logbuf.String()))
+ // gadget update has not been applied yet
+ c.Check(updater.updateCalls, Equals, 0)
+
+ // first comes a reboot to the new recovery system
+ c.Check(chg.Status(), Equals, state.DoingStatus, Commentf("remodel change failed: %v", chg.Err()))
+ c.Check(devicestate.RemodelingChange(st), NotNil)
+ restarting, kind := restart.Pending(st)
+ c.Check(restarting, Equals, true)
+ c.Assert(kind, Equals, restart.RestartSystemNow)
+ m, err := boot.ReadModeenv("")
+ c.Assert(err, IsNil)
+ c.Check(m.CurrentRecoverySystems, DeepEquals, []string{"1234", expectedLabel})
+ c.Check(m.GoodRecoverySystems, DeepEquals, []string{"1234"})
+ vars, err := bl.GetBootVars("try_recovery_system", "recovery_system_status")
+ c.Assert(err, IsNil)
+ c.Assert(vars, DeepEquals, map[string]string{
+ "try_recovery_system": expectedLabel,
+ "recovery_system_status": "try",
+ })
+ // simulate successful reboot to recovery and back
+ restart.MockPending(st, restart.RestartUnset)
+ // this would be done by snap-bootstrap in initramfs
+ err = bl.SetBootVars(map[string]string{
+ "try_recovery_system": expectedLabel,
+ "recovery_system_status": "tried",
+ })
+ c.Assert(err, IsNil)
+ // reset, so that after-reboot handling of tried system is executed
+ s.o.DeviceManager().ResetToPostBootState()
+ st.Unlock()
+ err = s.o.DeviceManager().Ensure()
+ st.Lock()
+ c.Assert(err, IsNil)
+
+ st.Unlock()
+ err = s.o.Settle(settleTimeout)
+ st.Lock()
+ c.Assert(err, IsNil)
+ // update has been called for all 3 structures because of the remodel
+ // policy (there is no content bump, so there would be no updates
+ // otherwise)
+ c.Check(updater.updateCalls, Equals, 3)
+ // a reboot was requested, as mock updated were applied
+ restarting, kind = restart.Pending(st)
+ c.Check(restarting, Equals, true)
+ c.Assert(kind, Equals, restart.RestartSystem)
+
+ m, err = boot.ReadModeenv("")
+ c.Assert(err, IsNil)
+ c.Check([]string(m.CurrentKernelCommandLines), DeepEquals, []string{
+ "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1",
+ "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1 foo bar baz",
+ })
+
+ // pretend we have the right command line
+ c.Assert(ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "proc/cmdline"),
+ []byte("snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1 foo bar baz"), 0444),
+ IsNil)
+
+ // run post boot code again
+ s.o.DeviceManager().ResetToPostBootState()
+ st.Unlock()
+ err = s.o.DeviceManager().Ensure()
+ st.Lock()
+ c.Assert(err, IsNil)
+
+ // verify command lines again
+ m, err = boot.ReadModeenv("")
+ c.Assert(err, IsNil)
+ c.Check([]string(m.CurrentKernelCommandLines), DeepEquals, []string{
+ "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1 foo bar baz",
+ })
+
+ c.Check(chg.Status(), Equals, state.DoneStatus, Commentf("remodel change failed: %v", chg.Err()))
+
+ var snapst snapstate.SnapState
+ err = snapstate.Get(st, "old-pc", &snapst)
+ c.Assert(err, IsNil)
+ // and the gadget tracking channel is the same as in the model
+ c.Check(snapst.TrackingChannel, Equals, "20/edge")
+
+ // ensure sorting is correct
+ tasks := chg.Tasks()
+ sort.Sort(byReadyTime(tasks))
+
+ var i int
+
+ // prepare first
+ c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Prepare snap "old-pc" (1) for remodel`))
+ i++
+ // then recovery system
+ i += validateRecoverySystemTasks(c, tasks[i:], expectedLabel)
+ // then gadget switch with update of assets and kernel command line
+ i += validateGadgetSwitchTasks(c, tasks[i:], "old-pc", "1")
+ // finally new model assertion
+ c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Set new model assertion`))
+ i++
+ c.Check(i, Equals, len(tasks))
+}
+
func (s *mgrsSuite) TestCheckRefreshFailureWithConcurrentRemoveOfConnectedSnap(c *C) {
hookMgr := s.o.HookManager()
c.Assert(hookMgr, NotNil)
diff --git a/overlord/snapstate/autorefresh_gating.go b/overlord/snapstate/autorefresh_gating.go
index 4ad5cf44e6..d94e5319d2 100644
--- a/overlord/snapstate/autorefresh_gating.go
+++ b/overlord/snapstate/autorefresh_gating.go
@@ -144,16 +144,21 @@ func holdDurationLeft(now time.Time, lastRefresh, firstHeld time.Time, maxDurati
// HoldRefresh marks affectingSnaps as held for refresh for up to holdTime.
// HoldTime of zero denotes maximum allowed hold time.
-// Holding may fail for only some snaps in which case HoldError is returned and
-// it contains the details of failed ones.
-func HoldRefresh(st *state.State, gatingSnap string, holdDuration time.Duration, affectingSnaps ...string) error {
+// Holding fails if not all snaps can be held, in that case HoldError is returned
+// and it contains the details of snaps that prevented holding. On success the
+// function returns the remaining hold time. The remaining hold time is the
+// minimum of the remaining hold time for all affecting snaps.
+func HoldRefresh(st *state.State, gatingSnap string, holdDuration time.Duration, affectingSnaps ...string) (time.Duration, error) {
gating, err := refreshGating(st)
if err != nil {
- return err
+ return 0, err
}
herr := &HoldError{
SnapsInError: make(map[string]HoldDurationError),
}
+
+ var durationMin time.Duration
+
now := timeNow()
for _, heldSnap := range affectingSnaps {
hold, ok := gating[heldSnap][gatingSnap]
@@ -165,7 +170,7 @@ func HoldRefresh(st *state.State, gatingSnap string, holdDuration time.Duration,
lastRefreshTime, err := lastRefreshed(st, heldSnap)
if err != nil {
- return err
+ return 0, err
}
mp := maxPostponement - maxPostponementBuffer
@@ -213,6 +218,11 @@ func HoldRefresh(st *state.State, gatingSnap string, holdDuration time.Duration,
gating[heldSnap] = make(map[string]*holdState)
}
gating[heldSnap][gatingSnap] = hold
+
+ // note, left is guaranteed to be > 0 at this point
+ if durationMin == 0 || left < durationMin {
+ durationMin = left
+ }
}
if len(herr.SnapsInError) > 0 {
@@ -228,9 +238,9 @@ func HoldRefresh(st *state.State, gatingSnap string, holdDuration time.Duration,
}
st.Set("snaps-hold", gating)
if len(herr.SnapsInError) > 0 {
- return herr
+ return 0, herr
}
- return nil
+ return durationMin, nil
}
// ProceedWithRefresh unblocks all snaps held by gatingSnap for refresh. This
diff --git a/overlord/snapstate/autorefresh_gating_test.go b/overlord/snapstate/autorefresh_gating_test.go
index 1541b7eca3..ef38539d3b 100644
--- a/overlord/snapstate/autorefresh_gating_test.go
+++ b/overlord/snapstate/autorefresh_gating_test.go
@@ -353,12 +353,16 @@ func (s *autorefreshGatingSuite) TestHoldRefreshHelper(c *C) {
mockLastRefreshed(c, st, "2021-05-09T10:00:00Z", "snap-a", "snap-b", "snap-c", "snap-d", "snap-e", "snap-f")
- c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c"), IsNil)
+ _, err := snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c")
+ c.Assert(err, IsNil)
// this could be merged with the above HoldRefresh call, but it's fine if
// done separately too.
- c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-e"), IsNil)
- c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-e"), IsNil)
- c.Assert(snapstate.HoldRefresh(st, "snap-f", 0, "snap-f"), IsNil)
+ _, err = snapstate.HoldRefresh(st, "snap-a", 0, "snap-e")
+ c.Assert(err, IsNil)
+ _, err = snapstate.HoldRefresh(st, "snap-d", 0, "snap-e")
+ c.Assert(err, IsNil)
+ _, err = snapstate.HoldRefresh(st, "snap-f", 0, "snap-f")
+ c.Assert(err, IsNil)
var gating map[string]map[string]*snapstate.HoldState
c.Assert(st.Get("snaps-hold", &gating), IsNil)
@@ -381,6 +385,65 @@ func (s *autorefreshGatingSuite) TestHoldRefreshHelper(c *C) {
})
}
+func (s *autorefreshGatingSuite) TestHoldRefreshReturnsMinimumHoldTime(c *C) {
+ st := s.state
+ st.Lock()
+ defer st.Unlock()
+
+ now := "2021-05-10T10:00:00Z"
+ restore := snapstate.MockTimeNow(func() time.Time {
+ t, err := time.Parse(time.RFC3339, now)
+ c.Assert(err, IsNil)
+ return t
+ })
+ defer restore()
+
+ mockInstalledSnap(c, st, snapAyaml, false)
+ mockInstalledSnap(c, st, snapByaml, false)
+ mockInstalledSnap(c, st, snapCyaml, false)
+ mockInstalledSnap(c, st, snapDyaml, false)
+ mockInstalledSnap(c, st, snapEyaml, false)
+ mockInstalledSnap(c, st, snapFyaml, false)
+
+ mockLastRefreshed(c, st, "2021-05-09T10:00:00Z", "snap-a", "snap-b", "snap-c", "snap-d", "snap-e", "snap-f")
+
+ // only holding self: max postponement - buffer time returned
+ rem, err := snapstate.HoldRefresh(st, "snap-a", 0, "snap-a")
+ c.Assert(err, IsNil)
+ c.Check(rem.String(), Equals, "2136h0m0s")
+
+ // holding self and some other snaps, max hold time of holding other snaps returned.
+ rem, err = snapstate.HoldRefresh(st, "snap-a", 0, "snap-a", "snap-b", "snap-c", "snap-e")
+ c.Assert(err, IsNil)
+ c.Check(rem.String(), Equals, "48h0m0s")
+
+ // advance time
+ now = "2021-05-11T12:00:00Z"
+ rem, err = snapstate.HoldRefresh(st, "snap-a", 0, "snap-a", "snap-b", "snap-c", "snap-e")
+ c.Assert(err, IsNil)
+ // it's now less due to previous hold
+ c.Check(rem.String(), Equals, "22h0m0s")
+
+ var gating map[string]map[string]*snapstate.HoldState
+ c.Assert(st.Get("snaps-hold", &gating), IsNil)
+ c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{
+ "snap-b": {
+ // holding of other snaps for maxOtherHoldDuration (48h)
+ "snap-a": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"),
+ },
+ "snap-c": {
+ "snap-a": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"),
+ },
+ "snap-e": {
+ "snap-a": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"),
+ },
+ "snap-a": {
+ // holding self set for maxPostponement minus 1 day due to last refresh.
+ "snap-a": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-08-07T10:00:00Z"),
+ },
+ })
+}
+
func (s *autorefreshGatingSuite) TestHoldRefreshHelperMultipleTimes(c *C) {
st := s.state
st.Lock()
@@ -402,7 +465,9 @@ func (s *autorefreshGatingSuite) TestHoldRefreshHelperMultipleTimes(c *C) {
// hold it for just a bit (10h) initially
hold := time.Hour * 10
- c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil)
+ rem, err := snapstate.HoldRefresh(st, "snap-b", hold, "snap-a")
+ c.Assert(err, IsNil)
+ c.Check(rem.String(), Equals, "48h0m0s")
var gating map[string]map[string]*snapstate.HoldState
c.Assert(st.Get("snaps-hold", &gating), IsNil)
c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{
@@ -413,7 +478,9 @@ func (s *autorefreshGatingSuite) TestHoldRefreshHelperMultipleTimes(c *C) {
// holding for a shorter time is fine too
hold = time.Hour * 5
- c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil)
+ rem, err = snapstate.HoldRefresh(st, "snap-b", hold, "snap-a")
+ c.Assert(err, IsNil)
+ c.Check(rem.String(), Equals, "48h0m0s")
c.Assert(st.Get("snaps-hold", &gating), IsNil)
c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{
"snap-a": {
@@ -428,7 +495,9 @@ func (s *autorefreshGatingSuite) TestHoldRefreshHelperMultipleTimes(c *C) {
// default hold time requested
hold = 0
- c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil)
+ rem, err = snapstate.HoldRefresh(st, "snap-b", hold, "snap-a")
+ c.Assert(err, IsNil)
+ c.Check(rem.String(), Equals, "26h0m0s")
c.Assert(st.Get("snaps-hold", &gating), IsNil)
c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{
"snap-a": {
@@ -459,7 +528,9 @@ func (s *autorefreshGatingSuite) TestHoldRefreshHelperCloseToMaxPostponement(c *
// request default hold time
var hold time.Duration
- c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil)
+ rem, err := snapstate.HoldRefresh(st, "snap-b", hold, "snap-a")
+ c.Assert(err, IsNil)
+ c.Check(rem.String(), Equals, "24h0m0s")
var gating map[string]map[string]*snapstate.HoldState
c.Assert(st.Get("snaps-hold", &gating), IsNil)
@@ -485,11 +556,13 @@ func (s *autorefreshGatingSuite) TestHoldRefreshExplicitHoldTime(c *C) {
hold := time.Hour * 24 * 3
// holding self for 3 days
- c.Assert(snapstate.HoldRefresh(st, "snap-a", hold, "snap-a"), IsNil)
+ _, err := snapstate.HoldRefresh(st, "snap-a", hold, "snap-a")
+ c.Assert(err, IsNil)
// snap-b holds snap-a for 1 day
hold = time.Hour * 24
- c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil)
+ _, err = snapstate.HoldRefresh(st, "snap-b", hold, "snap-a")
+ c.Assert(err, IsNil)
var gating map[string]map[string]*snapstate.HoldState
c.Assert(st.Get("snaps-hold", &gating), IsNil)
@@ -521,11 +594,12 @@ func (s *autorefreshGatingSuite) TestHoldRefreshHelperErrors(c *C) {
// holding itself
hold := time.Hour * 24 * 96
- c.Assert(snapstate.HoldRefresh(st, "snap-a", hold, "snap-a"), ErrorMatches, `cannot hold some snaps:\n - requested holding duration for snap "snap-a" of 2304h0m0s by snap "snap-a" exceeds maximum holding time`)
+ _, err := snapstate.HoldRefresh(st, "snap-a", hold, "snap-a")
+ c.Assert(err, ErrorMatches, `cannot hold some snaps:\n - requested holding duration for snap "snap-a" of 2304h0m0s by snap "snap-a" exceeds maximum holding time`)
// holding other snap
hold = time.Hour * 49
- err := snapstate.HoldRefresh(st, "snap-a", hold, "snap-b")
+ _, err = snapstate.HoldRefresh(st, "snap-a", hold, "snap-b")
c.Check(err, ErrorMatches, `cannot hold some snaps:\n - requested holding duration for snap "snap-b" of 49h0m0s by snap "snap-a" exceeds maximum holding time`)
herr, ok := err.(*snapstate.HoldError)
c.Assert(ok, Equals, true)
@@ -538,17 +612,21 @@ func (s *autorefreshGatingSuite) TestHoldRefreshHelperErrors(c *C) {
// hold for maximum allowed for other snaps
hold = time.Hour * 48
- c.Assert(snapstate.HoldRefresh(st, "snap-a", hold, "snap-b"), IsNil)
+ _, err = snapstate.HoldRefresh(st, "snap-a", hold, "snap-b")
+ c.Assert(err, IsNil)
// 2 days passed since it was first held
now = "2021-05-12T10:00:00Z"
hold = time.Minute * 2
- c.Assert(snapstate.HoldRefresh(st, "snap-a", hold, "snap-b"), ErrorMatches, `cannot hold some snaps:\n - snap "snap-a" cannot hold snap "snap-b" anymore, maximum refresh postponement exceeded`)
+ _, err = snapstate.HoldRefresh(st, "snap-a", hold, "snap-b")
+ c.Assert(err, ErrorMatches, `cannot hold some snaps:\n - snap "snap-a" cannot hold snap "snap-b" anymore, maximum refresh postponement exceeded`)
// refreshed long time ago (> maxPostponement)
mockLastRefreshed(c, st, "2021-01-01T10:00:00Z", "snap-b")
hold = time.Hour * 2
- c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-b"), ErrorMatches, `cannot hold some snaps:\n - snap "snap-b" cannot hold snap "snap-b" anymore, maximum refresh postponement exceeded`)
- c.Assert(snapstate.HoldRefresh(st, "snap-b", 0, "snap-b"), ErrorMatches, `cannot hold some snaps:\n - snap "snap-b" cannot hold snap "snap-b" anymore, maximum refresh postponement exceeded`)
+ _, err = snapstate.HoldRefresh(st, "snap-b", hold, "snap-b")
+ c.Assert(err, ErrorMatches, `cannot hold some snaps:\n - snap "snap-b" cannot hold snap "snap-b" anymore, maximum refresh postponement exceeded`)
+ _, err = snapstate.HoldRefresh(st, "snap-b", 0, "snap-b")
+ c.Assert(err, ErrorMatches, `cannot hold some snaps:\n - snap "snap-b" cannot hold snap "snap-b" anymore, maximum refresh postponement exceeded`)
}
func (s *autorefreshGatingSuite) TestHoldAndProceedWithRefreshHelper(c *C) {
@@ -575,10 +653,13 @@ func (s *autorefreshGatingSuite) TestHoldAndProceedWithRefreshHelper(c *C) {
c.Assert(err, IsNil)
c.Check(held, IsNil)
- c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c"), IsNil)
- c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-c"), IsNil)
+ _, err = snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c")
+ c.Assert(err, IsNil)
+ _, err = snapstate.HoldRefresh(st, "snap-d", 0, "snap-c")
+ c.Assert(err, IsNil)
// holding self
- c.Assert(snapstate.HoldRefresh(st, "snap-d", time.Hour*24*4, "snap-d"), IsNil)
+ _, err = snapstate.HoldRefresh(st, "snap-d", time.Hour*24*4, "snap-d")
+ c.Assert(err, IsNil)
held, err = snapstate.HeldSnaps(st)
c.Assert(err, IsNil)
@@ -622,16 +703,20 @@ func (s *autorefreshGatingSuite) TestDontHoldSomeSnapsIfSomeFail(c *C) {
defer restore()
// snap-b, base-snap-b get refreshed and affect snap-b (gating snap)
- c.Assert(snapstate.HoldRefresh(st, "snap-b", 0, "snap-b", "base-snap-b"), IsNil)
+ _, err := snapstate.HoldRefresh(st, "snap-b", 0, "snap-b", "base-snap-b")
+ c.Assert(err, IsNil)
// unrealted snap-d gets refreshed and holds itself
- c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-d"), IsNil)
+ _, err = snapstate.HoldRefresh(st, "snap-d", 0, "snap-d")
+ c.Assert(err, IsNil)
// advance time by 49h
now = "2021-05-03T11:00:00Z"
// snap-b, base-snap-b and snap-c get refreshed and snap-a (gating snap) wants to hold them
- c.Assert(snapstate.HoldRefresh(st, "snap-b", 0, "snap-b", "base-snap-b", "snap-c"), ErrorMatches, `cannot hold some snaps:\n - snap "snap-b" cannot hold snap "base-snap-b" anymore, maximum refresh postponement exceeded`)
+ _, err = snapstate.HoldRefresh(st, "snap-b", 0, "snap-b", "base-snap-b", "snap-c")
+ c.Assert(err, ErrorMatches, `cannot hold some snaps:\n - snap "snap-b" cannot hold snap "base-snap-b" anymore, maximum refresh postponement exceeded`)
// snap-bb (gating snap) wants to hold base-snap-b as well and succeeds since it didn't exceed its holding time yet
- c.Assert(snapstate.HoldRefresh(st, "snap-bb", 0, "base-snap-b"), IsNil)
+ _, err = snapstate.HoldRefresh(st, "snap-bb", 0, "base-snap-b")
+ c.Assert(err, IsNil)
held, err := snapstate.HeldSnaps(st)
c.Assert(err, IsNil)
@@ -660,8 +745,10 @@ func (s *autorefreshGatingSuite) TestPruneGatingHelper(c *C) {
mockInstalledSnap(c, st, snapCyaml, false)
mockInstalledSnap(c, st, snapDyaml, false)
- c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c"), IsNil)
- c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-d", "snap-c"), IsNil)
+ _, err := snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c")
+ c.Assert(err, IsNil)
+ _, err = snapstate.HoldRefresh(st, "snap-d", 0, "snap-d", "snap-c")
+ c.Assert(err, IsNil)
// sanity
held, err := snapstate.HeldSnaps(st)
c.Assert(err, IsNil)
@@ -731,8 +818,10 @@ func (s *autorefreshGatingSuite) TestResetGatingForRefreshedHelper(c *C) {
mockInstalledSnap(c, st, snapCyaml, false)
mockInstalledSnap(c, st, snapDyaml, false)
- c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c"), IsNil)
- c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-d", "snap-c"), IsNil)
+ _, err := snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c")
+ c.Assert(err, IsNil)
+ _, err = snapstate.HoldRefresh(st, "snap-d", 0, "snap-d", "snap-c")
+ c.Assert(err, IsNil)
c.Assert(snapstate.ResetGatingForRefreshed(st, "snap-b", "snap-c"), IsNil)
var gating map[string]map[string]*snapstate.HoldState
@@ -760,9 +849,11 @@ func (s *autorefreshGatingSuite) TestPruneSnapsHold(c *C) {
mockInstalledSnap(c, st, snapDyaml, false)
// snap-a is holding itself and 3 other snaps
- c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-a", "snap-b", "snap-c", "snap-d"), IsNil)
+ _, err := snapstate.HoldRefresh(st, "snap-a", 0, "snap-a", "snap-b", "snap-c", "snap-d")
+ c.Assert(err, IsNil)
// in addition, snap-c is held by snap-d.
- c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-c"), IsNil)
+ _, err = snapstate.HoldRefresh(st, "snap-d", 0, "snap-c")
+ c.Assert(err, IsNil)
// sanity check
held, err := snapstate.HeldSnaps(st)
@@ -1283,7 +1374,8 @@ func (s *autorefreshGatingSuite) TestAutoRefreshPhase1(c *C) {
defer restore()
// pretend some snaps are held
- c.Assert(snapstate.HoldRefresh(st, "gating-snap", 0, "snap-a", "snap-d"), IsNil)
+ _, err := snapstate.HoldRefresh(st, "gating-snap", 0, "snap-a", "snap-d")
+ c.Assert(err, IsNil)
// sanity check
heldSnaps, err := snapstate.HeldSnaps(st)
c.Assert(err, IsNil)
@@ -1799,7 +1891,8 @@ func (s *snapmgrTestSuite) TestAutoRefreshPhase2Held(c *C) {
chg := s.testAutoRefreshPhase2(c, nil, func(snapName string) {
if snapName == "snap-b" {
// pretend than snap-b calls snapctl --hold to hold refresh of base-snap-b
- c.Assert(snapstate.HoldRefresh(s.state, "snap-b", 0, "base-snap-b"), IsNil)
+ _, err := snapstate.HoldRefresh(s.state, "snap-b", 0, "base-snap-b")
+ c.Assert(err, IsNil)
}
}, expected)
@@ -1852,8 +1945,10 @@ func (s *snapmgrTestSuite) TestAutoRefreshPhase2Proceed(c *C) {
s.testAutoRefreshPhase2(c, func() {
// pretend that snap-a and base-snap-b are initially held
- c.Assert(snapstate.HoldRefresh(s.state, "snap-a", 0, "snap-a"), IsNil)
- c.Assert(snapstate.HoldRefresh(s.state, "snap-b", 0, "base-snap-b"), IsNil)
+ _, err := snapstate.HoldRefresh(s.state, "snap-a", 0, "snap-a")
+ c.Assert(err, IsNil)
+ _, err = snapstate.HoldRefresh(s.state, "snap-b", 0, "base-snap-b")
+ c.Assert(err, IsNil)
}, func(snapName string) {
if snapName == "snap-a" {
// pretend than snap-a calls snapctl --proceed
@@ -1883,10 +1978,12 @@ func (s *snapmgrTestSuite) TestAutoRefreshPhase2AllHeld(c *C) {
switch snapName {
case "snap-b":
// pretend that snap-b calls snapctl --hold to hold refresh of base-snap-b
- c.Assert(snapstate.HoldRefresh(s.state, "snap-b", 0, "base-snap-b"), IsNil)
+ _, err := snapstate.HoldRefresh(s.state, "snap-b", 0, "base-snap-b")
+ c.Assert(err, IsNil)
case "snap-a":
// pretend that snap-a calls snapctl --hold to hold itself
- c.Assert(snapstate.HoldRefresh(s.state, "snap-a", 0, "snap-a"), IsNil)
+ _, err := snapstate.HoldRefresh(s.state, "snap-a", 0, "snap-a")
+ c.Assert(err, IsNil)
default:
c.Fatalf("unexpected snap %q", snapName)
}
@@ -2309,8 +2406,10 @@ func (s *autorefreshGatingSuite) TestAutoRefreshForGatingSnap(c *C) {
defer restore()
// pretend some snaps are held
- c.Assert(snapstate.HoldRefresh(st, "snap-b", 0, "base-snap-b"), IsNil)
- c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-a"), IsNil)
+ _, err := snapstate.HoldRefresh(st, "snap-b", 0, "base-snap-b")
+ c.Assert(err, IsNil)
+ _, err = snapstate.HoldRefresh(st, "snap-a", 0, "snap-a")
+ c.Assert(err, IsNil)
lastRefreshTime := time.Now().Add(-99 * time.Hour)
st.Set("last-refresh", lastRefreshTime)
@@ -2419,7 +2518,8 @@ func (s *autorefreshGatingSuite) TestAutoRefreshForGatingSnapMoreAffectedSnaps(c
defer restore()
// pretend snap-b holds base-snap-b.
- c.Assert(snapstate.HoldRefresh(st, "snap-b", 0, "base-snap-b"), IsNil)
+ _, err := snapstate.HoldRefresh(st, "snap-b", 0, "base-snap-b")
+ c.Assert(err, IsNil)
// pretend that snap-b triggers auto-refresh (by calling snapctl refresh --proceed)
c.Assert(snapstate.AutoRefreshForGatingSnap(st, "snap-b"), IsNil)
@@ -2519,8 +2619,10 @@ func (s *autorefreshGatingSuite) TestAutoRefreshForGatingSnapNoCandidatesAnymore
defer restore()
// pretend some snaps are held
- c.Assert(snapstate.HoldRefresh(st, "snap-b", 0, "base-snap-b"), IsNil)
- c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-a"), IsNil)
+ _, err := snapstate.HoldRefresh(st, "snap-b", 0, "base-snap-b")
+ c.Assert(err, IsNil)
+ _, err = snapstate.HoldRefresh(st, "snap-a", 0, "snap-a")
+ c.Assert(err, IsNil)
// pretend that snap-b triggers auto-refresh (by calling snapctl refresh --proceed)
c.Assert(snapstate.AutoRefreshForGatingSnap(st, "snap-b"), IsNil)
diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go
index f541d55816..56f8734cf4 100644
--- a/overlord/snapstate/snapstate.go
+++ b/overlord/snapstate/snapstate.go
@@ -2175,7 +2175,8 @@ func autoRefreshPhase2(ctx context.Context, st *state.State, updates []*refreshC
return tasksets, nil
}
-// LinkNewBaseOrKernel will create prepare/link-snap tasks for a remodel
+// LinkNewBaseOrKernel creates a new task set with prepare/link-snap, and
+// additionally update-gadget-assets for the kernel snap, tasks for a remodel.
func LinkNewBaseOrKernel(st *state.State, name string) (*state.TaskSet, error) {
var snapst SnapState
err := Get(st, name, &snapst)
@@ -2217,36 +2218,131 @@ func LinkNewBaseOrKernel(st *state.State, name string) (*state.TaskSet, error) {
linkSnap := st.NewTask("link-snap", fmt.Sprintf(i18n.G("Make snap %q (%s) available to the system during remodel"), snapsup.InstanceName(), snapst.Current))
linkSnap.Set("snap-setup-task", prepareSnap.ID())
linkSnap.WaitFor(prepareSnap)
-
// we need this for remodel
ts := state.NewTaskSet(prepareSnap, linkSnap)
ts.MarkEdge(prepareSnap, DownloadAndChecksDoneEdge)
+ if info.Type() == snap.TypeKernel {
+ // kernel snaps can carry boot assets
+ gadgetUpdate := st.NewTask("update-gadget-assets", fmt.Sprintf(i18n.G("Update assets from %s %q (%s) for remodel"), snapsup.Type, snapsup.InstanceName(), snapst.Current))
+ gadgetUpdate.Set("snap-setup-task", prepareSnap.ID())
+ gadgetUpdate.WaitFor(linkSnap)
+ ts.AddTask(gadgetUpdate)
+ }
return ts, nil
}
-func AddLinkNewBaseOrKernel(st *state.State, ts *state.TaskSet) (*state.TaskSet, error) {
- var snapSetupTaskID string
+func findSnapSetupTask(tasks []*state.Task) (*state.Task, *SnapSetup, error) {
var snapsup SnapSetup
- allTasks := ts.Tasks()
- for _, tsk := range allTasks {
+ for _, tsk := range tasks {
if tsk.Has("snap-setup") {
- snapSetupTaskID = tsk.ID()
if err := tsk.Get("snap-setup", &snapsup); err != nil {
- return nil, err
+ return nil, nil, err
}
- break
+ return tsk, &snapsup, nil
}
}
- if snapSetupTaskID == "" {
+ return nil, nil, nil
+}
+
+// AddLinkNewBaseOrKernel creates the same tasks as LinkNewBaseOrKernel but adds
+// them to the provided task set.
+func AddLinkNewBaseOrKernel(st *state.State, ts *state.TaskSet) (*state.TaskSet, error) {
+ allTasks := ts.Tasks()
+ snapSetupTask, snapsup, err := findSnapSetupTask(allTasks)
+ if err != nil {
+ return nil, err
+ }
+ if snapSetupTask == nil {
return nil, fmt.Errorf("internal error: cannot identify task with snap-setup")
}
-
linkSnap := st.NewTask("link-snap",
fmt.Sprintf(i18n.G("Make snap %q (%s) available to the system during remodel"), snapsup.InstanceName(), snapsup.SideInfo.Revision))
- linkSnap.Set("snap-setup-task", snapSetupTaskID)
+ linkSnap.Set("snap-setup-task", snapSetupTask.ID())
// wait for the last task in existing set
linkSnap.WaitFor(allTasks[len(allTasks)-1])
ts.AddTask(linkSnap)
+ if snapsup.Type == snap.TypeKernel {
+ // kernel snaps can carry boot assets
+ gadgetUpdate := st.NewTask("update-gadget-assets", fmt.Sprintf(i18n.G("Update assets from %s %q (%s) for remodel"), snapsup.Type, snapsup.InstanceName(), snapsup.Revision()))
+ gadgetUpdate.Set("snap-setup-task", snapSetupTask.ID())
+ // wait for the last task in existing set
+ gadgetUpdate.WaitFor(linkSnap)
+ ts.AddTask(gadgetUpdate)
+ }
+ return ts, nil
+}
+
+// LinkNewBaseOrKernel creates a new task set with
+// prepare/update-gadget-assets/update-gadget-cmdline tasks for the gadget snap,
+// for remodel.
+func SwitchToNewGadget(st *state.State, name string) (*state.TaskSet, error) {
+ var snapst SnapState
+ err := Get(st, name, &snapst)
+ if err == state.ErrNoState {
+ return nil, &snap.NotInstalledError{Snap: name}
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ if err := CheckChangeConflict(st, name, nil); err != nil {
+ return nil, err
+ }
+
+ info, err := snapst.CurrentInfo()
+ if err != nil {
+ return nil, err
+ }
+
+ if info.Type() != snap.TypeGadget {
+ return nil, fmt.Errorf("internal error: cannot link type %v", info.Type())
+ }
+
+ snapsup := &SnapSetup{
+ SideInfo: snapst.CurrentSideInfo(),
+ Flags: snapst.Flags.ForSnapSetup(),
+ Type: info.Type(),
+ PlugsOnly: len(info.Slots) == 0,
+ InstanceKey: snapst.InstanceKey,
+ }
+
+ prepareSnap := st.NewTask("prepare-snap", fmt.Sprintf(i18n.G("Prepare snap %q (%s) for remodel"), snapsup.InstanceName(), snapst.Current))
+ prepareSnap.Set("snap-setup", &snapsup)
+
+ gadgetUpdate := st.NewTask("update-gadget-assets", fmt.Sprintf(i18n.G("Update assets from %s %q (%s) for remodel"), snapsup.Type, snapsup.InstanceName(), snapst.Current))
+ gadgetUpdate.WaitFor(prepareSnap)
+ gadgetUpdate.Set("snap-setup-task", prepareSnap.ID())
+ gadgetCmdline := st.NewTask("update-gadget-cmdline", fmt.Sprintf(i18n.G("Update kernel command line from %s %q (%s) for remodel"), snapsup.Type, snapsup.InstanceName(), snapst.Current))
+ gadgetCmdline.WaitFor(gadgetUpdate)
+ gadgetCmdline.Set("snap-setup-task", prepareSnap.ID())
+
+ // we need this for remodel
+ ts := state.NewTaskSet(prepareSnap, gadgetUpdate, gadgetCmdline)
+ ts.MarkEdge(prepareSnap, DownloadAndChecksDoneEdge)
+ return ts, nil
+}
+
+// AddGadgetAssetsTasks creates the same tasks as SwitchToNewGadget but adds
+// them to the provided task set.
+func AddGadgetAssetsTasks(st *state.State, ts *state.TaskSet) (*state.TaskSet, error) {
+ allTasks := ts.Tasks()
+ snapSetupTask, snapsup, err := findSnapSetupTask(allTasks)
+ if err != nil {
+ return nil, err
+ }
+ if snapSetupTask == nil {
+ return nil, fmt.Errorf("internal error: cannot identify task with snap-setup")
+ }
+ gadgetUpdate := st.NewTask("update-gadget-assets", fmt.Sprintf(i18n.G("Update assets from %s %q (%s) for remodel"), snapsup.Type, snapsup.InstanceName(), snapsup.Revision()))
+ gadgetUpdate.Set("snap-setup-task", snapSetupTask.ID())
+ // wait for the last task in existing set
+ gadgetUpdate.WaitFor(allTasks[len(allTasks)-1])
+ ts.AddTask(gadgetUpdate)
+ // gadget snaps can carry kernel command line fragments
+ gadgetCmdline := st.NewTask("update-gadget-cmdline", fmt.Sprintf(i18n.G("Update kernel command line from %s %q (%s) for remodel"), snapsup.Type, snapsup.InstanceName(), snapsup.Revision()))
+ gadgetCmdline.Set("snap-setup-task", snapSetupTask.ID())
+ gadgetCmdline.WaitFor(gadgetUpdate)
+ ts.AddTask(gadgetCmdline)
return ts, nil
}
diff --git a/overlord/snapstate/snapstate_remove_test.go b/overlord/snapstate/snapstate_remove_test.go
index 05c024f4ff..fb3176ef35 100644
--- a/overlord/snapstate/snapstate_remove_test.go
+++ b/overlord/snapstate/snapstate_remove_test.go
@@ -1642,8 +1642,10 @@ func (s *snapmgrTestSuite) TestRemovePrunesRefreshGatingDataOnLastRevision(c *C)
}
st.Set("refresh-candidates", rc)
- c.Assert(snapstate.HoldRefresh(st, "some-snap", 0, "foo-snap"), IsNil)
- c.Assert(snapstate.HoldRefresh(st, "another-snap", 0, "some-snap"), IsNil)
+ _, err := snapstate.HoldRefresh(st, "some-snap", 0, "foo-snap")
+ c.Assert(err, IsNil)
+ _, err = snapstate.HoldRefresh(st, "another-snap", 0, "some-snap")
+ c.Assert(err, IsNil)
held, err := snapstate.HeldSnaps(st)
c.Assert(err, IsNil)
@@ -1699,7 +1701,8 @@ func (s *snapmgrTestSuite) TestRemoveKeepsGatingDataIfNotLastRevision(c *C) {
rc := map[string]*snapstate.RefreshCandidate{"some-snap": {}}
st.Set("refresh-candidates", rc)
- c.Assert(snapstate.HoldRefresh(st, "some-snap", 0, "some-snap"), IsNil)
+ _, err := snapstate.HoldRefresh(st, "some-snap", 0, "some-snap")
+ c.Assert(err, IsNil)
held, err := snapstate.HeldSnaps(st)
c.Assert(err, IsNil)
diff --git a/overlord/snapstate/snapstate_update_test.go b/overlord/snapstate/snapstate_update_test.go
index 1421412ab9..0987d71f14 100644
--- a/overlord/snapstate/snapstate_update_test.go
+++ b/overlord/snapstate/snapstate_update_test.go
@@ -1108,7 +1108,8 @@ func (s *snapmgrTestSuite) TestUpdateResetsHoldState(c *C) {
tr.Commit()
// pretend that the snap was held during last auto-refresh
- c.Assert(snapstate.HoldRefresh(s.state, "gating-snap", 0, "some-snap", "other-snap"), IsNil)
+ _, err := snapstate.HoldRefresh(s.state, "gating-snap", 0, "some-snap", "other-snap")
+ c.Assert(err, IsNil)
// sanity check
held, err := snapstate.HeldSnaps(s.state)
c.Assert(err, IsNil)
diff --git a/sandbox/apparmor/apparmor_test.go b/sandbox/apparmor/apparmor_test.go
index 8df1641750..dec4c82622 100644
--- a/sandbox/apparmor/apparmor_test.go
+++ b/sandbox/apparmor/apparmor_test.go
@@ -195,48 +195,43 @@ func (s *apparmorSuite) TestProbeAppArmorKernelFeatures(c *C) {
func (s *apparmorSuite) TestProbeAppArmorParserFeatures(c *C) {
var testcases = []struct {
- exitCodes []string
+ exitCodes []int
expFeatures []string
}{
{
- exitCodes: []string{"1", "1"},
+ exitCodes: []int{1, 1},
},
{
- exitCodes: []string{"1", "0"},
+ exitCodes: []int{1, 0},
expFeatures: []string{"qipcrtr-socket"},
},
{
- exitCodes: []string{"0", "1"},
+ exitCodes: []int{0, 1},
expFeatures: []string{"unsafe"},
},
{
- exitCodes: []string{"0", "0"},
+ exitCodes: []int{0, 0},
expFeatures: []string{"qipcrtr-socket", "unsafe"},
},
}
for _, t := range testcases {
d := c.MkDir()
- err := ioutil.WriteFile(filepath.Join(d, "iter"), []byte("0"), 0755)
+ contents := ""
+ for _, code := range t.exitCodes {
+ contents += fmt.Sprintf("%d ", code)
+ }
+ err := ioutil.WriteFile(filepath.Join(d, "codes"), []byte(contents), 0755)
c.Assert(err, IsNil)
- c.Assert(t.exitCodes, HasLen, 2, Commentf("invalid test setup, must have two exit codes for two apparmor parser features probed"))
mockParserCmd := testutil.MockCommand(c, "apparmor_parser", fmt.Sprintf(`
cat >> %[1]s/stdin
echo "" >> %[1]s/stdin
-iter=$(cat %[1]s/iter)
-iter=$(( iter + 1 ))
-echo $iter > %[1]s/iter
-
-case $iter in
- 1)
- exit %[2]s
- ;;
- 2)
- exit %[3]s
- ;;
-esac
-`, d, t.exitCodes[0], t.exitCodes[1]))
+read -r EXIT_CODE CODES_FOR_NEXT_CALLS < %[1]s/codes
+echo "$CODES_FOR_NEXT_CALLS" > %[1]s/codes
+
+exit "$EXIT_CODE"
+`, d))
defer mockParserCmd.Restore()
restore := apparmor.MockParserSearchPath(mockParserCmd.BinDir())
defer restore()
@@ -249,7 +244,11 @@ esac
c.Check(features, DeepEquals, t.expFeatures)
}
- c.Check(mockParserCmd.Calls(), DeepEquals, [][]string{{"apparmor_parser", "--preprocess"}, {"apparmor_parser", "--preprocess"}})
+ var expectedCalls [][]string
+ for range t.exitCodes {
+ expectedCalls = append(expectedCalls, []string{"apparmor_parser", "--preprocess"})
+ }
+ c.Check(mockParserCmd.Calls(), DeepEquals, expectedCalls)
data, err := ioutil.ReadFile(filepath.Join(d, "stdin"))
c.Assert(err, IsNil)
c.Check(string(data), Equals, `profile snap-test {
diff --git a/secboot/export_sb_test.go b/secboot/export_sb_test.go
index 7c8356dd3f..1f7cdc51cf 100644
--- a/secboot/export_sb_test.go
+++ b/secboot/export_sb_test.go
@@ -24,8 +24,6 @@ import (
"io"
sb "github.com/snapcore/secboot"
- sb_efi "github.com/snapcore/secboot/efi"
- sb_tpm2 "github.com/snapcore/secboot/tpm2"
)
var (
@@ -33,7 +31,7 @@ var (
LockTPMSealedKeys = lockTPMSealedKeys
)
-func MockSbConnectToDefaultTPM(f func() (*sb_tpm2.Connection, error)) (restore func()) {
+func MockSbConnectToDefaultTPM(f func() (*sb.TPMConnection, error)) (restore func()) {
old := sbConnectToDefaultTPM
sbConnectToDefaultTPM = f
return func() {
@@ -41,7 +39,7 @@ func MockSbConnectToDefaultTPM(f func() (*sb_tpm2.Connection, error)) (restore f
}
}
-func MockProvisionTPM(f func(tpm *sb_tpm2.Connection, mode sb_tpm2.ProvisionMode, newLockoutAuth []byte) error) (restore func()) {
+func MockProvisionTPM(f func(tpm *sb.TPMConnection, mode sb.ProvisionMode, newLockoutAuth []byte) error) (restore func()) {
old := provisionTPM
provisionTPM = f
return func() {
@@ -49,31 +47,31 @@ func MockProvisionTPM(f func(tpm *sb_tpm2.Connection, mode sb_tpm2.ProvisionMode
}
}
-func MockSbEfiAddSecureBootPolicyProfile(f func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.SecureBootPolicyProfileParams) error) (restore func()) {
- old := sbefiAddSecureBootPolicyProfile
- sbefiAddSecureBootPolicyProfile = f
+func MockSbAddEFISecureBootPolicyProfile(f func(profile *sb.PCRProtectionProfile, params *sb.EFISecureBootPolicyProfileParams) error) (restore func()) {
+ old := sbAddEFISecureBootPolicyProfile
+ sbAddEFISecureBootPolicyProfile = f
return func() {
- sbefiAddSecureBootPolicyProfile = old
+ sbAddEFISecureBootPolicyProfile = old
}
}
-func MockSbEfiAddBootManagerProfile(f func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.BootManagerProfileParams) error) (restore func()) {
- old := sbefiAddBootManagerProfile
- sbefiAddBootManagerProfile = f
+func MockSbAddEFIBootManagerProfile(f func(profile *sb.PCRProtectionProfile, params *sb.EFIBootManagerProfileParams) error) (restore func()) {
+ old := sbAddEFIBootManagerProfile
+ sbAddEFIBootManagerProfile = f
return func() {
- sbefiAddBootManagerProfile = old
+ sbAddEFIBootManagerProfile = old
}
}
-func MockSbEfiAddSystemdStubProfile(f func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.SystemdStubProfileParams) error) (restore func()) {
- old := sbefiAddSystemdStubProfile
- sbefiAddSystemdStubProfile = f
+func MockSbAddSystemdEFIStubProfile(f func(profile *sb.PCRProtectionProfile, params *sb.SystemdEFIStubProfileParams) error) (restore func()) {
+ old := sbAddSystemdEFIStubProfile
+ sbAddSystemdEFIStubProfile = f
return func() {
- sbefiAddSystemdStubProfile = old
+ sbAddSystemdEFIStubProfile = old
}
}
-func MockSbAddSnapModelProfile(f func(profile *sb_tpm2.PCRProtectionProfile, params *sb_tpm2.SnapModelProfileParams) error) (restore func()) {
+func MockSbAddSnapModelProfile(f func(profile *sb.PCRProtectionProfile, params *sb.SnapModelProfileParams) error) (restore func()) {
old := sbAddSnapModelProfile
sbAddSnapModelProfile = f
return func() {
@@ -81,7 +79,7 @@ func MockSbAddSnapModelProfile(f func(profile *sb_tpm2.PCRProtectionProfile, par
}
}
-func MockSbSealKeyToTPMMultiple(f func(tpm *sb_tpm2.Connection, keys []*sb_tpm2.SealKeyRequest, params *sb_tpm2.KeyCreationParams) (sb_tpm2.PolicyAuthKey, error)) (restore func()) {
+func MockSbSealKeyToTPMMultiple(f func(tpm *sb.TPMConnection, keys []*sb.SealKeyRequest, params *sb.KeyCreationParams) (sb.TPMPolicyAuthKey, error)) (restore func()) {
old := sbSealKeyToTPMMultiple
sbSealKeyToTPMMultiple = f
return func() {
@@ -89,7 +87,7 @@ func MockSbSealKeyToTPMMultiple(f func(tpm *sb_tpm2.Connection, keys []*sb_tpm2.
}
}
-func MockSbUpdateKeyPCRProtectionPolicyMultiple(f func(tpm *sb_tpm2.Connection, keys []*sb_tpm2.SealedKeyObject, authKey sb_tpm2.PolicyAuthKey, pcrProfile *sb_tpm2.PCRProtectionProfile) error) (restore func()) {
+func MockSbUpdateKeyPCRProtectionPolicyMultiple(f func(tpm *sb.TPMConnection, keyPaths []string, authKey sb.TPMPolicyAuthKey, pcrProfile *sb.PCRProtectionProfile) error) (restore func()) {
old := sbUpdateKeyPCRProtectionPolicyMultiple
sbUpdateKeyPCRProtectionPolicyMultiple = f
return func() {
@@ -97,7 +95,7 @@ func MockSbUpdateKeyPCRProtectionPolicyMultiple(f func(tpm *sb_tpm2.Connection,
}
}
-func MockSbBlockPCRProtectionPolicies(f func(tpm *sb_tpm2.Connection, pcrs []int) error) (restore func()) {
+func MockSbBlockPCRProtectionPolicies(f func(tpm *sb.TPMConnection, pcrs []int) error) (restore func()) {
old := sbBlockPCRProtectionPolicies
sbBlockPCRProtectionPolicies = f
return func() {
@@ -114,7 +112,7 @@ func MockSbActivateVolumeWithRecoveryKey(f func(volumeName, sourceDevicePath str
}
}
-func MockSbActivateVolumeWithTPMSealedKey(f func(tpm *sb_tpm2.Connection, volumeName, sourceDevicePath, keyPath string,
+func MockSbActivateVolumeWithTPMSealedKey(f func(tpm *sb.TPMConnection, volumeName, sourceDevicePath, keyPath string,
pinReader io.Reader, options *sb.ActivateVolumeOptions) (bool, error)) (restore func()) {
old := sbActivateVolumeWithTPMSealedKey
sbActivateVolumeWithTPMSealedKey = f
@@ -140,7 +138,7 @@ func MockSbActivateVolumeWithKeyData(f func(volumeName, sourceDevicePath string,
}
}
-func MockSbMeasureSnapSystemEpochToTPM(f func(tpm *sb_tpm2.Connection, pcrIndex int) error) (restore func()) {
+func MockSbMeasureSnapSystemEpochToTPM(f func(tpm *sb.TPMConnection, pcrIndex int) error) (restore func()) {
old := sbMeasureSnapSystemEpochToTPM
sbMeasureSnapSystemEpochToTPM = f
return func() {
@@ -148,7 +146,7 @@ func MockSbMeasureSnapSystemEpochToTPM(f func(tpm *sb_tpm2.Connection, pcrIndex
}
}
-func MockSbMeasureSnapModelToTPM(f func(tpm *sb_tpm2.Connection, pcrIndex int, model sb.SnapModel) error) (restore func()) {
+func MockSbMeasureSnapModelToTPM(f func(tpm *sb.TPMConnection, pcrIndex int, model sb.SnapModel) error) (restore func()) {
old := sbMeasureSnapModelToTPM
sbMeasureSnapModelToTPM = f
return func() {
@@ -181,7 +179,7 @@ func MockSbAddRecoveryKeyToLUKS2Container(f func(devicePath string, key []byte,
}
}
-func MockIsTPMEnabled(f func(tpm *sb_tpm2.Connection) bool) (restore func()) {
+func MockIsTPMEnabled(f func(tpm *sb.TPMConnection) bool) (restore func()) {
old := isTPMEnabled
isTPMEnabled = f
return func() {
@@ -204,11 +202,3 @@ func MockSbDeactivateVolume(f func(volumeName string) error) (restore func()) {
sbDeactivateVolume = old
}
}
-
-func MockSbReadSealedKeyObject(f func(string) (*sb_tpm2.SealedKeyObject, error)) (restore func()) {
- old := sbReadSealedKeyObject
- sbReadSealedKeyObject = f
- return func() {
- sbReadSealedKeyObject = old
- }
-}
diff --git a/secboot/secboot_sb_test.go b/secboot/secboot_sb_test.go
index f334d68409..2a21d39985 100644
--- a/secboot/secboot_sb_test.go
+++ b/secboot/secboot_sb_test.go
@@ -33,10 +33,7 @@ import (
"path/filepath"
"github.com/canonical/go-tpm2"
- "github.com/canonical/go-tpm2/linux"
sb "github.com/snapcore/secboot"
- sb_efi "github.com/snapcore/secboot/efi"
- sb_tpm2 "github.com/snapcore/secboot/tpm2"
. "gopkg.in/check.v1"
"github.com/snapcore/snapd/asserts"
@@ -97,14 +94,14 @@ func (s *secbootSuite) TestCheckTPMKeySealingSupported(c *C) {
// TPM was detected but it's not enabled
{tpmErr: nil, tpmEnabled: false, sbData: sbEnabled, err: "TPM device is not enabled"},
// No TPM device
- {tpmErr: sb_tpm2.ErrNoTPM2Device, sbData: sbEnabled, err: "cannot connect to TPM device: no TPM2 device is available"},
+ {tpmErr: sb.ErrNoTPM2Device, sbData: sbEnabled, err: "cannot connect to TPM device: no TPM2 device is available"},
} {
c.Logf("%d: %v %v %v %q", i, tc.tpmErr, tc.tpmEnabled, tc.sbData, tc.err)
_, restore := mockSbTPMConnection(c, tc.tpmErr)
defer restore()
- restore = secboot.MockIsTPMEnabled(func(tpm *sb_tpm2.Connection) bool {
+ restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool {
return tc.tpmEnabled
})
defer restore()
@@ -147,19 +144,19 @@ func (s *secbootSuite) TestMeasureSnapSystemEpochWhenPossible(c *C) {
},
{
// TPM device does not exist
- tpmErr: sb_tpm2.ErrNoTPM2Device,
+ tpmErr: sb.ErrNoTPM2Device,
},
} {
mockTpm, restore := mockSbTPMConnection(c, tc.tpmErr)
defer restore()
- restore = secboot.MockIsTPMEnabled(func(tpm *sb_tpm2.Connection) bool {
+ restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool {
return tc.tpmEnabled
})
defer restore()
calls := 0
- restore = secboot.MockSbMeasureSnapSystemEpochToTPM(func(tpm *sb_tpm2.Connection, pcrIndex int) error {
+ restore = secboot.MockSbMeasureSnapSystemEpochToTPM(func(tpm *sb.TPMConnection, pcrIndex int) error {
calls++
c.Assert(tpm, Equals, mockTpm)
c.Assert(pcrIndex, Equals, 12)
@@ -205,7 +202,7 @@ func (s *secbootSuite) TestMeasureSnapModelWhenPossible(c *C) {
},
{
// TPM device does not exist
- tpmErr: sb_tpm2.ErrNoTPM2Device,
+ tpmErr: sb.ErrNoTPM2Device,
},
} {
c.Logf("%d: tpmErr:%v tpmEnabled:%v", i, tc.tpmErr, tc.tpmEnabled)
@@ -214,13 +211,13 @@ func (s *secbootSuite) TestMeasureSnapModelWhenPossible(c *C) {
mockTpm, restore := mockSbTPMConnection(c, tc.tpmErr)
defer restore()
- restore = secboot.MockIsTPMEnabled(func(tpm *sb_tpm2.Connection) bool {
+ restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool {
return tc.tpmEnabled
})
defer restore()
calls := 0
- restore = secboot.MockSbMeasureSnapModelToTPM(func(tpm *sb_tpm2.Connection, pcrIndex int, model sb.SnapModel) error {
+ restore = secboot.MockSbMeasureSnapModelToTPM(func(tpm *sb.TPMConnection, pcrIndex int, model sb.SnapModel) error {
calls++
c.Assert(tpm, Equals, mockTpm)
c.Assert(model, Equals, mockModel)
@@ -260,7 +257,7 @@ func (s *secbootSuite) TestLockTPMSealedKeys(c *C) {
},
// no TPM2 device, shouldn't return an error
{
- tpmErr: sb_tpm2.ErrNoTPM2Device,
+ tpmErr: sb.ErrNoTPM2Device,
},
// tpm is not enabled but we can lock it
{
@@ -284,13 +281,13 @@ func (s *secbootSuite) TestLockTPMSealedKeys(c *C) {
mockSbTPM, restoreConnect := mockSbTPMConnection(c, tc.tpmErr)
defer restoreConnect()
- restore := secboot.MockIsTPMEnabled(func(tpm *sb_tpm2.Connection) bool {
+ restore := secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool {
return tc.tpmEnabled
})
defer restore()
sbBlockPCRProtectionPolicesCalls := 0
- restore = secboot.MockSbBlockPCRProtectionPolicies(func(tpm *sb_tpm2.Connection, pcrs []int) error {
+ restore = secboot.MockSbBlockPCRProtectionPolicies(func(tpm *sb.TPMConnection, pcrs []int) error {
sbBlockPCRProtectionPolicesCalls++
c.Assert(tpm, Equals, mockSbTPM)
c.Assert(pcrs, DeepEquals, []int{12})
@@ -323,18 +320,22 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncrypted(c *C) {
// defer restore()
mockDiskWithEncDev := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "name-enc": "enc-dev-partuuid",
+ Structure: []disks.Partition{
+ {
+ FilesystemLabel: "name-enc",
+ PartitionUUID: "enc-dev-partuuid",
+ },
},
}
- mockDiskWithoutAnyDev := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{},
- }
+ mockDiskWithoutAnyDev := &disks.MockDiskMapping{}
mockDiskWithUnencDev := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "name": "unenc-dev-partuuid",
+ Structure: []disks.Partition{
+ {
+ FilesystemLabel: "name",
+ PartitionUUID: "unenc-dev-partuuid",
+ },
},
}
@@ -384,7 +385,7 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncrypted(c *C) {
// happy case with tpm and encrypted device, activation
// with recovery key
tpmEnabled: true, hasEncdev: true, activated: true,
- activateErr: &sb_tpm2.ActivateWithSealedKeyError{
+ activateErr: &sb.ActivateWithTPMSealedKeyError{
// activation error with nil recovery key error
// implies volume activated successfully using
// the recovery key,
@@ -396,7 +397,7 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncrypted(c *C) {
// tpm and encrypted device, successful activation, but
// recovery key non-nil is an unexpected state
tpmEnabled: true, hasEncdev: true, activated: true,
- activateErr: &sb_tpm2.ActivateWithSealedKeyError{
+ activateErr: &sb.ActivateWithTPMSealedKeyError{
RecoveryKeyUsageErr: fmt.Errorf("unexpected"),
},
expUnlockMethod: secboot.UnlockStatusUnknown,
@@ -428,25 +429,25 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncrypted(c *C) {
err: `cannot unlock encrypted device ".*/enc-dev-partuuid": cannot unlock with recovery key`,
}, {
// no tpm, has encrypted device, unlocked using the recovery key
- tpmErr: sb_tpm2.ErrNoTPM2Device, hasEncdev: true,
+ tpmErr: sb.ErrNoTPM2Device, hasEncdev: true,
rkAllow: true,
disk: mockDiskWithEncDev,
expUnlockMethod: secboot.UnlockedWithRecoveryKey,
}, {
// no tpm, has encrypted device, unlocking with recovery key not allowed
- tpmErr: sb_tpm2.ErrNoTPM2Device, hasEncdev: true,
+ tpmErr: sb.ErrNoTPM2Device, hasEncdev: true,
disk: mockDiskWithEncDev,
err: `cannot activate encrypted device ".*/enc-dev-partuuid": activation error`,
}, {
// no tpm, has encrypted device, recovery key unlocking fails
rkErr: errors.New("cannot unlock with recovery key"),
- tpmErr: sb_tpm2.ErrNoTPM2Device, hasEncdev: true,
+ tpmErr: sb.ErrNoTPM2Device, hasEncdev: true,
rkAllow: true,
disk: mockDiskWithEncDev,
err: `cannot unlock encrypted device ".*/enc-dev-partuuid": cannot unlock with recovery key`,
}, {
// no tpm, no encrypted device
- tpmErr: sb_tpm2.ErrNoTPM2Device,
+ tpmErr: sb.ErrNoTPM2Device,
disk: mockDiskWithUnencDev,
}, {
// no disks at all
@@ -468,7 +469,7 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncrypted(c *C) {
_, restoreConnect := mockSbTPMConnection(c, tc.tpmErr)
defer restoreConnect()
- restore = secboot.MockIsTPMEnabled(func(tpm *sb_tpm2.Connection) bool {
+ restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool {
return tc.tpmEnabled
})
defer restore()
@@ -479,10 +480,18 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncrypted(c *C) {
if tc.hasEncdev {
fsLabel += "-enc"
}
- partuuid, ok := tc.disk.FilesystemLabelToPartUUID[fsLabel]
+
+ partuuid := ""
if !tc.skipDiskEnsureCheck {
- c.Assert(ok, Equals, true)
+ for _, p := range tc.disk.Structure {
+ if p.FilesystemLabel == fsLabel {
+ partuuid = p.PartitionUUID
+ break
+ }
+ }
+ c.Assert(partuuid, Not(Equals), "", Commentf("didn't find fs label %s in disk", fsLabel))
}
+
devicePath := filepath.Join("/dev/disk/by-partuuid", partuuid)
expKeyPath := tc.keyfile
@@ -490,7 +499,7 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncrypted(c *C) {
expKeyPath = "vanilla-keyfile"
}
- restore = secboot.MockSbActivateVolumeWithTPMSealedKey(func(tpm *sb_tpm2.Connection, volumeName, sourceDevicePath,
+ restore = secboot.MockSbActivateVolumeWithTPMSealedKey(func(tpm *sb.TPMConnection, volumeName, sourceDevicePath,
keyPath string, pinReader io.Reader, options *sb.ActivateVolumeOptions) (bool, error) {
c.Assert(volumeName, Equals, "name-"+randomUUID)
c.Assert(sourceDevicePath, Equals, devicePath)
@@ -573,13 +582,13 @@ func (s *secbootSuite) TestEFIImageFromBootFile(c *C) {
for _, tc := range []struct {
bootFile bootloader.BootFile
- efiImage sb_efi.Image
+ efiImage sb.EFIImage
err string
}{
{
// happy case for EFI image
bootFile: bootloader.NewBootFile("", existingFile, bootloader.RoleRecovery),
- efiImage: sb_efi.FileImage(existingFile),
+ efiImage: sb.FileEFIImage(existingFile),
},
{
// missing EFI image
@@ -589,7 +598,7 @@ func (s *secbootSuite) TestEFIImageFromBootFile(c *C) {
{
// happy case for snap file
bootFile: bootloader.NewBootFile(snapFile, "rel", bootloader.RoleRecovery),
- efiImage: sb_efi.SnapFileImage{Container: snapf, FileName: "rel"},
+ efiImage: sb.SnapFileEFIImage{Container: snapf, FileName: "rel"},
},
{
// invalid snap file
@@ -726,14 +735,14 @@ func (s *secbootSuite) TestSealKey(c *C) {
// events for
// a -> kernel
- sequences1 := []*sb_efi.ImageLoadEvent{
+ sequences1 := []*sb.EFIImageLoadEvent{
{
- Source: sb_efi.Firmware,
- Image: sb_efi.FileImage(mockBF[0].Path),
- Next: []*sb_efi.ImageLoadEvent{
+ Source: sb.Firmware,
+ Image: sb.FileEFIImage(mockBF[0].Path),
+ Next: []*sb.EFIImageLoadEvent{
{
- Source: sb_efi.Shim,
- Image: sb_efi.SnapFileImage{
+ Source: sb.Shim,
+ Image: sb.SnapFileEFIImage{
Container: kernelSnap,
FileName: "kernel.efi",
},
@@ -745,14 +754,14 @@ func (s *secbootSuite) TestSealKey(c *C) {
// "cdk" events for
// c -> kernel OR
// d -> kernel
- cdk := []*sb_efi.ImageLoadEvent{
+ cdk := []*sb.EFIImageLoadEvent{
{
- Source: sb_efi.Shim,
- Image: sb_efi.FileImage(mockBF[2].Path),
- Next: []*sb_efi.ImageLoadEvent{
+ Source: sb.Shim,
+ Image: sb.FileEFIImage(mockBF[2].Path),
+ Next: []*sb.EFIImageLoadEvent{
{
- Source: sb_efi.Shim,
- Image: sb_efi.SnapFileImage{
+ Source: sb.Shim,
+ Image: sb.SnapFileEFIImage{
Container: kernelSnap,
FileName: "kernel.efi",
},
@@ -760,12 +769,12 @@ func (s *secbootSuite) TestSealKey(c *C) {
},
},
{
- Source: sb_efi.Shim,
- Image: sb_efi.FileImage(mockBF[3].Path),
- Next: []*sb_efi.ImageLoadEvent{
+ Source: sb.Shim,
+ Image: sb.FileEFIImage(mockBF[3].Path),
+ Next: []*sb.EFIImageLoadEvent{
{
- Source: sb_efi.Shim,
- Image: sb_efi.SnapFileImage{
+ Source: sb.Shim,
+ Image: sb.SnapFileEFIImage{
Container: kernelSnap,
FileName: "kernel.efi",
},
@@ -777,15 +786,15 @@ func (s *secbootSuite) TestSealKey(c *C) {
// events for
// a -> "cdk"
// b -> "cdk"
- sequences2 := []*sb_efi.ImageLoadEvent{
+ sequences2 := []*sb.EFIImageLoadEvent{
{
- Source: sb_efi.Firmware,
- Image: sb_efi.FileImage(mockBF[0].Path),
+ Source: sb.Firmware,
+ Image: sb.FileEFIImage(mockBF[0].Path),
Next: cdk,
},
{
- Source: sb_efi.Firmware,
- Image: sb_efi.FileImage(mockBF[1].Path),
+ Source: sb.Firmware,
+ Image: sb.FileEFIImage(mockBF[1].Path),
Next: cdk,
},
}
@@ -794,9 +803,9 @@ func (s *secbootSuite) TestSealKey(c *C) {
defer restore()
// mock adding EFI secure boot policy profile
- var pcrProfile *sb_tpm2.PCRProtectionProfile
+ var pcrProfile *sb.PCRProtectionProfile
addEFISbPolicyCalls := 0
- restore = secboot.MockSbEfiAddSecureBootPolicyProfile(func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.SecureBootPolicyProfileParams) error {
+ restore = secboot.MockSbAddEFISecureBootPolicyProfile(func(profile *sb.PCRProtectionProfile, params *sb.EFISecureBootPolicyProfileParams) error {
addEFISbPolicyCalls++
pcrProfile = profile
c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
@@ -806,7 +815,7 @@ func (s *secbootSuite) TestSealKey(c *C) {
case 2:
c.Assert(params.LoadSequences, DeepEquals, sequences2)
default:
- c.Error("AddSecureBootPolicyProfile shouldn't be called a third time")
+ c.Error("AddEFISecureBootPolicyProfile shouldn't be called a third time")
}
return tc.addEFISbPolicyErr
})
@@ -814,7 +823,7 @@ func (s *secbootSuite) TestSealKey(c *C) {
// mock adding EFI boot manager profile
addEFIBootManagerCalls := 0
- restore = secboot.MockSbEfiAddBootManagerProfile(func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.BootManagerProfileParams) error {
+ restore = secboot.MockSbAddEFIBootManagerProfile(func(profile *sb.PCRProtectionProfile, params *sb.EFIBootManagerProfileParams) error {
addEFIBootManagerCalls++
c.Assert(profile, Equals, pcrProfile)
c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
@@ -824,7 +833,7 @@ func (s *secbootSuite) TestSealKey(c *C) {
case 2:
c.Assert(params.LoadSequences, DeepEquals, sequences2)
default:
- c.Error("AddBootManagerProfile shouldn't be called a third time")
+ c.Error("AddEFIBootManagerProfile shouldn't be called a third time")
}
return tc.addEFIBootManagerErr
})
@@ -832,7 +841,7 @@ func (s *secbootSuite) TestSealKey(c *C) {
// mock adding systemd EFI stub profile
addSystemdEfiStubCalls := 0
- restore = secboot.MockSbEfiAddSystemdStubProfile(func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.SystemdStubProfileParams) error {
+ restore = secboot.MockSbAddSystemdEFIStubProfile(func(profile *sb.PCRProtectionProfile, params *sb.SystemdEFIStubProfileParams) error {
addSystemdEfiStubCalls++
c.Assert(profile, Equals, pcrProfile)
c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
@@ -843,7 +852,7 @@ func (s *secbootSuite) TestSealKey(c *C) {
case 2:
c.Assert(params.KernelCmdlines, DeepEquals, myParams.ModelParams[1].KernelCmdlines)
default:
- c.Error("AddSystemdStubProfile shouldn't be called a third time")
+ c.Error("AddSystemdEFIStubProfile shouldn't be called a third time")
}
return tc.addSystemdEFIStubErr
})
@@ -851,7 +860,7 @@ func (s *secbootSuite) TestSealKey(c *C) {
// mock adding snap model profile
addSnapModelCalls := 0
- restore = secboot.MockSbAddSnapModelProfile(func(profile *sb_tpm2.PCRProtectionProfile, params *sb_tpm2.SnapModelProfileParams) error {
+ restore = secboot.MockSbAddSnapModelProfile(func(profile *sb.PCRProtectionProfile, params *sb.SnapModelProfileParams) error {
addSnapModelCalls++
c.Assert(profile, Equals, pcrProfile)
c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
@@ -870,10 +879,10 @@ func (s *secbootSuite) TestSealKey(c *C) {
// mock provisioning
provisioningCalls := 0
- restore = secboot.MockProvisionTPM(func(t *sb_tpm2.Connection, mode sb_tpm2.ProvisionMode, newLockoutAuth []byte) error {
+ restore = secboot.MockProvisionTPM(func(t *sb.TPMConnection, mode sb.ProvisionMode, newLockoutAuth []byte) error {
provisioningCalls++
c.Assert(t, Equals, tpm)
- c.Assert(mode, Equals, sb_tpm2.ProvisionModeFull)
+ c.Assert(mode, Equals, sb.ProvisionModeFull)
c.Assert(myParams.TPMLockoutAuthFile, testutil.FilePresent)
return tc.provisioningErr
})
@@ -881,18 +890,18 @@ func (s *secbootSuite) TestSealKey(c *C) {
// mock sealing
sealCalls := 0
- restore = secboot.MockSbSealKeyToTPMMultiple(func(t *sb_tpm2.Connection, kr []*sb_tpm2.SealKeyRequest, params *sb_tpm2.KeyCreationParams) (sb_tpm2.PolicyAuthKey, error) {
+ restore = secboot.MockSbSealKeyToTPMMultiple(func(t *sb.TPMConnection, kr []*sb.SealKeyRequest, params *sb.KeyCreationParams) (sb.TPMPolicyAuthKey, error) {
sealCalls++
c.Assert(t, Equals, tpm)
- c.Assert(kr, DeepEquals, []*sb_tpm2.SealKeyRequest{{Key: myKey, Path: "keyfile"}, {Key: myKey2, Path: "keyfile2"}})
+ c.Assert(kr, DeepEquals, []*sb.SealKeyRequest{{Key: myKey, Path: "keyfile"}, {Key: myKey2, Path: "keyfile2"}})
c.Assert(params.AuthKey, Equals, myAuthKey)
c.Assert(params.PCRPolicyCounterHandle, Equals, tpm2.Handle(42))
- return sb_tpm2.PolicyAuthKey{}, tc.sealErr
+ return sb.TPMPolicyAuthKey{}, tc.sealErr
})
defer restore()
// mock TPM enabled check
- restore = secboot.MockIsTPMEnabled(func(t *sb_tpm2.Connection) bool {
+ restore = secboot.MockIsTPMEnabled(func(t *sb.TPMConnection) bool {
return tc.tpmEnabled
})
defer restore()
@@ -917,23 +926,18 @@ func (s *secbootSuite) TestResealKey(c *C) {
mockErr := errors.New("some error")
for _, tc := range []struct {
- tpmErr error
- tpmEnabled bool
- missingFile bool
- addEFISbPolicyErr error
- addEFIBootManagerErr error
- addSystemdEFIStubErr error
- addSnapModelErr error
- readSealedKeyObjectErr error
- provisioningErr error
- resealErr error
- resealCalls int
- expectedErr string
+ tpmErr error
+ tpmEnabled bool
+ missingFile bool
+ addEFISbPolicyErr error
+ addEFIBootManagerErr error
+ addSystemdEFIStubErr error
+ addSnapModelErr error
+ provisioningErr error
+ resealErr error
+ resealCalls int
+ expectedErr string
}{
- // happy case
- {tpmEnabled: true, resealCalls: 1, expectedErr: ""},
-
- // unhappy cases
{tpmErr: mockErr, expectedErr: "cannot connect to TPM: some error"},
{tpmEnabled: false, expectedErr: "TPM device is not enabled"},
{tpmEnabled: true, missingFile: true, expectedErr: "cannot build EFI image load sequences: file .*/file.efi does not exist"},
@@ -941,8 +945,8 @@ func (s *secbootSuite) TestResealKey(c *C) {
{tpmEnabled: true, addEFIBootManagerErr: mockErr, expectedErr: "cannot add EFI boot manager profile: some error"},
{tpmEnabled: true, addSystemdEFIStubErr: mockErr, expectedErr: "cannot add systemd EFI stub profile: some error"},
{tpmEnabled: true, addSnapModelErr: mockErr, expectedErr: "cannot add snap model profile: some error"},
- {tpmEnabled: true, readSealedKeyObjectErr: mockErr, expectedErr: "some error"},
{tpmEnabled: true, resealErr: mockErr, resealCalls: 1, expectedErr: "some error"},
+ {tpmEnabled: true, resealCalls: 1, expectedErr: ""},
} {
mockTPMPolicyAuthKey := []byte{1, 3, 3, 7}
mockTPMPolicyAuthKeyFile := filepath.Join(c.MkDir(), "policy-auth-key-file")
@@ -967,24 +971,10 @@ func (s *secbootSuite) TestResealKey(c *C) {
TPMPolicyAuthKeyFile: mockTPMPolicyAuthKeyFile,
}
- numMockSealedKeyObjects := len(myParams.KeyFiles)
- mockSealedKeyObjects := make([]*sb_tpm2.SealedKeyObject, 0, numMockSealedKeyObjects)
- for range myParams.KeyFiles {
- // Copy of
- // https://github.com/snapcore/secboot/blob/master/internal/compattest/testdata/v1/key
- // To create full looking
- // mockSealedKeyObjects, although {},{} would
- // have been enough as well
- mockSealedKeyFile := filepath.Join("test-data", "keyfile")
- mockSealedKeyObject, err := sb_tpm2.ReadSealedKeyObject(mockSealedKeyFile)
- c.Assert(err, IsNil)
- mockSealedKeyObjects = append(mockSealedKeyObjects, mockSealedKeyObject)
- }
-
- sequences := []*sb_efi.ImageLoadEvent{
+ sequences := []*sb.EFIImageLoadEvent{
{
- Source: sb_efi.Firmware,
- Image: sb_efi.FileImage(mockEFI.Path),
+ Source: sb.Firmware,
+ Image: sb.FileEFIImage(mockEFI.Path),
},
}
@@ -993,15 +983,15 @@ func (s *secbootSuite) TestResealKey(c *C) {
defer restore()
// mock TPM enabled check
- restore = secboot.MockIsTPMEnabled(func(t *sb_tpm2.Connection) bool {
+ restore = secboot.MockIsTPMEnabled(func(t *sb.TPMConnection) bool {
return tc.tpmEnabled
})
defer restore()
// mock adding EFI secure boot policy profile
- var pcrProfile *sb_tpm2.PCRProtectionProfile
+ var pcrProfile *sb.PCRProtectionProfile
addEFISbPolicyCalls := 0
- restore = secboot.MockSbEfiAddSecureBootPolicyProfile(func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.SecureBootPolicyProfileParams) error {
+ restore = secboot.MockSbAddEFISecureBootPolicyProfile(func(profile *sb.PCRProtectionProfile, params *sb.EFISecureBootPolicyProfileParams) error {
addEFISbPolicyCalls++
pcrProfile = profile
c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
@@ -1012,7 +1002,7 @@ func (s *secbootSuite) TestResealKey(c *C) {
// mock adding EFI boot manager profile
addEFIBootManagerCalls := 0
- restore = secboot.MockSbEfiAddBootManagerProfile(func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.BootManagerProfileParams) error {
+ restore = secboot.MockSbAddEFIBootManagerProfile(func(profile *sb.PCRProtectionProfile, params *sb.EFIBootManagerProfileParams) error {
addEFIBootManagerCalls++
c.Assert(profile, Equals, pcrProfile)
c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
@@ -1023,7 +1013,7 @@ func (s *secbootSuite) TestResealKey(c *C) {
// mock adding systemd EFI stub profile
addSystemdEfiStubCalls := 0
- restore = secboot.MockSbEfiAddSystemdStubProfile(func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.SystemdStubProfileParams) error {
+ restore = secboot.MockSbAddSystemdEFIStubProfile(func(profile *sb.PCRProtectionProfile, params *sb.SystemdEFIStubProfileParams) error {
addSystemdEfiStubCalls++
c.Assert(profile, Equals, pcrProfile)
c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
@@ -1035,7 +1025,7 @@ func (s *secbootSuite) TestResealKey(c *C) {
// mock adding snap model profile
addSnapModelCalls := 0
- restore = secboot.MockSbAddSnapModelProfile(func(profile *sb_tpm2.PCRProtectionProfile, params *sb_tpm2.SnapModelProfileParams) error {
+ restore = secboot.MockSbAddSnapModelProfile(func(profile *sb.PCRProtectionProfile, params *sb.SnapModelProfileParams) error {
addSnapModelCalls++
c.Assert(profile, Equals, pcrProfile)
c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
@@ -1045,22 +1035,13 @@ func (s *secbootSuite) TestResealKey(c *C) {
})
defer restore()
- // mock ReadSealedKeyObject
- readSealedKeyObjectCalls := 0
- restore = secboot.MockSbReadSealedKeyObject(func(keyfile string) (*sb_tpm2.SealedKeyObject, error) {
- readSealedKeyObjectCalls++
- c.Assert(keyfile, Equals, myParams.KeyFiles[readSealedKeyObjectCalls-1])
- return mockSealedKeyObjects[readSealedKeyObjectCalls-1], tc.readSealedKeyObjectErr
- })
- defer restore()
-
// mock PCR protection policy update
resealCalls := 0
- restore = secboot.MockSbUpdateKeyPCRProtectionPolicyMultiple(func(t *sb_tpm2.Connection, keys []*sb_tpm2.SealedKeyObject, authKey sb_tpm2.PolicyAuthKey, profile *sb_tpm2.PCRProtectionProfile) error {
+ restore = secboot.MockSbUpdateKeyPCRProtectionPolicyMultiple(func(t *sb.TPMConnection, keyPaths []string, authKey sb.TPMPolicyAuthKey, profile *sb.PCRProtectionProfile) error {
resealCalls++
c.Assert(t, Equals, tpm)
- c.Assert(keys, DeepEquals, mockSealedKeyObjects)
- c.Assert(authKey, DeepEquals, sb_tpm2.PolicyAuthKey(mockTPMPolicyAuthKey))
+ c.Assert(keyPaths, DeepEquals, []string{"keyfile", "keyfile2"})
+ c.Assert(authKey, DeepEquals, sb.TPMPolicyAuthKey(mockTPMPolicyAuthKey))
c.Assert(profile, Equals, pcrProfile)
return tc.resealErr
})
@@ -1073,7 +1054,7 @@ func (s *secbootSuite) TestResealKey(c *C) {
c.Assert(addSystemdEfiStubCalls, Equals, 1)
c.Assert(addSnapModelCalls, Equals, 1)
} else {
- c.Assert(err, ErrorMatches, tc.expectedErr, Commentf("%v", tc))
+ c.Assert(err, ErrorMatches, tc.expectedErr)
}
c.Assert(resealCalls, Equals, tc.resealCalls)
}
@@ -1110,13 +1091,13 @@ func createMockSnapFile(snapDir, snapPath, snapType string) (snap.Container, err
return snapfile.Open(snapPath)
}
-func mockSbTPMConnection(c *C, tpmErr error) (*sb_tpm2.Connection, func()) {
- tcti, err := linux.OpenDevice("/dev/null")
+func mockSbTPMConnection(c *C, tpmErr error) (*sb.TPMConnection, func()) {
+ tcti, err := tpm2.OpenTPMDevice("/dev/null")
c.Assert(err, IsNil)
- tpmctx := tpm2.NewTPMContext(tcti)
+ tpmctx, err := tpm2.NewTPMContext(tcti)
c.Assert(err, IsNil)
- tpm := &sb_tpm2.Connection{TPMContext: tpmctx}
- restore := secboot.MockSbConnectToDefaultTPM(func() (*sb_tpm2.Connection, error) {
+ tpm := &sb.TPMConnection{TPMContext: tpmctx}
+ restore := secboot.MockSbConnectToDefaultTPM(func() (*sb.TPMConnection, error) {
if tpmErr != nil {
return nil, tpmErr
}
@@ -1126,9 +1107,7 @@ func mockSbTPMConnection(c *C, tpmErr error) (*sb_tpm2.Connection, func()) {
}
func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyBadDisk(c *C) {
- disk := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{},
- }
+ disk := &disks.MockDiskMapping{}
unlockRes, err := secboot.UnlockEncryptedVolumeUsingKey(disk, "ubuntu-save", []byte("fooo"))
c.Assert(err, ErrorMatches, `filesystem label "ubuntu-save-enc" not found`)
c.Check(unlockRes, DeepEquals, secboot.UnlockResult{})
@@ -1136,8 +1115,11 @@ func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyBadDisk(c *C) {
func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyHappy(c *C) {
disk := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "ubuntu-save-enc": "123-123-123",
+ Structure: []disks.Partition{
+ {
+ FilesystemLabel: "ubuntu-save-enc",
+ PartitionUUID: "123-123-123",
+ },
},
}
restore := secboot.MockRandomKernelUUID(func() string {
@@ -1165,8 +1147,11 @@ func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyHappy(c *C) {
func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyErr(c *C) {
disk := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "ubuntu-save-enc": "123-123-123",
+ Structure: []disks.Partition{
+ {
+ FilesystemLabel: "ubuntu-save-enc",
+ PartitionUUID: "123-123-123",
+ },
},
}
restore := secboot.MockRandomKernelUUID(func() string {
@@ -1200,8 +1185,11 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyErr(
defer restore()
mockDiskWithEncDev := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "name-enc": "enc-dev-partuuid",
+ Structure: []disks.Partition{
+ {
+ FilesystemLabel: "name-enc",
+ PartitionUUID: "enc-dev-partuuid",
+ },
},
}
defaultDevice := "name"
@@ -1247,8 +1235,11 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV1An
defer restore()
mockDiskWithEncDev := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "device-name-enc": "enc-dev-partuuid",
+ Structure: []disks.Partition{
+ {
+ FilesystemLabel: "device-name-enc",
+ PartitionUUID: "enc-dev-partuuid",
+ },
},
}
@@ -1481,8 +1472,11 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2(c
defer restore()
mockDiskWithEncDev := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "device-name-enc": "enc-dev-partuuid",
+ Structure: []disks.Partition{
+ {
+ FilesystemLabel: "device-name-enc",
+ PartitionUUID: "enc-dev-partuuid",
+ },
},
}
@@ -1539,8 +1533,11 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2Mo
defer restore()
mockDiskWithEncDev := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "device-name-enc": "enc-dev-partuuid",
+ Structure: []disks.Partition{
+ {
+ FilesystemLabel: "device-name-enc",
+ PartitionUUID: "enc-dev-partuuid",
+ },
},
}
@@ -1591,8 +1588,11 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2Mo
defer restore()
mockDiskWithEncDev := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "device-name-enc": "enc-dev-partuuid",
+ Structure: []disks.Partition{
+ {
+ FilesystemLabel: "device-name-enc",
+ PartitionUUID: "enc-dev-partuuid",
+ },
},
}
@@ -1641,8 +1641,11 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2Al
defer restore()
mockDiskWithEncDev := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "device-name-enc": "enc-dev-partuuid",
+ Structure: []disks.Partition{
+ {
+ FilesystemLabel: "device-name-enc",
+ PartitionUUID: "enc-dev-partuuid",
+ },
},
}
@@ -1698,8 +1701,11 @@ func (s *secbootSuite) checkV2Key(c *C, keyFn string, prefixToDrop, expectedKey,
defer restore()
mockDiskWithEncDev := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "device-name-enc": "enc-dev-partuuid",
+ Structure: []disks.Partition{
+ {
+ FilesystemLabel: "device-name-enc",
+ PartitionUUID: "enc-dev-partuuid",
+ },
},
}
@@ -1763,8 +1769,11 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV1(c
defer restore()
mockDiskWithEncDev := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "device-name-enc": "enc-dev-partuuid",
+ Structure: []disks.Partition{
+ {
+ FilesystemLabel: "device-name-enc",
+ PartitionUUID: "enc-dev-partuuid",
+ },
},
}
@@ -1817,8 +1826,11 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyBadJ
defer restore()
mockDiskWithEncDev := &disks.MockDiskMapping{
- FilesystemLabelToPartUUID: map[string]string{
- "device-name-enc": "enc-dev-partuuid",
+ Structure: []disks.Partition{
+ {
+ FilesystemLabel: "device-name-enc",
+ PartitionUUID: "enc-dev-partuuid",
+ },
},
}
diff --git a/secboot/secboot_tpm.go b/secboot/secboot_tpm.go
index e6fdc9aa76..815b9d45cd 100644
--- a/secboot/secboot_tpm.go
+++ b/secboot/secboot_tpm.go
@@ -28,8 +28,6 @@ import (
"github.com/canonical/go-tpm2"
sb "github.com/snapcore/secboot"
- sb_efi "github.com/snapcore/secboot/efi"
- sb_tpm2 "github.com/snapcore/secboot/tpm2"
"golang.org/x/xerrors"
"github.com/snapcore/snapd/asserts"
@@ -46,18 +44,17 @@ const (
)
var (
- sbConnectToDefaultTPM = sb_tpm2.ConnectToDefaultTPM
- sbMeasureSnapSystemEpochToTPM = sb_tpm2.MeasureSnapSystemEpochToTPM
- sbMeasureSnapModelToTPM = sb_tpm2.MeasureSnapModelToTPM
- sbBlockPCRProtectionPolicies = sb_tpm2.BlockPCRProtectionPolicies
- sbActivateVolumeWithTPMSealedKey = sb_tpm2.ActivateVolumeWithSealedKey
- sbefiAddSecureBootPolicyProfile = sb_efi.AddSecureBootPolicyProfile
- sbefiAddBootManagerProfile = sb_efi.AddBootManagerProfile
- sbefiAddSystemdStubProfile = sb_efi.AddSystemdStubProfile
- sbAddSnapModelProfile = sb_tpm2.AddSnapModelProfile
- sbSealKeyToTPMMultiple = sb_tpm2.SealKeyToTPMMultiple
- sbUpdateKeyPCRProtectionPolicyMultiple = sb_tpm2.UpdateKeyPCRProtectionPolicyMultiple
- sbReadSealedKeyObject = sb_tpm2.ReadSealedKeyObject
+ sbConnectToDefaultTPM = sb.ConnectToDefaultTPM
+ sbMeasureSnapSystemEpochToTPM = sb.MeasureSnapSystemEpochToTPM
+ sbMeasureSnapModelToTPM = sb.MeasureSnapModelToTPM
+ sbBlockPCRProtectionPolicies = sb.BlockPCRProtectionPolicies
+ sbActivateVolumeWithTPMSealedKey = sb.ActivateVolumeWithTPMSealedKey
+ sbAddEFISecureBootPolicyProfile = sb.AddEFISecureBootPolicyProfile
+ sbAddEFIBootManagerProfile = sb.AddEFIBootManagerProfile
+ sbAddSystemdEFIStubProfile = sb.AddSystemdEFIStubProfile
+ sbAddSnapModelProfile = sb.AddSnapModelProfile
+ sbSealKeyToTPMMultiple = sb.SealKeyToTPMMultiple
+ sbUpdateKeyPCRProtectionPolicyMultiple = sb.UpdateKeyPCRProtectionPolicyMultiple
randutilRandomKernelUUID = randutil.RandomKernelUUID
@@ -68,7 +65,7 @@ var (
_ (sb.SnapModel) = ModelForSealing(nil)
)
-func isTPMEnabledImpl(tpm *sb_tpm2.Connection) bool {
+func isTPMEnabledImpl(tpm *sb.TPMConnection) bool {
return tpm.IsEnabled()
}
@@ -122,15 +119,15 @@ func checkSecureBootEnabled() error {
// for measurement from the initramfs.
const initramfsPCR = 12
-func insecureConnectToTPM() (*sb_tpm2.Connection, error) {
+func insecureConnectToTPM() (*sb.TPMConnection, error) {
return sbConnectToDefaultTPM()
}
-func measureWhenPossible(whatHow func(tpm *sb_tpm2.Connection) error) error {
+func measureWhenPossible(whatHow func(tpm *sb.TPMConnection) error) error {
// the model is ready, we're good to try measuring it now
tpm, err := insecureConnectToTPM()
if err != nil {
- if xerrors.Is(err, sb_tpm2.ErrNoTPM2Device) {
+ if xerrors.Is(err, sb.ErrNoTPM2Device) {
return nil
}
return fmt.Errorf("cannot open TPM connection: %v", err)
@@ -147,7 +144,7 @@ func measureWhenPossible(whatHow func(tpm *sb_tpm2.Connection) error) error {
// MeasureSnapSystemEpochWhenPossible measures the snap system epoch only if the
// TPM device is available. If there's no TPM device success is returned.
func MeasureSnapSystemEpochWhenPossible() error {
- measure := func(tpm *sb_tpm2.Connection) error {
+ measure := func(tpm *sb.TPMConnection) error {
return sbMeasureSnapSystemEpochToTPM(tpm, initramfsPCR)
}
@@ -161,7 +158,7 @@ func MeasureSnapSystemEpochWhenPossible() error {
// MeasureSnapModelWhenPossible measures the snap model only if the TPM device is
// available. If there's no TPM device success is returned.
func MeasureSnapModelWhenPossible(findModel func() (*asserts.Model, error)) error {
- measure := func(tpm *sb_tpm2.Connection) error {
+ measure := func(tpm *sb.TPMConnection) error {
model, err := findModel()
if err != nil {
return err
@@ -179,7 +176,7 @@ func MeasureSnapModelWhenPossible(findModel func() (*asserts.Model, error)) erro
func lockTPMSealedKeys() error {
tpm, tpmErr := sbConnectToDefaultTPM()
if tpmErr != nil {
- if xerrors.Is(tpmErr, sb_tpm2.ErrNoTPM2Device) {
+ if xerrors.Is(tpmErr, sb.ErrNoTPM2Device) {
logger.Noticef("cannot open TPM connection: %v", tpmErr)
return nil
}
@@ -211,7 +208,7 @@ func unlockVolumeUsingSealedKeyTPM(name, sealedEncryptionKeyFile, sourceDevice,
// Obtain a TPM connection.
tpm, tpmErr := sbConnectToDefaultTPM()
if tpmErr != nil {
- if !xerrors.Is(tpmErr, sb_tpm2.ErrNoTPM2Device) {
+ if !xerrors.Is(tpmErr, sb.ErrNoTPM2Device) {
return res, fmt.Errorf("cannot unlock encrypted device %q: %v", name, tpmErr)
}
logger.Noticef("cannot open TPM connection: %v", tpmErr)
@@ -248,10 +245,10 @@ func isActivatedWithRecoveryKey(err error) bool {
if err == nil {
return false
}
- // with non-nil err, we should check for err being ActivateWithSealedKeyError
+ // with non-nil err, we should check for err being ActivateWithTPMSealedKeyError
// and RecoveryKeyUsageErr inside that being nil - this indicates that the
// recovery key was used to unlock it
- activateErr, ok := err.(*sb_tpm2.ActivateWithSealedKeyError)
+ activateErr, ok := err.(*sb.ActivateWithTPMSealedKeyError)
if !ok {
return false
}
@@ -275,7 +272,7 @@ func activateVolOpts(allowRecoveryKey bool) *sb.ActivateVolumeOptions {
// unlockEncryptedPartitionWithSealedKey unseals the keyfile and opens an encrypted
// device. If activation with the sealed key fails, this function will attempt to
// activate it with the fallback recovery key instead.
-func unlockEncryptedPartitionWithSealedKey(tpm *sb_tpm2.Connection, name, device, keyfile string, allowRecovery bool) (UnlockMethod, error) {
+func unlockEncryptedPartitionWithSealedKey(tpm *sb.TPMConnection, name, device, keyfile string, allowRecovery bool) (UnlockMethod, error) {
options := activateVolOpts(allowRecovery)
// XXX: pinfile is currently not used
activated, err := sbActivateVolumeWithTPMSealedKey(tpm, name, device, keyfile, nil, options)
@@ -328,15 +325,15 @@ func SealKeys(keys []SealKeyRequest, params *SealKeysParams) error {
}
// Seal the provided keys to the TPM
- creationParams := sb_tpm2.KeyCreationParams{
+ creationParams := sb.KeyCreationParams{
PCRProfile: pcrProfile,
PCRPolicyCounterHandle: tpm2.Handle(params.PCRPolicyCounterHandle),
AuthKey: params.TPMPolicyAuthKey,
}
- sbKeys := make([]*sb_tpm2.SealKeyRequest, 0, len(keys))
+ sbKeys := make([]*sb.SealKeyRequest, 0, len(keys))
for i := range keys {
- sbKeys = append(sbKeys, &sb_tpm2.SealKeyRequest{
+ sbKeys = append(sbKeys, &sb.SealKeyRequest{
Key: keys[i].Key,
Path: keys[i].KeyFile,
})
@@ -382,25 +379,15 @@ func ResealKeys(params *ResealKeysParams) error {
return fmt.Errorf("cannot read the policy auth key file: %v", err)
}
- numSealedKeyObjects := len(params.KeyFiles)
- sealedKeyObjects := make([]*sb_tpm2.SealedKeyObject, 0, numSealedKeyObjects)
- for _, keyfile := range params.KeyFiles {
- sealedKeyObject, err := sbReadSealedKeyObject(keyfile)
- if err != nil {
- return err
- }
- sealedKeyObjects = append(sealedKeyObjects, sealedKeyObject)
- }
-
- return sbUpdateKeyPCRProtectionPolicyMultiple(tpm, sealedKeyObjects, authKey, pcrProfile)
+ return sbUpdateKeyPCRProtectionPolicyMultiple(tpm, params.KeyFiles, authKey, pcrProfile)
}
-func buildPCRProtectionProfile(modelParams []*SealKeyModelParams) (*sb_tpm2.PCRProtectionProfile, error) {
+func buildPCRProtectionProfile(modelParams []*SealKeyModelParams) (*sb.PCRProtectionProfile, error) {
numModels := len(modelParams)
- modelPCRProfiles := make([]*sb_tpm2.PCRProtectionProfile, 0, numModels)
+ modelPCRProfiles := make([]*sb.PCRProtectionProfile, 0, numModels)
for _, mp := range modelParams {
- modelProfile := sb_tpm2.NewPCRProtectionProfile()
+ modelProfile := sb.NewPCRProtectionProfile()
loadSequences, err := buildLoadSequences(mp.EFILoadChains)
if err != nil {
@@ -408,7 +395,7 @@ func buildPCRProtectionProfile(modelParams []*SealKeyModelParams) (*sb_tpm2.PCRP
}
// Add EFI secure boot policy profile
- policyParams := sb_efi.SecureBootPolicyProfileParams{
+ policyParams := sb.EFISecureBootPolicyProfileParams{
PCRAlgorithm: tpm2.HashAlgorithmSHA256,
LoadSequences: loadSequences,
// TODO:UC20: set SignatureDbUpdateKeystore to support applying forbidden
@@ -417,34 +404,34 @@ func buildPCRProtectionProfile(modelParams []*SealKeyModelParams) (*sb_tpm2.PCRP
// ensure that the PCR profile is updated before/after sbkeysync executes.
}
- if err := sbefiAddSecureBootPolicyProfile(modelProfile, &policyParams); err != nil {
+ if err := sbAddEFISecureBootPolicyProfile(modelProfile, &policyParams); err != nil {
return nil, fmt.Errorf("cannot add EFI secure boot policy profile: %v", err)
}
// Add EFI boot manager profile
- bootManagerParams := sb_efi.BootManagerProfileParams{
+ bootManagerParams := sb.EFIBootManagerProfileParams{
PCRAlgorithm: tpm2.HashAlgorithmSHA256,
LoadSequences: loadSequences,
}
- if err := sbefiAddBootManagerProfile(modelProfile, &bootManagerParams); err != nil {
+ if err := sbAddEFIBootManagerProfile(modelProfile, &bootManagerParams); err != nil {
return nil, fmt.Errorf("cannot add EFI boot manager profile: %v", err)
}
// Add systemd EFI stub profile
if len(mp.KernelCmdlines) != 0 {
- systemdStubParams := sb_efi.SystemdStubProfileParams{
+ systemdStubParams := sb.SystemdEFIStubProfileParams{
PCRAlgorithm: tpm2.HashAlgorithmSHA256,
PCRIndex: initramfsPCR,
KernelCmdlines: mp.KernelCmdlines,
}
- if err := sbefiAddSystemdStubProfile(modelProfile, &systemdStubParams); err != nil {
+ if err := sbAddSystemdEFIStubProfile(modelProfile, &systemdStubParams); err != nil {
return nil, fmt.Errorf("cannot add systemd EFI stub profile: %v", err)
}
}
// Add snap model profile
if mp.Model != nil {
- snapModelParams := sb_tpm2.SnapModelProfileParams{
+ snapModelParams := sb.SnapModelProfileParams{
PCRAlgorithm: tpm2.HashAlgorithmSHA256,
PCRIndex: initramfsPCR,
Models: []sb.SnapModel{mp.Model},
@@ -457,9 +444,9 @@ func buildPCRProtectionProfile(modelParams []*SealKeyModelParams) (*sb_tpm2.PCRP
modelPCRProfiles = append(modelPCRProfiles, modelProfile)
}
- var pcrProfile *sb_tpm2.PCRProtectionProfile
+ var pcrProfile *sb.PCRProtectionProfile
if numModels > 1 {
- pcrProfile = sb_tpm2.NewPCRProtectionProfile().AddProfileOR(modelPCRProfiles...)
+ pcrProfile = sb.NewPCRProtectionProfile().AddProfileOR(modelPCRProfiles...)
} else {
pcrProfile = modelPCRProfiles[0]
}
@@ -469,7 +456,7 @@ func buildPCRProtectionProfile(modelParams []*SealKeyModelParams) (*sb_tpm2.PCRP
return pcrProfile, nil
}
-func tpmProvision(tpm *sb_tpm2.Connection, lockoutAuthFile string) error {
+func tpmProvision(tpm *sb.TPMConnection, lockoutAuthFile string) error {
// Create and save the lockout authorization file
lockoutAuth := make([]byte, 16)
// crypto rand is protected against short reads
@@ -484,19 +471,19 @@ func tpmProvision(tpm *sb_tpm2.Connection, lockoutAuthFile string) error {
// 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 := provisionTPM(tpm, sb_tpm2.ProvisionModeFull, lockoutAuth); err != nil {
+ if err := provisionTPM(tpm, sb.ProvisionModeFull, lockoutAuth); err != nil {
logger.Noticef("TPM provisioning error: %v", err)
return fmt.Errorf("cannot provision TPM: %v", err)
}
return nil
}
-func provisionTPMImpl(tpm *sb_tpm2.Connection, mode sb_tpm2.ProvisionMode, lockoutAuth []byte) error {
+func provisionTPMImpl(tpm *sb.TPMConnection, mode sb.ProvisionMode, lockoutAuth []byte) error {
return tpm.EnsureProvisioned(mode, lockoutAuth)
}
// buildLoadSequences builds EFI load image event trees from this package LoadChains
-func buildLoadSequences(chains []*LoadChain) (loadseqs []*sb_efi.ImageLoadEvent, err error) {
+func buildLoadSequences(chains []*LoadChain) (loadseqs []*sb.EFIImageLoadEvent, err error) {
// this will build load event trees for the current
// device configuration, e.g. something like:
//
@@ -508,7 +495,7 @@ func buildLoadSequences(chains []*LoadChain) (loadseqs []*sb_efi.ImageLoadEvent,
for _, chain := range chains {
// root of load events has source Firmware
- loadseq, err := chain.loadEvent(sb_efi.Firmware)
+ loadseq, err := chain.loadEvent(sb.Firmware)
if err != nil {
return nil, err
}
@@ -518,11 +505,11 @@ func buildLoadSequences(chains []*LoadChain) (loadseqs []*sb_efi.ImageLoadEvent,
}
// loadEvent builds the corresponding load event and its tree
-func (lc *LoadChain) loadEvent(source sb_efi.ImageLoadEventSource) (*sb_efi.ImageLoadEvent, error) {
- var next []*sb_efi.ImageLoadEvent
+func (lc *LoadChain) loadEvent(source sb.EFIImageLoadEventSource) (*sb.EFIImageLoadEvent, error) {
+ var next []*sb.EFIImageLoadEvent
for _, nextChain := range lc.Next {
// everything that is not the root has source shim
- ev, err := nextChain.loadEvent(sb_efi.Shim)
+ ev, err := nextChain.loadEvent(sb.Shim)
if err != nil {
return nil, err
}
@@ -532,26 +519,26 @@ func (lc *LoadChain) loadEvent(source sb_efi.ImageLoadEventSource) (*sb_efi.Imag
if err != nil {
return nil, err
}
- return &sb_efi.ImageLoadEvent{
+ return &sb.EFIImageLoadEvent{
Source: source,
Image: image,
Next: next,
}, nil
}
-func efiImageFromBootFile(b *bootloader.BootFile) (sb_efi.Image, error) {
+func efiImageFromBootFile(b *bootloader.BootFile) (sb.EFIImage, error) {
if b.Snap == "" {
if !osutil.FileExists(b.Path) {
return nil, fmt.Errorf("file %s does not exist", b.Path)
}
- return sb_efi.FileImage(b.Path), nil
+ return sb.FileEFIImage(b.Path), nil
}
snapf, err := snapfile.Open(b.Snap)
if err != nil {
return nil, err
}
- return sb_efi.SnapFileImage{
+ return sb.SnapFileEFIImage{
Container: snapf,
FileName: b.Path,
}, nil
diff --git a/secboot/test-data/keyfile b/secboot/test-data/keyfile
deleted file mode 100644
index c588b55e7e..0000000000
--- a/secboot/test-data/keyfile
+++ /dev/null
Binary files differ
diff --git a/spread.yaml b/spread.yaml
index c40aa5ff01..9203c0bd9c 100644
--- a/spread.yaml
+++ b/spread.yaml
@@ -462,6 +462,9 @@ debug-each: |
echo "# device cgroup $cgroup_dev"
cat "/sys/fs/cgroup/devices/$cgroup_dev/devices.list" || true
fi
+ else
+ echo "# snap confinement device filtering maps"
+ ls -l /sys/fs/bpf/snap || true
fi
case "$SPREAD_SYSTEM" in
diff --git a/tests/lib/nested.sh b/tests/lib/nested.sh
index 2d4378e5c7..4dc55b6bb4 100644
--- a/tests/lib/nested.sh
+++ b/tests/lib/nested.sh
@@ -1044,7 +1044,18 @@ nested_start_core_vm_unit() {
# Wait for the snap command to be available
nested_wait_for_snap_command
# Wait for snap seeding to be done
- nested_exec "sudo snap wait system seed.loaded"
+ # retry this wait command up to 3 times since we sometimes see races
+ # where the snap command appears, then immediately disappears and then
+ # re-appears immediately after and so the next command fails
+ attempts=0
+ until nested_exec "sudo snap wait system seed.loaded"; do
+ attempts=$(( attempts + 1))
+ if [ "$attempts" = 3 ]; then
+ echo "failed to wait for snap wait command to return successfully"
+ return 1
+ fi
+ sleep 1
+ done
# Copy tools to be used on tests
nested_prepare_tools
# Wait for cloud init to be done if the system is using cloud-init
diff --git a/tests/main/auto-refresh-gating/task.yaml b/tests/main/auto-refresh-gating/task.yaml
index fe93c3a3ce..a7f3cefd1a 100644
--- a/tests/main/auto-refresh-gating/task.yaml
+++ b/tests/main/auto-refresh-gating/task.yaml
@@ -68,6 +68,11 @@ execute: |
MATCH "pending: none" < "$DEBUG_LOG_FILE"
NOMATCH "version:" < "$DEBUG_LOG_FILE"
+ echo "Check that --hold output contains remaining hold time"
+ # we cannot match precisely, this might be 48h0m0s if we are lucky, or
+ # a tiny bit less depending on timing.
+ MATCH "hold: 4[78]h.*m.*s" < "$DEBUG_LOG_FILE"
+
echo "Ensure our content snap was held and is still at version 1"
snap list | MATCH "$CONTENT_SNAP_NAME +1\.0\.0"
# sanity check for the gating snap.
diff --git a/tests/main/cwd/task.yaml b/tests/main/cwd/task.yaml
index 40c617f207..a682ce0a9f 100644
--- a/tests/main/cwd/task.yaml
+++ b/tests/main/cwd/task.yaml
@@ -7,11 +7,16 @@ details: |
to the view inside the mount namespace. If the directory does not exist the
special fallback /var/lib/snapd/void is used.
+# ubuntu-14.04: the test sets up a user session, which requires more recent systemd
+systems: [-ubuntu-14.04-*]
+
prepare: |
"$TESTSTOOLS"/snaps-state install-local test-snapd-sh
+ tests.session -u test prepare
restore: |
rmdir /tmp/test || true
+ tests.session -u test restore
debug: |
# Much of what we do depends on permissions. If the permissions on those
@@ -39,7 +44,15 @@ execute: |
# namespace but has permissions preventing the user to enter it (e.g.
# via a symlink attack in /tmp) is remapped to a special directory.
# FIXME: su doesn't have /snap/bin in PATH.
- test "$(cd /root && su -c "snap run test-snapd-sh.sh -c pwd" test)" = "/var/lib/snapd/void"
+ case "$SPREAD_SYSTEM" in
+ fedora-*|opensuse-tumbleweed-*|arch-linux-*)
+ # nothiing, we have to go through tests.session which always starts in
+ # the $HOME directory
+ ;;
+ *)
+ test "$(cd /root && runuser -u test -- sh -c "snap run test-snapd-sh.sh -c pwd" )" = "/var/lib/snapd/void"
+ ;;
+ esac
# Since the void directory is used when there are insufficient permissions
# to enter the regular directory we must be able to go there in the first
diff --git a/tests/main/download-timeout/task.yaml b/tests/main/download-timeout/task.yaml
index cd948b521f..f8800547d8 100644
--- a/tests/main/download-timeout/task.yaml
+++ b/tests/main/download-timeout/task.yaml
@@ -38,7 +38,7 @@ execute: |
tc filter add dev ens4 parent ffff: protocol ip u32 match u32 0 0 police rate 32kbit burst 16k drop flowid :1
echo "Installing a large snap fails if connection is very slow"
- snap install lxd 2>&1 | MATCH "download too slow:"
+ snap install --edge test-snapd-huge 2>&1 | MATCH "download too slow:"
echo "Downloading a large snap fails too"
- snap download lxd 2>&1 | MATCH "download too slow:"
+ snap download --edge test-snapd-huge 2>&1 | MATCH "download too slow:"
diff --git a/tests/main/lxd-postrm-purge/task.yaml b/tests/main/lxd-postrm-purge/task.yaml
index 50ecbef7fd..de36c79ad9 100644
--- a/tests/main/lxd-postrm-purge/task.yaml
+++ b/tests/main/lxd-postrm-purge/task.yaml
@@ -3,7 +3,8 @@ summary: Check that package remove and purge works inside LXD containers
# Since it's only apt remove --purge and lxd tests are rather long, limit to a
# couple of systems only. The postrm purge is more thoroughly checked in
# tests/main/postrm-purge.
-systems: [ubuntu-18.04-*, ubuntu-20.04-*]
+# ubuntu-18.04-32: i386 is not supported by lxd
+systems: [ubuntu-18.04-64, ubuntu-20.04-*]
# start early
priority: 1000
diff --git a/tests/main/lxd-services-smoke/task.yaml b/tests/main/lxd-services-smoke/task.yaml
index 1f207dd47d..c048c620c7 100644
--- a/tests/main/lxd-services-smoke/task.yaml
+++ b/tests/main/lxd-services-smoke/task.yaml
@@ -10,7 +10,8 @@ details: |
https://bugs.launchpad.net/snapd/+bug/1899614, this test should never fail on
any of the above.
-systems: [ubuntu-18.04*, ubuntu-20.04*]
+# ubuntu-18.04-32: i386 is not supported by lxd
+systems: [ubuntu-18.04-64, ubuntu-20.04*]
execute: |
echo "Installing lxd snap"
diff --git a/tests/main/lxd/task.yaml b/tests/main/lxd/task.yaml
index 77fb36f2bb..f211ed8bd0 100644
--- a/tests/main/lxd/task.yaml
+++ b/tests/main/lxd/task.yaml
@@ -7,7 +7,8 @@ backends: [-autopkgtest]
# Only run this on ubuntu 16+, lxd will not work on !ubuntu systems
# currently nor on ubuntu 14.04
# TODO: enable for ubuntu-21.10+ once the lxd image is published
-systems: [ubuntu-16*, ubuntu-18*, ubuntu-20*, ubuntu-21.04-*, ubuntu-core-*]
+# ubuntu-18.04-32: i386 is not supported by lxd
+systems: [ubuntu-16*, ubuntu-18.04-64, ubuntu-20*, ubuntu-21.04-*, ubuntu-core-*]
# Start before anything else as it can take a really long time.
priority: 1000
diff --git a/tests/main/non-home/task.yaml b/tests/main/non-home/task.yaml
index fabd4161f0..f475323b91 100644
--- a/tests/main/non-home/task.yaml
+++ b/tests/main/non-home/task.yaml
@@ -1,7 +1,8 @@
summary: Ensure running on none /home dirs gives a useful error
-# limit to ubuntu for easier user creation
-systems: [ubuntu-1*, ubuntu-2*]
+# limit to ubuntu for easier user creation, skip 14.04 due to missing session
+# setup
+systems: [ubuntu-16*, ubuntu-18*, ubuntu-2*]
environment:
TUSER: jim
@@ -11,19 +12,24 @@ environment:
prepare: |
echo "create a non home user"
adduser --home "$THOME" "$TUSER"
+ tests.session -u test prepare
+ tests.session -u "$TUSER" prepare
restore: |
+ tests.session -u "$TUSER" restore
+ tests.session -u test restore
"$TESTSTOOLS"/user-state remove-with-group "$TUSER"
+
execute: |
echo "Install a snap"
snap install test-snapd-sh
echo "Run as the test user (normal home dir)"
- su -c "snap run test-snapd-sh.sh -c 'echo foo'" test | MATCH foo
+ tests.session -u test exec sh -c "snap run test-snapd-sh.sh -c 'echo foo'" | MATCH foo
echo "Run as the non-home user (home dir outside of /home) - this will fail"
- not su -c "snap run test-snapd-sh.sh -c 'echo foo'" "$TUSER" 2>stderr.log
+ not tests.session -u "$TUSER" exec sh -c "snap run test-snapd-sh.sh -c 'echo foo'" 2>stderr.log
echo "Ensure we get a useful error message"
MATCH "Sorry, home directories outside of /home" < stderr.log
diff --git a/tests/main/parallel-install-basic/task.yaml b/tests/main/parallel-install-basic/task.yaml
index 04a5abe097..9194deebd8 100644
--- a/tests/main/parallel-install-basic/task.yaml
+++ b/tests/main/parallel-install-basic/task.yaml
@@ -1,5 +1,8 @@
summary: Checks for parallel installation of a local snap files
+# ubuntu-14.04: the test sets up a user session, which requires more recent systemd
+systems: [-ubuntu-14.04-*]
+
prepare: |
# ensure we have no snap user data directory yet
rm -rf /home/test/snap
@@ -7,22 +10,26 @@ prepare: |
snap set system experimental.parallel-instances=true
+ tests.session -u test prepare
+
restore: |
snap set system experimental.parallel-instances=null
+ tests.session -u test restore
+
execute: |
"$TESTSTOOLS"/snaps-state install-local test-snapd-sh
"$TESTSTOOLS"/snaps-state install-local-as test-snapd-sh test-snapd-sh_foo
- su -l -c '! test -d ~/snap/test-snapd-sh' test
- su -l -c '! test -d ~/snap/test-snapd-sh_foo' test
+ tests.session -u test exec sh -c '! test -d ~/snap/test-snapd-sh'
+ tests.session -u test exec sh -c '! test -d ~/snap/test-snapd-sh_foo'
- su -l -c 'test-snapd-sh_foo.sh -c "echo foo"' test | MATCH foo
- su -l -c 'test -d ~/snap/test-snapd-sh' test
- su -l -c 'test -d ~/snap/test-snapd-sh_foo' test
+ tests.session -u test exec sh -c 'test-snapd-sh_foo.sh -c "echo foo"' | MATCH foo
+ tests.session -u test exec sh -c 'test -d ~/snap/test-snapd-sh'
+ tests.session -u test exec sh -c 'test -d ~/snap/test-snapd-sh_foo'
# instance environment variables are correctly set up
- su -l -c 'test-snapd-sh_foo.sh -c "env"' test > snap_foo-env.txt
+ tests.session -u test exec sh -c 'test-snapd-sh_foo.sh -c "env"' test > snap_foo-env.txt
MATCH 'SNAP_INSTANCE_NAME=test-snapd-sh_foo' < snap_foo-env.txt
MATCH 'SNAP_NAME=test-snapd-sh' < snap_foo-env.txt
MATCH 'SNAP_INSTANCE_KEY=foo' < snap_foo-env.txt
@@ -33,7 +40,7 @@ execute: |
MATCH 'SNAP_USER_COMMON=/home/test/snap/test-snapd-sh_foo/common' < snap_foo-env.txt
# and non-instance one's are too
- su -l -c 'test-snapd-sh.sh -c env' test > snap-env.txt
+ tests.session -u test exec sh -c 'test-snapd-sh.sh -c env' test > snap-env.txt
MATCH 'SNAP_INSTANCE_NAME=test-snapd-sh' < snap-env.txt
MATCH 'SNAP_NAME=test-snapd-sh' < snap-env.txt
MATCH 'SNAP_INSTANCE_KEY=$' < snap-env.txt
@@ -50,15 +57,15 @@ execute: |
echo "Make sure snap data writes and reads work"
# instance can access its data
- su -l -c "test-snapd-sh_foo.sh -c 'cat \$SNAP_COMMON/foobar/data'" test | MATCH canary-instance
+ tests.session -u test exec sh -c "test-snapd-sh_foo.sh -c 'cat \$SNAP_COMMON/foobar/data'" | MATCH canary-instance
# non-instance sees its data
- su -l -c "test-snapd-sh.sh -c 'cat \$SNAP_COMMON/foobar/data'" test | MATCH canary-regular
+ tests.session -u test exec sh -c "test-snapd-sh.sh -c 'cat \$SNAP_COMMON/foobar/data'" | MATCH canary-regular
# instance can write data
- su -l -c "test-snapd-sh_foo.sh -c 'echo hello from instance \$SNAP_INSTANCE_NAME > \$SNAP_COMMON/foobar/hello'" test
+ tests.session -u test exec sh -c "test-snapd-sh_foo.sh -c 'echo hello from instance \$SNAP_INSTANCE_NAME > \$SNAP_COMMON/foobar/hello'"
MATCH 'hello from instance test-snapd-sh_foo' < /var/snap/test-snapd-sh_foo/common/foobar/hello
# and the file is not visible in non instance snap
- su -l -c "test-snapd-sh.sh -c 'cat \$SNAP_COMMON/foobar/hello || true'" test 2>&1 | MATCH 'cat: /var/snap/test-snapd-sh/common/foobar/hello: No such file or directory'
+ tests.session -u test exec sh -c "test-snapd-sh.sh -c 'cat \$SNAP_COMMON/foobar/hello || true'" 2>&1 | MATCH 'cat: /var/snap/test-snapd-sh/common/foobar/hello: No such file or directory'
echo "Make sure snap user data writes work"
echo canary-instance-snap > /home/test/snap/test-snapd-sh_foo/x1/canary
@@ -67,16 +74,16 @@ execute: |
chown test:test /home/test/snap/test-snapd-sh_foo/common/canary
# instance snap can write to user data
- su -l -c "test-snapd-sh_foo.sh -c 'echo hello user data from \$SNAP_INSTANCE_NAME > \$SNAP_USER_DATA/data'" test
+ tests.session -u test exec sh -c "test-snapd-sh_foo.sh -c 'echo hello user data from \$SNAP_INSTANCE_NAME > \$SNAP_USER_DATA/data'"
MATCH 'hello user data from test-snapd-sh_foo' < /home/test/snap/test-snapd-sh_foo/x1/data
# the file not present in non-instance snap data
not test -f /home/test/snap/test-snapd-sh/x1/data
# instance snap can write to common user data
- su -l -c "test-snapd-sh_foo.sh -c 'echo hello user data from \$SNAP_INSTANCE_NAME > \$SNAP_USER_COMMON/data'" test
+ tests.session -u test exec sh -c "test-snapd-sh_foo.sh -c 'echo hello user data from \$SNAP_INSTANCE_NAME > \$SNAP_USER_COMMON/data'"
MATCH 'hello user data from test-snapd-sh_foo' < /home/test/snap/test-snapd-sh_foo/common/data
# the file not present in non-instance snap data
not test -f /home/test/snap/test-snapd-sh/common/data
- su -l -c "test-snapd-sh_foo.sh -c 'cat \$SNAP_USER_COMMON/canary'" test | MATCH canary-instance-common
- su -l -c "test-snapd-sh_foo.sh -c 'cat \$SNAP_USER_DATA/canary'" test | MATCH canary-instance-snap
+ tests.session -u test exec sh -c "test-snapd-sh_foo.sh -c 'cat \$SNAP_USER_COMMON/canary'" | MATCH canary-instance-common
+ tests.session -u test exec sh -c "test-snapd-sh_foo.sh -c 'cat \$SNAP_USER_DATA/canary'" | MATCH canary-instance-snap
diff --git a/tests/main/security-private-tmp/task.yaml b/tests/main/security-private-tmp/task.yaml
index 17e0e9b40d..c3cb2653df 100644
--- a/tests/main/security-private-tmp/task.yaml
+++ b/tests/main/security-private-tmp/task.yaml
@@ -1,7 +1,8 @@
summary: Ensure that the security rules for private tmp are in place.
# ppc64el disabled because of https://bugs.launchpad.net/snappy/+bug/1655594
-systems: [-ubuntu-core-*, -ubuntu-*-ppc64el]
+# ubuntu-14.04: the test sets up a user session, which requires more recent systemd
+systems: [-ubuntu-core-*, -ubuntu-*-ppc64el, -ubuntu-14.04-*]
environment:
SNAP_INSTALL_DIR: $(pwd)/snap-install-dir
@@ -16,9 +17,11 @@ prepare: |
sed -i 's/test-snapd-sh/not-test-snapd-sh/g' "$SNAP_INSTALL_DIR/meta/snap.yaml"
snap pack "$SNAP_INSTALL_DIR"
snap install --dangerous not-test-snapd-sh_1.0_all.snap
+ tests.session -u test prepare
restore: |
rm -rf "$SNAP_INSTALL_DIR" /tmp/foo /tmp/snap.not-test-snapd-sh /tmp/snap.test-snapd-sh/
+ tests.session -u test restore
execute: |
SNAP_MOUNT_DIR="$(os.paths snap-mount-dir)"
diff --git a/tests/main/selinux-snap-restorecon/task.yaml b/tests/main/selinux-snap-restorecon/task.yaml
index a80034d7ff..fa9f74930b 100644
--- a/tests/main/selinux-snap-restorecon/task.yaml
+++ b/tests/main/selinux-snap-restorecon/task.yaml
@@ -10,19 +10,21 @@ prepare: |
if [ -d /home/test/snap ]; then
mv /home/test/snap /home/test/snap.old
fi
+ tests.session -u test prepare
restore: |
rm -rf /home/test/snap
if [ -d /home/test/snap.old ]; then
mv /home/test/snap.old /home/test/snap
fi
+ tests.session -u test restore
execute: |
# TODO: extend the test to work for root when the policy is fixed for admin_home_t
# TODO: use snap debug sandbox-features once selinux backend is added
test ! -d /home/test/snap
- su -c "test-snapd-sh.sh -c 'touch \$SNAP_USER_DATA/foo'" test
+ tests.session -u test exec sh -c "test-snapd-sh.sh -c 'touch \$SNAP_USER_DATA/foo'"
test -d /home/test/snap
echo "The snap user directory and data inside has the right context"
@@ -39,7 +41,7 @@ execute: |
ls -dZ /home/test/snap | MATCH ':unlabeled_t:'
echo "It gets restored recursively"
- su -c "test-snapd-sh.sh -c 'id -Z'" test
+ tests.session -u test exec sh -c "test-snapd-sh.sh -c 'id -Z'"
ls -dZ /home/test/snap /home/test/snap/test-snapd-sh /home/test/snap/test-snapd-sh/current/foo > test-labels
MATCH '^.*:snappy_home_t:.*/home/test/snap$' < test-labels
@@ -48,7 +50,7 @@ execute: |
echo "Restoring happens only when the context of \$HOME/snap is incorrect"
chcon -t unlabeled_t -R /home/test/snap/test-snapd-sh/current/foo
- su -c "test-snapd-sh.sh -c 'id -Z'" test
+ tests.session -u test exec sh -c "test-snapd-sh.sh -c 'id -Z'"
ls -dZ /home/test/snap /home/test/snap/test-snapd-sh /home/test/snap/test-snapd-sh/current/foo > test-labels
MATCH '^.*:snappy_home_t:.*/home/test/snap$' < test-labels
diff --git a/tests/main/snap-confine-drops-sys-admin/task.yaml b/tests/main/snap-confine-drops-sys-admin/task.yaml
index 8e184b0d83..b53ca628cc 100644
--- a/tests/main/snap-confine-drops-sys-admin/task.yaml
+++ b/tests/main/snap-confine-drops-sys-admin/task.yaml
@@ -4,7 +4,8 @@ summary: ensure that snap-confine drops CAP_SYS_ADMIN for non-root
# building the support C program. In the future it might be improved with the
# use of the classic snap where we just use classic to build the helper.
# Arch, CentOS, AMZN2, openSUSE do not have a static version of libcap.
-systems: [-ubuntu-core-*, -arch-linux-*, -centos-*, -amazon-linux-*, -opensuse-* ]
+# Ubuntu 14.04 does not have a proper session setup.
+systems: [-ubuntu-core-*, -arch-linux-*, -centos-*, -amazon-linux-*, -opensuse-*, -ubuntu-14.04-*]
environment:
# This is used to abbreviate some of the paths below.
@@ -23,6 +24,11 @@ prepare: |
# in the base snap.
gcc -Wall -Wextra -Werror ./has-sys-admin.c -o "$P/has-sys-admin" -lcap -static
+ tests.session -u test prepare
+
+restore: |
+ tests.session -u test restore
+
execute: |
echo "The test executables files have the expected mode and ownership"
#shellcheck disable=SC2012
@@ -33,8 +39,8 @@ execute: |
# user. The "test" user inside the spread suite is guaranteed to have
# UID/GID of 12345. First test that the program correctly detects that the
# test user does not have CAP_SYS_ADMIN and root does.
- su -l -c "$P/has-sys-admin" test | MATCH 'Does not have cap_sys_admin'
- "$P/has-sys-admin" | MATCH 'Has cap_sys_admin'
+ tests.session -u test exec sh -c "$P/has-sys-admin" | MATCH 'Does not have cap_sys_admin'
+ "$P/has-sys-admin" | MATCH 'Has cap_sys_admin'
echo "Running under snap-confine as non-root and root"
# This is the same as the two above but it goes through snap-confine as
@@ -43,17 +49,20 @@ execute: |
# as there are two shell expansions done. Also, we are using "snap run
# test-snapd-sh" in order to ensure that we can start the program even if
# su/sudo's secure PATH does not contain the snap bin directory.
- su -l -c "snap run test-snapd-sh.sh -c '\$SNAP_COMMON/has-sys-admin'" test | MATCH 'Does not have cap_sys_admin'
+ tests.session -u test exec sh -c "snap run test-snapd-sh.sh -c '\$SNAP_COMMON/has-sys-admin'" | \
+ MATCH 'Does not have cap_sys_admin'
#shellcheck disable=SC2016
- snap run test-snapd-sh.sh -c '$SNAP_COMMON/has-sys-admin' | MATCH 'Has cap_sys_admin'
+ snap run test-snapd-sh.sh -c '$SNAP_COMMON/has-sys-admin' | MATCH 'Has cap_sys_admin'
# We should preserve CAP_SYS_ADMIN when run under sudo from non-root or
# root
echo "Running under sudo and under snap-confine from non-root and root"
- su -l -c "sudo snap run test-snapd-sh.sh -c '\$SNAP_COMMON/has-sys-admin'" test | MATCH 'Has cap_sys_admin'
- sudo snap run test-snapd-sh.sh -c '$SNAP_COMMON/has-sys-admin' | MATCH 'Has cap_sys_admin'
+ tests.session -u test exec sh -c "sudo snap run test-snapd-sh.sh -c '\$SNAP_COMMON/has-sys-admin'" | \
+ MATCH 'Has cap_sys_admin'
+ sudo snap run test-snapd-sh.sh -c '$SNAP_COMMON/has-sys-admin' | MATCH 'Has cap_sys_admin'
# We should drop CAP_SYS_ADMIN when run with sudo -u test
echo "Running under sudo -u non-root under snap-confine from non-root and root"
- su -l -c "sudo -u test snap run test-snapd-sh.sh -c '\$SNAP_COMMON/has-sys-admin'" test | MATCH 'Does not have cap_sys_admin'
- sudo -u test snap run test-snapd-sh.sh -c '$SNAP_COMMON/has-sys-admin' | MATCH 'Does not have cap_sys_admin'
+ tests.session -u test exec sh -c "sudo -u test snap run test-snapd-sh.sh -c '\$SNAP_COMMON/has-sys-admin'" | \
+ MATCH 'Does not have cap_sys_admin'
+ sudo -u test snap run test-snapd-sh.sh -c '$SNAP_COMMON/has-sys-admin' | MATCH 'Does not have cap_sys_admin'
diff --git a/tests/main/snap-confine-privs/task.yaml b/tests/main/snap-confine-privs/task.yaml
index 9312b04ce1..babba413a1 100644
--- a/tests/main/snap-confine-privs/task.yaml
+++ b/tests/main/snap-confine-privs/task.yaml
@@ -10,7 +10,8 @@ details: |
# This test is not executed on a core system simply because of the hassle of
# building the support C program. In the future it might be improved with the
# use of the classic snap where we just use classic to build the helper.
-systems: [-ubuntu-core-*]
+# ubuntu-14.04: the test sets up a user session, which requires more recent systemd
+systems: [-ubuntu-core-*, -ubuntu-14.04-*]
environment:
# This is used to abbreviate some of the paths below.
@@ -33,6 +34,11 @@ prepare: |
chgrp root "$P/uids-and-gids-setgid"
chmod 2755 "$P/uids-and-gids-setgid"
+ tests.session -u test prepare
+
+restore: |
+ tests.session -u test restore
+
execute: |
echo "The test executables files have the expected mode and ownership"
#shellcheck disable=SC2012
@@ -45,15 +51,15 @@ execute: |
echo "Running as regular user"
# Spread runs all tests as root so we're using su to switch to the "test" user.
# The "test" user inside the spread suite is guaranteed to have UID/GID of 12345.
- su -l -c "$P/uids-and-gids" test | MATCH 'ruid=12345 euid=12345 suid=12345 rgid=12345 egid=12345 sgid=12345'
- su -l -c "$P/uids-and-gids-setuid" test | MATCH 'ruid=12345 euid=0 suid=0 rgid=12345 egid=12345 sgid=12345'
- su -l -c "$P/uids-and-gids-setgid" test | MATCH 'ruid=12345 euid=12345 suid=12345 rgid=12345 egid=0 sgid=0 '
+ tests.session -u test exec "$P/uids-and-gids" | MATCH 'ruid=12345 euid=12345 suid=12345 rgid=12345 egid=12345 sgid=12345'
+ tests.session -u test exec "$P/uids-and-gids-setuid" | MATCH 'ruid=12345 euid=0 suid=0 rgid=12345 egid=12345 sgid=12345'
+ tests.session -u test exec "$P/uids-and-gids-setgid" | MATCH 'ruid=12345 euid=12345 suid=12345 rgid=12345 egid=0 sgid=0 '
echo "Running as regular user via sudo"
# This is same as above except that we're also using sudo
- su -l -c "sudo $P/uids-and-gids" test | MATCH 'ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 '
- su -l -c "sudo $P/uids-and-gids-setuid" test | MATCH 'ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 '
- su -l -c "sudo $P/uids-and-gids-setgid" test | MATCH 'ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 '
+ tests.session -u test exec sh -c "sudo $P/uids-and-gids" | MATCH 'ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 '
+ tests.session -u test exec sh -c "sudo $P/uids-and-gids-setuid" | MATCH 'ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 '
+ tests.session -u test exec sh -c "sudo $P/uids-and-gids-setgid" | MATCH 'ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 '
echo "Running as regular user under snap-confine"
# This is the same as the two above but it goes through snap-confine as
@@ -61,12 +67,12 @@ execute: |
# expansions done. Note that we are using "snap run test-snapd-sh" in order
# to ensure that we can start the progam even if su/sudo's secure PATH does
# not contain the snap bin directory.
- su -l -c "snap run test-snapd-sh.sh -c '\$SNAP_COMMON/uids-and-gids'" test | MATCH 'ruid=12345 euid=12345 suid=12345 rgid=12345 egid=12345 sgid=12345'
- su -l -c "snap run test-snapd-sh.sh -c '\$SNAP_COMMON/uids-and-gids-setuid'" test | MATCH 'ruid=12345 euid=0 suid=0 rgid=12345 egid=12345 sgid=12345'
- su -l -c "snap run test-snapd-sh.sh -c '\$SNAP_COMMON/uids-and-gids-setgid'" test | MATCH 'ruid=12345 euid=12345 suid=12345 rgid=12345 egid=0 sgid=0 '
+ tests.session -u test exec sh -c "snap run test-snapd-sh.sh -c '\$SNAP_COMMON/uids-and-gids'" | MATCH 'ruid=12345 euid=12345 suid=12345 rgid=12345 egid=12345 sgid=12345'
+ tests.session -u test exec sh -c "snap run test-snapd-sh.sh -c '\$SNAP_COMMON/uids-and-gids-setuid'" | MATCH 'ruid=12345 euid=0 suid=0 rgid=12345 egid=12345 sgid=12345'
+ tests.session -u test exec sh -c "snap run test-snapd-sh.sh -c '\$SNAP_COMMON/uids-and-gids-setgid'" | MATCH 'ruid=12345 euid=12345 suid=12345 rgid=12345 egid=0 sgid=0 '
echo "Running as regular user, uder snap-conifne under sudo"
# This is the same one as the previous one but also using sudo.
- su -l -c "sudo snap run test-snapd-sh.sh -c '\$SNAP_COMMON/uids-and-gids'" test | MATCH 'ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 '
- su -l -c "sudo snap run test-snapd-sh.sh -c '\$SNAP_COMMON/uids-and-gids-setuid'" test | MATCH 'ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 '
- su -l -c "sudo snap run test-snapd-sh.sh -c '\$SNAP_COMMON/uids-and-gids-setgid'" test | MATCH 'ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 '
+ tests.session -u test exec sh -c "sudo snap run test-snapd-sh.sh -c '\$SNAP_COMMON/uids-and-gids'" | MATCH 'ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 '
+ tests.session -u test exec sh -c "sudo snap run test-snapd-sh.sh -c '\$SNAP_COMMON/uids-and-gids-setuid'" | MATCH 'ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 '
+ tests.session -u test exec sh -c "sudo snap run test-snapd-sh.sh -c '\$SNAP_COMMON/uids-and-gids-setgid'" | MATCH 'ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 '
diff --git a/tests/main/snap-confine-undesired-mode-group/task.yaml b/tests/main/snap-confine-undesired-mode-group/task.yaml
index 9f9f594278..1c996d46ec 100644
--- a/tests/main/snap-confine-undesired-mode-group/task.yaml
+++ b/tests/main/snap-confine-undesired-mode-group/task.yaml
@@ -1,5 +1,8 @@
summary: the snap-{run,confine,exec} chain does not create files with undesired properties.
+# ubuntu-14.04: the test sets up a user session, which requires more recent systemd
+systems: [-ubuntu-14.04-*]
+
prepare: |
# Install a snap with opengl and joystick plugs.
# This gives us all of the usual snap-confine configuration, along with all
@@ -8,13 +11,15 @@ prepare: |
snap install --dangerous ./test-snapd-app_1.0_all.snap
snap connect test-snapd-app:opengl
snap connect test-snapd-app:joystick
+ tests.session -u test prepare
restore: |
+ tests.session -u test restore
rm -rf /tmp/snap.test-snapd-app
execute: |
# Run the snap as a non-root user.
- su test -c 'snap run test-snapd-app.sh -c /bin/true'
+ tests.session -u test exec sh -c 'snap run test-snapd-app.sh -c /bin/true'
# Look for files that are owned by the test user, group owned by the test
# user or are world-writable in /run/snapd, /sys/fs/cgroup and in /tmp
diff --git a/tests/main/snapd-sigterm/task.yaml b/tests/main/snapd-sigterm/task.yaml
index 03e8debfc0..2f5ef6e0cc 100644
--- a/tests/main/snapd-sigterm/task.yaml
+++ b/tests/main/snapd-sigterm/task.yaml
@@ -11,7 +11,7 @@ restore: |
execute: |
echo "Make a request, keep the connection open"
- nc -U /run/snapd.socket << EOF &
+ nc -q 20 -U /run/snapd.socket << EOF &
GET /v2/apps HTTP/1.1
Host: localhost
@@ -21,7 +21,12 @@ execute: |
TEST_TIME0="$(date +'%s')"
systemctl stop snapd.service
- retry -n 10 sh -c 'systemctl status snapd.service | MATCH "inactive"'
+ # The systemctl command waits for the operation to complete, but just to be
+ # extra safe check that it's either inactive or has just been restarted. We
+ # don't want to stop the socket itself, as that might hide the issue we
+ # want to test.
+ retry -n 100 --wait 0.1 sh -c 'systemctl status snapd.service | MATCH "inactive"'
+
TEST_TIME1="$(date +'%s')"
if ((TEST_TIME1 > TEST_TIME0 + 5)); then
diff --git a/tests/main/snapd-snap/task.yaml b/tests/main/snapd-snap/task.yaml
index cac0a685c3..d68458f688 100644
--- a/tests/main/snapd-snap/task.yaml
+++ b/tests/main/snapd-snap/task.yaml
@@ -25,6 +25,10 @@ systems:
# locale, see https://bugs.launchpad.net/snapcraft/+bug/1922140
# a separate error occurs on centos 8, see https://bugs.launchpad.net/snapcraft/+bug/1922981
- -centos-*
+ # ubuntu-18.04-32: i386 is not supported by lxd
+ # TODO: enable i386 by using lxd 3.0, currently snapcraft is failing to get the
+ # lxd image when it is trying to build snapd
+ - -ubuntu-18.04-32
# Start early as it takes a long time.
priority: 100
diff --git a/tests/main/snapshot-users/task.yaml b/tests/main/snapshot-users/task.yaml
index 3133039469..bda95a9c24 100644
--- a/tests/main/snapshot-users/task.yaml
+++ b/tests/main/snapshot-users/task.yaml
@@ -1,13 +1,20 @@
summary: Check that the basic snapshots functionality works for different users
+# ubuntu-14.04: the test sets up a user session, which requires more recent systemd
+systems: [-ubuntu-14.04-*]
+
prepare: |
snap install test-snapd-sh
+ tests.session -u test prepare
+
+restore: |
+ tests.session -u test restore
execute: |
# use the snaps, so they create the dirs
test-snapd-sh.sh -c 'true'
SNAP_MOUNT_DIR="$(os.paths snap-mount-dir)"
- su -p -c "$SNAP_MOUNT_DIR/bin/test-snapd-sh.sh -c 'true'" test
+ tests.session -u test exec sh -c "$SNAP_MOUNT_DIR/bin/test-snapd-sh.sh -c 'true'" test
# drop in canaries for both users
echo "hello versioned test-snapd-sh" > /root/snap/test-snapd-sh/current/canary.txt
diff --git a/tests/nested/core/core20-create-recovery/task.yaml b/tests/nested/core/core20-create-recovery/task.yaml
index 2778a67662..af0394a2ae 100644
--- a/tests/nested/core/core20-create-recovery/task.yaml
+++ b/tests/nested/core/core20-create-recovery/task.yaml
@@ -8,7 +8,8 @@ prepare: |
execute: |
echo "Create a recovery system with a typical recovery system label"
boot_id="$( tests.nested boot-id )"
- echo '{"action":"create-recovery-system","params":{"recovery-system-label":"1234"}}' | tests.nested exec sudo test-snapd-curl.curl -X POST --unix-socket /run/snapd.socket http://localhost/v2/debug > change.out
+ echo '{"action":"create-recovery-system","params":{"recovery-system-label":"1234"}}' | \
+ tests.nested exec sudo test-snapd-curl.curl -X POST -d @- --unix-socket /run/snapd.socket http://localhost/v2/debug > change.out
REMOTE_CHG_ID=$(jq -r .change < change.out)
tests.nested wait-for reboot "${boot_id}"
tests.nested exec sudo snap watch "${REMOTE_CHG_ID}"
@@ -23,7 +24,8 @@ execute: |
echo "Create a recovery system with an alternative recovery system label"
boot_id="$( tests.nested boot-id )"
- echo '{"action":"create-recovery-system","params":{"recovery-system-label":"1234-1"}}' | tests.nested exec sudo test-snapd-curl.curl -X POST --unix-socket /run/snapd.socket http://localhost/v2/debug > change.out
+ echo '{"action":"create-recovery-system","params":{"recovery-system-label":"1234-1"}}' | \
+ tests.nested exec sudo test-snapd-curl.curl -X POST -d @- --unix-socket /run/snapd.socket http://localhost/v2/debug > change.out
REMOTE_CHG_ID=$(jq -r .change < change.out)
tests.nested wait-for reboot "${boot_id}"
tests.nested exec sudo snap watch "${REMOTE_CHG_ID}"
diff --git a/tests/nested/core/extra-snaps-assertions/task.yaml b/tests/nested/core/extra-snaps-assertions/task.yaml
deleted file mode 100644
index bb4a3e2304..0000000000
--- a/tests/nested/core/extra-snaps-assertions/task.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-summary: create ubuntu-core image and execute the suite in a nested qemu instance
-
-systems: [ubuntu-18.04-64]
-
-execute: |
- echo "Wait for first boot to be done"
- tests.nested exec "retry --wait 1 -n 120 sh -c 'snap changes | MATCH \"Done.*Initialize system state\"'"
-
- echo "We have a model assertion"
- tests.nested exec "snap known model" | MATCH "series: 16"
-
- EXPRESSION="^core18 .* +latest/$NESTED_CORE_CHANNEL +canonical\\* +base"
- if [ "$NESTED_BUILD_SNAPD_FROM_CURRENT" = "true" ]; then
- EXPRESSION="^core18 .* +x1 .* base"
- fi
-
- echo "Make sure core has an actual revision"
- tests.nested exec "snap list --unicode=never" | MATCH "$EXPRESSION"
diff --git a/tests/nested/manual/grade-signed-above-testkeys-boot/defaults.yaml b/tests/nested/manual/core20-grade-signed-above-testkeys-boot/defaults.yaml
index 30292153b8..30292153b8 100644
--- a/tests/nested/manual/grade-signed-above-testkeys-boot/defaults.yaml
+++ b/tests/nested/manual/core20-grade-signed-above-testkeys-boot/defaults.yaml
diff --git a/tests/nested/manual/grade-signed-above-testkeys-boot/prepare-device b/tests/nested/manual/core20-grade-signed-above-testkeys-boot/prepare-device
index 357c0850fa..357c0850fa 100755
--- a/tests/nested/manual/grade-signed-above-testkeys-boot/prepare-device
+++ b/tests/nested/manual/core20-grade-signed-above-testkeys-boot/prepare-device
diff --git a/tests/nested/manual/grade-signed-above-testkeys-boot/task.yaml b/tests/nested/manual/core20-grade-signed-above-testkeys-boot/task.yaml
index 69b1cca28e..69b1cca28e 100644
--- a/tests/nested/manual/grade-signed-above-testkeys-boot/task.yaml
+++ b/tests/nested/manual/core20-grade-signed-above-testkeys-boot/task.yaml
diff --git a/tests/nested/manual/grade-signed-cloud-init-testkeys/defaults.yaml b/tests/nested/manual/core20-grade-signed-cloud-init-testkeys/defaults.yaml
index 6417309d65..6417309d65 100644
--- a/tests/nested/manual/grade-signed-cloud-init-testkeys/defaults.yaml
+++ b/tests/nested/manual/core20-grade-signed-cloud-init-testkeys/defaults.yaml
diff --git a/tests/nested/manual/grade-signed-cloud-init-testkeys/prepare-device b/tests/nested/manual/core20-grade-signed-cloud-init-testkeys/prepare-device
index 357c0850fa..357c0850fa 100755
--- a/tests/nested/manual/grade-signed-cloud-init-testkeys/prepare-device
+++ b/tests/nested/manual/core20-grade-signed-cloud-init-testkeys/prepare-device
diff --git a/tests/nested/manual/grade-signed-cloud-init-testkeys/task.yaml b/tests/nested/manual/core20-grade-signed-cloud-init-testkeys/task.yaml
index a930eb5b08..a930eb5b08 100644
--- a/tests/nested/manual/grade-signed-cloud-init-testkeys/task.yaml
+++ b/tests/nested/manual/core20-grade-signed-cloud-init-testkeys/task.yaml
diff --git a/tests/nested/manual/core20-new-snapd-does-not-break-old-initrd/task.yaml b/tests/nested/manual/core20-new-snapd-does-not-break-old-initrd/task.yaml
new file mode 100644
index 0000000000..9d32fba916
--- /dev/null
+++ b/tests/nested/manual/core20-new-snapd-does-not-break-old-initrd/task.yaml
@@ -0,0 +1,97 @@
+summary: verify that new snapd's do not break old snap-bootstrap/kernel initrds
+
+systems: [ubuntu-20.04-64]
+
+# we have two variants here:
+#
+# 1. we start with stable kernel + stable snapd -> refresh to new snapd
+# 2. we start with stable kernel + new snapd
+#
+# and then in both cases we then trigger a reseal operation and reboot to make
+# sure that the old snap-bootstrap/initrd in the stable kernel can still unlock
+# the encrypted partitions
+
+environment:
+ NESTED_CUSTOM_MODEL: $TESTSLIB/assertions/ubuntu-core-20-amd64.model
+
+ # don't bundle the snapd snap via nested.sh's machinery, instead we will
+ # side-load our snapd snap built from this branch into the image via
+ # extra-snaps since we still want to use MS keys and such for this image, and
+ # defining this to be true brings with it i.e. snakeoil keys in the OVMF
+ # firmware for example
+ NESTED_BUILD_SNAPD_FROM_CURRENT: false
+
+ # we want snaps from the stable channel by default with the exception of the
+ # snapd snap which we will conditionally repack as per NESTED_BUILD_SNAPD_FROM_CURRENT
+ NESTED_CORE_CHANNEL: stable
+
+ START_SNAPD_VERSION/startwithnew: new
+ START_SNAPD_VERSION/startwithold: old
+
+ NESTED_IMAGE_ID: uc20-breakages-testing-$START_SNAPD_VERSION
+
+ # all variants need encryption turned on
+ NESTED_ENABLE_TPM: true
+ NESTED_ENABLE_SECURE_BOOT: true
+
+ INITIAL_KERNEL_REV_URL: https://storage.googleapis.com/snapd-spread-tests/snaps/pc-kernel_838.snap
+
+prepare: |
+ # always build the snapd snap from this branch - on the new variant it gets
+ # put into the image, on the old variant it will be refreshed to
+ snap download --channel="latest/edge" snapd
+ "$TESTSTOOLS"/snaps-state repack_snapd_deb_into_snap snapd
+ mv snapd-from-deb.snap snapd-from-branch.snap
+
+ # on both variants we use a local, non-asserted version of the snapd snap
+ # for the startwithnew variant, we use the snapd from this branch, for the
+ # startwithold variant, we use the snapd from stable but unpack it to
+ # prevent auto-refreshes from happening which may affect the test setup
+ if [ "$START_SNAPD_VERSION" = "new" ]; then
+ mv snapd-from-branch.snap "$(tests.nested get extra-snaps-path)"
+ else
+ # TODO: download a specific version of snapd from a GCE bucket instead
+ snap download snapd --stable --basename=snapd-stable-store
+ unsquashfs -d snapd snapd-stable-store.snap
+ touch ./snapd/in-case-mksquashfs-becomes-deterministic-someday
+ sudo snap pack snapd --filename=snapd-stable.snap
+ mv snapd-stable.snap "$(tests.nested get extra-snaps-path)"
+ fi
+
+ # use a specific version of the kernel snap and thus initramfs that we know
+ # doesn't support v2 secboot keys
+ wget --quiet "$INITIAL_KERNEL_REV_URL"
+
+ # unpack it and repack it so it doesn't match any store assertions and thus
+ # won't be automatically refreshed behind our backs when we boot the VM
+ unsquashfs -d pc-kernel-snap pc-kernel_838.snap
+ touch ./pc-kernel-snap/in-case-mksquashfs-becomes-deterministic-someday
+ snap pack pc-kernel-snap --filename=pc-kernel.snap
+ mv pc-kernel.snap "$(tests.nested get extra-snaps-path)"
+
+ # download the new kernel to try and refresh to, triggering a reseal
+ snap download pc-kernel --channel=20/candidate --basename=new-kernel
+
+ # build the image and start the VM up
+ tests.nested build-image core
+ tests.nested create-vm core
+
+execute: |
+ # on the old variant, copy and install the new snapd to it
+ if [ "$START_SNAPD_VERSION" = "old" ]; then
+ tests.nested copy snapd-from-branch.snap
+ tests.nested exec "sudo snap install --dangerous snapd-from-branch.snap"
+ fi
+
+ # try a refresh to a new kernel revision which will trigger a reseal and then
+ # a reboot
+ tests.nested copy new-kernel.snap
+
+ boot_id="$( tests.nested boot-id )"
+ REMOTE_CHG_ID=$(tests.nested exec "sudo snap install --dangerous new-kernel.snap --no-wait")
+ tests.nested wait-for reboot "${boot_id}"
+ tests.nested exec sudo snap watch "${REMOTE_CHG_ID}"
+
+ tests.nested exec "snap changes" | tail -n +2 | awk '{print $2}' | NOMATCH Error
+
+ # TODO: also check transitioning to the recovery seed system too?
diff --git a/tests/nested/manual/refresh-revert-fundamentals/task.yaml b/tests/nested/manual/refresh-revert-fundamentals/task.yaml
index b2785c3bbf..5f049c5c45 100644
--- a/tests/nested/manual/refresh-revert-fundamentals/task.yaml
+++ b/tests/nested/manual/refresh-revert-fundamentals/task.yaml
@@ -11,16 +11,13 @@ environment:
NESTED_CORE_REFRESH_CHANNEL: edge
NESTED_BUILD_SNAPD_FROM_CURRENT: false
NESTED_USE_CLOUD_INIT: true
- # TODO:UC20: temporarily disable secure boot and encryption support. The
- # location of encryption keys has changed, thus the nested VM will not boot
- # until the kernel snap is rebuilt with snapd 2.48.
- NESTED_ENABLE_SECURE_BOOT: false
- NESTED_ENABLE_TPM: false
+ NESTED_ENABLE_SECURE_BOOT: true
+ NESTED_ENABLE_TPM: true
SNAP/kernel: pc-kernel
TRACK/kernel: 20
- SNAP/gadget: pc
+ SNAP/gadget: pc
TRACK/gadget: 20
SNAP/snapd: snapd
@@ -54,7 +51,7 @@ execute: |
TO_REV="$(tests.nested snap-rev "$SNAP" $TRACK/$NESTED_CORE_REFRESH_CHANNEL)"
tests.nested exec "snap list $SNAP" | MATCH "^${SNAP}.*${FROM_REV}.*${TRACK}/${NESTED_CORE_CHANNEL}.*"
-
+
echo "Refresh the snap $SNAP"
INITIAL_BOOT_ID=$(tests.nested boot-id)
REFRESH_ID=$(tests.nested exec "sudo snap refresh --no-wait --channel $NESTED_CORE_REFRESH_CHANNEL $SNAP")